Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Postgres/src/backend/commands/   (Postgres Database Version 18.4©)  Datei vom 11.4.2026 mit Größe 692 kB image not shown  

Quellcode-Bibliothek tablecmds.c

  Sprache: C
 

/*-------------------------------------------------------------------------
 *
 * tablecmds.c
 *   Commands for creating and altering table structures and settings
 *
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *   src/backend/commands/tablecmds.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/attmap.h"
#include "access/genam.h"
#include "access/gist.h"
#include "access/heapam.h"
#include "access/heapam_xlog.h"
#include "access/multixact.h"
#include "access/reloptions.h"
#include "access/relscan.h"
#include "access/sysattr.h"
#include "access/tableam.h"
#include "access/toast_compression.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "access/xloginsert.h"
#include "catalog/catalog.h"
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_largeobject.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/comment.h"
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "commands/typecmds.h"
#include "commands/user.h"
#include "commands/vacuum.h"
#include "common/int.h"
#include "executor/executor.h"
#include "foreign/fdwapi.h"
#include "foreign/foreign.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/parsenodes.h"
#include "optimizer/optimizer.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
#include "parser/parse_type.h"
#include "parser/parse_utilcmd.h"
#include "parser/parser.h"
#include "partitioning/partbounds.h"
#include "partitioning/partdesc.h"
#include "pgstat.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/lock.h"
#include "storage/predicate.h"
#include "storage/smgr.h"
#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/partcache.h"
#include "utils/relcache.h"
#include "utils/ruleutils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/timestamp.h"
#include "utils/typcache.h"
#include "utils/usercontext.h"

/*
 * ON COMMIT action list
 */

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

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

 SubTransactionId creating_subid;
 SubTransactionId deleting_subid;
} OnCommitItem;

static List *on_commits = NIL;


/*
 * State information for ALTER TABLE
 *
 * The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo
 * structs, one for each table modified by the operation (the named table
 * plus any child tables that are affected).  We save lists of subcommands
 * to apply to this table (possibly modified by parse transformation steps);
 * these lists will be executed in Phase 2.  If a Phase 3 step is needed,
 * necessary information is stored in the constraints and newvals lists.
 *
 * Phase 2 is divided into multiple passes; subcommands are executed in
 * a pass determined by subcommand type.
 */


typedef enum AlterTablePass
{
 AT_PASS_UNSET = -1,   /* UNSET will cause ERROR */
 AT_PASS_DROP,    /* DROP (all flavors) */
 AT_PASS_ALTER_TYPE,   /* ALTER COLUMN TYPE */
 AT_PASS_ADD_COL,   /* ADD COLUMN */
 AT_PASS_SET_EXPRESSION,  /* ALTER SET EXPRESSION */
 AT_PASS_OLD_INDEX,   /* re-add existing indexes */
 AT_PASS_OLD_CONSTR,   /* re-add existing constraints */
 /* We could support a RENAME COLUMN pass here, but not currently used */
 AT_PASS_ADD_CONSTR,   /* ADD constraints (initial examination) */
 AT_PASS_COL_ATTRS,   /* set column attributes, eg NOT NULL */
 AT_PASS_ADD_INDEXCONSTR, /* ADD index-based constraints */
 AT_PASS_ADD_INDEX,   /* ADD indexes */
 AT_PASS_ADD_OTHERCONSTR, /* ADD other constraints, defaults */
 AT_PASS_MISC,    /* other stuff */
} AlterTablePass;

#define AT_NUM_PASSES   (AT_PASS_MISC + 1)

typedef struct AlteredTableInfo
{
 /* Information saved before any work commences: */
 Oid   relid;   /* Relation to work on */
 char  relkind;  /* Its relkind */
 TupleDesc oldDesc;  /* Pre-modification tuple descriptor */

 /*
  * Transiently set during Phase 2, normally set to NULL.
  *
  * ATRewriteCatalogs sets this when it starts, and closes when ATExecCmd
  * returns control.  This can be exploited by ATExecCmd subroutines to
  * close/reopen across transaction boundaries.
 */

 Relation rel;

 /* Information saved by Phase 1 for Phase 2: */
 List    *subcmds[AT_NUM_PASSES]; /* Lists of AlterTableCmd */
 /* Information saved by Phases 1/2 for Phase 3: */
 List    *constraints; /* List of NewConstraint */
 List    *newvals;  /* List of NewColumnValue */
 List    *afterStmts;  /* List of utility command parsetrees */
 bool  verify_new_notnull; /* T if we should recheck NOT NULL */
 int   rewrite;  /* Reason for forced rewrite, if any */
 bool  chgAccessMethod; /* T if SET ACCESS METHOD is used */
 Oid   newAccessMethod; /* new access method; 0 means no change,
 * if above is true */

 Oid   newTableSpace; /* new tablespace; 0 means no change */
 bool  chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 char  newrelpersistence; /* if above is true */
 Expr    *partition_constraint; /* for attach partition validation */
 /* true, if validating default due to some other attach/detach */
 bool  validate_default;
 /* Objects to rebuild after completing ALTER TYPE operations */
 List    *changedConstraintOids; /* OIDs of constraints to rebuild */
 List    *changedConstraintDefs; /* string definitions of same */
 List    *changedIndexOids; /* OIDs of indexes to rebuild */
 List    *changedIndexDefs; /* string definitions of same */
 char    *replicaIdentityIndex; /* index to reset as REPLICA IDENTITY */
 char    *clusterOnIndex; /* index to use for CLUSTER */
 List    *changedStatisticsOids; /* OIDs of statistics to rebuild */
 List    *changedStatisticsDefs; /* string definitions of same */
} AlteredTableInfo;

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

/*
 * Struct describing one new column value that needs to be computed during
 * Phase 3 copy (this could be either a new column with a non-null default, or
 * a column that we're changing the type of).  Columns without such an entry
 * are just copied from the old table during ATRewriteTable.  Note that the
 * expr is an expression over *old* table values, except when is_generated
 * is true; then it is an expression over columns of the *new* tuple.
 */

typedef struct NewColumnValue
{
 AttrNumber attnum;   /* which column */
 Expr    *expr;   /* expression to compute */
 ExprState  *exprstate;  /* execution state */
 bool  is_generated; /* is it a GENERATED expression? */
} NewColumnValue;

/*
 * Error-reporting support for RemoveRelations
 */

struct dropmsgstrings
{
 char  kind;
 int   nonexistent_code;
 const char *nonexistent_msg;
 const char *skipping_msg;
 const char *nota_msg;
 const char *drophint_msg;
};

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

/* communication between RemoveRelations and RangeVarCallbackForDropRelation */
struct DropRelationCallbackState
{
 /* These fields are set by RemoveRelations: */
 char  expected_relkind;
 LOCKMODE heap_lockmode;
 /* These fields are state to track which subsidiary locks are held: */
 Oid   heapOid;
 Oid   partParentOid;
 /* These fields are passed back by RangeVarCallbackForDropRelation: */
 char  actual_relkind;
 char  actual_relpersistence;
};

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

/*
 * ForeignTruncateInfo
 *
 * Information related to truncation of foreign tables.  This is used for
 * the elements in a hash table. It uses the server OID as lookup key,
 * and includes a per-server list of all foreign tables involved in the
 * truncation.
 */

typedef struct ForeignTruncateInfo
{
 Oid   serverid;
 List    *rels;
} ForeignTruncateInfo;

/* Partial or complete FK creation in addFkConstraint() */
typedef enum addFkConstraintSides
{
 addFkReferencedSide,
 addFkReferencingSide,
 addFkBothSides,
} addFkConstraintSides;

/*
 * Partition tables are expected to be dropped when the parent partitioned
 * table gets dropped. Hence for partitioning we use AUTO dependency.
 * Otherwise, for regular inheritance use NORMAL dependency.
 */

#define child_dependency_type(child_is_partition) \
 ((child_is_partition) ? DEPENDENCY_AUTO : DEPENDENCY_NORMAL)

static void truncate_check_rel(Oid relid, Form_pg_class reltuple);
static void truncate_check_perms(Oid relid, Form_pg_class reltuple);
static void truncate_check_activity(Relation rel);
static void RangeVarCallbackForTruncate(const RangeVar *relation,
          Oid relId, Oid oldRelId, void *arg);
static List *MergeAttributes(List *columns, const List *supers, char relpersistence,
        bool is_partition, List **supconstr,
        List **supnotnulls);
static List *MergeCheckConstraint(List *constraints, const char *name, Node *expr, bool is_enforced);
static void MergeChildAttribute(List *inh_columns, int exist_attno, int newcol_attno, const ColumnDef *newdef);
static ColumnDef *MergeInheritedAttribute(List *inh_columns, int exist_attno, const ColumnDef *newdef);
static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel, bool ispartition);
static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
static void StoreCatalogInheritance(Oid relationId, List *supers,
         bool child_is_partition);
static void StoreCatalogInheritance1(Oid relationId, Oid parentOid,
          int32 seqNumber, Relation inhRelation,
          bool child_is_partition);
static int findAttrByName(const char *attributeName, const List *columns);
static void AlterIndexNamespaces(Relation classRel, Relation rel,
         Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved);
static void AlterSeqNamespaces(Relation classRel, Relation rel,
          Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved,
          LOCKMODE lockmode);
static ObjectAddress ATExecAlterConstraint(List **wqueue, Relation rel,
             ATAlterConstraint *cmdcon,
             bool recurse, LOCKMODE lockmode);
static bool ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon, Relation conrel,
            Relation tgrel, Relation rel, HeapTuple contuple,
            bool recurse, LOCKMODE lockmode);
static bool ATExecAlterConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon,
           Relation conrel, Relation tgrel,
           Oid fkrelid, Oid pkrelid,
           HeapTuple contuple, LOCKMODE lockmode,
           Oid ReferencedParentDelTrigger,
           Oid ReferencedParentUpdTrigger,
           Oid ReferencingParentInsTrigger,
           Oid ReferencingParentUpdTrigger);
static bool ATExecAlterConstrDeferrability(List **wqueue, ATAlterConstraint *cmdcon,
             Relation conrel, Relation tgrel, Relation rel,
             HeapTuple contuple, bool recurse,
             List **otherrelids, LOCKMODE lockmode);
static bool ATExecAlterConstrInheritability(List **wqueue, ATAlterConstraint *cmdcon,
           Relation conrel, Relation rel,
           HeapTuple contuple, LOCKMODE lockmode);
static void AlterConstrTriggerDeferrability(Oid conoid, Relation tgrel, Relation rel,
           bool deferrable, bool initdeferred,
           List **otherrelids);
static void AlterConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
            Relation conrel, Relation tgrel,
            Oid fkrelid, Oid pkrelid,
            HeapTuple contuple, LOCKMODE lockmode,
            Oid ReferencedParentDelTrigger,
            Oid ReferencedParentUpdTrigger,
            Oid ReferencingParentInsTrigger,
            Oid ReferencingParentUpdTrigger);
static void AlterConstrDeferrabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
           Relation conrel, Relation tgrel, Relation rel,
           HeapTuple contuple, bool recurse,
           List **otherrelids, LOCKMODE lockmode);
static void AlterConstrUpdateConstraintEntry(ATAlterConstraint *cmdcon, Relation conrel,
            HeapTuple contuple);
static ObjectAddress ATExecValidateConstraint(List **wqueue,
             Relation rel, char *constrName,
             bool recurse, bool recursing, LOCKMODE lockmode);
static void QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation fkrel,
          Oid pkrelid, HeapTuple contuple, LOCKMODE lockmode);
static void QueueCheckConstraintValidation(List **wqueue, Relation conrel, Relation rel,
             char *constrName, HeapTuple contuple,
             bool recurse, bool recursing, LOCKMODE lockmode);
static void QueueNNConstraintValidation(List **wqueue, Relation conrel, Relation rel,
          HeapTuple contuple, bool recurse, bool recursing,
          LOCKMODE lockmode);
static int transformColumnNameList(Oid relId, List *colList,
         int16 *attnums, Oid *atttypids, Oid *attcollids);
static int transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
            List **attnamelist,
            int16 *attnums, Oid *atttypids, Oid *attcollids,
            Oid *opclasses, bool *pk_has_without_overlaps);
static Oid transformFkeyCheckAttrs(Relation pkrel,
         int numattrs, int16 *attnums,
         bool with_period, Oid *opclasses,
         bool *pk_has_without_overlaps);
static void checkFkeyPermissions(Relation rel, int16 *attnums, int natts);
static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId,
          Oid *funcid);
static void validateForeignKeyConstraint(char *conname,
           Relation rel, Relation pkrel,
           Oid pkindOid, Oid constraintOid, bool hasperiod);
static void CheckAlterTableIsSafe(Relation rel);
static void ATController(AlterTableStmt *parsetree,
       Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
       AlterTableUtilityContext *context);
static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
       bool recurse, bool recursing, LOCKMODE lockmode,
       AlterTableUtilityContext *context);
static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
         AlterTableUtilityContext *context);
static void ATExecCmd(List **wqueue, AlteredTableInfo *tab,
       AlterTableCmd *cmd, LOCKMODE lockmode, AlterTablePass cur_pass,
       AlterTableUtilityContext *context);
static AlterTableCmd *ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab,
            Relation rel, AlterTableCmd *cmd,
            bool recurse, LOCKMODE lockmode,
            AlterTablePass cur_pass,
            AlterTableUtilityContext *context);
static void ATRewriteTables(AlterTableStmt *parsetree,
       List **wqueue, LOCKMODE lockmode,
       AlterTableUtilityContext *context);
static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap);
static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
static void ATSimplePermissions(AlterTableType cmdtype, Relation rel, int allowed_targets);
static void ATSimpleRecursion(List **wqueue, Relation rel,
         AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
         AlterTableUtilityContext *context);
static void ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode);
static void ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
          LOCKMODE lockmode,
          AlterTableUtilityContext *context);
static List *find_typed_table_dependencies(Oid typeOid, const char *typeName,
             DropBehavior behavior);
static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
       bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode,
       AlterTableUtilityContext *context);
static ObjectAddress ATExecAddColumn(List **wqueue, AlteredTableInfo *tab,
          Relation rel, AlterTableCmd **cmd,
          bool recurse, bool recursing,
          LOCKMODE lockmode, AlterTablePass cur_pass,
          AlterTableUtilityContext *context);
static bool check_for_column_name_collision(Relation rel, const char *colname,
           bool if_not_exists);
static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, bool recurse,
            LOCKMODE lockmode);
static void set_attnotnull(List **wqueue, Relation rel, AttrNumber attnum,
         bool is_valid, bool queue_validation);
static ObjectAddress ATExecSetNotNull(List **wqueue, Relation rel,
           char *conName, char *colName,
           bool recurse, bool recursing,
           LOCKMODE lockmode);
static bool NotNullImpliedByRelConstraints(Relation rel, Form_pg_attribute attr);
static bool ConstraintImpliedByRelConstraint(Relation scanrel,
            List *testConstraint, List *provenConstraint);
static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
           Node *newDefault, LOCKMODE lockmode);
static ObjectAddress ATExecCookedColumnDefault(Relation rel, AttrNumber attnum,
              Node *newDefault);
static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName,
            Node *def, LOCKMODE lockmode, bool recurse, bool recursing);
static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName,
            Node *def, LOCKMODE lockmode, bool recurse, bool recursing);
static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode,
          bool recurse, bool recursing);
static ObjectAddress ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
           Node *newExpr, LOCKMODE lockmode);
static void ATPrepDropExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, LOCKMODE lockmode);
static ObjectAddress ATExecDropExpression(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode);
static ObjectAddress ATExecSetStatistics(Relation rel, const char *colName, int16 colNum,
           Node *newValue, LOCKMODE lockmode);
static ObjectAddress ATExecSetOptions(Relation rel, const char *colName,
           Node *options, bool isReset, LOCKMODE lockmode);
static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
           Node *newValue, LOCKMODE lockmode);
static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
        AlterTableCmd *cmd, LOCKMODE lockmode,
        AlterTableUtilityContext *context);
static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
           DropBehavior behavior,
           bool recurse, bool recursing,
           bool missing_ok, LOCKMODE lockmode,
           ObjectAddresses *addrs);
static void ATPrepAddPrimaryKey(List **wqueue, Relation rel, AlterTableCmd *cmd,
        bool recurse, LOCKMODE lockmode,
        AlterTableUtilityContext *context);
static void verifyNotNullPKCompatible(HeapTuple tuple, const char *colname);
static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
         IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
static ObjectAddress ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
           CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
static ObjectAddress ATExecAddConstraint(List **wqueue,
           AlteredTableInfo *tab, Relation rel,
           Constraint *newConstraint, bool recurse, bool is_readd,
           LOCKMODE lockmode);
static char *ChooseForeignKeyConstraintNameAddition(List *colnames);
static ObjectAddress ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
             IndexStmt *stmt, LOCKMODE lockmode);
static ObjectAddress ATAddCheckNNConstraint(List **wqueue,
           AlteredTableInfo *tab, Relation rel,
           Constraint *constr,
           bool recurse, bool recursing, bool is_readd,
           LOCKMODE lockmode);
static ObjectAddress ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab,
              Relation rel, Constraint *fkconstraint,
              bool recurse, bool recursing,
              LOCKMODE lockmode);
static int validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
           int numfksetcols, int16 *fksetcolsattnums,
           List *fksetcols);
static ObjectAddress addFkConstraint(addFkConstraintSides fkside,
          char *constraintname,
          Constraint *fkconstraint, Relation rel,
          Relation pkrel, Oid indexOid,
          Oid parentConstr,
          int numfks, int16 *pkattnum, int16 *fkattnum,
          Oid *pfeqoperators, Oid *ppeqoperators,
          Oid *ffeqoperators, int numfkdelsetcols,
          int16 *fkdelsetcols, bool is_internal,
          bool with_period);
static void addFkRecurseReferenced(Constraint *fkconstraint,
           Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
           int numfks, int16 *pkattnum, int16 *fkattnum,
           Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
           int numfkdelsetcols, int16 *fkdelsetcols,
           bool old_check_ok,
           Oid parentDelTrigger, Oid parentUpdTrigger,
           bool with_period);
static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint,
         Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
         int numfks, int16 *pkattnum, int16 *fkattnum,
         Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
         int numfkdelsetcols, int16 *fkdelsetcols,
         bool old_check_ok, LOCKMODE lockmode,
         Oid parentInsTrigger, Oid parentUpdTrigger,
         bool with_period);
static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
            Relation partitionRel);
static void CloneFkReferenced(Relation parentRel, Relation partitionRel);
static void CloneFkReferencing(List **wqueue, Relation parentRel,
          Relation partRel);
static void createForeignKeyCheckTriggers(Oid myRelOid, Oid refRelOid,
            Constraint *fkconstraint, Oid constraintOid,
            Oid indexOid,
            Oid parentInsTrigger, Oid parentUpdTrigger,
            Oid *insertTrigOid, Oid *updateTrigOid);
static void createForeignKeyActionTriggers(Oid myRelOid, Oid refRelOid,
             Constraint *fkconstraint, Oid constraintOid,
             Oid indexOid,
             Oid parentDelTrigger, Oid parentUpdTrigger,
             Oid *deleteTrigOid, Oid *updateTrigOid);
static bool tryAttachPartitionForeignKey(List **wqueue,
           ForeignKeyCacheInfo *fk,
           Relation partition,
           Oid parentConstrOid, int numfks,
           AttrNumber *mapped_conkey, AttrNumber *confkey,
           Oid *conpfeqop,
           Oid parentInsTrigger,
           Oid parentUpdTrigger,
           Relation trigrel);
static void AttachPartitionForeignKey(List **wqueue, Relation partition,
           Oid partConstrOid, Oid parentConstrOid,
           Oid parentInsTrigger, Oid parentUpdTrigger,
           Relation trigrel);
static void RemoveInheritedConstraint(Relation conrel, Relation trigrel,
           Oid conoid, Oid conrelid);
static void DropForeignKeyConstraintTriggers(Relation trigrel, Oid conoid,
            Oid confrelid, Oid conrelid);
static void GetForeignKeyActionTriggers(Relation trigrel,
          Oid conoid, Oid confrelid, Oid conrelid,
          Oid *deleteTriggerOid,
          Oid *updateTriggerOid);
static void GetForeignKeyCheckTriggers(Relation trigrel,
            Oid conoid, Oid confrelid, Oid conrelid,
            Oid *insertTriggerOid,
            Oid *updateTriggerOid);
static void ATExecDropConstraint(Relation rel, const char *constrName,
         DropBehavior behavior, bool recurse,
         bool missing_ok, LOCKMODE lockmode);
static ObjectAddress dropconstraint_internal(Relation rel,
            HeapTuple constraintTup, DropBehavior behavior,
            bool recurse, bool recursing,
            bool missing_ok, LOCKMODE lockmode);
static void ATPrepAlterColumnType(List **wqueue,
          AlteredTableInfo *tab, Relation rel,
          bool recurse, bool recursing,
          AlterTableCmd *cmd, LOCKMODE lockmode,
          AlterTableUtilityContext *context);
static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno);
static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
             AlterTableCmd *cmd, LOCKMODE lockmode);
static void RememberAllDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype,
             Relation rel, AttrNumber attnum, const char *colName);
static void RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab);
static void RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab);
static void RememberStatisticsForRebuilding(Oid stxoid, AlteredTableInfo *tab);
static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab,
           LOCKMODE lockmode);
static void ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId,
         char *cmd, List **wqueue, LOCKMODE lockmode,
         bool rewrite);
static void RebuildConstraintComment(AlteredTableInfo *tab, AlterTablePass pass,
          Oid objid, Relation rel, List *domname,
          const char *conname);
static void TryReuseIndex(Oid oldId, IndexStmt *stmt);
static void TryReuseForeignKey(Oid oldId, Constraint *con);
static ObjectAddress ATExecAlterColumnGenericOptions(Relation rel, const char *colName,
              List *options, LOCKMODE lockmode);
static void change_owner_fix_column_acls(Oid relationOid,
           Oid oldOwnerId, Oid newOwnerId);
static void change_owner_recurse_to_sequences(Oid relationOid,
             Oid newOwnerId, LOCKMODE lockmode);
static ObjectAddress ATExecClusterOn(Relation rel, const char *indexName,
          LOCKMODE lockmode);
static void ATExecDropCluster(Relation rel, LOCKMODE lockmode);
static void ATPrepSetAccessMethod(AlteredTableInfo *tab, Relation rel, const char *amname);
static void ATExecSetAccessMethodNoStorage(Relation rel, Oid newAccessMethodId);
static void ATPrepChangePersistence(AlteredTableInfo *tab, Relation rel,
         bool toLogged);
static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
        const char *tablespacename, LOCKMODE lockmode);
static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
static void ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace);
static void ATExecSetRelOptions(Relation rel, List *defList,
        AlterTableType operation,
        LOCKMODE lockmode);
static void ATExecEnableDisableTrigger(Relation rel, const char *trigname,
            char fires_when, bool skip_system, bool recurse,
            LOCKMODE lockmode);
static void ATExecEnableDisableRule(Relation rel, const char *rulename,
         char fires_when, LOCKMODE lockmode);
static void ATPrepAddInherit(Relation child_rel);
static ObjectAddress ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode);
static ObjectAddress ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode);
static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid,
           DependencyType deptype);
static ObjectAddress ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode);
static void ATExecGenericOptions(Relation rel, List *options);
static void ATExecSetRowSecurity(Relation rel, bool rls);
static void ATExecForceNoForceRowSecurity(Relation rel, bool force_rls);
static ObjectAddress ATExecSetCompression(Relation rel,
            const char *column, Node *newValue, LOCKMODE lockmode);

static void index_copy_data(Relation rel, RelFileLocator newrlocator);
static const char *storage_name(char c);

static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
           Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
            Oid oldrelid, void *arg);
static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec);
static void ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNumber *partattrs,
          List **partexprs, Oid *partopclass, Oid *partcollation,
          PartitionStrategy strategy);
static void CreateInheritance(Relation child_rel, Relation parent_rel, bool ispartition);
static void RemoveInheritance(Relation child_rel, Relation parent_rel,
         bool expect_detached);
static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
             PartitionCmd *cmd,
             AlterTableUtilityContext *context);
static void AttachPartitionEnsureIndexes(List **wqueue, Relation rel, Relation attachrel);
static void QueuePartitionConstraintValidation(List **wqueue, Relation scanrel,
              List *partConstraint,
              bool validate_default);
static void CloneRowTriggersToPartition(Relation parent, Relation partition);
static void DetachAddConstraintIfNeeded(List **wqueue, Relation partRel);
static void DropClonedTriggersFromPartition(Oid partitionId);
static ObjectAddress ATExecDetachPartition(List **wqueue, AlteredTableInfo *tab,
             Relation rel, RangeVar *name,
             bool concurrent);
static void DetachPartitionFinalize(Relation rel, Relation partRel,
         bool concurrent, Oid defaultPartOid);
static ObjectAddress ATExecDetachPartitionFinalize(Relation rel, RangeVar *name);
static ObjectAddress ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx,
             RangeVar *name);
static void validatePartitionedIndex(Relation partedIdx, Relation partedTbl);
static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
          Relation partitionTbl);
static void verifyPartitionIndexNotNull(IndexInfo *iinfo, Relation partition);
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
static char GetAttributeCompression(Oid atttypid, const char *compression);
static char GetAttributeStorage(Oid atttypid, const char *storagemode);


/* ----------------------------------------------------------------
 *  DefineRelation
 *    Creates a new relation.
 *
 * stmt carries parsetree information from an ordinary CREATE TABLE statement.
 * The other arguments are used to extend the behavior for other cases:
 * relkind: relkind to assign to the new relation
 * ownerId: if not InvalidOid, use this as the new relation's owner.
 * typaddress: if not null, it's set to the pg_type entry's address.
 * queryString: for error reporting
 *
 * Note that permissions checks are done against current user regardless of
 * ownerId.  A nonzero ownerId is used when someone is creating a relation
 * "on behalf of" someone else, so we still want to see that the current user
 * has permissions to do it.
 *
 * If successful, returns the address of the new relation.
 * ----------------------------------------------------------------
 */

ObjectAddress
DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
      ObjectAddress *typaddress, const char *queryString)
{
 char  relname[NAMEDATALEN];
 Oid   namespaceId;
 Oid   relationId;
 Oid   tablespaceId;
 Relation rel;
 TupleDesc descriptor;
 List    *inheritOids;
 List    *old_constraints;
 List    *old_notnulls;
 List    *rawDefaults;
 List    *cookedDefaults;
 List    *nncols;
 List    *connames = NIL;
 Datum  reloptions;
 ListCell   *listptr;
 AttrNumber attnum;
 bool  partitioned;
 const char *const validnsps[] = HEAP_RELOPT_NAMESPACES;
 Oid   ofTypeId;
 ObjectAddress address;
 LOCKMODE parentLockmode;
 Oid   accessMethodId = InvalidOid;

 /*
  * Truncate relname to appropriate length (probably a waste of time, as
  * parser should have done this already).
 */

 strlcpy(relname, stmt->relation->relname, NAMEDATALEN);

 /*
  * Check consistency of arguments
 */

 if (stmt->oncommit != ONCOMMIT_NOOP
  && stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
     errmsg("ON COMMIT can only be used on temporary tables")));

 if (stmt->partspec != NULL)
 {
  if (relkind != RELKIND_RELATION)
   elog(ERROR, "unexpected relkind: %d", (int) relkind);

  relkind = RELKIND_PARTITIONED_TABLE;
  partitioned = true;
 }
 else
  partitioned = false;

 if (relkind == RELKIND_PARTITIONED_TABLE &&
  stmt->relation->relpersistence == RELPERSISTENCE_UNLOGGED)
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("partitioned tables cannot be unlogged")));

 /*
  * Look up the namespace in which we are supposed to create the relation,
  * check we have permission to create there, lock it against concurrent
  * drop, and mark stmt->relation as RELPERSISTENCE_TEMP if a temporary
  * namespace is selected.
 */

 namespaceId =
  RangeVarGetAndCheckCreationNamespace(stmt->relation, NoLock, NULL);

 /*
  * Security check: disallow creating temp tables from security-restricted
  * code.  This is needed because calling code might not expect untrusted
  * tables to appear in pg_temp at the front of its search path.
 */

 if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
  && InSecurityRestrictedOperation())
  ereport(ERROR,
    (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     errmsg("cannot create temporary table within security-restricted operation")));

 /*
  * Determine the lockmode to use when scanning parents.  A self-exclusive
  * lock is needed here.
  *
  * For regular inheritance, if two backends attempt to add children to the
  * same parent simultaneously, and that parent has no pre-existing
  * children, then both will attempt to update the parent's relhassubclass
  * field, leading to a "tuple concurrently updated" error.  Also, this
  * interlocks against a concurrent ANALYZE on the parent table, which
  * might otherwise be attempting to clear the parent's relhassubclass
  * field, if its previous children were recently dropped.
  *
  * If the child table is a partition, then we instead grab an exclusive
  * lock on the parent because its partition descriptor will be changed by
  * addition of the new partition.
 */

 parentLockmode = (stmt->partbound != NULL ? AccessExclusiveLock :
       ShareUpdateExclusiveLock);

 /* Determine the list of OIDs of the parents. */
 inheritOids = NIL;
 foreach(listptr, stmt->inhRelations)
 {
  RangeVar   *rv = (RangeVar *) lfirst(listptr);
  Oid   parentOid;

  parentOid = RangeVarGetRelid(rv, parentLockmode, false);

  /*
   * Reject duplications in the list of parents.
 */

  if (list_member_oid(inheritOids, parentOid))
   ereport(ERROR,
     (errcode(ERRCODE_DUPLICATE_TABLE),
      errmsg("relation \"%s\" would be inherited from more than once",
       get_rel_name(parentOid))));

  inheritOids = lappend_oid(inheritOids, parentOid);
 }

 /*
  * Select tablespace to use: an explicitly indicated one, or (in the case
  * of a partitioned table) the parent's, if it has one.
 */

 if (stmt->tablespacename)
 {
  tablespaceId = get_tablespace_oid(stmt->tablespacename, false);

  if (partitioned && tablespaceId == MyDatabaseTableSpace)
   ereport(ERROR,
     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
      errmsg("cannot specify default tablespace for partitioned relations")));
 }
 else if (stmt->partbound)
 {
  Assert(list_length(inheritOids) == 1);
  tablespaceId = get_rel_tablespace(linitial_oid(inheritOids));
 }
 else
  tablespaceId = InvalidOid;

 /* still nothing? use the default */
 if (!OidIsValid(tablespaceId))
  tablespaceId = GetDefaultTablespace(stmt->relation->relpersistence,
           partitioned);

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

  aclresult = object_aclcheck(TableSpaceRelationId, tablespaceId, GetUserId(),
         ACL_CREATE);
  if (aclresult != ACLCHECK_OK)
   aclcheck_error(aclresult, OBJECT_TABLESPACE,
         get_tablespace_name(tablespaceId));
 }

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

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

 /*
  * Parse and validate reloptions, if any.
 */

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

 switch (relkind)
 {
  case RELKIND_VIEW:
   (void) view_reloptions(reloptions, true);
   break;
  case RELKIND_PARTITIONED_TABLE:
   (void) partitioned_table_reloptions(reloptions, true);
   break;
  default:
   (void) heap_reloptions(relkind, reloptions, true);
 }

 if (stmt->ofTypename)
 {
  AclResult aclresult;

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

  aclresult = object_aclcheck(TypeRelationId, ofTypeId, GetUserId(), ACL_USAGE);
  if (aclresult != ACLCHECK_OK)
   aclcheck_error_type(aclresult, ofTypeId);
 }
 else
  ofTypeId = InvalidOid;

 /*
  * Look up inheritance ancestors and generate relation schema, including
  * inherited attributes.  (Note that stmt->tableElts is destructively
  * modified by MergeAttributes.)
 */

 stmt->tableElts =
  MergeAttributes(stmt->tableElts, inheritOids,
      stmt->relation->relpersistence,
      stmt->partbound != NULL,
      &old_constraints, &old_notnulls);

 /*
  * Create a tuple descriptor from the relation schema.  Note that this
  * deals with column names, types, and in-descriptor NOT NULL flags, but
  * not default values, NOT NULL or CHECK constraints; we handle those
  * below.
 */

 descriptor = BuildDescForRelation(stmt->tableElts);

 /*
  * Find columns with default values and prepare for insertion of the
  * defaults.  Pre-cooked (that is, inherited) defaults go into a list of
  * CookedConstraint structs that we'll pass to heap_create_with_catalog,
  * while raw defaults go into a list of RawColumnDefault structs that will
  * be processed by AddRelationNewConstraints.  (We can't deal with raw
  * expressions until we can do transformExpr.)
 */

 rawDefaults = NIL;
 cookedDefaults = NIL;
 attnum = 0;

 foreach(listptr, stmt->tableElts)
 {
  ColumnDef  *colDef = lfirst(listptr);

  attnum++;
  if (colDef->raw_default != NULL)
  {
   RawColumnDefault *rawEnt;

   Assert(colDef->cooked_default == NULL);

   rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
   rawEnt->attnum = attnum;
   rawEnt->raw_default = colDef->raw_default;
   rawEnt->generated = colDef->generated;
   rawDefaults = lappend(rawDefaults, rawEnt);
  }
  else if (colDef->cooked_default != NULL)
  {
   CookedConstraint *cooked;

   cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
   cooked->contype = CONSTR_DEFAULT;
   cooked->conoid = InvalidOid; /* until created */
   cooked->name = NULL;
   cooked->attnum = attnum;
   cooked->expr = colDef->cooked_default;
   cooked->is_enforced = true;
   cooked->skip_validation = false;
   cooked->is_local = true/* not used for defaults */
   cooked->inhcount = 0/* ditto */
   cooked->is_no_inherit = false;
   cookedDefaults = lappend(cookedDefaults, cooked);
  }
 }

 /*
  * For relations with table AM and partitioned tables, select access
  * method to use: an explicitly indicated one, or (in the case of a
  * partitioned table) the parent's, if it has one.
 */

 if (stmt->accessMethod != NULL)
 {
  Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE);
  accessMethodId = get_table_am_oid(stmt->accessMethod, false);
 }
 else if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE)
 {
  if (stmt->partbound)
  {
   Assert(list_length(inheritOids) == 1);
   accessMethodId = get_rel_relam(linitial_oid(inheritOids));
  }

  if (RELKIND_HAS_TABLE_AM(relkind) && !OidIsValid(accessMethodId))
   accessMethodId = get_table_am_oid(default_table_access_method, false);
 }

 /*
  * Create the relation.  Inherited defaults and CHECK constraints are
  * passed in for immediate handling --- since they don't need parsing,
  * they can be stored immediately.
 */

 relationId = heap_create_with_catalog(relname,
            namespaceId,
            tablespaceId,
            InvalidOid,
            InvalidOid,
            ofTypeId,
            ownerId,
            accessMethodId,
            descriptor,
            list_concat(cookedDefaults,
               old_constraints),
            relkind,
            stmt->relation->relpersistence,
            false,
            false,
            stmt->oncommit,
            reloptions,
            true,
            allowSystemTableMods,
            false,
            InvalidOid,
            typaddress);

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

 CommandCounterIncrement();

 /*
  * Open the new relation and acquire exclusive lock on it.  This isn't
  * really necessary for locking out other backends (since they can't see
  * the new rel anyway until we commit), but it keeps the lock manager from
  * complaining about deadlock risks.
 */

 rel = relation_open(relationId, AccessExclusiveLock);

 /*
  * Now add any newly specified column default and generation expressions
  * to the new relation.  These are passed to us in the form of raw
  * parsetrees; we need to transform them to executable expression trees
  * before they can be added. The most convenient way to do that is to
  * apply the parser's transformExpr routine, but transformExpr doesn't
  * work unless we have a pre-existing relation. So, the transformation has
  * to be postponed to this final step of CREATE TABLE.
  *
  * This needs to be before processing the partitioning clauses because
  * those could refer to generated columns.
 */

 if (rawDefaults)
  AddRelationNewConstraints(rel, rawDefaults, NIL,
          truetruefalse, queryString);

 /*
  * Make column generation expressions visible for use by partitioning.
 */

 CommandCounterIncrement();

 /* Process and store partition bound, if any. */
 if (stmt->partbound)
 {
  PartitionBoundSpec *bound;
  ParseState *pstate;
  Oid   parentId = linitial_oid(inheritOids),
     defaultPartOid;
  Relation parent,
     defaultRel = NULL;
  ParseNamespaceItem *nsitem;

  /* Already have strong enough lock on the parent */
  parent = table_open(parentId, NoLock);

  /*
   * We are going to try to validate the partition bound specification
   * against the partition key of parentRel, so it better have one.
 */

  if (parent->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
   ereport(ERROR,
     (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
      errmsg("\"%s\" is not partitioned",
       RelationGetRelationName(parent))));

  /*
   * The partition constraint of the default partition depends on the
   * partition bounds of every other partition. It is possible that
   * another backend might be about to execute a query on the default
   * partition table, and that the query relies on previously cached
   * default partition constraints. We must therefore take a table lock
   * strong enough to prevent all queries on the default partition from
   * proceeding until we commit and send out a shared-cache-inval notice
   * that will make them update their index lists.
   *
   * Order of locking: The relation being added won't be visible to
   * other backends until it is committed, hence here in
   * DefineRelation() the order of locking the default partition and the
   * relation being added does not matter. But at all other places we
   * need to lock the default relation before we lock the relation being
   * added or removed i.e. we should take the lock in same order at all
   * the places such that lock parent, lock default partition and then
   * lock the partition so as to avoid a deadlock.
 */

  defaultPartOid =
   get_default_oid_from_partdesc(RelationGetPartitionDesc(parent,
                   true));
  if (OidIsValid(defaultPartOid))
   defaultRel = table_open(defaultPartOid, AccessExclusiveLock);

  /* Transform the bound values */
  pstate = make_parsestate(NULL);
  pstate->p_sourcetext = queryString;

  /*
   * Add an nsitem containing this relation, so that transformExpr
   * called on partition bound expressions is able to report errors
   * using a proper context.
 */

  nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock,
              NULL, falsefalse);
  addNSItemToQuery(pstate, nsitem, falsetruetrue);

  bound = transformPartitionBound(pstate, parent, stmt->partbound);

  /*
   * Check first that the new partition's bound is valid and does not
   * overlap with any of existing partitions of the parent.
 */

  check_new_partition_bound(relname, parent, bound, pstate);

  /*
   * If the default partition exists, its partition constraints will
   * change after the addition of this new partition such that it won't
   * allow any row that qualifies for this new partition. So, check that
   * the existing data in the default partition satisfies the constraint
   * as it will exist after adding this partition.
 */

  if (OidIsValid(defaultPartOid))
  {
   check_default_partition_contents(parent, defaultRel, bound);
   /* Keep the lock until commit. */
   table_close(defaultRel, NoLock);
  }

  /* Update the pg_class entry. */
  StorePartitionBound(rel, parent, bound);

  table_close(parent, NoLock);
 }

 /* Store inheritance information for new rel. */
 StoreCatalogInheritance(relationId, inheritOids, stmt->partbound != NULL);

 /*
  * Process the partitioning specification (if any) and store the partition
  * key information into the catalog.
 */

 if (partitioned)
 {
  ParseState *pstate;
  int   partnatts;
  AttrNumber partattrs[PARTITION_MAX_KEYS];
  Oid   partopclass[PARTITION_MAX_KEYS];
  Oid   partcollation[PARTITION_MAX_KEYS];
  List    *partexprs = NIL;

  pstate = make_parsestate(NULL);
  pstate->p_sourcetext = queryString;

  partnatts = list_length(stmt->partspec->partParams);

  /* Protect fixed-size arrays here and in executor */
  if (partnatts > PARTITION_MAX_KEYS)
   ereport(ERROR,
     (errcode(ERRCODE_TOO_MANY_COLUMNS),
      errmsg("cannot partition using more than %d columns",
       PARTITION_MAX_KEYS)));

  /*
   * We need to transform the raw parsetrees corresponding to partition
   * expressions into executable expression trees.  Like column defaults
   * and CHECK constraints, we could not have done the transformation
   * earlier.
 */

  stmt->partspec = transformPartitionSpec(rel, stmt->partspec);

  ComputePartitionAttrs(pstate, rel, stmt->partspec->partParams,
         partattrs, &partexprs, partopclass,
         partcollation, stmt->partspec->strategy);

  StorePartitionKey(rel, stmt->partspec->strategy, partnatts, partattrs,
        partexprs,
        partopclass, partcollation);

  /* make it all visible */
  CommandCounterIncrement();
 }

 /*
  * If we're creating a partition, create now all the indexes, triggers,
  * FKs defined in the parent.
  *
  * We can't do it earlier, because DefineIndex wants to know the partition
  * key which we just stored.
 */

 if (stmt->partbound)
 {
  Oid   parentId = linitial_oid(inheritOids);
  Relation parent;
  List    *idxlist;
  ListCell   *cell;

  /* Already have strong enough lock on the parent */
  parent = table_open(parentId, NoLock);
  idxlist = RelationGetIndexList(parent);

  /*
   * For each index in the parent table, create one in the partition
 */

  foreach(cell, idxlist)
  {
   Relation idxRel = index_open(lfirst_oid(cell), AccessShareLock);
   AttrMap    *attmap;
   IndexStmt  *idxstmt;
   Oid   constraintOid;

   if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
   {
    if (idxRel->rd_index->indisunique)
     ereport(ERROR,
       (errcode(ERRCODE_WRONG_OBJECT_TYPE),
        errmsg("cannot create foreign partition of partitioned table \"%s\"",
         RelationGetRelationName(parent)),
        errdetail("Table \"%s\" contains indexes that are unique.",
            RelationGetRelationName(parent))));
    else
    {
     index_close(idxRel, AccessShareLock);
     continue;
    }
   }

   attmap = build_attrmap_by_name(RelationGetDescr(rel),
             RelationGetDescr(parent),
             false);
   idxstmt =
    generateClonedIndexStmt(NULL, idxRel,
          attmap, &constraintOid);
   DefineIndex(RelationGetRelid(rel),
      idxstmt,
      InvalidOid,
      RelationGetRelid(idxRel),
      constraintOid,
      -1,
      falsefalsefalsefalsefalse);

   index_close(idxRel, AccessShareLock);
  }

  list_free(idxlist);

  /*
   * If there are any row-level triggers, clone them to the new
   * partition.
 */

  if (parent->trigdesc != NULL)
   CloneRowTriggersToPartition(parent, rel);

  /*
   * And foreign keys too.  Note that because we're freshly creating the
   * table, there is no need to verify these new constraints.
 */

  CloneForeignKeyConstraints(NULL, parent, rel);

  table_close(parent, NoLock);
 }

 /*
  * Now add any newly specified CHECK constraints to the new relation. Same
  * as for defaults above, but these need to come after partitioning is set
  * up.  We save the constraint names that were used, to avoid dupes below.
 */

 if (stmt->constraints)
 {
  List    *conlist;

  conlist = AddRelationNewConstraints(rel, NIL, stmt->constraints,
           truetruefalse, queryString);
  foreach_ptr(CookedConstraint, cons, conlist)
  {
   if (cons->name != NULL)
    connames = lappend(connames, cons->name);
  }
 }

 /*
  * Finally, merge the not-null constraints that are declared directly with
  * those that come from parent relations (making sure to count inheritance
  * appropriately for each), create them, and set the attnotnull flag on
  * columns that don't yet have it.
 */

 nncols = AddRelationNotNullConstraints(rel, stmt->nnconstraints,
             old_notnulls, connames);
 foreach_int(attrnum, nncols)
  set_attnotnull(NULL, rel, attrnum, truefalse);

 ObjectAddressSet(address, RelationRelationId, relationId);

 /*
  * Clean up.  We keep lock on new relation (although it shouldn't be
  * visible to anyone else anyway, until commit).
 */

 relation_close(rel, NoLock);

 return address;
}

/*
 * BuildDescForRelation
 *
 * Given a list of ColumnDef nodes, build a TupleDesc.
 *
 * Note: This is only for the limited purpose of table and view creation.  Not
 * everything is filled in.  A real tuple descriptor should be obtained from
 * the relcache.
 */

TupleDesc
BuildDescForRelation(const List *columns)
{
 int   natts;
 AttrNumber attnum;
 ListCell   *l;
 TupleDesc desc;
 char    *attname;
 Oid   atttypid;
 int32  atttypmod;
 Oid   attcollation;
 int   attdim;

 /*
  * allocate a new tuple descriptor
 */

 natts = list_length(columns);
 desc = CreateTemplateTupleDesc(natts);

 attnum = 0;

 foreach(l, columns)
 {
  ColumnDef  *entry = lfirst(l);
  AclResult aclresult;
  Form_pg_attribute att;

  /*
   * for each entry in the list, get the name and type information from
   * the list and have TupleDescInitEntry fill in the attribute
   * information we need.
 */

  attnum++;

  attname = entry->colname;
  typenameTypeIdAndMod(NULL, entry->typeName, &atttypid, &atttypmod);

  aclresult = object_aclcheck(TypeRelationId, atttypid, GetUserId(), ACL_USAGE);
  if (aclresult != ACLCHECK_OK)
   aclcheck_error_type(aclresult, atttypid);

  attcollation = GetColumnDefCollation(NULL, entry, atttypid);
  attdim = list_length(entry->typeName->arrayBounds);
  if (attdim > PG_INT16_MAX)
   ereport(ERROR,
     errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
     errmsg("too many array dimensions"));

  if (entry->typeName->setof)
   ereport(ERROR,
     (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
      errmsg("column \"%s\" cannot be declared SETOF",
       attname)));

  TupleDescInitEntry(desc, attnum, attname,
         atttypid, atttypmod, attdim);
  att = TupleDescAttr(desc, attnum - 1);

  /* Override TupleDescInitEntry's settings as requested */
  TupleDescInitEntryCollation(desc, attnum, attcollation);

  /* Fill in additional stuff not handled by TupleDescInitEntry */
  att->attnotnull = entry->is_not_null;
  att->attislocal = entry->is_local;
  att->attinhcount = entry->inhcount;
  att->attidentity = entry->identity;
  att->attgenerated = entry->generated;
  att->attcompression = GetAttributeCompression(att->atttypid, entry->compression);
  if (entry->storage)
   att->attstorage = entry->storage;
  else if (entry->storage_name)
   att->attstorage = GetAttributeStorage(att->atttypid, entry->storage_name);

  populate_compact_attribute(desc, attnum - 1);
 }

 return desc;
}

/*
 * Emit the right error or warning message for a "DROP" command issued on a
 * non-existent relation
 */

static void
DropErrorMsgNonExistent(RangeVar *rel, char rightkind, bool missing_ok)
{
 const struct dropmsgstrings *rentry;

 if (rel->schemaname != NULL &&
  !OidIsValid(LookupNamespaceNoError(rel->schemaname)))
 {
  if (!missing_ok)
  {
   ereport(ERROR,
     (errcode(ERRCODE_UNDEFINED_SCHEMA),
      errmsg("schema \"%s\" does not exist", rel->schemaname)));
  }
  else
  {
   ereport(NOTICE,
     (errmsg("schema \"%s\" does not exist, skipping",
       rel->schemaname)));
  }
  return;
 }

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

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

/*
 * Emit the right error message for a "DROP" command issued on a
 * relation of the wrong type
 */

static void
DropErrorMsgWrongType(const char *relname, char wrongkind, char rightkind)
{
 const struct dropmsgstrings *rentry;
 const struct dropmsgstrings *wentry;

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

 for (wentry = dropmsgstringarray; wentry->kind != '\0'; wentry++)
  if (wentry->kind == wrongkind)
   break;
 /* wrongkind could be something we don't have in our table... */

 ereport(ERROR,
   (errcode(ERRCODE_WRONG_OBJECT_TYPE),
    errmsg(rentry->nota_msg, relname),
    (wentry->kind != '\0') ? errhint("%s", _(wentry->drophint_msg)) : 0));
}

/*
 * RemoveRelations
 *  Implements DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW,
 *  DROP MATERIALIZED VIEW, DROP FOREIGN TABLE
 */

void
RemoveRelations(DropStmt *drop)
{
 ObjectAddresses *objects;
 char  relkind;
 ListCell   *cell;
 int   flags = 0;
 LOCKMODE lockmode = AccessExclusiveLock;

 /* DROP CONCURRENTLY uses a weaker lock, and has some restrictions */
 if (drop->concurrent)
 {
  /*
   * Note that for temporary relations this lock may get upgraded later
   * on, but as no other session can access a temporary relation, this
   * is actually fine.
 */

  lockmode = ShareUpdateExclusiveLock;
  Assert(drop->removeType == OBJECT_INDEX);
  if (list_length(drop->objects) != 1)
   ereport(ERROR,
     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
      errmsg("DROP INDEX CONCURRENTLY does not support dropping multiple objects")));
  if (drop->behavior == DROP_CASCADE)
   ereport(ERROR,
     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
      errmsg("DROP INDEX CONCURRENTLY does not support CASCADE")));
 }

 /*
  * First we identify all the relations, then we delete them in a single
  * performMultipleDeletions() call.  This is to avoid unwanted DROP
  * RESTRICT errors if one of the relations depends on another.
 */


 /* Determine required relkind */
 switch (drop->removeType)
 {
  case OBJECT_TABLE:
   relkind = RELKIND_RELATION;
   break;

  case OBJECT_INDEX:
   relkind = RELKIND_INDEX;
   break;

  case OBJECT_SEQUENCE:
   relkind = RELKIND_SEQUENCE;
   break;

  case OBJECT_VIEW:
   relkind = RELKIND_VIEW;
   break;

  case OBJECT_MATVIEW:
   relkind = RELKIND_MATVIEW;
   break;

  case OBJECT_FOREIGN_TABLE:
   relkind = RELKIND_FOREIGN_TABLE;
   break;

  default:
   elog(ERROR, "unrecognized drop object type: %d",
     (int) drop->removeType);
   relkind = 0;  /* keep compiler quiet */
   break;
 }

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

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

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

  AcceptInvalidationMessages();

  /* Look up the appropriate relation using namespace search. */
  state.expected_relkind = relkind;
  state.heap_lockmode = drop->concurrent ?
   ShareUpdateExclusiveLock : AccessExclusiveLock;
  /* We must initialize these fields to show that no locks are held: */
  state.heapOid = InvalidOid;
  state.partParentOid = InvalidOid;

  relOid = RangeVarGetRelidExtended(rel, lockmode, RVR_MISSING_OK,
            RangeVarCallbackForDropRelation,
            &state);

  /* Not there? */
  if (!OidIsValid(relOid))
  {
   DropErrorMsgNonExistent(rel, relkind, drop->missing_ok);
   continue;
  }

  /*
   * Decide if concurrent mode needs to be used here or not.  The
   * callback retrieved the rel's persistence for us.
 */

  if (drop->concurrent &&
   state.actual_relpersistence != RELPERSISTENCE_TEMP)
  {
   Assert(list_length(drop->objects) == 1 &&
       drop->removeType == OBJECT_INDEX);
   flags |= PERFORM_DELETION_CONCURRENTLY;
  }

  /*
   * Concurrent index drop cannot be used with partitioned indexes,
   * either.
 */

  if ((flags & PERFORM_DELETION_CONCURRENTLY) != 0 &&
   state.actual_relkind == RELKIND_PARTITIONED_INDEX)
   ereport(ERROR,
     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
      errmsg("cannot drop partitioned index \"%s\" concurrently",
       rel->relname)));

  /*
   * If we're told to drop a partitioned index, we must acquire lock on
   * all the children of its parent partitioned table before proceeding.
   * Otherwise we'd try to lock the child index partitions before their
   * tables, leading to potential deadlock against other sessions that
   * will lock those objects in the other order.
 */

  if (state.actual_relkind == RELKIND_PARTITIONED_INDEX)
   (void) find_all_inheritors(state.heapOid,
            state.heap_lockmode,
            NULL);

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

  add_exact_object_address(&obj, objects);
 }

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

 free_object_addresses(objects);
}

/*
 * Before acquiring a table lock, check whether we have sufficient rights.
 * In the case of DROP INDEX, also try to lock the table before the index.
 * Also, if the table to be dropped is a partition, we try to lock the parent
 * first.
 */

static void
RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
        void *arg)
{
 HeapTuple tuple;
 struct DropRelationCallbackState *state;
 char  expected_relkind;
 bool  is_partition;
 Form_pg_class classform;
 LOCKMODE heap_lockmode;
 bool  invalid_system_index = false;

 state = (struct DropRelationCallbackState *) arg;
 heap_lockmode = state->heap_lockmode;

 /*
  * If we previously locked some other index's heap, and the name we're
  * looking up no longer refers to that relation, release the now-useless
  * lock.
 */

 if (relOid != oldRelOid && OidIsValid(state->heapOid))
 {
  UnlockRelationOid(state->heapOid, heap_lockmode);
  state->heapOid = InvalidOid;
 }

 /*
  * Similarly, if we previously locked some other partition's heap, and the
  * name we're looking up no longer refers to that relation, release the
  * now-useless lock.
 */

 if (relOid != oldRelOid && OidIsValid(state->partParentOid))
 {
  UnlockRelationOid(state->partParentOid, AccessExclusiveLock);
  state->partParentOid = InvalidOid;
 }

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

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

 /* Pass back some data to save lookups in RemoveRelations */
 state->actual_relkind = classform->relkind;
 state->actual_relpersistence = classform->relpersistence;

 /*
  * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
  * but RemoveRelations() can only pass one relkind for a given relation.
  * It chooses RELKIND_RELATION for both regular and partitioned tables.
  * That means we must be careful before giving the wrong type error when
  * the relation is RELKIND_PARTITIONED_TABLE.  An equivalent problem
  * exists with indexes.
 */

 if (classform->relkind == RELKIND_PARTITIONED_TABLE)
  expected_relkind = RELKIND_RELATION;
 else if (classform->relkind == RELKIND_PARTITIONED_INDEX)
  expected_relkind = RELKIND_INDEX;
 else
  expected_relkind = classform->relkind;

 if (state->expected_relkind != expected_relkind)
  DropErrorMsgWrongType(rel->relname, classform->relkind,
         state->expected_relkind);

 /* Allow DROP to either table owner or schema owner */
 if (!object_ownercheck(RelationRelationId, relOid, GetUserId()) &&
  !object_ownercheck(NamespaceRelationId, classform->relnamespace, GetUserId()))
  aclcheck_error(ACLCHECK_NOT_OWNER,
        get_relkind_objtype(classform->relkind),
        rel->relname);

 /*
  * Check the case of a system index that might have been invalidated by a
  * failed concurrent process and allow its drop. For the time being, this
  * only concerns indexes of toast relations that became invalid during a
  * REINDEX CONCURRENTLY process.
 */

 if (IsSystemClass(relOid, classform) && classform->relkind == RELKIND_INDEX)
 {
  HeapTuple locTuple;
  Form_pg_index indexform;
  bool  indisvalid;

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

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

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

 /* In the case of an invalid index, it is fine to bypass this check */
 if (!invalid_system_index && !allowSystemTableMods && IsSystemClass(relOid, classform))
  ereport(ERROR,
    (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     errmsg("permission denied: \"%s\" is a system catalog",
      rel->relname)));

 ReleaseSysCache(tuple);

 /*
  * In DROP INDEX, attempt to acquire lock on the parent table before
  * locking the index.  index_drop() will need this anyway, and since
  * regular queries lock tables before their indexes, we risk deadlock if
  * we do it the other way around.  No error if we don't find a pg_index
  * entry, though --- the relation may have been dropped.  Note that this
  * code will execute for either plain or partitioned indexes.
 */

 if (expected_relkind == RELKIND_INDEX &&
  relOid != oldRelOid)
 {
  state->heapOid = IndexGetRelation(relOid, true);
  if (OidIsValid(state->heapOid))
   LockRelationOid(state->heapOid, heap_lockmode);
 }

 /*
  * Similarly, if the relation is a partition, we must acquire lock on its
  * parent before locking the partition.  That's because queries lock the
  * parent before its partitions, so we risk deadlock if we do it the other
  * way around.
 */

 if (is_partition && relOid != oldRelOid)
 {
  state->partParentOid = get_partition_parent(relOid, true);
  if (OidIsValid(state->partParentOid))
   LockRelationOid(state->partParentOid, AccessExclusiveLock);
 }
}

/*
 * ExecuteTruncate
 *  Executes a TRUNCATE command.
 *
 * This is a multi-relation truncate.  We first open and grab exclusive
 * lock on all relations involved, checking permissions and otherwise
 * verifying that the relation is OK for truncation.  Note that if relations
 * are foreign tables, at this stage, we have not yet checked that their
 * foreign data in external data sources are OK for truncation.  These are
 * checked when foreign data are actually truncated later.  In CASCADE mode,
 * relations having FK references to the targeted relations are automatically
 * added to the group; in RESTRICT mode, we check that all FK references are
 * internal to the group that's being truncated.  Finally all the relations
 * are truncated and reindexed.
 */

void
ExecuteTruncate(TruncateStmt *stmt)
{
 List    *rels = NIL;
 List    *relids = NIL;
 List    *relids_logged = NIL;
 ListCell   *cell;

 /*
  * Open, exclusive-lock, and check all the explicitly-specified relations
 */

 foreach(cell, stmt->relations)
 {
  RangeVar   *rv = lfirst(cell);
  Relation rel;
  bool  recurse = rv->inh;
  Oid   myrelid;
  LOCKMODE lockmode = AccessExclusiveLock;

  myrelid = RangeVarGetRelidExtended(rv, lockmode,
             0, RangeVarCallbackForTruncate,
             NULL);

  /* don't throw error for "TRUNCATE foo, foo" */
  if (list_member_oid(relids, myrelid))
   continue;

  /* open the relation, we already hold a lock on it */
  rel = table_open(myrelid, NoLock);

  /*
   * RangeVarGetRelidExtended() has done most checks with its callback,
   * but other checks with the now-opened Relation remain.
 */

  truncate_check_activity(rel);

  rels = lappend(rels, rel);
  relids = lappend_oid(relids, myrelid);

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

  if (recurse)
  {
   ListCell   *child;
   List    *children;

   children = find_all_inheritors(myrelid, lockmode, NULL);

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

    if (list_member_oid(relids, childrelid))
     continue;

    /* find_all_inheritors already got lock */
    rel = table_open(childrelid, NoLock);

    /*
     * It is possible that the parent table has children that are
     * temp tables of other backends.  We cannot safely access
     * such tables (because of buffering issues), and the best
     * thing to do is to silently ignore them.  Note that this
     * check is the same as one of the checks done in
     * truncate_check_activity() called below, still it is kept
     * here for simplicity.
 */

    if (RELATION_IS_OTHER_TEMP(rel))
    {
     table_close(rel, lockmode);
     continue;
    }

    /*
     * Inherited TRUNCATE commands perform access permission
     * checks on the parent table only. So we skip checking the
     * children's permissions and don't call
     * truncate_check_perms() here.
 */

    truncate_check_rel(RelationGetRelid(rel), rel->rd_rel);
    truncate_check_activity(rel);

    rels = lappend(rels, rel);
    relids = lappend_oid(relids, childrelid);

    /* Log this relation only if needed for logical decoding */
    if (RelationIsLogicallyLogged(rel))
     relids_logged = lappend_oid(relids_logged, childrelid);
   }
  }
  else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
   ereport(ERROR,
     (errcode(ERRCODE_WRONG_OBJECT_TYPE),
      errmsg("cannot truncate only a partitioned table"),
      errhint("Do not specify the ONLY keyword, or use TRUNCATE ONLY on the partitions directly.")));
 }

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

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

  table_close(rel, NoLock);
 }
}

/*
 * ExecuteTruncateGuts
 *
 * Internal implementation of TRUNCATE.  This is called by the actual TRUNCATE
 * command (see above) as well as replication subscribers that execute a
 * replicated TRUNCATE action.
 *
 * explicit_rels is the list of Relations to truncate that the command
 * specified.  relids is the list of Oids corresponding to explicit_rels.
 * relids_logged is the list of Oids (a subset of relids) that require
 * WAL-logging.  This is all a bit redundant, but the existing callers have
 * this information handy in this form.
 */

void
ExecuteTruncateGuts(List *explicit_rels,
     List *relids,
     List *relids_logged,
     DropBehavior behavior, bool restart_seqs,
     bool run_as_table_owner)
{
 List    *rels;
 List    *seq_relids = NIL;
 HTAB    *ft_htab = NULL;
 EState    *estate;
 ResultRelInfo *resultRelInfos;
 ResultRelInfo *resultRelInfo;
 SubTransactionId mySubid;
 ListCell   *cell;
 Oid     *logrelids;

 /*
  * Check the explicitly-specified relations.
  *
  * In CASCADE mode, suck in all referencing relations as well.  This
  * requires multiple iterations to find indirectly-dependent relations. At
  * each phase, we need to exclusive-lock new rels before looking for their
  * dependencies, else we might miss something.  Also, we check each rel as
  * soon as we open it, to avoid a faux pas such as holding lock for a long
  * time on a rel we have no permissions for.
 */

 rels = list_copy(explicit_rels);
 if (behavior == DROP_CASCADE)
 {
  for (;;)
  {
   List    *newrelids;

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

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

    rel = table_open(relid, AccessExclusiveLock);
    ereport(NOTICE,
      (errmsg("truncate cascades to table \"%s\"",
        RelationGetRelationName(rel))));
    truncate_check_rel(relid, rel->rd_rel);
    truncate_check_perms(relid, rel->rd_rel);
    truncate_check_activity(rel);
    rels = lappend(rels, rel);
    relids = lappend_oid(relids, relid);

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

 /*
  * Check foreign key references.  In CASCADE mode, this should be
  * unnecessary since we just pulled in all the references; but as a
  * cross-check, do it anyway if in an Assert-enabled build.
 */

#ifdef USE_ASSERT_CHECKING
 heap_truncate_check_FKs(rels, false);
#else
 if (behavior == DROP_RESTRICT)
  heap_truncate_check_FKs(rels, false);
#endif

 /*
  * If we are asked to restart sequences, find all the sequences, lock them
  * (we need AccessExclusiveLock for ResetSequence), and check permissions.
  * We want to do this early since it's pointless to do all the truncation
  * work only to fail on sequence permissions.
 */

 if (restart_seqs)
 {
  foreach(cell, rels)
  {
   Relation rel = (Relation) lfirst(cell);
   List    *seqlist = getOwnedSequences(RelationGetRelid(rel));
   ListCell   *seqcell;

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

    seq_rel = relation_open(seq_relid, AccessExclusiveLock);

    /* This check must match AlterSequence! */
    if (!object_ownercheck(RelationRelationId, seq_relid, GetUserId()))
     aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SEQUENCE,
           RelationGetRelationName(seq_rel));

    seq_relids = lappend_oid(seq_relids, seq_relid);

    relation_close(seq_rel, NoLock);
   }
  }
 }

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

 /*
  * To fire triggers, we'll need an EState as well as a ResultRelInfo for
  * each relation.  We don't need to call ExecOpenIndices, though.
  *
  * We put the ResultRelInfos in the es_opened_result_relations list, even
  * though we don't have a range table and don't populate the
  * es_result_relations array.  That's a bit bogus, but it's enough to make
  * ExecGetTriggerResultRel() find them.
 */

 estate = CreateExecutorState();
 resultRelInfos = (ResultRelInfo *)
  palloc(list_length(rels) * sizeof(ResultRelInfo));
 resultRelInfo = resultRelInfos;
 foreach(cell, rels)
 {
  Relation rel = (Relation) lfirst(cell);

  InitResultRelInfo(resultRelInfo,
        rel,
        0/* dummy rangetable index */
        NULL,
        0);
  estate->es_opened_result_relations =
   lappend(estate->es_opened_result_relations, resultRelInfo);
  resultRelInfo++;
 }

 /*
  * Process all BEFORE STATEMENT TRUNCATE triggers before we begin
  * truncating (this is because one of them might throw an error). Also, if
  * we were to allow them to prevent statement execution, that would need
  * to be handled here.
 */

 resultRelInfo = resultRelInfos;
 foreach(cell, rels)
 {
  UserContext ucxt;

  if (run_as_table_owner)
   SwitchToUntrustedUser(resultRelInfo->ri_RelationDesc->rd_rel->relowner,
          &ucxt);
  ExecBSTruncateTriggers(estate, resultRelInfo);
  if (run_as_table_owner)
   RestoreUserContext(&ucxt);
  resultRelInfo++;
 }

 /*
  * OK, truncate each table.
 */

 mySubid = GetCurrentSubTransactionId();

 foreach(cell, rels)
 {
  Relation rel = (Relation) lfirst(cell);

  /* Skip partitioned tables as there is nothing to do */
  if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
   continue;

  /*
   * Build the lists of foreign tables belonging to each foreign server
   * and pass each list to the foreign data wrapper's callback function,
   * so that each server can truncate its all foreign tables in bulk.
   * Each list is saved as a single entry in a hash table that uses the
   * server OID as lookup key.
 */

  if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
  {
   Oid   serverid = GetForeignServerIdByRelId(RelationGetRelid(rel));
   bool  found;
   ForeignTruncateInfo *ft_info;

   /* First time through, initialize hashtable for foreign tables */
   if (!ft_htab)
   {
    HASHCTL  hctl;

    memset(&hctl, 0sizeof(HASHCTL));
    hctl.keysize = sizeof(Oid);
    hctl.entrysize = sizeof(ForeignTruncateInfo);
    hctl.hcxt = CurrentMemoryContext;

    ft_htab = hash_create("TRUNCATE for Foreign Tables",
           32/* start small and extend */
           &hctl,
           HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
   }

   /* Find or create cached entry for the foreign table */
   ft_info = hash_search(ft_htab, &serverid, HASH_ENTER, &found);
   if (!found)
    ft_info->rels = NIL;

   /*
    * Save the foreign table in the entry of the server that the
    * foreign table belongs to.
 */

   ft_info->rels = lappend(ft_info->rels, rel);
   continue;
  }

  /*
   * Normally, we need a transaction-safe truncation here.  However, if
   * the table was either created in the current (sub)transaction or has
   * a new relfilenumber in the current (sub)transaction, then we can
   * just truncate it in-place, because a rollback would cause the whole
   * table or the current physical file to be thrown away anyway.
 */

  if (rel->rd_createSubid == mySubid ||
   rel->rd_newRelfilelocatorSubid == mySubid)
  {
   /* Immediate, non-rollbackable truncation is OK */
   heap_truncate_one_rel(rel);
  }
  else
  {
   Oid   heap_relid;
   Oid   toast_relid;
   ReindexParams reindex_params = {0};

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

   /*
    * Need the full transaction-safe pushups.
    *
    * Create a new empty storage file for the relation, and assign it
    * as the relfilenumber value. The old storage file is scheduled
    * for deletion at commit.
 */

   RelationSetNewRelfilenumber(rel, rel->rd_rel->relpersistence);

   heap_relid = RelationGetRelid(rel);

   /*
    * The same for the toast table, if any.
 */

   toast_relid = rel->rd_rel->reltoastrelid;
   if (OidIsValid(toast_relid))
   {
    Relation toastrel = relation_open(toast_relid,
              AccessExclusiveLock);

    RelationSetNewRelfilenumber(toastrel,
           toastrel->rd_rel->relpersistence);
    table_close(toastrel, NoLock);
   }

   /*
    * Reconstruct the indexes to match, and we're done.
 */

   reindex_relation(NULL, heap_relid, REINDEX_REL_PROCESS_TOAST,
        &reindex_params);
  }

  pgstat_count_truncate(rel);
 }

 /* Now go through the hash table, and truncate foreign tables */
 if (ft_htab)
 {
  ForeignTruncateInfo *ft_info;
  HASH_SEQ_STATUS seq;

  hash_seq_init(&seq, ft_htab);

  PG_TRY();
  {
   while ((ft_info = hash_seq_search(&seq)) != NULL)
   {
    FdwRoutine *routine = GetFdwRoutineByServerId(ft_info->serverid);

    /* truncate_check_rel() has checked that already */
    Assert(routine->ExecForeignTruncate != NULL);

    routine->ExecForeignTruncate(ft_info->rels,
            behavior,
            restart_seqs);
   }
  }
  PG_FINALLY();
  {
   hash_destroy(ft_htab);
  }
  PG_END_TRY();
 }

 /*
  * Restart owned sequences if we were asked to.
 */

 foreach(cell, seq_relids)
 {
  Oid   seq_relid = lfirst_oid(cell);

  ResetSequence(seq_relid);
 }

 /*
  * Write a WAL record to allow this set of actions to be logically
  * decoded.
  *
  * Assemble an array of relids so we can write a single WAL record for the
  * whole action.
 */

 if (relids_logged != NIL)
 {
  xl_heap_truncate xlrec;
  int   i = 0;

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

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

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

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

  XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);

  (void) XLogInsert(RM_HEAP_ID, XLOG_HEAP_TRUNCATE);
 }

 /*
  * Process all AFTER STATEMENT TRUNCATE triggers.
 */

 resultRelInfo = resultRelInfos;
 foreach(cell, rels)
 {
  UserContext ucxt;

  if (run_as_table_owner)
   SwitchToUntrustedUser(resultRelInfo->ri_RelationDesc->rd_rel->relowner,
          &ucxt);
  ExecASTruncateTriggers(estate, resultRelInfo);
  if (run_as_table_owner)
   RestoreUserContext(&ucxt);
  resultRelInfo++;
 }

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

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

 /*
  * Close any rels opened by CASCADE (can't do this while EState still
  * holds refs)
 */

 rels = list_difference_ptr(rels, explicit_rels);
 foreach(cell, rels)
 {
  Relation rel = (Relation) lfirst(cell);

  table_close(rel, NoLock);
 }
}

/*
 * Check that a given relation is safe to truncate.  Subroutine for
 * ExecuteTruncate() and RangeVarCallbackForTruncate().
 */

static void
truncate_check_rel(Oid relid, Form_pg_class reltuple)
{
 char    *relname = NameStr(reltuple->relname);

 /*
  * Only allow truncate on regular tables, foreign tables using foreign
  * data wrappers supporting TRUNCATE and partitioned tables (although, the
  * latter are only being included here for the following checks; no
  * physical truncation will occur in their case.).
 */

 if (reltuple->relkind == RELKIND_FOREIGN_TABLE)
 {
  Oid   serverid = GetForeignServerIdByRelId(relid);
  FdwRoutine *fdwroutine = GetFdwRoutineByServerId(serverid);

  if (!fdwroutine->ExecForeignTruncate)
   ereport(ERROR,
     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
      errmsg("cannot truncate foreign table \"%s\"",
       relname)));
 }
 else if (reltuple->relkind != RELKIND_RELATION &&
    reltuple->relkind != RELKIND_PARTITIONED_TABLE)
  ereport(ERROR,
    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     errmsg("\"%s\" is not a table", relname)));

 /*
  * Most system catalogs can't be truncated at all, or at least not unless
  * allow_system_table_mods=on. As an exception, however, we allow
  * pg_largeobject to be truncated as part of pg_upgrade, because we need
  * to change its relfilenode to match the old cluster, and allowing a
  * TRUNCATE command to be executed is the easiest way of doing that.
 */

 if (!allowSystemTableMods && IsSystemClass(relid, reltuple)
  && (!IsBinaryUpgrade || relid != LargeObjectRelationId))
  ereport(ERROR,
    (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     errmsg("permission denied: \"%s\" is a system catalog",
      relname)));

 InvokeObjectTruncateHook(relid);
}

/*
 * Check that current user has the permission to truncate given relation.
 */

static void
truncate_check_perms(Oid relid, Form_pg_class reltuple)
{
 char    *relname = NameStr(reltuple->relname);
 AclResult aclresult;

 /* Permissions checks */
 aclresult = pg_class_aclcheck(relid, GetUserId(), ACL_TRUNCATE);
 if (aclresult != ACLCHECK_OK)
  aclcheck_error(aclresult, get_relkind_objtype(reltuple->relkind),
        relname);
}

/*
 * Set of extra sanity checks to check if a given relation is safe to
 * truncate.  This is split with truncate_check_rel() as
 * RangeVarCallbackForTruncate() cannot open a Relation yet.
 */

static void
truncate_check_activity(Relation rel)
{
 /*
  * Don't allow truncate on temp tables of other backends ... their local
  * buffer manager is not going to cope.
 */

 if (RELATION_IS_OTHER_TEMP(rel))
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("cannot truncate temporary tables of other sessions")));

 /*
  * Also check for active uses of the relation in the current transaction,
  * including open scans and pending AFTER trigger events.
 */

 CheckTableNotInUse(rel, "TRUNCATE");
}

/*
 * storage_name
 *   returns the name corresponding to a typstorage/attstorage enum value
 */

static const char *
storage_name(char c)
{
 switch (c)
 {
  case TYPSTORAGE_PLAIN:
   return "PLAIN";
  case TYPSTORAGE_EXTERNAL:
   return "EXTERNAL";
  case TYPSTORAGE_EXTENDED:
   return "EXTENDED";
  case TYPSTORAGE_MAIN:
   return "MAIN";
  default:
   return "???";
 }
}

/*----------
 * MergeAttributes
 *  Returns new schema given initial schema and superclasses.
 *
 * Input arguments:
 * 'columns' is the column/attribute definition for the table. (It's a list
 *  of ColumnDef's.) It is destructively changed.
 * 'supers' is a list of OIDs of parent relations, already locked by caller.
 * 'relpersistence' is the persistence type of the table.
 * 'is_partition' tells if the table is a partition.
 *
 * Output arguments:
 * 'supconstr' receives a list of CookedConstraint representing
 *  CHECK constraints belonging to parent relations, updated as
 *  necessary to be valid for the child.
 * 'supnotnulls' receives a list of CookedConstraint representing
 *  not-null constraints based on those from parent relations.
 *
 * Return value:
 * Completed schema list.
 *
 * Notes:
 *   The order in which the attributes are inherited is very important.
 *   Intuitively, the inherited attributes should come first. If a table
 *   inherits from multiple parents, the order of those attributes are
 *   according to the order of the parents specified in CREATE TABLE.
 *
 *   Here's an example:
 *
 *  create table person (name text, age int4, location point);
 *  create table emp (salary int4, manager text) inherits(person);
 *  create table student (gpa float8) inherits (person);
 *  create table stud_emp (percent int4) inherits (emp, student);
 *
 *   The order of the attributes of stud_emp is:
 *
 *       person {1:name, 2:age, 3:location}
 *       /  \
 *      {6:gpa} student   emp {4:salary, 5:manager}
 *       \  /
 *         stud_emp {7:percent}
 *
 *    If the same attribute name appears multiple times, then it appears
 *    in the result table in the proper location for its first appearance.
 *
 *    Constraints (including not-null constraints) for the child table
 *    are the union of all relevant constraints, from both the child schema
 *    and parent tables.  In addition, in legacy inheritance, each column that
 *    appears in a primary key in any of the parents also gets a NOT NULL
 *    constraint (partitioning doesn't need this, because the PK itself gets
 *    inherited.)
 *
 *    The default value for a child column is defined as:
 *  (1) If the child schema specifies a default, that value is used.
 *  (2) If neither the child nor any parent specifies a default, then
 *   the column will not have a default.
 *  (3) If conflicting defaults are inherited from different parents
 *   (and not overridden by the child), an error is raised.
 *  (4) Otherwise the inherited default is used.
 *
 *  Note that the default-value infrastructure is used for generated
 *  columns' expressions too, so most of the preceding paragraph applies
 *  to generation expressions too.  We insist that a child column be
 *  generated if and only if its parent(s) are, but it need not have
 *  the same generation expression.
 *----------
 */

static List *
MergeAttributes(List *columns, const List *supers, char relpersistence,
    bool is_partition, List **supconstr, List **supnotnulls)
{
 List    *inh_columns = NIL;
 List    *constraints = NIL;
 List    *nnconstraints = NIL;
 bool  have_bogus_defaults = false;
 int   child_attno;
 static Node bogus_marker = {0}; /* marks conflicting defaults */
 List    *saved_columns = NIL;
 ListCell   *lc;

 /*
  * Check for and reject tables with too many columns. We perform this
  * check relatively early for two reasons: (a) we don't run the risk of
  * overflowing an AttrNumber in subsequent code (b) an O(n^2) algorithm is
  * okay if we're processing <= 1600 columns, but could take minutes to
  * execute if the user attempts to create a table with hundreds of
  * thousands of columns.
  *
  * Note that we also need to check that we do not exceed this figure after
  * including columns from inherited relations.
 */

 if (list_length(columns) > MaxHeapAttributeNumber)
  ereport(ERROR,
    (errcode(ERRCODE_TOO_MANY_COLUMNS),
     errmsg("tables can have at most %d columns",
      MaxHeapAttributeNumber)));

 /*
  * Check for duplicate names in the explicit list of attributes.
  *
  * Although we might consider merging such entries in the same way that we
  * handle name conflicts for inherited attributes, it seems to make more
  * sense to assume such conflicts are errors.
  *
  * We don't use foreach() here because we have two nested loops over the
  * columns list, with possible element deletions in the inner one.  If we
  * used foreach_delete_current() it could only fix up the state of one of
  * the loops, so it seems cleaner to use looping over list indexes for
  * both loops.  Note that any deletion will happen beyond where the outer
  * loop is, so its index never needs adjustment.
 */

 for (int coldefpos = 0; coldefpos < list_length(columns); coldefpos++)
 {
  ColumnDef  *coldef = list_nth_node(ColumnDef, columns, coldefpos);

  if (!is_partition && coldef->typeName == NULL)
  {
   /*
    * Typed table column option that does not belong to a column from
    * the type.  This works because the columns from the type come
    * first in the list.  (We omit this check for partition column
    * lists; those are processed separately below.)
 */

   ereport(ERROR,
     (errcode(ERRCODE_UNDEFINED_COLUMN),
      errmsg("column \"%s\" does not exist",
       coldef->colname)));
  }

  /* restpos scans all entries beyond coldef; incr is in loop body */
  for (int restpos = coldefpos + 1; restpos < list_length(columns);)
  {
   ColumnDef  *restdef = list_nth_node(ColumnDef, columns, restpos);

   if (strcmp(coldef->colname, restdef->colname) == 0)
   {
    if (coldef->is_from_type)
    {
     /*
      * merge the column options into the column from the type
 */

     coldef->is_not_null = restdef->is_not_null;
     coldef->raw_default = restdef->raw_default;
     coldef->cooked_default = restdef->cooked_default;
     coldef->constraints = restdef->constraints;
     coldef->is_from_type = false;
     columns = list_delete_nth_cell(columns, restpos);
    }
    else
     ereport(ERROR,
       (errcode(ERRCODE_DUPLICATE_COLUMN),
        errmsg("column \"%s\" specified more than once",
         coldef->colname)));
   }
   else
    restpos++;
  }
 }

 /*
  * In case of a partition, there are no new column definitions, only dummy
  * ColumnDefs created for column constraints.  Set them aside for now and
  * process them at the end.
 */

 if (is_partition)
 {
  saved_columns = columns;
  columns = NIL;
 }

 /*
  * Scan the parents left-to-right, and merge their attributes to form a
  * list of inherited columns (inh_columns).
 */

 child_attno = 0;
 foreach(lc, supers)
 {
  Oid   parent = lfirst_oid(lc);
  Relation relation;
  TupleDesc tupleDesc;
  TupleConstr *constr;
  AttrMap    *newattmap;
  List    *inherited_defaults;
  List    *cols_with_defaults;
  List    *nnconstrs;
  ListCell   *lc1;
  ListCell   *lc2;
  Bitmapset  *nncols = NULL;

  /* caller already got lock */
  relation = table_open(parent, NoLock);

  /*
   * Check for active uses of the parent partitioned table in the
   * current transaction, such as being used in some manner by an
   * enclosing command.
 */

  if (is_partition)
   CheckTableNotInUse(relation, "CREATE TABLE .. PARTITION OF");

  /*
   * We do not allow partitioned tables and partitions to participate in
   * regular inheritance.
 */

  if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !is_partition)
   ereport(ERROR,
     (errcode(ERRCODE_WRONG_OBJECT_TYPE),
      errmsg("cannot inherit from partitioned table \"%s\"",
       RelationGetRelationName(relation))));
  if (relation->rd_rel->relispartition && !is_partition)
   ereport(ERROR,
     (errcode(ERRCODE_WRONG_OBJECT_TYPE),
      errmsg("cannot inherit from partition \"%s\"",
       RelationGetRelationName(relation))));

  if (relation->rd_rel->relkind != RELKIND_RELATION &&
   relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
   relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
   ereport(ERROR,
     (errcode(ERRCODE_WRONG_OBJECT_TYPE),
      errmsg("inherited relation \"%s\" is not a table or foreign table",
       RelationGetRelationName(relation))));

  /*
   * If the parent is permanent, so must be all of its partitions.  Note
   * that inheritance allows that case.
 */

  if (is_partition &&
   relation->rd_rel->relpersistence != RELPERSISTENCE_TEMP &&
   relpersistence == RELPERSISTENCE_TEMP)
   ereport(ERROR,
     (errcode(ERRCODE_WRONG_OBJECT_TYPE),
      errmsg("cannot create a temporary relation as partition of permanent relation \"%s\"",
       RelationGetRelationName(relation))));

  /* Permanent rels cannot inherit from temporary ones */
  if (relpersistence != RELPERSISTENCE_TEMP &&
   relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
   ereport(ERROR,
     (errcode(ERRCODE_WRONG_OBJECT_TYPE),
      errmsg(!is_partition
       ? "cannot inherit from temporary relation \"%s\""
       : "cannot create a permanent relation as partition of temporary relation \"%s\"",
       RelationGetRelationName(relation))));

  /* If existing rel is temp, it must belong to this session */
  if (relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
   !relation->rd_islocaltemp)
   ereport(ERROR,
     (errcode(ERRCODE_WRONG_OBJECT_TYPE),
      errmsg(!is_partition
       ? "cannot inherit from temporary relation of another session"
       : "cannot create as partition of temporary relation of another session")));

  /*
   * We should have an UNDER permission flag for this, but for now,
   * demand that creator of a child table own the parent.
 */

  if (!object_ownercheck(RelationRelationId, RelationGetRelid(relation), GetUserId()))
   aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(relation->rd_rel->relkind),
         RelationGetRelationName(relation));

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

  /*
   * newattmap->attnums[] will contain the child-table attribute numbers
   * for the attributes of this parent table.  (They are not the same
   * for parents after the first one, nor if we have dropped columns.)
 */

  newattmap = make_attrmap(tupleDesc->natts);

  /* We can't process inherited defaults until newattmap is complete. */
  inherited_defaults = cols_with_defaults = NIL;

  /*
   * Request attnotnull on columns that have a not-null constraint
   * that's not marked NO INHERIT (even if not valid).
 */

  nnconstrs = RelationGetNotNullConstraints(RelationGetRelid(relation),
              truefalse);
  foreach_ptr(CookedConstraint, cc, nnconstrs)
   nncols = bms_add_member(nncols, cc->attnum);

  for (AttrNumber parent_attno = 1; parent_attno <= tupleDesc->natts;
    parent_attno++)
  {
   Form_pg_attribute attribute = TupleDescAttr(tupleDesc,
              parent_attno - 1);
   char    *attributeName = NameStr(attribute->attname);
   int   exist_attno;
   ColumnDef  *newdef;
   ColumnDef  *mergeddef;

   /*
    * Ignore dropped columns in the parent.
 */

   if (attribute->attisdropped)
    continue;  /* leave newattmap->attnums entry as zero */

   /*
    * Create new column definition
 */

   newdef = makeColumnDef(attributeName, attribute->atttypid,
           attribute->atttypmod, attribute->attcollation);
   newdef->storage = attribute->attstorage;
   newdef->generated = attribute->attgenerated;
   if (CompressionMethodIsValid(attribute->attcompression))
    newdef->compression =
     pstrdup(GetCompressionMethodName(attribute->attcompression));

   /*
    * Regular inheritance children are independent enough not to
    * inherit identity columns.  But partitions are integral part of
    * a partitioned table and inherit identity column.
 */

   if (is_partition)
    newdef->identity = attribute->attidentity;

   /*
    * Does it match some previously considered column from another
    * parent?
 */

   exist_attno = findAttrByName(attributeName, inh_columns);
   if (exist_attno > 0)
   {
    /*
     * Yes, try to merge the two column definitions.
 */

    mergeddef = MergeInheritedAttribute(inh_columns, exist_attno, newdef);

    newattmap->attnums[parent_attno - 1] = exist_attno;

    /*
     * Partitions have only one parent, so conflict should never
     * occur.
 */

    Assert(!is_partition);
   }
   else
   {
    /*
     * No, create a new inherited column
 */

    newdef->inhcount = 1;
    newdef->is_local = false;
    inh_columns = lappend(inh_columns, newdef);

    newattmap->attnums[parent_attno - 1] = ++child_attno;
    mergeddef = newdef;
   }

   /*
    * mark attnotnull if parent has it
 */

   if (bms_is_member(parent_attno, nncols))
    mergeddef->is_not_null = true;

   /*
    * Locate default/generation expression if any
 */

   if (attribute->atthasdef)
   {
    Node    *this_default;

    this_default = TupleDescGetDefault(tupleDesc, parent_attno);
    if (this_default == NULL)
     elog(ERROR, "default expression not found for attribute %d of relation \"%s\"",
       parent_attno, RelationGetRelationName(relation));

    /*
     * If it's a GENERATED default, it might contain Vars that
     * need to be mapped to the inherited column(s)' new numbers.
     * We can't do that till newattmap is ready, so just remember
     * all the inherited default expressions for the moment.
 */

    inherited_defaults = lappend(inherited_defaults, this_default);
    cols_with_defaults = lappend(cols_with_defaults, mergeddef);
   }
  }

  /*
   * Now process any inherited default expressions, adjusting attnos
   * using the completed newattmap map.
 */

  forboth(lc1, inherited_defaults, lc2, cols_with_defaults)
  {
   Node    *this_default = (Node *) lfirst(lc1);
   ColumnDef  *def = (ColumnDef *) lfirst(lc2);
   bool  found_whole_row;

   /* Adjust Vars to match new table's column numbering */
   this_default = map_variable_attnos(this_default,
              10,
              newattmap,
              InvalidOid, &found_whole_row);

   /*
    * For the moment we have to reject whole-row variables.  We could
    * convert them, if we knew the new table's rowtype OID, but that
    * hasn't been assigned yet.  (A variable could only appear in a
    * generation expression, so the error message is correct.)
 */

   if (found_whole_row)
    ereport(ERROR,
      (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
       errmsg("cannot convert whole-row table reference"),
       errdetail("Generation expression for column \"%s\" contains a whole-row reference to table \"%s\".",
           def->colname,
           RelationGetRelationName(relation))));

   /*
    * If we already had a default from some prior parent, check to
    * see if they are the same.  If so, no problem; if not, mark the
    * column as having a bogus default.  Below, we will complain if
    * the bogus default isn't overridden by the child columns.
 */

   Assert(def->raw_default == NULL);
   if (def->cooked_default == NULL)
    def->cooked_default = this_default;
   else if (!equal(def->cooked_default, this_default))
   {
    def->cooked_default = &bogus_marker;
    have_bogus_defaults = true;
   }
  }

  /*
   * Now copy the CHECK constraints of this parent, adjusting attnos
   * using the completed newattmap map.  Identically named constraints
   * are merged if possible, else we throw error.
 */

  if (constr && constr->num_check > 0)
  {
   ConstrCheck *check = constr->check;

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

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

    /* Adjust Vars to match new table's column numbering */
    expr = map_variable_attnos(stringToNode(check[i].ccbin),
             10,
             newattmap,
             InvalidOid, &found_whole_row);

    /*
     * For the moment we have to reject whole-row variables. We
     * could convert them, if we knew the new table's rowtype OID,
     * but that hasn't been assigned yet.
 */

    if (found_whole_row)
     ereport(ERROR,
       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
        errmsg("cannot convert whole-row table reference"),
        errdetail("Constraint \"%s\" contains a whole-row reference to table \"%s\".",
            name,
            RelationGetRelationName(relation))));

    constraints = MergeCheckConstraint(constraints, name, expr,
               check[i].ccenforced);
   }
  }

  /*
   * Also copy the not-null constraints from this parent.  The
   * attnotnull markings were already installed above.
 */

  foreach_ptr(CookedConstraint, nn, nnconstrs)
  {
   Assert(nn->contype == CONSTR_NOTNULL);

   nn->attnum = newattmap->attnums[nn->attnum - 1];

   nnconstraints = lappend(nnconstraints, nn);
  }

  free_attrmap(newattmap);

  /*
   * Close the parent rel, but keep our lock on it until xact commit.
   * That will prevent someone else from deleting or ALTERing the parent
   * before the child is committed.
 */

  table_close(relation, NoLock);
 }

 /*
  * If we had no inherited attributes, the result columns are just the
  * explicitly declared columns.  Otherwise, we need to merge the declared
  * columns into the inherited column list.  Although, we never have any
  * explicitly declared columns if the table is a partition.
 */

 if (inh_columns != NIL)
 {
  int   newcol_attno = 0;

  foreach(lc, columns)
  {
   ColumnDef  *newdef = lfirst_node(ColumnDef, lc);
   char    *attributeName = newdef->colname;
   int   exist_attno;

   /*
    * Partitions have only one parent and have no column definitions
    * of their own, so conflict should never occur.
 */

   Assert(!is_partition);

   newcol_attno++;

   /*
    * Does it match some inherited column?
 */

   exist_attno = findAttrByName(attributeName, inh_columns);
   if (exist_attno > 0)
   {
    /*
     * Yes, try to merge the two column definitions.
 */

    MergeChildAttribute(inh_columns, exist_attno, newcol_attno, newdef);
   }
   else
   {
    /*
     * No, attach new column unchanged to result columns.
 */

    inh_columns = lappend(inh_columns, newdef);
   }
  }

  columns = inh_columns;

  /*
   * Check that we haven't exceeded the legal # of columns after merging
   * in inherited columns.
 */

  if (list_length(columns) > MaxHeapAttributeNumber)
   ereport(ERROR,
     (errcode(ERRCODE_TOO_MANY_COLUMNS),
      errmsg("tables can have at most %d columns",
       MaxHeapAttributeNumber)));
 }

 /*
  * Now that we have the column definition list for a partition, we can
  * check whether the columns referenced in the column constraint specs
  * actually exist.  Also, merge column defaults.
 */

 if (is_partition)
 {
  foreach(lc, saved_columns)
  {
   ColumnDef  *restdef = lfirst(lc);
   bool  found = false;
   ListCell   *l;

   foreach(l, columns)
   {
    ColumnDef  *coldef = lfirst(l);

    if (strcmp(coldef->colname, restdef->colname) == 0)
    {
     found = true;

     /*
      * Check for conflicts related to generated columns.
      *
      * Same rules as above: generated-ness has to match the
      * parent, but the contents of the generation expression
      * can be different.
 */

     if (coldef->generated)
     {
      if (restdef->raw_default && !restdef->generated)
       ereport(ERROR,
         (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
          errmsg("column \"%s\" inherits from generated column but specifies default",
           restdef->colname)));
      if (restdef->identity)
       ereport(ERROR,
         (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
          errmsg("column \"%s\" inherits from generated column but specifies identity",
           restdef->colname)));
     }
     else
     {
      if (restdef->generated)
       ereport(ERROR,
         (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
          errmsg("child column \"%s\" specifies generation expression",
           restdef->colname),
          errhint("A child table column cannot be generated unless its parent column is.")));
     }

     if (coldef->generated && restdef->generated && coldef->generated != restdef->generated)
      ereport(ERROR,
        (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
         errmsg("column \"%s\" inherits from generated column of different kind",
          restdef->colname),
         errdetail("Parent column is %s, child column is %s.",
             coldef->generated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL",
             restdef->generated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL")));

     /*
      * Override the parent's default value for this column
      * (coldef->cooked_default) with the partition's local
      * definition (restdef->raw_default), if there's one. It
      * should be physically impossible to get a cooked default
      * in the local definition or a raw default in the
      * inherited definition, but make sure they're nulls, for
      * future-proofing.
 */

     Assert(restdef->cooked_default == NULL);
     Assert(coldef->raw_default == NULL);
     if (restdef->raw_default)
     {
      coldef->raw_default = restdef->raw_default;
      coldef->cooked_default = NULL;
     }
    }
   }

   /* complain for constraints on columns not in parent */
   if (!found)
    ereport(ERROR,
      (errcode(ERRCODE_UNDEFINED_COLUMN),
       errmsg("column \"%s\" does not exist",
        restdef->colname)));
  }
 }

 /*
  * If we found any conflicting parent default values, check to make sure
  * they were overridden by the child.
 */

 if (have_bogus_defaults)
 {
  foreach(lc, columns)
  {
   ColumnDef  *def = lfirst(lc);

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

 *supconstr = constraints;
 *supnotnulls = nnconstraints;

 return columns;
}


/*
 * MergeCheckConstraint
 *  Try to merge an inherited CHECK constraint with previous ones
 *
 * If we inherit identically-named constraints from multiple parents, we must
 * merge them, or throw an error if they don't have identical definitions.
 *
 * constraints is a list of CookedConstraint structs for previous constraints.
 *
 * If the new constraint matches an existing one, then the existing
 * constraint's inheritance count is updated.  If there is a conflict (same
 * name but different expression), throw an error.  If the constraint neither
 * matches nor conflicts with an existing one, a new constraint is appended to
 * the list.
 */

static List *
MergeCheckConstraint(List *constraints, const char *name, Node *expr, bool is_enforced)
{
 ListCell   *lc;
 CookedConstraint *newcon;

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

  Assert(ccon->contype == CONSTR_CHECK);

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

  if (equal(expr, ccon->expr))
  {
   /* OK to merge constraint with existing */
   if (pg_add_s16_overflow(ccon->inhcount, 1,
         &ccon->inhcount))
    ereport(ERROR,
      errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
      errmsg("too many inheritance parents"));

   /*
    * When enforceability differs, the merged constraint should be
    * marked as ENFORCED because one of the parents is ENFORCED.
 */

   if (!ccon->is_enforced && is_enforced)
   {
    ccon->is_enforced = true;
    ccon->skip_validation = false;
   }

   return constraints;
  }

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

 /*
  * Constraint couldn't be merged with an existing one and also didn't
  * conflict with an existing one, so add it as a new one to the list.
 */

 newcon = palloc0_object(CookedConstraint);
 newcon->contype = CONSTR_CHECK;
 newcon->name = pstrdup(name);
 newcon->expr = expr;
 newcon->inhcount = 1;
 newcon->is_enforced = is_enforced;
 newcon->skip_validation = !is_enforced;
 return lappend(constraints, newcon);
}

/*
 * MergeChildAttribute
 *  Merge given child attribute definition into given inherited attribute.
 *
 * Input arguments:
 * 'inh_columns' is the list of inherited ColumnDefs.
 * 'exist_attno' is the number of the inherited attribute in inh_columns
 * 'newcol_attno' is the attribute number in child table's schema definition
 * 'newdef' is the column/attribute definition from the child table.
 *
 * The ColumnDef in 'inh_columns' list is modified.  The child attribute's
 * ColumnDef remains unchanged.
 *
 * Notes:
 * - The attribute is merged according to the rules laid out in the prologue
 *   of MergeAttributes().
 * - If matching inherited attribute exists but the child attribute can not be
 *   merged into it, the function throws respective errors.
 * - A partition can not have its own column definitions. Hence this function
 *   is applicable only to a regular inheritance child.
 */

static void
MergeChildAttribute(List *inh_columns, int exist_attno, int newcol_attno, const ColumnDef *newdef)
{
 char    *attributeName = newdef->colname;
 ColumnDef  *inhdef;
 Oid   inhtypeid,
    newtypeid;
 int32  inhtypmod,
    newtypmod;
 Oid   inhcollid,
    newcollid;

 if (exist_attno == newcol_attno)
  ereport(NOTICE,
    (errmsg("merging column \"%s\" with inherited definition",
      attributeName)));
 else
  ereport(NOTICE,
    (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
     errdetail("User-specified column moved to the position of the inherited column.")));

 inhdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);

 /*
  * Must have the same type and typmod
 */

 typenameTypeIdAndMod(NULL, inhdef->typeName, &inhtypeid, &inhtypmod);
 typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
 if (inhtypeid != newtypeid || inhtypmod != newtypmod)
  ereport(ERROR,
    (errcode(ERRCODE_DATATYPE_MISMATCH),
     errmsg("column \"%s\" has a type conflict",
      attributeName),
     errdetail("%s versus %s",
         format_type_with_typemod(inhtypeid, inhtypmod),
         format_type_with_typemod(newtypeid, newtypmod))));

 /*
  * Must have the same collation
 */

 inhcollid = GetColumnDefCollation(NULL, inhdef, inhtypeid);
 newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
 if (inhcollid != newcollid)
  ereport(ERROR,
    (errcode(ERRCODE_COLLATION_MISMATCH),
     errmsg("column \"%s\" has a collation conflict",
      attributeName),
     errdetail("\"%s\" versus \"%s\"",
         get_collation_name(inhcollid),
         get_collation_name(newcollid))));

 /*
  * Identity is never inherited by a regular inheritance child. Pick
  * child's identity definition if there's one.
 */

 inhdef->identity = newdef->identity;

 /*
  * Copy storage parameter
 */

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

 /*
  * Copy compression parameter
 */

 if (inhdef->compression == NULL)
  inhdef->compression = newdef->compression;
 else if (newdef->compression != NULL)
 {
  if (strcmp(inhdef->compression, newdef->compression) != 0)
   ereport(ERROR,
     (errcode(ERRCODE_DATATYPE_MISMATCH),
      errmsg("column \"%s\" has a compression method conflict",
       attributeName),
      errdetail("%s versus %s", inhdef->compression, newdef->compression)));
 }

 /*
  * Merge of not-null constraints = OR 'em together
 */

 inhdef->is_not_null |= newdef->is_not_null;

 /*
  * Check for conflicts related to generated columns.
  *
  * If the parent column is generated, the child column will be made a
  * generated column if it isn't already.  If it is a generated column,
  * we'll take its generation expression in preference to the parent's.  We
  * must check that the child column doesn't specify a default value or
  * identity, which matches the rules for a single column in
  * parse_utilcmd.c.
  *
  * Conversely, if the parent column is not generated, the child column
  * can't be either.  (We used to allow that, but it results in being able
  * to override the generation expression via UPDATEs through the parent.)
 */

 if (inhdef->generated)
 {
  if (newdef->raw_default && !newdef->generated)
   ereport(ERROR,
     (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
      errmsg("column \"%s\" inherits from generated column but specifies default",
       inhdef->colname)));
  if (newdef->identity)
   ereport(ERROR,
     (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
      errmsg("column \"%s\" inherits from generated column but specifies identity",
       inhdef->colname)));
 }
 else
 {
  if (newdef->generated)
   ereport(ERROR,
     (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
      errmsg("child column \"%s\" specifies generation expression",
       inhdef->colname),
      errhint("A child table column cannot be generated unless its parent column is.")));
 }

 if (inhdef->generated && newdef->generated && newdef->generated != inhdef->generated)
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
     errmsg("column \"%s\" inherits from generated column of different kind",
      inhdef->colname),
     errdetail("Parent column is %s, child column is %s.",
         inhdef->generated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL",
         newdef->generated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL")));

 /*
  * If new def has a default, override previous default
 */

 if (newdef->raw_default != NULL)
 {
  inhdef->raw_default = newdef->raw_default;
  inhdef->cooked_default = newdef->cooked_default;
 }

 /* Mark the column as locally defined */
 inhdef->is_local = true;
}

/*
 * MergeInheritedAttribute
 *  Merge given parent attribute definition into specified attribute
 *  inherited from the previous parents.
 *
 * Input arguments:
 * 'inh_columns' is the list of previously inherited ColumnDefs.
 * 'exist_attno' is the number the existing matching attribute in inh_columns.
 * 'newdef' is the new parent column/attribute definition to be merged.
 *
 * The matching ColumnDef in 'inh_columns' list is modified and returned.
 *
 * Notes:
 * - The attribute is merged according to the rules laid out in the prologue
 *   of MergeAttributes().
 * - If matching inherited attribute exists but the new attribute can not be
 *   merged into it, the function throws respective errors.
 * - A partition inherits from only a single parent. Hence this function is
 *   applicable only to a regular inheritance.
 */

static ColumnDef *
MergeInheritedAttribute(List *inh_columns,
      int exist_attno,
      const ColumnDef *newdef)
{
 char    *attributeName = newdef->colname;
 ColumnDef  *prevdef;
 Oid   prevtypeid,
    newtypeid;
 int32  prevtypmod,
    newtypmod;
 Oid   prevcollid,
    newcollid;

 ereport(NOTICE,
   (errmsg("merging multiple inherited definitions of column \"%s\"",
     attributeName)));
 prevdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);

 /*
  * Must have the same type and typmod
 */

 typenameTypeIdAndMod(NULL, prevdef->typeName, &prevtypeid, &prevtypmod);
 typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
 if (prevtypeid != newtypeid || prevtypmod != newtypmod)
  ereport(ERROR,
    (errcode(ERRCODE_DATATYPE_MISMATCH),
     errmsg("inherited column \"%s\" has a type conflict",
      attributeName),
     errdetail("%s versus %s",
         format_type_with_typemod(prevtypeid, prevtypmod),
         format_type_with_typemod(newtypeid, newtypmod))));

 /*
  * Must have the same collation
 */

 prevcollid = GetColumnDefCollation(NULL, prevdef, prevtypeid);
 newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
 if (prevcollid != newcollid)
  ereport(ERROR,
    (errcode(ERRCODE_COLLATION_MISMATCH),
     errmsg("inherited column \"%s\" has a collation conflict",
      attributeName),
     errdetail("\"%s\" versus \"%s\"",
         get_collation_name(prevcollid),
         get_collation_name(newcollid))));

 /*
  * Copy/check storage parameter
 */

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

 /*
  * Copy/check compression parameter
 */

 if (prevdef->compression == NULL)
  prevdef->compression = newdef->compression;
 else if (newdef->compression != NULL)
 {
  if (strcmp(prevdef->compression, newdef->compression) != 0)
   ereport(ERROR,
     (errcode(ERRCODE_DATATYPE_MISMATCH),
      errmsg("column \"%s\" has a compression method conflict",
       attributeName),
      errdetail("%s versus %s",
          prevdef->compression, newdef->compression)));
 }

 /*
  * Check for GENERATED conflicts
 */

 if (prevdef->generated != newdef->generated)
  ereport(ERROR,
    (errcode(ERRCODE_DATATYPE_MISMATCH),
     errmsg("inherited column \"%s\" has a generation conflict",
      attributeName)));

 /*
  * Default and other constraints are handled by the caller.
 */


 if (pg_add_s16_overflow(prevdef->inhcount, 1,
       &prevdef->inhcount))
  ereport(ERROR,
    errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
    errmsg("too many inheritance parents"));

 return prevdef;
}

/*
 * StoreCatalogInheritance
 *  Updates the system catalogs with proper inheritance information.
 *
 * supers is a list of the OIDs of the new relation's direct ancestors.
 */

static void
StoreCatalogInheritance(Oid relationId, List *supers,
      bool child_is_partition)
{
 Relation relation;
 int32  seqNumber;
 ListCell   *entry;

 /*
  * sanity checks
 */

 Assert(OidIsValid(relationId));

 if (supers == NIL)
  return;

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

 relation = table_open(InheritsRelationId, RowExclusiveLock);

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

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

 table_close(relation, RowExclusiveLock);
}

/*
 * Make catalog entries showing relationId as being an inheritance child
 * of parentOid.  inhRelation is the already-opened pg_inherits catalog.
 */

static void
StoreCatalogInheritance1(Oid relationId, Oid parentOid,
       int32 seqNumber, Relation inhRelation,
       bool child_is_partition)
{
 ObjectAddress childobject,
    parentobject;

 /* store the pg_inherits row */
 StoreSingleInheritance(relationId, parentOid, seqNumber);

 /*
  * Store a dependency too
 */

 parentobject.classId = RelationRelationId;
 parentobject.objectId = parentOid;
 parentobject.objectSubId = 0;
 childobject.classId = RelationRelationId;
 childobject.objectId = relationId;
 childobject.objectSubId = 0;

 recordDependencyOn(&childobject, &parentobject,
        child_dependency_type(child_is_partition));

 /*
  * Post creation hook of this inheritance. Since object_access_hook
  * doesn't take multiple object identifiers, we relay oid of parent
  * relation using auxiliary_id argument.
 */

 InvokeObjectPostAlterHookArg(InheritsRelationId,
         relationId, 0,
         parentOid, false);

 /*
  * Mark the parent as having subclasses.
 */

 SetRelationHasSubclass(parentOid, true);
}

/*
 * Look for an existing column entry with the given name.
 *
 * Returns the index (starting with 1) if attribute already exists in columns,
 * 0 if it doesn't.
 */

static int
findAttrByName(const char *attributeName, const List *columns)
{
 ListCell   *lc;
 int   i = 1;

 foreach(lc, columns)
 {
  if (strcmp(attributeName, lfirst_node(ColumnDef, lc)->colname) == 0)
   return i;

  i++;
 }
 return 0;
}


/*
 * SetRelationHasSubclass
 *  Set the value of the relation's relhassubclass field in pg_class.
 *
 * It's always safe to set this field to true, because all SQL commands are
 * ready to see true and then find no children.  On the other hand, commands
 * generally assume zero children if this is false.
 *
 * Caller must hold any self-exclusive lock until end of transaction.  If the
 * new value is false, caller must have acquired that lock before reading the
 * evidence that justified the false value.  That way, it properly waits if
 * another backend is simultaneously concluding no need to change the tuple
 * (new and old values are true).
 *
 * NOTE: an important side-effect of this operation is that an SI invalidation
 * message is sent out to all backends --- including me --- causing plans
 * referencing the relation to be rebuilt with the new list of children.
 * This must happen even if we find that no change is needed in the pg_class
 * row.
 */

void
SetRelationHasSubclass(Oid relationId, bool relhassubclass)
{
 Relation relationRelation;
 HeapTuple tuple;
 Form_pg_class classtuple;

 Assert(CheckRelationOidLockedByMe(relationId,
           ShareUpdateExclusiveLock, false) ||
     CheckRelationOidLockedByMe(relationId,
           ShareRowExclusiveLock, true));

 /*
  * Fetch a modifiable copy of the tuple, modify it, update pg_class.
 */

 relationRelation = table_open(RelationRelationId, RowExclusiveLock);
 tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relationId));
 if (!HeapTupleIsValid(tuple))
  elog(ERROR, "cache lookup failed for relation %u", relationId);
 classtuple = (Form_pg_class) GETSTRUCT(tuple);

 if (classtuple->relhassubclass != relhassubclass)
 {
  classtuple->relhassubclass = relhassubclass;
  CatalogTupleUpdate(relationRelation, &tuple->t_self, tuple);
 }
 else
 {
  /* no need to change tuple, but force relcache rebuild anyway */
  CacheInvalidateRelcacheByTuple(tuple);
 }

 heap_freetuple(tuple);
 table_close(relationRelation, RowExclusiveLock);
}

/*
 * CheckRelationTableSpaceMove
 *  Check if relation can be moved to new tablespace.
 *
 * NOTE: The caller must hold AccessExclusiveLock on the relation.
 *
 * Returns true if the relation can be moved to the new tablespace; raises
 * an error if it is not possible to do the move; returns false if the move
 * would have no effect.
 */

bool
CheckRelationTableSpaceMove(Relation rel, Oid newTableSpaceId)
{
 Oid   oldTableSpaceId;

 /*
  * No work if no change in tablespace.  Note that MyDatabaseTableSpace is
  * stored as 0.
 */

 oldTableSpaceId = rel->rd_rel->reltablespace;
 if (newTableSpaceId == oldTableSpaceId ||
  (newTableSpaceId == MyDatabaseTableSpace && oldTableSpaceId == 0))
  return false;

 /*
  * We cannot support moving mapped relations into different tablespaces.
  * (In particular this eliminates all shared catalogs.)
 */

 if (RelationIsMapped(rel))
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("cannot move system relation \"%s\"",
      RelationGetRelationName(rel))));

 /* Cannot move a non-shared relation into pg_global */
 if (newTableSpaceId == GLOBALTABLESPACE_OID)
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     errmsg("only shared relations can be placed in pg_global tablespace")));

 /*
  * Do not allow moving temp tables of other backends ... their local
  * buffer manager is not going to cope.
 */

 if (RELATION_IS_OTHER_TEMP(rel))
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("cannot move temporary tables of other sessions")));

 return true;
}

/*
 * SetRelationTableSpace
 *  Set new reltablespace and relfilenumber in pg_class entry.
 *
 * newTableSpaceId is the new tablespace for the relation, and
 * newRelFilenumber its new filenumber.  If newRelFilenumber is
 * InvalidRelFileNumber, this field is not updated.
 *
 * NOTE: The caller must hold AccessExclusiveLock on the relation.
 *
 * The caller of this routine had better check if a relation can be
 * moved to this new tablespace by calling CheckRelationTableSpaceMove()
 * first, and is responsible for making the change visible with
 * CommandCounterIncrement().
 */

void
SetRelationTableSpace(Relation rel,
       Oid newTableSpaceId,
       RelFileNumber newRelFilenumber)
{
 Relation pg_class;
 HeapTuple tuple;
 ItemPointerData otid;
 Form_pg_class rd_rel;
 Oid   reloid = RelationGetRelid(rel);

 Assert(CheckRelationTableSpaceMove(rel, newTableSpaceId));

 /* Get a modifiable copy of the relation's pg_class row. */
 pg_class = table_open(RelationRelationId, RowExclusiveLock);

 tuple = SearchSysCacheLockedCopy1(RELOID, ObjectIdGetDatum(reloid));
 if (!HeapTupleIsValid(tuple))
  elog(ERROR, "cache lookup failed for relation %u", reloid);
 otid = tuple->t_self;
 rd_rel = (Form_pg_class) GETSTRUCT(tuple);

 /* Update the pg_class row. */
 rd_rel->reltablespace = (newTableSpaceId == MyDatabaseTableSpace) ?
  InvalidOid : newTableSpaceId;
 if (RelFileNumberIsValid(newRelFilenumber))
  rd_rel->relfilenode = newRelFilenumber;
 CatalogTupleUpdate(pg_class, &otid, tuple);
 UnlockTuple(pg_class, &otid, InplaceUpdateTupleLock);

 /*
  * Record dependency on tablespace.  This is only required for relations
  * that have no physical storage.
 */

 if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
  changeDependencyOnTablespace(RelationRelationId, reloid,
          rd_rel->reltablespace);

 heap_freetuple(tuple);
 table_close(pg_class, RowExclusiveLock);
}

/*
 *  renameatt_check   - basic sanity checks before attribute rename
 */

static void
renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
{
 char  relkind = classform->relkind;

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

 /*
  * Renaming the columns of sequences or toast tables doesn't actually
  * break anything from the system's point of view, since internal
  * references are by attnum.  But it doesn't seem right to allow users to
  * change names that are hardcoded into the system, hence the following
  * restriction.
 */

 if (relkind != RELKIND_RELATION &&
  relkind != RELKIND_VIEW &&
  relkind != RELKIND_MATVIEW &&
  relkind != RELKIND_COMPOSITE_TYPE &&
  relkind != RELKIND_INDEX &&
  relkind != RELKIND_PARTITIONED_INDEX &&
  relkind != RELKIND_FOREIGN_TABLE &&
  relkind != RELKIND_PARTITIONED_TABLE)
  ereport(ERROR,
    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     errmsg("cannot rename columns of relation \"%s\"",
      NameStr(classform->relname)),
     errdetail_relkind_not_supported(relkind)));

 /*
  * permissions checking.  only the owner of a class can change its schema.
 */

 if (!object_ownercheck(RelationRelationId, myrelid, GetUserId()))
  aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(myrelid)),
        NameStr(classform->relname));
 if (!allowSystemTableMods && IsSystemClass(myrelid, classform))
  ereport(ERROR,
    (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     errmsg("permission denied: \"%s\" is a system catalog",
      NameStr(classform->relname))));
}

/*
 *  renameatt_internal  - workhorse for renameatt
 *
 * Return value is the attribute number in the 'myrelid' relation.
 */

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

 /*
  * Grab an exclusive lock on the target table, which we will NOT release
  * until end of transaction.
 */

 targetrelation = relation_open(myrelid, AccessExclusiveLock);
 renameatt_check(myrelid, RelationGetForm(targetrelation), recursing);

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

 if (recurse)
 {
  List    *child_oids,
       *child_numparents;
  ListCell   *lo,
       *li;

  /*
   * we need the number of parents for each child so that the recursive
   * calls to renameatt() can determine whether there are any parents
   * outside the inheritance hierarchy being processed.
 */

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

  /*
   * find_all_inheritors does the recursive search of the inheritance
   * hierarchy, so all we have to do is process all of the relids in the
   * list that it returns.
 */

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

   if (childrelid == myrelid)
    continue;
   /* note we need not recurse again */
   renameatt_internal(childrelid, oldattname, newattname, falsetrue, numparents, behavior);
  }
 }
 else
 {
  /*
   * If we are told not to recurse, there had better not be any child
   * tables; else the rename would put them out of step.
   *
   * expected_parents will only be 0 if we are not already recursing.
 */

  if (expected_parents == 0 &&
   find_inheritance_children(myrelid, NoLock) != NIL)
   ereport(ERROR,
     (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
      errmsg("inherited column \"%s\" must be renamed in child tables too",
       oldattname)));
 }

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

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

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

 attrelation = table_open(AttributeRelationId, RowExclusiveLock);

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

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

 /*
  * if the attribute is inherited, forbid the renaming.  if this is a
  * top-level call to renameatt(), then expected_parents will be 0, so the
  * effect of this code will be to prohibit the renaming if the attribute
  * is inherited at all.  if this is a recursive call to renameatt(),
  * expected_parents will be the number of parents the current relation has
  * within the inheritance hierarchy being processed, so we'll prohibit the
  * renaming only if there are additional parents from elsewhere.
 */

 if (attform->attinhcount > expected_parents)
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
     errmsg("cannot rename inherited column \"%s\"",
      oldattname)));

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

 /* apply the update */
 namestrcpy(&(attform->attname), newattname);

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

 InvokeObjectPostAlterHook(RelationRelationId, myrelid, attnum);

 heap_freetuple(atttup);

 table_close(attrelation, RowExclusiveLock);

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

 return attnum;
}

/*
 * Perform permissions and integrity checks before acquiring a relation lock.
 */

static void
RangeVarCallbackForRenameAttribute(const RangeVar *rv, Oid relid, Oid oldrelid,
           void *arg)
{
 HeapTuple tuple;
 Form_pg_class form;

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

/*
 *  renameatt  - changes the name of an attribute in a relation
 *
 * The returned ObjectAddress is that of the renamed column.
 */

ObjectAddress
renameatt(RenameStmt *stmt)
{
 Oid   relid;
 AttrNumber attnum;
 ObjectAddress address;

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

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

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

 ObjectAddressSubSet(address, RelationRelationId, relid, attnum);

 return address;
}

/*
 * same logic as renameatt_internal
 */

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

 Assert(!myrelid || !mytypid);

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

  /*
   * don't tell it whether we're recursing; we allow changing typed
   * tables here
 */

  renameatt_check(myrelid, RelationGetForm(targetrelation), false);

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

 tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraintOid));
 if (!HeapTupleIsValid(tuple))
  elog(ERROR, "cache lookup failed for constraint %u",
    constraintOid);
 con = (Form_pg_constraint) GETSTRUCT(tuple);

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

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

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

    if (childrelid == myrelid)
     continue;

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

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

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

 ObjectAddressSet(address, ConstraintRelationId, constraintOid);

 ReleaseSysCache(tuple);

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

  CacheInvalidateRelcache(targetrelation);

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

 return address;
}

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

 if (stmt->renameType == OBJECT_DOMCONSTRAINT)
 {
  Relation rel;
  HeapTuple tup;

  typid = typenameTypeId(NULL, makeTypeNameFromNameList(castNode(List, stmt->object)));
  rel = table_open(TypeRelationId, RowExclusiveLock);
  tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
  if (!HeapTupleIsValid(tup))
   elog(ERROR, "cache lookup failed for type %u", typid);
  checkDomainOwner(tup);
  ReleaseSysCache(tup);
  table_close(rel, NoLock);
 }
 else
 {
  /* lock level taken here should match rename_constraint_internal */
  relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
           stmt->missing_ok ? RVR_MISSING_OK : 0,
           RangeVarCallbackForRenameAttribute,
           NULL);
  if (!OidIsValid(relid))
  {
   ereport(NOTICE,
     (errmsg("relation \"%s\" does not exist, skipping",
       stmt->relation->relname)));
   return InvalidObjectAddress;
  }
 }

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

/*
 * Execute ALTER TABLE/INDEX/SEQUENCE/VIEW/MATERIALIZED VIEW/FOREIGN TABLE
 * RENAME
 */

ObjectAddress
RenameRelation(RenameStmt *stmt)
{
 bool  is_index_stmt = stmt->renameType == OBJECT_INDEX;
 Oid   relid;
 ObjectAddress address;

 /*
  * Grab an exclusive lock on the target table, index, sequence, view,
  * materialized view, or foreign table, which we will NOT release until
  * end of transaction.
  *
  * Lock level used here should match RenameRelationInternal, to avoid lock
  * escalation.  However, because ALTER INDEX can be used with any relation
  * type, we mustn't believe without verification.
 */

 for (;;)
 {
  LOCKMODE lockmode;
  char  relkind;
  bool  obj_is_index;

  lockmode = is_index_stmt ? ShareUpdateExclusiveLock : AccessExclusiveLock;

  relid = RangeVarGetRelidExtended(stmt->relation, lockmode,
           stmt->missing_ok ? RVR_MISSING_OK : 0,
           RangeVarCallbackForAlterRelation,
           stmt);

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

  /*
   * We allow mismatched statement and object types (e.g., ALTER INDEX
   * to rename a table), but we might've used the wrong lock level.  If
   * that happens, retry with the correct lock level.  We don't bother
   * if we already acquired AccessExclusiveLock with an index, however.
 */

  relkind = get_rel_relkind(relid);
  obj_is_index = (relkind == RELKIND_INDEX ||
      relkind == RELKIND_PARTITIONED_INDEX);
  if (obj_is_index || is_index_stmt == obj_is_index)
   break;

  UnlockRelationOid(relid, lockmode);
  is_index_stmt = obj_is_index;
 }

 /* Do the work */
 RenameRelationInternal(relid, stmt->newname, false, is_index_stmt);

 ObjectAddressSet(address, RelationRelationId, relid);

 return address;
}

/*
 *  RenameRelationInternal - change the name of a relation
 */

void
RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bool is_index)
{
 Relation targetrelation;
 Relation relrelation; /* for RELATION relation */
 ItemPointerData otid;
 HeapTuple reltup;
 Form_pg_class relform;
 Oid   namespaceId;

 /*
  * Grab a lock on the target relation, which we will NOT release until end
  * of transaction.  We need at least a self-exclusive lock so that
  * concurrent DDL doesn't overwrite the rename if they start updating
  * while still seeing the old version.  The lock also guards against
  * triggering relcache reloads in concurrent sessions, which might not
  * handle this information changing under them.  For indexes, we can use a
  * reduced lock level because RelationReloadIndexInfo() handles indexes
  * specially.
 */

 targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
 namespaceId = RelationGetNamespace(targetrelation);

 /*
  * Find relation's pg_class tuple, and make sure newrelname isn't in use.
 */

 relrelation = table_open(RelationRelationId, RowExclusiveLock);

 reltup = SearchSysCacheLockedCopy1(RELOID, ObjectIdGetDatum(myrelid));
 if (!HeapTupleIsValid(reltup)) /* shouldn't happen */
  elog(ERROR, "cache lookup failed for relation %u", myrelid);
 otid = reltup->t_self;
 relform = (Form_pg_class) GETSTRUCT(reltup);

 if (get_relname_relid(newrelname, namespaceId) != InvalidOid)
  ereport(ERROR,
    (errcode(ERRCODE_DUPLICATE_TABLE),
     errmsg("relation \"%s\" already exists",
      newrelname)));

 /*
  * RenameRelation is careful not to believe the caller's idea of the
  * relation kind being handled.  We don't have to worry about this, but
  * let's not be totally oblivious to it.  We can process an index as
  * not-an-index, but not the other way around.
 */

 Assert(!is_index ||
     is_index == (targetrelation->rd_rel->relkind == RELKIND_INDEX ||
      targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX));

 /*
  * Update pg_class tuple with new relname.  (Scribbling on reltup is OK
  * because it's a copy...)
 */

 namestrcpy(&(relform->relname), newrelname);

 CatalogTupleUpdate(relrelation, &otid, reltup);
 UnlockTuple(relrelation, &otid, InplaceUpdateTupleLock);

 InvokeObjectPostAlterHookArg(RelationRelationId, myrelid, 0,
         InvalidOid, is_internal);

 heap_freetuple(reltup);
 table_close(relrelation, RowExclusiveLock);

 /*
  * Also rename the associated type, if any.
 */

 if (OidIsValid(targetrelation->rd_rel->reltype))
  RenameTypeInternal(targetrelation->rd_rel->reltype,
         newrelname, namespaceId);

 /*
  * Also rename the associated constraint, if any.
 */

 if (targetrelation->rd_rel->relkind == RELKIND_INDEX ||
  targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
 {
  Oid   constraintId = get_index_constraint(myrelid);

  if (OidIsValid(constraintId))
   RenameConstraintById(constraintId, newrelname);
 }

 /*
  * Close rel, but keep lock!
 */

 relation_close(targetrelation, NoLock);
}

/*
 *  ResetRelRewrite - reset relrewrite
 */

void
ResetRelRewrite(Oid myrelid)
{
 Relation relrelation; /* for RELATION relation */
 HeapTuple reltup;
 Form_pg_class relform;

 /*
  * Find relation's pg_class tuple.
 */

 relrelation = table_open(RelationRelationId, RowExclusiveLock);

 reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
 if (!HeapTupleIsValid(reltup)) /* shouldn't happen */
  elog(ERROR, "cache lookup failed for relation %u", myrelid);
 relform = (Form_pg_class) GETSTRUCT(reltup);

 /*
  * Update pg_class tuple.
 */

 relform->relrewrite = InvalidOid;

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

 heap_freetuple(reltup);
 table_close(relrelation, RowExclusiveLock);
}

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

void
CheckTableNotInUse(Relation rel, const char *stmt)
{
 int   expected_refcnt;

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

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

/*
 * CheckAlterTableIsSafe
 *  Verify that it's safe to allow ALTER TABLE on this relation.
 *
 * This consists of CheckTableNotInUse() plus a check that the relation
 * isn't another session's temp table.  We must split out the temp-table
 * check because there are callers of CheckTableNotInUse() that don't want
 * that, notably DROP TABLE.  (We must allow DROP or we couldn't clean out
 * an orphaned temp schema.)  Compare truncate_check_activity().
 */

static void
CheckAlterTableIsSafe(Relation rel)
{
 /*
  * Don't allow ALTER on temp tables of other backends.  Their local buffer
  * manager is not going to cope if we need to change the table's contents.
  * Even if we don't, there may be optimizations that assume temp tables
  * aren't subject to such interference.
 */

 if (RELATION_IS_OTHER_TEMP(rel))
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("cannot alter temporary tables of other sessions")));

 /*
  * Also check for active uses of the relation in the current transaction,
  * including open scans and pending AFTER trigger events.
 */

 CheckTableNotInUse(rel, "ALTER TABLE");
}

/*
 * AlterTableLookupRelation
 *  Look up, and lock, the OID for the relation named by an alter table
 *  statement.
 */

Oid
AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
{
 return RangeVarGetRelidExtended(stmt->relation, lockmode,
         stmt->missing_ok ? RVR_MISSING_OK : 0,
         RangeVarCallbackForAlterRelation,
         stmt);
}

/*
 * AlterTable
 *  Execute ALTER TABLE, which can be a list of subcommands
 *
 * ALTER TABLE is performed in three phases:
 *  1. Examine subcommands and perform pre-transformation checking.
 *  2. Validate and transform subcommands, and update system catalogs.
 *  3. Scan table(s) to check new constraints, and optionally recopy
 *     the data into new table(s).
 * Phase 3 is not performed unless one or more of the subcommands requires
 * it.  The intention of this design is to allow multiple independent
 * updates of the table schema to be performed with only one pass over the
 * data.
 *
 * ATPrepCmd performs phase 1.  A "work queue" entry is created for
 * each table to be affected (there may be multiple affected tables if the
 * commands traverse a table inheritance hierarchy).  Also we do preliminary
 * validation of the subcommands.  Because earlier subcommands may change
 * the catalog state seen by later commands, there are limits to what can
 * be done in this phase.  Generally, this phase acquires table locks,
 * checks permissions and relkind, and recurses to find child tables.
 *
 * ATRewriteCatalogs performs phase 2 for each affected table.
 * Certain subcommands need to be performed before others to avoid
 * unnecessary conflicts; for example, DROP COLUMN should come before
 * ADD COLUMN.  Therefore phase 1 divides the subcommands into multiple
 * lists, one for each logical "pass" of phase 2.
 *
 * ATRewriteTables performs phase 3 for those tables that need it.
 *
 * For most subcommand types, phases 2 and 3 do no explicit recursion,
 * since phase 1 already does it.  However, for certain subcommand types
 * it is only possible to determine how to recurse at phase 2 time; for
 * those cases, phase 1 sets the cmd->recurse flag.
 *
 * Thanks to the magic of MVCC, an error anywhere along the way rolls back
 * the whole operation; we don't have to do anything special to clean up.
 *
 * The caller must lock the relation, with an appropriate lock level
 * for the subcommands requested, using AlterTableGetLockLevel(stmt->cmds)
 * or higher. We pass the lock level down
 * so that we can apply it recursively to inherited tables. Note that the
 * lock level we want as we recurse might well be higher than required for
 * that specific subcommand. So we pass down the overall lock requirement,
 * rather than reassess it at lower levels.
 *
 * The caller also provides a "context" which is to be passed back to
 * utility.c when we need to execute a subcommand such as CREATE INDEX.
 * Some of the fields therein, such as the relid, are used here as well.
 */

void
AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
     AlterTableUtilityContext *context)
{
 Relation rel;

 /* Caller is required to provide an adequate lock. */
 rel = relation_open(context->relid, NoLock);

 CheckAlterTableIsSafe(rel);

 ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
}

/*
 * AlterTableInternal
 *
 * ALTER TABLE with target specified by OID
 *
 * We do not reject if the relation is already open, because it's quite
 * likely that one or more layers of caller have it open.  That means it
 * is unsafe to use this entry point for alterations that could break
 * existing query plans.  On the assumption it's not used for such, we
 * don't have to reject pending AFTER triggers, either.
 *
 * Also, since we don't have an AlterTableUtilityContext, this cannot be
 * used for any subcommand types that require parse transformation or
 * could generate subcommands that have to be passed to ProcessUtility.
 */

void
AlterTableInternal(Oid relid, List *cmds, bool recurse)
{
 Relation rel;
 LOCKMODE lockmode = AlterTableGetLockLevel(cmds);

 rel = relation_open(relid, lockmode);

 EventTriggerAlterTableRelid(relid);

 ATController(NULL, rel, cmds, recurse, lockmode, NULL);
}

/*
 * AlterTableGetLockLevel
 *
 * Sets the overall lock level required for the supplied list of subcommands.
 * Policy for doing this set according to needs of AlterTable(), see
 * comments there for overall explanation.
 *
 * Function is called before and after parsing, so it must give same
 * answer each time it is called. Some subcommands are transformed
 * into other subcommand types, so the transform must never be made to a
 * lower lock level than previously assigned. All transforms are noted below.
 *
 * Since this is called before we lock the table we cannot use table metadata
 * to influence the type of lock we acquire.
 *
 * There should be no lockmodes hardcoded into the subcommand functions. All
 * lockmode decisions for ALTER TABLE are made here only. The one exception is
 * ALTER TABLE RENAME which is treated as a different statement type T_RenameStmt
 * and does not travel through this section of code and cannot be combined with
 * any of the subcommands given here.
 *
 * Note that Hot Standby only knows about AccessExclusiveLocks on the primary
 * so any changes that might affect SELECTs running on standbys need to use
 * AccessExclusiveLocks even if you think a lesser lock would do, unless you
 * have a solution for that also.
 *
 * Also note that pg_dump uses only an AccessShareLock, meaning that anything
 * that takes a lock less than AccessExclusiveLock can change object definitions
 * while pg_dump is running. Be careful to check that the appropriate data is
 * derived by pg_dump using an MVCC snapshot, rather than syscache lookups,
 * otherwise we might end up with an inconsistent dump that can't restore.
 */

LOCKMODE
AlterTableGetLockLevel(List *cmds)
{
 /*
  * This only works if we read catalog tables using MVCC snapshots.
 */

 ListCell   *lcmd;
 LOCKMODE lockmode = ShareUpdateExclusiveLock;

 foreach(lcmd, cmds)
 {
  AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
  LOCKMODE cmd_lockmode = AccessExclusiveLock; /* default for compiler */

  switch (cmd->subtype)
  {
    /*
     * These subcommands rewrite the heap, so require full locks.
 */

   case AT_AddColumn: /* may rewrite heap, in some cases and visible
 * to SELECT */

   case AT_SetAccessMethod: /* must rewrite heap */
   case AT_SetTableSpace: /* must rewrite heap */
   case AT_AlterColumnType: /* must rewrite heap */
    cmd_lockmode = AccessExclusiveLock;
    break;

    /*
     * These subcommands may require addition of toast tables. If
     * we add a toast table to a table currently being scanned, we
     * might miss data added to the new toast table by concurrent
     * insert transactions.
 */

   case AT_SetStorage: /* may add toast tables, see
 * ATRewriteCatalogs() */

    cmd_lockmode = AccessExclusiveLock;
    break;

    /*
     * Removing constraints can affect SELECTs that have been
     * optimized assuming the constraint holds true. See also
     * CloneFkReferenced.
 */

   case AT_DropConstraint: /* as DROP INDEX */
   case AT_DropNotNull: /* may change some SQL plans */
    cmd_lockmode = AccessExclusiveLock;
    break;

    /*
     * Subcommands that may be visible to concurrent SELECTs
 */

   case AT_DropColumn: /* change visible to SELECT */
   case AT_AddColumnToView: /* CREATE VIEW */
   case AT_DropOids: /* used to equiv to DropColumn */
   case AT_EnableAlwaysRule: /* may change SELECT rules */
   case AT_EnableReplicaRule: /* may change SELECT rules */
   case AT_EnableRule: /* may change SELECT rules */
   case AT_DisableRule: /* may change SELECT rules */
    cmd_lockmode = AccessExclusiveLock;
    break;

    /*
     * Changing owner may remove implicit SELECT privileges
 */

   case AT_ChangeOwner: /* change visible to SELECT */
    cmd_lockmode = AccessExclusiveLock;
    break;

    /*
     * Changing foreign table options may affect optimization.
 */

   case AT_GenericOptions:
   case AT_AlterColumnGenericOptions:
    cmd_lockmode = AccessExclusiveLock;
    break;

    /*
     * These subcommands affect write operations only.
 */

   case AT_EnableTrig:
   case AT_EnableAlwaysTrig:
   case AT_EnableReplicaTrig:
   case AT_EnableTrigAll:
   case AT_EnableTrigUser:
   case AT_DisableTrig:
   case AT_DisableTrigAll:
   case AT_DisableTrigUser:
    cmd_lockmode = ShareRowExclusiveLock;
    break;

    /*
     * These subcommands affect write operations only. XXX
     * Theoretically, these could be ShareRowExclusiveLock.
 */

   case AT_ColumnDefault:
   case AT_CookedColumnDefault:
   case AT_AlterConstraint:
   case AT_AddIndex: /* from ADD CONSTRAINT */
   case AT_AddIndexConstraint:
   case AT_ReplicaIdentity:
   case AT_SetNotNull:
   case AT_EnableRowSecurity:
   case AT_DisableRowSecurity:
   case AT_ForceRowSecurity:
   case AT_NoForceRowSecurity:
   case AT_AddIdentity:
   case AT_DropIdentity:
   case AT_SetIdentity:
   case AT_SetExpression:
   case AT_DropExpression:
   case AT_SetCompression:
    cmd_lockmode = AccessExclusiveLock;
    break;

   case AT_AddConstraint:
   case AT_ReAddConstraint: /* becomes AT_AddConstraint */
   case AT_ReAddDomainConstraint: /* becomes AT_AddConstraint */
    if (IsA(cmd->def, Constraint))
    {
     Constraint *con = (Constraint *) cmd->def;

     switch (con->contype)
     {
      case CONSTR_EXCLUSION:
      case CONSTR_PRIMARY:
      case CONSTR_UNIQUE:

       /*
        * Cases essentially the same as CREATE INDEX. We
        * could reduce the lock strength to ShareLock if
        * we can work out how to allow concurrent catalog
        * updates. XXX Might be set down to
        * ShareRowExclusiveLock but requires further
        * analysis.
 */

       cmd_lockmode = AccessExclusiveLock;
       break;
      case CONSTR_FOREIGN:

       /*
        * We add triggers to both tables when we add a
        * Foreign Key, so the lock level must be at least
        * as strong as CREATE TRIGGER.
 */

       cmd_lockmode = ShareRowExclusiveLock;
       break;

      default:
       cmd_lockmode = AccessExclusiveLock;
     }
    }
    break;

    /*
     * These subcommands affect inheritance behaviour. Queries
     * started before us will continue to see the old inheritance
     * behaviour, while queries started after we commit will see
     * new behaviour. No need to prevent reads or writes to the
     * subtable while we hook it up though. Changing the TupDesc
     * may be a problem, so keep highest lock.
 */

   case AT_AddInherit:
   case AT_DropInherit:
    cmd_lockmode = AccessExclusiveLock;
    break;

    /*
     * These subcommands affect implicit row type conversion. They
     * have affects similar to CREATE/DROP CAST on queries. don't
     * provide for invalidating parse trees as a result of such
     * changes, so we keep these at AccessExclusiveLock.
 */

   case AT_AddOf:
   case AT_DropOf:
    cmd_lockmode = AccessExclusiveLock;
    break;

    /*
     * Only used by CREATE OR REPLACE VIEW which must conflict
     * with an SELECTs currently using the view.
 */

   case AT_ReplaceRelOptions:
    cmd_lockmode = AccessExclusiveLock;
    break;

    /*
     * These subcommands affect general strategies for performance
     * and maintenance, though don't change the semantic results
     * from normal data reads and writes. Delaying an ALTER TABLE
     * behind currently active writes only delays the point where
     * the new strategy begins to take effect, so there is no
     * benefit in waiting. In this case the minimum restriction
     * applies: we don't currently allow concurrent catalog
     * updates.
 */

   case AT_SetStatistics: /* Uses MVCC in getTableAttrs() */
   case AT_ClusterOn: /* Uses MVCC in getIndexes() */
   case AT_DropCluster: /* Uses MVCC in getIndexes() */
   case AT_SetOptions: /* Uses MVCC in getTableAttrs() */
   case AT_ResetOptions: /* Uses MVCC in getTableAttrs() */
    cmd_lockmode = ShareUpdateExclusiveLock;
    break;

   case AT_SetLogged:
   case AT_SetUnLogged:
    cmd_lockmode = AccessExclusiveLock;
    break;

   case AT_ValidateConstraint: /* Uses MVCC in getConstraints() */
    cmd_lockmode = ShareUpdateExclusiveLock;
    break;

    /*
     * Rel options are more complex than first appears. Options
     * are set here for tables, views and indexes; for historical
     * reasons these can all be used with ALTER TABLE, so we can't
     * decide between them using the basic grammar.
 */

   case AT_SetRelOptions: /* Uses MVCC in getIndexes() and
 * getTables() */

   case AT_ResetRelOptions: /* Uses MVCC in getIndexes() and
 * getTables() */

    cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
    break;

   case AT_AttachPartition:
    cmd_lockmode = ShareUpdateExclusiveLock;
    break;

   case AT_DetachPartition:
    if (((PartitionCmd *) cmd->def)->concurrent)
     cmd_lockmode = ShareUpdateExclusiveLock;
    else
     cmd_lockmode = AccessExclusiveLock;
    break;

   case AT_DetachPartitionFinalize:
    cmd_lockmode = ShareUpdateExclusiveLock;
    break;

   default:   /* oops */
    elog(ERROR, "unrecognized alter table type: %d",
      (int) cmd->subtype);
    break;
  }

  /*
   * Take the greatest lockmode from any subcommand
 */

  if (cmd_lockmode > lockmode)
   lockmode = cmd_lockmode;
 }

 return lockmode;
}

/*
 * ATController provides top level control over the phases.
 *
 * parsetree is passed in to allow it to be passed to event triggers
 * when requested.
 */

static void
ATController(AlterTableStmt *parsetree,
    Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
    AlterTableUtilityContext *context)
{
 List    *wqueue = NIL;
 ListCell   *lcmd;

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

  ATPrepCmd(&wqueue, rel, cmd, recurse, false, lockmode, context);
 }

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

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

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

/*
 * ATPrepCmd
 *
 * Traffic cop for ALTER TABLE Phase 1 operations, including simple
 * recursion and permission checks.
 *
 * Caller must have acquired appropriate lock type on relation already.
 * This lock should be held until commit.
 */

static void
ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
    bool recurse, bool recursing, LOCKMODE lockmode,
    AlterTableUtilityContext *context)
{
 AlteredTableInfo *tab;
 AlterTablePass pass = AT_PASS_UNSET;

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

 /*
  * Disallow any ALTER TABLE other than ALTER TABLE DETACH FINALIZE on
  * partitions that are pending detach.
 */

 if (rel->rd_rel->relispartition &&
  cmd->subtype != AT_DetachPartitionFinalize &&
  PartitionHasPendingDetach(RelationGetRelid(rel)))
  ereport(ERROR,
    errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    errmsg("cannot alter partition \"%s\" with an incomplete detach",
        RelationGetRelationName(rel)),
    errhint("Use ALTER TABLE ... DETACH PARTITION ... FINALIZE to complete the pending detach operation."));

 /*
  * Copy the original subcommand for each table, so we can scribble on it.
  * This avoids conflicts when different child tables need to make
  * different parse transformations (for example, the same column may have
  * different column numbers in different children).
 */

 cmd = copyObject(cmd);

 /*
  * Do permissions and relkind checking, recursion to child tables if
  * needed, and any additional phase-1 processing needed.  (But beware of
  * adding any processing that looks at table details that another
  * subcommand could change.  In some cases we reject multiple subcommands
  * that could try to change the same state in contrary ways.)
 */

 switch (cmd->subtype)
 {
  case AT_AddColumn:  /* ADD COLUMN */
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE |
        ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
   ATPrepAddColumn(wqueue, rel, recurse, recursing, false, cmd,
       lockmode, context);
   /* Recursion occurs during execution phase */
   pass = AT_PASS_ADD_COL;
   break;
  case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */
   ATSimplePermissions(cmd->subtype, rel, ATT_VIEW);
   ATPrepAddColumn(wqueue, rel, recurse, recursing, true, cmd,
       lockmode, context);
   /* Recursion occurs during execution phase */
   pass = AT_PASS_ADD_COL;
   break;
  case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */

   /*
    * We allow defaults on views so that INSERT into a view can have
    * default-ish behavior.  This works because the rewriter
    * substitutes default values into INSERTs before it expands
    * rules.
 */

   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
        ATT_FOREIGN_TABLE);
   ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
   /* No command-specific prep needed */
   pass = cmd->def ? AT_PASS_ADD_OTHERCONSTR : AT_PASS_DROP;
   break;
  case AT_CookedColumnDefault: /* add a pre-cooked default */
   /* This is currently used only in CREATE TABLE */
   /* (so the permission check really isn't necessary) */
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
   /* This command never recurses */
   pass = AT_PASS_ADD_OTHERCONSTR;
   break;
  case AT_AddIdentity:
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
        ATT_FOREIGN_TABLE);
   /* Set up recursion for phase 2; no other prep needed */
   if (recurse)
    cmd->recurse = true;
   pass = AT_PASS_ADD_OTHERCONSTR;
   break;
  case AT_SetIdentity:
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
        ATT_FOREIGN_TABLE);
   /* Set up recursion for phase 2; no other prep needed */
   if (recurse)
    cmd->recurse = true;
   /* This should run after AddIdentity, so do it in MISC pass */
   pass = AT_PASS_MISC;
   break;
  case AT_DropIdentity:
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
        ATT_FOREIGN_TABLE);
   /* Set up recursion for phase 2; no other prep needed */
   if (recurse)
    cmd->recurse = true;
   pass = AT_PASS_DROP;
   break;
  case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
   /* Set up recursion for phase 2; no other prep needed */
   if (recurse)
    cmd->recurse = true;
   pass = AT_PASS_DROP;
   break;
  case AT_SetNotNull:  /* ALTER COLUMN SET NOT NULL */
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
   /* Set up recursion for phase 2; no other prep needed */
   if (recurse)
    cmd->recurse = true;
   pass = AT_PASS_COL_ATTRS;
   break;
  case AT_SetExpression: /* ALTER COLUMN SET EXPRESSION */
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
   ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
   pass = AT_PASS_SET_EXPRESSION;
   break;
  case AT_DropExpression: /* ALTER COLUMN DROP EXPRESSION */
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
   ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
   ATPrepDropExpression(rel, cmd, recurse, recursing, lockmode);
   pass = AT_PASS_DROP;
   break;
  case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_MATVIEW |
        ATT_INDEX | ATT_PARTITIONED_INDEX | ATT_FOREIGN_TABLE);
   ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
   /* No command-specific prep needed */
   pass = AT_PASS_MISC;
   break;
  case AT_SetOptions:  /* ALTER COLUMN SET ( options ) */
  case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE |
        ATT_MATVIEW | ATT_FOREIGN_TABLE);
   /* This command never recurses */
   pass = AT_PASS_MISC;
   break;
  case AT_SetStorage:  /* ALTER COLUMN SET STORAGE */
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE |
        ATT_MATVIEW | ATT_FOREIGN_TABLE);
   ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
   /* No command-specific prep needed */
   pass = AT_PASS_MISC;
   break;
  case AT_SetCompression: /* ALTER COLUMN SET COMPRESSION */
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_MATVIEW);
   /* This command never recurses */
   /* No command-specific prep needed */
   pass = AT_PASS_MISC;
   break;
  case AT_DropColumn:  /* DROP COLUMN */
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE |
        ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
   ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd,
        lockmode, context);
   /* Recursion occurs during execution phase */
   pass = AT_PASS_DROP;
   break;
  case AT_AddIndex:  /* ADD INDEX */
   ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_PARTITIONED_TABLE);
   /* This command never recurses */
   /* No command-specific prep needed */
   pass = AT_PASS_ADD_INDEX;
   break;
  case AT_AddConstraint: /* ADD CONSTRAINT */
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
   ATPrepAddPrimaryKey(wqueue, rel, cmd, recurse, lockmode, context);
   if (recurse)
   {
    /* recurses at exec time; lock descendants and set flag */
    (void) find_all_inheritors(RelationGetRelid(rel), lockmode, NULL);
    cmd->recurse = true;
   }
   pass = AT_PASS_ADD_CONSTR;
   break;
  case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */
   ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_PARTITIONED_TABLE);
   /* This command never recurses */
   /* No command-specific prep needed */
   pass = AT_PASS_ADD_INDEXCONSTR;
   break;
  case AT_DropConstraint: /* DROP CONSTRAINT */
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
   ATCheckPartitionsNotInUse(rel, lockmode);
   /* Other recursion occurs during execution phase */
   /* No command-specific prep needed except saving recurse flag */
   if (recurse)
    cmd->recurse = true;
   pass = AT_PASS_DROP;
   break;
  case AT_AlterColumnType: /* ALTER COLUMN TYPE */
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE |
        ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
   /* See comments for ATPrepAlterColumnType */
   cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, recurse, lockmode,
           AT_PASS_UNSET, context);
   Assert(cmd != NULL);
   /* Performs own recursion */
   ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd,
          lockmode, context);
   pass = AT_PASS_ALTER_TYPE;
   break;
  case AT_AlterColumnGenericOptions:
   ATSimplePermissions(cmd->subtype, rel, ATT_FOREIGN_TABLE);
   /* This command never recurses */
   /* No command-specific prep needed */
   pass = AT_PASS_MISC;
   break;
  case AT_ChangeOwner: /* ALTER OWNER */
   /* This command never recurses */
   /* No command-specific prep needed */
   pass = AT_PASS_MISC;
   break;
  case AT_ClusterOn:  /* CLUSTER ON */
  case AT_DropCluster: /* SET WITHOUT CLUSTER */
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_MATVIEW);
   /* These commands never recurse */
   /* No command-specific prep needed */
   pass = AT_PASS_MISC;
   break;
  case AT_SetLogged:  /* SET LOGGED */
  case AT_SetUnLogged: /* SET UNLOGGED */
   ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_SEQUENCE);
   if (tab->chgPersistence)
    ereport(ERROR,
      (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
       errmsg("cannot change persistence setting twice")));
   ATPrepChangePersistence(tab, rel, cmd->subtype == AT_SetLogged);
   pass = AT_PASS_MISC;
   break;
  case AT_DropOids:  /* SET WITHOUT OIDS */
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
   pass = AT_PASS_DROP;
   break;
  case AT_SetAccessMethod: /* SET ACCESS METHOD */
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_MATVIEW);

   /* check if another access method change was already requested */
   if (tab->chgAccessMethod)
    ereport(ERROR,
      (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
       errmsg("cannot have multiple SET ACCESS METHOD subcommands")));

   ATPrepSetAccessMethod(tab, rel, cmd->name);
   pass = AT_PASS_MISC; /* does not matter; no work in Phase 2 */
   break;
  case AT_SetTableSpace: /* SET TABLESPACE */
   ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_PARTITIONED_TABLE |
        ATT_MATVIEW | ATT_INDEX | ATT_PARTITIONED_INDEX);
   /* This command never recurses */
   ATPrepSetTableSpace(tab, rel, cmd->name, lockmode);
   pass = AT_PASS_MISC; /* doesn't actually matter */
   break;
  case AT_SetRelOptions: /* SET (...) */
  case AT_ResetRelOptions: /* RESET (...) */
  case AT_ReplaceRelOptions: /* reset them all, then set just these */
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
        ATT_MATVIEW | ATT_INDEX);
   /* This command never recurses */
   /* No command-specific prep needed */
   pass = AT_PASS_MISC;
   break;
  case AT_AddInherit:  /* INHERIT */
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
   /* This command never recurses */
   ATPrepAddInherit(rel);
   pass = AT_PASS_MISC;
   break;
  case AT_DropInherit: /* NO INHERIT */
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
   /* This command never recurses */
   /* No command-specific prep needed */
   pass = AT_PASS_MISC;
   break;
  case AT_AlterConstraint: /* ALTER CONSTRAINT */
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE);
   /* Recursion occurs during execution phase */
   if (recurse)
    cmd->recurse = true;
   pass = AT_PASS_MISC;
   break;
  case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
   /* Recursion occurs during execution phase */
   /* No command-specific prep needed except saving recurse flag */
   if (recurse)
    cmd->recurse = true;
   pass = AT_PASS_MISC;
   break;
  case AT_ReplicaIdentity: /* REPLICA IDENTITY ... */
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_MATVIEW);
   pass = AT_PASS_MISC;
   /* This command never recurses */
   /* No command-specific prep needed */
   break;
  case AT_EnableTrig:  /* ENABLE TRIGGER variants */
  case AT_EnableAlwaysTrig:
  case AT_EnableReplicaTrig:
  case AT_EnableTrigAll:
  case AT_EnableTrigUser:
  case AT_DisableTrig: /* DISABLE TRIGGER variants */
  case AT_DisableTrigAll:
  case AT_DisableTrigUser:
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
   /* Set up recursion for phase 2; no other prep needed */
   if (recurse)
    cmd->recurse = true;
   pass = AT_PASS_MISC;
   break;
  case AT_EnableRule:  /* ENABLE/DISABLE RULE variants */
  case AT_EnableAlwaysRule:
  case AT_EnableReplicaRule:
  case AT_DisableRule:
  case AT_AddOf:   /* OF */
  case AT_DropOf:   /* NOT OF */
  case AT_EnableRowSecurity:
  case AT_DisableRowSecurity:
  case AT_ForceRowSecurity:
  case AT_NoForceRowSecurity:
   ATSimplePermissions(cmd->subtype, rel,
        ATT_TABLE | ATT_PARTITIONED_TABLE);
   /* These commands never recurse */
   /* No command-specific prep needed */
   pass = AT_PASS_MISC;
   break;
  case AT_GenericOptions:
   ATSimplePermissions(cmd->subtype, rel, ATT_FOREIGN_TABLE);
   /* No command-specific prep needed */
   pass = AT_PASS_MISC;
   break;
  case AT_AttachPartition:
   ATSimplePermissions(cmd->subtype, rel,
        ATT_PARTITIONED_TABLE | ATT_PARTITIONED_INDEX);
   /* No command-specific prep needed */
   pass = AT_PASS_MISC;
   break;
  case AT_DetachPartition:
   ATSimplePermissions(cmd->subtype, rel, ATT_PARTITIONED_TABLE);
   /* No command-specific prep needed */
   pass = AT_PASS_MISC;
   break;
  case AT_DetachPartitionFinalize:
   ATSimplePermissions(cmd->subtype, rel, ATT_PARTITIONED_TABLE);
   /* No command-specific prep needed */
   pass = AT_PASS_MISC;
   break;
  default:    /* oops */
   elog(ERROR, "unrecognized alter table type: %d",
     (int) cmd->subtype);
   pass = AT_PASS_UNSET; /* keep compiler quiet */
   break;
 }
 Assert(pass > AT_PASS_UNSET);

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

/*
 * ATRewriteCatalogs
 *
 * Traffic cop for ALTER TABLE Phase 2 operations.  Subcommands are
 * dispatched in a "safe" execution order (designed to avoid unnecessary
 * conflicts).
 */

static void
ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
      AlterTableUtilityContext *context)
{
 ListCell   *ltab;

 /*
  * We process all the tables "in parallel", one pass at a time.  This is
  * needed because we may have to propagate work from one table to another
  * (specifically, ALTER TYPE on a foreign key's PK has to dispatch the
  * re-adding of the foreign key constraint to the other table).  Work can
  * only be propagated into later passes, however.
 */

 for (AlterTablePass pass = 0; pass < AT_NUM_PASSES; pass++)
 {
  /* Go through each table that needs to be processed */
  foreach(ltab, *wqueue)
  {
   AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
   List    *subcmds = tab->subcmds[pass];
   ListCell   *lcmd;

   if (subcmds == NIL)
    continue;

   /*
    * Open the relation and store it in tab.  This allows subroutines
    * close and reopen, if necessary.  Appropriate lock was obtained
    * by phase 1, needn't get it again.
 */

   tab->rel = relation_open(tab->relid, NoLock);

   foreach(lcmd, subcmds)
    ATExecCmd(wqueue, tab,
        lfirst_node(AlterTableCmd, lcmd),
        lockmode, pass, context);

   /*
    * After the ALTER TYPE or SET EXPRESSION pass, do cleanup work
    * (this is not done in ATExecAlterColumnType since it should be
    * done only once if multiple columns of a table are altered).
 */

   if (pass == AT_PASS_ALTER_TYPE || pass == AT_PASS_SET_EXPRESSION)
    ATPostAlterTypeCleanup(wqueue, tab, lockmode);

   if (tab->rel)
   {
    relation_close(tab->rel, NoLock);
    tab->rel = NULL;
   }
  }
 }

 /* Check to see if a toast table must be added. */
 foreach(ltab, *wqueue)
 {
  AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);

  /*
   * If the table is source table of ATTACH PARTITION command, we did
   * not modify anything about it that will change its toasting
   * requirement, so no need to check.
 */

  if (((tab->relkind == RELKIND_RELATION ||
     tab->relkind == RELKIND_PARTITIONED_TABLE) &&
    tab->partition_constraint == NULL) ||
   tab->relkind == RELKIND_MATVIEW)
   AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
 }
}

/*
 * ATExecCmd: dispatch a subcommand to appropriate execution routine
 */

static void
ATExecCmd(List **wqueue, AlteredTableInfo *tab,
    AlterTableCmd *cmd, LOCKMODE lockmode, AlterTablePass cur_pass,
    AlterTableUtilityContext *context)
{
 ObjectAddress address = InvalidObjectAddress;
 Relation rel = tab->rel;

 switch (cmd->subtype)
 {
  case AT_AddColumn:  /* ADD COLUMN */
  case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */
   address = ATExecAddColumn(wqueue, tab, rel, &cmd,
           cmd->recurse, false,
           lockmode, cur_pass, context);
   break;
  case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */
   address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
   break;
  case AT_CookedColumnDefault: /* add a pre-cooked default */
   address = ATExecCookedColumnDefault(rel, cmd->num, cmd->def);
   break;
  case AT_AddIdentity:
   cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
           cur_pass, context);
   Assert(cmd != NULL);
   address = ATExecAddIdentity(rel, cmd->name, cmd->def, lockmode, cmd->recurse, false);
   break;
  case AT_SetIdentity:
   cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
           cur_pass, context);
   Assert(cmd != NULL);
   address = ATExecSetIdentity(rel, cmd->name, cmd->def, lockmode, cmd->recurse, false);
   break;
  case AT_DropIdentity:
   address = ATExecDropIdentity(rel, cmd->name, cmd->missing_ok, lockmode, cmd->recurse, false);
   break;
  case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
   address = ATExecDropNotNull(rel, cmd->name, cmd->recurse, lockmode);
   break;
  case AT_SetNotNull:  /* ALTER COLUMN SET NOT NULL */
   address = ATExecSetNotNull(wqueue, rel, NULL, cmd->name,
            cmd->recurse, false, lockmode);
   break;
  case AT_SetExpression:
   address = ATExecSetExpression(tab, rel, cmd->name, cmd->def, lockmode);
   break;
  case AT_DropExpression:
   address = ATExecDropExpression(rel, cmd->name, cmd->missing_ok, lockmode);
   break;
  case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */
   address = ATExecSetStatistics(rel, cmd->name, cmd->num, cmd->def, lockmode);
   break;
  case AT_SetOptions:  /* ALTER COLUMN SET ( options ) */
   address = ATExecSetOptions(rel, cmd->name, cmd->def, false, lockmode);
   break;
  case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */
   address = ATExecSetOptions(rel, cmd->name, cmd->def, true, lockmode);
   break;
  case AT_SetStorage:  /* ALTER COLUMN SET STORAGE */
   address = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
   break;
  case AT_SetCompression: /* ALTER COLUMN SET COMPRESSION */
   address = ATExecSetCompression(rel, cmd->name, cmd->def,
             lockmode);
   break;
  case AT_DropColumn:  /* DROP COLUMN */
   address = ATExecDropColumn(wqueue, rel, cmd->name,
            cmd->behavior, cmd->recurse, false,
            cmd->missing_ok, lockmode,
            NULL);
   break;
  case AT_AddIndex:  /* ADD INDEX */
   address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
          lockmode);
   break;
  case AT_ReAddIndex:  /* ADD INDEX */
   address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true,
          lockmode);
   break;
  case AT_ReAddStatistics: /* ADD STATISTICS */
   address = ATExecAddStatistics(tab, rel, (CreateStatsStmt *) cmd->def,
            true, lockmode);
   break;
  case AT_AddConstraint: /* ADD CONSTRAINT */
   /* Transform the command only during initial examination */
   if (cur_pass == AT_PASS_ADD_CONSTR)
    cmd = ATParseTransformCmd(wqueue, tab, rel, cmd,
            cmd->recurse, lockmode,
            cur_pass, context);
   /* Depending on constraint type, might be no more work to do now */
   if (cmd != NULL)
    address =
     ATExecAddConstraint(wqueue, tab, rel,
          (Constraint *) cmd->def,
          cmd->recurse, false, lockmode);
   break;
  case AT_ReAddConstraint: /* Re-add pre-existing check constraint */
   address =
    ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
         truetrue, lockmode);
   break;
  case AT_ReAddDomainConstraint: /* Re-add pre-existing domain check
 * constraint */

   address =
    AlterDomainAddConstraint(((AlterDomainStmt *) cmd->def)->typeName,
           ((AlterDomainStmt *) cmd->def)->def,
           NULL);
   break;
  case AT_ReAddComment: /* Re-add existing comment */
   address = CommentObject((CommentStmt *) cmd->def);
   break;
  case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */
   address = ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def,
              lockmode);
   break;
  case AT_AlterConstraint: /* ALTER CONSTRAINT */
   address = ATExecAlterConstraint(wqueue, rel,
           castNode(ATAlterConstraint, cmd->def),
           cmd->recurse, lockmode);
   break;
  case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
   address = ATExecValidateConstraint(wqueue, rel, cmd->name, cmd->recurse,
              false, lockmode);
   break;
  case AT_DropConstraint: /* DROP CONSTRAINT */
   ATExecDropConstraint(rel, cmd->name, cmd->behavior,
         cmd->recurse,
         cmd->missing_ok, lockmode);
   break;
  case AT_AlterColumnType: /* ALTER COLUMN TYPE */
   /* parse transformation was done earlier */
   address = ATExecAlterColumnType(tab, rel, cmd, lockmode);
   break;
  case AT_AlterColumnGenericOptions: /* ALTER COLUMN OPTIONS */
   address =
    ATExecAlterColumnGenericOptions(rel, cmd->name,
            (List *) cmd->def, lockmode);
   break;
  case AT_ChangeOwner: /* ALTER OWNER */
   ATExecChangeOwner(RelationGetRelid(rel),
         get_rolespec_oid(cmd->newowner, false),
         false, lockmode);
   break;
  case AT_ClusterOn:  /* CLUSTER ON */
   address = ATExecClusterOn(rel, cmd->name, lockmode);
   break;
  case AT_DropCluster: /* SET WITHOUT CLUSTER */
   ATExecDropCluster(rel, lockmode);
   break;
  case AT_SetLogged:  /* SET LOGGED */
  case AT_SetUnLogged: /* SET UNLOGGED */
   break;
  case AT_DropOids:  /* SET WITHOUT OIDS */
   /* nothing to do here, oid columns don't exist anymore */
   break;
  case AT_SetAccessMethod: /* SET ACCESS METHOD */

   /*
    * Only do this for partitioned tables, for which this is just a
    * catalog change.  Tables with storage are handled by Phase 3.
 */

   if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
    tab->chgAccessMethod)
    ATExecSetAccessMethodNoStorage(rel, tab->newAccessMethod);
   break;
  case AT_SetTableSpace: /* SET TABLESPACE */

   /*
    * Only do this for partitioned tables and indexes, for which this
    * is just a catalog change.  Other relation types which have
    * storage are handled by Phase 3.
 */

   if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
    rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
    ATExecSetTableSpaceNoStorage(rel, tab->newTableSpace);

   break;
  case AT_SetRelOptions: /* SET (...) */
  case AT_ResetRelOptions: /* RESET (...) */
  case AT_ReplaceRelOptions: /* replace entire option list */
   ATExecSetRelOptions(rel, (List *) cmd->def, cmd->subtype, lockmode);
   break;
  case AT_EnableTrig:  /* ENABLE TRIGGER name */
   ATExecEnableDisableTrigger(rel, cmd->name,
            TRIGGER_FIRES_ON_ORIGIN, false,
            cmd->recurse,
            lockmode);
   break;
  case AT_EnableAlwaysTrig: /* ENABLE ALWAYS TRIGGER name */
   ATExecEnableDisableTrigger(rel, cmd->name,
            TRIGGER_FIRES_ALWAYS, false,
            cmd->recurse,
            lockmode);
   break;
  case AT_EnableReplicaTrig: /* ENABLE REPLICA TRIGGER name */
   ATExecEnableDisableTrigger(rel, cmd->name,
            TRIGGER_FIRES_ON_REPLICA, false,
            cmd->recurse,
            lockmode);
   break;
  case AT_DisableTrig: /* DISABLE TRIGGER name */
   ATExecEnableDisableTrigger(rel, cmd->name,
            TRIGGER_DISABLED, false,
            cmd->recurse,
            lockmode);
   break;
  case AT_EnableTrigAll: /* ENABLE TRIGGER ALL */
   ATExecEnableDisableTrigger(rel, NULL,
            TRIGGER_FIRES_ON_ORIGIN, false,
            cmd->recurse,
            lockmode);
   break;
  case AT_DisableTrigAll: /* DISABLE TRIGGER ALL */
   ATExecEnableDisableTrigger(rel, NULL,
            TRIGGER_DISABLED, false,
            cmd->recurse,
            lockmode);
   break;
  case AT_EnableTrigUser: /* ENABLE TRIGGER USER */
   ATExecEnableDisableTrigger(rel, NULL,
            TRIGGER_FIRES_ON_ORIGIN, true,
            cmd->recurse,
            lockmode);
   break;
  case AT_DisableTrigUser: /* DISABLE TRIGGER USER */
   ATExecEnableDisableTrigger(rel, NULL,
            TRIGGER_DISABLED, true,
            cmd->recurse,
            lockmode);
   break;

  case AT_EnableRule:  /* ENABLE RULE name */
   ATExecEnableDisableRule(rel, cmd->name,
         RULE_FIRES_ON_ORIGIN, lockmode);
   break;
  case AT_EnableAlwaysRule: /* ENABLE ALWAYS RULE name */
   ATExecEnableDisableRule(rel, cmd->name,
         RULE_FIRES_ALWAYS, lockmode);
   break;
  case AT_EnableReplicaRule: /* ENABLE REPLICA RULE name */
   ATExecEnableDisableRule(rel, cmd->name,
         RULE_FIRES_ON_REPLICA, lockmode);
   break;
  case AT_DisableRule: /* DISABLE RULE name */
   ATExecEnableDisableRule(rel, cmd->name,
         RULE_DISABLED, lockmode);
   break;

  case AT_AddInherit:
   address = ATExecAddInherit(rel, (RangeVar *) cmd->def, lockmode);
   break;
  case AT_DropInherit:
   address = ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode);
   break;
  case AT_AddOf:
   address = ATExecAddOf(rel, (TypeName *) cmd->def, lockmode);
   break;
  case AT_DropOf:
   ATExecDropOf(rel, lockmode);
   break;
  case AT_ReplicaIdentity:
   ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
   break;
  case AT_EnableRowSecurity:
   ATExecSetRowSecurity(rel, true);
   break;
  case AT_DisableRowSecurity:
   ATExecSetRowSecurity(rel, false);
   break;
  case AT_ForceRowSecurity:
   ATExecForceNoForceRowSecurity(rel, true);
   break;
  case AT_NoForceRowSecurity:
   ATExecForceNoForceRowSecurity(rel, false);
   break;
  case AT_GenericOptions:
   ATExecGenericOptions(rel, (List *) cmd->def);
   break;
  case AT_AttachPartition:
   cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
           cur_pass, context);
   Assert(cmd != NULL);
   if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
    address = ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def,
            context);
   else
    address = ATExecAttachPartitionIdx(wqueue, rel,
               ((PartitionCmd *) cmd->def)->name);
   break;
  case AT_DetachPartition:
   cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
           cur_pass, context);
   Assert(cmd != NULL);
   /* ATPrepCmd ensures it must be a table */
   Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
   address = ATExecDetachPartition(wqueue, tab, rel,
           ((PartitionCmd *) cmd->def)->name,
           ((PartitionCmd *) cmd->def)->concurrent);
   break;
  case AT_DetachPartitionFinalize:
   address = ATExecDetachPartitionFinalize(rel, ((PartitionCmd *) cmd->def)->name);
   break;
  default:    /* oops */
   elog(ERROR, "unrecognized alter table type: %d",
     (int) cmd->subtype);
   break;
 }

 /*
  * Report the subcommand to interested event triggers.
 */

 if (cmd)
  EventTriggerCollectAlterTableSubcmd((Node *) cmd, address);

 /*
  * Bump the command counter to ensure the next subcommand in the sequence
  * can see the changes so far
 */

 CommandCounterIncrement();
}

/*
 * ATParseTransformCmd: perform parse transformation for one subcommand
 *
 * Returns the transformed subcommand tree, if there is one, else NULL.
 *
 * The parser may hand back additional AlterTableCmd(s) and/or other
 * utility statements, either before or after the original subcommand.
 * Other AlterTableCmds are scheduled into the appropriate slot of the
 * AlteredTableInfo (they had better be for later passes than the current one).
 * Utility statements that are supposed to happen before the AlterTableCmd
 * are executed immediately.  Those that are supposed to happen afterwards
 * are added to the tab->afterStmts list to be done at the very end.
 */

static AlterTableCmd *
ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
     AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
     AlterTablePass cur_pass, AlterTableUtilityContext *context)
{
 AlterTableCmd *newcmd = NULL;
 AlterTableStmt *atstmt = makeNode(AlterTableStmt);
 List    *beforeStmts;
 List    *afterStmts;
 ListCell   *lc;

 /* Gin up an AlterTableStmt with just this subcommand and this table */
 atstmt->relation =
  makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
      pstrdup(RelationGetRelationName(rel)),
      -1);
 atstmt->relation->inh = recurse;
 atstmt->cmds = list_make1(cmd);
 atstmt->objtype = OBJECT_TABLE; /* needn't be picky here */
 atstmt->missing_ok = false;

 /* Transform the AlterTableStmt */
 atstmt = transformAlterTableStmt(RelationGetRelid(rel),
          atstmt,
          context->queryString,
          &beforeStmts,
          &afterStmts);

 /* Execute any statements that should happen before these subcommand(s) */
 foreach(lc, beforeStmts)
 {
  Node    *stmt = (Node *) lfirst(lc);

  ProcessUtilityForAlterTable(stmt, context);
  CommandCounterIncrement();
 }

 /* Examine the transformed subcommands and schedule them appropriately */
 foreach(lc, atstmt->cmds)
 {
  AlterTableCmd *cmd2 = lfirst_node(AlterTableCmd, lc);
  AlterTablePass pass;

  /*
   * This switch need only cover the subcommand types that can be added
   * by parse_utilcmd.c; otherwise, we'll use the default strategy of
   * executing the subcommand immediately, as a substitute for the
   * original subcommand.  (Note, however, that this does cause
   * AT_AddConstraint subcommands to be rescheduled into later passes,
   * which is important for index and foreign key constraints.)
   *
   * We assume we needn't do any phase-1 checks for added subcommands.
 */

  switch (cmd2->subtype)
  {
   case AT_AddIndex:
    pass = AT_PASS_ADD_INDEX;
    break;
   case AT_AddIndexConstraint:
    pass = AT_PASS_ADD_INDEXCONSTR;
    break;
   case AT_AddConstraint:
    /* Recursion occurs during execution phase */
    if (recurse)
     cmd2->recurse = true;
    switch (castNode(Constraint, cmd2->def)->contype)
    {
     case CONSTR_NOTNULL:
      pass = AT_PASS_COL_ATTRS;
      break;
     case CONSTR_PRIMARY:
     case CONSTR_UNIQUE:
     case CONSTR_EXCLUSION:
      pass = AT_PASS_ADD_INDEXCONSTR;
      break;
     default:
      pass = AT_PASS_ADD_OTHERCONSTR;
      break;
    }
    break;
   case AT_AlterColumnGenericOptions:
    /* This command never recurses */
    /* No command-specific prep needed */
    pass = AT_PASS_MISC;
    break;
   default:
    pass = cur_pass;
    break;
  }

  if (pass < cur_pass)
  {
   /* Cannot schedule into a pass we already finished */
   elog(ERROR, "ALTER TABLE scheduling failure: too late for pass %d",
     pass);
  }
  else if (pass > cur_pass)
  {
   /* OK, queue it up for later */
   tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd2);
  }
  else
  {
   /*
    * We should see at most one subcommand for the current pass,
    * which is the transformed version of the original subcommand.
 */

   if (newcmd == NULL && cmd->subtype == cmd2->subtype)
   {
    /* Found the transformed version of our subcommand */
    newcmd = cmd2;
   }
   else
    elog(ERROR, "ALTER TABLE scheduling failure: bogus item for pass %d",
      pass);
  }
 }

 /* Queue up any after-statements to happen at the end */
 tab->afterStmts = list_concat(tab->afterStmts, afterStmts);

 return newcmd;
}

/*
 * ATRewriteTables: ALTER TABLE phase 3
 */

static void
ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
    AlterTableUtilityContext *context)
{
 ListCell   *ltab;

 /* Go through each table that needs to be checked or rewritten */
 foreach(ltab, *wqueue)
 {
  AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);

  /* Relations without storage may be ignored here */
  if (!RELKIND_HAS_STORAGE(tab->relkind))
   continue;

  /*
   * If we change column data types, the operation has to be propagated
   * to tables that use this table's rowtype as a column type.
   * tab->newvals will also be non-NULL in the case where we're adding a
   * column with a default.  We choose to forbid that case as well,
   * since composite types might eventually support defaults.
   *
   * (Eventually we'll probably need to check for composite type
   * dependencies even when we're just scanning the table without a
   * rewrite, but at the moment a composite type does not enforce any
   * constraints, so it's not necessary/appropriate to enforce them just
   * during ALTER.)
 */

  if (tab->newvals != NIL || tab->rewrite > 0)
  {
   Relation rel;

   rel = table_open(tab->relid, NoLock);
   find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
   table_close(rel, NoLock);
  }

  /*
   * We only need to rewrite the table if at least one column needs to
   * be recomputed, or we are changing its persistence or access method.
   *
   * There are two reasons for requiring a rewrite when changing
   * persistence: on one hand, we need to ensure that the buffers
   * belonging to each of the two relations are marked with or without
   * BM_PERMANENT properly.  On the other hand, since rewriting creates
   * and assigns a new relfilenumber, we automatically create or drop an
   * init fork for the relation as appropriate.
 */

  if (tab->rewrite > 0 && tab->relkind != RELKIND_SEQUENCE)
  {
   /* Build a temporary relation and copy data */
   Relation OldHeap;
   Oid   OIDNewHeap;
   Oid   NewAccessMethod;
   Oid   NewTableSpace;
   char  persistence;

   OldHeap = table_open(tab->relid, NoLock);

   /*
    * We don't support rewriting of system catalogs; there are too
    * many corner cases and too little benefit.  In particular this
    * is certainly not going to work for mapped catalogs.
 */

   if (IsSystemRelation(OldHeap))
    ereport(ERROR,
      (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
       errmsg("cannot rewrite system relation \"%s\"",
        RelationGetRelationName(OldHeap))));

   if (RelationIsUsedAsCatalogTable(OldHeap))
    ereport(ERROR,
      (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
       errmsg("cannot rewrite table \"%s\" used as a catalog table",
        RelationGetRelationName(OldHeap))));

   /*
    * Don't allow rewrite on temp tables of other backends ... their
    * local buffer manager is not going to cope.  (This is redundant
    * with the check in CheckAlterTableIsSafe, but for safety we'll
    * check here too.)
 */

   if (RELATION_IS_OTHER_TEMP(OldHeap))
    ereport(ERROR,
      (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
       errmsg("cannot rewrite temporary tables of other sessions")));

   /*
    * Select destination tablespace (same as original unless user
    * requested a change)
 */

   if (tab->newTableSpace)
    NewTableSpace = tab->newTableSpace;
   else
    NewTableSpace = OldHeap->rd_rel->reltablespace;

   /*
    * Select destination access method (same as original unless user
    * requested a change)
 */

   if (tab->chgAccessMethod)
    NewAccessMethod = tab->newAccessMethod;
   else
    NewAccessMethod = OldHeap->rd_rel->relam;

   /*
    * Select persistence of transient table (same as original unless
    * user requested a change)
 */

   persistence = tab->chgPersistence ?
    tab->newrelpersistence : OldHeap->rd_rel->relpersistence;

   table_close(OldHeap, NoLock);

   /*
    * Fire off an Event Trigger now, before actually rewriting the
    * table.
    *
    * We don't support Event Trigger for nested commands anywhere,
    * here included, and parsetree is given NULL when coming from
    * AlterTableInternal.
    *
    * And fire it only once.
 */

   if (parsetree)
    EventTriggerTableRewrite((Node *) parsetree,
           tab->relid,
           tab->rewrite);

   /*
    * Create transient table that will receive the modified data.
    *
    * Ensure it is marked correctly as logged or unlogged.  We have
    * to do this here so that buffers for the new relfilenumber will
    * have the right persistence set, and at the same time ensure
    * that the original filenumbers's buffers will get read in with
    * the correct setting (i.e. the original one).  Otherwise a
    * rollback after the rewrite would possibly result with buffers
    * for the original filenumbers having the wrong persistence
    * setting.
    *
    * NB: This relies on swap_relation_files() also swapping the
    * persistence. That wouldn't work for pg_class, but that can't be
    * unlogged anyway.
 */

   OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, NewAccessMethod,
            persistence, lockmode);

   /*
    * Copy the heap data into the new table with the desired
    * modifications, and test the current data within the table
    * against new constraints generated by ALTER TABLE commands.
 */

   ATRewriteTable(tab, OIDNewHeap);

   /*
    * Swap the physical files of the old and new heaps, then rebuild
    * indexes and discard the old heap.  We can use RecentXmin for
    * the table's new relfrozenxid because we rewrote all the tuples
    * in ATRewriteTable, so no older Xid remains in the table.  Also,
    * we never try to swap toast tables by content, since we have no
    * interest in letting this code work on system catalogs.
 */

   finish_heap_swap(tab->relid, OIDNewHeap,
        falsefalsetrue,
        !OidIsValid(tab->newTableSpace),
        RecentXmin,
        ReadNextMultiXactId(),
        persistence);

   InvokeObjectPostAlterHook(RelationRelationId, tab->relid, 0);
  }
  else if (tab->rewrite > 0 && tab->relkind == RELKIND_SEQUENCE)
  {
   if (tab->chgPersistence)
    SequenceChangePersistence(tab->relid, tab->newrelpersistence);
  }
  else
  {
   /*
    * If required, test the current data within the table against new
    * constraints generated by ALTER TABLE commands, but don't
    * rebuild data.
 */

   if (tab->constraints != NIL || tab->verify_new_notnull ||
    tab->partition_constraint != NULL)
    ATRewriteTable(tab, InvalidOid);

   /*
    * If we had SET TABLESPACE but no reason to reconstruct tuples,
    * just do a block-by-block copy.
 */

   if (tab->newTableSpace)
    ATExecSetTableSpace(tab->relid, tab->newTableSpace, lockmode);
  }

  /*
   * Also change persistence of owned sequences, so that it matches the
   * table persistence.
 */

  if (tab->chgPersistence)
  {
   List    *seqlist = getOwnedSequences(tab->relid);
   ListCell   *lc;

   foreach(lc, seqlist)
   {
    Oid   seq_relid = lfirst_oid(lc);

    SequenceChangePersistence(seq_relid, tab->newrelpersistence);
   }
  }
 }

 /*
  * Foreign key constraints are checked in a final pass, since (a) it's
  * generally best to examine each one separately, and (b) it's at least
  * theoretically possible that we have changed both relations of the
  * foreign key, and we'd better have finished both rewrites before we try
  * to read the tables.
 */

 foreach(ltab, *wqueue)
 {
  AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
  Relation rel = NULL;
  ListCell   *lcon;

  /* Relations without storage may be ignored here too */
  if (!RELKIND_HAS_STORAGE(tab->relkind))
   continue;

  foreach(lcon, tab->constraints)
  {
   NewConstraint *con = lfirst(lcon);

   if (con->contype == CONSTR_FOREIGN)
   {
    Constraint *fkconstraint = (Constraint *) con->qual;
    Relation refrel;

    if (rel == NULL)
    {
     /* Long since locked, no need for another */
     rel = table_open(tab->relid, NoLock);
    }

    refrel = table_open(con->refrelid, RowShareLock);

    validateForeignKeyConstraint(fkconstraint->conname, rel, refrel,
            con->refindid,
            con->conid,
            con->conwithperiod);

    /*
     * No need to mark the constraint row as validated, we did
     * that when we inserted the row earlier.
 */


    table_close(refrel, NoLock);
   }
  }

  if (rel)
   table_close(rel, NoLock);
 }

 /* Finally, run any afterStmts that were queued up */
 foreach(ltab, *wqueue)
 {
  AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
  ListCell   *lc;

  foreach(lc, tab->afterStmts)
  {
   Node    *stmt = (Node *) lfirst(lc);

   ProcessUtilityForAlterTable(stmt, context);
   CommandCounterIncrement();
  }
 }
}

/*
 * ATRewriteTable: scan or rewrite one table
 *
 * A rewrite is requested by passing a valid OIDNewHeap; in that case, caller
 * must already hold AccessExclusiveLock on it.
 */

static void
ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
{
 Relation oldrel;
 Relation newrel;
 TupleDesc oldTupDesc;
 TupleDesc newTupDesc;
 bool  needscan = false;
 List    *notnull_attrs;
 List    *notnull_virtual_attrs;
 int   i;
 ListCell   *l;
 EState    *estate;
 CommandId mycid;
 BulkInsertState bistate;
 int   ti_options;
 ExprState  *partqualstate = NULL;

 /*
  * Open the relation(s).  We have surely already locked the existing
  * table.
 */

 oldrel = table_open(tab->relid, NoLock);
 oldTupDesc = tab->oldDesc;
 newTupDesc = RelationGetDescr(oldrel); /* includes all mods */

 if (OidIsValid(OIDNewHeap))
 {
  Assert(CheckRelationOidLockedByMe(OIDNewHeap, AccessExclusiveLock,
            false));
  newrel = table_open(OIDNewHeap, NoLock);
 }
 else
  newrel = NULL;

 /*
  * Prepare a BulkInsertState and options for table_tuple_insert.  The FSM
  * is empty, so don't bother using it.
 */

 if (newrel)
 {
  mycid = GetCurrentCommandId(true);
  bistate = GetBulkInsertState();
  ti_options = TABLE_INSERT_SKIP_FSM;
 }
 else
 {
  /* keep compiler quiet about using these uninitialized */
  mycid = 0;
  bistate = NULL;
  ti_options = 0;
 }

 /*
  * Generate the constraint and default execution states
 */


 estate = CreateExecutorState();

 /* Build the needed expression execution states */
 foreach(l, tab->constraints)
 {
  NewConstraint *con = lfirst(l);

  switch (con->contype)
  {
   case CONSTR_CHECK:
    needscan = true;
    con->qualstate = ExecPrepareExpr((Expr *) expand_generated_columns_in_expr(con->qual, oldrel, 1), estate);
    break;
   case CONSTR_FOREIGN:
    /* Nothing to do here */
    break;
   default:
    elog(ERROR, "unrecognized constraint type: %d",
      (int) con->contype);
  }
 }

 /* Build expression execution states for partition check quals */
 if (tab->partition_constraint)
 {
  needscan = true;
  partqualstate = ExecPrepareExpr(tab->partition_constraint, estate);
 }

 foreach(l, tab->newvals)
 {
  NewColumnValue *ex = lfirst(l);

  /* expr already planned */
  ex->exprstate = ExecInitExpr((Expr *) ex->expr, NULL);
 }

 notnull_attrs = notnull_virtual_attrs = NIL;
 if (newrel || tab->verify_new_notnull)
 {
  /*
   * If we are rebuilding the tuples OR if we added any new but not
   * verified not-null constraints, check all *valid* not-null
   * constraints. This is a bit of overkill but it minimizes risk of
   * bugs.
   *
   * notnull_attrs does *not* collect attribute numbers for valid
   * not-null constraints over virtual generated columns; instead, they
   * are collected in notnull_virtual_attrs for verification elsewhere.
 */

  for (i = 0; i < newTupDesc->natts; i++)
  {
   CompactAttribute *attr = TupleDescCompactAttr(newTupDesc, i);

   if (attr->attnullability == ATTNULLABLE_VALID &&
    !attr->attisdropped)
   {
    Form_pg_attribute wholeatt = TupleDescAttr(newTupDesc, i);

    if (wholeatt->attgenerated != ATTRIBUTE_GENERATED_VIRTUAL)
     notnull_attrs = lappend_int(notnull_attrs, wholeatt->attnum);
    else
     notnull_virtual_attrs = lappend_int(notnull_virtual_attrs,
              wholeatt->attnum);
   }
  }
  if (notnull_attrs || notnull_virtual_attrs)
   needscan = true;
 }

 if (newrel || needscan)
 {
  ExprContext *econtext;
  TupleTableSlot *oldslot;
  TupleTableSlot *newslot;
  TableScanDesc scan;
  MemoryContext oldCxt;
  List    *dropped_attrs = NIL;
  ListCell   *lc;
  Snapshot snapshot;
  ResultRelInfo *rInfo = NULL;

  /*
   * When adding or changing a virtual generated column with a not-null
   * constraint, we need to evaluate whether the generation expression
   * is null.  For that, we borrow ExecRelGenVirtualNotNull().  Here, we
   * prepare a dummy ResultRelInfo.
 */

  if (notnull_virtual_attrs != NIL)
  {
   MemoryContext oldcontext;

   Assert(newTupDesc->constr->has_generated_virtual);
   Assert(newTupDesc->constr->has_not_null);
   oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
   rInfo = makeNode(ResultRelInfo);
   InitResultRelInfo(rInfo,
         oldrel,
         0/* dummy rangetable index */
         NULL,
         estate->es_instrument);
   MemoryContextSwitchTo(oldcontext);
  }

  if (newrel)
   ereport(DEBUG1,
     (errmsg_internal("rewriting table \"%s\"",
          RelationGetRelationName(oldrel))));
  else
   ereport(DEBUG1,
     (errmsg_internal("verifying table \"%s\"",
          RelationGetRelationName(oldrel))));

  if (newrel)
  {
   /*
    * All predicate locks on the tuples or pages are about to be made
    * invalid, because we move tuples around.  Promote them to
    * relation locks.
 */

   TransferPredicateLocksToHeapRelation(oldrel);
  }

  econtext = GetPerTupleExprContext(estate);

  /*
   * Create necessary tuple slots. When rewriting, two slots are needed,
   * otherwise one suffices. In the case where one slot suffices, we
   * need to use the new tuple descriptor, otherwise some constraints
   * can't be evaluated.  Note that even when the tuple layout is the
   * same and no rewrite is required, the tupDescs might not be
   * (consider ADD COLUMN without a default).
 */

  if (tab->rewrite)
  {
   Assert(newrel != NULL);
   oldslot = MakeSingleTupleTableSlot(oldTupDesc,
              table_slot_callbacks(oldrel));
   newslot = MakeSingleTupleTableSlot(newTupDesc,
              table_slot_callbacks(newrel));

   /*
    * Set all columns in the new slot to NULL initially, to ensure
    * columns added as part of the rewrite are initialized to NULL.
    * That is necessary as tab->newvals will not contain an
    * expression for columns with a NULL default, e.g. when adding a
    * column without a default together with a column with a default
    * requiring an actual rewrite.
 */

   ExecStoreAllNullTuple(newslot);
  }
  else
  {
   oldslot = MakeSingleTupleTableSlot(newTupDesc,
              table_slot_callbacks(oldrel));
   newslot = NULL;
  }

  /*
   * Any attributes that are dropped according to the new tuple
   * descriptor can be set to NULL. We precompute the list of dropped
   * attributes to avoid needing to do so in the per-tuple loop.
 */

  for (i = 0; i < newTupDesc->natts; i++)
  {
   if (TupleDescAttr(newTupDesc, i)->attisdropped)
    dropped_attrs = lappend_int(dropped_attrs, i);
  }

  /*
   * Scan through the rows, generating a new row if needed and then
   * checking all the constraints.
 */

  snapshot = RegisterSnapshot(GetLatestSnapshot());
  scan = table_beginscan(oldrel, snapshot, 0, NULL);

  /*
   * Switch to per-tuple memory context and reset it for each tuple
   * produced, so we don't leak memory.
 */

  oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));

  while (table_scan_getnextslot(scan, ForwardScanDirection, oldslot))
  {
   TupleTableSlot *insertslot;

   if (tab->rewrite > 0)
   {
    /* Extract data from old tuple */
    slot_getallattrs(oldslot);
    ExecClearTuple(newslot);

    /* copy attributes */
    memcpy(newslot->tts_values, oldslot->tts_values,
        sizeof(Datum) * oldslot->tts_nvalid);
    memcpy(newslot->tts_isnull, oldslot->tts_isnull,
        sizeof(bool) * oldslot->tts_nvalid);

    /* Set dropped attributes to null in new tuple */
    foreach(lc, dropped_attrs)
     newslot->tts_isnull[lfirst_int(lc)] = true;

    /*
     * Constraints and GENERATED expressions might reference the
     * tableoid column, so fill tts_tableOid with the desired
     * value.  (We must do this each time, because it gets
     * overwritten with newrel's OID during storing.)
 */

    newslot->tts_tableOid = RelationGetRelid(oldrel);

    /*
     * Process supplied expressions to replace selected columns.
     *
     * First, evaluate expressions whose inputs come from the old
     * tuple.
 */

    econtext->ecxt_scantuple = oldslot;

    foreach(l, tab->newvals)
    {
     NewColumnValue *ex = lfirst(l);

     if (ex->is_generated)
      continue;

     newslot->tts_values[ex->attnum - 1]
      = ExecEvalExpr(ex->exprstate,
            econtext,
            &newslot->tts_isnull[ex->attnum - 1]);
    }

    ExecStoreVirtualTuple(newslot);

    /*
     * Now, evaluate any expressions whose inputs come from the
     * new tuple.  We assume these columns won't reference each
     * other, so that there's no ordering dependency.
 */

    econtext->ecxt_scantuple = newslot;

    foreach(l, tab->newvals)
    {
     NewColumnValue *ex = lfirst(l);

     if (!ex->is_generated)
      continue;

     newslot->tts_values[ex->attnum - 1]
      = ExecEvalExpr(ex->exprstate,
            econtext,
            &newslot->tts_isnull[ex->attnum - 1]);
    }

    insertslot = newslot;
   }
   else
   {
    /*
     * If there's no rewrite, old and new table are guaranteed to
     * have the same AM, so we can just use the old slot to verify
     * new constraints etc.
 */

    insertslot = oldslot;
   }

   /* Now check any constraints on the possibly-changed tuple */
   econtext->ecxt_scantuple = insertslot;

   foreach_int(attn, notnull_attrs)
   {
    if (slot_attisnull(insertslot, attn))
    {
     Form_pg_attribute attr = TupleDescAttr(newTupDesc, attn - 1);

     ereport(ERROR,
       (errcode(ERRCODE_NOT_NULL_VIOLATION),
        errmsg("column \"%s\" of relation \"%s\" contains null values",
         NameStr(attr->attname),
         RelationGetRelationName(oldrel)),
        errtablecol(oldrel, attn)));
    }
   }

   if (notnull_virtual_attrs != NIL)
   {
    AttrNumber attnum;

    attnum = ExecRelGenVirtualNotNull(rInfo, insertslot,
              estate,
              notnull_virtual_attrs);
    if (attnum != InvalidAttrNumber)
    {
     Form_pg_attribute attr = TupleDescAttr(newTupDesc, attnum - 1);

     ereport(ERROR,
       errcode(ERRCODE_NOT_NULL_VIOLATION),
       errmsg("column \"%s\" of relation \"%s\" contains null values",
           NameStr(attr->attname),
           RelationGetRelationName(oldrel)),
       errtablecol(oldrel, attnum));
    }
   }

   foreach(l, tab->constraints)
   {
    NewConstraint *con = lfirst(l);

    switch (con->contype)
    {
     case CONSTR_CHECK:
      if (!ExecCheck(con->qualstate, econtext))
       ereport(ERROR,
         (errcode(ERRCODE_CHECK_VIOLATION),
          errmsg("check constraint \"%s\" of relation \"%s\" is violated by some row",
           con->name,
           RelationGetRelationName(oldrel)),
          errtableconstraint(oldrel, con->name)));
      break;
     case CONSTR_NOTNULL:
     case CONSTR_FOREIGN:
      /* Nothing to do here */
      break;
     default:
      elog(ERROR, "unrecognized constraint type: %d",
        (int) con->contype);
    }
   }

   if (partqualstate && !ExecCheck(partqualstate, econtext))
   {
    if (tab->validate_default)
     ereport(ERROR,
       (errcode(ERRCODE_CHECK_VIOLATION),
        errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
         RelationGetRelationName(oldrel)),
        errtable(oldrel)));
    else
     ereport(ERROR,
       (errcode(ERRCODE_CHECK_VIOLATION),
        errmsg("partition constraint of relation \"%s\" is violated by some row",
         RelationGetRelationName(oldrel)),
        errtable(oldrel)));
   }

   /* Write the tuple out to the new relation */
   if (newrel)
    table_tuple_insert(newrel, insertslot, mycid,
           ti_options, bistate);

   ResetExprContext(econtext);

   CHECK_FOR_INTERRUPTS();
  }

  MemoryContextSwitchTo(oldCxt);
  table_endscan(scan);
  UnregisterSnapshot(snapshot);

  ExecDropSingleTupleTableSlot(oldslot);
  if (newslot)
   ExecDropSingleTupleTableSlot(newslot);
 }

 FreeExecutorState(estate);

 table_close(oldrel, NoLock);
 if (newrel)
 {
  FreeBulkInsertState(bistate);

  table_finish_bulk_insert(newrel, ti_options);

  table_close(newrel, NoLock);
 }
}

/*
 * ATGetQueueEntry: find or create an entry in the ALTER TABLE work queue
 */

static AlteredTableInfo *
ATGetQueueEntry(List **wqueue, Relation rel)
{
 Oid   relid = RelationGetRelid(rel);
 AlteredTableInfo *tab;
 ListCell   *ltab;

 foreach(ltab, *wqueue)
 {
  tab = (AlteredTableInfo *) lfirst(ltab);
  if (tab->relid == relid)
   return tab;
 }

 /*
  * Not there, so add it.  Note that we make a copy of the relation's
  * existing descriptor before anything interesting can happen to it.
 */

 tab = (AlteredTableInfo *) palloc0(sizeof(AlteredTableInfo));
 tab->relid = relid;
 tab->rel = NULL;   /* set later */
 tab->relkind = rel->rd_rel->relkind;
 tab->oldDesc = CreateTupleDescCopyConstr(RelationGetDescr(rel));
 tab->newAccessMethod = InvalidOid;
 tab->chgAccessMethod = false;
 tab->newTableSpace = InvalidOid;
 tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
 tab->chgPersistence = false;

 *wqueue = lappend(*wqueue, tab);

 return tab;
}

static const char *
alter_table_type_to_string(AlterTableType cmdtype)
{
 switch (cmdtype)
 {
  case AT_AddColumn:
  case AT_AddColumnToView:
   return "ADD COLUMN";
  case AT_ColumnDefault:
  case AT_CookedColumnDefault:
   return "ALTER COLUMN ... SET DEFAULT";
  case AT_DropNotNull:
   return "ALTER COLUMN ... DROP NOT NULL";
  case AT_SetNotNull:
   return "ALTER COLUMN ... SET NOT NULL";
  case AT_SetExpression:
   return "ALTER COLUMN ... SET EXPRESSION";
  case AT_DropExpression:
   return "ALTER COLUMN ... DROP EXPRESSION";
  case AT_SetStatistics:
   return "ALTER COLUMN ... SET STATISTICS";
  case AT_SetOptions:
   return "ALTER COLUMN ... SET";
  case AT_ResetOptions:
   return "ALTER COLUMN ... RESET";
  case AT_SetStorage:
   return "ALTER COLUMN ... SET STORAGE";
  case AT_SetCompression:
   return "ALTER COLUMN ... SET COMPRESSION";
  case AT_DropColumn:
   return "DROP COLUMN";
  case AT_AddIndex:
  case AT_ReAddIndex:
   return NULL;  /* not real grammar */
  case AT_AddConstraint:
  case AT_ReAddConstraint:
  case AT_ReAddDomainConstraint:
  case AT_AddIndexConstraint:
   return "ADD CONSTRAINT";
  case AT_AlterConstraint:
   return "ALTER CONSTRAINT";
  case AT_ValidateConstraint:
   return "VALIDATE CONSTRAINT";
  case AT_DropConstraint:
   return "DROP CONSTRAINT";
  case AT_ReAddComment:
   return NULL;  /* not real grammar */
  case AT_AlterColumnType:
   return "ALTER COLUMN ... SET DATA TYPE";
  case AT_AlterColumnGenericOptions:
   return "ALTER COLUMN ... OPTIONS";
  case AT_ChangeOwner:
   return "OWNER TO";
  case AT_ClusterOn:
   return "CLUSTER ON";
  case AT_DropCluster:
   return "SET WITHOUT CLUSTER";
  case AT_SetAccessMethod:
   return "SET ACCESS METHOD";
  case AT_SetLogged:
   return "SET LOGGED";
  case AT_SetUnLogged:
   return "SET UNLOGGED";
  case AT_DropOids:
   return "SET WITHOUT OIDS";
  case AT_SetTableSpace:
   return "SET TABLESPACE";
  case AT_SetRelOptions:
   return "SET";
  case AT_ResetRelOptions:
   return "RESET";
  case AT_ReplaceRelOptions:
   return NULL;  /* not real grammar */
  case AT_EnableTrig:
   return "ENABLE TRIGGER";
  case AT_EnableAlwaysTrig:
   return "ENABLE ALWAYS TRIGGER";
  case AT_EnableReplicaTrig:
   return "ENABLE REPLICA TRIGGER";
  case AT_DisableTrig:
   return "DISABLE TRIGGER";
  case AT_EnableTrigAll:
   return "ENABLE TRIGGER ALL";
  case AT_DisableTrigAll:
   return "DISABLE TRIGGER ALL";
  case AT_EnableTrigUser:
   return "ENABLE TRIGGER USER";
  case AT_DisableTrigUser:
   return "DISABLE TRIGGER USER";
  case AT_EnableRule:
   return "ENABLE RULE";
  case AT_EnableAlwaysRule:
   return "ENABLE ALWAYS RULE";
  case AT_EnableReplicaRule:
   return "ENABLE REPLICA RULE";
  case AT_DisableRule:
   return "DISABLE RULE";
  case AT_AddInherit:
   return "INHERIT";
  case AT_DropInherit:
   return "NO INHERIT";
  case AT_AddOf:
   return "OF";
  case AT_DropOf:
   return "NOT OF";
  case AT_ReplicaIdentity:
   return "REPLICA IDENTITY";
  case AT_EnableRowSecurity:
   return "ENABLE ROW SECURITY";
  case AT_DisableRowSecurity:
   return "DISABLE ROW SECURITY";
  case AT_ForceRowSecurity:
   return "FORCE ROW SECURITY";
  case AT_NoForceRowSecurity:
   return "NO FORCE ROW SECURITY";
  case AT_GenericOptions:
   return "OPTIONS";
  case AT_AttachPartition:
   return "ATTACH PARTITION";
  case AT_DetachPartition:
   return "DETACH PARTITION";
  case AT_DetachPartitionFinalize:
   return "DETACH PARTITION ... FINALIZE";
  case AT_AddIdentity:
   return "ALTER COLUMN ... ADD IDENTITY";
  case AT_SetIdentity:
   return "ALTER COLUMN ... SET";
  case AT_DropIdentity:
   return "ALTER COLUMN ... DROP IDENTITY";
  case AT_ReAddStatistics:
   return NULL;  /* not real grammar */
 }

 return NULL;
}

/*
 * ATSimplePermissions
 *
 * - Ensure that it is a relation (or possibly a view)
 * - Ensure this user is the owner
 * - Ensure that it is not a system table
 */

static void
ATSimplePermissions(AlterTableType cmdtype, Relation rel, int allowed_targets)
{
 int   actual_target;

 switch (rel->rd_rel->relkind)
 {
  case RELKIND_RELATION:
   actual_target = ATT_TABLE;
   break;
  case RELKIND_PARTITIONED_TABLE:
   actual_target = ATT_PARTITIONED_TABLE;
   break;
  case RELKIND_VIEW:
   actual_target = ATT_VIEW;
   break;
  case RELKIND_MATVIEW:
   actual_target = ATT_MATVIEW;
   break;
  case RELKIND_INDEX:
   actual_target = ATT_INDEX;
   break;
  case RELKIND_PARTITIONED_INDEX:
   actual_target = ATT_PARTITIONED_INDEX;
   break;
  case RELKIND_COMPOSITE_TYPE:
   actual_target = ATT_COMPOSITE_TYPE;
   break;
  case RELKIND_FOREIGN_TABLE:
   actual_target = ATT_FOREIGN_TABLE;
   break;
  case RELKIND_SEQUENCE:
   actual_target = ATT_SEQUENCE;
   break;
  default:
   actual_target = 0;
   break;
 }

 /* Wrong target type? */
 if ((actual_target & allowed_targets) == 0)
 {
  const char *action_str = alter_table_type_to_string(cmdtype);

  if (action_str)
   ereport(ERROR,
     (errcode(ERRCODE_WRONG_OBJECT_TYPE),
   /* translator: %s is a group of some SQL keywords */
      errmsg("ALTER action %s cannot be performed on relation \"%s\"",
       action_str, RelationGetRelationName(rel)),
      errdetail_relkind_not_supported(rel->rd_rel->relkind)));
  else
   /* internal error? */
   elog(ERROR, "invalid ALTER action attempted on relation \"%s\"",
     RelationGetRelationName(rel));
 }

 /* Permissions checks */
 if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId()))
  aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
        RelationGetRelationName(rel));

 if (!allowSystemTableMods && IsSystemRelation(rel))
  ereport(ERROR,
    (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     errmsg("permission denied: \"%s\" is a system catalog",
      RelationGetRelationName(rel))));
}

/*
 * ATSimpleRecursion
 *
 * Simple table recursion sufficient for most ALTER TABLE operations.
 * All direct and indirect children are processed in an unspecified order.
 * Note that if a child inherits from the original table via multiple
 * inheritance paths, it will be visited just once.
 */

static void
ATSimpleRecursion(List **wqueue, Relation rel,
      AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
      AlterTableUtilityContext *context)
{
 /*
  * Propagate to children, if desired and if there are (or might be) any
  * children.
 */

 if (recurse && rel->rd_rel->relhassubclass)
 {
  Oid   relid = RelationGetRelid(rel);
  ListCell   *child;
  List    *children;

  children = find_all_inheritors(relid, lockmode, NULL);

  /*
   * find_all_inheritors does the recursive search of the inheritance
   * hierarchy, so all we have to do is process all of the relids in the
   * list that it returns.
 */

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

   if (childrelid == relid)
    continue;
   /* find_all_inheritors already got lock */
   childrel = relation_open(childrelid, NoLock);
   CheckAlterTableIsSafe(childrel);
   ATPrepCmd(wqueue, childrel, cmd, falsetrue, lockmode, context);
   relation_close(childrel, NoLock);
  }
 }
}

/*
 * Obtain list of partitions of the given table, locking them all at the given
 * lockmode and ensuring that they all pass CheckAlterTableIsSafe.
 *
 * This function is a no-op if the given relation is not a partitioned table;
 * in particular, nothing is done if it's a legacy inheritance parent.
 */

static void
ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode)
{
 if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 {
  List    *inh;
  ListCell   *cell;

  inh = find_all_inheritors(RelationGetRelid(rel), lockmode, NULL);
  /* first element is the parent rel; must ignore it */
  for_each_from(cell, inh, 1)
  {
   Relation childrel;

   /* find_all_inheritors already got lock */
   childrel = table_open(lfirst_oid(cell), NoLock);
   CheckAlterTableIsSafe(childrel);
   table_close(childrel, NoLock);
  }
  list_free(inh);
 }
}

/*
 * ATTypedTableRecursion
 *
 * Propagate ALTER TYPE operations to the typed tables of that type.
 * Also check the RESTRICT/CASCADE behavior.  Given CASCADE, also permit
 * recursion to inheritance children of the typed tables.
 */

static void
ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
       LOCKMODE lockmode, AlterTableUtilityContext *context)
{
 ListCell   *child;
 List    *children;

 Assert(rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE);

 children = find_typed_table_dependencies(rel->rd_rel->reltype,
            RelationGetRelationName(rel),
            cmd->behavior);

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

  childrel = relation_open(childrelid, lockmode);
  CheckAlterTableIsSafe(childrel);
  ATPrepCmd(wqueue, childrel, cmd, truetrue, lockmode, context);
  relation_close(childrel, NoLock);
 }
}


/*
 * find_composite_type_dependencies
 *
 * Check to see if the type "typeOid" is being used as a column in some table
 * (possibly nested several levels deep in composite types, arrays, etc!).
 * Eventually, we'd like to propagate the check or rewrite operation
 * into such tables, but for now, just error out if we find any.
 *
 * Caller should provide either the associated relation of a rowtype,
 * or a type name (not both) for use in the error message, if any.
 *
 * Note that "typeOid" is not necessarily a composite type; it could also be
 * another container type such as an array or range, or a domain over one of
 * these things.  The name of this function is therefore somewhat historical,
 * but it's not worth changing.
 *
 * We assume that functions and views depending on the type are not reasons
 * to reject the ALTER.  (How safe is this really?)
 */

void
find_composite_type_dependencies(Oid typeOid, Relation origRelation,
         const char *origTypeName)
{
 Relation depRel;
 ScanKeyData key[2];
 SysScanDesc depScan;
 HeapTuple depTup;

 /* since this function recurses, it could be driven to stack overflow */
 check_stack_depth();

 /*
  * We scan pg_depend to find those things that depend on the given type.
  * (We assume we can ignore refobjsubid for a type.)
 */

 depRel = table_open(DependRelationId, AccessShareLock);

 ScanKeyInit(&key[0],
    Anum_pg_depend_refclassid,
    BTEqualStrategyNumber, F_OIDEQ,
    ObjectIdGetDatum(TypeRelationId));
 ScanKeyInit(&key[1],
    Anum_pg_depend_refobjid,
    BTEqualStrategyNumber, F_OIDEQ,
    ObjectIdGetDatum(typeOid));

 depScan = systable_beginscan(depRel, DependReferenceIndexId, true,
         NULL, 2, key);

 while (HeapTupleIsValid(depTup = systable_getnext(depScan)))
 {
  Form_pg_depend pg_depend = (Form_pg_depend) GETSTRUCT(depTup);
  Relation rel;
  TupleDesc tupleDesc;
  Form_pg_attribute att;

  /* Check for directly dependent types */
  if (pg_depend->classid == TypeRelationId)
  {
   /*
    * This must be an array, domain, or range containing the given
    * type, so recursively check for uses of this type.  Note that
    * any error message will mention the original type not the
    * container; this is intentional.
 */

   find_composite_type_dependencies(pg_depend->objid,
            origRelation, origTypeName);
   continue;
  }

  /* Else, ignore dependees that aren't relations */
  if (pg_depend->classid != RelationRelationId)
   continue;

  rel = relation_open(pg_depend->objid, AccessShareLock);
  tupleDesc = RelationGetDescr(rel);

  /*
   * If objsubid identifies a specific column, refer to that in error
   * messages.  Otherwise, search to see if there's a user column of the
   * type.  (We assume system columns are never of interesting types.)
   * The search is needed because an index containing an expression
   * column of the target type will just be recorded as a whole-relation
   * dependency.  If we do not find a column of the type, the dependency
   * must indicate that the type is transiently referenced in an index
   * expression but not stored on disk, which we assume is OK, just as
   * we do for references in views.  (It could also be that the target
   * type is embedded in some container type that is stored in an index
   * column, but the previous recursion should catch such cases.)
 */

  if (pg_depend->objsubid > 0 && pg_depend->objsubid <= tupleDesc->natts)
   att = TupleDescAttr(tupleDesc, pg_depend->objsubid - 1);
  else
  {
   att = NULL;
   for (int attno = 1; attno <= tupleDesc->natts; attno++)
   {
    att = TupleDescAttr(tupleDesc, attno - 1);
    if (att->atttypid == typeOid && !att->attisdropped)
     break;
    att = NULL;
   }
   if (att == NULL)
   {
    /* No such column, so assume OK */
    relation_close(rel, AccessShareLock);
    continue;
   }
  }

  /*
   * We definitely should reject if the relation has storage.  If it's
   * partitioned, then perhaps we don't have to reject: if there are
   * partitions then we'll fail when we find one, else there is no
   * stored data to worry about.  However, it's possible that the type
   * change would affect conclusions about whether the type is sortable
   * or hashable and thus (if it's a partitioning column) break the
   * partitioning rule.  For now, reject for partitioned rels too.
 */

  if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind) ||
   RELKIND_HAS_PARTITIONS(rel->rd_rel->relkind))
  {
   if (origTypeName)
    ereport(ERROR,
      (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
       errmsg("cannot alter type \"%s\" because column \"%s.%s\" uses it",
        origTypeName,
        RelationGetRelationName(rel),
        NameStr(att->attname))));
   else if (origRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
    ereport(ERROR,
      (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
       errmsg("cannot alter type \"%s\" because column \"%s.%s\" uses it",
        RelationGetRelationName(origRelation),
        RelationGetRelationName(rel),
        NameStr(att->attname))));
   else if (origRelation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
    ereport(ERROR,
      (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
       errmsg("cannot alter foreign table \"%s\" because column \"%s.%s\" uses its row type",
        RelationGetRelationName(origRelation),
        RelationGetRelationName(rel),
        NameStr(att->attname))));
   else
    ereport(ERROR,
      (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
       errmsg("cannot alter table \"%s\" because column \"%s.%s\" uses its row type",
        RelationGetRelationName(origRelation),
        RelationGetRelationName(rel),
        NameStr(att->attname))));
  }
  else if (OidIsValid(rel->rd_rel->reltype))
  {
   /*
    * A view or composite type itself isn't a problem, but we must
    * recursively check for indirect dependencies via its rowtype.
 */

   find_composite_type_dependencies(rel->rd_rel->reltype,
            origRelation, origTypeName);
  }

  relation_close(rel, AccessShareLock);
 }

 systable_endscan(depScan);

 relation_close(depRel, AccessShareLock);
}


/*
 * find_typed_table_dependencies
 *
 * Check to see if a composite type is being used as the type of a
 * typed table.  Abort if any are found and behavior is RESTRICT.
 * Else return the list of tables.
 */

static List *
find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior behavior)
{
 Relation classRel;
 ScanKeyData key[1];
 TableScanDesc scan;
 HeapTuple tuple;
 List    *result = NIL;

 classRel = table_open(RelationRelationId, AccessShareLock);

 ScanKeyInit(&key[0],
    Anum_pg_class_reloftype,
    BTEqualStrategyNumber, F_OIDEQ,
    ObjectIdGetDatum(typeOid));

 scan = table_beginscan_catalog(classRel, 1, key);

 while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
 {
  Form_pg_class classform = (Form_pg_class) GETSTRUCT(tuple);

  if (behavior == DROP_RESTRICT)
   ereport(ERROR,
     (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
      errmsg("cannot alter type \"%s\" because it is the type of a typed table",
       typeName),
      errhint("Use ALTER ... CASCADE to alter the typed tables too.")));
  else
   result = lappend_oid(result, classform->oid);
 }

 table_endscan(scan);
 table_close(classRel, AccessShareLock);

 return result;
}


/*
 * check_of_type
 *
 * Check whether a type is suitable for CREATE TABLE OF/ALTER TABLE OF.  If it
 * isn't suitable, throw an error.  Currently, we require that the type
 * originated with CREATE TYPE AS.  We could support any row type, but doing so
 * would require handling a number of extra corner cases in the DDL commands.
 * (Also, allowing domain-over-composite would open up a can of worms about
 * whether and how the domain's constraints should apply to derived tables.)
 */

void
check_of_type(HeapTuple typetuple)
{
 Form_pg_type typ = (Form_pg_type) GETSTRUCT(typetuple);
 bool  typeOk = false;

 if (typ->typtype == TYPTYPE_COMPOSITE)
 {
  Relation typeRelation;

  Assert(OidIsValid(typ->typrelid));
  typeRelation = relation_open(typ->typrelid, AccessShareLock);
  typeOk = (typeRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE);

  /*
   * Close the parent rel, but keep our AccessShareLock on it until xact
   * commit.  That will prevent someone else from deleting or ALTERing
   * the type before the typed table creation/conversion commits.
 */

  relation_close(typeRelation, NoLock);

  if (!typeOk)
   ereport(ERROR,
     (errcode(ERRCODE_WRONG_OBJECT_TYPE),
      errmsg("type %s is the row type of another table",
       format_type_be(typ->oid)),
      errdetail("A typed table must use a stand-alone composite type created with CREATE TYPE.")));
 }
 else
  ereport(ERROR,
    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     errmsg("type %s is not a composite type",
      format_type_be(typ->oid))));
}


/*
 * ALTER TABLE ADD COLUMN
 *
 * Adds an additional attribute to a relation making the assumption that
 * CHECK, NOT NULL, and FOREIGN KEY constraints will be removed from the
 * AT_AddColumn AlterTableCmd by parse_utilcmd.c and added as independent
 * AlterTableCmd's.
 *
 * ADD COLUMN cannot use the normal ALTER TABLE recursion mechanism, because we
 * have to decide at runtime whether to recurse or not depending on whether we
 * actually add a column or merely merge with an existing column.  (We can't
 * check this in a static pre-pass because it won't handle multiple inheritance
 * situations correctly.)
 */

static void
ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
    bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode,
    AlterTableUtilityContext *context)
{
 if (rel->rd_rel->reloftype && !recursing)
  ereport(ERROR,
    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     errmsg("cannot add column to typed table")));

 if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
  ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);

 if (recurse && !is_view)
  cmd->recurse = true;
}

/*
 * Add a column to a table.  The return value is the address of the
 * new column in the parent relation.
 *
 * cmd is pass-by-ref so that we can replace it with the parse-transformed
 * copy (but that happens only after we check for IF NOT EXISTS).
 */

static ObjectAddress
ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
    AlterTableCmd **cmd, bool recurse, bool recursing,
    LOCKMODE lockmode, AlterTablePass cur_pass,
    AlterTableUtilityContext *context)
{
 Oid   myrelid = RelationGetRelid(rel);
 ColumnDef  *colDef = castNode(ColumnDef, (*cmd)->def);
 bool  if_not_exists = (*cmd)->missing_ok;
 Relation pgclass,
    attrdesc;
 HeapTuple reltup;
 Form_pg_class relform;
 Form_pg_attribute attribute;
 int   newattnum;
 char  relkind;
 Expr    *defval;
 List    *children;
 ListCell   *child;
 AlterTableCmd *childcmd;
 ObjectAddress address;
 TupleDesc tupdesc;

 /* since this function recurses, it could be driven to stack overflow */
 check_stack_depth();

 /* At top level, permission check was done in ATPrepCmd, else do it */
 if (recursing)
  ATSimplePermissions((*cmd)->subtype, rel,
       ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);

 if (rel->rd_rel->relispartition && !recursing)
  ereport(ERROR,
    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     errmsg("cannot add column to a partition")));

 attrdesc = table_open(AttributeRelationId, RowExclusiveLock);

 /*
  * Are we adding the column to a recursion child?  If so, check whether to
  * merge with an existing definition for the column.  If we do merge, we
  * must not recurse.  Children will already have the column, and recursing
  * into them would mess up attinhcount.
 */

 if (colDef->inhcount > 0)
 {
  HeapTuple tuple;

  /* Does child already have a column by this name? */
  tuple = SearchSysCacheCopyAttName(myrelid, colDef->colname);
  if (HeapTupleIsValid(tuple))
  {
   Form_pg_attribute childatt = (Form_pg_attribute) GETSTRUCT(tuple);
   Oid   ctypeId;
   int32  ctypmod;
   Oid   ccollid;

   /* Child column must match on type, typmod, and collation */
   typenameTypeIdAndMod(NULL, colDef->typeName, &ctypeId, &ctypmod);
   if (ctypeId != childatt->atttypid ||
    ctypmod != childatt->atttypmod)
    ereport(ERROR,
      (errcode(ERRCODE_DATATYPE_MISMATCH),
       errmsg("child table \"%s\" has different type for column \"%s\"",
        RelationGetRelationName(rel), colDef->colname)));
   ccollid = GetColumnDefCollation(NULL, colDef, ctypeId);
   if (ccollid != childatt->attcollation)
    ereport(ERROR,
      (errcode(ERRCODE_COLLATION_MISMATCH),
       errmsg("child table \"%s\" has different collation for column \"%s\"",
        RelationGetRelationName(rel), colDef->colname),
       errdetail("\"%s\" versus \"%s\"",
           get_collation_name(ccollid),
           get_collation_name(childatt->attcollation))));

   /* Bump the existing child att's inhcount */
   if (pg_add_s16_overflow(childatt->attinhcount, 1,
         &childatt->attinhcount))
    ereport(ERROR,
      errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
      errmsg("too many inheritance parents"));
   CatalogTupleUpdate(attrdesc, &tuple->t_self, tuple);

   heap_freetuple(tuple);

   /* Inform the user about the merge */
   ereport(NOTICE,
     (errmsg("merging definition of column \"%s\" for child \"%s\"",
       colDef->colname, RelationGetRelationName(rel))));

   table_close(attrdesc, RowExclusiveLock);

   /* Make the child column change visible */
   CommandCounterIncrement();

   return InvalidObjectAddress;
  }
 }

 /* skip if the name already exists and if_not_exists is true */
 if (!check_for_column_name_collision(rel, colDef->colname, if_not_exists))
 {
  table_close(attrdesc, RowExclusiveLock);
  return InvalidObjectAddress;
 }

 /*
  * Okay, we need to add the column, so go ahead and do parse
  * transformation.  This can result in queueing up, or even immediately
  * executing, subsidiary operations (such as creation of unique indexes);
  * so we mustn't do it until we have made the if_not_exists check.
  *
  * When recursing, the command was already transformed and we needn't do
  * so again.  Also, if context isn't given we can't transform.  (That
  * currently happens only for AT_AddColumnToView; we expect that view.c
  * passed us a ColumnDef that doesn't need work.)
 */

 if (context != NULL && !recursing)
 {
  *cmd = ATParseTransformCmd(wqueue, tab, rel, *cmd, recurse, lockmode,
           cur_pass, context);
  Assert(*cmd != NULL);
  colDef = castNode(ColumnDef, (*cmd)->def);
 }

 /*
  * Regular inheritance children are independent enough not to inherit the
  * identity column from parent hence cannot recursively add identity
  * column if the table has inheritance children.
  *
  * Partitions, on the other hand, are integral part of a partitioned table
  * and inherit identity column.  Hence propagate identity column down the
  * partition hierarchy.
 */

 if (colDef->identity &&
  recurse &&
  rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
  find_inheritance_children(myrelid, NoLock) != NIL)
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
     errmsg("cannot recursively add identity column to table that has child tables")));

 pgclass = table_open(RelationRelationId, RowExclusiveLock);

 reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
 if (!HeapTupleIsValid(reltup))
  elog(ERROR, "cache lookup failed for relation %u", myrelid);
 relform = (Form_pg_class) GETSTRUCT(reltup);
 relkind = relform->relkind;

 /* Determine the new attribute's number */
 newattnum = relform->relnatts + 1;
 if (newattnum > MaxHeapAttributeNumber)
  ereport(ERROR,
    (errcode(ERRCODE_TOO_MANY_COLUMNS),
     errmsg("tables can have at most %d columns",
      MaxHeapAttributeNumber)));

 /*
  * Construct new attribute's pg_attribute entry.
 */

 tupdesc = BuildDescForRelation(list_make1(colDef));

 attribute = TupleDescAttr(tupdesc, 0);

 /* Fix up attribute number */
 attribute->attnum = newattnum;

 /* make sure datatype is legal for a column */
 CheckAttributeType(NameStr(attribute->attname), attribute->atttypid, attribute->attcollation,
        list_make1_oid(rel->rd_rel->reltype),
        (attribute->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL ? CHKATYPE_IS_VIRTUAL : 0));

 InsertPgAttributeTuples(attrdesc, tupdesc, myrelid, NULL, NULL);

 table_close(attrdesc, RowExclusiveLock);

 /*
  * Update pg_class tuple as appropriate
 */

 relform->relnatts = newattnum;

 CatalogTupleUpdate(pgclass, &reltup->t_self, reltup);

 heap_freetuple(reltup);

 /* Post creation hook for new attribute */
 InvokeObjectPostCreateHook(RelationRelationId, myrelid, newattnum);

 table_close(pgclass, RowExclusiveLock);

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

 /*
  * Store the DEFAULT, if any, in the catalogs
 */

 if (colDef->raw_default)
 {
  RawColumnDefault *rawEnt;

  rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
  rawEnt->attnum = attribute->attnum;
  rawEnt->raw_default = copyObject(colDef->raw_default);
  rawEnt->generated = colDef->generated;

  /*
   * This function is intended for CREATE TABLE, so it processes a
   * _list_ of defaults, but we just do one.
 */

  AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
          falsetruefalse, NULL);

  /* Make the additional catalog changes visible */
  CommandCounterIncrement();
 }

 /*
  * Tell Phase 3 to fill in the default expression, if there is one.
  *
  * If there is no default, Phase 3 doesn't have to do anything, because
  * that effectively means that the default is NULL.  The heap tuple access
  * routines always check for attnum > # of attributes in tuple, and return
  * NULL if so, so without any modification of the tuple data we will get
  * the effect of NULL values in the new column.
  *
  * An exception occurs when the new column is of a domain type: the domain
  * might have a not-null constraint, or a check constraint that indirectly
  * rejects nulls.  If there are any domain constraints then we construct
  * an explicit NULL default value that will be passed through
  * CoerceToDomain processing.  (This is a tad inefficient, since it causes
  * rewriting the table which we really wouldn't have to do; but we do it
  * to preserve the historical behavior that such a failure will be raised
  * only if the table currently contains some rows.)
  *
  * Note: we use build_column_default, and not just the cooked default
  * returned by AddRelationNewConstraints, so that the right thing happens
  * when a datatype's default applies.
  *
  * Note: it might seem that this should happen at the end of Phase 2, so
  * that the effects of subsequent subcommands can be taken into account.
  * It's intentional that we do it now, though.  The new column should be
  * filled according to what is said in the ADD COLUMN subcommand, so that
  * the effects are the same as if this subcommand had been run by itself
  * and the later subcommands had been issued in new ALTER TABLE commands.
  *
  * We can skip this entirely for relations without storage, since Phase 3
  * is certainly not going to touch them.
 */

 if (RELKIND_HAS_STORAGE(relkind))
 {
  bool  has_domain_constraints;
  bool  has_missing = false;

  /*
   * For an identity column, we can't use build_column_default(),
   * because the sequence ownership isn't set yet.  So do it manually.
 */

  if (colDef->identity)
  {
   NextValueExpr *nve = makeNode(NextValueExpr);

   nve->seqid = RangeVarGetRelid(colDef->identitySequence, NoLock, false);
   nve->typeId = attribute->atttypid;

   defval = (Expr *) nve;
  }
  else
   defval = (Expr *) build_column_default(rel, attribute->attnum);

  /* Build CoerceToDomain(NULL) expression if needed */
  has_domain_constraints = DomainHasConstraints(attribute->atttypid);
  if (!defval && has_domain_constraints)
  {
   Oid   baseTypeId;
   int32  baseTypeMod;
   Oid   baseTypeColl;

   baseTypeMod = attribute->atttypmod;
   baseTypeId = getBaseTypeAndTypmod(attribute->atttypid, &baseTypeMod);
   baseTypeColl = get_typcollation(baseTypeId);
   defval = (Expr *) makeNullConst(baseTypeId, baseTypeMod, baseTypeColl);
   defval = (Expr *) coerce_to_target_type(NULL,
             (Node *) defval,
             baseTypeId,
             attribute->atttypid,
             attribute->atttypmod,
             COERCION_ASSIGNMENT,
             COERCE_IMPLICIT_CAST,
             -1);
   if (defval == NULL) /* should not happen */
    elog(ERROR, "failed to coerce base type to domain");
  }

  if (defval)
  {
   NewColumnValue *newval;

   /* Prepare defval for execution, either here or in Phase 3 */
   defval = expression_planner(defval);

   /* Add the new default to the newvals list */
   newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
   newval->attnum = attribute->attnum;
   newval->expr = defval;
   newval->is_generated = (colDef->generated != '\0');

   tab->newvals = lappend(tab->newvals, newval);

   /*
    * Attempt to skip a complete table rewrite by storing the
    * specified DEFAULT value outside of the heap.  This is only
    * allowed for plain relations and non-generated columns, and the
    * default expression can't be volatile (stable is OK).  Note that
    * contain_volatile_functions deems CoerceToDomain immutable, but
    * here we consider that coercion to a domain with constraints is
    * volatile; else it might fail even when the table is empty.
 */

   if (rel->rd_rel->relkind == RELKIND_RELATION &&
    !colDef->generated &&
    !has_domain_constraints &&
    !contain_volatile_functions((Node *) defval))
   {
    EState    *estate;
    ExprState  *exprState;
    Datum  missingval;
    bool  missingIsNull;

    /* Evaluate the default expression */
    estate = CreateExecutorState();
    exprState = ExecPrepareExpr(defval, estate);
    missingval = ExecEvalExpr(exprState,
            GetPerTupleExprContext(estate),
            &missingIsNull);
    /* If it turns out NULL, nothing to do; else store it */
    if (!missingIsNull)
    {
     StoreAttrMissingVal(rel, attribute->attnum, missingval);
     /* Make the additional catalog change visible */
     CommandCounterIncrement();
     has_missing = true;
    }
    FreeExecutorState(estate);
   }
   else
   {
    /*
     * Failed to use missing mode.  We have to do a table rewrite
     * to install the value --- unless it's a virtual generated
     * column.
 */

    if (colDef->generated != ATTRIBUTE_GENERATED_VIRTUAL)
     tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
   }
  }

  if (!has_missing)
  {
   /*
    * If the new column is NOT NULL, and there is no missing value,
    * tell Phase 3 it needs to check for NULLs.
 */

   tab->verify_new_notnull |= colDef->is_not_null;
  }
 }

 /*
  * Add needed dependency entries for the new column.
 */

 add_column_datatype_dependency(myrelid, newattnum, attribute->atttypid);
 add_column_collation_dependency(myrelid, newattnum, attribute->attcollation);

 /*
  * Propagate to children as appropriate.  Unlike most other ALTER
  * routines, we have to do this one level of recursion at a time; we can't
  * use find_all_inheritors to do it in one pass.
 */

 children =
  find_inheritance_children(RelationGetRelid(rel), lockmode);

 /*
  * If we are told not to recurse, there had better not be any child
  * tables; else the addition would put them out of step.
 */

 if (children && !recurse)
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
     errmsg("column must be added to child tables too")));

 /* Children should see column as singly inherited */
 if (!recursing)
 {
  childcmd = copyObject(*cmd);
  colDef = castNode(ColumnDef, childcmd->def);
  colDef->inhcount = 1;
  colDef->is_local = false;
 }
 else
  childcmd = *cmd;  /* no need to copy again */

 foreach(child, children)
 {
  Oid   childrelid = lfirst_oid(child);
  Relation childrel;
  AlteredTableInfo *childtab;

  /* find_inheritance_children already got lock */
  childrel = table_open(childrelid, NoLock);
  CheckAlterTableIsSafe(childrel);

  /* Find or create work queue entry for this table */
  childtab = ATGetQueueEntry(wqueue, childrel);

  /* Recurse to child; return value is ignored */
  ATExecAddColumn(wqueue, childtab, childrel,
      &childcmd, recurse, true,
      lockmode, cur_pass, context);

  table_close(childrel, NoLock);
 }

 ObjectAddressSubSet(address, RelationRelationId, myrelid, newattnum);
 return address;
}

/*
 * If a new or renamed column will collide with the name of an existing
 * column and if_not_exists is false then error out, else do nothing.
 */

static bool
check_for_column_name_collision(Relation rel, const char *colname,
        bool if_not_exists)
{
 HeapTuple attTuple;
 int   attnum;

 /*
  * this test is deliberately not attisdropped-aware, since if one tries to
  * add a column matching a dropped column name, it's gonna fail anyway.
 */

 attTuple = SearchSysCache2(ATTNAME,
          ObjectIdGetDatum(RelationGetRelid(rel)),
          PointerGetDatum(colname));
 if (!HeapTupleIsValid(attTuple))
  return true;

 attnum = ((Form_pg_attribute) GETSTRUCT(attTuple))->attnum;
 ReleaseSysCache(attTuple);

 /*
  * We throw a different error message for conflicts with system column
  * names, since they are normally not shown and the user might otherwise
  * be confused about the reason for the conflict.
 */

 if (attnum <= 0)
  ereport(ERROR,
    (errcode(ERRCODE_DUPLICATE_COLUMN),
     errmsg("column name \"%s\" conflicts with a system column name",
      colname)));
 else
 {
  if (if_not_exists)
  {
   ereport(NOTICE,
     (errcode(ERRCODE_DUPLICATE_COLUMN),
      errmsg("column \"%s\" of relation \"%s\" already exists, skipping",
       colname, RelationGetRelationName(rel))));
   return false;
  }

  ereport(ERROR,
    (errcode(ERRCODE_DUPLICATE_COLUMN),
     errmsg("column \"%s\" of relation \"%s\" already exists",
      colname, RelationGetRelationName(rel))));
 }

 return true;
}

/*
 * Install a column's dependency on its datatype.
 */

static void
add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid)
{
 ObjectAddress myself,
    referenced;

 myself.classId = RelationRelationId;
 myself.objectId = relid;
 myself.objectSubId = attnum;
 referenced.classId = TypeRelationId;
 referenced.objectId = typid;
 referenced.objectSubId = 0;
 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}

/*
 * Install a column's dependency on its collation.
 */

static void
add_column_collation_dependency(Oid relid, int32 attnum, Oid collid)
{
 ObjectAddress myself,
    referenced;

 /* We know the default collation is pinned, so don't bother recording it */
 if (OidIsValid(collid) && collid != DEFAULT_COLLATION_OID)
 {
  myself.classId = RelationRelationId;
  myself.objectId = relid;
  myself.objectSubId = attnum;
  referenced.classId = CollationRelationId;
  referenced.objectId = collid;
  referenced.objectSubId = 0;
  recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 }
}

/*
 * ALTER TABLE ALTER COLUMN DROP NOT NULL
 *
 * Return the address of the modified column.  If the column was already
 * nullable, InvalidObjectAddress is returned.
 */

static ObjectAddress
ATExecDropNotNull(Relation rel, const char *colName, bool recurse,
      LOCKMODE lockmode)
{
 HeapTuple tuple;
 HeapTuple conTup;
 Form_pg_attribute attTup;
 AttrNumber attnum;
 Relation attr_rel;
 ObjectAddress address;

 /*
  * lookup the attribute
 */

 attr_rel = table_open(AttributeRelationId, RowExclusiveLock);

 tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
 if (!HeapTupleIsValid(tuple))
  ereport(ERROR,
    (errcode(ERRCODE_UNDEFINED_COLUMN),
     errmsg("column \"%s\" of relation \"%s\" does not exist",
      colName, RelationGetRelationName(rel))));
 attTup = (Form_pg_attribute) GETSTRUCT(tuple);
 attnum = attTup->attnum;
 ObjectAddressSubSet(address, RelationRelationId,
      RelationGetRelid(rel), attnum);

 /* If the column is already nullable there's nothing to do. */
 if (!attTup->attnotnull)
 {
  table_close(attr_rel, RowExclusiveLock);
  return InvalidObjectAddress;
 }

 /* Prevent them from altering a system attribute */
 if (attnum <= 0)
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("cannot alter system column \"%s\"",
      colName)));

 if (attTup->attidentity)
  ereport(ERROR,
    (errcode(ERRCODE_SYNTAX_ERROR),
     errmsg("column \"%s\" of relation \"%s\" is an identity column",
      colName, RelationGetRelationName(rel))));

 /*
  * If rel is partition, shouldn't drop NOT NULL if parent has the same.
 */

 if (rel->rd_rel->relispartition)
 {
  Oid   parentId = get_partition_parent(RelationGetRelid(rel), false);
  Relation parent = table_open(parentId, AccessShareLock);
  TupleDesc tupDesc = RelationGetDescr(parent);
  AttrNumber parent_attnum;

  parent_attnum = get_attnum(parentId, colName);
  if (TupleDescAttr(tupDesc, parent_attnum - 1)->attnotnull)
   ereport(ERROR,
     (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
      errmsg("column \"%s\" is marked NOT NULL in parent table",
       colName)));
  table_close(parent, AccessShareLock);
 }

 /*
  * Find the constraint that makes this column NOT NULL, and drop it.
  * dropconstraint_internal() resets attnotnull.
 */

 conTup = findNotNullConstraintAttnum(RelationGetRelid(rel), attnum);
 if (conTup == NULL)
  elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation \"%s\"",
    colName, RelationGetRelationName(rel));

 /* The normal case: we have a pg_constraint row, remove it */
 dropconstraint_internal(rel, conTup, DROP_RESTRICT, recurse, false,
       false, lockmode);
 heap_freetuple(conTup);

 InvokeObjectPostAlterHook(RelationRelationId,
         RelationGetRelid(rel), attnum);

 table_close(attr_rel, RowExclusiveLock);

 return address;
}

/*
 * set_attnotnull
 *  Helper to update/validate the pg_attribute status of a not-null
 *  constraint
 *
 * pg_attribute.attnotnull is set true, if it isn't already.
 * If queue_validation is true, also set up wqueue to validate the constraint.
 * wqueue may be given as NULL when validation is not needed (e.g., on table
 * creation).
 */

static void
set_attnotnull(List **wqueue, Relation rel, AttrNumber attnum,
      bool is_valid, bool queue_validation)
{
 Form_pg_attribute attr;
 CompactAttribute *thisatt;

 Assert(!queue_validation || wqueue);

 CheckAlterTableIsSafe(rel);

 /*
  * Exit quickly by testing attnotnull from the tupledesc's copy of the
  * attribute.
 */

 attr = TupleDescAttr(RelationGetDescr(rel), attnum - 1);
 if (attr->attisdropped)
  return;

 if (!attr->attnotnull)
 {
  Relation attr_rel;
  HeapTuple tuple;

  attr_rel = table_open(AttributeRelationId, RowExclusiveLock);

  tuple = SearchSysCacheCopyAttNum(RelationGetRelid(rel), attnum);
  if (!HeapTupleIsValid(tuple))
   elog(ERROR, "cache lookup failed for attribute %d of relation %u",
     attnum, RelationGetRelid(rel));

  thisatt = TupleDescCompactAttr(RelationGetDescr(rel), attnum - 1);
  thisatt->attnullability = ATTNULLABLE_VALID;

  attr = (Form_pg_attribute) GETSTRUCT(tuple);

  attr->attnotnull = true;
  CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple);

  /*
   * If the nullness isn't already proven by validated constraints, have
   * ALTER TABLE phase 3 test for it.
 */

  if (queue_validation && wqueue &&
   !NotNullImpliedByRelConstraints(rel, attr))
  {
   AlteredTableInfo *tab;

   tab = ATGetQueueEntry(wqueue, rel);
   tab->verify_new_notnull = true;
  }

  CommandCounterIncrement();

  table_close(attr_rel, RowExclusiveLock);
  heap_freetuple(tuple);
 }
 else
 {
  CacheInvalidateRelcache(rel);
 }
}

/*
 * ALTER TABLE ALTER COLUMN SET NOT NULL
 *
 * Add a not-null constraint to a single table and its children.  Returns
 * the address of the constraint added to the parent relation, if one gets
 * added, or InvalidObjectAddress otherwise.
 *
 * We must recurse to child tables during execution, rather than using
 * ALTER TABLE's normal prep-time recursion.
 */

static ObjectAddress
ATExecSetNotNull(List **wqueue, Relation rel, char *conName, char *colName,
     bool recurse, bool recursing, LOCKMODE lockmode)
{
 HeapTuple tuple;
 AttrNumber attnum;
 ObjectAddress address;
 Constraint *constraint;
 CookedConstraint *ccon;
 List    *cooked;
 bool  is_no_inherit = false;

 /* Guard against stack overflow due to overly deep inheritance tree. */
 check_stack_depth();

 /* At top level, permission check was done in ATPrepCmd, else do it */
 if (recursing)
 {
  ATSimplePermissions(AT_AddConstraint, rel,
       ATT_PARTITIONED_TABLE | ATT_TABLE | ATT_FOREIGN_TABLE);
  Assert(conName != NULL);
 }

 attnum = get_attnum(RelationGetRelid(rel), colName);
 if (attnum == InvalidAttrNumber)
  ereport(ERROR,
    (errcode(ERRCODE_UNDEFINED_COLUMN),
     errmsg("column \"%s\" of relation \"%s\" does not exist",
      colName, RelationGetRelationName(rel))));

 /* Prevent them from altering a system attribute */
 if (attnum <= 0)
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("cannot alter system column \"%s\"",
      colName)));

 /* See if there's already a constraint */
 tuple = findNotNullConstraintAttnum(RelationGetRelid(rel), attnum);
 if (HeapTupleIsValid(tuple))
 {
  Form_pg_constraint conForm = (Form_pg_constraint) GETSTRUCT(tuple);
  bool  changed = false;

  /*
   * Don't let a NO INHERIT constraint be changed into inherit.
 */

  if (conForm->connoinherit && recurse)
   ereport(ERROR,
     errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("cannot change NO INHERIT status of NOT NULL constraint \"%s\" on relation \"%s\"",
         NameStr(conForm->conname),
         RelationGetRelationName(rel)));

  /*
   * If we find an appropriate constraint, we're almost done, but just
   * need to change some properties on it: if we're recursing, increment
   * coninhcount; if not, set conislocal if not already set.
 */

  if (recursing)
  {
   if (pg_add_s16_overflow(conForm->coninhcount, 1,
         &conForm->coninhcount))
    ereport(ERROR,
      errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
      errmsg("too many inheritance parents"));
   changed = true;
  }
  else if (!conForm->conislocal)
  {
   conForm->conislocal = true;
   changed = true;
  }
  else if (!conForm->convalidated)
  {
   /*
    * Flip attnotnull and convalidated, and also validate the
    * constraint.
 */

   return ATExecValidateConstraint(wqueue, rel, NameStr(conForm->conname),
           recurse, recursing, lockmode);
  }

  if (changed)
  {
   Relation constr_rel;

   constr_rel = table_open(ConstraintRelationId, RowExclusiveLock);

   CatalogTupleUpdate(constr_rel, &tuple->t_self, tuple);
   ObjectAddressSet(address, ConstraintRelationId, conForm->oid);
   table_close(constr_rel, RowExclusiveLock);
  }

  if (changed)
   return address;
  else
   return InvalidObjectAddress;
 }

 /*
  * If we're asked not to recurse, and children exist, raise an error for
  * partitioned tables.  For inheritance, we act as if NO INHERIT had been
  * specified.
 */

 if (!recurse &&
  find_inheritance_children(RelationGetRelid(rel),
          NoLock) != NIL)
 {
  if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
   ereport(ERROR,
     errcode(ERRCODE_INVALID_TABLE_DEFINITION),
     errmsg("constraint must be added to child tables too"),
     errhint("Do not specify the ONLY keyword."));
  else
   is_no_inherit = true;
 }

 /*
  * No constraint exists; we must add one.  First determine a name to use,
  * if we haven't already.
 */

 if (!recursing)
 {
  Assert(conName == NULL);
  conName = ChooseConstraintName(RelationGetRelationName(rel),
            colName, "not_null",
            RelationGetNamespace(rel),
            NIL);
 }

 constraint = makeNotNullConstraint(makeString(colName));
 constraint->is_no_inherit = is_no_inherit;
 constraint->conname = conName;

 /* and do it */
 cooked = AddRelationNewConstraints(rel, NIL, list_make1(constraint),
            false, !recursing, false, NULL);
 ccon = linitial(cooked);
 ObjectAddressSet(address, ConstraintRelationId, ccon->conoid);

 /* Mark pg_attribute.attnotnull for the column and queue validation */
 set_attnotnull(wqueue, rel, attnum, truetrue);

 InvokeObjectPostAlterHook(RelationRelationId,
         RelationGetRelid(rel), attnum);

 /*
  * Recurse to propagate the constraint to children that don't have one.
 */

 if (recurse)
 {
  List    *children;

  children = find_inheritance_children(RelationGetRelid(rel),
            lockmode);

  foreach_oid(childoid, children)
  {
   Relation childrel = table_open(childoid, NoLock);

   CommandCounterIncrement();

   ATExecSetNotNull(wqueue, childrel, conName, colName,
        recurse, true, lockmode);
   table_close(childrel, NoLock);
  }
 }

 return address;
}

/*
 * NotNullImpliedByRelConstraints
 *  Does rel's existing constraints imply NOT NULL for the given attribute?
 */

static bool
NotNullImpliedByRelConstraints(Relation rel, Form_pg_attribute attr)
{
 NullTest   *nnulltest = makeNode(NullTest);

 nnulltest->arg = (Expr *) makeVar(1,
           attr->attnum,
           attr->atttypid,
           attr->atttypmod,
           attr->attcollation,
           0);
 nnulltest->nulltesttype = IS_NOT_NULL;

 /*
  * argisrow = false is correct even for a composite column, because
  * attnotnull does not represent a SQL-spec IS NOT NULL test in such a
  * case, just IS DISTINCT FROM NULL.
 */

 nnulltest->argisrow = false;
 nnulltest->location = -1;

 if (ConstraintImpliedByRelConstraint(rel, list_make1(nnulltest), NIL))
 {
  ereport(DEBUG1,
    (errmsg_internal("existing constraints on column \"%s.%s\" are sufficient to prove that it does not contain nulls",
         RelationGetRelationName(rel), NameStr(attr->attname))));
  return true;
 }

 return false;
}

/*
 * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
 *
 * Return the address of the affected column.
 */

static ObjectAddress
ATExecColumnDefault(Relation rel, const char *colName,
     Node *newDefault, LOCKMODE lockmode)
{
 TupleDesc tupdesc = RelationGetDescr(rel);
 AttrNumber attnum;
 ObjectAddress address;

 /*
  * get the number of the attribute
 */

 attnum = get_attnum(RelationGetRelid(rel), colName);
 if (attnum == InvalidAttrNumber)
  ereport(ERROR,
    (errcode(ERRCODE_UNDEFINED_COLUMN),
     errmsg("column \"%s\" of relation \"%s\" does not exist",
      colName, RelationGetRelationName(rel))));

 /* Prevent them from altering a system attribute */
 if (attnum <= 0)
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("cannot alter system column \"%s\"",
      colName)));

 if (TupleDescAttr(tupdesc, attnum - 1)->attidentity)
  ereport(ERROR,
    (errcode(ERRCODE_SYNTAX_ERROR),
     errmsg("column \"%s\" of relation \"%s\" is an identity column",
      colName, RelationGetRelationName(rel)),
  /* translator: %s is an SQL ALTER command */
     newDefault ? 0 : errhint("Use %s instead.",
            "ALTER TABLE ... ALTER COLUMN ... DROP IDENTITY")));

 if (TupleDescAttr(tupdesc, attnum - 1)->attgenerated)
  ereport(ERROR,
    (errcode(ERRCODE_SYNTAX_ERROR),
     errmsg("column \"%s\" of relation \"%s\" is a generated column",
      colName, RelationGetRelationName(rel)),
     newDefault ?
  /* translator: %s is an SQL ALTER command */
     errhint("Use %s instead.""ALTER TABLE ... ALTER COLUMN ... SET EXPRESSION") :
     (TupleDescAttr(tupdesc, attnum - 1)->attgenerated == ATTRIBUTE_GENERATED_STORED ?
      errhint("Use %s instead.""ALTER TABLE ... ALTER COLUMN ... DROP EXPRESSION") : 0)));

 /*
  * Remove any old default for the column.  We use RESTRICT here for
  * safety, but at present we do not expect anything to depend on the
  * default.
  *
  * We treat removing the existing default as an internal operation when it
  * is preparatory to adding a new default, but as a user-initiated
  * operation when the user asked for a drop.
 */

 RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false,
       newDefault != NULL);

 if (newDefault)
 {
  /* SET DEFAULT */
  RawColumnDefault *rawEnt;

  rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
  rawEnt->attnum = attnum;
  rawEnt->raw_default = newDefault;
  rawEnt->generated = '\0';

  /*
   * This function is intended for CREATE TABLE, so it processes a
   * _list_ of defaults, but we just do one.
 */

  AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
          falsetruefalse, NULL);
 }

 ObjectAddressSubSet(address, RelationRelationId,
      RelationGetRelid(rel), attnum);
 return address;
}

/*
 * Add a pre-cooked default expression.
 *
 * Return the address of the affected column.
 */

static ObjectAddress
ATExecCookedColumnDefault(Relation rel, AttrNumber attnum,
        Node *newDefault)
{
 ObjectAddress address;

 /* We assume no checking is required */

 /*
  * Remove any old default for the column.  We use RESTRICT here for
  * safety, but at present we do not expect anything to depend on the
  * default.  (In ordinary cases, there could not be a default in place
  * anyway, but it's possible when combining LIKE with inheritance.)
 */

 RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false,
       true);

 (void) StoreAttrDefault(rel, attnum, newDefault, true);

 ObjectAddressSubSet(address, RelationRelationId,
      RelationGetRelid(rel), attnum);
 return address;
}

/*
 * ALTER TABLE ALTER COLUMN ADD IDENTITY
 *
 * Return the address of the affected column.
 */

static ObjectAddress
ATExecAddIdentity(Relation rel, const char *colName,
      Node *def, LOCKMODE lockmode, bool recurse, bool recursing)
{
 Relation attrelation;
 HeapTuple tuple;
 Form_pg_attribute attTup;
 AttrNumber attnum;
 ObjectAddress address;
 ColumnDef  *cdef = castNode(ColumnDef, def);
 bool  ispartitioned;

 ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 if (ispartitioned && !recurse)
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
     errmsg("cannot add identity to a column of only the partitioned table"),
     errhint("Do not specify the ONLY keyword.")));

 if (rel->rd_rel->relispartition && !recursing)
  ereport(ERROR,
    errcode(ERRCODE_INVALID_TABLE_DEFINITION),
    errmsg("cannot add identity to a column of a partition"));

 attrelation = table_open(AttributeRelationId, RowExclusiveLock);

 tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
 if (!HeapTupleIsValid(tuple))
  ereport(ERROR,
    (errcode(ERRCODE_UNDEFINED_COLUMN),
     errmsg("column \"%s\" of relation \"%s\" does not exist",
      colName, RelationGetRelationName(rel))));
 attTup = (Form_pg_attribute) GETSTRUCT(tuple);
 attnum = attTup->attnum;

 /* Can't alter a system attribute */
 if (attnum <= 0)
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("cannot alter system column \"%s\"",
      colName)));

 /*
  * Creating a column as identity implies NOT NULL, so adding the identity
  * to an existing column that is not NOT NULL would create a state that
  * cannot be reproduced without contortions.
 */

 if (!attTup->attnotnull)
  ereport(ERROR,
    (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     errmsg("column \"%s\" of relation \"%s\" must be declared NOT NULL before identity can be added",
      colName, RelationGetRelationName(rel))));

 /*
  * On the other hand, if a not-null constraint exists, then verify that
  * it's compatible.
 */

 if (attTup->attnotnull)
 {
  HeapTuple contup;
  Form_pg_constraint conForm;

  contup = findNotNullConstraintAttnum(RelationGetRelid(rel),
            attnum);
  if (!HeapTupleIsValid(contup))
   elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation \"%s\"",
     colName, RelationGetRelationName(rel));

  conForm = (Form_pg_constraint) GETSTRUCT(contup);
  if (!conForm->convalidated)
   ereport(ERROR,
     errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     errmsg("incompatible NOT VALID constraint \"%s\" on relation \"%s\"",
         NameStr(conForm->conname), RelationGetRelationName(rel)),
     errhint("You might need to validate it using %s.",
       "ALTER TABLE ... VALIDATE CONSTRAINT"));
 }

 if (attTup->attidentity)
  ereport(ERROR,
    (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     errmsg("column \"%s\" of relation \"%s\" is already an identity column",
      colName, RelationGetRelationName(rel))));

 if (attTup->atthasdef)
  ereport(ERROR,
    (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     errmsg("column \"%s\" of relation \"%s\" already has a default value",
      colName, RelationGetRelationName(rel))));

 attTup->attidentity = cdef->identity;
 CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);

 InvokeObjectPostAlterHook(RelationRelationId,
         RelationGetRelid(rel),
         attTup->attnum);
 ObjectAddressSubSet(address, RelationRelationId,
      RelationGetRelid(rel), attnum);
 heap_freetuple(tuple);

 table_close(attrelation, RowExclusiveLock);

 /*
  * Recurse to propagate the identity column to partitions.  Identity is
  * not inherited in regular inheritance children.
 */

 if (recurse && ispartitioned)
 {
  List    *children;
  ListCell   *lc;

  children = find_inheritance_children(RelationGetRelid(rel), lockmode);

  foreach(lc, children)
  {
   Relation childrel;

   childrel = table_open(lfirst_oid(lc), NoLock);
   ATExecAddIdentity(childrel, colName, def, lockmode, recurse, true);
   table_close(childrel, NoLock);
  }
 }

 return address;
}

/*
 * ALTER TABLE ALTER COLUMN SET { GENERATED or sequence options }
 *
 * Return the address of the affected column.
 */

static ObjectAddress
ATExecSetIdentity(Relation rel, const char *colName, Node *def,
      LOCKMODE lockmode, bool recurse, bool recursing)
{
 ListCell   *option;
 DefElem    *generatedEl = NULL;
 HeapTuple tuple;
 Form_pg_attribute attTup;
 AttrNumber attnum;
 Relation attrelation;
 ObjectAddress address;
 bool  ispartitioned;

 ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 if (ispartitioned && !recurse)
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
     errmsg("cannot change identity column of only the partitioned table"),
     errhint("Do not specify the ONLY keyword.")));

 if (rel->rd_rel->relispartition && !recursing)
  ereport(ERROR,
    errcode(ERRCODE_INVALID_TABLE_DEFINITION),
    errmsg("cannot change identity column of a partition"));

 foreach(option, castNode(List, def))
 {
  DefElem    *defel = lfirst_node(DefElem, option);

  if (strcmp(defel->defname, "generated") == 0)
  {
   if (generatedEl)
    ereport(ERROR,
      (errcode(ERRCODE_SYNTAX_ERROR),
       errmsg("conflicting or redundant options")));
   generatedEl = defel;
  }
  else
   elog(ERROR, "option \"%s\" not recognized",
     defel->defname);
 }

 /*
  * Even if there is nothing to change here, we run all the checks.  There
  * will be a subsequent ALTER SEQUENCE that relies on everything being
  * there.
 */


 attrelation = table_open(AttributeRelationId, RowExclusiveLock);
 tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
 if (!HeapTupleIsValid(tuple))
  ereport(ERROR,
    (errcode(ERRCODE_UNDEFINED_COLUMN),
     errmsg("column \"%s\" of relation \"%s\" does not exist",
      colName, RelationGetRelationName(rel))));

 attTup = (Form_pg_attribute) GETSTRUCT(tuple);
 attnum = attTup->attnum;

 if (attnum <= 0)
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("cannot alter system column \"%s\"",
      colName)));

 if (!attTup->attidentity)
  ereport(ERROR,
    (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     errmsg("column \"%s\" of relation \"%s\" is not an identity column",
      colName, RelationGetRelationName(rel))));

 if (generatedEl)
 {
  attTup->attidentity = defGetInt32(generatedEl);
  CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);

  InvokeObjectPostAlterHook(RelationRelationId,
          RelationGetRelid(rel),
          attTup->attnum);
  ObjectAddressSubSet(address, RelationRelationId,
       RelationGetRelid(rel), attnum);
 }
 else
  address = InvalidObjectAddress;

 heap_freetuple(tuple);
 table_close(attrelation, RowExclusiveLock);

 /*
  * Recurse to propagate the identity change to partitions. Identity is not
  * inherited in regular inheritance children.
 */

 if (generatedEl && recurse && ispartitioned)
 {
  List    *children;
  ListCell   *lc;

  children = find_inheritance_children(RelationGetRelid(rel), lockmode);

  foreach(lc, children)
  {
   Relation childrel;

   childrel = table_open(lfirst_oid(lc), NoLock);
   ATExecSetIdentity(childrel, colName, def, lockmode, recurse, true);
   table_close(childrel, NoLock);
  }
 }

 return address;
}

/*
 * ALTER TABLE ALTER COLUMN DROP IDENTITY
 *
 * Return the address of the affected column.
 */

static ObjectAddress
ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode,
       bool recurse, bool recursing)
{
 HeapTuple tuple;
 Form_pg_attribute attTup;
 AttrNumber attnum;
 Relation attrelation;
 ObjectAddress address;
 Oid   seqid;
 ObjectAddress seqaddress;
 bool  ispartitioned;

 ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 if (ispartitioned && !recurse)
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
     errmsg("cannot drop identity from a column of only the partitioned table"),
     errhint("Do not specify the ONLY keyword.")));

 if (rel->rd_rel->relispartition && !recursing)
  ereport(ERROR,
    errcode(ERRCODE_INVALID_TABLE_DEFINITION),
    errmsg("cannot drop identity from a column of a partition"));

 attrelation = table_open(AttributeRelationId, RowExclusiveLock);
 tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
 if (!HeapTupleIsValid(tuple))
  ereport(ERROR,
    (errcode(ERRCODE_UNDEFINED_COLUMN),
     errmsg("column \"%s\" of relation \"%s\" does not exist",
      colName, RelationGetRelationName(rel))));

 attTup = (Form_pg_attribute) GETSTRUCT(tuple);
 attnum = attTup->attnum;

 if (attnum <= 0)
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("cannot alter system column \"%s\"",
      colName)));

 if (!attTup->attidentity)
 {
  if (!missing_ok)
   ereport(ERROR,
     (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
      errmsg("column \"%s\" of relation \"%s\" is not an identity column",
       colName, RelationGetRelationName(rel))));
  else
  {
   ereport(NOTICE,
     (errmsg("column \"%s\" of relation \"%s\" is not an identity column, skipping",
       colName, RelationGetRelationName(rel))));
   heap_freetuple(tuple);
   table_close(attrelation, RowExclusiveLock);
   return InvalidObjectAddress;
  }
 }

 attTup->attidentity = '\0';
 CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);

 InvokeObjectPostAlterHook(RelationRelationId,
         RelationGetRelid(rel),
         attTup->attnum);
 ObjectAddressSubSet(address, RelationRelationId,
      RelationGetRelid(rel), attnum);
 heap_freetuple(tuple);

 table_close(attrelation, RowExclusiveLock);

 /*
  * Recurse to drop the identity from column in partitions.  Identity is
  * not inherited in regular inheritance children so ignore them.
 */

 if (recurse && ispartitioned)
 {
  List    *children;
  ListCell   *lc;

  children = find_inheritance_children(RelationGetRelid(rel), lockmode);

  foreach(lc, children)
  {
   Relation childrel;

   childrel = table_open(lfirst_oid(lc), NoLock);
   ATExecDropIdentity(childrel, colName, false, lockmode, recurse, true);
   table_close(childrel, NoLock);
  }
 }

 if (!recursing)
 {
  /* drop the internal sequence */
  seqid = getIdentitySequence(rel, attnum, false);
  deleteDependencyRecordsForClass(RelationRelationId, seqid,
          RelationRelationId, DEPENDENCY_INTERNAL);
  CommandCounterIncrement();
  seqaddress.classId = RelationRelationId;
  seqaddress.objectId = seqid;
  seqaddress.objectSubId = 0;
  performDeletion(&seqaddress, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
 }

 return address;
}

/*
 * ALTER TABLE ALTER COLUMN SET EXPRESSION
 *
 * Return the address of the affected column.
 */

static ObjectAddress
ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
     Node *newExpr, LOCKMODE lockmode)
{
 HeapTuple tuple;
 Form_pg_attribute attTup;
 AttrNumber attnum;
 char  attgenerated;
 bool  rewrite;
 Oid   attrdefoid;
 ObjectAddress address;
 Expr    *defval;
 NewColumnValue *newval;
 RawColumnDefault *rawEnt;

 tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
 if (!HeapTupleIsValid(tuple))
  ereport(ERROR,
    (errcode(ERRCODE_UNDEFINED_COLUMN),
     errmsg("column \"%s\" of relation \"%s\" does not exist",
      colName, RelationGetRelationName(rel))));

 attTup = (Form_pg_attribute) GETSTRUCT(tuple);

 attnum = attTup->attnum;
 if (attnum <= 0)
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("cannot alter system column \"%s\"",
      colName)));

 attgenerated = attTup->attgenerated;
 if (!attgenerated)
  ereport(ERROR,
    (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     errmsg("column \"%s\" of relation \"%s\" is not a generated column",
      colName, RelationGetRelationName(rel))));

 /*
  * TODO: This could be done, just need to recheck any constraints
  * afterwards.
 */

 if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL &&
  rel->rd_att->constr && rel->rd_att->constr->num_check > 0)
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("ALTER TABLE / SET EXPRESSION is not supported for virtual generated columns in tables with check constraints"),
     errdetail("Column \"%s\" of relation \"%s\" is a virtual generated column.",
         colName, RelationGetRelationName(rel))));

 if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL && attTup->attnotnull)
  tab->verify_new_notnull = true;

 /*
  * We need to prevent this because a change of expression could affect a
  * row filter and inject expressions that are not permitted in a row
  * filter.  XXX We could try to have a more precise check to catch only
  * publications with row filters, or even re-verify the row filter
  * expressions.
 */

 if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL &&
  GetRelationPublications(RelationGetRelid(rel)) != NIL)
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("ALTER TABLE / SET EXPRESSION is not supported for virtual generated columns in tables that are part of a publication"),
     errdetail("Column \"%s\" of relation \"%s\" is a virtual generated column.",
         colName, RelationGetRelationName(rel))));

 rewrite = (attgenerated == ATTRIBUTE_GENERATED_STORED);

 ReleaseSysCache(tuple);

 if (rewrite)
 {
  /*
   * Clear all the missing values if we're rewriting the table, since
   * this renders them pointless.
 */

  RelationClearMissing(rel);

  /* make sure we don't conflict with later attribute modifications */
  CommandCounterIncrement();

  /*
   * Find everything that depends on the column (constraints, indexes,
   * etc), and record enough information to let us recreate the objects
   * after rewrite.
 */

  RememberAllDependentForRebuilding(tab, AT_SetExpression, rel, attnum, colName);
 }

 /*
  * Drop the dependency records of the GENERATED expression, in particular
  * its INTERNAL dependency on the column, which would otherwise cause
  * dependency.c to refuse to perform the deletion.
 */

 attrdefoid = GetAttrDefaultOid(RelationGetRelid(rel), attnum);
 if (!OidIsValid(attrdefoid))
  elog(ERROR, "could not find attrdef tuple for relation %u attnum %d",
    RelationGetRelid(rel), attnum);
 (void) deleteDependencyRecordsFor(AttrDefaultRelationId, attrdefoid, false);

 /* Make above changes visible */
 CommandCounterIncrement();

 /*
  * Get rid of the GENERATED expression itself.  We use RESTRICT here for
  * safety, but at present we do not expect anything to depend on the
  * expression.
 */

 RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT,
       falsefalse);

 /* Prepare to store the new expression, in the catalogs */
 rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
 rawEnt->attnum = attnum;
 rawEnt->raw_default = newExpr;
 rawEnt->generated = attgenerated;

 /* Store the generated expression */
 AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
         falsetruefalse, NULL);

 /* Make above new expression visible */
 CommandCounterIncrement();

 if (rewrite)
 {
  /* Prepare for table rewrite */
  defval = (Expr *) build_column_default(rel, attnum);

  newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
  newval->attnum = attnum;
  newval->expr = expression_planner(defval);
  newval->is_generated = true;

  tab->newvals = lappend(tab->newvals, newval);
  tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 }

 /* Drop any pg_statistic entry for the column */
 RemoveStatistics(RelationGetRelid(rel), attnum);

 InvokeObjectPostAlterHook(RelationRelationId,
         RelationGetRelid(rel), attnum);

 ObjectAddressSubSet(address, RelationRelationId,
      RelationGetRelid(rel), attnum);
 return address;
}

/*
 * ALTER TABLE ALTER COLUMN DROP EXPRESSION
 */

static void
ATPrepDropExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, LOCKMODE lockmode)
{
 /*
  * Reject ONLY if there are child tables.  We could implement this, but it
  * is a bit complicated.  GENERATED clauses must be attached to the column
  * definition and cannot be added later like DEFAULT, so if a child table
  * has a generation expression that the parent does not have, the child
  * column will necessarily be an attislocal column.  So to implement ONLY
  * here, we'd need extra code to update attislocal of the direct child
  * tables, somewhat similar to how DROP COLUMN does it, so that the
  * resulting state can be properly dumped and restored.
 */

 if (!recurse &&
  find_inheritance_children(RelationGetRelid(rel), lockmode))
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("ALTER TABLE / DROP EXPRESSION must be applied to child tables too")));

 /*
  * Cannot drop generation expression from inherited columns.
 */

 if (!recursing)
 {
  HeapTuple tuple;
  Form_pg_attribute attTup;

  tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), cmd->name);
  if (!HeapTupleIsValid(tuple))
   ereport(ERROR,
     (errcode(ERRCODE_UNDEFINED_COLUMN),
      errmsg("column \"%s\" of relation \"%s\" does not exist",
       cmd->name, RelationGetRelationName(rel))));

  attTup = (Form_pg_attribute) GETSTRUCT(tuple);

  if (attTup->attinhcount > 0)
   ereport(ERROR,
     (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
      errmsg("cannot drop generation expression from inherited column")));
 }
}

/*
 * Return the address of the affected column.
 */

static ObjectAddress
ATExecDropExpression(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode)
{
 HeapTuple tuple;
 Form_pg_attribute attTup;
 AttrNumber attnum;
 Relation attrelation;
 Oid   attrdefoid;
 ObjectAddress address;

 attrelation = table_open(AttributeRelationId, RowExclusiveLock);
 tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
 if (!HeapTupleIsValid(tuple))
  ereport(ERROR,
    (errcode(ERRCODE_UNDEFINED_COLUMN),
     errmsg("column \"%s\" of relation \"%s\" does not exist",
      colName, RelationGetRelationName(rel))));

 attTup = (Form_pg_attribute) GETSTRUCT(tuple);
 attnum = attTup->attnum;

 if (attnum <= 0)
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("cannot alter system column \"%s\"",
      colName)));

 /*
  * TODO: This could be done, but it would need a table rewrite to
  * materialize the generated values.  Note that for the time being, we
  * still error with missing_ok, so that we don't silently leave the column
  * as generated.
 */

 if (attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("ALTER TABLE / DROP EXPRESSION is not supported for virtual generated columns"),
     errdetail("Column \"%s\" of relation \"%s\" is a virtual generated column.",
         colName, RelationGetRelationName(rel))));

 if (!attTup->attgenerated)
 {
  if (!missing_ok)
   ereport(ERROR,
     (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
      errmsg("column \"%s\" of relation \"%s\" is not a generated column",
       colName, RelationGetRelationName(rel))));
  else
  {
   ereport(NOTICE,
     (errmsg("column \"%s\" of relation \"%s\" is not a generated column, skipping",
       colName, RelationGetRelationName(rel))));
   heap_freetuple(tuple);
   table_close(attrelation, RowExclusiveLock);
   return InvalidObjectAddress;
  }
 }

 /*
  * Mark the column as no longer generated.  (The atthasdef flag needs to
  * get cleared too, but RemoveAttrDefault will handle that.)
 */

 attTup->attgenerated = '\0';
 CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);

 InvokeObjectPostAlterHook(RelationRelationId,
         RelationGetRelid(rel),
         attnum);
 heap_freetuple(tuple);

 table_close(attrelation, RowExclusiveLock);

 /*
  * Drop the dependency records of the GENERATED expression, in particular
  * its INTERNAL dependency on the column, which would otherwise cause
  * dependency.c to refuse to perform the deletion.
 */

 attrdefoid = GetAttrDefaultOid(RelationGetRelid(rel), attnum);
 if (!OidIsValid(attrdefoid))
  elog(ERROR, "could not find attrdef tuple for relation %u attnum %d",
    RelationGetRelid(rel), attnum);
 (void) deleteDependencyRecordsFor(AttrDefaultRelationId, attrdefoid, false);

 /* Make above changes visible */
 CommandCounterIncrement();

 /*
  * Get rid of the GENERATED expression itself.  We use RESTRICT here for
  * safety, but at present we do not expect anything to depend on the
  * default.
 */

 RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT,
       falsefalse);

 ObjectAddressSubSet(address, RelationRelationId,
      RelationGetRelid(rel), attnum);
 return address;
}

/*
 * ALTER TABLE ALTER COLUMN SET STATISTICS
 *
 * Return value is the address of the modified column
 */

static ObjectAddress
ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newValue, LOCKMODE lockmode)
{
 int   newtarget = 0;
 bool  newtarget_default;
 Relation attrelation;
 HeapTuple tuple,
    newtuple;
 Form_pg_attribute attrtuple;
 AttrNumber attnum;
 ObjectAddress address;
 Datum  repl_val[Natts_pg_attribute];
 bool  repl_null[Natts_pg_attribute];
 bool  repl_repl[Natts_pg_attribute];

 /*
  * We allow referencing columns by numbers only for indexes, since table
  * column numbers could contain gaps if columns are later dropped.
 */

 if (rel->rd_rel->relkind != RELKIND_INDEX &&
  rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
  !colName)
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("cannot refer to non-index column by number")));

 /* -1 was used in previous versions for the default setting */
 if (newValue && intVal(newValue) != -1)
 {
  newtarget = intVal(newValue);
  newtarget_default = false;
 }
 else
  newtarget_default = true;

 if (!newtarget_default)
 {
  /*
   * Limit target to a sane range
 */

  if (newtarget < 0)
  {
   ereport(ERROR,
     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      errmsg("statistics target %d is too low",
       newtarget)));
  }
  else if (newtarget > MAX_STATISTICS_TARGET)
  {
   newtarget = MAX_STATISTICS_TARGET;
   ereport(WARNING,
     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      errmsg("lowering statistics target to %d",
       newtarget)));
  }
 }

 attrelation = table_open(AttributeRelationId, RowExclusiveLock);

 if (colName)
 {
  tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);

  if (!HeapTupleIsValid(tuple))
   ereport(ERROR,
     (errcode(ERRCODE_UNDEFINED_COLUMN),
      errmsg("column \"%s\" of relation \"%s\" does not exist",
       colName, RelationGetRelationName(rel))));
 }
 else
 {
  tuple = SearchSysCacheAttNum(RelationGetRelid(rel), colNum);

  if (!HeapTupleIsValid(tuple))
   ereport(ERROR,
     (errcode(ERRCODE_UNDEFINED_COLUMN),
      errmsg("column number %d of relation \"%s\" does not exist",
       colNum, RelationGetRelationName(rel))));
 }

 attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);

 attnum = attrtuple->attnum;
 if (attnum <= 0)
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("cannot alter system column \"%s\"",
      colName)));

 /*
  * Prevent this as long as the ANALYZE code skips virtual generated
  * columns.
 */

 if (attrtuple->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("cannot alter statistics on virtual generated column \"%s\"",
      colName)));

 if (rel->rd_rel->relkind == RELKIND_INDEX ||
  rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
 {
  if (attnum > rel->rd_index->indnkeyatts)
   ereport(ERROR,
     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
      errmsg("cannot alter statistics on included column \"%s\" of index \"%s\"",
       NameStr(attrtuple->attname), RelationGetRelationName(rel))));
  else if (rel->rd_index->indkey.values[attnum - 1] != 0)
   ereport(ERROR,
     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
      errmsg("cannot alter statistics on non-expression column \"%s\" of index \"%s\"",
       NameStr(attrtuple->attname), RelationGetRelationName(rel)),
      errhint("Alter statistics on table column instead.")));
 }

 /* Build new tuple. */
 memset(repl_null, falsesizeof(repl_null));
 memset(repl_repl, falsesizeof(repl_repl));
 if (!newtarget_default)
  repl_val[Anum_pg_attribute_attstattarget - 1] = newtarget;
 else
  repl_null[Anum_pg_attribute_attstattarget - 1] = true;
 repl_repl[Anum_pg_attribute_attstattarget - 1] = true;
 newtuple = heap_modify_tuple(tuple, RelationGetDescr(attrelation),
         repl_val, repl_null, repl_repl);
 CatalogTupleUpdate(attrelation, &tuple->t_self, newtuple);

 InvokeObjectPostAlterHook(RelationRelationId,
         RelationGetRelid(rel),
         attrtuple->attnum);
 ObjectAddressSubSet(address, RelationRelationId,
      RelationGetRelid(rel), attnum);

 heap_freetuple(newtuple);

 ReleaseSysCache(tuple);

 table_close(attrelation, RowExclusiveLock);

 return address;
}

/*
 * Return value is the address of the modified column
 */

static ObjectAddress
ATExecSetOptions(Relation rel, const char *colName, Node *options,
     bool isReset, LOCKMODE lockmode)
{
 Relation attrelation;
 HeapTuple tuple,
    newtuple;
 Form_pg_attribute attrtuple;
 AttrNumber attnum;
 Datum  datum,
    newOptions;
 bool  isnull;
 ObjectAddress address;
 Datum  repl_val[Natts_pg_attribute];
 bool  repl_null[Natts_pg_attribute];
 bool  repl_repl[Natts_pg_attribute];

 attrelation = table_open(AttributeRelationId, RowExclusiveLock);

 tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);

 if (!HeapTupleIsValid(tuple))
  ereport(ERROR,
    (errcode(ERRCODE_UNDEFINED_COLUMN),
     errmsg("column \"%s\" of relation \"%s\" does not exist",
      colName, RelationGetRelationName(rel))));
 attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);

 attnum = attrtuple->attnum;
 if (attnum <= 0)
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("cannot alter system column \"%s\"",
      colName)));

 /* Generate new proposed attoptions (text array) */
 datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions,
       &isnull);
 newOptions = transformRelOptions(isnull ? (Datum) 0 : datum,
          castNode(List, options), NULL, NULL,
          false, isReset);
 /* Validate new options */
 (void) attribute_reloptions(newOptions, true);

 /* Build new tuple. */
 memset(repl_null, falsesizeof(repl_null));
 memset(repl_repl, falsesizeof(repl_repl));
 if (newOptions != (Datum) 0)
  repl_val[Anum_pg_attribute_attoptions - 1] = newOptions;
 else
  repl_null[Anum_pg_attribute_attoptions - 1] = true;
 repl_repl[Anum_pg_attribute_attoptions - 1] = true;
 newtuple = heap_modify_tuple(tuple, RelationGetDescr(attrelation),
         repl_val, repl_null, repl_repl);

 /* Update system catalog. */
 CatalogTupleUpdate(attrelation, &newtuple->t_self, newtuple);

 InvokeObjectPostAlterHook(RelationRelationId,
         RelationGetRelid(rel),
         attrtuple->attnum);
 ObjectAddressSubSet(address, RelationRelationId,
      RelationGetRelid(rel), attnum);

 heap_freetuple(newtuple);

 ReleaseSysCache(tuple);

 table_close(attrelation, RowExclusiveLock);

 return address;
}

/*
 * Helper function for ATExecSetStorage and ATExecSetCompression
 *
 * Set the attstorage and/or attcompression fields for index columns
 * associated with the specified table column.
 */

static void
SetIndexStorageProperties(Relation rel, Relation attrelation,
        AttrNumber attnum,
        bool setstorage, char newstorage,
        bool setcompression, char newcompression,
        LOCKMODE lockmode)
{
 ListCell   *lc;

 foreach(lc, RelationGetIndexList(rel))
 {
  Oid   indexoid = lfirst_oid(lc);
  Relation indrel;
  AttrNumber indattnum = 0;
  HeapTuple tuple;

  indrel = index_open(indexoid, lockmode);

  for (int i = 0; i < indrel->rd_index->indnatts; i++)
  {
   if (indrel->rd_index->indkey.values[i] == attnum)
   {
    indattnum = i + 1;
    break;
   }
  }

  if (indattnum == 0)
  {
   index_close(indrel, lockmode);
   continue;
  }

  tuple = SearchSysCacheCopyAttNum(RelationGetRelid(indrel), indattnum);

  if (HeapTupleIsValid(tuple))
  {
   Form_pg_attribute attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);

   if (setstorage)
    attrtuple->attstorage = newstorage;

   if (setcompression)
    attrtuple->attcompression = newcompression;

   CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);

   InvokeObjectPostAlterHook(RelationRelationId,
           RelationGetRelid(rel),
           attrtuple->attnum);

   heap_freetuple(tuple);
  }

  index_close(indrel, lockmode);
 }
}

/*
 * ALTER TABLE ALTER COLUMN SET STORAGE
 *
 * Return value is the address of the modified column
 */

static ObjectAddress
ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode)
{
 Relation attrelation;
 HeapTuple tuple;
 Form_pg_attribute attrtuple;
 AttrNumber attnum;
 ObjectAddress address;

 attrelation = table_open(AttributeRelationId, RowExclusiveLock);

 tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);

 if (!HeapTupleIsValid(tuple))
  ereport(ERROR,
    (errcode(ERRCODE_UNDEFINED_COLUMN),
     errmsg("column \"%s\" of relation \"%s\" does not exist",
      colName, RelationGetRelationName(rel))));
 attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);

 attnum = attrtuple->attnum;
 if (attnum <= 0)
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("cannot alter system column \"%s\"",
      colName)));

 attrtuple->attstorage = GetAttributeStorage(attrtuple->atttypid, strVal(newValue));

 CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);

 InvokeObjectPostAlterHook(RelationRelationId,
         RelationGetRelid(rel),
         attrtuple->attnum);

 /*
  * Apply the change to indexes as well (only for simple index columns,
  * matching behavior of index.c ConstructTupleDescriptor()).
 */

 SetIndexStorageProperties(rel, attrelation, attnum,
         true, attrtuple->attstorage,
         false0,
         lockmode);

 heap_freetuple(tuple);

 table_close(attrelation, RowExclusiveLock);

 ObjectAddressSubSet(address, RelationRelationId,
      RelationGetRelid(rel), attnum);
 return address;
}


/*
 * ALTER TABLE DROP COLUMN
 *
 * DROP COLUMN cannot use the normal ALTER TABLE recursion mechanism,
 * because we have to decide at runtime whether to recurse or not depending
 * on whether attinhcount goes to zero or not.  (We can't check this in a
 * static pre-pass because it won't handle multiple inheritance situations
 * correctly.)
 */

static void
ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
     AlterTableCmd *cmd, LOCKMODE lockmode,
     AlterTableUtilityContext *context)
{
 if (rel->rd_rel->reloftype && !recursing)
  ereport(ERROR,
    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     errmsg("cannot drop column from typed table")));

 if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
  ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);

 if (recurse)
  cmd->recurse = true;
}

/*
 * Drops column 'colName' from relation 'rel' and returns the address of the
 * dropped column.  The column is also dropped (or marked as no longer
 * inherited from relation) from the relation's inheritance children, if any.
 *
 * In the recursive invocations for inheritance child relations, instead of
 * dropping the column directly (if to be dropped at all), its object address
 * is added to 'addrs', which must be non-NULL in such invocations.  All
 * columns are dropped at the same time after all the children have been
 * checked recursively.
 */

static ObjectAddress
ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
     DropBehavior behavior,
     bool recurse, bool recursing,
     bool missing_ok, LOCKMODE lockmode,
     ObjectAddresses *addrs)
{
 HeapTuple tuple;
 Form_pg_attribute targetatt;
 AttrNumber attnum;
 List    *children;
 ObjectAddress object;
 bool  is_expr;

 /* At top level, permission check was done in ATPrepCmd, else do it */
 if (recursing)
  ATSimplePermissions(AT_DropColumn, rel,
       ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);

 /* Initialize addrs on the first invocation */
 Assert(!recursing || addrs != NULL);

 /* since this function recurses, it could be driven to stack overflow */
 check_stack_depth();

 if (!recursing)
  addrs = new_object_addresses();

 /*
  * get the number of the attribute
 */

 tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
 if (!HeapTupleIsValid(tuple))
 {
  if (!missing_ok)
  {
   ereport(ERROR,
     (errcode(ERRCODE_UNDEFINED_COLUMN),
      errmsg("column \"%s\" of relation \"%s\" does not exist",
       colName, RelationGetRelationName(rel))));
  }
  else
  {
   ereport(NOTICE,
     (errmsg("column \"%s\" of relation \"%s\" does not exist, skipping",
       colName, RelationGetRelationName(rel))));
   return InvalidObjectAddress;
  }
 }
 targetatt = (Form_pg_attribute) GETSTRUCT(tuple);

 attnum = targetatt->attnum;

 /* Can't drop a system attribute */
 if (attnum <= 0)
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("cannot drop system column \"%s\"",
      colName)));

 /*
  * Don't drop inherited columns, unless recursing (presumably from a drop
  * of the parent column)
 */

 if (targetatt->attinhcount > 0 && !recursing)
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
     errmsg("cannot drop inherited column \"%s\"",
      colName)));

 /*
  * Don't drop columns used in the partition key, either.  (If we let this
  * go through, the key column's dependencies would cause a cascaded drop
  * of the whole table, which is surely not what the user expected.)
 */

 if (has_partition_attrs(rel,
       bms_make_singleton(attnum - FirstLowInvalidHeapAttributeNumber),
       &is_expr))
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
     errmsg("cannot drop column \"%s\" because it is part of the partition key of relation \"%s\"",
      colName, RelationGetRelationName(rel))));

 ReleaseSysCache(tuple);

 /*
  * Propagate to children as appropriate.  Unlike most other ALTER
  * routines, we have to do this one level of recursion at a time; we can't
  * use find_all_inheritors to do it in one pass.
 */

 children =
  find_inheritance_children(RelationGetRelid(rel), lockmode);

 if (children)
 {
  Relation attr_rel;
  ListCell   *child;

  /*
   * In case of a partitioned table, the column must be dropped from the
   * partitions as well.
 */

  if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
   ereport(ERROR,
     (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
      errmsg("cannot drop column from only the partitioned table when partitions exist"),
      errhint("Do not specify the ONLY keyword.")));

  attr_rel = table_open(AttributeRelationId, RowExclusiveLock);
  foreach(child, children)
  {
   Oid   childrelid = lfirst_oid(child);
   Relation childrel;
   Form_pg_attribute childatt;

   /* find_inheritance_children already got lock */
   childrel = table_open(childrelid, NoLock);
   CheckAlterTableIsSafe(childrel);

   tuple = SearchSysCacheCopyAttName(childrelid, colName);
   if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
    elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
      colName, childrelid);
   childatt = (Form_pg_attribute) GETSTRUCT(tuple);

   if (childatt->attinhcount <= 0/* shouldn't happen */
    elog(ERROR, "relation %u has non-inherited attribute \"%s\"",
      childrelid, colName);

   if (recurse)
   {
    /*
     * If the child column has other definition sources, just
     * decrement its inheritance count; if not, recurse to delete
     * it.
 */

    if (childatt->attinhcount == 1 && !childatt->attislocal)
    {
     /* Time to delete this child column, too */
     ATExecDropColumn(wqueue, childrel, colName,
          behavior, truetrue,
          false, lockmode, addrs);
    }
    else
    {
     /* Child column must survive my deletion */
     childatt->attinhcount--;

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

     /* Make update visible */
     CommandCounterIncrement();
    }
   }
   else
   {
    /*
     * If we were told to drop ONLY in this table (no recursion),
     * we need to mark the inheritors' attributes as locally
     * defined rather than inherited.
 */

    childatt->attinhcount--;
    childatt->attislocal = true;

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

    /* Make update visible */
    CommandCounterIncrement();
   }

   heap_freetuple(tuple);

   table_close(childrel, NoLock);
  }
  table_close(attr_rel, RowExclusiveLock);
 }

 /* Add object to delete */
 object.classId = RelationRelationId;
 object.objectId = RelationGetRelid(rel);
 object.objectSubId = attnum;
 add_exact_object_address(&object, addrs);

 if (!recursing)
 {
  /* Recursion has ended, drop everything that was collected */
  performMultipleDeletions(addrs, behavior, 0);
  free_object_addresses(addrs);
 }

 return object;
}

/*
 * Prepare to add a primary key on a table, by adding not-null constraints
 * on all columns.
 *
 * The not-null constraints for a primary key must cover the whole inheritance
 * hierarchy (failing to ensure that leads to funny corner cases).  For the
 * normal case where we're asked to recurse, this routine checks if the
 * not-null constraints exist already, and if not queues a requirement for
 * them to be created by phase 2.
 *
 * For the case where we're asked not to recurse, we verify that a not-null
 * constraint exists on each column of each (direct) child table, throwing an
 * error if not.  Not throwing an error would also work, because a not-null
 * constraint would be created anyway, but it'd cause a silent scan of the
 * child table to verify absence of nulls.  We prefer to let the user know so
 * that they can add the constraint manually without having to hold
 * AccessExclusiveLock while at it.
 *
 * However, it's also important that we do not acquire locks on children if
 * the not-null constraints already exist on the parent, to avoid risking
 * deadlocks during parallel pg_restore of PKs on partitioned tables.
 */

static void
ATPrepAddPrimaryKey(List **wqueue, Relation rel, AlterTableCmd *cmd,
     bool recurse, LOCKMODE lockmode,
     AlterTableUtilityContext *context)
{
 Constraint *pkconstr;
 List    *children = NIL;
 bool  got_children = false;

 pkconstr = castNode(Constraint, cmd->def);
 if (pkconstr->contype != CONSTR_PRIMARY)
  return;

 /* Verify that columns are not-null, or request that they be made so */
 foreach_node(String, column, pkconstr->keys)
 {
  AlterTableCmd *newcmd;
  Constraint *nnconstr;
  HeapTuple tuple;

  /*
   * First check if a suitable constraint exists.  If it does, we don't
   * need to request another one.  We do need to bail out if it's not
   * valid, though.
 */

  tuple = findNotNullConstraint(RelationGetRelid(rel), strVal(column));
  if (tuple != NULL)
  {
   verifyNotNullPKCompatible(tuple, strVal(column));

   /* All good with this one; don't request another */
   heap_freetuple(tuple);
   continue;
  }
  else if (!recurse)
  {
   /*
    * No constraint on this column.  Asked not to recurse, we won't
    * create one here, but verify that all children have one.
 */

   if (!got_children)
   {
    children = find_inheritance_children(RelationGetRelid(rel),
              lockmode);
    /* only search for children on the first time through */
    got_children = true;
   }

   foreach_oid(childrelid, children)
   {
    HeapTuple tup;

    tup = findNotNullConstraint(childrelid, strVal(column));
    if (!tup)
     ereport(ERROR,
       errmsg("column \"%s\" of table \"%s\" is not marked NOT NULL",
           strVal(column), get_rel_name(childrelid)));
    /* verify it's good enough */
    verifyNotNullPKCompatible(tup, strVal(column));
   }
  }

  /* This column is not already not-null, so add it to the queue */
  nnconstr = makeNotNullConstraint(column);

  newcmd = makeNode(AlterTableCmd);
  newcmd->subtype = AT_AddConstraint;
  /* note we force recurse=true here; see above */
  newcmd->recurse = true;
  newcmd->def = (Node *) nnconstr;

  ATPrepCmd(wqueue, rel, newcmd, truefalse, lockmode, context);
 }
}

/*
 * Verify whether the given not-null constraint is compatible with a
 * primary key.  If not, an error is thrown.
 */

static void
verifyNotNullPKCompatible(HeapTuple tuple, const char *colname)
{
 Form_pg_constraint conForm = (Form_pg_constraint) GETSTRUCT(tuple);

 if (conForm->contype != CONSTRAINT_NOTNULL)
  elog(ERROR, "constraint %u is not a not-null constraint", conForm->oid);

 /* a NO INHERIT constraint is no good */
 if (conForm->connoinherit)
  ereport(ERROR,
    errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    errmsg("cannot create primary key on column \"%s\"", colname),
  /*- translator: fourth %s is a constraint characteristic such as NOT VALID */
    errdetail("The constraint \"%s\" on column \"%s\" of table \"%s\", marked %s, is incompatible with a primary key.",
        NameStr(conForm->conname), colname,
        get_rel_name(conForm->conrelid), "NO INHERIT"),
    errhint("You might need to make the existing constraint inheritable using %s.",
      "ALTER TABLE ... ALTER CONSTRAINT ... INHERIT"));

 /* an unvalidated constraint is no good */
 if (!conForm->convalidated)
  ereport(ERROR,
    errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    errmsg("cannot create primary key on column \"%s\"", colname),
  /*- translator: fourth %s is a constraint characteristic such as NOT VALID */
    errdetail("The constraint \"%s\" on column \"%s\" of table \"%s\", marked %s, is incompatible with a primary key.",
        NameStr(conForm->conname), colname,
        get_rel_name(conForm->conrelid), "NOT VALID"),
    errhint("You might need to validate it using %s.",
      "ALTER TABLE ... VALIDATE CONSTRAINT"));
}

/*
 * ALTER TABLE ADD INDEX
 *
 * There is no such command in the grammar, but parse_utilcmd.c converts
 * UNIQUE and PRIMARY KEY constraints into AT_AddIndex subcommands.  This lets
 * us schedule creation of the index at the appropriate time during ALTER.
 *
 * Return value is the address of the new index.
 */

static ObjectAddress
ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
      IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
 bool  check_rights;
 bool  skip_build;
 bool  quiet;
 ObjectAddress address;

 Assert(IsA(stmt, IndexStmt));
 Assert(!stmt->concurrent);

 /* The IndexStmt has already been through transformIndexStmt */
 Assert(stmt->transformed);

 /* suppress schema rights check when rebuilding existing index */
 check_rights = !is_rebuild;
 /* skip index build if phase 3 will do it or we're reusing an old one */
 skip_build = tab->rewrite > 0 || RelFileNumberIsValid(stmt->oldNumber);
 /* suppress notices when rebuilding existing index */
 quiet = is_rebuild;

 address = DefineIndex(RelationGetRelid(rel),
        stmt,
        InvalidOid, /* no predefined OID */
        InvalidOid, /* no parent index */
        InvalidOid, /* no parent constraint */
        -1/* total_parts unknown */
        true/* is_alter_table */
        check_rights,
        false/* check_not_in_use - we did it already */
        skip_build,
        quiet);

 /*
  * If TryReuseIndex() stashed a relfilenumber for us, we used it for the
  * new index instead of building from scratch.  Restore associated fields.
  * This may store InvalidSubTransactionId in both fields, in which case
  * relcache.c will assume it can rebuild the relcache entry.  Hence, do
  * this after the CCI that made catalog rows visible to any rebuild.  The
  * DROP of the old edition of this index will have scheduled the storage
  * for deletion at commit, so cancel that pending deletion.
 */

 if (RelFileNumberIsValid(stmt->oldNumber))
 {
  Relation irel = index_open(address.objectId, NoLock);

  irel->rd_createSubid = stmt->oldCreateSubid;
  irel->rd_firstRelfilelocatorSubid = stmt->oldFirstRelfilelocatorSubid;
  RelationPreserveStorage(irel->rd_locator, true);
  index_close(irel, NoLock);
 }

 return address;
}

/*
 * ALTER TABLE ADD STATISTICS
 *
 * This is no such command in the grammar, but we use this internally to add
 * AT_ReAddStatistics subcommands to rebuild extended statistics after a table
 * column type change.
 */

static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
     CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
 ObjectAddress address;

 Assert(IsA(stmt, CreateStatsStmt));

 /* The CreateStatsStmt has already been through transformStatsStmt */
 Assert(stmt->transformed);

 address = CreateStatistics(stmt, !is_rebuild);

 return address;
}

/*
 * ALTER TABLE ADD CONSTRAINT USING INDEX
 *
 * Returns the address of the new constraint.
 */

static ObjectAddress
ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
       IndexStmt *stmt, LOCKMODE lockmode)
{
 Oid   index_oid = stmt->indexOid;
 Relation indexRel;
 char    *indexName;
 IndexInfo  *indexInfo;
 char    *constraintName;
 char  constraintType;
 ObjectAddress address;
 bits16  flags;

 Assert(IsA(stmt, IndexStmt));
 Assert(OidIsValid(index_oid));
 Assert(stmt->isconstraint);

 /*
  * Doing this on partitioned tables is not a simple feature to implement,
  * so let's punt for now.
 */

 if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX is not supported on partitioned tables")));

 indexRel = index_open(index_oid, AccessShareLock);

 indexName = pstrdup(RelationGetRelationName(indexRel));

 indexInfo = BuildIndexInfo(indexRel);

 /* this should have been checked at parse time */
 if (!indexInfo->ii_Unique)
  elog(ERROR, "index \"%s\" is not unique", indexName);

 /*
  * Determine name to assign to constraint.  We require a constraint to
  * have the same name as the underlying index; therefore, use the index's
  * existing name as the default constraint name, and if the user
  * explicitly gives some other name for the constraint, rename the index
  * to match.
 */

 constraintName = stmt->idxname;
 if (constraintName == NULL)
  constraintName = indexName;
 else if (strcmp(constraintName, indexName) != 0)
 {
  ereport(NOTICE,
    (errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index \"%s\" to \"%s\"",
      indexName, constraintName)));
  RenameRelationInternal(index_oid, constraintName, falsetrue);
 }

 /* Extra checks needed if making primary key */
 if (stmt->primary)
  index_check_primary_key(rel, indexInfo, true, stmt);

 /* Note we currently don't support EXCLUSION constraints here */
 if (stmt->primary)
  constraintType = CONSTRAINT_PRIMARY;
 else
  constraintType = CONSTRAINT_UNIQUE;

 /* Create the catalog entries for the constraint */
 flags = INDEX_CONSTR_CREATE_UPDATE_INDEX |
  INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS |
  (stmt->initdeferred ? INDEX_CONSTR_CREATE_INIT_DEFERRED : 0) |
  (stmt->deferrable ? INDEX_CONSTR_CREATE_DEFERRABLE : 0) |
  (stmt->primary ? INDEX_CONSTR_CREATE_MARK_AS_PRIMARY : 0);

 address = index_constraint_create(rel,
           index_oid,
           InvalidOid,
           indexInfo,
           constraintName,
           constraintType,
           flags,
           allowSystemTableMods,
           false); /* is_internal */

 index_close(indexRel, NoLock);

 return address;
}

/*
 * ALTER TABLE ADD CONSTRAINT
 *
 * Return value is the address of the new constraint; if no constraint was
 * added, InvalidObjectAddress is returned.
 */

static ObjectAddress
ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
     Constraint *newConstraint, bool recurse, bool is_readd,
     LOCKMODE lockmode)
{
 ObjectAddress address = InvalidObjectAddress;

 Assert(IsA(newConstraint, Constraint));

 /*
  * Currently, we only expect to see CONSTR_CHECK, CONSTR_NOTNULL and
  * CONSTR_FOREIGN nodes arriving here (see the preprocessing done in
  * parse_utilcmd.c).
 */

 switch (newConstraint->contype)
 {
  case CONSTR_CHECK:
  case CONSTR_NOTNULL:
   address =
    ATAddCheckNNConstraint(wqueue, tab, rel,
            newConstraint, recurse, false, is_readd,
            lockmode);
   break;

  case CONSTR_FOREIGN:

   /*
    * Assign or validate constraint name
 */

   if (newConstraint->conname)
   {
    if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
           RelationGetRelid(rel),
           newConstraint->conname))
     ereport(ERROR,
       (errcode(ERRCODE_DUPLICATE_OBJECT),
        errmsg("constraint \"%s\" for relation \"%s\" already exists",
         newConstraint->conname,
         RelationGetRelationName(rel))));
   }
   else
    newConstraint->conname =
     ChooseConstraintName(RelationGetRelationName(rel),
           ChooseForeignKeyConstraintNameAddition(newConstraint->fk_attrs),
           "fkey",
           RelationGetNamespace(rel),
           NIL);

   address = ATAddForeignKeyConstraint(wqueue, tab, rel,
            newConstraint,
            recurse, false,
            lockmode);
   break;

  default:
   elog(ERROR, "unrecognized constraint type: %d",
     (int) newConstraint->contype);
 }

 return address;
}

/*
 * Generate the column-name portion of the constraint name for a new foreign
 * key given the list of column names that reference the referenced
 * table.  This will be passed to ChooseConstraintName along with the parent
 * table name and the "fkey" suffix.
 *
 * We know that less than NAMEDATALEN characters will actually be used, so we
 * can truncate the result once we've generated that many.
 *
 * XXX see also ChooseExtendedStatisticNameAddition and
 * ChooseIndexNameAddition.
 */

static char *
ChooseForeignKeyConstraintNameAddition(List *colnames)
{
 char  buf[NAMEDATALEN * 2];
 int   buflen = 0;
 ListCell   *lc;

 buf[0] = '\0';
 foreach(lc, colnames)
 {
  const char *name = strVal(lfirst(lc));

  if (buflen > 0)
   buf[buflen++] = '_'/* insert _ between names */

  /*
   * At this point we have buflen <= NAMEDATALEN.  name should be less
   * than NAMEDATALEN already, but use strlcpy for paranoia.
 */

  strlcpy(buf + buflen, name, NAMEDATALEN);
  buflen += strlen(buf + buflen);
  if (buflen >= NAMEDATALEN)
   break;
 }
 return pstrdup(buf);
}

/*
 * Add a check or not-null constraint to a single table and its children.
 * Returns the address of the constraint added to the parent relation,
 * if one gets added, or InvalidObjectAddress otherwise.
 *
 * Subroutine for ATExecAddConstraint.
 *
 * We must recurse to child tables during execution, rather than using
 * ALTER TABLE's normal prep-time recursion.  The reason is that all the
 * constraints *must* be given the same name, else they won't be seen as
 * related later.  If the user didn't explicitly specify a name, then
 * AddRelationNewConstraints would normally assign different names to the
 * child constraints.  To fix that, we must capture the name assigned at
 * the parent table and pass that down.
 */

static ObjectAddress
ATAddCheckNNConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
        Constraint *constr, bool recurse, bool recursing,
        bool is_readd, LOCKMODE lockmode)
{
 List    *newcons;
 ListCell   *lcon;
 List    *children;
 ListCell   *child;
 ObjectAddress address = InvalidObjectAddress;

 /* Guard against stack overflow due to overly deep inheritance tree. */
 check_stack_depth();

 /* At top level, permission check was done in ATPrepCmd, else do it */
 if (recursing)
  ATSimplePermissions(AT_AddConstraint, rel,
       ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);

 /*
  * Call AddRelationNewConstraints to do the work, making sure it works on
  * a copy of the Constraint so transformExpr can't modify the original. It
  * returns a list of cooked constraints.
  *
  * If the constraint ends up getting merged with a pre-existing one, it's
  * omitted from the returned list, which is what we want: we do not need
  * to do any validation work.  That can only happen at child tables,
  * though, since we disallow merging at the top level.
 */

 newcons = AddRelationNewConstraints(rel, NIL,
          list_make1(copyObject(constr)),
          recursing || is_readd, /* allow_merge */
          !recursing, /* is_local */
          is_readd, /* is_internal */
          NULL); /* queryString not available
 * here */


 /* we don't expect more than one constraint here */
 Assert(list_length(newcons) <= 1);

 /* Add each to-be-validated constraint to Phase 3's queue */
 foreach(lcon, newcons)
 {
  CookedConstraint *ccon = (CookedConstraint *) lfirst(lcon);

  if (!ccon->skip_validation && ccon->contype != CONSTR_NOTNULL)
  {
   NewConstraint *newcon;

   newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
   newcon->name = ccon->name;
   newcon->contype = ccon->contype;
   newcon->qual = ccon->expr;

   tab->constraints = lappend(tab->constraints, newcon);
  }

  /* Save the actually assigned name if it was defaulted */
  if (constr->conname == NULL)
   constr->conname = ccon->name;

  /*
   * If adding a valid not-null constraint, set the pg_attribute flag
   * and tell phase 3 to verify existing rows, if needed.  For an
   * invalid constraint, just set attnotnull, without queueing
   * verification.
 */

  if (constr->contype == CONSTR_NOTNULL)
   set_attnotnull(wqueue, rel, ccon->attnum,
         !constr->skip_validation,
         !constr->skip_validation);

  ObjectAddressSet(address, ConstraintRelationId, ccon->conoid);
 }

 /* At this point we must have a locked-down name to use */
 Assert(newcons == NIL || constr->conname != NULL);

 /* Advance command counter in case same table is visited multiple times */
 CommandCounterIncrement();

 /*
  * If the constraint got merged with an existing constraint, we're done.
  * We mustn't recurse to child tables in this case, because they've
  * already got the constraint, and visiting them again would lead to an
  * incorrect value for coninhcount.
 */

 if (newcons == NIL)
  return address;

 /*
  * If adding a NO INHERIT constraint, no need to find our children.
 */

 if (constr->is_no_inherit)
  return address;

 /*
  * Propagate to children as appropriate.  Unlike most other ALTER
  * routines, we have to do this one level of recursion at a time; we can't
  * use find_all_inheritors to do it in one pass.
 */

 children =
  find_inheritance_children(RelationGetRelid(rel), lockmode);

 /*
  * Check if ONLY was specified with ALTER TABLE.  If so, allow the
  * constraint creation only if there are no children currently. Error out
  * otherwise.
 */

 if (!recurse && children != NIL)
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
     errmsg("constraint must be added to child tables too")));

 /*
  * Recurse to create the constraint on each child.
 */

 foreach(child, children)
 {
  Oid   childrelid = lfirst_oid(child);
  Relation childrel;
  AlteredTableInfo *childtab;

  /* find_inheritance_children already got lock */
  childrel = table_open(childrelid, NoLock);
  CheckAlterTableIsSafe(childrel);

  /* Find or create work queue entry for this table */
  childtab = ATGetQueueEntry(wqueue, childrel);

  /* Recurse to this child */
  ATAddCheckNNConstraint(wqueue, childtab, childrel,
          constr, recurse, true, is_readd, lockmode);

  table_close(childrel, NoLock);
 }

 return address;
}

/*
 * Add a foreign-key constraint to a single table; return the new constraint's
 * address.
 *
 * Subroutine for ATExecAddConstraint.  Must already hold exclusive
 * lock on the rel, and have done appropriate validity checks for it.
 * We do permissions checks here, however.
 *
 * When the referenced or referencing tables (or both) are partitioned,
 * multiple pg_constraint rows are required -- one for each partitioned table
 * and each partition on each side (fortunately, not one for every combination
 * thereof).  We also need action triggers on each leaf partition on the
 * referenced side, and check triggers on each leaf partition on the
 * referencing side.
 */

static ObjectAddress
ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
        Constraint *fkconstraint,
        bool recurse, bool recursing, LOCKMODE lockmode)
{
 Relation pkrel;
 int16  pkattnum[INDEX_MAX_KEYS] = {0};
 int16  fkattnum[INDEX_MAX_KEYS] = {0};
 Oid   pktypoid[INDEX_MAX_KEYS] = {0};
 Oid   fktypoid[INDEX_MAX_KEYS] = {0};
 Oid   pkcolloid[INDEX_MAX_KEYS] = {0};
 Oid   fkcolloid[INDEX_MAX_KEYS] = {0};
 Oid   opclasses[INDEX_MAX_KEYS] = {0};
 Oid   pfeqoperators[INDEX_MAX_KEYS] = {0};
 Oid   ppeqoperators[INDEX_MAX_KEYS] = {0};
 Oid   ffeqoperators[INDEX_MAX_KEYS] = {0};
 int16  fkdelsetcols[INDEX_MAX_KEYS] = {0};
 bool  with_period;
 bool  pk_has_without_overlaps;
 int   i;
 int   numfks,
    numpks,
    numfkdelsetcols;
 Oid   indexOid;
 bool  old_check_ok;
 ObjectAddress address;
 ListCell   *old_pfeqop_item = list_head(fkconstraint->old_conpfeqop);

 /*
  * Grab ShareRowExclusiveLock on the pk table, so that someone doesn't
  * delete rows out from under us.
 */

 if (OidIsValid(fkconstraint->old_pktable_oid))
  pkrel = table_open(fkconstraint->old_pktable_oid, ShareRowExclusiveLock);
 else
  pkrel = table_openrv(fkconstraint->pktable, ShareRowExclusiveLock);

 /*
  * Validity checks (permission checks wait till we have the column
  * numbers)
 */

 if (!recurse && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
  ereport(ERROR,
    errcode(ERRCODE_WRONG_OBJECT_TYPE),
    errmsg("cannot use ONLY for foreign key on partitioned table \"%s\" referencing relation \"%s\"",
        RelationGetRelationName(rel),
        RelationGetRelationName(pkrel)));

 if (pkrel->rd_rel->relkind != RELKIND_RELATION &&
  pkrel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
  ereport(ERROR,
    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     errmsg("referenced relation \"%s\" is not a table",
      RelationGetRelationName(pkrel))));

 if (!allowSystemTableMods && IsSystemRelation(pkrel))
  ereport(ERROR,
    (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     errmsg("permission denied: \"%s\" is a system catalog",
      RelationGetRelationName(pkrel))));

 /*
  * References from permanent or unlogged tables to temp tables, and from
  * permanent tables to unlogged tables, are disallowed because the
  * referenced data can vanish out from under us.  References from temp
  * tables to any other table type are also disallowed, because other
  * backends might need to run the RI triggers on the perm table, but they
  * can't reliably see tuples in the local buffers of other backends.
 */

 switch (rel->rd_rel->relpersistence)
 {
  case RELPERSISTENCE_PERMANENT:
   if (!RelationIsPermanent(pkrel))
    ereport(ERROR,
      (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
       errmsg("constraints on permanent tables may reference only permanent tables")));
   break;
  case RELPERSISTENCE_UNLOGGED:
   if (!RelationIsPermanent(pkrel)
    && pkrel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED)
    ereport(ERROR,
      (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
       errmsg("constraints on unlogged tables may reference only permanent or unlogged tables")));
   break;
  case RELPERSISTENCE_TEMP:
   if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
    ereport(ERROR,
      (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
       errmsg("constraints on temporary tables may reference only temporary tables")));
   if (!pkrel->rd_islocaltemp || !rel->rd_islocaltemp)
    ereport(ERROR,
      (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
       errmsg("constraints on temporary tables must involve temporary tables of this session")));
   break;
 }

 /*
  * Look up the referencing attributes to make sure they exist, and record
  * their attnums and type and collation OIDs.
 */

 numfks = transformColumnNameList(RelationGetRelid(rel),
          fkconstraint->fk_attrs,
          fkattnum, fktypoid, fkcolloid);
 with_period = fkconstraint->fk_with_period || fkconstraint->pk_with_period;
 if (with_period && !fkconstraint->fk_with_period)
  ereport(ERROR,
    errcode(ERRCODE_INVALID_FOREIGN_KEY),
    errmsg("foreign key uses PERIOD on the referenced table but not the referencing table"));

 numfkdelsetcols = transformColumnNameList(RelationGetRelid(rel),
             fkconstraint->fk_del_set_cols,
             fkdelsetcols, NULL, NULL);
 numfkdelsetcols = validateFkOnDeleteSetColumns(numfks, fkattnum,
               numfkdelsetcols,
               fkdelsetcols,
               fkconstraint->fk_del_set_cols);

 /*
  * If the attribute list for the referenced table was omitted, lookup the
  * definition of the primary key and use it.  Otherwise, validate the
  * supplied attribute list.  In either case, discover the index OID and
  * index opclasses, and the attnums and type and collation OIDs of the
  * attributes.
 */

 if (fkconstraint->pk_attrs == NIL)
 {
  numpks = transformFkeyGetPrimaryKey(pkrel, &indexOid,
           &fkconstraint->pk_attrs,
           pkattnum, pktypoid, pkcolloid,
           opclasses, &pk_has_without_overlaps);

  /* If the primary key uses WITHOUT OVERLAPS, the fk must use PERIOD */
  if (pk_has_without_overlaps && !fkconstraint->fk_with_period)
   ereport(ERROR,
     errcode(ERRCODE_INVALID_FOREIGN_KEY),
     errmsg("foreign key uses PERIOD on the referenced table but not the referencing table"));
 }
 else
 {
  numpks = transformColumnNameList(RelationGetRelid(pkrel),
           fkconstraint->pk_attrs,
           pkattnum, pktypoid, pkcolloid);

  /* Since we got pk_attrs, one should be a period. */
  if (with_period && !fkconstraint->pk_with_period)
   ereport(ERROR,
     errcode(ERRCODE_INVALID_FOREIGN_KEY),
     errmsg("foreign key uses PERIOD on the referencing table but not the referenced table"));

  /* Look for an index matching the column list */
  indexOid = transformFkeyCheckAttrs(pkrel, numpks, pkattnum,
             with_period, opclasses, &pk_has_without_overlaps);
 }

 /*
  * If the referenced primary key has WITHOUT OVERLAPS, the foreign key
  * must use PERIOD.
 */

 if (pk_has_without_overlaps && !with_period)
  ereport(ERROR,
    errcode(ERRCODE_INVALID_FOREIGN_KEY),
    errmsg("foreign key must use PERIOD when referencing a primary key using WITHOUT OVERLAPS"));

 /*
  * Now we can check permissions.
 */

 checkFkeyPermissions(pkrel, pkattnum, numpks);

 /*
  * Check some things for generated columns.
 */

 for (i = 0; i < numfks; i++)
 {
  char  attgenerated = TupleDescAttr(RelationGetDescr(rel), fkattnum[i] - 1)->attgenerated;

  if (attgenerated)
  {
   /*
    * Check restrictions on UPDATE/DELETE actions, per SQL standard
 */

   if (fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETNULL ||
    fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETDEFAULT ||
    fkconstraint->fk_upd_action == FKCONSTR_ACTION_CASCADE)
    ereport(ERROR,
      (errcode(ERRCODE_SYNTAX_ERROR),
       errmsg("invalid %s action for foreign key constraint containing generated column",
        "ON UPDATE")));
   if (fkconstraint->fk_del_action == FKCONSTR_ACTION_SETNULL ||
    fkconstraint->fk_del_action == FKCONSTR_ACTION_SETDEFAULT)
    ereport(ERROR,
      (errcode(ERRCODE_SYNTAX_ERROR),
       errmsg("invalid %s action for foreign key constraint containing generated column",
        "ON DELETE")));
  }

  /*
   * FKs on virtual columns are not supported.  This would require
   * various additional support in ri_triggers.c, including special
   * handling in ri_NullCheck(), ri_KeysEqual(),
   * RI_FKey_fk_upd_check_required() (since all virtual columns appear
   * as NULL there).  Also not really practical as long as you can't
   * index virtual columns.
 */

  if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
   ereport(ERROR,
     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
      errmsg("foreign key constraints on virtual generated columns are not supported")));
 }

 /*
  * Some actions are currently unsupported for foreign keys using PERIOD.
 */

 if (fkconstraint->fk_with_period)
 {
  if (fkconstraint->fk_upd_action == FKCONSTR_ACTION_RESTRICT ||
   fkconstraint->fk_upd_action == FKCONSTR_ACTION_CASCADE ||
   fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETNULL ||
   fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETDEFAULT)
   ereport(ERROR,
     errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("unsupported %s action for foreign key constraint using PERIOD",
         "ON UPDATE"));

  if (fkconstraint->fk_del_action == FKCONSTR_ACTION_RESTRICT ||
   fkconstraint->fk_del_action == FKCONSTR_ACTION_CASCADE ||
   fkconstraint->fk_del_action == FKCONSTR_ACTION_SETNULL ||
   fkconstraint->fk_del_action == FKCONSTR_ACTION_SETDEFAULT)
   ereport(ERROR,
     errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("unsupported %s action for foreign key constraint using PERIOD",
         "ON DELETE"));
 }

 /*
  * Look up the equality operators to use in the constraint.
  *
  * Note that we have to be careful about the difference between the actual
  * PK column type and the opclass' declared input type, which might be
  * only binary-compatible with it.  The declared opcintype is the right
  * thing to probe pg_amop with.
 */

 if (numfks != numpks)
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_FOREIGN_KEY),
     errmsg("number of referencing and referenced columns for foreign key disagree")));

 /*
  * On the strength of a previous constraint, we might avoid scanning
  * tables to validate this one.  See below.
 */

 old_check_ok = (fkconstraint->old_conpfeqop != NIL);
 Assert(!old_check_ok || numfks == list_length(fkconstraint->old_conpfeqop));

 for (i = 0; i < numpks; i++)
 {
  Oid   pktype = pktypoid[i];
  Oid   fktype = fktypoid[i];
  Oid   fktyped;
  Oid   pkcoll = pkcolloid[i];
  Oid   fkcoll = fkcolloid[i];
  HeapTuple cla_ht;
  Form_pg_opclass cla_tup;
  Oid   amid;
  Oid   opfamily;
  Oid   opcintype;
  bool  for_overlaps;
  CompareType cmptype;
  Oid   pfeqop;
  Oid   ppeqop;
  Oid   ffeqop;
  int16  eqstrategy;
  Oid   pfeqop_right;

  /* We need several fields out of the pg_opclass entry */
  cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclasses[i]));
  if (!HeapTupleIsValid(cla_ht))
   elog(ERROR, "cache lookup failed for opclass %u", opclasses[i]);
  cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
  amid = cla_tup->opcmethod;
  opfamily = cla_tup->opcfamily;
  opcintype = cla_tup->opcintype;
  ReleaseSysCache(cla_ht);

  /*
   * Get strategy number from index AM.
   *
   * For a normal foreign-key constraint, this should not fail, since we
   * already checked that the index is unique and should therefore have
   * appropriate equal operators.  For a period foreign key, this could
   * fail if we selected a non-matching exclusion constraint earlier.
   * (XXX Maybe we should do these lookups earlier so we don't end up
   * doing that.)
 */

  for_overlaps = with_period && i == numpks - 1;
  cmptype = for_overlaps ? COMPARE_OVERLAP : COMPARE_EQ;
  eqstrategy = IndexAmTranslateCompareType(cmptype, amid, opfamily, true);
  if (eqstrategy == InvalidStrategy)
   ereport(ERROR,
     errcode(ERRCODE_UNDEFINED_OBJECT),
     for_overlaps
     ? errmsg("could not identify an overlaps operator for foreign key")
     : errmsg("could not identify an equality operator for foreign key"),
     errdetail("Could not translate compare type %d for operator family \"%s\" of access method \"%s\".",
         cmptype, get_opfamily_name(opfamily, false), get_am_name(amid)));

  /*
   * There had better be a primary equality operator for the index.
   * We'll use it for PK = PK comparisons.
 */

  ppeqop = get_opfamily_member(opfamily, opcintype, opcintype,
          eqstrategy);

  if (!OidIsValid(ppeqop))
   elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
     eqstrategy, opcintype, opcintype, opfamily);

  /*
   * Are there equality operators that take exactly the FK type? Assume
   * we should look through any domain here.
 */

  fktyped = getBaseType(fktype);

  pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
          eqstrategy);
  if (OidIsValid(pfeqop))
  {
   pfeqop_right = fktyped;
   ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
           eqstrategy);
  }
  else
  {
   /* keep compiler quiet */
   pfeqop_right = InvalidOid;
   ffeqop = InvalidOid;
  }

  if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
  {
   /*
    * Otherwise, look for an implicit cast from the FK type to the
    * opcintype, and if found, use the primary equality operator.
    * This is a bit tricky because opcintype might be a polymorphic
    * type such as ANYARRAY or ANYENUM; so what we have to test is
    * whether the two actual column types can be concurrently cast to
    * that type.  (Otherwise, we'd fail to reject combinations such
    * as int[] and point[].)
 */

   Oid   input_typeids[2];
   Oid   target_typeids[2];

   input_typeids[0] = pktype;
   input_typeids[1] = fktype;
   target_typeids[0] = opcintype;
   target_typeids[1] = opcintype;
   if (can_coerce_type(2, input_typeids, target_typeids,
        COERCION_IMPLICIT))
   {
    pfeqop = ffeqop = ppeqop;
    pfeqop_right = opcintype;
   }
  }

  if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
   ereport(ERROR,
     (errcode(ERRCODE_DATATYPE_MISMATCH),
      errmsg("foreign key constraint \"%s\" cannot be implemented",
       fkconstraint->conname),
      errdetail("Key columns \"%s\" of the referencing table and \"%s\" of the referenced table "
          "are of incompatible types: %s and %s.",
          strVal(list_nth(fkconstraint->fk_attrs, i)),
          strVal(list_nth(fkconstraint->pk_attrs, i)),
          format_type_be(fktype),
          format_type_be(pktype))));

  /*
   * This shouldn't be possible, but better check to make sure we have a
   * consistent state for the check below.
 */

  if ((OidIsValid(pkcoll) && !OidIsValid(fkcoll)) || (!OidIsValid(pkcoll) && OidIsValid(fkcoll)))
   elog(ERROR, "key columns are not both collatable");

  if (OidIsValid(pkcoll) && OidIsValid(fkcoll))
  {
   bool  pkcolldet;
   bool  fkcolldet;

   pkcolldet = get_collation_isdeterministic(pkcoll);
   fkcolldet = get_collation_isdeterministic(fkcoll);

   /*
    * SQL requires that both collations are the same.  This is
    * because we need a consistent notion of equality on both
    * columns.  We relax this by allowing different collations if
    * they are both deterministic.  (This is also for backward
    * compatibility, because PostgreSQL has always allowed this.)
 */

   if ((!pkcolldet || !fkcolldet) && pkcoll != fkcoll)
    ereport(ERROR,
      (errcode(ERRCODE_COLLATION_MISMATCH),
       errmsg("foreign key constraint \"%s\" cannot be implemented", fkconstraint->conname),
       errdetail("Key columns \"%s\" of the referencing table and \"%s\" of the referenced table "
           "have incompatible collations: \"%s\" and \"%s\".  "
           "If either collation is nondeterministic, then both collations have to be the same.",
           strVal(list_nth(fkconstraint->fk_attrs, i)),
           strVal(list_nth(fkconstraint->pk_attrs, i)),
           get_collation_name(fkcoll),
           get_collation_name(pkcoll))));
  }

  if (old_check_ok)
  {
   /*
    * When a pfeqop changes, revalidate the constraint.  We could
    * permit intra-opfamily changes, but that adds subtle complexity
    * without any concrete benefit for core types.  We need not
    * assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
 */

   old_check_ok = (pfeqop == lfirst_oid(old_pfeqop_item));
   old_pfeqop_item = lnext(fkconstraint->old_conpfeqop,
         old_pfeqop_item);
  }
  if (old_check_ok)
  {
   Oid   old_fktype;
   Oid   new_fktype;
   CoercionPathType old_pathtype;
   CoercionPathType new_pathtype;
   Oid   old_castfunc;
   Oid   new_castfunc;
   Oid   old_fkcoll;
   Oid   new_fkcoll;
   Form_pg_attribute attr = TupleDescAttr(tab->oldDesc,
               fkattnum[i] - 1);

   /*
    * Identify coercion pathways from each of the old and new FK-side
    * column types to the right (foreign) operand type of the pfeqop.
    * We may assume that pg_constraint.conkey is not changing.
 */

   old_fktype = attr->atttypid;
   new_fktype = fktype;
   old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
          &old_castfunc);
   new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
          &new_castfunc);

   old_fkcoll = attr->attcollation;
   new_fkcoll = fkcoll;

   /*
    * Upon a change to the cast from the FK column to its pfeqop
    * operand, revalidate the constraint.  For this evaluation, a
    * binary coercion cast is equivalent to no cast at all.  While
    * type implementors should design implicit casts with an eye
    * toward consistency of operations like equality, we cannot
    * assume here that they have done so.
    *
    * A function with a polymorphic argument could change behavior
    * arbitrarily in response to get_fn_expr_argtype().  Therefore,
    * when the cast destination is polymorphic, we only avoid
    * revalidation if the input type has not changed at all.  Given
    * just the core data types and operator classes, this requirement
    * prevents no would-be optimizations.
    *
    * If the cast converts from a base type to a domain thereon, then
    * that domain type must be the opcintype of the unique index.
    * Necessarily, the primary key column must then be of the domain
    * type.  Since the constraint was previously valid, all values on
    * the foreign side necessarily exist on the primary side and in
    * turn conform to the domain.  Consequently, we need not treat
    * domains specially here.
    *
    * If the collation changes, revalidation is required, unless both
    * collations are deterministic, because those share the same
    * notion of equality (because texteq reduces to bitwise
    * equality).
    *
    * We need not directly consider the PK type.  It's necessarily
    * binary coercible to the opcintype of the unique index column,
    * and ri_triggers.c will only deal with PK datums in terms of
    * that opcintype.  Changing the opcintype also changes pfeqop.
 */

   old_check_ok = (new_pathtype == old_pathtype &&
       new_castfunc == old_castfunc &&
       (!IsPolymorphicType(pfeqop_right) ||
        new_fktype == old_fktype) &&
       (new_fkcoll == old_fkcoll ||
        (get_collation_isdeterministic(old_fkcoll) && get_collation_isdeterministic(new_fkcoll))));
  }

  pfeqoperators[i] = pfeqop;
  ppeqoperators[i] = ppeqop;
  ffeqoperators[i] = ffeqop;
 }

 /*
  * For FKs with PERIOD we need additional operators to check whether the
  * referencing row's range is contained by the aggregated ranges of the
  * referenced row(s). For rangetypes and multirangetypes this is
  * fk.periodatt <@ range_agg(pk.periodatt). Those are the only types we
  * support for now. FKs will look these up at "runtime", but we should
  * make sure the lookup works here, even if we don't use the values.
 */

 if (with_period)
 {
  Oid   periodoperoid;
  Oid   aggedperiodoperoid;
  Oid   intersectoperoid;

  FindFKPeriodOpers(opclasses[numpks - 1], &periodoperoid, &aggedperiodoperoid,
        &intersectoperoid);
 }

 /* First, create the constraint catalog entry itself. */
 address = addFkConstraint(addFkBothSides,
         fkconstraint->conname, fkconstraint, rel, pkrel,
         indexOid,
         InvalidOid, /* no parent constraint */
         numfks,
         pkattnum,
         fkattnum,
         pfeqoperators,
         ppeqoperators,
         ffeqoperators,
         numfkdelsetcols,
         fkdelsetcols,
         false,
         with_period);

 /* Next process the action triggers at the referenced side and recurse */
 addFkRecurseReferenced(fkconstraint, rel, pkrel,
         indexOid,
         address.objectId,
         numfks,
         pkattnum,
         fkattnum,
         pfeqoperators,
         ppeqoperators,
         ffeqoperators,
         numfkdelsetcols,
         fkdelsetcols,
         old_check_ok,
         InvalidOid, InvalidOid,
         with_period);

 /* Lastly create the check triggers at the referencing side and recurse */
 addFkRecurseReferencing(wqueue, fkconstraint, rel, pkrel,
       indexOid,
       address.objectId,
       numfks,
       pkattnum,
       fkattnum,
       pfeqoperators,
       ppeqoperators,
       ffeqoperators,
       numfkdelsetcols,
       fkdelsetcols,
       old_check_ok,
       lockmode,
       InvalidOid, InvalidOid,
       with_period);

 /*
  * Done.  Close pk table, but keep lock until we've committed.
 */

 table_close(pkrel, NoLock);

 return address;
}

/*
 * validateFkOnDeleteSetColumns
 *  Verifies that columns used in ON DELETE SET NULL/DEFAULT (...)
 *  column lists are valid.
 *
 * If there are duplicates in the fksetcolsattnums[] array, this silently
 * removes the dups.  The new count of numfksetcols is returned.
 */

static int
validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
        int numfksetcols, int16 *fksetcolsattnums,
        List *fksetcols)
{
 int   numcolsout = 0;

 for (int i = 0; i < numfksetcols; i++)
 {
  int16  setcol_attnum = fksetcolsattnums[i];
  bool  seen = false;

  /* Make sure it's in fkattnums[] */
  for (int j = 0; j < numfks; j++)
  {
   if (fkattnums[j] == setcol_attnum)
   {
    seen = true;
    break;
   }
  }

  if (!seen)
  {
   char    *col = strVal(list_nth(fksetcols, i));

   ereport(ERROR,
     (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
      errmsg("column \"%s\" referenced in ON DELETE SET action must be part of foreign key", col)));
  }

  /* Now check for dups */
  seen = false;
  for (int j = 0; j < numcolsout; j++)
  {
   if (fksetcolsattnums[j] == setcol_attnum)
   {
    seen = true;
    break;
   }
  }
  if (!seen)
   fksetcolsattnums[numcolsout++] = setcol_attnum;
 }
 return numcolsout;
}

/*
 * addFkConstraint
 *  Install pg_constraint entries to implement a foreign key constraint.
 *  Caller must separately invoke addFkRecurseReferenced and
 *  addFkRecurseReferencing, as appropriate, to install pg_trigger entries
 *  and (for partitioned tables) recurse to partitions.
 *
 * fkside: the side of the FK (or both) to create.  Caller should
 *      call addFkRecurseReferenced if this is addFkReferencedSide,
 *      addFkRecurseReferencing if it's addFkReferencingSide, or both if it's
 *      addFkBothSides.
 * constraintname: the base name for the constraint being added,
 *      copied to fkconstraint->conname if the latter is not set
 * fkconstraint: the constraint being added
 * rel: the root referencing relation
 * pkrel: the referenced relation; might be a partition, if recursing
 * indexOid: the OID of the index (on pkrel) implementing this constraint
 * parentConstr: the OID of a parent constraint; InvalidOid if this is a
 *      top-level constraint
 * numfks: the number of columns in the foreign key
 * pkattnum: the attnum array of referenced attributes
 * fkattnum: the attnum array of referencing attributes
 * pf/pp/ffeqoperators: OID array of operators between columns
 * numfkdelsetcols: the number of columns in the ON DELETE SET NULL/DEFAULT
 *      (...) clause
 * fkdelsetcols: the attnum array of the columns in the ON DELETE SET
 *      NULL/DEFAULT clause
 * with_period: true if this is a temporal FK
 */

static ObjectAddress
addFkConstraint(addFkConstraintSides fkside,
    char *constraintname, Constraint *fkconstraint,
    Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
    int numfks, int16 *pkattnum,
    int16 *fkattnum, Oid *pfeqoperators, Oid *ppeqoperators,
    Oid *ffeqoperators, int numfkdelsetcols, int16 *fkdelsetcols,
    bool is_internal, bool with_period)
{
 ObjectAddress address;
 Oid   constrOid;
 char    *conname;
 bool  conislocal;
 int16  coninhcount;
 bool  connoinherit;

 /*
  * Verify relkind for each referenced partition.  At the top level, this
  * is redundant with a previous check, but we need it when recursing.
 */

 if (pkrel->rd_rel->relkind != RELKIND_RELATION &&
  pkrel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
  ereport(ERROR,
    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     errmsg("referenced relation \"%s\" is not a table",
      RelationGetRelationName(pkrel))));

 /*
  * Caller supplies us with a constraint name; however, it may be used in
  * this partition, so come up with a different one in that case.  Unless
  * truncation to NAMEDATALEN dictates otherwise, the new name will be the
  * supplied name with an underscore and digit(s) appended.
 */

 if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
        RelationGetRelid(rel),
        constraintname))
  conname = ChooseConstraintName(constraintname,
            NULL,
            "",
            RelationGetNamespace(rel), NIL);
 else
  conname = constraintname;

 if (fkconstraint->conname == NULL)
  fkconstraint->conname = pstrdup(conname);

 if (OidIsValid(parentConstr))
 {
  conislocal = false;
  coninhcount = 1;
  connoinherit = false;
 }
 else
 {
  conislocal = true;
  coninhcount = 0;

  /*
   * always inherit for partitioned tables, never for legacy inheritance
 */

  connoinherit = rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE;
 }

 /*
  * Record the FK constraint in pg_constraint.
 */

 constrOid = CreateConstraintEntry(conname,
           RelationGetNamespace(rel),
           CONSTRAINT_FOREIGN,
           fkconstraint->deferrable,
           fkconstraint->initdeferred,
           fkconstraint->is_enforced,
           fkconstraint->initially_valid,
           parentConstr,
           RelationGetRelid(rel),
           fkattnum,
           numfks,
           numfks,
           InvalidOid, /* not a domain constraint */
           indexOid,
           RelationGetRelid(pkrel),
           pkattnum,
           pfeqoperators,
           ppeqoperators,
           ffeqoperators,
           numfks,
           fkconstraint->fk_upd_action,
           fkconstraint->fk_del_action,
           fkdelsetcols,
           numfkdelsetcols,
           fkconstraint->fk_matchtype,
           NULL, /* no exclusion constraint */
           NULL, /* no check constraint */
           NULL,
           conislocal, /* islocal */
           coninhcount, /* inhcount */
           connoinherit, /* conNoInherit */
           with_period, /* conPeriod */
           is_internal); /* is_internal */

 ObjectAddressSet(address, ConstraintRelationId, constrOid);

 /*
  * In partitioning cases, create the dependency entries for this
  * constraint.  (For non-partitioned cases, relevant entries were created
  * by CreateConstraintEntry.)
  *
  * On the referenced side, we need the constraint to have an internal
  * dependency on its parent constraint; this means that this constraint
  * cannot be dropped on its own -- only through the parent constraint. It
  * also means the containing partition cannot be dropped on its own, but
  * it can be detached, at which point this dependency is removed (after
  * verifying that no rows are referenced via this FK.)
  *
  * When processing the referencing side, we link the constraint via the
  * special partitioning dependencies: the parent constraint is the primary
  * dependent, and the partition on which the foreign key exists is the
  * secondary dependency.  That way, this constraint is dropped if either
  * of these objects is.
  *
  * Note that this is only necessary for the subsidiary pg_constraint rows
  * in partitions; the topmost row doesn't need any of this.
 */

 if (OidIsValid(parentConstr))
 {
  ObjectAddress referenced;

  ObjectAddressSet(referenced, ConstraintRelationId, parentConstr);

  Assert(fkside != addFkBothSides);
  if (fkside == addFkReferencedSide)
   recordDependencyOn(&address, &referenced, DEPENDENCY_INTERNAL);
  else
  {
   recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_PRI);
   ObjectAddressSet(referenced, RelationRelationId, RelationGetRelid(rel));
   recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_SEC);
  }
 }

 /* make new constraint visible, in case we add more */
 CommandCounterIncrement();

 return address;
}

/*
 * addFkRecurseReferenced
 *  Recursive helper for the referenced side of foreign key creation,
 *  which creates the action triggers and recurses
 *
 * If the referenced relation is a plain relation, create the necessary action
 * triggers that implement the constraint.  If the referenced relation is a
 * partitioned table, then we create a pg_constraint row referencing the parent
 * of the referencing side for it and recurse on this routine for each
 * partition.
 *
 * fkconstraint: the constraint being added
 * rel: the root referencing relation
 * pkrel: the referenced relation; might be a partition, if recursing
 * indexOid: the OID of the index (on pkrel) implementing this constraint
 * parentConstr: the OID of a parent constraint; InvalidOid if this is a
 *      top-level constraint
 * numfks: the number of columns in the foreign key
 * pkattnum: the attnum array of referenced attributes
 * fkattnum: the attnum array of referencing attributes
 * numfkdelsetcols: the number of columns in the ON DELETE SET
 *      NULL/DEFAULT (...) clause
 * fkdelsetcols: the attnum array of the columns in the ON DELETE SET
 *      NULL/DEFAULT clause
 * pf/pp/ffeqoperators: OID array of operators between columns
 * old_check_ok: true if this constraint replaces an existing one that
 *      was already validated (thus this one doesn't need validation)
 * parentDelTrigger and parentUpdTrigger: when recursively called on a
 *      partition, the OIDs of the parent action triggers for DELETE and
 *      UPDATE respectively.
 * with_period: true if this is a temporal FK
 */

static void
addFkRecurseReferenced(Constraint *fkconstraint, Relation rel,
        Relation pkrel, Oid indexOid, Oid parentConstr,
        int numfks,
        int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators,
        Oid *ppeqoperators, Oid *ffeqoperators,
        int numfkdelsetcols, int16 *fkdelsetcols,
        bool old_check_ok,
        Oid parentDelTrigger, Oid parentUpdTrigger,
        bool with_period)
{
 Oid   deleteTriggerOid = InvalidOid,
    updateTriggerOid = InvalidOid;

 Assert(CheckRelationLockedByMe(pkrel, ShareRowExclusiveLock, true));
 Assert(CheckRelationLockedByMe(rel, ShareRowExclusiveLock, true));

 /*
  * Create action triggers to enforce the constraint, or skip them if the
  * constraint is NOT ENFORCED.
 */

 if (fkconstraint->is_enforced)
  createForeignKeyActionTriggers(RelationGetRelid(rel),
            RelationGetRelid(pkrel),
            fkconstraint,
            parentConstr, indexOid,
            parentDelTrigger, parentUpdTrigger,
            &deleteTriggerOid, &updateTriggerOid);

 /*
  * If the referenced table is partitioned, recurse on ourselves to handle
  * each partition.  We need one pg_constraint row created for each
  * partition in addition to the pg_constraint row for the parent table.
 */

 if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 {
  PartitionDesc pd = RelationGetPartitionDesc(pkrel, true);

  for (int i = 0; i < pd->nparts; i++)
  {
   Relation partRel;
   AttrMap    *map;
   AttrNumber *mapped_pkattnum;
   Oid   partIndexId;
   ObjectAddress address;

   /* XXX would it be better to acquire these locks beforehand? */
   partRel = table_open(pd->oids[i], ShareRowExclusiveLock);

   /*
    * Map the attribute numbers in the referenced side of the FK
    * definition to match the partition's column layout.
 */

   map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
              RelationGetDescr(pkrel),
              false);
   if (map)
   {
    mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
    for (int j = 0; j < numfks; j++)
     mapped_pkattnum[j] = map->attnums[pkattnum[j] - 1];
   }
   else
    mapped_pkattnum = pkattnum;

   /* Determine the index to use at this level */
   partIndexId = index_get_partition(partRel, indexOid);
   if (!OidIsValid(partIndexId))
    elog(ERROR, "index for %u not found in partition %s",
      indexOid, RelationGetRelationName(partRel));

   /* Create entry at this level ... */
   address = addFkConstraint(addFkReferencedSide,
           fkconstraint->conname, fkconstraint, rel,
           partRel, partIndexId, parentConstr,
           numfks, mapped_pkattnum,
           fkattnum, pfeqoperators, ppeqoperators,
           ffeqoperators, numfkdelsetcols,
           fkdelsetcols, true, with_period);
   /* ... and recurse to our children */
   addFkRecurseReferenced(fkconstraint, rel, partRel,
           partIndexId, address.objectId, numfks,
           mapped_pkattnum, fkattnum,
           pfeqoperators, ppeqoperators, ffeqoperators,
           numfkdelsetcols, fkdelsetcols,
           old_check_ok,
           deleteTriggerOid, updateTriggerOid,
           with_period);

   /* Done -- clean up (but keep the lock) */
   table_close(partRel, NoLock);
   if (map)
   {
    pfree(mapped_pkattnum);
    free_attrmap(map);
   }
  }
 }
}

/*
 * addFkRecurseReferencing
 *  Recursive helper for the referencing side of foreign key creation,
 *  which creates the check triggers and recurses
 *
 * If the referencing relation is a plain relation, create the necessary check
 * triggers that implement the constraint, and set up for Phase 3 constraint
 * verification.  If the referencing relation is a partitioned table, then
 * we create a pg_constraint row for it and recurse on this routine for each
 * partition.
 *
 * We assume that the referenced relation is locked against concurrent
 * deletions.  If it's a partitioned relation, every partition must be so
 * locked.
 *
 * wqueue: the ALTER TABLE work queue; NULL when not running as part
 *      of an ALTER TABLE sequence.
 * fkconstraint: the constraint being added
 * rel: the referencing relation; might be a partition, if recursing
 * pkrel: the root referenced relation
 * indexOid: the OID of the index (on pkrel) implementing this constraint
 * parentConstr: the OID of the parent constraint (there is always one)
 * numfks: the number of columns in the foreign key
 * pkattnum: the attnum array of referenced attributes
 * fkattnum: the attnum array of referencing attributes
 * pf/pp/ffeqoperators: OID array of operators between columns
 * numfkdelsetcols: the number of columns in the ON DELETE SET NULL/DEFAULT
 *      (...) clause
 * fkdelsetcols: the attnum array of the columns in the ON DELETE SET
 *      NULL/DEFAULT clause
 * old_check_ok: true if this constraint replaces an existing one that
 *      was already validated (thus this one doesn't need validation)
 * lockmode: the lockmode to acquire on partitions when recursing
 * parentInsTrigger and parentUpdTrigger: when being recursively called on
 *      a partition, the OIDs of the parent check triggers for INSERT and
 *      UPDATE respectively.
 * with_period: true if this is a temporal FK
 */

static void
addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
      Relation pkrel, Oid indexOid, Oid parentConstr,
      int numfks, int16 *pkattnum, int16 *fkattnum,
      Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
      int numfkdelsetcols, int16 *fkdelsetcols,
      bool old_check_ok, LOCKMODE lockmode,
      Oid parentInsTrigger, Oid parentUpdTrigger,
      bool with_period)
{
 Oid   insertTriggerOid = InvalidOid,
    updateTriggerOid = InvalidOid;

 Assert(OidIsValid(parentConstr));
 Assert(CheckRelationLockedByMe(rel, ShareRowExclusiveLock, true));
 Assert(CheckRelationLockedByMe(pkrel, ShareRowExclusiveLock, true));

 if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
  ereport(ERROR,
    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     errmsg("foreign key constraints are not supported on foreign tables")));

 /*
  * Add check triggers if the constraint is ENFORCED, and if needed,
  * schedule them to be checked in Phase 3.
  *
  * If the relation is partitioned, drill down to do it to its partitions.
 */

 if (fkconstraint->is_enforced)
  createForeignKeyCheckTriggers(RelationGetRelid(rel),
           RelationGetRelid(pkrel),
           fkconstraint,
           parentConstr,
           indexOid,
           parentInsTrigger, parentUpdTrigger,
           &insertTriggerOid, &updateTriggerOid);

 if (rel->rd_rel->relkind == RELKIND_RELATION)
 {
  /*
   * Tell Phase 3 to check that the constraint is satisfied by existing
   * rows. We can skip this during table creation, when constraint is
   * specified as NOT ENFORCED, or when requested explicitly by
   * specifying NOT VALID in an ADD FOREIGN KEY command, and when we're
   * recreating a constraint following a SET DATA TYPE operation that
   * did not impugn its validity.
 */

  if (wqueue && !old_check_ok && !fkconstraint->skip_validation &&
   fkconstraint->is_enforced)
  {
   NewConstraint *newcon;
   AlteredTableInfo *tab;

   tab = ATGetQueueEntry(wqueue, rel);

   newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
   newcon->name = get_constraint_name(parentConstr);
   newcon->contype = CONSTR_FOREIGN;
   newcon->refrelid = RelationGetRelid(pkrel);
   newcon->refindid = indexOid;
   newcon->conid = parentConstr;
   newcon->conwithperiod = fkconstraint->fk_with_period;
   newcon->qual = (Node *) fkconstraint;

   tab->constraints = lappend(tab->constraints, newcon);
  }
 }
 else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 {
  PartitionDesc pd = RelationGetPartitionDesc(rel, true);
  Relation trigrel;

  /*
   * Triggers of the foreign keys will be manipulated a bunch of times
   * in the loop below.  To avoid repeatedly opening/closing the trigger
   * catalog relation, we open it here and pass it to the subroutines
   * called below.
 */

  trigrel = table_open(TriggerRelationId, RowExclusiveLock);

  /*
   * Recurse to take appropriate action on each partition; either we
   * find an existing constraint to reparent to ours, or we create a new
   * one.
 */

  for (int i = 0; i < pd->nparts; i++)
  {
   Relation partition = table_open(pd->oids[i], lockmode);
   List    *partFKs;
   AttrMap    *attmap;
   AttrNumber mapped_fkattnum[INDEX_MAX_KEYS];
   bool  attached;
   ObjectAddress address;

   CheckAlterTableIsSafe(partition);

   attmap = build_attrmap_by_name(RelationGetDescr(partition),
             RelationGetDescr(rel),
             false);
   for (int j = 0; j < numfks; j++)
    mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];

   /* Check whether an existing constraint can be repurposed */
   partFKs = copyObject(RelationGetFKeyList(partition));
   attached = false;
   foreach_node(ForeignKeyCacheInfo, fk, partFKs)
   {
    if (tryAttachPartitionForeignKey(wqueue,
             fk,
             partition,
             parentConstr,
             numfks,
             mapped_fkattnum,
             pkattnum,
             pfeqoperators,
             insertTriggerOid,
             updateTriggerOid,
             trigrel))
    {
     attached = true;
     break;
    }
   }
   if (attached)
   {
    table_close(partition, NoLock);
    continue;
   }

   /*
    * No luck finding a good constraint to reuse; create our own.
 */

   address = addFkConstraint(addFkReferencingSide,
           fkconstraint->conname, fkconstraint,
           partition, pkrel, indexOid, parentConstr,
           numfks, pkattnum,
           mapped_fkattnum, pfeqoperators,
           ppeqoperators, ffeqoperators,
           numfkdelsetcols, fkdelsetcols, true,
           with_period);

   /* call ourselves to finalize the creation and we're done */
   addFkRecurseReferencing(wqueue, fkconstraint, partition, pkrel,
         indexOid,
         address.objectId,
         numfks,
         pkattnum,
         mapped_fkattnum,
         pfeqoperators,
         ppeqoperators,
         ffeqoperators,
         numfkdelsetcols,
         fkdelsetcols,
         old_check_ok,
         lockmode,
         insertTriggerOid,
         updateTriggerOid,
         with_period);

   table_close(partition, NoLock);
  }

  table_close(trigrel, RowExclusiveLock);
 }
}

/*
 * CloneForeignKeyConstraints
 *  Clone foreign keys from a partitioned table to a newly acquired
 *  partition.
 *
 * partitionRel is a partition of parentRel, so we can be certain that it has
 * the same columns with the same datatypes.  The columns may be in different
 * order, though.
 *
 * wqueue must be passed to set up phase 3 constraint checking, unless the
 * referencing-side partition is known to be empty (such as in CREATE TABLE /
 * PARTITION OF).
 */

static void
CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
         Relation partitionRel)
{
 /* This only works for declarative partitioning */
 Assert(parentRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);

 /*
  * First, clone constraints where the parent is on the referencing side.
 */

 CloneFkReferencing(wqueue, parentRel, partitionRel);

 /*
  * Clone constraints for which the parent is on the referenced side.
 */

 CloneFkReferenced(parentRel, partitionRel);
}

/*
 * CloneFkReferenced
 *  Subroutine for CloneForeignKeyConstraints
 *
 * Find all the FKs that have the parent relation on the referenced side;
 * clone those constraints to the given partition.  This is to be called
 * when the partition is being created or attached.
 *
 * This recurses to partitions, if the relation being attached is partitioned.
 * Recursion is done by calling addFkRecurseReferenced.
 */

static void
CloneFkReferenced(Relation parentRel, Relation partitionRel)
{
 Relation pg_constraint;
 AttrMap    *attmap;
 ListCell   *cell;
 SysScanDesc scan;
 ScanKeyData key[2];
 HeapTuple tuple;
 List    *clone = NIL;
 Relation trigrel;

 /*
  * Search for any constraints where this partition's parent is in the
  * referenced side.  However, we must not clone any constraint whose
  * parent constraint is also going to be cloned, to avoid duplicates.  So
  * do it in two steps: first construct the list of constraints to clone,
  * then go over that list cloning those whose parents are not in the list.
  * (We must not rely on the parent being seen first, since the catalog
  * scan could return children first.)
 */

 pg_constraint = table_open(ConstraintRelationId, RowShareLock);
 ScanKeyInit(&key[0],
    Anum_pg_constraint_confrelid, BTEqualStrategyNumber,
    F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(parentRel)));
 ScanKeyInit(&key[1],
    Anum_pg_constraint_contype, BTEqualStrategyNumber,
    F_CHAREQ, CharGetDatum(CONSTRAINT_FOREIGN));
 /* This is a seqscan, as we don't have a usable index ... */
 scan = systable_beginscan(pg_constraint, InvalidOid, true,
         NULL, 2, key);
 while ((tuple = systable_getnext(scan)) != NULL)
 {
  Form_pg_constraint constrForm = (Form_pg_constraint) GETSTRUCT(tuple);

  clone = lappend_oid(clone, constrForm->oid);
 }
 systable_endscan(scan);
 table_close(pg_constraint, RowShareLock);

 /*
  * Triggers of the foreign keys will be manipulated a bunch of times in
  * the loop below.  To avoid repeatedly opening/closing the trigger
  * catalog relation, we open it here and pass it to the subroutines called
  * below.
 */

 trigrel = table_open(TriggerRelationId, RowExclusiveLock);

 attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
           RelationGetDescr(parentRel),
           false);
 foreach(cell, clone)
 {
  Oid   constrOid = lfirst_oid(cell);
  Form_pg_constraint constrForm;
  Relation fkRel;
  Oid   indexOid;
  Oid   partIndexId;
  int   numfks;
  AttrNumber conkey[INDEX_MAX_KEYS];
  AttrNumber mapped_confkey[INDEX_MAX_KEYS];
  AttrNumber confkey[INDEX_MAX_KEYS];
  Oid   conpfeqop[INDEX_MAX_KEYS];
  Oid   conppeqop[INDEX_MAX_KEYS];
  Oid   conffeqop[INDEX_MAX_KEYS];
  int   numfkdelsetcols;
  AttrNumber confdelsetcols[INDEX_MAX_KEYS];
  Constraint *fkconstraint;
  ObjectAddress address;
  Oid   deleteTriggerOid = InvalidOid,
     updateTriggerOid = InvalidOid;

  tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
  if (!HeapTupleIsValid(tuple))
   elog(ERROR, "cache lookup failed for constraint %u", constrOid);
  constrForm = (Form_pg_constraint) GETSTRUCT(tuple);

  /*
   * As explained above: don't try to clone a constraint for which we're
   * going to clone the parent.
 */

  if (list_member_oid(clone, constrForm->conparentid))
  {
   ReleaseSysCache(tuple);
   continue;
  }

  /* We need the same lock level that CreateTrigger will acquire */
  fkRel = table_open(constrForm->conrelid, ShareRowExclusiveLock);

  indexOid = constrForm->conindid;
  DeconstructFkConstraintRow(tuple,
           &numfks,
           conkey,
           confkey,
           conpfeqop,
           conppeqop,
           conffeqop,
           &numfkdelsetcols,
           confdelsetcols);

  for (int i = 0; i < numfks; i++)
   mapped_confkey[i] = attmap->attnums[confkey[i] - 1];

  fkconstraint = makeNode(Constraint);
  fkconstraint->contype = CONSTRAINT_FOREIGN;
  fkconstraint->conname = NameStr(constrForm->conname);
  fkconstraint->deferrable = constrForm->condeferrable;
  fkconstraint->initdeferred = constrForm->condeferred;
  fkconstraint->location = -1;
  fkconstraint->pktable = NULL;
  /* ->fk_attrs determined below */
  fkconstraint->pk_attrs = NIL;
  fkconstraint->fk_matchtype = constrForm->confmatchtype;
  fkconstraint->fk_upd_action = constrForm->confupdtype;
  fkconstraint->fk_del_action = constrForm->confdeltype;
  fkconstraint->fk_del_set_cols = NIL;
  fkconstraint->old_conpfeqop = NIL;
  fkconstraint->old_pktable_oid = InvalidOid;
  fkconstraint->is_enforced = constrForm->conenforced;
  fkconstraint->skip_validation = false;
  fkconstraint->initially_valid = constrForm->convalidated;

  /* set up colnames that are used to generate the constraint name */
  for (int i = 0; i < numfks; i++)
  {
   Form_pg_attribute att;

   att = TupleDescAttr(RelationGetDescr(fkRel),
        conkey[i] - 1);
   fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
            makeString(NameStr(att->attname)));
  }

  /*
   * Add the new foreign key constraint pointing to the new partition.
   * Because this new partition appears in the referenced side of the
   * constraint, we don't need to set up for Phase 3 check.
 */

  partIndexId = index_get_partition(partitionRel, indexOid);
  if (!OidIsValid(partIndexId))
   elog(ERROR, "index for %u not found in partition %s",
     indexOid, RelationGetRelationName(partitionRel));

  /*
   * Get the "action" triggers belonging to the constraint to pass as
   * parent OIDs for similar triggers that will be created on the
   * partition in addFkRecurseReferenced().
 */

  if (constrForm->conenforced)
   GetForeignKeyActionTriggers(trigrel, constrOid,
          constrForm->confrelid, constrForm->conrelid,
          &deleteTriggerOid, &updateTriggerOid);

  /* Add this constraint ... */
  address = addFkConstraint(addFkReferencedSide,
          fkconstraint->conname, fkconstraint, fkRel,
          partitionRel, partIndexId, constrOid,
          numfks, mapped_confkey,
          conkey, conpfeqop, conppeqop, conffeqop,
          numfkdelsetcols, confdelsetcols, false,
          constrForm->conperiod);
  /* ... and recurse */
  addFkRecurseReferenced(fkconstraint,
          fkRel,
          partitionRel,
          partIndexId,
          address.objectId,
          numfks,
          mapped_confkey,
          conkey,
          conpfeqop,
          conppeqop,
          conffeqop,
          numfkdelsetcols,
          confdelsetcols,
          true,
          deleteTriggerOid,
          updateTriggerOid,
          constrForm->conperiod);

  table_close(fkRel, NoLock);
  ReleaseSysCache(tuple);
 }

 table_close(trigrel, RowExclusiveLock);
}

/*
 * CloneFkReferencing
 *  Subroutine for CloneForeignKeyConstraints
 *
 * For each FK constraint of the parent relation in the given list, find an
 * equivalent constraint in its partition relation that can be reparented;
 * if one cannot be found, create a new constraint in the partition as its
 * child.
 *
 * If wqueue is given, it is used to set up phase-3 verification for each
 * cloned constraint; omit it if such verification is not needed
 * (example: the partition is being created anew).
 */

static void
CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
{
 AttrMap    *attmap;
 List    *partFKs;
 List    *clone = NIL;
 ListCell   *cell;
 Relation trigrel;

 /* obtain a list of constraints that we need to clone */
 foreach(cell, RelationGetFKeyList(parentRel))
 {
  ForeignKeyCacheInfo *fk = lfirst(cell);

  /*
   * Refuse to attach a table as partition that this partitioned table
   * already has a foreign key to.  This isn't useful schema, which is
   * proven by the fact that there have been no user complaints that
   * it's already impossible to achieve this in the opposite direction,
   * i.e., creating a foreign key that references a partition.  This
   * restriction allows us to dodge some complexities around
   * pg_constraint and pg_trigger row creations that would be needed
   * during ATTACH/DETACH for this kind of relationship.
 */

  if (fk->confrelid == RelationGetRelid(partRel))
   ereport(ERROR,
     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
      errmsg("cannot attach table \"%s\" as a partition because it is referenced by foreign key \"%s\"",
       RelationGetRelationName(partRel),
       get_constraint_name(fk->conoid))));

  clone = lappend_oid(clone, fk->conoid);
 }

 /*
  * Silently do nothing if there's nothing to do.  In particular, this
  * avoids throwing a spurious error for foreign tables.
 */

 if (clone == NIL)
  return;

 if (partRel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
  ereport(ERROR,
    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     errmsg("foreign key constraints are not supported on foreign tables")));

 /*
  * Triggers of the foreign keys will be manipulated a bunch of times in
  * the loop below.  To avoid repeatedly opening/closing the trigger
  * catalog relation, we open it here and pass it to the subroutines called
  * below.
 */

 trigrel = table_open(TriggerRelationId, RowExclusiveLock);

 /*
  * The constraint key may differ, if the columns in the partition are
  * different.  This map is used to convert them.
 */

 attmap = build_attrmap_by_name(RelationGetDescr(partRel),
           RelationGetDescr(parentRel),
           false);

 partFKs = copyObject(RelationGetFKeyList(partRel));

 foreach(cell, clone)
 {
  Oid   parentConstrOid = lfirst_oid(cell);
  Form_pg_constraint constrForm;
  Relation pkrel;
  HeapTuple tuple;
  int   numfks;
  AttrNumber conkey[INDEX_MAX_KEYS];
  AttrNumber mapped_conkey[INDEX_MAX_KEYS];
  AttrNumber confkey[INDEX_MAX_KEYS];
  Oid   conpfeqop[INDEX_MAX_KEYS];
  Oid   conppeqop[INDEX_MAX_KEYS];
  Oid   conffeqop[INDEX_MAX_KEYS];
  int   numfkdelsetcols;
  AttrNumber confdelsetcols[INDEX_MAX_KEYS];
  Constraint *fkconstraint;
  bool  attached;
  Oid   indexOid;
  ObjectAddress address;
  ListCell   *lc;
  Oid   insertTriggerOid = InvalidOid,
     updateTriggerOid = InvalidOid;
  bool  with_period;

  tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(parentConstrOid));
  if (!HeapTupleIsValid(tuple))
   elog(ERROR, "cache lookup failed for constraint %u",
     parentConstrOid);
  constrForm = (Form_pg_constraint) GETSTRUCT(tuple);

  /* Don't clone constraints whose parents are being cloned */
  if (list_member_oid(clone, constrForm->conparentid))
  {
   ReleaseSysCache(tuple);
   continue;
  }

  /*
   * Need to prevent concurrent deletions.  If pkrel is a partitioned
   * relation, that means to lock all partitions.
 */

  pkrel = table_open(constrForm->confrelid, ShareRowExclusiveLock);
  if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
   (void) find_all_inheritors(RelationGetRelid(pkrel),
            ShareRowExclusiveLock, NULL);

  DeconstructFkConstraintRow(tuple, &numfks, conkey, confkey,
           conpfeqop, conppeqop, conffeqop,
           &numfkdelsetcols, confdelsetcols);
  for (int i = 0; i < numfks; i++)
   mapped_conkey[i] = attmap->attnums[conkey[i] - 1];

  /*
   * Get the "check" triggers belonging to the constraint, if it is
   * ENFORCED, to pass as parent OIDs for similar triggers that will be
   * created on the partition in addFkRecurseReferencing().  They are
   * also passed to tryAttachPartitionForeignKey() below to simply
   * assign as parents to the partition's existing "check" triggers,
   * that is, if the corresponding constraints is deemed attachable to
   * the parent constraint.
 */

  if (constrForm->conenforced)
   GetForeignKeyCheckTriggers(trigrel, constrForm->oid,
            constrForm->confrelid, constrForm->conrelid,
            &insertTriggerOid, &updateTriggerOid);

  /*
   * Before creating a new constraint, see whether any existing FKs are
   * fit for the purpose.  If one is, attach the parent constraint to
   * it, and don't clone anything.  This way we avoid the expensive
   * verification step and don't end up with a duplicate FK, and we
   * don't need to recurse to partitions for this constraint.
 */

  attached = false;
  foreach(lc, partFKs)
  {
   ForeignKeyCacheInfo *fk = lfirst_node(ForeignKeyCacheInfo, lc);

   if (tryAttachPartitionForeignKey(wqueue,
            fk,
            partRel,
            parentConstrOid,
            numfks,
            mapped_conkey,
            confkey,
            conpfeqop,
            insertTriggerOid,
            updateTriggerOid,
            trigrel))
   {
    attached = true;
    table_close(pkrel, NoLock);
    break;
   }
  }
  if (attached)
  {
   ReleaseSysCache(tuple);
   continue;
  }

  /* No dice.  Set up to create our own constraint */
  fkconstraint = makeNode(Constraint);
  fkconstraint->contype = CONSTRAINT_FOREIGN;
  /* ->conname determined below */
  fkconstraint->deferrable = constrForm->condeferrable;
  fkconstraint->initdeferred = constrForm->condeferred;
  fkconstraint->location = -1;
  fkconstraint->pktable = NULL;
  /* ->fk_attrs determined below */
  fkconstraint->pk_attrs = NIL;
  fkconstraint->fk_matchtype = constrForm->confmatchtype;
  fkconstraint->fk_upd_action = constrForm->confupdtype;
  fkconstraint->fk_del_action = constrForm->confdeltype;
  fkconstraint->fk_del_set_cols = NIL;
  fkconstraint->old_conpfeqop = NIL;
  fkconstraint->old_pktable_oid = InvalidOid;
  fkconstraint->is_enforced = constrForm->conenforced;
  fkconstraint->skip_validation = false;
  fkconstraint->initially_valid = constrForm->convalidated;
  for (int i = 0; i < numfks; i++)
  {
   Form_pg_attribute att;

   att = TupleDescAttr(RelationGetDescr(partRel),
        mapped_conkey[i] - 1);
   fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
            makeString(NameStr(att->attname)));
  }

  indexOid = constrForm->conindid;
  with_period = constrForm->conperiod;

  /* Create the pg_constraint entry at this level */
  address = addFkConstraint(addFkReferencingSide,
          NameStr(constrForm->conname), fkconstraint,
          partRel, pkrel, indexOid, parentConstrOid,
          numfks, confkey,
          mapped_conkey, conpfeqop,
          conppeqop, conffeqop,
          numfkdelsetcols, confdelsetcols,
          false, with_period);

  /* Done with the cloned constraint's tuple */
  ReleaseSysCache(tuple);

  /* Create the check triggers, and recurse to partitions, if any */
  addFkRecurseReferencing(wqueue,
        fkconstraint,
        partRel,
        pkrel,
        indexOid,
        address.objectId,
        numfks,
        confkey,
        mapped_conkey,
        conpfeqop,
        conppeqop,
        conffeqop,
        numfkdelsetcols,
        confdelsetcols,
        false/* no old check exists */
        AccessExclusiveLock,
        insertTriggerOid,
        updateTriggerOid,
        with_period);
  table_close(pkrel, NoLock);
 }

 table_close(trigrel, RowExclusiveLock);
}

/*
 * When the parent of a partition receives [the referencing side of] a foreign
 * key, we must propagate that foreign key to the partition.  However, the
 * partition might already have an equivalent foreign key; this routine
 * compares the given ForeignKeyCacheInfo (in the partition) to the FK defined
 * by the other parameters.  If they are equivalent, create the link between
 * the two constraints and return true.
 *
 * If the given FK does not match the one defined by rest of the params,
 * return false.
 */

static bool
tryAttachPartitionForeignKey(List **wqueue,
        ForeignKeyCacheInfo *fk,
        Relation partition,
        Oid parentConstrOid,
        int numfks,
        AttrNumber *mapped_conkey,
        AttrNumber *confkey,
        Oid *conpfeqop,
        Oid parentInsTrigger,
        Oid parentUpdTrigger,
        Relation trigrel)
{
 HeapTuple parentConstrTup;
 Form_pg_constraint parentConstr;
 HeapTuple partcontup;
 Form_pg_constraint partConstr;

 parentConstrTup = SearchSysCache1(CONSTROID,
           ObjectIdGetDatum(parentConstrOid));
 if (!HeapTupleIsValid(parentConstrTup))
  elog(ERROR, "cache lookup failed for constraint %u", parentConstrOid);
 parentConstr = (Form_pg_constraint) GETSTRUCT(parentConstrTup);

 /*
  * Do some quick & easy initial checks.  If any of these fail, we cannot
  * use this constraint.
 */

 if (fk->confrelid != parentConstr->confrelid || fk->nkeys != numfks)
 {
  ReleaseSysCache(parentConstrTup);
  return false;
 }
 for (int i = 0; i < numfks; i++)
 {
  if (fk->conkey[i] != mapped_conkey[i] ||
   fk->confkey[i] != confkey[i] ||
   fk->conpfeqop[i] != conpfeqop[i])
  {
   ReleaseSysCache(parentConstrTup);
   return false;
  }
 }

 /* Looks good so far; perform more extensive checks. */
 partcontup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
 if (!HeapTupleIsValid(partcontup))
  elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
 partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);

 /*
  * An error should be raised if the constraint enforceability is
  * different. Returning false without raising an error, as we do for other
  * attributes, could lead to a duplicate constraint with the same
  * enforceability as the parent. While this may be acceptable, it may not
  * be ideal. Therefore, it's better to raise an error and allow the user
  * to correct the enforceability before proceeding.
 */

 if (partConstr->conenforced != parentConstr->conenforced)
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     errmsg("constraint \"%s\" enforceability conflicts with constraint \"%s\" on relation \"%s\"",
      NameStr(parentConstr->conname),
      NameStr(partConstr->conname),
      RelationGetRelationName(partition))));

 if (OidIsValid(partConstr->conparentid) ||
  partConstr->condeferrable != parentConstr->condeferrable ||
  partConstr->condeferred != parentConstr->condeferred ||
  partConstr->confupdtype != parentConstr->confupdtype ||
  partConstr->confdeltype != parentConstr->confdeltype ||
  partConstr->confmatchtype != parentConstr->confmatchtype)
 {
  ReleaseSysCache(parentConstrTup);
  ReleaseSysCache(partcontup);
  return false;
 }

 ReleaseSysCache(parentConstrTup);
 ReleaseSysCache(partcontup);

 /* Looks good!  Attach this constraint. */
 AttachPartitionForeignKey(wqueue, partition, fk->conoid,
         parentConstrOid, parentInsTrigger,
         parentUpdTrigger, trigrel);

 return true;
}

/*
 * AttachPartitionForeignKey
 *
 * The subroutine for tryAttachPartitionForeignKey performs the final tasks of
 * attaching the constraint, removing redundant triggers and entries from
 * pg_constraint, and setting the constraint's parent.
 */

static void
AttachPartitionForeignKey(List **wqueue,
        Relation partition,
        Oid partConstrOid,
        Oid parentConstrOid,
        Oid parentInsTrigger,
        Oid parentUpdTrigger,
        Relation trigrel)
{
 HeapTuple parentConstrTup;
 Form_pg_constraint parentConstr;
 HeapTuple partcontup;
 Form_pg_constraint partConstr;
 bool  queueValidation;
 Oid   partConstrFrelid;
 Oid   partConstrRelid;
 bool  parentConstrIsEnforced;

 /* Fetch the parent constraint tuple */
 parentConstrTup = SearchSysCache1(CONSTROID,
           ObjectIdGetDatum(parentConstrOid));
 if (!HeapTupleIsValid(parentConstrTup))
  elog(ERROR, "cache lookup failed for constraint %u", parentConstrOid);
 parentConstr = (Form_pg_constraint) GETSTRUCT(parentConstrTup);
 parentConstrIsEnforced = parentConstr->conenforced;

 /* Fetch the child constraint tuple */
 partcontup = SearchSysCache1(CONSTROID,
         ObjectIdGetDatum(partConstrOid));
 if (!HeapTupleIsValid(partcontup))
  elog(ERROR, "cache lookup failed for constraint %u", partConstrOid);
 partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
 partConstrFrelid = partConstr->confrelid;
 partConstrRelid = partConstr->conrelid;

 /*
  * If the referenced table is partitioned, then the partition we're
  * attaching now has extra pg_constraint rows and action triggers that are
  * no longer needed.  Remove those.
 */

 if (get_rel_relkind(partConstrFrelid) == RELKIND_PARTITIONED_TABLE)
 {
  Relation pg_constraint = table_open(ConstraintRelationId, RowShareLock);

  RemoveInheritedConstraint(pg_constraint, trigrel, partConstrOid,
          partConstrRelid);

  table_close(pg_constraint, RowShareLock);
 }

 /*
  * Will we need to validate this constraint?   A valid parent constraint
  * implies that all child constraints have been validated, so if this one
  * isn't, we must trigger phase 3 validation.
 */

 queueValidation = parentConstr->convalidated && !partConstr->convalidated;

 ReleaseSysCache(partcontup);
 ReleaseSysCache(parentConstrTup);

 /*
  * The action triggers in the new partition become redundant -- the parent
  * table already has equivalent ones, and those will be able to reach the
  * partition.  Remove the ones in the partition.  We identify them because
  * they have our constraint OID, as well as being on the referenced rel.
 */

 DropForeignKeyConstraintTriggers(trigrel, partConstrOid, partConstrFrelid,
          partConstrRelid);

 ConstraintSetParentConstraint(partConstrOid, parentConstrOid,
          RelationGetRelid(partition));

 /*
  * Like the constraint, attach partition's "check" triggers to the
  * corresponding parent triggers if the constraint is ENFORCED. NOT
  * ENFORCED constraints do not have these triggers.
 */

 if (parentConstrIsEnforced)
 {
  Oid   insertTriggerOid,
     updateTriggerOid;

  GetForeignKeyCheckTriggers(trigrel,
           partConstrOid, partConstrFrelid, partConstrRelid,
           &insertTriggerOid, &updateTriggerOid);
  Assert(OidIsValid(insertTriggerOid) && OidIsValid(parentInsTrigger));
  TriggerSetParentTrigger(trigrel, insertTriggerOid, parentInsTrigger,
        RelationGetRelid(partition));
  Assert(OidIsValid(updateTriggerOid) && OidIsValid(parentUpdTrigger));
  TriggerSetParentTrigger(trigrel, updateTriggerOid, parentUpdTrigger,
        RelationGetRelid(partition));
 }

 /*
  * We updated this pg_constraint row above to set its parent; validating
  * it will cause its convalidated flag to change, so we need CCI here.  In
  * addition, we need it unconditionally for the rare case where the parent
  * table has *two* identical constraints; when reaching this function for
  * the second one, we must have made our changes visible, otherwise we
  * would try to attach both to this one.
 */

 CommandCounterIncrement();

 /* If validation is needed, put it in the queue now. */
 if (queueValidation)
 {
  Relation conrel;
  Oid   confrelid;

  conrel = table_open(ConstraintRelationId, RowExclusiveLock);

  partcontup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(partConstrOid));
  if (!HeapTupleIsValid(partcontup))
   elog(ERROR, "cache lookup failed for constraint %u", partConstrOid);

  confrelid = ((Form_pg_constraint) GETSTRUCT(partcontup))->confrelid;

  /* Use the same lock as for AT_ValidateConstraint */
  QueueFKConstraintValidation(wqueue, conrel, partition, confrelid,
         partcontup, ShareUpdateExclusiveLock);
  ReleaseSysCache(partcontup);
  table_close(conrel, RowExclusiveLock);
 }
}

/*
 * RemoveInheritedConstraint
 *
 * Removes the constraint and its associated trigger from the specified
 * relation, which inherited the given constraint.
 */

static void
RemoveInheritedConstraint(Relation conrel, Relation trigrel, Oid conoid,
        Oid conrelid)
{
 ObjectAddresses *objs;
 HeapTuple consttup;
 ScanKeyData key;
 SysScanDesc scan;
 HeapTuple trigtup;

 ScanKeyInit(&key,
    Anum_pg_constraint_conrelid,
    BTEqualStrategyNumber, F_OIDEQ,
    ObjectIdGetDatum(conrelid));

 scan = systable_beginscan(conrel,
         ConstraintRelidTypidNameIndexId,
         true, NULL, 1, &key);
 objs = new_object_addresses();
 while ((consttup = systable_getnext(scan)) != NULL)
 {
  Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(consttup);

  if (conform->conparentid != conoid)
   continue;
  else
  {
   ObjectAddress addr;
   SysScanDesc scan2;
   ScanKeyData key2;
   int   n PG_USED_FOR_ASSERTS_ONLY;

   ObjectAddressSet(addr, ConstraintRelationId, conform->oid);
   add_exact_object_address(&addr, objs);

   /*
    * First we must delete the dependency record that binds the
    * constraint records together.
 */

   n = deleteDependencyRecordsForSpecific(ConstraintRelationId,
               conform->oid,
               DEPENDENCY_INTERNAL,
               ConstraintRelationId,
               conoid);
   Assert(n == 1);  /* actually only one is expected */

   /*
    * Now search for the triggers for this constraint and set them up
    * for deletion too
 */

   ScanKeyInit(&key2,
      Anum_pg_trigger_tgconstraint,
      BTEqualStrategyNumber, F_OIDEQ,
      ObjectIdGetDatum(conform->oid));
   scan2 = systable_beginscan(trigrel, TriggerConstraintIndexId,
            true, NULL, 1, &key2);
   while ((trigtup = systable_getnext(scan2)) != NULL)
   {
    ObjectAddressSet(addr, TriggerRelationId,
         ((Form_pg_trigger) GETSTRUCT(trigtup))->oid);
    add_exact_object_address(&addr, objs);
   }
   systable_endscan(scan2);
  }
 }
 /* make the dependency deletions visible */
 CommandCounterIncrement();
 performMultipleDeletions(objs, DROP_RESTRICT,
        PERFORM_DELETION_INTERNAL);
 systable_endscan(scan);
}

/*
 * DropForeignKeyConstraintTriggers
 *
 * The subroutine for tryAttachPartitionForeignKey handles the deletion of
 * action triggers for the foreign key constraint.
 *
 * If valid confrelid and conrelid values are not provided, the respective
 * trigger check will be skipped, and the trigger will be considered for
 * removal.
 */

static void
DropForeignKeyConstraintTriggers(Relation trigrel, Oid conoid, Oid confrelid,
         Oid conrelid)
{
 ScanKeyData key;
 SysScanDesc scan;
 HeapTuple trigtup;

 ScanKeyInit(&key,
    Anum_pg_trigger_tgconstraint,
    BTEqualStrategyNumber, F_OIDEQ,
    ObjectIdGetDatum(conoid));
 scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
         NULL, 1, &key);
 while ((trigtup = systable_getnext(scan)) != NULL)
 {
  Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
  ObjectAddress trigger;

  /* Invalid if trigger is not for a referential integrity constraint */
  if (!OidIsValid(trgform->tgconstrrelid))
   continue;
  if (OidIsValid(conrelid) && trgform->tgconstrrelid != conrelid)
   continue;
  if (OidIsValid(confrelid) && trgform->tgrelid != confrelid)
   continue;

  /* We should be dropping trigger related to foreign key constraint */
  Assert(trgform->tgfoid == F_RI_FKEY_CHECK_INS ||
      trgform->tgfoid == F_RI_FKEY_CHECK_UPD ||
      trgform->tgfoid == F_RI_FKEY_CASCADE_DEL ||
      trgform->tgfoid == F_RI_FKEY_CASCADE_UPD ||
      trgform->tgfoid == F_RI_FKEY_RESTRICT_DEL ||
      trgform->tgfoid == F_RI_FKEY_RESTRICT_UPD ||
      trgform->tgfoid == F_RI_FKEY_SETNULL_DEL ||
      trgform->tgfoid == F_RI_FKEY_SETNULL_UPD ||
      trgform->tgfoid == F_RI_FKEY_SETDEFAULT_DEL ||
      trgform->tgfoid == F_RI_FKEY_SETDEFAULT_UPD ||
      trgform->tgfoid == F_RI_FKEY_NOACTION_DEL ||
      trgform->tgfoid == F_RI_FKEY_NOACTION_UPD);

  /*
   * The constraint is originally set up to contain this trigger as an
   * implementation object, so there's a dependency record that links
   * the two; however, since the trigger is no longer needed, we remove
   * the dependency link in order to be able to drop the trigger while
   * keeping the constraint intact.
 */

  deleteDependencyRecordsFor(TriggerRelationId,
           trgform->oid,
           false);
  /* make dependency deletion visible to performDeletion */
  CommandCounterIncrement();
  ObjectAddressSet(trigger, TriggerRelationId,
       trgform->oid);
  performDeletion(&trigger, DROP_RESTRICT, 0);
  /* make trigger drop visible, in case the loop iterates */
  CommandCounterIncrement();
 }

 systable_endscan(scan);
}

/*
 * GetForeignKeyActionTriggers
 *   Returns delete and update "action" triggers of the given relation
 *   belonging to the given constraint
 */

static void
GetForeignKeyActionTriggers(Relation trigrel,
       Oid conoid, Oid confrelid, Oid conrelid,
       Oid *deleteTriggerOid,
       Oid *updateTriggerOid)
{
 ScanKeyData key;
 SysScanDesc scan;
 HeapTuple trigtup;

 *deleteTriggerOid = *updateTriggerOid = InvalidOid;
 ScanKeyInit(&key,
    Anum_pg_trigger_tgconstraint,
    BTEqualStrategyNumber, F_OIDEQ,
    ObjectIdGetDatum(conoid));

 scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
         NULL, 1, &key);
 while ((trigtup = systable_getnext(scan)) != NULL)
 {
  Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);

  if (trgform->tgconstrrelid != conrelid)
   continue;
  if (trgform->tgrelid != confrelid)
   continue;
  /* Only ever look at "action" triggers on the PK side. */
  if (RI_FKey_trigger_type(trgform->tgfoid) != RI_TRIGGER_PK)
   continue;
  if (TRIGGER_FOR_DELETE(trgform->tgtype))
  {
   Assert(*deleteTriggerOid == InvalidOid);
   *deleteTriggerOid = trgform->oid;
  }
  else if (TRIGGER_FOR_UPDATE(trgform->tgtype))
  {
   Assert(*updateTriggerOid == InvalidOid);
   *updateTriggerOid = trgform->oid;
  }
#ifndef USE_ASSERT_CHECKING
  /* In an assert-enabled build, continue looking to find duplicates */
  if (OidIsValid(*deleteTriggerOid) && OidIsValid(*updateTriggerOid))
   break;
#endif
 }

 if (!OidIsValid(*deleteTriggerOid))
  elog(ERROR, "could not find ON DELETE action trigger of foreign key constraint %u",
    conoid);
 if (!OidIsValid(*updateTriggerOid))
  elog(ERROR, "could not find ON UPDATE action trigger of foreign key constraint %u",
    conoid);

 systable_endscan(scan);
}

/*
 * GetForeignKeyCheckTriggers
 *   Returns insert and update "check" triggers of the given relation
 *   belonging to the given constraint
 */

static void
GetForeignKeyCheckTriggers(Relation trigrel,
         Oid conoid, Oid confrelid, Oid conrelid,
         Oid *insertTriggerOid,
         Oid *updateTriggerOid)
{
 ScanKeyData key;
 SysScanDesc scan;
 HeapTuple trigtup;

 *insertTriggerOid = *updateTriggerOid = InvalidOid;
 ScanKeyInit(&key,
    Anum_pg_trigger_tgconstraint,
    BTEqualStrategyNumber, F_OIDEQ,
    ObjectIdGetDatum(conoid));

 scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
         NULL, 1, &key);
 while ((trigtup = systable_getnext(scan)) != NULL)
 {
  Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);

  if (trgform->tgconstrrelid != confrelid)
   continue;
  if (trgform->tgrelid != conrelid)
   continue;
  /* Only ever look at "check" triggers on the FK side. */
  if (RI_FKey_trigger_type(trgform->tgfoid) != RI_TRIGGER_FK)
   continue;
  if (TRIGGER_FOR_INSERT(trgform->tgtype))
  {
   Assert(*insertTriggerOid == InvalidOid);
   *insertTriggerOid = trgform->oid;
  }
  else if (TRIGGER_FOR_UPDATE(trgform->tgtype))
  {
   Assert(*updateTriggerOid == InvalidOid);
   *updateTriggerOid = trgform->oid;
  }
#ifndef USE_ASSERT_CHECKING
  /* In an assert-enabled build, continue looking to find duplicates. */
  if (OidIsValid(*insertTriggerOid) && OidIsValid(*updateTriggerOid))
   break;
#endif
 }

 if (!OidIsValid(*insertTriggerOid))
  elog(ERROR, "could not find ON INSERT check triggers of foreign key constraint %u",
    conoid);
 if (!OidIsValid(*updateTriggerOid))
  elog(ERROR, "could not find ON UPDATE check triggers of foreign key constraint %u",
    conoid);

 systable_endscan(scan);
}

/*
 * ALTER TABLE ALTER CONSTRAINT
 *
 * Update the attributes of a constraint.
 *
 * Currently only works for Foreign Key and not null constraints.
 *
 * If the constraint is modified, returns its address; otherwise, return
 * InvalidObjectAddress.
 */

static ObjectAddress
ATExecAlterConstraint(List **wqueue, Relation rel, ATAlterConstraint *cmdcon,
       bool recurse, LOCKMODE lockmode)
{
 Relation conrel;
 Relation tgrel;
 SysScanDesc scan;
 ScanKeyData skey[3];
 HeapTuple contuple;
 Form_pg_constraint currcon;
 ObjectAddress address;

 /*
  * Disallow altering ONLY a partitioned table, as it would make no sense.
  * This is okay for legacy inheritance.
 */

 if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
  ereport(ERROR,
    errcode(ERRCODE_INVALID_TABLE_DEFINITION),
    errmsg("constraint must be altered in child tables too"),
    errhint("Do not specify the ONLY keyword."));


 conrel = table_open(ConstraintRelationId, RowExclusiveLock);
 tgrel = table_open(TriggerRelationId, RowExclusiveLock);

 /*
  * Find and check the target constraint
 */

 ScanKeyInit(&skey[0],
    Anum_pg_constraint_conrelid,
    BTEqualStrategyNumber, F_OIDEQ,
    ObjectIdGetDatum(RelationGetRelid(rel)));
 ScanKeyInit(&skey[1],
    Anum_pg_constraint_contypid,
    BTEqualStrategyNumber, F_OIDEQ,
    ObjectIdGetDatum(InvalidOid));
 ScanKeyInit(&skey[2],
    Anum_pg_constraint_conname,
    BTEqualStrategyNumber, F_NAMEEQ,
    CStringGetDatum(cmdcon->conname));
 scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId,
         true, NULL, 3, skey);

 /* There can be at most one matching row */
 if (!HeapTupleIsValid(contuple = systable_getnext(scan)))
  ereport(ERROR,
    (errcode(ERRCODE_UNDEFINED_OBJECT),
     errmsg("constraint \"%s\" of relation \"%s\" does not exist",
      cmdcon->conname, RelationGetRelationName(rel))));

 currcon = (Form_pg_constraint) GETSTRUCT(contuple);
 if (cmdcon->alterDeferrability && currcon->contype != CONSTRAINT_FOREIGN)
  ereport(ERROR,
    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint",
      cmdcon->conname, RelationGetRelationName(rel))));
 if (cmdcon->alterEnforceability && currcon->contype != CONSTRAINT_FOREIGN)
  ereport(ERROR,
    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     errmsg("cannot alter enforceability of constraint \"%s\" of relation \"%s\"",
      cmdcon->conname, RelationGetRelationName(rel))));
 if (cmdcon->alterInheritability &&
  currcon->contype != CONSTRAINT_NOTNULL)
  ereport(ERROR,
    errcode(ERRCODE_WRONG_OBJECT_TYPE),
    errmsg("constraint \"%s\" of relation \"%s\" is not a not-null constraint",
        cmdcon->conname, RelationGetRelationName(rel)));

 /* Refuse to modify inheritability of inherited constraints */
 if (cmdcon->alterInheritability &&
  cmdcon->noinherit && currcon->coninhcount > 0)
  ereport(ERROR,
    errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    errmsg("cannot alter inherited constraint \"%s\" on relation \"%s\"",
        NameStr(currcon->conname),
        RelationGetRelationName(rel)));

 /*
  * If it's not the topmost constraint, raise an error.
  *
  * Altering a non-topmost constraint leaves some triggers untouched, since
  * they are not directly connected to this constraint; also, pg_dump would
  * ignore the deferrability status of the individual constraint, since it
  * only dumps topmost constraints.  Avoid these problems by refusing this
  * operation and telling the user to alter the parent constraint instead.
 */

 if (OidIsValid(currcon->conparentid))
 {
  HeapTuple tp;
  Oid   parent = currcon->conparentid;
  char    *ancestorname = NULL;
  char    *ancestortable = NULL;

  /* Loop to find the topmost constraint */
  while (HeapTupleIsValid(tp = SearchSysCache1(CONSTROID, ObjectIdGetDatum(parent))))
  {
   Form_pg_constraint contup = (Form_pg_constraint) GETSTRUCT(tp);

   /* If no parent, this is the constraint we want */
   if (!OidIsValid(contup->conparentid))
   {
    ancestorname = pstrdup(NameStr(contup->conname));
    ancestortable = get_rel_name(contup->conrelid);
    ReleaseSysCache(tp);
    break;
   }

   parent = contup->conparentid;
   ReleaseSysCache(tp);
  }

  ereport(ERROR,
    (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     errmsg("cannot alter constraint \"%s\" on relation \"%s\"",
      cmdcon->conname, RelationGetRelationName(rel)),
     ancestorname && ancestortable ?
     errdetail("Constraint \"%s\" is derived from constraint \"%s\" of relation \"%s\".",
         cmdcon->conname, ancestorname, ancestortable) : 0,
     errhint("You may alter the constraint it derives from instead.")));
 }

 address = InvalidObjectAddress;

 /*
  * Do the actual catalog work, and recurse if necessary.
 */

 if (ATExecAlterConstraintInternal(wqueue, cmdcon, conrel, tgrel, rel,
           contuple, recurse, lockmode))
  ObjectAddressSet(address, ConstraintRelationId, currcon->oid);

 systable_endscan(scan);

 table_close(tgrel, RowExclusiveLock);
 table_close(conrel, RowExclusiveLock);

 return address;
}

/*
 * A subroutine of ATExecAlterConstraint that calls the respective routines for
 * altering constraint's enforceability, deferrability or inheritability.
 */

static bool
ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon,
         Relation conrel, Relation tgrel, Relation rel,
         HeapTuple contuple, bool recurse,
         LOCKMODE lockmode)
{
 Form_pg_constraint currcon;
 bool  changed = false;
 List    *otherrelids = NIL;

 currcon = (Form_pg_constraint) GETSTRUCT(contuple);

 /*
  * Do the catalog work for the enforceability or deferrability change,
  * recurse if necessary.
  *
  * Note that even if deferrability is requested to be altered along with
  * enforceability, we don't need to explicitly update multiple entries in
  * pg_trigger related to deferrability.
  *
  * Modifying enforceability involves either creating or dropping the
  * trigger, during which the deferrability setting will be adjusted
  * automatically.
 */

 if (cmdcon->alterEnforceability &&
  ATExecAlterConstrEnforceability(wqueue, cmdcon, conrel, tgrel,
          currcon->conrelid, currcon->confrelid,
          contuple, lockmode, InvalidOid,
          InvalidOid, InvalidOid, InvalidOid))
  changed = true;

 else if (cmdcon->alterDeferrability &&
    ATExecAlterConstrDeferrability(wqueue, cmdcon, conrel, tgrel, rel,
           contuple, recurse, &otherrelids,
           lockmode))
 {
  /*
   * AlterConstrUpdateConstraintEntry already invalidated relcache for
   * the relations having the constraint itself; here we also invalidate
   * for relations that have any triggers that are part of the
   * constraint.
 */

  foreach_oid(relid, otherrelids)
   CacheInvalidateRelcacheByRelid(relid);

  changed = true;
 }

 /*
  * Do the catalog work for the inheritability change.
 */

 if (cmdcon->alterInheritability &&
  ATExecAlterConstrInheritability(wqueue, cmdcon, conrel, rel, contuple,
          lockmode))
  changed = true;

 return changed;
}

/*
 * Returns true if the constraint's enforceability is altered.
 *
 * Depending on whether the constraint is being set to ENFORCED or NOT
 * ENFORCED, it creates or drops the trigger accordingly.
 *
 * Note that we must recurse even when trying to change a constraint to not
 * enforced if it is already not enforced, in case descendant constraints
 * might be enforced and need to be changed to not enforced. Conversely, we
 * should do nothing if a constraint is being set to enforced and is already
 * enforced, as descendant constraints cannot be different in that case.
 */

static bool
ATExecAlterConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon,
        Relation conrel, Relation tgrel,
        Oid fkrelid, Oid pkrelid,
        HeapTuple contuple, LOCKMODE lockmode,
        Oid ReferencedParentDelTrigger,
        Oid ReferencedParentUpdTrigger,
        Oid ReferencingParentInsTrigger,
        Oid ReferencingParentUpdTrigger)
{
 Form_pg_constraint currcon;
 Oid   conoid;
 Relation rel;
 bool  changed = false;

 /* Since this function recurses, it could be driven to stack overflow */
 check_stack_depth();

 Assert(cmdcon->alterEnforceability);

 currcon = (Form_pg_constraint) GETSTRUCT(contuple);
 conoid = currcon->oid;

 /* Should be foreign key constraint */
 Assert(currcon->contype == CONSTRAINT_FOREIGN);

 rel = table_open(currcon->conrelid, lockmode);

 if (currcon->conenforced != cmdcon->is_enforced)
 {
  AlterConstrUpdateConstraintEntry(cmdcon, conrel, contuple);
  changed = true;
 }

 /* Drop triggers */
 if (!cmdcon->is_enforced)
 {
  /*
   * When setting a constraint to NOT ENFORCED, the constraint triggers
   * need to be dropped. Therefore, we must process the child relations
   * first, followed by the parent, to account for dependencies.
 */

  if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
   get_rel_relkind(currcon->confrelid) == RELKIND_PARTITIONED_TABLE)
   AlterConstrEnforceabilityRecurse(wqueue, cmdcon, conrel, tgrel,
            fkrelid, pkrelid, contuple,
            lockmode, InvalidOid, InvalidOid,
            InvalidOid, InvalidOid);

  /* Drop all the triggers */
  DropForeignKeyConstraintTriggers(tgrel, conoid, InvalidOid, InvalidOid);
 }
 else if (changed)   /* Create triggers */
 {
  Oid   ReferencedDelTriggerOid = InvalidOid,
     ReferencedUpdTriggerOid = InvalidOid,
     ReferencingInsTriggerOid = InvalidOid,
     ReferencingUpdTriggerOid = InvalidOid;

  /* Prepare the minimal information required for trigger creation. */
  Constraint *fkconstraint = makeNode(Constraint);

  fkconstraint->conname = pstrdup(NameStr(currcon->conname));
  fkconstraint->fk_matchtype = currcon->confmatchtype;
  fkconstraint->fk_upd_action = currcon->confupdtype;
  fkconstraint->fk_del_action = currcon->confdeltype;
  fkconstraint->deferrable = currcon->condeferrable;
  fkconstraint->initdeferred = currcon->condeferred;

  /* Create referenced triggers */
  if (currcon->conrelid == fkrelid)
   createForeignKeyActionTriggers(currcon->conrelid,
             currcon->confrelid,
             fkconstraint,
             conoid,
             currcon->conindid,
             ReferencedParentDelTrigger,
             ReferencedParentUpdTrigger,
             &ReferencedDelTriggerOid,
             &ReferencedUpdTriggerOid);

  /* Create referencing triggers */
  if (currcon->confrelid == pkrelid)
   createForeignKeyCheckTriggers(currcon->conrelid,
            pkrelid,
            fkconstraint,
            conoid,
            currcon->conindid,
            ReferencingParentInsTrigger,
            ReferencingParentUpdTrigger,
            &ReferencingInsTriggerOid,
            &ReferencingUpdTriggerOid);

  /*
   * Tell Phase 3 to check that the constraint is satisfied by existing
   * rows.  Only applies to leaf partitions, and (for constraints that
   * reference a partitioned table) only if this is not one of the
   * pg_constraint rows that exist solely to support action triggers.
 */

  if (rel->rd_rel->relkind == RELKIND_RELATION &&
   currcon->confrelid == pkrelid)
  {
   AlteredTableInfo *tab;
   NewConstraint *newcon;

   newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
   newcon->name = fkconstraint->conname;
   newcon->contype = CONSTR_FOREIGN;
   newcon->refrelid = currcon->confrelid;
   newcon->refindid = currcon->conindid;
   newcon->conid = currcon->oid;
   newcon->qual = (Node *) fkconstraint;

   /* Find or create work queue entry for this table */
   tab = ATGetQueueEntry(wqueue, rel);
   tab->constraints = lappend(tab->constraints, newcon);
  }

  /*
   * If the table at either end of the constraint is partitioned, we
   * need to recurse and create triggers for each constraint that is a
   * child of this one.
 */

  if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
   get_rel_relkind(currcon->confrelid) == RELKIND_PARTITIONED_TABLE)
   AlterConstrEnforceabilityRecurse(wqueue, cmdcon, conrel, tgrel,
            fkrelid, pkrelid, contuple,
            lockmode, ReferencedDelTriggerOid,
            ReferencedUpdTriggerOid,
            ReferencingInsTriggerOid,
            ReferencingUpdTriggerOid);
 }

 table_close(rel, NoLock);

 return changed;
}

/*
 * Returns true if the constraint's deferrability is altered.
 *
 * *otherrelids is appended OIDs of relations containing affected triggers.
 *
 * Note that we must recurse even when the values are correct, in case
 * indirect descendants have had their constraints altered locally.
 * (This could be avoided if we forbade altering constraints in partitions
 * but existing releases don't do that.)
 */

static bool
ATExecAlterConstrDeferrability(List **wqueue, ATAlterConstraint *cmdcon,
          Relation conrel, Relation tgrel, Relation rel,
          HeapTuple contuple, bool recurse,
          List **otherrelids, LOCKMODE lockmode)
{
 Form_pg_constraint currcon;
 Oid   refrelid;
 bool  changed = false;

 /* since this function recurses, it could be driven to stack overflow */
 check_stack_depth();

 Assert(cmdcon->alterDeferrability);

 currcon = (Form_pg_constraint) GETSTRUCT(contuple);
 refrelid = currcon->confrelid;

 /* Should be foreign key constraint */
 Assert(currcon->contype == CONSTRAINT_FOREIGN);

 /*
  * If called to modify a constraint that's already in the desired state,
  * silently do nothing.
 */

 if (currcon->condeferrable != cmdcon->deferrable ||
  currcon->condeferred != cmdcon->initdeferred)
 {
  AlterConstrUpdateConstraintEntry(cmdcon, conrel, contuple);
  changed = true;

  /*
   * Now we need to update the multiple entries in pg_trigger that
   * implement the constraint.
 */

  AlterConstrTriggerDeferrability(currcon->oid, tgrel, rel,
          cmdcon->deferrable,
          cmdcon->initdeferred, otherrelids);
 }

 /*
  * If the table at either end of the constraint is partitioned, we need to
  * handle every constraint that is a child of this one.
 */

 if (recurse && changed &&
  (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
   get_rel_relkind(refrelid) == RELKIND_PARTITIONED_TABLE))
  AlterConstrDeferrabilityRecurse(wqueue, cmdcon, conrel, tgrel, rel,
          contuple, recurse, otherrelids,
          lockmode);

 return changed;
}

/*
 * Returns true if the constraint's inheritability is altered.
 */

static bool
ATExecAlterConstrInheritability(List **wqueue, ATAlterConstraint *cmdcon,
        Relation conrel, Relation rel,
        HeapTuple contuple, LOCKMODE lockmode)
{
 Form_pg_constraint currcon;
 AttrNumber colNum;
 char    *colName;
 List    *children;

 Assert(cmdcon->alterInheritability);

 currcon = (Form_pg_constraint) GETSTRUCT(contuple);

 /* The current implementation only works for NOT NULL constraints */
 Assert(currcon->contype == CONSTRAINT_NOTNULL);

 /*
  * If called to modify a constraint that's already in the desired state,
  * silently do nothing.
 */

 if (cmdcon->noinherit == currcon->connoinherit)
  return false;

 AlterConstrUpdateConstraintEntry(cmdcon, conrel, contuple);
 CommandCounterIncrement();

 /* Fetch the column number and name */
 colNum = extractNotNullColumn(contuple);
 colName = get_attname(currcon->conrelid, colNum, false);

 /*
  * Propagate the change to children.  For this subcommand type we don't
  * recursively affect children, just the immediate level.
 */

 children = find_inheritance_children(RelationGetRelid(rel),
           lockmode);
 foreach_oid(childoid, children)
 {
  ObjectAddress addr;

  if (cmdcon->noinherit)
  {
   HeapTuple childtup;
   Form_pg_constraint childcon;

   childtup = findNotNullConstraint(childoid, colName);
   if (!childtup)
    elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation %u",
      colName, childoid);
   childcon = (Form_pg_constraint) GETSTRUCT(childtup);
   Assert(childcon->coninhcount > 0);
   childcon->coninhcount--;
   childcon->conislocal = true;
   CatalogTupleUpdate(conrel, &childtup->t_self, childtup);
   heap_freetuple(childtup);
  }
  else
  {
   Relation childrel = table_open(childoid, NoLock);

   addr = ATExecSetNotNull(wqueue, childrel, NameStr(currcon->conname),
         colName, truetrue, lockmode);
   if (OidIsValid(addr.objectId))
    CommandCounterIncrement();
   table_close(childrel, NoLock);
  }
 }

 return true;
}

/*
 * A subroutine of ATExecAlterConstrDeferrability that updated constraint
 * trigger's deferrability.
 *
 * The arguments to this function have the same meaning as the arguments to
 * ATExecAlterConstrDeferrability.
 */

static void
AlterConstrTriggerDeferrability(Oid conoid, Relation tgrel, Relation rel,
        bool deferrable, bool initdeferred,
        List **otherrelids)
{
 HeapTuple tgtuple;
 ScanKeyData tgkey;
 SysScanDesc tgscan;

 ScanKeyInit(&tgkey,
    Anum_pg_trigger_tgconstraint,
    BTEqualStrategyNumber, F_OIDEQ,
    ObjectIdGetDatum(conoid));
 tgscan = systable_beginscan(tgrel, TriggerConstraintIndexId, true,
        NULL, 1, &tgkey);
 while (HeapTupleIsValid(tgtuple = systable_getnext(tgscan)))
 {
  Form_pg_trigger tgform = (Form_pg_trigger) GETSTRUCT(tgtuple);
  Form_pg_trigger copy_tg;
  HeapTuple tgCopyTuple;

  /*
   * Remember OIDs of other relation(s) involved in FK constraint.
   * (Note: it's likely that we could skip forcing a relcache inval for
   * other rels that don't have a trigger whose properties change, but
   * let's be conservative.)
 */

  if (tgform->tgrelid != RelationGetRelid(rel))
   *otherrelids = list_append_unique_oid(*otherrelids,
              tgform->tgrelid);

  /*
   * Update enable status and deferrability of RI_FKey_noaction_del,
   * RI_FKey_noaction_upd, RI_FKey_check_ins and RI_FKey_check_upd
   * triggers, but not others; see createForeignKeyActionTriggers and
   * CreateFKCheckTrigger.
 */

  if (tgform->tgfoid != F_RI_FKEY_NOACTION_DEL &&
   tgform->tgfoid != F_RI_FKEY_NOACTION_UPD &&
   tgform->tgfoid != F_RI_FKEY_CHECK_INS &&
   tgform->tgfoid != F_RI_FKEY_CHECK_UPD)
   continue;

  tgCopyTuple = heap_copytuple(tgtuple);
  copy_tg = (Form_pg_trigger) GETSTRUCT(tgCopyTuple);

  copy_tg->tgdeferrable = deferrable;
  copy_tg->tginitdeferred = initdeferred;
  CatalogTupleUpdate(tgrel, &tgCopyTuple->t_self, tgCopyTuple);

  InvokeObjectPostAlterHook(TriggerRelationId, tgform->oid, 0);

  heap_freetuple(tgCopyTuple);
 }

 systable_endscan(tgscan);
}

/*
 * Invokes ATExecAlterConstrEnforceability for each constraint that is a child of
 * the specified constraint.
 *
 * Note that this doesn't handle recursion the normal way, viz. by scanning the
 * list of child relations and recursing; instead it uses the conparentid
 * relationships.  This may need to be reconsidered.
 *
 * The arguments to this function have the same meaning as the arguments to
 * ATExecAlterConstrEnforceability.
 */

static void
AlterConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
         Relation conrel, Relation tgrel,
         Oid fkrelid, Oid pkrelid,
         HeapTuple contuple, LOCKMODE lockmode,
         Oid ReferencedParentDelTrigger,
         Oid ReferencedParentUpdTrigger,
         Oid ReferencingParentInsTrigger,
         Oid ReferencingParentUpdTrigger)
{
 Form_pg_constraint currcon;
 Oid   conoid;
 ScanKeyData pkey;
 SysScanDesc pscan;
 HeapTuple childtup;

 currcon = (Form_pg_constraint) GETSTRUCT(contuple);
 conoid = currcon->oid;

 ScanKeyInit(&pkey,
    Anum_pg_constraint_conparentid,
    BTEqualStrategyNumber, F_OIDEQ,
    ObjectIdGetDatum(conoid));

 pscan = systable_beginscan(conrel, ConstraintParentIndexId,
          true, NULL, 1, &pkey);

 while (HeapTupleIsValid(childtup = systable_getnext(pscan)))
  ATExecAlterConstrEnforceability(wqueue, cmdcon, conrel, tgrel, fkrelid,
          pkrelid, childtup, lockmode,
          ReferencedParentDelTrigger,
          ReferencedParentUpdTrigger,
          ReferencingParentInsTrigger,
          ReferencingParentUpdTrigger);

 systable_endscan(pscan);
}

/*
 * Invokes ATExecAlterConstrDeferrability for each constraint that is a child of
 * the specified constraint.
 *
 * Note that this doesn't handle recursion the normal way, viz. by scanning the
 * list of child relations and recursing; instead it uses the conparentid
 * relationships.  This may need to be reconsidered.
 *
 * The arguments to this function have the same meaning as the arguments to
 * ATExecAlterConstrDeferrability.
 */

static void
AlterConstrDeferrabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
        Relation conrel, Relation tgrel, Relation rel,
        HeapTuple contuple, bool recurse,
        List **otherrelids, LOCKMODE lockmode)
{
 Form_pg_constraint currcon;
 Oid   conoid;
 ScanKeyData pkey;
 SysScanDesc pscan;
 HeapTuple childtup;

 currcon = (Form_pg_constraint) GETSTRUCT(contuple);
 conoid = currcon->oid;

 ScanKeyInit(&pkey,
    Anum_pg_constraint_conparentid,
    BTEqualStrategyNumber, F_OIDEQ,
    ObjectIdGetDatum(conoid));

 pscan = systable_beginscan(conrel, ConstraintParentIndexId,
          true, NULL, 1, &pkey);

 while (HeapTupleIsValid(childtup = systable_getnext(pscan)))
 {
  Form_pg_constraint childcon = (Form_pg_constraint) GETSTRUCT(childtup);
  Relation childrel;

  childrel = table_open(childcon->conrelid, lockmode);

  ATExecAlterConstrDeferrability(wqueue, cmdcon, conrel, tgrel, childrel,
            childtup, recurse, otherrelids, lockmode);
  table_close(childrel, NoLock);
 }

 systable_endscan(pscan);
}

/*
 * Update the constraint entry for the given ATAlterConstraint command, and
 * invoke the appropriate hooks.
 */

static void
AlterConstrUpdateConstraintEntry(ATAlterConstraint *cmdcon, Relation conrel,
         HeapTuple contuple)
{
 HeapTuple copyTuple;
 Form_pg_constraint copy_con;

 Assert(cmdcon->alterEnforceability || cmdcon->alterDeferrability ||
     cmdcon->alterInheritability);

 copyTuple = heap_copytuple(contuple);
 copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);

 if (cmdcon->alterEnforceability)
 {
  copy_con->conenforced = cmdcon->is_enforced;

  /*
   * NB: The convalidated status is irrelevant when the constraint is
   * set to NOT ENFORCED, but for consistency, it should still be set
   * appropriately. Similarly, if the constraint is later changed to
   * ENFORCED, validation will be performed during phase 3, so it makes
   * sense to mark it as valid in that case.
 */

  copy_con->convalidated = cmdcon->is_enforced;
 }
 if (cmdcon->alterDeferrability)
 {
  copy_con->condeferrable = cmdcon->deferrable;
  copy_con->condeferred = cmdcon->initdeferred;
 }
 if (cmdcon->alterInheritability)
  copy_con->connoinherit = cmdcon->noinherit;

 CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);
 InvokeObjectPostAlterHook(ConstraintRelationId, copy_con->oid, 0);

 /* Make new constraint flags visible to others */
 CacheInvalidateRelcacheByRelid(copy_con->conrelid);

 heap_freetuple(copyTuple);
}

/*
 * ALTER TABLE VALIDATE CONSTRAINT
 *
 * XXX The reason we handle recursion here rather than at Phase 1 is because
 * there's no good way to skip recursing when handling foreign keys: there is
 * no need to lock children in that case, yet we wouldn't be able to avoid
 * doing so at that level.
 *
 * Return value is the address of the validated constraint.  If the constraint
 * was already validated, InvalidObjectAddress is returned.
 */

static ObjectAddress
ATExecValidateConstraint(List **wqueue, Relation rel, char *constrName,
       bool recurse, bool recursing, LOCKMODE lockmode)
{
 Relation conrel;
 SysScanDesc scan;
 ScanKeyData skey[3];
 HeapTuple tuple;
 Form_pg_constraint con;
 ObjectAddress address;

 conrel = table_open(ConstraintRelationId, RowExclusiveLock);

 /*
  * Find and check the target constraint
 */

 ScanKeyInit(&skey[0],
    Anum_pg_constraint_conrelid,
    BTEqualStrategyNumber, F_OIDEQ,
    ObjectIdGetDatum(RelationGetRelid(rel)));
 ScanKeyInit(&skey[1],
    Anum_pg_constraint_contypid,
    BTEqualStrategyNumber, F_OIDEQ,
    ObjectIdGetDatum(InvalidOid));
 ScanKeyInit(&skey[2],
    Anum_pg_constraint_conname,
    BTEqualStrategyNumber, F_NAMEEQ,
    CStringGetDatum(constrName));
 scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId,
         true, NULL, 3, skey);

 /* There can be at most one matching row */
 if (!HeapTupleIsValid(tuple = systable_getnext(scan)))
  ereport(ERROR,
    (errcode(ERRCODE_UNDEFINED_OBJECT),
     errmsg("constraint \"%s\" of relation \"%s\" does not exist",
      constrName, RelationGetRelationName(rel))));

 con = (Form_pg_constraint) GETSTRUCT(tuple);
 if (con->contype != CONSTRAINT_FOREIGN &&
  con->contype != CONSTRAINT_CHECK &&
  con->contype != CONSTRAINT_NOTNULL)
  ereport(ERROR,
    errcode(ERRCODE_WRONG_OBJECT_TYPE),
    errmsg("cannot validate constraint \"%s\" of relation \"%s\"",
        constrName, RelationGetRelationName(rel)),
    errdetail("This operation is not supported for this type of constraint."));

 if (!con->conenforced)
  ereport(ERROR,
    (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     errmsg("cannot validate NOT ENFORCED constraint")));

 if (!con->convalidated)
 {
  if (con->contype == CONSTRAINT_FOREIGN)
  {
   QueueFKConstraintValidation(wqueue, conrel, rel, con->confrelid,
          tuple, lockmode);
  }
  else if (con->contype == CONSTRAINT_CHECK)
  {
   QueueCheckConstraintValidation(wqueue, conrel, rel, constrName,
             tuple, recurse, recursing, lockmode);
  }
  else if (con->contype == CONSTRAINT_NOTNULL)
  {
   QueueNNConstraintValidation(wqueue, conrel, rel,
          tuple, recurse, recursing, lockmode);
  }

  ObjectAddressSet(address, ConstraintRelationId, con->oid);
 }
 else
  address = InvalidObjectAddress; /* already validated */

 systable_endscan(scan);

 table_close(conrel, RowExclusiveLock);

 return address;
}

/*
 * QueueFKConstraintValidation
 *
 * Add an entry to the wqueue to validate the given foreign key constraint in
 * Phase 3 and update the convalidated field in the pg_constraint catalog
 * for the specified relation and all its children.
 */

static void
QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation fkrel,
       Oid pkrelid, HeapTuple contuple, LOCKMODE lockmode)
{
 Form_pg_constraint con;
 AlteredTableInfo *tab;
 HeapTuple copyTuple;
 Form_pg_constraint copy_con;

 con = (Form_pg_constraint) GETSTRUCT(contuple);
 Assert(con->contype == CONSTRAINT_FOREIGN);
 Assert(!con->convalidated);

 /*
  * Add the validation to phase 3's queue; not needed for partitioned
  * tables themselves, only for their partitions.
  *
  * When the referenced table (pkrelid) is partitioned, the referencing
  * table (fkrel) has one pg_constraint row pointing to each partition
  * thereof.  These rows are there only to support action triggers and no
  * table scan is needed, therefore skip this for them as well.
 */

 if (fkrel->rd_rel->relkind == RELKIND_RELATION &&
  con->confrelid == pkrelid)
 {
  NewConstraint *newcon;
  Constraint *fkconstraint;

  /* Queue validation for phase 3 */
  fkconstraint = makeNode(Constraint);
  /* for now this is all we need */
  fkconstraint->conname = pstrdup(NameStr(con->conname));

  newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
  newcon->name = fkconstraint->conname;
  newcon->contype = CONSTR_FOREIGN;
  newcon->refrelid = con->confrelid;
  newcon->refindid = con->conindid;
  newcon->conid = con->oid;
  newcon->qual = (Node *) fkconstraint;

  /* Find or create work queue entry for this table */
  tab = ATGetQueueEntry(wqueue, fkrel);
  tab->constraints = lappend(tab->constraints, newcon);
 }

 /*
  * If the table at either end of the constraint is partitioned, we need to
  * recurse and handle every unvalidate constraint that is a child of this
  * constraint.
 */

 if (fkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
  get_rel_relkind(con->confrelid) == RELKIND_PARTITIONED_TABLE)
 {
  ScanKeyData pkey;
  SysScanDesc pscan;
  HeapTuple childtup;

  ScanKeyInit(&pkey,
     Anum_pg_constraint_conparentid,
     BTEqualStrategyNumber, F_OIDEQ,
     ObjectIdGetDatum(con->oid));

  pscan = systable_beginscan(conrel, ConstraintParentIndexId,
           true, NULL, 1, &pkey);

  while (HeapTupleIsValid(childtup = systable_getnext(pscan)))
  {
   Form_pg_constraint childcon;
   Relation childrel;

   childcon = (Form_pg_constraint) GETSTRUCT(childtup);

   /*
    * If the child constraint has already been validated, no further
    * action is required for it or its descendants, as they are all
    * valid.
 */

   if (childcon->convalidated)
    continue;

   childrel = table_open(childcon->conrelid, lockmode);

   /*
    * NB: Note that pkrelid should be passed as-is during recursion,
    * as it is required to identify the root referenced table.
 */

   QueueFKConstraintValidation(wqueue, conrel, childrel, pkrelid,
          childtup, lockmode);
   table_close(childrel, NoLock);
  }

  systable_endscan(pscan);
 }

 /*
  * Now mark the pg_constraint row as validated (even if we didn't check,
  * notably the ones for partitions on the referenced side).
  *
  * We rely on transaction abort to roll back this change if phase 3
  * ultimately finds violating rows.  This is a bit ugly.
 */

 copyTuple = heap_copytuple(contuple);
 copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
 copy_con->convalidated = true;
 CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);

 InvokeObjectPostAlterHook(ConstraintRelationId, con->oid, 0);

 heap_freetuple(copyTuple);
}

/*
 * QueueCheckConstraintValidation
 *
 * Add an entry to the wqueue to validate the given check constraint in Phase 3
 * and update the convalidated field in the pg_constraint catalog for the
 * specified relation and all its inheriting children.
 */

static void
QueueCheckConstraintValidation(List **wqueue, Relation conrel, Relation rel,
          char *constrName, HeapTuple contuple,
          bool recurse, bool recursing, LOCKMODE lockmode)
{
 Form_pg_constraint con;
 AlteredTableInfo *tab;
 HeapTuple copyTuple;
 Form_pg_constraint copy_con;

 List    *children = NIL;
 ListCell   *child;
 NewConstraint *newcon;
 Datum  val;
 char    *conbin;

 con = (Form_pg_constraint) GETSTRUCT(contuple);
 Assert(con->contype == CONSTRAINT_CHECK);

 /*
  * If we're recursing, the parent has already done this, so skip it. Also,
  * if the constraint is a NO INHERIT constraint, we shouldn't try to look
  * for it in the children.
 */

 if (!recursing && !con->connoinherit)
  children = find_all_inheritors(RelationGetRelid(rel),
            lockmode, NULL);

 /*
  * For CHECK constraints, we must ensure that we only mark the constraint
  * as validated on the parent if it's already validated on the children.
  *
  * We recurse before validating on the parent, to reduce risk of
  * deadlocks.
 */

 foreach(child, children)
 {
  Oid   childoid = lfirst_oid(child);
  Relation childrel;

  if (childoid == RelationGetRelid(rel))
   continue;

  /*
   * If we are told not to recurse, there had better not be any child
   * tables, because we can't mark the constraint on the parent valid
   * unless it is valid for all child tables.
 */

  if (!recurse)
   ereport(ERROR,
     (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
      errmsg("constraint must be validated on child tables too")));

  /* find_all_inheritors already got lock */
  childrel = table_open(childoid, NoLock);

  ATExecValidateConstraint(wqueue, childrel, constrName, false,
         true, lockmode);
  table_close(childrel, NoLock);
 }

 /* Queue validation for phase 3 */
 newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
 newcon->name = constrName;
 newcon->contype = CONSTR_CHECK;
 newcon->refrelid = InvalidOid;
 newcon->refindid = InvalidOid;
 newcon->conid = con->oid;

 val = SysCacheGetAttrNotNull(CONSTROID, contuple,
         Anum_pg_constraint_conbin);
 conbin = TextDatumGetCString(val);
 newcon->qual = expand_generated_columns_in_expr(stringToNode(conbin), rel, 1);

 /* Find or create work queue entry for this table */
 tab = ATGetQueueEntry(wqueue, rel);
 tab->constraints = lappend(tab->constraints, newcon);

 /*
  * Invalidate relcache so that others see the new validated constraint.
 */

 CacheInvalidateRelcache(rel);

 /*
  * Now update the catalog, while we have the door open.
 */

 copyTuple = heap_copytuple(contuple);
 copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
 copy_con->convalidated = true;
 CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);

 InvokeObjectPostAlterHook(ConstraintRelationId, con->oid, 0);

 heap_freetuple(copyTuple);
}

/*
 * QueueNNConstraintValidation
 *
 * Add an entry to the wqueue to validate the given not-null constraint in
 * Phase 3 and update the convalidated field in the pg_constraint catalog for
 * the specified relation and all its inheriting children.
 */

static void
QueueNNConstraintValidation(List **wqueue, Relation conrel, Relation rel,
       HeapTuple contuple, bool recurse, bool recursing,
       LOCKMODE lockmode)
{
 Form_pg_constraint con;
 AlteredTableInfo *tab;
 HeapTuple copyTuple;
 Form_pg_constraint copy_con;
 List    *children = NIL;
 AttrNumber attnum;
 char    *colname;

 con = (Form_pg_constraint) GETSTRUCT(contuple);
 Assert(con->contype == CONSTRAINT_NOTNULL);

 attnum = extractNotNullColumn(contuple);

 /*
  * If we're recursing, we've already done this for parent, so skip it.
  * Also, if the constraint is a NO INHERIT constraint, we shouldn't try to
  * look for it in the children.
  *
  * We recurse before validating on the parent, to reduce risk of
  * deadlocks.
 */

 if (!recursing && !con->connoinherit)
  children = find_all_inheritors(RelationGetRelid(rel), lockmode, NULL);

 colname = get_attname(RelationGetRelid(rel), attnum, false);
 foreach_oid(childoid, children)
 {
  Relation childrel;
  HeapTuple contup;
  Form_pg_constraint childcon;
  char    *conname;

  if (childoid == RelationGetRelid(rel))
   continue;

  /*
   * If we are told not to recurse, there had better not be any child
   * tables, because we can't mark the constraint on the parent valid
   * unless it is valid for all child tables.
 */

  if (!recurse)
   ereport(ERROR,
     errcode(ERRCODE_INVALID_TABLE_DEFINITION),
     errmsg("constraint must be validated on child tables too"));

  /*
   * The column on child might have a different attnum, so search by
   * column name.
 */

  contup = findNotNullConstraint(childoid, colname);
  if (!contup)
   elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation \"%s\"",
     colname, get_rel_name(childoid));
  childcon = (Form_pg_constraint) GETSTRUCT(contup);
  if (childcon->convalidated)
   continue;

  /* find_all_inheritors already got lock */
  childrel = table_open(childoid, NoLock);
  conname = pstrdup(NameStr(childcon->conname));

  /* XXX improve ATExecValidateConstraint API to avoid double search */
  ATExecValidateConstraint(wqueue, childrel, conname,
         falsetrue, lockmode);
  table_close(childrel, NoLock);
 }

 /* Set attnotnull appropriately without queueing another validation */
 set_attnotnull(NULL, rel, attnum, truefalse);

 tab = ATGetQueueEntry(wqueue, rel);
 tab->verify_new_notnull = true;

 /*
  * Invalidate relcache so that others see the new validated constraint.
 */

 CacheInvalidateRelcache(rel);

 /*
  * Now update the catalogs, while we have the door open.
 */

 copyTuple = heap_copytuple(contuple);
 copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
 copy_con->convalidated = true;
 CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);

 InvokeObjectPostAlterHook(ConstraintRelationId, con->oid, 0);

 heap_freetuple(copyTuple);
}

/*
 * transformColumnNameList - transform list of column names
 *
 * Lookup each name and return its attnum and, optionally, type and collation
 * OIDs
 *
 * Note: the name of this function suggests that it's general-purpose,
 * but actually it's only used to look up names appearing in foreign-key
 * clauses.  The error messages would need work to use it in other cases,
 * and perhaps the validity checks as well.
 */

static int
transformColumnNameList(Oid relId, List *colList,
      int16 *attnums, Oid *atttypids, Oid *attcollids)
{
 ListCell   *l;
 int   attnum;

 attnum = 0;
 foreach(l, colList)
 {
  char    *attname = strVal(lfirst(l));
  HeapTuple atttuple;
  Form_pg_attribute attform;

  atttuple = SearchSysCacheAttName(relId, attname);
  if (!HeapTupleIsValid(atttuple))
   ereport(ERROR,
     (errcode(ERRCODE_UNDEFINED_COLUMN),
      errmsg("column \"%s\" referenced in foreign key constraint does not exist",
       attname)));
  attform = (Form_pg_attribute) GETSTRUCT(atttuple);
  if (attform->attnum < 0)
   ereport(ERROR,
     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
      errmsg("system columns cannot be used in foreign keys")));
  if (attnum >= INDEX_MAX_KEYS)
   ereport(ERROR,
     (errcode(ERRCODE_TOO_MANY_COLUMNS),
      errmsg("cannot have more than %d keys in a foreign key",
       INDEX_MAX_KEYS)));
  attnums[attnum] = attform->attnum;
  if (atttypids != NULL)
   atttypids[attnum] = attform->atttypid;
  if (attcollids != NULL)
   attcollids[attnum] = attform->attcollation;
  ReleaseSysCache(atttuple);
  attnum++;
 }

 return attnum;
}

/*
 * transformFkeyGetPrimaryKey -
 *
 * Look up the names, attnums, types, and collations of the primary key attributes
 * for the pkrel.  Also return the index OID and index opclasses of the
 * index supporting the primary key.  Also return whether the index has
 * WITHOUT OVERLAPS.
 *
 * All parameters except pkrel are output parameters.  Also, the function
 * return value is the number of attributes in the primary key.
 *
 * Used when the column list in the REFERENCES specification is omitted.
 */

static int
transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
         List **attnamelist,
         int16 *attnums, Oid *atttypids, Oid *attcollids,
         Oid *opclasses, bool *pk_has_without_overlaps)
{
 List    *indexoidlist;
 ListCell   *indexoidscan;
 HeapTuple indexTuple = NULL;
 Form_pg_index indexStruct = NULL;
 Datum  indclassDatum;
 oidvector  *indclass;
 int   i;

 /*
  * Get the list of index OIDs for the table from the relcache, and look up
  * each one in the pg_index syscache until we find one marked primary key
  * (hopefully there isn't more than one such).  Insist it's valid, too.
 */

 *indexOid = InvalidOid;

 indexoidlist = RelationGetIndexList(pkrel);

 foreach(indexoidscan, indexoidlist)
 {
  Oid   indexoid = lfirst_oid(indexoidscan);

  indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
  if (!HeapTupleIsValid(indexTuple))
   elog(ERROR, "cache lookup failed for index %u", indexoid);
  indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
  if (indexStruct->indisprimary && indexStruct->indisvalid)
  {
   /*
    * Refuse to use a deferrable primary key.  This is per SQL spec,
    * and there would be a lot of interesting semantic problems if we
    * tried to allow it.
 */

   if (!indexStruct->indimmediate)
    ereport(ERROR,
      (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
       errmsg("cannot use a deferrable primary key for referenced table \"%s\"",
        RelationGetRelationName(pkrel))));

   *indexOid = indexoid;
   break;
  }
  ReleaseSysCache(indexTuple);
 }

 list_free(indexoidlist);

 /*
  * Check that we found it
 */

 if (!OidIsValid(*indexOid))
  ereport(ERROR,
    (errcode(ERRCODE_UNDEFINED_OBJECT),
     errmsg("there is no primary key for referenced table \"%s\"",
      RelationGetRelationName(pkrel))));

 /* Must get indclass the hard way */
 indclassDatum = SysCacheGetAttrNotNull(INDEXRELID, indexTuple,
             Anum_pg_index_indclass);
 indclass = (oidvector *) DatumGetPointer(indclassDatum);

 /*
  * Now build the list of PK attributes from the indkey definition (we
  * assume a primary key cannot have expressional elements)
 */

 *attnamelist = NIL;
 for (i = 0; i < indexStruct->indnkeyatts; i++)
 {
  int   pkattno = indexStruct->indkey.values[i];

  attnums[i] = pkattno;
  atttypids[i] = attnumTypeId(pkrel, pkattno);
  attcollids[i] = attnumCollationId(pkrel, pkattno);
  opclasses[i] = indclass->values[i];
  *attnamelist = lappend(*attnamelist,
          makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno)))));
 }

 *pk_has_without_overlaps = indexStruct->indisexclusion;

 ReleaseSysCache(indexTuple);

 return i;
}

/*
 * transformFkeyCheckAttrs -
 *
 * Validate that the 'attnums' columns in the 'pkrel' relation are valid to
 * reference as part of a foreign key constraint.
 *
 * Returns the OID of the unique index supporting the constraint and
 * populates the caller-provided 'opclasses' array with the opclasses
 * associated with the index columns.  Also sets whether the index
 * uses WITHOUT OVERLAPS.
 *
 * Raises an ERROR on validation failure.
 */

static Oid
transformFkeyCheckAttrs(Relation pkrel,
      int numattrs, int16 *attnums,
      bool with_period, Oid *opclasses,
      bool *pk_has_without_overlaps)
{
 Oid   indexoid = InvalidOid;
 bool  found = false;
 bool  found_deferrable = false;
 List    *indexoidlist;
 ListCell   *indexoidscan;
 int   i,
    j;

 /*
  * Reject duplicate appearances of columns in the referenced-columns list.
  * Such a case is forbidden by the SQL standard, and even if we thought it
  * useful to allow it, there would be ambiguity about how to match the
  * list to unique indexes (in particular, it'd be unclear which index
  * opclass goes with which FK column).
 */

 for (i = 0; i < numattrs; i++)
 {
  for (j = i + 1; j < numattrs; j++)
  {
   if (attnums[i] == attnums[j])
    ereport(ERROR,
      (errcode(ERRCODE_INVALID_FOREIGN_KEY),
       errmsg("foreign key referenced-columns list must not contain duplicates")));
  }
 }

 /*
  * Get the list of index OIDs for the table from the relcache, and look up
  * each one in the pg_index syscache, and match unique indexes to the list
  * of attnums we are given.
 */

 indexoidlist = RelationGetIndexList(pkrel);

 foreach(indexoidscan, indexoidlist)
 {
  HeapTuple indexTuple;
  Form_pg_index indexStruct;

  indexoid = lfirst_oid(indexoidscan);
  indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
  if (!HeapTupleIsValid(indexTuple))
   elog(ERROR, "cache lookup failed for index %u", indexoid);
  indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);

  /*
   * Must have the right number of columns; must be unique (or if
   * temporal then exclusion instead) and not a partial index; forget it
   * if there are any expressions, too. Invalid indexes are out as well.
 */

  if (indexStruct->indnkeyatts == numattrs &&
   (with_period ? indexStruct->indisexclusion : indexStruct->indisunique) &&
   indexStruct->indisvalid &&
   heap_attisnull(indexTuple, Anum_pg_index_indpred, NULL) &&
   heap_attisnull(indexTuple, Anum_pg_index_indexprs, NULL))
  {
   Datum  indclassDatum;
   oidvector  *indclass;

   /* Must get indclass the hard way */
   indclassDatum = SysCacheGetAttrNotNull(INDEXRELID, indexTuple,
               Anum_pg_index_indclass);
   indclass = (oidvector *) DatumGetPointer(indclassDatum);

   /*
    * The given attnum list may match the index columns in any order.
    * Check for a match, and extract the appropriate opclasses while
    * we're at it.
    *
    * We know that attnums[] is duplicate-free per the test at the
    * start of this function, and we checked above that the number of
    * index columns agrees, so if we find a match for each attnums[]
    * entry then we must have a one-to-one match in some order.
 */

   for (i = 0; i < numattrs; i++)
   {
    found = false;
    for (j = 0; j < numattrs; j++)
    {
     if (attnums[i] == indexStruct->indkey.values[j])
     {
      opclasses[i] = indclass->values[j];
      found = true;
      break;
     }
    }
    if (!found)
     break;
   }
   /* The last attribute in the index must be the PERIOD FK part */
   if (found && with_period)
   {
    int16  periodattnum = attnums[numattrs - 1];

    found = (periodattnum == indexStruct->indkey.values[numattrs - 1]);
   }

   /*
    * Refuse to use a deferrable unique/primary key.  This is per SQL
    * spec, and there would be a lot of interesting semantic problems
    * if we tried to allow it.
 */

   if (found && !indexStruct->indimmediate)
   {
    /*
     * Remember that we found an otherwise matching index, so that
     * we can generate a more appropriate error message.
 */

    found_deferrable = true;
    found = false;
   }

   /* We need to know whether the index has WITHOUT OVERLAPS */
   if (found)
    *pk_has_without_overlaps = indexStruct->indisexclusion;
  }
  ReleaseSysCache(indexTuple);
  if (found)
   break;
 }

 if (!found)
 {
  if (found_deferrable)
   ereport(ERROR,
     (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
      errmsg("cannot use a deferrable unique constraint for referenced table \"%s\"",
       RelationGetRelationName(pkrel))));
  else
   ereport(ERROR,
     (errcode(ERRCODE_INVALID_FOREIGN_KEY),
      errmsg("there is no unique constraint matching given keys for referenced table \"%s\"",
       RelationGetRelationName(pkrel))));
 }

 list_free(indexoidlist);

 return indexoid;
}

/*
 * findFkeyCast -
 *
 * Wrapper around find_coercion_pathway() for ATAddForeignKeyConstraint().
 * Caller has equal regard for binary coercibility and for an exact match.
*/

static CoercionPathType
findFkeyCast(Oid targetTypeId, Oid sourceTypeId, Oid *funcid)
{
 CoercionPathType ret;

 if (targetTypeId == sourceTypeId)
 {
  ret = COERCION_PATH_RELABELTYPE;
  *funcid = InvalidOid;
 }
 else
 {
  ret = find_coercion_pathway(targetTypeId, sourceTypeId,
         COERCION_IMPLICIT, funcid);
  if (ret == COERCION_PATH_NONE)
   /* A previously-relied-upon cast is now gone. */
   elog(ERROR, "could not find cast from %u to %u",
     sourceTypeId, targetTypeId);
 }

 return ret;
}

/*
 * Permissions checks on the referenced table for ADD FOREIGN KEY
 *
 * Note: we have already checked that the user owns the referencing table,
 * else we'd have failed much earlier; no additional checks are needed for it.
 */

static void
checkFkeyPermissions(Relation rel, int16 *attnums, int natts)
{
 Oid   roleid = GetUserId();
 AclResult aclresult;
 int   i;

 /* Okay if we have relation-level REFERENCES permission */
 aclresult = pg_class_aclcheck(RelationGetRelid(rel), roleid,
          ACL_REFERENCES);
 if (aclresult == ACLCHECK_OK)
  return;
 /* Else we must have REFERENCES on each column */
 for (i = 0; i < natts; i++)
 {
  aclresult = pg_attribute_aclcheck(RelationGetRelid(rel), attnums[i],
            roleid, ACL_REFERENCES);
  if (aclresult != ACLCHECK_OK)
   aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
         RelationGetRelationName(rel));
 }
}

/*
 * Scan the existing rows in a table to verify they meet a proposed FK
 * constraint.
 *
 * Caller must have opened and locked both relations appropriately.
 */

static void
validateForeignKeyConstraint(char *conname,
        Relation rel,
        Relation pkrel,
        Oid pkindOid,
        Oid constraintOid,
        bool hasperiod)
{
 TupleTableSlot *slot;
 TableScanDesc scan;
 Trigger  trig = {0};
 Snapshot snapshot;
 MemoryContext oldcxt;
 MemoryContext perTupCxt;

 ereport(DEBUG1,
   (errmsg_internal("validating foreign key constraint \"%s\"", conname)));

 /*
  * Build a trigger call structure; we'll need it either way.
 */

 trig.tgoid = InvalidOid;
 trig.tgname = conname;
 trig.tgenabled = TRIGGER_FIRES_ON_ORIGIN;
 trig.tgisinternal = true;
 trig.tgconstrrelid = RelationGetRelid(pkrel);
 trig.tgconstrindid = pkindOid;
 trig.tgconstraint = constraintOid;
 trig.tgdeferrable = false;
 trig.tginitdeferred = false;
 /* we needn't fill in remaining fields */

 /*
  * See if we can do it with a single LEFT JOIN query.  A false result
  * indicates we must proceed with the fire-the-trigger method. We can't do
  * a LEFT JOIN for temporal FKs yet, but we can once we support temporal
  * left joins.
 */

 if (!hasperiod && RI_Initial_Check(&trig, rel, pkrel))
  return;

 /*
  * Scan through each tuple, calling RI_FKey_check_ins (insert trigger) as
  * if that tuple had just been inserted.  If any of those fail, it should
  * ereport(ERROR) and that's that.
 */

 snapshot = RegisterSnapshot(GetLatestSnapshot());
 slot = table_slot_create(rel, NULL);
 scan = table_beginscan(rel, snapshot, 0, NULL);

 perTupCxt = AllocSetContextCreate(CurrentMemoryContext,
           "validateForeignKeyConstraint",
           ALLOCSET_SMALL_SIZES);
 oldcxt = MemoryContextSwitchTo(perTupCxt);

 while (table_scan_getnextslot(scan, ForwardScanDirection, slot))
 {
  LOCAL_FCINFO(fcinfo, 0);
  TriggerData trigdata = {0};

  CHECK_FOR_INTERRUPTS();

  /*
   * Make a call to the trigger function
   *
   * No parameters are passed, but we do set a context
 */

  MemSet(fcinfo, 0, SizeForFunctionCallInfo(0));

  /*
   * We assume RI_FKey_check_ins won't look at flinfo...
 */

  trigdata.type = T_TriggerData;
  trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
  trigdata.tg_relation = rel;
  trigdata.tg_trigtuple = ExecFetchSlotHeapTuple(slot, false, NULL);
  trigdata.tg_trigslot = slot;
  trigdata.tg_trigger = &trig;

  fcinfo->context = (Node *) &trigdata;

  RI_FKey_check_ins(fcinfo);

  MemoryContextReset(perTupCxt);
 }

 MemoryContextSwitchTo(oldcxt);
 MemoryContextDelete(perTupCxt);
 table_endscan(scan);
 UnregisterSnapshot(snapshot);
 ExecDropSingleTupleTableSlot(slot);
}

/*
 * CreateFKCheckTrigger
 *  Creates the insert (on_insert=true) or update "check" trigger that
 *  implements a given foreign key
 *
 * Returns the OID of the so created trigger.
 */

static Oid
CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
      Oid constraintOid, Oid indexOid, Oid parentTrigOid,
      bool on_insert)
{
 ObjectAddress trigAddress;
 CreateTrigStmt *fk_trigger;

 /*
  * Note: for a self-referential FK (referencing and referenced tables are
  * the same), it is important that the ON UPDATE action fires before the
  * CHECK action, since both triggers will fire on the same row during an
  * UPDATE event; otherwise the CHECK trigger will be checking a non-final
  * state of the row.  Triggers fire in name order, so we ensure this by
  * using names like "RI_ConstraintTrigger_a_NNNN" for the action triggers
  * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
 */

 fk_trigger = makeNode(CreateTrigStmt);
 fk_trigger->replace = false;
 fk_trigger->isconstraint = true;
 fk_trigger->trigname = "RI_ConstraintTrigger_c";
 fk_trigger->relation = NULL;

 /* Either ON INSERT or ON UPDATE */
 if (on_insert)
 {
  fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
  fk_trigger->events = TRIGGER_TYPE_INSERT;
 }
 else
 {
  fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
  fk_trigger->events = TRIGGER_TYPE_UPDATE;
 }

 fk_trigger->args = NIL;
 fk_trigger->row = true;
 fk_trigger->timing = TRIGGER_TYPE_AFTER;
 fk_trigger->columns = NIL;
 fk_trigger->whenClause = NULL;
 fk_trigger->transitionRels = NIL;
 fk_trigger->deferrable = fkconstraint->deferrable;
 fk_trigger->initdeferred = fkconstraint->initdeferred;
 fk_trigger->constrrel = NULL;

 trigAddress = CreateTrigger(fk_trigger, NULL, myRelOid, refRelOid,
        constraintOid, indexOid, InvalidOid,
        parentTrigOid, NULL, truefalse);

 /* Make changes-so-far visible */
 CommandCounterIncrement();

 return trigAddress.objectId;
}

/*
 * createForeignKeyActionTriggers
 *  Create the referenced-side "action" triggers that implement a foreign
 *  key.
 *
 * Returns the OIDs of the so created triggers in *deleteTrigOid and
 * *updateTrigOid.
 */

static void
createForeignKeyActionTriggers(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
          Oid constraintOid, Oid indexOid,
          Oid parentDelTrigger, Oid parentUpdTrigger,
          Oid *deleteTrigOid, Oid *updateTrigOid)
{
 CreateTrigStmt *fk_trigger;
 ObjectAddress trigAddress;

 /*
  * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
  * DELETE action on the referenced table.
 */

 fk_trigger = makeNode(CreateTrigStmt);
 fk_trigger->replace = false;
 fk_trigger->isconstraint = true;
 fk_trigger->trigname = "RI_ConstraintTrigger_a";
 fk_trigger->relation = NULL;
 fk_trigger->args = NIL;
 fk_trigger->row = true;
 fk_trigger->timing = TRIGGER_TYPE_AFTER;
 fk_trigger->events = TRIGGER_TYPE_DELETE;
 fk_trigger->columns = NIL;
 fk_trigger->whenClause = NULL;
 fk_trigger->transitionRels = NIL;
 fk_trigger->constrrel = NULL;

 switch (fkconstraint->fk_del_action)
 {
  case FKCONSTR_ACTION_NOACTION:
   fk_trigger->deferrable = fkconstraint->deferrable;
   fk_trigger->initdeferred = fkconstraint->initdeferred;
   fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
   break;
  case FKCONSTR_ACTION_RESTRICT:
   fk_trigger->deferrable = false;
   fk_trigger->initdeferred = false;
   fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
   break;
  case FKCONSTR_ACTION_CASCADE:
   fk_trigger->deferrable = false;
   fk_trigger->initdeferred = false;
   fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
   break;
  case FKCONSTR_ACTION_SETNULL:
   fk_trigger->deferrable = false;
   fk_trigger->initdeferred = false;
   fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
   break;
  case FKCONSTR_ACTION_SETDEFAULT:
   fk_trigger->deferrable = false;
   fk_trigger->initdeferred = false;
   fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
   break;
  default:
   elog(ERROR, "unrecognized FK action type: %d",
     (int) fkconstraint->fk_del_action);
   break;
 }

 trigAddress = CreateTrigger(fk_trigger, NULL, refRelOid, myRelOid,
        constraintOid, indexOid, InvalidOid,
        parentDelTrigger, NULL, truefalse);
 if (deleteTrigOid)
  *deleteTrigOid = trigAddress.objectId;

 /* Make changes-so-far visible */
 CommandCounterIncrement();

 /*
  * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
  * UPDATE action on the referenced table.
 */

 fk_trigger = makeNode(CreateTrigStmt);
 fk_trigger->replace = false;
 fk_trigger->isconstraint = true;
 fk_trigger->trigname = "RI_ConstraintTrigger_a";
 fk_trigger->relation = NULL;
 fk_trigger->args = NIL;
 fk_trigger->row = true;
 fk_trigger->timing = TRIGGER_TYPE_AFTER;
 fk_trigger->events = TRIGGER_TYPE_UPDATE;
 fk_trigger->columns = NIL;
 fk_trigger->whenClause = NULL;
 fk_trigger->transitionRels = NIL;
 fk_trigger->constrrel = NULL;

 switch (fkconstraint->fk_upd_action)
 {
  case FKCONSTR_ACTION_NOACTION:
   fk_trigger->deferrable = fkconstraint->deferrable;
   fk_trigger->initdeferred = fkconstraint->initdeferred;
   fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
   break;
  case FKCONSTR_ACTION_RESTRICT:
   fk_trigger->deferrable = false;
   fk_trigger->initdeferred = false;
   fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
   break;
  case FKCONSTR_ACTION_CASCADE:
   fk_trigger->deferrable = false;
   fk_trigger->initdeferred = false;
   fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
   break;
  case FKCONSTR_ACTION_SETNULL:
   fk_trigger->deferrable = false;
   fk_trigger->initdeferred = false;
   fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
   break;
  case FKCONSTR_ACTION_SETDEFAULT:
   fk_trigger->deferrable = false;
   fk_trigger->initdeferred = false;
   fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
   break;
  default:
   elog(ERROR, "unrecognized FK action type: %d",
     (int) fkconstraint->fk_upd_action);
   break;
 }

 trigAddress = CreateTrigger(fk_trigger, NULL, refRelOid, myRelOid,
        constraintOid, indexOid, InvalidOid,
        parentUpdTrigger, NULL, truefalse);
 if (updateTrigOid)
  *updateTrigOid = trigAddress.objectId;
}

/*
 * createForeignKeyCheckTriggers
 *  Create the referencing-side "check" triggers that implement a foreign
 *  key.
 *
 * Returns the OIDs of the so created triggers in *insertTrigOid and
 * *updateTrigOid.
 */

static void
createForeignKeyCheckTriggers(Oid myRelOid, Oid refRelOid,
         Constraint *fkconstraint, Oid constraintOid,
         Oid indexOid,
         Oid parentInsTrigger, Oid parentUpdTrigger,
         Oid *insertTrigOid, Oid *updateTrigOid)
{
 *insertTrigOid = CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint,
            constraintOid, indexOid,
            parentInsTrigger, true);
 *updateTrigOid = CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint,
            constraintOid, indexOid,
            parentUpdTrigger, false);
}

/*
 * ALTER TABLE DROP CONSTRAINT
 *
 * Like DROP COLUMN, we can't use the normal ALTER TABLE recursion mechanism.
 */

static void
ATExecDropConstraint(Relation rel, const char *constrName,
      DropBehavior behavior, bool recurse,
      bool missing_ok, LOCKMODE lockmode)
{
 Relation conrel;
 SysScanDesc scan;
 ScanKeyData skey[3];
 HeapTuple tuple;
 bool  found = false;

 conrel = table_open(ConstraintRelationId, RowExclusiveLock);

 /*
  * Find and drop the target constraint
 */

 ScanKeyInit(&skey[0],
    Anum_pg_constraint_conrelid,
    BTEqualStrategyNumber, F_OIDEQ,
    ObjectIdGetDatum(RelationGetRelid(rel)));
 ScanKeyInit(&skey[1],
    Anum_pg_constraint_contypid,
    BTEqualStrategyNumber, F_OIDEQ,
    ObjectIdGetDatum(InvalidOid));
 ScanKeyInit(&skey[2],
    Anum_pg_constraint_conname,
    BTEqualStrategyNumber, F_NAMEEQ,
    CStringGetDatum(constrName));
 scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId,
         true, NULL, 3, skey);

 /* There can be at most one matching row */
 if (HeapTupleIsValid(tuple = systable_getnext(scan)))
 {
  dropconstraint_internal(rel, tuple, behavior, recurse, false,
        missing_ok, lockmode);
  found = true;
 }

 systable_endscan(scan);

 if (!found)
 {
  if (!missing_ok)
   ereport(ERROR,
     errcode(ERRCODE_UNDEFINED_OBJECT),
     errmsg("constraint \"%s\" of relation \"%s\" does not exist",
         constrName, RelationGetRelationName(rel)));
  else
   ereport(NOTICE,
     errmsg("constraint \"%s\" of relation \"%s\" does not exist, skipping",
         constrName, RelationGetRelationName(rel)));
 }

 table_close(conrel, RowExclusiveLock);
}

/*
 * Remove a constraint, using its pg_constraint tuple
 *
 * Implementation for ALTER TABLE DROP CONSTRAINT and ALTER TABLE ALTER COLUMN
 * DROP NOT NULL.
 *
 * Returns the address of the constraint being removed.
 */

static ObjectAddress
dropconstraint_internal(Relation rel, HeapTuple constraintTup, DropBehavior behavior,
      bool recurse, bool recursing, bool missing_ok,
      LOCKMODE lockmode)
{
 Relation conrel;
 Form_pg_constraint con;
 ObjectAddress conobj;
 List    *children;
 bool  is_no_inherit_constraint = false;
 char    *constrName;
 char    *colname = NULL;

 /* Guard against stack overflow due to overly deep inheritance tree. */
 check_stack_depth();

 /* At top level, permission check was done in ATPrepCmd, else do it */
 if (recursing)
  ATSimplePermissions(AT_DropConstraint, rel,
       ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);

 conrel = table_open(ConstraintRelationId, RowExclusiveLock);

 con = (Form_pg_constraint) GETSTRUCT(constraintTup);
 constrName = NameStr(con->conname);

 /* Don't allow drop of inherited constraints */
 if (con->coninhcount > 0 && !recursing)
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
     errmsg("cannot drop inherited constraint \"%s\" of relation \"%s\"",
      constrName, RelationGetRelationName(rel))));

 /*
  * Reset pg_constraint.attnotnull, if this is a not-null constraint.
  *
  * While doing that, we're in a good position to disallow dropping a not-
  * null constraint underneath a primary key, a replica identity index, or
  * a generated identity column.
 */

 if (con->contype == CONSTRAINT_NOTNULL)
 {
  Relation attrel = table_open(AttributeRelationId, RowExclusiveLock);
  AttrNumber attnum = extractNotNullColumn(constraintTup);
  Bitmapset  *pkattrs;
  Bitmapset  *irattrs;
  HeapTuple atttup;
  Form_pg_attribute attForm;

  /* save column name for recursion step */
  colname = get_attname(RelationGetRelid(rel), attnum, false);

  /*
   * Disallow if it's in the primary key.  For partitioned tables we
   * cannot rely solely on RelationGetIndexAttrBitmap, because it'll
   * return NULL if the primary key is invalid; but we still need to
   * protect not-null constraints under such a constraint, so check the
   * slow way.
 */

  pkattrs = RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_PRIMARY_KEY);

  if (pkattrs == NULL &&
   rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
  {
   Oid   pkindex = RelationGetPrimaryKeyIndex(rel, true);

   if (OidIsValid(pkindex))
   {
    Relation pk = relation_open(pkindex, AccessShareLock);

    pkattrs = NULL;
    for (int i = 0; i < pk->rd_index->indnkeyatts; i++)
     pkattrs = bms_add_member(pkattrs, pk->rd_index->indkey.values[i]);

    relation_close(pk, AccessShareLock);
   }
  }

  if (pkattrs &&
   bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber, pkattrs))
   ereport(ERROR,
     errcode(ERRCODE_INVALID_TABLE_DEFINITION),
     errmsg("column \"%s\" is in a primary key",
         get_attname(RelationGetRelid(rel), attnum, false)));

  /* Disallow if it's in the replica identity */
  irattrs = RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_IDENTITY_KEY);
  if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber, irattrs))
   ereport(ERROR,
     errcode(ERRCODE_INVALID_TABLE_DEFINITION),
     errmsg("column \"%s\" is in index used as replica identity",
         get_attname(RelationGetRelid(rel), attnum, false)));

  /* Disallow if it's a GENERATED AS IDENTITY column */
  atttup = SearchSysCacheCopyAttNum(RelationGetRelid(rel), attnum);
  if (!HeapTupleIsValid(atttup))
   elog(ERROR, "cache lookup failed for attribute %d of relation %u",
     attnum, RelationGetRelid(rel));
  attForm = (Form_pg_attribute) GETSTRUCT(atttup);
  if (attForm->attidentity != '\0')
   ereport(ERROR,
     errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     errmsg("column \"%s\" of relation \"%s\" is an identity column",
         get_attname(RelationGetRelid(rel), attnum,
            false),
         RelationGetRelationName(rel)));

  /* All good -- reset attnotnull if needed */
  if (attForm->attnotnull)
  {
   attForm->attnotnull = false;
   CatalogTupleUpdate(attrel, &atttup->t_self, atttup);
  }

  table_close(attrel, RowExclusiveLock);
 }

 is_no_inherit_constraint = con->connoinherit;

 /*
  * If it's a foreign-key constraint, we'd better lock the referenced table
  * and check that that's not in use, just as we've already done for the
  * constrained table (else we might, eg, be dropping a trigger that has
  * unfired events).  But we can/must skip that in the self-referential
  * case.
 */

 if (con->contype == CONSTRAINT_FOREIGN &&
  con->confrelid != RelationGetRelid(rel))
 {
  Relation frel;

  /* Must match lock taken by RemoveTriggerById: */
  frel = table_open(con->confrelid, AccessExclusiveLock);
  CheckAlterTableIsSafe(frel);
  table_close(frel, NoLock);
 }

 /*
  * Perform the actual constraint deletion
 */

 ObjectAddressSet(conobj, ConstraintRelationId, con->oid);
 performDeletion(&conobj, behavior, 0);

 /*
  * For partitioned tables, non-CHECK, non-NOT-NULL inherited constraints
  * are dropped via the dependency mechanism, so we're done here.
 */

 if (con->contype != CONSTRAINT_CHECK &&
  con->contype != CONSTRAINT_NOTNULL &&
  rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 {
  table_close(conrel, RowExclusiveLock);
  return conobj;
 }

 /*
  * Propagate to children as appropriate.  Unlike most other ALTER
  * routines, we have to do this one level of recursion at a time; we can't
  * use find_all_inheritors to do it in one pass.
 */

 if (!is_no_inherit_constraint)
  children = find_inheritance_children(RelationGetRelid(rel), lockmode);
 else
  children = NIL;

 foreach_oid(childrelid, children)
 {
  Relation childrel;
  HeapTuple tuple;
  Form_pg_constraint childcon;

  /* find_inheritance_children already got lock */
  childrel = table_open(childrelid, NoLock);
  CheckAlterTableIsSafe(childrel);

  /*
   * We search for not-null constraints by column name, and others by
   * constraint name.
 */

  if (con->contype == CONSTRAINT_NOTNULL)
  {
   tuple = findNotNullConstraint(childrelid, colname);
   if (!HeapTupleIsValid(tuple))
    elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation %u",
      colname, RelationGetRelid(childrel));
  }
  else
  {
   SysScanDesc scan;
   ScanKeyData skey[3];

   ScanKeyInit(&skey[0],
      Anum_pg_constraint_conrelid,
      BTEqualStrategyNumber, F_OIDEQ,
      ObjectIdGetDatum(childrelid));
   ScanKeyInit(&skey[1],
      Anum_pg_constraint_contypid,
      BTEqualStrategyNumber, F_OIDEQ,
      ObjectIdGetDatum(InvalidOid));
   ScanKeyInit(&skey[2],
      Anum_pg_constraint_conname,
      BTEqualStrategyNumber, F_NAMEEQ,
      CStringGetDatum(constrName));
   scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId,
           true, NULL, 3, skey);
   /* There can only be one, so no need to loop */
   tuple = systable_getnext(scan);
   if (!HeapTupleIsValid(tuple))
    ereport(ERROR,
      (errcode(ERRCODE_UNDEFINED_OBJECT),
       errmsg("constraint \"%s\" of relation \"%s\" does not exist",
        constrName,
        RelationGetRelationName(childrel))));
   tuple = heap_copytuple(tuple);
   systable_endscan(scan);
  }

  childcon = (Form_pg_constraint) GETSTRUCT(tuple);

  /* Right now only CHECK and not-null constraints can be inherited */
  if (childcon->contype != CONSTRAINT_CHECK &&
   childcon->contype != CONSTRAINT_NOTNULL)
   elog(ERROR, "inherited constraint is not a CHECK or not-null constraint");

  if (childcon->coninhcount <= 0/* shouldn't happen */
   elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
     childrelid, NameStr(childcon->conname));

  if (recurse)
  {
   /*
    * If the child constraint has other definition sources, just
    * decrement its inheritance count; if not, recurse to delete it.
 */

   if (childcon->coninhcount == 1 && !childcon->conislocal)
   {
    /* Time to delete this child constraint, too */
    dropconstraint_internal(childrel, tuple, behavior,
          recurse, true, missing_ok,
          lockmode);
   }
   else
   {
    /* Child constraint must survive my deletion */
    childcon->coninhcount--;
    CatalogTupleUpdate(conrel, &tuple->t_self, tuple);

    /* Make update visible */
    CommandCounterIncrement();
   }
  }
  else
  {
   /*
    * If we were told to drop ONLY in this table (no recursion) and
    * there are no further parents for this constraint, we need to
    * mark the inheritors' constraints as locally defined rather than
    * inherited.
 */

   childcon->coninhcount--;
   if (childcon->coninhcount == 0)
    childcon->conislocal = true;

   CatalogTupleUpdate(conrel, &tuple->t_self, tuple);

   /* Make update visible */
   CommandCounterIncrement();
  }

  heap_freetuple(tuple);

  table_close(childrel, NoLock);
 }

 table_close(conrel, RowExclusiveLock);

 return conobj;
}

/*
 * ALTER COLUMN TYPE
 *
 * Unlike other subcommand types, we do parse transformation for ALTER COLUMN
 * TYPE during phase 1 --- the AlterTableCmd passed in here is already
 * transformed (and must be, because we rely on some transformed fields).
 *
 * The point of this is that the execution of all ALTER COLUMN TYPEs for a
 * table will be done "in parallel" during phase 3, so all the USING
 * expressions should be parsed assuming the original column types.  Also,
 * this allows a USING expression to refer to a field that will be dropped.
 *
 * To make this work safely, AT_PASS_DROP then AT_PASS_ALTER_TYPE must be
 * the first two execution steps in phase 2; they must not see the effects
 * of any other subcommand types, since the USING expressions are parsed
 * against the unmodified table's state.
 */

static void
ATPrepAlterColumnType(List **wqueue,
       AlteredTableInfo *tab, Relation rel,
       bool recurse, bool recursing,
       AlterTableCmd *cmd, LOCKMODE lockmode,
       AlterTableUtilityContext *context)
{
 char    *colName = cmd->name;
 ColumnDef  *def = (ColumnDef *) cmd->def;
 TypeName   *typeName = def->typeName;
 Node    *transform = def->cooked_default;
 HeapTuple tuple;
 Form_pg_attribute attTup;
 AttrNumber attnum;
 Oid   targettype;
 int32  targettypmod;
 Oid   targetcollid;
 NewColumnValue *newval;
 ParseState *pstate = make_parsestate(NULL);
 AclResult aclresult;
 bool  is_expr;

 pstate->p_sourcetext = context->queryString;

 if (rel->rd_rel->reloftype && !recursing)
  ereport(ERROR,
    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     errmsg("cannot alter column type of typed table"),
     parser_errposition(pstate, def->location)));

 /* lookup the attribute so we can check inheritance status */
 tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
 if (!HeapTupleIsValid(tuple))
  ereport(ERROR,
    (errcode(ERRCODE_UNDEFINED_COLUMN),
     errmsg("column \"%s\" of relation \"%s\" does not exist",
      colName, RelationGetRelationName(rel)),
     parser_errposition(pstate, def->location)));
 attTup = (Form_pg_attribute) GETSTRUCT(tuple);
 attnum = attTup->attnum;

 /* Can't alter a system attribute */
 if (attnum <= 0)
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("cannot alter system column \"%s\"", colName),
     parser_errposition(pstate, def->location)));

 /*
  * Cannot specify USING when altering type of a generated column, because
  * that would violate the generation expression.
 */

 if (attTup->attgenerated && def->cooked_default)
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
     errmsg("cannot specify USING when altering type of generated column"),
     errdetail("Column \"%s\" is a generated column.", colName),
     parser_errposition(pstate, def->location)));

 /*
  * Don't alter inherited columns.  At outer level, there had better not be
  * any inherited definition; when recursing, we assume this was checked at
  * the parent level (see below).
 */

 if (attTup->attinhcount > 0 && !recursing)
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
     errmsg("cannot alter inherited column \"%s\"", colName),
     parser_errposition(pstate, def->location)));

 /* Don't alter columns used in the partition key */
 if (has_partition_attrs(rel,
       bms_make_singleton(attnum - FirstLowInvalidHeapAttributeNumber),
       &is_expr))
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
     errmsg("cannot alter column \"%s\" because it is part of the partition key of relation \"%s\"",
      colName, RelationGetRelationName(rel)),
     parser_errposition(pstate, def->location)));

 /* Look up the target type */
 typenameTypeIdAndMod(pstate, typeName, &targettype, &targettypmod);

 aclresult = object_aclcheck(TypeRelationId, targettype, GetUserId(), ACL_USAGE);
 if (aclresult != ACLCHECK_OK)
  aclcheck_error_type(aclresult, targettype);

 /* And the collation */
 targetcollid = GetColumnDefCollation(pstate, def, targettype);

 /* make sure datatype is legal for a column */
 CheckAttributeType(colName, targettype, targetcollid,
        list_make1_oid(rel->rd_rel->reltype),
        (attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL ? CHKATYPE_IS_VIRTUAL : 0));

 if (attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
 {
  /* do nothing */
 }
 else if (tab->relkind == RELKIND_RELATION ||
    tab->relkind == RELKIND_PARTITIONED_TABLE)
 {
  /*
   * Set up an expression to transform the old data value to the new
   * type. If a USING option was given, use the expression as
   * transformed by transformAlterTableStmt, else just take the old
   * value and try to coerce it.  We do this first so that type
   * incompatibility can be detected before we waste effort, and because
   * we need the expression to be parsed against the original table row
   * type.
 */

  if (!transform)
  {
   transform = (Node *) makeVar(1, attnum,
           attTup->atttypid, attTup->atttypmod,
           attTup->attcollation,
           0);
  }

  transform = coerce_to_target_type(pstate,
            transform, exprType(transform),
            targettype, targettypmod,
            COERCION_ASSIGNMENT,
            COERCE_IMPLICIT_CAST,
            -1);
  if (transform == NULL)
  {
   /* error text depends on whether USING was specified or not */
   if (def->cooked_default != NULL)
    ereport(ERROR,
      (errcode(ERRCODE_DATATYPE_MISMATCH),
       errmsg("result of USING clause for column \"%s\""
        " cannot be cast automatically to type %s",
        colName, format_type_be(targettype)),
       errhint("You might need to add an explicit cast.")));
   else
    ereport(ERROR,
      (errcode(ERRCODE_DATATYPE_MISMATCH),
       errmsg("column \"%s\" cannot be cast automatically to type %s",
        colName, format_type_be(targettype)),
       !attTup->attgenerated ?
    /* translator: USING is SQL, don't translate it */
       errhint("You might need to specify \"USING %s::%s\".",
         quote_identifier(colName),
         format_type_with_typemod(targettype,
                targettypmod)) : 0));
  }

  /* Fix collations after all else */
  assign_expr_collations(pstate, transform);

  /* Expand virtual generated columns in the expr. */
  transform = expand_generated_columns_in_expr(transform, rel, 1);

  /* Plan the expr now so we can accurately assess the need to rewrite. */
  transform = (Node *) expression_planner((Expr *) transform);

  /*
   * Add a work queue item to make ATRewriteTable update the column
   * contents.
 */

  newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
  newval->attnum = attnum;
  newval->expr = (Expr *) transform;
  newval->is_generated = false;

  tab->newvals = lappend(tab->newvals, newval);
  if (ATColumnChangeRequiresRewrite(transform, attnum))
   tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
 }
 else if (transform)
  ereport(ERROR,
    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     errmsg("\"%s\" is not a table",
      RelationGetRelationName(rel))));

 if (!RELKIND_HAS_STORAGE(tab->relkind) || attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
 {
  /*
   * For relations or columns without storage, do this check now.
   * Regular tables will check it later when the table is being
   * rewritten.
 */

  find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
 }

 ReleaseSysCache(tuple);

 /*
  * Recurse manually by queueing a new command for each child, if
  * necessary. We cannot apply ATSimpleRecursion here because we need to
  * remap attribute numbers in the USING expression, if any.
  *
  * If we are told not to recurse, there had better not be any child
  * tables; else the alter would put them out of step.
 */

 if (recurse)
 {
  Oid   relid = RelationGetRelid(rel);
  List    *child_oids,
       *child_numparents;
  ListCell   *lo,
       *li;

  child_oids = find_all_inheritors(relid, lockmode,
           &child_numparents);

  /*
   * find_all_inheritors does the recursive search of the inheritance
   * hierarchy, so all we have to do is process all of the relids in the
   * list that it returns.
 */

  forboth(lo, child_oids, li, child_numparents)
  {
   Oid   childrelid = lfirst_oid(lo);
   int   numparents = lfirst_int(li);
   Relation childrel;
   HeapTuple childtuple;
   Form_pg_attribute childattTup;

   if (childrelid == relid)
    continue;

   /* find_all_inheritors already got lock */
   childrel = relation_open(childrelid, NoLock);
   CheckAlterTableIsSafe(childrel);

   /*
    * Verify that the child doesn't have any inherited definitions of
    * this column that came from outside this inheritance hierarchy.
    * (renameatt makes a similar test, though in a different way
    * because of its different recursion mechanism.)
 */

   childtuple = SearchSysCacheAttName(RelationGetRelid(childrel),
              colName);
   if (!HeapTupleIsValid(childtuple))
    ereport(ERROR,
      (errcode(ERRCODE_UNDEFINED_COLUMN),
       errmsg("column \"%s\" of relation \"%s\" does not exist",
        colName, RelationGetRelationName(childrel))));
   childattTup = (Form_pg_attribute) GETSTRUCT(childtuple);

   if (childattTup->attinhcount > numparents)
    ereport(ERROR,
      (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
       errmsg("cannot alter inherited column \"%s\" of relation \"%s\"",
        colName, RelationGetRelationName(childrel))));

   ReleaseSysCache(childtuple);

   /*
    * Remap the attribute numbers.  If no USING expression was
    * specified, there is no need for this step.
 */

   if (def->cooked_default)
   {
    AttrMap    *attmap;
    bool  found_whole_row;

    /* create a copy to scribble on */
    cmd = copyObject(cmd);

    attmap = build_attrmap_by_name(RelationGetDescr(childrel),
              RelationGetDescr(rel),
              false);
    ((ColumnDef *) cmd->def)->cooked_default =
     map_variable_attnos(def->cooked_default,
          10,
          attmap,
          InvalidOid, &found_whole_row);
    if (found_whole_row)
     ereport(ERROR,
       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
        errmsg("cannot convert whole-row table reference"),
        errdetail("USING expression contains a whole-row table reference.")));
    pfree(attmap);
   }
   ATPrepCmd(wqueue, childrel, cmd, falsetrue, lockmode, context);
   relation_close(childrel, NoLock);
  }
 }
 else if (!recursing &&
    find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
  ereport(ERROR,
    (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
     errmsg("type of inherited column \"%s\" must be changed in child tables too",
      colName)));

 if (tab->relkind == RELKIND_COMPOSITE_TYPE)
  ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);
}

/*
 * When the data type of a column is changed, a rewrite might not be required
 * if the new type is sufficiently identical to the old one, and the USING
 * clause isn't trying to insert some other value.  It's safe to skip the
 * rewrite in these cases:
 *
 * - the old type is binary coercible to the new type
 * - the new type is an unconstrained domain over the old type
 * - {NEW,OLD} or {OLD,NEW} is {timestamptz,timestamp} and the timezone is UTC
 *
 * In the case of a constrained domain, we could get by with scanning the
 * table and checking the constraint rather than actually rewriting it, but we
 * don't currently try to do that.
 */

static bool
ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno)
{
 Assert(expr != NULL);

 for (;;)
 {
  /* only one varno, so no need to check that */
  if (IsA(expr, Var) && ((Var *) expr)->varattno == varattno)
   return false;
  else if (IsA(expr, RelabelType))
   expr = (Node *) ((RelabelType *) expr)->arg;
  else if (IsA(expr, CoerceToDomain))
  {
   CoerceToDomain *d = (CoerceToDomain *) expr;

   if (DomainHasConstraints(d->resulttype))
    return true;
   expr = (Node *) d->arg;
  }
  else if (IsA(expr, FuncExpr))
  {
   FuncExpr   *f = (FuncExpr *) expr;

   switch (f->funcid)
   {
    case F_TIMESTAMPTZ_TIMESTAMP:
    case F_TIMESTAMP_TIMESTAMPTZ:
     if (TimestampTimestampTzRequiresRewrite())
      return true;
     else
      expr = linitial(f->args);
     break;
    default:
     return true;
   }
  }
  else
   return true;
 }
}

/*
 * ALTER COLUMN .. SET DATA TYPE
 *
 * Return the address of the modified column.
 */

static ObjectAddress
ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
       AlterTableCmd *cmd, LOCKMODE lockmode)
{
 char    *colName = cmd->name;
 ColumnDef  *def = (ColumnDef *) cmd->def;
 TypeName   *typeName = def->typeName;
 HeapTuple heapTup;
 Form_pg_attribute attTup,
    attOldTup;
 AttrNumber attnum;
 HeapTuple typeTuple;
 Form_pg_type tform;
 Oid   targettype;
 int32  targettypmod;
 Oid   targetcollid;
 Node    *defaultexpr;
 Relation attrelation;
 Relation depRel;
 ScanKeyData key[3];
 SysScanDesc scan;
 HeapTuple depTup;
 ObjectAddress address;

 /*
  * Clear all the missing values if we're rewriting the table, since this
  * renders them pointless.
 */

 if (tab->rewrite)
 {
  Relation newrel;

  newrel = table_open(RelationGetRelid(rel), NoLock);
  RelationClearMissing(newrel);
  relation_close(newrel, NoLock);
  /* make sure we don't conflict with later attribute modifications */
  CommandCounterIncrement();
 }

 attrelation = table_open(AttributeRelationId, RowExclusiveLock);

 /* Look up the target column */
 heapTup = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
 if (!HeapTupleIsValid(heapTup)) /* shouldn't happen */
  ereport(ERROR,
    (errcode(ERRCODE_UNDEFINED_COLUMN),
     errmsg("column \"%s\" of relation \"%s\" does not exist",
      colName, RelationGetRelationName(rel))));
 attTup = (Form_pg_attribute) GETSTRUCT(heapTup);
 attnum = attTup->attnum;
 attOldTup = TupleDescAttr(tab->oldDesc, attnum - 1);

 /* Check for multiple ALTER TYPE on same column --- can't cope */
 if (attTup->atttypid != attOldTup->atttypid ||
  attTup->atttypmod != attOldTup->atttypmod)
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("cannot alter type of column \"%s\" twice",
      colName)));

 /* Look up the target type (should not fail, since prep found it) */
 typeTuple = typenameType(NULL, typeName, &targettypmod);
 tform = (Form_pg_type) GETSTRUCT(typeTuple);
 targettype = tform->oid;
 /* And the collation */
 targetcollid = GetColumnDefCollation(NULL, def, targettype);

 /*
  * If there is a default expression for the column, get it and ensure we
  * can coerce it to the new datatype.  (We must do this before changing
  * the column type, because build_column_default itself will try to
  * coerce, and will not issue the error message we want if it fails.)
  *
  * We remove any implicit coercion steps at the top level of the old
  * default expression; this has been agreed to satisfy the principle of
  * least surprise.  (The conversion to the new column type should act like
  * it started from what the user sees as the stored expression, and the
  * implicit coercions aren't going to be shown.)
 */

 if (attTup->atthasdef)
 {
  defaultexpr = build_column_default(rel, attnum);
  Assert(defaultexpr);
  defaultexpr = strip_implicit_coercions(defaultexpr);
  defaultexpr = coerce_to_target_type(NULL, /* no UNKNOWN params */
           defaultexpr, exprType(defaultexpr),
           targettype, targettypmod,
           COERCION_ASSIGNMENT,
           COERCE_IMPLICIT_CAST,
           -1);
  if (defaultexpr == NULL)
  {
   if (attTup->attgenerated)
    ereport(ERROR,
      (errcode(ERRCODE_DATATYPE_MISMATCH),
       errmsg("generation expression for column \"%s\" cannot be cast automatically to type %s",
        colName, format_type_be(targettype))));
   else
    ereport(ERROR,
      (errcode(ERRCODE_DATATYPE_MISMATCH),
       errmsg("default for column \"%s\" cannot be cast automatically to type %s",
        colName, format_type_be(targettype))));
  }
 }
 else
  defaultexpr = NULL;

 /*
  * Find everything that depends on the column (constraints, indexes, etc),
  * and record enough information to let us recreate the objects.
  *
  * The actual recreation does not happen here, but only after we have
  * performed all the individual ALTER TYPE operations.  We have to save
  * the info before executing ALTER TYPE, though, else the deparser will
  * get confused.
 */

 RememberAllDependentForRebuilding(tab, AT_AlterColumnType, rel, attnum, colName);

 /*
  * Now scan for dependencies of this column on other things.  The only
  * things we should find are the dependency on the column datatype and
  * possibly a collation dependency.  Those can be removed.
 */

 depRel = table_open(DependRelationId, RowExclusiveLock);

 ScanKeyInit(&key[0],
    Anum_pg_depend_classid,
    BTEqualStrategyNumber, F_OIDEQ,
    ObjectIdGetDatum(RelationRelationId));
 ScanKeyInit(&key[1],
    Anum_pg_depend_objid,
    BTEqualStrategyNumber, F_OIDEQ,
    ObjectIdGetDatum(RelationGetRelid(rel)));
 ScanKeyInit(&key[2],
    Anum_pg_depend_objsubid,
    BTEqualStrategyNumber, F_INT4EQ,
    Int32GetDatum((int32) attnum));

 scan = systable_beginscan(depRel, DependDependerIndexId, true,
         NULL, 3, key);

 while (HeapTupleIsValid(depTup = systable_getnext(scan)))
 {
  Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup);
  ObjectAddress foundObject;

  foundObject.classId = foundDep->refclassid;
  foundObject.objectId = foundDep->refobjid;
  foundObject.objectSubId = foundDep->refobjsubid;

  if (foundDep->deptype != DEPENDENCY_NORMAL)
   elog(ERROR, "found unexpected dependency type '%c'",
     foundDep->deptype);
  if (!(foundDep->refclassid == TypeRelationId &&
     foundDep->refobjid == attTup->atttypid) &&
   !(foundDep->refclassid == CollationRelationId &&
     foundDep->refobjid == attTup->attcollation))
   elog(ERROR, "found unexpected dependency for column: %s",
     getObjectDescription(&foundObject, false));

  CatalogTupleDelete(depRel, &depTup->t_self);
 }

 systable_endscan(scan);

 table_close(depRel, RowExclusiveLock);

 /*
  * Here we go --- change the recorded column type and collation.  (Note
  * heapTup is a copy of the syscache entry, so okay to scribble on.) First
  * fix up the missing value if any.
 */

 if (attTup->atthasmissing)
 {
  Datum  missingval;
  bool  missingNull;

  /* if rewrite is true the missing value should already be cleared */
  Assert(tab->rewrite == 0);

  /* Get the missing value datum */
  missingval = heap_getattr(heapTup,
          Anum_pg_attribute_attmissingval,
          attrelation->rd_att,
          &missingNull);

  /* if it's a null array there is nothing to do */

  if (!missingNull)
  {
   /*
    * Get the datum out of the array and repack it in a new array
    * built with the new type data. We assume that since the table
    * doesn't need rewriting, the actual Datum doesn't need to be
    * changed, only the array metadata.
 */


   int   one = 1;
   bool  isNull;
   Datum  valuesAtt[Natts_pg_attribute] = {0};
   bool  nullsAtt[Natts_pg_attribute] = {0};
   bool  replacesAtt[Natts_pg_attribute] = {0};
   HeapTuple newTup;

   missingval = array_get_element(missingval,
             1,
             &one,
             0,
             attTup->attlen,
             attTup->attbyval,
             attTup->attalign,
             &isNull);
   missingval = PointerGetDatum(construct_array(&missingval,
               1,
               targettype,
               tform->typlen,
               tform->typbyval,
               tform->typalign));

   valuesAtt[Anum_pg_attribute_attmissingval - 1] = missingval;
   replacesAtt[Anum_pg_attribute_attmissingval - 1] = true;
   nullsAtt[Anum_pg_attribute_attmissingval - 1] = false;

   newTup = heap_modify_tuple(heapTup, RelationGetDescr(attrelation),
            valuesAtt, nullsAtt, replacesAtt);
   heap_freetuple(heapTup);
   heapTup = newTup;
   attTup = (Form_pg_attribute) GETSTRUCT(heapTup);
  }
 }

 attTup->atttypid = targettype;
 attTup->atttypmod = targettypmod;
 attTup->attcollation = targetcollid;
 if (list_length(typeName->arrayBounds) > PG_INT16_MAX)
  ereport(ERROR,
    errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
    errmsg("too many array dimensions"));
 attTup->attndims = list_length(typeName->arrayBounds);
 attTup->attlen = tform->typlen;
 attTup->attbyval = tform->typbyval;
 attTup->attalign = tform->typalign;
 attTup->attstorage = tform->typstorage;
 attTup->attcompression = InvalidCompressionMethod;

 ReleaseSysCache(typeTuple);

 CatalogTupleUpdate(attrelation, &heapTup->t_self, heapTup);

 table_close(attrelation, RowExclusiveLock);

 /* Install dependencies on new datatype and collation */
 add_column_datatype_dependency(RelationGetRelid(rel), attnum, targettype);
 add_column_collation_dependency(RelationGetRelid(rel), attnum, targetcollid);

 /*
  * Drop any pg_statistic entry for the column, since it's now wrong type
 */

 RemoveStatistics(RelationGetRelid(rel), attnum);

 InvokeObjectPostAlterHook(RelationRelationId,
         RelationGetRelid(rel), attnum);

 /*
  * Update the default, if present, by brute force --- remove and re-add
  * the default.  Probably unsafe to take shortcuts, since the new version
  * may well have additional dependencies.  (It's okay to do this now,
  * rather than after other ALTER TYPE commands, since the default won't
  * depend on other column types.)
 */

 if (defaultexpr)
 {
  /*
   * If it's a GENERATED default, drop its dependency records, in
   * particular its INTERNAL dependency on the column, which would
   * otherwise cause dependency.c to refuse to perform the deletion.
 */

  if (attTup->attgenerated)
  {
   Oid   attrdefoid = GetAttrDefaultOid(RelationGetRelid(rel), attnum);

   if (!OidIsValid(attrdefoid))
    elog(ERROR, "could not find attrdef tuple for relation %u attnum %d",
      RelationGetRelid(rel), attnum);
   (void) deleteDependencyRecordsFor(AttrDefaultRelationId, attrdefoid, false);
  }

  /*
   * Make updates-so-far visible, particularly the new pg_attribute row
   * which will be updated again.
 */

  CommandCounterIncrement();

  /*
   * We use RESTRICT here for safety, but at present we do not expect
   * anything to depend on the default.
 */

  RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, true,
        true);

  (void) StoreAttrDefault(rel, attnum, defaultexpr, true);
 }

 ObjectAddressSubSet(address, RelationRelationId,
      RelationGetRelid(rel), attnum);

 /* Cleanup */
 heap_freetuple(heapTup);

 return address;
}

/*
 * Subroutine for ATExecAlterColumnType and ATExecSetExpression: Find everything
 * that depends on the column (constraints, indexes, etc), and record enough
 * information to let us recreate the objects.
 */

static void
RememberAllDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype,
          Relation rel, AttrNumber attnum, const char *colName)
{
 Relation depRel;
 ScanKeyData key[3];
 SysScanDesc scan;
 HeapTuple depTup;

 Assert(subtype == AT_AlterColumnType || subtype == AT_SetExpression);

 depRel = table_open(DependRelationId, RowExclusiveLock);

 ScanKeyInit(&key[0],
    Anum_pg_depend_refclassid,
    BTEqualStrategyNumber, F_OIDEQ,
    ObjectIdGetDatum(RelationRelationId));
 ScanKeyInit(&key[1],
    Anum_pg_depend_refobjid,
    BTEqualStrategyNumber, F_OIDEQ,
    ObjectIdGetDatum(RelationGetRelid(rel)));
 ScanKeyInit(&key[2],
    Anum_pg_depend_refobjsubid,
    BTEqualStrategyNumber, F_INT4EQ,
    Int32GetDatum((int32) attnum));

 scan = systable_beginscan(depRel, DependReferenceIndexId, true,
         NULL, 3, key);

 while (HeapTupleIsValid(depTup = systable_getnext(scan)))
 {
  Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup);
  ObjectAddress foundObject;

  foundObject.classId = foundDep->classid;
  foundObject.objectId = foundDep->objid;
  foundObject.objectSubId = foundDep->objsubid;

  switch (foundObject.classId)
  {
   case RelationRelationId:
    {
     char  relKind = get_rel_relkind(foundObject.objectId);

     if (relKind == RELKIND_INDEX ||
      relKind == RELKIND_PARTITIONED_INDEX)
     {
      Assert(foundObject.objectSubId == 0);
      RememberIndexForRebuilding(foundObject.objectId, tab);
     }
     else if (relKind == RELKIND_SEQUENCE)
     {
      /*
       * This must be a SERIAL column's sequence.  We need
       * not do anything to it.
 */

      Assert(foundObject.objectSubId == 0);
     }
     else
     {
      /* Not expecting any other direct dependencies... */
      elog(ERROR, "unexpected object depending on column: %s",
        getObjectDescription(&foundObject, false));
     }
     break;
    }

   case ConstraintRelationId:
    Assert(foundObject.objectSubId == 0);
    RememberConstraintForRebuilding(foundObject.objectId, tab);
    break;

   case ProcedureRelationId:

    /*
     * A new-style SQL function can depend on a column, if that
     * column is referenced in the parsed function body.  Ideally
     * we'd automatically update the function by deparsing and
     * reparsing it, but that's risky and might well fail anyhow.
     * FIXME someday.
     *
     * This is only a problem for AT_AlterColumnType, not
     * AT_SetExpression.
 */

    if (subtype == AT_AlterColumnType)
     ereport(ERROR,
       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
        errmsg("cannot alter type of a column used by a function or procedure"),
        errdetail("%s depends on column \"%s\"",
            getObjectDescription(&foundObject, false),
            colName)));
    break;

   case RewriteRelationId:

    /*
     * View/rule bodies have pretty much the same issues as
     * function bodies.  FIXME someday.
 */

    if (subtype == AT_AlterColumnType)
     ereport(ERROR,
       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
        errmsg("cannot alter type of a column used by a view or rule"),
        errdetail("%s depends on column \"%s\"",
            getObjectDescription(&foundObject, false),
            colName)));
    break;

   case TriggerRelationId:

    /*
     * A trigger can depend on a column because the column is
     * specified as an update target, or because the column is
     * used in the trigger's WHEN condition.  The first case would
     * not require any extra work, but the second case would
     * require updating the WHEN expression, which has the same
     * issues as above.  Since we can't easily tell which case
     * applies, we punt for both.  FIXME someday.
 */

    if (subtype == AT_AlterColumnType)
     ereport(ERROR,
       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
        errmsg("cannot alter type of a column used in a trigger definition"),
        errdetail("%s depends on column \"%s\"",
            getObjectDescription(&foundObject, false),
            colName)));
    break;

   case PolicyRelationId:

    /*
     * A policy can depend on a column because the column is
     * specified in the policy's USING or WITH CHECK qual
     * expressions.  It might be possible to rewrite and recheck
     * the policy expression, but punt for now.  It's certainly
     * easy enough to remove and recreate the policy; still, FIXME
     * someday.
 */

    if (subtype == AT_AlterColumnType)
     ereport(ERROR,
       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
        errmsg("cannot alter type of a column used in a policy definition"),
        errdetail("%s depends on column \"%s\"",
            getObjectDescription(&foundObject, false),
            colName)));
    break;

   case AttrDefaultRelationId:
    {
     ObjectAddress col = GetAttrDefaultColumnAddress(foundObject.objectId);

     if (col.objectId == RelationGetRelid(rel) &&
      col.objectSubId == attnum)
     {
      /*
       * Ignore the column's own default expression.  The
       * caller deals with it.
 */

     }
     else
     {
      /*
       * This must be a reference from the expression of a
       * generated column elsewhere in the same table.
       * Changing the type/generated expression of a column
       * that is used by a generated column is not allowed
       * by SQL standard, so just punt for now.  It might be
       * doable with some thinking and effort.
 */

      if (subtype == AT_AlterColumnType)
       ereport(ERROR,
         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
          errmsg("cannot alter type of a column used by a generated column"),
          errdetail("Column \"%s\" is used by generated column \"%s\".",
              colName,
              get_attname(col.objectId,
                 col.objectSubId,
                 false))));
     }
     break;
    }

   case StatisticExtRelationId:

    /*
     * Give the extended-stats machinery a chance to fix anything
     * that this column type change would break.
 */

    RememberStatisticsForRebuilding(foundObject.objectId, tab);
    break;

   case PublicationRelRelationId:

    /*
     * Column reference in a PUBLICATION ... FOR TABLE ... WHERE
     * clause.  Same issues as above.  FIXME someday.
 */

    if (subtype == AT_AlterColumnType)
     ereport(ERROR,
       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
        errmsg("cannot alter type of a column used by a publication WHERE clause"),
        errdetail("%s depends on column \"%s\"",
            getObjectDescription(&foundObject, false),
            colName)));
    break;

   default:

    /*
     * We don't expect any other sorts of objects to depend on a
     * column.
 */

    elog(ERROR, "unexpected object depending on column: %s",
      getObjectDescription(&foundObject, false));
    break;
  }
 }

 systable_endscan(scan);
 table_close(depRel, NoLock);
}

/*
 * Subroutine for ATExecAlterColumnType: remember that a replica identity
 * needs to be reset.
 */

static void
RememberReplicaIdentityForRebuilding(Oid indoid, AlteredTableInfo *tab)
{
 if (!get_index_isreplident(indoid))
  return;

 if (tab->replicaIdentityIndex)
  elog(ERROR, "relation %u has multiple indexes marked as replica identity", tab->relid);

 tab->replicaIdentityIndex = get_rel_name(indoid);
}

/*
 * Subroutine for ATExecAlterColumnType: remember any clustered index.
 */

static void
RememberClusterOnForRebuilding(Oid indoid, AlteredTableInfo *tab)
{
 if (!get_index_isclustered(indoid))
  return;

 if (tab->clusterOnIndex)
  elog(ERROR, "relation %u has multiple clustered indexes", tab->relid);

 tab->clusterOnIndex = get_rel_name(indoid);
}

/*
 * Subroutine for ATExecAlterColumnType: remember that a constraint needs
 * to be rebuilt (which we might already know).
 */

static void
RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab)
{
 /*
  * This de-duplication check is critical for two independent reasons: we
  * mustn't try to recreate the same constraint twice, and if a constraint
  * depends on more than one column whose type is to be altered, we must
  * capture its definition string before applying any of the column type
  * changes.  ruleutils.c will get confused if we ask again later.
 */

 if (!list_member_oid(tab->changedConstraintOids, conoid))
 {
  /* OK, capture the constraint's existing definition string */
  char    *defstring = pg_get_constraintdef_command(conoid);
  Oid   indoid;

  /*
   * It is critical to create not-null constraints ahead of primary key
   * indexes; otherwise, the not-null constraint would be created by the
   * primary key, and the constraint name would be wrong.
 */

  if (get_constraint_type(conoid) == CONSTRAINT_NOTNULL)
  {
   tab->changedConstraintOids = lcons_oid(conoid,
               tab->changedConstraintOids);
   tab->changedConstraintDefs = lcons(defstring,
              tab->changedConstraintDefs);
  }
  else
  {

   tab->changedConstraintOids = lappend_oid(tab->changedConstraintOids,
              conoid);
   tab->changedConstraintDefs = lappend(tab->changedConstraintDefs,
             defstring);
  }

  /*
   * For the index of a constraint, if any, remember if it is used for
   * the table's replica identity or if it is a clustered index, so that
   * ATPostAlterTypeCleanup() can queue up commands necessary to restore
   * those properties.
 */

  indoid = get_constraint_index(conoid);
  if (OidIsValid(indoid))
  {
   RememberReplicaIdentityForRebuilding(indoid, tab);
   RememberClusterOnForRebuilding(indoid, tab);
  }
 }
}

/*
 * Subroutine for ATExecAlterColumnType: remember that an index needs
 * to be rebuilt (which we might already know).
 */

static void
RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab)
{
 /*
  * This de-duplication check is critical for two independent reasons: we
  * mustn't try to recreate the same index twice, and if an index depends
  * on more than one column whose type is to be altered, we must capture
  * its definition string before applying any of the column type changes.
  * ruleutils.c will get confused if we ask again later.
 */

 if (!list_member_oid(tab->changedIndexOids, indoid))
 {
  /*
   * Before adding it as an index-to-rebuild, we'd better see if it
   * belongs to a constraint, and if so rebuild the constraint instead.
   * Typically this check fails, because constraint indexes normally
   * have only dependencies on their constraint.  But it's possible for
   * such an index to also have direct dependencies on table columns,
   * for example with a partial exclusion constraint.
 */

  Oid   conoid = get_index_constraint(indoid);

  if (OidIsValid(conoid))
  {
   RememberConstraintForRebuilding(conoid, tab);
  }
  else
  {
   /* OK, capture the index's existing definition string */
   char    *defstring = pg_get_indexdef_string(indoid);

   tab->changedIndexOids = lappend_oid(tab->changedIndexOids,
            indoid);
   tab->changedIndexDefs = lappend(tab->changedIndexDefs,
           defstring);

   /*
    * Remember if this index is used for the table's replica identity
    * or if it is a clustered index, so that ATPostAlterTypeCleanup()
    * can queue up commands necessary to restore those properties.
 */

   RememberReplicaIdentityForRebuilding(indoid, tab);
   RememberClusterOnForRebuilding(indoid, tab);
  }
 }
}

/*
 * Subroutine for ATExecAlterColumnType: remember that a statistics object
 * needs to be rebuilt (which we might already know).
 */

static void
RememberStatisticsForRebuilding(Oid stxoid, AlteredTableInfo *tab)
{
 /*
  * This de-duplication check is critical for two independent reasons: we
  * mustn't try to recreate the same statistics object twice, and if the
  * statistics object depends on more than one column whose type is to be
  * altered, we must capture its definition string before applying any of
  * the type changes. ruleutils.c will get confused if we ask again later.
 */

 if (!list_member_oid(tab->changedStatisticsOids, stxoid))
 {
  /* OK, capture the statistics object's existing definition string */
  char    *defstring = pg_get_statisticsobjdef_string(stxoid);

  tab->changedStatisticsOids = lappend_oid(tab->changedStatisticsOids,
             stxoid);
  tab->changedStatisticsDefs = lappend(tab->changedStatisticsDefs,
            defstring);
 }
}

/*
 * Cleanup after we've finished all the ALTER TYPE or SET EXPRESSION
 * operations for a particular relation.  We have to drop and recreate all the
 * indexes and constraints that depend on the altered columns.  We do the
 * actual dropping here, but re-creation is managed by adding work queue
 * entries to do those steps later.
 */

static void
ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
{
 ObjectAddress obj;
 ObjectAddresses *objects;
 ListCell   *def_item;
 ListCell   *oid_item;

 /*
  * Collect all the constraints and indexes to drop so we can process them
  * in a single call.  That way we don't have to worry about dependencies
  * among them.
 */

 objects = new_object_addresses();

 /*
  * Re-parse the index and constraint definitions, and attach them to the
  * appropriate work queue entries.  We do this before dropping because in
  * the case of a constraint on another table, we might not yet have
  * exclusive lock on the table the constraint is attached to, and we need
  * to get that before reparsing/dropping.  (That's possible at least for
  * FOREIGN KEY, CHECK, and EXCLUSION constraints; in non-FK cases it
  * requires a dependency on the target table's composite type in the other
  * table's constraint expressions.)
  *
  * We can't rely on the output of deparsing to tell us which relation to
  * operate on, because concurrent activity might have made the name
  * resolve differently.  Instead, we've got to use the OID of the
  * constraint or index we're processing to figure out which relation to
  * operate on.
 */

 forboth(oid_item, tab->changedConstraintOids,
   def_item, tab->changedConstraintDefs)
 {
  Oid   oldId = lfirst_oid(oid_item);
  HeapTuple tup;
  Form_pg_constraint con;
  Oid   relid;
  Oid   confrelid;
  bool  conislocal;

  tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(oldId));
  if (!HeapTupleIsValid(tup)) /* should not happen */
   elog(ERROR, "cache lookup failed for constraint %u", oldId);
  con = (Form_pg_constraint) GETSTRUCT(tup);
  if (OidIsValid(con->conrelid))
   relid = con->conrelid;
  else
  {
   /* must be a domain constraint */
   relid = get_typ_typrelid(getBaseType(con->contypid));
   if (!OidIsValid(relid))
    elog(ERROR, "could not identify relation associated with constraint %u", oldId);
  }
  confrelid = con->confrelid;
  conislocal = con->conislocal;
  ReleaseSysCache(tup);

  ObjectAddressSet(obj, ConstraintRelationId, oldId);
  add_exact_object_address(&obj, objects);

  /*
   * If the constraint is inherited (only), we don't want to inject a
   * new definition here; it'll get recreated when
   * ATAddCheckNNConstraint recurses from adding the parent table's
   * constraint.  But we had to carry the info this far so that we can
   * drop the constraint below.
 */

  if (!conislocal)
   continue;

  /*
   * When rebuilding another table's constraint that references the
   * table we're modifying, we might not yet have any lock on the other
   * table, so get one now.  We'll need AccessExclusiveLock for the DROP
   * CONSTRAINT step, so there's no value in asking for anything weaker.
 */

  if (relid != tab->relid)
   LockRelationOid(relid, AccessExclusiveLock);

  ATPostAlterTypeParse(oldId, relid, confrelid,
        (char *) lfirst(def_item),
        wqueue, lockmode, tab->rewrite);
 }
 forboth(oid_item, tab->changedIndexOids,
   def_item, tab->changedIndexDefs)
 {
  Oid   oldId = lfirst_oid(oid_item);
  Oid   relid;

  relid = IndexGetRelation(oldId, false);

  /*
   * As above, make sure we have lock on the index's table if it's not
   * the same table.
 */

  if (relid != tab->relid)
   LockRelationOid(relid, AccessExclusiveLock);

  ATPostAlterTypeParse(oldId, relid, InvalidOid,
        (char *) lfirst(def_item),
        wqueue, lockmode, tab->rewrite);

  ObjectAddressSet(obj, RelationRelationId, oldId);
  add_exact_object_address(&obj, objects);
 }

 /* add dependencies for new statistics */
 forboth(oid_item, tab->changedStatisticsOids,
   def_item, tab->changedStatisticsDefs)
 {
  Oid   oldId = lfirst_oid(oid_item);
  Oid   relid;

  relid = StatisticsGetRelation(oldId, false);

  /*
   * As above, make sure we have lock on the statistics object's table
   * if it's not the same table.  However, we take
   * ShareUpdateExclusiveLock here, aligning with the lock level used in
   * CreateStatistics and RemoveStatisticsById.
   *
   * CAUTION: this should be done after all cases that grab
   * AccessExclusiveLock, else we risk causing deadlock due to needing
   * to promote our table lock.
 */

  if (relid != tab->relid)
   LockRelationOid(relid, ShareUpdateExclusiveLock);

  ATPostAlterTypeParse(oldId, relid, InvalidOid,
        (char *) lfirst(def_item),
        wqueue, lockmode, tab->rewrite);

  ObjectAddressSet(obj, StatisticExtRelationId, oldId);
  add_exact_object_address(&obj, objects);
 }

 /*
  * Queue up command to restore replica identity index marking
 */

 if (tab->replicaIdentityIndex)
 {
  AlterTableCmd *cmd = makeNode(AlterTableCmd);
  ReplicaIdentityStmt *subcmd = makeNode(ReplicaIdentityStmt);

  subcmd->identity_type = REPLICA_IDENTITY_INDEX;
  subcmd->name = tab->replicaIdentityIndex;
  cmd->subtype = AT_ReplicaIdentity;
  cmd->def = (Node *) subcmd;

  /* do it after indexes and constraints */
  tab->subcmds[AT_PASS_OLD_CONSTR] =
   lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
 }

 /*
  * Queue up command to restore marking of index used for cluster.
 */

 if (tab->clusterOnIndex)
 {
  AlterTableCmd *cmd = makeNode(AlterTableCmd);

  cmd->subtype = AT_ClusterOn;
  cmd->name = tab->clusterOnIndex;

  /* do it after indexes and constraints */
  tab->subcmds[AT_PASS_OLD_CONSTR] =
   lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
 }

 /*
  * It should be okay to use DROP_RESTRICT here, since nothing else should
  * be depending on these objects.
 */

 performMultipleDeletions(objects, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);

 free_object_addresses(objects);

 /*
  * The objects will get recreated during subsequent passes over the work
  * queue.
 */

}

/*
 * Parse the previously-saved definition string for a constraint, index or
 * statistics object against the newly-established column data type(s), and
 * queue up the resulting command parsetrees for execution.
 *
 * This might fail if, for example, you have a WHERE clause that uses an
 * operator that's not available for the new column type.
 */

static void
ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
      List **wqueue, LOCKMODE lockmode, bool rewrite)
{
 List    *raw_parsetree_list;
 List    *querytree_list;
 ListCell   *list_item;
 Relation rel;

 /*
  * We expect that we will get only ALTER TABLE and CREATE INDEX
  * statements. Hence, there is no need to pass them through
  * parse_analyze_*() or the rewriter, but instead we need to pass them
  * through parse_utilcmd.c to make them ready for execution.
 */

 raw_parsetree_list = raw_parser(cmd, RAW_PARSE_DEFAULT);
 querytree_list = NIL;
 foreach(list_item, raw_parsetree_list)
 {
  RawStmt    *rs = lfirst_node(RawStmt, list_item);
  Node    *stmt = rs->stmt;

  if (IsA(stmt, IndexStmt))
   querytree_list = lappend(querytree_list,
          transformIndexStmt(oldRelId,
              (IndexStmt *) stmt,
              cmd));
  else if (IsA(stmt, AlterTableStmt))
  {
   List    *beforeStmts;
   List    *afterStmts;

   stmt = (Node *) transformAlterTableStmt(oldRelId,
             (AlterTableStmt *) stmt,
             cmd,
             &beforeStmts,
             &afterStmts);
   querytree_list = list_concat(querytree_list, beforeStmts);
   querytree_list = lappend(querytree_list, stmt);
   querytree_list = list_concat(querytree_list, afterStmts);
  }
  else if (IsA(stmt, CreateStatsStmt))
   querytree_list = lappend(querytree_list,
          transformStatsStmt(oldRelId,
              (CreateStatsStmt *) stmt,
              cmd));
  else
   querytree_list = lappend(querytree_list, stmt);
 }

 /* Caller should already have acquired whatever lock we need. */
 rel = relation_open(oldRelId, NoLock);

 /*
  * Attach each generated command to the proper place in the work queue.
  * Note this could result in creation of entirely new work-queue entries.
  *
  * Also note that we have to tweak the command subtypes, because it turns
  * out that re-creation of indexes and constraints has to act a bit
  * differently from initial creation.
 */

 foreach(list_item, querytree_list)
 {
  Node    *stm = (Node *) lfirst(list_item);
  AlteredTableInfo *tab;

  tab = ATGetQueueEntry(wqueue, rel);

  if (IsA(stm, IndexStmt))
  {
   IndexStmt  *stmt = (IndexStmt *) stm;
   AlterTableCmd *newcmd;

   if (!rewrite)
    TryReuseIndex(oldId, stmt);
   stmt->reset_default_tblspc = true;
   /* keep the index's comment */
   stmt->idxcomment = GetComment(oldId, RelationRelationId, 0);

   newcmd = makeNode(AlterTableCmd);
   newcmd->subtype = AT_ReAddIndex;
   newcmd->def = (Node *) stmt;
   tab->subcmds[AT_PASS_OLD_INDEX] =
    lappend(tab->subcmds[AT_PASS_OLD_INDEX], newcmd);
  }
  else if (IsA(stm, AlterTableStmt))
  {
   AlterTableStmt *stmt = (AlterTableStmt *) stm;
   ListCell   *lcmd;

   foreach(lcmd, stmt->cmds)
   {
    AlterTableCmd *cmd = lfirst_node(AlterTableCmd, lcmd);

    if (cmd->subtype == AT_AddIndex)
    {
     IndexStmt  *indstmt;
     Oid   indoid;

     indstmt = castNode(IndexStmt, cmd->def);
     indoid = get_constraint_index(oldId);

     if (!rewrite)
      TryReuseIndex(indoid, indstmt);
     /* keep any comment on the index */
     indstmt->idxcomment = GetComment(indoid,
              RelationRelationId, 0);
     indstmt->reset_default_tblspc = true;

     cmd->subtype = AT_ReAddIndex;
     tab->subcmds[AT_PASS_OLD_INDEX] =
      lappend(tab->subcmds[AT_PASS_OLD_INDEX], cmd);

     /* recreate any comment on the constraint */
     RebuildConstraintComment(tab,
            AT_PASS_OLD_INDEX,
            oldId,
            rel,
            NIL,
            indstmt->idxname);
    }
    else if (cmd->subtype == AT_AddConstraint)
    {
     Constraint *con = castNode(Constraint, cmd->def);

     con->old_pktable_oid = refRelId;
     /* rewriting neither side of a FK */
     if (con->contype == CONSTR_FOREIGN &&
      !rewrite && tab->rewrite == 0)
      TryReuseForeignKey(oldId, con);
     con->reset_default_tblspc = true;
     cmd->subtype = AT_ReAddConstraint;
     tab->subcmds[AT_PASS_OLD_CONSTR] =
      lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);

     /*
      * Recreate any comment on the constraint.  If we have
      * recreated a primary key, then transformTableConstraint
      * has added an unnamed not-null constraint here; skip
      * this in that case.
 */

     if (con->conname)
      RebuildConstraintComment(tab,
             AT_PASS_OLD_CONSTR,
             oldId,
             rel,
             NIL,
             con->conname);
     else
      Assert(con->contype == CONSTR_NOTNULL);
    }
    else
     elog(ERROR, "unexpected statement subtype: %d",
       (int) cmd->subtype);
   }
  }
  else if (IsA(stm, AlterDomainStmt))
  {
   AlterDomainStmt *stmt = (AlterDomainStmt *) stm;

   if (stmt->subtype == 'C'/* ADD CONSTRAINT */
   {
    Constraint *con = castNode(Constraint, stmt->def);
    AlterTableCmd *cmd = makeNode(AlterTableCmd);

    cmd->subtype = AT_ReAddDomainConstraint;
    cmd->def = (Node *) stmt;
    tab->subcmds[AT_PASS_OLD_CONSTR] =
     lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);

    /* recreate any comment on the constraint */
    RebuildConstraintComment(tab,
           AT_PASS_OLD_CONSTR,
           oldId,
           NULL,
           stmt->typeName,
           con->conname);
   }
   else
    elog(ERROR, "unexpected statement subtype: %d",
      (int) stmt->subtype);
  }
  else if (IsA(stm, CreateStatsStmt))
  {
   CreateStatsStmt *stmt = (CreateStatsStmt *) stm;
   AlterTableCmd *newcmd;

   /* keep the statistics object's comment */
   stmt->stxcomment = GetComment(oldId, StatisticExtRelationId, 0);

   newcmd = makeNode(AlterTableCmd);
   newcmd->subtype = AT_ReAddStatistics;
   newcmd->def = (Node *) stmt;
   tab->subcmds[AT_PASS_MISC] =
    lappend(tab->subcmds[AT_PASS_MISC], newcmd);
  }
  else
   elog(ERROR, "unexpected statement type: %d",
     (int) nodeTag(stm));
 }

 relation_close(rel, NoLock);
}

/*
 * Subroutine for ATPostAlterTypeParse() to recreate any existing comment
 * for a table or domain constraint that is being rebuilt.
 *
 * objid is the OID of the constraint.
 * Pass "rel" for a table constraint, or "domname" (domain's qualified name
 * as a string list) for a domain constraint.
 * (We could dig that info, as well as the conname, out of the pg_constraint
 * entry; but callers already have them so might as well pass them.)
 */

static void
RebuildConstraintComment(AlteredTableInfo *tab, AlterTablePass pass, Oid objid,
       Relation rel, List *domname,
       const char *conname)
{
 CommentStmt *cmd;
 char    *comment_str;
 AlterTableCmd *newcmd;

 /* Look for comment for object wanted, and leave if none */
 comment_str = GetComment(objid, ConstraintRelationId, 0);
 if (comment_str == NULL)
  return;

 /* Build CommentStmt node, copying all input data for safety */
 cmd = makeNode(CommentStmt);
 if (rel)
 {
  cmd->objtype = OBJECT_TABCONSTRAINT;
  cmd->object = (Node *)
   list_make3(makeString(get_namespace_name(RelationGetNamespace(rel))),
        makeString(pstrdup(RelationGetRelationName(rel))),
        makeString(pstrdup(conname)));
 }
 else
 {
  cmd->objtype = OBJECT_DOMCONSTRAINT;
  cmd->object = (Node *)
   list_make2(makeTypeNameFromNameList(copyObject(domname)),
        makeString(pstrdup(conname)));
 }
 cmd->comment = comment_str;

 /* Append it to list of commands */
 newcmd = makeNode(AlterTableCmd);
 newcmd->subtype = AT_ReAddComment;
 newcmd->def = (Node *) cmd;
 tab->subcmds[pass] = lappend(tab->subcmds[pass], newcmd);
}

/*
 * Subroutine for ATPostAlterTypeParse().  Calls out to CheckIndexCompatible()
 * for the real analysis, then mutates the IndexStmt based on that verdict.
 */

static void
TryReuseIndex(Oid oldId, IndexStmt *stmt)
{
 if (CheckIndexCompatible(oldId,
        stmt->accessMethod,
        stmt->indexParams,
        stmt->excludeOpNames,
        stmt->iswithoutoverlaps))
 {
  Relation irel = index_open(oldId, NoLock);

  /* If it's a partitioned index, there is no storage to share. */
  if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
  {
   stmt->oldNumber = irel->rd_locator.relNumber;
   stmt->oldCreateSubid = irel->rd_createSubid;
   stmt->oldFirstRelfilelocatorSubid = irel->rd_firstRelfilelocatorSubid;
  }
  index_close(irel, NoLock);
 }
}

/*
 * Subroutine for ATPostAlterTypeParse().
 *
 * Stash the old P-F equality operator into the Constraint node, for possible
 * use by ATAddForeignKeyConstraint() in determining whether revalidation of
 * this constraint can be skipped.
 */

static void
TryReuseForeignKey(Oid oldId, Constraint *con)
{
 HeapTuple tup;
 Datum  adatum;
 ArrayType  *arr;
 Oid     *rawarr;
 int   numkeys;
 int   i;

 Assert(con->contype == CONSTR_FOREIGN);
 Assert(con->old_conpfeqop == NIL); /* already prepared this node */

 tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(oldId));
 if (!HeapTupleIsValid(tup)) /* should not happen */
  elog(ERROR, "cache lookup failed for constraint %u", oldId);

 adatum = SysCacheGetAttrNotNull(CONSTROID, tup,
         Anum_pg_constraint_conpfeqop);
 arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
 numkeys = ARR_DIMS(arr)[0];
 /* test follows the one in ri_FetchConstraintInfo() */
 if (ARR_NDIM(arr) != 1 ||
  ARR_HASNULL(arr) ||
  ARR_ELEMTYPE(arr) != OIDOID)
  elog(ERROR, "conpfeqop is not a 1-D Oid array");
 rawarr = (Oid *) ARR_DATA_PTR(arr);

 /* stash a List of the operator Oids in our Constraint node */
 for (i = 0; i < numkeys; i++)
  con->old_conpfeqop = lappend_oid(con->old_conpfeqop, rawarr[i]);

 ReleaseSysCache(tup);
}

/*
 * ALTER COLUMN .. OPTIONS ( ... )
 *
 * Returns the address of the modified column
 */

static ObjectAddress
ATExecAlterColumnGenericOptions(Relation rel,
        const char *colName,
        List *options,
        LOCKMODE lockmode)
{
 Relation ftrel;
 Relation attrel;
 ForeignServer *server;
 ForeignDataWrapper *fdw;
 HeapTuple tuple;
 HeapTuple newtuple;
 bool  isnull;
 Datum  repl_val[Natts_pg_attribute];
 bool  repl_null[Natts_pg_attribute];
 bool  repl_repl[Natts_pg_attribute];
 Datum  datum;
 Form_pg_foreign_table fttableform;
 Form_pg_attribute atttableform;
 AttrNumber attnum;
 ObjectAddress address;

 if (options == NIL)
  return InvalidObjectAddress;

 /* First, determine FDW validator associated to the foreign table. */
 ftrel = table_open(ForeignTableRelationId, AccessShareLock);
 tuple = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(rel->rd_id));
 if (!HeapTupleIsValid(tuple))
  ereport(ERROR,
    (errcode(ERRCODE_UNDEFINED_OBJECT),
     errmsg("foreign table \"%s\" does not exist",
      RelationGetRelationName(rel))));
 fttableform = (Form_pg_foreign_table) GETSTRUCT(tuple);
 server = GetForeignServer(fttableform->ftserver);
 fdw = GetForeignDataWrapper(server->fdwid);

 table_close(ftrel, AccessShareLock);
 ReleaseSysCache(tuple);

 attrel = table_open(AttributeRelationId, RowExclusiveLock);
 tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
 if (!HeapTupleIsValid(tuple))
  ereport(ERROR,
    (errcode(ERRCODE_UNDEFINED_COLUMN),
     errmsg("column \"%s\" of relation \"%s\" does not exist",
      colName, RelationGetRelationName(rel))));

 /* Prevent them from altering a system attribute */
 atttableform = (Form_pg_attribute) GETSTRUCT(tuple);
 attnum = atttableform->attnum;
 if (attnum <= 0)
  ereport(ERROR,
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     errmsg("cannot alter system column \"%s\"", colName)));


 /* Initialize buffers for new tuple values */
 memset(repl_val, 0sizeof(repl_val));
 memset(repl_null, falsesizeof(repl_null));
 memset(repl_repl, falsesizeof(repl_repl));

 /* Extract the current options */
 datum = SysCacheGetAttr(ATTNAME,
       tuple,
       Anum_pg_attribute_attfdwoptions,
       &isnull);
 if (isnull)
  datum = PointerGetDatum(NULL);

 /* Transform the options */
 datum = transformGenericOptions(AttributeRelationId,
         datum,
         options,
         fdw->fdwvalidator);

 if (PointerIsValid(DatumGetPointer(datum)))
  repl_val[Anum_pg_attribute_attfdwoptions - 1] = datum;
 else
  repl_null[Anum_pg_attribute_attfdwoptions - 1] = true;

 repl_repl[Anum_pg_attribute_attfdwoptions - 1] = true;

 /* Everything looks good - update the tuple */

 newtuple = heap_modify_tuple(tuple, RelationGetDescr(attrel),
         repl_val, repl_null, repl_repl);

 CatalogTupleUpdate(attrel, &newtuple->t_self, newtuple);

 InvokeObjectPostAlterHook(RelationRelationId,
         RelationGetRelid(rel),
         atttableform->attnum);
 ObjectAddressSubSet(address, RelationRelationId,
      RelationGetRelid(rel), attnum);

 ReleaseSysCache(tuple);

 table_close(attrel, RowExclusiveLock);

 heap_freetuple(newtuple);

 return address;
}

/*
 * ALTER TABLE OWNER
 *
 * recursing is true if we are recursing from a table to its indexes,
 * sequences, or toast table.  We don't allow the ownership of those things to
 * be changed separately from the parent table.  Also, we can skip permission
 * checks (this is necessary not just an optimization, else we'd fail to
 * handle toast tables properly).
 *
 * recursing is also true if ALTER TYPE OWNER is calling us to fix up a
 * free-standing composite type.
 */

void
ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lockmode)
{
 Relation target_rel;
 Relation class_rel;
 HeapTuple tuple;
 Form_pg_class tuple_class;

 /*
  * Get exclusive lock till end of transaction on the target table. Use
  * relation_open so that we can work on indexes and sequences.
 */

 target_rel = relation_open(relationOid, lockmode);

 /* Get its pg_class tuple, too */
 class_rel = table_open(RelationRelationId, RowExclusiveLock);

 tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relationOid));
 if (!HeapTupleIsValid(tuple))
  elog(ERROR, "cache lookup failed for relation %u", relationOid);
 tuple_class = (Form_pg_class) GETSTRUCT(tuple);

 /* Can we change the ownership of this tuple? */
 switch (tuple_class->relkind)
 {
  case RELKIND_RELATION:
  case RELKIND_VIEW:
  case RELKIND_MATVIEW:
  case RELKIND_FOREIGN_TABLE:
  case RELKIND_PARTITIONED_TABLE:
   /* ok to change owner */
   break;
  case RELKIND_INDEX:
   if (!recursing)
   {
    /*
     * Because ALTER INDEX OWNER used to be allowed, and in fact
     * is generated by old versions of pg_dump, we give a warning
     * and do nothing rather than erroring out.  Also, to avoid
     * unnecessary chatter while restoring those old dumps, say
     * nothing at all if the command would be a no-op anyway.
 */

    if (tuple_class->relowner != newOwnerId)
     ereport(WARNING,
       (errcode(ERRCODE_WRONG_OBJECT_TYPE),
        errmsg("cannot change owner of index \"%s\"",
         NameStr(tuple_class->relname)),
        errhint("Change the ownership of the index's table instead.")));
    /* quick hack to exit via the no-op path */
    newOwnerId = tuple_class->relowner;
   }
   break;
  case RELKIND_PARTITIONED_INDEX:
   if (recursing)
    break;
   ereport(ERROR,
     (errcode(ERRCODE_WRONG_OBJECT_TYPE),
      errmsg("cannot change owner of index \"%s\"",
       NameStr(tuple_class->relname)),
      errhint("Change the ownership of the index's table instead.")));
   break;
  case RELKIND_SEQUENCE:
   if (!recursing &&
    tuple_class->relowner != newOwnerId)
   {
    /* if it's an owned sequence, disallow changing it by itself */
    Oid   tableId;
    int32  colId;

    if (sequenceIsOwned(relationOid, DEPENDENCY_AUTO, &tableId, &colId) ||
     sequenceIsOwned(relationOid, DEPENDENCY_INTERNAL, &tableId, &colId))
     ereport(ERROR,
       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
        errmsg("cannot change owner of sequence \"%s\"",
         NameStr(tuple_class->relname)),
        errdetail("Sequence \"%s\" is linked to table \"%s\".",
            NameStr(tuple_class->relname),
            get_rel_name(tableId))));
   }
   break;
  case RELKIND_COMPOSITE_TYPE:
   if (recursing)
    break;
   ereport(ERROR,
     (errcode(ERRCODE_WRONG_OBJECT_TYPE),
      errmsg("\"%s\" is a composite type",
       NameStr(tuple_class->relname)),
   /* translator: %s is an SQL ALTER command */
      errhint("Use %s instead.",
        "ALTER TYPE")));
   break;
  case RELKIND_TOASTVALUE:
   if (recursing)
    break;
   /* FALL THRU */
  default:
   ereport(ERROR,
     (errcode(ERRCODE_WRONG_OBJECT_TYPE),
      errmsg("cannot change owner of relation \"%s\"",
       NameStr(tuple_class->relname)),
      errdetail_relkind_not_supported(tuple_class->relkind)));
 }

 /*
  * If the new owner is the same as the existing owner, consider the
  * command to have succeeded.  This is for dump restoration purposes.
 */

 if (tuple_class->relowner != newOwnerId)
 {
  Datum  repl_val[Natts_pg_class];
  bool  repl_null[Natts_pg_class];
  bool  repl_repl[Natts_pg_class];
  Acl     *newAcl;
  Datum  aclDatum;
  bool  isNull;
  HeapTuple newtuple;

  /* skip permission checks when recursing to index or toast table */
  if (!recursing)
  {
   /* Superusers can always do it */
   if (!superuser())
   {
    Oid   namespaceOid = tuple_class->relnamespace;
    AclResult aclresult;

    /* Otherwise, must be owner of the existing object */
    if (!object_ownercheck(RelationRelationId, relationOid, GetUserId()))
     aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relationOid)),
           RelationGetRelationName(target_rel));

    /* Must be able to become new owner */
    check_can_set_role(GetUserId(), newOwnerId);

    /* New owner must have CREATE privilege on namespace */
    aclresult = object_aclcheck(NamespaceRelationId, namespaceOid, newOwnerId,
           ACL_CREATE);
    if (aclresult != ACLCHECK_OK)
     aclcheck_error(aclresult, OBJECT_SCHEMA,
           get_namespace_name(namespaceOid));
   }
  }

  memset(repl_null, falsesizeof(repl_null));
  memset(repl_repl, falsesizeof(repl_repl));

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5 in Prozent
C=93 H=95 G=93

¤ Dauer der Verarbeitung: 3.937 Sekunden  (vorverarbeitet am  2026-08-08) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.