/* -------------------------------------------------------------------------
 *
 * tablecmds.cpp
 *	  Commands for creating and altering table structures and settings
 *
 * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
 * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 * Portions Copyright (c) 2010-2012 Postgres-XC Development Group
 *
 *
 * IDENTIFICATION
 *	  src/gausskernel/optimizer/commands/tablecmds.cpp
 *
 * -------------------------------------------------------------------------
 */
#include "postgres.h"
#include "knl/knl_variable.h"

#include "access/cstore_delete.h"
#include "access/cstore_delta.h"
#include "access/cstore_insert.h"
#include "access/cstore_rewrite.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "access/reloptions.h"
#include "access/relscan.h"
#include "access/sysattr.h"
#include "access/transam.h"
#include "access/tuptoaster.h"
#include "access/visibilitymap.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "access/tableam.h"
#include "access/ustore/knl_uheap.h"
#include "access/ustore/knl_uscan.h"
#include "access/ustore/knl_undorequest.h"
#include "access/multixact.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/gs_matview.h"
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_description.h"
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_job.h"
#include "catalog/pg_job_proc.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_object.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_partition.h"
#include "catalog/pg_partition_fn.h"
#include "catalog/pg_hashbucket.h"
#include "catalog/pg_hashbucket_fn.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_synonym.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "catalog/pg_type_fn.h"
#include "catalog/pg_uid_fn.h"
#include "catalog/pg_rlspolicy.h"
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
#include "catalog/toasting.h"
#include "catalog/cstore_ctlg.h"
#include "catalog/storage_gtt.h"
#include "catalog/gs_encrypted_columns.h"
#include "catalog/gs_global_config.h"
#include "catalog/gs_matview.h"
#include "catalog/gs_db_privilege.h"
#include "catalog/namespace.h"
#include "commands/cluster.h"
#include "commands/comment.h"
#include "commands/dbcommands.h"
#include "commands/event_trigger.h"
#include "commands/defrem.h"
#include "commands/dbcommands.h"
#include "commands/sec_rls_cmds.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "commands/typecmds.h"
#include "commands/vacuum.h"
#include "commands/verify.h"
#include "commands/matview.h"
#include "commands/view.h"
#include "commands/view.h"
#include "executor/executor.h"
#include "executor/node/nodeModifyTable.h"
#include "foreign/fdwapi.h"
#include "foreign/foreign.h"
#include "gssignal/gs_signal.h"
#include "gs_policy/gs_policy_masking.h"
#include "gtm/gtm_client.h"
#include "miscadmin.h"
#include "tde_key_management/tde_key_manager.h"
#include "tde_key_management/tde_key_storage.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/parsenodes.h"
#include "optimizer/clauses.h"
#include "optimizer/planner.h"
#include "optimizer/var.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_oper.h"
#include "parser/parse_relation.h"
#include "parser/parse_type.h"
#include "parser/parse_utilcmd.h"
#include "parser/parser.h"
#include "pgxc/route.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "rewrite/rewriteRlsPolicy.h"
#include "rewrite/rewriteSupport.h"
#include "replication/slot.h"
#include "storage/buf/bufmgr.h"
#include "storage/freespace.h"
#include "storage/lmgr.h"
#include "storage/lock/lock.h"
#include "storage/page_compression.h"
#include "storage/predicate.h"
#include "storage/remote_read.h"
#include "storage/smgr/segment.h"
#include "storage/smgr/smgr.h"
#include "storage/tcap.h"
#include "streaming/streaming_catalog.h"
#include "tcop/utility.h"
#include "tcop/ddldeparse.h"
#include "utils/acl.h"
#include "utils/aiomem.h"
#include "utils/builtins.h"
#include "utils/extended_statistics.h"
#include "utils/fmgroids.h"
#include "utils/int8.h"
#include "utils/int16.h"
#include "utils/inval.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/partcache.h"
#include "utils/partitionmap.h"
#include "utils/partitionlocate.h"
#include "utils/partitionmap_gs.h"
#include "utils/partitionkey.h"
#include "utils/relcache.h"
#include "utils/sec_rls_utils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "access/heapam.h"
#include "utils/typcache.h"
#include "utils/numeric.h"
#include "utils/timestamp.h"
#include "catalog/pg_database.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_job.h"
#include "catalog/pg_user_status.h"
#include "gaussdb_version.h"
#include "workload/workload.h"
#include "utils/builtins.h"
#include "fmgr.h"
#include "pgstat.h"
#include "postmaster/rbcleaner.h"
#include "catalog/gs_collation.h"
#include "catalog/gs_dependencies_fn.h"
#ifdef ENABLE_MULTIPLE_NODES
#include "tsdb/utils/ts_relcache.h"
#include "tsdb/common/ts_tablecmds.h"
#include "tsdb/common/delta_ctlg.h"
#include "tsdb/utils/ctlg_utils.h"
#include "tsdb/utils/delta_utils.h"
#include "tsdb/utils/ctlg_utils.h"
#include "tsdb/cache/tags_cachemgr.h"
#include "tsdb/storage/delta_merge.h"
#include "tsdb/optimizer/policy.h"
#endif   /* ENABLE_MULTIPLE_NODES */

#ifdef PGXC
#include "pgxc/pgxc.h"
#include "pgxc/locator.h"
#include "access/gtm.h"
#include "catalog/pgxc_class.h"
#include "catalog/pgxc_slice.h"
#include "catalog/pgxc_node.h"
#include "commands/sequence.h"
#include "optimizer/pgxcship.h"
#include "pgxc/execRemote.h"
#include "pgxc/redistrib.h"
#include "pgxc/groupmgr.h"
#endif
#include "c.h"
#include "instruments/generate_report.h"
#include "gs_ledger/ledger_utils.h"
#include "gs_ledger/userchain.h"

#include "client_logic/client_logic.h"
#include "client_logic/cache.h"
#include "pgxc/redistrib.h"

#ifndef ENABLE_MULTIPLE_NODES
#include "utils/pl_package.h"
#endif

#ifdef ENABLE_HTAP
#include "access/htap/imcucache_mgr.h"
#include "access/htap/imcs_ctlg.h"
#endif

extern void vacuum_set_xid_limits(Relation rel, int64 freeze_min_age, int64 freeze_table_age, TransactionId* oldestXmin,
    TransactionId* freezeLimit, TransactionId* freezeTableLimit, MultiXactId* multiXactFrzLimit);

/*
 * ON COMMIT action list
 */
typedef struct OnCommitItem {
    Oid relid;               /* relid of relation */
    OnCommitAction oncommit; /* what to do at end of xact */

    /*
     * If this entry was created during the current transaction,
     * creating_subid is the ID of the creating subxact; if created in a prior
     * transaction, creating_subid is zero.  If deleted during the current
     * transaction, deleting_subid is the ID of the deleting subxact; if no
     * deletion request is pending, deleting_subid is zero.
     */
    SubTransactionId creating_subid;
    SubTransactionId deleting_subid;
} OnCommitItem;

static const char* ORCSupportOption[] = {"orientation", "compression", "version", "partial_cluster_rows"};

/* Context for check whether the targetEntry of view's querytree has changed */
typedef struct {
    Oid relid;
    int2 attnum;
    Query* query;
    Form_pg_attribute attForm;
} ViewQueryCheck_context;

/* Struct describing one new constraint to check in Phase 3 scan */
/* Note: new NOT NULL constraints are handled elsewhere */
typedef struct NewConstraint {
    char* name;         /* Constraint name, or NULL if none */
    ConstrType contype; /* CHECK or FOREIGN */
    Oid refrelid;       /* PK rel, if FOREIGN */
    Oid refindid;       /* OID of PK's index, if FOREIGN */
    Oid conid;          /* OID of pg_constraint entry, if FOREIGN */
    Node* qual;         /* Check expr or CONSTR_FOREIGN Constraint */
    List* qualstate;    /* Execution state for CHECK */
    bool isdisable;
} NewConstraint;

/*
 * Struct describing one new column value that needs to be computed during
 * Phase 3 copy (this could be either a new column with a non-null default, or
 * a column that we're changing the type of).  Columns without such an entry
 * are just copied from the old table during ATRewriteTable.  Note that the
 * expr is an expression over *old* table values.
 */
typedef struct NewColumnValue {
    AttrNumber attnum;    /* which column */
    Expr* expr;           /* expression to compute */
    ExprState* exprstate; /* execution state */
    bool is_generated;     /* is it a GENERATED expression? */
    bool is_autoinc;
    bool is_addloc;          /* is add column first or after */
    bool is_alter_using;          /* have alter type using clause */
    bool make_dml_change;
    AttrNumber newattnum;   /* is modify column first or after
                               -1 denote add;
                               0 denote modify without first|after;
                               > 0 denote modify with first|after */
    char *col_name;
    AttrNumber generate_attnum;
    bool is_updated;
} NewColumnValue;

/*
 * Error-reporting support for RemoveRelations
 */
struct dropmsgstrings {
    char kind;
    int nonexistent_code;
    const char* nonexistent_msg;
    const char* skipping_msg;
    const char* nota_msg;
    const char* drophint_msg;
};

static const struct dropmsgstrings dropmsgstringarray[] = {{RELKIND_RELATION,
                                                               ERRCODE_UNDEFINED_TABLE,
                                                               gettext_noop("table \"%s\" does not exist"),
                                                               gettext_noop("table \"%s\" does not exist, skipping"),
                                                               gettext_noop("\"%s\" is not a table"),
                                                               gettext_noop("Use DROP TABLE to remove a table.")},
    {RELKIND_SEQUENCE,
        ERRCODE_UNDEFINED_TABLE,
        gettext_noop("sequence \"%s\" does not exist"),
        gettext_noop("sequence \"%s\" does not exist, skipping"),
        gettext_noop("\"%s\" is not a sequence"),
        gettext_noop("Use DROP SEQUENCE to remove a sequence.")},
    {RELKIND_LARGE_SEQUENCE,
        ERRCODE_UNDEFINED_TABLE,
        gettext_noop("large sequence \"%s\" does not exist"),
        gettext_noop("large sequence \"%s\" does not exist, skipping"),
        gettext_noop("\"%s\" is not a large sequence"),
        gettext_noop("Use DROP LARGE SEQUENCE to remove a large sequence.")},
    {RELKIND_VIEW,
        ERRCODE_UNDEFINED_TABLE,
        gettext_noop("view \"%s\" does not exist"),
        gettext_noop("view \"%s\" does not exist, skipping"),
        gettext_noop("\"%s\" is not a view"),
        gettext_noop("Use DROP VIEW to remove a view.")},
    {RELKIND_CONTQUERY,
        ERRCODE_UNDEFINED_TABLE,
        gettext_noop("contview \"%s\" does not exist"),
        gettext_noop("contview \"%s\" does not exist, skipping"),
        gettext_noop("\"%s\" is not a view"),
        gettext_noop("Use DROP CONTVIEW to remove a contview.")},
    {RELKIND_MATVIEW,
        ERRCODE_UNDEFINED_TABLE,
        gettext_noop("materialized view \"%s\" does not exist"),
        gettext_noop("materialized view \"%s\" does not exist, skipping"),
        gettext_noop("\"%s\" is not a materialized view"),
        gettext_noop("Use DROP MATERIALIZED VIEW to remove a materialized view.")},
    {RELKIND_INDEX,
        ERRCODE_UNDEFINED_OBJECT,
        gettext_noop("index \"%s\" does not exist"),
        gettext_noop("index \"%s\" does not exist, skipping"),
        gettext_noop("\"%s\" is not an index"),
        gettext_noop("Use DROP INDEX to remove an index.")},
    {RELKIND_GLOBAL_INDEX,
        ERRCODE_UNDEFINED_OBJECT,
        gettext_noop("global partition index \"%s\" does not exist"),
        gettext_noop("global partition index \"%s\" does not exist, skipping"),
        gettext_noop("\"%s\" is not an global partition index"),
        gettext_noop("Use DROP INDEX to remove an global partition index.")},
    {RELKIND_COMPOSITE_TYPE,
        ERRCODE_UNDEFINED_OBJECT,
        gettext_noop("type \"%s\" does not exist"),
        gettext_noop("type \"%s\" does not exist, skipping"),
        gettext_noop("\"%s\" is not a type"),
        gettext_noop("Use DROP TYPE to remove a type.")},
    {RELKIND_FOREIGN_TABLE,
        ERRCODE_UNDEFINED_OBJECT,
        gettext_noop("foreign table \"%s\" does not exist"),
        gettext_noop("foreign table \"%s\" does not exist, skipping"),
        gettext_noop("\"%s\" is not a foreign table"),
        gettext_noop("Use DROP FOREIGN TABLE to remove a foreign table.")},
    {RELKIND_STREAM,
        ERRCODE_UNDEFINED_OBJECT,
        gettext_noop("stream \"%s\" does not exist"),
        gettext_noop("stream \"%s\" does not exist, skipping"),
        gettext_noop("\"%s\" is not a stream"),
        gettext_noop("Use DROP STREAM to remove a stream.")},
    {'\0', 0, NULL, NULL, NULL, NULL}};

struct DropRelationCallbackState {
    char relkind;
    Oid heapOid;
    bool concurrent;
};

// When merge partitions, if toast tables have repeat chunkid, replace it.
//
struct ChunkIdHashKey {
    Oid toastTableOid;
    Oid oldChunkId;
};

// Entry structures for the hash tables
//
struct OldToNewChunkIdMappingData {
    ChunkIdHashKey key;
    Oid newChunkId;
};

struct RenameTableNameData {
    char* schemaname;
    char* relname;
};

typedef OldToNewChunkIdMappingData* OldToNewChunkIdMapping;

/* Alter table target-type flags for ATSimplePermissions */
#define ATT_NULL 0x0000
#define ATT_TABLE 0x0001
#define ATT_VIEW 0x0002
#define ATT_INDEX 0x0004
#define ATT_COMPOSITE_TYPE 0x0008
#define ATT_FOREIGN_TABLE 0x0010
#define ATT_SEQUENCE 0x0020
#define ATT_MATVIEW  0x0040

#define DFS_SUPPORT_AT_CMD(cmd)                                                                              \
    ((cmd) == AT_AddNodeList || (cmd) == AT_SubCluster || (cmd) == AT_AddColumn || (cmd) == AT_DropColumn || \
        (cmd) == AT_AddStatistics || (cmd) == AT_DeleteStatistics || (cmd) == AT_AddConstraint ||            \
        (cmd) == AT_DropConstraint || (cmd) == AT_ColumnDefault || (cmd) == AT_ChangeOwner)

#define BASETABLE_SUPPORT_AT_CMD(cmd)                                                                           \
    ((cmd) == AT_SubCluster || (cmd) == AT_ChangeOwner || (cmd) == AT_SetRelOptions ||                          \
        (cmd) == AT_ResetRelOptions || (cmd) == AT_ReplaceRelOptions ||                                         \
        (cmd) == AT_EnableRls || (cmd) == AT_DisableRls || (cmd) == AT_ForceRls || (cmd) == AT_NoForceRls ||    \
        (cmd) == AT_SetNotNull || (cmd) == AT_AddIndex || (cmd) == AT_AddIndexConstraint ||                     \
        (cmd) == AT_ValidateConstraint || (cmd) == AT_NOValidateConstraint || (cmd) == AT_DISABLE_ValidateConstraint ||     \
        (cmd) == AT_DISABLE_NOValidateConstraint || (cmd) == AT_AddConstraint || (cmd) == AT_DropConstraint ||           \
        (cmd) == AT_EncryptionKeyRotation)

#define MATVIEW_SUPPORT_AT_CMD(cmd) ((cmd) == AT_SubCluster || (cmd) == AT_ChangeOwner)

#define MLOG_MAP_SUPPORT_AT_CMD(cmd) ((cmd) == AT_SubCluster)

#define HDFS_TBLSPC_SUPPORT_CREATE_LOGIC_OBJECT(kind)                                           \
    ((kind) == RELKIND_VIEW || (kind) == RELKIND_FOREIGN_TABLE || (kind) == RELKIND_SEQUENCE || \
        (kind) == RELKIND_COMPOSITE_TYPE || (kind) == RELKIND_STREAM || (kind) == RELKIND_CONTQUERY)

#define PARTITION_DDL_CMD(cmd)                                                          \
    ((cmd) == AT_AddPartition || (cmd) == AT_AddSubPartition ||                         \
        (cmd) == AT_DropPartition || (cmd) == AT_DropSubPartition ||                    \
        (cmd) == AT_ExchangePartition ||                                                \
        (cmd) == AT_TruncatePartition || (cmd) == AT_TruncateSubPartition ||            \
        (cmd) == AT_SetPartitionTableSpace ||                                           \
        (cmd) == AT_SplitPartition || (cmd) == AT_SplitSubPartition ||                  \
        (cmd) == AT_MergePartition)

static bool CStoreSupportATCmd(AlterTableType cmdtype);
static bool CStoreSupportConstraint(Constraint* cons);
static List* MergeAttributes(
    List* schema, List* supers, char relpersistence, List** supOids, List** supconstr, int* supOidCount);
static bool MergeCheckConstraint(List* constraints, char* name, Node* expr);
static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
static void StoreCatalogInheritance(Oid relationId, List* supers);
static void StoreCatalogInheritance1(Oid relationId, Oid parentOid, int16 seqNumber, Relation inhRelation);
static int findAttrByName(const char* attributeName, List* schema);
static void AlterIndexNamespaces(
    Relation classRel, Relation rel, Oid oldNspOid, Oid newNspOid, ObjectAddresses* objsMoved);
static void AlterSeqNamespaces(
    Relation classRel, Relation rel, Oid oldNspOid, Oid newNspOid, ObjectAddresses* objsMoved, LOCKMODE lockmode);
static ObjectAddress ATExecValidateConstraint(Relation rel, char* constrName, bool recurse, bool recursing, LOCKMODE lockmode);
static void ExecIndConstaint(Oid indexoid, bool enable);
static void ATExecValidateAbilityConstraint(Relation rel, char* constrName, bool recurse, bool recursing, bool enable, bool valid, LOCKMODE lockmode);
static int transformColumnNameList(Oid relId, List* colList, int16* attnums, Oid* atttypids);
static int transformFkeyGetPrimaryKey(
    Relation pkrel, Oid* indexOid, List** attnamelist, int16* attnums, Oid* atttypids, Oid* opclasses);
static Oid transformFkeyCheckAttrs(Relation pkrel, int numattrs, int16* attnums, Oid* opclasses);
static void checkFkeyPermissions(Relation rel, int16* attnums, int natts);
static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId, Oid* funcid);
static void validateCheckConstraint(Relation rel, HeapTuple constrtup);
static void validateCheckConstraintForBucket(Relation rel, Partition part, HeapTuple constrtup);
static void validateForeignKeyConstraint(char* conname, Relation rel, Relation pkrel, Oid pkindOid, Oid constraintOid);
static void createForeignKeyTriggers(
    Relation rel, Oid refRelOid, Constraint* fkconstraint, Oid constraintOid, Oid indexOid);
static void ATController(AlterTableStmt *parsetree, Relation rel, List* cmds, bool recurse, LOCKMODE lockmode, bool fromReplace);
static bool ATCheckLedgerTableCmd(Relation rel, AlterTableCmd* cmd);
static void ATPrepCmd(List** wqueue, Relation rel, AlterTableCmd* cmd, bool recurse, bool recursing, LOCKMODE lockmode,
    bool isDeltaTable = false);
static void ATRewriteCatalogs(List** wqueue, LOCKMODE lockmode, bool fromReplace);
static void ATExecCmd(List** wqueue, AlteredTableInfo* tab, Relation rel, AlterTableCmd* cmd, LOCKMODE lockmode, bool fromReplace);
static void ATRewriteTables(AlterTableStmt *parsetree, List** wqueue, LOCKMODE lockmode);
static void ATRewriteTable(AlteredTableInfo* tab, Relation oldrel, Relation newrel);
static void ATCStoreRewriteTable(AlteredTableInfo* tab, Relation heapRel, LOCKMODE lockMode, Oid targetTblspc);
static void ATCStoreRewritePartition(AlteredTableInfo* tab, LOCKMODE lockMode);
static void at_timeseries_check(Relation rel, AlterTableCmd* cmd);

#ifndef ENABLE_MULTIPLE_NODES
static void ATOnlyCheckCStoreTable(const AlteredTableInfo* tab, Relation rel);
#endif

static void PSortChangeTableSpace(Oid psortOid, Oid newTableSpace, LOCKMODE lockmode);
static void ForbidToRewriteOrTestCstoreIndex(AlteredTableInfo* tab);
static inline void ChangeTableSpaceForDeltaRelation(Oid deltaOid, Oid targetTableSpace, LOCKMODE lockmode);
static inline void ChangeTableSpaceForCudescRelation(
    Oid cudescIdxOid, Oid cudescOid, Oid targetTableSpace, LOCKMODE lockmode);

static void ExecRewriteRowTable(AlteredTableInfo*, Oid, LOCKMODE);
static void ExecRewriteRowPartitionedTable(AlteredTableInfo*, Oid, LOCKMODE);
static void ExecRewriteCStoreTable(AlteredTableInfo*, Oid, LOCKMODE);
static void ExecRewriteCStorePartitionedTable(AlteredTableInfo*, Oid, LOCKMODE);
static void ExecOnlyTestRowTable(AlteredTableInfo*);
static void ExecOnlyTestRowPartitionedTable(AlteredTableInfo*);
/**
 * @Description: Only check the validity of existing data, because of some altering operators.
 * For example, the query "alter table ... add column col data type not null" contains
 * "NOT NULL" constraint, if the relation has no data, the query will be executed successfully,
 * otherwise get a fail result.
 * @in tab, The AlteredTableInfo struct.
 * @return None.
 */
static void ExecOnlyTestCStoreTable(AlteredTableInfo*);
static void ExecOnlyTestCStorePartitionedTable(AlteredTableInfo*);
static void ExecChangeTableSpaceForRowTable(AlteredTableInfo*, LOCKMODE);
static void ExecChangeTableSpaceForRowPartition(AlteredTableInfo*, LOCKMODE);
static void ExecChangeTableSpaceForCStoreTable(AlteredTableInfo*, LOCKMODE);
static void ExecChangeTableSpaceForCStorePartition(AlteredTableInfo*, LOCKMODE);

static int GetAfterColumnAttnum(Oid attrelid, const char *after_name);
static Node *UpdateVarattnoAfterAddColumn(Node *node, int startattnum, int endattnum, bool is_increase);
static void UpdatePgStatisticFirstAfter(Relation rel, int startattnum, int endattnum, bool is_increase);
static void UpdatePgStatisticExtFirstAfter(Relation rel, int startattnum, int endattnum, bool is_increase);
static void UpdatePgDescriptionFirstAfter(Relation rel, int startattnum, int endattnum, bool is_increase);
static void UpdatePgAttributeFirstAfter(Relation attr_rel, Oid attrelid, int startattnum, int endattnum,
    bool is_increase);
static void UpdatePgIndexFirstAfter(Relation rel, int startattnum, int endattnum, bool is_increase);
static void UpdatePgConstraintFirstAfter(Relation rel, int startattnum, int endattnum, bool is_increase);
static void UpdatePgConstraintConfkeyFirstAfter(Relation rel, int startattnum, int endattnum,
    bool is_increase);
static void UpdateGenerateColFirstAfter(Relation rel, int startattnum, int endattnum, bool is_increase);
static void UpdateIndexFirstAfter(Relation rel);
static void UpdatePgAttrdefFirstAfter(Relation rel, int startattnum, int endattnum, bool is_increase);
static void UpdatePgDependFirstAfter(Relation rel, int startattnum, int endattnum, bool is_increase);
static void UpdatePgPartitionFirstAfter(Relation rel, int startattnum, int endattnum, bool is_increase,
    bool is_modified, bool *hasPartition);
static void UpdatePgTriggerFirstAfter(Relation rel, int startattnum, int endattnum, bool is_increase);
static void UpdatePgRlspolicyFirstAfter(Relation rel, int startattnum, int endattnum, bool is_increase);
static ViewInfoForAdd *GetViewInfoFirstAfter(const char *rel_name, Oid objid, bool keep_star);

static void UpdateDependRefobjsubidFirstAfter(Relation rel, Oid myrelid, int curattnum, int newattnum,
    bool *has_depend);
static void UpdateDependRefobjsubidToNewattnum(Relation rel, Oid myrelid, int curattnum, int newattnum);
static void UpdateAttrdefAdnumFirstAfter(Relation rel, Oid myrelid, int curattnum, int newattnum,
    bool *has_default);
static void UpdatePartitionPartkeyFirstAfter(Oid myrelid, int curattnum, int newattnum);
static void AlterColumnToFirstAfter(AlteredTableInfo* tab, Relation rel, AlterTableCmd* cmd,
    int curattnum);
static bool CheckIndexIsConstraint(Relation dep_rel, Oid objid, Oid *refobjid);

static AlteredTableInfo* ATGetQueueEntry(List** wqueue, Relation rel, bool isDeltaTable = false);
static void ATSimplePermissions(Relation rel, int allowed_targets);
static void ATWrongRelkindError(Relation rel, int allowed_targets);
static void ATSimpleRecursion(List** wqueue, Relation rel, AlterTableCmd* cmd, bool recurse, LOCKMODE lockmode);
static void ATTypedTableRecursion(List** wqueue, Relation rel, AlterTableCmd* cmd, LOCKMODE lockmode);
static List* find_typed_table_dependencies(Oid typeOid, const char* typname, DropBehavior behavior);
static void ATPrepAddColumn(List** wqueue, AlteredTableInfo* tab, Relation rel, bool recurse,
    bool recursing, AlterTableCmd* cmd, LOCKMODE lockmode);
static ObjectAddress ATExecAddColumn(List** wqueue, AlteredTableInfo* tab, Relation rel, ColumnDef* colDef, bool isOid,
    bool recurse, bool recursing, bool is_first, char *after_name, LOCKMODE lockmode);
static void check_for_column_name_collision(Relation rel, const char* colname);
static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
static void ATPrepAddOids(List** wqueue, Relation rel, bool recurse, AlterTableCmd* cmd, LOCKMODE lockmode);
static ObjectAddress ATExecDropNotNull(Relation rel, const char* colName, LOCKMODE lockmode);
static ObjectAddress ATExecSetNotNull(AlteredTableInfo* tab, Relation rel, const char* colName, LOCKMODE lockmode);
static ObjectAddress ATExecColumnDefault(Relation rel, const char* colName, Node* newDefault, LOCKMODE lockmode);
static void ATPrepSetStatistics(Relation rel);
static ObjectAddress ATExecSetStatistics(
    Relation rel, const char* colName, Node* newValue, AlterTableStatProperty additional_property, LOCKMODE lockmode);
static void ATExecAddStatistics(Relation rel, Node* def, LOCKMODE lockmode);
static void ATExecDeleteStatistics(Relation rel, Node* def, LOCKMODE lockmode);
static ObjectAddress ATExecSetOptions(Relation rel, const char* colName, Node* options, bool isReset, LOCKMODE lockmode);
static ObjectAddress ATExecSetStorage(Relation rel, const char* colName, Node* newValue, LOCKMODE lockmode);
static void ATPrepCheckDefault(Node* node);
static bool CheckLastColumn(Relation rel, AttrNumber attrnum);
static void ATPrepDropColumn(
    List** wqueue, Relation rel, bool recurse, bool recursing, AlterTableCmd* cmd, LOCKMODE lockmode);
static ObjectAddress ATExecDropColumn(List** wqueue, Relation rel, const char* colName, DropBehavior behavior, bool recurse,
    bool recursing, bool missing_ok, LOCKMODE lockmode, bool fromReplace);
static ObjectAddress ATExecAddIndex(AlteredTableInfo* tab, Relation rel, IndexStmt* stmt, bool is_rebuild, LOCKMODE lockmode);
static ObjectAddress ATExecAddConstraint(List** wqueue, AlteredTableInfo* tab, Relation rel, Constraint* newConstraint,
    bool recurse, bool is_readd, LOCKMODE lockmode);
static ObjectAddress ATExecAddIndexConstraint(AlteredTableInfo* tab, Relation rel, IndexStmt* stmt, LOCKMODE lockmode);
static ObjectAddress ATAddCheckConstraint(List** wqueue, AlteredTableInfo* tab, Relation rel, Constraint* constr, bool recurse,
    bool recursing, bool is_readd, LOCKMODE lockmode);
static ObjectAddress ATAddForeignKeyConstraint(AlteredTableInfo* tab, Relation rel, Constraint* fkconstraint, LOCKMODE lockmode);
static void ATExecDropConstraint(Relation rel, const char* constrName, DropBehavior behavior, bool recurse,
    bool recursing, bool missing_ok, LOCKMODE lockmode);
static void ATPrepAlterColumnType(List** wqueue, AlteredTableInfo* tab, Relation rel, bool recurse, bool recursing,
    AlterTableCmd* cmd, LOCKMODE lockmode);
static void ATPrepAlterModifyColumn(List** wqueue, AlteredTableInfo* tab, Relation rel, bool recurse,
    bool recursing, AlterTableCmd* cmd, LOCKMODE lockmode);
static bool ATColumnChangeRequiresRewrite(Node* expr, AttrNumber varattno);
static ObjectAddress ATExecAlterColumnType(AlteredTableInfo* tab, Relation rel, AlterTableCmd* cmd, LOCKMODE lockmode);
static void ATExecAlterModifyColumn(AlteredTableInfo* tab, Relation rel, AlterTableCmd* cmd);
static ObjectAddress ATExecAlterColumnGenericOptions(Relation rel, const char* colName, List* options, LOCKMODE lockmode);
static void ATPostAlterTypeCleanup(List** wqueue, AlteredTableInfo* tab, LOCKMODE lockmode);
static void ATPostAlterTypeParse(
    Oid oldId, Oid oldRelId, Oid refRelId, const char* cmd, List** wqueue, LOCKMODE lockmode, bool rewrite);
void TryReuseIndex(Oid oldId, IndexStmt* stmt);

void tryReusePartedIndex(Oid oldId, IndexStmt* stmt, Relation rel);

static void TryReuseForeignKey(Oid oldId, Constraint* con);
static void change_owner_fix_column_acls(Oid relationOid, Oid oldOwnerId, Oid newOwnerId);
static void change_owner_recurse_to_sequences(Oid relationOid, Oid newOwnerId, LOCKMODE lockmode);
static ObjectAddress ATExecClusterOn(Relation rel, const char* indexName, LOCKMODE lockmode);
static void ATExecDropCluster(Relation rel, LOCKMODE lockmode);
static void ATPrepSetTableSpace(AlteredTableInfo* tab, Relation rel, const char* tablespacename, LOCKMODE lockmode);
static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
static void ATExecSetRelOptionsToast(Oid toastid, List* defList, AlterTableType operation, LOCKMODE lockmode);
static void ATExecSetTableSpaceForPartitionP2(AlteredTableInfo* tab, Relation rel, Node* partition);
static void ATExecSetTableSpaceForPartitionP3(Oid tableOid, Oid partOid, Oid newTableSpace, LOCKMODE lockmode);
static void atexecset_table_space(Relation rel, Oid newTableSpace, Oid newrelfilenode);
static void ATExecSetRelOptions(Relation rel, List* defList, AlterTableType operation, 
    LOCKMODE lockmode, bool innerset = false, AlteredTableInfo* tab = NULL);
static void ATExecEnableDisableTrigger(
    Relation rel, const char* trigname, char fires_when, bool skip_system, LOCKMODE lockmode);
static void ATExecEnableDisableRule(Relation rel, const char* rulename, char fires_when, LOCKMODE lockmode);
static void ATPrepAddInherit(Relation child_rel);
static ObjectAddress ATExecAddInherit(Relation child_rel, RangeVar* parent, LOCKMODE lockmode);
static ObjectAddress ATExecDropInherit(Relation rel, RangeVar* parent, LOCKMODE lockmode);
static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid);
static ObjectAddress ATExecAddOf(Relation rel, const TypeName* ofTypename, LOCKMODE lockmode);
static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt* stmt, LOCKMODE lockmode);
static void ATExecGenericOptions(Relation rel, List* options);
static void ATExecSetCompress(Relation rel, const char* cmprsId);
#ifdef PGXC
static void AtExecDistributeBy(Relation rel, DistributeBy* options);
static void AtExecSubCluster(Relation rel, PGXCSubCluster* options);
static void AtExecAddNode(Relation rel, List* options);
static void AtExecDeleteNode(Relation rel, List* options);
static void AtExecCopySlice(CatCList* sliceList, Oid tabOid, Relation pgxcSliceRel);
static void AtExecUpdateSliceLike(Relation rel, const RangeVar* refTableName);
static void ATCheckCmd(Relation rel, AlterTableCmd* cmd);
static void sqlcmd_alter_exec_set_charsetcollate(Relation rel, CharsetCollateOptions* cc, LOCKMODE lockmode);
static void sqlcmd_alter_prep_convert_charset(AlteredTableInfo* tab, Relation rel,
                                              AlterTableCmd* cmd, LOCKMODE lockmode);
static void sqlcmd_alter_exec_convert_charset(AlteredTableInfo* tab, Relation rel,
                                              CharsetCollateOptions* cc, LOCKMODE lockmode);
static DFSFileType getSetFormatNewFormat(AlterTableCmd* cmd);
static bool checkColumnTypeIsBytea(Relation rel);
static DFSFileType getFormatByDefElem(DefElem* opt);
static DFSFileType getFormatByName(char* format);
static RedistribState* BuildRedistribCommands(Oid relid, List* subCmds);
static Oid* delete_node_list(Oid* old_oids, int old_num, Oid* del_oids, int del_num, int* new_num);
static Oid* add_node_list(Oid* old_oids, int old_num, Oid* add_oids, int add_num, int* new_num);
#endif

static void copy_relation_data(Relation rel, SMgrRelation* dstptr, ForkNumber forkNum, char relpersistence);
static void mergeHeapBlock(Relation src, Relation dest, ForkNumber forkNum, char relpersistence, BlockNumber srcBlocks,
    BlockNumber destBlocks, TupleDesc srcTupleDesc, Oid srcToastOid, Oid destToastOid, HTAB* chunkIdHashTable,
    bool destHasFSM);
static void mergeVMBlock(Relation src, Relation dest, BlockNumber srcHeapBlocks, BlockNumber destHeapBloks);
static const char* storage_name(char c);

static void RangeVarCallbackForDropRelation(
    const RangeVar* rel, Oid relOid, Oid oldRelOid, bool target_is_partition, void* arg);
static void RangeVarCallbackForAlterRelation(
    const RangeVar* rv, Oid relid, Oid oldrelid, bool target_is_partition, void* arg);

static bool isQueryUsingTempRelation_walker(Node *node, void *context);
static bool CheckRangePartitionKeyType(Oid typoid);
static void CheckRangePartitionKeyType(FormData_pg_attribute* attrs, List* pos);

static bool CheckListPartitionKeyType(Oid typoid);
static void CheckListPartitionKeyType(FormData_pg_attribute* attrs, List* pos);

static bool CheckHashPartitionKeyType(Oid typoid);
static void CheckHashPartitionKeyType(FormData_pg_attribute* attrs, List* pos);

static void CheckIntervalPartitionKeyType(FormData_pg_attribute* attrs, List* pos);
static void CheckIntervalValue(
    const FormData_pg_attribute* attrs, const List* pos, const IntervalPartitionDefState* intervalPartDef);
static void CheckPartitionTablespace(const char* spcname, Oid owner);
static bool ConfirmTypeInfo(Oid* target_oid, int* target_mod, Const* src, Form_pg_attribute attrs, bool isinterval);

static void ATPrepAddPartition(Relation rel);
static void ATPrepAddSubPartition(Relation rel);
static void ATPrepDropPartition(Relation rel);
static void ATPrepDropSubPartition(Relation rel);
static void ATPrepUnusableIndexPartition(Relation rel);
static void ATPrepUnusableAllIndexOnPartition(Relation rel);
static void ATExecAddPartition(Relation rel, AddPartitionState *partState);
static void ATExecAddSubPartition(Relation rel, AddSubPartitionState *subpartState);
static void CheckForAddPartition(Relation rel, List *partDefStateList);
static void CheckForAddSubPartition(Relation rel, Relation partrel, List *subpartDefStateList);
static void CheckTablespaceForAddPartition(Relation rel, List *partDefStateList);
static void CheckPartitionNameConflictForAddPartition(List *newPartitionNameList, List *existingPartitionNameList);
static void CheckPartitionValueConflictForAddPartition(Relation rel, Node *partDefState, bool partkeyIsFunc = false);
static void CheckSubpartitionForAddPartition(Relation rel, Node *partDefState);
static void ATExecDropPartition(Relation rel, AlterTableCmd *cmd);
static void ATExecDropSubPartition(Relation rel, AlterTableCmd *cmd);
static Oid GetPartOidByATcmd(Relation rel, AlterTableCmd *cmd, const char *command);
static Oid GetSubpartOidByATcmd(Relation rel, AlterTableCmd *cmd, Oid *partOid, const char *command);

static void ATExecUnusableIndexPartition(Relation rel, const char* partition_name);
static void ATExecUnusableIndex(Relation rel, bool needChangeCsn);
static void ATUnusableGlobalIndex(Relation rel);
static void ATExecUnusableAllIndexOnPartition(Relation rel, const char* partition_name);
static void ATExecEnableIndex(Relation rel, bool enable);
static void ATExecVisibleIndex(Relation rel, char* index_name, bool visible);
static void ATExecVisibleIndexDirect(Relation rel, bool visible);
static void ATExecModifyRowMovement(Relation rel, bool rowMovement);
static void ATExecTruncatePartition(Relation rel, AlterTableCmd* cmd);
static void ATExecTruncateSubPartition(Relation rel, AlterTableCmd* cmd);
static void checkStorageTypeForExchange(Relation partTableRel, Relation ordTableRel);
static void checkColStoreForExchange(Relation partTableRel, Relation ordTableRel);
static void ATExecExchangePartition(Relation partTableRel, AlterTableCmd* cmd);
static void UpdatePrevIntervalPartToRange(
    Relation srcRel, Relation pgPartition, int srcPartIndex, const char* briefCmd);
static void ATExecMergePartition(Relation partTableRel, AlterTableCmd* cmd);
static void ATExecAddTblIntoCBI(Relation idxRel, const AddTableIntoCBIState* state);
static void checkCompressForExchange(Relation partTableRel, Relation ordTableRel);
static void checkColumnForExchange(Relation partTableRel, Relation ordTableRel);
static void checkConstraintForExchange(Relation partTableRel, Relation ordTableRel);
/**
 * @Description: Get the all constraint for specified table.
 * @in relOid, the specified table oid.
 * @in conType, the constraint type, default value is invalid.
 * @return return constraint list.
 */
static List* getConstraintList(Oid relOid, char conType = CONSTRAINT_INVALID);
static void freeConstraintList(List* list);

/**
 * @Description: Whether or not the column has partial cluster key.
 * @in rel, One relation.
 * @in attNum, Represnet the attribute number.
 * @return If exits partial cluster key in the column, return true,
 * otherwise return false.
 */
static bool colHasPartialClusterKey(Relation rel, AttrNumber attNum);

static void checkDistributeForExchange(Relation partTableRel, Relation ordTableRel);
static void checkIndexForExchange(
    Relation partTableRel, Oid partOid, Relation ordTableRel, List** partIndexList, List** ordIndexList);
static void checkValidationForExchange(Relation partTableRel, Relation ordTableRel, Oid partOid, bool exchangeVerbose);

static void finishIndexSwap(List* partIndexList, List* ordIndexList);
static Oid getPartitionOid(Relation partTableRel, const char* partName, Node* rangePartDef);
static void ATExecSplitPartition(Relation partTableRel, AlterTableCmd* cmd);
static void ATExecSplitSubPartition(Relation partTableRel, AlterTableCmd* cmd);
static void checkSplitPointForSplit(SplitPartitionState* splitPart, Relation partTableRel, int srcPartIndex);
static void checkDestPartitionNameForSplit(Oid partTableOid, List* partDefList);
static List* getDestPartBoundaryList(Relation partTableRel, List* destPartDefList, List** listForFree);
static void freeDestPartBoundaryList(List* list1, List* list2);
static char* GenTemporaryPartitionName(Relation partTableRel, int sequence = 0);
static Oid AddTemporaryPartition(Relation partTableRel, Node* partDef);
static void AlterPartitionedSetWaitCleanGPI(bool alterGPI, Relation partTableRel, Oid partOid);
static void AlterSubPartitionedSetWaitCleanGPI(bool alterGPI, Relation partTableRel, Oid partOid, Oid subPartOid);
static Oid AddTemporaryRangePartitionForAlterPartitions(const AlterTableCmd* cmd, Relation partTableRel,
    int sequence, bool* renameTargetPart);
static Oid AddTemporaryPartitionForAlterPartitions(const AlterTableCmd* cmd, Relation rel,
    Oid srcPartOid, bool* renameTargetPart);
static void ExchangePartitionWithGPI(const AlterTableCmd* cmd, Relation partTableRel, Oid srcPartOid,
    TransactionId frozenXid, MultiXactId multiXid);
static void fastAddPartition(Relation partTableRel, List* destPartDefList, List** newPartOidList);
static void FastAddListSubPartition(Relation rel, List* destPartDefList, Oid partOid, List** newPartOidList);
static void FastAddRangeSubPartition(Relation rel, List* destPartDefList, Oid partOid, List** newPartOidList);
static void readTuplesAndInsert(Relation tempTableRel, Relation partTableRel);
static Oid createTempTableForPartition(Relation partTableRel, Partition part);
static void ATPrepEnableRowMovement(Relation rel);
static void ATPrepDisableRowMovement(Relation rel);
static void ATPrepTruncatePartition(Relation rel);
static void ATPrepTruncateSubPartition(Relation rel);
static void ATPrepExchangePartition(Relation rel);
static void ATPrepMergePartition(Relation rel);
static void ATPrepSplitPartition(Relation rel);
static void ATPrepSplitSubPartition(Relation rel);
static void ATPrepResetPartitionno(Relation rel);
static void ATExecResetPartitionno(Relation rel);
#ifdef ENABLE_HTAP
static void ATExecIMCSTORED(Relation rel, List* colList);
static void ATExecUNIMCSTORED(Relation rel);
static void ATExecModifyPartitionIMCSTORED(Relation rel, const char* partName, List* colList);
static void ATExecModifyPartitionUNIMCSTORED(Relation rel, const char* partName);
#endif
static void replaceRepeatChunkId(HTAB* chunkIdHashTable, List* srcPartToastRels);
static bool checkChunkIdRepeat(List* srcPartToastRels, int index, Oid chunkId);
static void addCudescTableForNewPartition(Relation relation, Oid newPartId);
static void addDeltaTableForNewPartition(Relation relation, Oid newPartId);
static bool OptionSupportedByORCRelation(const char* option);
static void checkObjectCreatedinHDFSTblspc(CreateStmt* stmt, char relkind);
/**
 * @Description: Previous check whether the object may be created.
 * @in stmt, the object struct.
 * @in dfsTablespace, whether is a HDFS tablespace.
 */
static void PreCheckCreatedObj(CreateStmt* stmt, bool dfsTablespace, char relKind);
static List* InitDfsOptions(List* options);
static void validateDfsTableDef(CreateStmt* stmt, bool isDfsTbl);
static void simple_delete_redis_tuples(Relation rel, Oid partOid);
static void ResetPartsRedisCtidRelOptions(Relation rel);
static void ResetOnePartRedisCtidRelOptions(Relation rel, Oid part_oid);
static void ResetRelRedisCtidInfo(Relation rel, Oid part_oid, HeapTuple tuple, Oid pgcat_oid, Datum* repl_val,
    const bool* repl_null, const bool* repl_repl);
static void ResetRelRedisCtidRelOptions(
    Relation rel, Oid part_oid, int cat_id, int att_num, int att_inx, Oid pgcat_oid);
static bool WLMRelationCanTruncate(Relation rel);
static OnCommitAction GttOncommitOption(const List *options);
static void ATCheckDuplicateColumn(const AlterTableCmd* cmd, const List* tabCmds);
static void ATCheckNotNullConstr(const AlterTableCmd* cmd, const AlteredTableInfo* tab);
static void DelDependencONDataType(Relation rel, Relation depRel, const Form_pg_attribute attTup);
static void ATExecEncryptionKeyRotation(Relation rel, LOCKMODE lockmode);
static void CopyTempAutoIncrement(Relation oldrel, Relation newrel);
static void ATAlterCheckModifiyColumnRepeatedly(const AlterTableCmd* cmd, const List* tab_cmds);
static int128 EvaluateAutoIncrement(Relation rel, TupleDesc desc, AttrNumber attnum, Datum* value, bool* is_null);
static void SetRelAutoIncrement(Relation rel, TupleDesc desc, int128 autoinc);
static Node* RecookAutoincAttrDefault(Relation rel, int attrno, Oid targettype, int targettypmod);
static void check_unsupported_charset_for_column(Oid collation, const char* col_name);
static void AlterTableNamespaceDependentProcess(Relation classRel ,Relation rel, Oid oldNspOid,
                                                Oid nspOid, ObjectAddresses* objsMoved, char* newrelname);


static inline void validate_relation_kind(Relation r)
{
	if (r->rd_rel->relkind == RELKIND_INDEX ||
		// r->rd_rel->relkind == RELKIND_PARTITIONED_INDEX ||
		r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
		elog(ERROR,
				"cannot open relation \"%s\"",
						RelationGetRelationName(r));
}

/* ----------------
 *		table_open - open a table relation by relation OID
 *
 *		This is essentially relation_open plus check that the relation
 *		is not an index nor a composite type.  (The caller should also
 *		check that it's not a view or foreign table before assuming it has
 *		storage.)
 * ----------------
 */
Relation table_open(Oid relationId, LOCKMODE lockmode)
{
	Relation	r;

	r = relation_open(relationId, lockmode);

	validate_relation_kind(r);

	return r;
}

inline static bool CStoreSupportATCmd(AlterTableType cmdtype)
{
    bool ret = false;
    switch (cmdtype) {
        case AT_AddPartition:
        case AT_ExchangePartition:
        case AT_TruncatePartition:
        case AT_DropPartition:
        case AT_ResetPartitionno:
        case AT_AddConstraint:
        case AT_DropConstraint:
        case AT_AddNodeList:
        case AT_DeleteNodeList:
        case AT_SubCluster:
        case AT_UpdateSliceLike:
        case AT_AddColumn:
        case AT_DropColumn:
        case AT_AlterColumnType:
        case AT_ColumnDefault:
        case AT_SetStatistics:
        case AT_AddStatistics:
        case AT_SET_COMPRESS:
        case AT_DeleteStatistics:
        case AT_SetTableSpace:
        case AT_SetPartitionTableSpace:
        case AT_SetOptions:
        case AT_ResetOptions:
        case AT_SetStorage:
        case AT_SetRelOptions:
        case AT_ResetRelOptions:
        case AT_MergePartition:
        case AT_ChangeOwner:
        case AT_EnableRls:
        case AT_DisableRls:
        case AT_ForceRls:
        case AT_NoForceRls:
        case AT_EncryptionKeyRotation:
#ifndef ENABLE_MULTIPLE_NODES
        case AT_AddIndex:
        case AT_AddIndexConstraint:
#endif
        case AT_VisibleIndex:
        case AT_InvisibleIndex:
        case AT_InvisibleIndexDirect:
        case AT_VisibleIndexDirect:
        case AT_DisableIndex:
        case AT_EnableIndex:
            ret = true;
            break;
        default:
            ret = false;
            break;
    }
    return ret;
}

inline static bool CStoreSupportConstraint(Constraint* cons)
{
    bool ret = false;
    switch (cons->contype) {
#ifndef ENABLE_MULTIPLE_NODES
        case CONSTR_PRIMARY:
        case CONSTR_UNIQUE:
            /* Only check deferrable attribute in primary and unique constraint */
            if (cons->deferrable || cons->initdeferred) {
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("column/timeseries store unsupport DEFERRABLE/INITIALLY DEFERRED on constraint \"%s\"",
                            GetConstraintType(cons->contype))));
            }
        case CONSTR_ATTR_NOT_DEFERRABLE: /* CONSTR_ATTR_XXX is a constraint attribute instead of actual constraint */
        case CONSTR_ATTR_IMMEDIATE:
#endif
        case CONSTR_NULL:
        case CONSTR_NOTNULL:
        case CONSTR_DEFAULT:
        case CONSTR_CLUSTER:
            ret = true;
            break;
        default:
            ret = false;
            break;
    }
    return ret;
}

/* get all partitions oid */
static List* get_all_part_oid(Oid relid)
{
    List* oid_list = NIL;
    Relation pgpartition;
    TableScanDesc scan;
    HeapTuple tuple;
    ScanKeyData keys[2];

    /* Process all partitions of this partitiond table */
    ScanKeyInit(&keys[0],
        Anum_pg_partition_parttype,
        BTEqualStrategyNumber,
        F_CHAREQ,
        CharGetDatum(PART_OBJ_TYPE_TABLE_PARTITION));

    ScanKeyInit(&keys[1], Anum_pg_partition_parentid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relid));

    pgpartition = heap_open(PartitionRelationId, AccessShareLock);
    scan = tableam_scan_begin(pgpartition, SnapshotNow, 2, keys);
    while ((tuple = (HeapTuple) tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) {
        oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple));
    }
    tableam_scan_end(scan);
    heap_close(pgpartition, AccessShareLock);

    return oid_list;
}
static List* find_cstore_delta(Relation rel, LOCKMODE lockmode)
{
    List* children = NIL;
    if (!RELATION_IS_PARTITIONED(rel)) {
        /*
         * Add column for delta table.
         */
        children = lappend_oid(children, RelationGetDeltaRelId(rel));
        LockRelationOid(RelationGetDeltaRelId(rel), lockmode);
    } else {
        List* part_oid_list = get_all_part_oid(RelationGetRelid(rel));
        ListCell* cell = NULL;
        foreach (cell, part_oid_list) {
            Oid part_oid = lfirst_oid(cell);
            Partition partrel = partitionOpen(rel, part_oid, lockmode);
            /*
             * Add column for delta table.
             */
            children = lappend_oid(children, partrel->pd_part->reldeltarelid);
            LockRelationOid(partrel->pd_part->reldeltarelid, lockmode);
            partitionClose(rel, partrel, NoLock);
        }
        list_free(part_oid_list);
    }
    return children;
}

static void CheckPartitionUnsupported(CreateStmt* stmt)
{
    if (stmt->partTableState && stmt->partTableState->intervalPartDef) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Unsupport feature"),
                errdetail(
                "cstore/timeseries don't support interval partition type.")));
    }

    if (stmt->partTableState != NULL && stmt->partTableState->subPartitionState != NULL) {
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        (errmsg("Unsupport feature"), errdetail("cstore/timeseries don't support subpartition table."),
                         errcause("The function is not implemented."),
                         erraction("Create a new row-store table to replace it."))));
    }

    if (stmt->partTableState != NULL && 
        (stmt->partTableState->partitionStrategy == PART_STRATEGY_LIST ||
        stmt->partTableState->partitionStrategy == PART_STRATEGY_HASH)) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Unsupport feature"),
                errdetail(
                "cstore/timeseries don't support LIST/HASH partition type.")));
    }
}

// all unsupported features are checked and error reported here for cstore table
static void CheckCStoreUnsupportedFeature(CreateStmt* stmt)
{
    Assert(stmt);

    if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                errmsg("global temporary table can only support heap table")));
    }

    if (stmt->ofTypename)
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Unsupport feature"),
                errdetail("cstore/timeseries don't support relation defination "
                          "with composite type using CREATE TABLE OF TYPENAME.")));

    if (stmt->inhRelations) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Unsupport feature"),
                errdetail("cstore/timeseries don't support relation defination with inheritance.")));
    }
    
    if (stmt->relation->schemaname != NULL &&
        IsSystemNamespace(get_namespace_oid(stmt->relation->schemaname, false))) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Unsupport feature"),
                errdetail("cstore/timeseries don't support relation defination with System namespace.")));
    }
    CheckPartitionUnsupported(stmt);
    // Check constraints
    ListCell* lc = NULL;
    foreach (lc, stmt->tableEltsDup) {
        Node* element = (Node*)lfirst(lc);
        /* check table-level constraints */
        if (IsA(element, Constraint) && !CStoreSupportConstraint((Constraint*)element)) {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("column/timeseries store unsupport constraint \"%s\"",
                        GetConstraintType(((Constraint*)element)->contype))));
        } else if (IsA(element, ColumnDef)) {
            List* colConsList = ((ColumnDef*)element)->constraints;
            ListCell* lc2 = NULL;
            /* check column-level constraints */
            foreach (lc2, colConsList) {
                Constraint* colCons = (Constraint*)lfirst(lc2);
                if (!CStoreSupportConstraint(colCons)) {
                    ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                            errmsg("column/timeseries store unsupport constraint \"%s\"",
                                GetConstraintType(colCons->contype))));
                }
            }
        }
    }
}

void CheckCStoreRelOption(StdRdOptions* std_opt)
{
    Assert(std_opt);
    if (std_opt->partial_cluster_rows < std_opt->max_batch_rows && std_opt->partial_cluster_rows >= 0) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                errmsg("PARTIAL_CLUSTER_ROWS cannot be less than MAX_BATCHROW."),
                errdetail("PARTIAL_CLUSTER_ROWS must be greater than or equal to MAX_BATCHROW."),
                errhint("PARTIAL_CLUSTER_ROWS is MAX_BATCHROW multiplied by an integer.")));
    }
}

static void partition_policy_interval_check(StdRdOptions* std_opt, bool is_ts_table)
{
    int32 typmod = -1;
    Interval* ttl_interval = NULL;
    Interval* period_interval = NULL;
    if (pg_strcasecmp(TIME_UNDEFINED, StdRdOptionsGetStringData(std_opt, ttl, TIME_UNDEFINED)) != 0) {
        ttl_interval = char_to_interval((char*)StdRdOptionsGetStringData(std_opt, ttl, TIME_UNDEFINED), typmod);
    }
    if (pg_strcasecmp(TIME_UNDEFINED, StdRdOptionsGetStringData(std_opt, period, TIME_UNDEFINED)) != 0) {
        period_interval = char_to_interval((char*)StdRdOptionsGetStringData(std_opt, period, TIME_UNDEFINED), typmod);
    }

    if (period_interval == NULL && is_ts_table) {
        period_interval = char_to_interval(TIME_ONE_DAY, typmod);
        ereport(WARNING, 
            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                errmsg(" using %s as default period.", TIME_ONE_DAY)));
    }

    if (ttl_interval != NULL && period_interval != NULL && interval_cmp_internal(period_interval, ttl_interval) > 0) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg(" period must smaller than ttl.")));
    }
}

static void check_partion_policy_rel_option(List* options, StdRdOptions* std_opt)
{
    Assert(std_opt);
    ListCell* opt = NULL;
    bool has_opt = false;

    if (options == NULL) {
        return; /* nothing to do */
    }
    bool is_ts_table = false;
    foreach (opt, options) {
        DefElem* def = (DefElem*)lfirst(opt);
        if (pg_strcasecmp(def->defname, ORIENTATION_TIMESERIES) == 0) {
            is_ts_table = true;
        }
        if (pg_strcasecmp(def->defname, "ttl") == 0 || pg_strcasecmp(def->defname, "period") == 0) {
            has_opt = true;
        }
    }

    if (!has_opt) {
        return; /* nothing to do */
    }

    partition_policy_interval_check(std_opt, is_ts_table);
}

/*
 * Brief        : Whether or not set orientation option and check the validity
 * Input        : options, the options list.
 *                isDfsTbl, whether or not is a dfs table.
 * Output       : isCUFormat, whether ot not the table is CU format.
 * Return Value : Retutn the true if has been setted otherwise return false.
 * Notes        : None.
 */
static bool isOrientationSet(List* options, bool* isCUFormat, bool isDfsTbl)
{
    bool isSetFormat = false;
    ListCell* cell = NULL;
    foreach (cell, options) {
        DefElem* def = (DefElem*)lfirst(cell);
        if (pg_strcasecmp(def->defname, "orientation") == 0) {
            if (isDfsTbl) {
                /* The orientation option values must be "ORIENTATION_COLUMN" or "ORIENTATION_ORC". */
                if (pg_strcasecmp(defGetString(def), ORIENTATION_ORC) != 0) {
                    ereport(ERROR,
                        (errcode(ERRCODE_INVALID_OPTION),
                            errmsg("Invalid string for  \"ORIENTATION\" option"),
                            errdetail("Valid string is \"orc\".")));
                }
            } else {
#ifdef ENABLE_MULTIPLE_NODES
                if (pg_strcasecmp(defGetString(def), ORIENTATION_COLUMN) != 0 &&
                    pg_strcasecmp(defGetString(def), ORIENTATION_TIMESERIES) != 0 &&
                    pg_strcasecmp(defGetString(def), ORIENTATION_ROW) != 0) {
                    ereport(ERROR,
                        (errcode(ERRCODE_INVALID_OPTION),
                            errmsg("Invalid string for  \"ORIENTATION\" option"),
                            errdetail("Valid string are \"column\", \"row\", \"timeseries\".")));
                }
#else   /* ENABLE_MULTIPLE_NODES */
                if (pg_strcasecmp(defGetString(def), ORIENTATION_COLUMN) != 0 &&
                    pg_strcasecmp(defGetString(def), ORIENTATION_ROW) != 0) {
                    ereport(ERROR,
                        (errcode(ERRCODE_INVALID_OPTION),
                            errmsg("Invalid string for  \"ORIENTATION\" option"),
                            errdetail("Valid string are \"column\", \"row\".")));
                }
#endif   /* ENABLE_MULTIPLE_NODES */
#ifdef ENABLE_FINANCE_MODE
                if (pg_strcasecmp(defGetString(def), ORIENTATION_COLUMN) == 0) {
                    ereport(ERROR,
                            (errcode(ERRCODE_INVALID_OPTION),
                                errmsg("Invalid string for  \"ORIENTATION\" option"),
                                errdetail("ORIENTATION=COLUNMN is incorrect, not work on finance mode.")));
                }
#endif
            }
            if (pg_strcasecmp(defGetString(def), ORIENTATION_COLUMN) == 0 && isCUFormat != NULL) {
                *isCUFormat = true;
            }

            isSetFormat = true;
            break;
        }
    }

    return isSetFormat;
}

/*
 * @Description: add default reloption of both row and column table.
 * @Param [IN] options: the table's option which user defined.
 * @Param [IN] relkind: table's kind(ordinary table or other database object).
 * @return: option with defalut options.
 */
static List* AddDefaultOptionsIfNeed(List* options, const char relkind, CreateStmt* stmt, Oid relnamespace)
{
    List* res = options;
    int8 relcmprs = stmt->row_compress;
    ListCell* cell = NULL;
    bool isCStore = false;
    bool isTsStore = false;
    bool hasCompression = false;
    bool createWithOrientationRow = false; /* To mark whether table have been create with(orientation = row) */
    bool isUstore = false;
    bool assignedStorageType = false;
    bool segment = false;
    TableCreateSupport tableCreateSupport{(int)COMPRESS_TYPE_NONE, false, false, false, false, false, true, false};
    bool hasOids = false;
    (void)isOrientationSet(options, NULL, false);
    foreach (cell, options) {
        DefElem* def = (DefElem*)lfirst(cell);
        if (pg_strcasecmp(def->defname, "orientation") == 0 &&
            pg_strcasecmp(defGetString(def), ORIENTATION_COLUMN) == 0) {
            isCStore = true;
            tableCreateSupport.is_orientation_row = false;
        } else if (pg_strcasecmp(def->defname, "orientation") == 0 &&
                   pg_strcasecmp(defGetString(def), ORIENTATION_ROW) == 0) {
            createWithOrientationRow = true;
            tableCreateSupport.is_orientation_row = true;
        } else if (pg_strcasecmp(def->defname, "orientation") == 0 &&
            pg_strcasecmp(defGetString(def), ORIENTATION_TIMESERIES) == 0) {
            isTsStore = true;
            tableCreateSupport.is_orientation_row = false;
        } else if (pg_strcasecmp(def->defname, "compression") == 0) {
            if (pg_strcasecmp(defGetString(def), COMPRESSION_NO) != 0 &&
                pg_strcasecmp(defGetString(def), COMPRESSION_YES) != 0 &&
                pg_strcasecmp(defGetString(def), COMPRESSION_LOW) != 0 &&
                pg_strcasecmp(defGetString(def), COMPRESSION_MIDDLE) != 0 &&
                pg_strcasecmp(defGetString(def), COMPRESSION_HIGH) != 0) {
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                        errmsg("Invalid string for  \"COMPRESSION\" option"),
                        errdetail(
                            "Valid string are \"no\", \"yes\", \"low\", \"middle\", \"high\" for non-dfs table.")));
            }
            hasCompression = true;
        } else if (pg_strcasecmp(def->defname, "version") == 0) {
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_OPTION),
                    errmsg("It is not allowed to assign version option for non-dfs table.")));
        } else if (pg_strcasecmp(def->defname, "segment") == 0) {
            segment = ReadBoolFromDefElem(def);
        } else {
            SetOneOfCompressOption(def, &tableCreateSupport);
        }

        if (pg_strcasecmp(def->defname, "orientation") == 0 && pg_strcasecmp(defGetString(def), ORIENTATION_ORC) == 0) {
#ifdef ENABLE_MULTIPLE_NODES
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_OPTION),
                    errmsg("Invalid string for  \"ORIENTATION\" option"),
                    errdetail("Valid string are \"column\", \"row\", \"timeseries\".")));
#else   /* ENABLE_MULTIPLE_NODES */
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_OPTION),
                    errmsg("Invalid string for  \"ORIENTATION\" option"),
                    errdetail("Valid string are \"column\", \"row\".")));
#endif   /* ENABLE_MULTIPLE_NODES */
        }
        if (pg_strcasecmp(def->defname, "storage_type") == 0) {
            if (pg_strcasecmp(defGetString(def), TABLE_ACCESS_METHOD_USTORE) == 0) {
                isUstore = true;
                tableCreateSupport.is_storage_type_ustore = true;
            }
            assignedStorageType = true;
        }
        if (pg_strcasecmp(def->defname, "oids") == 0) {
            hasOids = true;
        }
    }

    if (isUstore == true && hasOids == true) {
        ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("OIDS option is not supported for ustore table")));
    }

    if (isUstore && !createWithOrientationRow && !isCStore && !isTsStore) {
        DefElem* def = makeDefElem("orientation", (Node*)makeString(ORIENTATION_ROW));
        res = lcons(def, options);
    }
    if (isCStore && !hasCompression) {
        DefElem* def = makeDefElem("compression", (Node*)makeString(COMPRESSION_LOW));
        res = lappend(options, def);
    }
    if (isCStore && assignedStorageType) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPTION),
                        errmsg("There is a conflict caused by storage_type and orientation")));
    }

    bool noSupportTable = isCStore || isTsStore || relkind != RELKIND_RELATION ||
                          stmt->relation->relpersistence == RELPERSISTENCE_UNLOGGED ||
                          stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
                          stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP;
    if (noSupportTable && tableCreateSupport.compressType) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPTION),
                        errmsg("compresstype can not be used in column table, "
                               "view, unlogged table or temp table.")));
    }

    if (tableCreateSupport.compressType && ENABLE_DMS) {
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Compression is not supported while DMS and DSS enabled.\n")));
    }

    CheckCompressOption(&tableCreateSupport);

    if (isUstore && !isCStore && !hasCompression && !tableCreateSupport.compressType) {
        DefElem* def = makeDefElem("compression", (Node *)makeString(COMPRESSION_NO));
        res = lappend(options, def);
    }

    /* If it's a row table, give the defalut reloption with orientation and compression. */
    if (!isUstore && !isCStore && !isTsStore && (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW)) {
        Value* rowCmprOpt = NULL;
        if (IsCompressedByCmprsInPgclass((RelCompressType)relcmprs)) {
            rowCmprOpt = makeString(COMPRESSION_YES);
        } else {
            rowCmprOpt = makeString(COMPRESSION_NO);
        }
        /*
         * As column table default reloptions is {orientation=column,compression=low}, we
         * set the row table default reloptions {orientation=row,compression=no} to keep the
         * display format on "\d(+)" consistently.
         */
        if (options == NULL) {
            DefElem* def1 = makeDefElem("orientation", (Node*)makeString(ORIENTATION_ROW));
            DefElem* def2 = makeDefElem("compression", (Node*)rowCmprOpt);
            res = list_make2(def1, def2);
            if (g_instance.attr.attr_storage.enable_ustore && u_sess->attr.attr_sql.enable_default_ustore_table &&
                relkind != RELKIND_MATVIEW && !IsSystemNamespace(relnamespace) && !assignedStorageType) {
                DefElem* def3 = makeDefElem("storage_type", (Node*)makeString(TABLE_ACCESS_METHOD_USTORE_LOWER));
                res = lappend(res, def3);
            }
        } else {
            /*
             * To show orientation at the head of reloption when createWithOrientationRow
             * is false, we use lcons instead of lappend here.
             */
            if (!createWithOrientationRow) {
                DefElem *def1 = makeDefElem("orientation", (Node *)makeString(ORIENTATION_ROW));
                res = lcons(def1, options);
            }
            if (!hasCompression  && !tableCreateSupport.compressType) {
                DefElem *def2 = makeDefElem("compression", (Node *)rowCmprOpt);
                res = lappend(options, def2);
            }
            if (g_instance.attr.attr_storage.enable_ustore && u_sess->attr.attr_sql.enable_default_ustore_table &&
                relkind != RELKIND_MATVIEW && !IsSystemNamespace(relnamespace) && !assignedStorageType) {
                if (hasOids) {
                    ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("OIDS option is not supported for ustore table")));
                }
                DefElem *def2 = makeDefElem("storage_type", (Node *)makeString(TABLE_ACCESS_METHOD_USTORE_LOWER));
                res = lappend(options, def2);
            }
        }
    }
    return res;
}

/*
 * @Description: Previous check whether the object may be created.
 * @in stmt, the object struct.
 * @in dfsTablespace, whether is a HDFS tablespace.
 */
static void PreCheckCreatedObj(CreateStmt* stmt, bool dfsTablespace, char relKind)
{
#ifndef ENABLE_MULTIPLE_NODES
    if (stmt->subcluster != NULL) {
        if (stmt->subcluster->clustertype == SUBCLUSTER_GROUP || stmt->subcluster->clustertype == SUBCLUSTER_NODE)
            DISTRIBUTED_FEATURE_NOT_SUPPORTED();
    }
#endif

    bool ignore_enable_hadoop_env = false;
    ListCell* cell = NULL;
    foreach (cell, stmt->options) {
        DefElem* def = (DefElem*)lfirst(cell);
        if (0 == pg_strcasecmp(def->defname, OptIgnoreEnableHadoopEnv)) {
            if (1 == defGetInt64(def)) {
                ignore_enable_hadoop_env = true;
                break;
            }
        }
    }

    if (!ignore_enable_hadoop_env && u_sess->attr.attr_sql.enable_hadoop_env && !dfsTablespace) {
        if (relKind == RELKIND_RELATION && stmt->relation->relpersistence != RELPERSISTENCE_TEMP &&
            stmt->relation->relpersistence != RELPERSISTENCE_UNLOGGED) {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("It is unsupported to create row/cstore non-temporary/non-unlogged table in hadoop "
                           "enviroment.")));
        }
    }

    if (dfsTablespace && (stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
                             stmt->relation->relpersistence == RELPERSISTENCE_UNLOGGED)) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                errmsg("It is unsupported to create unlogged table and temporary table on DFS tablespace.")));
    }

    if (dfsTablespace && stmt->subcluster && !in_logic_cluster() && IS_PGXC_COORDINATOR &&
        stmt->subcluster->clustertype == SUBCLUSTER_GROUP) {
        /* For dfs table we are going to block TO-GROUP create table request */
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("It is unsupported to create table with to group option on DFS tablespace.")));
    }

    if ((RELKIND_FOREIGN_TABLE == relKind || RELKIND_STREAM == relKind)
        && stmt->subcluster && !in_logic_cluster() && IS_PGXC_COORDINATOR &&
        stmt->subcluster->clustertype == SUBCLUSTER_GROUP) {
        /* For foreign table we are going to block TO-GROUP create table request */
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                errmsg("It is unsupported to create foreign table with to group option.")));
    }

    if (!isRestoreMode && stmt->subcluster && stmt->subcluster->clustertype == SUBCLUSTER_NODE) {
        /* If not in restore mode,  we are going to block TO-NODE create table request */
        ereport(
            ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("CREATE TABLE ... TO NODE is not yet supported.")));
    }
}

/*
 * brief: Check Object to be created. initialize DFS table options.
 *        check DFS table definition.
 * input param @stmt: a CreateStmt struct.
 * input param @stmt: the object kind.
 * Return : None.
 */
static void checkObjectCreatedinHDFSTblspc(CreateStmt* stmt, char relkind)
{
    /*
     * If the object is a logic object, do not initialize and check.
     */
    if (HDFS_TBLSPC_SUPPORT_CREATE_LOGIC_OBJECT(relkind)) {
        return;
    }

    /*
     * Check options and add default option for Dfs relation if need.
     */
    stmt->options = InitDfsOptions(stmt->options);

    /*
     * Validate Dfs table definition.
     */
    validateDfsTableDef(stmt, true);
}

/*
 * brief: Whether ORC format relation support the option or not.
 * input param @option: the option to be checked.
 * Return true if the option is supported by ORC format relation,
 * otherwise return false.
 */
static bool OptionSupportedByORCRelation(const char* option)
{
    for (uint32 i = 0; i < sizeof(ORCSupportOption) / sizeof(char*); ++i) {
        if (0 == pg_strcasecmp(ORCSupportOption[i], option)) {
            return true;
        }
    }
    return false;
}

/*
 * Brief        : Initialize and validity check for options. If the user do
 *                not set option, we assign default value to the option.
 * Input        : options, the setted table options by user.
 * Output       : None.
 * Return Value : Retrun new options list.
 * Notes        : 1. The default orientation value is "ORIENTATION_ORC".
 *                2. The defualt compression value is "COMPRESSION_NO";
 *                3. The default version value is "ORC_VERSION_012".
 */
static List* InitDfsOptions(List* options)
{
    List* res = options;
    ListCell* cell = NULL;
    bool isSetFormat = false;
    bool hasSetCompression = false;
    bool hasSetVersion = false;

    isSetFormat = isOrientationSet(options, NULL, true);
    foreach (cell, options) {
        DefElem* def = (DefElem*)lfirst(cell);

        if (!OptionSupportedByORCRelation(def->defname)) {
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_OPTION),
                    errmsg("Unsupport \"%s\" option", def->defname),
                    errdetail("Valid options are \"%s\", \"%s\", \"%s\".",
                        ORCSupportOption[0],
                        ORCSupportOption[1],
                        ORCSupportOption[2])));
        }
        if (pg_strcasecmp(def->defname, "compression") == 0) {
            /*
             * YES=LOW=SNAPPY+LOW_COMPRESS; MIDDLE=SNAPPY+HIGH_COMPRESS; SNAPPY and LZ4
             * with low compress and ZLIB with HIGH compress; HIGH=ZLIB+HIGH_COMPRESS.
             */
            if (pg_strcasecmp(defGetString(def), COMPRESSION_NO) != 0 &&
                pg_strcasecmp(defGetString(def), COMPRESSION_YES) != 0 &&
                pg_strcasecmp(defGetString(def), COMPRESSION_ZLIB) != 0 &&
                pg_strcasecmp(defGetString(def), COMPRESSION_SNAPPY) != 0 &&
                pg_strcasecmp(defGetString(def), COMPRESSION_LZ4) != 0 &&
                pg_strcasecmp(defGetString(def), COMPRESSION_LOW) != 0 &&
                pg_strcasecmp(defGetString(def), COMPRESSION_MIDDLE) != 0 &&
                pg_strcasecmp(defGetString(def), COMPRESSION_HIGH) != 0) {
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                        errmsg("Invalid string for  \"COMPRESSION\" option"),
                        errdetail("Valid string are \"no\", \"yes\", \"low\", \"middle\", \"high\", \"snappy\", "
                                  "\"zlib\", \"lz4\" for dfs table.")));
            }
            hasSetCompression = true;
        }
        if (pg_strcasecmp(def->defname, "version") == 0) {
            if (pg_strcasecmp(defGetString(def), ORC_VERSION_012) != 0) {
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_OPTION),
                        errmsg("Invalid string for  \"VERSION\" option"),
                        errdetail("Valid string is \"0.12\".")));
            }

            hasSetVersion = true;
        }
    }

    if (!isSetFormat) {
        DefElem* def = makeDefElem("orientation", (Node*)makeString(ORIENTATION_ORC));
        res = lappend(res, def);
    }
    if (!hasSetCompression) {
        DefElem* def = NULL;

        def = makeDefElem("compression", (Node*)makeString(COMPRESSION_SNAPPY));
        res = lappend(res, def);
    }
    if (!hasSetVersion) {
        DefElem* def = makeDefElem("version", (Node*)makeString(ORC_VERSION_012));
        res = lappend(res, def);
    }

    return res;
}

/*
 * Brief        : Validate Dfs table definition.
 * Input        : stmt, a CreateStmt struct.
 *                dfsTbl, whethre or not the table is dfs table.
 *                The dfsTbl is true, the table is dfs table.
 * Output       : None.
 * Return Value : None.
 * Notes        : None.
 */
static void validateDfsTableDef(CreateStmt* stmt, bool isDfsTbl)
{
    ListCell* optionCell = NULL;
    char* optionValue = NULL;
    bool cuFormat = false;
    if (!isDfsTbl) {
        return;
    }

    foreach (optionCell, stmt->options) {
        DefElem* optionDef = (DefElem*)lfirst(optionCell);
        char* optionDefName = optionDef->defname;

        if (pg_strcasecmp(optionDefName, "orientation") == 0) {
            optionValue = defGetString(optionDef);
            break;
        }
    }

    /* determine whether it is a CU */
    if ((NULL != optionValue) && (0 == pg_strcasecmp(optionValue, ORIENTATION_COLUMN))) {
        cuFormat = true;
    }

    /*
     * Currently, we only support "Value-Based" partitioning scheme for partitioned
     * HDFS table
     */
    if (stmt->partTableState) {
        /*
         * For value partitioned HDFS table we should force RowMovement ON, as we
         * will enable it anyway for a table created as columanr(PAX-ORC) and also
         * partition case.
         */
        stmt->partTableState->rowMovement = ROWMOVEMENT_ENABLE;

        /* Number of partition key check */
        if (list_length(stmt->partTableState->partitionKey) == 0) {
            ereport(ERROR,
                (errcode(ERRCODE_PARTITION_ERROR),
                    errmsg("Num of partition keys in value-partitioned table should not be zeror")));
        } else if (list_length(stmt->partTableState->partitionKey) > MAX_VALUE_PARTKEY_NUMS) {
            ereport(ERROR,
                (errcode(ERRCODE_PARTITION_ERROR),
                    errmsg("Num of partition keys in value-partitioned table exceeds max allowed num:%d",
                        MAX_RANGE_PARTKEY_NUMS)));
        }

        /* Partition stragegy check */
        if (stmt->partTableState->partitionStrategy != PART_STRATEGY_VALUE) {
            ereport(ERROR,
                (errcode(ERRCODE_PARTITION_ERROR),
                    errmsg("Unsupport partition strategy '%s' feature for dfs table.",
                        GetPartitionStrategyNameByType(stmt->partTableState->partitionStrategy))));
        }
    }

    /*
     * Currently, support hash/replication distribution for dfs table(not cu format).
     * when support other distribution, this code will be deleted.
     */
    if (!cuFormat && stmt->distributeby != NULL && stmt->distributeby->disttype != DISTTYPE_HASH &&
        stmt->distributeby->disttype != DISTTYPE_REPLICATION) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                errmsg("Only support hash/replication distribution for dfs table.")));
    }
}

static void check_sub_part_tbl_space(Oid ownerId, char* tablespacename, List* subPartitionDefState)
{
    ListCell* subspccell = NULL;
    foreach(subspccell, subPartitionDefState) {
        RangePartitionDefState* subpartitiondef = (RangePartitionDefState*)lfirst(subspccell);
        char* subtablespacename = subpartitiondef->tablespacename;
        CheckPartitionTablespace(subtablespacename, ownerId);
    }
}

/* Check tablespace's permissions for partition */
static void check_part_tbl_space(CreateStmt* stmt, Oid ownerId, bool dfsTablespace)
{
    ListCell* spccell = NULL;
    /* check value partition table is created at DFS table space */
    if (stmt->partTableState->partitionStrategy == PART_STRATEGY_VALUE && !dfsTablespace)
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Value partitioned table can only be created on DFS tablespace.")));

    foreach (spccell, stmt->partTableState->partitionList) {
        if (nodeTag(lfirst(spccell)) == T_RangePartitionDefState) {
            RangePartitionDefState* partitiondef = (RangePartitionDefState*)lfirst(spccell);
            char* tablespacename = partitiondef->tablespacename;
            List* subPartitionDefState = partitiondef->subPartitionDefState;
            CheckPartitionTablespace(tablespacename, ownerId);
            check_sub_part_tbl_space(ownerId, tablespacename, subPartitionDefState);
        } else if (nodeTag(lfirst(spccell)) == T_HashPartitionDefState) {
            HashPartitionDefState* partitiondef = (HashPartitionDefState*)lfirst(spccell);
            char* tablespacename = partitiondef->tablespacename;
            List* subPartitionDefState = partitiondef->subPartitionDefState;
            CheckPartitionTablespace(tablespacename, ownerId);
            check_sub_part_tbl_space(ownerId, tablespacename, subPartitionDefState);
        } else if (nodeTag(lfirst(spccell)) == T_ListPartitionDefState) {
            ListPartitionDefState* partitiondef = (ListPartitionDefState*)lfirst(spccell);
            char* tablespacename = partitiondef->tablespacename;
            List* subPartitionDefState = partitiondef->subPartitionDefState;
            CheckPartitionTablespace(tablespacename, ownerId);
            check_sub_part_tbl_space(ownerId, tablespacename, subPartitionDefState);
        } else {
            ereport(ERROR, (errmodule(MOD_COMMAND), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Unknown PartitionDefState"),
                errdetail("N/A"), errcause("The partition type is incorrect."),
                erraction("Use the correct partition type.")));
            break;
        }
    }
}

static void alter_orientation(CreateStmt** stmt, bool all_field, bool all_tag, 
    Datum* reloptions, Node** orientedFrom, char** storeChar)
{
    static const char* const validnsps[] = HEAP_RELOPT_NAMESPACES;
    ListCell* cell = NULL;
    bool has_compression = false;

    if (all_field) {
        foreach (cell, (*stmt)->options) {
            DefElem* def = (DefElem*)lfirst(cell);
            if (pg_strcasecmp("orientation", def->defname) == 0) {
                def->arg = (Node*)makeString(ORIENTATION_COLUMN);
                break;
            }
        }
        ereport(NOTICE,
            (errcode(ERRCODE_SUCCESSFUL_COMPLETION),
                errmsg("'TSTAG' not found. Using '%s' as the orientation.", ORIENTATION_COLUMN),
                errhint("Please use both 'TSTAG' and 'TSFIELD' if orientation is '%s'.", ORIENTATION_TIMESERIES)));
        *reloptions = transformRelOptions((Datum)0, (*stmt)->options, NULL, validnsps, true, false);
        *orientedFrom = (Node*)makeString(ORIENTATION_COLUMN);
        *storeChar = ORIENTATION_COLUMN;
    } else if (all_tag) {
        foreach (cell, (*stmt)->options) {
            DefElem* def = (DefElem*)lfirst(cell);
            if (pg_strcasecmp("orientation", def->defname) == 0) {
                def->arg = (Node*)makeString(ORIENTATION_ROW);
            }
            if (pg_strcasecmp("compression", def->defname) == 0) {
                has_compression = true;
                def->arg = (Node*)makeString(COMPRESSION_NO);
            }
        }
        if (!has_compression) {
            Node* value = (Node*)makeString(COMPRESSION_NO);
            (*stmt)->options = lappend((*stmt)->options, makeDefElem("compression", value));
        }
        ereport(NOTICE,
            (errcode(ERRCODE_SUCCESSFUL_COMPLETION),
                errmsg("'TSFIELD' not found. Using '%s' as the orientation.", ORIENTATION_ROW),
                errhint("Please use both 'TSTAG' and 'TSFIELD' if orientation is '%s'.", ORIENTATION_TIMESERIES)));
        *reloptions = transformRelOptions((Datum)0, (*stmt)->options, NULL, validnsps, true, false);
        *orientedFrom = (Node*)makeString(ORIENTATION_ROW);
        *storeChar = ORIENTATION_ROW;
    }
}

/*
 * KVType only used in timeseries table.
 * Set kvtype to ATT_KV_UNDEFINED when using it in row-oriented or column-oriented table.
 */
static void clear_kvtype_row_column(CreateStmt* stmt)
{
    ListCell* cell = NULL;
    foreach (cell, stmt->tableElts) {
        ColumnDef* colDef = (ColumnDef*)lfirst(cell);
        colDef->kvtype = ATT_KV_UNDEFINED;
    }
}

static bool validate_timeseries(CreateStmt** stmt, Datum* reloptions, char** storeChar, Node** orientedFrom)
{
    bool kvtype_all_tag = true;
    bool kvtype_all_field = true;
    bool is_timeseries = true;
    int kvtype_time_count = 0;

    List *schema = (*stmt)->tableElts;
    ListCell *cell = NULL;

    /* check relpersistence, only permanent is supported */
    if ((*stmt)->relation->relpersistence != RELPERSISTENCE_PERMANENT)
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("unsupported persistency for timeseries table.")));

    /* Currently, only support hash or hidetag distribution for timeseries table.*/
    if ((*stmt)->distributeby != NULL &&
        ((*stmt)->distributeby->disttype != DISTTYPE_HASH && (*stmt)->distributeby->disttype != DISTTYPE_HIDETAG)) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION), 
                errmsg("Only support hash distribution for timeseries table.")));
    }

    foreach (cell, schema) {
        ColumnDef* colDef = (ColumnDef*)lfirst(cell);
        if (colDef->kvtype == ATT_KV_TAG) {
            kvtype_all_field = false;
        } else if (colDef->kvtype == ATT_KV_FIELD) {
            kvtype_all_tag = false;
        } else if (colDef->kvtype == ATT_KV_TIMETAG) {
            kvtype_time_count++;
        } else {
            if (colDef->kvtype != ATT_KV_HIDE) {
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("kvtype of '%s' must be defined when using timeseries.", colDef->colname)));
            }
        }
    }
    /* TIMESERIES only allowed one time column */
    if (kvtype_time_count != 1) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("TIMESERIES must have one and only one time column.")));
    }

    if (kvtype_all_field || kvtype_all_tag) {
        alter_orientation(stmt, kvtype_all_field, kvtype_all_tag, reloptions, orientedFrom, storeChar);
        is_timeseries = false;
    }

    if (is_timeseries) {
#ifdef ENABLE_MULTIPLE_NODES
        Tsdb::CheckTsRelname((*stmt)->relation->relname);
#endif  /* ENABLE_MULTIPLE_NODES */
        foreach (cell, schema) {
            ColumnDef* colDef = (ColumnDef*)lfirst(cell);
            int32 typmod = 0;
            HeapTuple typeTuple = typenameType(NULL, colDef->typname, &typmod);
            /* set it here to check the type distribution in DefineRelation */
            colDef->typname->typeOid = HeapTupleGetOid(typeTuple);
            ReleaseSysCache(typeTuple);

            // check the supported data type and error report if needed.
            if (!IsTypeSupportedByTsStore(colDef->kvtype, colDef->typname->typeOid)) {
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("type \"%s\" is not supported in timeseries store",
                        format_type_with_typemod(colDef->typname->typeOid, typmod))));
            }
        }
    }
    if (is_timeseries && !g_instance.attr.attr_common.enable_tsdb) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Cannot use orientation is timeseries when enable_tsdb is off.")));
    }
    return is_timeseries;
}

static void add_partiton(CreateStmt* stmt, StdRdOptions* std_opt)
{
    List* schema = stmt->tableElts;
    ListCell* cell = NULL;
    int32 typmod = -1;
    RangePartitionDefState* part1 = NULL;
    RangePartitionDefState* part2 = NULL;
    ColumnRef* col_ref = NULL;
    PartitionState* part_state = NULL;
    Interval* period_interval = NULL;

    if (0 != pg_strcasecmp(TIME_UNDEFINED, StdRdOptionsGetStringData(std_opt, period, TIME_UNDEFINED))) {
        period_interval = char_to_interval((char*)StdRdOptionsGetStringData(std_opt, period, TIME_UNDEFINED), typmod);
    } else {
        period_interval = char_to_interval(TIME_ONE_DAY, typmod);
    }

    /* Copy because timestamptz_to_str returns a static buffer  */
    char* part1_boundary_cstr = NULL;
    char* part2_boundary_cstr = NULL;
    TimestampTz current_sharp_time = GetCurrentTimestamp();
    if (INTERVAL_TO_USEC(period_interval) >= USECS_PER_DAY) {
        int tz;
        struct pg_tm tt, *tm = &tt;
        fsec_t fsec;
        const char* tzn = NULL;
        if (timestamp2tm(current_sharp_time, &tz, tm, &fsec, &tzn, NULL) != 0) {
            ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range.")));
        }
        current_sharp_time =
            current_sharp_time - (current_sharp_time % USECS_PER_DAY) + USECS_PER_DAY + (tz * USECS_PER_SEC);
        part1_boundary_cstr = pstrdup(timestamptz_to_str(current_sharp_time));
        part2_boundary_cstr = pstrdup(timestamptz_to_str(current_sharp_time + INTERVAL_TO_USEC(period_interval)));
    } else {
        part1_boundary_cstr = pstrdup(timestamptz_to_str(current_sharp_time + INTERVAL_TO_USEC(period_interval)));
        part2_boundary_cstr = pstrdup(timestamptz_to_str(current_sharp_time + 2 * INTERVAL_TO_USEC(period_interval)));
    }
    Datum constvalue1 = CStringGetDatum(part1_boundary_cstr);
    Const* con1 = makeConst(UNKNOWNOID, -1, InvalidOid, -1, constvalue1, false, true);
    Datum constvalue2 = CStringGetDatum(part2_boundary_cstr);
    Const* con2 = makeConst(UNKNOWNOID, -1, InvalidOid, -1, constvalue2, false, true);

    part1 = makeNode(RangePartitionDefState);
    part1->partitionName = "default_part_1";
    part1->boundary = list_make1(con1);
    part1->partitionno = 1;

    part2 = makeNode(RangePartitionDefState);
    part2->partitionName = "default_part_2";
    part2->boundary = list_make1(con2);
    part1->partitionno = 2;

    part_state = makeNode(PartitionState);
    part_state->partitionStrategy = 'r';
    part_state->rowMovement = ROWMOVEMENT_DEFAULT;
    foreach (cell, schema) {
        ColumnDef* col_def = (ColumnDef*)lfirst(cell);
        if (col_def->kvtype == ATT_KV_TIMETAG) {
            col_ref = makeNode(ColumnRef);
            col_ref->fields = list_make1(makeString(col_def->colname));
            col_ref->location = -1;
            part_state->partitionKey = list_make1(col_ref);
            break;
        }
    }
    part_state->partitionList = list_make2(part1, part2);
    stmt->partTableState = part_state;
}

static void partition_policy_check(CreateStmt* stmt, StdRdOptions* std_opt, bool timeseries_checked)
{
    List* schema = stmt->tableElts;
    ListCell* cell = NULL;

    partition_policy_interval_check(std_opt, timeseries_checked);
    /* 
     * For TIMESERIES storage, it is based on the partition table.
     * If it is not a partition table, turn relation into partition table by construncting
     * regarding partition structure explicitly with 2 partition forward
     */
    if (stmt->partTableState == NULL && timeseries_checked) {
        add_partiton(stmt, std_opt);
    }

    if (stmt->partTableState != NULL) {
        List *partitionKey = stmt->partTableState->partitionKey;
        ColumnRef *colRef = NULL;

        if (partitionKey->length != 1) {
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                    errmsg("Only support one partition Key.")));
        }
        colRef = (ColumnRef*) lfirst(list_head(partitionKey));
        Value *val =  (Value *) lfirst(list_head(colRef->fields));
        char *key_name = val->val.str;

        foreach(cell, schema) {
            ColumnDef  *colDef = (ColumnDef*)lfirst(cell);
            if (pg_strcasecmp(colDef->colname, key_name) == 0) {
                Oid result = InvalidOid;
                Type typtup = LookupTypeName(NULL, colDef->typname, NULL);
                if (typtup == NULL)
                    ereport(ERROR,
                        (errcode(ERRCODE_UNDEFINED_OBJECT),
                            errmsg("type \"%s\" does not exist.",
                            TypeNameToString(colDef->typname))));
                result = typeTypeId(typtup);
                ReleaseSysCache(typtup);
                if(result != TIMESTAMPOID && result != TIMESTAMPTZOID)
                    ereport(ERROR,
                        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                            errmsg(" Partition Key must be of type TIMESTAMP(TZ) when using ttl or period.")));
                if(timeseries_checked && colDef->kvtype != ATT_KV_TIMETAG) 
                    ereport(ERROR,
                        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                            errmsg(" Partition Key must be of kv type TSTAG.")));
            }
        }
    }
}

static void FetchSliceReftableOid(CreateStmt* stmt, Oid namespaceId)
{
    if (stmt->distributeby == NULL || stmt->distributeby->distState == NULL || 
        stmt->distributeby->distState->refTableName == NULL) {
        return;
    }

    char* refName = stmt->distributeby->distState->refTableName;
    Oid refOid = get_relname_relid(refName, namespaceId);
    if (!OidIsValid(refOid)) {
        /* User provided not-null yet invalid base table name */
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
            errmsg("Relation \"%s\" does not exist in current namespace", refName)));
    }
    stmt->distributeby->referenceoid = refOid;
}

#ifdef ENABLE_MOT
static void DetermineColumnCollationForMOTTable(Oid *collOid)
{
    AssertArg(collOid != nullptr);

    if (*collOid == DEFAULT_COLLATION_OID) {
        *collOid = C_COLLATION_OID;
    } else if (*collOid != 0 && *collOid != C_COLLATION_OID && *collOid != POSIX_COLLATION_OID) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("collations other than \"C\" or \"POSIX\" are not supported on MOT tables")));
    }
}
#endif

static void CheckPartitionKeyForCreateTable(PartitionState *partTableState, List *schema, TupleDesc descriptor)
{
    List *pos = NIL;
    bool partkeyIsFunc = false;

    /* get partitionkey's position */
    pos = GetPartitionkeyPos(partTableState->partitionKey, schema, &partkeyIsFunc);
    /* check partitionkey's datatype */
    if (partTableState->partitionStrategy == PART_STRATEGY_VALUE) {
        CheckValuePartitionKeyType(descriptor->attrs, pos);
    } else if (partTableState->partitionStrategy == PART_STRATEGY_INTERVAL) {
        CheckIntervalPartitionKeyType(descriptor->attrs, pos);
        CheckIntervalValue(descriptor->attrs, pos, partTableState->intervalPartDef);
    } else if (partTableState->partitionStrategy == PART_STRATEGY_RANGE) {
        CheckRangePartitionKeyType(descriptor->attrs, pos);
    } else if (partTableState->partitionStrategy == PART_STRATEGY_LIST) {
        CheckListPartitionKeyType(descriptor->attrs, pos);
    } else if (partTableState->partitionStrategy == PART_STRATEGY_HASH) {
        CheckHashPartitionKeyType(descriptor->attrs, pos);
    } else {
        list_free_ext(pos);
        ereport(ERROR, (errmodule(MOD_COMMAND), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("The partition type is not supported!"), errdetail("N/A"),
                        errcause("The partition type is incorrect."), erraction("Use the correct partition type.")));
    }

    /*
     * Check partitionkey's value for none value-partition table as for value
     * partition table, partition value is known until data get loaded.
     */
    if (partTableState->partitionStrategy != PART_STRATEGY_VALUE &&
        partTableState->partitionStrategy != PART_STRATEGY_HASH &&
        partTableState->partitionStrategy != PART_STRATEGY_LIST)
        ComparePartitionValue(pos, descriptor->attrs, partTableState->partitionList, true, partkeyIsFunc);
    else if (partTableState->partitionStrategy == PART_STRATEGY_LIST)
        CompareListValue(pos, descriptor->attrs, partTableState->partitionList, partkeyIsFunc);

    /* charset of partkey columns cannot be different from server_encoding */
    if (DB_IS_CMPT_BD) {
        foreach_cell (cell, pos) {
            int attidx = lfirst_int(cell);
            check_unsupported_charset_for_column(
                descriptor->attrs[attidx].attcollation, NameStr(descriptor->attrs[attidx].attname));
        }
    }

    list_free_ext(pos);
}

static List *GetSubPartitionDefList(PartitionState *partTableState, ListCell *cell)
{
    PartitionDefState *partitionDefState = (PartitionDefState *)lfirst(cell);
    List *subPartitionList = partitionDefState->subPartitionDefState;

    if (subPartitionList == NIL) {
        Const *boundaryDefault = makeNode(Const);
        boundaryDefault->ismaxvalue = true;
        boundaryDefault->location = -1;

        if (partTableState->partitionStrategy == PART_STRATEGY_RANGE) {
            RangePartitionDefState *tmpSubPartitionDefState = makeNode(RangePartitionDefState);
            tmpSubPartitionDefState->boundary = list_make1(boundaryDefault);
            subPartitionList = lappend(subPartitionList, tmpSubPartitionDefState);
        } else if (partTableState->partitionStrategy == PART_STRATEGY_LIST) {
            ListPartitionDefState *tmpSubPartitionDefState = makeNode(ListPartitionDefState);
            tmpSubPartitionDefState->boundary = list_make1(boundaryDefault);
            subPartitionList = lappend(subPartitionList, tmpSubPartitionDefState);
        } else if (partTableState->partitionStrategy == PART_STRATEGY_HASH) {
            HashPartitionDefState *tmpSubPartitionDefState = makeNode(HashPartitionDefState);
            tmpSubPartitionDefState->boundary = list_make1(boundaryDefault);
            subPartitionList = lappend(subPartitionList, tmpSubPartitionDefState);
        }

    }

    return subPartitionList;
}

void UpdatePartKeyExpr(Relation rel, PartitionState *partTableState, Oid partOid)
{
    ParseState* pstate = NULL;
    RangeTblEntry* rte = NULL;
    HeapTuple partTuple = NULL;
    pstate = make_parsestate(NULL);
    rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
    addRTEtoQuery(pstate, rte, true, true, true);
    Relation pgPartitionRel = heap_open(PartitionRelationId, RowExclusiveLock);
    if (OidIsValid(partOid))
        partTuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(partOid));
    else
        partTuple = searchPgPartitionByParentIdCopy(PART_OBJ_TYPE_PARTED_TABLE, rel->rd_id);
    if (!partTuple)
        ereport(ERROR,(errcode(ERRCODE_PARTITION_ERROR),errmsg("The partition can't be found")));
    bool isnull = false;
    fastgetattr(partTuple, Anum_pg_partition_partkeyexpr, RelationGetDescr(pgPartitionRel), &isnull);
    if (isnull) {
        if (OidIsValid(partOid))
            ReleaseSysCache(partTuple);
        else
            heap_freetuple(partTuple);
        heap_close(pgPartitionRel, RowExclusiveLock);
        return;
    }
	// Oid* partitionKeyDataType = NULL;
    Node* expr = transformExpr(pstate, (Node*)(linitial(partTableState->partitionKey)), EXPR_KIND_OTHER);
    assign_expr_collations(pstate, expr);
    bool nulls[Natts_pg_partition] = {false};
    bool replaces[Natts_pg_partition] = {false};
    Datum values[Natts_pg_partition] = {0};
    replaces[Anum_pg_partition_partkeyexpr - 1] = true;
    char* partkeyexpr = nodeToString(expr);
    values[Anum_pg_partition_partkeyexpr - 1] = partkeyexpr ? CStringGetTextDatum(partkeyexpr) : CStringGetTextDatum("");
    HeapTuple new_tuple = heap_modify_tuple(partTuple, RelationGetDescr(pgPartitionRel), values, nulls, replaces);
    simple_heap_update(pgPartitionRel, &new_tuple->t_self, new_tuple);
    CatalogUpdateIndexes(pgPartitionRel, new_tuple);
    if (OidIsValid(partOid))
        ReleaseSysCache(partTuple);
    else
        heap_freetuple(partTuple);
    heap_freetuple_ext(new_tuple);
    if (pgPartitionRel) {
        heap_close(pgPartitionRel, RowExclusiveLock);
    }
}

/*Check whether tables or partition stored in segment are created
in limited tablespaces */
void CheckSegmentIsInLimitTablespace(char* tableSpaceName, char* relName) 
{
    Oid tableSpaceId = InvalidOid;
    if (tableSpaceName != NULL) {
        tableSpaceId = get_tablespace_oid(tableSpaceName, false);
    }
    Oid tbSpcId = (tableSpaceId == InvalidOid) ? u_sess->proc_cxt.MyDatabaseTableSpace : tableSpaceId;
    uint64 tableSpaceMaxSize = 0;
    bool isLimit = TableSpaceUsageManager::IsLimited(tbSpcId, &tableSpaceMaxSize);
    if (isLimit) {
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmodule(MOD_SEGMENT_PAGE),
            errmsg("The partition %s do not support segment-page storage", relName != NULL ? relName : ""),
            errdetail("Segment-page storage doest not support limited tablespace \"%s\"", get_tablespace_name(tbSpcId)),
            errhint("use default or unlimited user defined tablespace before using segment-page storage.")));
    }
}

/* ----------------------------------------------------------------
 *		DefineRelation
 *				Creates a new relation.
 *
 * stmt carries parsetree information from an ordinary CREATE TABLE statement.
 * The other arguments are used to extend the behavior for other cases:
 * relkind: relkind to assign to the new relation
 * ownerId: if not InvalidOid, use this as the new relation's owner.
 * typaddress: if not null, it's set to the pg_type entry's address.
 *
 * Note that permissions checks are done against current user regardless of
 * ownerId.  A nonzero ownerId is used when someone is creating a relation
 * "on behalf of" someone else, so we still want to see that the current user
 * has permissions to do it.
 *
 * If successful, returns the address of the new relation.
 * ----------------------------------------------------------------
 */
ObjectAddress DefineRelation(CreateStmt* stmt, char relkind, Oid ownerId, ObjectAddress* typaddress, bool isCTAS, Oid typbasetype)
{
    char relname[NAMEDATALEN];
    Oid namespaceId;
    List* schema = stmt->tableElts;
    Oid relationId;
    Oid tablespaceId;
    Relation rel;
    TupleDesc descriptor;
    List* inheritOids = NIL;
    List* old_constraints = NIL;
    bool localHasOids = false;
    int parentOidCount;
    List* rawDefaults = NIL;
    List* cookedDefaults = NIL;
    List *ceLst = NIL;
    Datum reloptions;
    ListCell* listptr = NULL;
    AttrNumber attnum;
    static const char* const validnsps[] = HEAP_RELOPT_NAMESPACES;
    Oid ofTypeId;
    Node* orientedFrom = NULL;
    char* storeChar = ORIENTATION_ROW;
    bool timeseries_checked = false;
    bool dfsTablespace = false;
    bool isInitdbOnDN = false;
    bool isInLedgerNsp = false;
    HashBucketInfo* bucketinfo = NULL;
    DistributionType distType;
    ObjectAddress address;
    bool relhasuids = false;
    Oid nspdefcoll = InvalidOid;
    Oid rel_coll_oid = InvalidOid;
    List* depend_extend = NIL;

    /*
     * isalter is true, change the owner of the objects as the owner of the
     * namespace, if the owner of the namespce has the same name as the namescpe
     */
    bool isalter = false;
    bool hashbucket = false;
    int  bucketcnt  = 0;
    StorageType storage_type = HEAP_DISK;

    bool relisshared = u_sess->attr.attr_common.IsInplaceUpgrade && u_sess->upg_cxt.new_catalog_isshared;
    errno_t rc;
    /*
     * Truncate relname to appropriate length (probably a waste of time, as
     * parser should have done this already).
     */
    rc = strncpy_s(relname, NAMEDATALEN, stmt->relation->relname, NAMEDATALEN - 1);
    securec_check(rc, "", "");

    if (ISMATMAP(relname) || ISMLOG(relname)) {
        ereport(WARNING, (errcode(ERRCODE_INVALID_NAME),
            errmsg("\"%s\" is not an appropriated name for relation", relname),
            errdetail("The kernel may treat it as a %s table of materialized view",
                ISMATMAP(relname) ? "map" : "mlog")));
    }

    if (stmt->relation->relpersistence == RELPERSISTENCE_UNLOGGED && STMT_RETRY_ENABLED)
        stmt->relation->relpersistence = RELPERSISTENCE_PERMANENT;

    /* During grayscale upgrade, forbid creating LIST/RANGE tables if workingVersionNum is too low. */
    if (stmt->distributeby != NULL) {
        distType = stmt->distributeby->disttype;
        if ((distType == DISTTYPE_RANGE || distType == DISTTYPE_LIST) && 
            t_thrd.proc->workingVersionNum < RANGE_LIST_DISTRIBUTION_VERSION_NUM) {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg(
                        "Working Version Num less than %u does not support LIST/RANGE distributed tables.", 
                        RANGE_LIST_DISTRIBUTION_VERSION_NUM)));
        }
    }

    /*
     * Check consistency of arguments
     */
    if (stmt->oncommit != ONCOMMIT_NOOP
        && !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP
        || stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)) {
        ereport(ERROR,
               (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
               errmsg("ON COMMIT can only be used on temporary tables")));
    }

    //@Temp Table. We do not support on commit drop right now.
    if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP
        || stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
        && stmt->oncommit == ONCOMMIT_DROP) {
        ereport(
            ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                errmsg(
               "ON COMMIT only support PRESERVE ROWS or DELETE ROWS option")));
    }

    if (stmt->constraints != NIL && relkind == RELKIND_FOREIGN_TABLE) {
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("constraints on foreign tables are not supported")));
    }

    if (stmt->constraints != NIL && relkind == RELKIND_STREAM) {
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("constraints on streams are not supported")));
    }
    /*
     * For foreign table ROUNDROBIN distribution is a built-in support.
     */
    if (IsA(stmt, CreateForeignTableStmt) &&
        (IsSpecifiedFDW(((CreateForeignTableStmt*)stmt)->servername, DIST_FDW) ||
            IsSpecifiedFDW(((CreateForeignTableStmt*)stmt)->servername, LOG_FDW) ||
            IsSpecifiedFDW(((CreateForeignTableStmt*)stmt)->servername, GC_FDW)) &&
        (IS_PGXC_COORDINATOR || (isRestoreMode && stmt->subcluster)) && !stmt->distributeby) {
        stmt->distributeby = makeNode(DistributeBy);
        stmt->distributeby->disttype = DISTTYPE_ROUNDROBIN;
        stmt->distributeby->colname = NULL;
    }
    /*
     * Look up the namespace in which we are supposed to create the relation,
     * check we have permission to create there, lock it against concurrent
     * drop, and mark stmt->relation as RELPERSISTENCE_TEMP if a temporary
     * namespace is selected.
     */
    namespaceId = RangeVarGetAndCheckCreationNamespace(stmt->relation, NoLock, NULL, relkind);

    if (u_sess->attr.attr_sql.enforce_a_behavior) {
        /* Identify user ID that will own the table
         *
         * change the owner of the objects as the owner of the namespace
         * if the owner of the namespce has the same name as the namescpe
         * note: the object must be of the ordinary table, sequence, view or
         *		composite type
         */
        if (!OidIsValid(ownerId) && (relkind == RELKIND_RELATION || RELKIND_IS_SEQUENCE(relkind) ||
            relkind == RELKIND_VIEW || relkind == RELKIND_COMPOSITE_TYPE
            || relkind == RELKIND_CONTQUERY)) {
            bool anyResult = CheckRelationCreateAnyPrivilege(GetUserId(), relkind);
            ownerId = GetUserIdFromNspId(namespaceId, false, anyResult);
        }

        if (!OidIsValid(ownerId))
            ownerId = GetUserId();
        else if (ownerId != GetUserId())
            isalter = true;

        if (isalter) {
            /* Check namespace permissions. */
            AclResult aclresult;

            if (DB_IS_CMPT(PG_FORMAT) && u_sess->hook_cxt.forTsdbHook) {
                aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_CREATE);
            } else {
                aclresult = pg_namespace_aclcheck(namespaceId, ownerId, ACL_CREATE);
            }
            
            bool anyResult = false;
            if (aclresult != ACLCHECK_OK && !IsSysSchema(namespaceId)) {
                anyResult = CheckRelationCreateAnyPrivilege(ownerId, relkind);
            }
            if (aclresult != ACLCHECK_OK && !anyResult)
                aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceId));
        }
    }
    /*
     * Security check: disallow creating temp tables from security-restricted
     * code.  This is needed because calling code might not expect untrusted
     * tables to appear in pg_temp at the front of its search path.
     */
    if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP
        || stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
        && InSecurityRestrictedOperation()) {
        ereport(ERROR,
            (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                errmsg("cannot create temporary table within security-restricted operation")));
    }

    /* disallow creating table under blockchain schema directly */
    if (namespaceId == PG_BLOCKCHAIN_NAMESPACE) {
        ereport(ERROR,
            (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                errmsg("cannot create table under blockchain namspace.")));
    }
    isInLedgerNsp = IsLedgerNameSpace(namespaceId);
    nspdefcoll = get_nsp_default_collation(namespaceId);

    /*
     * Select tablespace to use.  If not specified, use default tablespace
     * (which may in turn default to database's default).
     */
    if (stmt->tablespacename) {
        tablespaceId = get_tablespace_oid(stmt->tablespacename, false);
    } else {
        tablespaceId = GetDefaultTablespace(stmt->relation->relpersistence);
        /* note InvalidOid is OK in this case */
    }

    dfsTablespace = IsSpecifiedTblspc(tablespaceId, FILESYSTEM_HDFS);
    if (dfsTablespace) {
        FEATURE_NOT_PUBLIC_ERROR("HDFS is not yet supported.");
    }

    if (dfsTablespace && is_feature_disabled(DATA_STORAGE_FORMAT)) {
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Unsupport the dfs table in this version.")));
    }

    PreCheckCreatedObj(stmt, dfsTablespace, relkind);

    /* Check permissions except when using database's default */
    if (OidIsValid(tablespaceId) && tablespaceId != u_sess->proc_cxt.MyDatabaseTableSpace) {
        AclResult aclresult;

        aclresult = pg_tablespace_aclcheck(tablespaceId, GetUserId(), ACL_CREATE);
        if (aclresult != ACLCHECK_OK)
            aclcheck_error(aclresult, ACL_KIND_TABLESPACE, get_tablespace_name(tablespaceId));
        /* view and sequence are not related to tablespace, so no need to check permissions */
        if (isalter && relkind != RELKIND_VIEW && relkind != RELKIND_CONTQUERY &&
            relkind != RELKIND_SEQUENCE && relkind != RELKIND_LARGE_SEQUENCE) {
            aclresult = pg_tablespace_aclcheck(tablespaceId, ownerId, ACL_CREATE);
            if (aclresult != ACLCHECK_OK)
                aclcheck_error(aclresult, ACL_KIND_TABLESPACE, get_tablespace_name(tablespaceId));
        }
    }

    /* In all cases disallow placing user relations in pg_global */
    if (!relisshared && tablespaceId == GLOBALTABLESPACE_OID)
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                errmsg("only shared relations can be placed in pg_global tablespace")));

    /* Identify user ID that will own the table */
    if (!OidIsValid(ownerId))
        ownerId = GetUserId();

    /* Add default options for relation if need. */
    if (!dfsTablespace) {
        if (!u_sess->attr.attr_common.IsInplaceUpgrade) {
            stmt->options = AddDefaultOptionsIfNeed(stmt->options, relkind, stmt, namespaceId);
        }
    } else {
        checkObjectCreatedinHDFSTblspc(stmt, relkind);
    }

    /* Only support one partial cluster key for dfs table. */
    if (stmt->clusterKeys && list_length(stmt->clusterKeys) > 1) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                errmsg("Only support one partial cluster key for dfs/cstore table.")));
    }

    /* Check tablespace's permissions for partition */
    if (stmt->partTableState) {
        check_part_tbl_space(stmt, ownerId, dfsTablespace);
    }

    /*
     * Parse and validate reloptions, if any.
     */
    /* global temp table */
    OnCommitAction oncommitAction = GttOncommitOption(stmt->options);
    if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP
        && relkind == RELKIND_RELATION) {
        if (oncommitAction != ONCOMMIT_NOOP && stmt->oncommit == ONCOMMIT_NOOP) {
            stmt->oncommit = oncommitAction;
        } else {
            if (oncommitAction != ONCOMMIT_NOOP && stmt->oncommit != ONCOMMIT_NOOP) {
                stmt->options = RemoveRelOption(stmt->options, "on_commit_delete_rows", NULL);
            }
            DefElem *opt = makeNode(DefElem);

            opt->type = T_DefElem;
            opt->defnamespace = NULL;
            opt->defname = "on_commit_delete_rows";
            opt->defaction = DEFELEM_UNSPEC;

            /* use reloptions to remember on commit clause */
            if (stmt->oncommit == ONCOMMIT_DELETE_ROWS) {
                opt->arg = reinterpret_cast<Node *>(makeString("true"));
            } else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS) {
                opt->arg = reinterpret_cast<Node *>(makeString("false"));
            } else if (stmt->oncommit == ONCOMMIT_NOOP) {
                opt->arg = reinterpret_cast<Node *>(makeString("false"));
            } else {
                elog(ERROR, "global temp table not support on commit drop clause");
            }
            stmt->options = lappend(stmt->options, opt);
        }
    } else if (oncommitAction != ONCOMMIT_NOOP) {
        elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be "
                    "specified by a regular table");
    }

    /* relation collation is stored using stmt->options. */
    if (DB_IS_CMPT_BD && relkind == RELKIND_RELATION) {
        (void)fill_relation_collation(stmt->collate, stmt->charset, &stmt->options, nspdefcoll);
    }

    fillTdeRelOptions(stmt->options, relkind);

    reloptions = transformRelOptions((Datum)0, stmt->options, NULL, validnsps, true, false);

    StdRdOptions* std_opt = (StdRdOptions*)heap_reloptions(relkind, reloptions, true);
    relhasuids = StdRdOptionsHasUids(std_opt, relkind);
    if (relhasuids && t_thrd.proc->workingVersionNum < HASUID_VERSION_NUM) {
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
            errmsg("hasuids is not supported in current version!")));
    }
    if (ENABLE_DMS && relhasuids) {
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
            errmsg("hasuids is not supported under Shared Storage.")));
    }
    if (std_opt != NULL) {
        RowTblCheckHashBucketOption(stmt->options, std_opt);
        if ((std_opt->segment)) {
            if (t_thrd.proc->workingVersionNum < SEGMENT_PAGE_VERSION_NUM) {
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("hash bucket table not supported in current version!")));
            }
            if (!XLogIsNeeded()) {
                ereport(ERROR, 
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 
                     errmodule(MOD_SEGMENT_PAGE), 
                     errmsg("segment-page storage requires wal-logging."),
                     errdetail("wal_level must >= WAL_LEVEL_ARCHIVE")));
            }
        }
        rel_coll_oid = std_opt->collate;
        hashbucket = std_opt->hashbucket;
        bucketcnt =  std_opt->bucketcnt;
        storage_type = (std_opt->segment == true) ? SEGMENT_PAGE : HEAP_DISK;

        if (pg_strcasecmp(ORIENTATION_COLUMN, StdRdOptionsGetStringData(std_opt, orientation, ORIENTATION_ROW)) == 0) {
            orientedFrom = (Node*)makeString(ORIENTATION_COLUMN);
            storeChar = ORIENTATION_COLUMN;
        } else if (pg_strcasecmp(ORIENTATION_ORC,
            StdRdOptionsGetStringData(std_opt, orientation, ORIENTATION_ROW)) == 0) {
            /*
             * Don't allow "create DFS table" to run inside a transaction block.
             *
             * "DfsDDLIsTopLevelXact" is set in "case T_CreateStmt" of
             * standard_ProcessUtility()
             *
             * exception: allow "CREATE DFS TABLE" operation in transaction block
             * during redis a table.
             */
            if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && u_sess->attr.attr_sql.enable_cluster_resize == false)
                PreventTransactionChain(u_sess->exec_cxt.DfsDDLIsTopLevelXact, "CREATE DFS TABLE");

            orientedFrom = (Node*)makeString(ORIENTATION_ORC);
            storeChar = ORIENTATION_COLUMN;
        } else if(0 == pg_strcasecmp(ORIENTATION_TIMESERIES,
                    StdRdOptionsGetStringData(std_opt, orientation, ORIENTATION_ROW))) {
            orientedFrom = (Node *)makeString(ORIENTATION_TIMESERIES);
            storeChar = ORIENTATION_TIMESERIES;
            /* for ts table redistribute, timeseries table redis_ is reserved */
            if (!u_sess->attr.attr_sql.enable_cluster_resize) {
                if (strncmp(relname, "redis_", 6) == 0) {
                    ereport(ERROR,
                        (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                            errmsg("Invalid table name prefix redis_, reserved in redis mode.")));
                }
            }
            /*
             * Check the kvtype parameter legality for timeseries storage method.
             * If all the kvtype exclude tstime are same, change the orientation to row or column explicitly.
             */
            timeseries_checked = validate_timeseries(&stmt, &reloptions, &storeChar, &orientedFrom);
            std_opt = (StdRdOptions*)heap_reloptions(relkind, reloptions, true);
        } else if (0 == pg_strcasecmp(TABLE_ACCESS_METHOD_USTORE,
            StdRdOptionsGetStringData(std_opt, storage_type, TABLE_ACCESS_METHOD_ASTORE))) {
            if ((t_thrd.proc->workingVersionNum < USTORE_VERSION && u_sess->attr.attr_common.upgrade_mode != 0)
#ifdef ENABLE_MULTIPLE_NODES
                || true
#endif
                ) {
                ereport(ERROR, (errmsg("Ustore table creation is not supported.")));
            }
            if (!g_instance.attr.attr_storage.enable_ustore) {
                ereport(ERROR, (errmsg("Ustore is disabled, please set enable_ustore=on.")));

            }
            if (isInLedgerNsp) {
                ereport(ERROR, 
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                         errmsg("Ustore table is not supported ledger user table.")));
            }
            orientedFrom = (Node *)makeString(TABLE_ACCESS_METHOD_USTORE);
            storeChar = TABLE_ACCESS_METHOD_USTORE;
        }

        // Set kvtype to ATT_KV_UNDEFINED in row-oriented or column-oriented table.
        if (0 != pg_strcasecmp(storeChar, ORIENTATION_TIMESERIES)) {
            clear_kvtype_row_column(stmt);
        }

        /*
         * Because we also support create partition policy for non timeseries table, we should check parameter
         * ttl and period if it contains
         */
        if (timeseries_checked ||
            0 != pg_strcasecmp(TIME_UNDEFINED, StdRdOptionsGetStringData(std_opt, ttl, TIME_UNDEFINED)) ||
            0 != pg_strcasecmp(TIME_UNDEFINED, StdRdOptionsGetStringData(std_opt, period, TIME_UNDEFINED))) {
            partition_policy_check(stmt, std_opt, timeseries_checked);
            if (stmt->partTableState != NULL) {
                check_part_tbl_space(stmt, ownerId, dfsTablespace);
                checkPartitionSynax(stmt);
            }
        }

        if (IS_SINGLE_NODE && stmt->partTableState != NULL) {
            if (stmt->partTableState->rowMovement != ROWMOVEMENT_DISABLE)
                stmt->partTableState->rowMovement = ROWMOVEMENT_ENABLE;
        }

        if (0 == pg_strcasecmp(storeChar, ORIENTATION_COLUMN)) {
            CheckCStoreUnsupportedFeature(stmt);
            CheckCStoreRelOption(std_opt);
            ForbidToSetOptionsForColTbl(stmt->options);
            if (stmt->partTableState) {
                if (stmt->partTableState->rowMovement == ROWMOVEMENT_DISABLE) {
                    ereport(NOTICE,
                        (errmsg("disable row movement is invalid for column stored tables."
                                " They always enable row movement between partitions.")));
                }
                /* always enable rowmovement for column stored tables */
                stmt->partTableState->rowMovement = ROWMOVEMENT_ENABLE;
            }
        } else if (0 == pg_strcasecmp(storeChar, ORIENTATION_TIMESERIES)) {
            /* check both support coloumn store and row store */
            CheckCStoreUnsupportedFeature(stmt);
            CheckCStoreRelOption(std_opt);
            if (stmt->partTableState) {
                if (stmt->partTableState->rowMovement == ROWMOVEMENT_DISABLE)
                    ereport(NOTICE,
                        (errmsg("disable row movement is invalid for timeseries stored tables."
                                " They always enable row movement between partitions.")));
                /* always enable rowmovement for column stored tables */
                stmt->partTableState->rowMovement = ROWMOVEMENT_ENABLE;
            }
            if (relkind == RELKIND_RELATION) {
                /* only care heap relation. ignore foreign table and index relation */
                forbid_to_set_options_for_timeseries_tbl(stmt->options);
            }

            /* construct distribute keys using tstag if not specified */
            if (stmt->distributeby == NULL) {
                ListCell* cell = NULL;
                DistributeBy* newnode = makeNode(DistributeBy);
                List* colnames = NIL;
                newnode->disttype = DISTTYPE_HASH;

                foreach (cell, schema) {
                    ColumnDef* colDef = (ColumnDef*)lfirst(cell);
                    if (colDef->kvtype == ATT_KV_TAG && IsTypeDistributable(colDef->typname->typeOid)) {
                        colnames = lappend(colnames, makeString(colDef->colname));
                    }
                }
                if (list_length(colnames) == 0) {
                    ereport(ERROR,
                        (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                            errmsg("No column can be used as distribution column.")));
                }
                newnode->colname = colnames;
                stmt->distributeby = newnode;
            /* if specified hidetag, add a hidden column as distribution column */
            } else if (stmt->distributeby->disttype == DISTTYPE_HIDETAG &&
                       stmt->distributeby->colname == NULL) {
                bool has_distcol = false;
                ListCell* cell;
                foreach (cell, schema) {
                    ColumnDef* colDef = (ColumnDef*)lfirst(cell);
                    if (colDef->kvtype == ATT_KV_TAG && IsTypeDistributable(colDef->typname->typeOid)) {
                        has_distcol = true;
                    }
                }
                if (!has_distcol) {
                    ereport(ERROR,
                        (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                            errmsg("No column can be used as distribution column.")));
                }
                ColumnDef* colDef = makeColumnDef(TS_PSEUDO_DIST_COLUMN, "char");
                colDef->kvtype = ATT_KV_HIDE;
                stmt->tableElts = lappend(stmt->tableElts, colDef);
                /* still use hash logic later */
                DistributeBy* distnode = stmt->distributeby;
                distnode->disttype = DISTTYPE_HASH;

                distnode->colname = lappend(distnode->colname, makeString(colDef->colname));
                ereport(LOG, (errmodule(MOD_TIMESERIES), errmsg("use implicit distribution column method.")));
            }
        } else if (pg_strcasecmp(storeChar, TABLE_ACCESS_METHOD_USTORE) == 0) {
            auto compression = StdRdOptionsGetStringData(std_opt, compression, COMPRESSION_NO);
            auto orientation = StdRdOptionsGetStringData(std_opt, orientation, ORIENTATION_ROW);
            if ((pg_strcasecmp(COMPRESSION_NO, compression) != 0 &&
                 pg_strcasecmp(ORIENTATION_COLUMN, orientation) == 0) ||
                IsCompressedByCmprsInPgclass((RelCompressType)stmt->row_compress)) {
                ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("UStore tables do not support compression.")));
            }
            ForbidToSetOptionsForRowTbl(stmt->options);
            ForbidToSetOptionsForUstoreTbl(stmt->options);
        } else {
            if (relkind == RELKIND_RELATION) {
                /* only care heap relation. ignore foreign table and index relation */
                ForbidToSetOptionsForRowTbl(stmt->options);
            }
        }

        if (pg_strcasecmp(storeChar, TABLE_ACCESS_METHOD_USTORE) != 0){
            /* init_td option is valid only when an Ustore table is created */
            ForbidToSetOptionsForNotUstoreTbl(stmt->options);
        }
        pfree_ext(std_opt);
    }

    if (pg_strcasecmp(storeChar, ORIENTATION_COLUMN) == 0) {
        ListCell *cell;
        foreach (cell, stmt->options) {
            DefElem *def = (DefElem *)lfirst(cell);
            if (pg_strcasecmp(def->defname, "storage_type") == 0) {
                char *cprType = NULL;
                if (nodeTag(def->arg) == T_String) {
                    cprType = strVal(def->arg);
                } else if (nodeTag(def->arg) == T_TypeName) {
                    cprType = TypeNameToString((TypeName*)def->arg);
                } else {
                    Assert(false);
                }
                if (pg_strcasecmp(cprType, "ustore") == 0) {
                    ereport(ERROR,
                        (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                         errmsg("orientation=column and storage_type=ustore can not be specified simultaneously")));
                }
            }
        }
    }

    if (orientedFrom == NULL) {
        /* default is ORIENTATION_ROW */
        orientedFrom = (Node *)makeString(ORIENTATION_ROW);
    }

    if (pg_strcasecmp(storeChar, ORIENTATION_ROW) == 0) {
        RowTblCheckCompressionOption(stmt->options, stmt->row_compress);
    } else if (relhasuids == true) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
            errmsg("hasuids is only supported for row table")));
    }

    if (stmt->ofTypename) {
        AclResult aclresult;

        ofTypeId = typenameTypeId(NULL, stmt->ofTypename);

        aclresult = pg_type_aclcheck(ofTypeId, GetUserId(), ACL_USAGE);
        if (aclresult != ACLCHECK_OK)
            aclcheck_error_type(aclresult, ofTypeId);
        if (isalter) {
            ofTypeId = typenameTypeId(NULL, stmt->ofTypename);

            aclresult = pg_type_aclcheck(ofTypeId, ownerId, ACL_USAGE);
            if (aclresult != ACLCHECK_OK)
                aclcheck_error_type(aclresult, ofTypeId);
        }
    } else
        ofTypeId = InvalidOid;

    if (enable_plpgsql_gsdependency()) {
        ListCell* cell = NULL;
        foreach(cell, schema) {
            ColumnDef* col_def = (ColumnDef*)lfirst(cell);
            depend_extend = lappend(depend_extend, col_def->typname->dependExtend);
        }
    }

    /*
     * Look up inheritance ancestors and generate relation schema, including
     * inherited attributes.
     */
    schema = MergeAttributes(
        schema, stmt->inhRelations, stmt->relation->relpersistence, &inheritOids, &old_constraints, &parentOidCount);

    if (isInLedgerNsp && relkind == RELKIND_RELATION && stmt->relation->relpersistence == RELPERSISTENCE_PERMANENT &&
        pg_strcasecmp(storeChar, ORIENTATION_ROW) == 0) {
        check_ledger_attrs_support(schema);
    }
    /*
     * Create a tuple descriptor from the relation schema.	Note that this
     * deals with column names, types, and NOT NULL constraints, but not
     * default values or CHECK constraints; we handle those below.
     */
    if (relkind == RELKIND_COMPOSITE_TYPE)
        descriptor = BuildDescForRelation(schema, orientedFrom, relkind);
    else
        descriptor = BuildDescForRelation(schema, orientedFrom, '\0', rel_coll_oid);

    /* Must specify at least one column when creating a table. */
    if (descriptor->natts == 0 && relkind != RELKIND_COMPOSITE_TYPE) {
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("must have at least one column")));
    }

    /* check column charset */
    if (DB_IS_CMPT_BD &&
        (0 == pg_strcasecmp(storeChar, ORIENTATION_COLUMN) || 0 == pg_strcasecmp(storeChar, ORIENTATION_TIMESERIES))) {
        for (int attidx = 0; attidx < descriptor->natts; attidx++) {
            check_unsupported_charset_for_column(
                descriptor->attrs[attidx].attcollation, NameStr(descriptor->attrs[attidx].attname));
        }
    }

    if (stmt->partTableState) {
        CheckPartitionKeyForCreateTable(stmt->partTableState, schema, descriptor);
        if (stmt->partTableState->subPartitionState != NULL) {
            ListCell *cell = NULL;
            Assert(list_length(stmt->partTableState->partitionKey) == 1);
            Assert(list_length(stmt->partTableState->subPartitionState->partitionKey) == 1);

            foreach (cell, stmt->partTableState->partitionList) {
                stmt->partTableState->subPartitionState->partitionList =
                    GetSubPartitionDefList(stmt->partTableState, cell);
                CheckPartitionKeyForCreateTable(stmt->partTableState->subPartitionState, schema, descriptor);
            }
            stmt->partTableState->subPartitionState->partitionList = NIL;

            if (hashbucket) {
                ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                (errmsg("Un-support feature"),
                                 errdetail("The subpartition table do not support hashbucket."),
                                 errcause("The function is not implemented."),
                                 erraction("Do not set up hashbucket."))));
            }
        }
    }

    localHasOids = interpretOidsOption(stmt->options);
    descriptor->tdhasoid = (localHasOids || parentOidCount > 0);

    if ((pg_strcasecmp(storeChar, ORIENTATION_COLUMN) == 0 ||
        pg_strcasecmp(storeChar, ORIENTATION_TIMESERIES) == 0 ||
        relhasuids) && localHasOids) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Local OID column not supported in column/timeseries/hasuids store tables.")));
    }

    bool is_gc_fdw = false;
    if (!isRestoreMode && IsA(stmt, CreateForeignTableStmt) &&
        (IsSpecifiedFDW(((CreateForeignTableStmt*)stmt)->servername, GC_FDW))) {
        is_gc_fdw = true;
    }

    /*
     * Find columns with default values and prepare for insertion of the
     * defaults.  Pre-cooked (that is, inherited) defaults go into a list of
     * CookedConstraint structs that we'll pass to heap_create_with_catalog,
     * while raw defaults go into a list of RawColumnDefault structs that will
     * be processed by AddRelationNewConstraints.  (We can't deal with raw
     * expressions until we can do transformExpr.)
     *
     * We can set the atthasdef flags now in the tuple descriptor; this just
     * saves StoreAttrDefault from having to do an immediate update of the
     * pg_attribute rows.
     */
    rawDefaults = NIL;
    cookedDefaults = NIL;
    attnum = 0;

#ifdef ENABLE_MOT
    bool isMot =
        (relkind == RELKIND_FOREIGN_TABLE) ? isMOTTableFromSrvName(((CreateForeignTableStmt*)stmt)->servername) : false;
#endif

    foreach (listptr, schema) {
        ColumnDef* colDef = (ColumnDef*)lfirst(listptr);

        attnum++;

#ifdef ENABLE_MOT
        if (isMot) {
            DetermineColumnCollationForMOTTable(&descriptor->attrs[attnum - 1].attcollation);
        }
#endif

        if (is_gc_fdw) {
            if (colDef->constraints != NULL || colDef->is_not_null == true) {
                ereport(ERROR,
                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                        errmsg("column constraint on openGauss foreign tables are not supported")));
            }

            Type ctype = typenameType(NULL, colDef->typname, NULL);

            if (ctype) {
                Form_pg_type typtup = (Form_pg_type)GETSTRUCT(ctype);
                if (typtup->typrelid > 0) {
                    ereport(ERROR,
                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                            errmsg("relation type column on openGauss foreign tables are not supported")));
                }

                ReleaseSysCache(ctype);
            }
        }

        if ((colDef->raw_default != NULL || colDef->update_default != NULL) && colDef->cooked_default == NULL) {
            RawColumnDefault* rawEnt = NULL;

            if (relkind == RELKIND_FOREIGN_TABLE) {
                if (!(IsA(stmt, CreateForeignTableStmt) && (
#ifdef ENABLE_MOT
                        isMOTTableFromSrvName(((CreateForeignTableStmt*)stmt)->servername) ||
#endif
                        isPostgresFDWFromSrvName(((CreateForeignTableStmt*)stmt)->servername))))
                    ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("%s on foreign tables are not supported",
                        colDef->generatedCol ? "generated column" : "default values")));
#ifdef ENABLE_MOT
                if (IsA(stmt, CreateForeignTableStmt) &&
                    isMOTTableFromSrvName(((CreateForeignTableStmt *)stmt)->servername) && colDef->generatedCol) {
                    ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_WRONG_OBJECT_TYPE),
                        errmsg("generated column on foreign tables are not supported")));
                }
#endif
            }

            if (relkind == RELKIND_STREAM) {
                ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("%s on streams are not supported",
                    colDef->generatedCol ? "generated column" : "default values")));
            }

            Assert(colDef->cooked_default == NULL);

            rawEnt = (RawColumnDefault*)palloc(sizeof(RawColumnDefault));
            rawEnt->attnum = attnum;
            rawEnt->raw_default = colDef->raw_default;
            rawEnt->generatedCol = colDef->generatedCol;
            rawEnt->update_expr = colDef->update_default;
            rawDefaults = lappend(rawDefaults, rawEnt);
            descriptor->attrs[attnum - 1].atthasdef = true;
        } else if (colDef->cooked_default != NULL || colDef->update_default != NULL) {
            CookedConstraint* cooked = NULL;

            cooked = (CookedConstraint*)palloc(sizeof(CookedConstraint));

            if (colDef->generatedCol)
                cooked->contype = CONSTR_GENERATED;
            else
                cooked->contype = CONSTR_DEFAULT;
            cooked->conoid = InvalidOid;    /* until created */
            cooked->name = NULL;
            cooked->attnum = attnum;
            cooked->expr = colDef->cooked_default;
            cooked->skip_validation = false;
            cooked->is_local = true; /* not used for defaults */
            cooked->inhcount = 0;    /* ditto */
            cooked->is_no_inherit = false;
            cooked->update_expr = colDef->update_default;
            cooked->isdisable = false;
            cookedDefaults = lappend(cookedDefaults, cooked);
            descriptor->attrs[attnum - 1].atthasdef  = true;
        }
        if (colDef->clientLogicColumnRef != NULL) {
            CeHeapInfo *ceHeapInfo = NULL;
            ceHeapInfo = (CeHeapInfo*) palloc(sizeof(CeHeapInfo));
            ceHeapInfo->attnum = attnum;
            process_encrypted_columns(colDef, ceHeapInfo);
            ceLst = lappend (ceLst, ceHeapInfo);
        }
    }


    /*Get hash partition key based on relation distribution info*/

    bool createbucket = false;
    bool isbucket = false;
    /* restore mode */
    if (isRestoreMode) {
        /* table need hash partition */
        if (hashbucket == true) {
            /* here is dn */
            if (u_sess->storage_cxt.dumpHashbucketIds != NULL) {
                Assert(stmt->distributeby == NULL);
                createbucket = true;
            } else {
                 if (unlikely(stmt->distributeby == NULL)) {
                    ereport(ERROR,
                        (errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("distributeby is NULL.")));
                }
            }

            bucketinfo = GetRelationBucketInfo(stmt->distributeby, descriptor, &createbucket, true);
            isbucket = true;
            Assert((createbucket == true && bucketinfo->bucketlist != NULL && bucketinfo->bucketcol != NULL) ||
                   (createbucket == false && bucketinfo->bucketlist == NULL && bucketinfo->bucketcol != NULL));
        }
    } else {
        /* here is normal mode */
        /* check if the table can be hash partition */
        if (!IS_SINGLE_NODE && !IsInitdb && (relkind == RELKIND_RELATION) && !IsSystemNamespace(namespaceId) &&
            !IsCStoreNamespace(namespaceId) && (0 == pg_strcasecmp(storeChar, ORIENTATION_ROW)) &&
            (stmt->relation->relpersistence == RELPERSISTENCE_PERMANENT)) {
            if (hashbucket == true || u_sess->attr.attr_storage.enable_hashbucket) {
                if (IS_PGXC_DATANODE) {
                    createbucket = true;
                }
                bucketinfo = GetRelationBucketInfo(stmt->distributeby, descriptor, &createbucket, hashbucket);
                isbucket = true;
                Assert((bucketinfo == NULL && u_sess->attr.attr_storage.enable_hashbucket) ||
                       (createbucket == true && bucketinfo->bucketlist != NULL && bucketinfo->bucketcol != NULL) ||
                       (createbucket == false && bucketinfo->bucketlist == NULL && bucketinfo->bucketcol != NULL));
            }
        } else if (hashbucket == true) {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("The table %s do not support hash bucket", stmt->relation->relname)));
        }
    }

    if (!IsInitdb && (relkind == RELKIND_RELATION) && !IsSystemNamespace(namespaceId) &&
        !IsCStoreNamespace(namespaceId) && (pg_strcasecmp(storeChar, ORIENTATION_ROW) == 0) &&
        (stmt->relation->relpersistence == RELPERSISTENCE_PERMANENT) && !u_sess->attr.attr_storage.enable_recyclebin) {
        bool isSegmentType = (storage_type == SEGMENT_PAGE);
        if (!isSegmentType && (u_sess->attr.attr_storage.enable_segment || bucketinfo != NULL)) {
            storage_type = SEGMENT_PAGE;
            DefElem *storage_def = makeDefElem("segment", (Node *)makeString("on"));
            stmt->options = lappend(stmt->options, storage_def);
            reloptions = transformRelOptions((Datum)0, stmt->options, NULL, validnsps, true, false);
        }
    } else if (storage_type == SEGMENT_PAGE) {
        if (u_sess->attr.attr_storage.enable_recyclebin) {
            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmodule(MOD_SEGMENT_PAGE),
                errmsg("The table %s do not support segment-page storage", stmt->relation->relname),
                errdetail("Segment-page storage doest not support recyclebin"),
                errhint("set enable_recyclebin = off before using segmnet-page storage.")));
        }
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("The table %s do not support segment storage", stmt->relation->relname)));
    }

    if (storage_type == SEGMENT_PAGE) {
        Oid tbspcId = (tablespaceId == InvalidOid) ? u_sess->proc_cxt.MyDatabaseTableSpace : tablespaceId;
        uint64 tablespaceMaxSize = 0;
        bool isLimit = TableSpaceUsageManager::IsLimited(tbspcId, &tablespaceMaxSize);
        if (isLimit) {
            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmodule(MOD_SEGMENT_PAGE),
                errmsg("The table %s do not support segment-page storage", stmt->relation->relname),
                errdetail("Segment-page storage doest not support limited tablespace \"%s\"", get_tablespace_name(tbspcId)),
                errhint("use default or unlimited user defined tablespace before using segment-page storage.")));
        }
        if (stmt->partTableState) {
            ListCell* cell = NULL;
            char* partitionName = NULL;
            char* tableSpaceName = NULL;
            IntervalPartitionDefState* interValPartDef = stmt->partTableState->intervalPartDef;
            foreach (cell, stmt->partTableState->partitionList) {
                if (IsA((lfirst(cell)), IntervalPartitionDefState)) {
                    IntervalPartitionDefState* partition = (IntervalPartitionDefState*)lfirst(cell);
                    ListCell* specCell = NULL;
                    foreach(specCell, partition->intervalTablespaces) {
                        tableSpaceName = ((Value*)lfirst(specCell))->val.str;
                        CheckSegmentIsInLimitTablespace(tableSpaceName, NULL);                     
                    }
                    continue;
                } else if (IsA((lfirst(cell)), RangePartitionDefState) || IsA((lfirst(cell)), HashPartitionDefState) || IsA((lfirst(cell)), ListPartitionDefState)) {
                    PartitionDefState* partition = (PartitionDefState*)lfirst(cell);
                    tableSpaceName = partition->tablespacename;
                    partitionName = partition->partitionName;
                } else if (IsA((lfirst(cell)), RangePartitionStartEndDefState)) {
                    RangePartitionStartEndDefState* partition = (RangePartitionStartEndDefState*)lfirst(cell);
                    tableSpaceName = partition->tableSpaceName;
                    partitionName = partition->partitionName;
                } else if (IsA((lfirst(cell)), RangePartitionindexDefState)) {
                    RangePartitionindexDefState* partition = (RangePartitionindexDefState*)lfirst(cell);
                    tableSpaceName = partition->tablespace;
                    partitionName = partition->name;
                } else {
                    Assert(false);
                }
                CheckSegmentIsInLimitTablespace(tableSpaceName, partitionName);
            }

            /* check for interval limited table for segment */
            if (interValPartDef && interValPartDef->intervalTablespaces && interValPartDef->intervalTablespaces->length != 0) {
                foreach (cell, interValPartDef->intervalTablespaces) {
                    tableSpaceName = ((Value*)lfirst(cell))->val.str;
                    CheckSegmentIsInLimitTablespace(tableSpaceName, partitionName);
                }
            }
        }
    }

    if (ENABLE_DMS && !u_sess->attr.attr_common.IsInplaceUpgrade) {
        if ((t_thrd.proc->workingVersionNum < PAGE_BASED_VERSION_NUM) 
            && ((stmt->relation->relpersistence == RELPERSISTENCE_UNLOGGED)
            || (stmt->relation->relpersistence == RELPERSISTENCE_TEMP)
            || (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
            || (relkind == RELKIND_MATVIEW)
            || (relkind == RELKIND_FOREIGN_TABLE))) {
            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Page Characteristics Table not supported in current version!")));
        }

        if ((relkind == RELKIND_RELATION && storage_type != SEGMENT_PAGE
            && (stmt->relation->relpersistence != RELPERSISTENCE_UNLOGGED)
            && (stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
            && (stmt->relation->relpersistence != RELPERSISTENCE_GLOBAL_TEMP))||
            pg_strcasecmp(storeChar, ORIENTATION_ROW) != 0 ||
            pg_strcasecmp(COMPRESSION_NO, StdRdOptionsGetStringData(std_opt, compression, COMPRESSION_NO)) != 0 ||
            IsCompressedByCmprsInPgclass((RelCompressType)stmt->row_compress)) {
            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Only support segment storage ASTORE while DMS and DSS enabled.\n"
                "Compression is not supported.")));
        }
    }

    if (!IsInitdb && u_sess->attr.attr_storage.enable_segment && storage_type == SEGMENT_PAGE &&
        !CheckSegmentStorageOption(stmt->options)) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                        errmsg("Only support segment storage type while parameter enable_segment is ON.")));
    }

    /*
     * Create the relation.  Inherited defaults and constraints are passed in
     * for immediate handling --- since they don't need parsing, they can be
     * stored immediately.
     */
    relationId = heap_create_with_catalog(relname,
        namespaceId,
        tablespaceId,
        InvalidOid,
        InvalidOid,
        ofTypeId,
        ownerId,
        descriptor,
        list_concat(cookedDefaults, old_constraints),
        relkind,
        stmt->relation->relpersistence,
        relisshared,
        relisshared,
        localHasOids,
        parentOidCount,
        stmt->oncommit,
        reloptions,
        true,
        (g_instance.attr.attr_common.allowSystemTableMods || u_sess->attr.attr_common.IsInplaceUpgrade),
        stmt->partTableState,
        stmt->row_compress,
        bucketinfo,
        true,
        ceLst,
        storage_type,
        AccessShareLock,
        typaddress,
        depend_extend,
        InvalidOid,
        typbasetype);
    if (bucketinfo != NULL) {
        pfree_ext(bucketinfo->bucketcol);
        pfree_ext(bucketinfo->bucketlist);
        pfree_ext(bucketinfo);
    }

    /* Store inheritance information for new rel. */
    StoreCatalogInheritance(relationId, inheritOids);

    if (relhasuids) {
        InsertUidEntry(relationId);
    }

    /*
     * We must bump the command counter to make the newly-created relation
     * tuple visible for opening.
     */
    CommandCounterIncrement();

#ifdef PGXC
    /*
     * Add to pgxc_class.
     * we need to do this after CommandCounterIncrement
     * Distribution info is to be added under the following conditions:
     * 1. The create table command is being run on a coordinator
     * 2. The create table command is being run in restore mode and
     *    the statement contains distribute by clause.
     *    While adding a new datanode to the cluster an existing dump
     *    that was taken from a datanode is used, and
     *    While adding a new coordinator to the cluster an exiting dump
     *    that was taken from a coordinator is used.
     *    The dump taken from a datanode does NOT contain any DISTRIBUTE BY
     *    clause. This fact is used here to make sure that when the
     *    DISTRIBUTE BY clause is missing in the statemnet the system
     *    should not try to find out the node list itself.
     * 3. When the sum of shmemNumDataNodes and shmemNumCoords equals to one,
     *    the create table command is executed on datanode.In this case, we
     *    do not write created table info in pgxc_class.
     */
    if ((*t_thrd.pgxc_cxt.shmemNumDataNodes + *t_thrd.pgxc_cxt.shmemNumCoords) == 1)
        isInitdbOnDN = true;

    if ((!u_sess->attr.attr_common.IsInplaceUpgrade || !IsSystemNamespace(namespaceId)) &&
        (IS_PGXC_COORDINATOR || (isRestoreMode && stmt->distributeby != NULL && !isInitdbOnDN)) &&
        (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
            (relkind == RELKIND_STREAM && stmt->distributeby != NULL) ||
#ifdef ENABLE_MOT
            (relkind == RELKIND_FOREIGN_TABLE && (stmt->distributeby != NULL ||
                (IsA(stmt, CreateForeignTableStmt) &&
                    isMOTTableFromSrvName(((CreateForeignTableStmt*)stmt)->servername)))))) {
#else
            (relkind == RELKIND_FOREIGN_TABLE && stmt->distributeby != NULL))) {
#endif
        char* logic_cluster_name = NULL;
        PGXCSubCluster* subcluster = stmt->subcluster;
        bool isinstallationgroup = (dfsTablespace || relkind == RELKIND_FOREIGN_TABLE 
                                    || relkind == RELKIND_STREAM);
        if (in_logic_cluster()) {
            isinstallationgroup = false;
            if (subcluster == NULL) {
                logic_cluster_name = PgxcGroupGetCurrentLogicCluster();
                if (logic_cluster_name != NULL) {
                    subcluster = makeNode(PGXCSubCluster);
                    subcluster->clustertype = SUBCLUSTER_GROUP;
                    subcluster->members = list_make1(makeString(logic_cluster_name));
                }
            }
        }

        /* assemble referenceoid for slice reference table creation */
        FetchSliceReftableOid(stmt, namespaceId);

        AddRelationDistribution(relname, relationId, stmt->distributeby, subcluster, 
                                inheritOids, descriptor, isinstallationgroup, isbucket, bucketcnt);

        if (logic_cluster_name != NULL && subcluster != NULL) {
            list_free_deep(subcluster->members);
            pfree_ext(subcluster);
            pfree_ext(logic_cluster_name);
        }

        CommandCounterIncrement();
        /* Make sure locator info gets rebuilt */
        RelationCacheInvalidateEntry(relationId);
    }
    /* If no Datanodes defined, do not create foreign table  */
    if (IS_PGXC_COORDINATOR && (relkind == RELKIND_FOREIGN_TABLE || relkind == RELKIND_STREAM) 
        && u_sess->pgxc_cxt.NumDataNodes == 0) {
        ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("No Datanode defined in cluster")));
    }
#endif
    /*
     * Open the new relation and acquire exclusive lock on it.	This isn't
     * really necessary for locking out other backends (since they can't see
     * the new rel anyway until we commit), but it keeps the lock manager from
     * complaining about deadlock risks.
     */
    rel = relation_open(relationId, AccessExclusiveLock);

    ereport(DEBUG1, (errmsg("Define relation <%s.%s>, reloid: %u, relfilenode: %u", stmt->relation->schemaname,
        stmt->relation->relname, relationId, rel->rd_node.relNode)));

    if (stmt->partTableState) {
        UpdatePartKeyExpr(rel, stmt->partTableState, InvalidOid);
        if (stmt->partTableState->subPartitionState) {
            List* partitionList = relationGetPartitionList(rel, NoLock);
            ListCell* cell = NULL;
            foreach (cell, partitionList) {
                Partition partition = (Partition)(lfirst(cell));
                UpdatePartKeyExpr(rel, stmt->partTableState->subPartitionState, partition->pd_id);
            }
            releasePartitionList(rel, &partitionList, NoLock);
        }
        CommandCounterIncrement();
    }
    /*
     * Now add any newly specified column default and generation expressions
     * to the new relation.  These are passed to us in the form of raw
     * parsetrees; we need to transform them to executable expression trees
     * before they can be added. The most convenient way to do that is to
     * apply the parser's transformExpr routine, but transformExpr doesn't
     * work unless we have a pre-existing relation. So, the transformation has
     * to be postponed to this final step of CREATE TABLE.
     */
    if (rawDefaults != NULL || stmt->constraints != NULL) {
        List *tmp = AddRelationNewConstraints(rel, rawDefaults, stmt->constraints, true, true);
        list_free_ext(tmp);
    }

    /*
     * Now add any cluter key constraint for relation if has.
     */
    if (stmt->clusterKeys)
        AddRelClusterConstraints(rel, stmt->clusterKeys);

    ObjectAddressSet(address, RelationRelationId, relationId);
    /*
     * Clean up.  We keep lock on new relation (although it shouldn't be
     * visible to anyone else anyway, until commit).
     */
    relation_close(rel, NoLock);
    list_free_ext(rawDefaults);
    list_free_ext(ceLst);
    if (enable_plpgsql_gsdependency_guc() && relkind != RELKIND_TOASTVALUE) {
        if (CompileWhich() == PLPGSQL_COMPILE_NULL) {
            (void)gsplsql_build_ref_type_dependency(get_rel_type_id(relationId));
        }
    }
    return address;
}

/*
 * Emit the right error or warning message for a "DROP" command issued on a
 * non-existent relation
 */
static void DropErrorMsgNonExistent(const char* relname, char rightkind, bool missing_ok)
{
    const struct dropmsgstrings* rentry = NULL;

    for (rentry = dropmsgstringarray; rentry->kind != '\0'; rentry++) {
        if (rentry->kind == rightkind) {
            if (!missing_ok) {
                ereport(ERROR, (errcode(rentry->nonexistent_code), errmsg(rentry->nonexistent_msg, relname)));
            } else {
                ereport(NOTICE, (errmsg(rentry->skipping_msg, relname)));
                break;
            }
        }
    }

    Assert(rentry->kind != '\0'); /* Should be impossible */
}

static void does_not_exist_skipping_ParallelDDLMode(ObjectType objtype, List* objname, List* objargs, bool missing_ok)
{
    char* msg = NULL;
    char* name = NULL;
    char* args = NULL;

    StringInfo message = makeStringInfo();

    switch (objtype) {
        case OBJECT_SCHEMA:
            msg = gettext_noop("schema \"%s\" does not exist");
            name = NameListToString(objname);
            break;
        case OBJECT_FUNCTION:
            msg = gettext_noop("function %s(%s) does not exist");
            name = NameListToString(objname);
            args = TypeNameListToString(objargs);
            break;
        default: {
            pfree_ext(message->data);
            pfree_ext(message);

            ereport(ERROR,
                (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                    errmsg("unexpected object type (%d)", (int)objtype)));
        } break;
    }

    if (missing_ok) {
        if (args == NULL) {
            appendStringInfo(message, msg, name);
        } else {
            appendStringInfo(message, msg, name, args);
        }

        appendStringInfo(message, ", skipping");

        ereport(NOTICE, (errmsg("%s", message->data)));

        pfree_ext(message->data);
        pfree_ext(message);
    } else {
        pfree_ext(message->data);
        pfree_ext(message);

        if (args == NULL)
            ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg(msg, name)));
        else
            ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg(msg, name, args)));
    }
}

static List *TryGetTypeNeedDrop(Relation rel)
{
    List *typlist = NULL;

    if (rel == NULL || rel->rd_att == NULL || rel->rd_rel->relkind != RELKIND_RELATION) {
        return NULL;
    }

    TupleDesc tupDesc = rel->rd_att;
    for (int i = 0; i < tupDesc->natts; i++) {
        if (!tupDesc->attrs[i].attisdropped && type_is_set(tupDesc->attrs[i].atttypid)) {
            typlist = lappend(typlist, &tupDesc->attrs[i].atttypid);
        }
    }

    return typlist;
}

/*
 * Emit the right error message for a "DROP" command issued on a
 * relation of the wrong type
 */
static void DropErrorMsgWrongType(const char* relname, char wrongkind, char rightkind)
{
    const struct dropmsgstrings* rentry = NULL;
    const struct dropmsgstrings* wentry = NULL;

    for (rentry = dropmsgstringarray; rentry->kind != '\0'; rentry++) {
        if (rentry->kind == rightkind) {
            break;
        }
    }
    Assert(rentry->kind != '\0');

    for (wentry = dropmsgstringarray; wentry->kind != '\0'; wentry++) {
        if (wentry->kind == wrongkind) {
            break;
        }
    }
    /* wrongkind could be something we don't have in our table... */
    ereport(ERROR,
        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
            errmsg(rentry->nota_msg, relname),
            (wentry->kind != '\0') ? errhint("%s", _(wentry->drophint_msg)) : 0));
}

/*
 * PreCheckforRemoveRelation
 *		Check before implementing DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW,
 *		DROP FOREIGN TABLE, DROP MATERIALIZED VIEW to exclude objects which do not exist.
 */
ObjectAddresses* PreCheckforRemoveRelation(DropStmt* drop, StringInfo tmp_queryString, RemoteQueryExecType* exec_type)
{
    ObjectAddresses* objects = NULL;
    char relkind;
    const char* relkind_s = NULL;
    ListCell* cell = NULL;
    LOCKMODE lockmode = AccessExclusiveLock;
    bool cn_miss_relation = false;
    uint32 flags = 0;
    StringInfo relation_namelist = makeStringInfo();

    /* DROP CONCURRENTLY uses a weaker lock, and has some restrictions */
    if (drop->concurrent) {
        flags |= PERFORM_DELETION_CONCURRENTLY;
        lockmode = ShareUpdateExclusiveLock;
        Assert(drop->removeType == OBJECT_INDEX);
        if (list_length(drop->objects) != 1)
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("DROP INDEX CONCURRENTLY does not support dropping multiple objects")));
        if (drop->behavior == DROP_CASCADE)
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("DROP INDEX CONCURRENTLY does not support CASCADE")));
    }

    /*
     * First we identify all the relations, then we delete them in a single
     * performMultipleDeletions() call.  This is to avoid unwanted DROP
     * RESTRICT errors if one of the relations depends on another.
     */
    /* Determine required relkind */
    switch (drop->removeType) {
        case OBJECT_TABLE:
            relkind = RELKIND_RELATION;
            relkind_s = "TABLE";
            break;

        case OBJECT_INDEX:
            relkind = RELKIND_INDEX;
            relkind_s = "INDEX";
            break;

        case OBJECT_SEQUENCE:
            relkind = RELKIND_SEQUENCE;
            relkind_s = "SEQUENCE";
            break;
        
        case OBJECT_LARGE_SEQUENCE:
            relkind = RELKIND_LARGE_SEQUENCE;
            relkind_s = "LARGE SEQUENCE";
            break;

        case OBJECT_VIEW:
            relkind = RELKIND_VIEW;
            relkind_s = "VIEW";
            break;

        case OBJECT_CONTQUERY:
            relkind = RELKIND_CONTQUERY;
            relkind_s = "CONTVIEW";
            break;

        case OBJECT_MATVIEW:
            relkind = RELKIND_MATVIEW;
            relkind_s = "MATERIALIZED VIEW";
            break;

        case OBJECT_FOREIGN_TABLE:
            relkind = RELKIND_FOREIGN_TABLE;
            relkind_s = "FOREIGN TABLE";
            break;
        case OBJECT_STREAM:
            relkind = RELKIND_STREAM;
            relkind_s = "STREAM";
            break;
        default: {
            ereport(ERROR,
                (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                    errmsg("unrecognized drop object type: %d", (int)drop->removeType)));
            relkind = 0;    /* keep compiler quiet */
            relkind_s = ""; /* keep compiler quiet */
        } break;
    }

    objects = new_object_addresses();

    foreach (cell, drop->objects) {
        RangeVar* rel = makeRangeVarFromNameList((List*)lfirst(cell));
        Oid relOid;
        ObjectAddress obj;
        Relation delrel;
        struct DropRelationCallbackState state;

        /*
         * These next few steps are a great deal like relation_openrv, but we
         * don't bother building a relcache entry since we don't need it.
         *
         * Check for shared-cache-inval messages before trying to access the
         * relation.  This is needed to cover the case where the name
         * identifies a rel that has been dropped and recreated since the
         * start of our transaction: if we don't flush the old syscache entry,
         * then we'll latch onto that entry and suffer an error later.
         */
        AcceptInvalidationMessages();

        /* Look up the appropriate relation using namespace search. */
        state.relkind = relkind;
        state.heapOid = InvalidOid;
        state.concurrent = drop->concurrent;

        /*
         * Redis in online expansion will get AccessShareLock for resizing table.
         * We use ExclusiveLock here to avoid collisions with AccessShareLock so
         * that we can send drop query to first CN and cancel the redis thread in
         * first CN.
         */
        if (CheckRangeVarInRedistribution(rel)) {
            if (lockmode == AccessExclusiveLock)
                lockmode = ExclusiveLock;
        } else {
            /* Reset lockmode in the next loop for normal table not in redis. */
            if (lockmode == ExclusiveLock)
                lockmode = AccessExclusiveLock;
        }

        relOid = RangeVarGetRelidExtended(
            rel, lockmode, true, false, false, false, RangeVarCallbackForDropRelation, (void*)&state);

        /*
         * Relation not found.
         *
         * For "DROP TABLE/INDEX/VIEW/...", just ERROR
         *
         * For "DROP TABLE/INDEX/VIEW/... IF EXISTS ...",
         * local CN: rewrite the querystring without the not-found relations
         * remote nodes: should not happen since local CN does have the relation
         * so that the query is passed down. In this case, we just ERROR.
         * In maintenance mode, we pass down the original querystring anyway.
         */
        if (!OidIsValid(relOid)) {
            bool missing_ok = drop->missing_ok;

            /* for the inconsistent index on nodes cause by creating index concurrently, missing is ok on local node */
            if (!u_sess->attr.attr_common.xc_maintenance_mode && relkind != RELKIND_INDEX) {
                if (IS_PGXC_COORDINATOR && !IsConnFromCoord())
                    cn_miss_relation = true;
                else
                    missing_ok = false;
            }

            DropErrorMsgNonExistent(rel->relname, relkind, missing_ok);

            continue;
        }

        TrForbidAccessRbObject(RelationRelationId, relOid, rel->relname);

        delrel = try_relation_open(relOid, NoLock);
        /*
         * Open up drop table command for table being redistributed right now.
         *
         * During online expansion time, we only allow to drop object when
         * the object is a table and the target table is not in read only mode
         */
        if (delrel != NULL && !u_sess->attr.attr_sql.enable_cluster_resize &&
            (RelationInClusterResizingReadOnly(delrel) ||
            (RelationInClusterResizing(delrel) && drop->removeType != OBJECT_TABLE))) {
            ereport(ERROR,
                (errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
                    errmsg("%s is redistributing, please retry later.", delrel->rd_rel->relname.data)));
        }

        if (delrel != NULL) {
            relation_close(delrel, NoLock);
        }

        /* OK, we're ready to delete this one */
        obj.classId = RelationRelationId;
        obj.objectId = relOid;
        obj.objectSubId = 0;

        add_exact_object_address(&obj, objects);

        /* Record relations that do exist on local CN. */
        char* relation_name = NameListToQuotedString((List*)lfirst(cell));
        appendStringInfo(relation_namelist, relation_namelist->data[0] ? ", %s" : "%s", relation_name);
        pfree_ext(relation_name);

        if (OidIsValid(state.heapOid)) {
            UnlockRelationOid(state.heapOid, state.concurrent ? ShareUpdateExclusiveLock : AccessExclusiveLock);
        }

        UnlockRelationOid(relOid, lockmode);
    }

    /*
     * Fabricate a new DROP TABLE/INDEX/VIEW/... querystring with relations found on local CN.
     * If no such relations, then there is nothing to be done on remote nodes.
     */
    if (cn_miss_relation) {
        if (relation_namelist->data[0])
            appendStringInfo(tmp_queryString,
                "DROP %s IF EXISTS %s %s",
                relkind_s,
                relation_namelist->data,
                drop->behavior == DROP_CASCADE ? "CASCADE" : "RESTRICT");
        else
            *exec_type = EXEC_ON_NONE;
    }

    pfree_ext(relation_namelist->data);
    pfree_ext(relation_namelist);

    return objects;
}

/*
 * RemoveRelationsonMainExecCN
 *		Implements DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW, DROP MATERIALIZED VIEW
 *		DROP FOREIGN TABLE on main execute coordinator.
 */
void RemoveRelationsonMainExecCN(DropStmt* drop, ObjectAddresses* objects)
{
    uint32 flags = 0;

    /* DROP CONCURRENTLY uses a weaker lock, and has some restrictions */
    if (drop->concurrent) {
        flags |= PERFORM_DELETION_CONCURRENTLY;
        Assert(drop->removeType == OBJECT_INDEX);
        if (list_length(drop->objects) != 1)
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("DROP INDEX CONCURRENTLY does not support dropping multiple objects")));
        if (drop->behavior == DROP_CASCADE)
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("DROP INDEX CONCURRENTLY does not support CASCADE")));
    }

    for (int i = 0; i < objects->numrefs; i++) {
        const ObjectAddress* thisobj = objects->refs + i;

        Assert(thisobj->objectId);

        if (thisobj->classId == RelationRelationId) {
            /*
             * In DROP INDEX CONCURRENTLY, take only ShareUpdateExclusiveLock on
             * the index for the moment.  index_drop() will promote the lock once
             * it's safe to do so.  In all other cases we need full exclusive
             * lock.
             */
            if (flags & PERFORM_DELETION_CONCURRENTLY) {
                LockRelationOid(thisobj->objectId, ShareUpdateExclusiveLock);
            } else {
                LockRelationOid(thisobj->objectId, AccessExclusiveLock);
            }
#ifdef ENABLE_MULTIPLE_NODES
            PreventDDLIfTsdbDisabled(thisobj->objectId);
            /* check if the rel is timeseries rel, if so, remove jobs */
            RemoveJobsWhenRemoveRelation(thisobj->objectId);
#endif  /* ENABLE_MULTIPLE_NODES */
        } else {
            /* assume we should lock the whole object not a sub-object */
            LockDatabaseObject(thisobj->classId, thisobj->objectId, 0, AccessExclusiveLock);
        }
    }

    performMultipleDeletions(objects, drop->behavior, flags);
}

/*
 * RemoveRelations
 *		Implements DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW,
 *		DROP FOREIGN TABLE on datanodes and none main execute coordinator.
 */
void RemoveRelations(DropStmt* drop, StringInfo tmp_queryString, RemoteQueryExecType* exec_type)
{
    ObjectAddresses* objects = NULL;
    char relkind;
    const char* relkind_s = NULL;
    ListCell* cell = NULL;
    uint32 flags = 0;
    LOCKMODE lockmode = AccessExclusiveLock;
    bool cn_miss_relation = false;
    StringInfo relation_namelist = makeStringInfo();
    char relPersistence;
    List *typlist = NULL;

    /* DROP CONCURRENTLY uses a weaker lock, and has some restrictions */
    if (drop->concurrent) {
        lockmode = ShareUpdateExclusiveLock;
        Assert(drop->removeType == OBJECT_INDEX);
        if (list_length(drop->objects) != 1)
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("DROP INDEX CONCURRENTLY does not support dropping multiple objects")));
        if (drop->behavior == DROP_CASCADE)
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("DROP INDEX CONCURRENTLY does not support CASCADE")));
    }

    /*
     * First we identify all the relations, then we delete them in a single
     * performMultipleDeletions() call.  This is to avoid unwanted DROP
     * RESTRICT errors if one of the relations depends on another.
     */
    /* Determine required relkind */
    switch (drop->removeType) {
        case OBJECT_TABLE:
            relkind = RELKIND_RELATION;
            relkind_s = "TABLE";
            break;

        case OBJECT_INDEX:
            relkind = RELKIND_INDEX;
            relkind_s = "INDEX";
            break;

        case OBJECT_SEQUENCE:
            relkind = RELKIND_SEQUENCE;
            relkind_s = "SEQUENCE";
            break;

        case OBJECT_LARGE_SEQUENCE:
            relkind = RELKIND_LARGE_SEQUENCE;
            relkind_s = "LARGE SEQUENCE";
            break;

        case OBJECT_VIEW:
            relkind = RELKIND_VIEW;
            relkind_s = "VIEW";
            break;

        case OBJECT_CONTQUERY:
            relkind = RELKIND_CONTQUERY;
            relkind_s = "CONTVIEW";
            break;

        case OBJECT_MATVIEW:
            relkind = RELKIND_MATVIEW;
            relkind_s = "MATERIALIZED VIEW";
            break;

        case OBJECT_FOREIGN_TABLE:
            relkind = RELKIND_FOREIGN_TABLE;
            relkind_s = "FOREIGN TABLE";
            break;

        case OBJECT_STREAM:
            relkind = RELKIND_STREAM;
            relkind_s = "STREAM";
            break;

        default: {
            ereport(ERROR,
                (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                    errmsg("unrecognized drop object type: %d", (int)drop->removeType)));

            relkind = 0;    /* keep compiler quiet */
            relkind_s = ""; /* keep compiler quiet */
        } break;
    }

    /* Lock and validate each relation; build a list of object addresses */
    objects = new_object_addresses();

    int cnt = 0; /* counter used to identify the round of the objects */
#ifdef ENABLE_MULTIPLE_NODES
    bool ts_drop_idx = false;
    Oid idx_id = InvalidOid;
#endif
    foreach (cell, drop->objects) {
        RangeVar* rel = makeRangeVarFromNameList((List*)lfirst(cell));
        Oid relOid;
        ObjectAddress obj;
        Relation delrel;
        struct DropRelationCallbackState state = {0};
        cnt++;

        /*
         * These next few steps are a great deal like relation_openrv, but we
         * don't bother building a relcache entry since we don't need it.
         *
         * Check for shared-cache-inval messages before trying to access the
         * relation.  This is needed to cover the case where the name
         * identifies a rel that has been dropped and recreated since the
         * start of our transaction: if we don't flush the old syscache entry,
         * then we'll latch onto that entry and suffer an error later.
         */
        AcceptInvalidationMessages();

        /* Look up the appropriate relation using namespace search. */
        state.relkind = relkind;
        state.heapOid = InvalidOid;
        state.concurrent = drop->concurrent;

#ifdef ENABLE_MULTIPLE_NODES
        /*
         * If the case for timeseries table with drop index, transfrom target object
         * from ts rel to the correct tag rel index(es) objects. Mark this scenario
         * by ts_drop_idx.
         */
        char idx_name[NAMEDATALEN] = {0};
        Oid nspname;
        /* if the object in first round is a timeseries table */
        if (drop->removeType == OBJECT_INDEX && cnt == 1 && check_ts_idx_ddl(rel, &idx_id, &nspname)) {
            ts_drop_idx = true;
        }
        if (ts_drop_idx) {
            if (cnt == 1 && list_length(drop->objects) == 1) {
                /* In upgrade scenario, drop the old multi-column index */
                char tag_relname[NAMEDATALEN] = {0};
                get_outdate_tag_relname(tag_relname, idx_id);
                /* add timeseries rel's distribution */
                add_ts_tag_distribution(tag_relname, nspname, rel->relname);
                get_ts_idx_tgt(idx_name, &idx_id, true);
            } else if (cnt > 1) {
                /* drop specified index on ts tag table */
                get_ts_idx_tgt(idx_name, &idx_id, true, false, rel->relname);
            } else {
                /* the first turn to drop specified index, skip */
                continue;
            }
            /* timeseries indexes are all in cstore namespace */
            rel->schemaname = "cstore";
            rel->relname = idx_name;
        }
#endif

        relOid = RangeVarGetRelidExtended(
            rel, lockmode, true, false, false, false, RangeVarCallbackForDropRelation, (void*)&state);

        /*
         * Relation not found.
         *
         * For "DROP TABLE/INDEX/VIEW/...", just ERROR
         *
         * For "DROP TABLE/INDEX/VIEW/... IF EXISTS ...",
         * local CN: rewrite the querystring without the not-found relations
         * remote nodes: should not happen since local CN does have the relation
         * so that the query is passed down. In this case, we just ERROR.
         * In maintenance mode, we pass down the original querystring anyway.
         */
        if (!OidIsValid(relOid)) {
            bool missing_ok = drop->missing_ok;

            /* for the inconsistent index on nodes cause by creating index concurrently, missing is ok on local node */
            if (!u_sess->attr.attr_common.xc_maintenance_mode && !IS_SINGLE_NODE && relkind != RELKIND_INDEX) {
                if (IS_PGXC_COORDINATOR && !IsConnFromCoord())
                    cn_miss_relation = true;
                else if (!ENABLE_ROUTER_DN) // in router, drop if exists should enable on dn.
                    missing_ok = false;
            }

            DropErrorMsgNonExistent(rel->relname, relkind, missing_ok);
            continue;
        }

#ifdef ENABLE_HTAP
        if (relkind == RELKIND_RELATION && CheckIsInTrans() && RelHasImcs(relOid)) {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("Dropping Rel: %d which enabled imcstore in transation, please unpopulate first.", relOid)));
        }
#endif

#ifdef ENABLE_MULTIPLE_NODES
        /* check if the rel is timeseries rel, if so, remove jobs */
        if (relkind == RELKIND_RELATION) {
            PreventDDLIfTsdbDisabled(relOid);
            RemoveJobsWhenRemoveRelation(relOid);
        }
#endif  /* ENABLE_MULTIPLE_NODES */
        if (relkind == RELKIND_RELATION) {
            DeleteUidEntry(relOid);
        } else if (relkind == RELKIND_LARGE_SEQUENCE && CheckSeqOwnedByAutoInc(relOid)) {
            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("cannot drop sequence owned by auto_increment column")));
        }

        delrel = try_relation_open(relOid, NoLock);
        /*Not allow to drop mlog*/
        if (relkind == RELKIND_RELATION && delrel != NULL && ISMLOG(delrel->rd_rel->relname.data)) {
            /*If we can find a base table, it is mlog.*/
            if (get_matview_mlog_baserelid(relOid)!= InvalidOid)
                ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("Use 'Drop table' to drop mlog table %s is not allowed.",delrel->rd_rel->relname.data)));
        }
        /*Not allow to drop mlog index*/
        if (relkind == RELKIND_INDEX && delrel != NULL && ISMLOG(delrel->rd_rel->relname.data)) {
            /*If we can find a base table, it is mlog.*/
            if (get_matview_mlog_baserelid(delrel->rd_index->indrelid) != InvalidOid)
                ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("Use 'Drop index' to drop index %s of mlog table is not allowed.",
                        delrel->rd_rel->relname.data)));
        }

        /*
         * Open up drop table command for table being redistributed right now.
         *
         * During online expansion time, we only allow to drop object when
         * the object is a table and the target table is not in read only mode
         */
        if (delrel != NULL && !u_sess->attr.attr_sql.enable_cluster_resize &&
            (RelationInClusterResizingReadOnly(delrel) ||
            (RelationInClusterResizing(delrel) && drop->removeType != OBJECT_TABLE))) {
            ereport(ERROR,
                (errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
                    errmsg("%s is redistributing, please retry later.", delrel->rd_rel->relname.data)));
        }

        // cstore relation doesn't support concurrent INDEX now.
         if (drop->concurrent == true && delrel != NULL && OidIsValid(delrel->rd_rel->relcudescrelid)) {
             ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                 errmsg("column store table does not support concurrent INDEX yet"),
                 errdetail("The feature is not currently supported")));
         }

        typlist = TryGetTypeNeedDrop(delrel);

        if (delrel != NULL) {
            relation_close(delrel, NoLock);
        }

        relPersistence = get_rel_persistence(relOid);
        if (drop->concurrent
            && !(relPersistence == RELPERSISTENCE_TEMP
            || relPersistence == RELPERSISTENCE_GLOBAL_TEMP)) {
            Assert(list_length(drop->objects) == 1 && drop->removeType == OBJECT_INDEX);
            flags |= PERFORM_DELETION_CONCURRENTLY;
        }

        /* OK, we're ready to delete this one */
        obj.classId = RelationRelationId;
        obj.objectId = relOid;
        obj.objectSubId = 0;

        TrForbidAccessRbObject(obj.classId, obj.objectId, rel->relname);

        add_exact_object_address(&obj, objects);
        if (relkind == RELKIND_RELATION) {
            add_type_object_address(typlist, objects);
        }

        /* Record relations that do exist on local CN. */
        char* relation_name = NameListToQuotedString((List*)lfirst(cell));
        appendStringInfo(relation_namelist, relation_namelist->data[0] ? ", %s" : "%s", relation_name);
        pfree_ext(relation_name);
    }

    /*
     * Fabricate a new DROP TABLE/INDEX/VIEW/... querystring with relations found on local CN.
     * If no such relations, then there is nothing to be done on remote nodes.
     */
    if ((IS_PGXC_COORDINATOR && !IsConnFromCoord()) || cn_miss_relation) {
        if (relation_namelist->data[0])
            appendStringInfo(tmp_queryString,
                "DROP %s IF EXISTS %s %s",
                relkind_s,
                relation_namelist->data,
                drop->behavior == DROP_CASCADE ? "CASCADE" : "RESTRICT");
        else
            *exec_type = EXEC_ON_NONE;
    }

    if (TrCheckRecyclebinDrop(drop, objects)) {
        /* Here we use Recyclebin-based-Drop. */
        TrDrop(drop, objects, drop->behavior);
    } else {
        /* Here we really delete them. */
        performMultipleDeletions(objects, drop->behavior, flags);
    }

    free_object_addresses(objects);
    pfree_ext(relation_namelist->data);
    pfree_ext(relation_namelist);
}

/*
 * PreCheckforRemoveObjects
 *		Check before implementing DROP SCHEMA, DROP FUNCTION to exclude objects which do not exist.
 */
ObjectAddresses* PreCheckforRemoveObjects(
    DropStmt* stmt, StringInfo tmp_queryString, RemoteQueryExecType* exec_type, bool isFirstNode, bool is_securityadmin)
{
    ObjectAddresses* objects = NULL;
    ListCell* cell1 = NULL;
    ListCell* cell2 = NULL;
    bool skip_check = false;
    bool cn_miss_relation = false;
    StringInfo relation_namelist = makeStringInfo();
    const char* relkind_s = NULL;

    if (stmt->removeType == OBJECT_SCHEMA)
        relkind_s = "SCHEMA";
    else if (stmt->removeType == OBJECT_FUNCTION)
        relkind_s = "FUNCATION";
    else {
        ereport(ERROR,
            (errcode(ERRCODE_UNEXPECTED_NODE_STATE), errmsg("unexpected object type (%d)", (int)stmt->removeType)));
    }

    objects = new_object_addresses();

    foreach (cell1, stmt->objects) {
        ObjectAddress address;
        List* objname = (List*)lfirst(cell1);
        List* objargs = NIL;
        Relation relation = NULL;
        Oid namespaceId;

        if (stmt->arguments) {
            cell2 = (!cell2 ? list_head(stmt->arguments) : lnext(cell2));
            objargs = (List*)lfirst(cell2);
        }

        /* Get an ObjectAddress for the object. */
        address =
            get_object_address(stmt->removeType, objname, objargs, &relation, AccessExclusiveLock, stmt->missing_ok);

        /* Issue NOTICE if supplied object was not found. */
        if (!OidIsValid(address.objectId)) {
            bool missing_ok = stmt->missing_ok;

            if (!u_sess->attr.attr_common.xc_maintenance_mode) {
                if (IS_PGXC_COORDINATOR && !IsConnFromCoord())
                    cn_miss_relation = true;
                else
                    missing_ok = false;
            }

            does_not_exist_skipping_ParallelDDLMode(stmt->removeType, objname, objargs, missing_ok);

            continue;
        }

        /*
         * Although COMMENT ON FUNCTION, SECURITY LABEL ON FUNCTION, etc. are
         * happy to operate on an aggregate as on any other function, we have
         * historically not allowed this for DROP FUNCTION.
         */
        if (stmt->removeType == OBJECT_FUNCTION) {
            Oid funcOid = address.objectId;
            if (IsMaskingFunctionOid(funcOid) && !u_sess->attr.attr_common.IsInplaceUpgrade) {
                ereport(ERROR, (errmodule(MOD_FUNCTION),
                    errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                        errmsg("function \"%s\" is a masking function, it can not be droped", 
                            NameListToString(objname)),
                                errdetail("cannot drop masking function")));
            }
            HeapTuple tup;

            /* if the function is a builtin function, its oid is less than 10000.
             * we can't allow drop the builtin functions
             */
            if (IsSystemObjOid(funcOid) && u_sess->attr.attr_common.IsInplaceUpgrade == false) {
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                        errmsg(
                            "function \"%s\" is a builtin function,it can not be droped", NameListToString(objname))));
            }

            tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid));
            if (!HeapTupleIsValid(tup)) {
                /* should not happen */
                ereport(ERROR,
                    (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for function %u", funcOid)));
            }

            if (((Form_pg_proc)GETSTRUCT(tup))->proisagg)
                ereport(ERROR,
                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                        errmsg("\"%s\" is an aggregate function", NameListToString(objname)),
                        errhint("Use DROP AGGREGATE to drop aggregate functions.")));

            CacheInvalidateFunction(funcOid, InvalidOid);
            ReleaseSysCache(tup);
        }

        //@Temp Table. myTempNamespace and myTempToastNamespace's owner is
        // bootstrap user, so can not be deleted by ordinary user. to ensuer this two
        // schema be deleted on session quiting, we should bypass acl check when
        // drop my own temp namespace
        if (stmt->removeType == OBJECT_SCHEMA && (address.objectId == u_sess->catalog_cxt.myTempNamespace ||
                                                     address.objectId == u_sess->catalog_cxt.myTempToastNamespace))
            skip_check = true;

        if (!skip_check) {
            AclResult aclresult = ACLCHECK_NO_PRIV;
            switch (stmt->removeType) {
                case OBJECT_SCHEMA:
                    aclresult = pg_namespace_aclcheck(address.objectId, GetUserId(), ACL_DROP);
                    break;
                case OBJECT_FUNCTION:
                    aclresult = pg_proc_aclcheck(address.objectId, GetUserId(), ACL_DROP);
                    break;
                default:
                    break;
            }
            skip_check = (aclresult == ACLCHECK_OK) ? true : false;
        }
        
        /* Check permissions. */
        namespaceId = get_object_namespace(&address);
        if ((!is_securityadmin) && (!skip_check) &&
            (!OidIsValid(namespaceId) || !pg_namespace_ownercheck(namespaceId, GetUserId())))
            check_object_ownership(GetUserId(), stmt->removeType, address, objname, objargs, relation);

        /* Release any relcache reference count, but keep lock until commit. */
        if (relation)
            heap_close(relation, NoLock);

        add_exact_object_address(&address, objects);

        /* Record relations that do exist on local CN. */
        char* relation_name = NameListToQuotedString((List*)lfirst(cell1));
        appendStringInfo(relation_namelist, relation_namelist->data[0] ? ", %s" : "%s", relation_name);
        pfree_ext(relation_name);

        if (isFirstNode)
            continue;

        Assert(address.classId != RelationRelationId);

        if (IsSharedRelation(address.classId))
            UnlockSharedObject(address.classId, address.objectId, 0, AccessExclusiveLock);
        else
            UnlockDatabaseObject(address.classId, address.objectId, 0, AccessExclusiveLock);
    }

    /*
     * Fabricate a new DROP TABLE/INDEX/VIEW/... querystring with relations found on local CN.
     * If no such relations, then there is nothing to be done on remote nodes.
     */
    if (cn_miss_relation) {
        if (relation_namelist->data[0])
            appendStringInfo(tmp_queryString,
                "DROP %s IF EXISTS %s %s",
                relkind_s,
                relation_namelist->data,
                stmt->behavior == DROP_CASCADE ? "CASCADE" : "RESTRICT");
        else
            *exec_type = EXEC_ON_NONE;
    }

    pfree_ext(relation_namelist->data);
    pfree_ext(relation_namelist);

    return objects;
}

/*
 * RemoveObjectsonMainExecCN
 *		Implements DROP SCHEMA, DROP FUNCTION
 *		on main execute coordinator.
 */
void RemoveObjectsonMainExecCN(DropStmt* drop, ObjectAddresses* objects, bool isFirstNode)
{
    if (!isFirstNode) {
        for (int i = 0; i < objects->numrefs; i++) {
            const ObjectAddress* thisobj = objects->refs + i;

            Assert(thisobj->classId != RelationRelationId);
            Assert(thisobj->objectId);

            if (IsSharedRelation(thisobj->classId))
                LockSharedObject(thisobj->classId, thisobj->objectId, 0, AccessExclusiveLock);
            else
                LockDatabaseObject(thisobj->classId, thisobj->objectId, 0, AccessExclusiveLock);
        }
    }

    /* Here we really delete them. */
    performMultipleDeletions(objects, drop->behavior, 0);
}

/*
 * Check whether we have the permission to process the classform.
 */
static bool CheckClassFormPermission(Form_pg_class classform)
{
    if (g_instance.attr.attr_common.allowSystemTableMods || u_sess->attr.attr_common.IsInplaceUpgrade ||
        !IsSystemClass(classform)) {
        return true;
    }
    return false;
}

/* Allow DROP to table owner, schema owner or users who have DROP privilege of the target object */
void DropRelationPermissionCheck(char relkind, Oid relOid, Oid nspOid, const char* relname)
{
    AclResult aclresult;
    if (relkind == RELKIND_INDEX) {
        Oid tableoid = IndexGetRelation(relOid, false);
        aclresult = pg_class_aclcheck(tableoid, GetUserId(), ACL_INDEX);
        bool anyResult = false;
        if (aclresult != ACLCHECK_OK && !IsSysSchema(GetNamespaceIdbyRelId(tableoid))) {
            anyResult = HasSpecAnyPriv(GetUserId(), DROP_ANY_INDEX, false);
        }
        aclresult = anyResult ? ACLCHECK_OK : aclresult;
    } else {
        aclresult = pg_class_aclcheck(relOid, GetUserId(), ACL_DROP);
    }

    if (aclresult != ACLCHECK_OK && !pg_class_ownercheck(relOid, GetUserId()) &&
        !pg_namespace_ownercheck(nspOid, GetUserId())) {
        aclcheck_error(aclresult, ACL_KIND_CLASS, relname);
    }
}

static bool IsPartitionDeltaCudesc(Oid relOid)
{
#define PARTITION_DELTA_NAME "pg_delta_part_"
#define PARTITION_CUDESC_NAME "pg_cudesc_part_"

    int attnum;
    bool found = false;
    ScanKeyData scanKey[1];
    TableScanDesc scan;
    Relation pgpartition = NULL;
    Relation rel = NULL;

    rel = try_relation_open(relOid, AccessShareLock);
    if (!RelationIsValid(rel)) {
        return false;
    }

    const char *relname = RelationGetRelationName(rel);
    if (strncmp(relname, PARTITION_DELTA_NAME, strlen(PARTITION_DELTA_NAME)) == 0) {
        attnum = Anum_pg_partition_reltoastrelid;
    } else if (strncmp(relname, PARTITION_CUDESC_NAME, strlen(PARTITION_CUDESC_NAME)) == 0) {
        attnum = Anum_pg_partition_relcudescrelid;
    } else {
        heap_close(rel, AccessShareLock);
        return false;
    }

    ScanKeyInit(&scanKey[0], attnum, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(rel->rd_id));
    pgpartition = heap_open(PartitionRelationId, AccessShareLock);
    scan = tableam_scan_begin(pgpartition, SnapshotNow, 1, scanKey);
    if (tableam_scan_getnexttuple(scan, ForwardScanDirection)) {
        found = true;
    }
    tableam_scan_end(scan);
    heap_close(pgpartition, AccessShareLock);
    heap_close(rel, AccessShareLock);

    return found;
}

/*
 * Before acquiring a table lock, check whether we have sufficient rights.
 * In the case of DROP INDEX, also try to lock the table before the index.
 */
static void RangeVarCallbackForDropRelation(
    const RangeVar* rel, Oid relOid, Oid oldRelOid, bool target_is_partition, void* arg)
{
    HeapTuple tuple;
    struct DropRelationCallbackState* state;
    char relkind;
    Form_pg_class classform;
    LOCKMODE heap_lockmode;
    bool invalid_system_index;

    state = (struct DropRelationCallbackState*)arg;
    relkind = state->relkind;
    heap_lockmode = state->concurrent ? ShareUpdateExclusiveLock : AccessExclusiveLock;
    invalid_system_index = false;

    if (target_is_partition)
        heap_lockmode = AccessShareLock;
    /*
     * If we previously locked some other index's heap, and the name we're
     * looking up no longer refers to that relation, release the now-useless
     * lock.
     */
    if (relOid != oldRelOid && OidIsValid(state->heapOid)) {
        UnlockRelationOid(state->heapOid, heap_lockmode);
        state->heapOid = InvalidOid;
    }

    /* Didn't find a relation, so no need for locking or permission checks. */
    if (!OidIsValid(relOid))
        return;

    tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
    if (!HeapTupleIsValid(tuple))
        return; /* concurrently dropped, so nothing to do */
    classform = (Form_pg_class)GETSTRUCT(tuple);

    char expected_relkind = classform->relkind;
    if (classform->relkind == RELKIND_GLOBAL_INDEX) {
        expected_relkind = RELKIND_INDEX;
    }
    bool flag = !(expected_relkind == RELKIND_STREAM && relkind == RELKIND_FOREIGN_TABLE)
                && !(expected_relkind == RELKIND_CONTQUERY && relkind == RELKIND_VIEW)
                && (expected_relkind != relkind) && !(u_sess->attr.attr_common.IsInplaceUpgrade &&
                relkind == RELKIND_RELATION && expected_relkind == RELKIND_TOASTVALUE);
    if (flag) {
        DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
    }

    /*
    * Check the case of a system index that might have been invalidated by a
    * failed concurrent process and allow its drop. For the time being, this
    * only concerns indexes of toast relations that became invalid during a
    * REINDEX CONCURRENTLY process.
    */
    if(IsSystemClass(classform)&&relkind == RELKIND_INDEX) {
        HeapTuple locTuple;
        Form_pg_index indexform;
        bool indisvalid;

        locTuple = SearchSysCache1(INDEXRELID,ObjectIdGetDatum(relOid));
        if(!HeapTupleIsValid(locTuple)) {
            ReleaseSysCache(tuple);
            return;
        }

        indexform = (Form_pg_index) GETSTRUCT(locTuple);
        indisvalid = indexform->indisvalid;
        ReleaseSysCache(locTuple);

        /*Mark object as being an invaild index of system catalogs*/
        if(!indisvalid)
            invalid_system_index = true;
    }

    if (IsPartitionDeltaCudesc(relOid)) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("cannot drop relation \"%s\", it is a partition delta/cudesc table", rel->relname)));
    }

    /* Permission Check */
    DropRelationPermissionCheck(relkind, relOid, classform->relnamespace, rel->relname);

    if (!invalid_system_index && !CheckClassFormPermission(classform)) {
        ereport(ERROR,
            (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                errmsg("permission denied: \"%s\" is a system catalog", rel->relname)));
    }

    ReleaseSysCache(tuple);

    /*
     * In DROP INDEX, attempt to acquire lock on the parent table before
     * locking the index.  index_drop() will need this anyway, and since
     * regular queries lock tables before their indexes, we risk deadlock if
     * we do it the other way around.  No error if we don't find a pg_index
     * entry, though --- the relation may have been dropped.
     */
    if ((relkind == RELKIND_INDEX || relkind == RELKIND_GLOBAL_INDEX) && relOid != oldRelOid) {
        state->heapOid = IndexGetRelation(relOid, true);
        if (OidIsValid(state->heapOid))
            LockRelationOid(state->heapOid, heap_lockmode);
    }
}

void TruncateOnePart(Relation rel, HeapTuple tup)
{
    Oid toastOid = ((Form_pg_partition)GETSTRUCT(tup))->reltoastrelid;
    Relation toastRel = NULL;
    MultiXactId minmulti = GetOldestMultiXactId();

    Oid partOid = HeapTupleGetOid(tup);
    Partition p = partitionOpen(rel, partOid, AccessExclusiveLock);
    PartitionSetNewRelfilenode(rel, p, u_sess->utils_cxt.RecentXmin,
        RelationIsColStore(rel) ? InvalidMultiXactId : minmulti);

    /* process the toast table */
    if (OidIsValid(toastOid)) {
        Assert(rel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED);
        toastRel = heap_open(toastOid, AccessExclusiveLock);
        RelationSetNewRelfilenode(toastRel, u_sess->utils_cxt.RecentXmin, minmulti);
        heap_close(toastRel, AccessExclusiveLock);
    }
    partitionClose(rel, p, AccessExclusiveLock);

    /* report truncate partition to PgStatCollector */
    Oid statFlag = RelationIsPartitionOfSubPartitionTable(rel) ? partid_get_parentid(rel->rd_id) : rel->rd_id;
    pgstat_report_truncate(partOid, statFlag, rel->rd_rel->relisshared);
}

/*
 * ExecuteTruncate
 *		Executes a TRUNCATE command.
 *
 * This is a multi-relation truncate.  We first open and grab exclusive
 * lock on all relations involved, checking permissions and otherwise
 * verifying that the relation is OK for truncation.  In CASCADE mode,
 * relations having FK references to the targeted relations are automatically
 * added to the group; in RESTRICT mode, we check that all FK references are
 * internal to the group that's being truncated.  Finally all the relations
 * are truncated and reindexed.
 */
#ifdef PGXC
void ExecuteTruncate(TruncateStmt* stmt, const char* sql_statement)
#else
void ExecuteTruncate(TruncateStmt* stmt)
#endif
{
    List* rels = NIL;
    List* relids = NIL;
    List* relids_logged = NIL;
    List* rels_in_redis = NIL;
    ListCell* cell = NULL;
#ifdef PGXC
    char* FirstExecNode = NULL;
    bool isFirstNode = false;

    if (TrCheckRecyclebinTruncate(stmt)) {
        TrTruncate(stmt);
        return;
    }

    if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
        FirstExecNode = find_first_exec_cn();
        isFirstNode = (strcmp(FirstExecNode, g_instance.attr.attr_common.PGXCNodeName) == 0);
    }
#endif

#ifdef PGXC
    if (stmt->restart_seqs)
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("PGXC does not support RESTART IDENTITY yet"),
                errdetail("The feature is not supported currently")));
#endif

#ifdef PGXC
    /*
     * If I am the main execute CN but not CCN,
     * Notify the CCN to create firstly, and then notify other CNs except me.
     */
    if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
        if (u_sess->attr.attr_sql.enable_parallel_ddl && !isFirstNode) {
            bool is_temp = false;
            RemoteQuery* step = makeNode(RemoteQuery);

            /* Check un-allowed case where truncate tables from different node groups */
            if (!ObjectsInSameNodeGroup(stmt->relations, T_TruncateStmt)) {
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("NOT-SUPPORT: Not support TRUNCATE multiple objects different nodegroup")));
            }

            foreach (cell, stmt->relations) {
                Oid relid;
                RangeVar* rel = (RangeVar*)lfirst(cell);

                relid = RangeVarGetRelid(rel, NoLock, false);

                if (IsTempTable(relid)) {
                    is_temp = true;
                    break;
                }
            }

            step->combine_type = COMBINE_TYPE_SAME;
            step->exec_nodes = NULL;
            step->sql_statement = pstrdup(sql_statement);
            step->force_autocommit = false;

            if (is_temp)
                step->exec_type = EXEC_ON_NONE;
            else
                step->exec_type = EXEC_ON_COORDS;

            step->is_temp = is_temp;
            ExecRemoteUtility_ParallelDDLMode(step, FirstExecNode);
            pfree_ext(step->sql_statement);
            pfree_ext(step);
        }
    }
#endif

    /*
     * Open, exclusive-lock, and check all the explicitly-specified relations
     */
    foreach (cell, stmt->relations) {
        RangeVar* rv = (RangeVar*)lfirst(cell);
        Relation rel, myrel;
        bool recurse = interpretInhOption(rv->inhOpt);
        Oid myrelid;
        LOCKMODE lockmode;

        /*
         * Need to let ProcSleep know if we could cancel redistribution transaction which
         * locks the table we want to truncate. ProcSleep will make sure we only cancel the
         * transaction doing redistribution.
         *
         * Move the following check outside of loop??
         */
        if (IS_PGXC_COORDINATOR)
            u_sess->exec_cxt.could_cancel_redistribution = true;

        if (IsGlobalTempTableParallelTrunc()) {
            /*
            * Truncate global temp table only cleans up the data in current backend,
            * only low-level locks are required.
            */
            myrelid = RangeVarGetRelid(rv, NoLock, false);
            if (IsGlobalTempTable(myrelid)) {
                lockmode = RowExclusiveLock;
            } else {
                lockmode = AccessExclusiveLock;
            }
            rel = heap_openrv(rv, lockmode);
            myrel = rel;
        } else {
            rel = heap_openrv(rv, AccessExclusiveLock);
            myrel = rel;
            myrelid = RelationGetRelid(rel);
            lockmode = AccessExclusiveLock;
        }

        TrForbidAccessRbObject(RelationRelationId, myrelid, rv->relname);

        /* find matview exists or not. */
        Oid mlogid = find_matview_mlog_table(myrelid);
        if (OidIsValid(mlogid)) {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Not support truncate table under materialized view. ")));
        }

        /* don't throw error for "TRUNCATE foo, foo" */
        if (list_member_oid(relids, myrelid)) {
            heap_close(rel, lockmode);
            continue;
        }
        truncate_check_rel(rel);
        rels = lappend(rels, rel);
        relids = lappend_oid(relids, myrelid);

        /* Log this relation only if needed for logical decoding */
        if (RelationIsLogicallyLogged(rel))
            relids_logged = lappend_oid(relids_logged, myrelid);

        if (recurse) {
            ListCell* child = NULL;
            List* children = NIL;

            children = find_all_inheritors(myrelid, AccessExclusiveLock, NULL);

            foreach (child, children) {
                Oid childrelid = lfirst_oid(child);

                if (list_member_oid(relids, childrelid))
                    continue;

                /* find_all_inheritors already got lock */
                rel = heap_open(childrelid, NoLock);
                truncate_check_rel(rel);
                rels = lappend(rels, rel);
                relids = lappend_oid(relids, childrelid);
                /* Log this relation only if needed for logical decoding */
                if (RelationIsLogicallyLogged(rel))
                    relids_logged = lappend_oid(relids_logged, childrelid);
            }
        }

#ifdef ENABLE_MULTIPLE_NODES
        PreventDDLIfTsdbDisabled(myrelid);
        /*
         * for timeseries relation, truncate tag table and delta table(if exists) on each DN
         */
        if (IS_PGXC_DATANODE && unlikely(RelationIsTsStore(rel))) {
            Oid tag_relid = get_tag_relid(RelationGetRelationName(rel), rel->rd_rel->relnamespace);
            Relation tag_rel = heap_open(tag_relid, AccessExclusiveLock);
            truncate_check_rel(tag_rel);
            rels = lappend(rels, tag_rel);
            relids = lappend_oid(relids, tag_relid);

            if (Tsdb::RelationEnablesTsdbDelta(rel)) {
                Relation delta_rel = Tsdb::RelationGetDeltaRelation(rel, AccessExclusiveLock);
                truncate_check_rel(delta_rel);
                rels = lappend(rels, delta_rel);
                relids = lappend_oid(relids, RelationGetRelid(delta_rel));
            }
        }
#endif  /* ENABLE_MULTIPLE_NODES */

#ifdef PGXC
        /*
         * If the truncate table is in the process of redistribution i.e
         * ALTER TABLE myrelid.table SET (APPEND_MODE=ON, rel_cn_oid = myrelid),
         * we have to truncate delete delta table and myrelid new table assocated
         * with myrelid.
         */
        if (IS_PGXC_DATANODE && RelationInClusterResizing(myrel) && !RelationInClusterResizingReadOnly(myrel)) {
            /*
             * Always keep the order consistent by operating on multi catchup delete delta first and then the delete
             * delta.
             */
            Relation delete_delta_rel_x =
                GetAndOpenDeleteDeltaRel(myrel, AccessExclusiveLock, true); /* Multi catchup delete delta */
            Relation delete_delta_rel = GetAndOpenDeleteDeltaRel(myrel, AccessExclusiveLock, false);
            Relation new_table_rel = GetAndOpenNewTableRel(myrel, AccessExclusiveLock);

            /* Multi catchup delta relation can be NULL. */
            if (delete_delta_rel_x) {
                truncate_check_rel(delete_delta_rel_x);
                rels = lappend(rels, delete_delta_rel_x);
                relids = lappend_oid(relids, RelationGetRelid(delete_delta_rel_x));
            }

            /*
             * delete_delta_rel and new_table_rel won't be NULL when myrel is in
             * redistribution. GetAndOpenDeleteDeltaRel and GetAndOpenNewTableRel will
             * issue errors if they are NULL. delete_delt_rel and new_table_rel will
             * be heap_close in the end when we travers rels list.
             */
            truncate_check_rel(delete_delta_rel);
            rels = lappend(rels, delete_delta_rel);
            relids = lappend_oid(relids, RelationGetRelid(delete_delta_rel));
            truncate_check_rel(new_table_rel);
            rels = lappend(rels, new_table_rel);
            relids = lappend_oid(relids, RelationGetRelid(new_table_rel));

            /*
             * Saved non partition relation in list and will truncate
             * redistribution relationed aux table before exit.
             * Partition table will be handled at paritioin table flow
             */
            rels_in_redis = lappend(rels_in_redis, myrel);
        }
#endif
    }

    ExecuteTruncateGuts(rels, relids, relids_logged, rels_in_redis,
                        stmt->behavior, stmt->restart_seqs, stmt);

    /* And close the rels */
    foreach(cell, rels)
    {
        Relation    rel = (Relation) lfirst(cell);

        heap_close(rel, NoLock);
    }
}


/*
 * ExecuteTruncateGuts
 *
 * Internal implementation of TRUNCATE.  This is called by the actual TRUNCATE
 * command (see above) as well as replication subscribers that execute a
 * replicated TRUNCATE action.
 *
 * explicit_rels is the list of Relations to truncate that the command
 * specified.  relids is the list of Oids corresponding to explicit_rels.
 * relids_logged is the list of Oids (a subset of relids) that require
 * WAL-logging.  This is all a bit redundant, but the existing callers have
 * this information handy in this form.
 */
void ExecuteTruncateGuts(
    List *explicit_rels, List *relids, List *relids_logged, List *rels_in_redis,
    DropBehavior behavior, bool restart_seqs, TruncateStmt* stmt)
{
    List       *rels;
    List       *seq_relids = NIL;
    EState       *estate;
    ResultRelInfo *resultRelInfos;
    ResultRelInfo *resultRelInfo;
    SubTransactionId mySubid;
    ListCell   *cell;
    Oid           *logrelids;
    List* autoinc_seqoids = NIL;
    bool isDfsTruncate = false;

    /*
     * Open, exclusive-lock, and check all the explicitly-specified relations
     *
     * In CASCADE mode, suck in all referencing relations as well.  This
     * requires multiple iterations to find indirectly-dependent relations. At
     * each phase, we need to exclusive-lock new rels before looking for their
     * dependencies, else we might miss something.  Also, we check each rel as
     * soon as we open it, to avoid a faux pas such as holding lock for a long
     * time on a rel we have no permissions for.
     */
    rels = list_copy(explicit_rels);
    if (behavior == DROP_CASCADE) {
        for (;;) {
            List* newrelids = NIL;

            newrelids = heap_truncate_find_FKs(relids);
            if (newrelids == NIL) {
                break; /* nothing else to add */
            }

            foreach (cell, newrelids) {
                Oid relid = lfirst_oid(cell);
                Relation rel;

                if (IsGlobalTempTableParallelTrunc() && IsGlobalTempTable(relid)) {
                    rel = heap_open(relid, RowExclusiveLock);
                } else {
                    rel = heap_open(relid, AccessExclusiveLock);
                }
                ereport(NOTICE, (errmsg("truncate cascades to table \"%s\"", RelationGetRelationName(rel))));
                truncate_check_rel(rel);
                rels = lappend(rels, rel);
                relids = lappend_oid(relids, relid);
                /* Log this relation only if needed for logical decoding */
                if (RelationIsLogicallyLogged(rel))
                    relids_logged = lappend_oid(relids_logged, relid);
            }
        }
    }

    /*
     * Check foreign key references.  In CASCADE mode, this should be
     * unnecessary since we just pulled in all the references; but as a
     * cross-check, do it anyway if in an Assert-enabled build.
     */
#ifdef USE_ASSERT_CHECKING
    heap_truncate_check_FKs(rels, false);
#else
    if (behavior == DROP_RESTRICT)
        heap_truncate_check_FKs(rels, false);
#endif

    /*
     * If we are asked to restart sequences, find all the sequences, lock them
     * (we need AccessExclusiveLock for ResetSequence), and check permissions.
     * We want to do this early since it's pointless to do all the truncation
     * work only to fail on sequence permissions.
     */
    if (restart_seqs) {
        foreach (cell, rels) {
            Relation rel = (Relation)lfirst(cell);
            List* seqlist = getOwnedSequences(RelationGetRelid(rel));
            Oid autoinc_seqoid = RelAutoIncSeqOid(rel);
            ListCell* seqcell = NULL;

            foreach (seqcell, seqlist) {
                Oid seq_relid = lfirst_oid(seqcell);
                Relation seq_rel;

                seq_rel = relation_open(seq_relid, AccessExclusiveLock);

                /* This check must match AlterSequence! */
                AclResult aclresult = pg_class_aclcheck(seq_relid, GetUserId(), ACL_ALTER);
                if (aclresult != ACLCHECK_OK && !pg_class_ownercheck(seq_relid, GetUserId()) &&
                        !(isOperatoradmin(GetUserId()) && u_sess->proc_cxt.clientIsGsroach &&
                            u_sess->attr.attr_security.operation_mode)) {
                    aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS, RelationGetRelationName(seq_rel));
                }
                if (seq_relid == autoinc_seqoid) {
                    autoinc_seqoids = lappend_oid(autoinc_seqoids, seq_relid);
                } else {
                    seq_relids = lappend_oid(seq_relids, seq_relid);
                }

                relation_close(seq_rel, NoLock);
            }
        }
    } else {
        /*
         * Even if we are not asked to restart sequences,
         * we should restart sequences created by auto_increment column.
         */
        foreach (cell, rels) {
            Relation rel = (Relation)lfirst(cell);
            Oid seq_relid = RelAutoIncSeqOid(rel);
            if (!OidIsValid(seq_relid)) {
                continue;
            }
            Relation seq_rel = relation_open(seq_relid, AccessExclusiveLock);
            /* This check must match AlterSequence! */
            AclResult aclresult = pg_class_aclcheck(seq_relid, GetUserId(), ACL_ALTER);
            if (aclresult != ACLCHECK_OK && !pg_class_ownercheck(seq_relid, GetUserId()) &&
                    !(isOperatoradmin(GetUserId()) && u_sess->proc_cxt.clientIsGsroach &&
                        u_sess->attr.attr_security.operation_mode)) {
                aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS, RelationGetRelationName(seq_rel));
            }
            autoinc_seqoids = lappend_oid(autoinc_seqoids, seq_relid);
            relation_close(seq_rel, NoLock);
        }
    }

    /* Prepare to catch AFTER triggers. */
    AfterTriggerBeginQuery();

    /*
     * To fire triggers, we'll need an EState as well as a ResultRelInfo for
     * each relation.  We don't need to call ExecOpenIndices, though.
     */
    estate = CreateExecutorState();
    resultRelInfos = (ResultRelInfo*)palloc(list_length(rels) * sizeof(ResultRelInfo));
    resultRelInfo = resultRelInfos;
    foreach (cell, rels) {
        Relation rel = (Relation)lfirst(cell);

        InitResultRelInfo(resultRelInfo,
            rel,
            0, /* dummy rangetable index */
            0);
        resultRelInfo++;
    }
    estate->es_result_relations = resultRelInfos;
    estate->es_num_result_relations = list_length(rels);

    /*
     * Process all BEFORE STATEMENT TRUNCATE triggers before we begin
     * truncating (this is because one of them might throw an error). Also, if
     * we were to allow them to prevent statement execution, that would need
     * to be handled here.
     */
    resultRelInfo = resultRelInfos;
    foreach (cell, rels) {
        estate->es_result_relation_info = resultRelInfo;
        ExecBSTruncateTriggers(estate, resultRelInfo);
        resultRelInfo++;
    }

    /*
     * OK, truncate each table.
     */
    mySubid = GetCurrentSubTransactionId();

    foreach (cell, rels) {
        Relation rel = (Relation)lfirst(cell);
        Oid heap_relid;
        Oid toast_relid;
        bool is_shared = rel->rd_rel->relisshared;
        MultiXactId	minmulti;
        /*
         * This effectively deletes all rows in the table, and may be done
         * in a serializable transaction.  In that case we must record a
         * rw-conflict in to this transaction from each transaction
         * holding a predicate lock on the table.
         */
        CheckTableForSerializableConflictIn(rel);

        if (RELATION_IS_GLOBAL_TEMP(rel) && !gtt_storage_attached(RelationGetRelid(rel))) {
            continue;
        }

        minmulti = GetOldestMultiXactId();

#ifdef ENABLE_MOT
        if (RelationIsForeignTable(rel) && isMOTFromTblOid(RelationGetRelid(rel))) {
            FdwRoutine* fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(rel));
            if (fdwroutine->TruncateForeignTable != NULL) {
                fdwroutine->TruncateForeignTable(stmt, rel);
            }
        } else if (!RELATION_IS_PARTITIONED(rel)) {
#else
        if (!RELATION_IS_PARTITIONED(rel)) {
#endif
            RelationSetNewRelfilenode(rel, u_sess->utils_cxt.RecentXmin,
                                      RelationIsColStore(rel) ? InvalidMultiXactId : minmulti,
                                      isDfsTruncate);

            if (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
                heap_create_init_fork(rel);

            heap_relid = RelationGetRelid(rel);
            toast_relid = rel->rd_rel->reltoastrelid;

            /*
             * The same for the toast table, if any.
             */
            if (OidIsValid(toast_relid)) {
                if (IsGlobalTempTableParallelTrunc() && RELATION_IS_GLOBAL_TEMP(rel)) {
                    rel = relation_open(toast_relid, RowExclusiveLock);
                } else {
                    rel = relation_open(toast_relid, AccessExclusiveLock);
                }
                RelationSetNewRelfilenode(rel, u_sess->utils_cxt.RecentXmin, minmulti);
                if (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
                    heap_create_init_fork(rel);
                heap_close(rel, NoLock);
            }

            /* report truncate to PgStatCollector */
            pgstat_count_truncate(rel);

            /*
             * Reconstruct the indexes to match, and we're done.
             */
            (void)ReindexRelation(heap_relid, REINDEX_REL_PROCESS_TOAST, REINDEX_ALL_INDEX, NULL,
                NULL, false, ALL_KIND, RELATION_IS_GLOBAL_TEMP(rel) ? true : false);
        } else {
            /* truncate partitioned table */
            List* partTupleList = NIL;
            ListCell* partCell = NULL;

            heap_relid = RelationGetRelid(rel);
            /* partitioned table unspport the unlogged table */
            Assert(rel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED);

            /* process all partition and toast */
            partTupleList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_PARTITION, rel->rd_id);
            foreach (partCell, partTupleList) {
                if (RelationIsSubPartitioned(rel)) {
                    HeapTuple tup = (HeapTuple)lfirst(partCell);
                    Oid partOid = HeapTupleGetOid(tup);
                    Partition p = partitionOpen(rel, partOid, AccessExclusiveLock);
                    Relation partRel = partitionGetRelation(rel, p);
                    List* subPartTupleList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_SUB_PARTITION, partOid);
                    ListCell* subPartCell = NULL;
                    foreach (subPartCell, subPartTupleList) {
                        HeapTuple tup = (HeapTuple)lfirst(subPartCell);
                        TruncateOnePart(partRel, tup);
                    }
                    freePartList(subPartTupleList);

                    releaseDummyRelation(&partRel);
                    partitionClose(rel, p, AccessExclusiveLock);
                } else {
                    HeapTuple tup = (HeapTuple)lfirst(partCell);
                    TruncateOnePart(rel, tup);
                }
            }

            RelationSetNewRelfilenode(rel, u_sess->utils_cxt.RecentXmin,
                                      RelationIsColStore(rel) ? InvalidMultiXactId : minmulti,
                                      isDfsTruncate);
            freePartList(partTupleList);
            pgstat_report_truncate(
                heap_relid, InvalidOid, is_shared); /* report truncate partitioned table to PgStatCollector */

            /* process all index
             *
             * Global temporary tables do not support partitions, no need to add IsTruncGTT here.
             */
            (void)ReindexRelation(heap_relid, REINDEX_REL_PROCESS_TOAST, REINDEX_ALL_INDEX, NULL);
        }
    }

    /*
     * Restart owned sequences if we were asked to.
     */
    foreach (cell, seq_relids) {
        Oid seq_relid = lfirst_oid(cell);

        ResetSequence(seq_relid, false);
    }
    foreach (cell, autoinc_seqoids) {
        Oid seq_relid = lfirst_oid(cell);

        ResetSequence(seq_relid, true);
    }

    /*
     * Write a WAL record to allow this set of actions to be logically decoded.
     *
     * Assemble an array of relids so we can write a single WAL record for the
     * whole action.
     */
    if (list_length(relids_logged) > 0)
    {
        xl_heap_truncate xlrec;
        int            i = 0;

        /* should only get here if wal_level >= logical */
        Assert(XLogLogicalInfoActive());

        logrelids = (Oid*)palloc(list_length(relids_logged) * sizeof(Oid));
        foreach (cell, relids_logged)
            logrelids[i++] = lfirst_oid(cell);

        xlrec.dbId = u_sess->proc_cxt.MyDatabaseId;
        xlrec.nrelids = list_length(relids_logged);
        xlrec.flags = 0;
        if (behavior == DROP_CASCADE)
            xlrec.flags |= XLH_TRUNCATE_CASCADE;
        if (restart_seqs)
            xlrec.flags |= XLH_TRUNCATE_RESTART_SEQS;

        XLogBeginInsert();
        XLogRegisterData((char *) &xlrec, SizeOfHeapTruncate);
        XLogRegisterData((char *) logrelids, list_length(relids_logged) * sizeof(Oid));

        // XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);

        (void) XLogInsert(RM_HEAP3_ID, XLOG_HEAP3_TRUNCATE);
    }

    /*
     * Process all AFTER STATEMENT TRUNCATE triggers.
     */
    resultRelInfo = resultRelInfos;
    foreach (cell, rels) {
        estate->es_result_relation_info = resultRelInfo;
        ExecASTruncateTriggers(estate, resultRelInfo);
        resultRelInfo++;
    }

#ifdef ENABLE_MULTIPLE_NODES
    if (stmt) {
        /*
         * In Postgres-XC, TRUNCATE needs to be launched to remote nodes before the
         * AFTER triggers are launched. This insures that the triggers are being fired
         * by correct events.
         */
        if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
            if (u_sess->attr.attr_sql.enable_parallel_ddl && !isFirstNode) {
                bool is_temp = false;
                RemoteQuery* step = makeNode(RemoteQuery);
                ExecNodes* exec_nodes = NULL;

                /* Check un-allowed case where truncate tables from different node groups */
                if (!ObjectsInSameNodeGroup(stmt->relations, T_TruncateStmt)) {
                    ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                            errmsg("NOT-SUPPORT: Not support TRUNCATE multiple objects different nodegroup")));
                }

                foreach (cell, stmt->relations) {
                    Oid relid;
                    RangeVar* rel = (RangeVar*)lfirst(cell);

                    relid = RangeVarGetRelid(rel, NoLock, false);

                    if (exec_nodes == NULL) {
                        exec_nodes = RelidGetExecNodes(relid);
                    }

                    if (IsTempTable(relid)) {
                        is_temp = true;
                        break;
                    }
                }

                step->combine_type = COMBINE_TYPE_SAME;
                step->exec_nodes = exec_nodes;
                step->sql_statement = pstrdup(sql_statement);
                step->force_autocommit = false;
                step->exec_type = EXEC_ON_DATANODES;
                step->is_temp = is_temp;
                ExecRemoteUtility_ParallelDDLMode(step, FirstExecNode);
                pfree_ext(step->sql_statement);
                pfree_ext(step);
            } else {
                bool is_temp = false;
                RemoteQuery* step = makeNode(RemoteQuery);
                ExecNodes* exec_nodes = NULL;

                /* Check un-allowed case where truncate tables from different node groups */
                if (!ObjectsInSameNodeGroup(stmt->relations, T_TruncateStmt)) {
                    ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                            errmsg("NOT-SUPPORT: Not support TRUNCATE multiple objects different nodegroup")));
                }

                foreach (cell, stmt->relations) {
                    Oid relid;
                    RangeVar* rel = (RangeVar*)lfirst(cell);

                    relid = RangeVarGetRelid(rel, NoLock, false);

                    if (exec_nodes == NULL) {
                        exec_nodes = RelidGetExecNodes(relid);
                    }

                    if (IsTempTable(relid)) {
                        is_temp = true;
                        break;
                    }
                }

                step->combine_type = COMBINE_TYPE_SAME;
                step->exec_nodes = exec_nodes;
                step->sql_statement = pstrdup(sql_statement);
                step->force_autocommit = false;
                step->exec_type = is_temp ? EXEC_ON_DATANODES : EXEC_ON_ALL_NODES;
                step->is_temp = is_temp;
                ExecRemoteUtility(step);
                pfree_ext(step->sql_statement);
                pfree_ext(step);
            }
        }
    }
#endif

    /* Handle queued AFTER triggers */
    AfterTriggerEndQuery(estate);

#ifdef PGXC
    /* Need to reset start and end ctid of rel in rels_in_redis list */
    if (IS_PGXC_DATANODE) {
        foreach (cell, rels_in_redis) {
            Relation rel = (Relation)lfirst(cell);
            if (!RELATION_IS_PARTITIONED(rel)) {
                ResetRelRedisCtidRelOptions(
                    rel, InvalidOid, RELOID, Natts_pg_class, Anum_pg_class_reloptions, RelationRelationId);
            } else {
                ResetPartsRedisCtidRelOptions(rel);
            }
            elog(LOG, "reset redis rel %s start and end ctid.", RelationGetRelationName(rel));
        }
    }
#endif

    /* We can clean up the EState now */
    FreeExecutorState(estate);

    /*
     * Close any rels opened by CASCADE (can't do this while EState still
     * holds refs)
     */
    rels = list_difference_ptr(rels, explicit_rels);
    foreach (cell, rels) {
        Relation rel = (Relation)lfirst(cell);

        /*
         * In order to execute truncate concurrently for GTT, GTT does not record time of truancate relation.
         */
        if (!(IsGlobalTempTableParallelTrunc() && RELATION_IS_GLOBAL_TEMP(rel))) {
            /* Record time of truancate relation. */
            recordRelationMTime(rel->rd_id, rel->rd_rel->relkind);
        }

        heap_close(rel, NoLock);
    }
}

/*
 * Check that a given rel is safe to truncate.	Subroutine for ExecuteTruncate
 */
void truncate_check_rel(Relation rel)
{
    AclResult aclresult;

    /* Only allow truncate on regular tables or MOT tables */
    /* @hdfs
     * Add error msg for a foreign table
     */
    if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) {
#ifdef ENABLE_MOT
        if (!isMOTFromTblOid(RelationGetRelid(rel))) {
#endif
            ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                    errmsg("It is not supported to truncate foreign table \"%s\".", RelationGetRelationName(rel))));
#ifdef ENABLE_MOT
        }
#endif
    } else if (rel->rd_rel->relkind == RELKIND_STREAM) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("It is not supported to truncate stream \"%s\".", RelationGetRelationName(rel))));
    } else if (rel->rd_rel->relkind == RELKIND_MATVIEW) {
#ifdef ENABLE_MULTIPLE_NODES
        if (!IS_PGXC_DATANODE)
#endif
        {
            ereport(ERROR,
                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                            errmsg("It is not supported to truncate matview \"%s\"", RelationGetRelationName(rel))));
        }
    } else if (rel->rd_rel->relkind != RELKIND_RELATION) {
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                        errmsg("It is not supported to truncate non-table \"%s\"", RelationGetRelationName(rel))));
#ifdef ENABLE_HTAP
    } else if (rel->rd_rel->relkind == RELKIND_RELATION && RelHasImcs(RelationGetRelid(rel))) {
        ereport(ERROR, (errcode(ERRCODE_WARNING),
             errmsg("Truncating table \"%s\" with HTAP, please unpopulate first", RelationGetRelationName(rel))));
    }
#else
    }
#endif

    if (is_ledger_related_rel(rel)) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("It is not supported to truncate blockchain table \"%s\"", RelationGetRelationName(rel))));
    }

    /* Permissions checks */
    aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), ACL_TRUNCATE);
    if (aclresult != ACLCHECK_OK)
        aclcheck_error(aclresult, ACL_KIND_CLASS, RelationGetRelationName(rel));

    if (!g_instance.attr.attr_common.allowSystemTableMods && !u_sess->attr.attr_common.IsInplaceUpgrade &&
        IsSystemRelation(rel) && !WLMRelationCanTruncate(rel))
        ereport(ERROR,
            (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                errmsg("permission denied: \"%s\" is a system catalog", RelationGetRelationName(rel))));

    /* Forbid truncate on shared relation during upgrade, to protect global/pg_filenode.map not changed */
    if (u_sess->attr.attr_common.upgrade_mode != 0 &&
        rel->rd_id < FirstBootstrapObjectId && rel->rd_rel->relisshared &&
        t_thrd.proc->workingVersionNum < RELMAP_4K_VERSION_NUM) {
        ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                 errmsg("cannot truncate shared relation during upgrade")));
    }

    /*
     * Don't allow truncate on temp tables of other backends ... their local
     * buffer manager is not going to cope.
     */
    if (RELATION_IS_OTHER_TEMP(rel)) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot truncate temporary tables of other sessions")));
    }

    /*
     * Also check for active uses of the relation in the current transaction,
     * including open scans and pending AFTER trigger events.
     */
    CheckTableNotInUse(rel, "TRUNCATE");
}

/*
 * storage_name
 *	  returns the name corresponding to a typstorage/attstorage enum value
 */
static const char* storage_name(char c)
{
    switch (c) {
        case 'p':
            return "PLAIN";
        case 'm':
            return "MAIN";
        case 'x':
            return "EXTENDED";
        case 'e':
            return "EXTERNAL";
        default:
            return "???";
    }
}

/* ----------
 * MergeAttributes
 *		Returns new schema given initial schema and superclasses.
 *
 * Input arguments:
 * 'schema' is the column/attribute definition for the table. (It's a list
 *		of ColumnDef's.) It is destructively changed.
 * 'supers' is a list of names (as RangeVar nodes) of parent relations.
 * 'relpersistence' is a persistence type of the table.
 *
 * Output arguments:
 * 'supOids' receives a list of the OIDs of the parent relations.
 * 'supconstr' receives a list of constraints belonging to the parents,
 *		updated as necessary to be valid for the child.
 * 'supOidCount' is set to the number of parents that have OID columns.
 *
 * Return value:
 * Completed schema list.
 *
 * Notes:
 *	  The order in which the attributes are inherited is very important.
 *	  Intuitively, the inherited attributes should come first. If a table
 *	  inherits from multiple parents, the order of those attributes are
 *	  according to the order of the parents specified in CREATE TABLE.
 *
 *	  Here's an example:
 *
 *		create table person (name text, age int4, location point);
 *		create table emp (salary int4, manager text) inherits(person);
 *		create table student (gpa float8) inherits (person);
 *		create table stud_emp (percent int4) inherits (emp, student);
 *
 *	  The order of the attributes of stud_emp is:
 *
 *							person {1:name, 2:age, 3:location}
 *							/	 \
 *			   {6:gpa}	student   emp {4:salary, 5:manager}
 *							\	 /
 *						   stud_emp {7:percent}
 *
 *	   If the same attribute name appears multiple times, then it appears
 *	   in the result table in the proper location for its first appearance.
 *
 *	   Constraints (including NOT NULL constraints) for the child table
 *	   are the union of all relevant constraints, from both the child schema
 *	   and parent tables.
 *
 *	   The default value for a child column is defined as:
 *		(1) If the child schema specifies a default, that value is used.
 *		(2) If neither the child nor any parent specifies a default, then
 *			the column will not have a default.
 *		(3) If conflicting defaults are inherited from different parents
 *			(and not overridden by the child), an error is raised.
 *		(4) Otherwise the inherited default is used.
 *		Rule (3) is new in Postgres 7.1; in earlier releases you got a
 *		rather arbitrary choice of which parent default to use.
 * ----------
 */
static List* MergeAttributes(
    List* schema, List* supers, char relpersistence, List** supOids, List** supconstr, int* supOidCount)
{
    ListCell* entry = NULL;
    List* inhSchema = NIL;
    List* parentOids = NIL;
    List* constraints = NIL;
    int parentsWithOids = 0;
    bool have_bogus_defaults = false;
    int child_attno;

    /*
     * Check for and reject tables with too many columns. We perform this
     * check relatively early for two reasons: (a) we don't run the risk of
     * overflowing an AttrNumber in subsequent code (b) an O(n^2) algorithm is
     * okay if we're processing <= 1600 columns, but could take minutes to
     * execute if the user attempts to create a table with hundreds of
     * thousands of columns.
     *
     * Note that we also need to check that any we do not exceed this figure
     * after including columns from inherited relations.
     */
    if (list_length(schema) > MaxHeapAttributeNumber) {
        ereport(ERROR,
            (errcode(ERRCODE_TOO_MANY_COLUMNS), errmsg("tables can have at most %d columns", MaxHeapAttributeNumber)));
    }

    /*
     * Check for duplicate names in the explicit list of attributes.
     *
     * Although we might consider merging such entries in the same way that we
     * handle name conflicts for inherited attributes, it seems to make more
     * sense to assume such conflicts are errors.
     */
    foreach (entry, schema) {
        ColumnDef* coldef = (ColumnDef*)lfirst(entry);
        ListCell* rest = lnext(entry);
        ListCell* prev = entry;

        if (u_sess->attr.attr_sql.enable_cluster_resize && coldef->dropped_attr != NULL) {
            continue;
        }
        if (coldef->typname == NULL) {

            /*
             * Typed table column option that does not belong to a column from
             * the type.  This works because the columns from the type come
             * first in the list.
             */
            ereport(
                ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("column \"%s\" does not exist", coldef->colname)));
        }

        while (rest != NULL) {
            ColumnDef* restdef = (ColumnDef*)lfirst(rest);
            ListCell* next = lnext(rest); /* need to save it in case we
                                           * delete it */

            if (strcmp(coldef->colname, restdef->colname) == 0) {
                if (coldef->is_from_type) {
                    /*
                     * merge the column options into the column from the type
                     */
                    coldef->is_not_null = restdef->is_not_null;
                    coldef->raw_default = restdef->raw_default;
                    coldef->cooked_default = restdef->cooked_default;
                    coldef->update_default = restdef->update_default;
                    coldef->constraints = restdef->constraints;
                    coldef->is_from_type = false;
                    coldef->kvtype = restdef->kvtype;
                    coldef->generatedCol = restdef->generatedCol;
                    coldef->cmprs_mode = restdef->cmprs_mode;
                    list_delete_cell(schema, rest, prev);
                } else {
#ifndef ENABLE_MULTIPLE_NODES
                    if (u_sess->attr.attr_common.plsql_show_all_error) {
                        StringInfoData message;
                        initStringInfo(&message);
                        appendStringInfo(&message, "column \"%s\" specified more than once", coldef->colname);
                        InsertErrorMessage(message.data, 0, true);
                    }
#endif
                    ereport(ERROR,
                        (errcode(ERRCODE_DUPLICATE_COLUMN),
                            errmsg("column \"%s\" specified more than once", coldef->colname)));
                }
            }
            prev = rest;
            rest = next;
        }
    }

    /*
     * Scan the parents left-to-right, and merge their attributes to form a
     * list of inherited attributes (inhSchema).  Also check to see if we need
     * to inherit an OID column.
     */
    child_attno = 0;
    foreach (entry, supers) {
        RangeVar* parent = (RangeVar*)lfirst(entry);
        Relation relation;
        TupleDesc tupleDesc;
        TupleConstr* constr = NULL;
        AttrNumber* newattno = NULL;
        AttrNumber parent_attno;

        /*
         * A self-exclusive lock is needed here.  If two backends attempt to
         * add children to the same parent simultaneously, and that parent has
         * no pre-existing children, then both will attempt to update the
         * parent's relhassubclass field, leading to a "tuple concurrently
         * updated" error.  Also, this interlocks against a concurrent ANALYZE
         * on the parent table, which might otherwise be attempting to clear
         * the parent's relhassubclass field, if its previous children were
         * recently dropped.
         */
        relation = heap_openrv(parent, ShareUpdateExclusiveLock);

        if (relation->rd_rel->relkind != RELKIND_RELATION) {
            ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                    errmsg("inherited relation \"%s\" is not a table", parent->relname)));
        }

        /* Permanent rels cannot inherit from temporary ones */
        if (relpersistence != RELPERSISTENCE_TEMP && relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP) {
            ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                    errmsg("cannot inherit from temporary relation \"%s\"", parent->relname)));
        }

        /* If existing rel is temp, it must belong to this session */
        if (relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP && !RelationIsLocalTemp(relation)) {
            ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                    errmsg("cannot inherit from temporary relation of another session")));
        }

        /*
         * We should have an UNDER permission flag for this, but for now,
         * demand that creator of a child table own the parent.
         */
        if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId())) {
            aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(relation));
        }

        /*
         * Reject duplications in the list of parents.
         */
        if (list_member_oid(parentOids, RelationGetRelid(relation))) {
            ereport(ERROR,
                (errcode(ERRCODE_DUPLICATE_TABLE),
                    errmsg("relation \"%s\" would be inherited from more than once", parent->relname)));
        }

        parentOids = lappend_oid(parentOids, RelationGetRelid(relation));

        if (relation->rd_rel->relhasoids) {
            parentsWithOids++;
        }

        tupleDesc = RelationGetDescr(relation);
        constr = tupleDesc->constr;

        /*
         * newattno[] will contain the child-table attribute numbers for the
         * attributes of this parent table.  (They are not the same for
         * parents after the first one, nor if we have dropped columns.)
         */
        newattno = (AttrNumber*)palloc0(tupleDesc->natts * sizeof(AttrNumber));

        for (parent_attno = 1; parent_attno <= tupleDesc->natts; parent_attno++) {
            Form_pg_attribute attribute = &tupleDesc->attrs[parent_attno - 1];
            char* attributeName = NameStr(attribute->attname);
            int exist_attno;
            ColumnDef* def = NULL;

            /*
             * Ignore dropped columns in the parent.
             */
            if (attribute->attisdropped) {
                continue; /* leave newattno entry as zero */
            }

            /*
             * Does it conflict with some previously inherited column?
             */
            exist_attno = findAttrByName(attributeName, inhSchema);
            if (exist_attno > 0) {
                Oid defTypeId;
                int32 deftypmod;
                Oid defCollId;

                /*
                 * Yes, try to merge the two column definitions. They must
                 * have the same type, typmod, and collation.
                 */
                ereport(NOTICE, (errmsg("merging multiple inherited definitions of column \"%s\"", attributeName)));
                def = (ColumnDef*)list_nth(inhSchema, exist_attno - 1);
                typenameTypeIdAndMod(NULL, def->typname, &defTypeId, &deftypmod);
                if (defTypeId != attribute->atttypid || deftypmod != attribute->atttypmod) {
                    ereport(ERROR,
                        (errcode(ERRCODE_DATATYPE_MISMATCH),
                            errmsg("inherited column \"%s\" has a type conflict", attributeName),
                            errdetail(
                                "%s versus %s", TypeNameToString(def->typname), format_type_be(attribute->atttypid))));
                }
                defCollId = GetColumnDefCollation(NULL, def, defTypeId);
                if (defCollId != attribute->attcollation) {
                    ereport(ERROR,
                        (errcode(ERRCODE_COLLATION_MISMATCH),
                            errmsg("inherited column \"%s\" has a collation conflict", attributeName),
                            errdetail("\"%s\" versus \"%s\"",
                                get_collation_name(defCollId),
                                get_collation_name(attribute->attcollation))));
                }

                /* Copy storage parameter */
                if (def->storage == 0) {
                    def->storage = attribute->attstorage;
                } else if (def->storage != attribute->attstorage) {
                    ereport(ERROR,
                        (errcode(ERRCODE_DATATYPE_MISMATCH),
                            errmsg("inherited column \"%s\" has a storage parameter conflict", attributeName),
                            errdetail(
                                "%s versus %s", storage_name(def->storage), storage_name(attribute->attstorage))));
                }

                def->inhcount++;
                /* Merge of NOT NULL constraints = OR 'em together */
                if (attribute->attnotnull) {
                    def->is_not_null = true;
                }
                if (def->kvtype == ATT_KV_UNDEFINED) {
                    def->kvtype = attribute->attkvtype;
                }
                if (def->cmprs_mode == ATT_CMPR_UNDEFINED) {
                    def->cmprs_mode = attribute->attcmprmode;
                }   
                /* Default and other constraints are handled below */
                newattno[parent_attno - 1] = exist_attno;

                /* Check for GENERATED conflicts */
                if (def->generatedCol != GetGeneratedCol(tupleDesc, parent_attno - 1)) {
                    ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_DATATYPE_MISMATCH),
                        errmsg("inherited column \"%s\" has a generation conflict", attributeName)));
                }
            } else {
                /*
                 * No, create a new inherited column
                 */
                def = makeNode(ColumnDef);
                def->colname = pstrdup(attributeName);
                def->typname = makeTypeNameFromOid(attribute->atttypid, attribute->atttypmod);
                def->inhcount = 1;
                def->is_local = false;
                def->is_not_null = attribute->attnotnull;
                def->is_from_type = false;
                def->storage = attribute->attstorage;
                def->kvtype = attribute->attkvtype;
                def->cmprs_mode = attribute->attcmprmode;
                def->raw_default = NULL;
                def->update_default = NULL;
                def->generatedCol = '\0';
                def->cooked_default = NULL;
                def->collClause = NULL;
                def->collOid = attribute->attcollation;
                def->constraints = NIL;
                inhSchema = lappend(inhSchema, def);
                newattno[parent_attno - 1] = ++child_attno;
            }

            /*
             * Copy default if any
             */
            if (attribute->atthasdef) {
                Node* this_default = NULL;
                Node* this_update_default = NULL;
                AttrDefault* attrdef = NULL;
                int i;

                /* Find default in constraint structure */
                if (unlikely(constr == NULL)) {
                    ereport(ERROR,
                        (errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("constr is NULL.")));
                }
                attrdef = constr->defval;
                for (i = 0; i < constr->num_defval; i++) {
                    if (attrdef[i].adnum == parent_attno) {
                        this_default = (Node*)stringToNode_skip_extern_fields(attrdef[i].adbin);
                        if (attrdef[i].has_on_update) {
                            this_update_default = (Node*)stringToNode_skip_extern_fields(attrdef[i].adbin_on_update);
                        }
                        break;
                    }
                }
                Assert(this_default != NULL || this_update_default != NULL);

                /*
                 * If default expr could contain any vars, we'd need to fix
                 * 'em, but it can't; so default is ready to apply to child.
                 *
                 * If we already had a default from some prior parent, check
                 * to see if they are the same.  If so, no problem; if not,
                 * mark the column as having a bogus default. Below, we will
                 * complain if the bogus default isn't overridden by the child
                 * schema.
                 */
                Assert(def->raw_default == NULL);
                if (def->cooked_default == NULL)
                    def->cooked_default = this_default;
                else if (!equal(def->cooked_default, this_default)) {
                    def->cooked_default = &u_sess->cmd_cxt.bogus_marker;
                    have_bogus_defaults = true;
                }
                if (def->update_default == NULL)
                    def->update_default = this_update_default;
            }
        }

        /*
         * Now copy the CHECK constraints of this parent, adjusting attnos
         * using the completed newattno[] map.	Identically named constraints
         * are merged if possible, else we throw error.
         */
        if (constr != NULL && constr->num_check > 0) {
            ConstrCheck* check = constr->check;
            int i;

            for (i = 0; i < constr->num_check; i++) {
                char* name = check[i].ccname;
                Node* expr = NULL;
                bool found_whole_row = false;

                /* ignore if the constraint is non-inheritable */
                if (check[i].ccnoinherit)
                    continue;

                /* Adjust Vars to match new table's column numbering */
                expr = map_variable_attnos(
                    (Node*)stringToNode(check[i].ccbin), 1, 0, newattno, tupleDesc->natts, &found_whole_row);

                /*
                 * For the moment we have to reject whole-row variables.
                 * We could convert them, if we knew the new table's rowtype
                 * OID, but that hasn't been assigned yet.
                 */
                if (found_whole_row)
                    ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                            errmsg("cannot convert whole-row table reference"),
                            errdetail("Constraint \"%s\" contains a whole-row reference to table \"%s\".",
                                name,
                                RelationGetRelationName(relation))));

                /* check for duplicate */
                if (!MergeCheckConstraint(constraints, name, expr)) {
                    /* nope, this is a new one */
                    CookedConstraint* cooked = NULL;

                    cooked = (CookedConstraint*)palloc(sizeof(CookedConstraint));
                    cooked->contype = CONSTR_CHECK;
                    cooked->conoid = InvalidOid;    /* until created */
                    cooked->name = pstrdup(name);
                    cooked->attnum = 0; /* not used for constraints */
                    cooked->expr = expr;
                    cooked->skip_validation = false;
                    cooked->is_local = false;
                    cooked->inhcount = 1;
                    cooked->is_no_inherit = false;
                    cooked->isdisable = false;
                    constraints = lappend(constraints, cooked);
                }
            }
        }

        pfree_ext(newattno);

        /*
         * Close the parent rel, but keep our AccessShareLock on it until xact
         * commit.	That will prevent someone else from deleting or ALTERing
         * the parent before the child is committed.
         */
        heap_close(relation, NoLock);
    }

    /*
     * If we had no inherited attributes, the result schema is just the
     * explicitly declared columns.  Otherwise, we need to merge the declared
     * columns into the inherited schema list.
     */
    if (inhSchema != NIL) {
        foreach (entry, schema) {
            ColumnDef* newdef = (ColumnDef*)lfirst(entry);
            char* attributeName = newdef->colname;
            int exist_attno;

            /*
             * Does it conflict with some previously inherited column?
             */
            exist_attno = findAttrByName(attributeName, inhSchema);
            if (exist_attno > 0) {
                Oid defTypeId, newTypeId;
                int32 deftypmod, newtypmod;
                Oid defcollid, newcollid;

                /*
                 * Yes, try to merge the two column definitions. They must
                 * have the same type, typmod, and collation.
                 */
                ereport(LOG,
                    (errmsg("%s: merging column \"%s\" with inherited definition",
                        g_instance.attr.attr_common.PGXCNodeName,
                        attributeName)));
                ereport(NOTICE, (errmsg("merging column \"%s\" with inherited definition", attributeName)));
                ColumnDef* def = (ColumnDef*)list_nth(inhSchema, exist_attno - 1);
                typenameTypeIdAndMod(NULL, def->typname, &defTypeId, &deftypmod);
                typenameTypeIdAndMod(NULL, newdef->typname, &newTypeId, &newtypmod);
                if (defTypeId != newTypeId || deftypmod != newtypmod)
                    ereport(ERROR,
                        (errcode(ERRCODE_DATATYPE_MISMATCH),
                            errmsg("column \"%s\" has a type conflict", attributeName),
                            errdetail(
                                "%s versus %s", TypeNameToString(def->typname), TypeNameToString(newdef->typname))));
                defcollid = GetColumnDefCollation(NULL, def, defTypeId);
                newcollid = GetColumnDefCollation(NULL, newdef, newTypeId);
                if (defcollid != newcollid)
                    ereport(ERROR,
                        (errcode(ERRCODE_COLLATION_MISMATCH),
                            errmsg("column \"%s\" has a collation conflict", attributeName),
                            errdetail(
                                "\"%s\" versus \"%s\"", get_collation_name(defcollid), get_collation_name(newcollid))));

                /* Copy storage parameter */
                if (def->storage == 0)
                    def->storage = newdef->storage;
                else if (newdef->storage != 0 && def->storage != newdef->storage)
                    ereport(ERROR,
                        (errcode(ERRCODE_DATATYPE_MISMATCH),
                            errmsg("column \"%s\" has a storage parameter conflict", attributeName),
                            errdetail("%s versus %s", storage_name(def->storage), storage_name(newdef->storage))));

                /* Mark the column as locally defined */
                def->is_local = true;
                /* Merge of NOT NULL constraints = OR 'em together */
                if (newdef->is_not_null)
                    def->is_not_null = true;
                /* Copy kv type parameter */
                if (def->kvtype == ATT_KV_UNDEFINED) {
                    def->kvtype = newdef->storage;
                } else if (newdef->kvtype != ATT_KV_UNDEFINED && def->kvtype != newdef->kvtype) {
                    ereport(ERROR,
                        (errcode(ERRCODE_DATATYPE_MISMATCH),
                            errmsg("column \"%s\" has a kvtype parameter conflict", attributeName),
                            errdetail("%s versus %s", storage_name(def->kvtype), storage_name(newdef->kvtype))));
                }
                if (def->cmprs_mode == ATT_CMPR_UNDEFINED)
                    def->cmprs_mode = newdef->cmprs_mode;
                /* If new def has a default, override previous default */
                if (newdef->raw_default != NULL) {
                    def->raw_default = newdef->raw_default;
                    def->cooked_default = newdef->cooked_default;
                }
                if (newdef->update_default != NULL) {
                    def->update_default = newdef->update_default;
                }
            } else {
                /*
                 * No, attach new column to result schema
                 */
                inhSchema = lappend(inhSchema, newdef);
            }
        }

        schema = inhSchema;

        /*
         * Check that we haven't exceeded the legal # of columns after merging
         * in inherited columns.
         */
        if (list_length(schema) > MaxHeapAttributeNumber)
            ereport(ERROR,
                (errcode(ERRCODE_TOO_MANY_COLUMNS),
                    errmsg("tables can have at most %d columns", MaxHeapAttributeNumber)));
    }

    /*
     * If we found any conflicting parent default values, check to make sure
     * they were overridden by the child.
     */
    if (have_bogus_defaults) {
        foreach (entry, schema) {
            ColumnDef* def = (ColumnDef*)lfirst(entry);

            if (def->cooked_default == &u_sess->cmd_cxt.bogus_marker) {
                if (def->generatedCol) {
                    ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
                        errmsg("column \"%s\" inherits conflicting generated column", def->colname),
                        errhint("To resolve the conflict, specify a generated expr explicitly.")));
                } else {
                    ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
                        errmsg("column \"%s\" inherits conflicting default values", def->colname),
                        errhint("To resolve the conflict, specify a default explicitly.")));
                }
            }
        }
    }

    *supOids = parentOids;
    *supconstr = constraints;
    *supOidCount = parentsWithOids;
    return schema;
}

/*
 * MergeCheckConstraint
 *		Try to merge an inherited CHECK constraint with previous ones
 *
 * If we inherit identically-named constraints from multiple parents, we must
 * merge them, or throw an error if they don't have identical definitions.
 *
 * constraints is a list of CookedConstraint structs for previous constraints.
 *
 * Returns TRUE if merged (constraint is a duplicate), or FALSE if it's
 * got a so-far-unique name, or throws error if conflict.
 */
static bool MergeCheckConstraint(List* constraints, char* name, Node* expr)
{
    ListCell* lc = NULL;

    foreach (lc, constraints) {
        CookedConstraint* ccon = (CookedConstraint*)lfirst(lc);

        Assert(ccon->contype == CONSTR_CHECK);

        /* Non-matching names never conflict */
        if (strcmp(ccon->name, name) != 0)
            continue;

        if (equal(expr, ccon->expr)) {
            /* OK to merge */
            ccon->inhcount++;
            return true;
        }

        ereport(ERROR,
            (errcode(ERRCODE_DUPLICATE_OBJECT),
                errmsg("check constraint name \"%s\" appears multiple times but with different expressions", name)));
    }

    return false;
}

/*
 * StoreCatalogInheritance
 *		Updates the system catalogs with proper inheritance information.
 *
 * supers is a list of the OIDs of the new relation's direct ancestors.
 */
static void StoreCatalogInheritance(Oid relationId, List* supers)
{
    Relation relation;
    int16 seqNumber;
    ListCell* entry = NULL;

    /*
     * sanity checks
     */
    AssertArg(OidIsValid(relationId));

    if (supers == NIL)
        return;

    /*
     * Store INHERITS information in pg_inherits using direct ancestors only.
     * Also enter dependencies on the direct ancestors, and make sure they are
     * marked with relhassubclass = true.
     *
     * (Once upon a time, both direct and indirect ancestors were found here
     * and then entered into pg_ipl.  Since that catalog doesn't exist
     * anymore, there's no need to look for indirect ancestors.)
     */
    relation = heap_open(InheritsRelationId, RowExclusiveLock);

    seqNumber = 1;
    foreach (entry, supers) {
        Oid parentOid = lfirst_oid(entry);

        StoreCatalogInheritance1(relationId, parentOid, seqNumber, relation);
        seqNumber++;
    }

    heap_close(relation, RowExclusiveLock);
}

/*
 * Make catalog entries showing relationId as being an inheritance child
 * of parentOid.  inhRelation is the already-opened pg_inherits catalog.
 */
static void StoreCatalogInheritance1(Oid relationId, Oid parentOid, int16 seqNumber, Relation inhRelation)
{
    TupleDesc desc = RelationGetDescr(inhRelation);
    Datum values[Natts_pg_inherits];
    bool nulls[Natts_pg_inherits];
    ObjectAddress childobject, parentobject;
    HeapTuple tuple;
    errno_t rc = EOK;

    /*
     * Make the pg_inherits entry
     */
    values[Anum_pg_inherits_inhrelid - 1] = ObjectIdGetDatum(relationId);
    values[Anum_pg_inherits_inhparent - 1] = ObjectIdGetDatum(parentOid);
    values[Anum_pg_inherits_inhseqno - 1] = Int16GetDatum(seqNumber);

    rc = memset_s(nulls, sizeof(nulls), 0, sizeof(nulls));
    securec_check(rc, "\0", "\0");

    tuple = heap_form_tuple(desc, values, nulls);

    (void)simple_heap_insert(inhRelation, tuple);

    CatalogUpdateIndexes(inhRelation, tuple);

    tableam_tops_free_tuple(tuple);

    /*
     * Store a dependency too
     */
    parentobject.classId = RelationRelationId;
    parentobject.objectId = parentOid;
    parentobject.objectSubId = 0;
    childobject.classId = RelationRelationId;
    childobject.objectId = relationId;
    childobject.objectSubId = 0;

    recordDependencyOn(&childobject, &parentobject, DEPENDENCY_NORMAL);

    /*
     * Mark the parent as having subclasses.
     */
    SetRelationHasSubclass(parentOid, true);
}

/*
 * Look for an existing schema entry with the given name.
 *
 * Returns the index (starting with 1) if attribute already exists in schema,
 * 0 if it doesn't.
 */
static int findAttrByName(const char* attributeName, List* schema)
{
    ListCell* s = NULL;
    int i = 1;

    foreach (s, schema) {
        ColumnDef* def = (ColumnDef*)lfirst(s);

        if (strcmp(attributeName, def->colname) == 0)
            return i;

        i++;
    }
    return 0;
}

/*
 * SetRelationHasSubclass
 *		Set the value of the relation's relhassubclass field in pg_class.
 *
 * NOTE: caller must be holding an appropriate lock on the relation.
 * ShareUpdateExclusiveLock is sufficient.
 *
 * NOTE: an important side-effect of this operation is that an SI invalidation
 * message is sent out to all backends --- including me --- causing plans
 * referencing the relation to be rebuilt with the new list of children.
 * This must happen even if we find that no change is needed in the pg_class
 * row.
 */
void SetRelationHasSubclass(Oid relationId, bool relhassubclass)
{
    Relation relationRelation;
    HeapTuple tuple;
    Form_pg_class classtuple;

    /*
     * Fetch a modifiable copy of the tuple, modify it, update pg_class.
     */
    relationRelation = heap_open(RelationRelationId, RowExclusiveLock);
    tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relationId));
    if (!HeapTupleIsValid(tuple)) {
        ereport(
            ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", relationId)));
    }

    classtuple = (Form_pg_class)GETSTRUCT(tuple);
    if (classtuple->relhassubclass != relhassubclass) {
        classtuple->relhassubclass = relhassubclass;
        simple_heap_update(relationRelation, &tuple->t_self, tuple);

        /* keep the catalog indexes up to date */
        CatalogUpdateIndexes(relationRelation, tuple);
    } else {
        /* no need to change tuple, but force relcache rebuild anyway */
        CacheInvalidateRelcacheByTuple(tuple);
    }

    tableam_tops_free_tuple(tuple);
    heap_close(relationRelation, RowExclusiveLock);
}

/*
 * Must be owner or have alter privilege to alter table
 */
static void ATPermissionCheck(Form_pg_class classform, Oid relid)
{
    AclResult aclresult;
    if (classform->relkind == RELKIND_COMPOSITE_TYPE) {
        Oid typeoid = get_typeoid(classform->relnamespace, NameStr(classform->relname));
        aclresult = pg_type_aclcheck(typeoid, GetUserId(), ACL_ALTER);
    } else if (classform->relkind == RELKIND_INDEX) {
        Oid tableoid = IndexGetRelation(relid, false);
        aclresult = pg_class_aclcheck(tableoid, GetUserId(), ACL_INDEX);
        bool anyResult = false;
        if (aclresult != ACLCHECK_OK && !IsSysSchema(GetNamespaceIdbyRelId(tableoid))) {
            anyResult = HasSpecAnyPriv(GetUserId(), ALTER_ANY_INDEX, false);
        }
        aclresult = anyResult ? ACLCHECK_OK : aclresult;
    } else {
        aclresult = pg_class_aclcheck(relid, GetUserId(), ACL_ALTER);
    }

    if (aclresult != ACLCHECK_OK && !pg_class_ownercheck(relid, GetUserId())) {
        aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS, NameStr(classform->relname));
    }
}

/*
 *		renameatt_check			- basic sanity checks before attribute rename
 */
static void renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
{
    char relkind = classform->relkind;

    if (classform->reloftype && !recursing) {
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot rename column of typed table")));
    }

    /*
     * Renaming the columns of sequences or toast tables doesn't actually
     * break anything from the system's point of view, since internal
     * references are by attnum.  But it doesn't seem right to allow users to
     * change names that are hardcoded into the system, hence the following
     * restriction.
     */
    if (relkind != RELKIND_RELATION &&
        relkind != RELKIND_VIEW &&
        relkind != RELKIND_CONTQUERY &&
        relkind != RELKIND_MATVIEW &&
        relkind != RELKIND_COMPOSITE_TYPE &&
        relkind != RELKIND_INDEX &&
        relkind != RELKIND_GLOBAL_INDEX &&
        relkind != RELKIND_STREAM &&
        relkind != RELKIND_FOREIGN_TABLE) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
            errmsg("\"%s\" is not a table, view, materialized view, composite type, index, stream or foreign table",
            NameStr(classform->relname))));
    }

    /* Permission check */
    ATPermissionCheck(classform, myrelid);

    if (!(g_instance.attr.attr_common.allowSystemTableMods && myrelid >= FirstBootstrapObjectId) &&
        !u_sess->attr.attr_common.IsInplaceUpgrade && IsSystemClass(classform))
        ereport(ERROR,
            (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                errmsg("permission denied: \"%s\" is a system catalog", NameStr(classform->relname))));
}

/*
 *		renameatt_internal		- workhorse for renameatt
 */
static AttrNumber  renameatt_internal(Oid myrelid, const char* oldattname, const char* newattname, bool recurse,
    bool recursing, int expected_parents, DropBehavior behavior)
{
    Relation targetrelation;
    Relation attrelation;
    HeapTuple atttup;
    Form_pg_attribute attform;
    AttrNumber attnum;

    Relation    ce_relation;
    HeapTuple   ce_tuple;
    Form_gs_encrypted_columns ce_form;

    /*
     * Grab an exclusive lock on the target table, which we will NOT release
     * until end of transaction.
     */
    targetrelation = relation_open(myrelid, AccessExclusiveLock);
    renameatt_check(myrelid, RelationGetForm(targetrelation), recursing);

    /*
     * if the 'recurse' flag is set then we are supposed to rename this
     * attribute in all classes that inherit from 'relname' (as well as in
     * 'relname').
     *
     * any permissions or problems with duplicate attributes will cause the
     * whole transaction to abort, which is what we want -- all or nothing.
     */
    if (recurse) {
        List* child_oids = NIL;
        List* child_numparents = NIL;
        ListCell* lo = NULL;
        ListCell* li = NULL;

        /*
         * we need the number of parents for each child so that the recursive
         * calls to renameatt() can determine whether there are any parents
         * outside the inheritance hierarchy being processed.
         */
        child_oids = find_all_inheritors(myrelid, AccessExclusiveLock, &child_numparents);

        /*
         * find_all_inheritors does the recursive search of the inheritance
         * hierarchy, so all we have to do is process all of the relids in the
         * list that it returns.
         */
        forboth(lo, child_oids, li, child_numparents)
        {
            Oid childrelid = lfirst_oid(lo);
            int numparents = lfirst_int(li);

            if (childrelid == myrelid)
                continue;
            /* note we need not recurse again */
            renameatt_internal(childrelid, oldattname, newattname, false, true, numparents, behavior);
        }
    } else {
        /*
         * If we are told not to recurse, there had better not be any child
         * tables; else the rename would put them out of step.
         *
         * expected_parents will only be 0 if we are not already recursing.
         */
        if (expected_parents == 0 && find_inheritance_children(myrelid, NoLock) != NIL)
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                    errmsg("inherited column \"%s\" must be renamed in child tables too", oldattname)));
    }

    /* rename attributes in typed tables of composite type */
    if (targetrelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) {
        List* child_oids = NIL;
        ListCell* lo = NULL;

        child_oids = find_typed_table_dependencies(
            targetrelation->rd_rel->reltype, RelationGetRelationName(targetrelation), behavior);

        foreach (lo, child_oids)
            renameatt_internal(lfirst_oid(lo), oldattname, newattname, true, true, 0, behavior);
    }

    attrelation = heap_open(AttributeRelationId, RowExclusiveLock);

    atttup = SearchSysCacheCopyAttName(myrelid, oldattname);
    if (!HeapTupleIsValid(atttup)) {
        ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("column \"%s\" does not exist", oldattname)));
    }
    attform = (Form_pg_attribute)GETSTRUCT(atttup);

    attnum = attform->attnum;
    if (attnum <= 0) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot rename system column \"%s\"", oldattname)));
    }

    /*
     * if the attribute is inherited, forbid the renaming.	if this is a
     * top-level call to renameatt(), then expected_parents will be 0, so the
     * effect of this code will be to prohibit the renaming if the attribute
     * is inherited at all.  if this is a recursive call to renameatt(),
     * expected_parents will be the number of parents the current relation has
     * within the inheritance hierarchy being processed, so we'll prohibit the
     * renaming only if there are additional parents from elsewhere.
     */
    if (attform->attinhcount > expected_parents) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("cannot rename inherited column \"%s\"", oldattname)));
    }

    /* new name should not already exist */
    check_for_column_name_collision(targetrelation, newattname);

    /* new name should not conflict with system columns */
    if (RelationIsColStore(targetrelation) && CHCHK_PSORT_RESERVE_COLUMN(newattname)) {
        ereport(ERROR,
            (errcode(ERRCODE_DUPLICATE_COLUMN),
                errmsg("column name \"%s\" conflicts with a system column name", newattname)));
    }

    /* apply the update */
    (void)namestrcpy(&(attform->attname), newattname);
    bool should_rename_ce = false;
    if (attform->atttypid == BYTEAWITHOUTORDERWITHEQUALCOLOID || attform->atttypid == BYTEAWITHOUTORDERCOLOID) {
        should_rename_ce = true;
    }

    simple_heap_update(attrelation, &atttup->t_self, atttup);

    /* keep system catalog indexes current */
    CatalogUpdateIndexes(attrelation, atttup);
    tableam_tops_free_tuple(atttup);
    heap_close(attrelation, RowExclusiveLock);

    /* recurse rename cstore delta table column name */
#ifdef ENABLE_MULTIPLE_NODES
    if (g_instance.attr.attr_storage.enable_delta_store && RelationIsCUFormat(targetrelation)) {
#else
    /*
     * Under centrailzed mode, there may be unique index on delta table. When checking unique
     * constraint, unique index on delta will be used. So we ignore enable_delta_store here
     * and alter delta table at the same time.
     */
    if (RelationIsCUFormat(targetrelation)) {
#endif
        List* child_oids = NIL;
        ListCell* child = NULL;
        child_oids = find_cstore_delta(targetrelation, AccessExclusiveLock);

        foreach (child, child_oids) {
            Oid childrelid = lfirst_oid(child);
            if (childrelid == myrelid)
                continue;
            renameatt_internal(childrelid, oldattname, newattname, false, true, 1, behavior);
        }
    }

    /* Recode time of reaname relation att. */
    recordRelationMTime(myrelid, targetrelation->rd_rel->relkind);

    relation_close(targetrelation, NoLock); /* close rel but keep lock */

    if (should_rename_ce) {
#ifdef ENABLE_MULTIPLE_NODES
        if (!IS_PGXC_COORDINATOR) {
            return;
        }
#endif
        ce_relation = heap_open(ClientLogicCachedColumnsId, RowExclusiveLock);
        ce_tuple =  search_sys_cache_copy_ce_col_name(myrelid, oldattname);
        if (!HeapTupleIsValid(ce_tuple))
            ereport(ERROR,
                    (errcode(ERRCODE_UNDEFINED_COLUMN),
                     errmsg("encrypted column \"%s\" does not exist",
                            oldattname)));
        ce_form = (Form_gs_encrypted_columns) GETSTRUCT(ce_tuple);
        namestrcpy(&(ce_form->column_name), newattname);
        simple_heap_update(ce_relation, &ce_tuple->t_self, ce_tuple);

        /* keep system catalog indexes current */
        CatalogUpdateIndexes(ce_relation, ce_tuple);

        heap_freetuple_ext(ce_tuple);

        heap_close(ce_relation, RowExclusiveLock);
    }
    return attnum;
}

/*
 * Perform permissions and integrity checks before acquiring a relation lock.
 */
static void RangeVarCallbackForRenameAttribute(
    const RangeVar* rv, Oid relid, Oid oldrelid, bool target_is_partition, void* arg)
{
    HeapTuple tuple;
    Form_pg_class form;

    tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
    if (!HeapTupleIsValid(tuple))
        return; /* concurrently dropped */
    form = (Form_pg_class)GETSTRUCT(tuple);
    renameatt_check(relid, form, false);
    ReleaseSysCache(tuple);
}

/*
 *		renameatt		- changes the name of a attribute in a relation
 */
ObjectAddress renameatt(RenameStmt* stmt)
{
    Oid relid;
    AttrNumber  attnum;
    ObjectAddress address;

    /* lock level taken here should match renameatt_internal */
    relid = RangeVarGetRelidExtended(stmt->relation,
        AccessExclusiveLock,
        stmt->missing_ok,
        false,
        false,
        false,
        RangeVarCallbackForRenameAttribute,
        NULL);

    if (!OidIsValid(relid)) {
        ereport(NOTICE, (errmsg("relation \"%s\" does not exist, skipping", stmt->relation->relname)));
        return InvalidObjectAddress;
    }

    TrForbidAccessRbObject(RelationRelationId, relid, stmt->relation->relname);
    if (enable_plpgsql_gsdependency_guc()) {
        Oid type_oid = get_rel_type_id(relid);
        if (OidIsValid(type_oid)) {
            GsDependObjDesc obj;
            gsplsql_get_depend_obj_by_typ_id(&obj, type_oid, InvalidOid);
            HeapTuple obj_tup = gsplsql_search_object(&obj, false);
            if (HeapTupleIsValid(obj_tup)) {
                heap_freetuple(obj_tup);
                pfree_ext(obj.schemaName);
                pfree_ext(obj.packageName);
                pfree_ext(obj.name);
                ereport(ERROR,
                    (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
                     errmsg("cannot rename attribute of the type because it is dependent on another object.")));
            }
            obj.refPosType = GSDEPEND_REFOBJ_POS_IN_TYPE;
            bool exist_dep = gsplsql_exist_dependency(&obj);
            pfree_ext(obj.schemaName);
            pfree_ext(obj.packageName);
            pfree_ext(obj.name);
            if (exist_dep) {
                ereport(ERROR,
                    (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
                     errmsg("cannot rename attribute of the type because it is dependent on another object."))); 
            }
        }
    }
    // Check relations's internal mask
    Relation rel = relation_open(relid, AccessShareLock);
    if ((((uint32)RelationGetInternalMask(rel)) & INTERNAL_MASK_DALTER))
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Un-support feature"),
                errdetail("internal relation doesn't allow ALTER")));
#ifdef ENABLE_MULTIPLE_NODES
    if (RelationIsTsStore(rel)) {
        bool kvtype_tag = get_kvtype_by_oldname(rel, stmt->subname);
        if (kvtype_tag) {
            Oid tag_relid = get_tag_relid(RelationGetRelationName(rel), RelationGetNamespace(rel));
            renameatt_internal(tag_relid,
                               stmt->subname,
                               stmt->newname,
                               interpretInhOption(stmt->relation->inhOpt),
                               false,
                               0,
                               stmt->behavior);
        }
    }
#endif
    /* We allow to alter global temp table only this session use it */
    CheckGttTableInUse(rel);
    if (is_masked_relation(relid, stmt->subname) && !u_sess->attr.attr_common.IsInplaceUpgrade){
        ereport(ERROR, (errmodule(MOD_COMMAND), errcode(ERRCODE_WRONG_OBJECT_TYPE),
            errmsg("Column: %s has bound some masking policies, can not be renamed.", stmt->subname),
                errdetail("cannot rename masking column")));
    }

#ifdef ENABLE_MOT
    if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE && isMOTFromTblOid(RelationGetRelid(rel))) {
        RenameForeingTableCmd cmd = {
            T_RenameForeingTableCmd,
            relid,
            stmt->renameType,
            stmt->subname,
            stmt->newname
        };
        FdwRoutine* fdwroutine;

        if (rel->rd_fdwroutine != nullptr) {
            fdwroutine = rel->rd_fdwroutine;
        } else {
            fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(rel));
        }

        if (fdwroutine->ValidateTableDef != nullptr) {
            fdwroutine->ValidateTableDef((Node*)&cmd);
        }
    }
#endif

    relation_close(rel, AccessShareLock);

    attnum = renameatt_internal(relid,
        stmt->subname,                              /* old att name */
        stmt->newname,                              /* new att name */
        interpretInhOption(stmt->relation->inhOpt), /* recursive? */
        false,                                      /* recursing? */
        0,                                          /* expected inhcount */
        stmt->behavior);

    /* This is an ALTER TABLE command so it's about the relid */
    ObjectAddressSubSet(address, RelationRelationId, relid, attnum);
    return address;

}

/*
 * same logic as renameatt_internal
 */
static ObjectAddress  rename_constraint_internal(Oid myrelid, Oid mytypid, const char* oldconname, const char* newconname,
    bool recurse, bool recursing, int expected_parents)
{
    Relation targetrelation = NULL;
    Oid constraintOid;
    HeapTuple tuple;
    Form_pg_constraint con;
    ObjectAddress address;

    AssertArg(!myrelid || !mytypid);

    if (mytypid) {
        constraintOid = get_domain_constraint_oid(mytypid, oldconname, false);
    } else {
        targetrelation = relation_open(myrelid, AccessExclusiveLock);

        /* We allow to alter global temp table only this session use it */
        CheckGttTableInUse(targetrelation);

        /*
         * don't tell it whether we're recursing; we allow changing typed
         * tables here
         */
        renameatt_check(myrelid, RelationGetForm(targetrelation), false);

        constraintOid = get_relation_constraint_oid(myrelid, oldconname, false);
    }

    TrForbidAccessRbObject(ConstraintRelationId, constraintOid, oldconname);

    tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraintOid));
    if (!HeapTupleIsValid(tuple)) {
        ereport(ERROR,
            (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for constraint %u", constraintOid)));
    }
    con = (Form_pg_constraint)GETSTRUCT(tuple);

    if (myrelid && con->contype == CONSTRAINT_CHECK && !con->connoinherit) {
        if (recurse) {
            List* child_oids = NIL;
            List* child_numparents = NIL;
            ListCell* lo = NULL;
            ListCell* li = NULL;

            child_oids = find_all_inheritors(myrelid, AccessExclusiveLock, &child_numparents);

            forboth(lo, child_oids, li, child_numparents)
            {
                Oid childrelid = lfirst_oid(lo);
                int numparents = lfirst_int(li);

                if (childrelid == myrelid)
                    continue;

                rename_constraint_internal(childrelid, InvalidOid, oldconname, newconname, false, true, numparents);
            }
        } else {
            if (expected_parents == 0 && find_inheritance_children(myrelid, NoLock) != NIL)
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                        errmsg("inherited constraint \"%s\" must be renamed in child tables too", oldconname)));
        }

        if (con->coninhcount > expected_parents)
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                    errmsg("cannot rename inherited constraint \"%s\"", oldconname)));
    }

    if (con->conindid && (con->contype == CONSTRAINT_PRIMARY || con->contype == CONSTRAINT_UNIQUE ||
                             con->contype == CONSTRAINT_EXCLUSION))
        /* rename the index; this renames the constraint as well */
        RenameRelationInternal(con->conindid, newconname);
    else
        RenameConstraintById(constraintOid, newconname);

    ObjectAddressSet(address, ConstraintRelationId, constraintOid);
    ReleaseSysCache(tuple);

    if (targetrelation) {
        /* Recode time of rename relation constraint. */
        recordRelationMTime(targetrelation->rd_id, targetrelation->rd_rel->relkind);

        /*
         * Invalidate relcache so as others can see the new constraint name.
         */
        CacheInvalidateRelcache(targetrelation);

        relation_close(targetrelation, NoLock); /* close rel but keep lock */
    }
    return address;
}

ObjectAddress RenameConstraint(RenameStmt* stmt)
{
    Oid relid = InvalidOid;
    Oid typid = InvalidOid;

    if (stmt->relationType == OBJECT_DOMAIN) {
        Relation rel;
        HeapTuple tup;

        typid = typenameTypeId(NULL, makeTypeNameFromNameList(stmt->object));
        rel = heap_open(TypeRelationId, RowExclusiveLock);
        tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
        if (!HeapTupleIsValid(tup)) {
            ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for type %u", typid)));
        }
        checkDomainOwner(tup);
        ReleaseSysCache(tup);
        heap_close(rel, NoLock);
    } else {
        /* lock level taken here should match rename_constraint_internal */
        relid = RangeVarGetRelidExtended(stmt->relation,
            AccessExclusiveLock,
            stmt->missing_ok,
            false,
            false,
            false,
            RangeVarCallbackForRenameAttribute,
            NULL);

        if (!OidIsValid(relid)) {
            ereport(NOTICE, (errmsg("relation \"%s\" does not exist, skipping", stmt->relation->relname)));
            return InvalidObjectAddress;
        }
    }

    return rename_constraint_internal(relid,
        typid,
        stmt->subname,
        stmt->newname,
        stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
        false,                                                               /* recursing? */
        0 /* expected inhcount */);
}

static bool FindSynonymExist(char* relname, char* relnamespace)
{
    HeapTuple htup = NULL;
    bool isnull = false;
    bool result = false;
    Relation rel_synonym = heap_open(PgSynonymRelationId, RowExclusiveLock);
    SysScanDesc adscan = systable_beginscan(rel_synonym, InvalidOid, false, NULL, 0, NULL);
    while (HeapTupleIsValid(htup = systable_getnext(adscan))) {
        Datum val = heap_getattr(htup, Anum_pg_synonym_synobjschema, rel_synonym->rd_att, &isnull);
        if (val && pg_strcasecmp(DatumGetCString(val), relnamespace) == 0) {
            val = heap_getattr(htup, Anum_pg_synonym_synobjname, rel_synonym->rd_att, &isnull);
            if (val && pg_strcasecmp(DatumGetCString(val), relname) == 0) {
                result = true;
            }
        }
    }
    systable_endscan(adscan);
    heap_close(rel_synonym, RowExclusiveLock);
    return result;
}

static int Compare_RenameTableNameData_func(const void* a, const void* b)
{
    if (strcmp(((const RenameTableNameData*)a)->schemaname, ((const RenameTableNameData*)b)->schemaname) == 0) {
        return strcmp(((const RenameTableNameData*)a)->relname, ((const RenameTableNameData*)b)->relname);
    } else {
        return strcmp(((const RenameTableNameData*)a)->schemaname, ((const RenameTableNameData*)b)->schemaname);
    }
}

static ObjectAddress RenameTableFeature(RenameStmt* stmt)
{
    char *orgiSchema = NULL, *orgitable = NULL, *modfySchema = NULL, *modfytable = NULL;
    Oid orgiNameSpace = InvalidOid, modfyNameSpace = InvalidOid;
    List* search_path = fetch_search_path(false);
    Oid relnamespace = InvalidOid;
    RangeVar* temp_name = NULL;
    Oid relid = InvalidOid, relid_temp = InvalidOid;
    Relation rel_pg_class, rel_pg_type;
    HeapTuple tup;
    HeapTuple newtup;
    Form_pg_class relform;
    ObjectAddress address;

    Datum values[Natts_pg_class] = { 0 };
    bool nulls[Natts_pg_class] = { false };
    bool replaces[Natts_pg_class] = { false };
    Datum type_values[Natts_pg_type] = { 0 };
    bool type_nulls[Natts_pg_type] = { false };
    bool type_replaces[Natts_pg_type] = { false };

    if (stmt->renameTargetList == NULL) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("Cannot get rename table name and modify name")));
    }
    RenameTableNameData storageTable[stmt->renameTargetList->length];
    bool tempSchema[stmt->renameTargetList->length] = { false };
    Relation lockRelation[stmt->renameTargetList->length];
    int tableName_Count = 0;
    ListCell* rename_Cell = NULL;
    foreach(rename_Cell, stmt->renameTargetList) {
        RenameCell* renameInfo = (RenameCell*)lfirst(rename_Cell);
        temp_name = renameInfo->original_name;
        orgiSchema = temp_name->schemaname;
        /* orgitable NOT NULL */
        Assert(temp_name->relname != NULL);
        orgitable = temp_name->relname;
        /* if schema name don't assign */
        if (orgiSchema == NULL && search_path != NIL) {
            ListCell* l = NULL;
            foreach(l, search_path) {
                relnamespace = lfirst_oid(l);
                if (!OidIsValid(relnamespace)) {
                    ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("Cannot get current namespace on Rename Table.")));
                }
                relid = get_relname_relid(orgitable, relnamespace);
                /* Traversal the search_path until the correct schema of table is found */
                if (OidIsValid(relid)) {
                    orgiSchema = get_namespace_name(relnamespace);
                    break;
                }
            }
        }
        if (orgiSchema == NULL) {
            orgiSchema = get_namespace_name(PG_PUBLIC_NAMESPACE);
            tempSchema[tableName_Count] = true;
        }
        storageTable[tableName_Count].schemaname = pstrdup(orgiSchema);
        storageTable[tableName_Count].relname = pstrdup(orgitable);
        tableName_Count++;

        if (temp_name->schemaname == NULL) {
            if (t_thrd.mem_cxt.msg_mem_cxt) {
                temp_name->schemaname = MemoryContextStrdup(t_thrd.mem_cxt.msg_mem_cxt, orgiSchema);
            } else {
                temp_name->schemaname = pstrdup(orgiSchema);
            }
        }
    }

    if (stmt->renameTargetList->length >= 2) {
        qsort((void*)storageTable, (size_t)stmt->renameTargetList->length, sizeof(RenameTableNameData), Compare_RenameTableNameData_func);
    }
    for (int num = 0; num < stmt->renameTargetList->length; num++) {
        if (orgiSchema != NULL && !tempSchema[num]) {
            orgiNameSpace = get_namespace_oid(storageTable[num].schemaname, false);
        } else if (OidIsValid(u_sess->catalog_cxt.myTempNamespace)) {
            orgiNameSpace = u_sess->catalog_cxt.myTempNamespace;
        }
        relid = get_relname_relid(storageTable[num].relname, orgiNameSpace);
        if(!OidIsValid(relid)) {
            lockRelation[num] = NULL;
            orgiNameSpace = InvalidOid;
            continue;
        } else {
            /* Don't support Tempporary Table */
            if (IsTempTable(relid) || IsGlobalTempTable(relid)) {
                ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("relation %s is temporary table, Rename table don't support.", get_rel_name(relid))));
            }
            lockRelation[num] = relation_open(relid, AccessExclusiveLock);
        }
    }

    foreach(rename_Cell, stmt->renameTargetList) {
    /* acquire the schema and table name in renameTargetList */
        RenameCell* renameInfo = (RenameCell*)lfirst(rename_Cell);
        temp_name = renameInfo->original_name;
        orgiSchema = temp_name->schemaname;
        if (orgiSchema != NULL) {
            orgiNameSpace = get_namespace_oid(orgiSchema, false);
        }
        orgitable = temp_name->relname;
        temp_name = renameInfo->modify_name;
        modfySchema = temp_name->schemaname;
        if (modfySchema != NULL) {
            modfyNameSpace = get_namespace_oid(modfySchema, false);
        }
        /* modfytable NOT NULL */
        Assert(temp_name->relname);
        modfytable = temp_name->relname;

        /* obtain search_path, get schema name */
        if (orgiSchema == NULL && search_path != NIL) {
            ListCell* l = NULL;
            foreach (l, search_path) {
                relnamespace = lfirst_oid(l);
                if (!OidIsValid(relnamespace)) {
                    ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("Cannot get current namespace on Rename Table.")));
                }
                relid = get_relname_relid(orgitable, relnamespace);
                /* Traversal the search_path until the correct schema of table is found */
                if (OidIsValid(relid)) {
                    orgiSchema = get_namespace_name(relnamespace);
                    orgiNameSpace = relnamespace;
                    if (modfySchema == NULL) {
                        modfyNameSpace = relnamespace;
                    }
                    break;
                }
            }
        } else if (search_path == NIL && orgiSchema == NULL) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("Rename Table search_path get NIL in error.")));
        }
        if (modfySchema == NULL && orgiSchema != NULL) {
            /* if modfytable table has no schema specified,
             * it's the same as orgiNameSpace */
            modfyNameSpace = orgiNameSpace;
        }

        if (temp_name->schemaname == NULL) {
            temp_name->schemaname = MemoryContextStrdup(t_thrd.mem_cxt.msg_mem_cxt, orgiSchema);
        }

        /* Check whether exist Synonym on old table name and new table name */
        if (orgiSchema == NULL) {
            orgiSchema = get_namespace_name(relnamespace);
        }
        if (FindSynonymExist(orgitable, orgiSchema)) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("Rename Table \"%s.%s\" exist Synonym, so Rename table can't execute.",
                orgiSchema, orgitable)));
        } else if (modfySchema != NULL && SearchSysCacheExists2(SYNONYMNAMENSP, PointerGetDatum(modfytable), ObjectIdGetDatum(modfyNameSpace))) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("Rename Table \"%s.%s\" exist Synonym, so Rename table can't execute.",
                modfySchema, modfytable)));
        } else if ((orgiSchema != NULL && modfySchema == NULL) &&
            SearchSysCacheExists2(SYNONYMNAMENSP, PointerGetDatum(modfytable), ObjectIdGetDatum(orgiNameSpace))) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("Rename Table \"%s.%s\" exist Synonym, so Rename table can't execute.",
                orgiSchema, modfytable)));
        } else if ((orgiSchema == NULL && modfySchema == NULL) &&
            SearchSysCacheExists2(SYNONYMNAMENSP, PointerGetDatum(modfytable), ObjectIdGetDatum(relnamespace))) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("Rename Table \"%s.%s\" exist Synonym, so Rename table can't execute.",
                get_namespace_name(relnamespace), modfytable)));
        } else if (orgitable != NULL) {
            Oid temp_namespace = InvalidOid;
            if (orgiSchema != NULL) {
                if (OidIsValid(orgiNameSpace)) {
                    temp_namespace = orgiNameSpace;
                } else {
                    temp_namespace = relnamespace;
                }
            } else {
                temp_namespace = relnamespace;
            }
            if (SearchSysCacheExists2(SYNONYMNAMENSP, PointerGetDatum(orgitable), ObjectIdGetDatum(temp_namespace))) {
                ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("Rename Table \"%s.%s\" is Synonym, so Rename table can't support.",
                    get_namespace_name(temp_namespace), orgitable)));
            }
        }

        /* check a user's access privileges to a namespace */
        if (pg_namespace_aclcheck(orgiNameSpace, GetUserId(), ACL_USAGE) != ACLCHECK_OK) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("User %s don't have creat privileges on Schema %s.",
                GetUserNameFromId(GetUserId()), get_namespace_name(orgiNameSpace))));
        }
        if (pg_namespace_aclcheck(modfyNameSpace, GetUserId(), ACL_USAGE) != ACLCHECK_OK) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("User %s don't have creat privileges on Schema %s.",
                GetUserNameFromId(GetUserId()), get_namespace_name(modfyNameSpace))));
        }

        /* Do rename table work */
        rel_pg_class = heap_open(RelationRelationId, RowExclusiveLock);
        relid = get_relname_relid(orgitable, orgiNameSpace);

        /* Support view but cannot span schemaes */
        if (!OidIsValid(relid)) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("relation \"%s.%s\" does not exist, skipping", get_namespace_name(orgiNameSpace), orgitable)));
        } else if (IsRelaionView(relid) && modfyNameSpace != orgiNameSpace) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("relation %s is view, Rename table don't support span schemaes.", get_rel_name(relid))));
        } else if (orgiNameSpace == modfyNameSpace && pg_strcasecmp(orgitable, modfytable) == 0) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("relation \"%s.%s\" already exists", get_namespace_name(modfyNameSpace), modfytable)));
        } else if (pg_class_aclcheck(relid, GetUserId(), ACL_ALTER) == ACLCHECK_NO_PRIV) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("permission denied for relation %s.%s", get_namespace_name(orgiNameSpace), orgitable)));
        } else if (OidIsValid(get_relname_relid(modfytable, modfyNameSpace))) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("relation \"%s.%s\" already exists", get_namespace_name(modfyNameSpace), modfytable)));
        }

        /* Rename regular table */
        replaces[Anum_pg_class_relname - 1] = true;
        values[Anum_pg_class_relname - 1] = DirectFunctionCall1(namein, CStringGetDatum(modfytable));
        type_replaces[Anum_pg_type_typname - 1] = true;
        type_values[Anum_pg_type_typname - 1] = DirectFunctionCall1(namein, CStringGetDatum(modfytable));
        if (modfySchema != NULL) {
            replaces[Anum_pg_class_relnamespace - 1] = true;
            values[Anum_pg_class_relnamespace - 1] = ObjectIdGetDatum(modfyNameSpace);
            type_replaces[Anum_pg_type_typnamespace - 1] = true;
            type_values[Anum_pg_type_typnamespace - 1] = ObjectIdGetDatum(modfyNameSpace);
        }

        /* delete table privileges */
        /* delete the table relacl. only superuser can operate the table */
        nulls[Anum_pg_class_relacl - 1] = true;
        replaces[Anum_pg_class_relacl - 1] = true;

        tup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
        if (!HeapTupleIsValid(tup)) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("cache lookup failed for relation %s", get_rel_name(relid))));
        }

        relform = (Form_pg_class)GETSTRUCT(tup);
        if (relform->relkind == RELKIND_RELATION && relform->parttype == PARTTYPE_PARTITIONED_RELATION) {
            renamePartitionedTable(relid, modfytable);
        } else if (relform->relhastriggers && modfyNameSpace != orgiNameSpace) {
            ScanKeyData key;
            bool is_find = false;
            HeapTuple tuple = NULL;
            Relation tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
            ScanKeyInit(&key, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relid));
            SysScanDesc scan = systable_beginscan(tgrel, TriggerRelidNameIndexId, true, NULL, 1, &key);
            while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
                Form_pg_trigger pg_trigger = (Form_pg_trigger)GETSTRUCT(tuple);
                if (!pg_trigger->tgisinternal) {
                    is_find = true;
                    break;
                }
            }
            systable_endscan(scan);
            heap_close(tgrel, RowExclusiveLock);
            if (is_find) {
                ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("Trigger in wrong schema on table %s", get_rel_name(relid))));
            }
        }
        /* Fix other dependent stuff */
        if (relform->relkind == RELKIND_RELATION || relform->relkind == RELKIND_MATVIEW) {
            ObjectAddresses* objsMoved = NULL;
            objsMoved = new_object_addresses();
            Relation rel;
            rel = relation_open(relid, NoLock);
            // AlterIndexNamespaces(rel_pg_class, rel, orgiNameSpace, modfyNameSpace, objsMoved);
            // AlterSeqNamespaces(rel_pg_class, rel, orgiNameSpace, modfyNameSpace, objsMoved, AccessExclusiveLock);
            AlterConstraintNamespaces(RelationGetRelid(rel), orgiNameSpace, modfyNameSpace, false, objsMoved);
            relation_close(rel, NoLock);
        }

        relid_temp = relid;
        newtup = heap_modify_tuple(tup, RelationGetDescr(rel_pg_class), values, nulls, replaces);
        simple_heap_update(rel_pg_class, &newtup->t_self, newtup);
        CatalogUpdateIndexes(rel_pg_class, newtup);
        ReleaseSysCache(tup);
        tableam_tops_free_tuple(newtup);
        heap_close(rel_pg_class, RowExclusiveLock);
        CommandCounterIncrement();

        rel_pg_type = heap_open(TypeRelationId, RowExclusiveLock);
        relid = get_typeoid(orgiNameSpace, orgitable);
        if (!OidIsValid(relid)) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("type \"%s.%s\" does not exist, skipping", get_namespace_name(orgiNameSpace), orgitable)));
        }
        tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(relid));
        if (!HeapTupleIsValid(tup)) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("cache lookup failed for type %s", get_rel_name(relid))));
        }
        newtup = heap_modify_tuple(tup, RelationGetDescr(rel_pg_type), type_values, type_nulls, type_replaces);
        simple_heap_update(rel_pg_type, &newtup->t_self, newtup);
        CatalogUpdateIndexes(rel_pg_type, newtup);
        ReleaseSysCache(tup);
        tableam_tops_free_tuple(newtup);
        heap_close(rel_pg_type, RowExclusiveLock);
        CommandCounterIncrement();

        /* update dependencies to point to the new schema */
        (void)changeDependencyFor(RelationRelationId, relid_temp, NamespaceRelationId, orgiNameSpace, modfyNameSpace);
    }
    for (int num = stmt->renameTargetList->length - 1; num >= 0; num--) {
        if (lockRelation[num] != NULL) {
            relation_close(lockRelation[num], AccessExclusiveLock);
        }
    }
    for (int num = 0; num < stmt->renameTargetList->length; num++) {
        pfree(storageTable[num].schemaname);
        pfree(storageTable[num].relname);
    }
    list_free_ext(search_path);
    ObjectAddressSet(address, RelationRelationId, relid);
    return address;
}

/*
 * Execute ALTER TABLE/INDEX/SEQUENCE/VIEW/FOREIGN TABLE RENAME
 */
ObjectAddress RenameRelation(RenameStmt* stmt)
{
    if (stmt->renameTableflag) {
        return RenameTableFeature(stmt);
    } else {
        Oid relid;
        ObjectAddress address;
        HeapTuple tuple;
        Datum name;
        bool isnull = false;
        char *relname = NULL;

        /*
         * Grab an exclusive lock on the target table, index, sequence or view,
         * which we will NOT release until end of transaction.
         *
         * Lock level used here should match RenameRelationInternal, to avoid lock
         * escalation.
         */
        relid = RangeVarGetRelidExtended(stmt->relation,
            AccessExclusiveLock,
            stmt->missing_ok,
            false,
            false,
            false,
            RangeVarCallbackForAlterRelation,
            (void*)stmt);

        if (!OidIsValid(relid)) {
            ereport(NOTICE, (errmsg("relation \"%s\" does not exist, skipping", stmt->relation->relname)));
            return InvalidObjectAddress;
        }

        tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
        if (!HeapTupleIsValid(tuple)) {
            ereport(ERROR,
                (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", relid)));
        }
        name = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relname, &isnull);
        Assert(!isnull);
        relname = DatumGetName(name)->data;
        if (ISMLOG(relname) || ISMATMAP(relname)) {
            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errmsg("Un-support feature"),
                errdetail("%s table doesn't support this ALTER yet.", ISMLOG(relname) ? "mlog" : "matviewmap"))));
        }
        ReleaseSysCache(tuple);
        if (enable_plpgsql_gsdependency_guc()) {
            bool exist_dep = false;
            char rel_kind = get_rel_relkind(relid);
            if (RELKIND_RELATION == rel_kind) {
                exist_dep = gsplsql_is_object_depend(get_rel_type_id(relid), GSDEPEND_OBJECT_TYPE_TYPE);
            }
            if (exist_dep) {
                ereport(ERROR,
                    (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
                     errmsg("The rename operator on %s is not allowed, "
                            "because it is dependent on another object.", stmt->relation->relname)));
            }
        }
        TrForbidAccessRbObject(RelationRelationId, relid, stmt->relation->relname);
        /* If table has history table, we need rename corresponding history table */
        if (is_ledger_usertable(relid)) {
            rename_hist_by_usertable(relid, stmt->newname);
        }

#ifdef ENABLE_MOT
        if (stmt->renameType == OBJECT_INDEX) {
            Oid relOid = IndexGetRelation(relid, false);
            Relation rel = RelationIdGetRelation(relOid);
            if (RelationIsForeignTable(rel) && isMOTFromTblOid(RelationGetRelid(rel))) {
                FdwRoutine* fdwroutine = rel->rd_fdwroutine;
                if (fdwroutine == nullptr) {
                    fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(rel));
                }
                if (fdwroutine->ValidateTableDef != nullptr) {
                    fdwroutine->ValidateTableDef((Node*)stmt);
                }
            }
            RelationClose(rel);
        }
#endif

        /* Do the work */
        RenameRelationInternal(relid, stmt->newname, stmt->newschema);
        /*
         * Record the changecsn of the table that defines the index
         */
        if (stmt->renameType == OBJECT_INDEX) {
            Oid relOid = IndexGetRelation(relid, false);
            Relation userRelaiton = RelationIdGetRelation(relOid);
            UpdatePgObjectChangecsn(relOid, userRelaiton->rd_rel->relkind);
            RelationClose(userRelaiton);
        }
        ObjectAddressSet(address, RelationRelationId, relid);

    return address;
}
}

/*
 *		RenameRelationInternal - change the name of a relation
 *
 *		XXX - When renaming sequences, we don't bother to modify the
 *			  sequence name that is stored within the sequence itself
 *			  (this would cause problems with MVCC). In the future,
 *			  the sequence name should probably be removed from the
 *			  sequence, AFAIK there's no need for it to be there.
 */
void RenameRelationInternal(Oid myrelid, const char* newrelname, char* newschema)
{
    Relation targetrelation;
    Relation relrelation; /* for RELATION relation */
    HeapTuple reltup;
    Form_pg_class relform;
    Oid namespaceId;
    Oid oldNspOid = InvalidOid;
    bool needChangeNsp = false;
    ObjectAddresses* objsMoved = NULL;
    ObjectAddress thisobj;
    bool is_present = false;

    thisobj.classId = RelationRelationId;
    thisobj.objectId = myrelid;
    thisobj.objectSubId = 0;

    /*
     * Grab an exclusive lock on the target table, index, sequence or view,
     * which we will NOT release until end of transaction.
     */
    targetrelation = relation_open(myrelid, AccessExclusiveLock);

    if (newschema != NULL) {
        if (targetrelation->rd_mlogoid != InvalidOid) {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    (errmsg("Un-support feature"),
                        errdetail("table owning matview doesn't support this ALTER yet."))));
        }

        if (targetrelation->rd_rel->relkind == RELKIND_MATVIEW) {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("ALTER MATERIALIZED VIEW is not yet supported.")));
        }

        /* Permission check */
        if (!pg_class_ownercheck(RelationGetRelid(targetrelation), GetUserId())) {
            aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(targetrelation));
        }
    }

    if (RelationIsSubPartitioned(targetrelation)) {
        ereport(
            ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
             (errmsg("Un-support feature"),
              errdetail("For subpartition table, ALTER TABLE ... RENAME is not yet supported."),
              errcause("The function is not implemented."), erraction("Create a new table to replace it."))));
    }

    if (OBJECT_IS_SEQUENCE(targetrelation->rd_rel->reltype)|| RELKIND_IS_SEQUENCE(targetrelation->rd_rel->relkind)) {
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("RENAME SEQUENCE is not yet supported.")));
    }

    /* We allow to alter global temp table only this session use it */
    CheckGttTableInUse(targetrelation);

    namespaceId = RelationGetNamespace(targetrelation);

    /*
     * Find relation's pg_class tuple, and make sure newrelname isn't in use.
     */
    relrelation = heap_open(RelationRelationId, RowExclusiveLock);

    reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
    if (!HeapTupleIsValid(reltup)) {
        /* shouldn't happen */
        ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", myrelid)));
    }

    relform = (Form_pg_class)GETSTRUCT(reltup);


    oldNspOid = namespaceId;
    if (newschema != NULL) {
        /* Get and lock schema OID and check its permissions. */
        RangeVar* newrv = makeRangeVar(newschema, (char*)newrelname, -1);
        Oid newNspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL, '\0');

        needChangeNsp = (newNspOid != namespaceId);
        if (needChangeNsp) {
            /* common checks on switching namespaces */
            CheckSetNamespace(namespaceId, newNspOid, RelationRelationId, myrelid);
            ledger_check_switch_schema(namespaceId, newNspOid);
            objsMoved = new_object_addresses();
            namespaceId = newNspOid;
            is_present = object_address_present(&thisobj, objsMoved);
        }
    }

    /*
     * Check relation name to ensure that it doesn't conflict with existing synonym.
     */
    if (!IsInitdb && GetSynonymOid(newrelname, namespaceId, true) != InvalidOid) {
        ereport(ERROR,
                (errmsg("relation name is already used by an existing synonym in schema \"%s\"",
                    get_namespace_name(namespaceId))));
    }

    if (get_relname_relid(newrelname, namespaceId) != InvalidOid) {
        if (newschema != NULL) {
            ereport(ERROR,
                (errcode(ERRCODE_DUPLICATE_TABLE),
                    errmsg("relation \"%s\" already exists in schema \"%s\"",
                        newrelname,
                        newschema)));
        } else {
            ereport(ERROR, (errcode(ERRCODE_DUPLICATE_TABLE), errmsg("relation \"%s\" already exists", newrelname)));
        }
    }

#ifdef ENABLE_MULTIPLE_NODES
    if (RelationIsTsStore(targetrelation)) {
        if (!g_instance.attr.attr_common.enable_tsdb) {
            ereport(ERROR, (errcode(ERRCODE_OPERATE_FAILED), 
                            errmsg("Cannot rename timeseries table when enable_tsdb is off.")));
        }
        Tsdb::CheckTsRelname(newrelname);
        /* Rename corresponding tag table synchronously */
        RenameTsTagRelaion(RelationGetRelationName(targetrelation),
                           targetrelation->rd_rel->relnamespace, newrelname);
    }

    /* Rename range/list distributed table */
    if (IsLocatorDistributedBySlice(GetLocatorType(myrelid))) {
        RenameDistributedTable(myrelid, newrelname);
    }

#endif   /* ENABLE_MULTIPLE_NODES */

    /*
     * Update pg_class tuple with new relname.	(Scribbling on reltup is OK
     * because it's a copy...)
     */
    (void)namestrcpy(&(relform->relname), newrelname);

    /* Update pg_class tuple with new nsp. */
    relform->relnamespace = namespaceId;

    simple_heap_update(relrelation, &reltup->t_self, reltup);

    /* keep the system catalog indexes current */
    CatalogUpdateIndexes(relrelation, reltup);

    /* Recode time of rename relation. */
    recordRelationMTime(myrelid, targetrelation->rd_rel->relkind);

    /*
     * check rename's target is partitioned table
     */
    relform = (Form_pg_class)GETSTRUCT(reltup);
    if (relform->relkind == RELKIND_RELATION && relform->parttype == PARTTYPE_PARTITIONED_RELATION) {
        renamePartitionedTable(myrelid, newrelname);
    }

    if (needChangeNsp && !is_present) {
        if (changeDependencyFor(RelationRelationId, myrelid, NamespaceRelationId, oldNspOid, namespaceId) != 1) {
            ereport(ERROR,
                (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                    errmsg("failed to change schema dependency for relation \"%s\"", NameStr(relform->relname))));
        }

        add_exact_object_address(&thisobj, objsMoved);
    }

    tableam_tops_free_tuple(reltup);
    heap_close(relrelation, RowExclusiveLock);

    if (needChangeNsp && !is_present) {
        AlterTableNamespaceDependentProcess(relrelation, targetrelation, oldNspOid, namespaceId, objsMoved,
                                            (char*)newrelname);
        if (targetrelation->rd_isblockchain) {
            rename_hist_by_newnsp(myrelid, newschema);
        }
        free_object_addresses(objsMoved);
    } else {
        /*
        * Also rename the associated type, if any.
        */
        if (OidIsValid(targetrelation->rd_rel->reltype))
            RenameTypeInternal(targetrelation->rd_rel->reltype, newrelname, oldNspOid);
    }

    /*
     * Also rename the associated constraint, if any.
     */
    if (RelationIsIndex(targetrelation)) {
        Oid constraintId = get_index_constraint(myrelid);

        if (OidIsValid(constraintId))
            RenameConstraintById(constraintId, newrelname);
    }
    /*
     * Close rel, but keep exclusive lock!
     */
    relation_close(targetrelation, NoLock);
}

/*
 *		ResetRelRewrite - reset relrewrite
 */
void ResetRelRewrite(Oid myrelid)
{
    HeapTuple newTuple;
    Relation pg_class;
    HeapTuple tuple;
    Datum relOptions;
    Datum newOptions;
    List* defList = NIL;
    Datum replVal[Natts_pg_class];
    bool replNull[Natts_pg_class];
    bool replRepl[Natts_pg_class];
    bool isNull = false;
    bool removed = false;
    errno_t rc;

    /*
     * Find relation's pg_class tuple.
     */
    pg_class = heap_open(RelationRelationId, RowExclusiveLock);
    tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
    if (!HeapTupleIsValid(tuple)) {
        ereport(ERROR,
            (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("could not find tuple for relation %u", myrelid)));
    }

    relOptions = fastgetattr(tuple, Anum_pg_class_reloptions, RelationGetDescr(pg_class), &isNull);
    if (!isNull) {
        defList = untransformRelOptions(relOptions);
        defList = RemoveRelOption(defList, "relrewrite", &removed);
        if (removed) {
            newOptions = transformRelOptions((Datum)0, defList, NULL, NULL, false, false);

            rc = memset_s(replVal, sizeof(replVal), 0, sizeof(replVal));
            securec_check(rc, "\0", "\0");
            rc = memset_s(replNull, sizeof(replNull), false, sizeof(replNull));
            securec_check(rc, "\0", "\0");
            rc = memset_s(replRepl, sizeof(replRepl), false, sizeof(replRepl));
            securec_check(rc, "\0", "\0");
            if (PointerIsValid(newOptions)) {
                replVal[Anum_pg_class_reloptions - 1] = newOptions;
                replNull[Anum_pg_class_reloptions - 1] = false;
            } else {
                replNull[Anum_pg_class_reloptions - 1] = true;
            }
            replRepl[Anum_pg_class_reloptions - 1] = true;
            newTuple = heap_modify_tuple(tuple, RelationGetDescr(pg_class), replVal, replNull, replRepl);
            simple_heap_update(pg_class, &newTuple->t_self, newTuple);
            CatalogUpdateIndexes(pg_class, newTuple);

            heap_freetuple_ext(newTuple);
        }
        list_free_ext(defList);
    }
    heap_freetuple_ext(tuple);

    heap_close(pg_class, RowExclusiveLock);
}

/*
 * @@GaussDB@@
 * Target              : data distributed by range or list
 * Brief               : Change the name of distributed table in pgxc_slice
 * Description :
 * Notes               :
 */
void RenameDistributedTable(Oid distributedTableOid, const char* distributedTableNewName)
{
    Relation distributedRelation = NULL;
    HeapTuple distributedRelationTuple = NULL;
    Form_pgxc_slice relationForm = NULL;

    /* shouldn't happen */
    if (!OidIsValid(distributedTableOid) || !PointerIsValid(distributedTableNewName)) {
        ereport(ERROR, (errcode(ERRCODE_OPERATE_FAILED), errmsg("internal error, rename distributed table failed")));
    }

    /*
     * Find relation's pgxc_slice tuple.
     */
    distributedRelation = relation_open(PgxcSliceRelationId, RowExclusiveLock);
    distributedRelationTuple = SearchTableEntryCopy(PGXC_SLICE_TYPE_TABLE, distributedTableOid);

    /* shouldn't happen */
    if (!HeapTupleIsValid(distributedRelationTuple)) {
        ereport(ERROR,
            (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", distributedTableOid)));
    }

    relationForm = (Form_pgxc_slice)GETSTRUCT(distributedRelationTuple);

    (void)namestrcpy(&(relationForm->relname), distributedTableNewName);
    simple_heap_update(distributedRelation, &(distributedRelationTuple->t_self), distributedRelationTuple);
    CatalogUpdateIndexes(distributedRelation, distributedRelationTuple);

    tableam_tops_free_tuple(distributedRelationTuple);

    relation_close(distributedRelation, RowExclusiveLock);
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		: Change the name of partitioned table in pg_partition
 * Description	:
 * Notes		:
 */
void renamePartitionedTable(Oid partitionedTableOid, const char* partitionedTableNewName)
{
    Relation partitionRelRelation = NULL;
    HeapTuple partitionRelationTuple = NULL;
    Form_pg_partition relationForm = NULL;

    /* shouldn't happen */
    if (!OidIsValid(partitionedTableOid) || !PointerIsValid(partitionedTableNewName)) {
        ereport(ERROR, (errcode(ERRCODE_OPERATE_FAILED), errmsg("internal error, rename partitioned table failed")));
    }

    /*
     * Find relation's pg_partition tuple.
     */
    partitionRelRelation = relation_open(PartitionRelationId, RowExclusiveLock);
    partitionRelationTuple = searchPgPartitionByParentIdCopy(PART_OBJ_TYPE_PARTED_TABLE, partitionedTableOid);

    /* shouldn't happen */
    if (!HeapTupleIsValid(partitionRelationTuple)) {
        ereport(ERROR,
            (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", partitionedTableOid)));
    }

    relationForm = (Form_pg_partition)GETSTRUCT(partitionRelationTuple);

    /*
     * Update pg_partition tuple with new relname.	(Scribbling on reltup is OK
     * because it's a copy...)
     */
    (void)namestrcpy(&(relationForm->relname), partitionedTableNewName);
    simple_heap_update(partitionRelRelation, &(partitionRelationTuple->t_self), partitionRelationTuple);

    /*
     * keep the system catalog indexes current
     */
    CatalogUpdateIndexes(partitionRelRelation, partitionRelationTuple);

    tableam_tops_free_tuple(partitionRelationTuple);

    relation_close(partitionRelRelation, RowExclusiveLock);
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		: Execute rename partition
 * Description	:
 * Notes		:
 */
ObjectAddress renamePartition(RenameStmt* stmt)
{
    Oid partitionedTableOid = InvalidOid;
    Oid partitionOid = InvalidOid;
    ParseState* pstate = NULL;
    RangePartitionDefState* rangePartDef = NULL;
    Relation rel = NULL;
    int2vector *partKeyArray = NULL;
    ObjectAddress address;

    /* shouldn't happen */
    if (!PointerIsValid(stmt) || !PointerIsValid(stmt->newname)) {
        ereport(ERROR, (errcode(ERRCODE_OPERATE_FAILED), errmsg("internal error, rename partition failed")));
    }

    /* Get oid of target partitioned table.
     *
     * Grab a shared lock on the target partitioned table,
     * which we will NOT release until end of transaction.
     *
     * Lock level used here should match renamePartitonInternal, to avoid lock
     * escalation.
     */
    partitionedTableOid = RangeVarGetRelidExtended(stmt->relation,
        ShareUpdateExclusiveLock,
        stmt->missing_ok,
        false,
        false,
        false,
        RangeVarCallbackForAlterRelation,
        (void*)stmt);

    if (!OidIsValid(partitionedTableOid)) {
        ereport(NOTICE, (errmsg("relation \"%s\" does not exist, skipping", stmt->relation->relname)));

        return InvalidObjectAddress;
    }

    TrForbidAccessRbObject(RelationRelationId, partitionedTableOid, stmt->relation->relname);

    rel = relation_open(partitionedTableOid, NoLock);

    if (RelationIsSubPartitioned(rel)) {
        ereport(
            ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
             (errmsg("Un-support feature"),
              errdetail("For subpartition table, ALTER TABLE ... RENAME PARTITION/SUBPARTITION is not yet supported."),
              errcause("The function is not implemented."), erraction("Use other actions instead."))));
    }
    /*
     * check relation is partitioned table
     */
    if (!PointerIsValid(rel->partMap)) {
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_TABLE),
                errmsg("relation \"%s\" is not partitioned table", stmt->relation->relname)));
    }

    /*
     * Get oid of target partition.
     * 1. If rename partition by name.
     */
    if (PointerIsValid(stmt->subname)) {
        partitionOid = PartitionNameGetPartitionOid(partitionedTableOid,
            stmt->subname,
            PART_OBJ_TYPE_TABLE_PARTITION,
            AccessExclusiveLock,
            true,
            false,
            NULL,
            NULL,
            NoLock);

        if (!OidIsValid(partitionOid)) {
            ereport(ERROR,
                (errcode(ERRCODE_UNDEFINED_TABLE),
                    errmsg(
                        "partition \"%s\" of relation \"%s\" does not exist", stmt->subname, stmt->relation->relname)));
        }
    } else {
        /*
         * 2. If rename partition by partition key value.
         */
        pstate = make_parsestate(NULL);

        rangePartDef = makeNode(RangePartitionDefState);
        rangePartDef->boundary = stmt->object;

        transformPartitionValue(pstate, (Node*)rangePartDef, false);
        partKeyArray = PartitionMapGetPartKeyArray(rel->partMap);
        rangePartDef->boundary = transformConstIntoTargetType(
            rel->rd_att->attrs, partKeyArray, rangePartDef->boundary);

        partitionOid =
            PartitionValuesGetPartitionOid(rel, rangePartDef->boundary, AccessExclusiveLock, true, true, false);

        pfree_ext(pstate);
        list_free_deep(rangePartDef->boundary);
        pfree_ext(rangePartDef);

        if (!OidIsValid(partitionOid)) {
            ereport(
                ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("The partition number is invalid or out-of-range")));
        }
    }

    /*
     * check partition new name does not exist.
     */
    if (InvalidOid != GetSysCacheOid3(PARTPARTOID,
                          NameGetDatum(stmt->newname),
                          CharGetDatum(PART_OBJ_TYPE_TABLE_PARTITION),
                          ObjectIdGetDatum(partitionedTableOid))) {
        ereport(ERROR,
            (errcode(ERRCODE_DUPLICATE_TABLE),
                errmsg("partition \"%s\" of relation \"%s\" already exists", stmt->newname, stmt->relation->relname)));
    }

    /* Do the work */
    renamePartitionInternal(partitionedTableOid, partitionOid, stmt->newname);

    UpdatePgObjectChangecsn(partitionedTableOid, rel->rd_rel->relkind);

    /* hold lock until committed */
    relation_close(rel, NoLock);
    ObjectAddressSet(address, PartitionRelationId, partitionOid);
    return address;
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		: Execute rename partition index
 * Description	:
 * Notes		:
 */
ObjectAddress renamePartitionIndex(RenameStmt* stmt)
{
    Oid partitionedTableIndexOid = InvalidOid;
    Oid partitionIndexOid = InvalidOid;
    ObjectAddress address;

    /* shouldn't happen */
    if (!PointerIsValid(stmt) || !PointerIsValid(stmt->subname) || !PointerIsValid(stmt->newname)) {
        ereport(
            ERROR, (errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("internal error, rename partition index failed")));
    }

    /* Get oid of target partitioned table index.
     *
     * Grab an RowExclusive lock on the target partitioned table,
     * which we will NOT release until end of transaction.
     *
     * Lock level used here should match renamePartitonInternal, to avoid lock
     * escalation.
     */
    partitionedTableIndexOid = RangeVarGetRelidExtended(stmt->relation,
        AccessShareLock,
        stmt->missing_ok,
        false,
        false,
        false,
        RangeVarCallbackForAlterRelation,
        (void*)stmt);

    if (!OidIsValid(partitionedTableIndexOid)) {
        ereport(NOTICE, (errmsg("index \"%s\" does not exist, skipping", stmt->relation->relname)));

        return InvalidObjectAddress;
    }

    TrForbidAccessRbObject(RelationRelationId, partitionedTableIndexOid, stmt->relation->relname);

    /* get partition index oid */
    partitionIndexOid = PartitionNameGetPartitionOid(partitionedTableIndexOid,
        stmt->subname,
        PART_OBJ_TYPE_INDEX_PARTITION,
        AccessExclusiveLock,
        true,
        false,
        NULL,
        NULL,
        NoLock);
    if (InvalidOid == partitionIndexOid) {
        ereport(
            ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("partition index \"%s\" does not exist", stmt->subname)));
    }

    /*
     * check partition index new name does not exist.
     */
    if (InvalidOid != GetSysCacheOid3(PARTPARTOID,
                          NameGetDatum(stmt->newname),
                          CharGetDatum(PART_OBJ_TYPE_INDEX_PARTITION),
                          ObjectIdGetDatum(partitionedTableIndexOid))) {
        ereport(
            ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("partition index \"%s\" already exists", stmt->newname)));
    }

    /* Do the work */
    renamePartitionInternal(partitionedTableIndexOid, partitionIndexOid, stmt->newname);

    Oid parRelOid = IndexGetRelation(partitionedTableIndexOid, false);
    Relation partRel = RelationIdGetRelation(parRelOid);
    UpdatePgObjectChangecsn(parRelOid, partRel->rd_rel->relkind);
    RelationClose(partRel);
    ObjectAddressSet(address, PartitionRelationId, partitionIndexOid);
    return address;
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		: Change the name of partition object (partition/index) in pg_partition
 * Description	:
 * Notes		:
 */
void renamePartitionInternal(Oid partitionedTableOid, Oid partitionOid, const char* partitionNewName)
{
    Relation partitionRelRelation = NULL;
    HeapTuple partitionRelationTuple = NULL;
    Form_pg_partition relationForm = NULL;

    /* shouldn't happen */
    if (!OidIsValid(partitionedTableOid) || !OidIsValid(partitionOid) || !PointerIsValid(partitionNewName)) {
        ereport(ERROR, (errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("internal error, rename partition failed")));
    }

    /*
     * Find relation's pg_partition tuple.
     */
    partitionRelRelation = relation_open(PartitionRelationId, RowExclusiveLock);
    partitionRelationTuple = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(partitionOid));

    /* shouldn't happen */
    if (!HeapTupleIsValid(partitionRelationTuple)) {
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_OBJECT),
                errmsg("cache lookup failed for partition %u of relation %u", partitionOid, partitionedTableOid)));
    }

    relationForm = (Form_pg_partition)GETSTRUCT(partitionRelationTuple);

    /*
     * Update pg_partition tuple with new relname.	(Scribbling on reltup is OK
     * because it's a copy...)
     */
    (void)namestrcpy(&(relationForm->relname), partitionNewName);
    simple_heap_update(partitionRelRelation, &(partitionRelationTuple->t_self), partitionRelationTuple);

    /*
     * Keep the system catalog indexes current.
     */
    CatalogUpdateIndexes(partitionRelRelation, partitionRelationTuple);

    tableam_tops_free_tuple(partitionRelationTuple);

    relation_close(partitionRelRelation, RowExclusiveLock);
}

/*
 * Disallow ALTER TABLE (and similar commands) when the current backend has
 * any open reference to the target table besides the one just acquired by
 * the calling command; this implies there's an open cursor or active plan.
 * We need this check because our lock doesn't protect us against stomping
 * on our own foot, only other people's feet!
 *
 * For ALTER TABLE, the only case known to cause serious trouble is ALTER
 * COLUMN TYPE, and some changes are obviously pretty benign, so this could
 * possibly be relaxed to only error out for certain types of alterations.
 * But the use-case for allowing any of these things is not obvious, so we
 * won't work hard at it for now.
 *
 * We also reject these commands if there are any pending AFTER trigger events
 * for the rel.  This is certainly necessary for the rewriting variants of
 * ALTER TABLE, because they don't preserve tuple TIDs and so the pending
 * events would try to fetch the wrong tuples.	It might be overly cautious
 * in other cases, but again it seems better to err on the side of paranoia.
 *
 * REINDEX calls this with "rel" referencing the index to be rebuilt; here
 * we are worried about active indexscans on the index.  The trigger-event
 * check can be skipped, since we are doing no damage to the parent table.
 *
 * The statement name (eg, "ALTER TABLE") is passed for use in error messages.
 */
void CheckTableNotInUse(Relation rel, const char* stmt)
{
    int expected_refcnt;

    expected_refcnt = rel->rd_isnailed ? 2 : 1;
    if (rel->rd_refcnt != expected_refcnt)
        ereport(ERROR,
            (errcode(ERRCODE_OBJECT_IN_USE),
                /* translator: first %s is a SQL command, eg ALTER TABLE */
                errmsg("cannot %s \"%s\" because "
                       "it is being used by active queries in this session",
                    stmt,
                    RelationGetRelationName(rel))));

    if (!RelationIsIndex(rel) && AfterTriggerPendingOnRel(RelationGetRelid(rel)))
        ereport(ERROR,
            (errcode(ERRCODE_OBJECT_IN_USE),
                /* translator: first %s is a SQL command, eg ALTER TABLE */
                errmsg("cannot %s \"%s\" because "
                       "it has pending trigger events",
                    stmt,
                    RelationGetRelationName(rel))));
}

void CheckPartitionNotInUse(Partition part, const char* stmt)
{
    const int expected_refcnt = 1;

    if (part->pd_refcnt != expected_refcnt)
        ereport(ERROR,
            (errcode(ERRCODE_OBJECT_IN_USE),
                /* translator: first %s is a SQL command, eg ALTER TABLE */
                errmsg("cannot %s \"%s\" because "
                       "it is being used by active queries in this session",
                    stmt,
                    PartitionGetPartitionName(part))));
}

/*
 * Check whether the type is a table of type, which can not be altered
 */
static void checkTypeIsTableOfOrPackage(AlterTableStmt* stmt)
{
    Oid typeNamespace = InvalidOid;
    Oid typeOid = InvalidOid;
    if (stmt->relation->schemaname != NULL) {
        typeNamespace = get_namespace_oid(stmt->relation->schemaname, false);
    } else {
        ListCell* cell = NULL;
        foreach (cell, u_sess->catalog_cxt.activeSearchPath) {
            typeNamespace = lfirst_oid(cell);
            typeOid = GetSysCacheOid2(TYPENAMENSP, CStringGetDatum(stmt->relation->relname),
                ObjectIdGetDatum(typeNamespace));
            if (OidIsValid(typeOid)) {
#ifndef ENABLE_MULTIPLE_NODES
                /* don't allow to alter package or procedure type */
                if (IsPackageDependType(typeOid, InvalidOid)) {
                    ereport(ERROR,
                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                            errmodule(MOD_PLSQL),
                            errmsg("Not allowed to alter type \"%s\"", stmt->relation->relname),
                            errdetail("\"%s\" is a package or procedure type", stmt->relation->relname),
                            errcause("feature not supported"),
                            erraction("check type name")));
                }
#endif
                HeapTuple typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typeOid));
    
                if (HeapTupleIsValid(typeTup)) {
                    if (((Form_pg_type)GETSTRUCT(typeTup))->typtype == TYPTYPE_TABLEOF) {
                        ereport(ERROR,
                            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                                errmsg("table type does not support alter.")));
                    }
                    ReleaseSysCache(typeTup);
                }
            }
        }
    }
}

/*
 * AlterTableLookupRelation
 *		Look up, and lock, the OID for the relation named by an alter table
 *		statement.
 */
Oid AlterTableLookupRelation(AlterTableStmt* stmt, LOCKMODE lockmode, bool unlock)
{
    // treat unusable index and unusable index partition ops like drop index or dop index partition,
    // though we do not provide interface to drop index partition
    if ((stmt->relkind == OBJECT_INDEX) && (stmt->cmds->length == 1)) {
        AlterTableCmd* index_cmd = (AlterTableCmd*)lfirst(stmt->cmds->head);
        struct DropRelationCallbackState state = {0};
        state.concurrent = false;
        state.heapOid = InvalidOid;
        state.relkind = RELKIND_INDEX;
        
        if (index_cmd->subtype == AT_UnusableIndex) {
            Oid relid = InvalidOid;
            relid = RangeVarGetRelidExtended(stmt->relation,
                lockmode,
                stmt->missing_ok,
                false,
                false,  // not on index partition
                false,  // not support synonym
                RangeVarCallbackForDropRelation,
                (void*)&state);
            if (OidIsValid(state.heapOid) && u_sess->attr.attr_sql.enable_parallel_ddl && unlock) {
                UnlockRelationOid(state.heapOid, state.concurrent ? ShareUpdateExclusiveLock : AccessExclusiveLock);
            }

            return relid;
        } else if (index_cmd->subtype == AT_UnusableIndexPartition) {
            Oid relid = InvalidOid;
            relid = RangeVarGetRelidExtended(stmt->relation,
                lockmode,
                stmt->missing_ok,
                false,
                true,   // on index partition
                false,  // not support synonym
                RangeVarCallbackForDropRelation,
                (void*)&state);
            if (OidIsValid(state.heapOid) && u_sess->attr.attr_sql.enable_parallel_ddl && unlock) {
                UnlockRelationOid(state.heapOid, AccessShareLock);
            }

            return relid;
        }
    }

    if (stmt->relkind == OBJECT_TABLE) {
        ListCell* lcmd = NULL;

        foreach (lcmd, stmt->cmds) {
            AlterTableCmd* cmd = (AlterTableCmd*)lfirst(lcmd);
            if (AT_TruncatePartition == cmd->subtype || AT_TruncateSubPartition == cmd->subtype) {
                return RangeVarGetRelidExtended(stmt->relation,
                    lockmode,
                    stmt->missing_ok,
                    false,
                    true,
                    false,  // not support synonym
                    RangeVarCallbackForAlterRelation,
                    (void*)stmt);
            }
        }
    }

    /* TABLE OF TYPE and package type don't support alter */
    if (stmt->relkind == OBJECT_TYPE) {
        checkTypeIsTableOfOrPackage(stmt);
    }

    // all other ops on index and heap, goes on
    return RangeVarGetRelidExtended(stmt->relation,
        lockmode,
        stmt->missing_ok,
        false,
        false,
        false,  // not support synonym
        RangeVarCallbackForAlterRelation,
        (void*)stmt);
}

/* enum of alter-table-instantly
 * DEFAULT_NULL means no operation to pg_attribute.
 * DEFAULT_NOT_NULL_CONST means updating the attinitdefval of pg_attribute.
 * DEFAULT_OTHER  means rewriting the all tuples.
 */
typedef enum {
    DEFAULT_NULL,           /* attribute that has no default value or has null default value */
    DEFAULT_NOT_NULL_CONST, /* attribute that has const-expression default value */
    DEFAULT_OTHER           /* other value */
} AT_INSTANT_DEFAULT_VALUE;

/*
 * Estimate whether alter-table-instantly is effective.
 * The follow does not adapt to alter-table-instantly:
 * 1. unsupported data type - refer to supportType
 * 2. the default value expression include  volatile function
 * 3. the memory used by default value is more than 128 bytes.
 * 4. the default value is actually null
 */
static AT_INSTANT_DEFAULT_VALUE shouldUpdateAllTuples(
    Expr* defaultExpr, Oid typeOid, int attLen, bool attByVal, bytea** defaultVal, char** initdefval)
{
    bool isNull = false;
    int i;
    bool tooLong = false;
    bytea* result = NULL;
    errno_t rc;

    /* it's difficult to handle all the datatypes, espicially complex datatypes.
     * so we just handle those datatypes which are normal and used most time.
     */
    const Oid supportType[] = {BOOLOID,
        BYTEAOID,
        CHAROID,
        INT8OID,
        INT2OID,
        INT4OID,
        TEXTOID,
        FLOAT4OID,
        FLOAT8OID,
        ABSTIMEOID,
        RELTIMEOID,
        TINTERVALOID,
        BPCHAROID,
        VARCHAROID,
        NUMERICOID,
        DATEOID,
        TIMEOID,
        TIMESTAMPOID,
        TIMESTAMPTZOID,
        INTERVALOID,
        TIMETZOID,
        INT1OID,
        SMALLDATETIMEOID};

    /* During inplace upgrade, we may allow extra column types. */
    const Oid extraSupportTypeInUpgrade[] = {OIDOID, NAMEOID, ACLITEMARRAYOID};

    /* check data type, if the data type is not supported, rewrite table */
    bool support = false;
    for (i = 0; i < int(sizeof(supportType) / sizeof(Oid)); ++i) {
        if (supportType[i] == typeOid) {
            support = true;
            break;
        }
    }

    /* check extra supported type during upgrade if needed */
    if (!support && u_sess->attr.attr_common.IsInplaceUpgrade) {
        for (i = 0; i < int(sizeof(extraSupportTypeInUpgrade) / sizeof(Oid)); ++i) {
            if (extraSupportTypeInUpgrade[i] == typeOid) {
                support = true;
                break;
            }
        }
    }

    if (!support) {
        return DEFAULT_OTHER;
    }

    /* alter-table-instantly does not support volatile default value. */
    if (contain_volatile_functions((Node*)defaultExpr))
        return DEFAULT_OTHER;

    EState* estate = CreateExecutorState();
    ExprState* exprstate = ExecInitExpr(expression_planner(defaultExpr), NULL);
    ExprContext* econtext = GetPerTupleExprContext(estate);

    MemoryContext newcxt = GetPerTupleMemoryContext(estate);
    MemoryContext oldcxt = MemoryContextSwitchTo(newcxt);
    Datum value = ExecEvalExpr(exprstate, econtext, &isNull);
    (void)MemoryContextSwitchTo(oldcxt);

    if (!isNull) {
        if (initdefval) {
            Oid typoutput = 0;
            bool typisvarlena = false;
            getTypeOutputInfo(typeOid, &typoutput, &typisvarlena);
            *initdefval = pstrdup(OidOutputFunctionCall(typoutput, value));
        }

        if (attByVal) {
            result = (bytea*)palloc(attLen + VARHDRSZ);
            SET_VARSIZE(result, attLen + VARHDRSZ);
            store_att_byval(VARDATA(result), value, attLen);
        } else {
            int len = att_addlength_datum(0, attLen, DatumGetPointer(value));
            if (len >= ATT_DEFAULT_LEN) {
                /* if the length of default value > 128, need rewrite table.
                 * this limitation ensure attinitdefval of relcache do not consume
                 * too many memory space.
                 */
                tooLong = true;
            } else {
                result = (bytea*)palloc(len + VARHDRSZ);
                SET_VARSIZE(result, len + VARHDRSZ);
                Assert((char*)result + VARHDRSZ == VARDATA(result));
                rc = memcpy_s((char*)result + VARHDRSZ, len, DatumGetPointer(value), len);
                securec_check(rc, "", "");
            }
        }
    }

    FreeExecutorState(estate);

    if (tooLong) {
        return DEFAULT_OTHER;
    }

    *defaultVal = result;

    return isNull ? DEFAULT_NULL : DEFAULT_NOT_NULL_CONST;
}

/* updateInitDefVal
 *
 * Update the attinitdefval field of pg_attribute for altering table instantly.
 */
static void updateInitDefVal(bytea* value, Relation rel, int16 attNum)
{
    Relation attRelation;
    HeapTuple tup;
    HeapTuple newTuple;
    Datum replVals[Natts_pg_attribute];
    bool replNulls[Natts_pg_attribute];
    bool replRepls[Natts_pg_attribute];
    errno_t rc;

    attRelation = heap_open(AttributeRelationId, RowExclusiveLock);

    tup = SearchSysCache2(ATTNUM, ObjectIdGetDatum(RelationGetRelid(rel)), Int16GetDatum(attNum));
    if (!HeapTupleIsValid(tup)) {
        ereport(ERROR,
            (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                errmsg("cache lookup failed for attribute %d of relation %u", attNum, RelationGetRelid(rel))));
    }

    /* Build new tuple. */
    rc = memset_s(replNulls, sizeof(replNulls), false, sizeof(replNulls));
    securec_check(rc, "\0", "\0");

    rc = memset_s(replRepls, sizeof(replRepls), false, sizeof(replRepls));
    securec_check(rc, "\0", "\0");

    replVals[Anum_pg_attribute_attinitdefval - 1] = PointerGetDatum(value);
    replRepls[Anum_pg_attribute_attinitdefval - 1] = true;
    newTuple = (HeapTuple) tableam_tops_modify_tuple(tup, RelationGetDescr(attRelation), replVals, replNulls, replRepls);
    ReleaseSysCache(tup);

    /* Update system catalog. */
    simple_heap_update(attRelation, &newTuple->t_self, newTuple);
    CatalogUpdateIndexes(attRelation, newTuple);
    tableam_tops_free_tuple(newTuple);

    heap_close(attRelation, RowExclusiveLock);

    /* Make the attribute's catalog entry visible */
    CommandCounterIncrement();
}

/*
 * Clear all attrinitdefvals of relation with relid. It is called when rewrite
 * table is finished and all tuple attrnum are equal to attrnum of tupledesc.
 */
void clearAttrInitDefVal(Oid relid)
{
    Datum replVals[Natts_pg_attribute];
    bool replNulls[Natts_pg_attribute];
    bool replRepls[Natts_pg_attribute];
    errno_t rc;

    Relation rel = heap_open(relid, AccessExclusiveLock);

    if (rel->rd_att->initdefvals != NULL) {
        rc = memset_s(replNulls, sizeof(replNulls), false, sizeof(replNulls));
        securec_check(rc, "\0", "\0");
        rc = memset_s(replRepls, sizeof(replRepls), false, sizeof(replRepls));
        securec_check(rc, "\0", "\0");

        /* set the attribute *attinitdefval* null to clear its value */
        replVals[Anum_pg_attribute_attinitdefval - 1] = (Datum)0;
        replRepls[Anum_pg_attribute_attinitdefval - 1] = true;
        replNulls[Anum_pg_attribute_attinitdefval - 1] = true;

        int natts = rel->rd_att->natts;
        HeapTuple* tuples = (HeapTuple*)palloc0(natts * sizeof(HeapTuple));
        Relation attRelation = heap_open(AttributeRelationId, RowExclusiveLock);

        /* Clear all attrinitdefvals of relation */
        for (int attno = 0; attno < natts; ++attno) {
            if (rel->rd_att->initdefvals[attno].isNull)
                continue;

            /* step 1: Build new tuple. */
            HeapTuple tup = SearchSysCache2(ATTNUM, ObjectIdGetDatum(relid), Int16GetDatum(attno + 1));
            if (!HeapTupleIsValid(tup)) {
                ereport(ERROR,
                    (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                        errmsg("cache lookup failed for attribute %d of relation %u", attno + 1, relid)));
            }

            tuples[attno] = (HeapTuple) tableam_tops_modify_tuple(tup, RelationGetDescr(attRelation), replVals, replNulls, replRepls);
            ReleaseSysCache(tup);

            /* step 2: Update system catalog. */
            simple_heap_update(attRelation, &tuples[attno]->t_self, tuples[attno]);
            CatalogUpdateIndexes(attRelation, tuples[attno]);

            /* free newtuple */
            tableam_tops_free_tuple(tuples[attno]);
            tuples[attno] = NULL;
        }

        pfree_ext(tuples);
        heap_close(attRelation, RowExclusiveLock);
    }

    heap_close(rel, AccessExclusiveLock);

    /* Make the attribute's catalog entry visible */
    CommandCounterIncrement();
}

/*
 * AlterTable
 *		Execute ALTER TABLE, which can be a list of subcommands
 *
 * ALTER TABLE is performed in three phases:
 *		1. Examine subcommands and perform pre-transformation checking.
 *		2. Update system catalogs.
 *		3. Scan table(s) to check new constraints, and optionally recopy
 *		   the data into new table(s).
 * Phase 3 is not performed unless one or more of the subcommands requires
 * it.	The intention of this design is to allow multiple independent
 * updates of the table schema to be performed with only one pass over the
 * data.
 *
 * ATPrepCmd performs phase 1.	A "work queue" entry is created for
 * each table to be affected (there may be multiple affected tables if the
 * commands traverse a table inheritance hierarchy).  Also we do preliminary
 * validation of the subcommands, including parse transformation of those
 * expressions that need to be evaluated with respect to the old table
 * schema.
 *
 * ATRewriteCatalogs performs phase 2 for each affected table.	(Note that
 * phases 2 and 3 normally do no explicit recursion, since phase 1 already
 * did it --- although some subcommands have to recurse in phase 2 instead.)
 * Certain subcommands need to be performed before others to avoid
 * unnecessary conflicts; for example, DROP COLUMN should come before
 * ADD COLUMN.	Therefore phase 1 divides the subcommands into multiple
 * lists, one for each logical "pass" of phase 2.
 *
 * ATRewriteTables performs phase 3 for those tables that need it.
 *
 * Thanks to the magic of MVCC, an error anywhere along the way rolls back
 * the whole operation; we don't have to do anything special to clean up.
 *
 * The caller must lock the relation, with an appropriate lock level
 * for the subcommands requested. Any subcommand that needs to rewrite
 * tuples in the table forces the whole command to be executed with
 * AccessExclusiveLock (actually, that is currently required always, but
 * we hope to relax it at some point).	We pass the lock level down
 * so that we can apply it recursively to inherited tables. Note that the
 * lock level we want as we recurse might well be higher than required for
 * that specific subcommand. So we pass down the overall lock requirement,
 * rather than reassess it at lower levels.
 *
 */
#ifdef PGXC
/*
 * In Postgres-XC, an extension is added to ALTER TABLE for modification
 * of the data distribution. Depending on the old and new distribution type
 * of the relation redistributed, a list of redistribution subcommands is built.
 * Data redistribution cannot be done in parallel of operations that need
 * the table to be rewritten like column addition/deletion.
 */
#endif

void AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt* stmt)
{
    Relation rel;

    /* Caller is required to provide an adequate lock. */
    rel = relation_open(relid, lockmode);

    if ((stmt->relkind == OBJECT_TABLE) && (rel->rd_rel->relkind == RELKIND_MATVIEW)) {
        stmt->relkind = OBJECT_MATVIEW;
    }

    /* We allow to alter global temp table only this session use it */
    CheckGttTableInUse(rel);

    CheckTableNotInUse(rel, "ALTER TABLE");

#ifdef ENABLE_MULTIPLE_NODES
    PreventDDLIfTsdbDisabled(relid);
#endif  /* ENABLE_MULTIPLE_NODES */

    /*
     * cmd list for 'ALTER FOREIGN TABLE ADD NODE'.
     * cmd list for 'ALTER TABLE DFSTbl ADD NODE'.
     */
    List* addNodeCmds = NIL;

    /* Unsupport ALTER statement for Foreign table */
    if (!stmt->fromCreate && (stmt->relkind == OBJECT_FOREIGN_TABLE || stmt->relkind == OBJECT_STREAM)) {
        ListCell* lc = NULL;

        /* @hdfs
         * Check whether support alter foreign table. Many FDW types for foreign tables exists,
         * Each type foreign table has own mchanism, we use function pointor to realize. For example,
         * wheather support alter owner, support alter colomn type, etc.
         */
        FdwRoutine* fdwroutine = GetFdwRoutineByRelId(relid, true);

        if (fdwroutine != NULL && NULL != fdwroutine->ValidateTableDef) {
            fdwroutine->ValidateTableDef((Node*)stmt);
        }

        foreach (lc, stmt->cmds) {
            AlterTableCmd* cmd = (AlterTableCmd*)lfirst(lc);

            if (AT_AddNodeList == cmd->subtype || AT_DeleteNodeList == cmd->subtype || AT_SubCluster == cmd->subtype ||
                AT_UpdateSliceLike == cmd->subtype) {
                /* append 'ALTER FOREIGN TABLE ADD NODE' cmd to ftAddNodeCmds */
                addNodeCmds = lappend(addNodeCmds, cmd);
            }
        }
    }

    if (!stmt->fromCreate && (((uint32)RelationGetInternalMask(rel)) & INTERNAL_MASK_DALTER)) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                (errmsg("Un-support feature"), errdetail("internal relation doesn't allow ALTER"))));
    }

    // Unsupport ALTER statement for column store
    if (!stmt->fromCreate) {
        ListCell* lc = NULL;
        foreach (lc, stmt->cmds) {
            AlterTableCmd* cmd = (AlterTableCmd*)lfirst(lc);
            if (cmd->subtype == AT_SetAutoIncrement) {
                if (rel->rd_rel->relkind != RELKIND_RELATION) {
                    ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                            (errmsg("Un-support feature"),
                                errdetail("ALTER TABLE AUTO_INCREMENT only supports ordinary table."))));
                }
                continue;
            }

            if (AT_AddOids == cmd->subtype) {
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        (errmsg("Un-support feature"),
                            errdetail("ALTER TABLE ... SET WITH OIDS is not yet supported."))));
            }

            if (AT_DropOids == cmd->subtype) {
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        (errmsg("Un-support feature"),
                            errdetail("ALTER TABLE ... SET WITHOUT OIDS is not yet supported."))));
            }

            /* We only check basetable when not in redistribution */
            if (rel->rd_rel->relkind == RELKIND_RELATION && !u_sess->attr.attr_sql.enable_cluster_resize) {
                /* Forbid some ALTER statements for Table that owns Matview */
                if ((InvalidOid != rel->rd_mlogoid) && !BASETABLE_SUPPORT_AT_CMD(cmd->subtype)) {
                    ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                            (errmsg("Un-support feature"),
                                errdetail("table owning matview doesn't support this ALTER yet."))));
                }

                /* Unsupport ALTER statement for mlog/matviewmap table */
                if ((ISMLOG(rel->rd_rel->relname.data) || ISMATMAP(rel->rd_rel->relname.data)) &&
                    !MLOG_MAP_SUPPORT_AT_CMD(cmd->subtype)) {
                    ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        (errmsg("Un-support feature"), errdetail("%s table doesn't support this ALTER yet.",
                        ISMLOG(rel->rd_rel->relname.data) ? "mlog" : "matviewmap"))));
                }
            }

            /* Forbid some ALTER statements for Matview */
            if ((stmt->relkind == OBJECT_MATVIEW) && !MATVIEW_SUPPORT_AT_CMD(cmd->subtype)) {
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("ALTER MATERIALIZED VIEW is not yet supported.")));
            }

            if (RelationIsSubPartitioned(rel) && cmd->subtype == AT_ClusterOn) {
                ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot cluster a subpartition table")));
            }

            if (RelationIsCUFormat(rel) && !CStoreSupportATCmd(cmd->subtype)) {
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("Un-support feature"),
                        errdetail("column-store relation doesn't support this ALTER yet")));
            }

            if (RelationIsTsStore(rel)) {
                at_timeseries_check(rel, cmd);
            }

            // We open up SetRelOptions for HDFS during online expansion so gs_redis could
            // set append_mode=read_only. Also we need to open up in CheckRedistributeOption.
            if (RelationIsPAXFormat(rel) &&
                !(DFS_SUPPORT_AT_CMD(cmd->subtype) ||
                    (u_sess->attr.attr_sql.enable_cluster_resize && AT_SetRelOptions == cmd->subtype))) {
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("Un-support feature"),
                        errdetail("It is not supported to alter a DFS table.")));
            }

            if (AT_SetTableSpace == cmd->subtype) {
                char* tblspcName = cmd->name;
                Oid tblspcOid = get_tablespace_oid(tblspcName, false);
                if (IsSpecifiedTblspc(tblspcOid, FILESYSTEM_HDFS)) {
                    ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                            errmsg("Un-support feature"),
                            errdetail("It is not supported to alter DFS tablespace.")));
                }
            }
        }
    }

    // Next version remove hack patch for 'ALTER FOREIGN TABLE ... ADD NODE'
    if (stmt->cmds != NIL) {
        /* process 'ALTER TABLE' cmd */
        ATController(stmt, rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt), lockmode, stmt->fromReplace);
        if (enable_plpgsql_gsdependency_guc()) {
            (void)gsplsql_build_ref_type_dependency(get_rel_type_id(relid));
        }
    } else {
        /* if do not call ATController, close the relation in here, but keep lock until commit */
        relation_close(rel, NoLock);
    }

    if (addNodeCmds != NIL) {
        /* process 'ALTER FOREIGN TABLE ... ADD NODE' cmd */
        DefElem* def = GetForeignTableOptionByName(relid, optErrorRel);
        if (def != NULL) {
            char* relname = strVal(def->arg);
            Oid errtableid = get_relname_relid(relname, get_rel_namespace(relid));

            /* open error table releation, closed in ATController */
            Relation errtablerel = relation_open(errtableid, lockmode);

            ATController(stmt, errtablerel, addNodeCmds, interpretInhOption(stmt->relation->inhOpt), lockmode, stmt->fromReplace);
        }
        list_free_ext(addNodeCmds);
    }
}

/*
 * AlterTableInternal
 *
 * ALTER TABLE with target specified by OID
 *
 * We do not reject if the relation is already open, because it's quite
 * likely that one or more layers of caller have it open.  That means it
 * is unsafe to use this entry point for alterations that could break
 * existing query plans.  On the assumption it's not used for such, we
 * don't have to reject pending AFTER triggers, either.
 */
void AlterTableInternal(Oid relid, List* cmds, bool recurse)
{
    Relation rel;
    LOCKMODE lockmode = AlterTableGetLockLevel(cmds);

    rel = relation_open(relid, lockmode);

    EventTriggerAlterTableRelid(relid);
    ATController(NULL, rel, cmds, recurse, lockmode, false);
}

static LOCKMODE set_lockmode(LOCKMODE mode, LOCKMODE cmd_mode)
{
    if (cmd_mode > mode) {
        mode = cmd_mode;
    }
    return mode;
}

#ifndef ENABLE_MULTIPLE_NODES
static LOCKMODE GetPartitionLockLevel(AlterTableType subType)
{
    if (PARTITION_DDL_CMD(subType)) {
        return ShareUpdateExclusiveLock;
    } else {
        return AccessExclusiveLock;
    }
}
#endif

/*
 * AlterTableGetLockLevel
 *
 * Sets the overall lock level required for the supplied list of subcommands.
 * Policy for doing this set according to needs of AlterTable(), see
 * comments there for overall explanation.
 *
 * Function is called before and after parsing, so it must give same
 * answer each time it is called. Some subcommands are transformed
 * into other subcommand types, so the transform must never be made to a
 * lower lock level than previously assigned. All transforms are noted below.
 *
 * Since this is called before we lock the table we cannot use table metadata
 * to influence the type of lock we acquire.
 *
 * There should be no lockmodes hardcoded into the subcommand functions. All
 * lockmode decisions for ALTER TABLE are made here only. The one exception is
 * ALTER TABLE RENAME which is treated as a different statement type T_RenameStmt
 * and does not travel through this section of code and cannot be combined with
 * any of the subcommands given here.
 */
LOCKMODE AlterTableGetLockLevel(List* cmds)
{
    /*
     * Late in 9.1 dev cycle a number of issues were uncovered with access to
     * catalog relations, leading to the decision to re-enforce all DDL at
     * AccessExclusiveLock level by default.
     *
     * The issues are that there is a pervasive assumption in the code that
     * the catalogs will not be read unless an AccessExclusiveLock is held. If
     * that rule is relaxed, we must protect against a number of potential
     * effects - infrequent, but proven possible with test cases where
     * multiple DDL operations occur in a stream against frequently accessed
     * tables.
     *
     * 1. Catalog tables are read using SnapshotNow, which has a race bug that
     * allows a scan to return no valid rows even when one is present in the
     * case of a commit of a concurrent update of the catalog table.
     * SnapshotNow also ignores transactions in progress, so takes the latest
     * committed version without waiting for the latest changes.
     *
     * 2. Relcache needs to be internally consistent, so unless we lock the
     * definition during reads we have no way to guarantee that.
     *
     * 3. Catcache access isn't coordinated at all so refreshes can occur at
     * any time.
     */
    ListCell* lcmd = NULL;

    /* default lock mode of DDL is the highest mode 8, even if commands list is empty */
    LOCKMODE lockmode = AccessExclusiveLock;

    if (cmds && list_length(cmds) > 0) {
        /* clear the default lock mode, so it's safe to compare with other lock modes. */
        lockmode = NoLock;
        foreach (lcmd, cmds) {
            AlterTableCmd* cmd = (AlterTableCmd*)lfirst(lcmd);
            LOCKMODE cmd_lockmode = u_sess->attr.attr_sql.enable_cluster_resize && cmd->subtype == AT_SetRelOptions ?
                ExclusiveLock : AccessExclusiveLock;
#ifndef ENABLE_MULTIPLE_NODES
            cmd_lockmode = GetPartitionLockLevel(cmd->subtype);
#endif
            /* if the partitionno is set first time in upgrade mode, we set lockmode to ShareUpdateExclusiveLock */
            if (cmd->subtype == AT_ResetPartitionno) {
                if (list_length(cmds) != 1) {
                    ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                        errmsg("RESET PARTITIONNO cannot be performed during multiple subcommands")));
                }
                if (t_thrd.proc->workingVersionNum >= PARTITION_ENHANCE_VERSION_NUM) {
                    cmd_lockmode = AccessExclusiveLock;
                } else {
                    cmd_lockmode = ShareUpdateExclusiveLock;
                }
            } else {
                switch (cmd->subtype) {
                    /*
                     * These subcommands affect write operations only.
                     */
                    case AT_EnableTrig:
                    case AT_EnableAlwaysTrig:
                    case AT_EnableReplicaTrig:
                    case AT_EnableTrigAll:
                    case AT_EnableTrigUser:
                    case AT_DisableTrig:
                    case AT_DisableTrigAll:
                    case AT_DisableTrigUser:
                        cmd_lockmode = ShareRowExclusiveLock;
                        break;
                    case AT_AddConstraint:
                    case AT_ProcessedConstraint:  /* becomes AT_AddConstraint */
                    case AT_AddConstraintRecurse: /* becomes AT_AddConstraint */
                    case AT_ReAddConstraint:      /* becomes AT_AddConstraint */
                        if (IsA(cmd->def, Constraint)) {
                            Constraint *con = (Constraint *) cmd->def;
                            if (con->contype == CONSTR_FOREIGN) {
                                /*
                                 * We add triggers to both tables when we add a
                                 * Foreign Key, so the lock level must be at least
                                 * as strong as CREATE TRIGGER.
                                 */
                                cmd_lockmode = ShareRowExclusiveLock;
                            }
                        }
                        break;
                    default:
                        break;
                }
            }
            /* update with the higher lock mode */
            lockmode = set_lockmode(lockmode, cmd_lockmode);
        }
    }

    return lockmode;
}

static void ATController(AlterTableStmt *parsetree, Relation rel, List* cmds, bool recurse, LOCKMODE lockmode, bool fromReplace)
{
    List* wqueue = NIL;
    ListCell* lcmd = NULL;
#ifdef PGXC
    RedistribState* redistribState = NULL;
    bool doRedistribute = false;
#endif

    /* Phase 1: preliminary examination of commands, create work queue */
    foreach (lcmd, cmds) {
        AlterTableCmd* cmd = (AlterTableCmd*)lfirst(lcmd);

#ifdef PGXC
        /* Check restrictions of ALTER TABLE in cluster */
        ATCheckCmd(rel, cmd);
#endif
        ATCheckLedgerTableCmd(rel, cmd);
        ATPrepCmd(&wqueue, rel, cmd, recurse, false, lockmode);
    }

#ifdef PGXC
    /* Only check that on local Coordinator */
    if (IS_PGXC_COORDINATOR) {
        ListCell* ltab = NULL;

        /*
         * Redistribution is only applied to the parent table and not subsequent
         * children. It is also not applied in recursion. This needs to be done
         * once all the commands have been treated.
         */
        foreach (ltab, wqueue) {
            AlteredTableInfo* tab = (AlteredTableInfo*)lfirst(ltab);

            if (RelationGetRelid(rel) == tab->relid && list_length(tab->subcmds[AT_PASS_DISTRIB]) > 0) {
                /*
                 * Check if there are any commands incompatible
                 * with redistribution. For the time being no other commands
                 * are authorized.
                 */
                doRedistribute = true;
                if (!IsConnFromCoord()) {
                    if (list_length(tab->subcmds[AT_PASS_ADD_COL]) > 0 || list_length(tab->subcmds[AT_PASS_DROP]) > 0 ||
                        list_length(tab->subcmds[AT_PASS_ALTER_TYPE]) > 0 ||
                        list_length(tab->subcmds[AT_PASS_OLD_CONSTR]) > 0 ||
                        list_length(tab->subcmds[AT_PASS_COL_ATTRS]) > 0 ||
                        list_length(tab->subcmds[AT_PASS_ADD_INDEX]) > 0 ||
                        list_length(tab->subcmds[AT_PASS_ADD_CONSTR]) > 0 ||
                        list_length(tab->subcmds[AT_PASS_MISC]) > 0)
                        ereport(ERROR,
                            (errcode(ERRCODE_STATEMENT_TOO_COMPLEX),
                                errmsg("Incompatible operation with data redistribution")));

                    /* Scan redistribution commands and improve operation */
                    redistribState = BuildRedistribCommands(RelationGetRelid(rel), tab->subcmds[AT_PASS_DISTRIB]);
                }

                break;
            }
        }
    }
#endif

    /* Close the relation, but keep lock until commit */
    relation_close(rel, NoLock);

    /* Phase 2: update system catalogs */
    ATRewriteCatalogs(&wqueue, lockmode, fromReplace);

#ifdef PGXC
    /* Invalidate cache for redistributed relation */
    if (doRedistribute) {
        Relation rel2 = relation_open(RelationGetRelid(rel), NoLock);

        /* Invalidate all entries related to this relation */
        CacheInvalidateRelcache(rel2);

        /* Make sure locator info is rebuilt */
        RelationCacheInvalidateEntry(RelationGetRelid(rel));
        relation_close(rel2, NoLock);
    }

    if (redistribState != NULL)
        FreeRedistribState(redistribState);
#endif

    /* Phase 3: scan/rewrite tables as needed */
    ATRewriteTables(parsetree, &wqueue, lockmode);
}

/*
 * ATPrepCmd
 *
 * Traffic cop for ALTER TABLE Phase 1 operations, including simple
 * recursion and permission checks.
 *
 * Caller must have acquired appropriate lock type on relation already.
 * This lock should be held until commit.
 */
static void ATPrepCmd(List** wqueue, Relation rel, AlterTableCmd* cmd, bool recurse, bool recursing, LOCKMODE lockmode,
        bool isDeltaTable)
{
    AlteredTableInfo* tab = NULL;
    int pass;

    /* Find or create work queue entry for this table */
    tab = ATGetQueueEntry(wqueue, rel, isDeltaTable);

    /*
     * Copy the original subcommand for each table.  This avoids conflicts
     * when different child tables need to make different parse
     * transformations (for example, the same column may have different column
     * numbers in different children).
     */
    cmd = (AlterTableCmd*)copyObject(cmd);

    cmd->recursing = recursing;
    /*
     * Do permissions checking, recursion to child tables if needed, and any
     * additional phase-1 processing needed.
     */
    switch (cmd->subtype) {
        case AT_AddColumn: /* ADD COLUMN */
            ATSimplePermissions(rel, ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE | ATT_SEQUENCE);
            ATPrepAddColumn(wqueue, tab, rel, recurse, recursing, cmd, lockmode);
            /* Recursion occurs during execution phase */
            pass = AT_PASS_ADD_COL;
            break;
        case AT_AddPartition: /* ADD PARTITION */
            ATSimplePermissions(rel, ATT_TABLE);
            ATPrepAddPartition(rel);
            /* Recursion occurs during execution phase */
            pass = AT_PASS_ADD_PARTITION;
            break;
        case AT_AddSubPartition: /* ADD SUBPARTITION */
            ATSimplePermissions(rel, ATT_TABLE);
            ATPrepAddSubPartition(rel);
            /* ADD SUBPARTITION obeys the same recursion order with ADD PARTITION */
            pass = AT_PASS_ADD_PARTITION;
            break;
        case AT_AddColumnToView: /* add column via CREATE OR REPLACE
                                  * VIEW */
            ATSimplePermissions(rel, ATT_VIEW);
            ATPrepAddColumn(wqueue, NULL, rel, recurse, recursing, cmd, lockmode);
            /* Recursion occurs during execution phase */
            pass = AT_PASS_ADD_COL;
            break;
        case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */

            /*
             * We allow defaults on views so that INSERT into a view can have
             * default-ish behavior.  This works because the rewriter
             * substitutes default values into INSERTs before it expands
             * rules.
             */
            ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW);
            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
            ATPrepCheckDefault(cmd->def);
            /* No command-specific prep needed */
            pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP;
            break;
        case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
            ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
            /* No command-specific prep needed */
            pass = AT_PASS_DROP;
            ATCheckNotNullConstr(cmd, tab);
            break;
        case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
            ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
            /* No command-specific prep needed */
            pass = AT_PASS_ADD_CONSTR;
            ATCheckNotNullConstr(cmd, tab);
            break;
        case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */
            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
            /* Performs own permission checks */
            ATPrepSetStatistics(rel);
            pass = AT_PASS_MISC;
            break;
        case AT_AddStatistics:    /* ADD STATISTICS */
        case AT_DeleteStatistics: /* DELETE STATISTICS */
            ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
            /* Performs own permission checks */
            ATPrepSetStatistics(rel);
            es_check_alter_table_statistics(rel, cmd);
            pass = AT_PASS_MISC;
            break;
        case AT_SetOptions:   /* ALTER COLUMN SET ( options ) */
        case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */
            ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_FOREIGN_TABLE);
            /* This command never recurses */
            pass = AT_PASS_MISC;
            break;
        case AT_SetStorage: /* ALTER COLUMN SET STORAGE */
            ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
            /* No command-specific prep needed */
            pass = AT_PASS_MISC;
            break;
        case AT_DropColumn: /* DROP COLUMN */
            ATSimplePermissions(rel,
                ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE |
                    (u_sess->attr.attr_common.IsInplaceUpgrade ? ATT_VIEW : ATT_NULL));
            ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
            /* Recursion occurs during execution phase */
            pass = AT_PASS_DROP;
            break;
        case AT_DropPartition: /* DROP PARTITION */
            ATSimplePermissions(rel, ATT_TABLE);
            ATPrepDropPartition(rel);
            /* Recursion occurs during execution phase */
            pass = AT_PASS_DROP;
            break;
        case AT_DropSubPartition: /* DROP SUBPARTITION */
            ATSimplePermissions(rel, ATT_TABLE);
            ATPrepDropSubPartition(rel);
            /* Recursion occurs during execution phase */
            pass = AT_PASS_DROP;
            break;
        case AT_UnusableIndexPartition: /* UNUSEABLE INDEX PARTITION */
            ATSimplePermissions(rel, ATT_INDEX);
            ATPrepUnusableIndexPartition(rel);
            /* Recursion occurs during execution phase */
            pass = AT_PASS_MISC;
            break;
        case AT_UnusableAllIndexOnPartition: /* UNUSEABLE ALL INDEX ON PARTITION */
            ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX);
            ATPrepUnusableAllIndexOnPartition(rel);
            /* Recursion occurs during execution phase */
            pass = AT_PASS_MISC;
            break;
        case AT_AddIndex: /* ADD INDEX */
            ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE);
            /* This command never recurses */
            /* No command-specific prep needed */
            pass = AT_PASS_ADD_INDEX;
            break;
        case AT_AddConstraint: /* ADD CONSTRAINT */
            ATSimplePermissions(rel, ATT_TABLE);
            /* Recursion occurs during execution phase */
            /* No command-specific prep needed except saving recurse flag */
            if (recurse)
                cmd->subtype = AT_AddConstraintRecurse;
            pass = AT_PASS_ADD_CONSTR;
            break;
        case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */
            ATSimplePermissions(rel, ATT_TABLE);
            /* This command never recurses */
            /* No command-specific prep needed */
            pass = AT_PASS_ADD_CONSTR;
            break;
        case AT_DropConstraint: /* DROP CONSTRAINT */
            /* @hdfs
             * ATSimplePermissions's second parameter is change from ATT_TABLE to
             * ATT_TABLE|ATT_FOREIGN_TABLE to suppert droping HDFS foreign table.
             */
            ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
            /* Recursion occurs during execution phase */
            /* No command-specific prep needed except saving recurse flag */
            if (recurse)
                cmd->subtype = AT_DropConstraintRecurse;
            pass = AT_PASS_DROP;
            break;
        case AT_AlterColumnType: /* ALTER COLUMN TYPE */
            ATSimplePermissions(rel, ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
            EventTriggerAlterTypeStart(cmd, rel);
            /* Performs own recursion */
            ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd, lockmode);
            pass = AT_PASS_ALTER_TYPE;
            ATCheckDuplicateColumn(cmd, tab->subcmds[pass]);
            break;
        case AT_AlterColumnGenericOptions:
            ATSimplePermissions(rel, ATT_FOREIGN_TABLE);
            /* This command never recurses */
            /* No command-specific prep needed */
            pass = AT_PASS_MISC;
            break;
        case AT_ChangeOwner: /* ALTER OWNER */
            /* This command never recurses */
            /* No command-specific prep needed */
            pass = AT_PASS_MISC;
            break;
        case AT_ClusterOn:   /* CLUSTER ON */
        case AT_DropCluster: /* SET WITHOUT CLUSTER */
            ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
            /* These commands never recurse */
            /* No command-specific prep needed */
            pass = AT_PASS_MISC;
            break;
        case AT_AddOids: /* SET WITH OIDS */

            /*
             * partitioned table can not be setted with or without oids
             */
            if (RELATION_IS_PARTITIONED(rel)) {
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot set with oids on partitioned table")));
            }

            /*
             * ustore table can not be setted with or without oids
             */
            if (RelationIsUstoreFormat(rel)) {
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot set with oids on ustore table")));
            }

            ATSimplePermissions(rel, ATT_TABLE);
            if (!rel->rd_rel->relhasoids || recursing)
                ATPrepAddOids(wqueue, rel, recurse, cmd, lockmode);
            /* Recursion occurs during execution phase */
            pass = AT_PASS_ADD_COL;
            break;
        case AT_DropOids: /* SET WITHOUT OIDS */

            /*
             * partitioned table can not be setted with or without oids
             */
            if (RELATION_IS_PARTITIONED(rel)) {
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot set without oids on partitioned table")));
            }

            /*
             * ustore table can not be setted with or without oids
             */
            if (RelationIsUstoreFormat(rel)) {
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot set with oids on ustore table")));
            }

            ATSimplePermissions(rel, ATT_TABLE);
            /* Performs own recursion */
            if (rel->rd_rel->relhasoids) {
                AlterTableCmd* dropCmd = makeNode(AlterTableCmd);

                dropCmd->subtype = AT_DropColumn;
                dropCmd->name = pstrdup("oid");
                dropCmd->behavior = cmd->behavior;
                ATPrepCmd(wqueue, rel, dropCmd, recurse, false, lockmode);
            }
            pass = AT_PASS_DROP;
            break;
        case AT_SetTableSpace: /* SET TABLESPACE */
        case AT_SetPartitionTableSpace:
            ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX | ATT_MATVIEW);
            /* This command never recurses */
            ATPrepSetTableSpace(tab, rel, cmd->name, lockmode);
            pass = AT_PASS_MISC; /* doesn't actually matter */
            break;
        case AT_UnusableIndex:
        case AT_SetRelOptions:     /* SET (...) */
        case AT_ResetRelOptions:   /* RESET (...) */
        case AT_ReplaceRelOptions: /* reset them all, then set just these */
        case AT_InvisibleIndex:
        case AT_VisibleIndex:
        case AT_InvisibleIndexDirect:
        case AT_VisibleIndexDirect:
            ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX | ATT_VIEW);
            /* This command never recurses */
            /* No command-specific prep needed */
            pass = AT_PASS_MISC;
            break;
        case AT_DisableIndex:
        case AT_EnableIndex:
            ATSimplePermissions(rel, ATT_INDEX);
            /* This command never recurses */
            /* No command-specific prep needed */
            pass = AT_PASS_MISC;
            break;
        case AT_AddInherit: /* INHERIT */
            ATSimplePermissions(rel, ATT_TABLE);
            /* This command never recurses */
            ATPrepAddInherit(rel);
            pass = AT_PASS_MISC;
            break;
        case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
            ATSimplePermissions(rel, ATT_TABLE);
            /* Recursion occurs during execution phase */
            /* No command-specific prep needed except saving recurse flag */
            if (recurse)
                cmd->subtype = AT_ValidateConstraintRecurse;
            pass = AT_PASS_MISC;
            break;
        case AT_NOValidateConstraint: /* ENABLE NOVALIDATE CONSTRAINT */
            ATSimplePermissions(rel, ATT_TABLE);
            if (recurse)
                cmd->subtype = AT_NOValidateConstraintRecurse;
            pass = AT_PASS_MISC;
            break;
        case AT_DISABLE_ValidateConstraint: /* DISABLE VALIDATE CONSTRAINT */
            ATSimplePermissions(rel, ATT_TABLE);
            if (recurse)
                cmd->subtype = AT_DISABLE_ValidateConstraintRecurse;
            pass = AT_PASS_MISC;
            break;
        case AT_DISABLE_NOValidateConstraint: /* DISABLE NOVALIDATE CONSTRAINT */
            ATSimplePermissions(rel, ATT_TABLE);
            if (recurse)
                cmd->subtype = AT_DISABLE_NOValidateConstraintRecurse;
            pass = AT_PASS_MISC;
            break;			
        case AT_ReplicaIdentity: /* REPLICA IDENTITY ... */
            ATSimplePermissions(rel, ATT_TABLE);
            pass = AT_PASS_MISC;
            /* This command never recurses */
            /* No command-specific prep needed */
            break;
        case AT_EnableTrig: /* ENABLE TRIGGER variants */
        case AT_EnableAlwaysTrig:
        case AT_EnableReplicaTrig:
        case AT_EnableTrigAll:
        case AT_EnableTrigUser:
        case AT_DisableTrig: /* DISABLE TRIGGER variants */
        case AT_DisableTrigAll:
        case AT_DisableTrigUser:
        case AT_EnableRule: /* ENABLE/DISABLE RULE variants */
        case AT_EnableAlwaysRule:
        case AT_EnableReplicaRule:
        case AT_DisableRule:
        case AT_EnableRls: /* ENABLE/DISABLE ROW LEVEL SECURITY */
        case AT_DisableRls:
        case AT_ForceRls: /* FORCE/NO-FORCE ROW LEVEL SECURITY */
        case AT_NoForceRls:
        case AT_EncryptionKeyRotation:
        case AT_DropInherit: /* NO INHERIT */
        case AT_AddOf:       /* OF */
        case AT_DropOf:      /* NOT OF */
        case AT_SetAutoIncrement:
        case AT_SetCharsetCollate:
            ATSimplePermissions(rel, ATT_TABLE);
            /* These commands never recurse */
            /* No command-specific prep needed */
            pass = AT_PASS_MISC;
            break;
        case AT_ConvertCharset:
            ATSimplePermissions(rel, ATT_TABLE);
            sqlcmd_alter_prep_convert_charset(tab, rel, cmd, lockmode);
            pass = AT_PASS_MISC;
            break;
        case AT_GenericOptions:
            ATSimplePermissions(rel, ATT_FOREIGN_TABLE);
            /* No command-specific prep needed */
            pass = AT_PASS_MISC;
            break;
        case AT_SET_COMPRESS:
            ATSimplePermissions(rel, ATT_TABLE);
            pass = AT_PASS_MISC;
            break;
        case AT_EnableRowMoveMent:
            ATSimplePermissions(rel, ATT_TABLE);
            ATPrepEnableRowMovement(rel);
            pass = AT_PASS_MISC;
            break;
        case AT_DisableRowMoveMent:
            ATSimplePermissions(rel, ATT_TABLE);
            ATPrepDisableRowMovement(rel);
            pass = AT_PASS_MISC;
            break;
        case AT_TruncatePartition:
            ATPrepTruncatePartition(rel);
            pass = AT_PASS_MISC;
            break;
        case AT_TruncateSubPartition:
            ATPrepTruncateSubPartition(rel);
            pass = AT_PASS_MISC;
            break;
        case AT_ExchangePartition:
            ATSimplePermissions(rel, ATT_TABLE);
            ATPrepExchangePartition(rel);
            pass = AT_PASS_MISC;
            break;
        case AT_MergePartition:
            ATSimplePermissions(rel, ATT_TABLE);
            ATPrepMergePartition(rel);
            pass = AT_PASS_MISC;
            break;
        case AT_SplitPartition:
            ATSimplePermissions(rel, ATT_TABLE);
            ATPrepSplitPartition(rel);
            pass = AT_PASS_MISC;
            break;
        case AT_AddIntoCBI:
            ATSimplePermissions(rel, ATT_INDEX);
            pass = AT_PASS_MISC;
            break;
        case AT_SplitSubPartition:
            ATSimplePermissions(rel, ATT_TABLE);
            ATPrepSplitSubPartition(rel);
            pass = AT_PASS_MISC;
            break;
        case AT_ResetPartitionno:
            ATSimplePermissions(rel, ATT_TABLE);
            ATPrepResetPartitionno(rel);
            pass = AT_PASS_MISC;
            break;
        case AT_ModifyColumn:
            ATSimplePermissions(rel, ATT_TABLE);
            ATPrepAlterModifyColumn(wqueue, tab, rel, recurse, recursing, cmd, lockmode);
            pass = AT_PASS_ALTER_TYPE;
            ATAlterCheckModifiyColumnRepeatedly(cmd, tab->subcmds[pass]);
            break;
#ifdef ENABLE_HTAP
        case AT_Imcstored:
            ATSimplePermissions(rel, ATT_TABLE);
            pass = AT_PASS_ADD_CONSTR;
            break;
        case AT_UnImcstored:
            ATSimplePermissions(rel, ATT_TABLE);
            pass = AT_PASS_DROP;
            break;
        case AT_ModifyPartitionImcstored:
            ATSimplePermissions(rel, ATT_TABLE);
            pass = AT_PASS_ADD_CONSTR;
            break;
        case AT_ModifyPartitionUnImcstored:
            ATSimplePermissions(rel, ATT_TABLE);
            pass = AT_PASS_DROP;
            break;
#endif
#ifdef PGXC
        case AT_DistributeBy:
        case AT_SubCluster:
            ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE | ATT_MATVIEW);
            /* No command-specific prep needed */
            pass = AT_PASS_DISTRIB;
            break;

        /* @hdfs
         * The HDFS foreign table support 'ALTER FOREIGN TABLE ADD NODE/DELETE NODE' cmd.
         */
        case AT_AddNodeList:
        case AT_DeleteNodeList:
        case AT_UpdateSliceLike:
            ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
            /* No command-specific prep needed */
            pass = AT_PASS_DISTRIB;
            break;
        case AT_COMMENTS:
            pass = AT_COMMENT;
            break;
#endif
        default: /* oops */
            ereport(ERROR,
                (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                    errmsg("unrecognized alter table type: %d", (int)cmd->subtype)));
            pass = 0; /* keep compiler quiet */
            break;
    }

    /* Add the subcommand to the appropriate list for phase 2 */
    tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd);
}

static bool ATCheckLedgerTableCmd(Relation rel, AlterTableCmd* cmd)
{
    switch (cmd->subtype) {
        case AT_AddColumn: /* ADD COLUMN */
        case AT_DropColumn: /* DROP COLUMN */
        case AT_AlterColumnType: /* ALTER COLUMN TYPE */
        case AT_ModifyColumn: /* MODIFY/CHANGE COLUMN */
        case AT_ExchangePartition: /* EXCHANGE PARTITION */
        case AT_DropPartition: /* DROP PARTITION */
        case AT_DropSubPartition: /* DROP PARTITION */
        case AT_TruncatePartition: /* TRUNCATE PARTITION */
        case AT_TruncateSubPartition: /* TRUNCATE PARTITION */
            /* Blockchain related tables can't ALTER */
            if (rel->rd_isblockchain) {
                ereport(ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                                errmsg("Unsupport to ALTER the structure of blockchain related table [%s].",
                                       RelationGetRelationName(rel))));
            }
            break;
        default: /* other option will pass */
            break;
    }
    return true;
}

/*
 * ATCheckDuplicateColumn: Check AT if exists duplicate column name
 */
static void ATCheckDuplicateColumn(const AlterTableCmd* cmd, const List* tabCmds)
{
    ListCell* tcmd = NULL;
    foreach (tcmd, tabCmds) {
        AlterTableCmd* acmd = (AlterTableCmd*)lfirst(tcmd);
        if (strcmp(acmd->name, cmd->name) == 0) {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("cannot alter type of column \"%s\" twice", cmd->name)));
        }
    }
}

/*
 * ATCheckNotNullConstr: Check AT set/drop not null if exists duplicate column name
 */
static void ATCheckNotNullConstr(const AlterTableCmd* cmd, const AlteredTableInfo* tab)
{
    ATCheckDuplicateColumn(cmd, tab->subcmds[AT_PASS_ADD_CONSTR]);
    ATCheckDuplicateColumn(cmd, tab->subcmds[AT_PASS_DROP]);
}

static Node* GetGeneratedAdbin(Relation rel, AttrNumber myattnum)
{
    Oid atttype = rel->rd_att->attrs[myattnum - 1].atttypid;
    int32 atttypmod = rel->rd_att->attrs[myattnum - 1].atttypmod;
    ScanKeyData key[2];
    HeapTuple def_tuple;
    Relation def_rel;
    SysScanDesc scan;
    Oid exprtype;
    Node *expr = NULL;

    def_rel = heap_open(AttrDefaultRelationId, RowExclusiveLock);
    ScanKeyInit(&key[0], Anum_pg_attrdef_adrelid, BTEqualStrategyNumber, F_OIDEQ,
        ObjectIdGetDatum(RelationGetRelid(rel)));
    ScanKeyInit(&key[1], Anum_pg_attrdef_adnum, BTEqualStrategyNumber, F_INT2EQ, Int16GetDatum(myattnum));

    scan = systable_beginscan(def_rel, AttrDefaultIndexId, true, NULL, 2, key);

    while (HeapTupleIsValid(def_tuple = systable_getnext(scan))) {
        bool is_null = false;
        Datum adbin_datum;
        char *adbin_string = NULL;

        adbin_datum = fastgetattr(def_tuple, Anum_pg_attrdef_adbin, def_rel->rd_att, &is_null);
        AssertEreport(!is_null, MOD_OPT, "");
        adbin_string = TextDatumGetCString(adbin_datum);
        expr = (Node *)stringToNode_skip_extern_fields(adbin_string);

        exprtype = exprType(expr);

        expr = coerce_to_target_type(NULL, /* no UNKNOWN params here */
            expr,
            exprtype,
            atttype,
            atttypmod,
            COERCION_ASSIGNMENT,
            COERCE_IMPLICIT_CAST,
            NULL,
            NULL,
            -1);

        /*
         * If there is nextval FuncExpr, we should lock the quoted sequence to avoid deadlock, this has beed done in
         * transformFuncExpr. See sqlcmd_lock_nextval_on_cn for more details.
         */
        (void)lockNextvalWalker(expr, NULL);

        pfree_ext(adbin_string);
    }
    systable_endscan(scan);
    heap_close(def_rel, RowExclusiveLock);

    return expr;
}

static void UpdateGeneratedExpr(AlteredTableInfo* tab)
{
    ListCell* l = NULL;
    foreach(l, tab->newvals) {
        NewColumnValue* ex = (NewColumnValue*)lfirst(l);
        Relation rel;
        AttrNumber attnum;

        if (!ex->is_generated) {
            continue;
        }

        rel = relation_open(tab->relid, NoLock);

        attnum = get_attnum(RelationGetRelid(rel), ex->col_name);
        if (attnum <= InvalidAttrNumber) { /* shouldn't happen */
            ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN),
                errmsg("column \"%s\" of relation \"%s\" does not exist", ex->col_name, RelationGetRelationName(rel))));
        }

        Expr *defval = (Expr *)GetGeneratedAdbin(rel, attnum);
        ex->expr = expression_planner(defval);
        ex->generate_attnum = attnum;

        relation_close(rel, NoLock);
    }
}

/*
 * ATRewriteCatalogs
 *
 * Traffic cop for ALTER TABLE Phase 2 operations.	Subcommands are
 * dispatched in a "safe" execution order (designed to avoid unnecessary
 * conflicts).
 */
static void ATRewriteCatalogs(List** wqueue, LOCKMODE lockmode, bool fromReplace)
{
    int pass;
    ListCell* ltab = NULL;

    /*
     * We process all the tables "in parallel", one pass at a time.  This is
     * needed because we may have to propagate work from one table to another
     * (specifically, ALTER TYPE on a foreign key's PK has to dispatch the
     * re-adding of the foreign key constraint to the other table).  Work can
     * only be propagated into later passes, however.
     */
    for (pass = 0; pass < AT_NUM_PASSES; pass++) {
        /* Go through each table that needs to be processed */
        foreach (ltab, *wqueue) {
            AlteredTableInfo* tab = (AlteredTableInfo*)lfirst(ltab);
            List* subcmds = tab->subcmds[pass];
            Relation rel;
            ListCell* lcmd = NULL;

            if (subcmds == NIL)
                continue;

            /*
             * Appropriate lock was obtained by phase 1, needn't get it again
             */
            rel = relation_open(tab->relid, NoLock);

            foreach (lcmd, subcmds)
                ATExecCmd(wqueue, tab, rel, (AlterTableCmd*)lfirst(lcmd), lockmode, fromReplace);

            /*
             * After the ALTER TYPE pass, do cleanup work (this is not done in
             * ATExecAlterColumnType since it should be done only once if
             * multiple columns of a table are altered).
             */
            if (pass == AT_PASS_ALTER_TYPE && !tab->isDeltaTable)
                ATPostAlterTypeCleanup(wqueue, tab, lockmode);

            relation_close(rel, NoLock);
        }
    }

    /* Check to see if a toast table must be added. */
    foreach (ltab, *wqueue) {
        AlteredTableInfo* tab = (AlteredTableInfo*)lfirst(ltab);

        if (tab->is_first_after) {
            UpdateGeneratedExpr(tab);
        }

        if (get_rel_persistence(tab->relid) == RELPERSISTENCE_GLOBAL_TEMP) {
            gtt_create_storage_files(tab->relid);
        }
        /* u_sess->attr.attr_sql.enable_cluster_resize = true, alter operation don't handle toast */
        if ((tab->relkind == RELKIND_RELATION || tab->relkind == RELKIND_MATVIEW) &&
            !u_sess->attr.attr_sql.enable_cluster_resize) {
            Relation rel = relation_open(tab->relid, NoLock);
            Datum toast_reloptions = (Datum)0;

            if (rel->rd_options != NULL && RelationIsTableAccessMethodUStoreType(rel->rd_options)) {
                List* optsList = NIL;
                DefElem* def = makeDefElem(pstrdup("storage_type"),
                    (Node*)makeString((char*)(TABLE_ACCESS_METHOD_USTORE)));
                optsList = lappend(optsList, def);
                toast_reloptions = transformRelOptions((Datum)0, optsList, NULL, NULL, false, false);
            }
            AlterTableCreateToastTable(tab->relid, toast_reloptions);
            relation_close(rel, NoLock);
        }
        /* check auto_increment indexes after rewrite catalogs */
        if (tab->relkind == RELKIND_RELATION) {
            CheckRelAutoIncrementIndex(tab->relid, NoLock);
        }
        /* recreate every table triggers */
        foreach_cell(def_item, tab->changedTriggerDefs) {
            char* cmd_str = (char*)lfirst(def_item);
            List* raw_parsetree_list = raw_parser(cmd_str);
            Node* stmt = (Node*)linitial(raw_parsetree_list);
            Assert(IsA(stmt, CreateTrigStmt));
            (void)CreateTrigger(
                (CreateTrigStmt*)stmt, cmd_str, InvalidOid, InvalidOid, InvalidOid, InvalidOid, false);
        }
    }
}

static void ATExecSetAutoIncrement(Relation rel, Value* value)
{
    int128 autoinc;
    if (!RelHasAutoInc(rel)) {
        return;
    }
    if (IsA(value, Integer)) {
        autoinc = (int128)intVal(value);
    } else { /* T_Float */
        autoinc = DatumGetInt128(DirectFunctionCall1(int16in, CStringGetDatum(strVal(value))));
    }

    if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) {
        tmptable_autoinc_setval(rel->rd_rel->relfilenode, TempRelAutoInc(rel), autoinc, false);
    } else {
        autoinc_setval(RelAutoIncSeqOid(rel), autoinc, false);
    }
}

static void sqlcmd_alter_exec_set_charsetcollate(Relation rel, CharsetCollateOptions* cc, LOCKMODE lockmode)
{
    List* new_reloption = NULL;

    (void)fill_relation_collation(cc->collate, cc->charset, &new_reloption);
    ATExecSetRelOptions(rel, new_reloption, AT_SetRelOptions, lockmode);
}

static void sqlcmd_alter_prep_convert_charset(AlteredTableInfo* tab, Relation rel, AlterTableCmd* cmd,
                                              LOCKMODE lockmode)
{
    CharsetCollateOptions* cc = (CharsetCollateOptions*)cmd->def;
    Node* transform;
    Oid targetcollid = InvalidOid;
    Oid targettypid = InvalidOid;
    ParseState* pstate = make_parsestate(NULL);
    CatCList *catlist = NULL;

    if (tab->relkind != RELKIND_RELATION)
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("\"%s\" should be a normal table", RelationGetRelationName(rel))));

    if (cc->charset == PG_INVALID_ENCODING) {
        cc->charset = get_charset_by_collation(get_nsp_default_collation(RelationGetNamespace(rel)));
        if (cc->charset == PG_INVALID_ENCODING) {
            cc->charset = GetDatabaseEncoding();
        }
    }
    targetcollid = transform_default_collation(cc->collate, cc->charset);

    catlist = SearchSysCacheList1(ATTNUM, ObjectIdGetDatum(rel->rd_id));
    for (int i = 0; i < catlist->n_members; i++) {
        HeapTuple tuple = t_thrd.lsc_cxt.FetchTupleFromCatCList(catlist, i);
        Form_pg_attribute attTup = (Form_pg_attribute)GETSTRUCT(tuple);
        int attnum = attTup->attnum;
        if (attnum <= 0 || attTup->attisdropped || !type_is_collatable(attTup->atttypid) ||
            attTup->attcollation == targetcollid)
            continue;

        transform = (Node*)makeVar(1, attnum, attTup->atttypid, attTup->atttypmod, attTup->attcollation, 0);
        /* When the charset is converted to the binary, the string type is converted to the corresponding binary type */
        targettypid = binary_need_transform_typeid(attTup->atttypid, &targetcollid);
        if (targettypid != attTup->atttypid) {
            transform = coerce_to_target_type(pstate,
                transform,
                exprType(transform),
                targettypid,
                attTup->atttypmod,
                COERCION_ASSIGNMENT,
                COERCE_IMPLICIT_CAST,
                NULL,
                NULL,
                -1);
            if (transform == NULL)
                ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH),
                        errmsg("column \"%s\" cannot be cast automatically to type %s", NameStr(attTup->attname),
                               format_type_be(targettypid))));
        }

        transform = coerce_to_target_charset(transform, cc->charset, targettypid, attTup->atttypmod, targetcollid);

        exprSetCollation(transform, targetcollid);

        /* Fix collations after all else */
        assign_expr_collations(pstate, transform);

        /* Plan the expr now so we can accurately assess the need to rewrite. */
        transform = (Node*)expression_planner((Expr*)transform);

        /*
         * Add a work queue item to make ATRewriteTable update the column
         * contents.
         */
        NewColumnValue* newval = (NewColumnValue*)palloc0(sizeof(NewColumnValue));
        newval->attnum = attnum;
        newval->expr = (Expr*)transform;
        newval->is_generated = false;
        newval->is_autoinc = false;

        tab->newvals = lappend(tab->newvals, newval);
        tab->rewrite = true;
    }

    ReleaseSysCacheList(catlist);
}

static void sqlcmd_alter_exec_convert_charset(AlteredTableInfo* tab, Relation rel, CharsetCollateOptions* cc,
                                              LOCKMODE lockmode)
{
    List* new_reloption = NULL;
    ListCell* lc = NULL;
    HeapTuple heapTup;
    Form_pg_attribute attTup;
    Relation attrelation;

    int target_charset = cc->charset;
    Oid target_coll_oid = fill_relation_collation(cc->collate, target_charset, &new_reloption);

    ATExecSetRelOptions(rel, new_reloption, AT_SetRelOptions, lockmode);

    attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
    foreach(lc, tab->newvals) {
        NewColumnValue* newval = (NewColumnValue*)lfirst(lc);
        heapTup = SearchSysCacheCopy2(ATTNUM, RelationGetRelid(rel), newval->attnum);
        attTup = (Form_pg_attribute)GETSTRUCT(heapTup);
        attTup->attcollation = target_coll_oid;
        attTup->atttypid = binary_need_transform_typeid(attTup->atttypid, &target_coll_oid);

        simple_heap_update(attrelation, &heapTup->t_self, heapTup);
        CatalogUpdateIndexes(attrelation, heapTup);
        add_column_collation_dependency(RelationGetRelid(rel), newval->attnum, target_coll_oid);

        tableam_tops_free_tuple(heapTup);
    }

    heap_close(attrelation, RowExclusiveLock);
}

static bool sqlcmd_partition_index_ddl_cmd(AlterTableType cmd)
{
    /* AT_UnusableAllIndexOnSubPartition is not supported */
    return ((cmd) == AT_UnusableIndexPartition || (cmd) == AT_UnusableAllIndexOnPartition ||
            (cmd) == AT_UnusableIndex || (cmd) == AT_AddIndex || (cmd) == AT_ReAddIndex ||
            (cmd) == AT_AddIndexConstraint);
}

static void ATCreateColumComments(Oid relOid, ColumnDef* columnDef)
{
    List *columnOptions = columnDef->columnOptions;
    ListCell *ColumnOption = NULL;
    foreach (ColumnOption, columnOptions) {
        void *pointer = lfirst(ColumnOption);
        if (IsA(pointer, CommentStmt)) {
            CommentStmt *commentStmt = (CommentStmt *)pointer;
            CreateComments(relOid, RelationRelationId, get_attnum(relOid, columnDef->colname),
                           commentStmt->comment);
            break;
        }
    }
}

/*
 * ATExecCmd: dispatch a subcommand to appropriate execution routine
 */
static void ATExecCmd(List** wqueue, AlteredTableInfo* tab, Relation rel, AlterTableCmd* cmd, LOCKMODE lockmode, bool fromReplace)
{
    ObjectAddress address = InvalidObjectAddress;
    bool commandCollected = false;
    elog(ES_LOGLEVEL, "[ATExecCmd] cmd subtype: %d", cmd->subtype);

    if (PARTITION_DDL_CMD(cmd->subtype) && RELATION_IS_PARTITIONED(rel)) {
        /* Register invalidation of the relation's relcache entry. */
        CacheInvalidateRelcache(rel);
        int partitionno = -GetCurrentPartitionNo(RelOidGetPartitionTupleid(rel->rd_id));
        if (!PARTITIONNO_IS_VALID(partitionno)) {
            RelationResetPartitionno(rel->rd_id, ShareUpdateExclusiveLock);
        }

        // add INTERVAL_PARTITION_LOCK_SDEQUENCE here to avoid ADD INTERVAL PARTITION
        if (RELATION_IS_INTERVAL_PARTITIONED(rel) && !u_sess->is_partition_autonomous_session) {
            LockPartitionObject(rel->rd_id, INTERVAL_PARTITION_LOCK_SDEQUENCE, PARTITION_EXCLUSIVE_LOCK);
        }
    }
    
    if (sqlcmd_partition_index_ddl_cmd(cmd->subtype) && RelationIsIndex(rel)) {
        Oid rel_id = IndexGetRelation(rel->rd_id, false);
        CacheInvalidateRelcacheByRelid(rel_id);
    }

    switch (cmd->subtype) {
        case AT_AddColumn:       /* ADD COLUMN */
        case AT_AddColumnToView: /* add column via CREATE OR REPLACE
                                  * VIEW */
            address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef*)cmd->def, false, false, false,
                cmd->is_first, cmd->after_name, lockmode);
            break;
        case AT_AddColumnRecurse:
            address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef*)cmd->def, false, true, false,
            cmd->is_first, cmd->after_name, lockmode);
            break;
        case AT_AddPartition: /* add partition */
            ATExecAddPartition(rel, (AddPartitionState*)cmd->def);
            break;
        case AT_AddSubPartition: /* add subpartition */
            ATExecAddSubPartition(rel, (AddSubPartitionState*)cmd->def);
            break;
        case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */
            address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
            break;
        case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
            address = ATExecDropNotNull(rel, cmd->name, lockmode);
            break;
        case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
            address = ATExecSetNotNull(tab, rel, cmd->name, lockmode);
            break;
        case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */
            address = ATExecSetStatistics(rel, cmd->name, cmd->def, cmd->additional_property, lockmode);
            break;
        case AT_AddStatistics: /* ADD STATISTICS */
            ATExecAddStatistics(rel, cmd->def, lockmode);
            /* prepare work es_check_alter_table_statistics broken the cmd->def, don't pass to event trigger */
            commandCollected = true;
            break;
        case AT_DeleteStatistics: /* DELETE STATISTICS */
            ATExecDeleteStatistics(rel, cmd->def, lockmode);
            commandCollected = true;
            break;
        case AT_SetOptions: /* ALTER COLUMN SET ( options ) */
            address = ATExecSetOptions(rel, cmd->name, cmd->def, false, lockmode);
            break;
        case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */
            address = ATExecSetOptions(rel, cmd->name, cmd->def, true, lockmode);
            break;
        case AT_SetStorage: /* ALTER COLUMN SET STORAGE */
            address = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
            break;
        case AT_DropColumn: /* DROP COLUMN */
            address = ATExecDropColumn(wqueue, rel, cmd->name, cmd->behavior, false, false, cmd->missing_ok, lockmode, fromReplace);
            break;
        case AT_DropColumnRecurse: /* DROP COLUMN with recursion */
            address = ATExecDropColumn(wqueue, rel, cmd->name, cmd->behavior, true, false, cmd->missing_ok, lockmode, fromReplace);
            break;
        case AT_DropPartition: /* drop partition */
            ATExecDropPartition(rel, cmd);
            break;
        case AT_DropSubPartition: /* drop subpartition */
            ATExecDropSubPartition(rel, cmd);
            break;
        case AT_UnusableIndexPartition: /* unusable index partition */
            ATExecUnusableIndexPartition(rel, cmd->name);
            break;
        case AT_UnusableAllIndexOnPartition: /* unusable all index on partition */
            ATExecUnusableAllIndexOnPartition(rel, cmd->name);
            break;
        case AT_UnusableIndex:
            ATExecUnusableIndex(rel, true);
            break;
        case AT_InvisibleIndex:
            ATExecVisibleIndex(rel, cmd->name, false);
            break;
        case AT_VisibleIndex:
            ATExecVisibleIndex(rel, cmd->name, true);
            break;
        case AT_InvisibleIndexDirect:
            ATExecVisibleIndexDirect(rel, false);
            break;
        case AT_VisibleIndexDirect:
            ATExecVisibleIndexDirect(rel, true);
            break;
        case AT_DisableIndex:
            ATExecEnableIndex(rel, false);
            break;
        case AT_EnableIndex:
            ATExecEnableIndex(rel, true);
            break;  
        case AT_AddIndex: /* ADD INDEX */
            address = ATExecAddIndex(tab, rel, (IndexStmt*)cmd->def, false, lockmode);
            break;
        case AT_ReAddIndex: /* ADD INDEX */
            address = ATExecAddIndex(tab, rel, (IndexStmt*)cmd->def, true, lockmode);
            break;
        case AT_AddConstraint: /* ADD CONSTRAINT */
            address = ATExecAddConstraint(wqueue, tab, rel, (Constraint*)cmd->def, false, false, lockmode);
            break;
        case AT_AddConstraintRecurse: /* ADD CONSTRAINT with recursion */
            address = ATExecAddConstraint(wqueue, tab, rel, (Constraint*)cmd->def, true, false, lockmode);
            break;
        case AT_ReAddConstraint: /* Re-add pre-existing check constraint */
            address = ATExecAddConstraint(wqueue, tab, rel, (Constraint*)cmd->def, false, true, lockmode);
            break;
        case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */
            address = ATExecAddIndexConstraint(tab, rel, (IndexStmt*)cmd->def, lockmode);
            break;
        case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
            address = ATExecValidateConstraint(rel, cmd->name, false, false, lockmode);
            break;
        case AT_ValidateConstraintRecurse: /* VALIDATE CONSTRAINT with
                                            * recursion */
            address = ATExecValidateConstraint(rel, cmd->name, true, false, lockmode);
            break;
        case AT_NOValidateConstraint: /* ENABLE NOVALIDATE CONSTRAINT */
            ATExecValidateAbilityConstraint(rel, cmd->name, false, false, true, false, lockmode);
            break;
        case AT_NOValidateConstraintRecurse: /* ENABLE NOVALIDATE CONSTRAINT with recursion */
            ATExecValidateAbilityConstraint(rel, cmd->name, true, false,  true, false, lockmode);
            break;
        case AT_DISABLE_ValidateConstraint: /* DISABLE VALIDATE CONSTRAINT */
            ATExecValidateAbilityConstraint(rel, cmd->name, false, false,  false, true, lockmode);
            break;
        case AT_DISABLE_ValidateConstraintRecurse: /* DISABLE VALIDATE CONSTRAINT with recursion */
            ATExecValidateAbilityConstraint(rel, cmd->name, true, false,  false, true, lockmode);
            break;
        case AT_DISABLE_NOValidateConstraint: /* DISABLE NOVALIDATE CONSTRAINT */
            ATExecValidateAbilityConstraint(rel, cmd->name, false, false, false, false, lockmode);
            break;
        case AT_DISABLE_NOValidateConstraintRecurse: /* DISABLE NOVALIDATE CONSTRAINT with recursion */
            ATExecValidateAbilityConstraint(rel, cmd->name, true, false, false, false, lockmode);
            break;
        case AT_DropConstraint: /* DROP CONSTRAINT */
            ATExecDropConstraint(rel, cmd->name, cmd->behavior, false, false, cmd->missing_ok, lockmode);
            break;
        case AT_DropConstraintRecurse: /* DROP CONSTRAINT with recursion */
            ATExecDropConstraint(rel, cmd->name, cmd->behavior, true, false, cmd->missing_ok, lockmode);
            break;
        case AT_AlterColumnType: /* ALTER COLUMN TYPE */
            address = ATExecAlterColumnType(tab, rel, cmd, lockmode);
            EventTriggerAlterTypeEnd((Node *) cmd, address, tab->rewrite);
            commandCollected = true;
            break;
        case AT_AlterColumnGenericOptions: /* ALTER COLUMN OPTIONS */
            address = ATExecAlterColumnGenericOptions(rel, cmd->name, (List*)cmd->def, lockmode);
            break;
        case AT_ChangeOwner: /* ALTER OWNER */
            ATExecChangeOwner(RelationGetRelid(rel), get_role_oid(cmd->name, false), false, lockmode);
            break;
        case AT_ClusterOn: /* CLUSTER ON */
            address = ATExecClusterOn(rel, cmd->name, lockmode);
            break;
        case AT_DropCluster: /* SET WITHOUT CLUSTER */
            ATExecDropCluster(rel, lockmode);
            break;
        case AT_AddOids: /* SET WITH OIDS */
            /* Use the ADD COLUMN code, unless prep decided to do nothing */
            if (cmd->def != NULL)
                address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef*)cmd->def, true, false, false, false, NULL, lockmode);
            break;
        case AT_AddOidsRecurse: /* SET WITH OIDS */
            /* Use the ADD COLUMN code, unless prep decided to do nothing */
            if (cmd->def != NULL)
                address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef*)cmd->def, true, true, false, false, NULL, lockmode);
            break;
        case AT_DropOids: /* SET WITHOUT OIDS */

            /*
             * Nothing to do here; we'll have generated a DropColumn
             * subcommand to do the real work
             */
            break;
        case AT_SetTableSpace: /* SET TABLESPACE */

            /*
             * Nothing to do here; Phase 3 does the work
             */
            break;
        case AT_SetPartitionTableSpace:
            ATExecSetTableSpaceForPartitionP2(tab, rel, cmd->def);
            break;
        case AT_SetRelOptions:     /* SET (...) */
        case AT_ResetRelOptions:   /* RESET (...) */
        case AT_ReplaceRelOptions: /* replace entire option list */
            ATExecSetRelOptions(rel, (List*)cmd->def, cmd->subtype, lockmode, false, tab);
            break;
        case AT_EnableTrig: /* ENABLE TRIGGER name */
            ATExecEnableDisableTrigger(rel, cmd->name, TRIGGER_FIRES_ON_ORIGIN, false, lockmode);
            break;
        case AT_EnableAlwaysTrig: /* ENABLE ALWAYS TRIGGER name */
            ATExecEnableDisableTrigger(rel, cmd->name, TRIGGER_FIRES_ALWAYS, false, lockmode);
            break;
        case AT_EnableReplicaTrig: /* ENABLE REPLICA TRIGGER name */
            ATExecEnableDisableTrigger(rel, cmd->name, TRIGGER_FIRES_ON_REPLICA, false, lockmode);
            break;
        case AT_DisableTrig: /* DISABLE TRIGGER name */
            ATExecEnableDisableTrigger(rel, cmd->name, TRIGGER_DISABLED, false, lockmode);
            break;
        case AT_EnableTrigAll: /* ENABLE TRIGGER ALL */
            ATExecEnableDisableTrigger(rel, NULL, TRIGGER_FIRES_ON_ORIGIN, false, lockmode);
            break;
        case AT_DisableTrigAll: /* DISABLE TRIGGER ALL */
            ATExecEnableDisableTrigger(rel, NULL, TRIGGER_DISABLED, false, lockmode);
            break;
        case AT_EnableTrigUser: /* ENABLE TRIGGER USER */
            ATExecEnableDisableTrigger(rel, NULL, TRIGGER_FIRES_ON_ORIGIN, true, lockmode);
            break;
        case AT_DisableTrigUser: /* DISABLE TRIGGER USER */
            ATExecEnableDisableTrigger(rel, NULL, TRIGGER_DISABLED, true, lockmode);
            break;

        case AT_EnableRule: /* ENABLE RULE name */
            ATExecEnableDisableRule(rel, cmd->name, RULE_FIRES_ON_ORIGIN, lockmode);
            break;
        case AT_EnableAlwaysRule: /* ENABLE ALWAYS RULE name */
            ATExecEnableDisableRule(rel, cmd->name, RULE_FIRES_ALWAYS, lockmode);
            break;
        case AT_EnableReplicaRule: /* ENABLE REPLICA RULE name */
            ATExecEnableDisableRule(rel, cmd->name, RULE_FIRES_ON_REPLICA, lockmode);
            break;
        case AT_DisableRule: /* DISABLE RULE name */
            ATExecEnableDisableRule(rel, cmd->name, RULE_DISABLED, lockmode);
            break;
        case AT_EnableRls: /* ENABLE ROW LEVEL SECURITY */
            ATExecEnableDisableRls(rel, RELATION_RLS_ENABLE, lockmode);
            break;
        case AT_DisableRls: /* DISABLE ROW LEVEL SECURITY */
            ATExecEnableDisableRls(rel, RELATION_RLS_DISABLE, lockmode);
            break;
        case AT_ForceRls: /* FORCE ROW LEVEL SECURITY */
            ATExecEnableDisableRls(rel, RELATION_RLS_FORCE_ENABLE, lockmode);
            break;
        case AT_NoForceRls: /* NO FORCE ROW LEVEL SECURITY */
            ATExecEnableDisableRls(rel, RELATION_RLS_FORCE_DISABLE, lockmode);
            break;
        case AT_EncryptionKeyRotation:
            ATExecEncryptionKeyRotation(rel, lockmode);
            break;
        case AT_AddInherit:
            address = ATExecAddInherit(rel, (RangeVar*)cmd->def, lockmode);
            break;
        case AT_DropInherit:
            address = ATExecDropInherit(rel, (RangeVar*)cmd->def, lockmode);
            break;
        case AT_AddOf:
            address = ATExecAddOf(rel, (TypeName*)cmd->def, lockmode);
            break;
        case AT_DropOf:
            ATExecDropOf(rel, lockmode);
            break;
        case AT_ReplicaIdentity:
            ATExecReplicaIdentity(rel, (ReplicaIdentityStmt*)cmd->def, lockmode);
            break;
        case AT_GenericOptions:
            ATExecGenericOptions(rel, (List*)cmd->def);
            break;
        case AT_SET_COMPRESS:
            ATExecSetCompress(rel, cmd->name);
            break;
        case AT_EnableRowMoveMent:
            ATExecModifyRowMovement(rel, true);
            break;
        case AT_DisableRowMoveMent:
            ATExecModifyRowMovement(rel, false);
            break;
        case AT_TruncatePartition:
            ATExecTruncatePartition(rel, cmd);
            break;
        case AT_TruncateSubPartition:
            ATExecTruncateSubPartition(rel, cmd);
            break;
        case AT_ExchangePartition:
            ATExecExchangePartition(rel, cmd);
            break;
        case AT_MergePartition:
            ATExecMergePartition(rel, cmd);
            break;
        case AT_SplitPartition:
            ATExecSplitPartition(rel, cmd);
            break;
        case AT_AddIntoCBI:
            ATExecAddTblIntoCBI(rel, (AddTableIntoCBIState *)cmd->def);
            break;
        case AT_SplitSubPartition:
            ATExecSplitSubPartition(rel, cmd);
            break;
        case AT_SetAutoIncrement:
            ATExecSetAutoIncrement(rel, (Value*)cmd->def);
            break;
        case AT_ResetPartitionno:
            ATExecResetPartitionno(rel);
            break;
        case AT_ModifyColumn:
            ATExecAlterModifyColumn(tab, rel, cmd);
            break;
        case AT_SetCharsetCollate:
            sqlcmd_alter_exec_set_charsetcollate(rel, (CharsetCollateOptions*)cmd->def, lockmode);
            break;
        case AT_ConvertCharset: /* CONVERT TO CHARACTER SET */
            sqlcmd_alter_exec_convert_charset(tab, rel, (CharsetCollateOptions*)cmd->def, lockmode);
            break;
#ifdef ENABLE_HTAP
        case AT_Imcstored:
            ATExecIMCSTORED(rel, (List*)cmd->def);
            break;
        case AT_UnImcstored:
            ATExecUNIMCSTORED(rel);
            break;
        case AT_ModifyPartitionImcstored:
            ATExecModifyPartitionIMCSTORED(rel, cmd->name, (List*)cmd->def);
            break;
        case AT_ModifyPartitionUnImcstored:
            ATExecModifyPartitionUNIMCSTORED(rel, cmd->name);
            break;
#endif
#ifdef PGXC
        case AT_DistributeBy:
            AtExecDistributeBy(rel, (DistributeBy*)cmd->def);
            break;
        case AT_SubCluster:
            AtExecSubCluster(rel, (PGXCSubCluster*)cmd->def);
            break;
        case AT_AddNodeList:
            AtExecAddNode(rel, (List*)cmd->def);
            break;
        case AT_DeleteNodeList:
            AtExecDeleteNode(rel, (List*)cmd->def);
            break;
        case AT_UpdateSliceLike:
            AtExecUpdateSliceLike(rel, cmd->exchange_with_rel);
            break;
#endif
        case AT_COMMENTS:
            /* alter table comment */
            CreateComments(rel->rd_id, RelationRelationId, 0, cmd->name);
            break;
        default: /* oops */
            ereport(ERROR,
                (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                    errmsg("unrecognized alter table type: %d", (int)cmd->subtype)));
            break;
    }

    /* Recode time of alter relation. */
    PgObjectType objectType = GetPgObjectTypePgClass(tab->relkind);
    if (objectType != OBJECT_TYPE_INVALID && !u_sess->is_partition_autonomous_session) {
        UpdatePgObjectMtime(tab->relid, objectType);
    }

    if (u_sess->is_partition_autonomous_session) {
        UpdatePgObjectChangecsn(tab->relid, objectType);
    }

    /* take ExclusiveLock to avoid PARTITION DDL COMMIT until we finish the InitPlan. Oid info will be masked here, and
     * be locked in CommitTransaction. Distribute mode doesn't support partition DDL/DML parallel work, no need this
     * action */
#ifndef ENABLE_MULTIPLE_NODES
    if (PARTITION_DDL_CMD(cmd->subtype)) {
        AddPartitionDDLInfo(RelationGetRelid(rel));
    }
#endif

    /*
     * Report the subcommand to interested event triggers.
     */
    if (cmd && !commandCollected)
        EventTriggerCollectAlterTableSubcmd((Node *) cmd, address, tab->rewrite);

    /*
     * Bump the command counter to ensure the next subcommand in the sequence
     * can see the changes so far
     */
    CommandCounterIncrement();
}

typedef void (*ExecRewriteFuncPtr)(AlteredTableInfo*, Oid, LOCKMODE);
typedef void (*ExecOnlyTestFuncPtr)(AlteredTableInfo*);
typedef void (*ExecChangeTabspcFuncPtr)(AlteredTableInfo*, LOCKMODE);

#define IDX_ROW_TBL 0
#define IDX_COL_TBL 1
#define IDX_ORDINARY_TBL 0
#define IDX_PARTITIONED_TBL 1

ExecRewriteFuncPtr ExecRewriteFuncPtrArray[2][2] = {
    {ExecRewriteRowTable, ExecRewriteRowPartitionedTable}, {ExecRewriteCStoreTable, ExecRewriteCStorePartitionedTable}};
ExecOnlyTestFuncPtr ExecOnlyTestFuncPtrArray[2][2] = {{ExecOnlyTestRowTable, ExecOnlyTestRowPartitionedTable},
    {ExecOnlyTestCStoreTable, ExecOnlyTestCStorePartitionedTable}};
ExecChangeTabspcFuncPtr ExecChangeTabspcFuncPtrArray[2][2] = {
    {ExecChangeTableSpaceForRowTable, ExecChangeTableSpaceForRowPartition},
    {ExecChangeTableSpaceForCStoreTable, ExecChangeTableSpaceForCStorePartition}};

/*
 * @Description: check this relation whether it's a temp table in current session
 * @Param[IN] topRelId: top relation OID
 * @See also: RELATION_IS_OTHER_TEMP()
 */
static void CheckTopRelationIsInMyTempSession(Oid topRelId)
{
    /* first reset it */
    u_sess->cmd_cxt.topRelatationIsInMyTempSession = false;

    Relation topRel = relation_open(topRelId, NoLock);
    u_sess->cmd_cxt.topRelatationIsInMyTempSession =
        /* check top realtion persistent */
        topRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
        /* check top relation namespace */
        (u_sess->catalog_cxt.myTempNamespace == topRel->rd_rel->relnamespace ||
            u_sess->catalog_cxt.myTempToastNamespace == topRel->rd_rel->relnamespace);

    relation_close(topRel, NoLock);
}

/*
 * ATRewriteTables: ALTER TABLE phase 3
 */
static void ATRewriteTables(AlterTableStmt *parsetree, List** wqueue, LOCKMODE lockmode)
{
    ListCell* ltab = NULL;

    /* Go through each table that needs to be checked or rewritten */
    foreach (ltab, *wqueue) {
        AlteredTableInfo* tab = (AlteredTableInfo*)lfirst(ltab);
        int rel_format_idx = IDX_ROW_TBL;
        int idxPartitionedOrNot = IDX_ORDINARY_TBL;

#ifdef PGXC
        /* Forbid table rewrite operations with online data redistribution */
        if (tab->rewrite > 0 && list_length(tab->subcmds[AT_PASS_DISTRIB]) > 0 && IS_PGXC_COORDINATOR && !IsConnFromCoord())
            ereport(ERROR,
                (errcode(ERRCODE_STATEMENT_TOO_COMPLEX), errmsg("Incompatible operation with data redistribution")));
#endif

        /* Foreign tables have no storage. */
        if (tab->relkind == RELKIND_FOREIGN_TABLE || tab->relkind == RELKIND_STREAM)
            continue;

        if (tab->relkind == RELKIND_RELATION) {
            Relation temprel = heap_open(tab->relid, NoLock);
            rel_format_idx =
                RelationIsCUFormat(temprel) ? IDX_COL_TBL : IDX_ROW_TBL;
            idxPartitionedOrNot = RELATION_IS_PARTITIONED(temprel) ? IDX_PARTITIONED_TBL : IDX_ORDINARY_TBL;
            heap_close(temprel, NoLock);
        } else if (tab->relkind == RELKIND_INDEX || tab->relkind == RELKIND_GLOBAL_INDEX) {
            Relation temprel = index_open(tab->relid, NoLock);
            rel_format_idx = IDX_ROW_TBL; /* row relation */
            idxPartitionedOrNot = RelationIsPartitioned(temprel) ? IDX_PARTITIONED_TBL : IDX_ORDINARY_TBL;
            index_close(temprel, NoLock);
        }

        /*
         * If we change column data types or add/remove OIDs, the operation
         * has to be propagated to tables that use this table's rowtype as a
         * column type.  tab->newvals will also be non-NULL in the case where
         * we're adding a column with a default.  We choose to forbid that
         * case as well, since composite types might eventually support
         * defaults.
         *
         * (Eventually we'll probably need to check for composite type
         * dependencies even when we're just scanning the table without a
         * rewrite, but at the moment a composite type does not enforce any
         * constraints, so it's not necessary/appropriate to enforce them just
         * during ALTER.)
         */
        if (tab->newvals != NIL || tab->rewrite > 0) {
            Relation rel;

            rel = heap_open(tab->relid, NoLock);
            find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
            heap_close(rel, NoLock);
        }

        /*
         * We only need to rewrite the table if at least one column needs to
         * be recomputed, or we are adding/removing the OID column.
         */
        if (tab->rewrite > 0) {
            /* Build a temporary relation and copy data */
            Relation OldHeap;
            Oid NewTableSpace;

            OldHeap = heap_open(tab->relid, NoLock);
            /*
             * Fire off an Event Trigger now, before actually rewriting the
             * table.
             *
             * We don't support Event Trigger for nested commands anywhere,
             * here included, and parsetree is given NULL when coming from
             * AlterTableInternal.
             *
             * And fire it only once.
             */
            if (parsetree)
                EventTriggerTableRewrite((Node *)parsetree,
                                         tab->relid,
                                         tab->rewrite);

            /*
             * We don't support rewriting of system catalogs; there are too
             * many corner cases and too little benefit.  In particular this
             * is certainly not going to work for mapped catalogs.
             */
            if (IsSystemRelation(OldHeap))
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("cannot rewrite system relation \"%s\"", RelationGetRelationName(OldHeap))));
            if (RelationIsUsedAsCatalogTable(OldHeap))
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg(
                            "cannot rewrite table \"%s\" used as a catalog table", RelationGetRelationName(OldHeap))));
            /*
             * Don't allow rewrite on temp tables of other backends ... their
             * local buffer manager is not going to cope.
             */
            if (RELATION_IS_OTHER_TEMP(OldHeap))
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("cannot rewrite temporary tables of other sessions")));

            /*
             * Select destination tablespace (same as original unless user
             * requested a change)
             */
            if (tab->newTableSpace)
                NewTableSpace = tab->newTableSpace;
            else
                NewTableSpace = OldHeap->rd_rel->reltablespace;

            heap_close(OldHeap, NoLock);

            ExecRewriteFuncPtrArray[rel_format_idx][idxPartitionedOrNot](tab, NewTableSpace, lockmode);
        } else {
            /*
             * Test the current data within the table against new constraints
             * generated by ALTER TABLE commands, but don't rebuild data.
             */
            if (tab->constraints != NIL || tab->new_notnull) {
                ExecOnlyTestFuncPtrArray[rel_format_idx][idxPartitionedOrNot](tab);
            }

            /*
             * If we had SET TABLESPACE but no reason to reconstruct tuples,
             * just do a block-by-block copy.
             */
            if (tab->newTableSpace) {
                CheckTopRelationIsInMyTempSession(tab->relid);
                if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && IsTransactionBlock() && rel_format_idx == 0 &&
                    tab->relkind == RELKIND_RELATION) {
                    ereport(ERROR,
                        (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                            errmsg("alter row table tablespace cannot run inside a transaction block")));
                }
                ExecChangeTabspcFuncPtrArray[rel_format_idx][idxPartitionedOrNot](tab, lockmode);
            }
        }
    }

#ifdef PGXC
    /*
     * In PGXC, do not check the FK constraints on the Coordinator, and just return
     * That is because a SELECT is generated whose plan will try and use
     * the Datanodes. We (currently) do not want to do that on the Coordinator,
     * when the command is passed down to the Datanodes it will
     * peform the check locally.
     * This issue was introduced when we added multi-step handling,
     * it caused foreign key constraints to fail.
     * issue for pg_catalog or any other cases?
     */
    if (IS_PGXC_COORDINATOR)
        return;
#endif
    /*
     * Foreign key constraints are checked in a final pass, since (a) it's
     * generally best to examine each one separately, and (b) it's at least
     * theoretically possible that we have changed both relations of the
     * foreign key, and we'd better have finished both rewrites before we try
     * to read the tables.
     */
    foreach (ltab, *wqueue) {
        AlteredTableInfo* tab = (AlteredTableInfo*)lfirst(ltab);
        Relation rel = NULL;
        ListCell* lcon = NULL;

        foreach (lcon, tab->constraints) {
            NewConstraint* con = (NewConstraint*)lfirst(lcon);

            if (con->contype == CONSTR_FOREIGN) {
                Constraint* fkconstraint = (Constraint*)con->qual;
                Relation refrel;

                if (rel == NULL) {
                    /* Long since locked, no need for another */
                    rel = heap_open(tab->relid, NoLock);
                }

                refrel = heap_open(con->refrelid, RowShareLock);

                validateForeignKeyConstraint(fkconstraint->conname, rel, refrel, con->refindid, con->conid);

                /*
                 * No need to mark the constraint row as validated, we did
                 * that when we inserted the row earlier.
                 */
                heap_close(refrel, NoLock);
            }
        }

        if (rel)
            heap_close(rel, NoLock);
    }
}

static UHeapTuple BackUpScanCuTup(UHeapTuple scanCuTup)
{
    /* use top transaction memcxt, abort transaction will free the memory if any error occur */
    MemoryContext oldCxt = MemoryContextSwitchTo(u_sess->top_transaction_mem_cxt);
    UHeapTuple backUpTup = UHeapCopyTuple(scanCuTup);
    MemoryContextSwitchTo(oldCxt);
    return backUpTup;
}

static UHeapTuple RestoreScanCuTup(UHeapTuple backUpTup)
{
    UHeapTuple scanCuTup = UHeapCopyTuple(backUpTup);
    return scanCuTup;
}

template<typename T, TableAmType amtype>
static T EvaluateGenExpr(AlteredTableInfo* tab, T tuple,
                                  TupleDesc newTupDesc, ExprContext* econtext,
                                  Datum* values, bool* isnull)
{
    bool hasGenCol = false;
    ListCell* l = NULL;
    T tup = tuple;

    if (tab->rewrite > 0) {
        foreach(l, tab->newvals)
        {
            NewColumnValue *ex = (NewColumnValue*)lfirst(l);

            if (!ex->is_generated)
                continue;

            hasGenCol = true;

            if (tab->is_first_after) {
                values[ex->generate_attnum - 1] = ExecEvalExpr(ex->exprstate, econtext,
                    &isnull[ex->generate_attnum - 1], NULL);
            } else {
                values[ex->attnum - 1] = ExecEvalExpr(ex->exprstate, econtext, &isnull[ex->attnum - 1], NULL);
            }
        }

        if (hasGenCol) {
            /*
             * Form the new tuple. Note that we don't explicitly pfree it,
             * since the per-tuple memory context will be reset shortly.
            */
            if (amtype == TAM_HEAP) {
                tup = (T)heap_form_tuple(newTupDesc, values, isnull);
            } else {
                tup = (T)tableam_tops_form_tuple(newTupDesc, values, isnull, TableAmUstore);
            }
        }
    }
    return tup;
}

/*
 * update values and isnull after modify column to a new loaction.
 * newattnum > 0 denotes modify with first or after column or add generated column.
 */
static void UpdateValueModifyFirstAfter(NewColumnValue *ex, Datum* values, bool* isnull)
{
    if (ex->newattnum > 0 && !ex->is_addloc) {
        Datum valuesTemp = values[ex->attnum - 1];
        bool isnullTemp = isnull[ex->attnum - 1];
        if (ex->newattnum > ex->attnum) {
            for (int i = ex->attnum; i <= ex->newattnum - 1; i++) {
                values[i - 1] = values[i];
                isnull[i - 1] = isnull[i];
            }
        } else {
            for (int i = ex->attnum - 1; i >= ex->newattnum; i--) {
                values[i] = values[i - 1];
                isnull[i] = isnull[i - 1];
            }
        }
        values[ex->newattnum - 1] = valuesTemp;
        isnull[ex->newattnum - 1] = isnullTemp;
    }
}

static void UpdateGeneratedColumnIsnull(AlteredTableInfo* tab, bool* isnull, bool has_generated)
{
    ListCell* l = NULL;

    if (!has_generated) {
        return;
    }

    foreach (l, tab->newvals) {
        NewColumnValue *ex = (NewColumnValue*)lfirst(l);

        if (!ex->is_generated) {
            continue;
        }

        isnull[ex->generate_attnum - 1] = true;
    }
}

static void repl_update_addcolumn_default(AlteredTableInfo* tab, Relation oldrel,  Relation newrel, List* notnull_attrs)
{
    TupleDesc newTupDesc;
    int i;
    ListCell* l = NULL;
    EState* estate = NULL;
    CommandId mycid;
    BulkInsertState bistate;
    uint32 hi_options;
    newTupDesc = RelationGetDescr(oldrel); /* includes all mods */

    CommandCounterIncrement();

    mycid = GetCurrentCommandId(true);

    bistate = GetBulkInsertState();
    hi_options = TABLE_INSERT_SKIP_FSM;

    estate = CreateExecutorState();

    ExprContext* econtext = NULL;
    Datum* values = NULL;
    bool* isnull = NULL;
    bool* repl = NULL;
    TupleTableSlot* newslot = NULL;
    TableScanDesc scan;
    HeapTuple tuple;
    HeapTuple htup;
    MemoryContext oldCxt;
    errno_t rc = EOK;

    /* estate has been freed, prepare again */
    foreach (l, tab->constraints) {
        NewConstraint* con = (NewConstraint*)lfirst(l);
        if (con->isdisable)
            continue;

        switch (con->contype) {
            case CONSTR_CHECK:
                if (estate->es_is_flt_frame){
                    con->qualstate = (List*)ExecPrepareExprList((List*)con->qual, estate);
                } else {
                    con->qualstate = (List*)ExecPrepareExpr((Expr*)con->qual, estate);
                }
                break;
            case CONSTR_FOREIGN:
                /* Nothing to do here */
                break;
            default: {
                ereport(ERROR,
                    (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                        errmsg("unrecognized constraint type: %d", (int)con->contype)));
            } break;
        }
    }

    econtext = GetPerTupleExprContext(estate);
    newslot = MakeSingleTupleTableSlot(newTupDesc, false, oldrel->rd_tam_ops);

    i = newTupDesc->natts;
    values = (Datum*)palloc(i * sizeof(Datum));
    isnull = (bool*)palloc(i * sizeof(bool));
    repl = (bool*)palloc(i * sizeof(bool));
    rc = memset_s(values, i * sizeof(Datum), 0, i * sizeof(Datum));
    securec_check(rc, "\0", "\0");
    rc = memset_s(isnull, i * sizeof(bool), true, i * sizeof(bool));
    securec_check(rc, "\0", "\0");
    rc = memset_s(repl, i * sizeof(bool), false, i * sizeof(bool));
    securec_check(rc, "\0", "\0");


    scan = tableam_scan_begin(newrel, SnapshotNow, 0, NULL);
    oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));

    ((HeapScanDesc) scan)->rs_tupdesc = newTupDesc;
    while ((tuple =  (HeapTuple) tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) {
        ItemPointer searchSlotTid;
        searchSlotTid = tableam_tops_get_t_self(newrel, tuple);
        tableam_tops_deform_tuple(tuple, newTupDesc, values, isnull);

        (void)ExecStoreTuple(tuple, newslot, InvalidBuffer, false);
                    econtext->ecxt_scantuple = newslot;

        foreach (l, tab->newvals) {
            NewColumnValue* ex = (NewColumnValue*)lfirst(l);
            /* ex->attnum 可能不是最终的字段下标 */
            if (ex->make_dml_change) {
                int attnum = -1;
                for (int n = 0; n < newTupDesc->natts; ++n) {
                    Form_pg_attribute thisattr = &newTupDesc->attrs[n];

                    /* skip the dropped and rewritted columns */
                    if (!thisattr->attisdropped && ex->col_name &&
                        pg_strcasecmp(ex->col_name, NameStr(thisattr->attname)) == 0) {
                        attnum = thisattr->attnum;
                        break;
                    }
                }

                if (attnum <= 0) {
                    ereport(ERROR, (
                        errmsg("can not find column \"%s\"", ex->col_name)));
                }

                values[attnum - 1] = ExecEvalExpr(ex->exprstate, econtext, &isnull[attnum - 1], NULL);
                repl[attnum - 1] = true;
            }
        }

        htup = heap_modify_tuple(tuple, newTupDesc, values, isnull, repl);

        /* Now check any constraints on the possibly-changed tuple */
        (void)ExecStoreTuple(htup, newslot, InvalidBuffer, false);
        econtext->ecxt_scantuple = newslot;

        foreach (l, notnull_attrs) {
            int attn = lfirst_int(l);

            /* replace heap_attisnull with relationAttIsNull
            * due to altering table instantly
            */
            if (relationAttIsNull(htup, attn + 1, newTupDesc))
                ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION),
                    errmsg("column \"%s\" contains null values", NameStr(newTupDesc->attrs[attn].attname))));
        }

        foreach (l, tab->constraints) {
            NewConstraint* con = (NewConstraint*)lfirst(l);

            switch (con->contype) {
                case CONSTR_CHECK:
                    if (!ExecQual(con->qualstate, econtext, true))
                        ereport(ERROR,
                            (errcode(ERRCODE_CHECK_VIOLATION),
                                errmsg("check constraint \"%s\" is violated by some row", con->name)));
                    break;
                case CONSTR_FOREIGN:
                    /* Nothing to do here */
                    break;
                default: {
                    ereport(ERROR,
                        (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                            errmsg("unrecognized constraint type: %d", (int)con->contype)));
                }
            }
        }


        TM_FailureData tmfd;
        bool updateIndexes = false;
        Bitmapset *modifiedIdxAttrs = NULL;
        bool allowInplaceUpdate = true;
        
        (void)tableam_tuple_update(newrel, NULL, searchSlotTid, htup, mycid, InvalidSnapshot, estate->es_snapshot, true, &newslot, &tmfd, &updateIndexes, &modifiedIdxAttrs,
            false, allowInplaceUpdate);

        ResetExprContext(econtext);

        CHECK_FOR_INTERRUPTS();
    }

    MemoryContextSwitchTo(oldCxt);
    tableam_scan_end(scan);
    ExecDropSingleTupleTableSlot(newslot);

    FreeExecutorState(estate);
    FreeBulkInsertState(bistate);
    if (((hi_options & TABLE_INSERT_SKIP_WAL) || enable_heap_bcm_data_replication()) &&
        !RelationIsSegmentTable(newrel))
        heap_sync(newrel);
    /*
        * After the temporary table is rewritten, the relfilenode changes.
        * We need to find new TmptableCacheEntry with new relfilenode.
        * Then set new auto_increment counter value in new TmptableCacheEntry.
        */
    CopyTempAutoIncrement(oldrel, newrel);

}

/*
 * change ATRewriteTable() input: oid->rel
 */
/*
 * ATRewriteTable: scan or rewrite one table
 *
 * oldrel is NULL if we don't need to rewrite
 */
static void ATRewriteTableInternal(AlteredTableInfo* tab, Relation oldrel, Relation newrel)
{
    TupleDesc oldTupDesc;
    TupleDesc newTupDesc;
    bool needscan = false;
    List* notnull_attrs = NIL;
    int i;
    ListCell* l = NULL;
    EState* estate = NULL;
    CommandId mycid;
    BulkInsertState bistate;
    uint32 hi_options;

    oldTupDesc = tab->oldDesc;
    newTupDesc = RelationGetDescr(oldrel); /* includes all mods */

    bool repl_modify = false;
    bool need_dml_change_col = false;

    /*
     * Prepare a BulkInsertState and options for heap_insert. Because we're
     * building a new heap, we can skip WAL-logging and fsync it to disk at
     * the end instead (unless WAL-logging is required for archiving or
     * streaming replication). The FSM is empty too, so don't bother using it.
     */
    if (newrel) {
        mycid = GetCurrentCommandId(true);
        bistate = GetBulkInsertState();

        hi_options = TABLE_INSERT_SKIP_FSM;
        if (!XLogIsNeeded())
            hi_options |= TABLE_INSERT_SKIP_WAL;
    } else {
        /* keep compiler quiet about using these uninitialized */
        mycid = 0;
        bistate = NULL;
        hi_options = 0;
    }

    /*
     * Generate the constraint and default execution states
     */
    estate = CreateExecutorState();

    /* Build the needed expression execution states */
    foreach (l, tab->constraints) {
        NewConstraint* con = (NewConstraint*)lfirst(l);
        if (con->isdisable) 
            continue;

        switch (con->contype) {
            case CONSTR_CHECK:
                needscan = true;
                if (estate->es_is_flt_frame){
                    con->qualstate = (List*)ExecPrepareExprList((List*)con->qual, estate);
                } else {
                    con->qualstate = (List*)ExecPrepareExpr((Expr*)con->qual, estate);
                }
                break;
            case CONSTR_FOREIGN:
                /* Nothing to do here */
                break;
            default: {
                ereport(ERROR,
                    (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                        errmsg("unrecognized constraint type: %d", (int)con->contype)));
            } break;
        }
    }

    if (newrel && RelationIsRowFormat(oldrel) && 
        tab->rewrite > 0 && XLogLogicalInfoActive() &&
        oldrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP &&
        oldrel->relreplident == REPLICA_IDENTITY_FULL) {

        repl_modify = true;
    }

    foreach (l, tab->newvals) {
        NewColumnValue* ex = (NewColumnValue*)lfirst(l);

        /* expr already planned */
        ex->exprstate = ExecInitExpr((Expr*)ex->expr, NULL);

        if (ex->is_generated || ex->is_alter_using) {
            repl_modify = false;
        }

        if (ex->make_dml_change) {
            need_dml_change_col = true;
        }
    }

    notnull_attrs = NIL;
    if (newrel || tab->new_notnull) {
        /*
         * If we are rebuilding the tuples OR if we added any new NOT NULL
         * constraints, check all not-null constraints.  This is a bit of
         * overkill but it minimizes risk of bugs, and heap_attisnull is a
         * pretty cheap test anyway.
         */
        for (i = 0; i < newTupDesc->natts; i++) {
            if (newTupDesc->attrs[i].attnotnull && !newTupDesc->attrs[i].attisdropped)
                notnull_attrs = lappend_int(notnull_attrs, i);
        }
        if (notnull_attrs != NULL)
            needscan = true;
    }

    if (newrel || needscan) {
        ExprContext* econtext = NULL;
        Datum* values = NULL;
        bool* isnull = NULL;
        bool isUstore = false;
        TupleTableSlot* oldslot = NULL;
        TupleTableSlot* newslot = NULL;
        TableScanDesc scan;
        HeapTuple tuple;
        UHeapTuple utuple;
        MemoryContext oldCxt;
        List* dropped_attrs = NIL;
        ListCell* lc = NULL;
        errno_t rc = EOK;
        int128 autoinc = 0;
        bool need_autoinc = false;
        bool has_generated = false;
        AttrNumber autoinc_attnum = (newTupDesc->constr && newTupDesc->constr->cons_autoinc) ?
            newTupDesc->constr->cons_autoinc->attnum : 0;

        isUstore = RelationIsUstoreFormat(oldrel);

        if (newrel)
            ereport(DEBUG1, (errmsg("rewriting table \"%s\"", RelationGetRelationName(oldrel))));
        else
            ereport(DEBUG1, (errmsg("verifying table \"%s\"", RelationGetRelationName(oldrel))));

        if (newrel) {
            /*
             * All predicate locks on the tuples or pages are about to be made
             * invalid, because we move tuples around.	Promote them to
             * relation locks.
             */
            TransferPredicateLocksToHeapRelation(oldrel);
        }

        econtext = GetPerTupleExprContext(estate);

        /*
         * Make tuple slots for old and new tuples.  Note that even when the
         * tuples are the same, the tupDescs might not be (consider ADD COLUMN
         * without a default).
         */
        oldslot = MakeSingleTupleTableSlot(oldTupDesc, false, oldrel->rd_tam_ops);
        newslot = MakeSingleTupleTableSlot(newTupDesc, false, oldrel->rd_tam_ops);

        /* Preallocate values/isnull arrays */
        int n = Max(newTupDesc->natts, oldTupDesc->natts);
        values = (Datum*)palloc(n * sizeof(Datum));
        isnull = (bool*)palloc(n * sizeof(bool));
        rc = memset_s(values, n * sizeof(Datum), 0, n * sizeof(Datum));
        securec_check(rc, "\0", "\0");
        rc = memset_s(isnull, n * sizeof(bool), true, n * sizeof(bool));
        securec_check(rc, "\0", "\0");

        /*
         * Any attributes that are dropped according to the new tuple
         * descriptor can be set to NULL. We precompute the list of dropped
         * attributes to avoid needing to do so in the per-tuple loop.
         */
        for (i = 0; i < newTupDesc->natts; i++) {
            if (newTupDesc->attrs[i].attisdropped)
                dropped_attrs = lappend_int(dropped_attrs, i);
        }

        /*
         * here we don't care oldTupDesc->initdefvals, because it's
         * handled during deforming old tuple. new values for added
         * colums maybe is from *tab->newvals* list, or newTupDesc'
         * initdefvals list.
         */
        if (newTupDesc->initdefvals) {
            TupInitDefVal* defvals = newTupDesc->initdefvals;

            /* skip all the existing columns within this relation */
            for (i = oldTupDesc->natts; i < newTupDesc->natts; ++i) {
                if (!defvals[i].isNull) {
                    /* we assign both *isnull* and *values* here instead of
                     * scaning loop, because all these are constant and not
                     * dependent on each tuple.
                     */
                    isnull[i] = false;
                    values[i] = fetchatt(&newTupDesc->attrs[i], defvals[i].datum);
                }
            }
        }

        /*
         * Scan through the rows, generating a new row if needed and then
         * checking all the constraints.
         */
        scan = tableam_scan_begin(oldrel, SnapshotNow, 0, NULL);

        /*
         * Switch to per-tuple memory context and reset it for each tuple
         * produced, so we don't leak memory.
         */
        oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));

        // it is special that oldTupDesc must be used for deforming the heap tuple,
        // so that scan->rs_tupdesc is overwritten here.
        //
        if (isUstore) {
            ((UHeapScanDesc) scan)->rs_tupdesc = oldTupDesc;
            while ((UHeapGetNextSlotGuts(scan, ForwardScanDirection, oldslot)) != NULL)
            {
                utuple = (UHeapTuple)oldslot->tts_tuple;

                if (tab->rewrite > 0)
                {
                    int newvals_num = 0;
                    /* Extract data from old tuple */
                    tableam_tops_deform_tuple(utuple, oldTupDesc, values, isnull);

                    /*
                     * Process supplied expressions to replace selected columns.
                     *
                     * First, evaluate expressions whose inputs come from the old
                     * tuple.
                     */
                    econtext->ecxt_scantuple = oldslot;

                    foreach(l, tab->newvals)
                    {
                        NewColumnValue *ex = (NewColumnValue*)lfirst(l);

                        if (ex->is_addloc) {
                            for (i = oldTupDesc->natts + newvals_num - 1; i >= ex->attnum - 1; i--) {
                                values[i + 1] = values[i];
                                isnull[i + 1] = isnull[i];
                            }
                            newvals_num++;
                        }

                        if (ex->is_generated) {
                            if (tab->is_first_after) {
                                UpdateValueModifyFirstAfter(ex, values, isnull);
                                has_generated = true;
                            } else {
                                isnull[ex->attnum - 1] = true;
                            }
                            continue;
                        }

                        values[ex->attnum - 1] = ExecEvalExpr(ex->exprstate, econtext, &isnull[ex->attnum - 1], NULL);

                        if (ex->is_autoinc) {
                            need_autoinc = (autoinc_attnum > 0);
                        }

                        if (tab->is_first_after) {
                            UpdateValueModifyFirstAfter(ex, values, isnull);
                        }
                    }

                    /* generated column */
                    UpdateGeneratedColumnIsnull(tab, isnull, has_generated);

                    /* auto_increment */
                    if (need_autoinc) {
                        autoinc = EvaluateAutoIncrement(oldrel, newTupDesc,
                            autoinc_attnum, &values[autoinc_attnum - 1], &isnull[autoinc_attnum - 1]);
                    }

                    /* Set dropped attributes to null in new tuple */
                    foreach(lc, dropped_attrs) {
                        isnull[lfirst_int(lc)] = true;
                    }

                    /*
                     * Form the new tuple. Note that we don't explicitly pfree it,
                     * since the per-tuple memory context will be reset shortly.
                     */
                    utuple = (UHeapTuple)tableam_tops_form_tuple(newTupDesc, values, isnull, TableAmUstore);
                }

                /* Now check any constraints on the possibly-changed tuple */
                (void)ExecStoreTuple(utuple, newslot, InvalidBuffer, false);
                econtext->ecxt_scantuple = newslot;

                /*
                 * Now, evaluate any expressions whose inputs come from the
                 * new tuple.  We assume these columns won't reference each
                 * other, so that there's no ordering dependency.
                 */
                utuple = EvaluateGenExpr<UHeapTuple, TAM_USTORE>(tab, utuple, newTupDesc, econtext, values, isnull);

                foreach(l, notnull_attrs)
                {
                    int attn = lfirst_int(l);

                    /* replace heap_attisnull with relationAttIsNull
                     * due to altering table instantly
                     */
                    if (tableam_tops_tuple_attisnull(utuple, attn + 1, newTupDesc))
                            ereport(ERROR,
                                    (errcode(ERRCODE_NOT_NULL_VIOLATION),
                                             errmsg("column \"%s\" contains null values",
                                                    NameStr(newTupDesc->attrs[attn].attname))));
                }

                foreach(l, tab->constraints)
                {
                    NewConstraint *con = (NewConstraint*)lfirst(l);
                    ListCell* lc = NULL;
                    if (con->isdisable)
                        continue;

                    switch (con->contype)
                    {
                        case CONSTR_CHECK:
                        {
                        if (estate->es_is_flt_frame){
                            foreach (lc, con->qualstate) {
                                ExprState* exprState = (ExprState*)lfirst(lc);

                                if (!ExecCheckByFlatten(exprState, econtext))
                                    ereport(ERROR,
                                        (errcode(ERRCODE_CHECK_VIOLATION),
                                             errmsg("check constraint \"%s\" is violated by some row",
                                                    con->name)));
                            }
                        } else {
                            if (!ExecQual(con->qualstate, econtext, true)){
                                ereport(ERROR,
                                        (errcode(ERRCODE_CHECK_VIOLATION),
                                             errmsg("check constraint \"%s\" is violated by some row",
                                                    con->name)));
                            }
                        }
                        }
                            break;
                        case CONSTR_FOREIGN:
                            /* Nothing to do here */
                            break;
                        default:
                        {
                            ereport(ERROR,
                                        (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                                         errmsg("unrecognized constraint type: %d", (int) con->contype)));
                        }
                    }
                }

                /* Write the tuple out to the new relation */
                if (newrel) {
                    (void)tableam_tuple_insert(newrel, utuple, mycid, hi_options, bistate);

                    if (autoinc > 0) {
                        SetRelAutoIncrement(oldrel, newTupDesc, autoinc);
                    }
                }

                /*
                 * We need to reset the flags of slot before entering the next loop so that inplaceindex_getnextslot
                 * will not try to clear it after we reset the context. Note that we don't explicitly pfree its
                 * tuple since the per-tuple memory context will be reset shortly.
                 */
                oldslot->tts_flags &= ~TTS_FLAG_SHOULDFREE;

                UHeapTuple backUpTup = BackUpScanCuTup(((UHeapScanDesc) scan)->rs_cutup);
                ResetExprContext(econtext);
                ((UHeapScanDesc) scan)->rs_cutup = RestoreScanCuTup(backUpTup);
                if (backUpTup != NULL) {
                    pfree_ext(backUpTup);
                }

                CHECK_FOR_INTERRUPTS();
                if (tab->is_first_after) {
                    rc = memset_s(values, n * sizeof(Datum), 0, n * sizeof(Datum));
                    securec_check(rc, "\0", "\0");
                    rc = memset_s(isnull, n * sizeof(bool), true, n * sizeof(bool));
                    securec_check(rc, "\0", "\0");
                }
            }
        } else {
            ((HeapScanDesc) scan)->rs_tupdesc = oldTupDesc;
            while ((tuple =  (HeapTuple) tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) {
                if (tab->check_pass_with_relempty == AT_FASN_FAIL_PRECISION) {
                    ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("column to be modified must be empty to decrease precision or scale")));
                } else if (tab->check_pass_with_relempty == AT_FASN_FAIL_TYPE) {
                    ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("column to be modified must be empty to change datatype")));
                }

                if (tab->rewrite > 0) {
                    Oid tupOid = InvalidOid;
                    int newvals_num = 0;

                    /* Extract data from old tuple */
                    tableam_tops_deform_tuple(tuple, oldTupDesc, values, isnull);
                    if (oldTupDesc->tdhasoid)
                        tupOid = HeapTupleGetOid(tuple);

                    /*
                     * Process supplied expressions to replace selected columns.
                     *
                     * First, evaluate expressions whose inputs come from the old
                     * tuple.
                     */
                    (void)ExecStoreTuple(tuple, oldslot, InvalidBuffer, false);
                    econtext->ecxt_scantuple = oldslot;

                    foreach (l, tab->newvals) {
                        NewColumnValue* ex = (NewColumnValue*)lfirst(l);

                        if (ex->is_addloc) {
                            for (i = oldTupDesc->natts + newvals_num - 1; i >= ex->attnum - 1; i--) {
                                values[i + 1] = values[i];
                                isnull[i + 1] = isnull[i];
                            }
                            newvals_num++;
                        }

                        if (ex->is_generated) {
                            if (tab->is_first_after) {
                                UpdateValueModifyFirstAfter(ex, values, isnull);
                                has_generated = true;
                            } else {
                                isnull[ex->attnum - 1] = true;
                            }
                            continue;
                        }

                        if (repl_modify && ex->make_dml_change) {
                            isnull[ex->attnum - 1] = true;
                        } else {
                            values[ex->attnum - 1] = ExecEvalExpr(ex->exprstate, econtext, &isnull[ex->attnum - 1]);
                        }

                        
                        if (ex->is_autoinc) {
                            need_autoinc = (autoinc_attnum > 0);
                        }

                        if (tab->is_first_after) {
                            UpdateValueModifyFirstAfter(ex, values, isnull);
                        }
                    }

                    /* generated column */
                    UpdateGeneratedColumnIsnull(tab, isnull, has_generated);

                    /* auto_increment */
                    if (need_autoinc) {
                        autoinc = EvaluateAutoIncrement(oldrel, newTupDesc,
                            autoinc_attnum, &values[autoinc_attnum - 1], &isnull[autoinc_attnum - 1]);
                    }

                    /* Set dropped attributes to null in new tuple */
                    foreach (lc, dropped_attrs) {
                        isnull[lfirst_int(lc)] = true;
                    }

                    /*
                     * Form the new tuple. Note that we don't explicitly pfree it,
                     * since the per-tuple memory context will be reset shortly.
                     */
                    tuple = (HeapTuple)heap_form_tuple(newTupDesc, values, isnull);

                    /* Preserve OID, if any */
                    if (newTupDesc->tdhasoid)
                        HeapTupleSetOid(tuple, tupOid);
                }

                /* Now check any constraints on the possibly-changed tuple */
                (void)ExecStoreTuple(tuple, newslot, InvalidBuffer, false);
                econtext->ecxt_scantuple = newslot;

                /*
                 * Now, evaluate any expressions whose inputs come from the
                 * new tuple.  We assume these columns won't reference each
                 * other, so that there's no ordering dependency.
                 */
                tuple = EvaluateGenExpr<HeapTuple, TAM_HEAP>(tab, tuple, newTupDesc, econtext, values, isnull);

                if (!repl_modify) {
                    foreach (l, notnull_attrs) {
                        int attn = lfirst_int(l);

                        /* replace heap_attisnull with relationAttIsNull
                        * due to altering table instantly
                        */
                        if (relationAttIsNull(tuple, attn + 1, newTupDesc))
                            ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION),
                                errmsg("column \"%s\" contains null values", NameStr(newTupDesc->attrs[attn].attname))));
                    }

                    foreach (l, tab->constraints) {
                        NewConstraint* con = (NewConstraint*)lfirst(l);
                        ListCell* lc = NULL;

                        switch (con->contype) {
                            case CONSTR_CHECK:
                            {
                                if (estate->es_is_flt_frame){
                                    foreach (lc, con->qualstate) {
                                        ExprState* exprState = (ExprState*)lfirst(lc);

                                        if (!ExecCheckByFlatten(exprState, econtext))
                                            ereport(ERROR,
                                                (errcode(ERRCODE_CHECK_VIOLATION),
                                                    errmsg("check constraint \"%s\" is violated by some row",
                                                            con->name)));
                                    }
                                } else {
                                    if (!ExecQualByRecursion(con->qualstate, econtext, true)){
                                        ereport(ERROR,
                                                (errcode(ERRCODE_CHECK_VIOLATION),
                                                    errmsg("check constraint \"%s\" is violated by some row",
                                                            con->name)));
                                    }
                                }
                                }
                                    break;
                            case CONSTR_FOREIGN:
                                /* Nothing to do here */
                                break;
                            default: {
                                ereport(ERROR,
                                    (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                                        errmsg("unrecognized constraint type: %d", (int)con->contype)));
                            }
                        }
                    }
                }

                /* Write the tuple out to the new relation */
                if (newrel) {
                    if (repl_modify) {
                        /* deal with "add column c1 mytyp default expr"
                         */
                        tuple = (HeapTuple)heap_form_tuple(newTupDesc, values, isnull);
                        (void)tableam_tuple_insert(newrel, tuple, mycid, hi_options, bistate);

                        if (autoinc > 0) {
                            SetRelAutoIncrement(oldrel, newTupDesc, autoinc);
                        }
                    } else {
                        (void)tableam_tuple_insert(newrel, tuple, mycid, hi_options, bistate);

                        if (autoinc > 0) {
                            SetRelAutoIncrement(oldrel, newTupDesc, autoinc);
                        }
                    }
                }
                ResetExprContext(econtext);

                CHECK_FOR_INTERRUPTS();
                if (tab->is_first_after) {
                    rc = memset_s(values, n * sizeof(Datum), 0, n * sizeof(Datum));
                    securec_check(rc, "\0", "\0");
                    rc = memset_s(isnull, n * sizeof(bool), true, n * sizeof(bool));
                    securec_check(rc, "\0", "\0");
                }
            }
        }

        MemoryContextSwitchTo(oldCxt);
        tableam_scan_end(scan);

        ExecDropSingleTupleTableSlot(oldslot);
        ExecDropSingleTupleTableSlot(newslot);
    }

    FreeExecutorState(estate);

    if (newrel) {
        FreeBulkInsertState(bistate);

        /* If we skipped writing WAL, then we need to sync the heap. */
        if (((hi_options & TABLE_INSERT_SKIP_WAL) || enable_heap_bcm_data_replication()) &&
            !RelationIsSegmentTable(newrel))
            heap_sync(newrel);
        /*
         * After the temporary table is rewritten, the relfilenode changes.
         * We need to find new TmptableCacheEntry with new relfilenode.
         * Then set new auto_increment counter value in new TmptableCacheEntry.
         */
        CopyTempAutoIncrement(oldrel, newrel);
    }

    if (repl_modify) {
        repl_update_addcolumn_default(tab, oldrel, newrel, notnull_attrs);
    }
}

static void ATRewriteTable(AlteredTableInfo* tab, Relation oldrel, Relation newrel)
{
    if (RELATION_CREATE_BUCKET(oldrel)) {
        oidvector* bucketlist = searchHashBucketByOid(oldrel->rd_bucketoid);

        for (int i = 0; i < bucketlist->dim1; i++) {
            Relation oldbucket = bucketGetRelation(oldrel, NULL, bucketlist->values[i]);
            Relation newbucket = NULL;
            if (newrel != NULL) {
                newbucket = bucketGetRelation(newrel, NULL, bucketlist->values[i]);
            }

            ATRewriteTableInternal(tab, oldbucket, newbucket);

            bucketCloseRelation(oldbucket);
            if (newbucket != NULL) {
                bucketCloseRelation(newbucket);
            }
        }

        if (newrel && (!XLogIsNeeded() || enable_heap_bcm_data_replication()) && !RelationIsSegmentTable(newrel)) {
            heap_sync(newrel);
        }
    } else {
        ATRewriteTableInternal(tab, oldrel, newrel);
    }
}

#ifndef ENABLE_MULTIPLE_NODES
/*
 * ATOnlyCheckCStoreTable
 * Only support not-null constraint check currently.
 */
static void ATOnlyCheckCStoreTable(const AlteredTableInfo* tab, Relation rel)
{
    TupleDesc oldTupDesc = NULL;
    TupleDesc newTupDesc = NULL;
    List* notnullAttrs = NIL;
    bool needscan = false;

    oldTupDesc = tab->oldDesc;
    newTupDesc = RelationGetDescr(rel); /* includes all mods */

    if (tab->constraints != NIL || tab->newvals != NIL) {
        ereport(ERROR,
        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
            errmsg("Un-support feature"),
            errdetail("column stored relation doesn't support this feature")));
    }

    if (tab->new_notnull) {
        /*
         * If we added any new NOT NULL constraints, check all not-null constraints.
         */
        for (int i = 0; i < newTupDesc->natts; i++) {
            if (newTupDesc->attrs[i].attnotnull && !newTupDesc->attrs[i].attisdropped)
                notnullAttrs = lappend_int(notnullAttrs, i);
        }
        if (notnullAttrs != NULL)
            needscan = true;
    }

    if (needscan) {
        CStoreScanDesc cstoreScan = NULL;
        VectorBatch* vecScanBatch = NULL;
        int attrNum = list_length(notnullAttrs);
        AttrNumber* colIdx = (AttrNumber*)palloc(sizeof(AttrNumber) * attrNum);
        int n = 0;
        ListCell* cell = NULL;
        ScalarVector *vec = NULL;
        foreach (cell, notnullAttrs) {
            colIdx[n++] = lfirst_int(cell) + 1;
        }

        ereport(DEBUG1, (errmsg("verifying table \"%s\"", RelationGetRelationName(rel))));

        /*
         * Scan through the cstore, generating a new row if needed and then
         * checking constraints.
         */
        cstoreScan = CStoreBeginScan(rel, attrNum, colIdx, SnapshotNow, false);

        do {
            vecScanBatch = CStoreGetNextBatch(cstoreScan);
            vec = vecScanBatch->m_arr;
            for (int rowIdx = 0; rowIdx < vecScanBatch->m_rows; rowIdx++) {
                for (n = 0; n < attrNum; ++n ) {
                    int attn = colIdx[n] - 1;
                    if (vec[attn].IsNull(rowIdx)) {
                        ereport(ERROR,
                        (errcode(ERRCODE_NOT_NULL_VIOLATION),
                            errmsg("column \"%s\" contains null values", NameStr(newTupDesc->attrs[attn].attname))));
                    }
                }
            }
        } while (!CStoreIsEndScan(cstoreScan));

        CStoreEndScan(cstoreScan);

        pfree_ext(colIdx);
    }

    list_free_ext(notnullAttrs);
}
#endif

/*
 * ATGetQueueEntry: find or create an entry in the ALTER TABLE work queue
 */
static AlteredTableInfo* ATGetQueueEntry(List** wqueue, Relation rel, bool isDeltaTable)
{
    Oid relid = RelationGetRelid(rel);
    AlteredTableInfo* tab = NULL;
    ListCell* ltab = NULL;

    foreach (ltab, *wqueue) {
        tab = (AlteredTableInfo*)lfirst(ltab);
        if (tab->relid == relid)
            return tab;
    }

    /*
     * Not there, so add it.  Note that we make a copy of the relation's
     * existing descriptor before anything interesting can happen to it.
     */
    tab = (AlteredTableInfo*)palloc0(sizeof(AlteredTableInfo));
    tab->relid = relid;
    tab->relkind = rel->rd_rel->relkind;
    tab->oldDesc = CreateTupleDescCopy(RelationGetDescr(rel));
    tab->isDeltaTable = isDeltaTable;
    *wqueue = lappend(*wqueue, tab);

    return tab;
}

/*
 * ATSimplePermissions
 *
 * - Ensure that it is a relation (or possibly a view)
 * - Ensure this user is the owner or have the needed privileges
 * - Ensure that it is not a system table
 */
static void ATSimplePermissions(Relation rel, int allowed_targets)
{
    int actual_target;

    switch (rel->rd_rel->relkind) {
        case RELKIND_RELATION:
            actual_target = ATT_TABLE;
            break;
        case RELKIND_CONTQUERY:
        case RELKIND_VIEW:
            actual_target = ATT_VIEW;
            break;
        case RELKIND_MATVIEW:
            actual_target = ATT_MATVIEW;
            break;
        case RELKIND_INDEX:
        case RELKIND_GLOBAL_INDEX:
            actual_target = ATT_INDEX;
            break;
        case RELKIND_COMPOSITE_TYPE:
            actual_target = ATT_COMPOSITE_TYPE;
            break;
        case RELKIND_STREAM:
        case RELKIND_FOREIGN_TABLE:
            actual_target = ATT_FOREIGN_TABLE;
            break;
        case RELKIND_SEQUENCE:
        case RELKIND_LARGE_SEQUENCE: {
            if (u_sess->attr.attr_common.IsInplaceUpgrade) {
                actual_target = ATT_SEQUENCE;
            } else
                actual_target = ATT_NULL;
            break;
        }
        default:
            actual_target = 0;
            break;
    }

    /* Wrong target type? */
    if (((uint32)actual_target & (uint32)allowed_targets) == 0)
        ATWrongRelkindError(rel, allowed_targets);

    /* Permission check */
    ATPermissionCheck(rel->rd_rel, RelationGetRelid(rel));

    if (!(g_instance.attr.attr_common.allowSystemTableMods && RelationGetRelid(rel) >= FirstBootstrapObjectId) &&
        !u_sess->attr.attr_common.IsInplaceUpgrade && IsSystemRelation(rel))
        ereport(ERROR,
            (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                errmsg("permission denied: \"%s\" is a system catalog", RelationGetRelationName(rel))));
}

/*
 * ATWrongRelkindError
 *
 * Throw an error when a relation has been determined to be of the wrong
 * type.
 */
static void ATWrongRelkindError(Relation rel, int allowed_targets)
{
    char* msg = NULL;

    switch (allowed_targets) {
        case ATT_TABLE:
            msg = _("\"%s\" is not a table");
            break;
        case ATT_TABLE | ATT_VIEW:
            msg = _("\"%s\" is not a table or view");
            break;
        case ATT_TABLE | ATT_FOREIGN_TABLE:
            msg = _("\"%s\" is not a table or foreign table");
            break;
        case ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE:
            msg = _("\"%s\" is not a table, composite type, or foreign table");
            break;
        case ATT_VIEW:
            msg = _("\"%s\" is not a view");
            break;
        case ATT_FOREIGN_TABLE:
            msg = _("\"%s\" is not a foreign table");
            break;
        case ATT_TABLE | ATT_VIEW | ATT_MATVIEW | ATT_INDEX:
            msg = _("\"%s\" is not a table, view, materialized view, or index");
            break;
        case ATT_TABLE | ATT_MATVIEW:
            msg = _("\"%s\" is not a table or materialized view");
            break;
        case ATT_TABLE | ATT_MATVIEW | ATT_INDEX:
            msg = _("\"%s\" is not a table, materialized view, or index");
            break;
        case ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_FOREIGN_TABLE:
            msg = _("\"%s\" is not a table, materialized view, composite type, or foreign table");
            break;
        default:
            /* shouldn't get here, add all necessary cases above */
            msg = _("\"%s\" is of the wrong type");
            break;
    }

    ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg(msg, RelationGetRelationName(rel))));
}

/*
 * ATSimpleRecursion
 *
 * Simple table recursion sufficient for most ALTER TABLE operations.
 * All direct and indirect children are processed in an unspecified order.
 * Note that if a child inherits from the original table via multiple
 * inheritance paths, it will be visited just once.
 */
static void ATSimpleRecursion(List** wqueue, Relation rel, AlterTableCmd* cmd, bool recurse, LOCKMODE lockmode)
{
    /*
     * Propagate to children if desired.  Non-table relations never have
     * children, so no need to search in that case.
     */
    if (recurse && rel->rd_rel->relkind == RELKIND_RELATION) {
        Oid relid = RelationGetRelid(rel);
        ListCell* child = NULL;
        List* children = NIL;

        bool isDeltaStore = RelationIsCUFormat(rel);
#ifdef ENABLE_MULTIPLE_NODES
        isDeltaStore = g_instance.attr.attr_storage.enable_delta_store && isDeltaStore;
#endif
        if (isDeltaStore)

        /*
         * Under centrailzed mode, there may be unique index on delta table. When checking unique
         * constraint, unique index on delta will be used. So we ignore enable_delta_store here
         * and alter delta table at the same time.
         */
            children = find_cstore_delta(rel, lockmode);
        else
            children = find_all_inheritors(relid, lockmode, NULL);

        /*
         * find_all_inheritors does the recursive search of the inheritance
         * hierarchy, so all we have to do is process all of the relids in the
         * list that it returns.
         */
        foreach (child, children) {
            Oid childrelid = lfirst_oid(child);
            Relation childrel;

            if (childrelid == relid)
                continue;
            /* find_all_inheritors already got lock */
            childrel = relation_open(childrelid, NoLock);
            CheckTableNotInUse(childrel, "ALTER TABLE");
            ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, isDeltaStore);
            relation_close(childrel, NoLock);
        }
    }
}

/*
 * ATTypedTableRecursion
 *
 * Propagate ALTER TYPE operations to the typed tables of that type.
 * Also check the RESTRICT/CASCADE behavior.  Given CASCADE, also permit
 * recursion to inheritance children of the typed tables.
 */
static void ATTypedTableRecursion(List** wqueue, Relation rel, AlterTableCmd* cmd, LOCKMODE lockmode)
{
    ListCell* child = NULL;
    List* children = NIL;

    Assert(rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE);

    children = find_typed_table_dependencies(rel->rd_rel->reltype, RelationGetRelationName(rel), cmd->behavior);

    foreach (child, children) {
        Oid childrelid = lfirst_oid(child);
        Relation childrel;

        childrel = relation_open(childrelid, lockmode);
        CheckTableNotInUse(childrel, "ALTER TABLE");
        ATPrepCmd(wqueue, childrel, cmd, true, true, lockmode);
        relation_close(childrel, NoLock);
    }
}

/*
 * find_composite_type_dependencies
 *
 * Check to see if a composite type is being used as a column in some
 * other table (possibly nested several levels deep in composite types!).
 * Eventually, we'd like to propagate the check or rewrite operation
 * into other such tables, but for now, just error out if we find any.
 *
 * Caller should provide either a table name or a type name (not both) to
 * report in the error message, if any.
 *
 * We assume that functions and views depending on the type are not reasons
 * to reject the ALTER.  (How safe is this really?)
 */
void find_composite_type_dependencies(Oid typeOid, Relation origRelation, const char* origTypeName)
{
    Relation depRel;
    ScanKeyData key[2];
    SysScanDesc depScan;
    HeapTuple depTup;
    Oid arrayOid;

    /*
     * We scan pg_depend to find those things that depend on the rowtype. (We
     * assume we can ignore refobjsubid for a rowtype.)
     */
    depRel = heap_open(DependRelationId, AccessShareLock);

    ScanKeyInit(&key[0], Anum_pg_depend_refclassid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(TypeRelationId));
    ScanKeyInit(&key[1], Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(typeOid));

    depScan = systable_beginscan(depRel, DependReferenceIndexId, true, NULL, 2, key);

    while (HeapTupleIsValid(depTup = systable_getnext(depScan))) {
        Form_pg_depend pg_depend = (Form_pg_depend)GETSTRUCT(depTup);
        Relation rel;
        Form_pg_attribute att;

        /* Ignore dependees that aren't user columns of relations */
        /* (we assume system columns are never of rowtypes) */
        if (pg_depend->classid != RelationRelationId || pg_depend->objsubid <= 0)
            continue;

        rel = relation_open(pg_depend->objid, AccessShareLock);
        att = &rel->rd_att->attrs[pg_depend->objsubid - 1];

        if (rel->rd_rel->relkind == RELKIND_RELATION || rel->rd_rel->relkind == RELKIND_MATVIEW) {
            if (origTypeName != NULL)
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("cannot alter type \"%s\" because column \"%s.%s\" uses it",
                            origTypeName,
                            RelationGetRelationName(rel),
                            NameStr(att->attname))));
            else if (origRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("cannot alter type \"%s\" because column \"%s.%s\" uses it",
                            RelationGetRelationName(origRelation),
                            RelationGetRelationName(rel),
                            NameStr(att->attname))));
            else if (origRelation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("cannot alter foreign table \"%s\" because column \"%s.%s\" uses its row type",
                            RelationGetRelationName(origRelation),
                            RelationGetRelationName(rel),
                            NameStr(att->attname))));
            else if (origRelation->rd_rel->relkind == RELKIND_STREAM)
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("cannot alter stream \"%s\" because column \"%s.%s\" uses its row type",
                            RelationGetRelationName(origRelation),
                            RelationGetRelationName(rel),
                            NameStr(att->attname))));
            else
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("cannot alter table \"%s\" because column \"%s.%s\" uses its row type",
                            RelationGetRelationName(origRelation),
                            RelationGetRelationName(rel),
                            NameStr(att->attname))));
        } else if (OidIsValid(rel->rd_rel->reltype)) {
            /*
             * A view or composite type itself isn't a problem, but we must
             * recursively check for indirect dependencies via its rowtype.
             */
            find_composite_type_dependencies(rel->rd_rel->reltype, origRelation, origTypeName);
        }

        relation_close(rel, AccessShareLock);
    }

    systable_endscan(depScan);

    relation_close(depRel, AccessShareLock);

    /*
     * If there's an array type for the rowtype, must check for uses of it,
     * too.
     */
    arrayOid = get_array_type(typeOid);
    if (OidIsValid(arrayOid))
        find_composite_type_dependencies(arrayOid, origRelation, origTypeName);
}

/*
 * find_typed_table_dependencies
 *
 * Check to see if a composite type is being used as the type of a
 * typed table.  Abort if any are found and behavior is RESTRICT.
 * Else return the list of tables.
 */
static List* find_typed_table_dependencies(Oid typeOid, const char* typname, DropBehavior behavior)
{
    Relation classRel;
    ScanKeyData key[1];
    TableScanDesc scan;
    HeapTuple tuple;
    List* result = NIL;

    classRel = heap_open(RelationRelationId, AccessShareLock);

    ScanKeyInit(&key[0], Anum_pg_class_reloftype, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(typeOid));

    scan = tableam_scan_begin(classRel, SnapshotNow, 1, key);

    while ((tuple = (HeapTuple) tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) {
        if (behavior == DROP_RESTRICT)
            ereport(ERROR,
                (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
                    errmsg("cannot alter type \"%s\" because it is the type of a typed table", typname),
                    errhint("Use ALTER ... CASCADE to alter the typed tables too.")));
        else
            result = lappend_oid(result, HeapTupleGetOid(tuple));
    }

    tableam_scan_end(scan);
    heap_close(classRel, AccessShareLock);

    return result;
}

/*
 * check_of_type
 *
 * Check whether a type is suitable for CREATE TABLE OF/ALTER TABLE OF.  If it
 * isn't suitable, throw an error.  Currently, we require that the type
 * originated with CREATE TYPE AS.	We could support any row type, but doing so
 * would require handling a number of extra corner cases in the DDL commands.
 */
void check_of_type(HeapTuple typetuple)
{
    Form_pg_type typ = (Form_pg_type)GETSTRUCT(typetuple);
    bool typeOk = false;

    if (typ->typtype == TYPTYPE_COMPOSITE || typ->typtype == TYPTYPE_ABSTRACT_OBJECT) {
        Relation typeRelation;

        Assert(OidIsValid(typ->typrelid));
        typeRelation = relation_open(typ->typrelid, AccessShareLock);
        typeOk = (typeRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE);

        /*
         * Close the parent rel, but keep our AccessShareLock on it until xact
         * commit.	That will prevent someone else from deleting or ALTERing
         * the type before the typed table creation/conversion commits.
         */
        relation_close(typeRelation, NoLock);
    }
    if (!typeOk)
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("type %s is not a composite type", format_type_be(HeapTupleGetOid(typetuple)))));
}

/*
 * ALTER TABLE ADD COLUMN
 *
 * Adds an additional attribute to a relation making the assumption that
 * CHECK, NOT NULL, and FOREIGN KEY constraints will be removed from the
 * AT_AddColumn AlterTableCmd by parse_utilcmd.c and added as independent
 * AlterTableCmd's.
 *
 * ADD COLUMN cannot use the normal ALTER TABLE recursion mechanism, because we
 * have to decide at runtime whether to recurse or not depending on whether we
 * actually add a column or merely merge with an existing column.  (We can't
 * check this in a static pre-pass because it won't handle multiple inheritance
 * situations correctly.)
 */
static void ATPrepAddColumn(List** wqueue, AlteredTableInfo* tab, Relation rel, bool recurse,
    bool recursing, AlterTableCmd* cmd, LOCKMODE lockmode)
{
    if (rel->rd_rel->reloftype && !recursing)
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot add column to typed table")));

    ColumnDef* colDef = (ColumnDef*)cmd->def;

    if (RelationIsColStore(rel)) {
        int32 typmod = 0;
        HeapTuple typeTuple = typenameType(NULL, colDef->typname, &typmod);
        Oid typeOid = HeapTupleGetOid(typeTuple);
        ReleaseSysCache(typeTuple);

        // check the supported data type and error report if needed.
        if (RelationIsCUFormat(rel) && !IsTypeSupportedByCStore(typeOid)) {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("type \"%s\" is not supported in column store", format_type_with_typemod(typeOid, typmod))));
        }
        if (RelationIsPAXFormat(rel) && !IsTypeSupportedByORCRelation(typeOid)) {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("type \"%s\" is not supported in DFS table.", format_type_with_typemod(typeOid, typmod))));
        }
    } else if (RelationIsTsStore(rel)) {
        int32 typmod = 0;
        HeapTuple typeTuple = typenameType(NULL, colDef->typname, &typmod);
        Oid typeOid = HeapTupleGetOid(typeTuple);
        ReleaseSysCache(typeTuple);

        // check the supported data type and error report if needed.
        if (!IsTypeSupportedByTsStore(colDef->kvtype, typeOid)) {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("type \"%s\" is not supported in timeseries store",
                    format_type_with_typemod(typeOid, typmod))));
        }
#ifdef ENABLE_HTAP
    } else if (RelHasImcs(RelationGetRelid(rel))) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Un-support feature"),
                errdetail("AddColumn operation is not supported for HTAP table. please unpopulate first.")));
    }
#else
}
#endif

    if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
        ATTypedTableRecursion(wqueue, rel, cmd, lockmode);

    if (recurse)
        cmd->subtype = AT_AddColumnRecurse;

    if ((cmd->is_first || cmd->after_name != NULL) && (tab != NULL)) {
        tab->rewrite |= AT_REWRITE_ALTER_PERSISTENCE;
    }
}

static bool contain_columndef_walker(Node* node, void* context)
{
    if (node == NULL)
        return false;

    if (IsA(node, ColumnRef))
        return true;

    return raw_expression_tree_walker(node, (bool (*)())contain_columndef_walker, context);
}

static void ATPrepCheckDefault(Node* node)
{
    if (contain_columndef_walker(node, NULL)) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                (errmsg("default value cannot reference to a column"),
                    errhint("Perhaps the default value is enclosed in double quotes"))));
    }
}

static FORCE_INLINE void ATExecAppendDefValExpr(_in_ AttrNumber attnum, _in_ Expr* defval, _out_ AlteredTableInfo* tab,
                                                ColumnDef  *colDef, bool is_autoinc, bool is_addloc, bool make_dml_change = false)
{
    NewColumnValue* newval;

    newval = (NewColumnValue*)palloc0(sizeof(NewColumnValue));
    newval->attnum = attnum;
    newval->expr = expression_planner(defval);
    newval->is_addloc = is_addloc;
    newval->newattnum = is_addloc ? -1 : 0;
    newval->generate_attnum = 0;

    tab->newvals = lappend(tab->newvals, newval);
    newval->is_generated = (colDef->generatedCol != '\0');
    newval->col_name = pstrdup(colDef->colname);
    newval->is_autoinc = is_autoinc;
    newval->make_dml_change = make_dml_change;
    tab->rewrite |= AT_REWRITE_ALTER_PERSISTENCE;
}

static int GetAfterColumnAttnum(Oid attrelid, const char *after_name)
{
    int afterattnum = -1;
    HeapTuple tuple;

    tuple = SearchSysCacheAttName(attrelid, after_name);
    if (!HeapTupleIsValid(tuple)) {
        ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
                errmsg("The %s column of relation %u is not exists.", after_name, attrelid)));
    }

    afterattnum = ((Form_pg_attribute)GETSTRUCT(tuple))->attnum + 1;
    ReleaseSysCache(tuple);
    return afterattnum;
}

static Node *UpdateVarattnoAfterAddColumn(Node *node, int startattnum, int endattnum, bool is_increase)
{
    if (node == NULL) {
        return node;
    }

    int curattnum = is_increase ? endattnum + 1 : startattnum - 1;
    int newattnum = is_increase ? startattnum : endattnum;

    switch (nodeTag(node)) {
        case T_Var: {
            Var *var = (Var *)node;
            Var *new_var = (Var *)copyObject(var);
            if (var->varattno >= startattnum && var->varattno <= endattnum) {
                new_var->varattno = is_increase ? (var->varattno + 1) : (var->varattno - 1);
                new_var->varoattno = is_increase ? (var->varoattno + 1) : (var->varoattno - 1);
            } else if (var->varattno == curattnum) {
                new_var->varattno = newattnum;
                new_var->varoattno = newattnum;
            }
            return (Node *)new_var;
        }
        case T_Const:
        case T_Param:
        case T_Rownum:
        case T_CoerceToDomainValue:
        case T_CaseTestExpr: {
            return node;
        }
        case T_TypeCast: {
            TypeCast *expr = (TypeCast *)node;
            TypeCast *newexpr = (TypeCast *)copyObject(expr);
            Node *expr_arg = UpdateVarattnoAfterAddColumn(expr->arg, startattnum, endattnum, is_increase);
            newexpr->arg = expr_arg;
            Node* frmt_str = UpdateVarattnoAfterAddColumn(expr->fmt_str, startattnum, endattnum, is_increase);
            newexpr->fmt_str = frmt_str;
            Node* nls_fmt_str = UpdateVarattnoAfterAddColumn(expr->nls_fmt_str, startattnum, endattnum, is_increase);
            newexpr->nls_fmt_str = nls_fmt_str;
            Node* default_expr = UpdateVarattnoAfterAddColumn(expr->default_expr, startattnum, endattnum, is_increase);
            newexpr->default_expr = default_expr; 
            return (Node *)newexpr;
        }
        case T_ArrayExpr: {
            ArrayExpr *expr = (ArrayExpr *)node;
            ArrayExpr *newexpr = (ArrayExpr *)copyObject(expr);
            List *expr_elements = (List *)UpdateVarattnoAfterAddColumn((Node *)expr->elements,
                startattnum, endattnum, is_increase);
            newexpr->elements = expr_elements;
            return (Node *)newexpr;
        }
        case T_FuncExpr: {
            FuncExpr *expr = (FuncExpr *)node;
            FuncExpr *newexpr = (FuncExpr *)copyObject(expr);
            List *expr_args = (List *)UpdateVarattnoAfterAddColumn((Node *)expr->args,
                startattnum, endattnum, is_increase);
            newexpr->args = expr_args;
            return (Node *)newexpr;
        }
        case T_OpExpr:
        case T_DistinctExpr:
        case T_NullIfExpr: {
            OpExpr *expr = (OpExpr *)node;
            OpExpr *newexpr = (OpExpr *)copyObject(expr);
            List *expr_args = (List *)UpdateVarattnoAfterAddColumn((Node *)expr->args,
                startattnum, endattnum, is_increase);
            newexpr->args = expr_args;
            return (Node *)newexpr;
        }
        case T_BoolExpr: {
            BoolExpr *expr = (BoolExpr *)node;
            BoolExpr *newexpr = (BoolExpr *)copyObject(expr);
            List *expr_args = (List *)UpdateVarattnoAfterAddColumn((Node *)expr->args,
                startattnum, endattnum, is_increase);
            newexpr->args = expr_args;
            return (Node *)newexpr;
        }
        case T_ScalarArrayOpExpr: {
            ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *)node;
            ScalarArrayOpExpr *newexpr = (ScalarArrayOpExpr *)copyObject(expr);
            List *expr_args = (List *)UpdateVarattnoAfterAddColumn((Node *)expr->args,
                startattnum, endattnum, is_increase);
            newexpr->args = expr_args;
            return (Node *)newexpr;
        }
        case T_ArrayRef: {
            ArrayRef *expr = (ArrayRef *)node;
            ArrayRef *newexpr = (ArrayRef *)copyObject(expr);
            List *expr_refupperindexpr = (List *)UpdateVarattnoAfterAddColumn((Node *)expr->refupperindexpr,
                startattnum, endattnum, is_increase);
            List *expr_reflowerindexpr = (List *)UpdateVarattnoAfterAddColumn((Node *)expr->reflowerindexpr,
                startattnum, endattnum, is_increase);
            Expr *expr_refexpr = (Expr *)UpdateVarattnoAfterAddColumn((Node *)expr->refexpr,
                startattnum, endattnum, is_increase);
            Expr *expr_refassgnexpr = (Expr *)UpdateVarattnoAfterAddColumn((Node *)expr->refassgnexpr,
                startattnum, endattnum, is_increase);
            newexpr->refupperindexpr = expr_refupperindexpr;
            newexpr->reflowerindexpr = expr_reflowerindexpr;
            newexpr->refexpr = expr_refexpr;
            newexpr->refassgnexpr = expr_refassgnexpr;
            return (Node *)newexpr;
        }
        case T_RowCompareExpr: {
            RowCompareExpr *expr = (RowCompareExpr *)node;
            RowCompareExpr *newexpr = (RowCompareExpr *)copyObject(expr);
            List *expr_largs = (List *)UpdateVarattnoAfterAddColumn((Node *)expr->largs,
                startattnum, endattnum, is_increase);
            List *expr_rargs = (List *)UpdateVarattnoAfterAddColumn((Node *)expr->rargs,
                startattnum, endattnum, is_increase);
            newexpr->largs = expr_largs;
            newexpr->rargs = expr_rargs;
            return (Node *)newexpr;
        }
        case T_ConvertRowtypeExpr: {
            ConvertRowtypeExpr *expr = (ConvertRowtypeExpr *)node;
            ConvertRowtypeExpr *newexpr = (ConvertRowtypeExpr *)copyObject(expr);
            Expr *expr_arg = (Expr *)UpdateVarattnoAfterAddColumn((Node *)expr->arg,
                startattnum, endattnum, is_increase);
            newexpr->arg = expr_arg;
            return (Node *)newexpr;
        }
        case T_FieldStore: {
            FieldStore *expr = (FieldStore *)node;
            FieldStore *newexpr = (FieldStore *)node;
            Expr *expr_arg = (Expr *)UpdateVarattnoAfterAddColumn((Node *)expr->arg,
                startattnum, endattnum, is_increase);
            List *expr_newvals = (List *)UpdateVarattnoAfterAddColumn((Node *)expr->newvals,
                startattnum, endattnum, is_increase);
            newexpr->arg = expr_arg;
            newexpr->newvals = expr_newvals;
            return (Node *)newexpr;
        }
        case T_FieldSelect: {
            FieldSelect *expr = (FieldSelect *)node;
            FieldSelect *newexpr = (FieldSelect *)copyObject(expr);
            Expr *expr_arg = (Expr *)UpdateVarattnoAfterAddColumn((Node *)expr->arg,
                startattnum, endattnum, is_increase);
            newexpr->arg = expr_arg;
            return (Node *)newexpr;
        }
        case T_MinMaxExpr: {
            MinMaxExpr *expr = (MinMaxExpr *)node;
            MinMaxExpr *newexpr = (MinMaxExpr *)copyObject(expr);
            List *expr_args = (List *)UpdateVarattnoAfterAddColumn((Node *)expr->args,
                startattnum, endattnum, is_increase);
            newexpr->args = expr_args;
            return (Node *)newexpr;
        }
        case T_BooleanTest: {
            BooleanTest *expr = (BooleanTest *)node;
            BooleanTest *newexpr = (BooleanTest *)copyObject(expr);
            Expr *expr_arg = (Expr *)UpdateVarattnoAfterAddColumn((Node *)expr->arg,
                startattnum, endattnum, is_increase);
            newexpr->arg = expr_arg;
            return (Node *)newexpr;
        }
        case T_RowExpr: {
            RowExpr *expr = (RowExpr *)node;
            RowExpr *newexpr = (RowExpr *)copyObject(expr);
            List *expr_args = (List *)UpdateVarattnoAfterAddColumn((Node *)expr->args,
                startattnum, endattnum, is_increase);
            newexpr->args = expr_args;
            return (Node *)newexpr;
        }
        case T_XmlExpr: {
            XmlExpr *expr = (XmlExpr *)node;
            XmlExpr *newExpr = (XmlExpr *)copyObject(expr);
            List *expr_args = (List *)UpdateVarattnoAfterAddColumn((Node *)expr->args,
                startattnum, endattnum, is_increase);
            List *expr_name_args = (List *)UpdateVarattnoAfterAddColumn((Node *)expr->named_args,
                startattnum, endattnum, is_increase);
            newExpr->args = expr_args;
            newExpr->named_args = expr_name_args;
            return (Node *)newExpr;
        }
        case T_RelabelType: {
            RelabelType *expr = (RelabelType *)node;
            RelabelType *newexpr =(RelabelType *)copyObject(expr);
            Expr *expr_arg = (Expr *)UpdateVarattnoAfterAddColumn((Node *)expr->arg,
                startattnum, endattnum, is_increase);
            newexpr->arg = expr_arg;
            return (Node *)newexpr;
        }
        case T_WindowFunc: {
            WindowFunc *expr = (WindowFunc *)node;
            WindowFunc *newexpr = (WindowFunc *)copyObject(expr);
            List *expr_args = (List *)UpdateVarattnoAfterAddColumn((Node *)expr->args,
                startattnum, endattnum, is_increase);
            newexpr->args = expr_args;
            List *expr_keep_args = (List *)UpdateVarattnoAfterAddColumn((Node *)expr->keep_args,
                startattnum, endattnum, is_increase);
            newexpr->keep_args = expr_keep_args;
            return (Node *)newexpr;
        }
        case T_ArrayCoerceExpr: {
            ArrayCoerceExpr *expr = (ArrayCoerceExpr *)node;
            ArrayCoerceExpr *newexpr = (ArrayCoerceExpr *)copyObject(expr);
            Expr *expr_arg = (Expr *)UpdateVarattnoAfterAddColumn((Node *)expr->arg,
                startattnum, endattnum, is_increase);
            newexpr->arg = expr_arg;
            return (Node *)newexpr;
        }
        case T_PredictByFunction: {
            PredictByFunction *expr = (PredictByFunction *)node;
            PredictByFunction *newexpr = (PredictByFunction *)copyObject(expr);
            List *expr_args = (List *)UpdateVarattnoAfterAddColumn((Node *)expr->model_args,
                startattnum, endattnum, is_increase);
            newexpr->model_args = expr_args;
            return (Node *)newexpr;
        }
        case T_NamedArgExpr: {
            NamedArgExpr *expr = (NamedArgExpr *)node;
            NamedArgExpr *newexpr = (NamedArgExpr *)copyObject(expr);
            Expr *expr_arg = (Expr *)UpdateVarattnoAfterAddColumn((Node *)expr->arg,
                startattnum, endattnum, is_increase);
            newexpr->arg = expr_arg;
            return (Node *)newexpr;
        }
        case T_CoerceViaIO: {
            CoerceViaIO *expr = (CoerceViaIO *)node;
            CoerceViaIO *newexpr = (CoerceViaIO *)copyObject(expr);
            Expr *expr_arg = (Expr *)UpdateVarattnoAfterAddColumn((Node *)expr->arg,
                startattnum, endattnum, is_increase);
            newexpr->arg = expr_arg;
            return (Node *)newexpr;
        }
        case T_CoerceToDomain: {
            CoerceToDomain *expr = (CoerceToDomain *)node;
            CoerceToDomain *newexpr = (CoerceToDomain *)copyObject(expr);
            Expr *expr_arg = (Expr *)UpdateVarattnoAfterAddColumn((Node *)expr->arg,
                startattnum, endattnum, is_increase);
            newexpr->arg = expr_arg;
            return (Node *)newexpr;
        }
        case T_CoalesceExpr: {
            CoalesceExpr* expr = (CoalesceExpr *)node;
            CoalesceExpr* newexpr = (CoalesceExpr *)copyObject(expr);
            List *expr_args = (List *)UpdateVarattnoAfterAddColumn((Node *)expr->args,
                startattnum, endattnum, is_increase);
            newexpr->args = expr_args;
            return (Node *)newexpr;
        }
        case T_NullTest: {
            NullTest *expr = (NullTest *)node;
            NullTest *newexpr = (NullTest *)copyObject(expr);
            Expr *expr_arg = (Expr *)UpdateVarattnoAfterAddColumn((Node *)expr->arg,
                startattnum, endattnum, is_increase);
            newexpr->arg = expr_arg;
            return (Node *)newexpr;
        }
        case T_CaseExpr: {
            CaseExpr *expr = (CaseExpr *)node;
            CaseExpr *newExpr = (CaseExpr *)copyObject(expr);

            List *expr_args = (List *)UpdateVarattnoAfterAddColumn((Node *)expr->args,
                startattnum, endattnum, is_increase);
            // case_default
            Expr *expr_defresult = (Expr *)UpdateVarattnoAfterAddColumn((Node *)expr->defresult,
                startattnum, endattnum, is_increase);
            // case_arg
            Expr *expr_arg = (Expr *)UpdateVarattnoAfterAddColumn((Node *)expr->arg,
                startattnum, endattnum, is_increase);
            newExpr->arg = expr_arg;
            newExpr->args = expr_args;
            newExpr->defresult = expr_defresult;
            return (Node *)newExpr;
        }
        case T_CaseWhen: {
            CaseWhen *expr = (CaseWhen *)node;
            CaseWhen *newexpr = (CaseWhen *)copyObject(expr);
            Expr *expr_expr = (Expr *)UpdateVarattnoAfterAddColumn((Node *)expr->expr,
                startattnum, endattnum, is_increase);
            Expr *expr_result = (Expr *)UpdateVarattnoAfterAddColumn((Node *)expr->result,
                startattnum, endattnum, is_increase);
            newexpr->expr = expr_expr;
            newexpr->result = expr_result;
            return (Node *)newexpr;
        }
        case T_List: {
            List *reslist = NIL;
            ListCell *temp = NULL;

            foreach(temp, (List *)node) {
                reslist = lappend(reslist,
                    UpdateVarattnoAfterAddColumn((Node *)lfirst(temp),
                        startattnum, endattnum, is_increase));
            }
            return (Node *)reslist;
        }
        case T_PrefixKey: {
            PrefixKey *expr = (PrefixKey *)node;
            PrefixKey *newexpr = (PrefixKey *)copyObject(expr);
            Expr *expr_arg = (Expr *)UpdateVarattnoAfterAddColumn((Node *)expr->arg,
                startattnum, endattnum, is_increase);
            newexpr->arg = expr_arg;
            return (Node *)newexpr;
        }
        default:
            ereport(ERROR,
                (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                    errmsg("unrecognized node type: %d for first|after col_name", (int)nodeTag(node))));
            break;
    }
    return NULL;
}

/*
 * update pg_statistic
 * 1. add column with first or after column
 * 2. modify column to first or after column
 */
static void UpdatePgStatisticFirstAfter(Relation rel, int startattnum, int endattnum, bool is_increase)
{
    Relation stat_rel;
    HeapTuple stat_tuple;
    ScanKeyData key[3];
    SysScanDesc scan;
    Form_pg_statistic stat_form;

    stat_rel = heap_open(StatisticRelationId, RowExclusiveLock);

    for (int i = (is_increase ? endattnum : startattnum);
        (is_increase ? i >= startattnum : i <= endattnum); (is_increase ? i-- : i++)) {
        ScanKeyInit(&key[0], Anum_pg_statistic_starelid, BTEqualStrategyNumber, F_OIDEQ,
            ObjectIdGetDatum(RelationGetRelid(rel)));
        ScanKeyInit(&key[1], Anum_pg_statistic_starelkind, BTEqualStrategyNumber, F_CHAREQ, ObjectIdGetDatum(STARELKIND_CLASS));
        ScanKeyInit(&key[2], Anum_pg_statistic_staattnum, BTEqualStrategyNumber, F_INT2EQ, Int16GetDatum(i));

        scan = systable_beginscan(stat_rel, StatisticRelidKindAttnumInhIndexId, true, NULL, 3, key);

       while (HeapTupleIsValid(stat_tuple = systable_getnext(scan))) {
            Datum values[Natts_pg_statistic] = { 0 };
            bool nulls[Natts_pg_statistic] = { 0 };
            bool replaces[Natts_pg_statistic] = { 0 };
            HeapTuple new_stat_tuple;

            stat_form = (Form_pg_statistic)GETSTRUCT(stat_tuple);

            values[Anum_pg_statistic_staattnum - 1] = is_increase ? Int16GetDatum(stat_form->staattnum + 1) :
                Int16GetDatum(stat_form->staattnum - 1);
            replaces[Anum_pg_statistic_staattnum - 1] = true;

            new_stat_tuple = heap_modify_tuple(stat_tuple, RelationGetDescr(stat_rel), values, nulls, replaces);
            simple_heap_update(stat_rel, &new_stat_tuple->t_self, new_stat_tuple);
            CatalogUpdateIndexes(stat_rel, new_stat_tuple);

            heap_freetuple_ext(new_stat_tuple);
        }
        systable_endscan(scan);
    }
    heap_close(stat_rel, RowExclusiveLock);
}

/*
 * update pg_statistic_ext
 * 1. add column with first or after column
 * 2. modify column to first or after column
 */
static void UpdatePgStatisticExtFirstAfter(Relation rel, int startattnum, int endattnum, bool is_increase)
{
    Relation stat_ext_rel;
    HeapTuple stat_ext_tuple;
    ScanKeyData key[2];
    SysScanDesc scan;
    int curattnum = is_increase ? endattnum + 1 : startattnum - 1;
    int newattnum = is_increase ? startattnum : endattnum;

    ScanKeyInit(&key[0], Anum_pg_statistic_ext_starelid, BTEqualStrategyNumber, F_OIDEQ,
        ObjectIdGetDatum(RelationGetRelid(rel)));
    ScanKeyInit(&key[1], Anum_pg_statistic_ext_starelkind, BTEqualStrategyNumber, F_CHAREQ, ObjectIdGetDatum(STARELKIND_CLASS));
    stat_ext_rel = heap_open(StatisticExtRelationId, RowExclusiveLock);
    scan = systable_beginscan(stat_ext_rel, StatisticExtRelidKindInhKeyIndexId, true, NULL, 2, key);

    while (HeapTupleIsValid(stat_ext_tuple = systable_getnext(scan))) {
        bool is_null = false;
        Datum values[Natts_pg_statistic_ext] = { 0 };
        bool nulls[Natts_pg_statistic_ext] = { 0 };
        bool replaces[Natts_pg_statistic_ext] = { 0 };
        int2vector *stakey = NULL;
        int2vector *new_stakey = NULL;
        HeapTuple new_stat_ext_tuple;

        Datum stakey_datum = fastgetattr(stat_ext_tuple, Anum_pg_statistic_ext_stakey, RelationGetDescr(stat_ext_rel), &is_null);
        stakey = (int2vector *)DatumGetPointer(stakey_datum);
        int2 *stakey_values = (int2 *)palloc0(stakey->dim1 * sizeof(int2));
        for (int i = 0; i < stakey->dim1; i++) {
            if (stakey->values[i] >= startattnum && stakey->values[i] <= endattnum) {
                stakey_values[i] = is_increase ? (stakey->values[i] + 1) : (stakey->values[i] - 1);
            } else if (stakey->values[i] == curattnum) {
                stakey_values[i] = newattnum;
            } else {
                stakey_values[i] = stakey->values[i];
            }
        }
        new_stakey = buildint2vector(stakey_values, stakey->dim1);
        values[Anum_pg_statistic_ext_stakey - 1] = PointerGetDatum(new_stakey);
        replaces[Anum_pg_statistic_ext_stakey - 1] = true;

        new_stat_ext_tuple = heap_modify_tuple(stat_ext_tuple, RelationGetDescr(stat_ext_rel), values, nulls, replaces);
        simple_heap_update(stat_ext_rel, &new_stat_ext_tuple->t_self, new_stat_ext_tuple);
        CatalogUpdateIndexes(stat_ext_rel, new_stat_ext_tuple);

        pfree_ext(new_stakey);
        pfree_ext(stakey_values);
        heap_freetuple_ext(new_stat_ext_tuple);
    }

    systable_endscan(scan);
    heap_close(stat_ext_rel, RowExclusiveLock);
}

/*
 * update pg_description
 * 1. add column with first or after col_name.
 * 2. modify column to first or after column.
 */
static void UpdatePgDescriptionFirstAfter(Relation rel, int startattnum, int endattnum, bool is_increase)
{
    Relation desc_rel;
    HeapTuple desc_tuple;
    ScanKeyData key[3];
    SysScanDesc scan;
    Form_pg_description desc_form;
    
    desc_rel = heap_open(DescriptionRelationId, RowExclusiveLock);

    for (int i = (is_increase ? endattnum : startattnum);
        (is_increase ? i >= startattnum : i <= endattnum); (is_increase ? i-- : i++)) {
        ScanKeyInit(&key[0], Anum_pg_description_objoid, BTEqualStrategyNumber, F_OIDEQ,
            ObjectIdGetDatum(RelationGetRelid(rel)));
        ScanKeyInit(&key[1], Anum_pg_description_classoid, BTEqualStrategyNumber, F_OIDEQ, RelationRelationId);
        ScanKeyInit(&key[2], Anum_pg_description_objsubid, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(i));

        scan = systable_beginscan(desc_rel, DescriptionObjIndexId, true, NULL, 3, key);

        while (HeapTupleIsValid(desc_tuple = systable_getnext(scan))) {
            Datum values[Natts_pg_description] = { 0 };
            bool nulls[Natts_pg_description] = { 0 };
            bool replaces[Natts_pg_description] = { 0 };
            HeapTuple new_desc_tuple;

            desc_form = (Form_pg_description)GETSTRUCT(desc_tuple);

            values[Anum_pg_description_objsubid - 1] = is_increase ? Int32GetDatum(desc_form->objsubid + 1) :
                Int32GetDatum(desc_form->objsubid - 1);
            replaces[Anum_pg_description_objsubid - 1] = true;

            new_desc_tuple = heap_modify_tuple(desc_tuple, RelationGetDescr(desc_rel), values, nulls, replaces);
            simple_heap_update(desc_rel, &new_desc_tuple->t_self, new_desc_tuple);
            CatalogUpdateIndexes(desc_rel, new_desc_tuple);

            heap_freetuple_ext(new_desc_tuple);
        }
        systable_endscan(scan);
    }

    heap_close(desc_rel, RowExclusiveLock);
}
 
/* 
 * update pg_attribute.
 * 1. add column with first or after col_name.
 * 2. modify column to first or after column.
 */
static void UpdatePgAttributeFirstAfter(Relation attr_rel, Oid attrelid, int startattnum, int endattnum,
    bool is_increase)
{
    ScanKeyData key[2];
    HeapTuple attr_tuple;
    SysScanDesc scan;
    Form_pg_attribute attr_form;

    for (int i = (is_increase ? endattnum : startattnum);
        (is_increase ? i >= startattnum : i <= endattnum); (is_increase ? i-- : i++)) {
        AttrNumber myattnum = (AttrNumber)i;
        ScanKeyInit(&key[0], Anum_pg_attribute_attrelid, BTEqualStrategyNumber, F_OIDEQ,
            ObjectIdGetDatum(attrelid));
        ScanKeyInit(&key[1], Anum_pg_attribute_attnum, BTEqualStrategyNumber, F_INT2EQ, Int16GetDatum(myattnum));

        scan = systable_beginscan(attr_rel, AttributeRelidNumIndexId, true, NULL, 2, key);

        /* only one */
        while (HeapTupleIsValid(attr_tuple = systable_getnext(scan))) {
            Datum values[Natts_pg_attribute] = { 0 };
            bool nulls[Natts_pg_attribute] = { 0 };
            bool replaces[Natts_pg_attribute] = { 0 };
            errno_t rc = 0;
            HeapTuple new_attr_tuple;
            char newattname[NAMEDATALEN];

            attr_form = (Form_pg_attribute)GETSTRUCT(attr_tuple);

            // update pg_attribute_attnum
            if (is_increase) {
                values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(attr_form->attnum + 1);
                replaces[Anum_pg_attribute_attnum - 1] = true;
            } else {
                values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(attr_form->attnum - 1);
                replaces[Anum_pg_attribute_attnum - 1] = true;
            }

            // if exists dropped column, update pg_attribute_attname of dropped column
            if (attr_form->attisdropped) {
                if (is_increase) {
                    rc = snprintf_s(newattname, sizeof(newattname),
                        sizeof(newattname) - 1, "........pg.dropped.%d........", attr_form->attnum + 1);
                    securec_check_ss(rc, "\0", "\0");
                } else {
                    rc = snprintf_s(newattname, sizeof(newattname),
                        sizeof(newattname) - 1, "........pg.dropped.%d........", attr_form->attnum - 1);
                    securec_check_ss(rc, "\0", "\0");
                }

                values[Anum_pg_attribute_attname - 1] = NameGetDatum(newattname);
                replaces[Anum_pg_attribute_attname - 1] = true;
            }

            new_attr_tuple = heap_modify_tuple(attr_tuple, RelationGetDescr(attr_rel), values, nulls, replaces);
            simple_heap_update(attr_rel, &new_attr_tuple->t_self, new_attr_tuple);
            CatalogUpdateIndexes(attr_rel, new_attr_tuple);

            heap_freetuple_ext(new_attr_tuple);
        }
        systable_endscan(scan);
    }
}

/*
 * update pg_index.
 * 1. add column with first or after col_name.
 * 2. modify column to first or after column.
 */
static void UpdatePgIndexFirstAfter(Relation rel, int startattnum, int endattnum, bool is_increase)
{
    Relation index_rel;
    HeapTuple index_tuple;
    ScanKeyData key;
    SysScanDesc scan;
    Form_pg_index index_form;
    int curattnum = is_increase ? endattnum + 1 : startattnum - 1;
    int newattnum = is_increase ? startattnum : endattnum;

    /* Prepare to scan pg_index for entries having indrelid = this rel. */
    ScanKeyInit(&key, Anum_pg_index_indrelid, BTEqualStrategyNumber, F_OIDEQ,
        ObjectIdGetDatum(RelationGetRelid(rel)));
    index_rel = heap_open(IndexRelationId, RowExclusiveLock);
    scan = systable_beginscan(index_rel, IndexIndrelidIndexId, true, NULL, 1, &key);

    while (HeapTupleIsValid(index_tuple = systable_getnext(scan))) {
        int numatts;
        bool is_null = false;
        Datum values[Natts_pg_index] = { 0 };
        bool nulls[Natts_pg_index] = { 0 };
        bool replaces[Natts_pg_index] = { 0 };
        int2vector *indkey = NULL;
        int2vector *new_indkey = NULL;
        HeapTuple new_index_tuple;

        index_form = (Form_pg_index)GETSTRUCT(index_tuple);
        numatts = index_form->indnatts;

        // update pg_index_indkey
        Datum indkey_datum = SysCacheGetAttr(INDEXRELID, index_tuple, Anum_pg_index_indkey, &is_null);
        AssertEreport(!is_null, MOD_OPT, "");
        indkey = (int2vector *)DatumGetPointer(indkey_datum);
        Assert(indkey->dim1 == numatts);
        int2 *indkey_values = (int2 *)palloc0(numatts * sizeof(int2));
        for (int i = 0; i < numatts; i++) {
            if (indkey->values[i] >= startattnum && indkey->values[i] <= endattnum) {
                indkey_values[i] = is_increase ? (indkey->values[i] + 1) : (indkey->values[i] - 1);
            } else if (indkey->values[i] == curattnum) {
                indkey_values[i] = newattnum;
            } else {
                indkey_values[i] = indkey->values[i];
            }
        }
        new_indkey = buildint2vector(indkey_values, numatts);
        values[Anum_pg_index_indkey - 1] = PointerGetDatum(new_indkey);
        replaces[Anum_pg_index_indkey - 1] = true;

        // udpate pg_index_indexprs
        if (!heap_attisnull(index_tuple, Anum_pg_index_indexprs, NULL)) {
            Datum exprs_datum;
            List *indexprs = NIL;
            List *new_indexprs = NIL;
            char* exprs_string = NULL;

            exprs_datum = SysCacheGetAttr(INDEXRELID, index_tuple, Anum_pg_index_indexprs, &is_null);
            AssertEreport(!is_null, MOD_OPT, "");
            exprs_string = TextDatumGetCString(exprs_datum);
            indexprs = (List *)stringToNode(exprs_string);

            new_indexprs = (List *)UpdateVarattnoAfterAddColumn((Node *)indexprs,
                startattnum, endattnum, is_increase);
            exprs_string = nodeToString(new_indexprs);
            values[Anum_pg_index_indexprs - 1] = CStringGetTextDatum(exprs_string);
            replaces[Anum_pg_index_indexprs - 1] = true;
            pfree_ext(exprs_string);
        }

        // update pg_index_indpred
        if (!heap_attisnull(index_tuple, Anum_pg_index_indpred, NULL)) {
            Datum pred_datum;
            List *indpred = NIL;
            List *new_indpred = NIL;
            char *pred_string = NULL;

            pred_datum = SysCacheGetAttr(INDEXRELID, index_tuple, Anum_pg_index_indpred, &is_null);
            AssertEreport(!is_null, MOD_OPT, "");
            pred_string = TextDatumGetCString(pred_datum);
            indpred = (List *)stringToNode(pred_string);

            new_indpred = (List *)UpdateVarattnoAfterAddColumn((Node *)indpred,
                startattnum, endattnum, is_increase);
            pred_string = nodeToString(new_indpred);
            values[Anum_pg_index_indpred - 1] = CStringGetTextDatum(pred_string);
            replaces[Anum_pg_index_indpred - 1] = true;
            pfree_ext(pred_string);
        }

        new_index_tuple = heap_modify_tuple(index_tuple, RelationGetDescr(index_rel), values, nulls, replaces);
        simple_heap_update(index_rel, &new_index_tuple->t_self, new_index_tuple);
        CatalogUpdateIndexes(index_rel, new_index_tuple);

        pfree_ext(new_indkey);
        pfree_ext(indkey_values);
        heap_freetuple_ext(new_index_tuple);
    }

    systable_endscan(scan);
    heap_close(index_rel, RowExclusiveLock);
}

/*
 * update pg_constraint.
 * 1. add column with first or after col_name.
 * 2. modify column to first or after column.
 */
static void UpdatePgConstraintFirstAfter(Relation rel, int startattnum, int endattnum, bool is_increase)
{
    ScanKeyData key;
    HeapTuple con_tuple;
    Relation con_rel;
    SysScanDesc scan;
    int curattnum = is_increase ? endattnum + 1 : startattnum - 1;
    int newattnum = is_increase ? startattnum : endattnum;

    ScanKeyInit(&key, Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ,
        ObjectIdGetDatum(RelationGetRelid(rel)));
    con_rel = heap_open(ConstraintRelationId, RowExclusiveLock);
    scan = systable_beginscan(con_rel, ConstraintRelidIndexId, true, NULL, 1, &key);

    while (HeapTupleIsValid(con_tuple = systable_getnext(scan))) {
        bool is_null = false;
        ArrayType *conkey_array = NULL;
        ArrayType *conincluding_array = NULL;
        Datum values[Natts_pg_constraint] = { 0 };
        bool nulls[Natts_pg_constraint] = { 0 };
        bool replaces[Natts_pg_constraint] = { 0 };
        HeapTuple new_con_tuple;

        // update pg_constraint_conkey
        Datum conkeyDatum = SysCacheGetAttr(CONSTROID, con_tuple, Anum_pg_constraint_conkey, &is_null);
        if (!is_null) {
            ArrayType* con_key_arr = DatumGetArrayTypeP(conkeyDatum);
            int con_key_num = ARR_DIMS(con_key_arr)[0];
            int16 *con_key_attnums = (int16 *)ARR_DATA_PTR(con_key_arr);
            Datum *conkey = (Datum *)palloc(con_key_num * sizeof(Datum));

            for (int i = 0; i < con_key_num; i++) {
                if (con_key_attnums[i] >= startattnum && con_key_attnums[i] <= endattnum) {
                    con_key_attnums[i] = is_increase ? (con_key_attnums[i] + 1) : (con_key_attnums[i] - 1);
                } else if (con_key_attnums[i] == curattnum) {
                    con_key_attnums[i] = newattnum;
                }
                conkey[i] = Int16GetDatum(con_key_attnums[i]);
            }
            conkey_array = construct_array(conkey, con_key_num, INT2OID, 2, true, 's');
            values[Anum_pg_constraint_conkey - 1] = PointerGetDatum(conkey_array);
            replaces[Anum_pg_constraint_conkey - 1] = true;
        }

        // update pg_constraint_conincluding
        Datum con_including_datum = SysCacheGetAttr(CONSTROID, con_tuple, Anum_pg_constraint_conincluding, &is_null);
        if (!is_null) {
            ArrayType* con_including_arr = DatumGetArrayTypeP(con_including_datum);
            int con_including_num = ARR_DIMS(con_including_arr)[0];
            int16* con_including_attnums = (int16 *)ARR_DATA_PTR(con_including_arr);
            Datum* conincluding = (Datum *)palloc(con_including_num * sizeof(Datum));

            for (int i = 0; i < con_including_num; i++) {
                if (con_including_attnums[i] >= startattnum && con_including_attnums[i] <= endattnum) {
                    con_including_attnums[i] = is_increase ?
                        (con_including_attnums[i] + 1) : (con_including_attnums[i] - 1);
                } else if (con_including_attnums[i] == curattnum) {
                    con_including_attnums[i] = newattnum;
                }
                conincluding[i] = Int16GetDatum(con_including_attnums[i]);
            }
            conincluding_array = construct_array(conincluding, con_including_num, INT2OID, 2, true, 's');
            values[Anum_pg_constraint_conincluding - 1] = PointerGetDatum(conincluding_array);
            replaces[Anum_pg_constraint_conincluding - 1] = true;
        }

        // update pg_constraint_conbin
        Datum conbin_datum = SysCacheGetAttr(CONSTROID, con_tuple, Anum_pg_constraint_conbin, &is_null);
        if (!is_null) {
            char *conbin_string = NULL;
            Node *conbin = NULL;
            Node *new_conbin = NULL;

            conbin_string = TextDatumGetCString(conbin_datum);
            conbin = (Node*)stringToNode(conbin_string);

            new_conbin = UpdateVarattnoAfterAddColumn(conbin, startattnum, endattnum, is_increase);
            conbin_string = nodeToString(new_conbin);
            values[Anum_pg_constraint_conbin - 1] = CStringGetTextDatum(conbin_string);
            replaces[Anum_pg_constraint_conbin - 1] = true;
            pfree_ext(conbin_string);
        }

        new_con_tuple = heap_modify_tuple(con_tuple, RelationGetDescr(con_rel), values, nulls, replaces);
        simple_heap_update(con_rel, &new_con_tuple->t_self, new_con_tuple);
        CatalogUpdateIndexes(con_rel, new_con_tuple);

        pfree_ext(conkey_array);
        pfree_ext(conincluding_array);
        heap_freetuple_ext(new_con_tuple);
    }

    systable_endscan(scan);
    heap_close(con_rel, RowExclusiveLock);
}

/*
 * update pg_constraint confkey.
 * 1. add column with first or after col_name.
 * 2. modify column to first or after column.
 */
static void UpdatePgConstraintConfkeyFirstAfter(Relation rel, int startattnum, int endattnum,
    bool is_increase)
{
    ScanKeyData key;
    HeapTuple con_tuple;
    Relation con_rel;
    SysScanDesc scan;
    int curattnum = is_increase ? endattnum + 1 : startattnum - 1;
    int newattnum = is_increase ? startattnum : endattnum;

    ScanKeyInit(&key, Anum_pg_constraint_confrelid, BTEqualStrategyNumber, F_OIDEQ,
        ObjectIdGetDatum(RelationGetRelid(rel)));
    con_rel = heap_open(ConstraintRelationId, RowExclusiveLock);
    scan = systable_beginscan(con_rel, InvalidOid, false, NULL, 1, &key);

    while (HeapTupleIsValid(con_tuple = systable_getnext(scan))) {
        bool is_null =  false;
        ArrayType* confkey_array = NULL;
        Datum values[Natts_pg_constraint] = { 0 };
        bool nulls[Natts_pg_constraint] = { 0 };
        bool replaces[Natts_pg_constraint] = { 0 };
        HeapTuple new_con_tuple;

        // update pg_constraint_confkey
        Datum confkey_datum = SysCacheGetAttr(CONSTROID, con_tuple, Anum_pg_constraint_confkey, &is_null);
        if (!is_null) {
            ArrayType* conf_key_rr = DatumGetArrayTypeP(confkey_datum);
            int confkey_num = ARR_DIMS(conf_key_rr)[0];
            int16 *confkey_attnums = (int16 *)ARR_DATA_PTR(conf_key_rr);
            Datum *confkey = (Datum *)palloc(confkey_num * sizeof(Datum));

            for (int i = 0; i < confkey_num; i++) {
                if (confkey_attnums[i] >= startattnum && confkey_attnums[i] <= endattnum) {
                    confkey_attnums[i] = is_increase ? (confkey_attnums[i] + 1) : (confkey_attnums[i] - 1);
                } else if (confkey_attnums[i] == curattnum) {
                    confkey_attnums[i] = newattnum;
                }
                confkey[i] = Int16GetDatum(confkey_attnums[i]);
            }
            confkey_array = construct_array(confkey, confkey_num, INT2OID, 2, true, 's');
            values[Anum_pg_constraint_confkey - 1] = PointerGetDatum(confkey_array);
            replaces[Anum_pg_constraint_confkey - 1] = true;
        }

        new_con_tuple = heap_modify_tuple(con_tuple, RelationGetDescr(con_rel), values, nulls, replaces);
        simple_heap_update(con_rel, &new_con_tuple->t_self, new_con_tuple);
        CatalogUpdateIndexes(con_rel, new_con_tuple);

        pfree_ext(confkey_array);
        heap_freetuple_ext(new_con_tuple);
    }

    systable_endscan(scan);
    heap_close(con_rel, RowExclusiveLock);
}

/*
 * update generated column information for pg_attrdef.
 * 1. add column with first or after col_name.
 * 2. modify column to first or after column.
 */
static void UpdateGenerateColFirstAfter(Relation rel, int startattnum, int endattnum, bool is_increase)
{
    ScanKeyData key;
    HeapTuple def_tuple;
    Relation def_rel;
    SysScanDesc scan;

    def_rel = heap_open(AttrDefaultRelationId, RowExclusiveLock);
    ScanKeyInit(&key, Anum_pg_attrdef_adrelid, BTEqualStrategyNumber, F_OIDEQ,
            ObjectIdGetDatum(RelationGetRelid(rel)));
    scan = systable_beginscan(def_rel, AttrDefaultIndexId, true, NULL, 1, &key);
    while (HeapTupleIsValid(def_tuple = systable_getnext(scan))) {
        bool is_null = false;
        char generated_col = '\0';
        Datum values[Natts_pg_attrdef] = { 0 };
        bool nulls[Natts_pg_attrdef] = { 0 };
        bool replaces[Natts_pg_attrdef] = { 0 };
        HeapTuple new_def_tuple;

        Datum adgencol = fastgetattr(def_tuple, Anum_pg_attrdef_adgencol, def_rel->rd_att, &is_null);
        if (!is_null) {
            generated_col = DatumGetChar(adgencol);
        }

        // update pg_attrdef_adbin
        if (generated_col == ATTRIBUTE_GENERATED_STORED ||
            generated_col == ATTRIBUTE_GENERATED_PERSISTED) {
            Datum adbin_datum;
            Node *adbin = NULL;
            Node *new_adbin = NULL;
            char *adbin_string = NULL;

            adbin_datum = fastgetattr(def_tuple, Anum_pg_attrdef_adbin, def_rel->rd_att, &is_null);
            AssertEreport(!is_null, MOD_OPT, "");
            adbin_string = TextDatumGetCString(adbin_datum);
            adbin = (Node *)stringToNode(adbin_string);

            new_adbin = UpdateVarattnoAfterAddColumn(adbin, startattnum, endattnum, is_increase);
            adbin_string = nodeToString(new_adbin);
            values[Anum_pg_attrdef_adbin - 1] = CStringGetTextDatum(adbin_string);
            replaces[Anum_pg_attrdef_adbin - 1] = true;
            pfree_ext(adbin_string);
        } else {
            continue;
        }

        new_def_tuple = heap_modify_tuple(def_tuple, RelationGetDescr(def_rel), values, nulls, replaces);
        simple_heap_update(def_rel, &new_def_tuple->t_self, new_def_tuple);
        CatalogUpdateIndexes(def_rel, new_def_tuple);

        heap_freetuple_ext(new_def_tuple);
    }
    systable_endscan(scan);
    heap_close(def_rel, RowExclusiveLock);
}


/*
 * update the exists index information.
 * 1. add column with first or after col_name.
 */
static void UpdateIndexFirstAfter(Relation rel)
{
    Relation pg_index_rel, table_index_rel;
    HeapTuple index_tuple;
    ScanKeyData key;
    SysScanDesc scan;
    MemoryContext oldcontext;

    /* Prepare to scan pg_index for entries having indrelid = this rel. */
    ScanKeyInit(&key, Anum_pg_index_indrelid, BTEqualStrategyNumber, F_OIDEQ,
        ObjectIdGetDatum(RelationGetRelid(rel)));
    pg_index_rel = heap_open(IndexRelationId, RowExclusiveLock);
    scan = systable_beginscan(pg_index_rel, IndexIndrelidIndexId, true, NULL, 1, &key);

    while (HeapTupleIsValid(index_tuple = systable_getnext(scan))) {
        Form_pg_index index_form = (Form_pg_index)GETSTRUCT(index_tuple);

        table_index_rel = index_open(index_form->indexrelid, RowExclusiveLock);

        oldcontext = MemoryContextSwitchTo(LocalMyDBCacheMemCxt());
        pfree_ext(table_index_rel->rd_indextuple);
        table_index_rel->rd_indextuple = heap_copytuple(index_tuple);
        table_index_rel->rd_index = (Form_pg_index)GETSTRUCT(table_index_rel->rd_indextuple);
        (void)MemoryContextSwitchTo(oldcontext);

        index_close(table_index_rel, RowExclusiveLock);
    }
    systable_endscan(scan);
    heap_close(pg_index_rel, RowExclusiveLock);
}

/*
 * update pg_attrdef.
 * 1. add column with first or after col_name.
 * 2. modify column to first or after column.
 */
static void UpdatePgAttrdefFirstAfter(Relation rel, int startattnum, int endattnum, bool is_increase)
{
    ScanKeyData key[2];
    HeapTuple def_tuple;
    Relation def_rel;
    SysScanDesc scan;
    Form_pg_attrdef def_form;

    def_rel = heap_open(AttrDefaultRelationId, RowExclusiveLock);

    for (int i = (is_increase ? endattnum : startattnum);
        (is_increase ? i >= startattnum : i <= endattnum); (is_increase ? i-- : i++)) {
        AttrNumber myattnum = (AttrNumber)i;
        ScanKeyInit(&key[0], Anum_pg_attrdef_adrelid, BTEqualStrategyNumber, F_OIDEQ,
            ObjectIdGetDatum(RelationGetRelid(rel)));
        ScanKeyInit(&key[1], Anum_pg_attrdef_adnum, BTEqualStrategyNumber, F_INT2EQ, Int16GetDatum(myattnum));

        scan = systable_beginscan(def_rel, AttrDefaultIndexId, true, NULL, 2, key);

        // only one
        while (HeapTupleIsValid(def_tuple = systable_getnext(scan))) {
            Datum values[Natts_pg_attrdef] = { 0 };
            bool nulls[Natts_pg_attrdef] = { 0 };
            bool replaces[Natts_pg_attrdef] = { 0 };
            HeapTuple new_def_tuple;

            def_form = (Form_pg_attrdef)GETSTRUCT(def_tuple);

            values[Anum_pg_attrdef_adnum - 1] = is_increase ? Int16GetDatum(def_form->adnum + 1) :
                Int16GetDatum(def_form->adnum - 1);
            replaces[Anum_pg_attrdef_adnum - 1] = true;

            new_def_tuple = heap_modify_tuple(def_tuple, RelationGetDescr(def_rel), values, nulls, replaces);
            simple_heap_update(def_rel, &new_def_tuple->t_self, new_def_tuple);
            CatalogUpdateIndexes(def_rel, new_def_tuple);

            heap_freetuple_ext(new_def_tuple);
        }
        systable_endscan(scan);
    }
    heap_close(def_rel, RowExclusiveLock);
}

/*
 * update pg_depend.
 * 1. add column with first or after col_name.
 * 2. modify column to first or after column.
 */
static void UpdatePgDependFirstAfter(Relation rel, int startattnum, int endattnum, bool is_increase)
{
    ScanKeyData key[2];
    HeapTuple dep_tuple;
    Relation dep_rel;
    SysScanDesc scan;
    Form_pg_depend dep_form;

    int curattnum = is_increase ? endattnum + 1 : startattnum - 1;
    int newattnum = is_increase ? startattnum : endattnum;

    dep_rel = heap_open(DependRelationId, RowExclusiveLock);

    // find pg_depend based on refobjid and refobjsubid, then update refobjsubid
    ScanKeyInit(&key[0], Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ,
        ObjectIdGetDatum(RelationGetRelid(rel)));
    ScanKeyInit(&key[1], Anum_pg_depend_refobjsubid, BTGreaterStrategyNumber, F_INT4GT, Int32GetDatum(0));

    scan = systable_beginscan(dep_rel, DependReferenceIndexId, true, NULL, 2, key);
    while (HeapTupleIsValid(dep_tuple = systable_getnext(scan))) {
        Datum values[Natts_pg_depend] = { 0 };
        bool nulls[Natts_pg_depend] = { 0 };
        bool replaces[Natts_pg_depend] = { 0 };
        HeapTuple new_dep_tuple;

        dep_form  = (Form_pg_depend)GETSTRUCT(dep_tuple);

        if (dep_form->refobjsubid >= startattnum && dep_form->refobjsubid <= endattnum) {
            values[Anum_pg_depend_refobjsubid - 1] = is_increase ?
                Int32GetDatum(dep_form->refobjsubid + 1) : Int32GetDatum(dep_form->refobjsubid - 1);
            replaces[Anum_pg_depend_refobjsubid - 1] = true;
        } else if (dep_form->refobjsubid == curattnum) {
            values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(newattnum);
            replaces[Anum_pg_depend_refobjsubid - 1] = true;
        }

        new_dep_tuple = heap_modify_tuple(dep_tuple, RelationGetDescr(dep_rel), values, nulls, replaces);
        simple_heap_update(dep_rel, &new_dep_tuple->t_self, new_dep_tuple);
        CatalogUpdateIndexes(dep_rel, new_dep_tuple);

        heap_freetuple_ext(new_dep_tuple);
    }
    systable_endscan(scan);
    heap_close(dep_rel, RowExclusiveLock);

    CommandCounterIncrement();

    dep_rel = heap_open(DependRelationId, RowExclusiveLock);

    // find pg_depend based on objid and objsubid, then update objsubid
    ScanKeyInit(&key[0], Anum_pg_depend_objid, BTEqualStrategyNumber, F_OIDEQ,
        ObjectIdGetDatum(RelationGetRelid(rel)));
    ScanKeyInit(&key[1], Anum_pg_depend_objsubid, BTGreaterStrategyNumber, F_INT4GT, Int32GetDatum(0));

    scan = systable_beginscan(dep_rel, DependDependerIndexId, true, NULL, 2, key);
    while (HeapTupleIsValid(dep_tuple = systable_getnext(scan))) {
        Datum values[Natts_pg_depend] = { 0 };
        bool nulls[Natts_pg_depend] = { 0 };
        bool replaces[Natts_pg_depend] = { 0 };
        HeapTuple new_dep_tuple;

        dep_form  = (Form_pg_depend)GETSTRUCT(dep_tuple);

        /* the situation has been updated in sqlcmd_update_depend_refobjsubid_first_after */
        if (dep_form->refobjsubid == -1 && dep_form->refobjid == RelationGetRelid(rel)) {
            continue;
        }

        if (dep_form->objsubid >= startattnum && dep_form->objsubid <= endattnum) {
            values[Anum_pg_depend_objsubid - 1] = is_increase ? Int32GetDatum(dep_form->objsubid + 1) :
                Int32GetDatum(dep_form->objsubid - 1);
            replaces[Anum_pg_depend_objsubid - 1] = true;
        } else if (dep_form->objsubid == curattnum) {
            values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(newattnum);
            replaces[Anum_pg_depend_objsubid - 1] = true;
        }

        new_dep_tuple = heap_modify_tuple(dep_tuple, RelationGetDescr(dep_rel), values, nulls, replaces);
        simple_heap_update(dep_rel, &new_dep_tuple->t_self, new_dep_tuple);
        CatalogUpdateIndexes(dep_rel, new_dep_tuple);

        heap_freetuple_ext(new_dep_tuple);
    }
    systable_endscan(scan);
    heap_close(dep_rel, RowExclusiveLock);
}

/*
 * update pg_partition.
 * 1. add column with first or after col_name.
 * 2. modify column to first or after column.
 */
static void UpdatePgPartitionFirstAfter(Relation rel, int startattnum, int endattnum, bool is_increase,
    bool is_modified, bool *has_partition)
{
    ScanKeyData key;
    HeapTuple par_tuple;
    Relation par_rel;
    SysScanDesc scan;
    int curattnum = is_increase ? endattnum + 1 : startattnum - 1;
    int newattnum = is_increase ? startattnum : endattnum;

    par_rel = heap_open(PartitionRelationId, RowExclusiveLock);

    ScanKeyInit(&key, Anum_pg_partition_parentid, BTEqualStrategyNumber, F_OIDEQ,
        ObjectIdGetDatum(RelationGetRelid(rel)));

    scan = systable_beginscan(par_rel, PartitionParentOidIndexId, true, NULL, 1, &key);
    while (HeapTupleIsValid(par_tuple = systable_getnext(scan))) {
        bool is_null = false;

        // update pg_partition_partkey
        Datum partkey_datum = SysCacheGetAttr(PARTRELID, par_tuple, Anum_pg_partition_partkey, &is_null);
        if (!is_null) {
            Datum values[Natts_pg_partition] = { 0 };
            bool nulls[Natts_pg_partition] = { 0 };
            bool replaces[Natts_pg_partition] = { 0 };
            int2vector *partkey = NULL;
            int2vector *new_partKey = NULL;
            HeapTuple new_par_tuple;

            partkey = (int2vector *)DatumGetPointer(partkey_datum);
            int2 *partkey_values = (int2 *)palloc0(partkey->dim1 * sizeof(int2));
            
            for (int i = 0; i < partkey->dim1; i++) {
                if (partkey->values[i] >= startattnum && partkey->values[i] <= endattnum) {
                    partkey_values[i] = is_increase ? (partkey->values[i] + 1) : (partkey->values[i] - 1);
                } else if (partkey->values[i] == curattnum) {
                    if (is_modified) {
                        if (has_partition != NULL) {
                            *has_partition = true;
                        }
                        partkey_values[i] = 0;
                    } else {
                        partkey_values[i] = newattnum;
                    }
                } else {
                    partkey_values[i] = partkey->values[i];
                }
            }
            new_partKey = buildint2vector(partkey_values, partkey->dim1);
            values[Anum_pg_partition_partkey - 1] = PointerGetDatum(new_partKey);
            replaces[Anum_pg_partition_partkey - 1] = true;

            new_par_tuple = heap_modify_tuple(par_tuple, RelationGetDescr(par_rel), values, nulls, replaces);
            simple_heap_update(par_rel, &new_par_tuple->t_self, new_par_tuple);
            CatalogUpdateIndexes(par_rel, new_par_tuple);

            pfree_ext(new_partKey);
            pfree_ext(partkey_values);
            heap_freetuple_ext(new_par_tuple);
        }
    }
    systable_endscan(scan);
    heap_close(par_rel, RowExclusiveLock);
}


static ViewInfoForAdd *GetViewInfoFirstAfter(const char *rel_name, Oid objid, bool keep_star)
{
    ScanKeyData entry;
    ViewInfoForAdd *info = NULL;

    ScanKeyInit(&entry, ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(objid));

    Relation rewrite_rel = heap_open(RewriteRelationId, AccessShareLock);

    SysScanDesc rewrite_scan = systable_beginscan(rewrite_rel, RewriteOidIndexId, true, NULL, 1, &entry);

    HeapTuple rewrite_tup = systable_getnext(rewrite_scan);

    if (HeapTupleIsValid(rewrite_tup)) {
        Form_pg_rewrite rewrite_form = (Form_pg_rewrite)GETSTRUCT(rewrite_tup);

        if (strcmp(NameStr(rewrite_form->rulename), ViewSelectRuleName) == 0) {
            bool is_null = false;

            Datum ev_actiom_datum = fastgetattr(rewrite_tup, Anum_pg_rewrite_ev_action, rewrite_rel->rd_att, &is_null);
            if (!is_null) {
                StringInfoData buf;

                initStringInfo(&buf);

                Relation ev_relation = heap_open(rewrite_form->ev_class, AccessShareLock);
                char *ev_action_string = TextDatumGetCString(ev_actiom_datum);
                List *ev_action = (List *)stringToNode(ev_action_string);
                Query* query = (Query*)linitial(ev_action);

                get_query_def(query,
                    &buf,
                    NIL,
                    RelationGetDescr(ev_relation),
                    0,
                    -1,
                    0,
#ifdef PGXC
                    false,
                    false,
                    NULL,
#endif /* PGXC */
                    false,
                    keep_star);
                appendStringInfo(&buf, ";");

                info = (ViewInfoForAdd *)palloc0(sizeof(ViewInfoForAdd));
                info->ev_class = rewrite_form->ev_class;
                info->query_string = pstrdup(buf.data);

                heap_close(ev_relation, AccessShareLock);

                FreeStringInfo(&buf);
                pfree_ext(ev_action_string);
            }
        } else {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("Un-supported feature"),
                    errdetail("rule %s depend on %s, alter table %s add ... first|after colname is not supported",
                    NameStr(rewrite_form->rulename), rel_name, rel_name)));
        }
    }
    systable_endscan(rewrite_scan);
    heap_close(rewrite_rel, AccessShareLock);

    return info;
}

/*
 * get sql for view.
 * 1. add column with first or after col_name.
 * 2. modify column to first or after column.
 */
static List *CheckPgRewriteFirstAfter(Relation rel)
{
    ScanKeyData key[2];
    HeapTuple dep_tuple;
    SysScanDesc dep_scan;
    Form_pg_depend dep_form;
    Oid pre_objid = 0;
    List *query_str = NIL;

    Relation dep_rel = heap_open(DependRelationId, AccessShareLock);

    ScanKeyInit(
        &key[0], Anum_pg_depend_refclassid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationRelationId));
    ScanKeyInit(
        &key[1], Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(rel)));

    dep_scan = systable_beginscan(dep_rel, DependReferenceIndexId, true, NULL, 2, key);

    while (HeapTupleIsValid(dep_tuple = systable_getnext(dep_scan))) {
        dep_form = (Form_pg_depend)GETSTRUCT(dep_tuple);

        if (dep_form->classid == RewriteRelationId) {
            ListCell* viewinfo = NULL;
            bool is_exist = false;

            if (dep_form->objid == pre_objid) {
                continue;
            }

            pre_objid = dep_form->objid;

            ViewInfoForAdd *info = GetViewInfoFirstAfter(NameStr(rel->rd_rel->relname), dep_form->objid, false);

            foreach (viewinfo, query_str) {
                ViewInfoForAdd *oldInfo = (ViewInfoForAdd *)lfirst(viewinfo);
                if (info != NULL && oldInfo->ev_class == info->ev_class) {
                    is_exist = true;
                    break;
                }
            }

            if (info != NULL && !is_exist) {
                query_str = lappend(query_str, info);
            }
        }
    }
    systable_endscan(dep_scan);
    heap_close(dep_rel, AccessShareLock);
    return query_str;
}

static bool check_changed_tle_walker(Node* node, ViewQueryCheck_context* context)
{
    if (node == NULL)
        return false;
    if (IsA(node, Var)) {
        Var* var = (Var*)node;
        Oid relid = InvalidOid;
        RangeTblEntry* rte = rt_fetch(var->varno, context->query->rtable);
        if (rte && rte->rtekind == RTE_RELATION) {
            relid = rte->relid;
        } else if (rte && rte->alias == NULL && rte->rtekind == RTE_JOIN && rte->joinaliasvars != NIL) {
            Var* aliasvar = (Var*)list_nth(rte->joinaliasvars, var->varattno - 1);
            RangeTblEntry* alias_rte = rt_fetch(aliasvar->varno, context->query->rtable);
            Assert(alias_rte->rtekind == RTE_RELATION);
            relid = alias_rte->relid;
        }
        if (relid == context->relid &&
            var->varattno == context->attnum) {
            return !(var->vartype == context->attForm->atttypid &&
                   var->vartypmod == context->attForm->atttypmod &&
                   var->varcollid == context->attForm->attcollation);
        } else {
            return false;
        }
    }
    return expression_tree_walker(node, (bool (*)())check_changed_tle_walker, context);
}

static void CheckPgAttribute(Oid obj_oid, char* attName, Form_pg_attribute new_attribute)
{
    if (!OidIsValid(obj_oid) || attName == NULL) {
        return;
    }
    const int keyNum = 2;
    Relation rel;
    ScanKeyData key[keyNum];
    SysScanDesc scan;
    HeapTuple tuple;
    HeapTuple new_dep_tuple;
    Form_pg_attribute attForm;
    rel = heap_open(AttributeRelationId, RowExclusiveLock);
    ScanKeyInit(&key[0], Anum_pg_attribute_attrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(obj_oid));
    ScanKeyInit(&key[1], Anum_pg_attribute_attname, BTEqualStrategyNumber, F_NAMEEQ, NameGetDatum(attName));
    scan = systable_beginscan(rel, AttributeRelidNameIndexId, true, SnapshotSelf, keyNum, &key[0]);
    tuple = systable_getnext(scan);
    if (!HeapTupleIsValid(tuple)) {
        systable_endscan(scan);
        heap_close(rel, RowExclusiveLock);
        elog(ERROR, "catalog lookup failed for column %s of relation %u", attName, obj_oid);
    }
    attForm = (Form_pg_attribute)GETSTRUCT(tuple);
    Datum values[Natts_pg_attribute] = { 0 };
    bool nulls[Natts_pg_attribute] = { 0 };
    bool replaces[Natts_pg_attribute] = { 0 };
    values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attribute->atttypid);
    values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attribute->attlen);
    values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(new_attribute->atttypmod);
    values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(new_attribute->attbyval);
    values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(new_attribute->attstorage);
    values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attribute->attcollation);
    replaces[Anum_pg_attribute_atttypid - 1] = true;
    replaces[Anum_pg_attribute_attlen - 1] = true;
    replaces[Anum_pg_attribute_atttypmod - 1] = true;
    replaces[Anum_pg_attribute_attbyval - 1] = true;
    replaces[Anum_pg_attribute_attstorage - 1] = true;
    replaces[Anum_pg_attribute_attcollation - 1] = true;
    new_dep_tuple = heap_modify_tuple(tuple, RelationGetDescr(rel), values, nulls, replaces);
    simple_heap_update(rel, &new_dep_tuple->t_self, new_dep_tuple);
    CatalogUpdateIndexes(rel, new_dep_tuple);
    heap_freetuple_ext(new_dep_tuple);
    CommandCounterIncrement();
    systable_endscan(scan);
    heap_close(rel, RowExclusiveLock);
}

static void UpdatePgAttributeForView(TargetEntry* old_tle, TargetEntry* new_tle, Oid view_oid)
{
    Node* old_node = (Node*)old_tle->expr;
    Node* new_node = (Node*)new_tle->expr;
    Assert(nodeTag(old_node) == nodeTag(new_node));
    char* col_name = old_tle->resname;
    Oid old_type = exprType(old_node);
    Oid new_type = exprType(new_node);
    int32 old_typmod = exprTypmod(old_node);
    int32 new_typmod = exprTypmod(new_node);
    if (old_type != new_type || old_typmod != new_typmod) {
        /* get from pg_type */
        HeapTuple tp = NULL;
        Form_pg_attribute att_form = (Form_pg_attribute)palloc0(sizeof(FormData_pg_attribute));
        tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(new_type));
        if (HeapTupleIsValid(tp)) {
            Form_pg_type typtup = (Form_pg_type)GETSTRUCT(tp);
            att_form->atttypid = new_type;
            att_form->atttypmod = new_typmod;
            att_form->attlen = typtup->typlen;
            att_form->attbyval = typtup->typbyval;
            att_form->attstorage = typtup->typstorage;
            att_form->attcollation = typtup->typcollation;
            ReleaseSysCache(tp);
        } else {
            elog(ERROR, "Cannot find the type with oid %u.", new_type);
        }
        CheckPgAttribute(view_oid, col_name, att_form);
        pfree_ext(att_form);
    }
}

List* GetOriginalViewQuery(Oid rw_oid)
{
    List *evAction = NIL;
    ScanKeyData entry;
    ScanKeyInit(&entry, ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(rw_oid));
    Relation rewrite_rel = heap_open(RewriteRelationId, RowExclusiveLock);
    SysScanDesc rewrite_scan = systable_beginscan(rewrite_rel, RewriteOidIndexId, true, NULL, 1, &entry);
    HeapTuple rewrite_tup = systable_getnext(rewrite_scan);
    if (!HeapTupleIsValid(rewrite_tup)) {
        elog(ERROR, "Cannot find the rewrite rule with oid %u.", rw_oid);
    }
    Form_pg_rewrite rewrite_form = (Form_pg_rewrite)GETSTRUCT(rewrite_tup);
    if (strcmp(NameStr(rewrite_form->rulename), "_RETURN") != 0) {
        elog(ERROR, "The rewrite rule with oid %u has unexpected name %s.", rw_oid, NameStr(rewrite_form->rulename));
    }
    bool is_null = false;
    Datum evActiomDatum = fastgetattr(rewrite_tup, Anum_pg_rewrite_ev_action, rewrite_rel->rd_att, &is_null);
    if (!is_null) {
        char *evActionString = TextDatumGetCString(evActiomDatum);
        evAction = (List *)stringToNode(evActionString);
    } else {
        elog(ERROR, "Cannot find the rewrite rule with oid %u.", rw_oid);
    }
    systable_endscan(rewrite_scan);
    heap_close(rewrite_rel, RowExclusiveLock);
    return evAction;
}

List* GetRefreshedViewQuery(Oid view_oid, Oid rw_oid)
{
    List* evAction = NIL;
    Query* query = NULL;
    HeapTuple tup = SearchSysCache1(RELOID, ObjectIdGetDatum(view_oid));
    if (!HeapTupleIsValid(tup)) {
        elog(ERROR, "Cannot find the view with oid %u.", view_oid);
    }
    Form_pg_class reltup = (Form_pg_class)GETSTRUCT(tup);
    char* view_def = GetCreateViewCommand(NameStr(reltup->relname), tup, reltup, rw_oid, view_oid, false);
    ReleaseSysCache(tup);

    List* raw_parsetree_list = raw_parser(view_def);
    Node* stmtNode = (Node*)linitial(raw_parsetree_list);
    Assert((IsA(stmtNode, ViewStmt) || IsA(stmtNode, CreateTableAsStmt)));
    if (IsA(stmtNode, ViewStmt)) {
        ViewStmt* stmt = (ViewStmt*)stmtNode;
        if (!IsA(stmt->query, Query)) {
            query = parse_analyze(stmt->query, view_def, NULL, 0);
        } else {
            query = (Query*)stmt->query;
        }
    } else if (IsA(stmtNode, CreateTableAsStmt)) {
        CreateTableAsStmt* stmt = (CreateTableAsStmt*)stmtNode;
        if (!IsA(stmt->query, Query)) {
            query = parse_analyze(stmt->query, view_def, NULL, 0);
        } else {
            query = (Query*)stmt->query;
        }
    }
    evAction = lappend(evAction, query);
    pfree(view_def);
    list_free(raw_parsetree_list);
    return evAction;
}

void UpdatePgrewriteForView(Oid rw_oid, List* evAction, List **query_str)
{
    List *new_query_str = NIL;
    ScanKeyData entry;
    ScanKeyInit(&entry, ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(rw_oid));
    Relation rewrite_rel = heap_open(RewriteRelationId, RowExclusiveLock);
    SysScanDesc rewrite_scan = systable_beginscan(rewrite_rel, RewriteOidIndexId, true, NULL, 1, &entry);
    HeapTuple rewrite_tup = systable_getnext(rewrite_scan);
    Form_pg_rewrite rewrite_form = (Form_pg_rewrite)GETSTRUCT(rewrite_tup);
    /* update pg_rewrite */
    char* actiontree = nodeToString((Node*)evAction);
    Datum values[Natts_pg_rewrite] = { 0 };
    bool nulls[Natts_pg_rewrite] = { 0 };
    bool replaces[Natts_pg_rewrite] = { 0 };
    HeapTuple new_dep_tuple;
    values[Anum_pg_rewrite_ev_action - 1] = CStringGetTextDatum(actiontree);
    replaces[Anum_pg_rewrite_ev_action - 1] = true;
    new_dep_tuple = heap_modify_tuple(rewrite_tup, RelationGetDescr(rewrite_rel), values, nulls, replaces);
    simple_heap_update(rewrite_rel, &new_dep_tuple->t_self, new_dep_tuple);
    CatalogUpdateIndexes(rewrite_rel, new_dep_tuple);
    CommandCounterIncrement();

    heap_freetuple_ext(new_dep_tuple);
    pfree_ext(actiontree);
    /* get new_query_str from pg_rewrite */
    if (query_str == NULL) {
        systable_endscan(rewrite_scan);
        heap_close(rewrite_rel, RowExclusiveLock);
        return;
    }
    Query* query = (Query*)linitial(evAction);
    StringInfoData buf;
    initStringInfo(&buf);
    Relation ev_relation = heap_open(rewrite_form->ev_class, AccessShareLock);
    get_query_def(query,
        &buf,
        NIL,
        RelationGetDescr(ev_relation),
        0,
        -1,
        0,
        false,
        false,
        NULL,
        false,
        false);
    appendStringInfo(&buf, ";");
    ViewInfoForAdd * info = static_cast<ViewInfoForAdd *>(palloc(sizeof(ViewInfoForAdd)));
    info->ev_class = rewrite_form->ev_class;
    info->query_string = pstrdup(buf.data);
    heap_close(ev_relation, AccessShareLock);
    FreeStringInfo(&buf);
    new_query_str = lappend(new_query_str, info);
    *query_str = new_query_str;
    systable_endscan(rewrite_scan);
    heap_close(rewrite_rel, RowExclusiveLock);
}

void UpdateAttrAndRewriteForView(Oid viewid, Oid rw_objid, List* originEvAction, Query* query, List **query_str)
{
    List* evAction = NIL;
    evAction = lappend(evAction, query);
    /*
     * update pg_rewrite with correct varattno and relation oid firstly,
     * so that GetRefreshedViewQuery which would scan pg_rewrite to get ev_action
     * can obtain right view_def.
     */
    UpdatePgrewriteForView(rw_objid, evAction, NULL);

    List* newEvAction = NIL;
    PG_TRY();
    {
        newEvAction = GetRefreshedViewQuery(viewid, rw_objid);
    }
    PG_CATCH();
    {
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_OBJECT),
                errmsg("The view %s is invalid, please make it valid before operation.",
                       get_rel_name(viewid)),
                    errhint("Please re-add missing table fields.")));
    }
    PG_END_TRY();

    Query* origin_query = (Query*)linitial(originEvAction);
    Query* freshed_query = (Query*)linitial(newEvAction);
    /*
     * check whether the querytree's targetEntry has changed,
     * and update the pg_attribute entry of the changed column if so
     */
    ListCell* lc1 = NULL;
    ListCell* lc2 = NULL;
    forboth (lc1, origin_query->targetList, lc2, freshed_query->targetList) {
        TargetEntry* tle = (TargetEntry*)lfirst(lc1);
        TargetEntry* tle2 = (TargetEntry*)lfirst(lc2);

        UpdatePgAttributeForView(tle, tle2, viewid);
    }

    freshed_query = UpdateRangeTableOfViewParse(viewid, freshed_query);

    /* update pg_rewrite with final ev_action */
    UpdatePgrewriteForView(rw_objid, list_make1(freshed_query), query_str);

    list_free_deep(newEvAction);
}

/*
 * create or replace view when the table has view.
 * 1. add column with first or after col_name.
 * 2. modify column to first or after column.
 */
void ReplaceViewQueryFirstAfter(List *query_str)
{
    if (query_str != NIL) {
        ListCell* viewinfo = NULL;

        foreach (viewinfo, query_str) {
            Query* query = NULL;
            List* parsetree_list = NULL;
            Node* parsetree = NULL;

            ViewInfoForAdd *info = (ViewInfoForAdd *)lfirst(viewinfo);
            parsetree_list = pg_parse_query(info->query_string);
            if (list_length(parsetree_list) != 1) {
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("this is not a view")));
            }

            parsetree = (Node *)linitial(parsetree_list);
            query = parse_analyze(parsetree, info->query_string, NULL, 0);
            StoreViewQuery(info->ev_class, query, true);
        }
    }
}

/*
 * update pg_trigger
 * 1. add column with first or after col_name.
 * 2. modify column to first or after column.
 */
static void UpdatePgTriggerFirstAfter(Relation rel, int startattnum, int endattnum, bool is_increase)
{
    if (!rel->rd_rel->relhastriggers) {
        return;
    }
    ScanKeyData key;
    HeapTuple tri_tuple;
    Relation tri_rel;
    SysScanDesc scan;
    int curattnum = is_increase ? endattnum + 1 : startattnum - 1;
    int newattnum = is_increase ? startattnum : endattnum;

    tri_rel = heap_open(TriggerRelationId, RowExclusiveLock);

    ScanKeyInit(&key, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber, F_OIDEQ,
        ObjectIdGetDatum(RelationGetRelid(rel)));

    scan = systable_beginscan(tri_rel, TriggerRelidNameIndexId, true, NULL, 1, &key);
    while (HeapTupleIsValid(tri_tuple = systable_getnext(scan))) {
        bool is_null = false;
        Datum values[Natts_pg_trigger] = { 0 };
        bool nulls[Natts_pg_trigger] = { 0 };
        bool replaces[Natts_pg_trigger] = { 0 };
        HeapTuple new_tri_tuple;
        int2vector *new_tgattr = NULL;
        int2 *tgattr_values = NULL;

        Datum tgattr_datum = fastgetattr(tri_tuple, Anum_pg_trigger_tgattr, tri_rel->rd_att, &is_null);
        if (!is_null) {
            int2vector *tgattr = (int2vector *)DatumGetPointer(tgattr_datum);
            int2 *tgattr_values = (int2 *)palloc0(tgattr->dim1 * sizeof(int2));
            for (int i = 0; i < tgattr->dim1; i++) {
                if (tgattr->values[i] >= startattnum && tgattr->values[i] <= endattnum) {
                    tgattr_values[i] = is_increase ? (tgattr->values[i] + 1) : (tgattr->values[i] - 1);
                } else if (tgattr->values[i] == curattnum) {
                    tgattr_values[i] = newattnum;
                } else {
                    tgattr_values[i] = tgattr->values[i];
                }
            }

            new_tgattr = buildint2vector(tgattr_values, tgattr->dim1);
            values[Anum_pg_trigger_tgattr - 1] = PointerGetDatum(new_tgattr);
            replaces[Anum_pg_trigger_tgattr - 1] = true;
        }

        Datum tgqual_datum = fastgetattr(tri_tuple, Anum_pg_trigger_tgqual, tri_rel->rd_att, &is_null);
        if (!is_null) {
            char *tgqual_string = NULL;
            Node *tgqual = NULL;
            Node *new_tgqual = NULL;

            tgqual_string = TextDatumGetCString(tgqual_datum);
            tgqual = (Node *)stringToNode(tgqual_string);

            new_tgqual = UpdateVarattnoAfterAddColumn(tgqual, startattnum, endattnum, is_increase);
            tgqual_string = nodeToString(new_tgqual);
            values[Anum_pg_trigger_tgqual - 1] = CStringGetTextDatum(tgqual_string);
            replaces[Anum_pg_trigger_tgqual - 1] = true;
            pfree_ext(tgqual_string);
        }

        new_tri_tuple = heap_modify_tuple(tri_tuple, RelationGetDescr(tri_rel), values, nulls, replaces);
        simple_heap_update(tri_rel, &new_tri_tuple->t_self, new_tri_tuple);
        CatalogUpdateIndexes(tri_rel, new_tri_tuple);

        pfree_ext(tgattr_values);
        pfree_ext(new_tgattr);
        heap_freetuple_ext(new_tri_tuple);
    }

    systable_endscan(scan);
    heap_close(tri_rel, RowExclusiveLock);
}

/*
 * update pg_rlspolicy
 * 1. add column with first or after col_name.
 * 2. modify column to first or after column.
 */
static void UpdatePgRlspolicyFirstAfter(Relation rel, int startattnum, int endattnum, bool is_increase)
{
    ScanKeyData key;
    HeapTuple rls_tuple;
    Relation rls_rel;
    SysScanDesc scan;

    rls_rel = heap_open(RlsPolicyRelationId, RowExclusiveLock);

    ScanKeyInit(&key, Anum_pg_rlspolicy_polrelid, BTEqualStrategyNumber, F_OIDEQ,
        ObjectIdGetDatum(RelationGetRelid(rel)));

    scan = systable_beginscan(rls_rel, PgRlspolicyPolrelidPolnameIndex, true, NULL, 1, &key);
    while (HeapTupleIsValid(rls_tuple = systable_getnext(scan))) {
        bool is_null = false;
        Datum values[Natts_pg_rlspolicy] = { 0 };
        bool nulls[Natts_pg_rlspolicy] = { 0 };
        bool replaces[Natts_pg_rlspolicy] = { 0 };
        HeapTuple new_rls_tuple;

        Datum polqual_datum = heap_getattr(rls_tuple, Anum_pg_rlspolicy_polqual, rls_rel->rd_att, &is_null);
        if (!is_null) {
            char *polqual_string = NULL;
            Node *polqual = NULL;
            Node *new_polqual = NULL;

            polqual_string = TextDatumGetCString(polqual_datum);
            polqual = (Node *)stringToNode(polqual_string);

            new_polqual = UpdateVarattnoAfterAddColumn(polqual, startattnum, endattnum, is_increase);
            polqual_string = nodeToString(new_polqual);
            values[Anum_pg_rlspolicy_polqual - 1] = CStringGetTextDatum(polqual_string);
            replaces[Anum_pg_rlspolicy_polqual - 1] = true;
            pfree_ext(polqual_string);
        }

        new_rls_tuple = heap_modify_tuple(rls_tuple, RelationGetDescr(rls_rel), values, nulls, replaces);
        simple_heap_update(rls_rel, &new_rls_tuple->t_self, new_rls_tuple);
        CatalogUpdateIndexes(rls_rel, new_rls_tuple);

        heap_freetuple_ext(new_rls_tuple);
    }

    systable_endscan(scan);
    heap_close(rls_rel, RowExclusiveLock);
}

#ifdef ENABLE_MOT
static void ATExecMOTAlterTable(AlterForeingTableCmd* cmd)
{
    FdwRoutine* fdwroutine;

    if (cmd->rel->rd_fdwroutine != nullptr) {
        fdwroutine = cmd->rel->rd_fdwroutine;
    } else {
        fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(cmd->rel));
    }

    if (fdwroutine->ValidateTableDef != nullptr) {
        fdwroutine->ValidateTableDef((Node*)cmd);
    }
}
#endif

static ObjectAddress ATExecAddColumn(List** wqueue, AlteredTableInfo* tab, Relation rel, ColumnDef* colDef, bool isOid,
    bool recurse, bool recursing, bool is_first, char *after_name, LOCKMODE lockmode)
{
    Oid myrelid = RelationGetRelid(rel);
    Relation pgclass = NULL;
    Relation attrdesc = NULL;
    Relation    cedesc = NULL;
    HeapTuple reltup = NULL;
    FormData_pg_attribute attribute;
    int newattnum = 0;
    int currattnum = 0;
    char relkind;
    HeapTuple typeTuple;
    Oid typeOid = InvalidOid;
    int32 typmod = -1;
    Oid collOid = InvalidOid;
    Form_pg_type tform = NULL;
    Expr* defval = NULL;
    List* children = NIL;
    ListCell* child = NULL;
    AclResult aclresult;
    bool isDfsTable = RelationIsPAXFormat(rel);
    ObjectAddress address;
    bool is_addloc = is_first || after_name != NULL;
    List *query_str = NIL;

    /* At top level, permission check was done in ATPrepCmd, else do it */
    if (recursing)
        ATSimplePermissions(rel, ATT_TABLE);

    attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);

    if (is_addloc) {
        if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) {
            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Un-supported feature"),
                    errdetail("foreign table is not supported for add column first|after columnName")));
        }

        if (RelationIsColumnFormat(rel)) {
            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Un-supported feature"),
                    errdetail("column orientated table is not supported for add column first|after columnName")));
        }
    }

    /*
    * if adding encrypted column
    */
    CeHeapInfo* ceHeapInfo = NULL;
    if (colDef->clientLogicColumnRef != NULL) {
        if (is_addloc) {
            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Un-supported feature"),
                    errdetail("encryption column is not supported for add column first|after columnName")));
        }
        if (colDef->clientLogicColumnRef != NULL) {
            ceHeapInfo = (CeHeapInfo *)palloc(sizeof(CeHeapInfo));
            process_encrypted_columns(colDef, ceHeapInfo);
        }
        cedesc = heap_open(ClientLogicCachedColumnsId, RowExclusiveLock);
    }

    /*
     * Are we adding the column to a recursion child?  If so, check whether to
     * merge with an existing definition for the column.  If we do merge, we
     * must not recurse.  Children will already have the column, and recursing
     * into them would mess up attinhcount.
     */
    if (colDef->inhcount > 0) {
        HeapTuple tuple;

        /* Does child already have a column by this name? */
        tuple = SearchSysCacheCopyAttName(myrelid, colDef->colname);
        if (HeapTupleIsValid(tuple)) {
            Form_pg_attribute childatt = (Form_pg_attribute)GETSTRUCT(tuple);
            Oid ctypeId = InvalidOid;
            int32 ctypmod = -1;
            Oid ccollid = InvalidOid;

            /* Child column must match on type, typmod, and collation */
            typenameTypeIdAndMod(NULL, colDef->typname, &ctypeId, &ctypmod);
            if (ctypeId != childatt->atttypid || ctypmod != childatt->atttypmod)
                ereport(ERROR,
                    (errcode(ERRCODE_DATATYPE_MISMATCH),
                        errmsg("child table \"%s\" has different type for column \"%s\"",
                            RelationGetRelationName(rel),
                            colDef->colname)));
            ccollid = GetColumnDefCollation(NULL, colDef, ctypeId);
            if (ccollid != childatt->attcollation)
                ereport(ERROR,
                    (errcode(ERRCODE_COLLATION_MISMATCH),
                        errmsg("child table \"%s\" has different collation for column \"%s\"",
                            RelationGetRelationName(rel),
                            colDef->colname),
                        errdetail("\"%s\" versus \"%s\"",
                            get_collation_name(ccollid),
                            get_collation_name(childatt->attcollation))));

            /* If it's OID, child column must actually be OID */
            if (isOid && childatt->attnum != ObjectIdAttributeNumber)
                ereport(ERROR,
                    (errcode(ERRCODE_DATATYPE_MISMATCH),
                        errmsg("child table \"%s\" has a conflicting \"%s\" column",
                            RelationGetRelationName(rel),
                            colDef->colname)));

            /* Bump the existing child att's inhcount */
            childatt->attinhcount++;
            simple_heap_update(attrdesc, &tuple->t_self, tuple);
            CatalogUpdateIndexes(attrdesc, tuple);

            tableam_tops_free_tuple(tuple);

            /* Inform the user about the merge */
            ereport(NOTICE,
                (errmsg("merging definition of column \"%s\" for child \"%s\"",
                    colDef->colname,
                    RelationGetRelationName(rel))));

            heap_close(attrdesc, RowExclusiveLock);
            return InvalidObjectAddress;
        }
    }

    pgclass = heap_open(RelationRelationId, RowExclusiveLock);

    reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
    if (!HeapTupleIsValid(reltup)) {
        ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", myrelid)));
    }
    relkind = ((Form_pg_class)GETSTRUCT(reltup))->relkind;

    /* new name should not already exist */
    check_for_column_name_collision(rel, colDef->colname);

    /* Determine the new attribute's number */
    if (isOid) {
        newattnum = ObjectIdAttributeNumber;
    } else {
        currattnum = ((Form_pg_class)GETSTRUCT(reltup))->relnatts;
        if (currattnum + 1 > MaxHeapAttributeNumber) {
            ereport(ERROR,
                (errcode(ERRCODE_TOO_MANY_COLUMNS),
                    errmsg("tables can have at most %d columns", MaxHeapAttributeNumber)));
        }
        if (is_first) {
            newattnum = 1;
        } else if (after_name != NULL) {
            newattnum = GetAfterColumnAttnum(myrelid, after_name);
        } else {
            newattnum = currattnum + 1;
        }
    }

    typeTuple = typenameType(NULL, colDef->typname, &typmod);
    tform = (Form_pg_type)GETSTRUCT(typeTuple);
    typeOid = HeapTupleGetOid(typeTuple);

    /* And the collation */
    Oid rel_coll_oid = rel->rd_options == NULL ? InvalidOid : ((StdRdOptions*)(rel)->rd_options)->collate;
    collOid = GetColumnDefCollation(NULL, colDef, typeOid, rel_coll_oid);
    if (DB_IS_CMPT_BD) {
        typeOid = binary_need_transform_typeid(typeOid, &collOid);
        if (RelationIsColStore(rel) || RelationIsTsStore(rel)) {
            check_unsupported_charset_for_column(collOid, colDef->colname);
        }
    }

    aclresult = pg_type_aclcheck(typeOid, GetUserId(), ACL_USAGE);
    if (aclresult != ACLCHECK_OK)
        aclcheck_error_type(aclresult, typeOid);

    /* make sure datatype is legal for a column */
    CheckAttributeType(colDef->colname, typeOid, collOid, list_make1_oid(rel->rd_rel->reltype), false);

#ifdef ENABLE_MOT
    if (relkind == RELKIND_FOREIGN_TABLE && isMOTFromTblOid(RelationGetRelid(rel))) {
        DetermineColumnCollationForMOTTable(&collOid);
    }
#endif

    bool isDelta = RELATION_IS_DELTA(rel);

    /* construct new attribute's pg_attribute entry */
    attribute.attrelid = myrelid;
    (void)namestrcpy(&(attribute.attname), colDef->colname);
    attribute.atttypid = typeOid;
    attribute.attstattarget = (newattnum > 0) ? -1 : 0;
    attribute.attlen = tform->typlen;
    attribute.attcacheoff = -1;
    attribute.atttypmod = typmod;
    attribute.attnum = newattnum;
    attribute.attbyval = tform->typbyval;
    attribute.attndims = list_length(colDef->typname->arrayBounds);
    attribute.attstorage = tform->typstorage;
    attribute.attalign = tform->typalign;
    attribute.attnotnull = colDef->is_not_null;
    attribute.atthasdef = false;
    attribute.attisdropped = false;
    attribute.attislocal = colDef->is_local;
    attribute.attkvtype = colDef->kvtype;
    if (!isDelta) {
        VerifyAttrCompressMode(colDef->cmprs_mode, attribute.attlen, colDef->colname);
        attribute.attcmprmode = colDef->cmprs_mode;
    } else {
        attribute.attcmprmode = ATT_CMPR_NOCOMPRESS;
    }
    attribute.attinhcount = colDef->inhcount;
    attribute.attcollation = collOid;
    /* attribute.attacl is handled by InsertPgAttributeTuple */
    ReleaseSysCache(typeTuple);

    if (!isDelta && RelationIsRowFormat(rel) &&  ATT_CMPR_NOCOMPRESS < colDef->cmprs_mode
        && colDef->cmprs_mode <= ATT_CMPR_NUMSTR) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
             errmsg("row-oriented table does not support compression")));
    }

    if (is_addloc) {
        UpdatePgAttributeFirstAfter(attrdesc, myrelid, newattnum, currattnum, true);
        UpdatePgDescriptionFirstAfter(rel, newattnum, currattnum, true);
        UpdatePgStatisticFirstAfter(rel, newattnum, currattnum, true);
        UpdatePgStatisticExtFirstAfter(rel, newattnum, currattnum, true);
        UpdatePgIndexFirstAfter(rel, newattnum, currattnum, true);
        UpdatePgConstraintFirstAfter(rel, newattnum, currattnum, true);
        UpdatePgConstraintConfkeyFirstAfter(rel, newattnum, currattnum, true);
        UpdatePgAttrdefFirstAfter(rel, newattnum, currattnum, true);
        UpdatePgPartitionFirstAfter(rel, newattnum, currattnum, true, false, NULL);
        UpdatePgTriggerFirstAfter(rel, newattnum, currattnum, true);
        UpdatePgRlspolicyFirstAfter(rel, newattnum, currattnum, true);
        query_str = CheckPgRewriteFirstAfter(rel);
        tab->rewrite |= AT_REWRITE_ALTER_PERSISTENCE;
        tab->is_first_after = true;
    } else if (rel->rd_rel->relkind == RELKIND_RELATION) {
        query_str = CheckPgRewriteFirstAfter(rel);
    }

    InsertPgAttributeTuple(attrdesc, &attribute, NULL);

    heap_close(attrdesc, RowExclusiveLock);

    if (colDef->clientLogicColumnRef != NULL) {
        ceHeapInfo->attnum = newattnum;
        insert_gs_sec_encrypted_column_tuple(ceHeapInfo, cedesc, myrelid,  NULL);
        heap_close(cedesc, RowExclusiveLock);
    }

    /*
     * Update pg_class tuple as appropriate
     */
    if (isOid)
        ((Form_pg_class)GETSTRUCT(reltup))->relhasoids = true;
    else
        ((Form_pg_class)GETSTRUCT(reltup))->relnatts = currattnum + 1;

    simple_heap_update(pgclass, &reltup->t_self, reltup);

    /* keep catalog indexes current */
    CatalogUpdateIndexes(pgclass, reltup);

    tableam_tops_free_tuple(reltup);

    /* Post creation hook for new attribute */
    InvokeObjectAccessHook(OAT_POST_CREATE, RelationRelationId, myrelid, newattnum, NULL);

    heap_close(pgclass, RowExclusiveLock);

    /* Make the attribute's catalog entry visible */
    CommandCounterIncrement();

    if (is_addloc) {
        UpdatePgDependFirstAfter(rel, newattnum, currattnum, true);
        UpdateGenerateColFirstAfter(rel, newattnum, currattnum, true);
        UpdateIndexFirstAfter(rel);

        /* create or replace view */
        ReplaceViewQueryFirstAfter(query_str);
    } else if (rel->rd_rel->relkind == RELKIND_RELATION && query_str != NIL) {
        ListCell* viewinfo = NULL;
        bool isViewValid = true;
        foreach (viewinfo, query_str) {
            ViewInfoForAdd *info = (ViewInfoForAdd *)lfirst(viewinfo);
            isViewValid &= GetPgObjectValid(info->ev_class,  get_rel_relkind(info->ev_class));
            if (!isViewValid) {
                break;
            }
        }
        if (isViewValid) {
            ReplaceViewQueryFirstAfter(query_str);
        }
    }

    /*
     * Store the DEFAULT, if any, in the catalogs
     */
    if (colDef->raw_default) {
        RawColumnDefault* rawEnt = NULL;

        if (relkind == RELKIND_FOREIGN_TABLE) {
#ifdef ENABLE_MOT
            if (!isMOTFromTblOid(RelationGetRelid(rel)) && !isPostgresFDWFromTblOid(RelationGetRelid(rel))) {
#else
            if (!isPostgresFDWFromTblOid(RelationGetRelid(rel))) {
#endif
                ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("%s on foreign tables are not supported",
                    colDef->generatedCol ? "generated column" : "default values")));
            }
        } else if (relkind ==  RELKIND_STREAM) {
            ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("%s on streams are not supported",
                colDef->generatedCol ? "generated column" : "default values")));
        } else if (RelationIsTsStore(rel)) {
            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("It's not supported to add column with default value for timeseries tables.")));
        }

        rawEnt = (RawColumnDefault*)palloc(sizeof(RawColumnDefault));
        rawEnt->attnum = attribute.attnum;
        rawEnt->raw_default = (Node*)copyObject(colDef->raw_default);
        rawEnt->update_expr = (Node*)copyObject(colDef->update_default);

        rawEnt->generatedCol = colDef->generatedCol;

        /*
         * This function is intended for CREATE TABLE, so it processes a
         * _list_ of defaults, but we just do one.
         */
        (void)AddRelationNewConstraints(rel, list_make1(rawEnt), NIL, false, true);

        /* Make the additional catalog changes visible */
        CommandCounterIncrement();
    }

    /*
     * Tell Phase 3 to fill in the default expression, if there is one.
     *
     * If there is no default, Phase 3 doesn't have to do anything, because
     * that effectively means that the default is NULL.  The heap tuple access
     * routines always check for attnum > # of attributes in tuple, and return
     * NULL if so, so without any modification of the tuple data we will get
     * the effect of NULL values in the new column.
     *
     * An exception occurs when the new column is of a domain type: the domain
     * might have a NOT NULL constraint, or a check constraint that indirectly
     * rejects nulls.  If there are any domain constraints then we construct
     * an explicit NULL default value that will be passed through
     * CoerceToDomain processing.  (This is a tad inefficient, since it causes
     * rewriting the table which we really don't have to do, but the present
     * design of domain processing doesn't offer any simple way of checking
     * the constraints more directly.)
     *
     * Note: we use build_column_default, and not just the cooked default
     * returned by AddRelationNewConstraints, so that the right thing happens
     * when a datatype's default applies.
     *
     * We skip this step completely for views and foreign tables.  For a view,
     * we can only get here from CREATE OR REPLACE VIEW, which historically
     * doesn't set up defaults, not even for domain-typed columns.  And in any
     * case we mustn't invoke Phase 3 on a view or foreign table, since they
     * have no storage.
     */
#ifdef ENABLE_MOT
    if ((relkind == RELKIND_FOREIGN_TABLE && isMOTFromTblOid(RelationGetRelid(rel)) && attribute.attnum > 0) ||
        (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE && relkind != RELKIND_FOREIGN_TABLE &&
        relkind != RELKIND_STREAM && relkind != RELKIND_CONTQUERY && attribute.attnum > 0)) {
#else
    if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE && relkind != RELKIND_FOREIGN_TABLE &&
        relkind != RELKIND_STREAM && relkind != RELKIND_CONTQUERY && attribute.attnum > 0) {
#endif
        /* test whether new column is null or not*/
        bool testNotNull = colDef->is_not_null;

        /*
         * Generally, relcache of nailed-in system catalogs should not be blowed away
         * for reliability consideration. Under such circumstances, visiting the
         * attdesc of the newly added system catalog column, which is necessary in building
         * its default, is impossible.
         * On the other hand, during inplace or online upgrade, we only prohibit invalidation
         * of pg_class's and pg_attribute's relcache. As a result, we do not support
         * non-NULL default values for new columns in pg_class, pg_attribute, pg_proc during
         * inplace or online upgrade at present.
         */
        if (u_sess->attr.attr_common.IsInplaceUpgrade &&
            (rel->rd_id == RelationRelationId || rel->rd_id == AttributeRelationId))
            defval = NULL;
        else
            defval = (Expr*)build_column_default(rel, attribute.attnum);

        if (defval == NULL && (GetDomainConstraints(typeOid) != NIL || is_addloc)) {
            Oid baseTypeId;
            int32 baseTypeMod;
            Oid baseTypeColl;

            baseTypeMod = typmod;
            baseTypeId = getBaseTypeAndTypmod(typeOid, &baseTypeMod);
            baseTypeColl = get_typcollation(baseTypeId);
            defval = (Expr*)makeNullConst(baseTypeId, baseTypeMod, baseTypeColl);
            if (GetDomainConstraints(typeOid) != NIL) {
                defval = (Expr*)coerce_to_target_type(
                    NULL, (Node*)defval, baseTypeId, typeOid, typmod, COERCION_ASSIGNMENT, COERCE_IMPLICIT_CAST,
                    NULL, NULL, -1);
                if (defval == NULL) /* should not happen */
                    ereport(ERROR,
                        (errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("failed to coerce base type to domain")));
            }
        }

        if (defval != NULL) {
            /* if now the row-storage table must be rewrited,
             * it isn't need to apply alter-table-instantly feature.
             * also exclude temp table and column table.
             */
            if (attribute.attnum == RelAutoIncAttrNum(rel)) {
                if (colDef->is_not_null) {
                    ATExecAppendDefValExpr(attribute.attnum, defval, tab, colDef, true, is_addloc);
                }
            } else if (contain_specified_function((Node*)defval, NEXTVALFUNCOID)
                    && u_sess->attr.attr_sql.sql_compatibility != D_FORMAT) {
                /* We don't support alter table add column which default with nextval expression. */
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("It's not supported to alter table add column default with nextval expression.")));
            } else if (RelationIsCUFormat(rel)) {
                ATExecAppendDefValExpr(attribute.attnum, defval, tab, colDef, false, false);
            } else if (colDef->generatedCol ) {
                ATExecAppendDefValExpr(attribute.attnum, defval, tab, colDef, false, true);
            } else if (RelationUsesSpaceType(rel->rd_rel->relpersistence) == SP_TEMP) {
                bytea* value = NULL;
                colDef->initdefval = NULL;
                (void)shouldUpdateAllTuples(defval, attribute.atttypid, attribute.attlen, attribute.attbyval, &value, &colDef->initdefval);
                ATExecAppendDefValExpr(attribute.attnum, defval, tab, colDef, false, true);
            } else if (tab->rewrite) {
                bytea* value = NULL;
                bool addcolumndef = false;

                if (XLogLogicalInfoActive() && RelationIsRowFormat(rel)) {
                    if (IsA(defval, Const) && constIsNull((Const*)defval)) {
                        ATExecAppendDefValExpr(attribute.attnum, defval, tab, colDef, false, true);
                    } else {
                        colDef->initdefval = NULL;
                        AT_INSTANT_DEFAULT_VALUE ret =
                        shouldUpdateAllTuples(defval, attribute.atttypid, attribute.attlen, attribute.attbyval, &value, &colDef->initdefval);

                        if (ret == DEFAULT_OTHER) {
                            addcolumndef = true;
                            colDef->initdefval = NULL;
                        }
                        
                        ATExecAppendDefValExpr(attribute.attnum, defval, tab, colDef, false, true, addcolumndef);
                    }
                } else {
                    ATExecAppendDefValExpr(attribute.attnum, defval, tab, colDef, false, true);
                }
            } else {
                bytea* value = NULL;
                colDef->initdefval = NULL;
                AT_INSTANT_DEFAULT_VALUE ret =
                    shouldUpdateAllTuples(defval, attribute.atttypid, attribute.attlen, attribute.attbyval, &value, &colDef->initdefval);

                if (ret == DEFAULT_NOT_NULL_CONST) {
                    Assert(value != NULL);
                    updateInitDefVal(value, rel, attribute.attnum);
                    pfree_ext(value);

                    /* new column has const default value,
                     * so Not-Null test is not necessary.
                     */
                    testNotNull = false;
                } else if (ret == DEFAULT_OTHER) {
                    colDef->initdefval = NULL;
                    if (isDfsTable) {
                        ereport(ERROR,
                            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                                (errmsg("It is not supported on DFS table. The detailed reasons are the"
                                        " followings:"),
                                    errdetail("1. the default value may be a volatile function.\n"
                                              "2. the storage length of default value may be greater than 127.\n"
                                              "3. the data type of new column is not supported."))));
                    }
                    ATExecAppendDefValExpr(attribute.attnum, defval, tab, colDef, false, false, true);
                }
                /* nothing to do if ret is DEFAULT_NULL */
            }
        }

        /*
         * If the new column is NOT NULL, tell Phase 3 it needs to test that.
         * (Note we don't do this for an OID column.  OID will be marked not
         * null, but since it's filled specially, there's no need to test
         * anything.)
         */
        if (testNotNull) {
            tab->new_notnull = true;
        }
    }

    /*
     * If we are adding an OID column, we have to tell Phase 3 to rewrite the
     * table to fix that.
     */
    if (isOid) {
        tab->rewrite |= AT_REWRITE_ALTER_OID;
    }

    if (RelationIsColStore(rel)) {
        /*
         * The DFS table do not rewrite data, only update the catalog for default value.
         */
        if (isDfsTable) {
            tab->rewrite = AT_REWRITE_ALTER_OID;
        } else {
            tab->rewrite |= AT_REWRITE_ALTER_OID;
        }
    }

    /*
     * Add needed dependency entries for the new column.
     */
    add_column_datatype_dependency(myrelid, newattnum, attribute.atttypid);
    add_column_collation_dependency(myrelid, newattnum, attribute.attcollation);

#ifdef ENABLE_MOT
    if (relkind == RELKIND_FOREIGN_TABLE && isMOTFromTblOid(RelationGetRelid(rel))) {
        AlterForeingTableCmd fcmd = {
            T_AlterForeingTableCmd,
            AT_AddColumn,
            rel,
            nullptr,
            (Node*)colDef,
            typeOid,
            defval
        };
        ATExecMOTAlterTable(&fcmd);
    }
#endif

#ifdef ENABLE_MULTIPLE_NODES
    if (unlikely(RelationIsTsStore(rel))) {
        /* add col to tag table */
        if (colDef->kvtype == ATT_KV_TAG) {
            Oid tag_relid = get_tag_relid(RelationGetRelationName(rel), rel->rd_rel->relnamespace);
            Relation tagrel;
            AlteredTableInfo* tagtab = NULL;
            List* index_col_name = NIL;

            tagrel = heap_open(tag_relid, lockmode);
            CheckTableNotInUse(tagrel, "ALTER TABLE");

            tagtab = ATGetQueueEntry(wqueue, tagrel);

            ATExecAddColumn(wqueue, tagtab, tagrel, colDef, isOid, false, false, false, NULL, lockmode);

            char tag_relname[NAMEDATALEN] = {0};
            Tsdb::GenMetaRelname(rel->rd_rel->relnamespace, Tsdb::MetaTableType::META_TABLE_TAGS,
                         tag_relname, TsConf::MAX_TS_NAME_LEN, RelationGetRelationName(rel));
            /* index_col_name contains the name of one new tag column which need to add */
            index_col_name = lappend(index_col_name, colDef->colname);
            create_tag_index(tag_relid, tag_relname, index_col_name);
            list_free_ext(index_col_name);
            heap_close(tagrel, NoLock);
        } else if (colDef->kvtype == ATT_KV_FIELD && Tsdb::RelationEnablesTsdbDelta(rel)) {
            /* if add TSField columns, update delta table simultaneously */
            Relation delta_rel = Tsdb::RelationGetDeltaRelation(rel, lockmode);
            CheckTableNotInUse(delta_rel, "ALTER TABLE");
            AlteredTableInfo* delta_tab = ATGetQueueEntry(wqueue, delta_rel);
            ATExecAddColumn(wqueue, delta_tab, delta_rel, colDef, isOid, false, false, false, NULL, lockmode);
            heap_close(delta_rel, NoLock);
        }
    }
#endif   /* ENABLE_MULTIPLE_NODES */

    if (RelationIsPAXFormat(rel)) {
        /*
         * Add column for delta table.
         */
        children = lappend_oid(children, RelationGetDeltaRelId(rel));
        /*
         * Get the lock to synchronize against concurrent drop.
         */
        LockRelationOid(RelationGetDeltaRelId(rel), lockmode);
        elog(DEBUG1,
            "[GET LOCK] Get the lock %d successfully on delta table of %s for altering operator.",
            lockmode,
            RelationGetRelationName(rel));
#ifdef ENABLE_MULTIPLE_NODES
    } else if (g_instance.attr.attr_storage.enable_delta_store && RelationIsCUFormat(rel)) {
#else
    /*
     * Under centrailzed mode, there may be unique index on delta table. When checking unique
     * constraint, unique index on delta will be used. So we ignore enable_delta_store here
     * and alter delta table at the same time.
     */
    } else if (RelationIsCUFormat(rel)) {
#endif
        /*
         * add cstore relation delta table to recurse, if col support inherit feture
         * we also need call find_inheritance_children as below
         */
        children = find_cstore_delta(rel, lockmode);
    } else {
        /*
         * Propagate to children as appropriate.  Unlike most other ALTER
         * routines, we have to do this one level of recursion at a time; we can't
         * use find_all_inheritors to do it in one pass.
         */
        children = find_inheritance_children(RelationGetRelid(rel), lockmode);
    }

    /*
     * If we are told not to recurse, there had better not be any child
     * tables; else the addition would put them out of step.
     */
    if (children && !recurse)
        ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("column must be added to child tables too")));

    /* Children should see column as singly inherited, Cstore table and delta talbe are not inherited table */
    if (!recursing && !RelationIsCUFormat(rel)) {
        colDef = (ColumnDef*)copyObject(colDef);
        colDef->inhcount = 1;
        colDef->is_local = false;
    }


	/* column Options */
    ATCreateColumComments(myrelid, colDef);

    foreach (child, children) {
        Oid childrelid = lfirst_oid(child);
        Relation childrel;
        AlteredTableInfo* childtab = NULL;

        /* find_inheritance_children already got lock */
        childrel = heap_open(childrelid, NoLock);
        CheckTableNotInUse(childrel, "ALTER TABLE");

        /* Find or create work queue entry for this table */
        childtab = ATGetQueueEntry(wqueue, childrel);

        /* Recurse to child */
        ATExecAddColumn(wqueue, childtab, childrel, colDef, isOid, recurse, true, is_first, after_name, lockmode);

        heap_close(childrel, NoLock);
    }
    ObjectAddressSubSet(address, RelationRelationId, myrelid, newattnum);
    return address;
}

/*
 * If a new or renamed column will collide with the name of an existing
 * column, error out.
 */
static void check_for_column_name_collision(Relation rel, const char* colname)
{
    HeapTuple attTuple;
    int attnum;

    /*
     * this test is deliberately not attisdropped-aware, since if one tries to
     * add a column matching a dropped column name, it's gonna fail anyway.
     */
    attTuple = SearchSysCache2(ATTNAME, ObjectIdGetDatum(RelationGetRelid(rel)), PointerGetDatum(colname));
    if (!HeapTupleIsValid(attTuple))
        return;

    attnum = ((Form_pg_attribute)GETSTRUCT(attTuple))->attnum;
    ReleaseSysCache(attTuple);

    /*
     * We throw a different error message for conflicts with system column
     * names, since they are normally not shown and the user might otherwise
     * be confused about the reason for the conflict.
     */
    if (attnum <= 0)
        ereport(ERROR,
            (errcode(ERRCODE_DUPLICATE_COLUMN),
                errmsg("column name \"%s\" conflicts with a system column name", colname)));
    else
        ereport(ERROR,
            (errcode(ERRCODE_DUPLICATE_COLUMN),
                errmsg("column \"%s\" of relation \"%s\" already exists", colname, RelationGetRelationName(rel))));
}

/*
 * Install a column's dependency on its datatype.
 */
static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid)
{
    ObjectAddress myself, referenced;

    myself.classId = RelationRelationId;
    myself.objectId = relid;
    myself.objectSubId = attnum;
    referenced.classId = TypeRelationId;
    referenced.objectId = typid;
    referenced.objectSubId = 0;
    recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}

/*
 * Install a column's dependency on its collation.
 */
static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid)
{
    ObjectAddress myself, referenced;

    /* We know the default collation is pinned, so don't bother recording it */
    if (OidIsValid(collid) && collid != DEFAULT_COLLATION_OID) {
        myself.classId = RelationRelationId;
        myself.objectId = relid;
        myself.objectSubId = attnum;
        referenced.classId = CollationRelationId;
        referenced.objectId = collid;
        referenced.objectSubId = 0;
        recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
    }
}

/*
 * ALTER TABLE SET WITH OIDS
 *
 * Basically this is an ADD COLUMN for the special OID column.	We have
 * to cons up a ColumnDef node because the ADD COLUMN code needs one.
 */
static void ATPrepAddOids(List** wqueue, Relation rel, bool recurse, AlterTableCmd* cmd, LOCKMODE lockmode)
{
    /* If we're recursing to a child table, the ColumnDef is already set up */
    if (cmd->def == NULL) {
        ColumnDef* cdef = makeNode(ColumnDef);

        cdef->colname = pstrdup("oid");
        cdef->typname = makeTypeNameFromOid(OIDOID, -1);
        cdef->inhcount = 0;
        cdef->is_local = true;
        cdef->is_not_null = true;
        cdef->storage = 0;
        // the best compression method for OID datatype is DELTA. but its data is stored
        // in header part of heap tuple, instead of data part. so don't compress it.
        //
        cdef->kvtype = ATT_KV_UNDEFINED;
        cdef->cmprs_mode = ATT_CMPR_NOCOMPRESS;
        cmd->def = (Node*)cdef;
    }
    ATPrepAddColumn(wqueue, NULL, rel, recurse, false, cmd, lockmode);

    if (recurse)
        cmd->subtype = AT_AddOidsRecurse;
}

/*
 * ALTER TABLE ALTER COLUMN DROP NOT NULL
 *
 * Return the address of the modified column.  If the column was already
 * nullable, InvalidObjectAddress is returned.
 */
static ObjectAddress ATExecDropNotNull(Relation rel, const char* colName, LOCKMODE lockmode)
{
    HeapTuple tuple;
    AttrNumber attnum;
    Relation attr_rel;
    List* indexoidlist = NIL;
    ListCell* indexoidscan = NULL;
    ObjectAddress address;
    Oid replidindex;

    /*
     * lookup the attribute
     */
    attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);

    tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);

    if (!HeapTupleIsValid(tuple))
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_COLUMN),
                errmsg("column \"%s\" of relation \"%s\" does not exist", colName, RelationGetRelationName(rel))));

    attnum = ((Form_pg_attribute)GETSTRUCT(tuple))->attnum;

    /* Prevent them from altering a system attribute */
    if (attnum <= 0)
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot alter system column \"%s\"", colName)));

    /*
     * Check that the attribute is not in a primary key
     *
     * Note: we'll throw error even if the pkey index is not valid.
     */
    /* Loop over all indexes on the relation */
    indexoidlist = RelationGetIndexList(rel);

    /* replica identity index */
    replidindex = rel->rd_replidindex;

    foreach (indexoidscan, indexoidlist) {
        Oid indexoid = lfirst_oid(indexoidscan);
        HeapTuple indexTuple;
        Form_pg_index indexStruct;
        int i;
        int indnkeyatts;

        indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
        if (!HeapTupleIsValid(indexTuple)) {
            ereport(
                ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for index %u", indexoid)));
        }

        indexStruct = (Form_pg_index)GETSTRUCT(indexTuple);
        indnkeyatts = GetIndexKeyAttsByTuple(NULL, indexTuple);

        /* If the index is not a primary key, skip the check */
        if (indexStruct->indisprimary) {
            /*
             * Loop over each attribute in the primary key and see if it
             * matches the to-be-altered attribute
             */
            for (i = 0; i < indnkeyatts; i++) {
                if (indexStruct->indkey.values[i] == attnum)
                    ereport(ERROR,
                        (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                            errmsg("column \"%s\" is in a primary key", colName)));
            }
        }

        /* REPLICA IDENTIFY can't drop not null */
        if (replidindex == indexoid) {
            for (i = 0; i < indnkeyatts; i++) {
                if (indexStruct->indkey.values[i] == attnum)
                    ereport(ERROR,
                        (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                            errmsg("column \"%s\" used as replica identity can't drop not null", colName)));
            }
        }

        ReleaseSysCache(indexTuple);
    }

    list_free_ext(indexoidlist);

    /*
     * Okay, actually perform the catalog change ... if needed
     */
    if (((Form_pg_attribute)GETSTRUCT(tuple))->attnotnull) {
        ((Form_pg_attribute)GETSTRUCT(tuple))->attnotnull = FALSE;

        simple_heap_update(attr_rel, &tuple->t_self, tuple);

        /* keep the system catalog indexes current */
        CatalogUpdateIndexes(attr_rel, tuple);
        ObjectAddressSubSet(address, RelationRelationId,
                            RelationGetRelid(rel), attnum);

    }
    else
        address = InvalidObjectAddress;

    heap_close(attr_rel, RowExclusiveLock);
    return address;
}

/*
 * ALTER TABLE ALTER COLUMN SET NOT NULL
 *
 * Return the address of the modified column.  If the column was already NOT
 * NULL, InvalidObjectAddress is returned.
 */
static ObjectAddress ATExecSetNotNull(AlteredTableInfo* tab, Relation rel, const char* colName, LOCKMODE lockmode)
{
    HeapTuple tuple;
    AttrNumber attnum;
    Relation attr_rel;
    ObjectAddress address;
    /*
     * lookup the attribute
     */
    attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);

    tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);

    if (!HeapTupleIsValid(tuple))
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_COLUMN),
                errmsg("column \"%s\" of relation \"%s\" does not exist", colName, RelationGetRelationName(rel))));

    attnum = ((Form_pg_attribute)GETSTRUCT(tuple))->attnum;

    /* Prevent them from altering a system attribute */
    if (attnum <= 0)
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot alter system column \"%s\"", colName)));

    /*
     * Okay, actually perform the catalog change ... if needed
     */
    if (!((Form_pg_attribute)GETSTRUCT(tuple))->attnotnull) {
        ((Form_pg_attribute)GETSTRUCT(tuple))->attnotnull = TRUE;

        simple_heap_update(attr_rel, &tuple->t_self, tuple);

        /* keep the system catalog indexes current */
        CatalogUpdateIndexes(attr_rel, tuple);

        /* Tell Phase 3 it needs to test the constraint */
        tab->new_notnull = true;
        ObjectAddressSubSet(address, RelationRelationId,
                            RelationGetRelid(rel), attnum);
    }
    else
        address = InvalidObjectAddress;

    heap_close(attr_rel, RowExclusiveLock);
    return address;
}

bool FetchOnUpdateExpress(Relation rel, const char* colName)
{
    HeapTuple htup = NULL;
    bool isnull = false;
    bool temp_on_update = FALSE;
    htup = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
    if (!HeapTupleIsValid(htup))
        ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN),
            errmsg("column \"%s\" of relation \"%s\" does not exist", colName, RelationGetRelationName(rel))));
    Form_pg_attribute attTup = (Form_pg_attribute)GETSTRUCT(htup);
    AttrNumber temp_attnum = attTup->attnum;

    ScanKeyData skey[2];
    Relation adrel = heap_open(AttrDefaultRelationId, RowExclusiveLock);
    ScanKeyInit(&skey[0], Anum_pg_attrdef_adrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(rel)));
    ScanKeyInit(&skey[1], Anum_pg_attrdef_adnum, BTEqualStrategyNumber, F_INT2EQ, Int16GetDatum(temp_attnum));
    SysScanDesc adscan = systable_beginscan(adrel, AttrDefaultIndexId, true, NULL, 2, skey);

    if (HeapTupleIsValid(htup = systable_getnext(adscan))) {
        Datum val = heap_getattr(htup, Anum_pg_attrdef_adsrc_on_update, adrel->rd_att, &isnull);
        if (val && pg_strcasecmp(TextDatumGetCString(val), "") != 0) {
            temp_on_update = TRUE;
        }
    }
    systable_endscan(adscan);
    heap_close(adrel, RowExclusiveLock);
    return temp_on_update;
}

/*
 * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
 */
static ObjectAddress ATExecColumnDefault(Relation rel, const char* colName, Node* newDefault, LOCKMODE lockmode)
{
    AttrNumber attnum;
    ObjectAddress address;
    TupleDesc tupdesc = RelationGetDescr(rel);

    /*
     * get the number of the attribute
     */
    attnum = get_attnum(RelationGetRelid(rel), colName);
    if (attnum == InvalidAttrNumber) {
        if (u_sess->attr.attr_common.IsInplaceUpgrade) {
            ereport(WARNING,
                (errcode(ERRCODE_UNDEFINED_COLUMN),
                    errmsg("column \"%s\" of relation \"%s\" does not exist while dropping default",
                        colName,
                        RelationGetRelationName(rel))));
            return InvalidObjectAddress;
        } else
            ereport(ERROR,
                (errcode(ERRCODE_UNDEFINED_COLUMN),
                    errmsg("column \"%s\" of relation \"%s\" does not exist", colName, RelationGetRelationName(rel))));
    }

    /* Prevent them from altering a system attribute */
    if (attnum <= 0)
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot alter system column \"%s\"", colName)));

    if (ISGENERATEDCOL(tupdesc, attnum - 1)) {
        ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_SYNTAX_ERROR),
            errmsg("column \"%s\" of relation \"%s\" is a generated column", colName, RelationGetRelationName(rel))));
    } else if (attnum == RelAutoIncAttrNum(rel)) {
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
            errmsg("cannot alter auto_increment column \"%s\" default", colName)));
    }

    bool on_update = FetchOnUpdateExpress(rel, colName);

    /*
     * Remove any old default for the column.  We use RESTRICT here for
     * safety, but at present we do not expect anything to depend on the
     * default.
     *
     * We treat removing the existing default as an internal operation when it
     * is preparatory to adding a new default, but as a user-initiated
     * operation when the user asked for a drop.
     */
    if (!on_update) {
        RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false, newDefault == NULL ? false : true);
    }

    if (newDefault != NULL || (on_update && newDefault == NULL)) {
        /* SET DEFAULT */
        RawColumnDefault* rawEnt = NULL;

        rawEnt = (RawColumnDefault*)palloc(sizeof(RawColumnDefault));
        rawEnt->attnum = attnum;
        rawEnt->raw_default = newDefault;
        rawEnt->generatedCol = '\0';
        rawEnt->update_expr = NULL;

        /*
         * This function is intended for CREATE TABLE, so it processes a
         * _list_ of defaults, but we just do one.
         */
        (void)AddRelationNewConstraints(rel, list_make1(rawEnt), NIL, false, true);
    }

    ObjectAddressSubSet(address, RelationRelationId,
                        RelationGetRelid(rel), attnum);
    return address;

}

/*
 * ALTER TABLE ALTER COLUMN SET STATISTICS
 */
static void ATPrepSetStatistics(Relation rel)
{
    /*
     * We do our own permission checking because (a) we want to allow SET
     * STATISTICS on indexes (for expressional index columns), and (b) we want
     * to allow SET STATISTICS on system catalogs without requiring
     * allowSystemTableMods to be turned on.
     */
    if (rel->rd_rel->relkind != RELKIND_RELATION && !RelationIsIndex(rel) && rel->rd_rel->relkind != RELKIND_STREAM &&
        rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE && rel->rd_rel->relkind != RELKIND_MATVIEW)
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("\"%s\" is not a table, materialized view, index, or foreign table", RelationGetRelationName(rel))));

    /* Permissions checks */
    AclResult aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), ACL_ALTER);
    if (aclresult != ACLCHECK_OK && !pg_class_ownercheck(RelationGetRelid(rel), GetUserId())) {
        aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS, RelationGetRelationName(rel));
    }
}

/*
 * Return value is the address of the modified column
 */
static ObjectAddress ATExecSetStatistics(
    Relation rel, const char* colName, Node* newValue, AlterTableStatProperty additional_property, LOCKMODE lockmode)
{
    int newtarget;
    Relation attrelation;
    HeapTuple tuple;
    Form_pg_attribute attrtuple;
    AttrNumber  attnum;
    ObjectAddress address;

    Assert(IsA(newValue, Integer));
    newtarget = intVal(newValue);

    /*
     * Limit target to a sane range
     */
    if (additional_property == AT_CMD_WithoutPercent) {
        if (newtarget < -1) {
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("statistics target %d is too low", newtarget)));
        } else if (newtarget > 10000) {
            newtarget = 10000;
            ereport(WARNING,
                (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("lowering statistics target to %d", newtarget)));
        }
    } else {
        // Example:additional_property == AT_CMD_WithPercent
        if (newtarget < 0 || newtarget > 100) {
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                    errmsg("statistics percent valid value is between 0 and 100")));
        }

        newtarget = -1 * newtarget - 1;  //-1 * newtarget - 1
    }
    attrelation = heap_open(AttributeRelationId, RowExclusiveLock);

    tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);

    if (!HeapTupleIsValid(tuple))
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_COLUMN),
                errmsg("column \"%s\" of relation \"%s\" does not exist", colName, RelationGetRelationName(rel))));
    attrtuple = (Form_pg_attribute)GETSTRUCT(tuple);

    attnum = attrtuple->attnum;
    if (attnum <= 0)
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot alter system column \"%s\"", colName)));

    attrtuple->attstattarget = newtarget;

    simple_heap_update(attrelation, &tuple->t_self, tuple);

    /* keep system catalog indexes current */
    CatalogUpdateIndexes(attrelation, tuple);
    ObjectAddressSubSet(address, RelationRelationId,
                        RelationGetRelid(rel), attnum);

    tableam_tops_free_tuple(tuple);

    heap_close(attrelation, RowExclusiveLock);

    return address;
}

/*
 * ATExecAddStatistics
 *     to execute add statistics
 *
 * @param (in) rel:
 *     the relation
 * @param (in) def:
 *     the alter table column definition
 * @param (in) lockmode:
 *     lock mode
 */
static void ATExecAddStatistics(Relation rel, Node* def, LOCKMODE lockmode)
{
    Assert(IsA(def, List));

    Oid relid = rel->rd_id;
    if (is_sys_table(relid)) {
        ereport(ERROR,
            ((errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("System catalog is not supported by extended statistic."))));
    }

    char relkind = es_get_starelkind();
    bool inh = es_get_stainherit();

    /* A list of VacAttrStats */
    int array_length = 0;
    VacAttrStats** vacattrstats_array = es_build_vacattrstats_array(rel, (List*)def, true, &array_length, inh);

    if (array_length > 0) {
        update_attstats(relid, relkind, false, array_length, vacattrstats_array, RelationGetRelPersistence(rel));
    }
}

/*
 * ATExecDeleteStatistics
 *     to execute delete statistics
 *
 * @param (in) rel:
 *     the relation
 * @param (in) def:
 *     the alter table column definition
 * @param (in) lockmode:
 *     lock mode
 */
static void ATExecDeleteStatistics(Relation rel, Node* def, LOCKMODE lockmode)
{
    Assert(IsA(def, List));

    Oid relid = rel->rd_id;
    if (is_sys_table(relid)) {
        ereport(ERROR,
            ((errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("System catalog is not supported by extended statistic."))));
    }

    char relkind = es_get_starelkind();
    bool inh = es_get_stainherit();

    /* A list of VacAttrStats */
    int array_length = 0;
    VacAttrStats** vacattrstats_array = es_build_vacattrstats_array(rel, (List*)def, false, &array_length, inh);

    if (array_length > 0) {
        delete_attstats(relid, relkind, false, array_length, vacattrstats_array, DELETE_STATS_MULTI);
    }
}

static ObjectAddress ATExecSetOptions(Relation rel, const char* colName, Node* options, bool isReset, LOCKMODE lockmode)
{
    Relation attrelation;
    HeapTuple tuple, newtuple;
    Form_pg_attribute attrtuple;
    AttrNumber  attnum;
    Datum datum, newOptions;
    bool isnull = false;
    Datum repl_val[Natts_pg_attribute];
    bool repl_null[Natts_pg_attribute];
    bool repl_repl[Natts_pg_attribute];
    errno_t rc = EOK;
    ObjectAddress address;

    attrelation = heap_open(AttributeRelationId, RowExclusiveLock);

    tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);

    if (!HeapTupleIsValid(tuple))
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_COLUMN),
                errmsg("column \"%s\" of relation \"%s\" does not exist", colName, RelationGetRelationName(rel))));
    attrtuple = (Form_pg_attribute)GETSTRUCT(tuple);

    attnum = attrtuple->attnum;
    if (attnum <= 0)
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot alter system column \"%s\"", colName)));

    Assert(IsA(options, List));
    ForbidToSetOptionsForAttribute((List*)options);

    /* Generate new proposed attoptions (text array) */
    datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions, &isnull);
    newOptions = transformRelOptions(isnull ? (Datum)0 : datum, (List*)options, NULL, NULL, false, isReset);
    /* Validate new options */
    (void)attribute_reloptions(newOptions, true);

    /* Build new tuple. */
    rc = memset_s(repl_null, sizeof(repl_null), false, sizeof(repl_null));
    securec_check(rc, "\0", "\0");
    rc = memset_s(repl_repl, sizeof(repl_repl), false, sizeof(repl_repl));
    securec_check(rc, "\0", "\0");
    if (newOptions != (Datum)0)
        repl_val[Anum_pg_attribute_attoptions - 1] = newOptions;
    else
        repl_null[Anum_pg_attribute_attoptions - 1] = true;
    repl_repl[Anum_pg_attribute_attoptions - 1] = true;
    newtuple = (HeapTuple) tableam_tops_modify_tuple(tuple, RelationGetDescr(attrelation), repl_val, repl_null, repl_repl);
    ReleaseSysCache(tuple);

    /* Update system catalog. */
    simple_heap_update(attrelation, &newtuple->t_self, newtuple);
    CatalogUpdateIndexes(attrelation, newtuple);
    ObjectAddressSubSet(address, RelationRelationId,
                        RelationGetRelid(rel), attnum);
    tableam_tops_free_tuple(newtuple);

    heap_close(attrelation, RowExclusiveLock);
    return address;
}

/*
 * ALTER TABLE ALTER COLUMN SET STORAGE
 *
 * Return value is the address of the modified column
 */
static ObjectAddress ATExecSetStorage(Relation rel, const char* colName, Node* newValue, LOCKMODE lockmode)
{
    char* storagemode = NULL;
    char newstorage;
    Relation attrelation;
    HeapTuple tuple;
    Form_pg_attribute attrtuple;
    AttrNumber  attnum;
    ObjectAddress address;

    Assert(IsA(newValue, String));
    storagemode = strVal(newValue);

    if (RelationIsColStore(rel)) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Storage type \"%s\" is meaningless for column relation", storagemode)));
    }

    if (pg_strcasecmp(storagemode, "plain") == 0)
        newstorage = 'p';
    else if (pg_strcasecmp(storagemode, "external") == 0)
        newstorage = 'e';
    else if (pg_strcasecmp(storagemode, "extended") == 0)
        newstorage = 'x';
    else if (pg_strcasecmp(storagemode, "main") == 0)
        newstorage = 'm';
    else {
        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid storage type \"%s\"", storagemode)));
        newstorage = 0; /* keep compiler quiet */
    }

    attrelation = heap_open(AttributeRelationId, RowExclusiveLock);

    tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);

    if (!HeapTupleIsValid(tuple))
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_COLUMN),
                errmsg("column \"%s\" of relation \"%s\" does not exist", colName, RelationGetRelationName(rel))));
    attrtuple = (Form_pg_attribute)GETSTRUCT(tuple);

    attnum = attrtuple->attnum;
    if (attnum <= 0)
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot alter system column \"%s\"", colName)));

    /*
     * safety check: do not allow toasted storage modes unless column datatype
     * is TOAST-aware.
     */
    if (newstorage == 'p' || TypeIsToastable(attrtuple->atttypid))
        attrtuple->attstorage = newstorage;
    else
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("column data type %s can only have storage PLAIN", format_type_be(attrtuple->atttypid))));

    simple_heap_update(attrelation, &tuple->t_self, tuple);

    /* keep system catalog indexes current */
    CatalogUpdateIndexes(attrelation, tuple);

    tableam_tops_free_tuple(tuple);

    heap_close(attrelation, RowExclusiveLock);
    ObjectAddressSubSet(address, RelationRelationId,
                        RelationGetRelid(rel), attnum);
    return address;
}

/*
 * ALTER TABLE DROP COLUMN
 *
 * DROP COLUMN cannot use the normal ALTER TABLE recursion mechanism,
 * because we have to decide at runtime whether to recurse or not depending
 * on whether attinhcount goes to zero or not.	(We can't check this in a
 * static pre-pass because it won't handle multiple inheritance situations
 * correctly.)
 */
static void ATPrepDropColumn(
    List** wqueue, Relation rel, bool recurse, bool recursing, AlterTableCmd* cmd, LOCKMODE lockmode)
{

#ifdef ENABLE_HTAP
    if (RelHasImcs(RelationGetRelid(rel))) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                errmsg("The rel enable imcstore, please unimcstore if want to drop column.")));
    }
#endif

    if (rel->rd_rel->reloftype && !recursing)
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot drop column from typed table")));

    if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
        ATTypedTableRecursion(wqueue, rel, cmd, lockmode);

    if (recurse)
        cmd->subtype = AT_DropColumnRecurse;
}
/*
 * Brief        : Check if the input column is the last column of the input Relation
 * Input        : rel, the input relation
 *                attrnum, the input attribute number which is start from 1
 * Output       : None
 * Return Value : true if it's the last column of the input relation, false otherwise
 */
static bool CheckLastColumn(Relation rel, AttrNumber attrnum)
{
    for (int col = 0; col < rel->rd_att->natts; ++col) {
        if (rel->rd_att->attrs[col].attisdropped)
            continue;
        if (ISGENERATEDCOL(rel->rd_att, col))
            continue;
        if (col != (attrnum - 1)) {
            return false;
        }
    }
    return true;
}


#ifdef ENABLE_MULTIPLE_NODES
/* Check if the ALTER TABLE DROP COLUMN operation is valid in timeseries table.
 * Invalid case:
 * 1. TSTime column is not allowed to drop
 * 2. At least one TSTag column should be remained
 *
 * Parameter:
 * - tsrel: an opened timeseries table
 * - attnum: AttrNumber of the column to be dropped
 * - colName: name of the column to be dropped
 */
static void CheckTsStoreDropColumn(const Relation tsrel, const AttrNumber attnum, const char* colName)
{
    int1 kvtype = tsrel->rd_att->attrs[attnum - 1]->attkvtype;
    if (kvtype == ATT_KV_TIMETAG) {
        ereport(ERROR, 
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("cannot drop the TSTime column \"%s\" from timeseries table", colName),
                errdetail("If the type of the column is TsTime, it cannot be dropped"),
                errcause("Unexpected error"),
                erraction("This column is not be dropped"),
                errmodule(MOD_TIMESERIES)));
    } else if (kvtype == ATT_KV_TAG && get_valid_natts_by_kvtype(tsrel, kvtype) <= 1) {
        ereport(ERROR, 
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("cannot drop the only TSTag column \"%s\" from timeseries table", colName),
                errdetail("When the column of TSTag is the last one, it cannot be dropped"),
                errcause("Unexpected error"),
                erraction("This column is not be dropped"),
                errmodule(MOD_TIMESERIES)));
    } else if (kvtype == ATT_KV_FIELD && get_valid_natts_by_kvtype(tsrel, kvtype) <= 1) {
        ereport(ERROR, 
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("cannot drop the only TSField column \"%s\" from timeseries table", colName),
                errdetail("When the column of TSField is the last one, it cannot be dropped"),
                errcause("Unexpected error"),
                erraction("This column is not be dropped"),
                errmodule(MOD_TIMESERIES)));
    }
}
#endif

static void ResetTempAutoIncrement(Relation rel, AttrNumber attnum)
{
    if (rel->rd_rel->relpersistence != RELPERSISTENCE_TEMP || RelAutoIncAttrNum(rel) != attnum) {
        return;
    }
    tmptable_autoinc_reset(rel->rd_rel->relfilenode, 1);
}

/*
 * Return value is that of the dropped column.
 */
static ObjectAddress  ATExecDropColumn(List** wqueue, Relation rel, const char* colName, DropBehavior behavior, bool recurse,
    bool recursing, bool missing_ok, LOCKMODE lockmode, bool fromReplace)
{
    HeapTuple tuple;
    Form_pg_attribute targetatt;
    AttrNumber attnum;
    List* children = NIL;
    ObjectAddress object;

    /* At top level, permission check was done in ATPrepCmd, else do it */
    if (recursing)
        ATSimplePermissions(rel, ATT_TABLE);

    /*
     * get the number of the attribute
     */
    tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
    if (!HeapTupleIsValid(tuple)) {
        if (!missing_ok) {
            ereport(ERROR,
                (errcode(ERRCODE_UNDEFINED_COLUMN),
                    errmsg("column \"%s\" of relation \"%s\" does not exist", colName, RelationGetRelationName(rel))));
        } else {
            ereport(NOTICE,
                (errmsg("column \"%s\" of relation \"%s\" does not exist, skipping",
                    colName,
                    RelationGetRelationName(rel))));
            return InvalidObjectAddress;
        }
    }
    targetatt = (Form_pg_attribute)GETSTRUCT(tuple);

    attnum = targetatt->attnum;

#ifdef ENABLE_MULTIPLE_NODES
    if (RelationIsTsStore(rel)) {
        CheckTsStoreDropColumn(rel, attnum, colName);
    }
#endif   /* ENABLE_MULTIPLE_NODES */

    /*
     * column of a partitioned table's partition key can not be dropped
     */
    if (is_partition_column(rel, attnum)) {
        ereport(
            ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot drop partitioning column \"%s\"", colName)));
    }

    /* Can't drop a system attribute, except OID */
    if (attnum <= 0 && attnum != ObjectIdAttributeNumber)
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot drop system column \"%s\"", colName)));

    /* Don't drop inherited columns */
    if (targetatt->attinhcount > 0 && !recursing)
        ereport(
            ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("cannot drop inherited column \"%s\"", colName)));

    ReleaseSysCache(tuple);

    /*
     * For a table, we don't allow to drop all the column.
     * We have to check if the drop column is the last column.
     * If it is, not allow to drop it.
     */
    if (!fromReplace && GetLocatorType(rel->rd_id) != LOCATOR_TYPE_HASH) {
        bool lastColumn = CheckLastColumn(rel, attnum);
        if (lastColumn) {
            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("must have at least one column")));
        }
    }

    if (u_sess->hook_cxt.invokePreDropColumnHook) {
        ((InvokePreDropColumnHookType)(u_sess->hook_cxt.invokePreDropColumnHook))(rel, attnum);
    }

    /*
     * Propagate to children as appropriate.  Unlike most other ALTER
     * routines, we have to do this one level of recursion at a time; we can't
     * use find_all_inheritors to do it in one pass.
     */
    if (RelationIsPAXFormat(rel)) {
        /* Add column for delta table. */
        children = lappend_oid(children, RelationGetDeltaRelId(rel));

        /* Get the lock to synchronize against concurrent drop. */
        LockRelationOid(RelationGetDeltaRelId(rel), lockmode);
        elog(DEBUG1,
            "[GET LOCK] Get the lock %d successfully on delta table of %s for altering operator.",
            lockmode,
            RelationGetRelationName(rel));
#ifdef ENABLE_MULTIPLE_NODES
    } else if (g_instance.attr.attr_storage.enable_delta_store && RelationIsCUFormat(rel)) {
#else
    /*
     * Under centrailzed mode, there may be unique index on delta table. When checking unique
     * constraint, unique index on delta will be used. So we ignore enable_delta_store here
     * and alter delta table at the same time.
     */
    } else if (RelationIsCUFormat(rel)) {
#endif
        /*
         * add cstore relation delta table to recurse, if col support inherit feture
         * we also need call find_inheritance_children as below
         */
        children = find_cstore_delta(rel, lockmode);
    } else {
        children = find_inheritance_children(RelationGetRelid(rel), lockmode);
    }

    if (children != NULL) {
        Relation attr_rel;
        ListCell* child = NULL;

        attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
        foreach (child, children) {
            Oid childrelid = lfirst_oid(child);
            Relation childrel;
            Form_pg_attribute childatt;

            /* find_inheritance_children already got lock */
            childrel = heap_open(childrelid, NoLock);
            CheckTableNotInUse(childrel, "ALTER TABLE");

            tuple = SearchSysCacheCopyAttName(childrelid, colName);
            if (!HeapTupleIsValid(tuple)) {
                /* shouldn't happen */
                Assert(0);
                ereport(ERROR,
                    (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                        errmsg("cache lookup failed for attribute \"%s\" of relation %u", colName, childrelid)));
            }
            childatt = (Form_pg_attribute)GETSTRUCT(tuple);

            /*
             * The detal table is not inherit table.
             */
            if (!RelationIsPAXFormat(rel) && !RelationIsCUFormat(rel) &&
                childatt->attinhcount <= 0) /* shouldn't happen */
                ereport(ERROR,
                    (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                        errmsg("relation %u has non-inherited attribute \"%s\"", childrelid, colName)));

            if (recurse) {
                if (RelationIsPAXFormat(rel) || RelationIsCUFormat(rel)) {
                    /*
                     * Delete this column of the delta table.
                     */
                    ATExecDropColumn(wqueue, childrel, colName, behavior, true, true, false, lockmode, fromReplace);
                } else if (childatt->attinhcount == 1 && !childatt->attislocal) {
                    /*
                     * If the child column has other definition sources, just
                     * decrement its inheritance count; if not, recurse to delete it.
                     * 
                     * Time to delete this child column, too
                     */

                    ATExecDropColumn(wqueue, childrel, colName, behavior, true, true, false, lockmode, fromReplace);
                } else {
                    /* Child column must survive my deletion */
                    childatt->attinhcount--;

                    simple_heap_update(attr_rel, &tuple->t_self, tuple);

                    /* keep the system catalog indexes current */
                    CatalogUpdateIndexes(attr_rel, tuple);

                    /* Make update visible */
                    CommandCounterIncrement();
                }
            } else {
                /*
                 * If we were told to drop ONLY in this table (no recursion),
                 * we need to mark the inheritors' attributes as locally
                 * defined rather than inherited.
                 */
                childatt->attinhcount--;
                childatt->attislocal = true;

                simple_heap_update(attr_rel, &tuple->t_self, tuple);

                /* keep the system catalog indexes current */
                CatalogUpdateIndexes(attr_rel, tuple);

                /* Make update visible */
                CommandCounterIncrement();
            }

            tableam_tops_free_tuple(tuple);

            heap_close(childrel, NoLock);
        }
        heap_close(attr_rel, RowExclusiveLock);
    }

    /*
     * If the dropped column has partial cluster key, must to update
     * relhasclusterkey in pg_class.
     */
    if (rel->rd_rel->relhasclusterkey && colHasPartialClusterKey(rel, attnum)) {
        SetRelHasClusterKey(rel, false);
    }

    /*
     * Delete the dependent objects in order and update the rel catalog
     */
    object.classId = RelationRelationId;
    object.objectId = RelationGetRelid(rel);
    object.objectSubId = attnum;

    performDeletion(&object, behavior, 0);

    /*
     * If it's a column table, when we drop the column, we also need to delete the
     * column information from the cudesc, and put the column file into the pending delete.
     */
    if (RelationIsCUFormat(rel)) {
        CStoreRelDropColumn(rel, attnum, rel->rd_rel->relowner);
    }
    ResetTempAutoIncrement(rel, attnum);

#ifdef ENABLE_MOT
    if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE && isMOTFromTblOid(RelationGetRelid(rel))) {
        AlterForeingTableCmd fcmd = {
            T_AlterForeingTableCmd,
            AT_DropColumn,
            rel,
            colName,
            nullptr,
            InvalidOid,
            nullptr
        };
        ATExecMOTAlterTable(&fcmd);
    }
#endif

#ifdef ENABLE_MULTIPLE_NODES
    if (unlikely(RelationIsTsStore(rel))) {
        /* drop column in tag table */
        if (rel->rd_att->attrs[attnum - 1]->attkvtype == ATT_KV_TAG) {
            Oid tag_relid = get_tag_relid(RelationGetRelationName(rel), rel->rd_rel->relnamespace);
            Relation tagrel = heap_open(tag_relid, lockmode);
            CheckTableNotInUse(tagrel, "ALTER TABLE");
            ATExecDropColumn(wqueue, tagrel, colName, behavior, false, false, true, lockmode, fromReplace);
            TagsCacheMgr::GetInstance().clear();

            heap_close(tagrel, NoLock);
        } else if (rel->rd_att->attrs[attnum - 1]->attkvtype == ATT_KV_FIELD && Tsdb::RelationEnablesTsdbDelta(rel)) {
            /* if drop TSField columns, update delta table simultaneously */
            Relation delta_rel = Tsdb::RelationGetDeltaRelation(rel, lockmode);
            CheckTableNotInUse(delta_rel, "ALTER TABLE");
            ATExecDropColumn(wqueue, delta_rel, colName, behavior, false, false, true, lockmode, fromReplace);
            heap_close(delta_rel, NoLock);
        }
    }
#endif   /* ENABLE_MULTIPLE_NODES */

    /*
     * If we dropped the OID column, must adjust pg_class.relhasoids and tell
     * Phase 3 to physically get rid of the column.  We formerly left the
     * column in place physically, but this caused subtle problems.  See
     * http://archives.postgresql.org/pgsql-hackers/2009-02/msg00363.php
     */
    if (attnum == ObjectIdAttributeNumber) {
        Relation class_rel;
        Form_pg_class tuple_class;
        AlteredTableInfo* tab = NULL;

        class_rel = heap_open(RelationRelationId, RowExclusiveLock);

        tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(RelationGetRelid(rel)));
        if (!HeapTupleIsValid(tuple)) {
            ereport(ERROR,
                (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                    errmsg("cache lookup failed for relation %u", RelationGetRelid(rel))));
        }
        tuple_class = (Form_pg_class)GETSTRUCT(tuple);

        tuple_class->relhasoids = false;
        simple_heap_update(class_rel, &tuple->t_self, tuple);

        /* Keep the catalog indexes up to date */
        CatalogUpdateIndexes(class_rel, tuple);

        heap_close(class_rel, RowExclusiveLock);

        /* Find or create work queue entry for this table */
        tab = ATGetQueueEntry(wqueue, rel);

        /* Tell Phase 3 to physically remove the OID column */
        tab->rewrite |= AT_REWRITE_ALTER_OID;
    }
    return object;
}

/**
 * Primary key/Unique constraint on cstore does not support deferrable
 */
FORCE_INLINE static void CheckCUConstraint(Relation rel, const IndexStmt* stmt)
{
    if (RelationIsCUFormat(rel) && stmt->isconstraint && (stmt->deferrable || stmt->initdeferred)) {
        ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                 errmsg("column store unsupport DEFERRABLE/INITIALLY DEFERRED on primary key/unique constraint")));
    }
}
/*
 * ALTER TABLE ADD INDEX
 *
 * There is no such command in the grammar, but parse_utilcmd.c converts
 * UNIQUE and PRIMARY KEY constraints into AT_AddIndex subcommands.  This lets
 * us schedule creation of the index at the appropriate time during ALTER.
 */
static ObjectAddress ATExecAddIndex(AlteredTableInfo* tab, Relation rel, IndexStmt* stmt, bool is_rebuild, LOCKMODE lockmode)
{
    bool check_rights = false;
    bool skip_build = false;
    bool quiet = false;
    ObjectAddress address;

    Assert(IsA(stmt, IndexStmt));
    Assert(!stmt->concurrent);

    CheckCUConstraint(rel, stmt);

    /* suppress schema rights check when rebuilding existing index */
    check_rights = !is_rebuild;
    /* skip index build if phase 3 will do it or we're reusing an old one */
    skip_build = tab->rewrite > 0 || OidIsValid(stmt->oldNode);
    /* suppress notices when rebuilding existing index */
    quiet = is_rebuild;

    /* The IndexStmt has already been through transformIndexStmt */
    WaitState oldStatus = pgstat_report_waitstatus(STATE_CREATE_INDEX);
    address  = DefineIndex(RelationGetRelid(rel),
        stmt,
        InvalidOid, /* no predefined OID */
        true,       /* is_alter_table */
        check_rights,
        skip_build,
        quiet,
        tab->is_modify_primary);
#ifndef ENABLE_MULTIPLE_NODES
    if (RelationIsCUFormat(rel) && (stmt->primary || stmt->unique)) {
        DefineDeltaUniqueIndex(RelationGetRelid(rel), stmt, address.objectId);
    }
#endif
    (void)pgstat_report_waitstatus(oldStatus);

    /*
     * If TryReuseIndex() stashed a relfilenode for us, we used it for the new
     * index instead of building from scratch.	The DROP of the old edition of
     * this index will have scheduled the storage for deletion at commit, so
     * cancel that pending deletion.
     */
    if (OidIsValid(stmt->oldNode)) {
        Relation irel = index_open(address.objectId, NoLock);
        /*
         * For global tmp table, the mapping between relid and RelFileNode are not recorded in pg_class,
         * so can not reuse relfilenode.
         */
        if (RELATION_IS_GLOBAL_TEMP(irel)) {
            index_close(irel, NoLock);
            return address;
        }

        if (!stmt->isPartitioned || stmt->isGlobal) {
            RelationPreserveStorage(irel->rd_node, true);
        } else {
            List* partOids = NIL;
            ListCell* cell = NULL;
            Partition partition = NULL;
            Oid partOid = InvalidOid;
            Oid partIndexOid = InvalidOid;

            partOids = relationGetPartitionOidList(rel);
            foreach (cell, partOids) {
                partOid = lfirst_oid(cell);
                partIndexOid = getPartitionIndexOid(RelationGetRelid(irel), partOid);

                partition = partitionOpen(irel, partIndexOid, NoLock);
                RelationPreserveStorage(partition->pd_node, true);
                partitionClose(irel, partition, NoLock);
            }
            releasePartitionOidList(&partOids);
        }

        index_close(irel, NoLock);
    }
    return address;
}

/*
 * ALTER TABLE ADD CONSTRAINT USING INDEX
 *
 * Returns the address of the new constraint.
 */
static ObjectAddress ATExecAddIndexConstraint(AlteredTableInfo* tab, Relation rel, IndexStmt* stmt, LOCKMODE lockmode)
{
    Oid index_oid = stmt->indexOid;
    Relation indexRel;
    char* indexName = NULL;
    IndexInfo* indexInfo = NULL;
    char* constraintName = NULL;
    char constraintType;
    ObjectAddress address;

    Assert(IsA(stmt, IndexStmt));
    Assert(OidIsValid(index_oid));
    Assert(stmt->isconstraint);

    indexRel = index_open(index_oid, AccessShareLock);

    indexName = pstrdup(RelationGetRelationName(indexRel));

    indexInfo = BuildIndexInfo(indexRel);

    /* this should have been checked at parse time */
    if (!indexInfo->ii_Unique)
        ereport(ERROR,
            (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("index \"%s\" is not unique", indexName)));
    /*
     * Determine name to assign to constraint.	We require a constraint to
     * have the same name as the underlying index; therefore, use the index's
     * existing name as the default constraint name, and if the user
     * explicitly gives some other name for the constraint, rename the index
     * to match.
     */
    constraintName = stmt->idxname;
    if (constraintName == NULL)
        constraintName = indexName;
    else if (strcmp(constraintName, indexName) != 0) {
        ereport(NOTICE,
            (errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index \"%s\" to \"%s\"",
                indexName,
                constraintName)));
        RenameRelationInternal(index_oid, constraintName);
    }

    /* Extra checks needed if making primary key */
    if (stmt->primary)
        index_check_primary_key(rel, indexInfo, true, stmt);

    /* Note we currently don't support EXCLUSION constraints here */
    if (stmt->primary)
        constraintType = CONSTRAINT_PRIMARY;
    else
        constraintType = CONSTRAINT_UNIQUE;

    /* Create the catalog entries for the constraint */
    address = index_constraint_create(rel,
        index_oid,
        indexInfo,
        constraintName,
        constraintType,
        stmt->deferrable,
        stmt->initdeferred,
        stmt->primary,
        true, /* update pg_index */
        true, /* remove old dependencies */
        (g_instance.attr.attr_common.allowSystemTableMods || u_sess->attr.attr_common.IsInplaceUpgrade), 
        stmt->isvalidated,
        stmt->isdisable);
    /* index constraint */
    CreateNonColumnComment(index_oid, stmt->indexOptions, RelationRelationId);

    index_close(indexRel, NoLock);
    return address;
}

/*
 * ALTER TABLE ADD CONSTRAINT
 *
 * Return value is the address of the new constraint; if no constraint was
 * added, InvalidObjectAddress is returned.
 */
static ObjectAddress ATExecAddConstraint(List** wqueue, AlteredTableInfo* tab, Relation rel, Constraint* newConstraint,
    bool recurse, bool is_readd, LOCKMODE lockmode)
{
    ObjectAddress address = InvalidObjectAddress;
    Assert(IsA(newConstraint, Constraint));

    if (RelationIsColStore(rel) && !CStoreSupportConstraint(newConstraint))
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("column store unsupport constraint \"%s\"", GetConstraintType(newConstraint->contype))));

    if (rel->rd_tam_ops == TableAmUstore && newConstraint->deferrable == true) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmodule(MOD_COMMAND),
                errmsg("Ustore table does not support to set deferrable."),
                errdetail("N/A"),
                errcause("feature not supported"),
                erraction("check constraints of columns")));
    }

    /*
     * Currently, we only expect to see CONSTR_CHECK and CONSTR_FOREIGN nodes
     * arriving here (see the preprocessing done in parse_utilcmd.c).  Use a
     * switch anyway to make it easier to add more code later.
     */
    switch (newConstraint->contype) {
        case CONSTR_CHECK:
            address = ATAddCheckConstraint(wqueue, tab, rel, newConstraint, recurse, false, is_readd, lockmode);
            break;

        case CONSTR_FOREIGN:

            /*
             * Note that we currently never recurse for FK constraints, so the
             * "recurse" flag is silently ignored.
             *
             * Assign or validate constraint name
             */
            if (newConstraint->conname) {
                if (ConstraintNameIsUsed(
                        CONSTRAINT_RELATION, RelationGetRelid(rel), RelationGetNamespace(rel), newConstraint->conname))
                    ereport(ERROR,
                        (errcode(ERRCODE_DUPLICATE_OBJECT),
                            errmsg("constraint \"%s\" for relation \"%s\" already exists",
                                newConstraint->conname,
                                RelationGetRelationName(rel))));
            } else
                newConstraint->conname = ChooseConstraintName(RelationGetRelationName(rel),
                    strVal(linitial(newConstraint->fk_attrs)),
                    "fkey",
                    RelationGetNamespace(rel),
                    NIL);

            address = ATAddForeignKeyConstraint(tab, rel, newConstraint, lockmode);
            break;
        case CONSTR_CLUSTER:
            if (rel->rd_rel->relhasclusterkey && !is_readd)
                ereport(ERROR,
                    (errcode(ERRCODE_DUPLICATE_OBJECT),
                        errmsg("cluster key for relation \"%s\" already exists", RelationGetRelationName(rel))));
            else {
                List * tmp = AddRelClusterConstraints(rel, list_make1(newConstraint));
                Assert(tmp != NIL && list_length(tmp) == 1);
                address = *((ObjectAddress*)linitial(tmp));
            }

            break;
        default: {
            ereport(ERROR,
                (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                    errmsg("unrecognized constraint type: %d", (int)newConstraint->contype)));
        }
    }
    return address;
}

/*
 * Add a check constraint to a single table and its children
 *
 * Subroutine for ATExecAddConstraint.
 *
 * We must recurse to child tables during execution, rather than using
 * ALTER TABLE's normal prep-time recursion.  The reason is that all the
 * constraints *must* be given the same name, else they won't be seen as
 * related later.  If the user didn't explicitly specify a name, then
 * AddRelationNewConstraints would normally assign different names to the
 * child constraints.  To fix that, we must capture the name assigned at
 * the parent table and pass that down.
 *
 * When re-adding a previously existing constraint (during ALTER COLUMN TYPE),
 * we don't need to recurse here, because recursion will be carried out at a
 * higher level; the constraint name issue doesn't apply because the names
 * have already been assigned and are just being re-used.  We need a separate
 * "is_readd" flag for that; just setting recurse=false would result in an
 * error if there are child tables.
 */
static ObjectAddress ATAddCheckConstraint(List** wqueue, AlteredTableInfo* tab, Relation rel, Constraint* constr, bool recurse,
    bool recursing, bool is_readd, LOCKMODE lockmode)
{
    List* newcons = NIL;
    ListCell* lcon = NULL;
    List* children = NIL;
    ListCell* child = NULL;
    ObjectAddress address = InvalidObjectAddress;

    /* At top level, permission check was done in ATPrepCmd, else do it */
    if (recursing)
        ATSimplePermissions(rel, ATT_TABLE);

    /*
     * Call AddRelationNewConstraints to do the work, making sure it works on
     * a copy of the Constraint so transformExpr can't modify the original. It
     * returns a list of cooked constraints.
     *
     * If the constraint ends up getting merged with a pre-existing one, it's
     * omitted from the returned list, which is what we want: we do not need
     * to do any validation work.  That can only happen at child tables,
     * though, since we disallow merging at the top level.
     */
    newcons = AddRelationNewConstraints(rel,
        NIL,
        list_make1(copyObject(constr)),
        recursing,   /* allow_merge */
        !recursing); /* is_local */
    /* we don't expect more than one constraint here */
    Assert(list_length(newcons) <= 1);


    /* Add each to-be-validated constraint to Phase 3's queue */
    foreach (lcon, newcons) {
        CookedConstraint* ccon = (CookedConstraint*)lfirst(lcon);

        if (!ccon->skip_validation) {
            NewConstraint* newcon = NULL;

            newcon = (NewConstraint*)palloc0(sizeof(NewConstraint));
            newcon->name = ccon->name;
            newcon->contype = ccon->contype;
            newcon->isdisable = ccon->isdisable;
            /* ExecQual wants implicit-AND format */
            newcon->qual = (Node*)make_ands_implicit((Expr*)ccon->expr);

            tab->constraints = lappend(tab->constraints, newcon);
        }

        /* Save the actually assigned name if it was defaulted */
        if (constr->conname == NULL)
            constr->conname = ccon->name;

        ObjectAddressSet(address, ConstraintRelationId, ccon->conoid);
    }

    /* At this point we must have a locked-down name to use */
    Assert(constr->conname != NULL);

    /* Advance command counter in case same table is visited multiple times */
    CommandCounterIncrement();

    /*
     * If the constraint got merged with an existing constraint, we're done.
     * We mustn't recurse to child tables in this case, because they've
     * already got the constraint, and visiting them again would lead to an
     * incorrect value for coninhcount.
     */
    if (newcons == NIL)
        return address;

    /*
     * If adding a NO INHERIT constraint, no need to find our children.
     * Likewise, in a re-add operation, we don't need to recurse (that will be
     * handled at higher levels).
     */
    if (constr->is_no_inherit || is_readd)
        return address;

    /*
     * Propagate to children as appropriate.  Unlike most other ALTER
     * routines, we have to do this one level of recursion at a time; we can't
     * use find_all_inheritors to do it in one pass.
     */
    children = find_inheritance_children(RelationGetRelid(rel), lockmode);

    /*
     * Check if ONLY was specified with ALTER TABLE.  If so, allow the
     * contraint creation only if there are no children currently.	Error out
     * otherwise.
     */
    if (!recurse && children != NIL)
        ereport(
            ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("constraint must be added to child tables too")));

    foreach (child, children) {
        Oid childrelid = lfirst_oid(child);
        Relation childrel;
        AlteredTableInfo* childtab = NULL;

        /* find_inheritance_children already got lock */
        childrel = heap_open(childrelid, NoLock);
        CheckTableNotInUse(childrel, "ALTER TABLE");

        /* Find or create work queue entry for this table */
        childtab = ATGetQueueEntry(wqueue, childrel);

        /* Recurse to child */
        ATAddCheckConstraint(wqueue, childtab, childrel, constr, recurse, true, is_readd, lockmode);

        heap_close(childrel, NoLock);
    }
    return address;
}

/*
 * Add a foreign-key constraint to a single table, return the new constraint's
 * address.
 *
 * Subroutine for ATExecAddConstraint.	Must already hold exclusive
 * lock on the rel, and have done appropriate validity checks for it.
 * We do permissions checks here, however.
 */
static ObjectAddress ATAddForeignKeyConstraint(AlteredTableInfo* tab, Relation rel, Constraint* fkconstraint, LOCKMODE lockmode)
{
    Relation pkrel;
    int16 pkattnum[INDEX_MAX_KEYS];
    int16 fkattnum[INDEX_MAX_KEYS];
    Oid pktypoid[INDEX_MAX_KEYS];
    Oid fktypoid[INDEX_MAX_KEYS];
    Oid opclasses[INDEX_MAX_KEYS];
    Oid pfeqoperators[INDEX_MAX_KEYS];
    Oid ppeqoperators[INDEX_MAX_KEYS];
    Oid ffeqoperators[INDEX_MAX_KEYS];
    int i;
    int numfks, numpks;
    Oid indexOid;
    Oid constrOid;
    bool old_check_ok = false;
    ListCell* old_pfeqop_item = list_head(fkconstraint->old_conpfeqop);
    ObjectAddress address;

    /*
     * Grab ShareRowExclusiveLock on the pk table, so that someone doesn't
	 * delete rows out from under us.
     */
    if (OidIsValid(fkconstraint->old_pktable_oid))
        pkrel = heap_open(fkconstraint->old_pktable_oid, ShareRowExclusiveLock);
    else
        pkrel = heap_openrv(fkconstraint->pktable, ShareRowExclusiveLock);

    /*
     * Validity checks (permission checks wait till we have the column
     * numbers)
     */
    if (pkrel->rd_rel->relkind != RELKIND_RELATION)
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("referenced relation \"%s\" is not a table", RelationGetRelationName(pkrel))));

    if (!g_instance.attr.attr_common.allowSystemTableMods && !u_sess->attr.attr_common.IsInplaceUpgrade &&
        IsSystemRelation(pkrel))
        ereport(ERROR,
            (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                errmsg("permission denied: \"%s\" is a system catalog", RelationGetRelationName(pkrel))));
	
#ifdef ENABLE_MULTIPLE_NODES
    if (RELATION_IS_PARTITIONED(pkrel))
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("Invalid FOREIGN KEY constraints"),
                errdetail("Partitioned table cannot be referenced table")));
#endif

    /*
     * References from permanent or unlogged tables to temp tables, and from
     * permanent tables to unlogged tables, are disallowed because the
     * referenced data can vanish out from under us.  References from temp
     * tables to any other table type are also disallowed, because other
     * backends might need to run the RI triggers on the perm table, but they
     * can't reliably see tuples in the local buffers of other backends.
     */
    switch (rel->rd_rel->relpersistence) {
        case RELPERSISTENCE_PERMANENT:
            if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT)
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                        errmsg("constraints on permanent tables may reference only permanent tables")));
            break;
        case RELPERSISTENCE_UNLOGGED:
            if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT &&
                pkrel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED)
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                        errmsg("constraints on unlogged tables may reference only permanent or unlogged tables")));
            break;
        case RELPERSISTENCE_TEMP:
            if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                        errmsg("constraints on temporary tables may reference only temporary tables")));
            if (!RelationIsLocalTemp(pkrel) || !RelationIsLocalTemp(rel))
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                        errmsg("constraints on temporary tables must involve temporary tables of this session")));
            break;
        case RELPERSISTENCE_GLOBAL_TEMP:
            if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP) {
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                        errmsg("constraints on global temporary tables may reference only global temporary tables")));
            }
            break;
        default:
            ereport(ERROR,
                (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                    errmsg("unrecognized table type: %d", (int)rel->rd_rel->relpersistence)));
    }

    /*
     * Look up the referencing attributes to make sure they exist, and record
     * their attnums and type OIDs.
     */
    errno_t rc;
    rc = memset_s(pkattnum, sizeof(pkattnum), 0, sizeof(pkattnum));
    securec_check(rc, "", "");
    rc = memset_s(fkattnum, sizeof(fkattnum), 0, sizeof(fkattnum));
    securec_check(rc, "", "");
    rc = memset_s(pktypoid, sizeof(pktypoid), 0, sizeof(pktypoid));
    securec_check(rc, "", "");
    rc = memset_s(fktypoid, sizeof(fktypoid), 0, sizeof(fktypoid));
    securec_check(rc, "", "");
    rc = memset_s(opclasses, sizeof(opclasses), 0, sizeof(opclasses));
    securec_check(rc, "", "");
    rc = memset_s(pfeqoperators, sizeof(pfeqoperators), 0, sizeof(pfeqoperators));
    securec_check(rc, "", "");
    rc = memset_s(ppeqoperators, sizeof(ppeqoperators), 0, sizeof(ppeqoperators));
    securec_check(rc, "", "");
    rc = memset_s(ffeqoperators, sizeof(ffeqoperators), 0, sizeof(ffeqoperators));
    securec_check(rc, "", "");

    numfks = transformColumnNameList(RelationGetRelid(rel), fkconstraint->fk_attrs, fkattnum, fktypoid);

    /*
     * If the attribute list for the referenced table was omitted, lookup the
     * definition of the primary key and use it.  Otherwise, validate the
     * supplied attribute list.  In either case, discover the index OID and
     * index opclasses, and the attnums and type OIDs of the attributes.
     */
    if (fkconstraint->pk_attrs == NIL) {
        numpks = transformFkeyGetPrimaryKey(pkrel, &indexOid, &fkconstraint->pk_attrs, pkattnum, pktypoid, opclasses);
    } else {
        numpks = transformColumnNameList(RelationGetRelid(pkrel), fkconstraint->pk_attrs, pkattnum, pktypoid);
        /* Look for an index matching the column list */
        indexOid = transformFkeyCheckAttrs(pkrel, numpks, pkattnum, opclasses);
    }

    /*
     * Now we can check permissions.
     */
    checkFkeyPermissions(pkrel, pkattnum, numpks);
    checkFkeyPermissions(rel, fkattnum, numfks);

    /*
     * Check some things for generated columns.
     */
    for (i = 0; i < numfks; i++)
    {
        char generated = GetGeneratedCol(RelationGetDescr(rel), fkattnum[i] - 1);

        if (generated)
        {
            /*
             * Check restrictions on UPDATE/DELETE actions, per SQL standard
             */
            if (fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETNULL ||
                fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETDEFAULT ||
                fkconstraint->fk_upd_action == FKCONSTR_ACTION_CASCADE)
                ereport(ERROR,
                        (errmodule(MOD_GEN_COL), errcode(ERRCODE_SYNTAX_ERROR),
                         errmsg("invalid %s action for foreign key constraint containing generated column",
                                "ON UPDATE")));
            if (fkconstraint->fk_del_action == FKCONSTR_ACTION_SETNULL ||
                fkconstraint->fk_del_action == FKCONSTR_ACTION_SETDEFAULT)
                ereport(ERROR,
                        (errmodule(MOD_GEN_COL), errcode(ERRCODE_SYNTAX_ERROR),
                         errmsg("invalid %s action for foreign key constraint containing generated column",
                                "ON DELETE")));
        }
    }

    /*
     * Look up the equality operators to use in the constraint.
     *
     * Note that we have to be careful about the difference between the actual
     * PK column type and the opclass' declared input type, which might be
     * only binary-compatible with it.	The declared opcintype is the right
     * thing to probe pg_amop with.
     */
    if (numfks != numpks)
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_FOREIGN_KEY),
                errmsg("number of referencing and referenced columns for foreign key disagree")));

    /*
     * On the strength of a previous constraint, we might avoid scanning
     * tables to validate this one.  See below.
     */
    old_check_ok = (fkconstraint->old_conpfeqop != NIL);
    Assert(!old_check_ok || numfks == list_length(fkconstraint->old_conpfeqop));

    for (i = 0; i < numpks; i++) {
        Oid pktype = pktypoid[i];
        Oid fktype = fktypoid[i];
        Oid fktyped;
        HeapTuple cla_ht;
        Form_pg_opclass cla_tup;
        Oid amid;
        Oid opfamily;
        Oid opcintype;
        Oid pfeqop;
        Oid ppeqop;
        Oid ffeqop;
        int16 eqstrategy;
        Oid pfeqop_right;

        /* We need several fields out of the pg_opclass entry */
        cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclasses[i]));
        if (!HeapTupleIsValid(cla_ht)) {
            ereport(ERROR,
                (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for opclass %u", opclasses[i])));
        }
        cla_tup = (Form_pg_opclass)GETSTRUCT(cla_ht);
        amid = cla_tup->opcmethod;
        opfamily = cla_tup->opcfamily;
        opcintype = cla_tup->opcintype;
        ReleaseSysCache(cla_ht);

        /*
         * Check it's a btree; currently this can never fail since no other
         * index AMs support unique indexes.  If we ever did have other types
         * of unique indexes, we'd need a way to determine which operator
         * strategy number is equality.  (Is it reasonable to insist that
         * every such index AM use btree's number for equality?)
         */
        if (!OID_IS_BTREE(amid))
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("only b-tree indexes are supported for foreign keys")));
        eqstrategy = BTEqualStrategyNumber;

        /*
         * There had better be a primary equality operator for the index.
         * We'll use it for PK = PK comparisons.
         */
        ppeqop = get_opfamily_member(opfamily, opcintype, opcintype, eqstrategy);

        if (!OidIsValid(ppeqop))
            ereport(ERROR,
                (errcode(ERRCODE_UNDEFINED_OBJECT),
                    errmsg("missing operator %d(%u,%u) in opfamily %u", eqstrategy, opcintype, opcintype, opfamily)));
        /*
         * Are there equality operators that take exactly the FK type? Assume
         * we should look through any domain here.
         */
        fktyped = getBaseType(fktype);

        pfeqop = get_opfamily_member(opfamily, opcintype, fktyped, eqstrategy);
        if (OidIsValid(pfeqop)) {
            pfeqop_right = fktyped;
            ffeqop = get_opfamily_member(opfamily, fktyped, fktyped, eqstrategy);
        } else {
            /* keep compiler quiet */
            pfeqop_right = InvalidOid;
            ffeqop = InvalidOid;
        }

        if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop))) {
            /*
             * Otherwise, look for an implicit cast from the FK type to the
             * opcintype, and if found, use the primary equality operator.
             * This is a bit tricky because opcintype might be a polymorphic
             * type such as ANYARRAY or ANYENUM; so what we have to test is
             * whether the two actual column types can be concurrently cast to
             * that type.  (Otherwise, we'd fail to reject combinations such
             * as int[] and point[].)
             */
            Oid input_typeids[2];
            Oid target_typeids[2];

            input_typeids[0] = pktype;
            input_typeids[1] = fktype;
            target_typeids[0] = opcintype;
            target_typeids[1] = opcintype;
            if (can_coerce_type(2, input_typeids, target_typeids, COERCION_IMPLICIT)) {
                pfeqop = ffeqop = ppeqop;
                pfeqop_right = opcintype;
            }
        }

        if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
            ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
                    errmsg("foreign key constraint \"%s\" "
                           "cannot be implemented",
                        fkconstraint->conname),
                    errdetail("Key columns \"%s\" and \"%s\" "
                              "are of incompatible types: %s and %s.",
                        strVal(list_nth(fkconstraint->fk_attrs, i)),
                        strVal(list_nth(fkconstraint->pk_attrs, i)),
                        format_type_be(fktype),
                        format_type_be(pktype))));

        if (old_check_ok) {
            /*
             * When a pfeqop changes, revalidate the constraint.  We could
             * permit intra-opfamily changes, but that adds subtle complexity
             * without any concrete benefit for core types.  We need not
             * assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
             */
            old_check_ok = (pfeqop == lfirst_oid(old_pfeqop_item));
            old_pfeqop_item = lnext(old_pfeqop_item);
        }
        if (old_check_ok) {
            Oid old_fktype;
            Oid new_fktype;
            CoercionPathType old_pathtype;
            CoercionPathType new_pathtype;
            Oid old_castfunc;
            Oid new_castfunc;

            /*
             * Identify coercion pathways from each of the old and new FK-side
             * column types to the right (foreign) operand type of the pfeqop.
             * We may assume that pg_constraint.conkey is not changing.
             */
            old_fktype = tab->oldDesc->attrs[fkattnum[i] - 1].atttypid;
            new_fktype = fktype;
            old_pathtype = findFkeyCast(pfeqop_right, old_fktype, &old_castfunc);
            new_pathtype = findFkeyCast(pfeqop_right, new_fktype, &new_castfunc);

            /*
             * Upon a change to the cast from the FK column to its pfeqop
             * operand, revalidate the constraint.	For this evaluation, a
             * binary coercion cast is equivalent to no cast at all.  While
             * type implementors should design implicit casts with an eye
             * toward consistency of operations like equality, we cannot
             * assume here that they have done so.
             *
             * A function with a polymorphic argument could change behavior
             * arbitrarily in response to get_fn_expr_argtype().  Therefore,
             * when the cast destination is polymorphic, we only avoid
             * revalidation if the input type has not changed at all.  Given
             * just the core data types and operator classes, this requirement
             * prevents no would-be optimizations.
             *
             * If the cast converts from a base type to a domain thereon, then
             * that domain type must be the opcintype of the unique index.
             * Necessarily, the primary key column must then be of the domain
             * type.  Since the constraint was previously valid, all values on
             * the foreign side necessarily exist on the primary side and in
             * turn conform to the domain.	Consequently, we need not treat
             * domains specially here.
             *
             * Since we require that all collations share the same notion of
             * equality (which they do, because texteq reduces to bitwise
             * equality), we don't compare collation here.
             *
             * We need not directly consider the PK type.  It's necessarily
             * binary coercible to the opcintype of the unique index column,
             * and ri_triggers.c will only deal with PK datums in terms of
             * that opcintype.	Changing the opcintype also changes pfeqop.
             */
            old_check_ok = (new_pathtype == old_pathtype && new_castfunc == old_castfunc &&
                            (!IsPolymorphicType(pfeqop_right) || new_fktype == old_fktype));
        }

        pfeqoperators[i] = pfeqop;
        ppeqoperators[i] = ppeqop;
        ffeqoperators[i] = ffeqop;
    }

#ifdef PGXC
    /* Check the shippability of this foreign key */
    if (IS_PGXC_COORDINATOR) {
        List* childRefs = NIL;
        List* parentRefs = NIL;

        /* Prepare call for shippability check */
        for (i = 0; i < numfks; i++)
            childRefs = lappend_int(childRefs, fkattnum[i]);
        for (i = 0; i < numpks; i++)
            parentRefs = lappend_int(parentRefs, pkattnum[i]);

        /* Now check shippability for this foreign key */
        if (!pgxc_check_fk_shippability(GetRelationLocInfo(RelationGetRelid(pkrel)),
                GetRelationLocInfo(RelationGetRelid(rel)),
                parentRefs,
                childRefs))
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("Cannot create foreign key whose evaluation cannot be enforced to remote nodes")));
    }
#endif

    /*
     * Record the FK constraint in pg_constraint.
     */
    constrOid = CreateConstraintEntry(fkconstraint->conname,
        RelationGetNamespace(rel),
        CONSTRAINT_FOREIGN,
        fkconstraint->deferrable,
        fkconstraint->initdeferred,
        fkconstraint->initially_valid,
        RelationGetRelid(rel),
        fkattnum,
        numfks,
        numfks,
        InvalidOid, /* not a domain
                     * constraint */
        indexOid,
        RelationGetRelid(pkrel),
        pkattnum,
        pfeqoperators,
        ppeqoperators,
        ffeqoperators,
        numpks,
        fkconstraint->fk_upd_action,
        fkconstraint->fk_del_action,
        fkconstraint->fk_matchtype,
        NULL, /* no exclusion constraint */
        NULL, /* no check constraint */
        NULL,
        NULL,
        true,                           /* islocal */
        0,                              /* inhcount */
        true,                           /* isnoinherit */
        fkconstraint->inforConstraint); /* @hdfs informational constraint */

    ObjectAddressSet(address, ConstraintRelationId, constrOid);
    /* foreign key constraint */
    CreateNonColumnComment(constrOid, fkconstraint->constraintOptions, ConstraintRelationId);

    /*
     * Create the triggers that will enforce the constraint.
     */
    createForeignKeyTriggers(rel, RelationGetRelid(pkrel), fkconstraint, constrOid, indexOid);

    /*
     * Tell Phase 3 to check that the constraint is satisfied by existing
     * rows. We can skip this during table creation, when requested explicitly
     * by specifying NOT VALID in an ADD FOREIGN KEY command, and when we're
     * recreating a constraint following a SET DATA TYPE operation that did
     * not impugn its validity.
     */
    if (!old_check_ok && !fkconstraint->skip_validation) {
        NewConstraint* newcon = (NewConstraint*)palloc0(sizeof(NewConstraint));
        newcon->name = fkconstraint->conname;
        newcon->contype = CONSTR_FOREIGN;
        newcon->refrelid = RelationGetRelid(pkrel);
        newcon->refindid = indexOid;
        newcon->conid = constrOid;
        newcon->qual = (Node*)fkconstraint;
        newcon->isdisable = false;

        tab->constraints = lappend(tab->constraints, newcon);
    }

    /*
     * Close pk table, but keep lock until we've committed.
     */
    heap_close(pkrel, NoLock);
    return address;
}

/*
 * ALTER TABLE VALIDATE CONSTRAINT
 *
 * XXX The reason we handle recursion here rather than at Phase 1 is because
 * there's no good way to skip recursing when handling foreign keys: there is
 * no need to lock children in that case, yet we wouldn't be able to avoid
 * doing so at that level.
 *
 * Return value is the address of the validated constraint.  If the constraint
 * was already validated, InvalidObjectAddress is returned.
 */
static ObjectAddress ATExecValidateConstraint(Relation rel, char* constrName, bool recurse, bool recursing, LOCKMODE lockmode)
{
    Relation conrel;
    SysScanDesc scan;
    ScanKeyData key;
    HeapTuple tuple;
    Form_pg_constraint con = NULL;
    bool found = false;
    ObjectAddress address;

    conrel = heap_open(ConstraintRelationId, RowExclusiveLock);

    /*
     * Find and check the target constraint
     */
    ScanKeyInit(
        &key, Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(rel)));
    scan = systable_beginscan(conrel, ConstraintRelidIndexId, true, NULL, 1, &key);

    while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
        con = (Form_pg_constraint)GETSTRUCT(tuple);
        if (strcmp(NameStr(con->conname), constrName) == 0) {
            found = true;
            break;
        }
    }

    if (!found)
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_OBJECT),
                errmsg(
                    "constraint \"%s\" of relation \"%s\" does not exist", constrName, RelationGetRelationName(rel))));

    if (con->contype != CONSTRAINT_FOREIGN && con->contype != CONSTRAINT_CHECK)
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key or check constraint",
                    constrName,
                    RelationGetRelationName(rel))));

    if (!con->convalidated) {
        HeapTuple copyTuple;
        Form_pg_constraint copy_con;

        if (con->contype == CONSTRAINT_FOREIGN) {
            Relation refrel;

            /*
             * Triggers are already in place on both tables, so a concurrent
             * write that alters the result here is not possible. Normally we
             * can run a query here to do the validation, which would only
             * require AccessShareLock. In some cases, it is possible that we
             * might need to fire triggers to perform the check, so we take a
             * lock at RowShareLock level just in case.
             */
            refrel = heap_open(con->confrelid, RowShareLock);

            validateForeignKeyConstraint(constrName, rel, refrel, con->conindid, HeapTupleGetOid(tuple));
            heap_close(refrel, NoLock);

            /*
             * Foreign keys do not inherit, so we purposely ignore the
             * recursion bit here
             */
        } else if (con->contype == CONSTRAINT_CHECK) {
            List* children = NIL;
            ListCell* child = NULL;

            /*
             * If we're recursing, the parent has already done this, so skip
             * it.
             */
            if (!recursing)
                children = find_all_inheritors(RelationGetRelid(rel), lockmode, NULL);

            /*
             * For CHECK constraints, we must ensure that we only mark the
             * constraint as validated on the parent if it's already validated
             * on the children.
             *
             * We recurse before validating on the parent, to reduce risk of
             * deadlocks.
             */
            foreach (child, children) {
                Oid childoid = lfirst_oid(child);
                Relation childrel;

                if (childoid == RelationGetRelid(rel))
                    continue;

                /*
                 * If we are told not to recurse, there had better not be any
                 * child tables; else the addition would put them out of step.
                 */
                if (!recurse)
                    ereport(ERROR,
                        (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                            errmsg("constraint must be validated on child tables too")));

                /* find_all_inheritors already got lock */
                childrel = heap_open(childoid, NoLock);

                ATExecValidateConstraint(childrel, constrName, false, true, lockmode);
                heap_close(childrel, NoLock);
            }

            if (!RelationIsPartitioned(rel)) {
                if (RELATION_CREATE_BUCKET(rel)) {
                    /* validate constraint for every buckets */
                    validateCheckConstraintForBucket(rel, NULL, tuple);
                } else {
                    validateCheckConstraint(rel, tuple);
                }
            } else if (RelationIsSubPartitioned(rel)) {
                List* partitions = NIL;
                ListCell* cell = NULL;
                Partition partition = NULL;
                Relation partRel = NULL;

                partitions = relationGetPartitionList(rel, lockmode);
                foreach (cell, partitions) {
                    partition = (Partition)lfirst(cell);
                    partRel = partitionGetRelation(rel, partition);

                    List *subpartitions = relationGetPartitionList(partRel, lockmode);
                    ListCell *subcell = NULL;
                    foreach (subcell, subpartitions) {
                        Partition subpartition = (Partition)lfirst(subcell);
                        if (RELATION_OWN_BUCKETKEY(rel)) {
                            /* validate constraint for every buckets */
                            validateCheckConstraintForBucket(partRel, subpartition, tuple);
                        } else {
                            Relation subpartRel = partitionGetRelation(partRel, subpartition);

                            validateCheckConstraint(subpartRel, tuple);

                            releaseDummyRelation(&subpartRel);
                        }
                    }
                    releasePartitionList(partRel, &subpartitions, lockmode);
                    releaseDummyRelation(&partRel);
                }
                releasePartitionList(rel, &partitions, lockmode);
            } else {
                List* partitions = NIL;
                ListCell* cell = NULL;
                Partition partition = NULL;
                Relation partRel = NULL;

                partitions = relationGetPartitionList(rel, lockmode);
                foreach (cell, partitions) {
                    partition = (Partition)lfirst(cell);
                    if (RELATION_OWN_BUCKETKEY(rel)) {
                        /* validate constraint for every buckets */
                        validateCheckConstraintForBucket(rel, partition, tuple);
                    } else {
                        partRel = partitionGetRelation(rel, partition);

                        validateCheckConstraint(partRel, tuple);

                        releaseDummyRelation(&partRel);
                    }
                }
                releasePartitionList(rel, &partitions, lockmode);
            }

            /*
             * Invalidate relcache so that others see the new validated
             * constraint.
             */
            CacheInvalidateRelcache(rel);
        }

        /*
         * Now update the catalog, while we have the door open.
         */
        Datum repl_val[Natts_pg_constraint];
        bool repl_null[Natts_pg_constraint];
        bool repl_repl[Natts_pg_constraint];
        errno_t rc = EOK;
        rc= memset_s(repl_null, sizeof(repl_null), false, sizeof(repl_null));
        securec_check(rc, "\0", "\0");
        rc= memset_s(repl_repl, sizeof(repl_repl), false, sizeof(repl_repl));
        securec_check(rc, "\0", "\0");
        repl_val[Anum_pg_constraint_condisable - 1] = BoolGetDatum(false);
        repl_repl[Anum_pg_constraint_condisable - 1] = true;
        copyTuple = (HeapTuple) tableam_tops_modify_tuple(tuple, RelationGetDescr(conrel), repl_val, repl_null, repl_repl);
        copy_con = (Form_pg_constraint)GETSTRUCT(copyTuple);
        copy_con->convalidated = true;
        simple_heap_update(conrel, &copyTuple->t_self, copyTuple);
        CatalogUpdateIndexes(conrel, copyTuple);
        tableam_tops_free_tuple(copyTuple);
        ObjectAddressSet(address, ConstraintRelationId,
                         HeapTupleGetOid(tuple));
    } else
        address = InvalidObjectAddress;     /* already validated */

    systable_endscan(scan);

    heap_close(conrel, RowExclusiveLock);
    return address;
}

static void ExecIndConstaint(Oid indexoid, bool enable)
{
    if (enable)
        reindex_index(indexoid, InvalidOid, false, NULL, false);
    else {
        Relation indexRel = relation_open(indexoid, RowExclusiveLock);
        ATExecUnusableIndex(indexRel, false);
        relation_close(indexRel, RowExclusiveLock);
    }
}

static void ATExecValidateAbilityConstraint(Relation rel, char* constrName, bool recurse, bool recursing, bool enable, bool valid, LOCKMODE lockmode)
{
    Relation conrel;
    SysScanDesc scan;
    ScanKeyData key;
    HeapTuple tuple;
    Form_pg_constraint con = NULL;
    bool found = false;
    Datum repl_val[Natts_pg_constraint];
    bool repl_null[Natts_pg_constraint];
    bool repl_repl[Natts_pg_constraint];
    errno_t rc = EOK;
    elog(WARNING, "ATExecValidateAbilityConstraint enable[%d], valid[%d]", enable, valid);
    conrel = heap_open(ConstraintRelationId, RowExclusiveLock);

    /*
     * Find and check the target constraint
     */
    ScanKeyInit(
        &key, Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(rel)));
    scan = systable_beginscan(conrel, ConstraintRelidIndexId, true, NULL, 1, &key);

    while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
        con = (Form_pg_constraint)GETSTRUCT(tuple);
        if (strcmp(NameStr(con->conname), constrName) == 0) {
            found = true;
            break;
        }
    }

    if (!found)
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_OBJECT),
                errmsg(
                    "constraint \"%s\" of relation \"%s\" does not exist", constrName, RelationGetRelationName(rel))));

    HeapTuple copyTuple;
    Form_pg_constraint copy_con;

    List* children = NIL;
    ListCell* child = NULL;

    /* If we're recursing, the parent has already done this, so skip it. */
    if (!recursing)
        children = find_all_inheritors(RelationGetRelid(rel), lockmode, NULL);

    /*
     * For CHECK constraints, we must ensure that we only mark the
     * constraint as validated on the parent if it's already validated
     * on the children.
     *
     * We recurse before validating on the parent, to reduce risk of
     * deadlocks.
     */
    foreach (child, children) {
        Oid childoid = lfirst_oid(child);
        Relation childrel;

        if (childoid == RelationGetRelid(rel))
            continue;

        /*
         * If we are told not to recurse, there had better not be any
         * child tables; else the addition would put them out of step.
         */
        if (!recurse)
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                    errmsg("constraint must be validated on child tables too")));

        /* find_all_inheritors already got lock */
        childrel = heap_open(childoid, NoLock);

        ATExecValidateAbilityConstraint(childrel, constrName, false, true, enable, valid, lockmode);
        heap_close(childrel, NoLock);
    }

    if (con->contype == CONSTRAINT_PRIMARY || con->contype == CONSTRAINT_UNIQUE)
        ExecIndConstaint(con->conindid, enable);
    /*
     * Invalidate relcache so that others see the new validated
     * constraint.
     */
    CacheInvalidateRelcache(rel);

    /*
     * Now update the catalog, while we have the door open.
     */
    rc = memset_s(repl_null, sizeof(repl_null), false, sizeof(repl_null));
    securec_check(rc, "\0", "\0");
    rc = memset_s(repl_repl, sizeof(repl_repl), false, sizeof(repl_repl));
    securec_check(rc, "\0", "\0");
    repl_val[Anum_pg_constraint_condisable -1] = BoolGetDatum(!enable);
    repl_repl[Anum_pg_constraint_condisable -1] = true;
    copyTuple = (HeapTuple) tableam_tops_modify_tuple(tuple, RelationGetDescr(conrel), repl_val, repl_null, repl_repl);
    copy_con = (Form_pg_constraint)GETSTRUCT(copyTuple);
    copy_con->convalidated = valid;
    simple_heap_update(conrel, &copyTuple->t_self, copyTuple);
    CatalogUpdateIndexes(conrel, copyTuple);
    tableam_tops_free_tuple(copyTuple);
    systable_endscan(scan);
    heap_close(conrel, RowExclusiveLock);
}

/*
 * transformColumnNameList - transform list of column names
 *
 * Lookup each name and return its attnum and type OID
 */
static int transformColumnNameList(Oid relId, List* colList, int16* attnums, Oid* atttypids)
{
    ListCell* l = NULL;
    int attnum;

    attnum = 0;
    foreach (l, colList) {
        char* attname = strVal(lfirst(l));
        HeapTuple atttuple;

        atttuple = SearchSysCacheAttName(relId, attname);
        if (!HeapTupleIsValid(atttuple))
            ereport(ERROR,
                (errcode(ERRCODE_UNDEFINED_COLUMN),
                    errmsg("column \"%s\" referenced in foreign key constraint does not exist", attname)));
        if (attnum >= INDEX_MAX_KEYS)
            ereport(ERROR,
                (errcode(ERRCODE_TOO_MANY_COLUMNS),
                    errmsg("cannot have more than %d keys in a foreign key", INDEX_MAX_KEYS)));
        attnums[attnum] = ((Form_pg_attribute)GETSTRUCT(atttuple))->attnum;
        atttypids[attnum] = ((Form_pg_attribute)GETSTRUCT(atttuple))->atttypid;
        ReleaseSysCache(atttuple);
        attnum++;
    }

    return attnum;
}

/*
 * transformFkeyGetPrimaryKey -
 *
 *	Look up the names, attnums, and types of the primary key attributes
 *	for the pkrel.	Also return the index OID and index opclasses of the
 *	index supporting the primary key.
 *
 *	All parameters except pkrel are output parameters.	Also, the function
 *	return value is the number of attributes in the primary key.
 *
 *	Used when the column list in the REFERENCES specification is omitted.
 */
static int transformFkeyGetPrimaryKey(
    Relation pkrel, Oid* indexOid, List** attnamelist, int16* attnums, Oid* atttypids, Oid* opclasses)
{
    List* indexoidlist = NIL;
    ListCell* indexoidscan = NULL;
    HeapTuple indexTuple = NULL;
    Form_pg_index indexStruct = NULL;
    Datum indclassDatum;
    bool isnull = false;
    oidvector* indclass = NULL;
    int indnkeyatts = 0;
    int i;

    /*
     * Get the list of index OIDs for the table from the relcache, and look up
     * each one in the pg_index syscache until we find one marked primary key
     * (hopefully there isn't more than one such).  Insist it's valid, too.
     */
    *indexOid = InvalidOid;

    indexoidlist = RelationGetIndexList(pkrel);

    foreach (indexoidscan, indexoidlist) {
        Oid indexoid = lfirst_oid(indexoidscan);

        indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
        if (!HeapTupleIsValid(indexTuple)) {
            ereport(
                ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for index %u", indexoid)));
        }

        indexStruct = (Form_pg_index)GETSTRUCT(indexTuple);
        indnkeyatts = GetIndexKeyAttsByTuple(NULL, indexTuple);
        if (indexStruct->indisprimary && IndexIsValid(indexStruct)) {
            /*
             * Refuse to use a deferrable primary key.	This is per SQL spec,
             * and there would be a lot of interesting semantic problems if we
             * tried to allow it.
             */
            if (!indexStruct->indimmediate)
                ereport(ERROR,
                    (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                        errmsg("cannot use a deferrable primary key for referenced table \"%s\"",
                            RelationGetRelationName(pkrel))));

            *indexOid = indexoid;
            break;
        }
        ReleaseSysCache(indexTuple);
    }

    list_free_ext(indexoidlist);

    /*
     * Check that we found it
     */
    if (!OidIsValid(*indexOid))
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_OBJECT),
                errmsg("there is no primary key for referenced table \"%s\"", RelationGetRelationName(pkrel))));

    /* Must get indclass the hard way */
    indclassDatum = SysCacheGetAttr(INDEXRELID, indexTuple, Anum_pg_index_indclass, &isnull);
    Assert(!isnull);
    indclass = (oidvector*)DatumGetPointer(indclassDatum);

    /*
     * Now build the list of PK attributes from the indkey definition (we
     * assume a primary key cannot have expressional elements)
     */
    *attnamelist = NIL;
    for (i = 0; i < indnkeyatts; i++) {
        int pkattno = indexStruct->indkey.values[i];

        attnums[i] = pkattno;
        atttypids[i] = attnumTypeId(pkrel, pkattno);
        opclasses[i] = indclass->values[i];
        *attnamelist = lappend(*attnamelist, makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno)))));
    }

    ReleaseSysCache(indexTuple);

    return i;
}

/*
 * transformFkeyCheckAttrs -
 *
 *	Make sure that the attributes of a referenced table belong to a unique
 *	(or primary key) constraint.  Return the OID of the index supporting
 *	the constraint, as well as the opclasses associated with the index
 *	columns.
 */
static Oid transformFkeyCheckAttrs(Relation pkrel, int numattrs, int16* attnums, Oid* opclasses) /* output parameter */
{
    Oid indexoid = InvalidOid;
    bool found = false;
    bool found_deferrable = false;
    List* indexoidlist = NIL;
    ListCell* indexoidscan = NULL;

    /*
     * Get the list of index OIDs for the table from the relcache, and look up
     * each one in the pg_index syscache, and match unique indexes to the list
     * of attnums we are given.
     */
    indexoidlist = RelationGetIndexList(pkrel);

    foreach (indexoidscan, indexoidlist) {
        HeapTuple indexTuple;
        Form_pg_index indexStruct;
        int indnkeyatts;
        int i, j;

        indexoid = lfirst_oid(indexoidscan);
        indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
        if (!HeapTupleIsValid(indexTuple)) {
            ereport(
                ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for index %u", indexoid)));
        }
        indexStruct = (Form_pg_index)GETSTRUCT(indexTuple);
        indnkeyatts = GetIndexKeyAttsByTuple(NULL, indexTuple);

        /*
         * Must have the right number of columns; must be unique and not a
         * partial index; forget it if there are any expressions, too. Invalid
         * indexes are out as well.
         */
        if (indnkeyatts == numattrs && indexStruct->indisunique && IndexIsValid(indexStruct) &&
            tableam_tops_tuple_attisnull(indexTuple, Anum_pg_index_indpred, NULL) &&
            tableam_tops_tuple_attisnull(indexTuple, Anum_pg_index_indexprs, NULL)) {
            /* Must get indclass the hard way */
            Datum indclassDatum;
            bool isnull = false;
            oidvector* indclass = NULL;

            indclassDatum = SysCacheGetAttr(INDEXRELID, indexTuple, Anum_pg_index_indclass, &isnull);
            Assert(!isnull);
            indclass = (oidvector*)DatumGetPointer(indclassDatum);

            /*
             * The given attnum list may match the index columns in any order.
             * Check that each list is a subset of the other.
             */
            for (i = 0; i < numattrs; i++) {
                found = false;
                for (j = 0; j < numattrs; j++) {
                    if (attnums[i] == indexStruct->indkey.values[j]) {
                        found = true;
                        break;
                    }
                }
                if (!found)
                    break;
            }
            if (found) {
                for (i = 0; i < numattrs; i++) {
                    found = false;
                    for (j = 0; j < numattrs; j++) {
                        if (attnums[j] == indexStruct->indkey.values[i]) {
                            opclasses[j] = indclass->values[i];
                            found = true;
                            break;
                        }
                    }
                    if (!found)
                        break;
                }
            }

            /*
             * Refuse to use a deferrable unique/primary key.  This is per SQL
             * spec, and there would be a lot of interesting semantic problems
             * if we tried to allow it.
             */
            if (found && !indexStruct->indimmediate) {
                /*
                 * Remember that we found an otherwise matching index, so that
                 * we can generate a more appropriate error message.
                 */
                found_deferrable = true;
                found = false;
            }
        }
        ReleaseSysCache(indexTuple);
        if (found)
            break;
    }

    if (!found) {
        if (found_deferrable)
            ereport(ERROR,
                (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                    errmsg("cannot use a deferrable unique constraint for referenced table \"%s\"",
                        RelationGetRelationName(pkrel))));
        else
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_FOREIGN_KEY),
                    errmsg("there is no unique constraint matching given keys for referenced table \"%s\"",
                        RelationGetRelationName(pkrel))));
    }

    list_free_ext(indexoidlist);

    return indexoid;
}

/*
 * findFkeyCast -
 *
 *	Wrapper around find_coercion_pathway() for ATAddForeignKeyConstraint().
 *	Caller has equal regard for binary coercibility and for an exact match.
 */
static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId, Oid* funcid)
{
    CoercionPathType ret;

    if (targetTypeId == sourceTypeId) {
        ret = COERCION_PATH_RELABELTYPE;
        *funcid = InvalidOid;
    } else {
        ret = find_coercion_pathway(targetTypeId, sourceTypeId, COERCION_IMPLICIT, funcid);
        if (ret == COERCION_PATH_NONE)
            /* A previously-relied-upon cast is now gone. */
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_CHARACTER_VALUE_FOR_CAST),
                    errmsg("could not find cast from %s to %s", format_type_be(sourceTypeId), format_type_be(targetTypeId))));
    }

    return ret;
}

/* Permissions checks for ADD FOREIGN KEY */
static void checkFkeyPermissions(Relation rel, int16* attnums, int natts)
{
    Oid roleid = GetUserId();
    AclResult aclresult;
    int i;

    /* Okay if we have relation-level REFERENCES permission */
    aclresult = pg_class_aclcheck(RelationGetRelid(rel), roleid, ACL_REFERENCES);
    if (aclresult == ACLCHECK_OK)
        return;
    /* Else we must have REFERENCES on each column */
    for (i = 0; i < natts; i++) {
        aclresult = pg_attribute_aclcheck(RelationGetRelid(rel), attnums[i], roleid, ACL_REFERENCES);
        if (aclresult != ACLCHECK_OK)
            aclcheck_error(aclresult, ACL_KIND_CLASS, RelationGetRelationName(rel));
    }
}

/*
 * Scan the existing rows in a table to verify they meet a proposed
 * CHECK constraint.
 *
 * The caller must have opened and locked the relation appropriately.
 */
static void validateCheckConstraint(Relation rel, HeapTuple constrtup)
{
    Datum val;
    HeapTuple tuple = NULL;
    bool isnull = false;
    MemoryContext oldcxt;
    ListCell* lc = NULL;

    Form_pg_constraint constrForm = (Form_pg_constraint)GETSTRUCT(constrtup);
    EState* estate = CreateExecutorState();

    /*
     * XXX this tuple doesn't really come from a syscache, but this doesn't
     * matter to SysCacheGetAttr, because it only wants to be able to fetch
     * the tupdesc
     */
    val = SysCacheGetAttr(CONSTROID, constrtup, Anum_pg_constraint_conbin, &isnull);
    if (isnull)
        ereport(ERROR,
            (errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
                errmsg("null conbin for constraint %u", HeapTupleGetOid(constrtup))));
    char* conbin = TextDatumGetCString(val);
    Expr* origexpr = (Expr*)stringToNode(conbin);
    List* exprstate = ExecPrepareExprList(make_ands_implicit(origexpr), estate);
    ExprContext* econtext = GetPerTupleExprContext(estate);
    TupleDesc tupdesc = RelationGetDescr(rel);
    TupleTableSlot* slot = MakeSingleTupleTableSlot(tupdesc, false, rel->rd_tam_ops);

    econtext->ecxt_scantuple = slot;

    TableScanDesc scan = tableam_scan_begin(rel, SnapshotNow, 0, NULL);

    while ((tuple = (HeapTuple) tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) {
        (void)ExecStoreTuple(tuple, slot, InvalidBuffer, false);

        /*
         * Switch to per-tuple memory context and reset it for each tuple
         * produced, so we don't leak memory.
         */
        oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
        if (estate->es_is_flt_frame){
            foreach (lc, exprstate) {
                ExprState* exprState = (ExprState*)lfirst(lc);

                if (!ExecCheckByFlatten(exprState, econtext))
                    ereport(ERROR,
                        (errcode(ERRCODE_CHECK_VIOLATION),
                            errmsg("check constraint \"%s\" is violated by some row", NameStr(constrForm->conname))));
            }
        } else {
            if (!ExecQualByRecursion(exprstate, econtext, true)){
                ereport(ERROR,
                        (errcode(ERRCODE_CHECK_VIOLATION),
                            errmsg("check constraint \"%s\" is violated by some row", NameStr(constrForm->conname))));
            }
        }

        ResetExprContext(econtext);
        MemoryContextSwitchTo(oldcxt);
    }

    tableam_scan_end(scan);
    ExecDropSingleTupleTableSlot(slot);
    FreeExecutorState(estate);
}

/*
 * Validate check constraint for a list of bucket.
 *
 * The caller must have opened and locked the relation and(or) partition appropriately.
 */
static void validateCheckConstraintForBucket(Relation rel, Partition part, HeapTuple constrtup)
{
    Relation bucketRel = NULL;
    oidvector* bucketlist = searchHashBucketByOid(rel->rd_bucketoid);

    for (int i = 0; i < bucketlist->dim1; i++) {
        /* Open the bucket and do the real validate */
        bucketRel = bucketGetRelation(rel, part, bucketlist->values[i]);

        validateCheckConstraint(bucketRel, constrtup);

        bucketCloseRelation(bucketRel);
    }
}

/*
 * Scan the existing rows in a table to verify they meet a proposed FK
 * constraint.
 *
 * Caller must have opened and locked both relations appropriately.
 */
static void validateForeignKeyConstraint(char* conname, Relation rel, Relation pkrel, Oid pkindOid, Oid constraintOid)
{
    TableScanDesc scan;
    HeapTuple tuple;
    Trigger trig;
    errno_t rc = EOK;

    ereport(DEBUG1, (errmsg("validating foreign key constraint \"%s\"", conname)));

    /*
     * Build a trigger call structure; we'll need it either way.
     */
    rc = memset_s(&trig, sizeof(trig), 0, sizeof(trig));
    securec_check(rc, "\0", "\0");
    trig.tgoid = InvalidOid;
    trig.tgname = conname;
    trig.tgenabled = TRIGGER_FIRES_ON_ORIGIN;
    trig.tgisinternal = TRUE;
    trig.tgconstrrelid = RelationGetRelid(pkrel);
    trig.tgconstrindid = pkindOid;
    trig.tgconstraint = constraintOid;
    trig.tgdeferrable = FALSE;
    trig.tginitdeferred = FALSE;
    /* we needn't fill in tgargs or tgqual */
    /*
     * See if we can do it with a single LEFT JOIN query.  A FALSE result
     * indicates we must proceed with the fire-the-trigger method.
     */
    if (RI_Initial_Check(&trig, rel, pkrel))
        return;

    /*
     * Scan through each tuple, calling RI_FKey_check_ins (insert trigger) as
     * if that tuple had just been inserted.  If any of those fail, it should
     * ereport(ERROR) and that's that.
     */
    scan = tableam_scan_begin(rel, SnapshotNow, 0, NULL);

    while ((tuple = (HeapTuple) tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) {
        FunctionCallInfoData fcinfo;
        TriggerData trigdata;

        /*
         * Make a call to the trigger function
         *
         * No parameters are passed, but we do set a context
         */
        rc = memset_s(&fcinfo, sizeof(fcinfo), 0, sizeof(fcinfo));
        securec_check(rc, "\0", "\0");
        rc = memset_s(&trigdata, sizeof(trigdata), 0, sizeof(trigdata));
        securec_check(rc, "\0", "\0");
        /*
         * We assume RI_FKey_check_ins won't look at flinfo...
         */
        trigdata.type = T_TriggerData;
        trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
        trigdata.tg_relation = rel;
        trigdata.tg_trigtuple = tuple;
        trigdata.tg_newtuple = NULL;
        trigdata.tg_trigger = &trig;
        trigdata.tg_trigtuplebuf = scan->rs_cbuf;
        trigdata.tg_newtuplebuf = InvalidBuffer;

        fcinfo.context = (Node*)&trigdata;

        RI_FKey_check_ins(&fcinfo);
    }

    tableam_scan_end(scan);
}

static void CreateFKCheckTrigger(
    Oid myRelOid, Oid refRelOid, Constraint* fkconstraint, Oid constraintOid, Oid indexOid, bool on_insert)
{
    CreateTrigStmt* fk_trigger = NULL;

    /*
     * Note: for a self-referential FK (referencing and referenced tables are
     * the same), it is important that the ON UPDATE action fires before the
     * CHECK action, since both triggers will fire on the same row during an
     * UPDATE event; otherwise the CHECK trigger will be checking a non-final
     * state of the row.  Triggers fire in name order, so we ensure this by
     * using names like "RI_ConstraintTrigger_a_NNNN" for the action triggers
     * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
     */
    fk_trigger = makeNode(CreateTrigStmt);
    fk_trigger->trigname = "RI_ConstraintTrigger_c";
    fk_trigger->relation = NULL;
    fk_trigger->row = true;
    fk_trigger->timing = TRIGGER_TYPE_AFTER;

    /* Either ON INSERT or ON UPDATE */
    if (on_insert) {
        fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
        fk_trigger->events = TRIGGER_TYPE_INSERT;
    } else {
        fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
        fk_trigger->events = TRIGGER_TYPE_UPDATE;
    }

    fk_trigger->columns = NIL;
    fk_trigger->whenClause = NULL;
    fk_trigger->isconstraint = true;
    fk_trigger->deferrable = fkconstraint->deferrable;
    fk_trigger->initdeferred = fkconstraint->initdeferred;
    fk_trigger->constrrel = NULL;
    fk_trigger->args = NIL;

    (void)CreateTrigger(fk_trigger, NULL, myRelOid, refRelOid, constraintOid, indexOid, true);

    /* Make changes-so-far visible */
    CommandCounterIncrement();
}

/*
 * Create the triggers that implement an FK constraint.
 */
static void createForeignKeyTriggers(
    Relation rel, Oid refRelOid, Constraint* fkconstraint, Oid constraintOid, Oid indexOid)
{
    Oid myRelOid;
    CreateTrigStmt* fk_trigger = NULL;

    myRelOid = RelationGetRelid(rel);

    /* Make changes-so-far visible */
    CommandCounterIncrement();

    /*
     * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
     * DELETE action on the referenced table.
     */
    fk_trigger = makeNode(CreateTrigStmt);
    fk_trigger->trigname = "RI_ConstraintTrigger_a";
    fk_trigger->relation = NULL;
    fk_trigger->row = true;
    fk_trigger->timing = TRIGGER_TYPE_AFTER;
    fk_trigger->events = TRIGGER_TYPE_DELETE;
    fk_trigger->columns = NIL;
    fk_trigger->whenClause = NULL;
    fk_trigger->isconstraint = true;
    fk_trigger->constrrel = NULL;
    switch (fkconstraint->fk_del_action) {
        case FKCONSTR_ACTION_NOACTION:
            fk_trigger->deferrable = fkconstraint->deferrable;
            fk_trigger->initdeferred = fkconstraint->initdeferred;
            fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
            break;
        case FKCONSTR_ACTION_RESTRICT:
            fk_trigger->deferrable = false;
            fk_trigger->initdeferred = false;
            fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
            break;
        case FKCONSTR_ACTION_CASCADE:
            fk_trigger->deferrable = false;
            fk_trigger->initdeferred = false;
            fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
            break;
        case FKCONSTR_ACTION_SETNULL:
            fk_trigger->deferrable = false;
            fk_trigger->initdeferred = false;
            fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
            break;
        case FKCONSTR_ACTION_SETDEFAULT:
            fk_trigger->deferrable = false;
            fk_trigger->initdeferred = false;
            fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
            break;
        default:
            ereport(ERROR,
                (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                    errmsg("unrecognized FK action type: %d", (int)fkconstraint->fk_del_action)));
            break;
    }
    fk_trigger->args = NIL;

    (void)CreateTrigger(fk_trigger, NULL, refRelOid, myRelOid, constraintOid, indexOid, true);

    /* Make changes-so-far visible */
    CommandCounterIncrement();

    /*
     * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
     * UPDATE action on the referenced table.
     */
    fk_trigger = makeNode(CreateTrigStmt);
    fk_trigger->trigname = "RI_ConstraintTrigger_a";
    fk_trigger->relation = NULL;
    fk_trigger->row = true;
    fk_trigger->timing = TRIGGER_TYPE_AFTER;
    fk_trigger->events = TRIGGER_TYPE_UPDATE;
    fk_trigger->columns = NIL;
    fk_trigger->whenClause = NULL;
    fk_trigger->isconstraint = true;
    fk_trigger->constrrel = NULL;
    switch (fkconstraint->fk_upd_action) {
        case FKCONSTR_ACTION_NOACTION:
            fk_trigger->deferrable = fkconstraint->deferrable;
            fk_trigger->initdeferred = fkconstraint->initdeferred;
            fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
            break;
        case FKCONSTR_ACTION_RESTRICT:
            fk_trigger->deferrable = false;
            fk_trigger->initdeferred = false;
            fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
            break;
        case FKCONSTR_ACTION_CASCADE:
            fk_trigger->deferrable = false;
            fk_trigger->initdeferred = false;
            fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
            break;
        case FKCONSTR_ACTION_SETNULL:
            fk_trigger->deferrable = false;
            fk_trigger->initdeferred = false;
            fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
            break;
        case FKCONSTR_ACTION_SETDEFAULT:
            fk_trigger->deferrable = false;
            fk_trigger->initdeferred = false;
            fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
            break;
        default:
            ereport(ERROR,
                (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                    errmsg("unrecognized FK action type: %d", (int)fkconstraint->fk_upd_action)));
            break;
    }
    fk_trigger->args = NIL;

    (void)CreateTrigger(fk_trigger, NULL, refRelOid, myRelOid, constraintOid, indexOid, true);

    /* Make changes-so-far visible */
    CommandCounterIncrement();

    /*
     * Build and execute CREATE CONSTRAINT TRIGGER statements for the CHECK
     * action for both INSERTs and UPDATEs on the referencing table.
     */
    CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint, constraintOid, indexOid, true);
    CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint, constraintOid, indexOid, false);
}

bool ConstraintSatisfyAutoIncrement(HeapTuple tuple, TupleDesc desc, AttrNumber attrnum, char contype)
{
    if (attrnum == 0 || (contype != CONSTRAINT_PRIMARY && contype != CONSTRAINT_UNIQUE)) {
        return false;
    }

    Datum* keys = NULL;
    int nKeys;
    bool isnull = false;
    Datum conkey = heap_getattr(tuple, Anum_pg_constraint_conkey, desc, &isnull);
    if (isnull) {
        return false;
    }
    deconstruct_array(DatumGetArrayTypeP(conkey), INT2OID, sizeof(int16), true, 's', &keys, NULL, &nKeys);
    if (DatumGetInt16(keys[0]) == attrnum) {
        pfree(keys);
        return true;
    }
    pfree(keys);
    return false;
}
/*
 * ALTER TABLE DROP CONSTRAINT
 *
 * Like DROP COLUMN, we can't use the normal ALTER TABLE recursion mechanism.
 */
static void ATExecDropConstraint(Relation rel, const char* constrName, DropBehavior behavior, bool recurse,
    bool recursing, bool missing_ok, LOCKMODE lockmode)
{
    List* children = NIL;
    ListCell* child = NULL;
    Relation conrel;
    Form_pg_constraint con;
    SysScanDesc scan;
    ScanKeyData key;
    HeapTuple tuple;
    bool found = false;
    bool is_no_inherit_constraint = false;

    /* At top level, permission check was done in ATPrepCmd, else do it */
    if (recursing)
        ATSimplePermissions(rel, ATT_TABLE);

    conrel = heap_open(ConstraintRelationId, RowExclusiveLock);

    /*
     * Find and drop the target constraint
     */
    ScanKeyInit(
        &key, Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(rel)));
    scan = systable_beginscan(conrel, ConstraintRelidIndexId, true, NULL, 1, &key);

    while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
        ObjectAddress conobj;

        con = (Form_pg_constraint)GETSTRUCT(tuple);

        if (strcmp(NameStr(con->conname), constrName) != 0) {
            continue;
        }

        /* Don't drop inherited constraints */
        if (con->coninhcount > 0 && !recursing)
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                    errmsg("cannot drop inherited constraint \"%s\" of relation \"%s\"",
                        constrName,
                        RelationGetRelationName(rel))));

        is_no_inherit_constraint = con->connoinherit;

        /*
         * XXX as a special hack, we turn on no-inherit here unconditionally
         * except for CHECK constraints.  This is because 9.2 until beta2
         * contained a bug that marked it false for all constraints, even
         * though it was only supported false for CHECK constraints.
         * See bug #6712.
         */
        if (con->contype != CONSTRAINT_CHECK)
            is_no_inherit_constraint = true;

        /* drop partial cluster key */
        if (con->contype == CONSTRAINT_CLUSTER) {
            SetRelHasClusterKey(rel, false);
        }

#ifndef ENABLE_MULTIPLE_NODES
        /*
         * If it's a foreign-key constraint, we'd better lock the referenced
         * table and check that that's not in use, just as we've already done
         * for the constrained table (else we might, eg, be dropping a trigger
         * that has unfired events).  But we can/must skip that in the
         * self-referential case.
         */
        if (con->contype == CONSTRAINT_FOREIGN &&
            con->confrelid != RelationGetRelid(rel))
        {
            Relation frel;

            /* Must match lock taken by RemoveTriggerById: */
            frel = heap_open(con->confrelid, AccessExclusiveLock);
            CheckTableNotInUse(frel, "ALTER TABLE");
            heap_close(frel, NoLock);
        }
#endif

        /*
         * Perform the actual constraint deletion
         */
        conobj.classId = ConstraintRelationId;
        conobj.objectId = HeapTupleGetOid(tuple);
        conobj.objectSubId = 0;

        performDeletion(&conobj, behavior, 0);

        found = true;

        /* constraint found and dropped -- no need to keep looping */
        break;
    }

    systable_endscan(scan);

    if (!found) {
        if (!missing_ok) {
            ereport(ERROR,
                (errcode(ERRCODE_UNDEFINED_OBJECT),
                    errmsg("constraint \"%s\" of relation \"%s\" does not exist",
                        constrName,
                        RelationGetRelationName(rel))));
        } else {
            ereport(NOTICE,
                (errmsg("constraint \"%s\" of relation \"%s\" does not exist, skipping",
                    constrName,
                    RelationGetRelationName(rel))));
            heap_close(conrel, RowExclusiveLock);
            return;
        }
    }

    /*
     * Propagate to children as appropriate.  Unlike most other ALTER
     * routines, we have to do this one level of recursion at a time; we can't
     * use find_all_inheritors to do it in one pass.
     */
    if (!is_no_inherit_constraint)
        children = find_inheritance_children(RelationGetRelid(rel), lockmode);
    else
        children = NIL;

    foreach (child, children) {
        Oid childrelid = lfirst_oid(child);
        Relation childrel;
        HeapTuple copy_tuple;

        /* find_inheritance_children already got lock */
        childrel = heap_open(childrelid, NoLock);
        CheckTableNotInUse(childrel, "ALTER TABLE");

        ScanKeyInit(&key, Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(childrelid));
        scan = systable_beginscan(conrel, ConstraintRelidIndexId, true, NULL, 1, &key);

        /* scan for matching tuple - there should only be one */
        while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
            con = (Form_pg_constraint)GETSTRUCT(tuple);

            /* Right now only CHECK constraints can be inherited */
            if (con->contype != CONSTRAINT_CHECK)
                continue;

            if (strcmp(NameStr(con->conname), constrName) == 0)
                break;
        }

        if (!HeapTupleIsValid(tuple))
            ereport(ERROR,
                (errcode(ERRCODE_UNDEFINED_OBJECT),
                    errmsg("constraint \"%s\" of relation \"%s\" does not exist",
                        constrName,
                        RelationGetRelationName(childrel))));

        copy_tuple = (HeapTuple)tableam_tops_copy_tuple(tuple);

        systable_endscan(scan);

        con = (Form_pg_constraint)GETSTRUCT(copy_tuple);

        if (con->coninhcount <= 0) /* shouldn't happen */
            ereport(ERROR,
                (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                    errmsg("relation %u has non-inherited constraint \"%s\"", childrelid, constrName)));

        if (recurse) {
            /*
             * If the child constraint has other definition sources, just
             * decrement its inheritance count; if not, recurse to delete it.
             */
            if (con->coninhcount == 1 && !con->conislocal) {
                /* Time to delete this child constraint, too */
                ATExecDropConstraint(childrel, constrName, behavior, true, true, false, lockmode);
            } else {
                /* Child constraint must survive my deletion */
                con->coninhcount--;
                simple_heap_update(conrel, &copy_tuple->t_self, copy_tuple);
                CatalogUpdateIndexes(conrel, copy_tuple);

                /* Make update visible */
                CommandCounterIncrement();
            }
        } else {
            /*
             * If we were told to drop ONLY in this table (no recursion), we
             * need to mark the inheritors' constraints as locally defined
             * rather than inherited.
             */
            con->coninhcount--;
            con->conislocal = true;

            simple_heap_update(conrel, &copy_tuple->t_self, copy_tuple);
            CatalogUpdateIndexes(conrel, copy_tuple);

            /* Make update visible */
            CommandCounterIncrement();
        }

        tableam_tops_free_tuple(copy_tuple);

        heap_close(childrel, NoLock);
    }

    heap_close(conrel, RowExclusiveLock);
}

static void CheckHugeToastInternal(TupleDesc reldesc, Relation rel, AttrNumber attnum)
{
    HeapTuple tuple;
    Datum values[reldesc->natts];
    bool isnull[reldesc->natts];
    TableScanDesc scan = tableam_scan_begin(rel, SnapshotNow, 0, NULL);
    while ((tuple = (HeapTuple)tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) {
        tableam_tops_deform_tuple(tuple, reldesc, values, isnull);
        if (!isnull[attnum - 1] && VARATT_IS_HUGE_TOAST_POINTER(DatumGetPointer(values[attnum - 1]))) {
            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Un-support alter clob/blob to text type when more than 1GB")));
        }
    }
    tableam_scan_end(scan);
}

void CheckHugeToast(AlteredTableInfo *tab, Relation rel, AttrNumber attnum)
{
    TupleDesc reldesc = tab->oldDesc;
    Form_pg_attribute attr = &reldesc->attrs[attnum - 1];

    if (attr->atttypid != CLOBOID && attr->atttypid != BLOBOID) {
        return;
    }

    if (RelationIsPartitioned(rel)) {
        ListCell *partCell = NULL;
        List *partList = relationGetPartitionList(rel, NoLock);
        foreach (partCell, partList) {
            Partition part = (Partition)lfirst(partCell);
            Relation partRel = partitionGetRelation(rel, part);
            if (RelationIsSubPartitioned(rel)) {
                ListCell *subPartCell = NULL;
                List *subPartList = relationGetPartitionList(partRel, NoLock);
                foreach (subPartCell, subPartList) {
                    Partition subPart = (Partition)lfirst(subPartCell);
                    Relation subPartRel = partitionGetRelation(partRel, subPart);
                    CheckHugeToastInternal(reldesc, subPartRel, attnum);
                    releaseDummyRelation(&subPartRel);
                }
                releasePartitionList(partRel, &subPartList, NoLock);
            } else {
                CheckHugeToastInternal(reldesc, partRel, attnum);
            }
            releaseDummyRelation(&partRel);
        }
        releasePartitionList(rel, &partList, NoLock);
    } else {
        CheckHugeToastInternal(reldesc, rel, attnum);
    }
}

/**
 * Check numeric type modify.
 *      column to be modified must be empty to decrease precision or scale;
 *      column to be modified must be empty to change datatype.
 * 
 * Only valid numeric scale decrease, And when set behavior_compat_options = 'float_as_numeric'
 * (float use numeric indicate), valid float(p) can't decrease precision and can't change to other datatype.
*/
static int CheckFloatIllegalTypeConversion(Relation rel, Oid origintype, int32 origintypmod, Oid targettype, int32 targettypmod)
{
    int32   oriprecision;
    int16   oriscale;
    int32   tarprecision;
    int16   tarscale;
    bool    compatibilityversion;
    int     result = AT_FASN_PASS;

    if (RelationIsCUFormat(rel) || !(u_sess->attr.attr_sql.sql_compatibility == A_FORMAT)) {
        return result;
    }

    compatibilityversion = FLOAT_AS_NUMERIC && t_thrd.proc->workingVersionNum >= FLOAT_VERSION_NUMBER;

    if (origintype == NUMERICOID) {
        oriprecision = (int32) ((((uint32)(origintypmod - VARHDRSZ)) >> 16) & 0xffff);
        oriscale = (int16) (((uint32)(origintypmod - VARHDRSZ)) & 0xffff);
    }

    if (targettype == NUMERICOID) {
        tarprecision = (int32) ((((uint32)(targettypmod - VARHDRSZ)) >> 16) & 0xffff);
        tarscale = (int16) (((uint32)(targettypmod - VARHDRSZ)) & 0xffff);
        
        if (origintype == NUMERICOID) {
            if (compatibilityversion) {
                // float decrease precision
                if (IS_FLOAT_AS_NUMERIC(oriscale) && IS_FLOAT_AS_NUMERIC(tarscale) && tarprecision < oriprecision) {
                    result = AT_FASN_FAIL_PRECISION;
                }
                // can't change datatype between float and numeric
                else if (IS_FLOAT_AS_NUMERIC(oriscale) != IS_FLOAT_AS_NUMERIC(tarscale)) {
                    result = AT_FASN_FAIL_PRECISION;
                }
            }

            // numeric decrease scale
            if (!IS_FLOAT_AS_NUMERIC(oriscale) && !IS_FLOAT_AS_NUMERIC(tarscale) && tarscale < oriscale) {
                result = AT_FASN_FAIL_PRECISION;
            }

            return result;
        } else if (compatibilityversion && IS_FLOAT_AS_NUMERIC(tarscale) 
                    && (origintype == INT4OID || origintype == INT2OID || origintype ==INT1OID)) {
            // O* has no int type, so all use NUMBER(38) instead.
            if (ceil(log10(2) * tarprecision) < 38) {
                result = AT_FASN_FAIL_PRECISION;

            }
            // convert int to numeric
            return result;
        }
    }

    if (compatibilityversion && origintype == NUMERICOID && IS_FLOAT_AS_NUMERIC(oriscale) && targettype != origintype) {
        // can't change other float to other datatype.
        result = AT_FASN_FAIL_TYPE;
    }

    return result;
}

/*
 * ALTER COLUMN TYPE
 */
static void ATPrepAlterColumnType(List** wqueue, AlteredTableInfo* tab, Relation rel, bool recurse, bool recursing,
    AlterTableCmd* cmd, LOCKMODE lockmode)
{
    char* colName = cmd->name;
    ColumnDef* def = (ColumnDef*)cmd->def;
    TypeName* typname = def->typname;
    Node* transform = def->raw_default;
    HeapTuple tuple;
    Form_pg_attribute attTup;
    AttrNumber attnum;
    Oid targettype = InvalidOid;
    int32 targettypmod = -1;
    Oid targetcollid = InvalidOid;
    int target_charset = PG_INVALID_ENCODING;
    NewColumnValue* newval = NULL;
    ParseState* pstate = make_parsestate(NULL);
    AclResult aclresult;

    if (rel->rd_rel->reloftype && !recursing)
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot alter column type of typed table")));

    /* lookup the attribute so we can check inheritance status */
    tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
    if (!HeapTupleIsValid(tuple))
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_COLUMN),
                errmsg("column \"%s\" of relation \"%s\" does not exist", colName, RelationGetRelationName(rel))));
    attTup = (Form_pg_attribute)GETSTRUCT(tuple);
    attnum = attTup->attnum;

    /* Can't alter a system attribute */
    if (attnum <= 0)
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot alter system column \"%s\"", colName)));

    /* Don't alter inherited columns */
    if (attTup->attinhcount > 0 && !recursing)
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("cannot alter inherited column \"%s\"", colName)));
    if (typname && list_length(typname->names) == 1 && !typname->pct_type) {
        char* tname = strVal(linitial(typname->names));

        if (strcmp(tname, "smallserial") == 0 || strcmp(tname, "serial2") == 0 || strcmp(tname, "serial") == 0 ||
            strcmp(tname, "serial4") == 0 || strcmp(tname, "bigserial") == 0 || strcmp(tname, "serial8") == 0 ||
            strcmp(tname, "largeserial") == 0 || strcmp(tname, "serial16") == 0)
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("cannot alter column type to \"%s\"", tname)));
    }

    if (typname == NULL) {
        ereport(ERROR,
            (errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
                errmsg("typname is null")));
    }
    /* Look up the target type */
    typenameTypeIdAndMod(NULL, typname, &targettype, &targettypmod);

    /* And the collation */
    Oid rel_coll_oid = rel->rd_options == NULL ? InvalidOid : ((StdRdOptions*)(rel)->rd_options)->collate;
    targetcollid = GetColumnDefCollation(NULL, def, targettype, rel_coll_oid);
    if (DB_IS_CMPT_BD) {
        targettype = binary_need_transform_typeid(targettype, &targetcollid);
        target_charset = get_charset_by_collation(targetcollid);
    }

    // check the unsupported datatype.
    if (RelationIsColStore(rel) && !IsTypeSupportedByCStore(targettype)) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("type \"%s\" is not supported in column store",
                    format_type_with_typemod(targettype, targettypmod))));
    }
#ifdef ENABLE_HTAP
    if (RelHasImcs(RelationGetRelid(rel))) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Un-support feature"),
                errdetail("AlterColumnType operation is not supported for HTAP table. please unpopulate first.")));
    }
#endif

    aclresult = pg_type_aclcheck(targettype, GetUserId(), ACL_USAGE);
    if (aclresult != ACLCHECK_OK)
        aclcheck_error_type(aclresult, targettype);

    /* make sure datatype is legal for a column */
    CheckAttributeType(colName, targettype, targetcollid, list_make1_oid(rel->rd_rel->reltype), false);

    if (tab->relkind == RELKIND_RELATION) {
        if (IS_PGXC_COORDINATOR && !IsSystemRelation(rel) && !IsCStoreNamespace(rel->rd_rel->relnamespace)) {
            HeapTuple tup = SearchSysCache(PGXCCLASSRELID, ObjectIdGetDatum(RelationGetRelid(rel)), 0, 0, 0);

            if (!HeapTupleIsValid(tup)) /* should not happen */
                ereport(ERROR,
                    (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                        errmsg("cache lookup failed for pgxc_class %u", RelationGetRelid(rel))));

            Form_pgxc_class pgxc_class = (Form_pgxc_class)GETSTRUCT(tup);
            if ((pgxc_class->pclocatortype == 'H' || pgxc_class->pclocatortype == 'M' ||
                pgxc_class->pclocatortype == 'L' || pgxc_class->pclocatortype == 'G') &&
                IsDistribColumn(RelationGetRelid(rel), attnum))
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot alter data type of distribute column")));

            ReleaseSysCache(tup);
        }
        /*
         * Set up an expression to transform the old data value to the new
         * type. If a USING option was given, transform and use that
         * expression, else just take the old value and try to coerce it.  We
         * do this first so that type incompatibility can be detected before
         * we waste effort, and because we need the expression to be parsed
         * against the original table row type.
         */
        if (transform != NULL) {
            /* Expression must be able to access vars of old table */
            RangeTblEntry* rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);

            addRTEtoQuery(pstate, rte, false, true, true);

            transform = transformExpr(pstate, transform, EXPR_KIND_ALTER_COL_TRANSFORM);

            if (RelationIsColStore(rel)) {
                Bitmapset* attrs_referred = NULL;
                /* Collect all the attributes refered in the expression. */
                pull_varattnos(transform, 1, &attrs_referred);
                if (attrs_referred != NULL) {
                    attrs_referred =
                        bms_del_member(attrs_referred, attnum - FirstLowInvalidHeapAttributeNumber);  // remove itself
                    if (!bms_is_empty(attrs_referred)) {
                        bms_free_ext(attrs_referred);
                        ereport(ERROR,
                            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                errmsg(
                                    "cannot refer to other columns in transform expression for column store table")));
                    }
                    bms_free_ext(attrs_referred);
                }
            }

            /* It can't return a set */
            if (expression_returns_set(transform))
                ereport(
                    ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("transform expression must not return a set")));

            /* No subplans or aggregates, either... */
            if (pstate->p_hasSubLinks)
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot use subquery in transform expression")));
            if (pstate->p_hasAggs)
                ereport(ERROR,
                    (errcode(ERRCODE_GROUPING_ERROR), errmsg("cannot use aggregate function in transform expression")));
            if (pstate->p_hasWindowFuncs)
                ereport(ERROR,
                    (errcode(ERRCODE_WINDOWING_ERROR), errmsg("cannot use window function in transform expression")));
        } else {
            transform = (Node*)makeVar(1, attnum, attTup->atttypid, attTup->atttypmod, attTup->attcollation, 0);
        }

        tab->check_pass_with_relempty = CheckFloatIllegalTypeConversion(rel, attTup->atttypid, attTup->atttypmod, targettype, targettypmod);

        transform = coerce_to_target_type(pstate,
            transform,
            exprType(transform),
            targettype,
            targettypmod,
            COERCION_ASSIGNMENT,
            COERCE_IMPLICIT_CAST,
            NULL,
            NULL,
            -1);
        if (transform == NULL)
            ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
                    errmsg(
                        "column \"%s\" cannot be cast automatically to type %s", colName, format_type_be(targettype)),
                    errhint("Specify a USING expression to perform the conversion.")));
#ifndef ENABLE_MULTIPLE_NODES
        transform = coerce_to_target_charset(transform, target_charset, attTup->atttypid, attTup->atttypmod, targetcollid);
#endif
        /* Fix collations after all else */
        assign_expr_collations(pstate, transform);

        /* Plan the expr now so we can accurately assess the need to rewrite. */
        transform = (Node*)expression_planner((Expr*)transform);

        /*
         * Add a work queue item to make ATRewriteTable update the column
         * contents.
         */
        newval = (NewColumnValue*)palloc0(sizeof(NewColumnValue));
        newval->attnum = attnum;
        newval->expr = (Expr*)transform;
        newval->is_generated = false;
        newval->is_autoinc = false;
        newval->is_addloc = false;
        newval->newattnum = 0;
        newval->col_name = pstrdup(colName);
        newval->generate_attnum = 0;
        newval->is_alter_using = (def->raw_default ? true : false);
        newval->is_updated = false;

        tab->newvals = lappend(tab->newvals, newval);
        if (ATColumnChangeRequiresRewrite(transform, attnum))
            tab->rewrite = AT_REWRITE_COLUMN_REWRITE;
        if (targettype != CLOBOID && targettype != BLOBOID) {
            CheckHugeToast(tab, rel, attnum);
        }
    } else if (transform != NULL)
        ereport(
            ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table", RelationGetRelationName(rel))));

    if (tab->relkind == RELKIND_COMPOSITE_TYPE || tab->relkind == RELKIND_FOREIGN_TABLE
        || tab->relkind == RELKIND_STREAM) {
        /*
         * For composite types, do this check now.	Tables will check it later
         * when the table is being rewritten.
         */
        find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
    }

    ReleaseSysCache(tuple);

    /*
     * The recursion case is handled by ATSimpleRecursion.	However, if we are
     * told not to recurse, there had better not be any child tables; else the
     * alter would put them out of step.
     */
    if (recurse)
        ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
    else if (!recursing && find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                errmsg("type of inherited column \"%s\" must be changed in child tables too", colName)));

    if (tab->relkind == RELKIND_COMPOSITE_TYPE)
        ATTypedTableRecursion(wqueue, rel, cmd, lockmode);
}

/*
 * When the data type of a column is changed, a rewrite might not be required
 * if the new type is sufficiently identical to the old one, and the USING
 * clause isn't trying to insert some other value.  It's safe to skip the
 * rewrite if the old type is binary coercible to the new type, or if the
 * new type is an unconstrained domain over the old type.  In the case of a
 * constrained domain, we could get by with scanning the table and checking
 * the constraint rather than actually rewriting it, but we don't currently
 * try to do that.
 */
static bool ATColumnChangeRequiresRewrite(Node* expr, AttrNumber varattno)
{
    Assert(expr != NULL);

    for (;;) {
        /* only one varno, so no need to check that */
        if (IsA(expr, Var) && ((Var*)expr)->varattno == varattno)
            return false;
        else if (IsA(expr, RelabelType))
            expr = (Node*)((RelabelType*)expr)->arg;
        else if (IsA(expr, CoerceToDomain)) {
            CoerceToDomain* d = (CoerceToDomain*)expr;

            if (GetDomainConstraints(d->resulttype) != NIL)
                return true;
            expr = (Node*)d->arg;
        } else
            return true;
    }
}

static void DelDependencONDataType(const Relation rel, Relation depRel, const Form_pg_attribute attTup)
{
    ScanKeyData key[3];
    SysScanDesc scan;
    HeapTuple depTup;
    AttrNumber attnum = attTup->attnum;

    /*
     * Now scan for dependencies of this column on other things.  The only
     * thing we should find is the dependency on the column datatype, which we
     * want to remove, possibly a collation dependency, and dependencies on
     * other columns if it is a generated column.
     */
    ScanKeyInit(&key[0], Anum_pg_depend_classid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationRelationId));
    ScanKeyInit(&key[1], Anum_pg_depend_objid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(rel)));
    ScanKeyInit(&key[2], Anum_pg_depend_objsubid, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum((int32)attnum));

    scan = systable_beginscan(depRel, DependDependerIndexId, true, NULL, 3, key);

    while (HeapTupleIsValid(depTup = systable_getnext(scan))) {
        Form_pg_depend foundDep = (Form_pg_depend)GETSTRUCT(depTup);
        ObjectAddress foundObject;
        foundObject.classId = foundDep->refclassid;
        foundObject.objectId = foundDep->refobjid;
        foundObject.objectSubId = foundDep->refobjsubid;

        if (foundDep->deptype != DEPENDENCY_NORMAL && foundDep->deptype != DEPENDENCY_AUTO) {
            ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("found unexpected dependency type '%c'", foundDep->deptype)));
        }
        if (!(foundDep->refclassid == TypeRelationId && foundDep->refobjid == attTup->atttypid) &&
            !(foundDep->refclassid == CollationRelationId && foundDep->refobjid == attTup->attcollation) &&
            !(foundDep->refclassid == RelationRelationId && foundDep->refobjid == RelationGetRelid(rel) &&
            foundDep->refobjsubid != 0)) {
            ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("found unexpected dependency for column")));
        }

        simple_heap_delete(depRel, &depTup->t_self);
    }

    systable_endscan(scan);
}

/*
 * update pg_attrdef adnum for the modified column with first or after column.
 */
static void UpdateAttrdefAdnumFirstAfter(Relation rel, Oid myrelid, int curattnum, int newattnum,
    bool *has_default)
{
    ScanKeyData key[2];
    HeapTuple def_tuple;
    SysScanDesc scan;

    ScanKeyInit(&key[0], Anum_pg_attrdef_adrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(myrelid));
    ScanKeyInit(&key[1], Anum_pg_attrdef_adnum, BTEqualStrategyNumber, F_INT2EQ, Int16GetDatum(curattnum));

    scan = systable_beginscan(rel, AttrDefaultIndexId, true, NULL, 2, key);

    def_tuple = systable_getnext(scan);
    if (HeapTupleIsValid(def_tuple)) {
        Datum values[Natts_pg_attrdef] = { 0 };
        bool nulls[Natts_pg_attrdef] = { 0 };
        bool replaces[Natts_pg_attrdef] = { 0 };
        HeapTuple new_def_tuple;

        if (has_default != NULL) {
            *has_default = true;
        }

        values[Anum_pg_attrdef_adnum - 1] = Int16GetDatum(newattnum);
        replaces[Anum_pg_attrdef_adnum - 1] = true;

        new_def_tuple = heap_modify_tuple(def_tuple, RelationGetDescr(rel), values, nulls, replaces);
        simple_heap_update(rel, &new_def_tuple->t_self, new_def_tuple);
        CatalogUpdateIndexes(rel, new_def_tuple);

        heap_freetuple_ext(new_def_tuple);
    }

    systable_endscan(scan);
}

/*
 * update pg_description objsubid for the modified column with first or after column.
 */
static void UpdateDescriptionObjsubidFirstAfter(Relation rel, Oid myrelid, int curattnum, int newattnum,
    bool *has_comment)
{
    ScanKeyData key[3];
    HeapTuple desc_tuple;
    SysScanDesc scan;

    ScanKeyInit(&key[0], Anum_pg_description_objoid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(myrelid));
    ScanKeyInit(&key[1], Anum_pg_description_classoid, BTEqualStrategyNumber, F_OIDEQ, RelationRelationId);
    ScanKeyInit(&key[2], Anum_pg_description_objsubid, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(curattnum));

    scan = systable_beginscan(rel, DescriptionObjIndexId, true, NULL, 3, key);

    desc_tuple = systable_getnext(scan);
    if (HeapTupleIsValid(desc_tuple)) {
        Datum values[Natts_pg_description] = { 0 };
        bool nulls[Natts_pg_description] = { 0 };
        bool replaces[Natts_pg_description] = { 0 };
        HeapTuple new_desc_tuple;

        if (has_comment != NULL) {
            *has_comment = true;
        }

        values[Anum_pg_description_objsubid - 1] = Int32GetDatum(newattnum);
        replaces[Anum_pg_description_objsubid - 1] = true;

        new_desc_tuple = heap_modify_tuple(desc_tuple, RelationGetDescr(rel), values, nulls, replaces);
        simple_heap_update(rel, &new_desc_tuple->t_self, new_desc_tuple);
        CatalogUpdateIndexes(rel, new_desc_tuple);

        heap_freetuple_ext(new_desc_tuple);
    }

    systable_endscan(scan);
}
 
/* 
 * update pg_depend refobjsubid for the modified column with first or after column.
 */
static void UpdateDependRefobjsubidFirstAfter(Relation rel, Oid myrelid, int curattnum, int newattnum,
    bool *has_depend)
{
    ScanKeyData key[2];
    HeapTuple dep_tuple;
    Form_pg_depend dep_form;
    SysScanDesc scan;

    ScanKeyInit(&key[0], Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(myrelid));
    ScanKeyInit(&key[1], Anum_pg_depend_refobjsubid, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(curattnum));

    scan = systable_beginscan(rel, DependReferenceIndexId, true, NULL, 2, key);
    while (HeapTupleIsValid(dep_tuple = systable_getnext(scan))) {
        Datum values[Natts_pg_depend] = { 0 };
        bool nulls[Natts_pg_depend] = { 0 };
        bool replaces[Natts_pg_depend] = { 0 };
        HeapTuple new_dep_tuple;

        dep_form  = (Form_pg_depend)GETSTRUCT(dep_tuple);

        if (has_depend != NULL) {
            *has_depend = true;
        }

        values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(-1);
        replaces[Anum_pg_depend_refobjsubid - 1] = true;

        if (dep_form->objid == myrelid) {
            int startattnum;
            int endattnum;
            bool is_increase = false;
            if (newattnum <= curattnum - 1) {
                startattnum = newattnum;
                endattnum = curattnum - 1;
                is_increase = true;
            } else {
                startattnum = curattnum + 1;
                endattnum = newattnum;
            }
            if (dep_form->objsubid >= startattnum && dep_form->objsubid <= endattnum) {
                values[Anum_pg_depend_objsubid - 1] = is_increase ?
                    Int32GetDatum(dep_form->objsubid + 1) : Int32GetDatum(dep_form->objsubid - 1);
                replaces[Anum_pg_depend_objsubid - 1] = true;
            } else if (dep_form->objsubid == curattnum) {
                values[Anum_pg_depend_objsubid - 1] = Int32GetDatum(newattnum);
                replaces[Anum_pg_depend_objsubid - 1] = true;
            }
        }

        new_dep_tuple = heap_modify_tuple(dep_tuple, RelationGetDescr(rel), values, nulls, replaces);
        simple_heap_update(rel, &new_dep_tuple->t_self, new_dep_tuple);
        CatalogUpdateIndexes(rel, new_dep_tuple);

        heap_freetuple_ext(new_dep_tuple);
    }
    systable_endscan(scan);
}

/*
 * update pg_depend refobjsubid for the modified column with first or after column.
 */
static void UpdateDependRefobjsubidToNewattnum(Relation rel, Oid myrelid, int curattnum, int newattnum)
{
    ScanKeyData key[2];
    HeapTuple dep_tuple;
    Form_pg_depend dep_form;
    SysScanDesc scan;

    ScanKeyInit(&key[0], Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(myrelid));
    ScanKeyInit(&key[1], Anum_pg_depend_refobjsubid, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(curattnum));

    scan = systable_beginscan(rel, DependReferenceIndexId, true, NULL, 2, key);
    while (HeapTupleIsValid(dep_tuple = systable_getnext(scan))) {
        Datum values[Natts_pg_depend] = { 0 };
        bool nulls[Natts_pg_depend] = { 0 };
        bool replaces[Natts_pg_depend] = { 0 };
        HeapTuple new_dep_tuple;
        dep_form  = (Form_pg_depend)GETSTRUCT(dep_tuple);

        values[Anum_pg_depend_refobjsubid - 1] = Int32GetDatum(newattnum);
        replaces[Anum_pg_depend_refobjsubid - 1] = true;

        new_dep_tuple = heap_modify_tuple(dep_tuple, RelationGetDescr(rel), values, nulls, replaces);
        simple_heap_update(rel, &new_dep_tuple->t_self, new_dep_tuple);
        CatalogUpdateIndexes(rel, new_dep_tuple);

        heap_freetuple_ext(new_dep_tuple);
    }

    systable_endscan(scan);
}

/*
 * update pg_partition partkey for the modified column with first or after column.
 */
static void UpdatePartitionPartkeyFirstAfter(Oid myrelid, int curattnum, int newattnum)
{
    ScanKeyData skey;
    HeapTuple par_tuple;
    Relation par_rel;
    SysScanDesc scan;

    par_rel = heap_open(PartitionRelationId, RowExclusiveLock);

    ScanKeyInit(&skey, Anum_pg_partition_parentid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(myrelid));

    scan = systable_beginscan(par_rel, PartitionParentOidIndexId, true, NULL, 1, &skey);
    while (HeapTupleIsValid(par_tuple = systable_getnext(scan))) {
        bool is_null = false;

        // update pg_partition_partkey
        Datum partkey_datum = SysCacheGetAttr(PARTRELID, par_tuple, Anum_pg_partition_partkey, &is_null);
        if (!is_null) {
            Datum values[Natts_pg_partition] = { 0 };
            bool nulls[Natts_pg_partition] = { 0 };
            bool replaces[Natts_pg_partition] = { 0 };
            int2vector *partkey = NULL;
            int2vector *new_partKey = NULL;
            HeapTuple new_par_tuple;

            partkey = (int2vector *)DatumGetPointer(partkey_datum);
            new_partKey = buildint2vector(NULL, partkey->dim1);
            for (int i = 0; i < partkey->dim1; i++) {
                if (partkey->values[i] == curattnum) {
                    new_partKey->values[i] = newattnum;
                } else {
                    new_partKey->values[i] = partkey->values[i];
                }
            }
            values[Anum_pg_partition_partkey - 1] = PointerGetDatum(new_partKey);
            replaces[Anum_pg_partition_partkey - 1] = true;

            new_par_tuple = heap_modify_tuple(par_tuple, RelationGetDescr(par_rel), values, nulls, replaces);
            simple_heap_update(par_rel, &new_par_tuple->t_self, new_par_tuple);
            CatalogUpdateIndexes(par_rel, new_par_tuple);

            pfree_ext(new_partKey);
            heap_freetuple_ext(new_par_tuple);
        }
    }
    systable_endscan(scan);
    heap_close(par_rel, RowExclusiveLock);
}

static int GetNewattnumFirstAfter(Relation rel, AlterTableCmd* cmd, int curattnum)
{
    bool is_first = cmd->is_first;
    char *after_name = cmd->after_name;
    int newattnum = 0;

    if (is_first && curattnum == 1) {
        return 0;
    }

    if (is_first) {
        newattnum = 1;
    } else if (after_name != NULL) {
        newattnum =GetAfterColumnAttnum(RelationGetRelid(rel), after_name) - 1;
        if (newattnum + 1 == curattnum) {
            return 0;
        }

        if (newattnum == curattnum) {
            ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN),
                errmsg("Unknown column \"%s\" in \"%s\"", after_name, RelationGetRelationName(rel))));
        } else if (newattnum < curattnum) {
            newattnum++;
        }
    }
    return newattnum;
}

static void AlterColumnToFirstAfter(AlteredTableInfo* tab, Relation rel, AlterTableCmd* cmd,
    int curattnum)
{
    Oid myrelid = RelationGetRelid(rel);
    int newattnum;
    Relation attr_rel;
    HeapTuple att_tuple_old, att_tuple_new;
    Form_pg_attribute att_form_old, attr_form_new;
    int startattnum, endattnum;
    bool has_comment = false;
    bool has_default = false;
    bool has_depend = false;
    bool has_partition = false;
    bool is_increase = false;
    List *query_str = NIL;

    newattnum = GetNewattnumFirstAfter(rel, cmd, curattnum);
    if (newattnum == 0) {
        return;
    }

    tab->rewrite = true;

    attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);

    att_tuple_old = SearchSysCacheCopy2(ATTNUM, ObjectIdGetDatum(myrelid), Int16GetDatum(curattnum));
    if (!HeapTupleIsValid(att_tuple_old)) {
        ereport(ERROR, (errmodule(MOD_SEC), errcode(ERRCODE_CACHE_LOOKUP_FAILED),
            errmsg("cache lookup failed for attribute %d of relation %u", curattnum, myrelid), errdetail("N/A"),
                errcause("System error."), erraction("Contact engineer to support.")));
    }

    att_form_old = (Form_pg_attribute)GETSTRUCT(att_tuple_old);

    att_form_old->attnum = 0;

    simple_heap_update(attr_rel, &att_tuple_old->t_self, att_tuple_old);
    CatalogUpdateIndexes(attr_rel, att_tuple_old);

    Relation desc_rel = heap_open(DescriptionRelationId, RowExclusiveLock);
    UpdateDescriptionObjsubidFirstAfter(desc_rel, myrelid, curattnum, 0, &has_comment);
 
    Relation def_rel = heap_open(AttrDefaultRelationId, RowExclusiveLock);
    UpdateAttrdefAdnumFirstAfter(def_rel, myrelid, curattnum, 0, &has_default);

    Relation dep_rel = heap_open(DependRelationId, RowExclusiveLock);
    UpdateDependRefobjsubidFirstAfter(dep_rel, myrelid, curattnum, newattnum, &has_depend);

    if (newattnum <= curattnum - 1) {
        startattnum = newattnum;
        endattnum = curattnum - 1;
        is_increase = true;
    } else {
        startattnum = curattnum + 1;
        endattnum = newattnum;
    }

    UpdatePgPartitionFirstAfter(rel, startattnum, endattnum, is_increase, true, &has_partition);
    UpdatePgAttributeFirstAfter(attr_rel, myrelid, startattnum, endattnum, is_increase);
    UpdatePgDescriptionFirstAfter(rel, startattnum, endattnum, is_increase);
    UpdatePgStatisticFirstAfter(rel, startattnum, endattnum, is_increase);
    UpdatePgStatisticExtFirstAfter(rel, startattnum, endattnum, is_increase);
    UpdatePgIndexFirstAfter(rel, startattnum, endattnum, is_increase);
    UpdatePgConstraintFirstAfter(rel, startattnum, endattnum, is_increase);
    UpdatePgConstraintConfkeyFirstAfter(rel, startattnum, endattnum, is_increase);
    UpdatePgAttrdefFirstAfter(rel, startattnum, endattnum, is_increase);
    query_str = CheckPgRewriteFirstAfter(rel);
    UpdatePgTriggerFirstAfter(rel, startattnum, endattnum, is_increase);
    UpdatePgRlspolicyFirstAfter(rel, startattnum, endattnum, is_increase);
    CommandCounterIncrement();

    UpdateGenerateColFirstAfter(rel, startattnum, endattnum, is_increase);
    UpdatePgDependFirstAfter(rel, startattnum, endattnum, is_increase);
    CommandCounterIncrement();

    att_tuple_new = SearchSysCacheCopy2(ATTNUM, ObjectIdGetDatum(myrelid), Int16GetDatum(0));
    if (!HeapTupleIsValid(att_tuple_new)) {
        ereport(ERROR, (errmodule(MOD_SEC), errcode(ERRCODE_CACHE_LOOKUP_FAILED),
            errmsg("cache lookup failed for attribute %d of relation %u", 0, myrelid), errdetail("N/A"),
                errcause("System error."), erraction("Contact engineer to support.")));
    }
    attr_form_new = (Form_pg_attribute)GETSTRUCT(att_tuple_new);

    attr_form_new->attnum = newattnum;
    simple_heap_update(attr_rel, &att_tuple_new->t_self, att_tuple_new);
    // keep system catalog indexes current
    CatalogUpdateIndexes(attr_rel, att_tuple_new);

    heap_close(attr_rel, RowExclusiveLock);
    heap_freetuple_ext(att_tuple_old);
    heap_freetuple_ext(att_tuple_new);

    if (has_comment) {
        UpdateDescriptionObjsubidFirstAfter(desc_rel, myrelid, 0, newattnum, NULL);
    }
    heap_close(desc_rel, RowExclusiveLock);
 
    if (has_default) {
        UpdateAttrdefAdnumFirstAfter(def_rel, myrelid, 0, newattnum, NULL);
    }
    heap_close(def_rel, RowExclusiveLock);

    if (has_depend) {
        UpdateDependRefobjsubidToNewattnum(dep_rel, myrelid, -1, newattnum);
    }
    heap_close(dep_rel, RowExclusiveLock);

    if (has_partition) {
        UpdatePartitionPartkeyFirstAfter(myrelid, 0, newattnum);
    }

    CommandCounterIncrement();

    /* create or replace view */
    ReplaceViewQueryFirstAfter(query_str);
}

static bool CheckIndexIsConstraint(Relation dep_rel, Oid objid, Oid *refobjid)
{
    ScanKeyData key[2];
    HeapTuple dep_tuple;
    SysScanDesc scan;
    Form_pg_depend dep_form;
    bool is_constraint = false;

    ScanKeyInit(&key[0], Anum_pg_depend_classid,
        BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationRelationId));
    ScanKeyInit(&key[1], Anum_pg_depend_objid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(objid));
    scan = systable_beginscan(dep_rel, DependDependerIndexId, true, NULL, 2, key);

    while (HeapTupleIsValid(dep_tuple = systable_getnext(scan))) {
        dep_form = (Form_pg_depend)GETSTRUCT(dep_tuple);
        if (dep_form->refclassid == ConstraintRelationId && dep_form->refobjsubid == 0) {
            *refobjid = dep_form->refobjid;
            is_constraint = true;
            break;
        }
    }
    systable_endscan(scan);
    return is_constraint;
}

static void UpdateNewvalsAttnum(AlteredTableInfo* tab, Relation rel, AlterTableCmd* cmd, char* col_name)
{
    ListCell* l = NULL;
    foreach(l, tab->newvals) {
        NewColumnValue* ex = (NewColumnValue*)lfirst(l);

        if (ex->col_name == NULL) {
            continue;
        }

        if (strcmp(ex->col_name, col_name) == 0 && !ex->is_updated) {
            HeapTuple heap_tup;
            Form_pg_attribute att_tup;

            heap_tup = SearchSysCacheCopyAttName(RelationGetRelid(rel), col_name);
            if (!HeapTupleIsValid(heap_tup)) { /* shouldn't happen */
                ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN),
                    errmsg("column \"%s\" of relation \"%s\" does not exist", col_name, RelationGetRelationName(rel))));
            }
            att_tup = (Form_pg_attribute)GETSTRUCT(heap_tup);
            ex->attnum = att_tup->attnum;
            ex->newattnum = GetNewattnumFirstAfter(rel, cmd, ex->attnum);
            ex->is_updated = true;

            tableam_tops_free_tuple(heap_tup);
            return;
        }
    }
}

bool InvalidateDependView(Oid viewOid, char objType)
{
    List* view_oid_list = NIL;
    List *dep_oid_list = NIL;
    // 1. filter the invalid view
    if (!GetPgObjectValid(viewOid, objType)) {
        return false;
    }
    // 2. find all views which depend on this view directly or indirectly
    view_oid_list = lappend_oid(view_oid_list, viewOid);
    dep_oid_list = lappend_oid(dep_oid_list, viewOid);
    const int keyNum = 2;
    ScanKeyData key[keyNum];
    SysScanDesc scan = NULL;
    HeapTuple tup = NULL;
    Relation dep_rel = heap_open(DependRelationId, AccessShareLock);
    while (list_length(dep_oid_list) > 0) {
        // (1) get dependent view oid
        Oid objid = linitial_oid(dep_oid_list);
        dep_oid_list = list_delete_first(dep_oid_list);
        List *rw_oid_list = NIL;
        ListCell *rw_cell = NULL;
        // (2) find rw_objid of pg_rewrite entry from pg_depend by objid
        ScanKeyInit(&key[0], Anum_pg_depend_refclassid, BTEqualStrategyNumber, F_OIDEQ,
            ObjectIdGetDatum(RelationRelationId));
        ScanKeyInit(&key[1], Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(objid));
        scan = systable_beginscan(dep_rel, DependReferenceIndexId, true, NULL, keyNum, key);
        while (HeapTupleIsValid((tup = systable_getnext(scan)))) {
            Form_pg_depend depform = (Form_pg_depend)GETSTRUCT(tup);
            Oid rw_objid = depform->objid;
            if (depform->classid == RewriteRelationId && depform->deptype == DEPENDENCY_NORMAL
                && !list_member_oid(rw_oid_list, rw_objid)) {
                rw_oid_list = lappend_oid(rw_oid_list, rw_objid);
            }
        }
        // (3) find dependent view oid from pg_rewrite by rw_objid
        foreach(rw_cell, rw_oid_list) {
            Oid rw_objid = lfirst_oid(rw_cell);
            Oid dep_view_oid = get_rewrite_relid(rw_objid, true);
            if (!OidIsValid(dep_view_oid) || dep_view_oid == objid) {
                continue;
            }
            char relkind = get_rel_relkind(dep_view_oid);
            if (relkind == objType && dep_view_oid == viewOid) {
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                        errmsg(
                            "infinite recursion detected in rules for relation: \"%s\"", get_rel_name(viewOid))));
            }
            if (relkind != RELKIND_VIEW && relkind != RELKIND_MATVIEW) {
                continue;
            }
            dep_oid_list = lappend_oid(dep_oid_list, dep_view_oid);
            view_oid_list = lappend_oid(view_oid_list, dep_view_oid);
        }
        list_free_ext(rw_oid_list);
        systable_endscan(scan);
    }
    heap_close(dep_rel, AccessShareLock);
    // 3. mark all dependent view invalid
    ListCell *dep_cell = NULL;
    foreach(dep_cell, view_oid_list) {
        Oid depoid = lfirst_oid(dep_cell);
        SetPgObjectValid(depoid, objType, false);
    }
    list_free_ext(view_oid_list);
    list_free_ext(dep_oid_list);
    return true;
}

static ObjectAddress ATExecAlterColumnType(AlteredTableInfo* tab, Relation rel, AlterTableCmd* cmd, LOCKMODE lockmode)
{
    char* colName = cmd->name;
    ColumnDef* def = (ColumnDef*)cmd->def;
    TypeName* typname = def->typname;
    HeapTuple heapTup;
    Form_pg_attribute attTup;
    AttrNumber attnum;
    HeapTuple typeTuple;
    Form_pg_type tform;
    Oid targettype = InvalidOid;
    int32 targettypmod = -1;
    Oid targetcollid = InvalidOid;
    Node* defaultexpr = NULL;
    Relation attrelation;
    Relation depRel;
    ScanKeyData key[3];
    SysScanDesc scan;
    HeapTuple depTup;
    char generatedCol = '\0';
    Node* update_expr = NULL;
    bool flagDropOnUpdateTimestamp = false;
    bool existOnUpdateTimestamp = false;
    ObjectAddress address;

    attrelation = heap_open(AttributeRelationId, RowExclusiveLock);

    /* Look up the target column */
    heapTup = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
    if (!HeapTupleIsValid(heapTup)) /* shouldn't happen */
        ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN),
                errmsg("column \"%s\" of relation \"%s\" does not exist", colName, RelationGetRelationName(rel))));
    attTup = (Form_pg_attribute)GETSTRUCT(heapTup);
    attnum = attTup->attnum;

    /*
     * data type of a partitioned table's partition key can not be changed
     */
    if (RELATION_IS_PARTITIONED(rel) && is_partition_column(rel, attnum)) {
        int2vector* partKey = PartitionMapGetPartKeyArray(rel->partMap);
        int i = 0;

        for (; i < partKey->dim1; i++) {
            if (attnum == partKey->values[i]) {
                ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("cannot alter data type of partitioning column \"%s\"", colName)));
            }
        }

        if ((rel)->rd_rel->parttype == PARTTYPE_SUBPARTITIONED_RELATION) {
            List *partTupleList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_PARTITION, RelationGetRelid(rel));
            if (partTupleList != NIL) {
                bool isnull = false;
                HeapTuple partTuple = (HeapTuple)linitial(partTupleList);
                Datum datum = SysCacheGetAttr(PARTRELID, partTuple, Anum_pg_partition_partkey, &isnull);
                if (!isnull) {
                    int2vector *subpartkey = (int2vector *)DatumGetPointer(datum);
                    for (int j = 0; j < subpartkey->dim1; j++) {
                        if (attnum == subpartkey->values[j]) {
                            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                errmsg("cannot alter data type of subpartitioning column \"%s\"", colName)));
                        }
                    }
                }
                list_free_ext(partTupleList);
            }
        }
    }

    /* Check for multiple ALTER TYPE on same column --- can't cope */
    if (!tab->is_first_after) {
        if (attTup->atttypid != tab->oldDesc->attrs[attnum - 1].atttypid ||
            attTup->atttypmod != tab->oldDesc->attrs[attnum - 1].atttypmod)
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot alter type of column \"%s\" twice", colName)));
    }

    /* Look up the target type (should not fail, since prep found it) */
    typeTuple = typenameType(NULL, typname, &targettypmod);
    tform = (Form_pg_type)GETSTRUCT(typeTuple);
    targettype = HeapTupleGetOid(typeTuple);
    /* And the collation */
    Oid rel_coll_oid = rel->rd_options == NULL ? InvalidOid : ((StdRdOptions*)(rel)->rd_options)->collate;
    targetcollid = GetColumnDefCollation(NULL, def, targettype, rel_coll_oid);
    if (DB_IS_CMPT_BD) {
        targettype = binary_need_transform_typeid(targettype, &targetcollid);
        if (RelationIsColStore(rel) || RelationIsTsStore(rel)) {
            check_unsupported_charset_for_column(targetcollid, colName);
        }
    }
    if (attnum == RelAutoIncAttrNum(rel)) {
        CheckAutoIncrementDatatype(targettype, colName);
    }
    generatedCol = GetGeneratedCol(rel->rd_att, attnum -1);

    /*
     * If there is a default expression for the column, get it and ensure we
     * can coerce it to the new datatype.  (We must do this before changing
     * the column type, because build_column_default itself will try to
     * coerce, and will not issue the error message we want if it fails.)
     *
     * We remove any implicit coercion steps at the top level of the old
     * default expression; this has been agreed to satisfy the principle of
     * least surprise.	(The conversion to the new column type should act like
     * it started from what the user sees as the stored expression, and the
     * implicit coercions aren't going to be shown.)
     */
    if (attTup->atthasdef) {
        if (RelAutoIncAttrNum(rel) == attnum) {
            defaultexpr = RecookAutoincAttrDefault(rel, attnum, targettype, targettypmod);
            if (defaultexpr == NULL) {
                if (generatedCol == ATTRIBUTE_GENERATED_STORED || generatedCol == ATTRIBUTE_GENERATED_PERSISTED) {
                    ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_DATATYPE_MISMATCH),
                        errmsg("generation expression for column \"%s\" cannot be cast automatically to type %s",
                            colName, format_type_be(targettype))));
                } else {
                    ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH),
                        errmsg("default for column \"%s\" cannot be cast automatically to type %s", colName,
                            format_type_be(targettype))));
                }
            }
        } else {
            defaultexpr = build_column_default(rel, attnum);
            /* for column only with on update but no default ,here could be NULL*/
            if (defaultexpr != NULL) {
                defaultexpr = strip_implicit_coercions(defaultexpr);
                defaultexpr = coerce_to_target_type(NULL, /* no UNKNOWN params */
                    defaultexpr,
                    exprType(defaultexpr),
                    targettype,
                    targettypmod,
                    COERCION_ASSIGNMENT,
                    COERCE_IMPLICIT_CAST,
                    NULL,
                    NULL,
                    -1);
                if (defaultexpr == NULL) {
                    if (generatedCol == ATTRIBUTE_GENERATED_STORED || generatedCol == ATTRIBUTE_GENERATED_PERSISTED) {
                        ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_DATATYPE_MISMATCH),
                            errmsg("generation expression for column \"%s\" cannot be cast automatically to type %s",
                                colName, format_type_be(targettype))));
                    } else {
                        ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH),
                            errmsg("default for column \"%s\" cannot be cast automatically to type %s", colName,
                                format_type_be(targettype))));
                    }
                }
            }
        }
    } else
        defaultexpr = NULL;

    /*
     * Find everything that depends on the column (constraints, indexes, etc),
     * and record enough information to let us recreate the objects.
     *
     * The actual recreation does not happen here, but only after we have
     * performed all the individual ALTER TYPE operations.	We have to save
     * the info before executing ALTER TYPE, though, else the deparser will
     * get confused.
     *
     * There could be multiple entries for the same object, so we must check
     * to ensure we process each one only once.  Note: we assume that an index
     * that implements a constraint will not show a direct dependency on the
     * column.
     */
    depRel = heap_open(DependRelationId, RowExclusiveLock);

    ScanKeyInit(
        &key[0], Anum_pg_depend_refclassid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationRelationId));
    ScanKeyInit(
        &key[1], Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(rel)));
    ScanKeyInit(&key[2], Anum_pg_depend_refobjsubid, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum((int32)attnum));

    scan = systable_beginscan(depRel, DependReferenceIndexId, true, NULL, 3, key);

    while (HeapTupleIsValid(depTup = systable_getnext(scan))) {
        Form_pg_depend foundDep = (Form_pg_depend)GETSTRUCT(depTup);
        ObjectAddress foundObject;

        /* We don't expect any PIN dependencies on columns */
        if (foundDep->deptype == DEPENDENCY_PIN)
            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot alter type of a pinned column")));

        foundObject.classId = foundDep->classid;
        foundObject.objectId = foundDep->objid;
        foundObject.objectSubId = foundDep->objsubid;

        switch (getObjectClass(&foundObject)) {
            case OCLASS_CLASS: {
                char relKind = get_rel_relkind(foundObject.objectId);

                if (relKind == RELKIND_INDEX || relKind == RELKIND_GLOBAL_INDEX) {
                    Assert(foundObject.objectSubId == 0);

                    Oid refobjid;
                    if (!list_member_oid(tab->changedConstraintOids, foundObject.objectId) &&
                        CheckIndexIsConstraint(depRel, foundObject.objectId, &refobjid)) {
                            tab->changedConstraintOids = lappend_oid(tab->changedConstraintOids, refobjid);
                            tab->changedConstraintDefs =
                                lappend(tab->changedConstraintDefs, pg_get_constraintdef_string(refobjid));
                    } else if (!list_member_oid(tab->changedIndexOids, foundObject.objectId)) {
                        /*
                         * Question: alter table set datatype and table index execute concurrently, data inconsistency
                         * occurs. The index file is deleted and metadata is left. Because the data type is not locked
                         * after modification, which ultimately leads to could not open file. Alter table column set
                         * datatype maybe trigger index operation but index is not locked. When the index data is
                         * inconsistent, we can use"reindex index" to repair the index.
                         * Solution: we should lock index at the beginning.The AccessExclusiveLock for index is used
                         * because we think AccessExclusiveLock for data table will block any operation and index
                         * will be not used to query data. This operation will block individual index operations,
                         * such as reindex index\set index tablespace.
                         * Testcase: alter table row_table alter column col_varchar set data type text,alter column
                         * col_smallint set data type bigint + alter index idx set tablespace.
                         */
                        LockRelationOid(foundObject.objectId, AccessExclusiveLock);
                        tab->changedIndexOids = lappend_oid(tab->changedIndexOids, foundObject.objectId);
                        tab->changedIndexDefs =
                            lappend(tab->changedIndexDefs, pg_get_indexdef_string(foundObject.objectId));
                    }
                } else if (RELKIND_IS_SEQUENCE(relKind)) {
                    /*
                     * This must be a SERIAL column's sequence.  We need
                     * not do anything to it.
                     */
                    Assert(foundObject.objectSubId == 0);
                } else if (relKind == RELKIND_RELATION && foundObject.objectSubId != 0 &&
                    GetGenerated(foundObject.objectId, foundObject.objectSubId)) {
                    /*
                     * Changing the type of a column that is used by a
                     * generated column is not allowed by SQL standard. It
                     * might be doable with some thinking and effort.
                     */
                    ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_SYNTAX_ERROR),
                        errmsg("cannot alter type of a column used by a generated column"),
                        errdetail("Column \"%s\" is used by generated column \"%s\".", colName,
                        get_attname(foundObject.objectId, foundObject.objectSubId))));
                }else {
                    /* Not expecting any other direct dependencies... */
                    ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                            errmsg("unexpected object depending on column: %s", getObjectDescription(&foundObject))));
                }
                break;
            }

            case OCLASS_CONSTRAINT:
                Assert(foundObject.objectSubId == 0);
                if (!list_member_oid(tab->changedConstraintOids, foundObject.objectId)) {
                    char* defstring = pg_get_constraintdef_string(foundObject.objectId);

                    /*
                     * Put NORMAL dependencies at the front of the list and
                     * AUTO dependencies at the back.  This makes sure that
                     * foreign-key constraints depending on this column will
                     * be dropped before unique or primary-key constraints of
                     * the column; which we must have because the FK
                     * constraints depend on the indexes belonging to the
                     * unique constraints.
                     */
                    if (foundDep->deptype == DEPENDENCY_NORMAL) {
                        tab->changedConstraintOids = lcons_oid(foundObject.objectId, tab->changedConstraintOids);
                        tab->changedConstraintDefs = lcons(defstring, tab->changedConstraintDefs);
                    } else {
                        tab->changedConstraintOids = lappend_oid(tab->changedConstraintOids, foundObject.objectId);
                        tab->changedConstraintDefs = lappend(tab->changedConstraintDefs, defstring);
                    }
                }
                break;

            case OCLASS_REWRITE: {
                /* XXX someday see if we can cope with revising views */
                    Oid objOid = get_rewrite_relid(foundObject.objectId, false);
                    char relKind = get_rel_relkind(objOid);
                    if (relKind == RELKIND_VIEW || relKind == RELKIND_MATVIEW) {
                        (void)InvalidateDependView(objOid,
                                                   relKind == RELKIND_VIEW ? OBJECT_TYPE_VIEW : OBJECT_TYPE_MATVIEW);
                    } else {
                        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                            errmsg("cannot alter type of a column used by a relation kind %c", relKind),
                            errdetail("%s depends on column \"%s\"", getObjectDescription(&foundObject), colName)));
                    }
                    break;
                }

            case OCLASS_TRIGGER:

                /*
                 * A trigger can depend on a column because the column is
                 * specified as an update target, or because the column is
                 * used in the trigger's WHEN condition.  The first case would
                 * not require any extra work, but the second case would
                 * require updating the WHEN expression, which will take a
                 * significant amount of new code.	Since we can't easily tell
                 * which case applies, we punt for both.
                 */
                ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("cannot alter type of a column used in a trigger definition"),
                        errdetail("%s depends on column \"%s\"", getObjectDescription(&foundObject), colName)));
                break;

            case OCLASS_DEFAULT:

                /*
                 * Ignore the column's default expression, since we will fix
                 * it below.
                 */
                break;

            case OCLASS_PROC:
            case OCLASS_TYPE:
            case OCLASS_CAST:
            case OCLASS_COLLATION:
            case OCLASS_CONVERSION:
            case OCLASS_LANGUAGE:
            case OCLASS_LARGEOBJECT:
            case OCLASS_OPERATOR:
            case OCLASS_OPCLASS:
            case OCLASS_OPFAMILY:
            case OCLASS_AMOP:
            case OCLASS_AMPROC:
            case OCLASS_SCHEMA:
            case OCLASS_TSPARSER:
            case OCLASS_TSDICT:
            case OCLASS_TSTEMPLATE:
            case OCLASS_TSCONFIG:
            case OCLASS_ROLE:
            case OCLASS_DATABASE:
            case OCLASS_TBLSPACE:
            case OCLASS_FDW:
            case OCLASS_FOREIGN_SERVER:
            case OCLASS_USER_MAPPING:
            case OCLASS_DEFACL:
            case OCLASS_EXTENSION:
            case OCLASS_DATA_SOURCE:
            case OCLASS_GLOBAL_SETTING_ARGS:
            case OCLASS_GS_CL_PROC:

                /*
                 * We don't expect any of these sorts of objects to depend on
                 * a column.
                 */
                ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("unexpected object depending on column: %s", getObjectDescription(&foundObject))));
                break;

            default:
                ereport(ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                        errmsg("unrecognized object class: %u", foundObject.classId)));
        }
    }

    systable_endscan(scan);

    DelDependencONDataType(rel, depRel, attTup);

    heap_close(depRel, RowExclusiveLock);

    if (tab->is_first_after) {
        UpdateNewvalsAttnum(tab, rel, cmd, colName);
    }

    /*
     * Here we go --- change the recorded column type and collation.  (Note
     * heapTup is a copy of the syscache entry, so okay to scribble on.)
     */
    attTup->atttypid = targettype;
    attTup->atttypmod = targettypmod;
    attTup->attcollation = targetcollid;
    attTup->attndims = list_length(typname->arrayBounds);
    attTup->attlen = tform->typlen;
    attTup->attbyval = tform->typbyval;
    attTup->attalign = tform->typalign;
    attTup->attstorage = tform->typstorage;

    ReleaseSysCache(typeTuple);

    simple_heap_update(attrelation, &heapTup->t_self, heapTup);

    /* keep system catalog indexes current */
    CatalogUpdateIndexes(attrelation, heapTup);

    heap_close(attrelation, RowExclusiveLock);

    /* Install dependencies on new datatype and collation */
    add_column_datatype_dependency(RelationGetRelid(rel), attnum, targettype);
    add_column_collation_dependency(RelationGetRelid(rel), attnum, targetcollid);

    /*
     * Drop any pg_statistic entry for the column, since it's now wrong type
     */
    if (RELATION_IS_GLOBAL_TEMP(rel)) {
        remove_gtt_att_statistic(RelationGetRelid(rel), attnum);
    } else {
        RemoveStatistics<'c'>(RelationGetRelid(rel), attnum);
    }

    /* def->constraints maybe is null when execute sql(alter table x alter column type new_type) */
    if (cmd->subtype == AT_AlterColumnType && def->constraints && def->constraints->head) {
        Constraint* temp_cons = (Constraint*)lfirst(def->constraints->head);
        if (temp_cons->contype == CONSTR_DEFAULT && temp_cons->update_expr != NULL) {
            update_expr = temp_cons->update_expr;
        }
    }

    /* when the default expr is NULL and on update expr exist, the defaultexpr should be NULL.
     * because the build_column_default maybe return the on update expr. */
    if (rel->rd_att->constr && rel->rd_att->constr->num_defval > 0) {
        int ndef = rel->rd_att->constr->num_defval -1;
        while (ndef >= 0 && rel->rd_att->constr->defval[ndef].adnum != attnum) {
            /* modify column on update expr when the column don't at the end of table */
            --ndef;
        }
        if (ndef >= 0) {
            if (pg_strcasecmp(rel->rd_att->constr->defval[ndef].adbin, "") == 0 &&
                rel->rd_att->constr->defval[ndef].has_on_update) {
                existOnUpdateTimestamp = true;
                if (update_expr == NULL) {
                    CommandCounterIncrement();
                    RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, true, true);
                    flagDropOnUpdateTimestamp = true;
                }
            }
        }
    }

    /*
     * Update the default, if present, by brute force --- remove and re-add
     * the default.  Probably unsafe to take shortcuts, since the new version
     * may well have additional dependencies.  (It's okay to do this now,
     * rather than after other ALTER TYPE commands, since the default won't
     * depend on other column types.)
     */
    if ((defaultexpr != NULL || update_expr != NULL) && !flagDropOnUpdateTimestamp) {
        /* Must make new row visible since it will be updated again */
        CommandCounterIncrement();

        /*
         * We use RESTRICT here for safety, but at present we do not expect
         * anything to depend on the default.
         */
        if (defaultexpr != NULL || (update_expr != NULL && existOnUpdateTimestamp)) {
            RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, true, true);
        }
        if (update_expr != NULL) {
            ParseState* pstate = make_parsestate(NULL);
            RangeTblEntry*  rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
            addRTEtoQuery(pstate, rte, true, true, true);
            pstate->p_rawdefaultlist = NULL;
            update_expr = cookDefault(pstate, update_expr, attTup->atttypid, attTup->atttypmod,
                attTup->attcollation, NameStr(attTup->attname), def->generatedCol);
        }

        StoreAttrDefault(rel, attnum, defaultexpr, generatedCol, update_expr);
    }
    ObjectAddressSubSet(address, RelationRelationId,
                        RelationGetRelid(rel), attnum);
    /* Cleanup */
    tableam_tops_free_tuple(heapTup);
    return address;
}

 /*
  * Returns the address of the modified column
  */
static ObjectAddress ATExecAlterColumnGenericOptions(Relation rel, const char* colName, List* options, LOCKMODE lockmode)
{
    Relation ftrel;
    Relation attrel;
    ForeignServer* server = NULL;
    ForeignDataWrapper* fdw = NULL;
    HeapTuple tuple;
    HeapTuple newtuple;
    bool isnull = false;
    Datum repl_val[Natts_pg_attribute];
    bool repl_null[Natts_pg_attribute];
    bool repl_repl[Natts_pg_attribute];
    Datum datum;
    Form_pg_foreign_table fttableform;
    Form_pg_attribute atttableform;
    errno_t rc = EOK;
    AttrNumber  attnum;
    ObjectAddress address;

    if (options == NIL)
        return InvalidObjectAddress;

    /* First, determine FDW validator associated to the foreign table. */
    ftrel = heap_open(ForeignTableRelationId, AccessShareLock);
    tuple = SearchSysCache1(FOREIGNTABLEREL, rel->rd_id);
    if (!HeapTupleIsValid(tuple))
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_OBJECT),
                errmsg("foreign table \"%s\" does not exist", RelationGetRelationName(rel))));
    fttableform = (Form_pg_foreign_table)GETSTRUCT(tuple);
    server = GetForeignServer(fttableform->ftserver);
    fdw = GetForeignDataWrapper(server->fdwid);

    heap_close(ftrel, AccessShareLock);
    ReleaseSysCache(tuple);

    attrel = heap_open(AttributeRelationId, RowExclusiveLock);
    tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
    if (!HeapTupleIsValid(tuple))
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_COLUMN),
                errmsg("column \"%s\" of relation \"%s\" does not exist", colName, RelationGetRelationName(rel))));

    /* Prevent them from altering a system attribute */
    atttableform = (Form_pg_attribute)GETSTRUCT(tuple);
    attnum = atttableform->attnum;
    if (attnum <= 0)
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot alter system column \"%s\"", colName)));

    /* Initialize buffers for new tuple values */
    rc = memset_s(repl_val, sizeof(repl_val), 0, sizeof(repl_val));
    securec_check(rc, "\0", "\0");
    rc = memset_s(repl_null, sizeof(repl_null), false, sizeof(repl_null));
    securec_check(rc, "\0", "\0");
    rc = memset_s(repl_repl, sizeof(repl_repl), false, sizeof(repl_repl));
    securec_check(rc, "\0", "\0");

    /* Extract the current options */
    datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attfdwoptions, &isnull);
    if (isnull)
        datum = PointerGetDatum(NULL);

    /* Transform the options */
    datum = transformGenericOptions(AttributeRelationId, datum, options, fdw->fdwvalidator);

    if (PointerIsValid(DatumGetPointer(datum)))
        repl_val[Anum_pg_attribute_attfdwoptions - 1] = datum;
    else
        repl_null[Anum_pg_attribute_attfdwoptions - 1] = true;

    repl_repl[Anum_pg_attribute_attfdwoptions - 1] = true;

    /* Everything looks good - update the tuple */
    newtuple = (HeapTuple) tableam_tops_modify_tuple(tuple, RelationGetDescr(attrel), repl_val, repl_null, repl_repl);

    ObjectAddressSubSet(address, RelationRelationId,
                        RelationGetRelid(rel), attnum);

    ReleaseSysCache(tuple);

    simple_heap_update(attrel, &newtuple->t_self, newtuple);
    CatalogUpdateIndexes(attrel, newtuple);

    heap_close(attrel, RowExclusiveLock);

    tableam_tops_free_tuple(newtuple);

    return address;
}

// delete record about psort oid and index oid from pg_depend,
// cut off their dependency and make psort independent.
// *oldIndexPassList* depends on AT_PASS_OLD_INDEX list.
static void ATCutOffPSortDependency(List* oldIndexPassList, LOCKMODE lockmode)
{
    ListCell* lcell = NULL;
    long ndeleted = 0;
    long nupdated = 0;

    foreach (lcell, oldIndexPassList) {
        AlterTableCmd* atCmd = (AlterTableCmd*)lfirst(lcell);
        Assert(atCmd);
        if (atCmd->subtype != AT_ReAddIndex)
            continue;

        IndexStmt* istmt = (IndexStmt*)atCmd->def;
        Assert(istmt);

        // valid oldPSortOid implies that we want to reuse the
        // existing psort index data, so we have to cut off their
        // dependency and keep it from performing deleting.
        if (!OidIsValid(istmt->oldPSortOid))
            continue;

        long num = deleteDependencyRecordsForClass(RelationRelationId,  // class id
            istmt->oldPSortOid,                                         // depender oid
            RelationRelationId,                                         // referenece class id
            DEPENDENCY_INTERNAL);                                       // dependency type
        if (num != 1) {
            Assert(false);
            ereport(ERROR,
                (errcode(ERRCODE_OPERATE_RESULT_NOT_EXPECTED),
                    errmsg("PSort %u should depend on only one index relation but not %ld.", istmt->oldPSortOid, num)));
        }
        ++ndeleted;

        // if the index is a partition table, handle all the
        // psort info for each partition.
        if (istmt->isPartitioned && !istmt->isGlobal) {
            Relation hrel = relation_openrv(istmt->relation, lockmode);
            Relation irel = index_open(istmt->indexOid, lockmode);
            List* partHeapOids = relationGetPartitionOidList(hrel);

            ListCell* cell = NULL;
            Relation pg_part = heap_open(PartitionRelationId, RowExclusiveLock);
            foreach (cell, partHeapOids) {
                // step 1: find each part oid of this index tuple.
                Oid partHeapOid = lfirst_oid(cell);
                Oid partIdxOid = getPartitionIndexOid(istmt->indexOid, partHeapOid);

                // step 2: lookup the right index tuple according to part oid.
                HeapTuple partIdxTup = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(partIdxOid));
                if (!HeapTupleIsValid(partIdxTup)) {
                    ereport(ERROR,
                        (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                            errmsg("Partition cache lookup failed for index partition %u", partIdxOid)));
                }

                // step 3: reset psort oid to be 0.
                //  so psort index cannot be droppend later.
                //  refer to index_drop() and heapDropPartitionIndex().
                Form_pg_partition partForm = (Form_pg_partition)GETSTRUCT(partIdxTup);
                partForm->relcudescrelid = InvalidOid;

                // step 4: update pg_partition relation and its index info.
                simple_heap_update(pg_part, &partIdxTup->t_self, partIdxTup);
                CatalogUpdateIndexes(pg_part, partIdxTup);

                tableam_tops_free_tuple(partIdxTup);
                ++nupdated;
            }
            heap_close(pg_part, RowExclusiveLock);

            releasePartitionOidList(&partHeapOids);
            index_close(irel, NoLock);
            heap_close(hrel, NoLock);
        }
    }

    // Make deletion of dependency record visible
    if ((ndeleted + nupdated) > 0) {
        CommandCounterIncrement();
    }
}

/*
 * Cleanup after we've finished all the ALTER TYPE operations for a
 * particular relation.  We have to drop and recreate all the indexes
 * and constraints that depend on the altered columns.
 */
static void ATPostAlterTypeCleanup(List** wqueue, AlteredTableInfo* tab, LOCKMODE lockmode)
{
    ObjectAddress obj;
    ListCell* def_item = NULL;
    ListCell* oid_item = NULL;

    /*
     * Re-parse the index and constraint definitions, and attach them to the
     * appropriate work queue entries.	We do this before dropping because in
     * the case of a FOREIGN KEY constraint, we might not yet have exclusive
     * lock on the table the constraint is attached to, and we need to get
     * that before dropping.  It's safe because the parser won't actually look
     * at the catalogs to detect the existing entry.
     *
     * We can't rely on the output of deparsing to tell us which relation
     * to operate on, because concurrent activity might have made the name
     * resolve differently.  Instead, we've got to use the OID of the
     * constraint or index we're processing to figure out which relation
     * to operate on.
     */
    forboth(oid_item, tab->changedConstraintOids, def_item, tab->changedConstraintDefs)
    {
        Oid oldId = lfirst_oid(oid_item);
        Oid relid;
        Oid confrelid;

        get_constraint_relation_oids(oldId, &relid, &confrelid);
        ATPostAlterTypeParse(oldId, relid, confrelid, (char*)lfirst(def_item), wqueue, lockmode, tab->rewrite > 0);
    }
    forboth(oid_item, tab->changedIndexOids, def_item, tab->changedIndexDefs)
    {
        Oid oldId = lfirst_oid(oid_item);
        Oid relid;

        relid = IndexGetRelation(oldId, false);
        ATPostAlterTypeParse(oldId, relid, InvalidOid, (char*)lfirst(def_item), wqueue, lockmode, tab->rewrite > 0);
    }

    /*
     * Now we can drop the existing constraints and indexes --- constraints
     * first, since some of them might depend on the indexes.  In fact, we
     * have to delete FOREIGN KEY constraints before UNIQUE constraints, but
     * we already ordered the constraint list to ensure that would happen. It
     * should be okay to use DROP_RESTRICT here, since nothing else should be
     * depending on these objects.
     */
    foreach (oid_item, tab->changedConstraintOids) {
        obj.classId = ConstraintRelationId;
        obj.objectId = lfirst_oid(oid_item);
        obj.objectSubId = 0;
        performDeletion(&obj, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
    }

    if (tab->rewrite <= 0  && tab->subcmds[AT_PASS_OLD_INDEX]) {
        /*
         * existing indexes will be dropped. but rewrite is false, that
         * means existing indexes should be reserved and reused. so we
         * should remove the dependency between psort and its reference
         * index relation.
         */
        ATCutOffPSortDependency(tab->subcmds[AT_PASS_OLD_INDEX], lockmode);
    }

    foreach (oid_item, tab->changedIndexOids) {
        obj.classId = RelationRelationId;
        obj.objectId = lfirst_oid(oid_item);
        obj.objectSubId = 0;
        performDeletion(&obj, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
    }

    foreach (oid_item, tab->changedTriggerOids) {
        obj.classId = TriggerRelationId;
        obj.objectId = lfirst_oid(oid_item);
        obj.objectSubId = 0;
        performDeletion(&obj, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
    }

    /*
     * The objects will get recreated during subsequent passes over the work
     * queue.
     */
}

static void setPrimaryNotnull(Oid relid, IndexStmt *stmt, AlteredTableInfo* tab)
{
    if (stmt->primary && !stmt->internal_flag) {
        ListCell* columns = NULL;
        IndexElem* iparam = NULL;

        tab->is_modify_primary = true;
        foreach (columns, stmt->indexParams) {
            HeapTuple atttuple;
            Form_pg_attribute attform;

            iparam = (IndexElem*)lfirst(columns);

            atttuple = SearchSysCacheAttName(relid, iparam->name);
            if (!HeapTupleIsValid(atttuple)) {
                ereport(ERROR,
                    (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                        errmsg("cache lookup failed for attribute %s of relation %u", iparam->name, relid)));
            }
            attform = (Form_pg_attribute)GETSTRUCT(atttuple);

            if (!attform->attnotnull) {
                /* Add a subcommand to make this one NOT NULL */
                AlterTableCmd* cmd = makeNode(AlterTableCmd);

                cmd->subtype = AT_SetNotNull;
                cmd->name = pstrdup(NameStr(attform->attname));
                tab->subcmds[AT_PASS_ADD_CONSTR] =
                    lappend(tab->subcmds[AT_PASS_ADD_CONSTR], cmd);
            }

            ReleaseSysCache(atttuple);
        }
    }
}

/*
 * Attach each generated command to the proper place in the work queue.
 * Note this could result in creation of entirely new work-queue entries.
 *
 * Also note that we have to tweak the command subtypes, because it turns
 * out that re-creation of indexes and constraints has to act a bit
 * differently from initial creation.
 */
static void AttachEachCommandInQueue(
    Oid oldId, Oid refRelId, List** wqueue, bool rewrite, List* querytree_list, Relation rel)
{
    ListCell* list_item = NULL;
    foreach (list_item, querytree_list) {
        Node* stm = (Node*)lfirst(list_item);
        AlteredTableInfo* tab = NULL;

        switch (nodeTag(stm)) {
            case T_IndexStmt: {
                IndexStmt* stmt = (IndexStmt*)stm;
                SetPartionIndexType(stmt, rel, true);
                AlterTableCmd* newcmd = NULL;

                if (!rewrite) {
                    if (!stmt->isPartitioned || stmt->isGlobal) {
                        TryReuseIndex(oldId, stmt);
                    } else {
                        tryReusePartedIndex(oldId, stmt, rel);
                    }
                }

                tab = ATGetQueueEntry(wqueue, rel);
                newcmd = makeNode(AlterTableCmd);
                newcmd->subtype = AT_ReAddIndex;
                newcmd->def = (Node*)stmt;
                tab->subcmds[AT_PASS_OLD_INDEX] = lappend(tab->subcmds[AT_PASS_OLD_INDEX], newcmd);
                break;
            }
            case T_AlterTableStmt: {
                AlterTableStmt* stmt = (AlterTableStmt*)stm;
                ListCell* lcmd = NULL;

                tab = ATGetQueueEntry(wqueue, rel);
                foreach (lcmd, stmt->cmds) {
                    AlterTableCmd* cmd = (AlterTableCmd*)lfirst(lcmd);
                    Constraint* con = NULL;

                    switch (cmd->subtype) {
                        case AT_AddIndex:
                            Assert(IsA(cmd->def, IndexStmt));
                            SetPartionIndexType((IndexStmt*)cmd->def, rel, true);
                            if (!rewrite) {
                                if (RelationIsPartitioned(rel) && !((IndexStmt*)cmd->def)->isGlobal) {
                                    tryReusePartedIndex(get_constraint_index(oldId), (IndexStmt*)cmd->def, rel);
                                } else {
                                    TryReuseIndex(get_constraint_index(oldId), (IndexStmt*)cmd->def);
                                }
                            }

                            cmd->subtype = AT_ReAddIndex;
                            tab->subcmds[AT_PASS_OLD_INDEX] = lappend(tab->subcmds[AT_PASS_OLD_INDEX], cmd);

                            if (tab->is_first_after) {
                                setPrimaryNotnull(rel->rd_id, (IndexStmt*)cmd->def, tab);
                            }
                            break;
                        case AT_AddConstraint:
                            Assert(IsA(cmd->def, Constraint));
                            con = (Constraint*)cmd->def;
                            con->old_pktable_oid = refRelId;
                            /* rewriting neither side of a FK */
                            if (con->contype == CONSTR_FOREIGN && !rewrite && tab->rewrite == 0)
                                TryReuseForeignKey(oldId, con);
                            cmd->subtype = AT_ReAddConstraint;
                            tab->subcmds[AT_PASS_OLD_CONSTR] = lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
                            break;
                        default:
                            ereport(ERROR,
                                (errcode(ERRCODE_UNDEFINED_OBJECT),
                                    errmsg("unexpected statement type: %d", (int)cmd->subtype)));
                    }
                }
                break;
            }
            default: {
                ereport(ERROR,
                    (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                        errmsg("unexpected statement type: %d", (int)nodeTag(stm))));
            }
        }
    }
}

static void ATPostAlterTypeParse(
    Oid oldId, Oid oldRelId, Oid refRelId, const char* cmd_str, List** wqueue, LOCKMODE lockmode, bool rewrite)
{
    ListCell* list_item = NULL;
    List* raw_parsetree_list = raw_parser(cmd_str);
    List* querytree_list = NIL;
    Relation rel;

    /*
     * We expect that we will get only ALTER TABLE and CREATE INDEX
     * statements. Hence, there is no need to pass them through
     * parse_analyze() or the rewriter, but instead we need to pass them
     * through parse_utilcmd.c to make them ready for execution.
     */
    foreach (list_item, raw_parsetree_list) {
        Node* stmt = (Node*)lfirst(list_item);

        if (IsA(stmt, IndexStmt)) {
            querytree_list = lappend(querytree_list, transformIndexStmt(oldRelId, (IndexStmt*)stmt, cmd_str));
        } else if (IsA(stmt, AlterTableStmt)) {
            querytree_list =
                list_concat(querytree_list, transformAlterTableStmt(oldRelId, (AlterTableStmt*)stmt, cmd_str));
        } else {
            querytree_list = lappend(querytree_list, stmt);
        }
    }

    /* Caller should already have acquired whatever lock we need. */
    rel = relation_open(oldRelId, NoLock);

    AttachEachCommandInQueue(oldId, refRelId, wqueue, rewrite, querytree_list, rel);

    relation_close(rel, NoLock);
}

/*
 * Subroutine for ATPostAlterTypeParse().  Calls out to CheckIndexCompatible()
 * for the real analysis, then mutates the IndexStmt based on that verdict.
 * Global temporary table cannot reuse relfilenode because the relfilenode mapping is maintained in memory.
 */
void TryReuseIndex(Oid oldId, IndexStmt* stmt)
{
    if (CheckIndexCompatible(oldId, stmt->accessMethod, stmt->indexParams, stmt->excludeOpNames) &&
        !IsGlobalTempTable(oldId)) {
        Relation irel = index_open(oldId, NoLock);

        /*
         * try to reuse existing index storage. for row relation, only need *oldNode*.
         *for column relation, also remember the oid of psort index.
         */
        stmt->oldNode = irel->rd_node.relNode;
        stmt->oldPSortOid = irel->rd_rel->relcudescrelid;
        stmt->indexOid = oldId;
        index_close(irel, NoLock);
    }
}

/*
 * @@GaussDB@@
 * Target       : data partition
 * Brief        : Subroutine for ATPostAlterTypeParse().  Calls out to CheckIndexCompatible()
 *                for the real analysis, then mutates the IndexStmt based on that verdict.
 * Description  :
 * Notes        :
 * Input        :
 * Output       :
 */
void tryReusePartedIndex(Oid oldId, IndexStmt* stmt, Relation rel)
{
    List* partOids = NIL;
    ListCell* cell = NULL;
    Oid partOid = InvalidOid;
    Oid partIndexOid = InvalidOid;
    Partition partition = NULL;

    if (CheckIndexCompatible(oldId, stmt->accessMethod, stmt->indexParams, stmt->excludeOpNames)) {
        Relation irel = index_open(oldId, NoLock);

        /*
         * try to reuse existing index storage. for row relation, only need 'oldNode'.
         * for column relation, also remember the oid of psort index. index partition
         * relation oid is needed for each partition.
         */
        stmt->oldNode = irel->rd_node.relNode;
        stmt->oldPSortOid = irel->rd_rel->relcudescrelid;
        stmt->indexOid = oldId;

        partOids = relationGetPartitionOidList(rel);
        foreach (cell, partOids) {
            partOid = lfirst_oid(cell);
            partIndexOid = getPartitionIndexOid(oldId, partOid);

            partition = partitionOpen(irel, partIndexOid, NoLock);
            /* remember the old relfilenodes and psort oids. */
            stmt->partIndexOldNodes = lappend_oid(stmt->partIndexOldNodes, partition->pd_part->relfilenode);
            stmt->partIndexOldPSortOid = lappend_oid(stmt->partIndexOldPSortOid, partition->pd_part->relcudescrelid);
            partitionClose(irel, partition, NoLock);
        }
        releasePartitionOidList(&partOids);

        index_close(irel, NoLock);
    }
}

/*
 * Subroutine for ATPostAlterTypeParse().
 *
 * Stash the old P-F equality operator into the Constraint node, for possible
 * use by ATAddForeignKeyConstraint() in determining whether revalidation of
 * this constraint can be skipped.
 */
static void TryReuseForeignKey(Oid oldId, Constraint* con)
{
    HeapTuple tup;
    Datum adatum;
    bool isNull = false;
    ArrayType* arr = NULL;
    Oid* rawarr = NULL;
    int numkeys;
    int i;

    Assert(con->contype == CONSTR_FOREIGN);
    Assert(con->old_conpfeqop == NIL); /* already prepared this node */

    tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(oldId));
    if (!HeapTupleIsValid(tup)) {
        /* should not happen */
        ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for constraint %u", oldId)));
    }

    adatum = SysCacheGetAttr(CONSTROID, tup, Anum_pg_constraint_conpfeqop, &isNull);
    if (isNull)
        ereport(ERROR, (errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("null conpfeqop for constraint %u", oldId)));
    arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
    numkeys = ARR_DIMS(arr)[0];
    /* test follows the one in ri_FetchConstraintInfo() */
    if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != OIDOID)
        ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("conpfeqop is not a 1-D Oid array")));
    rawarr = (Oid*)ARR_DATA_PTR(arr);

    /* stash a List of the operator Oids in our Constraint node */
    for (i = 0; i < numkeys; i++)
        con->old_conpfeqop = lcons_oid(rawarr[i], con->old_conpfeqop);

    ReleaseSysCache(tup);
}

static void AlterTypeOwnerByTbl(Relation target_rel, Oid newOwnerId)
{
    TupleDesc tupDesc = target_rel->rd_att;
    char* typnamespace = NULL;
    char* typname = NULL;
    HeapTuple tuple;
    Form_pg_type typeForm;

    for (int i = 0; i < tupDesc->natts; i++) {
        if (!tupDesc->attrs[i].attisdropped && type_is_set(tupDesc->attrs[i].atttypid)) {
            Oid atttypid = tupDesc->attrs[i].atttypid;

            tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(atttypid));
            if (!HeapTupleIsValid(tuple)) {
                ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                    errmsg("cache lookup failed for type %u", atttypid)));
            }
            typeForm = (Form_pg_type)GETSTRUCT(tuple);
            typnamespace = pstrdup(get_namespace_name(typeForm->typnamespace));
            typname = NameStr(typeForm->typname);

            List *typlist = list_make2(makeString(typnamespace), makeString(typname));
            AlterTypeOwner(typlist, newOwnerId, OBJECT_TYPE, false);
            ReleaseSysCache(tuple);
        }
    }
}

/*
 * ALTER TABLE OWNER
 *
 * recursing is true if we are recursing from a table to its indexes,
 * sequences, or toast table.  We don't allow the ownership of those things to
 * be changed separately from the parent table.  Also, we can skip permission
 * checks (this is necessary not just an optimization, else we'd fail to
 * handle toast tables properly).
 *
 * recursing is also true if ALTER TYPE OWNER is calling us to fix up a
 * free-standing composite type.
 */
void ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lockmode)
{
    Relation target_rel;
    Relation class_rel;
    HeapTuple tuple;
    Form_pg_class tuple_class;

    /*
     * Get exclusive lock till end of transaction on the target table. Use
     * relation_open so that we can work on indexes and sequences.
     */
    target_rel = relation_open(relationOid, lockmode);

    /* Get its pg_class tuple, too */
    class_rel = heap_open(RelationRelationId, RowExclusiveLock);

    tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relationOid));
    if (!HeapTupleIsValid(tuple))
        ereport(
            ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", relationOid)));
    tuple_class = (Form_pg_class)GETSTRUCT(tuple);

    /* Can we change the ownership of this tuple? */
    switch (tuple_class->relkind) {
        case RELKIND_RELATION:
        case RELKIND_VIEW:
        case RELKIND_CONTQUERY:
        case RELKIND_MATVIEW:
        case RELKIND_FOREIGN_TABLE:
        case RELKIND_STREAM:
            /* ok to change owner */
            if (IS_PGXC_COORDINATOR && in_logic_cluster()) {
                Oid new_group_oid, group_oid;
                bool is_admin = systemDBA_arg(newOwnerId) || superuser_arg(newOwnerId);

                /* check whether the group_oid of the two owners are same.
                 *  Only allow changing owner to other group's role in redistribution.
                 * Or allow changing owner to sysadmin users.
                 */
                group_oid = get_pgxc_logic_groupoid(tuple_class->relowner);
                if (tuple_class->relkind != RELKIND_CONTQUERY && tuple_class->relkind != RELKIND_VIEW 
                    && !OidIsValid(group_oid) && is_pgxc_class_table(relationOid)) {
                    group_oid = get_pgxc_class_groupoid(relationOid);
                }

                new_group_oid = get_pgxc_logic_groupoid(newOwnerId);

                if (group_oid != new_group_oid && OidIsValid(group_oid) && !is_admin) {
                    char in_redis_new = 'n';
                    char in_redis = get_pgxc_group_redistributionstatus(group_oid);
                    if (OidIsValid(new_group_oid)) {
                        in_redis_new = get_pgxc_group_redistributionstatus(new_group_oid);
                    }
                    if (in_redis != 'y' && in_redis != 't' && in_redis_new != 'y' && in_redis_new != 't') {
                        /* Old group and new group are not redistribution group */
                        ereport(ERROR,
                            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                                errmsg("Can not change owner of \"%s\" to other logic cluster users.",
                                    NameStr(tuple_class->relname))));
                    }
                }
            }
            break;
        case RELKIND_INDEX:
        case RELKIND_GLOBAL_INDEX:
            if (!recursing) {
                /*
                 * Because ALTER INDEX OWNER used to be allowed, and in fact
                 * is generated by old versions of pg_dump, we give a warning
                 * and do nothing rather than erroring out.  Also, to avoid
                 * unnecessary chatter while restoring those old dumps, say
                 * nothing at all if the command would be a no-op anyway.
                 */
                if (tuple_class->relowner != newOwnerId)
                    ereport(WARNING,
                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                            errmsg("cannot change owner of index \"%s\"", NameStr(tuple_class->relname)),
                            errhint("Change the ownership of the index's table, instead.")));
                /* quick hack to exit via the no-op path */
                newOwnerId = tuple_class->relowner;
            }
            break;
        case RELKIND_SEQUENCE:
        case RELKIND_LARGE_SEQUENCE:
            if (!recursing && tuple_class->relowner != newOwnerId) {
                /* if it's an owned sequence, disallow changing it by itself */
                Oid tableId;
                int32 colId;

                if (sequenceIsOwned(relationOid, &tableId, &colId))
                    ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                            errmsg("cannot change owner of (large) sequence \"%s\"", NameStr(tuple_class->relname)),
                            errdetail("Sequence \"%s\" is linked to table \"%s\".",
                                NameStr(tuple_class->relname),
                                get_rel_name(tableId))));
            }
            break;
        case RELKIND_COMPOSITE_TYPE:
            if (recursing)
                break;
            ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                    errmsg("\"%s\" is a composite type", NameStr(tuple_class->relname)),
                    errhint("Use ALTER TYPE instead.")));
            break;
        case RELKIND_TOASTVALUE:
            if (recursing)
                break;
            /* fall through */
        default:
            ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                    errmsg("\"%s\" is not a table, view, sequence, or foreign table", NameStr(tuple_class->relname))));
    }

    /*
     * If the new owner is the same as the existing owner, consider the
     * command to have succeeded.  This is for dump restoration purposes.
     */
    if (tuple_class->relowner != newOwnerId) {
        Datum repl_val[Natts_pg_class];
        bool repl_null[Natts_pg_class];
        bool repl_repl[Natts_pg_class];
        Acl* newAcl = NULL;
        Datum aclDatum;
        bool isNull = false;
        HeapTuple newtuple;
        errno_t rc = EOK;

        /* skip permission checks when recursing to index or toast table */
        if (!recursing) {
            /* Superusers can always do it, except manipulate independent role's objects. */
            if ((!superuser() && !(isOperatoradmin(GetUserId()) && u_sess->attr.attr_security.operation_mode)) ||
                is_role_independent(tuple_class->relowner)) {
                Oid namespaceOid = tuple_class->relnamespace;
                AclResult aclresult;

                /* Otherwise, must be owner or has privileges of the existing object's owner. */
                if (!has_privs_of_role(GetUserId(), tuple_class->relowner))
                    aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(target_rel));

                /* Must be able to become new owner */
                check_is_member_of_role(GetUserId(), newOwnerId);

                /* New owner must have CREATE privilege on namespace */
                aclresult = pg_namespace_aclcheck(namespaceOid, newOwnerId, ACL_CREATE);
                if (aclresult != ACLCHECK_OK)
                    aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid));
            }

            /* check whether newOwner has enough space on CN, or translation tableSize from oldOwner to newOwner on DN*/
            if (g_instance.attr.attr_resource.enable_perm_space && !IS_SINGLE_NODE) {
                UserData* newUserdata = GetUserDataFromHTab(newOwnerId, false);

                FunctionCallInfo fcinfo =
                    (FunctionCallInfo)palloc0(sizeof(FunctionCallInfoData) + sizeof(bool) + sizeof(Datum));

                fcinfo->nargs = 1;
                fcinfo->argnull = (bool*)((char*)fcinfo + sizeof(FunctionCallInfoData));
                fcinfo->argnull[0] = false;
                fcinfo->arg = (Datum*)((char*)fcinfo->argnull + sizeof(bool));
                fcinfo->arg[0] = UInt32GetDatum(relationOid);

                int64 tableSize = DatumGetInt64(pg_total_relation_size(fcinfo));

                pfree_ext(fcinfo);

                DataSpaceType type = RelationUsesSpaceType(target_rel->rd_rel->relpersistence);

                if (IS_PGXC_COORDINATOR) {
                    if (type == SP_PERM) {
                        if (newUserdata->spacelimit > 0 &&
                            (newUserdata->totalspace + tableSize > newUserdata->spacelimit)) {
                            ereport(ERROR,
                                (errcode(ERRCODE_INSUFFICIENT_RESOURCES),
                                    errmsg("no perm space is available for the targeted owner")));
                        }
                        if (newUserdata->parent && newUserdata->parent->spacelimit > 0 &&
                            (newUserdata->parent->totalspace + tableSize > newUserdata->parent->spacelimit)) {
                            ereport(ERROR,
                                (errcode(ERRCODE_INSUFFICIENT_RESOURCES),
                                    errmsg("no perm space is available for the targeted user group")));
                        }
                    } else {
                        if (newUserdata->tmpSpaceLimit > 0 &&
                            (newUserdata->tmpSpace + tableSize > newUserdata->tmpSpaceLimit)) {
                            ereport(ERROR,
                                (errcode(ERRCODE_INSUFFICIENT_RESOURCES),
                                    errmsg("no temp space is available for the targeted owner")));
                        }
                        if (newUserdata->parent && newUserdata->parent->tmpSpaceLimit > 0 &&
                            (newUserdata->parent->tmpSpace + tableSize > newUserdata->parent->tmpSpaceLimit)) {
                            ereport(ERROR,
                                (errcode(ERRCODE_INSUFFICIENT_RESOURCES),
                                    errmsg("no temp space is available for the targeted user group")));
                        }
                    }
                }

                if (IS_PGXC_DATANODE) {
                    if (newOwnerId != BOOTSTRAP_SUPERUSERID) {
                        perm_space_increase(newOwnerId, tableSize, type);
                    }
                    perm_space_decrease(tuple_class->relowner, tableSize, type);
                }
            }
        }

        rc = memset_s(repl_null, sizeof(repl_null), false, sizeof(repl_null));
        securec_check(rc, "\0", "\0");
        rc = memset_s(repl_repl, sizeof(repl_repl), false, sizeof(repl_repl));
        securec_check(rc, "\0", "\0");

        repl_repl[Anum_pg_class_relowner - 1] = true;
        repl_val[Anum_pg_class_relowner - 1] = ObjectIdGetDatum(newOwnerId);

        /*
         * Determine the modified ACL for the new owner.  This is only
         * necessary when the ACL is non-null.
         */
        aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl, &isNull);
        if (!isNull) {
            newAcl = aclnewowner(DatumGetAclP(aclDatum), tuple_class->relowner, newOwnerId);
            repl_repl[Anum_pg_class_relacl - 1] = true;
            repl_val[Anum_pg_class_relacl - 1] = PointerGetDatum(newAcl);
        }

        newtuple = (HeapTuple) tableam_tops_modify_tuple(tuple, RelationGetDescr(class_rel), repl_val, repl_null, repl_repl);

        simple_heap_update(class_rel, &newtuple->t_self, newtuple);
        CatalogUpdateIndexes(class_rel, newtuple);

        tableam_tops_free_tuple(newtuple);

        /*
         * We must similarly update any per-column ACLs to reflect the new
         * owner; for neatness reasons that's split out as a subroutine.
         */
        change_owner_fix_column_acls(relationOid, tuple_class->relowner, newOwnerId);

        /*
         * Update owner dependency reference, if any.  A composite type has
         * none, because it's tracked for the pg_type entry instead of here;
         * indexes and TOAST tables don't have their own entries either.
         * the table in CSTORE_NAMESPACE such like pg_cudesc_xxx, pg_delta_xxx
         * don't have their own entries either.
         */
        if (tuple_class->relkind != RELKIND_COMPOSITE_TYPE && tuple_class->relkind != RELKIND_INDEX &&
            tuple_class->relkind != RELKIND_GLOBAL_INDEX && tuple_class->relkind != RELKIND_TOASTVALUE &&
            tuple_class->relnamespace != CSTORE_NAMESPACE)
            changeDependencyOnOwner(RelationRelationId, relationOid, newOwnerId);

        /*
         * Also change the ownership of the table's row type, if it has one
         */
        if (tuple_class->relkind != RELKIND_INDEX && tuple_class->relkind != RELKIND_GLOBAL_INDEX)
            AlterTypeOwnerInternal(tuple_class->reltype, newOwnerId, tuple_class->relkind == RELKIND_COMPOSITE_TYPE);

        /*
         * If we are operating on a table, also change the ownership of any
         * indexes and sequences that belong to the table, as well as the
         * table's toast table (if it has one)
         */
        if (tuple_class->relkind == RELKIND_RELATION ||
            tuple_class->relkind == RELKIND_MATVIEW ||
            tuple_class->relkind == RELKIND_TOASTVALUE) {
            List* index_oid_list = NIL;
            ListCell* i = NULL;

            /* Find all the indexes belonging to this relation */
            index_oid_list = RelationGetIndexList(target_rel);

            /* For each index, recursively change its ownership */
            foreach (i, index_oid_list)
                ATExecChangeOwner(lfirst_oid(i), newOwnerId, true, lockmode);

            list_free_ext(index_oid_list);
        }

        if (tuple_class->relkind == RELKIND_RELATION ||
            tuple_class->relkind == RELKIND_MATVIEW) {
            /* If it has a toast table, recurse to change its ownership */
            if (tuple_class->reltoastrelid != InvalidOid)
                ATExecChangeOwner(tuple_class->reltoastrelid, newOwnerId, true, lockmode);

            /* If it has a map table, recurse to change its ownership */
            if (tuple_class->relkind == RELKIND_MATVIEW) {
                char matviewmap[NAMEDATALEN];
                errno_t rc = snprintf_s(matviewmap, sizeof(matviewmap), sizeof(matviewmap) - 1,
                                        "matviewmap_%u", relationOid);
                securec_check_ss(rc, "\0", "\0");

                Oid mapOid = RelnameGetRelid(matviewmap);
                if (mapOid != InvalidOid) {
                    ATExecChangeOwner(mapOid, newOwnerId, true, lockmode);
                }
            }

            /* If it has a mlog table, recurse to change its ownership */
            if (target_rel->rd_mlogoid != InvalidOid) {
                char mlog[NAMEDATALEN];
                errno_t rc = snprintf_s(mlog, sizeof(mlog), sizeof(mlog) - 1, "mlog_%u", relationOid);
                securec_check_ss(rc, "\0", "\0");

                Oid mlogOid = RelnameGetRelid(mlog);
                if (mlogOid != InvalidOid) {
                    ATExecChangeOwner(mlogOid, newOwnerId, true, lockmode);
                }
            }

            /* If it has a cudesc table, recurse to change its ownership */
            if (tuple_class->relcudescrelid != InvalidOid)
                /*
                 * for timeseries cudesc table, its relcudescrelid is its partition oid,
                 * do not need to change partition's owner
                 * for column store table, the cudescrelid is the only one cudesc row table oid
                 */
                if (!isPartitionObject(tuple_class->relcudescrelid, PART_OBJ_TYPE_TABLE_PARTITION, true)) {
                    ATExecChangeOwner(tuple_class->relcudescrelid, newOwnerId, true, lockmode);
                }

            /* If it has a delta table, recurse to change its ownership */
            if (tuple_class->reldeltarelid != InvalidOid)
                ATExecChangeOwner(tuple_class->reldeltarelid, newOwnerId, true, lockmode);
            /* the timeseries table should recurse to change its tag table */
#ifdef ENABLE_MULTIPLE_NODES
            /* if with reloption period or ttl, will need to update job owner */
            update_rel_job_owner(target_rel, newOwnerId, lockmode);
            if (unlikely(RelationIsTsStore(target_rel))) {
                Oid tag_relid = get_tag_relid(RelationGetRelationName(target_rel),
                                              target_rel->rd_rel->relnamespace);
                ATExecChangeOwner(tag_relid, newOwnerId, true, lockmode);
            }
#endif
            /* If it has dependent sequences, recurse to change them too */
            change_owner_recurse_to_sequences(relationOid, newOwnerId, lockmode);
        }

        if ((tuple_class->relkind == RELKIND_INDEX || tuple_class->relkind == RELKIND_GLOBAL_INDEX) &&
            tuple_class->relam == PSORT_AM_OID) {
            /* if it is PSORT index,  recurse to change PSORT releateion's ownership */
            if (tuple_class->relcudescrelid != InvalidOid)
                ATExecChangeOwner(tuple_class->relcudescrelid, newOwnerId, true, lockmode);
        }

        if (tuple_class->relkind == RELKIND_FOREIGN_TABLE || tuple_class->relkind == RELKIND_STREAM) {
            // if it is a foreign talbe or a stream, should change its errortable's ownership
            DefElem* def = GetForeignTableOptionByName(relationOid, optErrorRel);

            if (def != NULL) {
                char* relname = strVal(def->arg);
                Oid errorOid = get_relname_relid(relname, get_rel_namespace(relationOid));
                ATExecChangeOwner(errorOid, newOwnerId, true, lockmode);
            }
        }

        /* also need change the owner of type if the attribute is set type */
        if (tuple_class->relkind == RELKIND_RELATION) {
            AlterTypeOwnerByTbl(target_rel, newOwnerId);
        }

        if (tuple_class->parttype == PARTTYPE_PARTITIONED_RELATION) {
            List* partCacheList = NIL;
            ListCell* cell = NULL;
            HeapTuple partTuple = NULL;
            Form_pg_partition partForm = NULL;

            if (tuple_class->relkind == RELKIND_RELATION) {
                partCacheList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_PARTITION, relationOid);
            } else if (tuple_class->relkind == RELKIND_INDEX) {
                partCacheList = searchPgPartitionByParentId(PART_OBJ_TYPE_INDEX_PARTITION, relationOid);
            } else if (tuple_class->relkind == RELKIND_GLOBAL_INDEX) {
                /* If it has a toast table, recurse to change its ownership */
                if (tuple_class->reltoastrelid != InvalidOid)
                    ATExecChangeOwner(tuple_class->reltoastrelid, newOwnerId, true, lockmode);

                /* If it has a cudesc table, recurse to change its ownership */
                if (tuple_class->relcudescrelid != InvalidOid)
                    ATExecChangeOwner(tuple_class->relcudescrelid, newOwnerId, true, lockmode);

                /* If it has a delta table, recurse to change its ownership */
                if (tuple_class->reldeltarelid != InvalidOid)
                    ATExecChangeOwner(tuple_class->reldeltarelid, newOwnerId, true, lockmode);
                partCacheList = NIL;
            } else {
                partCacheList = NIL;
            }

            if (partCacheList != NIL) {
                foreach (cell, partCacheList) {
                    partTuple = (HeapTuple)lfirst(cell);

                    if (PointerIsValid(partTuple)) {
                        partForm = (Form_pg_partition)GETSTRUCT(partTuple);
                        /* If it has a toast table, recurse to change its ownership */
                        if (partForm->reltoastrelid != InvalidOid)
                            ATExecChangeOwner(partForm->reltoastrelid, newOwnerId, true, lockmode);

                        /* If it has a cudesc table, recurse to change its ownership */
                        if (partForm->relcudescrelid != InvalidOid)
                            ATExecChangeOwner(partForm->relcudescrelid, newOwnerId, true, lockmode);

                        /* If it has a delta table, recurse to change its ownership */
                        if (partForm->reldeltarelid != InvalidOid)
                            ATExecChangeOwner(partForm->reldeltarelid, newOwnerId, true, lockmode);
                        /* change the cudesc rel owner */
#ifdef ENABLE_MULTIPLE_NODES
                        if (RelationIsTsStore(target_rel)) {
                            Oid part_oid = HeapTupleGetOid(partTuple);
                            List* cudesclist = search_cudesc(part_oid, true);
                            ListCell* lc;
                            foreach(lc, cudesclist) {
                                ATExecChangeOwner(lfirst_oid(lc), newOwnerId, true, lockmode);
                            }
                            list_free_ext(cudesclist);
                        }
#endif
                    }
                }
                freePartList(partCacheList);
            }
        }
    }

    ReleaseSysCache(tuple);
    heap_close(class_rel, RowExclusiveLock);
    relation_close(target_rel, NoLock);
}

/*
 * change_owner_fix_column_acls
 *
 * Helper function for ATExecChangeOwner.  Scan the columns of the table
 * and fix any non-null column ACLs to reflect the new owner.
 */
static void change_owner_fix_column_acls(Oid relationOid, Oid oldOwnerId, Oid newOwnerId)
{
    Relation attRelation;
    SysScanDesc scan;
    ScanKeyData key[1];
    HeapTuple attributeTuple;

    attRelation = heap_open(AttributeRelationId, RowExclusiveLock);
    ScanKeyInit(&key[0], Anum_pg_attribute_attrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relationOid));
    scan = systable_beginscan(attRelation, AttributeRelidNumIndexId, true, NULL, 1, key);
    while (HeapTupleIsValid(attributeTuple = systable_getnext(scan))) {
        Form_pg_attribute att = (Form_pg_attribute)GETSTRUCT(attributeTuple);
        Datum repl_val[Natts_pg_attribute];
        bool repl_null[Natts_pg_attribute];
        bool repl_repl[Natts_pg_attribute];
        Acl* newAcl = NULL;
        Datum aclDatum;
        bool isNull = false;
        HeapTuple newtuple;
        errno_t rc = EOK;

        /* Ignore dropped columns */
        if (att->attisdropped)
            continue;

        aclDatum = heap_getattr(attributeTuple, Anum_pg_attribute_attacl, RelationGetDescr(attRelation), &isNull);
        /* Null ACLs do not require changes */
        if (isNull)
            continue;

        rc = memset_s(repl_null, sizeof(repl_null), false, sizeof(repl_null));
        securec_check(rc, "\0", "\0");
        rc = memset_s(repl_repl, sizeof(repl_repl), false, sizeof(repl_repl));
        securec_check(rc, "\0", "\0");

        newAcl = aclnewowner(DatumGetAclP(aclDatum), oldOwnerId, newOwnerId);
        repl_repl[Anum_pg_attribute_attacl - 1] = true;
        repl_val[Anum_pg_attribute_attacl - 1] = PointerGetDatum(newAcl);

        newtuple = (HeapTuple) tableam_tops_modify_tuple(attributeTuple, RelationGetDescr(attRelation), repl_val, repl_null, repl_repl);

        simple_heap_update(attRelation, &newtuple->t_self, newtuple);
        CatalogUpdateIndexes(attRelation, newtuple);

        tableam_tops_free_tuple(newtuple);
    }
    systable_endscan(scan);
    heap_close(attRelation, RowExclusiveLock);
}

/*
 * change_owner_recurse_to_sequences
 *
 * Helper function for ATExecChangeOwner.  Examines pg_depend searching
 * for sequences that are dependent on serial columns, and changes their
 * ownership.
 */
static void change_owner_recurse_to_sequences(Oid relationOid, Oid newOwnerId, LOCKMODE lockmode)
{
    Relation depRel;
    SysScanDesc scan;
    ScanKeyData key[2];
    HeapTuple tup;

    /*
     * SERIAL sequences are those having an auto dependency on one of the
     * table's columns (we don't care *which* column, exactly).
     */
    depRel = heap_open(DependRelationId, AccessShareLock);

    ScanKeyInit(
        &key[0], Anum_pg_depend_refclassid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationRelationId));
    ScanKeyInit(&key[1], Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relationOid));
    /* we leave refobjsubid unspecified */
    scan = systable_beginscan(depRel, DependReferenceIndexId, true, NULL, 2, key);

    while (HeapTupleIsValid(tup = systable_getnext(scan))) {
        Form_pg_depend depForm = (Form_pg_depend)GETSTRUCT(tup);
        Relation seqRel;

        /* skip dependencies other than auto dependencies on columns */
        if (depForm->refobjsubid == 0 || depForm->classid != RelationRelationId || depForm->objsubid != 0 ||
            depForm->deptype != DEPENDENCY_AUTO)
            continue;

        /* Use relation_open just in case it's an index */
        seqRel = relation_open(depForm->objid, lockmode);

        /* skip non-sequence relations */
        if (!RELKIND_IS_SEQUENCE(RelationGetForm(seqRel)->relkind)) {
            /* No need to keep the lock */
            relation_close(seqRel, lockmode);
            continue;
        }

        /* We don't need to close the sequence while we alter it. */
        ATExecChangeOwner(depForm->objid, newOwnerId, true, lockmode);

        /* Now we can close it.  Keep the lock till end of transaction. */
        relation_close(seqRel, NoLock);
    }

    systable_endscan(scan);

    relation_close(depRel, AccessShareLock);
}

/*
 * ALTER TABLE CLUSTER ON
 *
 * The only thing we have to do is to change the indisclustered bits.
 * Return the address of the new clustering index.
 */
static ObjectAddress ATExecClusterOn(Relation rel, const char* indexName, LOCKMODE lockmode)
{
    Oid indexOid;
    ObjectAddress address;

    indexOid = get_relname_relid(indexName, rel->rd_rel->relnamespace);

    if (!OidIsValid(indexOid))
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_OBJECT),
                errmsg("index \"%s\" for table \"%s\" does not exist", indexName, RelationGetRelationName(rel))));

    /* Check index is valid to cluster on */
    check_index_is_clusterable(rel, indexOid, false, lockmode);

    /* And do the work */
    mark_index_clustered(rel, indexOid);
    ObjectAddressSet(address,
                     RelationRelationId, indexOid);

    return address;
}

/*
 * ALTER TABLE SET WITHOUT CLUSTER
 *
 * We have to find any indexes on the table that have indisclustered bit
 * set and turn it off.
 */
static void ATExecDropCluster(Relation rel, LOCKMODE lockmode)
{
    mark_index_clustered(rel, InvalidOid);
}

/*
 * ALTER TABLE SET TABLESPACE
 */
static void ATPrepSetTableSpace(AlteredTableInfo* tab, Relation rel, const char* tablespacename, LOCKMODE lockmode)
{
    Oid tablespaceId;
    AclResult aclresult;

    /* Check that the tablespace exists */
    tablespaceId = get_tablespace_oid(tablespacename, false);

    /* Check its permissions */
    aclresult = pg_tablespace_aclcheck(tablespaceId, GetUserId(), ACL_CREATE);
    if (aclresult != ACLCHECK_OK)
        aclcheck_error(aclresult, ACL_KIND_TABLESPACE, tablespacename);

    /* Save info for Phase 3 to do the real work */
    if (OidIsValid(tab->newTableSpace))
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION), errmsg("cannot have multiple SET TABLESPACE subcommands")));
    tab->newTableSpace = tablespaceId;
}

// psort tuple is fetched from pg_class either normal or partition relation.
// for normal relation, the tuple oid is stored in FormData_pg_class.relcudescrelid,
// while partition relation, tuple oid is stored in Form_pg_partition.relcudescrelid.
//
static void ATExecSetPSortOption(Oid psortOid, List* defList, AlterTableType operation, LOCKMODE lockmode)
{
    HeapTuple newtuple;
    Datum datum;
    bool isnull = false;
    Datum newOptions;
    Datum repl_val[Natts_pg_class];
    bool repl_null[Natts_pg_class];
    bool repl_repl[Natts_pg_class];

    Assert(InvalidOid != psortOid);
    Relation pgclass = heap_open(RelationRelationId, RowExclusiveLock);
    Relation psortRel = heap_open(psortOid, lockmode);

    HeapTuple psortTup = SearchSysCache1(RELOID, ObjectIdGetDatum(psortOid));
    if (!HeapTupleIsValid(psortTup)) {
        ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", psortOid)));
    }

    // check that it's a Column-Store heap relation.
    if (!RelationIsColStore(psortRel)) {
        if (RELKIND_RELATION != psortRel->rd_rel->relkind) {
            ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                    errmsg("relkind of psort tuple shouldn't be '%c'.", psortRel->rd_rel->relkind),
                    errdetail("psort tuple's relkind must be '%c'.", RELKIND_RELATION)));
        } else {
            ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                    errmsg("psort tuple doesn't have the correct ORIENTATION value."),
                    errdetail("ORIENTATION value within psort tuple must be COLUMN.")));
        }
    }

    ForbidUserToSetDefinedOptions(defList);
    ForbidToSetOptionsForPSort(defList);

    if (operation == AT_ReplaceRelOptions) {
        datum = (Datum)0;
        isnull = true;
    } else {
        datum = SysCacheGetAttr(RELOID, psortTup, Anum_pg_class_reloptions, &isnull);
    }

    newOptions =
        transformRelOptions(isnull ? (Datum)0 : datum, defList, NULL, NULL, false, operation == AT_ResetRelOptions);

    bytea* heapRelOpt = heap_reloptions(RELKIND_RELATION, newOptions, true);
    Assert(heapRelOpt != NULL);
    CheckCStoreRelOption((StdRdOptions*)heapRelOpt);

    errno_t rc = 0;
    rc = memset_s(repl_val, sizeof(repl_val), 0, sizeof(repl_val));
    securec_check_c(rc, "\0", "\0");
    rc = memset_s(repl_null, sizeof(repl_null), false, sizeof(repl_null));
    securec_check_c(rc, "\0", "\0");
    rc = memset_s(repl_repl, sizeof(repl_repl), false, sizeof(repl_repl));
    securec_check_c(rc, "\0", "\0");

    if (newOptions != (Datum)0)
        repl_val[Anum_pg_class_reloptions - 1] = newOptions;
    else
        repl_null[Anum_pg_class_reloptions - 1] = true;
    repl_repl[Anum_pg_class_reloptions - 1] = true;

    newtuple = (HeapTuple) tableam_tops_modify_tuple(psortTup, RelationGetDescr(pgclass), repl_val, repl_null, repl_repl);
    simple_heap_update(pgclass, &newtuple->t_self, newtuple);
    CatalogUpdateIndexes(pgclass, newtuple);
    tableam_tops_free_tuple(newtuple);

    ReleaseSysCache(psortTup);

    heap_close(psortRel, NoLock);
    heap_close(pgclass, RowExclusiveLock);
}

static void ATExecSetRelOptionsToast(Oid toastid, List* defList, AlterTableType operation, LOCKMODE lockmode)
{
    Relation pgclass;
    HeapTuple tuple;
    HeapTuple newtuple;
    Datum datum;
    bool isnull = false;
    Datum newOptions;
    Datum repl_val[Natts_pg_class];
    bool repl_null[Natts_pg_class];
    bool repl_repl[Natts_pg_class];
    static const char* const validnsps[] = HEAP_RELOPT_NAMESPACES;
    errno_t rc = EOK;

    Relation toastrel;

    pgclass = heap_open(RelationRelationId, RowExclusiveLock);

    toastrel = heap_open(toastid, lockmode);

    /* Fetch heap tuple */
    tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(toastid));
    if (!HeapTupleIsValid(tuple)) {
        ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", toastid)));
    }

    if (operation == AT_ReplaceRelOptions) {
        /*
         * If we're supposed to replace the reloptions list, we just
         * pretend there were none before.
         */
        datum = (Datum)0;
        isnull = true;
    } else {
        /* Get the old reloptions */
        datum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, &isnull);
    }

    newOptions = transformRelOptions(
        isnull ? (Datum)0 : datum, defList, "toast", validnsps, false, operation == AT_ResetRelOptions);

    (void)heap_reloptions(RELKIND_TOASTVALUE, newOptions, true);

    rc = memset_s(repl_val, sizeof(repl_val), 0, sizeof(repl_val));
    securec_check(rc, "\0", "\0");
    rc = memset_s(repl_null, sizeof(repl_null), false, sizeof(repl_null));
    securec_check(rc, "\0", "\0");
    rc = memset_s(repl_repl, sizeof(repl_repl), false, sizeof(repl_repl));
    securec_check(rc, "\0", "\0");

    if (newOptions != (Datum)0)
        repl_val[Anum_pg_class_reloptions - 1] = newOptions;
    else
        repl_null[Anum_pg_class_reloptions - 1] = true;

    repl_repl[Anum_pg_class_reloptions - 1] = true;

    newtuple = (HeapTuple) tableam_tops_modify_tuple(tuple, RelationGetDescr(pgclass), repl_val, repl_null, repl_repl);

    simple_heap_update(pgclass, &newtuple->t_self, newtuple);

    CatalogUpdateIndexes(pgclass, newtuple);

    tableam_tops_free_tuple(newtuple);

    ReleaseSysCache(tuple);

    heap_close(toastrel, NoLock);
    heap_close(pgclass, RowExclusiveLock);
}

/* Check if compressed options have changed. */
inline bool CheckIfModifyCompressedOptions(PageCompressOpts* newCompressOpt, RelFileCompressOption current) 
{
    if (newCompressOpt->compressType != (int)current.compressAlgorithm || 
        newCompressOpt->compressLevel != (int)current.compressLevel || 
        newCompressOpt->compressChunkSize != CHUNK_SIZE_LIST[current.compressChunkSize] ||
        newCompressOpt->compressPreallocChunks != (int)current.compressPreallocChunks ||
        newCompressOpt->compressByteConvert != (int)current.byteConvert ||
        newCompressOpt->compressDiffConvert != (int)current.diffConvert) {
        return true;
    } 
    return false;
}

/**
 * Row compressed Options can not be used in segment table, 
 * column table, view, unlogged table or temp table.
 */
bool CheckTableSupportSetCompressedOptions(Relation rel) 
{
    if (rel == NULL) {
        return false;
    }

    if (rel->rd_rel->relkind != RELKIND_RELATION) {
        return false;
    }

    if (!RelationIsPermanent(rel)) {
        return false;        
    }

    if (RelationIsColStore(rel) || RelationIsTsStore(rel)) {
        return false;
    }
    return true;
}

bool CheckDefListContainsCompressedOptions(List* defList) 
{
    if (defList == NULL) {
        return false;
    }
    static const char *compressedOptions[] = {"compresstype",   "compress_chunk_size",   "compress_prealloc_chunks",
                                            "compress_level", "compress_byte_convert", "compress_diff_convert"};
    static const int compressedOptionsNum = 6;
    ListCell *opt = NULL;
    for (int i = 0; i < compressedOptionsNum; ++i) {
        foreach (opt, defList) {
            DefElem *def = (DefElem *)lfirst(opt);
            if (pg_strcasecmp(def->defname, compressedOptions[i]) == 0) {
                return true;
            }
        }
    }
    return false;
}

/**
 * Set compressed options need to rebuild table, check whether the modification need to rebuild table,
 * and check whether the new compressed options is valid.
 */ 
bool static transformTableCompressedOptions(Relation rel, bytea* relOption, List* defList)
{
    /* only row table can modify compressed options */
    if (!CheckTableSupportSetCompressedOptions(rel)) {
        ForbidUserToSetCompressedOptions(defList);
        return false;
    }

    PageCompressOpts* newCompressOpt = &(((StdRdOptions*)relOption)->compress);
    RelFileCompressOption currentCompressOpt;
    TransCompressOptions(rel->rd_node, &currentCompressOpt);

    if(!CheckIfModifyCompressedOptions(newCompressOpt, currentCompressOpt)) {
        return false;
    }

    /* check whether the new compression parameter is valid */
    if (newCompressOpt->compressType == COMPRESS_TYPE_NONE && 
            (newCompressOpt->compressLevel != 0 || newCompressOpt->compressChunkSize != BLCKSZ / 2 ||
            newCompressOpt->compressPreallocChunks != 0 || newCompressOpt->compressByteConvert != false ||
            newCompressOpt->compressDiffConvert != false)) {
                ereport(ERROR, (errcode(ERRCODE_INVALID_OPTION),
                                errmsg("compress_level=0, compress_chunk_size=4096, compress_prealloc_chunks=0, compress_byte_convert=false, compress_diff_convert=false should be set when compresstype=0")));
    }

    if (newCompressOpt->compressType == COMPRESS_TYPE_PGZSTD) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPTION),
                        errmsg("row-compression feature current not support algorithm is PGZSTD.")));
    }

    if ((newCompressOpt->compressType != COMPRESS_TYPE_ZSTD && newCompressOpt->compressType != COMPRESS_TYPE_ZLIB) &&
        newCompressOpt->compressLevel != 0) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPTION),
            errmsg("compress_level should be used with ZSTD or ZLIB algorithm."))); 
    }

    if (!newCompressOpt->compressByteConvert && newCompressOpt->compressDiffConvert) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPTION),
                        errmsg("compress_diff_convert should be used with compress_byte_convert.")));
    }

    bool success = false;
    ConvertChunkSize(newCompressOpt->compressChunkSize, &success);
    if (!success) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPTION),
                        errmsg("invalid compress_chunk_size %u, must be one of %d, %d, %d or %d",
                                newCompressOpt->compressChunkSize, BLCKSZ / 16, BLCKSZ / 8, BLCKSZ / 4, BLCKSZ / 2)));
    }
    if (newCompressOpt->compressPreallocChunks >= BLCKSZ / newCompressOpt->compressChunkSize) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPTION), 
                        errmsg("invalid compress_prealloc_chunks %u, must be less than %u for %s",
                                newCompressOpt->compressPreallocChunks,
                                BLCKSZ / newCompressOpt->compressChunkSize,
                                RelationGetRelationName(rel))));
    }

    return true;
}

/**
 * Do not modify compression parameters of index.
 */
void static transfromIndexCompressedOptions(Relation rel, bytea* relOoption, List* defList)
{
    if (rel->rd_node.opt == 0) {
        ForbidUserToSetCompressedOptions(defList);
        return;
    }

    PageCompressOpts* newCompressOpt = &(((StdRdOptions*)relOoption)->compress);
    RelFileCompressOption current;
    TransCompressOptions(rel->rd_node, &current);
    if (newCompressOpt) {
        if (newCompressOpt->compressType != (int)current.compressAlgorithm) {
            ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("change compresstype OPTION is not supported")));
        }
        if ((int)current.compressAlgorithm != COMPRESS_TYPE_NONE &&
            newCompressOpt->compressChunkSize != CHUNK_SIZE_LIST[current.compressChunkSize]) {
            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                            errmsg("change compress_chunk_size OPTION is not supported")));
        }
        if (!newCompressOpt->compressByteConvert && newCompressOpt->compressDiffConvert) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_OPTION),
                            errmsg("compress_diff_convert should be used with compress_byte_convert.")));
        }
        if (current.compressAlgorithm == COMPRESS_TYPE_PGLZ) {
            ListCell *opt = NULL;
            foreach (opt, defList) {
                DefElem *def = (DefElem *)lfirst(opt);
                if (pg_strcasecmp(def->defname, "compress_level") == 0) {
                    ereport(ERROR, (errcode(ERRCODE_INVALID_OPTION),
                                    errmsg("compress_level should be used with ZSTD or ZLIB algorithm.")));
                }
            }
        }
    } else {
        if ((int)current.compressAlgorithm != COMPRESS_TYPE_NONE) {
            ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("change compresstype OPTION is not supported")));
        }
    }

    /*
     * forbid modify partition CompressOption
     */
    if (HEAP_IS_PARTITIONED(rel)) {
        if ((int)current.compressLevel != newCompressOpt->compressLevel) {
            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                            errmsg("change partition compressLevel OPTION is not supported")));
        }
        if ((int)current.compressPreallocChunks != newCompressOpt->compressPreallocChunks) {
            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                            errmsg("change partition compress_prealloc_chunks OPTION is not supported")));
        }
    }
}

/*
 * Check whether the new compressed options are valid, and whether need to rewrite table.
 * Modifying the compressed options of row table causes to rewrite.
 */
bool static transformCompressedOptions(Relation rel, bytea* relOption, List* defList, AlteredTableInfo* tab) {
    /* If delist doesn't contains compressed options, return false. */
    if (!relOption || defList == NULL || !CheckDefListContainsCompressedOptions(defList)) {
        return false;
    }

    /* If the relkind doesn't support compressed options, check if delist contains compressed options.
     * If does, throw exception. 
     */
    if (!REL_SUPPORT_COMPRESSED(rel)) {
        ForbidUserToSetCompressedOptions(defList);
        return false;
    }

    /* Most compressed options can be modified only in row table */
    if (tab != NULL && RelationIsRelation(rel)) {
        return transformTableCompressedOptions(rel, relOption, defList);
    } else {
        transfromIndexCompressedOptions(rel, relOption, defList);
    }
    return false;
}

/*
 * Set, reset, or replace reloptions.
 */
static void ATExecSetRelOptions(Relation rel, List* defList, AlterTableType operation, LOCKMODE lockmode, bool innerset, AlteredTableInfo* tab)
{
    Oid relid;
    Relation pgclass;
    HeapTuple tuple, newtuple, tmptuple;
    Datum datum;
    Datum oldOptions;
    bool isnull = false;
    Datum newOptions;
    Datum repl_val[Natts_pg_class];
    bool repl_null[Natts_pg_class];
    bool repl_repl[Natts_pg_class];
    static const char* const validnsps[] = HEAP_RELOPT_NAMESPACES;
    List* partCacheList = NIL;
    ListCell* cell = NULL;
    Oid toastOid = InvalidOid;
    HeapTuple partTuple = NULL;
    Form_pg_partition partForm = NULL;
    errno_t rc = EOK;
    Oid rel_cn_oid = InvalidOid;
    RedisHtlAction redis_action = REDIS_REL_INVALID;
    char* merge_list = NULL;
    bool oldRelHasUids = RELATION_HAS_UIDS(rel);
    bool newRelHasUids = false;
    if (defList == NIL && operation != AT_ReplaceRelOptions)
        return; /* nothing to do */

    if (GttOncommitOption(defList) != ONCOMMIT_NOOP) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_OPERATION),
                errmsg("table cannot add or modify on commit parameter by ALTER TABLE command.")));
    }
    
    if (!innerset) {
        /* forbid user to set or change inner options */
        ForbidOutUsersToSetInnerOptions(defList);
        ForbidToAlterOptionsForTdeTbl(defList);
    }

#ifdef ENABLE_MULTIPLE_NODES
    CheckRedistributeOption(defList, &rel_cn_oid, &redis_action, &merge_list, rel);
#else
    CheckRedistributeOption(defList, &rel_cn_oid, &redis_action, rel);
#endif

    defList = list_delete_name(defList, "merge_list");
    pgclass = heap_open(RelationRelationId, RowExclusiveLock);

    /* Fetch heap tuple */
    relid = RelationGetRelid(rel);
    tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
    if (!HeapTupleIsValid(tuple)) {
        ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", relid)));
    }

#ifdef ENABLE_HTAP
    if (HAVE_HTAP_TABLES && CheckDefListContainsCompressedOptions(defList) && RelHasImcs(RelationGetRelid(rel))) {
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
            errmsg("CompressdOptions can not be not modified for imcstore table, please unpopulate first.")));
    }
#endif

    // we have to handle psort tuple's options if this is an index relation using PSORT method.
    // it's identifyed by access method whose oid is PSORT_AM_OID.
    // and the psort tuple id is saved in index relation's relcudescrelid field.

    bool needSetPsortOpt = false;
    Oid psortTid = InvalidOid;
    Oid indexAmId = DatumGetObjectId(SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relam, &isnull));
    if (!isnull && indexAmId == PSORT_AM_OID) {
        needSetPsortOpt = true;
        Assert(RelationIsIndex(rel));
        psortTid = DatumGetObjectId(SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relcudescrelid, &isnull));
    }

    if (operation == AT_ReplaceRelOptions) {
        /*
         * If we're supposed to replace the reloptions list, we just pretend
         * there were none before.
         */
        datum = (Datum)0;
        isnull = true;
    } else {
        /* Get the old reloptions */
        datum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, &isnull);
    }
    if (rel->rd_rel->relkind == RELKIND_RELATION) {
        oldOptions = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, &isnull);
    }

    /* remove the redis reloptions. */
    if (redis_action == REDIS_REL_NORMAL) {
        List* old_reloptions = untransformRelOptions(datum);
        RemoveRedisRelOptionsFromList(&old_reloptions);
        RemoveRedisRelOptionsFromList(&defList);

        /* defList should keep unchanged. */
        if (old_reloptions != NIL) {
            defList = list_concat(defList, old_reloptions);
        }
        /* Generate new proposed reloptions (text array) */
        newOptions = transformRelOptions((Datum)0, defList, NULL, validnsps, false, operation == AT_ResetRelOptions);

        if (old_reloptions != NIL && defList != NULL) {
            defList = list_truncate(defList, list_length(defList) - list_length(old_reloptions));
        }

        list_free_ext(old_reloptions);
        List *redis_reloptions = AlterTableSetRedistribute(rel, redis_action, merge_list);
        list_free(redis_reloptions);
    } else if (redis_action == REDIS_REL_READ_ONLY) {
        List* redis_reloptions = AlterTableSetRedistribute(rel, redis_action, merge_list);
        List* old_reloptions = untransformRelOptions(datum);
        RemoveRedisRelOptionsFromList(&old_reloptions);

        if (old_reloptions != NIL) {
            redis_reloptions = list_concat(redis_reloptions, old_reloptions);
        }

        if (redis_reloptions != NIL) {
            defList = list_concat(defList, redis_reloptions);
        }

        newOptions = transformRelOptions((Datum)0, defList, NULL, validnsps, false, operation == AT_ResetRelOptions);

        if (redis_reloptions != NIL) {
            defList = list_truncate(defList, list_length(defList) - list_length(redis_reloptions));
        }

        list_free_ext(redis_reloptions);
    } else {
        /* Generate new proposed reloptions (text array) */
        List* redis_reloptions = AlterTableSetRedistribute(rel, redis_action, merge_list);

        if (redis_reloptions != NIL)
            defList = list_concat(defList, redis_reloptions);

        newOptions = transformRelOptions(
            isnull ? (Datum)0 : datum, defList, NULL, validnsps, false, operation == AT_ResetRelOptions);
        if (redis_reloptions != NIL)
            defList = list_truncate(defList, list_length(defList) - list_length(redis_reloptions));

        list_free_ext(redis_reloptions);
    }

    /* Validate */
    bytea* relOpt = NULL;
    switch (rel->rd_rel->relkind) {
        case RELKIND_RELATION: {
            /* this options only can be used when define a new relation.
             * forbid to change or reset these options.
             */
            ForbidUserToSetDefinedOptions(defList);

            bytea* heapRelOpt = heap_reloptions(rel->rd_rel->relkind, newOptions, true);
            relOpt = heapRelOpt;
            const char* algo = RelationGetAlgo(rel);
            newRelHasUids = StdRdOptionsHasUids(heapRelOpt, RELKIND_RELATION);
            if (rel->rd_rel->relhasoids && newRelHasUids) {
                ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                    errmsg("table with oids cannot add or modify hasuids by ALTER TABLE command.")));
            }
            if (ENABLE_DMS && newRelHasUids) {
                ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                    errmsg("table under Shared Storage cannot add or modify hasuids by ALTER TABLE command.")));
            }
            if (RelationIsColStore(rel)) {
                /* un-supported options. dont care its values */
                ForbidToSetOptionsForColTbl(defList);

                if (NULL != heapRelOpt) {
                    /* validate the values of these options */
                    CheckCStoreRelOption((StdRdOptions*)heapRelOpt);
                }
            } else if (RelationIsTsStore(rel)) {
                forbid_to_set_options_for_timeseries_tbl(defList);
            } else {
                /* un-supported options. dont care its values */
                ForbidToSetOptionsForRowTbl(defList);
                if (algo == NULL || *algo == '\0') {
                    ForbidToSetTdeOptionsForNonTdeTbl(defList);
                }
                if (RelationIsUstoreFormat(rel)) {
                    ForbidToSetOptionsForUstoreTbl(defList);
                }
            }

            /* validate the values of ttl and period for partition manager */
            if (NULL != heapRelOpt) {
                check_partion_policy_rel_option(defList, (StdRdOptions*)heapRelOpt);
            }
            break;
        }
        case RELKIND_TOASTVALUE:
        case RELKIND_MATVIEW:
        case RELKIND_CONTQUERY:
        case RELKIND_VIEW:{
            Assert(oldRelHasUids == false);
            (void)heap_reloptions(rel->rd_rel->relkind, newOptions, true);
            break;
        }
        case RELKIND_INDEX:
        case RELKIND_GLOBAL_INDEX: {
            ForbidUserToSetDefinedIndexOptions(rel, defList);
            Assert(oldRelHasUids == false);
            relOpt = index_reloptions(rel->rd_am->amoptions, newOptions, true);
            break;
        }
        default:
            ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                    errmsg("\"%s\" is not a table, view, materialized view, index, or TOAST table", RelationGetRelationName(rel))));
            break;
    }

    if (rel->rd_options && REL_SUPPORT_COMPRESSED(rel)) {
        SetupPageCompressForRelation(&rel->rd_node, &((StdRdOptions *)(void *)(rel->rd_options))->compress,
                                     RelationGetRelationName(rel));
    }

    /* Only row table support rewriting table when modifying compressed options. */
    if (transformCompressedOptions(rel, relOpt, defList, tab)) {
        /*
         * The oldOptions will be used in phase 3 to delete the old data file correctly, 
         * so the tmpTuple will not be released manually, but be released by memory context.
         */
        tmptuple = heap_copytuple(tuple);
        oldOptions = SysCacheGetAttr(RELOID, tmptuple, Anum_pg_class_reloptions, &isnull);

        /*
         * If modiying compressed options of row table, set tab->rewrite to 
         * AT_REWRITE_ALTER_COMPRESSION, which will case rewriting table during 
         * the phase 3 of altering reloptions.
         */ 
        tab->rewrite = AT_REWRITE_ALTER_COMPRESSION;
        tab->opt = rel->rd_node.opt;
        tab->newOptions = newOptions;
        tab->oldOptions = oldOptions;
    } else if (tab != NULL) {
        tab->rewrite = 0;
    }

    /* Special-case validation of view options */
    if (rel->rd_rel->relkind == RELKIND_VIEW) {
        Query* view_query = get_view_query(rel);
        ListCell* cell = NULL;
        bool check_option = false;

        foreach(cell, defList) {
            DefElem* defel = (DefElem*)lfirst(cell);

            if (pg_strcasecmp(defel->defname, "check_option") == 0) {
                check_option = true;
                break;
            }
        }

        /*
         * If the check option is specified, look to see if the view is
         * actually auto-updatable or not.
         */
        if (check_option) {
            const char *view_updatable_error = view_query_is_auto_updatable(view_query, true);

            if (view_updatable_error)
                ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("WITH CHECK OPTION is supported only on auto-updatable views"),
                        errhint("%s", view_updatable_error)));

            /*
             * Views based on MySQL foreign table is not allowed to add check option,
             * because returning clause which check option dependend on is not supported
             * on MySQL.
             */
            if (CheckMySQLFdwForWCO(view_query))
                ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("WITH CHECK OPTION is not supported on views that base on MySQL foreign table")));
        }
    }

    /*
     * All we need do here is update the pg_class row; the new options will be
     * propagated into relcaches during post-commit cache inval.
     */
    rc = memset_s(repl_val, sizeof(repl_val), 0, sizeof(repl_val));
    securec_check(rc, "\0", "\0");
    rc = memset_s(repl_null, sizeof(repl_null), false, sizeof(repl_null));
    securec_check(rc, "\0", "\0");
    rc = memset_s(repl_repl, sizeof(repl_repl), false, sizeof(repl_repl));
    securec_check(rc, "\0", "\0");

    if (newOptions != (Datum)0)
        repl_val[Anum_pg_class_reloptions - 1] = newOptions;
    else
        repl_null[Anum_pg_class_reloptions - 1] = true;

    repl_repl[Anum_pg_class_reloptions - 1] = true;

    newtuple = (HeapTuple) tableam_tops_modify_tuple(tuple, RelationGetDescr(pgclass), repl_val, repl_null, repl_repl);

    simple_heap_update(pgclass, &newtuple->t_self, newtuple);

    CatalogUpdateIndexes(pgclass, newtuple);

    tableam_tops_free_tuple(newtuple);

    ReleaseSysCache(tuple);

    AddOrDropUidsAttr(relid, oldRelHasUids, newRelHasUids);

    /* repeat the whole exercise for the toast table, if there's one */
    if (RELATION_IS_PARTITIONED(rel)) {
        partCacheList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_PARTITION, relid);
        foreach (cell, partCacheList) {
            partTuple = (HeapTuple)lfirst(cell);

            if (PointerIsValid(partTuple)) {
                partForm = (Form_pg_partition)GETSTRUCT(partTuple);
                toastOid = partForm->reltoastrelid;
                if (OidIsValid(toastOid)) {
                    ATExecSetRelOptionsToast(toastOid, defList, operation, lockmode);
                }
            }
        }
    } else if (RelationIsPartitioned(rel) && needSetPsortOpt) {
        // fetch index-partition tuples from pg_partition table, which has the same parentid,
        // then set the relation-options for psort tuple in pg_class.
        //
        partCacheList = searchPgPartitionByParentId(PART_OBJ_TYPE_INDEX_PARTITION, relid);
        foreach (cell, partCacheList) {
            partTuple = (HeapTuple)lfirst(cell);
            if (PointerIsValid(partTuple)) {
                partForm = (Form_pg_partition)GETSTRUCT(partTuple);
                ATExecSetPSortOption(partForm->relcudescrelid, defList, operation, lockmode);
            }
        }
    } else {
        if (OidIsValid(rel->rd_rel->reltoastrelid)) {
            ATExecSetRelOptionsToast(rel->rd_rel->reltoastrelid, defList, operation, lockmode);
        }

        if (needSetPsortOpt) {
            ATExecSetPSortOption(psortTid, defList, operation, lockmode);
        }
    }
    heap_close(pgclass, RowExclusiveLock);

    if (RELATION_IS_PARTITIONED(rel)) {
        AlterTableSetPartRelOptions(rel, defList, operation, lockmode, merge_list, redis_action);
#ifdef ENABLE_MULTIPLE_NODES
        if (IS_MAIN_COORDINATOR) {
            UpdatePartPolicyWhenATRelOptions(rel, defList);
        }
        /* we need to call ATDeltaReloptions on each node for table creating, dropping, deltamerge */
        if (RelationIsTsStore(rel)) {
            Tsdb::ATDeltaReloptions(rel, defList);
        }
#endif
    }
    if (merge_list != NULL) {
        pfree(merge_list);
        merge_list = NULL;
    }
    list_free_deep(partCacheList);
}

/*
 * Target		: data partition
 * Brief		:
 * Description	: get oid of target partition from Node *partition, and
 *				: save it in AlteredTableInfo->partid
 * Notes		:
 */
static void ATExecSetTableSpaceForPartitionP2(AlteredTableInfo* tab, Relation rel, Node* partition)
{
    RangePartitionDefState* rangePartDef = NULL;
    Oid partOid = InvalidOid;

    if (!RelationIsPartitioned(rel)) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_OPERATION),
                errmsg(
                    "\"%s\" must be a partitioned table for 'MOVE PARTITION CLAUSE'", RelationGetRelationName(rel))));
    }

    if (RelationIsSubPartitioned(rel)) {
        ereport(
            ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
             (errmsg("Un-support feature"),
              errdetail("For subpartition table, modifying tablespace is not yet supported."),
              errcause("The function is not implemented."), erraction("Use other actions instead."))));
    }

    if (!RelationIsRelation(rel) && !RelationIsIndex(rel)) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_OPERATION),
                errmsg("can not set tablespace for partition of neither table nor index")));
    }

    if (OidIsValid(tab->partid)) {
        ereport(
            ERROR, (errcode(ERRCODE_INVALID_OPERATION), errmsg("cannot have multiple MOVE TABLESPACE subcommands")));
    }

    /* check partition node type, it can be RangeVar or RangePartitionDefState */
    switch (nodeTag(partition)) {
        case T_RangeVar: {
            char objectType = RelationIsRelation(rel) ? PART_OBJ_TYPE_TABLE_PARTITION : PART_OBJ_TYPE_INDEX_PARTITION;

            partOid = PartitionNameGetPartitionOid(rel->rd_id,
                ((RangeVar*)partition)->relname,
                objectType,
                AccessExclusiveLock,
                false,
                false,
                NULL,
                NULL,
                NoLock);
            break;
        }
        case T_RangePartitionDefState: {
            if (RelationIsIndex(rel)) {
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("can not specify 'PARTITION FOR (value,,,)' for 'MOVE PARTITION CLAUSE'")));
            }
            rangePartDef = (RangePartitionDefState*)partition;
            transformPartitionValue(make_parsestate(NULL), (Node*)rangePartDef, false);
            rangePartDef->boundary = transformConstIntoTargetType(rel->rd_att->attrs,
                ((RangePartitionMap*)rel->partMap)->base.partitionKey,
                rangePartDef->boundary);
            partOid =
                PartitionValuesGetPartitionOid(rel, rangePartDef->boundary, AccessExclusiveLock, true, false, false);
            break;
        }
        default: {
            ereport(ERROR,
                (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                    errmsg("invalid partition node type in 'MOVE PARTITION CLAUSE'")));
            break;
        }
    }

    /* cehck oid validity of found partition */
    if (!OidIsValid(partOid)) {
        ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("The partition number is invalid or out-of-range")));
    }

    tab->partid = partOid;
}

/*
 * Target		: data partition
 * Brief		:
 * Description	: move a partition to new tablespace
 * Notes		:
 */
static void ATExecSetTableSpaceForPartitionP3(Oid tableOid, Oid partOid, Oid newTableSpace, LOCKMODE lockmode)
{
    Relation rel;
    Relation partRel;
    Partition part;
    Oid oldTableSpace;
    Oid reltoastrelid;
    Oid reltoastidxid;
    Oid newrelfilenode;
    Relation pg_partition;
    HeapTuple tuple;
    Form_pg_partition pd_part;
    bool isbucket;
    bool newcbi = false;

    /*
     * Need lock here in case we are recursing to toast table or index
     */
    rel = relation_open(tableOid, NoLock);
    if (RelationIsSubPartitioned(rel)) {
        ereport(
            ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
             (errmsg("Un-support feature"),
              errdetail("For subpartition table, modifying tablespace is not yet supported."),
              errcause("The function is not implemented."), erraction("Use other actions instead."))));
    }
    part = partitionOpen(rel, partOid, lockmode);

    /*
     * No work if no change in tablespace.
     */
    oldTableSpace = part->pd_part->reltablespace;
    if (newTableSpace == oldTableSpace ||
        (newTableSpace == u_sess->proc_cxt.MyDatabaseTableSpace && oldTableSpace == 0)) {
        partitionClose(rel, part, NoLock);
        relation_close(rel, NoLock);
        return;
    }

    /* Can't move a non-shared relation into pg_global */
    if (newTableSpace == GLOBALTABLESPACE_OID)
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                errmsg("only shared relations can be placed in pg_global tablespace")));

    reltoastrelid = part->pd_part->reltoastrelid;
    reltoastidxid = part->pd_part->reltoastidxid;

    /* Get a modifiable copy of the relation's pg_class row */
    pg_partition = heap_open(PartitionRelationId, RowExclusiveLock);

    tuple = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(partOid));
    if (!HeapTupleIsValid(tuple))
        ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for partition %u", partOid)));

    pd_part = (Form_pg_partition)GETSTRUCT(tuple);

    /*
     * Relfilenodes are not unique across tablespaces, so we need to allocate
     * a new one in the new tablespace.
     */
    if (RelationGetStorageType(rel) == (uint4)HEAP_DISK) {
        newrelfilenode = GetNewRelFileNode(newTableSpace, NULL, rel->rd_rel->relpersistence);
    } else {
        newcbi = RelationIsCrossBucketIndex(rel);
        isbucket = BUCKET_OID_IS_VALID(rel->rd_bucketoid) && !newcbi;
        Oid database_id = (ConvertToRelfilenodeTblspcOid(newTableSpace) == GLOBALTABLESPACE_OID) ?
            InvalidOid : u_sess->proc_cxt.MyDatabaseId;
        newrelfilenode = seg_alloc_segment(ConvertToRelfilenodeTblspcOid(newTableSpace),
                                           database_id, isbucket, InvalidBlockNumber);
    }
    partRel = partitionGetRelation(rel, part);
    /* make sure we create the right underlying storage for cross-bucket index */
    partRel->newcbi = newcbi;
    atexecset_table_space(partRel, newTableSpace, newrelfilenode);

    elog(LOG,
        "Row Partition: %s(%u) tblspc %u/%u/%u => %u/%u/%u",
        RelationGetRelationName(partRel),
        RelationGetRelid(partRel),
        partRel->rd_node.spcNode,
        partRel->rd_node.dbNode,
        partRel->rd_node.relNode,
        newTableSpace,
        partRel->rd_node.dbNode,
        newrelfilenode);

    /* update the pg_class row */
    pd_part->reltablespace = ConvertToPgclassRelTablespaceOid(newTableSpace);
    pd_part->relfilenode = newrelfilenode;

    simple_heap_update(pg_partition, &tuple->t_self, tuple);
    CatalogUpdateIndexes(pg_partition, tuple);

    tableam_tops_free_tuple(tuple);

    heap_close(pg_partition, RowExclusiveLock);

    partitionClose(rel, part, NoLock);
    releaseDummyRelation(&partRel);
    relation_close(rel, NoLock);

    /* Make sure the reltablespace change is visible */
    CommandCounterIncrement();

    /* Move associated toast relation and/or index, too */
    if (OidIsValid(reltoastrelid))
        ATExecSetTableSpace(reltoastrelid, newTableSpace, lockmode);
    if (OidIsValid(reltoastidxid))
        ATExecSetTableSpace(reltoastidxid, newTableSpace, lockmode);
}

static void atexecset_table_space_internal(Relation rel, RelFileNode& newrnode, Oid newTableSpace, Oid newrelfilenode)
{
    ForkNumber forkNum;
    SMgrRelation dstrel;

    /* Open old and new relation */
    dstrel = smgropen(newrnode, rel->rd_backend);

    /* open rel storage avoid relcache invalided*/
    RelationOpenSmgr(rel);
    
    /* we should not copy relation to the limited space tablespace */
    RelFileNode newFileNode = dstrel->smgr_rnode.node;

    /* copy main fork */
    copy_relation_data(rel, &dstrel, MAIN_FORKNUM, rel->rd_rel->relpersistence);

    /* copy those extra forks that exist */
    for (forkNum = (ForkNumber)(MAIN_FORKNUM + 1); forkNum <= MAX_FORKNUM; forkNum = (ForkNumber)(forkNum + 1)) {
        /* it's meaningless to copy BCM files, so ignore it */
        if ((BCM_FORKNUM != forkNum) && smgrexists(rel->rd_smgr, forkNum)) {
            smgrcreate(dstrel, forkNum, false);
            /*
             * WAL log creation if the relation is persistent, or this is the
             * init fork of an unlogged relation.
             */
            if (RelationNeedsWAL(rel) ||
                (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED && forkNum == INIT_FORKNUM))
                log_smgrcreate(&newrnode, forkNum);

            copy_relation_data(rel, &dstrel, forkNum, rel->rd_rel->relpersistence);
        }
    }

    /* close new one */
    smgrclose(dstrel);
}

static void atexecset_table_space(Relation rel, Oid newTableSpace, Oid newrelfilenode)
{
    /*
     * Since we copy the file directly without looking at the shared buffers,
     * we'd better first flush out any pages of the source relation that are
     * in shared buffers.  We assume no new changes will be made while we are
     * holding exclusive lock on the rel.
     */
    FlushRelationBuffers(rel);

    /*
     * Create and copy all forks of the relation, and schedule unlinking of
     * old physical files.
     *
     * NOTE: any conflict in relfilenode value will be caught in
     * RelationCreateStorage().
     */
    RelFileNode newrnode;
    newrnode = rel->rd_node;
    newrnode.relNode = newrelfilenode;
    newrnode.spcNode = newTableSpace;
    RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel->rd_rel->relowner, rel->rd_bucketoid, rel);
    
    if (RELATION_CREATE_BUCKET(rel)) {
        oidvector* bucketlist = searchHashBucketByOid(rel->rd_bucketoid);
        for (int i = 0; i < bucketlist->dim1; i++) {
            Relation bucket = bucketGetRelation(rel, NULL, bucketlist->values[i]);

            newrnode.bucketNode = bucket->rd_node.bucketNode;
            atexecset_table_space_internal(bucket, newrnode, newTableSpace, newrelfilenode);

            bucketCloseRelation(bucket);
        }
    } else {
        // Set base relation tablespace with no hashbucket.
        atexecset_table_space_internal(rel, newrnode, newTableSpace, newrelfilenode);
    }

    // We must open smgr again, because once deal with invalid syscache msg,
    // smgr maybe is closed and removed from smgr hash table, thus dst and
    // rel->smgr are dangling pointer. If this memory area are reused, it is very
    // dangerous if we still use dst and rel->smgr.
    RelationOpenSmgr(rel);

    /* drop old relation */
    RelationDropStorage(rel);
}

static bool NeedToSetTableSpace(Relation oldRelation, Oid targetTableSpace)
{
    /*
     * No work if no change in tablespace.
     */
    Oid oldTableSpace = oldRelation->rd_rel->reltablespace;
    if (targetTableSpace == oldTableSpace ||
        (targetTableSpace == u_sess->proc_cxt.MyDatabaseTableSpace && oldTableSpace == InvalidOid)) {
        return false;
    }

    /*
     * We cannot support moving mapped relations into different tablespaces.
     * (In particular this eliminates all shared catalogs.)
     */
    if (RelationIsMapped(oldRelation))
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("cannot move system relation \"%s\"", RelationGetRelationName(oldRelation))));

    /* Can't move a non-shared relation into pg_global */
    if (targetTableSpace == GLOBALTABLESPACE_OID)
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                errmsg("only shared relations can be placed in pg_global tablespace")));

    /*
     * Don't allow moving temp tables of other backends ... their local buffer
     * manager is not going to cope.
     * If the top relation is a temp relation in this session, then we treat its
     * all subordinate relations as temp.
     */
    if (!u_sess->cmd_cxt.topRelatationIsInMyTempSession && RELATION_IS_OTHER_TEMP(oldRelation))
        ereport(
            ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot move temporary tables of other sessions")));

    return true;
}

/*
 * Execute ALTER TABLE SET TABLESPACE for cases where there is no tuple
 * rewriting to be done, so we just want to copy the data as fast as possible.
 */
static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
{
    Relation rel;
    Oid reltoastrelid;
    Oid reltoastidxid;
    Oid newrelfilenode;
    Relation pg_class;
    HeapTuple tuple;
    Form_pg_class rd_rel;
    bool isbucket;

    /* require that newTableSpace is valid */
    Assert(OidIsValid(newTableSpace));

    /*
     * Need lock here in case we are recursing to toast table or index
     */
    rel = relation_open(tableOid, lockmode);

    if (RELATION_IS_GLOBAL_TEMP(rel)) {
        const char* objType = RelationIsIndex(rel) ? "index" : "table";
        ereport(ERROR,
           (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
               errmsg("not support alter %s set tablespace on global temp table.", objType)));
    }

    /*
     * No work if no change in tablespace.
     */
    if (!NeedToSetTableSpace(rel, newTableSpace)) {
        relation_close(rel, NoLock);
        return;
    }

    reltoastrelid = rel->rd_rel->reltoastrelid;
    reltoastidxid = rel->rd_rel->reltoastidxid;

    /* Get a modifiable copy of the relation's pg_class row */
    pg_class = heap_open(RelationRelationId, RowExclusiveLock);

    tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(tableOid));
    if (!HeapTupleIsValid(tuple)) {
        ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", tableOid)));
    }
    rd_rel = (Form_pg_class)GETSTRUCT(tuple);

    /*
     * Relfilenodes are not unique across tablespaces, so we need to allocate
     * a new one in the new tablespace.
     */
    if (rel->storage_type == HEAP_DISK) {
        newrelfilenode = GetNewRelFileNode(newTableSpace, NULL, rel->rd_rel->relpersistence);
    } else {
        Assert(rel->storage_type == SEGMENT_PAGE);
        bool newcbi = RelationIsCrossBucketIndex(rel);
        isbucket = BUCKET_OID_IS_VALID(rel->rd_bucketoid) && !newcbi;
        newrelfilenode = seg_alloc_segment(newTableSpace, rel->rd_node.dbNode, isbucket, InvalidBlockNumber);
        /* make sure we create the right underlying storage for cross-bucket index */
        rel->newcbi = newcbi;
    }

    atexecset_table_space(rel, newTableSpace, newrelfilenode);

    elog(LOG, "Row Relation: %s(%u) tblspc %u/%u/%u => %u/%u/%u",
        RelationGetRelationName(rel), RelationGetRelid(rel), rel->rd_node.spcNode, rel->rd_node.dbNode,
        rel->rd_node.relNode, newTableSpace, rel->rd_node.dbNode, newrelfilenode);

    /* update the pg_class row */
    rd_rel->reltablespace = ConvertToPgclassRelTablespaceOid(newTableSpace);
    rd_rel->relfilenode = newrelfilenode;
    simple_heap_update(pg_class, &tuple->t_self, tuple);
    CatalogUpdateIndexes(pg_class, tuple);

    tableam_tops_free_tuple(tuple);

    heap_close(pg_class, RowExclusiveLock);
    UpdatePgObjectChangecsn(tableOid, rel->rd_rel->relkind);

    relation_close(rel, NoLock);

    /* Make sure the reltablespace change is visible */
    CommandCounterIncrement();

    /* Move associated toast relation and/or index, too */
    if (OidIsValid(reltoastrelid))
        ATExecSetTableSpace(reltoastrelid, newTableSpace, lockmode);
    if (OidIsValid(reltoastidxid))
        ATExecSetTableSpace(reltoastidxid, newTableSpace, lockmode);
}   

 /*
  * If the rel isn't temp, we must fsync it down to disk before it's safe
  * to commit the transaction.  (For a temp rel we don't care since the rel
  * will be uninteresting after a crash anyway.)
  *
  * It's obvious that we must do this when not WAL-logging the copy. It's
  * less obvious that we have to do it even if we did WAL-log the copied
  * pages. The reason is that since we're copying outside shared buffers, a
  * CHECKPOINT occurring during the copy has no way to flush the previously
  * written data to disk (indeed it won't know the new rel even exists).  A
  * crash later on would replay WAL from the checkpoint, therefore it
  * wouldn't replay our earlier WAL entries. If we do not fsync those pages
  * here, they might still not be on disk when the crash occurs.
  */
static void JudgeSmgrDsync(char relpersistence, bool copying_initfork, SMgrRelation dst, ForkNumber forkNum) 
{
    if (relpersistence == RELPERSISTENCE_PERMANENT || copying_initfork ||
        ((relpersistence == RELPERSISTENCE_TEMP) && STMT_RETRY_ENABLED)) {
        smgrimmedsync(dst, forkNum);
    }
}

/*
 * Copy data, block by block
 */
static void copy_relation_data(Relation rel, SMgrRelation* dstptr, ForkNumber forkNum, char relpersistence)
{
    char* buf = NULL;
    char* unalign_buffer = NULL;
    Page page;
    bool use_wal = false;
    bool copying_initfork = false;
    char *bufToWrite = NULL;
    BlockNumber nblocks;
    BlockNumber blkno;
    TdeInfo tde_info = {0};
    SMgrRelation src = rel->rd_smgr;
    SMgrRelation dst = *dstptr;
    RelFileNode newFileNode = dst->smgr_rnode.node;
    BackendId backendId = dst->smgr_rnode.backend;

    /*
     * palloc the buffer so that it's MAXALIGN'd.  If it were just a local
     * char[] array, the compiler might align it on any byte boundary, which
     * can seriously hurt transfer speed to and from the kernel; not to
     * mention possibly making log_newpage's accesses to the page header fail.
     */
    ADIO_RUN()
    {
        buf = (char*)adio_align_alloc(BLCKSZ);
    }
    ADIO_ELSE()
    {
        if (ENABLE_DSS) {
            unalign_buffer = (char*)palloc(BLCKSZ + ALIGNOF_BUFFER);
            buf = (char*)BUFFERALIGN(unalign_buffer);
        } else {
            buf = (char*)palloc(BLCKSZ);
        }
    }
    ADIO_END();
    page = (Page)buf;

    /*
     * The init fork for an unlogged relation in many respects has to be
     * treated the same as normal relation, changes need to be WAL logged and
     * it needs to be synced to disk.
     */
    copying_initfork = relpersistence == RELPERSISTENCE_UNLOGGED && forkNum == INIT_FORKNUM;

    /*
     * We need to log the copied data in WAL iff WAL archiving/streaming is
     * enabled AND it's a permanent relation.
     */
    use_wal = XLogIsNeeded() && (relpersistence == RELPERSISTENCE_PERMANENT || copying_initfork ||
                                    ((relpersistence == RELPERSISTENCE_TEMP) && STMT_RETRY_ENABLED));

    nblocks = smgrnblocks(src, forkNum);

    /*
     * check tablespace size limitation when extending new relation file.
     * Segment-page storage will check limitation in smgrextend. But we have to get tablespace limit
     * infomation here to avoid accessing system table in smgrextend.
     */
    if (IsSegmentFileNode(newFileNode)) {
        TableSpaceUsageManager::IsExceedMaxsize(newFileNode.spcNode, 0, true);
	    /* We should not remove the data to the limited tablespace under segment mode. */
	    if (u_sess->cmd_cxt.l_isLimit) {
	        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmodule(MOD_SEGMENT_PAGE),
	            errmsg("Don't support moving relation to limited tablespace in segment-page storage!"),
	            errdetail("Segment-page storage does not support limited tablespace \"%s\"", get_tablespace_name(newFileNode.spcNode)),
	            errhint("use default or unlimited user defined tablespace before using segment-page storage.")));
	    }
    } else {
        TableSpaceUsageManager::IsExceedMaxsize(newFileNode.spcNode, ((uint64)BLCKSZ) * nblocks, false);
    }

    // We must open smgr again, because once deal with invalid syscache msg,
    // smgr maybe is closed and removed from smgr hash table, thus dst and
    // rel->smgr are dangling pointer. If this memory area are reused, it is very
    // dangerous if we still use dst and rel->smgr.
    //
    RelationOpenSmgr(rel);
    *dstptr = dst = smgropen(newFileNode, backendId);
    src = rel->rd_smgr;

    /* maybe can add prefetch here */
    for (blkno = 0; blkno < nblocks; blkno++) {
        /* If we got a cancel signal during the copy of the data, quit */
        CHECK_FOR_INTERRUPTS();

        SMGR_READ_STATUS rdStatus = smgrread(src, forkNum, blkno, buf);

        if (rdStatus == SMGR_RD_CRC_ERROR) {
            addBadBlockStat(&src->smgr_rnode.node, forkNum);
            addGlobalRepairBadBlockStat(src->smgr_rnode, forkNum, blkno);

            if (RelationNeedsWAL(rel) && CanRemoteRead() && !IsSegmentFileNode(src->smgr_rnode.node)) {
                ereport(WARNING,
                    (errcode(ERRCODE_DATA_CORRUPTED),
                        errmsg("invalid page in block %u of relation %s, try to remote read",
                            blkno,
                            relpath(src->smgr_rnode, forkNum)),
                        handle_in_client(true)));

                RemoteReadBlock(src->smgr_rnode, forkNum, blkno, buf, NULL);

                /*
                 * segment-page storage may fail here, because it use logic blocknumber while CRC 
                 * use physical block number
                 */
                if (PageIsVerified(page, blkno)) {
                    smgrwrite(src, forkNum, blkno, buf, false);
                    UpdateRepairTime(src->smgr_rnode.node, forkNum, blkno);
                } else {
                    ereport(ERROR,
                        (errcode(ERRCODE_DATA_CORRUPTED),
                            (errmsg("fail to remote read page, data corrupted in network"))));
                }
            } else {
                ereport(ERROR,
                    (errcode(ERRCODE_DATA_CORRUPTED),
                        errmsg("invalid page in block %u of relation %s",
                            blkno,
                            relpathbackend(src->smgr_rnode.node, src->smgr_rnode.backend, forkNum))));
            }
        }

        PageDataDecryptIfNeed(page);

        if (RelationisEncryptEnable(rel)) {
            GetTdeInfoFromRel(rel, &tde_info);
        }

        if (IsSegmentFileNode(dst->smgr_rnode.node)) {
            /* 
             * Segment-page storage requiress extending data through buffer to set LSN correctly,
             * and avoid half-page write. DataEncrypt and SetChecksum is done during buffer flush.
             */
            Assert(use_wal);

            Buffer buf = ReadBufferWithoutRelcache(dst->smgr_rnode.node, forkNum, P_NEW, RBM_NORMAL, NULL, NULL);
#ifdef USE_ASSERT_CHECKING
            BufferDesc *buf_desc = GetBufferDescriptor(buf - 1);
            Assert(buf_desc->tag.blockNum == blkno);
#endif
            LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
            XLogRecPtr xlog_ptr = log_newpage(&dst->smgr_rnode.node, forkNum, blkno, page, false, &tde_info);
            errno_t rc = memcpy_s(BufferGetBlock(buf), BLCKSZ, page, BLCKSZ);
            securec_check(rc, "\0", "\0");
            PageSetLSN(BufferGetPage(buf), xlog_ptr);
            MarkBufferDirty(buf);

            UnlockReleaseBuffer(buf);
        } else {
            if (RelationIsUstoreFormat(rel)) {
                if (ExecuteUndoActionsForPartition(rel, dst, forkNum, blkno, blkno, ROLLBACK_OP_FOR_MOVE_TBLSPC)) {
                    *dstptr = dst = smgropen(newFileNode, backendId);
                    src = rel->rd_smgr;
                }
            } else {
                /*
                * WAL-log the copied page. Unfortunately we don't know what kind of a
                * page this is, so we have to log the full page including any unused
                * space.
                */
                if (use_wal) {
                    log_newpage(&dst->smgr_rnode.node, forkNum, blkno, page, false, &tde_info);
                }
                if (RelationisEncryptEnable(rel)) {
                    bufToWrite = PageDataEncryptIfNeed(page, &tde_info, true);
                } else {
                    bufToWrite = page;
                }
                PageSetChecksumInplace((Page)bufToWrite, blkno);
                smgrextend(dst, forkNum, blkno, bufToWrite, false);
            }
        }
    }

    ADIO_RUN()
    {
        adio_align_free(buf);
    }
    ADIO_ELSE()
    {
        if (ENABLE_DSS) {
            pfree_ext(unalign_buffer);
        } else {
            pfree_ext(buf);
        }
    }
    ADIO_END();

    JudgeSmgrDsync(relpersistence, copying_initfork, dst, forkNum);
}

static void mergeHeapBlock(Relation src, Relation dest, ForkNumber forkNum, char relpersistence, BlockNumber srcBlocks,
    BlockNumber destBlocks, TupleDesc srcTupleDesc, Oid srcToastOid, Oid destToastOid, HTAB* chunkIdHashTable,
    bool destHasFSM)
{
    char* buf = NULL;
    char* unaligned_buffer = NULL;
    char* bufToWrite = NULL;
    Page page = NULL;
    bool use_wal = false;
    BlockNumber src_blkno = 0;
    BlockNumber dest_blkno = 0;
    HeapTupleData tuple;
    TdeInfo tde_info = {0};
    errno_t rc = EOK;

    if (srcBlocks == 0) {
        return;
    }

    // check tablespace size limitation when extending new files.
    STORAGE_SPACE_OPERATION(dest, ((uint64)BLCKSZ) * srcBlocks);

    /*
     * palloc the buffer so that it's MAXALIGN'd.  If it were just a local
     * char[] array, the compiler might align it on any byte boundary, which
     * can seriously hurt transfer speed to and from the kernel; not to
     * mention possibly making log_newpage's accesses to the page header fail.
     */
    ADIO_RUN()
    {
        buf = (char*)adio_align_alloc(BLCKSZ);
    }
    ADIO_ELSE()
    {
        if (ENABLE_DSS) {
            unaligned_buffer = (char*)palloc(BLCKSZ + ALIGNOF_BUFFER);
            buf = (char*)BUFFERALIGN(unaligned_buffer);
        } else {
            buf = (char*)palloc(BLCKSZ);
        }
    }
    ADIO_END();
    page = (Page)buf;

    /*
     * We need to log the copied data in WAL iff WAL archiving/streaming is
     * enabled AND it's a permanent relation.
     */
    use_wal = XLogIsNeeded() && (relpersistence == RELPERSISTENCE_PERMANENT ||
                                    ((relpersistence == RELPERSISTENCE_TEMP) && STMT_RETRY_ENABLED));

    for (src_blkno = 0; src_blkno < srcBlocks; src_blkno++) {
        OffsetNumber tupleNum = 0;
        OffsetNumber tupleNo = 0;

        /* If we got a cancel signal during the copy of the data, quit */
        CHECK_FOR_INTERRUPTS();

        RelationOpenSmgr(src);
        SMGR_READ_STATUS rdStatus = smgrread(src->rd_smgr, forkNum, src_blkno, buf);

        if (rdStatus == SMGR_RD_CRC_ERROR) {
            addBadBlockStat(&src->rd_node, forkNum);
            addGlobalRepairBadBlockStat(src->rd_smgr->smgr_rnode, forkNum, src_blkno);

            if (RelationNeedsWAL(src) && CanRemoteRead() && !IsSegmentFileNode(src->rd_node)) {
                ereport(WARNING,
                    (errcode(ERRCODE_DATA_CORRUPTED),
                        errmsg("invalid page in block %u of relation %s, try to remote read",
                            src_blkno,
                            relpath(src->rd_smgr->smgr_rnode, forkNum)),
                        handle_in_client(true)));

                RemoteReadBlock(src->rd_smgr->smgr_rnode, forkNum, src_blkno, buf, NULL);

                if (PageIsVerified(page, src_blkno)) {
                    smgrwrite(src->rd_smgr, forkNum, src_blkno, buf, false);
                    UpdateRepairTime(src->rd_node, forkNum, src_blkno);
                } else {
                    ereport(ERROR,
                        (errcode(ERRCODE_DATA_CORRUPTED),
                            (errmsg("fail to remote read page, data corrupted in network"))));
                }
            } else {
                ereport(ERROR,
                    (errcode(ERRCODE_DATA_CORRUPTED),
                        errmsg("invalid page in block %u of relation %s",
                            src_blkno,
                            relpathbackend(src->rd_smgr->smgr_rnode.node, src->rd_smgr->smgr_rnode.backend, forkNum))));
            }
        }

        PageDataDecryptIfNeed(page);

        dest_blkno = destBlocks + src_blkno;
        tupleNum = tableam_tops_page_get_max_offsetnumber(src, page);

        for (tupleNo = FirstOffsetNumber; tupleNo <= tupleNum; tupleNo++) {

            if (!tableam_tops_page_get_item(src, &tuple, page, tupleNo, destBlocks)) {
                continue;
            }
            ChunkIdHashKey hashkey;
            OldToNewChunkIdMapping mapping = NULL;

            /* If toast storage, modify va_toastrelid and va_valueid. */
            if (OidIsValid(destToastOid)) {
                int numAttrs = srcTupleDesc->natts;
                Datum values[numAttrs];
                bool isNull[numAttrs];
                int i = 0;

                /* Ustore not support compress yet */
                if (RelationIsUstoreFormat(src) || !tableam_tuple_check_compress(src, static_cast<Tuple>(&tuple))) {
                    tableam_tops_deform_tuple(&tuple, srcTupleDesc, values, isNull);
                } else {
                    Assert(page != NULL);
                    Assert(PageIsCompressed(page));
                    tableam_tops_deform_cmprs_tuple(&tuple, srcTupleDesc, values, isNull, (char*)getPageDict(page));
                }
                for (i = 0; i < numAttrs; i++) {
                    struct varlena* value = NULL;

                    value = (struct varlena*)DatumGetPointer(values[i]);
                    if (srcTupleDesc->attrs[i].attlen == -1 && !isNull[i] && VARATT_IS_EXTERNAL(value)) {
                        struct varatt_external* toastPointer = NULL;

                        toastPointer = (varatt_external*)(VARDATA_EXTERNAL((varattrib_1b_e*)(value)));
                        toastPointer->va_toastrelid = destToastOid;

                        rc = memset_s(&hashkey, sizeof(hashkey), 0, sizeof(hashkey));
                        securec_check(rc, "\0", "\0");
                        hashkey.toastTableOid = srcToastOid;
                        hashkey.oldChunkId = toastPointer->va_valueid;

                        mapping = (OldToNewChunkIdMapping)hash_search(chunkIdHashTable, &hashkey, HASH_FIND, NULL);

                        if (PointerIsValid(mapping)) {
                            toastPointer->va_valueid = mapping->newChunkId;
                        }
                    }
                }
            } else if (RelationIsToast(dest)) {
                /* for toast, more than 1GB CLOB/BLOB the first chunk chunk_data */
                Datum values[3];
                bool isNull[3];
                tableam_tops_deform_tuple(&tuple, src->rd_att, values, isNull);
                struct varlena* value = (struct varlena*)DatumGetPointer(values[2]);
                if (!isNull[2] && VARATT_IS_EXTERNAL_ONDISK_B(value)) {
                    struct varatt_external* toastPointer = NULL;

                    toastPointer = (varatt_external*)(VARDATA_EXTERNAL((varattrib_1b_e*)(value)));
                    Assert(toastPointer->va_toastrelid == src->rd_id);
                    toastPointer->va_toastrelid = dest->rd_id;

                    rc = memset_s(&hashkey, sizeof(hashkey), 0, sizeof(hashkey));
                    securec_check(rc, "\0", "\0");
                    hashkey.toastTableOid = src->rd_id;
                    hashkey.oldChunkId = toastPointer->va_valueid;

                    mapping = (OldToNewChunkIdMapping)hash_search(chunkIdHashTable, &hashkey, HASH_FIND, NULL);

                    if (PointerIsValid(mapping)) {
                        toastPointer->va_valueid = mapping->newChunkId;
                    }
                }
            }
        }

        /* merge FSM */
        if (RelationisEncryptEnable(src)) {
            GetTdeInfoFromRel(src, &tde_info);
        }
        if (destHasFSM) {
            Size freespace = tableam_tops_page_get_freespace(src, page);

            RecordPageWithFreeSpace(dest, dest_blkno, freespace);
        }

        if (IsSegmentFileNode(dest->rd_node)) {
            Assert(use_wal);
            Buffer buf = ReadBufferExtended(dest, forkNum, P_NEW, RBM_NORMAL, NULL);
#ifdef USE_ASSERT_CHECKING
            BufferDesc *buf_desc = GetBufferDescriptor(buf - 1);
            Assert(buf_desc->tag.blockNum == dest_blkno);
#endif
            LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
            XLogRecPtr xlog_ptr = log_newpage(&dest->rd_node, forkNum, dest_blkno, page, true, &tde_info);
            errno_t rc = memcpy_s(BufferGetBlock(buf), BLCKSZ, page, BLCKSZ);
            securec_check(rc, "\0", "\0");
            PageSetLSN(BufferGetPage(buf), xlog_ptr);
            MarkBufferDirty(buf);
            UnlockReleaseBuffer(buf);
        } else {
            RelationOpenSmgr(dest);
            if (RelationIsUstoreFormat(src)) {
                PartitionToastInfo pToastInfo = {InvalidOid, InvalidOid, InvalidOid, InvalidOid, NULL, NULL};
                if (OidIsValid(destToastOid)) {
                    pToastInfo.srcPartTupleOid = srcToastOid;
                    pToastInfo.destPartTupleOid = destToastOid;
                    pToastInfo.tupDesc = srcTupleDesc;
                    pToastInfo.chunkIdHashTable = chunkIdHashTable;
                } else if (RelationIsToast(dest)) {
                    pToastInfo.tupDesc = src->rd_att;
                    pToastInfo.srcToastRelOid = src->rd_id;
                    pToastInfo.destToastRelOid = dest->rd_id;
                    pToastInfo.chunkIdHashTable = chunkIdHashTable;
                }
                if (ExecuteUndoActionsForPartition(src, dest->rd_smgr, forkNum, src_blkno, dest_blkno,
                    ROLLBACK_OP_FOR_MERGE_PARTITION, &pToastInfo)) {
                    RelationOpenSmgr(dest);
                }
            } else {
                if (use_wal) {
                    log_newpage(&dest->rd_smgr->smgr_rnode.node, forkNum, dest_blkno, page, true, &tde_info);
                }

                if (RelationisEncryptEnable(src)) {
                    bufToWrite = PageDataEncryptIfNeed(page, &tde_info, true);
                } else {
                    bufToWrite = page;
                }

                /* heap block mix in the block number to checksum. need recalculate */
                PageSetChecksumInplace((Page)bufToWrite, dest_blkno);

                smgrextend(dest->rd_smgr, forkNum, dest_blkno, bufToWrite, false);
            }
        }
    }

    ADIO_RUN()
    {
        adio_align_free(buf);
    }
    ADIO_ELSE()
    {
        if (ENABLE_DSS) {
            pfree_ext(unaligned_buffer);
        } else {
            pfree_ext(buf);
        }
    }
    ADIO_END();

    /*
     * If the rel isn't temp, we must fsync it down to disk before it's safe
     * to commit the transaction.  (For a temp rel we don't care since the rel
     * will be uninteresting after a crash anyway.)
     *
     * It's obvious that we must do this when not WAL-logging the copy. It's
     * less obvious that we have to do it even if we did WAL-log the copied
     * pages. The reason is that since we're copying outside shared buffers, a
     * CHECKPOINT occurring during the copy has no way to flush the previously
     * written data to disk (indeed it won't know the new rel even exists).  A
     * crash later on would replay WAL from the checkpoint, therefore it
     * wouldn't replay our earlier WAL entries. If we do not fsync those pages
     * here, they might still not be on disk when the crash occurs.
     */
    if (relpersistence == RELPERSISTENCE_PERMANENT || ((relpersistence == RELPERSISTENCE_TEMP) && STMT_RETRY_ENABLED))
        smgrimmedsync(dest->rd_smgr, forkNum);
}

static void mergeVMBlock(Relation src, Relation dest, BlockNumber srcHeapBlocks, BlockNumber destHeapBlocks)
{
    Buffer srcVMBuffer = InvalidBuffer;
    Buffer destVMBuffer = InvalidBuffer;
    BlockNumber srcBlockNum = 0;

    for (srcBlockNum = 0; srcBlockNum < srcHeapBlocks; srcBlockNum++) {
        bool VMValue = false;

        visibilitymap_pin(dest, destHeapBlocks + srcBlockNum, &destVMBuffer);
        VMValue = visibilitymap_test(src, srcBlockNum, &srcVMBuffer);

        if (VMValue) {
            visibilitymap_set(dest,
                destHeapBlocks + srcBlockNum,
                InvalidBuffer,
                InvalidXLogRecPtr,
                destVMBuffer,
                InvalidTransactionId,
                false);
        }

    }

    if (BufferIsValid(srcVMBuffer)) {
        ReleaseBuffer(srcVMBuffer);
    }

    if (BufferIsValid(destVMBuffer)) {
        ReleaseBuffer(destVMBuffer);
    }
}

/*
 * ALTER TABLE ENABLE/DISABLE TRIGGER
 *
 * We just pass this off to trigger.c.
 */
static void ATExecEnableDisableTrigger(
    Relation rel, const char* trigname, char fires_when, bool skip_system, LOCKMODE lockmode)
{
    EnableDisableTrigger(rel, trigname, fires_when, skip_system);
}

/*
 * ALTER TABLE ENABLE/DISABLE RULE
 *
 * We just pass this off to rewriteDefine.c.
 */
static void ATExecEnableDisableRule(Relation rel, const char* trigname, char fires_when, LOCKMODE lockmode)
{
    EnableDisableRule(rel, trigname, fires_when);
}

/*
 * ATExecEnableDisableRls
 *
 *     ALTER TABLE table_name ENABLE/DISABLE ROW LEVEL SECURITY.
 *     ALTER TABLE table_name FORCE/NO FORCE ROW LEVEL SECURITY.
 *
 * @param (in) rel: the relation information
 * @param (in) changeType: ENABLE, DISABLE, FORCE, NO FORCE R.L.S
 * @param (in) lockmode: Lock mode for relation open
 * @return: NULL
 */
void ATExecEnableDisableRls(Relation rel, RelationRlsStatus changeType, LOCKMODE lockmode)
{
    /* Check whether need to change on current node */
    if (SupportRlsOnCurrentNode() == false) {
        return;
    }
    /* Check license whether support this feature */
    LicenseSupportRls();

    /* Check whether support RLS for this relation */
    SupportRlsForRel(rel);

    List* roption = NULL;
    /* Enable or Disable Rls */
    switch (changeType) {
        case RELATION_RLS_ENABLE:
            /* update the column data when RLS is disabled */
            roption = list_make1(makeDefElem("enable_rowsecurity", (Node*)makeString("true")));
            ATExecSetRelOptions(rel, roption, AT_SetRelOptions, lockmode);
            break;
        case RELATION_RLS_DISABLE:
            /* update the column data when RLS is disabled */
            roption = list_make1(makeDefElem("enable_rowsecurity", NULL));
            ATExecSetRelOptions(rel, roption, AT_ResetRelOptions, lockmode);
            break;
        case RELATION_RLS_FORCE_ENABLE:
            /* update the column data when RLS is not forced */
            roption = list_make1(makeDefElem("force_rowsecurity", (Node*)makeString("true")));
            ATExecSetRelOptions(rel, roption, AT_SetRelOptions, lockmode);
            break;
        case RELATION_RLS_FORCE_DISABLE:
            /* only update the column data when RLS is forced */
            roption = list_make1(makeDefElem("force_rowsecurity", NULL));
            ATExecSetRelOptions(rel, roption, AT_ResetRelOptions, lockmode);
            break;
        default:
            ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                    errmsg("unknown type %u for ALTER TABLE ROW LEVEL SECURITY", changeType)));
    }
}

/*
 * ATExecEncryptionKeyRotation
 *
 *     ALTER TABLE table_name ENCRYPTION KEY ROTATION.
 *
 * @param (in) rel: the relation information
 * @param (in) lockmode: Lock mode for relation open
 * @return: NULL
 */
static void ATExecEncryptionKeyRotation(Relation rel, LOCKMODE lockmode)
{
    List* tde_reloption = NULL;
    char* dek_cipher = NULL;
    const TDEData* tde_data = NULL;
    errno_t rc = EOK;

    /* get new tde info */
    TDEKeyManager *tde_key_manager= New(CurrentMemoryContext) TDEKeyManager();
    tde_key_manager->init();
    tde_data = tde_key_manager->create_dek();

    dek_cipher = (char*)palloc0(strlen(tde_data->dek_cipher) + 1);
    rc = memcpy_s(dek_cipher, (strlen(tde_data->dek_cipher) + 1), tde_data->dek_cipher, 
        (strlen(tde_data->dek_cipher) + 1));
    securec_check(rc, "\0", "\0");

    /* encryption key rotation */
    tde_reloption = list_make1(makeDefElem("dek_cipher", (Node*)makeString(dek_cipher)));
    ATExecSetRelOptions(rel, tde_reloption, AT_SetRelOptions, lockmode, true);
    ereport(LOG, (errmsg("TDE key rotation success"), errdetail("check TDE table info dek_cipher is changed")));
    /* sync TDE storage hash table */
    if (IS_PGXC_DATANODE) {
        tde_key_manager->save_key(tde_data);
    }
    DELETE_EX2(tde_key_manager);
    pfree_ext(dek_cipher);
}

/*
 * ALTER TABLE INHERIT
 *
 * Add a parent to the child's parents. This verifies that all the columns and
 * check constraints of the parent appear in the child and that they have the
 * same data types and expressions.
 */
static void ATPrepAddInherit(Relation child_rel)
{
    if (child_rel->rd_rel->reloftype)
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot change inheritance of typed table")));
}
 /*
  * Return the address of the new parent relation.
  */
static ObjectAddress ATExecAddInherit(Relation child_rel, RangeVar* parent, LOCKMODE lockmode)
{
    Relation parent_rel, catalogRelation;
    SysScanDesc scan;
    ScanKeyData key;
    HeapTuple inheritsTuple;
    int32 inhseqno;
    List* children = NIL;
    ObjectAddress address;

    if (RELATION_IS_PARTITIONED(child_rel)) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("relation \"%s\" is a partitioned table", RelationGetRelationName(child_rel)),
                errdetail("can not add inheritance for a partitioned table")));
    }

    /*
     * A self-exclusive lock is needed here.  See the similar case in
     * MergeAttributes() for a full explanation.
     */
    parent_rel = heap_openrv(parent, ShareUpdateExclusiveLock);

    if (RELATION_IS_PARTITIONED(parent_rel)) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("inherited relation \"%s\" is a partitioned table", parent->relname),
                errdetail("can not inherit from partitioned table")));
    }

    /*
     * Must be owner of both parent and child -- child was checked by
     * ATSimplePermissions call in ATPrepCmd
     */
    ATSimplePermissions(parent_rel, ATT_TABLE);

    /* Permanent rels cannot inherit from temporary ones */
    if (parent_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
        child_rel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("cannot inherit from temporary relation \"%s\"", RelationGetRelationName(parent_rel))));

    /* If parent rel is temp, it must belong to this session */
    if (parent_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP && !RelationIsLocalTemp(parent_rel))
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot inherit from temporary relation of another session")));

    /* Ditto for the child */
    if (child_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP && !RelationIsLocalTemp(child_rel))
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot inherit to temporary relation of another session")));

    /*
     * Check for duplicates in the list of parents, and determine the highest
     * inhseqno already present; we'll use the next one for the new parent.
     * (Note: get RowExclusiveLock because we will write pg_inherits below.)
     *
     * Note: we do not reject the case where the child already inherits from
     * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
     */
    catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
    ScanKeyInit(
        &key, Anum_pg_inherits_inhrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(child_rel)));
    scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true, NULL, 1, &key);

    /* inhseqno sequences start at 1 */
    inhseqno = 0;
    while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan))) {
        Form_pg_inherits inh = (Form_pg_inherits)GETSTRUCT(inheritsTuple);

        if (inh->inhparent == RelationGetRelid(parent_rel))
            ereport(ERROR,
                (errcode(ERRCODE_DUPLICATE_TABLE),
                    errmsg("relation \"%s\" would be inherited from more than once",
                        RelationGetRelationName(parent_rel))));
        if (inh->inhseqno > inhseqno)
            inhseqno = inh->inhseqno;
    }
    systable_endscan(scan);

    /*
     * Prevent circularity by seeing if proposed parent inherits from child.
     * (In particular, this disallows making a rel inherit from itself.)
     *
     * This is not completely bulletproof because of race conditions: in
     * multi-level inheritance trees, someone else could concurrently be
     * making another inheritance link that closes the loop but does not join
     * either of the rels we have locked.  Preventing that seems to require
     * exclusive locks on the entire inheritance tree, which is a cure worse
     * than the disease.  find_all_inheritors() will cope with circularity
     * anyway, so don't sweat it too much.
     *
     * We use weakest lock we can on child's children, namely AccessShareLock.
     */
    children = find_all_inheritors(RelationGetRelid(child_rel), AccessShareLock, NULL);

    if (list_member_oid(children, RelationGetRelid(parent_rel)))
        ereport(ERROR,
            (errcode(ERRCODE_DUPLICATE_TABLE),
                errmsg("circular inheritance not allowed"),
                errdetail(
                    "\"%s\" is already a child of \"%s\".", parent->relname, RelationGetRelationName(child_rel))));

    /* If parent has OIDs then child must have OIDs */
    if (parent_rel->rd_rel->relhasoids && !child_rel->rd_rel->relhasoids)
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("table \"%s\" without OIDs cannot inherit from table \"%s\" with OIDs",
                    RelationGetRelationName(child_rel),
                    RelationGetRelationName(parent_rel))));

    /* Match up the columns and bump attinhcount as needed */
    MergeAttributesIntoExisting(child_rel, parent_rel);

    /* Match up the constraints and bump coninhcount as needed */
    MergeConstraintsIntoExisting(child_rel, parent_rel);

    /*
     * OK, it looks valid.	Make the catalog entries that show inheritance.
     */
    StoreCatalogInheritance1(RelationGetRelid(child_rel), RelationGetRelid(parent_rel), inhseqno + 1, catalogRelation);

    ObjectAddressSet(address, RelationRelationId,
                     RelationGetRelid(parent_rel));

    /* Now we're done with pg_inherits */
    heap_close(catalogRelation, RowExclusiveLock);

    /* keep our lock on the parent relation until commit */
    heap_close(parent_rel, NoLock);

    return address;
}

/*
 * Obtain the source-text form of the constraint expression for a check
 * constraint, given its pg_constraint tuple
 */
static char* decompile_conbin(HeapTuple contup, TupleDesc tupdesc)
{
    Form_pg_constraint con;
    bool isnull = false;
    Datum attr;
    Datum expr;

    con = (Form_pg_constraint)GETSTRUCT(contup);
    attr = heap_getattr(contup, Anum_pg_constraint_conbin, tupdesc, &isnull);
    if (isnull)
        ereport(ERROR,
            (errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("null conbin for constraint %u", HeapTupleGetOid(contup))));

    expr = DirectFunctionCall2(pg_get_expr, attr, ObjectIdGetDatum(con->conrelid));
    return TextDatumGetCString(expr);
}

/*
 * Determine whether two check constraints are functionally equivalent
 *
 * The test we apply is to see whether they reverse-compile to the same
 * source string.  This insulates us from issues like whether attributes
 * have the same physical column numbers in parent and child relations.
 */
static bool constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
{
    Form_pg_constraint acon = (Form_pg_constraint)GETSTRUCT(a);
    Form_pg_constraint bcon = (Form_pg_constraint)GETSTRUCT(b);

    if (acon->condeferrable != bcon->condeferrable || acon->condeferred != bcon->condeferred ||
        strcmp(decompile_conbin(a, tupleDesc), decompile_conbin(b, tupleDesc)) != 0)
        return false;
    else
        return true;
}

/*
 * Check columns in child table match up with columns in parent, and increment
 * their attinhcount.
 *
 * Called by ATExecAddInherit
 *
 * Currently all parent columns must be found in child. Missing columns are an
 * error.  One day we might consider creating new columns like CREATE TABLE
 * does.  However, that is widely unpopular --- in the common use case of
 * partitioned tables it's a foot-gun.
 *
 * The data type must match exactly. If the parent column is NOT NULL then
 * the child must be as well. Defaults are not compared, however.
 */
static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
{
    Relation attrrel;
    AttrNumber parent_attno;
    int parent_natts;
    TupleDesc tupleDesc;
    HeapTuple tuple;

    attrrel = heap_open(AttributeRelationId, RowExclusiveLock);

    tupleDesc = RelationGetDescr(parent_rel);
    parent_natts = tupleDesc->natts;

    for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++) {
        Form_pg_attribute attribute = &tupleDesc->attrs[parent_attno - 1];
        char* attributeName = NameStr(attribute->attname);

        /* Ignore dropped columns in the parent. */
        if (attribute->attisdropped)
            continue;

        /* Find same column in child (matching on column name). */
        tuple = SearchSysCacheCopyAttName(RelationGetRelid(child_rel), attributeName);
        if (HeapTupleIsValid(tuple)) {
            /* Check they are same type, typmod, and collation */
            Form_pg_attribute childatt = (Form_pg_attribute)GETSTRUCT(tuple);

            if (attribute->atttypid != childatt->atttypid || attribute->atttypmod != childatt->atttypmod)
                ereport(ERROR,
                    (errcode(ERRCODE_DATATYPE_MISMATCH),
                        errmsg("child table \"%s\" has different type for column \"%s\"",
                            RelationGetRelationName(child_rel),
                            attributeName)));

            if (attribute->attcollation != childatt->attcollation)
                ereport(ERROR,
                    (errcode(ERRCODE_COLLATION_MISMATCH),
                        errmsg("child table \"%s\" has different collation for column \"%s\"",
                            RelationGetRelationName(child_rel),
                            attributeName)));

            /*
             * Check child doesn't discard NOT NULL property.  (Other
             * constraints are checked elsewhere.)
             */
            if (attribute->attnotnull && !childatt->attnotnull)
                ereport(ERROR,
                    (errcode(ERRCODE_DATATYPE_MISMATCH),
                        errmsg("column \"%s\" in child table must be marked NOT NULL", attributeName)));

            /*
             * OK, bump the child column's inheritance count.  (If we fail
             * later on, this change will just roll back.)
             */
            childatt->attinhcount++;
            simple_heap_update(attrrel, &tuple->t_self, tuple);
            CatalogUpdateIndexes(attrrel, tuple);
            tableam_tops_free_tuple(tuple);
        } else {
            ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("child table is missing column \"%s\"", attributeName)));
        }
    }

    heap_close(attrrel, RowExclusiveLock);
}

/*
 * Check constraints in child table match up with constraints in parent,
 * and increment their coninhcount.
 *
 * Constraints that are marked ONLY in the parent are ignored.
 *
 * Called by ATExecAddInherit
 *
 * Currently all constraints in parent must be present in the child. One day we
 * may consider adding new constraints like CREATE TABLE does.
 *
 * XXX This is O(N^2) which may be an issue with tables with hundreds of
 * constraints. As long as tables have more like 10 constraints it shouldn't be
 * a problem though. Even 100 constraints ought not be the end of the world.
 *
 * XXX See MergeWithExistingConstraint too if you change this code.
 */
static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
{
    Relation catalog_relation;
    TupleDesc tuple_desc;
    SysScanDesc parent_scan;
    ScanKeyData parent_key;
    HeapTuple parent_tuple;

    catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
    tuple_desc = RelationGetDescr(catalog_relation);

    /* Outer loop scans through the parent's constraint definitions */
    ScanKeyInit(&parent_key,
        Anum_pg_constraint_conrelid,
        BTEqualStrategyNumber,
        F_OIDEQ,
        ObjectIdGetDatum(RelationGetRelid(parent_rel)));
    parent_scan = systable_beginscan(catalog_relation, ConstraintRelidIndexId, true, NULL, 1, &parent_key);

    while (HeapTupleIsValid(parent_tuple = systable_getnext(parent_scan))) {
        Form_pg_constraint parent_con = (Form_pg_constraint)GETSTRUCT(parent_tuple);
        SysScanDesc child_scan;
        ScanKeyData child_key;
        HeapTuple child_tuple;
        bool found = false;

        if (parent_con->contype != CONSTRAINT_CHECK)
            continue;

        /* if the parent's constraint is marked NO INHERIT, it's not inherited */
        if (parent_con->connoinherit)
            continue;

        /* Search for a child constraint matching this one */
        ScanKeyInit(&child_key,
            Anum_pg_constraint_conrelid,
            BTEqualStrategyNumber,
            F_OIDEQ,
            ObjectIdGetDatum(RelationGetRelid(child_rel)));
        child_scan = systable_beginscan(catalog_relation, ConstraintRelidIndexId, true, NULL, 1, &child_key);

        while (HeapTupleIsValid(child_tuple = systable_getnext(child_scan))) {
            Form_pg_constraint child_con = (Form_pg_constraint)GETSTRUCT(child_tuple);
            HeapTuple child_copy;

            if (child_con->contype != CONSTRAINT_CHECK)
                continue;

            if (strcmp(NameStr(parent_con->conname), NameStr(child_con->conname)) != 0)
                continue;

            if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
                ereport(ERROR,
                    (errcode(ERRCODE_DATATYPE_MISMATCH),
                        errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
                            RelationGetRelationName(child_rel),
                            NameStr(parent_con->conname))));

            /* If the constraint is "no inherit" then cannot merge */
            if (child_con->connoinherit)
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                        errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
                            NameStr(child_con->conname),
                            RelationGetRelationName(child_rel))));

            /*
             * OK, bump the child constraint's inheritance count.  (If we fail
             * later on, this change will just roll back.)
             */
            child_copy = (HeapTuple)tableam_tops_copy_tuple(child_tuple);
            child_con = (Form_pg_constraint)GETSTRUCT(child_copy);
            child_con->coninhcount++;
            simple_heap_update(catalog_relation, &child_copy->t_self, child_copy);
            CatalogUpdateIndexes(catalog_relation, child_copy);
            tableam_tops_free_tuple(child_copy);

            found = true;
            break;
        }

        systable_endscan(child_scan);

        if (!found)
            ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
                    errmsg("child table is missing constraint \"%s\"", NameStr(parent_con->conname))));
    }

    systable_endscan(parent_scan);
    heap_close(catalog_relation, RowExclusiveLock);
}

/*
 * ALTER TABLE NO INHERIT
 *
 * Drop a parent from the child's parents. This just adjusts the attinhcount
 * and attislocal of the columns and removes the pg_inherit and pg_depend
 * entries.
 *
 * If attinhcount goes to 0 then attislocal gets set to true. If it goes back
 * up attislocal stays true, which means if a child is ever removed from a
 * parent then its columns will never be automatically dropped which may
 * surprise. But at least we'll never surprise by dropping columns someone
 * isn't expecting to be dropped which would actually mean data loss.
 *
 * coninhcount and conislocal for inherited constraints are adjusted in
 * exactly the same way.
 */
static ObjectAddress ATExecDropInherit(Relation rel, RangeVar* parent, LOCKMODE lockmode)
{
    Relation parent_rel;
    Oid      parent_oid;
    Relation catalogRelation;
    SysScanDesc scan;
    ScanKeyData key[3];
    HeapTuple inheritsTuple, attributeTuple, constraintTuple;
    List* connames = NIL;
    bool found = false;
    ObjectAddress address;

    /*
     * AccessShareLock on the parent is probably enough, seeing that DROP
     * TABLE doesn't lock parent tables at all.  We need some lock since we'll
     * be inspecting the parent's schema.
     */
    parent_rel = heap_openrv(parent, AccessShareLock);

    /*
     * We don't bother to check ownership of the parent table --- ownership of
     * the child is presumed enough rights.
     */
    /*
     * Find and destroy the pg_inherits entry linking the two, or error out if
     * there is none.
     */
    catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
    ScanKeyInit(
        &key[0], Anum_pg_inherits_inhrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(rel)));
    scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true, NULL, 1, key);

    while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan))) {
        Oid inhparent;

        inhparent = ((Form_pg_inherits)GETSTRUCT(inheritsTuple))->inhparent;
        if (inhparent == RelationGetRelid(parent_rel)) {
            simple_heap_delete(catalogRelation, &inheritsTuple->t_self);
            found = true;
            break;
        }
    }
    parent_oid = RelationGetRelid(parent_rel);
    systable_endscan(scan);
    heap_close(catalogRelation, RowExclusiveLock);

    if (!found)
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_TABLE),
                errmsg("relation \"%s\" is not a parent of relation \"%s\"",
                    RelationGetRelationName(parent_rel),
                    RelationGetRelationName(rel))));

    /*
     * Search through child columns looking for ones matching parent rel
     */
    catalogRelation = heap_open(AttributeRelationId, RowExclusiveLock);
    ScanKeyInit(
        &key[0], Anum_pg_attribute_attrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(rel)));
    scan = systable_beginscan(catalogRelation, AttributeRelidNumIndexId, true, NULL, 1, key);
    while (HeapTupleIsValid(attributeTuple = systable_getnext(scan))) {
        Form_pg_attribute att = (Form_pg_attribute)GETSTRUCT(attributeTuple);

        /* Ignore if dropped or not inherited */
        if (att->attisdropped)
            continue;
        if (att->attinhcount <= 0)
            continue;

        if (SearchSysCacheExistsAttName(RelationGetRelid(parent_rel), NameStr(att->attname))) {
            /* Decrement inhcount and possibly set islocal to true */
            HeapTuple copyTuple = (HeapTuple)tableam_tops_copy_tuple(attributeTuple);
            Form_pg_attribute copy_att = (Form_pg_attribute)GETSTRUCT(copyTuple);

            copy_att->attinhcount--;
            if (copy_att->attinhcount == 0)
                copy_att->attislocal = true;

            simple_heap_update(catalogRelation, &copyTuple->t_self, copyTuple);
            CatalogUpdateIndexes(catalogRelation, copyTuple);
            tableam_tops_free_tuple(copyTuple);
        }
    }
    systable_endscan(scan);
    heap_close(catalogRelation, RowExclusiveLock);

    /*
     * Likewise, find inherited check constraints and disinherit them. To do
     * this, we first need a list of the names of the parent's check
     * constraints.  (We cheat a bit by only checking for name matches,
     * assuming that the expressions will match.)
     */
    catalogRelation = heap_open(ConstraintRelationId, RowExclusiveLock);
    ScanKeyInit(&key[0],
        Anum_pg_constraint_conrelid,
        BTEqualStrategyNumber,
        F_OIDEQ,
        ObjectIdGetDatum(RelationGetRelid(parent_rel)));
    scan = systable_beginscan(catalogRelation, ConstraintRelidIndexId, true, NULL, 1, key);

    connames = NIL;

    while (HeapTupleIsValid(constraintTuple = systable_getnext(scan))) {
        Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(constraintTuple);

        if (con->contype == CONSTRAINT_CHECK)
            connames = lappend(connames, pstrdup(NameStr(con->conname)));
    }

    systable_endscan(scan);

    /* Now scan the child's constraints */
    ScanKeyInit(
        &key[0], Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(rel)));
    scan = systable_beginscan(catalogRelation, ConstraintRelidIndexId, true, NULL, 1, key);

    while (HeapTupleIsValid(constraintTuple = systable_getnext(scan))) {
        Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(constraintTuple);

        if (con->contype != CONSTRAINT_CHECK)
            continue;

        bool match = false;
        ListCell* lc = NULL;

        foreach (lc, connames) {
            if (strcmp(NameStr(con->conname), (char*)lfirst(lc)) == 0) {
                match = true;
                break;
            }
        }

        if (match) {
            /* Decrement inhcount and possibly set islocal to true */
            HeapTuple copyTuple = (HeapTuple) tableam_tops_copy_tuple(constraintTuple);
            Form_pg_constraint copy_con = (Form_pg_constraint)GETSTRUCT(copyTuple);

            if (copy_con->coninhcount <= 0) /* shouldn't happen */
                ereport(ERROR,
                    (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                        errmsg("relation %u has non-inherited constraint \"%s\"",
                            RelationGetRelid(rel),
                            NameStr(copy_con->conname))));

            copy_con->coninhcount--;
            if (copy_con->coninhcount == 0)
                copy_con->conislocal = true;

            simple_heap_update(catalogRelation, &copyTuple->t_self, copyTuple);
            CatalogUpdateIndexes(catalogRelation, copyTuple);
            tableam_tops_free_tuple(copyTuple);
        }
    }
    list_free_deep(connames);
    systable_endscan(scan);
    heap_close(catalogRelation, RowExclusiveLock);

    drop_parent_dependency(RelationGetRelid(rel), RelationRelationId, RelationGetRelid(parent_rel));

    /* keep our lock on the parent relation until commit */
    heap_close(parent_rel, NoLock);

    ObjectAddressSet(address, RelationRelationId, parent_oid);

    return address;
}

/*
 * Drop the dependency created by StoreCatalogInheritance1 (CREATE TABLE
 * INHERITS/ALTER TABLE INHERIT -- refclassid will be RelationRelationId) or
 * heap_create_with_catalog (CREATE TABLE OF/ALTER TABLE OF -- refclassid will
 * be TypeRelationId).	There's no convenient way to do this, so go trawling
 * through pg_depend.
 */
static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid)
{
    Relation catalogRelation;
    SysScanDesc scan;
    ScanKeyData key[3];
    HeapTuple depTuple;

    catalogRelation = heap_open(DependRelationId, RowExclusiveLock);

    ScanKeyInit(&key[0], Anum_pg_depend_classid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationRelationId));
    ScanKeyInit(&key[1], Anum_pg_depend_objid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relid));
    ScanKeyInit(&key[2], Anum_pg_depend_objsubid, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(0));

    scan = systable_beginscan(catalogRelation, DependDependerIndexId, true, NULL, 3, key);

    while (HeapTupleIsValid(depTuple = systable_getnext(scan))) {
        Form_pg_depend dep = (Form_pg_depend)GETSTRUCT(depTuple);

        if (dep->refclassid == refclassid && dep->refobjid == refobjid && dep->refobjsubid == 0 &&
            dep->deptype == DEPENDENCY_NORMAL)
            simple_heap_delete(catalogRelation, &depTuple->t_self);
    }

    systable_endscan(scan);
    heap_close(catalogRelation, RowExclusiveLock);
}

/*
 * ALTER TABLE OF
 *
 * Attach a table to a composite type, as though it had been created with CREATE
 * TABLE OF.  All attname, atttypid, atttypmod and attcollation must match.  The
 * subject table must not have inheritance parents.  These restrictions ensure
 * that you cannot create a configuration impossible with CREATE TABLE OF alone.
 */
static ObjectAddress ATExecAddOf(Relation rel, const TypeName* ofTypename, LOCKMODE lockmode)
{
    Oid relid = RelationGetRelid(rel);
    Type typetuple;
    Oid typid;
    Relation inheritsRelation, relationRelation;
    SysScanDesc scan;
    ScanKeyData key;
    AttrNumber table_attno, type_attno;
    TupleDesc typeTupleDesc, tableTupleDesc;
    ObjectAddress tableobj, typeobj;
    HeapTuple classtuple;

    if (RELATION_IS_PARTITIONED(rel)) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("relation \"%s\" is a partitioned table", RelationGetRelationName(rel)),
                errdetail("can not add of_type for partitioned table")));
    }

    /* Validate the type. */
    typetuple = typenameType(NULL, ofTypename, NULL);
    check_of_type(typetuple);
    typid = HeapTupleGetOid(typetuple);

    /* Fail if the table has any inheritance parents. */
    inheritsRelation = heap_open(InheritsRelationId, AccessShareLock);
    ScanKeyInit(&key, Anum_pg_inherits_inhrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relid));
    scan = systable_beginscan(inheritsRelation, InheritsRelidSeqnoIndexId, true, NULL, 1, &key);
    if (HeapTupleIsValid(systable_getnext(scan)))
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("typed tables cannot inherit")));
    systable_endscan(scan);
    heap_close(inheritsRelation, AccessShareLock);

    /*
     * Check the tuple descriptors for compatibility.  Unlike inheritance, we
     * require that the order also match.  However, attnotnull need not match.
     * Also unlike inheritance, we do not require matching relhasoids.
     */
    typeTupleDesc = lookup_rowtype_tupdesc(typid, -1);
    tableTupleDesc = RelationGetDescr(rel);
    table_attno = 1;
    for (type_attno = 1; type_attno <= typeTupleDesc->natts; type_attno++) {
        Form_pg_attribute type_attr, table_attr;
        const char* type_attname = NULL;
        const char* table_attname = NULL;

        /* Get the next non-dropped type attribute. */
        type_attr = &typeTupleDesc->attrs[type_attno - 1];
        if (type_attr->attisdropped)
            continue;
        type_attname = NameStr(type_attr->attname);

        /* Get the next non-dropped table attribute. */
        do {
            if (table_attno > tableTupleDesc->natts)
                ereport(ERROR,
                    (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("table is missing column \"%s\"", type_attname)));
            table_attr = &tableTupleDesc->attrs[table_attno++ - 1];
        } while (table_attr->attisdropped);
        table_attname = NameStr(table_attr->attname);

        /* Compare name. */
        if (strncmp(table_attname, type_attname, NAMEDATALEN) != 0)
            ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
                    errmsg("table has column \"%s\" where type requires \"%s\"", table_attname, type_attname)));

        /* Compare type. */
        if (table_attr->atttypid != type_attr->atttypid || table_attr->atttypmod != type_attr->atttypmod ||
            table_attr->attcollation != type_attr->attcollation)
            ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
                    errmsg("table \"%s\" has different type for column \"%s\"",
                        RelationGetRelationName(rel),
                        type_attname)));
    }
    DecrTupleDescRefCount(typeTupleDesc);

    /* Any remaining columns at the end of the table had better be dropped. */
    for (; table_attno <= tableTupleDesc->natts; table_attno++) {
        Form_pg_attribute table_attr = &tableTupleDesc->attrs[table_attno - 1];

        if (!table_attr->attisdropped)
            ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
                    errmsg("table has extra column \"%s\"", NameStr(table_attr->attname))));
    }

    /* If the table was already typed, drop the existing dependency. */
    if (rel->rd_rel->reloftype)
        drop_parent_dependency(relid, TypeRelationId, rel->rd_rel->reloftype);

    /* Record a dependency on the new type. */
    tableobj.classId = RelationRelationId;
    tableobj.objectId = relid;
    tableobj.objectSubId = 0;
    typeobj.classId = TypeRelationId;
    typeobj.objectId = typid;
    typeobj.objectSubId = 0;
    recordDependencyOn(&tableobj, &typeobj, DEPENDENCY_NORMAL);

    /* Update pg_class.reloftype */
    relationRelation = heap_open(RelationRelationId, RowExclusiveLock);
    classtuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
    if (!HeapTupleIsValid(classtuple)) {
        ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", relid)));
    }
    ((Form_pg_class)GETSTRUCT(classtuple))->reloftype = typid;
    simple_heap_update(relationRelation, &classtuple->t_self, classtuple);
    CatalogUpdateIndexes(relationRelation, classtuple);
    tableam_tops_free_tuple(classtuple);
    heap_close(relationRelation, RowExclusiveLock);

    ReleaseSysCache(typetuple);

    return typeobj;
}

/*
 * ALTER TABLE NOT OF
 *
 * Detach a typed table from its originating type.	Just clear reloftype and
 * remove the dependency.
 */
static void ATExecDropOf(Relation rel, LOCKMODE lockmode)
{
    Oid relid = RelationGetRelid(rel);
    Relation relationRelation;
    HeapTuple tuple;

    if (!OidIsValid(rel->rd_rel->reloftype))
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a typed table", RelationGetRelationName(rel))));

    if (RELATION_IS_PARTITIONED(rel)) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("relation \"%s\" is a partitioned table", RelationGetRelationName(rel)),
                errdetail("drop of_type for partitioned table, this is a could not happening event")));
    }

    /*
     * We don't bother to check ownership of the type --- ownership of the
     * table is presumed enough rights.  No lock required on the type, either.
     */
    drop_parent_dependency(relid, TypeRelationId, rel->rd_rel->reloftype);

    /* Clear pg_class.reloftype */
    relationRelation = heap_open(RelationRelationId, RowExclusiveLock);
    tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
    if (!HeapTupleIsValid(tuple)) {
        ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", relid)));
    }

    ((Form_pg_class)GETSTRUCT(tuple))->reloftype = InvalidOid;
    simple_heap_update(relationRelation, &tuple->t_self, tuple);
    CatalogUpdateIndexes(relationRelation, tuple);
    tableam_tops_free_tuple(tuple);
    heap_close(relationRelation, RowExclusiveLock);
}
/*
 * relation_mark_replica_identity: Update a table's replica identity
 *
 * Iff ri_type = REPLICA_IDENTITY_INDEX, indexOid must be the Oid of a suitable
 * index. Otherwise, it should be InvalidOid.
 */
static void relation_mark_replica_identity(Relation rel, char ri_type, Oid indexOid, bool is_internal)
{
    Relation pg_index;
    Relation pg_class;
    HeapTuple pg_class_tuple;
    HeapTuple pg_index_tuple;
    Form_pg_index pg_index_form;
    ListCell* index = NULL;
    bool isNull = false;
    Datum replident;
    Datum indisreplident;
    char relreplident = '\0';
    bool isreplident = false;

    /*
     * Check whether relreplident has changed, and update it if so.
     */
    pg_class = heap_open(RelationRelationId, RowExclusiveLock);
    pg_class_tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(RelationGetRelid(rel)));
    if (!HeapTupleIsValid(pg_class_tuple))
        ereport(ERROR,
            ((errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                errmsg("cache lookup failed for relation \"%s\"", RelationGetRelationName(rel)))));
    replident = heap_getattr(pg_class_tuple, Anum_pg_class_relreplident, RelationGetDescr(pg_class), &isNull);
    if (!isNull)
        relreplident = CharGetDatum(replident);

    if (relreplident == '\0' || relreplident != ri_type) {
        HeapTuple newctup = NULL;
        Datum values[Natts_pg_class];
        bool nulls[Natts_pg_class];
        bool replaces[Natts_pg_class];
        errno_t rc;
        rc = memset_s(values, sizeof(values), 0, sizeof(values));
        securec_check(rc, "\0", "\0");
        rc = memset_s(nulls, sizeof(nulls), false, sizeof(nulls));
        securec_check(rc, "\0", "\0");
        rc = memset_s(replaces, sizeof(replaces), false, sizeof(replaces));
        securec_check(rc, "\0", "\0");

        replaces[Anum_pg_class_relreplident - 1] = true;
        values[Anum_pg_class_relreplident - 1] = ri_type;

        newctup = (HeapTuple) tableam_tops_modify_tuple(pg_class_tuple, RelationGetDescr(pg_class), values, nulls, replaces);

        simple_heap_update(pg_class, &pg_class_tuple->t_self, newctup);
        CatalogUpdateIndexes(pg_class, newctup);
        tableam_tops_free_tuple(newctup);
    }
    heap_close(pg_class, RowExclusiveLock);
    tableam_tops_free_tuple(pg_class_tuple);

    /*
     * Check whether the correct index is marked indisreplident; if so, we're
     * done.
     */
    if (OidIsValid(indexOid)) {
        Assert(ri_type == REPLICA_IDENTITY_INDEX);

        Relation reltmp = heap_open(IndexRelationId, AccessShareLock);
        pg_index_tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexOid));
        if (!HeapTupleIsValid(pg_index_tuple)) {
            ereport(
                ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for index %u", indexOid)));
        }
        pg_index_form = (Form_pg_index)GETSTRUCT(pg_index_tuple);
        isNull = false;
        isreplident = false;
        indisreplident = heap_getattr(pg_index_tuple, Anum_pg_index_indisreplident, RelationGetDescr(reltmp), &isNull);
        heap_close(reltmp, AccessShareLock);

        if (!isNull)
            isreplident = BoolGetDatum(indisreplident);

        if (isreplident) {
            ReleaseSysCache(pg_index_tuple);
            return;
        }
        ReleaseSysCache(pg_index_tuple);
    }

    /*
     * Clear the indisreplident flag from any index that had it previously, and
     * set it for any index that should have it now.
     */
    pg_index = heap_open(IndexRelationId, RowExclusiveLock);
    foreach (index, RelationGetIndexList(rel)) {
        Oid thisIndexOid = lfirst_oid(index);
        bool dirty = false;

        pg_index_tuple = SearchSysCacheCopy1(INDEXRELID, ObjectIdGetDatum(thisIndexOid));
        if (!HeapTupleIsValid(pg_index_tuple)) {
            ereport(ERROR,
                (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for index %u", thisIndexOid)));
        }
        pg_index_form = (Form_pg_index)GETSTRUCT(pg_index_tuple);
        isNull = false;
        isreplident = false;
        indisreplident =
            heap_getattr(pg_index_tuple, Anum_pg_index_indisreplident, RelationGetDescr(pg_index), &isNull);
        if (!isNull)
            isreplident = BoolGetDatum(indisreplident);
        /*
         * Unset the bit if set.  We know it's wrong because we checked this
         * earlier.
         */
        if (isreplident) {
            dirty = true;
            isreplident = false;
        } else if (thisIndexOid == indexOid) {
            dirty = true;
            isreplident = true;
        }

        if (dirty) {
            HeapTuple newitup = NULL;
            Datum values[Natts_pg_index];
            bool nulls[Natts_pg_index];
            bool replaces[Natts_pg_index];
            errno_t rc;
            rc = memset_s(values, sizeof(values), 0, sizeof(values));
            securec_check(rc, "\0", "\0");
            rc = memset_s(nulls, sizeof(nulls), false, sizeof(nulls));
            securec_check(rc, "\0", "\0");
            rc = memset_s(replaces, sizeof(replaces), false, sizeof(replaces));
            securec_check(rc, "\0", "\0");

            replaces[Anum_pg_index_indisreplident - 1] = true;
            values[Anum_pg_index_indisreplident - 1] = isreplident;
            newitup = (HeapTuple) tableam_tops_modify_tuple(pg_index_tuple, RelationGetDescr(pg_index), values, nulls, replaces);
            simple_heap_update(pg_index, &pg_index_tuple->t_self, newitup);
            CatalogUpdateIndexes(pg_index, newitup);
            tableam_tops_free_tuple(newitup);
        }
        tableam_tops_free_tuple(pg_index_tuple);
    }

    heap_close(pg_index, RowExclusiveLock);
}

/*
 * ALTER TABLE <name> REPLICA IDENTITY ...
 */
static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt* stmt, LOCKMODE lockmode)
{
    Oid indexOid;
    Relation indexRel;
    int key;

    if (stmt->identity_type == REPLICA_IDENTITY_DEFAULT) {
        relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
        return;
    } else if (stmt->identity_type == REPLICA_IDENTITY_FULL) {
        relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
        return;
    } else if (stmt->identity_type == REPLICA_IDENTITY_NOTHING) {
        relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
        return;
    } else if (stmt->identity_type == REPLICA_IDENTITY_INDEX) {
        /* fallthrough */;
    } else
        ereport(ERROR,
            ((errcode(ERRCODE_UNEXPECTED_NODE_STATE),
                errmsg("unexpected identity type %u", (uint)stmt->identity_type))));

    /* Check that the index exists */
    indexOid = get_relname_relid(stmt->name, rel->rd_rel->relnamespace);
    if (!OidIsValid(indexOid))
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_OBJECT),
                errmsg("index \"%s\" for table \"%s\" does not exist", stmt->name, RelationGetRelationName(rel))));

    indexRel = index_open(indexOid, ShareLock);

    /* Check that the index is on the relation we're altering. */
    if (indexRel->rd_index == NULL || indexRel->rd_index->indrelid != RelationGetRelid(rel))
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("\"%s\" is not an index for table \"%s\"",
                    RelationGetRelationName(indexRel),
                    RelationGetRelationName(rel))));
    /* The AM must support uniqueness, and the index must in fact be unique. */
    if (!indexRel->rd_am->amcanunique || !indexRel->rd_index->indisunique)
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("cannot use non-unique index \"%s\" as replica identity", RelationGetRelationName(indexRel))));
    /* Deferred indexes are not guaranteed to be always unique. */
    if (!indexRel->rd_index->indimmediate)
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg(
                    "cannot use non-immediate index \"%s\" as replica identity", RelationGetRelationName(indexRel))));
    /* Expression indexes aren't supported. */
    if (RelationGetIndexExpressions(indexRel) != NIL)
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("cannot use expression index \"%s\" as replica identity", RelationGetRelationName(indexRel))));
    /* Predicate indexes aren't supported. */
    if (RelationGetIndexPredicate(indexRel) != NIL)
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("cannot use partial index \"%s\" as replica identity", RelationGetRelationName(indexRel))));
    /* And neither are invalid indexes. */
    if (!IndexIsValid(indexRel->rd_index))
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("cannot use invalid index \"%s\" as replica identity", RelationGetRelationName(indexRel))));

    /* Check index for nullable columns. */
    for (key = 0; key < IndexRelationGetNumberOfKeyAttributes(indexRel); key++) {
        int16 attno = indexRel->rd_index->indkey.values[key];
        Form_pg_attribute attr;

        /* Of the system columns, only oid is indexable. */
        if (attno <= 0 && attno != ObjectIdAttributeNumber)
            ereport(ERROR,
                ((errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                    errmsg("internal column %d in unique index \"%s\"", attno, RelationGetRelationName(indexRel)))));

        attr = &rel->rd_att->attrs[attno - 1];
        if (!attr->attnotnull)
            ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                    errmsg("index \"%s\" cannot be used as replica identity because column \"%s\" is nullable",
                        RelationGetRelationName(indexRel),
                        NameStr(attr->attname))));
    }

    /* This index is suitable for use as a replica identity. Mark it. */
    relation_mark_replica_identity(rel, stmt->identity_type, indexOid, true);

    index_close(indexRel, NoLock);
}

/*
 * ALTER FOREIGN TABLE <name> OPTIONS (...)
 */
static void ATExecGenericOptions(Relation rel, List* options)
{
    Relation ftrel;
    ForeignServer* server = NULL;
    ForeignDataWrapper* fdw = NULL;
    HeapTuple tuple;
    bool isnull = false;
    Datum repl_val[Natts_pg_foreign_table];
    bool repl_null[Natts_pg_foreign_table];
    bool repl_repl[Natts_pg_foreign_table];
    Datum datum;
    Form_pg_foreign_table tableform;
    errno_t rc;
    DefElemAction actions;
    double num_rows = 0.0;
    char* total_rows = NULL;

    if (options == NIL)
        return;

    ftrel = heap_open(ForeignTableRelationId, RowExclusiveLock);

    tuple = SearchSysCacheCopy1(FOREIGNTABLEREL, rel->rd_id);
    if (!HeapTupleIsValid(tuple))
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_OBJECT),
                errmsg("foreign table \"%s\" does not exist", RelationGetRelationName(rel))));
    tableform = (Form_pg_foreign_table)GETSTRUCT(tuple);
    server = GetForeignServer(tableform->ftserver);
    fdw = GetForeignDataWrapper(server->fdwid);

    rc = memset_s(repl_val, sizeof(repl_val), 0, sizeof(repl_val));
    securec_check(rc, "\0", "\0");
    rc = memset_s(repl_null, sizeof(repl_null), false, sizeof(repl_null));
    securec_check(rc, "\0", "\0");
    rc = memset_s(repl_repl, sizeof(repl_repl), false, sizeof(repl_repl));
    securec_check(rc, "\0", "\0");

    /* Extract the current options */
    datum = SysCacheGetAttr(FOREIGNTABLEREL, tuple, Anum_pg_foreign_table_ftoptions, &isnull);
    if (isnull)
        datum = PointerGetDatum(NULL);

    /* add write_only to options */
    if (tableform->ftwriteonly) {
        List* resultOptions = untransformRelOptions(datum);
        Node* val = (Node*)makeString(pstrdup("true"));
        resultOptions = lappend(resultOptions, makeDefElem(pstrdup("write_only"), val));
        datum = optionListToArray(resultOptions);
        list_free_ext(resultOptions);
    }

    options = regularizeObsLocationInfo(options);

    /*
     * we insert type information into option in order to distinguish server type
     * hdfs_fdw_validator function.
     */
    char* optValue = getServerOptionValue(server->serverid, "type");
    DefElem* defElem = NULL;
    if (NULL != optValue) {
        defElem = makeDefElem("type", (Node*)makeString(optValue));
        options = lappend(options, defElem);
    }

    /* Transform the options */
    datum = transformGenericOptions(ForeignTableRelationId, datum, options, fdw->fdwvalidator);
    // update obs foreign table options totalrows in pg_class
    //
    if (IS_PGXC_COORDINATOR) {
        /* As for DEFELEM_DROP status, we do not deal with toltal rows in pg_class. */
        actions = getFTAlterAction(options, "totalrows");
        if ((actions == DEFELEM_SET) || (actions == DEFELEM_ADD)) {
            total_rows = getFTOptionValue(options, "totalrows");
            num_rows = convertFTOptionValue(total_rows);
            updateTotalRows(rel->rd_id, num_rows);
        }
    }

    List* resultOptions = untransformRelOptions(datum);

    if (NULL != optValue) {
        options = list_delete(options, defElem);
        resultOptions = list_delete(resultOptions, defElem);
        pfree_ext(defElem);
    }

    /* remove write_only from datum */
    if (tableform->ftwriteonly) {
        bool found = false;
        resultOptions = FindOrRemoveForeignTableOption(resultOptions, "write_only", true, &found);
        Assert(found);
    }

    datum = optionListToArray(resultOptions);

    if (PointerIsValid(DatumGetPointer(datum)))
        repl_val[Anum_pg_foreign_table_ftoptions - 1] = datum;
    else
        repl_null[Anum_pg_foreign_table_ftoptions - 1] = true;

    repl_repl[Anum_pg_foreign_table_ftoptions - 1] = true;

    /* Everything looks good - update the tuple */
    tuple = (HeapTuple) tableam_tops_modify_tuple(tuple, RelationGetDescr(ftrel), repl_val, repl_null, repl_repl);

    simple_heap_update(ftrel, &tuple->t_self, tuple);
    CatalogUpdateIndexes(ftrel, tuple);

    /*
     * Invalidate relcache so that all sessions will refresh any cached plans
     * that might depend on the old options.
     */
    CacheInvalidateRelcache(rel);

    heap_close(ftrel, RowExclusiveLock);

    tableam_tops_free_tuple(tuple);
}

#ifdef ENABLE_HTAP
/*
 * ALTER TABLE <name> IMCSTORED(...)
 */
static void ATExecIMCSTORED(Relation rel, List* colList) 
{
    Oid relOid = RelationGetRelid(rel);
    int2vector* imcsAtts = NULL;
    int imcsNatts = 0;

    CheckImcstoreCacheReady();
    CheckForEnableImcs(rel, colList, imcsAtts, &imcsNatts);
    /* standbynode populate */
    if (IMCS_IS_PRIMARY_MODE) {
        AbortIfSinglePrimary();
        CreateImcsDescForPrimaryNode(rel, imcsAtts, imcsNatts);
        SendImcstoredRequest(relOid, InvalidOid, imcsAtts->values, imcsNatts, TYPE_IMCSTORED);
    } else {
        AlterTableEnableImcstore(rel, imcsAtts, imcsNatts);
    }
    pfree_ext(imcsAtts);
}

/*
 * ALTER TABLE <name> UNIMCSTORED
 */
static void ATExecUNIMCSTORED(Relation rel) 
{
    Oid relOid = RelationGetRelid(rel);

    CheckImcstoreCacheReady();
    if (!RelHasImcs(relOid) && !IMCU_CACHE->m_is_promote) {
        ereport(ERROR, (errmsg("rel not populated, no need to be unpopulate.")));
    }

    if (t_thrd.postmaster_cxt.HaShmData->current_mode == PRIMARY_MODE) {
        AbortIfSinglePrimary();
        SendUnImcstoredRequest(relOid, InvalidOid, TYPE_UNIMCSTORED);
    }
    AlterTableDisableImcstore(rel);
}

/*
 * ALTER TABLE <name> MODIFY PARTITION <part_name> IMCSTORED
 */
static void ATExecModifyPartitionIMCSTORED(Relation rel, const char* partName, List* colList)
{
    Oid partOid = InvalidOid;
    Oid relOid = RelationGetRelid(rel);
    int2vector* imcsAtts = NULL;
    int imcsNatts = 0;

    CheckImcstoreCacheReady();
    partOid = ImcsPartNameGetPartOid(relOid, partName);
    CheckForEnableImcs(rel, colList, imcsAtts, &imcsNatts, partOid);

    /* check if other partitions of partitioned table populated */
    if (IMCU_CACHE->GetImcsDesc(relOid) == NULL) {
        // first partitition to populate, create imcsdesc for partitioned table
        // In case of specify partition, different table may have different imcs cols,
        // do not remember imcs cols for partitioned table */
        IMCU_CACHE->CreateImcsDesc(rel, NULL, 0);
    }

    /* start populate partition */
    Partition part = partitionOpen(rel, partOid, AccessExclusiveLock);
    Relation partRel = partitionGetRelation(rel, part);
    PG_TRY();
    {
        /* populate on standbynode */
        if (t_thrd.postmaster_cxt.HaShmData->current_mode == PRIMARY_MODE) {
            AbortIfSinglePrimary();
            CreateImcsDescForPrimaryNode(partRel, imcsAtts, imcsNatts);
            SendImcstoredRequest(relOid, partOid, imcsAtts->values, imcsNatts, TYPE_PARTITION_IMCSTORED);
        } else {
            /* populate on currrent node */
            AlterTableEnableImcstore(partRel, imcsAtts, imcsNatts);
            IMCU_CACHE->UpdateImcsStatus(RelationGetRelid(rel), IMCS_POPULATE_COMPLETE);
        }
    }
    PG_CATCH();
    {
        releaseDummyRelation(&partRel);
        partitionClose(rel, part, AccessExclusiveLock);
        pfree_ext(imcsAtts);
        IMCU_CACHE->UpdateImcsStatus(RelationGetRelid(rel), IMCS_POPULATE_ERROR);
        PG_RE_THROW();
    }
    PG_END_TRY();
    releaseDummyRelation(&partRel);
    partitionClose(rel, part, AccessExclusiveLock);
    pfree_ext(imcsAtts);
}

/*
 * ALTER TABLE <name> MODIFY PARTITION <part_name> UNIMCSTORED
 */
static void ATExecModifyPartitionUNIMCSTORED(Relation rel, const char* partName)
{
    Oid partOid = InvalidOid;
    Oid relOid = RelationGetRelid(rel);

    CheckImcstoreCacheReady();
    partOid = ImcsPartNameGetPartOid(relOid, partName);
    if ((!RelHasImcs(relOid) || !RelHasImcs(partOid)) && !IMCU_CACHE->m_is_promote) {
        ereport(ERROR, (errmsg("partition %d of rel %d not populated", partOid, relOid)));
    }

    /* start unpopulate partition */
    Partition part = partitionOpen(rel, partOid, AccessExclusiveLock);
    Relation partRel = partitionGetRelation(rel, part);
    PG_TRY();
    {
        /* unpopulate partition on standby node */
        if (t_thrd.postmaster_cxt.HaShmData->current_mode == PRIMARY_MODE) {
            AbortIfSinglePrimary();
            SendUnImcstoredRequest(relOid, partOid, TYPE_PARTITION_UNIMCSTORED);
        }
        /* unpopulate partition on current node */
        AlterTableDisableImcstore(partRel);
        /* drop imcs for rel if all partition unpopulated */
        DropImcsForPartitionedRelIfNeed(rel);
    }
    PG_CATCH();
    {
        releaseDummyRelation(&partRel);
        partitionClose(rel, part, AccessExclusiveLock);
    }
    PG_END_TRY();
    releaseDummyRelation(&partRel);
    partitionClose(rel, part, AccessExclusiveLock);
}
#endif

#ifdef PGXC
/*
 * ALTER TABLE <name> DISTRIBUTE BY ...
 */
static void AtExecDistributeBy(Relation rel, DistributeBy* options)
{
    Oid relid;
    char locatortype;
    int hashalgorithm = 0;
    int hashbuckets = 0;
    AttrNumber* attnum = NULL;
    int distributeKeyNum = 0;

    /* Nothing to do on Datanodes */
    if (IS_PGXC_DATANODE || options == NULL)
        return;

    relid = RelationGetRelid(rel);

    if (options->colname) {
        distributeKeyNum = list_length(options->colname);
        attnum = (int2*)palloc(distributeKeyNum * sizeof(AttrNumber));
    } else {
        distributeKeyNum = 1;
        attnum = (int2*)palloc(1 * sizeof(AttrNumber));
    }

    /* Get necessary distribution information */
    GetRelationDistributionItems(
        relid, options, RelationGetDescr(rel), &locatortype, &hashalgorithm, &hashbuckets, attnum);

    /*
     * It is not checked if the distribution type list is the same as the old one,
     * user might define a different sub-cluster at the same time.
     */
    /* Update pgxc_class entry */
    PgxcClassAlter(relid,
        locatortype,
        attnum,
        distributeKeyNum,
        hashalgorithm,
        hashbuckets,
        0,
        NULL,
        '\0',
        PGXC_CLASS_ALTER_DISTRIBUTION,
        NULL);
    pfree_ext(attnum);

    /* Make the additional catalog changes visible */
    CommandCounterIncrement();
}

/*
 * ALTER TABLE <name> TO [ NODE nodelist | GROUP groupname ]
 */
static void AtExecSubCluster(Relation rel, PGXCSubCluster* options)
{
    Oid* nodeoids = NULL;
    int numnodes;
    ListCell* lc = NULL;
    char* group_name = NULL;

    if (!u_sess->attr.attr_sql.enable_cluster_resize) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                (errmsg("The alter table to group(or node) can be used only for expansion."))));
    }

    /* Nothing to do on Datanodes */
    if (IS_PGXC_DATANODE || options == NULL)
        return;

    /* Get node group name for ALTER TABLE ... TO Group */
    if (options->clustertype == SUBCLUSTER_GROUP) {
        Assert(list_length(options->members) == 1);

        foreach (lc, options->members) {
            group_name = strVal(lfirst(lc));
        }
    }

    /*
     * It is not checked if the new subcluster list is the same as the old one,
     * user might define a different distribution type.
     */
    /* Obtain new node information */
    nodeoids = GetRelationDistributionNodes(options, &numnodes);

    /* Update pgxc_class entry */
    PgxcClassAlter(
        RelationGetRelid(rel), '\0', NULL, 0, 0, 0, numnodes, nodeoids, 'y', PGXC_CLASS_ALTER_NODES, group_name);

    /* Make the additional catalog changes visible */
    CommandCounterIncrement();
}

/* 
 * copy tabOid2 slice tuples (info from slicelist) to tabOid1 (destination)
 */
static void AtExecCopySlice(CatCList* sliceList, Oid tabOid, Relation pgxcSliceRel)
{
    HeapTuple oldTup, newTup;
    Datum values[Natts_pgxc_slice] = {0};
    bool nulls[Natts_pgxc_slice] = {false};
    bool replaces[Natts_pgxc_slice] = {false};
    
    for (int i = 0; i < sliceList->n_members; i++) {
        oldTup = t_thrd.lsc_cxt.FetchTupleFromCatCList(sliceList, i);
        bool isnull = false;
        Datum val = fastgetattr(oldTup, Anum_pgxc_slice_type, RelationGetDescr(pgxcSliceRel), &isnull);
        if (DatumGetChar(val) == PGXC_SLICE_TYPE_TABLE) {
            continue;
        }
        values[Anum_pgxc_slice_relid - 1] = tabOid;
        replaces[Anum_pgxc_slice_relid - 1] = true;
        newTup = heap_modify_tuple(oldTup, RelationGetDescr(pgxcSliceRel), values, nulls, replaces);
        (void)simple_heap_insert(pgxcSliceRel, newTup);
        CatalogUpdateIndexes(pgxcSliceRel, newTup);
        heap_freetuple_ext(newTup);
    }
}
/*
 * ALTER TABLE <tablename1> UPDATE SLICE LIKE <tablename2>
 * Update pgxc_slice entry only used for range/list table redistribution
 */
static void AtExecUpdateSliceLike(Relation rel, const RangeVar* refTableName)
{
    int i;
    HeapTuple oldTup;
    Relation pgxcSliceRel;
    Relation invalRel;
    Oid tabOid1, tabOid2;
    CatCList* sliceList1 = NULL;
    CatCList* sliceList2 = NULL;
    /* Nothing to do on Datanodes or input is null */
    if (IS_PGXC_DATANODE || rel == NULL || refTableName == NULL) {
        return;
    }
    /* 'Update slice like' can be only used by redistribution tool */
    if (!u_sess->attr.attr_sql.enable_cluster_resize) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Update slice like can be only used by redistribution tool.")));
    }
    /* 'Update slice like' should only be used for range/list table */
    if (rel->rd_locator_info != NULL && IsLocatorDistributedBySlice(rel->rd_locator_info->locatorType)) {
        tabOid1 = RelationGetRelid(rel);
        tabOid2 = RangeVarGetRelidExtended(refTableName, NoLock, true, false, false, true, NULL, NULL);
        invalRel = relation_open(tabOid1, AccessExclusiveLock);
        pgxcSliceRel = heap_open(PgxcSliceRelationId, RowExclusiveLock);
        sliceList1 = SearchSysCacheList2(PGXCSLICERELID, ObjectIdGetDatum(tabOid1), 
            CharGetDatum(PGXC_SLICE_TYPE_SLICE));
        sliceList2 = SearchSysCacheList2(PGXCSLICERELID, ObjectIdGetDatum(tabOid2),
            CharGetDatum(PGXC_SLICE_TYPE_SLICE));
        if (sliceList1->n_members == 0 || sliceList2->n_members == 0 ||
            sliceList1->n_members != sliceList2->n_members) {
            ReleaseSysCacheList(sliceList1);
            ReleaseSysCacheList(sliceList2);
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                    errmsg("Both table should have the same slice count.")));
        }
        /* drop tabOid1 slice tuples except table entry tuple */
        for (i = 0; i < sliceList1->n_members; i++) {
            oldTup = t_thrd.lsc_cxt.FetchTupleFromCatCList(sliceList1, i);
            simple_heap_delete(pgxcSliceRel, &oldTup->t_self);
        }
        ReleaseSysCacheList(sliceList1);
        
        /* copy tabOid2 slice tuples to tabOid1 */
        AtExecCopySlice(sliceList2, tabOid1, pgxcSliceRel);
        ReleaseSysCacheList(sliceList2);
        heap_close(pgxcSliceRel, RowExclusiveLock);
        /* inval relation cache of tabOid1 */
        CacheInvalidateRelcache(invalRel);
        relation_close(invalRel, NoLock);
        CommandCounterIncrement();
    }
}

/*
 * ALTER TABLE <name> ADD NODE nodelist
 */
static void AtExecAddNode(Relation rel, List* options)
{
    Oid* add_oids = NULL;
    Oid* old_oids = NULL;
    int add_num, old_num;

    /* Nothing to do on Datanodes */
    if (IS_PGXC_DATANODE || options == NIL)
        return;

    /*
     * Build a new array of sorted node Oids given the list of name nodes
     * to be added.
     */
    add_oids = BuildRelationDistributionNodes(options, &add_num);

    /*
     * Then check if nodes to be added are not in existing node
     * list and build updated list of nodes.
     */
    old_num = get_pgxc_classnodes(RelationGetRelid(rel), &old_oids);

    /* Add elements to array */
    old_oids = add_node_list(old_oids, old_num, add_oids, add_num, &old_num);

    /* Sort once again the newly-created array of node Oids to maintain consistency */
    old_oids = SortRelationDistributionNodes(old_oids, old_num);

    /* Update pgxc_class entry */
    PgxcClassAlter(RelationGetRelid(rel), '\0', NULL, 0, 0, 0, old_num, old_oids, 'y', PGXC_CLASS_ALTER_NODES, NULL);

    /* Make the additional catalog changes visible */
    CommandCounterIncrement();
}

/*
 * ALTER TABLE <name> DELETE NODE nodelist
 */
static void AtExecDeleteNode(Relation rel, List* options)
{
    Oid* del_oids = NULL;
    Oid* old_oids = NULL;
    int del_num;
    int old_num;

    /* Nothing to do on Datanodes */
    if (IS_PGXC_DATANODE || options == NIL)
        return;

    /*
     * Build a new array of sorted node Oids given the list of name nodes
     * to be deleted.
     */
    del_oids = BuildRelationDistributionNodes(options, &del_num);

    /*
     * Check if nodes to be deleted are really included in existing
     * node list and get updated list of nodes.
     */
    old_num = get_pgxc_classnodes(RelationGetRelid(rel), &old_oids);

    /* Delete elements on array */
    old_oids = delete_node_list(old_oids, old_num, del_oids, del_num, &old_num);

    /* Update pgxc_class entry */
    PgxcClassAlter(RelationGetRelid(rel), '\0', NULL, 0, 0, 0, old_num, old_oids, '\0', PGXC_CLASS_ALTER_NODES, NULL);

    /* Make the additional catalog changes visible */
    CommandCounterIncrement();
}

static void ATCheckCmdGenericOptions(Relation rel, AlterTableCmd* cmd)
{
    DFSFileType fileType = DFS_INVALID;
    DFSFileType formatType = DFS_INVALID;
    Oid relid = InvalidOid;
    ForeignTable* table = NULL;
    ForeignServer* server = NULL;
    ForeignDataWrapper* wrapper = NULL;
    char* fdwnamestr = NULL;
    char* format = NULL;

    if (RelationIsForeignTable(rel) || RelationIsStream(rel)) {
        relid = RelationGetRelid(rel);
        table = GetForeignTable(relid);
        server = GetForeignServer(table->serverid);
        wrapper = GetForeignDataWrapper(server->fdwid);
        fdwnamestr = wrapper->fdwname;
        if (0 == strncasecmp(fdwnamestr, HDFS_FDW, NAMEDATALEN) ||
            0 == strncasecmp(fdwnamestr, DFS_FDW, NAMEDATALEN)) {
            formatType = getSetFormatNewFormat(cmd);
            if (RelationIsPartitioned(rel)) {
                if (DFS_TEXT == formatType || DFS_CSV == formatType || DFS_CARBONDATA == formatType) {
                    ereport(ERROR,
                        (errmsg("The obs foreign partition table cannot support on text, csv, carbondata "
                                "format.")));
                }
            }

            format = HdfsGetOptionValue(relid, OPTION_NAME_FORMAT);
            fileType = getFormatByName(format);
            if (checkColumnTypeIsBytea(rel) &&
                (DFS_CARBONDATA == fileType || DFS_TEXT == fileType || DFS_CSV == fileType)) {
                if (DFS_ORC == formatType || DFS_PARQUET == formatType) {
                    ereport(ERROR,
                        (errmsg("The obs foreign table has column type bytea, cannot support on orc format.")));
                }
            }
        }
    }

}

/*
 * ATCheckCmd
 *
 * Check ALTER TABLE restrictions in openGauss
 */
static void ATCheckCmd(Relation rel, AlterTableCmd* cmd)
{
    /* Do nothing in the case of a remote node */
    if (u_sess->attr.attr_sql.enable_parallel_ddl) {
        if (IS_PGXC_DATANODE)
            return;
    } else {
        if (IS_PGXC_DATANODE || IsConnFromCoord())
            return;
    }

    switch (cmd->subtype) {
        case AT_DropColumn: {
            AttrNumber attnum = get_attnum(RelationGetRelid(rel), cmd->name);

            /* Distribution column cannot be dropped */
            if (IsDistribColumn(RelationGetRelid(rel), attnum))
                ereport(
                    ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Distribution column cannot be dropped")));

            break;
        }
        case AT_DistributeBy:
            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Distribution mode cannot be altered")));
            break;
        case AT_SubCluster:
            break;
        case AT_GenericOptions: {
            ATCheckCmdGenericOptions(rel, cmd);
            break;
        }
        default:
            break;
    }
}

/*
 * Check the Valid of set format option to Foreign Partition Table
 */
static DFSFileType getSetFormatNewFormat(AlterTableCmd* cmd)
{
    if (nodeTag(cmd->def) == T_List) {
        List* defs = (List*)cmd->def;
        ListCell* lc = NULL;

        foreach (lc, defs) {
            DefElem* def = (DefElem*)lfirst(lc);
            if (def->defaction == DEFELEM_SET && pg_strcasecmp(def->defname, OPTION_NAME_FORMAT) == 0) {
                return getFormatByDefElem(def);
            }
        }
    }
    return DFS_INVALID;
}

static bool checkColumnTypeIsBytea(Relation rel)
{
    int tupIndex = 0;
    TupleDesc tupdesc = RelationGetDescr(rel);

    for (tupIndex = 0; tupIndex < tupdesc->natts; tupIndex++) {
        Form_pg_attribute attr = &tupdesc->attrs[tupIndex];
        if (BYTEAOID == attr->atttypid) {
            return true;
        }
    }
    return false;
}

/*
 * brief: Get format option.
 * input param @DefElem: the option information struct pointer
 */

static DFSFileType getFormatByDefElem(DefElem* opt)
{
    char* format = NULL;

    if (0 == pg_strcasecmp(opt->defname, OPTION_NAME_FORMAT)) {
        format = defGetString(opt);
        return getFormatByName(format);
    }
    return DFS_INVALID;
}

/*
 * brief: Get format option.
 * input param @format: the format(ORC/TEXT/CSV/Parquet/Carbondata)
 */
static DFSFileType getFormatByName(char* format)
{
    /*
     * Currently, the orc format is supported for hdfs foreign table, but
     * the parquet and csv formats will be supported in the future.
     */
    DFSFileType formatType = DFS_INVALID;

    if (format != NULL) {
        if (0 == pg_strcasecmp(format, DFS_FORMAT_ORC)) {
            formatType = DFS_ORC;
        } else if (0 == pg_strcasecmp(format, DFS_FORMAT_TEXT)) {
            formatType = DFS_TEXT;
        } else if (0 == pg_strcasecmp(format, DFS_FORMAT_CSV)) {
            formatType = DFS_CSV;
        } else if (0 == pg_strcasecmp(format, DFS_FORMAT_PARQUET)) {
            formatType = DFS_PARQUET;
        } else if (0 == pg_strcasecmp(format, DFS_FORMAT_CARBONDATA)) {
            formatType = DFS_CARBONDATA;
        } else {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmodule(MOD_DFS),
                    errmsg("Invalid option \"%s\"", format),
                    errhint("Valid options in this context are: orc, parquet, carbondata, text, csv")));
        }
    }

    return formatType;
}

/*
 * BuildRedistribCommands
 * Evaluate new and old distribution and build the list of operations
 * necessary to perform table redistribution.
 */
static RedistribState* BuildRedistribCommands(Oid relid, List* subCmds)
{
    RedistribState* redistribState = makeRedistribState(relid);
    RelationLocInfo* oldLocInfo = NULL;
    RelationLocInfo* newLocInfo = NULL; /* Former locator info */
    Relation rel;
    Oid* new_oid_array = NULL; /* Modified list of Oids */
    int new_num, i;            /* Modified number of Oids */
    ListCell* item = NULL;

    /* Get necessary information about relation */
    rel = relation_open(redistribState->relid, NoLock);
    oldLocInfo = RelationGetLocInfo(rel);
    Assert(oldLocInfo);

    /*
     * Get a copy of the locator information that will be modified by
     * successive ALTER TABLE commands.
     */
    newLocInfo = CopyRelationLocInfo(oldLocInfo);
    /* The node list of this locator information will be rebuilt after command scan */
    list_free_ext(newLocInfo->nodeList);
    list_free_ext(newLocInfo->partAttrNum);
    newLocInfo->nodeList = NULL;
    newLocInfo->partAttrNum = NULL;

    /* Get the list to be modified */
    new_num = get_pgxc_classnodes(RelationGetRelid(rel), &new_oid_array);

    foreach (item, subCmds) {
        AlterTableCmd* cmd = (AlterTableCmd*)lfirst(item);
        DistributeBy* distributeby = NULL;
        AttrNumber* attnum = NULL;
        int distributeKeyNum;

        switch (cmd->subtype) {
            case AT_DistributeBy:
                /*
                 * Get necessary distribution information and update to new
                 * distribution type.
                 */
                distributeby = (DistributeBy*)cmd->def;
                if (distributeby != NULL && distributeby->colname != NIL) {
                    distributeKeyNum = list_length(distributeby->colname);
                    attnum = (AttrNumber*)palloc(distributeKeyNum * sizeof(AttrNumber));
                } else {
                    distributeKeyNum = 1;
                    attnum = (AttrNumber*)palloc(1 * sizeof(AttrNumber));
                }
                GetRelationDistributionItems(redistribState->relid,
                    (DistributeBy*)cmd->def,
                    RelationGetDescr(rel),
                    &(newLocInfo->locatorType),
                    NULL,
                    NULL,
                    attnum);
                for (i = 0; i < distributeKeyNum; i++)
                    newLocInfo->partAttrNum = lappend_int(newLocInfo->partAttrNum, attnum[i]);
                pfree_ext(attnum);
                break;
            case AT_SubCluster:
                /* Update new list of nodes */
                new_oid_array = GetRelationDistributionNodes((PGXCSubCluster*)cmd->def, &new_num);
                break;
            case AT_AddNodeList: {
                Oid* add_oids = NULL;
                int add_num;
                add_oids = BuildRelationDistributionNodes((List*)cmd->def, &add_num);
                /* Add elements to array */
                new_oid_array = add_node_list(new_oid_array, new_num, add_oids, add_num, &new_num);
            } break;
            case AT_DeleteNodeList: {
                Oid* del_oids = NULL;
                int del_num;
                del_oids = BuildRelationDistributionNodes((List*)cmd->def, &del_num);
                /* Delete elements from array */
                new_oid_array = delete_node_list(new_oid_array, new_num, del_oids, del_num, &new_num);
            } break;
            case AT_UpdateSliceLike: {
                /* 
                 * Update slice like the temptable in pgxc_slice.
                 * Nothing need to do here, because 'update slice' only follows
                 * 'add/delete node' when redistribution is working,
                 * and the other preparations have been done by 'add/delete node'.
                 */
            } break;
            default:
                Assert(0); /* Should not happen */
        }
    }

    /* Build relation node list for new locator info */
    for (i = 0; i < new_num; i++)
        newLocInfo->nodeList =
            lappend_int(newLocInfo->nodeList, PGXCNodeGetNodeId(new_oid_array[i], PGXC_NODE_DATANODE));

    /* Build the command tree for table redistribution */
    PGXCRedistribCreateCommandList(redistribState, newLocInfo);

    if (ISMATMAP(rel->rd_rel->relname.data)) {
        FreeRelationLocInfo(newLocInfo);
        pfree_ext(new_oid_array);
        relation_close(rel, NoLock);

        return redistribState;
    }

    /*
     * Using the new locator info already available, check if constraints on
     * relation are compatible with the new distribution.
     */
    foreach (item, RelationGetIndexList(rel)) {
        Oid indid = lfirst_oid(item);
        Relation indexRel = index_open(indid, AccessShareLock);
        List* indexColNums = NIL;
        int2vector* colIdsPtr = &indexRel->rd_index->indkey;

        /*
         * Prepare call to shippability check. Attributes set to 0 correspond
         * to index expressions and are evaluated internally, so they are not
         * appended in given list.
         */
        for (i = 0; i < colIdsPtr->dim1; i++) {
            if (colIdsPtr->values[i] > 0)
                indexColNums = lappend_int(indexColNums, colIdsPtr->values[i]);
        }

        if (!pgxc_check_index_shippability(newLocInfo,
                indexRel->rd_index->indisprimary,
                indexRel->rd_index->indisunique,
                indexRel->rd_index->indisexclusion,
                indexColNums,
                indexRel->rd_indexprs))
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("Cannot alter table to distribution incompatible "
                           "with existing constraints")));

        index_close(indexRel, AccessShareLock);
    }

    /* Clean up */
    FreeRelationLocInfo(newLocInfo);
    pfree_ext(new_oid_array);
    relation_close(rel, NoLock);

    return redistribState;
}

/*
 * Delete from given Oid array old_oids the given oid list del_oids
 * and build a new one.
 */
static Oid* delete_node_list(Oid* old_oids, int old_num, Oid* del_oids, int del_num, int* new_num)
{
    /* Allocate former array and data */
    Oid* new_oids = old_oids;
    int loc_new_num = old_num;
    int i;

    /*
     * Delete from existing node Oid array the elements to be removed.
     * An error is returned if an element to be deleted is not in existing array.
     * It is not necessary to sort once again the result array of node Oids
     * as here only a deletion of elements is done.
     */
    for (i = 0; i < del_num; i++) {
        Oid nodeoid = del_oids[i];
        int j, position;
        bool is_listed = false;
        NameData nodename = {{0}};
        position = 0;

        for (j = 0; j < loc_new_num; j++) {
            /* Check if element can be removed */
            if (PgxcNodeCheckDnMatric(nodeoid, new_oids[j])) {
                is_listed = true;
                position = j;
            }
        }

        /* Move all the elements from [j+1, n-1] to [j, n-2] */
        if (is_listed) {
            for (j = position + 1; j < loc_new_num; j++)
                new_oids[j - 1] = new_oids[j];

            loc_new_num--;

            /* Not possible to have an empty list */
            if (loc_new_num == 0)
                ereport(ERROR,
                    (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("Node list is empty: one node at least is mandatory")));

            new_oids = (Oid*)repalloc(new_oids, loc_new_num * sizeof(Oid));
        } else
            ereport(ERROR,
                (errcode(ERRCODE_DUPLICATE_OBJECT),
                    errmsg("PGXC Node %s: object not in relation node list", get_pgxc_nodename(nodeoid, &nodename))));
    }

    /* Save new number of nodes */
    *new_num = loc_new_num;
    return new_oids;
}

/*
 * Add to given Oid array old_oids the given oid list add_oids
 * and build a new one.
 */
static Oid* add_node_list(Oid* old_oids, int old_num, Oid* add_oids, int add_num, int* new_num)
{
    /* Allocate former array and data */
    Oid* new_oids = old_oids;
    int loc_new_num = old_num;
    int i;

    /*
     * Build new Oid list, both addition and old list are already sorted.
     * The idea here is to go through the list of nodes to be added and
     * add the elements one-by-one on the existing list.
     * An error is returned if an element to be added already exists
     * in relation node array.
     * Here we do O(n^2) scan to avoid a dependency with the way
     * oids are sorted by heap APIs. They are sorted once again once
     * the addition operation is completed.
     */
    for (i = 0; i < add_num; i++) {
        Oid nodeoid = add_oids[i];
        int j;
        NameData nodename = {{0}};

        /* Check if element is already a part of array */
        for (j = 0; j < loc_new_num; j++) {
            /* Item is already in node list */
            if (PgxcNodeCheckDnMatric(nodeoid, new_oids[j]))
                ereport(ERROR,
                    (errcode(ERRCODE_DUPLICATE_OBJECT),
                        errmsg("PGXC Node %s: object already in relation node list",
                            get_pgxc_nodename(nodeoid, &nodename))));
        }

        /* If we are here, element can be added safely in node array */
        loc_new_num++;
        new_oids = (Oid*)repalloc(new_oids, loc_new_num * sizeof(Oid));
        new_oids[loc_new_num - 1] = nodeoid;
    }

    /* Sort once again the newly-created array of node Oids to maintain consistency */
    new_oids = SortRelationDistributionNodes(new_oids, loc_new_num);

    /* Save new number of nodes */
    *new_num = loc_new_num;
    return new_oids;
}
#endif

/*
 * Execute ALTER TABLE SET SCHEMA
 */
ObjectAddress AlterTableNamespace(AlterObjectSchemaStmt* stmt, Oid *oldschema)
{
    Relation rel;
    Oid relid;
    Oid oldNspOid;
    Oid nspOid;
    RangeVar* newrv = NULL;
    ObjectAddresses* objsMoved = NULL;
    ObjectAddress myself;

    relid = RangeVarGetRelidExtended(stmt->relation,
        AccessExclusiveLock,
        stmt->missing_ok,
        false,
        false,
        false,
        RangeVarCallbackForAlterRelation,
        (void*)stmt);

    if (!OidIsValid(relid)) {
        ereport(NOTICE, (errmsg("relation \"%s\" does not exist, skipping", stmt->relation->relname)));
        return InvalidObjectAddress;
    }

#ifdef ENABLE_MOT
    if (IsMOTForeignTable(relid)) {
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Un-support feature"),
                        errdetail("target table is a mot table")));
    }
#endif

    TrForbidAccessRbObject(RelationRelationId, relid, stmt->relation->relname);

    rel = relation_open(relid, NoLock);

    if (rel->rd_mlogoid != InvalidOid) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                (errmsg("Un-support feature"),
                    errdetail("table owning matview doesn't support this ALTER yet."))));
    }

    if (rel->rd_rel->relkind == RELKIND_MATVIEW) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("ALTER MATERIALIZED VIEW is not yet supported.")));
    }

    /* Permission check */
    if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId())) {
        aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(rel));
    }

    oldNspOid = RelationGetNamespace(rel);

    /* We allow to alter global temp table only this session use it */
    CheckGttTableInUse(rel);

    /* If it's an owned sequence, disallow moving it by itself. */
    if (RELKIND_IS_SEQUENCE(rel->rd_rel->relkind)) {
        ereport(
            ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("ALTER SEQUENCE SET SCHEMA is not yet supported.")));

        Oid tableId;
        int32 colId;

        if (sequenceIsOwned(relid, &tableId, &colId))
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("cannot move an owned (large) sequence into another schema"),
                    errdetail("Sequence \"%s\" is linked to table \"%s\".",
                        RelationGetRelationName(rel),
                        get_rel_name(tableId))));
    }

    /* Get and lock schema OID and check its permissions. */
    newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1);
    nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL, '\0');

    /* common checks on switching namespaces */
    CheckSetNamespace(oldNspOid, nspOid, RelationRelationId, relid);
    ledger_check_switch_schema(oldNspOid, nspOid);
    objsMoved = new_object_addresses();
    AlterTableNamespaceInternal(rel, oldNspOid, nspOid, objsMoved);
    if (rel->rd_isblockchain) {
        rename_hist_by_newnsp(RelationGetRelid(rel), stmt->newschema);
    }
    free_object_addresses(objsMoved);

    ObjectAddressSet(myself, RelationRelationId, relid);

    if (oldschema)
        *oldschema = oldNspOid;

    /* close rel, but keep lock until commit */
    relation_close(rel, NoLock);
    return myself;
}

/*
 * The guts of relocating a table or materialized view to another namespace:
 * besides moving the relation itself, its dependent objects are relocated to
 * the new schema.
 */
void AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid, ObjectAddresses* objsMoved)
{
    Relation classRel;

    Assert(objsMoved != NULL);

    if (enable_plpgsql_gsdependency_guc() &&
        gsplsql_is_object_depend(rel->rd_rel->reltype, GSDEPEND_OBJECT_TYPE_TYPE)) {
        ereport(ERROR,
            (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
                errmsg("The set schema operator of %s is not allowed, "
                        "because it is referenced by another object.", NameStr(rel->rd_rel->relname))));
    }

    /* OK, modify the pg_class row and pg_depend entry */
    classRel = heap_open(RelationRelationId, RowExclusiveLock);

    AlterRelationNamespaceInternal(classRel, RelationGetRelid(rel), oldNspOid, nspOid, true, objsMoved);

    AlterTableNamespaceDependentProcess(classRel, rel, oldNspOid, nspOid, objsMoved, NULL);

    heap_close(classRel, RowExclusiveLock);
}

static void AlterTableNamespaceDependentProcess(Relation classRel ,Relation rel, Oid oldNspOid,
                                                Oid nspOid, ObjectAddresses* objsMoved, char* newrelname)
{
    /* Fix the table's row type too */
    (void)AlterTypeNamespaceInternal(rel->rd_rel->reltype, nspOid, false, false, objsMoved, newrelname);

    /* Change the table's set type too */
    TupleDesc tupDesc = rel->rd_att;
    for (int i = 0; i < tupDesc->natts; i++) {
        if (!tupDesc->attrs[i].attisdropped && type_is_set(tupDesc->attrs[i].atttypid)) {
            (void)AlterTypeNamespaceInternal(tupDesc->attrs[i].atttypid, nspOid, false, false, objsMoved);
        }
    }

    /* Fix other dependent stuff */
    if (rel->rd_rel->relkind == RELKIND_RELATION ||
        rel->rd_rel->relkind == RELKIND_MATVIEW) {
        AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
        AlterSeqNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved, AccessExclusiveLock);
        AlterConstraintNamespaces(RelationGetRelid(rel), oldNspOid, nspOid, false, objsMoved);
    }
}

/*
 * The guts of relocating a relation to another namespace: fix the pg_class
 * entry, and the pg_depend entry if any.  Caller must already have
 * opened and write-locked pg_class.
 */
void AlterRelationNamespaceInternal(
    Relation classRel, Oid relOid, Oid oldNspOid, Oid newNspOid, bool hasDependEntry, ObjectAddresses* objsMoved)
{
    HeapTuple classTup;
    Form_pg_class classForm;
    ObjectAddress thisobj;

    classTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relOid));
    if (!HeapTupleIsValid(classTup)) {
        ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", relOid)));
    }
    classForm = (Form_pg_class)GETSTRUCT(classTup);

    Assert(classForm->relnamespace == oldNspOid);

    thisobj.classId = RelationRelationId;
    thisobj.objectId = relOid;
    thisobj.objectSubId = 0;

    /*
     * Do nothing when there's nothing to do.
     */
    if (!object_address_present(&thisobj, objsMoved)) {
        /*
         * Check relation name to ensure that it doesn't conflict with existing synonym.
         */
        if (!IsInitdb && GetSynonymOid(NameStr(classForm->relname), newNspOid, true) != InvalidOid) {
            ereport(ERROR,
                    (errmsg("relation name is already used by an existing synonym in schema \"%s\"",
                        get_namespace_name(newNspOid))));
        }
        /* check for duplicate name (more friendly than unique-index failure) */
        if (get_relname_relid(NameStr(classForm->relname), newNspOid) != InvalidOid)
            ereport(ERROR,
                (errcode(ERRCODE_DUPLICATE_TABLE),
                    errmsg("relation \"%s\" already exists in schema \"%s\"",
                        NameStr(classForm->relname),
                        get_namespace_name(newNspOid))));

        /* classTup is a copy, so OK to scribble on */
        classForm->relnamespace = newNspOid;

        simple_heap_update(classRel, &classTup->t_self, classTup);
        CatalogUpdateIndexes(classRel, classTup);

        /* Update dependency on schema if caller said so */
        if (hasDependEntry &&
            changeDependencyFor(RelationRelationId, relOid, NamespaceRelationId, oldNspOid, newNspOid) != 1)
            ereport(ERROR,
                (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                    errmsg("failed to change schema dependency for relation \"%s\"", NameStr(classForm->relname))));
        add_exact_object_address(&thisobj, objsMoved);

        /* Recode time of alter relation namespace. */
        PgObjectType objectType = GetPgObjectTypePgClass(classForm->relkind);
        if (objectType != OBJECT_TYPE_INVALID) {
            UpdatePgObjectMtime(relOid, objectType);
        }
    }

    tableam_tops_free_tuple(classTup);
}

/*
 * Move all indexes for the specified relation to another namespace.
 *
 * Note: we assume adequate permission checking was done by the caller,
 * and that the caller has a suitable lock on the owning relation.
 */
static void AlterIndexNamespaces(
    Relation classRel, Relation rel, Oid oldNspOid, Oid newNspOid, ObjectAddresses* objsMoved)
{
    List* indexList = NIL;
    ListCell* l = NULL;

    indexList = RelationGetIndexList(rel, true);

    foreach (l, indexList) {
        Oid indexOid = lfirst_oid(l);
        ObjectAddress thisobj;

        thisobj.classId = RelationRelationId;
        thisobj.objectId = indexOid;
        thisobj.objectSubId = 0;

        /*
         * Note: currently, the index will not have its own dependency on the
         * namespace, so we don't need to do changeDependencyFor(). There's no
         * row type in pg_type, either.
         *
         * XXX this objsMoved test may be pointless -- surely we have a single
         * dependency link from a relation to each index?
         */
        if (!object_address_present(&thisobj, objsMoved)) {
            AlterRelationNamespaceInternal(classRel, indexOid, oldNspOid, newNspOid, false, objsMoved);
            add_exact_object_address(&thisobj, objsMoved);
        }
    }

    list_free_ext(indexList);
}

/*
 * Move all SERIAL-column sequences of the specified relation to another
 * namespace.
 *
 * Note: we assume adequate permission checking was done by the caller,
 * and that the caller has a suitable lock on the owning relation.
 */
static void AlterSeqNamespaces(
    Relation classRel, Relation rel, Oid oldNspOid, Oid newNspOid, ObjectAddresses* objsMoved, LOCKMODE lockmode)
{
    Relation depRel;
    SysScanDesc scan;
    ScanKeyData key[2];
    HeapTuple tup;

    /*
     * SERIAL sequences are those having an auto dependency on one of the
     * table's columns (we don't care *which* column, exactly).
     */
    depRel = heap_open(DependRelationId, AccessShareLock);

    ScanKeyInit(
        &key[0], Anum_pg_depend_refclassid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationRelationId));
    ScanKeyInit(
        &key[1], Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(rel)));
    /* we leave refobjsubid unspecified */
    scan = systable_beginscan(depRel, DependReferenceIndexId, true, NULL, 2, key);

    while (HeapTupleIsValid(tup = systable_getnext(scan))) {
        Form_pg_depend depForm = (Form_pg_depend)GETSTRUCT(tup);
        Relation seqRel;

        /* skip dependencies other than auto dependencies on columns */
        if (depForm->refobjsubid == 0 || depForm->classid != RelationRelationId || depForm->objsubid != 0 ||
            depForm->deptype != DEPENDENCY_AUTO)
            continue;

        /* Use relation_open just in case it's an index */
        seqRel = relation_open(depForm->objid, lockmode);

        /* skip non-sequence relations */
        if (!RELKIND_IS_SEQUENCE(RelationGetForm(seqRel)->relkind)) {
            /* No need to keep the lock */
            relation_close(seqRel, lockmode);
            continue;
        } else {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("There's dependent sequence, but ALTER SEQUENCE SET SCHEMA is not yet supported.")));
        }
        /* Fix the pg_class and pg_depend entries */
        AlterRelationNamespaceInternal(classRel, depForm->objid, oldNspOid, newNspOid, true, objsMoved);

        /*
         * Sequences have entries in pg_type. We need to be careful to move
         * them to the new namespace, too.
         */
        (void)AlterTypeNamespaceInternal(RelationGetForm(seqRel)->reltype, newNspOid, false, false, objsMoved);

        /* Now we can close it.  Keep the lock till end of transaction. */
        relation_close(seqRel, NoLock);
    }

    systable_endscan(scan);

    relation_close(depRel, AccessShareLock);
}

/*
 * This code supports
 *	CREATE TEMP TABLE ... ON COMMIT { DROP | PRESERVE ROWS | DELETE ROWS }
 *
 * Because we only support this for TEMP tables, it's sufficient to remember
 * the state in a backend-local data structure.
 */
/*
 * Register a newly-created relation's ON COMMIT action.
 */
void register_on_commit_action(Oid relid, OnCommitAction action)
{
    OnCommitItem* oc = NULL;
    MemoryContext oldcxt;

    /*
     * We needn't bother registering the relation unless there is an ON COMMIT
     * action we need to take.
     */
    if (action == ONCOMMIT_NOOP || action == ONCOMMIT_PRESERVE_ROWS)
        return;

    oldcxt = MemoryContextSwitchTo(u_sess->cache_mem_cxt);

    oc = (OnCommitItem*)palloc(sizeof(OnCommitItem));
    oc->relid = relid;
    oc->oncommit = action;
    oc->creating_subid = GetCurrentSubTransactionId();
    oc->deleting_subid = InvalidSubTransactionId;

    u_sess->cmd_cxt.on_commits = lcons(oc, u_sess->cmd_cxt.on_commits);

    (void)MemoryContextSwitchTo(oldcxt);
}

/*
 * Unregister any ON COMMIT action when a relation is deleted.
 *
 * Actually, we only mark the OnCommitItem entry as to be deleted after commit.
 */
void remove_on_commit_action(Oid relid)
{
    ListCell* l = NULL;

    foreach (l, u_sess->cmd_cxt.on_commits) {
        OnCommitItem* oc = (OnCommitItem*)lfirst(l);

        if (oc->relid == relid) {
            oc->deleting_subid = GetCurrentSubTransactionId();
            break;
        }
    }
}

/*
 * Perform ON COMMIT actions.
 *
 * This is invoked just before actually committing, since it's possible
 * to encounter errors.
 */
void PreCommit_on_commit_actions(void)
{
    ListCell* l = NULL;
    List* oids_to_truncate = NIL;

    foreach (l, u_sess->cmd_cxt.on_commits) {
        OnCommitItem* oc = (OnCommitItem*)lfirst(l);

        /* Ignore entry if already dropped in this xact */
        if (oc->deleting_subid != InvalidSubTransactionId)
            continue;

        switch (oc->oncommit) {
            case ONCOMMIT_NOOP:
            case ONCOMMIT_PRESERVE_ROWS:
                /* Do nothing (there shouldn't be such entries, actually) */
                break;
            case ONCOMMIT_DELETE_ROWS:
                 /*
                 * If this transaction hasn't accessed any temporary
                 * relations, we can skip truncating ON COMMIT DELETE ROWS
                 * tables, as they must still be empty.
                 */
                if (t_thrd.xact_cxt.MyXactAccessedTempRel)
                    oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
                break;
            case ONCOMMIT_DROP: {
                ObjectAddress object;

                object.classId = RelationRelationId;
                object.objectId = oc->relid;
                object.objectSubId = 0;

                /*
                 * Since this is an automatic drop, rather than one
                 * directly initiated by the user, we pass the
                 * PERFORM_DELETION_INTERNAL flag.
                 */
                performDeletion(&object, DROP_CASCADE, PERFORM_DELETION_INTERNAL);

                /*
                 * Note that table deletion will call
                 * remove_on_commit_action, so the entry should get marked
                 * as deleted.
                 */
                Assert(oc->deleting_subid != InvalidSubTransactionId);
                break;
            }
        }
    }
    if (oids_to_truncate != NIL) {
        heap_truncate(oids_to_truncate);
        CommandCounterIncrement(); /* XXX needed? */
    }
}

/*
 * Post-commit or post-abort cleanup for ON COMMIT management.
 *
 * All we do here is remove no-longer-needed OnCommitItem entries.
 *
 * During commit, remove entries that were deleted during this transaction;
 * during abort, remove those created during this transaction.
 */
void AtEOXact_on_commit_actions(bool isCommit)
{
    ListCell* cur_item = NULL;
    ListCell* prev_item = NULL;

    prev_item = NULL;
    cur_item = list_head(u_sess->cmd_cxt.on_commits);

    while (cur_item != NULL) {
        OnCommitItem* oc = (OnCommitItem*)lfirst(cur_item);

        if (isCommit ? oc->deleting_subid != InvalidSubTransactionId : oc->creating_subid != InvalidSubTransactionId) {
            /* cur_item must be removed */
            u_sess->cmd_cxt.on_commits = list_delete_cell(u_sess->cmd_cxt.on_commits, cur_item, prev_item);
            pfree_ext(oc);
            if (prev_item != NULL)
                cur_item = lnext(prev_item);
            else
                cur_item = list_head(u_sess->cmd_cxt.on_commits);
        } else {
            /* cur_item must be preserved */
            oc->creating_subid = InvalidSubTransactionId;
            oc->deleting_subid = InvalidSubTransactionId;
            prev_item = cur_item;
            cur_item = lnext(prev_item);
        }
    }
}

/*
 * Post-subcommit or post-subabort cleanup for ON COMMIT management.
 *
 * During subabort, we can immediately remove entries created during this
 * subtransaction.	During subcommit, just relabel entries marked during
 * this subtransaction as being the parent's responsibility.
 */
void AtEOSubXact_on_commit_actions(bool isCommit, SubTransactionId mySubid, SubTransactionId parentSubid)
{
    ListCell* cur_item = NULL;
    ListCell* prev_item = NULL;

    prev_item = NULL;
    cur_item = list_head(u_sess->cmd_cxt.on_commits);

    while (cur_item != NULL) {
        OnCommitItem* oc = (OnCommitItem*)lfirst(cur_item);

        if (!isCommit && oc->creating_subid == mySubid) {
            /* cur_item must be removed */
            u_sess->cmd_cxt.on_commits = list_delete_cell(u_sess->cmd_cxt.on_commits, cur_item, prev_item);
            pfree_ext(oc);
            if (prev_item != NULL)
                cur_item = lnext(prev_item);
            else
                cur_item = list_head(u_sess->cmd_cxt.on_commits);
        } else {
            /* cur_item must be preserved */
            if (oc->creating_subid == mySubid)
                oc->creating_subid = parentSubid;
            if (oc->deleting_subid == mySubid)
                oc->deleting_subid = isCommit ? parentSubid : InvalidSubTransactionId;
            prev_item = cur_item;
            cur_item = lnext(prev_item);
        }
    }
}

/*
 * This is intended as a callback for RangeVarGetRelidExtended().  It allows
 * the relation to be locked only if (1) it's a plain table, materialized
 * view, or TOAST table and (2) the current user is the owner (or the
 * superuser).  This meets the permission-checking needs of CLUSTER, REINDEX
 * TABLE, and REFRESH MATERIALIZED VIEW; we expose it here so that it can be
 * used by all.
 */
void RangeVarCallbackOwnsTable(const RangeVar* relation, Oid relId, Oid oldRelId, bool target_is_partition, void* arg)
{
    char relkind;

    /* Nothing to do if the relation was not found. */
    if (!OidIsValid(relId)) {
        return;
    }

    /*
     * If the relation does exist, check whether it's an index.  But note that
     * the relation might have been dropped between the time we did the name
     * lookup and now.	In that case, there's nothing to do.
     */
    relkind = get_rel_relkind(relId);
    if (!relkind) {
        return;
    }
    if (relkind != RELKIND_RELATION &&
        relkind != RELKIND_TOASTVALUE &&
        relkind != RELKIND_MATVIEW) {
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("\"%s\" is not a table or materialized view", relation->relname)));
    }
    /* Check permissions */
    AclResult aclresult = pg_class_aclcheck(relId, GetUserId(), ACL_INDEX);
    if (aclresult != ACLCHECK_OK && !pg_class_ownercheck(relId, GetUserId())) {
        aclcheck_error(aclresult, ACL_KIND_CLASS, relation->relname);
    }
}

/*
 * This is intended as a callback for RangeVarGetRelidExtended().  It allows
 * the relation to be locked only if (1) it's a materialized view and
 * (2) the current user is the owner (or the superuser).
 * This meets the permission-checking needs of and REFRESH MATERIALIZED VIEW;
 * we expose it here so that it can be used by all.
 */
void RangeVarCallbackOwnsMatView(const RangeVar* relation, Oid relId, Oid oldRelId, bool target_is_partition, void* arg)
{
    char relkind;

    /* Nothing to do if the relation was not found. */
    if (!OidIsValid(relId)) {
        return;
    }

    /*
     * If the relation does exist, check whether it's an index.  But note that
     * the relation might have been dropped between the time we did the name
     * lookup and now.	In that case, there's nothing to do.
     */
    relkind = get_rel_relkind(relId);
    if (!relkind) {
        return;
    }
    if (relkind != RELKIND_RELATION &&
        relkind != RELKIND_TOASTVALUE &&
        relkind != RELKIND_MATVIEW) {
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("\"%s\" is not a table or materialized view", relation->relname)));
    }

    /* Check permissions */
    AclResult aclresult = pg_class_aclcheck(relId, GetUserId(), ACL_INSERT | ACL_DELETE);
    if (aclresult != ACLCHECK_OK) {
        aclcheck_error(aclresult, ACL_KIND_CLASS, relation->relname);
    }

    bool is_owner = pg_class_ownercheck(relId, GetUserId());
    if (!is_owner) {
        aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, relation->relname);
    }
}

/*
 * Callback to RangeVarGetRelidExtended(), similar to
 * RangeVarCallbackOwnsTable() but without checks on the type of the relation.
 */
void RangeVarCallbackOwnsRelation(
    const RangeVar* relation, Oid relId, Oid oldRelId, bool target_is_partition, void* arg)
{
    HeapTuple tuple;

    /* Nothing to do if the relation was not found. */
    if (!OidIsValid(relId)) {
        return;
    }

    tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relId));
    if (!HeapTupleIsValid(tuple)) {
        /* should not happen */
        ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", relId)));
    }

    AclResult aclresult = pg_class_aclcheck(relId, GetUserId(), ACL_INDEX);
    if (aclresult != ACLCHECK_OK && !pg_class_ownercheck(relId, GetUserId()) &&
        !HasSpecAnyPriv(GetUserId(), CREATE_ANY_INDEX, false)) {
        aclcheck_error(aclresult, ACL_KIND_CLASS, relation->relname);
    }
    if (!g_instance.attr.attr_common.allowSystemTableMods && !u_sess->attr.attr_common.IsInplaceUpgrade &&
        IsSystemClass((Form_pg_class)GETSTRUCT(tuple))) {
        ereport(ERROR,
            (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                errmsg("permission denied: \"%s\" is a system catalog", relation->relname)));
    }

    ReleaseSysCache(tuple);
}

/*
 * Common RangeVarGetRelid callback for rename, set schema, and alter table
 * processing.
 */
static void RangeVarCallbackForAlterRelation(
    const RangeVar* rv, Oid relid, Oid oldrelid, bool target_is_partition, void* arg)
{
    Node* stmt = (Node*)arg;
    ObjectType reltype;
    HeapTuple tuple;
    Form_pg_class classform;
    AclResult aclresult;
    char relkind;

    tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
    if (!HeapTupleIsValid(tuple)) {
        return; /* concurrently dropped */
    }
    classform = (Form_pg_class)GETSTRUCT(tuple);
    relkind = classform->relkind;

    /*
     * Extract the specified relation type from the statement parse tree.
     */
    if (IsA(stmt, RenameStmt)) {
        reltype = ((RenameStmt*)stmt)->renameType;
    } else if (IsA(stmt, AlterObjectSchemaStmt)) {
        reltype = ((AlterObjectSchemaStmt*)stmt)->objectType;
    } else if (IsA(stmt, AlterTableStmt)) {
        reltype = ((AlterTableStmt*)stmt)->relkind;
    } else {
        reltype = OBJECT_TABLE; /* placate compiler */
        ereport(ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
            errmsg("unrecognized node type: %d", (int)nodeTag(stmt))));
    }

    /*
     * Must own relation or have the ALTER privilege of the relation.
     * But if we are truncating a partition, we will not check owner.
    */
    if (reltype == OBJECT_TYPE) {
        Oid typeoid = get_typeoid(classform->relnamespace, NameStr(classform->relname));
        aclresult = pg_type_aclcheck(typeoid, GetUserId(), ACL_ALTER);
    } else if (reltype == OBJECT_INDEX || reltype == OBJECT_PARTITION_INDEX) {
        Oid tableoid = IndexGetRelation(relid, false);
        aclresult = pg_class_aclcheck(tableoid, GetUserId(), ACL_INDEX);
        bool anyResult = false;
        if (aclresult != ACLCHECK_OK && !IsSysSchema(GetNamespaceIdbyRelId(tableoid))) {
            anyResult = HasSpecAnyPriv(GetUserId(), ALTER_ANY_INDEX, false);
        }
        aclresult = anyResult ? ACLCHECK_OK : aclresult;
    } else {
        aclresult = pg_class_aclcheck(relid, GetUserId(), ACL_ALTER);
    }

    if (!target_is_partition && aclresult != ACLCHECK_OK && !pg_class_ownercheck(relid, GetUserId()) &&
        !(isOperatoradmin(GetUserId()) && u_sess->attr.attr_security.operation_mode)) {
        aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS, rv->relname);
    }

    /* No system table modifications unless explicitly allowed or during inplace upgrade. */
    if (!(g_instance.attr.attr_common.allowSystemTableMods && relid >= FirstBootstrapObjectId) &&
        !u_sess->attr.attr_common.IsInplaceUpgrade && IsSystemClass(classform)) {
        ereport(ERROR,
            (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                errmsg("permission denied: \"%s\" is a system catalog", rv->relname)));
    }

    switch (relid) {
        case DatabaseRelationId:
        case AuthIdRelationId:
        case AuthMemRelationId:
        case RelationRelationId:
        case AttributeRelationId:
        case ProcedureRelationId:
        case TypeRelationId:
        case UserStatusRelationId:
            /*
             * Schema change of these nailed-in system catalogs is very dangerous!!!
             * Later, we may need an exclusive GUC variable to enable such change.
             */
            if (!u_sess->attr.attr_common.IsInplaceUpgrade)
                ereport(ERROR,
                    (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                        errmsg("permission denied: system catalog \"%s\" can not be altered", rv->relname)));
            break;
        default:
            break;
    }
    /*
     * For ALTER .. RENAME, check permissions: the user must (still)
     * have CREATE rights on the containing namespace.
     */
    if (IsA(stmt, RenameStmt)) {
        aclresult = pg_namespace_aclcheck(classform->relnamespace, GetUserId(), ACL_CREATE);
        if (aclresult != ACLCHECK_OK) {
            aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(classform->relnamespace));
        }
    }

    /*
     * For compatibility with prior releases, we allow ALTER TABLE to be used
     * with most other types of relations (but not composite types). We allow
     * similar flexibility for ALTER INDEX in the case of RENAME, but not
     * otherwise.  Otherwise, the user must select the correct form of the
     * command for the relation at issue.
     */
    if (reltype == OBJECT_SEQUENCE && relkind != RELKIND_SEQUENCE) {
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a sequence", rv->relname)));
    }

    if (reltype == OBJECT_LARGE_SEQUENCE && relkind != RELKIND_LARGE_SEQUENCE) {
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a large sequence", rv->relname)));
    }

    if (reltype == OBJECT_VIEW && relkind != RELKIND_VIEW && relkind != RELKIND_CONTQUERY) {
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a view", rv->relname)));
    }

    if (reltype == OBJECT_CONTQUERY && relkind != RELKIND_CONTQUERY) {
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a contview", rv->relname)));
    }

    if (reltype == OBJECT_MATVIEW && relkind != RELKIND_MATVIEW) {
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a materialized view", rv->relname)));
    }

    if (reltype == OBJECT_FOREIGN_TABLE && relkind != RELKIND_FOREIGN_TABLE && relkind != RELKIND_STREAM) {
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a foreign table", rv->relname)));
    }

    if (reltype == OBJECT_STREAM && relkind != RELKIND_STREAM) {
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a stream", rv->relname)));
    }

    if (reltype == OBJECT_TYPE && relkind != RELKIND_COMPOSITE_TYPE) {
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a composite type", rv->relname)));
    }

    if (reltype == OBJECT_INDEX && relkind != RELKIND_INDEX && relkind != RELKIND_GLOBAL_INDEX &&
        !IsA(stmt, RenameStmt)) {
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not an index", rv->relname)));
    }
    /*
     * Don't allow ALTER TABLE on composite types. We want people to use ALTER
     * TYPE for that.
     */
    if (reltype != OBJECT_TYPE && relkind == RELKIND_COMPOSITE_TYPE) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("\"%s\" is a composite type", rv->relname),
                errhint("Use ALTER TYPE instead.")));
    }

    if (reltype != OBJECT_FOREIGN_TABLE && relkind == RELKIND_FOREIGN_TABLE) {
#ifdef ENABLE_MOT
        if (isMOTFromTblOid(relid)) {
            ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("\"%s\" is not a table", rv->relname),
                errhint("Use ALTER FOREIGN TABLE to alter a foreign table.")));
        } else {
#endif
            ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                    errmsg("\"%s\" is a foreign table, which does not support column constraints.", rv->relname)));
#ifdef ENABLE_MOT
        }
#endif
    }

#ifdef ENABLE_MULTIPLE_NODES
    if (reltype != OBJECT_STREAM && relkind == RELKIND_STREAM) {
        if (!range_var_is_stream(rv, true)) {
            ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                    errmsg("\"%s\" is a stream, which does not support column constraints.", rv->relname)));
        }
    }
#endif

    /*
     * Don't allow ALTER TABLE .. SET SCHEMA on relations that can't be moved
     * to a different schema, such as indexes and TOAST tables.
     */
    if (IsA(stmt, AlterObjectSchemaStmt) &&
        relkind != RELKIND_RELATION &&
        relkind != RELKIND_VIEW &&
        relkind != RELKIND_CONTQUERY &&
        !RELKIND_IS_SEQUENCE(relkind) &&
        relkind != RELKIND_STREAM &&
        relkind != RELKIND_FOREIGN_TABLE &&
        relkind != RELKIND_MATVIEW) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("\"%s\" is not a table, view, sequence, or foreign table", rv->relname)));
    }

    ReleaseSysCache(tuple);
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		: check if the partition is in use
 * Description	:
 * Notes		:
 */
void checkPartNotInUse(Partition part, const char* stmt)
{
    const int expected_refcnt = 1;

    if (part->pd_refcnt != expected_refcnt) {
        ereport(ERROR,
            (errcode(ERRCODE_OBJECT_IN_USE),
                /* translator: first %s is a SQL command, eg ALTER TABLE */
                errmsg("cannot %s \"%s\" because it is in use", stmt, PartitionGetPartitionName(part))));
    }
}

extern Node* GetColumnRef(Node* key, bool* isExpr, bool* isFunc);
/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		: get paritionkey's sequence in column definition
 * Description	:
 * Notes		: invoker to free the return list
 */
List* GetPartitionkeyPos(List* partitionkeys, List* schema, bool* partkeyIsFunc)
{
    ListCell* partitionkey_cell = NULL;
    ListCell* schema_cell = NULL;
    int column_count = 0;
    List* pos = NULL;

    if (schema == NIL) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                        errmsg("there is no column for a partitioned table!")));
    }

    int len = schema->length;
    if (partitionkeys == NIL) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION), errmsg("there is no partition key!")));
    }

    bool* is_exist = (bool*)palloc(len * sizeof(bool));
    errno_t rc = EOK;
    rc = memset_s(is_exist, len * sizeof(bool), 0, len * sizeof(bool));
    securec_check(rc, "\0", "\0");
    bool isExpr = false;
    foreach (partitionkey_cell, partitionkeys) {
        ColumnRef* partitionkey_ref = (ColumnRef*)GetColumnRef((Node*)lfirst(partitionkey_cell), &isExpr, partkeyIsFunc);
        if (!partitionkey_ref)
            ereport(ERROR,(errcode(ERRCODE_UNDEFINED_COLUMN),(errmsg("The partition key doesn't have any column."))));
        if (isExpr && partitionkeys->length > 1)
            ereport(ERROR,(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),(errmsg("The multi partition expr keys are not supported."))));
        char* partitonkey_name = ((Value*)linitial(partitionkey_ref->fields))->val.str;

        foreach (schema_cell, schema) {
            ColumnDef* schema_def = (ColumnDef*)lfirst(schema_cell);

            /* find the column that has the same name as the partitionkey */
            if (!strcmp(partitonkey_name, schema_def->colname)) {

                /*
                 * Generated columns cannot work: They are computed after BEFORE
                 * triggers, but partition routing is done before all triggers.
                 */
                if (schema_def->generatedCol) {
                    ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                        errmsg("cannot use generated column in partition key"),
                        errdetail("Column \"%s\" is a generated column.", partitonkey_name)));
                }

                /* duplicate partitionkey name */
                if (is_exist[column_count]) {
                    pfree_ext(is_exist);
                    ereport(ERROR,
                        (errcode(ERRCODE_DUPLICATE_COLUMN), errmsg("duplicate partition key: %s", partitonkey_name)));
                }

                /* recoed attribute info when the partitionkey is unique */
                is_exist[column_count] = true;
                break;
            }

            column_count++;
        }

        /* fail to find the partitionkey in column definition */
        if (column_count >= len) {
            pfree_ext(is_exist);
            ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN),
                    errmsg("undefined column\"%s\" is used as a partitioning column", partitonkey_name)));
        }

        pos = lappend_int(pos, column_count);
        column_count = 0;
        partitonkey_name = NULL;
    }

    pfree_ext(is_exist);

    return pos;
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		: check the partitionkey's datatype
 * Description	:
 * Notes		:
 */
static void CheckRangePartitionKeyType(FormData_pg_attribute* attrs, List* pos)
{
    int location = 0;
    ListCell* cell = NULL;
    Oid typoid = InvalidOid;
    foreach (cell, pos) {
        bool result = false;

        location = lfirst_int(cell);
        typoid = attrs[location].atttypid;
        /* check datatype for range partitionkey */
        result = CheckRangePartitionKeyType(typoid);

        if (!result) {
            list_free_ext(pos);
            ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
                    errmsg("column %s cannot serve as a range partitioning column because of its datatype",
                        NameStr(attrs[location].attname))));
        }
    }
}

static void CheckListPartitionKeyType(FormData_pg_attribute* attrs, List* pos)
{
    if (pos == NULL || attrs == NULL) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION), errmsg("invalid list partiiton table definition")));
    }
    int location;
    ListCell* cell = NULL;

    foreach (cell, pos) {
        location = lfirst_int(cell);
        if (!CheckListPartitionKeyType(attrs[location].atttypid)) {
            list_free_ext(pos);
            ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH),
                errmsg("column %s cannot serve as a list partitioning column because of its datatype",
                    NameStr(attrs[location].attname))));
        }
    }
}

static void CheckHashPartitionKeyType(FormData_pg_attribute* attrs, List* pos)
{
    int location = 0;
    ListCell* cell = NULL;
    Oid typeOid = InvalidOid;

    foreach (cell, pos) {
        location = lfirst_int(cell);
        typeOid = attrs[location].atttypid;
        /* Check datatype for hash partitionkey */
        if (!CheckHashPartitionKeyType(typeOid)) {
            list_free_ext(pos);
            ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH),
                            errmsg("column %s cannot serve as a hash partitioning column because of its datatype",
                                   NameStr(attrs[location].attname))));
        }
    }
}

static void CheckIntervalPartitionKeyType(FormData_pg_attribute* attrs, List* pos)
{
    /* must be one partitionkey for interval partition, have checked before */
    Assert(pos->length == 1);

    ListCell* cell = list_head(pos);
    int location = lfirst_int(cell);
    Oid typoid = attrs[location].atttypid;
    if (typoid != TIMESTAMPOID && typoid != TIMESTAMPTZOID && typoid != DATEOID) {
        list_free_ext(pos);
        ereport(ERROR,
            (errcode(ERRCODE_DATATYPE_MISMATCH),
                errmsg("column %s cannot serve as a interval partitioning column because of its datatype",
                    NameStr(attrs[location].attname))));
    }
}

static void CheckIntervalValue(
    const FormData_pg_attribute* attrs, const List* pos, const IntervalPartitionDefState* intervalPartDef)
{
    /* must be one partitionkey for interval partition, have checked before */
    Assert(pos->length == 1);

    ListCell* cell = list_head(pos);
    int location = lfirst_int(cell);
    Oid typoid = attrs[location].atttypid;
    if (typoid != DATEOID) {
        return;
    }

    int32 typmod = -1;
    Interval* interval = NULL;
    A_Const* node = reinterpret_cast<A_Const*>(intervalPartDef->partInterval);
    interval = char_to_interval(node->val.val.str, typmod);
    if (interval->time != 0) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                errmsg("the interval of DATE type must be an integer multiple of days")));
    }
    pfree(interval);
}

/*
 * @@GaussDB@@
 * Target		: value-partition type check
 * Brief		: check the value partitionkey's datatype
 * Description	:
 * Notes		:
 */
void CheckValuePartitionKeyType(FormData_pg_attribute* attrs, List* pos)
{
    int location = 0;
    ListCell* cell = NULL;
    Oid typoid = InvalidOid;

    foreach (cell, pos) {
        location = lfirst_int(cell);
        typoid = attrs[location].atttypid;
        /*
         * Check datatype for partitionkey NOTE: currently we reuse distribution
         * key's restriction as value-based parition is equal-evaluated we can't
         * the same criteria with Range-Partition Key
         */
        /* We just error-out first partition-column with invalid datatype */
        if (!(typoid == INT8OID || typoid == INT1OID || typoid == INT2OID || typoid == INT4OID ||
                typoid == NUMERICOID || typoid == CHAROID || typoid == BPCHAROID || typoid == VARCHAROID ||
                typoid == NVARCHAR2OID || typoid == DATEOID || typoid == TIMEOID || typoid == TIMESTAMPOID ||
                typoid == TIMESTAMPTZOID || typoid == INTERVALOID || typoid == TIMETZOID ||
                typoid == SMALLDATETIMEOID || typoid == TEXTOID)) {
            list_free_ext(pos);
            ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
                    errmsg("column \"%s\" cannot be served as a value-partitioning column because of its datatype [%s]",
                        NameStr(attrs[location].attname),
                        format_type_with_typemod(attrs[location].atttypid, attrs[location].atttypmod))));
        }
    }
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		: check datatype for range partitionkey
 * Description	:
 * Notes		:
 */
static bool CheckRangePartitionKeyType(Oid typoid)
{
    bool result = true;

    switch (typoid) {
        case INT2OID:
            result = true;
            break;

        case INT4OID:
            result = true;
            break;

        case INT8OID:
            result = true;
            break;

        case DATEOID:
            result = true;
            break;

        case TIMESTAMPOID:
            result = true;
            break;

        case TIMESTAMPTZOID:
            result = true;
            break;

        case NUMERICOID:
            result = true;
            break;

        case CLOBOID:
            result = true;
            break;

        case TEXTOID:
            result = true;
            break;

        case CHAROID:
            result = true;
            break;

        case BPCHAROID:
            result = true;
            break;

        case VARCHAROID:
            result = true;
            break;

        case NVARCHAR2OID:
            result = true;
            break;

        case FLOAT4OID:
            result = true;
            break;

        case FLOAT8OID:
            result = true;
            break;

        case NAMEOID:
            result = true;
            break;

        default:
            result = false;
            break;
    }

    return result;
}

static bool CheckListPartitionKeyType(Oid typoid)
{
    switch (typoid) {
        case INT1OID:
        case INT2OID:
        case INT4OID:
        case INT8OID:
        case NUMERICOID:
        case CHAROID:
        case VARCHAROID:
        case BPCHAROID:
        case NVARCHAR2OID:
        case DATEOID:
        case TIMESTAMPOID:
        case TIMESTAMPTZOID:
            return true;
        default:
            return false;
    }
}

static bool CheckHashPartitionKeyType(Oid typoid)
{
    switch (typoid) {
        case INT1OID:
        case INT2OID:
        case INT4OID:
        case INT8OID:
        case NUMERICOID:
        case CHAROID:
        case BPCHAROID:
        case VARCHAROID:
        case TEXTOID:
        case DATEOID:
        case TIMEOID:
        case NVARCHAR2OID:
        case TIMESTAMPOID:
        case TIMESTAMPTZOID:
            return true;
        default:
            return false;
    }
}


/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		: check tablespace permission for partition
 * Description	:
 * Notes		: maybe the owner of the partition is not the current user
 */
static void CheckPartitionTablespace(const char* spcname, Oid owner)
{
    Oid spcoid = InvalidOid;

    if (spcname == NULL || !OidIsValid(owner))
        return;

    spcoid = get_tablespace_oid(spcname, false);

    /* Check permissions except when using database's default */
    if (OidIsValid(spcoid) && u_sess->proc_cxt.MyDatabaseTableSpace != spcoid) {
        AclResult aclresult;

        aclresult = pg_tablespace_aclcheck(spcoid, GetUserId(), ACL_CREATE);
        if (aclresult != ACLCHECK_OK)
            aclcheck_error(aclresult, ACL_KIND_TABLESPACE, get_tablespace_name(spcoid));
    }

    /* In all cases disallow placing user relations in pg_global */
    if (spcoid == GLOBALTABLESPACE_OID) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                errmsg("only shared relations can be placed in pg_global tablespace")));
    }
}

Oid GetPartkeyExprType(Oid* target_oid, int* target_mod)
{
    *target_oid = INT8OID;
    Relation typeRel = heap_open(TypeRelationId, RowExclusiveLock);
    HeapTuple typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(*target_oid));
    Form_pg_type pgTypeForm = (Form_pg_type)GETSTRUCT(typeTuple);
    *target_mod = pgTypeForm->typtypmod;
    Oid typcollation = pgTypeForm->typcollation;
    ReleaseSysCache(typeTuple);
    heap_close(typeRel, RowExclusiveLock);
    return typcollation;
}

static void FillListPartitionValueList(List** result, RowExpr* row, const List* keyPos, FormData_pg_attribute* attrs,
    int boundId)
{
    Const* targetExpr = NULL;
    ListCell* keyCell = NULL;
    ListCell* posCell = NULL;

    forboth (keyCell, row->args, posCell, keyPos) {
        /* transform the const to target datatype */
        targetExpr = (Const*)GetTargetValue(&attrs[lfirst_int(posCell)], (Const*)lfirst(keyCell), false, false);
        if (targetExpr == NULL) {
            for (int i = 0; i < boundId; i++) {
                list_free_ext(result[i]);
            }
            pfree_ext(result);
            ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                errmsg("partition key value must be const or const-evaluable expression")));
        }
        targetExpr->constcollid = attrs[lfirst_int(posCell)].attcollation;
        result[boundId] = lappend(result[boundId], targetExpr);
    }
}

static List **GetListPartitionValueLists(const List *keyPos, FormData_pg_attribute *attrs, List *value,
                                         bool partkeyIsFunc)
{
    Node* cell = NULL;
    ListCell* valueCell = NULL;
    int count = 0;
    Const* targetExpr = NULL;
    List** result = (List**)palloc0(value->length * sizeof(List*));

    foreach(valueCell, value) {
        cell = (Node*)lfirst(valueCell);
        if (IsA(cell, RowExpr)) { /* Multi-keys partition boundary values */
            FillListPartitionValueList(result, (RowExpr*)cell, keyPos, attrs, count++);
            continue;
        }
        /* del with maxvalue  */
        if (((Const*)cell)->ismaxvalue) {
            Const* max = makeNode(Const);
            max->xpr.type = T_Const;
            max->ismaxvalue = true;
            result[count] = lappend(result[count], max);
            count++;
            continue;
        }

        /* transform the const to target datatype */
        targetExpr = (Const*)GetTargetValue(&attrs[lfirst_int(keyPos->head)], (Const*)cell, false, partkeyIsFunc);
        if (targetExpr == NULL) {
            for (int i = 0; i < count; i++) {
                list_free_ext(result[i]);
            }
            pfree_ext(result);
            ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                    errmsg("partition key value must be const or const-evaluable expression")));
        }
        if (partkeyIsFunc) {
            Oid target_oid = InvalidOid;
            int target_mod = -1;
            targetExpr->constcollid = GetPartkeyExprType(&target_oid, &target_mod);
        } else {
            targetExpr->constcollid = attrs[lfirst_int(keyPos->head)].attcollation;
        }
        result[count] = lappend(result[count], targetExpr);
        count++;
    }

    return result;
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		: transform the partition value as an arry
 * Description	:
 * Notes		: the invoker should free the arry
 */
Const* GetPartitionValue(List* pos, FormData_pg_attribute* attrs, List* value, bool isinterval, bool isPartition, bool partkeyIsFunc)
{
    Const* result = NULL;
    Const* cell = NULL;
    ListCell* pos_cell = NULL;
    ListCell* value_cell = NULL;
    int valuepos = 0;
    int count = 0;
    Const* target_expr = NULL;

    /* lack of partitionkey value */
    if (pos->length > value->length) {
        list_free_ext(pos);
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
            errmsg("%s bound list contains too few elements", (isPartition ? "partition" : "distribution"))));
    }

    if (pos->length < value->length) {
        list_free_ext(pos);
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
            errmsg("%s bound list contains too many elements", (isPartition ? "partition" : "distribution"))));
    }

    result = (Const*)palloc(pos->length * sizeof(Const));
    errno_t rc = memset_s(result, pos->length * sizeof(Const), 0, pos->length * sizeof(Const));
    securec_check(rc, "", "");

    forboth(pos_cell, pos, value_cell, value)
    {
        valuepos = lfirst_int(pos_cell);
        cell = (Const*)lfirst(value_cell);

        /* del with maxvalue  */
        if (cell->ismaxvalue) {
            result[count].xpr.type = T_Const;
            result[count].ismaxvalue = cell->ismaxvalue;

            count++;
            continue;
        }

        /* transform the const to target datatype */
        target_expr = (Const*)GetTargetValue(&attrs[valuepos], cell, isinterval, partkeyIsFunc);
        if (target_expr == NULL) {
            pfree_ext(result);
            list_free_ext(pos);
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_OPERATION),
                    errmsg("%s key value must be const or const-evaluable expression",
                        (isPartition ? "partition" : "distribution"))));
        }

        result[count] = *target_expr;
        if (partkeyIsFunc) {
            Oid target_oid = InvalidOid;
            int target_mod = -1;
            result[count].constcollid = GetPartkeyExprType(&target_oid, &target_mod);
        } else {
            result[count].constcollid = attrs[valuepos].attcollation;
        }

        count++;
    }

    Assert(count == pos->length);

    return result;
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		:
 * Description	:
 * Notes		:
 */
Node* GetTargetValue(Form_pg_attribute attrs, Const* src, bool isinterval, bool partkeyIsFunc)
{
    Oid target_oid = InvalidOid;
    int target_mod = -1;
    Node* expr = NULL;
    Node* target_expr = NULL;

    Assert(src);

    /* transform the const to target datatype */
    if (partkeyIsFunc) {
        GetPartkeyExprType(&target_oid, &target_mod);
    } else if (!ConfirmTypeInfo(&target_oid, &target_mod, src, attrs, isinterval)) {
        return NULL;
    }

    expr = (Node*)coerce_to_target_type(
        NULL, (Node*)src, exprType((Node*)src), target_oid, target_mod, COERCION_ASSIGNMENT, COERCE_IMPLICIT_CAST,
        NULL, NULL, -1);
    if (expr == NULL) {
        return NULL;
    }

    /* convert source const's charset to target partkey's charset */
    if (!partkeyIsFunc && DB_IS_CMPT_BD && OidIsValid(attrs->attcollation)) {
        assign_expr_collations(NULL, expr);
        if (attrs->attcollation != exprCollation(expr)) {
            int attcharset = get_valid_charset_by_collation(attrs->attcollation);
            expr = coerce_to_target_charset(expr, attcharset, target_oid, target_mod, attrs->attcollation);

            Assert(expr != NULL);
            if (!IsA(expr, Const)) {
                expr = (Node*)evaluate_expr((Expr*)expr, target_oid, target_mod, attrs->attcollation);
            } else if (attrs->attcollation != exprCollation(expr)) {
                if (expr == (Node*)src) {
                    /* We are not sure where src comes from, avoid set src->constcollid directly. */
                    expr = (Node*)copyObject((void*)src);
                }
                /*
                 * The expr is used to compute hash or compare it with the partition boundary.
                 * Set the correct collation to ensure the correctness of the partition pruning and routing.
                 */
                exprSetCollation(expr, attrs->attcollation);
            }
        }
    }

    switch (nodeTag(expr)) {
        /* do nothing for Const */
        case T_Const:
            target_expr = expr;
            break;

        /* get return value for function expression */
        case T_FuncExpr: {
            FuncExpr* funcexpr = (FuncExpr*)expr;
            expr = (Node*)evaluate_expr(
                (Expr*)funcexpr, exprType((Node*)funcexpr), exprTypmod((Node*)funcexpr), funcexpr->funccollid);
            if (T_Const == nodeTag((Node*)expr)) {
                target_expr = expr;
            }
        } break;

        case T_RelabelType: {
            expr = (Node*)(((RelabelType*)expr)->arg);
            if (T_Const == nodeTag((Node*)expr)) {
                target_expr = expr;
            }
        } break;

        default:
            target_expr = NULL;
            break;
    }

    return target_expr;
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		:
 * Description	:
 * Notes		:
 */
static bool ConfirmTypeInfo(Oid* target_oid, int* target_mod, Const* src, Form_pg_attribute attrs, bool isinterval)
{
    Assert(src && target_oid && target_mod);

    *target_oid = attrs->atttypid;
    *target_mod = attrs->atttypmod;

    if (isinterval) {
        Oid srcid = src->consttype;

        if ((*target_oid == DATEOID) || (*target_oid == TIMESTAMPOID) || (*target_oid == TIMESTAMPTZOID)) {
            if (srcid == INTERVALOID) {
                *target_oid = INTERVALOID;
                *target_mod = src->consttypmod;
            } else if (srcid == UNKNOWNOID) {
                *target_oid = INTERVALOID;
                *target_mod = -1;
            } else {
                return false;
            }
        }
    }

    return true;
}

static void ReportListPartitionIntersect(const List* partitionList, List** values[], int partValueLen[],
    int idxFirst, int idxSecond)
{
    for (int e = 0; e < partitionList->length; ++e) {
        for (int f = 0; f < partValueLen[e]; ++f) {
            list_free_ext(values[e][f]);
        }
        pfree_ext(values[e]);
    }
    int i = 0;
    ListCell* cell;
    if (idxFirst == idxSecond) {
        foreach (cell, partitionList) {
            if (i == idxFirst) {
                ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                    errmsg("list partition %s has overlapped value",
                           ((ListPartitionDefState*)lfirst(cell))->partitionName)));
            }
            ++i;
        }
    } else {
        int bigIdx = (idxFirst >= idxSecond) ? idxFirst : idxSecond;
        int smallIdx = (idxFirst >= idxSecond) ? idxSecond : idxFirst;
        char* partNameFirst = NULL;
        char* partNameSecond = NULL;
        foreach (cell, partitionList) {
            if (i == smallIdx) {
                partNameFirst = ((ListPartitionDefState*)lfirst(cell))->partitionName;
            } else if (i == bigIdx) {
                partNameSecond = ((ListPartitionDefState*)lfirst(cell))->partitionName;
                break;
            }
            ++i;
        }
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
            errmsg("list partition %s and %s has overlapped value", partNameFirst, partNameSecond)));
    }
}

/* Each Const in RowExpr corresponds to a partition key column. Check length and datatype. */
static void sqlcmd_check_list_partition_rowexpr_bound(RowExpr *bound, const List *key_pos, FormData_pg_attribute *attrs,
    List *part_def_list, char* part_name)
{
    if (list_length(bound->args) != list_length(key_pos)) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
            errmsg("Invalid partition values"),
            errdetail("list partition values in \"%s\" does not match the number of partition keys", part_name)));
    }

    ListCell* keyvalue_cell = NULL;
    ListCell* keypos_cell = NULL;
    /* check multi-keys partition boundary values datatype */
    forboth (keyvalue_cell, bound->args, keypos_cell, key_pos) {
        Const* key_value = (Const*)lfirst(keyvalue_cell);
        FormData_pg_attribute key_pg_attr = attrs[lfirst_int(keypos_cell)];
        if (!can_coerce_type(1, &key_value->consttype, &key_pg_attr.atttypid, COERCION_IMPLICIT)) {
            list_free_ext(part_def_list);
            ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH),
                errmsg("list partition value in \"%s\" does not match datatype of partition key \"%s\"",
                    part_name, NameStr(key_pg_attr.attname))));
        }
    }
}

/* Check the boundary length and datatype of each list partition. */
static void sqlcmd_check_list_partition_boundary(ListPartitionDefState *part_def, const List *key_pos,
    FormData_pg_attribute *attrs, List *part_def_list, bool* has_default)
{
    List *boundaries = part_def->boundary;
    char* part_name = part_def->partitionName;
    Const* key_value_const = NULL;
    Oid first_key_type = attrs[linitial_int(key_pos)].atttypid;

    foreach_cell(bound_cell, boundaries) {
        Node* bound = (Node*)lfirst(bound_cell);
        /* check multi-keys partition boundary values */
        if (IsA(bound, RowExpr)) {
            sqlcmd_check_list_partition_rowexpr_bound((RowExpr*)bound, key_pos, attrs, part_def_list, part_name);
            continue;
        }

        key_value_const = (Const *)bound;
        if (key_value_const->ismaxvalue) {
            /* default partition boundary can only have one max Const */
            if (list_length(boundaries) != 1) {
                ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                    errmsg("The default partition can have only one boundary value.")));
            }
            /* Cannot have two default partition */
            if (*has_default) {
                ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                    errmsg("Partition table has multiple default partitions")));
            }
            *has_default = true;
            continue;
        }
        /* check single-key partition */
        if (list_length(key_pos) != 1) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                errmsg("Invalid partition values"),
                errdetail("list partition values in \"%s\" does not match the number of partition keys", part_name)));
        }
        /* check single-key partition boundary value datatype */
        if (!can_coerce_type(1, &key_value_const->consttype, &first_key_type, COERCION_IMPLICIT)) {
            list_free_ext(part_def_list);
            ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH),
                errmsg("list partition value in \"%s\" does not match datatype of partition key \"%s\"",
                    part_name, NameStr(attrs[linitial_int(key_pos)].attname))));
        }
    }
}

static void sqlcmd_check_list_partition_have_duplicate_values(List** key_values_array[], int part_idx, int bound_idx,
    List *partition_list, int part_value_len[])
{
    ListCell* c1 = NULL;
    ListCell* c2 = NULL;
    for (int k = 0; k < bound_idx; ++k) {
        forboth (c1, key_values_array[part_idx][bound_idx], c2, key_values_array[part_idx][k]) {
            if (ConstCompareWithNull((Const*)lfirst(c1), (Const*)lfirst(c2), ((Const*)lfirst(c2))->constcollid) != 0) {
                break;
            }
        }
        /* All key values are equal, the key value set is duplicate. */
        if (c1 == NULL) {
            ReportListPartitionIntersect(
                partition_list, key_values_array, part_value_len, part_idx, part_idx);
        }
    }
}

static void sqlcmd_check_two_list_partition_values_overlapped(List** key_values_array[], int p1_idx, int b1_idx,
    int p2_idx, int b2_idx, List *partition_list, int part_value_len[])
{
    ListCell* c1 = NULL;
    ListCell* c2 = NULL;
    Const* con1 = NULL;
    Const* con2 = NULL;
    forboth (c1, key_values_array[p1_idx][b1_idx], c2, key_values_array[p2_idx][b2_idx]) {
        con1 = (Const*)lfirst(c1);
        con2 = (Const*)lfirst(c2);
        if (con1->ismaxvalue || con2->ismaxvalue) {
            Assert(!(con1->ismaxvalue && con2->ismaxvalue));
            break;
        }
        if (ConstCompareWithNull(con1, con2, con2->constcollid) != 0) {
            break;
        }
    }
    /* All key values are equal, the key value set is duplicate. */
    if (c1 == NULL) {
        ReportListPartitionIntersect(partition_list, key_values_array, part_value_len, p2_idx, p1_idx);
    }
}

static void sqlcmd_check_list_value_overlapped_with_others(List** key_values_array[], int part_idx, int bound_idx,
    List *partition_list, int part_value_len[])
{
    for (int m = 0; m < part_idx; ++m) {
        for (int n = 0; n < part_value_len[m]; ++n) {
            sqlcmd_check_two_list_partition_values_overlapped(
                key_values_array, part_idx, bound_idx, m, n, partition_list, part_value_len);
        }
    }
}

void CompareListValue(const List* pos, FormData_pg_attribute* attrs, List *partitionList, bool partkeyIsFunc)
{
    if (pos == NULL || attrs == NULL) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_OPERATION), errmsg("invalid list partiiton table definition")));
    }

    List** values_array[partitionList->length]; // save all the list partion values.
    int part_value_len[partitionList->length]; // save list length of each partitionList
    int part_list_idx = 0;
    bool has_default = false;
    /* Check if datatype of values ars consistent with partition keys' */
    foreach_cell(cell, partitionList) {
        ListPartitionDefState *part_def = (ListPartitionDefState*)lfirst(cell);
        sqlcmd_check_list_partition_boundary(part_def, pos, attrs, partitionList, &has_default);
        part_value_len[part_list_idx] = list_length(part_def->boundary);
        values_array[part_list_idx] = GetListPartitionValueLists(pos, attrs, part_def->boundary, partkeyIsFunc);
        ++part_list_idx;
    }

    /* Check if list partition has intersect values */
    /* XXX need a better way to reduce time complexity */
    for (int i = 0; i < partitionList->length; ++i) {
        for (int j = 0; j < part_value_len[i]; ++j) {
            /* Check if value overlapped in same list */
            sqlcmd_check_list_partition_have_duplicate_values(values_array, i, j, partitionList, part_value_len);
            /* Check if value overlapped in different list */
            sqlcmd_check_list_value_overlapped_with_others(values_array, i, j, partitionList, part_value_len);
        }
    }
    for (int e = 0; e < partitionList->length; ++e) {
        for (int f = 0; f < part_value_len[e]; ++f) {
            list_free_ext(values_array[e][f]);
        }
        pfree_ext(values_array[e]);
    }
}


/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		:
 * Description	:
 * Notes		:
 */
void ComparePartitionValue(List *pos, FormData_pg_attribute *attrs, List *partitionList, bool isPartition,
                           bool partkeyIsFunc)
{
    Const* pre_value = NULL;
    Const* cur_value = NULL;
    Const** pre = NULL;
    Const** cur = NULL;
    ListCell* cell = NULL;
    List* value = NIL;
    bool is_intreval = false;
    int result = 0;
    int counter = 0;
    errno_t rc = EOK;

    if (pos == NULL || attrs == NULL) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
            errmsg(isPartition ? "invalid range partition table definition" : 
                "invalid range distribution table definition")));
    }
    /* no partitionvalues */
    if (!pos->length) {
        list_free_ext(pos);
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION), 
            errmsg(isPartition ? "there is no partition key" : "there is no distribution key")));
    }
    pre = (Const**)palloc0(pos->length * sizeof(Const*));
    cur = (Const**)palloc0(pos->length * sizeof(Const*));

    foreach (cell, partitionList) {
        value = ((RangePartitionDefState*)lfirst(cell))->boundary;

        if (pre_value == NULL) {
            pre_value = GetPartitionValue(pos, attrs, value, is_intreval, isPartition, partkeyIsFunc);
            for (counter = 0; counter < pos->length; counter++) {
                pre[counter] = pre_value + counter;
            }
        } else {
            cur_value = GetPartitionValue(pos, attrs, value, is_intreval, isPartition, partkeyIsFunc);
            for (counter = 0; counter < pos->length; counter++) {
                cur[counter] = cur_value + counter;
            }
            result = partitonKeyCompare(cur, pre, pos->length);

            /* compare partition value */
            if (result <= 0) {
                pfree_ext(pre);
                pfree_ext(cur);
                pfree_ext(pre_value);
                pfree_ext(cur_value);
                list_free_ext(pos);
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_OPERATION),
                        errmsg((isPartition ? "partition bound of partition \"%s\" is too low" : 
                            "distribution bound of slice \"%s\" is too low"),
                            ((RangePartitionDefState*)lfirst(cell))->partitionName)));
            }

            rc = memcpy_s(pre, pos->length * sizeof(Const*), cur, pos->length * sizeof(Const*));
            securec_check(rc, "\0", "\0");
            pfree_ext(pre_value);
            pre_value = cur_value;
            cur_value = NULL;
        }
    }
    pfree_ext(pre);
    pfree_ext(cur);
    pfree_ext(pre_value);
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		:
 * Description	:
 * Notes		:
 */
static void ATPrepAddPartition(Relation rel)
{
    if (!RELATION_IS_PARTITIONED(rel)) {
        ereport(
            ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("can not add partition against NON-PARTITIONED table")));
    }

    if (rel->partMap->type == PART_TYPE_INTERVAL && !u_sess->is_partition_autonomous_session) {
        ereport(ERROR, (errcode(ERRCODE_OPERATE_NOT_SUPPORTED),
            errmsg("can not add partition against interval partitioned table")));
    }
}

static void ATPrepAddSubPartition(Relation rel)
{
    if (!RelationIsSubPartitioned(rel)) {
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
            errmsg("Wrong object type"),
            errdetail("Can not add subpartition against NON-SUBPARTITIONED table"),
            errcause("ADD SUBPARTITION works on a NON-SUBPARTITIONED table"),
            erraction("Please check DDL syntax for \"ADD SUBPARTITION\"")));
    }

    if (rel->partMap->type == PART_TYPE_INTERVAL) {
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
            errmsg("Un-support feature"),
            errdetail("Can not add subpartition against interval partitioned table"),
            errcause("ADD SUBPARTITION works on an interval partitioned table"),
            erraction("Please check DDL syntax for \"ADD SUBPARTITION\"")));
    }
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		:
 * Description	:
 * Notes		:
 */
static void ATPrepDropPartition(Relation rel)
{
    if (!RELATION_IS_PARTITIONED(rel)) {
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
            errmsg("Un-support feature"),
            errdetail("Can not drop partition against NON-PARTITIONED table"),
            errcause("DROP PARTITION works on a NON-PARTITIONED table"),
            erraction("Please check DDL syntax for \"DROP PARTITION\"")));
    }

    if (rel->partMap->type == PART_TYPE_HASH) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION), errmsg("Droping hash partition is unsupported.")));
    }
}

static void ATPrepDropSubPartition(Relation rel)
{
    if (!RelationIsSubPartitioned(rel)) {
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
            errmsg("Un-support feature"),
            errdetail("Can not drop subpartition against NON-SUBPARTITIONED table"),
            errcause("DROP SUBPARTITION works on a NON-SUBPARTITIONED table"),
            erraction("Please check DDL syntax for \"DROP SUBPARTITION\"")));
    }

    char subparttype = PART_STRATEGY_INVALID;
    RelationGetSubpartitionInfo(rel, &subparttype, NULL, NULL);
    if (subparttype == PART_STRATEGY_HASH) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION), errmsg("Un-support feature"),
            errdetail("The syntax is unsupported for hash subpartition"),
            errcause("Try DROP SUBPARTITION on a hash-subpartitioned table"),
            erraction("Please check DDL syntax for \"DROP SUBPARTITION\"")));
    }
}

static void ATPrepUnusableIndexPartition(Relation rel)
{
    if (!RelationIsPartitioned(rel)) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("can not set unusable index partition against NON-PARTITIONED index")));
    }
}

static void ATPrepUnusableAllIndexOnPartition(Relation rel)
{
    if (!RELATION_IS_PARTITIONED(rel)) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("can not set all index unusable on one partition against NON-PARTITIONED table")));
    }
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		:
 * Description	:
 * Notes		:
 */
static void ATPrepEnableRowMovement(Relation rel)
{
    if (!RELATION_IS_PARTITIONED(rel)) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("can not enable row movement against NON-PARTITIONED table")));
    }
}
/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		:
 * Description	:
 * Notes		:
 */
static void ATPrepDisableRowMovement(Relation rel)
{
    if (!RELATION_IS_PARTITIONED(rel)) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("can not disable row movement against NON-PARTITIONED table")));
    }
    if (RelationIsColStore(rel)) {
        ereport(NOTICE,
            (errmsg("disable row movement is invalid for column stored tables. They always enable row movement between "
                    "partitions.")));
    }
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		:
 * Description	:
 * Notes		:
 */
static void ATPrepTruncatePartition(Relation rel)
{
    AclResult aclresult;
    if (!RELATION_IS_PARTITIONED(rel)) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("can not truncate partition against NON-PARTITIONED table")));
    }

    aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), ACL_TRUNCATE);
    if (aclresult != ACLCHECK_OK)
        aclcheck_error(aclresult, ACL_KIND_CLASS, RelationGetRelationName(rel));
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		:
 * Description	:
 * Notes		:
 */
static void ATPrepTruncateSubPartition(Relation rel)
{
    AclResult aclresult;
    if (!RELATION_IS_PARTITIONED(rel)) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("can not truncate partition against NON-PARTITIONED table")));
    }

    aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), ACL_TRUNCATE);
    if (aclresult != ACLCHECK_OK)
        aclcheck_error(aclresult, ACL_KIND_CLASS, RelationGetRelationName(rel));
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		: ALTER TABLE EXCHANGE PARTITION WITH TABLE
 * Description	: check change with table which is or not ordinary table
 * Notes		:
 */
static void ATPrepExchangePartition(Relation rel)
{
    if (!RELATION_IS_PARTITIONED(rel)) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("can not exchange partition against NON-PARTITIONED table")));
    }

    if (RelationIsSubPartitioned(rel)) {
        ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                 (errmsg("Un-support feature"),
                  errdetail("For subpartition table, exchange partition is not yet supported."),
                  errcause("The function is not implemented."), erraction("Use other actions instead."))));
    }
}

static void ATPrepMergePartition(Relation rel)
{
    if (!RELATION_IS_PARTITIONED(rel)) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("can not merge partitions against NON-PARTITIONED table")));
    }

    if (RelationIsSubPartitioned(rel)) {
        ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                 (errmsg("Un-support feature"),
                  errdetail("For subpartition table, merge partitions is not yet supported."),
                  errcause("The function is not implemented."), erraction("Use other actions instead."))));
    }

    if (rel->partMap->type == PART_TYPE_LIST || rel->partMap->type == PART_TYPE_HASH) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("can not merge LIST/HASH partition table")));
    }
}

static void ATPrepSplitPartition(Relation rel)
{
    if (!RELATION_IS_PARTITIONED(rel)) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("can not split partition against NON-PARTITIONED table")));
    }

    if (RelationIsSubPartitioned(rel)) {
        ereport(
            ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
             (errmsg("Un-support feature"),
              errdetail("For subpartition table, split partition is not yet supported."),
              errcause("The function is not implemented."), erraction("Use other actions instead."))));
    }

    if (rel->partMap->type == PART_TYPE_LIST || rel->partMap->type == PART_TYPE_HASH) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("can not split LIST/HASH partition table")));
    }
}

static void ATPrepSplitSubPartition(Relation rel)
{
    if (!RelationIsSubPartitioned(rel)) {
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                        errmsg("can not split subpartition against NON-SUBPARTITIONED table")));
    }
}

static void ATPrepResetPartitionno(Relation rel)
{
    if (!RELATION_IS_PARTITIONED(rel)) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("can not reset partitionno against NON-PARTITIONED table")));
    }
}

static void ATExecAddPartition(Relation rel, AddPartitionState *partState)
{
    Assert(RELATION_IS_PARTITIONED(rel));

    int partitionno = -GetCurrentPartitionNo(RelOidGetPartitionTupleid(rel->rd_id));
    Assert(PARTITIONNO_IS_VALID(partitionno));

    ListCell* cell = NULL;
    ListCell* subcell = NULL;
    foreach (cell, partState->partitionList) {
        partitionno++;
        PartitionDefState* partitionDefState = (PartitionDefState*)lfirst(cell);
        partitionDefState->partitionno = partitionno;
        int subpartitionno = 0;
        foreach(subcell, partitionDefState->subPartitionDefState) {
            subpartitionno++;
            PartitionDefState* subpartitionDefState = (PartitionDefState*)lfirst(subcell);
            subpartitionDefState->partitionno = subpartitionno;
        }
    }

    if (rel->partMap->type == PART_TYPE_LIST) {
        if (!IsA(linitial(partState->partitionList), ListPartitionDefState)) {
            ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("can not add none-list partition to list partition table")));
        }
    } else if (rel->partMap->type == PART_TYPE_HASH) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("can not add hash partition")));
    } else {
        if (!IsA(linitial(partState->partitionList), RangePartitionDefState)) {
            ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("can not add none-range partition to range partition table")));
        }
    }

    ATExecAddPartitionInternal(rel, partState);
    /* inplace update on partitioned table, because we can't cover the wait_clean_gpi info, which is inplace updated */
    UpdateCurrentPartitionNo(RelOidGetPartitionTupleid(rel->rd_id), -partitionno, true);
}

/* check tablespace permission for add partition/subpartition */
static void CheckTablespaceForAddPartition(Relation rel, List *partDefStateList)
{
    ListCell *cell = NULL;
    foreach (cell, partDefStateList) {
        PartitionDefState *partDef = (PartitionDefState*)lfirst(cell);
        if (PointerIsValid(partDef->tablespacename)) {
            CheckPartitionTablespace(partDef->tablespacename, rel->rd_rel->relowner);
        }
        CheckTablespaceForAddPartition(rel, partDef->subPartitionDefState);
    }
}

static void CheckPartitionNameConflictForAddPartition(List *newPartitionNameList, List *existingPartitionNameList)
{
    Assert(PointerIsValid(newPartitionNameList));
    Assert(PointerIsValid(existingPartitionNameList));


    ListCell *cell1 = NULL;
    ListCell *cell2 = NULL;
    ListCell *lc = NULL;
    foreach (cell1, newPartitionNameList) {
        char *newPartitionName1 = (char *)lfirst(cell1);

        /* 1. the newPartitionNameList should not be different itself */
        lc = cell1;
        while ((lc = lnext(lc)) != NULL) {
            char *newPartitionName2 = (char *)lfirst(lc);
            if (strcmp(newPartitionName1, newPartitionName2) == 0) {
                ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT),
                    errmsg("The partition name \"%s\" is duplicated with another new partition name",
                        newPartitionName1),
                    errdetail("N/A"),
                    errcause("When ADD PARTITION/SUBPARTITION, one partition name is duplicated with another one"),
                    erraction("Check the syntax, and change the duplicated partition name")));
            }
        }

        /* 2. the newPartitionNameList should not be different from existingPartitionNameList */
        foreach (cell2, existingPartitionNameList) {
            char *existingPartitionName = (char *)lfirst(cell2);
            if (strcmp(newPartitionName1, existingPartitionName) == 0) {
                ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT),
                    errmsg("The partition name \"%s\" is duplicated with existing partition name", newPartitionName1),
                    errdetail("N/A"),
                    errcause(
                        "When ADD PARTITION/SUBPARTITION, one partition name is duplicated with the existing name"),
                    erraction("Check the syntax, and change the duplicated partition name")));
            }
        }
    }
}

/*
 * This function is used to find an existing list partition by the new boundary.
 * Used for adding a list partition syntax, for example:
 * 'ADD PARTITION VALUES (listValueList)' or 'SPLIT PARTITION VALUES (expr_list)'
 */
static Oid FindPartOidByListBoundary(Relation rel, ListPartitionMap *partMap, Node* boundKey, bool partkeyIsFunc)
{
    List *partKeyValueList = NIL;
    Oid res;
    if (IsA(boundKey, RowExpr)) { /* Multi-keys partition boundary values */
        partKeyValueList = transformConstIntoTargetType(
            rel->rd_att->attrs, partMap->base.partitionKey, ((RowExpr*)boundKey)->args);
        res = PartitionValuesGetPartitionOid(rel, partKeyValueList, AccessShareLock, false, true, false);
        list_free_ext(partKeyValueList);
        return res;
    }

    Const* con = (Const*)boundKey;
    FormData_pg_attribute attr = rel->rd_att->attrs[partMap->base.partitionKey->values[0] - 1];

    if (con->ismaxvalue) {
        /*
         * DEFAULT boundary of a list partition has only one Const.
         * So it cannot be used to PartitionValuesGetPartitionOid for multi-keys partition.
         * Just return default partition Oid.
         */
        for (int i = 0; i < partMap->listElementsNum; i++) {
            ListPartElement *list = &partMap->listElements[i];
            if (list->boundary[0].values[0]->ismaxvalue) {
                return list->partitionOid;
            }
        }
        return InvalidOid;
    }
    con = (Const*)GetTargetValue(&attr, con, false, partkeyIsFunc);
    if (!PointerIsValid(con)) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
            errmsg("partition key value must be const or const-evaluable expression")));
    }
    if (!OidIsValid(con->constcollid) && OidIsValid(attr.attcollation)) {
        con->constcollid = attr.attcollation;
    }
    partKeyValueList = list_make1(con);
    res = PartitionValuesGetPartitionOid(rel, partKeyValueList, AccessShareLock, false, true, false);
    list_free_ext(partKeyValueList);
    return res;
}

static void CheckPartitionValueConflictForAddPartition(Relation rel, Node *partDefState, bool partkeyIsFunc)
{
    Assert(IsA(partDefState, RangePartitionDefState) || IsA(partDefState, ListPartitionDefState));

    ListCell *cell = NULL;
    List *partKeyValueList = NIL;
    Oid existingPartOid = InvalidOid;

    incre_partmap_refcount(rel->partMap);

    int partNum = getNumberOfPartitions(rel);
    if (nodeTag(partDefState) == T_RangePartitionDefState) {
        RangePartitionDefState *partDef = (RangePartitionDefState *)partDefState;
        RangePartitionMap *partMap = (RangePartitionMap *)rel->partMap;
        Const *curBound = (Const *)copyObject(partMap->rangeElements[partNum - 1].boundary[0]);
        Const *val = partDef->curStartVal;
        if (!curBound->ismaxvalue && val != NULL && partitonKeyCompare(&curBound, &val, 1) != 0) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                errmsg("start value of partition \"%s\" NOT EQUAL up-boundary of last partition.",
                partDef->partitionInitName ? partDef->partitionInitName : partDef->partitionName)));
        }
        partKeyValueList = transformConstIntoTargetType(rel->rd_att->attrs, partMap->base.partitionKey,
                                                        partDef->boundary, partkeyIsFunc);
        pfree_ext(curBound);
        existingPartOid = PartitionValuesGetPartitionOid(rel, partKeyValueList, AccessShareLock, false, true, false);
        list_free_ext(partKeyValueList);
        if (OidIsValid(existingPartOid)) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                errmsg("upper boundary of adding partition MUST overtop last existing partition")));
        }
    } else {
        ListPartitionDefState *partDef = (ListPartitionDefState *)partDefState;

        foreach (cell, partDef->boundary) {
            existingPartOid = FindPartOidByListBoundary(rel, (ListPartitionMap *)rel->partMap, (Node*)lfirst(cell), partkeyIsFunc);
            if (OidIsValid(existingPartOid)) {
                ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                    errmsg("list boundary of adding partition MUST NOT overlap with existing partition")));
            }
        }
    }

    decre_partmap_refcount(rel->partMap);
}

bool IsPartKeyFunc(Relation rel, bool isPartRel, bool forSubPartition, PartitionExprKeyInfo* partExprKeyInfo)
{
    HeapTuple partTuple = NULL;
    if (forSubPartition) {
        if (isPartRel) {
            partTuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(rel->rd_id));
        } else {
            PartitionMap* map = rel->partMap;
            Oid partitionId = InvalidOid;
            if (map->type == PART_TYPE_LIST) {
                partitionId = ((ListPartitionMap*)map)->listElements[0].partitionOid;
            } else if (map->type == PART_TYPE_HASH) {
                partitionId = ((HashPartitionMap*)map)->hashElements[0].partitionOid;
            } else {
                partitionId = ((RangePartitionMap*)map)->rangeElements[0].partitionOid;
            }
            partTuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(partitionId));
        }
    } else {
        partTuple = searchPgPartitionByParentIdCopy(PART_OBJ_TYPE_PARTED_TABLE, rel->rd_id);
    }
    if (!partTuple)
        ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("The partTuple for oid %d can't be found", rel->rd_id)));

    bool isNull = false;
    Datum datum = 0;
    datum = SysCacheGetAttr(PARTRELID, partTuple, Anum_pg_partition_partkeyexpr, &isNull);
    if (partExprKeyInfo)
        partExprKeyInfo->partkeyexprIsNull = isNull;
    if (isNull) {
        if (forSubPartition)
            ReleaseSysCache(partTuple);
        else
            heap_freetuple(partTuple);
        return false;
    }

    char* partKeyStr = TextDatumGetCString(datum);
    Node* partkeyexpr = NULL;
    if (forSubPartition)
        ReleaseSysCache(partTuple);
    else
        heap_freetuple(partTuple);

    partkeyexpr = (Node*)stringToNode_skip_extern_fields(partKeyStr);
    if (!partExprKeyInfo)
        pfree_ext(partKeyStr);
    else
        partExprKeyInfo->partExprKeyStr = partKeyStr;

    if (!partkeyexpr)
        ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("The partkeyexpr can't be NULL")));

    if (partkeyexpr->type == T_OpExpr)
        return false;
    else if (partkeyexpr->type == T_FuncExpr)
        return true;
    else
        ereport(ERROR,
            (errcode(ERRCODE_NODE_ID_MISSMATCH),
                errmsg("The node type %d is wrong, it must be T_OpExpr or T_FuncExpr", partkeyexpr->type)));
    /* suppress warning: -Wreturn-type */
    return false;
}

static void CheckSubpartitionForAddPartition(Relation rel, Node *partDefState)
{
    Assert(IsA(partDefState, RangePartitionDefState) || IsA(partDefState, ListPartitionDefState));

    List *subPartitionDefStateList = NIL;
    if (nodeTag(partDefState) == T_RangePartitionDefState) {
        RangePartitionDefState *partDef= (RangePartitionDefState*)partDefState;
        subPartitionDefStateList = partDef->subPartitionDefState;
    } else {
        ListPartitionDefState *partDef= (ListPartitionDefState*)partDefState;
        subPartitionDefStateList = partDef->subPartitionDefState;
    }

    if (subPartitionDefStateList == NIL) {
        return;
    }

    /* a. only can be subpartition table */
    if (!RelationIsSubPartitioned(rel)) {
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
            errmsg("Un-support feature"),
            errdetail("Can not add subpartition against NON-SUBPARTITIONED table"),
            errcause("ADD SUBPARTITION works on a NON-SUBPARTITIONED table"),
            erraction("Please check DDL syntax for \"ADD SUBPARTITION\"")));
    }

    /* get the nessary subpartition info */
    char subparttype = PART_STRATEGY_INVALID;
    List *subpartKeyPosList = NIL;
    RelationGetSubpartitionInfo(rel, &subparttype, &subpartKeyPosList, NULL);

    /* b. check the subpartition type is same as the relation itself */
    NodeTag subparttypeTag = GetPartitionStateType(subparttype);
    ListCell* subcell = NULL;
    foreach (subcell, subPartitionDefStateList) {
        Node *subPartitionDefState = (Node *)lfirst(subcell);
        if (nodeTag(subPartitionDefState) != subparttypeTag) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                errmsg("The syntax format of subpartition is incorrect, the declaration and "
                "definition of the subpartition do not match."),
                errdetail("The syntax format of subpartition %s is incorrect.",
                GetPartitionDefStateName(subPartitionDefState)),
                errcause("The declaration and definition of the subpartition do not match."),
                erraction("Consistent declaration and definition of subpartition.")));
        }
    }

    bool partkeyIsFunc = IsPartKeyFunc(rel, false, true);
    /* c. subpartition values constraint */
    switch (subparttype) {
        case PART_STRATEGY_RANGE:
            ComparePartitionValue(subpartKeyPosList, (RelationGetDescr(rel))->attrs, subPartitionDefStateList, true, partkeyIsFunc);
            break;
        case PART_STRATEGY_LIST:
            CompareListValue(subpartKeyPosList, (RelationGetDescr(rel))->attrs, subPartitionDefStateList, partkeyIsFunc);
            break;
        case PART_STRATEGY_HASH:
            break;
        default:
            ereport(ERROR, (errmodule(MOD_COMMAND), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Only support RANGE/LIST/HASH for subpartition table"),
                errdetail("N/A"), errcause("The partition type is incorrect."),
                erraction("Use the correct partition type.")));
    }

    list_free_ext(subpartKeyPosList);
}

static void CheckForAddPartition(Relation rel, List *partDefStateList)
{
    Assert(RELATION_IS_PARTITIONED(rel));

#ifdef ENABLE_HTAP
    if (RelHasImcs(RelationGetRelid(rel))) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                errmsg("The rel enable imcstore, please unimcstore if want to add partition.")));
    }
#endif

    /* check 1: tablespace privileges */
    CheckTablespaceForAddPartition(rel, partDefStateList);

    /* check 2: can not add more partition, because more enough */
    List* newPartitionNameList = GetPartitionNameList(partDefStateList);
    List* existingPartitionNameList = RelationGetPartitionNameList(rel);
    int npart = list_length(newPartitionNameList);
    int nnewpart = list_length(existingPartitionNameList);
    if ((npart + nnewpart) > MAX_PARTITION_NUM) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                errmsg("too many partitions for partitioned table"),
                errhint("Number of partitions can not be more than %d", MAX_PARTITION_NUM)));
    }

    /* check 3: name conflict check */
    CheckPartitionNameConflictForAddPartition(newPartitionNameList, existingPartitionNameList);
    list_free_deep(existingPartitionNameList);
    /* don't free the cell of newPartitionNameList */
    list_free_ext(newPartitionNameList);

    /* check 4: partition values constraint */
    bool partkeyIsFunc = IsPartKeyFunc(rel, false, false);
    int2vector *partitionKey = GetPartitionKey(rel->partMap);
    List *partKeyPosList = NIL;
    for (int i = 0; i < partitionKey->dim1; i++) {
        partKeyPosList = lappend_int(partKeyPosList, partitionKey->values[i] - 1);
    }
    if (rel->partMap->type == PART_TYPE_RANGE) {
        ComparePartitionValue(partKeyPosList, (RelationGetDescr(rel))->attrs, partDefStateList, true, partkeyIsFunc);
    } else if (rel->partMap->type == PART_TYPE_LIST) {
        CompareListValue(partKeyPosList, (RelationGetDescr(rel))->attrs, partDefStateList, partkeyIsFunc);
    } else if (PartitionMapIsInterval(rel->partMap)) {
        Assert(list_length(partDefStateList) == 1);
    }
    list_free_ext(partKeyPosList);

    ListCell *cell = NULL;
    foreach (cell, partDefStateList) {
        /* check 5: new adding partitions behind the last partition */
        CheckPartitionValueConflictForAddPartition(rel, (Node*)lfirst(cell), partkeyIsFunc);

        /* check 6: constraint for subpartition */
        CheckSubpartitionForAddPartition(rel, (Node*)lfirst(cell));
    }

    /* check 7: whether has the unusable local index */
    if (!checkRelationLocalIndexesUsable(rel)) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                errmsg("can't add partition bacause the relation %s has unusable local index",
                    NameStr(rel->rd_rel->relname)),
                errhint("please reindex the unusable index first.")));
    }
}

static void CheckForAddSubPartition(Relation rel, Relation partrel, List *subpartDefStateList)
{
    Assert(RELATION_IS_PARTITIONED(rel));
    Assert(RELATION_IS_PARTITIONED(partrel));

#ifdef ENABLE_HTAP
    if (RelHasImcs(RelationGetRelid(partrel))) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                errmsg("The part rel enable imcstore, please unimcstore if want to add subpartition.")));
    }
#endif

    /* check 1: tablespace privileges */
    CheckTablespaceForAddPartition(rel, subpartDefStateList);

    /* check 2: can not add more partition, because more enough */
    List* newPartitionNameList = GetPartitionNameList(subpartDefStateList);
    List* existingPartitionNameList = RelationGetPartitionNameList(rel);
    int npart = list_length(newPartitionNameList);
    int nnewpart = list_length(existingPartitionNameList);
    if ((npart + nnewpart) > MAX_PARTITION_NUM) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                errmsg("too many partitions for partitioned table"),
                errhint("Number of partitions can not be more than %d", MAX_PARTITION_NUM)));
    }

    /* check 3: name conflict check */
    CheckPartitionNameConflictForAddPartition(newPartitionNameList, existingPartitionNameList);
    list_free_deep(existingPartitionNameList);
    /* don't free the cell of newPartitionNameList */
    list_free_ext(newPartitionNameList);

    /* check 4: partition values constraint */
    bool partkeyIsFunc = IsPartKeyFunc(partrel, true, true);
    int2vector *partitionKey = GetPartitionKey(partrel->partMap);
    List *partKeyPosList = NIL;
    for (int i = 0; i < partitionKey->dim1; i++) {
        partKeyPosList = lappend_int(partKeyPosList, partitionKey->values[i] - 1);
    }
    if (partrel->partMap->type == PART_TYPE_RANGE) {
        ComparePartitionValue(partKeyPosList, (RelationGetDescr(partrel))->attrs, subpartDefStateList, true, partkeyIsFunc);
    } else if (partrel->partMap->type == PART_TYPE_LIST) {
        CompareListValue(partKeyPosList, (RelationGetDescr(partrel))->attrs, subpartDefStateList, partkeyIsFunc);
    }
    list_free_ext(partKeyPosList);

    ListCell *cell = NULL;
    foreach (cell, subpartDefStateList) {
        /* check 5: new adding partitions behind the last partition */
        CheckPartitionValueConflictForAddPartition(partrel, (Node*)lfirst(cell), partkeyIsFunc);
    }

    /* check 6: whether has the unusable local index */
    if (!checkRelationLocalIndexesUsable(rel)) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                errmsg("can't add partition bacause the relation %s has unusable local index",
                    NameStr(rel->rd_rel->relname)),
                errhint("please reindex the unusable index first.")));
    }
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		:
 * Description	:
 * Notes		:
 */
void ATExecAddPartitionInternal(Relation rel, AddPartitionState *partState)
{
    Relation pgPartRel = NULL;
    Oid newPartOid = InvalidOid;
    List *newSubpartOidList = NIL;
    Datum new_reloptions;
    Datum rel_reloptions;
    HeapTuple tuple;
    bool isnull = false;
    List* old_reloptions = NIL;
    ListCell* cell = NULL;
    Oid bucketOid;
    Relation parentrel = NULL;
    char partStrategy = PartTypeGetPartStrategy(rel->partMap->type);
    char subparttype = PART_STRATEGY_INVALID;
    int2vector *subpartitionKey = NULL;
    PartitionDefState* partDef = NULL;

    /* if the relation is a partrel of a subpartition, here we get the relation first */
    if (RelationIsPartitionOfSubPartitionTable(rel)) {
        /* the lock of parentrel has been obtained already, seen in ATExecAddSubPartition */
        parentrel = heap_open(rel->parentId, NoLock);
    }

    /* step 1: Check before the actual work */
    if (RelationIsPartitionOfSubPartitionTable(rel)) {
        CheckForAddSubPartition(parentrel, rel, partState->partitionList);
    } else {
        CheckForAddPartition(rel, partState->partitionList);
    }

    bool* isTimestamptz = CheckPartkeyHasTimestampwithzone(rel);
    bool *isTimestamptzForSubPartKey = NULL;
    if (RelationIsSubPartitioned(rel)) {
        List *subpartKeyPosList = NIL;
        RelationGetSubpartitionInfo(rel, &subparttype, &subpartKeyPosList, &subpartitionKey);
        isTimestamptzForSubPartKey = CheckSubPartkeyHasTimestampwithzone(rel, subpartKeyPosList);
        list_free_ext(subpartKeyPosList);
    }

    pgPartRel = relation_open(PartitionRelationId, RowExclusiveLock);

    /* step 2: add new partition entry in pg_partition */
    /* TRANSFORM into target first */
    Oid relOid =
        RelationIsPartitionOfSubPartitionTable(rel) ? ObjectIdGetDatum(rel->parentId) : ObjectIdGetDatum(rel->rd_id);
    tuple = SearchSysCache1(RELOID, relOid);
    if (!HeapTupleIsValid(tuple))
        ereport(ERROR, (errmodule(MOD_COMMAND), errcode(ERRCODE_CACHE_LOOKUP_FAILED),
            errmsg("cache lookup failed for relation %u", relOid), errdetail("N/A"),
                errcause("System error."), erraction("Contact engineer to support.")));
    rel_reloptions = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, &isnull);

    old_reloptions = untransformRelOptions(rel_reloptions);
    RemoveRedisRelOptionsFromList(&old_reloptions);
    new_reloptions = transformRelOptions((Datum)0, old_reloptions, NULL, NULL, false, false);
    ReleaseSysCache(tuple);

    if (old_reloptions != NIL)
        list_free_ext(old_reloptions);

    bucketOid = RelationGetBucketOid(rel);

    PartitionExprKeyInfo partExprKeyInfo = PartitionExprKeyInfo();
    partExprKeyInfo.partkeyIsFunc = IsPartKeyFunc(rel, false, true, &partExprKeyInfo);

    List *partitionNameList =
        list_concat(GetPartitionNameList(partState->partitionList), RelationGetPartitionNameList(rel));
    foreach (cell, partState->partitionList) {
        partDef = (PartitionDefState*)lfirst(cell);

        PartitionState *partitionState = makeNode(PartitionState);
        partitionState->partitionStrategy = partStrategy;
        partitionState->partitionNameList = partitionNameList;
        if (RelationIsSubPartitioned(rel)) {
            partitionState->subPartitionState = makeNode(PartitionState);
            partitionState->subPartitionState->partitionStrategy = subparttype;
            if (partDef->subPartitionDefState == NIL) {
                Node *subPartitionDefState = MakeDefaultSubpartition(partitionState, (PartitionDefState *)partDef);
                partDef->subPartitionDefState = lappend(partDef->subPartitionDefState, subPartitionDefState);
            }
        }

        if (rel->partMap->type == PART_TYPE_LIST) {
            newPartOid = HeapAddListPartition(pgPartRel,
                rel->rd_id,
                rel->rd_rel->reltablespace,
                bucketOid,
                (ListPartitionDefState *)partDef,
                rel->rd_rel->relowner,
                (Datum)new_reloptions,
                isTimestamptz,
                RelationGetStorageType(rel),
                subpartitionKey,
                RelationIsPartitionOfSubPartitionTable(rel),
                &partExprKeyInfo);
        } else {
            newPartOid = heapAddRangePartition(pgPartRel,
                rel->rd_id,
                rel->rd_rel->reltablespace,
                bucketOid,
                (RangePartitionDefState *)partDef,
                rel->rd_rel->relowner,
                (Datum)new_reloptions,
                isTimestamptz,
                RelationGetStorageType(rel),
                AccessExclusiveLock,
                subpartitionKey,
                RelationIsPartitionOfSubPartitionTable(rel),
                &partExprKeyInfo,
                partStrategy);
        }

        Oid partTablespaceOid =
            GetPartTablespaceOidForSubpartition(rel->rd_rel->reltablespace, partDef->tablespacename);
        newSubpartOidList = addNewSubPartitionTuplesForPartition(pgPartRel,
            newPartOid,
            partTablespaceOid,
            bucketOid,
            rel->rd_rel->relowner,
            (Datum)new_reloptions,
            isTimestamptzForSubPartKey,
            RelationGetStorageType(rel),
            partitionState,
            (Node *)partDef,
            AccessExclusiveLock);

        /* step 3: no need to update number of partitions in pg_partition */
        /*
         * We must bump the command counter to make the newly-created partition
         * tuple visible for opening.
         */
        CommandCounterIncrement();

        if (RelationIsColStore(rel)) {
            addCudescTableForNewPartition(rel, newPartOid);
            addDeltaTableForNewPartition(rel, newPartOid);
        }

        if (RelationIsPartitionOfSubPartitionTable(rel)) {
            addIndexForPartition(parentrel, newPartOid);
            addToastTableForNewPartition(rel, newPartOid, true);
        } else if (RelationIsSubPartitioned(rel)) {
            Assert(newSubpartOidList != NIL);
            Partition part = partitionOpen(rel, newPartOid, AccessExclusiveLock);
            Relation partrel = partitionGetRelation(rel, part);
            ListCell* lc = NULL;
            foreach (lc, newSubpartOidList) {
                Oid subpartOid = lfirst_oid(lc);
                addIndexForPartition(rel, subpartOid);
                addToastTableForNewPartition(partrel, subpartOid, true);
            }
            releaseDummyRelation(&partrel);
            partitionClose(rel, part, NoLock);
        } else {
            addIndexForPartition(rel, newPartOid);
            addToastTableForNewPartition(rel, newPartOid);
        }

        /* step 4: invalidate relation */
        if (RelationIsPartitionOfSubPartitionTable(rel)) {
            CacheInvalidateRelcache(parentrel);
            CacheInvalidatePartcacheByPartid(rel->rd_id);
        } else {
            CacheInvalidateRelcache(rel);
        }
        pfree_ext(partitionState->subPartitionState);
        pfree_ext(partitionState);
    }

    /* close relation, done */
    relation_close(pgPartRel, NoLock);
    pfree_ext(isTimestamptz);
    pfree_ext(isTimestamptzForSubPartKey);
    list_free_ext(partitionNameList);

    if (RelationIsPartitionOfSubPartitionTable(rel)) {
        heap_close(parentrel, NoLock);
    }
}

static void ATExecAddSubPartition(Relation rel, AddSubPartitionState *subpartState)
{
    Assert(PointerIsValid(subpartState->partitionName));
    Assert(RelationIsSubPartitioned(rel));

    /* get partoid and lock partition */
    Oid partOid = PartitionNameGetPartitionOid(rel->rd_id,
        subpartState->partitionName,
        PART_OBJ_TYPE_TABLE_PARTITION,
        ShareUpdateExclusiveLock,
        false,
        false,
        NULL,
        NULL,
        NoLock);
    Partition part = partitionOpen(rel, partOid, NoLock);
    Relation partrel = partitionGetRelation(rel, part);

    AddPartitionState* partState = makeNode(AddPartitionState);
    partState->partitionList = subpartState->subPartitionList;

    int subpartitionno = -GetCurrentSubPartitionNo(partOid);
    Assert(PARTITIONNO_IS_VALID(subpartitionno));
    ListCell* cell = NULL;
    foreach (cell, partState->partitionList) {
        subpartitionno++;
        PartitionDefState* partitionDefState = (PartitionDefState*)lfirst(cell);
        partitionDefState->partitionno = subpartitionno;
    }

    if (partrel->partMap->type == PART_TYPE_LIST) {
        if (!IsA(linitial(partState->partitionList), ListPartitionDefState)) {
            ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("can not add none-list subpartition to list subpartition table")));
        }
    } else if (partrel->partMap->type == PART_TYPE_HASH) {
        ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("can not add hash subpartition")));
    } else {
        if (!IsA(linitial(partState->partitionList), RangePartitionDefState)) {
            ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("can not add none-range subpartition to range subpartition table")));
        }
    }

    ATExecAddPartitionInternal(partrel, partState);
    UpdateCurrentSubPartitionNo(partOid, -subpartitionno);

    releaseDummyRelation(&partrel);
    partitionClose(rel, part, NoLock);
    pfree_ext(partState);
}

/* Assume the caller has already hold RowExclusiveLock on the pg_partition. */
static void UpdateIntervalPartToRange(Relation relPartition, Oid partOid, const char* stmt)
{
    bool dirty = false;
    /* Fetch a copy of the tuple to scribble on */
    HeapTuple parttup = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(partOid));
    if (!HeapTupleIsValid(parttup)) {
        ereport(ERROR,
            (errcode(ERRCODE_SQL_ROUTINE_EXCEPTION),
                errmsg("pg_partition entry for partid %u vanished during %s.", partOid, stmt)));
    }
    Form_pg_partition partform = (Form_pg_partition)GETSTRUCT(parttup);

    /* Apply required updates, if any, to copied tuple */
    if (partform->partstrategy == PART_STRATEGY_INTERVAL) {
        partform->partstrategy = PART_STRATEGY_RANGE;
        dirty = true;
    } else {
        ereport(LOG,
            (errcode(ERRCODE_SQL_ROUTINE_EXCEPTION),
                errmsg("pg_partition entry for partid %u is not a interval "
                       "partition when execute %s .",
                    partOid,
                    stmt)));
    }

    /* If anything changed, write out the tuple. */
    if (dirty) {
        simple_heap_update(relPartition, &parttup->t_self, parttup);
        CatalogUpdateIndexes(relPartition, parttup);
    }
    tableam_tops_free_tuple(parttup);
}

/* assume caller already hold AccessExclusiveLock on the partition being dropped
 * if the intervalPartOid is not InvalidOid, the interval partition which is specificed by it
 * need to be changed to normal range partition.
 */
void fastDropPartition(Relation rel, Oid partOid, const char* stmt, Oid intervalPartOid, bool sendInvalid)
{
    Partition part = NULL;
    Relation pg_partition = NULL;
    char* partName = NULL;
    pg_partition = relation_open(PartitionRelationId, RowExclusiveLock);

    /* step 2: drop the targeting partition entry in pg_partition */
    part = partitionOpenWithRetry(rel, partOid, AccessExclusiveLock, stmt);
    if (!part) {
        ereport(ERROR,
            (errcode(ERRCODE_LOCK_WAIT_TIMEOUT),
                errmsg("could not acquire AccessExclusiveLock on dest table partition \"%s\", %s failed",
                       getPartitionName(partOid, false), stmt)));
    }
    partName = pstrdup(PartitionGetPartitionName(part));
    /* drop toast table, index, and finally the partition iteselt */
    dropIndexForPartition(partOid);
    dropToastTableOnPartition(partOid);
    if (RelationIsColStore(rel)) {
        dropCuDescTableOnPartition(partOid);
        dropDeltaTableOnPartition(partOid);
    }
    heapDropPartition(rel, part);
    if (intervalPartOid) {
        UpdateIntervalPartToRange(pg_partition, intervalPartOid, stmt);
    }
    ereport(LOG,
            (errmsg("Partition %s(%d) has been fast dropped.", partName, partOid)));
    pfree_ext(partName);

    /* step 3: no need to update number of partitions in pg_partition */
    /* step 4: invalidate relation */
    /* if the relation is a partrel, we send invalid message to the partition itself,
     * else we send invalid message to the relation */
    if (sendInvalid) {
        if (RelationIsPartitionOfSubPartitionTable(rel)) {
            CacheInvalidatePartcacheByPartid(rel->rd_id);
        } else {
            CacheInvalidateRelcache(rel);
        }
    }


    /* make the dropping partition invisible, fresh partition map for the new partition */
    relation_close(pg_partition, RowExclusiveLock);

    /*
     * We must bump the command counter to make the newly-droped partition
     * tuple visible.
     */
    CommandCounterIncrement();
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		:
 * Description	:
 * Notes		:
 */
static void ATExecDropPartition(Relation rel, AlterTableCmd *cmd)
{
    Assert(RELATION_IS_PARTITIONED(rel));

    Oid partOid = InvalidOid;
    List *subpartOidList = NIL;
    Oid subpartOid = InvalidOid;
    ListCell* cell = NULL;
    Partition part = NULL;
    Relation partrel = NULL;

    /* getting the dropping partition's oid, and lock partition */
    partOid = GetPartOidByATcmd(rel, cmd, "DROP PARTITION");

    /* check 1: check validity of partition oid */
    if (!OidIsValid(partOid)) {
        ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("The partition number is invalid or out-of-range")));
    }

#ifdef ENABLE_HTAP
    if (RelHasImcs(partOid)) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                errmsg("The part rel enable imcstore, please unimcstore if want to drop it.")));
    }
#endif

    /* get subpartOidList if is subpartition, and lock subpartition */
    if (RelationIsSubPartitioned(rel)) {
        part = partitionOpen(rel, partOid, NoLock);
        partrel = partitionGetRelation(rel, part);
        subpartOidList = relationGetPartitionOidList(partrel);
        foreach (cell, subpartOidList) {
            subpartOid = lfirst_oid(cell);
            LockPartitionOid(partOid, subpartOid, AccessExclusiveLock);
        }
    }

    /* check 2: can not drop the last existing partition */
    if (getNumberOfPartitions(rel) == 1) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_OPERATION), errmsg("Cannot drop the only partition of a partitioned table")));
    }
    if (RelationIsSubPartitioned(rel)) {
        foreach (cell, subpartOidList) {
            subpartOid = DatumGetObjectId(lfirst(cell));
            AlterSubPartitionedSetWaitCleanGPI(cmd->alterGPI, rel, partOid, subpartOid);
        }
    } else {
        AlterPartitionedSetWaitCleanGPI(cmd->alterGPI, rel, partOid);
    }

    if (!cmd->alterGPI) {
        // Unusable Global Index
        ATUnusableGlobalIndex(rel);
    }

    Oid changeToRangePartOid = InvalidOid;
    if (rel->partMap->type != PART_TYPE_LIST && rel->partMap->type != PART_TYPE_HASH) {
#ifdef ENABLE_MULTIPLE_NODES
        if (unlikely(RelationIsTsStore(rel) && OidIsValid(RelationGetDeltaRelId(rel))) && IS_PGXC_DATANODE) {
            Tsdb::DeleteDeltaByPartition(GetActiveSnapshot(), rel, partOid);
        }
#endif  /* ENABLE_MULTIPLE_NODES */
        changeToRangePartOid = GetNeedDegradToRangePartOid(rel, partOid);
    }

    if (RelationIsSubPartitioned(rel)) {
        foreach (cell, subpartOidList) {
            subpartOid = DatumGetObjectId(lfirst(cell));
            /* we don't send invalid message to the partition here, as the partition will be dropped soon */
            fastDropPartition(partrel, subpartOid, "DROP SUBPARTITION", InvalidOid, false);
        }
        releasePartitionOidList(&subpartOidList);
        releaseDummyRelation(&partrel);
        partitionClose(rel, part, NoLock);
        fastDropPartition(rel, partOid, "DROP PARTITION");
    } else {
        fastDropPartition(rel, partOid, "DROP PARTITION", changeToRangePartOid);
    }
}

static void ATExecDropSubPartition(Relation rel, AlterTableCmd *cmd)
{
    Assert(RelationIsSubPartitioned(rel));

    Oid partOid = InvalidOid;
    Oid subpartOid = InvalidOid;

    /* getting the dropping subpartition's oid, and lock subpartition */
    subpartOid = GetSubpartOidByATcmd(rel, cmd, &partOid, "DROP SUBPARTITION");

    /* check 1: check validity of partition oid */
    if (!OidIsValid(partOid)) {
        ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
            errmsg("The partition which owns the subpartition is missing"),
            errdetail("N/A"),
            errcause("Maybe the subpartition table is dropped"),
            erraction("Check system table 'pg_partition' for more information")));
    }
    if (!OidIsValid(subpartOid)) {
        ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
            errmsg("The subpartition number is invalid or out-of-range"),
            errdetail("N/A"),
            errcause("Wrong or invalid value for DROP SUBPARTITION"),
            erraction("Please check DDL syntax for \"DROP SUBPARTITION\"")));
    }

#ifdef ENABLE_HTAP
    if (RelHasImcs(partOid)) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                errmsg("The subpart rel enable imcstore, please unimcstore if want to drop it.")));
    }
#endif

    Partition part = partitionOpen(rel, partOid, NoLock);
    Relation partrel = partitionGetRelation(rel, part);

    /* check 2: can not drop the last existing subpartition */
    if (getNumberOfPartitions(partrel) == 1) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
            errmsg("Cannot drop the only subpartition of a partitioned table"),
            errdetail("N/A"),
            errcause("DROP SUBPARTITION works on the partition which has only one subpartition"),
            erraction("Please check DDL syntax for \"DROP SUBPARTITION\"")));
    }
    AlterSubPartitionedSetWaitCleanGPI(cmd->alterGPI, rel, partOid, subpartOid);

    if (!cmd->alterGPI) {
        // Unusable Global Index
        ATUnusableGlobalIndex(rel);
    }

    fastDropPartition(partrel, subpartOid, "DROP SUBPARTITION");
    releaseDummyRelation(&partrel);
    partitionClose(rel, part, NoLock);
}

static Oid GetPartOidByATcmd(Relation rel, AlterTableCmd *cmd, const char *command)
{
    Oid partOid = InvalidOid;

    /* FIRST IS the PARTITION (partname) branch */
    if (PointerIsValid(cmd->name)) {
        partOid = PartitionNameGetPartitionOid(rel->rd_id,
            cmd->name,
            PART_OBJ_TYPE_TABLE_PARTITION,
            AccessExclusiveLock,
            false,
            false,
            NULL,
            NULL,
            NoLock);
        return partOid;
    }

    /* next IS the PARTITION FOR (partvalue) branch */
    RangePartitionDefState *rangePartDef = (RangePartitionDefState*)cmd->def;
    switch (rel->partMap->type) {
        case PART_TYPE_RANGE:
        case PART_TYPE_INTERVAL:
            rangePartDef->boundary = transformConstIntoTargetType(rel->rd_att->attrs,
                                                                  rel->partMap->partitionKey,
                rangePartDef->boundary);
            break;
        case PART_TYPE_LIST:
            rangePartDef->boundary = transformConstIntoTargetType(rel->rd_att->attrs,
                                                                  rel->partMap->partitionKey,
                rangePartDef->boundary);
            break;
        case PART_TYPE_HASH:
            rangePartDef->boundary = transformConstIntoTargetType(rel->rd_att->attrs,
                                                                  rel->partMap->partitionKey,
                rangePartDef->boundary);
            break;
        default:
            ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION), errmsg("Unknown partitioned type"),
                errdetail("This parttype is not supported for %s", command),
                errcause("Only range/list/hash/interval partitioned table is supported for %s", command),
                erraction("Please check DDL syntax for \"%s\"", command)));
    }
    partOid = PartitionValuesGetPartitionOid(rel,
        rangePartDef->boundary,
        AccessExclusiveLock,
        true,
        true, /* will check validity of partition oid next step */
        false);
    return partOid;
}

static Oid GetSubpartOidByATcmd(Relation rel, AlterTableCmd *cmd, Oid *partOid, const char *command)
{
    Oid subpartOid = InvalidOid;

    /* FIRST IS the SUBPARTITION (subpartname) branch */
    if (PointerIsValid(cmd->name)) {
        subpartOid = SubPartitionNameGetSubPartitionOid(rel->rd_id,
            cmd->name,
            ShareUpdateExclusiveLock,  /* partition lock */
            AccessExclusiveLock,       /* subpartition lock */
            false,
            false,
            NULL,
            NULL,
            NoLock,
            partOid);
        return subpartOid;
    }

    /* next IS the SUBPARTITION FOR (subpartvalue) branch */
    int2vector *subpartitionKey = NULL;
    RelationGetSubpartitionInfo(rel, NULL, NULL, &subpartitionKey);

    RangePartitionDefState *rangePartDef = (RangePartitionDefState*)cmd->def;
    int2vector *partitionKey = NULL;
    switch (rel->partMap->type) {
        case PART_TYPE_RANGE:
            partitionKey = rel->partMap->partitionKey;
            break;
        case PART_TYPE_LIST:
            partitionKey = rel->partMap->partitionKey;
            break;
        case PART_TYPE_HASH:
            partitionKey = rel->partMap->partitionKey;
            break;
        default:
            ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION), errmsg("Unknown partitioned type"),
                errdetail("This parttype is not supported for %s", command),
                errcause("Only range/list/hash partitioned table is supported for %s", command),
                erraction("Please check DDL syntax for \"%s\"", command)));
    }
    if (list_length(rangePartDef->boundary) != (partitionKey->dim1 + subpartitionKey->dim1)) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
            (errmsg("Number of boundary items NOT EQUAL to number of partition keys"),
            errdetail("There must be %d boundary items for %s in a subpartitioned table",
                partitionKey->dim1 + subpartitionKey->dim1, command),
            errcause("N/A"), erraction("Check whether the SQL statements is correct."))));
    }
    List *partBoundary = NIL;
    List *subpartBoundary = NIL;
    ListCell *cell = list_head(rangePartDef->boundary);
    for (int i = 0; i < list_length(rangePartDef->boundary); i++) {
        if (i < partitionKey->dim1) {
            partBoundary = lappend(partBoundary, lfirst(cell));
        } else {
            subpartBoundary = lappend(subpartBoundary, lfirst(cell));
        }
        cell = lnext(cell);
    }
    partBoundary = transformConstIntoTargetType(rel->rd_att->attrs,
        partitionKey,
        partBoundary);
    subpartBoundary = transformConstIntoTargetType(rel->rd_att->attrs,
        subpartitionKey,
        subpartBoundary);

    subpartOid = SubPartitionValuesGetSubPartitionOid(rel,
        partBoundary,
        subpartBoundary,
        ShareUpdateExclusiveLock,  /* partition lock */
        AccessExclusiveLock,       /* subpartition lock */
        true,
        true, /* will check validity of partition oid next step */
        false,
        partOid);
    list_free_ext(partBoundary);
    list_free_ext(subpartBoundary);

    return subpartOid;
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		:
 * Description	:
 * Notes		:
 */
void ATExecSetIndexUsableState(Oid objclassOid, Oid objOid, bool newState)
{
    bool dirty = false;
    Relation sys_table = NULL;
    int sysCacheId = 0;
    HeapTuple sys_tuple = NULL;

    if (objclassOid != IndexRelationId && objclassOid != PartitionRelationId)
        return;

    sys_table = relation_open(objclassOid, RowExclusiveLock);

    /* drop toast table, index, and finally the partition iteselt */
    /* get the corresponding tuple for partOid */
    if (objclassOid == PartitionRelationId)
        sysCacheId = PARTRELID;
    else if (objclassOid == IndexRelationId)
        sysCacheId = INDEXRELID;

    // update the indisusable field
    sys_tuple = SearchSysCacheCopy1(sysCacheId, ObjectIdGetDatum(objOid));
    if (sys_tuple) {
        if (objclassOid == PartitionRelationId) {
            if (((Form_pg_partition)GETSTRUCT(sys_tuple))->indisusable != newState) {
                ((Form_pg_partition)GETSTRUCT(sys_tuple))->indisusable = newState;
                dirty = true;
            }
        } else if (objclassOid == IndexRelationId) {
            if (((Form_pg_index)GETSTRUCT(sys_tuple))->indisusable != newState) {
                ((Form_pg_index)GETSTRUCT(sys_tuple))->indisusable = newState;
                dirty = true;
            }
        }

        /* Keep the system catalog indexes current. */
        if (dirty) {
            simple_heap_update(sys_table, &(sys_tuple->t_self), sys_tuple);
            CatalogUpdateIndexes(sys_table, sys_tuple);
        }
        tableam_tops_free_tuple(sys_tuple);
    }
    relation_close(sys_table, RowExclusiveLock);

    if (dirty) {
        CommandCounterIncrement();
    }
}

template <typename T>
void ATExecSetIndexValues(Datum *values, unsigned attribute, T new_state) {
    Assert(!"not supported");
}

template <>
void ATExecSetIndexValues(Datum *values, unsigned attribute, bool new_state) {
    values[attribute - 1] = DatumGetBool(new_state);
}

template <typename T>
void ATExecSetIndexState(Oid obj_oid, unsigned attribute, T new_state) {
    if (attribute > Natts_pg_index) {
        ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
                errmsg("attribute %d is greater than Natts_pg_index(%d)",
                attribute, Natts_pg_index)));
        return;
    }

    bool dirty = false;
    Relation sys_table = NULL;
    HeapTuple sys_tuple = NULL;
    bool is_null = false;

    sys_table = relation_open(IndexRelationId, RowExclusiveLock);

    // update the indisvisible field
    sys_tuple = SearchSysCacheCopy1(INDEXRELID, ObjectIdGetDatum(obj_oid));
    if (sys_tuple) {
        Datum old_state = heap_getattr(sys_tuple, attribute, RelationGetDescr(sys_table), &is_null);
        dirty = (is_null || BoolGetDatum(old_state) != new_state);

        /* Keep the system catalog indexes current. */
        if (dirty) {
            HeapTuple newitup = NULL;
            Datum values[Natts_pg_index];
            bool nulls[Natts_pg_index];
            bool replaces[Natts_pg_index];
            errno_t rc;
            rc = memset_s(values, sizeof(values), 0, sizeof(values));
            securec_check(rc, "\0", "\0");
            rc = memset_s(nulls, sizeof(nulls), false, sizeof(nulls));
            securec_check(rc, "\0", "\0");
            rc = memset_s(replaces, sizeof(replaces), false, sizeof(replaces));
            securec_check(rc, "\0", "\0");

            replaces[attribute - 1] = true;
            ATExecSetIndexValues(values, attribute, new_state);

            newitup =
                (HeapTuple)tableam_tops_modify_tuple(sys_tuple, RelationGetDescr(sys_table), values, nulls, replaces);
            simple_heap_update(sys_table, &(sys_tuple->t_self), newitup);
            CatalogUpdateIndexes(sys_table, newitup);
            tableam_tops_free_tuple(newitup);
        }
        tableam_tops_free_tuple(sys_tuple);
    }
    relation_close(sys_table, RowExclusiveLock);
}

void ATExecSetIndexEnableState(Oid obj_oid, bool new_state) {
    ATExecSetIndexState(obj_oid, Anum_pg_index_indisenable, new_state);
}

static void ATExecEnableIndex(Relation rel, bool enable) {
    if (!RelationIsIndex(rel) || !OidIsValid(rel->rd_id)) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("can not set enable/disable index for relation %s, as it is not a index",
                    RelationGetRelationName(rel))));
    }

    auto index_tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(rel->rd_id));
    if(!HeapTupleIsValid(index_tuple))
        elog(ERROR, "cache lookup failed for index %u", rel->rd_id);
    if (heap_attisnull(index_tuple, Anum_pg_index_indexprs, NULL)) {
        ReleaseSysCache(index_tuple);
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("can not set enable/disable index for relation %s, as it is not a function based index",
                    RelationGetRelationName(rel))));
    }
    ReleaseSysCache(index_tuple);

    ATExecSetIndexEnableState(rel->rd_id, enable);
}

void ATExecSetIndexVisibleState(Oid objOid, bool newState)
{
    ATExecSetIndexState(objOid, Anum_pg_index_indisvisible, newState);
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		:
 * Description	:
 * Notes		:
 */
static void ATExecUnusableIndexPartition(Relation rel, const char* partition_name)
{
    Oid indexPartOid = InvalidOid;
    Oid heapPartOid = InvalidOid;

    if (partition_name == NULL)
        return;

    if (OidIsValid(rel->rd_rel->relcudescrelid) || rel->rd_rel->relam == CBTREE_AM_OID ||
        rel->rd_rel->relam == CGIN_AM_OID) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Un-support feature"),
                errdetail("column-store index doesn't support this ALTER yet")));
    }

    /* the AccessShareLock lock on heap relation is held by AlterTableLookupRelation(). */
    /* getting the partition's oid, lock it the same time */
    indexPartOid = PartitionNameGetPartitionOid(rel->rd_id,
        partition_name,
        PART_OBJ_TYPE_INDEX_PARTITION,
        AccessExclusiveLock,  // lock on index partition
        false,
        false,
        PartitionNameCallbackForIndexPartition,
        (void*)&heapPartOid,
        AccessExclusiveLock);  // lock on heap partition
    // call the internal function
    ATExecSetIndexUsableState(PartitionRelationId, indexPartOid, false);
    /* Invoke cache invalidation to refresh index relation data */
    CacheInvalidateRelcache(rel);
}

static void ATUnusableGlobalIndex(Relation rel)
{
    ListCell* index = NULL;
    bool dirty = false;
    HeapTuple sysTuple = NULL;
    Relation sysTable = NULL;

    sysTable = relation_open(IndexRelationId, RowExclusiveLock);
    // update the indisusable field
    foreach (index, RelationGetSpecificKindIndexList(rel, true)) {
        Oid currIndexOid = lfirst_oid(index);
        sysTuple = SearchSysCacheCopy1(INDEXRELID, ObjectIdGetDatum(currIndexOid));
        if (sysTuple) {
            if (((Form_pg_index)GETSTRUCT(sysTuple))->indisusable != false) {
                ((Form_pg_index)GETSTRUCT(sysTuple))->indisusable = false;
                dirty = true;
            }
        } else {
            ereport(ERROR,
                (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("could not find tuple for relation %u", currIndexOid)));
        }

        /* Keep the system catalog indexes current. */
        if (dirty) {
            simple_heap_update(sysTable, &(sysTuple->t_self), sysTuple);
            CatalogUpdateIndexes(sysTable, sysTuple);
        }
        tableam_tops_free_tuple(sysTuple);
    }
    relation_close(sysTable, RowExclusiveLock);

    if (dirty) {
        /* invalidate relation */
        CacheInvalidateRelcache(rel);
        CommandCounterIncrement();
    }
    
    ereport(NOTICE, (errmsg("Command without UPDATE GLOBAL INDEX will disable global index")));
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		:
 * Description	:
 * Notes		:
 */
static void ATExecUnusableAllIndexOnPartition(Relation rel, const char* partition_name)
{
    Oid partOid = InvalidOid;
    Relation pg_partition = NULL;
    List* partIndexlist = NIL;
    ListCell* lc = NULL;

    if (partition_name == NULL)
        return;

    if (RelationIsColStore(rel)) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Un-support feature"),
                errdetail("column-store relation doesn't support this ALTER yet")));
    }

    if (RelationIsSubPartitioned(rel)) {
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        (errmsg("Un-support feature"),
                         errdetail("For subpartition table, UNUSABLE LOCAL INDEXES is not yet supported."),
                         errcause("The function is not implemented."), erraction("Use other actions instead."))));
    }

    /* getting the partition's oid, lock it the same time */
    partOid = PartitionNameGetPartitionOid(rel->rd_id,
        partition_name,
        PART_OBJ_TYPE_TABLE_PARTITION,
        AccessExclusiveLock,
        false,
        false,
        NULL,
        NULL,
        NoLock);

    /* first get the list of index partition on targeting table partition */
    partIndexlist = searchPartitionIndexesByblid(partOid);
    if (!PointerIsValid(partIndexlist)) {
        ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("no local index defined on partition %u", partOid)));
    }

    // open pg_partition
    pg_partition = relation_open(PartitionRelationId, RowExclusiveLock);
    // for each
    foreach (lc, partIndexlist) {
        HeapTuple partIndexTuple = NULL;
        Oid partIndId = InvalidOid;
        Oid parentIndId = InvalidOid;
        Partition indexPart = NULL;
        Relation parentIndex = NULL;

        partIndexTuple = (HeapTuple)lfirst(lc);
        parentIndId = (((Form_pg_partition)GETSTRUCT(partIndexTuple)))->parentid;
        partIndId = HeapTupleGetOid(partIndexTuple);

        // open index and it's partition
        parentIndex = index_open(parentIndId, AccessShareLock);
        indexPart = partitionOpen(parentIndex, partIndId, AccessExclusiveLock);

        // update the indisusable field , by calling the internal function
        ATExecSetIndexUsableState(PartitionRelationId, partIndId, false);

        // close index and it's partition
        partitionClose(parentIndex, indexPart, NoLock);
        index_close(parentIndex, NoLock);
        /* Invoke cache invalidation to refresh index relation data */
        CacheInvalidateRelcacheByRelid(parentIndId);
    }

    freePartList(partIndexlist);

    // update the indisusable field , by calling the internal function
    ATExecSetIndexUsableState(PartitionRelationId, partOid, false);

    // close pg_partition
    relation_close(pg_partition, RowExclusiveLock);
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		:
 * Description	:
 * Notes		:
 */
static void ATExecUnusableIndex(Relation rel, bool needChangeCsn)
{
    List* indexPartitionTupleList = NULL;
    ListCell* cell = NULL;
    Oid heapOid = InvalidOid;
    Relation heapRelation = NULL;

    if (!RelationIsIndex(rel))
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("can not set unusable index for relation %s , as it is not a index",
                    RelationGetRelationName(rel))));

    // cstore relation doesn't support this feature now.
    if (OidIsValid(rel->rd_rel->relcudescrelid) || rel->rd_rel->relam == CBTREE_AM_OID ||
        rel->rd_rel->relam == CGIN_AM_OID) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Un-support feature"),
                errdetail("column-store index doesn't support this ALTER yet")));
    }

    heapOid = IndexGetRelation(rel->rd_id, false);
    // the index is already lock by AccessExclusive lock, do not lock again.
    // AccessExclusiveLock on heap already held by call AlterTableLookupRelation().
    heapRelation = relation_open(heapOid, NoLock);

#ifdef ENABLE_MOT
    if (heapRelation->rd_rel->relkind == RELKIND_FOREIGN_TABLE && isMOTFromTblOid(heapOid)) {
        AlterForeingTableCmd fcmd = {
            T_AlterForeingTableCmd,
            AT_UnusableIndex,
            heapRelation,
            nullptr,
            nullptr,
            InvalidOid,
            nullptr
        };
        ATExecMOTAlterTable(&fcmd);
    }
#endif

    // call the internal function, update pg_index system table
    ATExecSetIndexUsableState(IndexRelationId, rel->rd_id, false);

    // if partitioned index, do extra work: set local index unusable
    if (RelationIsPartitioned(rel)) {
        // update pg_partition system table
        indexPartitionTupleList = searchPgPartitionByParentId(PART_OBJ_TYPE_INDEX_PARTITION, rel->rd_id);
        foreach (cell, indexPartitionTupleList) {
            Partition indexPartition = NULL;
            Partition heapPartition = NULL;
            Oid indexPartOid = HeapTupleGetOid((HeapTuple)lfirst(cell));
            Oid heapPartOid = indexPartGetHeapPart(indexPartOid, false);

            // lock heap partition
            heapPartition = partitionOpen(heapRelation, heapPartOid, AccessExclusiveLock);
            // lock index partition
            indexPartition = partitionOpen(rel, indexPartOid, AccessExclusiveLock);
            // update the indisusable field , by calling the internal function
            ATExecSetIndexUsableState(PartitionRelationId, indexPartOid, false);
            // close heap part and index part
            partitionClose(heapRelation, heapPartition, NoLock);
            partitionClose(rel, indexPartition, NoLock);
        }
        freePartList(indexPartitionTupleList);
    }
    // recode changecsn of table owing the index
    if (needChangeCsn)
        UpdatePgObjectChangecsn(heapOid, heapRelation->rd_rel->relkind);
    // close heap relation but maintain the lock.
    relation_close(heapRelation, NoLock);
}

static void ATExecVisibleIndex(Relation rel, char* index_name, bool visible)
{
    ListCell* index = NULL;
    bool found = false;

    if (!RelationIsRelation(rel))
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("can not set visible for relation %s, as it is not a ordinary table",
                    RelationGetRelationName(rel))));

    foreach (index, RelationGetIndexList(rel, true)) {
        Oid indexId = lfirst_oid(index);
        Relation indexRel = index_open(indexId, AccessShareLock);
        if (strcmp(index_name, RelationGetRelationName(indexRel)) != 0) {
            index_close(indexRel, AccessShareLock);
            continue;
        }
        index_close(indexRel, AccessShareLock);

        ATExecSetIndexVisibleState(indexId, visible);

        found = true;
        break;
    }

    if (!found) {
        ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
                errmsg("index \"%s\" of relation \"%s\" does not exist",
                index_name, RelationGetRelationName(rel))));
    }
}

static void ATExecVisibleIndexDirect(Relation rel, bool visible)
{
    if (RelationIsIndex(rel) && OidIsValid(rel->rd_id)) {
        ATExecSetIndexVisibleState(rel->rd_id, visible);
    } else {        
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("can not set visible for relation %s, as it is not a index",
                    RelationGetRelationName(rel))));
    }
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		:
 * Description	:
 * Notes		:
 */
static void ATExecModifyRowMovement(Relation rel, bool rowMovement)
{
    HeapTuple tuple = NULL;
    Oid relid = rel->rd_id;
    Relation pg_class;
    Form_pg_class rd_rel;
    bool dirty = false;

    pg_class = heap_open(RelationRelationId, RowExclusiveLock);

    /* get the tuple of partitioned table */
    tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
    if (!HeapTupleIsValid(tuple)) {
        ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("could not find tuple for relation %u", relid)));
    }

    rd_rel = (Form_pg_class)GETSTRUCT(tuple);

    /* modify the tuple */
    if (rd_rel->relrowmovement != rowMovement) {
        rd_rel->relrowmovement = rowMovement;
        dirty = true;
    }

    /* update pg_class */
    if (dirty) {
        simple_heap_update(pg_class, &tuple->t_self, tuple);
        CatalogUpdateIndexes(pg_class, tuple);
        /* the above sends a cache inval message */
    } else {
        /* no need to change tuple, but force relcache inval anyway */
        CacheInvalidateRelcacheByTuple(tuple);
    }

    tableam_tops_free_tuple(tuple);
    heap_close(pg_class, RowExclusiveLock);

    CommandCounterIncrement();
}

static void ATExecResetPartitionno(Relation rel)
{
    Assert(RELATION_IS_PARTITIONED(rel));

    bool isupgrade = (t_thrd.proc->workingVersionNum < PARTITION_ENHANCE_VERSION_NUM);
    LOCKMODE relationlock = isupgrade ? ShareUpdateExclusiveLock : AccessExclusiveLock;

    RelationResetPartitionno(rel->rd_id, relationlock);
}

List* GetPartitionBoundary(Relation partTableRel, Node *PartDef)
{
    List *boundary = NIL;
    int2vector *partitionKey = NULL;
    switch (nodeTag(PartDef)) {
        case T_RangePartitionDefState:
            boundary = ((RangePartitionDefState *)PartDef)->boundary;
            break;
        case T_ListPartitionDefState:
            boundary = ((ListPartitionDefState *)PartDef)->boundary;
            break;
        case T_HashPartitionDefState:
            boundary = ((HashPartitionDefState *)PartDef)->boundary;
            break;
        default:
            ereport(ERROR,
                (errmodule(MOD_COMMAND),
                    errcode(ERRCODE_INVALID_OPERATION),
                    errmsg("Can't get the partitioned table boundary."),
                        errdetail("Table %s is unsupported for getting the boundary.",
                            RelationGetRelationName(partTableRel)),
                    errcause("Target table is unsupported for getting the boundary."),
                    erraction("Check the table type.")));
            break;
    }
    partitionKey = PartitionMapGetPartKeyArray(partTableRel->partMap);
    boundary = transformConstIntoTargetType(partTableRel->rd_att->attrs, partitionKey, boundary);
    return boundary;
}

static char** getPartitionIndexesName(Oid partitionOid, List* indexList) {
    if (indexList == NIL) {
        return NULL;
    }
    int loc = 0;
    ListCell* cell = NULL;
    char** partitionIndexNames = (char**)palloc(sizeof(char*)*indexList->length);
    foreach (cell, indexList) {
        Oid indexOid = lfirst_oid(cell);
        char* name = getPartitionIndexName(indexOid, partitionOid);
        if (name == NULL) {
            ereport(ERROR,
                (errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
                    errmsg("Invalid name of local index %u on the partition %u", indexOid, partitionOid)));
        }
        partitionIndexNames[loc++] = name;
    }
    return partitionIndexNames;
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		: Change the names of the local indexes on the target partition table in pg_partition.
 * Description	:
 * Notes		:
 */
static void renamePartitionIndexes(Oid partitionedTableOid, Oid partitionOid, char** partitionIndexNames,
    List* indexList)
{
    if (partitionIndexNames == NULL) {
        return;
    }
    int loc = 0;
    ListCell* cell = NULL;
    foreach (cell, indexList) {
        Oid indexOid = lfirst_oid(cell);

        Relation indexRel = relation_open(indexOid, AccessShareLock);

        Oid indexDestPartOid = getPartitionIndexOid(indexRel->rd_id, partitionOid);
        Partition indexPart = partitionOpen(indexRel, indexDestPartOid, AccessExclusiveLock);

        renamePartitionInternal(partitionedTableOid, indexDestPartOid, partitionIndexNames[loc++]);

        partitionClose(indexRel, indexPart, NoLock);
        relation_close(indexRel, AccessShareLock);
    }
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		: Truncate operation, create a new partition to replace the original partition.
 * Description	:
 * Notes		:
 */
static Oid heap_truncate_one_part_new(const AlterTableCmd* cmd, Relation partRel, Oid srcPartOid,
    Relation rel = InvalidRelation)
{
    Partition srcPart = NULL;
    bool renameTargetPart = false;
    char* destPartitionName = NULL;

    Oid destPartOid = AddTemporaryPartitionForAlterPartitions(cmd, partRel, srcPartOid, &renameTargetPart);
    if (RelationIsPartitionOfSubPartitionTable(partRel)) {
        int subpartitionno = GetCurrentSubPartitionNo(srcPartOid);
        PARTITIONNO_VALID_ASSERT(subpartitionno);
        UpdateCurrentSubPartitionNo(destPartOid, subpartitionno);
    } else {
        int partitionno = GetCurrentPartitionNo(srcPartOid);
        PARTITIONNO_VALID_ASSERT(partitionno);
        UpdateCurrentPartitionNo(destPartOid, partitionno, false);
    }

    List* indexList = NULL;
    if (RelationIsPartitionOfSubPartitionTable(partRel) && RelationIsValid(rel)) {
        indexList = RelationGetSpecificKindIndexList(rel, false);
    } else {
        indexList = RelationGetSpecificKindIndexList(partRel, false);
    }
    char** partitionIndexNames = getPartitionIndexesName(srcPartOid, indexList);

    srcPart = partitionOpen(partRel, srcPartOid, AccessExclusiveLock);
    destPartitionName = pstrdup(PartitionGetPartitionName(srcPart));
    partitionClose(partRel, srcPart, NoLock);

    CommandCounterIncrement();
    fastDropPartition(partRel, srcPartOid, "TRUNCATE PARTITION");

    CommandCounterIncrement();
    renamePartitionIndexes(partRel->rd_id, destPartOid, partitionIndexNames, indexList);

    if (renameTargetPart) {
        CommandCounterIncrement();
        renamePartitionInternal(partRel->rd_id, destPartOid, destPartitionName);
    }

    list_free_ext(indexList);
    pfree_ext(partitionIndexNames);
    pfree_ext(destPartitionName);

    return destPartOid;
}

static void ATExecTruncatePartitionForSubpartitionTable(Relation rel, Oid partOid, AlterTableCmd* cmd, bool hasGPI)
{
    /* truncate subpartitioned table */
    Partition part = partitionOpen(rel, partOid, AccessExclusiveLock);
    Relation partRel = partitionGetRelation(rel, part);

    List *subPartOidList = relationGetPartitionOidList(partRel);
    ListCell *subPartOidCell = NULL;
    Oid subPartOid = InvalidOid;
    foreach (subPartOidCell, subPartOidList) {
        subPartOid = lfirst_oid(subPartOidCell);
        LockPartitionOid(partOid, subPartOid, AccessExclusiveLock);
    }

    if (!cmd->alterGPI) {
        // Unusable Global Index
        ATUnusableGlobalIndex(rel);
    }
    foreach (subPartOidCell, subPartOidList) {
        Oid subPartOid = lfirst_oid(subPartOidCell);

        if (cmd->alterGPI) {
            AlterSubPartitionedSetWaitCleanGPI(cmd->alterGPI, rel, partOid, subPartOid);
        }

        if (!cmd->alterGPI || !hasGPI || RelationIsColStore(rel)) {
            heap_truncate_one_part(partRel, subPartOid);
        } else {
            heap_truncate_one_part_new(cmd, partRel, subPartOid, rel);
        }
        pgstat_report_truncate(subPartOid, rel->rd_id, rel->rd_rel->relisshared);
    }

    releaseDummyRelation(&partRel);
    partitionClose(rel, part, NoLock);
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		:
 * Description	:
 * Notes		:
 */
static void ATExecTruncatePartition(Relation rel, AlterTableCmd* cmd)
{
    List* oidList = NULL;
    List* relid = lappend_oid(NULL, rel->rd_id);
    Oid partOid = InvalidOid;
    Oid destPartOid = InvalidOid;
    Oid newPartOid = InvalidOid;
    Relation newTableRel = NULL;
    bool hasGPI = (list_length(RelationGetSpecificKindIndexList(rel, true)) > 0);

    oidList = heap_truncate_find_FKs(relid);
    if (PointerIsValid(oidList)) {
        ereport(ERROR,
            (errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
                errmsg("cannot truncate a partition owned by partitioned table which is referenced in a foreign key "
                       "constraint")));
    }

#ifdef PGXC
    if (IS_PGXC_COORDINATOR) {
        u_sess->catalog_cxt.redistribution_cancelable = true;

        /* this maybe useless, consider to remove */
        u_sess->exec_cxt.could_cancel_redistribution = true;
    }

    /*
     * If parent rel is in redistribution, we need to truncate the same
     * partition in its new table rel.
     */
    if (RelationInClusterResizing(rel) && !RelationInClusterResizingReadOnly(rel)) {
        /*
         * If the target table is under online extension, it should always trigger
         * redis cancel, even though currently no lock confict yet, later it will still
         * meet, because redistribute the target table need to lock all the partition.
         * So we can trigger it now.
         */
        newTableRel = GetAndOpenNewTableRel(rel, AccessExclusiveLock);
    }

    if (IS_PGXC_COORDINATOR) {
        u_sess->catalog_cxt.redistribution_cancelable = false;
    }
#endif

    /*
     * Get the partition oid
     * 1. Get partition oid from part_name cluase
     * 2. Get partition oid values clause
     */
    if (PointerIsValid(cmd->name)) {
        partOid = PartitionNameGetPartitionOid(rel->rd_id,
            cmd->name,
            PART_OBJ_TYPE_TABLE_PARTITION,
            AccessExclusiveLock,
            false,
            false,
            NULL,
            NULL,
            NoLock);
        if (newTableRel) {
            newPartOid = PartitionNameGetPartitionOid(newTableRel->rd_id,
                cmd->name,
                PART_OBJ_TYPE_TABLE_PARTITION,
                AccessExclusiveLock,
                false,
                false,
                NULL,
                NULL,
                NoLock);
        }
    } else {
        List *boundary = GetPartitionBoundary(rel, cmd->def);
        partOid = PartitionValuesGetPartitionOid(rel,
            boundary,
            AccessExclusiveLock,
            true,
            true, /* will check validity of partition oid next step */
            false);
        if (newTableRel) {
            newPartOid = PartitionValuesGetPartitionOid(newTableRel,
                boundary,
                AccessExclusiveLock,
                true,
                true, /* will check validity of partition oid next step */
                false);
        }
    }

    if (!OidIsValid(partOid)) {
        ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("The partition number is invalid or out-of-range")));
    }

#ifdef ENABLE_HTAP
    if (RelHasImcs(partOid)) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                errmsg("The part rel enable imcstore, please unimcstore if want to truncate it.")));
    }
#endif

    if (RelationIsSubPartitioned(rel)) {
        ATExecTruncatePartitionForSubpartitionTable(rel, partOid, cmd, hasGPI);
        return;
    }

    if (!cmd->alterGPI) {
        // Unusable Global Index
        ATUnusableGlobalIndex(rel);
    } else {
        AlterPartitionedSetWaitCleanGPI(cmd->alterGPI, rel, partOid);
    }

    if (!cmd->alterGPI || !hasGPI || RelationIsColStore(rel)) {
        heap_truncate_one_part(rel, partOid);
    } else {
        destPartOid = heap_truncate_one_part_new(cmd, rel, partOid);
    }

    pgstat_report_truncate(partOid, rel->rd_id, rel->rd_rel->relisshared);

    /* If newTableRel is not NULL, the parent rel must be in redistribution */
    if (newTableRel) {
        heap_truncate_one_part(newTableRel, newPartOid);

        /*  delete partOid related rows in delete delta table  */
        simple_delete_redis_tuples(rel, partOid);
        if (OidIsValid(destPartOid)) {
            ResetOnePartRedisCtidRelOptions(rel, destPartOid);
        } else {
            ResetOnePartRedisCtidRelOptions(rel, partOid);
        }

        /* clean up */
        heap_close(newTableRel, AccessExclusiveLock);
        pgstat_report_truncate(newPartOid, newTableRel->rd_id, newTableRel->rd_rel->relisshared);
    }

#ifdef ENABLE_MULTIPLE_NODES
    if (unlikely(RelationIsTsStore(rel) && OidIsValid(RelationGetDeltaRelId(rel))) && IS_PGXC_DATANODE) {
        Tsdb::DeleteDeltaByPartition(GetActiveSnapshot(), rel, partOid);
    }
#endif  /* ENABLE_MULTIPLE_NODES */
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		:
 * Description	:
 * Notes		:
 */
static void ATExecTruncateSubPartition(Relation rel, AlterTableCmd* cmd)
{
    List* oidList = NULL;
    List* relid = lappend_oid(NULL, rel->rd_id);
    Oid subPartOid = InvalidOid;
    bool hasGPI = (list_length(RelationGetSpecificKindIndexList(rel, true)) > 0);

    oidList = heap_truncate_find_FKs(relid);
    if (PointerIsValid(oidList)) {
        ereport(ERROR,
            (errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
                errmsg("cannot truncate a partition owned by partitioned table which is referenced in a foreign key "
                       "constraint")));
    }

    /*
     * Get the partition oid
     * 1. Get partition oid from part_name cluase
     * 2. Get partition oid values clause
     */
    Oid partOid = InvalidOid;
    if (PointerIsValid(cmd->name)) {
        subPartOid = SubPartitionNameGetSubPartitionOid(rel->rd_id,
            cmd->name,
            ShareUpdateExclusiveLock,
            AccessExclusiveLock,
            false,
            false,
            NULL,
            NULL,
            NoLock,
            &partOid);
    } else {
        ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("The subpartition name is invalid")));
    }

    if (!OidIsValid(subPartOid)) {
        ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("The subpartition name is invalid")));
    }

#ifdef ENABLE_HTAP
    if (RelHasImcs(subPartOid)) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                errmsg("The subpart rel enable imcstore, please unimcstore if want to truncate it.")));
    }
#endif

    Partition part = partitionOpen(rel, partOid, NoLock);
    Relation partRel = partitionGetRelation(rel, part);

    if (!cmd->alterGPI) {
        // Unusable Global Index
        ATUnusableGlobalIndex(rel);
    } else {
        AlterSubPartitionedSetWaitCleanGPI(cmd->alterGPI, rel, partOid, subPartOid);
    }

    if (!cmd->alterGPI || !hasGPI || RelationIsColStore(rel)) {
        heap_truncate_one_part(partRel, subPartOid);
    } else {
        heap_truncate_one_part_new(cmd, partRel, subPartOid, rel);
    }
    pgstat_report_truncate(subPartOid, rel->rd_id, rel->rd_rel->relisshared);

    releaseDummyRelation(&partRel);
    partitionClose(rel, part, NoLock);

#ifdef ENABLE_MULTIPLE_NODES
    if (unlikely(RelationIsTsStore(rel) && OidIsValid(RelationGetDeltaRelId(rel))) && IS_PGXC_DATANODE) {
        Tsdb::DeleteDeltaByPartition(GetActiveSnapshot(), rel, subPartOid);
    }
#endif  /* ENABLE_MULTIPLE_NODES */
}

/*
 * - Brief: delete tuples in a redis delete delta table for partition table
 *          being redistributed
 * - Parameter:
 *      @deltaRel: redis delete delta relation
 *      @partOid: partition oid
 * - Return:
 *      @void:
 */
static void delete_delta_table_tuples(Relation deltaRel, Oid partOid)
{
    ScanKeyData key;
    TableScanDesc scan;
    HeapTuple tup;

    Assert(deltaRel != NULL);
    /* delete_delta_table's first column including partitionoid */
    uint64 keyValue = (((uint64)u_sess->pgxc_cxt.PGXCNodeIdentifier << 32) | partOid);
    /* For Redistribution, delete_delta_table's first column, including xc_node_id and relid(partitionoid) */
    ScanKeyInit(&key, 1, BTEqualStrategyNumber, F_INT8EQ, UInt64GetDatum(keyValue));

    scan = tableam_scan_begin(deltaRel, SnapshotNow, 1, &key);

    while (HeapTupleIsValid(tup = (HeapTuple) tableam_scan_getnexttuple(scan, ForwardScanDirection))) {
        simple_heap_delete(deltaRel, &tup->t_self);
    }

    tableam_scan_end(scan);
}

/*
 * - Brief: delete tuples in redis related delete delta tables for partition table
 *          being redistributed
 * - Parameter:
 *      @rel: relation being redistributed
 *      @partOid: partition oid
 * - Return:
 *      @void:
 */
static void simple_delete_redis_tuples(Relation rel, Oid partOid)
{
    Relation deltaRel;

    /*
     * First delete the multi catchup delta table tuples.
     * Always keep the order consistent by operating on multi catchup delete delta first and then the delete delta.
     */
    deltaRel = GetAndOpenDeleteDeltaRel(rel, RowExclusiveLock, true);
    if (deltaRel) {
        delete_delta_table_tuples(deltaRel, partOid);
        heap_close(deltaRel, RowExclusiveLock);
    }

    deltaRel = GetAndOpenDeleteDeltaRel(rel, RowExclusiveLock, false);
    if (deltaRel) {
        delete_delta_table_tuples(deltaRel, partOid);
        heap_close(deltaRel, RowExclusiveLock);
    }
}

// find each index partition corresponding to srcPartOids,
// under clonedIndexRelation.
// then, add the fake relation for local index to end of merging_btrees_list.
static List* generateMergeingIndexes(
    Relation destIndexRelation, Relation clonedIndexRelation, int2 bucketId, List* heapPartOids, List** partList)
{
    ListCell* cell = NULL;
    List* merging_btrees_list = NIL;
    List* merging_part_list = NIL;

    if (!OID_IS_BTREE(clonedIndexRelation->rd_rel->relam))
        return merging_btrees_list;

    merging_btrees_list = lappend(merging_btrees_list, destIndexRelation);

    foreach (cell, heapPartOids) {
        Oid heapPartOid = InvalidOid;
        Oid indexPartOid = InvalidOid;
        Partition indexPart = NULL;
        Relation indexPartRel = NULL;

        heapPartOid = lfirst_oid(cell);
        indexPartOid = getPartitionIndexOid(clonedIndexRelation->rd_id, heapPartOid);
        // the index partition already locked by checkPartitionLocalIndexesUsable()
        indexPart = partitionOpen(clonedIndexRelation, indexPartOid, NoLock, bucketId);
        if (!indexPart->pd_part->indisusable) {
            ereport(ERROR,
                (errcode(ERRCODE_UNEXPECTED_NODE_STATE),
                    errmsg("can not merge index partition %s bacause it is unusable local index",
                        PartitionGetPartitionName(indexPart))));
        }
        indexPartRel = partitionGetRelation(clonedIndexRelation, indexPart);

        // append indexPartRel
        merging_btrees_list = lappend(merging_btrees_list, indexPartRel);
        merging_part_list = lappend(merging_part_list, indexPart);
    }

    if (partList != NULL)
        *partList = merging_part_list;

    return merging_btrees_list;
}

static void destroyMergeingIndexes(Relation srcIndexRelation, List* merging_btrees_list, List* merging_part_list)
{
    ListCell* cell1 = NULL;
    ListCell* cell2 = NULL;
    int i = 0;

    cell2 = list_head(merging_part_list);

    foreach (cell1, merging_btrees_list) {
        Relation indexRelation = (Relation)lfirst(cell1);
        Partition indexPartition = NULL;

        if (i == 0) {
            relation_close(indexRelation, NoLock);
        } else if (!OidIsValid(indexRelation->parentId)) {
            relation_close(indexRelation, NoLock);
        } else {

            indexPartition = (Partition)lfirst(cell2);
            partitionClose(srcIndexRelation, indexPartition, NoLock);
            releaseDummyRelation(&indexRelation);
            cell2 = lnext(cell2);
        }
        i++;
    }
    list_free_ext(merging_btrees_list);
    list_free_ext(merging_part_list);
}

static void mergePartitionIndexSwap(List* indexRel, List* indexDestPartOid, List* indexDestOid, TransactionId FreezeXid,
                                    MultiXactId FreezeMultiXid)
{
    ListCell* cell1 = NULL;
    ListCell* cell2 = NULL;
    ListCell* cell3 = NULL;

    forthree(cell1, indexRel, cell2, indexDestPartOid, cell3, indexDestOid)
    {
        Relation currentIndex = (Relation)lfirst(cell1);
        Oid dstPartOid = lfirst_oid(cell2);
        Oid clonedIndexRelationId = lfirst_oid(cell3);
        Partition dstPart;

        /* before swap refilenode, promote lock on index partition from ExclusiveLock to AccessExclusiveLock */
        dstPart = partitionOpenWithRetry(currentIndex, dstPartOid, AccessExclusiveLock, "MERGE PARTITIONS");
        if (!dstPart) {
            ereport(ERROR,
                (errcode(ERRCODE_LOCK_WAIT_TIMEOUT),
                    errmsg(
                        "could not acquire AccessExclusiveLock on dest index partition \"%s\", MERGE PARTITIONS failed",
                        getPartitionName(dstPartOid, false))));
        }
        /* swap relfilenode between temp index relation and dest index partition */
        finishPartitionHeapSwap(dstPartOid, clonedIndexRelationId, false, FreezeXid, FreezeMultiXid);
        partitionClose(currentIndex, dstPart, NoLock);
    }
}

static void mergePartitionHeapSwap(Relation partTableRel, Oid destPartOid, Oid tempTableOid, TransactionId FreezeXid,
                                   MultiXactId FreezeMultiXid)
{
    Partition destPart;

    /* before swap refilenode, promote lock on heap partition from ExclusiveLock to AccessExclusiveLock */
    destPart = partitionOpenWithRetry(partTableRel, destPartOid, AccessExclusiveLock, "MERGE PARTITIONS");

    /* step 4: swap relfilenode and delete temp table */
    if (!destPart) {
        ereport(ERROR,
            (errcode(ERRCODE_LOCK_WAIT_TIMEOUT),
                errmsg("could not acquire AccessExclusiveLock on dest table partition \"%s\", MERGE PARTITIONS failed",
                    getPartitionName(destPartOid, false))));
    }

    finishPartitionHeapSwap(destPartOid, tempTableOid, false, FreezeXid, FreezeMultiXid);
    partitionClose(partTableRel, destPart, NoLock);
}

static inline void FlushBufferIfNotBucket(Relation rel, int2 bucketId)
{
    if (bucketId == InvalidBktId) {
        FlushRelationBuffers(rel);
        smgrimmedsync(rel->rd_smgr, MAIN_FORKNUM);
    }
}

static void mergePartitionBTreeIndexes(List* srcPartOids, List* srcPartMergeOffset, List* indexRel_list,
    List* indexDestOid_list, int2 bucketId)
{
    ListCell* cell1 = NULL;
    ListCell* cell2 = NULL;

    forboth(cell1, indexRel_list, cell2, indexDestOid_list)
    {
        Relation currentIndex = (Relation)lfirst(cell1);
        Oid clonedIndexRelationId = lfirst_oid(cell2);
        bool skip_build = false;
        Relation clonedIndexRelation;
        List* merging_btrees_list = NIL;
        List* merging_part_list = NIL;
        int2 bktid = RelationIsCrossBucketIndex(currentIndex) ? InvalidBktId : bucketId;
        
        if (OID_IS_BTREE(currentIndex->rd_rel->relam))
            skip_build = true;
        else
            skip_build = false;

        /* merge several indexes together */
        if (skip_build) {
            /*
             * add the newly created index into merging_btrees_list
             * we now begin creating a list of index relation for merging
             */
            clonedIndexRelation = index_open(clonedIndexRelationId, AccessExclusiveLock, bktid);
            if (RelationIsCrossBucketIndex(clonedIndexRelation)) {
                if (!clonedIndexRelation->newcbi) {
                    /* here the crossbucket index was already merged */
                    index_close(clonedIndexRelation, AccessExclusiveLock);
                    continue;
                }
                /* the first time to attempt to merge this crossbucket index when newcbi is true */
                clonedIndexRelation->newcbi = false;
            }
            merging_btrees_list =
                generateMergeingIndexes(clonedIndexRelation, currentIndex, bktid, srcPartOids, &merging_part_list);

            /* merging indexes: using the merging_btrees_list as the source */
            mergeBTreeIndexes(merging_btrees_list, srcPartMergeOffset, bucketId);

            /* destroy the merging indexes list */
            destroyMergeingIndexes(currentIndex, merging_btrees_list, merging_part_list);
        }
    }
}

static void mergePartitionHeapData(Relation partTableRel, Relation tempTableRel, List* srcPartOids, List* indexRel_list,
    List* indexDestOid_list, int2 bucketId, TransactionId* freezexid, MultiXactId* freezeMultixid)
{
    TransactionId FreezeXid = InvalidTransactionId;
    MultiXactId FreezeMultiXid = InvalidMultiXactId;
    HTAB* chunkIdHashTable = NULL;
    ListCell* cell1 = NULL;
    List* mergeToastIndexes = NIL;
    List* srcPartToastMergeOffset = NIL;
    List* srcPartMergeOffset = NIL;
    bool hasToast = false;
    Relation tempTableToastRel = NULL;
    Relation tempTableToastIndexRel = NULL;
    BlockNumber mergeHeapBlocks = 0;
    BlockNumber mergeToastBlocks = 0;
    int partNum = 0;
    int iterator = 0;
    bool* srcPartsHasVM = NULL;
    bool hasVM = false;
    bool hasFSM = false;

    partNum = srcPartOids->length;

    if (OidIsValid(tempTableRel->rd_rel->reltoastrelid)) {
        hasToast = true;
        tempTableToastRel = relation_open(tempTableRel->rd_rel->reltoastrelid, AccessExclusiveLock, bucketId);
        tempTableToastIndexRel = index_open(tempTableToastRel->rd_rel->reltoastidxid, AccessExclusiveLock, bucketId);
        mergeToastIndexes = lappend(mergeToastIndexes, tempTableToastIndexRel);
    }

    /* step 3: merge each src partition's tuple into the temp table */
    mergeHeapBlocks = 0;
    mergeToastBlocks = 0;

    /*
     *  3.1 check chunk_id of toast table not repeat
     */
    if (hasToast) {
        HASHCTL hashCtl;
        List* srcPartToastRels = NIL;
        errno_t rc = EOK;

        /* Initialize hash tables */
        rc = memset_s(&hashCtl, sizeof(hashCtl), 0, sizeof(hashCtl));
        securec_check(rc, "\0", "\0");
        hashCtl.keysize = sizeof(ChunkIdHashKey);
        hashCtl.entrysize = sizeof(OldToNewChunkIdMapping);
        hashCtl.hash = tag_hash;

        chunkIdHashTable =
            hash_create("Merge partition / Old to new chunkId map", 128, &hashCtl, HASH_ELEM | HASH_FUNCTION);

        foreach (cell1, srcPartOids) {
            Oid srcPartOid = lfirst_oid(cell1);
            Partition srcPartition = NULL;
            Relation srcPartToastRel = NULL;
            Relation srcPartToastIndexRel = NULL;

            srcPartition = partitionOpen(partTableRel, srcPartOid, NoLock);

            /* open toast table and it's index */
            srcPartToastRel = relation_open(srcPartition->pd_part->reltoastrelid, ExclusiveLock, bucketId);
            srcPartToastIndexRel = index_open(srcPartToastRel->rd_rel->reltoastidxid, ExclusiveLock, bucketId);

            srcPartToastRels = lappend(srcPartToastRels, srcPartToastRel);
            mergeToastIndexes = lappend(mergeToastIndexes, srcPartToastIndexRel);

            partitionClose(partTableRel, srcPartition, NoLock);
        }

        /* Find repeat chunkId in toast tables, and replace. */
        replaceRepeatChunkId(chunkIdHashTable, srcPartToastRels);

        foreach (cell1, srcPartToastRels) {
            Relation srcPartToastRel = (Relation)lfirst(cell1);
            relation_close(srcPartToastRel, NoLock);
        }

        list_free_ext(srcPartToastRels);
    }
    /*
     * 3.2 check VM and FSM of src partitions
     */
    srcPartsHasVM = (bool*)palloc0(partNum * sizeof(bool));

    iterator = 0;
    foreach (cell1, srcPartOids) {
        Oid srcPartOid = lfirst_oid(cell1);
        Partition srcPartition = NULL;

        srcPartition = partitionOpen(partTableRel, srcPartOid, NoLock, bucketId);

        if (smgrexists(srcPartition->pd_smgr, VISIBILITYMAP_FORKNUM)) {
            srcPartsHasVM[iterator] = true;
            hasVM = true;
        }

        if (smgrexists(srcPartition->pd_smgr, FSM_FORKNUM)) {
            hasFSM = true;
        }

        iterator++;

        PartitionCloseSmgr(srcPartition);
        partitionClose(partTableRel, srcPartition, NoLock);
    }

    /* create VM on temp table if need */
    if (hasVM) {
        /* Retry to open smgr in case it is cloesd when we process SI messages  */
        RelationOpenSmgr(tempTableRel);
        smgrcreate(tempTableRel->rd_smgr, VISIBILITYMAP_FORKNUM, false);
    }

    /* create FSM on temp table if need */
    if (hasFSM) {
        /* Retry to open smgr in case it is cloesd when we process SI messages  */
        RelationOpenSmgr(tempTableRel);
        smgrcreate(tempTableRel->rd_smgr, FSM_FORKNUM, false);
    }

    /*
     * 3.3 merge heap and toast, if any
     */
    iterator = 0;
    foreach (cell1, srcPartOids) {
        Oid srcPartOid = lfirst_oid(cell1);
        Partition srcPartition = NULL;
        Relation srcPartRel = NULL;
        char persistency;
        BlockNumber srcPartHeapBlocks = 0;
        TransactionId relfrozenxid = InvalidTransactionId;
        MultiXactId relminmxid = InvalidMultiXactId;

        srcPartition = partitionOpen(partTableRel, srcPartOid, ExclusiveLock, bucketId);  // already ExclusiveLock
                                                                                          // locked
        srcPartRel = partitionGetRelation(partTableRel, srcPartition);
        PartitionOpenSmgr(srcPartition);

        getPartitionRelxids(srcPartRel, &relfrozenxid, &relminmxid);
        /* update final fronzenxid, we choose the least one */
        if (!TransactionIdIsValid(FreezeXid) || TransactionIdPrecedes(relfrozenxid, FreezeXid))
            FreezeXid = relfrozenxid;

        if (!MultiXactIdIsValid(FreezeMultiXid) || MultiXactIdPrecedes(relminmxid, FreezeMultiXid))
            FreezeMultiXid = relminmxid;

        /* Retry to open smgr in case it is cloesd when we process SI messages  */
        RelationOpenSmgr(tempTableRel);

        /* Ensure smgr is opened */
        RelationOpenSmgr(srcPartRel);

        /* flush dirty pages to disk. Bucket tables has already flushed buffers before for performance. */
        FlushBufferIfNotBucket(srcPartRel, bucketId);

        persistency = srcPartRel->rd_rel->relpersistence;

        srcPartHeapBlocks = smgrnblocks(srcPartRel->rd_smgr, MAIN_FORKNUM);

        /* merge heap */
        mergeHeapBlock(srcPartRel,
            tempTableRel,
            MAIN_FORKNUM,
            persistency,
            srcPartHeapBlocks,
            mergeHeapBlocks,
            srcPartRel->rd_att,
            srcPartRel->rd_rel->reltoastrelid,
            tempTableRel->rd_rel->reltoastrelid,
            hasToast ? chunkIdHashTable : NULL,
            hasFSM);

        /* merge toast table */
        if (hasToast) {
            Relation srcPartToastRel = NULL;
            char toastPersistency;
            BlockNumber srcPartToastBlocks = 0;

            srcPartToastRel = relation_open(srcPartition->pd_part->reltoastrelid, NoLock, bucketId);
            RelationOpenSmgr(srcPartToastRel);
            srcPartToastMergeOffset = lappend_int(srcPartToastMergeOffset, mergeToastBlocks);

            FlushBufferIfNotBucket(srcPartToastRel, bucketId);

            toastPersistency = srcPartToastRel->rd_rel->relpersistence;

            srcPartToastBlocks = smgrnblocks(srcPartToastRel->rd_smgr, MAIN_FORKNUM);

            mergeHeapBlock(srcPartToastRel,
                tempTableToastRel,
                MAIN_FORKNUM,
                toastPersistency,
                srcPartToastBlocks,
                mergeToastBlocks,
                NULL,
                InvalidOid,
                InvalidOid,
                chunkIdHashTable,
                false);
            mergeToastBlocks += srcPartToastBlocks;

            RelationCloseSmgr(srcPartToastRel);
            relation_close(srcPartToastRel, NoLock);
        }

        /* merge VM */
        if (hasVM && srcPartsHasVM[iterator]) {
            mergeVMBlock(srcPartRel, tempTableRel, srcPartHeapBlocks, mergeHeapBlocks);
        }

        PartitionCloseSmgr(srcPartition);
        partitionClose(partTableRel, srcPartition, NoLock);
        releaseDummyRelation(&srcPartRel);

        iterator++;
        srcPartMergeOffset = lappend_int(srcPartMergeOffset, mergeHeapBlocks);
        mergeHeapBlocks += srcPartHeapBlocks;
    }

    pfree_ext(srcPartsHasVM);

    if (freezexid != NULL)
        *freezexid = FreezeXid;

    if (freezeMultixid != NULL)
        *freezeMultixid = FreezeMultiXid;
    /*
     * 3.4 merge toast indexes and destroy chunkId hash table
     */
    if (hasToast) {
        mergeBTreeIndexes(mergeToastIndexes, srcPartToastMergeOffset, bucketId);
        destroyMergeingIndexes(NULL, mergeToastIndexes, NULL);
        RelationCloseSmgr(tempTableToastRel);
        heap_close(tempTableToastRel, NoLock);
        hash_destroy(chunkIdHashTable);
    }

    /*
     *	3.5 merge btree-indexes
     *
     */
    mergePartitionBTreeIndexes(srcPartOids, srcPartMergeOffset, indexRel_list, indexDestOid_list, bucketId);
}

static void UpdatePrevIntervalPartToRange(Relation srcRel, Relation pgPartition, int srcPartIndex, const char* briefCmd)
{
    RangePartitionMap *parts = reinterpret_cast<RangePartitionMap *>(srcRel->partMap);
    for (int i = srcPartIndex - 1; i > 0; --i) {
        if (parts->rangeElements[i].isInterval) {
            UpdateIntervalPartToRange(pgPartition, parts->rangeElements[i].partitionOid, briefCmd);
        }
    }
}

static void FlushMergedPartitionBuffers(Relation partTableRel, const List* srcPartOids, bool hasToast)
{
    ListCell* cell = NULL;
    foreach (cell, srcPartOids) {
        Oid srcPartOid = lfirst_oid(cell);

        /*
         * 1. Alreay exclusive locked at begin. No need to acquire any lock here
         * 2. Use InvalidBktId as FlushRelationBuffers do not compare bucketNode with buffer tag.
         */
        Partition srcPartition = partitionOpen(partTableRel, srcPartOid, NoLock, InvalidBktId);
        Relation srcPartRel = partitionGetRelation(partTableRel, srcPartition);
        RelationOpenSmgr(srcPartRel);

        FlushRelationBuffers(srcPartRel);
        smgrimmedsync(srcPartRel->rd_smgr, MAIN_FORKNUM);

        if (hasToast) {
            Relation srcPartToastRel = relation_open(srcPartition->pd_part->reltoastrelid, NoLock, InvalidBktId);
            RelationOpenSmgr(srcPartToastRel);

            FlushRelationBuffers(srcPartToastRel);
            smgrimmedsync(srcPartToastRel->rd_smgr, MAIN_FORKNUM);

            RelationCloseSmgr(srcPartToastRel);
            relation_close(srcPartToastRel, NoLock);
        }

        RelationCloseSmgr(srcPartRel);
        partitionClose(partTableRel, srcPartition, NoLock);
        releaseDummyRelation(&srcPartRel);
    }
}

/*
 * MERGE partitions p1, p2...pn into partition px
 * infact, pn is the same partition with px
 *  if px is deferent with pn, change pn's name to px
 */
static void ATExecMergePartition(Relation partTableRel, AlterTableCmd* cmd)
{
    List* srcPartitions = NIL;
    List* srcPartOids = NIL;
    List* index_list = NIL;
    List* indexRel_list = NIL;
    List* clonedIndexRelId_list = NIL;
    List* indexDestPartOid_list = NIL;
    ListCell* cell = NULL;
    char* destPartName = NULL;
    char* oldPartName = NULL;
    Oid destPartOid = InvalidOid;
    Partition destPart = NULL;
    Relation destPartRel = NULL;
    bool renameTargetPart = false;
    bool needUpdateIntervalToRange = false;
    int firstPartIndex = -1;
    int curPartIndex = -1;
    int prevPartIndex = -1;
    int iterator = 0;
    int partNum;
    Oid targetPartTablespaceOid = InvalidOid;
    TupleDesc partedTableHeapDesc;
    Datum partedTableRelOptions = 0;
    HeapTuple tuple = NULL;
    bool isNull = false;
    Oid tempTableOid = InvalidOid;
    Relation tempTableRel = NULL;
    ObjectAddress object;
    TransactionId FreezeXid;
    MultiXactId FreezeMultiXid;
    LOCKMODE lockMode = NoLock;

    srcPartitions = (List*)cmd->def;
    destPartName = cmd->name;
    partNum = srcPartitions->length;

    /* Branch if we are dealing with column-store */
    if (RelationIsColStore(partTableRel)) {
        ATExecCStoreMergePartition(partTableRel, cmd);
        return;
    }

    if (RelationIsUstoreFormat(partTableRel)) {
        PreventTransactionChain(true, "ALTER TALBE MERGE PARITITON");
    }

    /* the source partitions, must be at least 2, to merge into 1 partition  */
    if (partNum < 2) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("source partitions must be at least two partitions")));
    }
    if (partNum > MAX_MERGE_PARTITIONS) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                errmsg("merge partitions of relation \"%s\", source partitions must be no more than %d partitions",
                    RelationGetRelationName(partTableRel),
                    MAX_MERGE_PARTITIONS)));
    }

    /*
     * step 1: lock src partitions, and check the continuity of srcPartitions.
     * for 1...nth partition, we use AccessExclusiveLock lockmode
     * althought merge is something like delete and insert.
     */
    foreach (cell, srcPartitions) {
        char* partName = NULL;
        Oid srcPartOid = InvalidOid;

        iterator++;
        partName = strVal(lfirst(cell));

        /* from name to partition oid */
        srcPartOid = PartitionNameGetPartitionOid(partTableRel->rd_id,
            partName,
            PART_OBJ_TYPE_TABLE_PARTITION,
            AccessExclusiveLock,  // get AccessExclusiveLock lock on src partitions
            false,          // no missing
            false,          // wait
            NULL,
            NULL,
            NoLock);
        /* check local index 'usable' state */
        if (!checkPartitionLocalIndexesUsable(srcPartOid)) {
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                    errmsg("can't merge partition bacause partition %s has unusable local index", partName),
                    errhint("please reindex the unusable index first.")));
        }

#ifdef ENABLE_HTAP
        if (RelHasImcs(srcPartOid)) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                    errmsg("The part rel %s enable imcstore, please unimcstore if want to merge it.", partName)));
        }
#endif

        /* from partitionoid to partition sequence */
        curPartIndex = partOidGetPartSequence(partTableRel, srcPartOid) - 1;

        /* check the continuity of sequence, not the first round loop */
        if (iterator != 1) {
            if (curPartIndex - prevPartIndex != 1)
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                        errmsg("source partitions must be continuous and in ascending order of boundary")));
            RangePartitionMap* partMap = reinterpret_cast<RangePartitionMap*>(partTableRel->partMap);
            RangeElement* ranges = partMap->rangeElements;
            if (ranges[curPartIndex].isInterval) {
                // previous partition's upperBound should be equal with current partition's lowerBound
                Const* prevUpper = ranges[prevPartIndex].boundary[0];
                if (ValueCmpLowBoudary(&prevUpper, ranges + curPartIndex, partMap->intervalValue) != 0) {
                    ereport(ERROR,
                        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                            errmsg("source partitions must be continuous and in ascending order of boundary")));
                }
                needUpdateIntervalToRange = true;
            }
        } else {
            firstPartIndex = curPartIndex;
        }
        prevPartIndex = curPartIndex;

        /* save the last source partition name */
        if (iterator == partNum) {
            oldPartName = partName;
            destPartOid = srcPartOid;
        }

        /* save oid of src partition */
        srcPartOids = lappend_oid(srcPartOids, srcPartOid);
    }
    if (oldPartName == NULL) {
        ereport(ERROR,
            (errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
                errmsg("the last source partition name oldPartName is NULL")));
    }

    if (strcmp(oldPartName, destPartName) != 0) {
        /*  check partition new name does not exist. */
        if (InvalidOid != GetSysCacheOid3(PARTPARTOID,
                              NameGetDatum(destPartName),
                              CharGetDatum(PART_OBJ_TYPE_TABLE_PARTITION),
                              ObjectIdGetDatum(partTableRel->rd_id))) {
            ereport(ERROR,
                (errcode(ERRCODE_DUPLICATE_OBJECT),
                    errmsg("target partition's name \"%s\" already exists", destPartName)));
        }

        renameTargetPart = true;
    }

    if (cmd->alterGPI) {
        destPartOid = AddTemporaryRangePartitionForAlterPartitions(cmd, partTableRel, curPartIndex, &renameTargetPart);
        int partitionno = GetPartitionnoFromSequence(partTableRel->partMap, curPartIndex);
        UpdateCurrentPartitionNo(destPartOid, partitionno, false);
        lockMode = AccessExclusiveLock;
    }

    /*
     * step 2: create a temp table for merge
     * get desc of partitioned table
     */
    partedTableHeapDesc = RelationGetDescr(partTableRel);

    tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(RelationGetRelid(partTableRel)));
    if (!HeapTupleIsValid(tuple)) {
        ereport(ERROR,
            (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                errmsg("cache lookup failed for relation %u", RelationGetRelid(partTableRel))));
    }
    partedTableRelOptions = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, &isNull);
    if (isNull) {
        partedTableRelOptions = (Datum)0;
    }

    /*
     * open the dest partition.
     * If it's not alterGPI, destPart was already locked by PartitionNameGetPartitionOid() call.
     */
    destPart = partitionOpen(partTableRel, destPartOid, lockMode);
    destPartRel = partitionGetRelation(partTableRel, destPart);

    /* check target partition tablespace */
    if (PointerIsValid(cmd->target_partition_tablespace)) {
        targetPartTablespaceOid = get_tablespace_oid(cmd->target_partition_tablespace, false);
    } else {
        targetPartTablespaceOid = destPartRel->rd_rel->reltablespace;
    }

    /* create temp table and open it */
    tempTableOid = makePartitionNewHeap(partTableRel,
        partedTableHeapDesc,
        partedTableRelOptions,
        destPartRel->rd_id,
        destPartRel->rd_rel->reltoastrelid,
        targetPartTablespaceOid);
    object.classId = RelationRelationId;
    object.objectId = tempTableOid;
    object.objectSubId = 0;

    ReleaseSysCache(tuple);
    partitionClose(partTableRel, destPart, NoLock);
    releaseDummyRelation(&destPartRel);

    /* open temp relation */
    tempTableRel = relation_open(tempTableOid, AccessExclusiveLock);
    RelationOpenSmgr(tempTableRel);

    /* lock the index relation on partitioned table and check the usability */
    index_list = RelationGetSpecificKindIndexList(partTableRel, false);
    foreach (cell, index_list) {
        Oid dstIndexPartTblspcOid;
        Oid clonedIndexRelationId;
        Oid indexDestPartOid;
        Oid indexId = lfirst_oid(cell);
        char tmp_idxname[NAMEDATALEN];
        Relation currentIndex;
        bool skip_build = false;
        errno_t rc = EOK;

        /* Open the index relation, use AccessShareLock */
        currentIndex = index_open(indexId, AccessShareLock);

        if (!IndexIsUsable(currentIndex->rd_index)) {
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                    errmsg("merge partitions cannot process inusable index relation \''%s\''",
                        RelationGetRelationName(currentIndex))));
        } else {
            indexRel_list = lappend(indexRel_list, currentIndex);
        }

        if (OID_IS_BTREE(currentIndex->rd_rel->relam))
            skip_build = true;
        else
            skip_build = false;

        /* build name for tmp index */
        rc = snprintf_s(tmp_idxname,
            sizeof(tmp_idxname),
            sizeof(tmp_idxname) - 1,
            "pg_tmp_%u_%u_index",
            destPartOid,
            currentIndex->rd_id);
        securec_check_ss_c(rc, "\0", "\0");

        /* get tablespace oid of this index partition */
        dstIndexPartTblspcOid = getPartitionIndexTblspcOid(currentIndex->rd_id, destPartOid);

        /* build the same index for tmp table */
        clonedIndexRelationId =
            generateClonedIndex(currentIndex, tempTableRel, tmp_idxname, dstIndexPartTblspcOid, skip_build, false);
        indexDestPartOid = getPartitionIndexOid(currentIndex->rd_id, destPartOid);

        clonedIndexRelId_list = lappend_oid(clonedIndexRelId_list, clonedIndexRelationId);
        indexDestPartOid_list = lappend_oid(indexDestPartOid_list, indexDestPartOid);
    }

    if (OidIsValid(tempTableRel->rd_rel->reltoastrelid)) {
        /* set new empty filenode for toast index */
        Relation toastRel = relation_open(tempTableRel->rd_rel->reltoastrelid, AccessExclusiveLock);
        Relation toastIndexRel = index_open(toastRel->rd_rel->reltoastidxid, AccessExclusiveLock);
        RelationSetNewRelfilenode(toastIndexRel, InvalidTransactionId, InvalidMultiXactId);
        relation_close(toastRel, NoLock);
        index_close(toastIndexRel, NoLock);
    }

    /* step3: merge internal */
    if (RELATION_OWN_BUCKETKEY(partTableRel)) {
        /*
         * Flushing relation buffer needs to scan all buffers; It's too slow to scan buckets one by one.
         * Thus we do the flushing outside the inner loop.
         */
        bool hasToast = OidIsValid(tempTableRel->rd_rel->reltoastrelid);
        FlushMergedPartitionBuffers(partTableRel, srcPartOids, hasToast);

        Relation tempbucketRel = NULL;
        oidvector* bucketlist = searchHashBucketByOid(partTableRel->rd_bucketoid);

        for (int i = 0; i < bucketlist->dim1; i++) {
            tempbucketRel = bucketGetRelation(tempTableRel, NULL, bucketlist->values[i]);
            mergePartitionHeapData(partTableRel,
                tempbucketRel,
                srcPartOids,
                indexRel_list,
                clonedIndexRelId_list,
                bucketlist->values[i],
                &FreezeXid,
                &FreezeMultiXid);

            /* first bucket already merged into target cross bucket index. */
            if (i != 0) {
                AddCBIForPartition(partTableRel, tempbucketRel, indexRel_list, clonedIndexRelId_list);
            }
            bucketCloseRelation(tempbucketRel);
        }
    } else {
        mergePartitionHeapData(
            partTableRel, tempTableRel, srcPartOids, indexRel_list, clonedIndexRelId_list, InvalidBktId, &FreezeXid,
            &FreezeMultiXid);
    }

    /* close temp relation */
    RelationCloseSmgr(tempTableRel);
    heap_close(tempTableRel, NoLock);

    /* swap the index relfilenode*/
    mergePartitionIndexSwap(indexRel_list, indexDestPartOid_list, clonedIndexRelId_list, FreezeXid, FreezeMultiXid);

    /* swap the heap relfilenode */
    mergePartitionHeapSwap(partTableRel, destPartOid, tempTableOid, FreezeXid, FreezeMultiXid);
    CommandCounterIncrement();

    /*free index list*/
    list_free_ext(index_list);
    list_free_ext(clonedIndexRelId_list);
    list_free_ext(indexDestPartOid_list);
    const char* BRIEF_CMD_MERGE = "MERGE PARTITIONS";
    if (needUpdateIntervalToRange) {
        /* update previous partition and the new partition to range partition */
        Relation pgPartition = relation_open(PartitionRelationId, RowExclusiveLock);
        UpdatePrevIntervalPartToRange(partTableRel, pgPartition, firstPartIndex, BRIEF_CMD_MERGE);

        /* update merge result partition to range partition */
        UpdateIntervalPartToRange(pgPartition, destPartOid, BRIEF_CMD_MERGE);
        relation_close(pgPartition, NoLock);
    }
    /* ensure that preceding changes are all visible to the next deletion step. */
    CommandCounterIncrement();

    /* delete temp table */
    performDeletion(&object, DROP_CASCADE, PERFORM_DELETION_INTERNAL);

    /* close every index relation */
    foreach (cell, indexRel_list) {
        Relation currentIndex = (Relation)lfirst(cell);
        index_close(currentIndex, NoLock);
    }
    list_free_ext(indexRel_list);

    /* step 5: drop src partitions */
    foreach (cell, srcPartOids) {
        Oid srcPartOid = InvalidOid;
        srcPartOid = lfirst_oid(cell);
        if (destPartOid != srcPartOid) {
            AlterPartitionedSetWaitCleanGPI(cmd->alterGPI, partTableRel, srcPartOid);
            fastDropPartition(partTableRel, srcPartOid, "MERGE PARTITIONS");
        }
    }

    /*
     * step 6: rename p(n) to p(target) if needed, the dest partition is now locked by swap refilenode processing step
     */
    if (renameTargetPart) {
        renamePartitionInternal(partTableRel->rd_id, destPartOid, destPartName);
    }

    /* step 7: Unusable Global Index */
    if (!cmd->alterGPI) {
        // Unusable Global Index
        ATUnusableGlobalIndex(partTableRel);
    } else {
        AddGPIForPartition(RelationGetRelid(partTableRel), destPartOid);
    }
}


static void ATExecAddTblIntoCBI(Relation idxRel, const AddTableIntoCBIState* state)
{
    Oid relid;
    Oid tmpPartOid;
    Relation tmpHeapRel;
    Relation heapRel;
    Relation targetHeap;
    Relation targetIndex;
    Partition heapPart;
    Partition idxPart;
    List *heapparts       = NULL;
    List *indexparts      = NULL;
    List *tmpPartOids     = NULL;

    if (!u_sess->attr.attr_sql.enable_cluster_resize) {
        ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("ALTER INDEX ADD TABLE only support in pg_redis.")));
    }
    if (!RelationIsCrossBucketIndex(idxRel)) {
        ereport(LOG, (errmsg("alter index add table only support crossbucket index")));
        return;
    }
    ereport(LOG,
            (errmsg("ALTER INDEX %s ADD TABLE %s.", RelationGetRelationName(idxRel), state->relation->relname)));

    relid = RangeVarGetRelid(state->relation, NoLock, false);
    heapRel = heap_open(relid, AccessShareLock);

    if (RelationIsPartitioned(heapRel)) {
        tmpHeapRel = heap_open(IndexGetRelation(RelationGetRelid(idxRel), false), AccessShareLock);
        heapparts = relationGetPartitionList(heapRel, AccessShareLock);
        indexparts = RelationIsGlobalIndex(idxRel) ? NULL : GetIndexPartitionListByOrder(idxRel, AccessExclusiveLock);
        tmpPartOids = relationGetPartitionOidList(tmpHeapRel);
        for (int i = 0; i < list_length(heapparts); i++) {
            heapPart = (Partition)list_nth(heapparts, i);
            idxPart = (indexparts != NULL) ? (Partition)list_nth(indexparts, i) : NULL;
            targetHeap = partitionGetRelation(heapRel, heapPart);
            targetIndex = (idxPart != NULL) ? partitionGetRelation(idxRel, idxPart) : idxRel;
            tmpPartOid = list_nth_oid(tmpPartOids, i);
            ScanHeapInsertCBI(heapRel, targetHeap, targetIndex, tmpPartOid);
            releaseDummyRelation(&targetHeap);
            partitionClose(heapRel, heapPart, AccessShareLock);
            if (!RelationIsGlobalIndex(idxRel)) {
                releaseDummyRelation(&targetIndex);
                partitionClose(idxRel, idxPart, AccessExclusiveLock);
            }
        }
        list_free_ext(heapparts);
        list_free_ext(indexparts);
        list_free_ext(tmpPartOids);
        heap_close(tmpHeapRel, AccessShareLock);
    } else {
        ScanHeapInsertCBI(heapRel, heapRel, idxRel, InvalidOid);
    }
    heap_close(heapRel, AccessShareLock);
}

// When merge toast table, values of the first column may be repeat.
// So, we must replace these values, and record them in repeatChunkIdList.
// When merge heap, modify toast_pointer->va_valueid by repeatChunkIdList.
static void replaceRepeatChunkId(HTAB* chunkIdHashTable, List* srcPartToastRels)
{
    ListCell* cell = NULL;
    int i = 0;
    errno_t rc = EOK;

    foreach (cell, srcPartToastRels) {
        Relation srcPartToastRel = NULL;
        Relation toastIndexRel = NULL;
        TableScanDesc scan = NULL;
        void *tuple = NULL;
        TupleDesc tupleDesc = NULL;
        int numAttrs = 0;

        srcPartToastRel = (Relation)lfirst(cell);
        toastIndexRel = index_open(srcPartToastRel->rd_rel->reltoastidxid, RowExclusiveLock);

        tupleDesc = srcPartToastRel->rd_att;
        numAttrs = tupleDesc->natts;

        scan = tableam_scan_begin(srcPartToastRel, SnapshotNow, 0, NULL);

        while ((tuple = tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) {
            Datum values[numAttrs];
            bool isNull[numAttrs];
            Oid oldChunkId = InvalidOid;
            Oid newChunkId = InvalidOid;

            bool found = false;
            ChunkIdHashKey hashkey;
            OldToNewChunkIdMapping mapping = NULL;

            rc = memset_s(values, numAttrs, 0, numAttrs);
            securec_check(rc, "\0", "\0");

            rc = memset_s(isNull, numAttrs, 0, numAttrs);
            securec_check(rc, "\0", "\0");

            tableam_tops_deform_tuple(tuple, tupleDesc, values, isNull);

            oldChunkId = (Oid)values[0];

            rc = memset_s(&hashkey, sizeof(hashkey), 0, sizeof(hashkey));
            securec_check(rc, "\0", "\0");
            hashkey.toastTableOid = RelationGetRelid(srcPartToastRel);
            hashkey.oldChunkId = oldChunkId;

            mapping = (OldToNewChunkIdMapping)hash_search(chunkIdHashTable, &hashkey, HASH_FIND, NULL);

            // One data may be cut into several tuples.
            // These tuples have the same chunkId.
            // So we replace the same new value if need.
            if (PointerIsValid(mapping)) {
                {
                    HeapTuple copyTuple = NULL;

                    values[0] = mapping->newChunkId;
                    copyTuple = (HeapTuple)tableam_tops_form_tuple(tupleDesc, values, isNull, tupleDesc->td_tam_ops);

                    simple_heap_delete(srcPartToastRel, &((HeapTuple)tuple)->t_self);
                    (void)simple_heap_insert(srcPartToastRel, copyTuple);

                    (void)index_insert(toastIndexRel,
                        values,
                        isNull,
                        &(copyTuple->t_self),
                        srcPartToastRel,
                        toastIndexRel->rd_index->indisunique ? UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);

                    tableam_tops_free_tuple(copyTuple);
                }

                continue;
            }

            if (checkChunkIdRepeat(srcPartToastRels, i, oldChunkId)) {
                HeapTuple copyTuple = NULL;

                // Get one new oid, and it is not repeat in other toast tables.
                do {
                    newChunkId = GetNewObjectId();
                } while (checkChunkIdRepeat(srcPartToastRels, -1, newChunkId));

                values[0] = newChunkId;

                copyTuple = (HeapTuple)tableam_tops_form_tuple(tupleDesc, values, isNull, tupleDesc->td_tam_ops);

                simple_heap_delete(srcPartToastRel, &((HeapTuple)tuple)->t_self);
                (void)simple_heap_insert(srcPartToastRel, copyTuple);
                {
                    copyTuple = heap_form_tuple(tupleDesc, values, isNull);

                    simple_heap_delete(srcPartToastRel, &((HeapTuple)tuple)->t_self);
                    (void)simple_heap_insert(srcPartToastRel, copyTuple);

                    (void)index_insert(toastIndexRel,
                        values,
                        isNull,
                        &(copyTuple->t_self),
                        srcPartToastRel,
                        toastIndexRel->rd_index->indisunique ? UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);

                    tableam_tops_free_tuple(copyTuple);
                }

                // Enter hash table
                mapping = (OldToNewChunkIdMapping)hash_search(chunkIdHashTable, &hashkey, HASH_ENTER, &found);

                Assert(!found);

                mapping->newChunkId = newChunkId;
            }
        }

        tableam_scan_end(scan);
        index_close(toastIndexRel, RowExclusiveLock);

        i++;
    }
}

// Check whether or not chunkId is repeat in the other toast tables.
static bool checkChunkIdRepeat(List* srcPartToastRels, int selfIndex, Oid chunkId)
{
    ListCell* cell = NULL;
    int i = 0;

    foreach (cell, srcPartToastRels) {
        Relation srcPartToastRel = (Relation)lfirst(cell);

        // skip self.
        if ((i++ == selfIndex)) {
            continue;
        }

        if (toastrel_valueid_exists(srcPartToastRel, chunkId)) {
            return true;
        }
    }

    return false;
}

static void ExecuteUndoActionsForRelation(Relation rel)
{
    Assert(RelationIsUstoreFormat(rel));

    RelationOpenSmgr(rel);

    BlockNumber srcHeapBlocks = smgrnblocks(rel->rd_smgr, MAIN_FORKNUM);
    if (srcHeapBlocks == 0) {
        RelationCloseSmgr(rel);
        return;
    } 

    for (BlockNumber blkno = 0; blkno < srcHeapBlocks; blkno ++) {
        ExecuteUndoActionsForPartition(rel, rel->rd_smgr, MAIN_FORKNUM, blkno, blkno,
            ROLLBACK_OP_FOR_EXCHANGE_PARTITION);
    }

    RelationCloseSmgr(rel);
}

static void ExecuteUndoActionsForExchangePartition(Relation partTableRel, Oid partOid, Relation ordTableRel)
{
    Partition part = NULL;
    Relation partRel = NULL;

    part = partitionOpen(partTableRel, partOid, NoLock);
    partRel = partitionGetRelation(partTableRel, part);
    ExecuteUndoActionsForRelation(partRel);
    releaseDummyRelation(&partRel);
    partitionClose(partTableRel, part, NoLock);

    ExecuteUndoActionsForRelation(ordTableRel);
}

// Description : Execute exchange
static void ATExecExchangePartition(Relation partTableRel, AlterTableCmd* cmd)
{
    Oid ordTableOid = InvalidOid;
    Oid partOid = InvalidOid;
    Relation ordTableRel = NULL;
    List* partIndexList = NIL;
    List* ordIndexList = NIL;
    TransactionId relfrozenxid = InvalidTransactionId;
    MultiXactId relminmxid = InvalidMultiXactId;

    ordTableOid = RangeVarGetRelid(cmd->exchange_with_rel, AccessExclusiveLock, false);

    partOid = getPartitionOid(partTableRel, cmd->name, cmd->def);

    if (!OidIsValid(partOid)) {
        ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg("Specified partition does not exist")));
    }

    Assert(OidIsValid(ordTableOid));

#ifdef ENABLE_HTAP
    if (RelHasImcs(partOid) || RelHasImcs(ordTableOid)) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                errmsg("ExchangePartition not support for rel enabled imcstore, please unimcstore first.")));
    }
#endif

    ordTableRel = heap_open(ordTableOid, NoLock);

    if (ordTableRel->rd_rel->relkind != RELKIND_RELATION ||
        ordTableRel->rd_rel->parttype == PARTTYPE_PARTITIONED_RELATION ||
        ordTableRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
        ordTableRel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED ||
        ordTableRel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) {
        ereport(
            ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("ALTER TABLE EXCHANGE requires an ordinary table")));
    }

    // Check storage parameters for two tables
    checkStorageTypeForExchange(partTableRel, ordTableRel);

    // Check row level security policy
    if (RelationHasRlspolicy(partTableRel->rd_id) || RelationHasRlspolicy(ordTableRel->rd_id)) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("ALTER TABLE EXCHANGE not support with row level security policy table")));
    }

    // Check ordinary competence
    CheckTableNotInUse(ordTableRel, "ALTER TABLE");

    /* Check permission for independent user's tables */
    if (is_role_independent(partTableRel->rd_rel->relowner) &&
        !has_privs_of_role(GetUserId(), partTableRel->rd_rel->relowner)) {
        aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(partTableRel));
    }
    if (is_role_independent(ordTableRel->rd_rel->relowner) &&
        !has_privs_of_role(GetUserId(), ordTableRel->rd_rel->relowner)) {
        aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(ordTableRel));
    }
    
    ATSimplePermissions(ordTableRel, ATT_TABLE);

    // Check compress
    checkCompressForExchange(partTableRel, ordTableRel);

    // Check number, type of column
    checkColumnForExchange(partTableRel, ordTableRel);

    // Check constraint of two tables
    checkConstraintForExchange(partTableRel, ordTableRel);

#ifdef PGXC
    // Check distribute of two tables only on coordinator
    if (IS_PGXC_COORDINATOR) {
        checkDistributeForExchange(partTableRel, ordTableRel);
    }
#endif

    // Check number, type of index
    checkIndexForExchange(partTableRel, partOid, ordTableRel, &partIndexList, &ordIndexList);

    if (RelationIsUstoreFormat(partTableRel)) {
        ExecuteUndoActionsForExchangePartition(partTableRel, partOid, ordTableRel);
    }

    // Check if the tables are colstore
    checkColStoreForExchange(partTableRel, ordTableRel);
    // Swap object of partition and ordinary table
    LockPartition(partTableRel->rd_id, partOid, AccessExclusiveLock, PARTITION_LOCK);

    // Check the value is valided for partition boundary
    if (cmd->check_validation) {
        checkValidationForExchange(partTableRel, ordTableRel, partOid, cmd->exchange_verbose);
    }
    if (RelationIsPartition(ordTableRel))
        getPartitionRelxids(ordTableRel, &relfrozenxid, &relminmxid);
    else
        getRelationRelxids(ordTableRel, &relfrozenxid, &relminmxid);

    // Swap relfilenode of table and toast table
    finishPartitionHeapSwap(partOid, ordTableRel->rd_id, false, relfrozenxid, relminmxid);

    // Swap relfilenode of index
    Assert(list_length(partIndexList) == list_length(ordIndexList));
    if (0 != list_length(partIndexList)) {
        finishIndexSwap(partIndexList, ordIndexList);
        list_free_ext(partIndexList);
        list_free_ext(ordIndexList);
    }

    heap_close(ordTableRel, NoLock);

    if (!cmd->alterGPI) {
        // Unusable Global Index
        ATUnusableGlobalIndex(partTableRel);
    } else {
        ExchangePartitionWithGPI(cmd, partTableRel, partOid, relfrozenxid, relminmxid);
    }
}

static void checkStorageTypeForExchange(Relation partTableRel, Relation ordTableRel)
{
    if (RELATION_HAS_BUCKET(ordTableRel) != RELATION_HAS_BUCKET(partTableRel)) {
        ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("ALTER TABLE EXCHANGE requires both ordinary table and partitioned table "
                               "to have the same hashbucket option(on or off)")));
    }

    if (RELATION_CREATE_BUCKET(ordTableRel)) {
        oidvector *bucketList1 = searchHashBucketByOid(ordTableRel->rd_bucketoid);
        oidvector *bucketList2 = searchHashBucketByOid(partTableRel->rd_bucketoid);
        if (!hashbucket_eq(bucketList1, bucketList2)) {
            ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                            errmsg("ALTER TABLE EXCHANGE requires both ordinary table and partitioned table "
                                   "to have the same buckets list")));
        }
    }
    if (ordTableRel->storage_type != partTableRel->storage_type) {
        ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("ALTER TABLE EXCHANGE requires both ordinary table and partitioned table "
                               "to have the same storage type")));
    }
}

static void checkColStoreForExchange(Relation partTableRel, Relation ordTableRel)
{
    if ((RelationIsColStore(partTableRel) && !RelationIsColStore(ordTableRel)) ||
        (!RelationIsColStore(partTableRel) && RelationIsColStore(ordTableRel)))
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("tables in ALTER TABLE EXCHANGE PARTITION must have the same column/row storage")));
}
// Description : Check compress
static void checkCompressForExchange(Relation partTableRel, Relation ordTableRel)
{
    if (partTableRel->rd_rel->relcmprs != ordTableRel->rd_rel->relcmprs) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("tables in ALTER TABLE EXCHANGE PARTITION must have the same type of compress")));
    }
    if (partTableRel->rd_node.opt != ordTableRel->rd_node.opt) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("tables in ALTER TABLE EXCHANGE PARTITION must have the same type of compress")));
    }
}

// Description : Check number, type of column
static void checkColumnForExchange(Relation partTableRel, Relation ordTableRel)
{
    CatCList* partAttList = NULL;
    CatCList* ordAttList = NULL;
    HeapTuple partHeapTuple = NULL;
    HeapTuple ordHeapTuple = NULL;
    int i = 0;
    Relation attrdefRel = NULL;

    // Get column list
    partAttList = SearchSysCacheList1(ATTNUM, ObjectIdGetDatum(partTableRel->rd_id));
    ordAttList = SearchSysCacheList1(ATTNUM, ObjectIdGetDatum(ordTableRel->rd_id));

    Relation attRelation = heap_open(AttributeRelationId, AccessShareLock);
    TupleDesc attDesc = RelationGetDescr(attRelation);
    heap_close(attRelation, AccessShareLock);

    Datum* partVals = (Datum*)palloc(sizeof(Datum) * Natts_pg_attribute);
    Datum* ordVals = (Datum*)palloc(sizeof(Datum) * Natts_pg_attribute);
    bool* partNulls = (bool*)palloc(sizeof(bool) * Natts_pg_attribute);
    bool* ordNulls = (bool*)palloc(sizeof(bool) * Natts_pg_attribute);

    // Check column number
    if (partAttList->n_members != ordAttList->n_members) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("tables in ALTER TABLE EXCHANGE PARTITION must have the same number of columns")));
    }

    attrdefRel = heap_open(AttrDefaultRelationId, RowExclusiveLock);

    for (i = 0; i < partAttList->n_members; i++) {
        int j = 0;
        ScanKeyData scankeys[2];
        SysScanDesc partAttrdefScan = NULL;
        HeapTuple partAttrdefTuple = NULL;
        SysScanDesc ordAttrdefScan = NULL;
        HeapTuple ordAttrdefTuple = NULL;

        partHeapTuple = t_thrd.lsc_cxt.FetchTupleFromCatCList(partAttList, i);
        heap_deform_tuple(partHeapTuple, attDesc, partVals, partNulls);

        for (j = 0; j < ordAttList->n_members; j++) {
            ordHeapTuple = t_thrd.lsc_cxt.FetchTupleFromCatCList(ordAttList, i);
            heap_deform_tuple(ordHeapTuple, attDesc, ordVals, ordNulls);

            if (DatumGetInt8(ordVals[Anum_pg_attribute_attnum - 1]) ==
                DatumGetInt8(partVals[Anum_pg_attribute_attnum - 1])) {
                break;
            }
        }

        // Check column name
        if (strcmp(DatumGetName(ordVals[Anum_pg_attribute_attname - 1])->data,
            DatumGetName(partVals[Anum_pg_attribute_attname - 1])->data) != 0) {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("column name mismatch in ALTER TABLE EXCHANGE PARTITION")));
        }

        // Check column type and length
        if (DatumGetObjectId(ordVals[Anum_pg_attribute_atttypid - 1]) !=
            DatumGetObjectId(partVals[Anum_pg_attribute_atttypid - 1]) ||
            DatumGetInt8(ordVals[Anum_pg_attribute_attlen - 1]) !=
            DatumGetInt8(partVals[Anum_pg_attribute_attlen - 1]) ||
            DatumGetInt8(ordVals[Anum_pg_attribute_atttypmod - 1]) !=
            DatumGetInt8(partVals[Anum_pg_attribute_atttypmod - 1])) {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("column type or size mismatch in ALTER TABLE EXCHANGE PARTITION")));
        }

        // Check column not null constraint
        if (DatumGetBool(ordVals[Anum_pg_attribute_attnotnull - 1]) !=
            DatumGetBool(partVals[Anum_pg_attribute_attnotnull - 1])) {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("column not null constraint mismatch in ALTER TABLE EXCHANGE PARTITION")));
        }

        // Check column default constraint
        if (DatumGetBool(ordVals[Anum_pg_attribute_atthasdef - 1]) !=
            DatumGetBool(partVals[Anum_pg_attribute_atthasdef - 1])) {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("column default constraint mismatch in ALTER TABLE EXCHANGE PARTITION")));
        }

        ScanKeyInit(&scankeys[0],
            Anum_pg_attrdef_adrelid,
            BTEqualStrategyNumber,
            F_OIDEQ,
            ObjectIdGetDatum(partTableRel->rd_id));
        ScanKeyInit(&scankeys[1], Anum_pg_attrdef_adnum, BTEqualStrategyNumber, F_INT2EQ,
            partVals[Anum_pg_attribute_attnum - 1]);

        partAttrdefScan = systable_beginscan(attrdefRel, AttrDefaultIndexId, true, NULL, 2, scankeys);
        partAttrdefTuple = systable_getnext(partAttrdefScan);

        ScanKeyInit(&scankeys[0],
            Anum_pg_attrdef_adrelid,
            BTEqualStrategyNumber,
            F_OIDEQ,
            ObjectIdGetDatum(ordTableRel->rd_id));
        ScanKeyInit(&scankeys[1], Anum_pg_attrdef_adnum, BTEqualStrategyNumber, F_INT2EQ,
            ordVals[Anum_pg_attribute_attnum - 1]);

        ordAttrdefScan = systable_beginscan(attrdefRel, AttrDefaultIndexId, true, NULL, 2, scankeys);
        ordAttrdefTuple = systable_getnext(ordAttrdefScan);

        if ((partAttrdefTuple != NULL) && (ordAttrdefTuple != NULL)) {
            bool isnull = false;
            Datum partAdsrc = (Datum)0;
            Datum ordAdsrc = (Datum)0;

            partAdsrc = heap_getattr(partAttrdefTuple, Anum_pg_attrdef_adsrc, attrdefRel->rd_att, &isnull);
            ordAdsrc = heap_getattr(ordAttrdefTuple, Anum_pg_attrdef_adsrc, attrdefRel->rd_att, &isnull);

            if (strcmp(TextDatumGetCString(partAdsrc), TextDatumGetCString(ordAdsrc)) != 0) {
                systable_endscan(partAttrdefScan);
                systable_endscan(ordAttrdefScan);

                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("column default constraint mismatch in ALTER TABLE EXCHANGE PARTITION")));
            }
        }

        systable_endscan(partAttrdefScan);
        systable_endscan(ordAttrdefScan);

        // Check column collation
        if (DatumGetObjectId(ordVals[Anum_pg_attribute_attcollation - 1]) !=
            DatumGetObjectId(partVals[Anum_pg_attribute_attcollation - 1])) {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("column collation mismatch in ALTER TABLE EXCHANGE PARTITION")));
        }
        // Check column storage
        if (DatumGetChar(ordVals[Anum_pg_attribute_attstorage - 1]) !=
            DatumGetChar(partVals[Anum_pg_attribute_attstorage - 1])) {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("column storage mismatch in ALTER TABLE EXCHANGE PARTITION")));
        }

        // Check the type of column compress
        if (DatumGetInt8(ordVals[Anum_pg_attribute_attcmprmode - 1]) !=
            DatumGetInt8(partVals[Anum_pg_attribute_attcmprmode - 1])) {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("the type of column compress mismatch in ALTER TABLE EXCHANGE PARTITION")));
        }

        // Check kv storage type
        if (DatumGetInt8(ordVals[Anum_pg_attribute_attkvtype - 1]) !=
            DatumGetInt8(partVals[Anum_pg_attribute_attkvtype - 1])) {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("the kv storage type of column mismatch in ALTER TABLE EXCHANGE PARTITION")));
        }
    }

    heap_close(attrdefRel, RowExclusiveLock);

    ReleaseSysCacheList(partAttList);
    ReleaseSysCacheList(ordAttList);
    pfree_ext(ordVals);
    pfree_ext(partVals);
    pfree_ext(ordNulls);
    pfree_ext(partNulls);
}

bool checkPartitionLocalIndexesUsable(Oid partitionOid)
{
    bool ret = true;
    Relation pg_part_rel = NULL;
    ScanKeyData scankeys[1];
    SysScanDesc partScan = NULL;
    HeapTuple partTuple = NULL;

    pg_part_rel = relation_open(PartitionRelationId, AccessShareLock);
    ScanKeyInit(
        &scankeys[0], Anum_pg_partition_indextblid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(partitionOid));
    partScan = systable_beginscan(pg_part_rel, PartitionIndexTableIdIndexId, true, NULL, 1, scankeys);
    while (NULL != (partTuple = systable_getnext(partScan))) {
        Relation indexRel = NULL;
        Partition indexPart = NULL;
        Oid indexRelOid = ((Form_pg_partition)GETSTRUCT(partTuple))->parentid;
        Oid indexPartOid = HeapTupleGetOid(partTuple);

        // the index relation is already locked by upper caller function
        indexRel = index_open(indexRelOid, NoLock);
        // we will keep the lock on index partition.
        indexPart = partitionOpen(indexRel, indexPartOid, ExclusiveLock);
        if (!indexPart->pd_part->indisusable) {
            partitionClose(indexRel, indexPart, NoLock);
            index_close(indexRel, NoLock);
            ret = false;
            break;
        } else {
            partitionClose(indexRel, indexPart, NoLock);
            index_close(indexRel, NoLock);
            continue;
        }
    }

    systable_endscan(partScan);
    relation_close(pg_part_rel, AccessShareLock);
    return ret;
}

/*
 * @Description: check whether the partitioned relation has usable index.
 * @in relation: the partitioned relation.
 * @return: whether the partitioned relation has usable index.
 * notes: the invoker must check relation is partitioned first.
 */
bool checkRelationLocalIndexesUsable(Relation relation)
{
    bool ret = true;
    Relation indrel;
    SysScanDesc indscan;
    ScanKeyData skey;
    HeapTuple htup;

    /* Prepare to scan pg_index for entries having indrelid = this rel. */
    Oid relid = RelationIsPartitionOfSubPartitionTable(relation) ? relation->parentId : RelationGetRelid(relation);
    ScanKeyInit(
        &skey, Anum_pg_index_indrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relid));

    indrel = heap_open(IndexRelationId, AccessShareLock);
    indscan = systable_beginscan(indrel, IndexIndrelidIndexId, true, NULL, 1, &skey);

    while (HeapTupleIsValid(htup = systable_getnext(indscan))) {
        Form_pg_index index = (Form_pg_index)GETSTRUCT(htup);
        Relation index_relation = index_open(index->indexrelid, AccessShareLock);

        if (!IndexIsUsable(index) && !RelationIsGlobalIndex(index_relation)) {
            index_close(index_relation, AccessShareLock);
            ret = false;
            break;
        }
        index_close(index_relation, AccessShareLock);
    }

    systable_endscan(indscan);
    heap_close(indrel, AccessShareLock);

    return ret;
}

// Description : Check constraint of two tables
static void checkConstraintForExchange(Relation partTableRel, Relation ordTableRel)
{
    List* partConList = NIL;
    List* ordConList = NIL;
    Relation pgConstraint = NULL;
    ListCell* partCell = NULL;
    ListCell* ordCell = NULL;

    // Get constraint list
    partConList = getConstraintList(partTableRel->rd_id);
    ordConList = getConstraintList(ordTableRel->rd_id);

    // Check constraint number
    if (list_length(partConList) != list_length(ordConList)) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("constraint mismatch in ALTER TABLE EXCHANGE PARTITION")));
    }

    pgConstraint = heap_open(ConstraintRelationId, AccessShareLock);

    foreach (partCell, partConList) {
        Form_pg_constraint partConForm = NULL;
        HeapTuple partConTuple = NULL;
        bool isNull = false;
        Datum partConDatum;
        ArrayType* partConKeyArr = NULL;
        int16* partConKeyAttNums = NULL;
        int partConKeyNum = 0;
        bool isMatch = false;

        partConTuple = (HeapTuple)lfirst(partCell);
        partConForm = (Form_pg_constraint)GETSTRUCT(partConTuple);

        // Get column number and column index
        // of the constriant on partitioned table
        partConDatum = heap_getattr(partConTuple, Anum_pg_constraint_conkey, RelationGetDescr(pgConstraint), &isNull);
        partConKeyArr = DatumGetArrayTypeP(partConDatum);
        partConKeyNum = ARR_DIMS(partConKeyArr)[0];
        partConKeyAttNums = (int16*)ARR_DATA_PTR(partConKeyArr);

        foreach (ordCell, ordConList) {
            Form_pg_constraint ordConForm = NULL;
            HeapTuple ordConTuple = NULL;
            Datum ordConDatum;
            ArrayType* ordConKeyArr = NULL;
            int16* ordConKeyAttNums = NULL;
            int ordConKeyNum = 0;
            int i = 0;

            isMatch = false;

            ordConTuple = (HeapTuple)lfirst(ordCell);
            ordConForm = (Form_pg_constraint)GETSTRUCT(ordConTuple);

            // get column number and column index
            // of the constriant on partitioned table
            ordConDatum = heap_getattr(ordConTuple, Anum_pg_constraint_conkey, RelationGetDescr(pgConstraint), &isNull);
            ordConKeyArr = DatumGetArrayTypeP(ordConDatum);
            ordConKeyNum = ARR_DIMS(ordConKeyArr)[0];
            ordConKeyAttNums = (int16*)ARR_DATA_PTR(ordConKeyArr);

            if ((ordConForm->contype == partConForm->contype) && (ordConKeyNum == partConKeyNum)) {
                // If the constriant is check constriant,
                // check the check expression
                if (ordConForm->contype == 'c') {
                    if (constraints_equivalent(ordConTuple, partConTuple, pgConstraint->rd_att)) {
                        isMatch = true;
                    }
                } else {
                    isMatch = true;

                    for (i = 0; i < ordConKeyNum; i++) {
                        if (ordConKeyAttNums[i] != partConKeyAttNums[i]) {
                            isMatch = false;
                            break;
                        }
                    }
                }

                if (isMatch) {
                    break;
                }
            }
        }

        if (!isMatch) {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("constraint mismatch in ALTER TABLE EXCHANGE PARTITION")));
        }
    }

    heap_close(pgConstraint, AccessShareLock);

    freeConstraintList(partConList);
    freeConstraintList(ordConList);
}

/*
 * Alter Matview and their maps and mlogs to group
 */
void ATMatviewGroup(List* stmts, Oid mvid, LOCKMODE lockmode)
{
    ListCell *lc = NULL;
    Query *query = NULL;
    List *relids = NIL;
    AlterTableStmt *stmtf = (AlterTableStmt *)linitial(stmts);
    AlterTableCmd *cmd = (AlterTableCmd *)linitial(stmtf->cmds);

    if (list_length(stmtf->cmds) != 1 || cmd->subtype != AT_SubCluster) {
        return;
    }

    if ((get_rel_relkind(mvid) != RELKIND_MATVIEW) || (!is_incremental_matview(mvid))) {
        return;
    }

    /* Alter matmap node group */
    Oid mapid = get_matview_mapid(mvid);
    if (mapid) {
        stmtf->relation->relname = get_rel_name(mapid);
        AlterTable(mapid, lockmode, stmtf);
    }

    /* Alter matview reltable node group */
    Relation matview = heap_open(mvid, lockmode);
    query = get_matview_query(matview);
    relids = pull_up_rels_recursive((Node *)query);

    foreach (lc, relids) {
        Oid relid = (Oid)lfirst_oid(lc);
        Oid mlogid = find_matview_mlog_table(relid);

        stmtf->relation->relname = get_rel_name(mlogid);
        AlterTable(mlogid, lockmode, stmtf);
    }

    heap_close(matview, NoLock);
    return;
}

// Description : Get constraint tuple list of table
static List* getConstraintList(Oid relOid, char conType)
{
    List* result = NIL;
    Relation pgConstraint = NULL;
    HeapTuple tuple = NULL;
    SysScanDesc scan = NULL;
    ScanKeyData skey[2];
    int nkeys;

    pgConstraint = heap_open(ConstraintRelationId, AccessShareLock);

    ScanKeyInit(&skey[0], Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relOid));
    if (conType != CONSTRAINT_INVALID) {
        ScanKeyInit(&skey[1], Anum_pg_constraint_contype, BTEqualStrategyNumber, F_CHAREQ, CharGetDatum(conType));
        nkeys = 2;
        scan = systable_beginscan(pgConstraint, ConstraintRelidIndexId, false, NULL, nkeys, skey);

    } else {
        nkeys = 1;
        scan = systable_beginscan(pgConstraint, ConstraintRelidIndexId, true, NULL, nkeys, skey);
    }

    while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
        HeapTuple resultTuple = (HeapTuple) tableam_tops_copy_tuple(tuple);
        result = lappend(result, resultTuple);
    }

    systable_endscan(scan);
    heap_close(pgConstraint, AccessShareLock);

    return result;
}

// Description : Free constraint tuple list of table
static void freeConstraintList(List* list)
{
    ListCell* cell = NULL;
    HeapTuple tuple = NULL;

    foreach (cell, list) {
        tuple = (HeapTuple)lfirst(cell);
        if (HeapTupleIsValid(tuple)) {
            tableam_tops_free_tuple(tuple);
        }
    }

    list_free_ext(list);
}

static bool colHasPartialClusterKey(Relation rel, AttrNumber attNum)
{
    List* constraintList = getConstraintList(RelationGetRelid(rel), CONSTRAINT_CLUSTER);
    bool colHasCluster = false;
    ListCell* lc = NULL;
    Relation pgConstraint = NULL;
    bool isNull = false;
    Datum conKeyDatum;

    ArrayType* conKeyArr = NULL;
    int16* conKeyAttNums = NULL;
    int conKeyNum = 0;
    int i = 0;

    pgConstraint = heap_open(ConstraintRelationId, AccessShareLock);

    foreach (lc, constraintList) {
        HeapTuple constrTuple = (HeapTuple)lfirst(lc);

        conKeyDatum = heap_getattr(constrTuple, Anum_pg_constraint_conkey, RelationGetDescr(pgConstraint), &isNull);
        conKeyArr = DatumGetArrayTypeP(conKeyDatum);
        conKeyNum = ARR_DIMS(conKeyArr)[0];
        conKeyAttNums = (int16*)ARR_DATA_PTR(conKeyArr);

        for (i = 0; i < conKeyNum; i++) {
            if (attNum == conKeyAttNums[i]) {
                colHasCluster = true;
                break;
            }
        }
    }

    heap_close(pgConstraint, AccessShareLock);

    freeConstraintList(constraintList);

    return colHasCluster;
}

// Description : Check distribute attribute of two tables
static void checkDistributeForExchange(Relation partTableRel, Relation ordTableRel)
{
    Relation pgxcClass = NULL;
    HeapTuple partTuple = NULL;
    HeapTuple ordTuple = NULL;
    Form_pgxc_class partForm = NULL;
    Form_pgxc_class ordForm = NULL;
    bool isMatch = false;
    oidvector* partNodes = NULL;
    oidvector* ordNodes = NULL;
    bool isNull = false;

    pgxcClass = heap_open(PgxcClassRelationId, RowExclusiveLock);

    partTuple = SearchSysCache1(PGXCCLASSRELID, ObjectIdGetDatum(partTableRel->rd_id));
    if (!HeapTupleIsValid(partTuple))
        ereport(ERROR,
            ((errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                errmsg("cache lookup failed for relaton %u", partTableRel->rd_id))));

    Datum part_nodes_datum = heap_getattr(partTuple, Anum_pgxc_class_nodes, RelationGetDescr(pgxcClass), &isNull);

    if (isNull)
        elog(PANIC, "Can't get nodeoid for relation %s", RelationGetRelationName(partTableRel));
    partNodes = (oidvector*)PG_DETOAST_DATUM(part_nodes_datum);

    partForm = (Form_pgxc_class)GETSTRUCT(partTuple);

    /* Get ord information */
    ordTuple = SearchSysCache1(PGXCCLASSRELID, ObjectIdGetDatum(ordTableRel->rd_id));
    if (!HeapTupleIsValid(ordTuple)) {
        ereport(ERROR,
            (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                errmodule(MOD_HDFS),
                    errmsg("cache lookup failed from %u", PGXCCLASSRELID)));
    }

    Datum ordnodes_datum = heap_getattr(ordTuple, Anum_pgxc_class_nodes, RelationGetDescr(pgxcClass), &isNull);
    if (isNull)
        ereport(PANIC,
            ((errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
                errmsg("Can't get nodeoid for relation %s", RelationGetRelationName(ordTableRel)))));

    ordNodes = (oidvector*)PG_DETOAST_DATUM(ordnodes_datum);

    ordForm = (Form_pgxc_class)GETSTRUCT(ordTuple);

    if (partForm->pclocatortype == ordForm->pclocatortype && partForm->pchashalgorithm == ordForm->pchashalgorithm &&
        partNodes->dim1 == ordNodes->dim1 &&
        DatumGetBool(DirectFunctionCall2(
            int2vectoreq, PointerGetDatum(&partForm->pcattnum), PointerGetDatum(&ordForm->pcattnum)))) {
        int i = 0;

        isMatch = true;

        for (i = 0; i < partNodes->dim1; i++) {
            if (partNodes->values[i] != ordNodes->values[i]) {
                isMatch = false;
                break;
            }
        }
    }

    if ((oidvector*)DatumGetPointer(part_nodes_datum) != partNodes)
        pfree_ext(partNodes);

    if ((oidvector*)DatumGetPointer(ordnodes_datum) != ordNodes)
        pfree_ext(ordNodes);

    if (!isMatch) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("distribute mismatch for tables in ALTER TABLE EXCHANGE PARTITION")));
    }

    ReleaseSysCache(partTuple);
    ReleaseSysCache(ordTuple);

    heap_close(pgxcClass, RowExclusiveLock);
}

// Description : Check index of two tables
static void checkIndexForExchange(
    Relation partTableRel, Oid partOid, Relation ordTableRel, List** partIndexList, List** ordIndexList)
{
    ListCell* oidCell = NULL;
    ListCell* tupleCell = NULL;
    List* partTableIndexOidList = NIL;
    List* ordTableIndexOidList = NIL;
    HeapTuple ordTableIndexTuple = NULL;
    List* ordTableIndexTupleList = NIL;
    bool* matchFlag = NULL;

    partTableIndexOidList = RelationGetSpecificKindIndexList(partTableRel, false);
    ordTableIndexOidList = RelationGetIndexList(ordTableRel);

    if (list_length(partTableIndexOidList) == 0 && list_length(ordTableIndexOidList) == 0) {
        return;
    }

    if (list_length(partTableIndexOidList) != list_length(ordTableIndexOidList)) {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("tables in ALTER TABLE EXCHANGE PARTITION must have the same number of indexs")));
    }

    foreach (oidCell, ordTableIndexOidList) {
        Oid ordTableIndexOid = lfirst_oid(oidCell);
        ordTableIndexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(ordTableIndexOid));
        if (!HeapTupleIsValid(ordTableIndexTuple)) {
            ereport(ERROR,
                (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for index %u", ordTableIndexOid)));
        }

        ordTableIndexTupleList = lappend(ordTableIndexTupleList, ordTableIndexTuple);
    }

    matchFlag = (bool*)palloc0(list_length(partTableIndexOidList) * sizeof(bool));

    foreach (oidCell, partTableIndexOidList) {
        Oid partTableIndexOid = lfirst_oid(oidCell);
        Oid partIndexOid = InvalidOid;
        HeapTuple indexPartTuple = NULL;
        Form_pg_partition indexPartForm = NULL;
        HeapTuple partTalbeIndexTuple = NULL;
        Form_pg_index partTalbeIndexForm = NULL;
        Form_pg_index ordTalbeIndexForm = NULL;
        bool isMatch = false;
        bool partTableIndexPartUsable = true;
        int matchFlagIndex = -1;

        // step 1: check index 'indisusable' state
        partTalbeIndexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(partTableIndexOid));
        partTalbeIndexForm = (Form_pg_index)GETSTRUCT(partTalbeIndexTuple);
        if (!IndexIsValid(partTalbeIndexForm)) {
            partTableIndexPartUsable = false;
        }

        partIndexOid = getPartitionIndexOid(partTableIndexOid, partOid);
        // step 2: check index partition 'indisusable' state
        if (partTableIndexPartUsable) {
            indexPartTuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(partIndexOid));
            if (!HeapTupleIsValid(indexPartTuple)) {
                ereport(ERROR,
                    (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                        errmodule(MOD_HDFS),
                            errmsg("cache lookup failed from %u", PARTRELID)));
            }
            indexPartForm = (Form_pg_partition)GETSTRUCT(indexPartTuple);

            if (!indexPartForm->indisusable) {
                partTableIndexPartUsable = false;
            }
            ReleaseSysCache(indexPartTuple);
        }

        // step 3: check whether 2 index are matching
        foreach (tupleCell, ordTableIndexTupleList) {
            Oid ordTableIndexOid = InvalidOid;
            HeapTuple partTableIndexClassTuple = NULL;
            HeapTuple ordTableIndexClassTuple = NULL;
            Form_pg_class partTableIndexClassForm = NULL;
            Form_pg_class ordTableIndexClassForm = NULL;
            bool ordTableIndexUsable = true;

            isMatch = false;

            matchFlagIndex++;

            if (matchFlag[matchFlagIndex]) {
                continue;
            }

            ordTableIndexTuple = (HeapTuple)lfirst(tupleCell);
            ordTalbeIndexForm = (Form_pg_index)GETSTRUCT(ordTableIndexTuple);

            ordTableIndexOid = ordTalbeIndexForm->indexrelid;
            if (!IndexIsValid(ordTalbeIndexForm)) {
                ordTableIndexUsable = false;
            }
            if (partTableIndexPartUsable != ordTableIndexUsable) {
                continue;
            }

            partTableIndexClassTuple = SearchSysCache1(RELOID, ObjectIdGetDatum(partTableIndexOid));
            ordTableIndexClassTuple = SearchSysCache1(RELOID, ObjectIdGetDatum(ordTableIndexOid));
            partTableIndexClassForm = (Form_pg_class)GETSTRUCT(partTableIndexClassTuple);
            ordTableIndexClassForm = (Form_pg_class)GETSTRUCT(ordTableIndexClassTuple);

            // index access method
            if (partTableIndexClassForm->relam != ordTableIndexClassForm->relam) {
                ReleaseSysCache(partTableIndexClassTuple);
                ReleaseSysCache(ordTableIndexClassTuple);
                continue;
            }

            ReleaseSysCache(partTableIndexClassTuple);
            ReleaseSysCache(ordTableIndexClassTuple);

            if ((ordTalbeIndexForm->indnatts == partTalbeIndexForm->indnatts) &&
                (ordTalbeIndexForm->indisunique == partTalbeIndexForm->indisunique) &&
                (ordTalbeIndexForm->indisprimary == partTalbeIndexForm->indisprimary)) {
                int i = 0;
                isMatch = true;

                for (i = 0; i < partTalbeIndexForm->indkey.dim1; i++) {
                    if (ordTalbeIndexForm->indkey.values[i] != partTalbeIndexForm->indkey.values[i]) {
                        isMatch = false;
                        break;
                    }
                }
            }

            if (isMatch) {
                *partIndexList = lappend_oid(*partIndexList, partIndexOid);
                *ordIndexList = lappend_oid(*ordIndexList, ordTableIndexOid);

                matchFlag[matchFlagIndex] = true;
                break;
            }
        }

        ReleaseSysCache(partTalbeIndexTuple);

        if (!isMatch) {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("index mismatch for tables in ALTER TABLE EXCHANGE PARTITION")));
        }
    }

    foreach (tupleCell, ordTableIndexTupleList) {
        ordTableIndexTuple = (HeapTuple)lfirst(tupleCell);
        ReleaseSysCache(ordTableIndexTuple);
    }

    list_free_ext(partTableIndexOidList);
    list_free_ext(ordTableIndexOidList);
    list_free_ext(ordTableIndexTupleList);

    pfree_ext(matchFlag);
}

// Description : Check all tuples of ordinary whether locate the partition
template <bool exchangeVerbose>
static void checkValidationForExchangeTable(Relation partTableRel, Relation ordTableRel, int partSeq)
{
    TableScanDesc scan = NULL;
    void* tuple = NULL;
    TupleDesc tupleDesc = NULL;
    HTAB* partRelHTAB = NULL;
    List* indexList = NIL;
    List* indexRelList = NIL;
    List* indexInfoList = NIL;
    ListCell* cell = NULL;
    ListCell* cell1 = NULL;
    EState* estate = NULL;
    TupleTableSlot* indexslot = NULL;
    bool relisustore = RelationIsUstoreFormat(ordTableRel);

    if (relisustore != RelationIsUstoreFormat(partTableRel))
    {
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
            errmsg("cannot exchange between different orientations")));
    }


    tupleDesc = ordTableRel->rd_att;

    if (exchangeVerbose) {
        indexList = RelationGetIndexList(partTableRel, false);

        foreach (cell, indexList) {
            Oid indexOid = lfirst_oid(cell);
            Relation indexRel = relation_open(indexOid, RowExclusiveLock);
            IndexInfo* indexInfo = BuildIndexInfo(indexRel);

            indexRelList = lappend(indexRelList, indexRel);
            indexInfoList = lappend(indexInfoList, indexInfo);
        }

        if (PointerIsValid(indexRelList)) {
            estate = CreateExecutorState();
            /*
            * We need a ResultRelInfo so we can use the regular executor's
            * index-entry-making machinery.  (There used to be a huge amount of code
            * here that basically duplicated execUtils.c ...)
            */
            ResultRelInfo *resultRelInfo = makeNode(ResultRelInfo);
            resultRelInfo->ri_RangeTableIndex = 1;      /* dummy */
            resultRelInfo->ri_RelationDesc = ordTableRel;
            ExecOpenIndices(resultRelInfo, false);
            estate->es_result_relations = resultRelInfo;
            estate->es_num_result_relations = 1;
            estate->es_result_relation_info = resultRelInfo;

            indexslot = MakeSingleTupleTableSlot(RelationGetDescr(partTableRel), false, partTableRel->rd_tam_ops);
        }
    }

    scan = scan_handler_tbl_beginscan(ordTableRel, SnapshotNow, 0, NULL);

    while ((tuple = scan_handler_tbl_getnext(scan, ForwardScanDirection, ordTableRel)) != NULL) {
        if (!isTupleLocatePartition(partTableRel, partSeq, tupleDesc, tuple)) {
            if (!exchangeVerbose) {
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("some rows in table do not qualify for specified partition")));
            } else {
                Oid targetPartOid = InvalidOid;
                Relation partRel = NULL;
                Partition part = NULL;
                Tuple copyTuple = NULL;
                int2 bucketId = InvalidBktId;

                // get right partition oid for the tuple
                targetPartOid = heapTupleGetPartitionOid(partTableRel, (HeapTuple)tuple, NULL, true);

                searchFakeReationForPartitionOid(partRelHTAB, CurrentMemoryContext, partTableRel, targetPartOid,
                    INVALID_PARTITION_NO, partRel, part, RowExclusiveLock);

                if (RELATION_HAS_BUCKET(partTableRel)) {
                    // Get the target bucket.
                    bucketId = computeTupleBucketId(partTableRel, (HeapTuple)tuple);
                    searchHBucketFakeRelation(partRelHTAB, CurrentMemoryContext, partRel, bucketId, partRel);
                }

                /* insert the copied tuple in the right partition */
                copyTuple = tableam_tops_copy_tuple(tuple);
                if (partRel == NULL) {
                    ereport(ERROR,
                        (errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
                            errmsg("part not found for partition relation")));
                }
                tableam_tuple_insert(partRel, copyTuple, GetCurrentCommandId(true), 0, NULL);

                /* insert the index tuple */
                if (PointerIsValid(indexRelList)) {
                    (void)ExecStoreTuple(copyTuple, indexslot, InvalidBuffer, false);
                }

                forboth(cell, indexRelList, cell1, indexInfoList)
                {
                    Relation indexRel = (Relation)lfirst(cell);
                    IndexInfo* indexInfo = (IndexInfo*)lfirst(cell1);
                    Oid indexOid = RelationGetRelid(indexRel);
                    Oid partIndexOid = InvalidOid;
                    Relation partIndexRel = NULL;
                    Partition partIndex = NULL;

                    Datum values[tupleDesc->natts];
                    bool isNull[tupleDesc->natts];
                    bool estateIsNotNull = false;

                    bool isglobal = RelationIsGlobalIndex(indexRel);
                    if (!isglobal) {
                        partIndexOid = getPartitionIndexOid(indexOid, targetPartOid);
                        searchFakeReationForPartitionOid(partRelHTAB,
                            CurrentMemoryContext,
                            indexRel,
                            partIndexOid,
                            INVALID_PARTITION_NO,
                            partIndexRel,
                            partIndex,
                            RowExclusiveLock);
                        indexRel = partIndexRel;
                    }

                    if (RELATION_HAS_BUCKET(indexRel) && !(RelationAmIsBtree(indexRel) &&
                        RELOPTIONS_CROSSBUCKET(indexRel->rd_options))) {
                        searchHBucketFakeRelation(
                            partRelHTAB, CurrentMemoryContext, indexRel, bucketId, indexRel);
                    }

                    if (indexInfo->ii_Expressions != NIL || indexInfo->ii_ExclusionOps != NULL) {
                        ExprContext* econtext = GetPerTupleExprContext(estate);
                        econtext->ecxt_scantuple = indexslot;
                        estateIsNotNull = true;
                    }

                    FormIndexDatum(indexInfo, indexslot, estateIsNotNull ? estate : NULL, values, isNull);

                    (void)index_insert(indexRel,
                        values,
                        isNull,
                        &((HeapTuple)copyTuple)->t_self,
                        partRel,
                        indexRel->rd_index->indisunique ? UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);
                }

                /* tableam_tops_free_tuple is not ready so we add UStore hack path */
                tableam_tops_free_tuple(copyTuple);
                TM_Result result;
                TM_FailureData tmfd;
                Relation fake_relation = GetTableScanDesc(scan, ordTableRel)->rs_rd;
                TupleTableSlot* oldslot = NULL;
                result = tableam_tuple_delete(fake_relation,
                    &((HeapTuple)tuple)->t_self,
                    GetCurrentCommandId(true),
                    InvalidSnapshot,
                    GetActiveSnapshot(),
                    true,
                    &oldslot,
                    &tmfd,
                    true);
                switch (result) {
                    case TM_SelfModified:
                        /* Tuple was already updated in current command? */
                        ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), 
                            errmsg("tuple already updated by self")));
                        break;

                    case TM_Ok: { /* done successfully */
                        if (PointerIsValid(indexRelList)) {
                            Bitmapset *modifiedIdxAttrs = NULL;
                            ExecIndexTuplesState exec_index_tuples_state;
                            exec_index_tuples_state.estate = estate;
                            exec_index_tuples_state.targetPartRel = NULL;
                            exec_index_tuples_state.p = NULL;
                            exec_index_tuples_state.conflict = NULL;
                            exec_index_tuples_state.rollbackIndex = false;
                            tableam_tops_exec_delete_index_tuples(oldslot, fake_relation, NULL,
                                &((HeapTuple)tuple)->t_self, exec_index_tuples_state, modifiedIdxAttrs);
                        }
                        if (oldslot) {
                            ExecDropSingleTupleTableSlot(oldslot);
                        }
                        break;
                    }
                    case TM_Updated:
                        ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), 
                            errmsg("tuple concurrently updated")));
                        break;

                    case TM_Deleted:
                        ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), 
                            errmsg("tuple concurrently updated")));
                        break;

                    default:
                        ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), 
                            errmsg("unrecognized delete status: %u", result)));
                        break;
                }

            }
        }
    }

    scan_handler_tbl_endscan(scan);

    if (PointerIsValid(partRelHTAB)) {
        FakeRelationCacheDestroy(partRelHTAB);
    }

    if (exchangeVerbose) {
        foreach (cell, indexRelList) {
            Relation indexRel = (Relation)lfirst(cell);

            relation_close(indexRel, RowExclusiveLock);
        }

        list_free_ext(indexRelList);
        list_free_ext(indexInfoList);

        if (PointerIsValid(estate)) {
            ExecCloseIndices(estate->es_result_relation_info);
            pfree_ext(estate->es_result_relations);
            FreeExecutorState(estate);
        }

        if (PointerIsValid(indexslot)) {
            ExecDropSingleTupleTableSlot(indexslot);
        }
    }
}

template <bool exchangeVerbose>
static void checkValidationForExchangeCStore(Relation partTableRel, Relation ordTableRel, int partSeq)
{
    RangePartitionMap* partMap = (RangePartitionMap*)(partTableRel->partMap);
    int2vector* partkeyColumns = partMap->base.partitionKey;
    int partkeyColumnNum = partkeyColumns->dim1;

    AttrNumber* scanAttrNumbers = NULL;
    int scanColNum = 0;

    CStoreScanDesc scanstate;
    VectorBatch* vecScanBatch = NULL;
    ScalarVector* pVec = NULL;
    ScalarValue* pVals = NULL;
    Datum* values = NULL;
    bool* nulls = NULL;
    FormData_pg_attribute* attrs = ordTableRel->rd_att->attrs;

    Const consts[MAX_RANGE_PARTKEY_NUMS];
    Const* partKeyValues[MAX_RANGE_PARTKEY_NUMS];
    bool isInPart = false;

    const int tididx = 1;       // junk column for cstore delete
    const int tableoidIdx = 2;  // junk column(tableoid) for cstore delete
    int countNotInPart = 0;
    EState* estate = NULL;
    VectorBatch* pBatchNotInPart = NULL;
    VectorBatch* pBatchForDelete = NULL;
    ScalarVector* pValNotInPart = NULL;
    CStoreDelete* ordTabelDelete = NULL;
    ResultRelInfo* resultRelInfo = NULL;
    CStorePartitionInsert* partionInsert = NULL;

    // perpare for cstore scan
    if (exchangeVerbose) {
        // use all columns and ctid for scan key
        scanColNum = ordTableRel->rd_att->natts + 2;
        scanAttrNumbers = (AttrNumber*)palloc(sizeof(AttrNumber) * scanColNum);

        for (int i = 0; i < (scanColNum - 2); i++) {
            scanAttrNumbers[i] = attrs[i].attnum;
        }

        // ctid for delete
        scanAttrNumbers[scanColNum - 2] = SelfItemPointerAttributeNumber;
        scanAttrNumbers[scanColNum - 1] = TableOidAttributeNumber;

        // init cstore partition insert
        resultRelInfo = makeNode(ResultRelInfo);
        InitResultRelInfo(resultRelInfo, partTableRel, 1, 0);
        ExecOpenIndices(resultRelInfo, false);
        resultRelInfo->ri_junkFilter = makeNode(JunkFilter);
        resultRelInfo->ri_junkFilter->jf_junkAttNo = tididx;
        resultRelInfo->ri_junkFilter->jf_xc_part_id = tableoidIdx;
        partionInsert =
            New(CurrentMemoryContext) CStorePartitionInsert(partTableRel, resultRelInfo, TUPLE_SORT, false, NULL, NULL);

        // init  cstore delete
        estate = CreateExecutorState();
        ordTabelDelete = New(CurrentMemoryContext) CStoreDelete(ordTableRel, estate, false, NULL, NULL);
        ordTabelDelete->InitSortState();
    } else {
        // use parition key for scan key
        scanColNum = partkeyColumnNum;
        scanAttrNumbers = (AttrNumber*)palloc(sizeof(AttrNumber) * scanColNum);

        for (int i = 0; i < scanColNum; i++) {
            scanAttrNumbers[i] = partkeyColumns->values[i];
        }
    }

    // datum values for scan partition key values
    values = (Datum*)palloc(sizeof(Datum) * partkeyColumnNum);
    nulls = (bool*)palloc(sizeof(bool) * partkeyColumnNum);

    // scan columnar table
    scanstate = CStoreBeginScan(ordTableRel, scanColNum, scanAttrNumbers, SnapshotNow, true);

    if (exchangeVerbose) {
        // perpare move data which not in exchange partition
        pBatchNotInPart = New(CurrentMemoryContext) VectorBatch(CurrentMemoryContext, scanstate->m_pScanBatch);
        TupleDesc tupdesc = CreateTemplateTupleDesc(2, false);
        TupleDescInitEntry(tupdesc, (AttrNumber)1, "ctid", INT8OID, -1, 0);
        TupleDescInitEntry(tupdesc, (AttrNumber)2, "tableoid", INT8OID, -1, 0);
        pBatchForDelete = New(CurrentMemoryContext) VectorBatch(CurrentMemoryContext, tupdesc);
        pBatchNotInPart->CreateSysColContainer(CurrentMemoryContext, scanstate->ps.ps_ProjInfo->pi_sysAttrList);
    }

    do {
        vecScanBatch = CStoreGetNextBatch(scanstate);
        if (!BatchIsNull(vecScanBatch)) {
            for (int row = 0; row < vecScanBatch->m_rows; row++) {
                for (int partkeyIdx = 0; partkeyIdx < partkeyColumnNum; partkeyIdx++) {
                    // transform VectorBatch to partition key values
                    int col = partkeyColumns->values[partkeyIdx] - 1;
                    pVec = vecScanBatch->m_arr + col;
                    if (pVec->IsNull(row)) {
                        nulls[partkeyIdx] = true;
                        values[partkeyIdx] = (Datum)0;
                    } else {
                        nulls[partkeyIdx] = false;
                        pVals = pVec->m_vals;
                        if (pVec->m_desc.encoded == false)
                            values[partkeyIdx] = pVals[row];
                        else {
                            Assert(attrs[col].attlen < 0 || attrs[col].attlen > 8);
                            Datum v = ScalarVector::Decode(pVals[row]);
                            values[partkeyIdx] =
                                (attrs[col].attlen < 0) ? v : PointerGetDatum((char*)v + VARHDRSZ_SHORT);
                        }
                    }

                    partKeyValues[partkeyIdx] = transformDatum2Const(partTableRel->rd_att,
                        partkeyColumns->values[partkeyIdx],
                        values[partkeyIdx],
                        nulls[partkeyIdx],
                        &consts[partkeyIdx]);
                }

                // is this row in the exchange partition
                isInPart = isPartKeyValuesInPartition(partMap, partKeyValues, partkeyColumnNum, partSeq);

                if (!isInPart) {
                    if (!exchangeVerbose) {
                        ereport(ERROR,
                            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                errmsg("some rows in table do not qualify for specified partition")));
                    } else {
                        // all column
                        for (int col = 0; col < vecScanBatch->m_cols; col++) {
                            pVec = vecScanBatch->m_arr + col;
                            pValNotInPart = pBatchNotInPart->m_arr + col;

                            //  shallow copy
                            pValNotInPart->m_vals[countNotInPart] = pVec->m_vals[row];
                            pValNotInPart->m_flag[countNotInPart] = pVec->m_flag[row];
                            pValNotInPart->m_rows++;
                        }

                        // ctid
                        pVec = vecScanBatch->GetSysVector(SelfItemPointerAttributeNumber);
                        pValNotInPart = pBatchNotInPart->GetSysVector(SelfItemPointerAttributeNumber);
                        pValNotInPart->m_vals[countNotInPart] = pVec->m_vals[row];
                        pValNotInPart->m_flag[countNotInPart] = pVec->m_flag[row];
                        pValNotInPart->m_rows++;

                        // tableoid
                        pVec = vecScanBatch->GetSysVector(TableOidAttributeNumber);
                        pValNotInPart = pBatchNotInPart->GetSysVector(TableOidAttributeNumber);
                        pValNotInPart->m_vals[countNotInPart] = pVec->m_vals[row];
                        pValNotInPart->m_flag[countNotInPart] = pVec->m_flag[row];
                        pValNotInPart->m_rows++;

                        countNotInPart++;
                    }
                }
            }

            if (exchangeVerbose && (countNotInPart != 0)) {
                // routing to the right partition and insert
                Assert(countNotInPart <= vecScanBatch->m_rows);
                pBatchNotInPart->m_rows = countNotInPart;

                // insert to right partition
                partionInsert->BatchInsert(pBatchNotInPart, TABLE_INSERT_FROZEN);

                // delete from old table
                pBatchForDelete->m_rows = countNotInPart;
                pBatchForDelete->m_arr[tididx - 1].copy(pBatchNotInPart->GetSysVector(SelfItemPointerAttributeNumber));
                pBatchForDelete->m_arr[tableoidIdx - 1].copy(pBatchNotInPart->GetSysVector(TableOidAttributeNumber));
                ordTabelDelete->PutDeleteBatch(pBatchForDelete, resultRelInfo->ri_junkFilter);

                // reset batch and count
                pBatchNotInPart->Reset();
                pBatchForDelete->Reset();
                countNotInPart = 0;
            }
        }
    } while (!CStoreIsEndScan(scanstate));

    CStoreEndScan(scanstate);

    if (exchangeVerbose) {
        partionInsert->EndBatchInsert();
        DELETE_EX(partionInsert);

        (void)ordTabelDelete->ExecDelete();
        DELETE_EX(ordTabelDelete);

        ExecCloseIndices(resultRelInfo);
    }

    pfree_ext(scanAttrNumbers);
    pfree_ext(values);
    pfree_ext(nulls);
}

static Oid getPartitionElementsOid(const char* partitionElement)
{
    return *(Oid*)partitionElement;
}

int getPartitionElementsIndexByOid(Relation partTableRel, Oid partOid)
{
    int partSeq = -1;
    int partitionElementsNum = 0;
    char* partitionElements = NULL;
    void* partMap = NULL;
    int offset = 0;

    switch (partTableRel->partMap->type) {
        case PART_TYPE_LIST:
            partMap = (ListPartitionMap*)(partTableRel->partMap);
            partitionElementsNum = ((ListPartitionMap*)partMap)->listElementsNum;
            partitionElements = (char*)(((ListPartitionMap*)partMap)->listElements);
            offset = sizeof(ListPartElement);
            break;
        case PART_TYPE_HASH:
            partMap = (HashPartitionMap*)(partTableRel->partMap);
            partitionElementsNum = ((HashPartitionMap*)partMap)->hashElementsNum;
            partitionElements = (char*)(((HashPartitionMap*)partMap)->hashElements);
            offset = sizeof(HashPartElement);
            break;
        case PART_TYPE_RANGE:
        case PART_TYPE_INTERVAL:
            partMap = (RangePartitionMap*)(partTableRel->partMap);
            partitionElementsNum = ((RangePartitionMap*)partMap)->rangeElementsNum;
            partitionElements = (char*)(((RangePartitionMap*)partMap)->rangeElements);
            offset = sizeof(RangeElement);
            break;
        default:
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_OPERATION),
                    errmsg("The partitioned table is unsupported for getting the sequence of partition elements.")));
            break;
    }

    // the premise is that partitioned table was locked by AccessExclusiveLock
    for (int i = 0; i < partitionElementsNum; i++) {
        if (partOid == getPartitionElementsOid(partitionElements)) {
            partSeq = i;
            break;
        }
        partitionElements += offset;
    }

    return partSeq;
}

static void checkValidationForExchange(Relation partTableRel, Relation ordTableRel, Oid partOid, bool exchangeVerbose)
{
    Assert(partTableRel && partTableRel->partMap);

    int partSeq = getPartitionElementsIndexByOid(partTableRel, partOid);

    if (RelationIsColStore(ordTableRel)) {
        if (exchangeVerbose)
            checkValidationForExchangeCStore<true>(partTableRel, ordTableRel, partSeq);
        else
            checkValidationForExchangeCStore<false>(partTableRel, ordTableRel, partSeq);
    } else {
        if (exchangeVerbose)
            checkValidationForExchangeTable<true>(partTableRel, ordTableRel, partSeq);
        else
            checkValidationForExchangeTable<false>(partTableRel, ordTableRel, partSeq);
    }
}

// Description : Get partition oid by name or partition key values
static Oid getPartitionOid(Relation partTableRel, const char *partName, Node *PartDef)
{
    Oid partOid = InvalidOid;

    if (PointerIsValid(partName)) {
        partOid = PartitionNameGetPartitionOid(RelationGetRelid(partTableRel),
            partName,
            PART_OBJ_TYPE_TABLE_PARTITION,
            AccessExclusiveLock,
            true,
            false,
            NULL,
            NULL,
            NoLock);
    } else {
        List* boundary = GetPartitionBoundary(partTableRel, PartDef);
        partOid = PartitionValuesGetPartitionOid(
            partTableRel, boundary, AccessExclusiveLock, true, true, false);
    }

    return partOid;
}

// Description : Swap relfilenode of index
static void finishIndexSwap(List* partIndexList, List* ordIndexList)
{
    ListCell* cell1 = NULL;
    ListCell* cell2 = NULL;

    forboth(cell1, partIndexList, cell2, ordIndexList)
    {
        Oid partOid, ordOid;
        partOid = (Oid)lfirst_oid(cell1);
        ordOid = (Oid)lfirst_oid(cell2);

        finishPartitionHeapSwap(partOid, ordOid, true, u_sess->utils_cxt.RecentGlobalXmin, GetOldestMultiXactId());
    }
}

static void ATExecSplitPartition(Relation partTableRel, AlterTableCmd* cmd)
{
    SplitPartitionState* splitPart = NULL;
    List* destPartDefList = NIL;
    RangePartitionMap* partMap = NULL;
    Oid partTableOid = InvalidOid;
    Oid srcPartOid = InvalidOid;
    int srcPartIndex = -1;
    ListCell* cell = NULL;
    int currentPartNum;
    int targetPartNum;
    int partKeyNum = 0;
    List* newPartOidList = NIL;
    List* destPartBoundaryList = NIL;
    List* listForFree = NIL;

    Partition part = NULL;
    Oid tempTableOid = InvalidOid;
    Relation tempTableRel = NULL;
    ObjectAddress object;

    splitPart = (SplitPartitionState*)cmd->def;
    destPartDefList = splitPart->dest_partition_define_list;
    partMap = (RangePartitionMap*)partTableRel->partMap;
    partKeyNum = partMap->base.partitionKey->dim1;
    partTableOid = RelationGetRelid(partTableRel);

    // get src partition oid
    if (PointerIsValid(splitPart->src_partition_name)) {
        srcPartOid = PartitionNameGetPartitionOid(RelationGetRelid(partTableRel),
            splitPart->src_partition_name,
            PART_OBJ_TYPE_TABLE_PARTITION,
            AccessExclusiveLock,
            true,
            false,
            NULL,
            NULL,
            NoLock);
    } else {
        splitPart->partition_for_values = transformConstIntoTargetType(
            partTableRel->rd_att->attrs, partMap->base.partitionKey, splitPart->partition_for_values);
        srcPartOid = PartitionValuesGetPartitionOid(
            partTableRel, splitPart->partition_for_values, AccessExclusiveLock, true, true, false);
    }

    // check final partition num
    targetPartNum = getNumberOfPartitions(partTableRel) + list_length(destPartDefList) - 1;
    if (targetPartNum > MAX_PARTITION_NUM) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                errmsg("too many partitions for partitioned table"),
                errhint("Number of partitions can not be more than %d", MAX_PARTITION_NUM)));
    }

    /* check src partition exists */
    if (!OidIsValid(srcPartOid)) {
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_TABLE),
                splitPart->src_partition_name != NULL
                    ? errmsg("split partition \"%s\" does not exist.", splitPart->src_partition_name)
                    : errmsg("split partition does not exist.")));
    }

#ifdef ENABLE_HTAP
    if (RelHasImcs(srcPartOid)) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                errmsg("The part rel enable imcstore, please unimcstore if want to split it.")));
    }
#endif

    /* check local index 'usable' state */
    if (!checkRelationLocalIndexesUsable(partTableRel)) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                errmsg("can't split partition bacause relation %s has unusable local index",
                    NameStr(partTableRel->rd_rel->relname)),
                errhint("please reindex the unusable index first.")));
    }

    // check dest partitions name not existing
    checkDestPartitionNameForSplit(partTableOid, destPartDefList);

    // check dest partition tablespace
    foreach (cell, destPartDefList) {
        RangePartitionDefState* rangePartDef = (RangePartitionDefState*)lfirst(cell);

        CheckPartitionTablespace(rangePartDef->tablespacename, partTableRel->rd_rel->relowner);
    }

    // get src partition sequence
    srcPartIndex = partOidGetPartSequence(partTableRel, srcPartOid) - 1;
    if (srcPartIndex < 0) {
        Assert(false);
        ereport(ERROR,
            (errcode(ERRCODE_NO_DATA_FOUND),
                errmsg("the partition oid(%u) of partition name (%s) is not found in partitioned table(%u).",
                    srcPartOid,
                    splitPart->src_partition_name ? splitPart->src_partition_name : "NULL",
                    partTableRel->rd_id)));
    }
    bool isPrevInterval = srcPartIndex > 0 && partMap->rangeElements[srcPartIndex - 1].isInterval;
    // if split point
    if (PointerIsValid(splitPart->split_point)) {
        RangePartitionDefState* rangePartDef = NULL;

        // check split point value
        checkSplitPointForSplit(splitPart, partTableRel, srcPartIndex);

        Assert(list_length(destPartDefList) == 2);

        // set the two dest partitions boundary
        rangePartDef = (RangePartitionDefState*)list_nth(destPartDefList, 0);
        rangePartDef->boundary = splitPart->split_point;

        /*
         * generate boundary for the second partititon
         */
        rangePartDef = (RangePartitionDefState*)list_nth(destPartDefList, 1);
        rangePartDef->boundary = getRangePartitionBoundaryList(partTableRel, srcPartIndex);
    } else {
        // not split point
        int compare = 0;
        ListCell* otherCell = NULL;

        if (list_length(destPartDefList) < 2) {
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_OPERATION),
                    errmsg("the number of resulting partitions must be more than one")));
        }

        // transform partition key type
        destPartBoundaryList = getDestPartBoundaryList(partTableRel, destPartDefList, &listForFree);

        // check the first dest partition boundary
        if (srcPartIndex != 0) {
            if (!partMap->rangeElements[srcPartIndex].isInterval) {
                compare = comparePartitionKey(partMap, (Const**)lfirst(list_head(destPartBoundaryList)),
                    partMap->rangeElements[srcPartIndex - 1].boundary, partKeyNum);
            } else {
                Const** partKeyValue = (Const**)lfirst(list_head(destPartBoundaryList));
                RangeElement& srcPartition = partMap->rangeElements[srcPartIndex];
                compare = ValueCmpLowBoudary(partKeyValue, &srcPartition, partMap->intervalValue);
            }
            if (compare <= 0) {
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_OPERATION),
                        errmsg("the bound of the first resulting partition is too low")));
            }
        }

        // check the dest partitions boundary
        forboth(cell, destPartBoundaryList, otherCell, destPartDefList)
        {
            Const** currentBoundary = (Const**)lfirst(cell);
            RangePartitionDefState* rangePartDef = (RangePartitionDefState*)lfirst(otherCell);
            Const** nextBoudary = NULL;

            if (!PointerIsValid(cell->next)) {
                break;
            }

            nextBoudary = (Const**)lfirst(cell->next);
            rangePartDef = (RangePartitionDefState*)lfirst(otherCell->next);

            compare = comparePartitionKey(partMap, currentBoundary, nextBoudary, partKeyNum);

            if (compare >= 0) {
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_OPERATION),
                        errmsg("the bound of resulting partition \"%s\" is too low", rangePartDef->partitionName)));
            }
        }

        // check the last dest partition boundary equal the src partition boundary
        compare = comparePartitionKey(partMap,
            (Const**)lfirst(list_tail(destPartBoundaryList)),
            partMap->rangeElements[srcPartIndex].boundary,
            partKeyNum);
        if (compare != 0) {
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_OPERATION),
                    errmsg("the bound of the last resulting partition is not equal with specified partition bound")));
        }
    }

    // add dest partitions
    int partitionno = -GetCurrentPartitionNo(RelOidGetPartitionTupleid(partTableRel->rd_id));
    Assert(PARTITIONNO_IS_VALID(partitionno));
    foreach (cell, destPartDefList) {
        partitionno++;
        PartitionDefState* partDef = (PartitionDefState*)lfirst(cell);
        partDef->partitionno = partitionno;
    }
    fastAddPartition(partTableRel, destPartDefList, &newPartOidList);
    /* inplace update on partitioned table, because we can't cover the wait_clean_gpi info, which is inplace updated */
    UpdateCurrentPartitionNo(RelOidGetPartitionTupleid(partTableRel->rd_id), -partitionno, true);

    freeDestPartBoundaryList(destPartBoundaryList, listForFree);
    if (isPrevInterval) {
        // modify all previous *interval* partitions to range partitions, *possibly* no such partitions
        Relation pgPartition = relation_open(PartitionRelationId, RowExclusiveLock);
        UpdatePrevIntervalPartToRange(partTableRel, pgPartition, srcPartIndex, "SPLIT PARTITION");
        relation_close(pgPartition, NoLock);
    }
#ifdef PGXC
    if (IS_PGXC_DATANODE) {
#endif
        part = partitionOpen(partTableRel, srcPartOid, AccessExclusiveLock);

        // creat temp table and swap relfilenode with src partition
        tempTableOid = createTempTableForPartition(partTableRel, part);
        finishPartitionHeapSwap(srcPartOid, tempTableOid, false, u_sess->utils_cxt.RecentXmin, GetOldestMultiXactId());

        CommandCounterIncrement();

        partitionClose(partTableRel, part, NoLock);
#ifdef PGXC
    }
#endif

    AlterPartitionedSetWaitCleanGPI(cmd->alterGPI, partTableRel, srcPartOid);
    // drop src partition
    fastDropPartition(partTableRel, srcPartOid, "SPLIT PARTITION");
    currentPartNum = getNumberOfPartitions(partTableRel);
    if (currentPartNum != targetPartNum) {
        ereport(ERROR,
                (errmsg("An error occurred in the split partition process. "
                        "The number of target partitions is %d, but the number of current partitions is %d.",
                        targetPartNum, currentPartNum)));
    }

#ifdef PGXC
    if (IS_PGXC_DATANODE) {
#endif
        tempTableRel = relation_open(tempTableOid, AccessExclusiveLock);

        // read temp table tuples and insert into partitioned table
        readTuplesAndInsert(tempTableRel, partTableRel);

        relation_close(tempTableRel, NoLock);

        // delete temp table
        object.classId = RelationRelationId;
        object.objectId = tempTableOid;
        object.objectSubId = 0;
        performDeletion(&object, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);

        // reindex dest partitions
        foreach (cell, newPartOidList) {
            Oid partOid = lfirst_oid(cell);

            reindexPartition(partTableOid, partOid, REINDEX_REL_SUPPRESS_INDEX_USE, REINDEX_ALL_INDEX);

            if (cmd->alterGPI) {
                AddGPIForPartition(partTableOid, partOid);
            }
        }
#ifdef PGXC
    }
#endif

    list_free_ext(newPartOidList);

    if (!cmd->alterGPI) {
        // Unusable Global Index
        ATUnusableGlobalIndex(partTableRel);
    }
}
#ifdef USE_SPQ
void spq_btbuild_update_pg_class(Relation heap, Relation index)
{
    List *options = NIL;
    DefElem *opt;
    opt = makeNode(DefElem);
    opt->type = T_DefElem;
    opt->defnamespace = NULL;
    opt->defname = "spq_build";
    opt->defaction = DEFELEM_SET;
    opt->arg = (Node *)makeString("finish");
    options = lappend(options, opt);
    ATExecSetRelOptions(index, options, AT_SetRelOptions, ShareUpdateExclusiveLock);
}
#endif

void CheckSrcListSubPartitionForSplit(Relation rel, Oid partOid, Oid subPartOid)
{
    Partition part = partitionOpen(rel, partOid, NoLock);
    Relation partRel = partitionGetRelation(rel, part);
    Partition subPart = partitionOpen(partRel, subPartOid, NoLock);
    if (subPart->pd_part->partstrategy == PART_STRATEGY_HASH) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION), errmsg("Hash subpartition does not support split."),
                        errdetail("N/A"), errcause("Hash subpartition does not support split."),
                        erraction("Use the correct split action")));
    }
    if (subPart->pd_part->partstrategy != PART_STRATEGY_LIST) {
        ereport(
            ERROR,
            (errcode(ERRCODE_INVALID_OPERATION), errmsg("The syntax format of split subpartition is incorrect."),
             errdetail("SPLIT SUBPARTITION NAME VALUES shouldn't be used, it's for list subpartitions."),
             errcause("Wrong split subpartition syntax used."), erraction("Use proper split subpartition syntax.")));
    }
    partitionClose(partRel, subPart, NoLock);

    int srcSubPartIndex = partOidGetPartSequence(partRel, subPartOid) - 1;
    List* boundary = getListPartitionBoundaryList(partRel, srcSubPartIndex);
    if (!(list_length(boundary) == 1 && ((Const*)list_nth(boundary, 0))->ismaxvalue)) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_OPERATION),
                errmsg("Only the default boundary subpartition can be splited.")));
    }

    releaseDummyRelation(&partRel);
    partitionClose(rel, part, NoLock);
    list_free_deep(boundary);
}

void checkDestListSubPartitionNameForSplit(Relation rel, List* destPartDefList, Oid srcSubPartOid)
{
    Assert(list_length(destPartDefList) == 2);

    char *srcSubPartName = getPartitionName(srcSubPartOid, false);
    char *destSubPartName1 = ((ListPartitionDefState*)list_nth(destPartDefList, 0))->partitionName;
    char *destSubPartName2 = ((ListPartitionDefState*)list_nth(destPartDefList, 1))->partitionName;
    ListCell* lc = NULL;


    // check dest subpartitions name with self
    if (!strcmp(destSubPartName1, destSubPartName2)) {
        ereport(ERROR,
                    (errcode(ERRCODE_DUPLICATE_OBJECT),
                         errmsg("The dest subpartitions has the same name.")));
    }
    /* check dest partitions name with existing partitions name
     * We allow the dest subpartition to have the same name as the src subpartition.
     * Because we're going to delete the src subpartition.
     */
    List *partOidList = relationGetPartitionOidList(rel);
    foreach (lc, destPartDefList) {
        char *destSubPartName = ((ListPartitionDefState*)lfirst(lc))->partitionName;
        if (!strcmp(destSubPartName, srcSubPartName)) {
            continue;
        }
        ListCell *cell = NULL;
        foreach (cell, partOidList) {
            Oid partOid = lfirst_oid(cell);
            if (InvalidOid != GetSysCacheOid3(PARTPARTOID, NameGetDatum(destSubPartName),
                                              CharGetDatum(PART_OBJ_TYPE_TABLE_SUB_PARTITION),
                                              ObjectIdGetDatum(partOid))) {
                ereport(ERROR,
                        (errcode(ERRCODE_DUPLICATE_OBJECT),
                         errmsg("resulting subpartition \"%s\" name conflicts with an existing subpartition",
                                destSubPartName)));
            }
        }
        if (InvalidOid != GetSysCacheOid3(PARTPARTOID, NameGetDatum(destSubPartName),
                                            CharGetDatum(PART_OBJ_TYPE_TABLE_PARTITION),
                                            ObjectIdGetDatum(RelationGetRelid(rel)))) {
            ereport(ERROR,
                    (errcode(ERRCODE_DUPLICATE_OBJECT),
                        errmsg("resulting subpartition \"%s\" name conflicts with an existing partition",
                            destSubPartName)));
        }
    }
}

static void CheckDestRangeSubPartitionNameForSplit(Relation rel, List* destPartDefList)
{
    Assert(list_length(destPartDefList) == 2);

    ListCell* lc = NULL;

    // check dest subpartitions name with self
    checkPartitionName(destPartDefList);

    // check dest subpartitions name with existing subpartitions name
    List *partOidList = relationGetPartitionOidList(rel);
    foreach (lc, destPartDefList) {
        char *destSubPartName = ((ListPartitionDefState*)lfirst(lc))->partitionName;

        ListCell *cell = NULL;
        foreach (cell, partOidList) {
            Oid partOid = lfirst_oid(cell);
            if (InvalidOid != GetSysCacheOid3(PARTPARTOID, NameGetDatum(destSubPartName),
                                              CharGetDatum(PART_OBJ_TYPE_TABLE_SUB_PARTITION),
                                              ObjectIdGetDatum(partOid))) {
                ereport(ERROR,
                        (errcode(ERRCODE_DUPLICATE_OBJECT),
                         errmsg("resulting subpartition \"%s\" name conflicts with an existing subpartition",
                                destSubPartName)));
            }
        }
        if (InvalidOid != GetSysCacheOid3(PARTPARTOID, NameGetDatum(destSubPartName),
                                            CharGetDatum(PART_OBJ_TYPE_TABLE_PARTITION),
                                            ObjectIdGetDatum(RelationGetRelid(rel)))) {
            ereport(ERROR,
                    (errcode(ERRCODE_DUPLICATE_OBJECT),
                        errmsg("resulting subpartition \"%s\" name conflicts with an existing partition",
                            destSubPartName)));
        }
    }
}

static void ChecksrcRangeSubPartitionNameForSplit(Relation rel, Oid partOid, Oid subPartOid)
{
    Partition part = partitionOpen(rel, partOid, NoLock);
    Relation partRel = partitionGetRelation(rel, part);
    Partition subPart = partitionOpen(partRel, subPartOid, NoLock);
    if (subPart->pd_part->partstrategy == PART_STRATEGY_HASH) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION), errmsg("Hash subpartition does not support split."),
                        errdetail("N/A"), errcause("Hash subpartition does not support split."),
                        erraction("Use the correct split action")));
    }
    if (subPart->pd_part->partstrategy != PART_STRATEGY_RANGE) {
        ereport(
            ERROR,
            (errcode(ERRCODE_INVALID_OPERATION), errmsg("The syntax format of split subpartition is incorrect."),
             errdetail("SPLIT SUBPARTITION NAME AT shouldn't be used, it's for range subpartitions."),
             errcause("Wrong split subpartition syntax used."), erraction("Use proper split subpartition syntax.")));
    }
    partitionClose(partRel, subPart, NoLock);
    releaseDummyRelation(&partRel);
    partitionClose(rel, part, NoLock);
}

void CheckDestListSubPartitionBoundaryForSplit(Relation rel, Oid partOid, SplitPartitionState* splitSubPart)
{
    ListCell *cell = NULL;
    Oid existingSubPartOid;
    Oid defaultSubPartOid = InvalidOid;
    Partition part = partitionOpen(rel, partOid, NoLock);
    Relation partRel = partitionGetRelation(rel, part);
    ListPartitionMap* partMap = (ListPartitionMap*)partRel->partMap;
    ParseState* pstate = make_parsestate(NULL);

    splitSubPart->newListSubPartitionBoundry =
        transformListPartitionValue(pstate, splitSubPart->newListSubPartitionBoundry, true, true);
    pfree_ext(pstate);

    for (int i = 0; i < partMap->listElementsNum; i++) {
        ListPartElement *list = &partMap->listElements[i];
        if (list->boundary[0].values[0]->ismaxvalue) {
            defaultSubPartOid = list->partitionOid;
            break;
        }
    }

    bool partkeyIsFunc = IsPartKeyFunc(partRel, true, true);
    foreach (cell, splitSubPart->newListSubPartitionBoundry) {
        existingSubPartOid = FindPartOidByListBoundary(partRel, partMap, (Node*)lfirst(cell), partkeyIsFunc);
        if (OidIsValid(existingSubPartOid) && existingSubPartOid != defaultSubPartOid) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                errmsg("list subpartition %s has overlapped value", getPartitionName(existingSubPartOid, false))));
        }
    }

    releaseDummyRelation(&partRel);
    partitionClose(rel, part, NoLock);
}

int GetNumberOfSubPartitions(Relation rel)
{
    int subPartitionNum = 0;
    List *partOidList = relationGetPartitionOidList(rel);
    ListCell *cell = NULL;
    foreach (cell, partOidList) {
        Oid partOid = lfirst_oid(cell);
        Partition part = partitionOpen(rel, partOid, NoLock);
        Relation partRel = partitionGetRelation(rel, part);
        subPartitionNum += GetNumberOfSubPartitions(partRel);
        releaseDummyRelation(&partRel);
        partitionClose(rel, part, NoLock);
    }
    return subPartitionNum;
}
static void ATExecSplitSubPartition(Relation partTableRel, AlterTableCmd* cmd)
{
    SplitPartitionState* splitSubPart = NULL;
    ListCell* cell = NULL;
    List* destPartDefList = NIL;
    List* newSubPartOidList = NIL;
    Oid tempTableOid = InvalidOid;
    int currentPartNum = 0;
    Oid partOid = InvalidOid;
    Oid srcSubPartOid = InvalidOid;
    int subpartitionno = INVALID_PARTITION_NO;

    splitSubPart = (SplitPartitionState*)cmd->def;
    destPartDefList = splitSubPart->dest_partition_define_list;

    // check final partition num
    currentPartNum = GetNumberOfSubPartitions(partTableRel);
    if ((currentPartNum + list_length(destPartDefList) - 1) > MAX_PARTITION_NUM) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                errmsg("too many subpartitions for partitioned table"),
                errhint("Number of subpartitions can not be more than %d", MAX_PARTITION_NUM)));
    }
    if (splitSubPart->splitType == LISTSUBPARTITIION) {
        srcSubPartOid = SubPartitionNameGetSubPartitionOid(RelationGetRelid(partTableRel),
            splitSubPart->src_partition_name,
            ShareUpdateExclusiveLock, /* partition lock */
            AccessExclusiveLock,      /* subpartition lock */
            true,
            false,
            NULL,
            NULL,
            NoLock,
            &partOid);
        /* check src subpartition exists */
        if (!OidIsValid(srcSubPartOid)) {
            ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE),
                            splitSubPart->src_partition_name != NULL
                                ? errmsg("split subpartition \"%s\" does not exist.", splitSubPart->src_partition_name)
                                : errmsg("split subpartition does not exist.")));
        }

#ifdef ENABLE_HTAP
        if (RelHasImcs(srcSubPartOid)) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                    errmsg("The subpart rel enable imcstore, please unimcstore if want to split it.")));
        }
#endif

        // check src subpartition is default subpartition
        CheckSrcListSubPartitionForSplit(partTableRel, partOid, srcSubPartOid);
        // check dest subpartitions name not existing
        checkDestListSubPartitionNameForSplit(partTableRel, destPartDefList, srcSubPartOid);
        // check new boundry not existing
        CheckDestListSubPartitionBoundaryForSplit(partTableRel, partOid, splitSubPart);

        /* check local index 'usable' state */
        if (!checkRelationLocalIndexesUsable(partTableRel)) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                            errmsg("can't split subpartition bacause relation %s has unusable local index",
                                   NameStr(partTableRel->rd_rel->relname)),
                            errhint("please reindex the unusable index first.")));
        }

        // check dest partition tablespace
        subpartitionno = -GetCurrentSubPartitionNo(partOid);
        Assert(PARTITIONNO_IS_VALID(subpartitionno));
        foreach (cell, destPartDefList) {
            ListPartitionDefState *listSubPartDef = (ListPartitionDefState *)lfirst(cell);
            subpartitionno++;
            listSubPartDef->partitionno = subpartitionno;
            CheckPartitionTablespace(listSubPartDef->tablespacename, partTableRel->rd_rel->relowner);
        }

        ListPartitionDefState* listPartDef = NULL;
        // set the two dest subpartitions boundary
        listPartDef = (ListPartitionDefState*)list_nth(destPartDefList, 0);
        listPartDef->boundary = splitSubPart->newListSubPartitionBoundry;

        /*
         * generate boundary for the second partititon
         */
        listPartDef = (ListPartitionDefState*)list_nth(destPartDefList, 1);
        Const *boundaryDefault = makeNode(Const);
        boundaryDefault->ismaxvalue = true;
        boundaryDefault->location = -1;
        listPartDef->boundary = list_make1(boundaryDefault);
    } else if (splitSubPart->splitType == RANGESUBPARTITIION) {
        srcSubPartOid = SubPartitionNameGetSubPartitionOid(RelationGetRelid(partTableRel),
            splitSubPart->src_partition_name,
            ShareUpdateExclusiveLock, /* partition lock */
            AccessExclusiveLock,      /* subpartition lock */
            true,
            false,
            NULL,
            NULL,
            NoLock,
            &partOid);
        /* check src subpartition exists */
        if (!OidIsValid(srcSubPartOid)) {
            ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE),
                            splitSubPart->src_partition_name != NULL
                                ? errmsg("split subpartition \"%s\" does not exist.", splitSubPart->src_partition_name)
                                : errmsg("split subpartition does not exist.")));
        }

#ifdef ENABLE_HTAP
        if (RelHasImcs(srcSubPartOid)) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                    errmsg("The subpart rel enable imcstore, please unimcstore if want to split it.")));
        }
#endif

        // check src subpartitions
        ChecksrcRangeSubPartitionNameForSplit(partTableRel, partOid, srcSubPartOid);
        // check dest subpartitions name not existing
        CheckDestRangeSubPartitionNameForSplit(partTableRel, destPartDefList);
        /* check local index 'usable' state */
        if (!checkRelationLocalIndexesUsable(partTableRel)) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                            errmsg("can't split subpartition bacause relation %s has unusable local index",
                                   NameStr(partTableRel->rd_rel->relname)),
                            errhint("please reindex the unusable index first.")));
        }

        // check dest partition tablespace
        subpartitionno = -GetCurrentSubPartitionNo(partOid);
        Assert(PARTITIONNO_IS_VALID(subpartitionno));
        foreach (cell, destPartDefList) {
            RangePartitionDefState *listSubPartDef = (RangePartitionDefState *)lfirst(cell);
            subpartitionno++;
            listSubPartDef->partitionno = subpartitionno;
            CheckPartitionTablespace(listSubPartDef->tablespacename, partTableRel->rd_rel->relowner);
        }
        Partition part = partitionOpen(partTableRel, partOid, NoLock);
        Relation partRel = partitionGetRelation(partTableRel, part);

        // get src partition sequence
        int srcSubPartIndex = partOidGetPartSequence(partRel, srcSubPartOid) - 1;
        if (srcSubPartIndex < 0) {
            Assert(false);
            ereport(ERROR,
                    (errcode(ERRCODE_NO_DATA_FOUND),
                     errmsg("the subpartition oid(%u) of subpartition name (%s) is not found in partitioned table(%u).",
                            srcSubPartOid, splitSubPart->src_partition_name ? splitSubPart->src_partition_name : "NULL",
                            partTableRel->rd_id)));
        }

        if (PointerIsValid(splitSubPart->split_point)) {
            RangePartitionDefState *rangePartDef = NULL;

            // check split point value
            checkSplitPointForSplit(splitSubPart, partRel, srcSubPartIndex);

            Assert(list_length(destPartDefList) == 2);

            // set the two dest partitions boundary
            rangePartDef = (RangePartitionDefState *)list_nth(destPartDefList, 0);
            rangePartDef->boundary = splitSubPart->split_point;

            /*
             * generate boundary for the second partititon
             */
            rangePartDef = (RangePartitionDefState *)list_nth(destPartDefList, 1);
            rangePartDef->boundary = getRangePartitionBoundaryList(partRel, srcSubPartIndex);
        }

        // add dest partitions
        FastAddRangeSubPartition(partTableRel, destPartDefList, partOid, &newSubPartOidList);

        releaseDummyRelation(&partRel);
        partitionClose(partTableRel, part, NoLock);
    }
    Partition part = partitionOpen(partTableRel, partOid, NoLock);
    Relation partRel = partitionGetRelation(partTableRel, part);
    Partition subPart = partitionOpen(partRel, srcSubPartOid, NoLock);

    // creat temp table and swap relfilenode with src partition
    tempTableOid = createTempTableForPartition(partTableRel, subPart);
    finishPartitionHeapSwap(srcSubPartOid, tempTableOid, false, u_sess->utils_cxt.RecentXmin, GetOldestMultiXactId());

    CommandCounterIncrement();

    partitionClose(partRel, subPart, NoLock);

    AlterSubPartitionedSetWaitCleanGPI(cmd->alterGPI, partTableRel, partOid, srcSubPartOid);

    if (cmd->alterGPI) {
        DeleteGPITuplesForSubPartition(RelationGetRelid(partTableRel), partOid, srcSubPartOid);
    }

    // drop src partition
    fastDropPartition(partRel, srcSubPartOid, "SPLIT SUBPARTITION", InvalidOid, false);
    CacheInvalidatePartcache(part);

    releaseDummyRelation(&partRel);
    partitionClose(partTableRel, part, NoLock);

    if (splitSubPart->splitType == LISTSUBPARTITIION) {
        /* 
         * add dest subpartitions. For list subpartition We allow the dest subpartition name
         * to be the same as the src subpartition name. So we need delete src subpartition
         * and then add the dest subpartition name.
         */
        FastAddListSubPartition(partTableRel, destPartDefList, partOid, &newSubPartOidList);
    }

    UpdateCurrentSubPartitionNo(partOid, -subpartitionno);

    Relation tempTableRel = relation_open(tempTableOid, AccessExclusiveLock);

    // read temp table tuples and insert into partitioned table
    readTuplesAndInsert(tempTableRel, partTableRel);

    relation_close(tempTableRel, NoLock);

    // delete temp table
    ObjectAddress object;
    object.classId = RelationRelationId;
    object.objectId = tempTableOid;
    object.objectSubId = 0;
    performDeletion(&object, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);

    // reindex dest partitions
    foreach (cell, newSubPartOidList) {
        Oid subPartOid = lfirst_oid(cell);

        reindexPartition(RelationGetRelid(partTableRel), subPartOid, REINDEX_REL_SUPPRESS_INDEX_USE, REINDEX_ALL_INDEX);
        if (cmd->alterGPI) {
            AddGPIForSubPartition(RelationGetRelid(partTableRel), partOid, subPartOid);
        }
    }
    list_free_ext(newSubPartOidList);

    if (!cmd->alterGPI) {
        // Unusable Global Index
        ATUnusableGlobalIndex(partTableRel);
    }
}

// check split point
static void checkSplitPointForSplit(SplitPartitionState* splitPart, Relation partTableRel, int srcPartIndex)
{
    RangePartitionMap* partMap = NULL;
    ParseState* pstate = NULL;
    ListCell* cell = NULL;
    Const* partKeyValueArr[MAX_RANGE_PARTKEY_NUMS] = {NULL};
    int i = 0;
    int partKeyNum = 0;
    int compareSrcPart = 0;

    // get partition key number
    partMap = (RangePartitionMap*)partTableRel->partMap;
    partKeyNum = partMap->base.partitionKey->dim1;

    // check split point length
    if (partKeyNum != list_length(splitPart->split_point)) {
        if (RelationIsPartitionOfSubPartitionTable(partTableRel)) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                            (errmsg("Number of boundary items NOT EQUAL to number of partition keys"),
                             errdetail("There can only be one boundary value for split range subpartitions"),
                             errcause("N/A"), erraction("Check whether the SQL statements. is correct."))));
        } else {
            ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                            (errmsg("number of boundary items NOT EQUAL to number of partition keys"), errdetail("N/A"),
                             errcause("N/A"), erraction("Check whether the SQL statements. is correct."))));
        }
    }

    pstate = make_parsestate(NULL);
    splitPart->split_point = transformRangePartitionValueInternal(pstate, splitPart->split_point, true, true);
    pfree_ext(pstate);

    bool partkeyIsFunc = IsPartKeyFunc(partTableRel, false, partTableRel->parentId != InvalidOid);
    List *tmp = splitPart->split_point;
    splitPart->split_point = transformConstIntoTargetType(partTableRel->rd_att->attrs, partMap->base.partitionKey,
                                                          splitPart->split_point, partkeyIsFunc);
    list_free_ext(tmp);

    foreach (cell, splitPart->split_point) {
        partKeyValueArr[i++] = (Const*)lfirst(cell);
    }

    // compare split point with src partition
    compareSrcPart =
        comparePartitionKey(partMap, partKeyValueArr, partMap->rangeElements[srcPartIndex].boundary, partKeyNum);
    if (compareSrcPart >= 0) {
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("split point is too high")));
    }
    bool lowerOverflow = false;
    if (partMap->rangeElements[srcPartIndex].isInterval) {
        // compare split point with the lower boundary of src partition
        RangeElement& srcPart = partMap->rangeElements[srcPartIndex];
        Interval *interval = partMap->intervalValue;
        lowerOverflow = !ValueSatisfyLowBoudary(&partKeyValueArr[0], &srcPart, interval, false);
    } else {
        // compare split point with the previous partition of src partition
        if (srcPartIndex != 0) {
            lowerOverflow = comparePartitionKey(
                partMap, partKeyValueArr, partMap->rangeElements[srcPartIndex - 1].boundary, partKeyNum) <= 0;
        }
    }
    if (lowerOverflow) {
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("split point is too low")));
    }
}

static void checkDestPartitionNameForSplit(Oid partTableOid, List* partDefList)
{
    ListCell* cell = NULL;

    // check dest partitions name with self
    checkPartitionName(partDefList);

    // check dest partitions name with existing partitions name
    foreach (cell, partDefList) {
        RangePartitionDefState* rangePartDef = (RangePartitionDefState*)lfirst(cell);

        if (InvalidOid != GetSysCacheOid3(PARTPARTOID,
                              NameGetDatum(rangePartDef->partitionName),
                              CharGetDatum(PART_OBJ_TYPE_TABLE_PARTITION),
                              ObjectIdGetDatum(partTableOid))) {
            ereport(ERROR,
                (errcode(ERRCODE_DUPLICATE_OBJECT),
                    errmsg("resulting partition \"%s\" name conflicts with that of an existing partition",
                        rangePartDef->partitionName)));
        }
    }
}

static List* getDestPartBoundaryList(Relation partTableRel, List* destPartDefList, List** listForFree)
{
    ListCell* cell = NULL;
    List* result = NIL;
    bool partkeyIsFunc = IsPartKeyFunc(partTableRel, false, false);
    foreach (cell, destPartDefList) {
        RangePartitionDefState* rangePartDef = (RangePartitionDefState*)lfirst(cell);
        List* partKeyValueList = NIL;
        ListCell* otherCell = NULL;
        Const** partKeyValueArr = (Const**)palloc0(sizeof(Const*) * MAX_RANGE_PARTKEY_NUMS);
        int i = 0;

        partKeyValueList = transformConstIntoTargetType(partTableRel->rd_att->attrs,
            ((RangePartitionMap*)partTableRel->partMap)->base.partitionKey,
            rangePartDef->boundary, partkeyIsFunc);

        foreach (otherCell, partKeyValueList) {
            partKeyValueArr[i++] = (Const*)lfirst(otherCell);
        }

        result = lappend(result, partKeyValueArr);
        *listForFree = lappend(*listForFree, partKeyValueList);
    }

    return result;
}

static void freeDestPartBoundaryList(List* list1, List* list2)
{
    ListCell* cell = NULL;

    foreach (cell, list1) {
        Const** partKeyValues = (Const**)lfirst(cell);

        pfree_ext(partKeyValues);
    }

    list_free_ext(list1);

    foreach (cell, list2) {
        List* partKeyList = (List*)lfirst(cell);

        list_free_deep(partKeyList);
    }

    list_free_ext(list2);
}

/*
 * Description: Generates a partition's name for the temporary partition that may be created during merge partitions.
 *
 * Parameters:
 * 	@in partTableRel: partition table relation.
 * 	@in sequence: current partition sequence, default 0.
 *
 * Returns:
 * 	sample partition name. This must be pfree'd by the caller.
 */
static char* GenTemporaryPartitionName(Relation partTableRel, int sequence)
{
    char tmpName[NAMEDATALEN];
    errno_t ret;

    ret = snprintf_s(tmpName,
        NAMEDATALEN,
        NAMEDATALEN - 1,
        "pg_partition_%u_%d_%ld",
        partTableRel->rd_id,
        sequence,
        GetCurrentTimestamp());
    securec_check_ss_c(ret, "\0", "\0");

    return pstrdup(tmpName);
}

#ifndef ENABLE_MULTIPLE_NODES
static Oid GetNewPartitionOid(Relation pgPartRel, Relation partTableRel, Node *partDef,
    Oid bucketOid, bool *isTimestamptz, StorageType stype, Datum new_reloptions, bool isSubpartition)
{
#else
static Oid GetNewPartitionOid(Relation pgPartRel, Relation partTableRel, Node *partDef,
    Oid bucketOid, bool *isTimestamptz, StorageType stype, bool isSubpartition)
{
    Datum new_reloptions = (Datum)0;
#endif
    Oid newPartOid = InvalidOid;
    switch (nodeTag(partDef)) {
        case T_RangePartitionDefState:
            newPartOid = heapAddRangePartition(pgPartRel,
                partTableRel->rd_id,
                partTableRel->rd_rel->reltablespace,
                bucketOid,
                (RangePartitionDefState *)partDef,
                partTableRel->rd_rel->relowner,
                new_reloptions,
                isTimestamptz,
                stype,
                AccessExclusiveLock,
                NULL,
                isSubpartition);
            break;
        case T_ListPartitionDefState:
            newPartOid = HeapAddListPartition(pgPartRel,
                partTableRel->rd_id,
                partTableRel->rd_rel->reltablespace,
                bucketOid,
                (ListPartitionDefState *)partDef,
                partTableRel->rd_rel->relowner,
                new_reloptions,
                isTimestamptz,
                stype,
                NULL,
                isSubpartition);
            break;
        case T_HashPartitionDefState:
            newPartOid = HeapAddHashPartition(pgPartRel,
                partTableRel->rd_id,
                partTableRel->rd_rel->reltablespace,
                bucketOid,
                (HashPartitionDefState *)partDef,
                partTableRel->rd_rel->relowner,
                new_reloptions,
                isTimestamptz,
                stype,
                NULL,
                isSubpartition);
            break;
        default:
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_OPERATION),
                    errmsg("The partitioned table is unsupported for adding new partition when data is exchanged.")));
            break;
    }

    return newPartOid;
}

/*
 * Description: Add a temporary partition during merge partitions.
 *
 * Parameters:
 * 	@in partTableRel: partition table relation
 * 	@in partDef: partition description.
 *
 * Returns:
 * 	temporary partition oid.
 */
static Oid AddTemporaryPartition(Relation partTableRel, Node* partDef)
{
    Relation pgPartRel = NULL;
    HeapTuple tuple = NULL;
    Oid newPartOid = InvalidOid;
    Oid bucketOid = InvalidOid;
    bool isnull = false;
    Datum rel_reloptions;
    Datum new_reloptions;
    List* old_reloptions = NIL;
    bool isPartitionOfSubPartition = RelationIsPartitionOfSubPartitionTable(partTableRel);

    bool* isTimestamptz = CheckPartkeyHasTimestampwithzone(partTableRel);
    bucketOid = RelationGetBucketOid(partTableRel);
    pgPartRel = relation_open(PartitionRelationId, RowExclusiveLock);

    /* add new partition entry in pg_partition */
    Oid relOid = isPartitionOfSubPartition ?
                 ObjectIdGetDatum(partTableRel->parentId) : ObjectIdGetDatum(partTableRel->rd_id);
    tuple = SearchSysCache1(RELOID, relOid);
    if (!HeapTupleIsValid(tuple)) {
        ereport(ERROR,
                (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                 (errmsg("cache lookup failed"), errdetail("cache lookup failed for relation %u", relOid),
                  errcause("The oid of the target relation is invalid."),
                  erraction("Check whether the target relation is correct."))));
    }
    rel_reloptions = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, &isnull);

    old_reloptions = untransformRelOptions(rel_reloptions);
    RemoveRedisRelOptionsFromList(&old_reloptions);
    new_reloptions = transformRelOptions((Datum)0, old_reloptions, NULL, NULL, false, false);
    ReleaseSysCache(tuple);

    if (old_reloptions != NIL) {
        list_free_ext(old_reloptions);
    }

    /* Temporary tables do not use segment-page */
#ifndef ENABLE_MULTIPLE_NODES
    newPartOid = GetNewPartitionOid(pgPartRel, partTableRel, partDef, bucketOid,
        isTimestamptz, RelationGetStorageType(partTableRel), new_reloptions, isPartitionOfSubPartition);
#else
    newPartOid = GetNewPartitionOid(pgPartRel, partTableRel, partDef, bucketOid, isTimestamptz,
        RelationGetStorageType(partTableRel), isPartitionOfSubPartition);
#endif

    // We must bump the command counter to make the newly-created
    // partition tuple visible for opening.
    CommandCounterIncrement();

    if (isPartitionOfSubPartition) {
        Relation rel = heap_open(partTableRel->parentId, AccessShareLock);
        addIndexForPartition(rel, newPartOid);
        heap_close(rel, NoLock);
    } else {
        addIndexForPartition(partTableRel, newPartOid);
    }

    addToastTableForNewPartition(partTableRel, newPartOid, isPartitionOfSubPartition);

    // invalidate relation
    CacheInvalidateRelcache(partTableRel);

    pfree_ext(isTimestamptz);
    relation_close(pgPartRel, NoLock);

    return newPartOid;
}

/*
 * Description: Set one partitioned relation's reloptions wait_clean_gpi and one partition's reloptions wait_clean_gpi.
 *
 * Parameters:
 * 	@in alterGPI: check whether is global partition index alter statement.
 * 	@in partTableRel: partition table relation.
 * 	@in partOid: partition oid.
 */
static void AlterPartitionedSetWaitCleanGPI(bool alterGPI, Relation partTableRel, Oid targetPartOid)
{
    if (!alterGPI) {
        return;
    }

    Partition targetPart = NULL;
    Partition part = NULL;
    Relation partRel = NULL;
    Oid parentOid = partid_get_parentid(targetPartOid);
    if (parentOid != partTableRel->rd_id) {
        part = partitionOpen(partTableRel, parentOid, AccessShareLock);
        partRel = partitionGetRelation(partTableRel, part);
        targetPart = partitionOpen(partRel, targetPartOid, AccessExclusiveLock);
    } else {
        targetPart = partitionOpen(partTableRel, targetPartOid, AccessExclusiveLock);
    }

    if (!PartitionEnableWaitCleanGpi(targetPart)) {
        /* partition create not set wait_clean_gpi, must use update, and we ensure no concurrency */
        PartitionSetWaitCleanGpi(targetPartOid, true, false);
        /* Partitioned create set wait_clean_gpi=n, and we want save it, so just use inplace */
        PartitionedSetWaitCleanGpi(RelationGetRelationName(partTableRel), RelationGetRelid(partTableRel), true, true);
    }

    if (partRel != NULL) {
        partitionClose(partRel, targetPart, NoLock);
        releaseDummyRelation(&partRel);
        partitionClose(partTableRel, part, NoLock);
    } else {
        partitionClose(partTableRel, targetPart, NoLock);
    }
}

/*
 * Description: Set one subpartitioned relation's reloptions wait_clean_gpi and 
 * one subpartition's reloptions wait_clean_gpi.
 *
 * Parameters:
 * 	@in alterGPI: check whether is global partition index alter statement.
 * 	@in partTableRel: partition table relation.
 * 	@in subpartOid: subpartition oid.
 */
static void AlterSubPartitionedSetWaitCleanGPI(bool alterGPI, Relation partTableRel, Oid partOid, Oid subPartOid)
{
    if (!alterGPI) {
        return;
    }

    Partition part = partitionOpen(partTableRel, partOid, AccessShareLock);
    Relation partRel = partitionGetRelation(partTableRel, part);
    Partition subPart = partitionOpen(partRel, subPartOid, AccessExclusiveLock);
    if (!PartitionEnableWaitCleanGpi(subPart)) {
        /* partition create not set wait_clean_gpi, must use update, and we ensure no concurrency */
        PartitionSetWaitCleanGpi(subPartOid, true, false);
        /* Partitioned create set wait_clean_gpi=n, and we want save it, so just use inplace */
        PartitionedSetWaitCleanGpi(RelationGetRelationName(partTableRel), RelationGetRelid(partTableRel), true, true);
    }
    partitionClose(partRel, subPart, NoLock);
    releaseDummyRelation(&partRel);
    partitionClose(partTableRel, part, NoLock);
}


/*
 * Description: Add a temporary range partition during merge/exchange partitions.
 *
 * Parameters:
 * 	@in cmd: subcommand of an ALTER TABLE.
 * 	@in partTableRel: partition table relation.
 * 	@in sequence: current partition sequence.
 * 	@in renameTargetPart: if need to rename target partition's name.
 *
 * Returns:
 * 	temporary partition oid.
 */
static Oid AddTemporaryRangePartitionForAlterPartitions(const AlterTableCmd* cmd, Relation partTableRel,
    int sequence, bool* renameTargetPart)
{
    Oid newPartOid = InvalidOid;
    RangePartitionDefState* partDef = NULL;

    partDef = makeNode(RangePartitionDefState);
    if (*renameTargetPart) {
        /* use target partition's name to add a new partition if it not exists. */
        partDef->partitionName = pstrdup(cmd->name);
        *renameTargetPart = false;
    } else {
        /* use temporary partition's name to add a new partition if target partition's name already exists. */
        partDef->partitionName = GenTemporaryPartitionName(partTableRel, sequence);
        *renameTargetPart = true;
    }
    partDef->boundary = getRangePartitionBoundaryList(partTableRel, sequence);
    partDef->tablespacename = pstrdup(cmd->target_partition_tablespace);
    partDef->partitionno = GetPartitionnoFromSequence(partTableRel->partMap, sequence);
    partDef->curStartVal = NULL;
    partDef->partitionInitName = NULL;
    newPartOid = AddTemporaryPartition(partTableRel, (Node*)partDef);
    pfree_ext(partDef->partitionName);
    pfree_ext(partDef->tablespacename);
    pfree_ext(partDef);

    return newPartOid;
}

/*
 * Description: Add a temporary list partition during exchange partitions.
 *
 * Parameters:
 * 	@in cmd: subcommand of an ALTER TABLE.
 * 	@in partTableRel: partition table relation.
 * 	@in sequence: current partition sequence.
 * 	@in renameTargetPart: if need to rename target partition's name.
 *
 * Returns:
 * 	temporary partition oid.
 */
static Oid AddTemporaryListPartitionForAlterPartitions(const AlterTableCmd* cmd, Relation partTableRel,
    int sequence, bool* renameTargetPart)
{
    Oid newPartOid = InvalidOid;
    ListPartitionDefState* partDef = NULL;

    partDef = makeNode(ListPartitionDefState);
    if (*renameTargetPart) {
        /* use target partition's name to add a new partition if it not exists. */
        partDef->partitionName = pstrdup(cmd->name);
        *renameTargetPart = false;
    } else {
        /* use temporary partition's name to add a new partition if target partition's name already exists. */
        partDef->partitionName = GenTemporaryPartitionName(partTableRel, sequence);
        *renameTargetPart = true;
    }
    partDef->boundary = getListPartitionBoundaryList(partTableRel, sequence);
    partDef->tablespacename = pstrdup(cmd->target_partition_tablespace);
    partDef->partitionno = GetPartitionnoFromSequence(partTableRel->partMap, sequence);
    newPartOid = AddTemporaryPartition(partTableRel, (Node*)partDef);
    pfree_ext(partDef->partitionName);
    pfree_ext(partDef->tablespacename);
    pfree_ext(partDef);

    return newPartOid;
}

/*
 * Description: Add a temporary hash partition during exchange partitions.
 *
 * Parameters:
 * 	@in cmd: subcommand of an ALTER TABLE.
 * 	@in partTableRel: partition table relation.
 * 	@in sequence: current partition sequence.
 * 	@in renameTargetPart: if need to rename target partition's name.
 *
 * Returns:
 * 	temporary partition oid.
 */
static Oid AddTemporaryHashPartitionForAlterPartitions(const AlterTableCmd* cmd, Relation partTableRel,
    int sequence, bool* renameTargetPart)
{
    Oid newPartOid = InvalidOid;
    HashPartitionDefState* partDef = NULL;

    partDef = makeNode(HashPartitionDefState);
    if (*renameTargetPart) {
        /* use target partition's name to add a new partition if it not exists. */
        partDef->partitionName = pstrdup(cmd->name);
        *renameTargetPart = false;
    } else {
        /* use temporary partition's name to add a new partition if target partition's name already exists. */
        partDef->partitionName = GenTemporaryPartitionName(partTableRel, sequence);
        *renameTargetPart = true;
    }
    partDef->boundary = getHashPartitionBoundaryList(partTableRel, sequence);
    partDef->tablespacename = pstrdup(cmd->target_partition_tablespace);
    partDef->partitionno = GetPartitionnoFromSequence(partTableRel->partMap, sequence);
    newPartOid = AddTemporaryPartition(partTableRel, (Node*)partDef);
    pfree_ext(partDef->partitionName);
    pfree_ext(partDef->tablespacename);
    pfree_ext(partDef);

    return newPartOid;
}

/*
 * Description: Add a temporary partition during exchange/truncate partition.
 *
 * Parameters:
 * 	@in cmd: subcommand of an ALTER TABLE.
 * 	@in partTableRel: partition table relation.
 * 	@in srcPartOid: current partition oid.
 */
static Oid AddTemporaryPartitionForAlterPartitions(const AlterTableCmd* cmd, Relation partTableRel,
    Oid srcPartOid, bool* renameTargetPart)
{
    Oid destPartOid = InvalidOid;
    int partSeq = getPartitionElementsIndexByOid(partTableRel, srcPartOid);

    switch (partTableRel->partMap->type) {
        case PART_TYPE_LIST: {
            destPartOid = AddTemporaryListPartitionForAlterPartitions(cmd, partTableRel, partSeq, renameTargetPart);
            break;
        }
        case PART_TYPE_HASH: {
            destPartOid = AddTemporaryHashPartitionForAlterPartitions(cmd, partTableRel, partSeq, renameTargetPart);
            break;
        }
        case PART_TYPE_RANGE:
        case PART_TYPE_INTERVAL: {
            RangePartitionMap *partmap = (RangePartitionMap *)partTableRel->partMap;
            bool isinterval = partmap->rangeElements[partSeq].isInterval;
            destPartOid = AddTemporaryRangePartitionForAlterPartitions(cmd, partTableRel, partSeq, renameTargetPart);

            if (!isinterval) {
                break;
            }

            /* if srcPartOid is a interval partition oid, we need transform destPart to interval */
            Relation pg_partition = relation_open(PartitionRelationId, RowExclusiveLock);
            HeapTuple parttup = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(destPartOid));
            Assert(HeapTupleIsValid(parttup));

            Form_pg_partition partform = (Form_pg_partition)GETSTRUCT(parttup);
            partform->partstrategy = PART_STRATEGY_INTERVAL;
            simple_heap_update(pg_partition, &parttup->t_self, parttup);
            CatalogUpdateIndexes(pg_partition, parttup);

            tableam_tops_free_tuple(parttup);
            relation_close(pg_partition, RowExclusiveLock);
            CommandCounterIncrement();
            break;
        }
        default:
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_OPERATION),
                    errmsg("Only the List/Hash/Range partitioned table is supported for update global index.")));
            break;
    }
    return destPartOid;
}

/*
 * Description: Add a temporary partition during exchange partition.
 *
 * Parameters:
 * 	@in cmd: subcommand of an ALTER TABLE.
 * 	@in partTableRel: partition table relation.
 * 	@in srcPartOid: current partition oid.
 */
static void ExchangePartitionWithGPI(const AlterTableCmd* cmd, Relation partTableRel, Oid srcPartOid,
    TransactionId frozenXid, MultiXactId multiXid)
{
    // Return if no global index exists
    List* gpiList = RelationGetSpecificKindIndexList(partTableRel, true);
    if (list_length(gpiList) == 0) {
        return;
    }

    List* indexList = NIL;
    ListCell* cell = NULL;
    Partition srcPart = NULL;
    bool renameTargetPart = false;
    char* destPartitionName = NULL;

    Oid destPartOid = AddTemporaryPartitionForAlterPartitions(cmd, partTableRel, srcPartOid, &renameTargetPart);
    int partitionno = GetCurrentPartitionNo(srcPartOid);
    PARTITIONNO_VALID_ASSERT(partitionno);
    UpdateCurrentPartitionNo(destPartOid, partitionno, false);

    srcPart = partitionOpen(partTableRel, srcPartOid, AccessExclusiveLock);
    destPartitionName = pstrdup(PartitionGetPartitionName(srcPart));
    partitionClose(partTableRel, srcPart, NoLock);

    indexList = RelationGetSpecificKindIndexList(partTableRel, false);
    foreach (cell, indexList) {
        Oid indexOid = lfirst_oid(cell);
        Relation indexRel = relation_open(indexOid, RowExclusiveLock);
        Oid indexSrcPartOid = getPartitionIndexOid(indexRel->rd_id, srcPartOid);
        Oid indexDestPartOid = getPartitionIndexOid(indexRel->rd_id, destPartOid);
        Partition dstPart = NULL;

        /* before swap refilenode, promote lock on index partition from ExclusiveLock to AccessExclusiveLock */
        dstPart = partitionOpenWithRetry(indexRel, indexDestPartOid, AccessExclusiveLock, "EXCHANGE PARTITIONS");
        if (!dstPart) {
            ereport(ERROR,
                (errcode(ERRCODE_LOCK_WAIT_TIMEOUT),
                    errmsg(
                        "could not acquire AccessExclusiveLock on dest index partition \"%s\", "
                        "EXCHANGE PARTITIONS failed",
                        getPartitionName(indexDestPartOid, false))));
        }

        /* swap relfilenode between temp index relation and dest index partition */
        finishPartitionHeapSwap(indexDestPartOid, indexSrcPartOid, false, frozenXid, multiXid, true);
        partitionClose(indexRel, dstPart, NoLock);
        relation_close(indexRel, RowExclusiveLock);
    }

    // Swap relfilenode of table and toast table
    CommandCounterIncrement();
    finishPartitionHeapSwap(destPartOid, srcPartOid, false, frozenXid, multiXid, true);

    CommandCounterIncrement();
    AlterPartitionedSetWaitCleanGPI(cmd->alterGPI, partTableRel, srcPartOid);
    fastDropPartition(partTableRel, srcPartOid, "EXCHANGE PARTITIONS");

    if (renameTargetPart) {
        CommandCounterIncrement();
        renamePartitionInternal(partTableRel->rd_id, destPartOid, destPartitionName);
    }

    list_free_ext(indexList);
    pfree_ext(destPartitionName);

    AddGPIForPartition(RelationGetRelid(partTableRel), destPartOid);
}

static void fastAddPartition(Relation partTableRel, List* destPartDefList, List** newPartOidList)
{
    Relation pgPartRel = NULL;
    Oid newPartOid = InvalidOid;
    ListCell* cell = NULL;
    Oid bucketOid;

    bool* isTimestamptz = CheckPartkeyHasTimestampwithzone(partTableRel);

    bucketOid = RelationGetBucketOid(partTableRel);

    pgPartRel = relation_open(PartitionRelationId, RowExclusiveLock);

#ifndef ENABLE_MULTIPLE_NODES
    bool isNull = false;
    HeapTuple tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(partTableRel->rd_id));
    Datum relOptions = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, &isNull);
    List* oldRelOptions = untransformRelOptions(relOptions);
    Datum newRelOptions = transformRelOptions((Datum)0, oldRelOptions, NULL, NULL, false, false);
    ReleaseSysCache(tuple);
    list_free_ext(oldRelOptions);
#endif

    foreach (cell, destPartDefList) {
        RangePartitionDefState* partDef = (RangePartitionDefState*)lfirst(cell);

        newPartOid = heapAddRangePartition(pgPartRel,
            partTableRel->rd_id,
            partTableRel->rd_rel->reltablespace,
            bucketOid,
            partDef,
            partTableRel->rd_rel->relowner,
#ifndef ENABLE_MULTIPLE_NODES
            (Datum)newRelOptions,
#else
            (Datum)0,
#endif
            isTimestamptz,
            RelationGetStorageType(partTableRel),
            AccessExclusiveLock);

        *newPartOidList = lappend_oid(*newPartOidList, newPartOid);

        // We must bump the command counter to make the newly-created
        // partition tuple visible for opening.
        CommandCounterIncrement();

        addIndexForPartition(partTableRel, newPartOid);

        addToastTableForNewPartition(partTableRel, newPartOid);

        ereport(LOG,
                (errmsg("Partition %s(%d) has been fast Added.", partDef->partitionName, newPartOid)));

        // invalidate relation
        CacheInvalidateRelcache(partTableRel);
    }

    pfree_ext(isTimestamptz);
    relation_close(pgPartRel, NoLock);
}

static void FastAddListSubPartition(Relation rel, List* destPartDefList, Oid partOid, List** newPartOidList)
{
    Relation pgPartRel = NULL;
    Oid newSubPartOid = InvalidOid;
    ListCell* cell = NULL;
    Oid bucketOid;

    Partition part = partitionOpen(rel, partOid, ShareUpdateExclusiveLock);
    Relation partRel = partitionGetRelation(rel, part);

    bool* isTimestamptz = CheckPartkeyHasTimestampwithzone(partRel, true);

    bucketOid = RelationGetBucketOid(partRel);

    pgPartRel = relation_open(PartitionRelationId, RowExclusiveLock);

    foreach (cell, destPartDefList) {
        ListPartitionDefState* subPartDef = (ListPartitionDefState*)lfirst(cell);

        newSubPartOid = HeapAddListPartition(pgPartRel,
            partRel->rd_id,
            partRel->rd_rel->reltablespace,
            bucketOid,
            subPartDef,
            partRel->rd_rel->relowner,
            (Datum)0,
            isTimestamptz,
            RelationGetStorageType(partRel),
            NULL,
            true);

        *newPartOidList = lappend_oid(*newPartOidList, newSubPartOid);

        // We must bump the command counter to make the newly-created
        // partition tuple visible for opening.
        CommandCounterIncrement();

        addIndexForPartition(rel, newSubPartOid);

        addToastTableForNewPartition(partRel, newSubPartOid, true);

        // invalidate relation
        CacheInvalidateRelcache(partRel);
        CacheInvalidatePartcache(part);
    }

    pfree_ext(isTimestamptz);
    relation_close(pgPartRel, NoLock);
    releaseDummyRelation(&partRel);
    partitionClose(rel, part, NoLock);
}

static void FastAddRangeSubPartition(Relation rel, List* destPartDefList, Oid partOid, List** newPartOidList)
{
    Relation pgPartRel = NULL;
    Oid newSubPartOid = InvalidOid;
    ListCell* cell = NULL;
    Oid bucketOid;

    Partition part = partitionOpen(rel, partOid, ShareUpdateExclusiveLock);
    Relation partRel = partitionGetRelation(rel, part);

    bool* isTimestamptz = CheckPartkeyHasTimestampwithzone(partRel, true);

    bucketOid = RelationGetBucketOid(partRel);

    pgPartRel = relation_open(PartitionRelationId, RowExclusiveLock);

    foreach (cell, destPartDefList) {
        RangePartitionDefState* subPartDef = (RangePartitionDefState*)lfirst(cell);

        newSubPartOid = heapAddRangePartition(pgPartRel,
            partRel->rd_id,
            partRel->rd_rel->reltablespace,
            bucketOid,
            subPartDef,
            partRel->rd_rel->relowner,
            (Datum)0,
            isTimestamptz,
            RelationGetStorageType(partRel),
            AccessExclusiveLock,
            NULL,
            true);

        *newPartOidList = lappend_oid(*newPartOidList, newSubPartOid);

        // We must bump the command counter to make the newly-created
        // partition tuple visible for opening.
        CommandCounterIncrement();

        addIndexForPartition(rel, newSubPartOid);

        addToastTableForNewPartition(partRel, newSubPartOid, true);

        // invalidate relation
        CacheInvalidateRelcache(partRel);
        CacheInvalidatePartcache(part);
    }

    pfree_ext(isTimestamptz);
    relation_close(pgPartRel, NoLock);
    releaseDummyRelation(&partRel);
    partitionClose(rel, part, NoLock);
}

static Oid createTempTableForPartition(Relation partTableRel, Partition part)
{
    TupleDesc partTableHeapDesc;
    Datum partTableRelOptions = 0;
    HeapTuple tuple = NULL;
    bool isNull = false;
    Oid tempTableOid = InvalidOid;

    partTableHeapDesc = RelationGetDescr(partTableRel);

    /* get reloptions */
    tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(RelationGetRelid(partTableRel)));
    if (!HeapTupleIsValid(tuple)) {
        ereport(ERROR,
            (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                errmsg("cache lookup failed for relation %u", RelationGetRelid(partTableRel))));
    }
    partTableRelOptions = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, &isNull);
    if (isNull) {
        partTableRelOptions = (Datum)0;
    }

    // create temp table
    tempTableOid = makePartitionNewHeap(partTableRel,
        partTableHeapDesc,
        partTableRelOptions,
        part->pd_id,
        part->pd_part->reltoastrelid,
        part->pd_part->reltablespace);

    ReleaseSysCache(tuple);

    return tempTableOid;
}

static void readTuplesAndInsertInternal(Relation tempTableRel, Relation partTableRel, int2 bucketId)
{
    TableScanDesc scan = NULL;
    Tuple tuple = NULL;
    HTAB *partRelHTAB = NULL;
    bool relisustore = RelationIsUstoreFormat(tempTableRel);

    scan = tableam_scan_begin(tempTableRel, SnapshotNow, 0, NULL);

    while ((tuple = tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) {
        Oid targetPartOid = InvalidOid;
        int partitionno = INVALID_PARTITION_NO;
        Oid targetSubPartOid = InvalidOid;
        int subpartitionno = INVALID_PARTITION_NO;
        Relation partRel = NULL;
        Partition part = NULL;
        Relation subPartRel = NULL;
        Partition subPart = NULL;
        Tuple copyTuple = NULL;
        char* partExprKeyStr = NULL;

        /* tableam_tops_copy_tuple is not ready so we add UStore hack path */
        copyTuple = tableam_tops_copy_tuple(tuple);
        bool partExprKeyIsNull = PartExprKeyIsNull(partTableRel, &partExprKeyStr);
        if (partExprKeyIsNull) {
            /* If the partition key does not contain an expression */
            targetPartOid = heapTupleGetPartitionOid(partTableRel, (void *)tuple, &partitionno, true);
        } else {
            /* If the partition key contain an expression */
            TupleTableSlot *slot = MakeSingleTupleTableSlot(RelationGetDescr(tempTableRel));
            slot->tts_tuple = tuple;
            PartKeyExprResult partKeyExprResult = ComputePartKeyExprTuple(partTableRel, NULL, slot, NULL, partExprKeyStr);
            targetPartOid = heapTupleGetPartitionOid(partTableRel, (void*)(&partKeyExprResult), &partitionno, false, false, false);
            if (PointerIsValid(slot)) {
                ExecDropSingleTupleTableSlot(slot);
            }
            pfree(partExprKeyStr);
        }

        searchFakeReationForPartitionOid(partRelHTAB, CurrentMemoryContext, partTableRel, targetPartOid, partitionno,
            partRel, part, RowExclusiveLock);
        if (RelationIsSubPartitioned(partTableRel)) {
            targetSubPartOid = heapTupleGetPartitionOid(partRel, (void *)tuple, &subpartitionno, true);
            searchFakeReationForPartitionOid(partRelHTAB, CurrentMemoryContext, partRel, targetSubPartOid,
                subpartitionno, subPartRel, subPart, RowExclusiveLock);
            partRel = subPartRel;
        }
        if (bucketId != InvalidBktId) {
            searchHBucketFakeRelation(partRelHTAB, CurrentMemoryContext, partRel, bucketId, partRel);
        }

        if (RelationIsSubPartitioned(partTableRel)) {
            AlterSubPartitionedSetWaitCleanGPI(true, partTableRel, targetPartOid, targetSubPartOid);
        } else {
            AlterPartitionedSetWaitCleanGPI(true, partTableRel, targetPartOid);
        }

        if (relisustore) {
            Oid reloid = RelationGetRelid(partRel);
            if (reloid != InvalidOid) {
                ((UHeapTuple)copyTuple)->table_oid = reloid;
                ((UHeapTuple)copyTuple)->xc_node_id = u_sess->pgxc_cxt.PGXCNodeIdentifier;
            }
        }
        tableam_tuple_insert(partRel, copyTuple, GetCurrentCommandId(true), HEAP_INSERT_SPLIT_PARTITION, NULL);
        HeapTuple tup = (HeapTuple)copyTuple;
        tableam_tops_free_tuple(tup);
    }

    tableam_scan_end(scan);

    if (PointerIsValid(partRelHTAB)) {
        FakeRelationCacheDestroy(partRelHTAB);
    }
}

static void readTuplesAndInsert(Relation tempTableRel, Relation partTableRel)
{
    if (RELATION_CREATE_BUCKET(tempTableRel)) {
        Relation bucketRel = NULL;
        oidvector* bucketlist = searchHashBucketByOid(tempTableRel->rd_bucketoid);

        for (int i = 0; i < bucketlist->dim1; i++) {
            bucketRel = bucketGetRelation(tempTableRel, NULL, bucketlist->values[i]);
            readTuplesAndInsertInternal(bucketRel, partTableRel, bucketlist->values[i]);
            bucketCloseRelation(bucketRel);
        }
    } else {
        readTuplesAndInsertInternal(tempTableRel, partTableRel, InvalidBktId);
    }
}

List* transformIntoTargetType(FormData_pg_attribute* attrs, int2 keypos, List* boundary)
{
    Node* nodeBoundaryItem = NULL;
    Const* constBoundaryItem = NULL;
    Const* targetConstBoundaryItem = NULL;
    ListCell* cell = NULL;
    List* newBoundaryList = NULL;

    foreach (cell, boundary) {
        nodeBoundaryItem = (Node*)lfirst(cell);
        Assert(nodeTag(nodeBoundaryItem) == T_Const);
        constBoundaryItem = (Const*)nodeBoundaryItem;
        if (constBoundaryItem->ismaxvalue) {
            targetConstBoundaryItem = constBoundaryItem;
        } else {
            targetConstBoundaryItem = (Const*)GetTargetValue(&attrs[keypos - 1], constBoundaryItem, false);
            if (!PointerIsValid(targetConstBoundaryItem)) {
                list_free_ext(newBoundaryList);
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_OPERATION),
                        errmsg("partition key value must be const or const-evaluable expression")));
            }
            if (!OidIsValid(targetConstBoundaryItem->constcollid) && OidIsValid(attrs[keypos - 1].attcollation)) {
                targetConstBoundaryItem->constcollid = attrs[keypos - 1].attcollation;
            }
        }
        Assert(nodeTag(targetConstBoundaryItem) == T_Const);
        newBoundaryList = lappend(newBoundaryList, targetConstBoundaryItem);
    }

    return newBoundaryList;
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		:
 * Description	:
 * Notes		:
 */
List* transformConstIntoTargetType(FormData_pg_attribute* attrs, int2vector* partitionKey, List* boundary, bool partkeyIsFunc)
{
    int counter = 0;
    int2 partKeyPos = 0;
    Node* nodeBoundaryItem = NULL;
    Const* constBoundaryItem = NULL;
    Const* targetConstBoundaryItem = NULL;
    ListCell* cell = NULL;
    List* newBoundaryList = NULL;

    if (partitionKey->dim1 != boundary->length) {
        ereport(ERROR,

            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                errmsg("number of boundary items NOT EQUAL to number of partition keys")));
    }

    foreach (cell, boundary) {
        nodeBoundaryItem = (Node*)lfirst(cell);
        Assert(nodeTag(nodeBoundaryItem) == T_Const);
        constBoundaryItem = (Const*)nodeBoundaryItem;

        partKeyPos = partitionKey->values[counter++];

        if (constBoundaryItem->ismaxvalue) {
            targetConstBoundaryItem = constBoundaryItem;
        } else {
            targetConstBoundaryItem = (Const*)GetTargetValue(&attrs[partKeyPos - 1], constBoundaryItem, false, partkeyIsFunc);
            if (!PointerIsValid(targetConstBoundaryItem)) {
                list_free_ext(newBoundaryList);
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_OPERATION),
                        errmsg("partition key value must be const or const-evaluable expression")));
            }
            if (!OidIsValid(targetConstBoundaryItem->constcollid) && OidIsValid(attrs[partKeyPos - 1].attcollation)) {
                targetConstBoundaryItem->constcollid = attrs[partKeyPos - 1].attcollation;
            }
        }
        Assert(nodeTag(targetConstBoundaryItem) == T_Const);
        newBoundaryList = lappend(newBoundaryList, targetConstBoundaryItem);
    }

    return newBoundaryList;
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		: create a toast for a new partition
 * Description	:
 * Input		: relation: partitined table 's relation
 *			:
 * Output	:
 * Return		:
 * Notes		:
 */
void addToastTableForNewPartition(Relation relation, Oid newPartId, bool isForSubpartition)
{
    Oid firstPartitionId = InvalidOid;
    Oid firstPartitionToastId = InvalidOid;
    Partition firstPartition = NULL;
    Datum reloptions = (Datum)0;
    bool isnull = false;
    HeapTuple reltuple = NULL;

    /* create toast table */
    firstPartitionId = ((RangePartitionMap*)relation->partMap)->rangeElements[0].partitionOid;
    firstPartition = partitionOpen(relation, firstPartitionId, NoLock);
    firstPartitionToastId = firstPartition->pd_part->reltoastrelid;
    
    
    if (OidIsValid(firstPartitionToastId)) {
        reltuple = SearchSysCache1(RELOID, ObjectIdGetDatum(firstPartitionToastId));
        if (!PointerIsValid(reltuple)) {
            ereport(ERROR,
                (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                    errmsg("cache lookup failed for toast table: %u", firstPartitionToastId)));
        }
        reloptions = SysCacheGetAttr(RELOID, reltuple, Anum_pg_class_reloptions, &isnull);

        if (isnull) {
            reloptions = (Datum)0;
        }

        if (isForSubpartition) {
            (void)CreateToastTableForSubPartition(relation, newPartId, reloptions, AccessExclusiveLock);
        } else {
            (void)createToastTableForPartition(relation->rd_id, newPartId, reloptions, AccessExclusiveLock);
        }

        if (PointerIsValid(reltuple)) {
            ReleaseSysCache(reltuple);
        }

        /* Make the changes visible */
        CommandCounterIncrement();
    }
    partitionClose(relation, firstPartition, NoLock);
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		: create a cuDesc table for a new partition
 * Description	:
 * Input		: relation: partitined table 's relation
 *			:
 * Output	:
 * Return		:
 * Notes		:
 */
static void addCudescTableForNewPartition(Relation relation, Oid newPartId)
{
    Oid firstPartitionId = InvalidOid;
    Oid firstPartitionCudescTableId = InvalidOid;
    Partition firstPartition = NULL;
    Datum reloptions = (Datum)0;
    bool isnull = false;
    HeapTuple reltuple = NULL;

    /* create toast table */
    firstPartitionId = ((RangePartitionMap*)relation->partMap)->rangeElements[0].partitionOid;
    firstPartition = partitionOpen(relation, firstPartitionId, NoLock);
    firstPartitionCudescTableId = firstPartition->pd_part->relcudescrelid;

    if (OidIsValid(firstPartitionCudescTableId)) {
        reltuple = SearchSysCache1(RELOID, ObjectIdGetDatum(firstPartitionCudescTableId));
        if (!PointerIsValid(reltuple)) {
            ereport(ERROR,
                (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                    errmsg("cache lookup failed for cuDesc table: %u", firstPartitionCudescTableId)));
        }
        reloptions = SysCacheGetAttr(RELOID, reltuple, Anum_pg_class_reloptions, &isnull);

        if (isnull) {
            reloptions = (Datum)0;
        }

        (void)createCUDescTableForPartition(relation->rd_id, newPartId, reloptions);
        if (PointerIsValid(reltuple)) {
            ReleaseSysCache(reltuple);
        }

        /* Make the changes visible */
        CommandCounterIncrement();
    }
    partitionClose(relation, firstPartition, NoLock);
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		: create a delta table for a new partition
 * Description	:
 * Input		: relation: partitined table 's relation
 *			:
 * Output	:
 * Return		:
 * Notes		:
 */
static void addDeltaTableForNewPartition(Relation relation, Oid newPartId)
{
    Oid firstPartitionId = InvalidOid;
    Oid firstPartitionDeltaTableId = InvalidOid;
    Partition firstPartition = NULL;
    Datum reloptions = (Datum)0;
    bool isnull = false;
    HeapTuple reltuple = NULL;

    /* create toast table */
    firstPartitionId = ((RangePartitionMap*)relation->partMap)->rangeElements[0].partitionOid;
    firstPartition = partitionOpen(relation, firstPartitionId, NoLock);
    firstPartitionDeltaTableId = firstPartition->pd_part->reldeltarelid;

    if (OidIsValid(firstPartitionDeltaTableId)) {
        reltuple = SearchSysCache1(RELOID, ObjectIdGetDatum(firstPartitionDeltaTableId));
        if (!PointerIsValid(reltuple)) {
            ereport(ERROR,
                (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                    errmsg("cache lookup failed for delta  table: %u", firstPartitionDeltaTableId)));
        }
        reloptions = SysCacheGetAttr(RELOID, reltuple, Anum_pg_class_reloptions, &isnull);

        if (isnull) {
            reloptions = (Datum)0;
        }

        (void)createDeltaTableForPartition(relation->rd_id, newPartId, reloptions, NULL);
        if (PointerIsValid(reltuple)) {
            ReleaseSysCache(reltuple);
        }

        /* Make the changes visible */
        CommandCounterIncrement();
    }
    partitionClose(relation, firstPartition, NoLock);
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		: create a partiton for a special SN(sequence number)
 * Description	:
 * Input		: relation: partitined table 's relation
 *			: seqnum: the sequnce number for interval partition to be created
 * Output	:
 * Return		: the oid of the interval partition to be created
 * Notes		:
 */
Oid addPartitionBySN(Relation relation, int seqnum)
{
    return InvalidOid;
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		: get boundary for table partition
 * Description	:
 * Notes		:
 */
Datum caculateBoundary(Datum transpoint, Oid attrtypid, Datum intervalue, Oid intertypid, int seqnum)
{
    return (Datum)0;
}

#ifdef PGXC
/*
 * IsTempTable
 *
 * Check if given table Oid is temporary.
 */
bool IsTempTable(Oid relid)
{
    HeapTuple tuple = NULL;
    bool res = false;

    // if oid is invalid, it's not a table, and not a temp table.
    if (InvalidOid == relid)
        return false;

    tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
    if (!HeapTupleIsValid(tuple)) {
        return false;
    }
    Form_pg_class classForm = (Form_pg_class)GETSTRUCT(tuple);
    res = (classForm->relpersistence == RELPERSISTENCE_TEMP);
    ReleaseSysCache(tuple);

    return res;
}

bool IsGlobalTempTable(Oid relid)
{
    HeapTuple tuple = NULL;
    bool res = false;

    // if oid is invalid, it's not a table, and not a temp table.
    if (InvalidOid == relid)
        return false;

    tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
    if (!HeapTupleIsValid(tuple)) {
        return false;
    }
    Form_pg_class classForm = (Form_pg_class)GETSTRUCT(tuple);
    res = (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP);
    ReleaseSysCache(tuple);

    return res;
}

bool IsGlobalTempTableParallelTrunc()
{
#ifndef ENABLE_MULTIPLE_NODES
    if (u_sess->attr.attr_sql.enable_gtt_concurrent_truncate) {
        return true;
    }
#endif

    return false;
}

/*
 * IsUnloggedTable
 *
 * Check if given table Oid is unlogged.
 */
bool IsUnloggedTable(Oid relid)
{
    HeapTuple tuple = NULL;
    bool res = false;

    // if oid is invalid, it's not a table, and not a unlogged table.
    if (InvalidOid == relid)
        return false;

    tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
    if (!HeapTupleIsValid(tuple)) {
        return false;
    }
    Form_pg_class classForm = (Form_pg_class)GETSTRUCT(tuple);
    res = (classForm->relpersistence == RELPERSISTENCE_UNLOGGED);
    ReleaseSysCache(tuple);

    return res;
}

/*
 * IsRelaionView
 *
 * Check if given object Oid is view.
 */
bool IsRelaionView(Oid relid)
{
    Relation rel;
    bool res = false;
    /*
     * Is it correct to open without locks?
     * we just check if this object is view.
     */
    rel = relation_open(relid, NoLock);
    res = (rel->rd_rel->relkind == RELKIND_VIEW || rel->rd_rel->relkind == RELKIND_CONTQUERY);
    relation_close(rel, NoLock);
    return res;
}

/*
 * IsIndexUsingTemp
 *
 * Check if given index relation uses temporary tables.
 */
bool IsIndexUsingTempTable(Oid relid)
{
    bool res = false;
    HeapTuple tuple;
    Oid parent_id = InvalidOid;

    tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(relid));
    if (HeapTupleIsValid(tuple)) {
        Form_pg_index index = (Form_pg_index)GETSTRUCT(tuple);
        parent_id = index->indrelid;

        /* Release system cache BEFORE looking at the parent table */
        ReleaseSysCache(tuple);

        res = IsTempTable(parent_id);
    } else
        res = false; /* Default case */

    return res;
}

/*
 * IsOnCommitActions
 *
 * Check if there are any on-commit actions activated.
 */
bool IsOnCommitActions(void)
{
    return list_length(u_sess->cmd_cxt.on_commits) > 0;
}

/*
 * DropTableThrowErrorExternal
 *
 * Error interface for DROP when looking for execution node type.
 */
void DropTableThrowErrorExternal(RangeVar* relation, ObjectType removeType, bool missing_ok)
{
    char relkind;

    /* Determine required relkind */
    switch (removeType) {
        case OBJECT_TABLE:
            relkind = RELKIND_RELATION;
            break;

        case OBJECT_INDEX:
            relkind = RELKIND_INDEX;
            break;

        case OBJECT_SEQUENCE:
            relkind = RELKIND_SEQUENCE;
            break;

        case OBJECT_LARGE_SEQUENCE:
            relkind = RELKIND_LARGE_SEQUENCE;
            break;

        case OBJECT_VIEW:
            relkind = RELKIND_VIEW;
            break;

        case OBJECT_MATVIEW:
            relkind = RELKIND_MATVIEW;
            break;

        case OBJECT_CONTQUERY:
            relkind = RELKIND_CONTQUERY;
            break;
            
        case OBJECT_FOREIGN_TABLE:
            relkind = RELKIND_FOREIGN_TABLE;
            break;

        case OBJECT_STREAM:
            relkind = RELKIND_STREAM;
            break;

        default: {
            ereport(ERROR,
                (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                    errmsg("unrecognized drop object type: %d", (int)removeType)));

            relkind = 0; /* keep compiler quiet */
        } break;
    }

    DropErrorMsgNonExistent(relation->relname, relkind, missing_ok);
}
#endif

typedef struct CompressMetaInfo {
    const char* id;
    const int8 type;
} CompressMetaInfo;

static const CompressMetaInfo s_CmprsMetaInfo[] = {
    {"CMPRS_NOT_SUPPORT", REL_CMPRS_NOT_SUPPORT},

    /* IMPORTANT: add compression type after this line */
    {"NOCOMPRESS", REL_CMPRS_PAGE_PLAIN},
    {"COMPRESS", REL_CMPRS_FIELDS_EXTRACT},

    /* IMPORTANT: add compression type before this line */
    {"CMPRS_MAX", REL_CMPRS_MAX_TYPE},
};

#define GET_FIRST_CMPRS_METAITEM() ((CompressMetaInfo*)(s_CmprsMetaInfo + 1))
#define MOVE_NEXT_CMPRS_METAITEM(item) ((item)++)
#define LAST_CMPRS_METAITEM(item) ((item)->type == REL_CMPRS_MAX_TYPE)

static int8 getCompressType(const char* id)
{
    const CompressMetaInfo* metaItem = NULL;

    metaItem = GET_FIRST_CMPRS_METAITEM();
    while (!LAST_CMPRS_METAITEM(metaItem)) {
        if (0 == pg_strcasecmp(metaItem->id, id)) {
            break;
        }

        MOVE_NEXT_CMPRS_METAITEM(metaItem);
    }

    return metaItem->type;
}

static List* ATExecReplaceRelOptionListCell(List* options, char* keyName, char* newValue)
{
    ListCell* opt = NULL;
    ListCell* prev = NULL;
    bool found = false;

    foreach (opt, options) {
        DefElem* optDef = (DefElem*)lfirst(opt);
        if (pg_strcasecmp(optDef->defname, keyName) == 0) {
            found = true;
            break;
        }
        prev = opt;
    }

    if (found) {
        /* first delete this list cell. */
        options = list_delete_cell(options, opt, prev);

        /* insert new value */
        DefElem* newElem = makeDefElem(keyName, (Node*)makeString(newValue));
        options = lappend(options, newElem);
    }

    return options;
}

/*
 * ATDisableROTCompress is short for: Alter table disable row-oriented-table compress
 */
static void ATDisableROTCompress(const Relation rel, const int8 cmprsType)
{
    if (!RelationIsRowFormat(rel)) {
        return;
    }
    if (IsCompressedByCmprsInPgclass((RelCompressType)cmprsType)) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
             errmsg("row-oriented table does not support compression")));
    }
    /*
     * relcmprs(pg_class's field) of mlog table in materialized view is 0,
     * and it does not satisfys the following check. Because we have disabled
     * compression for row-oriented table, we work around it with reporting
     * an error and forbid coredump.
     *
     * Why relcmprs of mlog table is 0? The reason is in GetCreateTableStmt.
     * It uses row_compress in stmt->into, and this value is created by
     * rule CreateMatViewStmt in gram.y. It is not convenient for us to set it
     * to 1 there, otherwise, it is too hack. We can not let users specify
     * compress/nocompress when creating materialized view, either.
     *
     * In summary, checking it here may be the best choice.
     */
    if (CHECK_CMPRS_NOT_SUPPORT(RELATION_GET_CMPRS_ATTR(rel))) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
             errmsg("row-oriented table does not support compression.")));
    }
}

static void ATExecSetCompress(Relation rel, const char* cmprsId)
{
    Oid relId = RelationGetRelid(rel);
    Relation pgclass;
    HeapTuple relTuple;
    const int8 cmprsType = getCompressType(cmprsId);

    ATDisableROTCompress(rel, cmprsType);

    Assert(!CHECK_CMPRS_NOT_SUPPORT(RELATION_GET_CMPRS_ATTR(rel)));
    Assert(CHECK_CMPRS_VALID(cmprsType));

    /* always overwrite relcmprs field and compression options for row relation */
    pgclass = heap_open(RelationRelationId, RowExclusiveLock);

    relTuple = SearchSysCacheCopy1((int)RELOID, ObjectIdGetDatum(relId));
    if (!HeapTupleIsValid(relTuple)) {
        ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", relId)));
    }

    Datum replVal[Natts_pg_class] = {0};
    bool replNull[Natts_pg_class] = {false};
    bool replChange[Natts_pg_class] = {false};

    errno_t rc = memset_s(replVal, sizeof(replVal), 0, sizeof(replVal));
    securec_check(rc, "\0", "\0");
    rc = memset_s(replNull, sizeof(replNull), false, sizeof(replNull));
    securec_check(rc, "\0", "\0");
    rc = memset_s(replChange, sizeof(replChange), false, sizeof(replChange));
    securec_check(rc, "\0", "\0");

    /* first change the value of field RELCMPRS in pg_class */
    replVal[ANUM_PG_CLASS_RELCMPRS - 1] = cmprsType;
    replChange[ANUM_PG_CLASS_RELCMPRS - 1] = true;

    /* then change the value of COMPRESSION in relation options field */
    bool isNull = false;
    Datum relOptions = heap_getattr(relTuple, Anum_pg_class_reloptions, RelationGetDescr(pgclass), &isNull);
    if (!isNull) {
        List* options = untransformRelOptions(relOptions);
        options = ATExecReplaceRelOptionListCell(options,
            "compression",
            IsCompressedByCmprsInPgclass((RelCompressType)cmprsType) ? pstrdup(COMPRESSION_YES)
                                                                     : pstrdup(COMPRESSION_NO));
        relOptions = transformRelOptions((Datum)0, options, NULL, NULL, false, false);
        list_free_deep(options);

        replVal[Anum_pg_class_reloptions - 1] = relOptions;
        replChange[Anum_pg_class_reloptions - 1] = true;
    }

    HeapTuple newTuple = (HeapTuple) tableam_tops_modify_tuple(relTuple, RelationGetDescr(pgclass), replVal, replNull, replChange);
    simple_heap_update(pgclass, &newTuple->t_self, newTuple);
    CatalogUpdateIndexes(pgclass, newTuple);
    tableam_tops_free_tuple(newTuple);
    tableam_tops_free_tuple(relTuple);
    heap_close(pgclass, RowExclusiveLock);
}

/* CStore Rewrite Table Methods */
#include "access/cstore_rewrite.h"

#define RelAttrName(__tupdesc, __attridx) (NameStr((__tupdesc)->attrs[(__attridx)].attname))

// get all the attributes to be checked or rewrited.
//
static void ATCStoreGetRewriteAttrs(_in_ AlteredTableInfo* tab, _in_ TupleDesc oldTupDesc, _in_ TupleDesc newTupDesc,
    _out_ CStoreRewriteColumn** rewriteInfo, _out_ bool* rewriteFlags, _out_ int* nColsOfEachType)
{
    Assert(newTupDesc->natts >= oldTupDesc->natts);
    Assert(nColsOfEachType && rewriteFlags);

    for (int pass = 0; pass < AT_NUM_PASSES; ++pass) {
        if (tab->subcmds[pass] == NIL) {
            continue;
        }

        List* subcmds = tab->subcmds[pass];
        ListCell* cmdCell = NULL;

        foreach (cmdCell, subcmds) {
            AlterTableCmd* cmd = (AlterTableCmd*)lfirst(cmdCell);

            // ADD COLUMN.
            if (pass == AT_PASS_ADD_COL) {
                Assert(cmd->def && cmd->def->type == T_ColumnDef);
                ColumnDef* colDef = (ColumnDef*)cmd->def;
                Assert(colDef->colname && colDef->colname[0] != '\0');

                // search newTupDesc' attributes backward, so as to reduce loop as possible.
                for (int attrIdx = newTupDesc->natts - 1; attrIdx >= 0; --attrIdx) {
                    if (pg_strcasecmp(colDef->colname, RelAttrName(newTupDesc, attrIdx)) == 0) {
                        Assert(rewriteInfo[attrIdx] == NULL);
                        rewriteInfo[attrIdx] = CStoreRewriteColumn::CreateForAddColumn(attrIdx + 1);

                        // collect how many new columns will be added.
                        ++nColsOfEachType[CSRT_ADD_COL];
                        rewriteFlags[attrIdx] = true;
                        break;
                    }
                }
                continue;
            }

            // ALTER COLUMN DATA TYPE.
            if (pass == AT_PASS_ALTER_TYPE) {
                Assert(cmd->name && cmd->name[0] != '\0');
                for (int attrIdx = 0; attrIdx < oldTupDesc->natts; ++attrIdx) {
                    if (pg_strcasecmp(cmd->name, RelAttrName(oldTupDesc, attrIdx)) == 0) {
                        // forbit multiple ALTER TYPE on the same column.
                        Assert(rewriteInfo[attrIdx] == NULL);
                        rewriteInfo[attrIdx] = CStoreRewriteColumn::CreateForSetDataType(attrIdx + 1);

                        // collect how many existing columns will be changed data type.
                        ++nColsOfEachType[CSRT_SET_DATA_TYPE];
                        rewriteFlags[attrIdx] = true;
                        break;
                    }
                }
                continue;
            }
        }
    }
}

static void ATCStoreRewriteTable(AlteredTableInfo* tab, Relation oldHeapRel, LOCKMODE lockMode, Oid targetTblspc)
{
    bool tblspcChanged = NeedToSetTableSpace(oldHeapRel, targetTblspc);
    Oid newfilenode = InvalidOid;
    Oid cudescOid = InvalidOid;
    Oid cudescIdxOid = InvalidOid;
    Relation cudescRel = NULL;
    Relation pg_class = NULL;
    Relation CUReplicationRel = NULL;
    HeapTuple pgclass_tuple = NULL;
    Form_pg_class pgclass_form = NULL;
    CStoreRewriter* rewriter = NULL;
    errno_t rc;

    /*
     * Notice: old TupleDesc has been copied and saved in tab->oldDesc.
     *   now the new TupleDesc can be seen and found in oldHeapRel.
     */
    TupleDesc newTupDesc = RelationGetDescr(oldHeapRel);
    TupleDesc oldTupDesc = tab->oldDesc;
    Assert(newTupDesc->natts >= oldTupDesc->natts);

    /* unsupported table/column constraints: CHECK; FOREIGN EKY; */
    Assert(tab->constraints == NIL);

    int nColsOfEachType[CSRT_NUM];
    rc = memset_s(nColsOfEachType, sizeof(int) * CSRT_NUM, 0, sizeof(int) * CSRT_NUM);
    securec_check(rc, "", "");

    int maxCols = newTupDesc->natts;
    bool* rewriteFlags = (bool*)palloc0(sizeof(bool) * maxCols);
    CStoreRewriteColumn** rewriteInfo = (CStoreRewriteColumn**)palloc0(sizeof(void*) * maxCols);

    /* split out: ADD COLUMNs; SET DATA TYPE COLUMNs; the others */
    ATCStoreGetRewriteAttrs(tab, oldTupDesc, newTupDesc, rewriteInfo, rewriteFlags, nColsOfEachType);

    /* set recomputing expression for updated columns. */
    ListCell* l = NULL;
    foreach (l, tab->newvals) {
        NewColumnValue* ex = (NewColumnValue*)lfirst(l);
        ex->exprstate = ExecInitExpr((Expr*)ex->expr, NULL);

        /* we expect only one NewColumnValue for each attrubute. */
        Assert(rewriteInfo[ex->attnum - 1] != NULL);
        Assert(rewriteInfo[ex->attnum - 1]->newValue == NULL);

        ColumnNewValue* newValExp = (ColumnNewValue*)palloc(sizeof(ColumnNewValue));
        newValExp->expr = ex->expr;
        newValExp->exprstate = ex->exprstate;
        rewriteInfo[ex->attnum - 1]->newValue = newValExp;
    }

    /* set NOT NULL constraint for updated columns. */
    if (tab->rewrite>0 || tab->new_notnull) {
        for (int i = 0; i < maxCols; ++i) {
            if (rewriteInfo[i] != NULL && !rewriteInfo[i]->isDropped && newTupDesc->attrs[i].attnotnull) {
                rewriteInfo[i]->notNull = true;
            }
        }
    }

    /* rewrite the column-store table. */
    rewriter = New(CurrentMemoryContext) CStoreRewriter(oldHeapRel, oldTupDesc, newTupDesc);

    /* lock order:
     * 1. column relation
     * 2. Delta relation [ Delta Index relation ]
     * 3. Cudesc relation + Cudesc Index relation
     */
    if (OidIsValid(oldHeapRel->rd_rel->reldeltarelid)) {
        LockRelationOid(oldHeapRel->rd_rel->reldeltarelid, lockMode);
    }
    cudescOid = oldHeapRel->rd_rel->relcudescrelid;
    cudescRel = heap_open(cudescOid, lockMode);
    cudescIdxOid = cudescRel->rd_rel->relcudescidx;
    /* don't index_open(cudescIdxOid) becuase rewriter need to reindex Cudesc relation */
    LockRelationOid(cudescIdxOid, lockMode);

    if (tblspcChanged) {
        /* Handle Delta && Delta Index Relation
         * Now it's safe to copy the relation data by block directly,
         * because Delta Relation now is unusable and has no data.
         *
         * When Delta Relation is usable, all tuples in it must be scanned,
         * re-written and merged/appended into CU files
         */
        ChangeTableSpaceForDeltaRelation(oldHeapRel->rd_rel->reldeltarelid, targetTblspc, lockMode);

        /* Handle each column' data */
        /* Here it's safe to open pg_class relation, because:
         * 1. Cannot change tablespace of partitioned table;
         * 2. Cannot ADD COLUMN/SET DATA TYPE for one partition;
         * 3. Changing tablespace for one partition cannot hit this branch;
         * so it's a ordinary relation from pg_class.
         */
        pg_class = heap_open(RelationRelationId, RowExclusiveLock);

        /* Get a modifiable copy of the relation's pg_class row */
        pgclass_tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(tab->relid));
        if (!HeapTupleIsValid(pgclass_tuple)) {
            ereport(ERROR,
                (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", tab->relid)));
        }
        pgclass_form = (Form_pg_class)GETSTRUCT(pgclass_tuple);

        /*
         * Relfilenodes are not unique across tablespaces, so we need to allocate
         * a new one in the new tablespace.
         */
        newfilenode = GetNewRelFileNode(targetTblspc, NULL, oldHeapRel->rd_rel->relpersistence);

        RelFileNode CUReplicationFile = {
            ConvertToRelfilenodeTblspcOid(targetTblspc), oldHeapRel->rd_node.dbNode, newfilenode, InvalidBktId};
        CUReplicationRel = CreateCUReplicationRelation(CUReplicationFile,
            oldHeapRel->rd_backend,
            oldHeapRel->rd_rel->relpersistence,
            RelationGetRelationName(oldHeapRel));

        for (int i = 0; i < maxCols; ++i) {
            Form_pg_attribute thisattr = &newTupDesc->attrs[i];

            /* skip the dropped and rewritted columns */
            if (!thisattr->attisdropped && !rewriteFlags[i]) {
                CStoreCopyColumnData(CUReplicationRel, oldHeapRel, thisattr->attnum);
            }
        }

        /* prepare to handle ADD COLUMNs + SET DATATYPE COLUMNs */
        rewriter->ChangeTableSpace(CUReplicationRel);
    }

    /* handle Cudesc relation + ADD COLUMNs + SET DATATYPE COLUMNs */
    rewriter->BeginRewriteCols(maxCols, rewriteInfo, nColsOfEachType, rewriteFlags);
    rewriter->RewriteColsData();
    rewriter->EndRewriteCols();
    DELETE_EX(rewriter);

    if (tblspcChanged) {
        CStoreCopyColumnDataEnd(oldHeapRel, targetTblspc, newfilenode);

        /* destroy fake relation */
        FreeFakeRelcacheEntry(CUReplicationRel);

        /* update the pg_class row */
        pgclass_form->reltablespace = ConvertToPgclassRelTablespaceOid(targetTblspc);
        pgclass_form->relfilenode = newfilenode;
        simple_heap_update(pg_class, &pgclass_tuple->t_self, pgclass_tuple);
        CatalogUpdateIndexes(pg_class, pgclass_tuple);

        tableam_tops_free_tuple(pgclass_tuple);
        heap_close(pg_class, RowExclusiveLock);

        /* Make sure the reltablespace change is visible */
        CommandCounterIncrement();

        /* Handle Cudesc Index Relation */
        ATExecSetTableSpace(cudescIdxOid, ConvertToRelfilenodeTblspcOid(targetTblspc), lockMode);
    }

    /* unlock until committed */
    heap_close(cudescRel, NoLock);

    /* clean up work at last. */
    for (int k = 0; k < maxCols; ++k) {
        if (rewriteInfo[k]) {
            CStoreRewriteColumn::Destroy(&rewriteInfo[k]);
        }
    }
    pfree_ext(rewriteInfo);
    pfree_ext(rewriteFlags);
}

static void ATCStoreRewritePartition(AlteredTableInfo* tab, LOCKMODE lockMode)
{
    Relation parentRel = NULL;
    Relation newPartitionRel = NULL;
    List* partitionsList = NULL;
    ListCell* partitionCell = NULL;

    // construct a AlteredTableInfo object for a partition.
    // when it's passed into ATCStoreRewriteTable(), we take it as
    // the normall heap relation. therefore relid && partid shouldn't be
    // used, and so reset them.
    AlteredTableInfo* partitionTabInfo = (AlteredTableInfo*)palloc(sizeof(AlteredTableInfo));
    *partitionTabInfo = *tab;
    partitionTabInfo->relid = InvalidOid;
    partitionTabInfo->partid = InvalidOid;

    parentRel = heap_open(tab->relid, AccessExclusiveLock);
    Assert(RELATION_IS_PARTITIONED(parentRel) == true);

    partitionsList = relationGetPartitionList(parentRel, AccessExclusiveLock);
    foreach (partitionCell, partitionsList) {
        newPartitionRel = partitionGetRelation(parentRel, (Partition)lfirst(partitionCell));

        // rewrite each partition as the normall relation.
        ATCStoreRewriteTable(partitionTabInfo, newPartitionRel, lockMode, newPartitionRel->rd_rel->reltablespace);

        releaseDummyRelation(&newPartitionRel);
    }

    releasePartitionList(parentRel, &partitionsList, AccessExclusiveLock);
    heap_close(parentRel, NoLock);

    pfree_ext(partitionTabInfo);
    partitionTabInfo = NULL;
}

/*
 * @Description: forbidden to set tablespace for partitioned table
 * @Param[IN] tab: Alter Table Info
 * @See also:
 */
static void ForbidToChangeTableSpaceOfPartitionedTable(AlteredTableInfo* tab)
{
    if (OidIsValid(tab->newTableSpace) && !OidIsValid(tab->partid)) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                errmsg("can not set tablespace for partitioned relation"),
                errdetail("set tablespace for partition instead")));
    }
}

/*
 * @Description: rewrite row relation data.
 * @Param[IN] lockmode: lock mode used during rewriting data
 * @Param[IN] NewTableSpace: new tablespace used by row relation
 * @Param[IN] tab: Alter Table Info
 * @See also:
 */
static void ExecRewriteRowTable(AlteredTableInfo* tab, Oid NewTableSpace, LOCKMODE lockmode)
{
    ForbidToRewriteOrTestCstoreIndex(tab);
    Oid OIDNewHeap = make_new_heap(tab->relid, NewTableSpace);

    /*
     * Copy the heap data into the new table with the desired
     * modifications, and test the current data within the table
     * against new constraints generated by ALTER TABLE commands.
     */
    Relation oldRel = heap_open(tab->relid, NoLock);
    Relation newRel = heap_open(OIDNewHeap, lockmode); 

    /*
     * Temporarily set the relOptions of the old rel to th ones before
     * modification to execute rewrite table.
     */
    if (tab->rewrite == AT_REWRITE_ALTER_COMPRESSION) {
        oldRel->rd_node.opt = tab->opt;
    }
    ATRewriteTable(tab, oldRel, newRel);
    heap_close(oldRel, NoLock);
    heap_close(newRel, NoLock);

    /*
     * Swap the physical files of the old and new heaps, then rebuild
     * indexes and discard the old heap.  We can use RecentXmin for
     * the table's new relfrozenxid because we rewrote all the tuples
     * in ATRewriteTable, so no older Xid remains in the table.  Also,
     * we never try to swap toast tables by content, since we have no
     * interest in letting this code work on system catalogs.
     */
    finish_heap_swap(tab->relid, OIDNewHeap, false, false, true, u_sess->utils_cxt.RecentXmin,
                     GetOldestMultiXactId(), NULL, tab);

    /* clear all attrinitdefval */
    clearAttrInitDefVal(tab->relid);
}

/*
 * @Description: rewrite column relation data.
 * @Param[IN] lockmode: lock mode used during rewriting data
 * @Param[IN] NewTableSpace: new tablespace used by column relation
 * @Param[IN] tab: Alter Table Info
 * @See also:
 */
static void ExecRewriteCStoreTable(AlteredTableInfo* tab, Oid NewTableSpace, LOCKMODE lockmode)
{
    Relation OldHeap = heap_open(tab->relid, NoLock);
    ATCStoreRewriteTable(tab, OldHeap, lockmode, NewTableSpace);
    heap_close(OldHeap, NoLock);

    /* then, rebuild its index. */
    (void)ReindexRelation(
        tab->relid, REINDEX_REL_SUPPRESS_INDEX_USE | REINDEX_REL_CHECK_CONSTRAINTS, REINDEX_ALL_INDEX, NULL);
}

/*
 * @Description: rewrite row partitioned relation data.
 *    Take each partition as a ordinary table, and rewrite it.
 * @Param[IN] lockmode: lock mode used during rewriting each partition.
 * @Param[IN] NewTableSpace: new tablespace used by some partition.
 * @Param[IN] tab: Alter Table Info
 * @See also:
 */
static void ExecRewriteRowPartitionedTable(AlteredTableInfo* tab, Oid NewTableSpace, LOCKMODE lockmode)
{
    Relation partitionedTableRel = NULL;
    TupleDesc partTabHeapDesc = NULL;
    HeapTuple tuple = NULL;
    List* tempTableOidList = NIL;
    List* partitions = NULL;
    ListCell* cell = NULL;
    Oid tempTableOid = InvalidOid;
    Datum partTabRelOptions = 0;
    int reindexFlags = 0;
    bool isNull = false;

    ForbidToChangeTableSpaceOfPartitionedTable(tab);
    ForbidToRewriteOrTestCstoreIndex(tab);

    partitionedTableRel = heap_open(tab->relid, AccessExclusiveLock);
    partTabHeapDesc = RelationGetDescr(partitionedTableRel);

    tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(tab->relid));
    if (!HeapTupleIsValid(tuple)) {
        ereport(
            ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", tab->relid)));
    }
    partTabRelOptions = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, &isNull);
    if (isNull) {
        partTabRelOptions = (Datum)0;
    }

    if (RelationIsSubPartitioned(partitionedTableRel)) {
        partitions = relationGetPartitionList(partitionedTableRel, AccessExclusiveLock);
        foreach (cell, partitions) {
            Partition partition = (Partition)lfirst(cell);
            Relation partrel = partitionGetRelation(partitionedTableRel, partition);

            List *subpartitions = relationGetPartitionList(partrel, AccessExclusiveLock);
            ListCell* subcell = NULL;
            foreach (subcell, subpartitions) {
                Partition subpartition = (Partition)lfirst(subcell);
                Relation oldRel = partitionGetRelation(partrel, subpartition);
                Datum relOptions = 0;

                /*
                 * Make new partition heap with the new reloptions when modifying 
                 * compressed options.
                 */
                if (tab->rewrite == AT_REWRITE_ALTER_COMPRESSION) {
                    relOptions = tab->newOptions;
                } else {
                    relOptions = partTabRelOptions;
                }
                /* make a temp table for swapping partition */
                Oid OIDNewHeap = makePartitionNewHeap(partrel,
                    RelationGetDescr(partrel),
                    relOptions,
                    oldRel->rd_id,
                    oldRel->rd_rel->reltoastrelid,
                    oldRel->rd_rel->reltablespace,
                    false,
                    partitionedTableRel->rd_rel->relfilenode);

                Relation newRel = heap_open(OIDNewHeap, lockmode);
                /* rewrite the temp table by partition */
                ATRewriteTable(tab, oldRel, newRel);
                heap_close(newRel, NoLock);

                /* swap the temp table and partition */
                finishPartitionHeapSwap(oldRel->rd_id, OIDNewHeap, false, u_sess->utils_cxt.RecentXmin,
                                        GetOldestMultiXactId(), false, tab);

                /* record the temp table oid for dropping */
                tempTableOidList = lappend_oid(tempTableOidList, OIDNewHeap);

                releaseDummyRelation(&oldRel);
            }
            releasePartitionList(partrel, &subpartitions, AccessExclusiveLock);
            releaseDummyRelation(&partrel);
        }
    } else {
        partitions = relationGetPartitionList(partitionedTableRel, AccessExclusiveLock);
        foreach (cell, partitions) {
            Partition partition = (Partition)lfirst(cell);
            Relation oldRel = partitionGetRelation(partitionedTableRel, partition);
            Datum relOptions = 0;

            /*
             * Make new partition heap with the new reloptions when modifying 
             * compressed options.
             */
            if (tab->rewrite == AT_REWRITE_ALTER_COMPRESSION) {
                relOptions = tab->newOptions;
            } else {
                relOptions = partTabRelOptions;
            }
            /* make a temp table for swapping partition */
            Oid OIDNewHeap = makePartitionNewHeap(partitionedTableRel,
                partTabHeapDesc,
                relOptions,
                oldRel->rd_id,
                oldRel->rd_rel->reltoastrelid,
                oldRel->rd_rel->reltablespace);

            Relation newRel = heap_open(OIDNewHeap, lockmode);
            /* rewrite the temp table by partition */
            ATRewriteTable(tab, oldRel, newRel);
            heap_close(newRel, NoLock);

            /* swap the temp table and partition */
            finishPartitionHeapSwap(oldRel->rd_id, OIDNewHeap, false, u_sess->utils_cxt.RecentXmin,
                GetOldestMultiXactId(), false, tab);

            /* record the temp table oid for dropping */
            tempTableOidList = lappend_oid(tempTableOidList, OIDNewHeap);

            releaseDummyRelation(&oldRel);
        }
    }

    ReleaseSysCache(tuple);

    /* rebuild index of partitioned table */
    reindexFlags = REINDEX_REL_SUPPRESS_INDEX_USE | REINDEX_REL_CHECK_CONSTRAINTS;
    (void)ReindexRelation(tab->relid, reindexFlags, REINDEX_ALL_INDEX, NULL);

    /* drop the temp tables for swapping */
    foreach (cell, tempTableOidList) {
        ObjectAddress object;

        tempTableOid = DatumGetObjectId(lfirst(cell));

        object.classId = RelationRelationId;
        object.objectId = tempTableOid;
        object.objectSubId = 0;

        performDeletion(&object, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
    }
    list_free_ext(tempTableOidList);

    releasePartitionList(partitionedTableRel, &partitions, AccessExclusiveLock);
    heap_close(partitionedTableRel, NoLock);

    /* clear all attrinitdefval */
    clearAttrInitDefVal(tab->relid);
}

/*
 * @Description: rewrite column partitioned relation data.
 *    Take each partition as a ordinary table, and rewrite it.
 * @Param[IN] lockmode: lock mode used during rewriting each partition.
 * @Param[IN] targetTableSpace: new tablespace used by some partition.
 * @Param[IN] tab: Alter Table Info
 * @See also:
 */
static void ExecRewriteCStorePartitionedTable(AlteredTableInfo* tab, Oid targetTableSpace, LOCKMODE lockmode)
{
    /* forbid to change tablespace for partitioned table.
     * so argument *targetTableSpace* is not used.
     */
    ForbidToChangeTableSpaceOfPartitionedTable(tab);

    Relation OldHeap = heap_open(tab->relid, NoLock);
    ATCStoreRewritePartition(tab, lockmode);
    heap_close(OldHeap, NoLock);

    /* then, rebuild its index. */
    (void)ReindexRelation(
        tab->relid, REINDEX_REL_SUPPRESS_INDEX_USE | REINDEX_REL_CHECK_CONSTRAINTS, REINDEX_ALL_INDEX, NULL);
}

/*
 * @Description: Only check relation data becuase constraints changed
 * @Param[IN] tab: Alter Table Info
 * @See also:
 */
static void ExecOnlyTestRowTable(AlteredTableInfo* tab)
{
    ForbidToRewriteOrTestCstoreIndex(tab);

    Relation oldRel = heap_open(tab->relid, NoLock);
    ATRewriteTable(tab, oldRel, NULL);
    heap_close(oldRel, NoLock);
}

/*
 * @Description: Only check relation data becuase constraints changed
 * @Param[IN] tab: Alter Table Info
 * @See also:
 */
static void ExecOnlyTestCStoreTable(AlteredTableInfo* tab)
{
#ifdef ENABLE_MULTIPLE_NODES
    ereport(ERROR,
        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
            errmsg("Un-support feature"),
            errdetail("column stored relation doesn't support this feature")));
#else
    ForbidToRewriteOrTestCstoreIndex(tab);

    Relation rel = heap_open(tab->relid, NoLock);
    ATOnlyCheckCStoreTable(tab, rel);
    heap_close(rel, NoLock);
    return;
#endif
}

/**
 * @Description: Build a list of all attributes. The attributes must has
 * "NOT NULL" constraint and is not dropped column.
 * @in tuple_desc, The tuple description.
 * @return
 */
List* make_not_null_attrs(TupleDesc tuple_desc)
{
    List* not_null_attrs = NIL;
    for (int i = 0; i < tuple_desc->natts; i++) {
        if (tuple_desc->attrs[i].attnotnull && !tuple_desc->attrs[i].attisdropped)
            not_null_attrs = lappend_int(not_null_attrs, i);
    }

    return not_null_attrs;
}

/*
 * @Description: Only check relation data becuase constraints changed
 * @Param[IN] tab:  Alter Table Info
 * @See also:
 */
static void ExecOnlyTestRowPartitionedTable(AlteredTableInfo* tab)
{
    Relation partRel = NULL;
    Partition partition = NULL;
    ListCell* lc1 = NULL;
    ListCell* lc2 = NULL;
    Relation partitionedTableRel = NULL;
    List* partitions = NULL;
    Relation subPartRel = NULL;
    Partition subPartition = NULL;
    List* subPartitions = NULL;

    ForbidToRewriteOrTestCstoreIndex(tab);

    /* get all partitions of target partitioned table */
    partitionedTableRel = heap_open(tab->relid, NoLock);
    partitions = relationGetPartitionList(partitionedTableRel, NoLock);

    foreach (lc1, partitions) {
        partition = (Partition)lfirst(lc1);
        partRel = partitionGetRelation(partitionedTableRel, partition);
        if (RelationIsSubPartitioned(partitionedTableRel)) {
            subPartitions = relationGetPartitionList(partRel, NoLock);
            foreach (lc2, subPartitions) {
                subPartition = (Partition)lfirst(lc2);
                subPartRel = partitionGetRelation(partRel, subPartition);
                /* check each partition */
                ATRewriteTable(tab, subPartRel, NULL);
                releaseDummyRelation(&subPartRel);
            }
            releasePartitionList(partRel, &subPartitions, NoLock);
            releaseDummyRelation(&partRel);
        } else {
            /* check each partition */
            ATRewriteTable(tab, partRel, NULL);
            releaseDummyRelation(&partRel);
        }
    }

    releasePartitionList(partitionedTableRel, &partitions, NoLock);
    heap_close(partitionedTableRel, NoLock);
}

/*
 * @Description: Only check relation data becuase constraints changed
 * @Param[IN] tab:  Alter Table Info
 * @See also:
 */
static void ExecOnlyTestCStorePartitionedTable(AlteredTableInfo* tab)
{
#ifdef ENABLE_MULTIPLE_NODES
    ereport(ERROR,
        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
            errmsg("Un-support feature"),
            errdetail("column stored relation doesn't support this feature")));
#else
    Relation partRel = NULL;
    Partition partition = NULL;
    ListCell* cell = NULL;
    Relation partitionedTableRel = NULL;
    List* partitions = NULL;

    ForbidToRewriteOrTestCstoreIndex(tab);

    /* get all partitions of target partitioned table */
    partitionedTableRel = heap_open(tab->relid, NoLock);
    partitions = relationGetPartitionList(partitionedTableRel, NoLock);

    foreach (cell, partitions) {
        partition = (Partition)lfirst(cell);
        partRel = partitionGetRelation(partitionedTableRel, partition);
        /* check each partition */
        ATOnlyCheckCStoreTable(tab, partRel);
        releaseDummyRelation(&partRel);
    }

    releasePartitionList(partitionedTableRel, &partitions, NoLock);
    heap_close(partitionedTableRel, NoLock);
#endif
}

/*
 * @Description: Only check PSort relation data.
 * @Param[IN] tab: Alter Table Info
 * @See also:
 */
static void ForbidToRewriteOrTestCstoreIndex(AlteredTableInfo* tab)
{
    if (tab->relkind == RELKIND_INDEX || tab->relkind == RELKIND_GLOBAL_INDEX) {
        Relation rel = index_open(tab->relid, AccessShareLock);
        if (rel->rd_rel->relam == PSORT_AM_OID) {
            index_close(rel, AccessShareLock);

            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    (errmsg("Un-support feature"), errdetail("PSort relation doesn't support this feature"))));
        }

        if (rel->rd_rel->relam == CBTREE_AM_OID) {
            index_close(rel, AccessShareLock);

            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    (errmsg("Un-support feature"), errdetail("CBtree relation doesn't support this feature"))));
        }

        if (rel->rd_rel->relam == CGIN_AM_OID) {
            index_close(rel, AccessShareLock);

            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    (errmsg("Un-support feature"), errdetail("CGinBtree relation doesn't support this feature"))));
        }
        index_close(rel, AccessShareLock);
    }
}

/*
 * @Description: SET TABLESPACE for psort relation
 * @Param[IN] lockmode:  lock mode used during changing tablespace.
 * @Param[IN] newTableSpace: the new/target tablespace.
 * @Param[IN] psortOid: Oid of PSort relation
 * @See also:
 */
static void PSortChangeTableSpace(Oid psortOid, Oid newTableSpace, LOCKMODE lockmode)
{
    AlteredTableInfo* tabinfo = (AlteredTableInfo*)palloc0(sizeof(AlteredTableInfo));

    /* fill the needed info */
    Assert(OidIsValid(newTableSpace));
    tabinfo->relid = psortOid;
    tabinfo->relkind = RELKIND_RELATION;
    tabinfo->newTableSpace = newTableSpace;

    /* treat psort as a column table */
    ExecChangeTableSpaceForCStoreTable(tabinfo, lockmode);

    pfree_ext(tabinfo);
}

/*
 * @Description: change tablespace for row relation.
 *    PSort index is handled in this branch, because its oid is remembered here.
 * @Param[IN] lockmode: lock mode used during changing tablespace.
 * @Param[IN] tab: Alter Table Info
 * @See also: the comments of function ExecChangeTableSpaceForRowPartition()
 */
static void ExecChangeTableSpaceForRowTable(AlteredTableInfo* tab, LOCKMODE lockmode)
{
#ifdef ENABLE_HTAP
    if (RelHasImcs(tab->relid)) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                errmsg("The rel enable imcstore, please unimcstore if want to exchange tablespace.")));
    }
#endif

    ATExecSetTableSpace(tab->relid, tab->newTableSpace, lockmode);

    /* handle a special index type: PSORT index */
    if (tab->relkind == RELKIND_INDEX || tab->relkind == RELKIND_GLOBAL_INDEX) {
        Relation rel = index_open(tab->relid, lockmode);
        if (rel->rd_rel->relam == PSORT_AM_OID) {
            PSortChangeTableSpace(rel->rd_rel->relcudescrelid, /* psort oid */
                tab->newTableSpace,
                lockmode);
        }
        index_close(rel, NoLock);
        Oid heapId = IndexGetRelation(tab->relid, false);
        Relation userRel = RelationIdGetRelation(heapId);
        UpdatePgObjectChangecsn(heapId, userRel->rd_rel->relkind);
        RelationClose(userRel);
    }
}

/*
 * @Description: change tablespace for Delta relation.
 * @Param[IN] deltaOid: Oid of Delta relation
 * @Param[IN] lockmode: lock mode used during changing tablespace.
 * @Param[IN] targetTableSpace: the new tablespace.
 * @See also:
 */
static inline void ChangeTableSpaceForDeltaRelation(Oid deltaOid, Oid targetTableSpace, LOCKMODE lockmode)
{
    if (OidIsValid(deltaOid)) {
        /* ATExecSetTableSpace() requires that targetTableSpace is not InvalidOid */
        targetTableSpace = ConvertToRelfilenodeTblspcOid(targetTableSpace);
        Assert(OidIsValid(targetTableSpace));

        /* lock delta relation with lockmode */
        Relation deltaRel = heap_open(deltaOid, lockmode);

        /* change tablespace for Delta Relation */
        ATExecSetTableSpace(deltaOid, targetTableSpace, lockmode);

        /* unlock until committed */
        relation_close(deltaRel, NoLock);

        /* change tablespace for Delta Index Relation */
    }
}

/*
 * @Description: change tablespace for CUDesc and its index relation.
 * @Param[IN] cudescIdxOid: Oid of Cudesc Index relation
 * @Param[IN] cudescOid: Oid of Cudesc relation
 * @Param[IN] lockmode: lock mode used during changing tablespace
 * @Param[IN] targetTableSpace: the new tablespace
 * @See also:
 */
static inline void ChangeTableSpaceForCudescRelation(
    Oid cudescIdxOid, Oid cudescOid, Oid targetTableSpace, LOCKMODE lockmode)
{
    /* ATExecSetTableSpace() requires that targetTableSpace is valid */
    targetTableSpace = ConvertToRelfilenodeTblspcOid(targetTableSpace);
    Assert(OidIsValid(targetTableSpace));

    /* change tablespace for Cudesc Relation */
    Assert(OidIsValid(cudescOid));
    ATExecSetTableSpace(cudescOid, targetTableSpace, lockmode);

    /* change tablespace for Cudesc Index Relation */
    Assert(OidIsValid(cudescIdxOid));
    ATExecSetTableSpace(cudescIdxOid, targetTableSpace, lockmode);
}

/*
 * @Description: change tablespace for column relation.
 * @Param[IN] lockmode: lock mode used during changing tablespace
 * @Param[IN] tab: Alter Table Info
 * @See also:
 */
static void ExecChangeTableSpaceForCStoreTable(AlteredTableInfo* tab, LOCKMODE lockmode)
{
    Relation colRel = NULL;
    Relation cudescRel = NULL;
    Relation cudescIdxRel = NULL;
    Oid cudescOid = InvalidOid;
    Oid cudescIdxOid = InvalidOid;
    Oid targetTableSpace = tab->newTableSpace;
    Oid newrelfilenode = InvalidOid;

    /* here maybe open a heap relation or index relation, so call relation_open() */
    colRel = relation_open(tab->relid, lockmode);

    /* No work if no change in tablespace. */
    if (!NeedToSetTableSpace(colRel, targetTableSpace)) {
        relation_close(colRel, NoLock);
        return;
    }

    /* lock order:
     * 1. column relation
     * 2. Delta relation [ Delta Index relation ]
     * 3. Cudesc relation + Cudesc Index relation
     */
    if (OidIsValid(colRel->rd_rel->reldeltarelid)) {
        LockRelationOid(colRel->rd_rel->reldeltarelid, lockmode);
    }
    cudescOid = colRel->rd_rel->relcudescrelid;
    cudescRel = heap_open(cudescOid, lockmode);
    cudescIdxOid = cudescRel->rd_rel->relcudescidx;
    cudescIdxRel = index_open(cudescIdxOid, lockmode);

    /* 1. Handle Delta && Delta Index Relation */
    ChangeTableSpaceForDeltaRelation(colRel->rd_rel->reldeltarelid, targetTableSpace, lockmode);

    /* 2. Handle each column' data */
    Relation pg_class = heap_open(RelationRelationId, RowExclusiveLock);

    /* Get a modifiable copy of the relation's pg_class row */
    HeapTuple tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(tab->relid));
    if (!HeapTupleIsValid(tuple)) {
        ereport(
            ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", tab->relid)));
    }

    Form_pg_class rd_rel = (Form_pg_class)GETSTRUCT(tuple);
    newrelfilenode = CStoreSetTableSpaceForColumnData(colRel, targetTableSpace);

    /* update the pg_class row */
    rd_rel->reltablespace = ConvertToPgclassRelTablespaceOid(targetTableSpace);
    rd_rel->relfilenode = newrelfilenode;
    simple_heap_update(pg_class, &tuple->t_self, tuple);
    CatalogUpdateIndexes(pg_class, tuple);

    tableam_tops_free_tuple(tuple);
    heap_close(pg_class, RowExclusiveLock);

    /* Make sure the reltablespace change is visible */
    CommandCounterIncrement();

    /* 3. Handle Cudesc && Index Relation */
    ChangeTableSpaceForCudescRelation(cudescIdxOid, cudescOid, targetTableSpace, lockmode);

    index_close(cudescIdxRel, NoLock);
    heap_close(cudescRel, NoLock);
    relation_close(colRel, NoLock);
}

/*
 * @Description: change tablespace for row partition.
 * first, forbid to SET TABLESPACE for partitioned table.
 * if it's a
 *   1) row heap partition,
 *   2) row index partition,
 *      the two are the same,
 *      step 1: copy data to new tablespace
 *      step 2: update pg_partition.reltablespace && pg_partition.relfilenode
 *      step 3: handle toast && toast index if necessary.
 *   3) psort index partition,
 *      it's a column table, so that
 *      step 1: update pg_partition.reltablespace (ATExecSetTableSpaceForPartitionP3)
 *      step 2: handle psort as an ordinary column table. (ExecChangeTableSpaceForCStoreTable)
 *
 * @Param[IN] lockmode: lock mode used during changing tablespace
 * @Param[IN] tab: Alter Table Info
 * @See also:
 */
static void ExecChangeTableSpaceForRowPartition(AlteredTableInfo* tab, LOCKMODE lockmode)
{
#ifdef ENABLE_HTAP
    if (RelHasImcs(tab->relid)) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                errmsg("The part rel enable imcstore, please unimcstore if want to exchange tablespace.")));
    }
#endif

    ForbidToChangeTableSpaceOfPartitionedTable(tab);

    /* input lockmode is the lock mode of patitioned table, which is >= AccessShareLock.
     * Take and example, t1 is a row partitioned relation and under tblspc1 tablespace. now execute
     *        ALTER TABLE t1 MOVE PARTITION p1 TO pg_default, ADD COLUMN c_char2 char(5);
     * it will triggle Altering-Table-Instantly feature, and needn't rewrite all the tuple data.
     * so lock mode is 8, and SET TABLESPACE branch is entered into. So that input lockmode may
     * be not AccessShareLock.
     *
     * Here we should use the lock mode of partition, which is AccessExclusiveLock.
     * see also ATExecSetTableSpaceForPartitionP2().
     */
    const LOCKMODE partitionLock = AccessExclusiveLock;

    ATExecSetTableSpaceForPartitionP3(tab->relid, tab->partid, tab->newTableSpace, partitionLock);

    /* handle a special index type: PSORT index */
    if (tab->relkind == RELKIND_INDEX || tab->relkind == RELKIND_GLOBAL_INDEX) {
        Relation rel = index_open(tab->relid, NoLock);
        if (rel->rd_rel->relam == PSORT_AM_OID) {
            Partition part = partitionOpen(rel, tab->partid, partitionLock);
            PSortChangeTableSpace(part->pd_part->relcudescrelid, /* psort oid */
                tab->newTableSpace,
                partitionLock);
            partitionClose(rel, part, NoLock);
        }
        index_close(rel, NoLock);
    }
}

/*
 * @Description: change tablespace for column partition
 * @Param[IN] lockmode: lock mode used during changing tablespace
 * @Param[IN] tab: Alter Table Info
 * @See also:
 */
static void ExecChangeTableSpaceForCStorePartition(AlteredTableInfo* tab, LOCKMODE lockmode)
{
    Relation parentRel = NULL;
    Partition partition = NULL;
    Relation partitionRel = NULL;
    Relation cudescRel = NULL;
    Relation cudescIdxRel = NULL;
    Oid partOid = tab->partid;
    Oid cudescOid = InvalidOid;
    Oid cudescIdxOid = InvalidOid;
    Oid targetTableSpace = tab->newTableSpace;
    Oid newrelfilenode = InvalidOid;

    ForbidToChangeTableSpaceOfPartitionedTable(tab);

    /* input lockmode is the lock mode of patitioned table, which is AccessShareLock.
     * Here we should use the lock mode of partition, which is AccessExclusiveLock.
     * see also ATExecSetTableSpaceForPartitionP2().
     */
    const LOCKMODE partitionLock = AccessExclusiveLock;

    /* here maybe open a heap relation or index relation, so call relation_open() */
    parentRel = relation_open(tab->relid, NoLock);
    partition = partitionOpen(parentRel, partOid, partitionLock);
    partitionRel = partitionGetRelation(parentRel, partition);

    /* No work if no change in tablespace. */
    if (!NeedToSetTableSpace(partitionRel, targetTableSpace)) {
        releaseDummyRelation(&partitionRel);
        partitionClose(parentRel, partition, NoLock);
        relation_close(parentRel, NoLock);
        return;
    }

    /* lock order:
     * 1. column relation
     * 2. Delta relation [ Delta Index relation ]
     * 3. Cudesc relation + Cudesc Index relation
     */
    if (OidIsValid(partitionRel->rd_rel->reldeltarelid)) {
        LockRelationOid(partitionRel->rd_rel->reldeltarelid, partitionLock);
    }
    cudescOid = partitionRel->rd_rel->relcudescrelid;
    cudescRel = heap_open(cudescOid, partitionLock);
    cudescIdxOid = cudescRel->rd_rel->relcudescidx;
    cudescIdxRel = index_open(cudescIdxOid, partitionLock);

    /* 1. Handle Delta && Delta Index Relation */
    ChangeTableSpaceForDeltaRelation(partitionRel->rd_rel->reldeltarelid, targetTableSpace, partitionLock);

    /* 2. Handle each column' data */
    Relation pg_partition = heap_open(PartitionRelationId, RowExclusiveLock);

    /* Get a modifiable copy of the relation's pg_partition row */
    HeapTuple tuple = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(partOid));
    if (!HeapTupleIsValid(tuple))
        ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for partition %u", partOid)));
    Form_pg_partition rd_rel = (Form_pg_partition)GETSTRUCT(tuple);

    newrelfilenode = CStoreSetTableSpaceForColumnData(partitionRel, targetTableSpace);

    /* update the pg_partition row */
    rd_rel->reltablespace = ConvertToPgclassRelTablespaceOid(targetTableSpace);
    rd_rel->relfilenode = newrelfilenode;
    simple_heap_update(pg_partition, &tuple->t_self, tuple);
    CatalogUpdateIndexes(pg_partition, tuple);

    tableam_tops_free_tuple(tuple);
    heap_close(pg_partition, RowExclusiveLock);

    /* Make sure the reltablespace change is visible */
    CommandCounterIncrement();

    /* 3. Handle Cudesc && Index Relation */
    ChangeTableSpaceForCudescRelation(cudescIdxOid, cudescOid, targetTableSpace, partitionLock);

    index_close(cudescIdxRel, NoLock);
    heap_close(cudescRel, NoLock);

    releaseDummyRelation(&partitionRel);
    partitionClose(parentRel, partition, NoLock);
    relation_close(parentRel, NoLock);
}

/**
 * @Description: Whether judge the column is partition column.
 * @in rel, A relation.
 * @in att_no, Attribute number.
 * @return If the the column is partition column, return true, otherwise return false.
 */
bool is_partition_column(Relation rel, AttrNumber att_no)
{
    bool is_part_col = false;

    if (RelationIsValuePartitioned(rel)) {
        List* part_col_list = ((ValuePartitionMap*)rel->partMap)->partList;
        ListCell* lc = NULL;
        foreach (lc, part_col_list) {
            if (att_no == lfirst_int(lc)) {
                is_part_col = true;
                break;
            }
        }
    } else if (RelationIsCommonPartitioned(rel)) {
        int2vector* part_key = PartitionMapGetPartKeyArray(rel->partMap);
        for (int i = 0; i < part_key->dim1; i++) {
            if (att_no == part_key->values[i]) {
                is_part_col = true;
                break;
            }
        }
    } else if (RelationIsSubPartitioned(rel)) {
        int2vector* partKey = PartitionMapGetPartKeyArray(rel->partMap);
        for (int i = 0; i < partKey->dim1; i++) {
            if (att_no == partKey->values[i]) {
                return true;
            }
        }
        List *partOidList = relationGetPartitionOidList(rel);
        Oid partOid = linitial_oid(partOidList);
        Partition part = partitionOpen(rel, partOid, NoLock);
        Relation partRel = partitionGetRelation(rel, part);
        int2vector* subPartKey = PartitionMapGetPartKeyArray(partRel->partMap);
        for (int i = 0; i < subPartKey->dim1; i++) {
            if (att_no == subPartKey->values[i]) {
                is_part_col = true;
                break;
            }
        }
        releaseDummyRelation(&partRel);
        partitionClose(rel, part, NoLock);
        if (partOidList != NULL) {
            releasePartitionOidList(&partOidList);
        }
    }

    return is_part_col;
}

/**
 * @Description: Reset every partition's start_ctid/end_ctid of rel
 * @in rel, parent relation of partitions.
 * @return void
 */
static void ResetPartsRedisCtidRelOptions(Relation rel)
{
    Relation pg_partition = NULL;
    ScanKeyData key[2];
    SysScanDesc scan = NULL;
    HeapTuple tuple = NULL;
    TupleDesc part_tupdesc = NULL;
    List* redis_reloptions = NIL;

    pg_partition = heap_open(PartitionRelationId, RowExclusiveLock);
    part_tupdesc = RelationGetDescr(pg_partition);
    ScanKeyInit(&key[0], Anum_pg_partition_parttype, BTEqualStrategyNumber, F_CHAREQ,
        CharGetDatum(PART_OBJ_TYPE_TABLE_PARTITION));
    ScanKeyInit(&key[1], Anum_pg_partition_parentid, BTEqualStrategyNumber,F_OIDEQ, 
        ObjectIdGetDatum(RelationGetRelid(rel)));

    scan = systable_beginscan(pg_partition, PartitionParentOidIndexId, true, NULL, 2, key);
    redis_reloptions = AlterTableSetRedistribute(rel, REDIS_REL_RESET_CTID, NULL);
    while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
        HeapTuple dtuple;
        Datum repl_val[Natts_pg_partition];
        bool repl_null[Natts_pg_partition];
        bool repl_repl[Natts_pg_partition];
        bool isNull = false;

        Datum newOptions = (Datum)0;
        errno_t errorno = EOK;

        errorno = memset_s(repl_null, sizeof(repl_null), false, sizeof(repl_null));
        securec_check_c(errorno, "\0", "\0");

        errorno = memset_s(repl_repl, sizeof(repl_repl), false, sizeof(repl_repl));
        securec_check_c(errorno, "\0", "\0");

        Datum dval = fastgetattr(tuple, Anum_pg_partition_reloptions, part_tupdesc, &isNull);
        /* reset redis reloptions info */
        newOptions = transformRelOptions(isNull ? (Datum)0 : dval, redis_reloptions, NULL, NULL, false, false);

        if (newOptions != (Datum)0) {
            repl_val[Anum_pg_partition_reloptions - 1] = newOptions;
            repl_null[Anum_pg_partition_reloptions - 1] = false;
        } else {
            repl_null[Anum_pg_partition_reloptions - 1] = true;
        }

        repl_repl[Anum_pg_partition_reloptions - 1] = true;

        dtuple = (HeapTuple) tableam_tops_modify_tuple(tuple, part_tupdesc, repl_val, repl_null, repl_repl);

        simple_heap_update(pg_partition, &dtuple->t_self, dtuple);
        CatalogUpdateIndexes(pg_partition, dtuple);
        /* reset partition reloptions info. */
        if (RELATION_HAS_BUCKET(rel) && RELATION_OWN_BUCKET(rel)) {
            Partition part = partitionOpen(rel, HeapTupleGetOid(tuple), AccessExclusiveLock);
            Relation partrel = partitionGetRelation(rel, part);
            reset_merge_list_on_pgxc_class(partrel);
            releaseDummyRelation(&partrel);
            partitionClose(rel, part, NoLock);
        }
        tableam_tops_free_tuple(dtuple);
    }
    systable_endscan(scan);
    list_free_ext(redis_reloptions);
    heap_close(pg_partition, RowExclusiveLock);
}


/**
 * @Description: Reset one partition(oid = part_oid)'s ctid info of rel
 * @in rel, parent relation.
 * @in part_oid, oid of partition
 * @return void
 */
static void ResetOnePartRedisCtidRelOptions(Relation rel, Oid part_oid)
{
    Assert(rel != NULL && OidIsValid(part_oid));

    ResetRelRedisCtidRelOptions(
        rel, part_oid, PARTRELID, Natts_pg_partition, Anum_pg_partition_reloptions, PartitionRelationId);
}

static void ResetRelRedisCtidInfo(Relation rel, Oid part_oid, HeapTuple tuple, Oid pgcat_oid, Datum* repl_val,
    const bool* repl_null, const bool* repl_repl)
{
    Relation pgcatrel;
    HeapTuple newtuple;

    pgcatrel = heap_open(pgcat_oid, RowExclusiveLock);
    newtuple = (HeapTuple) tableam_tops_modify_tuple(tuple, RelationGetDescr(pgcatrel), repl_val, repl_null, repl_repl);

    simple_heap_update(pgcatrel, &newtuple->t_self, newtuple);
    CatalogUpdateIndexes(pgcatrel, newtuple);
    /* reseet partition reloptions info. */
    if (RELATION_HAS_BUCKET(rel) && RELATION_OWN_BUCKET(rel)) {
        if (OidIsValid(part_oid)) {
            Partition part = partitionOpen(rel, part_oid, AccessExclusiveLock);
            Relation partrel = partitionGetRelation(rel, part);
            reset_merge_list_on_pgxc_class(partrel);
            releaseDummyRelation(&partrel);
            partitionClose(rel, part, NoLock);
        } else {
            reset_merge_list_on_pgxc_class(rel);
        }
    }
    tableam_tops_free_tuple(newtuple);
    ReleaseSysCache(tuple);
    heap_close(pgcatrel, RowExclusiveLock);
}

/**
 * @Description: relation(parent relation when partition rel)
 * @in rel, relaton or partition's fakeRelation.
 * @part_oid, partition's oid
 * @in cat_id, catche id of pg_class or pg_partition
 * @in att_num, att_num of pgcat(pg_class or pg_partition)
 * @in att_inx, att_inx of reloptions
 * @Oid pgcat_oid, pg_class or pg_partition
 * @return void
 */
static void ResetRelRedisCtidRelOptions(Relation rel, Oid part_oid, int cat_id, int att_num, int att_inx, Oid pgcat_oid)
{
    Datum newOptions = (Datum)0;
    Datum oldOptions = (Datum)0;
    List* redis_reloptions = NIL;
    Datum* repl_val = NULL;
    bool* repl_null = NULL;
    bool* repl_repl = NULL;
    HeapTuple tuple;
    bool isnull = false;

    repl_val = (Datum*)palloc0(att_num * sizeof(Datum));
    repl_null = (bool*)palloc0(att_num);
    repl_repl = (bool*)palloc0(att_num);

    if (OidIsValid(part_oid)) {
        tuple = SearchSysCache1(cat_id, ObjectIdGetDatum(part_oid));
    } else {
        tuple = SearchSysCache1(cat_id, ObjectIdGetDatum(rel->rd_id));
    }
    if (!HeapTupleIsValid(tuple)) {
        ereport(
            ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for partition %u", rel->rd_id)));
    }

    /* Get the old reloptions */
    oldOptions = SysCacheGetAttr(cat_id, tuple, att_inx, &isnull);

    /* reset redis reloptions info */
    redis_reloptions = AlterTableSetRedistribute(rel, REDIS_REL_RESET_CTID, NULL);
    newOptions = transformRelOptions(isnull ? (Datum)0 : oldOptions, redis_reloptions, NULL, NULL, false, false);
    list_free_ext(redis_reloptions);

    if (newOptions != (Datum)0) {
        repl_val[att_inx - 1] = newOptions;
        repl_null[att_inx - 1] = false;
    } else
        repl_null[att_inx - 1] = true;

    repl_repl[att_inx - 1] = true;

    ResetRelRedisCtidInfo(rel, part_oid, tuple, pgcat_oid, repl_val, repl_null, repl_repl);

    pfree_ext(repl_val);
    pfree_ext(repl_null);
    pfree_ext(repl_repl);
    return;
}
/**
 * WLM has 5 system tables, those tables store monitoring data on CN node, will influence node-restore && node-expand.
 * So, need to grant truncate permission of those 5 tables to user, to reduce influence.
 *
 */
static bool WLMRelationCanTruncate(Relation rel)
{
    const char* targetRelname = get_rel_name(rel->rd_id);
    if ((strcmp(targetRelname, WLM_USER_RESOURCE_HISTORY) == 0 || strcmp(targetRelname, WLM_INSTANCE_HISTORY) == 0 ||
            strcmp(targetRelname, WLM_EC_OPERATOR_INFO) == 0 || strcmp(targetRelname, WLM_OPERATOR_INFO) == 0 ||
            strcmp(targetRelname, WLM_SESSION_INFO) == 0) &&
        IsSystemNamespace(RelationGetNamespace(rel))) {
        return true;
    }
    return false;
}

static bool password_contain_space(const char *pwd)
{
    if (pwd == NULL || strlen(pwd) == 0) {
        return true;
    }
    return false;
}

void CreateWeakPasswordDictionary(CreateWeakPasswordDictionaryStmt* stmt)
{
    Relation rel;
    HeapTuple tup = NULL;
    ListCell* pwd_obj = NULL;
    bool is_null = false;

    if (!has_createrole_privilege(GetUserId())) {
        ereport(ERROR,
            (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                errmsg("Permission denied")));
    }

    rel = heap_open(GsGlobalConfigRelationId, RowExclusiveLock);

    foreach (pwd_obj, stmt->weak_password_string_list) {
        Datum values[Natts_gs_global_config] = {0};
        bool nulls[Natts_gs_global_config] = {false};
        const char* pwd = (const char *)(((Value*)lfirst(pwd_obj))->val.str);
        if (password_contain_space(pwd)) {
            continue;
        }
        const char* name = "weak_password";
        TableScanDesc scan = tableam_scan_begin(rel, SnapshotNow, 0, NULL);
        TupleDesc tupdesc = RelationGetDescr(rel);
        bool flag = false;

        while ((tup = (HeapTuple) tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) {
            Datum ex_datum = heap_getattr(tup, Anum_gs_global_config_value, tupdesc, &is_null);
            if (is_null) {
                continue;
            }
            char *ex_pwd = text_to_cstring(DatumGetTextP(ex_datum));
            if (strcmp(ex_pwd, pwd) == 0) {
                flag = true;
                break;
            }
        }

        tableam_scan_end(scan);
        if (flag == false) {
            values[Anum_gs_global_config_name - 1] = DirectFunctionCall1(namein, CStringGetDatum(name));
            values[Anum_gs_global_config_value - 1] = CStringGetTextDatum(pwd);
            tup = (HeapTuple) heap_form_tuple(RelationGetDescr(rel), values, nulls);
            simple_heap_insert(rel, tup);
            heap_freetuple_ext(tup);
        }
    }
    heap_close(rel, RowExclusiveLock);
}

/*
 * Brief : delete all weak passwords.
 */
void DropWeakPasswordDictionary()
{
    if (!has_createrole_privilege(GetUserId())) {
       ereport(ERROR,
            (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                errmsg("Permission denied")));
    }

    Relation rel = heap_open(GsGlobalConfigRelationId, RowExclusiveLock);

    HeapTuple tuple = NULL;
    bool is_null = false;

    TableScanDesc scan = heap_beginscan(rel, SnapshotNow, 0, NULL);
    TupleDesc tupdesc = RelationGetDescr(rel);

    /* if the relation is valid, then delete the records of the role */
    while ((tuple = (HeapTuple) tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) {
        if (strcmp(DatumGetCString(heap_getattr(tuple, Anum_gs_global_config_name, tupdesc, &is_null)), 
            "weak_password") == 0) {
            Assert(is_null == false);
            simple_heap_delete(rel, &tuple->t_self);            
        }
    }
    tableam_scan_end(scan);
    heap_close(rel, RowExclusiveLock);
}

/**
 * @Description: check for alter table when relation is timeseries store
 * @in rel, relation which need alter table.
 * @in cmd, alter table command.
 * @return void
 */
static void at_timeseries_check(Relation rel, AlterTableCmd* cmd)
{
    if (!g_instance.attr.attr_common.enable_tsdb) {
        ereport(ERROR, (errcode(ERRCODE_OPERATE_FAILED), 
                        errmsg("Cannot alter timeseries table when enable_tsdb is off.")));
    }
    if (!CStoreSupportATCmd(cmd->subtype))
        ereport(ERROR,
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("Un-support feature"),
                errdetail("timeseries store relation doesn't support this ALTER yet")));
    switch (cmd->subtype) {
        case AT_AddPartition:
        case AT_DropPartition:
        case AT_ResetPartitionno:
        case AT_SetRelOptions:
        case AT_DropColumn:
        case AT_TruncatePartition:
        case AT_TruncateSubPartition:
        case AT_ChangeOwner:
            break;
        case AT_AddColumn: {
            ColumnDef* def = (ColumnDef*)cmd->def;
            if (def->kvtype == ATT_KV_UNDEFINED)
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("Un-support feature"),
                        errdetail("column kvtype should be defined for timeseries store relation")));
            if (def->kvtype == ATT_KV_TIMETAG)
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("Un-support feature"),
                        errdetail("column kvtype cannot defined as TSTIME")));
            break;
        }
        default:
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("This ALTER command is not support in timeseries store.")));
            break;
    }
}

static OnCommitAction GttOncommitOption(const List *options)
{
    ListCell *listptr;
    OnCommitAction action = ONCOMMIT_NOOP;

    foreach(listptr, options) {
        DefElem *def = reinterpret_cast<DefElem *>(lfirst(listptr));
        if (strcmp(def->defname, "on_commit_delete_rows") == 0) {
            bool res = false;
            char *sval = defGetString(def);

            if (!parse_bool(sval, &res)) {
                ereport(ERROR,
                       (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                         errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
            }

            if (res) {
                action = ONCOMMIT_DELETE_ROWS;
            } else {
                action = ONCOMMIT_PRESERVE_ROWS;
            }
            break;
        }
    }
    return action;
}

/*
 * Returns true iff any relation underlying this query is a temporary database
 * object (table, view, or materialized view).
 *
 */
bool
isQueryUsingTempRelation(Query *query)
{
   return isQueryUsingTempRelation_walker((Node *) query, NULL);
}

static bool
isQueryUsingTempRelation_walker(Node *node, void *context)
{
   if (node == NULL)
       return false;

   if (IsA(node, Query))
   {
       Query      *query = (Query *) node;
       ListCell   *rtable = NULL;

       foreach(rtable, query->rtable)
       {
           RangeTblEntry *rte = (RangeTblEntry *)lfirst(rtable);

           if (rte->rtekind == RTE_RELATION)
           {
               Relation    rel = heap_open(rte->relid, AccessShareLock);
               char        relpersistence = rel->rd_rel->relpersistence;

               heap_close(rel, AccessShareLock);
               if (relpersistence == RELPERSISTENCE_TEMP)
                   return true;
           }
       }

       return query_tree_walker(query,
                                (bool (*)())isQueryUsingTempRelation_walker,
                                context,
                                QTW_IGNORE_JOINALIASES);
   }

   return expression_tree_walker(node,
                                 (bool (*)())isQueryUsingTempRelation_walker,
                                 context);
}

void ExecutePurge(PurgeStmt *stmt)
{
    switch (stmt->purtype) {
        case PURGE_TABLE:
            TrPurgeObject(stmt->purobj, RB_OBJ_TABLE);
            break;
        case PURGE_INDEX:
            TrPurgeObject(stmt->purobj, RB_OBJ_INDEX);
            break;
        case PURGE_TABLESPACE: {
            Oid spcId;
            spcId = ConvertToPgclassRelTablespaceOid(
                        get_tablespace_oid(stmt->purobj->relname, false));
            if (!pg_tablespace_ownercheck(spcId, GetUserId())) {
                aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE, 
                    stmt->purobj->relname);
            }
            RbCltPurgeSpace(spcId);
            break;
        }
        case PURGE_RECYCLEBIN: {
            Oid userId = GetUserId();
            /*
             * Superusers bypass all permission checking.
             * Database Security: Support seperation of privilege.
             */
            if (!(superuser_arg(userId) || systemDBA_arg(userId))) {
                ereport(ERROR,
                    (errmsg("Only superuser can do purge recyclebin operation.")));
            }
            RbCltPurgeRecyclebin();
            break;
        }
        default:
            ereport(ERROR,
                (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                    errmsg("unrecognized purge type: %d", (int)stmt->purtype)));
    }
}

/*
 * TIMECAPSULE:
 *     TIMECAPSULE TABLE { table_name } TO { TIMESTAMP | CSN } expression
 *     TIMECAPSULE TABLE { table_name } TO BEFORE DROP [RENAME TO new_tablename]
 *     TIMECAPSULE TABLE { table_name } TO BEFORE TRUNCATE [ FORCE ]
 */
void ExecuteTimeCapsule(TimeCapsuleStmt *stmt)
{
    switch (stmt->tcaptype) {
        case TIMECAPSULE_VERSION:
            TvRestoreVersion(stmt);
            break;

        case TIMECAPSULE_DROP:
            TrRestoreDrop(stmt);
            break;

        case TIMECAPSULE_TRUNCATE:
            TrRestoreTruncate(stmt);
            break;

        default:
            ereport(ERROR,
                (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                    errmsg("unrecognized timecapsule type: %d", (int)stmt->tcaptype)));
    }
}

/*
 * AlterCreateChainTables
 *    If it is a ledger usertable, that should invoking this function.
 *    then create a history table.
 */
void AlterCreateChainTables(Oid relOid, Datum reloptions, CreateStmt *mainTblStmt)
{
    Relation rel = NULL;

    rel = heap_open(relOid, AccessExclusiveLock);

    /* Ledger user table only support for the regular relation. */
    if (!rel->rd_isblockchain) {
        heap_close(rel, NoLock);
        return;
    }

    create_hist_relation(rel, reloptions, mainTblStmt);
    heap_close(rel, NoLock);
}

void CheckDropViewValidity(ObjectType stmtType, char relKind, const char* relname)
{
    if (relKind != RELKIND_VIEW && relKind != RELKIND_CONTQUERY) {
        return;
    }

    if ((stmtType == OBJECT_CONTQUERY) || (stmtType == OBJECT_VIEW)) {
        return;
    }

    char expectedRelKind;
    switch (stmtType) {
        case OBJECT_TABLE:
            expectedRelKind = RELKIND_RELATION;
            break;
        case OBJECT_INDEX:
            expectedRelKind = RELKIND_INDEX;
            break;
        case OBJECT_SEQUENCE:
            expectedRelKind = RELKIND_SEQUENCE;
            break;
        case OBJECT_LARGE_SEQUENCE:
            expectedRelKind = RELKIND_LARGE_SEQUENCE;
            break;
        case OBJECT_MATVIEW:
            expectedRelKind = RELKIND_MATVIEW;
            break;
        case OBJECT_FOREIGN_TABLE:
            expectedRelKind = RELKIND_FOREIGN_TABLE;
            break;
        case OBJECT_STREAM:
            expectedRelKind = RELKIND_STREAM;
            break;
        default: {
            ereport(ERROR,
                (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                    errmsg("unrecognized drop object type: %d", (int)stmtType)));
            expectedRelKind = 0;    /* keep compiler quiet */
        } break;
    }
    DropErrorMsgWrongType(relname, relKind, expectedRelKind);
}
void ShrinkCfsChunkRestore(Oid relationId, LOCKMODE lockmode, bool nowait)
{
    Relation relation = try_relation_open(relationId, lockmode);
    if (relation == NULL) {
        ereport(ERROR,(errcode(ERRCODE_RELATION_OPEN_ERROR),
            errmsg("could not open relation with OID %u", relationId)));
    }

    if (relation->rd_node.opt == 0) {
        relation_close(relation, lockmode);
        ereport(ERROR, (errcode(ERRCODE_OPERATE_FAILED),
            errmsg("could not deal with uncompressed relation whose OID is %u in cfs shrink.", relationId)));
    }

    LockRelFileNode(relation->rd_node, lockmode);
    SmgrChunkFragmentsRestore(relation->rd_node, MAIN_FORKNUM, relation->rd_rel->parttype, nowait);
    SmgrChunkFragmentsRestoreRecord(relation->rd_node, MAIN_FORKNUM);
    UnlockRelFileNode(relation->rd_node, lockmode);
    UnlockRelationOid(relationId, lockmode);
    relation_close(relation, lockmode);
}

void ShrinkRelationChunk(ShrinkStmt* shrink)
{
    ListCell* cell = NULL;
    foreach (cell, shrink->relations) {
        RangeVar* r = (RangeVar*)lfirst(cell);
        Oid reloid = get_relname_relid(r->relname, PG_CATALOG_NAMESPACE);
        if (unlikely((long)(OidIsValid(reloid) && reloid < FirstBootstrapObjectId))) {
            ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                errmsg("permission denied: \"%s\" is a system catalog", r->relname),
                errhint("just normal compressed table can be shrinked")));
        }

        reloid = RangeVarGetRelid(r, AccessShareLock, true);
        if (!OidIsValid(reloid)) {
            ereport(ERROR, (errmsg("[shrink] relation %s%s%s does not exist.",
                                   r->schemaname == NULL ? "" : r->schemaname,
                                   r->schemaname == NULL ? "" : ".",
                                   r->relname)));
        }
        ShrinkCfsChunkRestore(reloid, AccessShareLock, shrink->nowait);
    }
}

static int128 EvaluateAutoIncrement(Relation rel, TupleDesc desc, AttrNumber attnum, Datum* value, bool* is_null)
{
    ConstrAutoInc* cons_autoinc = desc->constr->cons_autoinc;
    int128 autoinc;
    bool modify_value = false;

    if (*is_null) {
        autoinc = 0;
        modify_value = desc->attrs[attnum - 1].attnotnull;
    } else {
        autoinc = datum2autoinc(cons_autoinc, *value);
        modify_value = (autoinc == 0);
    }
     /*
      * By default, when datum is NULL/0, auto increase;
      * If NO_AUTO_VALUE_ON_ZERO is set, auto increase only when datum is NULL.
      */
    if (*is_null || (autoinc == 0 && !CheckPluginNoAutoValueOnZero())) {
        if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) {
            autoinc = tmptable_autoinc_nextval(rel->rd_rel->relfilenode, cons_autoinc->next);
        } else {
            autoinc = nextval_internal(cons_autoinc->seqoid);
        }
        if (modify_value) {
            *is_null = false;
            *value = autoinc2datum(cons_autoinc, autoinc);
        }
    }
    return autoinc;
}

static void SetRelAutoIncrement(Relation rel, TupleDesc desc, int128 autoinc)
{
    if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) {
        tmptable_autoinc_setval(rel->rd_rel->relfilenode, desc->constr->cons_autoinc->next, autoinc, true);
    } else {
        autoinc_setval(desc->constr->cons_autoinc->seqoid, autoinc, true);
    }
}

static void CopyTempAutoIncrement(Relation oldrel, Relation newrel)
{
    if (oldrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP ||
        oldrel->rd_rel->relfilenode == newrel->rd_rel->relfilenode ||
        !RelHasAutoInc(oldrel)) {
        return;
    }
    int128* value = find_tmptable_cache_autoinc(oldrel->rd_rel->relfilenode);
    if (value != NULL) {
        tmptable_autoinc_reset(newrel->rd_rel->relfilenode, *value);
    }
}

static void ATAlterCheckModifiyColumnRepeatedly(const AlterTableCmd* cmd, const List* tab_cmds)
{
    ListCell* tcmd = NULL;
    foreach (tcmd, tab_cmds) {
        AlterTableCmd* acmd = (AlterTableCmd*)lfirst(tcmd);
        if (acmd->name != NULL && strcmp(acmd->name, cmd->name) == 0) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                errmsg("Invalid modify column operation"),
                errdetail("cannot modify or change column \"%s\" twice", cmd->name)));
        }
    }
}

void CheckAutoIncrementDatatype(Oid typid, const char* colname)
{
    switch (typid) {
        case BOOLOID:
        case INT1OID:
        case INT2OID:
        case INT4OID:
        case INT8OID:
        case INT16OID:
        case FLOAT4OID:
        case FLOAT8OID:
            break;
        default:
            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                errmsg("The datatype of column '%s' does not support auto_increment", colname)));
            break;
    }
}

void CheckRelAutoIncrementIndex(Oid relid, LOCKMODE lockmode)
{
    List* idxoidlist = NULL;
    bool found = false;
    Relation rel = relation_open(relid, lockmode);
    AttrNumber autoinc_attnum = RelAutoIncAttrNum(rel);

    if (autoinc_attnum <= 0) {
        relation_close(rel, lockmode);
        return;
    }

    if (!rel->rd_rel->relhasindex) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
            (errmsg("auto_increment column must be defined as a unique or primary key"))));
    }

    idxoidlist = RelationGetIndexList(rel);
    relation_close(rel, lockmode);

    foreach_cell(l, idxoidlist) {
        Relation idxrel = index_open(lfirst_oid(l), AccessShareLock);
        Form_pg_index index = idxrel->rd_index;

        /* auto_increment column in dolphin support non-unique/primary index */
        if (IndexIsValid(index) && (u_sess->attr.attr_sql.dolphin || index->indisunique || index->indisprimary) &&
            index->indkey.values[0] == autoinc_attnum) {
            found = true;
            index_close(idxrel, AccessShareLock);
            break;
        }
        index_close(idxrel, AccessShareLock);
    }

    list_free(idxoidlist);
    if (!found) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
            (errmsg("auto_increment column must be defined as a unique or primary key"))));
    }
}

/* Daparse a expression and reparse it. Return new expression node. */
static Node* ReparseSingleRelExpr(Relation rel, Node* src_expr)
{
    Node* dst_expr = NULL;
    List* parsetree = NULL;
    SelectStmt* stmt = NULL;
    StringInfoData query_string;
    List* deparse_context = deparse_context_for(RelationGetRelationName(rel), RelationGetRelid(rel));
    char* expr_string = deparse_expression(src_expr, deparse_context, false, false);
    /* construct sql */
    initStringInfo(&query_string);
    appendStringInfo(&query_string, "SELECT %s ;", expr_string);
    /* parse sql */
    parsetree = raw_parser(query_string.data, NULL);
    /* get SelectStmt from parsetree */
    Assert(list_length(parsetree) == 1);
    dst_expr = (Node*)linitial(parsetree);
    Assert(IsA(dst_expr, SelectStmt));
    stmt = (SelectStmt*)dst_expr;
    /* get ResTarget from SelectStmt */
    Assert(list_length(stmt->targetList) == 1);
    dst_expr = (Node*)linitial(stmt->targetList);
    /* get reparsed expr from ResTarget */
    Assert(IsA(dst_expr, ResTarget));
    dst_expr = ((ResTarget*)dst_expr)->val;
    list_free(parsetree);
    pfree(query_string.data);
    pfree(expr_string);
    list_free_deep(deparse_context);
    return dst_expr;
}

/* Rebuild the generated expression because the data type of the column referenced in it has changed. */
static Node* RebuildGeneratedColumnExpr(Relation rel, AttrNumber gen_attnum)
{
    ParseState* pstate = NULL;
    RangeTblEntry *rte = NULL;
    Form_pg_attribute pgattr = &rel->rd_att->attrs[gen_attnum - 1];
    Node* gen_expr = build_column_default(rel, gen_attnum);

    Assert(gen_expr);
    /* reparse generated column expression */
    gen_expr = ReparseSingleRelExpr(rel, gen_expr);
    /* cook generated expression */
    pstate = make_parsestate(NULL);
    rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
    addRTEtoQuery(pstate, rte, false, true, true);
    gen_expr = cookDefault(pstate, gen_expr, pgattr->atttypid, pgattr->atttypmod, pgattr->attcollation,
        NameStr(pgattr->attname), ATTRIBUTE_GENERATED_STORED);
    /* readd pg_attrdef */
    RemoveAttrDefault(RelationGetRelid(rel), gen_attnum, DROP_RESTRICT, true, true);
    StoreAttrDefault(rel, gen_attnum, gen_expr, ATTRIBUTE_GENERATED_STORED, NULL, true);
    pfree(pstate);
    return gen_expr;
}

static void ATPrepAlterModifyColumn(List** wqueue, AlteredTableInfo* tab, Relation rel, bool recurse,
    bool recursing, AlterTableCmd* cmd, LOCKMODE lockmode)
{
    ColumnDef* def = (ColumnDef*)cmd->def;
    Node* tmp_expr = def->raw_default;
    char* tmp_name = cmd->name;
    if (def->generatedCol != ATTRIBUTE_GENERATED_STORED && def->generatedCol != ATTRIBUTE_GENERATED_PERSISTED &&
        (tmp_expr == NULL || !IsA(tmp_expr, AutoIncrement))) {
        ATPrepCheckDefault(tmp_expr);
    }

    def->raw_default = NULL;
    cmd->name = def->colname;
    /* For ATPrepAlterColumnType, raw_default is used to convert the original data to the target type. */
    ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd, lockmode);
    cmd->name = tmp_name;
    def->raw_default = tmp_expr;
}

char* GetCreateViewCommand(const char *rel_name, HeapTuple tup, Form_pg_class reltup, Oid pg_rewrite_oid, Oid view_oid,
    bool keep_star)
{
    StringInfoData buf;
    ViewInfoForAdd* view_info = NULL;
    char* view_options = NULL;
    bool isnull = true;
    const char* ns_name = quote_identifier(get_namespace_name(reltup->relnamespace));

    initStringInfo(&buf);
    if (reltup->relkind == RELKIND_MATVIEW) {
        appendStringInfo(&buf, "CREATE MATERIALIZED ");
    } else {
        appendStringInfo(&buf, "CREATE OR REPLACE ");
        if (reltup->relpersistence == RELPERSISTENCE_TEMP) {
            appendStringInfo(&buf, "TEMPORARY ");
        }
    }
    if (ns_name) {
        appendStringInfo(&buf, "VIEW %s.%s(", ns_name, quote_identifier(NameStr(reltup->relname)));
    } else {
        appendStringInfo(&buf, "VIEW %s(", quote_identifier(NameStr(reltup->relname)));
    }
    for (AttrNumber i = 1; i <= reltup->relnatts; i++) {
        char* attname = get_relid_attribute_name(view_oid, i);
        if (i == reltup->relnatts) {
            appendStringInfo(&buf, "%s) ", quote_identifier(attname));
        } else {
            appendStringInfo(&buf, "%s, ", quote_identifier(attname));
        }
    }
    Datum reloptions = SysCacheGetAttr(RELOID, tup, Anum_pg_class_reloptions, &isnull);
    if (!isnull) {
        Datum sep = CStringGetTextDatum(", ");
        Datum txt = OidFunctionCall2(F_ARRAY_TO_TEXT, reloptions, sep);
        view_options = TextDatumGetCString(txt);
    }
    if (view_options && strlen(view_options) > 0) {
        appendStringInfo(&buf, "WITH (%s) ", view_options);
    }
    pfree_ext(view_options);
    /* concat CREATE VIEW command with query */
    view_info = GetViewInfoFirstAfter(rel_name, pg_rewrite_oid, keep_star);
    if (view_info == NULL) {
        pfree_ext(buf.data);
        return NULL; /* should not happen */
    }
    appendStringInfo(&buf, "AS %s", view_info->query_string);
    pfree_ext(view_info->query_string);
    pfree_ext(view_info);
    return buf.data;
}

static void ATAlterRecordRebuildView(AlteredTableInfo* tab, Relation rel, Oid pg_rewrite_oid, bool type_changed)
{
    HeapTuple tup;
    char* view_def = NULL;
    Oid view_oid = get_rewrite_relid(pg_rewrite_oid, true);
    /* the view has been recorded */
    if (list_member_oid(tab->changedViewOids, view_oid) || !type_changed) {
        return;
    }
    /* get pg_class tuple by view oid */
    tup = SearchSysCache1(RELOID, ObjectIdGetDatum(view_oid));
    if (!HeapTupleIsValid(tup)) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
            errmsg("Invalid modify column operation"),
            errdetail("modify or change a column used by materialized view or rule is not supported")));
    }
    Form_pg_class reltup = (Form_pg_class)GETSTRUCT(tup);
    if (reltup->relkind != RELKIND_VIEW) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
            errmsg("Invalid modify column operation"),
            errdetail("modify or change a column used by materialized view or rule is not supported")));
    }
    /* print CREATE VIEW command */
    view_def = GetCreateViewCommand(NameStr(rel->rd_rel->relname), tup, reltup, pg_rewrite_oid, view_oid);
    ReleaseSysCache(tup);
    if (view_def) {
        /* record it */
        tab->changedViewOids = lappend_oid(tab->changedViewOids, view_oid);
        tab->changedViewDefs = lappend(tab->changedViewDefs, view_def);
    }
}

static Node* CookRlspolicyQual(Relation rel, Node* src_qual)
{
    ParseState* pstate = make_parsestate(NULL);
    RangeTblEntry* rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, false);
    addRTEtoQuery(pstate, rte, false, true, true);
    /* Transform expr clause */
    Node *cooked_qual = transformWhereClause(pstate, src_qual, EXPR_KIND_POLICY, "POLICY");
    /* Take care of collations */
    assign_expr_collations(pstate, cooked_qual);
    pfree(pstate);
    return cooked_qual;
}

/*
 * Find a row level security policy by oid. Rebuild qual expression tree by replacing Var node;
 */
static void ATAlterModifyRebuildRlspolicyExpr(Relation rel, Oid pg_rlspolicy_oid)
{
    Relation rlsp_rel;
    ScanKeyData scankey;
    SysScanDesc scan;
    HeapTuple tuple;
    Datum values[Natts_pg_rlspolicy] = {0};
    bool nulls[Natts_pg_rlspolicy] = {0};
    bool replaces[Natts_pg_rlspolicy] = {0};
    Datum datum;
    bool isnull = false;
    char* polqual = NULL;
    Node *expr = NULL;

    ScanKeyInit(&scankey, ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ,
        ObjectIdGetDatum(pg_rlspolicy_oid));
    rlsp_rel = heap_open(RlsPolicyRelationId, RowExclusiveLock);
    scan = systable_beginscan(rlsp_rel, PgRlspolicyOidIndex, true, NULL, 1, &scankey);
    tuple = systable_getnext(scan);
    if (HeapTupleIsValid(tuple)) {
        datum = heap_getattr(tuple, Anum_pg_rlspolicy_polqual, RelationGetDescr(rlsp_rel), &isnull);
        if (!isnull) {
            polqual = TextDatumGetCString(datum);
            /* rebuild polqual expression */
            expr = (Node*)stringToNode_skip_extern_fields(polqual);
            expr = ReparseSingleRelExpr(rel, expr);
            expr = CookRlspolicyQual(rel, expr);
            pfree_ext(polqual);
            /* update polqual */
            polqual = nodeToString(expr);
            values[Anum_pg_rlspolicy_polqual - 1] = CStringGetTextDatum(polqual);
            replaces[Anum_pg_rlspolicy_polqual - 1] = true;
            tuple = heap_modify_tuple(tuple, RelationGetDescr(rlsp_rel), values, nulls, replaces);
            simple_heap_update(rlsp_rel, &tuple->t_self, tuple);
            CatalogUpdateIndexes(rlsp_rel, tuple);
        }
    }

    systable_endscan(scan);
    heap_close(rlsp_rel, RowExclusiveLock);
    pfree(DatumGetPointer(values[Anum_pg_rlspolicy_polqual - 1]));
    pfree_ext(polqual);
}

static void ATHandleClassObjectDependOnModifiedColumn(AlteredTableInfo* tab, Relation dep_rel,
    ObjectAddress* object)
{
    char relKind = get_rel_relkind(object->objectId);

    if (relKind == RELKIND_INDEX || relKind == RELKIND_GLOBAL_INDEX) {
        Assert(object->objectSubId == 0);
        Oid refobjid;
        if (!list_member_oid(tab->changedConstraintOids, object->objectId) &&
            CheckIndexIsConstraint(dep_rel, object->objectId, &refobjid)) {
            tab->changedConstraintOids = lappend_oid(tab->changedConstraintOids, refobjid);
            tab->changedConstraintDefs =
                lappend(tab->changedConstraintDefs, pg_get_constraintdef_string(refobjid));
        } else if (!list_member_oid(tab->changedIndexOids, object->objectId)) {
            /*
             * Question: alter table set datatype and table index execute concurrently, data inconsistency
             * occurs. The index file is deleted and metadata is left. Because the data type is not locked
             * after modification, which ultimately leads to could not open file. Alter table column set
             * datatype maybe trigger index operation but index is not locked. When the index data is
             * inconsistent, we can use"reindex index" to repair the index.
             * Solution: we should lock index at the beginning.The ACCESS_EXCLUSIVE_LOCK for index is used
             * because we think ACCESS_EXCLUSIVE_LOCK for data table will block any operation and index
             * will be not used to query data. This operation will block individual index operations,
             * such as reindex index\set index tablespace.
             * Testcase: alter table row_table alter column col_varchar set data type text,alter column
             * col_smallint set data type bigint + alter index idx set tablespace.
             */
            LockRelationOid(object->objectId, AccessExclusiveLock);
            tab->changedIndexOids = lappend_oid(tab->changedIndexOids, object->objectId);
            tab->changedIndexDefs = lappend(tab->changedIndexDefs, pg_get_indexdef_string(object->objectId));
        }
    } else if (RELKIND_IS_SEQUENCE(relKind)) {
        /*
         * This must be a SERIAL or AUTO_INCREMENT column's sequence.  We need not do anything to it.
         */
        Assert(object->objectSubId == 0);
    } else if (relKind == RELKIND_RELATION && object->objectSubId != 0 &&
        GetGenerated(object->objectId, object->objectSubId)) {
        if (!list_member_int(tab->changedGeneratedCols, object->objectSubId)) {
            tab->changedGeneratedCols = lappend_int(tab->changedGeneratedCols, object->objectSubId);
        }
    } else {
        /* Not expecting any other direct dependencies... */
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
            errmsg("unexpected object depending on column: %s", getObjectDescription((object)))));
    }
}

static void ATAlterRecordRebuildConstraint(AlteredTableInfo* tab, Oid constraint_oid, Form_pg_depend found_dep)
{
    if (!list_member_oid(tab->changedConstraintOids, constraint_oid)) {
        char* defstring = pg_get_constraintdef_string(constraint_oid);
        /*
         * Put NORMAL dependencies at the front of the list and
         * AUTO dependencies at the back.  This makes sure that
         * foreign-key constraints depending on this column will
         * be dropped before unique or primary-key constraints of
         * the column; which we must have because the FK
         * constraints depend on the indexes belonging to the
         * unique constraints.
         */
        if (found_dep->deptype == DEPENDENCY_NORMAL) {
            tab->changedConstraintOids = lcons_oid(constraint_oid, tab->changedConstraintOids);
            tab->changedConstraintDefs = lcons(defstring, tab->changedConstraintDefs);
        } else {
            tab->changedConstraintOids = lappend_oid(tab->changedConstraintOids, constraint_oid);
            tab->changedConstraintDefs = lappend(tab->changedConstraintDefs, defstring);
        }
    }
}

static void ATAlterRecordRebuildTrigger(AlteredTableInfo* tab, Oid trigger_oid, bool type_changed)
{
    if (!list_member_oid(tab->changedTriggerOids, trigger_oid) && type_changed) {
        char* defstring = pg_get_triggerdef_string(trigger_oid);
        tab->changedTriggerOids = lappend_oid(tab->changedTriggerOids, trigger_oid);
        tab->changedTriggerDefs = lappend(tab->changedTriggerDefs, defstring);
    }
}

static void ATAlterRecordRebuildRlsp(AlteredTableInfo* tab, Oid rlsp_oid, bool type_changed)
{
    if (!list_member_oid(tab->changedRLSPolicies, rlsp_oid) && type_changed) {
        tab->changedRLSPolicies = lappend_oid(tab->changedRLSPolicies, rlsp_oid);
    }
}

static void ATHandleObjectsDependOnModifiedColumn(AlteredTableInfo* tab, Relation rel,
    Form_pg_attribute pg_attr, AttrNumber attnum, bool type_changed)
{
    ScanKeyData key[3];
    SysScanDesc scan;
    HeapTuple dep_tup;
    Relation dep_rel = heap_open(DependRelationId, RowExclusiveLock);

    ScanKeyInit(
        &key[0], Anum_pg_depend_refclassid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationRelationId));
    ScanKeyInit(
        &key[1], Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(rel)));
    ScanKeyInit(&key[2], Anum_pg_depend_refobjsubid, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum((int32)attnum));

    scan = systable_beginscan(dep_rel, DependReferenceIndexId, true, NULL, 3, key);

    while (HeapTupleIsValid(dep_tup = systable_getnext(scan))) {
        Form_pg_depend found_dep = (Form_pg_depend)GETSTRUCT(dep_tup);
        ObjectAddress found_object;

        /* We don't expect any PIN dependencies on columns */
        if (found_dep->deptype == DEPENDENCY_PIN) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                    errmsg("Invalid modify column operation"),
                    errdetail("cannot modify or change a pinned column")));
        }

        found_object.classId = found_dep->classid;
        found_object.objectId = found_dep->objid;
        found_object.objectSubId = found_dep->objsubid;
        switch (getObjectClass(&found_object)) {
            case OCLASS_CLASS:
                ATHandleClassObjectDependOnModifiedColumn(tab, dep_rel, &found_object);
                break;

            case OCLASS_CONSTRAINT:
                Assert(found_object.objectSubId == 0);
                ATAlterRecordRebuildConstraint(tab, found_object.objectId, found_dep);
                break;

            case OCLASS_REWRITE:
                ATAlterRecordRebuildView(tab, rel, found_object.objectId, type_changed);
                break;

            case OCLASS_TRIGGER:
                Assert(found_object.objectSubId == 0);
                ATAlterRecordRebuildTrigger(tab, found_object.objectId, type_changed);
                break;

            case OCLASS_RLSPOLICY:
                Assert(found_object.objectSubId == 0);
                ATAlterRecordRebuildRlsp(tab, found_object.objectId, type_changed);
                break;

            case OCLASS_DEFAULT:
                break;

            case OCLASS_CL_CACHED_COLUMN:
                ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                    errmsg("Invalid modify column operation"),
                    errdetail("modify or change encrypted column is not supported")));
                break;

            case OCLASS_PROC:
            case OCLASS_TYPE:
            case OCLASS_CAST:
            case OCLASS_COLLATION:
            case OCLASS_CONVERSION:
            case OCLASS_LANGUAGE:
            case OCLASS_LARGEOBJECT:
            case OCLASS_OPERATOR:
            case OCLASS_OPCLASS:
            case OCLASS_OPFAMILY:
            case OCLASS_AMOP:
            case OCLASS_AMPROC:
            case OCLASS_SCHEMA:
            case OCLASS_TSPARSER:
            case OCLASS_TSDICT:
            case OCLASS_TSTEMPLATE:
            case OCLASS_TSCONFIG:
            case OCLASS_ROLE:
            case OCLASS_DATABASE:
            case OCLASS_TBLSPACE:
            case OCLASS_FDW:
            case OCLASS_FOREIGN_SERVER:
            case OCLASS_USER_MAPPING:
            case OCLASS_DEFACL:
            case OCLASS_EXTENSION:
            case OCLASS_DATA_SOURCE:
            case OCLASS_GLOBAL_SETTING_ARGS:
            case OCLASS_GS_CL_PROC:
                ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("unexpected object depending on column: %s", getObjectDescription(&found_object))));
                break;
            default:
                ereport(ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                    errmsg("unrecognized object class: %u", found_object.classId)));
        }
    }

    systable_endscan(scan);
    DelDependencONDataType(rel, dep_rel, pg_attr);
    heap_close(dep_rel, RowExclusiveLock);
}

static NewColumnValue* findNewColumnValue(AlteredTableInfo* tab, const char* col_name)
{
    NewColumnValue* result = NULL;

    foreach_cell(vcell, tab->newvals) {
        result = (NewColumnValue*)lfirst(vcell);
        if (result->col_name != NULL && strcmp(col_name, result->col_name) == 0) {
            return result;
        }
    }
    return NULL;
}

static int128 getAutoIncrementValue(Relation rel, ColumnDef* def, AttrNumber attnum)
{
    AttrNumber autoinc_attnum = RelAutoIncAttrNum(rel);
    if (autoinc_attnum > 0) {
        if (autoinc_attnum == attnum) {
            if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) {
                return *rel->rd_att->constr->cons_autoinc->next;
            } else {
                return autoinc_get_nextval(RelAutoIncSeqOid(rel));
            }
        } else if (def->raw_default && IsA(def->raw_default, AutoIncrement)) {
            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                (errmsg("Incorrect table definition, there can be only one auto_increment column"))));
        }
    }
    return 0;
}

static void ATAlterModifyAutoinColumn(AlteredTableInfo* tab, Relation rel, ColumnDef* def,
    AttrNumber attnum, int128 autoinc)
{
    if (autoinc > 0) { /* Column is auto_increment before modified, keep auto_increment value. */
        if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) {
            tmptable_autoinc_setval(
                rel->rd_rel->relfilenode, rel->rd_att->constr->cons_autoinc->next, autoinc, false);
        } else {
            autoinc_setval(RelAutoIncSeqOid(rel), autoinc, false);
        }
    } else { /* Column is not auto_increment before modified, need rewrite table later. */
        Expr* defval = NULL;
        NewColumnValue* newval = findNewColumnValue(tab, def->colname);
        if (newval != NULL) {
            newval->is_autoinc = true;
            tab->rewrite = true;
        } else {
            defval = (Expr*)build_column_default(rel, attnum);
            Assert(defval != NULL);
            ATExecAppendDefValExpr(attnum, defval, tab, def, true, false);
        }
    }
}

static void ATAlterModifyColumnDefault(AlteredTableInfo* tab, Relation rel, ColumnDef* def,
    AttrNumber attnum, int128 autoinc)
{
    RawColumnDefault raw_col_def;

    raw_col_def.attnum = attnum;
    raw_col_def.raw_default = (Node*)copyObject(def->raw_default);
    raw_col_def.generatedCol = def->generatedCol;
    raw_col_def.update_expr = (Node*)copyObject(def->update_default);
    (void)AddRelationNewConstraints(rel, list_make1(&raw_col_def), NIL, false, true);
    CommandCounterIncrement();
    /* AUTO_INCREMENT and GENERATED COLUMN need rewrite table */
    if (RelAutoIncAttrNum(rel) == attnum) {
        ATAlterModifyAutoinColumn(tab, rel, def, attnum, autoinc);
        if (list_length(tab->changedGeneratedCols) > 0) {
            ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                (errmsg("generated column cannot refer to auto_increment column"))));
        }
    } else if (def->generatedCol == ATTRIBUTE_GENERATED_STORED || def->generatedCol == ATTRIBUTE_GENERATED_PERSISTED) {
        if (list_length(tab->changedGeneratedCols) > 0) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                errmsg("Invalid modify column operation"),
                errdetail("A generated column cannot reference another generated column.")));
        }

        NewColumnValue* newval = findNewColumnValue(tab, def->colname);
        Expr* defval = (Expr*)build_column_default(rel, attnum);
        Assert(defval != NULL);
        if (newval != NULL) {
            newval->expr = expression_planner(defval);
            newval->is_generated = true;
            tab->rewrite = true;
        } else {
            ATExecAppendDefValExpr(attnum, defval, tab, def, false, false);
        }
    }
}

/*
 * Rebulid generated column expression and update pg_attrdef.
 * Set rewrite if need.
 */
static void ATRewriteChangedGeneratedColumn(AlteredTableInfo* tab, Relation rel, AttrNumber gen_attnum)
{
    char *gen_col_name = NameStr(rel->rd_att->attrs[gen_attnum - 1].attname);
    NewColumnValue* newval = findNewColumnValue(tab, gen_col_name);
    Expr* defval = (Expr*)RebuildGeneratedColumnExpr(rel, gen_attnum);
    Assert(defval != NULL);
    /*
     * Rebuilt generated column expression does not affect the data of the generated column.
     * Replace the expression only when the generated column needs to be rewritten.
     */
    if (newval == NULL) {
        newval = (NewColumnValue*)palloc0(sizeof(NewColumnValue));
        newval->attnum = gen_attnum;
        newval->expr = expression_planner(defval);
        newval->is_generated = true;
        newval->is_autoinc = false;
        newval->generate_attnum = 0;
        newval->col_name = pstrdup(gen_col_name);
        tab->newvals = lappend(tab->newvals, newval);
        tab->rewrite = true;
    } else if (newval->is_generated) {
        newval->expr = expression_planner(defval);
        tab->rewrite = true;
    }
}

static bool ModifiedColumnIsPrimaryKey(AlteredTableInfo* tab, AttrNumber attrnum)
{
    foreach_cell(cell, tab->changedConstraintOids) {
        Datum* keys = NULL;
        Datum conkey_datum;
        int key_count;
        bool isnull = false;
        Oid constraint_oid = lfirst_oid(cell);
        HeapTuple tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraint_oid));
        if (!HeapTupleIsValid(tuple)) {
            ereport(ERROR,
                (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                errmsg("cache lookup failed for constraint %u", constraint_oid)));
        }

        if (((Form_pg_constraint)GETSTRUCT(tuple))->contype != CONSTRAINT_PRIMARY) {
            ReleaseSysCache(tuple);
            continue;
        }

        conkey_datum = SysCacheGetAttr(CONSTROID, tuple, Anum_pg_constraint_conkey, &isnull);
        if (isnull) {
            ReleaseSysCache(tuple);
            continue;
        }

        deconstruct_array(DatumGetArrayTypeP(conkey_datum), INT2OID, sizeof(int16), true, 's', &keys, NULL, &key_count);
        for (int i = 0; i < key_count; i++) {
            if (DatumGetInt16(keys[i]) == attrnum) {
                pfree_ext(keys);
                ReleaseSysCache(tuple);
                return true;
            }
        }

        pfree_ext(keys);
        ReleaseSysCache(tuple);
    }

    return false;
}

static void ATExecAlterModifyColumn(AlteredTableInfo* tab, Relation rel, AlterTableCmd* cmd)
{
    ColumnDef* def = (ColumnDef*)cmd->def;
    AttrNumber attnum;
    HeapTuple attr_tuple;
    HeapTuple type_tuple;
    Form_pg_attribute pg_attr;
    Form_pg_type pg_type;
    Relation att_rel;
    Oid typid;
    int32 typmod = -1;
    Oid collid = InvalidOid;
    AclResult aclresult;
    int128 autoinc = 0;
    char* col_name = def->colname;
    bool type_changed = false;
    bool is_first_after = cmd->is_first || cmd->after_name != NULL;

    att_rel = heap_open(AttributeRelationId, RowExclusiveLock);
    attnum = get_attnum(RelationGetRelid(rel), col_name);
    if (attnum == InvalidAttrNumber) {
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_COLUMN),
                errmsg("column \"%s\" of relation \"%s\" does not exist", col_name, RelationGetRelationName(rel))));
    }

    /* Check and get new type and collation */
    type_tuple = typenameType(NULL, def->typname, &typmod);
    pg_type = (Form_pg_type)GETSTRUCT(type_tuple);
    typid = HeapTupleGetOid(type_tuple);
    aclresult = pg_type_aclcheck(typid, GetUserId(), ACL_USAGE);
    if (aclresult != ACLCHECK_OK) {
        aclcheck_error_type(aclresult, typid);
    }
    collid = GetColumnDefCollation(NULL, def, typid);
    CheckAttributeType(col_name, typid, collid, list_make1_oid(rel->rd_rel->reltype), false);

    /* Check and save AUTO_INCREMENT */
    autoinc = getAutoIncrementValue(rel, def, attnum);

    /* drop old default */
    RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false, true);

    /* Look up the target column */
    attr_tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), col_name);
    if (!HeapTupleIsValid(attr_tuple)) {
        ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN),
                errmsg("column \"%s\" of relation \"%s\" does not exist", col_name, RelationGetRelationName(rel))));
    }
    pg_attr = (Form_pg_attribute)GETSTRUCT(attr_tuple);
    type_changed = (pg_attr->atttypid != typid || pg_attr->atttypmod != typmod || pg_attr->attcollation != collid);
    /* Check column partkey */
    if (is_partition_column(rel, attnum)) {
        if (type_changed) {
            ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION),
                errmsg("Invalid modify column operation"),
                errdetail("modify or change partition key column is not supported")));
        } else if (def->generatedCol) {
            ereport(ERROR,
                (errmodule(MOD_GEN_COL), errcode(ERRCODE_INVALID_OPERATION),
                errmsg("Invalid modify column operation"),
                errdetail("cannot modify or change a partition key column as a generated column")));
        }
    }

    /* drop comment on column */
    DeleteComments(RelationGetRelid(rel), RelationRelationId, attnum);
    /* Working with objects that depend on the column being modified. */
    ATHandleObjectsDependOnModifiedColumn(tab, rel, pg_attr, attnum, type_changed);
    /* Primary key column must be not null. */
    def->is_not_null = def->is_not_null ? def->is_not_null : ModifiedColumnIsPrimaryKey(tab, attnum);
    if (!pg_attr->attnotnull && def->is_not_null) {
        tab->new_notnull = true;
    }

    if (is_first_after || tab->is_first_after) {
        UpdateNewvalsAttnum(tab, rel, cmd, col_name);
    }

    pg_attr->atttypid = typid;
    pg_attr->attlen = pg_type->typlen;
    pg_attr->atttypmod = typmod;
    pg_attr->attbyval = pg_type->typbyval;
    pg_attr->attndims = list_length(def->typname->arrayBounds);
    pg_attr->attstorage = pg_type->typstorage;
    pg_attr->attalign = pg_type->typalign;
    pg_attr->attcollation = collid;
    pg_attr->attnotnull = def->is_not_null;
    pg_attr->attislocal = def->is_local;
    pg_attr->attinhcount = def->inhcount;
    pg_attr->atthasdef = false;
    ReleaseSysCache(type_tuple);
    simple_heap_update(att_rel, &attr_tuple->t_self, attr_tuple);
    CatalogUpdateIndexes(att_rel, attr_tuple);
    heap_close(att_rel, RowExclusiveLock);

    /* Install dependencies on new datatype and collation */
    add_column_datatype_dependency(RelationGetRelid(rel), attnum, typid);
    add_column_collation_dependency(RelationGetRelid(rel), attnum, collid);

    /*  Drop any pg_statistic entry for the column, since it's now wrong type */
    if (RELATION_IS_GLOBAL_TEMP(rel)) {
        remove_gtt_att_statistic(RelationGetRelid(rel), attnum);
    } else {
        RemoveStatistics<'c'>(RelationGetRelid(rel), attnum);
    }

    if (def->raw_default || def->update_default) {
        CommandCounterIncrement();
        ATAlterModifyColumnDefault(tab, rel, def, attnum, autoinc);
    }

    foreach_cell(attcell, tab->changedGeneratedCols) {
        CommandCounterIncrement();
        ATRewriteChangedGeneratedColumn(tab, rel, (AttrNumber)lfirst_int(attcell));
    }
    list_free_ext(tab->changedGeneratedCols);

    foreach_cell(rlspcell,  tab->changedRLSPolicies) {
        CommandCounterIncrement();
        ATAlterModifyRebuildRlspolicyExpr(rel, lfirst_oid(rlspcell));
    }
    list_free_ext(tab->changedRLSPolicies);

    /* recreate views */
    foreach_cell(view_def_cell, tab->changedViewDefs) {
        CommandCounterIncrement();
        char* cmd_str = (char*)lfirst(view_def_cell);
        List* raw_parsetree_list = raw_parser(cmd_str);
        Node* stmt = (Node*)linitial(raw_parsetree_list);
        Assert(IsA(stmt, ViewStmt));
        DefineView((ViewStmt*)stmt, cmd_str);
    }
    list_free_ext(tab->changedViewOids);
    list_free_ext(tab->changedViewDefs);

    if (cmd->is_first || cmd->after_name != NULL) {
        tab->is_first_after = true;
        CommandCounterIncrement();

        AlterColumnToFirstAfter(tab, rel, cmd, attnum);
    }
}

static Node* RecookAutoincAttrDefault(Relation rel, int attrno, Oid targettype, int targettypmod)
{
    TupleDesc rd_att = rel->rd_att;
    AutoIncrement* aexpr = NULL;
    Node* expr = NULL;
    AttrDefault* defval = rd_att->constr->defval;
    int ndef = rd_att->constr->num_defval;

    CheckAutoIncrementDatatype(targettype, rd_att->attrs[attrno - 1].attname.data);
    while (--ndef >= 0) {
        if (attrno == defval[ndef].adnum) {
            expr = (Node*)stringToNode_skip_extern_fields(defval[ndef].adbin);
            break;
        }
    }
    Assert(expr != NULL);
    Assert(IsA(expr, AutoIncrement));
    aexpr = (AutoIncrement*)expr;
    (void)find_coercion_pathway(INT16OID, targettype, COERCION_ASSIGNMENT, &aexpr->autoincin_funcid);
    (void)find_coercion_pathway(targettype, INT16OID, COERCION_ASSIGNMENT, &aexpr->autoincout_funcid);
    aexpr->expr = strip_implicit_coercions(aexpr->expr);
    aexpr->expr = coerce_to_target_type(NULL, /* no UNKNOWN params */
        aexpr->expr,
        exprType(aexpr->expr),
        targettype,
        targettypmod,
        COERCION_ASSIGNMENT,
        COERCE_IMPLICIT_CAST,
        NULL,
        NULL,
        -1);
    return (Node*)aexpr;
}

/*
 * findout view which depend on proc, then rebuild it. It will check view's
 * column type and name(checkViewTupleDesc) when rebuild the view.
 */
void RebuildDependViewForProc(Oid proc_oid)
{
    ScanKeyData key[2];
    SysScanDesc scan = NULL;
    HeapTuple tup = NULL;
    List *oid_list = NIL;

    /* open pg_depend to find which view depend on this proc */
    Relation depRel = heap_open(DependRelationId, AccessShareLock);

    ScanKeyInit(&key[0], Anum_pg_depend_refclassid, BTEqualStrategyNumber, F_OIDEQ,
        ObjectIdGetDatum(ProcedureRelationId));
    ScanKeyInit(&key[1], Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(proc_oid));

    scan = systable_beginscan(depRel, DependReferenceIndexId, true, NULL, 2, key);
    while (HeapTupleIsValid((tup = systable_getnext(scan)))) {
        Form_pg_depend depform = (Form_pg_depend)GETSTRUCT(tup);

        if (depform->classid == RewriteRelationId && depform->deptype == DEPENDENCY_NORMAL) {
            oid_list = lappend_oid(oid_list, depform->objid);
        }
    }
    systable_endscan(scan);
    heap_close(depRel, AccessShareLock);

    /* rebuild view by rewrite oid */
    ListCell *cell = NULL;
    foreach(cell, oid_list) {
        Oid objid = lfirst_oid(cell);
        Oid view_oid = get_rewrite_relid(objid, true);
        if (!OidIsValid(view_oid)) {
            continue;
        }
        tup = SearchSysCache1(RELOID, ObjectIdGetDatum(view_oid));
        if (!HeapTupleIsValid(tup)) {
            continue;
        }
        Form_pg_class reltup = (Form_pg_class)GETSTRUCT(tup);
        if (reltup->relkind != RELKIND_VIEW) {
            ReleaseSysCache(tup);
            continue;
        }

        /* get rebuild view sql */
        char *view_def = GetCreateViewCommand(NameStr(reltup->relname), tup, reltup, objid, view_oid);
        ReleaseSysCache(tup);

        CommandCounterIncrement();
        List* raw_parsetree_list = raw_parser(view_def);
        Node* stmt = (Node*)linitial(raw_parsetree_list);
        Assert(IsA(stmt, ViewStmt));

        PG_TRY();
        {
            DefineView((ViewStmt*)stmt, view_def);
        }
        PG_CATCH();
        {
            /* If there is an error in rebuilding the view, ignore it and set it invalid. */
            InvalidateDependView(view_oid, OBJECT_TYPE_VIEW);
        }
        PG_END_TRY();
        pfree(view_def);
        list_free(raw_parsetree_list);
    }
    list_free_ext(oid_list);
}

static void check_unsupported_charset_for_column(Oid collation, const char* col_name)
{
    if (!OidIsValid(collation)) {
        return;
    }

    int attcharset = get_valid_charset_by_collation(collation);
    if (attcharset != PG_SQL_ASCII && attcharset != GetDatabaseEncoding()) {
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
            errmsg("difference between the charset of column %s and the database encoding has not supported",
                col_name)));
    }
}

bool IsComputedColumn(Oid adrelid, int2 asnum)
{
    Relation    attrdef;
    ScanKeyData keys[2];
    SysScanDesc scan;
    HeapTuple   tup;
    bool        res = false;

    attrdef = relation_open(AttrDefaultRelationId, AccessShareLock);
    ScanKeyInit(&keys[0],
                Anum_pg_attrdef_adrelid,
                BTEqualStrategyNumber,
                F_OIDEQ,
                ObjectIdGetDatum(adrelid));
    ScanKeyInit(&keys[1],
                Anum_pg_attrdef_adnum,
                BTEqualStrategyNumber,
                F_INT2EQ,
                Int16GetDatum(asnum));
    scan = systable_beginscan(attrdef, AttrDefaultIndexId, true, NULL, 2, keys);
    if (HeapTupleIsValid(tup = systable_getnext(scan))) {
        bool isnull = false;
        char generatedCol = '\0';
        Datum adgencol = fastgetattr(tup, Anum_pg_attrdef_adgencol, attrdef->rd_att, &isnull);
        if (!isnull) {
            generatedCol = DatumGetChar(adgencol);
        }
        res = (generatedCol == ATTRIBUTE_GENERATED_PERSISTED);
    }

    systable_endscan(scan);
    relation_close(attrdef, AccessShareLock);
    return res;
}