/*-------------------------------------------------------------------------
*
* tablecmds.c
* Commands for creating and altering table structures and settings
*
* Portions Copyright (c) 1996-2024, 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);
static List *MergeCheckConstraint(List *constraints, const char *name, Node *expr);
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(Relation rel, AlterTableCmd *cmd,
bool recurse, bool recursing, LOCKMODE lockmode);
static bool ATExecAlterConstrRecurse(Constraint *cmdcon, Relation conrel, Relation tgrel,
Relation rel, HeapTuple contuple, List **otherrelids,
LOCKMODE lockmode);
static ObjectAddress ATExecValidateConstraint(List **wqueue,
Relation rel, char *constrName,
bool recurse, bool recursing, LOCKMODE lockmode);
static int transformColumnNameList(Oid relId, List *colList,
int16 *attnums, Oid *atttypids);
static int transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
List **attnamelist,
int16 *attnums, Oid *atttypids,
Oid *opclasses, 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, LOCKMODE lockmode);
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, LOCKMODE lockmode);
static void ATPrepSetNotNull(List **wqueue, Relation rel,
AlterTableCmd *cmd, bool recurse, bool recursing,
LOCKMODE lockmode,
AlterTableUtilityContext *context);
static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
const char *colName, LOCKMODE lockmode);
static void ATExecCheckNotNull(AlteredTableInfo *tab, Relation rel,
const char *colName, 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 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 ATAddCheckConstraint(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 void validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
int numfksetcols, const 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(Relation rel, Oid refRelOid,
Constraint *fkconstraint, Oid constraintOid,
Oid indexOid,
Oid parentDelTrigger, Oid parentUpdTrigger,
Oid *deleteTrigOid, Oid *updateTrigOid);
static bool tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
Oid partRelid,
Oid parentConstrOid, int numfks,
AttrNumber *mapped_conkey, AttrNumber *confkey,
Oid *conpfeqop,
Oid parentInsTrigger,
Oid parentUpdTrigger,
Relation trigrel);
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 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 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 *rawDefaults;
List *cookedDefaults;
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,
true, false);
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);
/*
* Create a tuple descriptor from the relation schema. Note that this
* deals with column names, types, and not-null constraints, but not
* default values or CHECK constraints; we handle those below.
*/
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.)
*
* We can set the atthasdef flags now in the tuple descriptor; this just
* saves StoreAttrDefault from having to do an immediate update of the
* pg_attribute rows.
*/
rawDefaults = NIL;
cookedDefaults = NIL;
attnum = 0;
foreach(listptr, stmt->tableElts)
{
ColumnDef *colDef = lfirst(listptr);
Form_pg_attribute attr;
attnum++;
attr = TupleDescAttr(descriptor, attnum - 1);
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->missingMode = false;
rawEnt->generated = colDef->generated;
rawDefaults = lappend(rawDefaults, rawEnt);
attr->atthasdef = true;
}
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->skip_validation = false;
cooked->is_local = true; /* not used for defaults */
cooked->inhcount = 0; /* ditto */
cooked->is_no_inherit = false;
cookedDefaults = lappend(cookedDefaults, cooked);
attr->atthasdef = true;
}
}
/*
* 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 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,
true, true, false, 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, false, false);
addNSItemToQuery(pstate, nsitem, false, true, true);
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,
false, false, false, false, false);
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.
*/
if (stmt->constraints)
AddRelationNewConstraints(rel, NIL, stmt->constraints,
true, true, false, queryString);
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);
}
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,
(void *) &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, 0, sizeof(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((char *) &xlrec, SizeOfHeapTruncate);
XLogRegisterData((char *) 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 constraints belonging to the parents,
* updated as necessary to be valid for the child.
*
* Return value:
* Completed schema list.
*
* Notes:
* The order in which the attributes are inherited is very important.
* Intuitively, the inherited attributes should come first. If a table
* inherits from multiple parents, the order of those attributes are
* according to the order of the parents specified in CREATE TABLE.
*
* Here's an example:
*
* create table person (name text, age int4, location point);
* create table emp (salary int4, manager text) inherits(person);
* create table student (gpa float8) inherits (person);
* create table stud_emp (percent int4) inherits (emp, student);
*
* The order of the attributes of stud_emp is:
*
* person {1:name, 2:age, 3:location}
* / \
* {6:gpa} student emp {4:salary, 5:manager}
* \ /
* stud_emp {7:percent}
*
* If the same attribute name appears multiple times, then it appears
* in the result table in the proper location for its first appearance.
*
* Constraints (including not-null constraints) for the child table
* are the union of all relevant constraints, from both the child schema
* and parent tables.
*
* The default value for a child column is defined as:
* (1) If the child schema specifies a default, that value is used.
* (2) If neither the child nor any parent specifies a default, then
* the column will not have a default.
* (3) If conflicting defaults are inherited from different parents
* (and not overridden by the child), an error is raised.
* (4) Otherwise the inherited default is used.
*
* 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 *inh_columns = NIL;
List *constraints = 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;
ListCell *lc1;
ListCell *lc2;
/* 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;
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->is_not_null = attribute->attnotnull;
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;
}
/*
* 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,
1, 0,
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),
1, 0,
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);
}
}
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, we merge parent's not-null constraints and
* defaults into each corresponding column definition.
*/
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;
coldef->is_not_null |= restdef->is_not_null;
/*
* 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.")));
}
/*
* 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;
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)
{
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"));
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;
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 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))));
/*
* Merge of not-null constraints = OR 'em together
*/
prevdef->is_not_null
|