24e8d47c创建于 2025年5月21日历史提交
/* -------------------------------------------------------------------------
 *
 * heap.cpp
 *	  code to create and destroy openGauss heap relations
 *
 * 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/common/backend/catalog/heap.cpp
 *
 *
 * INTERFACE ROUTINES
 *		heap_create()			- Create an uncataloged heap relation
 *		heap_create_with_catalog() - Create a cataloged relation
 *		heap_drop_with_catalog() - Removes named relation from catalogs
 *
 * NOTES
 *	  this code taken from access/heap/create.c, which contains
 *	  the old heap_create_with_catalog, amcreate, and amdestroy.
 *	  those routines will soon call these routines using the function
 *	  manager,
 *	  just like the poorly named "NewXXX" routines do.	The
 *	  "New" routines are all going to die soon, once and for all!
 *		-cim 1/13/91
 *
 * -------------------------------------------------------------------------
 */
#include "postgres.h"
#include "fmgr.h"
#include "knl/knl_variable.h"

#include "access/cstore_delta.h"
#include "access/reloptions.h"
#include "access/sysattr.h"
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "access/multixact.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/gs_matview.h"
#include "catalog/gs_obsscaninfo.h"
#include "catalog/heap.h"
#include "catalog/index.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_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_object.h"
#include "catalog/pg_obsscaninfo.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_proc.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_statistic_history.h"
#include "catalog/pg_statistic_lock.h"
#include "catalog/pg_synonym.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
#include "catalog/pg_type_fn.h"
#include "catalog/pg_uid_fn.h"
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
#include "catalog/storage_gtt.h"
#include "catalog/pg_subscription_rel.h"
#include "commands/matview.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "commands/typecmds.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/params.h"
#include "optimizer/var.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
#include "parser/parse_utilcmd.h"
#include "parser/parsetree.h"
#include "pgxc/groupmgr.h"
#include "storage/buf/buf.h"
#include "storage/predicate.h"
#include "storage/page_compression.h"
#include "storage/buf/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/smgr/smgr.h"
#include "storage/smgr/segment.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/extended_statistics.h"
#include "utils/fmgroids.h"
#include "utils/hotkey.h"
#include "utils/int8.h"
#include "utils/int16.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/partitionmap.h"
#include "utils/partitionmap_gs.h"
#include "utils/partitionkey.h"
#include "utils/partcache.h"
#include "utils/rel.h"
#include "utils/rel_gs.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "access/heapam.h"
#include "foreign/fdwapi.h"
#include "instruments/generate_report.h"
#include "catalog/gs_encrypted_columns.h"
#include "catalog/gs_dependencies_fn.h"
#include "catalog/gs_sql_limit.h"
#include "utils/plpgsql.h"
#include "tcop/autonomoustransaction.h"

#ifdef PGXC
#include "catalog/pgxc_class.h"
#include "catalog/pgxc_node.h"
#include "catalog/pgxc_slice.h"
#include "commands/comment.h"
#include "pgxc/locator.h"
#include "pgxc/groupmgr.h"
#include "pgxc/nodemgr.h"
#include "pgxc/pgxc.h"
#include "pgxc/pgxcnode.h"
#include "replication/bcm.h"
#endif

#include "client_logic/client_logic.h"

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


static void AddNewRelationTuple(Relation pg_class_desc, Relation new_rel_desc, Oid new_rel_oid, Oid new_type_oid,
    Oid reloftype, Oid relowner, char relkind, char relpersistence, Datum relacl, Datum reloptions,
    int2vector* bucketcol, bool ispartrel);
static oidvector* BuildIntervalTablespace(const IntervalPartitionDefState* intervalPartDef);
static void deletePartitionTuple(Oid part_id);
static void addNewPartitionTuplesForPartition(Relation pg_partition_rel,
    Oid relid, Oid reltablespace,
    Oid bucketOid, PartitionState* partTableState, Oid ownerid,
    Datum reloptions, const TupleDesc tupledesc, char strategy, StorageType storage_type, LOCKMODE partLockMode,
    PartitionExprKeyInfo *partExprKeyInfo = NULL);
static void addNewPartitionTupleForTable(Relation pg_partition_rel, const char* relname, const Oid reloid,
    const Oid reltablespaceid, const TupleDesc reltupledesc, const PartitionState* partTableState, Oid ownerid,
    Datum reloptions, PartitionExprKeyInfo *partExprKeyInfo = NULL);

static void addNewPartitionTupleForValuePartitionedTable(Relation pg_partition_rel, const char* relname,
    const Oid reloid, const Oid reltablespaceid, const TupleDesc reltupledesc, const PartitionState* partTableState,
    Datum reloptions);

static void heapDropPartitionTable(Relation relation);

static ObjectAddress AddNewRelationType(const char* typeName, Oid typeNamespace, Oid new_rel_oid, char new_rel_kind, Oid ownerid,
    Oid new_row_type, Oid new_array_type, Oid typbasetype = InvalidOid);
static void RelationRemoveInheritance(Oid relid);
static Oid StoreRelCheck(
    Relation rel, const char* ccname, Node* expr, bool is_validated, bool is_local, int inhcount, bool is_no_inherit,
    bool is_deferrable = false, bool is_deferred = false, bool is_disable = false);
static void StoreConstraints(Relation rel, List* cooked_constraints);
static bool MergeWithExistingConstraint(
    Relation rel, char* ccname, Node* expr, bool allow_merge, bool is_local, bool is_no_inherit);
static void SetRelationNumChecks(Relation rel, int numchecks);
static Node* cookConstraint(ParseState* pstate, Node* raw_constraint, char* relname);
static List* insert_ordered_unique_oid(List* list, Oid datum);
static void InitPartitionDef(Partition newPartition, Oid partOid, char strategy);
static void InitSubPartitionDef(Partition newPartition, Oid partOid, char strategy);
static bool binary_upgrade_is_next_part_pg_partition_oid_valid();
static Oid binary_upgrade_get_next_part_pg_partition_oid();
bool binary_upgrade_is_next_part_toast_pg_class_oid_valid();
static Oid binary_upgrade_get_next_part_toast_pg_class_oid();

static Oid binary_upgrade_get_next_part_toast_pg_class_rfoid();
static Oid binary_upgrade_get_next_part_pg_partition_rfoid();
static void LockSeqConstraints(Relation rel, List* Constraints);
static bool ReferenceGenerated(const List *rawdefaultlist, AttrNumber attnum);
static bool CheckNestedGeneratedWalker(Node *node, ParseState *context);
static bool CheckNestedGenerated(ParseState *pstate, Node *node);
extern void getErrorTableFilePath(char* buf, int len, Oid databaseid, Oid reid);
extern void make_tmptable_cache_key(Oid relNode);

#define RELKIND_IN_RTM (relkind == RELKIND_RELATION || relkind == RELKIND_TOASTVALUE || relkind == RELKIND_MATVIEW)

static RangePartitionDefState *MakeRangeDefaultSubpartition(PartitionState *partitionState, char *partitionName,
    char *tablespacename);
static ListPartitionDefState *MakeListDefaultSubpartition(PartitionState *partitionState, char *partitionName,
    char *tablespacename);
static HashPartitionDefState *MakeHashDefaultSubpartition(PartitionState *partitionState, char *partitionName,
    char *tablespacename);
static void MakeDefaultSubpartitionName(PartitionState *partitionState, char **subPartitionName,
    const char *partitionName);
/* ----------------------------------------------------------------
 *				XXX UGLY HARD CODED BADNESS FOLLOWS XXX
 *
 *		these should all be moved to someplace in the lib/catalog
 *		module, if not obliterated first.
 * ----------------------------------------------------------------
 */

/*
 * Note:
 *		Should the system special case these attributes in the future?
 *		Advantage:	consume much less space in the ATTRIBUTE relation.
 *		Disadvantage:  special cases will be all over the place.
 */

/*
 * The initializers below do not include trailing variable length fields,
 * but that's OK - we're never going to reference anything beyond the
 * fixed-size portion of the structure anyway.
 */

static FormData_pg_attribute a1 = {0,
    {"ctid"},
    TIDOID,
    0,
    sizeof(ItemPointerData),
    SelfItemPointerAttributeNumber,
    0,
    -1,
    -1,
    false,
    'p',
    's',
    true,
    false,
    false,
    true,
    0};

static FormData_pg_attribute a2 = {0,
    {"oid"},
    OIDOID,
    0,
    sizeof(Oid),
    ObjectIdAttributeNumber,
    0,
    -1,
    -1,
    true,
    'p',
    'i',
    true,
    false,
    false,
    true,
    0};

static FormData_pg_attribute a3 = {0,
    {"xmin"},
    XIDOID,
    0,
    sizeof(TransactionId),
    MinTransactionIdAttributeNumber,
    0,
    -1,
    -1,
    FLOAT8PASSBYVAL,
    'p',
    'd',
    true,
    false,
    false,
    true,
    0};

static FormData_pg_attribute a4 = {0,
    {"cmin"},
    CIDOID,
    0,
    sizeof(CommandId),
    MinCommandIdAttributeNumber,
    0,
    -1,
    -1,
    true,
    'p',
    'i',
    true,
    false,
    false,
    true,
    0};

static FormData_pg_attribute a5 = {0,
    {"xmax"},
    XIDOID,
    0,
    sizeof(TransactionId),
    MaxTransactionIdAttributeNumber,
    0,
    -1,
    -1,
    FLOAT8PASSBYVAL,
    'p',
    'd',
    true,
    false,
    false,
    true,
    0};

static FormData_pg_attribute a6 = {0,
    {"cmax"},
    CIDOID,
    0,
    sizeof(CommandId),
    MaxCommandIdAttributeNumber,
    0,
    -1,
    -1,
    true,
    'p',
    'i',
    true,
    false,
    false,
    true,
    0};

/*
 * We decided to call this attribute "tableoid" rather than say
 * "classoid" on the basis that in the future there may be more than one
 * table of a particular class/type. In any case table is still the word
 * used in SQL.
 */
static FormData_pg_attribute a7 = {0,
    {"tableoid"},
    OIDOID,
    0,
    sizeof(Oid),
    TableOidAttributeNumber,
    0,
    -1,
    -1,
    true,
    'p',
    'i',
    true,
    false,
    false,
    true,
    0};

#ifdef PGXC
/*
 * In XC we need some sort of node identification for each tuple
 * We are adding another system column that would serve as node identifier.
 * This is not only required by WHERE CURRENT OF but it can be used any
 * where we want to know the originating Datanode of a tuple received
 * at the Coordinator
 */
static FormData_pg_attribute a8 = {0,
    {"xc_node_id"},
    INT4OID,
    0,
    sizeof(int4),
    XC_NodeIdAttributeNumber,
    0,
    -1,
    -1,
    true,
    'p',
    'i',
    true,
    false,
    false,
    true,
    0};

static FormData_pg_attribute a9 = {0,
    {"tablebucketid"},
    INT2OID,
    0,
    sizeof(int2),
    BucketIdAttributeNumber,
    0,
    -1,
    -1,
    true,
    'p',
    'i',
    true,
    false,
    false,
    true,
    0};

static FormData_pg_attribute a10 = {0,
    {"gs_tuple_uid"},
    INT8OID,
    0,
    sizeof(int64),
    UidAttributeNumber,
    0,
    -1,
    -1,
    true,
    'p',
    'd',
    true,
    false,
    false,
    true,
    0};

#ifdef USE_SPQ
static FormData_pg_attribute a11 = {0,
    {"_root_ctid"},
    TIDOID,
    0,
    sizeof(ItemPointerData),
    RootSelfItemPointerAttributeNumber,
    0,
    -1,
    -1,
    false,
    'p',
    's',
    true,
    false,
    false,
    true,
    0};
static const Form_pg_attribute SysAtt[] = {&a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9, &a10, &a11};
#else
static const Form_pg_attribute SysAtt[] = {&a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9, &a10};
#endif
#else
static const Form_pg_attribute SysAtt[] = {&a1, &a2, &a3, &a4, &a5, &a6, &a7};
#endif

/*
 * This function returns a Form_pg_attribute pointer for a system attribute.
 * Note that we elog if the presented attno is invalid, which would only
 * happen if there's a problem upstream.
 */
Form_pg_attribute SystemAttributeDefinition(AttrNumber attno, bool relhasoids, bool relhasbucket, bool relhasuids)
{
    if (attno >= 0 || attno < -(int)lengthof(SysAtt))
        ereport(
            ERROR, (errcode(ERRCODE_INVALID_COLUMN_DEFINITION), errmsg("invalid system attribute number %d", attno)));
    if (attno == ObjectIdAttributeNumber && !relhasoids)
        ereport(
            ERROR, (errcode(ERRCODE_INVALID_COLUMN_DEFINITION), errmsg("invalid system attribute number %d", attno)));
    if (attno == BucketIdAttributeNumber && !relhasbucket)
        ereport(
            ERROR, (errcode(ERRCODE_INVALID_COLUMN_DEFINITION), errmsg("invalid system attribute number %d", attno)));
    if (attno == UidAttributeNumber && !relhasuids) {
        ereport(
            ERROR, (errcode(ERRCODE_INVALID_COLUMN_DEFINITION), errmsg("invalid system attribute number %d", attno)));
    }
    return SysAtt[-attno - 1];
}

/*
 * If the given name is a system attribute name, return a Form_pg_attribute
 * pointer for a prototype definition.	If not, return NULL.
 */
Form_pg_attribute SystemAttributeByName(const char* attname, bool relhasoids)
{
    int j;

    for (j = 0; j < (int)lengthof(SysAtt); j++) {
        Form_pg_attribute att = SysAtt[j];

        if (relhasoids || att->attnum != ObjectIdAttributeNumber) {
            if (strcmp(NameStr(att->attname), attname) == 0)
                return att;
        }
    }

    return NULL;
}

int GetSysAttLength(bool hasBucketAttr)
{
    if (hasBucketAttr) {
        return lengthof(SysAtt);
    } else {
        return lengthof(SysAtt) - 1;
    }

}

static void InitPartitionDef(Partition newPartition, Oid partOid, char strategy)
{
    newPartition->pd_part->parttype = PART_OBJ_TYPE_TABLE_PARTITION;
    newPartition->pd_part->parentid = partOid;
    newPartition->pd_part->rangenum = 0;
    newPartition->pd_part->intervalnum = 0;
    newPartition->pd_part->partstrategy = strategy;
    newPartition->pd_part->reltoastrelid = InvalidOid;
    newPartition->pd_part->reltoastidxid = InvalidOid;
    newPartition->pd_part->indextblid = InvalidOid;
    newPartition->pd_part->reldeltarelid = InvalidOid;
    newPartition->pd_part->reldeltaidx = InvalidOid;
    newPartition->pd_part->relcudescrelid = InvalidOid;
    newPartition->pd_part->relcudescidx = InvalidOid;
    newPartition->pd_part->indisusable = true;
}

static void InitSubPartitionDef(Partition newPartition, Oid partOid, char strategy)
{
    newPartition->pd_part->parttype = PART_OBJ_TYPE_TABLE_SUB_PARTITION;
    newPartition->pd_part->parentid = partOid;
    newPartition->pd_part->rangenum = 0;
    newPartition->pd_part->intervalnum = 0;
    newPartition->pd_part->partstrategy = strategy;
    newPartition->pd_part->reltoastrelid = InvalidOid;
    newPartition->pd_part->reltoastidxid = InvalidOid;
    newPartition->pd_part->indextblid = InvalidOid;
    newPartition->pd_part->reldeltarelid = InvalidOid;
    newPartition->pd_part->reldeltaidx = InvalidOid;
    newPartition->pd_part->relcudescrelid = InvalidOid;
    newPartition->pd_part->relcudescidx = InvalidOid;
    newPartition->pd_part->indisusable = true;
}

/* ----------------------------------------------------------------
 *				XXX END OF UGLY HARD CODED BADNESS XXX
 * ---------------------------------------------------------------- */

/* ----------------------------------------------------------------
 *		heap_create		- Create an uncataloged heap relation
 *
 *		Note API change: the caller must now always provide the OID
 *		to use for the relation.  The relfilenode may (and, normally,
 *		should) be left unspecified.
 *
 *		rel->rd_rel is initialized by RelationBuildLocalRelation,
 *		and is mostly zeroes at return.
 * ----------------------------------------------------------------
 */
Relation heap_create(const char* relname, Oid relnamespace, Oid reltablespace, Oid relid, Oid relfilenode,
    Oid bucketOid, TupleDesc tupDesc, char relkind, char relpersistence, bool partitioned_relation, bool rowMovement,
    bool shared_relation, bool mapped_relation, bool allow_system_table_mods, int8 row_compress, Datum reloptions,
    Oid ownerid, bool skip_create_storage, TableAmType tam_type, int8 relindexsplit, StorageType storage_type,
    bool newcbi, Oid accessMethodObjectId)
{
    bool create_storage = false;
    Relation rel;
    bool isbucket = false;

    if ((IsInitdb && EnableInitDBSegment) || (u_sess->attr.attr_common.IsInplaceUpgrade && ENABLE_DMS)) {
        /* store tables in segment storage as all possible while initdb */
        if (relpersistence == RELPERSISTENCE_PERMANENT) {
            storage_type = SEGMENT_PAGE;
        }
    }

    /* The caller must have provided an OID for the relation. */
    Assert(OidIsValid(relid));

    /*
     * sanity checks
     */
    if (!allow_system_table_mods &&
        (IsSystemNamespace(relnamespace) || IsToastNamespace(relnamespace) || IsCStoreNamespace(relnamespace) ||
        IsPackageSchemaOid(relnamespace) || IsPldeveloper(relnamespace)) && IsNormalProcessingMode()) {
        ereport(ERROR,
            (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                errmsg("permission denied to create \"%s.%s\"", get_namespace_name(relnamespace), relname),
                errdetail("System catalog modifications are currently disallowed.")));
    }
    /*
     * Decide if we need storage or not, and handle a couple other special
     * cases for particular relkinds.
     */
    switch (relkind) {
        case RELKIND_VIEW:
        case RELKIND_CONTQUERY:
        case RELKIND_COMPOSITE_TYPE:
        case RELKIND_STREAM:
        case RELKIND_FOREIGN_TABLE:
            create_storage = false;

            /*
             * Force reltablespace to zero if the relation has no physical
             * storage.  This is mainly just for cleanliness' sake.
             */
            reltablespace = InvalidOid;
            break;
        case RELKIND_SEQUENCE:
        case RELKIND_LARGE_SEQUENCE:
            create_storage = true;

            /*
             * Force reltablespace to zero for sequences, since we don't
             * support moving them around into different tablespaces.
             */
            reltablespace = InvalidOid;
            break;
        case RELKIND_GLOBAL_INDEX:
            create_storage = true;
            break;
        default:
            if (!partitioned_relation) {
                create_storage = true;
            } else {
                create_storage = false;
            }
            break;
    }

    if (ENABLE_DSS && !partitioned_relation) {
        /*
         * when we store systable to segment, we should allocate segment header page
         * for all objects, to avoid some issues, like: pg_table_size for view.
         * if the view has no segment header page, the seg_totalblocks' call will crash,
         * because of read_head_buffer's magic number check fail.
         */
        create_storage = true;
    }

    /*
     * Never allow a pg_class entry to explicitly specify the database's
     * default tablespace in reltablespace; force it to zero instead. This
     * ensures that if the database is cloned with a different default
     * tablespace, the pg_class entry will still match where CREATE DATABASE
     * will put the physically copied relation.
     *
     * Yes, this is a bit of a hack.
     */
    reltablespace = ConvertToPgclassRelTablespaceOid(reltablespace);

    /*
     * Unless otherwise requested, the physical ID (relfilenode) is initially
     * the same as the logical ID (OID).  When the caller did specify a
     * relfilenode, it already exists; do not attempt to create it.
     */
    if (OidIsValid(relfilenode)) {
        if (u_sess->proc_cxt.IsBinaryUpgrade) {
            if (!partitioned_relation && storage_type == SEGMENT_PAGE) {
                isbucket = BUCKET_OID_IS_VALID(bucketOid) && !newcbi;
                Oid database_id = (ConvertToRelfilenodeTblspcOid(reltablespace) == GLOBALTABLESPACE_OID) ?
                    InvalidOid : u_sess->proc_cxt.MyDatabaseId;
                relfilenode = seg_alloc_segment(ConvertToRelfilenodeTblspcOid(reltablespace),
                                                database_id, isbucket, relfilenode);
            }
        } else {
            create_storage = false;
        }
    } else if ((storage_type == SEGMENT_PAGE && !partitioned_relation) ||
                (storage_type == SEGMENT_PAGE && ENABLE_DSS && create_storage)) {
        isbucket = BUCKET_OID_IS_VALID(bucketOid) && !newcbi;
        Oid database_id = (ConvertToRelfilenodeTblspcOid(reltablespace) == GLOBALTABLESPACE_OID) ?
            InvalidOid : u_sess->proc_cxt.MyDatabaseId;
        relfilenode = (Oid)seg_alloc_segment(ConvertToRelfilenodeTblspcOid(reltablespace),
                                             database_id, isbucket, InvalidBlockNumber);
        ereport(LOG, (errmsg("Segment Relation %s(%u) set relfilenode %u xid %lu", relname, relid, relfilenode,
            GetCurrentTransactionIdIfAny())));
    } else {
        relfilenode = relid;
    }

    /*
     * build the relcache entry.
     */
    rel = RelationBuildLocalRelation(relname,
                                     relnamespace,
                                     tupDesc,
                                     relid,
                                     relfilenode,
                                     reltablespace,
                                     shared_relation,
                                     mapped_relation,
                                     relpersistence,
                                     relkind,
                                     row_compress,
                                     reloptions,
                                     tam_type,
                                     relindexsplit,
                                     storage_type,
                                     accessMethodObjectId);

    if (partitioned_relation) {
        rel->rd_rel->parttype = PARTTYPE_PARTITIONED_RELATION;
    }
    rel->rd_rel->relrowmovement = rowMovement;

    /*
     * Save newcbi as a context indicator to
     * avoid missing information in later index building process.
     */
    rel->newcbi = newcbi;

    if (u_sess->attr.attr_common.IsInplaceUpgrade && !u_sess->upg_cxt.new_catalog_need_storage)
        create_storage = false;

    if (skip_create_storage) {
        create_storage = false;
    }
    /*
     * Have the storage manager create the relation's disk file, if needed.
     *
     * We only create the main fork here, other forks will be created on
     * demand.
     */
    if (create_storage) {
        rel->rd_bucketoid = bucketOid;
        RelationOpenSmgr(rel);
        RelationCreateStorage(rel->rd_node, relpersistence, ownerid, bucketOid, rel);
    }

    if (RelationUsesSpaceType(rel->rd_rel->relpersistence) == SP_TEMP) {
        make_tmptable_cache_key(rel->rd_rel->relfilenode);
    }

    return rel;
}

/* ----------------------------------------------------------------
 *		heap_create_with_catalog		- Create a cataloged relation
 *
 *		this is done in multiple steps:
 *
 *		1) CheckAttributeNamesTypes() is used to make certain the tuple
 *		   descriptor contains a valid set of attribute names and types
 *
 *		2) pg_class is opened and get_relname_relid()
 *		   performs a scan to ensure that no relation with the
 *		   same name already exists.
 *
 *		3) heap_create() is called to create the new relation on disk.
 *
 *		4) TypeCreate() is called to define a new type corresponding
 *		   to the new relation.
 *
 *		5) AddNewRelationTuple() is called to register the
 *		   relation in pg_class.
 *
 *		6) AddNewAttributeTuples() is called to register the
 *		   new relation's schema in pg_attribute.
 *
 *		7) StoreConstraints is called ()		- vadim 08/22/97
 *
 *		8) the relations are closed and the new relation's oid
 *		   is returned.
 *
 * ----------------------------------------------------------------
 */

/* --------------------------------
 *		CheckAttributeNamesTypes
 *
 *		this is used to make certain the tuple descriptor contains a
 *		valid set of attribute names and datatypes.  a problem simply
 *		generates ereport(ERROR) which aborts the current transaction.
 * --------------------------------
 */
void CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind, bool allow_system_table_mods)
{
    int i;
    int j;
    int natts = tupdesc->natts;

    /* Sanity check on column count */
    if (natts < 0 || natts > MaxHeapAttributeNumber)
        ereport(ERROR,
            (errcode(ERRCODE_TOO_MANY_COLUMNS), errmsg("tables can have at most %d columns", MaxHeapAttributeNumber)));

    /*
     * first check for collision with system attribute names
     *
     * Skip this for a view or type relation, since those don't have system
     * attributes.
     */
    if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE && relkind != RELKIND_CONTQUERY) {
        for (i = 0; i < natts; i++) {
            if (SystemAttributeByName(NameStr(tupdesc->attrs[i].attname), tupdesc->tdhasoid) != NULL)
                ereport(ERROR,
                    (errcode(ERRCODE_DUPLICATE_COLUMN),
                        errmsg("column name \"%s\" conflicts with a system column name",
                            NameStr(tupdesc->attrs[i].attname))));
        }
    }

    /*
     * next check for repeated attribute names
     */
    for (i = 1; i < natts; i++) {
        for (j = 0; j < i; j++) {
            if (strcmp(NameStr(tupdesc->attrs[j].attname), NameStr(tupdesc->attrs[i].attname)) == 0)
                ereport(ERROR,
                    (errcode(ERRCODE_DUPLICATE_COLUMN),
                        errmsg("column name \"%s\" specified more than once", NameStr(tupdesc->attrs[j].attname))));
        }
    }

    /*
     * next check the attribute types
     */
    for (i = 0; i < natts; i++) {
        CheckAttributeType(NameStr(tupdesc->attrs[i].attname),
            tupdesc->attrs[i].atttypid,
            tupdesc->attrs[i].attcollation,
            NIL, /* assume we're creating a new rowtype */
            allow_system_table_mods);
    }
}

/* --------------------------------
 *		CheckAttributeType
 *
 *		Verify that the proposed datatype of an attribute is legal.
 *		This is needed mainly because there are types (and pseudo-types)
 *		in the catalogs that we do not support as elements of real tuples.
 *		We also check some other properties required of a table column.
 *
 * If the attribute is being proposed for addition to an existing table or
 * composite type, pass a one-element list of the rowtype OID as
 * containing_rowtypes.  When checking a to-be-created rowtype, it's
 * sufficient to pass NIL, because there could not be any recursive reference
 * to a not-yet-existing rowtype.
 * --------------------------------
 */
void CheckAttributeType(
    const char* attname, Oid atttypid, Oid attcollation, List* containing_rowtypes, bool allow_system_table_mods)
{
    char att_typtype = get_typtype(atttypid);
    Oid att_typelem;

    if (atttypid == UNKNOWNOID) {
        /*
         * Warn user, but don't fail, if column to be created has UNKNOWN type
         * (usually as a result of a 'retrieve into' - jolly)
         */
        ereport(WARNING,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                errmsg("column \"%s\" has type \"unknown\"", attname),
                errdetail("Proceeding with relation creation anyway.")));
    } else if (att_typtype == TYPTYPE_PSEUDO) {
        /*
         * Refuse any attempt to create a pseudo-type column, except for a
         * special hack for pg_statistic: allow ANYARRAY when modifying system
         * catalogs (this allows creating pg_statistic and cloning it during
         * VACUUM FULL)
         */
        if (atttypid != ANYARRAYOID || !allow_system_table_mods) {
#ifndef ENABLE_MULTIPLE_NODES
            if (u_sess->attr.attr_common.plsql_show_all_error) {
                StringInfoData message;
                initStringInfo(&message);
                appendStringInfo(&message, "column \"%s\" has pseudo-type %s", attname, format_type_be(atttypid));
                InsertErrorMessage(message.data, 0, true);
            }
#endif
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                    errmsg("column \"%s\" has pseudo-type %s", attname, format_type_be(atttypid))));
        }
    } else if (att_typtype == TYPTYPE_DOMAIN) {
        /*
         * If it's a domain, recurse to check its base type.
         */
        CheckAttributeType(attname, getBaseType(atttypid), attcollation, containing_rowtypes, allow_system_table_mods);
    } else if (att_typtype == TYPTYPE_COMPOSITE || att_typtype == TYPTYPE_ABSTRACT_OBJECT) {
        /*
         * For a composite type, recurse into its attributes.
         */
        Relation relation;
        TupleDesc tupdesc;
        int i;

        /*
         * Check for self-containment.	Eventually we might be able to allow
         * this (just return without complaint, if so) but it's not clear how
         * many other places would require anti-recursion defenses before it
         * would be safe to allow tables to contain their own rowtype.
         */
        if (list_member_oid(containing_rowtypes, atttypid)) {
            if (strcmp(attname, "pljson_list_data") == 0) {
                return;
            }
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                    errmsg("composite type %s cannot be made a member of itself", format_type_be(atttypid))));
        }

        containing_rowtypes = lcons_oid(atttypid, containing_rowtypes);

        relation = relation_open(get_typ_typrelid(atttypid), AccessShareLock);

        tupdesc = RelationGetDescr(relation);

        for (i = 0; i < tupdesc->natts; i++) {
            Form_pg_attribute attr = &tupdesc->attrs[i];

            if (attr->attisdropped)
                continue;
            CheckAttributeType(NameStr(attr->attname),
                attr->atttypid,
                attr->attcollation,
                containing_rowtypes,
                allow_system_table_mods);
        }

        relation_close(relation, AccessShareLock);

        containing_rowtypes = list_delete_first(containing_rowtypes);
    } else if (att_typtype == TYPTYPE_TABLEOF) {
        /*
         * For a table of type, find its base elemid and collation
         */
        att_typelem = get_element_type(atttypid);
        Oid att_collation = get_typcollation(att_typelem);
        CheckAttributeType(attname, att_typelem, att_collation, containing_rowtypes, allow_system_table_mods);
    } else if (OidIsValid((att_typelem = get_element_type(atttypid)))) {
        /*
         * Must recurse into array types, too, in case they are composite.
         */
        CheckAttributeType(attname, att_typelem, attcollation, containing_rowtypes, allow_system_table_mods);
    }

    /*
     * This might not be strictly invalid per SQL standard, but it is pretty
     * useless, and it cannot be dumped, so we must disallow it.
     */
    if (!OidIsValid(attcollation) && type_is_collatable(atttypid))
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                errmsg("no collation was derived for column \"%s\" with collatable type %s",
                    attname,
                    format_type_be(atttypid)),
                errhint("Use the COLLATE clause to set the collation explicitly.")));
}

void InsertTablebucketidAttribute(Oid newRelOid)
{
    CatalogIndexState indstate;
    Relation rel;
    int tablebucketidIndex = -BucketIdAttributeNumber - 1;
    /*
     * open pg_attribute and its indexes.
     */
    rel = heap_open(AttributeRelationId, RowExclusiveLock);
    indstate = CatalogOpenIndexes(rel);

    FormData_pg_attribute attStruct;
    errno_t rc = memcpy_s(&attStruct, sizeof(FormData_pg_attribute), (char*)SysAtt[tablebucketidIndex],
        sizeof(FormData_pg_attribute));
    securec_check(rc, "\0", "\0");
    attStruct.attrelid = newRelOid;

    InsertPgAttributeTuple(rel, &attStruct, indstate);
    CatalogCloseIndexes(indstate);
    heap_close(rel, RowExclusiveLock);
}

/*
 * InsertPgAttributeTuple
 *		Construct and insert a new tuple in pg_attribute.
 *
 * Caller has already opened and locked pg_attribute.  new_attribute is the
 * attribute to insert (but we ignore attacl and attoptions, which are always
 * initialized to NULL).
 *
 * indstate is the index state for CatalogIndexInsert.	It can be passed as
 * NULL, in which case we'll fetch the necessary info.  (Don't do this when
 * inserting multiple attributes, because it's a tad more expensive.)
 */
void InsertPgAttributeTuple(Relation pg_attribute_rel, Form_pg_attribute new_attribute, CatalogIndexState indstate)
{
    Datum values[Natts_pg_attribute];
    bool nulls[Natts_pg_attribute];
    HeapTuple tup;
    errno_t rc;
    /* This is a tad tedious, but way cleaner than what we used to do... */
    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");

    values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attribute->attrelid);
    values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attribute->attname);
    values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attribute->atttypid);
    values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attribute->attstattarget);
    values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attribute->attlen);
    values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attribute->attnum);
    values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attribute->attndims);
    values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(new_attribute->attcacheoff);
    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_attalign - 1] = CharGetDatum(new_attribute->attalign);
    values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attribute->attnotnull);
    values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attribute->atthasdef);
    values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attribute->attisdropped);
    values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal);
    values[Anum_pg_attribute_attcmprmode - 1] = Int8GetDatum(new_attribute->attcmprmode);
    values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attribute->attinhcount);
    values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attribute->attcollation);
    values[Anum_pg_attribute_attkvtype - 1] = Int8GetDatum(new_attribute->attkvtype);

    /* start out with empty permissions and empty options */
    nulls[Anum_pg_attribute_attacl - 1] = true;
    nulls[Anum_pg_attribute_attoptions - 1] = true;
    nulls[Anum_pg_attribute_attfdwoptions - 1] = true;

    /* at default, new fileld attinitdefval of pg_attribute is null. */
    nulls[Anum_pg_attribute_attinitdefval - 1] = true;
    nulls[Anum_pg_attribute_attdroppedname - 1] = true;

    tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);

    /* finally insert the new tuple, update the indexes, and clean up */
    (void)simple_heap_insert(pg_attribute_rel, tup);

    if (indstate != NULL)
        CatalogIndexInsert(indstate, tup);
    else
        CatalogUpdateIndexes(pg_attribute_rel, tup);

    heap_freetuple(tup);
}

static bool make_gs_depend_param_body(GsDependParamBody* gs_depend_param_body, const char* typ_name,
    const char relkind, const Oid namespace_oid)
{
    bool need_build_depend = false;
    int cw = CompileWhich();
    need_build_depend = (relkind == RELKIND_RELATION || relkind == RELKIND_COMPOSITE_TYPE) &&
        (cw == PLPGSQL_COMPILE_PACKAGE_PROC || cw == PLPGSQL_COMPILE_PACKAGE || cw == PLPGSQL_COMPILE_PROC);
    if (!need_build_depend) {
        return false;
    }
    gs_depend_param_body->dependNamespaceOid = namespace_oid;
    if (NULL != u_sess->plsql_cxt.curr_compile_context &&
        NULL != u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package) {
        PLpgSQL_package* pkg = u_sess->plsql_cxt.curr_compile_context->plpgsql_curr_compile_package;
        gs_depend_param_body->dependPkgOid = pkg->pkg_oid;
        gs_depend_param_body->dependPkgName = pkg->pkg_signature;
    }
    char* real_typ_name = ParseTypeName((char*)typ_name, gs_depend_param_body->dependPkgOid);
    if (real_typ_name == NULL) {
        gs_depend_param_body->dependName = pstrdup(typ_name);
    } else {
        gs_depend_param_body->dependName = real_typ_name;
    }
    gs_depend_param_body->refPosType = GSDEPEND_REFOBJ_POS_IN_TYPE;
    gs_depend_param_body->type = GSDEPEND_OBJECT_TYPE_TYPE;
    return true;
}

/* --------------------------------
 *		AddNewAttributeTuples
 *
 *		this registers the new relation's schema by adding
 *		tuples to pg_attribute.
 * --------------------------------
 */
static void AddNewAttributeTuples(Oid new_rel_oid, TupleDesc tupdesc, char relkind,
    bool oidislocal, int oidinhcount, bool hasbucket, bool hasuids, List* depend_extend, const char* typ_name, Oid namespace_oid)
{
    Form_pg_attribute attr;
    int i;
    Relation rel;
    CatalogIndexState indstate;
    int natts = tupdesc->natts;
    ObjectAddress myself, referenced;

    /*
     * open pg_attribute and its indexes.
     */
    rel = heap_open(AttributeRelationId, RowExclusiveLock);

    indstate = CatalogOpenIndexes(rel);
    GsDependParamBody gs_depend_param_body;
    gsplsql_init_gs_depend_param_body(&gs_depend_param_body);
    bool need_build_depend = false;
    if (enable_plpgsql_gsdependency()) {
        need_build_depend = make_gs_depend_param_body(&gs_depend_param_body, typ_name, relkind, namespace_oid);
    }
    ListCell* depend_extend_cell = list_head(depend_extend);
    /*
     * First we add the user attributes.  This is also a convenient place to
     * add dependencies on their datatypes and collations.
     */
    for (i = 0; i < natts; i++) {
        attr = &tupdesc->attrs[i];
        /* Fill in the correct relation OID */
        attr->attrelid = new_rel_oid;
        /* Make sure these are OK, too */
        attr->attstattarget = -1;
        attr->attcacheoff = -1;

        InsertPgAttributeTuple(rel, attr, indstate);

        /*
         * During inplace/grey upgrade, we don't record dependencies
         * for new catalog attributes.  Also if the column is dropped
         * no need to record dependency anymore
         */
        if (!u_sess->attr.attr_common.IsInplaceUpgrade && !attr->attisdropped) {
            /* Add dependency info */
            myself.classId = RelationRelationId;
            myself.objectId = new_rel_oid;
            myself.objectSubId = i + 1;
            referenced.classId = TypeRelationId;
            referenced.objectId = attr->atttypid;
            referenced.objectSubId = 0;

            if (need_build_depend) {
                if (NULL != depend_extend_cell) {
                    gs_depend_param_body.dependExtend = (TypeDependExtend*)lfirst(depend_extend_cell);
                } else {
                    gs_depend_param_body.dependExtend = NULL;
                }
                recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL, &gs_depend_param_body);
            } else {
                recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
            }

            /* The default collation is pinned, so don't bother recording it */
            if (OidIsValid(attr->attcollation) && attr->attcollation != DEFAULT_COLLATION_OID) {
                referenced.classId = CollationRelationId;
                referenced.objectId = attr->attcollation;
                referenced.objectSubId = 0;
                recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
            }
        }
        if (need_build_depend && NULL != depend_extend_cell) {
            depend_extend_cell = lnext(depend_extend_cell);
        }
    }
    if (need_build_depend) {
        pfree_ext(gs_depend_param_body.dependName);
    }

    /*
     * Next we add the system attributes.  Skip OID if rel has no OIDs. Skip
     * all for a view or type relation.  We don't bother with making datatype
     * dependencies here, since presumably all these types are pinned.
     */
    if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE && relkind != RELKIND_CONTQUERY) {
        for (i = 0; i < (int)lengthof(SysAtt); i++) {
            FormData_pg_attribute attStruct;
            errno_t rc;
            /* skip OID where appropriate */
            if (!tupdesc->tdhasoid && SysAtt[i]->attnum == ObjectIdAttributeNumber)
                continue;
#ifdef USE_SPQ
            if (SysAtt[i]->attnum == RootSelfItemPointerAttributeNumber)
                continue;
#endif
            if (!hasbucket && SysAtt[i]->attnum == BucketIdAttributeNumber)
                continue;
            if (!hasuids && SysAtt[i]->attnum == UidAttributeNumber)
                continue;
            rc = memcpy_s(&attStruct, sizeof(FormData_pg_attribute), (char*)SysAtt[i], sizeof(FormData_pg_attribute));
            securec_check(rc, "\0", "\0");

            /* Fill in the correct relation OID in the copied tuple */
            attStruct.attrelid = new_rel_oid;

            /* Fill in correct inheritance info for the OID column */
            if (attStruct.attnum == ObjectIdAttributeNumber) {
                attStruct.attislocal = oidislocal;
                attStruct.attinhcount = oidinhcount;
            }

            InsertPgAttributeTuple(rel, &attStruct, indstate);
        }
    }

    /*
     * clean up
     */
    CatalogCloseIndexes(indstate);

    heap_close(rel, RowExclusiveLock);
}

static void AddNewGsSecEncryptedColumnsTuples(const Oid new_rel_oid, List *ceLst)
{
    Relation rel;
    CatalogIndexState indstate;
    /*
     * open gs_encrypted_columns and its indexes.
     */
    rel = heap_open(ClientLogicCachedColumnsId, RowExclusiveLock);

    indstate = CatalogOpenIndexes(rel);
    ListCell *li = NULL;
    foreach (li, ceLst) {
        CeHeapInfo *ceHeapInfo = (CeHeapInfo *)lfirst(li);
        insert_gs_sec_encrypted_column_tuple(ceHeapInfo, rel, new_rel_oid, indstate);
    }
    /*
     * clean up
     */
    CatalogCloseIndexes(indstate);
    heap_close(rel, RowExclusiveLock);
}

/* --------------------------------
 *		InsertPgClassTuple
 *
 *		Construct and insert a new tuple in pg_class.
 *
 * Caller has already opened and locked pg_class.
 * Tuple data is taken from new_rel_desc->rd_rel, except for the
 * variable-width fields which are not present in a cached reldesc.
 * relacl and reloptions are passed in Datum form (to avoid having
 * to reference the data types in heap.h).	Pass (Datum) 0 to set them
 * to NULL.
 * --------------------------------
 */
void InsertPgClassTuple(
    Relation pg_class_desc, Relation new_rel_desc, Oid new_rel_oid, Datum relacl, Datum reloptions, char relkind, int2vector* bucketcol)
{
    Form_pg_class rd_rel = new_rel_desc->rd_rel;
    Datum values[Natts_pg_class];
    bool nulls[Natts_pg_class];
    HeapTuple tup;
    errno_t rc;

    /* This is a tad tedious, but way cleaner than what we used to do... */
    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");

    values[Anum_pg_class_relname - 1] = NameGetDatum(&rd_rel->relname);
    values[Anum_pg_class_relnamespace - 1] = ObjectIdGetDatum(rd_rel->relnamespace);
    values[Anum_pg_class_reltype - 1] = ObjectIdGetDatum(rd_rel->reltype);
    values[Anum_pg_class_reloftype - 1] = ObjectIdGetDatum(rd_rel->reloftype);
    values[Anum_pg_class_relowner - 1] = ObjectIdGetDatum(rd_rel->relowner);
    values[Anum_pg_class_relam - 1] = ObjectIdGetDatum(rd_rel->relam);
    values[Anum_pg_class_relfilenode - 1] = ObjectIdGetDatum(rd_rel->relfilenode);
    values[Anum_pg_class_reltablespace - 1] = ObjectIdGetDatum(rd_rel->reltablespace);
    values[Anum_pg_class_relpages - 1] = Float8GetDatum(rd_rel->relpages);
    values[Anum_pg_class_reltuples - 1] = Float8GetDatum(rd_rel->reltuples);
    values[Anum_pg_class_relallvisible - 1] = Int32GetDatum(rd_rel->relallvisible);
    values[Anum_pg_class_reltoastrelid - 1] = ObjectIdGetDatum(rd_rel->reltoastrelid);
    values[Anum_pg_class_reltoastidxid - 1] = ObjectIdGetDatum(rd_rel->reltoastidxid);
    values[Anum_pg_class_reldeltarelid - 1] = ObjectIdGetDatum(rd_rel->reldeltarelid);
    values[Anum_pg_class_reldeltaidx - 1] = ObjectIdGetDatum(rd_rel->reldeltaidx);
    values[Anum_pg_class_relcudescrelid - 1] = ObjectIdGetDatum(rd_rel->relcudescrelid);
    values[Anum_pg_class_relcudescidx - 1] = ObjectIdGetDatum(rd_rel->relcudescidx);
    values[Anum_pg_class_relhasindex - 1] = BoolGetDatum(rd_rel->relhasindex);
    values[Anum_pg_class_relisshared - 1] = BoolGetDatum(rd_rel->relisshared);
    values[Anum_pg_class_relpersistence - 1] = CharGetDatum(rd_rel->relpersistence);
    values[Anum_pg_class_relkind - 1] = CharGetDatum(rd_rel->relkind);
    values[Anum_pg_class_relnatts - 1] = Int16GetDatum(rd_rel->relnatts);
    values[Anum_pg_class_relchecks - 1] = Int16GetDatum(rd_rel->relchecks);
    values[Anum_pg_class_relhasoids - 1] = BoolGetDatum(rd_rel->relhasoids);
    values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
    values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
    values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
    values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
    values[ANUM_PG_CLASS_RELCMPRS - 1] = CharGetDatum(rd_rel->relcmprs);
    values[Anum_pg_class_relhasclusterkey - 1] = BoolGetDatum(rd_rel->relhasclusterkey);
    values[Anum_pg_clsss_relrowmovement - 1] = BoolGetDatum(rd_rel->relrowmovement);
    values[Anum_pg_class_parttype - 1] = CharGetDatum(rd_rel->parttype);
    values[Anum_pg_class_relfrozenxid - 1] = ShortTransactionIdGetDatum(rd_rel->relfrozenxid);

    if (relacl != (Datum)0)
        values[Anum_pg_class_relacl - 1] = relacl;
    else
        nulls[Anum_pg_class_relacl - 1] = true;
    if (reloptions != (Datum)0)
        values[Anum_pg_class_reloptions - 1] = reloptions;
    else
        nulls[Anum_pg_class_reloptions - 1] = true;

    if (RELKIND_IN_RTM)
        values[Anum_pg_class_relfrozenxid64 - 1] = u_sess->utils_cxt.RecentXmin;
    else
        values[Anum_pg_class_relfrozenxid64 - 1] = InvalidTransactionId;

    if (!IsSystemNamespace(rd_rel->relnamespace) && rd_rel->relkind == RELKIND_RELATION)
        values[Anum_pg_class_relreplident - 1] = REPLICA_IDENTITY_DEFAULT;
    else
        values[Anum_pg_class_relreplident - 1] = REPLICA_IDENTITY_NOTHING;

    if (new_rel_desc->relreplident && new_rel_desc->relreplident != REPLICA_IDENTITY_NOTHING) {
        values[Anum_pg_class_relreplident - 1] = new_rel_desc->relreplident;
    }

    if (OidIsValid(new_rel_desc->rd_bucketoid)) {
        Assert(new_rel_desc->storage_type == SEGMENT_PAGE);
        values[Anum_pg_class_relbucket - 1] = ObjectIdGetDatum(new_rel_desc->rd_bucketoid);
    } else if (new_rel_desc->storage_type == SEGMENT_PAGE) {
        values[Anum_pg_class_relbucket - 1] = ObjectIdGetDatum(VirtualSegmentOid);
    } else {
        nulls[Anum_pg_class_relbucket - 1] = true;
    }

#ifndef ENABLE_MULTIPLE_NODES
    if (RELKIND_IN_RTM && !is_cstore_option(relkind, reloptions)) {
        values[Anum_pg_class_relminmxid - 1] = GetOldestMultiXactId();
    } else {
        values[Anum_pg_class_relminmxid - 1] = InvalidMultiXactId;
    }
#endif

    if (bucketcol != NULL)
        values[Anum_pg_class_relbucketkey - 1] = PointerGetDatum(bucketcol);
    else
        nulls[Anum_pg_class_relbucketkey - 1] = true;

    tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls);

    /*
     * The new tuple must have the oid already chosen for the rel.	Sure would
     * be embarrassing to do this sort of thing in polite company.
     */
    HeapTupleSetOid(tup, new_rel_oid);

    /* finally insert the new tuple, update the indexes, and clean up */
    (void)simple_heap_insert(pg_class_desc, tup);

    CatalogUpdateIndexes(pg_class_desc, tup);

    heap_freetuple(tup);
}

/* --------------------------------
 *		AddNewRelationTuple
 *
 *		this registers the new relation in the catalogs by
 *		adding a tuple to pg_class.
 * --------------------------------
 */
static void AddNewRelationTuple(Relation pg_class_desc, Relation new_rel_desc, Oid new_rel_oid, Oid new_type_oid,
    Oid reloftype, Oid relowner, char relkind, char relpersistence, Datum relacl, Datum reloptions,
    int2vector* bucketcol, bool ispartrel)
{
    Form_pg_class new_rel_reltup;

    /*
     * first we update some of the information in our uncataloged relation's
     * relation descriptor.
     */
    new_rel_reltup = new_rel_desc->rd_rel;

    switch (relkind) {
        case RELKIND_RELATION:
        case RELKIND_MATVIEW:
        case RELKIND_INDEX:
        case RELKIND_TOASTVALUE:
        case RELKIND_GLOBAL_INDEX:
            /* The relation is real, but as yet empty */
            new_rel_reltup->relpages = 0;
            new_rel_reltup->reltuples = 0;
            new_rel_reltup->relallvisible = 0;
            break;
        case RELKIND_SEQUENCE:
        case RELKIND_LARGE_SEQUENCE:
            /* Sequences always have a known size */
            new_rel_reltup->relpages = 1;
            new_rel_reltup->reltuples = 1;
            new_rel_reltup->relallvisible = 0;
            break;
        default:
            /* Views, etc, have no disk storage */
            new_rel_reltup->relpages = 0;
            new_rel_reltup->reltuples = 0;
            new_rel_reltup->relallvisible = 0;
            break;
    }

    /* Initialize relfrozenxid */
    if (RELKIND_IN_RTM) {
        /*
         * Initialize to the minimum XID that could put tuples in the table.
         * We know that no xacts older than RecentXmin are still running, so
         * that will do.
         */
        new_rel_reltup->relfrozenxid = (ShortTransactionId)u_sess->utils_cxt.RecentXmin;
    } else {
        /*
         * Other relation types will not contain XIDs, so set relfrozenxid to
         * InvalidTransactionId.  (Note: a sequence does contain a tuple, but
         * we force its xmin to be FrozenTransactionId always; see
         * commands/sequence.c.)
         */
        new_rel_reltup->relfrozenxid = (ShortTransactionId)InvalidTransactionId;
    }

    /* global temp table not remember transaction info in catalog */
    if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP) {
        new_rel_reltup->relfrozenxid = (ShortTransactionId)InvalidTransactionId;
    }

    new_rel_reltup->relowner = relowner;
    new_rel_reltup->reltype = new_type_oid;
    new_rel_reltup->reloftype = reloftype;

    new_rel_desc->rd_att->tdtypeid = new_type_oid;

    /* Now build and insert the tuple */
    InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid, relacl, reloptions, relkind, bucketcol);
}

#ifdef PGXC

/* --------------------------------
 *		cmp_nodes
 *
 *		Compare the Oids of two XC nodes
 *		to sort them in ascending order by their names
 * --------------------------------
 */
static int cmp_nodes(const void* p1, const void* p2)
{
    NameData node1 = {{0}};
    NameData node2 = {{0}};

    return strcmp(get_pgxc_nodename(*(Oid*)p1, &node1), get_pgxc_nodename(*(Oid*)p2, &node2));
}

static void CheckDistColsUnique(DistributeBy *distribBy, int distribKeyNum, int2 *attnum)
{
    if (distribBy == NULL) {
        return;
    }

    /* Check whether include identical column in distribute key */
    for (int i = 0; i < distribKeyNum - 1; i++) {
        for (int j = i + 1; j < distribKeyNum; j++) {
            if (attnum[i] == attnum[j]) {
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                        errmsg("Include identical distribution column \"%s\"",
                            strVal(list_nth(distribBy->colname, i)))));
            }
        }
    }
}

static List* GetDistColsPos(DistributeBy* distributeBy, TupleDesc desc)
{
    int i;
    List* pos = NULL;
    ListCell* cell = NULL;
    char* colname = NULL;
    FormData_pg_attribute *attrs = desc->attrs;

    foreach (cell, distributeBy->colname) {
        colname = strVal((Value*)lfirst(cell));

        for (i = 0; i < desc->natts; i++) {
            if (strcmp(colname, attrs[i].attname.data) == 0) {
                break;
            }
        }

        Assert(i < desc->natts);
        pos = lappend_int(pos, i);
    }

    return pos;
}


static void CheckListDistEntry(List* entry, int distkeynum, bool isDefault)
{
    int boundaryLength = list_length(entry);
    if (boundaryLength != distkeynum && !isDefault) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                errmsg("Distribution boundary length (%d) does not equal to the number of distribute keys (%d).",
                boundaryLength, distkeynum)));
    }
}

/*
 * Get the List Distribution entry's items in string format,
 * the return value should be freed by caller.
 */
static char* DistEntryToString(List* entry)
{
    Oid typoutput;
    bool typIsVarlena = false;
    char* outputstr = NULL;
    ListCell* cell = NULL;
    StringInfoData string;

    initStringInfo(&string);
    appendStringInfoChar(&string, '\'');

    foreach (cell, entry) {
        Const* element = (Const *)lfirst(cell);

        if (cell != list_head(entry)) {
            appendStringInfoChar(&string, ',');
        }

        if (element->constisnull) {
            appendStringInfoString(&string, "NULL");
        } else if (element->ismaxvalue) {
            appendStringInfoString(&string, "MAXVALUE");
        } else {
            getTypeOutputInfo(element->consttype, &typoutput, &typIsVarlena);
            outputstr = OidOutputFunctionCall(typoutput, element->constvalue);
            appendStringInfo(&string, "%s", outputstr);
        }
    }

    appendStringInfoChar(&string, '\'');
    return string.data;
}

/* Check for default within one list slice */
static bool PreCheckListSlice(ListSliceDefState* slicedef)
{
    List* boundary = NULL;

    List* boundaries = NULL;
    ListCell* cell = NULL;
    ListCell* next = NULL;
    Const* firstConst = NULL;

    boundaries = slicedef->boundaries;
    foreach (cell, boundaries) {
        next = cell;
        boundary = (List*)lfirst(cell);

        /* skip checking current slice if it's DEFAULT slice */
        firstConst = linitial_node(Const, boundary);
        if (firstConst->ismaxvalue) {
            return true;
        }
    }

    return false;
}

static int ConstCmp(const void* a, const void* b)
{
    const SliceConstInfo* rea = (const SliceConstInfo*)a;
    const SliceConstInfo* reb = (const SliceConstInfo*)b;
    return partitonKeyCompare((Const**)rea->sliceBoundaryValue, (Const**)reb->sliceBoundaryValue, rea->sliceNum);
}

static void IsDuplicateBoundariesExist(SliceConstInfo* sliceConstInfo, int totalBoundariesNum, int distkeynum)
{
    /* sort all the boundaries values */
    qsort(sliceConstInfo, totalBoundariesNum, sizeof(SliceConstInfo), ConstCmp);
    /* if duplicate boundaries exist,report and exit */
    for (int i = 0; i < totalBoundariesNum - 1; i++) {
        int cmp = partitonKeyCompare(sliceConstInfo[i].sliceBoundaryValue,
            sliceConstInfo[i + 1].sliceBoundaryValue, distkeynum);
        if (cmp == 0) {
            if (strcmp(sliceConstInfo[i].sliceName, sliceConstInfo[i + 1].sliceName) == 0) {
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                        errmsg("List value %s specified multiple times in slice %s.",
                            DistEntryToString(sliceConstInfo[i].sliceBoundary),
                            sliceConstInfo[i].sliceName)));
            } else {
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                        errmsg("List value %s specified multiple times in slices %s and %s.",
                            DistEntryToString(sliceConstInfo[i].sliceBoundary),
                            sliceConstInfo[i].sliceName,
                            sliceConstInfo[i + 1].sliceName)));
            }
        }
    }
}

static int GetTotalBoundariesNum(List* sliceList)
{
    int result = 0;
    ListSliceDefState* slicedef = NULL;
    List* boundaryList = NULL;
    foreach_cell(sliceCell, sliceList) {
        slicedef = (ListSliceDefState*)lfirst(sliceCell);
        boundaryList = slicedef->boundaries;
        result += boundaryList->length;
    }
    return result;
}

static void CheckDuplicateListSlices(List* pos, FormData_pg_attribute* attrs, DistributeBy *distby)
{
    List* boundary = NULL;
    List* sliceList = NULL;
    ListSliceDefState* slicedef = NULL;
    List* boundaryList = NULL;
    int sliceCount = 0;
    int totalBoundariesNum = 0;
    bool is_intreval = false;
    SliceConstInfo* sliceConstInfo = NULL;
    int distkeynum = list_length(distby->colname);
    List* constsList = NULL;

    if (pos == NULL || attrs == NULL) {
        ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
            errmsg("invalid list distribution table definition")));
    }

    sliceList = distby->distState->sliceList;
    totalBoundariesNum = GetTotalBoundariesNum(sliceList);
    sliceConstInfo = (SliceConstInfo*)palloc0(totalBoundariesNum * sizeof(SliceConstInfo));

    foreach_cell(sliceCell, sliceList) {
        slicedef = (ListSliceDefState*)lfirst(sliceCell);
        boundaryList = slicedef->boundaries;

        /* check that current slice have correct default boundaries */
        bool isDefault = PreCheckListSlice(slicedef);
        if (isDefault) {
            if (lnext(sliceCell) != NULL) {
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                        errmsg("DEFAULT slice must be last slice specified")));
            }
            continue;
        }

        foreach_cell(boundaryCell, boundaryList) {
            boundary = (List*)lfirst(boundaryCell);
            /* check whether each boundary within current slice has correct number of elements */
            CheckListDistEntry(boundary, distkeynum, isDefault);
            /* check whether exist the same boundaries */
            Const* curConstValue = GetPartitionValue(pos, attrs, boundary, is_intreval, false);
            constsList = lappend(constsList, curConstValue);
            sliceConstInfo[sliceCount].sliceName = slicedef->name;
            sliceConstInfo[sliceCount].sliceNum = distkeynum;
            sliceConstInfo[sliceCount].sliceBoundary = boundary;
            for (int counter = 0; counter < pos->length; counter++) {
                sliceConstInfo[sliceCount].sliceBoundaryValue[counter] = curConstValue + counter;
            }
            sliceCount++;
        }
    }
    IsDuplicateBoundariesExist(sliceConstInfo, totalBoundariesNum, distkeynum);
    if (constsList != NULL) {
        list_free_deep(constsList);
    }
    pfree_ext(sliceConstInfo);
    return;
}

static void CheckOneBoundaryValue(List* boundary, List* posList, TupleDesc desc)
{
    int pos;
    Const* srcConst = NULL;
    Const* targetConst = NULL;
    ListCell* boundaryCell = NULL;
    ListCell* posCell = NULL;
    FormData_pg_attribute* attrs = desc->attrs;

    forboth(boundaryCell, boundary, posCell, posList) {
        srcConst = (Const*)lfirst(boundaryCell);
        if (srcConst->ismaxvalue || srcConst->constisnull) {
            continue;
        }

        pos = lfirst_int(posCell);
        targetConst = (Const*)GetTargetValue(&attrs[pos], srcConst, false);
        if (!PointerIsValid(targetConst)) {
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_OPERATION),
                    errmsg("slice boundary value can't be converted into distribution key data type")));
        }
    }
}

/*
 * check whether every boundary value can be converted to distribute key type successfully
 */
static void CheckBoundaryValues(DistributeBy *distributeby, List* posList, TupleDesc desc)
{
    ListCell* cell = NULL;
    ListCell* boundaryCell = NULL;
    List* boundary = NULL;
    ListSliceDefState* listDef = NULL;
    RangePartitionDefState* rangeDef = NULL;

    if (distributeby->disttype == DISTTYPE_RANGE) {
        foreach(cell, distributeby->distState->sliceList) {
            rangeDef = (RangePartitionDefState*)lfirst(cell);
            CheckOneBoundaryValue(rangeDef->boundary, posList, desc);
        }
    } else {
        foreach(cell, distributeby->distState->sliceList) {
            listDef = (ListSliceDefState*)lfirst(cell);
            foreach(boundaryCell, listDef->boundaries) {
                boundary = (List*)lfirst(boundaryCell);
                CheckOneBoundaryValue(boundary, posList, desc);
            }
        }
    }

    return;
}

static void CheckDistStateBoundaryValue(DistributeBy *distributeby, List* posList, TupleDesc desc)
{
    int oldFormat = u_sess->attr.attr_sql.sql_compatibility;

    PG_TRY();
    {
        /*
         * some scenario should report error,
         * like: distribute by (int) (slice s1 values less than ('a')),
         * 'a' can't be convert to integer, references to int4in
         */
        u_sess->attr.attr_sql.sql_compatibility = A_FORMAT;
        CheckBoundaryValues(distributeby, posList, desc);
    }
    PG_CATCH();
    {
        u_sess->attr.attr_sql.sql_compatibility = oldFormat;
        PG_RE_THROW();
    }
    PG_END_TRY();

    u_sess->attr.attr_sql.sql_compatibility = oldFormat;

    return;
}

static void CheckDistBoundaries(DistributeBy *distributeby, TupleDesc desc)
{
    List* posList = NULL;

    if (distributeby == NULL || distributeby->distState == NULL) {
        return;
    }

    posList = GetDistColsPos(distributeby, desc);
    if (distributeby->disttype == DISTTYPE_RANGE) {
        /*
         * 1. check length of every slice boundaries
         * 2. check whether range slice boundaries ascending
         */
        ComparePartitionValue(posList, desc->attrs, distributeby->distState->sliceList, false);
    } else {
        /*
         * DISTTYPE_LIST
         * 1. check length of every slice boundaries
         * 2. check whether list slice boundaries unique
         */
        CheckDuplicateListSlices(posList, desc->attrs, distributeby);
    }

    CheckDistStateBoundaryValue(distributeby, posList, desc);

    list_free_ext(posList);
    return;
}

static void CheckNodeInNodeGroup(char** dnName, Oid dnOid, char* sliceName, Oid* nodeoids, int numnodes)
{
    bool isExist = false;
    for (int i = 0; i < numnodes; i++) {
        if (PGXCNodeGetNodeId(dnOid, PGXC_NODE_DATANODE) == PGXCNodeGetNodeId(nodeoids[i], PGXC_NODE_DATANODE)) {
            isExist = true;
            break;
        }
    }

    if (!isExist) {
        if (u_sess->attr.attr_sql.enable_cluster_resize) {
            /*
             * we should clear non-existed datanode name when cluster resizing,
             * e.g. create table (like distribution)
             * because the specified dn_name maybe doesn't exist after shrinking.
             */
            *dnName = NULL;
            ereport(WARNING,
                (errmsg("the specified datanode %s in slice %s is not in node group, ignore it when cluster resizing",
                    *dnName, sliceName)));
        } else {
            ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                    errmsg("datanode %s specified in slice %s is not contained in the specified node group",
                        *dnName, sliceName)));
        }
    }
}

static void CheckDistDatanodeValidity(DistributeBy* distributeby, Oid* nodeoids, int numnodes)
{
    ListCell* cell = NULL;
    Node* node = NULL;
    DistState* distState = NULL;
    Oid dnOid;

    if (distributeby == NULL || distributeby->distState == NULL) {
        return;
    }

    /*
     * Checks: 1. specified dn exists; 2. specified dn is contained in specifed node group.
     */
    distState = distributeby->distState;
    foreach (cell, distState->sliceList) {
        node = (Node*)lfirst(cell);
        switch (nodeTag(node)) {
            case T_RangePartitionDefState: {
                RangePartitionDefState *def = (RangePartitionDefState*)node;
                if (def->tablespacename != NULL) {
                    dnOid = get_pgxc_datanodeoid(def->tablespacename, false);
                    CheckNodeInNodeGroup(&def->tablespacename, dnOid, def->partitionName, nodeoids, numnodes);
                }
                break;
            }
            case T_ListSliceDefState: {
                ListSliceDefState* def = (ListSliceDefState*)node;
                if (def->datanode_name != NULL) {
                    dnOid = get_pgxc_datanodeoid(def->datanode_name, false);
                    CheckNodeInNodeGroup(&def->datanode_name, dnOid, def->name, nodeoids, numnodes);
                }
                break;
            }
            default:
                ereport(ERROR,
                    (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
                        errmsg("unrecognized node type: %d", (int)nodeTag(node))));
        }
    }

    return;
}

static void CheckSliceRefNodeGroup(DistributeBy *distributeby, char* groupName)
{
    Oid groupOid, baseGroupOid;

    /*
     * If groupname is NULL, it means we didn't specify GROUP in table creation,
     * so just use the default installation as target group
     */
    if (groupName == NULL) {
        groupName = PgxcGroupGetInstallationGroup();
    }
    groupOid = get_pgxc_groupoid(groupName, true);
    baseGroupOid = get_pgxc_class_groupoid(distributeby->referenceoid);
    if (baseGroupOid != groupOid) {
        char *baseGroupName = get_pgxc_groupname(baseGroupOid);
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_ATTRIBUTE),
                    errmsg("table's node group (%s) is not the same with referenced table's node group(%s)",
                        groupName, baseGroupName)));
    }

    return;
}

/*
 * Check whether base table can references the slice of another table
 */
static void CheckSliceReferenceValidity(Oid relid, DistributeBy *distributeby, TupleDesc descriptor, char* groupName)
{
    char baseType;
    Oid keyType;
    Oid baseKeyType;
    AttrNumber keyIdx;
    AttrNumber baseKeyIdx;
    DistributionType reftype;
    char *colname = NULL;
    RelationLocInfo* baseLocInfo = NULL;
    ListCell* colAttrCell = NULL;
    ListCell* colNameCell = NULL;

    /* check whether the two tables' distribute type are the same */
    baseLocInfo = GetRelationLocInfo(distributeby->referenceoid);
    if (baseLocInfo == NULL) {
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("The referenced table is not distributed")));
    }

    baseType = baseLocInfo->locatorType;
    reftype = distributeby->disttype;
    if (!((baseType == LOCATOR_TYPE_RANGE && reftype == DISTTYPE_RANGE) ||
        (baseType == LOCATOR_TYPE_LIST && reftype == DISTTYPE_LIST))) {
        FreeRelationLocInfo(baseLocInfo);
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("Assigned distribution strategy is not the same as that of base table")));
    }

    /* check whether the two tables' distribution key number are the same */
    if (list_length(distributeby->colname) != list_length(baseLocInfo->partAttrNum)) {
        FreeRelationLocInfo(baseLocInfo);
        ereport(ERROR,
            (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("number of table's distribution keys is not the same with referenced table.")));
    }

    /* check whether the two tables' distribution key type are the same */
    forboth(colAttrCell, baseLocInfo->partAttrNum, colNameCell, distributeby->colname) {
        baseKeyIdx = lfirst_int(colAttrCell);
        colname = strVal(lfirst(colNameCell));

        baseKeyType = get_atttype(distributeby->referenceoid, baseKeyIdx);
        keyIdx = get_attnum(relid, colname);
        keyType = descriptor->attrs[keyIdx - 1].atttypid;

        if (baseKeyType != keyType) {
            FreeRelationLocInfo(baseLocInfo);
            ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                    errmsg("Column %s is not of the same datatype as base table", colname)));
        }
    }

    /* check the two tables' node group are the same */
    CheckSliceRefNodeGroup(distributeby, groupName);

    FreeRelationLocInfo(baseLocInfo);
    return;
}

static char* GetRelationDistributionGroupName(PGXCSubCluster* subcluster, Oid* nodeoids, int numnodes, bool first)
{
    char *group_name = NULL;

    /* Now OK to insert data in catalog */
    if (subcluster != NULL && subcluster->clustertype == SUBCLUSTER_GROUP) {
        ListCell* lc = NULL;
        Assert(list_length(subcluster->members) == 1);
        foreach (lc, subcluster->members) {
            group_name = strVal(lfirst(lc));
            break;
        }
    } else if (subcluster != NULL && subcluster->clustertype == SUBCLUSTER_NODE) {
        /*
         * If we use the grammar "create table to node", when we support
         * multi-nodegroup, it must be converted to "create table to group",
         * but we are not sure to find the group correct through "to node".
         *
         * In non-logic cluster mode, we use "to node" for create table to installatation
         * group or redistribution group in restore mode, so we do this conversion here.
         *
         * In logic cluster mode, we use "to node" for create table to logic cluster
         * node group in restore mode.
         */
        group_name = DeduceGroupByNodeList(nodeoids, numnodes);
    } else if (first) {
        /*
         * Neither TO NODE nor TO GROUP is not specified in CREATE TABLE,
         * the foreign table and hdfs table is created in installation group.
         */
        group_name = PgxcGroupGetInstallationGroup();
    }

    /*
     * If groupname is NULL, it means we didn't specify GROUP in table creation,
     * so just use the default installation as target group
     */
    if (group_name == NULL) {
        if (isRestoreMode || strcmp(u_sess->attr.attr_sql.default_storage_nodegroup, INSTALLATION_MODE) == 0) {
            group_name = PgxcGroupGetInstallationGroup();
        } else {
            group_name = u_sess->attr.attr_sql.default_storage_nodegroup;
        }
    }

    return group_name;
}

/* --------------------------------
 *		AddRelationDistribution
 *
 *		Add to pgxc_class table
 * --------------------------------
 */
void AddRelationDistribution(const char *relname, Oid relid, DistributeBy* distributeby, PGXCSubCluster* subcluster,
    List* parentOids, TupleDesc descriptor, bool isinstallationgroup, bool isbucket, int bucketmaplen)
{
    char locatortype = '\0';
    int hashalgorithm = 0;
    int hashbuckets = 0;
    int2* attnum = NULL;
    ObjectAddress myself, referenced;
    int numnodes;
    Oid* nodeoids = NULL;
    int distributeKeyNum = 0;
    char* group_name = NULL;
    Oid   group_oid = InvalidOid;
    int   bucketCnt;

    if (distributeby != NULL && distributeby->colname) {
        distributeKeyNum = list_length(distributeby->colname);
        attnum = (int2*)palloc(distributeKeyNum * sizeof(int2));
    } else {
        distributeKeyNum = 1;
        attnum = (int2*)palloc(1 * sizeof(int2));
    }

    /* Obtain details of distribution information */
    GetRelationDistributionItems(relid, distributeby, descriptor, &locatortype, &hashalgorithm, &hashbuckets, attnum);

    /* Check whether include identical column in distribute key */
    CheckDistColsUnique(distributeby, distributeKeyNum, attnum);

    /* Check whether list/range boundaries valid */
    CheckDistBoundaries(distributeby, descriptor);

    /* Obtain details of nodes and classify them */
    nodeoids = GetRelationDistributionNodes(subcluster, &numnodes);

    /* Check Datanode validity */
    CheckDistDatanodeValidity(distributeby, nodeoids, numnodes);

    /* Determine node group name */
    group_name = GetRelationDistributionGroupName(subcluster, nodeoids, numnodes, isinstallationgroup);

    /* check if there is any child group can match the maplen */
    (void)BucketMapCacheGetBucketmap(group_name, &bucketCnt);
    if (bucketmaplen != 0 && bucketmaplen != bucketCnt) {
        bucketCnt = bucketmaplen;
        if (!is_pgxc_group_bucketcnt_exists(get_pgxc_groupoid(group_name, false), bucketmaplen, &group_name,
            &group_oid)) {
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_OPERATION),
                 errmsg("there is no group can match the bucket map length %d", bucketmaplen)));
        }
        Assert(OidIsValid(group_oid));
        t_thrd.xact_cxt.PGXCGroupOid = group_oid;
    }

    PgxcClassCreate(
        relid, locatortype, attnum, hashalgorithm, hashbuckets, numnodes, nodeoids, distributeKeyNum, group_name);

    if (IsLocatorDistributedBySlice(locatortype) && distributeby != NULL) {
        /* Report WARNING when slice count less than numnodes */
        if (!OidIsValid(distributeby->referenceoid) && list_length(distributeby->distState->sliceList) < numnodes) {
            ereport(NOTICE, (errmsg("table's slice count is less than datanode count.")));
        }

        /* Check whether base table and ref table have the same reqs */
        if (OidIsValid(distributeby->referenceoid)) {
            CheckSliceReferenceValidity(relid, distributeby, descriptor, group_name);
        }

        /*
         * generate a random startpos to reduce slice skewness.
         * we should guarantee startpos the same in all CNs when creating a list/range distribution table.
         * implement it later using ((uint32)random()) % ((uint32)numnodes),
         * and route the startpos to other CNs.
         */

        /* write slice information to pgxc_slice, keep startpos to 0 at present. */
        PgxcSliceCreate(relname, relid, distributeby, descriptor, nodeoids, (uint32)numnodes, 0);
    }

    pfree(attnum);
    /* Make dependency entries */
    myself.classId = PgxcClassRelationId;
    myself.objectId = relid;
    myself.objectSubId = 0;

    /* Dependency on relation */
    referenced.classId = RelationRelationId;
    referenced.objectId = relid;
    referenced.objectSubId = 0;
    recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
}

static const char* GetDistributeTypeName(DistributionType disttype)
{
    const char *str = "";
    switch (disttype) {
        case DISTTYPE_HASH:
            str = "hash";
            break;
        case DISTTYPE_REPLICATION:
            str = "replication";
            break;
        case DISTTYPE_MODULO:
            str = "modulo";
            break;
        case DISTTYPE_ROUNDROBIN:
            str = "roundrobin";
            break;
        case DISTTYPE_HIDETAG:
            str = "hidetag";
            break;
        case DISTTYPE_LIST:
            str = "list";
            break;
        case DISTTYPE_RANGE:
            str = "range";
            break;
        default:
            break;
    }

    return str;
}

static void CheckDistributeKeyAndType(Oid relid, DistributeBy *distributeby,
    TupleDesc descriptor, AttrNumber *attnum)
{
    int i = 0;
    AttrNumber localAttrNum = 0;
    char *colname = NULL;
    ListCell *cell = NULL;

    foreach (cell, distributeby->colname) {
        colname = strVal(lfirst(cell));
        localAttrNum = get_attnum(relid, colname);

        /*
         * Validate user-specified distribute column.
         * System columns cannot be used.
         */
        if (localAttrNum <= 0 && localAttrNum >= -(int)lengthof(SysAtt)) {
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                    errmsg("Invalid distribution column specified")));
        }

        if (distributeby->disttype == DISTTYPE_LIST || distributeby->disttype == DISTTYPE_RANGE) {
            if (!IsTypeDistributableForSlice(descriptor->attrs[localAttrNum - 1].atttypid)) {
                ereport(ERROR,
                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                        errmsg("Column %s is not a %s distributable data type", colname,
                            GetDistributeTypeName(distributeby->disttype))));
            }
        } else {
            if (!IsTypeDistributable(descriptor->attrs[localAttrNum - 1].atttypid)) {
                ereport(ERROR,
                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                        errmsg("Column %s is not a %s distributable data type", colname,
                            GetDistributeTypeName(distributeby->disttype))));
            }
        }

        attnum[i++] = localAttrNum;
    }
}

/*
 * GetRelationDistributionItems
 * Obtain distribution type and related items based on deparsed information
 * of clause DISTRIBUTE BY.
 * Depending on the column types given a fallback to a safe distribution can be done.
 */
void GetRelationDistributionItems(Oid relid, DistributeBy* distributeby, TupleDesc descriptor, char* locatortype,
    int* hashalgorithm, int* hashbuckets, AttrNumber* attnum)
{
    int local_hashalgorithm = 0;
    int local_hashbuckets = 0;
    char local_locatortype = '\0';
    AttrNumber local_attnum = 0;

    if (distributeby == NULL) {
        /*
         * If no distribution was specified, and we have not chosen
         * one based on primary key or foreign key, use first column with
         * a supported data type.
         */
        FormData_pg_attribute attr;
        int i;

        local_locatortype = LOCATOR_TYPE_HASH;

        for (i = 0; i < descriptor->natts; i++) {
            attr = descriptor->attrs[i];
            if (IsTypeDistributable(attr.atttypid)) {
                /* distribute on this column */
                local_attnum = i + 1;
                attnum[0] = local_attnum;
                Oid namespaceid = get_rel_namespace(relid);

                /*
                 * As create hdfs table will inner create a delta table. For preventing
                 * duplicate print notice, we check the relnamespace here to forbid
                 * print notice when create a delta table.
                 */
                if (!IsCStoreNamespace(namespaceid))
                    ereport(NOTICE,
                        (errcode(ERRCODE_SUCCESSFUL_COMPLETION),
                            errmsg("The 'DISTRIBUTE BY' clause is not specified. Using '%s' as the distribution column "
                                   "by default.",
                                attr.attname.data),
                            errhint(
                                "Please use 'DISTRIBUTE BY' clause to specify suitable data distribution column.")));
                break;
            }
        }
        if (local_attnum == 0) {
            FEATURE_NOT_PUBLIC_ERROR("There is no hash distributable column");
        }
        /* If we did not find a usable type, fall back to round robin */
        if (local_attnum == 0) {
            attnum[0] = local_attnum;
            local_locatortype = LOCATOR_TYPE_RROBIN;
        }
    } else {
        /*
         * User specified distribution type
         */
        switch (distributeby->disttype) {
            case DISTTYPE_HASH:
                CheckDistributeKeyAndType(relid, distributeby, descriptor, attnum);
                local_locatortype = LOCATOR_TYPE_HASH;
                break;
            case DISTTYPE_MODULO:
                CheckDistributeKeyAndType(relid, distributeby, descriptor, attnum);
                local_locatortype = LOCATOR_TYPE_MODULO;
                break;
            case DISTTYPE_REPLICATION:
                local_locatortype = LOCATOR_TYPE_REPLICATED;
                attnum[0] = local_attnum;
                break;
            case DISTTYPE_ROUNDROBIN:
                local_locatortype = LOCATOR_TYPE_RROBIN;
                attnum[0] = local_attnum;
                break;
            case DISTTYPE_LIST:
                CheckDistributeKeyAndType(relid, distributeby, descriptor, attnum);
                local_locatortype = LOCATOR_TYPE_LIST;
                break;
            case DISTTYPE_RANGE:
                CheckDistributeKeyAndType(relid, distributeby, descriptor, attnum);
                local_locatortype = LOCATOR_TYPE_RANGE;
                break;
            default:
                ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("Invalid distribution type")));
        }
    }

    /* Use default hash values */
    if (local_locatortype == LOCATOR_TYPE_HASH) {
        local_hashalgorithm = 1;
        local_hashbuckets = HASH_SIZE;
    }

    /* Save results */
    if (hashalgorithm != NULL)
        *hashalgorithm = local_hashalgorithm;
    if (hashbuckets != NULL)
        *hashbuckets = local_hashbuckets;
    if (locatortype != NULL)
        *locatortype = local_locatortype;
}

HashBucketInfo* GetRelationBucketInfo(DistributeBy* distributeby,
                                      TupleDesc tupledsc,
                                      bool* createbucket,
                                      bool hashbucket)
{
    Form_pg_attribute attr = NULL;
    int2vector* bucketkey = NULL;
    HashBucketInfo* bucketinfo = NULL;
    int nattr = tupledsc->natts;
    Oid buckettmp[BUCKETDATALEN];
    int bucketcnt = 0;
    bool needbucket = *createbucket;
    int i = 0;

    /* sanity check for bucket map */
    if (!isRestoreMode && needbucket == true && t_thrd.xact_cxt.PGXCNodeId == -1) {
        if (distributeby == NULL || distributeby->disttype != DISTTYPE_HASH) {
            if (hashbucket) {
                Assert(0);
                ereport(ERROR,
                    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                        errmsg("Create hash table directly on DN is not allowed.")));
            } else {
                ereport(LOG, (errmsg("Create table without bucket when directly created on dn.")));
                return NULL;
            }
        } else {
            Assert(0);
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("Create hash table but there is no valid PGXCBucketMap.")));
        }
    }

    bucketinfo = (HashBucketInfo*)palloc0(sizeof(HashBucketInfo));
    bucketinfo->bucketOid = InvalidOid;
    if (t_thrd.xact_cxt.PGXCNodeId == MAX_DN_NODE_NUM) {
        Assert(u_sess->attr.attr_sql.enable_cluster_resize);
        if (u_sess->attr.attr_sql.enable_cluster_resize && !isRestoreMode && needbucket == true) {
            *createbucket = false;
            ereport(LOG, (errmsg("Create table without bucket when PGXCNodeId is MAX_DN_NODE_NUM.")));

        } else {
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                    errmsg("Invalid BucketMap or wrong NodeId(%d)", MAX_DN_NODE_NUM)));
        }
    }
    /* get the bucket key columns from distribute by statement */
    if (distributeby == NULL) {
        bool found = false;

        bucketkey = buildint2vector(NULL, 1);
        for (i = 0; i < nattr; i++) {
            attr = &tupledsc->attrs[i];
            if (IsTypeDistributable(attr->atttypid)) {
                bucketkey->values[0] = attr->attnum;
                bucketinfo->bucketcol = bucketkey;
                found = true;
                break;
            }
        }

        if (!found) {
            if (hashbucket) {
                ereport(
                    ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("There is no hash distributable column")));
            } else {
                pfree(bucketinfo);
                return NULL;
            }
        }
    } else if (distributeby->disttype == DISTTYPE_HASH) {
        /*
         * User specified distribution type
         */
        AttrNumber local_attnum = 0;
        ListCell* cell = NULL;
        char* colname = NULL;
        bool* is_exist = NULL;
        int j;
        /*
         * Validate user-specified hash column.
         * System columns cannot be used.
         */
        bucketkey = buildint2vector(NULL, distributeby->colname->length);
        is_exist = (bool*)palloc0(nattr * sizeof(bool));

        foreach (cell, distributeby->colname) {
            colname = strVal(lfirst(cell));
            for (j = 0; j < nattr; j++) {
                attr = &tupledsc->attrs[j];
                if (strcmp(colname, attr->attname.data) == 0) {
                    local_attnum = attr->attnum;
                    break;
                }
            }

            if (j == nattr) {
                pfree(bucketkey);
                pfree(is_exist);
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("Invalid distribution column specified")));
            }

            if (!IsTypeDistributable(attr->atttypid)) {
                pfree(bucketkey);
                pfree(is_exist);
                ereport(ERROR,
                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                        errmsg("Column %s is not a hash distributable data type", colname)));
            }

            if (is_exist[local_attnum - 1] == true) {
                pfree(bucketkey);
                pfree(is_exist);
                ereport(ERROR, (errcode(ERRCODE_DUPLICATE_COLUMN), errmsg("duplicate partition key: %s", colname)));
            }
            bucketkey->values[i++] = local_attnum;
            is_exist[local_attnum - 1] = true;
        }
        pfree(is_exist);
        bucketinfo->bucketcol = bucketkey;
    } else {
        if (hashbucket) {
            ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    errmsg("distributeby no hash is invalid with hashbucket")));
        } else {
            pfree(bucketinfo);
            return NULL;
        }
    }

    if (*createbucket == true) {
        /* loop to get the bucket list on current node */
        if (isRestoreMode) {
            if (u_sess->storage_cxt.dumpHashbucketIds != NULL && u_sess->storage_cxt.dumpHashbucketIdNum != 0) {
                for (i = 0; i < u_sess->storage_cxt.dumpHashbucketIdNum; i++) {
                    buckettmp[bucketcnt++] = u_sess->storage_cxt.dumpHashbucketIds[i];
                }
            }
        } else {
            for (int i = 0; i < t_thrd.xact_cxt.PGXCBucketCnt; i++) {
                /*
                 * if bucket seqno equals to PGXCNodeId which get from CN then
                 * then we will add a bucket for current relation later
                 */
                if (t_thrd.xact_cxt.PGXCBucketMap[i] == t_thrd.xact_cxt.PGXCNodeId) {
                    buckettmp[bucketcnt++] = i;
                }
            }
        }
        if (bucketcnt == 0) {
            Assert(0);
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                    errmsg("Invalid BucketMap or wrong NodeId(%d)", t_thrd.xact_cxt.PGXCNodeId)));
        }
        /* make sure bucket map is in ascending order */
        qsort(buckettmp, bucketcnt, sizeof(Oid), bid_cmp);
        bucketinfo->bucketlist = buildoidvector(buckettmp, bucketcnt);
    }

    return bucketinfo;
}

/*
 * BuildRelationDistributionNodes
 * Build an unsorted node Oid array based on a node name list.
 */
Oid* BuildRelationDistributionNodes(List* nodes, int* numnodes)
{
    Oid* nodeoids = NULL;
    ListCell* item = NULL;

    *numnodes = 0;

    /* Allocate once enough space for OID array */
    nodeoids = (Oid*)palloc0(u_sess->pgxc_cxt.NumDataNodes * sizeof(Oid));

    /* Do process for each node name */
    foreach (item, nodes) {
        char* node_name = strVal(lfirst(item));
        Oid noid = get_pgxc_nodeoid(node_name);

        /* Check existence of node */
        if (!OidIsValid(noid))
            ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("PGXC Node %s: object not defined", node_name)));

        if (get_pgxc_nodetype(noid) != PGXC_NODE_DATANODE)
            ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("PGXC node %s: not a Datanode", node_name)));

        /* Can be added if necessary */
        if (*numnodes != 0) {
            bool is_listed = false;
            int i;

            /* Id Oid already listed? */
            for (i = 0; i < *numnodes; i++) {
                if (nodeoids[i] == noid) {
                    is_listed = true;
                    break;
                }
            }

            if (!is_listed) {
                (*numnodes)++;
                nodeoids[*numnodes - 1] = noid;
            }
        } else {
            (*numnodes)++;
            nodeoids[*numnodes - 1] = noid;
        }
    }

    return nodeoids;
}

/*
 * GetRelationDistributionNodes
 * Transform subcluster information generated by query deparsing of TO NODE or
 * TO GROUP clause into a sorted array of nodes OIDs.
 */
Oid* GetRelationDistributionNodes(PGXCSubCluster* subcluster, int* numnodes)
{
    ListCell* lc = NULL;
    Oid* nodes = NULL;

    *numnodes = 0;

    if (subcluster == NULL) {
        int i;

        if (!isRestoreMode) {
            /*
             * If no subcluster is defined, all the Datanodes are associated to the
             * table. So obtain list of node Oids currenly known to the session.
             * There could be a difference between the content of pgxc_node catalog
             * table and current session, because someone may change nodes and not
             * yet update session data.
             */
            *numnodes = u_sess->pgxc_cxt.NumDataNodes;

            /* No nodes found ?? */
            if (*numnodes == 0)
                ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("No Datanode defined in cluster")));

            nodes = (Oid*)palloc(u_sess->pgxc_cxt.NumDataNodes * sizeof(Oid));
            for (i = 0; i < u_sess->pgxc_cxt.NumDataNodes; i++)
                nodes[i] = PGXCNodeGetNodeOid(i, PGXC_NODE_DATANODE);
        } else {
            PgxcNodeGetOids(NULL, &nodes, NULL, numnodes, false);
            /* No nodes found ?? */
            if (*numnodes == 0)
                ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("No Datanode defined in cluster")));
        }
    }

    /* Build list of nodes from given group */
    if (nodes == NULL && subcluster->clustertype == SUBCLUSTER_GROUP) {
        Assert(list_length(subcluster->members) == 1);

        foreach (lc, subcluster->members) {
            const char* group_name = strVal(lfirst(lc));
            Oid group_oid = get_pgxc_groupoid(group_name);

            if (!OidIsValid(group_oid))
                ereport(
                    ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("PGXC Group %s: group not defined", group_name)));

            *numnodes = get_pgxc_groupmembers(group_oid, &nodes);
        }
    } else if (nodes == NULL) {
        /*
         * This is the case of a list of nodes names.
         * Here the result is a sorted array of node Oids
         */
        nodes = BuildRelationDistributionNodes(subcluster->members, numnodes);
    }

    /* Return a sorted array of node OIDs */
    return SortRelationDistributionNodes(nodes, *numnodes);
}

/*
 * SortRelationDistributionNodes
 * Sort elements in a node array.
 */
Oid* SortRelationDistributionNodes(Oid* nodeoids, int numnodes)
{
    qsort(nodeoids, numnodes, sizeof(Oid), cmp_nodes);
    return nodeoids;
}
#endif

/* --------------------------------
 *		AddNewRelationType -
 *
 *		define a composite type corresponding to the new relation
 * --------------------------------
 */
static ObjectAddress AddNewRelationType(const char* typname, Oid typeNamespace, Oid new_rel_oid, char new_rel_kind, Oid ownerid,
    Oid new_row_type, Oid new_array_type, Oid typbasetype)
{
    return TypeCreate(new_row_type, /* optional predetermined OID */
        typname,                    /* type name */
        typeNamespace,              /* type namespace */
        new_rel_oid,                /* relation oid */
        new_rel_kind,               /* relation kind */
        ownerid,                    /* owner's ID */
        -1,                         /* internal size (varlena) */
        TYPTYPE_COMPOSITE,          /* type-type (composite) */
        TYPCATEGORY_COMPOSITE,      /* type-category (ditto) */
        false,                      /* composite types are never preferred */
        DEFAULT_TYPDELIM,           /* default array delimiter */
        F_RECORD_IN,                /* input procedure */
        F_RECORD_OUT,               /* output procedure */
        F_RECORD_RECV,              /* receive procedure */
        F_RECORD_SEND,              /* send procedure */
        InvalidOid,                 /* typmodin procedure - none */
        InvalidOid,                 /* typmodout procedure - none */
        InvalidOid,                 /* analyze procedure - default */
        InvalidOid,                 /* array element type - irrelevant */
        false,                      /* this is not an array type */
        new_array_type,             /* array type if any */
        typbasetype,                /* domain base type - irrelevant */
        NULL,                       /* default value - none */
        NULL,                       /* default binary representation */
        false,                      /* passed by reference */
        'd',                        /* alignment - must be the largest! */
        'x',                        /* fully TOASTable */
        -1,                         /* typmod */
        0,                          /* array dimensions for typBaseType */
        false,                      /* Type NOT NULL */
        InvalidOid);                /* rowtypes never have a collation */
}

static Datum AddSegmentOption(Datum relOptions)
{
    DefElem *def = makeDefElem(pstrdup("segment"), (Node *)makeString((char *)"on"));
    List* optsList = untransformRelOptions(relOptions);
    optsList = lappend(optsList, def);

    return transformRelOptions((Datum)0, optsList, NULL, NULL, false, false);
}

static Datum AddRelrewriteOption(Datum relOptions, Oid relrewrite)
{
    DefElem *def = makeDefElem(pstrdup("relrewrite"), (Node *)makeInteger(relrewrite));
    List* optsList = untransformRelOptions(relOptions);
    optsList = lappend(optsList, def);

    return transformRelOptions((Datum)0, optsList, NULL, NULL, false, false);
}

Node* GetColumnRef(Node* key, bool* isExpr, bool* isFunc)
{
    Node* result = NULL;
    A_Expr* aexpr = NULL;
    Node* col = NULL;
    FuncCall *funcexpr = NULL;
    ListCell* cell = NULL;
    switch (key->type) {
        case T_ColumnRef:
            result = key;
            break;
        case T_A_Expr:
            *isExpr = true;
            aexpr = (A_Expr*)key;
            col = GetColumnRef(aexpr->lexpr, isExpr, isFunc);
            if(col)
                result = col;
            else
                result = GetColumnRef(aexpr->rexpr, isExpr, isFunc);
            break;
        case T_FuncCall:
            *isExpr = true;
            *isFunc = true;
            funcexpr = (FuncCall*)key;
            foreach(cell, funcexpr->args) {
                result = GetColumnRef((Node*)(lfirst(cell)), isExpr, isFunc);
                if (result)
                    break;
            }
            break;
        default:
            break;
    }
    return result;
}

static bool CheckPartitionExprKey(List* keys, char partStrategy, bool* isFunc)
{
    bool isExpr = false;
    ListCell* cell = NULL;
    foreach (cell, keys) {
        Node* col = GetColumnRef((Node*)lfirst(cell), &isExpr, isFunc);
        if (!col)
            ereport(ERROR,(errcode(ERRCODE_UNDEFINED_COLUMN),(errmsg("The partition key doesn't have any column."))));
        if (isExpr && keys->length > 1)
            ereport(ERROR,(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),(errmsg("The multi partition expr keys are not supported."))));
    }
    if (isExpr && (partStrategy == PART_STRATEGY_VALUE || partStrategy == PART_STRATEGY_INTERVAL))
        ereport(ERROR,(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),(errmsg("The partition expr key doesn't support value or interval partition"))));
    return !isExpr;
}

/* --------------------------------
 *		heap_create_with_catalog
 *
 *		creates a new cataloged relation.  see comments above.
 *
 * Arguments:
 *	relname: name to give to new rel
 *	relnamespace: OID of namespace it goes in
 *	reltablespace: OID of tablespace it goes in
 *	relid: OID to assign to new rel, or InvalidOid to select a new OID
 *	reltypeid: OID to assign to rel's rowtype, or InvalidOid to select one
 *	reloftypeid: if a typed table, OID of underlying type; else InvalidOid
 *	ownerid: OID of new rel's owner
 *	tupdesc: tuple descriptor (source of column definitions)
 *	cooked_constraints: list of precooked check constraints and defaults
 *	relkind: relkind for new rel
 *	relpersistence: rel's persistence status (permanent, temp, or unlogged)
 *	shared_relation: TRUE if it's to be a shared relation
 *	mapped_relation: TRUE if the relation will use the relfilenode map
 *	oidislocal: TRUE if oid column (if any) should be marked attislocal
 *	oidinhcount: attinhcount to assign to oid column (if any)
 *	oncommit: ON COMMIT marking (only relevant if it's a temp table)
 *	reloptions: reloptions in Datum form, or (Datum) 0 if none
 *	use_user_acl: TRUE if should look for user-defined default permissions;
 *		if FALSE, relacl is always set NULL
 *	allow_system_table_mods: TRUE to allow creation in system namespaces
 * Output parameters:
 * typaddress: if not null, gets the object address of the new pg_type entry
 *
 * Returns the OID of the new relation
 * --------------------------------
 */
Oid heap_create_with_catalog(const char *relname, Oid relnamespace, Oid reltablespace, Oid relid, Oid reltypeid,
                             Oid reloftypeid, Oid ownerid, TupleDesc tupdesc, List *cooked_constraints, char relkind,
                             char relpersistence, bool shared_relation, bool mapped_relation, bool oidislocal,
                             int oidinhcount, OnCommitAction oncommit, Datum reloptions, bool use_user_acl,
                             bool allow_system_table_mods, PartitionState *partTableState, int8 row_compress,
                             HashBucketInfo *bucketinfo, bool record_dependce, List *ceLst, StorageType storage_type,
                             LOCKMODE partLockMode, ObjectAddress *typaddress, List* depend_extend, Oid relrewrite,
                             Oid typbasetype)
{
    Relation pg_class_desc;
    Relation new_rel_desc;
    Relation pg_partition_desc = NULL;
    Acl* relacl = NULL;
    Oid existing_relid;
    Oid old_type_oid;
    Oid new_type_oid = InvalidOid;
    Oid new_array_oid = InvalidOid;
    MemoryContext old_context;
    Oid relfileid = InvalidOid;
    Oid relbucketOid = InvalidOid;
    int2vector* bucketcol = NULL;
    bool relhasbucket = false;
    ObjectAddress new_type_addr;
    bool relhasuids = false;

    if ((IsInitdb && EnableInitDBSegment) || (u_sess->attr.attr_common.IsInplaceUpgrade && ENABLE_DMS)) {
        if (relpersistence == RELPERSISTENCE_UNLOGGED) {
            relpersistence = RELPERSISTENCE_PERMANENT;
            ereport(WARNING,
                (errmsg("Store unlogged table in segment when enable system table segment")));
        }
        /* store tables in segment storage as all possible while initdb */
        if (relpersistence == RELPERSISTENCE_PERMANENT && (relkind == RELKIND_RELATION ||
            relkind == RELKIND_INDEX || relkind == RELKIND_GLOBAL_INDEX)) {
            storage_type = SEGMENT_PAGE;
            reloptions = AddSegmentOption(reloptions);
        }
    }

    pg_class_desc = heap_open(RelationRelationId, RowExclusiveLock);

    /*
     * sanity checks
     */
    Assert(IsNormalProcessingMode() || IsBootstrapProcessingMode());

    CheckAttributeNamesTypes(tupdesc, relkind, allow_system_table_mods);

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

    /*
     * This would fail later on anyway, if the relation already exists.  But
     * by catching it here we can emit a nicer error message.
     */
    existing_relid = get_relname_relid(relname, relnamespace);
    if (existing_relid != InvalidOid) {
        Oid namespaceid_of_existing_rel = get_rel_namespace(existing_relid);
        char* namespace_of_existing_rel = get_namespace_name(namespaceid_of_existing_rel);
        ereport(ERROR, (errmodule(MOD_COMMAND), errcode(ERRCODE_DUPLICATE_TABLE),
            errmsg("relation \"%s\" already exists in schema \"%s\"", relname, namespace_of_existing_rel),
            errdetail("creating new table with existing name in the same schema"),
            errcause("the name of the table being created already exists"),
            erraction("change a new name")));
    }

    /*
     * Since we are going to create a rowtype as well, also check for
     * collision with an existing type name.  If there is one and it's an
     * autogenerated array, we can rename it out of the way; otherwise we can
     * at least give a good error message.
     */
    old_type_oid = GetSysCacheOid2(TYPENAMENSP, CStringGetDatum(relname), ObjectIdGetDatum(relnamespace));
    if (OidIsValid(old_type_oid)) {
        if (!moveArrayTypeName(old_type_oid, relname, relnamespace))
            ereport(ERROR,
                (errcode(ERRCODE_DUPLICATE_OBJECT),
                    errmsg("type \"%s\" already exists", relname),
                    errhint("A relation has an associated type of the same name, "
                            "so you must use a name that doesn't conflict "
                            "with any existing type.")));
    }

    /*
     * Shared relations must be in pg_global (last-ditch check)
     */
    if (shared_relation && reltablespace != GLOBALTABLESPACE_OID)
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                errmsg("shared relations must be placed in pg_global tablespace")));

    /*
     * Allocate an OID for the relation, unless we were told what to use.
     *
     * The OID will be the relfilenode as well, so make sure it doesn't
     * collide with either pg_class OIDs or existing physical files.
     */
    if (!OidIsValid(relid)) {
        bool is_partition_toast_tbl = strncmp(relname, "pg_toast_part", strlen("pg_toast_part")) == 0;
        /*
         * Use binary-upgrade override for pg_class.oid/relfilenode, if
         * supplied.
         */
        if (u_sess->proc_cxt.IsBinaryUpgrade && OidIsValid(u_sess->upg_cxt.binary_upgrade_next_heap_pg_class_oid) &&
            (relkind == RELKIND_RELATION || RELKIND_IS_SEQUENCE(relkind) || relkind == RELKIND_VIEW ||
            relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE || relkind == RELKIND_MATVIEW
            || relkind == RELKIND_STREAM || relkind == RELKIND_CONTQUERY)) {
            relid = u_sess->upg_cxt.binary_upgrade_next_heap_pg_class_oid;
            u_sess->upg_cxt.binary_upgrade_next_heap_pg_class_oid = InvalidOid;

            relfileid = u_sess->upg_cxt.binary_upgrade_next_heap_pg_class_rfoid;
            u_sess->upg_cxt.binary_upgrade_next_heap_pg_class_rfoid = InvalidOid;

        } else if (u_sess->proc_cxt.IsBinaryUpgrade && !is_partition_toast_tbl &&
                   OidIsValid(u_sess->upg_cxt.binary_upgrade_next_toast_pg_class_oid) &&
                   relkind == RELKIND_TOASTVALUE) {
            relid = u_sess->upg_cxt.binary_upgrade_next_toast_pg_class_oid;
            u_sess->upg_cxt.binary_upgrade_next_toast_pg_class_oid = InvalidOid;

            relfileid = u_sess->upg_cxt.binary_upgrade_next_toast_pg_class_rfoid;
            u_sess->upg_cxt.binary_upgrade_next_toast_pg_class_rfoid = InvalidOid;
        } else if (u_sess->proc_cxt.IsBinaryUpgrade &&
                   (is_partition_toast_tbl && binary_upgrade_is_next_part_toast_pg_class_oid_valid()) &&
                   relkind == RELKIND_TOASTVALUE) {
            relid = binary_upgrade_get_next_part_toast_pg_class_oid();
            relfileid = binary_upgrade_get_next_part_toast_pg_class_rfoid();
        } else if (u_sess->attr.attr_common.IsInplaceUpgrade &&
                   OidIsValid(u_sess->upg_cxt.Inplace_upgrade_next_heap_pg_class_oid) &&
                   (relkind == RELKIND_RELATION || RELKIND_IS_SEQUENCE(relkind) || relkind == RELKIND_VIEW ||
                   relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE
                   || relkind == RELKIND_STREAM || relkind == RELKIND_CONTQUERY)) {
            relid = u_sess->upg_cxt.Inplace_upgrade_next_heap_pg_class_oid;
            u_sess->upg_cxt.Inplace_upgrade_next_heap_pg_class_oid = InvalidOid;
        } else if (u_sess->attr.attr_common.IsInplaceUpgrade &&
                   OidIsValid(u_sess->upg_cxt.Inplace_upgrade_next_toast_pg_class_oid) &&
                   relkind == RELKIND_TOASTVALUE) {
            /* So far, system catalogs are not partitioned. */
            Assert(!is_partition_toast_tbl);

            relid = u_sess->upg_cxt.Inplace_upgrade_next_toast_pg_class_oid;
            u_sess->upg_cxt.Inplace_upgrade_next_toast_pg_class_oid = InvalidOid;
        } else
            relid = GetNewRelFileNode(reltablespace, pg_class_desc, relpersistence);
    }

    /*
     * Determine the relation's initial permissions.
     */
    if (use_user_acl) {
        switch (relkind) {
            case RELKIND_RELATION:
            case RELKIND_VIEW:
            case RELKIND_CONTQUERY:
            case RELKIND_MATVIEW:
            case RELKIND_FOREIGN_TABLE:
            case RELKIND_STREAM:
                relacl = get_user_default_acl(ACL_OBJECT_RELATION, ownerid, relnamespace);
                break;
            case RELKIND_SEQUENCE:
            case RELKIND_LARGE_SEQUENCE: // tbf
                relacl = get_user_default_acl(ACL_OBJECT_SEQUENCE, ownerid, relnamespace);
                break;
            default:
                relacl = NULL;
                break;
        }
    } else
        relacl = NULL;

    /*
     * Determine the relation's bucket info.
     */
    if (bucketinfo != NULL && !OidIsValid(bucketinfo->bucketOid)) {
        Oid bucketid;
        bucketcol = bucketinfo->bucketcol;

        Assert(bucketcol != NULL);
        if (bucketinfo->bucketlist != NULL) {
            bucketid =
                hash_any((unsigned char*)bucketinfo->bucketlist->values, bucketinfo->bucketlist->dim1 * sizeof(Oid));

            relbucketOid = searchHashBucketByBucketid(bucketinfo->bucketlist, bucketid);

            if (!OidIsValid(relbucketOid)) {
                relbucketOid = insertHashBucketEntry(bucketinfo->bucketlist, bucketid);
            }
        } else {
            relbucketOid = VirtualBktOid;
        }

        relhasbucket = true;

    } else if (bucketinfo != NULL) {
        relbucketOid = bucketinfo->bucketOid;
        Assert(OidIsValid(relbucketOid));
        relhasbucket = true;
    }

    if (OidIsValid(relrewrite)) {
        reloptions = AddRelrewriteOption(reloptions, relrewrite);
    }

    /* Get tableAmType from reloptions and relkind */
    bytea* hreloptions = heap_reloptions(relkind, reloptions, false);
    TableAmType tam = get_tableam_from_reloptions(hreloptions, relkind, InvalidOid);
    int8 indexsplit = get_indexsplit_from_reloptions(hreloptions, InvalidOid);

    /* Get uids info from reloptions */
    relhasuids = StdRdOptionsHasUids(hreloptions, relkind);
    PartitionExprKeyInfo partExprKeyInfo = PartitionExprKeyInfo();
    bool subpartkeyexprIsNull = true;
    bool subpartkeyIsFunc = false;
    if (partTableState) {
        partExprKeyInfo.partkeyexprIsNull = CheckPartitionExprKey(partTableState->partitionKey, partTableState->partitionStrategy, &(partExprKeyInfo.partkeyIsFunc));
        if (partTableState->subPartitionState)
            subpartkeyexprIsNull = CheckPartitionExprKey(partTableState->subPartitionState->partitionKey,
                                                         partTableState->subPartitionState->partitionStrategy, &subpartkeyIsFunc);
    }
    /*
     * Create the relcache entry (mostly dummy at this point) and the physical
     * disk file.  (If we fail further down, it's the smgr's responsibility to
     * remove the disk file again.)
     */

    new_rel_desc = heap_create(relname,
        relnamespace,
        reltablespace,
        relid,
        relfileid,
        (partTableState == NULL) ? relbucketOid : InvalidOid,
        tupdesc,
        relkind,
        relpersistence,
        partTableState ? (partTableState->partitionStrategy == PART_STRATEGY_VALUE ? false : true) : false,
        partTableState ? (partTableState->rowMovement == ROWMOVEMENT_ENABLE ? true : false) : false,
        shared_relation,
        mapped_relation,
        allow_system_table_mods,
        row_compress,
        reloptions,
        ownerid,
        false,
        tam,
        indexsplit,
        storage_type
    );

    if (OidIsValid(relrewrite)) {
        Relation OldHeap = heap_open(relrewrite, AccessExclusiveLock);
        new_rel_desc->relreplident = OldHeap->relreplident;
        heap_close(OldHeap, AccessExclusiveLock);
    }

    /* Recode the table or other object in pg_class create time. */
    PgObjectType objectType = GetPgObjectTypePgClass(relkind);
    if (objectType != OBJECT_TYPE_INVALID) {
        PgObjectOption objectOpt = {true, true, true, true};
        CreatePgObject(relid, objectType, ownerid, objectOpt);
    }

    Assert(relid == RelationGetRelid(new_rel_desc));

    new_rel_desc->rd_bucketoid = relbucketOid;
    /*
     * Decide whether to create an array type over the relation's rowtype. We
     * do not create any array types for system catalogs (ie, those made
     * during initdb).	We create array types for regular relations, views,
     * composite types and foreign tables ... but not, eg, for toast tables or
     * sequences.
     */
    if ((IsUnderPostmaster && !u_sess->attr.attr_common.IsInplaceUpgrade &&
        (relkind == RELKIND_RELATION || relkind == RELKIND_VIEW || relkind == RELKIND_FOREIGN_TABLE ||
        relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_MATVIEW || relkind == RELKIND_STREAM ||
        relkind == RELKIND_CONTQUERY)) ||
        (strcmp(relname, "bulk_exception") == 0) || (strcmp(relname, "desc_rec") == 0))
        new_array_oid = AssignTypeArrayOid();

    /*
     * Since defining a relation also defines a complex type, we add a new
     * system type corresponding to the new relation.  The OID of the type can
     * be preselected by the caller, but if reltypeid is InvalidOid, we'll
     * generate a new OID for it.
     */

    /* save the CurrentMemoryContext, so we can switch to it later */
    old_context = CurrentMemoryContext;

    /*
     * try to report a nicer error message rather than 'duplicate key'
     */
    PG_TRY();
    {
        /*
         * NOTE: we could get a unique-index failure here, in case someone else is
         * creating the same type name in parallel but hadn't committed yet when
         * we checked for a duplicate name above. in such case we try to emit a
         * nicer error message using try...catch
         */
        new_type_addr = AddNewRelationType(
            relname,
            relnamespace,
            relid,
            relkind,
            ownerid,
            reltypeid,
            new_array_oid,
            typbasetype);
        new_type_oid = new_type_addr.objectId;
        if (typaddress)
            *typaddress = new_type_addr;

    }
    PG_CATCH();
    {
        ErrorData* edata = NULL;

        /*
         * if we are in here we are most likely in ErrorContext
         * we have to switch to the old_context to allowing for CopyErrorData()
         * because FlushErrorState will free everything in ErrorContext
         */
        (void*)MemoryContextSwitchTo(old_context);
        edata = CopyErrorData();

        if (edata->sqlerrcode == ERRCODE_UNIQUE_VIOLATION) {
            FlushErrorState();
            /* the old edata is no longer used */
            FreeErrorData(edata);

            ereport(ERROR,
                (errcode(ERRCODE_DUPLICATE_TABLE),
                    errmsg("relation \"%s\" already exists", relname),
                    errdetail("because of creating the same table in parallel")));

        } else {
            /* we still reporting other error messages */
            ReThrowError(edata);
        }
    }
    PG_END_TRY();

    /*
     * Now make the array type if wanted.
     */
    if (OidIsValid(new_array_oid)) {
        char* relarrayname = NULL;

        relarrayname = makeArrayTypeName(relname, relnamespace);

        (void)TypeCreate(new_array_oid, /* force the type's OID to this */
            relarrayname,               /* Array type name */
            relnamespace,               /* Same namespace as parent */
            InvalidOid,                 /* Not composite, no relationOid */
            0,                          /* relkind, also N/A here */
            ownerid,                    /* owner's ID */
            -1,                         /* Internal size (varlena) */
            TYPTYPE_BASE,               /* Not composite - typelem is */
            TYPCATEGORY_ARRAY,          /* type-category (array) */
            false,                      /* array types are never preferred */
            DEFAULT_TYPDELIM,           /* default array delimiter */
            F_ARRAY_IN,                 /* array input proc */
            F_ARRAY_OUT,                /* array output proc */
            F_ARRAY_RECV,               /* array recv (bin) proc */
            F_ARRAY_SEND,               /* array send (bin) proc */
            InvalidOid,                 /* typmodin procedure - none */
            InvalidOid,                 /* typmodout procedure - none */
            F_ARRAY_TYPANALYZE,         /* array analyze procedure */
            new_type_oid,               /* array element type - the rowtype */
            true,                       /* yes, this is an array type */
            InvalidOid,                 /* this has no array type */
            InvalidOid,                 /* domain base type - irrelevant */
            NULL,                       /* default value - none */
            NULL,                       /* default binary representation */
            false,                      /* passed by reference */
            'd',                        /* alignment - must be the largest! */
            'x',                        /* fully TOASTable */
            -1,                         /* typmod */
            0,                          /* array dimensions for typBaseType */
            false,                      /* Type NOT NULL */
            InvalidOid);                /* rowtypes never have a collation */

        pfree(relarrayname);
    }

    /*
     * for value-partitioned relation, we need set rd_rel->parttype here before
     * insert pg_class enrty
     */
    if (partTableState != NULL && partTableState->partitionStrategy == PART_STRATEGY_VALUE) {
        new_rel_desc->rd_rel->parttype = PARTTYPE_VALUE_PARTITIONED_RELATION;
    }

    if (partTableState != NULL && partTableState->subPartitionState != NULL) {
        new_rel_desc->rd_rel->parttype = PARTTYPE_SUBPARTITIONED_RELATION;
    }

    /*
     * now create an entry in pg_class for the relation.
     *
     * NOTE: we could get a unique-index failure here, in case someone else is
     * creating the same relation name in parallel but hadn't committed yet
     * when we checked for a duplicate name above.
     */
    AddNewRelationTuple(pg_class_desc,
        new_rel_desc,
        relid,
        new_type_oid,
        reloftypeid,
        ownerid,
        relkind,
        relpersistence,
        PointerGetDatum(relacl),
        reloptions,
        bucketcol,
        (partTableState != NULL));
    /*
     * now add tuples to pg_attribute for the attributes in our new relation.
     */
    AddNewAttributeTuples(
        relid, new_rel_desc->rd_att, relkind, oidislocal, oidinhcount, relhasbucket, relhasuids, depend_extend, relname, relnamespace);
    if (ceLst != NULL) {
        AddNewGsSecEncryptedColumnsTuples(relid, ceLst);
    }
    if (partTableState && (RELKIND_TOASTVALUE != relkind)) {
        pg_partition_desc = heap_open(PartitionRelationId, RowExclusiveLock);

        if (partTableState->partitionStrategy == PART_STRATEGY_VALUE) {
            /* add partitioned table entry to pg_partition */
            addNewPartitionTupleForValuePartitionedTable(
                pg_partition_desc, relname, relid, reltablespace, tupdesc, partTableState, reloptions);
        } else {
            /* add partitioned table entry to pg_partition */
            addNewPartitionTupleForTable(pg_partition_desc, /*RelationData pointer for pg_partition*/
                relname,                                    /*partitioned table's name*/
                relid,                                      /*partitioned table's oid in pg_class*/
                reltablespace,                              /*partitioned table's tablespace*/
                tupdesc,                                    /*partitioned table's tuple descriptor*/
                partTableState,                             /*partition schema of partitioned table*/
                ownerid,                                    /*partitioned table's owner id*/
                reloptions,
                &partExprKeyInfo);

            partExprKeyInfo.partkeyexprIsNull = subpartkeyexprIsNull;
            partExprKeyInfo.partkeyIsFunc = subpartkeyIsFunc;
            /* add partition entry to pg_partition */
            addNewPartitionTuplesForPartition(pg_partition_desc, /*RelationData pointer for pg_partition*/
                relid,                                           /*partitioned table's oid in pg_class*/
                reltablespace,                                   /*partitioned table's tablespace*/
                relbucketOid,                                    /*partitioned table's bucket info*/
                partTableState,                                  /*partition schema of partitioned table*/
                ownerid,                                         /*partitioned table's owner id*/
                reloptions,
                tupdesc,
                partTableState->partitionStrategy,
                storage_type,
                partLockMode,
                &partExprKeyInfo);
        }
    }

    /*
     * Make a dependency link to force the relation to be deleted if its
     * namespace is.  Also make a dependency link to its owner, as well as
     * dependencies for any roles mentioned in the default ACL.
     *
     * For composite types, these dependencies are tracked for the pg_type
     * entry, so we needn't record them here.  Likewise, TOAST tables don't
     * need a namespace dependency (they live in a pinned namespace) nor an
     * owner dependency (they depend indirectly through the parent table), nor
     * should they have any ACL entries.  The same applies for extension
     * dependencies.
     *
     * If it's a temp table, we do not make it an extension member; this
     * prevents the unintuitive result that deletion of the temp table at
     * session end would make the whole extension go away.
     *
     * Also, skip this in bootstrap mode, since we don't make dependencies
     * while bootstrapping.
     */
    if (record_dependce == true && relkind != RELKIND_COMPOSITE_TYPE && relkind != RELKIND_TOASTVALUE &&
        !IsBootstrapProcessingMode()) {
        ObjectAddress myself;

        myself.classId = RelationRelationId;
        myself.objectId = relid;
        myself.objectSubId = 0;

        if (u_sess->attr.attr_common.IsInplaceUpgrade && myself.objectId < FirstBootstrapObjectId)
            recordPinnedDependency(&myself);
        else {
            ObjectAddress referenced;

            referenced.classId = NamespaceRelationId;
            referenced.objectId = relnamespace;
            referenced.objectSubId = 0;
            recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);

            recordDependencyOnOwner(RelationRelationId, relid, ownerid);

            if (relpersistence != RELPERSISTENCE_TEMP)
                recordDependencyOnCurrentExtension(&myself, false);

            if (reloftypeid) {
                referenced.classId = TypeRelationId;
                referenced.objectId = reloftypeid;
                referenced.objectSubId = 0;
                recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
            }

            if (relacl != NULL) {
                int nnewmembers;
                Oid* newmembers = NULL;

                nnewmembers = aclmembers(relacl, &newmembers);
                updateAclDependencies(RelationRelationId, relid, 0, ownerid, 0, NULL, nnewmembers, newmembers);
            }
        }
    }

    /* Post creation hook for new relation */
    InvokeObjectAccessHook(OAT_POST_CREATE, RelationRelationId, relid, 0, NULL);

    /*
     * Store any supplied constraints and defaults.
     *
     * NB: this may do a CommandCounterIncrement and rebuild the relcache
     * entry, so the relation must be valid and self-consistent at this point.
     * In particular, there are not yet constraints and defaults anywhere.
     */
    StoreConstraints(new_rel_desc, cooked_constraints);

    /*
     * If there's a special on-commit action, remember it
     */
    if (oncommit != ONCOMMIT_NOOP)
        register_on_commit_action(relid, oncommit);

    if (relpersistence == RELPERSISTENCE_UNLOGGED) {
        Assert(RELKIND_IN_RTM);
        heap_create_init_fork(new_rel_desc);
    }

    /*
     * ok, the relation has been cataloged, so close our relations and return
     * the OID of the newly created relation.
     */
    heap_close(new_rel_desc, NoLock); /* do not unlock till end of xact */
    heap_close(pg_class_desc, RowExclusiveLock);

    if (pg_partition_desc) {
        heap_close(pg_partition_desc, RowExclusiveLock);
    }

    return relid;
}

/*
 * Set up an init fork for an unlogged table so that it can be correctly
 * reinitialized on restart.  An immediate sync is required even if the
 * page has been logged, because the write did not go through
 * shared_buffers and therefore a concurrent checkpoint may have moved
 * the redo pointer past our xlog record.  Recovery may as well remove it
 * while replaying, for example, XLOG_DBASE_CREATE or XLOG_TBLSPC_CREATE
 * record. Therefore, logging is necessary even if wal_level=minimal.
 */
void heap_create_init_fork(Relation rel)
{
    RelationOpenSmgr(rel);
    smgrcreate(rel->rd_smgr, INIT_FORKNUM, false);
    log_smgrcreate(&rel->rd_smgr->smgr_rnode.node, INIT_FORKNUM);
    smgrimmedsync(rel->rd_smgr, INIT_FORKNUM);
}

/*
 *		RelationRemoveInheritance
 *
 * Formerly, this routine checked for child relations and aborted the
 * deletion if any were found.	Now we rely on the dependency mechanism
 * to check for or delete child relations.	By the time we get here,
 * there are no children and we need only remove any pg_inherits rows
 * linking this relation to its parent(s).
 */
static void RelationRemoveInheritance(Oid relid)
{
    Relation catalogRelation;
    SysScanDesc scan;
    ScanKeyData key;
    HeapTuple tuple;

    catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);

    ScanKeyInit(&key, Anum_pg_inherits_inhrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relid));

    scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true, NULL, 1, &key);

    while (HeapTupleIsValid(tuple = systable_getnext(scan)))
        simple_heap_delete(catalogRelation, &tuple->t_self);

    systable_endscan(scan);
    heap_close(catalogRelation, RowExclusiveLock);
}

/*
 *		DeleteRelationTuple
 *
 * Remove pg_class row for the given relid.
 *
 * Note: this is shared by relation deletion and index deletion.  It's
 * not intended for use anyplace else.
 */
void DeleteRelationTuple(Oid relid)
{
    Relation pg_class_desc;
    HeapTuple tup;

    /* Grab an appropriate lock on the pg_class relation */
    pg_class_desc = heap_open(RelationRelationId, RowExclusiveLock);

    tup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
    if (!HeapTupleIsValid(tup))
        ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", relid)));

    /* delete the relation tuple from pg_class, and finish up */
    simple_heap_delete(pg_class_desc, &tup->t_self);

    ReleaseSysCache(tup);

    heap_close(pg_class_desc, RowExclusiveLock);
}

/*
 *		DeleteAttributeTuples
 *
 * Remove pg_attribute rows for the given relid.
 *
 * Note: this is shared by relation deletion and index deletion.  It's
 * not intended for use anyplace else.
 */
void DeleteAttributeTuples(Oid relid)
{
    Relation attrel;
    SysScanDesc scan;
    ScanKeyData key[1];
    HeapTuple atttup;

    /* Grab an appropriate lock on the pg_attribute relation */
    attrel = heap_open(AttributeRelationId, RowExclusiveLock);

    /* Use the index to scan only attributes of the target relation */
    ScanKeyInit(&key[0], Anum_pg_attribute_attrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relid));

    scan = systable_beginscan(attrel, AttributeRelidNumIndexId, true, NULL, 1, key);

    /* Delete all the matching tuples */
    while ((atttup = systable_getnext(scan)) != NULL)
        simple_heap_delete(attrel, &atttup->t_self);

    /* Clean up after the scan */
    systable_endscan(scan);
    heap_close(attrel, RowExclusiveLock);
}

/*
 *		DeleteSystemAttributeTuples
 *
 * Remove pg_attribute rows for system columns of the given relid.
 *
 * Note: this is only used when converting a table to a view.  Views don't
 * have system columns, so we should remove them from pg_attribute.
 */
void DeleteSystemAttributeTuples(Oid relid)
{
    Relation attrel;
    SysScanDesc scan;
    ScanKeyData key[2];
    HeapTuple atttup;

    /* Grab an appropriate lock on the pg_attribute relation */
    attrel = heap_open(AttributeRelationId, RowExclusiveLock);

    /* Use the index to scan only system attributes of the target relation */
    ScanKeyInit(&key[0], Anum_pg_attribute_attrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relid));
    ScanKeyInit(&key[1], Anum_pg_attribute_attnum, BTLessEqualStrategyNumber, F_INT2LE, Int16GetDatum(0));

    scan = systable_beginscan(attrel, AttributeRelidNumIndexId, true, NULL, 2, key);

    /* Delete all the matching tuples */
    while ((atttup = systable_getnext(scan)) != NULL)
        simple_heap_delete(attrel, &atttup->t_self);

    /* Clean up after the scan */
    systable_endscan(scan);
    heap_close(attrel, RowExclusiveLock);
}

/*
 *		RemoveAttributeById
 *
 * This is the guts of ALTER TABLE DROP COLUMN: actually mark the attribute
 * deleted in pg_attribute.  We also remove pg_statistic entries for it.
 * (Everything else needed, such as getting rid of any pg_attrdef entry,
 * is handled by dependency.c.)
 *
 * During inplace or online upgrade, we may need to delete newly added columns
 * to perform upgrade rollback. In that case, we remove the attribute's
 * pg_attribute entry and decrease the relation's relnatts in pg_class.
 * We don't need to rewrite the whole catalog, since there should be no entry
 * of new schema version or, even if there was, they should have been removed
 * during proceeding rollback procedure.
 */
void RemoveAttributeById(Oid relid, AttrNumber attnum)
{
    Relation rel = NULL;
    Relation attr_rel = NULL;
    Relation pgclass = NULL;
    HeapTuple reltuple;
    HeapTuple atttuple;
    Form_pg_attribute attStruct;
    char newattname[NAMEDATALEN];
    bool isRedisDropColumn = false;
    Datum values[Natts_pg_attribute] = { 0 };
    bool nulls[Natts_pg_attribute] = { 0 };
    bool replaces[Natts_pg_attribute] = { 0 };
    const int keyNum = 2;
    ScanKeyData key[keyNum];
    SysScanDesc scan;
    HeapTuple newatttuple;

    /*
     * Grab an exclusive lock on the target table, which we will NOT release
     * until end of transaction.  (In the simple case where we are directly
     * dropping this column, AlterTableDropColumn already did this ... but
     * when cascading from a drop of some other object, we may not have any
     * lock.)
     */
    rel = relation_open(relid, AccessExclusiveLock);

    attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);

    ScanKeyInit(&key[0], Anum_pg_attribute_attrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relid));
    ScanKeyInit(&key[1], Anum_pg_attribute_attnum, BTEqualStrategyNumber, F_INT2EQ, Int16GetDatum(attnum));
    scan = systable_beginscan(attr_rel, AttributeRelidNumIndexId, true, SnapshotSelf, keyNum, key);
    atttuple = systable_getnext(scan);
    if (!HeapTupleIsValid(atttuple)) /* shouldn't happen */
    {
        Assert(0);
        ereport(ERROR,
            (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                errmsg("cache lookup failed for attribute %d of relation %u", attnum, relid)));
    }

    if (RelationIsRedistributeDest(rel) && (attnum > 0 && attnum == rel->rd_att->natts))
        isRedisDropColumn = true;

    if (attnum < 0 || isRedisDropColumn) {
        /* System attribute (probably OID) ... just delete the row */

        simple_heap_delete(attr_rel, &atttuple->t_self);
    } else {
        errno_t rc;
        /* Dropping user attributes is lots harder */
        attStruct = (Form_pg_attribute)GETSTRUCT(atttuple);

        /* Mark the attribute as dropped */
        values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(true);

        /*
         * Set the type OID to invalid.  A dropped attribute's type link
         * cannot be relied on (once the attribute is dropped, the type might
         * be too). Fortunately we do not need the type row --- the only
         * really essential information is the type's typlen and typalign,
         * which are preserved in the attribute's attlen and attalign.  We set
         * atttypid to zero here as a means of catching code that incorrectly
         * expects it to be valid.
         */
        values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(InvalidOid);

        /* Remove any NOT NULL constraint the column may have */
        values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(false);

        /* We don't want to keep stats for it anymore */
        values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(0);

        values[Anum_pg_attribute_attdroppedname - 1] = NameGetDatum(&(attStruct->attname));

        /*
         * Change the column name to something that isn't likely to conflict
         */

        rc =
            snprintf_s(newattname, sizeof(newattname), sizeof(newattname) - 1, "........pg.dropped.%d........", attnum);
        securec_check_ss(rc, "\0", "\0");
        values[Anum_pg_attribute_attname - 1] = NameGetDatum(newattname);
        replaces[Anum_pg_attribute_attisdropped - 1] = true;
        replaces[Anum_pg_attribute_atttypid - 1] = true;
        replaces[Anum_pg_attribute_attnotnull - 1] = true;
        replaces[Anum_pg_attribute_attstattarget - 1] = true;
        replaces[Anum_pg_attribute_attdroppedname - 1] = true;
        replaces[Anum_pg_attribute_attname - 1] = true;

        newatttuple = heap_modify_tuple(atttuple, RelationGetDescr(attr_rel), values, nulls, replaces);
        simple_heap_update(attr_rel, &newatttuple->t_self, newatttuple);

        /* keep the system catalog indexes current */
        CatalogUpdateIndexes(attr_rel, newatttuple);
        heap_freetuple_ext(newatttuple);
    }
    systable_endscan(scan);

    /*
     * Because updating the pg_attribute row will trigger a relcache flush for
     * the target relation, we need not do anything else to notify other
     * backends of the change.
     */

    heap_close(attr_rel, RowExclusiveLock);

    if (attnum > 0) {
        if (RELATION_IS_GLOBAL_TEMP(rel)) {
            remove_gtt_att_statistic(relid, attnum);
        } else {
            RemoveStatistics<'c'>(relid, attnum);
        }
    }

    /* decrease the relation's relnatts in pg_class */
    if (isRedisDropColumn) {
        pgclass = heap_open(RelationRelationId, RowExclusiveLock);

        reltuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
        if (!HeapTupleIsValid(reltuple))
            ereport(
                ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", relid)));

        ((Form_pg_class)GETSTRUCT(reltuple))->relnatts -= 1;
        simple_heap_update(pgclass, &reltuple->t_self, reltuple);

        /* keep catalog indexes current */
        CatalogUpdateIndexes(pgclass, reltuple);

        heap_freetuple(reltuple);
        heap_close(pgclass, RowExclusiveLock);
    }

    relation_close(rel, NoLock);
}

/*
 *		RemoveAttrDefault
 *
 * If the specified relation/attribute has a default, remove it.
 * (If no default, raise error if complain is true, else return quietly.)
 */
void RemoveAttrDefault(Oid relid, AttrNumber attnum, DropBehavior behavior, bool complain, bool internal)
{
    Relation attrdef_rel;
    ScanKeyData scankeys[2];
    SysScanDesc scan;
    HeapTuple tuple;
    bool found = false;

    attrdef_rel = heap_open(AttrDefaultRelationId, RowExclusiveLock);

    ScanKeyInit(&scankeys[0], Anum_pg_attrdef_adrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relid));
    ScanKeyInit(&scankeys[1], Anum_pg_attrdef_adnum, BTEqualStrategyNumber, F_INT2EQ, Int16GetDatum(attnum));

    scan = systable_beginscan(attrdef_rel, AttrDefaultIndexId, true, NULL, 2, scankeys);

    /* There should be at most one matching tuple, but we loop anyway */
    while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
        ObjectAddress object;

        object.classId = AttrDefaultRelationId;
        object.objectId = HeapTupleGetOid(tuple);
        object.objectSubId = 0;

        performDeletion(&object, behavior, internal ? PERFORM_DELETION_INTERNAL : 0);

        found = true;
    }

    systable_endscan(scan);
    heap_close(attrdef_rel, RowExclusiveLock);

    if (complain && !found)
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                errmsg("could not find attrdef tuple for relation %u attnum %d", relid, attnum)));
}

/*
 *		RemoveAttrDefaultById
 *
 * Remove a pg_attrdef entry specified by OID.	This is the guts of
 * attribute-default removal.  Note it should be called via performDeletion,
 * not directly.
 */
void RemoveAttrDefaultById(Oid attrdefId)
{
    Relation attrdef_rel;
    Relation attr_rel;
    Relation myrel;
    ScanKeyData scankeys[1];
    SysScanDesc scan;
    HeapTuple tuple;
    Oid myrelid;
    AttrNumber myattnum;

    /* Grab an appropriate lock on the pg_attrdef relation */
    attrdef_rel = heap_open(AttrDefaultRelationId, RowExclusiveLock);

    /* Find the pg_attrdef tuple */
    ScanKeyInit(&scankeys[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(attrdefId));

    scan = systable_beginscan(attrdef_rel, AttrDefaultOidIndexId, true, NULL, 1, scankeys);

    tuple = systable_getnext(scan);
    if (!HeapTupleIsValid(tuple))
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("could not find tuple for attrdef %u", attrdefId)));

    myrelid = ((Form_pg_attrdef)GETSTRUCT(tuple))->adrelid;
    myattnum = ((Form_pg_attrdef)GETSTRUCT(tuple))->adnum;

    /* Get an exclusive lock on the relation owning the attribute */
    myrel = relation_open(myrelid, AccessExclusiveLock);

    /* Now we can delete the pg_attrdef row */
    simple_heap_delete(attrdef_rel, &tuple->t_self);

    systable_endscan(scan);
    heap_close(attrdef_rel, RowExclusiveLock);

    /* Fix the pg_attribute row */
    attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);

    tuple = SearchSysCacheCopy2(ATTNUM, ObjectIdGetDatum(myrelid), Int16GetDatum(myattnum));
    if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
    {
        Assert(0);
        ereport(ERROR,
            (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                errmsg("cache lookup failed for attribute %d of relation %u", myattnum, myrelid)));
    }

    ((Form_pg_attribute)GETSTRUCT(tuple))->atthasdef = false;

    simple_heap_update(attr_rel, &tuple->t_self, tuple);

    /* keep the system catalog indexes current */
    CatalogUpdateIndexes(attr_rel, tuple);

    /*
     * Our update of the pg_attribute row will force a relcache rebuild, so
     * there's nothing else to do here.
     */
    heap_close(attr_rel, RowExclusiveLock);

    /* Keep lock on attribute's rel until end of xact */
    relation_close(myrel, NoLock);
}

/*
 * Remove error info table file for bulkload
 * there are problems in a rollback transaction
 */
static void RemoveBulkLoadErrorTableFile(Oid databaseid, Oid relid)
{
    char filepath[PATH_MAX + 1];
    getErrorTableFilePath(filepath, PATH_MAX + 1, databaseid, relid);
    struct stat st;
    // is there an error table file
    if (0 == stat(filepath, &st)) {
        if (unlink(filepath)) {
            ereport(ERROR, (errcode_for_file_access(), errmsg("could not remove file \"%s\": %m", filepath)));
        }
    }
}

#ifdef ENABLE_MOT
static void MotFdwDropForeignRelation(Relation rel, FdwRoutine* fdwRoutine)
{
    /* Forward drop stmt to MOT FDW. */
    if (fdwRoutine->GetFdwType != NULL && fdwRoutine->GetFdwType() == MOT_ORC && fdwRoutine->ValidateTableDef != NULL) {
        DropForeignStmt stmt;
        stmt.type = T_DropForeignStmt;
        stmt.relkind = RELKIND_RELATION;
        stmt.reloid = RelationGetRelid(rel);
        stmt.indexoid = 0;
        stmt.name = rel->rd_rel->relname.data;

        fdwRoutine->ValidateTableDef((Node*)&stmt);
    }
}
#endif

/*
 * heap_drop_with_catalog	- removes specified relation from catalogs
 *
 * Note that this routine is not responsible for dropping objects that are
 * linked to the pg_class entry via dependencies (for example, indexes and
 * constraints).  Those are deleted by the dependency-tracing logic in
 * dependency.c before control gets here.  In general, therefore, this routine
 * should never be called directly; go through performDeletion() instead.
 */
void heap_drop_with_catalog(Oid relid)
{
    Relation rel;

    /*
     * Open and lock the relation.
     */
    rel = relation_open(relid, AccessExclusiveLock);
    if (RelationUsesSpaceType(rel->rd_rel->relpersistence) == SP_TEMP) {
        make_tmptable_cache_key(rel->rd_rel->relfilenode);
    }

    /*
     * There can no longer be anyone *else* touching the relation, but we
     * might still have open queries or cursors, or pending trigger events, in
     * our own session.
     */
    CheckTableNotInUse(rel, "DROP TABLE");

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

    /*
     * Delete pg_foreign_table tuple first.
     */
    bool flag = rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE || rel->rd_rel->relkind == RELKIND_STREAM;
    if (flag) {
        Relation foreignRel;
        HeapTuple tuple;

        foreignRel = heap_open(ForeignTableRelationId, RowExclusiveLock);

        tuple = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(relid));
        if (!HeapTupleIsValid(tuple))
            ereport(ERROR,
                (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for foreign table %u", relid)));

        /* remove error table file */
        RemoveBulkLoadErrorTableFile(foreignRel->rd_node.dbNode, relid);

        simple_heap_delete(foreignRel, &tuple->t_self);

        ReleaseSysCache(tuple);
        heap_close(foreignRel, RowExclusiveLock);

        /* @hdfs
         * When we drop a foreign partition table, we need to delete the corresponding
         * tuple in pg_partition.
         */
        FdwRoutine* fdwRoutine = GetFdwRoutineByRelId(relid, true);
        if (fdwRoutine != NULL) {
            if (NULL != fdwRoutine->PartitionTblProcess) {
                fdwRoutine->PartitionTblProcess(NULL, relid, HDFS_DROP_PARTITIONED_FOREIGNTBL);
            }

#ifdef ENABLE_MOT
            /* Forward drop stmt to MOT FDW. */
            MotFdwDropForeignRelation(rel, fdwRoutine);
#endif
        }
    }

    /* drop enty for pg_partition */
    if (RelationIsValuePartitioned(rel)) {
        HeapTuple partTuple = searchPgPartitionByParentIdCopy(PART_OBJ_TYPE_PARTED_TABLE, relid);
        if (partTuple) {
            Relation pg_partitionRel = heap_open(PartitionRelationId, RowExclusiveLock);
            simple_heap_delete(pg_partitionRel, &partTuple->t_self);

            heap_freetuple(partTuple);
            heap_close(pg_partitionRel, RowExclusiveLock);
        } else
            ereport(ERROR,
                (errcode(ERRCODE_DATA_EXCEPTION),
                    errmsg(
                        "Catalog table pg_partition may get trashed on table %s as it is not consitant with pg_class",
                        RelationGetRelationName(rel))));
    }

    if (RELATION_IS_PARTITIONED(rel)) {
        heapDropPartitionTable(rel);
    }

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

    /*
     * When dropping temp objects, we need further check whether current datanode is
     * suffered from an unclean shutdown without gsql reconnect, as in this case the
     * context of temp backend thread is inconsistant with other datanodes (timelineID),
     * in order to prevent user further operator temp object, we error-out the DROP
     * statement to tell user to have to re-connect server.
     */
    if (STMT_RETRY_ENABLED) {
        // do noting for now, if query retry is on, just to skip validateTempRelation here
    } else if (RelationIsLocalTemp(rel)) {
        if (!validateTempNamespace(u_sess->catalog_cxt.myTempNamespace))
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_TEMP_OBJECTS),
                    errmsg("Temp tables are invalid because datanode %s restart. "
                           "Quit your session to clean invalid temp tables.",
                        g_instance.attr.attr_common.PGXCNodeName)));
    }

    /*
     * Schedule unlinking of the relation's physical files at commit.
     */
    if (rel->rd_rel->relkind != RELKIND_VIEW && rel->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
        rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE && rel->rd_rel->relkind != RELKIND_STREAM &&
        rel->rd_rel->relkind != RELKIND_CONTQUERY) {
        RelationDropStorage(rel);
    }

    /* Recode time of delete relation. */
    PgObjectType drop_type = GetPgObjectTypePgClass(rel->rd_rel->relkind);
    if (drop_type != OBJECT_TYPE_INVALID) {
        DeletePgObject(rel->rd_id, drop_type);
    }

    /*
     * Close relcache entry, but *keep* AccessExclusiveLock on the relation
     * until transaction commit.  This ensures no one else will try to do
     * something with the doomed relation.
     */
    if (ISMLOG(RelationGetForm(rel)->relname.data)) {
        Oid base_relid = get_matview_mlog_baserelid(relid);
        if (OidIsValid(base_relid)) {
            CacheInvalidateRelcacheByRelid(base_relid);
        }
    }
    relation_close(rel, NoLock);

    /*
     * Remove any associated relation synchronization states.
     */
    RemoveSubscriptionRel(InvalidOid, relid);

    /*
     * Forget any ON COMMIT action for the rel
     */
    remove_on_commit_action(relid);

    /*
     * Flush the relation from the relcache.  We want to do this before
     * starting to remove catalog entries, just to be certain that no relcache
     * entry rebuild will happen partway through.  (That should not really
     * matter, since we don't do CommandCounterIncrement here, but let's be
     * safe.)
     */
    RelationForgetRelation(relid);

    /*
     * remove inheritance information
     */
    RelationRemoveInheritance(relid);

    /*
     * delete statistics
     */
    RemoveStatistics<'c'>(relid, 0);

    /*
     * delete hotkeys
     */
    pgstat_send_cleanup_hotkeys(InvalidOid, relid);

    /*
     * delete attribute tuples
     */
    DeleteAttributeTuples(relid);

    /*
     * delete relation tuple
     */
    DeleteRelationTuple(relid);
}

/*
 * Store a default expression for column attnum of relation rel.
 */
Oid StoreAttrDefault(Relation rel, AttrNumber attnum, Node* expr, char generatedCol, Node* update_expr, bool skip_dep)
{
    char* adbin = NULL;
    char* adbin_on_update = NULL;
    char* adsrc = NULL;
    char* update_src = NULL;
    Relation adrel;
    HeapTuple tuple;
    Datum values[Natts_pg_attrdef];
    Relation attrrel;
    HeapTuple atttup;
    Form_pg_attribute attStruct;
    Oid attrdefOid;
    ObjectAddress colobject, defobject;
    ScanKeyData skey[2];
    HeapTuple htup = NULL;
    bool isnull = false;
    bool on_update_exist = false;
    bool adin_on_update_exist = false;

    /*
     * Flatten expression to string form for storage.
     */
    if (update_expr != NULL) {
        adbin_on_update = nodeToString(update_expr);
        update_src = deparse_expression(
            update_expr, deparse_context_for(RelationGetRelationName(rel), RelationGetRelid(rel)), false, false);
    } else {
        Relation adrel_temp = 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(attnum));
        SysScanDesc adscan = systable_beginscan(adrel_temp, AttrDefaultIndexId, true, NULL, 2, skey);
        if (HeapTupleIsValid(htup = systable_getnext(adscan))) {
            Datum val = heap_getattr(htup, Anum_pg_attrdef_adbin_on_update, adrel_temp->rd_att, &isnull);
            if (val && pg_strcasecmp(TextDatumGetCString(val), "") != 0) {
                adbin_on_update = TextDatumGetCString(val);
                on_update_exist = true;
            }
            val = heap_getattr(htup, Anum_pg_attrdef_adsrc_on_update, adrel_temp->rd_att, &isnull);
            if (val && pg_strcasecmp(TextDatumGetCString(val), "") != 0) {
                update_src = TextDatumGetCString(val);
                adin_on_update_exist = true;
            }
        }
        systable_endscan(adscan);
        heap_close(adrel_temp, RowExclusiveLock);
    }

    /*
     * Also deparse it to form the mostly-obsolete adsrc field.
     */
    adsrc = deparse_expression(
        expr, deparse_context_for(RelationGetRelationName(rel), RelationGetRelid(rel)), false, false);

    /*
     * Make the pg_attrdef entry.
     */
    values[Anum_pg_attrdef_adrelid - 1] = RelationGetRelid(rel);
    values[Anum_pg_attrdef_adnum - 1] = attnum;
    if (expr != NULL) {
        adbin = nodeToString(expr);
    }
    values[Anum_pg_attrdef_adbin - 1] = adbin ? CStringGetTextDatum(adbin) : CStringGetTextDatum("");
    values[Anum_pg_attrdef_adsrc - 1] = adsrc ? CStringGetTextDatum(adsrc) : CStringGetTextDatum("");
    values[Anum_pg_attrdef_adbin_on_update - 1] = (update_expr != NULL || (on_update_exist && adin_on_update_exist)) ?
        CStringGetTextDatum(adbin_on_update) : CStringGetTextDatum("");
    values[Anum_pg_attrdef_adsrc_on_update - 1] = (update_expr != NULL || (on_update_exist && adin_on_update_exist)) ?
        CStringGetTextDatum(update_src) : CStringGetTextDatum("");

    if (t_thrd.proc->workingVersionNum >= GENERATED_COL_VERSION_NUM) {
        values[Anum_pg_attrdef_adgencol - 1] = CharGetDatum(generatedCol);
    } else {
        /* set to default value \0 */
        values[Anum_pg_attrdef_adgencol - 1] = CharGetDatum(0);
    }

    adrel = heap_open(AttrDefaultRelationId, RowExclusiveLock);

    tuple = heap_form_tuple(adrel->rd_att, values, u_sess->catalog_cxt.nulls);
    if (on_update_exist) {
        RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false, true);
    }
    attrdefOid = simple_heap_insert(adrel, tuple);

    CatalogUpdateIndexes(adrel, tuple);

    defobject.classId = AttrDefaultRelationId;
    defobject.objectId = attrdefOid;
    defobject.objectSubId = 0;

    heap_close(adrel, RowExclusiveLock);

    /* now can free some of the stuff allocated above */
    pfree(DatumGetPointer(values[Anum_pg_attrdef_adbin - 1]));
    pfree(DatumGetPointer(values[Anum_pg_attrdef_adsrc - 1]));
    pfree(DatumGetPointer(values[Anum_pg_attrdef_adbin_on_update - 1]));
    pfree(DatumGetPointer(values[Anum_pg_attrdef_adsrc_on_update - 1]));
    heap_freetuple(tuple);
    pfree(adsrc);
    if (adbin) {
        pfree(adbin);
    }
    if (adbin_on_update) {
        pfree(adbin_on_update);
    }
    if (update_src) {
        pfree(update_src);
    }

    /*
     * Update the pg_attribute entry for the column to show that a default
     * exists.
     */
    attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
    atttup = SearchSysCacheCopy2(ATTNUM, ObjectIdGetDatum(RelationGetRelid(rel)), Int16GetDatum(attnum));
    if (!HeapTupleIsValid(atttup)) {
        Assert(0);
        ereport(ERROR,
            (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                errmsg("cache lookup failed for attribute %d of relation %u", attnum, RelationGetRelid(rel))));
    }
    attStruct = (Form_pg_attribute)GETSTRUCT(atttup);
    if (!attStruct->atthasdef) {
        attStruct->atthasdef = true;
        simple_heap_update(attrrel, &atttup->t_self, atttup);
        /* keep catalog indexes current */
        CatalogUpdateIndexes(attrrel, atttup);
    }
    heap_close(attrrel, RowExclusiveLock);
    heap_freetuple(atttup);

    /*
     * Make a dependency so that the pg_attrdef entry goes away if the column
     * (or whole table) is deleted.
     */
    colobject.classId = RelationRelationId;
    colobject.objectId = RelationGetRelid(rel);
    colobject.objectSubId = attnum;

    recordDependencyOn(&defobject, &colobject, DEPENDENCY_AUTO);

    if (skip_dep) {
        return InvalidOid;
    }
    /*
     * Record dependencies on objects used in the expression, too.
     */
    if (generatedCol == ATTRIBUTE_GENERATED_STORED || generatedCol == ATTRIBUTE_GENERATED_PERSISTED) {
        /*
         * Generated column: Dropping anything that the generation expression
         * refers to automatically drops the generated column.
         */
        recordDependencyOnSingleRelExpr(&colobject, expr, RelationGetRelid(rel),
                                        DEPENDENCY_AUTO,
                                        DEPENDENCY_AUTO);
    } else {
        /*
         * Normal default: Dropping anything that the default refers to
         * requires CASCADE and drops the default only.
         */
        recordDependencyOnExpr(&defobject, expr, NIL, DEPENDENCY_NORMAL);
    }
    return attrdefOid;
}

/*
 * Store a check-constraint expression for the given relation.
 *
 * Caller is responsible for updating the count of constraints
 * in the pg_class entry for the relation.
 * The OID of the new constraint is returned.
 */
static Oid StoreRelCheck(
    Relation rel, const char* ccname, Node* expr, bool is_validated, bool is_local, int inhcount, bool is_no_inherit,
    bool is_deferrable, bool is_deferred, bool is_disable)
{
    char* ccbin = NULL;
    char* ccsrc = NULL;
    List* varList = NIL;
    int keycount;
    int16* attNos = NULL;
    Oid constrOid;

    /*
     * Flatten expression to string form for storage.
     */
    ccbin = nodeToString(expr);

    /*
     * Also deparse it to form the mostly-obsolete consrc field.
     */
    ccsrc = deparse_expression(
        expr, deparse_context_for(RelationGetRelationName(rel), RelationGetRelid(rel)), false, false);

    /*
     * Find columns of rel that are used in expr
     *
     * NB: pull_var_clause is okay here only because we don't allow subselects
     * in check constraints; it would fail to examine the contents of
     * subselects.
     */
    varList = pull_var_clause(expr, PVC_REJECT_AGGREGATES, PVC_REJECT_PLACEHOLDERS);
    keycount = list_length(varList);

    if (keycount > 0) {
        ListCell* vl = NULL;
        int i = 0;

        attNos = (int16*)palloc(keycount * sizeof(int16));
        foreach (vl, varList) {
            Var* var = (Var*)lfirst(vl);
            int j;

            for (j = 0; j < i; j++)
                if (attNos[j] == var->varattno)
                    break;
            if (j == i)
                attNos[i++] = var->varattno;
        }
        keycount = i;
    } else
        attNos = NULL;

    /*
     * Create the Check Constraint
     */
    constrOid = CreateConstraintEntry(ccname, /* Constraint Name */
        RelationGetNamespace(rel),      /* namespace */
        CONSTRAINT_CHECK,               /* Constraint Type */
        is_deferrable,                  /* Is Deferrable */
        is_deferred,                    /* Is Deferred */
        is_validated,
        RelationGetRelid(rel), /* relation */
        attNos,                /* attrs in the constraint */
        keycount,              /* # key attrs in the constraint */
        keycount,              /* # total attrs in the constraint */
        InvalidOid,            /* not a domain constraint */
        InvalidOid,            /* no associated index */
        InvalidOid,            /* Foreign key fields */
        NULL,
        NULL,
        NULL,
        NULL,
        0,
        ' ',
        ' ',
        ' ',
        NULL,          /* not an exclusion constraint */
        expr,          /* Tree form of check constraint */
        ccbin,         /* Binary form of check constraint */
        ccsrc,         /* Source form of check constraint */
        is_local,      /* conislocal */
        inhcount,      /* coninhcount */
        is_no_inherit, /* connoinherit */
        NULL,          /* @hdfs softconstraint info */
        is_disable);

    pfree(ccbin);
    pfree(ccsrc);
    return constrOid;
}

/*
 * Store defaults and constraints (passed as a list of CookedConstraint).
 *
 * NOTE: only pre-cooked expressions will be passed this way, which is to
 * say expressions inherited from an existing relation.  Newly parsed
 * expressions can be added later, by direct calls to StoreAttrDefault
 * and StoreRelCheck (see AddRelationNewConstraints()).
 */
static void StoreConstraints(Relation rel, List* cooked_constraints)
{
    int numchecks = 0;
    ListCell* lc = NULL;

    if (cooked_constraints == NIL)
        return; /* nothing to do */

    /*
     * Deparsing of constraint expressions will fail unless the just-created
     * pg_attribute tuples for this relation are made visible.	So, bump the
     * command counter.  CAUTION: this will cause a relcache entry rebuild.
     */
    CommandCounterIncrement();

    foreach (lc, cooked_constraints) {
        CookedConstraint* con = (CookedConstraint*)lfirst(lc);

        switch (con->contype) {
            case CONSTR_DEFAULT:
                con->conoid = StoreAttrDefault(rel, con->attnum, con->expr, 0,con->update_expr);
                break;
            case CONSTR_GENERATED:
                con->conoid = StoreAttrDefault(rel, con->attnum, con->expr, ATTRIBUTE_GENERATED_STORED, NULL);
                break;
            case CONSTR_CHECK:
                con->conoid = StoreRelCheck(
                    rel, con->name, con->expr, !con->skip_validation, con->is_local, con->inhcount, con->is_no_inherit, false, false, con->isdisable);
                numchecks++;
                break;
            default:
                ereport(ERROR,
                    (errcode(ERRCODE_UNDEFINED_PARAMETER),
                        errmsg("unrecognized constraint type: %d", (int)con->contype)));
        }
    }

    if (numchecks > 0)
        SetRelationNumChecks(rel, numchecks);
}

static Node* CookAutoIncDefault(ParseState* pstate, Relation rel, RawColumnDefault* colDef, Form_pg_attribute atp)
{
    AutoIncrement *autoinc = NULL;

    CheckAutoIncrementDatatype(atp->atttypid, NameStr(atp->attname));
    if (RelHasAutoInc(rel)) {
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                (errmsg("Incorrect column definition, there can be only one auto_increment column"))));
    }

    autoinc = makeNode(AutoIncrement);
    (void)find_coercion_pathway(INT16OID, atp->atttypid, COERCION_ASSIGNMENT, &autoinc->autoincin_funcid);
    (void)find_coercion_pathway(atp->atttypid, INT16OID, COERCION_ASSIGNMENT, &autoinc->autoincout_funcid);
    autoinc->expr = cookDefault(pstate, ((AutoIncrement*)colDef->raw_default)->expr, atp->atttypid,
        atp->atttypmod, atp->attcollation, NameStr(atp->attname), colDef->generatedCol);

    /* If relation is temp table, cooked default must be a constant of the auto increment start value. */
    if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) {
        Const* con = (Const*)((AutoIncrement*)colDef->raw_default)->expr;
        tmptable_autoinc_reset(rel->rd_rel->relfilenode, DatumGetInt128(con->constvalue));
    }
    return (Node*)autoinc;
}

static void CheckAutoIncrementCheckExpr(Node* expr, AttrNumber attrnum)
{
    if (attrnum == 0) {
        return;
    }
    Bitmapset *checkattrs = NULL;

    pull_varattnos(expr, 1, &checkattrs);
    if (bms_is_member(attrnum - FirstLowInvalidHeapAttributeNumber, checkattrs)) {
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                    (errmsg("check constraint cannot refer to an auto_increment column"))));
    }
}

/*
 * AddRelationNewConstraints
 *
 * Add new column default expressions and/or constraint check expressions
 * to an existing relation.  This is defined to do both for efficiency in
 * DefineRelation, but of course you can do just one or the other by passing
 * empty lists.
 *
 * rel: relation to be modified
 * newColDefaults: list of RawColumnDefault structures
 * newConstraints: list of Constraint nodes
 * allow_merge: TRUE if check constraints may be merged with existing ones
 * is_local: TRUE if definition is local, FALSE if it's inherited
 *
 * All entries in newColDefaults will be processed.  Entries in newConstraints
 * will be processed only if they are CONSTR_CHECK type.
 *
 * Returns a list of CookedConstraint nodes that shows the cooked form of
 * the default and constraint expressions added to the relation.
 *
 * NB: caller should have opened rel with AccessExclusiveLock, and should
 * hold that lock till end of transaction.	Also, we assume the caller has
 * done a CommandCounterIncrement if necessary to make the relation's catalog
 * tuples visible.
 */
List* AddRelationNewConstraints(
    Relation rel, List* newColDefaults, List* newConstraints, bool allow_merge, bool is_local)
{
    List* cookedConstraints = NIL;
    TupleDesc tupleDesc;
    TupleConstr* oldconstr = NULL;
    int numoldchecks;
    ParseState* pstate = NULL;
    RangeTblEntry* rte = NULL;
    int numchecks;
    List* checknames = NIL;
    ListCell* cell = NULL;
    Node* expr = NULL;
    CookedConstraint* cooked = NULL;
    Oid defOid;
    AttrNumber autoinc_attnum = RelAutoIncAttrNum(rel);
    Node* update_expr = NULL;
    Bitmapset* generated_by_attrs = NULL;
    /*
     * Get info about existing constraints.
     */
    tupleDesc = RelationGetDescr(rel);
    oldconstr = tupleDesc->constr;
    if (oldconstr != NULL)
        numoldchecks = oldconstr->num_check;
    else
        numoldchecks = 0;

    /*
     * Create a dummy ParseState and insert the target relation as its sole
     * rangetable entry.  We need a ParseState for transformExpr.
     */
    pstate = make_parsestate(NULL);
    rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
    addRTEtoQuery(pstate, rte, true, true, true);

    pstate->p_rawdefaultlist = newColDefaults;

    if (u_sess->hook_cxt.invokePreAddConstraintsHook) {
        ((InvokePreAddConstraintsHookType)(u_sess->hook_cxt.invokePreAddConstraintsHook))(rel, pstate,
                                                                                          newColDefaults);
    }

    /*
     * Process column default expressions.
     */
    foreach (cell, newColDefaults) {
        RawColumnDefault* colDef = (RawColumnDefault*)lfirst(cell);
        Form_pg_attribute atp = &rel->rd_att->attrs[colDef->attnum - 1];

        if (colDef->raw_default != NULL) {
            if (IsA(colDef->raw_default, AutoIncrement)) {
                autoinc_attnum = colDef->attnum;
                expr = CookAutoIncDefault(pstate, rel, colDef, atp);
            } else {
                expr = cookDefault(pstate, colDef->raw_default, atp->atttypid, atp->atttypmod,
                    atp->attcollation, NameStr(atp->attname), colDef->generatedCol);
                if (colDef->generatedCol == ATTRIBUTE_GENERATED_STORED ||
                    colDef->generatedCol == ATTRIBUTE_GENERATED_PERSISTED) {
                    pull_varattnos(expr, 1, &generated_by_attrs);
                }
            }
        }

        if (colDef->update_expr != NULL) {
            update_expr = cookDefault(pstate, colDef->update_expr, atp->atttypid, atp->atttypmod,
                atp->attcollation, NameStr(atp->attname), colDef->generatedCol);
        }

        /*
         * If the expression is just a NULL constant, we do not bother to make
         * an explicit pg_attrdef entry, since the default behavior is
         * equivalent.  This applies to column defaults, but not for
         * generation expressions.
         *
         * Note a nonobvious property of this test: if the column is of a
         * domain type, what we'll get is not a bare null Const but a
         * CoerceToDomain expr, so we will not discard the default.  This is
         * critical because the column default needs to be retained to
         * override any default that the domain might have.
         */
        if (expr != NULL && !colDef->generatedCol && IsA(expr, Const) && ((Const *)expr)->constisnull)
            continue;

        defOid = StoreAttrDefault(rel, colDef->attnum, expr, colDef->generatedCol, update_expr);

        cooked = (CookedConstraint*)palloc(sizeof(CookedConstraint));
        cooked->contype = CONSTR_DEFAULT;
        cooked->conoid = defOid;
        cooked->name = NULL;
        cooked->attnum = colDef->attnum;
        cooked->expr = expr;
        cooked->skip_validation = false;
        cooked->is_local = is_local;
        cooked->inhcount = is_local ? 0 : 1;
        cooked->is_no_inherit = false;
        cooked->isdisable = false;
        cookedConstraints = lappend(cookedConstraints, cooked);
        expr = NULL;
        update_expr = NULL;
    }
    if (autoinc_attnum > 0 &&
        bms_is_member(autoinc_attnum - FirstLowInvalidHeapAttributeNumber, generated_by_attrs)) {
        bms_free_ext(generated_by_attrs);
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
            (errmsg("generated column cannot refer to auto_increment column"))));
    }
    bms_free_ext(generated_by_attrs);

    pstate->p_rawdefaultlist = NIL;
    /*
     * Process constraint expressions.
     */
    numchecks = numoldchecks;
    checknames = NIL;
    foreach (cell, newConstraints) {
        Constraint* cdef = (Constraint*)lfirst(cell);
        char* ccname = NULL;
        Oid constrOid;

        if (cdef->contype != CONSTR_CHECK)
            continue;

        if (cdef->raw_expr != NULL) {
            Assert(cdef->cooked_expr == NULL);

            /*
             * Transform raw parsetree to executable expression, and verify
             * it's valid as a CHECK constraint.
             */
            expr = cookConstraint(pstate, cdef->raw_expr, RelationGetRelationName(rel));
        } else {
            Assert(cdef->cooked_expr != NULL);

            /*
             * Here, we assume the parser will only pass us valid CHECK
             * expressions, so we do no particular checking.
             */
            expr = (Node*)stringToNode(cdef->cooked_expr);
        }

        /*
         * Check constraint expressions cannot contain auto_increment column.
         */
        CheckAutoIncrementCheckExpr(expr, autoinc_attnum);
        /*
         * Check name uniqueness, or generate a name if none was given.
         */
        if (cdef->conname != NULL) {
            ListCell* cell2 = NULL;

            ccname = cdef->conname;
            /* Check against other new constraints */
            /* Needed because we don't do CommandCounterIncrement in loop */
            foreach (cell2, checknames) {
                if (strcmp((char*)lfirst(cell2), ccname) == 0)
                    ereport(ERROR,
                        (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("check constraint \"%s\" already exists", ccname)));
            }

            /* save name for future checks */
            checknames = lappend(checknames, ccname);

            /*
             * Check against pre-existing constraints.	If we are allowed to
             * merge with an existing constraint, there's no more to do here.
             * (We omit the duplicate constraint from the result, which is
             * what ATAddCheckConstraint wants.)
             */
            if (MergeWithExistingConstraint(rel, ccname, expr, allow_merge, is_local, cdef->is_no_inherit))
                continue;
        } else {
            /*
             * When generating a name, we want to create "tab_col_check" for a
             * column constraint and "tab_check" for a table constraint.  We
             * no longer have any info about the syntactic positioning of the
             * constraint phrase, so we approximate this by seeing whether the
             * expression references more than one column.	(If the user
             * played by the rules, the result is the same...)
             *
             * Note: pull_var_clause() doesn't descend into sublinks, but we
             * eliminated those above; and anyway this only needs to be an
             * approximate answer.
             */
            List* vars = NIL;
            char* colname = NULL;

            vars = pull_var_clause(expr, PVC_REJECT_AGGREGATES, PVC_REJECT_PLACEHOLDERS);

            /* eliminate duplicates */
            vars = list_union(NIL, vars);

            if (list_length(vars) == 1)
                colname = get_attname(RelationGetRelid(rel), ((Var*)linitial(vars))->varattno);
            else
                colname = NULL;

            ccname = ChooseConstraintName(
                RelationGetRelationName(rel), colname, "check", RelationGetNamespace(rel), checknames);

            /* save name for future checks */
            checknames = lappend(checknames, ccname);
        }

        /*
         * OK, store it.
         */
        constrOid = StoreRelCheck(rel, ccname, expr, !cdef->skip_validation,
                                    is_local, is_local ? 0 : 1, cdef->is_no_inherit,
                                    cdef->deferrable, cdef->initdeferred, cdef->isdisable);
        ListCell *cell = NULL;
        foreach (cell, cdef->constraintOptions) {
            void *pointer = lfirst(cell);
            if (IsA(pointer, CommentStmt)) {
                CommentStmt *commentStmt = (CommentStmt *)pointer;
                CreateComments(constrOid, ConstraintRelationId, 0, commentStmt->comment);
                break;
            }
        }
        numchecks++;

        cooked = (CookedConstraint*)palloc(sizeof(CookedConstraint));
        cooked->contype = CONSTR_CHECK;
        cooked->conoid = constrOid;
        cooked->name = ccname;
        cooked->attnum = 0;
        cooked->expr = expr;
        cooked->skip_validation = cdef->skip_validation;
        cooked->is_local = is_local;
        cooked->inhcount = is_local ? 0 : 1;
        cooked->is_no_inherit = cdef->is_no_inherit;
        cooked->isdisable = cdef->isdisable;
        cookedConstraints = lappend(cookedConstraints, cooked);
    }

    /*
     * Update the count of constraints in the relation's pg_class tuple. We do
     * this even if there was no change, in order to ensure that an SI update
     * message is sent out for the pg_class tuple, which will force other
     * backends to rebuild their relcache entries for the rel. (This is
     * critical if we added defaults but not constraints.)
     */
    SetRelationNumChecks(rel, numchecks);

    /*
     * Lock the sequence that the constranits depend, we may lock other object
     * in the farther.
     */
    LockSeqConstraints(rel, cookedConstraints);

    return cookedConstraints;
}

/*
 * Check for a pre-existing check constraint that conflicts with a proposed
 * new one, and either adjust its conislocal/coninhcount settings or throw
 * error as needed.
 *
 * Returns TRUE if merged (constraint is a duplicate), or FALSE if it's
 * got a so-far-unique name, or throws error if conflict.
 *
 * XXX See MergeConstraintsIntoExisting too if you change this code.
 */
static bool MergeWithExistingConstraint(
    Relation rel, char* ccname, Node* expr, bool allow_merge, bool is_local, bool is_no_inherit)
{
    bool found = false;
    Relation conDesc = NULL;
    SysScanDesc conscan = NULL;
    ScanKeyData skey[2];
    HeapTuple tup = NULL;

    /* Search for a pg_constraint entry with same name and relation */
    conDesc = heap_open(ConstraintRelationId, ShareUpdateExclusiveLock);

    ScanKeyInit(&skey[0], Anum_pg_constraint_conname, BTEqualStrategyNumber, F_NAMEEQ, CStringGetDatum(ccname));

    ScanKeyInit(&skey[1],
        Anum_pg_constraint_connamespace,
        BTEqualStrategyNumber,
        F_OIDEQ,
        ObjectIdGetDatum(RelationGetNamespace(rel)));

    conscan = systable_beginscan(conDesc, ConstraintNameNspIndexId, true, NULL, 2, skey);

    while (HeapTupleIsValid(tup = systable_getnext(conscan))) {
        Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(tup);

        if (con->conrelid == RelationGetRelid(rel)) {
            /* Found it.  Conflicts if not identical check constraint */
            if (con->contype == CONSTRAINT_CHECK) {
                Datum val;
                bool isnull = false;

                val = fastgetattr(tup, Anum_pg_constraint_conbin, conDesc->rd_att, &isnull);
                if (isnull)
                    ereport(ERROR,
                        (errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
                            errmsg("null conbin for rel %s", RelationGetRelationName(rel))));
                if (equal(expr, stringToNode(TextDatumGetCString(val))))
                    found = true;
            }
            if (!found || !allow_merge)
                ereport(ERROR,
                    (errcode(ERRCODE_DUPLICATE_OBJECT),
                        errmsg("constraint \"%s\" for relation \"%s\" already exists",
                            ccname,
                            RelationGetRelationName(rel))));

            tup = heap_copytuple(tup);
            con = (Form_pg_constraint)GETSTRUCT(tup);

            /* If the constraint is "no inherit" then cannot merge */
            if (con->connoinherit)
                ereport(ERROR,
                    (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                        errmsg("constraint \"%s\" conflicts with non-inherited constraint on relation \"%s\"",
                            ccname,
                            RelationGetRelationName(rel))));

            if (is_local)
                con->conislocal = true;
            else
                con->coninhcount++;
            if (is_no_inherit) {
                Assert(is_local);
                con->connoinherit = true;
            }
            /* OK to update the tuple */
            ereport(NOTICE, (errmsg("merging constraint \"%s\" with inherited definition", ccname)));
            simple_heap_update(conDesc, &tup->t_self, tup);
            CatalogUpdateIndexes(conDesc, tup);
            break;
        }
    }

    systable_endscan(conscan);
    heap_close(conDesc, ShareUpdateExclusiveLock);

    return found;
}

/*
 * Update the count of constraints in the relation's pg_class tuple.
 *
 * Caller had better hold exclusive lock on the relation.
 *
 * An important side effect is that a SI update message will be sent out for
 * the pg_class tuple, which will force other backends to rebuild their
 * relcache entries for the rel.  Also, this backend will rebuild its
 * own relcache entry at the next CommandCounterIncrement.
 */
static void SetRelationNumChecks(Relation rel, int numchecks)
{
    Relation relrel;
    HeapTuple reltup;
    Form_pg_class relStruct;

    relrel = heap_open(RelationRelationId, RowExclusiveLock);
    reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(RelationGetRelid(rel)));
    if (!HeapTupleIsValid(reltup))
        ereport(ERROR,
            (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                errmsg("cache lookup failed for relation %u", RelationGetRelid(rel))));
    relStruct = (Form_pg_class)GETSTRUCT(reltup);

    if (relStruct->relchecks != numchecks) {
        relStruct->relchecks = numchecks;

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

        /* keep catalog indexes current */
        CatalogUpdateIndexes(relrel, reltup);
    } else {
        /* Skip the disk update, but force relcache inval anyway */
        CacheInvalidateRelcache(rel);
    }

    heap_freetuple(reltup);
    heap_close(relrel, RowExclusiveLock);
}

static bool ReferenceGenerated(const List *rawdefaultlist, AttrNumber attnum)
{
    ListCell *cell = NULL;

    foreach (cell, rawdefaultlist) {
        RawColumnDefault *colDef = (RawColumnDefault *)lfirst(cell);
        if (colDef->attnum == attnum && colDef->generatedCol) {
            return true;
        }
    }

    return false;
}

/*
 * Check for references to generated columns
 */
static bool CheckNestedGeneratedWalker(Node *node, ParseState *context)
{
    ParseState *pstate = context;
    if (node == NULL) {
        return false;
    } else if (IsA(node, Var)) {
        Var *var = (Var *)node;
        RangeTblEntry *rte = rt_fetch(var->varno, pstate->p_rtable);
        Oid relid = rte->relid;
        AttrNumber attnum = var->varattno;

        if (OidIsValid(relid) && AttributeNumberIsValid(attnum) &&
            (ReferenceGenerated(pstate->p_rawdefaultlist, attnum) || GetGenerated(relid, attnum))) {
            ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_SYNTAX_ERROR),
                errmsg("cannot use generated column \"%s\" in column generation expression",
                get_attname(relid, attnum)),
                errdetail("A generated column cannot reference another generated column."),
                parser_errposition(pstate, var->location)));
        }

        return false;
    } else {
        return expression_tree_walker(node, (bool (*)())CheckNestedGeneratedWalker, context);
    }
}

static bool CheckNestedGenerated(ParseState *pstate, Node *node)
{
    return CheckNestedGeneratedWalker(node, pstate);
}

Node* parseParamRef(ParseState* pstate, ParamRef* pref)
{
    Node* param_expr = NULL;
    if (likely(u_sess->parser_cxt.param_info != NULL)) {
        ParamListInfo params_info = (ParamListInfo)u_sess->parser_cxt.param_info;
        int i = pref->number - 1;
        char* str_expr = Datum_to_string(params_info->params[i].value, params_info->params[i].ptype, params_info->params[i].isnull);
        Value* val = (Value*)palloc0(sizeof(Value));
        val->type = T_String;
        val->val.str = str_expr;
        if (params_info->params[i].isnull)
            val->type = T_Null;
        param_expr = (Node*)make_const(pstate, val, 0);
        pfree_ext(val);
        if (pref->number == params_info->numParams && u_sess->parser_cxt.ddl_pbe_context != NULL) {
            MemoryContextDelete(u_sess->parser_cxt.ddl_pbe_context);
            if (((IS_PGXC_COORDINATOR || IS_PGXC_DATANODE) && IsConnFromCoord())) {
                u_sess->parser_cxt.param_info = NULL;
            }
            u_sess->parser_cxt.ddl_pbe_context = NULL;
        }
    } else {
        ereport(ERROR,
            (errmodule(MOD_OPT),
                errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
                errmsg("u_sess->parser_cxt.param_info should not be NULL")));
    }
    return param_expr;
}

/*
 * Take a raw default and convert it to a cooked format ready for
 * storage.
 *
 * Parse state should be set up to recognize any vars that might appear
 * in the expression.  (Even though we plan to reject vars, it's more
 * user-friendly to give the correct error message than "unknown var".)
 *
 * If atttypid is not InvalidOid, coerce the expression to the specified
 * type (and typmod atttypmod).   attname is only needed in this case:
 * it is used in the error message, if any.
 */
Node *cookDefault(ParseState *pstate, Node *raw_default, Oid atttypid, int32 atttypmod, Oid attcollation,
    char *attname, char generatedCol)
{
    Node* expr = NULL;

    Assert(raw_default != NULL);

    /* Version control for DDL PBE */
    if (t_thrd.proc->workingVersionNum >= DDL_PBE_VERSION_NUM)
        pstate->p_paramref_hook = parseParamRef;
    /*
     * Transform raw parsetree to executable expression.
     */
    pstate->p_expr_kind = generatedCol ? EXPR_KIND_GENERATED_COLUMN : EXPR_KIND_COLUMN_DEFAULT;
    expr = transformExpr(pstate, raw_default, pstate->p_expr_kind);
    pstate->p_expr_kind = EXPR_KIND_NONE;

    if (generatedCol == ATTRIBUTE_GENERATED_STORED || generatedCol == ATTRIBUTE_GENERATED_PERSISTED)
    {
        (void)CheckNestedGenerated(pstate, expr);

        if (contain_mutable_functions(expr))
            ereport(ERROR, (errmodule(MOD_GEN_COL), errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                errmsg("generation expression is not immutable")));
    }

    /* Version control for DDL PBE */
    if (t_thrd.proc->workingVersionNum >= DDL_PBE_VERSION_NUM)
        pstate->p_paramref_hook = NULL;
    /*
     * Make sure default expr does not refer to any vars.
     */
    if (contain_var_clause(expr) && !generatedCol)
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), errmsg("cannot use column references in default expression")));

    /*
     * Make sure default expr does not refer to rownum.
     */
#ifndef ENABLE_MULTIPLE_NODES
    ExcludeRownumExpr(pstate, expr);
#endif
    /*
     * transformExpr() should have already rejected subqueries, aggregates,
     * window functions, and SRFs, based on the EXPR_KIND_ for a default
     * expression.
     */
    if (!pstate->p_is_flt_frame) {
        if (expression_returns_set(expr))
            ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH),
                errmsg("%s expression must not return a set", generatedCol ? "generated column" : "default")));
    }

    /*
     * No subplans or aggregates, either...
     */
    if (pstate->p_hasSubLinks)
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
            errmsg("cannot use subquery in %s expression", generatedCol ? "generated column" : "default")));
    if (pstate->p_hasAggs)
        ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR),
            errmsg("cannot use aggregate function in %s expression", generatedCol ? "generated column" : "default")));
    if (pstate->p_hasWindowFuncs)
        ereport(ERROR, (errcode(ERRCODE_WINDOWING_ERROR),
            errmsg("cannot use window function in %s expression", generatedCol ? "generated column" : "default")));

    /*
     * Coerce the expression to the correct type and typmod, if given. This
     * should match the parser's processing of non-defaulted expressions ---
     * see transformAssignedExpr().
     */
    if (OidIsValid(atttypid)) {
        Oid type_id = exprType(expr);
        if (type_is_set(atttypid)) {
            expr = coerce_to_settype(
                    pstate, expr, type_id, atttypid, atttypmod, COERCION_ASSIGNMENT, COERCE_IMPLICIT_CAST, -1, attcollation);
        } else {
            expr = coerce_to_target_type(pstate, expr, type_id, atttypid, atttypmod, COERCION_ASSIGNMENT,
                COERCE_IMPLICIT_CAST, NULL, NULL, -1);
            if (expr == NULL)
                ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH),
                    errmsg("column \"%s\" is of type %s but %s expression is of type %s", attname, format_type_be(atttypid),
                    generatedCol ? "generated column" : "default", format_type_be(type_id)),
                    errhint("You will need to rewrite or cast the expression.")));
        }
    }

    /*
     * Finally, take care of collations in the finished expression.
     */
    assign_expr_collations(pstate, expr);
    if (DB_IS_CMPT_BD && OidIsValid(attcollation)) {
        int attcharset = get_valid_charset_by_collation(attcollation);
        expr = coerce_to_target_charset(expr, attcharset, atttypid, atttypmod, attcollation);
    }
    return expr;
}

/*
 * Take a raw CHECK constraint expression and convert it to a cooked format
 * ready for storage.
 *
 * Parse state must be set up to recognize any vars that might appear
 * in the expression.
 */
static Node* cookConstraint(ParseState* pstate, Node* raw_constraint, char* relname)
{
    Node* expr = NULL;

    /*
     * Transform raw parsetree to executable expression.
     */
    expr = transformExpr(pstate, raw_constraint, EXPR_KIND_CHECK_CONSTRAINT);

    /*
     * Make sure it yields a boolean result.
     */
    expr = coerce_to_boolean(pstate, expr, "CHECK");

    /*
     * Take care of collations.
     */
    assign_expr_collations(pstate, expr);

    /*
     * Make sure no outside relations are referred to.
     */
    if (list_length(pstate->p_rtable) != 1)
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
                errmsg("only table \"%s\" can be referenced in check constraint", relname)));

    /*
     * No subplans or aggregates, either...
     */
    if (pstate->p_hasSubLinks)
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot use subquery in check constraint")));
    if (pstate->p_hasAggs)
        ereport(ERROR, (errcode(ERRCODE_GROUPING_ERROR), errmsg("cannot use aggregate function in check constraint")));
    if (pstate->p_hasWindowFuncs)
        ereport(ERROR, (errcode(ERRCODE_WINDOWING_ERROR), errmsg("cannot use window function in check constraint")));

    return expr;
}

/*
 * CopyStatistics --- copy entries in pg_statistic from one rel to another
 *
 * This function was enhanced by multi-column statistics.
 */
void CopyStatistics(Oid fromrelid, Oid torelid, char starelkind)
{
    Relation pgstatistic;
    SysScanDesc scan;
    ScanKeyData key[2];
    int nkeys = 2;
    HeapTuple tuple;

    /* Now search for stat records */
    ScanKeyInit(&key[0], Anum_pg_statistic_starelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(fromrelid));
    if (starelkind == STARELKIND_CLASS) {
        ScanKeyInit(
            &key[1], Anum_pg_statistic_starelkind, BTEqualStrategyNumber, F_CHAREQ, ObjectIdGetDatum(STARELKIND_CLASS));
    }
    /* retain this interface for partition */
    else if (starelkind == STARELKIND_PARTITION) {
        ScanKeyInit(&key[1],
            Anum_pg_statistic_starelkind,
            BTEqualStrategyNumber,
            F_CHAREQ,
            ObjectIdGetDatum(STARELKIND_PARTITION));
    }
    /* invalid starelkind, report ERROR */
    else {
        ereport(ERROR, (errcode(ERRCODE_UNDEFINED_PARAMETER), errmsg("invalid starelkind for pg_statistic")));
    }

    pgstatistic = heap_open(StatisticRelationId, RowExclusiveLock);

    scan = systable_beginscan(pgstatistic, StatisticRelidKindAttnumInhIndexId, true, NULL, nkeys, key);

    while (HeapTupleIsValid((tuple = systable_getnext(scan))))
    {
        Form_pg_statistic statform;

        /* make a modifiable copy */
        tuple = heap_copytuple(tuple);
        statform = (Form_pg_statistic) GETSTRUCT(tuple);

        /* update the copy of the tuple and insert it */
        statform->starelid = torelid;
        (void)simple_heap_insert(pgstatistic, tuple);
        CatalogUpdateIndexes(pgstatistic, tuple);

        heap_freetuple(tuple);
    }

    systable_endscan(scan);

    heap_close(pgstatistic, RowExclusiveLock);

    /*
     * NOTE: scan key is reused. Since the two keys have the same attrnum
     * in pg_statistic and pg_statistic_ext, we don't need to change it
     */
    pgstatistic = heap_open(StatisticExtRelationId, RowExclusiveLock);
    scan = systable_beginscan(pgstatistic, StatisticExtRelidKindInhKeyIndexId, true, NULL, nkeys, key);

    while (HeapTupleIsValid((tuple = systable_getnext(scan))))
    {
        Form_pg_statistic_ext statform;

        /* make a modifiable copy */
        tuple = heap_copytuple(tuple);
        statform = (Form_pg_statistic_ext) GETSTRUCT(tuple);

        /* update the copy of the tuple and insert it */
        statform->starelid = torelid;
        (void)simple_heap_insert(pgstatistic, tuple);
        CatalogUpdateIndexes(pgstatistic, tuple);

        heap_freetuple(tuple);
    }

    systable_endscan(scan);

    heap_close(pgstatistic, RowExclusiveLock);
}

/*
 * RemoveStatistics --- remove entries in pg_statistic for a rel or column
 *
 * If attnum is zero, remove all entries for rel;
 * else remove only the one(s) for that column.
 *
 * This function was enhanced by multi-column statistics.
 */
template <char starelkind>
void RemoveStatistics(Oid relid, AttrNumber attnum)
{
    Relation pgstatistic;
    SysScanDesc scan;
    ScanKeyData key[3];
    int nkeys;
    HeapTuple tuple;

    /* We want to find all statistics for this table */
    ScanKeyInit(&key[0], Anum_pg_statistic_starelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relid));
    if (starelkind == STARELKIND_CLASS) {
        ScanKeyInit(
            &key[1], Anum_pg_statistic_starelkind, BTEqualStrategyNumber, F_CHAREQ, ObjectIdGetDatum(STARELKIND_CLASS));
    }
    /* retain this interface for partition */
    else if (starelkind == STARELKIND_PARTITION) {
        ScanKeyInit(&key[1],
            Anum_pg_statistic_starelkind,
            BTEqualStrategyNumber,
            F_CHAREQ,
            ObjectIdGetDatum(STARELKIND_PARTITION));
    }
    /* invalid starelkind, report ERROR */
    else {
        ereport(ERROR, (errcode(ERRCODE_UNDEFINED_PARAMETER), errmsg("invalid starelkind for pg_statistic")));
    }

    if (0 == attnum) {
        nkeys = 2;
    } else {
        ScanKeyInit(&key[2], Anum_pg_statistic_staattnum, BTEqualStrategyNumber, F_INT2EQ, Int16GetDatum(attnum));
        nkeys = 3;
    }

    pgstatistic = heap_open(StatisticRelationId, RowExclusiveLock);
    scan = systable_beginscan(pgstatistic, StatisticRelidKindAttnumInhIndexId, true, NULL, nkeys, key);

    /* we must loop even when attnum != 0, in case of inherited stats */
    while (HeapTupleIsValid(tuple = systable_getnext(scan)))
        simple_heap_delete(pgstatistic, &tuple->t_self);

    systable_endscan(scan);
    heap_close(pgstatistic, RowExclusiveLock);

    /*
     * NOTE: scan key is reused with the first two keys. Since the two keys have
     * the same attrnum in pg_statistic and pg_statistic_ext, we don't need to change it
     */
    nkeys = 2;
    pgstatistic = heap_open(StatisticExtRelationId, RowExclusiveLock);
    scan = systable_beginscan(pgstatistic, StatisticExtRelidKindInhKeyIndexId, true, NULL, nkeys, key);

    /* we must loop here, in case of inherited stats and extended stats */
    while (HeapTupleIsValid(tuple = systable_getnext(scan))) {
        if (0 == attnum)
            simple_heap_delete(pgstatistic, &tuple->t_self);
        else {
            bool isnull = false;
            Datum attnums = heap_getattr(tuple, Anum_pg_statistic_ext_stakey, RelationGetDescr(pgstatistic), &isnull);

            int2vector* keys = (int2vector*)DatumGetPointer(attnums);
            int key_nums = keys->dim1;
            int i;
            for (i = 0; i < key_nums; i++) {
                if (attnum == keys->values[i])
                    break;
            }
            /* found multi-column stat that contains certain attnum */
            if (i < key_nums)
                simple_heap_delete(pgstatistic, &tuple->t_self);
        }
    }

    systable_endscan(scan);
    heap_close(pgstatistic, RowExclusiveLock);

    if (t_thrd.proc->workingVersionNum >= STATISTIC_HISTORY_VERSION_NUMBER) {
        RemoveStatisticHistory(relid, attnum);
        if (attnum == 0) {
            Oid namespaceid = get_rel_namespace(relid);
            RemoveStatisticLockTab(namespaceid, relid);
        }
    }
}

/*
 * RelationTruncateIndexes - truncate all indexes associated
 * with the heap relation to zero tuples.
 *
 * The routine will truncate and then reconstruct the indexes on
 * the specified relation.	Caller must hold exclusive lock on rel.
 */
static void RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
{
    ListCell* indlist = NULL;

    /* Ask the relcache to produce a list of the indexes of the rel */
    foreach (indlist, RelationGetIndexList(heapRelation)) {
        Oid indexId = lfirst_oid(indlist);
        Relation currentIndex;
        IndexInfo* indexInfo = NULL;

        /* Open the index relation; use exclusive lock, just to be sure */
        currentIndex = index_open(indexId, lockmode);

        /* Fetch info needed for index_build */
        indexInfo = BuildIndexInfo(currentIndex);

        /*
         * Now truncate the actual file (and discard buffers).
         */
        RelationTruncate(currentIndex, 0);

        /* truncate psort relation */
        if (unlikely(currentIndex->rd_rel->relam == PSORT_AM_OID)) {
            Relation psort_rel = heap_open(currentIndex->rd_rel->relcudescrelid, lockmode);
            heap_truncate_one_rel(psort_rel);
            heap_close(psort_rel, NoLock);
        }

        /* Initialize the index and rebuild */
        /* Note: we do not need to re-establish pkey setting */
        index_build(heapRelation, NULL, currentIndex, NULL, indexInfo, false, true, INDEX_CREATE_NONE_PARTITION);

        /* We're done with this index */
        index_close(currentIndex, NoLock);
    }
}

/*
 *	 heap_truncate
 *
 *	 This routine deletes all data within all the specified relations.
 *
 * This is not transaction-safe!  There is another, transaction-safe
 * implementation in commands/tablecmds.c.	We now use this only for
 * ON COMMIT truncation of temporary tables, where it doesn't matter.
 */
void heap_truncate(List* relids)
{
    List* relations = NIL;
    ListCell* cell = NULL;

    /* Open relations for processing, and grab exclusive access on each */
    foreach (cell, relids) {
        Oid rid = lfirst_oid(cell);
        Relation rel;
        LOCKMODE lockmode = AccessExclusiveLock;

        /* truncate global temp table only need RowExclusiveLock */
        if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP) {
            lockmode = RowExclusiveLock;
        }

        rel = heap_open(rid, lockmode);
        relations = lappend(relations, rel);
    }

    /* Don't allow truncate on tables that are referenced by foreign keys */
    heap_truncate_check_FKs(relations, true);

    /* OK to do it */
    foreach (cell, relations) {
        Relation rel = (RelationData*)lfirst(cell);

        /* Truncate the relation */
        heap_truncate_one_rel(rel);
        pgstat_count_truncate(rel);

        /* Close the relation, but keep exclusive lock on it until commit */
        heap_close(rel, NoLock);
    }
}

/* truncate one partition of ColumnStore in the same xact */
static void TruncateColCStorePartitionInSameXact(Relation rel, Partition p)
{
    /* truncate cudesc heap relation */
    Relation cudesc_rel = heap_open(p->pd_part->relcudescrelid, AccessExclusiveLock);
    heap_truncate_one_rel(cudesc_rel);
    heap_close(cudesc_rel, NoLock);

    /* truncate delta heap relation */
    Relation delta_rel = heap_open(p->pd_part->reldeltarelid, AccessExclusiveLock);
    heap_truncate_one_rel(delta_rel);
    heap_close(delta_rel, NoLock);

    /* truncate each column */
    Relation partRel = partitionGetRelation(rel, p);
    CStore::TruncateStorageInSameXact(partRel);
    releaseDummyRelation(&partRel);
}

/*
 *	 heap_truncate_one_rel
 *
 *	 This routine deletes all data within the specified relation.
 *
 * This is not transaction-safe, because the truncation is done immediately
 * and cannot be rolled back later.  Caller is responsible for having
 * checked permissions etc, and must have obtained AccessExclusiveLock.
 */
void heap_truncate_one_rel(Relation rel)
{
    Oid toastrelid;
    LOCKMODE lockmode = AccessExclusiveLock;

    if (RELATION_IS_GLOBAL_TEMP(rel)) {
        if (!gtt_storage_attached(RelationGetRelid(rel)))
            return;

        /*
         * Truncate global temp table only need RowExclusiveLock
         */
        lockmode = RowExclusiveLock;
    }

#ifdef ENABLE_MOT
    if (RelationIsForeignTable(rel) && isMOTFromTblOid(RelationGetRelid(rel))) {
        FdwRoutine* fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(rel));

        if (fdwroutine->TruncateForeignTable != NULL) {
            fdwroutine->TruncateForeignTable(NULL, rel);
        }
    } else if (!RELATION_IS_PARTITIONED(rel)) {
#else
    if (!RELATION_IS_PARTITIONED(rel)) {
#endif
        /* Truncate the actual file (and discard buffers) */
        RelationTruncate(rel, 0);

        /* If the relation is a cloumn store */
        if (RelationIsColStore(rel)) {
            /* cudesc */
            Relation cudesc_rel = heap_open(rel->rd_rel->relcudescrelid, lockmode);
            heap_truncate_one_rel(cudesc_rel);
            heap_close(cudesc_rel, NoLock);

            /* delta */
            Relation delta_rel = heap_open(rel->rd_rel->reldeltarelid, lockmode);
            heap_truncate_one_rel(delta_rel);
            heap_close(delta_rel, NoLock);

            /* each column */
            CStore::TruncateStorageInSameXact(rel);
        }

        /* If the relation has indexes, truncate the indexes too */
        RelationTruncateIndexes(rel, lockmode);

        /* If there is a toast table, truncate that too */
        toastrelid = rel->rd_rel->reltoastrelid;
        if (OidIsValid(toastrelid)) {
            Relation toastrel = heap_open(toastrelid, lockmode);

            RelationTruncate(toastrel, 0);
            RelationTruncateIndexes(toastrel, lockmode);
            /* keep the lock... */
            heap_close(toastrel, NoLock);
        }
    } else { /* partitioned table */
        List* partOidList = NIL;
        List* indexList = NIL;
        ListCell* indCell = NULL;
        Relation currentIndex = NULL;
        List* currentParttiionIndexList = NIL;
        ListCell* partCell = NULL;

        /* truncate each partition */
        partOidList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_PARTITION, rel->rd_id);
        foreach (partCell, partOidList) {
            Partition p = partitionOpen(rel, HeapTupleGetOid((HeapTuple)lfirst(partCell)), lockmode);

            PartitionTruncate(rel, p, 0);

            /* truncate before reindex */
            if (RelationIsColStore(rel)) {
                TruncateColCStorePartitionInSameXact(rel, p);
            }
            partitionClose(rel, p, NoLock);
        }

        /* process index */
        indexList = RelationGetIndexList(rel);
        foreach (indCell, indexList) {
            ListCell* cell1 = NULL;
            IndexInfo* indexInfo = NULL;
            Oid indexId = lfirst_oid(indCell);
            currentIndex = index_open(indexId, lockmode);

            indexInfo = BuildIndexInfo(currentIndex);

            currentParttiionIndexList = searchPgPartitionByParentId(PART_OBJ_TYPE_INDEX_PARTITION, indexId);

            foreach (cell1, currentParttiionIndexList) {
                Partition indexPart =
                    partitionOpen(currentIndex, HeapTupleGetOid((HeapTuple)lfirst(cell1)), lockmode);
                Partition p;

                PartitionTruncate(currentIndex, indexPart, 0);
                /* truncate psort relation */
                if (unlikely(currentIndex->rd_rel->relam == PSORT_AM_OID)) {
                    Relation psort_rel = heap_open(currentIndex->rd_rel->relcudescrelid, lockmode);
                    heap_truncate_one_rel(psort_rel);
                    heap_close(psort_rel, NoLock);
                }

                p = partitionOpen(rel, indexPart->pd_part->indextblid, NoLock);
                index_build(rel, p, currentIndex, indexPart, indexInfo, false, true, INDEX_CREATE_LOCAL_PARTITION);

                partitionClose(rel, p, NoLock);
                partitionClose(currentIndex, indexPart, NoLock);
            }
            freePartList(currentParttiionIndexList);

            index_close(currentIndex, NoLock);
        }

        /* process toast table for non-bucketed relation */
        foreach (partCell, partOidList) {
            HeapTuple tup = (HeapTuple)lfirst(partCell);

            Form_pg_partition partForm = (Form_pg_partition)GETSTRUCT(tup);

            if (partForm->reltoastrelid != InvalidOid) {
                Relation toastrel = heap_open(partForm->reltoastrelid, lockmode);

                RelationTruncate(toastrel, 0);
                RelationTruncateIndexes(toastrel, lockmode);
                /* keep the lock... */
                heap_close(toastrel, NoLock);
            }
        }

        freePartList(partOidList);
    }
	// for GTT
    if (RELATION_IS_GLOBAL_TEMP(rel)) {
        up_gtt_relstats(rel, 0, 0, 0, u_sess->utils_cxt.RecentXmin);
    }
}

/*
 * heap_truncate_check_FKs
 *		Check for foreign keys referencing a list of relations that
 *		are to be truncated, and raise error if there are any
 *
 * We disallow such FKs (except self-referential ones) since the whole point
 * of TRUNCATE is to not scan the individual rows to be thrown away.
 *
 * This is split out so it can be shared by both implementations of truncate.
 * Caller should already hold a suitable lock on the relations.
 *
 * tempTables is only used to select an appropriate error message.
 */
void heap_truncate_check_FKs(List* relations, bool tempTables)
{
    List* oids = NIL;
    List* dependents = NIL;
    ListCell* cell = NULL;

    /*
     * Build a list of OIDs of the interesting relations.
     *
     * If a relation has no triggers, then it can neither have FKs nor be
     * referenced by a FK from another table, so we can ignore it.
     */
    foreach (cell, relations) {
        Relation rel = (Relation)lfirst(cell);

        if (rel->rd_rel->relhastriggers)
            oids = lappend_oid(oids, RelationGetRelid(rel));
    }

    /*
     * Fast path: if no relation has triggers, none has FKs either.
     */
    if (oids == NIL)
        return;

    /*
     * Otherwise, must scan pg_constraint.	We make one pass with all the
     * relations considered; if this finds nothing, then all is well.
     */
    dependents = heap_truncate_find_FKs(oids);
    if (dependents == NIL)
        return;

    /*
     * Otherwise we repeat the scan once per relation to identify a particular
     * pair of relations to complain about.  This is pretty slow, but
     * performance shouldn't matter much in a failure path.  The reason for
     * doing things this way is to ensure that the message produced is not
     * dependent on chance row locations within pg_constraint.
     */
    foreach (cell, oids) {
        Oid relid = lfirst_oid(cell);
        ListCell* cell2 = NULL;

        dependents = heap_truncate_find_FKs(list_make1_oid(relid));

        foreach (cell2, dependents) {
            Oid relid2 = lfirst_oid(cell2);

            if (!list_member_oid(oids, relid2)) {
                char* relname = get_rel_name(relid);
                char* relname2 = get_rel_name(relid2);

                if (tempTables)
                    ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                            errmsg("unsupported ON COMMIT and foreign key combination"),
                            errdetail(
                                "Table \"%s\" references \"%s\", but they do not have the same ON COMMIT setting.",
                                relname2,
                                relname)));
                else
                    ereport(ERROR,
                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                            errmsg("cannot truncate a table referenced in a foreign key constraint"),
                            errdetail("Table \"%s\" references \"%s\".", relname2, relname),
                            errhint("Truncate table \"%s\" at the same time, "
                                    "or use TRUNCATE ... CASCADE.",
                                relname2)));
            }
        }
    }
}

/*
 * heap_truncate_find_FKs
 *		Find relations having foreign keys referencing any of the given rels
 *
 * Input and result are both lists of relation OIDs.  The result contains
 * no duplicates, does *not* include any rels that were already in the input
 * list, and is sorted in OID order.  (The last property is enforced mainly
 * to guarantee consistent behavior in the regression tests; we don't want
 * behavior to change depending on chance locations of rows in pg_constraint.)
 *
 * Note: caller should already have appropriate lock on all rels mentioned
 * in relationIds.	Since adding or dropping an FK requires exclusive lock
 * on both rels, this ensures that the answer will be stable.
 */
List* heap_truncate_find_FKs(List* relationIds)
{
    List* result = NIL;
    Relation fkeyRel;
    SysScanDesc fkeyScan;
    HeapTuple tuple;

    /*
     * Must scan pg_constraint.  Right now, it is a seqscan because there is
     * no available index on confrelid.
     */
    fkeyRel = heap_open(ConstraintRelationId, AccessShareLock);

    fkeyScan = systable_beginscan(fkeyRel, InvalidOid, false, NULL, 0, NULL);

    while (HeapTupleIsValid(tuple = systable_getnext(fkeyScan))) {
        Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(tuple);

        /* Not a foreign key */
        if (con->contype != CONSTRAINT_FOREIGN)
            continue;

        /* Not referencing one of our list of tables */
        if (!list_member_oid(relationIds, con->confrelid))
            continue;

        /* Add referencer unless already in input or result list */
        if (!list_member_oid(relationIds, con->conrelid))
            result = insert_ordered_unique_oid(result, con->conrelid);
    }

    systable_endscan(fkeyScan);
    heap_close(fkeyRel, AccessShareLock);

    return result;
}

/*
 * insert_ordered_unique_oid
 *		Insert a new Oid into a sorted list of Oids, preserving ordering,
 *		and eliminating duplicates
 *
 * Building the ordered list this way is O(N^2), but with a pretty small
 * constant, so for the number of entries we expect it will probably be
 * faster than trying to apply qsort().  It seems unlikely someone would be
 * trying to truncate a table with thousands of dependent tables ...
 */
static List* insert_ordered_unique_oid(List* list, Oid datum)
{
    ListCell* prev = NULL;

    /* Does the datum belong at the front? */
    if (list == NIL || datum < linitial_oid(list))
        return lcons_oid(datum, list);
    /* Does it match the first entry? */
    if (datum == linitial_oid(list))
        return list; /* duplicate, so don't insert */
    /* No, so find the entry it belongs after */
    prev = list_head(list);
    for (;;) {
        ListCell* curr = lnext(prev);

        if (curr == NULL || datum < lfirst_oid(curr))
            break; /* it belongs after 'prev', before 'curr' */

        if (datum == lfirst_oid(curr))
            return list; /* duplicate, so don't insert */

        prev = curr;
    }
    /* Insert datum into list after 'prev' */
    (void)lappend_cell_oid(list, prev, datum);
    return list;
}

/*
 * @@GaussDB@@
 * Brief		: buildPartitionKey
 * Description	: build partitioin key attribute NO. array based on table's TupleDesc
 * Notes		:
 */
int2vector* buildPartitionKey(List* keys, TupleDesc tupledsc)
{
    ListCell* cell = NULL;
    ColumnRef* col = NULL;
    int i = 0;
    int j = 0;
    int attnum = tupledsc->natts;
    int partkeyNum = keys->length;
    char* columName = NULL;
    bool finded = false;
    FormData_pg_attribute* attrs = tupledsc->attrs;
    int2vector* partkey = NULL;

    partkey = buildint2vector(NULL, partkeyNum);
    bool isExpr = false;
    bool isFunc = false;
    foreach (cell, keys) {
        col = (ColumnRef*)GetColumnRef((Node*)lfirst(cell), &isExpr, &isFunc);
        if (!col)
            ereport(ERROR,(errcode(ERRCODE_UNDEFINED_COLUMN),(errmsg("The partition key doesn't have any column."))));
        if (isExpr && keys->length > 1)
            ereport(ERROR,(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),(errmsg("The multi partition expr keys are not supported."))));
        columName = ((Value*)linitial(col->fields))->val.str;
        finded = false;
        for (j = 0; j < attnum; j++) {
            if (strcmp(columName, attrs[j].attname.data) == 0) {
                partkey->values[i] = attrs[j].attnum;
                finded = true;
                break;
            }
        }

        if (!finded) {
            ereport(ERROR,
                (errcode(ERRCODE_INTERVAL_FIELD_OVERFLOW),
                    (errmsg("buildPartitionKey(): partKeys specified NONE IS found'"))));
        }

        i++;
    }
    return partkey;
}

static oidvector* BuildIntervalTablespace(const IntervalPartitionDefState* intervalPartDef)
{
    if (intervalPartDef->intervalTablespaces == NULL || intervalPartDef->intervalTablespaces->length == 0) {
        return NULL;
    }
    oidvector* tablespaceVec = buildoidvector(NULL, intervalPartDef->intervalTablespaces->length);
    ListCell* cell = NULL;
    int i = 0;
    const char* tablespaceName;
    Oid tableSpaceOid;
    AclResult aclresult;

    foreach (cell, intervalPartDef->intervalTablespaces) {
        tablespaceName = ((Value*)lfirst(cell))->val.str;
        tableSpaceOid = get_tablespace_oid(tablespaceName, false);

        if (tableSpaceOid != u_sess->proc_cxt.MyDatabaseTableSpace) {
            aclresult = pg_tablespace_aclcheck(tableSpaceOid, GetUserId(), ACL_CREATE);
            if (aclresult != ACLCHECK_OK) {
                aclcheck_error(aclresult, ACL_KIND_TABLESPACE, tablespaceName);
            }
        }
        tablespaceVec->values[i] = tableSpaceOid;
        i++;
    }
    return tablespaceVec;
}

static Datum BuildInterval(Node* partInterval)
{
    Assert(IsA(partInterval, A_Const));
    A_Const* constNode = (A_Const*)partInterval;
    Assert(IsA(&constNode->val, String));

    ArrayBuildState* astate = NULL;
    astate = accumArrayResult(
        astate, PointerGetDatum(cstring_to_text(constNode->val.val.str)), false, TEXTOID, CurrentMemoryContext);
    return makeArrayResult(astate, CurrentMemoryContext);
}

/*
 * @@GaussDB@@
 * Brief		: AddNewPartitionTuple
 * Description	: Insert a entry to pg_partition. The entry is for partitioned-table or partition.
 * Notes		:
 */
void addNewPartitionTuple(Relation pg_part_desc, Partition new_part_desc, PartitionTupleInfo *partTupleInfo)
{
    Form_pg_partition new_part_tup = new_part_desc->pd_part;
    /*
     * first we update some of the information in our uncataloged relation's
     * relation descriptor.
     */
    /* The relation is real, but as yet empty */
    new_part_tup->relpages = 0;
    new_part_tup->reltuples = 0;
    new_part_tup->relallvisible = 0;
    /* Initialize relfrozenxid */
    /*
     * Initialize to the minimum XID that could put tuples in the table.
     * We know that no xacts older than RecentXmin are still running, so
     * that will do.
     */
    /* relfrozenxid is aborted, we use relfrozenxid64 instead */
    new_part_tup->relfrozenxid = (ShortTransactionId)InvalidTransactionId;

    /* Now build and insert the tuple */
    insertPartitionEntry(pg_part_desc, new_part_desc, new_part_desc->pd_id, partTupleInfo);
}

static void deletePartitionTuple(Oid part_id)
{
    Relation pg_partition_desc;
    HeapTuple tup;

    /* Grab an appropriate lock on the pg_partition relation */

    pg_partition_desc = heap_open(PartitionRelationId, RowExclusiveLock);

    tup = SearchSysCache1(PARTRELID, ObjectIdGetDatum(part_id));

    if (!HeapTupleIsValid(tup)) {
        ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for partition %u", part_id)));
    }

    /* delete the relation tuple from pg_partition, and finish up */
    else {
        simple_heap_delete(pg_partition_desc, &tup->t_self);
    }

    ReleaseSysCache(tup);

    heap_close(pg_partition_desc, RowExclusiveLock);
}

void dropToastTableOnPartition(Oid partId)
{
    ObjectAddresses* objects = NULL;
    ObjectAddress obj;
    Oid partToastRelid = InvalidOid;
    Form_pg_partition partitionTuple = NULL;
    HeapTuple partitionTupleRaw = NULL;

    /*partToastRelid MUST be a valid oid*/
    partitionTupleRaw = SearchSysCache1((int)PARTRELID, ObjectIdGetDatum(partId));
    if (!PointerIsValid(partitionTupleRaw)) {
        return;
    }
    partitionTuple = (Form_pg_partition)GETSTRUCT(partitionTupleRaw);
    partToastRelid = partitionTuple->reltoastrelid;
    if (!OidIsValid(partToastRelid)) {
        ReleaseSysCache(partitionTupleRaw);
        return;
    }

    /* OK, we're ready to delete toast table on partition*/
    objects = new_object_addresses();
    obj.classId = RelationRelationId;
    obj.objectId = partToastRelid;
    obj.objectSubId = 0;
    add_exact_object_address(&obj, objects);

    /*call performMultipleDeletions, it then delete
     * a, pg_toast_oid type object in pg_type
     * b, pg_toast_oid_index index object in pg_class
     * c, pg_toast_oid toast relation in pg_class.
     */
    performMultipleDeletions(objects, DROP_CASCADE, 0);

    /*cleanup*/
    free_object_addresses(objects);
    ReleaseSysCache(partitionTupleRaw);
}

void dropCuDescTableOnPartition(Oid partId)
{
    ObjectAddresses* objects = NULL;
    ObjectAddress obj;
    Oid partCuDescRelid = InvalidOid;
    Form_pg_partition partitionTuple = NULL;
    HeapTuple partitionTupleRaw = NULL;

    /*partToastRelid MUST be a valid oid*/
    partitionTupleRaw = SearchSysCache1((int)PARTRELID, ObjectIdGetDatum(partId));
    if (!PointerIsValid(partitionTupleRaw)) {
        return;
    }
    partitionTuple = (Form_pg_partition)GETSTRUCT(partitionTupleRaw);
    partCuDescRelid = partitionTuple->relcudescrelid;
    if (!OidIsValid(partCuDescRelid)) {
        ReleaseSysCache(partitionTupleRaw);
        return;
    }

    /* OK, we're ready to delete toast table on partition*/
    objects = new_object_addresses();
    obj.classId = RelationRelationId;
    obj.objectId = partCuDescRelid;
    obj.objectSubId = 0;
    add_exact_object_address(&obj, objects);

    /*call performMultipleDeletions, it then delete
     * a, pg_toast_oid type object in pg_type
     * b, pg_toast_oid_index index object in pg_class
     * c, pg_toast_oid toast relation in pg_class.
     */
    performMultipleDeletions(objects, DROP_CASCADE, 0);

    /*cleanup*/
    free_object_addresses(objects);
    ReleaseSysCache(partitionTupleRaw);
}

void dropDeltaTableOnPartition(Oid partId)
{
    ObjectAddresses* objects = NULL;
    ObjectAddress obj;
    Oid partDeltaTableRelid = InvalidOid;
    Form_pg_partition partitionTuple = NULL;
    HeapTuple partitionTupleRaw = NULL;

    /*partToastRelid MUST be a valid oid*/
    partitionTupleRaw = SearchSysCache1((int)PARTRELID, ObjectIdGetDatum(partId));
    if (!PointerIsValid(partitionTupleRaw)) {
        return;
    }
    partitionTuple = (Form_pg_partition)GETSTRUCT(partitionTupleRaw);
    partDeltaTableRelid = partitionTuple->reldeltarelid;
    if (!OidIsValid(partDeltaTableRelid)) {
        ReleaseSysCache(partitionTupleRaw);
        return;
    }

    /* OK, we're ready to delete toast table on partition*/
    objects = new_object_addresses();
    obj.classId = RelationRelationId;
    obj.objectId = partDeltaTableRelid;
    obj.objectSubId = 0;
    add_exact_object_address(&obj, objects);

    /*call performMultipleDeletions, it then delete
     * a, pg_toast_oid type object in pg_type
     * b, pg_toast_oid_index index object in pg_class
     * c, pg_toast_oid toast relation in pg_class.
     */
    performMultipleDeletions(objects, DROP_CASCADE, 0);

    /*cleanup*/
    free_object_addresses(objects);
    ReleaseSysCache(partitionTupleRaw);
}

/*
 * @@GaussDB@@
 * Brief        :
 * Description  : Build local PartitionData instance, which is not in partition hashtable
 * Notes        :
 * Parameters   : part_name: partition name
 *                for_partitioned_table: true for partitioned table, false for partition
 *                part_tablespace: partition's tablespace.
 *                part_id: partition's oid. This parameter cannot be InvalidOid.
 *                relbufferpool: partition's bufferpool. All partitions of one partitioned table have same bufferpool.

 *                partFileNode: whether an existing index definition is
                                compatible with a prospective index definition,
                                such that the existing index storage
                                could become the storage of the new index,
                                avoiding a rebuild.
 *
 */
Partition heapCreatePartition(const char* part_name, bool for_partitioned_table, Oid part_tablespace, Oid part_id,
    Oid partFileNode, Oid bucketOid, Oid ownerid, StorageType storage_type, bool newcbi, Datum reloptions)
{
    Partition new_part_desc = NULL;
    bool createStorage = false;
    bool isbucket = false;

    /* The caller must have provided an OID for the partition. */
    Assert(OidIsValid(part_id));

    /*
     * Never allow a pg_partition entry to explicitly specify the database's
     * default tablespace in reltablespace; force it to zero instead. This
     * ensures that if the database is cloned with a different default
     * tablespace, the pg_class entry will still match where CREATE DATABASE
     * will put the physically copied relation.
     *
     * Yes, this is a bit of a hack.
     */
    part_tablespace = ConvertToPgclassRelTablespaceOid(part_tablespace);

    if (!for_partitioned_table) {
        if (!OidIsValid(partFileNode)) {
            createStorage = true;
            if (storage_type == SEGMENT_PAGE) {
                Assert(part_tablespace != GLOBALTABLESPACE_OID);
                isbucket = BUCKET_OID_IS_VALID(bucketOid) && !newcbi;
                partFileNode = (Oid)seg_alloc_segment(ConvertToRelfilenodeTblspcOid(part_tablespace),
                                                      u_sess->proc_cxt.MyDatabaseId, isbucket, InvalidBlockNumber);
                ereport(LOG, (errmsg("Segment Partition %s(%u) set relfilenode %u xid %lu", part_name, part_id,
                    partFileNode, GetCurrentTransactionIdIfAny())));
            } else {
                partFileNode = part_id;
            }
        }
        if (u_sess->proc_cxt.IsBinaryUpgrade) {
            createStorage = true;
            if (storage_type == SEGMENT_PAGE) {
                Assert(part_tablespace != GLOBALTABLESPACE_OID);
                BlockNumber preassignedBlock = OidIsValid(partFileNode) ? partFileNode : InvalidBlockNumber;
                isbucket = BUCKET_OID_IS_VALID(bucketOid) && !newcbi;
                partFileNode = (Oid)seg_alloc_segment(ConvertToRelfilenodeTblspcOid(part_tablespace),
                                                      u_sess->proc_cxt.MyDatabaseId, isbucket, preassignedBlock);
            }
        }
    }

    /*
     * build the partcache entry.
     */
    new_part_desc = PartitionBuildLocalPartition(part_name,
        part_id,      /* partition oid */
        partFileNode, /* partition's file node, same as partition oid*/
        part_tablespace,
        for_partitioned_table ? HEAP_DISK : storage_type,
        reloptions);

    /*
     * Save newcbi as a context indicator to
     * avoid missing information in later index building process.
     */
    new_part_desc->newcbi = newcbi;

    /*
     * if this pg_partition entry is for partitioned table, then part_filenode is invalid.
     * if this pg_partition entry is for partition, then part_filenode must be specified.
     */
    if (!for_partitioned_table && createStorage) {
        PartitionOpenSmgr(new_part_desc);
        if (newcbi) {
            /* using a dummy relation to pass on newcbi flag */
            RelationData dummyrel;
            dummyrel.rd_id = InvalidOid;
            dummyrel.newcbi = true;
            RelationCreateStorage(new_part_desc->pd_node,
                RELPERSISTENCE_PERMANENT, /* partition table's persitence MUST be 'p'(permanent table)*/
                ownerid,
                bucketOid,
                &dummyrel);
        } else {
            RelationCreateStorage(new_part_desc->pd_node,
                RELPERSISTENCE_PERMANENT, /* partition table's persitence MUST be 'p'(permanent table)*/
                ownerid,
                bucketOid);
        }
    }

    return new_part_desc;

}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		:
 * Description	:
 * Notes		:
 */
void heapDropPartitionToastList(List* toastList)
{
    ObjectAddresses* objects = NULL;
    ObjectAddress obj;
    Oid toastRelid = InvalidOid;
    ListCell* cell = NULL;

    /* OK, we're ready to delete toast table on partition*/
    objects = new_object_addresses();

    foreach (cell, toastList) {
        toastRelid = lfirst_oid(cell);
        if (OidIsValid(toastRelid)) {
            obj.classId = RelationRelationId;
            obj.objectId = toastRelid;
            obj.objectSubId = 0;
            add_exact_object_address(&obj, objects);
        }
    }

    /*call performMultipleDeletions, it then delete
     * a, pg_toast_oid type object in pg_type
     * b, pg_toast_oid_index index object in pg_class
     * c, pg_toast_oid toast relation in pg_class.
     */
    performMultipleDeletions(objects, DROP_CASCADE, 0);

    /*cleanup*/
    free_object_addresses(objects);
}

void heapDropSubPartitionList(Relation rel, Oid partId)
{
    ListCell* cell = NULL;
    List *partCacheList = NIL;
    HeapTuple partTuple = NULL;
    Oid subPartOid = InvalidOid;
    Form_pg_partition partForm = NULL;
    Oid toastOid = InvalidOid;
    Oid cuDesc = InvalidOid;
    Oid delta = InvalidOid;
    List *toastOidList = NIL;
    List *cuDescOidList = NIL;
    List *deltaOidList = NIL;
    List *subPartOidList = NIL;

    partCacheList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_SUB_PARTITION, partId);
    foreach (cell, partCacheList) {
        partTuple = (HeapTuple)lfirst(cell);

        if (PointerIsValid(partTuple)) {
            subPartOid = HeapTupleGetOid(partTuple);
            subPartOidList = lappend_oid(subPartOidList, subPartOid);

            partForm = (Form_pg_partition)GETSTRUCT(partTuple);
            toastOid = partForm->reltoastrelid;
            cuDesc = partForm->relcudescrelid;
            delta = partForm->reldeltarelid;

            if (OidIsValid(toastOid)) {
                toastOidList = lappend_oid(toastOidList, toastOid);
            }

            if (OidIsValid(cuDesc)) {
                cuDescOidList = lappend_oid(cuDescOidList, cuDesc);
            }

            if (OidIsValid(delta)) {
                deltaOidList = lappend_oid(deltaOidList, delta);
            }
        }
    }

    /*
     * drop table partition's toast table
     */
    if (PointerIsValid(toastOidList)) {
        heapDropPartitionToastList(toastOidList);
        list_free(toastOidList);
    }

    if (PointerIsValid(cuDescOidList)) {
        heapDropPartitionToastList(cuDescOidList);
        list_free(cuDescOidList);
    }

    if (PointerIsValid(deltaOidList)) {
        heapDropPartitionToastList(deltaOidList);
        list_free(deltaOidList);
    }

    /*
     * drop table partition
     */
    if (PointerIsValid(subPartOidList)) {
        heapDropPartitionList(rel, subPartOidList);
        list_free(subPartOidList);
    }
    freePartList(partCacheList);
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		:
 * Description	:
 * Notes		:
 */
void heapDropPartitionList(Relation rel, List* partitionList)
{
    List* newPartList = NIL;
    ListCell* cell = NULL;
    Oid partId = InvalidOid;
    Partition part = NULL;
    Relation partRel = NULL;

    /*check input parameters validity*/
    Assert(PointerIsValid(rel));
    Assert(RELATION_IS_PARTITIONED(rel));
    Assert(PointerIsValid(partitionList));

    /*
     * step 1: delete depending objs on partition list
     */
    foreach (cell, partitionList) {
        partId = lfirst_oid(cell);
        if (OidIsValid(partId)) {
            part = partitionOpen(rel, partId, AccessExclusiveLock);
            if (RelationIsSubPartitioned(rel)) {
                partRel = partitionGetRelation(rel, part);
                heapDropSubPartitionList(partRel, partId);
                releaseDummyRelation(&partRel);
            }
            newPartList = lappend(newPartList, part);
        }
    }

    /*
     * step 2: iterate partList, delete one by one
     */
    foreach (cell, newPartList) {
        part = (Partition)lfirst(cell);
        heapDropPartition(rel, part);
    }

    /*
     * clean up
     */
    list_free(newPartList);
}
/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		:
 * Description	:
 * Notes		: the caller MUST have AccessExclusiveLock lock on partition
 */
void heapDropPartition(Relation relation, Partition part)
{
    Oid partid = InvalidOid;
    Relation partRel = NULL;

    /*
     * Open and lock the relation.
     */
    Assert(PointerIsValid(part));

    partid = part->pd_id;

    checkPartNotInUse(part, "DROP PARTITION");

    if (!(RelationIsSubPartitioned(relation) && part->pd_part->parttype == PART_OBJ_TYPE_TABLE_PARTITION)) {
        partRel = partitionGetRelation(relation, part);

        TransferPredicateLocksToHeapRelation(partRel);

        /*
         * Schedule unlinking of the relation's physical files at commit.
         */
        RelationDropStorage(partRel);
        releaseDummyRelation(&partRel);
    }

    partitionClose(relation, part, NoLock);
    /*
     * Flush the partition from the partcache.  We want to do this before
     * starting to remove catalog entries, just to be certain that no partcache
     * entry rebuild will happen partway through.  (That should not really
     * matter, since we don't do CommandCounterIncrement here, but let's be
     * safe.)
     */
    PartitionForgetPartition(partid);

    /*
     * delete statistics
     */
    RemoveStatistics<'p'>(partid, 0);

    /*
     * delete partition tuple
     */
    deletePartitionTuple(partid);
}

static void heapDropPartitionTable(Relation relation)
{
    List* partCacheList = NIL;
    List* partitionOidList = NIL;
    List* toastOidList = NIL;
    List* cuDescOidList = NIL;
    List* deltaOidList = NIL;
    Oid partOid = InvalidOid;
    Oid toastOid = InvalidOid;
    Oid cuDesc = InvalidOid;
    Oid delta = InvalidOid;
    Partition partTable = NULL;
    HeapTuple partTuple = NULL;
    HeapTuple tableTuple = NULL;
    Form_pg_partition partForm = NULL;
    ListCell* cell = NULL;

    partCacheList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_PARTITION, RelationGetRelid(relation));
    tableTuple = searchPgPartitionByParentIdCopy(PART_OBJ_TYPE_PARTED_TABLE, RelationGetRelid(relation));

    /*first form partition list and partitioned table list
     *the opened partition must already LOCKED before realy dropped.
     */
    foreach (cell, partCacheList) {
        partTuple = (HeapTuple)lfirst(cell);

        if (PointerIsValid(partTuple)) {
            partOid = HeapTupleGetOid(partTuple);
            partitionOidList = lappend_oid(partitionOidList, partOid);

            partForm = (Form_pg_partition)GETSTRUCT(partTuple);
            toastOid = partForm->reltoastrelid;
            cuDesc = partForm->relcudescrelid;
            delta = partForm->reldeltarelid;

            if (OidIsValid(toastOid)) {
                toastOidList = lappend_oid(toastOidList, toastOid);
            }

            if (OidIsValid(cuDesc)) {
                cuDescOidList = lappend_oid(cuDescOidList, cuDesc);
            }

            if (OidIsValid(delta)) {
                deltaOidList = lappend_oid(deltaOidList, delta);
            }
        }
    }

    /*
     * drop table partition's toast table
     */
    if (PointerIsValid(toastOidList)) {
        heapDropPartitionToastList(toastOidList);
        list_free(toastOidList);
    }

    if (PointerIsValid(cuDescOidList)) {
        heapDropPartitionToastList(cuDescOidList);
        list_free(cuDescOidList);
    }

    if (PointerIsValid(deltaOidList)) {
        heapDropPartitionToastList(deltaOidList);
        list_free(deltaOidList);
    }

    /*
     * drop table partition
     */
    if (PointerIsValid(partitionOidList)) {
        heapDropPartitionList(relation, partitionOidList);
        list_free(partitionOidList);
    }

    /*
     * drop partitioned table
     */
    if (PointerIsValid(tableTuple)) {
        partOid = HeapTupleGetOid(tableTuple);
        partTable = partitionOpen(relation, partOid, AccessExclusiveLock);
        heapDropPartition(relation, partTable);
        heap_freetuple(tableTuple);
    } else {
        ereport(ERROR,
            (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                errmsg("cache lookup failed for partitioned table %u in pg_partition", RelationGetRelid(relation))));
    }

    freePartList(partCacheList);
}

/*
 * @@GaussDB@@
 * Target		: data partition
 * Brief		:
 * Description	:
 * Notes		:
 */
void heapDropPartitionIndex(Relation parentIndex, Oid partIndexId)
{
    Partition partIndex = NULL;
    Relation partRel = NULL;
    ObjectAddress obj;

    partIndex = partitionOpen(parentIndex, partIndexId, AccessExclusiveLock);
    partRel = partitionGetRelation(parentIndex, partIndex);

    // delete CStore index
    //
    if (partRel->rd_rel->relcudescrelid != InvalidOid) {
        obj.classId = RelationRelationId;
        obj.objectId = partRel->rd_rel->relcudescrelid;
        obj.objectSubId = 0;
        performDeletion(&obj, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
    }

#ifndef ENABLE_MULTIPLE_NODES
    /* Deltete index on part delta. */
    if (RelationIsCUFormat(parentIndex) && partRel->rd_index != NULL && partRel->rd_index->indisunique) {
        obj.classId = RelationRelationId;
        obj.objectId = GetDeltaIdxFromCUIdx(RelationGetRelid(partRel), true);
        obj.objectSubId = 0;
        performDeletion(&obj, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
    }
#endif

    /*
     * we should process the predicate lock on the partition
     */
    TransferPredicateLocksToHeapRelation(partRel);

    RelationDropStorage(partRel);

    partitionClose(parentIndex, partIndex, NoLock);

    PartitionForgetPartition(partIndexId);

    deletePartitionTuple(partIndexId);

    releaseDummyRelation(&partRel);
}

void GetNewPartitionOidAndNewPartrelfileOid(List *subPartitionDefState, Oid *newPartitionOid, Oid *newPartrelfileOid,
                                            Relation pgPartRel, Oid newPartitionTableSpaceOid)
{
    if (subPartitionDefState != NULL) {
        if (u_sess->proc_cxt.IsBinaryUpgrade &&
            OidIsValid(u_sess->upg_cxt.binary_upgrade_next_partrel_pg_partition_oid)) {
            *newPartitionOid = u_sess->upg_cxt.binary_upgrade_next_partrel_pg_partition_oid;
            u_sess->upg_cxt.binary_upgrade_next_partrel_pg_partition_oid = InvalidOid;
            *newPartrelfileOid = u_sess->upg_cxt.binary_upgrade_next_partrel_pg_partition_rfoid;
            u_sess->upg_cxt.binary_upgrade_next_partrel_pg_partition_rfoid = InvalidOid;
        } else {
            *newPartitionOid = GetNewOid(pgPartRel);
        }
    } else {
        if (u_sess->proc_cxt.IsBinaryUpgrade && binary_upgrade_is_next_part_pg_partition_oid_valid()) {
            *newPartitionOid = binary_upgrade_get_next_part_pg_partition_oid();
            *newPartrelfileOid = binary_upgrade_get_next_part_pg_partition_rfoid();
        } else {
            *newPartitionOid = GetNewRelFileNode(newPartitionTableSpaceOid, pgPartRel,
                                                 RELPERSISTENCE_PERMANENT); /* partition's persistence only
                                                                               can be 'p'(permanent table) */
        }
    }
}

/*
 * @@GaussDB@@
 * Brief		:
 * Description	: add new partition, USED by addNewPartitionTuplesForPartition() iteration
 *                             and ADD PARTITION
 * Notes		:
 * Parameters   : pg_partition_rel:  RelationData pointer for pg_partition,
 *                                   the caller MUST get RowExclusiveLock on pg_partition
 *                partTableOid: partitioned table's oid
 *                partTablespace: partitioned table's tablespace.
 *                               If partition's tablespace is not provided, it will inherit from parent partitioned
 * table. partBufferPool:  partitioned table's BufferPoolId. All partitions of one partitioned table have same
 * BufferPoolId. newPartDef: partition description.
 *
 */
Oid heapAddRangePartition(Relation pgPartRel, Oid partTableOid, Oid partTablespace, Oid bucketOid,
    RangePartitionDefState *newPartDef, Oid ownerid, Datum reloptions, const bool *isTimestamptz,
    StorageType storage_type, LOCKMODE partLockMode, int2vector* subpartition_key, bool isSubpartition,
    PartitionExprKeyInfo *partExprKeyInfo, char partStrategy)
{
    Datum boundaryValue = (Datum)0;
    Oid newPartitionOid = InvalidOid;
    Oid newPartitionTableSpaceOid = InvalidOid;
    Relation relation;
    Partition newPartition;
    Oid newPartrelfileOid = InvalidOid;

    /*missing partition definition structure*/
    if (!PointerIsValid(newPartDef)) {
        ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("missing definition for new partition")));
    }
    /*boundary and boundary length check*/
    if (!PointerIsValid(newPartDef->boundary)) {
        ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("boundary not defined for new partition")));
    }
    if (newPartDef->boundary->length > MAX_PARTKEY_NUMS) {
        ereport(ERROR,
            (errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
                errmsg("too many partition keys, allowed is %d", MAX_PARTKEY_NUMS)));
    }

    /*new partition name check*/
    if (!PointerIsValid(newPartDef->partitionName)) {
        ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), errmsg("partition name is invalid")));
    }

    /* transform boundary value */
    boundaryValue = transformPartitionBoundary(newPartDef->boundary, isTimestamptz);

    /* get partition tablespace oid */
    if (newPartDef->tablespacename) {
        newPartitionTableSpaceOid = get_tablespace_oid(newPartDef->tablespacename, false);
    }

    if (!OidIsValid(newPartitionTableSpaceOid)) {
        newPartitionTableSpaceOid = partTablespace;
    }

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

        aclresult = pg_tablespace_aclcheck(newPartitionTableSpaceOid, GetUserId(), ACL_CREATE);
        if (aclresult != ACLCHECK_OK) {
            aclcheck_error(aclresult, ACL_KIND_TABLESPACE, get_tablespace_name(newPartitionTableSpaceOid));
        }
    }

    /* create partition */
    GetNewPartitionOidAndNewPartrelfileOid(newPartDef->subPartitionDefState, &newPartitionOid, &newPartrelfileOid,
                                           pgPartRel, newPartitionTableSpaceOid);

    if (partLockMode != NoLock) {
        LockPartitionOid(partTableOid, (uint32)newPartitionOid, partLockMode);
    }
    bool forPartitionTable = false;
    if (isSubpartition) { /* Level-2 partition of subpartition */
        forPartitionTable = false;
    } else if (newPartDef->subPartitionDefState != NULL) { /* Level-2 partition of subpartition */
        forPartitionTable = true;
    } else { /* Common partition */
        forPartitionTable = false;
    }
    newPartition = heapCreatePartition(newPartDef->partitionName, /* partition's name */
        forPartitionTable,                                                    /* false for partition */
        newPartitionTableSpaceOid,                                /* partition's tablespace*/
        newPartitionOid,                                          /* partition's oid*/
        newPartrelfileOid,
        bucketOid,
        ownerid,
        storage_type,
        false,
        reloptions);

    Assert(newPartitionOid == PartitionGetPartid(newPartition));
    PartitionTupleInfo partTupleInfo = PartitionTupleInfo();
    if (isSubpartition) {
        InitSubPartitionDef(newPartition, partTableOid, partStrategy);
        partTupleInfo.partitionno = INVALID_PARTITION_NO;
        partTupleInfo.subpartitionno = newPartDef->partitionno;
    } else {
        InitPartitionDef(newPartition, partTableOid, partStrategy);
        partTupleInfo.partitionno = newPartDef->partitionno;
        partTupleInfo.subpartitionno = -list_length(newPartDef->subPartitionDefState);
    }
    partTupleInfo.pkey = subpartition_key;
    partTupleInfo.boundaries = boundaryValue;
    partTupleInfo.reloptions = reloptions;
    if (partExprKeyInfo) {
        partTupleInfo.partexprkeyinfo.partkeyexprIsNull = partExprKeyInfo->partkeyexprIsNull;
        partTupleInfo.partexprkeyinfo.partkeyIsFunc = partExprKeyInfo->partkeyIsFunc;
        partTupleInfo.partexprkeyinfo.partExprKeyStr = partExprKeyInfo->partExprKeyStr;
    }

    /* step 3: insert into pg_partition tuple */
    addNewPartitionTuple(pgPartRel, newPartition, &partTupleInfo);

    if (isSubpartition) {
        PartitionCloseSmgr(newPartition);
        Partition part = newPartition;
        if (PartitionIsBucket(newPartition)) {
            part = newPartition->parent;
            bucketClosePartition(newPartition);
        }
        PartitionClose(part);
    } else {
        relation = relation_open(partTableOid, NoLock);
        PartitionCloseSmgr(newPartition);

        partitionClose(relation, newPartition, NoLock);
        relation_close(relation, NoLock);
    }
    return newPartitionOid;
}

#define IsDigital(_ch) (((_ch) >= '0') && ((_ch) <= '9'))

static unsigned ExtractIntervalPartNameSuffix(const char* partName)
{
    if (partName == NULL) {
        return 0;
    }

    size_t len = strlen(partName);
    size_t constPartLen = strlen(INTERVAL_PARTITION_NAME_PREFIX);
    /* 5 is length of MAX_PARTITION_NUM */
    if (len <= constPartLen || len > constPartLen + INTERVAL_PARTITION_NAME_SUFFIX_LEN) {
        return 0;
    }

    if (strncmp(partName, INTERVAL_PARTITION_NAME_PREFIX, constPartLen) != 0) {
        return 0;
    }

    for (size_t i = constPartLen; i < len; ++i) {
        if (!IsDigital(partName[i])) {
            return 0;
        }
    }

    return (unsigned)atoi(partName + constPartLen);
}

int RangeElementOidCmp(const void* a, const void* b)
{
    const RangeElement* rea = (const RangeElement*)a;
    const RangeElement* reb = (const RangeElement*)b;

    if (rea->partitionOid < reb->partitionOid) {
        return 1;
    }

    if (rea->partitionOid == reb->partitionOid) {
        return 0;
    }

    return -1;
}

char* GenIntervalPartitionName(Relation rel)
{
    unsigned suffix = 0;
    Oid existingPartOid;
    RangePartitionMap* partMap = (RangePartitionMap*)rel->partMap;
    RangeElement* eles = CopyRangeElementsWithoutBoundary(partMap->rangeElements, partMap->rangeElementsNum);
    /* sort desc by oid */
    qsort(eles, partMap->rangeElementsNum, sizeof(RangeElement), RangeElementOidCmp);
    for (int i = 0; i < partMap->rangeElementsNum; ++i) {
        /* merge or split may result in range oid bigger than interval range oid */
        if (!eles[i].isInterval) {
            continue;
        }

        char* name = PartitionOidGetName(eles[i].partitionOid);
        if ((suffix = ExtractIntervalPartNameSuffix(name)) != 0) {
            pfree(name);
            break;
        }
    }
    pfree(eles);

    char* partName = (char*)palloc0(NAMEDATALEN);
    error_t rc;
    while (true) {
        ++suffix;
        suffix = (suffix % MAX_PARTITION_NUM == 0 ? MAX_PARTITION_NUM : suffix % MAX_PARTITION_NUM);
        rc = snprintf_s(partName, NAMEDATALEN, NAMEDATALEN - 1, INTERVAL_PARTITION_NAME_PREFIX_FMT, suffix);
        securec_check_ss(rc, "\0", "\0");
        existingPartOid = PartitionNameGetPartitionOid(
            rel->rd_id, partName, PART_OBJ_TYPE_TABLE_PARTITION, AccessShareLock, true, false, NULL, NULL, NoLock);
        if (!OidIsValid(existingPartOid)) {
            return partName;
        }
    }
}

Oid GetRecentUsedTablespace(Relation rel)
{
    RangePartitionMap* partMap = (RangePartitionMap*)rel->partMap;
    Assert(partMap->rangeElementsNum >= 1);
    RangeElement* maxOidEle = &partMap->rangeElements[0];
    for (int i = 1; i < partMap->rangeElementsNum; ++i) {
        if (partMap->rangeElements[i].partitionOid > maxOidEle->partitionOid) {
            maxOidEle = &partMap->rangeElements[i];
        }
    }

    /* no interval partition yet */
    if (!maxOidEle->isInterval) {
        return InvalidOid;
    }
    return PartitionOidGetTablespace(maxOidEle->partitionOid);
}

Oid ChooseIntervalTablespace(Relation rel)
{
    const oidvector* tablespaceVec = ((RangePartitionMap*)rel->partMap)->intervalTablespace;
    Assert(tablespaceVec->dim1 >= 1);
    if (tablespaceVec->dim1 == 1) {
        return tablespaceVec->values[0];
    }

    const Oid recentUsed = GetRecentUsedTablespace(rel);
    if (!OidIsValid(recentUsed)) {
        return tablespaceVec->values[0];
    }

    int i = 0;
    for (; i < tablespaceVec->dim1; ++i) {
        if (tablespaceVec->values[i] == recentUsed) {
            break;
        }
    }

    return tablespaceVec->values[(i + 1) % tablespaceVec->dim1];
}

Oid HeapAddIntervalPartition(Relation pgPartRel, Relation rel, Oid partTableOid, Oid partrelfileOid, Oid partTablespace,
    Oid bucketOid, Datum boundaryValue, Oid ownerid, Datum reloptions, StorageType storage_type)
{
    Oid newPartitionOid = InvalidOid;
    Oid newPartitionTableSpaceOid = InvalidOid;
    Relation relation;
    Partition newPartition;

    if (((RangePartitionMap*)rel->partMap)->intervalTablespace != NULL) {
        newPartitionTableSpaceOid = ChooseIntervalTablespace(rel);
    }

    if (!OidIsValid(newPartitionTableSpaceOid)) {
        newPartitionTableSpaceOid = partTablespace;
    }

    /* Check permissions except when using database's default */
    if (OidIsValid(newPartitionTableSpaceOid) && newPartitionTableSpaceOid != u_sess->proc_cxt.MyDatabaseTableSpace) {
        AclResult aclresult = pg_tablespace_aclcheck(newPartitionTableSpaceOid, GetUserId(), ACL_CREATE);
        if (aclresult != ACLCHECK_OK) {
            aclcheck_error(aclresult, ACL_KIND_TABLESPACE, get_tablespace_name(newPartitionTableSpaceOid));
        }
    }

    /* create partition */
    if (!OidIsValid(partrelfileOid) && u_sess->proc_cxt.IsBinaryUpgrade &&
        binary_upgrade_is_next_part_pg_partition_oid_valid()) {
        newPartitionOid = binary_upgrade_get_next_part_pg_partition_oid();
        partrelfileOid = binary_upgrade_get_next_part_pg_partition_rfoid();
    } else if (!OidIsValid(partrelfileOid)) {
        newPartitionOid = GetNewRelFileNode(newPartitionTableSpaceOid,
            pgPartRel,
            RELPERSISTENCE_PERMANENT); /* partition's persistence only can be 'p'(permanent table) */
    } else {
        Assert(t_thrd.xact_cxt.inheritFileNode);
        ereport(NOTICE, (errmsg("Define inheritFileNode %u for new interval partition", partrelfileOid)));
    }

    LockPartitionOid(partTableOid, (uint32)newPartitionOid, AccessExclusiveLock);
    char* partName = GenIntervalPartitionName(rel);

    newPartition = heapCreatePartition(partName, /* partition's name */
        false,                                   /* false for partition */
        newPartitionTableSpaceOid,               /* partition's tablespace */
        newPartitionOid,                         /* partition's oid */
        partrelfileOid,
        bucketOid,
        ownerid,
        storage_type,
        false,
        reloptions);
    pfree(partName);

    Assert(newPartitionOid == PartitionGetPartid(newPartition));

    /* the partitionno on relation tuple is negative */
    int partitionno = -GetCurrentPartitionNo(RelOidGetPartitionTupleid(partTableOid));
    if (!PARTITIONNO_IS_VALID(partitionno)) {
        RelationResetPartitionno(partTableOid, RowExclusiveLock);
        partitionno = -GetCurrentPartitionNo(RelOidGetPartitionTupleid(partTableOid));
        Assert(PARTITIONNO_IS_VALID(partitionno));
    }

    InitPartitionDef(newPartition, partTableOid, PART_STRATEGY_INTERVAL);

    PartitionTupleInfo partTupleInfo = PartitionTupleInfo();
    partTupleInfo.boundaries = boundaryValue;
    partTupleInfo.reloptions = reloptions;
    partTupleInfo.partitionno = ++partitionno;
    partTupleInfo.subpartitionno = INVALID_PARTITION_NO;

    /* step 3: insert into pg_partition tuple*/
    addNewPartitionTuple(pgPartRel, newPartition, &partTupleInfo);
    /* inplace update on partitioned table, because we can't cover the wait_clean_gpi info, which is inplace updated */
    UpdateCurrentPartitionNo(RelOidGetPartitionTupleid(partTableOid), -partitionno, true);

    relation = relation_open(partTableOid, NoLock);
    PartitionCloseSmgr(newPartition);

    partitionClose(relation, newPartition, NoLock);
    relation_close(relation, NoLock);
    return newPartitionOid;
}

Oid HeapAddListPartition(Relation pgPartRel, Oid partTableOid, Oid partTablespace, Oid bucketOid,
    ListPartitionDefState* newListPartDef, Oid ownerid, Datum reloptions, const bool* isTimestamptz,
    StorageType storage_type, int2vector* subpartition_key, bool isSubpartition,
    PartitionExprKeyInfo *partExprKeyInfo)
{
    Datum boundaryValue = (Datum)0;
    Oid newListPartitionOid = InvalidOid;
    Oid newPartitionTableSpaceOid = InvalidOid;
    Oid partrelfileOid = InvalidOid;
    Relation relation;
    Partition newListPartition;
    /*missing partition definition structure*/
    if (!PointerIsValid(newListPartDef)) {
        ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("missing definition for new partition")));
    }
    /*boundary and boundary length check*/
    if (!PointerIsValid(newListPartDef->boundary)) {
        ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("boundary not defined for new partition")));
    }
    if (newListPartDef->boundary->length > PARTKEY_VALUE_MAXNUM) {
        ereport(ERROR,
            (errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
                errmsg("too many partition keys, allowed is %d", PARTKEY_VALUE_MAXNUM)));
    }

    /*new partition name check*/
    if (!PointerIsValid(newListPartDef->partitionName)) {
        ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), errmsg("partition name is invalid")));
    }

    /* transform boundary value */
    boundaryValue = transformListBoundary(newListPartDef->boundary, isTimestamptz);

    /* get partition tablespace oid */
    if (newListPartDef->tablespacename) {
        newPartitionTableSpaceOid = get_tablespace_oid(newListPartDef->tablespacename, false);
    }

    if (!OidIsValid(newPartitionTableSpaceOid)) {
        newPartitionTableSpaceOid = partTablespace;
    }

    /* Check permissions except when using database's default */
    if (OidIsValid(newPartitionTableSpaceOid) && newPartitionTableSpaceOid != u_sess->proc_cxt.MyDatabaseTableSpace) {
        AclResult aclresult = pg_tablespace_aclcheck(newPartitionTableSpaceOid, GetUserId(), ACL_CREATE);
        if (aclresult != ACLCHECK_OK) {
            aclcheck_error(aclresult, ACL_KIND_TABLESPACE, get_tablespace_name(newPartitionTableSpaceOid));
        }
    }

    /* create partition */
    GetNewPartitionOidAndNewPartrelfileOid(newListPartDef->subPartitionDefState, &newListPartitionOid, &partrelfileOid,
                                           pgPartRel, newPartitionTableSpaceOid);

    LockPartitionOid(partTableOid, (uint32)newListPartitionOid, AccessExclusiveLock);

    bool forPartitionTable = false;
    if (isSubpartition) { /* Level-2 partition of subpartition */
        forPartitionTable = false;
    } else if (newListPartDef->subPartitionDefState != NULL) { /* Level-2 partition of subpartition */
        forPartitionTable = true;
    } else { /* Common partition */
        forPartitionTable = false;
    }
    newListPartition = heapCreatePartition(newListPartDef->partitionName, forPartitionTable, newPartitionTableSpaceOid,
                                           newListPartitionOid, partrelfileOid, bucketOid, ownerid, storage_type,
                                           false, reloptions);

    Assert(newListPartitionOid == PartitionGetPartid(newListPartition));
    PartitionTupleInfo partTupleInfo = PartitionTupleInfo();
    if (isSubpartition) {
        InitSubPartitionDef(newListPartition, partTableOid, PART_STRATEGY_LIST);
        partTupleInfo.partitionno = INVALID_PARTITION_NO;
        partTupleInfo.subpartitionno = newListPartDef->partitionno;
    } else {
        InitPartitionDef(newListPartition, partTableOid, PART_STRATEGY_LIST);
        partTupleInfo.partitionno = newListPartDef->partitionno;
        partTupleInfo.subpartitionno = -list_length(newListPartDef->subPartitionDefState);
    }
    partTupleInfo.pkey = subpartition_key;
    partTupleInfo.boundaries = boundaryValue;
    partTupleInfo.reloptions = reloptions;
    if (partExprKeyInfo) {
        partTupleInfo.partexprkeyinfo.partkeyexprIsNull = partExprKeyInfo->partkeyexprIsNull;
        partTupleInfo.partexprkeyinfo.partkeyIsFunc = partExprKeyInfo->partkeyIsFunc;
        partTupleInfo.partexprkeyinfo.partExprKeyStr = partExprKeyInfo->partExprKeyStr;
    }

    /* step 3: insert into pg_partition tuple */
    addNewPartitionTuple(pgPartRel, newListPartition, &partTupleInfo);

    if (isSubpartition) {
        PartitionCloseSmgr(newListPartition);
        Partition part = newListPartition;
        if (PartitionIsBucket(newListPartition)) {
            part = newListPartition->parent;
            bucketClosePartition(newListPartition);
        }
        PartitionClose(part);
    } else {
        relation = relation_open(partTableOid, NoLock);
        PartitionCloseSmgr(newListPartition);

        partitionClose(relation, newListPartition, NoLock);
        relation_close(relation, NoLock);
    }
    return newListPartitionOid;
}

Timestamp Align2UpBoundary(Timestamp value, Interval* intervalValue, Timestamp boundary)
{
    Timestamp nearbyBoundary = boundary;
    Interval* diff = DatumGetIntervalP(timestamp_mi(value, boundary));
    /* approximate multiple */
    int multiple = (int)(INTERVAL_TO_USEC(diff) / INTERVAL_TO_USEC(intervalValue));
    pfree(diff);
    if (multiple != 0) {
        Interval* integerInterval = DatumGetIntervalP(interval_mul(intervalValue, (float8)multiple));
        nearbyBoundary = DatumGetTimestamp(timestamp_pl_interval(boundary, integerInterval));
        pfree(integerInterval);
    }
    if (nearbyBoundary <= value) {
        while (true) {
            nearbyBoundary = DatumGetTimestamp(timestamp_pl_interval(nearbyBoundary, intervalValue));
            if (nearbyBoundary > value) {
                return nearbyBoundary;
            }
        }
    } else {
        while (true) {
            Timestamp res = DatumGetTimestamp(timestamp_mi_interval(nearbyBoundary, intervalValue));
            if (res <= value) {
                return nearbyBoundary;
            }
            nearbyBoundary = res;
        }
    }
}

Datum Timestamp2Boundarys(Relation rel, Timestamp ts)
{
    Const consts;
    RangePartitionMap* partMap = (RangePartitionMap*)rel->partMap;
    Const* lastPartBoundary = partMap->rangeElements[partMap->rangeElementsNum - 1].boundary[0];
    bool isTimestamptz = lastPartBoundary->consttype == TIMESTAMPTZOID;
    Datum columnRaw;
    if (lastPartBoundary->consttype == DATEOID) {
        columnRaw = timestamp2date(ts);
    } else {
        columnRaw = TimestampGetDatum(ts);
    }
    int2vector* partKeyColumn = partMap->base.partitionKey;
    Assert(partKeyColumn->dim1 == 1);

    (void)transformDatum2Const(rel->rd_att, partKeyColumn->values[0], columnRaw, false, &consts);
    List* bondary = list_make1(&consts);
    Datum res = transformPartitionBoundary(bondary, &isTimestamptz);
    list_free(bondary);
    return res;
}

/*
 * calculate date type partititon boundary by insertedValue and lastBoundary. DestBoundary should match the equation:
 * abs(destBoundary - lastBoundary) = n * interval, (n > 0)
 */
static Datum DateAlign2UpBoundary(Datum insertedValueDatum, Interval *intervalValue, Const *lastBoundary)
{
    Timestamp insertedTs;
    Timestamp boundaryTs;
    if (lastBoundary->consttype == DATEOID) {
        insertedTs = date2timestamp(DatumGetDateADT(insertedValueDatum));
        boundaryTs = date2timestamp(DatumGetDateADT(lastBoundary->constvalue));
    } else {
        insertedTs = DatumGetTimestamp(insertedValueDatum);
        boundaryTs = DatumGetTimestamp(lastBoundary->constvalue);
    }

    Timestamp nearbyValue = boundaryTs;
    Interval *diff = DatumGetIntervalP(timestamp_mi(insertedTs, boundaryTs));
    int multiple = (int)(INTERVAL_TO_USEC(diff) / INTERVAL_TO_USEC(intervalValue));
    pfree_ext(diff);
    if (multiple != 0) {
        Interval *integerInterval = DatumGetIntervalP(interval_mul(intervalValue, (float8)multiple));
        nearbyValue = DatumGetTimestamp(timestamp_pl_interval(boundaryTs, integerInterval));
        pfree_ext(integerInterval);
    }
    if (nearbyValue <= insertedTs) {
        while (true) {
            nearbyValue = DatumGetTimestamp(timestamp_pl_interval(nearbyValue, intervalValue));
            if (nearbyValue > insertedTs) {
                break;
            }
            CHECK_FOR_INTERRUPTS();
        }
    } else {
        while (true) {
            Timestamp res = DatumGetTimestamp(timestamp_mi_interval(nearbyValue, intervalValue));
            if (res <= insertedTs) {
                break;
            }
            nearbyValue = res;
            CHECK_FOR_INTERRUPTS();
        }
    }

    if (lastBoundary->consttype == DATEOID) {
        return timestamp2date(nearbyValue);
    } else {
        return TimestampGetDatum(nearbyValue);
    }
}

static Node *GetIntervalBoundaryByTuple(Relation rel, Tuple tuple)
{
    Assert(PartitionMapIsInterval(rel->partMap));

    RangePartitionMap *partMap = (RangePartitionMap*)rel->partMap;
    int2vector *partKeyColumn = partMap->base.partitionKey;
    Assert(partKeyColumn->dim1 == 1);
    Assert(partMap->base.type == PART_TYPE_INTERVAL);
    Assert(partMap->rangeElementsNum >= 1);

    Const *lastPartBoundary = partMap->rangeElements[partMap->rangeElementsNum - 1].boundary[0];

    bool isNull = false;
    Datum constValue = tableam_tops_tuple_fast_getattr(tuple, partKeyColumn->values[0], rel->rd_att, &isNull);

    Datum destValue;
    if (lastPartBoundary->consttype == DATEOID || lastPartBoundary->consttype == TIMESTAMPOID
        || lastPartBoundary->consttype == TIMESTAMPTZOID) {
        destValue = DateAlign2UpBoundary(constValue, partMap->intervalValue, lastPartBoundary);
    } else {
        ereport(ERROR, (errmsg("Interval partition key only support type date, timestamp or timestamptz")));
    }

    return (Node *)makeConst(lastPartBoundary->consttype, lastPartBoundary->consttypmod,
        lastPartBoundary->constcollid, lastPartBoundary->constlen, destValue, isNull, lastPartBoundary->constbyval);
}

Datum GetPartBoundaryByTuple(Relation rel, Tuple tuple)
{
    RangePartitionMap* partMap = (RangePartitionMap*)rel->partMap;
    int2vector* partKeyColumn = partMap->base.partitionKey;
    Assert(partKeyColumn->dim1 == 1);
    Assert(partMap->base.type == PART_TYPE_INTERVAL);
    Assert(partMap->rangeElementsNum >= 1);

    Const* lastPartBoundary = partMap->rangeElements[partMap->rangeElementsNum - 1].boundary[0];
    Assert(lastPartBoundary->consttype == TIMESTAMPOID || lastPartBoundary->consttype == TIMESTAMPTZOID ||
           lastPartBoundary->consttype == DATEOID);

    bool isNull = false;
    Datum columnRaw;
    Timestamp value;
    Timestamp boundaryTs;

    columnRaw = tableam_tops_tuple_fast_getattr(tuple, partKeyColumn->values[0], rel->rd_att, &isNull);

    if (lastPartBoundary->consttype == DATEOID) {
        value = date2timestamp(DatumGetDateADT(columnRaw));
        boundaryTs = date2timestamp(DatumGetDateADT(lastPartBoundary->constvalue));
    } else {
        value = DatumGetTimestamp(columnRaw);
        boundaryTs = DatumGetTimestamp(lastPartBoundary->constvalue);
    }
    return Timestamp2Boundarys(rel, Align2UpBoundary(value, partMap->intervalValue, boundaryTs));
}

static char *HeapGetIntervalBoundaryCstring(Relation rel, Tuple tuple)
{
    Node *boundary = GetIntervalBoundaryByTuple(rel, tuple);
    bool *isTimestamptz = CheckPartkeyHasTimestampwithzone(rel);
    char *boundaryStr = ConstBondaryGetString((Const *)boundary, *isTimestamptz);
    StringInfo boundaries = makeStringInfo();
    appendStringInfo(boundaries, "'%s'", boundaryStr);

    char *boundariesStr = boundaries->data;
    pfree_ext(isTimestamptz);
    pfree_ext(boundary);
    pfree_ext(boundaries);

    return boundariesStr;
}

static char *HeapGetIntervalNextTablespace(Relation rel)
{
    RangePartitionMap *partMap = (RangePartitionMap *)rel->partMap;
    Oid newTbsOid = InvalidOid;
    if (partMap->intervalTablespace != NULL) {
        newTbsOid = ChooseIntervalTablespace(rel);
    }
    if (!OidIsValid(newTbsOid)) {
        return NULL;
    }

    return get_tablespace_name(newTbsOid);
}

static bool HeapAddIntervalPartitionByAutonomousSession(Relation rel, Tuple insertTuple)
{
    char *relname = RelationGetRelationName(rel);
    ereport(LOG, (errmsg("add new interval partition on heap %s by autonomous session.", relname)));
    bool result = true;

    PG_TRY();
    {
        char *spcname = get_namespace_name(RelationGetNamespace(rel));
        char *destname = GenIntervalPartitionName(rel);
        char *boundary = HeapGetIntervalBoundaryCstring(rel, insertTuple);
        char *partspcname = HeapGetIntervalNextTablespace(rel);
        u_sess->is_partition_autonomous_query = true;
        CreateAutonomousSession();

        u_sess->SPI_cxt.autonomous_session->ExecSimpleQuery("START TRANSACTION;", NULL, 0);

        ATResult res = u_sess->SPI_cxt.autonomous_session->ExecSimpleQuery("select txid_current();", NULL, 0);
        int64 currentXid = DatumGetInt64(res.ResTup);

        StringInfo sql = makeStringInfo();
        // add partition
        appendStringInfo(sql, "ALTER TABLE %s.%s ADD PARTITION %s VALUES LESS THAN (%s)", quote_identifier(spcname),
            quote_identifier(relname), quote_identifier(destname), boundary);

        // specify tablespace definition
        if (PointerIsValid(partspcname)) {
            appendStringInfo(sql, " TABLESPACE %s", quote_identifier(partspcname));
        }
        appendStringInfo(sql, ";");

        appendStringInfo(sql, "COMMIT;");

        u_sess->SPI_cxt.autonomous_session->ExecSimpleQuery(sql->data, NULL, currentXid, true);

        DestroyStringInfo(sql);
        u_sess->is_partition_autonomous_query = false;
        DestoryAutonomousSession(false);

        pfree_ext(spcname);
        pfree_ext(destname);
        pfree_ext(boundary);
        pfree_ext(partspcname);
    }
    PG_CATCH();
    {
        u_sess->is_partition_autonomous_query = false;
        DestoryAutonomousSession(true);
        result = false;
        FlushErrorState();
        ereport(LOG,
            (errmsg("add new interval partition on heap %s by autonomous session failed.", relname)));
    }
    PG_END_TRY();

    return result;
}

Oid AddNewIntervalPartition(Relation rel, void* insertTuple, int *partitionno, bool isDDL)
{
    Relation pgPartRel = NULL;
    Oid newPartOid = InvalidOid;
    Datum newRelOptions;
    Datum relOptions;
    HeapTuple tuple;
    bool isNull = false;
    List* oldRelOptions = NIL;
    Oid bucketOid;

    if (rel->partMap->isDirty) {
        CacheInvalidateRelcache(rel);
    }

    /* it will accept invalidation messages */
    LockPartitionObject(rel->rd_id, INTERVAL_PARTITION_LOCK_SDEQUENCE, PARTITION_EXCLUSIVE_LOCK);
    partitionRoutingForTuple(rel, insertTuple, u_sess->catalog_cxt.route, false, true);

    /* if the partition exists, return partition's oid. This may occur if another session do the same work. */
    if (u_sess->catalog_cxt.route->fileExist) {
        Assert(OidIsValid(u_sess->catalog_cxt.route->partitionId));
        UnlockPartitionObject(rel->rd_id, INTERVAL_PARTITION_LOCK_SDEQUENCE, PARTITION_EXCLUSIVE_LOCK);
        if (PointerIsValid(partitionno)) {
            *partitionno = GetPartitionnoFromSequence(rel->partMap, u_sess->catalog_cxt.route->partSeq);
        }
        return u_sess->catalog_cxt.route->partitionId;
    }

    /* can not add more partition, because more enough */
    if ((getNumberOfPartitions(rel) + 1) > 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)));
    }

    /* 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",
                    RelationGetRelationName(rel)),
                errhint("please reindex the unusable index first.")));
    }

    if (RELATION_SUPPORT_AUTONOMOUS_EXTEND_PARTITION
        && HeapAddIntervalPartitionByAutonomousSession(rel, insertTuple)) {
        UnlockPartitionObject(rel->rd_id, INTERVAL_PARTITION_LOCK_SDEQUENCE, PARTITION_EXCLUSIVE_LOCK);
        AcceptInvalidationMessages();
        // routing again
        partitionRoutingForTuple(rel, insertTuple, u_sess->catalog_cxt.route, false, true);
        if (u_sess->catalog_cxt.route->fileExist) {
            Assert(OidIsValid(u_sess->catalog_cxt.route->partitionId));
            newPartOid = u_sess->catalog_cxt.route->partitionId;
            if (PointerIsValid(partitionno)) {
                *partitionno = GetPartitionnoFromSequence(rel->partMap, u_sess->catalog_cxt.route->partSeq);
            }
        } else {
            ereport(ERROR, (errmsg("search failed for new added interval partition.")));
        }
        return newPartOid;
    }

    ereport(LOG,
        (errmsg("add new interval partition on heap %s by current session.", RelationGetRelationName(rel))));

    pgPartRel = relation_open(PartitionRelationId, RowExclusiveLock);

    /* add new partition entry in pg_partition */
    /* TRANSFORM into target first */
    tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(rel->rd_id));
    if (!HeapTupleIsValid(tuple)) {
        ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
            errmsg("cache lookup failed for relation %u", rel->rd_id)));
    }
    relOptions = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, &isNull);

    oldRelOptions = untransformRelOptions(relOptions);
    newRelOptions = transformRelOptions((Datum)0, oldRelOptions, NULL, NULL, false, false);
    ReleaseSysCache(tuple);

    if (oldRelOptions != NIL) {
        list_free_ext(oldRelOptions);
    }

    bucketOid = RelationGetBucketOid(rel);
    newPartOid = HeapAddIntervalPartition(pgPartRel,
        rel,
        rel->rd_id,
        InvalidOid,
        rel->rd_rel->reltablespace,
        bucketOid,
        GetPartBoundaryByTuple(rel, (HeapTuple)insertTuple),
        rel->rd_rel->relowner,
        (Datum)newRelOptions,
        RelationGetStorageType(rel));

    CommandCounterIncrement();
    addIndexForPartition(rel, newPartOid);

    addToastTableForNewPartition(rel, newPartOid);
    /* invalidate relation */
    CacheInvalidateRelcache(rel);

    /* close relation, done */
    relation_close(pgPartRel, NoLock);

    /*
     * We must bump the command counter to make the newly-created
     * table partition visible for using.
     */
    CommandCounterIncrement();

    /*
     * If add interval partition in the DDL, do not need to change the csn
     * because the scn has been changed in the DDL.
     */
    if (!isDDL) {
        UpdatePgObjectChangecsn(RelationGetRelid(rel), rel->rd_rel->relkind);
    }

    /* take ExclusiveLock to avoid PARTITION DDL COMMIT until we finish the InitPlan. Oid info will be masked here, and
     * be locked in CommitTransaction. */
#ifndef ENABLE_MULTIPLE_NODES
    AddPartitionDDLInfo(RelationGetRelid(rel));
#endif

    if (PointerIsValid(partitionno)) {
        *partitionno = GetCurrentPartitionNo(newPartOid);
        PARTITIONNO_VALID_ASSERT(*partitionno);
    }

    return newPartOid;
}

Oid HeapAddHashPartition(Relation pgPartRel, Oid partTableOid, Oid partTablespace, Oid bucketOid,
    HashPartitionDefState* newHashPartDef, Oid ownerid, Datum reloptions, const bool* isTimestamptz,
    StorageType storage_type, int2vector* subpartition_key, bool isSubpartition,
    PartitionExprKeyInfo *partExprKeyInfo)
{
    Datum boundaryValue = (Datum)0;
    Oid newHashPartitionOid = InvalidOid;
    Oid newPartitionTableSpaceOid = InvalidOid;
    Oid partrelfileOid = InvalidOid;
    Relation relation;
    Partition newHashPartition;

    /*missing partition definition structure*/
    if (!PointerIsValid(newHashPartDef)) {
        ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("missing definition for new partition")));
    }
    /*boundary and boundary length check*/
    if (!PointerIsValid(newHashPartDef->boundary)) {
        ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("boundary not defined for new partition")));
    }
    if (newHashPartDef->boundary->length != 1) {
        ereport(ERROR,
            (errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
                errmsg("too many partition keys, allowed is 1")));
    }

    /*new partition name check*/
    if (!PointerIsValid(newHashPartDef->partitionName)) {
        ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), errmsg("partition name is invalid")));
    }

    /* transform boundary value */
    bool isTime = false;
    boundaryValue = transformPartitionBoundary(newHashPartDef->boundary, &isTime);

    /* get partition tablespace oid */
    if (newHashPartDef->tablespacename) {
        newPartitionTableSpaceOid = get_tablespace_oid(newHashPartDef->tablespacename, false);
    }

    if (!OidIsValid(newPartitionTableSpaceOid)) {
        newPartitionTableSpaceOid = partTablespace;
    }

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

        aclresult = pg_tablespace_aclcheck(newPartitionTableSpaceOid, GetUserId(), ACL_CREATE);
        if (aclresult != ACLCHECK_OK) {
            aclcheck_error(aclresult, ACL_KIND_TABLESPACE, get_tablespace_name(newPartitionTableSpaceOid));
        }
    }

    /* create partition */
    GetNewPartitionOidAndNewPartrelfileOid(newHashPartDef->subPartitionDefState, &newHashPartitionOid, &partrelfileOid,
                                           pgPartRel, newPartitionTableSpaceOid);

    LockPartitionOid(partTableOid, (uint32)newHashPartitionOid, AccessExclusiveLock);

    bool forPartitionTable = false;
    if (isSubpartition) { /* Level-2 partition of subpartition */
        forPartitionTable = false;
    } else if (newHashPartDef->subPartitionDefState != NULL) { /* Level-2 partition of subpartition */
        forPartitionTable = true;
    } else { /* Common partition */
        forPartitionTable = false;
    }
    newHashPartition = heapCreatePartition(newHashPartDef->partitionName,
                                           forPartitionTable,
                                           newPartitionTableSpaceOid,
                                           newHashPartitionOid,
                                           partrelfileOid,
                                           bucketOid,
                                           ownerid,
                                           storage_type,
                                           false,
                                           reloptions);

    Assert(newHashPartitionOid == PartitionGetPartid(newHashPartition));
    PartitionTupleInfo partTupleInfo = PartitionTupleInfo();
    if (isSubpartition) {
        InitSubPartitionDef(newHashPartition, partTableOid, PART_STRATEGY_HASH);
        partTupleInfo.partitionno = INVALID_PARTITION_NO;
        partTupleInfo.subpartitionno = newHashPartDef->partitionno;
    } else {
        InitPartitionDef(newHashPartition, partTableOid, PART_STRATEGY_HASH);
        partTupleInfo.partitionno = newHashPartDef->partitionno;
        partTupleInfo.subpartitionno = -list_length(newHashPartDef->subPartitionDefState);
    }
    partTupleInfo.pkey = subpartition_key;
    partTupleInfo.boundaries = boundaryValue;
    partTupleInfo.reloptions = reloptions;
    if (partExprKeyInfo) {
        partTupleInfo.partexprkeyinfo.partkeyexprIsNull = partExprKeyInfo->partkeyexprIsNull;
        partTupleInfo.partexprkeyinfo.partkeyIsFunc = partExprKeyInfo->partkeyIsFunc;
        partTupleInfo.partexprkeyinfo.partExprKeyStr = partExprKeyInfo->partExprKeyStr;
    }

    /* step 3: insert into pg_partition tuple */
    addNewPartitionTuple(pgPartRel, newHashPartition, &partTupleInfo);

    if (isSubpartition) {
        PartitionCloseSmgr(newHashPartition);
        Partition part = newHashPartition;
        if (PartitionIsBucket(newHashPartition)) {
            part = newHashPartition->parent;
            bucketClosePartition(newHashPartition);
        }
        PartitionClose(part);
    } else {
        relation = relation_open(partTableOid, NoLock);
        PartitionCloseSmgr(newHashPartition);

        partitionClose(relation, newHashPartition, NoLock);
        relation_close(relation, NoLock);
    }
    return newHashPartitionOid;
}

static void addNewPartitionTupleForValuePartitionedTable(Relation pg_partition_rel, const char* relname,
    const Oid reloid, const Oid reltablespaceid, const TupleDesc reltupledesc, const PartitionState* partTableState,
    Datum reloptions)
{
    Datum values[Natts_pg_partition];
    bool nulls[Natts_pg_partition];
    HeapTuple tup;
    int2vector* partition_key_attr_no = buildPartitionKey(partTableState->partitionKey, reltupledesc);
    errno_t errorno = EOK;

    errorno = memset_s(values, sizeof(values), 0, sizeof(values));
    securec_check_c(errorno, "\0", "\0");

    errorno = memset_s(nulls, sizeof(nulls), 0, sizeof(nulls));
    securec_check_c(errorno, "\0", "\0");

    values[Anum_pg_partition_relname - 1] = NameGetDatum(relname);
    values[Anum_pg_partition_parttype - 1] = CharGetDatum(PART_OBJ_TYPE_PARTED_TABLE);
    values[Anum_pg_partition_parentid - 1] = ObjectIdGetDatum(reloid);
    values[Anum_pg_partition_rangenum - 1] = UInt32GetDatum(0);
    values[Anum_pg_partition_intervalnum - 1] = UInt32GetDatum(0);
    values[Anum_pg_partition_partstrategy - 1] = CharGetDatum(partTableState->partitionStrategy);
    values[Anum_pg_partition_relfilenode - 1] = ObjectIdGetDatum(InvalidOid);
    values[Anum_pg_partition_reltablespace - 1] = ObjectIdGetDatum(InvalidOid);
    values[Anum_pg_partition_relpages - 1] = Float8GetDatum(0);
    values[Anum_pg_partition_reltuples - 1] = Float8GetDatum(0);
    values[Anum_pg_partition_relallvisible - 1] = UInt32GetDatum(0);
    values[Anum_pg_partition_reltoastrelid - 1] = ObjectIdGetDatum(InvalidOid);
    values[Anum_pg_partition_reltoastidxid - 1] = ObjectIdGetDatum(InvalidOid);
    values[Anum_pg_partition_indextblid - 1] = ObjectIdGetDatum(InvalidOid);
    values[Anum_pg_partition_deltarelid - 1] = ObjectIdGetDatum(InvalidOid);
    values[Anum_pg_partition_reldeltaidx - 1] = ObjectIdGetDatum(InvalidOid);
    values[Anum_pg_partition_relcudescrelid - 1] = ObjectIdGetDatum(InvalidOid);
    values[Anum_pg_partition_relcudescidx - 1] = ObjectIdGetDatum(InvalidOid);
    values[Anum_pg_partition_indisusable - 1] = BoolGetDatum(true);
    values[Anum_pg_partition_relfrozenxid - 1] = ShortTransactionIdGetDatum(InvalidTransactionId);

    /*partition key*/
    values[Anum_pg_partition_partkey - 1] = PointerGetDatum(partition_key_attr_no);

    nulls[Anum_pg_partition_intablespace - 1] = true;
    nulls[Anum_pg_partition_intspnum - 1] = true;

    /*interval*/
    nulls[Anum_pg_partition_interval - 1] = true;

    /*maxvalue*/
    nulls[Anum_pg_partition_boundaries - 1] = true;

    /*transit point*/
    nulls[Anum_pg_partition_transit - 1] = true;

    /*reloptions*/
    if (reloptions != (Datum)0) {
        values[Anum_pg_partition_reloptions - 1] = reloptions;
        nulls[Anum_pg_partition_reloptions - 1] = false;
    } else {
        nulls[Anum_pg_partition_reloptions - 1] = true;
    }
    values[Anum_pg_partition_relfrozenxid64 - 1] = TransactionIdGetDatum(InvalidTransactionId);
#ifndef ENABLE_MULTIPLE_NODES
    values[Anum_pg_partition_relminmxid - 1] = TransactionIdGetDatum(InvalidMultiXactId);
#endif
    /*form a tuple using values and null array, and insert it*/
    tup = heap_form_tuple(RelationGetDescr(pg_partition_rel), values, nulls);
    HeapTupleSetOid(tup, InvalidOid);
    (void)simple_heap_insert(pg_partition_rel, tup);
    CatalogUpdateIndexes(pg_partition_rel, tup);

    heap_freetuple(tup);

    if (partition_key_attr_no != NULL) {
        pfree(partition_key_attr_no);
    }
}

/*
 * @@GaussDB@@
 * Brief		:
 * Description	: Add new partition tuple into pg_partition for partitioned-table
 * Notes		:
 * Parameters : pg_partition_rel:  RelationData pointer for pg_partition,
 *                                   the caller MUST get RowExclusiveLock on pg_partition
 *                relname:       partitioned table's name
 *                reloid :       partitioned table's oid in pg_class
 *                reltupledesc : TupleDesc for partitioned tuple, it is used to get partition key attribute number.
 *                partTableState: partition definition of partitioned table.
 */
static void addNewPartitionTupleForTable(Relation pg_partition_rel, const char* relname, const Oid reloid,
    const Oid reltablespaceid, const TupleDesc reltupledesc, const PartitionState* partTableState, Oid ownerid,
    Datum reloptions, PartitionExprKeyInfo *partExprKeyInfo)
{
    Datum interval = (Datum)0;
    Datum transition_point = (Datum)0;
    Oid new_partition_oid = InvalidOid;
    int2vector* partition_key_attr_no = NULL;
    oidvector* interval_talespace = NULL;
    Relation relation = NULL;
    Partition new_partition = NULL;
    Datum newOptions;

    Oid new_partition_rfoid = InvalidOid;

    Assert(pg_partition_rel);
    if (partTableState->partitionStrategy != 'l' && partTableState->partitionStrategy != 'h') {
        RangePartitionDefState* lastPartition = NULL;
        lastPartition = (RangePartitionDefState*)lfirst(partTableState->partitionList->tail);

        if (lastPartition->boundary->length > MAX_PARTKEY_NUMS) {
            ereport(ERROR,
                (errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
                    errmsg("number of partition key columns MUST less or equal than %d", MAX_PARTKEY_NUMS)));
        }
    }

    partition_key_attr_no = buildPartitionKey(partTableState->partitionKey, reltupledesc);
    if (partTableState->intervalPartDef != NULL) {
        interval_talespace = BuildIntervalTablespace(partTableState->intervalPartDef);
        interval = BuildInterval(partTableState->intervalPartDef->partInterval);
    }

    /*step1: create partition relation, initialize and set tuple properties*/
    if (u_sess->proc_cxt.IsBinaryUpgrade && OidIsValid(u_sess->upg_cxt.binary_upgrade_next_partrel_pg_partition_oid)) {
        new_partition_oid = u_sess->upg_cxt.binary_upgrade_next_partrel_pg_partition_oid;
        u_sess->upg_cxt.binary_upgrade_next_partrel_pg_partition_oid = InvalidOid;
        new_partition_rfoid = u_sess->upg_cxt.binary_upgrade_next_partrel_pg_partition_rfoid;
        u_sess->upg_cxt.binary_upgrade_next_partrel_pg_partition_rfoid = InvalidOid;
    } else {
        new_partition_oid = GetNewOid(pg_partition_rel);
    }

    new_partition = heapCreatePartition(relname, /* partitioned table's name*/
        true,                                    /* this entry is for partitioned table */
        reltablespaceid,                         /*tablespace's oid for partitioned table */
        new_partition_oid,                       /* partitioned table's oid in pg_partition*/
        new_partition_rfoid,
        InvalidOid,
        ownerid,
        HEAP_DISK,
        false,
        reloptions);

    Assert(new_partition_oid == PartitionGetPartid(new_partition));
    new_partition->pd_part->parttype = PART_OBJ_TYPE_PARTED_TABLE;
    new_partition->pd_part->parentid = reloid;
    new_partition->pd_part->rangenum = 0;
    new_partition->pd_part->intervalnum = 0;
    new_partition->pd_part->partstrategy = partTableState->partitionStrategy;
    new_partition->pd_part->reltoastrelid = InvalidOid;
    new_partition->pd_part->reltoastidxid = InvalidOid;
    new_partition->pd_part->indextblid = InvalidOid;
    new_partition->pd_part->reldeltarelid = InvalidOid;
    new_partition->pd_part->reldeltaidx = InvalidOid;
    new_partition->pd_part->relcudescrelid = InvalidOid;
    new_partition->pd_part->relcudescidx = InvalidOid;
    new_partition->pd_part->indisusable = true;

    /* Update reloptions with wait_clean_gpi=n */
    newOptions = SetWaitCleanGpiRelOptions(reloptions, false);

    PartitionTupleInfo partTupleInfo = PartitionTupleInfo();
    partTupleInfo.pkey = partition_key_attr_no; /* number array for partition key column of partitioned table */
    partTupleInfo.intablespace = interval_talespace;
    partTupleInfo.interval = interval;
    partTupleInfo.boundaries = (Datum)0;
    partTupleInfo.transitionPoint = transition_point;
    partTupleInfo.reloptions = newOptions;
    partTupleInfo.partitionno = -list_length(partTableState->partitionList);
    partTupleInfo.subpartitionno = INVALID_PARTITION_NO;
    if (partExprKeyInfo) {
        partTupleInfo.partexprkeyinfo.partkeyexprIsNull = partExprKeyInfo->partkeyexprIsNull;
        partTupleInfo.partexprkeyinfo.partkeyIsFunc = partExprKeyInfo->partkeyIsFunc;
        partTupleInfo.partexprkeyinfo.partExprKeyStr = partExprKeyInfo->partExprKeyStr;
    }

    /*step 2: insert into pg_partition tuple*/
    addNewPartitionTuple(pg_partition_rel, new_partition, &partTupleInfo);
    relation = relation_open(reloid, NoLock);
    partitionClose(relation, new_partition, NoLock);
    relation_close(relation, NoLock);

    pfree_ext(partition_key_attr_no);
    pfree_ext(interval_talespace);
    if (interval != 0) {
        pfree(DatumGetPointer(interval));
    }
}

bool IsExistDefaultSubpartitionName(List *partitionNameList, char *defaultPartitionName)
{
    ListCell *cell = NULL;
    foreach (cell, partitionNameList) {
        char *partitionName = (char *)lfirst(cell);
        if (!strcmp(partitionName, defaultPartitionName)) {
            return true;
        }
    }
    return false;
}

static void MakeDefaultSubpartitionName(PartitionState *partitionState, char **subPartitionName,
    const char *partitionName)
{
    int numLen = 0;
    int subPartitionNameLen = 0;
    List *partitionNameList = NIL;
    if (PointerIsValid(partitionState->partitionList)) {
        partitionNameList = GetPartitionNameList(partitionState->partitionList);
    } else if (PointerIsValid(partitionState->partitionNameList)) {
        partitionNameList = partitionState->partitionNameList;
    }


    for (int i = 1; i < INT32_MAX_VALUE; i++) {
        numLen = (int)log10(i) + 1;
        subPartitionNameLen = strlen(partitionName) + strlen("_subpartdefault") + numLen + 1;
        if (subPartitionNameLen > NAMEDATALEN) {
            subPartitionNameLen = strlen("sys_subpartdefault") + numLen + 1;
            *subPartitionName = (char *)palloc0(subPartitionNameLen);
            errno_t rc = snprintf_s(*subPartitionName, subPartitionNameLen, subPartitionNameLen - 1,
                                    "sys_subpartdefault%d", i);
            securec_check_ss(rc, "\0", "\0");
        } else {
            *subPartitionName = (char *)palloc0(subPartitionNameLen);
            errno_t rc = snprintf_s(*subPartitionName, subPartitionNameLen, subPartitionNameLen - 1,
                                    "%s_subpartdefault%d", partitionName, i);
            securec_check_ss(rc, "\0", "\0");
        }

        bool isExist = IsExistDefaultSubpartitionName(partitionNameList, *subPartitionName);
        if (isExist) {
            pfree_ext(*subPartitionName);
        } else {
            break;
        }
    }

    if (PointerIsValid(partitionState->partitionList)) {
        list_free_ext(partitionNameList);
    }
}

static ListPartitionDefState *MakeListDefaultSubpartition(PartitionState *partitionState, char *partitionName,
                                                   char *tablespacename)
{
    ListPartitionDefState *subPartitionDefState = makeNode(ListPartitionDefState);
    MakeDefaultSubpartitionName(partitionState, &subPartitionDefState->partitionName, partitionName);

    Const *boundaryDefault = makeNode(Const);
    boundaryDefault->ismaxvalue = true;
    boundaryDefault->location = -1;
    subPartitionDefState->boundary = list_make1(boundaryDefault);
    subPartitionDefState->tablespacename = pstrdup(tablespacename);
    return subPartitionDefState;
}

static HashPartitionDefState *MakeHashDefaultSubpartition(PartitionState *partitionState, char *partitionName,
                                                   char *tablespacename)
{
    HashPartitionDefState *subPartitionDefState = makeNode(HashPartitionDefState);
    MakeDefaultSubpartitionName(partitionState, &subPartitionDefState->partitionName, partitionName);

    Const *boundaryDefault = makeConst(INT4OID, -1, InvalidOid, sizeof(int32), Int32GetDatum(0), false, true);
    subPartitionDefState->boundary = list_make1(boundaryDefault);
    subPartitionDefState->tablespacename = pstrdup(tablespacename);
    return subPartitionDefState;
}

static RangePartitionDefState *MakeRangeDefaultSubpartition(PartitionState *partitionState, char *partitionName,
                                                     char *tablespacename)
{
    RangePartitionDefState *subPartitionDefState = makeNode(RangePartitionDefState);
    MakeDefaultSubpartitionName(partitionState, &subPartitionDefState->partitionName, partitionName);

    Const *boundaryDefault = makeNode(Const);
    boundaryDefault->ismaxvalue = true;
    subPartitionDefState->boundary = list_make1(boundaryDefault);
    subPartitionDefState->tablespacename = pstrdup(tablespacename);
    return subPartitionDefState;
}

Node *MakeDefaultSubpartition(PartitionState *partitionState, PartitionDefState *partitionDefState)
{
    PartitionState *subPartitionState = partitionState->subPartitionState;
    char subPartitionStrategy = subPartitionState->partitionStrategy;
    char *partitionName = partitionDefState->partitionName;
    char *tablespacename = partitionDefState->tablespacename;

    if (subPartitionStrategy == PART_STRATEGY_LIST) {
        ListPartitionDefState *subPartitionDefState =
            MakeListDefaultSubpartition(partitionState, partitionName, tablespacename);
        subPartitionDefState->partitionno = 1;
        return (Node *)subPartitionDefState;
    } else if (subPartitionStrategy == PART_STRATEGY_HASH) {
        HashPartitionDefState *subPartitionDefState =
            MakeHashDefaultSubpartition(partitionState, partitionName, tablespacename);
        subPartitionDefState->partitionno = 1;
        return (Node *)subPartitionDefState;
    } else {
        RangePartitionDefState *subPartitionDefState =
            MakeRangeDefaultSubpartition(partitionState, partitionName, tablespacename);
        subPartitionDefState->partitionno = 1;
        return (Node *)subPartitionDefState;
    }
}

List *addNewSubPartitionTuplesForPartition(Relation pgPartRel, Oid partTableOid, Oid partTablespace,
                                                 Oid bucketOid, Oid ownerid, Datum reloptions,
                                                 const bool *isTimestamptz, StorageType storage_type,
                                                 PartitionState *partitionState, Node *partitionDefState,
                                                 LOCKMODE partLockMode)
{
    List *subpartOidList = NIL;
    if (partitionState->subPartitionState == NULL) {
        return NIL;
    }

    PartitionState *subPartitionState = partitionState->subPartitionState;
    ListCell *lc = NULL;
    Oid subpartOid = InvalidOid;
    char subPartitionStrategy = subPartitionState->partitionStrategy;
    List *subPartitionDefStateList = ((PartitionDefState *)partitionDefState)->subPartitionDefState;

    foreach (lc, subPartitionDefStateList) {
        if (subPartitionStrategy == PART_STRATEGY_LIST) {
            ListPartitionDefState *subPartitionDefState = (ListPartitionDefState *)lfirst(lc);
            subpartOid = HeapAddListPartition(pgPartRel, partTableOid, partTablespace, bucketOid,
                subPartitionDefState, ownerid, reloptions, isTimestamptz, storage_type, NULL, true);
        } else if (subPartitionStrategy == PART_STRATEGY_HASH) {
            HashPartitionDefState *subPartitionDefState = (HashPartitionDefState *)lfirst(lc);
            subpartOid = HeapAddHashPartition(pgPartRel, partTableOid, partTablespace, bucketOid,
                subPartitionDefState, ownerid, reloptions, isTimestamptz, storage_type, NULL, true);
        } else {
            RangePartitionDefState *subPartitionDefState = (RangePartitionDefState *)lfirst(lc);
            subpartOid = heapAddRangePartition(pgPartRel, partTableOid, partTablespace, bucketOid,
                subPartitionDefState, ownerid, reloptions, isTimestamptz, storage_type, partLockMode, NULL, true);
        }
        if (OidIsValid(subpartOid)) {
            subpartOidList = lappend_oid(subpartOidList, subpartOid);
        }
    }

    return subpartOidList;
}

/*
 * check whether the partion key has timestampwithzone type.
 */
static void IsPartitionKeyContainTimestampwithzoneType(const PartitionState *partTableState, const TupleDesc tupledesc,
                                                       bool *isTimestamptz, int partKeyNum)
{
    ListCell *partKeyCell = NULL;
    ColumnRef *col = NULL;
    char *columName = NULL;
    int partKeyIdx = 0;
    int attnum = tupledesc->natts;
    FormData_pg_attribute *attrs = tupledesc->attrs;

    foreach (partKeyCell, partTableState->partitionKey) {
        col = (ColumnRef *)lfirst(partKeyCell);
        columName = ((Value *)linitial(col->fields))->val.str;

        isTimestamptz[partKeyIdx] = false;
        for (int i = 0; i < attnum; i++) {
            if (TIMESTAMPTZOID == attrs[i].atttypid && 0 == strcmp(columName, attrs[i].attname.data)) {
                isTimestamptz[partKeyIdx] = true;
                break;
            }
        }
        partKeyIdx++;
    }

    Assert(partKeyIdx == partKeyNum);
}

/* if partition's tablespace is not provided, it will inherit from parent partitioned
 * so for a subpartition, the tablespace itself is used, if not exist, inherit from the partition, and if
 * still not exist, inherit from the table */
Oid GetPartTablespaceOidForSubpartition(Oid reltablespace, const char* partTablespacename)
{
    Oid partTablespaceOid = InvalidOid;
    /* get partition tablespace oid */
    if (PointerIsValid(partTablespacename)) {
        partTablespaceOid = get_tablespace_oid(partTablespacename, false);
    }
    if (!OidIsValid(partTablespaceOid)) {
        partTablespaceOid = reltablespace;
    }
    return partTablespaceOid;
}

/*
 * @@GaussDB@@
 * Brief		:
 * Description	: add new partition tuples of partitioned table into pg_partition
 * Notes		:
 * Parameters   : pg_partition_rel:  RelationData pointer for pg_partition,
 *                                   the caller MUST get RowExclusiveLock on pg_partition
 *                relid: partitioned table's oid
 *                reltablespace: partitioned table's tablespace.
 *                               If partition's tablespace is not provided, it will inherit from parent partitioned
 * table. BufferPoolId:  partitioned table's BufferPoolId. All partitions of one partitioned table have same
 * BufferPoolId. partTableState: partition schema of partitioned table which is being created right now.
 *
 */
static void addNewPartitionTuplesForPartition(Relation pg_partition_rel, Oid relid,
    Oid reltablespace, Oid bucketOid, PartitionState* partTableState, Oid ownerid, Datum reloptions,
    const TupleDesc tupledesc, char strategy, StorageType storage_type, LOCKMODE partLockMode, PartitionExprKeyInfo *partExprKeyInfo)
{
    int partKeyNum = list_length(partTableState->partitionKey);
    bool isTimestamptzForPartKey[partKeyNum];
    memset_s(isTimestamptzForPartKey, sizeof(isTimestamptzForPartKey), 0, sizeof(isTimestamptzForPartKey));
    IsPartitionKeyContainTimestampwithzoneType(partTableState, tupledesc, isTimestamptzForPartKey, partKeyNum);

    bool *isTimestamptzForSubPartKey = NULL;
    if (partTableState->subPartitionState != NULL) {
        int subPartKeyNum = list_length(partTableState->subPartitionState->partitionKey);
        isTimestamptzForSubPartKey = (bool*)palloc0(sizeof(bool) * subPartKeyNum);
        IsPartitionKeyContainTimestampwithzoneType(partTableState->subPartitionState, tupledesc,
                                                   isTimestamptzForSubPartKey, subPartKeyNum);
    }

    ListCell* cell = NULL;

    Assert(pg_partition_rel);

    List *subPartitionKey = NIL;
    int2vector* subpartition_key_attr_no = NULL;
    if (partTableState->subPartitionState != NULL) {
        subPartitionKey = partTableState->subPartitionState->partitionKey;
        subpartition_key_attr_no = buildPartitionKey(subPartitionKey, tupledesc);
    }

    /*insert partition entries into pg_partition*/
    foreach (cell, partTableState->partitionList) {
        if (strategy == PART_STRATEGY_LIST) {
            ListPartitionDefState* partitionDefState = (ListPartitionDefState*)lfirst(cell);
            if (partTableState->subPartitionState != NULL && partitionDefState->subPartitionDefState == NULL) {
                Node *subPartitionDefState =
                    MakeDefaultSubpartition(partTableState, (PartitionDefState *)partitionDefState);
                partitionDefState->subPartitionDefState =
                    lappend(partitionDefState->subPartitionDefState, subPartitionDefState);
            }
            Oid partitionOid = HeapAddListPartition(pg_partition_rel,
                relid,
                reltablespace,
                bucketOid,
                partitionDefState,
                ownerid,
                reloptions,
                isTimestamptzForPartKey,
                storage_type,
                subpartition_key_attr_no,
                false,
                partExprKeyInfo);

            Oid partTablespaceOid =
                GetPartTablespaceOidForSubpartition(reltablespace, partitionDefState->tablespacename);
            List *subpartitionOidList = addNewSubPartitionTuplesForPartition(pg_partition_rel, partitionOid,
                partTablespaceOid, bucketOid, ownerid, reloptions, isTimestamptzForSubPartKey, storage_type,
                partTableState, (Node *)partitionDefState, partLockMode);
            if (subpartitionOidList != NIL) {
                list_free_ext(subpartitionOidList);
            }
        } else if (strategy == PART_STRATEGY_HASH) {
            HashPartitionDefState* partitionDefState = (HashPartitionDefState*)lfirst(cell);
            if (partTableState->subPartitionState != NULL && partitionDefState->subPartitionDefState == NULL) {
                Node *subPartitionDefState =
                    MakeDefaultSubpartition(partTableState, (PartitionDefState *)partitionDefState);
                partitionDefState->subPartitionDefState =
                    lappend(partitionDefState->subPartitionDefState, subPartitionDefState);
            }
            Oid partitionOid = HeapAddHashPartition(pg_partition_rel,
                relid,
                reltablespace,
                bucketOid,
                partitionDefState,
                ownerid,
                reloptions,
                isTimestamptzForPartKey,
                storage_type,
                subpartition_key_attr_no,
                false,
                partExprKeyInfo);

            Oid partTablespaceOid =
                GetPartTablespaceOidForSubpartition(reltablespace, partitionDefState->tablespacename);
            List *subpartitionOidList = addNewSubPartitionTuplesForPartition(pg_partition_rel, partitionOid,
                partTablespaceOid, bucketOid, ownerid, reloptions, isTimestamptzForSubPartKey, storage_type,
                partTableState, (Node *)partitionDefState, partLockMode);
            if (subpartitionOidList != NIL) {
                list_free_ext(subpartitionOidList);
            }
        } else {
            RangePartitionDefState* partitionDefState = (RangePartitionDefState*)lfirst(cell);
            if (partTableState->subPartitionState != NULL && partitionDefState->subPartitionDefState == NULL) {
                Node *subPartitionDefState =
                    MakeDefaultSubpartition(partTableState, (PartitionDefState *)partitionDefState);;
                partitionDefState->subPartitionDefState =
                    lappend(partitionDefState->subPartitionDefState, subPartitionDefState);
            }
            Oid partitionOid = heapAddRangePartition(pg_partition_rel,
                relid,
                reltablespace,
                bucketOid,
                (RangePartitionDefState*)lfirst(cell),
                ownerid,
                reloptions,
                isTimestamptzForPartKey,
                storage_type,
                partLockMode,
                subpartition_key_attr_no,
                false,
                partExprKeyInfo);

            Oid partTablespaceOid =
                GetPartTablespaceOidForSubpartition(reltablespace, partitionDefState->tablespacename);
            List *subpartitionOidList = addNewSubPartitionTuplesForPartition(pg_partition_rel, partitionOid,
                partTablespaceOid, bucketOid, ownerid, reloptions, isTimestamptzForSubPartKey, storage_type,
                partTableState, (Node *)partitionDefState, partLockMode);
            if (subpartitionOidList != NIL) {
                list_free_ext(subpartitionOidList);
            }
        }
    }
    if (isTimestamptzForSubPartKey != NULL) {
        pfree_ext(isTimestamptzForSubPartKey);
    }
}

void heap_truncate_one_part(Relation rel, Oid partOid)
{
    SubTransactionId mySubid;
    Partition p = partitionOpen(rel, partOid, AccessExclusiveLock);
    Oid toastOid = p->pd_part->reltoastrelid;
    List* partIndexlist = NULL;
    Relation parentIndex = NULL;
    mySubid = GetCurrentSubTransactionId();
    MultiXactId multiXid = GetOldestMultiXactId();;

    partIndexlist = searchPartitionIndexesByblid(partOid);

    /*
     * Truncate a partition need a new relfilenode.
     * Otherwise, recovery may fail in the following scenarios:
     *
     *  start transaction;
     *  create table p1 *** partition by ***;
     *  copy p1 from ***;
     *	alter table p1 truncate partition ***;
     *	commit;
     *	copy p1 from ***;
     *	update p1 set ***;
     */

    CheckTableForSerializableConflictIn(rel);

    PartitionSetNewRelfilenode(rel, p, u_sess->utils_cxt.RecentXmin,
                               RelationIsColStore(rel) ? InvalidMultiXactId : multiXid);

    /* truncate the toast table */
    if (toastOid != InvalidOid) {
        Relation toastRel = heap_open(toastOid, AccessExclusiveLock);
        RelationSetNewRelfilenode(toastRel, u_sess->utils_cxt.RecentXmin, multiXid);
        heap_close(toastRel, NoLock);
    }

    /* rebuild part index */
    if (PointerIsValid(partIndexlist)) {
        ListCell* lc = NULL;
        Oid partIndId = InvalidOid;
        Oid parentIndId = InvalidOid;
        HeapTuple partIndexTuple = NULL;
        Partition indexPart = NULL;

        foreach (lc, partIndexlist) {
            partIndexTuple = (HeapTuple)lfirst(lc);
            parentIndId = (((Form_pg_partition)GETSTRUCT(partIndexTuple)))->parentid;
            partIndId = HeapTupleGetOid(partIndexTuple);
            parentIndex = index_open(parentIndId, AccessShareLock);
            indexPart = partitionOpen(parentIndex, partIndId, AccessExclusiveLock);
            reindex_partIndex(rel, p, parentIndex, indexPart);
            partitionClose(parentIndex, indexPart, NoLock);
            index_close(parentIndex, NoLock);
        }
    }

    /* rebuild the toast index */
    if (toastOid != InvalidOid) {
        (void)ReindexRelation(toastOid, 0, REINDEX_BTREE_INDEX, NULL);
    }

    freePartList(partIndexlist);
    partitionClose(rel, p, NoLock);
}

/*
 * Compute the hash value of a hash-bucket tuple
 * */
int2 computeTupleBucketId(Relation rel, HeapTuple tuple)
{
    TupleDesc tup_desc = rel->rd_att;
    int2vector* col_ids = rel->rd_bucketkey->bucketKey; /* vector of column id */
    MultiHashKey mkeys;

    Assert(REALTION_BUCKETKEY_VALID(rel));
    mkeys.keyNum = (uint32)col_ids->dim1;
    mkeys.keyTypes = rel->rd_bucketkey->bucketKeyType;
    mkeys.locatorType = LOCATOR_TYPE_HASH;

    /* set the mutikey values */
    mkeys.keyValues = (Datum*)palloc(mkeys.keyNum * sizeof(Datum));
    mkeys.isNulls = (bool*)palloc(mkeys.keyNum * sizeof(bool));

    for (int i = 0; i < col_ids->dim1; i++) {
        mkeys.isNulls[i] = false;
        mkeys.keyValues[i] =
            fastgetattr((tuple), col_ids->values[i], tup_desc, &mkeys.isNulls[i]);  // get the column value
    }

    uint32 hashValue = hash_multikey(&mkeys);

    pfree(mkeys.keyValues);
    pfree(mkeys.isNulls);

    return GetBucketID(hashValue, rel->rd_bucketmapsize);
}

int lookupHBucketid(oidvector *buckets, int low, int2 bktId)
{
    int high = buckets->dim1 - 1;
    Assert(low <= high);
    /* binary search */
    while (low <= high) {
        int  mid = (high + low) / 2;
        int2 bid = buckets->values[mid];
        if (bktId == bid) {
            return mid;
        } else if (bktId > bid) {
            low = mid + 1;
        } else {
            high = mid - 1;
        }
    }
    return -1;
}

extern PartKeyExprResult
ComputePartKeyExprTuple(Relation rel, EState *estate, TupleTableSlot *slot, Relation partRel, char *partExprKeyStr);

Oid getPartitionIdFromTuple(Relation rel, void *tuple, EState* estate, TupleTableSlot* slot, int *partitionno, bool isDDL, bool canIgnore)
{
    char* partExprKeyStr = NULL;
    Oid targetOid = InvalidOid;
    bool partExprKeyIsNull = PartExprKeyIsNull(rel, &partExprKeyStr);
    if (partExprKeyIsNull) {
        targetOid = heapTupleGetPartitionOid(rel, tuple, partitionno, isDDL, canIgnore);
    } else {
        PartKeyExprResult partKeyExprResult = ComputePartKeyExprTuple(rel, estate, slot, NULL, partExprKeyStr);
        targetOid = heapTupleGetPartitionOid(rel, (void*)(&partKeyExprResult), partitionno, isDDL, canIgnore, false);
    }
    pfree_ext(partExprKeyStr);
    return targetOid;
}

static bool binary_upgrade_is_next_part_pg_partition_oid_valid()
{
    if (NULL == u_sess->upg_cxt.binary_upgrade_next_part_pg_partition_oid) {
        return false;
    }

    if (u_sess->upg_cxt.binary_upgrade_cur_part_pg_partition_oid >=
        u_sess->upg_cxt.binary_upgrade_max_part_pg_partition_oid) {
        return false;
    }

    if (!OidIsValid(
            u_sess->upg_cxt
                .binary_upgrade_next_part_pg_partition_oid[u_sess->upg_cxt.binary_upgrade_cur_part_pg_partition_oid])) {
        return false;
    }

    return true;
}

static Oid binary_upgrade_get_next_part_pg_partition_oid()
{
    Oid old_part_pg_partition_oid = InvalidOid;
    if (false == binary_upgrade_is_next_part_pg_partition_oid_valid()) {
        return InvalidOid;
    }

    old_part_pg_partition_oid =
        u_sess->upg_cxt
            .binary_upgrade_next_part_pg_partition_oid[u_sess->upg_cxt.binary_upgrade_cur_part_pg_partition_oid];

    u_sess->upg_cxt
        .binary_upgrade_next_part_pg_partition_oid[u_sess->upg_cxt.binary_upgrade_cur_part_pg_partition_oid] =
        InvalidOid;

    u_sess->upg_cxt.binary_upgrade_cur_part_pg_partition_oid++;

    return old_part_pg_partition_oid;
}

static Oid binary_upgrade_get_next_part_pg_partition_rfoid()
{
    Oid old_part_pg_partition_rfoid = InvalidOid;

    if (NULL == u_sess->upg_cxt.binary_upgrade_next_part_pg_partition_rfoid) {
        return InvalidOid;
    }

    if (u_sess->upg_cxt.binary_upgrade_cur_part_pg_partition_rfoid >=
        u_sess->upg_cxt.binary_upgrade_max_part_pg_partition_rfoid) {
        return InvalidOid;
    }

    old_part_pg_partition_rfoid =
        u_sess->upg_cxt
            .binary_upgrade_next_part_pg_partition_rfoid[u_sess->upg_cxt.binary_upgrade_cur_part_pg_partition_rfoid];

    u_sess->upg_cxt
        .binary_upgrade_next_part_pg_partition_rfoid[u_sess->upg_cxt.binary_upgrade_cur_part_pg_partition_rfoid] =
        InvalidOid;

    u_sess->upg_cxt.binary_upgrade_cur_part_pg_partition_rfoid++;

    return old_part_pg_partition_rfoid;
}

bool binary_upgrade_is_next_part_toast_pg_class_oid_valid()
{
    if (NULL == u_sess->upg_cxt.binary_upgrade_next_part_toast_pg_class_oid) {
        return false;
    }

    if (u_sess->upg_cxt.binary_upgrade_cur_part_toast_pg_class_oid >=
        u_sess->upg_cxt.binary_upgrade_max_part_toast_pg_class_oid) {
        return false;
    }

    if (!OidIsValid(u_sess->upg_cxt.binary_upgrade_next_part_toast_pg_class_oid
                        [u_sess->upg_cxt.binary_upgrade_cur_part_toast_pg_class_oid])) {
        return false;
    }

    return true;
}

static Oid binary_upgrade_get_next_part_toast_pg_class_oid()
{
    Oid old_part_toast_pg_class_oid = InvalidOid;
    if (false == binary_upgrade_is_next_part_toast_pg_class_oid_valid()) {
        return InvalidOid;
    }

    old_part_toast_pg_class_oid =
        u_sess->upg_cxt
            .binary_upgrade_next_part_toast_pg_class_oid[u_sess->upg_cxt.binary_upgrade_cur_part_toast_pg_class_oid];

    u_sess->upg_cxt
        .binary_upgrade_next_part_toast_pg_class_oid[u_sess->upg_cxt.binary_upgrade_cur_part_toast_pg_class_oid] =
        InvalidOid;

    u_sess->upg_cxt.binary_upgrade_cur_part_toast_pg_class_oid++;

    return old_part_toast_pg_class_oid;
}

static Oid binary_upgrade_get_next_part_toast_pg_class_rfoid()
{
    Oid old_part_toast_pg_class_rfoid = InvalidOid;

    if (NULL == u_sess->upg_cxt.binary_upgrade_next_part_toast_pg_class_rfoid) {
        return InvalidOid;
    }

    if (u_sess->upg_cxt.binary_upgrade_cur_part_toast_pg_class_rfoid >=
        u_sess->upg_cxt.binary_upgrade_max_part_toast_pg_class_rfoid) {
        return InvalidOid;
    }

    old_part_toast_pg_class_rfoid = u_sess->upg_cxt.binary_upgrade_next_part_toast_pg_class_rfoid
                                        [u_sess->upg_cxt.binary_upgrade_cur_part_toast_pg_class_rfoid];

    u_sess->upg_cxt
        .binary_upgrade_next_part_toast_pg_class_rfoid[u_sess->upg_cxt.binary_upgrade_cur_part_toast_pg_class_rfoid] =
        InvalidOid;

    u_sess->upg_cxt.binary_upgrade_cur_part_toast_pg_class_rfoid++;

    return old_part_toast_pg_class_rfoid;
}

static int TransformClusterColNameList(Oid relId, List* colList, int16* attnums)
{
    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\" 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 cluster key", INDEX_MAX_KEYS)));
        if ((((Form_pg_attribute)GETSTRUCT(atttuple))->atttypid) == HLL_OID)
            ereport(
                ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("partial cluster key don't support HLL type")));
        attnums[attnum] = ((Form_pg_attribute)GETSTRUCT(atttuple))->attnum;
        ReleaseSysCache(atttuple);
        attnum++;
    }

    return attnum;
}

void SetRelHasClusterKey(Relation rel, bool has)
{
    Relation relrel;
    HeapTuple reltup;
    Form_pg_class relStruct;

    relrel = heap_open(RelationRelationId, RowExclusiveLock);
    reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(RelationGetRelid(rel)));
    if (!HeapTupleIsValid(reltup))
        ereport(ERROR,
            (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                errmsg("cache lookup failed for relation %u", RelationGetRelid(rel))));

    relStruct = (Form_pg_class)GETSTRUCT(reltup);

    if (relStruct->relhasclusterkey != has) {
        /* update relhasclusterkey */
        relStruct->relhasclusterkey = has;

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

        /* keep catalog indexes current */
        CatalogUpdateIndexes(relrel, reltup);
    } else {
        /* Skip the disk update, but force relcache inval anyway */
        CacheInvalidateRelcache(rel);
    }

    heap_freetuple(reltup);
    heap_close(relrel, RowExclusiveLock);
}

/*
 * Add cluster key constraint for relation.
 * return address List of constraint
 */
List* AddRelClusterConstraints(Relation rel, List* clusterKeys)
{
    List *result = NIL;

    if (!RelationIsColStore(rel)) {
        ereport(ERROR,
            (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                errmsg("partial cluster key constraint does not support row/timeseries store")));
    }

    ListCell* cell = NULL;

    int nKeys = list_length(clusterKeys);

    foreach (cell, clusterKeys) {
        Constraint* cdef = (Constraint*)lfirst(cell);
        ObjectAddress * address = NULL;
        Assert(cdef->contype == CONSTR_CLUSTER);

        List* colNameList = cdef->keys;
        int colNum = list_length(colNameList);

        int16* attNums = (int16*)palloc(sizeof(int16) * colNum);

        TransformClusterColNameList(rel->rd_id, colNameList, attNums);

        char* conname = cdef->conname;
        if (conname != NULL) {
            if (ConstraintNameIsUsed(CONSTRAINT_RELATION, RelationGetRelid(rel), RelationGetNamespace(rel), conname))
                ereport(ERROR,
                    (errcode(ERRCODE_DUPLICATE_OBJECT),
                        errmsg("constraint \"%s\" for relation \"%s\" already exists",
                            conname,
                            RelationGetRelationName(rel))));
        } else {

            conname =
                ChooseConstraintName(RelationGetRelationName(rel), NULL, "cluster", RelationGetNamespace(rel), NIL);
        }
        address = (ObjectAddress *)palloc0(sizeof(ObjectAddress));
        address->classId = ConstraintRelationId;
        /*
         * Create the Check Constraint
         */
        address->objectId = CreateConstraintEntry(conname, /* Constraint Name */
            RelationGetNamespace(rel), /* namespace */
            CONSTRAINT_CLUSTER,        /* Constraint Type */
            false,                     /* Is Deferrable */
            false,                     /* Is Deferred */
            true,                      /* Is Validated */
            RelationGetRelid(rel),     /* relation */
            attNums,                   /* attrs in the constraint */
            colNum,                    /* # key attrs in the constraint */
            colNum,                    /* total # attrs (include attrs and key attrs) in the constraint */
            InvalidOid,                /* not a domain constraint */
            InvalidOid,                /* no associated index */
            InvalidOid,                /* Foreign key fields */
            NULL,
            NULL,
            NULL,
            NULL,
            0,
            ' ',
            ' ',
            ' ',
            NULL,                   /* not an exclusion constraint */
            NULL,                   /* Tree form of check constraint */
            NULL,                   /* Binary form of check constraint */
            NULL,                   /* Source form of check constraint */
            true,                   /* conislocal */
            0,                      /* coninhcount */
            true,                   /* connoinherit */
            cdef->inforConstraint); /* @hdfs informational constraint */
        CreateNonColumnComment(address->objectId, cdef->constraintOptions, ConstraintRelationId);
        pfree(attNums);
        result = lappend(result, address);
    }

    if (nKeys > 0)
        SetRelHasClusterKey(rel, true);

    return result;
}

/*
 * @hdfs
 * Brief        : Check the constraint from pg_constraint.
 * Description  : Check the constraint from pg_constraint. According to ccname, judge whether
                   existence of the same constraint in pg_constraint for this rel or not.
 * Input        : ccname, constraint name to be found.
                  rel, target relation. Check if the rel has the same constraint named as ccname.
 * Output       : none.
 * Return Value : If we find the same constraint in rel return true, otherwise return false.
 * Notes        : none.
 */
bool FindExistingConstraint(const char* ccname, Relation rel)
{
    bool found = false;
    Relation conDesc;
    SysScanDesc conscan;
    ScanKeyData skey[2];
    HeapTuple tup;

    /* Search for a pg_constraint entry with same name and relation */
    conDesc = heap_open(ConstraintRelationId, ShareUpdateExclusiveLock);

    ScanKeyInit(&skey[0], Anum_pg_constraint_conname, BTEqualStrategyNumber, F_NAMEEQ, CStringGetDatum(ccname));

    ScanKeyInit(&skey[1],
        Anum_pg_constraint_connamespace,
        BTEqualStrategyNumber,
        F_OIDEQ,
        ObjectIdGetDatum(RelationGetNamespace(rel)));

    conscan = systable_beginscan(conDesc, ConstraintNameNspIndexId, true, NULL, 2, skey);

    while (HeapTupleIsValid(tup = systable_getnext(conscan))) {
        Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(tup);

        if (0 == pg_strcasecmp(NameStr(con->conname), ccname)) {
            /* Found it. */
            found = true;
            break;
        }
    }

    systable_endscan(conscan);
    heap_close(conDesc, ShareUpdateExclusiveLock);

    return found;
}

/**
 * @Description: Build the column map. Store the column number using
 * bitmap method.
 * @in tuple_desc, A tuple descriptor.
 * @return reutrn the column map.
 */
char* make_column_map(TupleDesc tuple_desc)
{
#define COLS_IN_BYTE 8

    FormData_pg_attribute* attrs = tuple_desc->attrs;
    char* col_map = (char*)palloc0((MaxHeapAttributeNumber + COLS_IN_BYTE) / COLS_IN_BYTE);
    int col_cnt;

    Assert(tuple_desc->natts > 0);
    for (col_cnt = 0; col_cnt < tuple_desc->natts; col_cnt++) {

        if (!attrs[col_cnt].attisdropped && attrs[col_cnt].attnum > 0) {
            col_map[attrs[col_cnt].attnum >> 3] |= (1 << (attrs[col_cnt].attnum % COLS_IN_BYTE));
        }
    }

    return col_map;
}

/**
 * @Description: check whether the partition keys has timestampwithzone type.
 * @input: partTableRel, the partition table relation.
 * @return: a bool array to indicate the result. The length of array is equal to the number of partition keys.
 * @Notes: remember to pfree the array.
 */
bool* CheckPartkeyHasTimestampwithzone(Relation partTableRel, bool isForSubPartition)
{
    Relation pgPartRel = NULL;
    HeapTuple partitionTableTuple = NULL;
    Datum partkey_raw = (Datum)0;
    bool isNull = false;
    int n_key_column = 0;
    ArrayType* partkey_columns = NULL;
    int16* attnums = NULL;
    int relationAttNumber = 0;
    TupleDesc relationTupleDesc = NULL;
    FormData_pg_attribute* relationAtts = NULL;

    pgPartRel = relation_open(PartitionRelationId, AccessShareLock);

    if (isForSubPartition || RelationIsPartitionOfSubPartitionTable(partTableRel)) {
        partitionTableTuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(partTableRel->rd_id));
    } else {
        partitionTableTuple =
        searchPgPartitionByParentIdCopy(PART_OBJ_TYPE_PARTED_TABLE, ObjectIdGetDatum(partTableRel->rd_id));
    }

    if (NULL == partitionTableTuple) {
        relation_close(pgPartRel, AccessShareLock);
        ereport(ERROR,
            (errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
                errmsg("null partition key value for relation \"%s\" in check partkey type.",
                    RelationGetRelationName(partTableRel))));
    }

    partkey_raw = heap_getattr(partitionTableTuple, Anum_pg_partition_partkey, RelationGetDescr(pgPartRel), &isNull);

    if (isNull) {
        relation_close(pgPartRel, AccessShareLock);
        ereport(ERROR,
            (errcode(ERRCODE_UNEXPECTED_NULL_VALUE),
                errmsg("null partition key value for relation \"%s\" in check partkey type.",
                    RelationGetRelationName(partTableRel))));
    }
    /*  convert Datum to ArrayType*/
    partkey_columns = DatumGetArrayTypeP(partkey_raw);

    /* Get number of partition key columns from int2verctor*/
    n_key_column = ARR_DIMS(partkey_columns)[0];

    /*CHECK: the ArrayType of partition key is valid*/
    if (ARR_NDIM(partkey_columns) != 1 || n_key_column < 0 || n_key_column > MAX_RANGE_PARTKEY_NUMS ||
        ARR_HASNULL(partkey_columns) || ARR_ELEMTYPE(partkey_columns) != INT2OID) {
        relation_close(pgPartRel, AccessShareLock);
        ereport(ERROR,
            (errcode(ERRCODE_DATA_CORRUPTED),
                errmsg("partition key column's number of relation \"%s\" is not a 1-D smallint array in check partkey "
                       "type.",
                    RelationGetRelationName(partTableRel))));
    }
    Assert(n_key_column <= MAX_RANGE_PARTKEY_NUMS);
    /* Get int2 array of partition key column numbers*/
    attnums = (int16*)ARR_DATA_PTR(partkey_columns);

    relationTupleDesc = partTableRel->rd_att;
    relationAttNumber = relationTupleDesc->natts;
    relationAtts = relationTupleDesc->attrs;

    bool* isTimestamptz = (bool*)palloc0(sizeof(bool) * n_key_column);

    for (int i = 0; i < n_key_column; i++) {
        int attnum = (int)(attnums[i]);
        if (attnum >= 1 && attnum <= relationAttNumber) {
            if (relationAtts[attnum - 1].atttypid == TIMESTAMPTZOID) {
                isTimestamptz[i] = true;
            }
        } else {
            relation_close(pgPartRel, AccessShareLock);
            ereport(ERROR,
                (errcode(ERRCODE_DATA_CORRUPTED),
                    errmsg("partition key column's number of %s not in the range of all its columns",
                        RelationGetRelationName(partTableRel))));
        }
    }

    if (isForSubPartition || RelationIsPartitionOfSubPartitionTable(partTableRel))
        ReleaseSysCache(partitionTableTuple);
    else
        heap_freetuple_ext(partitionTableTuple);

    relation_close(pgPartRel, AccessShareLock);
    return isTimestamptz;
}

bool *CheckSubPartkeyHasTimestampwithzone(Relation partTableRel, List *subpartKeyPosList)
{
    Assert(RelationIsSubPartitioned(partTableRel));

    int subPartKeyNum = list_length(subpartKeyPosList);
    bool *isTimestamptzForSubPartKey = (bool *)palloc0(sizeof(bool) * subPartKeyNum);
    ListCell *subpartKeyCell = NULL;
    int partKeyIdx = 0;
    foreach (subpartKeyCell, subpartKeyPosList) {
        int pos = lfirst_int(subpartKeyCell);
        if ((RelationGetDescr(partTableRel))->attrs[pos].atttypid == TIMESTAMPTZOID) {
            isTimestamptzForSubPartKey[partKeyIdx] = true;
        }
        partKeyIdx++;
    }

    return isTimestamptzForSubPartKey;
}

/*
 * LockSeqConstraints
 *
 * Lock the sequence that the Constraint depend
 */
static void LockSeqConstraints(Relation rel, List* Constraints)
{
    if (rel == NULL || Constraints == NULL) {
        return;
    }

    /* We only Lock sequence now, so just check the func involved sequece */
    contain_func_context context =
        init_contain_func_context(list_make4_oid(NEXTVALFUNCOID, CURRVALFUNCOID, SETVAL1FUNCOID, SETVAL3FUNCOID), true);
    ListCell* lc = NULL;
    ListCell* fitem = NULL;
    foreach (lc, Constraints) {
        CookedConstraint* constr = (CookedConstraint*)lfirst(lc);

        /* contype should be CONSTR_DEFAULT or CONSTR_CHECK */
        Assert(constr->contype == CONSTR_DEFAULT || constr->contype == CONSTR_CHECK);

        (void)contains_specified_func(constr->expr, &context);

        foreach (fitem, context.func_exprs) {
            FuncExpr* func = (FuncExpr*)lfirst(fitem);
            Node* node = (Node*)linitial(func->args);
            if (IsA(node, Const)) {
                Oid seqid = ((Const*)node)->constvalue;
                LockRelationOid(seqid, AccessShareLock);
                Relation seq = RelationIdGetRelation(seqid);
                if (!RelationIsValid(seq) || !RelationIsSequnce(seq)) {
                    if (constr->contype == CONSTR_DEFAULT) {
                        ereport(ERROR,
                            (errcode(ERRCODE_UNDEFINED_PARAMETER),
                                errmsg("Object in DEFAULT constraints of column \"%s\" doesn't exists",
                                    quote_identifier((char*)attnumAttName(rel, constr->attnum)))));
                    } else {
                        ereport(ERROR,
                            (errcode(ERRCODE_UNDEFINED_PARAMETER),
                                errmsg("Object in CHECK constraints doesn't exists")));
                    }
                }
                RelationClose(seq);
            }
        }

        if (context.func_exprs) {
            list_free(context.func_exprs);
            context.func_exprs = NIL;
        }
    }

    list_free(context.funcids);
    context.funcids = NIL;

    return;
}

/*
 * Check whether a materialized view is in an initial, unloaded state.
 *
 * The check here must match what is set up in heap_create_init_fork().
 * Currently the init fork is an empty file.  A missing heap is also
 * considered to be unloaded.
 */
bool
heap_is_matview_init_state(Relation rel)
{
    Assert(rel->rd_rel->relkind == RELKIND_MATVIEW);

    /* no storage is allocated on CN, so always false */
    if (IS_PGXC_COORDINATOR)
        return false;

    RelationOpenSmgr(rel);

    if (!smgrexists(rel->rd_smgr, MAIN_FORKNUM)) {
        return true;
    }

    return (smgrnblocks(rel->rd_smgr, MAIN_FORKNUM) < 1);
}

/* Get index's indnkeyatts value with indexTuple */
int GetIndexKeyAttsByTuple(Relation relation, HeapTuple indexTuple)
{
    bool isnull = false;
    Datum indkeyDatum;
    TupleDesc tupleDesc = RelationIsValid(relation) ? RelationGetDescr(relation) : GetDefaultPgIndexDesc();
    Form_pg_index index = (Form_pg_index)GETSTRUCT(indexTuple);

    /*
     * This scenario will only occur after the upgrade, This scenario means that
     * the indnatts and Indnkeyatts of all current indexes are equal
     */
    if (heap_attisnull(indexTuple, Anum_pg_index_indnkeyatts, NULL)) {
        return index->indnatts;
    }

    indkeyDatum = fastgetattr(indexTuple, Anum_pg_index_indnkeyatts, tupleDesc, &isnull);
    Assert(!isnull);

    return DatumGetInt16(indkeyDatum);
}

bool GetIndexVisibleStateByTuple(HeapTuple indexTuple)
{
    bool isnull = false;
    Datum visibleDatum = heap_getattr(indexTuple, Anum_pg_index_indisvisible, GetDefaultPgIndexDesc(), &isnull);

    return isnull || DatumGetBool(visibleDatum);
}

void AddOrDropUidsAttr(Oid relOid, bool oldRelHasUids, bool newRelHasUids)
{
    /* reloption uids don't change, do nothing */
    if (oldRelHasUids == newRelHasUids) {
        return;
    }
    /* insert uid attr if new reloption has uid */
    if (newRelHasUids) {
        Relation rel = heap_open(AttributeRelationId, RowExclusiveLock);
        CatalogIndexState indstate = CatalogOpenIndexes(rel);
        FormData_pg_attribute attStruct;
        errno_t rc;
        int uidAttrNum = -UidAttributeNumber;
        rc = memcpy_s(&attStruct, sizeof(FormData_pg_attribute),
            (char*)SysAtt[uidAttrNum - 1], sizeof(FormData_pg_attribute));
        securec_check(rc, "\0", "\0");
        /* Fill in the correct relation OID in the copied tuple */
        attStruct.attrelid = relOid;
        InsertPgAttributeTuple(rel, &attStruct, indstate);
        CatalogCloseIndexes(indstate);
        heap_close(rel, RowExclusiveLock);

        /* Also insert uid backup table */
        InsertUidEntry(relOid);
    } else { /* delete attr if new reloption does not has uid */
        ObjectAddress object;
        object.classId = RelationRelationId;
        object.objectId = relOid;
        object.objectSubId = UidAttributeNumber;
        performDeletion(&object, DROP_RESTRICT, 0);
    }
}

static void heap_serialize_rel_attribute(Relation att_rel, Oid rel_oid,
    int att_idx, StringInfoData* concat_name, bool* depend_undefined)
{
    ScanKeyData skey[2];
    SysScanDesc scan;
    HeapTuple tuple;
    bool is_null = false;
    int key_num = 0;
    ScanKeyInit(&skey[key_num++], Anum_pg_attribute_attrelid, BTEqualStrategyNumber,
        F_OIDEQ, ObjectIdGetDatum(rel_oid));
    ScanKeyInit(&skey[key_num++], Anum_pg_attribute_attnum, BTEqualStrategyNumber,
        F_INT4EQ, Int32GetDatum(att_idx));
    scan = systable_beginscan(att_rel, AttributeRelidNumIndexId, true, SnapshotSelf, key_num, skey);
    tuple = systable_getnext(scan);
    if (!HeapTupleIsValid(tuple)) {
        systable_endscan(scan);
        return;
    }
    Datum id_dropped_datum = heap_getattr(tuple, Anum_pg_attribute_attisdropped,
        RelationGetDescr(att_rel), &is_null);
    if (is_null || DatumGetBool(id_dropped_datum)) {
        systable_endscan(scan);
        return;
    }
    Datum att_name_datum = heap_getattr(tuple, Anum_pg_attribute_attname,
        RelationGetDescr(att_rel), &is_null);
    if (!is_null) {
        appendStringInfoString(concat_name, DatumGetName(att_name_datum)->data);
    }
    Datum typ_oid_datum = heap_getattr(tuple, Anum_pg_attribute_atttypid,
        RelationGetDescr(att_rel), &is_null);
    appendStringInfoString(concat_name, ":");
    Oid typ_oid = DatumGetObjectId(typ_oid_datum);
    if (!is_null && OidIsValid(typ_oid) && typ_oid != UNDEFINEDOID) {
        (void)MakeTypeNamesStrForTypeOid(DatumGetObjectId(typ_oid_datum), depend_undefined, concat_name);
    } else if (NULL != depend_undefined) {
        *depend_undefined = true;
    }
    appendStringInfoString(concat_name, ",");
    systable_endscan(scan);
}

char* heap_serialize_row_attr(Oid rel_oid, bool* depend_undefined)
{
    Relation rel;
    StringInfoData concat_name;
    char rel_kind = get_rel_relkind(rel_oid);
    if (rel_kind != RELKIND_COMPOSITE_TYPE && rel_kind != RELKIND_RELATION) {
        return NULL;
    }
    int att_num = get_relnatts(rel_oid);
    rel = heap_open(AttributeRelationId, AccessShareLock);
    initStringInfo(&concat_name);
    for (int i = 1; i <= att_num; i++) {
       heap_serialize_rel_attribute(rel, rel_oid, i, &concat_name, depend_undefined);
    }
    heap_close(rel, AccessShareLock);
    char* ret = pstrdup(concat_name.data);
    FreeStringInfo(&concat_name);
    return ret;
}

#ifdef USE_SPQ
HeapTuple heaptuple_from_pg_attribute(Relation pg_attribute_rel,
                                      Form_pg_attribute new_attribute)
{
    Datum values[Natts_pg_attribute] = { 0 };
    bool nulls[Natts_pg_attribute] = { false };

    values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(new_attribute->attrelid);
    values[Anum_pg_attribute_attname - 1] = NameGetDatum(&new_attribute->attname);
    values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(new_attribute->atttypid);
    values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attribute->attstattarget);
    values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attribute->attlen);
    values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attribute->attnum);
    values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attribute->attndims);
    values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(new_attribute->attcacheoff);
    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_attalign - 1] = CharGetDatum(new_attribute->attalign);
    values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attribute->attnotnull);
    values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attribute->atthasdef);
    values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attribute->attisdropped);
    values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal);
    values[Anum_pg_attribute_attcmprmode - 1] = Int8GetDatum(new_attribute->attcmprmode);
    values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attribute->attinhcount);
    values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attribute->attcollation);
    values[Anum_pg_attribute_attkvtype - 1] = Int8GetDatum(new_attribute->attkvtype);

    /* start out with empty permissions and empty options */
    nulls[Anum_pg_attribute_attacl - 1] = true;
    nulls[Anum_pg_attribute_attoptions - 1] = true;
    nulls[Anum_pg_attribute_attfdwoptions - 1] = true;

    /* at default, new fileld attinitdefval of pg_attribute is null. */
    nulls[Anum_pg_attribute_attinitdefval - 1] = true;
    nulls[Anum_pg_attribute_attdroppedname - 1] = true;

    return heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);
}
#endif

bool GetIndexEnableStateByTuple(HeapTuple indexTuple)
{
    bool isnull = false;
    Datum enableDatum = heap_getattr(indexTuple, Anum_pg_index_indisenable, GetDefaultPgIndexDesc(), &isnull);
    return isnull || DatumGetBool(enableDatum);
}

bool relationHasDisableIndex(Relation relation) {
    auto indexoidlist = RelationGetIndexList(relation);
    foreach_cell(l, indexoidlist) {
        auto indexOid = lfirst_oid(l);
        auto indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexOid));
        if (!HeapTupleIsValid(indexTuple))
            ereport(ERROR,
                (errcode(ERRCODE_CACHE_LOOKUP_FAILED),
                    errmsg("could not find tuple for amop entry %u", indexOid)));

        auto enableState = GetIndexEnableStateByTuple(indexTuple);
        ReleaseSysCache(indexTuple);
        if (!enableState) {
            list_free(indexoidlist);
            return true;
        }
    }
    list_free(indexoidlist);
    return false;
}

bool resultRelationsHasDisableIndex(PlannedStmt *plannedstmt) {
    if (nullptr == plannedstmt->resultRelations) {
        return false;
    }
    ListCell *lc = NULL;
    foreach (lc, (List*)linitial(plannedstmt->resultRelations)) {
        Index idx = lfirst_int(lc);
        auto rid = getrelid(idx, plannedstmt->rtable);
        if (OidIsValid(rid) == false || rid < FirstNormalObjectId) {
            continue;
        }

        auto rel = heap_open(rid, AccessShareLock);
        if (relationHasDisableIndex(rel)) {
            heap_close(rel, NoLock);
            return true;
        }
        heap_close(rel, NoLock);
    }
    return false;
}

void CheckWriteCommandWithDisableIndex(PlannedStmt *plannedstmt) {
    if (!CommandIsReadOnly(plannedstmt) && resultRelationsHasDisableIndex(plannedstmt)) {
        ereport(ERROR, (errcode(ERRCODE_OPERATOR_INTERVENTION),
            errmsg("The relation has no permit to write because it has index in disable state")));
    }
}