| 1 | /*------------------------------------------------------------------------- |
| 2 | * |
| 3 | * tablecmds.c |
| 4 | * Commands for creating and altering table structures and settings |
| 5 | * |
| 6 | * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group |
| 7 | * Portions Copyright (c) 1994, Regents of the University of California |
| 8 | * |
| 9 | * |
| 10 | * IDENTIFICATION |
| 11 | * src/backend/commands/tablecmds.c |
| 12 | * |
| 13 | *------------------------------------------------------------------------- |
| 14 | */ |
| 15 | #include "postgres.h" |
| 16 | |
| 17 | #include "access/genam.h" |
| 18 | #include "access/heapam.h" |
| 19 | #include "access/heapam_xlog.h" |
| 20 | #include "access/multixact.h" |
| 21 | #include "access/reloptions.h" |
| 22 | #include "access/relscan.h" |
| 23 | #include "access/tableam.h" |
| 24 | #include "access/sysattr.h" |
| 25 | #include "access/tableam.h" |
| 26 | #include "access/tupconvert.h" |
| 27 | #include "access/xact.h" |
| 28 | #include "access/xlog.h" |
| 29 | #include "catalog/catalog.h" |
| 30 | #include "catalog/dependency.h" |
| 31 | #include "catalog/heap.h" |
| 32 | #include "catalog/index.h" |
| 33 | #include "catalog/indexing.h" |
| 34 | #include "catalog/namespace.h" |
| 35 | #include "catalog/objectaccess.h" |
| 36 | #include "catalog/partition.h" |
| 37 | #include "catalog/pg_am.h" |
| 38 | #include "catalog/pg_collation.h" |
| 39 | #include "catalog/pg_constraint.h" |
| 40 | #include "catalog/pg_depend.h" |
| 41 | #include "catalog/pg_foreign_table.h" |
| 42 | #include "catalog/pg_inherits.h" |
| 43 | #include "catalog/pg_namespace.h" |
| 44 | #include "catalog/pg_opclass.h" |
| 45 | #include "catalog/pg_tablespace.h" |
| 46 | #include "catalog/pg_trigger.h" |
| 47 | #include "catalog/pg_type.h" |
| 48 | #include "catalog/storage.h" |
| 49 | #include "catalog/storage_xlog.h" |
| 50 | #include "catalog/toasting.h" |
| 51 | #include "commands/cluster.h" |
| 52 | #include "commands/comment.h" |
| 53 | #include "commands/defrem.h" |
| 54 | #include "commands/event_trigger.h" |
| 55 | #include "commands/policy.h" |
| 56 | #include "commands/sequence.h" |
| 57 | #include "commands/tablecmds.h" |
| 58 | #include "commands/tablespace.h" |
| 59 | #include "commands/trigger.h" |
| 60 | #include "commands/typecmds.h" |
| 61 | #include "commands/user.h" |
| 62 | #include "executor/executor.h" |
| 63 | #include "foreign/foreign.h" |
| 64 | #include "miscadmin.h" |
| 65 | #include "nodes/makefuncs.h" |
| 66 | #include "nodes/nodeFuncs.h" |
| 67 | #include "nodes/parsenodes.h" |
| 68 | #include "optimizer/optimizer.h" |
| 69 | #include "parser/parse_clause.h" |
| 70 | #include "parser/parse_coerce.h" |
| 71 | #include "parser/parse_collate.h" |
| 72 | #include "parser/parse_expr.h" |
| 73 | #include "parser/parse_oper.h" |
| 74 | #include "parser/parse_relation.h" |
| 75 | #include "parser/parse_type.h" |
| 76 | #include "parser/parse_utilcmd.h" |
| 77 | #include "parser/parser.h" |
| 78 | #include "partitioning/partbounds.h" |
| 79 | #include "partitioning/partdesc.h" |
| 80 | #include "pgstat.h" |
| 81 | #include "rewrite/rewriteDefine.h" |
| 82 | #include "rewrite/rewriteHandler.h" |
| 83 | #include "rewrite/rewriteManip.h" |
| 84 | #include "storage/bufmgr.h" |
| 85 | #include "storage/lmgr.h" |
| 86 | #include "storage/lock.h" |
| 87 | #include "storage/predicate.h" |
| 88 | #include "storage/smgr.h" |
| 89 | #include "utils/acl.h" |
| 90 | #include "utils/builtins.h" |
| 91 | #include "utils/fmgroids.h" |
| 92 | #include "utils/inval.h" |
| 93 | #include "utils/lsyscache.h" |
| 94 | #include "utils/memutils.h" |
| 95 | #include "utils/partcache.h" |
| 96 | #include "utils/relcache.h" |
| 97 | #include "utils/ruleutils.h" |
| 98 | #include "utils/snapmgr.h" |
| 99 | #include "utils/syscache.h" |
| 100 | #include "utils/timestamp.h" |
| 101 | #include "utils/typcache.h" |
| 102 | |
| 103 | |
| 104 | /* |
| 105 | * ON COMMIT action list |
| 106 | */ |
| 107 | typedef struct OnCommitItem |
| 108 | { |
| 109 | Oid relid; /* relid of relation */ |
| 110 | OnCommitAction oncommit; /* what to do at end of xact */ |
| 111 | |
| 112 | /* |
| 113 | * If this entry was created during the current transaction, |
| 114 | * creating_subid is the ID of the creating subxact; if created in a prior |
| 115 | * transaction, creating_subid is zero. If deleted during the current |
| 116 | * transaction, deleting_subid is the ID of the deleting subxact; if no |
| 117 | * deletion request is pending, deleting_subid is zero. |
| 118 | */ |
| 119 | SubTransactionId creating_subid; |
| 120 | SubTransactionId deleting_subid; |
| 121 | } OnCommitItem; |
| 122 | |
| 123 | static List *on_commits = NIL; |
| 124 | |
| 125 | |
| 126 | /* |
| 127 | * State information for ALTER TABLE |
| 128 | * |
| 129 | * The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo |
| 130 | * structs, one for each table modified by the operation (the named table |
| 131 | * plus any child tables that are affected). We save lists of subcommands |
| 132 | * to apply to this table (possibly modified by parse transformation steps); |
| 133 | * these lists will be executed in Phase 2. If a Phase 3 step is needed, |
| 134 | * necessary information is stored in the constraints and newvals lists. |
| 135 | * |
| 136 | * Phase 2 is divided into multiple passes; subcommands are executed in |
| 137 | * a pass determined by subcommand type. |
| 138 | */ |
| 139 | |
| 140 | #define AT_PASS_UNSET -1 /* UNSET will cause ERROR */ |
| 141 | #define AT_PASS_DROP 0 /* DROP (all flavors) */ |
| 142 | #define AT_PASS_ALTER_TYPE 1 /* ALTER COLUMN TYPE */ |
| 143 | #define AT_PASS_OLD_INDEX 2 /* re-add existing indexes */ |
| 144 | #define AT_PASS_OLD_CONSTR 3 /* re-add existing constraints */ |
| 145 | /* We could support a RENAME COLUMN pass here, but not currently used */ |
| 146 | #define AT_PASS_ADD_COL 4 /* ADD COLUMN */ |
| 147 | #define AT_PASS_COL_ATTRS 5 /* set other column attributes */ |
| 148 | #define AT_PASS_ADD_INDEX 6 /* ADD indexes */ |
| 149 | #define AT_PASS_ADD_CONSTR 7 /* ADD constraints, defaults */ |
| 150 | #define AT_PASS_MISC 8 /* other stuff */ |
| 151 | #define AT_NUM_PASSES 9 |
| 152 | |
| 153 | typedef struct AlteredTableInfo |
| 154 | { |
| 155 | /* Information saved before any work commences: */ |
| 156 | Oid relid; /* Relation to work on */ |
| 157 | char relkind; /* Its relkind */ |
| 158 | TupleDesc oldDesc; /* Pre-modification tuple descriptor */ |
| 159 | /* Information saved by Phase 1 for Phase 2: */ |
| 160 | List *subcmds[AT_NUM_PASSES]; /* Lists of AlterTableCmd */ |
| 161 | /* Information saved by Phases 1/2 for Phase 3: */ |
| 162 | List *constraints; /* List of NewConstraint */ |
| 163 | List *newvals; /* List of NewColumnValue */ |
| 164 | bool verify_new_notnull; /* T if we should recheck NOT NULL */ |
| 165 | int rewrite; /* Reason for forced rewrite, if any */ |
| 166 | Oid newTableSpace; /* new tablespace; 0 means no change */ |
| 167 | bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */ |
| 168 | char newrelpersistence; /* if above is true */ |
| 169 | Expr *partition_constraint; /* for attach partition validation */ |
| 170 | /* true, if validating default due to some other attach/detach */ |
| 171 | bool validate_default; |
| 172 | /* Objects to rebuild after completing ALTER TYPE operations */ |
| 173 | List *changedConstraintOids; /* OIDs of constraints to rebuild */ |
| 174 | List *changedConstraintDefs; /* string definitions of same */ |
| 175 | List *changedIndexOids; /* OIDs of indexes to rebuild */ |
| 176 | List *changedIndexDefs; /* string definitions of same */ |
| 177 | } AlteredTableInfo; |
| 178 | |
| 179 | /* Struct describing one new constraint to check in Phase 3 scan */ |
| 180 | /* Note: new NOT NULL constraints are handled elsewhere */ |
| 181 | typedef struct NewConstraint |
| 182 | { |
| 183 | char *name; /* Constraint name, or NULL if none */ |
| 184 | ConstrType contype; /* CHECK or FOREIGN */ |
| 185 | Oid refrelid; /* PK rel, if FOREIGN */ |
| 186 | Oid refindid; /* OID of PK's index, if FOREIGN */ |
| 187 | Oid conid; /* OID of pg_constraint entry, if FOREIGN */ |
| 188 | Node *qual; /* Check expr or CONSTR_FOREIGN Constraint */ |
| 189 | ExprState *qualstate; /* Execution state for CHECK expr */ |
| 190 | } NewConstraint; |
| 191 | |
| 192 | /* |
| 193 | * Struct describing one new column value that needs to be computed during |
| 194 | * Phase 3 copy (this could be either a new column with a non-null default, or |
| 195 | * a column that we're changing the type of). Columns without such an entry |
| 196 | * are just copied from the old table during ATRewriteTable. Note that the |
| 197 | * expr is an expression over *old* table values. |
| 198 | */ |
| 199 | typedef struct NewColumnValue |
| 200 | { |
| 201 | AttrNumber attnum; /* which column */ |
| 202 | Expr *expr; /* expression to compute */ |
| 203 | ExprState *exprstate; /* execution state */ |
| 204 | } NewColumnValue; |
| 205 | |
| 206 | /* |
| 207 | * Error-reporting support for RemoveRelations |
| 208 | */ |
| 209 | struct dropmsgstrings |
| 210 | { |
| 211 | char kind; |
| 212 | int nonexistent_code; |
| 213 | const char *nonexistent_msg; |
| 214 | const char *skipping_msg; |
| 215 | const char *nota_msg; |
| 216 | const char *drophint_msg; |
| 217 | }; |
| 218 | |
| 219 | static const struct dropmsgstrings dropmsgstringarray[] = { |
| 220 | {RELKIND_RELATION, |
| 221 | ERRCODE_UNDEFINED_TABLE, |
| 222 | gettext_noop("table \"%s\" does not exist" ), |
| 223 | gettext_noop("table \"%s\" does not exist, skipping" ), |
| 224 | gettext_noop("\"%s\" is not a table" ), |
| 225 | gettext_noop("Use DROP TABLE to remove a table." )}, |
| 226 | {RELKIND_SEQUENCE, |
| 227 | ERRCODE_UNDEFINED_TABLE, |
| 228 | gettext_noop("sequence \"%s\" does not exist" ), |
| 229 | gettext_noop("sequence \"%s\" does not exist, skipping" ), |
| 230 | gettext_noop("\"%s\" is not a sequence" ), |
| 231 | gettext_noop("Use DROP SEQUENCE to remove a sequence." )}, |
| 232 | {RELKIND_VIEW, |
| 233 | ERRCODE_UNDEFINED_TABLE, |
| 234 | gettext_noop("view \"%s\" does not exist" ), |
| 235 | gettext_noop("view \"%s\" does not exist, skipping" ), |
| 236 | gettext_noop("\"%s\" is not a view" ), |
| 237 | gettext_noop("Use DROP VIEW to remove a view." )}, |
| 238 | {RELKIND_MATVIEW, |
| 239 | ERRCODE_UNDEFINED_TABLE, |
| 240 | gettext_noop("materialized view \"%s\" does not exist" ), |
| 241 | gettext_noop("materialized view \"%s\" does not exist, skipping" ), |
| 242 | gettext_noop("\"%s\" is not a materialized view" ), |
| 243 | gettext_noop("Use DROP MATERIALIZED VIEW to remove a materialized view." )}, |
| 244 | {RELKIND_INDEX, |
| 245 | ERRCODE_UNDEFINED_OBJECT, |
| 246 | gettext_noop("index \"%s\" does not exist" ), |
| 247 | gettext_noop("index \"%s\" does not exist, skipping" ), |
| 248 | gettext_noop("\"%s\" is not an index" ), |
| 249 | gettext_noop("Use DROP INDEX to remove an index." )}, |
| 250 | {RELKIND_COMPOSITE_TYPE, |
| 251 | ERRCODE_UNDEFINED_OBJECT, |
| 252 | gettext_noop("type \"%s\" does not exist" ), |
| 253 | gettext_noop("type \"%s\" does not exist, skipping" ), |
| 254 | gettext_noop("\"%s\" is not a type" ), |
| 255 | gettext_noop("Use DROP TYPE to remove a type." )}, |
| 256 | {RELKIND_FOREIGN_TABLE, |
| 257 | ERRCODE_UNDEFINED_OBJECT, |
| 258 | gettext_noop("foreign table \"%s\" does not exist" ), |
| 259 | gettext_noop("foreign table \"%s\" does not exist, skipping" ), |
| 260 | gettext_noop("\"%s\" is not a foreign table" ), |
| 261 | gettext_noop("Use DROP FOREIGN TABLE to remove a foreign table." )}, |
| 262 | {RELKIND_PARTITIONED_TABLE, |
| 263 | ERRCODE_UNDEFINED_TABLE, |
| 264 | gettext_noop("table \"%s\" does not exist" ), |
| 265 | gettext_noop("table \"%s\" does not exist, skipping" ), |
| 266 | gettext_noop("\"%s\" is not a table" ), |
| 267 | gettext_noop("Use DROP TABLE to remove a table." )}, |
| 268 | {RELKIND_PARTITIONED_INDEX, |
| 269 | ERRCODE_UNDEFINED_OBJECT, |
| 270 | gettext_noop("index \"%s\" does not exist" ), |
| 271 | gettext_noop("index \"%s\" does not exist, skipping" ), |
| 272 | gettext_noop("\"%s\" is not an index" ), |
| 273 | gettext_noop("Use DROP INDEX to remove an index." )}, |
| 274 | {'\0', 0, NULL, NULL, NULL, NULL} |
| 275 | }; |
| 276 | |
| 277 | struct DropRelationCallbackState |
| 278 | { |
| 279 | char relkind; |
| 280 | Oid heapOid; |
| 281 | Oid partParentOid; |
| 282 | bool concurrent; |
| 283 | }; |
| 284 | |
| 285 | /* Alter table target-type flags for ATSimplePermissions */ |
| 286 | #define ATT_TABLE 0x0001 |
| 287 | #define ATT_VIEW 0x0002 |
| 288 | #define ATT_MATVIEW 0x0004 |
| 289 | #define ATT_INDEX 0x0008 |
| 290 | #define ATT_COMPOSITE_TYPE 0x0010 |
| 291 | #define ATT_FOREIGN_TABLE 0x0020 |
| 292 | #define ATT_PARTITIONED_INDEX 0x0040 |
| 293 | |
| 294 | /* |
| 295 | * Partition tables are expected to be dropped when the parent partitioned |
| 296 | * table gets dropped. Hence for partitioning we use AUTO dependency. |
| 297 | * Otherwise, for regular inheritance use NORMAL dependency. |
| 298 | */ |
| 299 | #define child_dependency_type(child_is_partition) \ |
| 300 | ((child_is_partition) ? DEPENDENCY_AUTO : DEPENDENCY_NORMAL) |
| 301 | |
| 302 | static void truncate_check_rel(Oid relid, Form_pg_class reltuple); |
| 303 | static void truncate_check_activity(Relation rel); |
| 304 | static void RangeVarCallbackForTruncate(const RangeVar *relation, |
| 305 | Oid relId, Oid oldRelId, void *arg); |
| 306 | static List *MergeAttributes(List *schema, List *supers, char relpersistence, |
| 307 | bool is_partition, List **supconstr); |
| 308 | static bool MergeCheckConstraint(List *constraints, char *name, Node *expr); |
| 309 | static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel); |
| 310 | static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel); |
| 311 | static void StoreCatalogInheritance(Oid relationId, List *supers, |
| 312 | bool child_is_partition); |
| 313 | static void StoreCatalogInheritance1(Oid relationId, Oid parentOid, |
| 314 | int32 seqNumber, Relation inhRelation, |
| 315 | bool child_is_partition); |
| 316 | static int findAttrByName(const char *attributeName, List *schema); |
| 317 | static void AlterIndexNamespaces(Relation classRel, Relation rel, |
| 318 | Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved); |
| 319 | static void AlterSeqNamespaces(Relation classRel, Relation rel, |
| 320 | Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved, |
| 321 | LOCKMODE lockmode); |
| 322 | static ObjectAddress ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd, |
| 323 | bool recurse, bool recursing, LOCKMODE lockmode); |
| 324 | static ObjectAddress ATExecValidateConstraint(Relation rel, char *constrName, |
| 325 | bool recurse, bool recursing, LOCKMODE lockmode); |
| 326 | static int transformColumnNameList(Oid relId, List *colList, |
| 327 | int16 *attnums, Oid *atttypids); |
| 328 | static int transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid, |
| 329 | List **attnamelist, |
| 330 | int16 *attnums, Oid *atttypids, |
| 331 | Oid *opclasses); |
| 332 | static Oid transformFkeyCheckAttrs(Relation pkrel, |
| 333 | int numattrs, int16 *attnums, |
| 334 | Oid *opclasses); |
| 335 | static void checkFkeyPermissions(Relation rel, int16 *attnums, int natts); |
| 336 | static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId, |
| 337 | Oid *funcid); |
| 338 | static void validateCheckConstraint(Relation rel, HeapTuple constrtup); |
| 339 | static void validateForeignKeyConstraint(char *conname, |
| 340 | Relation rel, Relation pkrel, |
| 341 | Oid pkindOid, Oid constraintOid); |
| 342 | static void ATController(AlterTableStmt *parsetree, |
| 343 | Relation rel, List *cmds, bool recurse, LOCKMODE lockmode); |
| 344 | static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, |
| 345 | bool recurse, bool recursing, LOCKMODE lockmode); |
| 346 | static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode); |
| 347 | static void ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, |
| 348 | AlterTableCmd *cmd, LOCKMODE lockmode); |
| 349 | static void ATRewriteTables(AlterTableStmt *parsetree, |
| 350 | List **wqueue, LOCKMODE lockmode); |
| 351 | static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode); |
| 352 | static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel); |
| 353 | static void ATSimplePermissions(Relation rel, int allowed_targets); |
| 354 | static void ATWrongRelkindError(Relation rel, int allowed_targets); |
| 355 | static void ATSimpleRecursion(List **wqueue, Relation rel, |
| 356 | AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode); |
| 357 | static void ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode); |
| 358 | static void ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd, |
| 359 | LOCKMODE lockmode); |
| 360 | static List *find_typed_table_dependencies(Oid typeOid, const char *typeName, |
| 361 | DropBehavior behavior); |
| 362 | static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing, |
| 363 | bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode); |
| 364 | static ObjectAddress ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, |
| 365 | Relation rel, ColumnDef *colDef, |
| 366 | bool recurse, bool recursing, |
| 367 | bool if_not_exists, LOCKMODE lockmode); |
| 368 | static bool check_for_column_name_collision(Relation rel, const char *colname, |
| 369 | bool if_not_exists); |
| 370 | static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid); |
| 371 | static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid); |
| 372 | static void ATPrepDropNotNull(Relation rel, bool recurse, bool recursing); |
| 373 | static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode); |
| 374 | static void ATPrepSetNotNull(List **wqueue, Relation rel, |
| 375 | AlterTableCmd *cmd, bool recurse, bool recursing, |
| 376 | LOCKMODE lockmode); |
| 377 | static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel, |
| 378 | const char *colName, LOCKMODE lockmode); |
| 379 | static void ATExecCheckNotNull(AlteredTableInfo *tab, Relation rel, |
| 380 | const char *colName, LOCKMODE lockmode); |
| 381 | static bool NotNullImpliedByRelConstraints(Relation rel, Form_pg_attribute attr); |
| 382 | static bool ConstraintImpliedByRelConstraint(Relation scanrel, |
| 383 | List *testConstraint, List *provenConstraint); |
| 384 | static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName, |
| 385 | Node *newDefault, LOCKMODE lockmode); |
| 386 | static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName, |
| 387 | Node *def, LOCKMODE lockmode); |
| 388 | static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName, |
| 389 | Node *def, LOCKMODE lockmode); |
| 390 | static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode); |
| 391 | static void ATPrepSetStatistics(Relation rel, const char *colName, int16 colNum, |
| 392 | Node *newValue, LOCKMODE lockmode); |
| 393 | static ObjectAddress ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, |
| 394 | Node *newValue, LOCKMODE lockmode); |
| 395 | static ObjectAddress ATExecSetOptions(Relation rel, const char *colName, |
| 396 | Node *options, bool isReset, LOCKMODE lockmode); |
| 397 | static ObjectAddress ATExecSetStorage(Relation rel, const char *colName, |
| 398 | Node *newValue, LOCKMODE lockmode); |
| 399 | static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing, |
| 400 | AlterTableCmd *cmd, LOCKMODE lockmode); |
| 401 | static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName, |
| 402 | DropBehavior behavior, |
| 403 | bool recurse, bool recursing, |
| 404 | bool missing_ok, LOCKMODE lockmode); |
| 405 | static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel, |
| 406 | IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode); |
| 407 | static ObjectAddress ATExecAddConstraint(List **wqueue, |
| 408 | AlteredTableInfo *tab, Relation rel, |
| 409 | Constraint *newConstraint, bool recurse, bool is_readd, |
| 410 | LOCKMODE lockmode); |
| 411 | static char *ChooseForeignKeyConstraintNameAddition(List *colnames); |
| 412 | static ObjectAddress ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel, |
| 413 | IndexStmt *stmt, LOCKMODE lockmode); |
| 414 | static ObjectAddress ATAddCheckConstraint(List **wqueue, |
| 415 | AlteredTableInfo *tab, Relation rel, |
| 416 | Constraint *constr, |
| 417 | bool recurse, bool recursing, bool is_readd, |
| 418 | LOCKMODE lockmode); |
| 419 | static ObjectAddress ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, |
| 420 | Relation rel, Constraint *fkconstraint, Oid parentConstr, |
| 421 | bool recurse, bool recursing, |
| 422 | LOCKMODE lockmode); |
| 423 | static ObjectAddress addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, |
| 424 | Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr, |
| 425 | int numfks, int16 *pkattnum, int16 *fkattnum, |
| 426 | Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators, |
| 427 | bool old_check_ok); |
| 428 | static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, |
| 429 | Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr, |
| 430 | int numfks, int16 *pkattnum, int16 *fkattnum, |
| 431 | Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators, |
| 432 | bool old_check_ok, LOCKMODE lockmode); |
| 433 | static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel, |
| 434 | Relation partitionRel); |
| 435 | static void CloneFkReferenced(Relation parentRel, Relation partitionRel); |
| 436 | static void CloneFkReferencing(List **wqueue, Relation parentRel, |
| 437 | Relation partRel); |
| 438 | static void createForeignKeyCheckTriggers(Oid myRelOid, Oid refRelOid, |
| 439 | Constraint *fkconstraint, Oid constraintOid, |
| 440 | Oid indexOid); |
| 441 | static void createForeignKeyActionTriggers(Relation rel, Oid refRelOid, |
| 442 | Constraint *fkconstraint, Oid constraintOid, |
| 443 | Oid indexOid); |
| 444 | static bool tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk, |
| 445 | Oid partRelid, |
| 446 | Oid parentConstrOid, int numfks, |
| 447 | AttrNumber *mapped_conkey, AttrNumber *confkey, |
| 448 | Oid *conpfeqop); |
| 449 | static void ATExecDropConstraint(Relation rel, const char *constrName, |
| 450 | DropBehavior behavior, |
| 451 | bool recurse, bool recursing, |
| 452 | bool missing_ok, LOCKMODE lockmode); |
| 453 | static void ATPrepAlterColumnType(List **wqueue, |
| 454 | AlteredTableInfo *tab, Relation rel, |
| 455 | bool recurse, bool recursing, |
| 456 | AlterTableCmd *cmd, LOCKMODE lockmode); |
| 457 | static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno); |
| 458 | static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, |
| 459 | AlterTableCmd *cmd, LOCKMODE lockmode); |
| 460 | static void RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab); |
| 461 | static void RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab); |
| 462 | static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, |
| 463 | LOCKMODE lockmode); |
| 464 | static void ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, |
| 465 | char *cmd, List **wqueue, LOCKMODE lockmode, |
| 466 | bool rewrite); |
| 467 | static void RebuildConstraintComment(AlteredTableInfo *tab, int pass, |
| 468 | Oid objid, Relation rel, List *domname, |
| 469 | const char *conname); |
| 470 | static void TryReuseIndex(Oid oldId, IndexStmt *stmt); |
| 471 | static void TryReuseForeignKey(Oid oldId, Constraint *con); |
| 472 | static ObjectAddress ATExecAlterColumnGenericOptions(Relation rel, const char *colName, |
| 473 | List *options, LOCKMODE lockmode); |
| 474 | static void change_owner_fix_column_acls(Oid relationOid, |
| 475 | Oid oldOwnerId, Oid newOwnerId); |
| 476 | static void change_owner_recurse_to_sequences(Oid relationOid, |
| 477 | Oid newOwnerId, LOCKMODE lockmode); |
| 478 | static ObjectAddress ATExecClusterOn(Relation rel, const char *indexName, |
| 479 | LOCKMODE lockmode); |
| 480 | static void ATExecDropCluster(Relation rel, LOCKMODE lockmode); |
| 481 | static bool ATPrepChangePersistence(Relation rel, bool toLogged); |
| 482 | static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, |
| 483 | const char *tablespacename, LOCKMODE lockmode); |
| 484 | static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode); |
| 485 | static void ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace); |
| 486 | static void ATExecSetRelOptions(Relation rel, List *defList, |
| 487 | AlterTableType operation, |
| 488 | LOCKMODE lockmode); |
| 489 | static void ATExecEnableDisableTrigger(Relation rel, const char *trigname, |
| 490 | char fires_when, bool skip_system, LOCKMODE lockmode); |
| 491 | static void ATExecEnableDisableRule(Relation rel, const char *rulename, |
| 492 | char fires_when, LOCKMODE lockmode); |
| 493 | static void ATPrepAddInherit(Relation child_rel); |
| 494 | static ObjectAddress ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode); |
| 495 | static ObjectAddress ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode); |
| 496 | static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid, |
| 497 | DependencyType deptype); |
| 498 | static ObjectAddress ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode); |
| 499 | static void ATExecDropOf(Relation rel, LOCKMODE lockmode); |
| 500 | static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode); |
| 501 | static void ATExecGenericOptions(Relation rel, List *options); |
| 502 | static void ATExecEnableRowSecurity(Relation rel); |
| 503 | static void ATExecDisableRowSecurity(Relation rel); |
| 504 | static void ATExecForceNoForceRowSecurity(Relation rel, bool force_rls); |
| 505 | |
| 506 | static void index_copy_data(Relation rel, RelFileNode newrnode); |
| 507 | static const char *storage_name(char c); |
| 508 | |
| 509 | static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, |
| 510 | Oid oldRelOid, void *arg); |
| 511 | static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, |
| 512 | Oid oldrelid, void *arg); |
| 513 | static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy); |
| 514 | static void ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNumber *partattrs, |
| 515 | List **partexprs, Oid *partopclass, Oid *partcollation, char strategy); |
| 516 | static void CreateInheritance(Relation child_rel, Relation parent_rel); |
| 517 | static void RemoveInheritance(Relation child_rel, Relation parent_rel); |
| 518 | static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel, |
| 519 | PartitionCmd *cmd); |
| 520 | static void AttachPartitionEnsureIndexes(Relation rel, Relation attachrel); |
| 521 | static void QueuePartitionConstraintValidation(List **wqueue, Relation scanrel, |
| 522 | List *partConstraint, |
| 523 | bool validate_default); |
| 524 | static void CloneRowTriggersToPartition(Relation parent, Relation partition); |
| 525 | static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name); |
| 526 | static ObjectAddress ATExecAttachPartitionIdx(List **wqueue, Relation rel, |
| 527 | RangeVar *name); |
| 528 | static void validatePartitionedIndex(Relation partedIdx, Relation partedTbl); |
| 529 | static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx, |
| 530 | Relation partitionTbl); |
| 531 | static List *GetParentedForeignKeyRefs(Relation partition); |
| 532 | static void ATDetachCheckNoForeignKeyRefs(Relation partition); |
| 533 | |
| 534 | |
| 535 | /* ---------------------------------------------------------------- |
| 536 | * DefineRelation |
| 537 | * Creates a new relation. |
| 538 | * |
| 539 | * stmt carries parsetree information from an ordinary CREATE TABLE statement. |
| 540 | * The other arguments are used to extend the behavior for other cases: |
| 541 | * relkind: relkind to assign to the new relation |
| 542 | * ownerId: if not InvalidOid, use this as the new relation's owner. |
| 543 | * typaddress: if not null, it's set to the pg_type entry's address. |
| 544 | * queryString: for error reporting |
| 545 | * |
| 546 | * Note that permissions checks are done against current user regardless of |
| 547 | * ownerId. A nonzero ownerId is used when someone is creating a relation |
| 548 | * "on behalf of" someone else, so we still want to see that the current user |
| 549 | * has permissions to do it. |
| 550 | * |
| 551 | * If successful, returns the address of the new relation. |
| 552 | * ---------------------------------------------------------------- |
| 553 | */ |
| 554 | ObjectAddress |
| 555 | DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, |
| 556 | ObjectAddress *typaddress, const char *queryString) |
| 557 | { |
| 558 | char relname[NAMEDATALEN]; |
| 559 | Oid namespaceId; |
| 560 | Oid relationId; |
| 561 | Oid tablespaceId; |
| 562 | Relation rel; |
| 563 | TupleDesc descriptor; |
| 564 | List *inheritOids; |
| 565 | List *old_constraints; |
| 566 | List *rawDefaults; |
| 567 | List *cookedDefaults; |
| 568 | Datum reloptions; |
| 569 | ListCell *listptr; |
| 570 | AttrNumber attnum; |
| 571 | bool partitioned; |
| 572 | static char *validnsps[] = HEAP_RELOPT_NAMESPACES; |
| 573 | Oid ofTypeId; |
| 574 | ObjectAddress address; |
| 575 | LOCKMODE parentLockmode; |
| 576 | const char *accessMethod = NULL; |
| 577 | Oid accessMethodId = InvalidOid; |
| 578 | |
| 579 | /* |
| 580 | * Truncate relname to appropriate length (probably a waste of time, as |
| 581 | * parser should have done this already). |
| 582 | */ |
| 583 | StrNCpy(relname, stmt->relation->relname, NAMEDATALEN); |
| 584 | |
| 585 | /* |
| 586 | * Check consistency of arguments |
| 587 | */ |
| 588 | if (stmt->oncommit != ONCOMMIT_NOOP |
| 589 | && stmt->relation->relpersistence != RELPERSISTENCE_TEMP) |
| 590 | ereport(ERROR, |
| 591 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 592 | errmsg("ON COMMIT can only be used on temporary tables" ))); |
| 593 | |
| 594 | if (stmt->partspec != NULL) |
| 595 | { |
| 596 | if (relkind != RELKIND_RELATION) |
| 597 | elog(ERROR, "unexpected relkind: %d" , (int) relkind); |
| 598 | |
| 599 | relkind = RELKIND_PARTITIONED_TABLE; |
| 600 | partitioned = true; |
| 601 | } |
| 602 | else |
| 603 | partitioned = false; |
| 604 | |
| 605 | /* |
| 606 | * Look up the namespace in which we are supposed to create the relation, |
| 607 | * check we have permission to create there, lock it against concurrent |
| 608 | * drop, and mark stmt->relation as RELPERSISTENCE_TEMP if a temporary |
| 609 | * namespace is selected. |
| 610 | */ |
| 611 | namespaceId = |
| 612 | RangeVarGetAndCheckCreationNamespace(stmt->relation, NoLock, NULL); |
| 613 | |
| 614 | /* |
| 615 | * Security check: disallow creating temp tables from security-restricted |
| 616 | * code. This is needed because calling code might not expect untrusted |
| 617 | * tables to appear in pg_temp at the front of its search path. |
| 618 | */ |
| 619 | if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP |
| 620 | && InSecurityRestrictedOperation()) |
| 621 | ereport(ERROR, |
| 622 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| 623 | errmsg("cannot create temporary table within security-restricted operation" ))); |
| 624 | |
| 625 | /* |
| 626 | * Determine the lockmode to use when scanning parents. A self-exclusive |
| 627 | * lock is needed here. |
| 628 | * |
| 629 | * For regular inheritance, if two backends attempt to add children to the |
| 630 | * same parent simultaneously, and that parent has no pre-existing |
| 631 | * children, then both will attempt to update the parent's relhassubclass |
| 632 | * field, leading to a "tuple concurrently updated" error. Also, this |
| 633 | * interlocks against a concurrent ANALYZE on the parent table, which |
| 634 | * might otherwise be attempting to clear the parent's relhassubclass |
| 635 | * field, if its previous children were recently dropped. |
| 636 | * |
| 637 | * If the child table is a partition, then we instead grab an exclusive |
| 638 | * lock on the parent because its partition descriptor will be changed by |
| 639 | * addition of the new partition. |
| 640 | */ |
| 641 | parentLockmode = (stmt->partbound != NULL ? AccessExclusiveLock : |
| 642 | ShareUpdateExclusiveLock); |
| 643 | |
| 644 | /* Determine the list of OIDs of the parents. */ |
| 645 | inheritOids = NIL; |
| 646 | foreach(listptr, stmt->inhRelations) |
| 647 | { |
| 648 | RangeVar *rv = (RangeVar *) lfirst(listptr); |
| 649 | Oid parentOid; |
| 650 | |
| 651 | parentOid = RangeVarGetRelid(rv, parentLockmode, false); |
| 652 | |
| 653 | /* |
| 654 | * Reject duplications in the list of parents. |
| 655 | */ |
| 656 | if (list_member_oid(inheritOids, parentOid)) |
| 657 | ereport(ERROR, |
| 658 | (errcode(ERRCODE_DUPLICATE_TABLE), |
| 659 | errmsg("relation \"%s\" would be inherited from more than once" , |
| 660 | get_rel_name(parentOid)))); |
| 661 | |
| 662 | inheritOids = lappend_oid(inheritOids, parentOid); |
| 663 | } |
| 664 | |
| 665 | /* |
| 666 | * Select tablespace to use: an explicitly indicated one, or (in the case |
| 667 | * of a partitioned table) the parent's, if it has one. |
| 668 | */ |
| 669 | if (stmt->tablespacename) |
| 670 | { |
| 671 | tablespaceId = get_tablespace_oid(stmt->tablespacename, false); |
| 672 | |
| 673 | if (partitioned && tablespaceId == MyDatabaseTableSpace) |
| 674 | ereport(ERROR, |
| 675 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 676 | errmsg("cannot specify default tablespace for partitioned relations" ))); |
| 677 | } |
| 678 | else if (stmt->partbound) |
| 679 | { |
| 680 | /* |
| 681 | * For partitions, when no other tablespace is specified, we default |
| 682 | * the tablespace to the parent partitioned table's. |
| 683 | */ |
| 684 | Assert(list_length(inheritOids) == 1); |
| 685 | tablespaceId = get_rel_tablespace(linitial_oid(inheritOids)); |
| 686 | } |
| 687 | else |
| 688 | tablespaceId = InvalidOid; |
| 689 | |
| 690 | /* still nothing? use the default */ |
| 691 | if (!OidIsValid(tablespaceId)) |
| 692 | tablespaceId = GetDefaultTablespace(stmt->relation->relpersistence, |
| 693 | partitioned); |
| 694 | |
| 695 | /* Check permissions except when using database's default */ |
| 696 | if (OidIsValid(tablespaceId) && tablespaceId != MyDatabaseTableSpace) |
| 697 | { |
| 698 | AclResult aclresult; |
| 699 | |
| 700 | aclresult = pg_tablespace_aclcheck(tablespaceId, GetUserId(), |
| 701 | ACL_CREATE); |
| 702 | if (aclresult != ACLCHECK_OK) |
| 703 | aclcheck_error(aclresult, OBJECT_TABLESPACE, |
| 704 | get_tablespace_name(tablespaceId)); |
| 705 | } |
| 706 | |
| 707 | /* In all cases disallow placing user relations in pg_global */ |
| 708 | if (tablespaceId == GLOBALTABLESPACE_OID) |
| 709 | ereport(ERROR, |
| 710 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 711 | errmsg("only shared relations can be placed in pg_global tablespace" ))); |
| 712 | |
| 713 | /* Identify user ID that will own the table */ |
| 714 | if (!OidIsValid(ownerId)) |
| 715 | ownerId = GetUserId(); |
| 716 | |
| 717 | /* |
| 718 | * Parse and validate reloptions, if any. |
| 719 | */ |
| 720 | reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps, |
| 721 | true, false); |
| 722 | |
| 723 | if (relkind == RELKIND_VIEW) |
| 724 | (void) view_reloptions(reloptions, true); |
| 725 | else |
| 726 | (void) heap_reloptions(relkind, reloptions, true); |
| 727 | |
| 728 | if (stmt->ofTypename) |
| 729 | { |
| 730 | AclResult aclresult; |
| 731 | |
| 732 | ofTypeId = typenameTypeId(NULL, stmt->ofTypename); |
| 733 | |
| 734 | aclresult = pg_type_aclcheck(ofTypeId, GetUserId(), ACL_USAGE); |
| 735 | if (aclresult != ACLCHECK_OK) |
| 736 | aclcheck_error_type(aclresult, ofTypeId); |
| 737 | } |
| 738 | else |
| 739 | ofTypeId = InvalidOid; |
| 740 | |
| 741 | /* |
| 742 | * Look up inheritance ancestors and generate relation schema, including |
| 743 | * inherited attributes. (Note that stmt->tableElts is destructively |
| 744 | * modified by MergeAttributes.) |
| 745 | */ |
| 746 | stmt->tableElts = |
| 747 | MergeAttributes(stmt->tableElts, inheritOids, |
| 748 | stmt->relation->relpersistence, |
| 749 | stmt->partbound != NULL, |
| 750 | &old_constraints); |
| 751 | |
| 752 | /* |
| 753 | * Create a tuple descriptor from the relation schema. Note that this |
| 754 | * deals with column names, types, and NOT NULL constraints, but not |
| 755 | * default values or CHECK constraints; we handle those below. |
| 756 | */ |
| 757 | descriptor = BuildDescForRelation(stmt->tableElts); |
| 758 | |
| 759 | /* |
| 760 | * Find columns with default values and prepare for insertion of the |
| 761 | * defaults. Pre-cooked (that is, inherited) defaults go into a list of |
| 762 | * CookedConstraint structs that we'll pass to heap_create_with_catalog, |
| 763 | * while raw defaults go into a list of RawColumnDefault structs that will |
| 764 | * be processed by AddRelationNewConstraints. (We can't deal with raw |
| 765 | * expressions until we can do transformExpr.) |
| 766 | * |
| 767 | * We can set the atthasdef flags now in the tuple descriptor; this just |
| 768 | * saves StoreAttrDefault from having to do an immediate update of the |
| 769 | * pg_attribute rows. |
| 770 | */ |
| 771 | rawDefaults = NIL; |
| 772 | cookedDefaults = NIL; |
| 773 | attnum = 0; |
| 774 | |
| 775 | foreach(listptr, stmt->tableElts) |
| 776 | { |
| 777 | ColumnDef *colDef = lfirst(listptr); |
| 778 | Form_pg_attribute attr; |
| 779 | |
| 780 | attnum++; |
| 781 | attr = TupleDescAttr(descriptor, attnum - 1); |
| 782 | |
| 783 | if (colDef->raw_default != NULL) |
| 784 | { |
| 785 | RawColumnDefault *rawEnt; |
| 786 | |
| 787 | Assert(colDef->cooked_default == NULL); |
| 788 | |
| 789 | rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault)); |
| 790 | rawEnt->attnum = attnum; |
| 791 | rawEnt->raw_default = colDef->raw_default; |
| 792 | rawEnt->missingMode = false; |
| 793 | rawEnt->generated = colDef->generated; |
| 794 | rawDefaults = lappend(rawDefaults, rawEnt); |
| 795 | attr->atthasdef = true; |
| 796 | } |
| 797 | else if (colDef->cooked_default != NULL) |
| 798 | { |
| 799 | CookedConstraint *cooked; |
| 800 | |
| 801 | cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint)); |
| 802 | cooked->contype = CONSTR_DEFAULT; |
| 803 | cooked->conoid = InvalidOid; /* until created */ |
| 804 | cooked->name = NULL; |
| 805 | cooked->attnum = attnum; |
| 806 | cooked->expr = colDef->cooked_default; |
| 807 | cooked->skip_validation = false; |
| 808 | cooked->is_local = true; /* not used for defaults */ |
| 809 | cooked->inhcount = 0; /* ditto */ |
| 810 | cooked->is_no_inherit = false; |
| 811 | cookedDefaults = lappend(cookedDefaults, cooked); |
| 812 | attr->atthasdef = true; |
| 813 | } |
| 814 | |
| 815 | if (colDef->identity) |
| 816 | attr->attidentity = colDef->identity; |
| 817 | |
| 818 | if (colDef->generated) |
| 819 | attr->attgenerated = colDef->generated; |
| 820 | } |
| 821 | |
| 822 | /* |
| 823 | * If the statement hasn't specified an access method, but we're defining |
| 824 | * a type of relation that needs one, use the default. |
| 825 | */ |
| 826 | if (stmt->accessMethod != NULL) |
| 827 | { |
| 828 | accessMethod = stmt->accessMethod; |
| 829 | |
| 830 | if (partitioned) |
| 831 | ereport(ERROR, |
| 832 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 833 | errmsg("specifying a table access method is not supported on a partitioned table" ))); |
| 834 | |
| 835 | } |
| 836 | else if (relkind == RELKIND_RELATION || |
| 837 | relkind == RELKIND_TOASTVALUE || |
| 838 | relkind == RELKIND_MATVIEW) |
| 839 | accessMethod = default_table_access_method; |
| 840 | |
| 841 | /* look up the access method, verify it is for a table */ |
| 842 | if (accessMethod != NULL) |
| 843 | accessMethodId = get_table_am_oid(accessMethod, false); |
| 844 | |
| 845 | /* |
| 846 | * Create the relation. Inherited defaults and constraints are passed in |
| 847 | * for immediate handling --- since they don't need parsing, they can be |
| 848 | * stored immediately. |
| 849 | */ |
| 850 | relationId = heap_create_with_catalog(relname, |
| 851 | namespaceId, |
| 852 | tablespaceId, |
| 853 | InvalidOid, |
| 854 | InvalidOid, |
| 855 | ofTypeId, |
| 856 | ownerId, |
| 857 | accessMethodId, |
| 858 | descriptor, |
| 859 | list_concat(cookedDefaults, |
| 860 | old_constraints), |
| 861 | relkind, |
| 862 | stmt->relation->relpersistence, |
| 863 | false, |
| 864 | false, |
| 865 | stmt->oncommit, |
| 866 | reloptions, |
| 867 | true, |
| 868 | allowSystemTableMods, |
| 869 | false, |
| 870 | InvalidOid, |
| 871 | typaddress); |
| 872 | |
| 873 | /* |
| 874 | * We must bump the command counter to make the newly-created relation |
| 875 | * tuple visible for opening. |
| 876 | */ |
| 877 | CommandCounterIncrement(); |
| 878 | |
| 879 | /* |
| 880 | * Open the new relation and acquire exclusive lock on it. This isn't |
| 881 | * really necessary for locking out other backends (since they can't see |
| 882 | * the new rel anyway until we commit), but it keeps the lock manager from |
| 883 | * complaining about deadlock risks. |
| 884 | */ |
| 885 | rel = relation_open(relationId, AccessExclusiveLock); |
| 886 | |
| 887 | /* |
| 888 | * Now add any newly specified column default and generation expressions |
| 889 | * to the new relation. These are passed to us in the form of raw |
| 890 | * parsetrees; we need to transform them to executable expression trees |
| 891 | * before they can be added. The most convenient way to do that is to |
| 892 | * apply the parser's transformExpr routine, but transformExpr doesn't |
| 893 | * work unless we have a pre-existing relation. So, the transformation has |
| 894 | * to be postponed to this final step of CREATE TABLE. |
| 895 | * |
| 896 | * This needs to be before processing the partitioning clauses because |
| 897 | * those could refer to generated columns. |
| 898 | */ |
| 899 | if (rawDefaults) |
| 900 | AddRelationNewConstraints(rel, rawDefaults, NIL, |
| 901 | true, true, false, queryString); |
| 902 | |
| 903 | /* |
| 904 | * Make column generation expressions visible for use by partitioning. |
| 905 | */ |
| 906 | CommandCounterIncrement(); |
| 907 | |
| 908 | /* Process and store partition bound, if any. */ |
| 909 | if (stmt->partbound) |
| 910 | { |
| 911 | PartitionBoundSpec *bound; |
| 912 | ParseState *pstate; |
| 913 | Oid parentId = linitial_oid(inheritOids), |
| 914 | defaultPartOid; |
| 915 | Relation parent, |
| 916 | defaultRel = NULL; |
| 917 | RangeTblEntry *rte; |
| 918 | |
| 919 | /* Already have strong enough lock on the parent */ |
| 920 | parent = table_open(parentId, NoLock); |
| 921 | |
| 922 | /* |
| 923 | * We are going to try to validate the partition bound specification |
| 924 | * against the partition key of parentRel, so it better have one. |
| 925 | */ |
| 926 | if (parent->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) |
| 927 | ereport(ERROR, |
| 928 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| 929 | errmsg("\"%s\" is not partitioned" , |
| 930 | RelationGetRelationName(parent)))); |
| 931 | |
| 932 | /* |
| 933 | * The partition constraint of the default partition depends on the |
| 934 | * partition bounds of every other partition. It is possible that |
| 935 | * another backend might be about to execute a query on the default |
| 936 | * partition table, and that the query relies on previously cached |
| 937 | * default partition constraints. We must therefore take a table lock |
| 938 | * strong enough to prevent all queries on the default partition from |
| 939 | * proceeding until we commit and send out a shared-cache-inval notice |
| 940 | * that will make them update their index lists. |
| 941 | * |
| 942 | * Order of locking: The relation being added won't be visible to |
| 943 | * other backends until it is committed, hence here in |
| 944 | * DefineRelation() the order of locking the default partition and the |
| 945 | * relation being added does not matter. But at all other places we |
| 946 | * need to lock the default relation before we lock the relation being |
| 947 | * added or removed i.e. we should take the lock in same order at all |
| 948 | * the places such that lock parent, lock default partition and then |
| 949 | * lock the partition so as to avoid a deadlock. |
| 950 | */ |
| 951 | defaultPartOid = |
| 952 | get_default_oid_from_partdesc(RelationGetPartitionDesc(parent)); |
| 953 | if (OidIsValid(defaultPartOid)) |
| 954 | defaultRel = table_open(defaultPartOid, AccessExclusiveLock); |
| 955 | |
| 956 | /* Transform the bound values */ |
| 957 | pstate = make_parsestate(NULL); |
| 958 | pstate->p_sourcetext = queryString; |
| 959 | |
| 960 | /* |
| 961 | * Add an RTE containing this relation, so that transformExpr called |
| 962 | * on partition bound expressions is able to report errors using a |
| 963 | * proper context. |
| 964 | */ |
| 965 | rte = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, |
| 966 | NULL, false, false); |
| 967 | addRTEtoQuery(pstate, rte, false, true, true); |
| 968 | bound = transformPartitionBound(pstate, parent, stmt->partbound); |
| 969 | |
| 970 | /* |
| 971 | * Check first that the new partition's bound is valid and does not |
| 972 | * overlap with any of existing partitions of the parent. |
| 973 | */ |
| 974 | check_new_partition_bound(relname, parent, bound); |
| 975 | |
| 976 | /* |
| 977 | * If the default partition exists, its partition constraints will |
| 978 | * change after the addition of this new partition such that it won't |
| 979 | * allow any row that qualifies for this new partition. So, check that |
| 980 | * the existing data in the default partition satisfies the constraint |
| 981 | * as it will exist after adding this partition. |
| 982 | */ |
| 983 | if (OidIsValid(defaultPartOid)) |
| 984 | { |
| 985 | check_default_partition_contents(parent, defaultRel, bound); |
| 986 | /* Keep the lock until commit. */ |
| 987 | table_close(defaultRel, NoLock); |
| 988 | } |
| 989 | |
| 990 | /* Update the pg_class entry. */ |
| 991 | StorePartitionBound(rel, parent, bound); |
| 992 | |
| 993 | table_close(parent, NoLock); |
| 994 | } |
| 995 | |
| 996 | /* Store inheritance information for new rel. */ |
| 997 | StoreCatalogInheritance(relationId, inheritOids, stmt->partbound != NULL); |
| 998 | |
| 999 | /* |
| 1000 | * Process the partitioning specification (if any) and store the partition |
| 1001 | * key information into the catalog. |
| 1002 | */ |
| 1003 | if (partitioned) |
| 1004 | { |
| 1005 | ParseState *pstate; |
| 1006 | char strategy; |
| 1007 | int partnatts; |
| 1008 | AttrNumber partattrs[PARTITION_MAX_KEYS]; |
| 1009 | Oid partopclass[PARTITION_MAX_KEYS]; |
| 1010 | Oid partcollation[PARTITION_MAX_KEYS]; |
| 1011 | List *partexprs = NIL; |
| 1012 | |
| 1013 | pstate = make_parsestate(NULL); |
| 1014 | pstate->p_sourcetext = queryString; |
| 1015 | |
| 1016 | partnatts = list_length(stmt->partspec->partParams); |
| 1017 | |
| 1018 | /* Protect fixed-size arrays here and in executor */ |
| 1019 | if (partnatts > PARTITION_MAX_KEYS) |
| 1020 | ereport(ERROR, |
| 1021 | (errcode(ERRCODE_TOO_MANY_COLUMNS), |
| 1022 | errmsg("cannot partition using more than %d columns" , |
| 1023 | PARTITION_MAX_KEYS))); |
| 1024 | |
| 1025 | /* |
| 1026 | * We need to transform the raw parsetrees corresponding to partition |
| 1027 | * expressions into executable expression trees. Like column defaults |
| 1028 | * and CHECK constraints, we could not have done the transformation |
| 1029 | * earlier. |
| 1030 | */ |
| 1031 | stmt->partspec = transformPartitionSpec(rel, stmt->partspec, |
| 1032 | &strategy); |
| 1033 | |
| 1034 | ComputePartitionAttrs(pstate, rel, stmt->partspec->partParams, |
| 1035 | partattrs, &partexprs, partopclass, |
| 1036 | partcollation, strategy); |
| 1037 | |
| 1038 | StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs, |
| 1039 | partopclass, partcollation); |
| 1040 | |
| 1041 | /* make it all visible */ |
| 1042 | CommandCounterIncrement(); |
| 1043 | } |
| 1044 | |
| 1045 | /* |
| 1046 | * If we're creating a partition, create now all the indexes, triggers, |
| 1047 | * FKs defined in the parent. |
| 1048 | * |
| 1049 | * We can't do it earlier, because DefineIndex wants to know the partition |
| 1050 | * key which we just stored. |
| 1051 | */ |
| 1052 | if (stmt->partbound) |
| 1053 | { |
| 1054 | Oid parentId = linitial_oid(inheritOids); |
| 1055 | Relation parent; |
| 1056 | List *idxlist; |
| 1057 | ListCell *cell; |
| 1058 | |
| 1059 | /* Already have strong enough lock on the parent */ |
| 1060 | parent = table_open(parentId, NoLock); |
| 1061 | idxlist = RelationGetIndexList(parent); |
| 1062 | |
| 1063 | /* |
| 1064 | * For each index in the parent table, create one in the partition |
| 1065 | */ |
| 1066 | foreach(cell, idxlist) |
| 1067 | { |
| 1068 | Relation idxRel = index_open(lfirst_oid(cell), AccessShareLock); |
| 1069 | AttrNumber *attmap; |
| 1070 | IndexStmt *idxstmt; |
| 1071 | Oid constraintOid; |
| 1072 | |
| 1073 | if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) |
| 1074 | { |
| 1075 | if (idxRel->rd_index->indisunique) |
| 1076 | ereport(ERROR, |
| 1077 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 1078 | errmsg("cannot create foreign partition of partitioned table \"%s\"" , |
| 1079 | RelationGetRelationName(parent)), |
| 1080 | errdetail("Table \"%s\" contains indexes that are unique." , |
| 1081 | RelationGetRelationName(parent)))); |
| 1082 | else |
| 1083 | { |
| 1084 | index_close(idxRel, AccessShareLock); |
| 1085 | continue; |
| 1086 | } |
| 1087 | } |
| 1088 | |
| 1089 | attmap = convert_tuples_by_name_map(RelationGetDescr(rel), |
| 1090 | RelationGetDescr(parent), |
| 1091 | gettext_noop("could not convert row type" )); |
| 1092 | idxstmt = |
| 1093 | generateClonedIndexStmt(NULL, idxRel, |
| 1094 | attmap, RelationGetDescr(rel)->natts, |
| 1095 | &constraintOid); |
| 1096 | DefineIndex(RelationGetRelid(rel), |
| 1097 | idxstmt, |
| 1098 | InvalidOid, |
| 1099 | RelationGetRelid(idxRel), |
| 1100 | constraintOid, |
| 1101 | false, false, false, false, false); |
| 1102 | |
| 1103 | index_close(idxRel, AccessShareLock); |
| 1104 | } |
| 1105 | |
| 1106 | list_free(idxlist); |
| 1107 | |
| 1108 | /* |
| 1109 | * If there are any row-level triggers, clone them to the new |
| 1110 | * partition. |
| 1111 | */ |
| 1112 | if (parent->trigdesc != NULL) |
| 1113 | CloneRowTriggersToPartition(parent, rel); |
| 1114 | |
| 1115 | /* |
| 1116 | * And foreign keys too. Note that because we're freshly creating the |
| 1117 | * table, there is no need to verify these new constraints. |
| 1118 | */ |
| 1119 | CloneForeignKeyConstraints(NULL, parent, rel); |
| 1120 | |
| 1121 | table_close(parent, NoLock); |
| 1122 | } |
| 1123 | |
| 1124 | /* |
| 1125 | * Now add any newly specified CHECK constraints to the new relation. Same |
| 1126 | * as for defaults above, but these need to come after partitioning is set |
| 1127 | * up. |
| 1128 | */ |
| 1129 | if (stmt->constraints) |
| 1130 | AddRelationNewConstraints(rel, NIL, stmt->constraints, |
| 1131 | true, true, false, queryString); |
| 1132 | |
| 1133 | ObjectAddressSet(address, RelationRelationId, relationId); |
| 1134 | |
| 1135 | /* |
| 1136 | * Clean up. We keep lock on new relation (although it shouldn't be |
| 1137 | * visible to anyone else anyway, until commit). |
| 1138 | */ |
| 1139 | relation_close(rel, NoLock); |
| 1140 | |
| 1141 | return address; |
| 1142 | } |
| 1143 | |
| 1144 | /* |
| 1145 | * Emit the right error or warning message for a "DROP" command issued on a |
| 1146 | * non-existent relation |
| 1147 | */ |
| 1148 | static void |
| 1149 | DropErrorMsgNonExistent(RangeVar *rel, char rightkind, bool missing_ok) |
| 1150 | { |
| 1151 | const struct dropmsgstrings *rentry; |
| 1152 | |
| 1153 | if (rel->schemaname != NULL && |
| 1154 | !OidIsValid(LookupNamespaceNoError(rel->schemaname))) |
| 1155 | { |
| 1156 | if (!missing_ok) |
| 1157 | { |
| 1158 | ereport(ERROR, |
| 1159 | (errcode(ERRCODE_UNDEFINED_SCHEMA), |
| 1160 | errmsg("schema \"%s\" does not exist" , rel->schemaname))); |
| 1161 | } |
| 1162 | else |
| 1163 | { |
| 1164 | ereport(NOTICE, |
| 1165 | (errmsg("schema \"%s\" does not exist, skipping" , |
| 1166 | rel->schemaname))); |
| 1167 | } |
| 1168 | return; |
| 1169 | } |
| 1170 | |
| 1171 | for (rentry = dropmsgstringarray; rentry->kind != '\0'; rentry++) |
| 1172 | { |
| 1173 | if (rentry->kind == rightkind) |
| 1174 | { |
| 1175 | if (!missing_ok) |
| 1176 | { |
| 1177 | ereport(ERROR, |
| 1178 | (errcode(rentry->nonexistent_code), |
| 1179 | errmsg(rentry->nonexistent_msg, rel->relname))); |
| 1180 | } |
| 1181 | else |
| 1182 | { |
| 1183 | ereport(NOTICE, (errmsg(rentry->skipping_msg, rel->relname))); |
| 1184 | break; |
| 1185 | } |
| 1186 | } |
| 1187 | } |
| 1188 | |
| 1189 | Assert(rentry->kind != '\0'); /* Should be impossible */ |
| 1190 | } |
| 1191 | |
| 1192 | /* |
| 1193 | * Emit the right error message for a "DROP" command issued on a |
| 1194 | * relation of the wrong type |
| 1195 | */ |
| 1196 | static void |
| 1197 | DropErrorMsgWrongType(const char *relname, char wrongkind, char rightkind) |
| 1198 | { |
| 1199 | const struct dropmsgstrings *rentry; |
| 1200 | const struct dropmsgstrings *wentry; |
| 1201 | |
| 1202 | for (rentry = dropmsgstringarray; rentry->kind != '\0'; rentry++) |
| 1203 | if (rentry->kind == rightkind) |
| 1204 | break; |
| 1205 | Assert(rentry->kind != '\0'); |
| 1206 | |
| 1207 | for (wentry = dropmsgstringarray; wentry->kind != '\0'; wentry++) |
| 1208 | if (wentry->kind == wrongkind) |
| 1209 | break; |
| 1210 | /* wrongkind could be something we don't have in our table... */ |
| 1211 | |
| 1212 | ereport(ERROR, |
| 1213 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 1214 | errmsg(rentry->nota_msg, relname), |
| 1215 | (wentry->kind != '\0') ? errhint("%s" , _(wentry->drophint_msg)) : 0)); |
| 1216 | } |
| 1217 | |
| 1218 | /* |
| 1219 | * RemoveRelations |
| 1220 | * Implements DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW, |
| 1221 | * DROP MATERIALIZED VIEW, DROP FOREIGN TABLE |
| 1222 | */ |
| 1223 | void |
| 1224 | RemoveRelations(DropStmt *drop) |
| 1225 | { |
| 1226 | ObjectAddresses *objects; |
| 1227 | char relkind; |
| 1228 | ListCell *cell; |
| 1229 | int flags = 0; |
| 1230 | LOCKMODE lockmode = AccessExclusiveLock; |
| 1231 | |
| 1232 | /* DROP CONCURRENTLY uses a weaker lock, and has some restrictions */ |
| 1233 | if (drop->concurrent) |
| 1234 | { |
| 1235 | flags |= PERFORM_DELETION_CONCURRENTLY; |
| 1236 | lockmode = ShareUpdateExclusiveLock; |
| 1237 | Assert(drop->removeType == OBJECT_INDEX); |
| 1238 | if (list_length(drop->objects) != 1) |
| 1239 | ereport(ERROR, |
| 1240 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 1241 | errmsg("DROP INDEX CONCURRENTLY does not support dropping multiple objects" ))); |
| 1242 | if (drop->behavior == DROP_CASCADE) |
| 1243 | ereport(ERROR, |
| 1244 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 1245 | errmsg("DROP INDEX CONCURRENTLY does not support CASCADE" ))); |
| 1246 | } |
| 1247 | |
| 1248 | /* |
| 1249 | * First we identify all the relations, then we delete them in a single |
| 1250 | * performMultipleDeletions() call. This is to avoid unwanted DROP |
| 1251 | * RESTRICT errors if one of the relations depends on another. |
| 1252 | */ |
| 1253 | |
| 1254 | /* Determine required relkind */ |
| 1255 | switch (drop->removeType) |
| 1256 | { |
| 1257 | case OBJECT_TABLE: |
| 1258 | relkind = RELKIND_RELATION; |
| 1259 | break; |
| 1260 | |
| 1261 | case OBJECT_INDEX: |
| 1262 | relkind = RELKIND_INDEX; |
| 1263 | break; |
| 1264 | |
| 1265 | case OBJECT_SEQUENCE: |
| 1266 | relkind = RELKIND_SEQUENCE; |
| 1267 | break; |
| 1268 | |
| 1269 | case OBJECT_VIEW: |
| 1270 | relkind = RELKIND_VIEW; |
| 1271 | break; |
| 1272 | |
| 1273 | case OBJECT_MATVIEW: |
| 1274 | relkind = RELKIND_MATVIEW; |
| 1275 | break; |
| 1276 | |
| 1277 | case OBJECT_FOREIGN_TABLE: |
| 1278 | relkind = RELKIND_FOREIGN_TABLE; |
| 1279 | break; |
| 1280 | |
| 1281 | default: |
| 1282 | elog(ERROR, "unrecognized drop object type: %d" , |
| 1283 | (int) drop->removeType); |
| 1284 | relkind = 0; /* keep compiler quiet */ |
| 1285 | break; |
| 1286 | } |
| 1287 | |
| 1288 | /* Lock and validate each relation; build a list of object addresses */ |
| 1289 | objects = new_object_addresses(); |
| 1290 | |
| 1291 | foreach(cell, drop->objects) |
| 1292 | { |
| 1293 | RangeVar *rel = makeRangeVarFromNameList((List *) lfirst(cell)); |
| 1294 | Oid relOid; |
| 1295 | ObjectAddress obj; |
| 1296 | struct DropRelationCallbackState state; |
| 1297 | |
| 1298 | /* |
| 1299 | * These next few steps are a great deal like relation_openrv, but we |
| 1300 | * don't bother building a relcache entry since we don't need it. |
| 1301 | * |
| 1302 | * Check for shared-cache-inval messages before trying to access the |
| 1303 | * relation. This is needed to cover the case where the name |
| 1304 | * identifies a rel that has been dropped and recreated since the |
| 1305 | * start of our transaction: if we don't flush the old syscache entry, |
| 1306 | * then we'll latch onto that entry and suffer an error later. |
| 1307 | */ |
| 1308 | AcceptInvalidationMessages(); |
| 1309 | |
| 1310 | /* Look up the appropriate relation using namespace search. */ |
| 1311 | state.relkind = relkind; |
| 1312 | state.heapOid = InvalidOid; |
| 1313 | state.partParentOid = InvalidOid; |
| 1314 | state.concurrent = drop->concurrent; |
| 1315 | relOid = RangeVarGetRelidExtended(rel, lockmode, RVR_MISSING_OK, |
| 1316 | RangeVarCallbackForDropRelation, |
| 1317 | (void *) &state); |
| 1318 | |
| 1319 | /* Not there? */ |
| 1320 | if (!OidIsValid(relOid)) |
| 1321 | { |
| 1322 | DropErrorMsgNonExistent(rel, relkind, drop->missing_ok); |
| 1323 | continue; |
| 1324 | } |
| 1325 | |
| 1326 | /* OK, we're ready to delete this one */ |
| 1327 | obj.classId = RelationRelationId; |
| 1328 | obj.objectId = relOid; |
| 1329 | obj.objectSubId = 0; |
| 1330 | |
| 1331 | add_exact_object_address(&obj, objects); |
| 1332 | } |
| 1333 | |
| 1334 | performMultipleDeletions(objects, drop->behavior, flags); |
| 1335 | |
| 1336 | free_object_addresses(objects); |
| 1337 | } |
| 1338 | |
| 1339 | /* |
| 1340 | * Before acquiring a table lock, check whether we have sufficient rights. |
| 1341 | * In the case of DROP INDEX, also try to lock the table before the index. |
| 1342 | * Also, if the table to be dropped is a partition, we try to lock the parent |
| 1343 | * first. |
| 1344 | */ |
| 1345 | static void |
| 1346 | RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid, |
| 1347 | void *arg) |
| 1348 | { |
| 1349 | HeapTuple tuple; |
| 1350 | struct DropRelationCallbackState *state; |
| 1351 | char relkind; |
| 1352 | char expected_relkind; |
| 1353 | bool is_partition; |
| 1354 | Form_pg_class classform; |
| 1355 | LOCKMODE heap_lockmode; |
| 1356 | bool invalid_system_index = false; |
| 1357 | |
| 1358 | state = (struct DropRelationCallbackState *) arg; |
| 1359 | relkind = state->relkind; |
| 1360 | heap_lockmode = state->concurrent ? |
| 1361 | ShareUpdateExclusiveLock : AccessExclusiveLock; |
| 1362 | |
| 1363 | /* |
| 1364 | * If we previously locked some other index's heap, and the name we're |
| 1365 | * looking up no longer refers to that relation, release the now-useless |
| 1366 | * lock. |
| 1367 | */ |
| 1368 | if (relOid != oldRelOid && OidIsValid(state->heapOid)) |
| 1369 | { |
| 1370 | UnlockRelationOid(state->heapOid, heap_lockmode); |
| 1371 | state->heapOid = InvalidOid; |
| 1372 | } |
| 1373 | |
| 1374 | /* |
| 1375 | * Similarly, if we previously locked some other partition's heap, and the |
| 1376 | * name we're looking up no longer refers to that relation, release the |
| 1377 | * now-useless lock. |
| 1378 | */ |
| 1379 | if (relOid != oldRelOid && OidIsValid(state->partParentOid)) |
| 1380 | { |
| 1381 | UnlockRelationOid(state->partParentOid, AccessExclusiveLock); |
| 1382 | state->partParentOid = InvalidOid; |
| 1383 | } |
| 1384 | |
| 1385 | /* Didn't find a relation, so no need for locking or permission checks. */ |
| 1386 | if (!OidIsValid(relOid)) |
| 1387 | return; |
| 1388 | |
| 1389 | tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid)); |
| 1390 | if (!HeapTupleIsValid(tuple)) |
| 1391 | return; /* concurrently dropped, so nothing to do */ |
| 1392 | classform = (Form_pg_class) GETSTRUCT(tuple); |
| 1393 | is_partition = classform->relispartition; |
| 1394 | |
| 1395 | /* |
| 1396 | * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE, |
| 1397 | * but RemoveRelations() can only pass one relkind for a given relation. |
| 1398 | * It chooses RELKIND_RELATION for both regular and partitioned tables. |
| 1399 | * That means we must be careful before giving the wrong type error when |
| 1400 | * the relation is RELKIND_PARTITIONED_TABLE. An equivalent problem |
| 1401 | * exists with indexes. |
| 1402 | */ |
| 1403 | if (classform->relkind == RELKIND_PARTITIONED_TABLE) |
| 1404 | expected_relkind = RELKIND_RELATION; |
| 1405 | else if (classform->relkind == RELKIND_PARTITIONED_INDEX) |
| 1406 | expected_relkind = RELKIND_INDEX; |
| 1407 | else |
| 1408 | expected_relkind = classform->relkind; |
| 1409 | |
| 1410 | if (relkind != expected_relkind) |
| 1411 | DropErrorMsgWrongType(rel->relname, classform->relkind, relkind); |
| 1412 | |
| 1413 | /* Allow DROP to either table owner or schema owner */ |
| 1414 | if (!pg_class_ownercheck(relOid, GetUserId()) && |
| 1415 | !pg_namespace_ownercheck(classform->relnamespace, GetUserId())) |
| 1416 | aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relOid)), |
| 1417 | rel->relname); |
| 1418 | |
| 1419 | /* |
| 1420 | * Check the case of a system index that might have been invalidated by a |
| 1421 | * failed concurrent process and allow its drop. For the time being, this |
| 1422 | * only concerns indexes of toast relations that became invalid during a |
| 1423 | * REINDEX CONCURRENTLY process. |
| 1424 | */ |
| 1425 | if (IsSystemClass(relOid, classform) && relkind == RELKIND_INDEX) |
| 1426 | { |
| 1427 | HeapTuple locTuple; |
| 1428 | Form_pg_index indexform; |
| 1429 | bool indisvalid; |
| 1430 | |
| 1431 | locTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(relOid)); |
| 1432 | if (!HeapTupleIsValid(locTuple)) |
| 1433 | { |
| 1434 | ReleaseSysCache(tuple); |
| 1435 | return; |
| 1436 | } |
| 1437 | |
| 1438 | indexform = (Form_pg_index) GETSTRUCT(locTuple); |
| 1439 | indisvalid = indexform->indisvalid; |
| 1440 | ReleaseSysCache(locTuple); |
| 1441 | |
| 1442 | /* Mark object as being an invalid index of system catalogs */ |
| 1443 | if (!indisvalid) |
| 1444 | invalid_system_index = true; |
| 1445 | } |
| 1446 | |
| 1447 | /* In the case of an invalid index, it is fine to bypass this check */ |
| 1448 | if (!invalid_system_index && !allowSystemTableMods && IsSystemClass(relOid, classform)) |
| 1449 | ereport(ERROR, |
| 1450 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| 1451 | errmsg("permission denied: \"%s\" is a system catalog" , |
| 1452 | rel->relname))); |
| 1453 | |
| 1454 | ReleaseSysCache(tuple); |
| 1455 | |
| 1456 | /* |
| 1457 | * In DROP INDEX, attempt to acquire lock on the parent table before |
| 1458 | * locking the index. index_drop() will need this anyway, and since |
| 1459 | * regular queries lock tables before their indexes, we risk deadlock if |
| 1460 | * we do it the other way around. No error if we don't find a pg_index |
| 1461 | * entry, though --- the relation may have been dropped. |
| 1462 | */ |
| 1463 | if ((relkind == RELKIND_INDEX || relkind == RELKIND_PARTITIONED_INDEX) && |
| 1464 | relOid != oldRelOid) |
| 1465 | { |
| 1466 | state->heapOid = IndexGetRelation(relOid, true); |
| 1467 | if (OidIsValid(state->heapOid)) |
| 1468 | LockRelationOid(state->heapOid, heap_lockmode); |
| 1469 | } |
| 1470 | |
| 1471 | /* |
| 1472 | * Similarly, if the relation is a partition, we must acquire lock on its |
| 1473 | * parent before locking the partition. That's because queries lock the |
| 1474 | * parent before its partitions, so we risk deadlock it we do it the other |
| 1475 | * way around. |
| 1476 | */ |
| 1477 | if (is_partition && relOid != oldRelOid) |
| 1478 | { |
| 1479 | state->partParentOid = get_partition_parent(relOid); |
| 1480 | if (OidIsValid(state->partParentOid)) |
| 1481 | LockRelationOid(state->partParentOid, AccessExclusiveLock); |
| 1482 | } |
| 1483 | } |
| 1484 | |
| 1485 | /* |
| 1486 | * ExecuteTruncate |
| 1487 | * Executes a TRUNCATE command. |
| 1488 | * |
| 1489 | * This is a multi-relation truncate. We first open and grab exclusive |
| 1490 | * lock on all relations involved, checking permissions and otherwise |
| 1491 | * verifying that the relation is OK for truncation. In CASCADE mode, |
| 1492 | * relations having FK references to the targeted relations are automatically |
| 1493 | * added to the group; in RESTRICT mode, we check that all FK references are |
| 1494 | * internal to the group that's being truncated. Finally all the relations |
| 1495 | * are truncated and reindexed. |
| 1496 | */ |
| 1497 | void |
| 1498 | ExecuteTruncate(TruncateStmt *stmt) |
| 1499 | { |
| 1500 | List *rels = NIL; |
| 1501 | List *relids = NIL; |
| 1502 | List *relids_logged = NIL; |
| 1503 | ListCell *cell; |
| 1504 | |
| 1505 | /* |
| 1506 | * Open, exclusive-lock, and check all the explicitly-specified relations |
| 1507 | */ |
| 1508 | foreach(cell, stmt->relations) |
| 1509 | { |
| 1510 | RangeVar *rv = lfirst(cell); |
| 1511 | Relation rel; |
| 1512 | bool recurse = rv->inh; |
| 1513 | Oid myrelid; |
| 1514 | LOCKMODE lockmode = AccessExclusiveLock; |
| 1515 | |
| 1516 | myrelid = RangeVarGetRelidExtended(rv, lockmode, |
| 1517 | 0, RangeVarCallbackForTruncate, |
| 1518 | NULL); |
| 1519 | |
| 1520 | /* open the relation, we already hold a lock on it */ |
| 1521 | rel = table_open(myrelid, NoLock); |
| 1522 | |
| 1523 | /* don't throw error for "TRUNCATE foo, foo" */ |
| 1524 | if (list_member_oid(relids, myrelid)) |
| 1525 | { |
| 1526 | table_close(rel, lockmode); |
| 1527 | continue; |
| 1528 | } |
| 1529 | |
| 1530 | /* |
| 1531 | * RangeVarGetRelidExtended() has done most checks with its callback, |
| 1532 | * but other checks with the now-opened Relation remain. |
| 1533 | */ |
| 1534 | truncate_check_activity(rel); |
| 1535 | |
| 1536 | rels = lappend(rels, rel); |
| 1537 | relids = lappend_oid(relids, myrelid); |
| 1538 | /* Log this relation only if needed for logical decoding */ |
| 1539 | if (RelationIsLogicallyLogged(rel)) |
| 1540 | relids_logged = lappend_oid(relids_logged, myrelid); |
| 1541 | |
| 1542 | if (recurse) |
| 1543 | { |
| 1544 | ListCell *child; |
| 1545 | List *children; |
| 1546 | |
| 1547 | children = find_all_inheritors(myrelid, lockmode, NULL); |
| 1548 | |
| 1549 | foreach(child, children) |
| 1550 | { |
| 1551 | Oid childrelid = lfirst_oid(child); |
| 1552 | |
| 1553 | if (list_member_oid(relids, childrelid)) |
| 1554 | continue; |
| 1555 | |
| 1556 | /* find_all_inheritors already got lock */ |
| 1557 | rel = table_open(childrelid, NoLock); |
| 1558 | |
| 1559 | /* |
| 1560 | * It is possible that the parent table has children that are |
| 1561 | * temp tables of other backends. We cannot safely access |
| 1562 | * such tables (because of buffering issues), and the best |
| 1563 | * thing to do is to silently ignore them. Note that this |
| 1564 | * check is the same as one of the checks done in |
| 1565 | * truncate_check_activity() called below, still it is kept |
| 1566 | * here for simplicity. |
| 1567 | */ |
| 1568 | if (RELATION_IS_OTHER_TEMP(rel)) |
| 1569 | { |
| 1570 | table_close(rel, lockmode); |
| 1571 | continue; |
| 1572 | } |
| 1573 | |
| 1574 | truncate_check_rel(RelationGetRelid(rel), rel->rd_rel); |
| 1575 | truncate_check_activity(rel); |
| 1576 | |
| 1577 | rels = lappend(rels, rel); |
| 1578 | relids = lappend_oid(relids, childrelid); |
| 1579 | /* Log this relation only if needed for logical decoding */ |
| 1580 | if (RelationIsLogicallyLogged(rel)) |
| 1581 | relids_logged = lappend_oid(relids_logged, childrelid); |
| 1582 | } |
| 1583 | } |
| 1584 | else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) |
| 1585 | ereport(ERROR, |
| 1586 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 1587 | errmsg("cannot truncate only a partitioned table" ), |
| 1588 | errhint("Do not specify the ONLY keyword, or use TRUNCATE ONLY on the partitions directly." ))); |
| 1589 | } |
| 1590 | |
| 1591 | ExecuteTruncateGuts(rels, relids, relids_logged, |
| 1592 | stmt->behavior, stmt->restart_seqs); |
| 1593 | |
| 1594 | /* And close the rels */ |
| 1595 | foreach(cell, rels) |
| 1596 | { |
| 1597 | Relation rel = (Relation) lfirst(cell); |
| 1598 | |
| 1599 | table_close(rel, NoLock); |
| 1600 | } |
| 1601 | } |
| 1602 | |
| 1603 | /* |
| 1604 | * ExecuteTruncateGuts |
| 1605 | * |
| 1606 | * Internal implementation of TRUNCATE. This is called by the actual TRUNCATE |
| 1607 | * command (see above) as well as replication subscribers that execute a |
| 1608 | * replicated TRUNCATE action. |
| 1609 | * |
| 1610 | * explicit_rels is the list of Relations to truncate that the command |
| 1611 | * specified. relids is the list of Oids corresponding to explicit_rels. |
| 1612 | * relids_logged is the list of Oids (a subset of relids) that require |
| 1613 | * WAL-logging. This is all a bit redundant, but the existing callers have |
| 1614 | * this information handy in this form. |
| 1615 | */ |
| 1616 | void |
| 1617 | ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged, |
| 1618 | DropBehavior behavior, bool restart_seqs) |
| 1619 | { |
| 1620 | List *rels; |
| 1621 | List *seq_relids = NIL; |
| 1622 | EState *estate; |
| 1623 | ResultRelInfo *resultRelInfos; |
| 1624 | ResultRelInfo *resultRelInfo; |
| 1625 | SubTransactionId mySubid; |
| 1626 | ListCell *cell; |
| 1627 | Oid *logrelids; |
| 1628 | |
| 1629 | /* |
| 1630 | * Check the explicitly-specified relations. |
| 1631 | * |
| 1632 | * In CASCADE mode, suck in all referencing relations as well. This |
| 1633 | * requires multiple iterations to find indirectly-dependent relations. At |
| 1634 | * each phase, we need to exclusive-lock new rels before looking for their |
| 1635 | * dependencies, else we might miss something. Also, we check each rel as |
| 1636 | * soon as we open it, to avoid a faux pas such as holding lock for a long |
| 1637 | * time on a rel we have no permissions for. |
| 1638 | */ |
| 1639 | rels = list_copy(explicit_rels); |
| 1640 | if (behavior == DROP_CASCADE) |
| 1641 | { |
| 1642 | for (;;) |
| 1643 | { |
| 1644 | List *newrelids; |
| 1645 | |
| 1646 | newrelids = heap_truncate_find_FKs(relids); |
| 1647 | if (newrelids == NIL) |
| 1648 | break; /* nothing else to add */ |
| 1649 | |
| 1650 | foreach(cell, newrelids) |
| 1651 | { |
| 1652 | Oid relid = lfirst_oid(cell); |
| 1653 | Relation rel; |
| 1654 | |
| 1655 | rel = table_open(relid, AccessExclusiveLock); |
| 1656 | ereport(NOTICE, |
| 1657 | (errmsg("truncate cascades to table \"%s\"" , |
| 1658 | RelationGetRelationName(rel)))); |
| 1659 | truncate_check_rel(relid, rel->rd_rel); |
| 1660 | truncate_check_activity(rel); |
| 1661 | rels = lappend(rels, rel); |
| 1662 | relids = lappend_oid(relids, relid); |
| 1663 | /* Log this relation only if needed for logical decoding */ |
| 1664 | if (RelationIsLogicallyLogged(rel)) |
| 1665 | relids_logged = lappend_oid(relids_logged, relid); |
| 1666 | } |
| 1667 | } |
| 1668 | } |
| 1669 | |
| 1670 | /* |
| 1671 | * Check foreign key references. In CASCADE mode, this should be |
| 1672 | * unnecessary since we just pulled in all the references; but as a |
| 1673 | * cross-check, do it anyway if in an Assert-enabled build. |
| 1674 | */ |
| 1675 | #ifdef USE_ASSERT_CHECKING |
| 1676 | heap_truncate_check_FKs(rels, false); |
| 1677 | #else |
| 1678 | if (behavior == DROP_RESTRICT) |
| 1679 | heap_truncate_check_FKs(rels, false); |
| 1680 | #endif |
| 1681 | |
| 1682 | /* |
| 1683 | * If we are asked to restart sequences, find all the sequences, lock them |
| 1684 | * (we need AccessExclusiveLock for ResetSequence), and check permissions. |
| 1685 | * We want to do this early since it's pointless to do all the truncation |
| 1686 | * work only to fail on sequence permissions. |
| 1687 | */ |
| 1688 | if (restart_seqs) |
| 1689 | { |
| 1690 | foreach(cell, rels) |
| 1691 | { |
| 1692 | Relation rel = (Relation) lfirst(cell); |
| 1693 | List *seqlist = getOwnedSequences(RelationGetRelid(rel), 0); |
| 1694 | ListCell *seqcell; |
| 1695 | |
| 1696 | foreach(seqcell, seqlist) |
| 1697 | { |
| 1698 | Oid seq_relid = lfirst_oid(seqcell); |
| 1699 | Relation seq_rel; |
| 1700 | |
| 1701 | seq_rel = relation_open(seq_relid, AccessExclusiveLock); |
| 1702 | |
| 1703 | /* This check must match AlterSequence! */ |
| 1704 | if (!pg_class_ownercheck(seq_relid, GetUserId())) |
| 1705 | aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SEQUENCE, |
| 1706 | RelationGetRelationName(seq_rel)); |
| 1707 | |
| 1708 | seq_relids = lappend_oid(seq_relids, seq_relid); |
| 1709 | |
| 1710 | relation_close(seq_rel, NoLock); |
| 1711 | } |
| 1712 | } |
| 1713 | } |
| 1714 | |
| 1715 | /* Prepare to catch AFTER triggers. */ |
| 1716 | AfterTriggerBeginQuery(); |
| 1717 | |
| 1718 | /* |
| 1719 | * To fire triggers, we'll need an EState as well as a ResultRelInfo for |
| 1720 | * each relation. We don't need to call ExecOpenIndices, though. |
| 1721 | */ |
| 1722 | estate = CreateExecutorState(); |
| 1723 | resultRelInfos = (ResultRelInfo *) |
| 1724 | palloc(list_length(rels) * sizeof(ResultRelInfo)); |
| 1725 | resultRelInfo = resultRelInfos; |
| 1726 | foreach(cell, rels) |
| 1727 | { |
| 1728 | Relation rel = (Relation) lfirst(cell); |
| 1729 | |
| 1730 | InitResultRelInfo(resultRelInfo, |
| 1731 | rel, |
| 1732 | 0, /* dummy rangetable index */ |
| 1733 | NULL, |
| 1734 | 0); |
| 1735 | resultRelInfo++; |
| 1736 | } |
| 1737 | estate->es_result_relations = resultRelInfos; |
| 1738 | estate->es_num_result_relations = list_length(rels); |
| 1739 | |
| 1740 | /* |
| 1741 | * Process all BEFORE STATEMENT TRUNCATE triggers before we begin |
| 1742 | * truncating (this is because one of them might throw an error). Also, if |
| 1743 | * we were to allow them to prevent statement execution, that would need |
| 1744 | * to be handled here. |
| 1745 | */ |
| 1746 | resultRelInfo = resultRelInfos; |
| 1747 | foreach(cell, rels) |
| 1748 | { |
| 1749 | estate->es_result_relation_info = resultRelInfo; |
| 1750 | ExecBSTruncateTriggers(estate, resultRelInfo); |
| 1751 | resultRelInfo++; |
| 1752 | } |
| 1753 | |
| 1754 | /* |
| 1755 | * OK, truncate each table. |
| 1756 | */ |
| 1757 | mySubid = GetCurrentSubTransactionId(); |
| 1758 | |
| 1759 | foreach(cell, rels) |
| 1760 | { |
| 1761 | Relation rel = (Relation) lfirst(cell); |
| 1762 | |
| 1763 | /* Skip partitioned tables as there is nothing to do */ |
| 1764 | if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) |
| 1765 | continue; |
| 1766 | |
| 1767 | /* |
| 1768 | * Normally, we need a transaction-safe truncation here. However, if |
| 1769 | * the table was either created in the current (sub)transaction or has |
| 1770 | * a new relfilenode in the current (sub)transaction, then we can just |
| 1771 | * truncate it in-place, because a rollback would cause the whole |
| 1772 | * table or the current physical file to be thrown away anyway. |
| 1773 | */ |
| 1774 | if (rel->rd_createSubid == mySubid || |
| 1775 | rel->rd_newRelfilenodeSubid == mySubid) |
| 1776 | { |
| 1777 | /* Immediate, non-rollbackable truncation is OK */ |
| 1778 | heap_truncate_one_rel(rel); |
| 1779 | } |
| 1780 | else |
| 1781 | { |
| 1782 | Oid heap_relid; |
| 1783 | Oid toast_relid; |
| 1784 | |
| 1785 | /* |
| 1786 | * This effectively deletes all rows in the table, and may be done |
| 1787 | * in a serializable transaction. In that case we must record a |
| 1788 | * rw-conflict in to this transaction from each transaction |
| 1789 | * holding a predicate lock on the table. |
| 1790 | */ |
| 1791 | CheckTableForSerializableConflictIn(rel); |
| 1792 | |
| 1793 | /* |
| 1794 | * Need the full transaction-safe pushups. |
| 1795 | * |
| 1796 | * Create a new empty storage file for the relation, and assign it |
| 1797 | * as the relfilenode value. The old storage file is scheduled for |
| 1798 | * deletion at commit. |
| 1799 | */ |
| 1800 | RelationSetNewRelfilenode(rel, rel->rd_rel->relpersistence); |
| 1801 | |
| 1802 | heap_relid = RelationGetRelid(rel); |
| 1803 | |
| 1804 | /* |
| 1805 | * The same for the toast table, if any. |
| 1806 | */ |
| 1807 | toast_relid = rel->rd_rel->reltoastrelid; |
| 1808 | if (OidIsValid(toast_relid)) |
| 1809 | { |
| 1810 | Relation toastrel = relation_open(toast_relid, |
| 1811 | AccessExclusiveLock); |
| 1812 | |
| 1813 | RelationSetNewRelfilenode(toastrel, |
| 1814 | toastrel->rd_rel->relpersistence); |
| 1815 | table_close(toastrel, NoLock); |
| 1816 | } |
| 1817 | |
| 1818 | /* |
| 1819 | * Reconstruct the indexes to match, and we're done. |
| 1820 | */ |
| 1821 | reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST, 0); |
| 1822 | } |
| 1823 | |
| 1824 | pgstat_count_truncate(rel); |
| 1825 | } |
| 1826 | |
| 1827 | /* |
| 1828 | * Restart owned sequences if we were asked to. |
| 1829 | */ |
| 1830 | foreach(cell, seq_relids) |
| 1831 | { |
| 1832 | Oid seq_relid = lfirst_oid(cell); |
| 1833 | |
| 1834 | ResetSequence(seq_relid); |
| 1835 | } |
| 1836 | |
| 1837 | /* |
| 1838 | * Write a WAL record to allow this set of actions to be logically |
| 1839 | * decoded. |
| 1840 | * |
| 1841 | * Assemble an array of relids so we can write a single WAL record for the |
| 1842 | * whole action. |
| 1843 | */ |
| 1844 | if (list_length(relids_logged) > 0) |
| 1845 | { |
| 1846 | xl_heap_truncate xlrec; |
| 1847 | int i = 0; |
| 1848 | |
| 1849 | /* should only get here if wal_level >= logical */ |
| 1850 | Assert(XLogLogicalInfoActive()); |
| 1851 | |
| 1852 | logrelids = palloc(list_length(relids_logged) * sizeof(Oid)); |
| 1853 | foreach(cell, relids_logged) |
| 1854 | logrelids[i++] = lfirst_oid(cell); |
| 1855 | |
| 1856 | xlrec.dbId = MyDatabaseId; |
| 1857 | xlrec.nrelids = list_length(relids_logged); |
| 1858 | xlrec.flags = 0; |
| 1859 | if (behavior == DROP_CASCADE) |
| 1860 | xlrec.flags |= XLH_TRUNCATE_CASCADE; |
| 1861 | if (restart_seqs) |
| 1862 | xlrec.flags |= XLH_TRUNCATE_RESTART_SEQS; |
| 1863 | |
| 1864 | XLogBeginInsert(); |
| 1865 | XLogRegisterData((char *) &xlrec, SizeOfHeapTruncate); |
| 1866 | XLogRegisterData((char *) logrelids, list_length(relids_logged) * sizeof(Oid)); |
| 1867 | |
| 1868 | XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN); |
| 1869 | |
| 1870 | (void) XLogInsert(RM_HEAP_ID, XLOG_HEAP_TRUNCATE); |
| 1871 | } |
| 1872 | |
| 1873 | /* |
| 1874 | * Process all AFTER STATEMENT TRUNCATE triggers. |
| 1875 | */ |
| 1876 | resultRelInfo = resultRelInfos; |
| 1877 | foreach(cell, rels) |
| 1878 | { |
| 1879 | estate->es_result_relation_info = resultRelInfo; |
| 1880 | ExecASTruncateTriggers(estate, resultRelInfo); |
| 1881 | resultRelInfo++; |
| 1882 | } |
| 1883 | |
| 1884 | /* Handle queued AFTER triggers */ |
| 1885 | AfterTriggerEndQuery(estate); |
| 1886 | |
| 1887 | /* We can clean up the EState now */ |
| 1888 | FreeExecutorState(estate); |
| 1889 | |
| 1890 | /* |
| 1891 | * Close any rels opened by CASCADE (can't do this while EState still |
| 1892 | * holds refs) |
| 1893 | */ |
| 1894 | rels = list_difference_ptr(rels, explicit_rels); |
| 1895 | foreach(cell, rels) |
| 1896 | { |
| 1897 | Relation rel = (Relation) lfirst(cell); |
| 1898 | |
| 1899 | table_close(rel, NoLock); |
| 1900 | } |
| 1901 | } |
| 1902 | |
| 1903 | /* |
| 1904 | * Check that a given relation is safe to truncate. Subroutine for |
| 1905 | * ExecuteTruncate() and RangeVarCallbackForTruncate(). |
| 1906 | */ |
| 1907 | static void |
| 1908 | truncate_check_rel(Oid relid, Form_pg_class reltuple) |
| 1909 | { |
| 1910 | AclResult aclresult; |
| 1911 | char *relname = NameStr(reltuple->relname); |
| 1912 | |
| 1913 | /* |
| 1914 | * Only allow truncate on regular tables and partitioned tables (although, |
| 1915 | * the latter are only being included here for the following checks; no |
| 1916 | * physical truncation will occur in their case.) |
| 1917 | */ |
| 1918 | if (reltuple->relkind != RELKIND_RELATION && |
| 1919 | reltuple->relkind != RELKIND_PARTITIONED_TABLE) |
| 1920 | ereport(ERROR, |
| 1921 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 1922 | errmsg("\"%s\" is not a table" , relname))); |
| 1923 | |
| 1924 | /* Permissions checks */ |
| 1925 | aclresult = pg_class_aclcheck(relid, GetUserId(), ACL_TRUNCATE); |
| 1926 | if (aclresult != ACLCHECK_OK) |
| 1927 | aclcheck_error(aclresult, get_relkind_objtype(reltuple->relkind), |
| 1928 | relname); |
| 1929 | |
| 1930 | if (!allowSystemTableMods && IsSystemClass(relid, reltuple)) |
| 1931 | ereport(ERROR, |
| 1932 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| 1933 | errmsg("permission denied: \"%s\" is a system catalog" , |
| 1934 | relname))); |
| 1935 | } |
| 1936 | |
| 1937 | /* |
| 1938 | * Set of extra sanity checks to check if a given relation is safe to |
| 1939 | * truncate. This is split with truncate_check_rel() as |
| 1940 | * RangeVarCallbackForTruncate() cannot open a Relation yet. |
| 1941 | */ |
| 1942 | static void |
| 1943 | truncate_check_activity(Relation rel) |
| 1944 | { |
| 1945 | /* |
| 1946 | * Don't allow truncate on temp tables of other backends ... their local |
| 1947 | * buffer manager is not going to cope. |
| 1948 | */ |
| 1949 | if (RELATION_IS_OTHER_TEMP(rel)) |
| 1950 | ereport(ERROR, |
| 1951 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 1952 | errmsg("cannot truncate temporary tables of other sessions" ))); |
| 1953 | |
| 1954 | /* |
| 1955 | * Also check for active uses of the relation in the current transaction, |
| 1956 | * including open scans and pending AFTER trigger events. |
| 1957 | */ |
| 1958 | CheckTableNotInUse(rel, "TRUNCATE" ); |
| 1959 | } |
| 1960 | |
| 1961 | /* |
| 1962 | * storage_name |
| 1963 | * returns the name corresponding to a typstorage/attstorage enum value |
| 1964 | */ |
| 1965 | static const char * |
| 1966 | storage_name(char c) |
| 1967 | { |
| 1968 | switch (c) |
| 1969 | { |
| 1970 | case 'p': |
| 1971 | return "PLAIN" ; |
| 1972 | case 'm': |
| 1973 | return "MAIN" ; |
| 1974 | case 'x': |
| 1975 | return "EXTENDED" ; |
| 1976 | case 'e': |
| 1977 | return "EXTERNAL" ; |
| 1978 | default: |
| 1979 | return "???" ; |
| 1980 | } |
| 1981 | } |
| 1982 | |
| 1983 | /*---------- |
| 1984 | * MergeAttributes |
| 1985 | * Returns new schema given initial schema and superclasses. |
| 1986 | * |
| 1987 | * Input arguments: |
| 1988 | * 'schema' is the column/attribute definition for the table. (It's a list |
| 1989 | * of ColumnDef's.) It is destructively changed. |
| 1990 | * 'supers' is a list of OIDs of parent relations, already locked by caller. |
| 1991 | * 'relpersistence' is a persistence type of the table. |
| 1992 | * 'is_partition' tells if the table is a partition |
| 1993 | * |
| 1994 | * Output arguments: |
| 1995 | * 'supconstr' receives a list of constraints belonging to the parents, |
| 1996 | * updated as necessary to be valid for the child. |
| 1997 | * |
| 1998 | * Return value: |
| 1999 | * Completed schema list. |
| 2000 | * |
| 2001 | * Notes: |
| 2002 | * The order in which the attributes are inherited is very important. |
| 2003 | * Intuitively, the inherited attributes should come first. If a table |
| 2004 | * inherits from multiple parents, the order of those attributes are |
| 2005 | * according to the order of the parents specified in CREATE TABLE. |
| 2006 | * |
| 2007 | * Here's an example: |
| 2008 | * |
| 2009 | * create table person (name text, age int4, location point); |
| 2010 | * create table emp (salary int4, manager text) inherits(person); |
| 2011 | * create table student (gpa float8) inherits (person); |
| 2012 | * create table stud_emp (percent int4) inherits (emp, student); |
| 2013 | * |
| 2014 | * The order of the attributes of stud_emp is: |
| 2015 | * |
| 2016 | * person {1:name, 2:age, 3:location} |
| 2017 | * / \ |
| 2018 | * {6:gpa} student emp {4:salary, 5:manager} |
| 2019 | * \ / |
| 2020 | * stud_emp {7:percent} |
| 2021 | * |
| 2022 | * If the same attribute name appears multiple times, then it appears |
| 2023 | * in the result table in the proper location for its first appearance. |
| 2024 | * |
| 2025 | * Constraints (including NOT NULL constraints) for the child table |
| 2026 | * are the union of all relevant constraints, from both the child schema |
| 2027 | * and parent tables. |
| 2028 | * |
| 2029 | * The default value for a child column is defined as: |
| 2030 | * (1) If the child schema specifies a default, that value is used. |
| 2031 | * (2) If neither the child nor any parent specifies a default, then |
| 2032 | * the column will not have a default. |
| 2033 | * (3) If conflicting defaults are inherited from different parents |
| 2034 | * (and not overridden by the child), an error is raised. |
| 2035 | * (4) Otherwise the inherited default is used. |
| 2036 | * Rule (3) is new in Postgres 7.1; in earlier releases you got a |
| 2037 | * rather arbitrary choice of which parent default to use. |
| 2038 | *---------- |
| 2039 | */ |
| 2040 | static List * |
| 2041 | MergeAttributes(List *schema, List *supers, char relpersistence, |
| 2042 | bool is_partition, List **supconstr) |
| 2043 | { |
| 2044 | ListCell *entry; |
| 2045 | List *inhSchema = NIL; |
| 2046 | List *constraints = NIL; |
| 2047 | bool have_bogus_defaults = false; |
| 2048 | int child_attno; |
| 2049 | static Node bogus_marker = {0}; /* marks conflicting defaults */ |
| 2050 | List *saved_schema = NIL; |
| 2051 | |
| 2052 | /* |
| 2053 | * Check for and reject tables with too many columns. We perform this |
| 2054 | * check relatively early for two reasons: (a) we don't run the risk of |
| 2055 | * overflowing an AttrNumber in subsequent code (b) an O(n^2) algorithm is |
| 2056 | * okay if we're processing <= 1600 columns, but could take minutes to |
| 2057 | * execute if the user attempts to create a table with hundreds of |
| 2058 | * thousands of columns. |
| 2059 | * |
| 2060 | * Note that we also need to check that we do not exceed this figure after |
| 2061 | * including columns from inherited relations. |
| 2062 | */ |
| 2063 | if (list_length(schema) > MaxHeapAttributeNumber) |
| 2064 | ereport(ERROR, |
| 2065 | (errcode(ERRCODE_TOO_MANY_COLUMNS), |
| 2066 | errmsg("tables can have at most %d columns" , |
| 2067 | MaxHeapAttributeNumber))); |
| 2068 | |
| 2069 | /* |
| 2070 | * Check for duplicate names in the explicit list of attributes. |
| 2071 | * |
| 2072 | * Although we might consider merging such entries in the same way that we |
| 2073 | * handle name conflicts for inherited attributes, it seems to make more |
| 2074 | * sense to assume such conflicts are errors. |
| 2075 | */ |
| 2076 | foreach(entry, schema) |
| 2077 | { |
| 2078 | ColumnDef *coldef = lfirst(entry); |
| 2079 | ListCell *rest = lnext(entry); |
| 2080 | ListCell *prev = entry; |
| 2081 | |
| 2082 | if (!is_partition && coldef->typeName == NULL) |
| 2083 | { |
| 2084 | /* |
| 2085 | * Typed table column option that does not belong to a column from |
| 2086 | * the type. This works because the columns from the type come |
| 2087 | * first in the list. (We omit this check for partition column |
| 2088 | * lists; those are processed separately below.) |
| 2089 | */ |
| 2090 | ereport(ERROR, |
| 2091 | (errcode(ERRCODE_UNDEFINED_COLUMN), |
| 2092 | errmsg("column \"%s\" does not exist" , |
| 2093 | coldef->colname))); |
| 2094 | } |
| 2095 | |
| 2096 | while (rest != NULL) |
| 2097 | { |
| 2098 | ColumnDef *restdef = lfirst(rest); |
| 2099 | ListCell *next = lnext(rest); /* need to save it in case we |
| 2100 | * delete it */ |
| 2101 | |
| 2102 | if (strcmp(coldef->colname, restdef->colname) == 0) |
| 2103 | { |
| 2104 | if (coldef->is_from_type) |
| 2105 | { |
| 2106 | /* |
| 2107 | * merge the column options into the column from the type |
| 2108 | */ |
| 2109 | coldef->is_not_null = restdef->is_not_null; |
| 2110 | coldef->raw_default = restdef->raw_default; |
| 2111 | coldef->cooked_default = restdef->cooked_default; |
| 2112 | coldef->constraints = restdef->constraints; |
| 2113 | coldef->is_from_type = false; |
| 2114 | schema = list_delete_cell(schema, rest, prev); |
| 2115 | |
| 2116 | /* |
| 2117 | * As two elements are merged and one is removed, we |
| 2118 | * should never finish with an empty list. |
| 2119 | */ |
| 2120 | Assert(schema != NIL); |
| 2121 | } |
| 2122 | else |
| 2123 | ereport(ERROR, |
| 2124 | (errcode(ERRCODE_DUPLICATE_COLUMN), |
| 2125 | errmsg("column \"%s\" specified more than once" , |
| 2126 | coldef->colname))); |
| 2127 | } |
| 2128 | prev = rest; |
| 2129 | rest = next; |
| 2130 | } |
| 2131 | } |
| 2132 | |
| 2133 | /* |
| 2134 | * In case of a partition, there are no new column definitions, only dummy |
| 2135 | * ColumnDefs created for column constraints. Set them aside for now and |
| 2136 | * process them at the end. |
| 2137 | */ |
| 2138 | if (is_partition) |
| 2139 | { |
| 2140 | saved_schema = schema; |
| 2141 | schema = NIL; |
| 2142 | } |
| 2143 | |
| 2144 | /* |
| 2145 | * Scan the parents left-to-right, and merge their attributes to form a |
| 2146 | * list of inherited attributes (inhSchema). Also check to see if we need |
| 2147 | * to inherit an OID column. |
| 2148 | */ |
| 2149 | child_attno = 0; |
| 2150 | foreach(entry, supers) |
| 2151 | { |
| 2152 | Oid parent = lfirst_oid(entry); |
| 2153 | Relation relation; |
| 2154 | TupleDesc tupleDesc; |
| 2155 | TupleConstr *constr; |
| 2156 | AttrNumber *newattno; |
| 2157 | AttrNumber parent_attno; |
| 2158 | |
| 2159 | /* caller already got lock */ |
| 2160 | relation = table_open(parent, NoLock); |
| 2161 | |
| 2162 | /* |
| 2163 | * Check for active uses of the parent partitioned table in the |
| 2164 | * current transaction, such as being used in some manner by an |
| 2165 | * enclosing command. |
| 2166 | */ |
| 2167 | if (is_partition) |
| 2168 | CheckTableNotInUse(relation, "CREATE TABLE .. PARTITION OF" ); |
| 2169 | |
| 2170 | /* |
| 2171 | * We do not allow partitioned tables and partitions to participate in |
| 2172 | * regular inheritance. |
| 2173 | */ |
| 2174 | if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && |
| 2175 | !is_partition) |
| 2176 | ereport(ERROR, |
| 2177 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 2178 | errmsg("cannot inherit from partitioned table \"%s\"" , |
| 2179 | RelationGetRelationName(relation)))); |
| 2180 | if (relation->rd_rel->relispartition && !is_partition) |
| 2181 | ereport(ERROR, |
| 2182 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 2183 | errmsg("cannot inherit from partition \"%s\"" , |
| 2184 | RelationGetRelationName(relation)))); |
| 2185 | |
| 2186 | if (relation->rd_rel->relkind != RELKIND_RELATION && |
| 2187 | relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE && |
| 2188 | relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) |
| 2189 | ereport(ERROR, |
| 2190 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 2191 | errmsg("inherited relation \"%s\" is not a table or foreign table" , |
| 2192 | RelationGetRelationName(relation)))); |
| 2193 | |
| 2194 | /* |
| 2195 | * If the parent is permanent, so must be all of its partitions. Note |
| 2196 | * that inheritance allows that case. |
| 2197 | */ |
| 2198 | if (is_partition && |
| 2199 | relation->rd_rel->relpersistence != RELPERSISTENCE_TEMP && |
| 2200 | relpersistence == RELPERSISTENCE_TEMP) |
| 2201 | ereport(ERROR, |
| 2202 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 2203 | errmsg("cannot create a temporary relation as partition of permanent relation \"%s\"" , |
| 2204 | RelationGetRelationName(relation)))); |
| 2205 | |
| 2206 | /* Permanent rels cannot inherit from temporary ones */ |
| 2207 | if (relpersistence != RELPERSISTENCE_TEMP && |
| 2208 | relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP) |
| 2209 | ereport(ERROR, |
| 2210 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 2211 | errmsg(!is_partition |
| 2212 | ? "cannot inherit from temporary relation \"%s\"" |
| 2213 | : "cannot create a permanent relation as partition of temporary relation \"%s\"" , |
| 2214 | RelationGetRelationName(relation)))); |
| 2215 | |
| 2216 | /* If existing rel is temp, it must belong to this session */ |
| 2217 | if (relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP && |
| 2218 | !relation->rd_islocaltemp) |
| 2219 | ereport(ERROR, |
| 2220 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 2221 | errmsg(!is_partition |
| 2222 | ? "cannot inherit from temporary relation of another session" |
| 2223 | : "cannot create as partition of temporary relation of another session" ))); |
| 2224 | |
| 2225 | /* |
| 2226 | * We should have an UNDER permission flag for this, but for now, |
| 2227 | * demand that creator of a child table own the parent. |
| 2228 | */ |
| 2229 | if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId())) |
| 2230 | aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(relation->rd_rel->relkind), |
| 2231 | RelationGetRelationName(relation)); |
| 2232 | |
| 2233 | tupleDesc = RelationGetDescr(relation); |
| 2234 | constr = tupleDesc->constr; |
| 2235 | |
| 2236 | /* |
| 2237 | * newattno[] will contain the child-table attribute numbers for the |
| 2238 | * attributes of this parent table. (They are not the same for |
| 2239 | * parents after the first one, nor if we have dropped columns.) |
| 2240 | */ |
| 2241 | newattno = (AttrNumber *) |
| 2242 | palloc0(tupleDesc->natts * sizeof(AttrNumber)); |
| 2243 | |
| 2244 | for (parent_attno = 1; parent_attno <= tupleDesc->natts; |
| 2245 | parent_attno++) |
| 2246 | { |
| 2247 | Form_pg_attribute attribute = TupleDescAttr(tupleDesc, |
| 2248 | parent_attno - 1); |
| 2249 | char *attributeName = NameStr(attribute->attname); |
| 2250 | int exist_attno; |
| 2251 | ColumnDef *def; |
| 2252 | |
| 2253 | /* |
| 2254 | * Ignore dropped columns in the parent. |
| 2255 | */ |
| 2256 | if (attribute->attisdropped) |
| 2257 | continue; /* leave newattno entry as zero */ |
| 2258 | |
| 2259 | /* |
| 2260 | * Does it conflict with some previously inherited column? |
| 2261 | */ |
| 2262 | exist_attno = findAttrByName(attributeName, inhSchema); |
| 2263 | if (exist_attno > 0) |
| 2264 | { |
| 2265 | Oid defTypeId; |
| 2266 | int32 deftypmod; |
| 2267 | Oid defCollId; |
| 2268 | |
| 2269 | /* |
| 2270 | * Yes, try to merge the two column definitions. They must |
| 2271 | * have the same type, typmod, and collation. |
| 2272 | */ |
| 2273 | ereport(NOTICE, |
| 2274 | (errmsg("merging multiple inherited definitions of column \"%s\"" , |
| 2275 | attributeName))); |
| 2276 | def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1); |
| 2277 | typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod); |
| 2278 | if (defTypeId != attribute->atttypid || |
| 2279 | deftypmod != attribute->atttypmod) |
| 2280 | ereport(ERROR, |
| 2281 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
| 2282 | errmsg("inherited column \"%s\" has a type conflict" , |
| 2283 | attributeName), |
| 2284 | errdetail("%s versus %s" , |
| 2285 | format_type_with_typemod(defTypeId, |
| 2286 | deftypmod), |
| 2287 | format_type_with_typemod(attribute->atttypid, |
| 2288 | attribute->atttypmod)))); |
| 2289 | defCollId = GetColumnDefCollation(NULL, def, defTypeId); |
| 2290 | if (defCollId != attribute->attcollation) |
| 2291 | ereport(ERROR, |
| 2292 | (errcode(ERRCODE_COLLATION_MISMATCH), |
| 2293 | errmsg("inherited column \"%s\" has a collation conflict" , |
| 2294 | attributeName), |
| 2295 | errdetail("\"%s\" versus \"%s\"" , |
| 2296 | get_collation_name(defCollId), |
| 2297 | get_collation_name(attribute->attcollation)))); |
| 2298 | |
| 2299 | /* Copy storage parameter */ |
| 2300 | if (def->storage == 0) |
| 2301 | def->storage = attribute->attstorage; |
| 2302 | else if (def->storage != attribute->attstorage) |
| 2303 | ereport(ERROR, |
| 2304 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
| 2305 | errmsg("inherited column \"%s\" has a storage parameter conflict" , |
| 2306 | attributeName), |
| 2307 | errdetail("%s versus %s" , |
| 2308 | storage_name(def->storage), |
| 2309 | storage_name(attribute->attstorage)))); |
| 2310 | |
| 2311 | def->inhcount++; |
| 2312 | /* Merge of NOT NULL constraints = OR 'em together */ |
| 2313 | def->is_not_null |= attribute->attnotnull; |
| 2314 | /* Default and other constraints are handled below */ |
| 2315 | newattno[parent_attno - 1] = exist_attno; |
| 2316 | |
| 2317 | /* Check for GENERATED conflicts */ |
| 2318 | if (def->generated != attribute->attgenerated) |
| 2319 | ereport(ERROR, |
| 2320 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
| 2321 | errmsg("inherited column \"%s\" has a generation conflict" , |
| 2322 | attributeName))); |
| 2323 | } |
| 2324 | else |
| 2325 | { |
| 2326 | /* |
| 2327 | * No, create a new inherited column |
| 2328 | */ |
| 2329 | def = makeNode(ColumnDef); |
| 2330 | def->colname = pstrdup(attributeName); |
| 2331 | def->typeName = makeTypeNameFromOid(attribute->atttypid, |
| 2332 | attribute->atttypmod); |
| 2333 | def->inhcount = 1; |
| 2334 | def->is_local = false; |
| 2335 | def->is_not_null = attribute->attnotnull; |
| 2336 | def->is_from_type = false; |
| 2337 | def->storage = attribute->attstorage; |
| 2338 | def->raw_default = NULL; |
| 2339 | def->cooked_default = NULL; |
| 2340 | def->generated = attribute->attgenerated; |
| 2341 | def->collClause = NULL; |
| 2342 | def->collOid = attribute->attcollation; |
| 2343 | def->constraints = NIL; |
| 2344 | def->location = -1; |
| 2345 | inhSchema = lappend(inhSchema, def); |
| 2346 | newattno[parent_attno - 1] = ++child_attno; |
| 2347 | } |
| 2348 | |
| 2349 | /* |
| 2350 | * Copy default if any |
| 2351 | */ |
| 2352 | if (attribute->atthasdef) |
| 2353 | { |
| 2354 | Node *this_default = NULL; |
| 2355 | AttrDefault *attrdef; |
| 2356 | int i; |
| 2357 | |
| 2358 | /* Find default in constraint structure */ |
| 2359 | Assert(constr != NULL); |
| 2360 | attrdef = constr->defval; |
| 2361 | for (i = 0; i < constr->num_defval; i++) |
| 2362 | { |
| 2363 | if (attrdef[i].adnum == parent_attno) |
| 2364 | { |
| 2365 | this_default = stringToNode(attrdef[i].adbin); |
| 2366 | break; |
| 2367 | } |
| 2368 | } |
| 2369 | Assert(this_default != NULL); |
| 2370 | |
| 2371 | /* |
| 2372 | * If default expr could contain any vars, we'd need to fix |
| 2373 | * 'em, but it can't; so default is ready to apply to child. |
| 2374 | * |
| 2375 | * If we already had a default from some prior parent, check |
| 2376 | * to see if they are the same. If so, no problem; if not, |
| 2377 | * mark the column as having a bogus default. Below, we will |
| 2378 | * complain if the bogus default isn't overridden by the child |
| 2379 | * schema. |
| 2380 | */ |
| 2381 | Assert(def->raw_default == NULL); |
| 2382 | if (def->cooked_default == NULL) |
| 2383 | def->cooked_default = this_default; |
| 2384 | else if (!equal(def->cooked_default, this_default)) |
| 2385 | { |
| 2386 | def->cooked_default = &bogus_marker; |
| 2387 | have_bogus_defaults = true; |
| 2388 | } |
| 2389 | } |
| 2390 | } |
| 2391 | |
| 2392 | /* |
| 2393 | * Now copy the CHECK constraints of this parent, adjusting attnos |
| 2394 | * using the completed newattno[] map. Identically named constraints |
| 2395 | * are merged if possible, else we throw error. |
| 2396 | */ |
| 2397 | if (constr && constr->num_check > 0) |
| 2398 | { |
| 2399 | ConstrCheck *check = constr->check; |
| 2400 | int i; |
| 2401 | |
| 2402 | for (i = 0; i < constr->num_check; i++) |
| 2403 | { |
| 2404 | char *name = check[i].ccname; |
| 2405 | Node *expr; |
| 2406 | bool found_whole_row; |
| 2407 | |
| 2408 | /* ignore if the constraint is non-inheritable */ |
| 2409 | if (check[i].ccnoinherit) |
| 2410 | continue; |
| 2411 | |
| 2412 | /* Adjust Vars to match new table's column numbering */ |
| 2413 | expr = map_variable_attnos(stringToNode(check[i].ccbin), |
| 2414 | 1, 0, |
| 2415 | newattno, tupleDesc->natts, |
| 2416 | InvalidOid, &found_whole_row); |
| 2417 | |
| 2418 | /* |
| 2419 | * For the moment we have to reject whole-row variables. We |
| 2420 | * could convert them, if we knew the new table's rowtype OID, |
| 2421 | * but that hasn't been assigned yet. |
| 2422 | */ |
| 2423 | if (found_whole_row) |
| 2424 | ereport(ERROR, |
| 2425 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 2426 | errmsg("cannot convert whole-row table reference" ), |
| 2427 | errdetail("Constraint \"%s\" contains a whole-row reference to table \"%s\"." , |
| 2428 | name, |
| 2429 | RelationGetRelationName(relation)))); |
| 2430 | |
| 2431 | /* check for duplicate */ |
| 2432 | if (!MergeCheckConstraint(constraints, name, expr)) |
| 2433 | { |
| 2434 | /* nope, this is a new one */ |
| 2435 | CookedConstraint *cooked; |
| 2436 | |
| 2437 | cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint)); |
| 2438 | cooked->contype = CONSTR_CHECK; |
| 2439 | cooked->conoid = InvalidOid; /* until created */ |
| 2440 | cooked->name = pstrdup(name); |
| 2441 | cooked->attnum = 0; /* not used for constraints */ |
| 2442 | cooked->expr = expr; |
| 2443 | cooked->skip_validation = false; |
| 2444 | cooked->is_local = false; |
| 2445 | cooked->inhcount = 1; |
| 2446 | cooked->is_no_inherit = false; |
| 2447 | constraints = lappend(constraints, cooked); |
| 2448 | } |
| 2449 | } |
| 2450 | } |
| 2451 | |
| 2452 | pfree(newattno); |
| 2453 | |
| 2454 | /* |
| 2455 | * Close the parent rel, but keep our lock on it until xact commit. |
| 2456 | * That will prevent someone else from deleting or ALTERing the parent |
| 2457 | * before the child is committed. |
| 2458 | */ |
| 2459 | table_close(relation, NoLock); |
| 2460 | } |
| 2461 | |
| 2462 | /* |
| 2463 | * If we had no inherited attributes, the result schema is just the |
| 2464 | * explicitly declared columns. Otherwise, we need to merge the declared |
| 2465 | * columns into the inherited schema list. Although, we never have any |
| 2466 | * explicitly declared columns if the table is a partition. |
| 2467 | */ |
| 2468 | if (inhSchema != NIL) |
| 2469 | { |
| 2470 | int schema_attno = 0; |
| 2471 | |
| 2472 | foreach(entry, schema) |
| 2473 | { |
| 2474 | ColumnDef *newdef = lfirst(entry); |
| 2475 | char *attributeName = newdef->colname; |
| 2476 | int exist_attno; |
| 2477 | |
| 2478 | schema_attno++; |
| 2479 | |
| 2480 | /* |
| 2481 | * Does it conflict with some previously inherited column? |
| 2482 | */ |
| 2483 | exist_attno = findAttrByName(attributeName, inhSchema); |
| 2484 | if (exist_attno > 0) |
| 2485 | { |
| 2486 | ColumnDef *def; |
| 2487 | Oid defTypeId, |
| 2488 | newTypeId; |
| 2489 | int32 deftypmod, |
| 2490 | newtypmod; |
| 2491 | Oid defcollid, |
| 2492 | newcollid; |
| 2493 | |
| 2494 | /* |
| 2495 | * Partitions have only one parent and have no column |
| 2496 | * definitions of their own, so conflict should never occur. |
| 2497 | */ |
| 2498 | Assert(!is_partition); |
| 2499 | |
| 2500 | /* |
| 2501 | * Yes, try to merge the two column definitions. They must |
| 2502 | * have the same type, typmod, and collation. |
| 2503 | */ |
| 2504 | if (exist_attno == schema_attno) |
| 2505 | ereport(NOTICE, |
| 2506 | (errmsg("merging column \"%s\" with inherited definition" , |
| 2507 | attributeName))); |
| 2508 | else |
| 2509 | ereport(NOTICE, |
| 2510 | (errmsg("moving and merging column \"%s\" with inherited definition" , attributeName), |
| 2511 | errdetail("User-specified column moved to the position of the inherited column." ))); |
| 2512 | def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1); |
| 2513 | typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod); |
| 2514 | typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod); |
| 2515 | if (defTypeId != newTypeId || deftypmod != newtypmod) |
| 2516 | ereport(ERROR, |
| 2517 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
| 2518 | errmsg("column \"%s\" has a type conflict" , |
| 2519 | attributeName), |
| 2520 | errdetail("%s versus %s" , |
| 2521 | format_type_with_typemod(defTypeId, |
| 2522 | deftypmod), |
| 2523 | format_type_with_typemod(newTypeId, |
| 2524 | newtypmod)))); |
| 2525 | defcollid = GetColumnDefCollation(NULL, def, defTypeId); |
| 2526 | newcollid = GetColumnDefCollation(NULL, newdef, newTypeId); |
| 2527 | if (defcollid != newcollid) |
| 2528 | ereport(ERROR, |
| 2529 | (errcode(ERRCODE_COLLATION_MISMATCH), |
| 2530 | errmsg("column \"%s\" has a collation conflict" , |
| 2531 | attributeName), |
| 2532 | errdetail("\"%s\" versus \"%s\"" , |
| 2533 | get_collation_name(defcollid), |
| 2534 | get_collation_name(newcollid)))); |
| 2535 | |
| 2536 | /* |
| 2537 | * Identity is never inherited. The new column can have an |
| 2538 | * identity definition, so we always just take that one. |
| 2539 | */ |
| 2540 | def->identity = newdef->identity; |
| 2541 | |
| 2542 | /* Copy storage parameter */ |
| 2543 | if (def->storage == 0) |
| 2544 | def->storage = newdef->storage; |
| 2545 | else if (newdef->storage != 0 && def->storage != newdef->storage) |
| 2546 | ereport(ERROR, |
| 2547 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
| 2548 | errmsg("column \"%s\" has a storage parameter conflict" , |
| 2549 | attributeName), |
| 2550 | errdetail("%s versus %s" , |
| 2551 | storage_name(def->storage), |
| 2552 | storage_name(newdef->storage)))); |
| 2553 | |
| 2554 | /* Mark the column as locally defined */ |
| 2555 | def->is_local = true; |
| 2556 | /* Merge of NOT NULL constraints = OR 'em together */ |
| 2557 | def->is_not_null |= newdef->is_not_null; |
| 2558 | /* If new def has a default, override previous default */ |
| 2559 | if (newdef->raw_default != NULL) |
| 2560 | { |
| 2561 | def->raw_default = newdef->raw_default; |
| 2562 | def->cooked_default = newdef->cooked_default; |
| 2563 | } |
| 2564 | } |
| 2565 | else |
| 2566 | { |
| 2567 | /* |
| 2568 | * No, attach new column to result schema |
| 2569 | */ |
| 2570 | inhSchema = lappend(inhSchema, newdef); |
| 2571 | } |
| 2572 | } |
| 2573 | |
| 2574 | schema = inhSchema; |
| 2575 | |
| 2576 | /* |
| 2577 | * Check that we haven't exceeded the legal # of columns after merging |
| 2578 | * in inherited columns. |
| 2579 | */ |
| 2580 | if (list_length(schema) > MaxHeapAttributeNumber) |
| 2581 | ereport(ERROR, |
| 2582 | (errcode(ERRCODE_TOO_MANY_COLUMNS), |
| 2583 | errmsg("tables can have at most %d columns" , |
| 2584 | MaxHeapAttributeNumber))); |
| 2585 | } |
| 2586 | |
| 2587 | /* |
| 2588 | * Now that we have the column definition list for a partition, we can |
| 2589 | * check whether the columns referenced in the column constraint specs |
| 2590 | * actually exist. Also, we merge NOT NULL and defaults into each |
| 2591 | * corresponding column definition. |
| 2592 | */ |
| 2593 | if (is_partition) |
| 2594 | { |
| 2595 | foreach(entry, saved_schema) |
| 2596 | { |
| 2597 | ColumnDef *restdef = lfirst(entry); |
| 2598 | bool found = false; |
| 2599 | ListCell *l; |
| 2600 | |
| 2601 | foreach(l, schema) |
| 2602 | { |
| 2603 | ColumnDef *coldef = lfirst(l); |
| 2604 | |
| 2605 | if (strcmp(coldef->colname, restdef->colname) == 0) |
| 2606 | { |
| 2607 | found = true; |
| 2608 | coldef->is_not_null |= restdef->is_not_null; |
| 2609 | |
| 2610 | /* |
| 2611 | * Override the parent's default value for this column |
| 2612 | * (coldef->cooked_default) with the partition's local |
| 2613 | * definition (restdef->raw_default), if there's one. It |
| 2614 | * should be physically impossible to get a cooked default |
| 2615 | * in the local definition or a raw default in the |
| 2616 | * inherited definition, but make sure they're nulls, for |
| 2617 | * future-proofing. |
| 2618 | */ |
| 2619 | Assert(restdef->cooked_default == NULL); |
| 2620 | Assert(coldef->raw_default == NULL); |
| 2621 | if (restdef->raw_default) |
| 2622 | { |
| 2623 | coldef->raw_default = restdef->raw_default; |
| 2624 | coldef->cooked_default = NULL; |
| 2625 | } |
| 2626 | } |
| 2627 | } |
| 2628 | |
| 2629 | /* complain for constraints on columns not in parent */ |
| 2630 | if (!found) |
| 2631 | ereport(ERROR, |
| 2632 | (errcode(ERRCODE_UNDEFINED_COLUMN), |
| 2633 | errmsg("column \"%s\" does not exist" , |
| 2634 | restdef->colname))); |
| 2635 | } |
| 2636 | } |
| 2637 | |
| 2638 | /* |
| 2639 | * If we found any conflicting parent default values, check to make sure |
| 2640 | * they were overridden by the child. |
| 2641 | */ |
| 2642 | if (have_bogus_defaults) |
| 2643 | { |
| 2644 | foreach(entry, schema) |
| 2645 | { |
| 2646 | ColumnDef *def = lfirst(entry); |
| 2647 | |
| 2648 | if (def->cooked_default == &bogus_marker) |
| 2649 | ereport(ERROR, |
| 2650 | (errcode(ERRCODE_INVALID_COLUMN_DEFINITION), |
| 2651 | errmsg("column \"%s\" inherits conflicting default values" , |
| 2652 | def->colname), |
| 2653 | errhint("To resolve the conflict, specify a default explicitly." ))); |
| 2654 | } |
| 2655 | } |
| 2656 | |
| 2657 | *supconstr = constraints; |
| 2658 | return schema; |
| 2659 | } |
| 2660 | |
| 2661 | |
| 2662 | /* |
| 2663 | * MergeCheckConstraint |
| 2664 | * Try to merge an inherited CHECK constraint with previous ones |
| 2665 | * |
| 2666 | * If we inherit identically-named constraints from multiple parents, we must |
| 2667 | * merge them, or throw an error if they don't have identical definitions. |
| 2668 | * |
| 2669 | * constraints is a list of CookedConstraint structs for previous constraints. |
| 2670 | * |
| 2671 | * Returns true if merged (constraint is a duplicate), or false if it's |
| 2672 | * got a so-far-unique name, or throws error if conflict. |
| 2673 | */ |
| 2674 | static bool |
| 2675 | MergeCheckConstraint(List *constraints, char *name, Node *expr) |
| 2676 | { |
| 2677 | ListCell *lc; |
| 2678 | |
| 2679 | foreach(lc, constraints) |
| 2680 | { |
| 2681 | CookedConstraint *ccon = (CookedConstraint *) lfirst(lc); |
| 2682 | |
| 2683 | Assert(ccon->contype == CONSTR_CHECK); |
| 2684 | |
| 2685 | /* Non-matching names never conflict */ |
| 2686 | if (strcmp(ccon->name, name) != 0) |
| 2687 | continue; |
| 2688 | |
| 2689 | if (equal(expr, ccon->expr)) |
| 2690 | { |
| 2691 | /* OK to merge */ |
| 2692 | ccon->inhcount++; |
| 2693 | return true; |
| 2694 | } |
| 2695 | |
| 2696 | ereport(ERROR, |
| 2697 | (errcode(ERRCODE_DUPLICATE_OBJECT), |
| 2698 | errmsg("check constraint name \"%s\" appears multiple times but with different expressions" , |
| 2699 | name))); |
| 2700 | } |
| 2701 | |
| 2702 | return false; |
| 2703 | } |
| 2704 | |
| 2705 | |
| 2706 | /* |
| 2707 | * StoreCatalogInheritance |
| 2708 | * Updates the system catalogs with proper inheritance information. |
| 2709 | * |
| 2710 | * supers is a list of the OIDs of the new relation's direct ancestors. |
| 2711 | */ |
| 2712 | static void |
| 2713 | StoreCatalogInheritance(Oid relationId, List *supers, |
| 2714 | bool child_is_partition) |
| 2715 | { |
| 2716 | Relation relation; |
| 2717 | int32 seqNumber; |
| 2718 | ListCell *entry; |
| 2719 | |
| 2720 | /* |
| 2721 | * sanity checks |
| 2722 | */ |
| 2723 | AssertArg(OidIsValid(relationId)); |
| 2724 | |
| 2725 | if (supers == NIL) |
| 2726 | return; |
| 2727 | |
| 2728 | /* |
| 2729 | * Store INHERITS information in pg_inherits using direct ancestors only. |
| 2730 | * Also enter dependencies on the direct ancestors, and make sure they are |
| 2731 | * marked with relhassubclass = true. |
| 2732 | * |
| 2733 | * (Once upon a time, both direct and indirect ancestors were found here |
| 2734 | * and then entered into pg_ipl. Since that catalog doesn't exist |
| 2735 | * anymore, there's no need to look for indirect ancestors.) |
| 2736 | */ |
| 2737 | relation = table_open(InheritsRelationId, RowExclusiveLock); |
| 2738 | |
| 2739 | seqNumber = 1; |
| 2740 | foreach(entry, supers) |
| 2741 | { |
| 2742 | Oid parentOid = lfirst_oid(entry); |
| 2743 | |
| 2744 | StoreCatalogInheritance1(relationId, parentOid, seqNumber, relation, |
| 2745 | child_is_partition); |
| 2746 | seqNumber++; |
| 2747 | } |
| 2748 | |
| 2749 | table_close(relation, RowExclusiveLock); |
| 2750 | } |
| 2751 | |
| 2752 | /* |
| 2753 | * Make catalog entries showing relationId as being an inheritance child |
| 2754 | * of parentOid. inhRelation is the already-opened pg_inherits catalog. |
| 2755 | */ |
| 2756 | static void |
| 2757 | StoreCatalogInheritance1(Oid relationId, Oid parentOid, |
| 2758 | int32 seqNumber, Relation inhRelation, |
| 2759 | bool child_is_partition) |
| 2760 | { |
| 2761 | ObjectAddress childobject, |
| 2762 | parentobject; |
| 2763 | |
| 2764 | /* store the pg_inherits row */ |
| 2765 | StoreSingleInheritance(relationId, parentOid, seqNumber); |
| 2766 | |
| 2767 | /* |
| 2768 | * Store a dependency too |
| 2769 | */ |
| 2770 | parentobject.classId = RelationRelationId; |
| 2771 | parentobject.objectId = parentOid; |
| 2772 | parentobject.objectSubId = 0; |
| 2773 | childobject.classId = RelationRelationId; |
| 2774 | childobject.objectId = relationId; |
| 2775 | childobject.objectSubId = 0; |
| 2776 | |
| 2777 | recordDependencyOn(&childobject, &parentobject, |
| 2778 | child_dependency_type(child_is_partition)); |
| 2779 | |
| 2780 | /* |
| 2781 | * Post creation hook of this inheritance. Since object_access_hook |
| 2782 | * doesn't take multiple object identifiers, we relay oid of parent |
| 2783 | * relation using auxiliary_id argument. |
| 2784 | */ |
| 2785 | InvokeObjectPostAlterHookArg(InheritsRelationId, |
| 2786 | relationId, 0, |
| 2787 | parentOid, false); |
| 2788 | |
| 2789 | /* |
| 2790 | * Mark the parent as having subclasses. |
| 2791 | */ |
| 2792 | SetRelationHasSubclass(parentOid, true); |
| 2793 | } |
| 2794 | |
| 2795 | /* |
| 2796 | * Look for an existing schema entry with the given name. |
| 2797 | * |
| 2798 | * Returns the index (starting with 1) if attribute already exists in schema, |
| 2799 | * 0 if it doesn't. |
| 2800 | */ |
| 2801 | static int |
| 2802 | findAttrByName(const char *attributeName, List *schema) |
| 2803 | { |
| 2804 | ListCell *s; |
| 2805 | int i = 1; |
| 2806 | |
| 2807 | foreach(s, schema) |
| 2808 | { |
| 2809 | ColumnDef *def = lfirst(s); |
| 2810 | |
| 2811 | if (strcmp(attributeName, def->colname) == 0) |
| 2812 | return i; |
| 2813 | |
| 2814 | i++; |
| 2815 | } |
| 2816 | return 0; |
| 2817 | } |
| 2818 | |
| 2819 | |
| 2820 | /* |
| 2821 | * SetRelationHasSubclass |
| 2822 | * Set the value of the relation's relhassubclass field in pg_class. |
| 2823 | * |
| 2824 | * NOTE: caller must be holding an appropriate lock on the relation. |
| 2825 | * ShareUpdateExclusiveLock is sufficient. |
| 2826 | * |
| 2827 | * NOTE: an important side-effect of this operation is that an SI invalidation |
| 2828 | * message is sent out to all backends --- including me --- causing plans |
| 2829 | * referencing the relation to be rebuilt with the new list of children. |
| 2830 | * This must happen even if we find that no change is needed in the pg_class |
| 2831 | * row. |
| 2832 | */ |
| 2833 | void |
| 2834 | SetRelationHasSubclass(Oid relationId, bool relhassubclass) |
| 2835 | { |
| 2836 | Relation relationRelation; |
| 2837 | HeapTuple tuple; |
| 2838 | Form_pg_class classtuple; |
| 2839 | |
| 2840 | /* |
| 2841 | * Fetch a modifiable copy of the tuple, modify it, update pg_class. |
| 2842 | */ |
| 2843 | relationRelation = table_open(RelationRelationId, RowExclusiveLock); |
| 2844 | tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relationId)); |
| 2845 | if (!HeapTupleIsValid(tuple)) |
| 2846 | elog(ERROR, "cache lookup failed for relation %u" , relationId); |
| 2847 | classtuple = (Form_pg_class) GETSTRUCT(tuple); |
| 2848 | |
| 2849 | if (classtuple->relhassubclass != relhassubclass) |
| 2850 | { |
| 2851 | classtuple->relhassubclass = relhassubclass; |
| 2852 | CatalogTupleUpdate(relationRelation, &tuple->t_self, tuple); |
| 2853 | } |
| 2854 | else |
| 2855 | { |
| 2856 | /* no need to change tuple, but force relcache rebuild anyway */ |
| 2857 | CacheInvalidateRelcacheByTuple(tuple); |
| 2858 | } |
| 2859 | |
| 2860 | heap_freetuple(tuple); |
| 2861 | table_close(relationRelation, RowExclusiveLock); |
| 2862 | } |
| 2863 | |
| 2864 | /* |
| 2865 | * renameatt_check - basic sanity checks before attribute rename |
| 2866 | */ |
| 2867 | static void |
| 2868 | renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing) |
| 2869 | { |
| 2870 | char relkind = classform->relkind; |
| 2871 | |
| 2872 | if (classform->reloftype && !recursing) |
| 2873 | ereport(ERROR, |
| 2874 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 2875 | errmsg("cannot rename column of typed table" ))); |
| 2876 | |
| 2877 | /* |
| 2878 | * Renaming the columns of sequences or toast tables doesn't actually |
| 2879 | * break anything from the system's point of view, since internal |
| 2880 | * references are by attnum. But it doesn't seem right to allow users to |
| 2881 | * change names that are hardcoded into the system, hence the following |
| 2882 | * restriction. |
| 2883 | */ |
| 2884 | if (relkind != RELKIND_RELATION && |
| 2885 | relkind != RELKIND_VIEW && |
| 2886 | relkind != RELKIND_MATVIEW && |
| 2887 | relkind != RELKIND_COMPOSITE_TYPE && |
| 2888 | relkind != RELKIND_INDEX && |
| 2889 | relkind != RELKIND_PARTITIONED_INDEX && |
| 2890 | relkind != RELKIND_FOREIGN_TABLE && |
| 2891 | relkind != RELKIND_PARTITIONED_TABLE) |
| 2892 | ereport(ERROR, |
| 2893 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 2894 | errmsg("\"%s\" is not a table, view, materialized view, composite type, index, or foreign table" , |
| 2895 | NameStr(classform->relname)))); |
| 2896 | |
| 2897 | /* |
| 2898 | * permissions checking. only the owner of a class can change its schema. |
| 2899 | */ |
| 2900 | if (!pg_class_ownercheck(myrelid, GetUserId())) |
| 2901 | aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(myrelid)), |
| 2902 | NameStr(classform->relname)); |
| 2903 | if (!allowSystemTableMods && IsSystemClass(myrelid, classform)) |
| 2904 | ereport(ERROR, |
| 2905 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| 2906 | errmsg("permission denied: \"%s\" is a system catalog" , |
| 2907 | NameStr(classform->relname)))); |
| 2908 | } |
| 2909 | |
| 2910 | /* |
| 2911 | * renameatt_internal - workhorse for renameatt |
| 2912 | * |
| 2913 | * Return value is the attribute number in the 'myrelid' relation. |
| 2914 | */ |
| 2915 | static AttrNumber |
| 2916 | renameatt_internal(Oid myrelid, |
| 2917 | const char *oldattname, |
| 2918 | const char *newattname, |
| 2919 | bool recurse, |
| 2920 | bool recursing, |
| 2921 | int expected_parents, |
| 2922 | DropBehavior behavior) |
| 2923 | { |
| 2924 | Relation targetrelation; |
| 2925 | Relation attrelation; |
| 2926 | HeapTuple atttup; |
| 2927 | Form_pg_attribute attform; |
| 2928 | AttrNumber attnum; |
| 2929 | |
| 2930 | /* |
| 2931 | * Grab an exclusive lock on the target table, which we will NOT release |
| 2932 | * until end of transaction. |
| 2933 | */ |
| 2934 | targetrelation = relation_open(myrelid, AccessExclusiveLock); |
| 2935 | renameatt_check(myrelid, RelationGetForm(targetrelation), recursing); |
| 2936 | |
| 2937 | /* |
| 2938 | * if the 'recurse' flag is set then we are supposed to rename this |
| 2939 | * attribute in all classes that inherit from 'relname' (as well as in |
| 2940 | * 'relname'). |
| 2941 | * |
| 2942 | * any permissions or problems with duplicate attributes will cause the |
| 2943 | * whole transaction to abort, which is what we want -- all or nothing. |
| 2944 | */ |
| 2945 | if (recurse) |
| 2946 | { |
| 2947 | List *child_oids, |
| 2948 | *child_numparents; |
| 2949 | ListCell *lo, |
| 2950 | *li; |
| 2951 | |
| 2952 | /* |
| 2953 | * we need the number of parents for each child so that the recursive |
| 2954 | * calls to renameatt() can determine whether there are any parents |
| 2955 | * outside the inheritance hierarchy being processed. |
| 2956 | */ |
| 2957 | child_oids = find_all_inheritors(myrelid, AccessExclusiveLock, |
| 2958 | &child_numparents); |
| 2959 | |
| 2960 | /* |
| 2961 | * find_all_inheritors does the recursive search of the inheritance |
| 2962 | * hierarchy, so all we have to do is process all of the relids in the |
| 2963 | * list that it returns. |
| 2964 | */ |
| 2965 | forboth(lo, child_oids, li, child_numparents) |
| 2966 | { |
| 2967 | Oid childrelid = lfirst_oid(lo); |
| 2968 | int numparents = lfirst_int(li); |
| 2969 | |
| 2970 | if (childrelid == myrelid) |
| 2971 | continue; |
| 2972 | /* note we need not recurse again */ |
| 2973 | renameatt_internal(childrelid, oldattname, newattname, false, true, numparents, behavior); |
| 2974 | } |
| 2975 | } |
| 2976 | else |
| 2977 | { |
| 2978 | /* |
| 2979 | * If we are told not to recurse, there had better not be any child |
| 2980 | * tables; else the rename would put them out of step. |
| 2981 | * |
| 2982 | * expected_parents will only be 0 if we are not already recursing. |
| 2983 | */ |
| 2984 | if (expected_parents == 0 && |
| 2985 | find_inheritance_children(myrelid, NoLock) != NIL) |
| 2986 | ereport(ERROR, |
| 2987 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 2988 | errmsg("inherited column \"%s\" must be renamed in child tables too" , |
| 2989 | oldattname))); |
| 2990 | } |
| 2991 | |
| 2992 | /* rename attributes in typed tables of composite type */ |
| 2993 | if (targetrelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) |
| 2994 | { |
| 2995 | List *child_oids; |
| 2996 | ListCell *lo; |
| 2997 | |
| 2998 | child_oids = find_typed_table_dependencies(targetrelation->rd_rel->reltype, |
| 2999 | RelationGetRelationName(targetrelation), |
| 3000 | behavior); |
| 3001 | |
| 3002 | foreach(lo, child_oids) |
| 3003 | renameatt_internal(lfirst_oid(lo), oldattname, newattname, true, true, 0, behavior); |
| 3004 | } |
| 3005 | |
| 3006 | attrelation = table_open(AttributeRelationId, RowExclusiveLock); |
| 3007 | |
| 3008 | atttup = SearchSysCacheCopyAttName(myrelid, oldattname); |
| 3009 | if (!HeapTupleIsValid(atttup)) |
| 3010 | ereport(ERROR, |
| 3011 | (errcode(ERRCODE_UNDEFINED_COLUMN), |
| 3012 | errmsg("column \"%s\" does not exist" , |
| 3013 | oldattname))); |
| 3014 | attform = (Form_pg_attribute) GETSTRUCT(atttup); |
| 3015 | |
| 3016 | attnum = attform->attnum; |
| 3017 | if (attnum <= 0) |
| 3018 | ereport(ERROR, |
| 3019 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 3020 | errmsg("cannot rename system column \"%s\"" , |
| 3021 | oldattname))); |
| 3022 | |
| 3023 | /* |
| 3024 | * if the attribute is inherited, forbid the renaming. if this is a |
| 3025 | * top-level call to renameatt(), then expected_parents will be 0, so the |
| 3026 | * effect of this code will be to prohibit the renaming if the attribute |
| 3027 | * is inherited at all. if this is a recursive call to renameatt(), |
| 3028 | * expected_parents will be the number of parents the current relation has |
| 3029 | * within the inheritance hierarchy being processed, so we'll prohibit the |
| 3030 | * renaming only if there are additional parents from elsewhere. |
| 3031 | */ |
| 3032 | if (attform->attinhcount > expected_parents) |
| 3033 | ereport(ERROR, |
| 3034 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 3035 | errmsg("cannot rename inherited column \"%s\"" , |
| 3036 | oldattname))); |
| 3037 | |
| 3038 | /* new name should not already exist */ |
| 3039 | (void) check_for_column_name_collision(targetrelation, newattname, false); |
| 3040 | |
| 3041 | /* apply the update */ |
| 3042 | namestrcpy(&(attform->attname), newattname); |
| 3043 | |
| 3044 | CatalogTupleUpdate(attrelation, &atttup->t_self, atttup); |
| 3045 | |
| 3046 | InvokeObjectPostAlterHook(RelationRelationId, myrelid, attnum); |
| 3047 | |
| 3048 | heap_freetuple(atttup); |
| 3049 | |
| 3050 | table_close(attrelation, RowExclusiveLock); |
| 3051 | |
| 3052 | relation_close(targetrelation, NoLock); /* close rel but keep lock */ |
| 3053 | |
| 3054 | return attnum; |
| 3055 | } |
| 3056 | |
| 3057 | /* |
| 3058 | * Perform permissions and integrity checks before acquiring a relation lock. |
| 3059 | */ |
| 3060 | static void |
| 3061 | RangeVarCallbackForRenameAttribute(const RangeVar *rv, Oid relid, Oid oldrelid, |
| 3062 | void *arg) |
| 3063 | { |
| 3064 | HeapTuple tuple; |
| 3065 | Form_pg_class form; |
| 3066 | |
| 3067 | tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); |
| 3068 | if (!HeapTupleIsValid(tuple)) |
| 3069 | return; /* concurrently dropped */ |
| 3070 | form = (Form_pg_class) GETSTRUCT(tuple); |
| 3071 | renameatt_check(relid, form, false); |
| 3072 | ReleaseSysCache(tuple); |
| 3073 | } |
| 3074 | |
| 3075 | /* |
| 3076 | * renameatt - changes the name of an attribute in a relation |
| 3077 | * |
| 3078 | * The returned ObjectAddress is that of the renamed column. |
| 3079 | */ |
| 3080 | ObjectAddress |
| 3081 | renameatt(RenameStmt *stmt) |
| 3082 | { |
| 3083 | Oid relid; |
| 3084 | AttrNumber attnum; |
| 3085 | ObjectAddress address; |
| 3086 | |
| 3087 | /* lock level taken here should match renameatt_internal */ |
| 3088 | relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock, |
| 3089 | stmt->missing_ok ? RVR_MISSING_OK : 0, |
| 3090 | RangeVarCallbackForRenameAttribute, |
| 3091 | NULL); |
| 3092 | |
| 3093 | if (!OidIsValid(relid)) |
| 3094 | { |
| 3095 | ereport(NOTICE, |
| 3096 | (errmsg("relation \"%s\" does not exist, skipping" , |
| 3097 | stmt->relation->relname))); |
| 3098 | return InvalidObjectAddress; |
| 3099 | } |
| 3100 | |
| 3101 | attnum = |
| 3102 | renameatt_internal(relid, |
| 3103 | stmt->subname, /* old att name */ |
| 3104 | stmt->newname, /* new att name */ |
| 3105 | stmt->relation->inh, /* recursive? */ |
| 3106 | false, /* recursing? */ |
| 3107 | 0, /* expected inhcount */ |
| 3108 | stmt->behavior); |
| 3109 | |
| 3110 | ObjectAddressSubSet(address, RelationRelationId, relid, attnum); |
| 3111 | |
| 3112 | return address; |
| 3113 | } |
| 3114 | |
| 3115 | /* |
| 3116 | * same logic as renameatt_internal |
| 3117 | */ |
| 3118 | static ObjectAddress |
| 3119 | rename_constraint_internal(Oid myrelid, |
| 3120 | Oid mytypid, |
| 3121 | const char *oldconname, |
| 3122 | const char *newconname, |
| 3123 | bool recurse, |
| 3124 | bool recursing, |
| 3125 | int expected_parents) |
| 3126 | { |
| 3127 | Relation targetrelation = NULL; |
| 3128 | Oid constraintOid; |
| 3129 | HeapTuple tuple; |
| 3130 | Form_pg_constraint con; |
| 3131 | ObjectAddress address; |
| 3132 | |
| 3133 | AssertArg(!myrelid || !mytypid); |
| 3134 | |
| 3135 | if (mytypid) |
| 3136 | { |
| 3137 | constraintOid = get_domain_constraint_oid(mytypid, oldconname, false); |
| 3138 | } |
| 3139 | else |
| 3140 | { |
| 3141 | targetrelation = relation_open(myrelid, AccessExclusiveLock); |
| 3142 | |
| 3143 | /* |
| 3144 | * don't tell it whether we're recursing; we allow changing typed |
| 3145 | * tables here |
| 3146 | */ |
| 3147 | renameatt_check(myrelid, RelationGetForm(targetrelation), false); |
| 3148 | |
| 3149 | constraintOid = get_relation_constraint_oid(myrelid, oldconname, false); |
| 3150 | } |
| 3151 | |
| 3152 | tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraintOid)); |
| 3153 | if (!HeapTupleIsValid(tuple)) |
| 3154 | elog(ERROR, "cache lookup failed for constraint %u" , |
| 3155 | constraintOid); |
| 3156 | con = (Form_pg_constraint) GETSTRUCT(tuple); |
| 3157 | |
| 3158 | if (myrelid && con->contype == CONSTRAINT_CHECK && !con->connoinherit) |
| 3159 | { |
| 3160 | if (recurse) |
| 3161 | { |
| 3162 | List *child_oids, |
| 3163 | *child_numparents; |
| 3164 | ListCell *lo, |
| 3165 | *li; |
| 3166 | |
| 3167 | child_oids = find_all_inheritors(myrelid, AccessExclusiveLock, |
| 3168 | &child_numparents); |
| 3169 | |
| 3170 | forboth(lo, child_oids, li, child_numparents) |
| 3171 | { |
| 3172 | Oid childrelid = lfirst_oid(lo); |
| 3173 | int numparents = lfirst_int(li); |
| 3174 | |
| 3175 | if (childrelid == myrelid) |
| 3176 | continue; |
| 3177 | |
| 3178 | rename_constraint_internal(childrelid, InvalidOid, oldconname, newconname, false, true, numparents); |
| 3179 | } |
| 3180 | } |
| 3181 | else |
| 3182 | { |
| 3183 | if (expected_parents == 0 && |
| 3184 | find_inheritance_children(myrelid, NoLock) != NIL) |
| 3185 | ereport(ERROR, |
| 3186 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 3187 | errmsg("inherited constraint \"%s\" must be renamed in child tables too" , |
| 3188 | oldconname))); |
| 3189 | } |
| 3190 | |
| 3191 | if (con->coninhcount > expected_parents) |
| 3192 | ereport(ERROR, |
| 3193 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 3194 | errmsg("cannot rename inherited constraint \"%s\"" , |
| 3195 | oldconname))); |
| 3196 | } |
| 3197 | |
| 3198 | if (con->conindid |
| 3199 | && (con->contype == CONSTRAINT_PRIMARY |
| 3200 | || con->contype == CONSTRAINT_UNIQUE |
| 3201 | || con->contype == CONSTRAINT_EXCLUSION)) |
| 3202 | /* rename the index; this renames the constraint as well */ |
| 3203 | RenameRelationInternal(con->conindid, newconname, false, true); |
| 3204 | else |
| 3205 | RenameConstraintById(constraintOid, newconname); |
| 3206 | |
| 3207 | ObjectAddressSet(address, ConstraintRelationId, constraintOid); |
| 3208 | |
| 3209 | ReleaseSysCache(tuple); |
| 3210 | |
| 3211 | if (targetrelation) |
| 3212 | { |
| 3213 | /* |
| 3214 | * Invalidate relcache so as others can see the new constraint name. |
| 3215 | */ |
| 3216 | CacheInvalidateRelcache(targetrelation); |
| 3217 | |
| 3218 | relation_close(targetrelation, NoLock); /* close rel but keep lock */ |
| 3219 | } |
| 3220 | |
| 3221 | return address; |
| 3222 | } |
| 3223 | |
| 3224 | ObjectAddress |
| 3225 | RenameConstraint(RenameStmt *stmt) |
| 3226 | { |
| 3227 | Oid relid = InvalidOid; |
| 3228 | Oid typid = InvalidOid; |
| 3229 | |
| 3230 | if (stmt->renameType == OBJECT_DOMCONSTRAINT) |
| 3231 | { |
| 3232 | Relation rel; |
| 3233 | HeapTuple tup; |
| 3234 | |
| 3235 | typid = typenameTypeId(NULL, makeTypeNameFromNameList(castNode(List, stmt->object))); |
| 3236 | rel = table_open(TypeRelationId, RowExclusiveLock); |
| 3237 | tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); |
| 3238 | if (!HeapTupleIsValid(tup)) |
| 3239 | elog(ERROR, "cache lookup failed for type %u" , typid); |
| 3240 | checkDomainOwner(tup); |
| 3241 | ReleaseSysCache(tup); |
| 3242 | table_close(rel, NoLock); |
| 3243 | } |
| 3244 | else |
| 3245 | { |
| 3246 | /* lock level taken here should match rename_constraint_internal */ |
| 3247 | relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock, |
| 3248 | stmt->missing_ok ? RVR_MISSING_OK : 0, |
| 3249 | RangeVarCallbackForRenameAttribute, |
| 3250 | NULL); |
| 3251 | if (!OidIsValid(relid)) |
| 3252 | { |
| 3253 | ereport(NOTICE, |
| 3254 | (errmsg("relation \"%s\" does not exist, skipping" , |
| 3255 | stmt->relation->relname))); |
| 3256 | return InvalidObjectAddress; |
| 3257 | } |
| 3258 | } |
| 3259 | |
| 3260 | return |
| 3261 | rename_constraint_internal(relid, typid, |
| 3262 | stmt->subname, |
| 3263 | stmt->newname, |
| 3264 | (stmt->relation && |
| 3265 | stmt->relation->inh), /* recursive? */ |
| 3266 | false, /* recursing? */ |
| 3267 | 0 /* expected inhcount */ ); |
| 3268 | |
| 3269 | } |
| 3270 | |
| 3271 | /* |
| 3272 | * Execute ALTER TABLE/INDEX/SEQUENCE/VIEW/MATERIALIZED VIEW/FOREIGN TABLE |
| 3273 | * RENAME |
| 3274 | */ |
| 3275 | ObjectAddress |
| 3276 | RenameRelation(RenameStmt *stmt) |
| 3277 | { |
| 3278 | bool is_index = stmt->renameType == OBJECT_INDEX; |
| 3279 | Oid relid; |
| 3280 | ObjectAddress address; |
| 3281 | |
| 3282 | /* |
| 3283 | * Grab an exclusive lock on the target table, index, sequence, view, |
| 3284 | * materialized view, or foreign table, which we will NOT release until |
| 3285 | * end of transaction. |
| 3286 | * |
| 3287 | * Lock level used here should match RenameRelationInternal, to avoid lock |
| 3288 | * escalation. |
| 3289 | */ |
| 3290 | relid = RangeVarGetRelidExtended(stmt->relation, |
| 3291 | is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock, |
| 3292 | stmt->missing_ok ? RVR_MISSING_OK : 0, |
| 3293 | RangeVarCallbackForAlterRelation, |
| 3294 | (void *) stmt); |
| 3295 | |
| 3296 | if (!OidIsValid(relid)) |
| 3297 | { |
| 3298 | ereport(NOTICE, |
| 3299 | (errmsg("relation \"%s\" does not exist, skipping" , |
| 3300 | stmt->relation->relname))); |
| 3301 | return InvalidObjectAddress; |
| 3302 | } |
| 3303 | |
| 3304 | /* Do the work */ |
| 3305 | RenameRelationInternal(relid, stmt->newname, false, is_index); |
| 3306 | |
| 3307 | ObjectAddressSet(address, RelationRelationId, relid); |
| 3308 | |
| 3309 | return address; |
| 3310 | } |
| 3311 | |
| 3312 | /* |
| 3313 | * RenameRelationInternal - change the name of a relation |
| 3314 | */ |
| 3315 | void |
| 3316 | RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bool is_index) |
| 3317 | { |
| 3318 | Relation targetrelation; |
| 3319 | Relation relrelation; /* for RELATION relation */ |
| 3320 | HeapTuple reltup; |
| 3321 | Form_pg_class relform; |
| 3322 | Oid namespaceId; |
| 3323 | |
| 3324 | /* |
| 3325 | * Grab a lock on the target relation, which we will NOT release until end |
| 3326 | * of transaction. We need at least a self-exclusive lock so that |
| 3327 | * concurrent DDL doesn't overwrite the rename if they start updating |
| 3328 | * while still seeing the old version. The lock also guards against |
| 3329 | * triggering relcache reloads in concurrent sessions, which might not |
| 3330 | * handle this information changing under them. For indexes, we can use a |
| 3331 | * reduced lock level because RelationReloadIndexInfo() handles indexes |
| 3332 | * specially. |
| 3333 | */ |
| 3334 | targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock); |
| 3335 | namespaceId = RelationGetNamespace(targetrelation); |
| 3336 | |
| 3337 | /* |
| 3338 | * Find relation's pg_class tuple, and make sure newrelname isn't in use. |
| 3339 | */ |
| 3340 | relrelation = table_open(RelationRelationId, RowExclusiveLock); |
| 3341 | |
| 3342 | reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid)); |
| 3343 | if (!HeapTupleIsValid(reltup)) /* shouldn't happen */ |
| 3344 | elog(ERROR, "cache lookup failed for relation %u" , myrelid); |
| 3345 | relform = (Form_pg_class) GETSTRUCT(reltup); |
| 3346 | |
| 3347 | if (get_relname_relid(newrelname, namespaceId) != InvalidOid) |
| 3348 | ereport(ERROR, |
| 3349 | (errcode(ERRCODE_DUPLICATE_TABLE), |
| 3350 | errmsg("relation \"%s\" already exists" , |
| 3351 | newrelname))); |
| 3352 | |
| 3353 | /* |
| 3354 | * Update pg_class tuple with new relname. (Scribbling on reltup is OK |
| 3355 | * because it's a copy...) |
| 3356 | */ |
| 3357 | namestrcpy(&(relform->relname), newrelname); |
| 3358 | |
| 3359 | CatalogTupleUpdate(relrelation, &reltup->t_self, reltup); |
| 3360 | |
| 3361 | InvokeObjectPostAlterHookArg(RelationRelationId, myrelid, 0, |
| 3362 | InvalidOid, is_internal); |
| 3363 | |
| 3364 | heap_freetuple(reltup); |
| 3365 | table_close(relrelation, RowExclusiveLock); |
| 3366 | |
| 3367 | /* |
| 3368 | * Also rename the associated type, if any. |
| 3369 | */ |
| 3370 | if (OidIsValid(targetrelation->rd_rel->reltype)) |
| 3371 | RenameTypeInternal(targetrelation->rd_rel->reltype, |
| 3372 | newrelname, namespaceId); |
| 3373 | |
| 3374 | /* |
| 3375 | * Also rename the associated constraint, if any. |
| 3376 | */ |
| 3377 | if (targetrelation->rd_rel->relkind == RELKIND_INDEX || |
| 3378 | targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) |
| 3379 | { |
| 3380 | Oid constraintId = get_index_constraint(myrelid); |
| 3381 | |
| 3382 | if (OidIsValid(constraintId)) |
| 3383 | RenameConstraintById(constraintId, newrelname); |
| 3384 | } |
| 3385 | |
| 3386 | /* |
| 3387 | * Close rel, but keep lock! |
| 3388 | */ |
| 3389 | relation_close(targetrelation, NoLock); |
| 3390 | } |
| 3391 | |
| 3392 | /* |
| 3393 | * Disallow ALTER TABLE (and similar commands) when the current backend has |
| 3394 | * any open reference to the target table besides the one just acquired by |
| 3395 | * the calling command; this implies there's an open cursor or active plan. |
| 3396 | * We need this check because our lock doesn't protect us against stomping |
| 3397 | * on our own foot, only other people's feet! |
| 3398 | * |
| 3399 | * For ALTER TABLE, the only case known to cause serious trouble is ALTER |
| 3400 | * COLUMN TYPE, and some changes are obviously pretty benign, so this could |
| 3401 | * possibly be relaxed to only error out for certain types of alterations. |
| 3402 | * But the use-case for allowing any of these things is not obvious, so we |
| 3403 | * won't work hard at it for now. |
| 3404 | * |
| 3405 | * We also reject these commands if there are any pending AFTER trigger events |
| 3406 | * for the rel. This is certainly necessary for the rewriting variants of |
| 3407 | * ALTER TABLE, because they don't preserve tuple TIDs and so the pending |
| 3408 | * events would try to fetch the wrong tuples. It might be overly cautious |
| 3409 | * in other cases, but again it seems better to err on the side of paranoia. |
| 3410 | * |
| 3411 | * REINDEX calls this with "rel" referencing the index to be rebuilt; here |
| 3412 | * we are worried about active indexscans on the index. The trigger-event |
| 3413 | * check can be skipped, since we are doing no damage to the parent table. |
| 3414 | * |
| 3415 | * The statement name (eg, "ALTER TABLE") is passed for use in error messages. |
| 3416 | */ |
| 3417 | void |
| 3418 | CheckTableNotInUse(Relation rel, const char *stmt) |
| 3419 | { |
| 3420 | int expected_refcnt; |
| 3421 | |
| 3422 | expected_refcnt = rel->rd_isnailed ? 2 : 1; |
| 3423 | if (rel->rd_refcnt != expected_refcnt) |
| 3424 | ereport(ERROR, |
| 3425 | (errcode(ERRCODE_OBJECT_IN_USE), |
| 3426 | /* translator: first %s is a SQL command, eg ALTER TABLE */ |
| 3427 | errmsg("cannot %s \"%s\" because it is being used by active queries in this session" , |
| 3428 | stmt, RelationGetRelationName(rel)))); |
| 3429 | |
| 3430 | if (rel->rd_rel->relkind != RELKIND_INDEX && |
| 3431 | rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX && |
| 3432 | AfterTriggerPendingOnRel(RelationGetRelid(rel))) |
| 3433 | ereport(ERROR, |
| 3434 | (errcode(ERRCODE_OBJECT_IN_USE), |
| 3435 | /* translator: first %s is a SQL command, eg ALTER TABLE */ |
| 3436 | errmsg("cannot %s \"%s\" because it has pending trigger events" , |
| 3437 | stmt, RelationGetRelationName(rel)))); |
| 3438 | } |
| 3439 | |
| 3440 | /* |
| 3441 | * AlterTableLookupRelation |
| 3442 | * Look up, and lock, the OID for the relation named by an alter table |
| 3443 | * statement. |
| 3444 | */ |
| 3445 | Oid |
| 3446 | AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode) |
| 3447 | { |
| 3448 | return RangeVarGetRelidExtended(stmt->relation, lockmode, |
| 3449 | stmt->missing_ok ? RVR_MISSING_OK : 0, |
| 3450 | RangeVarCallbackForAlterRelation, |
| 3451 | (void *) stmt); |
| 3452 | } |
| 3453 | |
| 3454 | /* |
| 3455 | * AlterTable |
| 3456 | * Execute ALTER TABLE, which can be a list of subcommands |
| 3457 | * |
| 3458 | * ALTER TABLE is performed in three phases: |
| 3459 | * 1. Examine subcommands and perform pre-transformation checking. |
| 3460 | * 2. Update system catalogs. |
| 3461 | * 3. Scan table(s) to check new constraints, and optionally recopy |
| 3462 | * the data into new table(s). |
| 3463 | * Phase 3 is not performed unless one or more of the subcommands requires |
| 3464 | * it. The intention of this design is to allow multiple independent |
| 3465 | * updates of the table schema to be performed with only one pass over the |
| 3466 | * data. |
| 3467 | * |
| 3468 | * ATPrepCmd performs phase 1. A "work queue" entry is created for |
| 3469 | * each table to be affected (there may be multiple affected tables if the |
| 3470 | * commands traverse a table inheritance hierarchy). Also we do preliminary |
| 3471 | * validation of the subcommands, including parse transformation of those |
| 3472 | * expressions that need to be evaluated with respect to the old table |
| 3473 | * schema. |
| 3474 | * |
| 3475 | * ATRewriteCatalogs performs phase 2 for each affected table. (Note that |
| 3476 | * phases 2 and 3 normally do no explicit recursion, since phase 1 already |
| 3477 | * did it --- although some subcommands have to recurse in phase 2 instead.) |
| 3478 | * Certain subcommands need to be performed before others to avoid |
| 3479 | * unnecessary conflicts; for example, DROP COLUMN should come before |
| 3480 | * ADD COLUMN. Therefore phase 1 divides the subcommands into multiple |
| 3481 | * lists, one for each logical "pass" of phase 2. |
| 3482 | * |
| 3483 | * ATRewriteTables performs phase 3 for those tables that need it. |
| 3484 | * |
| 3485 | * Thanks to the magic of MVCC, an error anywhere along the way rolls back |
| 3486 | * the whole operation; we don't have to do anything special to clean up. |
| 3487 | * |
| 3488 | * The caller must lock the relation, with an appropriate lock level |
| 3489 | * for the subcommands requested, using AlterTableGetLockLevel(stmt->cmds) |
| 3490 | * or higher. We pass the lock level down |
| 3491 | * so that we can apply it recursively to inherited tables. Note that the |
| 3492 | * lock level we want as we recurse might well be higher than required for |
| 3493 | * that specific subcommand. So we pass down the overall lock requirement, |
| 3494 | * rather than reassess it at lower levels. |
| 3495 | */ |
| 3496 | void |
| 3497 | AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt) |
| 3498 | { |
| 3499 | Relation rel; |
| 3500 | |
| 3501 | /* Caller is required to provide an adequate lock. */ |
| 3502 | rel = relation_open(relid, NoLock); |
| 3503 | |
| 3504 | CheckTableNotInUse(rel, "ALTER TABLE" ); |
| 3505 | |
| 3506 | ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode); |
| 3507 | } |
| 3508 | |
| 3509 | /* |
| 3510 | * AlterTableInternal |
| 3511 | * |
| 3512 | * ALTER TABLE with target specified by OID |
| 3513 | * |
| 3514 | * We do not reject if the relation is already open, because it's quite |
| 3515 | * likely that one or more layers of caller have it open. That means it |
| 3516 | * is unsafe to use this entry point for alterations that could break |
| 3517 | * existing query plans. On the assumption it's not used for such, we |
| 3518 | * don't have to reject pending AFTER triggers, either. |
| 3519 | */ |
| 3520 | void |
| 3521 | AlterTableInternal(Oid relid, List *cmds, bool recurse) |
| 3522 | { |
| 3523 | Relation rel; |
| 3524 | LOCKMODE lockmode = AlterTableGetLockLevel(cmds); |
| 3525 | |
| 3526 | rel = relation_open(relid, lockmode); |
| 3527 | |
| 3528 | EventTriggerAlterTableRelid(relid); |
| 3529 | |
| 3530 | ATController(NULL, rel, cmds, recurse, lockmode); |
| 3531 | } |
| 3532 | |
| 3533 | /* |
| 3534 | * AlterTableGetLockLevel |
| 3535 | * |
| 3536 | * Sets the overall lock level required for the supplied list of subcommands. |
| 3537 | * Policy for doing this set according to needs of AlterTable(), see |
| 3538 | * comments there for overall explanation. |
| 3539 | * |
| 3540 | * Function is called before and after parsing, so it must give same |
| 3541 | * answer each time it is called. Some subcommands are transformed |
| 3542 | * into other subcommand types, so the transform must never be made to a |
| 3543 | * lower lock level than previously assigned. All transforms are noted below. |
| 3544 | * |
| 3545 | * Since this is called before we lock the table we cannot use table metadata |
| 3546 | * to influence the type of lock we acquire. |
| 3547 | * |
| 3548 | * There should be no lockmodes hardcoded into the subcommand functions. All |
| 3549 | * lockmode decisions for ALTER TABLE are made here only. The one exception is |
| 3550 | * ALTER TABLE RENAME which is treated as a different statement type T_RenameStmt |
| 3551 | * and does not travel through this section of code and cannot be combined with |
| 3552 | * any of the subcommands given here. |
| 3553 | * |
| 3554 | * Note that Hot Standby only knows about AccessExclusiveLocks on the master |
| 3555 | * so any changes that might affect SELECTs running on standbys need to use |
| 3556 | * AccessExclusiveLocks even if you think a lesser lock would do, unless you |
| 3557 | * have a solution for that also. |
| 3558 | * |
| 3559 | * Also note that pg_dump uses only an AccessShareLock, meaning that anything |
| 3560 | * that takes a lock less than AccessExclusiveLock can change object definitions |
| 3561 | * while pg_dump is running. Be careful to check that the appropriate data is |
| 3562 | * derived by pg_dump using an MVCC snapshot, rather than syscache lookups, |
| 3563 | * otherwise we might end up with an inconsistent dump that can't restore. |
| 3564 | */ |
| 3565 | LOCKMODE |
| 3566 | AlterTableGetLockLevel(List *cmds) |
| 3567 | { |
| 3568 | /* |
| 3569 | * This only works if we read catalog tables using MVCC snapshots. |
| 3570 | */ |
| 3571 | ListCell *lcmd; |
| 3572 | LOCKMODE lockmode = ShareUpdateExclusiveLock; |
| 3573 | |
| 3574 | foreach(lcmd, cmds) |
| 3575 | { |
| 3576 | AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd); |
| 3577 | LOCKMODE cmd_lockmode = AccessExclusiveLock; /* default for compiler */ |
| 3578 | |
| 3579 | switch (cmd->subtype) |
| 3580 | { |
| 3581 | /* |
| 3582 | * These subcommands rewrite the heap, so require full locks. |
| 3583 | */ |
| 3584 | case AT_AddColumn: /* may rewrite heap, in some cases and visible |
| 3585 | * to SELECT */ |
| 3586 | case AT_SetTableSpace: /* must rewrite heap */ |
| 3587 | case AT_AlterColumnType: /* must rewrite heap */ |
| 3588 | cmd_lockmode = AccessExclusiveLock; |
| 3589 | break; |
| 3590 | |
| 3591 | /* |
| 3592 | * These subcommands may require addition of toast tables. If |
| 3593 | * we add a toast table to a table currently being scanned, we |
| 3594 | * might miss data added to the new toast table by concurrent |
| 3595 | * insert transactions. |
| 3596 | */ |
| 3597 | case AT_SetStorage: /* may add toast tables, see |
| 3598 | * ATRewriteCatalogs() */ |
| 3599 | cmd_lockmode = AccessExclusiveLock; |
| 3600 | break; |
| 3601 | |
| 3602 | /* |
| 3603 | * Removing constraints can affect SELECTs that have been |
| 3604 | * optimized assuming the constraint holds true. See also |
| 3605 | * CloneFkReferenced. |
| 3606 | */ |
| 3607 | case AT_DropConstraint: /* as DROP INDEX */ |
| 3608 | case AT_DropNotNull: /* may change some SQL plans */ |
| 3609 | cmd_lockmode = AccessExclusiveLock; |
| 3610 | break; |
| 3611 | |
| 3612 | /* |
| 3613 | * Subcommands that may be visible to concurrent SELECTs |
| 3614 | */ |
| 3615 | case AT_DropColumn: /* change visible to SELECT */ |
| 3616 | case AT_AddColumnToView: /* CREATE VIEW */ |
| 3617 | case AT_DropOids: /* used to equiv to DropColumn */ |
| 3618 | case AT_EnableAlwaysRule: /* may change SELECT rules */ |
| 3619 | case AT_EnableReplicaRule: /* may change SELECT rules */ |
| 3620 | case AT_EnableRule: /* may change SELECT rules */ |
| 3621 | case AT_DisableRule: /* may change SELECT rules */ |
| 3622 | cmd_lockmode = AccessExclusiveLock; |
| 3623 | break; |
| 3624 | |
| 3625 | /* |
| 3626 | * Changing owner may remove implicit SELECT privileges |
| 3627 | */ |
| 3628 | case AT_ChangeOwner: /* change visible to SELECT */ |
| 3629 | cmd_lockmode = AccessExclusiveLock; |
| 3630 | break; |
| 3631 | |
| 3632 | /* |
| 3633 | * Changing foreign table options may affect optimization. |
| 3634 | */ |
| 3635 | case AT_GenericOptions: |
| 3636 | case AT_AlterColumnGenericOptions: |
| 3637 | cmd_lockmode = AccessExclusiveLock; |
| 3638 | break; |
| 3639 | |
| 3640 | /* |
| 3641 | * These subcommands affect write operations only. |
| 3642 | */ |
| 3643 | case AT_EnableTrig: |
| 3644 | case AT_EnableAlwaysTrig: |
| 3645 | case AT_EnableReplicaTrig: |
| 3646 | case AT_EnableTrigAll: |
| 3647 | case AT_EnableTrigUser: |
| 3648 | case AT_DisableTrig: |
| 3649 | case AT_DisableTrigAll: |
| 3650 | case AT_DisableTrigUser: |
| 3651 | cmd_lockmode = ShareRowExclusiveLock; |
| 3652 | break; |
| 3653 | |
| 3654 | /* |
| 3655 | * These subcommands affect write operations only. XXX |
| 3656 | * Theoretically, these could be ShareRowExclusiveLock. |
| 3657 | */ |
| 3658 | case AT_ColumnDefault: |
| 3659 | case AT_AlterConstraint: |
| 3660 | case AT_AddIndex: /* from ADD CONSTRAINT */ |
| 3661 | case AT_AddIndexConstraint: |
| 3662 | case AT_ReplicaIdentity: |
| 3663 | case AT_SetNotNull: |
| 3664 | case AT_EnableRowSecurity: |
| 3665 | case AT_DisableRowSecurity: |
| 3666 | case AT_ForceRowSecurity: |
| 3667 | case AT_NoForceRowSecurity: |
| 3668 | case AT_AddIdentity: |
| 3669 | case AT_DropIdentity: |
| 3670 | case AT_SetIdentity: |
| 3671 | cmd_lockmode = AccessExclusiveLock; |
| 3672 | break; |
| 3673 | |
| 3674 | case AT_AddConstraint: |
| 3675 | case AT_ProcessedConstraint: /* becomes AT_AddConstraint */ |
| 3676 | case AT_AddConstraintRecurse: /* becomes AT_AddConstraint */ |
| 3677 | case AT_ReAddConstraint: /* becomes AT_AddConstraint */ |
| 3678 | case AT_ReAddDomainConstraint: /* becomes AT_AddConstraint */ |
| 3679 | if (IsA(cmd->def, Constraint)) |
| 3680 | { |
| 3681 | Constraint *con = (Constraint *) cmd->def; |
| 3682 | |
| 3683 | switch (con->contype) |
| 3684 | { |
| 3685 | case CONSTR_EXCLUSION: |
| 3686 | case CONSTR_PRIMARY: |
| 3687 | case CONSTR_UNIQUE: |
| 3688 | |
| 3689 | /* |
| 3690 | * Cases essentially the same as CREATE INDEX. We |
| 3691 | * could reduce the lock strength to ShareLock if |
| 3692 | * we can work out how to allow concurrent catalog |
| 3693 | * updates. XXX Might be set down to |
| 3694 | * ShareRowExclusiveLock but requires further |
| 3695 | * analysis. |
| 3696 | */ |
| 3697 | cmd_lockmode = AccessExclusiveLock; |
| 3698 | break; |
| 3699 | case CONSTR_FOREIGN: |
| 3700 | |
| 3701 | /* |
| 3702 | * We add triggers to both tables when we add a |
| 3703 | * Foreign Key, so the lock level must be at least |
| 3704 | * as strong as CREATE TRIGGER. |
| 3705 | */ |
| 3706 | cmd_lockmode = ShareRowExclusiveLock; |
| 3707 | break; |
| 3708 | |
| 3709 | default: |
| 3710 | cmd_lockmode = AccessExclusiveLock; |
| 3711 | } |
| 3712 | } |
| 3713 | break; |
| 3714 | |
| 3715 | /* |
| 3716 | * These subcommands affect inheritance behaviour. Queries |
| 3717 | * started before us will continue to see the old inheritance |
| 3718 | * behaviour, while queries started after we commit will see |
| 3719 | * new behaviour. No need to prevent reads or writes to the |
| 3720 | * subtable while we hook it up though. Changing the TupDesc |
| 3721 | * may be a problem, so keep highest lock. |
| 3722 | */ |
| 3723 | case AT_AddInherit: |
| 3724 | case AT_DropInherit: |
| 3725 | cmd_lockmode = AccessExclusiveLock; |
| 3726 | break; |
| 3727 | |
| 3728 | /* |
| 3729 | * These subcommands affect implicit row type conversion. They |
| 3730 | * have affects similar to CREATE/DROP CAST on queries. don't |
| 3731 | * provide for invalidating parse trees as a result of such |
| 3732 | * changes, so we keep these at AccessExclusiveLock. |
| 3733 | */ |
| 3734 | case AT_AddOf: |
| 3735 | case AT_DropOf: |
| 3736 | cmd_lockmode = AccessExclusiveLock; |
| 3737 | break; |
| 3738 | |
| 3739 | /* |
| 3740 | * Only used by CREATE OR REPLACE VIEW which must conflict |
| 3741 | * with an SELECTs currently using the view. |
| 3742 | */ |
| 3743 | case AT_ReplaceRelOptions: |
| 3744 | cmd_lockmode = AccessExclusiveLock; |
| 3745 | break; |
| 3746 | |
| 3747 | /* |
| 3748 | * These subcommands affect general strategies for performance |
| 3749 | * and maintenance, though don't change the semantic results |
| 3750 | * from normal data reads and writes. Delaying an ALTER TABLE |
| 3751 | * behind currently active writes only delays the point where |
| 3752 | * the new strategy begins to take effect, so there is no |
| 3753 | * benefit in waiting. In this case the minimum restriction |
| 3754 | * applies: we don't currently allow concurrent catalog |
| 3755 | * updates. |
| 3756 | */ |
| 3757 | case AT_SetStatistics: /* Uses MVCC in getTableAttrs() */ |
| 3758 | case AT_ClusterOn: /* Uses MVCC in getIndexes() */ |
| 3759 | case AT_DropCluster: /* Uses MVCC in getIndexes() */ |
| 3760 | case AT_SetOptions: /* Uses MVCC in getTableAttrs() */ |
| 3761 | case AT_ResetOptions: /* Uses MVCC in getTableAttrs() */ |
| 3762 | cmd_lockmode = ShareUpdateExclusiveLock; |
| 3763 | break; |
| 3764 | |
| 3765 | case AT_SetLogged: |
| 3766 | case AT_SetUnLogged: |
| 3767 | cmd_lockmode = AccessExclusiveLock; |
| 3768 | break; |
| 3769 | |
| 3770 | case AT_ValidateConstraint: /* Uses MVCC in getConstraints() */ |
| 3771 | cmd_lockmode = ShareUpdateExclusiveLock; |
| 3772 | break; |
| 3773 | |
| 3774 | /* |
| 3775 | * Rel options are more complex than first appears. Options |
| 3776 | * are set here for tables, views and indexes; for historical |
| 3777 | * reasons these can all be used with ALTER TABLE, so we can't |
| 3778 | * decide between them using the basic grammar. |
| 3779 | */ |
| 3780 | case AT_SetRelOptions: /* Uses MVCC in getIndexes() and |
| 3781 | * getTables() */ |
| 3782 | case AT_ResetRelOptions: /* Uses MVCC in getIndexes() and |
| 3783 | * getTables() */ |
| 3784 | cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def); |
| 3785 | break; |
| 3786 | |
| 3787 | case AT_AttachPartition: |
| 3788 | cmd_lockmode = ShareUpdateExclusiveLock; |
| 3789 | break; |
| 3790 | |
| 3791 | case AT_DetachPartition: |
| 3792 | cmd_lockmode = AccessExclusiveLock; |
| 3793 | break; |
| 3794 | |
| 3795 | case AT_CheckNotNull: |
| 3796 | |
| 3797 | /* |
| 3798 | * This only examines the table's schema; but lock must be |
| 3799 | * strong enough to prevent concurrent DROP NOT NULL. |
| 3800 | */ |
| 3801 | cmd_lockmode = AccessShareLock; |
| 3802 | break; |
| 3803 | |
| 3804 | default: /* oops */ |
| 3805 | elog(ERROR, "unrecognized alter table type: %d" , |
| 3806 | (int) cmd->subtype); |
| 3807 | break; |
| 3808 | } |
| 3809 | |
| 3810 | /* |
| 3811 | * Take the greatest lockmode from any subcommand |
| 3812 | */ |
| 3813 | if (cmd_lockmode > lockmode) |
| 3814 | lockmode = cmd_lockmode; |
| 3815 | } |
| 3816 | |
| 3817 | return lockmode; |
| 3818 | } |
| 3819 | |
| 3820 | /* |
| 3821 | * ATController provides top level control over the phases. |
| 3822 | * |
| 3823 | * parsetree is passed in to allow it to be passed to event triggers |
| 3824 | * when requested. |
| 3825 | */ |
| 3826 | static void |
| 3827 | ATController(AlterTableStmt *parsetree, |
| 3828 | Relation rel, List *cmds, bool recurse, LOCKMODE lockmode) |
| 3829 | { |
| 3830 | List *wqueue = NIL; |
| 3831 | ListCell *lcmd; |
| 3832 | |
| 3833 | /* Phase 1: preliminary examination of commands, create work queue */ |
| 3834 | foreach(lcmd, cmds) |
| 3835 | { |
| 3836 | AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd); |
| 3837 | |
| 3838 | ATPrepCmd(&wqueue, rel, cmd, recurse, false, lockmode); |
| 3839 | } |
| 3840 | |
| 3841 | /* Close the relation, but keep lock until commit */ |
| 3842 | relation_close(rel, NoLock); |
| 3843 | |
| 3844 | /* Phase 2: update system catalogs */ |
| 3845 | ATRewriteCatalogs(&wqueue, lockmode); |
| 3846 | |
| 3847 | /* Phase 3: scan/rewrite tables as needed */ |
| 3848 | ATRewriteTables(parsetree, &wqueue, lockmode); |
| 3849 | } |
| 3850 | |
| 3851 | /* |
| 3852 | * ATPrepCmd |
| 3853 | * |
| 3854 | * Traffic cop for ALTER TABLE Phase 1 operations, including simple |
| 3855 | * recursion and permission checks. |
| 3856 | * |
| 3857 | * Caller must have acquired appropriate lock type on relation already. |
| 3858 | * This lock should be held until commit. |
| 3859 | */ |
| 3860 | static void |
| 3861 | ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, |
| 3862 | bool recurse, bool recursing, LOCKMODE lockmode) |
| 3863 | { |
| 3864 | AlteredTableInfo *tab; |
| 3865 | int pass = AT_PASS_UNSET; |
| 3866 | |
| 3867 | /* Find or create work queue entry for this table */ |
| 3868 | tab = ATGetQueueEntry(wqueue, rel); |
| 3869 | |
| 3870 | /* |
| 3871 | * Copy the original subcommand for each table. This avoids conflicts |
| 3872 | * when different child tables need to make different parse |
| 3873 | * transformations (for example, the same column may have different column |
| 3874 | * numbers in different children). |
| 3875 | */ |
| 3876 | cmd = copyObject(cmd); |
| 3877 | |
| 3878 | /* |
| 3879 | * Do permissions checking, recursion to child tables if needed, and any |
| 3880 | * additional phase-1 processing needed. |
| 3881 | */ |
| 3882 | switch (cmd->subtype) |
| 3883 | { |
| 3884 | case AT_AddColumn: /* ADD COLUMN */ |
| 3885 | ATSimplePermissions(rel, |
| 3886 | ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE); |
| 3887 | ATPrepAddColumn(wqueue, rel, recurse, recursing, false, cmd, |
| 3888 | lockmode); |
| 3889 | /* Recursion occurs during execution phase */ |
| 3890 | pass = AT_PASS_ADD_COL; |
| 3891 | break; |
| 3892 | case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */ |
| 3893 | ATSimplePermissions(rel, ATT_VIEW); |
| 3894 | ATPrepAddColumn(wqueue, rel, recurse, recursing, true, cmd, |
| 3895 | lockmode); |
| 3896 | /* Recursion occurs during execution phase */ |
| 3897 | pass = AT_PASS_ADD_COL; |
| 3898 | break; |
| 3899 | case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */ |
| 3900 | |
| 3901 | /* |
| 3902 | * We allow defaults on views so that INSERT into a view can have |
| 3903 | * default-ish behavior. This works because the rewriter |
| 3904 | * substitutes default values into INSERTs before it expands |
| 3905 | * rules. |
| 3906 | */ |
| 3907 | ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE); |
| 3908 | ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); |
| 3909 | /* No command-specific prep needed */ |
| 3910 | pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP; |
| 3911 | break; |
| 3912 | case AT_AddIdentity: |
| 3913 | ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE); |
| 3914 | /* This command never recurses */ |
| 3915 | pass = AT_PASS_ADD_CONSTR; |
| 3916 | break; |
| 3917 | case AT_SetIdentity: |
| 3918 | ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE); |
| 3919 | /* This command never recurses */ |
| 3920 | pass = AT_PASS_COL_ATTRS; |
| 3921 | break; |
| 3922 | case AT_DropIdentity: |
| 3923 | ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE); |
| 3924 | /* This command never recurses */ |
| 3925 | pass = AT_PASS_DROP; |
| 3926 | break; |
| 3927 | case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */ |
| 3928 | ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); |
| 3929 | ATPrepDropNotNull(rel, recurse, recursing); |
| 3930 | ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); |
| 3931 | pass = AT_PASS_DROP; |
| 3932 | break; |
| 3933 | case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */ |
| 3934 | ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); |
| 3935 | /* Need command-specific recursion decision */ |
| 3936 | ATPrepSetNotNull(wqueue, rel, cmd, recurse, recursing, lockmode); |
| 3937 | pass = AT_PASS_COL_ATTRS; |
| 3938 | break; |
| 3939 | case AT_CheckNotNull: /* check column is already marked NOT NULL */ |
| 3940 | ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); |
| 3941 | ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); |
| 3942 | /* No command-specific prep needed */ |
| 3943 | pass = AT_PASS_COL_ATTRS; |
| 3944 | break; |
| 3945 | case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */ |
| 3946 | ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); |
| 3947 | /* Performs own permission checks */ |
| 3948 | ATPrepSetStatistics(rel, cmd->name, cmd->num, cmd->def, lockmode); |
| 3949 | pass = AT_PASS_MISC; |
| 3950 | break; |
| 3951 | case AT_SetOptions: /* ALTER COLUMN SET ( options ) */ |
| 3952 | case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */ |
| 3953 | ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_FOREIGN_TABLE); |
| 3954 | /* This command never recurses */ |
| 3955 | pass = AT_PASS_MISC; |
| 3956 | break; |
| 3957 | case AT_SetStorage: /* ALTER COLUMN SET STORAGE */ |
| 3958 | ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE); |
| 3959 | ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); |
| 3960 | /* No command-specific prep needed */ |
| 3961 | pass = AT_PASS_MISC; |
| 3962 | break; |
| 3963 | case AT_DropColumn: /* DROP COLUMN */ |
| 3964 | ATSimplePermissions(rel, |
| 3965 | ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE); |
| 3966 | ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd, lockmode); |
| 3967 | /* Recursion occurs during execution phase */ |
| 3968 | pass = AT_PASS_DROP; |
| 3969 | break; |
| 3970 | case AT_AddIndex: /* ADD INDEX */ |
| 3971 | ATSimplePermissions(rel, ATT_TABLE); |
| 3972 | /* This command never recurses */ |
| 3973 | /* No command-specific prep needed */ |
| 3974 | pass = AT_PASS_ADD_INDEX; |
| 3975 | break; |
| 3976 | case AT_AddConstraint: /* ADD CONSTRAINT */ |
| 3977 | ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); |
| 3978 | /* Recursion occurs during execution phase */ |
| 3979 | /* No command-specific prep needed except saving recurse flag */ |
| 3980 | if (recurse) |
| 3981 | cmd->subtype = AT_AddConstraintRecurse; |
| 3982 | pass = AT_PASS_ADD_CONSTR; |
| 3983 | break; |
| 3984 | case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */ |
| 3985 | ATSimplePermissions(rel, ATT_TABLE); |
| 3986 | /* This command never recurses */ |
| 3987 | /* No command-specific prep needed */ |
| 3988 | pass = AT_PASS_ADD_CONSTR; |
| 3989 | break; |
| 3990 | case AT_DropConstraint: /* DROP CONSTRAINT */ |
| 3991 | ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); |
| 3992 | ATCheckPartitionsNotInUse(rel, lockmode); |
| 3993 | /* Other recursion occurs during execution phase */ |
| 3994 | /* No command-specific prep needed except saving recurse flag */ |
| 3995 | if (recurse) |
| 3996 | cmd->subtype = AT_DropConstraintRecurse; |
| 3997 | pass = AT_PASS_DROP; |
| 3998 | break; |
| 3999 | case AT_AlterColumnType: /* ALTER COLUMN TYPE */ |
| 4000 | ATSimplePermissions(rel, |
| 4001 | ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE); |
| 4002 | /* Performs own recursion */ |
| 4003 | ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd, lockmode); |
| 4004 | pass = AT_PASS_ALTER_TYPE; |
| 4005 | break; |
| 4006 | case AT_AlterColumnGenericOptions: |
| 4007 | ATSimplePermissions(rel, ATT_FOREIGN_TABLE); |
| 4008 | /* This command never recurses */ |
| 4009 | /* No command-specific prep needed */ |
| 4010 | pass = AT_PASS_MISC; |
| 4011 | break; |
| 4012 | case AT_ChangeOwner: /* ALTER OWNER */ |
| 4013 | /* This command never recurses */ |
| 4014 | /* No command-specific prep needed */ |
| 4015 | pass = AT_PASS_MISC; |
| 4016 | break; |
| 4017 | case AT_ClusterOn: /* CLUSTER ON */ |
| 4018 | case AT_DropCluster: /* SET WITHOUT CLUSTER */ |
| 4019 | ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW); |
| 4020 | /* These commands never recurse */ |
| 4021 | /* No command-specific prep needed */ |
| 4022 | pass = AT_PASS_MISC; |
| 4023 | break; |
| 4024 | case AT_SetLogged: /* SET LOGGED */ |
| 4025 | ATSimplePermissions(rel, ATT_TABLE); |
| 4026 | tab->chgPersistence = ATPrepChangePersistence(rel, true); |
| 4027 | /* force rewrite if necessary; see comment in ATRewriteTables */ |
| 4028 | if (tab->chgPersistence) |
| 4029 | { |
| 4030 | tab->rewrite |= AT_REWRITE_ALTER_PERSISTENCE; |
| 4031 | tab->newrelpersistence = RELPERSISTENCE_PERMANENT; |
| 4032 | } |
| 4033 | pass = AT_PASS_MISC; |
| 4034 | break; |
| 4035 | case AT_SetUnLogged: /* SET UNLOGGED */ |
| 4036 | ATSimplePermissions(rel, ATT_TABLE); |
| 4037 | tab->chgPersistence = ATPrepChangePersistence(rel, false); |
| 4038 | /* force rewrite if necessary; see comment in ATRewriteTables */ |
| 4039 | if (tab->chgPersistence) |
| 4040 | { |
| 4041 | tab->rewrite |= AT_REWRITE_ALTER_PERSISTENCE; |
| 4042 | tab->newrelpersistence = RELPERSISTENCE_UNLOGGED; |
| 4043 | } |
| 4044 | pass = AT_PASS_MISC; |
| 4045 | break; |
| 4046 | case AT_DropOids: /* SET WITHOUT OIDS */ |
| 4047 | ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); |
| 4048 | pass = AT_PASS_DROP; |
| 4049 | break; |
| 4050 | case AT_SetTableSpace: /* SET TABLESPACE */ |
| 4051 | ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX | |
| 4052 | ATT_PARTITIONED_INDEX); |
| 4053 | /* This command never recurses */ |
| 4054 | ATPrepSetTableSpace(tab, rel, cmd->name, lockmode); |
| 4055 | pass = AT_PASS_MISC; /* doesn't actually matter */ |
| 4056 | break; |
| 4057 | case AT_SetRelOptions: /* SET (...) */ |
| 4058 | case AT_ResetRelOptions: /* RESET (...) */ |
| 4059 | case AT_ReplaceRelOptions: /* reset them all, then set just these */ |
| 4060 | ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_MATVIEW | ATT_INDEX); |
| 4061 | /* This command never recurses */ |
| 4062 | /* No command-specific prep needed */ |
| 4063 | pass = AT_PASS_MISC; |
| 4064 | break; |
| 4065 | case AT_AddInherit: /* INHERIT */ |
| 4066 | ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); |
| 4067 | /* This command never recurses */ |
| 4068 | ATPrepAddInherit(rel); |
| 4069 | pass = AT_PASS_MISC; |
| 4070 | break; |
| 4071 | case AT_DropInherit: /* NO INHERIT */ |
| 4072 | ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); |
| 4073 | /* This command never recurses */ |
| 4074 | /* No command-specific prep needed */ |
| 4075 | pass = AT_PASS_MISC; |
| 4076 | break; |
| 4077 | case AT_AlterConstraint: /* ALTER CONSTRAINT */ |
| 4078 | ATSimplePermissions(rel, ATT_TABLE); |
| 4079 | pass = AT_PASS_MISC; |
| 4080 | break; |
| 4081 | case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */ |
| 4082 | ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); |
| 4083 | /* Recursion occurs during execution phase */ |
| 4084 | /* No command-specific prep needed except saving recurse flag */ |
| 4085 | if (recurse) |
| 4086 | cmd->subtype = AT_ValidateConstraintRecurse; |
| 4087 | pass = AT_PASS_MISC; |
| 4088 | break; |
| 4089 | case AT_ReplicaIdentity: /* REPLICA IDENTITY ... */ |
| 4090 | ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW); |
| 4091 | pass = AT_PASS_MISC; |
| 4092 | /* This command never recurses */ |
| 4093 | /* No command-specific prep needed */ |
| 4094 | break; |
| 4095 | case AT_EnableTrig: /* ENABLE TRIGGER variants */ |
| 4096 | case AT_EnableAlwaysTrig: |
| 4097 | case AT_EnableReplicaTrig: |
| 4098 | case AT_EnableTrigAll: |
| 4099 | case AT_EnableTrigUser: |
| 4100 | case AT_DisableTrig: /* DISABLE TRIGGER variants */ |
| 4101 | case AT_DisableTrigAll: |
| 4102 | case AT_DisableTrigUser: |
| 4103 | ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); |
| 4104 | pass = AT_PASS_MISC; |
| 4105 | break; |
| 4106 | case AT_EnableRule: /* ENABLE/DISABLE RULE variants */ |
| 4107 | case AT_EnableAlwaysRule: |
| 4108 | case AT_EnableReplicaRule: |
| 4109 | case AT_DisableRule: |
| 4110 | case AT_AddOf: /* OF */ |
| 4111 | case AT_DropOf: /* NOT OF */ |
| 4112 | case AT_EnableRowSecurity: |
| 4113 | case AT_DisableRowSecurity: |
| 4114 | case AT_ForceRowSecurity: |
| 4115 | case AT_NoForceRowSecurity: |
| 4116 | ATSimplePermissions(rel, ATT_TABLE); |
| 4117 | /* These commands never recurse */ |
| 4118 | /* No command-specific prep needed */ |
| 4119 | pass = AT_PASS_MISC; |
| 4120 | break; |
| 4121 | case AT_GenericOptions: |
| 4122 | ATSimplePermissions(rel, ATT_FOREIGN_TABLE); |
| 4123 | /* No command-specific prep needed */ |
| 4124 | pass = AT_PASS_MISC; |
| 4125 | break; |
| 4126 | case AT_AttachPartition: |
| 4127 | ATSimplePermissions(rel, ATT_TABLE | ATT_PARTITIONED_INDEX); |
| 4128 | /* No command-specific prep needed */ |
| 4129 | pass = AT_PASS_MISC; |
| 4130 | break; |
| 4131 | case AT_DetachPartition: |
| 4132 | ATSimplePermissions(rel, ATT_TABLE); |
| 4133 | /* No command-specific prep needed */ |
| 4134 | pass = AT_PASS_MISC; |
| 4135 | break; |
| 4136 | default: /* oops */ |
| 4137 | elog(ERROR, "unrecognized alter table type: %d" , |
| 4138 | (int) cmd->subtype); |
| 4139 | pass = AT_PASS_UNSET; /* keep compiler quiet */ |
| 4140 | break; |
| 4141 | } |
| 4142 | Assert(pass > AT_PASS_UNSET); |
| 4143 | |
| 4144 | /* Add the subcommand to the appropriate list for phase 2 */ |
| 4145 | tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd); |
| 4146 | } |
| 4147 | |
| 4148 | /* |
| 4149 | * ATRewriteCatalogs |
| 4150 | * |
| 4151 | * Traffic cop for ALTER TABLE Phase 2 operations. Subcommands are |
| 4152 | * dispatched in a "safe" execution order (designed to avoid unnecessary |
| 4153 | * conflicts). |
| 4154 | */ |
| 4155 | static void |
| 4156 | ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode) |
| 4157 | { |
| 4158 | int pass; |
| 4159 | ListCell *ltab; |
| 4160 | |
| 4161 | /* |
| 4162 | * We process all the tables "in parallel", one pass at a time. This is |
| 4163 | * needed because we may have to propagate work from one table to another |
| 4164 | * (specifically, ALTER TYPE on a foreign key's PK has to dispatch the |
| 4165 | * re-adding of the foreign key constraint to the other table). Work can |
| 4166 | * only be propagated into later passes, however. |
| 4167 | */ |
| 4168 | for (pass = 0; pass < AT_NUM_PASSES; pass++) |
| 4169 | { |
| 4170 | /* Go through each table that needs to be processed */ |
| 4171 | foreach(ltab, *wqueue) |
| 4172 | { |
| 4173 | AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab); |
| 4174 | List *subcmds = tab->subcmds[pass]; |
| 4175 | Relation rel; |
| 4176 | ListCell *lcmd; |
| 4177 | |
| 4178 | if (subcmds == NIL) |
| 4179 | continue; |
| 4180 | |
| 4181 | /* |
| 4182 | * Appropriate lock was obtained by phase 1, needn't get it again |
| 4183 | */ |
| 4184 | rel = relation_open(tab->relid, NoLock); |
| 4185 | |
| 4186 | foreach(lcmd, subcmds) |
| 4187 | ATExecCmd(wqueue, tab, rel, |
| 4188 | castNode(AlterTableCmd, lfirst(lcmd)), |
| 4189 | lockmode); |
| 4190 | |
| 4191 | /* |
| 4192 | * After the ALTER TYPE pass, do cleanup work (this is not done in |
| 4193 | * ATExecAlterColumnType since it should be done only once if |
| 4194 | * multiple columns of a table are altered). |
| 4195 | */ |
| 4196 | if (pass == AT_PASS_ALTER_TYPE) |
| 4197 | ATPostAlterTypeCleanup(wqueue, tab, lockmode); |
| 4198 | |
| 4199 | relation_close(rel, NoLock); |
| 4200 | } |
| 4201 | } |
| 4202 | |
| 4203 | /* Check to see if a toast table must be added. */ |
| 4204 | foreach(ltab, *wqueue) |
| 4205 | { |
| 4206 | AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab); |
| 4207 | |
| 4208 | /* |
| 4209 | * If the table is source table of ATTACH PARTITION command, we did |
| 4210 | * not modify anything about it that will change its toasting |
| 4211 | * requirement, so no need to check. |
| 4212 | */ |
| 4213 | if (((tab->relkind == RELKIND_RELATION || |
| 4214 | tab->relkind == RELKIND_PARTITIONED_TABLE) && |
| 4215 | tab->partition_constraint == NULL) || |
| 4216 | tab->relkind == RELKIND_MATVIEW) |
| 4217 | AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode); |
| 4218 | } |
| 4219 | } |
| 4220 | |
| 4221 | /* |
| 4222 | * ATExecCmd: dispatch a subcommand to appropriate execution routine |
| 4223 | */ |
| 4224 | static void |
| 4225 | ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, |
| 4226 | AlterTableCmd *cmd, LOCKMODE lockmode) |
| 4227 | { |
| 4228 | ObjectAddress address = InvalidObjectAddress; |
| 4229 | |
| 4230 | switch (cmd->subtype) |
| 4231 | { |
| 4232 | case AT_AddColumn: /* ADD COLUMN */ |
| 4233 | case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */ |
| 4234 | address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def, |
| 4235 | false, false, |
| 4236 | cmd->missing_ok, lockmode); |
| 4237 | break; |
| 4238 | case AT_AddColumnRecurse: |
| 4239 | address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def, |
| 4240 | true, false, |
| 4241 | cmd->missing_ok, lockmode); |
| 4242 | break; |
| 4243 | case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */ |
| 4244 | address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode); |
| 4245 | break; |
| 4246 | case AT_AddIdentity: |
| 4247 | address = ATExecAddIdentity(rel, cmd->name, cmd->def, lockmode); |
| 4248 | break; |
| 4249 | case AT_SetIdentity: |
| 4250 | address = ATExecSetIdentity(rel, cmd->name, cmd->def, lockmode); |
| 4251 | break; |
| 4252 | case AT_DropIdentity: |
| 4253 | address = ATExecDropIdentity(rel, cmd->name, cmd->missing_ok, lockmode); |
| 4254 | break; |
| 4255 | case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */ |
| 4256 | address = ATExecDropNotNull(rel, cmd->name, lockmode); |
| 4257 | break; |
| 4258 | case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */ |
| 4259 | address = ATExecSetNotNull(tab, rel, cmd->name, lockmode); |
| 4260 | break; |
| 4261 | case AT_CheckNotNull: /* check column is already marked NOT NULL */ |
| 4262 | ATExecCheckNotNull(tab, rel, cmd->name, lockmode); |
| 4263 | break; |
| 4264 | case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */ |
| 4265 | address = ATExecSetStatistics(rel, cmd->name, cmd->num, cmd->def, lockmode); |
| 4266 | break; |
| 4267 | case AT_SetOptions: /* ALTER COLUMN SET ( options ) */ |
| 4268 | address = ATExecSetOptions(rel, cmd->name, cmd->def, false, lockmode); |
| 4269 | break; |
| 4270 | case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */ |
| 4271 | address = ATExecSetOptions(rel, cmd->name, cmd->def, true, lockmode); |
| 4272 | break; |
| 4273 | case AT_SetStorage: /* ALTER COLUMN SET STORAGE */ |
| 4274 | address = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode); |
| 4275 | break; |
| 4276 | case AT_DropColumn: /* DROP COLUMN */ |
| 4277 | address = ATExecDropColumn(wqueue, rel, cmd->name, |
| 4278 | cmd->behavior, false, false, |
| 4279 | cmd->missing_ok, lockmode); |
| 4280 | break; |
| 4281 | case AT_DropColumnRecurse: /* DROP COLUMN with recursion */ |
| 4282 | address = ATExecDropColumn(wqueue, rel, cmd->name, |
| 4283 | cmd->behavior, true, false, |
| 4284 | cmd->missing_ok, lockmode); |
| 4285 | break; |
| 4286 | case AT_AddIndex: /* ADD INDEX */ |
| 4287 | address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false, |
| 4288 | lockmode); |
| 4289 | break; |
| 4290 | case AT_ReAddIndex: /* ADD INDEX */ |
| 4291 | address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true, |
| 4292 | lockmode); |
| 4293 | break; |
| 4294 | case AT_AddConstraint: /* ADD CONSTRAINT */ |
| 4295 | address = |
| 4296 | ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def, |
| 4297 | false, false, lockmode); |
| 4298 | break; |
| 4299 | case AT_AddConstraintRecurse: /* ADD CONSTRAINT with recursion */ |
| 4300 | address = |
| 4301 | ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def, |
| 4302 | true, false, lockmode); |
| 4303 | break; |
| 4304 | case AT_ReAddConstraint: /* Re-add pre-existing check constraint */ |
| 4305 | address = |
| 4306 | ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def, |
| 4307 | true, true, lockmode); |
| 4308 | break; |
| 4309 | case AT_ReAddDomainConstraint: /* Re-add pre-existing domain check |
| 4310 | * constraint */ |
| 4311 | address = |
| 4312 | AlterDomainAddConstraint(((AlterDomainStmt *) cmd->def)->typeName, |
| 4313 | ((AlterDomainStmt *) cmd->def)->def, |
| 4314 | NULL); |
| 4315 | break; |
| 4316 | case AT_ReAddComment: /* Re-add existing comment */ |
| 4317 | address = CommentObject((CommentStmt *) cmd->def); |
| 4318 | break; |
| 4319 | case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */ |
| 4320 | address = ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def, |
| 4321 | lockmode); |
| 4322 | break; |
| 4323 | case AT_AlterConstraint: /* ALTER CONSTRAINT */ |
| 4324 | address = ATExecAlterConstraint(rel, cmd, false, false, lockmode); |
| 4325 | break; |
| 4326 | case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */ |
| 4327 | address = ATExecValidateConstraint(rel, cmd->name, false, false, |
| 4328 | lockmode); |
| 4329 | break; |
| 4330 | case AT_ValidateConstraintRecurse: /* VALIDATE CONSTRAINT with |
| 4331 | * recursion */ |
| 4332 | address = ATExecValidateConstraint(rel, cmd->name, true, false, |
| 4333 | lockmode); |
| 4334 | break; |
| 4335 | case AT_DropConstraint: /* DROP CONSTRAINT */ |
| 4336 | ATExecDropConstraint(rel, cmd->name, cmd->behavior, |
| 4337 | false, false, |
| 4338 | cmd->missing_ok, lockmode); |
| 4339 | break; |
| 4340 | case AT_DropConstraintRecurse: /* DROP CONSTRAINT with recursion */ |
| 4341 | ATExecDropConstraint(rel, cmd->name, cmd->behavior, |
| 4342 | true, false, |
| 4343 | cmd->missing_ok, lockmode); |
| 4344 | break; |
| 4345 | case AT_AlterColumnType: /* ALTER COLUMN TYPE */ |
| 4346 | address = ATExecAlterColumnType(tab, rel, cmd, lockmode); |
| 4347 | break; |
| 4348 | case AT_AlterColumnGenericOptions: /* ALTER COLUMN OPTIONS */ |
| 4349 | address = |
| 4350 | ATExecAlterColumnGenericOptions(rel, cmd->name, |
| 4351 | (List *) cmd->def, lockmode); |
| 4352 | break; |
| 4353 | case AT_ChangeOwner: /* ALTER OWNER */ |
| 4354 | ATExecChangeOwner(RelationGetRelid(rel), |
| 4355 | get_rolespec_oid(cmd->newowner, false), |
| 4356 | false, lockmode); |
| 4357 | break; |
| 4358 | case AT_ClusterOn: /* CLUSTER ON */ |
| 4359 | address = ATExecClusterOn(rel, cmd->name, lockmode); |
| 4360 | break; |
| 4361 | case AT_DropCluster: /* SET WITHOUT CLUSTER */ |
| 4362 | ATExecDropCluster(rel, lockmode); |
| 4363 | break; |
| 4364 | case AT_SetLogged: /* SET LOGGED */ |
| 4365 | case AT_SetUnLogged: /* SET UNLOGGED */ |
| 4366 | break; |
| 4367 | case AT_DropOids: /* SET WITHOUT OIDS */ |
| 4368 | /* nothing to do here, oid columns don't exist anymore */ |
| 4369 | break; |
| 4370 | case AT_SetTableSpace: /* SET TABLESPACE */ |
| 4371 | |
| 4372 | /* |
| 4373 | * Only do this for partitioned tables and indexes, for which this |
| 4374 | * is just a catalog change. Other relation types which have |
| 4375 | * storage are handled by Phase 3. |
| 4376 | */ |
| 4377 | if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE || |
| 4378 | rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) |
| 4379 | ATExecSetTableSpaceNoStorage(rel, tab->newTableSpace); |
| 4380 | |
| 4381 | break; |
| 4382 | case AT_SetRelOptions: /* SET (...) */ |
| 4383 | case AT_ResetRelOptions: /* RESET (...) */ |
| 4384 | case AT_ReplaceRelOptions: /* replace entire option list */ |
| 4385 | ATExecSetRelOptions(rel, (List *) cmd->def, cmd->subtype, lockmode); |
| 4386 | break; |
| 4387 | case AT_EnableTrig: /* ENABLE TRIGGER name */ |
| 4388 | ATExecEnableDisableTrigger(rel, cmd->name, |
| 4389 | TRIGGER_FIRES_ON_ORIGIN, false, lockmode); |
| 4390 | break; |
| 4391 | case AT_EnableAlwaysTrig: /* ENABLE ALWAYS TRIGGER name */ |
| 4392 | ATExecEnableDisableTrigger(rel, cmd->name, |
| 4393 | TRIGGER_FIRES_ALWAYS, false, lockmode); |
| 4394 | break; |
| 4395 | case AT_EnableReplicaTrig: /* ENABLE REPLICA TRIGGER name */ |
| 4396 | ATExecEnableDisableTrigger(rel, cmd->name, |
| 4397 | TRIGGER_FIRES_ON_REPLICA, false, lockmode); |
| 4398 | break; |
| 4399 | case AT_DisableTrig: /* DISABLE TRIGGER name */ |
| 4400 | ATExecEnableDisableTrigger(rel, cmd->name, |
| 4401 | TRIGGER_DISABLED, false, lockmode); |
| 4402 | break; |
| 4403 | case AT_EnableTrigAll: /* ENABLE TRIGGER ALL */ |
| 4404 | ATExecEnableDisableTrigger(rel, NULL, |
| 4405 | TRIGGER_FIRES_ON_ORIGIN, false, lockmode); |
| 4406 | break; |
| 4407 | case AT_DisableTrigAll: /* DISABLE TRIGGER ALL */ |
| 4408 | ATExecEnableDisableTrigger(rel, NULL, |
| 4409 | TRIGGER_DISABLED, false, lockmode); |
| 4410 | break; |
| 4411 | case AT_EnableTrigUser: /* ENABLE TRIGGER USER */ |
| 4412 | ATExecEnableDisableTrigger(rel, NULL, |
| 4413 | TRIGGER_FIRES_ON_ORIGIN, true, lockmode); |
| 4414 | break; |
| 4415 | case AT_DisableTrigUser: /* DISABLE TRIGGER USER */ |
| 4416 | ATExecEnableDisableTrigger(rel, NULL, |
| 4417 | TRIGGER_DISABLED, true, lockmode); |
| 4418 | break; |
| 4419 | |
| 4420 | case AT_EnableRule: /* ENABLE RULE name */ |
| 4421 | ATExecEnableDisableRule(rel, cmd->name, |
| 4422 | RULE_FIRES_ON_ORIGIN, lockmode); |
| 4423 | break; |
| 4424 | case AT_EnableAlwaysRule: /* ENABLE ALWAYS RULE name */ |
| 4425 | ATExecEnableDisableRule(rel, cmd->name, |
| 4426 | RULE_FIRES_ALWAYS, lockmode); |
| 4427 | break; |
| 4428 | case AT_EnableReplicaRule: /* ENABLE REPLICA RULE name */ |
| 4429 | ATExecEnableDisableRule(rel, cmd->name, |
| 4430 | RULE_FIRES_ON_REPLICA, lockmode); |
| 4431 | break; |
| 4432 | case AT_DisableRule: /* DISABLE RULE name */ |
| 4433 | ATExecEnableDisableRule(rel, cmd->name, |
| 4434 | RULE_DISABLED, lockmode); |
| 4435 | break; |
| 4436 | |
| 4437 | case AT_AddInherit: |
| 4438 | address = ATExecAddInherit(rel, (RangeVar *) cmd->def, lockmode); |
| 4439 | break; |
| 4440 | case AT_DropInherit: |
| 4441 | address = ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode); |
| 4442 | break; |
| 4443 | case AT_AddOf: |
| 4444 | address = ATExecAddOf(rel, (TypeName *) cmd->def, lockmode); |
| 4445 | break; |
| 4446 | case AT_DropOf: |
| 4447 | ATExecDropOf(rel, lockmode); |
| 4448 | break; |
| 4449 | case AT_ReplicaIdentity: |
| 4450 | ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode); |
| 4451 | break; |
| 4452 | case AT_EnableRowSecurity: |
| 4453 | ATExecEnableRowSecurity(rel); |
| 4454 | break; |
| 4455 | case AT_DisableRowSecurity: |
| 4456 | ATExecDisableRowSecurity(rel); |
| 4457 | break; |
| 4458 | case AT_ForceRowSecurity: |
| 4459 | ATExecForceNoForceRowSecurity(rel, true); |
| 4460 | break; |
| 4461 | case AT_NoForceRowSecurity: |
| 4462 | ATExecForceNoForceRowSecurity(rel, false); |
| 4463 | break; |
| 4464 | case AT_GenericOptions: |
| 4465 | ATExecGenericOptions(rel, (List *) cmd->def); |
| 4466 | break; |
| 4467 | case AT_AttachPartition: |
| 4468 | if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) |
| 4469 | ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def); |
| 4470 | else |
| 4471 | ATExecAttachPartitionIdx(wqueue, rel, |
| 4472 | ((PartitionCmd *) cmd->def)->name); |
| 4473 | break; |
| 4474 | case AT_DetachPartition: |
| 4475 | /* ATPrepCmd ensures it must be a table */ |
| 4476 | Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); |
| 4477 | ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name); |
| 4478 | break; |
| 4479 | default: /* oops */ |
| 4480 | elog(ERROR, "unrecognized alter table type: %d" , |
| 4481 | (int) cmd->subtype); |
| 4482 | break; |
| 4483 | } |
| 4484 | |
| 4485 | /* |
| 4486 | * Report the subcommand to interested event triggers. |
| 4487 | */ |
| 4488 | EventTriggerCollectAlterTableSubcmd((Node *) cmd, address); |
| 4489 | |
| 4490 | /* |
| 4491 | * Bump the command counter to ensure the next subcommand in the sequence |
| 4492 | * can see the changes so far |
| 4493 | */ |
| 4494 | CommandCounterIncrement(); |
| 4495 | } |
| 4496 | |
| 4497 | /* |
| 4498 | * ATRewriteTables: ALTER TABLE phase 3 |
| 4499 | */ |
| 4500 | static void |
| 4501 | ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode) |
| 4502 | { |
| 4503 | ListCell *ltab; |
| 4504 | |
| 4505 | /* Go through each table that needs to be checked or rewritten */ |
| 4506 | foreach(ltab, *wqueue) |
| 4507 | { |
| 4508 | AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab); |
| 4509 | |
| 4510 | /* Relations without storage may be ignored here */ |
| 4511 | if (!RELKIND_HAS_STORAGE(tab->relkind)) |
| 4512 | continue; |
| 4513 | |
| 4514 | /* |
| 4515 | * If we change column data types or add/remove OIDs, the operation |
| 4516 | * has to be propagated to tables that use this table's rowtype as a |
| 4517 | * column type. tab->newvals will also be non-NULL in the case where |
| 4518 | * we're adding a column with a default. We choose to forbid that |
| 4519 | * case as well, since composite types might eventually support |
| 4520 | * defaults. |
| 4521 | * |
| 4522 | * (Eventually we'll probably need to check for composite type |
| 4523 | * dependencies even when we're just scanning the table without a |
| 4524 | * rewrite, but at the moment a composite type does not enforce any |
| 4525 | * constraints, so it's not necessary/appropriate to enforce them just |
| 4526 | * during ALTER.) |
| 4527 | */ |
| 4528 | if (tab->newvals != NIL || tab->rewrite > 0) |
| 4529 | { |
| 4530 | Relation rel; |
| 4531 | |
| 4532 | rel = table_open(tab->relid, NoLock); |
| 4533 | find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL); |
| 4534 | table_close(rel, NoLock); |
| 4535 | } |
| 4536 | |
| 4537 | /* |
| 4538 | * We only need to rewrite the table if at least one column needs to |
| 4539 | * be recomputed, we are adding/removing the OID column, or we are |
| 4540 | * changing its persistence. |
| 4541 | * |
| 4542 | * There are two reasons for requiring a rewrite when changing |
| 4543 | * persistence: on one hand, we need to ensure that the buffers |
| 4544 | * belonging to each of the two relations are marked with or without |
| 4545 | * BM_PERMANENT properly. On the other hand, since rewriting creates |
| 4546 | * and assigns a new relfilenode, we automatically create or drop an |
| 4547 | * init fork for the relation as appropriate. |
| 4548 | */ |
| 4549 | if (tab->rewrite > 0) |
| 4550 | { |
| 4551 | /* Build a temporary relation and copy data */ |
| 4552 | Relation OldHeap; |
| 4553 | Oid OIDNewHeap; |
| 4554 | Oid NewTableSpace; |
| 4555 | char persistence; |
| 4556 | |
| 4557 | OldHeap = table_open(tab->relid, NoLock); |
| 4558 | |
| 4559 | /* |
| 4560 | * We don't support rewriting of system catalogs; there are too |
| 4561 | * many corner cases and too little benefit. In particular this |
| 4562 | * is certainly not going to work for mapped catalogs. |
| 4563 | */ |
| 4564 | if (IsSystemRelation(OldHeap)) |
| 4565 | ereport(ERROR, |
| 4566 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 4567 | errmsg("cannot rewrite system relation \"%s\"" , |
| 4568 | RelationGetRelationName(OldHeap)))); |
| 4569 | |
| 4570 | if (RelationIsUsedAsCatalogTable(OldHeap)) |
| 4571 | ereport(ERROR, |
| 4572 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 4573 | errmsg("cannot rewrite table \"%s\" used as a catalog table" , |
| 4574 | RelationGetRelationName(OldHeap)))); |
| 4575 | |
| 4576 | /* |
| 4577 | * Don't allow rewrite on temp tables of other backends ... their |
| 4578 | * local buffer manager is not going to cope. |
| 4579 | */ |
| 4580 | if (RELATION_IS_OTHER_TEMP(OldHeap)) |
| 4581 | ereport(ERROR, |
| 4582 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 4583 | errmsg("cannot rewrite temporary tables of other sessions" ))); |
| 4584 | |
| 4585 | /* |
| 4586 | * Select destination tablespace (same as original unless user |
| 4587 | * requested a change) |
| 4588 | */ |
| 4589 | if (tab->newTableSpace) |
| 4590 | NewTableSpace = tab->newTableSpace; |
| 4591 | else |
| 4592 | NewTableSpace = OldHeap->rd_rel->reltablespace; |
| 4593 | |
| 4594 | /* |
| 4595 | * Select persistence of transient table (same as original unless |
| 4596 | * user requested a change) |
| 4597 | */ |
| 4598 | persistence = tab->chgPersistence ? |
| 4599 | tab->newrelpersistence : OldHeap->rd_rel->relpersistence; |
| 4600 | |
| 4601 | table_close(OldHeap, NoLock); |
| 4602 | |
| 4603 | /* |
| 4604 | * Fire off an Event Trigger now, before actually rewriting the |
| 4605 | * table. |
| 4606 | * |
| 4607 | * We don't support Event Trigger for nested commands anywhere, |
| 4608 | * here included, and parsetree is given NULL when coming from |
| 4609 | * AlterTableInternal. |
| 4610 | * |
| 4611 | * And fire it only once. |
| 4612 | */ |
| 4613 | if (parsetree) |
| 4614 | EventTriggerTableRewrite((Node *) parsetree, |
| 4615 | tab->relid, |
| 4616 | tab->rewrite); |
| 4617 | |
| 4618 | /* |
| 4619 | * Create transient table that will receive the modified data. |
| 4620 | * |
| 4621 | * Ensure it is marked correctly as logged or unlogged. We have |
| 4622 | * to do this here so that buffers for the new relfilenode will |
| 4623 | * have the right persistence set, and at the same time ensure |
| 4624 | * that the original filenode's buffers will get read in with the |
| 4625 | * correct setting (i.e. the original one). Otherwise a rollback |
| 4626 | * after the rewrite would possibly result with buffers for the |
| 4627 | * original filenode having the wrong persistence setting. |
| 4628 | * |
| 4629 | * NB: This relies on swap_relation_files() also swapping the |
| 4630 | * persistence. That wouldn't work for pg_class, but that can't be |
| 4631 | * unlogged anyway. |
| 4632 | */ |
| 4633 | OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, persistence, |
| 4634 | lockmode); |
| 4635 | |
| 4636 | /* |
| 4637 | * Copy the heap data into the new table with the desired |
| 4638 | * modifications, and test the current data within the table |
| 4639 | * against new constraints generated by ALTER TABLE commands. |
| 4640 | */ |
| 4641 | ATRewriteTable(tab, OIDNewHeap, lockmode); |
| 4642 | |
| 4643 | /* |
| 4644 | * Swap the physical files of the old and new heaps, then rebuild |
| 4645 | * indexes and discard the old heap. We can use RecentXmin for |
| 4646 | * the table's new relfrozenxid because we rewrote all the tuples |
| 4647 | * in ATRewriteTable, so no older Xid remains in the table. Also, |
| 4648 | * we never try to swap toast tables by content, since we have no |
| 4649 | * interest in letting this code work on system catalogs. |
| 4650 | */ |
| 4651 | finish_heap_swap(tab->relid, OIDNewHeap, |
| 4652 | false, false, true, |
| 4653 | !OidIsValid(tab->newTableSpace), |
| 4654 | RecentXmin, |
| 4655 | ReadNextMultiXactId(), |
| 4656 | persistence); |
| 4657 | } |
| 4658 | else |
| 4659 | { |
| 4660 | /* |
| 4661 | * If required, test the current data within the table against new |
| 4662 | * constraints generated by ALTER TABLE commands, but don't |
| 4663 | * rebuild data. |
| 4664 | */ |
| 4665 | if (tab->constraints != NIL || tab->verify_new_notnull || |
| 4666 | tab->partition_constraint != NULL) |
| 4667 | ATRewriteTable(tab, InvalidOid, lockmode); |
| 4668 | |
| 4669 | /* |
| 4670 | * If we had SET TABLESPACE but no reason to reconstruct tuples, |
| 4671 | * just do a block-by-block copy. |
| 4672 | */ |
| 4673 | if (tab->newTableSpace) |
| 4674 | ATExecSetTableSpace(tab->relid, tab->newTableSpace, lockmode); |
| 4675 | } |
| 4676 | } |
| 4677 | |
| 4678 | /* |
| 4679 | * Foreign key constraints are checked in a final pass, since (a) it's |
| 4680 | * generally best to examine each one separately, and (b) it's at least |
| 4681 | * theoretically possible that we have changed both relations of the |
| 4682 | * foreign key, and we'd better have finished both rewrites before we try |
| 4683 | * to read the tables. |
| 4684 | */ |
| 4685 | foreach(ltab, *wqueue) |
| 4686 | { |
| 4687 | AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab); |
| 4688 | Relation rel = NULL; |
| 4689 | ListCell *lcon; |
| 4690 | |
| 4691 | /* Relations without storage may be ignored here too */ |
| 4692 | if (!RELKIND_HAS_STORAGE(tab->relkind)) |
| 4693 | continue; |
| 4694 | |
| 4695 | foreach(lcon, tab->constraints) |
| 4696 | { |
| 4697 | NewConstraint *con = lfirst(lcon); |
| 4698 | |
| 4699 | if (con->contype == CONSTR_FOREIGN) |
| 4700 | { |
| 4701 | Constraint *fkconstraint = (Constraint *) con->qual; |
| 4702 | Relation refrel; |
| 4703 | |
| 4704 | if (rel == NULL) |
| 4705 | { |
| 4706 | /* Long since locked, no need for another */ |
| 4707 | rel = table_open(tab->relid, NoLock); |
| 4708 | } |
| 4709 | |
| 4710 | refrel = table_open(con->refrelid, RowShareLock); |
| 4711 | |
| 4712 | validateForeignKeyConstraint(fkconstraint->conname, rel, refrel, |
| 4713 | con->refindid, |
| 4714 | con->conid); |
| 4715 | |
| 4716 | /* |
| 4717 | * No need to mark the constraint row as validated, we did |
| 4718 | * that when we inserted the row earlier. |
| 4719 | */ |
| 4720 | |
| 4721 | table_close(refrel, NoLock); |
| 4722 | } |
| 4723 | } |
| 4724 | |
| 4725 | if (rel) |
| 4726 | table_close(rel, NoLock); |
| 4727 | } |
| 4728 | } |
| 4729 | |
| 4730 | /* |
| 4731 | * ATRewriteTable: scan or rewrite one table |
| 4732 | * |
| 4733 | * OIDNewHeap is InvalidOid if we don't need to rewrite |
| 4734 | */ |
| 4735 | static void |
| 4736 | ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) |
| 4737 | { |
| 4738 | Relation oldrel; |
| 4739 | Relation newrel; |
| 4740 | TupleDesc oldTupDesc; |
| 4741 | TupleDesc newTupDesc; |
| 4742 | bool needscan = false; |
| 4743 | List *notnull_attrs; |
| 4744 | int i; |
| 4745 | ListCell *l; |
| 4746 | EState *estate; |
| 4747 | CommandId mycid; |
| 4748 | BulkInsertState bistate; |
| 4749 | int ti_options; |
| 4750 | ExprState *partqualstate = NULL; |
| 4751 | |
| 4752 | /* |
| 4753 | * Open the relation(s). We have surely already locked the existing |
| 4754 | * table. |
| 4755 | */ |
| 4756 | oldrel = table_open(tab->relid, NoLock); |
| 4757 | oldTupDesc = tab->oldDesc; |
| 4758 | newTupDesc = RelationGetDescr(oldrel); /* includes all mods */ |
| 4759 | |
| 4760 | if (OidIsValid(OIDNewHeap)) |
| 4761 | newrel = table_open(OIDNewHeap, lockmode); |
| 4762 | else |
| 4763 | newrel = NULL; |
| 4764 | |
| 4765 | /* |
| 4766 | * Prepare a BulkInsertState and options for table_tuple_insert. Because |
| 4767 | * we're building a new heap, we can skip WAL-logging and fsync it to disk |
| 4768 | * at the end instead (unless WAL-logging is required for archiving or |
| 4769 | * streaming replication). The FSM is empty too, so don't bother using it. |
| 4770 | */ |
| 4771 | if (newrel) |
| 4772 | { |
| 4773 | mycid = GetCurrentCommandId(true); |
| 4774 | bistate = GetBulkInsertState(); |
| 4775 | |
| 4776 | ti_options = TABLE_INSERT_SKIP_FSM; |
| 4777 | if (!XLogIsNeeded()) |
| 4778 | ti_options |= TABLE_INSERT_SKIP_WAL; |
| 4779 | } |
| 4780 | else |
| 4781 | { |
| 4782 | /* keep compiler quiet about using these uninitialized */ |
| 4783 | mycid = 0; |
| 4784 | bistate = NULL; |
| 4785 | ti_options = 0; |
| 4786 | } |
| 4787 | |
| 4788 | /* |
| 4789 | * Generate the constraint and default execution states |
| 4790 | */ |
| 4791 | |
| 4792 | estate = CreateExecutorState(); |
| 4793 | |
| 4794 | /* Build the needed expression execution states */ |
| 4795 | foreach(l, tab->constraints) |
| 4796 | { |
| 4797 | NewConstraint *con = lfirst(l); |
| 4798 | |
| 4799 | switch (con->contype) |
| 4800 | { |
| 4801 | case CONSTR_CHECK: |
| 4802 | needscan = true; |
| 4803 | con->qualstate = ExecPrepareExpr((Expr *) con->qual, estate); |
| 4804 | break; |
| 4805 | case CONSTR_FOREIGN: |
| 4806 | /* Nothing to do here */ |
| 4807 | break; |
| 4808 | default: |
| 4809 | elog(ERROR, "unrecognized constraint type: %d" , |
| 4810 | (int) con->contype); |
| 4811 | } |
| 4812 | } |
| 4813 | |
| 4814 | /* Build expression execution states for partition check quals */ |
| 4815 | if (tab->partition_constraint) |
| 4816 | { |
| 4817 | needscan = true; |
| 4818 | partqualstate = ExecPrepareExpr(tab->partition_constraint, estate); |
| 4819 | } |
| 4820 | |
| 4821 | foreach(l, tab->newvals) |
| 4822 | { |
| 4823 | NewColumnValue *ex = lfirst(l); |
| 4824 | |
| 4825 | /* expr already planned */ |
| 4826 | ex->exprstate = ExecInitExpr((Expr *) ex->expr, NULL); |
| 4827 | } |
| 4828 | |
| 4829 | notnull_attrs = NIL; |
| 4830 | if (newrel || tab->verify_new_notnull) |
| 4831 | { |
| 4832 | /* |
| 4833 | * If we are rebuilding the tuples OR if we added any new but not |
| 4834 | * verified NOT NULL constraints, check all not-null constraints. This |
| 4835 | * is a bit of overkill but it minimizes risk of bugs, and |
| 4836 | * heap_attisnull is a pretty cheap test anyway. |
| 4837 | */ |
| 4838 | for (i = 0; i < newTupDesc->natts; i++) |
| 4839 | { |
| 4840 | Form_pg_attribute attr = TupleDescAttr(newTupDesc, i); |
| 4841 | |
| 4842 | if (attr->attnotnull && !attr->attisdropped) |
| 4843 | notnull_attrs = lappend_int(notnull_attrs, i); |
| 4844 | } |
| 4845 | if (notnull_attrs) |
| 4846 | needscan = true; |
| 4847 | } |
| 4848 | |
| 4849 | if (newrel || needscan) |
| 4850 | { |
| 4851 | ExprContext *econtext; |
| 4852 | TupleTableSlot *oldslot; |
| 4853 | TupleTableSlot *newslot; |
| 4854 | TableScanDesc scan; |
| 4855 | MemoryContext oldCxt; |
| 4856 | List *dropped_attrs = NIL; |
| 4857 | ListCell *lc; |
| 4858 | Snapshot snapshot; |
| 4859 | |
| 4860 | if (newrel) |
| 4861 | ereport(DEBUG1, |
| 4862 | (errmsg("rewriting table \"%s\"" , |
| 4863 | RelationGetRelationName(oldrel)))); |
| 4864 | else |
| 4865 | ereport(DEBUG1, |
| 4866 | (errmsg("verifying table \"%s\"" , |
| 4867 | RelationGetRelationName(oldrel)))); |
| 4868 | |
| 4869 | if (newrel) |
| 4870 | { |
| 4871 | /* |
| 4872 | * All predicate locks on the tuples or pages are about to be made |
| 4873 | * invalid, because we move tuples around. Promote them to |
| 4874 | * relation locks. |
| 4875 | */ |
| 4876 | TransferPredicateLocksToHeapRelation(oldrel); |
| 4877 | } |
| 4878 | |
| 4879 | econtext = GetPerTupleExprContext(estate); |
| 4880 | |
| 4881 | /* |
| 4882 | * Create necessary tuple slots. When rewriting, two slots are needed, |
| 4883 | * otherwise one suffices. In the case where one slot suffices, we |
| 4884 | * need to use the new tuple descriptor, otherwise some constraints |
| 4885 | * can't be evaluated. Note that even when the tuple layout is the |
| 4886 | * same and no rewrite is required, the tupDescs might not be |
| 4887 | * (consider ADD COLUMN without a default). |
| 4888 | */ |
| 4889 | if (tab->rewrite) |
| 4890 | { |
| 4891 | Assert(newrel != NULL); |
| 4892 | oldslot = MakeSingleTupleTableSlot(oldTupDesc, |
| 4893 | table_slot_callbacks(oldrel)); |
| 4894 | newslot = MakeSingleTupleTableSlot(newTupDesc, |
| 4895 | table_slot_callbacks(newrel)); |
| 4896 | } |
| 4897 | else |
| 4898 | { |
| 4899 | oldslot = MakeSingleTupleTableSlot(newTupDesc, |
| 4900 | table_slot_callbacks(oldrel)); |
| 4901 | newslot = NULL; |
| 4902 | } |
| 4903 | |
| 4904 | /* |
| 4905 | * Any attributes that are dropped according to the new tuple |
| 4906 | * descriptor can be set to NULL. We precompute the list of dropped |
| 4907 | * attributes to avoid needing to do so in the per-tuple loop. |
| 4908 | */ |
| 4909 | for (i = 0; i < newTupDesc->natts; i++) |
| 4910 | { |
| 4911 | if (TupleDescAttr(newTupDesc, i)->attisdropped) |
| 4912 | dropped_attrs = lappend_int(dropped_attrs, i); |
| 4913 | } |
| 4914 | |
| 4915 | /* |
| 4916 | * Scan through the rows, generating a new row if needed and then |
| 4917 | * checking all the constraints. |
| 4918 | */ |
| 4919 | snapshot = RegisterSnapshot(GetLatestSnapshot()); |
| 4920 | scan = table_beginscan(oldrel, snapshot, 0, NULL); |
| 4921 | |
| 4922 | /* |
| 4923 | * Switch to per-tuple memory context and reset it for each tuple |
| 4924 | * produced, so we don't leak memory. |
| 4925 | */ |
| 4926 | oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); |
| 4927 | |
| 4928 | while (table_scan_getnextslot(scan, ForwardScanDirection, oldslot)) |
| 4929 | { |
| 4930 | TupleTableSlot *insertslot; |
| 4931 | |
| 4932 | if (tab->rewrite > 0) |
| 4933 | { |
| 4934 | /* Extract data from old tuple */ |
| 4935 | slot_getallattrs(oldslot); |
| 4936 | ExecClearTuple(newslot); |
| 4937 | |
| 4938 | /* copy attributes */ |
| 4939 | memcpy(newslot->tts_values, oldslot->tts_values, |
| 4940 | sizeof(Datum) * oldslot->tts_nvalid); |
| 4941 | memcpy(newslot->tts_isnull, oldslot->tts_isnull, |
| 4942 | sizeof(bool) * oldslot->tts_nvalid); |
| 4943 | |
| 4944 | /* Set dropped attributes to null in new tuple */ |
| 4945 | foreach(lc, dropped_attrs) |
| 4946 | newslot->tts_isnull[lfirst_int(lc)] = true; |
| 4947 | |
| 4948 | /* |
| 4949 | * Process supplied expressions to replace selected columns. |
| 4950 | * Expression inputs come from the old tuple. |
| 4951 | */ |
| 4952 | econtext->ecxt_scantuple = oldslot; |
| 4953 | |
| 4954 | foreach(l, tab->newvals) |
| 4955 | { |
| 4956 | NewColumnValue *ex = lfirst(l); |
| 4957 | |
| 4958 | newslot->tts_values[ex->attnum - 1] |
| 4959 | = ExecEvalExpr(ex->exprstate, |
| 4960 | econtext, |
| 4961 | &newslot->tts_isnull[ex->attnum - 1]); |
| 4962 | } |
| 4963 | |
| 4964 | ExecStoreVirtualTuple(newslot); |
| 4965 | |
| 4966 | /* |
| 4967 | * Constraints might reference the tableoid column, so |
| 4968 | * initialize t_tableOid before evaluating them. |
| 4969 | */ |
| 4970 | newslot->tts_tableOid = RelationGetRelid(oldrel); |
| 4971 | insertslot = newslot; |
| 4972 | } |
| 4973 | else |
| 4974 | { |
| 4975 | /* |
| 4976 | * If there's no rewrite, old and new table are guaranteed to |
| 4977 | * have the same AM, so we can just use the old slot to verify |
| 4978 | * new constraints etc. |
| 4979 | */ |
| 4980 | insertslot = oldslot; |
| 4981 | } |
| 4982 | |
| 4983 | /* Now check any constraints on the possibly-changed tuple */ |
| 4984 | econtext->ecxt_scantuple = insertslot; |
| 4985 | |
| 4986 | foreach(l, notnull_attrs) |
| 4987 | { |
| 4988 | int attn = lfirst_int(l); |
| 4989 | |
| 4990 | if (slot_attisnull(insertslot, attn + 1)) |
| 4991 | { |
| 4992 | Form_pg_attribute attr = TupleDescAttr(newTupDesc, attn); |
| 4993 | |
| 4994 | ereport(ERROR, |
| 4995 | (errcode(ERRCODE_NOT_NULL_VIOLATION), |
| 4996 | errmsg("column \"%s\" contains null values" , |
| 4997 | NameStr(attr->attname)), |
| 4998 | errtablecol(oldrel, attn + 1))); |
| 4999 | } |
| 5000 | } |
| 5001 | |
| 5002 | foreach(l, tab->constraints) |
| 5003 | { |
| 5004 | NewConstraint *con = lfirst(l); |
| 5005 | |
| 5006 | switch (con->contype) |
| 5007 | { |
| 5008 | case CONSTR_CHECK: |
| 5009 | if (!ExecCheck(con->qualstate, econtext)) |
| 5010 | ereport(ERROR, |
| 5011 | (errcode(ERRCODE_CHECK_VIOLATION), |
| 5012 | errmsg("check constraint \"%s\" is violated by some row" , |
| 5013 | con->name), |
| 5014 | errtableconstraint(oldrel, con->name))); |
| 5015 | break; |
| 5016 | case CONSTR_FOREIGN: |
| 5017 | /* Nothing to do here */ |
| 5018 | break; |
| 5019 | default: |
| 5020 | elog(ERROR, "unrecognized constraint type: %d" , |
| 5021 | (int) con->contype); |
| 5022 | } |
| 5023 | } |
| 5024 | |
| 5025 | if (partqualstate && !ExecCheck(partqualstate, econtext)) |
| 5026 | { |
| 5027 | if (tab->validate_default) |
| 5028 | ereport(ERROR, |
| 5029 | (errcode(ERRCODE_CHECK_VIOLATION), |
| 5030 | errmsg("updated partition constraint for default partition would be violated by some row" ))); |
| 5031 | else |
| 5032 | ereport(ERROR, |
| 5033 | (errcode(ERRCODE_CHECK_VIOLATION), |
| 5034 | errmsg("partition constraint is violated by some row" ))); |
| 5035 | } |
| 5036 | |
| 5037 | /* Write the tuple out to the new relation */ |
| 5038 | if (newrel) |
| 5039 | table_tuple_insert(newrel, insertslot, mycid, |
| 5040 | ti_options, bistate); |
| 5041 | |
| 5042 | ResetExprContext(econtext); |
| 5043 | |
| 5044 | CHECK_FOR_INTERRUPTS(); |
| 5045 | } |
| 5046 | |
| 5047 | MemoryContextSwitchTo(oldCxt); |
| 5048 | table_endscan(scan); |
| 5049 | UnregisterSnapshot(snapshot); |
| 5050 | |
| 5051 | ExecDropSingleTupleTableSlot(oldslot); |
| 5052 | if (newslot) |
| 5053 | ExecDropSingleTupleTableSlot(newslot); |
| 5054 | } |
| 5055 | |
| 5056 | FreeExecutorState(estate); |
| 5057 | |
| 5058 | table_close(oldrel, NoLock); |
| 5059 | if (newrel) |
| 5060 | { |
| 5061 | FreeBulkInsertState(bistate); |
| 5062 | |
| 5063 | table_finish_bulk_insert(newrel, ti_options); |
| 5064 | |
| 5065 | table_close(newrel, NoLock); |
| 5066 | } |
| 5067 | } |
| 5068 | |
| 5069 | /* |
| 5070 | * ATGetQueueEntry: find or create an entry in the ALTER TABLE work queue |
| 5071 | */ |
| 5072 | static AlteredTableInfo * |
| 5073 | ATGetQueueEntry(List **wqueue, Relation rel) |
| 5074 | { |
| 5075 | Oid relid = RelationGetRelid(rel); |
| 5076 | AlteredTableInfo *tab; |
| 5077 | ListCell *ltab; |
| 5078 | |
| 5079 | foreach(ltab, *wqueue) |
| 5080 | { |
| 5081 | tab = (AlteredTableInfo *) lfirst(ltab); |
| 5082 | if (tab->relid == relid) |
| 5083 | return tab; |
| 5084 | } |
| 5085 | |
| 5086 | /* |
| 5087 | * Not there, so add it. Note that we make a copy of the relation's |
| 5088 | * existing descriptor before anything interesting can happen to it. |
| 5089 | */ |
| 5090 | tab = (AlteredTableInfo *) palloc0(sizeof(AlteredTableInfo)); |
| 5091 | tab->relid = relid; |
| 5092 | tab->relkind = rel->rd_rel->relkind; |
| 5093 | tab->oldDesc = CreateTupleDescCopyConstr(RelationGetDescr(rel)); |
| 5094 | tab->newrelpersistence = RELPERSISTENCE_PERMANENT; |
| 5095 | tab->chgPersistence = false; |
| 5096 | |
| 5097 | *wqueue = lappend(*wqueue, tab); |
| 5098 | |
| 5099 | return tab; |
| 5100 | } |
| 5101 | |
| 5102 | /* |
| 5103 | * ATSimplePermissions |
| 5104 | * |
| 5105 | * - Ensure that it is a relation (or possibly a view) |
| 5106 | * - Ensure this user is the owner |
| 5107 | * - Ensure that it is not a system table |
| 5108 | */ |
| 5109 | static void |
| 5110 | ATSimplePermissions(Relation rel, int allowed_targets) |
| 5111 | { |
| 5112 | int actual_target; |
| 5113 | |
| 5114 | switch (rel->rd_rel->relkind) |
| 5115 | { |
| 5116 | case RELKIND_RELATION: |
| 5117 | case RELKIND_PARTITIONED_TABLE: |
| 5118 | actual_target = ATT_TABLE; |
| 5119 | break; |
| 5120 | case RELKIND_VIEW: |
| 5121 | actual_target = ATT_VIEW; |
| 5122 | break; |
| 5123 | case RELKIND_MATVIEW: |
| 5124 | actual_target = ATT_MATVIEW; |
| 5125 | break; |
| 5126 | case RELKIND_INDEX: |
| 5127 | actual_target = ATT_INDEX; |
| 5128 | break; |
| 5129 | case RELKIND_PARTITIONED_INDEX: |
| 5130 | actual_target = ATT_PARTITIONED_INDEX; |
| 5131 | break; |
| 5132 | case RELKIND_COMPOSITE_TYPE: |
| 5133 | actual_target = ATT_COMPOSITE_TYPE; |
| 5134 | break; |
| 5135 | case RELKIND_FOREIGN_TABLE: |
| 5136 | actual_target = ATT_FOREIGN_TABLE; |
| 5137 | break; |
| 5138 | default: |
| 5139 | actual_target = 0; |
| 5140 | break; |
| 5141 | } |
| 5142 | |
| 5143 | /* Wrong target type? */ |
| 5144 | if ((actual_target & allowed_targets) == 0) |
| 5145 | ATWrongRelkindError(rel, allowed_targets); |
| 5146 | |
| 5147 | /* Permissions checks */ |
| 5148 | if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId())) |
| 5149 | aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind), |
| 5150 | RelationGetRelationName(rel)); |
| 5151 | |
| 5152 | if (!allowSystemTableMods && IsSystemRelation(rel)) |
| 5153 | ereport(ERROR, |
| 5154 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| 5155 | errmsg("permission denied: \"%s\" is a system catalog" , |
| 5156 | RelationGetRelationName(rel)))); |
| 5157 | } |
| 5158 | |
| 5159 | /* |
| 5160 | * ATWrongRelkindError |
| 5161 | * |
| 5162 | * Throw an error when a relation has been determined to be of the wrong |
| 5163 | * type. |
| 5164 | */ |
| 5165 | static void |
| 5166 | ATWrongRelkindError(Relation rel, int allowed_targets) |
| 5167 | { |
| 5168 | char *msg; |
| 5169 | |
| 5170 | switch (allowed_targets) |
| 5171 | { |
| 5172 | case ATT_TABLE: |
| 5173 | msg = _("\"%s\" is not a table" ); |
| 5174 | break; |
| 5175 | case ATT_TABLE | ATT_VIEW: |
| 5176 | msg = _("\"%s\" is not a table or view" ); |
| 5177 | break; |
| 5178 | case ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE: |
| 5179 | msg = _("\"%s\" is not a table, view, or foreign table" ); |
| 5180 | break; |
| 5181 | case ATT_TABLE | ATT_VIEW | ATT_MATVIEW | ATT_INDEX: |
| 5182 | msg = _("\"%s\" is not a table, view, materialized view, or index" ); |
| 5183 | break; |
| 5184 | case ATT_TABLE | ATT_MATVIEW: |
| 5185 | msg = _("\"%s\" is not a table or materialized view" ); |
| 5186 | break; |
| 5187 | case ATT_TABLE | ATT_MATVIEW | ATT_INDEX: |
| 5188 | msg = _("\"%s\" is not a table, materialized view, or index" ); |
| 5189 | break; |
| 5190 | case ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE: |
| 5191 | msg = _("\"%s\" is not a table, materialized view, or foreign table" ); |
| 5192 | break; |
| 5193 | case ATT_TABLE | ATT_FOREIGN_TABLE: |
| 5194 | msg = _("\"%s\" is not a table or foreign table" ); |
| 5195 | break; |
| 5196 | case ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE: |
| 5197 | msg = _("\"%s\" is not a table, composite type, or foreign table" ); |
| 5198 | break; |
| 5199 | case ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_FOREIGN_TABLE: |
| 5200 | msg = _("\"%s\" is not a table, materialized view, index, or foreign table" ); |
| 5201 | break; |
| 5202 | case ATT_VIEW: |
| 5203 | msg = _("\"%s\" is not a view" ); |
| 5204 | break; |
| 5205 | case ATT_FOREIGN_TABLE: |
| 5206 | msg = _("\"%s\" is not a foreign table" ); |
| 5207 | break; |
| 5208 | default: |
| 5209 | /* shouldn't get here, add all necessary cases above */ |
| 5210 | msg = _("\"%s\" is of the wrong type" ); |
| 5211 | break; |
| 5212 | } |
| 5213 | |
| 5214 | ereport(ERROR, |
| 5215 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 5216 | errmsg(msg, RelationGetRelationName(rel)))); |
| 5217 | } |
| 5218 | |
| 5219 | /* |
| 5220 | * ATSimpleRecursion |
| 5221 | * |
| 5222 | * Simple table recursion sufficient for most ALTER TABLE operations. |
| 5223 | * All direct and indirect children are processed in an unspecified order. |
| 5224 | * Note that if a child inherits from the original table via multiple |
| 5225 | * inheritance paths, it will be visited just once. |
| 5226 | */ |
| 5227 | static void |
| 5228 | ATSimpleRecursion(List **wqueue, Relation rel, |
| 5229 | AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode) |
| 5230 | { |
| 5231 | /* |
| 5232 | * Propagate to children if desired. Only plain tables, foreign tables |
| 5233 | * and partitioned tables have children, so no need to search for other |
| 5234 | * relkinds. |
| 5235 | */ |
| 5236 | if (recurse && |
| 5237 | (rel->rd_rel->relkind == RELKIND_RELATION || |
| 5238 | rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE || |
| 5239 | rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)) |
| 5240 | { |
| 5241 | Oid relid = RelationGetRelid(rel); |
| 5242 | ListCell *child; |
| 5243 | List *children; |
| 5244 | |
| 5245 | children = find_all_inheritors(relid, lockmode, NULL); |
| 5246 | |
| 5247 | /* |
| 5248 | * find_all_inheritors does the recursive search of the inheritance |
| 5249 | * hierarchy, so all we have to do is process all of the relids in the |
| 5250 | * list that it returns. |
| 5251 | */ |
| 5252 | foreach(child, children) |
| 5253 | { |
| 5254 | Oid childrelid = lfirst_oid(child); |
| 5255 | Relation childrel; |
| 5256 | |
| 5257 | if (childrelid == relid) |
| 5258 | continue; |
| 5259 | /* find_all_inheritors already got lock */ |
| 5260 | childrel = relation_open(childrelid, NoLock); |
| 5261 | CheckTableNotInUse(childrel, "ALTER TABLE" ); |
| 5262 | ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode); |
| 5263 | relation_close(childrel, NoLock); |
| 5264 | } |
| 5265 | } |
| 5266 | } |
| 5267 | |
| 5268 | /* |
| 5269 | * Obtain list of partitions of the given table, locking them all at the given |
| 5270 | * lockmode and ensuring that they all pass CheckTableNotInUse. |
| 5271 | * |
| 5272 | * This function is a no-op if the given relation is not a partitioned table; |
| 5273 | * in particular, nothing is done if it's a legacy inheritance parent. |
| 5274 | */ |
| 5275 | static void |
| 5276 | ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode) |
| 5277 | { |
| 5278 | if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) |
| 5279 | { |
| 5280 | List *inh; |
| 5281 | ListCell *cell; |
| 5282 | |
| 5283 | inh = find_all_inheritors(RelationGetRelid(rel), lockmode, NULL); |
| 5284 | /* first element is the parent rel; must ignore it */ |
| 5285 | for_each_cell(cell, lnext(list_head(inh))) |
| 5286 | { |
| 5287 | Relation childrel; |
| 5288 | |
| 5289 | /* find_all_inheritors already got lock */ |
| 5290 | childrel = table_open(lfirst_oid(cell), NoLock); |
| 5291 | CheckTableNotInUse(childrel, "ALTER TABLE" ); |
| 5292 | table_close(childrel, NoLock); |
| 5293 | } |
| 5294 | list_free(inh); |
| 5295 | } |
| 5296 | } |
| 5297 | |
| 5298 | /* |
| 5299 | * ATTypedTableRecursion |
| 5300 | * |
| 5301 | * Propagate ALTER TYPE operations to the typed tables of that type. |
| 5302 | * Also check the RESTRICT/CASCADE behavior. Given CASCADE, also permit |
| 5303 | * recursion to inheritance children of the typed tables. |
| 5304 | */ |
| 5305 | static void |
| 5306 | ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd, |
| 5307 | LOCKMODE lockmode) |
| 5308 | { |
| 5309 | ListCell *child; |
| 5310 | List *children; |
| 5311 | |
| 5312 | Assert(rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE); |
| 5313 | |
| 5314 | children = find_typed_table_dependencies(rel->rd_rel->reltype, |
| 5315 | RelationGetRelationName(rel), |
| 5316 | cmd->behavior); |
| 5317 | |
| 5318 | foreach(child, children) |
| 5319 | { |
| 5320 | Oid childrelid = lfirst_oid(child); |
| 5321 | Relation childrel; |
| 5322 | |
| 5323 | childrel = relation_open(childrelid, lockmode); |
| 5324 | CheckTableNotInUse(childrel, "ALTER TABLE" ); |
| 5325 | ATPrepCmd(wqueue, childrel, cmd, true, true, lockmode); |
| 5326 | relation_close(childrel, NoLock); |
| 5327 | } |
| 5328 | } |
| 5329 | |
| 5330 | |
| 5331 | /* |
| 5332 | * find_composite_type_dependencies |
| 5333 | * |
| 5334 | * Check to see if the type "typeOid" is being used as a column in some table |
| 5335 | * (possibly nested several levels deep in composite types, arrays, etc!). |
| 5336 | * Eventually, we'd like to propagate the check or rewrite operation |
| 5337 | * into such tables, but for now, just error out if we find any. |
| 5338 | * |
| 5339 | * Caller should provide either the associated relation of a rowtype, |
| 5340 | * or a type name (not both) for use in the error message, if any. |
| 5341 | * |
| 5342 | * Note that "typeOid" is not necessarily a composite type; it could also be |
| 5343 | * another container type such as an array or range, or a domain over one of |
| 5344 | * these things. The name of this function is therefore somewhat historical, |
| 5345 | * but it's not worth changing. |
| 5346 | * |
| 5347 | * We assume that functions and views depending on the type are not reasons |
| 5348 | * to reject the ALTER. (How safe is this really?) |
| 5349 | */ |
| 5350 | void |
| 5351 | find_composite_type_dependencies(Oid typeOid, Relation origRelation, |
| 5352 | const char *origTypeName) |
| 5353 | { |
| 5354 | Relation depRel; |
| 5355 | ScanKeyData key[2]; |
| 5356 | SysScanDesc depScan; |
| 5357 | HeapTuple depTup; |
| 5358 | |
| 5359 | /* since this function recurses, it could be driven to stack overflow */ |
| 5360 | check_stack_depth(); |
| 5361 | |
| 5362 | /* |
| 5363 | * We scan pg_depend to find those things that depend on the given type. |
| 5364 | * (We assume we can ignore refobjsubid for a type.) |
| 5365 | */ |
| 5366 | depRel = table_open(DependRelationId, AccessShareLock); |
| 5367 | |
| 5368 | ScanKeyInit(&key[0], |
| 5369 | Anum_pg_depend_refclassid, |
| 5370 | BTEqualStrategyNumber, F_OIDEQ, |
| 5371 | ObjectIdGetDatum(TypeRelationId)); |
| 5372 | ScanKeyInit(&key[1], |
| 5373 | Anum_pg_depend_refobjid, |
| 5374 | BTEqualStrategyNumber, F_OIDEQ, |
| 5375 | ObjectIdGetDatum(typeOid)); |
| 5376 | |
| 5377 | depScan = systable_beginscan(depRel, DependReferenceIndexId, true, |
| 5378 | NULL, 2, key); |
| 5379 | |
| 5380 | while (HeapTupleIsValid(depTup = systable_getnext(depScan))) |
| 5381 | { |
| 5382 | Form_pg_depend pg_depend = (Form_pg_depend) GETSTRUCT(depTup); |
| 5383 | Relation rel; |
| 5384 | Form_pg_attribute att; |
| 5385 | |
| 5386 | /* Check for directly dependent types */ |
| 5387 | if (pg_depend->classid == TypeRelationId) |
| 5388 | { |
| 5389 | /* |
| 5390 | * This must be an array, domain, or range containing the given |
| 5391 | * type, so recursively check for uses of this type. Note that |
| 5392 | * any error message will mention the original type not the |
| 5393 | * container; this is intentional. |
| 5394 | */ |
| 5395 | find_composite_type_dependencies(pg_depend->objid, |
| 5396 | origRelation, origTypeName); |
| 5397 | continue; |
| 5398 | } |
| 5399 | |
| 5400 | /* Else, ignore dependees that aren't user columns of relations */ |
| 5401 | /* (we assume system columns are never of interesting types) */ |
| 5402 | if (pg_depend->classid != RelationRelationId || |
| 5403 | pg_depend->objsubid <= 0) |
| 5404 | continue; |
| 5405 | |
| 5406 | rel = relation_open(pg_depend->objid, AccessShareLock); |
| 5407 | att = TupleDescAttr(rel->rd_att, pg_depend->objsubid - 1); |
| 5408 | |
| 5409 | if (rel->rd_rel->relkind == RELKIND_RELATION || |
| 5410 | rel->rd_rel->relkind == RELKIND_MATVIEW || |
| 5411 | rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) |
| 5412 | { |
| 5413 | if (origTypeName) |
| 5414 | ereport(ERROR, |
| 5415 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 5416 | errmsg("cannot alter type \"%s\" because column \"%s.%s\" uses it" , |
| 5417 | origTypeName, |
| 5418 | RelationGetRelationName(rel), |
| 5419 | NameStr(att->attname)))); |
| 5420 | else if (origRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) |
| 5421 | ereport(ERROR, |
| 5422 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 5423 | errmsg("cannot alter type \"%s\" because column \"%s.%s\" uses it" , |
| 5424 | RelationGetRelationName(origRelation), |
| 5425 | RelationGetRelationName(rel), |
| 5426 | NameStr(att->attname)))); |
| 5427 | else if (origRelation->rd_rel->relkind == RELKIND_FOREIGN_TABLE) |
| 5428 | ereport(ERROR, |
| 5429 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 5430 | errmsg("cannot alter foreign table \"%s\" because column \"%s.%s\" uses its row type" , |
| 5431 | RelationGetRelationName(origRelation), |
| 5432 | RelationGetRelationName(rel), |
| 5433 | NameStr(att->attname)))); |
| 5434 | else |
| 5435 | ereport(ERROR, |
| 5436 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 5437 | errmsg("cannot alter table \"%s\" because column \"%s.%s\" uses its row type" , |
| 5438 | RelationGetRelationName(origRelation), |
| 5439 | RelationGetRelationName(rel), |
| 5440 | NameStr(att->attname)))); |
| 5441 | } |
| 5442 | else if (OidIsValid(rel->rd_rel->reltype)) |
| 5443 | { |
| 5444 | /* |
| 5445 | * A view or composite type itself isn't a problem, but we must |
| 5446 | * recursively check for indirect dependencies via its rowtype. |
| 5447 | */ |
| 5448 | find_composite_type_dependencies(rel->rd_rel->reltype, |
| 5449 | origRelation, origTypeName); |
| 5450 | } |
| 5451 | |
| 5452 | relation_close(rel, AccessShareLock); |
| 5453 | } |
| 5454 | |
| 5455 | systable_endscan(depScan); |
| 5456 | |
| 5457 | relation_close(depRel, AccessShareLock); |
| 5458 | } |
| 5459 | |
| 5460 | |
| 5461 | /* |
| 5462 | * find_typed_table_dependencies |
| 5463 | * |
| 5464 | * Check to see if a composite type is being used as the type of a |
| 5465 | * typed table. Abort if any are found and behavior is RESTRICT. |
| 5466 | * Else return the list of tables. |
| 5467 | */ |
| 5468 | static List * |
| 5469 | find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior behavior) |
| 5470 | { |
| 5471 | Relation classRel; |
| 5472 | ScanKeyData key[1]; |
| 5473 | TableScanDesc scan; |
| 5474 | HeapTuple tuple; |
| 5475 | List *result = NIL; |
| 5476 | |
| 5477 | classRel = table_open(RelationRelationId, AccessShareLock); |
| 5478 | |
| 5479 | ScanKeyInit(&key[0], |
| 5480 | Anum_pg_class_reloftype, |
| 5481 | BTEqualStrategyNumber, F_OIDEQ, |
| 5482 | ObjectIdGetDatum(typeOid)); |
| 5483 | |
| 5484 | scan = table_beginscan_catalog(classRel, 1, key); |
| 5485 | |
| 5486 | while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) |
| 5487 | { |
| 5488 | Form_pg_class classform = (Form_pg_class) GETSTRUCT(tuple); |
| 5489 | |
| 5490 | if (behavior == DROP_RESTRICT) |
| 5491 | ereport(ERROR, |
| 5492 | (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), |
| 5493 | errmsg("cannot alter type \"%s\" because it is the type of a typed table" , |
| 5494 | typeName), |
| 5495 | errhint("Use ALTER ... CASCADE to alter the typed tables too." ))); |
| 5496 | else |
| 5497 | result = lappend_oid(result, classform->oid); |
| 5498 | } |
| 5499 | |
| 5500 | table_endscan(scan); |
| 5501 | table_close(classRel, AccessShareLock); |
| 5502 | |
| 5503 | return result; |
| 5504 | } |
| 5505 | |
| 5506 | |
| 5507 | /* |
| 5508 | * check_of_type |
| 5509 | * |
| 5510 | * Check whether a type is suitable for CREATE TABLE OF/ALTER TABLE OF. If it |
| 5511 | * isn't suitable, throw an error. Currently, we require that the type |
| 5512 | * originated with CREATE TYPE AS. We could support any row type, but doing so |
| 5513 | * would require handling a number of extra corner cases in the DDL commands. |
| 5514 | * (Also, allowing domain-over-composite would open up a can of worms about |
| 5515 | * whether and how the domain's constraints should apply to derived tables.) |
| 5516 | */ |
| 5517 | void |
| 5518 | check_of_type(HeapTuple typetuple) |
| 5519 | { |
| 5520 | Form_pg_type typ = (Form_pg_type) GETSTRUCT(typetuple); |
| 5521 | bool typeOk = false; |
| 5522 | |
| 5523 | if (typ->typtype == TYPTYPE_COMPOSITE) |
| 5524 | { |
| 5525 | Relation typeRelation; |
| 5526 | |
| 5527 | Assert(OidIsValid(typ->typrelid)); |
| 5528 | typeRelation = relation_open(typ->typrelid, AccessShareLock); |
| 5529 | typeOk = (typeRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE); |
| 5530 | |
| 5531 | /* |
| 5532 | * Close the parent rel, but keep our AccessShareLock on it until xact |
| 5533 | * commit. That will prevent someone else from deleting or ALTERing |
| 5534 | * the type before the typed table creation/conversion commits. |
| 5535 | */ |
| 5536 | relation_close(typeRelation, NoLock); |
| 5537 | } |
| 5538 | if (!typeOk) |
| 5539 | ereport(ERROR, |
| 5540 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 5541 | errmsg("type %s is not a composite type" , |
| 5542 | format_type_be(typ->oid)))); |
| 5543 | } |
| 5544 | |
| 5545 | |
| 5546 | /* |
| 5547 | * ALTER TABLE ADD COLUMN |
| 5548 | * |
| 5549 | * Adds an additional attribute to a relation making the assumption that |
| 5550 | * CHECK, NOT NULL, and FOREIGN KEY constraints will be removed from the |
| 5551 | * AT_AddColumn AlterTableCmd by parse_utilcmd.c and added as independent |
| 5552 | * AlterTableCmd's. |
| 5553 | * |
| 5554 | * ADD COLUMN cannot use the normal ALTER TABLE recursion mechanism, because we |
| 5555 | * have to decide at runtime whether to recurse or not depending on whether we |
| 5556 | * actually add a column or merely merge with an existing column. (We can't |
| 5557 | * check this in a static pre-pass because it won't handle multiple inheritance |
| 5558 | * situations correctly.) |
| 5559 | */ |
| 5560 | static void |
| 5561 | ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing, |
| 5562 | bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode) |
| 5563 | { |
| 5564 | if (rel->rd_rel->reloftype && !recursing) |
| 5565 | ereport(ERROR, |
| 5566 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 5567 | errmsg("cannot add column to typed table" ))); |
| 5568 | |
| 5569 | if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) |
| 5570 | ATTypedTableRecursion(wqueue, rel, cmd, lockmode); |
| 5571 | |
| 5572 | if (recurse && !is_view) |
| 5573 | cmd->subtype = AT_AddColumnRecurse; |
| 5574 | } |
| 5575 | |
| 5576 | /* |
| 5577 | * Add a column to a table. The return value is the address of the |
| 5578 | * new column in the parent relation. |
| 5579 | */ |
| 5580 | static ObjectAddress |
| 5581 | ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, |
| 5582 | ColumnDef *colDef, |
| 5583 | bool recurse, bool recursing, |
| 5584 | bool if_not_exists, LOCKMODE lockmode) |
| 5585 | { |
| 5586 | Oid myrelid = RelationGetRelid(rel); |
| 5587 | Relation pgclass, |
| 5588 | attrdesc; |
| 5589 | HeapTuple reltup; |
| 5590 | FormData_pg_attribute attribute; |
| 5591 | int newattnum; |
| 5592 | char relkind; |
| 5593 | HeapTuple typeTuple; |
| 5594 | Oid typeOid; |
| 5595 | int32 typmod; |
| 5596 | Oid collOid; |
| 5597 | Form_pg_type tform; |
| 5598 | Expr *defval; |
| 5599 | List *children; |
| 5600 | ListCell *child; |
| 5601 | AclResult aclresult; |
| 5602 | ObjectAddress address; |
| 5603 | |
| 5604 | /* At top level, permission check was done in ATPrepCmd, else do it */ |
| 5605 | if (recursing) |
| 5606 | ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); |
| 5607 | |
| 5608 | if (rel->rd_rel->relispartition && !recursing) |
| 5609 | ereport(ERROR, |
| 5610 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 5611 | errmsg("cannot add column to a partition" ))); |
| 5612 | |
| 5613 | attrdesc = table_open(AttributeRelationId, RowExclusiveLock); |
| 5614 | |
| 5615 | /* |
| 5616 | * Are we adding the column to a recursion child? If so, check whether to |
| 5617 | * merge with an existing definition for the column. If we do merge, we |
| 5618 | * must not recurse. Children will already have the column, and recursing |
| 5619 | * into them would mess up attinhcount. |
| 5620 | */ |
| 5621 | if (colDef->inhcount > 0) |
| 5622 | { |
| 5623 | HeapTuple tuple; |
| 5624 | |
| 5625 | /* Does child already have a column by this name? */ |
| 5626 | tuple = SearchSysCacheCopyAttName(myrelid, colDef->colname); |
| 5627 | if (HeapTupleIsValid(tuple)) |
| 5628 | { |
| 5629 | Form_pg_attribute childatt = (Form_pg_attribute) GETSTRUCT(tuple); |
| 5630 | Oid ctypeId; |
| 5631 | int32 ctypmod; |
| 5632 | Oid ccollid; |
| 5633 | |
| 5634 | /* Child column must match on type, typmod, and collation */ |
| 5635 | typenameTypeIdAndMod(NULL, colDef->typeName, &ctypeId, &ctypmod); |
| 5636 | if (ctypeId != childatt->atttypid || |
| 5637 | ctypmod != childatt->atttypmod) |
| 5638 | ereport(ERROR, |
| 5639 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
| 5640 | errmsg("child table \"%s\" has different type for column \"%s\"" , |
| 5641 | RelationGetRelationName(rel), colDef->colname))); |
| 5642 | ccollid = GetColumnDefCollation(NULL, colDef, ctypeId); |
| 5643 | if (ccollid != childatt->attcollation) |
| 5644 | ereport(ERROR, |
| 5645 | (errcode(ERRCODE_COLLATION_MISMATCH), |
| 5646 | errmsg("child table \"%s\" has different collation for column \"%s\"" , |
| 5647 | RelationGetRelationName(rel), colDef->colname), |
| 5648 | errdetail("\"%s\" versus \"%s\"" , |
| 5649 | get_collation_name(ccollid), |
| 5650 | get_collation_name(childatt->attcollation)))); |
| 5651 | |
| 5652 | /* Bump the existing child att's inhcount */ |
| 5653 | childatt->attinhcount++; |
| 5654 | CatalogTupleUpdate(attrdesc, &tuple->t_self, tuple); |
| 5655 | |
| 5656 | heap_freetuple(tuple); |
| 5657 | |
| 5658 | /* Inform the user about the merge */ |
| 5659 | ereport(NOTICE, |
| 5660 | (errmsg("merging definition of column \"%s\" for child \"%s\"" , |
| 5661 | colDef->colname, RelationGetRelationName(rel)))); |
| 5662 | |
| 5663 | table_close(attrdesc, RowExclusiveLock); |
| 5664 | return InvalidObjectAddress; |
| 5665 | } |
| 5666 | } |
| 5667 | |
| 5668 | pgclass = table_open(RelationRelationId, RowExclusiveLock); |
| 5669 | |
| 5670 | reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid)); |
| 5671 | if (!HeapTupleIsValid(reltup)) |
| 5672 | elog(ERROR, "cache lookup failed for relation %u" , myrelid); |
| 5673 | relkind = ((Form_pg_class) GETSTRUCT(reltup))->relkind; |
| 5674 | |
| 5675 | /* |
| 5676 | * Cannot add identity column if table has children, because identity does |
| 5677 | * not inherit. (Adding column and identity separately will work.) |
| 5678 | */ |
| 5679 | if (colDef->identity && |
| 5680 | recurse && |
| 5681 | find_inheritance_children(myrelid, NoLock) != NIL) |
| 5682 | ereport(ERROR, |
| 5683 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 5684 | errmsg("cannot recursively add identity column to table that has child tables" ))); |
| 5685 | |
| 5686 | /* skip if the name already exists and if_not_exists is true */ |
| 5687 | if (!check_for_column_name_collision(rel, colDef->colname, if_not_exists)) |
| 5688 | { |
| 5689 | table_close(attrdesc, RowExclusiveLock); |
| 5690 | heap_freetuple(reltup); |
| 5691 | table_close(pgclass, RowExclusiveLock); |
| 5692 | return InvalidObjectAddress; |
| 5693 | } |
| 5694 | |
| 5695 | /* Determine the new attribute's number */ |
| 5696 | newattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts + 1; |
| 5697 | if (newattnum > MaxHeapAttributeNumber) |
| 5698 | ereport(ERROR, |
| 5699 | (errcode(ERRCODE_TOO_MANY_COLUMNS), |
| 5700 | errmsg("tables can have at most %d columns" , |
| 5701 | MaxHeapAttributeNumber))); |
| 5702 | |
| 5703 | typeTuple = typenameType(NULL, colDef->typeName, &typmod); |
| 5704 | tform = (Form_pg_type) GETSTRUCT(typeTuple); |
| 5705 | typeOid = tform->oid; |
| 5706 | |
| 5707 | aclresult = pg_type_aclcheck(typeOid, GetUserId(), ACL_USAGE); |
| 5708 | if (aclresult != ACLCHECK_OK) |
| 5709 | aclcheck_error_type(aclresult, typeOid); |
| 5710 | |
| 5711 | collOid = GetColumnDefCollation(NULL, colDef, typeOid); |
| 5712 | |
| 5713 | /* make sure datatype is legal for a column */ |
| 5714 | CheckAttributeType(colDef->colname, typeOid, collOid, |
| 5715 | list_make1_oid(rel->rd_rel->reltype), |
| 5716 | 0); |
| 5717 | |
| 5718 | /* construct new attribute's pg_attribute entry */ |
| 5719 | attribute.attrelid = myrelid; |
| 5720 | namestrcpy(&(attribute.attname), colDef->colname); |
| 5721 | attribute.atttypid = typeOid; |
| 5722 | attribute.attstattarget = (newattnum > 0) ? -1 : 0; |
| 5723 | attribute.attlen = tform->typlen; |
| 5724 | attribute.atttypmod = typmod; |
| 5725 | attribute.attnum = newattnum; |
| 5726 | attribute.attbyval = tform->typbyval; |
| 5727 | attribute.attndims = list_length(colDef->typeName->arrayBounds); |
| 5728 | attribute.attstorage = tform->typstorage; |
| 5729 | attribute.attalign = tform->typalign; |
| 5730 | attribute.attnotnull = colDef->is_not_null; |
| 5731 | attribute.atthasdef = false; |
| 5732 | attribute.atthasmissing = false; |
| 5733 | attribute.attidentity = colDef->identity; |
| 5734 | attribute.attgenerated = colDef->generated; |
| 5735 | attribute.attisdropped = false; |
| 5736 | attribute.attislocal = colDef->is_local; |
| 5737 | attribute.attinhcount = colDef->inhcount; |
| 5738 | attribute.attcollation = collOid; |
| 5739 | /* attribute.attacl is handled by InsertPgAttributeTuple */ |
| 5740 | |
| 5741 | ReleaseSysCache(typeTuple); |
| 5742 | |
| 5743 | InsertPgAttributeTuple(attrdesc, &attribute, NULL); |
| 5744 | |
| 5745 | table_close(attrdesc, RowExclusiveLock); |
| 5746 | |
| 5747 | /* |
| 5748 | * Update pg_class tuple as appropriate |
| 5749 | */ |
| 5750 | ((Form_pg_class) GETSTRUCT(reltup))->relnatts = newattnum; |
| 5751 | |
| 5752 | CatalogTupleUpdate(pgclass, &reltup->t_self, reltup); |
| 5753 | |
| 5754 | heap_freetuple(reltup); |
| 5755 | |
| 5756 | /* Post creation hook for new attribute */ |
| 5757 | InvokeObjectPostCreateHook(RelationRelationId, myrelid, newattnum); |
| 5758 | |
| 5759 | table_close(pgclass, RowExclusiveLock); |
| 5760 | |
| 5761 | /* Make the attribute's catalog entry visible */ |
| 5762 | CommandCounterIncrement(); |
| 5763 | |
| 5764 | /* |
| 5765 | * Store the DEFAULT, if any, in the catalogs |
| 5766 | */ |
| 5767 | if (colDef->raw_default) |
| 5768 | { |
| 5769 | RawColumnDefault *rawEnt; |
| 5770 | |
| 5771 | rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault)); |
| 5772 | rawEnt->attnum = attribute.attnum; |
| 5773 | rawEnt->raw_default = copyObject(colDef->raw_default); |
| 5774 | |
| 5775 | /* |
| 5776 | * Attempt to skip a complete table rewrite by storing the specified |
| 5777 | * DEFAULT value outside of the heap. This may be disabled inside |
| 5778 | * AddRelationNewConstraints if the optimization cannot be applied. |
| 5779 | */ |
| 5780 | rawEnt->missingMode = (!colDef->generated); |
| 5781 | |
| 5782 | rawEnt->generated = colDef->generated; |
| 5783 | |
| 5784 | /* |
| 5785 | * This function is intended for CREATE TABLE, so it processes a |
| 5786 | * _list_ of defaults, but we just do one. |
| 5787 | */ |
| 5788 | AddRelationNewConstraints(rel, list_make1(rawEnt), NIL, |
| 5789 | false, true, false, NULL); |
| 5790 | |
| 5791 | /* Make the additional catalog changes visible */ |
| 5792 | CommandCounterIncrement(); |
| 5793 | |
| 5794 | /* |
| 5795 | * Did the request for a missing value work? If not we'll have to do a |
| 5796 | * rewrite |
| 5797 | */ |
| 5798 | if (!rawEnt->missingMode) |
| 5799 | tab->rewrite |= AT_REWRITE_DEFAULT_VAL; |
| 5800 | } |
| 5801 | |
| 5802 | /* |
| 5803 | * Tell Phase 3 to fill in the default expression, if there is one. |
| 5804 | * |
| 5805 | * If there is no default, Phase 3 doesn't have to do anything, because |
| 5806 | * that effectively means that the default is NULL. The heap tuple access |
| 5807 | * routines always check for attnum > # of attributes in tuple, and return |
| 5808 | * NULL if so, so without any modification of the tuple data we will get |
| 5809 | * the effect of NULL values in the new column. |
| 5810 | * |
| 5811 | * An exception occurs when the new column is of a domain type: the domain |
| 5812 | * might have a NOT NULL constraint, or a check constraint that indirectly |
| 5813 | * rejects nulls. If there are any domain constraints then we construct |
| 5814 | * an explicit NULL default value that will be passed through |
| 5815 | * CoerceToDomain processing. (This is a tad inefficient, since it causes |
| 5816 | * rewriting the table which we really don't have to do, but the present |
| 5817 | * design of domain processing doesn't offer any simple way of checking |
| 5818 | * the constraints more directly.) |
| 5819 | * |
| 5820 | * Note: we use build_column_default, and not just the cooked default |
| 5821 | * returned by AddRelationNewConstraints, so that the right thing happens |
| 5822 | * when a datatype's default applies. |
| 5823 | * |
| 5824 | * We skip this step completely for views and foreign tables. For a view, |
| 5825 | * we can only get here from CREATE OR REPLACE VIEW, which historically |
| 5826 | * doesn't set up defaults, not even for domain-typed columns. And in any |
| 5827 | * case we mustn't invoke Phase 3 on a view or foreign table, since they |
| 5828 | * have no storage. |
| 5829 | */ |
| 5830 | if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE |
| 5831 | && relkind != RELKIND_FOREIGN_TABLE && attribute.attnum > 0) |
| 5832 | { |
| 5833 | /* |
| 5834 | * For an identity column, we can't use build_column_default(), |
| 5835 | * because the sequence ownership isn't set yet. So do it manually. |
| 5836 | */ |
| 5837 | if (colDef->identity) |
| 5838 | { |
| 5839 | NextValueExpr *nve = makeNode(NextValueExpr); |
| 5840 | |
| 5841 | nve->seqid = RangeVarGetRelid(colDef->identitySequence, NoLock, false); |
| 5842 | nve->typeId = typeOid; |
| 5843 | |
| 5844 | defval = (Expr *) nve; |
| 5845 | |
| 5846 | /* must do a rewrite for identity columns */ |
| 5847 | tab->rewrite |= AT_REWRITE_DEFAULT_VAL; |
| 5848 | } |
| 5849 | else |
| 5850 | defval = (Expr *) build_column_default(rel, attribute.attnum); |
| 5851 | |
| 5852 | if (!defval && DomainHasConstraints(typeOid)) |
| 5853 | { |
| 5854 | Oid baseTypeId; |
| 5855 | int32 baseTypeMod; |
| 5856 | Oid baseTypeColl; |
| 5857 | |
| 5858 | baseTypeMod = typmod; |
| 5859 | baseTypeId = getBaseTypeAndTypmod(typeOid, &baseTypeMod); |
| 5860 | baseTypeColl = get_typcollation(baseTypeId); |
| 5861 | defval = (Expr *) makeNullConst(baseTypeId, baseTypeMod, baseTypeColl); |
| 5862 | defval = (Expr *) coerce_to_target_type(NULL, |
| 5863 | (Node *) defval, |
| 5864 | baseTypeId, |
| 5865 | typeOid, |
| 5866 | typmod, |
| 5867 | COERCION_ASSIGNMENT, |
| 5868 | COERCE_IMPLICIT_CAST, |
| 5869 | -1); |
| 5870 | if (defval == NULL) /* should not happen */ |
| 5871 | elog(ERROR, "failed to coerce base type to domain" ); |
| 5872 | } |
| 5873 | |
| 5874 | if (defval) |
| 5875 | { |
| 5876 | NewColumnValue *newval; |
| 5877 | |
| 5878 | newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue)); |
| 5879 | newval->attnum = attribute.attnum; |
| 5880 | newval->expr = expression_planner(defval); |
| 5881 | |
| 5882 | tab->newvals = lappend(tab->newvals, newval); |
| 5883 | } |
| 5884 | |
| 5885 | if (DomainHasConstraints(typeOid)) |
| 5886 | tab->rewrite |= AT_REWRITE_DEFAULT_VAL; |
| 5887 | |
| 5888 | if (!TupleDescAttr(rel->rd_att, attribute.attnum - 1)->atthasmissing) |
| 5889 | { |
| 5890 | /* |
| 5891 | * If the new column is NOT NULL, and there is no missing value, |
| 5892 | * tell Phase 3 it needs to check for NULLs. |
| 5893 | */ |
| 5894 | tab->verify_new_notnull |= colDef->is_not_null; |
| 5895 | } |
| 5896 | } |
| 5897 | |
| 5898 | /* |
| 5899 | * Add needed dependency entries for the new column. |
| 5900 | */ |
| 5901 | add_column_datatype_dependency(myrelid, newattnum, attribute.atttypid); |
| 5902 | add_column_collation_dependency(myrelid, newattnum, attribute.attcollation); |
| 5903 | |
| 5904 | /* |
| 5905 | * Propagate to children as appropriate. Unlike most other ALTER |
| 5906 | * routines, we have to do this one level of recursion at a time; we can't |
| 5907 | * use find_all_inheritors to do it in one pass. |
| 5908 | */ |
| 5909 | children = find_inheritance_children(RelationGetRelid(rel), lockmode); |
| 5910 | |
| 5911 | /* |
| 5912 | * If we are told not to recurse, there had better not be any child |
| 5913 | * tables; else the addition would put them out of step. |
| 5914 | */ |
| 5915 | if (children && !recurse) |
| 5916 | ereport(ERROR, |
| 5917 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 5918 | errmsg("column must be added to child tables too" ))); |
| 5919 | |
| 5920 | /* Children should see column as singly inherited */ |
| 5921 | if (!recursing) |
| 5922 | { |
| 5923 | colDef = copyObject(colDef); |
| 5924 | colDef->inhcount = 1; |
| 5925 | colDef->is_local = false; |
| 5926 | } |
| 5927 | |
| 5928 | foreach(child, children) |
| 5929 | { |
| 5930 | Oid childrelid = lfirst_oid(child); |
| 5931 | Relation childrel; |
| 5932 | AlteredTableInfo *childtab; |
| 5933 | |
| 5934 | /* find_inheritance_children already got lock */ |
| 5935 | childrel = table_open(childrelid, NoLock); |
| 5936 | CheckTableNotInUse(childrel, "ALTER TABLE" ); |
| 5937 | |
| 5938 | /* Find or create work queue entry for this table */ |
| 5939 | childtab = ATGetQueueEntry(wqueue, childrel); |
| 5940 | |
| 5941 | /* Recurse to child; return value is ignored */ |
| 5942 | ATExecAddColumn(wqueue, childtab, childrel, |
| 5943 | colDef, recurse, true, |
| 5944 | if_not_exists, lockmode); |
| 5945 | |
| 5946 | table_close(childrel, NoLock); |
| 5947 | } |
| 5948 | |
| 5949 | ObjectAddressSubSet(address, RelationRelationId, myrelid, newattnum); |
| 5950 | return address; |
| 5951 | } |
| 5952 | |
| 5953 | /* |
| 5954 | * If a new or renamed column will collide with the name of an existing |
| 5955 | * column and if_not_exists is false then error out, else do nothing. |
| 5956 | */ |
| 5957 | static bool |
| 5958 | check_for_column_name_collision(Relation rel, const char *colname, |
| 5959 | bool if_not_exists) |
| 5960 | { |
| 5961 | HeapTuple attTuple; |
| 5962 | int attnum; |
| 5963 | |
| 5964 | /* |
| 5965 | * this test is deliberately not attisdropped-aware, since if one tries to |
| 5966 | * add a column matching a dropped column name, it's gonna fail anyway. |
| 5967 | */ |
| 5968 | attTuple = SearchSysCache2(ATTNAME, |
| 5969 | ObjectIdGetDatum(RelationGetRelid(rel)), |
| 5970 | PointerGetDatum(colname)); |
| 5971 | if (!HeapTupleIsValid(attTuple)) |
| 5972 | return true; |
| 5973 | |
| 5974 | attnum = ((Form_pg_attribute) GETSTRUCT(attTuple))->attnum; |
| 5975 | ReleaseSysCache(attTuple); |
| 5976 | |
| 5977 | /* |
| 5978 | * We throw a different error message for conflicts with system column |
| 5979 | * names, since they are normally not shown and the user might otherwise |
| 5980 | * be confused about the reason for the conflict. |
| 5981 | */ |
| 5982 | if (attnum <= 0) |
| 5983 | ereport(ERROR, |
| 5984 | (errcode(ERRCODE_DUPLICATE_COLUMN), |
| 5985 | errmsg("column name \"%s\" conflicts with a system column name" , |
| 5986 | colname))); |
| 5987 | else |
| 5988 | { |
| 5989 | if (if_not_exists) |
| 5990 | { |
| 5991 | ereport(NOTICE, |
| 5992 | (errcode(ERRCODE_DUPLICATE_COLUMN), |
| 5993 | errmsg("column \"%s\" of relation \"%s\" already exists, skipping" , |
| 5994 | colname, RelationGetRelationName(rel)))); |
| 5995 | return false; |
| 5996 | } |
| 5997 | |
| 5998 | ereport(ERROR, |
| 5999 | (errcode(ERRCODE_DUPLICATE_COLUMN), |
| 6000 | errmsg("column \"%s\" of relation \"%s\" already exists" , |
| 6001 | colname, RelationGetRelationName(rel)))); |
| 6002 | } |
| 6003 | |
| 6004 | return true; |
| 6005 | } |
| 6006 | |
| 6007 | /* |
| 6008 | * Install a column's dependency on its datatype. |
| 6009 | */ |
| 6010 | static void |
| 6011 | add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid) |
| 6012 | { |
| 6013 | ObjectAddress myself, |
| 6014 | referenced; |
| 6015 | |
| 6016 | myself.classId = RelationRelationId; |
| 6017 | myself.objectId = relid; |
| 6018 | myself.objectSubId = attnum; |
| 6019 | referenced.classId = TypeRelationId; |
| 6020 | referenced.objectId = typid; |
| 6021 | referenced.objectSubId = 0; |
| 6022 | recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); |
| 6023 | } |
| 6024 | |
| 6025 | /* |
| 6026 | * Install a column's dependency on its collation. |
| 6027 | */ |
| 6028 | static void |
| 6029 | add_column_collation_dependency(Oid relid, int32 attnum, Oid collid) |
| 6030 | { |
| 6031 | ObjectAddress myself, |
| 6032 | referenced; |
| 6033 | |
| 6034 | /* We know the default collation is pinned, so don't bother recording it */ |
| 6035 | if (OidIsValid(collid) && collid != DEFAULT_COLLATION_OID) |
| 6036 | { |
| 6037 | myself.classId = RelationRelationId; |
| 6038 | myself.objectId = relid; |
| 6039 | myself.objectSubId = attnum; |
| 6040 | referenced.classId = CollationRelationId; |
| 6041 | referenced.objectId = collid; |
| 6042 | referenced.objectSubId = 0; |
| 6043 | recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); |
| 6044 | } |
| 6045 | } |
| 6046 | |
| 6047 | /* |
| 6048 | * ALTER TABLE ALTER COLUMN DROP NOT NULL |
| 6049 | */ |
| 6050 | |
| 6051 | static void |
| 6052 | ATPrepDropNotNull(Relation rel, bool recurse, bool recursing) |
| 6053 | { |
| 6054 | /* |
| 6055 | * If the parent is a partitioned table, like check constraints, we do not |
| 6056 | * support removing the NOT NULL while partitions exist. |
| 6057 | */ |
| 6058 | if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) |
| 6059 | { |
| 6060 | PartitionDesc partdesc = RelationGetPartitionDesc(rel); |
| 6061 | |
| 6062 | Assert(partdesc != NULL); |
| 6063 | if (partdesc->nparts > 0 && !recurse && !recursing) |
| 6064 | ereport(ERROR, |
| 6065 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 6066 | errmsg("cannot remove constraint from only the partitioned table when partitions exist" ), |
| 6067 | errhint("Do not specify the ONLY keyword." ))); |
| 6068 | } |
| 6069 | } |
| 6070 | |
| 6071 | /* |
| 6072 | * Return the address of the modified column. If the column was already |
| 6073 | * nullable, InvalidObjectAddress is returned. |
| 6074 | */ |
| 6075 | static ObjectAddress |
| 6076 | ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode) |
| 6077 | { |
| 6078 | HeapTuple tuple; |
| 6079 | Form_pg_attribute attTup; |
| 6080 | AttrNumber attnum; |
| 6081 | Relation attr_rel; |
| 6082 | List *indexoidlist; |
| 6083 | ListCell *indexoidscan; |
| 6084 | ObjectAddress address; |
| 6085 | |
| 6086 | /* |
| 6087 | * lookup the attribute |
| 6088 | */ |
| 6089 | attr_rel = table_open(AttributeRelationId, RowExclusiveLock); |
| 6090 | |
| 6091 | tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); |
| 6092 | if (!HeapTupleIsValid(tuple)) |
| 6093 | ereport(ERROR, |
| 6094 | (errcode(ERRCODE_UNDEFINED_COLUMN), |
| 6095 | errmsg("column \"%s\" of relation \"%s\" does not exist" , |
| 6096 | colName, RelationGetRelationName(rel)))); |
| 6097 | attTup = (Form_pg_attribute) GETSTRUCT(tuple); |
| 6098 | attnum = attTup->attnum; |
| 6099 | |
| 6100 | /* Prevent them from altering a system attribute */ |
| 6101 | if (attnum <= 0) |
| 6102 | ereport(ERROR, |
| 6103 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 6104 | errmsg("cannot alter system column \"%s\"" , |
| 6105 | colName))); |
| 6106 | |
| 6107 | if (attTup->attidentity) |
| 6108 | ereport(ERROR, |
| 6109 | (errcode(ERRCODE_SYNTAX_ERROR), |
| 6110 | errmsg("column \"%s\" of relation \"%s\" is an identity column" , |
| 6111 | colName, RelationGetRelationName(rel)))); |
| 6112 | |
| 6113 | /* |
| 6114 | * Check that the attribute is not in a primary key |
| 6115 | * |
| 6116 | * Note: we'll throw error even if the pkey index is not valid. |
| 6117 | */ |
| 6118 | |
| 6119 | /* Loop over all indexes on the relation */ |
| 6120 | indexoidlist = RelationGetIndexList(rel); |
| 6121 | |
| 6122 | foreach(indexoidscan, indexoidlist) |
| 6123 | { |
| 6124 | Oid indexoid = lfirst_oid(indexoidscan); |
| 6125 | HeapTuple indexTuple; |
| 6126 | Form_pg_index indexStruct; |
| 6127 | int i; |
| 6128 | |
| 6129 | indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid)); |
| 6130 | if (!HeapTupleIsValid(indexTuple)) |
| 6131 | elog(ERROR, "cache lookup failed for index %u" , indexoid); |
| 6132 | indexStruct = (Form_pg_index) GETSTRUCT(indexTuple); |
| 6133 | |
| 6134 | /* If the index is not a primary key, skip the check */ |
| 6135 | if (indexStruct->indisprimary) |
| 6136 | { |
| 6137 | /* |
| 6138 | * Loop over each attribute in the primary key and see if it |
| 6139 | * matches the to-be-altered attribute |
| 6140 | */ |
| 6141 | for (i = 0; i < indexStruct->indnkeyatts; i++) |
| 6142 | { |
| 6143 | if (indexStruct->indkey.values[i] == attnum) |
| 6144 | ereport(ERROR, |
| 6145 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 6146 | errmsg("column \"%s\" is in a primary key" , |
| 6147 | colName))); |
| 6148 | } |
| 6149 | } |
| 6150 | |
| 6151 | ReleaseSysCache(indexTuple); |
| 6152 | } |
| 6153 | |
| 6154 | list_free(indexoidlist); |
| 6155 | |
| 6156 | /* If rel is partition, shouldn't drop NOT NULL if parent has the same */ |
| 6157 | if (rel->rd_rel->relispartition) |
| 6158 | { |
| 6159 | Oid parentId = get_partition_parent(RelationGetRelid(rel)); |
| 6160 | Relation parent = table_open(parentId, AccessShareLock); |
| 6161 | TupleDesc tupDesc = RelationGetDescr(parent); |
| 6162 | AttrNumber parent_attnum; |
| 6163 | |
| 6164 | parent_attnum = get_attnum(parentId, colName); |
| 6165 | if (TupleDescAttr(tupDesc, parent_attnum - 1)->attnotnull) |
| 6166 | ereport(ERROR, |
| 6167 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 6168 | errmsg("column \"%s\" is marked NOT NULL in parent table" , |
| 6169 | colName))); |
| 6170 | table_close(parent, AccessShareLock); |
| 6171 | } |
| 6172 | |
| 6173 | /* |
| 6174 | * Okay, actually perform the catalog change ... if needed |
| 6175 | */ |
| 6176 | if (attTup->attnotnull) |
| 6177 | { |
| 6178 | attTup->attnotnull = false; |
| 6179 | |
| 6180 | CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple); |
| 6181 | |
| 6182 | ObjectAddressSubSet(address, RelationRelationId, |
| 6183 | RelationGetRelid(rel), attnum); |
| 6184 | } |
| 6185 | else |
| 6186 | address = InvalidObjectAddress; |
| 6187 | |
| 6188 | InvokeObjectPostAlterHook(RelationRelationId, |
| 6189 | RelationGetRelid(rel), attnum); |
| 6190 | |
| 6191 | table_close(attr_rel, RowExclusiveLock); |
| 6192 | |
| 6193 | return address; |
| 6194 | } |
| 6195 | |
| 6196 | /* |
| 6197 | * ALTER TABLE ALTER COLUMN SET NOT NULL |
| 6198 | */ |
| 6199 | |
| 6200 | static void |
| 6201 | ATPrepSetNotNull(List **wqueue, Relation rel, |
| 6202 | AlterTableCmd *cmd, bool recurse, bool recursing, |
| 6203 | LOCKMODE lockmode) |
| 6204 | { |
| 6205 | /* |
| 6206 | * If we're already recursing, there's nothing to do; the topmost |
| 6207 | * invocation of ATSimpleRecursion already visited all children. |
| 6208 | */ |
| 6209 | if (recursing) |
| 6210 | return; |
| 6211 | |
| 6212 | /* |
| 6213 | * If we have ALTER TABLE ONLY ... SET NOT NULL on a partitioned table, |
| 6214 | * apply ALTER TABLE ... CHECK NOT NULL to every child. Otherwise, use |
| 6215 | * normal recursion logic. |
| 6216 | */ |
| 6217 | if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && |
| 6218 | !recurse) |
| 6219 | { |
| 6220 | AlterTableCmd *newcmd = makeNode(AlterTableCmd); |
| 6221 | |
| 6222 | newcmd->subtype = AT_CheckNotNull; |
| 6223 | newcmd->name = pstrdup(cmd->name); |
| 6224 | ATSimpleRecursion(wqueue, rel, newcmd, true, lockmode); |
| 6225 | } |
| 6226 | else |
| 6227 | ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); |
| 6228 | } |
| 6229 | |
| 6230 | /* |
| 6231 | * Return the address of the modified column. If the column was already NOT |
| 6232 | * NULL, InvalidObjectAddress is returned. |
| 6233 | */ |
| 6234 | static ObjectAddress |
| 6235 | ATExecSetNotNull(AlteredTableInfo *tab, Relation rel, |
| 6236 | const char *colName, LOCKMODE lockmode) |
| 6237 | { |
| 6238 | HeapTuple tuple; |
| 6239 | AttrNumber attnum; |
| 6240 | Relation attr_rel; |
| 6241 | ObjectAddress address; |
| 6242 | |
| 6243 | /* |
| 6244 | * lookup the attribute |
| 6245 | */ |
| 6246 | attr_rel = table_open(AttributeRelationId, RowExclusiveLock); |
| 6247 | |
| 6248 | tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); |
| 6249 | |
| 6250 | if (!HeapTupleIsValid(tuple)) |
| 6251 | ereport(ERROR, |
| 6252 | (errcode(ERRCODE_UNDEFINED_COLUMN), |
| 6253 | errmsg("column \"%s\" of relation \"%s\" does not exist" , |
| 6254 | colName, RelationGetRelationName(rel)))); |
| 6255 | |
| 6256 | attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum; |
| 6257 | |
| 6258 | /* Prevent them from altering a system attribute */ |
| 6259 | if (attnum <= 0) |
| 6260 | ereport(ERROR, |
| 6261 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 6262 | errmsg("cannot alter system column \"%s\"" , |
| 6263 | colName))); |
| 6264 | |
| 6265 | /* |
| 6266 | * Okay, actually perform the catalog change ... if needed |
| 6267 | */ |
| 6268 | if (!((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull) |
| 6269 | { |
| 6270 | ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = true; |
| 6271 | |
| 6272 | CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple); |
| 6273 | |
| 6274 | /* |
| 6275 | * Ordinarily phase 3 must ensure that no NULLs exist in columns that |
| 6276 | * are set NOT NULL; however, if we can find a constraint which proves |
| 6277 | * this then we can skip that. We needn't bother looking if we've |
| 6278 | * already found that we must verify some other NOT NULL constraint. |
| 6279 | */ |
| 6280 | if (!tab->verify_new_notnull && |
| 6281 | !NotNullImpliedByRelConstraints(rel, (Form_pg_attribute) GETSTRUCT(tuple))) |
| 6282 | { |
| 6283 | /* Tell Phase 3 it needs to test the constraint */ |
| 6284 | tab->verify_new_notnull = true; |
| 6285 | } |
| 6286 | |
| 6287 | ObjectAddressSubSet(address, RelationRelationId, |
| 6288 | RelationGetRelid(rel), attnum); |
| 6289 | } |
| 6290 | else |
| 6291 | address = InvalidObjectAddress; |
| 6292 | |
| 6293 | InvokeObjectPostAlterHook(RelationRelationId, |
| 6294 | RelationGetRelid(rel), attnum); |
| 6295 | |
| 6296 | table_close(attr_rel, RowExclusiveLock); |
| 6297 | |
| 6298 | return address; |
| 6299 | } |
| 6300 | |
| 6301 | /* |
| 6302 | * ALTER TABLE ALTER COLUMN CHECK NOT NULL |
| 6303 | * |
| 6304 | * This doesn't exist in the grammar, but we generate AT_CheckNotNull |
| 6305 | * commands against the partitions of a partitioned table if the user |
| 6306 | * writes ALTER TABLE ONLY ... SET NOT NULL on the partitioned table, |
| 6307 | * or tries to create a primary key on it (which internally creates |
| 6308 | * AT_SetNotNull on the partitioned table). Such a command doesn't |
| 6309 | * allow us to actually modify any partition, but we want to let it |
| 6310 | * go through if the partitions are already properly marked. |
| 6311 | * |
| 6312 | * In future, this might need to adjust the child table's state, likely |
| 6313 | * by incrementing an inheritance count for the attnotnull constraint. |
| 6314 | * For now we need only check for the presence of the flag. |
| 6315 | */ |
| 6316 | static void |
| 6317 | ATExecCheckNotNull(AlteredTableInfo *tab, Relation rel, |
| 6318 | const char *colName, LOCKMODE lockmode) |
| 6319 | { |
| 6320 | HeapTuple tuple; |
| 6321 | |
| 6322 | tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName); |
| 6323 | |
| 6324 | if (!HeapTupleIsValid(tuple)) |
| 6325 | ereport(ERROR, |
| 6326 | (errcode(ERRCODE_UNDEFINED_COLUMN), |
| 6327 | errmsg("column \"%s\" of relation \"%s\" does not exist" , |
| 6328 | colName, RelationGetRelationName(rel)))); |
| 6329 | |
| 6330 | if (!((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull) |
| 6331 | ereport(ERROR, |
| 6332 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 6333 | errmsg("constraint must be added to child tables too" ), |
| 6334 | errdetail("Column \"%s\" of relation \"%s\" is not already NOT NULL." , |
| 6335 | colName, RelationGetRelationName(rel)), |
| 6336 | errhint("Do not specify the ONLY keyword." ))); |
| 6337 | |
| 6338 | ReleaseSysCache(tuple); |
| 6339 | } |
| 6340 | |
| 6341 | /* |
| 6342 | * NotNullImpliedByRelConstraints |
| 6343 | * Does rel's existing constraints imply NOT NULL for the given attribute? |
| 6344 | */ |
| 6345 | static bool |
| 6346 | NotNullImpliedByRelConstraints(Relation rel, Form_pg_attribute attr) |
| 6347 | { |
| 6348 | NullTest *nnulltest = makeNode(NullTest); |
| 6349 | |
| 6350 | nnulltest->arg = (Expr *) makeVar(1, |
| 6351 | attr->attnum, |
| 6352 | attr->atttypid, |
| 6353 | attr->atttypmod, |
| 6354 | attr->attcollation, |
| 6355 | 0); |
| 6356 | nnulltest->nulltesttype = IS_NOT_NULL; |
| 6357 | |
| 6358 | /* |
| 6359 | * argisrow = false is correct even for a composite column, because |
| 6360 | * attnotnull does not represent a SQL-spec IS NOT NULL test in such a |
| 6361 | * case, just IS DISTINCT FROM NULL. |
| 6362 | */ |
| 6363 | nnulltest->argisrow = false; |
| 6364 | nnulltest->location = -1; |
| 6365 | |
| 6366 | if (ConstraintImpliedByRelConstraint(rel, list_make1(nnulltest), NIL)) |
| 6367 | { |
| 6368 | ereport(DEBUG1, |
| 6369 | (errmsg("existing constraints on column \"%s\".\"%s\" are sufficient to prove that it does not contain nulls" , |
| 6370 | RelationGetRelationName(rel), NameStr(attr->attname)))); |
| 6371 | return true; |
| 6372 | } |
| 6373 | |
| 6374 | return false; |
| 6375 | } |
| 6376 | |
| 6377 | /* |
| 6378 | * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT |
| 6379 | * |
| 6380 | * Return the address of the affected column. |
| 6381 | */ |
| 6382 | static ObjectAddress |
| 6383 | ATExecColumnDefault(Relation rel, const char *colName, |
| 6384 | Node *newDefault, LOCKMODE lockmode) |
| 6385 | { |
| 6386 | TupleDesc tupdesc = RelationGetDescr(rel); |
| 6387 | AttrNumber attnum; |
| 6388 | ObjectAddress address; |
| 6389 | |
| 6390 | /* |
| 6391 | * get the number of the attribute |
| 6392 | */ |
| 6393 | attnum = get_attnum(RelationGetRelid(rel), colName); |
| 6394 | if (attnum == InvalidAttrNumber) |
| 6395 | ereport(ERROR, |
| 6396 | (errcode(ERRCODE_UNDEFINED_COLUMN), |
| 6397 | errmsg("column \"%s\" of relation \"%s\" does not exist" , |
| 6398 | colName, RelationGetRelationName(rel)))); |
| 6399 | |
| 6400 | /* Prevent them from altering a system attribute */ |
| 6401 | if (attnum <= 0) |
| 6402 | ereport(ERROR, |
| 6403 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 6404 | errmsg("cannot alter system column \"%s\"" , |
| 6405 | colName))); |
| 6406 | |
| 6407 | if (TupleDescAttr(tupdesc, attnum - 1)->attidentity) |
| 6408 | ereport(ERROR, |
| 6409 | (errcode(ERRCODE_SYNTAX_ERROR), |
| 6410 | errmsg("column \"%s\" of relation \"%s\" is an identity column" , |
| 6411 | colName, RelationGetRelationName(rel)), |
| 6412 | newDefault ? 0 : errhint("Use ALTER TABLE ... ALTER COLUMN ... DROP IDENTITY instead." ))); |
| 6413 | |
| 6414 | if (TupleDescAttr(tupdesc, attnum - 1)->attgenerated) |
| 6415 | ereport(ERROR, |
| 6416 | (errcode(ERRCODE_SYNTAX_ERROR), |
| 6417 | errmsg("column \"%s\" of relation \"%s\" is a generated column" , |
| 6418 | colName, RelationGetRelationName(rel)))); |
| 6419 | |
| 6420 | /* |
| 6421 | * Remove any old default for the column. We use RESTRICT here for |
| 6422 | * safety, but at present we do not expect anything to depend on the |
| 6423 | * default. |
| 6424 | * |
| 6425 | * We treat removing the existing default as an internal operation when it |
| 6426 | * is preparatory to adding a new default, but as a user-initiated |
| 6427 | * operation when the user asked for a drop. |
| 6428 | */ |
| 6429 | RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false, |
| 6430 | newDefault == NULL ? false : true); |
| 6431 | |
| 6432 | if (newDefault) |
| 6433 | { |
| 6434 | /* SET DEFAULT */ |
| 6435 | RawColumnDefault *rawEnt; |
| 6436 | |
| 6437 | rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault)); |
| 6438 | rawEnt->attnum = attnum; |
| 6439 | rawEnt->raw_default = newDefault; |
| 6440 | rawEnt->missingMode = false; |
| 6441 | rawEnt->generated = '\0'; |
| 6442 | |
| 6443 | /* |
| 6444 | * This function is intended for CREATE TABLE, so it processes a |
| 6445 | * _list_ of defaults, but we just do one. |
| 6446 | */ |
| 6447 | AddRelationNewConstraints(rel, list_make1(rawEnt), NIL, |
| 6448 | false, true, false, NULL); |
| 6449 | } |
| 6450 | |
| 6451 | ObjectAddressSubSet(address, RelationRelationId, |
| 6452 | RelationGetRelid(rel), attnum); |
| 6453 | return address; |
| 6454 | } |
| 6455 | |
| 6456 | /* |
| 6457 | * ALTER TABLE ALTER COLUMN ADD IDENTITY |
| 6458 | * |
| 6459 | * Return the address of the affected column. |
| 6460 | */ |
| 6461 | static ObjectAddress |
| 6462 | ATExecAddIdentity(Relation rel, const char *colName, |
| 6463 | Node *def, LOCKMODE lockmode) |
| 6464 | { |
| 6465 | Relation attrelation; |
| 6466 | HeapTuple tuple; |
| 6467 | Form_pg_attribute attTup; |
| 6468 | AttrNumber attnum; |
| 6469 | ObjectAddress address; |
| 6470 | ColumnDef *cdef = castNode(ColumnDef, def); |
| 6471 | |
| 6472 | attrelation = table_open(AttributeRelationId, RowExclusiveLock); |
| 6473 | |
| 6474 | tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); |
| 6475 | if (!HeapTupleIsValid(tuple)) |
| 6476 | ereport(ERROR, |
| 6477 | (errcode(ERRCODE_UNDEFINED_COLUMN), |
| 6478 | errmsg("column \"%s\" of relation \"%s\" does not exist" , |
| 6479 | colName, RelationGetRelationName(rel)))); |
| 6480 | attTup = (Form_pg_attribute) GETSTRUCT(tuple); |
| 6481 | attnum = attTup->attnum; |
| 6482 | |
| 6483 | /* Can't alter a system attribute */ |
| 6484 | if (attnum <= 0) |
| 6485 | ereport(ERROR, |
| 6486 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 6487 | errmsg("cannot alter system column \"%s\"" , |
| 6488 | colName))); |
| 6489 | |
| 6490 | /* |
| 6491 | * Creating a column as identity implies NOT NULL, so adding the identity |
| 6492 | * to an existing column that is not NOT NULL would create a state that |
| 6493 | * cannot be reproduced without contortions. |
| 6494 | */ |
| 6495 | if (!attTup->attnotnull) |
| 6496 | ereport(ERROR, |
| 6497 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
| 6498 | errmsg("column \"%s\" of relation \"%s\" must be declared NOT NULL before identity can be added" , |
| 6499 | colName, RelationGetRelationName(rel)))); |
| 6500 | |
| 6501 | if (attTup->attidentity) |
| 6502 | ereport(ERROR, |
| 6503 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
| 6504 | errmsg("column \"%s\" of relation \"%s\" is already an identity column" , |
| 6505 | colName, RelationGetRelationName(rel)))); |
| 6506 | |
| 6507 | if (attTup->atthasdef) |
| 6508 | ereport(ERROR, |
| 6509 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
| 6510 | errmsg("column \"%s\" of relation \"%s\" already has a default value" , |
| 6511 | colName, RelationGetRelationName(rel)))); |
| 6512 | |
| 6513 | attTup->attidentity = cdef->identity; |
| 6514 | CatalogTupleUpdate(attrelation, &tuple->t_self, tuple); |
| 6515 | |
| 6516 | InvokeObjectPostAlterHook(RelationRelationId, |
| 6517 | RelationGetRelid(rel), |
| 6518 | attTup->attnum); |
| 6519 | ObjectAddressSubSet(address, RelationRelationId, |
| 6520 | RelationGetRelid(rel), attnum); |
| 6521 | heap_freetuple(tuple); |
| 6522 | |
| 6523 | table_close(attrelation, RowExclusiveLock); |
| 6524 | |
| 6525 | return address; |
| 6526 | } |
| 6527 | |
| 6528 | /* |
| 6529 | * ALTER TABLE ALTER COLUMN SET { GENERATED or sequence options } |
| 6530 | * |
| 6531 | * Return the address of the affected column. |
| 6532 | */ |
| 6533 | static ObjectAddress |
| 6534 | ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmode) |
| 6535 | { |
| 6536 | ListCell *option; |
| 6537 | DefElem *generatedEl = NULL; |
| 6538 | HeapTuple tuple; |
| 6539 | Form_pg_attribute attTup; |
| 6540 | AttrNumber attnum; |
| 6541 | Relation attrelation; |
| 6542 | ObjectAddress address; |
| 6543 | |
| 6544 | foreach(option, castNode(List, def)) |
| 6545 | { |
| 6546 | DefElem *defel = lfirst_node(DefElem, option); |
| 6547 | |
| 6548 | if (strcmp(defel->defname, "generated" ) == 0) |
| 6549 | { |
| 6550 | if (generatedEl) |
| 6551 | ereport(ERROR, |
| 6552 | (errcode(ERRCODE_SYNTAX_ERROR), |
| 6553 | errmsg("conflicting or redundant options" ))); |
| 6554 | generatedEl = defel; |
| 6555 | } |
| 6556 | else |
| 6557 | elog(ERROR, "option \"%s\" not recognized" , |
| 6558 | defel->defname); |
| 6559 | } |
| 6560 | |
| 6561 | /* |
| 6562 | * Even if there is nothing to change here, we run all the checks. There |
| 6563 | * will be a subsequent ALTER SEQUENCE that relies on everything being |
| 6564 | * there. |
| 6565 | */ |
| 6566 | |
| 6567 | attrelation = table_open(AttributeRelationId, RowExclusiveLock); |
| 6568 | tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); |
| 6569 | if (!HeapTupleIsValid(tuple)) |
| 6570 | ereport(ERROR, |
| 6571 | (errcode(ERRCODE_UNDEFINED_COLUMN), |
| 6572 | errmsg("column \"%s\" of relation \"%s\" does not exist" , |
| 6573 | colName, RelationGetRelationName(rel)))); |
| 6574 | |
| 6575 | attTup = (Form_pg_attribute) GETSTRUCT(tuple); |
| 6576 | attnum = attTup->attnum; |
| 6577 | |
| 6578 | if (attnum <= 0) |
| 6579 | ereport(ERROR, |
| 6580 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 6581 | errmsg("cannot alter system column \"%s\"" , |
| 6582 | colName))); |
| 6583 | |
| 6584 | if (!attTup->attidentity) |
| 6585 | ereport(ERROR, |
| 6586 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
| 6587 | errmsg("column \"%s\" of relation \"%s\" is not an identity column" , |
| 6588 | colName, RelationGetRelationName(rel)))); |
| 6589 | |
| 6590 | if (generatedEl) |
| 6591 | { |
| 6592 | attTup->attidentity = defGetInt32(generatedEl); |
| 6593 | CatalogTupleUpdate(attrelation, &tuple->t_self, tuple); |
| 6594 | |
| 6595 | InvokeObjectPostAlterHook(RelationRelationId, |
| 6596 | RelationGetRelid(rel), |
| 6597 | attTup->attnum); |
| 6598 | ObjectAddressSubSet(address, RelationRelationId, |
| 6599 | RelationGetRelid(rel), attnum); |
| 6600 | } |
| 6601 | else |
| 6602 | address = InvalidObjectAddress; |
| 6603 | |
| 6604 | heap_freetuple(tuple); |
| 6605 | table_close(attrelation, RowExclusiveLock); |
| 6606 | |
| 6607 | return address; |
| 6608 | } |
| 6609 | |
| 6610 | /* |
| 6611 | * ALTER TABLE ALTER COLUMN DROP IDENTITY |
| 6612 | * |
| 6613 | * Return the address of the affected column. |
| 6614 | */ |
| 6615 | static ObjectAddress |
| 6616 | ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode) |
| 6617 | { |
| 6618 | HeapTuple tuple; |
| 6619 | Form_pg_attribute attTup; |
| 6620 | AttrNumber attnum; |
| 6621 | Relation attrelation; |
| 6622 | ObjectAddress address; |
| 6623 | Oid seqid; |
| 6624 | ObjectAddress seqaddress; |
| 6625 | |
| 6626 | attrelation = table_open(AttributeRelationId, RowExclusiveLock); |
| 6627 | tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); |
| 6628 | if (!HeapTupleIsValid(tuple)) |
| 6629 | ereport(ERROR, |
| 6630 | (errcode(ERRCODE_UNDEFINED_COLUMN), |
| 6631 | errmsg("column \"%s\" of relation \"%s\" does not exist" , |
| 6632 | colName, RelationGetRelationName(rel)))); |
| 6633 | |
| 6634 | attTup = (Form_pg_attribute) GETSTRUCT(tuple); |
| 6635 | attnum = attTup->attnum; |
| 6636 | |
| 6637 | if (attnum <= 0) |
| 6638 | ereport(ERROR, |
| 6639 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 6640 | errmsg("cannot alter system column \"%s\"" , |
| 6641 | colName))); |
| 6642 | |
| 6643 | if (!attTup->attidentity) |
| 6644 | { |
| 6645 | if (!missing_ok) |
| 6646 | ereport(ERROR, |
| 6647 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
| 6648 | errmsg("column \"%s\" of relation \"%s\" is not an identity column" , |
| 6649 | colName, RelationGetRelationName(rel)))); |
| 6650 | else |
| 6651 | { |
| 6652 | ereport(NOTICE, |
| 6653 | (errmsg("column \"%s\" of relation \"%s\" is not an identity column, skipping" , |
| 6654 | colName, RelationGetRelationName(rel)))); |
| 6655 | heap_freetuple(tuple); |
| 6656 | table_close(attrelation, RowExclusiveLock); |
| 6657 | return InvalidObjectAddress; |
| 6658 | } |
| 6659 | } |
| 6660 | |
| 6661 | attTup->attidentity = '\0'; |
| 6662 | CatalogTupleUpdate(attrelation, &tuple->t_self, tuple); |
| 6663 | |
| 6664 | InvokeObjectPostAlterHook(RelationRelationId, |
| 6665 | RelationGetRelid(rel), |
| 6666 | attTup->attnum); |
| 6667 | ObjectAddressSubSet(address, RelationRelationId, |
| 6668 | RelationGetRelid(rel), attnum); |
| 6669 | heap_freetuple(tuple); |
| 6670 | |
| 6671 | table_close(attrelation, RowExclusiveLock); |
| 6672 | |
| 6673 | /* drop the internal sequence */ |
| 6674 | seqid = getOwnedSequence(RelationGetRelid(rel), attnum); |
| 6675 | deleteDependencyRecordsForClass(RelationRelationId, seqid, |
| 6676 | RelationRelationId, DEPENDENCY_INTERNAL); |
| 6677 | CommandCounterIncrement(); |
| 6678 | seqaddress.classId = RelationRelationId; |
| 6679 | seqaddress.objectId = seqid; |
| 6680 | seqaddress.objectSubId = 0; |
| 6681 | performDeletion(&seqaddress, DROP_RESTRICT, PERFORM_DELETION_INTERNAL); |
| 6682 | |
| 6683 | return address; |
| 6684 | } |
| 6685 | |
| 6686 | /* |
| 6687 | * ALTER TABLE ALTER COLUMN SET STATISTICS |
| 6688 | */ |
| 6689 | static void |
| 6690 | ATPrepSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newValue, LOCKMODE lockmode) |
| 6691 | { |
| 6692 | /* |
| 6693 | * We do our own permission checking because (a) we want to allow SET |
| 6694 | * STATISTICS on indexes (for expressional index columns), and (b) we want |
| 6695 | * to allow SET STATISTICS on system catalogs without requiring |
| 6696 | * allowSystemTableMods to be turned on. |
| 6697 | */ |
| 6698 | if (rel->rd_rel->relkind != RELKIND_RELATION && |
| 6699 | rel->rd_rel->relkind != RELKIND_MATVIEW && |
| 6700 | rel->rd_rel->relkind != RELKIND_INDEX && |
| 6701 | rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX && |
| 6702 | rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE && |
| 6703 | rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) |
| 6704 | ereport(ERROR, |
| 6705 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 6706 | errmsg("\"%s\" is not a table, materialized view, index, or foreign table" , |
| 6707 | RelationGetRelationName(rel)))); |
| 6708 | |
| 6709 | /* |
| 6710 | * We allow referencing columns by numbers only for indexes, since table |
| 6711 | * column numbers could contain gaps if columns are later dropped. |
| 6712 | */ |
| 6713 | if (rel->rd_rel->relkind != RELKIND_INDEX && |
| 6714 | rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX && |
| 6715 | !colName) |
| 6716 | ereport(ERROR, |
| 6717 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 6718 | errmsg("cannot refer to non-index column by number" ))); |
| 6719 | |
| 6720 | /* Permissions checks */ |
| 6721 | if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId())) |
| 6722 | aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind), |
| 6723 | RelationGetRelationName(rel)); |
| 6724 | } |
| 6725 | |
| 6726 | /* |
| 6727 | * Return value is the address of the modified column |
| 6728 | */ |
| 6729 | static ObjectAddress |
| 6730 | ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newValue, LOCKMODE lockmode) |
| 6731 | { |
| 6732 | int newtarget; |
| 6733 | Relation attrelation; |
| 6734 | HeapTuple tuple; |
| 6735 | Form_pg_attribute attrtuple; |
| 6736 | AttrNumber attnum; |
| 6737 | ObjectAddress address; |
| 6738 | |
| 6739 | Assert(IsA(newValue, Integer)); |
| 6740 | newtarget = intVal(newValue); |
| 6741 | |
| 6742 | /* |
| 6743 | * Limit target to a sane range |
| 6744 | */ |
| 6745 | if (newtarget < -1) |
| 6746 | { |
| 6747 | ereport(ERROR, |
| 6748 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 6749 | errmsg("statistics target %d is too low" , |
| 6750 | newtarget))); |
| 6751 | } |
| 6752 | else if (newtarget > 10000) |
| 6753 | { |
| 6754 | newtarget = 10000; |
| 6755 | ereport(WARNING, |
| 6756 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 6757 | errmsg("lowering statistics target to %d" , |
| 6758 | newtarget))); |
| 6759 | } |
| 6760 | |
| 6761 | attrelation = table_open(AttributeRelationId, RowExclusiveLock); |
| 6762 | |
| 6763 | if (colName) |
| 6764 | { |
| 6765 | tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); |
| 6766 | |
| 6767 | if (!HeapTupleIsValid(tuple)) |
| 6768 | ereport(ERROR, |
| 6769 | (errcode(ERRCODE_UNDEFINED_COLUMN), |
| 6770 | errmsg("column \"%s\" of relation \"%s\" does not exist" , |
| 6771 | colName, RelationGetRelationName(rel)))); |
| 6772 | } |
| 6773 | else |
| 6774 | { |
| 6775 | tuple = SearchSysCacheCopyAttNum(RelationGetRelid(rel), colNum); |
| 6776 | |
| 6777 | if (!HeapTupleIsValid(tuple)) |
| 6778 | ereport(ERROR, |
| 6779 | (errcode(ERRCODE_UNDEFINED_COLUMN), |
| 6780 | errmsg("column number %d of relation \"%s\" does not exist" , |
| 6781 | colNum, RelationGetRelationName(rel)))); |
| 6782 | } |
| 6783 | |
| 6784 | attrtuple = (Form_pg_attribute) GETSTRUCT(tuple); |
| 6785 | |
| 6786 | attnum = attrtuple->attnum; |
| 6787 | if (attnum <= 0) |
| 6788 | ereport(ERROR, |
| 6789 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 6790 | errmsg("cannot alter system column \"%s\"" , |
| 6791 | colName))); |
| 6792 | |
| 6793 | if (rel->rd_rel->relkind == RELKIND_INDEX || |
| 6794 | rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) |
| 6795 | { |
| 6796 | if (attnum > rel->rd_index->indnkeyatts) |
| 6797 | ereport(ERROR, |
| 6798 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 6799 | errmsg("cannot alter statistics on included column \"%s\" of index \"%s\"" , |
| 6800 | NameStr(attrtuple->attname), RelationGetRelationName(rel)))); |
| 6801 | else if (rel->rd_index->indkey.values[attnum - 1] != 0) |
| 6802 | ereport(ERROR, |
| 6803 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 6804 | errmsg("cannot alter statistics on non-expression column \"%s\" of index \"%s\"" , |
| 6805 | NameStr(attrtuple->attname), RelationGetRelationName(rel)), |
| 6806 | errhint("Alter statistics on table column instead." ))); |
| 6807 | } |
| 6808 | |
| 6809 | attrtuple->attstattarget = newtarget; |
| 6810 | |
| 6811 | CatalogTupleUpdate(attrelation, &tuple->t_self, tuple); |
| 6812 | |
| 6813 | InvokeObjectPostAlterHook(RelationRelationId, |
| 6814 | RelationGetRelid(rel), |
| 6815 | attrtuple->attnum); |
| 6816 | ObjectAddressSubSet(address, RelationRelationId, |
| 6817 | RelationGetRelid(rel), attnum); |
| 6818 | heap_freetuple(tuple); |
| 6819 | |
| 6820 | table_close(attrelation, RowExclusiveLock); |
| 6821 | |
| 6822 | return address; |
| 6823 | } |
| 6824 | |
| 6825 | /* |
| 6826 | * Return value is the address of the modified column |
| 6827 | */ |
| 6828 | static ObjectAddress |
| 6829 | ATExecSetOptions(Relation rel, const char *colName, Node *options, |
| 6830 | bool isReset, LOCKMODE lockmode) |
| 6831 | { |
| 6832 | Relation attrelation; |
| 6833 | HeapTuple tuple, |
| 6834 | newtuple; |
| 6835 | Form_pg_attribute attrtuple; |
| 6836 | AttrNumber attnum; |
| 6837 | Datum datum, |
| 6838 | newOptions; |
| 6839 | bool isnull; |
| 6840 | ObjectAddress address; |
| 6841 | Datum repl_val[Natts_pg_attribute]; |
| 6842 | bool repl_null[Natts_pg_attribute]; |
| 6843 | bool repl_repl[Natts_pg_attribute]; |
| 6844 | |
| 6845 | attrelation = table_open(AttributeRelationId, RowExclusiveLock); |
| 6846 | |
| 6847 | tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName); |
| 6848 | |
| 6849 | if (!HeapTupleIsValid(tuple)) |
| 6850 | ereport(ERROR, |
| 6851 | (errcode(ERRCODE_UNDEFINED_COLUMN), |
| 6852 | errmsg("column \"%s\" of relation \"%s\" does not exist" , |
| 6853 | colName, RelationGetRelationName(rel)))); |
| 6854 | attrtuple = (Form_pg_attribute) GETSTRUCT(tuple); |
| 6855 | |
| 6856 | attnum = attrtuple->attnum; |
| 6857 | if (attnum <= 0) |
| 6858 | ereport(ERROR, |
| 6859 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 6860 | errmsg("cannot alter system column \"%s\"" , |
| 6861 | colName))); |
| 6862 | |
| 6863 | /* Generate new proposed attoptions (text array) */ |
| 6864 | datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions, |
| 6865 | &isnull); |
| 6866 | newOptions = transformRelOptions(isnull ? (Datum) 0 : datum, |
| 6867 | castNode(List, options), NULL, NULL, |
| 6868 | false, isReset); |
| 6869 | /* Validate new options */ |
| 6870 | (void) attribute_reloptions(newOptions, true); |
| 6871 | |
| 6872 | /* Build new tuple. */ |
| 6873 | memset(repl_null, false, sizeof(repl_null)); |
| 6874 | memset(repl_repl, false, sizeof(repl_repl)); |
| 6875 | if (newOptions != (Datum) 0) |
| 6876 | repl_val[Anum_pg_attribute_attoptions - 1] = newOptions; |
| 6877 | else |
| 6878 | repl_null[Anum_pg_attribute_attoptions - 1] = true; |
| 6879 | repl_repl[Anum_pg_attribute_attoptions - 1] = true; |
| 6880 | newtuple = heap_modify_tuple(tuple, RelationGetDescr(attrelation), |
| 6881 | repl_val, repl_null, repl_repl); |
| 6882 | |
| 6883 | /* Update system catalog. */ |
| 6884 | CatalogTupleUpdate(attrelation, &newtuple->t_self, newtuple); |
| 6885 | |
| 6886 | InvokeObjectPostAlterHook(RelationRelationId, |
| 6887 | RelationGetRelid(rel), |
| 6888 | attrtuple->attnum); |
| 6889 | ObjectAddressSubSet(address, RelationRelationId, |
| 6890 | RelationGetRelid(rel), attnum); |
| 6891 | |
| 6892 | heap_freetuple(newtuple); |
| 6893 | |
| 6894 | ReleaseSysCache(tuple); |
| 6895 | |
| 6896 | table_close(attrelation, RowExclusiveLock); |
| 6897 | |
| 6898 | return address; |
| 6899 | } |
| 6900 | |
| 6901 | /* |
| 6902 | * ALTER TABLE ALTER COLUMN SET STORAGE |
| 6903 | * |
| 6904 | * Return value is the address of the modified column |
| 6905 | */ |
| 6906 | static ObjectAddress |
| 6907 | ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode) |
| 6908 | { |
| 6909 | char *storagemode; |
| 6910 | char newstorage; |
| 6911 | Relation attrelation; |
| 6912 | HeapTuple tuple; |
| 6913 | Form_pg_attribute attrtuple; |
| 6914 | AttrNumber attnum; |
| 6915 | ObjectAddress address; |
| 6916 | |
| 6917 | Assert(IsA(newValue, String)); |
| 6918 | storagemode = strVal(newValue); |
| 6919 | |
| 6920 | if (pg_strcasecmp(storagemode, "plain" ) == 0) |
| 6921 | newstorage = 'p'; |
| 6922 | else if (pg_strcasecmp(storagemode, "external" ) == 0) |
| 6923 | newstorage = 'e'; |
| 6924 | else if (pg_strcasecmp(storagemode, "extended" ) == 0) |
| 6925 | newstorage = 'x'; |
| 6926 | else if (pg_strcasecmp(storagemode, "main" ) == 0) |
| 6927 | newstorage = 'm'; |
| 6928 | else |
| 6929 | { |
| 6930 | ereport(ERROR, |
| 6931 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 6932 | errmsg("invalid storage type \"%s\"" , |
| 6933 | storagemode))); |
| 6934 | newstorage = 0; /* keep compiler quiet */ |
| 6935 | } |
| 6936 | |
| 6937 | attrelation = table_open(AttributeRelationId, RowExclusiveLock); |
| 6938 | |
| 6939 | tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); |
| 6940 | |
| 6941 | if (!HeapTupleIsValid(tuple)) |
| 6942 | ereport(ERROR, |
| 6943 | (errcode(ERRCODE_UNDEFINED_COLUMN), |
| 6944 | errmsg("column \"%s\" of relation \"%s\" does not exist" , |
| 6945 | colName, RelationGetRelationName(rel)))); |
| 6946 | attrtuple = (Form_pg_attribute) GETSTRUCT(tuple); |
| 6947 | |
| 6948 | attnum = attrtuple->attnum; |
| 6949 | if (attnum <= 0) |
| 6950 | ereport(ERROR, |
| 6951 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 6952 | errmsg("cannot alter system column \"%s\"" , |
| 6953 | colName))); |
| 6954 | |
| 6955 | /* |
| 6956 | * safety check: do not allow toasted storage modes unless column datatype |
| 6957 | * is TOAST-aware. |
| 6958 | */ |
| 6959 | if (newstorage == 'p' || TypeIsToastable(attrtuple->atttypid)) |
| 6960 | attrtuple->attstorage = newstorage; |
| 6961 | else |
| 6962 | ereport(ERROR, |
| 6963 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 6964 | errmsg("column data type %s can only have storage PLAIN" , |
| 6965 | format_type_be(attrtuple->atttypid)))); |
| 6966 | |
| 6967 | CatalogTupleUpdate(attrelation, &tuple->t_self, tuple); |
| 6968 | |
| 6969 | InvokeObjectPostAlterHook(RelationRelationId, |
| 6970 | RelationGetRelid(rel), |
| 6971 | attrtuple->attnum); |
| 6972 | |
| 6973 | heap_freetuple(tuple); |
| 6974 | |
| 6975 | table_close(attrelation, RowExclusiveLock); |
| 6976 | |
| 6977 | ObjectAddressSubSet(address, RelationRelationId, |
| 6978 | RelationGetRelid(rel), attnum); |
| 6979 | return address; |
| 6980 | } |
| 6981 | |
| 6982 | |
| 6983 | /* |
| 6984 | * ALTER TABLE DROP COLUMN |
| 6985 | * |
| 6986 | * DROP COLUMN cannot use the normal ALTER TABLE recursion mechanism, |
| 6987 | * because we have to decide at runtime whether to recurse or not depending |
| 6988 | * on whether attinhcount goes to zero or not. (We can't check this in a |
| 6989 | * static pre-pass because it won't handle multiple inheritance situations |
| 6990 | * correctly.) |
| 6991 | */ |
| 6992 | static void |
| 6993 | ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing, |
| 6994 | AlterTableCmd *cmd, LOCKMODE lockmode) |
| 6995 | { |
| 6996 | if (rel->rd_rel->reloftype && !recursing) |
| 6997 | ereport(ERROR, |
| 6998 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 6999 | errmsg("cannot drop column from typed table" ))); |
| 7000 | |
| 7001 | if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) |
| 7002 | ATTypedTableRecursion(wqueue, rel, cmd, lockmode); |
| 7003 | |
| 7004 | if (recurse) |
| 7005 | cmd->subtype = AT_DropColumnRecurse; |
| 7006 | } |
| 7007 | |
| 7008 | /* |
| 7009 | * Return value is the address of the dropped column. |
| 7010 | */ |
| 7011 | static ObjectAddress |
| 7012 | ATExecDropColumn(List **wqueue, Relation rel, const char *colName, |
| 7013 | DropBehavior behavior, |
| 7014 | bool recurse, bool recursing, |
| 7015 | bool missing_ok, LOCKMODE lockmode) |
| 7016 | { |
| 7017 | HeapTuple tuple; |
| 7018 | Form_pg_attribute targetatt; |
| 7019 | AttrNumber attnum; |
| 7020 | List *children; |
| 7021 | ObjectAddress object; |
| 7022 | bool is_expr; |
| 7023 | |
| 7024 | /* At top level, permission check was done in ATPrepCmd, else do it */ |
| 7025 | if (recursing) |
| 7026 | ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); |
| 7027 | |
| 7028 | /* |
| 7029 | * get the number of the attribute |
| 7030 | */ |
| 7031 | tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName); |
| 7032 | if (!HeapTupleIsValid(tuple)) |
| 7033 | { |
| 7034 | if (!missing_ok) |
| 7035 | { |
| 7036 | ereport(ERROR, |
| 7037 | (errcode(ERRCODE_UNDEFINED_COLUMN), |
| 7038 | errmsg("column \"%s\" of relation \"%s\" does not exist" , |
| 7039 | colName, RelationGetRelationName(rel)))); |
| 7040 | } |
| 7041 | else |
| 7042 | { |
| 7043 | ereport(NOTICE, |
| 7044 | (errmsg("column \"%s\" of relation \"%s\" does not exist, skipping" , |
| 7045 | colName, RelationGetRelationName(rel)))); |
| 7046 | return InvalidObjectAddress; |
| 7047 | } |
| 7048 | } |
| 7049 | targetatt = (Form_pg_attribute) GETSTRUCT(tuple); |
| 7050 | |
| 7051 | attnum = targetatt->attnum; |
| 7052 | |
| 7053 | /* Can't drop a system attribute */ |
| 7054 | if (attnum <= 0) |
| 7055 | ereport(ERROR, |
| 7056 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 7057 | errmsg("cannot drop system column \"%s\"" , |
| 7058 | colName))); |
| 7059 | |
| 7060 | /* |
| 7061 | * Don't drop inherited columns, unless recursing (presumably from a drop |
| 7062 | * of the parent column) |
| 7063 | */ |
| 7064 | if (targetatt->attinhcount > 0 && !recursing) |
| 7065 | ereport(ERROR, |
| 7066 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 7067 | errmsg("cannot drop inherited column \"%s\"" , |
| 7068 | colName))); |
| 7069 | |
| 7070 | /* |
| 7071 | * Don't drop columns used in the partition key, either. (If we let this |
| 7072 | * go through, the key column's dependencies would cause a cascaded drop |
| 7073 | * of the whole table, which is surely not what the user expected.) |
| 7074 | */ |
| 7075 | if (has_partition_attrs(rel, |
| 7076 | bms_make_singleton(attnum - FirstLowInvalidHeapAttributeNumber), |
| 7077 | &is_expr)) |
| 7078 | ereport(ERROR, |
| 7079 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 7080 | errmsg("cannot drop column \"%s\" because it is part of the partition key of relation \"%s\"" , |
| 7081 | colName, RelationGetRelationName(rel)))); |
| 7082 | |
| 7083 | ReleaseSysCache(tuple); |
| 7084 | |
| 7085 | /* |
| 7086 | * Propagate to children as appropriate. Unlike most other ALTER |
| 7087 | * routines, we have to do this one level of recursion at a time; we can't |
| 7088 | * use find_all_inheritors to do it in one pass. |
| 7089 | */ |
| 7090 | children = find_inheritance_children(RelationGetRelid(rel), lockmode); |
| 7091 | |
| 7092 | if (children) |
| 7093 | { |
| 7094 | Relation attr_rel; |
| 7095 | ListCell *child; |
| 7096 | |
| 7097 | /* |
| 7098 | * In case of a partitioned table, the column must be dropped from the |
| 7099 | * partitions as well. |
| 7100 | */ |
| 7101 | if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse) |
| 7102 | ereport(ERROR, |
| 7103 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 7104 | errmsg("cannot drop column from only the partitioned table when partitions exist" ), |
| 7105 | errhint("Do not specify the ONLY keyword." ))); |
| 7106 | |
| 7107 | attr_rel = table_open(AttributeRelationId, RowExclusiveLock); |
| 7108 | foreach(child, children) |
| 7109 | { |
| 7110 | Oid childrelid = lfirst_oid(child); |
| 7111 | Relation childrel; |
| 7112 | Form_pg_attribute childatt; |
| 7113 | |
| 7114 | /* find_inheritance_children already got lock */ |
| 7115 | childrel = table_open(childrelid, NoLock); |
| 7116 | CheckTableNotInUse(childrel, "ALTER TABLE" ); |
| 7117 | |
| 7118 | tuple = SearchSysCacheCopyAttName(childrelid, colName); |
| 7119 | if (!HeapTupleIsValid(tuple)) /* shouldn't happen */ |
| 7120 | elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u" , |
| 7121 | colName, childrelid); |
| 7122 | childatt = (Form_pg_attribute) GETSTRUCT(tuple); |
| 7123 | |
| 7124 | if (childatt->attinhcount <= 0) /* shouldn't happen */ |
| 7125 | elog(ERROR, "relation %u has non-inherited attribute \"%s\"" , |
| 7126 | childrelid, colName); |
| 7127 | |
| 7128 | if (recurse) |
| 7129 | { |
| 7130 | /* |
| 7131 | * If the child column has other definition sources, just |
| 7132 | * decrement its inheritance count; if not, recurse to delete |
| 7133 | * it. |
| 7134 | */ |
| 7135 | if (childatt->attinhcount == 1 && !childatt->attislocal) |
| 7136 | { |
| 7137 | /* Time to delete this child column, too */ |
| 7138 | ATExecDropColumn(wqueue, childrel, colName, |
| 7139 | behavior, true, true, |
| 7140 | false, lockmode); |
| 7141 | } |
| 7142 | else |
| 7143 | { |
| 7144 | /* Child column must survive my deletion */ |
| 7145 | childatt->attinhcount--; |
| 7146 | |
| 7147 | CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple); |
| 7148 | |
| 7149 | /* Make update visible */ |
| 7150 | CommandCounterIncrement(); |
| 7151 | } |
| 7152 | } |
| 7153 | else |
| 7154 | { |
| 7155 | /* |
| 7156 | * If we were told to drop ONLY in this table (no recursion), |
| 7157 | * we need to mark the inheritors' attributes as locally |
| 7158 | * defined rather than inherited. |
| 7159 | */ |
| 7160 | childatt->attinhcount--; |
| 7161 | childatt->attislocal = true; |
| 7162 | |
| 7163 | CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple); |
| 7164 | |
| 7165 | /* Make update visible */ |
| 7166 | CommandCounterIncrement(); |
| 7167 | } |
| 7168 | |
| 7169 | heap_freetuple(tuple); |
| 7170 | |
| 7171 | table_close(childrel, NoLock); |
| 7172 | } |
| 7173 | table_close(attr_rel, RowExclusiveLock); |
| 7174 | } |
| 7175 | |
| 7176 | /* |
| 7177 | * Perform the actual column deletion |
| 7178 | */ |
| 7179 | object.classId = RelationRelationId; |
| 7180 | object.objectId = RelationGetRelid(rel); |
| 7181 | object.objectSubId = attnum; |
| 7182 | |
| 7183 | performDeletion(&object, behavior, 0); |
| 7184 | |
| 7185 | return object; |
| 7186 | } |
| 7187 | |
| 7188 | /* |
| 7189 | * ALTER TABLE ADD INDEX |
| 7190 | * |
| 7191 | * There is no such command in the grammar, but parse_utilcmd.c converts |
| 7192 | * UNIQUE and PRIMARY KEY constraints into AT_AddIndex subcommands. This lets |
| 7193 | * us schedule creation of the index at the appropriate time during ALTER. |
| 7194 | * |
| 7195 | * Return value is the address of the new index. |
| 7196 | */ |
| 7197 | static ObjectAddress |
| 7198 | ATExecAddIndex(AlteredTableInfo *tab, Relation rel, |
| 7199 | IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode) |
| 7200 | { |
| 7201 | bool check_rights; |
| 7202 | bool skip_build; |
| 7203 | bool quiet; |
| 7204 | ObjectAddress address; |
| 7205 | |
| 7206 | Assert(IsA(stmt, IndexStmt)); |
| 7207 | Assert(!stmt->concurrent); |
| 7208 | |
| 7209 | /* The IndexStmt has already been through transformIndexStmt */ |
| 7210 | Assert(stmt->transformed); |
| 7211 | |
| 7212 | /* suppress schema rights check when rebuilding existing index */ |
| 7213 | check_rights = !is_rebuild; |
| 7214 | /* skip index build if phase 3 will do it or we're reusing an old one */ |
| 7215 | skip_build = tab->rewrite > 0 || OidIsValid(stmt->oldNode); |
| 7216 | /* suppress notices when rebuilding existing index */ |
| 7217 | quiet = is_rebuild; |
| 7218 | |
| 7219 | address = DefineIndex(RelationGetRelid(rel), |
| 7220 | stmt, |
| 7221 | InvalidOid, /* no predefined OID */ |
| 7222 | InvalidOid, /* no parent index */ |
| 7223 | InvalidOid, /* no parent constraint */ |
| 7224 | true, /* is_alter_table */ |
| 7225 | check_rights, |
| 7226 | false, /* check_not_in_use - we did it already */ |
| 7227 | skip_build, |
| 7228 | quiet); |
| 7229 | |
| 7230 | /* |
| 7231 | * If TryReuseIndex() stashed a relfilenode for us, we used it for the new |
| 7232 | * index instead of building from scratch. The DROP of the old edition of |
| 7233 | * this index will have scheduled the storage for deletion at commit, so |
| 7234 | * cancel that pending deletion. |
| 7235 | */ |
| 7236 | if (OidIsValid(stmt->oldNode)) |
| 7237 | { |
| 7238 | Relation irel = index_open(address.objectId, NoLock); |
| 7239 | |
| 7240 | RelationPreserveStorage(irel->rd_node, true); |
| 7241 | index_close(irel, NoLock); |
| 7242 | } |
| 7243 | |
| 7244 | return address; |
| 7245 | } |
| 7246 | |
| 7247 | /* |
| 7248 | * ALTER TABLE ADD CONSTRAINT USING INDEX |
| 7249 | * |
| 7250 | * Returns the address of the new constraint. |
| 7251 | */ |
| 7252 | static ObjectAddress |
| 7253 | ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel, |
| 7254 | IndexStmt *stmt, LOCKMODE lockmode) |
| 7255 | { |
| 7256 | Oid index_oid = stmt->indexOid; |
| 7257 | Relation indexRel; |
| 7258 | char *indexName; |
| 7259 | IndexInfo *indexInfo; |
| 7260 | char *constraintName; |
| 7261 | char constraintType; |
| 7262 | ObjectAddress address; |
| 7263 | bits16 flags; |
| 7264 | |
| 7265 | Assert(IsA(stmt, IndexStmt)); |
| 7266 | Assert(OidIsValid(index_oid)); |
| 7267 | Assert(stmt->isconstraint); |
| 7268 | |
| 7269 | /* |
| 7270 | * Doing this on partitioned tables is not a simple feature to implement, |
| 7271 | * so let's punt for now. |
| 7272 | */ |
| 7273 | if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) |
| 7274 | ereport(ERROR, |
| 7275 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 7276 | errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX is not supported on partitioned tables" ))); |
| 7277 | |
| 7278 | indexRel = index_open(index_oid, AccessShareLock); |
| 7279 | |
| 7280 | indexName = pstrdup(RelationGetRelationName(indexRel)); |
| 7281 | |
| 7282 | indexInfo = BuildIndexInfo(indexRel); |
| 7283 | |
| 7284 | /* this should have been checked at parse time */ |
| 7285 | if (!indexInfo->ii_Unique) |
| 7286 | elog(ERROR, "index \"%s\" is not unique" , indexName); |
| 7287 | |
| 7288 | /* |
| 7289 | * Determine name to assign to constraint. We require a constraint to |
| 7290 | * have the same name as the underlying index; therefore, use the index's |
| 7291 | * existing name as the default constraint name, and if the user |
| 7292 | * explicitly gives some other name for the constraint, rename the index |
| 7293 | * to match. |
| 7294 | */ |
| 7295 | constraintName = stmt->idxname; |
| 7296 | if (constraintName == NULL) |
| 7297 | constraintName = indexName; |
| 7298 | else if (strcmp(constraintName, indexName) != 0) |
| 7299 | { |
| 7300 | ereport(NOTICE, |
| 7301 | (errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index \"%s\" to \"%s\"" , |
| 7302 | indexName, constraintName))); |
| 7303 | RenameRelationInternal(index_oid, constraintName, false, true); |
| 7304 | } |
| 7305 | |
| 7306 | /* Extra checks needed if making primary key */ |
| 7307 | if (stmt->primary) |
| 7308 | index_check_primary_key(rel, indexInfo, true, stmt); |
| 7309 | |
| 7310 | /* Note we currently don't support EXCLUSION constraints here */ |
| 7311 | if (stmt->primary) |
| 7312 | constraintType = CONSTRAINT_PRIMARY; |
| 7313 | else |
| 7314 | constraintType = CONSTRAINT_UNIQUE; |
| 7315 | |
| 7316 | /* Create the catalog entries for the constraint */ |
| 7317 | flags = INDEX_CONSTR_CREATE_UPDATE_INDEX | |
| 7318 | INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS | |
| 7319 | (stmt->initdeferred ? INDEX_CONSTR_CREATE_INIT_DEFERRED : 0) | |
| 7320 | (stmt->deferrable ? INDEX_CONSTR_CREATE_DEFERRABLE : 0) | |
| 7321 | (stmt->primary ? INDEX_CONSTR_CREATE_MARK_AS_PRIMARY : 0); |
| 7322 | |
| 7323 | address = index_constraint_create(rel, |
| 7324 | index_oid, |
| 7325 | InvalidOid, |
| 7326 | indexInfo, |
| 7327 | constraintName, |
| 7328 | constraintType, |
| 7329 | flags, |
| 7330 | allowSystemTableMods, |
| 7331 | false); /* is_internal */ |
| 7332 | |
| 7333 | index_close(indexRel, NoLock); |
| 7334 | |
| 7335 | return address; |
| 7336 | } |
| 7337 | |
| 7338 | /* |
| 7339 | * ALTER TABLE ADD CONSTRAINT |
| 7340 | * |
| 7341 | * Return value is the address of the new constraint; if no constraint was |
| 7342 | * added, InvalidObjectAddress is returned. |
| 7343 | */ |
| 7344 | static ObjectAddress |
| 7345 | ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, |
| 7346 | Constraint *newConstraint, bool recurse, bool is_readd, |
| 7347 | LOCKMODE lockmode) |
| 7348 | { |
| 7349 | ObjectAddress address = InvalidObjectAddress; |
| 7350 | |
| 7351 | Assert(IsA(newConstraint, Constraint)); |
| 7352 | |
| 7353 | /* |
| 7354 | * Currently, we only expect to see CONSTR_CHECK and CONSTR_FOREIGN nodes |
| 7355 | * arriving here (see the preprocessing done in parse_utilcmd.c). Use a |
| 7356 | * switch anyway to make it easier to add more code later. |
| 7357 | */ |
| 7358 | switch (newConstraint->contype) |
| 7359 | { |
| 7360 | case CONSTR_CHECK: |
| 7361 | address = |
| 7362 | ATAddCheckConstraint(wqueue, tab, rel, |
| 7363 | newConstraint, recurse, false, is_readd, |
| 7364 | lockmode); |
| 7365 | break; |
| 7366 | |
| 7367 | case CONSTR_FOREIGN: |
| 7368 | |
| 7369 | /* |
| 7370 | * Assign or validate constraint name |
| 7371 | */ |
| 7372 | if (newConstraint->conname) |
| 7373 | { |
| 7374 | if (ConstraintNameIsUsed(CONSTRAINT_RELATION, |
| 7375 | RelationGetRelid(rel), |
| 7376 | newConstraint->conname)) |
| 7377 | ereport(ERROR, |
| 7378 | (errcode(ERRCODE_DUPLICATE_OBJECT), |
| 7379 | errmsg("constraint \"%s\" for relation \"%s\" already exists" , |
| 7380 | newConstraint->conname, |
| 7381 | RelationGetRelationName(rel)))); |
| 7382 | } |
| 7383 | else |
| 7384 | newConstraint->conname = |
| 7385 | ChooseConstraintName(RelationGetRelationName(rel), |
| 7386 | ChooseForeignKeyConstraintNameAddition(newConstraint->fk_attrs), |
| 7387 | "fkey" , |
| 7388 | RelationGetNamespace(rel), |
| 7389 | NIL); |
| 7390 | |
| 7391 | address = ATAddForeignKeyConstraint(wqueue, tab, rel, |
| 7392 | newConstraint, InvalidOid, |
| 7393 | recurse, false, |
| 7394 | lockmode); |
| 7395 | break; |
| 7396 | |
| 7397 | default: |
| 7398 | elog(ERROR, "unrecognized constraint type: %d" , |
| 7399 | (int) newConstraint->contype); |
| 7400 | } |
| 7401 | |
| 7402 | return address; |
| 7403 | } |
| 7404 | |
| 7405 | /* |
| 7406 | * Generate the column-name portion of the constraint name for a new foreign |
| 7407 | * key given the list of column names that reference the referenced |
| 7408 | * table. This will be passed to ChooseConstraintName along with the parent |
| 7409 | * table name and the "fkey" suffix. |
| 7410 | * |
| 7411 | * We know that less than NAMEDATALEN characters will actually be used, so we |
| 7412 | * can truncate the result once we've generated that many. |
| 7413 | * |
| 7414 | * XXX see also ChooseExtendedStatisticNameAddition and |
| 7415 | * ChooseIndexNameAddition. |
| 7416 | */ |
| 7417 | static char * |
| 7418 | ChooseForeignKeyConstraintNameAddition(List *colnames) |
| 7419 | { |
| 7420 | char buf[NAMEDATALEN * 2]; |
| 7421 | int buflen = 0; |
| 7422 | ListCell *lc; |
| 7423 | |
| 7424 | buf[0] = '\0'; |
| 7425 | foreach(lc, colnames) |
| 7426 | { |
| 7427 | const char *name = strVal(lfirst(lc)); |
| 7428 | |
| 7429 | if (buflen > 0) |
| 7430 | buf[buflen++] = '_'; /* insert _ between names */ |
| 7431 | |
| 7432 | /* |
| 7433 | * At this point we have buflen <= NAMEDATALEN. name should be less |
| 7434 | * than NAMEDATALEN already, but use strlcpy for paranoia. |
| 7435 | */ |
| 7436 | strlcpy(buf + buflen, name, NAMEDATALEN); |
| 7437 | buflen += strlen(buf + buflen); |
| 7438 | if (buflen >= NAMEDATALEN) |
| 7439 | break; |
| 7440 | } |
| 7441 | return pstrdup(buf); |
| 7442 | } |
| 7443 | |
| 7444 | /* |
| 7445 | * Add a check constraint to a single table and its children. Returns the |
| 7446 | * address of the constraint added to the parent relation, if one gets added, |
| 7447 | * or InvalidObjectAddress otherwise. |
| 7448 | * |
| 7449 | * Subroutine for ATExecAddConstraint. |
| 7450 | * |
| 7451 | * We must recurse to child tables during execution, rather than using |
| 7452 | * ALTER TABLE's normal prep-time recursion. The reason is that all the |
| 7453 | * constraints *must* be given the same name, else they won't be seen as |
| 7454 | * related later. If the user didn't explicitly specify a name, then |
| 7455 | * AddRelationNewConstraints would normally assign different names to the |
| 7456 | * child constraints. To fix that, we must capture the name assigned at |
| 7457 | * the parent table and pass that down. |
| 7458 | */ |
| 7459 | static ObjectAddress |
| 7460 | ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, |
| 7461 | Constraint *constr, bool recurse, bool recursing, |
| 7462 | bool is_readd, LOCKMODE lockmode) |
| 7463 | { |
| 7464 | List *newcons; |
| 7465 | ListCell *lcon; |
| 7466 | List *children; |
| 7467 | ListCell *child; |
| 7468 | ObjectAddress address = InvalidObjectAddress; |
| 7469 | |
| 7470 | /* At top level, permission check was done in ATPrepCmd, else do it */ |
| 7471 | if (recursing) |
| 7472 | ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); |
| 7473 | |
| 7474 | /* |
| 7475 | * Call AddRelationNewConstraints to do the work, making sure it works on |
| 7476 | * a copy of the Constraint so transformExpr can't modify the original. It |
| 7477 | * returns a list of cooked constraints. |
| 7478 | * |
| 7479 | * If the constraint ends up getting merged with a pre-existing one, it's |
| 7480 | * omitted from the returned list, which is what we want: we do not need |
| 7481 | * to do any validation work. That can only happen at child tables, |
| 7482 | * though, since we disallow merging at the top level. |
| 7483 | */ |
| 7484 | newcons = AddRelationNewConstraints(rel, NIL, |
| 7485 | list_make1(copyObject(constr)), |
| 7486 | recursing | is_readd, /* allow_merge */ |
| 7487 | !recursing, /* is_local */ |
| 7488 | is_readd, /* is_internal */ |
| 7489 | NULL); /* queryString not available |
| 7490 | * here */ |
| 7491 | |
| 7492 | /* we don't expect more than one constraint here */ |
| 7493 | Assert(list_length(newcons) <= 1); |
| 7494 | |
| 7495 | /* Add each to-be-validated constraint to Phase 3's queue */ |
| 7496 | foreach(lcon, newcons) |
| 7497 | { |
| 7498 | CookedConstraint *ccon = (CookedConstraint *) lfirst(lcon); |
| 7499 | |
| 7500 | if (!ccon->skip_validation) |
| 7501 | { |
| 7502 | NewConstraint *newcon; |
| 7503 | |
| 7504 | newcon = (NewConstraint *) palloc0(sizeof(NewConstraint)); |
| 7505 | newcon->name = ccon->name; |
| 7506 | newcon->contype = ccon->contype; |
| 7507 | newcon->qual = ccon->expr; |
| 7508 | |
| 7509 | tab->constraints = lappend(tab->constraints, newcon); |
| 7510 | } |
| 7511 | |
| 7512 | /* Save the actually assigned name if it was defaulted */ |
| 7513 | if (constr->conname == NULL) |
| 7514 | constr->conname = ccon->name; |
| 7515 | |
| 7516 | ObjectAddressSet(address, ConstraintRelationId, ccon->conoid); |
| 7517 | } |
| 7518 | |
| 7519 | /* At this point we must have a locked-down name to use */ |
| 7520 | Assert(constr->conname != NULL); |
| 7521 | |
| 7522 | /* Advance command counter in case same table is visited multiple times */ |
| 7523 | CommandCounterIncrement(); |
| 7524 | |
| 7525 | /* |
| 7526 | * If the constraint got merged with an existing constraint, we're done. |
| 7527 | * We mustn't recurse to child tables in this case, because they've |
| 7528 | * already got the constraint, and visiting them again would lead to an |
| 7529 | * incorrect value for coninhcount. |
| 7530 | */ |
| 7531 | if (newcons == NIL) |
| 7532 | return address; |
| 7533 | |
| 7534 | /* |
| 7535 | * If adding a NO INHERIT constraint, no need to find our children. |
| 7536 | */ |
| 7537 | if (constr->is_no_inherit) |
| 7538 | return address; |
| 7539 | |
| 7540 | /* |
| 7541 | * Propagate to children as appropriate. Unlike most other ALTER |
| 7542 | * routines, we have to do this one level of recursion at a time; we can't |
| 7543 | * use find_all_inheritors to do it in one pass. |
| 7544 | */ |
| 7545 | children = find_inheritance_children(RelationGetRelid(rel), lockmode); |
| 7546 | |
| 7547 | /* |
| 7548 | * Check if ONLY was specified with ALTER TABLE. If so, allow the |
| 7549 | * constraint creation only if there are no children currently. Error out |
| 7550 | * otherwise. |
| 7551 | */ |
| 7552 | if (!recurse && children != NIL) |
| 7553 | ereport(ERROR, |
| 7554 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 7555 | errmsg("constraint must be added to child tables too" ))); |
| 7556 | |
| 7557 | foreach(child, children) |
| 7558 | { |
| 7559 | Oid childrelid = lfirst_oid(child); |
| 7560 | Relation childrel; |
| 7561 | AlteredTableInfo *childtab; |
| 7562 | |
| 7563 | /* find_inheritance_children already got lock */ |
| 7564 | childrel = table_open(childrelid, NoLock); |
| 7565 | CheckTableNotInUse(childrel, "ALTER TABLE" ); |
| 7566 | |
| 7567 | /* Find or create work queue entry for this table */ |
| 7568 | childtab = ATGetQueueEntry(wqueue, childrel); |
| 7569 | |
| 7570 | /* Recurse to child */ |
| 7571 | ATAddCheckConstraint(wqueue, childtab, childrel, |
| 7572 | constr, recurse, true, is_readd, lockmode); |
| 7573 | |
| 7574 | table_close(childrel, NoLock); |
| 7575 | } |
| 7576 | |
| 7577 | return address; |
| 7578 | } |
| 7579 | |
| 7580 | /* |
| 7581 | * Add a foreign-key constraint to a single table; return the new constraint's |
| 7582 | * address. |
| 7583 | * |
| 7584 | * Subroutine for ATExecAddConstraint. Must already hold exclusive |
| 7585 | * lock on the rel, and have done appropriate validity checks for it. |
| 7586 | * We do permissions checks here, however. |
| 7587 | * |
| 7588 | * When the referenced or referencing tables (or both) are partitioned, |
| 7589 | * multiple pg_constraint rows are required -- one for each partitioned table |
| 7590 | * and each partition on each side (fortunately, not one for every combination |
| 7591 | * thereof). We also need action triggers on each leaf partition on the |
| 7592 | * referenced side, and check triggers on each leaf partition on the |
| 7593 | * referencing side. |
| 7594 | */ |
| 7595 | static ObjectAddress |
| 7596 | ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, |
| 7597 | Constraint *fkconstraint, Oid parentConstr, |
| 7598 | bool recurse, bool recursing, LOCKMODE lockmode) |
| 7599 | { |
| 7600 | Relation pkrel; |
| 7601 | int16 pkattnum[INDEX_MAX_KEYS]; |
| 7602 | int16 fkattnum[INDEX_MAX_KEYS]; |
| 7603 | Oid pktypoid[INDEX_MAX_KEYS]; |
| 7604 | Oid fktypoid[INDEX_MAX_KEYS]; |
| 7605 | Oid opclasses[INDEX_MAX_KEYS]; |
| 7606 | Oid pfeqoperators[INDEX_MAX_KEYS]; |
| 7607 | Oid ppeqoperators[INDEX_MAX_KEYS]; |
| 7608 | Oid ffeqoperators[INDEX_MAX_KEYS]; |
| 7609 | int i; |
| 7610 | int numfks, |
| 7611 | numpks; |
| 7612 | Oid indexOid; |
| 7613 | bool old_check_ok; |
| 7614 | ObjectAddress address; |
| 7615 | ListCell *old_pfeqop_item = list_head(fkconstraint->old_conpfeqop); |
| 7616 | |
| 7617 | /* |
| 7618 | * Grab ShareRowExclusiveLock on the pk table, so that someone doesn't |
| 7619 | * delete rows out from under us. |
| 7620 | */ |
| 7621 | if (OidIsValid(fkconstraint->old_pktable_oid)) |
| 7622 | pkrel = table_open(fkconstraint->old_pktable_oid, ShareRowExclusiveLock); |
| 7623 | else |
| 7624 | pkrel = table_openrv(fkconstraint->pktable, ShareRowExclusiveLock); |
| 7625 | |
| 7626 | /* |
| 7627 | * Validity checks (permission checks wait till we have the column |
| 7628 | * numbers) |
| 7629 | */ |
| 7630 | if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) |
| 7631 | { |
| 7632 | if (!recurse) |
| 7633 | ereport(ERROR, |
| 7634 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 7635 | errmsg("cannot use ONLY for foreign key on partitioned table \"%s\" referencing relation \"%s\"" , |
| 7636 | RelationGetRelationName(rel), |
| 7637 | RelationGetRelationName(pkrel)))); |
| 7638 | if (fkconstraint->skip_validation && !fkconstraint->initially_valid) |
| 7639 | ereport(ERROR, |
| 7640 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 7641 | errmsg("cannot add NOT VALID foreign key on partitioned table \"%s\" referencing relation \"%s\"" , |
| 7642 | RelationGetRelationName(rel), |
| 7643 | RelationGetRelationName(pkrel)), |
| 7644 | errdetail("This feature is not yet supported on partitioned tables." ))); |
| 7645 | } |
| 7646 | |
| 7647 | if (pkrel->rd_rel->relkind != RELKIND_RELATION && |
| 7648 | pkrel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) |
| 7649 | ereport(ERROR, |
| 7650 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 7651 | errmsg("referenced relation \"%s\" is not a table" , |
| 7652 | RelationGetRelationName(pkrel)))); |
| 7653 | |
| 7654 | if (!allowSystemTableMods && IsSystemRelation(pkrel)) |
| 7655 | ereport(ERROR, |
| 7656 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| 7657 | errmsg("permission denied: \"%s\" is a system catalog" , |
| 7658 | RelationGetRelationName(pkrel)))); |
| 7659 | |
| 7660 | /* |
| 7661 | * References from permanent or unlogged tables to temp tables, and from |
| 7662 | * permanent tables to unlogged tables, are disallowed because the |
| 7663 | * referenced data can vanish out from under us. References from temp |
| 7664 | * tables to any other table type are also disallowed, because other |
| 7665 | * backends might need to run the RI triggers on the perm table, but they |
| 7666 | * can't reliably see tuples in the local buffers of other backends. |
| 7667 | */ |
| 7668 | switch (rel->rd_rel->relpersistence) |
| 7669 | { |
| 7670 | case RELPERSISTENCE_PERMANENT: |
| 7671 | if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT) |
| 7672 | ereport(ERROR, |
| 7673 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 7674 | errmsg("constraints on permanent tables may reference only permanent tables" ))); |
| 7675 | break; |
| 7676 | case RELPERSISTENCE_UNLOGGED: |
| 7677 | if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT |
| 7678 | && pkrel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED) |
| 7679 | ereport(ERROR, |
| 7680 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 7681 | errmsg("constraints on unlogged tables may reference only permanent or unlogged tables" ))); |
| 7682 | break; |
| 7683 | case RELPERSISTENCE_TEMP: |
| 7684 | if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP) |
| 7685 | ereport(ERROR, |
| 7686 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 7687 | errmsg("constraints on temporary tables may reference only temporary tables" ))); |
| 7688 | if (!pkrel->rd_islocaltemp || !rel->rd_islocaltemp) |
| 7689 | ereport(ERROR, |
| 7690 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 7691 | errmsg("constraints on temporary tables must involve temporary tables of this session" ))); |
| 7692 | break; |
| 7693 | } |
| 7694 | |
| 7695 | /* |
| 7696 | * Look up the referencing attributes to make sure they exist, and record |
| 7697 | * their attnums and type OIDs. |
| 7698 | */ |
| 7699 | MemSet(pkattnum, 0, sizeof(pkattnum)); |
| 7700 | MemSet(fkattnum, 0, sizeof(fkattnum)); |
| 7701 | MemSet(pktypoid, 0, sizeof(pktypoid)); |
| 7702 | MemSet(fktypoid, 0, sizeof(fktypoid)); |
| 7703 | MemSet(opclasses, 0, sizeof(opclasses)); |
| 7704 | MemSet(pfeqoperators, 0, sizeof(pfeqoperators)); |
| 7705 | MemSet(ppeqoperators, 0, sizeof(ppeqoperators)); |
| 7706 | MemSet(ffeqoperators, 0, sizeof(ffeqoperators)); |
| 7707 | |
| 7708 | numfks = transformColumnNameList(RelationGetRelid(rel), |
| 7709 | fkconstraint->fk_attrs, |
| 7710 | fkattnum, fktypoid); |
| 7711 | |
| 7712 | /* |
| 7713 | * If the attribute list for the referenced table was omitted, lookup the |
| 7714 | * definition of the primary key and use it. Otherwise, validate the |
| 7715 | * supplied attribute list. In either case, discover the index OID and |
| 7716 | * index opclasses, and the attnums and type OIDs of the attributes. |
| 7717 | */ |
| 7718 | if (fkconstraint->pk_attrs == NIL) |
| 7719 | { |
| 7720 | numpks = transformFkeyGetPrimaryKey(pkrel, &indexOid, |
| 7721 | &fkconstraint->pk_attrs, |
| 7722 | pkattnum, pktypoid, |
| 7723 | opclasses); |
| 7724 | } |
| 7725 | else |
| 7726 | { |
| 7727 | numpks = transformColumnNameList(RelationGetRelid(pkrel), |
| 7728 | fkconstraint->pk_attrs, |
| 7729 | pkattnum, pktypoid); |
| 7730 | /* Look for an index matching the column list */ |
| 7731 | indexOid = transformFkeyCheckAttrs(pkrel, numpks, pkattnum, |
| 7732 | opclasses); |
| 7733 | } |
| 7734 | |
| 7735 | /* |
| 7736 | * Now we can check permissions. |
| 7737 | */ |
| 7738 | checkFkeyPermissions(pkrel, pkattnum, numpks); |
| 7739 | |
| 7740 | /* |
| 7741 | * Check some things for generated columns. |
| 7742 | */ |
| 7743 | for (i = 0; i < numfks; i++) |
| 7744 | { |
| 7745 | char attgenerated = TupleDescAttr(RelationGetDescr(rel), fkattnum[i] - 1)->attgenerated; |
| 7746 | |
| 7747 | if (attgenerated) |
| 7748 | { |
| 7749 | /* |
| 7750 | * Check restrictions on UPDATE/DELETE actions, per SQL standard |
| 7751 | */ |
| 7752 | if (fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETNULL || |
| 7753 | fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETDEFAULT || |
| 7754 | fkconstraint->fk_upd_action == FKCONSTR_ACTION_CASCADE) |
| 7755 | ereport(ERROR, |
| 7756 | (errcode(ERRCODE_SYNTAX_ERROR), |
| 7757 | errmsg("invalid %s action for foreign key constraint containing generated column" , |
| 7758 | "ON UPDATE" ))); |
| 7759 | if (fkconstraint->fk_del_action == FKCONSTR_ACTION_SETNULL || |
| 7760 | fkconstraint->fk_del_action == FKCONSTR_ACTION_SETDEFAULT) |
| 7761 | ereport(ERROR, |
| 7762 | (errcode(ERRCODE_SYNTAX_ERROR), |
| 7763 | errmsg("invalid %s action for foreign key constraint containing generated column" , |
| 7764 | "ON DELETE" ))); |
| 7765 | } |
| 7766 | } |
| 7767 | |
| 7768 | /* |
| 7769 | * Look up the equality operators to use in the constraint. |
| 7770 | * |
| 7771 | * Note that we have to be careful about the difference between the actual |
| 7772 | * PK column type and the opclass' declared input type, which might be |
| 7773 | * only binary-compatible with it. The declared opcintype is the right |
| 7774 | * thing to probe pg_amop with. |
| 7775 | */ |
| 7776 | if (numfks != numpks) |
| 7777 | ereport(ERROR, |
| 7778 | (errcode(ERRCODE_INVALID_FOREIGN_KEY), |
| 7779 | errmsg("number of referencing and referenced columns for foreign key disagree" ))); |
| 7780 | |
| 7781 | /* |
| 7782 | * On the strength of a previous constraint, we might avoid scanning |
| 7783 | * tables to validate this one. See below. |
| 7784 | */ |
| 7785 | old_check_ok = (fkconstraint->old_conpfeqop != NIL); |
| 7786 | Assert(!old_check_ok || numfks == list_length(fkconstraint->old_conpfeqop)); |
| 7787 | |
| 7788 | for (i = 0; i < numpks; i++) |
| 7789 | { |
| 7790 | Oid pktype = pktypoid[i]; |
| 7791 | Oid fktype = fktypoid[i]; |
| 7792 | Oid fktyped; |
| 7793 | HeapTuple cla_ht; |
| 7794 | Form_pg_opclass cla_tup; |
| 7795 | Oid amid; |
| 7796 | Oid opfamily; |
| 7797 | Oid opcintype; |
| 7798 | Oid pfeqop; |
| 7799 | Oid ppeqop; |
| 7800 | Oid ffeqop; |
| 7801 | int16 eqstrategy; |
| 7802 | Oid pfeqop_right; |
| 7803 | |
| 7804 | /* We need several fields out of the pg_opclass entry */ |
| 7805 | cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclasses[i])); |
| 7806 | if (!HeapTupleIsValid(cla_ht)) |
| 7807 | elog(ERROR, "cache lookup failed for opclass %u" , opclasses[i]); |
| 7808 | cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht); |
| 7809 | amid = cla_tup->opcmethod; |
| 7810 | opfamily = cla_tup->opcfamily; |
| 7811 | opcintype = cla_tup->opcintype; |
| 7812 | ReleaseSysCache(cla_ht); |
| 7813 | |
| 7814 | /* |
| 7815 | * Check it's a btree; currently this can never fail since no other |
| 7816 | * index AMs support unique indexes. If we ever did have other types |
| 7817 | * of unique indexes, we'd need a way to determine which operator |
| 7818 | * strategy number is equality. (Is it reasonable to insist that |
| 7819 | * every such index AM use btree's number for equality?) |
| 7820 | */ |
| 7821 | if (amid != BTREE_AM_OID) |
| 7822 | elog(ERROR, "only b-tree indexes are supported for foreign keys" ); |
| 7823 | eqstrategy = BTEqualStrategyNumber; |
| 7824 | |
| 7825 | /* |
| 7826 | * There had better be a primary equality operator for the index. |
| 7827 | * We'll use it for PK = PK comparisons. |
| 7828 | */ |
| 7829 | ppeqop = get_opfamily_member(opfamily, opcintype, opcintype, |
| 7830 | eqstrategy); |
| 7831 | |
| 7832 | if (!OidIsValid(ppeqop)) |
| 7833 | elog(ERROR, "missing operator %d(%u,%u) in opfamily %u" , |
| 7834 | eqstrategy, opcintype, opcintype, opfamily); |
| 7835 | |
| 7836 | /* |
| 7837 | * Are there equality operators that take exactly the FK type? Assume |
| 7838 | * we should look through any domain here. |
| 7839 | */ |
| 7840 | fktyped = getBaseType(fktype); |
| 7841 | |
| 7842 | pfeqop = get_opfamily_member(opfamily, opcintype, fktyped, |
| 7843 | eqstrategy); |
| 7844 | if (OidIsValid(pfeqop)) |
| 7845 | { |
| 7846 | pfeqop_right = fktyped; |
| 7847 | ffeqop = get_opfamily_member(opfamily, fktyped, fktyped, |
| 7848 | eqstrategy); |
| 7849 | } |
| 7850 | else |
| 7851 | { |
| 7852 | /* keep compiler quiet */ |
| 7853 | pfeqop_right = InvalidOid; |
| 7854 | ffeqop = InvalidOid; |
| 7855 | } |
| 7856 | |
| 7857 | if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop))) |
| 7858 | { |
| 7859 | /* |
| 7860 | * Otherwise, look for an implicit cast from the FK type to the |
| 7861 | * opcintype, and if found, use the primary equality operator. |
| 7862 | * This is a bit tricky because opcintype might be a polymorphic |
| 7863 | * type such as ANYARRAY or ANYENUM; so what we have to test is |
| 7864 | * whether the two actual column types can be concurrently cast to |
| 7865 | * that type. (Otherwise, we'd fail to reject combinations such |
| 7866 | * as int[] and point[].) |
| 7867 | */ |
| 7868 | Oid input_typeids[2]; |
| 7869 | Oid target_typeids[2]; |
| 7870 | |
| 7871 | input_typeids[0] = pktype; |
| 7872 | input_typeids[1] = fktype; |
| 7873 | target_typeids[0] = opcintype; |
| 7874 | target_typeids[1] = opcintype; |
| 7875 | if (can_coerce_type(2, input_typeids, target_typeids, |
| 7876 | COERCION_IMPLICIT)) |
| 7877 | { |
| 7878 | pfeqop = ffeqop = ppeqop; |
| 7879 | pfeqop_right = opcintype; |
| 7880 | } |
| 7881 | } |
| 7882 | |
| 7883 | if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop))) |
| 7884 | ereport(ERROR, |
| 7885 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
| 7886 | errmsg("foreign key constraint \"%s\" cannot be implemented" , |
| 7887 | fkconstraint->conname), |
| 7888 | errdetail("Key columns \"%s\" and \"%s\" " |
| 7889 | "are of incompatible types: %s and %s." , |
| 7890 | strVal(list_nth(fkconstraint->fk_attrs, i)), |
| 7891 | strVal(list_nth(fkconstraint->pk_attrs, i)), |
| 7892 | format_type_be(fktype), |
| 7893 | format_type_be(pktype)))); |
| 7894 | |
| 7895 | if (old_check_ok) |
| 7896 | { |
| 7897 | /* |
| 7898 | * When a pfeqop changes, revalidate the constraint. We could |
| 7899 | * permit intra-opfamily changes, but that adds subtle complexity |
| 7900 | * without any concrete benefit for core types. We need not |
| 7901 | * assess ppeqop or ffeqop, which RI_Initial_Check() does not use. |
| 7902 | */ |
| 7903 | old_check_ok = (pfeqop == lfirst_oid(old_pfeqop_item)); |
| 7904 | old_pfeqop_item = lnext(old_pfeqop_item); |
| 7905 | } |
| 7906 | if (old_check_ok) |
| 7907 | { |
| 7908 | Oid old_fktype; |
| 7909 | Oid new_fktype; |
| 7910 | CoercionPathType old_pathtype; |
| 7911 | CoercionPathType new_pathtype; |
| 7912 | Oid old_castfunc; |
| 7913 | Oid new_castfunc; |
| 7914 | Form_pg_attribute attr = TupleDescAttr(tab->oldDesc, |
| 7915 | fkattnum[i] - 1); |
| 7916 | |
| 7917 | /* |
| 7918 | * Identify coercion pathways from each of the old and new FK-side |
| 7919 | * column types to the right (foreign) operand type of the pfeqop. |
| 7920 | * We may assume that pg_constraint.conkey is not changing. |
| 7921 | */ |
| 7922 | old_fktype = attr->atttypid; |
| 7923 | new_fktype = fktype; |
| 7924 | old_pathtype = findFkeyCast(pfeqop_right, old_fktype, |
| 7925 | &old_castfunc); |
| 7926 | new_pathtype = findFkeyCast(pfeqop_right, new_fktype, |
| 7927 | &new_castfunc); |
| 7928 | |
| 7929 | /* |
| 7930 | * Upon a change to the cast from the FK column to its pfeqop |
| 7931 | * operand, revalidate the constraint. For this evaluation, a |
| 7932 | * binary coercion cast is equivalent to no cast at all. While |
| 7933 | * type implementors should design implicit casts with an eye |
| 7934 | * toward consistency of operations like equality, we cannot |
| 7935 | * assume here that they have done so. |
| 7936 | * |
| 7937 | * A function with a polymorphic argument could change behavior |
| 7938 | * arbitrarily in response to get_fn_expr_argtype(). Therefore, |
| 7939 | * when the cast destination is polymorphic, we only avoid |
| 7940 | * revalidation if the input type has not changed at all. Given |
| 7941 | * just the core data types and operator classes, this requirement |
| 7942 | * prevents no would-be optimizations. |
| 7943 | * |
| 7944 | * If the cast converts from a base type to a domain thereon, then |
| 7945 | * that domain type must be the opcintype of the unique index. |
| 7946 | * Necessarily, the primary key column must then be of the domain |
| 7947 | * type. Since the constraint was previously valid, all values on |
| 7948 | * the foreign side necessarily exist on the primary side and in |
| 7949 | * turn conform to the domain. Consequently, we need not treat |
| 7950 | * domains specially here. |
| 7951 | * |
| 7952 | * Since we require that all collations share the same notion of |
| 7953 | * equality (which they do, because texteq reduces to bitwise |
| 7954 | * equality), we don't compare collation here. |
| 7955 | * |
| 7956 | * We need not directly consider the PK type. It's necessarily |
| 7957 | * binary coercible to the opcintype of the unique index column, |
| 7958 | * and ri_triggers.c will only deal with PK datums in terms of |
| 7959 | * that opcintype. Changing the opcintype also changes pfeqop. |
| 7960 | */ |
| 7961 | old_check_ok = (new_pathtype == old_pathtype && |
| 7962 | new_castfunc == old_castfunc && |
| 7963 | (!IsPolymorphicType(pfeqop_right) || |
| 7964 | new_fktype == old_fktype)); |
| 7965 | } |
| 7966 | |
| 7967 | pfeqoperators[i] = pfeqop; |
| 7968 | ppeqoperators[i] = ppeqop; |
| 7969 | ffeqoperators[i] = ffeqop; |
| 7970 | } |
| 7971 | |
| 7972 | /* |
| 7973 | * Create all the constraint and trigger objects, recursing to partitions |
| 7974 | * as necessary. First handle the referenced side. |
| 7975 | */ |
| 7976 | address = addFkRecurseReferenced(wqueue, fkconstraint, rel, pkrel, |
| 7977 | indexOid, |
| 7978 | InvalidOid, /* no parent constraint */ |
| 7979 | numfks, |
| 7980 | pkattnum, |
| 7981 | fkattnum, |
| 7982 | pfeqoperators, |
| 7983 | ppeqoperators, |
| 7984 | ffeqoperators, |
| 7985 | old_check_ok); |
| 7986 | |
| 7987 | /* Now handle the referencing side. */ |
| 7988 | addFkRecurseReferencing(wqueue, fkconstraint, rel, pkrel, |
| 7989 | indexOid, |
| 7990 | address.objectId, |
| 7991 | numfks, |
| 7992 | pkattnum, |
| 7993 | fkattnum, |
| 7994 | pfeqoperators, |
| 7995 | ppeqoperators, |
| 7996 | ffeqoperators, |
| 7997 | old_check_ok, |
| 7998 | lockmode); |
| 7999 | |
| 8000 | /* |
| 8001 | * Done. Close pk table, but keep lock until we've committed. |
| 8002 | */ |
| 8003 | table_close(pkrel, NoLock); |
| 8004 | |
| 8005 | return address; |
| 8006 | } |
| 8007 | |
| 8008 | /* |
| 8009 | * addFkRecurseReferenced |
| 8010 | * subroutine for ATAddForeignKeyConstraint; recurses on the referenced |
| 8011 | * side of the constraint |
| 8012 | * |
| 8013 | * Create pg_constraint rows for the referenced side of the constraint, |
| 8014 | * referencing the parent of the referencing side; also create action triggers |
| 8015 | * on leaf partitions. If the table is partitioned, recurse to handle each |
| 8016 | * partition. |
| 8017 | * |
| 8018 | * wqueue is the ALTER TABLE work queue; can be NULL when not running as part |
| 8019 | * of an ALTER TABLE sequence. |
| 8020 | * fkconstraint is the constraint being added. |
| 8021 | * rel is the root referencing relation. |
| 8022 | * pkrel is the referenced relation; might be a partition, if recursing. |
| 8023 | * indexOid is the OID of the index (on pkrel) implementing this constraint. |
| 8024 | * parentConstr is the OID of a parent constraint; InvalidOid if this is a |
| 8025 | * top-level constraint. |
| 8026 | * numfks is the number of columns in the foreign key |
| 8027 | * pkattnum is the attnum array of referenced attributes. |
| 8028 | * fkattnum is the attnum array of referencing attributes. |
| 8029 | * pf/pp/ffeqoperators are OID array of operators between columns. |
| 8030 | * old_check_ok signals that this constraint replaces an existing one that |
| 8031 | * was already validated (thus this one doesn't need validation). |
| 8032 | */ |
| 8033 | static ObjectAddress |
| 8034 | addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel, |
| 8035 | Relation pkrel, Oid indexOid, Oid parentConstr, |
| 8036 | int numfks, |
| 8037 | int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators, |
| 8038 | Oid *ppeqoperators, Oid *ffeqoperators, bool old_check_ok) |
| 8039 | { |
| 8040 | ObjectAddress address; |
| 8041 | Oid constrOid; |
| 8042 | char *conname; |
| 8043 | bool conislocal; |
| 8044 | int coninhcount; |
| 8045 | bool connoinherit; |
| 8046 | |
| 8047 | /* |
| 8048 | * Verify relkind for each referenced partition. At the top level, this |
| 8049 | * is redundant with a previous check, but we need it when recursing. |
| 8050 | */ |
| 8051 | if (pkrel->rd_rel->relkind != RELKIND_RELATION && |
| 8052 | pkrel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) |
| 8053 | ereport(ERROR, |
| 8054 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 8055 | errmsg("referenced relation \"%s\" is not a table" , |
| 8056 | RelationGetRelationName(pkrel)))); |
| 8057 | |
| 8058 | /* |
| 8059 | * Caller supplies us with a constraint name; however, it may be used in |
| 8060 | * this partition, so come up with a different one in that case. |
| 8061 | */ |
| 8062 | if (ConstraintNameIsUsed(CONSTRAINT_RELATION, |
| 8063 | RelationGetRelid(rel), |
| 8064 | fkconstraint->conname)) |
| 8065 | conname = ChooseConstraintName(RelationGetRelationName(rel), |
| 8066 | ChooseForeignKeyConstraintNameAddition(fkconstraint->fk_attrs), |
| 8067 | "fkey" , |
| 8068 | RelationGetNamespace(rel), NIL); |
| 8069 | else |
| 8070 | conname = fkconstraint->conname; |
| 8071 | |
| 8072 | if (OidIsValid(parentConstr)) |
| 8073 | { |
| 8074 | conislocal = false; |
| 8075 | coninhcount = 1; |
| 8076 | connoinherit = false; |
| 8077 | } |
| 8078 | else |
| 8079 | { |
| 8080 | conislocal = true; |
| 8081 | coninhcount = 0; |
| 8082 | |
| 8083 | /* |
| 8084 | * always inherit for partitioned tables, never for legacy inheritance |
| 8085 | */ |
| 8086 | connoinherit = rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE; |
| 8087 | } |
| 8088 | |
| 8089 | /* |
| 8090 | * Record the FK constraint in pg_constraint. |
| 8091 | */ |
| 8092 | constrOid = CreateConstraintEntry(conname, |
| 8093 | RelationGetNamespace(rel), |
| 8094 | CONSTRAINT_FOREIGN, |
| 8095 | fkconstraint->deferrable, |
| 8096 | fkconstraint->initdeferred, |
| 8097 | fkconstraint->initially_valid, |
| 8098 | parentConstr, |
| 8099 | RelationGetRelid(rel), |
| 8100 | fkattnum, |
| 8101 | numfks, |
| 8102 | numfks, |
| 8103 | InvalidOid, /* not a domain constraint */ |
| 8104 | indexOid, |
| 8105 | RelationGetRelid(pkrel), |
| 8106 | pkattnum, |
| 8107 | pfeqoperators, |
| 8108 | ppeqoperators, |
| 8109 | ffeqoperators, |
| 8110 | numfks, |
| 8111 | fkconstraint->fk_upd_action, |
| 8112 | fkconstraint->fk_del_action, |
| 8113 | fkconstraint->fk_matchtype, |
| 8114 | NULL, /* no exclusion constraint */ |
| 8115 | NULL, /* no check constraint */ |
| 8116 | NULL, |
| 8117 | conislocal, /* islocal */ |
| 8118 | coninhcount, /* inhcount */ |
| 8119 | connoinherit, /* conNoInherit */ |
| 8120 | false); /* is_internal */ |
| 8121 | |
| 8122 | ObjectAddressSet(address, ConstraintRelationId, constrOid); |
| 8123 | |
| 8124 | /* |
| 8125 | * Mark the child constraint as part of the parent constraint; it must not |
| 8126 | * be dropped on its own. (This constraint is deleted when the partition |
| 8127 | * is detached, but a special check needs to occur that the partition |
| 8128 | * contains no referenced values.) |
| 8129 | */ |
| 8130 | if (OidIsValid(parentConstr)) |
| 8131 | { |
| 8132 | ObjectAddress referenced; |
| 8133 | |
| 8134 | ObjectAddressSet(referenced, ConstraintRelationId, parentConstr); |
| 8135 | recordDependencyOn(&address, &referenced, DEPENDENCY_INTERNAL); |
| 8136 | } |
| 8137 | |
| 8138 | /* make new constraint visible, in case we add more */ |
| 8139 | CommandCounterIncrement(); |
| 8140 | |
| 8141 | /* |
| 8142 | * If the referenced table is a plain relation, create the action triggers |
| 8143 | * that enforce the constraint. |
| 8144 | */ |
| 8145 | if (pkrel->rd_rel->relkind == RELKIND_RELATION) |
| 8146 | { |
| 8147 | createForeignKeyActionTriggers(rel, RelationGetRelid(pkrel), |
| 8148 | fkconstraint, |
| 8149 | constrOid, indexOid); |
| 8150 | } |
| 8151 | |
| 8152 | /* |
| 8153 | * If the referenced table is partitioned, recurse on ourselves to handle |
| 8154 | * each partition. We need one pg_constraint row created for each |
| 8155 | * partition in addition to the pg_constraint row for the parent table. |
| 8156 | */ |
| 8157 | if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) |
| 8158 | { |
| 8159 | PartitionDesc pd = RelationGetPartitionDesc(pkrel); |
| 8160 | |
| 8161 | for (int i = 0; i < pd->nparts; i++) |
| 8162 | { |
| 8163 | Relation partRel; |
| 8164 | AttrNumber *map; |
| 8165 | AttrNumber *mapped_pkattnum; |
| 8166 | Oid partIndexId; |
| 8167 | |
| 8168 | partRel = table_open(pd->oids[i], ShareRowExclusiveLock); |
| 8169 | |
| 8170 | /* |
| 8171 | * Map the attribute numbers in the referenced side of the FK |
| 8172 | * definition to match the partition's column layout. |
| 8173 | */ |
| 8174 | map = convert_tuples_by_name_map_if_req(RelationGetDescr(partRel), |
| 8175 | RelationGetDescr(pkrel), |
| 8176 | gettext_noop("could not convert row type" )); |
| 8177 | if (map) |
| 8178 | { |
| 8179 | mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks); |
| 8180 | for (int j = 0; j < numfks; j++) |
| 8181 | mapped_pkattnum[j] = map[pkattnum[j] - 1]; |
| 8182 | } |
| 8183 | else |
| 8184 | mapped_pkattnum = pkattnum; |
| 8185 | |
| 8186 | /* do the deed */ |
| 8187 | partIndexId = index_get_partition(partRel, indexOid); |
| 8188 | if (!OidIsValid(partIndexId)) |
| 8189 | elog(ERROR, "index for %u not found in partition %s" , |
| 8190 | indexOid, RelationGetRelationName(partRel)); |
| 8191 | addFkRecurseReferenced(wqueue, fkconstraint, rel, partRel, |
| 8192 | partIndexId, constrOid, numfks, |
| 8193 | mapped_pkattnum, fkattnum, |
| 8194 | pfeqoperators, ppeqoperators, ffeqoperators, |
| 8195 | old_check_ok); |
| 8196 | |
| 8197 | /* Done -- clean up (but keep the lock) */ |
| 8198 | table_close(partRel, NoLock); |
| 8199 | if (map) |
| 8200 | { |
| 8201 | pfree(mapped_pkattnum); |
| 8202 | pfree(map); |
| 8203 | } |
| 8204 | } |
| 8205 | } |
| 8206 | |
| 8207 | return address; |
| 8208 | } |
| 8209 | |
| 8210 | /* |
| 8211 | * addFkRecurseReferencing |
| 8212 | * subroutine for ATAddForeignKeyConstraint and CloneFkReferencing |
| 8213 | * |
| 8214 | * If the referencing relation is a plain relation, create the necessary check |
| 8215 | * triggers that implement the constraint, and set up for Phase 3 constraint |
| 8216 | * verification. If the referencing relation is a partitioned table, then |
| 8217 | * we create a pg_constraint row for it and recurse on this routine for each |
| 8218 | * partition. |
| 8219 | * |
| 8220 | * We assume that the referenced relation is locked against concurrent |
| 8221 | * deletions. If it's a partitioned relation, every partition must be so |
| 8222 | * locked. |
| 8223 | * |
| 8224 | * wqueue is the ALTER TABLE work queue; can be NULL when not running as part |
| 8225 | * of an ALTER TABLE sequence. |
| 8226 | * fkconstraint is the constraint being added. |
| 8227 | * rel is the referencing relation; might be a partition, if recursing. |
| 8228 | * pkrel is the root referenced relation. |
| 8229 | * indexOid is the OID of the index (on pkrel) implementing this constraint. |
| 8230 | * parentConstr is the OID of the parent constraint (there is always one). |
| 8231 | * numfks is the number of columns in the foreign key |
| 8232 | * pkattnum is the attnum array of referenced attributes. |
| 8233 | * fkattnum is the attnum array of referencing attributes. |
| 8234 | * pf/pp/ffeqoperators are OID array of operators between columns. |
| 8235 | * old_check_ok signals that this constraint replaces an existing one that |
| 8236 | * was already validated (thus this one doesn't need validation). |
| 8237 | * lockmode is the lockmode to acquire on partitions when recursing. |
| 8238 | */ |
| 8239 | static void |
| 8240 | addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel, |
| 8241 | Relation pkrel, Oid indexOid, Oid parentConstr, |
| 8242 | int numfks, int16 *pkattnum, int16 *fkattnum, |
| 8243 | Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators, |
| 8244 | bool old_check_ok, LOCKMODE lockmode) |
| 8245 | { |
| 8246 | AssertArg(OidIsValid(parentConstr)); |
| 8247 | |
| 8248 | if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) |
| 8249 | ereport(ERROR, |
| 8250 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 8251 | errmsg("foreign key constraints are not supported on foreign tables" ))); |
| 8252 | |
| 8253 | /* |
| 8254 | * If the referencing relation is a plain table, add the check triggers to |
| 8255 | * it and, if necessary, schedule it to be checked in Phase 3. |
| 8256 | * |
| 8257 | * If the relation is partitioned, drill down to do it to its partitions. |
| 8258 | */ |
| 8259 | if (rel->rd_rel->relkind == RELKIND_RELATION) |
| 8260 | { |
| 8261 | createForeignKeyCheckTriggers(RelationGetRelid(rel), |
| 8262 | RelationGetRelid(pkrel), |
| 8263 | fkconstraint, |
| 8264 | parentConstr, |
| 8265 | indexOid); |
| 8266 | |
| 8267 | /* |
| 8268 | * Tell Phase 3 to check that the constraint is satisfied by existing |
| 8269 | * rows. We can skip this during table creation, when requested |
| 8270 | * explicitly by specifying NOT VALID in an ADD FOREIGN KEY command, |
| 8271 | * and when we're recreating a constraint following a SET DATA TYPE |
| 8272 | * operation that did not impugn its validity. |
| 8273 | */ |
| 8274 | if (wqueue && !old_check_ok && !fkconstraint->skip_validation) |
| 8275 | { |
| 8276 | NewConstraint *newcon; |
| 8277 | AlteredTableInfo *tab; |
| 8278 | |
| 8279 | tab = ATGetQueueEntry(wqueue, rel); |
| 8280 | |
| 8281 | newcon = (NewConstraint *) palloc0(sizeof(NewConstraint)); |
| 8282 | newcon->name = get_constraint_name(parentConstr); |
| 8283 | newcon->contype = CONSTR_FOREIGN; |
| 8284 | newcon->refrelid = RelationGetRelid(pkrel); |
| 8285 | newcon->refindid = indexOid; |
| 8286 | newcon->conid = parentConstr; |
| 8287 | newcon->qual = (Node *) fkconstraint; |
| 8288 | |
| 8289 | tab->constraints = lappend(tab->constraints, newcon); |
| 8290 | } |
| 8291 | } |
| 8292 | else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) |
| 8293 | { |
| 8294 | PartitionDesc pd = RelationGetPartitionDesc(rel); |
| 8295 | |
| 8296 | /* |
| 8297 | * Recurse to take appropriate action on each partition; either we |
| 8298 | * find an existing constraint to reparent to ours, or we create a new |
| 8299 | * one. |
| 8300 | */ |
| 8301 | for (int i = 0; i < pd->nparts; i++) |
| 8302 | { |
| 8303 | Oid partitionId = pd->oids[i]; |
| 8304 | Relation partition = table_open(partitionId, lockmode); |
| 8305 | List *partFKs; |
| 8306 | AttrNumber *attmap; |
| 8307 | AttrNumber mapped_fkattnum[INDEX_MAX_KEYS]; |
| 8308 | bool attached; |
| 8309 | char *conname; |
| 8310 | Oid constrOid; |
| 8311 | ObjectAddress address, |
| 8312 | referenced; |
| 8313 | ListCell *cell; |
| 8314 | |
| 8315 | CheckTableNotInUse(partition, "ALTER TABLE" ); |
| 8316 | |
| 8317 | attmap = convert_tuples_by_name_map(RelationGetDescr(partition), |
| 8318 | RelationGetDescr(rel), |
| 8319 | gettext_noop("could not convert row type" )); |
| 8320 | for (int j = 0; j < numfks; j++) |
| 8321 | mapped_fkattnum[j] = attmap[fkattnum[j] - 1]; |
| 8322 | |
| 8323 | /* Check whether an existing constraint can be repurposed */ |
| 8324 | partFKs = copyObject(RelationGetFKeyList(partition)); |
| 8325 | attached = false; |
| 8326 | foreach(cell, partFKs) |
| 8327 | { |
| 8328 | ForeignKeyCacheInfo *fk; |
| 8329 | |
| 8330 | fk = lfirst_node(ForeignKeyCacheInfo, cell); |
| 8331 | if (tryAttachPartitionForeignKey(fk, |
| 8332 | partitionId, |
| 8333 | parentConstr, |
| 8334 | numfks, |
| 8335 | mapped_fkattnum, |
| 8336 | pkattnum, |
| 8337 | pfeqoperators)) |
| 8338 | { |
| 8339 | attached = true; |
| 8340 | break; |
| 8341 | } |
| 8342 | } |
| 8343 | if (attached) |
| 8344 | { |
| 8345 | table_close(partition, NoLock); |
| 8346 | continue; |
| 8347 | } |
| 8348 | |
| 8349 | /* |
| 8350 | * No luck finding a good constraint to reuse; create our own. |
| 8351 | */ |
| 8352 | if (ConstraintNameIsUsed(CONSTRAINT_RELATION, |
| 8353 | RelationGetRelid(partition), |
| 8354 | fkconstraint->conname)) |
| 8355 | conname = ChooseConstraintName(RelationGetRelationName(partition), |
| 8356 | ChooseForeignKeyConstraintNameAddition(fkconstraint->fk_attrs), |
| 8357 | "fkey" , |
| 8358 | RelationGetNamespace(partition), NIL); |
| 8359 | else |
| 8360 | conname = fkconstraint->conname; |
| 8361 | constrOid = |
| 8362 | CreateConstraintEntry(conname, |
| 8363 | RelationGetNamespace(partition), |
| 8364 | CONSTRAINT_FOREIGN, |
| 8365 | fkconstraint->deferrable, |
| 8366 | fkconstraint->initdeferred, |
| 8367 | fkconstraint->initially_valid, |
| 8368 | parentConstr, |
| 8369 | partitionId, |
| 8370 | mapped_fkattnum, |
| 8371 | numfks, |
| 8372 | numfks, |
| 8373 | InvalidOid, |
| 8374 | indexOid, |
| 8375 | RelationGetRelid(pkrel), |
| 8376 | pkattnum, |
| 8377 | pfeqoperators, |
| 8378 | ppeqoperators, |
| 8379 | ffeqoperators, |
| 8380 | numfks, |
| 8381 | fkconstraint->fk_upd_action, |
| 8382 | fkconstraint->fk_del_action, |
| 8383 | fkconstraint->fk_matchtype, |
| 8384 | NULL, |
| 8385 | NULL, |
| 8386 | NULL, |
| 8387 | false, |
| 8388 | 1, |
| 8389 | false, |
| 8390 | false); |
| 8391 | |
| 8392 | /* |
| 8393 | * Give this constraint partition-type dependencies on the parent |
| 8394 | * constraint as well as the table. |
| 8395 | */ |
| 8396 | ObjectAddressSet(address, ConstraintRelationId, constrOid); |
| 8397 | ObjectAddressSet(referenced, ConstraintRelationId, parentConstr); |
| 8398 | recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_PRI); |
| 8399 | ObjectAddressSet(referenced, RelationRelationId, partitionId); |
| 8400 | recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_SEC); |
| 8401 | |
| 8402 | /* Make all this visible before recursing */ |
| 8403 | CommandCounterIncrement(); |
| 8404 | |
| 8405 | /* call ourselves to finalize the creation and we're done */ |
| 8406 | addFkRecurseReferencing(wqueue, fkconstraint, partition, pkrel, |
| 8407 | indexOid, |
| 8408 | constrOid, |
| 8409 | numfks, |
| 8410 | pkattnum, |
| 8411 | mapped_fkattnum, |
| 8412 | pfeqoperators, |
| 8413 | ppeqoperators, |
| 8414 | ffeqoperators, |
| 8415 | old_check_ok, |
| 8416 | lockmode); |
| 8417 | |
| 8418 | table_close(partition, NoLock); |
| 8419 | } |
| 8420 | } |
| 8421 | } |
| 8422 | |
| 8423 | /* |
| 8424 | * CloneForeignKeyConstraints |
| 8425 | * Clone foreign keys from a partitioned table to a newly acquired |
| 8426 | * partition. |
| 8427 | * |
| 8428 | * partitionRel is a partition of parentRel, so we can be certain that it has |
| 8429 | * the same columns with the same datatypes. The columns may be in different |
| 8430 | * order, though. |
| 8431 | * |
| 8432 | * wqueue must be passed to set up phase 3 constraint checking, unless the |
| 8433 | * referencing-side partition is known to be empty (such as in CREATE TABLE / |
| 8434 | * PARTITION OF). |
| 8435 | */ |
| 8436 | static void |
| 8437 | CloneForeignKeyConstraints(List **wqueue, Relation parentRel, |
| 8438 | Relation partitionRel) |
| 8439 | { |
| 8440 | /* This only works for declarative partitioning */ |
| 8441 | Assert(parentRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); |
| 8442 | |
| 8443 | /* |
| 8444 | * Clone constraints for which the parent is on the referenced side. |
| 8445 | */ |
| 8446 | CloneFkReferenced(parentRel, partitionRel); |
| 8447 | |
| 8448 | /* |
| 8449 | * Now clone constraints where the parent is on the referencing side. |
| 8450 | */ |
| 8451 | CloneFkReferencing(wqueue, parentRel, partitionRel); |
| 8452 | } |
| 8453 | |
| 8454 | /* |
| 8455 | * CloneFkReferenced |
| 8456 | * Subroutine for CloneForeignKeyConstraints |
| 8457 | * |
| 8458 | * Find all the FKs that have the parent relation on the referenced side; |
| 8459 | * clone those constraints to the given partition. This is to be called |
| 8460 | * when the partition is being created or attached. |
| 8461 | * |
| 8462 | * This recurses to partitions, if the relation being attached is partitioned. |
| 8463 | * Recursion is done by calling addFkRecurseReferenced. |
| 8464 | */ |
| 8465 | static void |
| 8466 | CloneFkReferenced(Relation parentRel, Relation partitionRel) |
| 8467 | { |
| 8468 | Relation pg_constraint; |
| 8469 | AttrNumber *attmap; |
| 8470 | ListCell *cell; |
| 8471 | SysScanDesc scan; |
| 8472 | ScanKeyData key[2]; |
| 8473 | HeapTuple tuple; |
| 8474 | List *clone = NIL; |
| 8475 | |
| 8476 | /* |
| 8477 | * Search for any constraints where this partition is in the referenced |
| 8478 | * side. However, we must ignore any constraint whose parent constraint |
| 8479 | * is also going to be cloned, to avoid duplicates. So do it in two |
| 8480 | * steps: first construct the list of constraints to clone, then go over |
| 8481 | * that list cloning those whose parents are not in the list. (We must |
| 8482 | * not rely on the parent being seen first, since the catalog scan could |
| 8483 | * return children first.) |
| 8484 | */ |
| 8485 | pg_constraint = table_open(ConstraintRelationId, RowShareLock); |
| 8486 | ScanKeyInit(&key[0], |
| 8487 | Anum_pg_constraint_confrelid, BTEqualStrategyNumber, |
| 8488 | F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(parentRel))); |
| 8489 | ScanKeyInit(&key[1], |
| 8490 | Anum_pg_constraint_contype, BTEqualStrategyNumber, |
| 8491 | F_CHAREQ, CharGetDatum(CONSTRAINT_FOREIGN)); |
| 8492 | /* This is a seqscan, as we don't have a usable index ... */ |
| 8493 | scan = systable_beginscan(pg_constraint, InvalidOid, true, |
| 8494 | NULL, 2, key); |
| 8495 | while ((tuple = systable_getnext(scan)) != NULL) |
| 8496 | { |
| 8497 | Form_pg_constraint constrForm = (Form_pg_constraint) GETSTRUCT(tuple); |
| 8498 | |
| 8499 | /* Only try to clone the top-level constraint; skip child ones. */ |
| 8500 | if (constrForm->conparentid != InvalidOid) |
| 8501 | continue; |
| 8502 | |
| 8503 | clone = lappend_oid(clone, constrForm->oid); |
| 8504 | } |
| 8505 | systable_endscan(scan); |
| 8506 | table_close(pg_constraint, RowShareLock); |
| 8507 | |
| 8508 | attmap = convert_tuples_by_name_map(RelationGetDescr(partitionRel), |
| 8509 | RelationGetDescr(parentRel), |
| 8510 | gettext_noop("could not convert row type" )); |
| 8511 | foreach(cell, clone) |
| 8512 | { |
| 8513 | Oid constrOid = lfirst_oid(cell); |
| 8514 | Form_pg_constraint constrForm; |
| 8515 | Relation fkRel; |
| 8516 | Oid indexOid; |
| 8517 | Oid partIndexId; |
| 8518 | int numfks; |
| 8519 | AttrNumber conkey[INDEX_MAX_KEYS]; |
| 8520 | AttrNumber mapped_confkey[INDEX_MAX_KEYS]; |
| 8521 | AttrNumber confkey[INDEX_MAX_KEYS]; |
| 8522 | Oid conpfeqop[INDEX_MAX_KEYS]; |
| 8523 | Oid conppeqop[INDEX_MAX_KEYS]; |
| 8524 | Oid conffeqop[INDEX_MAX_KEYS]; |
| 8525 | Constraint *fkconstraint; |
| 8526 | |
| 8527 | tuple = SearchSysCache1(CONSTROID, constrOid); |
| 8528 | if (!HeapTupleIsValid(tuple)) |
| 8529 | elog(ERROR, "cache lookup failed for constraint %u" , constrOid); |
| 8530 | constrForm = (Form_pg_constraint) GETSTRUCT(tuple); |
| 8531 | |
| 8532 | /* |
| 8533 | * Because we're only expanding the key space at the referenced side, |
| 8534 | * we don't need to prevent any operation in the referencing table, so |
| 8535 | * AccessShareLock suffices (assumes that dropping the constraint |
| 8536 | * acquires AEL). |
| 8537 | */ |
| 8538 | fkRel = table_open(constrForm->conrelid, AccessShareLock); |
| 8539 | |
| 8540 | indexOid = constrForm->conindid; |
| 8541 | DeconstructFkConstraintRow(tuple, |
| 8542 | &numfks, |
| 8543 | conkey, |
| 8544 | confkey, |
| 8545 | conpfeqop, |
| 8546 | conppeqop, |
| 8547 | conffeqop); |
| 8548 | for (int i = 0; i < numfks; i++) |
| 8549 | mapped_confkey[i] = attmap[confkey[i] - 1]; |
| 8550 | |
| 8551 | fkconstraint = makeNode(Constraint); |
| 8552 | /* for now this is all we need */ |
| 8553 | fkconstraint->conname = NameStr(constrForm->conname); |
| 8554 | fkconstraint->fk_upd_action = constrForm->confupdtype; |
| 8555 | fkconstraint->fk_del_action = constrForm->confdeltype; |
| 8556 | fkconstraint->deferrable = constrForm->condeferrable; |
| 8557 | fkconstraint->initdeferred = constrForm->condeferred; |
| 8558 | fkconstraint->initially_valid = true; |
| 8559 | fkconstraint->fk_matchtype = constrForm->confmatchtype; |
| 8560 | |
| 8561 | /* set up colnames that are used to generate the constraint name */ |
| 8562 | for (int i = 0; i < numfks; i++) |
| 8563 | { |
| 8564 | Form_pg_attribute att; |
| 8565 | |
| 8566 | att = TupleDescAttr(RelationGetDescr(fkRel), |
| 8567 | conkey[i] - 1); |
| 8568 | fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs, |
| 8569 | makeString(NameStr(att->attname))); |
| 8570 | } |
| 8571 | |
| 8572 | /* |
| 8573 | * Add the new foreign key constraint pointing to the new partition. |
| 8574 | * Because this new partition appears in the referenced side of the |
| 8575 | * constraint, we don't need to set up for Phase 3 check. |
| 8576 | */ |
| 8577 | partIndexId = index_get_partition(partitionRel, indexOid); |
| 8578 | if (!OidIsValid(partIndexId)) |
| 8579 | elog(ERROR, "index for %u not found in partition %s" , |
| 8580 | indexOid, RelationGetRelationName(partitionRel)); |
| 8581 | addFkRecurseReferenced(NULL, |
| 8582 | fkconstraint, |
| 8583 | fkRel, |
| 8584 | partitionRel, |
| 8585 | partIndexId, |
| 8586 | constrOid, |
| 8587 | numfks, |
| 8588 | mapped_confkey, |
| 8589 | conkey, |
| 8590 | conpfeqop, |
| 8591 | conppeqop, |
| 8592 | conffeqop, |
| 8593 | true); |
| 8594 | |
| 8595 | table_close(fkRel, NoLock); |
| 8596 | ReleaseSysCache(tuple); |
| 8597 | } |
| 8598 | } |
| 8599 | |
| 8600 | /* |
| 8601 | * CloneFkReferencing |
| 8602 | * Subroutine for CloneForeignKeyConstraints |
| 8603 | * |
| 8604 | * For each FK constraint of the parent relation in the given list, find an |
| 8605 | * equivalent constraint in its partition relation that can be reparented; |
| 8606 | * if one cannot be found, create a new constraint in the partition as its |
| 8607 | * child. |
| 8608 | * |
| 8609 | * If wqueue is given, it is used to set up phase-3 verification for each |
| 8610 | * cloned constraint; if omitted, we assume that such verification is not |
| 8611 | * needed (example: the partition is being created anew). |
| 8612 | */ |
| 8613 | static void |
| 8614 | CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel) |
| 8615 | { |
| 8616 | AttrNumber *attmap; |
| 8617 | List *partFKs; |
| 8618 | List *clone = NIL; |
| 8619 | ListCell *cell; |
| 8620 | |
| 8621 | /* obtain a list of constraints that we need to clone */ |
| 8622 | foreach(cell, RelationGetFKeyList(parentRel)) |
| 8623 | { |
| 8624 | ForeignKeyCacheInfo *fk = lfirst(cell); |
| 8625 | |
| 8626 | clone = lappend_oid(clone, fk->conoid); |
| 8627 | } |
| 8628 | |
| 8629 | /* |
| 8630 | * Silently do nothing if there's nothing to do. In particular, this |
| 8631 | * avoids throwing a spurious error for foreign tables. |
| 8632 | */ |
| 8633 | if (clone == NIL) |
| 8634 | return; |
| 8635 | |
| 8636 | if (partRel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) |
| 8637 | ereport(ERROR, |
| 8638 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 8639 | errmsg("foreign key constraints are not supported on foreign tables" ))); |
| 8640 | |
| 8641 | /* |
| 8642 | * The constraint key may differ, if the columns in the partition are |
| 8643 | * different. This map is used to convert them. |
| 8644 | */ |
| 8645 | attmap = convert_tuples_by_name_map(RelationGetDescr(partRel), |
| 8646 | RelationGetDescr(parentRel), |
| 8647 | gettext_noop("could not convert row type" )); |
| 8648 | |
| 8649 | partFKs = copyObject(RelationGetFKeyList(partRel)); |
| 8650 | |
| 8651 | foreach(cell, clone) |
| 8652 | { |
| 8653 | Oid parentConstrOid = lfirst_oid(cell); |
| 8654 | Form_pg_constraint constrForm; |
| 8655 | Relation pkrel; |
| 8656 | HeapTuple tuple; |
| 8657 | int numfks; |
| 8658 | AttrNumber conkey[INDEX_MAX_KEYS]; |
| 8659 | AttrNumber mapped_conkey[INDEX_MAX_KEYS]; |
| 8660 | AttrNumber confkey[INDEX_MAX_KEYS]; |
| 8661 | Oid conpfeqop[INDEX_MAX_KEYS]; |
| 8662 | Oid conppeqop[INDEX_MAX_KEYS]; |
| 8663 | Oid conffeqop[INDEX_MAX_KEYS]; |
| 8664 | Constraint *fkconstraint; |
| 8665 | bool attached; |
| 8666 | Oid indexOid; |
| 8667 | Oid constrOid; |
| 8668 | ObjectAddress address, |
| 8669 | referenced; |
| 8670 | ListCell *cell; |
| 8671 | |
| 8672 | tuple = SearchSysCache1(CONSTROID, parentConstrOid); |
| 8673 | if (!HeapTupleIsValid(tuple)) |
| 8674 | elog(ERROR, "cache lookup failed for constraint %u" , |
| 8675 | parentConstrOid); |
| 8676 | constrForm = (Form_pg_constraint) GETSTRUCT(tuple); |
| 8677 | |
| 8678 | /* Don't clone constraints whose parents are being cloned */ |
| 8679 | if (list_member_oid(clone, constrForm->conparentid)) |
| 8680 | { |
| 8681 | ReleaseSysCache(tuple); |
| 8682 | continue; |
| 8683 | } |
| 8684 | |
| 8685 | /* |
| 8686 | * Need to prevent concurrent deletions. If pkrel is a partitioned |
| 8687 | * relation, that means to lock all partitions. |
| 8688 | */ |
| 8689 | pkrel = table_open(constrForm->confrelid, ShareRowExclusiveLock); |
| 8690 | if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) |
| 8691 | (void) find_all_inheritors(RelationGetRelid(pkrel), |
| 8692 | ShareRowExclusiveLock, NULL); |
| 8693 | |
| 8694 | DeconstructFkConstraintRow(tuple, &numfks, conkey, confkey, |
| 8695 | conpfeqop, conppeqop, conffeqop); |
| 8696 | for (int i = 0; i < numfks; i++) |
| 8697 | mapped_conkey[i] = attmap[conkey[i] - 1]; |
| 8698 | |
| 8699 | /* |
| 8700 | * Before creating a new constraint, see whether any existing FKs are |
| 8701 | * fit for the purpose. If one is, attach the parent constraint to |
| 8702 | * it, and don't clone anything. This way we avoid the expensive |
| 8703 | * verification step and don't end up with a duplicate FK, and we |
| 8704 | * don't need to recurse to partitions for this constraint. |
| 8705 | */ |
| 8706 | attached = false; |
| 8707 | foreach(cell, partFKs) |
| 8708 | { |
| 8709 | ForeignKeyCacheInfo *fk = lfirst_node(ForeignKeyCacheInfo, cell); |
| 8710 | |
| 8711 | if (tryAttachPartitionForeignKey(fk, |
| 8712 | RelationGetRelid(partRel), |
| 8713 | parentConstrOid, |
| 8714 | numfks, |
| 8715 | mapped_conkey, |
| 8716 | confkey, |
| 8717 | conpfeqop)) |
| 8718 | { |
| 8719 | attached = true; |
| 8720 | table_close(pkrel, NoLock); |
| 8721 | break; |
| 8722 | } |
| 8723 | } |
| 8724 | if (attached) |
| 8725 | { |
| 8726 | ReleaseSysCache(tuple); |
| 8727 | continue; |
| 8728 | } |
| 8729 | |
| 8730 | /* No dice. Set up to create our own constraint */ |
| 8731 | fkconstraint = makeNode(Constraint); |
| 8732 | if (ConstraintNameIsUsed(CONSTRAINT_RELATION, |
| 8733 | RelationGetRelid(partRel), |
| 8734 | NameStr(constrForm->conname))) |
| 8735 | fkconstraint->conname = |
| 8736 | ChooseConstraintName(RelationGetRelationName(partRel), |
| 8737 | ChooseForeignKeyConstraintNameAddition(fkconstraint->fk_attrs), |
| 8738 | "fkey" , |
| 8739 | RelationGetNamespace(partRel), NIL); |
| 8740 | else |
| 8741 | fkconstraint->conname = pstrdup(NameStr(constrForm->conname)); |
| 8742 | fkconstraint->fk_upd_action = constrForm->confupdtype; |
| 8743 | fkconstraint->fk_del_action = constrForm->confdeltype; |
| 8744 | fkconstraint->deferrable = constrForm->condeferrable; |
| 8745 | fkconstraint->initdeferred = constrForm->condeferred; |
| 8746 | fkconstraint->fk_matchtype = constrForm->confmatchtype; |
| 8747 | for (int i = 0; i < numfks; i++) |
| 8748 | { |
| 8749 | Form_pg_attribute att; |
| 8750 | |
| 8751 | att = TupleDescAttr(RelationGetDescr(partRel), |
| 8752 | mapped_conkey[i] - 1); |
| 8753 | fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs, |
| 8754 | makeString(NameStr(att->attname))); |
| 8755 | } |
| 8756 | |
| 8757 | indexOid = constrForm->conindid; |
| 8758 | constrOid = |
| 8759 | CreateConstraintEntry(fkconstraint->conname, |
| 8760 | constrForm->connamespace, |
| 8761 | CONSTRAINT_FOREIGN, |
| 8762 | fkconstraint->deferrable, |
| 8763 | fkconstraint->initdeferred, |
| 8764 | constrForm->convalidated, |
| 8765 | parentConstrOid, |
| 8766 | RelationGetRelid(partRel), |
| 8767 | mapped_conkey, |
| 8768 | numfks, |
| 8769 | numfks, |
| 8770 | InvalidOid, /* not a domain constraint */ |
| 8771 | indexOid, |
| 8772 | constrForm->confrelid, /* same foreign rel */ |
| 8773 | confkey, |
| 8774 | conpfeqop, |
| 8775 | conppeqop, |
| 8776 | conffeqop, |
| 8777 | numfks, |
| 8778 | fkconstraint->fk_upd_action, |
| 8779 | fkconstraint->fk_del_action, |
| 8780 | fkconstraint->fk_matchtype, |
| 8781 | NULL, |
| 8782 | NULL, |
| 8783 | NULL, |
| 8784 | false, /* islocal */ |
| 8785 | 1, /* inhcount */ |
| 8786 | false, /* conNoInherit */ |
| 8787 | true); |
| 8788 | |
| 8789 | /* Set up partition dependencies for the new constraint */ |
| 8790 | ObjectAddressSet(address, ConstraintRelationId, constrOid); |
| 8791 | ObjectAddressSet(referenced, ConstraintRelationId, parentConstrOid); |
| 8792 | recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_PRI); |
| 8793 | ObjectAddressSet(referenced, RelationRelationId, |
| 8794 | RelationGetRelid(partRel)); |
| 8795 | recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_SEC); |
| 8796 | |
| 8797 | /* Done with the cloned constraint's tuple */ |
| 8798 | ReleaseSysCache(tuple); |
| 8799 | |
| 8800 | /* Make all this visible before recursing */ |
| 8801 | CommandCounterIncrement(); |
| 8802 | |
| 8803 | addFkRecurseReferencing(wqueue, |
| 8804 | fkconstraint, |
| 8805 | partRel, |
| 8806 | pkrel, |
| 8807 | indexOid, |
| 8808 | constrOid, |
| 8809 | numfks, |
| 8810 | confkey, |
| 8811 | mapped_conkey, |
| 8812 | conpfeqop, |
| 8813 | conppeqop, |
| 8814 | conffeqop, |
| 8815 | false, /* no old check exists */ |
| 8816 | AccessExclusiveLock); |
| 8817 | table_close(pkrel, NoLock); |
| 8818 | } |
| 8819 | } |
| 8820 | |
| 8821 | /* |
| 8822 | * When the parent of a partition receives [the referencing side of] a foreign |
| 8823 | * key, we must propagate that foreign key to the partition. However, the |
| 8824 | * partition might already have an equivalent foreign key; this routine |
| 8825 | * compares the given ForeignKeyCacheInfo (in the partition) to the FK defined |
| 8826 | * by the other parameters. If they are equivalent, create the link between |
| 8827 | * the two constraints and return true. |
| 8828 | * |
| 8829 | * If the given FK does not match the one defined by rest of the params, |
| 8830 | * return false. |
| 8831 | */ |
| 8832 | static bool |
| 8833 | tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk, |
| 8834 | Oid partRelid, |
| 8835 | Oid parentConstrOid, |
| 8836 | int numfks, |
| 8837 | AttrNumber *mapped_conkey, |
| 8838 | AttrNumber *confkey, |
| 8839 | Oid *conpfeqop) |
| 8840 | { |
| 8841 | HeapTuple parentConstrTup; |
| 8842 | Form_pg_constraint parentConstr; |
| 8843 | HeapTuple partcontup; |
| 8844 | Form_pg_constraint partConstr; |
| 8845 | Relation trigrel; |
| 8846 | ScanKeyData key; |
| 8847 | SysScanDesc scan; |
| 8848 | HeapTuple trigtup; |
| 8849 | |
| 8850 | parentConstrTup = SearchSysCache1(CONSTROID, |
| 8851 | ObjectIdGetDatum(parentConstrOid)); |
| 8852 | if (!HeapTupleIsValid(parentConstrTup)) |
| 8853 | elog(ERROR, "cache lookup failed for constraint %u" , parentConstrOid); |
| 8854 | parentConstr = (Form_pg_constraint) GETSTRUCT(parentConstrTup); |
| 8855 | |
| 8856 | /* |
| 8857 | * Do some quick & easy initial checks. If any of these fail, we cannot |
| 8858 | * use this constraint. |
| 8859 | */ |
| 8860 | if (fk->confrelid != parentConstr->confrelid || fk->nkeys != numfks) |
| 8861 | { |
| 8862 | ReleaseSysCache(parentConstrTup); |
| 8863 | return false; |
| 8864 | } |
| 8865 | for (int i = 0; i < numfks; i++) |
| 8866 | { |
| 8867 | if (fk->conkey[i] != mapped_conkey[i] || |
| 8868 | fk->confkey[i] != confkey[i] || |
| 8869 | fk->conpfeqop[i] != conpfeqop[i]) |
| 8870 | { |
| 8871 | ReleaseSysCache(parentConstrTup); |
| 8872 | return false; |
| 8873 | } |
| 8874 | } |
| 8875 | |
| 8876 | /* |
| 8877 | * Looks good so far; do some more extensive checks. Presumably the check |
| 8878 | * for 'convalidated' could be dropped, since we don't really care about |
| 8879 | * that, but let's be careful for now. |
| 8880 | */ |
| 8881 | partcontup = SearchSysCache1(CONSTROID, |
| 8882 | ObjectIdGetDatum(fk->conoid)); |
| 8883 | if (!HeapTupleIsValid(partcontup)) |
| 8884 | elog(ERROR, "cache lookup failed for constraint %u" , fk->conoid); |
| 8885 | partConstr = (Form_pg_constraint) GETSTRUCT(partcontup); |
| 8886 | if (OidIsValid(partConstr->conparentid) || |
| 8887 | !partConstr->convalidated || |
| 8888 | partConstr->condeferrable != parentConstr->condeferrable || |
| 8889 | partConstr->condeferred != parentConstr->condeferred || |
| 8890 | partConstr->confupdtype != parentConstr->confupdtype || |
| 8891 | partConstr->confdeltype != parentConstr->confdeltype || |
| 8892 | partConstr->confmatchtype != parentConstr->confmatchtype) |
| 8893 | { |
| 8894 | ReleaseSysCache(parentConstrTup); |
| 8895 | ReleaseSysCache(partcontup); |
| 8896 | return false; |
| 8897 | } |
| 8898 | |
| 8899 | ReleaseSysCache(partcontup); |
| 8900 | ReleaseSysCache(parentConstrTup); |
| 8901 | |
| 8902 | /* |
| 8903 | * Looks good! Attach this constraint. The action triggers in the new |
| 8904 | * partition become redundant -- the parent table already has equivalent |
| 8905 | * ones, and those will be able to reach the partition. Remove the ones |
| 8906 | * in the partition. We identify them because they have our constraint |
| 8907 | * OID, as well as being on the referenced rel. |
| 8908 | */ |
| 8909 | trigrel = table_open(TriggerRelationId, RowExclusiveLock); |
| 8910 | ScanKeyInit(&key, |
| 8911 | Anum_pg_trigger_tgconstraint, |
| 8912 | BTEqualStrategyNumber, F_OIDEQ, |
| 8913 | ObjectIdGetDatum(fk->conoid)); |
| 8914 | |
| 8915 | scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true, |
| 8916 | NULL, 1, &key); |
| 8917 | while ((trigtup = systable_getnext(scan)) != NULL) |
| 8918 | { |
| 8919 | Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup); |
| 8920 | ObjectAddress trigger; |
| 8921 | |
| 8922 | if (trgform->tgconstrrelid != fk->conrelid) |
| 8923 | continue; |
| 8924 | if (trgform->tgrelid != fk->confrelid) |
| 8925 | continue; |
| 8926 | |
| 8927 | /* |
| 8928 | * The constraint is originally set up to contain this trigger as an |
| 8929 | * implementation object, so there's a dependency record that links |
| 8930 | * the two; however, since the trigger is no longer needed, we remove |
| 8931 | * the dependency link in order to be able to drop the trigger while |
| 8932 | * keeping the constraint intact. |
| 8933 | */ |
| 8934 | deleteDependencyRecordsFor(TriggerRelationId, |
| 8935 | trgform->oid, |
| 8936 | false); |
| 8937 | /* make dependency deletion visible to performDeletion */ |
| 8938 | CommandCounterIncrement(); |
| 8939 | ObjectAddressSet(trigger, TriggerRelationId, |
| 8940 | trgform->oid); |
| 8941 | performDeletion(&trigger, DROP_RESTRICT, 0); |
| 8942 | /* make trigger drop visible, in case the loop iterates */ |
| 8943 | CommandCounterIncrement(); |
| 8944 | } |
| 8945 | |
| 8946 | systable_endscan(scan); |
| 8947 | table_close(trigrel, RowExclusiveLock); |
| 8948 | |
| 8949 | ConstraintSetParentConstraint(fk->conoid, parentConstrOid, partRelid); |
| 8950 | CommandCounterIncrement(); |
| 8951 | return true; |
| 8952 | } |
| 8953 | |
| 8954 | |
| 8955 | /* |
| 8956 | * ALTER TABLE ALTER CONSTRAINT |
| 8957 | * |
| 8958 | * Update the attributes of a constraint. |
| 8959 | * |
| 8960 | * Currently only works for Foreign Key constraints. |
| 8961 | * Foreign keys do not inherit, so we purposely ignore the |
| 8962 | * recursion bit here, but we keep the API the same for when |
| 8963 | * other constraint types are supported. |
| 8964 | * |
| 8965 | * If the constraint is modified, returns its address; otherwise, return |
| 8966 | * InvalidObjectAddress. |
| 8967 | */ |
| 8968 | static ObjectAddress |
| 8969 | ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd, |
| 8970 | bool recurse, bool recursing, LOCKMODE lockmode) |
| 8971 | { |
| 8972 | Constraint *cmdcon; |
| 8973 | Relation conrel; |
| 8974 | SysScanDesc scan; |
| 8975 | ScanKeyData skey[3]; |
| 8976 | HeapTuple contuple; |
| 8977 | Form_pg_constraint currcon; |
| 8978 | ObjectAddress address; |
| 8979 | |
| 8980 | cmdcon = castNode(Constraint, cmd->def); |
| 8981 | |
| 8982 | conrel = table_open(ConstraintRelationId, RowExclusiveLock); |
| 8983 | |
| 8984 | /* |
| 8985 | * Find and check the target constraint |
| 8986 | */ |
| 8987 | ScanKeyInit(&skey[0], |
| 8988 | Anum_pg_constraint_conrelid, |
| 8989 | BTEqualStrategyNumber, F_OIDEQ, |
| 8990 | ObjectIdGetDatum(RelationGetRelid(rel))); |
| 8991 | ScanKeyInit(&skey[1], |
| 8992 | Anum_pg_constraint_contypid, |
| 8993 | BTEqualStrategyNumber, F_OIDEQ, |
| 8994 | ObjectIdGetDatum(InvalidOid)); |
| 8995 | ScanKeyInit(&skey[2], |
| 8996 | Anum_pg_constraint_conname, |
| 8997 | BTEqualStrategyNumber, F_NAMEEQ, |
| 8998 | CStringGetDatum(cmdcon->conname)); |
| 8999 | scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId, |
| 9000 | true, NULL, 3, skey); |
| 9001 | |
| 9002 | /* There can be at most one matching row */ |
| 9003 | if (!HeapTupleIsValid(contuple = systable_getnext(scan))) |
| 9004 | ereport(ERROR, |
| 9005 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
| 9006 | errmsg("constraint \"%s\" of relation \"%s\" does not exist" , |
| 9007 | cmdcon->conname, RelationGetRelationName(rel)))); |
| 9008 | |
| 9009 | currcon = (Form_pg_constraint) GETSTRUCT(contuple); |
| 9010 | if (currcon->contype != CONSTRAINT_FOREIGN) |
| 9011 | ereport(ERROR, |
| 9012 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 9013 | errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint" , |
| 9014 | cmdcon->conname, RelationGetRelationName(rel)))); |
| 9015 | |
| 9016 | if (currcon->condeferrable != cmdcon->deferrable || |
| 9017 | currcon->condeferred != cmdcon->initdeferred) |
| 9018 | { |
| 9019 | HeapTuple copyTuple; |
| 9020 | HeapTuple tgtuple; |
| 9021 | Form_pg_constraint copy_con; |
| 9022 | List *otherrelids = NIL; |
| 9023 | ScanKeyData tgkey; |
| 9024 | SysScanDesc tgscan; |
| 9025 | Relation tgrel; |
| 9026 | ListCell *lc; |
| 9027 | |
| 9028 | /* |
| 9029 | * Now update the catalog, while we have the door open. |
| 9030 | */ |
| 9031 | copyTuple = heap_copytuple(contuple); |
| 9032 | copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple); |
| 9033 | copy_con->condeferrable = cmdcon->deferrable; |
| 9034 | copy_con->condeferred = cmdcon->initdeferred; |
| 9035 | CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple); |
| 9036 | |
| 9037 | InvokeObjectPostAlterHook(ConstraintRelationId, |
| 9038 | currcon->oid, 0); |
| 9039 | |
| 9040 | heap_freetuple(copyTuple); |
| 9041 | |
| 9042 | /* |
| 9043 | * Now we need to update the multiple entries in pg_trigger that |
| 9044 | * implement the constraint. |
| 9045 | */ |
| 9046 | tgrel = table_open(TriggerRelationId, RowExclusiveLock); |
| 9047 | |
| 9048 | ScanKeyInit(&tgkey, |
| 9049 | Anum_pg_trigger_tgconstraint, |
| 9050 | BTEqualStrategyNumber, F_OIDEQ, |
| 9051 | ObjectIdGetDatum(currcon->oid)); |
| 9052 | |
| 9053 | tgscan = systable_beginscan(tgrel, TriggerConstraintIndexId, true, |
| 9054 | NULL, 1, &tgkey); |
| 9055 | |
| 9056 | while (HeapTupleIsValid(tgtuple = systable_getnext(tgscan))) |
| 9057 | { |
| 9058 | Form_pg_trigger tgform = (Form_pg_trigger) GETSTRUCT(tgtuple); |
| 9059 | Form_pg_trigger copy_tg; |
| 9060 | |
| 9061 | /* |
| 9062 | * Remember OIDs of other relation(s) involved in FK constraint. |
| 9063 | * (Note: it's likely that we could skip forcing a relcache inval |
| 9064 | * for other rels that don't have a trigger whose properties |
| 9065 | * change, but let's be conservative.) |
| 9066 | */ |
| 9067 | if (tgform->tgrelid != RelationGetRelid(rel)) |
| 9068 | otherrelids = list_append_unique_oid(otherrelids, |
| 9069 | tgform->tgrelid); |
| 9070 | |
| 9071 | /* |
| 9072 | * Update deferrability of RI_FKey_noaction_del, |
| 9073 | * RI_FKey_noaction_upd, RI_FKey_check_ins and RI_FKey_check_upd |
| 9074 | * triggers, but not others; see createForeignKeyActionTriggers |
| 9075 | * and CreateFKCheckTrigger. |
| 9076 | */ |
| 9077 | if (tgform->tgfoid != F_RI_FKEY_NOACTION_DEL && |
| 9078 | tgform->tgfoid != F_RI_FKEY_NOACTION_UPD && |
| 9079 | tgform->tgfoid != F_RI_FKEY_CHECK_INS && |
| 9080 | tgform->tgfoid != F_RI_FKEY_CHECK_UPD) |
| 9081 | continue; |
| 9082 | |
| 9083 | copyTuple = heap_copytuple(tgtuple); |
| 9084 | copy_tg = (Form_pg_trigger) GETSTRUCT(copyTuple); |
| 9085 | |
| 9086 | copy_tg->tgdeferrable = cmdcon->deferrable; |
| 9087 | copy_tg->tginitdeferred = cmdcon->initdeferred; |
| 9088 | CatalogTupleUpdate(tgrel, ©Tuple->t_self, copyTuple); |
| 9089 | |
| 9090 | InvokeObjectPostAlterHook(TriggerRelationId, currcon->oid, 0); |
| 9091 | |
| 9092 | heap_freetuple(copyTuple); |
| 9093 | } |
| 9094 | |
| 9095 | systable_endscan(tgscan); |
| 9096 | |
| 9097 | table_close(tgrel, RowExclusiveLock); |
| 9098 | |
| 9099 | /* |
| 9100 | * Invalidate relcache so that others see the new attributes. We must |
| 9101 | * inval both the named rel and any others having relevant triggers. |
| 9102 | * (At present there should always be exactly one other rel, but |
| 9103 | * there's no need to hard-wire such an assumption here.) |
| 9104 | */ |
| 9105 | CacheInvalidateRelcache(rel); |
| 9106 | foreach(lc, otherrelids) |
| 9107 | { |
| 9108 | CacheInvalidateRelcacheByRelid(lfirst_oid(lc)); |
| 9109 | } |
| 9110 | |
| 9111 | ObjectAddressSet(address, ConstraintRelationId, currcon->oid); |
| 9112 | } |
| 9113 | else |
| 9114 | address = InvalidObjectAddress; |
| 9115 | |
| 9116 | systable_endscan(scan); |
| 9117 | |
| 9118 | table_close(conrel, RowExclusiveLock); |
| 9119 | |
| 9120 | return address; |
| 9121 | } |
| 9122 | |
| 9123 | /* |
| 9124 | * ALTER TABLE VALIDATE CONSTRAINT |
| 9125 | * |
| 9126 | * XXX The reason we handle recursion here rather than at Phase 1 is because |
| 9127 | * there's no good way to skip recursing when handling foreign keys: there is |
| 9128 | * no need to lock children in that case, yet we wouldn't be able to avoid |
| 9129 | * doing so at that level. |
| 9130 | * |
| 9131 | * Return value is the address of the validated constraint. If the constraint |
| 9132 | * was already validated, InvalidObjectAddress is returned. |
| 9133 | */ |
| 9134 | static ObjectAddress |
| 9135 | ATExecValidateConstraint(Relation rel, char *constrName, bool recurse, |
| 9136 | bool recursing, LOCKMODE lockmode) |
| 9137 | { |
| 9138 | Relation conrel; |
| 9139 | SysScanDesc scan; |
| 9140 | ScanKeyData skey[3]; |
| 9141 | HeapTuple tuple; |
| 9142 | Form_pg_constraint con; |
| 9143 | ObjectAddress address; |
| 9144 | |
| 9145 | conrel = table_open(ConstraintRelationId, RowExclusiveLock); |
| 9146 | |
| 9147 | /* |
| 9148 | * Find and check the target constraint |
| 9149 | */ |
| 9150 | ScanKeyInit(&skey[0], |
| 9151 | Anum_pg_constraint_conrelid, |
| 9152 | BTEqualStrategyNumber, F_OIDEQ, |
| 9153 | ObjectIdGetDatum(RelationGetRelid(rel))); |
| 9154 | ScanKeyInit(&skey[1], |
| 9155 | Anum_pg_constraint_contypid, |
| 9156 | BTEqualStrategyNumber, F_OIDEQ, |
| 9157 | ObjectIdGetDatum(InvalidOid)); |
| 9158 | ScanKeyInit(&skey[2], |
| 9159 | Anum_pg_constraint_conname, |
| 9160 | BTEqualStrategyNumber, F_NAMEEQ, |
| 9161 | CStringGetDatum(constrName)); |
| 9162 | scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId, |
| 9163 | true, NULL, 3, skey); |
| 9164 | |
| 9165 | /* There can be at most one matching row */ |
| 9166 | if (!HeapTupleIsValid(tuple = systable_getnext(scan))) |
| 9167 | ereport(ERROR, |
| 9168 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
| 9169 | errmsg("constraint \"%s\" of relation \"%s\" does not exist" , |
| 9170 | constrName, RelationGetRelationName(rel)))); |
| 9171 | |
| 9172 | con = (Form_pg_constraint) GETSTRUCT(tuple); |
| 9173 | if (con->contype != CONSTRAINT_FOREIGN && |
| 9174 | con->contype != CONSTRAINT_CHECK) |
| 9175 | ereport(ERROR, |
| 9176 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 9177 | errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key or check constraint" , |
| 9178 | constrName, RelationGetRelationName(rel)))); |
| 9179 | |
| 9180 | if (!con->convalidated) |
| 9181 | { |
| 9182 | HeapTuple copyTuple; |
| 9183 | Form_pg_constraint copy_con; |
| 9184 | |
| 9185 | if (con->contype == CONSTRAINT_FOREIGN) |
| 9186 | { |
| 9187 | Relation refrel; |
| 9188 | |
| 9189 | /* |
| 9190 | * Triggers are already in place on both tables, so a concurrent |
| 9191 | * write that alters the result here is not possible. Normally we |
| 9192 | * can run a query here to do the validation, which would only |
| 9193 | * require AccessShareLock. In some cases, it is possible that we |
| 9194 | * might need to fire triggers to perform the check, so we take a |
| 9195 | * lock at RowShareLock level just in case. |
| 9196 | */ |
| 9197 | refrel = table_open(con->confrelid, RowShareLock); |
| 9198 | |
| 9199 | validateForeignKeyConstraint(constrName, rel, refrel, |
| 9200 | con->conindid, |
| 9201 | con->oid); |
| 9202 | table_close(refrel, NoLock); |
| 9203 | |
| 9204 | /* |
| 9205 | * We disallow creating invalid foreign keys to or from |
| 9206 | * partitioned tables, so ignoring the recursion bit is okay. |
| 9207 | */ |
| 9208 | } |
| 9209 | else if (con->contype == CONSTRAINT_CHECK) |
| 9210 | { |
| 9211 | List *children = NIL; |
| 9212 | ListCell *child; |
| 9213 | |
| 9214 | /* |
| 9215 | * If we're recursing, the parent has already done this, so skip |
| 9216 | * it. Also, if the constraint is a NO INHERIT constraint, we |
| 9217 | * shouldn't try to look for it in the children. |
| 9218 | */ |
| 9219 | if (!recursing && !con->connoinherit) |
| 9220 | children = find_all_inheritors(RelationGetRelid(rel), |
| 9221 | lockmode, NULL); |
| 9222 | |
| 9223 | /* |
| 9224 | * For CHECK constraints, we must ensure that we only mark the |
| 9225 | * constraint as validated on the parent if it's already validated |
| 9226 | * on the children. |
| 9227 | * |
| 9228 | * We recurse before validating on the parent, to reduce risk of |
| 9229 | * deadlocks. |
| 9230 | */ |
| 9231 | foreach(child, children) |
| 9232 | { |
| 9233 | Oid childoid = lfirst_oid(child); |
| 9234 | Relation childrel; |
| 9235 | |
| 9236 | if (childoid == RelationGetRelid(rel)) |
| 9237 | continue; |
| 9238 | |
| 9239 | /* |
| 9240 | * If we are told not to recurse, there had better not be any |
| 9241 | * child tables, because we can't mark the constraint on the |
| 9242 | * parent valid unless it is valid for all child tables. |
| 9243 | */ |
| 9244 | if (!recurse) |
| 9245 | ereport(ERROR, |
| 9246 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 9247 | errmsg("constraint must be validated on child tables too" ))); |
| 9248 | |
| 9249 | /* find_all_inheritors already got lock */ |
| 9250 | childrel = table_open(childoid, NoLock); |
| 9251 | |
| 9252 | ATExecValidateConstraint(childrel, constrName, false, |
| 9253 | true, lockmode); |
| 9254 | table_close(childrel, NoLock); |
| 9255 | } |
| 9256 | |
| 9257 | validateCheckConstraint(rel, tuple); |
| 9258 | |
| 9259 | /* |
| 9260 | * Invalidate relcache so that others see the new validated |
| 9261 | * constraint. |
| 9262 | */ |
| 9263 | CacheInvalidateRelcache(rel); |
| 9264 | } |
| 9265 | |
| 9266 | /* |
| 9267 | * Now update the catalog, while we have the door open. |
| 9268 | */ |
| 9269 | copyTuple = heap_copytuple(tuple); |
| 9270 | copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple); |
| 9271 | copy_con->convalidated = true; |
| 9272 | CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple); |
| 9273 | |
| 9274 | InvokeObjectPostAlterHook(ConstraintRelationId, con->oid, 0); |
| 9275 | |
| 9276 | heap_freetuple(copyTuple); |
| 9277 | |
| 9278 | ObjectAddressSet(address, ConstraintRelationId, con->oid); |
| 9279 | } |
| 9280 | else |
| 9281 | address = InvalidObjectAddress; /* already validated */ |
| 9282 | |
| 9283 | systable_endscan(scan); |
| 9284 | |
| 9285 | table_close(conrel, RowExclusiveLock); |
| 9286 | |
| 9287 | return address; |
| 9288 | } |
| 9289 | |
| 9290 | |
| 9291 | /* |
| 9292 | * transformColumnNameList - transform list of column names |
| 9293 | * |
| 9294 | * Lookup each name and return its attnum and type OID |
| 9295 | */ |
| 9296 | static int |
| 9297 | transformColumnNameList(Oid relId, List *colList, |
| 9298 | int16 *attnums, Oid *atttypids) |
| 9299 | { |
| 9300 | ListCell *l; |
| 9301 | int attnum; |
| 9302 | |
| 9303 | attnum = 0; |
| 9304 | foreach(l, colList) |
| 9305 | { |
| 9306 | char *attname = strVal(lfirst(l)); |
| 9307 | HeapTuple atttuple; |
| 9308 | |
| 9309 | atttuple = SearchSysCacheAttName(relId, attname); |
| 9310 | if (!HeapTupleIsValid(atttuple)) |
| 9311 | ereport(ERROR, |
| 9312 | (errcode(ERRCODE_UNDEFINED_COLUMN), |
| 9313 | errmsg("column \"%s\" referenced in foreign key constraint does not exist" , |
| 9314 | attname))); |
| 9315 | if (attnum >= INDEX_MAX_KEYS) |
| 9316 | ereport(ERROR, |
| 9317 | (errcode(ERRCODE_TOO_MANY_COLUMNS), |
| 9318 | errmsg("cannot have more than %d keys in a foreign key" , |
| 9319 | INDEX_MAX_KEYS))); |
| 9320 | attnums[attnum] = ((Form_pg_attribute) GETSTRUCT(atttuple))->attnum; |
| 9321 | atttypids[attnum] = ((Form_pg_attribute) GETSTRUCT(atttuple))->atttypid; |
| 9322 | ReleaseSysCache(atttuple); |
| 9323 | attnum++; |
| 9324 | } |
| 9325 | |
| 9326 | return attnum; |
| 9327 | } |
| 9328 | |
| 9329 | /* |
| 9330 | * transformFkeyGetPrimaryKey - |
| 9331 | * |
| 9332 | * Look up the names, attnums, and types of the primary key attributes |
| 9333 | * for the pkrel. Also return the index OID and index opclasses of the |
| 9334 | * index supporting the primary key. |
| 9335 | * |
| 9336 | * All parameters except pkrel are output parameters. Also, the function |
| 9337 | * return value is the number of attributes in the primary key. |
| 9338 | * |
| 9339 | * Used when the column list in the REFERENCES specification is omitted. |
| 9340 | */ |
| 9341 | static int |
| 9342 | transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid, |
| 9343 | List **attnamelist, |
| 9344 | int16 *attnums, Oid *atttypids, |
| 9345 | Oid *opclasses) |
| 9346 | { |
| 9347 | List *indexoidlist; |
| 9348 | ListCell *indexoidscan; |
| 9349 | HeapTuple indexTuple = NULL; |
| 9350 | Form_pg_index indexStruct = NULL; |
| 9351 | Datum indclassDatum; |
| 9352 | bool isnull; |
| 9353 | oidvector *indclass; |
| 9354 | int i; |
| 9355 | |
| 9356 | /* |
| 9357 | * Get the list of index OIDs for the table from the relcache, and look up |
| 9358 | * each one in the pg_index syscache until we find one marked primary key |
| 9359 | * (hopefully there isn't more than one such). Insist it's valid, too. |
| 9360 | */ |
| 9361 | *indexOid = InvalidOid; |
| 9362 | |
| 9363 | indexoidlist = RelationGetIndexList(pkrel); |
| 9364 | |
| 9365 | foreach(indexoidscan, indexoidlist) |
| 9366 | { |
| 9367 | Oid indexoid = lfirst_oid(indexoidscan); |
| 9368 | |
| 9369 | indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid)); |
| 9370 | if (!HeapTupleIsValid(indexTuple)) |
| 9371 | elog(ERROR, "cache lookup failed for index %u" , indexoid); |
| 9372 | indexStruct = (Form_pg_index) GETSTRUCT(indexTuple); |
| 9373 | if (indexStruct->indisprimary && indexStruct->indisvalid) |
| 9374 | { |
| 9375 | /* |
| 9376 | * Refuse to use a deferrable primary key. This is per SQL spec, |
| 9377 | * and there would be a lot of interesting semantic problems if we |
| 9378 | * tried to allow it. |
| 9379 | */ |
| 9380 | if (!indexStruct->indimmediate) |
| 9381 | ereport(ERROR, |
| 9382 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
| 9383 | errmsg("cannot use a deferrable primary key for referenced table \"%s\"" , |
| 9384 | RelationGetRelationName(pkrel)))); |
| 9385 | |
| 9386 | *indexOid = indexoid; |
| 9387 | break; |
| 9388 | } |
| 9389 | ReleaseSysCache(indexTuple); |
| 9390 | } |
| 9391 | |
| 9392 | list_free(indexoidlist); |
| 9393 | |
| 9394 | /* |
| 9395 | * Check that we found it |
| 9396 | */ |
| 9397 | if (!OidIsValid(*indexOid)) |
| 9398 | ereport(ERROR, |
| 9399 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
| 9400 | errmsg("there is no primary key for referenced table \"%s\"" , |
| 9401 | RelationGetRelationName(pkrel)))); |
| 9402 | |
| 9403 | /* Must get indclass the hard way */ |
| 9404 | indclassDatum = SysCacheGetAttr(INDEXRELID, indexTuple, |
| 9405 | Anum_pg_index_indclass, &isnull); |
| 9406 | Assert(!isnull); |
| 9407 | indclass = (oidvector *) DatumGetPointer(indclassDatum); |
| 9408 | |
| 9409 | /* |
| 9410 | * Now build the list of PK attributes from the indkey definition (we |
| 9411 | * assume a primary key cannot have expressional elements) |
| 9412 | */ |
| 9413 | *attnamelist = NIL; |
| 9414 | for (i = 0; i < indexStruct->indnkeyatts; i++) |
| 9415 | { |
| 9416 | int pkattno = indexStruct->indkey.values[i]; |
| 9417 | |
| 9418 | attnums[i] = pkattno; |
| 9419 | atttypids[i] = attnumTypeId(pkrel, pkattno); |
| 9420 | opclasses[i] = indclass->values[i]; |
| 9421 | *attnamelist = lappend(*attnamelist, |
| 9422 | makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno))))); |
| 9423 | } |
| 9424 | |
| 9425 | ReleaseSysCache(indexTuple); |
| 9426 | |
| 9427 | return i; |
| 9428 | } |
| 9429 | |
| 9430 | /* |
| 9431 | * transformFkeyCheckAttrs - |
| 9432 | * |
| 9433 | * Make sure that the attributes of a referenced table belong to a unique |
| 9434 | * (or primary key) constraint. Return the OID of the index supporting |
| 9435 | * the constraint, as well as the opclasses associated with the index |
| 9436 | * columns. |
| 9437 | */ |
| 9438 | static Oid |
| 9439 | transformFkeyCheckAttrs(Relation pkrel, |
| 9440 | int numattrs, int16 *attnums, |
| 9441 | Oid *opclasses) /* output parameter */ |
| 9442 | { |
| 9443 | Oid indexoid = InvalidOid; |
| 9444 | bool found = false; |
| 9445 | bool found_deferrable = false; |
| 9446 | List *indexoidlist; |
| 9447 | ListCell *indexoidscan; |
| 9448 | int i, |
| 9449 | j; |
| 9450 | |
| 9451 | /* |
| 9452 | * Reject duplicate appearances of columns in the referenced-columns list. |
| 9453 | * Such a case is forbidden by the SQL standard, and even if we thought it |
| 9454 | * useful to allow it, there would be ambiguity about how to match the |
| 9455 | * list to unique indexes (in particular, it'd be unclear which index |
| 9456 | * opclass goes with which FK column). |
| 9457 | */ |
| 9458 | for (i = 0; i < numattrs; i++) |
| 9459 | { |
| 9460 | for (j = i + 1; j < numattrs; j++) |
| 9461 | { |
| 9462 | if (attnums[i] == attnums[j]) |
| 9463 | ereport(ERROR, |
| 9464 | (errcode(ERRCODE_INVALID_FOREIGN_KEY), |
| 9465 | errmsg("foreign key referenced-columns list must not contain duplicates" ))); |
| 9466 | } |
| 9467 | } |
| 9468 | |
| 9469 | /* |
| 9470 | * Get the list of index OIDs for the table from the relcache, and look up |
| 9471 | * each one in the pg_index syscache, and match unique indexes to the list |
| 9472 | * of attnums we are given. |
| 9473 | */ |
| 9474 | indexoidlist = RelationGetIndexList(pkrel); |
| 9475 | |
| 9476 | foreach(indexoidscan, indexoidlist) |
| 9477 | { |
| 9478 | HeapTuple indexTuple; |
| 9479 | Form_pg_index indexStruct; |
| 9480 | |
| 9481 | indexoid = lfirst_oid(indexoidscan); |
| 9482 | indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid)); |
| 9483 | if (!HeapTupleIsValid(indexTuple)) |
| 9484 | elog(ERROR, "cache lookup failed for index %u" , indexoid); |
| 9485 | indexStruct = (Form_pg_index) GETSTRUCT(indexTuple); |
| 9486 | |
| 9487 | /* |
| 9488 | * Must have the right number of columns; must be unique and not a |
| 9489 | * partial index; forget it if there are any expressions, too. Invalid |
| 9490 | * indexes are out as well. |
| 9491 | */ |
| 9492 | if (indexStruct->indnkeyatts == numattrs && |
| 9493 | indexStruct->indisunique && |
| 9494 | indexStruct->indisvalid && |
| 9495 | heap_attisnull(indexTuple, Anum_pg_index_indpred, NULL) && |
| 9496 | heap_attisnull(indexTuple, Anum_pg_index_indexprs, NULL)) |
| 9497 | { |
| 9498 | Datum indclassDatum; |
| 9499 | bool isnull; |
| 9500 | oidvector *indclass; |
| 9501 | |
| 9502 | /* Must get indclass the hard way */ |
| 9503 | indclassDatum = SysCacheGetAttr(INDEXRELID, indexTuple, |
| 9504 | Anum_pg_index_indclass, &isnull); |
| 9505 | Assert(!isnull); |
| 9506 | indclass = (oidvector *) DatumGetPointer(indclassDatum); |
| 9507 | |
| 9508 | /* |
| 9509 | * The given attnum list may match the index columns in any order. |
| 9510 | * Check for a match, and extract the appropriate opclasses while |
| 9511 | * we're at it. |
| 9512 | * |
| 9513 | * We know that attnums[] is duplicate-free per the test at the |
| 9514 | * start of this function, and we checked above that the number of |
| 9515 | * index columns agrees, so if we find a match for each attnums[] |
| 9516 | * entry then we must have a one-to-one match in some order. |
| 9517 | */ |
| 9518 | for (i = 0; i < numattrs; i++) |
| 9519 | { |
| 9520 | found = false; |
| 9521 | for (j = 0; j < numattrs; j++) |
| 9522 | { |
| 9523 | if (attnums[i] == indexStruct->indkey.values[j]) |
| 9524 | { |
| 9525 | opclasses[i] = indclass->values[j]; |
| 9526 | found = true; |
| 9527 | break; |
| 9528 | } |
| 9529 | } |
| 9530 | if (!found) |
| 9531 | break; |
| 9532 | } |
| 9533 | |
| 9534 | /* |
| 9535 | * Refuse to use a deferrable unique/primary key. This is per SQL |
| 9536 | * spec, and there would be a lot of interesting semantic problems |
| 9537 | * if we tried to allow it. |
| 9538 | */ |
| 9539 | if (found && !indexStruct->indimmediate) |
| 9540 | { |
| 9541 | /* |
| 9542 | * Remember that we found an otherwise matching index, so that |
| 9543 | * we can generate a more appropriate error message. |
| 9544 | */ |
| 9545 | found_deferrable = true; |
| 9546 | found = false; |
| 9547 | } |
| 9548 | } |
| 9549 | ReleaseSysCache(indexTuple); |
| 9550 | if (found) |
| 9551 | break; |
| 9552 | } |
| 9553 | |
| 9554 | if (!found) |
| 9555 | { |
| 9556 | if (found_deferrable) |
| 9557 | ereport(ERROR, |
| 9558 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
| 9559 | errmsg("cannot use a deferrable unique constraint for referenced table \"%s\"" , |
| 9560 | RelationGetRelationName(pkrel)))); |
| 9561 | else |
| 9562 | ereport(ERROR, |
| 9563 | (errcode(ERRCODE_INVALID_FOREIGN_KEY), |
| 9564 | errmsg("there is no unique constraint matching given keys for referenced table \"%s\"" , |
| 9565 | RelationGetRelationName(pkrel)))); |
| 9566 | } |
| 9567 | |
| 9568 | list_free(indexoidlist); |
| 9569 | |
| 9570 | return indexoid; |
| 9571 | } |
| 9572 | |
| 9573 | /* |
| 9574 | * findFkeyCast - |
| 9575 | * |
| 9576 | * Wrapper around find_coercion_pathway() for ATAddForeignKeyConstraint(). |
| 9577 | * Caller has equal regard for binary coercibility and for an exact match. |
| 9578 | */ |
| 9579 | static CoercionPathType |
| 9580 | findFkeyCast(Oid targetTypeId, Oid sourceTypeId, Oid *funcid) |
| 9581 | { |
| 9582 | CoercionPathType ret; |
| 9583 | |
| 9584 | if (targetTypeId == sourceTypeId) |
| 9585 | { |
| 9586 | ret = COERCION_PATH_RELABELTYPE; |
| 9587 | *funcid = InvalidOid; |
| 9588 | } |
| 9589 | else |
| 9590 | { |
| 9591 | ret = find_coercion_pathway(targetTypeId, sourceTypeId, |
| 9592 | COERCION_IMPLICIT, funcid); |
| 9593 | if (ret == COERCION_PATH_NONE) |
| 9594 | /* A previously-relied-upon cast is now gone. */ |
| 9595 | elog(ERROR, "could not find cast from %u to %u" , |
| 9596 | sourceTypeId, targetTypeId); |
| 9597 | } |
| 9598 | |
| 9599 | return ret; |
| 9600 | } |
| 9601 | |
| 9602 | /* |
| 9603 | * Permissions checks on the referenced table for ADD FOREIGN KEY |
| 9604 | * |
| 9605 | * Note: we have already checked that the user owns the referencing table, |
| 9606 | * else we'd have failed much earlier; no additional checks are needed for it. |
| 9607 | */ |
| 9608 | static void |
| 9609 | checkFkeyPermissions(Relation rel, int16 *attnums, int natts) |
| 9610 | { |
| 9611 | Oid roleid = GetUserId(); |
| 9612 | AclResult aclresult; |
| 9613 | int i; |
| 9614 | |
| 9615 | /* Okay if we have relation-level REFERENCES permission */ |
| 9616 | aclresult = pg_class_aclcheck(RelationGetRelid(rel), roleid, |
| 9617 | ACL_REFERENCES); |
| 9618 | if (aclresult == ACLCHECK_OK) |
| 9619 | return; |
| 9620 | /* Else we must have REFERENCES on each column */ |
| 9621 | for (i = 0; i < natts; i++) |
| 9622 | { |
| 9623 | aclresult = pg_attribute_aclcheck(RelationGetRelid(rel), attnums[i], |
| 9624 | roleid, ACL_REFERENCES); |
| 9625 | if (aclresult != ACLCHECK_OK) |
| 9626 | aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind), |
| 9627 | RelationGetRelationName(rel)); |
| 9628 | } |
| 9629 | } |
| 9630 | |
| 9631 | /* |
| 9632 | * Scan the existing rows in a table to verify they meet a proposed |
| 9633 | * CHECK constraint. |
| 9634 | * |
| 9635 | * The caller must have opened and locked the relation appropriately. |
| 9636 | */ |
| 9637 | static void |
| 9638 | validateCheckConstraint(Relation rel, HeapTuple constrtup) |
| 9639 | { |
| 9640 | EState *estate; |
| 9641 | Datum val; |
| 9642 | char *conbin; |
| 9643 | Expr *origexpr; |
| 9644 | ExprState *exprstate; |
| 9645 | TableScanDesc scan; |
| 9646 | ExprContext *econtext; |
| 9647 | MemoryContext oldcxt; |
| 9648 | TupleTableSlot *slot; |
| 9649 | Form_pg_constraint constrForm; |
| 9650 | bool isnull; |
| 9651 | Snapshot snapshot; |
| 9652 | |
| 9653 | /* |
| 9654 | * VALIDATE CONSTRAINT is a no-op for foreign tables and partitioned |
| 9655 | * tables. |
| 9656 | */ |
| 9657 | if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE || |
| 9658 | rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) |
| 9659 | return; |
| 9660 | |
| 9661 | constrForm = (Form_pg_constraint) GETSTRUCT(constrtup); |
| 9662 | |
| 9663 | estate = CreateExecutorState(); |
| 9664 | |
| 9665 | /* |
| 9666 | * XXX this tuple doesn't really come from a syscache, but this doesn't |
| 9667 | * matter to SysCacheGetAttr, because it only wants to be able to fetch |
| 9668 | * the tupdesc |
| 9669 | */ |
| 9670 | val = SysCacheGetAttr(CONSTROID, constrtup, Anum_pg_constraint_conbin, |
| 9671 | &isnull); |
| 9672 | if (isnull) |
| 9673 | elog(ERROR, "null conbin for constraint %u" , |
| 9674 | constrForm->oid); |
| 9675 | conbin = TextDatumGetCString(val); |
| 9676 | origexpr = (Expr *) stringToNode(conbin); |
| 9677 | exprstate = ExecPrepareExpr(origexpr, estate); |
| 9678 | |
| 9679 | econtext = GetPerTupleExprContext(estate); |
| 9680 | slot = table_slot_create(rel, NULL); |
| 9681 | econtext->ecxt_scantuple = slot; |
| 9682 | |
| 9683 | snapshot = RegisterSnapshot(GetLatestSnapshot()); |
| 9684 | scan = table_beginscan(rel, snapshot, 0, NULL); |
| 9685 | |
| 9686 | /* |
| 9687 | * Switch to per-tuple memory context and reset it for each tuple |
| 9688 | * produced, so we don't leak memory. |
| 9689 | */ |
| 9690 | oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); |
| 9691 | |
| 9692 | while (table_scan_getnextslot(scan, ForwardScanDirection, slot)) |
| 9693 | { |
| 9694 | if (!ExecCheck(exprstate, econtext)) |
| 9695 | ereport(ERROR, |
| 9696 | (errcode(ERRCODE_CHECK_VIOLATION), |
| 9697 | errmsg("check constraint \"%s\" is violated by some row" , |
| 9698 | NameStr(constrForm->conname)), |
| 9699 | errtableconstraint(rel, NameStr(constrForm->conname)))); |
| 9700 | |
| 9701 | ResetExprContext(econtext); |
| 9702 | } |
| 9703 | |
| 9704 | MemoryContextSwitchTo(oldcxt); |
| 9705 | table_endscan(scan); |
| 9706 | UnregisterSnapshot(snapshot); |
| 9707 | ExecDropSingleTupleTableSlot(slot); |
| 9708 | FreeExecutorState(estate); |
| 9709 | } |
| 9710 | |
| 9711 | /* |
| 9712 | * Scan the existing rows in a table to verify they meet a proposed FK |
| 9713 | * constraint. |
| 9714 | * |
| 9715 | * Caller must have opened and locked both relations appropriately. |
| 9716 | */ |
| 9717 | static void |
| 9718 | validateForeignKeyConstraint(char *conname, |
| 9719 | Relation rel, |
| 9720 | Relation pkrel, |
| 9721 | Oid pkindOid, |
| 9722 | Oid constraintOid) |
| 9723 | { |
| 9724 | TupleTableSlot *slot; |
| 9725 | TableScanDesc scan; |
| 9726 | Trigger trig; |
| 9727 | Snapshot snapshot; |
| 9728 | MemoryContext oldcxt; |
| 9729 | MemoryContext perTupCxt; |
| 9730 | |
| 9731 | ereport(DEBUG1, |
| 9732 | (errmsg("validating foreign key constraint \"%s\"" , conname))); |
| 9733 | |
| 9734 | /* |
| 9735 | * Build a trigger call structure; we'll need it either way. |
| 9736 | */ |
| 9737 | MemSet(&trig, 0, sizeof(trig)); |
| 9738 | trig.tgoid = InvalidOid; |
| 9739 | trig.tgname = conname; |
| 9740 | trig.tgenabled = TRIGGER_FIRES_ON_ORIGIN; |
| 9741 | trig.tgisinternal = true; |
| 9742 | trig.tgconstrrelid = RelationGetRelid(pkrel); |
| 9743 | trig.tgconstrindid = pkindOid; |
| 9744 | trig.tgconstraint = constraintOid; |
| 9745 | trig.tgdeferrable = false; |
| 9746 | trig.tginitdeferred = false; |
| 9747 | /* we needn't fill in remaining fields */ |
| 9748 | |
| 9749 | /* |
| 9750 | * See if we can do it with a single LEFT JOIN query. A false result |
| 9751 | * indicates we must proceed with the fire-the-trigger method. |
| 9752 | */ |
| 9753 | if (RI_Initial_Check(&trig, rel, pkrel)) |
| 9754 | return; |
| 9755 | |
| 9756 | /* |
| 9757 | * Scan through each tuple, calling RI_FKey_check_ins (insert trigger) as |
| 9758 | * if that tuple had just been inserted. If any of those fail, it should |
| 9759 | * ereport(ERROR) and that's that. |
| 9760 | */ |
| 9761 | snapshot = RegisterSnapshot(GetLatestSnapshot()); |
| 9762 | slot = table_slot_create(rel, NULL); |
| 9763 | scan = table_beginscan(rel, snapshot, 0, NULL); |
| 9764 | |
| 9765 | perTupCxt = AllocSetContextCreate(CurrentMemoryContext, |
| 9766 | "validateForeignKeyConstraint" , |
| 9767 | ALLOCSET_SMALL_SIZES); |
| 9768 | oldcxt = MemoryContextSwitchTo(perTupCxt); |
| 9769 | |
| 9770 | while (table_scan_getnextslot(scan, ForwardScanDirection, slot)) |
| 9771 | { |
| 9772 | LOCAL_FCINFO(fcinfo, 0); |
| 9773 | TriggerData trigdata; |
| 9774 | |
| 9775 | CHECK_FOR_INTERRUPTS(); |
| 9776 | |
| 9777 | /* |
| 9778 | * Make a call to the trigger function |
| 9779 | * |
| 9780 | * No parameters are passed, but we do set a context |
| 9781 | */ |
| 9782 | MemSet(fcinfo, 0, SizeForFunctionCallInfo(0)); |
| 9783 | |
| 9784 | /* |
| 9785 | * We assume RI_FKey_check_ins won't look at flinfo... |
| 9786 | */ |
| 9787 | trigdata.type = T_TriggerData; |
| 9788 | trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW; |
| 9789 | trigdata.tg_relation = rel; |
| 9790 | trigdata.tg_trigtuple = ExecFetchSlotHeapTuple(slot, false, NULL); |
| 9791 | trigdata.tg_trigslot = slot; |
| 9792 | trigdata.tg_newtuple = NULL; |
| 9793 | trigdata.tg_newslot = NULL; |
| 9794 | trigdata.tg_trigger = &trig; |
| 9795 | |
| 9796 | fcinfo->context = (Node *) &trigdata; |
| 9797 | |
| 9798 | RI_FKey_check_ins(fcinfo); |
| 9799 | |
| 9800 | MemoryContextReset(perTupCxt); |
| 9801 | } |
| 9802 | |
| 9803 | MemoryContextSwitchTo(oldcxt); |
| 9804 | MemoryContextDelete(perTupCxt); |
| 9805 | table_endscan(scan); |
| 9806 | UnregisterSnapshot(snapshot); |
| 9807 | ExecDropSingleTupleTableSlot(slot); |
| 9808 | } |
| 9809 | |
| 9810 | static void |
| 9811 | CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint, |
| 9812 | Oid constraintOid, Oid indexOid, bool on_insert) |
| 9813 | { |
| 9814 | CreateTrigStmt *fk_trigger; |
| 9815 | |
| 9816 | /* |
| 9817 | * Note: for a self-referential FK (referencing and referenced tables are |
| 9818 | * the same), it is important that the ON UPDATE action fires before the |
| 9819 | * CHECK action, since both triggers will fire on the same row during an |
| 9820 | * UPDATE event; otherwise the CHECK trigger will be checking a non-final |
| 9821 | * state of the row. Triggers fire in name order, so we ensure this by |
| 9822 | * using names like "RI_ConstraintTrigger_a_NNNN" for the action triggers |
| 9823 | * and "RI_ConstraintTrigger_c_NNNN" for the check triggers. |
| 9824 | */ |
| 9825 | fk_trigger = makeNode(CreateTrigStmt); |
| 9826 | fk_trigger->trigname = "RI_ConstraintTrigger_c" ; |
| 9827 | fk_trigger->relation = NULL; |
| 9828 | fk_trigger->row = true; |
| 9829 | fk_trigger->timing = TRIGGER_TYPE_AFTER; |
| 9830 | |
| 9831 | /* Either ON INSERT or ON UPDATE */ |
| 9832 | if (on_insert) |
| 9833 | { |
| 9834 | fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins" ); |
| 9835 | fk_trigger->events = TRIGGER_TYPE_INSERT; |
| 9836 | } |
| 9837 | else |
| 9838 | { |
| 9839 | fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd" ); |
| 9840 | fk_trigger->events = TRIGGER_TYPE_UPDATE; |
| 9841 | } |
| 9842 | |
| 9843 | fk_trigger->columns = NIL; |
| 9844 | fk_trigger->transitionRels = NIL; |
| 9845 | fk_trigger->whenClause = NULL; |
| 9846 | fk_trigger->isconstraint = true; |
| 9847 | fk_trigger->deferrable = fkconstraint->deferrable; |
| 9848 | fk_trigger->initdeferred = fkconstraint->initdeferred; |
| 9849 | fk_trigger->constrrel = NULL; |
| 9850 | fk_trigger->args = NIL; |
| 9851 | |
| 9852 | (void) CreateTrigger(fk_trigger, NULL, myRelOid, refRelOid, constraintOid, |
| 9853 | indexOid, InvalidOid, InvalidOid, NULL, true, false); |
| 9854 | |
| 9855 | /* Make changes-so-far visible */ |
| 9856 | CommandCounterIncrement(); |
| 9857 | } |
| 9858 | |
| 9859 | /* |
| 9860 | * createForeignKeyActionTriggers |
| 9861 | * Create the referenced-side "action" triggers that implement a foreign |
| 9862 | * key. |
| 9863 | */ |
| 9864 | static void |
| 9865 | createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint, |
| 9866 | Oid constraintOid, Oid indexOid) |
| 9867 | { |
| 9868 | CreateTrigStmt *fk_trigger; |
| 9869 | |
| 9870 | /* |
| 9871 | * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON |
| 9872 | * DELETE action on the referenced table. |
| 9873 | */ |
| 9874 | fk_trigger = makeNode(CreateTrigStmt); |
| 9875 | fk_trigger->trigname = "RI_ConstraintTrigger_a" ; |
| 9876 | fk_trigger->relation = NULL; |
| 9877 | fk_trigger->row = true; |
| 9878 | fk_trigger->timing = TRIGGER_TYPE_AFTER; |
| 9879 | fk_trigger->events = TRIGGER_TYPE_DELETE; |
| 9880 | fk_trigger->columns = NIL; |
| 9881 | fk_trigger->transitionRels = NIL; |
| 9882 | fk_trigger->whenClause = NULL; |
| 9883 | fk_trigger->isconstraint = true; |
| 9884 | fk_trigger->constrrel = NULL; |
| 9885 | switch (fkconstraint->fk_del_action) |
| 9886 | { |
| 9887 | case FKCONSTR_ACTION_NOACTION: |
| 9888 | fk_trigger->deferrable = fkconstraint->deferrable; |
| 9889 | fk_trigger->initdeferred = fkconstraint->initdeferred; |
| 9890 | fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del" ); |
| 9891 | break; |
| 9892 | case FKCONSTR_ACTION_RESTRICT: |
| 9893 | fk_trigger->deferrable = false; |
| 9894 | fk_trigger->initdeferred = false; |
| 9895 | fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del" ); |
| 9896 | break; |
| 9897 | case FKCONSTR_ACTION_CASCADE: |
| 9898 | fk_trigger->deferrable = false; |
| 9899 | fk_trigger->initdeferred = false; |
| 9900 | fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del" ); |
| 9901 | break; |
| 9902 | case FKCONSTR_ACTION_SETNULL: |
| 9903 | fk_trigger->deferrable = false; |
| 9904 | fk_trigger->initdeferred = false; |
| 9905 | fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del" ); |
| 9906 | break; |
| 9907 | case FKCONSTR_ACTION_SETDEFAULT: |
| 9908 | fk_trigger->deferrable = false; |
| 9909 | fk_trigger->initdeferred = false; |
| 9910 | fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del" ); |
| 9911 | break; |
| 9912 | default: |
| 9913 | elog(ERROR, "unrecognized FK action type: %d" , |
| 9914 | (int) fkconstraint->fk_del_action); |
| 9915 | break; |
| 9916 | } |
| 9917 | fk_trigger->args = NIL; |
| 9918 | |
| 9919 | (void) CreateTrigger(fk_trigger, NULL, refRelOid, RelationGetRelid(rel), |
| 9920 | constraintOid, |
| 9921 | indexOid, InvalidOid, InvalidOid, NULL, true, false); |
| 9922 | |
| 9923 | /* Make changes-so-far visible */ |
| 9924 | CommandCounterIncrement(); |
| 9925 | |
| 9926 | /* |
| 9927 | * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON |
| 9928 | * UPDATE action on the referenced table. |
| 9929 | */ |
| 9930 | fk_trigger = makeNode(CreateTrigStmt); |
| 9931 | fk_trigger->trigname = "RI_ConstraintTrigger_a" ; |
| 9932 | fk_trigger->relation = NULL; |
| 9933 | fk_trigger->row = true; |
| 9934 | fk_trigger->timing = TRIGGER_TYPE_AFTER; |
| 9935 | fk_trigger->events = TRIGGER_TYPE_UPDATE; |
| 9936 | fk_trigger->columns = NIL; |
| 9937 | fk_trigger->transitionRels = NIL; |
| 9938 | fk_trigger->whenClause = NULL; |
| 9939 | fk_trigger->isconstraint = true; |
| 9940 | fk_trigger->constrrel = NULL; |
| 9941 | switch (fkconstraint->fk_upd_action) |
| 9942 | { |
| 9943 | case FKCONSTR_ACTION_NOACTION: |
| 9944 | fk_trigger->deferrable = fkconstraint->deferrable; |
| 9945 | fk_trigger->initdeferred = fkconstraint->initdeferred; |
| 9946 | fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd" ); |
| 9947 | break; |
| 9948 | case FKCONSTR_ACTION_RESTRICT: |
| 9949 | fk_trigger->deferrable = false; |
| 9950 | fk_trigger->initdeferred = false; |
| 9951 | fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd" ); |
| 9952 | break; |
| 9953 | case FKCONSTR_ACTION_CASCADE: |
| 9954 | fk_trigger->deferrable = false; |
| 9955 | fk_trigger->initdeferred = false; |
| 9956 | fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd" ); |
| 9957 | break; |
| 9958 | case FKCONSTR_ACTION_SETNULL: |
| 9959 | fk_trigger->deferrable = false; |
| 9960 | fk_trigger->initdeferred = false; |
| 9961 | fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd" ); |
| 9962 | break; |
| 9963 | case FKCONSTR_ACTION_SETDEFAULT: |
| 9964 | fk_trigger->deferrable = false; |
| 9965 | fk_trigger->initdeferred = false; |
| 9966 | fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd" ); |
| 9967 | break; |
| 9968 | default: |
| 9969 | elog(ERROR, "unrecognized FK action type: %d" , |
| 9970 | (int) fkconstraint->fk_upd_action); |
| 9971 | break; |
| 9972 | } |
| 9973 | fk_trigger->args = NIL; |
| 9974 | |
| 9975 | (void) CreateTrigger(fk_trigger, NULL, refRelOid, RelationGetRelid(rel), |
| 9976 | constraintOid, |
| 9977 | indexOid, InvalidOid, InvalidOid, NULL, true, false); |
| 9978 | } |
| 9979 | |
| 9980 | /* |
| 9981 | * createForeignKeyCheckTriggers |
| 9982 | * Create the referencing-side "check" triggers that implement a foreign |
| 9983 | * key. |
| 9984 | */ |
| 9985 | static void |
| 9986 | createForeignKeyCheckTriggers(Oid myRelOid, Oid refRelOid, |
| 9987 | Constraint *fkconstraint, Oid constraintOid, |
| 9988 | Oid indexOid) |
| 9989 | { |
| 9990 | CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint, constraintOid, |
| 9991 | indexOid, true); |
| 9992 | CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint, constraintOid, |
| 9993 | indexOid, false); |
| 9994 | } |
| 9995 | |
| 9996 | /* |
| 9997 | * ALTER TABLE DROP CONSTRAINT |
| 9998 | * |
| 9999 | * Like DROP COLUMN, we can't use the normal ALTER TABLE recursion mechanism. |
| 10000 | */ |
| 10001 | static void |
| 10002 | ATExecDropConstraint(Relation rel, const char *constrName, |
| 10003 | DropBehavior behavior, |
| 10004 | bool recurse, bool recursing, |
| 10005 | bool missing_ok, LOCKMODE lockmode) |
| 10006 | { |
| 10007 | List *children; |
| 10008 | ListCell *child; |
| 10009 | Relation conrel; |
| 10010 | Form_pg_constraint con; |
| 10011 | SysScanDesc scan; |
| 10012 | ScanKeyData skey[3]; |
| 10013 | HeapTuple tuple; |
| 10014 | bool found = false; |
| 10015 | bool is_no_inherit_constraint = false; |
| 10016 | char contype; |
| 10017 | |
| 10018 | /* At top level, permission check was done in ATPrepCmd, else do it */ |
| 10019 | if (recursing) |
| 10020 | ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); |
| 10021 | |
| 10022 | conrel = table_open(ConstraintRelationId, RowExclusiveLock); |
| 10023 | |
| 10024 | /* |
| 10025 | * Find and drop the target constraint |
| 10026 | */ |
| 10027 | ScanKeyInit(&skey[0], |
| 10028 | Anum_pg_constraint_conrelid, |
| 10029 | BTEqualStrategyNumber, F_OIDEQ, |
| 10030 | ObjectIdGetDatum(RelationGetRelid(rel))); |
| 10031 | ScanKeyInit(&skey[1], |
| 10032 | Anum_pg_constraint_contypid, |
| 10033 | BTEqualStrategyNumber, F_OIDEQ, |
| 10034 | ObjectIdGetDatum(InvalidOid)); |
| 10035 | ScanKeyInit(&skey[2], |
| 10036 | Anum_pg_constraint_conname, |
| 10037 | BTEqualStrategyNumber, F_NAMEEQ, |
| 10038 | CStringGetDatum(constrName)); |
| 10039 | scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId, |
| 10040 | true, NULL, 3, skey); |
| 10041 | |
| 10042 | /* There can be at most one matching row */ |
| 10043 | if (HeapTupleIsValid(tuple = systable_getnext(scan))) |
| 10044 | { |
| 10045 | ObjectAddress conobj; |
| 10046 | |
| 10047 | con = (Form_pg_constraint) GETSTRUCT(tuple); |
| 10048 | |
| 10049 | /* Don't drop inherited constraints */ |
| 10050 | if (con->coninhcount > 0 && !recursing) |
| 10051 | ereport(ERROR, |
| 10052 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 10053 | errmsg("cannot drop inherited constraint \"%s\" of relation \"%s\"" , |
| 10054 | constrName, RelationGetRelationName(rel)))); |
| 10055 | |
| 10056 | is_no_inherit_constraint = con->connoinherit; |
| 10057 | contype = con->contype; |
| 10058 | |
| 10059 | /* |
| 10060 | * If it's a foreign-key constraint, we'd better lock the referenced |
| 10061 | * table and check that that's not in use, just as we've already done |
| 10062 | * for the constrained table (else we might, eg, be dropping a trigger |
| 10063 | * that has unfired events). But we can/must skip that in the |
| 10064 | * self-referential case. |
| 10065 | */ |
| 10066 | if (contype == CONSTRAINT_FOREIGN && |
| 10067 | con->confrelid != RelationGetRelid(rel)) |
| 10068 | { |
| 10069 | Relation frel; |
| 10070 | |
| 10071 | /* Must match lock taken by RemoveTriggerById: */ |
| 10072 | frel = table_open(con->confrelid, AccessExclusiveLock); |
| 10073 | CheckTableNotInUse(frel, "ALTER TABLE" ); |
| 10074 | table_close(frel, NoLock); |
| 10075 | } |
| 10076 | |
| 10077 | /* |
| 10078 | * Perform the actual constraint deletion |
| 10079 | */ |
| 10080 | conobj.classId = ConstraintRelationId; |
| 10081 | conobj.objectId = con->oid; |
| 10082 | conobj.objectSubId = 0; |
| 10083 | |
| 10084 | performDeletion(&conobj, behavior, 0); |
| 10085 | |
| 10086 | found = true; |
| 10087 | } |
| 10088 | |
| 10089 | systable_endscan(scan); |
| 10090 | |
| 10091 | if (!found) |
| 10092 | { |
| 10093 | if (!missing_ok) |
| 10094 | { |
| 10095 | ereport(ERROR, |
| 10096 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
| 10097 | errmsg("constraint \"%s\" of relation \"%s\" does not exist" , |
| 10098 | constrName, RelationGetRelationName(rel)))); |
| 10099 | } |
| 10100 | else |
| 10101 | { |
| 10102 | ereport(NOTICE, |
| 10103 | (errmsg("constraint \"%s\" of relation \"%s\" does not exist, skipping" , |
| 10104 | constrName, RelationGetRelationName(rel)))); |
| 10105 | table_close(conrel, RowExclusiveLock); |
| 10106 | return; |
| 10107 | } |
| 10108 | } |
| 10109 | |
| 10110 | /* |
| 10111 | * For partitioned tables, non-CHECK inherited constraints are dropped via |
| 10112 | * the dependency mechanism, so we're done here. |
| 10113 | */ |
| 10114 | if (contype != CONSTRAINT_CHECK && |
| 10115 | rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) |
| 10116 | { |
| 10117 | table_close(conrel, RowExclusiveLock); |
| 10118 | return; |
| 10119 | } |
| 10120 | |
| 10121 | /* |
| 10122 | * Propagate to children as appropriate. Unlike most other ALTER |
| 10123 | * routines, we have to do this one level of recursion at a time; we can't |
| 10124 | * use find_all_inheritors to do it in one pass. |
| 10125 | */ |
| 10126 | if (!is_no_inherit_constraint) |
| 10127 | children = find_inheritance_children(RelationGetRelid(rel), lockmode); |
| 10128 | else |
| 10129 | children = NIL; |
| 10130 | |
| 10131 | /* |
| 10132 | * For a partitioned table, if partitions exist and we are told not to |
| 10133 | * recurse, it's a user error. It doesn't make sense to have a constraint |
| 10134 | * be defined only on the parent, especially if it's a partitioned table. |
| 10135 | */ |
| 10136 | if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && |
| 10137 | children != NIL && !recurse) |
| 10138 | ereport(ERROR, |
| 10139 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 10140 | errmsg("cannot remove constraint from only the partitioned table when partitions exist" ), |
| 10141 | errhint("Do not specify the ONLY keyword." ))); |
| 10142 | |
| 10143 | foreach(child, children) |
| 10144 | { |
| 10145 | Oid childrelid = lfirst_oid(child); |
| 10146 | Relation childrel; |
| 10147 | HeapTuple copy_tuple; |
| 10148 | |
| 10149 | /* find_inheritance_children already got lock */ |
| 10150 | childrel = table_open(childrelid, NoLock); |
| 10151 | CheckTableNotInUse(childrel, "ALTER TABLE" ); |
| 10152 | |
| 10153 | ScanKeyInit(&skey[0], |
| 10154 | Anum_pg_constraint_conrelid, |
| 10155 | BTEqualStrategyNumber, F_OIDEQ, |
| 10156 | ObjectIdGetDatum(childrelid)); |
| 10157 | ScanKeyInit(&skey[1], |
| 10158 | Anum_pg_constraint_contypid, |
| 10159 | BTEqualStrategyNumber, F_OIDEQ, |
| 10160 | ObjectIdGetDatum(InvalidOid)); |
| 10161 | ScanKeyInit(&skey[2], |
| 10162 | Anum_pg_constraint_conname, |
| 10163 | BTEqualStrategyNumber, F_NAMEEQ, |
| 10164 | CStringGetDatum(constrName)); |
| 10165 | scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId, |
| 10166 | true, NULL, 3, skey); |
| 10167 | |
| 10168 | /* There can be at most one matching row */ |
| 10169 | if (!HeapTupleIsValid(tuple = systable_getnext(scan))) |
| 10170 | ereport(ERROR, |
| 10171 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
| 10172 | errmsg("constraint \"%s\" of relation \"%s\" does not exist" , |
| 10173 | constrName, |
| 10174 | RelationGetRelationName(childrel)))); |
| 10175 | |
| 10176 | copy_tuple = heap_copytuple(tuple); |
| 10177 | |
| 10178 | systable_endscan(scan); |
| 10179 | |
| 10180 | con = (Form_pg_constraint) GETSTRUCT(copy_tuple); |
| 10181 | |
| 10182 | /* Right now only CHECK constraints can be inherited */ |
| 10183 | if (con->contype != CONSTRAINT_CHECK) |
| 10184 | elog(ERROR, "inherited constraint is not a CHECK constraint" ); |
| 10185 | |
| 10186 | if (con->coninhcount <= 0) /* shouldn't happen */ |
| 10187 | elog(ERROR, "relation %u has non-inherited constraint \"%s\"" , |
| 10188 | childrelid, constrName); |
| 10189 | |
| 10190 | if (recurse) |
| 10191 | { |
| 10192 | /* |
| 10193 | * If the child constraint has other definition sources, just |
| 10194 | * decrement its inheritance count; if not, recurse to delete it. |
| 10195 | */ |
| 10196 | if (con->coninhcount == 1 && !con->conislocal) |
| 10197 | { |
| 10198 | /* Time to delete this child constraint, too */ |
| 10199 | ATExecDropConstraint(childrel, constrName, behavior, |
| 10200 | true, true, |
| 10201 | false, lockmode); |
| 10202 | } |
| 10203 | else |
| 10204 | { |
| 10205 | /* Child constraint must survive my deletion */ |
| 10206 | con->coninhcount--; |
| 10207 | CatalogTupleUpdate(conrel, ©_tuple->t_self, copy_tuple); |
| 10208 | |
| 10209 | /* Make update visible */ |
| 10210 | CommandCounterIncrement(); |
| 10211 | } |
| 10212 | } |
| 10213 | else |
| 10214 | { |
| 10215 | /* |
| 10216 | * If we were told to drop ONLY in this table (no recursion), we |
| 10217 | * need to mark the inheritors' constraints as locally defined |
| 10218 | * rather than inherited. |
| 10219 | */ |
| 10220 | con->coninhcount--; |
| 10221 | con->conislocal = true; |
| 10222 | |
| 10223 | CatalogTupleUpdate(conrel, ©_tuple->t_self, copy_tuple); |
| 10224 | |
| 10225 | /* Make update visible */ |
| 10226 | CommandCounterIncrement(); |
| 10227 | } |
| 10228 | |
| 10229 | heap_freetuple(copy_tuple); |
| 10230 | |
| 10231 | table_close(childrel, NoLock); |
| 10232 | } |
| 10233 | |
| 10234 | table_close(conrel, RowExclusiveLock); |
| 10235 | } |
| 10236 | |
| 10237 | /* |
| 10238 | * ALTER COLUMN TYPE |
| 10239 | */ |
| 10240 | static void |
| 10241 | ATPrepAlterColumnType(List **wqueue, |
| 10242 | AlteredTableInfo *tab, Relation rel, |
| 10243 | bool recurse, bool recursing, |
| 10244 | AlterTableCmd *cmd, LOCKMODE lockmode) |
| 10245 | { |
| 10246 | char *colName = cmd->name; |
| 10247 | ColumnDef *def = (ColumnDef *) cmd->def; |
| 10248 | TypeName *typeName = def->typeName; |
| 10249 | Node *transform = def->cooked_default; |
| 10250 | HeapTuple tuple; |
| 10251 | Form_pg_attribute attTup; |
| 10252 | AttrNumber attnum; |
| 10253 | Oid targettype; |
| 10254 | int32 targettypmod; |
| 10255 | Oid targetcollid; |
| 10256 | NewColumnValue *newval; |
| 10257 | ParseState *pstate = make_parsestate(NULL); |
| 10258 | AclResult aclresult; |
| 10259 | bool is_expr; |
| 10260 | |
| 10261 | if (rel->rd_rel->reloftype && !recursing) |
| 10262 | ereport(ERROR, |
| 10263 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 10264 | errmsg("cannot alter column type of typed table" ))); |
| 10265 | |
| 10266 | /* lookup the attribute so we can check inheritance status */ |
| 10267 | tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName); |
| 10268 | if (!HeapTupleIsValid(tuple)) |
| 10269 | ereport(ERROR, |
| 10270 | (errcode(ERRCODE_UNDEFINED_COLUMN), |
| 10271 | errmsg("column \"%s\" of relation \"%s\" does not exist" , |
| 10272 | colName, RelationGetRelationName(rel)))); |
| 10273 | attTup = (Form_pg_attribute) GETSTRUCT(tuple); |
| 10274 | attnum = attTup->attnum; |
| 10275 | |
| 10276 | /* Can't alter a system attribute */ |
| 10277 | if (attnum <= 0) |
| 10278 | ereport(ERROR, |
| 10279 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 10280 | errmsg("cannot alter system column \"%s\"" , |
| 10281 | colName))); |
| 10282 | |
| 10283 | /* |
| 10284 | * Don't alter inherited columns. At outer level, there had better not be |
| 10285 | * any inherited definition; when recursing, we assume this was checked at |
| 10286 | * the parent level (see below). |
| 10287 | */ |
| 10288 | if (attTup->attinhcount > 0 && !recursing) |
| 10289 | ereport(ERROR, |
| 10290 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 10291 | errmsg("cannot alter inherited column \"%s\"" , |
| 10292 | colName))); |
| 10293 | |
| 10294 | /* Don't alter columns used in the partition key */ |
| 10295 | if (has_partition_attrs(rel, |
| 10296 | bms_make_singleton(attnum - FirstLowInvalidHeapAttributeNumber), |
| 10297 | &is_expr)) |
| 10298 | ereport(ERROR, |
| 10299 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 10300 | errmsg("cannot alter column \"%s\" because it is part of the partition key of relation \"%s\"" , |
| 10301 | colName, RelationGetRelationName(rel)))); |
| 10302 | |
| 10303 | /* Look up the target type */ |
| 10304 | typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod); |
| 10305 | |
| 10306 | aclresult = pg_type_aclcheck(targettype, GetUserId(), ACL_USAGE); |
| 10307 | if (aclresult != ACLCHECK_OK) |
| 10308 | aclcheck_error_type(aclresult, targettype); |
| 10309 | |
| 10310 | /* And the collation */ |
| 10311 | targetcollid = GetColumnDefCollation(NULL, def, targettype); |
| 10312 | |
| 10313 | /* make sure datatype is legal for a column */ |
| 10314 | CheckAttributeType(colName, targettype, targetcollid, |
| 10315 | list_make1_oid(rel->rd_rel->reltype), |
| 10316 | 0); |
| 10317 | |
| 10318 | if (tab->relkind == RELKIND_RELATION || |
| 10319 | tab->relkind == RELKIND_PARTITIONED_TABLE) |
| 10320 | { |
| 10321 | /* |
| 10322 | * Set up an expression to transform the old data value to the new |
| 10323 | * type. If a USING option was given, use the expression as |
| 10324 | * transformed by transformAlterTableStmt, else just take the old |
| 10325 | * value and try to coerce it. We do this first so that type |
| 10326 | * incompatibility can be detected before we waste effort, and because |
| 10327 | * we need the expression to be parsed against the original table row |
| 10328 | * type. |
| 10329 | */ |
| 10330 | if (!transform) |
| 10331 | { |
| 10332 | transform = (Node *) makeVar(1, attnum, |
| 10333 | attTup->atttypid, attTup->atttypmod, |
| 10334 | attTup->attcollation, |
| 10335 | 0); |
| 10336 | } |
| 10337 | |
| 10338 | transform = coerce_to_target_type(pstate, |
| 10339 | transform, exprType(transform), |
| 10340 | targettype, targettypmod, |
| 10341 | COERCION_ASSIGNMENT, |
| 10342 | COERCE_IMPLICIT_CAST, |
| 10343 | -1); |
| 10344 | if (transform == NULL) |
| 10345 | { |
| 10346 | /* error text depends on whether USING was specified or not */ |
| 10347 | if (def->cooked_default != NULL) |
| 10348 | ereport(ERROR, |
| 10349 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
| 10350 | errmsg("result of USING clause for column \"%s\"" |
| 10351 | " cannot be cast automatically to type %s" , |
| 10352 | colName, format_type_be(targettype)), |
| 10353 | errhint("You might need to add an explicit cast." ))); |
| 10354 | else |
| 10355 | ereport(ERROR, |
| 10356 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
| 10357 | errmsg("column \"%s\" cannot be cast automatically to type %s" , |
| 10358 | colName, format_type_be(targettype)), |
| 10359 | /* translator: USING is SQL, don't translate it */ |
| 10360 | errhint("You might need to specify \"USING %s::%s\"." , |
| 10361 | quote_identifier(colName), |
| 10362 | format_type_with_typemod(targettype, |
| 10363 | targettypmod)))); |
| 10364 | } |
| 10365 | |
| 10366 | /* Fix collations after all else */ |
| 10367 | assign_expr_collations(pstate, transform); |
| 10368 | |
| 10369 | /* Plan the expr now so we can accurately assess the need to rewrite. */ |
| 10370 | transform = (Node *) expression_planner((Expr *) transform); |
| 10371 | |
| 10372 | /* |
| 10373 | * Add a work queue item to make ATRewriteTable update the column |
| 10374 | * contents. |
| 10375 | */ |
| 10376 | newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue)); |
| 10377 | newval->attnum = attnum; |
| 10378 | newval->expr = (Expr *) transform; |
| 10379 | |
| 10380 | tab->newvals = lappend(tab->newvals, newval); |
| 10381 | if (ATColumnChangeRequiresRewrite(transform, attnum)) |
| 10382 | tab->rewrite |= AT_REWRITE_COLUMN_REWRITE; |
| 10383 | } |
| 10384 | else if (transform) |
| 10385 | ereport(ERROR, |
| 10386 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 10387 | errmsg("\"%s\" is not a table" , |
| 10388 | RelationGetRelationName(rel)))); |
| 10389 | |
| 10390 | if (tab->relkind == RELKIND_COMPOSITE_TYPE || |
| 10391 | tab->relkind == RELKIND_FOREIGN_TABLE) |
| 10392 | { |
| 10393 | /* |
| 10394 | * For composite types, do this check now. Tables will check it later |
| 10395 | * when the table is being rewritten. |
| 10396 | */ |
| 10397 | find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL); |
| 10398 | } |
| 10399 | |
| 10400 | ReleaseSysCache(tuple); |
| 10401 | |
| 10402 | /* |
| 10403 | * Recurse manually by queueing a new command for each child, if |
| 10404 | * necessary. We cannot apply ATSimpleRecursion here because we need to |
| 10405 | * remap attribute numbers in the USING expression, if any. |
| 10406 | * |
| 10407 | * If we are told not to recurse, there had better not be any child |
| 10408 | * tables; else the alter would put them out of step. |
| 10409 | */ |
| 10410 | if (recurse) |
| 10411 | { |
| 10412 | Oid relid = RelationGetRelid(rel); |
| 10413 | List *child_oids, |
| 10414 | *child_numparents; |
| 10415 | ListCell *lo, |
| 10416 | *li; |
| 10417 | |
| 10418 | child_oids = find_all_inheritors(relid, lockmode, |
| 10419 | &child_numparents); |
| 10420 | |
| 10421 | /* |
| 10422 | * find_all_inheritors does the recursive search of the inheritance |
| 10423 | * hierarchy, so all we have to do is process all of the relids in the |
| 10424 | * list that it returns. |
| 10425 | */ |
| 10426 | forboth(lo, child_oids, li, child_numparents) |
| 10427 | { |
| 10428 | Oid childrelid = lfirst_oid(lo); |
| 10429 | int numparents = lfirst_int(li); |
| 10430 | Relation childrel; |
| 10431 | HeapTuple childtuple; |
| 10432 | Form_pg_attribute childattTup; |
| 10433 | |
| 10434 | if (childrelid == relid) |
| 10435 | continue; |
| 10436 | |
| 10437 | /* find_all_inheritors already got lock */ |
| 10438 | childrel = relation_open(childrelid, NoLock); |
| 10439 | CheckTableNotInUse(childrel, "ALTER TABLE" ); |
| 10440 | |
| 10441 | /* |
| 10442 | * Verify that the child doesn't have any inherited definitions of |
| 10443 | * this column that came from outside this inheritance hierarchy. |
| 10444 | * (renameatt makes a similar test, though in a different way |
| 10445 | * because of its different recursion mechanism.) |
| 10446 | */ |
| 10447 | childtuple = SearchSysCacheAttName(RelationGetRelid(childrel), |
| 10448 | colName); |
| 10449 | if (!HeapTupleIsValid(childtuple)) |
| 10450 | ereport(ERROR, |
| 10451 | (errcode(ERRCODE_UNDEFINED_COLUMN), |
| 10452 | errmsg("column \"%s\" of relation \"%s\" does not exist" , |
| 10453 | colName, RelationGetRelationName(childrel)))); |
| 10454 | childattTup = (Form_pg_attribute) GETSTRUCT(childtuple); |
| 10455 | |
| 10456 | if (childattTup->attinhcount > numparents) |
| 10457 | ereport(ERROR, |
| 10458 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 10459 | errmsg("cannot alter inherited column \"%s\" of relation \"%s\"" , |
| 10460 | colName, RelationGetRelationName(childrel)))); |
| 10461 | |
| 10462 | ReleaseSysCache(childtuple); |
| 10463 | |
| 10464 | /* |
| 10465 | * Remap the attribute numbers. If no USING expression was |
| 10466 | * specified, there is no need for this step. |
| 10467 | */ |
| 10468 | if (def->cooked_default) |
| 10469 | { |
| 10470 | AttrNumber *attmap; |
| 10471 | bool found_whole_row; |
| 10472 | |
| 10473 | /* create a copy to scribble on */ |
| 10474 | cmd = copyObject(cmd); |
| 10475 | |
| 10476 | attmap = convert_tuples_by_name_map(RelationGetDescr(childrel), |
| 10477 | RelationGetDescr(rel), |
| 10478 | gettext_noop("could not convert row type" )); |
| 10479 | ((ColumnDef *) cmd->def)->cooked_default = |
| 10480 | map_variable_attnos(def->cooked_default, |
| 10481 | 1, 0, |
| 10482 | attmap, RelationGetDescr(rel)->natts, |
| 10483 | InvalidOid, &found_whole_row); |
| 10484 | if (found_whole_row) |
| 10485 | ereport(ERROR, |
| 10486 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 10487 | errmsg("cannot convert whole-row table reference" ), |
| 10488 | errdetail("USING expression contains a whole-row table reference." ))); |
| 10489 | pfree(attmap); |
| 10490 | } |
| 10491 | ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode); |
| 10492 | relation_close(childrel, NoLock); |
| 10493 | } |
| 10494 | } |
| 10495 | else if (!recursing && |
| 10496 | find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL) |
| 10497 | ereport(ERROR, |
| 10498 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 10499 | errmsg("type of inherited column \"%s\" must be changed in child tables too" , |
| 10500 | colName))); |
| 10501 | |
| 10502 | if (tab->relkind == RELKIND_COMPOSITE_TYPE) |
| 10503 | ATTypedTableRecursion(wqueue, rel, cmd, lockmode); |
| 10504 | } |
| 10505 | |
| 10506 | /* |
| 10507 | * When the data type of a column is changed, a rewrite might not be required |
| 10508 | * if the new type is sufficiently identical to the old one, and the USING |
| 10509 | * clause isn't trying to insert some other value. It's safe to skip the |
| 10510 | * rewrite in these cases: |
| 10511 | * |
| 10512 | * - the old type is binary coercible to the new type |
| 10513 | * - the new type is an unconstrained domain over the old type |
| 10514 | * - {NEW,OLD} or {OLD,NEW} is {timestamptz,timestamp} and the timezone is UTC |
| 10515 | * |
| 10516 | * In the case of a constrained domain, we could get by with scanning the |
| 10517 | * table and checking the constraint rather than actually rewriting it, but we |
| 10518 | * don't currently try to do that. |
| 10519 | */ |
| 10520 | static bool |
| 10521 | ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno) |
| 10522 | { |
| 10523 | Assert(expr != NULL); |
| 10524 | |
| 10525 | for (;;) |
| 10526 | { |
| 10527 | /* only one varno, so no need to check that */ |
| 10528 | if (IsA(expr, Var) &&((Var *) expr)->varattno == varattno) |
| 10529 | return false; |
| 10530 | else if (IsA(expr, RelabelType)) |
| 10531 | expr = (Node *) ((RelabelType *) expr)->arg; |
| 10532 | else if (IsA(expr, CoerceToDomain)) |
| 10533 | { |
| 10534 | CoerceToDomain *d = (CoerceToDomain *) expr; |
| 10535 | |
| 10536 | if (DomainHasConstraints(d->resulttype)) |
| 10537 | return true; |
| 10538 | expr = (Node *) d->arg; |
| 10539 | } |
| 10540 | else if (IsA(expr, FuncExpr)) |
| 10541 | { |
| 10542 | FuncExpr *f = (FuncExpr *) expr; |
| 10543 | |
| 10544 | switch (f->funcid) |
| 10545 | { |
| 10546 | case F_TIMESTAMPTZ_TIMESTAMP: |
| 10547 | case F_TIMESTAMP_TIMESTAMPTZ: |
| 10548 | if (TimestampTimestampTzRequiresRewrite()) |
| 10549 | return true; |
| 10550 | else |
| 10551 | expr = linitial(f->args); |
| 10552 | break; |
| 10553 | default: |
| 10554 | return true; |
| 10555 | } |
| 10556 | } |
| 10557 | else |
| 10558 | return true; |
| 10559 | } |
| 10560 | } |
| 10561 | |
| 10562 | /* |
| 10563 | * ALTER COLUMN .. SET DATA TYPE |
| 10564 | * |
| 10565 | * Return the address of the modified column. |
| 10566 | */ |
| 10567 | static ObjectAddress |
| 10568 | ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, |
| 10569 | AlterTableCmd *cmd, LOCKMODE lockmode) |
| 10570 | { |
| 10571 | char *colName = cmd->name; |
| 10572 | ColumnDef *def = (ColumnDef *) cmd->def; |
| 10573 | TypeName *typeName = def->typeName; |
| 10574 | HeapTuple heapTup; |
| 10575 | Form_pg_attribute attTup, |
| 10576 | attOldTup; |
| 10577 | AttrNumber attnum; |
| 10578 | HeapTuple typeTuple; |
| 10579 | Form_pg_type tform; |
| 10580 | Oid targettype; |
| 10581 | int32 targettypmod; |
| 10582 | Oid targetcollid; |
| 10583 | Node *defaultexpr; |
| 10584 | Relation attrelation; |
| 10585 | Relation depRel; |
| 10586 | ScanKeyData key[3]; |
| 10587 | SysScanDesc scan; |
| 10588 | HeapTuple depTup; |
| 10589 | ObjectAddress address; |
| 10590 | |
| 10591 | /* |
| 10592 | * Clear all the missing values if we're rewriting the table, since this |
| 10593 | * renders them pointless. |
| 10594 | */ |
| 10595 | if (tab->rewrite) |
| 10596 | { |
| 10597 | Relation newrel; |
| 10598 | |
| 10599 | newrel = table_open(RelationGetRelid(rel), NoLock); |
| 10600 | RelationClearMissing(newrel); |
| 10601 | relation_close(newrel, NoLock); |
| 10602 | /* make sure we don't conflict with later attribute modifications */ |
| 10603 | CommandCounterIncrement(); |
| 10604 | } |
| 10605 | |
| 10606 | attrelation = table_open(AttributeRelationId, RowExclusiveLock); |
| 10607 | |
| 10608 | /* Look up the target column */ |
| 10609 | heapTup = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); |
| 10610 | if (!HeapTupleIsValid(heapTup)) /* shouldn't happen */ |
| 10611 | ereport(ERROR, |
| 10612 | (errcode(ERRCODE_UNDEFINED_COLUMN), |
| 10613 | errmsg("column \"%s\" of relation \"%s\" does not exist" , |
| 10614 | colName, RelationGetRelationName(rel)))); |
| 10615 | attTup = (Form_pg_attribute) GETSTRUCT(heapTup); |
| 10616 | attnum = attTup->attnum; |
| 10617 | attOldTup = TupleDescAttr(tab->oldDesc, attnum - 1); |
| 10618 | |
| 10619 | /* Check for multiple ALTER TYPE on same column --- can't cope */ |
| 10620 | if (attTup->atttypid != attOldTup->atttypid || |
| 10621 | attTup->atttypmod != attOldTup->atttypmod) |
| 10622 | ereport(ERROR, |
| 10623 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 10624 | errmsg("cannot alter type of column \"%s\" twice" , |
| 10625 | colName))); |
| 10626 | |
| 10627 | /* Look up the target type (should not fail, since prep found it) */ |
| 10628 | typeTuple = typenameType(NULL, typeName, &targettypmod); |
| 10629 | tform = (Form_pg_type) GETSTRUCT(typeTuple); |
| 10630 | targettype = tform->oid; |
| 10631 | /* And the collation */ |
| 10632 | targetcollid = GetColumnDefCollation(NULL, def, targettype); |
| 10633 | |
| 10634 | /* |
| 10635 | * If there is a default expression for the column, get it and ensure we |
| 10636 | * can coerce it to the new datatype. (We must do this before changing |
| 10637 | * the column type, because build_column_default itself will try to |
| 10638 | * coerce, and will not issue the error message we want if it fails.) |
| 10639 | * |
| 10640 | * We remove any implicit coercion steps at the top level of the old |
| 10641 | * default expression; this has been agreed to satisfy the principle of |
| 10642 | * least surprise. (The conversion to the new column type should act like |
| 10643 | * it started from what the user sees as the stored expression, and the |
| 10644 | * implicit coercions aren't going to be shown.) |
| 10645 | */ |
| 10646 | if (attTup->atthasdef) |
| 10647 | { |
| 10648 | defaultexpr = build_column_default(rel, attnum); |
| 10649 | Assert(defaultexpr); |
| 10650 | defaultexpr = strip_implicit_coercions(defaultexpr); |
| 10651 | defaultexpr = coerce_to_target_type(NULL, /* no UNKNOWN params */ |
| 10652 | defaultexpr, exprType(defaultexpr), |
| 10653 | targettype, targettypmod, |
| 10654 | COERCION_ASSIGNMENT, |
| 10655 | COERCE_IMPLICIT_CAST, |
| 10656 | -1); |
| 10657 | if (defaultexpr == NULL) |
| 10658 | { |
| 10659 | if (attTup->attgenerated) |
| 10660 | ereport(ERROR, |
| 10661 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
| 10662 | errmsg("generation expression for column \"%s\" cannot be cast automatically to type %s" , |
| 10663 | colName, format_type_be(targettype)))); |
| 10664 | else |
| 10665 | ereport(ERROR, |
| 10666 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
| 10667 | errmsg("default for column \"%s\" cannot be cast automatically to type %s" , |
| 10668 | colName, format_type_be(targettype)))); |
| 10669 | } |
| 10670 | } |
| 10671 | else |
| 10672 | defaultexpr = NULL; |
| 10673 | |
| 10674 | /* |
| 10675 | * Find everything that depends on the column (constraints, indexes, etc), |
| 10676 | * and record enough information to let us recreate the objects. |
| 10677 | * |
| 10678 | * The actual recreation does not happen here, but only after we have |
| 10679 | * performed all the individual ALTER TYPE operations. We have to save |
| 10680 | * the info before executing ALTER TYPE, though, else the deparser will |
| 10681 | * get confused. |
| 10682 | */ |
| 10683 | depRel = table_open(DependRelationId, RowExclusiveLock); |
| 10684 | |
| 10685 | ScanKeyInit(&key[0], |
| 10686 | Anum_pg_depend_refclassid, |
| 10687 | BTEqualStrategyNumber, F_OIDEQ, |
| 10688 | ObjectIdGetDatum(RelationRelationId)); |
| 10689 | ScanKeyInit(&key[1], |
| 10690 | Anum_pg_depend_refobjid, |
| 10691 | BTEqualStrategyNumber, F_OIDEQ, |
| 10692 | ObjectIdGetDatum(RelationGetRelid(rel))); |
| 10693 | ScanKeyInit(&key[2], |
| 10694 | Anum_pg_depend_refobjsubid, |
| 10695 | BTEqualStrategyNumber, F_INT4EQ, |
| 10696 | Int32GetDatum((int32) attnum)); |
| 10697 | |
| 10698 | scan = systable_beginscan(depRel, DependReferenceIndexId, true, |
| 10699 | NULL, 3, key); |
| 10700 | |
| 10701 | while (HeapTupleIsValid(depTup = systable_getnext(scan))) |
| 10702 | { |
| 10703 | Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup); |
| 10704 | ObjectAddress foundObject; |
| 10705 | |
| 10706 | /* We don't expect any PIN dependencies on columns */ |
| 10707 | if (foundDep->deptype == DEPENDENCY_PIN) |
| 10708 | elog(ERROR, "cannot alter type of a pinned column" ); |
| 10709 | |
| 10710 | foundObject.classId = foundDep->classid; |
| 10711 | foundObject.objectId = foundDep->objid; |
| 10712 | foundObject.objectSubId = foundDep->objsubid; |
| 10713 | |
| 10714 | switch (getObjectClass(&foundObject)) |
| 10715 | { |
| 10716 | case OCLASS_CLASS: |
| 10717 | { |
| 10718 | char relKind = get_rel_relkind(foundObject.objectId); |
| 10719 | |
| 10720 | if (relKind == RELKIND_INDEX || |
| 10721 | relKind == RELKIND_PARTITIONED_INDEX) |
| 10722 | { |
| 10723 | Assert(foundObject.objectSubId == 0); |
| 10724 | RememberIndexForRebuilding(foundObject.objectId, tab); |
| 10725 | } |
| 10726 | else if (relKind == RELKIND_SEQUENCE) |
| 10727 | { |
| 10728 | /* |
| 10729 | * This must be a SERIAL column's sequence. We need |
| 10730 | * not do anything to it. |
| 10731 | */ |
| 10732 | Assert(foundObject.objectSubId == 0); |
| 10733 | } |
| 10734 | else if (relKind == RELKIND_RELATION && |
| 10735 | foundObject.objectSubId != 0 && |
| 10736 | get_attgenerated(foundObject.objectId, foundObject.objectSubId)) |
| 10737 | { |
| 10738 | /* |
| 10739 | * Changing the type of a column that is used by a |
| 10740 | * generated column is not allowed by SQL standard. It |
| 10741 | * might be doable with some thinking and effort. |
| 10742 | */ |
| 10743 | ereport(ERROR, |
| 10744 | (errcode(ERRCODE_SYNTAX_ERROR), |
| 10745 | errmsg("cannot alter type of a column used by a generated column" ), |
| 10746 | errdetail("Column \"%s\" is used by generated column \"%s\"." , |
| 10747 | colName, get_attname(foundObject.objectId, foundObject.objectSubId, false)))); |
| 10748 | } |
| 10749 | else |
| 10750 | { |
| 10751 | /* Not expecting any other direct dependencies... */ |
| 10752 | elog(ERROR, "unexpected object depending on column: %s" , |
| 10753 | getObjectDescription(&foundObject)); |
| 10754 | } |
| 10755 | break; |
| 10756 | } |
| 10757 | |
| 10758 | case OCLASS_CONSTRAINT: |
| 10759 | Assert(foundObject.objectSubId == 0); |
| 10760 | RememberConstraintForRebuilding(foundObject.objectId, tab); |
| 10761 | break; |
| 10762 | |
| 10763 | case OCLASS_REWRITE: |
| 10764 | /* XXX someday see if we can cope with revising views */ |
| 10765 | ereport(ERROR, |
| 10766 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 10767 | errmsg("cannot alter type of a column used by a view or rule" ), |
| 10768 | errdetail("%s depends on column \"%s\"" , |
| 10769 | getObjectDescription(&foundObject), |
| 10770 | colName))); |
| 10771 | break; |
| 10772 | |
| 10773 | case OCLASS_TRIGGER: |
| 10774 | |
| 10775 | /* |
| 10776 | * A trigger can depend on a column because the column is |
| 10777 | * specified as an update target, or because the column is |
| 10778 | * used in the trigger's WHEN condition. The first case would |
| 10779 | * not require any extra work, but the second case would |
| 10780 | * require updating the WHEN expression, which will take a |
| 10781 | * significant amount of new code. Since we can't easily tell |
| 10782 | * which case applies, we punt for both. FIXME someday. |
| 10783 | */ |
| 10784 | ereport(ERROR, |
| 10785 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 10786 | errmsg("cannot alter type of a column used in a trigger definition" ), |
| 10787 | errdetail("%s depends on column \"%s\"" , |
| 10788 | getObjectDescription(&foundObject), |
| 10789 | colName))); |
| 10790 | break; |
| 10791 | |
| 10792 | case OCLASS_POLICY: |
| 10793 | |
| 10794 | /* |
| 10795 | * A policy can depend on a column because the column is |
| 10796 | * specified in the policy's USING or WITH CHECK qual |
| 10797 | * expressions. It might be possible to rewrite and recheck |
| 10798 | * the policy expression, but punt for now. It's certainly |
| 10799 | * easy enough to remove and recreate the policy; still, FIXME |
| 10800 | * someday. |
| 10801 | */ |
| 10802 | ereport(ERROR, |
| 10803 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 10804 | errmsg("cannot alter type of a column used in a policy definition" ), |
| 10805 | errdetail("%s depends on column \"%s\"" , |
| 10806 | getObjectDescription(&foundObject), |
| 10807 | colName))); |
| 10808 | break; |
| 10809 | |
| 10810 | case OCLASS_DEFAULT: |
| 10811 | |
| 10812 | /* |
| 10813 | * Ignore the column's default expression, since we will fix |
| 10814 | * it below. |
| 10815 | */ |
| 10816 | Assert(defaultexpr); |
| 10817 | break; |
| 10818 | |
| 10819 | case OCLASS_STATISTIC_EXT: |
| 10820 | |
| 10821 | /* |
| 10822 | * Give the extended-stats machinery a chance to fix anything |
| 10823 | * that this column type change would break. |
| 10824 | */ |
| 10825 | UpdateStatisticsForTypeChange(foundObject.objectId, |
| 10826 | RelationGetRelid(rel), attnum, |
| 10827 | attTup->atttypid, targettype); |
| 10828 | break; |
| 10829 | |
| 10830 | case OCLASS_PROC: |
| 10831 | case OCLASS_TYPE: |
| 10832 | case OCLASS_CAST: |
| 10833 | case OCLASS_COLLATION: |
| 10834 | case OCLASS_CONVERSION: |
| 10835 | case OCLASS_LANGUAGE: |
| 10836 | case OCLASS_LARGEOBJECT: |
| 10837 | case OCLASS_OPERATOR: |
| 10838 | case OCLASS_OPCLASS: |
| 10839 | case OCLASS_OPFAMILY: |
| 10840 | case OCLASS_AM: |
| 10841 | case OCLASS_AMOP: |
| 10842 | case OCLASS_AMPROC: |
| 10843 | case OCLASS_SCHEMA: |
| 10844 | case OCLASS_TSPARSER: |
| 10845 | case OCLASS_TSDICT: |
| 10846 | case OCLASS_TSTEMPLATE: |
| 10847 | case OCLASS_TSCONFIG: |
| 10848 | case OCLASS_ROLE: |
| 10849 | case OCLASS_DATABASE: |
| 10850 | case OCLASS_TBLSPACE: |
| 10851 | case OCLASS_FDW: |
| 10852 | case OCLASS_FOREIGN_SERVER: |
| 10853 | case OCLASS_USER_MAPPING: |
| 10854 | case OCLASS_DEFACL: |
| 10855 | case OCLASS_EXTENSION: |
| 10856 | case OCLASS_EVENT_TRIGGER: |
| 10857 | case OCLASS_PUBLICATION: |
| 10858 | case OCLASS_PUBLICATION_REL: |
| 10859 | case OCLASS_SUBSCRIPTION: |
| 10860 | case OCLASS_TRANSFORM: |
| 10861 | |
| 10862 | /* |
| 10863 | * We don't expect any of these sorts of objects to depend on |
| 10864 | * a column. |
| 10865 | */ |
| 10866 | elog(ERROR, "unexpected object depending on column: %s" , |
| 10867 | getObjectDescription(&foundObject)); |
| 10868 | break; |
| 10869 | |
| 10870 | /* |
| 10871 | * There's intentionally no default: case here; we want the |
| 10872 | * compiler to warn if a new OCLASS hasn't been handled above. |
| 10873 | */ |
| 10874 | } |
| 10875 | } |
| 10876 | |
| 10877 | systable_endscan(scan); |
| 10878 | |
| 10879 | /* |
| 10880 | * Now scan for dependencies of this column on other things. The only |
| 10881 | * thing we should find is the dependency on the column datatype, which we |
| 10882 | * want to remove, possibly a collation dependency, and dependencies on |
| 10883 | * other columns if it is a generated column. |
| 10884 | */ |
| 10885 | ScanKeyInit(&key[0], |
| 10886 | Anum_pg_depend_classid, |
| 10887 | BTEqualStrategyNumber, F_OIDEQ, |
| 10888 | ObjectIdGetDatum(RelationRelationId)); |
| 10889 | ScanKeyInit(&key[1], |
| 10890 | Anum_pg_depend_objid, |
| 10891 | BTEqualStrategyNumber, F_OIDEQ, |
| 10892 | ObjectIdGetDatum(RelationGetRelid(rel))); |
| 10893 | ScanKeyInit(&key[2], |
| 10894 | Anum_pg_depend_objsubid, |
| 10895 | BTEqualStrategyNumber, F_INT4EQ, |
| 10896 | Int32GetDatum((int32) attnum)); |
| 10897 | |
| 10898 | scan = systable_beginscan(depRel, DependDependerIndexId, true, |
| 10899 | NULL, 3, key); |
| 10900 | |
| 10901 | while (HeapTupleIsValid(depTup = systable_getnext(scan))) |
| 10902 | { |
| 10903 | Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup); |
| 10904 | ObjectAddress foundObject; |
| 10905 | |
| 10906 | foundObject.classId = foundDep->refclassid; |
| 10907 | foundObject.objectId = foundDep->refobjid; |
| 10908 | foundObject.objectSubId = foundDep->refobjsubid; |
| 10909 | |
| 10910 | if (foundDep->deptype != DEPENDENCY_NORMAL && |
| 10911 | foundDep->deptype != DEPENDENCY_AUTO) |
| 10912 | elog(ERROR, "found unexpected dependency type '%c'" , |
| 10913 | foundDep->deptype); |
| 10914 | if (!(foundDep->refclassid == TypeRelationId && |
| 10915 | foundDep->refobjid == attTup->atttypid) && |
| 10916 | !(foundDep->refclassid == CollationRelationId && |
| 10917 | foundDep->refobjid == attTup->attcollation) && |
| 10918 | !(foundDep->refclassid == RelationRelationId && |
| 10919 | foundDep->refobjid == RelationGetRelid(rel) && |
| 10920 | foundDep->refobjsubid != 0) |
| 10921 | ) |
| 10922 | elog(ERROR, "found unexpected dependency for column: %s" , |
| 10923 | getObjectDescription(&foundObject)); |
| 10924 | |
| 10925 | CatalogTupleDelete(depRel, &depTup->t_self); |
| 10926 | } |
| 10927 | |
| 10928 | systable_endscan(scan); |
| 10929 | |
| 10930 | table_close(depRel, RowExclusiveLock); |
| 10931 | |
| 10932 | /* |
| 10933 | * Here we go --- change the recorded column type and collation. (Note |
| 10934 | * heapTup is a copy of the syscache entry, so okay to scribble on.) First |
| 10935 | * fix up the missing value if any. |
| 10936 | */ |
| 10937 | if (attTup->atthasmissing) |
| 10938 | { |
| 10939 | Datum missingval; |
| 10940 | bool missingNull; |
| 10941 | |
| 10942 | /* if rewrite is true the missing value should already be cleared */ |
| 10943 | Assert(tab->rewrite == 0); |
| 10944 | |
| 10945 | /* Get the missing value datum */ |
| 10946 | missingval = heap_getattr(heapTup, |
| 10947 | Anum_pg_attribute_attmissingval, |
| 10948 | attrelation->rd_att, |
| 10949 | &missingNull); |
| 10950 | |
| 10951 | /* if it's a null array there is nothing to do */ |
| 10952 | |
| 10953 | if (!missingNull) |
| 10954 | { |
| 10955 | /* |
| 10956 | * Get the datum out of the array and repack it in a new array |
| 10957 | * built with the new type data. We assume that since the table |
| 10958 | * doesn't need rewriting, the actual Datum doesn't need to be |
| 10959 | * changed, only the array metadata. |
| 10960 | */ |
| 10961 | |
| 10962 | int one = 1; |
| 10963 | bool isNull; |
| 10964 | Datum valuesAtt[Natts_pg_attribute]; |
| 10965 | bool nullsAtt[Natts_pg_attribute]; |
| 10966 | bool replacesAtt[Natts_pg_attribute]; |
| 10967 | HeapTuple newTup; |
| 10968 | |
| 10969 | MemSet(valuesAtt, 0, sizeof(valuesAtt)); |
| 10970 | MemSet(nullsAtt, false, sizeof(nullsAtt)); |
| 10971 | MemSet(replacesAtt, false, sizeof(replacesAtt)); |
| 10972 | |
| 10973 | missingval = array_get_element(missingval, |
| 10974 | 1, |
| 10975 | &one, |
| 10976 | 0, |
| 10977 | attTup->attlen, |
| 10978 | attTup->attbyval, |
| 10979 | attTup->attalign, |
| 10980 | &isNull); |
| 10981 | missingval = PointerGetDatum( |
| 10982 | construct_array(&missingval, |
| 10983 | 1, |
| 10984 | targettype, |
| 10985 | tform->typlen, |
| 10986 | tform->typbyval, |
| 10987 | tform->typalign)); |
| 10988 | |
| 10989 | valuesAtt[Anum_pg_attribute_attmissingval - 1] = missingval; |
| 10990 | replacesAtt[Anum_pg_attribute_attmissingval - 1] = true; |
| 10991 | nullsAtt[Anum_pg_attribute_attmissingval - 1] = false; |
| 10992 | |
| 10993 | newTup = heap_modify_tuple(heapTup, RelationGetDescr(attrelation), |
| 10994 | valuesAtt, nullsAtt, replacesAtt); |
| 10995 | heap_freetuple(heapTup); |
| 10996 | heapTup = newTup; |
| 10997 | attTup = (Form_pg_attribute) GETSTRUCT(heapTup); |
| 10998 | } |
| 10999 | } |
| 11000 | |
| 11001 | attTup->atttypid = targettype; |
| 11002 | attTup->atttypmod = targettypmod; |
| 11003 | attTup->attcollation = targetcollid; |
| 11004 | attTup->attndims = list_length(typeName->arrayBounds); |
| 11005 | attTup->attlen = tform->typlen; |
| 11006 | attTup->attbyval = tform->typbyval; |
| 11007 | attTup->attalign = tform->typalign; |
| 11008 | attTup->attstorage = tform->typstorage; |
| 11009 | |
| 11010 | ReleaseSysCache(typeTuple); |
| 11011 | |
| 11012 | CatalogTupleUpdate(attrelation, &heapTup->t_self, heapTup); |
| 11013 | |
| 11014 | table_close(attrelation, RowExclusiveLock); |
| 11015 | |
| 11016 | /* Install dependencies on new datatype and collation */ |
| 11017 | add_column_datatype_dependency(RelationGetRelid(rel), attnum, targettype); |
| 11018 | add_column_collation_dependency(RelationGetRelid(rel), attnum, targetcollid); |
| 11019 | |
| 11020 | /* |
| 11021 | * Drop any pg_statistic entry for the column, since it's now wrong type |
| 11022 | */ |
| 11023 | RemoveStatistics(RelationGetRelid(rel), attnum); |
| 11024 | |
| 11025 | InvokeObjectPostAlterHook(RelationRelationId, |
| 11026 | RelationGetRelid(rel), attnum); |
| 11027 | |
| 11028 | /* |
| 11029 | * Update the default, if present, by brute force --- remove and re-add |
| 11030 | * the default. Probably unsafe to take shortcuts, since the new version |
| 11031 | * may well have additional dependencies. (It's okay to do this now, |
| 11032 | * rather than after other ALTER TYPE commands, since the default won't |
| 11033 | * depend on other column types.) |
| 11034 | */ |
| 11035 | if (defaultexpr) |
| 11036 | { |
| 11037 | /* Must make new row visible since it will be updated again */ |
| 11038 | CommandCounterIncrement(); |
| 11039 | |
| 11040 | /* |
| 11041 | * We use RESTRICT here for safety, but at present we do not expect |
| 11042 | * anything to depend on the default. |
| 11043 | */ |
| 11044 | RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, true, |
| 11045 | true); |
| 11046 | |
| 11047 | StoreAttrDefault(rel, attnum, defaultexpr, true, false); |
| 11048 | } |
| 11049 | |
| 11050 | ObjectAddressSubSet(address, RelationRelationId, |
| 11051 | RelationGetRelid(rel), attnum); |
| 11052 | |
| 11053 | /* Cleanup */ |
| 11054 | heap_freetuple(heapTup); |
| 11055 | |
| 11056 | return address; |
| 11057 | } |
| 11058 | |
| 11059 | /* |
| 11060 | * Subroutine for ATExecAlterColumnType: remember that a constraint needs |
| 11061 | * to be rebuilt (which we might already know). |
| 11062 | */ |
| 11063 | static void |
| 11064 | RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab) |
| 11065 | { |
| 11066 | /* |
| 11067 | * This de-duplication check is critical for two independent reasons: we |
| 11068 | * mustn't try to recreate the same constraint twice, and if a constraint |
| 11069 | * depends on more than one column whose type is to be altered, we must |
| 11070 | * capture its definition string before applying any of the column type |
| 11071 | * changes. ruleutils.c will get confused if we ask again later. |
| 11072 | */ |
| 11073 | if (!list_member_oid(tab->changedConstraintOids, conoid)) |
| 11074 | { |
| 11075 | /* OK, capture the constraint's existing definition string */ |
| 11076 | char *defstring = pg_get_constraintdef_command(conoid); |
| 11077 | |
| 11078 | tab->changedConstraintOids = lappend_oid(tab->changedConstraintOids, |
| 11079 | conoid); |
| 11080 | tab->changedConstraintDefs = lappend(tab->changedConstraintDefs, |
| 11081 | defstring); |
| 11082 | } |
| 11083 | } |
| 11084 | |
| 11085 | /* |
| 11086 | * Subroutine for ATExecAlterColumnType: remember that an index needs |
| 11087 | * to be rebuilt (which we might already know). |
| 11088 | */ |
| 11089 | static void |
| 11090 | RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab) |
| 11091 | { |
| 11092 | /* |
| 11093 | * This de-duplication check is critical for two independent reasons: we |
| 11094 | * mustn't try to recreate the same index twice, and if an index depends |
| 11095 | * on more than one column whose type is to be altered, we must capture |
| 11096 | * its definition string before applying any of the column type changes. |
| 11097 | * ruleutils.c will get confused if we ask again later. |
| 11098 | */ |
| 11099 | if (!list_member_oid(tab->changedIndexOids, indoid)) |
| 11100 | { |
| 11101 | /* |
| 11102 | * Before adding it as an index-to-rebuild, we'd better see if it |
| 11103 | * belongs to a constraint, and if so rebuild the constraint instead. |
| 11104 | * Typically this check fails, because constraint indexes normally |
| 11105 | * have only dependencies on their constraint. But it's possible for |
| 11106 | * such an index to also have direct dependencies on table columns, |
| 11107 | * for example with a partial exclusion constraint. |
| 11108 | */ |
| 11109 | Oid conoid = get_index_constraint(indoid); |
| 11110 | |
| 11111 | if (OidIsValid(conoid)) |
| 11112 | { |
| 11113 | RememberConstraintForRebuilding(conoid, tab); |
| 11114 | } |
| 11115 | else |
| 11116 | { |
| 11117 | /* OK, capture the index's existing definition string */ |
| 11118 | char *defstring = pg_get_indexdef_string(indoid); |
| 11119 | |
| 11120 | tab->changedIndexOids = lappend_oid(tab->changedIndexOids, |
| 11121 | indoid); |
| 11122 | tab->changedIndexDefs = lappend(tab->changedIndexDefs, |
| 11123 | defstring); |
| 11124 | } |
| 11125 | } |
| 11126 | } |
| 11127 | |
| 11128 | /* |
| 11129 | * Cleanup after we've finished all the ALTER TYPE operations for a |
| 11130 | * particular relation. We have to drop and recreate all the indexes |
| 11131 | * and constraints that depend on the altered columns. We do the |
| 11132 | * actual dropping here, but re-creation is managed by adding work |
| 11133 | * queue entries to do those steps later. |
| 11134 | */ |
| 11135 | static void |
| 11136 | ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode) |
| 11137 | { |
| 11138 | ObjectAddress obj; |
| 11139 | ObjectAddresses *objects; |
| 11140 | ListCell *def_item; |
| 11141 | ListCell *oid_item; |
| 11142 | |
| 11143 | /* |
| 11144 | * Collect all the constraints and indexes to drop so we can process them |
| 11145 | * in a single call. That way we don't have to worry about dependencies |
| 11146 | * among them. |
| 11147 | */ |
| 11148 | objects = new_object_addresses(); |
| 11149 | |
| 11150 | /* |
| 11151 | * Re-parse the index and constraint definitions, and attach them to the |
| 11152 | * appropriate work queue entries. We do this before dropping because in |
| 11153 | * the case of a FOREIGN KEY constraint, we might not yet have exclusive |
| 11154 | * lock on the table the constraint is attached to, and we need to get |
| 11155 | * that before reparsing/dropping. |
| 11156 | * |
| 11157 | * We can't rely on the output of deparsing to tell us which relation to |
| 11158 | * operate on, because concurrent activity might have made the name |
| 11159 | * resolve differently. Instead, we've got to use the OID of the |
| 11160 | * constraint or index we're processing to figure out which relation to |
| 11161 | * operate on. |
| 11162 | */ |
| 11163 | forboth(oid_item, tab->changedConstraintOids, |
| 11164 | def_item, tab->changedConstraintDefs) |
| 11165 | { |
| 11166 | Oid oldId = lfirst_oid(oid_item); |
| 11167 | HeapTuple tup; |
| 11168 | Form_pg_constraint con; |
| 11169 | Oid relid; |
| 11170 | Oid confrelid; |
| 11171 | char contype; |
| 11172 | bool conislocal; |
| 11173 | |
| 11174 | tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(oldId)); |
| 11175 | if (!HeapTupleIsValid(tup)) /* should not happen */ |
| 11176 | elog(ERROR, "cache lookup failed for constraint %u" , oldId); |
| 11177 | con = (Form_pg_constraint) GETSTRUCT(tup); |
| 11178 | if (OidIsValid(con->conrelid)) |
| 11179 | relid = con->conrelid; |
| 11180 | else |
| 11181 | { |
| 11182 | /* must be a domain constraint */ |
| 11183 | relid = get_typ_typrelid(getBaseType(con->contypid)); |
| 11184 | if (!OidIsValid(relid)) |
| 11185 | elog(ERROR, "could not identify relation associated with constraint %u" , oldId); |
| 11186 | } |
| 11187 | confrelid = con->confrelid; |
| 11188 | contype = con->contype; |
| 11189 | conislocal = con->conislocal; |
| 11190 | ReleaseSysCache(tup); |
| 11191 | |
| 11192 | ObjectAddressSet(obj, ConstraintRelationId, oldId); |
| 11193 | add_exact_object_address(&obj, objects); |
| 11194 | |
| 11195 | /* |
| 11196 | * If the constraint is inherited (only), we don't want to inject a |
| 11197 | * new definition here; it'll get recreated when ATAddCheckConstraint |
| 11198 | * recurses from adding the parent table's constraint. But we had to |
| 11199 | * carry the info this far so that we can drop the constraint below. |
| 11200 | */ |
| 11201 | if (!conislocal) |
| 11202 | continue; |
| 11203 | |
| 11204 | /* |
| 11205 | * When rebuilding an FK constraint that references the table we're |
| 11206 | * modifying, we might not yet have any lock on the FK's table, so get |
| 11207 | * one now. We'll need AccessExclusiveLock for the DROP CONSTRAINT |
| 11208 | * step, so there's no value in asking for anything weaker. |
| 11209 | */ |
| 11210 | if (relid != tab->relid && contype == CONSTRAINT_FOREIGN) |
| 11211 | LockRelationOid(relid, AccessExclusiveLock); |
| 11212 | |
| 11213 | ATPostAlterTypeParse(oldId, relid, confrelid, |
| 11214 | (char *) lfirst(def_item), |
| 11215 | wqueue, lockmode, tab->rewrite); |
| 11216 | } |
| 11217 | forboth(oid_item, tab->changedIndexOids, |
| 11218 | def_item, tab->changedIndexDefs) |
| 11219 | { |
| 11220 | Oid oldId = lfirst_oid(oid_item); |
| 11221 | Oid relid; |
| 11222 | |
| 11223 | relid = IndexGetRelation(oldId, false); |
| 11224 | ATPostAlterTypeParse(oldId, relid, InvalidOid, |
| 11225 | (char *) lfirst(def_item), |
| 11226 | wqueue, lockmode, tab->rewrite); |
| 11227 | |
| 11228 | ObjectAddressSet(obj, RelationRelationId, oldId); |
| 11229 | add_exact_object_address(&obj, objects); |
| 11230 | } |
| 11231 | |
| 11232 | /* |
| 11233 | * It should be okay to use DROP_RESTRICT here, since nothing else should |
| 11234 | * be depending on these objects. |
| 11235 | */ |
| 11236 | performMultipleDeletions(objects, DROP_RESTRICT, PERFORM_DELETION_INTERNAL); |
| 11237 | |
| 11238 | free_object_addresses(objects); |
| 11239 | |
| 11240 | /* |
| 11241 | * The objects will get recreated during subsequent passes over the work |
| 11242 | * queue. |
| 11243 | */ |
| 11244 | } |
| 11245 | |
| 11246 | /* |
| 11247 | * Parse the previously-saved definition string for a constraint or index |
| 11248 | * against the newly-established column data type(s), and queue up the |
| 11249 | * resulting command parsetrees for execution. |
| 11250 | * |
| 11251 | * This might fail if, for example, you have a WHERE clause that uses an |
| 11252 | * operator that's not available for the new column type. |
| 11253 | */ |
| 11254 | static void |
| 11255 | ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, |
| 11256 | List **wqueue, LOCKMODE lockmode, bool rewrite) |
| 11257 | { |
| 11258 | List *raw_parsetree_list; |
| 11259 | List *querytree_list; |
| 11260 | ListCell *list_item; |
| 11261 | Relation rel; |
| 11262 | |
| 11263 | /* |
| 11264 | * We expect that we will get only ALTER TABLE and CREATE INDEX |
| 11265 | * statements. Hence, there is no need to pass them through |
| 11266 | * parse_analyze() or the rewriter, but instead we need to pass them |
| 11267 | * through parse_utilcmd.c to make them ready for execution. |
| 11268 | */ |
| 11269 | raw_parsetree_list = raw_parser(cmd); |
| 11270 | querytree_list = NIL; |
| 11271 | foreach(list_item, raw_parsetree_list) |
| 11272 | { |
| 11273 | RawStmt *rs = lfirst_node(RawStmt, list_item); |
| 11274 | Node *stmt = rs->stmt; |
| 11275 | |
| 11276 | if (IsA(stmt, IndexStmt)) |
| 11277 | querytree_list = lappend(querytree_list, |
| 11278 | transformIndexStmt(oldRelId, |
| 11279 | (IndexStmt *) stmt, |
| 11280 | cmd)); |
| 11281 | else if (IsA(stmt, AlterTableStmt)) |
| 11282 | querytree_list = list_concat(querytree_list, |
| 11283 | transformAlterTableStmt(oldRelId, |
| 11284 | (AlterTableStmt *) stmt, |
| 11285 | cmd)); |
| 11286 | else |
| 11287 | querytree_list = lappend(querytree_list, stmt); |
| 11288 | } |
| 11289 | |
| 11290 | /* Caller should already have acquired whatever lock we need. */ |
| 11291 | rel = relation_open(oldRelId, NoLock); |
| 11292 | |
| 11293 | /* |
| 11294 | * Attach each generated command to the proper place in the work queue. |
| 11295 | * Note this could result in creation of entirely new work-queue entries. |
| 11296 | * |
| 11297 | * Also note that we have to tweak the command subtypes, because it turns |
| 11298 | * out that re-creation of indexes and constraints has to act a bit |
| 11299 | * differently from initial creation. |
| 11300 | */ |
| 11301 | foreach(list_item, querytree_list) |
| 11302 | { |
| 11303 | Node *stm = (Node *) lfirst(list_item); |
| 11304 | AlteredTableInfo *tab; |
| 11305 | |
| 11306 | tab = ATGetQueueEntry(wqueue, rel); |
| 11307 | |
| 11308 | if (IsA(stm, IndexStmt)) |
| 11309 | { |
| 11310 | IndexStmt *stmt = (IndexStmt *) stm; |
| 11311 | AlterTableCmd *newcmd; |
| 11312 | |
| 11313 | if (!rewrite) |
| 11314 | TryReuseIndex(oldId, stmt); |
| 11315 | stmt->reset_default_tblspc = true; |
| 11316 | /* keep the index's comment */ |
| 11317 | stmt->idxcomment = GetComment(oldId, RelationRelationId, 0); |
| 11318 | |
| 11319 | newcmd = makeNode(AlterTableCmd); |
| 11320 | newcmd->subtype = AT_ReAddIndex; |
| 11321 | newcmd->def = (Node *) stmt; |
| 11322 | tab->subcmds[AT_PASS_OLD_INDEX] = |
| 11323 | lappend(tab->subcmds[AT_PASS_OLD_INDEX], newcmd); |
| 11324 | } |
| 11325 | else if (IsA(stm, AlterTableStmt)) |
| 11326 | { |
| 11327 | AlterTableStmt *stmt = (AlterTableStmt *) stm; |
| 11328 | ListCell *lcmd; |
| 11329 | |
| 11330 | foreach(lcmd, stmt->cmds) |
| 11331 | { |
| 11332 | AlterTableCmd *cmd = castNode(AlterTableCmd, lfirst(lcmd)); |
| 11333 | |
| 11334 | if (cmd->subtype == AT_AddIndex) |
| 11335 | { |
| 11336 | IndexStmt *indstmt; |
| 11337 | Oid indoid; |
| 11338 | |
| 11339 | indstmt = castNode(IndexStmt, cmd->def); |
| 11340 | indoid = get_constraint_index(oldId); |
| 11341 | |
| 11342 | if (!rewrite) |
| 11343 | TryReuseIndex(indoid, indstmt); |
| 11344 | /* keep any comment on the index */ |
| 11345 | indstmt->idxcomment = GetComment(indoid, |
| 11346 | RelationRelationId, 0); |
| 11347 | indstmt->reset_default_tblspc = true; |
| 11348 | |
| 11349 | cmd->subtype = AT_ReAddIndex; |
| 11350 | tab->subcmds[AT_PASS_OLD_INDEX] = |
| 11351 | lappend(tab->subcmds[AT_PASS_OLD_INDEX], cmd); |
| 11352 | |
| 11353 | /* recreate any comment on the constraint */ |
| 11354 | RebuildConstraintComment(tab, |
| 11355 | AT_PASS_OLD_INDEX, |
| 11356 | oldId, |
| 11357 | rel, |
| 11358 | NIL, |
| 11359 | indstmt->idxname); |
| 11360 | } |
| 11361 | else if (cmd->subtype == AT_AddConstraint) |
| 11362 | { |
| 11363 | Constraint *con = castNode(Constraint, cmd->def); |
| 11364 | |
| 11365 | con->old_pktable_oid = refRelId; |
| 11366 | /* rewriting neither side of a FK */ |
| 11367 | if (con->contype == CONSTR_FOREIGN && |
| 11368 | !rewrite && tab->rewrite == 0) |
| 11369 | TryReuseForeignKey(oldId, con); |
| 11370 | con->reset_default_tblspc = true; |
| 11371 | cmd->subtype = AT_ReAddConstraint; |
| 11372 | tab->subcmds[AT_PASS_OLD_CONSTR] = |
| 11373 | lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd); |
| 11374 | |
| 11375 | /* recreate any comment on the constraint */ |
| 11376 | RebuildConstraintComment(tab, |
| 11377 | AT_PASS_OLD_CONSTR, |
| 11378 | oldId, |
| 11379 | rel, |
| 11380 | NIL, |
| 11381 | con->conname); |
| 11382 | } |
| 11383 | else if (cmd->subtype == AT_SetNotNull) |
| 11384 | { |
| 11385 | /* |
| 11386 | * The parser will create AT_SetNotNull subcommands for |
| 11387 | * columns of PRIMARY KEY indexes/constraints, but we need |
| 11388 | * not do anything with them here, because the columns' |
| 11389 | * NOT NULL marks will already have been propagated into |
| 11390 | * the new table definition. |
| 11391 | */ |
| 11392 | } |
| 11393 | else |
| 11394 | elog(ERROR, "unexpected statement subtype: %d" , |
| 11395 | (int) cmd->subtype); |
| 11396 | } |
| 11397 | } |
| 11398 | else if (IsA(stm, AlterDomainStmt)) |
| 11399 | { |
| 11400 | AlterDomainStmt *stmt = (AlterDomainStmt *) stm; |
| 11401 | |
| 11402 | if (stmt->subtype == 'C') /* ADD CONSTRAINT */ |
| 11403 | { |
| 11404 | Constraint *con = castNode(Constraint, stmt->def); |
| 11405 | AlterTableCmd *cmd = makeNode(AlterTableCmd); |
| 11406 | |
| 11407 | cmd->subtype = AT_ReAddDomainConstraint; |
| 11408 | cmd->def = (Node *) stmt; |
| 11409 | tab->subcmds[AT_PASS_OLD_CONSTR] = |
| 11410 | lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd); |
| 11411 | |
| 11412 | /* recreate any comment on the constraint */ |
| 11413 | RebuildConstraintComment(tab, |
| 11414 | AT_PASS_OLD_CONSTR, |
| 11415 | oldId, |
| 11416 | NULL, |
| 11417 | stmt->typeName, |
| 11418 | con->conname); |
| 11419 | } |
| 11420 | else |
| 11421 | elog(ERROR, "unexpected statement subtype: %d" , |
| 11422 | (int) stmt->subtype); |
| 11423 | } |
| 11424 | else |
| 11425 | elog(ERROR, "unexpected statement type: %d" , |
| 11426 | (int) nodeTag(stm)); |
| 11427 | } |
| 11428 | |
| 11429 | relation_close(rel, NoLock); |
| 11430 | } |
| 11431 | |
| 11432 | /* |
| 11433 | * Subroutine for ATPostAlterTypeParse() to recreate any existing comment |
| 11434 | * for a table or domain constraint that is being rebuilt. |
| 11435 | * |
| 11436 | * objid is the OID of the constraint. |
| 11437 | * Pass "rel" for a table constraint, or "domname" (domain's qualified name |
| 11438 | * as a string list) for a domain constraint. |
| 11439 | * (We could dig that info, as well as the conname, out of the pg_constraint |
| 11440 | * entry; but callers already have them so might as well pass them.) |
| 11441 | */ |
| 11442 | static void |
| 11443 | (AlteredTableInfo *tab, int pass, Oid objid, |
| 11444 | Relation rel, List *domname, |
| 11445 | const char *conname) |
| 11446 | { |
| 11447 | CommentStmt *cmd; |
| 11448 | char *; |
| 11449 | AlterTableCmd *newcmd; |
| 11450 | |
| 11451 | /* Look for comment for object wanted, and leave if none */ |
| 11452 | comment_str = GetComment(objid, ConstraintRelationId, 0); |
| 11453 | if (comment_str == NULL) |
| 11454 | return; |
| 11455 | |
| 11456 | /* Build CommentStmt node, copying all input data for safety */ |
| 11457 | cmd = makeNode(CommentStmt); |
| 11458 | if (rel) |
| 11459 | { |
| 11460 | cmd->objtype = OBJECT_TABCONSTRAINT; |
| 11461 | cmd->object = (Node *) |
| 11462 | list_make3(makeString(get_namespace_name(RelationGetNamespace(rel))), |
| 11463 | makeString(pstrdup(RelationGetRelationName(rel))), |
| 11464 | makeString(pstrdup(conname))); |
| 11465 | } |
| 11466 | else |
| 11467 | { |
| 11468 | cmd->objtype = OBJECT_DOMCONSTRAINT; |
| 11469 | cmd->object = (Node *) |
| 11470 | list_make2(makeTypeNameFromNameList(copyObject(domname)), |
| 11471 | makeString(pstrdup(conname))); |
| 11472 | } |
| 11473 | cmd->comment = comment_str; |
| 11474 | |
| 11475 | /* Append it to list of commands */ |
| 11476 | newcmd = makeNode(AlterTableCmd); |
| 11477 | newcmd->subtype = AT_ReAddComment; |
| 11478 | newcmd->def = (Node *) cmd; |
| 11479 | tab->subcmds[pass] = lappend(tab->subcmds[pass], newcmd); |
| 11480 | } |
| 11481 | |
| 11482 | /* |
| 11483 | * Subroutine for ATPostAlterTypeParse(). Calls out to CheckIndexCompatible() |
| 11484 | * for the real analysis, then mutates the IndexStmt based on that verdict. |
| 11485 | */ |
| 11486 | static void |
| 11487 | TryReuseIndex(Oid oldId, IndexStmt *stmt) |
| 11488 | { |
| 11489 | if (CheckIndexCompatible(oldId, |
| 11490 | stmt->accessMethod, |
| 11491 | stmt->indexParams, |
| 11492 | stmt->excludeOpNames)) |
| 11493 | { |
| 11494 | Relation irel = index_open(oldId, NoLock); |
| 11495 | |
| 11496 | /* If it's a partitioned index, there is no storage to share. */ |
| 11497 | if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX) |
| 11498 | stmt->oldNode = irel->rd_node.relNode; |
| 11499 | index_close(irel, NoLock); |
| 11500 | } |
| 11501 | } |
| 11502 | |
| 11503 | /* |
| 11504 | * Subroutine for ATPostAlterTypeParse(). |
| 11505 | * |
| 11506 | * Stash the old P-F equality operator into the Constraint node, for possible |
| 11507 | * use by ATAddForeignKeyConstraint() in determining whether revalidation of |
| 11508 | * this constraint can be skipped. |
| 11509 | */ |
| 11510 | static void |
| 11511 | TryReuseForeignKey(Oid oldId, Constraint *con) |
| 11512 | { |
| 11513 | HeapTuple tup; |
| 11514 | Datum adatum; |
| 11515 | bool isNull; |
| 11516 | ArrayType *arr; |
| 11517 | Oid *rawarr; |
| 11518 | int numkeys; |
| 11519 | int i; |
| 11520 | |
| 11521 | Assert(con->contype == CONSTR_FOREIGN); |
| 11522 | Assert(con->old_conpfeqop == NIL); /* already prepared this node */ |
| 11523 | |
| 11524 | tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(oldId)); |
| 11525 | if (!HeapTupleIsValid(tup)) /* should not happen */ |
| 11526 | elog(ERROR, "cache lookup failed for constraint %u" , oldId); |
| 11527 | |
| 11528 | adatum = SysCacheGetAttr(CONSTROID, tup, |
| 11529 | Anum_pg_constraint_conpfeqop, &isNull); |
| 11530 | if (isNull) |
| 11531 | elog(ERROR, "null conpfeqop for constraint %u" , oldId); |
| 11532 | arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */ |
| 11533 | numkeys = ARR_DIMS(arr)[0]; |
| 11534 | /* test follows the one in ri_FetchConstraintInfo() */ |
| 11535 | if (ARR_NDIM(arr) != 1 || |
| 11536 | ARR_HASNULL(arr) || |
| 11537 | ARR_ELEMTYPE(arr) != OIDOID) |
| 11538 | elog(ERROR, "conpfeqop is not a 1-D Oid array" ); |
| 11539 | rawarr = (Oid *) ARR_DATA_PTR(arr); |
| 11540 | |
| 11541 | /* stash a List of the operator Oids in our Constraint node */ |
| 11542 | for (i = 0; i < numkeys; i++) |
| 11543 | con->old_conpfeqop = lappend_oid(con->old_conpfeqop, rawarr[i]); |
| 11544 | |
| 11545 | ReleaseSysCache(tup); |
| 11546 | } |
| 11547 | |
| 11548 | /* |
| 11549 | * ALTER COLUMN .. OPTIONS ( ... ) |
| 11550 | * |
| 11551 | * Returns the address of the modified column |
| 11552 | */ |
| 11553 | static ObjectAddress |
| 11554 | ATExecAlterColumnGenericOptions(Relation rel, |
| 11555 | const char *colName, |
| 11556 | List *options, |
| 11557 | LOCKMODE lockmode) |
| 11558 | { |
| 11559 | Relation ftrel; |
| 11560 | Relation attrel; |
| 11561 | ForeignServer *server; |
| 11562 | ForeignDataWrapper *fdw; |
| 11563 | HeapTuple tuple; |
| 11564 | HeapTuple newtuple; |
| 11565 | bool isnull; |
| 11566 | Datum repl_val[Natts_pg_attribute]; |
| 11567 | bool repl_null[Natts_pg_attribute]; |
| 11568 | bool repl_repl[Natts_pg_attribute]; |
| 11569 | Datum datum; |
| 11570 | Form_pg_foreign_table fttableform; |
| 11571 | Form_pg_attribute atttableform; |
| 11572 | AttrNumber attnum; |
| 11573 | ObjectAddress address; |
| 11574 | |
| 11575 | if (options == NIL) |
| 11576 | return InvalidObjectAddress; |
| 11577 | |
| 11578 | /* First, determine FDW validator associated to the foreign table. */ |
| 11579 | ftrel = table_open(ForeignTableRelationId, AccessShareLock); |
| 11580 | tuple = SearchSysCache1(FOREIGNTABLEREL, rel->rd_id); |
| 11581 | if (!HeapTupleIsValid(tuple)) |
| 11582 | ereport(ERROR, |
| 11583 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
| 11584 | errmsg("foreign table \"%s\" does not exist" , |
| 11585 | RelationGetRelationName(rel)))); |
| 11586 | fttableform = (Form_pg_foreign_table) GETSTRUCT(tuple); |
| 11587 | server = GetForeignServer(fttableform->ftserver); |
| 11588 | fdw = GetForeignDataWrapper(server->fdwid); |
| 11589 | |
| 11590 | table_close(ftrel, AccessShareLock); |
| 11591 | ReleaseSysCache(tuple); |
| 11592 | |
| 11593 | attrel = table_open(AttributeRelationId, RowExclusiveLock); |
| 11594 | tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName); |
| 11595 | if (!HeapTupleIsValid(tuple)) |
| 11596 | ereport(ERROR, |
| 11597 | (errcode(ERRCODE_UNDEFINED_COLUMN), |
| 11598 | errmsg("column \"%s\" of relation \"%s\" does not exist" , |
| 11599 | colName, RelationGetRelationName(rel)))); |
| 11600 | |
| 11601 | /* Prevent them from altering a system attribute */ |
| 11602 | atttableform = (Form_pg_attribute) GETSTRUCT(tuple); |
| 11603 | attnum = atttableform->attnum; |
| 11604 | if (attnum <= 0) |
| 11605 | ereport(ERROR, |
| 11606 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 11607 | errmsg("cannot alter system column \"%s\"" , colName))); |
| 11608 | |
| 11609 | |
| 11610 | /* Initialize buffers for new tuple values */ |
| 11611 | memset(repl_val, 0, sizeof(repl_val)); |
| 11612 | memset(repl_null, false, sizeof(repl_null)); |
| 11613 | memset(repl_repl, false, sizeof(repl_repl)); |
| 11614 | |
| 11615 | /* Extract the current options */ |
| 11616 | datum = SysCacheGetAttr(ATTNAME, |
| 11617 | tuple, |
| 11618 | Anum_pg_attribute_attfdwoptions, |
| 11619 | &isnull); |
| 11620 | if (isnull) |
| 11621 | datum = PointerGetDatum(NULL); |
| 11622 | |
| 11623 | /* Transform the options */ |
| 11624 | datum = transformGenericOptions(AttributeRelationId, |
| 11625 | datum, |
| 11626 | options, |
| 11627 | fdw->fdwvalidator); |
| 11628 | |
| 11629 | if (PointerIsValid(DatumGetPointer(datum))) |
| 11630 | repl_val[Anum_pg_attribute_attfdwoptions - 1] = datum; |
| 11631 | else |
| 11632 | repl_null[Anum_pg_attribute_attfdwoptions - 1] = true; |
| 11633 | |
| 11634 | repl_repl[Anum_pg_attribute_attfdwoptions - 1] = true; |
| 11635 | |
| 11636 | /* Everything looks good - update the tuple */ |
| 11637 | |
| 11638 | newtuple = heap_modify_tuple(tuple, RelationGetDescr(attrel), |
| 11639 | repl_val, repl_null, repl_repl); |
| 11640 | |
| 11641 | CatalogTupleUpdate(attrel, &newtuple->t_self, newtuple); |
| 11642 | |
| 11643 | InvokeObjectPostAlterHook(RelationRelationId, |
| 11644 | RelationGetRelid(rel), |
| 11645 | atttableform->attnum); |
| 11646 | ObjectAddressSubSet(address, RelationRelationId, |
| 11647 | RelationGetRelid(rel), attnum); |
| 11648 | |
| 11649 | ReleaseSysCache(tuple); |
| 11650 | |
| 11651 | table_close(attrel, RowExclusiveLock); |
| 11652 | |
| 11653 | heap_freetuple(newtuple); |
| 11654 | |
| 11655 | return address; |
| 11656 | } |
| 11657 | |
| 11658 | /* |
| 11659 | * ALTER TABLE OWNER |
| 11660 | * |
| 11661 | * recursing is true if we are recursing from a table to its indexes, |
| 11662 | * sequences, or toast table. We don't allow the ownership of those things to |
| 11663 | * be changed separately from the parent table. Also, we can skip permission |
| 11664 | * checks (this is necessary not just an optimization, else we'd fail to |
| 11665 | * handle toast tables properly). |
| 11666 | * |
| 11667 | * recursing is also true if ALTER TYPE OWNER is calling us to fix up a |
| 11668 | * free-standing composite type. |
| 11669 | */ |
| 11670 | void |
| 11671 | ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lockmode) |
| 11672 | { |
| 11673 | Relation target_rel; |
| 11674 | Relation class_rel; |
| 11675 | HeapTuple tuple; |
| 11676 | Form_pg_class tuple_class; |
| 11677 | |
| 11678 | /* |
| 11679 | * Get exclusive lock till end of transaction on the target table. Use |
| 11680 | * relation_open so that we can work on indexes and sequences. |
| 11681 | */ |
| 11682 | target_rel = relation_open(relationOid, lockmode); |
| 11683 | |
| 11684 | /* Get its pg_class tuple, too */ |
| 11685 | class_rel = table_open(RelationRelationId, RowExclusiveLock); |
| 11686 | |
| 11687 | tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relationOid)); |
| 11688 | if (!HeapTupleIsValid(tuple)) |
| 11689 | elog(ERROR, "cache lookup failed for relation %u" , relationOid); |
| 11690 | tuple_class = (Form_pg_class) GETSTRUCT(tuple); |
| 11691 | |
| 11692 | /* Can we change the ownership of this tuple? */ |
| 11693 | switch (tuple_class->relkind) |
| 11694 | { |
| 11695 | case RELKIND_RELATION: |
| 11696 | case RELKIND_VIEW: |
| 11697 | case RELKIND_MATVIEW: |
| 11698 | case RELKIND_FOREIGN_TABLE: |
| 11699 | case RELKIND_PARTITIONED_TABLE: |
| 11700 | /* ok to change owner */ |
| 11701 | break; |
| 11702 | case RELKIND_INDEX: |
| 11703 | if (!recursing) |
| 11704 | { |
| 11705 | /* |
| 11706 | * Because ALTER INDEX OWNER used to be allowed, and in fact |
| 11707 | * is generated by old versions of pg_dump, we give a warning |
| 11708 | * and do nothing rather than erroring out. Also, to avoid |
| 11709 | * unnecessary chatter while restoring those old dumps, say |
| 11710 | * nothing at all if the command would be a no-op anyway. |
| 11711 | */ |
| 11712 | if (tuple_class->relowner != newOwnerId) |
| 11713 | ereport(WARNING, |
| 11714 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 11715 | errmsg("cannot change owner of index \"%s\"" , |
| 11716 | NameStr(tuple_class->relname)), |
| 11717 | errhint("Change the ownership of the index's table, instead." ))); |
| 11718 | /* quick hack to exit via the no-op path */ |
| 11719 | newOwnerId = tuple_class->relowner; |
| 11720 | } |
| 11721 | break; |
| 11722 | case RELKIND_PARTITIONED_INDEX: |
| 11723 | if (recursing) |
| 11724 | break; |
| 11725 | ereport(ERROR, |
| 11726 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 11727 | errmsg("cannot change owner of index \"%s\"" , |
| 11728 | NameStr(tuple_class->relname)), |
| 11729 | errhint("Change the ownership of the index's table, instead." ))); |
| 11730 | break; |
| 11731 | case RELKIND_SEQUENCE: |
| 11732 | if (!recursing && |
| 11733 | tuple_class->relowner != newOwnerId) |
| 11734 | { |
| 11735 | /* if it's an owned sequence, disallow changing it by itself */ |
| 11736 | Oid tableId; |
| 11737 | int32 colId; |
| 11738 | |
| 11739 | if (sequenceIsOwned(relationOid, DEPENDENCY_AUTO, &tableId, &colId) || |
| 11740 | sequenceIsOwned(relationOid, DEPENDENCY_INTERNAL, &tableId, &colId)) |
| 11741 | ereport(ERROR, |
| 11742 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 11743 | errmsg("cannot change owner of sequence \"%s\"" , |
| 11744 | NameStr(tuple_class->relname)), |
| 11745 | errdetail("Sequence \"%s\" is linked to table \"%s\"." , |
| 11746 | NameStr(tuple_class->relname), |
| 11747 | get_rel_name(tableId)))); |
| 11748 | } |
| 11749 | break; |
| 11750 | case RELKIND_COMPOSITE_TYPE: |
| 11751 | if (recursing) |
| 11752 | break; |
| 11753 | ereport(ERROR, |
| 11754 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 11755 | errmsg("\"%s\" is a composite type" , |
| 11756 | NameStr(tuple_class->relname)), |
| 11757 | errhint("Use ALTER TYPE instead." ))); |
| 11758 | break; |
| 11759 | case RELKIND_TOASTVALUE: |
| 11760 | if (recursing) |
| 11761 | break; |
| 11762 | /* FALL THRU */ |
| 11763 | default: |
| 11764 | ereport(ERROR, |
| 11765 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 11766 | errmsg("\"%s\" is not a table, view, sequence, or foreign table" , |
| 11767 | NameStr(tuple_class->relname)))); |
| 11768 | } |
| 11769 | |
| 11770 | /* |
| 11771 | * If the new owner is the same as the existing owner, consider the |
| 11772 | * command to have succeeded. This is for dump restoration purposes. |
| 11773 | */ |
| 11774 | if (tuple_class->relowner != newOwnerId) |
| 11775 | { |
| 11776 | Datum repl_val[Natts_pg_class]; |
| 11777 | bool repl_null[Natts_pg_class]; |
| 11778 | bool repl_repl[Natts_pg_class]; |
| 11779 | Acl *newAcl; |
| 11780 | Datum aclDatum; |
| 11781 | bool isNull; |
| 11782 | HeapTuple newtuple; |
| 11783 | |
| 11784 | /* skip permission checks when recursing to index or toast table */ |
| 11785 | if (!recursing) |
| 11786 | { |
| 11787 | /* Superusers can always do it */ |
| 11788 | if (!superuser()) |
| 11789 | { |
| 11790 | Oid namespaceOid = tuple_class->relnamespace; |
| 11791 | AclResult aclresult; |
| 11792 | |
| 11793 | /* Otherwise, must be owner of the existing object */ |
| 11794 | if (!pg_class_ownercheck(relationOid, GetUserId())) |
| 11795 | aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relationOid)), |
| 11796 | RelationGetRelationName(target_rel)); |
| 11797 | |
| 11798 | /* Must be able to become new owner */ |
| 11799 | check_is_member_of_role(GetUserId(), newOwnerId); |
| 11800 | |
| 11801 | /* New owner must have CREATE privilege on namespace */ |
| 11802 | aclresult = pg_namespace_aclcheck(namespaceOid, newOwnerId, |
| 11803 | ACL_CREATE); |
| 11804 | if (aclresult != ACLCHECK_OK) |
| 11805 | aclcheck_error(aclresult, OBJECT_SCHEMA, |
| 11806 | get_namespace_name(namespaceOid)); |
| 11807 | } |
| 11808 | } |
| 11809 | |
| 11810 | memset(repl_null, false, sizeof(repl_null)); |
| 11811 | memset(repl_repl, false, sizeof(repl_repl)); |
| 11812 | |
| 11813 | repl_repl[Anum_pg_class_relowner - 1] = true; |
| 11814 | repl_val[Anum_pg_class_relowner - 1] = ObjectIdGetDatum(newOwnerId); |
| 11815 | |
| 11816 | /* |
| 11817 | * Determine the modified ACL for the new owner. This is only |
| 11818 | * necessary when the ACL is non-null. |
| 11819 | */ |
| 11820 | aclDatum = SysCacheGetAttr(RELOID, tuple, |
| 11821 | Anum_pg_class_relacl, |
| 11822 | &isNull); |
| 11823 | if (!isNull) |
| 11824 | { |
| 11825 | newAcl = aclnewowner(DatumGetAclP(aclDatum), |
| 11826 | tuple_class->relowner, newOwnerId); |
| 11827 | repl_repl[Anum_pg_class_relacl - 1] = true; |
| 11828 | repl_val[Anum_pg_class_relacl - 1] = PointerGetDatum(newAcl); |
| 11829 | } |
| 11830 | |
| 11831 | newtuple = heap_modify_tuple(tuple, RelationGetDescr(class_rel), repl_val, repl_null, repl_repl); |
| 11832 | |
| 11833 | CatalogTupleUpdate(class_rel, &newtuple->t_self, newtuple); |
| 11834 | |
| 11835 | heap_freetuple(newtuple); |
| 11836 | |
| 11837 | /* |
| 11838 | * We must similarly update any per-column ACLs to reflect the new |
| 11839 | * owner; for neatness reasons that's split out as a subroutine. |
| 11840 | */ |
| 11841 | change_owner_fix_column_acls(relationOid, |
| 11842 | tuple_class->relowner, |
| 11843 | newOwnerId); |
| 11844 | |
| 11845 | /* |
| 11846 | * Update owner dependency reference, if any. A composite type has |
| 11847 | * none, because it's tracked for the pg_type entry instead of here; |
| 11848 | * indexes and TOAST tables don't have their own entries either. |
| 11849 | */ |
| 11850 | if (tuple_class->relkind != RELKIND_COMPOSITE_TYPE && |
| 11851 | tuple_class->relkind != RELKIND_INDEX && |
| 11852 | tuple_class->relkind != RELKIND_PARTITIONED_INDEX && |
| 11853 | tuple_class->relkind != RELKIND_TOASTVALUE) |
| 11854 | changeDependencyOnOwner(RelationRelationId, relationOid, |
| 11855 | newOwnerId); |
| 11856 | |
| 11857 | /* |
| 11858 | * Also change the ownership of the table's row type, if it has one |
| 11859 | */ |
| 11860 | if (tuple_class->relkind != RELKIND_INDEX && |
| 11861 | tuple_class->relkind != RELKIND_PARTITIONED_INDEX) |
| 11862 | AlterTypeOwnerInternal(tuple_class->reltype, newOwnerId); |
| 11863 | |
| 11864 | /* |
| 11865 | * If we are operating on a table or materialized view, also change |
| 11866 | * the ownership of any indexes and sequences that belong to the |
| 11867 | * relation, as well as its toast table (if it has one). |
| 11868 | */ |
| 11869 | if (tuple_class->relkind == RELKIND_RELATION || |
| 11870 | tuple_class->relkind == RELKIND_PARTITIONED_TABLE || |
| 11871 | tuple_class->relkind == RELKIND_MATVIEW || |
| 11872 | tuple_class->relkind == RELKIND_TOASTVALUE) |
| 11873 | { |
| 11874 | List *index_oid_list; |
| 11875 | ListCell *i; |
| 11876 | |
| 11877 | /* Find all the indexes belonging to this relation */ |
| 11878 | index_oid_list = RelationGetIndexList(target_rel); |
| 11879 | |
| 11880 | /* For each index, recursively change its ownership */ |
| 11881 | foreach(i, index_oid_list) |
| 11882 | ATExecChangeOwner(lfirst_oid(i), newOwnerId, true, lockmode); |
| 11883 | |
| 11884 | list_free(index_oid_list); |
| 11885 | } |
| 11886 | |
| 11887 | /* If it has a toast table, recurse to change its ownership */ |
| 11888 | if (tuple_class->reltoastrelid != InvalidOid) |
| 11889 | ATExecChangeOwner(tuple_class->reltoastrelid, newOwnerId, |
| 11890 | true, lockmode); |
| 11891 | |
| 11892 | /* If it has dependent sequences, recurse to change them too */ |
| 11893 | change_owner_recurse_to_sequences(relationOid, newOwnerId, lockmode); |
| 11894 | } |
| 11895 | |
| 11896 | InvokeObjectPostAlterHook(RelationRelationId, relationOid, 0); |
| 11897 | |
| 11898 | ReleaseSysCache(tuple); |
| 11899 | table_close(class_rel, RowExclusiveLock); |
| 11900 | relation_close(target_rel, NoLock); |
| 11901 | } |
| 11902 | |
| 11903 | /* |
| 11904 | * change_owner_fix_column_acls |
| 11905 | * |
| 11906 | * Helper function for ATExecChangeOwner. Scan the columns of the table |
| 11907 | * and fix any non-null column ACLs to reflect the new owner. |
| 11908 | */ |
| 11909 | static void |
| 11910 | change_owner_fix_column_acls(Oid relationOid, Oid oldOwnerId, Oid newOwnerId) |
| 11911 | { |
| 11912 | Relation attRelation; |
| 11913 | SysScanDesc scan; |
| 11914 | ScanKeyData key[1]; |
| 11915 | HeapTuple attributeTuple; |
| 11916 | |
| 11917 | attRelation = table_open(AttributeRelationId, RowExclusiveLock); |
| 11918 | ScanKeyInit(&key[0], |
| 11919 | Anum_pg_attribute_attrelid, |
| 11920 | BTEqualStrategyNumber, F_OIDEQ, |
| 11921 | ObjectIdGetDatum(relationOid)); |
| 11922 | scan = systable_beginscan(attRelation, AttributeRelidNumIndexId, |
| 11923 | true, NULL, 1, key); |
| 11924 | while (HeapTupleIsValid(attributeTuple = systable_getnext(scan))) |
| 11925 | { |
| 11926 | Form_pg_attribute att = (Form_pg_attribute) GETSTRUCT(attributeTuple); |
| 11927 | Datum repl_val[Natts_pg_attribute]; |
| 11928 | bool repl_null[Natts_pg_attribute]; |
| 11929 | bool repl_repl[Natts_pg_attribute]; |
| 11930 | Acl *newAcl; |
| 11931 | Datum aclDatum; |
| 11932 | bool isNull; |
| 11933 | HeapTuple newtuple; |
| 11934 | |
| 11935 | /* Ignore dropped columns */ |
| 11936 | if (att->attisdropped) |
| 11937 | continue; |
| 11938 | |
| 11939 | aclDatum = heap_getattr(attributeTuple, |
| 11940 | Anum_pg_attribute_attacl, |
| 11941 | RelationGetDescr(attRelation), |
| 11942 | &isNull); |
| 11943 | /* Null ACLs do not require changes */ |
| 11944 | if (isNull) |
| 11945 | continue; |
| 11946 | |
| 11947 | memset(repl_null, false, sizeof(repl_null)); |
| 11948 | memset(repl_repl, false, sizeof(repl_repl)); |
| 11949 | |
| 11950 | newAcl = aclnewowner(DatumGetAclP(aclDatum), |
| 11951 | oldOwnerId, newOwnerId); |
| 11952 | repl_repl[Anum_pg_attribute_attacl - 1] = true; |
| 11953 | repl_val[Anum_pg_attribute_attacl - 1] = PointerGetDatum(newAcl); |
| 11954 | |
| 11955 | newtuple = heap_modify_tuple(attributeTuple, |
| 11956 | RelationGetDescr(attRelation), |
| 11957 | repl_val, repl_null, repl_repl); |
| 11958 | |
| 11959 | CatalogTupleUpdate(attRelation, &newtuple->t_self, newtuple); |
| 11960 | |
| 11961 | heap_freetuple(newtuple); |
| 11962 | } |
| 11963 | systable_endscan(scan); |
| 11964 | table_close(attRelation, RowExclusiveLock); |
| 11965 | } |
| 11966 | |
| 11967 | /* |
| 11968 | * change_owner_recurse_to_sequences |
| 11969 | * |
| 11970 | * Helper function for ATExecChangeOwner. Examines pg_depend searching |
| 11971 | * for sequences that are dependent on serial columns, and changes their |
| 11972 | * ownership. |
| 11973 | */ |
| 11974 | static void |
| 11975 | change_owner_recurse_to_sequences(Oid relationOid, Oid newOwnerId, LOCKMODE lockmode) |
| 11976 | { |
| 11977 | Relation depRel; |
| 11978 | SysScanDesc scan; |
| 11979 | ScanKeyData key[2]; |
| 11980 | HeapTuple tup; |
| 11981 | |
| 11982 | /* |
| 11983 | * SERIAL sequences are those having an auto dependency on one of the |
| 11984 | * table's columns (we don't care *which* column, exactly). |
| 11985 | */ |
| 11986 | depRel = table_open(DependRelationId, AccessShareLock); |
| 11987 | |
| 11988 | ScanKeyInit(&key[0], |
| 11989 | Anum_pg_depend_refclassid, |
| 11990 | BTEqualStrategyNumber, F_OIDEQ, |
| 11991 | ObjectIdGetDatum(RelationRelationId)); |
| 11992 | ScanKeyInit(&key[1], |
| 11993 | Anum_pg_depend_refobjid, |
| 11994 | BTEqualStrategyNumber, F_OIDEQ, |
| 11995 | ObjectIdGetDatum(relationOid)); |
| 11996 | /* we leave refobjsubid unspecified */ |
| 11997 | |
| 11998 | scan = systable_beginscan(depRel, DependReferenceIndexId, true, |
| 11999 | NULL, 2, key); |
| 12000 | |
| 12001 | while (HeapTupleIsValid(tup = systable_getnext(scan))) |
| 12002 | { |
| 12003 | Form_pg_depend depForm = (Form_pg_depend) GETSTRUCT(tup); |
| 12004 | Relation seqRel; |
| 12005 | |
| 12006 | /* skip dependencies other than auto dependencies on columns */ |
| 12007 | if (depForm->refobjsubid == 0 || |
| 12008 | depForm->classid != RelationRelationId || |
| 12009 | depForm->objsubid != 0 || |
| 12010 | !(depForm->deptype == DEPENDENCY_AUTO || depForm->deptype == DEPENDENCY_INTERNAL)) |
| 12011 | continue; |
| 12012 | |
| 12013 | /* Use relation_open just in case it's an index */ |
| 12014 | seqRel = relation_open(depForm->objid, lockmode); |
| 12015 | |
| 12016 | /* skip non-sequence relations */ |
| 12017 | if (RelationGetForm(seqRel)->relkind != RELKIND_SEQUENCE) |
| 12018 | { |
| 12019 | /* No need to keep the lock */ |
| 12020 | relation_close(seqRel, lockmode); |
| 12021 | continue; |
| 12022 | } |
| 12023 | |
| 12024 | /* We don't need to close the sequence while we alter it. */ |
| 12025 | ATExecChangeOwner(depForm->objid, newOwnerId, true, lockmode); |
| 12026 | |
| 12027 | /* Now we can close it. Keep the lock till end of transaction. */ |
| 12028 | relation_close(seqRel, NoLock); |
| 12029 | } |
| 12030 | |
| 12031 | systable_endscan(scan); |
| 12032 | |
| 12033 | relation_close(depRel, AccessShareLock); |
| 12034 | } |
| 12035 | |
| 12036 | /* |
| 12037 | * ALTER TABLE CLUSTER ON |
| 12038 | * |
| 12039 | * The only thing we have to do is to change the indisclustered bits. |
| 12040 | * |
| 12041 | * Return the address of the new clustering index. |
| 12042 | */ |
| 12043 | static ObjectAddress |
| 12044 | ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode) |
| 12045 | { |
| 12046 | Oid indexOid; |
| 12047 | ObjectAddress address; |
| 12048 | |
| 12049 | indexOid = get_relname_relid(indexName, rel->rd_rel->relnamespace); |
| 12050 | |
| 12051 | if (!OidIsValid(indexOid)) |
| 12052 | ereport(ERROR, |
| 12053 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
| 12054 | errmsg("index \"%s\" for table \"%s\" does not exist" , |
| 12055 | indexName, RelationGetRelationName(rel)))); |
| 12056 | |
| 12057 | /* Check index is valid to cluster on */ |
| 12058 | check_index_is_clusterable(rel, indexOid, false, lockmode); |
| 12059 | |
| 12060 | /* And do the work */ |
| 12061 | mark_index_clustered(rel, indexOid, false); |
| 12062 | |
| 12063 | ObjectAddressSet(address, |
| 12064 | RelationRelationId, indexOid); |
| 12065 | |
| 12066 | return address; |
| 12067 | } |
| 12068 | |
| 12069 | /* |
| 12070 | * ALTER TABLE SET WITHOUT CLUSTER |
| 12071 | * |
| 12072 | * We have to find any indexes on the table that have indisclustered bit |
| 12073 | * set and turn it off. |
| 12074 | */ |
| 12075 | static void |
| 12076 | ATExecDropCluster(Relation rel, LOCKMODE lockmode) |
| 12077 | { |
| 12078 | mark_index_clustered(rel, InvalidOid, false); |
| 12079 | } |
| 12080 | |
| 12081 | /* |
| 12082 | * ALTER TABLE SET TABLESPACE |
| 12083 | */ |
| 12084 | static void |
| 12085 | ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, const char *tablespacename, LOCKMODE lockmode) |
| 12086 | { |
| 12087 | Oid tablespaceId; |
| 12088 | |
| 12089 | /* Check that the tablespace exists */ |
| 12090 | tablespaceId = get_tablespace_oid(tablespacename, false); |
| 12091 | |
| 12092 | /* Check permissions except when moving to database's default */ |
| 12093 | if (OidIsValid(tablespaceId) && tablespaceId != MyDatabaseTableSpace) |
| 12094 | { |
| 12095 | AclResult aclresult; |
| 12096 | |
| 12097 | aclresult = pg_tablespace_aclcheck(tablespaceId, GetUserId(), ACL_CREATE); |
| 12098 | if (aclresult != ACLCHECK_OK) |
| 12099 | aclcheck_error(aclresult, OBJECT_TABLESPACE, tablespacename); |
| 12100 | } |
| 12101 | |
| 12102 | /* Save info for Phase 3 to do the real work */ |
| 12103 | if (OidIsValid(tab->newTableSpace)) |
| 12104 | ereport(ERROR, |
| 12105 | (errcode(ERRCODE_SYNTAX_ERROR), |
| 12106 | errmsg("cannot have multiple SET TABLESPACE subcommands" ))); |
| 12107 | |
| 12108 | tab->newTableSpace = tablespaceId; |
| 12109 | } |
| 12110 | |
| 12111 | /* |
| 12112 | * Set, reset, or replace reloptions. |
| 12113 | */ |
| 12114 | static void |
| 12115 | ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, |
| 12116 | LOCKMODE lockmode) |
| 12117 | { |
| 12118 | Oid relid; |
| 12119 | Relation pgclass; |
| 12120 | HeapTuple tuple; |
| 12121 | HeapTuple newtuple; |
| 12122 | Datum datum; |
| 12123 | bool isnull; |
| 12124 | Datum newOptions; |
| 12125 | Datum repl_val[Natts_pg_class]; |
| 12126 | bool repl_null[Natts_pg_class]; |
| 12127 | bool repl_repl[Natts_pg_class]; |
| 12128 | static char *validnsps[] = HEAP_RELOPT_NAMESPACES; |
| 12129 | |
| 12130 | if (defList == NIL && operation != AT_ReplaceRelOptions) |
| 12131 | return; /* nothing to do */ |
| 12132 | |
| 12133 | pgclass = table_open(RelationRelationId, RowExclusiveLock); |
| 12134 | |
| 12135 | /* Fetch heap tuple */ |
| 12136 | relid = RelationGetRelid(rel); |
| 12137 | tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); |
| 12138 | if (!HeapTupleIsValid(tuple)) |
| 12139 | elog(ERROR, "cache lookup failed for relation %u" , relid); |
| 12140 | |
| 12141 | if (operation == AT_ReplaceRelOptions) |
| 12142 | { |
| 12143 | /* |
| 12144 | * If we're supposed to replace the reloptions list, we just pretend |
| 12145 | * there were none before. |
| 12146 | */ |
| 12147 | datum = (Datum) 0; |
| 12148 | isnull = true; |
| 12149 | } |
| 12150 | else |
| 12151 | { |
| 12152 | /* Get the old reloptions */ |
| 12153 | datum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, |
| 12154 | &isnull); |
| 12155 | } |
| 12156 | |
| 12157 | /* Generate new proposed reloptions (text array) */ |
| 12158 | newOptions = transformRelOptions(isnull ? (Datum) 0 : datum, |
| 12159 | defList, NULL, validnsps, false, |
| 12160 | operation == AT_ResetRelOptions); |
| 12161 | |
| 12162 | /* Validate */ |
| 12163 | switch (rel->rd_rel->relkind) |
| 12164 | { |
| 12165 | case RELKIND_RELATION: |
| 12166 | case RELKIND_TOASTVALUE: |
| 12167 | case RELKIND_MATVIEW: |
| 12168 | case RELKIND_PARTITIONED_TABLE: |
| 12169 | (void) heap_reloptions(rel->rd_rel->relkind, newOptions, true); |
| 12170 | break; |
| 12171 | case RELKIND_VIEW: |
| 12172 | (void) view_reloptions(newOptions, true); |
| 12173 | break; |
| 12174 | case RELKIND_INDEX: |
| 12175 | case RELKIND_PARTITIONED_INDEX: |
| 12176 | (void) index_reloptions(rel->rd_indam->amoptions, newOptions, true); |
| 12177 | break; |
| 12178 | default: |
| 12179 | ereport(ERROR, |
| 12180 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 12181 | errmsg("\"%s\" is not a table, view, materialized view, index, or TOAST table" , |
| 12182 | RelationGetRelationName(rel)))); |
| 12183 | break; |
| 12184 | } |
| 12185 | |
| 12186 | /* Special-case validation of view options */ |
| 12187 | if (rel->rd_rel->relkind == RELKIND_VIEW) |
| 12188 | { |
| 12189 | Query *view_query = get_view_query(rel); |
| 12190 | List *view_options = untransformRelOptions(newOptions); |
| 12191 | ListCell *cell; |
| 12192 | bool check_option = false; |
| 12193 | |
| 12194 | foreach(cell, view_options) |
| 12195 | { |
| 12196 | DefElem *defel = (DefElem *) lfirst(cell); |
| 12197 | |
| 12198 | if (strcmp(defel->defname, "check_option" ) == 0) |
| 12199 | check_option = true; |
| 12200 | } |
| 12201 | |
| 12202 | /* |
| 12203 | * If the check option is specified, look to see if the view is |
| 12204 | * actually auto-updatable or not. |
| 12205 | */ |
| 12206 | if (check_option) |
| 12207 | { |
| 12208 | const char *view_updatable_error = |
| 12209 | view_query_is_auto_updatable(view_query, true); |
| 12210 | |
| 12211 | if (view_updatable_error) |
| 12212 | ereport(ERROR, |
| 12213 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 12214 | errmsg("WITH CHECK OPTION is supported only on automatically updatable views" ), |
| 12215 | errhint("%s" , _(view_updatable_error)))); |
| 12216 | } |
| 12217 | } |
| 12218 | |
| 12219 | /* |
| 12220 | * All we need do here is update the pg_class row; the new options will be |
| 12221 | * propagated into relcaches during post-commit cache inval. |
| 12222 | */ |
| 12223 | memset(repl_val, 0, sizeof(repl_val)); |
| 12224 | memset(repl_null, false, sizeof(repl_null)); |
| 12225 | memset(repl_repl, false, sizeof(repl_repl)); |
| 12226 | |
| 12227 | if (newOptions != (Datum) 0) |
| 12228 | repl_val[Anum_pg_class_reloptions - 1] = newOptions; |
| 12229 | else |
| 12230 | repl_null[Anum_pg_class_reloptions - 1] = true; |
| 12231 | |
| 12232 | repl_repl[Anum_pg_class_reloptions - 1] = true; |
| 12233 | |
| 12234 | newtuple = heap_modify_tuple(tuple, RelationGetDescr(pgclass), |
| 12235 | repl_val, repl_null, repl_repl); |
| 12236 | |
| 12237 | CatalogTupleUpdate(pgclass, &newtuple->t_self, newtuple); |
| 12238 | |
| 12239 | InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0); |
| 12240 | |
| 12241 | heap_freetuple(newtuple); |
| 12242 | |
| 12243 | ReleaseSysCache(tuple); |
| 12244 | |
| 12245 | /* repeat the whole exercise for the toast table, if there's one */ |
| 12246 | if (OidIsValid(rel->rd_rel->reltoastrelid)) |
| 12247 | { |
| 12248 | Relation toastrel; |
| 12249 | Oid toastid = rel->rd_rel->reltoastrelid; |
| 12250 | |
| 12251 | toastrel = table_open(toastid, lockmode); |
| 12252 | |
| 12253 | /* Fetch heap tuple */ |
| 12254 | tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(toastid)); |
| 12255 | if (!HeapTupleIsValid(tuple)) |
| 12256 | elog(ERROR, "cache lookup failed for relation %u" , toastid); |
| 12257 | |
| 12258 | if (operation == AT_ReplaceRelOptions) |
| 12259 | { |
| 12260 | /* |
| 12261 | * If we're supposed to replace the reloptions list, we just |
| 12262 | * pretend there were none before. |
| 12263 | */ |
| 12264 | datum = (Datum) 0; |
| 12265 | isnull = true; |
| 12266 | } |
| 12267 | else |
| 12268 | { |
| 12269 | /* Get the old reloptions */ |
| 12270 | datum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, |
| 12271 | &isnull); |
| 12272 | } |
| 12273 | |
| 12274 | newOptions = transformRelOptions(isnull ? (Datum) 0 : datum, |
| 12275 | defList, "toast" , validnsps, false, |
| 12276 | operation == AT_ResetRelOptions); |
| 12277 | |
| 12278 | (void) heap_reloptions(RELKIND_TOASTVALUE, newOptions, true); |
| 12279 | |
| 12280 | memset(repl_val, 0, sizeof(repl_val)); |
| 12281 | memset(repl_null, false, sizeof(repl_null)); |
| 12282 | memset(repl_repl, false, sizeof(repl_repl)); |
| 12283 | |
| 12284 | if (newOptions != (Datum) 0) |
| 12285 | repl_val[Anum_pg_class_reloptions - 1] = newOptions; |
| 12286 | else |
| 12287 | repl_null[Anum_pg_class_reloptions - 1] = true; |
| 12288 | |
| 12289 | repl_repl[Anum_pg_class_reloptions - 1] = true; |
| 12290 | |
| 12291 | newtuple = heap_modify_tuple(tuple, RelationGetDescr(pgclass), |
| 12292 | repl_val, repl_null, repl_repl); |
| 12293 | |
| 12294 | CatalogTupleUpdate(pgclass, &newtuple->t_self, newtuple); |
| 12295 | |
| 12296 | InvokeObjectPostAlterHookArg(RelationRelationId, |
| 12297 | RelationGetRelid(toastrel), 0, |
| 12298 | InvalidOid, true); |
| 12299 | |
| 12300 | heap_freetuple(newtuple); |
| 12301 | |
| 12302 | ReleaseSysCache(tuple); |
| 12303 | |
| 12304 | table_close(toastrel, NoLock); |
| 12305 | } |
| 12306 | |
| 12307 | table_close(pgclass, RowExclusiveLock); |
| 12308 | } |
| 12309 | |
| 12310 | /* |
| 12311 | * Execute ALTER TABLE SET TABLESPACE for cases where there is no tuple |
| 12312 | * rewriting to be done, so we just want to copy the data as fast as possible. |
| 12313 | */ |
| 12314 | static void |
| 12315 | ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode) |
| 12316 | { |
| 12317 | Relation rel; |
| 12318 | Oid oldTableSpace; |
| 12319 | Oid reltoastrelid; |
| 12320 | Oid newrelfilenode; |
| 12321 | RelFileNode newrnode; |
| 12322 | Relation pg_class; |
| 12323 | HeapTuple tuple; |
| 12324 | Form_pg_class rd_rel; |
| 12325 | List *reltoastidxids = NIL; |
| 12326 | ListCell *lc; |
| 12327 | |
| 12328 | /* |
| 12329 | * Need lock here in case we are recursing to toast table or index |
| 12330 | */ |
| 12331 | rel = relation_open(tableOid, lockmode); |
| 12332 | |
| 12333 | /* |
| 12334 | * No work if no change in tablespace. |
| 12335 | */ |
| 12336 | oldTableSpace = rel->rd_rel->reltablespace; |
| 12337 | if (newTableSpace == oldTableSpace || |
| 12338 | (newTableSpace == MyDatabaseTableSpace && oldTableSpace == 0)) |
| 12339 | { |
| 12340 | InvokeObjectPostAlterHook(RelationRelationId, |
| 12341 | RelationGetRelid(rel), 0); |
| 12342 | |
| 12343 | relation_close(rel, NoLock); |
| 12344 | return; |
| 12345 | } |
| 12346 | |
| 12347 | /* |
| 12348 | * We cannot support moving mapped relations into different tablespaces. |
| 12349 | * (In particular this eliminates all shared catalogs.) |
| 12350 | */ |
| 12351 | if (RelationIsMapped(rel)) |
| 12352 | ereport(ERROR, |
| 12353 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 12354 | errmsg("cannot move system relation \"%s\"" , |
| 12355 | RelationGetRelationName(rel)))); |
| 12356 | |
| 12357 | /* Can't move a non-shared relation into pg_global */ |
| 12358 | if (newTableSpace == GLOBALTABLESPACE_OID) |
| 12359 | ereport(ERROR, |
| 12360 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 12361 | errmsg("only shared relations can be placed in pg_global tablespace" ))); |
| 12362 | |
| 12363 | /* |
| 12364 | * Don't allow moving temp tables of other backends ... their local buffer |
| 12365 | * manager is not going to cope. |
| 12366 | */ |
| 12367 | if (RELATION_IS_OTHER_TEMP(rel)) |
| 12368 | ereport(ERROR, |
| 12369 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 12370 | errmsg("cannot move temporary tables of other sessions" ))); |
| 12371 | |
| 12372 | reltoastrelid = rel->rd_rel->reltoastrelid; |
| 12373 | /* Fetch the list of indexes on toast relation if necessary */ |
| 12374 | if (OidIsValid(reltoastrelid)) |
| 12375 | { |
| 12376 | Relation toastRel = relation_open(reltoastrelid, lockmode); |
| 12377 | |
| 12378 | reltoastidxids = RelationGetIndexList(toastRel); |
| 12379 | relation_close(toastRel, lockmode); |
| 12380 | } |
| 12381 | |
| 12382 | /* Get a modifiable copy of the relation's pg_class row */ |
| 12383 | pg_class = table_open(RelationRelationId, RowExclusiveLock); |
| 12384 | |
| 12385 | tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(tableOid)); |
| 12386 | if (!HeapTupleIsValid(tuple)) |
| 12387 | elog(ERROR, "cache lookup failed for relation %u" , tableOid); |
| 12388 | rd_rel = (Form_pg_class) GETSTRUCT(tuple); |
| 12389 | |
| 12390 | /* |
| 12391 | * Relfilenodes are not unique in databases across tablespaces, so we need |
| 12392 | * to allocate a new one in the new tablespace. |
| 12393 | */ |
| 12394 | newrelfilenode = GetNewRelFileNode(newTableSpace, NULL, |
| 12395 | rel->rd_rel->relpersistence); |
| 12396 | |
| 12397 | /* Open old and new relation */ |
| 12398 | newrnode = rel->rd_node; |
| 12399 | newrnode.relNode = newrelfilenode; |
| 12400 | newrnode.spcNode = newTableSpace; |
| 12401 | |
| 12402 | /* hand off to AM to actually create the new filenode and copy the data */ |
| 12403 | if (rel->rd_rel->relkind == RELKIND_INDEX) |
| 12404 | { |
| 12405 | index_copy_data(rel, newrnode); |
| 12406 | } |
| 12407 | else |
| 12408 | { |
| 12409 | Assert(rel->rd_rel->relkind == RELKIND_RELATION || |
| 12410 | rel->rd_rel->relkind == RELKIND_MATVIEW || |
| 12411 | rel->rd_rel->relkind == RELKIND_TOASTVALUE); |
| 12412 | table_relation_copy_data(rel, &newrnode); |
| 12413 | } |
| 12414 | |
| 12415 | /* |
| 12416 | * Update the pg_class row. |
| 12417 | * |
| 12418 | * NB: This wouldn't work if ATExecSetTableSpace() were allowed to be |
| 12419 | * executed on pg_class or its indexes (the above copy wouldn't contain |
| 12420 | * the updated pg_class entry), but that's forbidden above. |
| 12421 | */ |
| 12422 | rd_rel->reltablespace = (newTableSpace == MyDatabaseTableSpace) ? InvalidOid : newTableSpace; |
| 12423 | rd_rel->relfilenode = newrelfilenode; |
| 12424 | CatalogTupleUpdate(pg_class, &tuple->t_self, tuple); |
| 12425 | |
| 12426 | InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0); |
| 12427 | |
| 12428 | heap_freetuple(tuple); |
| 12429 | |
| 12430 | table_close(pg_class, RowExclusiveLock); |
| 12431 | |
| 12432 | relation_close(rel, NoLock); |
| 12433 | |
| 12434 | /* Make sure the reltablespace change is visible */ |
| 12435 | CommandCounterIncrement(); |
| 12436 | |
| 12437 | /* Move associated toast relation and/or indexes, too */ |
| 12438 | if (OidIsValid(reltoastrelid)) |
| 12439 | ATExecSetTableSpace(reltoastrelid, newTableSpace, lockmode); |
| 12440 | foreach(lc, reltoastidxids) |
| 12441 | ATExecSetTableSpace(lfirst_oid(lc), newTableSpace, lockmode); |
| 12442 | |
| 12443 | /* Clean up */ |
| 12444 | list_free(reltoastidxids); |
| 12445 | } |
| 12446 | |
| 12447 | /* |
| 12448 | * Special handling of ALTER TABLE SET TABLESPACE for relations with no |
| 12449 | * storage that have an interest in preserving tablespace. |
| 12450 | * |
| 12451 | * Since these have no storage the tablespace can be updated with a simple |
| 12452 | * metadata only operation to update the tablespace. |
| 12453 | */ |
| 12454 | static void |
| 12455 | ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace) |
| 12456 | { |
| 12457 | HeapTuple tuple; |
| 12458 | Oid oldTableSpace; |
| 12459 | Relation pg_class; |
| 12460 | Form_pg_class rd_rel; |
| 12461 | Oid reloid = RelationGetRelid(rel); |
| 12462 | |
| 12463 | /* |
| 12464 | * Shouldn't be called on relations having storage; these are processed in |
| 12465 | * phase 3. |
| 12466 | */ |
| 12467 | Assert(!RELKIND_HAS_STORAGE(rel->rd_rel->relkind)); |
| 12468 | |
| 12469 | /* Can't allow a non-shared relation in pg_global */ |
| 12470 | if (newTableSpace == GLOBALTABLESPACE_OID) |
| 12471 | ereport(ERROR, |
| 12472 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 12473 | errmsg("only shared relations can be placed in pg_global tablespace" ))); |
| 12474 | |
| 12475 | /* |
| 12476 | * No work if no change in tablespace. |
| 12477 | */ |
| 12478 | oldTableSpace = rel->rd_rel->reltablespace; |
| 12479 | if (newTableSpace == oldTableSpace || |
| 12480 | (newTableSpace == MyDatabaseTableSpace && oldTableSpace == 0)) |
| 12481 | { |
| 12482 | InvokeObjectPostAlterHook(RelationRelationId, reloid, 0); |
| 12483 | return; |
| 12484 | } |
| 12485 | |
| 12486 | /* Get a modifiable copy of the relation's pg_class row */ |
| 12487 | pg_class = table_open(RelationRelationId, RowExclusiveLock); |
| 12488 | |
| 12489 | tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid)); |
| 12490 | if (!HeapTupleIsValid(tuple)) |
| 12491 | elog(ERROR, "cache lookup failed for relation %u" , reloid); |
| 12492 | rd_rel = (Form_pg_class) GETSTRUCT(tuple); |
| 12493 | |
| 12494 | /* update the pg_class row */ |
| 12495 | rd_rel->reltablespace = (newTableSpace == MyDatabaseTableSpace) ? InvalidOid : newTableSpace; |
| 12496 | CatalogTupleUpdate(pg_class, &tuple->t_self, tuple); |
| 12497 | |
| 12498 | InvokeObjectPostAlterHook(RelationRelationId, reloid, 0); |
| 12499 | |
| 12500 | heap_freetuple(tuple); |
| 12501 | |
| 12502 | table_close(pg_class, RowExclusiveLock); |
| 12503 | |
| 12504 | /* Make sure the reltablespace change is visible */ |
| 12505 | CommandCounterIncrement(); |
| 12506 | } |
| 12507 | |
| 12508 | /* |
| 12509 | * Alter Table ALL ... SET TABLESPACE |
| 12510 | * |
| 12511 | * Allows a user to move all objects of some type in a given tablespace in the |
| 12512 | * current database to another tablespace. Objects can be chosen based on the |
| 12513 | * owner of the object also, to allow users to move only their objects. |
| 12514 | * The user must have CREATE rights on the new tablespace, as usual. The main |
| 12515 | * permissions handling is done by the lower-level table move function. |
| 12516 | * |
| 12517 | * All to-be-moved objects are locked first. If NOWAIT is specified and the |
| 12518 | * lock can't be acquired then we ereport(ERROR). |
| 12519 | */ |
| 12520 | Oid |
| 12521 | AlterTableMoveAll(AlterTableMoveAllStmt *stmt) |
| 12522 | { |
| 12523 | List *relations = NIL; |
| 12524 | ListCell *l; |
| 12525 | ScanKeyData key[1]; |
| 12526 | Relation rel; |
| 12527 | TableScanDesc scan; |
| 12528 | HeapTuple tuple; |
| 12529 | Oid orig_tablespaceoid; |
| 12530 | Oid new_tablespaceoid; |
| 12531 | List *role_oids = roleSpecsToIds(stmt->roles); |
| 12532 | |
| 12533 | /* Ensure we were not asked to move something we can't */ |
| 12534 | if (stmt->objtype != OBJECT_TABLE && stmt->objtype != OBJECT_INDEX && |
| 12535 | stmt->objtype != OBJECT_MATVIEW) |
| 12536 | ereport(ERROR, |
| 12537 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 12538 | errmsg("only tables, indexes, and materialized views exist in tablespaces" ))); |
| 12539 | |
| 12540 | /* Get the orig and new tablespace OIDs */ |
| 12541 | orig_tablespaceoid = get_tablespace_oid(stmt->orig_tablespacename, false); |
| 12542 | new_tablespaceoid = get_tablespace_oid(stmt->new_tablespacename, false); |
| 12543 | |
| 12544 | /* Can't move shared relations in to or out of pg_global */ |
| 12545 | /* This is also checked by ATExecSetTableSpace, but nice to stop earlier */ |
| 12546 | if (orig_tablespaceoid == GLOBALTABLESPACE_OID || |
| 12547 | new_tablespaceoid == GLOBALTABLESPACE_OID) |
| 12548 | ereport(ERROR, |
| 12549 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 12550 | errmsg("cannot move relations in to or out of pg_global tablespace" ))); |
| 12551 | |
| 12552 | /* |
| 12553 | * Must have CREATE rights on the new tablespace, unless it is the |
| 12554 | * database default tablespace (which all users implicitly have CREATE |
| 12555 | * rights on). |
| 12556 | */ |
| 12557 | if (OidIsValid(new_tablespaceoid) && new_tablespaceoid != MyDatabaseTableSpace) |
| 12558 | { |
| 12559 | AclResult aclresult; |
| 12560 | |
| 12561 | aclresult = pg_tablespace_aclcheck(new_tablespaceoid, GetUserId(), |
| 12562 | ACL_CREATE); |
| 12563 | if (aclresult != ACLCHECK_OK) |
| 12564 | aclcheck_error(aclresult, OBJECT_TABLESPACE, |
| 12565 | get_tablespace_name(new_tablespaceoid)); |
| 12566 | } |
| 12567 | |
| 12568 | /* |
| 12569 | * Now that the checks are done, check if we should set either to |
| 12570 | * InvalidOid because it is our database's default tablespace. |
| 12571 | */ |
| 12572 | if (orig_tablespaceoid == MyDatabaseTableSpace) |
| 12573 | orig_tablespaceoid = InvalidOid; |
| 12574 | |
| 12575 | if (new_tablespaceoid == MyDatabaseTableSpace) |
| 12576 | new_tablespaceoid = InvalidOid; |
| 12577 | |
| 12578 | /* no-op */ |
| 12579 | if (orig_tablespaceoid == new_tablespaceoid) |
| 12580 | return new_tablespaceoid; |
| 12581 | |
| 12582 | /* |
| 12583 | * Walk the list of objects in the tablespace and move them. This will |
| 12584 | * only find objects in our database, of course. |
| 12585 | */ |
| 12586 | ScanKeyInit(&key[0], |
| 12587 | Anum_pg_class_reltablespace, |
| 12588 | BTEqualStrategyNumber, F_OIDEQ, |
| 12589 | ObjectIdGetDatum(orig_tablespaceoid)); |
| 12590 | |
| 12591 | rel = table_open(RelationRelationId, AccessShareLock); |
| 12592 | scan = table_beginscan_catalog(rel, 1, key); |
| 12593 | while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) |
| 12594 | { |
| 12595 | Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple); |
| 12596 | Oid relOid = relForm->oid; |
| 12597 | |
| 12598 | /* |
| 12599 | * Do not move objects in pg_catalog as part of this, if an admin |
| 12600 | * really wishes to do so, they can issue the individual ALTER |
| 12601 | * commands directly. |
| 12602 | * |
| 12603 | * Also, explicitly avoid any shared tables, temp tables, or TOAST |
| 12604 | * (TOAST will be moved with the main table). |
| 12605 | */ |
| 12606 | if (IsCatalogNamespace(relForm->relnamespace) || |
| 12607 | relForm->relisshared || |
| 12608 | isAnyTempNamespace(relForm->relnamespace) || |
| 12609 | IsToastNamespace(relForm->relnamespace)) |
| 12610 | continue; |
| 12611 | |
| 12612 | /* Only move the object type requested */ |
| 12613 | if ((stmt->objtype == OBJECT_TABLE && |
| 12614 | relForm->relkind != RELKIND_RELATION && |
| 12615 | relForm->relkind != RELKIND_PARTITIONED_TABLE) || |
| 12616 | (stmt->objtype == OBJECT_INDEX && |
| 12617 | relForm->relkind != RELKIND_INDEX && |
| 12618 | relForm->relkind != RELKIND_PARTITIONED_INDEX) || |
| 12619 | (stmt->objtype == OBJECT_MATVIEW && |
| 12620 | relForm->relkind != RELKIND_MATVIEW)) |
| 12621 | continue; |
| 12622 | |
| 12623 | /* Check if we are only moving objects owned by certain roles */ |
| 12624 | if (role_oids != NIL && !list_member_oid(role_oids, relForm->relowner)) |
| 12625 | continue; |
| 12626 | |
| 12627 | /* |
| 12628 | * Handle permissions-checking here since we are locking the tables |
| 12629 | * and also to avoid doing a bunch of work only to fail part-way. Note |
| 12630 | * that permissions will also be checked by AlterTableInternal(). |
| 12631 | * |
| 12632 | * Caller must be considered an owner on the table to move it. |
| 12633 | */ |
| 12634 | if (!pg_class_ownercheck(relOid, GetUserId())) |
| 12635 | aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relOid)), |
| 12636 | NameStr(relForm->relname)); |
| 12637 | |
| 12638 | if (stmt->nowait && |
| 12639 | !ConditionalLockRelationOid(relOid, AccessExclusiveLock)) |
| 12640 | ereport(ERROR, |
| 12641 | (errcode(ERRCODE_OBJECT_IN_USE), |
| 12642 | errmsg("aborting because lock on relation \"%s.%s\" is not available" , |
| 12643 | get_namespace_name(relForm->relnamespace), |
| 12644 | NameStr(relForm->relname)))); |
| 12645 | else |
| 12646 | LockRelationOid(relOid, AccessExclusiveLock); |
| 12647 | |
| 12648 | /* Add to our list of objects to move */ |
| 12649 | relations = lappend_oid(relations, relOid); |
| 12650 | } |
| 12651 | |
| 12652 | table_endscan(scan); |
| 12653 | table_close(rel, AccessShareLock); |
| 12654 | |
| 12655 | if (relations == NIL) |
| 12656 | ereport(NOTICE, |
| 12657 | (errcode(ERRCODE_NO_DATA_FOUND), |
| 12658 | errmsg("no matching relations in tablespace \"%s\" found" , |
| 12659 | orig_tablespaceoid == InvalidOid ? "(database default)" : |
| 12660 | get_tablespace_name(orig_tablespaceoid)))); |
| 12661 | |
| 12662 | /* Everything is locked, loop through and move all of the relations. */ |
| 12663 | foreach(l, relations) |
| 12664 | { |
| 12665 | List *cmds = NIL; |
| 12666 | AlterTableCmd *cmd = makeNode(AlterTableCmd); |
| 12667 | |
| 12668 | cmd->subtype = AT_SetTableSpace; |
| 12669 | cmd->name = stmt->new_tablespacename; |
| 12670 | |
| 12671 | cmds = lappend(cmds, cmd); |
| 12672 | |
| 12673 | EventTriggerAlterTableStart((Node *) stmt); |
| 12674 | /* OID is set by AlterTableInternal */ |
| 12675 | AlterTableInternal(lfirst_oid(l), cmds, false); |
| 12676 | EventTriggerAlterTableEnd(); |
| 12677 | } |
| 12678 | |
| 12679 | return new_tablespaceoid; |
| 12680 | } |
| 12681 | |
| 12682 | static void |
| 12683 | index_copy_data(Relation rel, RelFileNode newrnode) |
| 12684 | { |
| 12685 | SMgrRelation dstrel; |
| 12686 | |
| 12687 | dstrel = smgropen(newrnode, rel->rd_backend); |
| 12688 | RelationOpenSmgr(rel); |
| 12689 | |
| 12690 | /* |
| 12691 | * Since we copy the file directly without looking at the shared buffers, |
| 12692 | * we'd better first flush out any pages of the source relation that are |
| 12693 | * in shared buffers. We assume no new changes will be made while we are |
| 12694 | * holding exclusive lock on the rel. |
| 12695 | */ |
| 12696 | FlushRelationBuffers(rel); |
| 12697 | |
| 12698 | /* |
| 12699 | * Create and copy all forks of the relation, and schedule unlinking of |
| 12700 | * old physical files. |
| 12701 | * |
| 12702 | * NOTE: any conflict in relfilenode value will be caught in |
| 12703 | * RelationCreateStorage(). |
| 12704 | */ |
| 12705 | RelationCreateStorage(newrnode, rel->rd_rel->relpersistence); |
| 12706 | |
| 12707 | /* copy main fork */ |
| 12708 | RelationCopyStorage(rel->rd_smgr, dstrel, MAIN_FORKNUM, |
| 12709 | rel->rd_rel->relpersistence); |
| 12710 | |
| 12711 | /* copy those extra forks that exist */ |
| 12712 | for (ForkNumber forkNum = MAIN_FORKNUM + 1; |
| 12713 | forkNum <= MAX_FORKNUM; forkNum++) |
| 12714 | { |
| 12715 | if (smgrexists(rel->rd_smgr, forkNum)) |
| 12716 | { |
| 12717 | smgrcreate(dstrel, forkNum, false); |
| 12718 | |
| 12719 | /* |
| 12720 | * WAL log creation if the relation is persistent, or this is the |
| 12721 | * init fork of an unlogged relation. |
| 12722 | */ |
| 12723 | if (rel->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT || |
| 12724 | (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED && |
| 12725 | forkNum == INIT_FORKNUM)) |
| 12726 | log_smgrcreate(&newrnode, forkNum); |
| 12727 | RelationCopyStorage(rel->rd_smgr, dstrel, forkNum, |
| 12728 | rel->rd_rel->relpersistence); |
| 12729 | } |
| 12730 | } |
| 12731 | |
| 12732 | /* drop old relation, and close new one */ |
| 12733 | RelationDropStorage(rel); |
| 12734 | smgrclose(dstrel); |
| 12735 | } |
| 12736 | |
| 12737 | /* |
| 12738 | * ALTER TABLE ENABLE/DISABLE TRIGGER |
| 12739 | * |
| 12740 | * We just pass this off to trigger.c. |
| 12741 | */ |
| 12742 | static void |
| 12743 | ATExecEnableDisableTrigger(Relation rel, const char *trigname, |
| 12744 | char fires_when, bool skip_system, LOCKMODE lockmode) |
| 12745 | { |
| 12746 | EnableDisableTrigger(rel, trigname, fires_when, skip_system, lockmode); |
| 12747 | } |
| 12748 | |
| 12749 | /* |
| 12750 | * ALTER TABLE ENABLE/DISABLE RULE |
| 12751 | * |
| 12752 | * We just pass this off to rewriteDefine.c. |
| 12753 | */ |
| 12754 | static void |
| 12755 | ATExecEnableDisableRule(Relation rel, const char *rulename, |
| 12756 | char fires_when, LOCKMODE lockmode) |
| 12757 | { |
| 12758 | EnableDisableRule(rel, rulename, fires_when); |
| 12759 | } |
| 12760 | |
| 12761 | /* |
| 12762 | * ALTER TABLE INHERIT |
| 12763 | * |
| 12764 | * Add a parent to the child's parents. This verifies that all the columns and |
| 12765 | * check constraints of the parent appear in the child and that they have the |
| 12766 | * same data types and expressions. |
| 12767 | */ |
| 12768 | static void |
| 12769 | ATPrepAddInherit(Relation child_rel) |
| 12770 | { |
| 12771 | if (child_rel->rd_rel->reloftype) |
| 12772 | ereport(ERROR, |
| 12773 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 12774 | errmsg("cannot change inheritance of typed table" ))); |
| 12775 | |
| 12776 | if (child_rel->rd_rel->relispartition) |
| 12777 | ereport(ERROR, |
| 12778 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 12779 | errmsg("cannot change inheritance of a partition" ))); |
| 12780 | |
| 12781 | if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) |
| 12782 | ereport(ERROR, |
| 12783 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 12784 | errmsg("cannot change inheritance of partitioned table" ))); |
| 12785 | } |
| 12786 | |
| 12787 | /* |
| 12788 | * Return the address of the new parent relation. |
| 12789 | */ |
| 12790 | static ObjectAddress |
| 12791 | ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode) |
| 12792 | { |
| 12793 | Relation parent_rel; |
| 12794 | List *children; |
| 12795 | ObjectAddress address; |
| 12796 | const char *trigger_name; |
| 12797 | |
| 12798 | /* |
| 12799 | * A self-exclusive lock is needed here. See the similar case in |
| 12800 | * MergeAttributes() for a full explanation. |
| 12801 | */ |
| 12802 | parent_rel = table_openrv(parent, ShareUpdateExclusiveLock); |
| 12803 | |
| 12804 | /* |
| 12805 | * Must be owner of both parent and child -- child was checked by |
| 12806 | * ATSimplePermissions call in ATPrepCmd |
| 12807 | */ |
| 12808 | ATSimplePermissions(parent_rel, ATT_TABLE | ATT_FOREIGN_TABLE); |
| 12809 | |
| 12810 | /* Permanent rels cannot inherit from temporary ones */ |
| 12811 | if (parent_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP && |
| 12812 | child_rel->rd_rel->relpersistence != RELPERSISTENCE_TEMP) |
| 12813 | ereport(ERROR, |
| 12814 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 12815 | errmsg("cannot inherit from temporary relation \"%s\"" , |
| 12816 | RelationGetRelationName(parent_rel)))); |
| 12817 | |
| 12818 | /* If parent rel is temp, it must belong to this session */ |
| 12819 | if (parent_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP && |
| 12820 | !parent_rel->rd_islocaltemp) |
| 12821 | ereport(ERROR, |
| 12822 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 12823 | errmsg("cannot inherit from temporary relation of another session" ))); |
| 12824 | |
| 12825 | /* Ditto for the child */ |
| 12826 | if (child_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP && |
| 12827 | !child_rel->rd_islocaltemp) |
| 12828 | ereport(ERROR, |
| 12829 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 12830 | errmsg("cannot inherit to temporary relation of another session" ))); |
| 12831 | |
| 12832 | /* Prevent partitioned tables from becoming inheritance parents */ |
| 12833 | if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) |
| 12834 | ereport(ERROR, |
| 12835 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 12836 | errmsg("cannot inherit from partitioned table \"%s\"" , |
| 12837 | parent->relname))); |
| 12838 | |
| 12839 | /* Likewise for partitions */ |
| 12840 | if (parent_rel->rd_rel->relispartition) |
| 12841 | ereport(ERROR, |
| 12842 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 12843 | errmsg("cannot inherit from a partition" ))); |
| 12844 | |
| 12845 | /* |
| 12846 | * Prevent circularity by seeing if proposed parent inherits from child. |
| 12847 | * (In particular, this disallows making a rel inherit from itself.) |
| 12848 | * |
| 12849 | * This is not completely bulletproof because of race conditions: in |
| 12850 | * multi-level inheritance trees, someone else could concurrently be |
| 12851 | * making another inheritance link that closes the loop but does not join |
| 12852 | * either of the rels we have locked. Preventing that seems to require |
| 12853 | * exclusive locks on the entire inheritance tree, which is a cure worse |
| 12854 | * than the disease. find_all_inheritors() will cope with circularity |
| 12855 | * anyway, so don't sweat it too much. |
| 12856 | * |
| 12857 | * We use weakest lock we can on child's children, namely AccessShareLock. |
| 12858 | */ |
| 12859 | children = find_all_inheritors(RelationGetRelid(child_rel), |
| 12860 | AccessShareLock, NULL); |
| 12861 | |
| 12862 | if (list_member_oid(children, RelationGetRelid(parent_rel))) |
| 12863 | ereport(ERROR, |
| 12864 | (errcode(ERRCODE_DUPLICATE_TABLE), |
| 12865 | errmsg("circular inheritance not allowed" ), |
| 12866 | errdetail("\"%s\" is already a child of \"%s\"." , |
| 12867 | parent->relname, |
| 12868 | RelationGetRelationName(child_rel)))); |
| 12869 | |
| 12870 | /* |
| 12871 | * If child_rel has row-level triggers with transition tables, we |
| 12872 | * currently don't allow it to become an inheritance child. See also |
| 12873 | * prohibitions in ATExecAttachPartition() and CreateTrigger(). |
| 12874 | */ |
| 12875 | trigger_name = FindTriggerIncompatibleWithInheritance(child_rel->trigdesc); |
| 12876 | if (trigger_name != NULL) |
| 12877 | ereport(ERROR, |
| 12878 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 12879 | errmsg("trigger \"%s\" prevents table \"%s\" from becoming an inheritance child" , |
| 12880 | trigger_name, RelationGetRelationName(child_rel)), |
| 12881 | errdetail("ROW triggers with transition tables are not supported in inheritance hierarchies." ))); |
| 12882 | |
| 12883 | /* OK to create inheritance */ |
| 12884 | CreateInheritance(child_rel, parent_rel); |
| 12885 | |
| 12886 | ObjectAddressSet(address, RelationRelationId, |
| 12887 | RelationGetRelid(parent_rel)); |
| 12888 | |
| 12889 | /* keep our lock on the parent relation until commit */ |
| 12890 | table_close(parent_rel, NoLock); |
| 12891 | |
| 12892 | return address; |
| 12893 | } |
| 12894 | |
| 12895 | /* |
| 12896 | * CreateInheritance |
| 12897 | * Catalog manipulation portion of creating inheritance between a child |
| 12898 | * table and a parent table. |
| 12899 | * |
| 12900 | * Common to ATExecAddInherit() and ATExecAttachPartition(). |
| 12901 | */ |
| 12902 | static void |
| 12903 | CreateInheritance(Relation child_rel, Relation parent_rel) |
| 12904 | { |
| 12905 | Relation catalogRelation; |
| 12906 | SysScanDesc scan; |
| 12907 | ScanKeyData key; |
| 12908 | HeapTuple inheritsTuple; |
| 12909 | int32 inhseqno; |
| 12910 | |
| 12911 | /* Note: get RowExclusiveLock because we will write pg_inherits below. */ |
| 12912 | catalogRelation = table_open(InheritsRelationId, RowExclusiveLock); |
| 12913 | |
| 12914 | /* |
| 12915 | * Check for duplicates in the list of parents, and determine the highest |
| 12916 | * inhseqno already present; we'll use the next one for the new parent. |
| 12917 | * Also, if proposed child is a partition, it cannot already be |
| 12918 | * inheriting. |
| 12919 | * |
| 12920 | * Note: we do not reject the case where the child already inherits from |
| 12921 | * the parent indirectly; CREATE TABLE doesn't reject comparable cases. |
| 12922 | */ |
| 12923 | ScanKeyInit(&key, |
| 12924 | Anum_pg_inherits_inhrelid, |
| 12925 | BTEqualStrategyNumber, F_OIDEQ, |
| 12926 | ObjectIdGetDatum(RelationGetRelid(child_rel))); |
| 12927 | scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, |
| 12928 | true, NULL, 1, &key); |
| 12929 | |
| 12930 | /* inhseqno sequences start at 1 */ |
| 12931 | inhseqno = 0; |
| 12932 | while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan))) |
| 12933 | { |
| 12934 | Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple); |
| 12935 | |
| 12936 | if (inh->inhparent == RelationGetRelid(parent_rel)) |
| 12937 | ereport(ERROR, |
| 12938 | (errcode(ERRCODE_DUPLICATE_TABLE), |
| 12939 | errmsg("relation \"%s\" would be inherited from more than once" , |
| 12940 | RelationGetRelationName(parent_rel)))); |
| 12941 | |
| 12942 | if (inh->inhseqno > inhseqno) |
| 12943 | inhseqno = inh->inhseqno; |
| 12944 | } |
| 12945 | systable_endscan(scan); |
| 12946 | |
| 12947 | /* Match up the columns and bump attinhcount as needed */ |
| 12948 | MergeAttributesIntoExisting(child_rel, parent_rel); |
| 12949 | |
| 12950 | /* Match up the constraints and bump coninhcount as needed */ |
| 12951 | MergeConstraintsIntoExisting(child_rel, parent_rel); |
| 12952 | |
| 12953 | /* |
| 12954 | * OK, it looks valid. Make the catalog entries that show inheritance. |
| 12955 | */ |
| 12956 | StoreCatalogInheritance1(RelationGetRelid(child_rel), |
| 12957 | RelationGetRelid(parent_rel), |
| 12958 | inhseqno + 1, |
| 12959 | catalogRelation, |
| 12960 | parent_rel->rd_rel->relkind == |
| 12961 | RELKIND_PARTITIONED_TABLE); |
| 12962 | |
| 12963 | /* Now we're done with pg_inherits */ |
| 12964 | table_close(catalogRelation, RowExclusiveLock); |
| 12965 | } |
| 12966 | |
| 12967 | /* |
| 12968 | * Obtain the source-text form of the constraint expression for a check |
| 12969 | * constraint, given its pg_constraint tuple |
| 12970 | */ |
| 12971 | static char * |
| 12972 | decompile_conbin(HeapTuple contup, TupleDesc tupdesc) |
| 12973 | { |
| 12974 | Form_pg_constraint con; |
| 12975 | bool isnull; |
| 12976 | Datum attr; |
| 12977 | Datum expr; |
| 12978 | |
| 12979 | con = (Form_pg_constraint) GETSTRUCT(contup); |
| 12980 | attr = heap_getattr(contup, Anum_pg_constraint_conbin, tupdesc, &isnull); |
| 12981 | if (isnull) |
| 12982 | elog(ERROR, "null conbin for constraint %u" , con->oid); |
| 12983 | |
| 12984 | expr = DirectFunctionCall2(pg_get_expr, attr, |
| 12985 | ObjectIdGetDatum(con->conrelid)); |
| 12986 | return TextDatumGetCString(expr); |
| 12987 | } |
| 12988 | |
| 12989 | /* |
| 12990 | * Determine whether two check constraints are functionally equivalent |
| 12991 | * |
| 12992 | * The test we apply is to see whether they reverse-compile to the same |
| 12993 | * source string. This insulates us from issues like whether attributes |
| 12994 | * have the same physical column numbers in parent and child relations. |
| 12995 | */ |
| 12996 | static bool |
| 12997 | constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc) |
| 12998 | { |
| 12999 | Form_pg_constraint acon = (Form_pg_constraint) GETSTRUCT(a); |
| 13000 | Form_pg_constraint bcon = (Form_pg_constraint) GETSTRUCT(b); |
| 13001 | |
| 13002 | if (acon->condeferrable != bcon->condeferrable || |
| 13003 | acon->condeferred != bcon->condeferred || |
| 13004 | strcmp(decompile_conbin(a, tupleDesc), |
| 13005 | decompile_conbin(b, tupleDesc)) != 0) |
| 13006 | return false; |
| 13007 | else |
| 13008 | return true; |
| 13009 | } |
| 13010 | |
| 13011 | /* |
| 13012 | * Check columns in child table match up with columns in parent, and increment |
| 13013 | * their attinhcount. |
| 13014 | * |
| 13015 | * Called by CreateInheritance |
| 13016 | * |
| 13017 | * Currently all parent columns must be found in child. Missing columns are an |
| 13018 | * error. One day we might consider creating new columns like CREATE TABLE |
| 13019 | * does. However, that is widely unpopular --- in the common use case of |
| 13020 | * partitioned tables it's a foot-gun. |
| 13021 | * |
| 13022 | * The data type must match exactly. If the parent column is NOT NULL then |
| 13023 | * the child must be as well. Defaults are not compared, however. |
| 13024 | */ |
| 13025 | static void |
| 13026 | MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel) |
| 13027 | { |
| 13028 | Relation attrrel; |
| 13029 | AttrNumber parent_attno; |
| 13030 | int parent_natts; |
| 13031 | TupleDesc tupleDesc; |
| 13032 | HeapTuple tuple; |
| 13033 | bool child_is_partition = false; |
| 13034 | |
| 13035 | attrrel = table_open(AttributeRelationId, RowExclusiveLock); |
| 13036 | |
| 13037 | tupleDesc = RelationGetDescr(parent_rel); |
| 13038 | parent_natts = tupleDesc->natts; |
| 13039 | |
| 13040 | /* If parent_rel is a partitioned table, child_rel must be a partition */ |
| 13041 | if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) |
| 13042 | child_is_partition = true; |
| 13043 | |
| 13044 | for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++) |
| 13045 | { |
| 13046 | Form_pg_attribute attribute = TupleDescAttr(tupleDesc, |
| 13047 | parent_attno - 1); |
| 13048 | char *attributeName = NameStr(attribute->attname); |
| 13049 | |
| 13050 | /* Ignore dropped columns in the parent. */ |
| 13051 | if (attribute->attisdropped) |
| 13052 | continue; |
| 13053 | |
| 13054 | /* Find same column in child (matching on column name). */ |
| 13055 | tuple = SearchSysCacheCopyAttName(RelationGetRelid(child_rel), |
| 13056 | attributeName); |
| 13057 | if (HeapTupleIsValid(tuple)) |
| 13058 | { |
| 13059 | /* Check they are same type, typmod, and collation */ |
| 13060 | Form_pg_attribute childatt = (Form_pg_attribute) GETSTRUCT(tuple); |
| 13061 | |
| 13062 | if (attribute->atttypid != childatt->atttypid || |
| 13063 | attribute->atttypmod != childatt->atttypmod) |
| 13064 | ereport(ERROR, |
| 13065 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
| 13066 | errmsg("child table \"%s\" has different type for column \"%s\"" , |
| 13067 | RelationGetRelationName(child_rel), |
| 13068 | attributeName))); |
| 13069 | |
| 13070 | if (attribute->attcollation != childatt->attcollation) |
| 13071 | ereport(ERROR, |
| 13072 | (errcode(ERRCODE_COLLATION_MISMATCH), |
| 13073 | errmsg("child table \"%s\" has different collation for column \"%s\"" , |
| 13074 | RelationGetRelationName(child_rel), |
| 13075 | attributeName))); |
| 13076 | |
| 13077 | /* |
| 13078 | * Check child doesn't discard NOT NULL property. (Other |
| 13079 | * constraints are checked elsewhere.) |
| 13080 | */ |
| 13081 | if (attribute->attnotnull && !childatt->attnotnull) |
| 13082 | ereport(ERROR, |
| 13083 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
| 13084 | errmsg("column \"%s\" in child table must be marked NOT NULL" , |
| 13085 | attributeName))); |
| 13086 | |
| 13087 | /* |
| 13088 | * OK, bump the child column's inheritance count. (If we fail |
| 13089 | * later on, this change will just roll back.) |
| 13090 | */ |
| 13091 | childatt->attinhcount++; |
| 13092 | |
| 13093 | /* |
| 13094 | * In case of partitions, we must enforce that value of attislocal |
| 13095 | * is same in all partitions. (Note: there are only inherited |
| 13096 | * attributes in partitions) |
| 13097 | */ |
| 13098 | if (child_is_partition) |
| 13099 | { |
| 13100 | Assert(childatt->attinhcount == 1); |
| 13101 | childatt->attislocal = false; |
| 13102 | } |
| 13103 | |
| 13104 | CatalogTupleUpdate(attrrel, &tuple->t_self, tuple); |
| 13105 | heap_freetuple(tuple); |
| 13106 | } |
| 13107 | else |
| 13108 | { |
| 13109 | ereport(ERROR, |
| 13110 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
| 13111 | errmsg("child table is missing column \"%s\"" , |
| 13112 | attributeName))); |
| 13113 | } |
| 13114 | } |
| 13115 | |
| 13116 | table_close(attrrel, RowExclusiveLock); |
| 13117 | } |
| 13118 | |
| 13119 | /* |
| 13120 | * Check constraints in child table match up with constraints in parent, |
| 13121 | * and increment their coninhcount. |
| 13122 | * |
| 13123 | * Constraints that are marked ONLY in the parent are ignored. |
| 13124 | * |
| 13125 | * Called by CreateInheritance |
| 13126 | * |
| 13127 | * Currently all constraints in parent must be present in the child. One day we |
| 13128 | * may consider adding new constraints like CREATE TABLE does. |
| 13129 | * |
| 13130 | * XXX This is O(N^2) which may be an issue with tables with hundreds of |
| 13131 | * constraints. As long as tables have more like 10 constraints it shouldn't be |
| 13132 | * a problem though. Even 100 constraints ought not be the end of the world. |
| 13133 | * |
| 13134 | * XXX See MergeWithExistingConstraint too if you change this code. |
| 13135 | */ |
| 13136 | static void |
| 13137 | MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel) |
| 13138 | { |
| 13139 | Relation catalog_relation; |
| 13140 | TupleDesc tuple_desc; |
| 13141 | SysScanDesc parent_scan; |
| 13142 | ScanKeyData parent_key; |
| 13143 | HeapTuple parent_tuple; |
| 13144 | bool child_is_partition = false; |
| 13145 | |
| 13146 | catalog_relation = table_open(ConstraintRelationId, RowExclusiveLock); |
| 13147 | tuple_desc = RelationGetDescr(catalog_relation); |
| 13148 | |
| 13149 | /* If parent_rel is a partitioned table, child_rel must be a partition */ |
| 13150 | if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) |
| 13151 | child_is_partition = true; |
| 13152 | |
| 13153 | /* Outer loop scans through the parent's constraint definitions */ |
| 13154 | ScanKeyInit(&parent_key, |
| 13155 | Anum_pg_constraint_conrelid, |
| 13156 | BTEqualStrategyNumber, F_OIDEQ, |
| 13157 | ObjectIdGetDatum(RelationGetRelid(parent_rel))); |
| 13158 | parent_scan = systable_beginscan(catalog_relation, ConstraintRelidTypidNameIndexId, |
| 13159 | true, NULL, 1, &parent_key); |
| 13160 | |
| 13161 | while (HeapTupleIsValid(parent_tuple = systable_getnext(parent_scan))) |
| 13162 | { |
| 13163 | Form_pg_constraint parent_con = (Form_pg_constraint) GETSTRUCT(parent_tuple); |
| 13164 | SysScanDesc child_scan; |
| 13165 | ScanKeyData child_key; |
| 13166 | HeapTuple child_tuple; |
| 13167 | bool found = false; |
| 13168 | |
| 13169 | if (parent_con->contype != CONSTRAINT_CHECK) |
| 13170 | continue; |
| 13171 | |
| 13172 | /* if the parent's constraint is marked NO INHERIT, it's not inherited */ |
| 13173 | if (parent_con->connoinherit) |
| 13174 | continue; |
| 13175 | |
| 13176 | /* Search for a child constraint matching this one */ |
| 13177 | ScanKeyInit(&child_key, |
| 13178 | Anum_pg_constraint_conrelid, |
| 13179 | BTEqualStrategyNumber, F_OIDEQ, |
| 13180 | ObjectIdGetDatum(RelationGetRelid(child_rel))); |
| 13181 | child_scan = systable_beginscan(catalog_relation, ConstraintRelidTypidNameIndexId, |
| 13182 | true, NULL, 1, &child_key); |
| 13183 | |
| 13184 | while (HeapTupleIsValid(child_tuple = systable_getnext(child_scan))) |
| 13185 | { |
| 13186 | Form_pg_constraint child_con = (Form_pg_constraint) GETSTRUCT(child_tuple); |
| 13187 | HeapTuple child_copy; |
| 13188 | |
| 13189 | if (child_con->contype != CONSTRAINT_CHECK) |
| 13190 | continue; |
| 13191 | |
| 13192 | if (strcmp(NameStr(parent_con->conname), |
| 13193 | NameStr(child_con->conname)) != 0) |
| 13194 | continue; |
| 13195 | |
| 13196 | if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc)) |
| 13197 | ereport(ERROR, |
| 13198 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
| 13199 | errmsg("child table \"%s\" has different definition for check constraint \"%s\"" , |
| 13200 | RelationGetRelationName(child_rel), |
| 13201 | NameStr(parent_con->conname)))); |
| 13202 | |
| 13203 | /* If the child constraint is "no inherit" then cannot merge */ |
| 13204 | if (child_con->connoinherit) |
| 13205 | ereport(ERROR, |
| 13206 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| 13207 | errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"" , |
| 13208 | NameStr(child_con->conname), |
| 13209 | RelationGetRelationName(child_rel)))); |
| 13210 | |
| 13211 | /* |
| 13212 | * If the child constraint is "not valid" then cannot merge with a |
| 13213 | * valid parent constraint |
| 13214 | */ |
| 13215 | if (parent_con->convalidated && !child_con->convalidated) |
| 13216 | ereport(ERROR, |
| 13217 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| 13218 | errmsg("constraint \"%s\" conflicts with NOT VALID constraint on child table \"%s\"" , |
| 13219 | NameStr(child_con->conname), |
| 13220 | RelationGetRelationName(child_rel)))); |
| 13221 | |
| 13222 | /* |
| 13223 | * OK, bump the child constraint's inheritance count. (If we fail |
| 13224 | * later on, this change will just roll back.) |
| 13225 | */ |
| 13226 | child_copy = heap_copytuple(child_tuple); |
| 13227 | child_con = (Form_pg_constraint) GETSTRUCT(child_copy); |
| 13228 | child_con->coninhcount++; |
| 13229 | |
| 13230 | /* |
| 13231 | * In case of partitions, an inherited constraint must be |
| 13232 | * inherited only once since it cannot have multiple parents and |
| 13233 | * it is never considered local. |
| 13234 | */ |
| 13235 | if (child_is_partition) |
| 13236 | { |
| 13237 | Assert(child_con->coninhcount == 1); |
| 13238 | child_con->conislocal = false; |
| 13239 | } |
| 13240 | |
| 13241 | CatalogTupleUpdate(catalog_relation, &child_copy->t_self, child_copy); |
| 13242 | heap_freetuple(child_copy); |
| 13243 | |
| 13244 | found = true; |
| 13245 | break; |
| 13246 | } |
| 13247 | |
| 13248 | systable_endscan(child_scan); |
| 13249 | |
| 13250 | if (!found) |
| 13251 | ereport(ERROR, |
| 13252 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
| 13253 | errmsg("child table is missing constraint \"%s\"" , |
| 13254 | NameStr(parent_con->conname)))); |
| 13255 | } |
| 13256 | |
| 13257 | systable_endscan(parent_scan); |
| 13258 | table_close(catalog_relation, RowExclusiveLock); |
| 13259 | } |
| 13260 | |
| 13261 | /* |
| 13262 | * ALTER TABLE NO INHERIT |
| 13263 | * |
| 13264 | * Return value is the address of the relation that is no longer parent. |
| 13265 | */ |
| 13266 | static ObjectAddress |
| 13267 | ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) |
| 13268 | { |
| 13269 | ObjectAddress address; |
| 13270 | Relation parent_rel; |
| 13271 | |
| 13272 | if (rel->rd_rel->relispartition) |
| 13273 | ereport(ERROR, |
| 13274 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 13275 | errmsg("cannot change inheritance of a partition" ))); |
| 13276 | |
| 13277 | /* |
| 13278 | * AccessShareLock on the parent is probably enough, seeing that DROP |
| 13279 | * TABLE doesn't lock parent tables at all. We need some lock since we'll |
| 13280 | * be inspecting the parent's schema. |
| 13281 | */ |
| 13282 | parent_rel = table_openrv(parent, AccessShareLock); |
| 13283 | |
| 13284 | /* |
| 13285 | * We don't bother to check ownership of the parent table --- ownership of |
| 13286 | * the child is presumed enough rights. |
| 13287 | */ |
| 13288 | |
| 13289 | /* Off to RemoveInheritance() where most of the work happens */ |
| 13290 | RemoveInheritance(rel, parent_rel); |
| 13291 | |
| 13292 | ObjectAddressSet(address, RelationRelationId, |
| 13293 | RelationGetRelid(parent_rel)); |
| 13294 | |
| 13295 | /* keep our lock on the parent relation until commit */ |
| 13296 | table_close(parent_rel, NoLock); |
| 13297 | |
| 13298 | return address; |
| 13299 | } |
| 13300 | |
| 13301 | /* |
| 13302 | * RemoveInheritance |
| 13303 | * |
| 13304 | * Drop a parent from the child's parents. This just adjusts the attinhcount |
| 13305 | * and attislocal of the columns and removes the pg_inherit and pg_depend |
| 13306 | * entries. |
| 13307 | * |
| 13308 | * If attinhcount goes to 0 then attislocal gets set to true. If it goes back |
| 13309 | * up attislocal stays true, which means if a child is ever removed from a |
| 13310 | * parent then its columns will never be automatically dropped which may |
| 13311 | * surprise. But at least we'll never surprise by dropping columns someone |
| 13312 | * isn't expecting to be dropped which would actually mean data loss. |
| 13313 | * |
| 13314 | * coninhcount and conislocal for inherited constraints are adjusted in |
| 13315 | * exactly the same way. |
| 13316 | * |
| 13317 | * Common to ATExecDropInherit() and ATExecDetachPartition(). |
| 13318 | */ |
| 13319 | static void |
| 13320 | RemoveInheritance(Relation child_rel, Relation parent_rel) |
| 13321 | { |
| 13322 | Relation catalogRelation; |
| 13323 | SysScanDesc scan; |
| 13324 | ScanKeyData key[3]; |
| 13325 | HeapTuple attributeTuple, |
| 13326 | constraintTuple; |
| 13327 | List *connames; |
| 13328 | bool found; |
| 13329 | bool child_is_partition = false; |
| 13330 | |
| 13331 | /* If parent_rel is a partitioned table, child_rel must be a partition */ |
| 13332 | if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) |
| 13333 | child_is_partition = true; |
| 13334 | |
| 13335 | found = DeleteInheritsTuple(RelationGetRelid(child_rel), |
| 13336 | RelationGetRelid(parent_rel)); |
| 13337 | if (!found) |
| 13338 | { |
| 13339 | if (child_is_partition) |
| 13340 | ereport(ERROR, |
| 13341 | (errcode(ERRCODE_UNDEFINED_TABLE), |
| 13342 | errmsg("relation \"%s\" is not a partition of relation \"%s\"" , |
| 13343 | RelationGetRelationName(child_rel), |
| 13344 | RelationGetRelationName(parent_rel)))); |
| 13345 | else |
| 13346 | ereport(ERROR, |
| 13347 | (errcode(ERRCODE_UNDEFINED_TABLE), |
| 13348 | errmsg("relation \"%s\" is not a parent of relation \"%s\"" , |
| 13349 | RelationGetRelationName(parent_rel), |
| 13350 | RelationGetRelationName(child_rel)))); |
| 13351 | } |
| 13352 | |
| 13353 | /* |
| 13354 | * Search through child columns looking for ones matching parent rel |
| 13355 | */ |
| 13356 | catalogRelation = table_open(AttributeRelationId, RowExclusiveLock); |
| 13357 | ScanKeyInit(&key[0], |
| 13358 | Anum_pg_attribute_attrelid, |
| 13359 | BTEqualStrategyNumber, F_OIDEQ, |
| 13360 | ObjectIdGetDatum(RelationGetRelid(child_rel))); |
| 13361 | scan = systable_beginscan(catalogRelation, AttributeRelidNumIndexId, |
| 13362 | true, NULL, 1, key); |
| 13363 | while (HeapTupleIsValid(attributeTuple = systable_getnext(scan))) |
| 13364 | { |
| 13365 | Form_pg_attribute att = (Form_pg_attribute) GETSTRUCT(attributeTuple); |
| 13366 | |
| 13367 | /* Ignore if dropped or not inherited */ |
| 13368 | if (att->attisdropped) |
| 13369 | continue; |
| 13370 | if (att->attinhcount <= 0) |
| 13371 | continue; |
| 13372 | |
| 13373 | if (SearchSysCacheExistsAttName(RelationGetRelid(parent_rel), |
| 13374 | NameStr(att->attname))) |
| 13375 | { |
| 13376 | /* Decrement inhcount and possibly set islocal to true */ |
| 13377 | HeapTuple copyTuple = heap_copytuple(attributeTuple); |
| 13378 | Form_pg_attribute copy_att = (Form_pg_attribute) GETSTRUCT(copyTuple); |
| 13379 | |
| 13380 | copy_att->attinhcount--; |
| 13381 | if (copy_att->attinhcount == 0) |
| 13382 | copy_att->attislocal = true; |
| 13383 | |
| 13384 | CatalogTupleUpdate(catalogRelation, ©Tuple->t_self, copyTuple); |
| 13385 | heap_freetuple(copyTuple); |
| 13386 | } |
| 13387 | } |
| 13388 | systable_endscan(scan); |
| 13389 | table_close(catalogRelation, RowExclusiveLock); |
| 13390 | |
| 13391 | /* |
| 13392 | * Likewise, find inherited check constraints and disinherit them. To do |
| 13393 | * this, we first need a list of the names of the parent's check |
| 13394 | * constraints. (We cheat a bit by only checking for name matches, |
| 13395 | * assuming that the expressions will match.) |
| 13396 | */ |
| 13397 | catalogRelation = table_open(ConstraintRelationId, RowExclusiveLock); |
| 13398 | ScanKeyInit(&key[0], |
| 13399 | Anum_pg_constraint_conrelid, |
| 13400 | BTEqualStrategyNumber, F_OIDEQ, |
| 13401 | ObjectIdGetDatum(RelationGetRelid(parent_rel))); |
| 13402 | scan = systable_beginscan(catalogRelation, ConstraintRelidTypidNameIndexId, |
| 13403 | true, NULL, 1, key); |
| 13404 | |
| 13405 | connames = NIL; |
| 13406 | |
| 13407 | while (HeapTupleIsValid(constraintTuple = systable_getnext(scan))) |
| 13408 | { |
| 13409 | Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(constraintTuple); |
| 13410 | |
| 13411 | if (con->contype == CONSTRAINT_CHECK) |
| 13412 | connames = lappend(connames, pstrdup(NameStr(con->conname))); |
| 13413 | } |
| 13414 | |
| 13415 | systable_endscan(scan); |
| 13416 | |
| 13417 | /* Now scan the child's constraints */ |
| 13418 | ScanKeyInit(&key[0], |
| 13419 | Anum_pg_constraint_conrelid, |
| 13420 | BTEqualStrategyNumber, F_OIDEQ, |
| 13421 | ObjectIdGetDatum(RelationGetRelid(child_rel))); |
| 13422 | scan = systable_beginscan(catalogRelation, ConstraintRelidTypidNameIndexId, |
| 13423 | true, NULL, 1, key); |
| 13424 | |
| 13425 | while (HeapTupleIsValid(constraintTuple = systable_getnext(scan))) |
| 13426 | { |
| 13427 | Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(constraintTuple); |
| 13428 | bool match; |
| 13429 | ListCell *lc; |
| 13430 | |
| 13431 | if (con->contype != CONSTRAINT_CHECK) |
| 13432 | continue; |
| 13433 | |
| 13434 | match = false; |
| 13435 | foreach(lc, connames) |
| 13436 | { |
| 13437 | if (strcmp(NameStr(con->conname), (char *) lfirst(lc)) == 0) |
| 13438 | { |
| 13439 | match = true; |
| 13440 | break; |
| 13441 | } |
| 13442 | } |
| 13443 | |
| 13444 | if (match) |
| 13445 | { |
| 13446 | /* Decrement inhcount and possibly set islocal to true */ |
| 13447 | HeapTuple copyTuple = heap_copytuple(constraintTuple); |
| 13448 | Form_pg_constraint copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple); |
| 13449 | |
| 13450 | if (copy_con->coninhcount <= 0) /* shouldn't happen */ |
| 13451 | elog(ERROR, "relation %u has non-inherited constraint \"%s\"" , |
| 13452 | RelationGetRelid(child_rel), NameStr(copy_con->conname)); |
| 13453 | |
| 13454 | copy_con->coninhcount--; |
| 13455 | if (copy_con->coninhcount == 0) |
| 13456 | copy_con->conislocal = true; |
| 13457 | |
| 13458 | CatalogTupleUpdate(catalogRelation, ©Tuple->t_self, copyTuple); |
| 13459 | heap_freetuple(copyTuple); |
| 13460 | } |
| 13461 | } |
| 13462 | |
| 13463 | systable_endscan(scan); |
| 13464 | table_close(catalogRelation, RowExclusiveLock); |
| 13465 | |
| 13466 | drop_parent_dependency(RelationGetRelid(child_rel), |
| 13467 | RelationRelationId, |
| 13468 | RelationGetRelid(parent_rel), |
| 13469 | child_dependency_type(child_is_partition)); |
| 13470 | |
| 13471 | /* |
| 13472 | * Post alter hook of this inherits. Since object_access_hook doesn't take |
| 13473 | * multiple object identifiers, we relay oid of parent relation using |
| 13474 | * auxiliary_id argument. |
| 13475 | */ |
| 13476 | InvokeObjectPostAlterHookArg(InheritsRelationId, |
| 13477 | RelationGetRelid(child_rel), 0, |
| 13478 | RelationGetRelid(parent_rel), false); |
| 13479 | } |
| 13480 | |
| 13481 | /* |
| 13482 | * Drop the dependency created by StoreCatalogInheritance1 (CREATE TABLE |
| 13483 | * INHERITS/ALTER TABLE INHERIT -- refclassid will be RelationRelationId) or |
| 13484 | * heap_create_with_catalog (CREATE TABLE OF/ALTER TABLE OF -- refclassid will |
| 13485 | * be TypeRelationId). There's no convenient way to do this, so go trawling |
| 13486 | * through pg_depend. |
| 13487 | */ |
| 13488 | static void |
| 13489 | drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid, |
| 13490 | DependencyType deptype) |
| 13491 | { |
| 13492 | Relation catalogRelation; |
| 13493 | SysScanDesc scan; |
| 13494 | ScanKeyData key[3]; |
| 13495 | HeapTuple depTuple; |
| 13496 | |
| 13497 | catalogRelation = table_open(DependRelationId, RowExclusiveLock); |
| 13498 | |
| 13499 | ScanKeyInit(&key[0], |
| 13500 | Anum_pg_depend_classid, |
| 13501 | BTEqualStrategyNumber, F_OIDEQ, |
| 13502 | ObjectIdGetDatum(RelationRelationId)); |
| 13503 | ScanKeyInit(&key[1], |
| 13504 | Anum_pg_depend_objid, |
| 13505 | BTEqualStrategyNumber, F_OIDEQ, |
| 13506 | ObjectIdGetDatum(relid)); |
| 13507 | ScanKeyInit(&key[2], |
| 13508 | Anum_pg_depend_objsubid, |
| 13509 | BTEqualStrategyNumber, F_INT4EQ, |
| 13510 | Int32GetDatum(0)); |
| 13511 | |
| 13512 | scan = systable_beginscan(catalogRelation, DependDependerIndexId, true, |
| 13513 | NULL, 3, key); |
| 13514 | |
| 13515 | while (HeapTupleIsValid(depTuple = systable_getnext(scan))) |
| 13516 | { |
| 13517 | Form_pg_depend dep = (Form_pg_depend) GETSTRUCT(depTuple); |
| 13518 | |
| 13519 | if (dep->refclassid == refclassid && |
| 13520 | dep->refobjid == refobjid && |
| 13521 | dep->refobjsubid == 0 && |
| 13522 | dep->deptype == deptype) |
| 13523 | CatalogTupleDelete(catalogRelation, &depTuple->t_self); |
| 13524 | } |
| 13525 | |
| 13526 | systable_endscan(scan); |
| 13527 | table_close(catalogRelation, RowExclusiveLock); |
| 13528 | } |
| 13529 | |
| 13530 | /* |
| 13531 | * ALTER TABLE OF |
| 13532 | * |
| 13533 | * Attach a table to a composite type, as though it had been created with CREATE |
| 13534 | * TABLE OF. All attname, atttypid, atttypmod and attcollation must match. The |
| 13535 | * subject table must not have inheritance parents. These restrictions ensure |
| 13536 | * that you cannot create a configuration impossible with CREATE TABLE OF alone. |
| 13537 | * |
| 13538 | * The address of the type is returned. |
| 13539 | */ |
| 13540 | static ObjectAddress |
| 13541 | ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode) |
| 13542 | { |
| 13543 | Oid relid = RelationGetRelid(rel); |
| 13544 | Type typetuple; |
| 13545 | Form_pg_type typeform; |
| 13546 | Oid typeid; |
| 13547 | Relation inheritsRelation, |
| 13548 | relationRelation; |
| 13549 | SysScanDesc scan; |
| 13550 | ScanKeyData key; |
| 13551 | AttrNumber table_attno, |
| 13552 | type_attno; |
| 13553 | TupleDesc typeTupleDesc, |
| 13554 | tableTupleDesc; |
| 13555 | ObjectAddress tableobj, |
| 13556 | typeobj; |
| 13557 | HeapTuple classtuple; |
| 13558 | |
| 13559 | /* Validate the type. */ |
| 13560 | typetuple = typenameType(NULL, ofTypename, NULL); |
| 13561 | check_of_type(typetuple); |
| 13562 | typeform = (Form_pg_type) GETSTRUCT(typetuple); |
| 13563 | typeid = typeform->oid; |
| 13564 | |
| 13565 | /* Fail if the table has any inheritance parents. */ |
| 13566 | inheritsRelation = table_open(InheritsRelationId, AccessShareLock); |
| 13567 | ScanKeyInit(&key, |
| 13568 | Anum_pg_inherits_inhrelid, |
| 13569 | BTEqualStrategyNumber, F_OIDEQ, |
| 13570 | ObjectIdGetDatum(relid)); |
| 13571 | scan = systable_beginscan(inheritsRelation, InheritsRelidSeqnoIndexId, |
| 13572 | true, NULL, 1, &key); |
| 13573 | if (HeapTupleIsValid(systable_getnext(scan))) |
| 13574 | ereport(ERROR, |
| 13575 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 13576 | errmsg("typed tables cannot inherit" ))); |
| 13577 | systable_endscan(scan); |
| 13578 | table_close(inheritsRelation, AccessShareLock); |
| 13579 | |
| 13580 | /* |
| 13581 | * Check the tuple descriptors for compatibility. Unlike inheritance, we |
| 13582 | * require that the order also match. However, attnotnull need not match. |
| 13583 | */ |
| 13584 | typeTupleDesc = lookup_rowtype_tupdesc(typeid, -1); |
| 13585 | tableTupleDesc = RelationGetDescr(rel); |
| 13586 | table_attno = 1; |
| 13587 | for (type_attno = 1; type_attno <= typeTupleDesc->natts; type_attno++) |
| 13588 | { |
| 13589 | Form_pg_attribute type_attr, |
| 13590 | table_attr; |
| 13591 | const char *type_attname, |
| 13592 | *table_attname; |
| 13593 | |
| 13594 | /* Get the next non-dropped type attribute. */ |
| 13595 | type_attr = TupleDescAttr(typeTupleDesc, type_attno - 1); |
| 13596 | if (type_attr->attisdropped) |
| 13597 | continue; |
| 13598 | type_attname = NameStr(type_attr->attname); |
| 13599 | |
| 13600 | /* Get the next non-dropped table attribute. */ |
| 13601 | do |
| 13602 | { |
| 13603 | if (table_attno > tableTupleDesc->natts) |
| 13604 | ereport(ERROR, |
| 13605 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
| 13606 | errmsg("table is missing column \"%s\"" , |
| 13607 | type_attname))); |
| 13608 | table_attr = TupleDescAttr(tableTupleDesc, table_attno - 1); |
| 13609 | table_attno++; |
| 13610 | } while (table_attr->attisdropped); |
| 13611 | table_attname = NameStr(table_attr->attname); |
| 13612 | |
| 13613 | /* Compare name. */ |
| 13614 | if (strncmp(table_attname, type_attname, NAMEDATALEN) != 0) |
| 13615 | ereport(ERROR, |
| 13616 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
| 13617 | errmsg("table has column \"%s\" where type requires \"%s\"" , |
| 13618 | table_attname, type_attname))); |
| 13619 | |
| 13620 | /* Compare type. */ |
| 13621 | if (table_attr->atttypid != type_attr->atttypid || |
| 13622 | table_attr->atttypmod != type_attr->atttypmod || |
| 13623 | table_attr->attcollation != type_attr->attcollation) |
| 13624 | ereport(ERROR, |
| 13625 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
| 13626 | errmsg("table \"%s\" has different type for column \"%s\"" , |
| 13627 | RelationGetRelationName(rel), type_attname))); |
| 13628 | } |
| 13629 | DecrTupleDescRefCount(typeTupleDesc); |
| 13630 | |
| 13631 | /* Any remaining columns at the end of the table had better be dropped. */ |
| 13632 | for (; table_attno <= tableTupleDesc->natts; table_attno++) |
| 13633 | { |
| 13634 | Form_pg_attribute table_attr = TupleDescAttr(tableTupleDesc, |
| 13635 | table_attno - 1); |
| 13636 | |
| 13637 | if (!table_attr->attisdropped) |
| 13638 | ereport(ERROR, |
| 13639 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
| 13640 | errmsg("table has extra column \"%s\"" , |
| 13641 | NameStr(table_attr->attname)))); |
| 13642 | } |
| 13643 | |
| 13644 | /* If the table was already typed, drop the existing dependency. */ |
| 13645 | if (rel->rd_rel->reloftype) |
| 13646 | drop_parent_dependency(relid, TypeRelationId, rel->rd_rel->reloftype, |
| 13647 | DEPENDENCY_NORMAL); |
| 13648 | |
| 13649 | /* Record a dependency on the new type. */ |
| 13650 | tableobj.classId = RelationRelationId; |
| 13651 | tableobj.objectId = relid; |
| 13652 | tableobj.objectSubId = 0; |
| 13653 | typeobj.classId = TypeRelationId; |
| 13654 | typeobj.objectId = typeid; |
| 13655 | typeobj.objectSubId = 0; |
| 13656 | recordDependencyOn(&tableobj, &typeobj, DEPENDENCY_NORMAL); |
| 13657 | |
| 13658 | /* Update pg_class.reloftype */ |
| 13659 | relationRelation = table_open(RelationRelationId, RowExclusiveLock); |
| 13660 | classtuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid)); |
| 13661 | if (!HeapTupleIsValid(classtuple)) |
| 13662 | elog(ERROR, "cache lookup failed for relation %u" , relid); |
| 13663 | ((Form_pg_class) GETSTRUCT(classtuple))->reloftype = typeid; |
| 13664 | CatalogTupleUpdate(relationRelation, &classtuple->t_self, classtuple); |
| 13665 | |
| 13666 | InvokeObjectPostAlterHook(RelationRelationId, relid, 0); |
| 13667 | |
| 13668 | heap_freetuple(classtuple); |
| 13669 | table_close(relationRelation, RowExclusiveLock); |
| 13670 | |
| 13671 | ReleaseSysCache(typetuple); |
| 13672 | |
| 13673 | return typeobj; |
| 13674 | } |
| 13675 | |
| 13676 | /* |
| 13677 | * ALTER TABLE NOT OF |
| 13678 | * |
| 13679 | * Detach a typed table from its originating type. Just clear reloftype and |
| 13680 | * remove the dependency. |
| 13681 | */ |
| 13682 | static void |
| 13683 | ATExecDropOf(Relation rel, LOCKMODE lockmode) |
| 13684 | { |
| 13685 | Oid relid = RelationGetRelid(rel); |
| 13686 | Relation relationRelation; |
| 13687 | HeapTuple tuple; |
| 13688 | |
| 13689 | if (!OidIsValid(rel->rd_rel->reloftype)) |
| 13690 | ereport(ERROR, |
| 13691 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 13692 | errmsg("\"%s\" is not a typed table" , |
| 13693 | RelationGetRelationName(rel)))); |
| 13694 | |
| 13695 | /* |
| 13696 | * We don't bother to check ownership of the type --- ownership of the |
| 13697 | * table is presumed enough rights. No lock required on the type, either. |
| 13698 | */ |
| 13699 | |
| 13700 | drop_parent_dependency(relid, TypeRelationId, rel->rd_rel->reloftype, |
| 13701 | DEPENDENCY_NORMAL); |
| 13702 | |
| 13703 | /* Clear pg_class.reloftype */ |
| 13704 | relationRelation = table_open(RelationRelationId, RowExclusiveLock); |
| 13705 | tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid)); |
| 13706 | if (!HeapTupleIsValid(tuple)) |
| 13707 | elog(ERROR, "cache lookup failed for relation %u" , relid); |
| 13708 | ((Form_pg_class) GETSTRUCT(tuple))->reloftype = InvalidOid; |
| 13709 | CatalogTupleUpdate(relationRelation, &tuple->t_self, tuple); |
| 13710 | |
| 13711 | InvokeObjectPostAlterHook(RelationRelationId, relid, 0); |
| 13712 | |
| 13713 | heap_freetuple(tuple); |
| 13714 | table_close(relationRelation, RowExclusiveLock); |
| 13715 | } |
| 13716 | |
| 13717 | /* |
| 13718 | * relation_mark_replica_identity: Update a table's replica identity |
| 13719 | * |
| 13720 | * Iff ri_type = REPLICA_IDENTITY_INDEX, indexOid must be the Oid of a suitable |
| 13721 | * index. Otherwise, it should be InvalidOid. |
| 13722 | */ |
| 13723 | static void |
| 13724 | relation_mark_replica_identity(Relation rel, char ri_type, Oid indexOid, |
| 13725 | bool is_internal) |
| 13726 | { |
| 13727 | Relation pg_index; |
| 13728 | Relation pg_class; |
| 13729 | HeapTuple pg_class_tuple; |
| 13730 | HeapTuple pg_index_tuple; |
| 13731 | Form_pg_class pg_class_form; |
| 13732 | Form_pg_index pg_index_form; |
| 13733 | |
| 13734 | ListCell *index; |
| 13735 | |
| 13736 | /* |
| 13737 | * Check whether relreplident has changed, and update it if so. |
| 13738 | */ |
| 13739 | pg_class = table_open(RelationRelationId, RowExclusiveLock); |
| 13740 | pg_class_tuple = SearchSysCacheCopy1(RELOID, |
| 13741 | ObjectIdGetDatum(RelationGetRelid(rel))); |
| 13742 | if (!HeapTupleIsValid(pg_class_tuple)) |
| 13743 | elog(ERROR, "cache lookup failed for relation \"%s\"" , |
| 13744 | RelationGetRelationName(rel)); |
| 13745 | pg_class_form = (Form_pg_class) GETSTRUCT(pg_class_tuple); |
| 13746 | if (pg_class_form->relreplident != ri_type) |
| 13747 | { |
| 13748 | pg_class_form->relreplident = ri_type; |
| 13749 | CatalogTupleUpdate(pg_class, &pg_class_tuple->t_self, pg_class_tuple); |
| 13750 | } |
| 13751 | table_close(pg_class, RowExclusiveLock); |
| 13752 | heap_freetuple(pg_class_tuple); |
| 13753 | |
| 13754 | /* |
| 13755 | * Check whether the correct index is marked indisreplident; if so, we're |
| 13756 | * done. |
| 13757 | */ |
| 13758 | if (OidIsValid(indexOid)) |
| 13759 | { |
| 13760 | Assert(ri_type == REPLICA_IDENTITY_INDEX); |
| 13761 | |
| 13762 | pg_index_tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexOid)); |
| 13763 | if (!HeapTupleIsValid(pg_index_tuple)) |
| 13764 | elog(ERROR, "cache lookup failed for index %u" , indexOid); |
| 13765 | pg_index_form = (Form_pg_index) GETSTRUCT(pg_index_tuple); |
| 13766 | |
| 13767 | if (pg_index_form->indisreplident) |
| 13768 | { |
| 13769 | ReleaseSysCache(pg_index_tuple); |
| 13770 | return; |
| 13771 | } |
| 13772 | ReleaseSysCache(pg_index_tuple); |
| 13773 | } |
| 13774 | |
| 13775 | /* |
| 13776 | * Clear the indisreplident flag from any index that had it previously, |
| 13777 | * and set it for any index that should have it now. |
| 13778 | */ |
| 13779 | pg_index = table_open(IndexRelationId, RowExclusiveLock); |
| 13780 | foreach(index, RelationGetIndexList(rel)) |
| 13781 | { |
| 13782 | Oid thisIndexOid = lfirst_oid(index); |
| 13783 | bool dirty = false; |
| 13784 | |
| 13785 | pg_index_tuple = SearchSysCacheCopy1(INDEXRELID, |
| 13786 | ObjectIdGetDatum(thisIndexOid)); |
| 13787 | if (!HeapTupleIsValid(pg_index_tuple)) |
| 13788 | elog(ERROR, "cache lookup failed for index %u" , thisIndexOid); |
| 13789 | pg_index_form = (Form_pg_index) GETSTRUCT(pg_index_tuple); |
| 13790 | |
| 13791 | /* |
| 13792 | * Unset the bit if set. We know it's wrong because we checked this |
| 13793 | * earlier. |
| 13794 | */ |
| 13795 | if (pg_index_form->indisreplident) |
| 13796 | { |
| 13797 | dirty = true; |
| 13798 | pg_index_form->indisreplident = false; |
| 13799 | } |
| 13800 | else if (thisIndexOid == indexOid) |
| 13801 | { |
| 13802 | dirty = true; |
| 13803 | pg_index_form->indisreplident = true; |
| 13804 | } |
| 13805 | |
| 13806 | if (dirty) |
| 13807 | { |
| 13808 | CatalogTupleUpdate(pg_index, &pg_index_tuple->t_self, pg_index_tuple); |
| 13809 | InvokeObjectPostAlterHookArg(IndexRelationId, thisIndexOid, 0, |
| 13810 | InvalidOid, is_internal); |
| 13811 | } |
| 13812 | heap_freetuple(pg_index_tuple); |
| 13813 | } |
| 13814 | |
| 13815 | table_close(pg_index, RowExclusiveLock); |
| 13816 | } |
| 13817 | |
| 13818 | /* |
| 13819 | * ALTER TABLE <name> REPLICA IDENTITY ... |
| 13820 | */ |
| 13821 | static void |
| 13822 | ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode) |
| 13823 | { |
| 13824 | Oid indexOid; |
| 13825 | Relation indexRel; |
| 13826 | int key; |
| 13827 | |
| 13828 | if (stmt->identity_type == REPLICA_IDENTITY_DEFAULT) |
| 13829 | { |
| 13830 | relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true); |
| 13831 | return; |
| 13832 | } |
| 13833 | else if (stmt->identity_type == REPLICA_IDENTITY_FULL) |
| 13834 | { |
| 13835 | relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true); |
| 13836 | return; |
| 13837 | } |
| 13838 | else if (stmt->identity_type == REPLICA_IDENTITY_NOTHING) |
| 13839 | { |
| 13840 | relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true); |
| 13841 | return; |
| 13842 | } |
| 13843 | else if (stmt->identity_type == REPLICA_IDENTITY_INDEX) |
| 13844 | { |
| 13845 | /* fallthrough */ ; |
| 13846 | } |
| 13847 | else |
| 13848 | elog(ERROR, "unexpected identity type %u" , stmt->identity_type); |
| 13849 | |
| 13850 | |
| 13851 | /* Check that the index exists */ |
| 13852 | indexOid = get_relname_relid(stmt->name, rel->rd_rel->relnamespace); |
| 13853 | if (!OidIsValid(indexOid)) |
| 13854 | ereport(ERROR, |
| 13855 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
| 13856 | errmsg("index \"%s\" for table \"%s\" does not exist" , |
| 13857 | stmt->name, RelationGetRelationName(rel)))); |
| 13858 | |
| 13859 | indexRel = index_open(indexOid, ShareLock); |
| 13860 | |
| 13861 | /* Check that the index is on the relation we're altering. */ |
| 13862 | if (indexRel->rd_index == NULL || |
| 13863 | indexRel->rd_index->indrelid != RelationGetRelid(rel)) |
| 13864 | ereport(ERROR, |
| 13865 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 13866 | errmsg("\"%s\" is not an index for table \"%s\"" , |
| 13867 | RelationGetRelationName(indexRel), |
| 13868 | RelationGetRelationName(rel)))); |
| 13869 | /* The AM must support uniqueness, and the index must in fact be unique. */ |
| 13870 | if (!indexRel->rd_indam->amcanunique || |
| 13871 | !indexRel->rd_index->indisunique) |
| 13872 | ereport(ERROR, |
| 13873 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 13874 | errmsg("cannot use non-unique index \"%s\" as replica identity" , |
| 13875 | RelationGetRelationName(indexRel)))); |
| 13876 | /* Deferred indexes are not guaranteed to be always unique. */ |
| 13877 | if (!indexRel->rd_index->indimmediate) |
| 13878 | ereport(ERROR, |
| 13879 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 13880 | errmsg("cannot use non-immediate index \"%s\" as replica identity" , |
| 13881 | RelationGetRelationName(indexRel)))); |
| 13882 | /* Expression indexes aren't supported. */ |
| 13883 | if (RelationGetIndexExpressions(indexRel) != NIL) |
| 13884 | ereport(ERROR, |
| 13885 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 13886 | errmsg("cannot use expression index \"%s\" as replica identity" , |
| 13887 | RelationGetRelationName(indexRel)))); |
| 13888 | /* Predicate indexes aren't supported. */ |
| 13889 | if (RelationGetIndexPredicate(indexRel) != NIL) |
| 13890 | ereport(ERROR, |
| 13891 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 13892 | errmsg("cannot use partial index \"%s\" as replica identity" , |
| 13893 | RelationGetRelationName(indexRel)))); |
| 13894 | /* And neither are invalid indexes. */ |
| 13895 | if (!indexRel->rd_index->indisvalid) |
| 13896 | ereport(ERROR, |
| 13897 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 13898 | errmsg("cannot use invalid index \"%s\" as replica identity" , |
| 13899 | RelationGetRelationName(indexRel)))); |
| 13900 | |
| 13901 | /* Check index for nullable columns. */ |
| 13902 | for (key = 0; key < IndexRelationGetNumberOfKeyAttributes(indexRel); key++) |
| 13903 | { |
| 13904 | int16 attno = indexRel->rd_index->indkey.values[key]; |
| 13905 | Form_pg_attribute attr; |
| 13906 | |
| 13907 | /* |
| 13908 | * Reject any other system columns. (Going forward, we'll disallow |
| 13909 | * indexes containing such columns in the first place, but they might |
| 13910 | * exist in older branches.) |
| 13911 | */ |
| 13912 | if (attno <= 0) |
| 13913 | ereport(ERROR, |
| 13914 | (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), |
| 13915 | errmsg("index \"%s\" cannot be used as replica identity because column %d is a system column" , |
| 13916 | RelationGetRelationName(indexRel), attno))); |
| 13917 | |
| 13918 | attr = TupleDescAttr(rel->rd_att, attno - 1); |
| 13919 | if (!attr->attnotnull) |
| 13920 | ereport(ERROR, |
| 13921 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 13922 | errmsg("index \"%s\" cannot be used as replica identity because column \"%s\" is nullable" , |
| 13923 | RelationGetRelationName(indexRel), |
| 13924 | NameStr(attr->attname)))); |
| 13925 | } |
| 13926 | |
| 13927 | /* This index is suitable for use as a replica identity. Mark it. */ |
| 13928 | relation_mark_replica_identity(rel, stmt->identity_type, indexOid, true); |
| 13929 | |
| 13930 | index_close(indexRel, NoLock); |
| 13931 | } |
| 13932 | |
| 13933 | /* |
| 13934 | * ALTER TABLE ENABLE/DISABLE ROW LEVEL SECURITY |
| 13935 | */ |
| 13936 | static void |
| 13937 | ATExecEnableRowSecurity(Relation rel) |
| 13938 | { |
| 13939 | Relation pg_class; |
| 13940 | Oid relid; |
| 13941 | HeapTuple tuple; |
| 13942 | |
| 13943 | relid = RelationGetRelid(rel); |
| 13944 | |
| 13945 | pg_class = table_open(RelationRelationId, RowExclusiveLock); |
| 13946 | |
| 13947 | tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid)); |
| 13948 | |
| 13949 | if (!HeapTupleIsValid(tuple)) |
| 13950 | elog(ERROR, "cache lookup failed for relation %u" , relid); |
| 13951 | |
| 13952 | ((Form_pg_class) GETSTRUCT(tuple))->relrowsecurity = true; |
| 13953 | CatalogTupleUpdate(pg_class, &tuple->t_self, tuple); |
| 13954 | |
| 13955 | table_close(pg_class, RowExclusiveLock); |
| 13956 | heap_freetuple(tuple); |
| 13957 | } |
| 13958 | |
| 13959 | static void |
| 13960 | ATExecDisableRowSecurity(Relation rel) |
| 13961 | { |
| 13962 | Relation pg_class; |
| 13963 | Oid relid; |
| 13964 | HeapTuple tuple; |
| 13965 | |
| 13966 | relid = RelationGetRelid(rel); |
| 13967 | |
| 13968 | /* Pull the record for this relation and update it */ |
| 13969 | pg_class = table_open(RelationRelationId, RowExclusiveLock); |
| 13970 | |
| 13971 | tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid)); |
| 13972 | |
| 13973 | if (!HeapTupleIsValid(tuple)) |
| 13974 | elog(ERROR, "cache lookup failed for relation %u" , relid); |
| 13975 | |
| 13976 | ((Form_pg_class) GETSTRUCT(tuple))->relrowsecurity = false; |
| 13977 | CatalogTupleUpdate(pg_class, &tuple->t_self, tuple); |
| 13978 | |
| 13979 | table_close(pg_class, RowExclusiveLock); |
| 13980 | heap_freetuple(tuple); |
| 13981 | } |
| 13982 | |
| 13983 | /* |
| 13984 | * ALTER TABLE FORCE/NO FORCE ROW LEVEL SECURITY |
| 13985 | */ |
| 13986 | static void |
| 13987 | ATExecForceNoForceRowSecurity(Relation rel, bool force_rls) |
| 13988 | { |
| 13989 | Relation pg_class; |
| 13990 | Oid relid; |
| 13991 | HeapTuple tuple; |
| 13992 | |
| 13993 | relid = RelationGetRelid(rel); |
| 13994 | |
| 13995 | pg_class = table_open(RelationRelationId, RowExclusiveLock); |
| 13996 | |
| 13997 | tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid)); |
| 13998 | |
| 13999 | if (!HeapTupleIsValid(tuple)) |
| 14000 | elog(ERROR, "cache lookup failed for relation %u" , relid); |
| 14001 | |
| 14002 | ((Form_pg_class) GETSTRUCT(tuple))->relforcerowsecurity = force_rls; |
| 14003 | CatalogTupleUpdate(pg_class, &tuple->t_self, tuple); |
| 14004 | |
| 14005 | table_close(pg_class, RowExclusiveLock); |
| 14006 | heap_freetuple(tuple); |
| 14007 | } |
| 14008 | |
| 14009 | /* |
| 14010 | * ALTER FOREIGN TABLE <name> OPTIONS (...) |
| 14011 | */ |
| 14012 | static void |
| 14013 | ATExecGenericOptions(Relation rel, List *options) |
| 14014 | { |
| 14015 | Relation ftrel; |
| 14016 | ForeignServer *server; |
| 14017 | ForeignDataWrapper *fdw; |
| 14018 | HeapTuple tuple; |
| 14019 | bool isnull; |
| 14020 | Datum repl_val[Natts_pg_foreign_table]; |
| 14021 | bool repl_null[Natts_pg_foreign_table]; |
| 14022 | bool repl_repl[Natts_pg_foreign_table]; |
| 14023 | Datum datum; |
| 14024 | Form_pg_foreign_table tableform; |
| 14025 | |
| 14026 | if (options == NIL) |
| 14027 | return; |
| 14028 | |
| 14029 | ftrel = table_open(ForeignTableRelationId, RowExclusiveLock); |
| 14030 | |
| 14031 | tuple = SearchSysCacheCopy1(FOREIGNTABLEREL, rel->rd_id); |
| 14032 | if (!HeapTupleIsValid(tuple)) |
| 14033 | ereport(ERROR, |
| 14034 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
| 14035 | errmsg("foreign table \"%s\" does not exist" , |
| 14036 | RelationGetRelationName(rel)))); |
| 14037 | tableform = (Form_pg_foreign_table) GETSTRUCT(tuple); |
| 14038 | server = GetForeignServer(tableform->ftserver); |
| 14039 | fdw = GetForeignDataWrapper(server->fdwid); |
| 14040 | |
| 14041 | memset(repl_val, 0, sizeof(repl_val)); |
| 14042 | memset(repl_null, false, sizeof(repl_null)); |
| 14043 | memset(repl_repl, false, sizeof(repl_repl)); |
| 14044 | |
| 14045 | /* Extract the current options */ |
| 14046 | datum = SysCacheGetAttr(FOREIGNTABLEREL, |
| 14047 | tuple, |
| 14048 | Anum_pg_foreign_table_ftoptions, |
| 14049 | &isnull); |
| 14050 | if (isnull) |
| 14051 | datum = PointerGetDatum(NULL); |
| 14052 | |
| 14053 | /* Transform the options */ |
| 14054 | datum = transformGenericOptions(ForeignTableRelationId, |
| 14055 | datum, |
| 14056 | options, |
| 14057 | fdw->fdwvalidator); |
| 14058 | |
| 14059 | if (PointerIsValid(DatumGetPointer(datum))) |
| 14060 | repl_val[Anum_pg_foreign_table_ftoptions - 1] = datum; |
| 14061 | else |
| 14062 | repl_null[Anum_pg_foreign_table_ftoptions - 1] = true; |
| 14063 | |
| 14064 | repl_repl[Anum_pg_foreign_table_ftoptions - 1] = true; |
| 14065 | |
| 14066 | /* Everything looks good - update the tuple */ |
| 14067 | |
| 14068 | tuple = heap_modify_tuple(tuple, RelationGetDescr(ftrel), |
| 14069 | repl_val, repl_null, repl_repl); |
| 14070 | |
| 14071 | CatalogTupleUpdate(ftrel, &tuple->t_self, tuple); |
| 14072 | |
| 14073 | /* |
| 14074 | * Invalidate relcache so that all sessions will refresh any cached plans |
| 14075 | * that might depend on the old options. |
| 14076 | */ |
| 14077 | CacheInvalidateRelcache(rel); |
| 14078 | |
| 14079 | InvokeObjectPostAlterHook(ForeignTableRelationId, |
| 14080 | RelationGetRelid(rel), 0); |
| 14081 | |
| 14082 | table_close(ftrel, RowExclusiveLock); |
| 14083 | |
| 14084 | heap_freetuple(tuple); |
| 14085 | } |
| 14086 | |
| 14087 | /* |
| 14088 | * Preparation phase for SET LOGGED/UNLOGGED |
| 14089 | * |
| 14090 | * This verifies that we're not trying to change a temp table. Also, |
| 14091 | * existing foreign key constraints are checked to avoid ending up with |
| 14092 | * permanent tables referencing unlogged tables. |
| 14093 | * |
| 14094 | * Return value is false if the operation is a no-op (in which case the |
| 14095 | * checks are skipped), otherwise true. |
| 14096 | */ |
| 14097 | static bool |
| 14098 | ATPrepChangePersistence(Relation rel, bool toLogged) |
| 14099 | { |
| 14100 | Relation pg_constraint; |
| 14101 | HeapTuple tuple; |
| 14102 | SysScanDesc scan; |
| 14103 | ScanKeyData skey[1]; |
| 14104 | |
| 14105 | /* |
| 14106 | * Disallow changing status for a temp table. Also verify whether we can |
| 14107 | * get away with doing nothing; in such cases we don't need to run the |
| 14108 | * checks below, either. |
| 14109 | */ |
| 14110 | switch (rel->rd_rel->relpersistence) |
| 14111 | { |
| 14112 | case RELPERSISTENCE_TEMP: |
| 14113 | ereport(ERROR, |
| 14114 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 14115 | errmsg("cannot change logged status of table \"%s\" because it is temporary" , |
| 14116 | RelationGetRelationName(rel)), |
| 14117 | errtable(rel))); |
| 14118 | break; |
| 14119 | case RELPERSISTENCE_PERMANENT: |
| 14120 | if (toLogged) |
| 14121 | /* nothing to do */ |
| 14122 | return false; |
| 14123 | break; |
| 14124 | case RELPERSISTENCE_UNLOGGED: |
| 14125 | if (!toLogged) |
| 14126 | /* nothing to do */ |
| 14127 | return false; |
| 14128 | break; |
| 14129 | } |
| 14130 | |
| 14131 | /* |
| 14132 | * Check that the table is not part any publication when changing to |
| 14133 | * UNLOGGED as UNLOGGED tables can't be published. |
| 14134 | */ |
| 14135 | if (!toLogged && |
| 14136 | list_length(GetRelationPublications(RelationGetRelid(rel))) > 0) |
| 14137 | ereport(ERROR, |
| 14138 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
| 14139 | errmsg("cannot change table \"%s\" to unlogged because it is part of a publication" , |
| 14140 | RelationGetRelationName(rel)), |
| 14141 | errdetail("Unlogged relations cannot be replicated." ))); |
| 14142 | |
| 14143 | /* |
| 14144 | * Check existing foreign key constraints to preserve the invariant that |
| 14145 | * permanent tables cannot reference unlogged ones. Self-referencing |
| 14146 | * foreign keys can safely be ignored. |
| 14147 | */ |
| 14148 | pg_constraint = table_open(ConstraintRelationId, AccessShareLock); |
| 14149 | |
| 14150 | /* |
| 14151 | * Scan conrelid if changing to permanent, else confrelid. This also |
| 14152 | * determines whether a useful index exists. |
| 14153 | */ |
| 14154 | ScanKeyInit(&skey[0], |
| 14155 | toLogged ? Anum_pg_constraint_conrelid : |
| 14156 | Anum_pg_constraint_confrelid, |
| 14157 | BTEqualStrategyNumber, F_OIDEQ, |
| 14158 | ObjectIdGetDatum(RelationGetRelid(rel))); |
| 14159 | scan = systable_beginscan(pg_constraint, |
| 14160 | toLogged ? ConstraintRelidTypidNameIndexId : InvalidOid, |
| 14161 | true, NULL, 1, skey); |
| 14162 | |
| 14163 | while (HeapTupleIsValid(tuple = systable_getnext(scan))) |
| 14164 | { |
| 14165 | Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple); |
| 14166 | |
| 14167 | if (con->contype == CONSTRAINT_FOREIGN) |
| 14168 | { |
| 14169 | Oid foreignrelid; |
| 14170 | Relation foreignrel; |
| 14171 | |
| 14172 | /* the opposite end of what we used as scankey */ |
| 14173 | foreignrelid = toLogged ? con->confrelid : con->conrelid; |
| 14174 | |
| 14175 | /* ignore if self-referencing */ |
| 14176 | if (RelationGetRelid(rel) == foreignrelid) |
| 14177 | continue; |
| 14178 | |
| 14179 | foreignrel = relation_open(foreignrelid, AccessShareLock); |
| 14180 | |
| 14181 | if (toLogged) |
| 14182 | { |
| 14183 | if (foreignrel->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT) |
| 14184 | ereport(ERROR, |
| 14185 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 14186 | errmsg("could not change table \"%s\" to logged because it references unlogged table \"%s\"" , |
| 14187 | RelationGetRelationName(rel), |
| 14188 | RelationGetRelationName(foreignrel)), |
| 14189 | errtableconstraint(rel, NameStr(con->conname)))); |
| 14190 | } |
| 14191 | else |
| 14192 | { |
| 14193 | if (foreignrel->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT) |
| 14194 | ereport(ERROR, |
| 14195 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
| 14196 | errmsg("could not change table \"%s\" to unlogged because it references logged table \"%s\"" , |
| 14197 | RelationGetRelationName(rel), |
| 14198 | RelationGetRelationName(foreignrel)), |
| 14199 | errtableconstraint(rel, NameStr(con->conname)))); |
| 14200 | } |
| 14201 | |
| 14202 | relation_close(foreignrel, AccessShareLock); |
| 14203 | } |
| 14204 | } |
| 14205 | |
| 14206 | systable_endscan(scan); |
| 14207 | |
| 14208 | table_close(pg_constraint, AccessShareLock); |
| 14209 | |
| 14210 | return true; |
| 14211 | } |
| 14212 | |
| 14213 | /* |
| 14214 | * Execute ALTER TABLE SET SCHEMA |
| 14215 | */ |
| 14216 | ObjectAddress |
| 14217 | AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema) |
| 14218 | { |
| 14219 | Relation rel; |
| 14220 | Oid relid; |
| 14221 | Oid oldNspOid; |
| 14222 | Oid nspOid; |
| 14223 | RangeVar *newrv; |
| 14224 | ObjectAddresses *objsMoved; |
| 14225 | ObjectAddress myself; |
| 14226 | |
| 14227 | relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock, |
| 14228 | stmt->missing_ok ? RVR_MISSING_OK : 0, |
| 14229 | RangeVarCallbackForAlterRelation, |
| 14230 | (void *) stmt); |
| 14231 | |
| 14232 | if (!OidIsValid(relid)) |
| 14233 | { |
| 14234 | ereport(NOTICE, |
| 14235 | (errmsg("relation \"%s\" does not exist, skipping" , |
| 14236 | stmt->relation->relname))); |
| 14237 | return InvalidObjectAddress; |
| 14238 | } |
| 14239 | |
| 14240 | rel = relation_open(relid, NoLock); |
| 14241 | |
| 14242 | oldNspOid = RelationGetNamespace(rel); |
| 14243 | |
| 14244 | /* If it's an owned sequence, disallow moving it by itself. */ |
| 14245 | if (rel->rd_rel->relkind == RELKIND_SEQUENCE) |
| 14246 | { |
| 14247 | Oid tableId; |
| 14248 | int32 colId; |
| 14249 | |
| 14250 | if (sequenceIsOwned(relid, DEPENDENCY_AUTO, &tableId, &colId) || |
| 14251 | sequenceIsOwned(relid, DEPENDENCY_INTERNAL, &tableId, &colId)) |
| 14252 | ereport(ERROR, |
| 14253 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 14254 | errmsg("cannot move an owned sequence into another schema" ), |
| 14255 | errdetail("Sequence \"%s\" is linked to table \"%s\"." , |
| 14256 | RelationGetRelationName(rel), |
| 14257 | get_rel_name(tableId)))); |
| 14258 | } |
| 14259 | |
| 14260 | /* Get and lock schema OID and check its permissions. */ |
| 14261 | newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1); |
| 14262 | nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL); |
| 14263 | |
| 14264 | /* common checks on switching namespaces */ |
| 14265 | CheckSetNamespace(oldNspOid, nspOid); |
| 14266 | |
| 14267 | objsMoved = new_object_addresses(); |
| 14268 | AlterTableNamespaceInternal(rel, oldNspOid, nspOid, objsMoved); |
| 14269 | free_object_addresses(objsMoved); |
| 14270 | |
| 14271 | ObjectAddressSet(myself, RelationRelationId, relid); |
| 14272 | |
| 14273 | if (oldschema) |
| 14274 | *oldschema = oldNspOid; |
| 14275 | |
| 14276 | /* close rel, but keep lock until commit */ |
| 14277 | relation_close(rel, NoLock); |
| 14278 | |
| 14279 | return myself; |
| 14280 | } |
| 14281 | |
| 14282 | /* |
| 14283 | * The guts of relocating a table or materialized view to another namespace: |
| 14284 | * besides moving the relation itself, its dependent objects are relocated to |
| 14285 | * the new schema. |
| 14286 | */ |
| 14287 | void |
| 14288 | AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid, |
| 14289 | ObjectAddresses *objsMoved) |
| 14290 | { |
| 14291 | Relation classRel; |
| 14292 | |
| 14293 | Assert(objsMoved != NULL); |
| 14294 | |
| 14295 | /* OK, modify the pg_class row and pg_depend entry */ |
| 14296 | classRel = table_open(RelationRelationId, RowExclusiveLock); |
| 14297 | |
| 14298 | AlterRelationNamespaceInternal(classRel, RelationGetRelid(rel), oldNspOid, |
| 14299 | nspOid, true, objsMoved); |
| 14300 | |
| 14301 | /* Fix the table's row type too */ |
| 14302 | AlterTypeNamespaceInternal(rel->rd_rel->reltype, |
| 14303 | nspOid, false, false, objsMoved); |
| 14304 | |
| 14305 | /* Fix other dependent stuff */ |
| 14306 | if (rel->rd_rel->relkind == RELKIND_RELATION || |
| 14307 | rel->rd_rel->relkind == RELKIND_MATVIEW || |
| 14308 | rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) |
| 14309 | { |
| 14310 | AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved); |
| 14311 | AlterSeqNamespaces(classRel, rel, oldNspOid, nspOid, |
| 14312 | objsMoved, AccessExclusiveLock); |
| 14313 | AlterConstraintNamespaces(RelationGetRelid(rel), oldNspOid, nspOid, |
| 14314 | false, objsMoved); |
| 14315 | } |
| 14316 | |
| 14317 | table_close(classRel, RowExclusiveLock); |
| 14318 | } |
| 14319 | |
| 14320 | /* |
| 14321 | * The guts of relocating a relation to another namespace: fix the pg_class |
| 14322 | * entry, and the pg_depend entry if any. Caller must already have |
| 14323 | * opened and write-locked pg_class. |
| 14324 | */ |
| 14325 | void |
| 14326 | AlterRelationNamespaceInternal(Relation classRel, Oid relOid, |
| 14327 | Oid oldNspOid, Oid newNspOid, |
| 14328 | bool hasDependEntry, |
| 14329 | ObjectAddresses *objsMoved) |
| 14330 | { |
| 14331 | HeapTuple classTup; |
| 14332 | Form_pg_class classForm; |
| 14333 | ObjectAddress thisobj; |
| 14334 | bool already_done = false; |
| 14335 | |
| 14336 | classTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relOid)); |
| 14337 | if (!HeapTupleIsValid(classTup)) |
| 14338 | elog(ERROR, "cache lookup failed for relation %u" , relOid); |
| 14339 | classForm = (Form_pg_class) GETSTRUCT(classTup); |
| 14340 | |
| 14341 | Assert(classForm->relnamespace == oldNspOid); |
| 14342 | |
| 14343 | thisobj.classId = RelationRelationId; |
| 14344 | thisobj.objectId = relOid; |
| 14345 | thisobj.objectSubId = 0; |
| 14346 | |
| 14347 | /* |
| 14348 | * If the object has already been moved, don't move it again. If it's |
| 14349 | * already in the right place, don't move it, but still fire the object |
| 14350 | * access hook. |
| 14351 | */ |
| 14352 | already_done = object_address_present(&thisobj, objsMoved); |
| 14353 | if (!already_done && oldNspOid != newNspOid) |
| 14354 | { |
| 14355 | /* check for duplicate name (more friendly than unique-index failure) */ |
| 14356 | if (get_relname_relid(NameStr(classForm->relname), |
| 14357 | newNspOid) != InvalidOid) |
| 14358 | ereport(ERROR, |
| 14359 | (errcode(ERRCODE_DUPLICATE_TABLE), |
| 14360 | errmsg("relation \"%s\" already exists in schema \"%s\"" , |
| 14361 | NameStr(classForm->relname), |
| 14362 | get_namespace_name(newNspOid)))); |
| 14363 | |
| 14364 | /* classTup is a copy, so OK to scribble on */ |
| 14365 | classForm->relnamespace = newNspOid; |
| 14366 | |
| 14367 | CatalogTupleUpdate(classRel, &classTup->t_self, classTup); |
| 14368 | |
| 14369 | /* Update dependency on schema if caller said so */ |
| 14370 | if (hasDependEntry && |
| 14371 | changeDependencyFor(RelationRelationId, |
| 14372 | relOid, |
| 14373 | NamespaceRelationId, |
| 14374 | oldNspOid, |
| 14375 | newNspOid) != 1) |
| 14376 | elog(ERROR, "failed to change schema dependency for relation \"%s\"" , |
| 14377 | NameStr(classForm->relname)); |
| 14378 | } |
| 14379 | if (!already_done) |
| 14380 | { |
| 14381 | add_exact_object_address(&thisobj, objsMoved); |
| 14382 | |
| 14383 | InvokeObjectPostAlterHook(RelationRelationId, relOid, 0); |
| 14384 | } |
| 14385 | |
| 14386 | heap_freetuple(classTup); |
| 14387 | } |
| 14388 | |
| 14389 | /* |
| 14390 | * Move all indexes for the specified relation to another namespace. |
| 14391 | * |
| 14392 | * Note: we assume adequate permission checking was done by the caller, |
| 14393 | * and that the caller has a suitable lock on the owning relation. |
| 14394 | */ |
| 14395 | static void |
| 14396 | AlterIndexNamespaces(Relation classRel, Relation rel, |
| 14397 | Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved) |
| 14398 | { |
| 14399 | List *indexList; |
| 14400 | ListCell *l; |
| 14401 | |
| 14402 | indexList = RelationGetIndexList(rel); |
| 14403 | |
| 14404 | foreach(l, indexList) |
| 14405 | { |
| 14406 | Oid indexOid = lfirst_oid(l); |
| 14407 | ObjectAddress thisobj; |
| 14408 | |
| 14409 | thisobj.classId = RelationRelationId; |
| 14410 | thisobj.objectId = indexOid; |
| 14411 | thisobj.objectSubId = 0; |
| 14412 | |
| 14413 | /* |
| 14414 | * Note: currently, the index will not have its own dependency on the |
| 14415 | * namespace, so we don't need to do changeDependencyFor(). There's no |
| 14416 | * row type in pg_type, either. |
| 14417 | * |
| 14418 | * XXX this objsMoved test may be pointless -- surely we have a single |
| 14419 | * dependency link from a relation to each index? |
| 14420 | */ |
| 14421 | if (!object_address_present(&thisobj, objsMoved)) |
| 14422 | { |
| 14423 | AlterRelationNamespaceInternal(classRel, indexOid, |
| 14424 | oldNspOid, newNspOid, |
| 14425 | false, objsMoved); |
| 14426 | add_exact_object_address(&thisobj, objsMoved); |
| 14427 | } |
| 14428 | } |
| 14429 | |
| 14430 | list_free(indexList); |
| 14431 | } |
| 14432 | |
| 14433 | /* |
| 14434 | * Move all identity and SERIAL-column sequences of the specified relation to another |
| 14435 | * namespace. |
| 14436 | * |
| 14437 | * Note: we assume adequate permission checking was done by the caller, |
| 14438 | * and that the caller has a suitable lock on the owning relation. |
| 14439 | */ |
| 14440 | static void |
| 14441 | AlterSeqNamespaces(Relation classRel, Relation rel, |
| 14442 | Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved, |
| 14443 | LOCKMODE lockmode) |
| 14444 | { |
| 14445 | Relation depRel; |
| 14446 | SysScanDesc scan; |
| 14447 | ScanKeyData key[2]; |
| 14448 | HeapTuple tup; |
| 14449 | |
| 14450 | /* |
| 14451 | * SERIAL sequences are those having an auto dependency on one of the |
| 14452 | * table's columns (we don't care *which* column, exactly). |
| 14453 | */ |
| 14454 | depRel = table_open(DependRelationId, AccessShareLock); |
| 14455 | |
| 14456 | ScanKeyInit(&key[0], |
| 14457 | Anum_pg_depend_refclassid, |
| 14458 | BTEqualStrategyNumber, F_OIDEQ, |
| 14459 | ObjectIdGetDatum(RelationRelationId)); |
| 14460 | ScanKeyInit(&key[1], |
| 14461 | Anum_pg_depend_refobjid, |
| 14462 | BTEqualStrategyNumber, F_OIDEQ, |
| 14463 | ObjectIdGetDatum(RelationGetRelid(rel))); |
| 14464 | /* we leave refobjsubid unspecified */ |
| 14465 | |
| 14466 | scan = systable_beginscan(depRel, DependReferenceIndexId, true, |
| 14467 | NULL, 2, key); |
| 14468 | |
| 14469 | while (HeapTupleIsValid(tup = systable_getnext(scan))) |
| 14470 | { |
| 14471 | Form_pg_depend depForm = (Form_pg_depend) GETSTRUCT(tup); |
| 14472 | Relation seqRel; |
| 14473 | |
| 14474 | /* skip dependencies other than auto dependencies on columns */ |
| 14475 | if (depForm->refobjsubid == 0 || |
| 14476 | depForm->classid != RelationRelationId || |
| 14477 | depForm->objsubid != 0 || |
| 14478 | !(depForm->deptype == DEPENDENCY_AUTO || depForm->deptype == DEPENDENCY_INTERNAL)) |
| 14479 | continue; |
| 14480 | |
| 14481 | /* Use relation_open just in case it's an index */ |
| 14482 | seqRel = relation_open(depForm->objid, lockmode); |
| 14483 | |
| 14484 | /* skip non-sequence relations */ |
| 14485 | if (RelationGetForm(seqRel)->relkind != RELKIND_SEQUENCE) |
| 14486 | { |
| 14487 | /* No need to keep the lock */ |
| 14488 | relation_close(seqRel, lockmode); |
| 14489 | continue; |
| 14490 | } |
| 14491 | |
| 14492 | /* Fix the pg_class and pg_depend entries */ |
| 14493 | AlterRelationNamespaceInternal(classRel, depForm->objid, |
| 14494 | oldNspOid, newNspOid, |
| 14495 | true, objsMoved); |
| 14496 | |
| 14497 | /* |
| 14498 | * Sequences have entries in pg_type. We need to be careful to move |
| 14499 | * them to the new namespace, too. |
| 14500 | */ |
| 14501 | AlterTypeNamespaceInternal(RelationGetForm(seqRel)->reltype, |
| 14502 | newNspOid, false, false, objsMoved); |
| 14503 | |
| 14504 | /* Now we can close it. Keep the lock till end of transaction. */ |
| 14505 | relation_close(seqRel, NoLock); |
| 14506 | } |
| 14507 | |
| 14508 | systable_endscan(scan); |
| 14509 | |
| 14510 | relation_close(depRel, AccessShareLock); |
| 14511 | } |
| 14512 | |
| 14513 | |
| 14514 | /* |
| 14515 | * This code supports |
| 14516 | * CREATE TEMP TABLE ... ON COMMIT { DROP | PRESERVE ROWS | DELETE ROWS } |
| 14517 | * |
| 14518 | * Because we only support this for TEMP tables, it's sufficient to remember |
| 14519 | * the state in a backend-local data structure. |
| 14520 | */ |
| 14521 | |
| 14522 | /* |
| 14523 | * Register a newly-created relation's ON COMMIT action. |
| 14524 | */ |
| 14525 | void |
| 14526 | register_on_commit_action(Oid relid, OnCommitAction action) |
| 14527 | { |
| 14528 | OnCommitItem *oc; |
| 14529 | MemoryContext oldcxt; |
| 14530 | |
| 14531 | /* |
| 14532 | * We needn't bother registering the relation unless there is an ON COMMIT |
| 14533 | * action we need to take. |
| 14534 | */ |
| 14535 | if (action == ONCOMMIT_NOOP || action == ONCOMMIT_PRESERVE_ROWS) |
| 14536 | return; |
| 14537 | |
| 14538 | oldcxt = MemoryContextSwitchTo(CacheMemoryContext); |
| 14539 | |
| 14540 | oc = (OnCommitItem *) palloc(sizeof(OnCommitItem)); |
| 14541 | oc->relid = relid; |
| 14542 | oc->oncommit = action; |
| 14543 | oc->creating_subid = GetCurrentSubTransactionId(); |
| 14544 | oc->deleting_subid = InvalidSubTransactionId; |
| 14545 | |
| 14546 | on_commits = lcons(oc, on_commits); |
| 14547 | |
| 14548 | MemoryContextSwitchTo(oldcxt); |
| 14549 | } |
| 14550 | |
| 14551 | /* |
| 14552 | * Unregister any ON COMMIT action when a relation is deleted. |
| 14553 | * |
| 14554 | * Actually, we only mark the OnCommitItem entry as to be deleted after commit. |
| 14555 | */ |
| 14556 | void |
| 14557 | remove_on_commit_action(Oid relid) |
| 14558 | { |
| 14559 | ListCell *l; |
| 14560 | |
| 14561 | foreach(l, on_commits) |
| 14562 | { |
| 14563 | OnCommitItem *oc = (OnCommitItem *) lfirst(l); |
| 14564 | |
| 14565 | if (oc->relid == relid) |
| 14566 | { |
| 14567 | oc->deleting_subid = GetCurrentSubTransactionId(); |
| 14568 | break; |
| 14569 | } |
| 14570 | } |
| 14571 | } |
| 14572 | |
| 14573 | /* |
| 14574 | * Perform ON COMMIT actions. |
| 14575 | * |
| 14576 | * This is invoked just before actually committing, since it's possible |
| 14577 | * to encounter errors. |
| 14578 | */ |
| 14579 | void |
| 14580 | PreCommit_on_commit_actions(void) |
| 14581 | { |
| 14582 | ListCell *l; |
| 14583 | List *oids_to_truncate = NIL; |
| 14584 | List *oids_to_drop = NIL; |
| 14585 | |
| 14586 | foreach(l, on_commits) |
| 14587 | { |
| 14588 | OnCommitItem *oc = (OnCommitItem *) lfirst(l); |
| 14589 | |
| 14590 | /* Ignore entry if already dropped in this xact */ |
| 14591 | if (oc->deleting_subid != InvalidSubTransactionId) |
| 14592 | continue; |
| 14593 | |
| 14594 | switch (oc->oncommit) |
| 14595 | { |
| 14596 | case ONCOMMIT_NOOP: |
| 14597 | case ONCOMMIT_PRESERVE_ROWS: |
| 14598 | /* Do nothing (there shouldn't be such entries, actually) */ |
| 14599 | break; |
| 14600 | case ONCOMMIT_DELETE_ROWS: |
| 14601 | |
| 14602 | /* |
| 14603 | * If this transaction hasn't accessed any temporary |
| 14604 | * relations, we can skip truncating ON COMMIT DELETE ROWS |
| 14605 | * tables, as they must still be empty. |
| 14606 | */ |
| 14607 | if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE)) |
| 14608 | oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid); |
| 14609 | break; |
| 14610 | case ONCOMMIT_DROP: |
| 14611 | oids_to_drop = lappend_oid(oids_to_drop, oc->relid); |
| 14612 | break; |
| 14613 | } |
| 14614 | } |
| 14615 | |
| 14616 | /* |
| 14617 | * Truncate relations before dropping so that all dependencies between |
| 14618 | * relations are removed after they are worked on. Doing it like this |
| 14619 | * might be a waste as it is possible that a relation being truncated will |
| 14620 | * be dropped anyway due to its parent being dropped, but this makes the |
| 14621 | * code more robust because of not having to re-check that the relation |
| 14622 | * exists at truncation time. |
| 14623 | */ |
| 14624 | if (oids_to_truncate != NIL) |
| 14625 | heap_truncate(oids_to_truncate); |
| 14626 | |
| 14627 | if (oids_to_drop != NIL) |
| 14628 | { |
| 14629 | ObjectAddresses *targetObjects = new_object_addresses(); |
| 14630 | ListCell *l; |
| 14631 | |
| 14632 | foreach(l, oids_to_drop) |
| 14633 | { |
| 14634 | ObjectAddress object; |
| 14635 | |
| 14636 | object.classId = RelationRelationId; |
| 14637 | object.objectId = lfirst_oid(l); |
| 14638 | object.objectSubId = 0; |
| 14639 | |
| 14640 | Assert(!object_address_present(&object, targetObjects)); |
| 14641 | |
| 14642 | add_exact_object_address(&object, targetObjects); |
| 14643 | } |
| 14644 | |
| 14645 | /* |
| 14646 | * Since this is an automatic drop, rather than one directly initiated |
| 14647 | * by the user, we pass the PERFORM_DELETION_INTERNAL flag. |
| 14648 | */ |
| 14649 | performMultipleDeletions(targetObjects, DROP_CASCADE, |
| 14650 | PERFORM_DELETION_INTERNAL | PERFORM_DELETION_QUIETLY); |
| 14651 | |
| 14652 | #ifdef USE_ASSERT_CHECKING |
| 14653 | |
| 14654 | /* |
| 14655 | * Note that table deletion will call remove_on_commit_action, so the |
| 14656 | * entry should get marked as deleted. |
| 14657 | */ |
| 14658 | foreach(l, on_commits) |
| 14659 | { |
| 14660 | OnCommitItem *oc = (OnCommitItem *) lfirst(l); |
| 14661 | |
| 14662 | if (oc->oncommit != ONCOMMIT_DROP) |
| 14663 | continue; |
| 14664 | |
| 14665 | Assert(oc->deleting_subid != InvalidSubTransactionId); |
| 14666 | } |
| 14667 | #endif |
| 14668 | } |
| 14669 | } |
| 14670 | |
| 14671 | /* |
| 14672 | * Post-commit or post-abort cleanup for ON COMMIT management. |
| 14673 | * |
| 14674 | * All we do here is remove no-longer-needed OnCommitItem entries. |
| 14675 | * |
| 14676 | * During commit, remove entries that were deleted during this transaction; |
| 14677 | * during abort, remove those created during this transaction. |
| 14678 | */ |
| 14679 | void |
| 14680 | AtEOXact_on_commit_actions(bool isCommit) |
| 14681 | { |
| 14682 | ListCell *cur_item; |
| 14683 | ListCell *prev_item; |
| 14684 | |
| 14685 | prev_item = NULL; |
| 14686 | cur_item = list_head(on_commits); |
| 14687 | |
| 14688 | while (cur_item != NULL) |
| 14689 | { |
| 14690 | OnCommitItem *oc = (OnCommitItem *) lfirst(cur_item); |
| 14691 | |
| 14692 | if (isCommit ? oc->deleting_subid != InvalidSubTransactionId : |
| 14693 | oc->creating_subid != InvalidSubTransactionId) |
| 14694 | { |
| 14695 | /* cur_item must be removed */ |
| 14696 | on_commits = list_delete_cell(on_commits, cur_item, prev_item); |
| 14697 | pfree(oc); |
| 14698 | if (prev_item) |
| 14699 | cur_item = lnext(prev_item); |
| 14700 | else |
| 14701 | cur_item = list_head(on_commits); |
| 14702 | } |
| 14703 | else |
| 14704 | { |
| 14705 | /* cur_item must be preserved */ |
| 14706 | oc->creating_subid = InvalidSubTransactionId; |
| 14707 | oc->deleting_subid = InvalidSubTransactionId; |
| 14708 | prev_item = cur_item; |
| 14709 | cur_item = lnext(prev_item); |
| 14710 | } |
| 14711 | } |
| 14712 | } |
| 14713 | |
| 14714 | /* |
| 14715 | * Post-subcommit or post-subabort cleanup for ON COMMIT management. |
| 14716 | * |
| 14717 | * During subabort, we can immediately remove entries created during this |
| 14718 | * subtransaction. During subcommit, just relabel entries marked during |
| 14719 | * this subtransaction as being the parent's responsibility. |
| 14720 | */ |
| 14721 | void |
| 14722 | AtEOSubXact_on_commit_actions(bool isCommit, SubTransactionId mySubid, |
| 14723 | SubTransactionId parentSubid) |
| 14724 | { |
| 14725 | ListCell *cur_item; |
| 14726 | ListCell *prev_item; |
| 14727 | |
| 14728 | prev_item = NULL; |
| 14729 | cur_item = list_head(on_commits); |
| 14730 | |
| 14731 | while (cur_item != NULL) |
| 14732 | { |
| 14733 | OnCommitItem *oc = (OnCommitItem *) lfirst(cur_item); |
| 14734 | |
| 14735 | if (!isCommit && oc->creating_subid == mySubid) |
| 14736 | { |
| 14737 | /* cur_item must be removed */ |
| 14738 | on_commits = list_delete_cell(on_commits, cur_item, prev_item); |
| 14739 | pfree(oc); |
| 14740 | if (prev_item) |
| 14741 | cur_item = lnext(prev_item); |
| 14742 | else |
| 14743 | cur_item = list_head(on_commits); |
| 14744 | } |
| 14745 | else |
| 14746 | { |
| 14747 | /* cur_item must be preserved */ |
| 14748 | if (oc->creating_subid == mySubid) |
| 14749 | oc->creating_subid = parentSubid; |
| 14750 | if (oc->deleting_subid == mySubid) |
| 14751 | oc->deleting_subid = isCommit ? parentSubid : InvalidSubTransactionId; |
| 14752 | prev_item = cur_item; |
| 14753 | cur_item = lnext(prev_item); |
| 14754 | } |
| 14755 | } |
| 14756 | } |
| 14757 | |
| 14758 | /* |
| 14759 | * This is intended as a callback for RangeVarGetRelidExtended(). It allows |
| 14760 | * the relation to be locked only if (1) it's a plain table, materialized |
| 14761 | * view, or TOAST table and (2) the current user is the owner (or the |
| 14762 | * superuser). This meets the permission-checking needs of CLUSTER, REINDEX |
| 14763 | * TABLE, and REFRESH MATERIALIZED VIEW; we expose it here so that it can be |
| 14764 | * used by all. |
| 14765 | */ |
| 14766 | void |
| 14767 | RangeVarCallbackOwnsTable(const RangeVar *relation, |
| 14768 | Oid relId, Oid oldRelId, void *arg) |
| 14769 | { |
| 14770 | char relkind; |
| 14771 | |
| 14772 | /* Nothing to do if the relation was not found. */ |
| 14773 | if (!OidIsValid(relId)) |
| 14774 | return; |
| 14775 | |
| 14776 | /* |
| 14777 | * If the relation does exist, check whether it's an index. But note that |
| 14778 | * the relation might have been dropped between the time we did the name |
| 14779 | * lookup and now. In that case, there's nothing to do. |
| 14780 | */ |
| 14781 | relkind = get_rel_relkind(relId); |
| 14782 | if (!relkind) |
| 14783 | return; |
| 14784 | if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE && |
| 14785 | relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE) |
| 14786 | ereport(ERROR, |
| 14787 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 14788 | errmsg("\"%s\" is not a table or materialized view" , relation->relname))); |
| 14789 | |
| 14790 | /* Check permissions */ |
| 14791 | if (!pg_class_ownercheck(relId, GetUserId())) |
| 14792 | aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relId)), relation->relname); |
| 14793 | } |
| 14794 | |
| 14795 | /* |
| 14796 | * Callback to RangeVarGetRelidExtended() for TRUNCATE processing. |
| 14797 | */ |
| 14798 | static void |
| 14799 | RangeVarCallbackForTruncate(const RangeVar *relation, |
| 14800 | Oid relId, Oid oldRelId, void *arg) |
| 14801 | { |
| 14802 | HeapTuple tuple; |
| 14803 | |
| 14804 | /* Nothing to do if the relation was not found. */ |
| 14805 | if (!OidIsValid(relId)) |
| 14806 | return; |
| 14807 | |
| 14808 | tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relId)); |
| 14809 | if (!HeapTupleIsValid(tuple)) /* should not happen */ |
| 14810 | elog(ERROR, "cache lookup failed for relation %u" , relId); |
| 14811 | |
| 14812 | truncate_check_rel(relId, (Form_pg_class) GETSTRUCT(tuple)); |
| 14813 | |
| 14814 | ReleaseSysCache(tuple); |
| 14815 | } |
| 14816 | |
| 14817 | /* |
| 14818 | * Callback to RangeVarGetRelidExtended(), similar to |
| 14819 | * RangeVarCallbackOwnsTable() but without checks on the type of the relation. |
| 14820 | */ |
| 14821 | void |
| 14822 | RangeVarCallbackOwnsRelation(const RangeVar *relation, |
| 14823 | Oid relId, Oid oldRelId, void *arg) |
| 14824 | { |
| 14825 | HeapTuple tuple; |
| 14826 | |
| 14827 | /* Nothing to do if the relation was not found. */ |
| 14828 | if (!OidIsValid(relId)) |
| 14829 | return; |
| 14830 | |
| 14831 | tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relId)); |
| 14832 | if (!HeapTupleIsValid(tuple)) /* should not happen */ |
| 14833 | elog(ERROR, "cache lookup failed for relation %u" , relId); |
| 14834 | |
| 14835 | if (!pg_class_ownercheck(relId, GetUserId())) |
| 14836 | aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relId)), |
| 14837 | relation->relname); |
| 14838 | |
| 14839 | if (!allowSystemTableMods && |
| 14840 | IsSystemClass(relId, (Form_pg_class) GETSTRUCT(tuple))) |
| 14841 | ereport(ERROR, |
| 14842 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| 14843 | errmsg("permission denied: \"%s\" is a system catalog" , |
| 14844 | relation->relname))); |
| 14845 | |
| 14846 | ReleaseSysCache(tuple); |
| 14847 | } |
| 14848 | |
| 14849 | /* |
| 14850 | * Common RangeVarGetRelid callback for rename, set schema, and alter table |
| 14851 | * processing. |
| 14852 | */ |
| 14853 | static void |
| 14854 | RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid, |
| 14855 | void *arg) |
| 14856 | { |
| 14857 | Node *stmt = (Node *) arg; |
| 14858 | ObjectType reltype; |
| 14859 | HeapTuple tuple; |
| 14860 | Form_pg_class classform; |
| 14861 | AclResult aclresult; |
| 14862 | char relkind; |
| 14863 | |
| 14864 | tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); |
| 14865 | if (!HeapTupleIsValid(tuple)) |
| 14866 | return; /* concurrently dropped */ |
| 14867 | classform = (Form_pg_class) GETSTRUCT(tuple); |
| 14868 | relkind = classform->relkind; |
| 14869 | |
| 14870 | /* Must own relation. */ |
| 14871 | if (!pg_class_ownercheck(relid, GetUserId())) |
| 14872 | aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relid)), rv->relname); |
| 14873 | |
| 14874 | /* No system table modifications unless explicitly allowed. */ |
| 14875 | if (!allowSystemTableMods && IsSystemClass(relid, classform)) |
| 14876 | ereport(ERROR, |
| 14877 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| 14878 | errmsg("permission denied: \"%s\" is a system catalog" , |
| 14879 | rv->relname))); |
| 14880 | |
| 14881 | /* |
| 14882 | * Extract the specified relation type from the statement parse tree. |
| 14883 | * |
| 14884 | * Also, for ALTER .. RENAME, check permissions: the user must (still) |
| 14885 | * have CREATE rights on the containing namespace. |
| 14886 | */ |
| 14887 | if (IsA(stmt, RenameStmt)) |
| 14888 | { |
| 14889 | aclresult = pg_namespace_aclcheck(classform->relnamespace, |
| 14890 | GetUserId(), ACL_CREATE); |
| 14891 | if (aclresult != ACLCHECK_OK) |
| 14892 | aclcheck_error(aclresult, OBJECT_SCHEMA, |
| 14893 | get_namespace_name(classform->relnamespace)); |
| 14894 | reltype = ((RenameStmt *) stmt)->renameType; |
| 14895 | } |
| 14896 | else if (IsA(stmt, AlterObjectSchemaStmt)) |
| 14897 | reltype = ((AlterObjectSchemaStmt *) stmt)->objectType; |
| 14898 | |
| 14899 | else if (IsA(stmt, AlterTableStmt)) |
| 14900 | reltype = ((AlterTableStmt *) stmt)->relkind; |
| 14901 | else |
| 14902 | { |
| 14903 | elog(ERROR, "unrecognized node type: %d" , (int) nodeTag(stmt)); |
| 14904 | reltype = OBJECT_TABLE; /* placate compiler */ |
| 14905 | } |
| 14906 | |
| 14907 | /* |
| 14908 | * For compatibility with prior releases, we allow ALTER TABLE to be used |
| 14909 | * with most other types of relations (but not composite types). We allow |
| 14910 | * similar flexibility for ALTER INDEX in the case of RENAME, but not |
| 14911 | * otherwise. Otherwise, the user must select the correct form of the |
| 14912 | * command for the relation at issue. |
| 14913 | */ |
| 14914 | if (reltype == OBJECT_SEQUENCE && relkind != RELKIND_SEQUENCE) |
| 14915 | ereport(ERROR, |
| 14916 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 14917 | errmsg("\"%s\" is not a sequence" , rv->relname))); |
| 14918 | |
| 14919 | if (reltype == OBJECT_VIEW && relkind != RELKIND_VIEW) |
| 14920 | ereport(ERROR, |
| 14921 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 14922 | errmsg("\"%s\" is not a view" , rv->relname))); |
| 14923 | |
| 14924 | if (reltype == OBJECT_MATVIEW && relkind != RELKIND_MATVIEW) |
| 14925 | ereport(ERROR, |
| 14926 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 14927 | errmsg("\"%s\" is not a materialized view" , rv->relname))); |
| 14928 | |
| 14929 | if (reltype == OBJECT_FOREIGN_TABLE && relkind != RELKIND_FOREIGN_TABLE) |
| 14930 | ereport(ERROR, |
| 14931 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 14932 | errmsg("\"%s\" is not a foreign table" , rv->relname))); |
| 14933 | |
| 14934 | if (reltype == OBJECT_TYPE && relkind != RELKIND_COMPOSITE_TYPE) |
| 14935 | ereport(ERROR, |
| 14936 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 14937 | errmsg("\"%s\" is not a composite type" , rv->relname))); |
| 14938 | |
| 14939 | if (reltype == OBJECT_INDEX && relkind != RELKIND_INDEX && |
| 14940 | relkind != RELKIND_PARTITIONED_INDEX |
| 14941 | && !IsA(stmt, RenameStmt)) |
| 14942 | ereport(ERROR, |
| 14943 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 14944 | errmsg("\"%s\" is not an index" , rv->relname))); |
| 14945 | |
| 14946 | /* |
| 14947 | * Don't allow ALTER TABLE on composite types. We want people to use ALTER |
| 14948 | * TYPE for that. |
| 14949 | */ |
| 14950 | if (reltype != OBJECT_TYPE && relkind == RELKIND_COMPOSITE_TYPE) |
| 14951 | ereport(ERROR, |
| 14952 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 14953 | errmsg("\"%s\" is a composite type" , rv->relname), |
| 14954 | errhint("Use ALTER TYPE instead." ))); |
| 14955 | |
| 14956 | /* |
| 14957 | * Don't allow ALTER TABLE .. SET SCHEMA on relations that can't be moved |
| 14958 | * to a different schema, such as indexes and TOAST tables. |
| 14959 | */ |
| 14960 | if (IsA(stmt, AlterObjectSchemaStmt) && |
| 14961 | relkind != RELKIND_RELATION && |
| 14962 | relkind != RELKIND_VIEW && |
| 14963 | relkind != RELKIND_MATVIEW && |
| 14964 | relkind != RELKIND_SEQUENCE && |
| 14965 | relkind != RELKIND_FOREIGN_TABLE && |
| 14966 | relkind != RELKIND_PARTITIONED_TABLE) |
| 14967 | ereport(ERROR, |
| 14968 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 14969 | errmsg("\"%s\" is not a table, view, materialized view, sequence, or foreign table" , |
| 14970 | rv->relname))); |
| 14971 | |
| 14972 | ReleaseSysCache(tuple); |
| 14973 | } |
| 14974 | |
| 14975 | /* |
| 14976 | * Transform any expressions present in the partition key |
| 14977 | * |
| 14978 | * Returns a transformed PartitionSpec, as well as the strategy code |
| 14979 | */ |
| 14980 | static PartitionSpec * |
| 14981 | transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy) |
| 14982 | { |
| 14983 | PartitionSpec *newspec; |
| 14984 | ParseState *pstate; |
| 14985 | RangeTblEntry *rte; |
| 14986 | ListCell *l; |
| 14987 | |
| 14988 | newspec = makeNode(PartitionSpec); |
| 14989 | |
| 14990 | newspec->strategy = partspec->strategy; |
| 14991 | newspec->partParams = NIL; |
| 14992 | newspec->location = partspec->location; |
| 14993 | |
| 14994 | /* Parse partitioning strategy name */ |
| 14995 | if (pg_strcasecmp(partspec->strategy, "hash" ) == 0) |
| 14996 | *strategy = PARTITION_STRATEGY_HASH; |
| 14997 | else if (pg_strcasecmp(partspec->strategy, "list" ) == 0) |
| 14998 | *strategy = PARTITION_STRATEGY_LIST; |
| 14999 | else if (pg_strcasecmp(partspec->strategy, "range" ) == 0) |
| 15000 | *strategy = PARTITION_STRATEGY_RANGE; |
| 15001 | else |
| 15002 | ereport(ERROR, |
| 15003 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 15004 | errmsg("unrecognized partitioning strategy \"%s\"" , |
| 15005 | partspec->strategy))); |
| 15006 | |
| 15007 | /* Check valid number of columns for strategy */ |
| 15008 | if (*strategy == PARTITION_STRATEGY_LIST && |
| 15009 | list_length(partspec->partParams) != 1) |
| 15010 | ereport(ERROR, |
| 15011 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| 15012 | errmsg("cannot use \"list\" partition strategy with more than one column" ))); |
| 15013 | |
| 15014 | /* |
| 15015 | * Create a dummy ParseState and insert the target relation as its sole |
| 15016 | * rangetable entry. We need a ParseState for transformExpr. |
| 15017 | */ |
| 15018 | pstate = make_parsestate(NULL); |
| 15019 | rte = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, |
| 15020 | NULL, false, true); |
| 15021 | addRTEtoQuery(pstate, rte, true, true, true); |
| 15022 | |
| 15023 | /* take care of any partition expressions */ |
| 15024 | foreach(l, partspec->partParams) |
| 15025 | { |
| 15026 | PartitionElem *pelem = castNode(PartitionElem, lfirst(l)); |
| 15027 | |
| 15028 | if (pelem->expr) |
| 15029 | { |
| 15030 | /* Copy, to avoid scribbling on the input */ |
| 15031 | pelem = copyObject(pelem); |
| 15032 | |
| 15033 | /* Now do parse transformation of the expression */ |
| 15034 | pelem->expr = transformExpr(pstate, pelem->expr, |
| 15035 | EXPR_KIND_PARTITION_EXPRESSION); |
| 15036 | |
| 15037 | /* we have to fix its collations too */ |
| 15038 | assign_expr_collations(pstate, pelem->expr); |
| 15039 | } |
| 15040 | |
| 15041 | newspec->partParams = lappend(newspec->partParams, pelem); |
| 15042 | } |
| 15043 | |
| 15044 | return newspec; |
| 15045 | } |
| 15046 | |
| 15047 | /* |
| 15048 | * Compute per-partition-column information from a list of PartitionElems. |
| 15049 | * Expressions in the PartitionElems must be parse-analyzed already. |
| 15050 | */ |
| 15051 | static void |
| 15052 | ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNumber *partattrs, |
| 15053 | List **partexprs, Oid *partopclass, Oid *partcollation, |
| 15054 | char strategy) |
| 15055 | { |
| 15056 | int attn; |
| 15057 | ListCell *lc; |
| 15058 | Oid am_oid; |
| 15059 | |
| 15060 | attn = 0; |
| 15061 | foreach(lc, partParams) |
| 15062 | { |
| 15063 | PartitionElem *pelem = castNode(PartitionElem, lfirst(lc)); |
| 15064 | Oid atttype; |
| 15065 | Oid attcollation; |
| 15066 | |
| 15067 | if (pelem->name != NULL) |
| 15068 | { |
| 15069 | /* Simple attribute reference */ |
| 15070 | HeapTuple atttuple; |
| 15071 | Form_pg_attribute attform; |
| 15072 | |
| 15073 | atttuple = SearchSysCacheAttName(RelationGetRelid(rel), |
| 15074 | pelem->name); |
| 15075 | if (!HeapTupleIsValid(atttuple)) |
| 15076 | ereport(ERROR, |
| 15077 | (errcode(ERRCODE_UNDEFINED_COLUMN), |
| 15078 | errmsg("column \"%s\" named in partition key does not exist" , |
| 15079 | pelem->name), |
| 15080 | parser_errposition(pstate, pelem->location))); |
| 15081 | attform = (Form_pg_attribute) GETSTRUCT(atttuple); |
| 15082 | |
| 15083 | if (attform->attnum <= 0) |
| 15084 | ereport(ERROR, |
| 15085 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| 15086 | errmsg("cannot use system column \"%s\" in partition key" , |
| 15087 | pelem->name), |
| 15088 | parser_errposition(pstate, pelem->location))); |
| 15089 | |
| 15090 | /* |
| 15091 | * Generated columns cannot work: They are computed after BEFORE |
| 15092 | * triggers, but partition routing is done before all triggers. |
| 15093 | */ |
| 15094 | if (attform->attgenerated) |
| 15095 | ereport(ERROR, |
| 15096 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| 15097 | errmsg("cannot use generated column in partition key" ), |
| 15098 | errdetail("Column \"%s\" is a generated column." , |
| 15099 | pelem->name), |
| 15100 | parser_errposition(pstate, pelem->location))); |
| 15101 | |
| 15102 | partattrs[attn] = attform->attnum; |
| 15103 | atttype = attform->atttypid; |
| 15104 | attcollation = attform->attcollation; |
| 15105 | ReleaseSysCache(atttuple); |
| 15106 | } |
| 15107 | else |
| 15108 | { |
| 15109 | /* Expression */ |
| 15110 | Node *expr = pelem->expr; |
| 15111 | |
| 15112 | Assert(expr != NULL); |
| 15113 | atttype = exprType(expr); |
| 15114 | attcollation = exprCollation(expr); |
| 15115 | |
| 15116 | /* |
| 15117 | * Strip any top-level COLLATE clause. This ensures that we treat |
| 15118 | * "x COLLATE y" and "(x COLLATE y)" alike. |
| 15119 | */ |
| 15120 | while (IsA(expr, CollateExpr)) |
| 15121 | expr = (Node *) ((CollateExpr *) expr)->arg; |
| 15122 | |
| 15123 | if (IsA(expr, Var) && |
| 15124 | ((Var *) expr)->varattno > 0) |
| 15125 | { |
| 15126 | /* |
| 15127 | * User wrote "(column)" or "(column COLLATE something)". |
| 15128 | * Treat it like simple attribute anyway. |
| 15129 | */ |
| 15130 | partattrs[attn] = ((Var *) expr)->varattno; |
| 15131 | } |
| 15132 | else |
| 15133 | { |
| 15134 | Bitmapset *expr_attrs = NULL; |
| 15135 | int i; |
| 15136 | |
| 15137 | partattrs[attn] = 0; /* marks the column as expression */ |
| 15138 | *partexprs = lappend(*partexprs, expr); |
| 15139 | |
| 15140 | /* |
| 15141 | * Try to simplify the expression before checking for |
| 15142 | * mutability. The main practical value of doing it in this |
| 15143 | * order is that an inline-able SQL-language function will be |
| 15144 | * accepted if its expansion is immutable, whether or not the |
| 15145 | * function itself is marked immutable. |
| 15146 | * |
| 15147 | * Note that expression_planner does not change the passed in |
| 15148 | * expression destructively and we have already saved the |
| 15149 | * expression to be stored into the catalog above. |
| 15150 | */ |
| 15151 | expr = (Node *) expression_planner((Expr *) expr); |
| 15152 | |
| 15153 | /* |
| 15154 | * Partition expression cannot contain mutable functions, |
| 15155 | * because a given row must always map to the same partition |
| 15156 | * as long as there is no change in the partition boundary |
| 15157 | * structure. |
| 15158 | */ |
| 15159 | if (contain_mutable_functions(expr)) |
| 15160 | ereport(ERROR, |
| 15161 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| 15162 | errmsg("functions in partition key expression must be marked IMMUTABLE" ))); |
| 15163 | |
| 15164 | /* |
| 15165 | * transformPartitionSpec() should have already rejected |
| 15166 | * subqueries, aggregates, window functions, and SRFs, based |
| 15167 | * on the EXPR_KIND_ for partition expressions. |
| 15168 | */ |
| 15169 | |
| 15170 | /* |
| 15171 | * Cannot have expressions containing whole-row references or |
| 15172 | * system column references. |
| 15173 | */ |
| 15174 | pull_varattnos(expr, 1, &expr_attrs); |
| 15175 | if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber, |
| 15176 | expr_attrs)) |
| 15177 | ereport(ERROR, |
| 15178 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| 15179 | errmsg("partition key expressions cannot contain whole-row references" ))); |
| 15180 | for (i = FirstLowInvalidHeapAttributeNumber; i < 0; i++) |
| 15181 | { |
| 15182 | if (bms_is_member(i - FirstLowInvalidHeapAttributeNumber, |
| 15183 | expr_attrs)) |
| 15184 | ereport(ERROR, |
| 15185 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| 15186 | errmsg("partition key expressions cannot contain system column references" ))); |
| 15187 | } |
| 15188 | |
| 15189 | /* |
| 15190 | * Generated columns cannot work: They are computed after |
| 15191 | * BEFORE triggers, but partition routing is done before all |
| 15192 | * triggers. |
| 15193 | */ |
| 15194 | i = -1; |
| 15195 | while ((i = bms_next_member(expr_attrs, i)) >= 0) |
| 15196 | { |
| 15197 | AttrNumber attno = i + FirstLowInvalidHeapAttributeNumber; |
| 15198 | |
| 15199 | if (TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated) |
| 15200 | ereport(ERROR, |
| 15201 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| 15202 | errmsg("cannot use generated column in partition key" ), |
| 15203 | errdetail("Column \"%s\" is a generated column." , |
| 15204 | get_attname(RelationGetRelid(rel), attno, false)), |
| 15205 | parser_errposition(pstate, pelem->location))); |
| 15206 | } |
| 15207 | |
| 15208 | /* |
| 15209 | * While it is not exactly *wrong* for a partition expression |
| 15210 | * to be a constant, it seems better to reject such keys. |
| 15211 | */ |
| 15212 | if (IsA(expr, Const)) |
| 15213 | ereport(ERROR, |
| 15214 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| 15215 | errmsg("cannot use constant expression as partition key" ))); |
| 15216 | } |
| 15217 | } |
| 15218 | |
| 15219 | /* |
| 15220 | * Apply collation override if any |
| 15221 | */ |
| 15222 | if (pelem->collation) |
| 15223 | attcollation = get_collation_oid(pelem->collation, false); |
| 15224 | |
| 15225 | /* |
| 15226 | * Check we have a collation iff it's a collatable type. The only |
| 15227 | * expected failures here are (1) COLLATE applied to a noncollatable |
| 15228 | * type, or (2) partition expression had an unresolved collation. But |
| 15229 | * we might as well code this to be a complete consistency check. |
| 15230 | */ |
| 15231 | if (type_is_collatable(atttype)) |
| 15232 | { |
| 15233 | if (!OidIsValid(attcollation)) |
| 15234 | ereport(ERROR, |
| 15235 | (errcode(ERRCODE_INDETERMINATE_COLLATION), |
| 15236 | errmsg("could not determine which collation to use for partition expression" ), |
| 15237 | errhint("Use the COLLATE clause to set the collation explicitly." ))); |
| 15238 | } |
| 15239 | else |
| 15240 | { |
| 15241 | if (OidIsValid(attcollation)) |
| 15242 | ereport(ERROR, |
| 15243 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
| 15244 | errmsg("collations are not supported by type %s" , |
| 15245 | format_type_be(atttype)))); |
| 15246 | } |
| 15247 | |
| 15248 | partcollation[attn] = attcollation; |
| 15249 | |
| 15250 | /* |
| 15251 | * Identify the appropriate operator class. For list and range |
| 15252 | * partitioning, we use a btree operator class; hash partitioning uses |
| 15253 | * a hash operator class. |
| 15254 | */ |
| 15255 | if (strategy == PARTITION_STRATEGY_HASH) |
| 15256 | am_oid = HASH_AM_OID; |
| 15257 | else |
| 15258 | am_oid = BTREE_AM_OID; |
| 15259 | |
| 15260 | if (!pelem->opclass) |
| 15261 | { |
| 15262 | partopclass[attn] = GetDefaultOpClass(atttype, am_oid); |
| 15263 | |
| 15264 | if (!OidIsValid(partopclass[attn])) |
| 15265 | { |
| 15266 | if (strategy == PARTITION_STRATEGY_HASH) |
| 15267 | ereport(ERROR, |
| 15268 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
| 15269 | errmsg("data type %s has no default operator class for access method \"%s\"" , |
| 15270 | format_type_be(atttype), "hash" ), |
| 15271 | errhint("You must specify a hash operator class or define a default hash operator class for the data type." ))); |
| 15272 | else |
| 15273 | ereport(ERROR, |
| 15274 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
| 15275 | errmsg("data type %s has no default operator class for access method \"%s\"" , |
| 15276 | format_type_be(atttype), "btree" ), |
| 15277 | errhint("You must specify a btree operator class or define a default btree operator class for the data type." ))); |
| 15278 | |
| 15279 | } |
| 15280 | } |
| 15281 | else |
| 15282 | partopclass[attn] = ResolveOpClass(pelem->opclass, |
| 15283 | atttype, |
| 15284 | am_oid == HASH_AM_OID ? "hash" : "btree" , |
| 15285 | am_oid); |
| 15286 | |
| 15287 | attn++; |
| 15288 | } |
| 15289 | } |
| 15290 | |
| 15291 | /* |
| 15292 | * PartConstraintImpliedByRelConstraint |
| 15293 | * Do scanrel's existing constraints imply the partition constraint? |
| 15294 | * |
| 15295 | * "Existing constraints" include its check constraints and column-level |
| 15296 | * NOT NULL constraints. partConstraint describes the partition constraint, |
| 15297 | * in implicit-AND form. |
| 15298 | */ |
| 15299 | bool |
| 15300 | PartConstraintImpliedByRelConstraint(Relation scanrel, |
| 15301 | List *partConstraint) |
| 15302 | { |
| 15303 | List *existConstraint = NIL; |
| 15304 | TupleConstr *constr = RelationGetDescr(scanrel)->constr; |
| 15305 | int i; |
| 15306 | |
| 15307 | if (constr && constr->has_not_null) |
| 15308 | { |
| 15309 | int natts = scanrel->rd_att->natts; |
| 15310 | |
| 15311 | for (i = 1; i <= natts; i++) |
| 15312 | { |
| 15313 | Form_pg_attribute att = TupleDescAttr(scanrel->rd_att, i - 1); |
| 15314 | |
| 15315 | if (att->attnotnull && !att->attisdropped) |
| 15316 | { |
| 15317 | NullTest *ntest = makeNode(NullTest); |
| 15318 | |
| 15319 | ntest->arg = (Expr *) makeVar(1, |
| 15320 | i, |
| 15321 | att->atttypid, |
| 15322 | att->atttypmod, |
| 15323 | att->attcollation, |
| 15324 | 0); |
| 15325 | ntest->nulltesttype = IS_NOT_NULL; |
| 15326 | |
| 15327 | /* |
| 15328 | * argisrow=false is correct even for a composite column, |
| 15329 | * because attnotnull does not represent a SQL-spec IS NOT |
| 15330 | * NULL test in such a case, just IS DISTINCT FROM NULL. |
| 15331 | */ |
| 15332 | ntest->argisrow = false; |
| 15333 | ntest->location = -1; |
| 15334 | existConstraint = lappend(existConstraint, ntest); |
| 15335 | } |
| 15336 | } |
| 15337 | } |
| 15338 | |
| 15339 | return ConstraintImpliedByRelConstraint(scanrel, partConstraint, existConstraint); |
| 15340 | } |
| 15341 | |
| 15342 | /* |
| 15343 | * ConstraintImpliedByRelConstraint |
| 15344 | * Do scanrel's existing constraints imply the given constraint? |
| 15345 | * |
| 15346 | * testConstraint is the constraint to validate. provenConstraint is a |
| 15347 | * caller-provided list of conditions which this function may assume |
| 15348 | * to be true. Both provenConstraint and testConstraint must be in |
| 15349 | * implicit-AND form, must only contain immutable clauses, and must |
| 15350 | * contain only Vars with varno = 1. |
| 15351 | */ |
| 15352 | bool |
| 15353 | ConstraintImpliedByRelConstraint(Relation scanrel, List *testConstraint, List *provenConstraint) |
| 15354 | { |
| 15355 | List *existConstraint = list_copy(provenConstraint); |
| 15356 | TupleConstr *constr = RelationGetDescr(scanrel)->constr; |
| 15357 | int num_check, |
| 15358 | i; |
| 15359 | |
| 15360 | num_check = (constr != NULL) ? constr->num_check : 0; |
| 15361 | for (i = 0; i < num_check; i++) |
| 15362 | { |
| 15363 | Node *cexpr; |
| 15364 | |
| 15365 | /* |
| 15366 | * If this constraint hasn't been fully validated yet, we must ignore |
| 15367 | * it here. |
| 15368 | */ |
| 15369 | if (!constr->check[i].ccvalid) |
| 15370 | continue; |
| 15371 | |
| 15372 | cexpr = stringToNode(constr->check[i].ccbin); |
| 15373 | |
| 15374 | /* |
| 15375 | * Run each expression through const-simplification and |
| 15376 | * canonicalization. It is necessary, because we will be comparing it |
| 15377 | * to similarly-processed partition constraint expressions, and may |
| 15378 | * fail to detect valid matches without this. |
| 15379 | */ |
| 15380 | cexpr = eval_const_expressions(NULL, cexpr); |
| 15381 | cexpr = (Node *) canonicalize_qual((Expr *) cexpr, true); |
| 15382 | |
| 15383 | existConstraint = list_concat(existConstraint, |
| 15384 | make_ands_implicit((Expr *) cexpr)); |
| 15385 | } |
| 15386 | |
| 15387 | /* |
| 15388 | * Try to make the proof. Since we are comparing CHECK constraints, we |
| 15389 | * need to use weak implication, i.e., we assume existConstraint is |
| 15390 | * not-false and try to prove the same for testConstraint. |
| 15391 | * |
| 15392 | * Note that predicate_implied_by assumes its first argument is known |
| 15393 | * immutable. That should always be true for both NOT NULL and partition |
| 15394 | * constraints, so we don't test it here. |
| 15395 | */ |
| 15396 | return predicate_implied_by(testConstraint, existConstraint, true); |
| 15397 | } |
| 15398 | |
| 15399 | /* |
| 15400 | * QueuePartitionConstraintValidation |
| 15401 | * |
| 15402 | * Add an entry to wqueue to have the given partition constraint validated by |
| 15403 | * Phase 3, for the given relation, and all its children. |
| 15404 | * |
| 15405 | * We first verify whether the given constraint is implied by pre-existing |
| 15406 | * relation constraints; if it is, there's no need to scan the table to |
| 15407 | * validate, so don't queue in that case. |
| 15408 | */ |
| 15409 | static void |
| 15410 | QueuePartitionConstraintValidation(List **wqueue, Relation scanrel, |
| 15411 | List *partConstraint, |
| 15412 | bool validate_default) |
| 15413 | { |
| 15414 | /* |
| 15415 | * Based on the table's existing constraints, determine whether or not we |
| 15416 | * may skip scanning the table. |
| 15417 | */ |
| 15418 | if (PartConstraintImpliedByRelConstraint(scanrel, partConstraint)) |
| 15419 | { |
| 15420 | if (!validate_default) |
| 15421 | ereport(DEBUG1, |
| 15422 | (errmsg("partition constraint for table \"%s\" is implied by existing constraints" , |
| 15423 | RelationGetRelationName(scanrel)))); |
| 15424 | else |
| 15425 | ereport(DEBUG1, |
| 15426 | (errmsg("updated partition constraint for default partition \"%s\" is implied by existing constraints" , |
| 15427 | RelationGetRelationName(scanrel)))); |
| 15428 | return; |
| 15429 | } |
| 15430 | |
| 15431 | /* |
| 15432 | * Constraints proved insufficient. For plain relations, queue a |
| 15433 | * validation item now; for partitioned tables, recurse to process each |
| 15434 | * partition. |
| 15435 | */ |
| 15436 | if (scanrel->rd_rel->relkind == RELKIND_RELATION) |
| 15437 | { |
| 15438 | AlteredTableInfo *tab; |
| 15439 | |
| 15440 | /* Grab a work queue entry. */ |
| 15441 | tab = ATGetQueueEntry(wqueue, scanrel); |
| 15442 | Assert(tab->partition_constraint == NULL); |
| 15443 | tab->partition_constraint = (Expr *) linitial(partConstraint); |
| 15444 | tab->validate_default = validate_default; |
| 15445 | } |
| 15446 | else if (scanrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) |
| 15447 | { |
| 15448 | PartitionDesc partdesc = RelationGetPartitionDesc(scanrel); |
| 15449 | int i; |
| 15450 | |
| 15451 | for (i = 0; i < partdesc->nparts; i++) |
| 15452 | { |
| 15453 | Relation part_rel; |
| 15454 | bool found_whole_row; |
| 15455 | List *thisPartConstraint; |
| 15456 | |
| 15457 | /* |
| 15458 | * This is the minimum lock we need to prevent deadlocks. |
| 15459 | */ |
| 15460 | part_rel = table_open(partdesc->oids[i], AccessExclusiveLock); |
| 15461 | |
| 15462 | /* |
| 15463 | * Adjust the constraint for scanrel so that it matches this |
| 15464 | * partition's attribute numbers. |
| 15465 | */ |
| 15466 | thisPartConstraint = |
| 15467 | map_partition_varattnos(partConstraint, 1, |
| 15468 | part_rel, scanrel, &found_whole_row); |
| 15469 | /* There can never be a whole-row reference here */ |
| 15470 | if (found_whole_row) |
| 15471 | elog(ERROR, "unexpected whole-row reference found in partition constraint" ); |
| 15472 | |
| 15473 | QueuePartitionConstraintValidation(wqueue, part_rel, |
| 15474 | thisPartConstraint, |
| 15475 | validate_default); |
| 15476 | table_close(part_rel, NoLock); /* keep lock till commit */ |
| 15477 | } |
| 15478 | } |
| 15479 | } |
| 15480 | |
| 15481 | /* |
| 15482 | * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES |
| 15483 | * |
| 15484 | * Return the address of the newly attached partition. |
| 15485 | */ |
| 15486 | static ObjectAddress |
| 15487 | ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd) |
| 15488 | { |
| 15489 | Relation attachrel, |
| 15490 | catalog; |
| 15491 | List *attachrel_children; |
| 15492 | List *partConstraint; |
| 15493 | SysScanDesc scan; |
| 15494 | ScanKeyData skey; |
| 15495 | AttrNumber attno; |
| 15496 | int natts; |
| 15497 | TupleDesc tupleDesc; |
| 15498 | ObjectAddress address; |
| 15499 | const char *trigger_name; |
| 15500 | bool found_whole_row; |
| 15501 | Oid defaultPartOid; |
| 15502 | List *partBoundConstraint; |
| 15503 | |
| 15504 | /* |
| 15505 | * We must lock the default partition if one exists, because attaching a |
| 15506 | * new partition will change its partition constraint. |
| 15507 | */ |
| 15508 | defaultPartOid = |
| 15509 | get_default_oid_from_partdesc(RelationGetPartitionDesc(rel)); |
| 15510 | if (OidIsValid(defaultPartOid)) |
| 15511 | LockRelationOid(defaultPartOid, AccessExclusiveLock); |
| 15512 | |
| 15513 | attachrel = table_openrv(cmd->name, AccessExclusiveLock); |
| 15514 | |
| 15515 | /* |
| 15516 | * XXX I think it'd be a good idea to grab locks on all tables referenced |
| 15517 | * by FKs at this point also. |
| 15518 | */ |
| 15519 | |
| 15520 | /* |
| 15521 | * Must be owner of both parent and source table -- parent was checked by |
| 15522 | * ATSimplePermissions call in ATPrepCmd |
| 15523 | */ |
| 15524 | ATSimplePermissions(attachrel, ATT_TABLE | ATT_FOREIGN_TABLE); |
| 15525 | |
| 15526 | /* A partition can only have one parent */ |
| 15527 | if (attachrel->rd_rel->relispartition) |
| 15528 | ereport(ERROR, |
| 15529 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 15530 | errmsg("\"%s\" is already a partition" , |
| 15531 | RelationGetRelationName(attachrel)))); |
| 15532 | |
| 15533 | if (OidIsValid(attachrel->rd_rel->reloftype)) |
| 15534 | ereport(ERROR, |
| 15535 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 15536 | errmsg("cannot attach a typed table as partition" ))); |
| 15537 | |
| 15538 | /* |
| 15539 | * Table being attached should not already be part of inheritance; either |
| 15540 | * as a child table... |
| 15541 | */ |
| 15542 | catalog = table_open(InheritsRelationId, AccessShareLock); |
| 15543 | ScanKeyInit(&skey, |
| 15544 | Anum_pg_inherits_inhrelid, |
| 15545 | BTEqualStrategyNumber, F_OIDEQ, |
| 15546 | ObjectIdGetDatum(RelationGetRelid(attachrel))); |
| 15547 | scan = systable_beginscan(catalog, InheritsRelidSeqnoIndexId, true, |
| 15548 | NULL, 1, &skey); |
| 15549 | if (HeapTupleIsValid(systable_getnext(scan))) |
| 15550 | ereport(ERROR, |
| 15551 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 15552 | errmsg("cannot attach inheritance child as partition" ))); |
| 15553 | systable_endscan(scan); |
| 15554 | |
| 15555 | /* ...or as a parent table (except the case when it is partitioned) */ |
| 15556 | ScanKeyInit(&skey, |
| 15557 | Anum_pg_inherits_inhparent, |
| 15558 | BTEqualStrategyNumber, F_OIDEQ, |
| 15559 | ObjectIdGetDatum(RelationGetRelid(attachrel))); |
| 15560 | scan = systable_beginscan(catalog, InheritsParentIndexId, true, NULL, |
| 15561 | 1, &skey); |
| 15562 | if (HeapTupleIsValid(systable_getnext(scan)) && |
| 15563 | attachrel->rd_rel->relkind == RELKIND_RELATION) |
| 15564 | ereport(ERROR, |
| 15565 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 15566 | errmsg("cannot attach inheritance parent as partition" ))); |
| 15567 | systable_endscan(scan); |
| 15568 | table_close(catalog, AccessShareLock); |
| 15569 | |
| 15570 | /* |
| 15571 | * Prevent circularity by seeing if rel is a partition of attachrel. (In |
| 15572 | * particular, this disallows making a rel a partition of itself.) |
| 15573 | * |
| 15574 | * We do that by checking if rel is a member of the list of attachrel's |
| 15575 | * partitions provided the latter is partitioned at all. We want to avoid |
| 15576 | * having to construct this list again, so we request the strongest lock |
| 15577 | * on all partitions. We need the strongest lock, because we may decide |
| 15578 | * to scan them if we find out that the table being attached (or its leaf |
| 15579 | * partitions) may contain rows that violate the partition constraint. If |
| 15580 | * the table has a constraint that would prevent such rows, which by |
| 15581 | * definition is present in all the partitions, we need not scan the |
| 15582 | * table, nor its partitions. But we cannot risk a deadlock by taking a |
| 15583 | * weaker lock now and the stronger one only when needed. |
| 15584 | */ |
| 15585 | attachrel_children = find_all_inheritors(RelationGetRelid(attachrel), |
| 15586 | AccessExclusiveLock, NULL); |
| 15587 | if (list_member_oid(attachrel_children, RelationGetRelid(rel))) |
| 15588 | ereport(ERROR, |
| 15589 | (errcode(ERRCODE_DUPLICATE_TABLE), |
| 15590 | errmsg("circular inheritance not allowed" ), |
| 15591 | errdetail("\"%s\" is already a child of \"%s\"." , |
| 15592 | RelationGetRelationName(rel), |
| 15593 | RelationGetRelationName(attachrel)))); |
| 15594 | |
| 15595 | /* If the parent is permanent, so must be all of its partitions. */ |
| 15596 | if (rel->rd_rel->relpersistence != RELPERSISTENCE_TEMP && |
| 15597 | attachrel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) |
| 15598 | ereport(ERROR, |
| 15599 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 15600 | errmsg("cannot attach a temporary relation as partition of permanent relation \"%s\"" , |
| 15601 | RelationGetRelationName(rel)))); |
| 15602 | |
| 15603 | /* Temp parent cannot have a partition that is itself not a temp */ |
| 15604 | if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP && |
| 15605 | attachrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP) |
| 15606 | ereport(ERROR, |
| 15607 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 15608 | errmsg("cannot attach a permanent relation as partition of temporary relation \"%s\"" , |
| 15609 | RelationGetRelationName(rel)))); |
| 15610 | |
| 15611 | /* If the parent is temp, it must belong to this session */ |
| 15612 | if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP && |
| 15613 | !rel->rd_islocaltemp) |
| 15614 | ereport(ERROR, |
| 15615 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 15616 | errmsg("cannot attach as partition of temporary relation of another session" ))); |
| 15617 | |
| 15618 | /* Ditto for the partition */ |
| 15619 | if (attachrel->rd_rel->relpersistence == RELPERSISTENCE_TEMP && |
| 15620 | !attachrel->rd_islocaltemp) |
| 15621 | ereport(ERROR, |
| 15622 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 15623 | errmsg("cannot attach temporary relation of another session as partition" ))); |
| 15624 | |
| 15625 | /* Check if there are any columns in attachrel that aren't in the parent */ |
| 15626 | tupleDesc = RelationGetDescr(attachrel); |
| 15627 | natts = tupleDesc->natts; |
| 15628 | for (attno = 1; attno <= natts; attno++) |
| 15629 | { |
| 15630 | Form_pg_attribute attribute = TupleDescAttr(tupleDesc, attno - 1); |
| 15631 | char *attributeName = NameStr(attribute->attname); |
| 15632 | |
| 15633 | /* Ignore dropped */ |
| 15634 | if (attribute->attisdropped) |
| 15635 | continue; |
| 15636 | |
| 15637 | /* Try to find the column in parent (matching on column name) */ |
| 15638 | if (!SearchSysCacheExists2(ATTNAME, |
| 15639 | ObjectIdGetDatum(RelationGetRelid(rel)), |
| 15640 | CStringGetDatum(attributeName))) |
| 15641 | ereport(ERROR, |
| 15642 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
| 15643 | errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"" , |
| 15644 | RelationGetRelationName(attachrel), attributeName, |
| 15645 | RelationGetRelationName(rel)), |
| 15646 | errdetail("The new partition may contain only the columns present in parent." ))); |
| 15647 | } |
| 15648 | |
| 15649 | /* |
| 15650 | * If child_rel has row-level triggers with transition tables, we |
| 15651 | * currently don't allow it to become a partition. See also prohibitions |
| 15652 | * in ATExecAddInherit() and CreateTrigger(). |
| 15653 | */ |
| 15654 | trigger_name = FindTriggerIncompatibleWithInheritance(attachrel->trigdesc); |
| 15655 | if (trigger_name != NULL) |
| 15656 | ereport(ERROR, |
| 15657 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 15658 | errmsg("trigger \"%s\" prevents table \"%s\" from becoming a partition" , |
| 15659 | trigger_name, RelationGetRelationName(attachrel)), |
| 15660 | errdetail("ROW triggers with transition tables are not supported on partitions" ))); |
| 15661 | |
| 15662 | /* |
| 15663 | * Check that the new partition's bound is valid and does not overlap any |
| 15664 | * of existing partitions of the parent - note that it does not return on |
| 15665 | * error. |
| 15666 | */ |
| 15667 | check_new_partition_bound(RelationGetRelationName(attachrel), rel, |
| 15668 | cmd->bound); |
| 15669 | |
| 15670 | /* OK to create inheritance. Rest of the checks performed there */ |
| 15671 | CreateInheritance(attachrel, rel); |
| 15672 | |
| 15673 | /* Update the pg_class entry. */ |
| 15674 | StorePartitionBound(attachrel, rel, cmd->bound); |
| 15675 | |
| 15676 | /* Ensure there exists a correct set of indexes in the partition. */ |
| 15677 | AttachPartitionEnsureIndexes(rel, attachrel); |
| 15678 | |
| 15679 | /* and triggers */ |
| 15680 | CloneRowTriggersToPartition(rel, attachrel); |
| 15681 | |
| 15682 | /* |
| 15683 | * Clone foreign key constraints. Callee is responsible for setting up |
| 15684 | * for phase 3 constraint verification. |
| 15685 | */ |
| 15686 | CloneForeignKeyConstraints(wqueue, rel, attachrel); |
| 15687 | |
| 15688 | /* |
| 15689 | * Generate partition constraint from the partition bound specification. |
| 15690 | * If the parent itself is a partition, make sure to include its |
| 15691 | * constraint as well. |
| 15692 | */ |
| 15693 | partBoundConstraint = get_qual_from_partbound(attachrel, rel, cmd->bound); |
| 15694 | partConstraint = list_concat(partBoundConstraint, |
| 15695 | RelationGetPartitionQual(rel)); |
| 15696 | |
| 15697 | /* Skip validation if there are no constraints to validate. */ |
| 15698 | if (partConstraint) |
| 15699 | { |
| 15700 | /* |
| 15701 | * Run the partition quals through const-simplification similar to |
| 15702 | * check constraints. We skip canonicalize_qual, though, because |
| 15703 | * partition quals should be in canonical form already. |
| 15704 | */ |
| 15705 | partConstraint = |
| 15706 | (List *) eval_const_expressions(NULL, |
| 15707 | (Node *) partConstraint); |
| 15708 | |
| 15709 | /* XXX this sure looks wrong */ |
| 15710 | partConstraint = list_make1(make_ands_explicit(partConstraint)); |
| 15711 | |
| 15712 | /* |
| 15713 | * Adjust the generated constraint to match this partition's attribute |
| 15714 | * numbers. |
| 15715 | */ |
| 15716 | partConstraint = map_partition_varattnos(partConstraint, 1, attachrel, |
| 15717 | rel, &found_whole_row); |
| 15718 | /* There can never be a whole-row reference here */ |
| 15719 | if (found_whole_row) |
| 15720 | elog(ERROR, |
| 15721 | "unexpected whole-row reference found in partition key" ); |
| 15722 | |
| 15723 | /* Validate partition constraints against the table being attached. */ |
| 15724 | QueuePartitionConstraintValidation(wqueue, attachrel, partConstraint, |
| 15725 | false); |
| 15726 | } |
| 15727 | |
| 15728 | /* |
| 15729 | * If we're attaching a partition other than the default partition and a |
| 15730 | * default one exists, then that partition's partition constraint changes, |
| 15731 | * so add an entry to the work queue to validate it, too. (We must not do |
| 15732 | * this when the partition being attached is the default one; we already |
| 15733 | * did it above!) |
| 15734 | */ |
| 15735 | if (OidIsValid(defaultPartOid)) |
| 15736 | { |
| 15737 | Relation defaultrel; |
| 15738 | List *defPartConstraint; |
| 15739 | |
| 15740 | Assert(!cmd->bound->is_default); |
| 15741 | |
| 15742 | /* we already hold a lock on the default partition */ |
| 15743 | defaultrel = table_open(defaultPartOid, NoLock); |
| 15744 | defPartConstraint = |
| 15745 | get_proposed_default_constraint(partBoundConstraint); |
| 15746 | |
| 15747 | /* |
| 15748 | * Map the Vars in the constraint expression from rel's attnos to |
| 15749 | * defaultrel's. |
| 15750 | */ |
| 15751 | defPartConstraint = |
| 15752 | map_partition_varattnos(defPartConstraint, |
| 15753 | 1, defaultrel, rel, NULL); |
| 15754 | QueuePartitionConstraintValidation(wqueue, defaultrel, |
| 15755 | defPartConstraint, true); |
| 15756 | |
| 15757 | /* keep our lock until commit. */ |
| 15758 | table_close(defaultrel, NoLock); |
| 15759 | } |
| 15760 | |
| 15761 | ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel)); |
| 15762 | |
| 15763 | /* keep our lock until commit */ |
| 15764 | table_close(attachrel, NoLock); |
| 15765 | |
| 15766 | return address; |
| 15767 | } |
| 15768 | |
| 15769 | /* |
| 15770 | * AttachPartitionEnsureIndexes |
| 15771 | * subroutine for ATExecAttachPartition to create/match indexes |
| 15772 | * |
| 15773 | * Enforce the indexing rule for partitioned tables during ALTER TABLE / ATTACH |
| 15774 | * PARTITION: every partition must have an index attached to each index on the |
| 15775 | * partitioned table. |
| 15776 | */ |
| 15777 | static void |
| 15778 | AttachPartitionEnsureIndexes(Relation rel, Relation attachrel) |
| 15779 | { |
| 15780 | List *idxes; |
| 15781 | List *attachRelIdxs; |
| 15782 | Relation *attachrelIdxRels; |
| 15783 | IndexInfo **attachInfos; |
| 15784 | int i; |
| 15785 | ListCell *cell; |
| 15786 | MemoryContext cxt; |
| 15787 | MemoryContext oldcxt; |
| 15788 | |
| 15789 | cxt = AllocSetContextCreate(CurrentMemoryContext, |
| 15790 | "AttachPartitionEnsureIndexes" , |
| 15791 | ALLOCSET_DEFAULT_SIZES); |
| 15792 | oldcxt = MemoryContextSwitchTo(cxt); |
| 15793 | |
| 15794 | idxes = RelationGetIndexList(rel); |
| 15795 | attachRelIdxs = RelationGetIndexList(attachrel); |
| 15796 | attachrelIdxRels = palloc(sizeof(Relation) * list_length(attachRelIdxs)); |
| 15797 | attachInfos = palloc(sizeof(IndexInfo *) * list_length(attachRelIdxs)); |
| 15798 | |
| 15799 | /* Build arrays of all existing indexes and their IndexInfos */ |
| 15800 | i = 0; |
| 15801 | foreach(cell, attachRelIdxs) |
| 15802 | { |
| 15803 | Oid cldIdxId = lfirst_oid(cell); |
| 15804 | |
| 15805 | attachrelIdxRels[i] = index_open(cldIdxId, AccessShareLock); |
| 15806 | attachInfos[i] = BuildIndexInfo(attachrelIdxRels[i]); |
| 15807 | i++; |
| 15808 | } |
| 15809 | |
| 15810 | /* |
| 15811 | * If we're attaching a foreign table, we must fail if any of the indexes |
| 15812 | * is a constraint index; otherwise, there's nothing to do here. Do this |
| 15813 | * before starting work, to avoid wasting the effort of building a few |
| 15814 | * non-unique indexes before coming across a unique one. |
| 15815 | */ |
| 15816 | if (attachrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) |
| 15817 | { |
| 15818 | foreach(cell, idxes) |
| 15819 | { |
| 15820 | Oid idx = lfirst_oid(cell); |
| 15821 | Relation idxRel = index_open(idx, AccessShareLock); |
| 15822 | |
| 15823 | if (idxRel->rd_index->indisunique || |
| 15824 | idxRel->rd_index->indisprimary) |
| 15825 | ereport(ERROR, |
| 15826 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 15827 | errmsg("cannot attach foreign table \"%s\" as partition of partitioned table \"%s\"" , |
| 15828 | RelationGetRelationName(attachrel), |
| 15829 | RelationGetRelationName(rel)), |
| 15830 | errdetail("Table \"%s\" contains unique indexes." , |
| 15831 | RelationGetRelationName(rel)))); |
| 15832 | index_close(idxRel, AccessShareLock); |
| 15833 | } |
| 15834 | |
| 15835 | goto out; |
| 15836 | } |
| 15837 | |
| 15838 | /* |
| 15839 | * For each index on the partitioned table, find a matching one in the |
| 15840 | * partition-to-be; if one is not found, create one. |
| 15841 | */ |
| 15842 | foreach(cell, idxes) |
| 15843 | { |
| 15844 | Oid idx = lfirst_oid(cell); |
| 15845 | Relation idxRel = index_open(idx, AccessShareLock); |
| 15846 | IndexInfo *info; |
| 15847 | AttrNumber *attmap; |
| 15848 | bool found = false; |
| 15849 | Oid constraintOid; |
| 15850 | |
| 15851 | /* |
| 15852 | * Ignore indexes in the partitioned table other than partitioned |
| 15853 | * indexes. |
| 15854 | */ |
| 15855 | if (idxRel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX) |
| 15856 | { |
| 15857 | index_close(idxRel, AccessShareLock); |
| 15858 | continue; |
| 15859 | } |
| 15860 | |
| 15861 | /* construct an indexinfo to compare existing indexes against */ |
| 15862 | info = BuildIndexInfo(idxRel); |
| 15863 | attmap = convert_tuples_by_name_map(RelationGetDescr(attachrel), |
| 15864 | RelationGetDescr(rel), |
| 15865 | gettext_noop("could not convert row type" )); |
| 15866 | constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx); |
| 15867 | |
| 15868 | /* |
| 15869 | * Scan the list of existing indexes in the partition-to-be, and mark |
| 15870 | * the first matching, unattached one we find, if any, as partition of |
| 15871 | * the parent index. If we find one, we're done. |
| 15872 | */ |
| 15873 | for (i = 0; i < list_length(attachRelIdxs); i++) |
| 15874 | { |
| 15875 | Oid cldIdxId = RelationGetRelid(attachrelIdxRels[i]); |
| 15876 | Oid cldConstrOid = InvalidOid; |
| 15877 | |
| 15878 | /* does this index have a parent? if so, can't use it */ |
| 15879 | if (attachrelIdxRels[i]->rd_rel->relispartition) |
| 15880 | continue; |
| 15881 | |
| 15882 | if (CompareIndexInfo(attachInfos[i], info, |
| 15883 | attachrelIdxRels[i]->rd_indcollation, |
| 15884 | idxRel->rd_indcollation, |
| 15885 | attachrelIdxRels[i]->rd_opfamily, |
| 15886 | idxRel->rd_opfamily, |
| 15887 | attmap, |
| 15888 | RelationGetDescr(rel)->natts)) |
| 15889 | { |
| 15890 | /* |
| 15891 | * If this index is being created in the parent because of a |
| 15892 | * constraint, then the child needs to have a constraint also, |
| 15893 | * so look for one. If there is no such constraint, this |
| 15894 | * index is no good, so keep looking. |
| 15895 | */ |
| 15896 | if (OidIsValid(constraintOid)) |
| 15897 | { |
| 15898 | cldConstrOid = |
| 15899 | get_relation_idx_constraint_oid(RelationGetRelid(attachrel), |
| 15900 | cldIdxId); |
| 15901 | /* no dice */ |
| 15902 | if (!OidIsValid(cldConstrOid)) |
| 15903 | continue; |
| 15904 | } |
| 15905 | |
| 15906 | /* bingo. */ |
| 15907 | IndexSetParentIndex(attachrelIdxRels[i], idx); |
| 15908 | if (OidIsValid(constraintOid)) |
| 15909 | ConstraintSetParentConstraint(cldConstrOid, constraintOid, |
| 15910 | RelationGetRelid(attachrel)); |
| 15911 | found = true; |
| 15912 | |
| 15913 | CommandCounterIncrement(); |
| 15914 | break; |
| 15915 | } |
| 15916 | } |
| 15917 | |
| 15918 | /* |
| 15919 | * If no suitable index was found in the partition-to-be, create one |
| 15920 | * now. |
| 15921 | */ |
| 15922 | if (!found) |
| 15923 | { |
| 15924 | IndexStmt *stmt; |
| 15925 | Oid constraintOid; |
| 15926 | |
| 15927 | stmt = generateClonedIndexStmt(NULL, |
| 15928 | idxRel, attmap, |
| 15929 | RelationGetDescr(rel)->natts, |
| 15930 | &constraintOid); |
| 15931 | DefineIndex(RelationGetRelid(attachrel), stmt, InvalidOid, |
| 15932 | RelationGetRelid(idxRel), |
| 15933 | constraintOid, |
| 15934 | true, false, false, false, false); |
| 15935 | } |
| 15936 | |
| 15937 | index_close(idxRel, AccessShareLock); |
| 15938 | } |
| 15939 | |
| 15940 | out: |
| 15941 | /* Clean up. */ |
| 15942 | for (i = 0; i < list_length(attachRelIdxs); i++) |
| 15943 | index_close(attachrelIdxRels[i], AccessShareLock); |
| 15944 | MemoryContextSwitchTo(oldcxt); |
| 15945 | MemoryContextDelete(cxt); |
| 15946 | } |
| 15947 | |
| 15948 | /* |
| 15949 | * CloneRowTriggersToPartition |
| 15950 | * subroutine for ATExecAttachPartition/DefineRelation to create row |
| 15951 | * triggers on partitions |
| 15952 | */ |
| 15953 | static void |
| 15954 | CloneRowTriggersToPartition(Relation parent, Relation partition) |
| 15955 | { |
| 15956 | Relation pg_trigger; |
| 15957 | ScanKeyData key; |
| 15958 | SysScanDesc scan; |
| 15959 | HeapTuple tuple; |
| 15960 | MemoryContext perTupCxt; |
| 15961 | |
| 15962 | ScanKeyInit(&key, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber, |
| 15963 | F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(parent))); |
| 15964 | pg_trigger = table_open(TriggerRelationId, RowExclusiveLock); |
| 15965 | scan = systable_beginscan(pg_trigger, TriggerRelidNameIndexId, |
| 15966 | true, NULL, 1, &key); |
| 15967 | |
| 15968 | perTupCxt = AllocSetContextCreate(CurrentMemoryContext, |
| 15969 | "clone trig" , ALLOCSET_SMALL_SIZES); |
| 15970 | |
| 15971 | while (HeapTupleIsValid(tuple = systable_getnext(scan))) |
| 15972 | { |
| 15973 | Form_pg_trigger trigForm = (Form_pg_trigger) GETSTRUCT(tuple); |
| 15974 | CreateTrigStmt *trigStmt; |
| 15975 | Node *qual = NULL; |
| 15976 | Datum value; |
| 15977 | bool isnull; |
| 15978 | List *cols = NIL; |
| 15979 | List *trigargs = NIL; |
| 15980 | MemoryContext oldcxt; |
| 15981 | |
| 15982 | /* |
| 15983 | * Ignore statement-level triggers; those are not cloned. |
| 15984 | */ |
| 15985 | if (!TRIGGER_FOR_ROW(trigForm->tgtype)) |
| 15986 | continue; |
| 15987 | |
| 15988 | /* We don't clone internal triggers, either */ |
| 15989 | if (trigForm->tgisinternal) |
| 15990 | continue; |
| 15991 | |
| 15992 | /* |
| 15993 | * Complain if we find an unexpected trigger type. |
| 15994 | */ |
| 15995 | if (!TRIGGER_FOR_AFTER(trigForm->tgtype)) |
| 15996 | elog(ERROR, "unexpected trigger \"%s\" found" , |
| 15997 | NameStr(trigForm->tgname)); |
| 15998 | |
| 15999 | /* Use short-lived context for CREATE TRIGGER */ |
| 16000 | oldcxt = MemoryContextSwitchTo(perTupCxt); |
| 16001 | |
| 16002 | /* |
| 16003 | * If there is a WHEN clause, generate a 'cooked' version of it that's |
| 16004 | * appropriate for the partition. |
| 16005 | */ |
| 16006 | value = heap_getattr(tuple, Anum_pg_trigger_tgqual, |
| 16007 | RelationGetDescr(pg_trigger), &isnull); |
| 16008 | if (!isnull) |
| 16009 | { |
| 16010 | bool found_whole_row; |
| 16011 | |
| 16012 | qual = stringToNode(TextDatumGetCString(value)); |
| 16013 | qual = (Node *) map_partition_varattnos((List *) qual, PRS2_OLD_VARNO, |
| 16014 | partition, parent, |
| 16015 | &found_whole_row); |
| 16016 | if (found_whole_row) |
| 16017 | elog(ERROR, "unexpected whole-row reference found in trigger WHEN clause" ); |
| 16018 | qual = (Node *) map_partition_varattnos((List *) qual, PRS2_NEW_VARNO, |
| 16019 | partition, parent, |
| 16020 | &found_whole_row); |
| 16021 | if (found_whole_row) |
| 16022 | elog(ERROR, "unexpected whole-row reference found in trigger WHEN clause" ); |
| 16023 | } |
| 16024 | |
| 16025 | /* |
| 16026 | * If there is a column list, transform it to a list of column names. |
| 16027 | * Note we don't need to map this list in any way ... |
| 16028 | */ |
| 16029 | if (trigForm->tgattr.dim1 > 0) |
| 16030 | { |
| 16031 | int i; |
| 16032 | |
| 16033 | for (i = 0; i < trigForm->tgattr.dim1; i++) |
| 16034 | { |
| 16035 | Form_pg_attribute col; |
| 16036 | |
| 16037 | col = TupleDescAttr(parent->rd_att, |
| 16038 | trigForm->tgattr.values[i] - 1); |
| 16039 | cols = lappend(cols, |
| 16040 | makeString(pstrdup(NameStr(col->attname)))); |
| 16041 | } |
| 16042 | } |
| 16043 | |
| 16044 | /* Reconstruct trigger arguments list. */ |
| 16045 | if (trigForm->tgnargs > 0) |
| 16046 | { |
| 16047 | char *p; |
| 16048 | |
| 16049 | value = heap_getattr(tuple, Anum_pg_trigger_tgargs, |
| 16050 | RelationGetDescr(pg_trigger), &isnull); |
| 16051 | if (isnull) |
| 16052 | elog(ERROR, "tgargs is null for trigger \"%s\" in partition \"%s\"" , |
| 16053 | NameStr(trigForm->tgname), RelationGetRelationName(partition)); |
| 16054 | |
| 16055 | p = (char *) VARDATA_ANY(DatumGetByteaPP(value)); |
| 16056 | |
| 16057 | for (int i = 0; i < trigForm->tgnargs; i++) |
| 16058 | { |
| 16059 | trigargs = lappend(trigargs, makeString(pstrdup(p))); |
| 16060 | p += strlen(p) + 1; |
| 16061 | } |
| 16062 | } |
| 16063 | |
| 16064 | trigStmt = makeNode(CreateTrigStmt); |
| 16065 | trigStmt->trigname = NameStr(trigForm->tgname); |
| 16066 | trigStmt->relation = NULL; |
| 16067 | trigStmt->funcname = NULL; /* passed separately */ |
| 16068 | trigStmt->args = trigargs; |
| 16069 | trigStmt->row = true; |
| 16070 | trigStmt->timing = trigForm->tgtype & TRIGGER_TYPE_TIMING_MASK; |
| 16071 | trigStmt->events = trigForm->tgtype & TRIGGER_TYPE_EVENT_MASK; |
| 16072 | trigStmt->columns = cols; |
| 16073 | trigStmt->whenClause = NULL; /* passed separately */ |
| 16074 | trigStmt->isconstraint = OidIsValid(trigForm->tgconstraint); |
| 16075 | trigStmt->transitionRels = NIL; /* not supported at present */ |
| 16076 | trigStmt->deferrable = trigForm->tgdeferrable; |
| 16077 | trigStmt->initdeferred = trigForm->tginitdeferred; |
| 16078 | trigStmt->constrrel = NULL; /* passed separately */ |
| 16079 | |
| 16080 | CreateTrigger(trigStmt, NULL, RelationGetRelid(partition), |
| 16081 | trigForm->tgconstrrelid, InvalidOid, InvalidOid, |
| 16082 | trigForm->tgfoid, trigForm->oid, qual, |
| 16083 | false, true); |
| 16084 | |
| 16085 | MemoryContextSwitchTo(oldcxt); |
| 16086 | MemoryContextReset(perTupCxt); |
| 16087 | } |
| 16088 | |
| 16089 | MemoryContextDelete(perTupCxt); |
| 16090 | |
| 16091 | systable_endscan(scan); |
| 16092 | table_close(pg_trigger, RowExclusiveLock); |
| 16093 | } |
| 16094 | |
| 16095 | /* |
| 16096 | * ALTER TABLE DETACH PARTITION |
| 16097 | * |
| 16098 | * Return the address of the relation that is no longer a partition of rel. |
| 16099 | */ |
| 16100 | static ObjectAddress |
| 16101 | ATExecDetachPartition(Relation rel, RangeVar *name) |
| 16102 | { |
| 16103 | Relation partRel, |
| 16104 | classRel; |
| 16105 | HeapTuple tuple, |
| 16106 | newtuple; |
| 16107 | Datum new_val[Natts_pg_class]; |
| 16108 | bool new_null[Natts_pg_class], |
| 16109 | new_repl[Natts_pg_class]; |
| 16110 | ObjectAddress address; |
| 16111 | Oid defaultPartOid; |
| 16112 | List *indexes; |
| 16113 | List *fks; |
| 16114 | ListCell *cell; |
| 16115 | |
| 16116 | /* |
| 16117 | * We must lock the default partition, because detaching this partition |
| 16118 | * will change its partition constraint. |
| 16119 | */ |
| 16120 | defaultPartOid = |
| 16121 | get_default_oid_from_partdesc(RelationGetPartitionDesc(rel)); |
| 16122 | if (OidIsValid(defaultPartOid)) |
| 16123 | LockRelationOid(defaultPartOid, AccessExclusiveLock); |
| 16124 | |
| 16125 | partRel = table_openrv(name, ShareUpdateExclusiveLock); |
| 16126 | |
| 16127 | /* Ensure that foreign keys still hold after this detach */ |
| 16128 | ATDetachCheckNoForeignKeyRefs(partRel); |
| 16129 | |
| 16130 | /* All inheritance related checks are performed within the function */ |
| 16131 | RemoveInheritance(partRel, rel); |
| 16132 | |
| 16133 | /* Update pg_class tuple */ |
| 16134 | classRel = table_open(RelationRelationId, RowExclusiveLock); |
| 16135 | tuple = SearchSysCacheCopy1(RELOID, |
| 16136 | ObjectIdGetDatum(RelationGetRelid(partRel))); |
| 16137 | if (!HeapTupleIsValid(tuple)) |
| 16138 | elog(ERROR, "cache lookup failed for relation %u" , |
| 16139 | RelationGetRelid(partRel)); |
| 16140 | Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition); |
| 16141 | |
| 16142 | /* Clear relpartbound and reset relispartition */ |
| 16143 | memset(new_val, 0, sizeof(new_val)); |
| 16144 | memset(new_null, false, sizeof(new_null)); |
| 16145 | memset(new_repl, false, sizeof(new_repl)); |
| 16146 | new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0; |
| 16147 | new_null[Anum_pg_class_relpartbound - 1] = true; |
| 16148 | new_repl[Anum_pg_class_relpartbound - 1] = true; |
| 16149 | newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel), |
| 16150 | new_val, new_null, new_repl); |
| 16151 | |
| 16152 | ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false; |
| 16153 | CatalogTupleUpdate(classRel, &newtuple->t_self, newtuple); |
| 16154 | heap_freetuple(newtuple); |
| 16155 | |
| 16156 | if (OidIsValid(defaultPartOid)) |
| 16157 | { |
| 16158 | /* |
| 16159 | * If the relation being detached is the default partition itself, |
| 16160 | * remove it from the parent's pg_partitioned_table entry. |
| 16161 | * |
| 16162 | * If not, we must invalidate default partition's relcache entry, as |
| 16163 | * in StorePartitionBound: its partition constraint depends on every |
| 16164 | * other partition's partition constraint. |
| 16165 | */ |
| 16166 | if (RelationGetRelid(partRel) == defaultPartOid) |
| 16167 | update_default_partition_oid(RelationGetRelid(rel), InvalidOid); |
| 16168 | else |
| 16169 | CacheInvalidateRelcacheByRelid(defaultPartOid); |
| 16170 | } |
| 16171 | |
| 16172 | /* detach indexes too */ |
| 16173 | indexes = RelationGetIndexList(partRel); |
| 16174 | foreach(cell, indexes) |
| 16175 | { |
| 16176 | Oid idxid = lfirst_oid(cell); |
| 16177 | Relation idx; |
| 16178 | Oid constrOid; |
| 16179 | |
| 16180 | if (!has_superclass(idxid)) |
| 16181 | continue; |
| 16182 | |
| 16183 | Assert((IndexGetRelation(get_partition_parent(idxid), false) == |
| 16184 | RelationGetRelid(rel))); |
| 16185 | |
| 16186 | idx = index_open(idxid, AccessExclusiveLock); |
| 16187 | IndexSetParentIndex(idx, InvalidOid); |
| 16188 | |
| 16189 | /* If there's a constraint associated with the index, detach it too */ |
| 16190 | constrOid = get_relation_idx_constraint_oid(RelationGetRelid(partRel), |
| 16191 | idxid); |
| 16192 | if (OidIsValid(constrOid)) |
| 16193 | ConstraintSetParentConstraint(constrOid, InvalidOid, InvalidOid); |
| 16194 | |
| 16195 | index_close(idx, NoLock); |
| 16196 | } |
| 16197 | table_close(classRel, RowExclusiveLock); |
| 16198 | |
| 16199 | /* |
| 16200 | * Detach any foreign keys that are inherited. This includes creating |
| 16201 | * additional action triggers. |
| 16202 | */ |
| 16203 | fks = copyObject(RelationGetFKeyList(partRel)); |
| 16204 | foreach(cell, fks) |
| 16205 | { |
| 16206 | ForeignKeyCacheInfo *fk = lfirst(cell); |
| 16207 | HeapTuple contup; |
| 16208 | Form_pg_constraint conform; |
| 16209 | Constraint *fkconstraint; |
| 16210 | |
| 16211 | contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid)); |
| 16212 | if (!HeapTupleIsValid(contup)) |
| 16213 | elog(ERROR, "cache lookup failed for constraint %u" , fk->conoid); |
| 16214 | conform = (Form_pg_constraint) GETSTRUCT(contup); |
| 16215 | |
| 16216 | /* consider only the inherited foreign keys */ |
| 16217 | if (conform->contype != CONSTRAINT_FOREIGN || |
| 16218 | !OidIsValid(conform->conparentid)) |
| 16219 | { |
| 16220 | ReleaseSysCache(contup); |
| 16221 | continue; |
| 16222 | } |
| 16223 | |
| 16224 | /* unset conparentid and adjust conislocal, coninhcount, etc. */ |
| 16225 | ConstraintSetParentConstraint(fk->conoid, InvalidOid, InvalidOid); |
| 16226 | |
| 16227 | /* |
| 16228 | * Make the action triggers on the referenced relation. When this was |
| 16229 | * a partition the action triggers pointed to the parent rel (they |
| 16230 | * still do), but now we need separate ones of our own. |
| 16231 | */ |
| 16232 | fkconstraint = makeNode(Constraint); |
| 16233 | fkconstraint->conname = pstrdup(NameStr(conform->conname)); |
| 16234 | fkconstraint->fk_upd_action = conform->confupdtype; |
| 16235 | fkconstraint->fk_del_action = conform->confdeltype; |
| 16236 | fkconstraint->deferrable = conform->condeferrable; |
| 16237 | fkconstraint->initdeferred = conform->condeferred; |
| 16238 | |
| 16239 | createForeignKeyActionTriggers(partRel, conform->confrelid, |
| 16240 | fkconstraint, fk->conoid, |
| 16241 | conform->conindid); |
| 16242 | |
| 16243 | ReleaseSysCache(contup); |
| 16244 | } |
| 16245 | list_free_deep(fks); |
| 16246 | |
| 16247 | /* |
| 16248 | * Any sub-constrains that are in the referenced-side of a larger |
| 16249 | * constraint have to be removed. This partition is no longer part of the |
| 16250 | * key space of the constraint. |
| 16251 | */ |
| 16252 | foreach(cell, GetParentedForeignKeyRefs(partRel)) |
| 16253 | { |
| 16254 | Oid constrOid = lfirst_oid(cell); |
| 16255 | ObjectAddress constraint; |
| 16256 | |
| 16257 | ConstraintSetParentConstraint(constrOid, InvalidOid, InvalidOid); |
| 16258 | deleteDependencyRecordsForClass(ConstraintRelationId, |
| 16259 | constrOid, |
| 16260 | ConstraintRelationId, |
| 16261 | DEPENDENCY_INTERNAL); |
| 16262 | CommandCounterIncrement(); |
| 16263 | |
| 16264 | ObjectAddressSet(constraint, ConstraintRelationId, constrOid); |
| 16265 | performDeletion(&constraint, DROP_RESTRICT, 0); |
| 16266 | } |
| 16267 | CommandCounterIncrement(); |
| 16268 | |
| 16269 | /* |
| 16270 | * Invalidate the parent's relcache so that the partition is no longer |
| 16271 | * included in its partition descriptor. |
| 16272 | */ |
| 16273 | CacheInvalidateRelcache(rel); |
| 16274 | |
| 16275 | ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel)); |
| 16276 | |
| 16277 | /* keep our lock until commit */ |
| 16278 | table_close(partRel, NoLock); |
| 16279 | |
| 16280 | return address; |
| 16281 | } |
| 16282 | |
| 16283 | /* |
| 16284 | * Before acquiring lock on an index, acquire the same lock on the owning |
| 16285 | * table. |
| 16286 | */ |
| 16287 | struct AttachIndexCallbackState |
| 16288 | { |
| 16289 | Oid partitionOid; |
| 16290 | Oid parentTblOid; |
| 16291 | bool lockedParentTbl; |
| 16292 | }; |
| 16293 | |
| 16294 | static void |
| 16295 | RangeVarCallbackForAttachIndex(const RangeVar *rv, Oid relOid, Oid oldRelOid, |
| 16296 | void *arg) |
| 16297 | { |
| 16298 | struct AttachIndexCallbackState *state; |
| 16299 | Form_pg_class classform; |
| 16300 | HeapTuple tuple; |
| 16301 | |
| 16302 | state = (struct AttachIndexCallbackState *) arg; |
| 16303 | |
| 16304 | if (!state->lockedParentTbl) |
| 16305 | { |
| 16306 | LockRelationOid(state->parentTblOid, AccessShareLock); |
| 16307 | state->lockedParentTbl = true; |
| 16308 | } |
| 16309 | |
| 16310 | /* |
| 16311 | * If we previously locked some other heap, and the name we're looking up |
| 16312 | * no longer refers to an index on that relation, release the now-useless |
| 16313 | * lock. XXX maybe we should do *after* we verify whether the index does |
| 16314 | * not actually belong to the same relation ... |
| 16315 | */ |
| 16316 | if (relOid != oldRelOid && OidIsValid(state->partitionOid)) |
| 16317 | { |
| 16318 | UnlockRelationOid(state->partitionOid, AccessShareLock); |
| 16319 | state->partitionOid = InvalidOid; |
| 16320 | } |
| 16321 | |
| 16322 | /* Didn't find a relation, so no need for locking or permission checks. */ |
| 16323 | if (!OidIsValid(relOid)) |
| 16324 | return; |
| 16325 | |
| 16326 | tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid)); |
| 16327 | if (!HeapTupleIsValid(tuple)) |
| 16328 | return; /* concurrently dropped, so nothing to do */ |
| 16329 | classform = (Form_pg_class) GETSTRUCT(tuple); |
| 16330 | if (classform->relkind != RELKIND_PARTITIONED_INDEX && |
| 16331 | classform->relkind != RELKIND_INDEX) |
| 16332 | ereport(ERROR, |
| 16333 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| 16334 | errmsg("\"%s\" is not an index" , rv->relname))); |
| 16335 | ReleaseSysCache(tuple); |
| 16336 | |
| 16337 | /* |
| 16338 | * Since we need only examine the heap's tupledesc, an access share lock |
| 16339 | * on it (preventing any DDL) is sufficient. |
| 16340 | */ |
| 16341 | state->partitionOid = IndexGetRelation(relOid, false); |
| 16342 | LockRelationOid(state->partitionOid, AccessShareLock); |
| 16343 | } |
| 16344 | |
| 16345 | /* |
| 16346 | * ALTER INDEX i1 ATTACH PARTITION i2 |
| 16347 | */ |
| 16348 | static ObjectAddress |
| 16349 | ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name) |
| 16350 | { |
| 16351 | Relation partIdx; |
| 16352 | Relation partTbl; |
| 16353 | Relation parentTbl; |
| 16354 | ObjectAddress address; |
| 16355 | Oid partIdxId; |
| 16356 | Oid currParent; |
| 16357 | struct AttachIndexCallbackState state; |
| 16358 | |
| 16359 | /* |
| 16360 | * We need to obtain lock on the index 'name' to modify it, but we also |
| 16361 | * need to read its owning table's tuple descriptor -- so we need to lock |
| 16362 | * both. To avoid deadlocks, obtain lock on the table before doing so on |
| 16363 | * the index. Furthermore, we need to examine the parent table of the |
| 16364 | * partition, so lock that one too. |
| 16365 | */ |
| 16366 | state.partitionOid = InvalidOid; |
| 16367 | state.parentTblOid = parentIdx->rd_index->indrelid; |
| 16368 | state.lockedParentTbl = false; |
| 16369 | partIdxId = |
| 16370 | RangeVarGetRelidExtended(name, AccessExclusiveLock, 0, |
| 16371 | RangeVarCallbackForAttachIndex, |
| 16372 | (void *) &state); |
| 16373 | /* Not there? */ |
| 16374 | if (!OidIsValid(partIdxId)) |
| 16375 | ereport(ERROR, |
| 16376 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
| 16377 | errmsg("index \"%s\" does not exist" , name->relname))); |
| 16378 | |
| 16379 | /* no deadlock risk: RangeVarGetRelidExtended already acquired the lock */ |
| 16380 | partIdx = relation_open(partIdxId, AccessExclusiveLock); |
| 16381 | |
| 16382 | /* we already hold locks on both tables, so this is safe: */ |
| 16383 | parentTbl = relation_open(parentIdx->rd_index->indrelid, AccessShareLock); |
| 16384 | partTbl = relation_open(partIdx->rd_index->indrelid, NoLock); |
| 16385 | |
| 16386 | ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partIdx)); |
| 16387 | |
| 16388 | /* Silently do nothing if already in the right state */ |
| 16389 | currParent = partIdx->rd_rel->relispartition ? |
| 16390 | get_partition_parent(partIdxId) : InvalidOid; |
| 16391 | if (currParent != RelationGetRelid(parentIdx)) |
| 16392 | { |
| 16393 | IndexInfo *childInfo; |
| 16394 | IndexInfo *parentInfo; |
| 16395 | AttrNumber *attmap; |
| 16396 | bool found; |
| 16397 | int i; |
| 16398 | PartitionDesc partDesc; |
| 16399 | Oid constraintOid, |
| 16400 | cldConstrId = InvalidOid; |
| 16401 | |
| 16402 | /* |
| 16403 | * If this partition already has an index attached, refuse the |
| 16404 | * operation. |
| 16405 | */ |
| 16406 | refuseDupeIndexAttach(parentIdx, partIdx, partTbl); |
| 16407 | |
| 16408 | if (OidIsValid(currParent)) |
| 16409 | ereport(ERROR, |
| 16410 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
| 16411 | errmsg("cannot attach index \"%s\" as a partition of index \"%s\"" , |
| 16412 | RelationGetRelationName(partIdx), |
| 16413 | RelationGetRelationName(parentIdx)), |
| 16414 | errdetail("Index \"%s\" is already attached to another index." , |
| 16415 | RelationGetRelationName(partIdx)))); |
| 16416 | |
| 16417 | /* Make sure it indexes a partition of the other index's table */ |
| 16418 | partDesc = RelationGetPartitionDesc(parentTbl); |
| 16419 | found = false; |
| 16420 | for (i = 0; i < partDesc->nparts; i++) |
| 16421 | { |
| 16422 | if (partDesc->oids[i] == state.partitionOid) |
| 16423 | { |
| 16424 | found = true; |
| 16425 | break; |
| 16426 | } |
| 16427 | } |
| 16428 | if (!found) |
| 16429 | ereport(ERROR, |
| 16430 | (errmsg("cannot attach index \"%s\" as a partition of index \"%s\"" , |
| 16431 | RelationGetRelationName(partIdx), |
| 16432 | RelationGetRelationName(parentIdx)), |
| 16433 | errdetail("Index \"%s\" is not an index on any partition of table \"%s\"." , |
| 16434 | RelationGetRelationName(partIdx), |
| 16435 | RelationGetRelationName(parentTbl)))); |
| 16436 | |
| 16437 | /* Ensure the indexes are compatible */ |
| 16438 | childInfo = BuildIndexInfo(partIdx); |
| 16439 | parentInfo = BuildIndexInfo(parentIdx); |
| 16440 | attmap = convert_tuples_by_name_map(RelationGetDescr(partTbl), |
| 16441 | RelationGetDescr(parentTbl), |
| 16442 | gettext_noop("could not convert row type" )); |
| 16443 | if (!CompareIndexInfo(childInfo, parentInfo, |
| 16444 | partIdx->rd_indcollation, |
| 16445 | parentIdx->rd_indcollation, |
| 16446 | partIdx->rd_opfamily, |
| 16447 | parentIdx->rd_opfamily, |
| 16448 | attmap, |
| 16449 | RelationGetDescr(parentTbl)->natts)) |
| 16450 | ereport(ERROR, |
| 16451 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| 16452 | errmsg("cannot attach index \"%s\" as a partition of index \"%s\"" , |
| 16453 | RelationGetRelationName(partIdx), |
| 16454 | RelationGetRelationName(parentIdx)), |
| 16455 | errdetail("The index definitions do not match." ))); |
| 16456 | |
| 16457 | /* |
| 16458 | * If there is a constraint in the parent, make sure there is one in |
| 16459 | * the child too. |
| 16460 | */ |
| 16461 | constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(parentTbl), |
| 16462 | RelationGetRelid(parentIdx)); |
| 16463 | |
| 16464 | if (OidIsValid(constraintOid)) |
| 16465 | { |
| 16466 | cldConstrId = get_relation_idx_constraint_oid(RelationGetRelid(partTbl), |
| 16467 | partIdxId); |
| 16468 | if (!OidIsValid(cldConstrId)) |
| 16469 | ereport(ERROR, |
| 16470 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| 16471 | errmsg("cannot attach index \"%s\" as a partition of index \"%s\"" , |
| 16472 | RelationGetRelationName(partIdx), |
| 16473 | RelationGetRelationName(parentIdx)), |
| 16474 | errdetail("The index \"%s\" belongs to a constraint in table \"%s\" but no constraint exists for index \"%s\"." , |
| 16475 | RelationGetRelationName(parentIdx), |
| 16476 | RelationGetRelationName(parentTbl), |
| 16477 | RelationGetRelationName(partIdx)))); |
| 16478 | } |
| 16479 | |
| 16480 | /* All good -- do it */ |
| 16481 | IndexSetParentIndex(partIdx, RelationGetRelid(parentIdx)); |
| 16482 | if (OidIsValid(constraintOid)) |
| 16483 | ConstraintSetParentConstraint(cldConstrId, constraintOid, |
| 16484 | RelationGetRelid(partTbl)); |
| 16485 | |
| 16486 | pfree(attmap); |
| 16487 | |
| 16488 | validatePartitionedIndex(parentIdx, parentTbl); |
| 16489 | } |
| 16490 | |
| 16491 | relation_close(parentTbl, AccessShareLock); |
| 16492 | /* keep these locks till commit */ |
| 16493 | relation_close(partTbl, NoLock); |
| 16494 | relation_close(partIdx, NoLock); |
| 16495 | |
| 16496 | return address; |
| 16497 | } |
| 16498 | |
| 16499 | /* |
| 16500 | * Verify whether the given partition already contains an index attached |
| 16501 | * to the given partitioned index. If so, raise an error. |
| 16502 | */ |
| 16503 | static void |
| 16504 | refuseDupeIndexAttach(Relation parentIdx, Relation partIdx, Relation partitionTbl) |
| 16505 | { |
| 16506 | Oid existingIdx; |
| 16507 | |
| 16508 | existingIdx = index_get_partition(partitionTbl, |
| 16509 | RelationGetRelid(parentIdx)); |
| 16510 | if (OidIsValid(existingIdx)) |
| 16511 | ereport(ERROR, |
| 16512 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
| 16513 | errmsg("cannot attach index \"%s\" as a partition of index \"%s\"" , |
| 16514 | RelationGetRelationName(partIdx), |
| 16515 | RelationGetRelationName(parentIdx)), |
| 16516 | errdetail("Another index is already attached for partition \"%s\"." , |
| 16517 | RelationGetRelationName(partitionTbl)))); |
| 16518 | } |
| 16519 | |
| 16520 | /* |
| 16521 | * Verify whether the set of attached partition indexes to a parent index on |
| 16522 | * a partitioned table is complete. If it is, mark the parent index valid. |
| 16523 | * |
| 16524 | * This should be called each time a partition index is attached. |
| 16525 | */ |
| 16526 | static void |
| 16527 | validatePartitionedIndex(Relation partedIdx, Relation partedTbl) |
| 16528 | { |
| 16529 | Relation inheritsRel; |
| 16530 | SysScanDesc scan; |
| 16531 | ScanKeyData key; |
| 16532 | int tuples = 0; |
| 16533 | HeapTuple inhTup; |
| 16534 | bool updated = false; |
| 16535 | |
| 16536 | Assert(partedIdx->rd_rel->relkind == RELKIND_PARTITIONED_INDEX); |
| 16537 | |
| 16538 | /* |
| 16539 | * Scan pg_inherits for this parent index. Count each valid index we find |
| 16540 | * (verifying the pg_index entry for each), and if we reach the total |
| 16541 | * amount we expect, we can mark this parent index as valid. |
| 16542 | */ |
| 16543 | inheritsRel = table_open(InheritsRelationId, AccessShareLock); |
| 16544 | ScanKeyInit(&key, Anum_pg_inherits_inhparent, |
| 16545 | BTEqualStrategyNumber, F_OIDEQ, |
| 16546 | ObjectIdGetDatum(RelationGetRelid(partedIdx))); |
| 16547 | scan = systable_beginscan(inheritsRel, InheritsParentIndexId, true, |
| 16548 | NULL, 1, &key); |
| 16549 | while ((inhTup = systable_getnext(scan)) != NULL) |
| 16550 | { |
| 16551 | Form_pg_inherits inhForm = (Form_pg_inherits) GETSTRUCT(inhTup); |
| 16552 | HeapTuple indTup; |
| 16553 | Form_pg_index indexForm; |
| 16554 | |
| 16555 | indTup = SearchSysCache1(INDEXRELID, |
| 16556 | ObjectIdGetDatum(inhForm->inhrelid)); |
| 16557 | if (!HeapTupleIsValid(indTup)) |
| 16558 | elog(ERROR, "cache lookup failed for index %u" , inhForm->inhrelid); |
| 16559 | indexForm = (Form_pg_index) GETSTRUCT(indTup); |
| 16560 | if (indexForm->indisvalid) |
| 16561 | tuples += 1; |
| 16562 | ReleaseSysCache(indTup); |
| 16563 | } |
| 16564 | |
| 16565 | /* Done with pg_inherits */ |
| 16566 | systable_endscan(scan); |
| 16567 | table_close(inheritsRel, AccessShareLock); |
| 16568 | |
| 16569 | /* |
| 16570 | * If we found as many inherited indexes as the partitioned table has |
| 16571 | * partitions, we're good; update pg_index to set indisvalid. |
| 16572 | */ |
| 16573 | if (tuples == RelationGetPartitionDesc(partedTbl)->nparts) |
| 16574 | { |
| 16575 | Relation idxRel; |
| 16576 | HeapTuple newtup; |
| 16577 | |
| 16578 | idxRel = table_open(IndexRelationId, RowExclusiveLock); |
| 16579 | |
| 16580 | newtup = heap_copytuple(partedIdx->rd_indextuple); |
| 16581 | ((Form_pg_index) GETSTRUCT(newtup))->indisvalid = true; |
| 16582 | updated = true; |
| 16583 | |
| 16584 | CatalogTupleUpdate(idxRel, &partedIdx->rd_indextuple->t_self, newtup); |
| 16585 | |
| 16586 | table_close(idxRel, RowExclusiveLock); |
| 16587 | } |
| 16588 | |
| 16589 | /* |
| 16590 | * If this index is in turn a partition of a larger index, validating it |
| 16591 | * might cause the parent to become valid also. Try that. |
| 16592 | */ |
| 16593 | if (updated && partedIdx->rd_rel->relispartition) |
| 16594 | { |
| 16595 | Oid parentIdxId, |
| 16596 | parentTblId; |
| 16597 | Relation parentIdx, |
| 16598 | parentTbl; |
| 16599 | |
| 16600 | /* make sure we see the validation we just did */ |
| 16601 | CommandCounterIncrement(); |
| 16602 | |
| 16603 | parentIdxId = get_partition_parent(RelationGetRelid(partedIdx)); |
| 16604 | parentTblId = get_partition_parent(RelationGetRelid(partedTbl)); |
| 16605 | parentIdx = relation_open(parentIdxId, AccessExclusiveLock); |
| 16606 | parentTbl = relation_open(parentTblId, AccessExclusiveLock); |
| 16607 | Assert(!parentIdx->rd_index->indisvalid); |
| 16608 | |
| 16609 | validatePartitionedIndex(parentIdx, parentTbl); |
| 16610 | |
| 16611 | relation_close(parentIdx, AccessExclusiveLock); |
| 16612 | relation_close(parentTbl, AccessExclusiveLock); |
| 16613 | } |
| 16614 | } |
| 16615 | |
| 16616 | /* |
| 16617 | * Return an OID list of constraints that reference the given relation |
| 16618 | * that are marked as having a parent constraints. |
| 16619 | */ |
| 16620 | static List * |
| 16621 | GetParentedForeignKeyRefs(Relation partition) |
| 16622 | { |
| 16623 | Relation pg_constraint; |
| 16624 | HeapTuple tuple; |
| 16625 | SysScanDesc scan; |
| 16626 | ScanKeyData key[2]; |
| 16627 | List *constraints = NIL; |
| 16628 | |
| 16629 | /* |
| 16630 | * If no indexes, or no columns are referenceable by FKs, we can avoid the |
| 16631 | * scan. |
| 16632 | */ |
| 16633 | if (RelationGetIndexList(partition) == NIL || |
| 16634 | bms_is_empty(RelationGetIndexAttrBitmap(partition, |
| 16635 | INDEX_ATTR_BITMAP_KEY))) |
| 16636 | return NIL; |
| 16637 | |
| 16638 | /* Search for constraints referencing this table */ |
| 16639 | pg_constraint = table_open(ConstraintRelationId, AccessShareLock); |
| 16640 | ScanKeyInit(&key[0], |
| 16641 | Anum_pg_constraint_confrelid, BTEqualStrategyNumber, |
| 16642 | F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(partition))); |
| 16643 | ScanKeyInit(&key[1], |
| 16644 | Anum_pg_constraint_contype, BTEqualStrategyNumber, |
| 16645 | F_CHAREQ, CharGetDatum(CONSTRAINT_FOREIGN)); |
| 16646 | |
| 16647 | /* XXX This is a seqscan, as we don't have a usable index */ |
| 16648 | scan = systable_beginscan(pg_constraint, InvalidOid, true, NULL, 2, key); |
| 16649 | while ((tuple = systable_getnext(scan)) != NULL) |
| 16650 | { |
| 16651 | Form_pg_constraint constrForm = (Form_pg_constraint) GETSTRUCT(tuple); |
| 16652 | |
| 16653 | /* |
| 16654 | * We only need to process constraints that are part of larger ones. |
| 16655 | */ |
| 16656 | if (!OidIsValid(constrForm->conparentid)) |
| 16657 | continue; |
| 16658 | |
| 16659 | constraints = lappend_oid(constraints, constrForm->oid); |
| 16660 | } |
| 16661 | |
| 16662 | systable_endscan(scan); |
| 16663 | table_close(pg_constraint, AccessShareLock); |
| 16664 | |
| 16665 | return constraints; |
| 16666 | } |
| 16667 | |
| 16668 | /* |
| 16669 | * During DETACH PARTITION, verify that any foreign keys pointing to the |
| 16670 | * partitioned table would not become invalid. An error is raised if any |
| 16671 | * referenced values exist. |
| 16672 | */ |
| 16673 | static void |
| 16674 | ATDetachCheckNoForeignKeyRefs(Relation partition) |
| 16675 | { |
| 16676 | List *constraints; |
| 16677 | ListCell *cell; |
| 16678 | |
| 16679 | constraints = GetParentedForeignKeyRefs(partition); |
| 16680 | |
| 16681 | foreach(cell, constraints) |
| 16682 | { |
| 16683 | Oid constrOid = lfirst_oid(cell); |
| 16684 | HeapTuple tuple; |
| 16685 | Form_pg_constraint constrForm; |
| 16686 | Relation rel; |
| 16687 | Trigger trig; |
| 16688 | |
| 16689 | tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid)); |
| 16690 | if (!HeapTupleIsValid(tuple)) |
| 16691 | elog(ERROR, "cache lookup failed for constraint %u" , constrOid); |
| 16692 | constrForm = (Form_pg_constraint) GETSTRUCT(tuple); |
| 16693 | |
| 16694 | Assert(OidIsValid(constrForm->conparentid)); |
| 16695 | Assert(constrForm->confrelid == RelationGetRelid(partition)); |
| 16696 | |
| 16697 | /* prevent data changes into the referencing table until commit */ |
| 16698 | rel = table_open(constrForm->conrelid, ShareLock); |
| 16699 | |
| 16700 | MemSet(&trig, 0, sizeof(trig)); |
| 16701 | trig.tgoid = InvalidOid; |
| 16702 | trig.tgname = NameStr(constrForm->conname); |
| 16703 | trig.tgenabled = TRIGGER_FIRES_ON_ORIGIN; |
| 16704 | trig.tgisinternal = true; |
| 16705 | trig.tgconstrrelid = RelationGetRelid(partition); |
| 16706 | trig.tgconstrindid = constrForm->conindid; |
| 16707 | trig.tgconstraint = constrForm->oid; |
| 16708 | trig.tgdeferrable = false; |
| 16709 | trig.tginitdeferred = false; |
| 16710 | /* we needn't fill in remaining fields */ |
| 16711 | |
| 16712 | RI_PartitionRemove_Check(&trig, rel, partition); |
| 16713 | |
| 16714 | ReleaseSysCache(tuple); |
| 16715 | |
| 16716 | table_close(rel, NoLock); |
| 16717 | } |
| 16718 | } |
| 16719 | |