Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * tablecmds.c
4 : * Commands for creating and altering table structures and settings
5 : *
6 : * Portions Copyright (c) 1996-2026, 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/attmap.h"
18 : #include "access/genam.h"
19 : #include "access/gist.h"
20 : #include "access/heapam.h"
21 : #include "access/heapam_xlog.h"
22 : #include "access/multixact.h"
23 : #include "access/reloptions.h"
24 : #include "access/relscan.h"
25 : #include "access/sysattr.h"
26 : #include "access/tableam.h"
27 : #include "access/toast_compression.h"
28 : #include "access/tupconvert.h"
29 : #include "access/xact.h"
30 : #include "access/xlog.h"
31 : #include "access/xloginsert.h"
32 : #include "catalog/catalog.h"
33 : #include "catalog/heap.h"
34 : #include "catalog/index.h"
35 : #include "catalog/namespace.h"
36 : #include "catalog/objectaccess.h"
37 : #include "catalog/partition.h"
38 : #include "catalog/pg_am.h"
39 : #include "catalog/pg_attrdef.h"
40 : #include "catalog/pg_collation.h"
41 : #include "catalog/pg_constraint.h"
42 : #include "catalog/pg_depend.h"
43 : #include "catalog/pg_extension_d.h"
44 : #include "catalog/pg_foreign_table.h"
45 : #include "catalog/pg_inherits.h"
46 : #include "catalog/pg_largeobject.h"
47 : #include "catalog/pg_largeobject_metadata.h"
48 : #include "catalog/pg_namespace.h"
49 : #include "catalog/pg_opclass.h"
50 : #include "catalog/pg_policy.h"
51 : #include "catalog/pg_proc.h"
52 : #include "catalog/pg_publication_rel.h"
53 : #include "catalog/pg_rewrite.h"
54 : #include "catalog/pg_statistic_ext.h"
55 : #include "catalog/pg_tablespace.h"
56 : #include "catalog/pg_trigger.h"
57 : #include "catalog/pg_type.h"
58 : #include "catalog/storage.h"
59 : #include "catalog/storage_xlog.h"
60 : #include "catalog/toasting.h"
61 : #include "commands/comment.h"
62 : #include "commands/defrem.h"
63 : #include "commands/event_trigger.h"
64 : #include "commands/extension.h"
65 : #include "commands/repack.h"
66 : #include "commands/sequence.h"
67 : #include "commands/tablecmds.h"
68 : #include "commands/tablespace.h"
69 : #include "commands/trigger.h"
70 : #include "commands/typecmds.h"
71 : #include "commands/user.h"
72 : #include "commands/vacuum.h"
73 : #include "common/int.h"
74 : #include "executor/executor.h"
75 : #include "foreign/fdwapi.h"
76 : #include "foreign/foreign.h"
77 : #include "miscadmin.h"
78 : #include "nodes/makefuncs.h"
79 : #include "nodes/nodeFuncs.h"
80 : #include "nodes/parsenodes.h"
81 : #include "optimizer/optimizer.h"
82 : #include "parser/parse_coerce.h"
83 : #include "parser/parse_collate.h"
84 : #include "parser/parse_expr.h"
85 : #include "parser/parse_relation.h"
86 : #include "parser/parse_type.h"
87 : #include "parser/parse_utilcmd.h"
88 : #include "parser/parser.h"
89 : #include "partitioning/partbounds.h"
90 : #include "partitioning/partdesc.h"
91 : #include "pgstat.h"
92 : #include "rewrite/rewriteDefine.h"
93 : #include "rewrite/rewriteHandler.h"
94 : #include "rewrite/rewriteManip.h"
95 : #include "storage/bufmgr.h"
96 : #include "storage/lmgr.h"
97 : #include "storage/lock.h"
98 : #include "storage/predicate.h"
99 : #include "storage/smgr.h"
100 : #include "tcop/utility.h"
101 : #include "utils/acl.h"
102 : #include "utils/builtins.h"
103 : #include "utils/fmgroids.h"
104 : #include "utils/inval.h"
105 : #include "utils/lsyscache.h"
106 : #include "utils/memutils.h"
107 : #include "utils/partcache.h"
108 : #include "utils/relcache.h"
109 : #include "utils/ruleutils.h"
110 : #include "utils/snapmgr.h"
111 : #include "utils/syscache.h"
112 : #include "utils/timestamp.h"
113 : #include "utils/typcache.h"
114 : #include "utils/usercontext.h"
115 :
116 : /*
117 : * ON COMMIT action list
118 : */
119 : typedef struct OnCommitItem
120 : {
121 : Oid relid; /* relid of relation */
122 : OnCommitAction oncommit; /* what to do at end of xact */
123 :
124 : /*
125 : * If this entry was created during the current transaction,
126 : * creating_subid is the ID of the creating subxact; if created in a prior
127 : * transaction, creating_subid is zero. If deleted during the current
128 : * transaction, deleting_subid is the ID of the deleting subxact; if no
129 : * deletion request is pending, deleting_subid is zero.
130 : */
131 : SubTransactionId creating_subid;
132 : SubTransactionId deleting_subid;
133 : } OnCommitItem;
134 :
135 : static List *on_commits = NIL;
136 :
137 :
138 : /*
139 : * State information for ALTER TABLE
140 : *
141 : * The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo
142 : * structs, one for each table modified by the operation (the named table
143 : * plus any child tables that are affected). We save lists of subcommands
144 : * to apply to this table (possibly modified by parse transformation steps);
145 : * these lists will be executed in Phase 2. If a Phase 3 step is needed,
146 : * necessary information is stored in the constraints and newvals lists.
147 : *
148 : * Phase 2 is divided into multiple passes; subcommands are executed in
149 : * a pass determined by subcommand type.
150 : */
151 :
152 : typedef enum AlterTablePass
153 : {
154 : AT_PASS_UNSET = -1, /* UNSET will cause ERROR */
155 : AT_PASS_DROP, /* DROP (all flavors) */
156 : AT_PASS_ALTER_TYPE, /* ALTER COLUMN TYPE */
157 : AT_PASS_ADD_COL, /* ADD COLUMN */
158 : AT_PASS_SET_EXPRESSION, /* ALTER SET EXPRESSION */
159 : AT_PASS_OLD_INDEX, /* re-add existing indexes */
160 : AT_PASS_OLD_CONSTR, /* re-add existing constraints */
161 : /* We could support a RENAME COLUMN pass here, but not currently used */
162 : AT_PASS_ADD_CONSTR, /* ADD constraints (initial examination) */
163 : AT_PASS_COL_ATTRS, /* set column attributes, eg NOT NULL */
164 : AT_PASS_ADD_INDEXCONSTR, /* ADD index-based constraints */
165 : AT_PASS_ADD_INDEX, /* ADD indexes */
166 : AT_PASS_ADD_OTHERCONSTR, /* ADD other constraints, defaults */
167 : AT_PASS_MISC, /* other stuff */
168 : } AlterTablePass;
169 :
170 : #define AT_NUM_PASSES (AT_PASS_MISC + 1)
171 :
172 : typedef struct AlteredTableInfo
173 : {
174 : /* Information saved before any work commences: */
175 : Oid relid; /* Relation to work on */
176 : char relkind; /* Its relkind */
177 : TupleDesc oldDesc; /* Pre-modification tuple descriptor */
178 :
179 : /*
180 : * Transiently set during Phase 2, normally set to NULL.
181 : *
182 : * ATRewriteCatalogs sets this when it starts, and closes when ATExecCmd
183 : * returns control. This can be exploited by ATExecCmd subroutines to
184 : * close/reopen across transaction boundaries.
185 : */
186 : Relation rel;
187 :
188 : /* Information saved by Phase 1 for Phase 2: */
189 : List *subcmds[AT_NUM_PASSES]; /* Lists of AlterTableCmd */
190 : /* Information saved by Phases 1/2 for Phase 3: */
191 : List *constraints; /* List of NewConstraint */
192 : List *newvals; /* List of NewColumnValue */
193 : List *afterStmts; /* List of utility command parsetrees */
194 : bool verify_new_notnull; /* T if we should recheck NOT NULL */
195 : int rewrite; /* Reason for forced rewrite, if any */
196 : bool chgAccessMethod; /* T if SET ACCESS METHOD is used */
197 : Oid newAccessMethod; /* new access method; 0 means no change,
198 : * if above is true */
199 : Oid newTableSpace; /* new tablespace; 0 means no change */
200 : bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
201 : char newrelpersistence; /* if above is true */
202 : Expr *partition_constraint; /* for attach partition validation */
203 : /* true, if validating default due to some other attach/detach */
204 : bool validate_default;
205 : /* Objects to rebuild after completing ALTER TYPE operations */
206 : List *changedConstraintOids; /* OIDs of constraints to rebuild */
207 : List *changedConstraintDefs; /* string definitions of same */
208 : List *changedIndexOids; /* OIDs of indexes to rebuild */
209 : List *changedIndexDefs; /* string definitions of same */
210 : char *replicaIdentityIndex; /* index to reset as REPLICA IDENTITY */
211 : char *clusterOnIndex; /* index to use for CLUSTER */
212 : List *changedStatisticsOids; /* OIDs of statistics to rebuild */
213 : List *changedStatisticsDefs; /* string definitions of same */
214 : } AlteredTableInfo;
215 :
216 : /* Struct describing one new constraint to check in Phase 3 scan */
217 : /* Note: new not-null constraints are handled elsewhere */
218 : typedef struct NewConstraint
219 : {
220 : char *name; /* Constraint name, or NULL if none */
221 : ConstrType contype; /* CHECK or FOREIGN */
222 : Oid refrelid; /* PK rel, if FOREIGN */
223 : Oid refindid; /* OID of PK's index, if FOREIGN */
224 : bool conwithperiod; /* Whether the new FOREIGN KEY uses PERIOD */
225 : Oid conid; /* OID of pg_constraint entry, if FOREIGN */
226 : Node *qual; /* Check expr or CONSTR_FOREIGN Constraint */
227 : ExprState *qualstate; /* Execution state for CHECK expr */
228 : } NewConstraint;
229 :
230 : /*
231 : * Struct describing one new column value that needs to be computed during
232 : * Phase 3 copy (this could be either a new column with a non-null default, or
233 : * a column that we're changing the type of). Columns without such an entry
234 : * are just copied from the old table during ATRewriteTable. Note that the
235 : * expr is an expression over *old* table values, except when is_generated
236 : * is true; then it is an expression over columns of the *new* tuple.
237 : */
238 : typedef struct NewColumnValue
239 : {
240 : AttrNumber attnum; /* which column */
241 : Expr *expr; /* expression to compute */
242 : ExprState *exprstate; /* execution state */
243 : bool is_generated; /* is it a GENERATED expression? */
244 : } NewColumnValue;
245 :
246 : /*
247 : * Error-reporting support for RemoveRelations
248 : */
249 : struct dropmsgstrings
250 : {
251 : char kind;
252 : int nonexistent_code;
253 : const char *nonexistent_msg;
254 : const char *skipping_msg;
255 : const char *nota_msg;
256 : const char *drophint_msg;
257 : };
258 :
259 : static const struct dropmsgstrings dropmsgstringarray[] = {
260 : {RELKIND_RELATION,
261 : ERRCODE_UNDEFINED_TABLE,
262 : gettext_noop("table \"%s\" does not exist"),
263 : gettext_noop("table \"%s\" does not exist, skipping"),
264 : gettext_noop("\"%s\" is not a table"),
265 : gettext_noop("Use DROP TABLE to remove a table.")},
266 : {RELKIND_SEQUENCE,
267 : ERRCODE_UNDEFINED_TABLE,
268 : gettext_noop("sequence \"%s\" does not exist"),
269 : gettext_noop("sequence \"%s\" does not exist, skipping"),
270 : gettext_noop("\"%s\" is not a sequence"),
271 : gettext_noop("Use DROP SEQUENCE to remove a sequence.")},
272 : {RELKIND_VIEW,
273 : ERRCODE_UNDEFINED_TABLE,
274 : gettext_noop("view \"%s\" does not exist"),
275 : gettext_noop("view \"%s\" does not exist, skipping"),
276 : gettext_noop("\"%s\" is not a view"),
277 : gettext_noop("Use DROP VIEW to remove a view.")},
278 : {RELKIND_MATVIEW,
279 : ERRCODE_UNDEFINED_TABLE,
280 : gettext_noop("materialized view \"%s\" does not exist"),
281 : gettext_noop("materialized view \"%s\" does not exist, skipping"),
282 : gettext_noop("\"%s\" is not a materialized view"),
283 : gettext_noop("Use DROP MATERIALIZED VIEW to remove a materialized view.")},
284 : {RELKIND_INDEX,
285 : ERRCODE_UNDEFINED_OBJECT,
286 : gettext_noop("index \"%s\" does not exist"),
287 : gettext_noop("index \"%s\" does not exist, skipping"),
288 : gettext_noop("\"%s\" is not an index"),
289 : gettext_noop("Use DROP INDEX to remove an index.")},
290 : {RELKIND_COMPOSITE_TYPE,
291 : ERRCODE_UNDEFINED_OBJECT,
292 : gettext_noop("type \"%s\" does not exist"),
293 : gettext_noop("type \"%s\" does not exist, skipping"),
294 : gettext_noop("\"%s\" is not a type"),
295 : gettext_noop("Use DROP TYPE to remove a type.")},
296 : {RELKIND_FOREIGN_TABLE,
297 : ERRCODE_UNDEFINED_OBJECT,
298 : gettext_noop("foreign table \"%s\" does not exist"),
299 : gettext_noop("foreign table \"%s\" does not exist, skipping"),
300 : gettext_noop("\"%s\" is not a foreign table"),
301 : gettext_noop("Use DROP FOREIGN TABLE to remove a foreign table.")},
302 : {RELKIND_PARTITIONED_TABLE,
303 : ERRCODE_UNDEFINED_TABLE,
304 : gettext_noop("table \"%s\" does not exist"),
305 : gettext_noop("table \"%s\" does not exist, skipping"),
306 : gettext_noop("\"%s\" is not a table"),
307 : gettext_noop("Use DROP TABLE to remove a table.")},
308 : {RELKIND_PARTITIONED_INDEX,
309 : ERRCODE_UNDEFINED_OBJECT,
310 : gettext_noop("index \"%s\" does not exist"),
311 : gettext_noop("index \"%s\" does not exist, skipping"),
312 : gettext_noop("\"%s\" is not an index"),
313 : gettext_noop("Use DROP INDEX to remove an index.")},
314 : {RELKIND_PROPGRAPH,
315 : ERRCODE_UNDEFINED_OBJECT,
316 : gettext_noop("property graph \"%s\" does not exist"),
317 : gettext_noop("property graph \"%s\" does not exist, skipping"),
318 : gettext_noop("\"%s\" is not a property graph"),
319 : gettext_noop("Use DROP PROPERTY GRAPH to remove a property graph.")},
320 : {'\0', 0, NULL, NULL, NULL, NULL}
321 : };
322 :
323 : /* communication between RemoveRelations and RangeVarCallbackForDropRelation */
324 : struct DropRelationCallbackState
325 : {
326 : /* These fields are set by RemoveRelations: */
327 : char expected_relkind;
328 : LOCKMODE heap_lockmode;
329 : /* These fields are state to track which subsidiary locks are held: */
330 : Oid heapOid;
331 : Oid partParentOid;
332 : /* These fields are passed back by RangeVarCallbackForDropRelation: */
333 : char actual_relkind;
334 : char actual_relpersistence;
335 : };
336 :
337 : /* Alter table target-type flags for ATSimplePermissions */
338 : #define ATT_TABLE 0x0001
339 : #define ATT_VIEW 0x0002
340 : #define ATT_MATVIEW 0x0004
341 : #define ATT_INDEX 0x0008
342 : #define ATT_COMPOSITE_TYPE 0x0010
343 : #define ATT_FOREIGN_TABLE 0x0020
344 : #define ATT_PARTITIONED_INDEX 0x0040
345 : #define ATT_SEQUENCE 0x0080
346 : #define ATT_PARTITIONED_TABLE 0x0100
347 :
348 : /*
349 : * ForeignTruncateInfo
350 : *
351 : * Information related to truncation of foreign tables. This is used for
352 : * the elements in a hash table. It uses the server OID as lookup key,
353 : * and includes a per-server list of all foreign tables involved in the
354 : * truncation.
355 : */
356 : typedef struct ForeignTruncateInfo
357 : {
358 : Oid serverid;
359 : List *rels;
360 : } ForeignTruncateInfo;
361 :
362 : /* Partial or complete FK creation in addFkConstraint() */
363 : typedef enum addFkConstraintSides
364 : {
365 : addFkReferencedSide,
366 : addFkReferencingSide,
367 : addFkBothSides,
368 : } addFkConstraintSides;
369 :
370 : /*
371 : * Hold extension dependencies of one partition index, during
372 : * MERGE/SPLIT PARTITION processing.
373 : *
374 : * collectPartitionIndexExtDeps() builds a list of these entries sorted by
375 : * parentIndexOid with exactly one entry per parent partitioned index; the
376 : * list is then consumed by applyPartitionIndexExtDeps() to re-record the
377 : * same dependencies on the newly created partition's indexes.
378 : *
379 : * extensionOids is kept sorted ascending so that equality checks between
380 : * entries from different partitions can be done in a single pass.
381 : * indexOid is carried only so that conflict errors can cite specific
382 : * partition index names.
383 : */
384 : typedef struct PartitionIndexExtDepEntry
385 : {
386 : Oid parentIndexOid; /* OID of the parent partitioned index */
387 : Oid indexOid; /* OID of a representative partition index */
388 : List *extensionOids; /* OIDs of dependent extensions, sorted asc */
389 : } PartitionIndexExtDepEntry;
390 :
391 : /*
392 : * Partition tables are expected to be dropped when the parent partitioned
393 : * table gets dropped. Hence for partitioning we use AUTO dependency.
394 : * Otherwise, for regular inheritance use NORMAL dependency.
395 : */
396 : #define child_dependency_type(child_is_partition) \
397 : ((child_is_partition) ? DEPENDENCY_AUTO : DEPENDENCY_NORMAL)
398 :
399 : static void truncate_check_rel(Oid relid, Form_pg_class reltuple);
400 : static void truncate_check_perms(Oid relid, Form_pg_class reltuple);
401 : static void truncate_check_activity(Relation rel);
402 : static void RangeVarCallbackForTruncate(const RangeVar *relation,
403 : Oid relId, Oid oldRelId, void *arg);
404 : static List *MergeAttributes(List *columns, const List *supers, char relpersistence,
405 : bool is_partition, List **supconstr,
406 : List **supnotnulls);
407 : static List *MergeCheckConstraint(List *constraints, const char *name, Node *expr, bool is_enforced);
408 : static void MergeChildAttribute(List *inh_columns, int exist_attno, int newcol_attno, const ColumnDef *newdef);
409 : static ColumnDef *MergeInheritedAttribute(List *inh_columns, int exist_attno, const ColumnDef *newdef);
410 : static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel, bool ispartition);
411 : static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
412 : static void StoreCatalogInheritance(Oid relationId, List *supers,
413 : bool child_is_partition);
414 : static void StoreCatalogInheritance1(Oid relationId, Oid parentOid,
415 : int32 seqNumber, Relation inhRelation,
416 : bool child_is_partition);
417 : static int findAttrByName(const char *attributeName, const List *columns);
418 : static void AlterIndexNamespaces(Relation classRel, Relation rel,
419 : Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved);
420 : static void AlterSeqNamespaces(Relation classRel, Relation rel,
421 : Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved,
422 : LOCKMODE lockmode);
423 : static ObjectAddress ATExecAlterConstraint(List **wqueue, Relation rel,
424 : ATAlterConstraint *cmdcon,
425 : bool recurse, LOCKMODE lockmode);
426 : static bool ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon, Relation conrel,
427 : Relation tgrel, Relation rel, HeapTuple contuple,
428 : bool recurse, LOCKMODE lockmode);
429 : static bool ATExecAlterFKConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon,
430 : Relation conrel, Relation tgrel,
431 : Oid fkrelid, Oid pkrelid,
432 : HeapTuple contuple, LOCKMODE lockmode,
433 : Oid ReferencedParentDelTrigger,
434 : Oid ReferencedParentUpdTrigger,
435 : Oid ReferencingParentInsTrigger,
436 : Oid ReferencingParentUpdTrigger);
437 : static bool ATExecAlterCheckConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon,
438 : Relation conrel, HeapTuple contuple,
439 : bool recurse, bool recursing,
440 : LOCKMODE lockmode);
441 : static bool ATExecAlterConstrDeferrability(List **wqueue, ATAlterConstraint *cmdcon,
442 : Relation conrel, Relation tgrel, Relation rel,
443 : HeapTuple contuple, bool recurse,
444 : List **otherrelids, LOCKMODE lockmode);
445 : static bool ATExecAlterConstrInheritability(List **wqueue, ATAlterConstraint *cmdcon,
446 : Relation conrel, Relation rel,
447 : HeapTuple contuple, LOCKMODE lockmode);
448 : static void AlterConstrTriggerDeferrability(Oid conoid, Relation tgrel, Relation rel,
449 : bool deferrable, bool initdeferred,
450 : List **otherrelids);
451 : static void AlterFKConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
452 : Relation conrel, Relation tgrel,
453 : Oid fkrelid, Oid pkrelid,
454 : HeapTuple contuple, LOCKMODE lockmode,
455 : Oid ReferencedParentDelTrigger,
456 : Oid ReferencedParentUpdTrigger,
457 : Oid ReferencingParentInsTrigger,
458 : Oid ReferencingParentUpdTrigger);
459 : static void AlterCheckConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
460 : Relation conrel, Oid conrelid,
461 : bool recurse, bool recursing,
462 : LOCKMODE lockmode);
463 : static void AlterConstrDeferrabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
464 : Relation conrel, Relation tgrel, Relation rel,
465 : HeapTuple contuple, bool recurse,
466 : List **otherrelids, LOCKMODE lockmode);
467 : static void AlterConstrUpdateConstraintEntry(ATAlterConstraint *cmdcon, Relation conrel,
468 : HeapTuple contuple);
469 : static ObjectAddress ATExecValidateConstraint(List **wqueue,
470 : Relation rel, char *constrName,
471 : bool recurse, bool recursing, LOCKMODE lockmode);
472 : static void QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation fkrel,
473 : Oid pkrelid, HeapTuple contuple, LOCKMODE lockmode);
474 : static void QueueCheckConstraintValidation(List **wqueue, Relation conrel, Relation rel,
475 : char *constrName, HeapTuple contuple,
476 : bool recurse, bool recursing, LOCKMODE lockmode);
477 : static void QueueNNConstraintValidation(List **wqueue, Relation conrel, Relation rel,
478 : HeapTuple contuple, bool recurse, bool recursing,
479 : LOCKMODE lockmode);
480 : static int transformColumnNameList(Oid relId, List *colList,
481 : int16 *attnums, Oid *atttypids, Oid *attcollids);
482 : static int transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
483 : List **attnamelist,
484 : int16 *attnums, Oid *atttypids, Oid *attcollids,
485 : Oid *opclasses, bool *pk_has_without_overlaps);
486 : static Oid transformFkeyCheckAttrs(Relation pkrel,
487 : int numattrs, int16 *attnums,
488 : bool with_period, Oid *opclasses,
489 : bool *pk_has_without_overlaps);
490 : static void checkFkeyPermissions(Relation rel, int16 *attnums, int natts);
491 : static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId,
492 : Oid *funcid);
493 : static void validateForeignKeyConstraint(char *conname,
494 : Relation rel, Relation pkrel,
495 : Oid pkindOid, Oid constraintOid, bool hasperiod);
496 : static void CheckAlterTableIsSafe(Relation rel);
497 : static void ATController(AlterTableStmt *parsetree,
498 : Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
499 : AlterTableUtilityContext *context);
500 : static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
501 : bool recurse, bool recursing, LOCKMODE lockmode,
502 : AlterTableUtilityContext *context);
503 : static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
504 : AlterTableUtilityContext *context);
505 : static void ATExecCmd(List **wqueue, AlteredTableInfo *tab,
506 : AlterTableCmd *cmd, LOCKMODE lockmode, AlterTablePass cur_pass,
507 : AlterTableUtilityContext *context);
508 : static AlterTableCmd *ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab,
509 : Relation rel, AlterTableCmd *cmd,
510 : bool recurse, LOCKMODE lockmode,
511 : AlterTablePass cur_pass,
512 : AlterTableUtilityContext *context);
513 : static void ATRewriteTables(AlterTableStmt *parsetree,
514 : List **wqueue, LOCKMODE lockmode,
515 : AlterTableUtilityContext *context);
516 : static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap);
517 : static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
518 : static void ATSimplePermissions(AlterTableType cmdtype, Relation rel, int allowed_targets);
519 : static void ATSimpleRecursion(List **wqueue, Relation rel,
520 : AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
521 : AlterTableUtilityContext *context);
522 : static void ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode);
523 : static void ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
524 : LOCKMODE lockmode,
525 : AlterTableUtilityContext *context);
526 : static List *find_typed_table_dependencies(Oid typeOid, const char *typeName,
527 : DropBehavior behavior);
528 : static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
529 : bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode,
530 : AlterTableUtilityContext *context);
531 : static ObjectAddress ATExecAddColumn(List **wqueue, AlteredTableInfo *tab,
532 : Relation rel, AlterTableCmd **cmd,
533 : bool recurse, bool recursing,
534 : LOCKMODE lockmode, AlterTablePass cur_pass,
535 : AlterTableUtilityContext *context);
536 : static bool check_for_column_name_collision(Relation rel, const char *colname,
537 : bool if_not_exists);
538 : static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
539 : static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
540 : static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, bool recurse,
541 : LOCKMODE lockmode);
542 : static void set_attnotnull(List **wqueue, Relation rel, AttrNumber attnum,
543 : bool is_valid, bool queue_validation);
544 : static ObjectAddress ATExecSetNotNull(List **wqueue, Relation rel,
545 : char *conName, char *colName,
546 : bool recurse, bool recursing,
547 : LOCKMODE lockmode);
548 : static bool NotNullImpliedByRelConstraints(Relation rel, Form_pg_attribute attr);
549 : static bool ConstraintImpliedByRelConstraint(Relation scanrel,
550 : List *testConstraint, List *provenConstraint);
551 : static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
552 : Node *newDefault, LOCKMODE lockmode);
553 : static ObjectAddress ATExecCookedColumnDefault(Relation rel, AttrNumber attnum,
554 : Node *newDefault);
555 : static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName,
556 : Node *def, LOCKMODE lockmode, bool recurse, bool recursing);
557 : static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName,
558 : Node *def, LOCKMODE lockmode, bool recurse, bool recursing);
559 : static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode,
560 : bool recurse, bool recursing);
561 : static ObjectAddress ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
562 : Node *newExpr, LOCKMODE lockmode);
563 : static void ATPrepDropExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, LOCKMODE lockmode);
564 : static ObjectAddress ATExecDropExpression(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode);
565 : static ObjectAddress ATExecSetStatistics(Relation rel, const char *colName, int16 colNum,
566 : Node *newValue, LOCKMODE lockmode);
567 : static ObjectAddress ATExecSetOptions(Relation rel, const char *colName,
568 : Node *options, bool isReset, LOCKMODE lockmode);
569 : static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
570 : Node *newValue, LOCKMODE lockmode);
571 : static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
572 : AlterTableCmd *cmd, LOCKMODE lockmode,
573 : AlterTableUtilityContext *context);
574 : static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
575 : DropBehavior behavior,
576 : bool recurse, bool recursing,
577 : bool missing_ok, LOCKMODE lockmode,
578 : ObjectAddresses *addrs);
579 : static void ATPrepAddPrimaryKey(List **wqueue, Relation rel, AlterTableCmd *cmd,
580 : bool recurse, LOCKMODE lockmode,
581 : AlterTableUtilityContext *context);
582 : static void verifyNotNullPKCompatible(HeapTuple tuple, const char *colname);
583 : static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
584 : IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
585 : static ObjectAddress ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
586 : CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
587 : static ObjectAddress ATExecAddConstraint(List **wqueue,
588 : AlteredTableInfo *tab, Relation rel,
589 : Constraint *newConstraint, bool recurse, bool is_readd,
590 : LOCKMODE lockmode);
591 : static char *ChooseForeignKeyConstraintNameAddition(List *colnames);
592 : static ObjectAddress ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
593 : IndexStmt *stmt, LOCKMODE lockmode);
594 : static ObjectAddress ATAddCheckNNConstraint(List **wqueue,
595 : AlteredTableInfo *tab, Relation rel,
596 : Constraint *constr,
597 : bool recurse, bool recursing, bool is_readd,
598 : LOCKMODE lockmode);
599 : static ObjectAddress ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab,
600 : Relation rel, Constraint *fkconstraint,
601 : bool recurse, bool recursing,
602 : LOCKMODE lockmode);
603 : static int validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
604 : int numfksetcols, int16 *fksetcolsattnums,
605 : List *fksetcols);
606 : static ObjectAddress addFkConstraint(addFkConstraintSides fkside,
607 : char *constraintname,
608 : Constraint *fkconstraint, Relation rel,
609 : Relation pkrel, Oid indexOid,
610 : Oid parentConstr,
611 : int numfks, int16 *pkattnum, int16 *fkattnum,
612 : Oid *pfeqoperators, Oid *ppeqoperators,
613 : Oid *ffeqoperators, int numfkdelsetcols,
614 : int16 *fkdelsetcols, bool is_internal,
615 : bool with_period);
616 : static void addFkRecurseReferenced(Constraint *fkconstraint,
617 : Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
618 : int numfks, int16 *pkattnum, int16 *fkattnum,
619 : Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
620 : int numfkdelsetcols, int16 *fkdelsetcols,
621 : bool old_check_ok,
622 : Oid parentDelTrigger, Oid parentUpdTrigger,
623 : bool with_period);
624 : static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint,
625 : Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
626 : int numfks, int16 *pkattnum, int16 *fkattnum,
627 : Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
628 : int numfkdelsetcols, int16 *fkdelsetcols,
629 : bool old_check_ok, LOCKMODE lockmode,
630 : Oid parentInsTrigger, Oid parentUpdTrigger,
631 : bool with_period);
632 : static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
633 : Relation partitionRel);
634 : static void CloneFkReferenced(Relation parentRel, Relation partitionRel);
635 : static void CloneFkReferencing(List **wqueue, Relation parentRel,
636 : Relation partRel);
637 : static void createForeignKeyCheckTriggers(Oid myRelOid, Oid refRelOid,
638 : Constraint *fkconstraint, Oid constraintOid,
639 : Oid indexOid,
640 : Oid parentInsTrigger, Oid parentUpdTrigger,
641 : Oid *insertTrigOid, Oid *updateTrigOid);
642 : static void createForeignKeyActionTriggers(Oid myRelOid, Oid refRelOid,
643 : Constraint *fkconstraint, Oid constraintOid,
644 : Oid indexOid,
645 : Oid parentDelTrigger, Oid parentUpdTrigger,
646 : Oid *deleteTrigOid, Oid *updateTrigOid);
647 : static bool tryAttachPartitionForeignKey(List **wqueue,
648 : ForeignKeyCacheInfo *fk,
649 : Relation partition,
650 : Oid parentConstrOid, int numfks,
651 : AttrNumber *mapped_conkey, AttrNumber *confkey,
652 : Oid *conpfeqop,
653 : Oid parentInsTrigger,
654 : Oid parentUpdTrigger,
655 : Relation trigrel);
656 : static void AttachPartitionForeignKey(List **wqueue, Relation partition,
657 : Oid partConstrOid, Oid parentConstrOid,
658 : Oid parentInsTrigger, Oid parentUpdTrigger,
659 : Relation trigrel);
660 : static void RemoveInheritedConstraint(Relation conrel, Relation trigrel,
661 : Oid conoid, Oid conrelid);
662 : static void DropForeignKeyConstraintTriggers(Relation trigrel, Oid conoid,
663 : Oid confrelid, Oid conrelid);
664 : static void GetForeignKeyActionTriggers(Relation trigrel,
665 : Oid conoid, Oid confrelid, Oid conrelid,
666 : Oid *deleteTriggerOid,
667 : Oid *updateTriggerOid);
668 : static void GetForeignKeyCheckTriggers(Relation trigrel,
669 : Oid conoid, Oid confrelid, Oid conrelid,
670 : Oid *insertTriggerOid,
671 : Oid *updateTriggerOid);
672 : static void ATExecDropConstraint(Relation rel, const char *constrName,
673 : DropBehavior behavior, bool recurse,
674 : bool missing_ok, LOCKMODE lockmode);
675 : static ObjectAddress dropconstraint_internal(Relation rel,
676 : HeapTuple constraintTup, DropBehavior behavior,
677 : bool recurse, bool recursing,
678 : bool missing_ok, LOCKMODE lockmode);
679 : static void ATPrepAlterColumnType(List **wqueue,
680 : AlteredTableInfo *tab, Relation rel,
681 : bool recurse, bool recursing,
682 : AlterTableCmd *cmd, LOCKMODE lockmode,
683 : AlterTableUtilityContext *context);
684 : static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno);
685 : static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
686 : AlterTableCmd *cmd, LOCKMODE lockmode);
687 : static void RememberAllDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype,
688 : Relation rel, AttrNumber attnum, const char *colName);
689 : static void RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab);
690 : static void RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab);
691 : static void RememberStatisticsForRebuilding(Oid stxoid, AlteredTableInfo *tab);
692 : static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab,
693 : LOCKMODE lockmode);
694 : static void ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId,
695 : char *cmd, List **wqueue, LOCKMODE lockmode,
696 : bool rewrite);
697 : static void RebuildConstraintComment(AlteredTableInfo *tab, AlterTablePass pass,
698 : Oid objid, Relation rel, List *domname,
699 : const char *conname);
700 : static void TryReuseIndex(Oid oldId, IndexStmt *stmt);
701 : static void TryReuseForeignKey(Oid oldId, Constraint *con);
702 : static ObjectAddress ATExecAlterColumnGenericOptions(Relation rel, const char *colName,
703 : List *options, LOCKMODE lockmode);
704 : static void change_owner_fix_column_acls(Oid relationOid,
705 : Oid oldOwnerId, Oid newOwnerId);
706 : static void change_owner_recurse_to_sequences(Oid relationOid,
707 : Oid newOwnerId, LOCKMODE lockmode);
708 : static ObjectAddress ATExecClusterOn(Relation rel, const char *indexName,
709 : LOCKMODE lockmode);
710 : static void ATExecDropCluster(Relation rel, LOCKMODE lockmode);
711 : static void ATPrepSetAccessMethod(AlteredTableInfo *tab, Relation rel, const char *amname);
712 : static void ATExecSetAccessMethodNoStorage(Relation rel, Oid newAccessMethodId);
713 : static void ATPrepChangePersistence(AlteredTableInfo *tab, Relation rel,
714 : bool toLogged);
715 : static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
716 : const char *tablespacename, LOCKMODE lockmode);
717 : static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
718 : static void ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace);
719 : static void ATExecSetRelOptions(Relation rel, List *defList,
720 : AlterTableType operation,
721 : LOCKMODE lockmode);
722 : static void ATExecEnableDisableTrigger(Relation rel, const char *trigname,
723 : char fires_when, bool skip_system, bool recurse,
724 : LOCKMODE lockmode);
725 : static void ATExecEnableDisableRule(Relation rel, const char *rulename,
726 : char fires_when, LOCKMODE lockmode);
727 : static void ATPrepChangeInherit(Relation child_rel);
728 : static ObjectAddress ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode);
729 : static ObjectAddress ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode);
730 : static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid,
731 : DependencyType deptype);
732 : static ObjectAddress ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
733 : static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
734 : static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode);
735 : static void ATExecGenericOptions(Relation rel, List *options);
736 : static void ATExecSetRowSecurity(Relation rel, bool rls);
737 : static void ATExecForceNoForceRowSecurity(Relation rel, bool force_rls);
738 : static ObjectAddress ATExecSetCompression(Relation rel,
739 : const char *column, Node *newValue, LOCKMODE lockmode);
740 :
741 : static void index_copy_data(Relation rel, RelFileLocator newrlocator);
742 : static const char *storage_name(char c);
743 :
744 : static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
745 : Oid oldRelOid, void *arg);
746 : static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
747 : Oid oldrelid, void *arg);
748 : static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec);
749 : static void ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNumber *partattrs,
750 : List **partexprs, Oid *partopclass, Oid *partcollation,
751 : PartitionStrategy strategy);
752 : static void CreateInheritance(Relation child_rel, Relation parent_rel, bool ispartition);
753 : static void RemoveInheritance(Relation child_rel, Relation parent_rel,
754 : bool expect_detached);
755 : static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
756 : PartitionCmd *cmd,
757 : AlterTableUtilityContext *context);
758 : static void AttachPartitionEnsureIndexes(List **wqueue, Relation rel, Relation attachrel);
759 : static void QueuePartitionConstraintValidation(List **wqueue, Relation scanrel,
760 : List *partConstraint,
761 : bool validate_default);
762 : static void CloneRowTriggersToPartition(Relation parent, Relation partition);
763 : static void DropClonedTriggersFromPartition(Oid partitionId);
764 : static ObjectAddress ATExecDetachPartition(List **wqueue, AlteredTableInfo *tab,
765 : Relation rel, RangeVar *name,
766 : bool concurrent);
767 : static void DetachPartitionFinalize(Relation rel, Relation partRel,
768 : bool concurrent, Oid defaultPartOid);
769 : static ObjectAddress ATExecDetachPartitionFinalize(Relation rel, RangeVar *name);
770 : static ObjectAddress ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx,
771 : RangeVar *name);
772 : static void validatePartitionedIndex(Relation partedIdx, Relation partedTbl);
773 : static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
774 : Relation partitionTbl);
775 : static void verifyPartitionIndexNotNull(IndexInfo *iinfo, Relation partition);
776 : static List *GetParentedForeignKeyRefs(Relation partition);
777 : static void ATDetachCheckNoForeignKeyRefs(Relation partition);
778 : static char GetAttributeCompression(Oid atttypid, const char *compression);
779 : static char GetAttributeStorage(Oid atttypid, const char *storagemode);
780 :
781 : static void ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
782 : PartitionCmd *cmd, AlterTableUtilityContext *context);
783 : static void ATExecSplitPartition(List **wqueue, AlteredTableInfo *tab,
784 : Relation rel, PartitionCmd *cmd,
785 : AlterTableUtilityContext *context);
786 : static List *collectPartitionIndexExtDeps(List *partitionOids);
787 : static void applyPartitionIndexExtDeps(Oid newPartOid, List *extDepState);
788 : static void freePartitionIndexExtDeps(List *extDepState);
789 :
790 : /* ----------------------------------------------------------------
791 : * DefineRelation
792 : * Creates a new relation.
793 : *
794 : * stmt carries parsetree information from an ordinary CREATE TABLE statement.
795 : * The other arguments are used to extend the behavior for other cases:
796 : * relkind: relkind to assign to the new relation
797 : * ownerId: if not InvalidOid, use this as the new relation's owner.
798 : * typaddress: if not null, it's set to the pg_type entry's address.
799 : * queryString: for error reporting
800 : *
801 : * Note that permissions checks are done against current user regardless of
802 : * ownerId. A nonzero ownerId is used when someone is creating a relation
803 : * "on behalf of" someone else, so we still want to see that the current user
804 : * has permissions to do it.
805 : *
806 : * If successful, returns the address of the new relation.
807 : * ----------------------------------------------------------------
808 : */
809 : ObjectAddress
810 42095 : DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
811 : ObjectAddress *typaddress, const char *queryString)
812 : {
813 : char relname[NAMEDATALEN];
814 : Oid namespaceId;
815 : Oid relationId;
816 : Oid tablespaceId;
817 : Relation rel;
818 : TupleDesc descriptor;
819 : List *inheritOids;
820 : List *old_constraints;
821 : List *old_notnulls;
822 : List *rawDefaults;
823 : List *cookedDefaults;
824 : List *nncols;
825 42095 : List *connames = NIL;
826 : Datum reloptions;
827 : ListCell *listptr;
828 : AttrNumber attnum;
829 : bool partitioned;
830 42095 : const char *const validnsps[] = HEAP_RELOPT_NAMESPACES;
831 : Oid ofTypeId;
832 : ObjectAddress address;
833 : LOCKMODE parentLockmode;
834 42095 : Oid accessMethodId = InvalidOid;
835 :
836 : /*
837 : * Truncate relname to appropriate length (probably a waste of time, as
838 : * parser should have done this already).
839 : */
840 42095 : strlcpy(relname, stmt->relation->relname, NAMEDATALEN);
841 :
842 : /*
843 : * Check consistency of arguments
844 : */
845 42095 : if (stmt->oncommit != ONCOMMIT_NOOP
846 128 : && stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
847 8 : ereport(ERROR,
848 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
849 : errmsg("ON COMMIT can only be used on temporary tables")));
850 :
851 42087 : if (stmt->partspec != NULL)
852 : {
853 3659 : if (relkind != RELKIND_RELATION)
854 0 : elog(ERROR, "unexpected relkind: %d", (int) relkind);
855 :
856 3659 : relkind = RELKIND_PARTITIONED_TABLE;
857 3659 : partitioned = true;
858 : }
859 : else
860 38428 : partitioned = false;
861 :
862 42087 : if (relkind == RELKIND_PARTITIONED_TABLE &&
863 3659 : stmt->relation->relpersistence == RELPERSISTENCE_UNLOGGED)
864 4 : ereport(ERROR,
865 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
866 : errmsg("partitioned tables cannot be unlogged")));
867 :
868 : /*
869 : * Look up the namespace in which we are supposed to create the relation,
870 : * check we have permission to create there, lock it against concurrent
871 : * drop, and mark stmt->relation as RELPERSISTENCE_TEMP if a temporary
872 : * namespace is selected.
873 : */
874 : namespaceId =
875 42083 : RangeVarGetAndCheckCreationNamespace(stmt->relation, NoLock, NULL);
876 :
877 : /*
878 : * Security check: disallow creating temp tables from security-restricted
879 : * code. This is needed because calling code might not expect untrusted
880 : * tables to appear in pg_temp at the front of its search path.
881 : */
882 42083 : if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
883 2329 : && InSecurityRestrictedOperation())
884 0 : ereport(ERROR,
885 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
886 : errmsg("cannot create temporary table within security-restricted operation")));
887 :
888 : /*
889 : * Determine the lockmode to use when scanning parents. A self-exclusive
890 : * lock is needed here.
891 : *
892 : * For regular inheritance, if two backends attempt to add children to the
893 : * same parent simultaneously, and that parent has no pre-existing
894 : * children, then both will attempt to update the parent's relhassubclass
895 : * field, leading to a "tuple concurrently updated" error. Also, this
896 : * interlocks against a concurrent ANALYZE on the parent table, which
897 : * might otherwise be attempting to clear the parent's relhassubclass
898 : * field, if its previous children were recently dropped.
899 : *
900 : * If the child table is a partition, then we instead grab an exclusive
901 : * lock on the parent because its partition descriptor will be changed by
902 : * addition of the new partition.
903 : */
904 42083 : parentLockmode = (stmt->partbound != NULL ? AccessExclusiveLock :
905 : ShareUpdateExclusiveLock);
906 :
907 : /* Determine the list of OIDs of the parents. */
908 42083 : inheritOids = NIL;
909 50272 : foreach(listptr, stmt->inhRelations)
910 : {
911 8189 : RangeVar *rv = (RangeVar *) lfirst(listptr);
912 : Oid parentOid;
913 :
914 8189 : parentOid = RangeVarGetRelid(rv, parentLockmode, false);
915 :
916 : /*
917 : * Reject duplications in the list of parents.
918 : */
919 8189 : if (list_member_oid(inheritOids, parentOid))
920 0 : ereport(ERROR,
921 : (errcode(ERRCODE_DUPLICATE_TABLE),
922 : errmsg("relation \"%s\" would be inherited from more than once",
923 : get_rel_name(parentOid))));
924 :
925 8189 : inheritOids = lappend_oid(inheritOids, parentOid);
926 : }
927 :
928 : /*
929 : * Select tablespace to use: an explicitly indicated one, or (in the case
930 : * of a partitioned table) the parent's, if it has one.
931 : */
932 42083 : if (stmt->tablespacename)
933 : {
934 92 : tablespaceId = get_tablespace_oid(stmt->tablespacename, false);
935 :
936 88 : if (partitioned && tablespaceId == MyDatabaseTableSpace)
937 4 : ereport(ERROR,
938 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
939 : errmsg("cannot specify default tablespace for partitioned relations")));
940 : }
941 41991 : else if (stmt->partbound)
942 : {
943 : Assert(list_length(inheritOids) == 1);
944 6540 : tablespaceId = get_rel_tablespace(linitial_oid(inheritOids));
945 : }
946 : else
947 35451 : tablespaceId = InvalidOid;
948 :
949 : /* still nothing? use the default */
950 42075 : if (!OidIsValid(tablespaceId))
951 41953 : tablespaceId = GetDefaultTablespace(stmt->relation->relpersistence,
952 : partitioned);
953 :
954 : /* Check permissions except when using database's default */
955 42071 : if (OidIsValid(tablespaceId) && tablespaceId != MyDatabaseTableSpace)
956 : {
957 : AclResult aclresult;
958 :
959 138 : aclresult = object_aclcheck(TableSpaceRelationId, tablespaceId, GetUserId(),
960 : ACL_CREATE);
961 138 : if (aclresult != ACLCHECK_OK)
962 3 : aclcheck_error(aclresult, OBJECT_TABLESPACE,
963 3 : get_tablespace_name(tablespaceId));
964 : }
965 :
966 : /* In all cases disallow placing user relations in pg_global */
967 42068 : if (tablespaceId == GLOBALTABLESPACE_OID)
968 12 : ereport(ERROR,
969 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
970 : errmsg("only shared relations can be placed in pg_global tablespace")));
971 :
972 : /* Identify user ID that will own the table */
973 42056 : if (!OidIsValid(ownerId))
974 41901 : ownerId = GetUserId();
975 :
976 : /*
977 : * Parse and validate reloptions, if any.
978 : */
979 42056 : reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
980 : true, false);
981 :
982 42044 : switch (relkind)
983 : {
984 10750 : case RELKIND_VIEW:
985 10750 : (void) view_reloptions(reloptions, true);
986 10738 : break;
987 3643 : case RELKIND_PARTITIONED_TABLE:
988 3643 : (void) partitioned_table_reloptions(reloptions, true);
989 3639 : break;
990 27651 : default:
991 27651 : (void) heap_reloptions(relkind, reloptions, true);
992 : }
993 :
994 41964 : if (stmt->ofTypename)
995 : {
996 : AclResult aclresult;
997 :
998 57 : ofTypeId = typenameTypeId(NULL, stmt->ofTypename);
999 :
1000 57 : aclresult = object_aclcheck(TypeRelationId, ofTypeId, GetUserId(), ACL_USAGE);
1001 57 : if (aclresult != ACLCHECK_OK)
1002 4 : aclcheck_error_type(aclresult, ofTypeId);
1003 : }
1004 : else
1005 41907 : ofTypeId = InvalidOid;
1006 :
1007 : /*
1008 : * Look up inheritance ancestors and generate relation schema, including
1009 : * inherited attributes. (Note that stmt->tableElts is destructively
1010 : * modified by MergeAttributes.)
1011 : */
1012 41800 : stmt->tableElts =
1013 41960 : MergeAttributes(stmt->tableElts, inheritOids,
1014 41960 : stmt->relation->relpersistence,
1015 41960 : stmt->partbound != NULL,
1016 : &old_constraints, &old_notnulls);
1017 :
1018 : /*
1019 : * Create a tuple descriptor from the relation schema. Note that this
1020 : * deals with column names, types, and in-descriptor NOT NULL flags, but
1021 : * not default values, NOT NULL or CHECK constraints; we handle those
1022 : * below.
1023 : */
1024 41800 : descriptor = BuildDescForRelation(stmt->tableElts);
1025 :
1026 : /*
1027 : * Find columns with default values and prepare for insertion of the
1028 : * defaults. Pre-cooked (that is, inherited) defaults go into a list of
1029 : * CookedConstraint structs that we'll pass to heap_create_with_catalog,
1030 : * while raw defaults go into a list of RawColumnDefault structs that will
1031 : * be processed by AddRelationNewConstraints. (We can't deal with raw
1032 : * expressions until we can do transformExpr.)
1033 : */
1034 41768 : rawDefaults = NIL;
1035 41768 : cookedDefaults = NIL;
1036 41768 : attnum = 0;
1037 :
1038 208569 : foreach(listptr, stmt->tableElts)
1039 : {
1040 166801 : ColumnDef *colDef = lfirst(listptr);
1041 :
1042 166801 : attnum++;
1043 166801 : if (colDef->raw_default != NULL)
1044 : {
1045 : RawColumnDefault *rawEnt;
1046 :
1047 : Assert(colDef->cooked_default == NULL);
1048 :
1049 2205 : rawEnt = palloc_object(RawColumnDefault);
1050 2205 : rawEnt->attnum = attnum;
1051 2205 : rawEnt->raw_default = colDef->raw_default;
1052 2205 : rawEnt->generated = colDef->generated;
1053 2205 : rawDefaults = lappend(rawDefaults, rawEnt);
1054 : }
1055 164596 : else if (colDef->cooked_default != NULL)
1056 : {
1057 : CookedConstraint *cooked;
1058 :
1059 362 : cooked = palloc_object(CookedConstraint);
1060 362 : cooked->contype = CONSTR_DEFAULT;
1061 362 : cooked->conoid = InvalidOid; /* until created */
1062 362 : cooked->name = NULL;
1063 362 : cooked->attnum = attnum;
1064 362 : cooked->expr = colDef->cooked_default;
1065 362 : cooked->is_enforced = true;
1066 362 : cooked->skip_validation = false;
1067 362 : cooked->is_local = true; /* not used for defaults */
1068 362 : cooked->inhcount = 0; /* ditto */
1069 362 : cooked->is_no_inherit = false;
1070 362 : cookedDefaults = lappend(cookedDefaults, cooked);
1071 : }
1072 : }
1073 :
1074 41768 : TupleDescFinalize(descriptor);
1075 :
1076 : /*
1077 : * For relations with table AM and partitioned tables, select access
1078 : * method to use: an explicitly indicated one, or (in the case of a
1079 : * partitioned table) the parent's, if it has one.
1080 : */
1081 41768 : if (stmt->accessMethod != NULL)
1082 : {
1083 : Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE);
1084 87 : accessMethodId = get_table_am_oid(stmt->accessMethod, false);
1085 : }
1086 41681 : else if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE)
1087 : {
1088 26905 : if (stmt->partbound)
1089 : {
1090 : Assert(list_length(inheritOids) == 1);
1091 6425 : accessMethodId = get_rel_relam(linitial_oid(inheritOids));
1092 : }
1093 :
1094 26905 : if (RELKIND_HAS_TABLE_AM(relkind) && !OidIsValid(accessMethodId))
1095 23253 : accessMethodId = get_table_am_oid(default_table_access_method, false);
1096 : }
1097 :
1098 : /*
1099 : * Create the relation. Inherited defaults and CHECK constraints are
1100 : * passed in for immediate handling --- since they don't need parsing,
1101 : * they can be stored immediately.
1102 : */
1103 41756 : relationId = heap_create_with_catalog(relname,
1104 : namespaceId,
1105 : tablespaceId,
1106 : InvalidOid,
1107 : InvalidOid,
1108 : ofTypeId,
1109 : ownerId,
1110 : accessMethodId,
1111 : descriptor,
1112 : list_concat(cookedDefaults,
1113 : old_constraints),
1114 : relkind,
1115 41756 : stmt->relation->relpersistence,
1116 : false,
1117 : false,
1118 : stmt->oncommit,
1119 : reloptions,
1120 : true,
1121 : allowSystemTableMods,
1122 : false,
1123 : InvalidOid,
1124 : typaddress);
1125 :
1126 : /*
1127 : * We must bump the command counter to make the newly-created relation
1128 : * tuple visible for opening.
1129 : */
1130 41720 : CommandCounterIncrement();
1131 :
1132 : /*
1133 : * Open the new relation and acquire exclusive lock on it. This isn't
1134 : * really necessary for locking out other backends (since they can't see
1135 : * the new rel anyway until we commit), but it keeps the lock manager from
1136 : * complaining about deadlock risks.
1137 : */
1138 41720 : rel = relation_open(relationId, AccessExclusiveLock);
1139 :
1140 : /*
1141 : * Now add any newly specified column default and generation expressions
1142 : * to the new relation. These are passed to us in the form of raw
1143 : * parsetrees; we need to transform them to executable expression trees
1144 : * before they can be added. The most convenient way to do that is to
1145 : * apply the parser's transformExpr routine, but transformExpr doesn't
1146 : * work unless we have a pre-existing relation. So, the transformation has
1147 : * to be postponed to this final step of CREATE TABLE.
1148 : *
1149 : * This needs to be before processing the partitioning clauses because
1150 : * those could refer to generated columns.
1151 : */
1152 41720 : if (rawDefaults)
1153 1856 : AddRelationNewConstraints(rel, rawDefaults, NIL,
1154 : true, true, false, queryString);
1155 :
1156 : /*
1157 : * Make column generation expressions visible for use by partitioning.
1158 : */
1159 41592 : CommandCounterIncrement();
1160 :
1161 : /* Process and store partition bound, if any. */
1162 41592 : if (stmt->partbound)
1163 : {
1164 : PartitionBoundSpec *bound;
1165 : ParseState *pstate;
1166 6488 : Oid parentId = linitial_oid(inheritOids),
1167 : defaultPartOid;
1168 : Relation parent,
1169 6488 : defaultRel = NULL;
1170 : ParseNamespaceItem *nsitem;
1171 :
1172 : /* Already have strong enough lock on the parent */
1173 6488 : parent = table_open(parentId, NoLock);
1174 :
1175 : /*
1176 : * We are going to try to validate the partition bound specification
1177 : * against the partition key of parentRel, so it better have one.
1178 : */
1179 6488 : if (parent->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
1180 12 : ereport(ERROR,
1181 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
1182 : errmsg("\"%s\" is not partitioned",
1183 : RelationGetRelationName(parent))));
1184 :
1185 : /*
1186 : * The partition constraint of the default partition depends on the
1187 : * partition bounds of every other partition. It is possible that
1188 : * another backend might be about to execute a query on the default
1189 : * partition table, and that the query relies on previously cached
1190 : * default partition constraints. We must therefore take a table lock
1191 : * strong enough to prevent all queries on the default partition from
1192 : * proceeding until we commit and send out a shared-cache-inval notice
1193 : * that will make them update their index lists.
1194 : *
1195 : * Order of locking: The relation being added won't be visible to
1196 : * other backends until it is committed, hence here in
1197 : * DefineRelation() the order of locking the default partition and the
1198 : * relation being added does not matter. But at all other places we
1199 : * need to lock the default relation before we lock the relation being
1200 : * added or removed i.e. we should take the lock in same order at all
1201 : * the places such that lock parent, lock default partition and then
1202 : * lock the partition so as to avoid a deadlock.
1203 : */
1204 : defaultPartOid =
1205 6476 : get_default_oid_from_partdesc(RelationGetPartitionDesc(parent,
1206 : true));
1207 6476 : if (OidIsValid(defaultPartOid))
1208 255 : defaultRel = table_open(defaultPartOid, AccessExclusiveLock);
1209 :
1210 : /* Transform the bound values */
1211 6476 : pstate = make_parsestate(NULL);
1212 6476 : pstate->p_sourcetext = queryString;
1213 :
1214 : /*
1215 : * Add an nsitem containing this relation, so that transformExpr
1216 : * called on partition bound expressions is able to report errors
1217 : * using a proper context.
1218 : */
1219 6476 : nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock,
1220 : NULL, false, false);
1221 6476 : addNSItemToQuery(pstate, nsitem, false, true, true);
1222 :
1223 6476 : bound = transformPartitionBound(pstate, parent, stmt->partbound);
1224 :
1225 : /*
1226 : * Check first that the new partition's bound is valid and does not
1227 : * overlap with any of existing partitions of the parent.
1228 : */
1229 6340 : check_new_partition_bound(relname, parent, bound, pstate);
1230 :
1231 : /*
1232 : * If the default partition exists, its partition constraints will
1233 : * change after the addition of this new partition such that it won't
1234 : * allow any row that qualifies for this new partition. So, check that
1235 : * the existing data in the default partition satisfies the constraint
1236 : * as it will exist after adding this partition.
1237 : */
1238 6264 : if (OidIsValid(defaultPartOid))
1239 : {
1240 235 : check_default_partition_contents(parent, defaultRel, bound);
1241 : /* Keep the lock until commit. */
1242 223 : table_close(defaultRel, NoLock);
1243 : }
1244 :
1245 : /* Update the pg_class entry. */
1246 6252 : StorePartitionBound(rel, parent, bound);
1247 :
1248 6252 : table_close(parent, NoLock);
1249 : }
1250 :
1251 : /* Store inheritance information for new rel. */
1252 41356 : StoreCatalogInheritance(relationId, inheritOids, stmt->partbound != NULL);
1253 :
1254 : /*
1255 : * Process the partitioning specification (if any) and store the partition
1256 : * key information into the catalog.
1257 : */
1258 41356 : if (partitioned)
1259 : {
1260 : ParseState *pstate;
1261 : int partnatts;
1262 : AttrNumber partattrs[PARTITION_MAX_KEYS];
1263 : Oid partopclass[PARTITION_MAX_KEYS];
1264 : Oid partcollation[PARTITION_MAX_KEYS];
1265 3639 : List *partexprs = NIL;
1266 :
1267 3639 : pstate = make_parsestate(NULL);
1268 3639 : pstate->p_sourcetext = queryString;
1269 :
1270 3639 : partnatts = list_length(stmt->partspec->partParams);
1271 :
1272 : /* Protect fixed-size arrays here and in executor */
1273 3639 : if (partnatts > PARTITION_MAX_KEYS)
1274 0 : ereport(ERROR,
1275 : (errcode(ERRCODE_TOO_MANY_COLUMNS),
1276 : errmsg("cannot partition using more than %d columns",
1277 : PARTITION_MAX_KEYS)));
1278 :
1279 : /*
1280 : * We need to transform the raw parsetrees corresponding to partition
1281 : * expressions into executable expression trees. Like column defaults
1282 : * and CHECK constraints, we could not have done the transformation
1283 : * earlier.
1284 : */
1285 3639 : stmt->partspec = transformPartitionSpec(rel, stmt->partspec);
1286 :
1287 3619 : ComputePartitionAttrs(pstate, rel, stmt->partspec->partParams,
1288 : partattrs, &partexprs, partopclass,
1289 3619 : partcollation, stmt->partspec->strategy);
1290 :
1291 3531 : StorePartitionKey(rel, stmt->partspec->strategy, partnatts, partattrs,
1292 : partexprs,
1293 : partopclass, partcollation);
1294 :
1295 : /* make it all visible */
1296 3531 : CommandCounterIncrement();
1297 : }
1298 :
1299 : /*
1300 : * If we're creating a partition, create now all the indexes, triggers,
1301 : * FKs defined in the parent.
1302 : *
1303 : * We can't do it earlier, because DefineIndex wants to know the partition
1304 : * key which we just stored.
1305 : */
1306 41248 : if (stmt->partbound)
1307 : {
1308 6248 : Oid parentId = linitial_oid(inheritOids);
1309 : Relation parent;
1310 : List *idxlist;
1311 : ListCell *cell;
1312 :
1313 : /* Already have strong enough lock on the parent */
1314 6248 : parent = table_open(parentId, NoLock);
1315 6248 : idxlist = RelationGetIndexList(parent);
1316 :
1317 : /*
1318 : * For each index in the parent table, create one in the partition
1319 : */
1320 7338 : foreach(cell, idxlist)
1321 : {
1322 1102 : Relation idxRel = index_open(lfirst_oid(cell), AccessShareLock);
1323 : AttrMap *attmap;
1324 : IndexStmt *idxstmt;
1325 : Oid constraintOid;
1326 :
1327 1102 : if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
1328 : {
1329 24 : if (idxRel->rd_index->indisunique)
1330 8 : ereport(ERROR,
1331 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
1332 : errmsg("cannot create foreign partition of partitioned table \"%s\"",
1333 : RelationGetRelationName(parent)),
1334 : errdetail("Table \"%s\" contains indexes that are unique.",
1335 : RelationGetRelationName(parent))));
1336 : else
1337 : {
1338 16 : index_close(idxRel, AccessShareLock);
1339 16 : continue;
1340 : }
1341 : }
1342 :
1343 1078 : attmap = build_attrmap_by_name(RelationGetDescr(rel),
1344 : RelationGetDescr(parent),
1345 : false);
1346 : idxstmt =
1347 1078 : generateClonedIndexStmt(NULL, idxRel,
1348 : attmap, &constraintOid);
1349 1078 : DefineIndex(NULL,
1350 : RelationGetRelid(rel),
1351 : idxstmt,
1352 : InvalidOid,
1353 : RelationGetRelid(idxRel),
1354 : constraintOid,
1355 : -1,
1356 : false, false, false, false, false);
1357 :
1358 1074 : index_close(idxRel, AccessShareLock);
1359 : }
1360 :
1361 6236 : list_free(idxlist);
1362 :
1363 : /*
1364 : * If there are any row-level triggers, clone them to the new
1365 : * partition.
1366 : */
1367 6236 : if (parent->trigdesc != NULL)
1368 260 : CloneRowTriggersToPartition(parent, rel);
1369 :
1370 : /*
1371 : * And foreign keys too. Note that because we're freshly creating the
1372 : * table, there is no need to verify these new constraints.
1373 : */
1374 6236 : CloneForeignKeyConstraints(NULL, parent, rel);
1375 :
1376 6236 : table_close(parent, NoLock);
1377 : }
1378 :
1379 : /*
1380 : * Now add any newly specified CHECK constraints to the new relation. Same
1381 : * as for defaults above, but these need to come after partitioning is set
1382 : * up. We save the constraint names that were used, to avoid dupes below.
1383 : */
1384 41236 : if (stmt->constraints)
1385 : {
1386 : List *conlist;
1387 :
1388 498 : conlist = AddRelationNewConstraints(rel, NIL, stmt->constraints,
1389 : true, true, false, queryString);
1390 1509 : foreach_ptr(CookedConstraint, cons, conlist)
1391 : {
1392 553 : if (cons->name != NULL)
1393 553 : connames = lappend(connames, cons->name);
1394 : }
1395 : }
1396 :
1397 : /*
1398 : * Finally, merge the not-null constraints that are declared directly with
1399 : * those that come from parent relations (making sure to count inheritance
1400 : * appropriately for each), create them, and set the attnotnull flag on
1401 : * columns that don't yet have it.
1402 : */
1403 41216 : nncols = AddRelationNotNullConstraints(rel, stmt->nnconstraints,
1404 : old_notnulls, connames);
1405 92618 : foreach_int(attrnum, nncols)
1406 10290 : set_attnotnull(NULL, rel, attrnum, true, false);
1407 :
1408 41164 : ObjectAddressSet(address, RelationRelationId, relationId);
1409 :
1410 : /*
1411 : * Clean up. We keep lock on new relation (although it shouldn't be
1412 : * visible to anyone else anyway, until commit).
1413 : */
1414 41164 : relation_close(rel, NoLock);
1415 :
1416 41164 : return address;
1417 : }
1418 :
1419 : /*
1420 : * BuildDescForRelation
1421 : *
1422 : * Given a list of ColumnDef nodes, build a TupleDesc.
1423 : *
1424 : * Note: This is only for the limited purpose of table and view creation. Not
1425 : * everything is filled in. A real tuple descriptor should be obtained from
1426 : * the relcache.
1427 : */
1428 : TupleDesc
1429 44457 : BuildDescForRelation(const List *columns)
1430 : {
1431 : int natts;
1432 : AttrNumber attnum;
1433 : ListCell *l;
1434 : TupleDesc desc;
1435 : char *attname;
1436 : Oid atttypid;
1437 : int32 atttypmod;
1438 : Oid attcollation;
1439 : int attdim;
1440 :
1441 : /*
1442 : * allocate a new tuple descriptor
1443 : */
1444 44457 : natts = list_length(columns);
1445 44457 : desc = CreateTemplateTupleDesc(natts);
1446 :
1447 44457 : attnum = 0;
1448 :
1449 214844 : foreach(l, columns)
1450 : {
1451 170427 : ColumnDef *entry = lfirst(l);
1452 : AclResult aclresult;
1453 : Form_pg_attribute att;
1454 :
1455 : /*
1456 : * for each entry in the list, get the name and type information from
1457 : * the list and have TupleDescInitEntry fill in the attribute
1458 : * information we need.
1459 : */
1460 170427 : attnum++;
1461 :
1462 170427 : attname = entry->colname;
1463 170427 : typenameTypeIdAndMod(NULL, entry->typeName, &atttypid, &atttypmod);
1464 :
1465 170427 : aclresult = object_aclcheck(TypeRelationId, atttypid, GetUserId(), ACL_USAGE);
1466 170427 : if (aclresult != ACLCHECK_OK)
1467 28 : aclcheck_error_type(aclresult, atttypid);
1468 :
1469 170399 : attcollation = GetColumnDefCollation(NULL, entry, atttypid);
1470 170399 : attdim = list_length(entry->typeName->arrayBounds);
1471 170399 : if (attdim > PG_INT16_MAX)
1472 0 : ereport(ERROR,
1473 : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
1474 : errmsg("too many array dimensions"));
1475 :
1476 170399 : if (entry->typeName->setof)
1477 0 : ereport(ERROR,
1478 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
1479 : errmsg("column \"%s\" cannot be declared SETOF",
1480 : attname)));
1481 :
1482 170399 : TupleDescInitEntry(desc, attnum, attname,
1483 : atttypid, atttypmod, attdim);
1484 170399 : att = TupleDescAttr(desc, attnum - 1);
1485 :
1486 : /* Override TupleDescInitEntry's settings as requested */
1487 170399 : TupleDescInitEntryCollation(desc, attnum, attcollation);
1488 :
1489 : /* Fill in additional stuff not handled by TupleDescInitEntry */
1490 170399 : att->attnotnull = entry->is_not_null;
1491 170399 : att->attislocal = entry->is_local;
1492 170399 : att->attinhcount = entry->inhcount;
1493 170399 : att->attidentity = entry->identity;
1494 170399 : att->attgenerated = entry->generated;
1495 170399 : att->attcompression = GetAttributeCompression(att->atttypid, entry->compression);
1496 170391 : if (entry->storage)
1497 16893 : att->attstorage = entry->storage;
1498 153498 : else if (entry->storage_name)
1499 39 : att->attstorage = GetAttributeStorage(att->atttypid, entry->storage_name);
1500 :
1501 170387 : populate_compact_attribute(desc, attnum - 1);
1502 : }
1503 :
1504 44417 : TupleDescFinalize(desc);
1505 :
1506 44417 : return desc;
1507 : }
1508 :
1509 : /*
1510 : * Emit the right error or warning message for a "DROP" command issued on a
1511 : * non-existent relation
1512 : */
1513 : static void
1514 641 : DropErrorMsgNonExistent(RangeVar *rel, char rightkind, bool missing_ok)
1515 : {
1516 : const struct dropmsgstrings *rentry;
1517 :
1518 721 : if (rel->schemaname != NULL &&
1519 80 : !OidIsValid(LookupNamespaceNoError(rel->schemaname)))
1520 : {
1521 28 : if (!missing_ok)
1522 : {
1523 0 : ereport(ERROR,
1524 : (errcode(ERRCODE_UNDEFINED_SCHEMA),
1525 : errmsg("schema \"%s\" does not exist", rel->schemaname)));
1526 : }
1527 : else
1528 : {
1529 28 : ereport(NOTICE,
1530 : (errmsg("schema \"%s\" does not exist, skipping",
1531 : rel->schemaname)));
1532 : }
1533 28 : return;
1534 : }
1535 :
1536 898 : for (rentry = dropmsgstringarray; rentry->kind != '\0'; rentry++)
1537 : {
1538 898 : if (rentry->kind == rightkind)
1539 : {
1540 613 : if (!missing_ok)
1541 : {
1542 90 : ereport(ERROR,
1543 : (errcode(rentry->nonexistent_code),
1544 : errmsg(rentry->nonexistent_msg, rel->relname)));
1545 : }
1546 : else
1547 : {
1548 523 : ereport(NOTICE, (errmsg(rentry->skipping_msg, rel->relname)));
1549 523 : break;
1550 : }
1551 : }
1552 : }
1553 :
1554 : Assert(rentry->kind != '\0'); /* Should be impossible */
1555 : }
1556 :
1557 : /*
1558 : * Emit the right error message for a "DROP" command issued on a
1559 : * relation of the wrong type
1560 : */
1561 : static void
1562 4 : DropErrorMsgWrongType(const char *relname, char wrongkind, char rightkind)
1563 : {
1564 : const struct dropmsgstrings *rentry;
1565 : const struct dropmsgstrings *wentry;
1566 :
1567 4 : for (rentry = dropmsgstringarray; rentry->kind != '\0'; rentry++)
1568 4 : if (rentry->kind == rightkind)
1569 4 : break;
1570 : Assert(rentry->kind != '\0');
1571 :
1572 40 : for (wentry = dropmsgstringarray; wentry->kind != '\0'; wentry++)
1573 40 : if (wentry->kind == wrongkind)
1574 4 : break;
1575 : /* wrongkind could be something we don't have in our table... */
1576 :
1577 4 : ereport(ERROR,
1578 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
1579 : errmsg(rentry->nota_msg, relname),
1580 : (wentry->kind != '\0') ? errhint("%s", _(wentry->drophint_msg)) : 0));
1581 : }
1582 :
1583 : /*
1584 : * RemoveRelations
1585 : * Implements DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW,
1586 : * DROP MATERIALIZED VIEW, DROP FOREIGN TABLE, DROP PROPERTY GRAPH
1587 : */
1588 : void
1589 11827 : RemoveRelations(DropStmt *drop)
1590 : {
1591 : ObjectAddresses *objects;
1592 : char relkind;
1593 : ListCell *cell;
1594 11827 : int flags = 0;
1595 11827 : LOCKMODE lockmode = AccessExclusiveLock;
1596 :
1597 : /* DROP CONCURRENTLY uses a weaker lock, and has some restrictions */
1598 11827 : if (drop->concurrent)
1599 : {
1600 : /*
1601 : * Note that for temporary relations this lock may get upgraded later
1602 : * on, but as no other session can access a temporary relation, this
1603 : * is actually fine.
1604 : */
1605 121 : lockmode = ShareUpdateExclusiveLock;
1606 : Assert(drop->removeType == OBJECT_INDEX);
1607 121 : if (list_length(drop->objects) != 1)
1608 4 : ereport(ERROR,
1609 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1610 : errmsg("DROP INDEX CONCURRENTLY does not support dropping multiple objects")));
1611 117 : if (drop->behavior == DROP_CASCADE)
1612 0 : ereport(ERROR,
1613 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1614 : errmsg("DROP INDEX CONCURRENTLY does not support CASCADE")));
1615 : }
1616 :
1617 : /*
1618 : * First we identify all the relations, then we delete them in a single
1619 : * performMultipleDeletions() call. This is to avoid unwanted DROP
1620 : * RESTRICT errors if one of the relations depends on another.
1621 : */
1622 :
1623 : /* Determine required relkind */
1624 11823 : switch (drop->removeType)
1625 : {
1626 10224 : case OBJECT_TABLE:
1627 10224 : relkind = RELKIND_RELATION;
1628 10224 : break;
1629 :
1630 567 : case OBJECT_INDEX:
1631 567 : relkind = RELKIND_INDEX;
1632 567 : break;
1633 :
1634 120 : case OBJECT_SEQUENCE:
1635 120 : relkind = RELKIND_SEQUENCE;
1636 120 : break;
1637 :
1638 672 : case OBJECT_VIEW:
1639 672 : relkind = RELKIND_VIEW;
1640 672 : break;
1641 :
1642 83 : case OBJECT_MATVIEW:
1643 83 : relkind = RELKIND_MATVIEW;
1644 83 : break;
1645 :
1646 107 : case OBJECT_FOREIGN_TABLE:
1647 107 : relkind = RELKIND_FOREIGN_TABLE;
1648 107 : break;
1649 :
1650 50 : case OBJECT_PROPGRAPH:
1651 50 : relkind = RELKIND_PROPGRAPH;
1652 50 : break;
1653 :
1654 0 : default:
1655 0 : elog(ERROR, "unrecognized drop object type: %d",
1656 : (int) drop->removeType);
1657 : relkind = 0; /* keep compiler quiet */
1658 : break;
1659 : }
1660 :
1661 : /* Lock and validate each relation; build a list of object addresses */
1662 11823 : objects = new_object_addresses();
1663 :
1664 26061 : foreach(cell, drop->objects)
1665 : {
1666 14349 : RangeVar *rel = makeRangeVarFromNameList((List *) lfirst(cell));
1667 : Oid relOid;
1668 : ObjectAddress obj;
1669 : struct DropRelationCallbackState state;
1670 :
1671 : /*
1672 : * These next few steps are a great deal like relation_openrv, but we
1673 : * don't bother building a relcache entry since we don't need it.
1674 : *
1675 : * Check for shared-cache-inval messages before trying to access the
1676 : * relation. This is needed to cover the case where the name
1677 : * identifies a rel that has been dropped and recreated since the
1678 : * start of our transaction: if we don't flush the old syscache entry,
1679 : * then we'll latch onto that entry and suffer an error later.
1680 : */
1681 14349 : AcceptInvalidationMessages();
1682 :
1683 : /* Look up the appropriate relation using namespace search. */
1684 14349 : state.expected_relkind = relkind;
1685 28698 : state.heap_lockmode = drop->concurrent ?
1686 14349 : ShareUpdateExclusiveLock : AccessExclusiveLock;
1687 : /* We must initialize these fields to show that no locks are held: */
1688 14349 : state.heapOid = InvalidOid;
1689 14349 : state.partParentOid = InvalidOid;
1690 :
1691 14349 : relOid = RangeVarGetRelidExtended(rel, lockmode, RVR_MISSING_OK,
1692 : RangeVarCallbackForDropRelation,
1693 : &state);
1694 :
1695 : /* Not there? */
1696 14332 : if (!OidIsValid(relOid))
1697 : {
1698 641 : DropErrorMsgNonExistent(rel, relkind, drop->missing_ok);
1699 551 : continue;
1700 : }
1701 :
1702 : /*
1703 : * Decide if concurrent mode needs to be used here or not. The
1704 : * callback retrieved the rel's persistence for us.
1705 : */
1706 13691 : if (drop->concurrent &&
1707 113 : state.actual_relpersistence != RELPERSISTENCE_TEMP)
1708 : {
1709 : Assert(list_length(drop->objects) == 1 &&
1710 : drop->removeType == OBJECT_INDEX);
1711 101 : flags |= PERFORM_DELETION_CONCURRENTLY;
1712 : }
1713 :
1714 : /*
1715 : * Concurrent index drop cannot be used with partitioned indexes,
1716 : * either.
1717 : */
1718 13691 : if ((flags & PERFORM_DELETION_CONCURRENTLY) != 0 &&
1719 101 : state.actual_relkind == RELKIND_PARTITIONED_INDEX)
1720 4 : ereport(ERROR,
1721 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1722 : errmsg("cannot drop partitioned index \"%s\" concurrently",
1723 : rel->relname)));
1724 :
1725 : /*
1726 : * If we're told to drop a partitioned index, we must acquire lock on
1727 : * all the children of its parent partitioned table before proceeding.
1728 : * Otherwise we'd try to lock the child index partitions before their
1729 : * tables, leading to potential deadlock against other sessions that
1730 : * will lock those objects in the other order.
1731 : */
1732 13687 : if (state.actual_relkind == RELKIND_PARTITIONED_INDEX)
1733 50 : (void) find_all_inheritors(state.heapOid,
1734 : state.heap_lockmode,
1735 : NULL);
1736 :
1737 : /* OK, we're ready to delete this one */
1738 13687 : obj.classId = RelationRelationId;
1739 13687 : obj.objectId = relOid;
1740 13687 : obj.objectSubId = 0;
1741 :
1742 13687 : add_exact_object_address(&obj, objects);
1743 : }
1744 :
1745 11712 : performMultipleDeletions(objects, drop->behavior, flags);
1746 :
1747 11609 : free_object_addresses(objects);
1748 11609 : }
1749 :
1750 : /*
1751 : * Before acquiring a table lock, check whether we have sufficient rights.
1752 : * In the case of DROP INDEX, also try to lock the table before the index.
1753 : * Also, if the table to be dropped is a partition, we try to lock the parent
1754 : * first.
1755 : */
1756 : static void
1757 14573 : RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
1758 : void *arg)
1759 : {
1760 : HeapTuple tuple;
1761 : struct DropRelationCallbackState *state;
1762 : char expected_relkind;
1763 : bool is_partition;
1764 : Form_pg_class classform;
1765 : LOCKMODE heap_lockmode;
1766 14573 : bool invalid_system_index = false;
1767 :
1768 14573 : state = (struct DropRelationCallbackState *) arg;
1769 14573 : heap_lockmode = state->heap_lockmode;
1770 :
1771 : /*
1772 : * If we previously locked some other index's heap, and the name we're
1773 : * looking up no longer refers to that relation, release the now-useless
1774 : * lock.
1775 : */
1776 14573 : if (relOid != oldRelOid && OidIsValid(state->heapOid))
1777 : {
1778 0 : UnlockRelationOid(state->heapOid, heap_lockmode);
1779 0 : state->heapOid = InvalidOid;
1780 : }
1781 :
1782 : /*
1783 : * Similarly, if we previously locked some other partition's heap, and the
1784 : * name we're looking up no longer refers to that relation, release the
1785 : * now-useless lock.
1786 : */
1787 14573 : if (relOid != oldRelOid && OidIsValid(state->partParentOid))
1788 : {
1789 0 : UnlockRelationOid(state->partParentOid, AccessExclusiveLock);
1790 0 : state->partParentOid = InvalidOid;
1791 : }
1792 :
1793 : /* Didn't find a relation, so no need for locking or permission checks. */
1794 14573 : if (!OidIsValid(relOid))
1795 646 : return;
1796 :
1797 13927 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
1798 13927 : if (!HeapTupleIsValid(tuple))
1799 0 : return; /* concurrently dropped, so nothing to do */
1800 13927 : classform = (Form_pg_class) GETSTRUCT(tuple);
1801 13927 : is_partition = classform->relispartition;
1802 :
1803 : /* Pass back some data to save lookups in RemoveRelations */
1804 13927 : state->actual_relkind = classform->relkind;
1805 13927 : state->actual_relpersistence = classform->relpersistence;
1806 :
1807 : /*
1808 : * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
1809 : * but RemoveRelations() can only pass one relkind for a given relation.
1810 : * It chooses RELKIND_RELATION for both regular and partitioned tables.
1811 : * That means we must be careful before giving the wrong type error when
1812 : * the relation is RELKIND_PARTITIONED_TABLE. An equivalent problem
1813 : * exists with indexes.
1814 : */
1815 13927 : if (classform->relkind == RELKIND_PARTITIONED_TABLE)
1816 2247 : expected_relkind = RELKIND_RELATION;
1817 11680 : else if (classform->relkind == RELKIND_PARTITIONED_INDEX)
1818 57 : expected_relkind = RELKIND_INDEX;
1819 : else
1820 11623 : expected_relkind = classform->relkind;
1821 :
1822 13927 : if (state->expected_relkind != expected_relkind)
1823 4 : DropErrorMsgWrongType(rel->relname, classform->relkind,
1824 4 : state->expected_relkind);
1825 :
1826 : /* Allow DROP to either table owner or schema owner */
1827 13923 : if (!object_ownercheck(RelationRelationId, relOid, GetUserId()) &&
1828 12 : !object_ownercheck(NamespaceRelationId, classform->relnamespace, GetUserId()))
1829 12 : aclcheck_error(ACLCHECK_NOT_OWNER,
1830 12 : get_relkind_objtype(classform->relkind),
1831 12 : rel->relname);
1832 :
1833 : /*
1834 : * Check the case of a system index that might have been invalidated by a
1835 : * failed concurrent process and allow its drop. For the time being, this
1836 : * only concerns indexes of toast relations that became invalid during a
1837 : * REINDEX CONCURRENTLY process.
1838 : */
1839 13911 : if (IsSystemClass(relOid, classform) && classform->relkind == RELKIND_INDEX)
1840 : {
1841 : HeapTuple locTuple;
1842 : Form_pg_index indexform;
1843 : bool indisvalid;
1844 :
1845 0 : locTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(relOid));
1846 0 : if (!HeapTupleIsValid(locTuple))
1847 : {
1848 0 : ReleaseSysCache(tuple);
1849 0 : return;
1850 : }
1851 :
1852 0 : indexform = (Form_pg_index) GETSTRUCT(locTuple);
1853 0 : indisvalid = indexform->indisvalid;
1854 0 : ReleaseSysCache(locTuple);
1855 :
1856 : /* Mark object as being an invalid index of system catalogs */
1857 0 : if (!indisvalid)
1858 0 : invalid_system_index = true;
1859 : }
1860 :
1861 : /* In the case of an invalid index, it is fine to bypass this check */
1862 13911 : if (!invalid_system_index && !allowSystemTableMods && IsSystemClass(relOid, classform))
1863 1 : ereport(ERROR,
1864 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1865 : errmsg("permission denied: \"%s\" is a system catalog",
1866 : rel->relname)));
1867 :
1868 13910 : ReleaseSysCache(tuple);
1869 :
1870 : /*
1871 : * In DROP INDEX, attempt to acquire lock on the parent table before
1872 : * locking the index. index_drop() will need this anyway, and since
1873 : * regular queries lock tables before their indexes, we risk deadlock if
1874 : * we do it the other way around. No error if we don't find a pg_index
1875 : * entry, though --- the relation may have been dropped. Note that this
1876 : * code will execute for either plain or partitioned indexes.
1877 : */
1878 13910 : if (expected_relkind == RELKIND_INDEX &&
1879 : relOid != oldRelOid)
1880 : {
1881 559 : state->heapOid = IndexGetRelation(relOid, true);
1882 559 : if (OidIsValid(state->heapOid))
1883 559 : LockRelationOid(state->heapOid, heap_lockmode);
1884 : }
1885 :
1886 : /*
1887 : * Similarly, if the relation is a partition, we must acquire lock on its
1888 : * parent before locking the partition. That's because queries lock the
1889 : * parent before its partitions, so we risk deadlock if we do it the other
1890 : * way around.
1891 : */
1892 13910 : if (is_partition && relOid != oldRelOid)
1893 : {
1894 397 : state->partParentOid = get_partition_parent(relOid, true);
1895 397 : if (OidIsValid(state->partParentOid))
1896 397 : LockRelationOid(state->partParentOid, AccessExclusiveLock);
1897 : }
1898 : }
1899 :
1900 : /*
1901 : * ExecuteTruncate
1902 : * Executes a TRUNCATE command.
1903 : *
1904 : * This is a multi-relation truncate. We first open and grab exclusive
1905 : * lock on all relations involved, checking permissions and otherwise
1906 : * verifying that the relation is OK for truncation. Note that if relations
1907 : * are foreign tables, at this stage, we have not yet checked that their
1908 : * foreign data in external data sources are OK for truncation. These are
1909 : * checked when foreign data are actually truncated later. In CASCADE mode,
1910 : * relations having FK references to the targeted relations are automatically
1911 : * added to the group; in RESTRICT mode, we check that all FK references are
1912 : * internal to the group that's being truncated. Finally all the relations
1913 : * are truncated and reindexed.
1914 : */
1915 : void
1916 1158 : ExecuteTruncate(TruncateStmt *stmt)
1917 : {
1918 1158 : List *rels = NIL;
1919 1158 : List *relids = NIL;
1920 1158 : List *relids_logged = NIL;
1921 : ListCell *cell;
1922 :
1923 : /*
1924 : * Open, exclusive-lock, and check all the explicitly-specified relations
1925 : */
1926 2476 : foreach(cell, stmt->relations)
1927 : {
1928 1354 : RangeVar *rv = lfirst(cell);
1929 : Relation rel;
1930 1354 : bool recurse = rv->inh;
1931 : Oid myrelid;
1932 1354 : LOCKMODE lockmode = AccessExclusiveLock;
1933 :
1934 1354 : myrelid = RangeVarGetRelidExtended(rv, lockmode,
1935 : 0, RangeVarCallbackForTruncate,
1936 : NULL);
1937 :
1938 : /* don't throw error for "TRUNCATE foo, foo" */
1939 1331 : if (list_member_oid(relids, myrelid))
1940 1 : continue;
1941 :
1942 : /* open the relation, we already hold a lock on it */
1943 1330 : rel = table_open(myrelid, NoLock);
1944 :
1945 : /*
1946 : * RangeVarGetRelidExtended() has done most checks with its callback,
1947 : * but other checks with the now-opened Relation remain.
1948 : */
1949 1330 : truncate_check_activity(rel);
1950 :
1951 1325 : rels = lappend(rels, rel);
1952 1325 : relids = lappend_oid(relids, myrelid);
1953 :
1954 : /* Log this relation only if needed for logical decoding */
1955 1325 : if (RelationIsLogicallyLogged(rel))
1956 39 : relids_logged = lappend_oid(relids_logged, myrelid);
1957 :
1958 1325 : if (recurse)
1959 : {
1960 : ListCell *child;
1961 : List *children;
1962 :
1963 1292 : children = find_all_inheritors(myrelid, lockmode, NULL);
1964 :
1965 3816 : foreach(child, children)
1966 : {
1967 2524 : Oid childrelid = lfirst_oid(child);
1968 :
1969 2524 : if (list_member_oid(relids, childrelid))
1970 1304 : continue;
1971 :
1972 : /* find_all_inheritors already got lock */
1973 1220 : rel = table_open(childrelid, NoLock);
1974 :
1975 : /*
1976 : * It is possible that the parent table has children that are
1977 : * temp tables of other backends. We cannot safely access
1978 : * such tables (because of buffering issues), and the best
1979 : * thing to do is to silently ignore them. Note that this
1980 : * check is the same as one of the checks done in
1981 : * truncate_check_activity() called below, still it is kept
1982 : * here for simplicity.
1983 : */
1984 1220 : if (RELATION_IS_OTHER_TEMP(rel))
1985 : {
1986 4 : table_close(rel, lockmode);
1987 4 : continue;
1988 : }
1989 :
1990 : /*
1991 : * Inherited TRUNCATE commands perform access permission
1992 : * checks on the parent table only. So we skip checking the
1993 : * children's permissions and don't call
1994 : * truncate_check_perms() here.
1995 : */
1996 1216 : truncate_check_rel(RelationGetRelid(rel), rel->rd_rel);
1997 1216 : truncate_check_activity(rel);
1998 :
1999 1216 : rels = lappend(rels, rel);
2000 1216 : relids = lappend_oid(relids, childrelid);
2001 :
2002 : /* Log this relation only if needed for logical decoding */
2003 1216 : if (RelationIsLogicallyLogged(rel))
2004 11 : relids_logged = lappend_oid(relids_logged, childrelid);
2005 : }
2006 : }
2007 33 : else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
2008 8 : ereport(ERROR,
2009 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2010 : errmsg("cannot truncate only a partitioned table"),
2011 : errhint("Do not specify the ONLY keyword, or use TRUNCATE ONLY on the partitions directly.")));
2012 : }
2013 :
2014 1122 : ExecuteTruncateGuts(rels, relids, relids_logged,
2015 1122 : stmt->behavior, stmt->restart_seqs, false);
2016 :
2017 : /* And close the rels */
2018 3501 : foreach(cell, rels)
2019 : {
2020 2432 : Relation rel = (Relation) lfirst(cell);
2021 :
2022 2432 : table_close(rel, NoLock);
2023 : }
2024 1069 : }
2025 :
2026 : /*
2027 : * ExecuteTruncateGuts
2028 : *
2029 : * Internal implementation of TRUNCATE. This is called by the actual TRUNCATE
2030 : * command (see above) as well as replication subscribers that execute a
2031 : * replicated TRUNCATE action.
2032 : *
2033 : * explicit_rels is the list of Relations to truncate that the command
2034 : * specified. relids is the list of Oids corresponding to explicit_rels.
2035 : * relids_logged is the list of Oids (a subset of relids) that require
2036 : * WAL-logging. This is all a bit redundant, but the existing callers have
2037 : * this information handy in this form.
2038 : */
2039 : void
2040 1142 : ExecuteTruncateGuts(List *explicit_rels,
2041 : List *relids,
2042 : List *relids_logged,
2043 : DropBehavior behavior, bool restart_seqs,
2044 : bool run_as_table_owner)
2045 : {
2046 : List *rels;
2047 1142 : List *seq_relids = NIL;
2048 1142 : HTAB *ft_htab = NULL;
2049 : EState *estate;
2050 : ResultRelInfo *resultRelInfos;
2051 : ResultRelInfo *resultRelInfo;
2052 : SubTransactionId mySubid;
2053 : ListCell *cell;
2054 : Oid *logrelids;
2055 :
2056 : /*
2057 : * Check the explicitly-specified relations.
2058 : *
2059 : * In CASCADE mode, suck in all referencing relations as well. This
2060 : * requires multiple iterations to find indirectly-dependent relations. At
2061 : * each phase, we need to exclusive-lock new rels before looking for their
2062 : * dependencies, else we might miss something. Also, we check each rel as
2063 : * soon as we open it, to avoid a faux pas such as holding lock for a long
2064 : * time on a rel we have no permissions for.
2065 : */
2066 1142 : rels = list_copy(explicit_rels);
2067 1142 : if (behavior == DROP_CASCADE)
2068 : {
2069 : for (;;)
2070 26 : {
2071 : List *newrelids;
2072 :
2073 51 : newrelids = heap_truncate_find_FKs(relids);
2074 51 : if (newrelids == NIL)
2075 25 : break; /* nothing else to add */
2076 :
2077 88 : foreach(cell, newrelids)
2078 : {
2079 62 : Oid relid = lfirst_oid(cell);
2080 : Relation rel;
2081 :
2082 62 : rel = table_open(relid, AccessExclusiveLock);
2083 62 : ereport(NOTICE,
2084 : (errmsg("truncate cascades to table \"%s\"",
2085 : RelationGetRelationName(rel))));
2086 62 : truncate_check_rel(relid, rel->rd_rel);
2087 62 : truncate_check_perms(relid, rel->rd_rel);
2088 62 : truncate_check_activity(rel);
2089 62 : rels = lappend(rels, rel);
2090 62 : relids = lappend_oid(relids, relid);
2091 :
2092 : /* Log this relation only if needed for logical decoding */
2093 62 : if (RelationIsLogicallyLogged(rel))
2094 0 : relids_logged = lappend_oid(relids_logged, relid);
2095 : }
2096 : }
2097 : }
2098 :
2099 : /*
2100 : * Check foreign key references. In CASCADE mode, this should be
2101 : * unnecessary since we just pulled in all the references; but as a
2102 : * cross-check, do it anyway if in an Assert-enabled build.
2103 : */
2104 : #ifdef USE_ASSERT_CHECKING
2105 : heap_truncate_check_FKs(rels, false);
2106 : #else
2107 1142 : if (behavior == DROP_RESTRICT)
2108 1117 : heap_truncate_check_FKs(rels, false);
2109 : #endif
2110 :
2111 : /*
2112 : * If we are asked to restart sequences, find all the sequences, lock them
2113 : * (we need AccessExclusiveLock for ResetSequence), and check permissions.
2114 : * We want to do this early since it's pointless to do all the truncation
2115 : * work only to fail on sequence permissions.
2116 : */
2117 1093 : if (restart_seqs)
2118 : {
2119 32 : foreach(cell, rels)
2120 : {
2121 16 : Relation rel = (Relation) lfirst(cell);
2122 16 : List *seqlist = getOwnedSequences(RelationGetRelid(rel));
2123 : ListCell *seqcell;
2124 :
2125 39 : foreach(seqcell, seqlist)
2126 : {
2127 23 : Oid seq_relid = lfirst_oid(seqcell);
2128 : Relation seq_rel;
2129 :
2130 23 : seq_rel = relation_open(seq_relid, AccessExclusiveLock);
2131 :
2132 : /* This check must match AlterSequence! */
2133 23 : if (!object_ownercheck(RelationRelationId, seq_relid, GetUserId()))
2134 0 : aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SEQUENCE,
2135 0 : RelationGetRelationName(seq_rel));
2136 :
2137 23 : seq_relids = lappend_oid(seq_relids, seq_relid);
2138 :
2139 23 : relation_close(seq_rel, NoLock);
2140 : }
2141 : }
2142 : }
2143 :
2144 : /* Prepare to catch AFTER triggers. */
2145 1093 : AfterTriggerBeginQuery();
2146 :
2147 : /*
2148 : * To fire triggers, we'll need an EState as well as a ResultRelInfo for
2149 : * each relation. We don't need to call ExecOpenIndices, though.
2150 : *
2151 : * We put the ResultRelInfos in the es_opened_result_relations list, even
2152 : * though we don't have a range table and don't populate the
2153 : * es_result_relations array. That's a bit bogus, but it's enough to make
2154 : * ExecGetTriggerResultRel() find them.
2155 : */
2156 1093 : estate = CreateExecutorState();
2157 : resultRelInfos = (ResultRelInfo *)
2158 1093 : palloc(list_length(rels) * sizeof(ResultRelInfo));
2159 1093 : resultRelInfo = resultRelInfos;
2160 3627 : foreach(cell, rels)
2161 : {
2162 2534 : Relation rel = (Relation) lfirst(cell);
2163 :
2164 2534 : InitResultRelInfo(resultRelInfo,
2165 : rel,
2166 : 0, /* dummy rangetable index */
2167 : NULL,
2168 : 0);
2169 2534 : estate->es_opened_result_relations =
2170 2534 : lappend(estate->es_opened_result_relations, resultRelInfo);
2171 2534 : resultRelInfo++;
2172 : }
2173 :
2174 : /*
2175 : * Process all BEFORE STATEMENT TRUNCATE triggers before we begin
2176 : * truncating (this is because one of them might throw an error). Also, if
2177 : * we were to allow them to prevent statement execution, that would need
2178 : * to be handled here.
2179 : */
2180 1093 : resultRelInfo = resultRelInfos;
2181 3627 : foreach(cell, rels)
2182 : {
2183 : UserContext ucxt;
2184 :
2185 2534 : if (run_as_table_owner)
2186 36 : SwitchToUntrustedUser(resultRelInfo->ri_RelationDesc->rd_rel->relowner,
2187 : &ucxt);
2188 2534 : ExecBSTruncateTriggers(estate, resultRelInfo);
2189 2534 : if (run_as_table_owner)
2190 36 : RestoreUserContext(&ucxt);
2191 2534 : resultRelInfo++;
2192 : }
2193 :
2194 : /*
2195 : * OK, truncate each table.
2196 : */
2197 1093 : mySubid = GetCurrentSubTransactionId();
2198 :
2199 3627 : foreach(cell, rels)
2200 : {
2201 2534 : Relation rel = (Relation) lfirst(cell);
2202 :
2203 : /* Skip partitioned tables as there is nothing to do */
2204 2534 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
2205 477 : continue;
2206 :
2207 : /*
2208 : * Build the lists of foreign tables belonging to each foreign server
2209 : * and pass each list to the foreign data wrapper's callback function,
2210 : * so that each server can truncate its all foreign tables in bulk.
2211 : * Each list is saved as a single entry in a hash table that uses the
2212 : * server OID as lookup key.
2213 : */
2214 2057 : if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
2215 17 : {
2216 17 : Oid serverid = GetForeignServerIdByRelId(RelationGetRelid(rel));
2217 : bool found;
2218 : ForeignTruncateInfo *ft_info;
2219 :
2220 : /* First time through, initialize hashtable for foreign tables */
2221 17 : if (!ft_htab)
2222 : {
2223 : HASHCTL hctl;
2224 :
2225 15 : memset(&hctl, 0, sizeof(HASHCTL));
2226 15 : hctl.keysize = sizeof(Oid);
2227 15 : hctl.entrysize = sizeof(ForeignTruncateInfo);
2228 15 : hctl.hcxt = CurrentMemoryContext;
2229 :
2230 15 : ft_htab = hash_create("TRUNCATE for Foreign Tables",
2231 : 32, /* start small and extend */
2232 : &hctl,
2233 : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
2234 : }
2235 :
2236 : /* Find or create cached entry for the foreign table */
2237 17 : ft_info = hash_search(ft_htab, &serverid, HASH_ENTER, &found);
2238 17 : if (!found)
2239 15 : ft_info->rels = NIL;
2240 :
2241 : /*
2242 : * Save the foreign table in the entry of the server that the
2243 : * foreign table belongs to.
2244 : */
2245 17 : ft_info->rels = lappend(ft_info->rels, rel);
2246 17 : continue;
2247 : }
2248 :
2249 : /*
2250 : * Normally, we need a transaction-safe truncation here. However, if
2251 : * the table was either created in the current (sub)transaction or has
2252 : * a new relfilenumber in the current (sub)transaction, then we can
2253 : * just truncate it in-place, because a rollback would cause the whole
2254 : * table or the current physical file to be thrown away anyway.
2255 : */
2256 2040 : if (rel->rd_createSubid == mySubid ||
2257 2024 : rel->rd_newRelfilelocatorSubid == mySubid)
2258 : {
2259 : /* Immediate, non-rollbackable truncation is OK */
2260 48 : heap_truncate_one_rel(rel);
2261 : }
2262 : else
2263 : {
2264 : Oid heap_relid;
2265 : Oid toast_relid;
2266 1992 : ReindexParams reindex_params = {0};
2267 :
2268 : /*
2269 : * This effectively deletes all rows in the table, and may be done
2270 : * in a serializable transaction. In that case we must record a
2271 : * rw-conflict in to this transaction from each transaction
2272 : * holding a predicate lock on the table.
2273 : */
2274 1992 : CheckTableForSerializableConflictIn(rel);
2275 :
2276 : /*
2277 : * Need the full transaction-safe pushups.
2278 : *
2279 : * Create a new empty storage file for the relation, and assign it
2280 : * as the relfilenumber value. The old storage file is scheduled
2281 : * for deletion at commit.
2282 : */
2283 1992 : RelationSetNewRelfilenumber(rel, rel->rd_rel->relpersistence);
2284 :
2285 1992 : heap_relid = RelationGetRelid(rel);
2286 :
2287 : /*
2288 : * The same for the toast table, if any.
2289 : */
2290 1992 : toast_relid = rel->rd_rel->reltoastrelid;
2291 1992 : if (OidIsValid(toast_relid))
2292 : {
2293 1213 : Relation toastrel = relation_open(toast_relid,
2294 : AccessExclusiveLock);
2295 :
2296 1213 : RelationSetNewRelfilenumber(toastrel,
2297 1213 : toastrel->rd_rel->relpersistence);
2298 1213 : table_close(toastrel, NoLock);
2299 : }
2300 :
2301 : /*
2302 : * Reconstruct the indexes to match, and we're done.
2303 : */
2304 1992 : reindex_relation(NULL, heap_relid, REINDEX_REL_PROCESS_TOAST,
2305 : &reindex_params);
2306 : }
2307 :
2308 2040 : pgstat_count_truncate(rel);
2309 : }
2310 :
2311 : /* Now go through the hash table, and truncate foreign tables */
2312 1093 : if (ft_htab)
2313 : {
2314 : ForeignTruncateInfo *ft_info;
2315 : HASH_SEQ_STATUS seq;
2316 :
2317 15 : hash_seq_init(&seq, ft_htab);
2318 :
2319 15 : PG_TRY();
2320 : {
2321 26 : while ((ft_info = hash_seq_search(&seq)) != NULL)
2322 : {
2323 15 : FdwRoutine *routine = GetFdwRoutineByServerId(ft_info->serverid);
2324 :
2325 : /* truncate_check_rel() has checked that already */
2326 : Assert(routine->ExecForeignTruncate != NULL);
2327 :
2328 15 : routine->ExecForeignTruncate(ft_info->rels,
2329 : behavior,
2330 : restart_seqs);
2331 : }
2332 : }
2333 4 : PG_FINALLY();
2334 : {
2335 15 : hash_destroy(ft_htab);
2336 : }
2337 15 : PG_END_TRY();
2338 : }
2339 :
2340 : /*
2341 : * Restart owned sequences if we were asked to.
2342 : */
2343 1112 : foreach(cell, seq_relids)
2344 : {
2345 23 : Oid seq_relid = lfirst_oid(cell);
2346 :
2347 23 : ResetSequence(seq_relid);
2348 : }
2349 :
2350 : /*
2351 : * Write a WAL record to allow this set of actions to be logically
2352 : * decoded.
2353 : *
2354 : * Assemble an array of relids so we can write a single WAL record for the
2355 : * whole action.
2356 : */
2357 1089 : if (relids_logged != NIL)
2358 : {
2359 : xl_heap_truncate xlrec;
2360 33 : int i = 0;
2361 :
2362 : /* should only get here if effective_wal_level is 'logical' */
2363 : Assert(XLogLogicalInfoActive());
2364 :
2365 33 : logrelids = palloc(list_length(relids_logged) * sizeof(Oid));
2366 84 : foreach(cell, relids_logged)
2367 51 : logrelids[i++] = lfirst_oid(cell);
2368 :
2369 33 : xlrec.dbId = MyDatabaseId;
2370 33 : xlrec.nrelids = list_length(relids_logged);
2371 33 : xlrec.flags = 0;
2372 33 : if (behavior == DROP_CASCADE)
2373 1 : xlrec.flags |= XLH_TRUNCATE_CASCADE;
2374 33 : if (restart_seqs)
2375 2 : xlrec.flags |= XLH_TRUNCATE_RESTART_SEQS;
2376 :
2377 33 : XLogBeginInsert();
2378 33 : XLogRegisterData(&xlrec, SizeOfHeapTruncate);
2379 33 : XLogRegisterData(logrelids, list_length(relids_logged) * sizeof(Oid));
2380 :
2381 33 : XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
2382 :
2383 33 : (void) XLogInsert(RM_HEAP_ID, XLOG_HEAP_TRUNCATE);
2384 : }
2385 :
2386 : /*
2387 : * Process all AFTER STATEMENT TRUNCATE triggers.
2388 : */
2389 1089 : resultRelInfo = resultRelInfos;
2390 3619 : foreach(cell, rels)
2391 : {
2392 : UserContext ucxt;
2393 :
2394 2530 : if (run_as_table_owner)
2395 36 : SwitchToUntrustedUser(resultRelInfo->ri_RelationDesc->rd_rel->relowner,
2396 : &ucxt);
2397 2530 : ExecASTruncateTriggers(estate, resultRelInfo);
2398 2530 : if (run_as_table_owner)
2399 36 : RestoreUserContext(&ucxt);
2400 2530 : resultRelInfo++;
2401 : }
2402 :
2403 : /* Handle queued AFTER triggers */
2404 1089 : AfterTriggerEndQuery(estate);
2405 :
2406 : /* We can clean up the EState now */
2407 1089 : FreeExecutorState(estate);
2408 :
2409 : /*
2410 : * Close any rels opened by CASCADE (can't do this while EState still
2411 : * holds refs)
2412 : */
2413 1089 : rels = list_difference_ptr(rels, explicit_rels);
2414 1151 : foreach(cell, rels)
2415 : {
2416 62 : Relation rel = (Relation) lfirst(cell);
2417 :
2418 62 : table_close(rel, NoLock);
2419 : }
2420 1089 : }
2421 :
2422 : /*
2423 : * Check that a given relation is safe to truncate. Subroutine for
2424 : * ExecuteTruncate() and RangeVarCallbackForTruncate().
2425 : */
2426 : static void
2427 2728 : truncate_check_rel(Oid relid, Form_pg_class reltuple)
2428 : {
2429 2728 : char *relname = NameStr(reltuple->relname);
2430 :
2431 : /*
2432 : * Only allow truncate on regular tables, foreign tables using foreign
2433 : * data wrappers supporting TRUNCATE and partitioned tables (although, the
2434 : * latter are only being included here for the following checks; no
2435 : * physical truncation will occur in their case.).
2436 : */
2437 2728 : if (reltuple->relkind == RELKIND_FOREIGN_TABLE)
2438 : {
2439 19 : Oid serverid = GetForeignServerIdByRelId(relid);
2440 19 : FdwRoutine *fdwroutine = GetFdwRoutineByServerId(serverid);
2441 :
2442 18 : if (!fdwroutine->ExecForeignTruncate)
2443 1 : ereport(ERROR,
2444 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2445 : errmsg("cannot truncate foreign table \"%s\"",
2446 : relname)));
2447 : }
2448 2709 : else if (reltuple->relkind != RELKIND_RELATION &&
2449 492 : reltuple->relkind != RELKIND_PARTITIONED_TABLE)
2450 0 : ereport(ERROR,
2451 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2452 : errmsg("\"%s\" is not a table", relname)));
2453 :
2454 : /*
2455 : * Most system catalogs can't be truncated at all, or at least not unless
2456 : * allow_system_table_mods=on. As an exception, however, we allow
2457 : * pg_largeobject and pg_largeobject_metadata to be truncated as part of
2458 : * pg_upgrade, because we need to change its relfilenode to match the old
2459 : * cluster, and allowing a TRUNCATE command to be executed is the easiest
2460 : * way of doing that.
2461 : */
2462 2726 : if (!allowSystemTableMods && IsSystemClass(relid, reltuple)
2463 65 : && (!IsBinaryUpgrade ||
2464 32 : (relid != LargeObjectRelationId &&
2465 : relid != LargeObjectMetadataRelationId)))
2466 1 : ereport(ERROR,
2467 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
2468 : errmsg("permission denied: \"%s\" is a system catalog",
2469 : relname)));
2470 :
2471 2725 : InvokeObjectTruncateHook(relid);
2472 2725 : }
2473 :
2474 : /*
2475 : * Check that current user has the permission to truncate given relation.
2476 : */
2477 : static void
2478 1509 : truncate_check_perms(Oid relid, Form_pg_class reltuple)
2479 : {
2480 1509 : char *relname = NameStr(reltuple->relname);
2481 : AclResult aclresult;
2482 :
2483 : /* Permissions checks */
2484 1509 : aclresult = pg_class_aclcheck(relid, GetUserId(), ACL_TRUNCATE);
2485 1509 : if (aclresult != ACLCHECK_OK)
2486 20 : aclcheck_error(aclresult, get_relkind_objtype(reltuple->relkind),
2487 : relname);
2488 1489 : }
2489 :
2490 : /*
2491 : * Set of extra sanity checks to check if a given relation is safe to
2492 : * truncate. This is split with truncate_check_rel() as
2493 : * RangeVarCallbackForTruncate() cannot open a Relation yet.
2494 : */
2495 : static void
2496 2608 : truncate_check_activity(Relation rel)
2497 : {
2498 : /*
2499 : * Don't allow truncate on temp tables of other backends ... their local
2500 : * buffer manager is not going to cope.
2501 : */
2502 2608 : if (RELATION_IS_OTHER_TEMP(rel))
2503 1 : ereport(ERROR,
2504 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2505 : errmsg("cannot truncate temporary tables of other sessions")));
2506 :
2507 : /*
2508 : * Also check for active uses of the relation in the current transaction,
2509 : * including open scans and pending AFTER trigger events.
2510 : */
2511 2607 : CheckTableNotInUse(rel, "TRUNCATE");
2512 2603 : }
2513 :
2514 : /*
2515 : * storage_name
2516 : * returns the name corresponding to a typstorage/attstorage enum value
2517 : */
2518 : static const char *
2519 16 : storage_name(char c)
2520 : {
2521 16 : switch (c)
2522 : {
2523 0 : case TYPSTORAGE_PLAIN:
2524 0 : return "PLAIN";
2525 0 : case TYPSTORAGE_EXTERNAL:
2526 0 : return "EXTERNAL";
2527 8 : case TYPSTORAGE_EXTENDED:
2528 8 : return "EXTENDED";
2529 8 : case TYPSTORAGE_MAIN:
2530 8 : return "MAIN";
2531 0 : default:
2532 0 : return "???";
2533 : }
2534 : }
2535 :
2536 : /*----------
2537 : * MergeAttributes
2538 : * Returns new schema given initial schema and superclasses.
2539 : *
2540 : * Input arguments:
2541 : * 'columns' is the column/attribute definition for the table. (It's a list
2542 : * of ColumnDef's.) It is destructively changed.
2543 : * 'supers' is a list of OIDs of parent relations, already locked by caller.
2544 : * 'relpersistence' is the persistence type of the table.
2545 : * 'is_partition' tells if the table is a partition.
2546 : *
2547 : * Output arguments:
2548 : * 'supconstr' receives a list of CookedConstraint representing
2549 : * CHECK constraints belonging to parent relations, updated as
2550 : * necessary to be valid for the child.
2551 : * 'supnotnulls' receives a list of CookedConstraint representing
2552 : * not-null constraints based on those from parent relations.
2553 : *
2554 : * Return value:
2555 : * Completed schema list.
2556 : *
2557 : * Notes:
2558 : * The order in which the attributes are inherited is very important.
2559 : * Intuitively, the inherited attributes should come first. If a table
2560 : * inherits from multiple parents, the order of those attributes are
2561 : * according to the order of the parents specified in CREATE TABLE.
2562 : *
2563 : * Here's an example:
2564 : *
2565 : * create table person (name text, age int4, location point);
2566 : * create table emp (salary int4, manager text) inherits(person);
2567 : * create table student (gpa float8) inherits (person);
2568 : * create table stud_emp (percent int4) inherits (emp, student);
2569 : *
2570 : * The order of the attributes of stud_emp is:
2571 : *
2572 : * person {1:name, 2:age, 3:location}
2573 : * / \
2574 : * {6:gpa} student emp {4:salary, 5:manager}
2575 : * \ /
2576 : * stud_emp {7:percent}
2577 : *
2578 : * If the same attribute name appears multiple times, then it appears
2579 : * in the result table in the proper location for its first appearance.
2580 : *
2581 : * Constraints (including not-null constraints) for the child table
2582 : * are the union of all relevant constraints, from both the child schema
2583 : * and parent tables. In addition, in legacy inheritance, each column that
2584 : * appears in a primary key in any of the parents also gets a NOT NULL
2585 : * constraint (partitioning doesn't need this, because the PK itself gets
2586 : * inherited.)
2587 : *
2588 : * The default value for a child column is defined as:
2589 : * (1) If the child schema specifies a default, that value is used.
2590 : * (2) If neither the child nor any parent specifies a default, then
2591 : * the column will not have a default.
2592 : * (3) If conflicting defaults are inherited from different parents
2593 : * (and not overridden by the child), an error is raised.
2594 : * (4) Otherwise the inherited default is used.
2595 : *
2596 : * Note that the default-value infrastructure is used for generated
2597 : * columns' expressions too, so most of the preceding paragraph applies
2598 : * to generation expressions too. We insist that a child column be
2599 : * generated if and only if its parent(s) are, but it need not have
2600 : * the same generation expression.
2601 : *----------
2602 : */
2603 : static List *
2604 41960 : MergeAttributes(List *columns, const List *supers, char relpersistence,
2605 : bool is_partition, List **supconstr, List **supnotnulls)
2606 : {
2607 41960 : List *inh_columns = NIL;
2608 41960 : List *constraints = NIL;
2609 41960 : List *nnconstraints = NIL;
2610 41960 : bool have_bogus_defaults = false;
2611 : int child_attno;
2612 : static Node bogus_marker = {0}; /* marks conflicting defaults */
2613 41960 : List *saved_columns = NIL;
2614 : ListCell *lc;
2615 :
2616 : /*
2617 : * Check for and reject tables with too many columns. We perform this
2618 : * check relatively early for two reasons: (a) we don't run the risk of
2619 : * overflowing an AttrNumber in subsequent code (b) an O(n^2) algorithm is
2620 : * okay if we're processing <= 1600 columns, but could take minutes to
2621 : * execute if the user attempts to create a table with hundreds of
2622 : * thousands of columns.
2623 : *
2624 : * Note that we also need to check that we do not exceed this figure after
2625 : * including columns from inherited relations.
2626 : */
2627 41960 : if (list_length(columns) > MaxHeapAttributeNumber)
2628 0 : ereport(ERROR,
2629 : (errcode(ERRCODE_TOO_MANY_COLUMNS),
2630 : errmsg("tables can have at most %d columns",
2631 : MaxHeapAttributeNumber)));
2632 :
2633 : /*
2634 : * Check for duplicate names in the explicit list of attributes.
2635 : *
2636 : * Although we might consider merging such entries in the same way that we
2637 : * handle name conflicts for inherited attributes, it seems to make more
2638 : * sense to assume such conflicts are errors.
2639 : *
2640 : * We don't use foreach() here because we have two nested loops over the
2641 : * columns list, with possible element deletions in the inner one. If we
2642 : * used foreach_delete_current() it could only fix up the state of one of
2643 : * the loops, so it seems cleaner to use looping over list indexes for
2644 : * both loops. Note that any deletion will happen beyond where the outer
2645 : * loop is, so its index never needs adjustment.
2646 : */
2647 193669 : for (int coldefpos = 0; coldefpos < list_length(columns); coldefpos++)
2648 : {
2649 151725 : ColumnDef *coldef = list_nth_node(ColumnDef, columns, coldefpos);
2650 :
2651 151725 : if (!is_partition && coldef->typeName == NULL)
2652 : {
2653 : /*
2654 : * Typed table column option that does not belong to a column from
2655 : * the type. This works because the columns from the type come
2656 : * first in the list. (We omit this check for partition column
2657 : * lists; those are processed separately below.)
2658 : */
2659 4 : ereport(ERROR,
2660 : (errcode(ERRCODE_UNDEFINED_COLUMN),
2661 : errmsg("column \"%s\" does not exist",
2662 : coldef->colname)));
2663 : }
2664 :
2665 : /* restpos scans all entries beyond coldef; incr is in loop body */
2666 4108127 : for (int restpos = coldefpos + 1; restpos < list_length(columns);)
2667 : {
2668 3956418 : ColumnDef *restdef = list_nth_node(ColumnDef, columns, restpos);
2669 :
2670 3956418 : if (strcmp(coldef->colname, restdef->colname) == 0)
2671 : {
2672 33 : if (coldef->is_from_type)
2673 : {
2674 : /*
2675 : * merge the column options into the column from the type
2676 : */
2677 21 : coldef->is_not_null = restdef->is_not_null;
2678 21 : coldef->raw_default = restdef->raw_default;
2679 21 : coldef->cooked_default = restdef->cooked_default;
2680 21 : coldef->constraints = restdef->constraints;
2681 21 : coldef->is_from_type = false;
2682 21 : columns = list_delete_nth_cell(columns, restpos);
2683 : }
2684 : else
2685 12 : ereport(ERROR,
2686 : (errcode(ERRCODE_DUPLICATE_COLUMN),
2687 : errmsg("column \"%s\" specified more than once",
2688 : coldef->colname)));
2689 : }
2690 : else
2691 3956385 : restpos++;
2692 : }
2693 : }
2694 :
2695 : /*
2696 : * In case of a partition, there are no new column definitions, only dummy
2697 : * ColumnDefs created for column constraints. Set them aside for now and
2698 : * process them at the end.
2699 : */
2700 41944 : if (is_partition)
2701 : {
2702 6532 : saved_columns = columns;
2703 6532 : columns = NIL;
2704 : }
2705 :
2706 : /*
2707 : * Scan the parents left-to-right, and merge their attributes to form a
2708 : * list of inherited columns (inh_columns).
2709 : */
2710 41944 : child_attno = 0;
2711 50061 : foreach(lc, supers)
2712 : {
2713 8173 : Oid parent = lfirst_oid(lc);
2714 : Relation relation;
2715 : TupleDesc tupleDesc;
2716 : TupleConstr *constr;
2717 : AttrMap *newattmap;
2718 : List *inherited_defaults;
2719 : List *cols_with_defaults;
2720 : List *nnconstrs;
2721 : ListCell *lc1;
2722 : ListCell *lc2;
2723 8173 : Bitmapset *nncols = NULL;
2724 :
2725 : /* caller already got lock */
2726 8173 : relation = table_open(parent, NoLock);
2727 :
2728 : /*
2729 : * Check for active uses of the parent partitioned table in the
2730 : * current transaction, such as being used in some manner by an
2731 : * enclosing command.
2732 : */
2733 8173 : if (is_partition)
2734 6532 : CheckTableNotInUse(relation, "CREATE TABLE .. PARTITION OF");
2735 :
2736 : /*
2737 : * We do not allow partitioned tables and partitions to participate in
2738 : * regular inheritance.
2739 : */
2740 8169 : if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !is_partition)
2741 4 : ereport(ERROR,
2742 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2743 : errmsg("cannot inherit from partitioned table \"%s\"",
2744 : RelationGetRelationName(relation))));
2745 8165 : if (relation->rd_rel->relispartition && !is_partition)
2746 4 : ereport(ERROR,
2747 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2748 : errmsg("cannot inherit from partition \"%s\"",
2749 : RelationGetRelationName(relation))));
2750 :
2751 8161 : if (relation->rd_rel->relkind != RELKIND_RELATION &&
2752 6528 : relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
2753 6516 : relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
2754 0 : ereport(ERROR,
2755 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2756 : errmsg("inherited relation \"%s\" is not a table or foreign table",
2757 : RelationGetRelationName(relation))));
2758 :
2759 : /*
2760 : * If the parent is permanent, so must be all of its partitions. Note
2761 : * that inheritance allows that case.
2762 : */
2763 8161 : if (is_partition &&
2764 6528 : relation->rd_rel->relpersistence != RELPERSISTENCE_TEMP &&
2765 : relpersistence == RELPERSISTENCE_TEMP)
2766 4 : ereport(ERROR,
2767 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2768 : errmsg("cannot create a temporary relation as partition of permanent relation \"%s\"",
2769 : RelationGetRelationName(relation))));
2770 :
2771 : /* Permanent rels cannot inherit from temporary ones */
2772 8157 : if (relpersistence != RELPERSISTENCE_TEMP &&
2773 7887 : relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
2774 16 : ereport(ERROR,
2775 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2776 : errmsg(!is_partition
2777 : ? "cannot inherit from temporary relation \"%s\""
2778 : : "cannot create a permanent relation as partition of temporary relation \"%s\"",
2779 : RelationGetRelationName(relation))));
2780 :
2781 : /* If existing rel is temp, it must belong to this session */
2782 8141 : if (RELATION_IS_OTHER_TEMP(relation))
2783 0 : ereport(ERROR,
2784 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2785 : errmsg(!is_partition
2786 : ? "cannot inherit from temporary relation of another session"
2787 : : "cannot create as partition of temporary relation of another session")));
2788 :
2789 : /*
2790 : * We should have an UNDER permission flag for this, but for now,
2791 : * demand that creator of a child table own the parent.
2792 : */
2793 8141 : if (!object_ownercheck(RelationRelationId, RelationGetRelid(relation), GetUserId()))
2794 0 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(relation->rd_rel->relkind),
2795 0 : RelationGetRelationName(relation));
2796 :
2797 8141 : tupleDesc = RelationGetDescr(relation);
2798 8141 : constr = tupleDesc->constr;
2799 :
2800 : /*
2801 : * newattmap->attnums[] will contain the child-table attribute numbers
2802 : * for the attributes of this parent table. (They are not the same
2803 : * for parents after the first one, nor if we have dropped columns.)
2804 : */
2805 8141 : newattmap = make_attrmap(tupleDesc->natts);
2806 :
2807 : /* We can't process inherited defaults until newattmap is complete. */
2808 8141 : inherited_defaults = cols_with_defaults = NIL;
2809 :
2810 : /*
2811 : * Request attnotnull on columns that have a not-null constraint
2812 : * that's not marked NO INHERIT (even if not valid).
2813 : */
2814 8141 : nnconstrs = RelationGetNotNullConstraints(RelationGetRelid(relation),
2815 : true, false);
2816 18020 : foreach_ptr(CookedConstraint, cc, nnconstrs)
2817 1738 : nncols = bms_add_member(nncols, cc->attnum);
2818 :
2819 24348 : for (AttrNumber parent_attno = 1; parent_attno <= tupleDesc->natts;
2820 16207 : parent_attno++)
2821 : {
2822 16231 : Form_pg_attribute attribute = TupleDescAttr(tupleDesc,
2823 : parent_attno - 1);
2824 16231 : char *attributeName = NameStr(attribute->attname);
2825 : int exist_attno;
2826 : ColumnDef *newdef;
2827 : ColumnDef *mergeddef;
2828 :
2829 : /*
2830 : * Ignore dropped columns in the parent.
2831 : */
2832 16231 : if (attribute->attisdropped)
2833 132 : continue; /* leave newattmap->attnums entry as zero */
2834 :
2835 : /*
2836 : * Create new column definition
2837 : */
2838 16099 : newdef = makeColumnDef(attributeName, attribute->atttypid,
2839 : attribute->atttypmod, attribute->attcollation);
2840 16099 : newdef->storage = attribute->attstorage;
2841 16099 : newdef->generated = attribute->attgenerated;
2842 16099 : if (CompressionMethodIsValid(attribute->attcompression))
2843 24 : newdef->compression =
2844 24 : pstrdup(GetCompressionMethodName(attribute->attcompression));
2845 :
2846 : /*
2847 : * Regular inheritance children are independent enough not to
2848 : * inherit identity columns. But partitions are integral part of
2849 : * a partitioned table and inherit identity column.
2850 : */
2851 16099 : if (is_partition)
2852 13121 : newdef->identity = attribute->attidentity;
2853 :
2854 : /*
2855 : * Does it match some previously considered column from another
2856 : * parent?
2857 : */
2858 16099 : exist_attno = findAttrByName(attributeName, inh_columns);
2859 16099 : if (exist_attno > 0)
2860 : {
2861 : /*
2862 : * Yes, try to merge the two column definitions.
2863 : */
2864 245 : mergeddef = MergeInheritedAttribute(inh_columns, exist_attno, newdef);
2865 :
2866 221 : newattmap->attnums[parent_attno - 1] = exist_attno;
2867 :
2868 : /*
2869 : * Partitions have only one parent, so conflict should never
2870 : * occur.
2871 : */
2872 : Assert(!is_partition);
2873 : }
2874 : else
2875 : {
2876 : /*
2877 : * No, create a new inherited column
2878 : */
2879 15854 : newdef->inhcount = 1;
2880 15854 : newdef->is_local = false;
2881 15854 : inh_columns = lappend(inh_columns, newdef);
2882 :
2883 15854 : newattmap->attnums[parent_attno - 1] = ++child_attno;
2884 15854 : mergeddef = newdef;
2885 : }
2886 :
2887 : /*
2888 : * mark attnotnull if parent has it
2889 : */
2890 16075 : if (bms_is_member(parent_attno, nncols))
2891 1738 : mergeddef->is_not_null = true;
2892 :
2893 : /*
2894 : * Locate default/generation expression if any
2895 : */
2896 16075 : if (attribute->atthasdef)
2897 : {
2898 : Node *this_default;
2899 :
2900 554 : this_default = TupleDescGetDefault(tupleDesc, parent_attno);
2901 554 : if (this_default == NULL)
2902 0 : elog(ERROR, "default expression not found for attribute %d of relation \"%s\"",
2903 : parent_attno, RelationGetRelationName(relation));
2904 :
2905 : /*
2906 : * If it's a GENERATED default, it might contain Vars that
2907 : * need to be mapped to the inherited column(s)' new numbers.
2908 : * We can't do that till newattmap is ready, so just remember
2909 : * all the inherited default expressions for the moment.
2910 : */
2911 554 : inherited_defaults = lappend(inherited_defaults, this_default);
2912 554 : cols_with_defaults = lappend(cols_with_defaults, mergeddef);
2913 : }
2914 : }
2915 :
2916 : /*
2917 : * Now process any inherited default expressions, adjusting attnos
2918 : * using the completed newattmap map.
2919 : */
2920 8671 : forboth(lc1, inherited_defaults, lc2, cols_with_defaults)
2921 : {
2922 554 : Node *this_default = (Node *) lfirst(lc1);
2923 554 : ColumnDef *def = (ColumnDef *) lfirst(lc2);
2924 : bool found_whole_row;
2925 :
2926 : /* Adjust Vars to match new table's column numbering */
2927 554 : this_default = map_variable_attnos(this_default,
2928 : 1, 0,
2929 : newattmap,
2930 : InvalidOid, &found_whole_row);
2931 :
2932 : /*
2933 : * For the moment we have to reject whole-row variables. We could
2934 : * convert them, if we knew the new table's rowtype OID, but that
2935 : * hasn't been assigned yet. (A variable could only appear in a
2936 : * generation expression, so the error message is correct.)
2937 : */
2938 554 : if (found_whole_row)
2939 0 : ereport(ERROR,
2940 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2941 : errmsg("cannot convert whole-row table reference"),
2942 : errdetail("Generation expression for column \"%s\" contains a whole-row reference to table \"%s\".",
2943 : def->colname,
2944 : RelationGetRelationName(relation))));
2945 :
2946 : /*
2947 : * If we already had a default from some prior parent, check to
2948 : * see if they are the same. If so, no problem; if not, mark the
2949 : * column as having a bogus default. Below, we will complain if
2950 : * the bogus default isn't overridden by the child columns.
2951 : */
2952 : Assert(def->raw_default == NULL);
2953 554 : if (def->cooked_default == NULL)
2954 526 : def->cooked_default = this_default;
2955 28 : else if (!equal(def->cooked_default, this_default))
2956 : {
2957 24 : def->cooked_default = &bogus_marker;
2958 24 : have_bogus_defaults = true;
2959 : }
2960 : }
2961 :
2962 : /*
2963 : * Now copy the CHECK constraints of this parent, adjusting attnos
2964 : * using the completed newattmap map. Identically named constraints
2965 : * are merged if possible, else we throw error.
2966 : */
2967 8117 : if (constr && constr->num_check > 0)
2968 : {
2969 245 : ConstrCheck *check = constr->check;
2970 :
2971 770 : for (int i = 0; i < constr->num_check; i++)
2972 : {
2973 525 : char *name = check[i].ccname;
2974 : Node *expr;
2975 : bool found_whole_row;
2976 :
2977 : /* ignore if the constraint is non-inheritable */
2978 525 : if (check[i].ccnoinherit)
2979 32 : continue;
2980 :
2981 : /* Adjust Vars to match new table's column numbering */
2982 493 : expr = map_variable_attnos(stringToNode(check[i].ccbin),
2983 : 1, 0,
2984 : newattmap,
2985 : InvalidOid, &found_whole_row);
2986 :
2987 : /*
2988 : * For the moment we have to reject whole-row variables. We
2989 : * could convert them, if we knew the new table's rowtype OID,
2990 : * but that hasn't been assigned yet.
2991 : */
2992 493 : if (found_whole_row)
2993 0 : ereport(ERROR,
2994 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2995 : errmsg("cannot convert whole-row table reference"),
2996 : errdetail("Constraint \"%s\" contains a whole-row reference to table \"%s\".",
2997 : name,
2998 : RelationGetRelationName(relation))));
2999 :
3000 493 : constraints = MergeCheckConstraint(constraints, name, expr,
3001 493 : check[i].ccenforced);
3002 : }
3003 : }
3004 :
3005 : /*
3006 : * Also copy the not-null constraints from this parent. The
3007 : * attnotnull markings were already installed above.
3008 : */
3009 17972 : foreach_ptr(CookedConstraint, nn, nnconstrs)
3010 : {
3011 : Assert(nn->contype == CONSTR_NOTNULL);
3012 :
3013 1738 : nn->attnum = newattmap->attnums[nn->attnum - 1];
3014 :
3015 1738 : nnconstraints = lappend(nnconstraints, nn);
3016 : }
3017 :
3018 8117 : free_attrmap(newattmap);
3019 :
3020 : /*
3021 : * Close the parent rel, but keep our lock on it until xact commit.
3022 : * That will prevent someone else from deleting or ALTERing the parent
3023 : * before the child is committed.
3024 : */
3025 8117 : table_close(relation, NoLock);
3026 : }
3027 :
3028 : /*
3029 : * If we had no inherited attributes, the result columns are just the
3030 : * explicitly declared columns. Otherwise, we need to merge the declared
3031 : * columns into the inherited column list. Although, we never have any
3032 : * explicitly declared columns if the table is a partition.
3033 : */
3034 41888 : if (inh_columns != NIL)
3035 : {
3036 7823 : int newcol_attno = 0;
3037 :
3038 8475 : foreach(lc, columns)
3039 : {
3040 704 : ColumnDef *newdef = lfirst_node(ColumnDef, lc);
3041 704 : char *attributeName = newdef->colname;
3042 : int exist_attno;
3043 :
3044 : /*
3045 : * Partitions have only one parent and have no column definitions
3046 : * of their own, so conflict should never occur.
3047 : */
3048 : Assert(!is_partition);
3049 :
3050 704 : newcol_attno++;
3051 :
3052 : /*
3053 : * Does it match some inherited column?
3054 : */
3055 704 : exist_attno = findAttrByName(attributeName, inh_columns);
3056 704 : if (exist_attno > 0)
3057 : {
3058 : /*
3059 : * Yes, try to merge the two column definitions.
3060 : */
3061 251 : MergeChildAttribute(inh_columns, exist_attno, newcol_attno, newdef);
3062 : }
3063 : else
3064 : {
3065 : /*
3066 : * No, attach new column unchanged to result columns.
3067 : */
3068 453 : inh_columns = lappend(inh_columns, newdef);
3069 : }
3070 : }
3071 :
3072 7771 : columns = inh_columns;
3073 :
3074 : /*
3075 : * Check that we haven't exceeded the legal # of columns after merging
3076 : * in inherited columns.
3077 : */
3078 7771 : if (list_length(columns) > MaxHeapAttributeNumber)
3079 0 : ereport(ERROR,
3080 : (errcode(ERRCODE_TOO_MANY_COLUMNS),
3081 : errmsg("tables can have at most %d columns",
3082 : MaxHeapAttributeNumber)));
3083 : }
3084 :
3085 : /*
3086 : * Now that we have the column definition list for a partition, we can
3087 : * check whether the columns referenced in the column constraint specs
3088 : * actually exist. Also, merge column defaults.
3089 : */
3090 41836 : if (is_partition)
3091 : {
3092 6639 : foreach(lc, saved_columns)
3093 : {
3094 151 : ColumnDef *restdef = lfirst(lc);
3095 151 : bool found = false;
3096 : ListCell *l;
3097 :
3098 549 : foreach(l, columns)
3099 : {
3100 422 : ColumnDef *coldef = lfirst(l);
3101 :
3102 422 : if (strcmp(coldef->colname, restdef->colname) == 0)
3103 : {
3104 151 : found = true;
3105 :
3106 : /*
3107 : * Check for conflicts related to generated columns.
3108 : *
3109 : * Same rules as above: generated-ness has to match the
3110 : * parent, but the contents of the generation expression
3111 : * can be different.
3112 : */
3113 151 : if (coldef->generated)
3114 : {
3115 80 : if (restdef->raw_default && !restdef->generated)
3116 8 : ereport(ERROR,
3117 : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3118 : errmsg("column \"%s\" inherits from generated column but specifies default",
3119 : restdef->colname)));
3120 72 : if (restdef->identity)
3121 0 : ereport(ERROR,
3122 : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3123 : errmsg("column \"%s\" inherits from generated column but specifies identity",
3124 : restdef->colname)));
3125 : }
3126 : else
3127 : {
3128 71 : if (restdef->generated)
3129 8 : ereport(ERROR,
3130 : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3131 : errmsg("child column \"%s\" specifies generation expression",
3132 : restdef->colname),
3133 : errhint("A child table column cannot be generated unless its parent column is.")));
3134 : }
3135 :
3136 135 : if (coldef->generated && restdef->generated && coldef->generated != restdef->generated)
3137 8 : ereport(ERROR,
3138 : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3139 : errmsg("column \"%s\" inherits from generated column of different kind",
3140 : restdef->colname),
3141 : errdetail("Parent column is %s, child column is %s.",
3142 : coldef->generated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL",
3143 : restdef->generated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL")));
3144 :
3145 : /*
3146 : * Override the parent's default value for this column
3147 : * (coldef->cooked_default) with the partition's local
3148 : * definition (restdef->raw_default), if there's one. It
3149 : * should be physically impossible to get a cooked default
3150 : * in the local definition or a raw default in the
3151 : * inherited definition, but make sure they're nulls, for
3152 : * future-proofing.
3153 : */
3154 : Assert(restdef->cooked_default == NULL);
3155 : Assert(coldef->raw_default == NULL);
3156 127 : if (restdef->raw_default)
3157 : {
3158 79 : coldef->raw_default = restdef->raw_default;
3159 79 : coldef->cooked_default = NULL;
3160 : }
3161 : }
3162 : }
3163 :
3164 : /* complain for constraints on columns not in parent */
3165 127 : if (!found)
3166 0 : ereport(ERROR,
3167 : (errcode(ERRCODE_UNDEFINED_COLUMN),
3168 : errmsg("column \"%s\" does not exist",
3169 : restdef->colname)));
3170 : }
3171 : }
3172 :
3173 : /*
3174 : * If we found any conflicting parent default values, check to make sure
3175 : * they were overridden by the child.
3176 : */
3177 41812 : if (have_bogus_defaults)
3178 : {
3179 60 : foreach(lc, columns)
3180 : {
3181 48 : ColumnDef *def = lfirst(lc);
3182 :
3183 48 : if (def->cooked_default == &bogus_marker)
3184 : {
3185 12 : if (def->generated)
3186 8 : ereport(ERROR,
3187 : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3188 : errmsg("column \"%s\" inherits conflicting generation expressions",
3189 : def->colname),
3190 : errhint("To resolve the conflict, specify a generation expression explicitly.")));
3191 : else
3192 4 : ereport(ERROR,
3193 : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3194 : errmsg("column \"%s\" inherits conflicting default values",
3195 : def->colname),
3196 : errhint("To resolve the conflict, specify a default explicitly.")));
3197 : }
3198 : }
3199 : }
3200 :
3201 41800 : *supconstr = constraints;
3202 41800 : *supnotnulls = nnconstraints;
3203 :
3204 41800 : return columns;
3205 : }
3206 :
3207 :
3208 : /*
3209 : * MergeCheckConstraint
3210 : * Try to merge an inherited CHECK constraint with previous ones
3211 : *
3212 : * If we inherit identically-named constraints from multiple parents, we must
3213 : * merge them, or throw an error if they don't have identical definitions.
3214 : *
3215 : * constraints is a list of CookedConstraint structs for previous constraints.
3216 : *
3217 : * If the new constraint matches an existing one, then the existing
3218 : * constraint's inheritance count is updated. If there is a conflict (same
3219 : * name but different expression), throw an error. If the constraint neither
3220 : * matches nor conflicts with an existing one, a new constraint is appended to
3221 : * the list.
3222 : */
3223 : static List *
3224 493 : MergeCheckConstraint(List *constraints, const char *name, Node *expr, bool is_enforced)
3225 : {
3226 : ListCell *lc;
3227 : CookedConstraint *newcon;
3228 :
3229 1521 : foreach(lc, constraints)
3230 : {
3231 1128 : CookedConstraint *ccon = (CookedConstraint *) lfirst(lc);
3232 :
3233 : Assert(ccon->contype == CONSTR_CHECK);
3234 :
3235 : /* Non-matching names never conflict */
3236 1128 : if (strcmp(ccon->name, name) != 0)
3237 1028 : continue;
3238 :
3239 100 : if (equal(expr, ccon->expr))
3240 : {
3241 : /* OK to merge constraint with existing */
3242 100 : if (pg_add_s16_overflow(ccon->inhcount, 1,
3243 : &ccon->inhcount))
3244 0 : ereport(ERROR,
3245 : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
3246 : errmsg("too many inheritance parents"));
3247 :
3248 : /*
3249 : * When enforceability differs, the merged constraint should be
3250 : * marked as ENFORCED because one of the parents is ENFORCED.
3251 : */
3252 100 : if (!ccon->is_enforced && is_enforced)
3253 : {
3254 32 : ccon->is_enforced = true;
3255 32 : ccon->skip_validation = false;
3256 : }
3257 :
3258 100 : return constraints;
3259 : }
3260 :
3261 0 : ereport(ERROR,
3262 : (errcode(ERRCODE_DUPLICATE_OBJECT),
3263 : errmsg("check constraint name \"%s\" appears multiple times but with different expressions",
3264 : name)));
3265 : }
3266 :
3267 : /*
3268 : * Constraint couldn't be merged with an existing one and also didn't
3269 : * conflict with an existing one, so add it as a new one to the list.
3270 : */
3271 393 : newcon = palloc0_object(CookedConstraint);
3272 393 : newcon->contype = CONSTR_CHECK;
3273 393 : newcon->name = pstrdup(name);
3274 393 : newcon->expr = expr;
3275 393 : newcon->inhcount = 1;
3276 393 : newcon->is_enforced = is_enforced;
3277 393 : newcon->skip_validation = !is_enforced;
3278 393 : return lappend(constraints, newcon);
3279 : }
3280 :
3281 : /*
3282 : * MergeChildAttribute
3283 : * Merge given child attribute definition into given inherited attribute.
3284 : *
3285 : * Input arguments:
3286 : * 'inh_columns' is the list of inherited ColumnDefs.
3287 : * 'exist_attno' is the number of the inherited attribute in inh_columns
3288 : * 'newcol_attno' is the attribute number in child table's schema definition
3289 : * 'newdef' is the column/attribute definition from the child table.
3290 : *
3291 : * The ColumnDef in 'inh_columns' list is modified. The child attribute's
3292 : * ColumnDef remains unchanged.
3293 : *
3294 : * Notes:
3295 : * - The attribute is merged according to the rules laid out in the prologue
3296 : * of MergeAttributes().
3297 : * - If matching inherited attribute exists but the child attribute can not be
3298 : * merged into it, the function throws respective errors.
3299 : * - A partition can not have its own column definitions. Hence this function
3300 : * is applicable only to a regular inheritance child.
3301 : */
3302 : static void
3303 251 : MergeChildAttribute(List *inh_columns, int exist_attno, int newcol_attno, const ColumnDef *newdef)
3304 : {
3305 251 : char *attributeName = newdef->colname;
3306 : ColumnDef *inhdef;
3307 : Oid inhtypeid,
3308 : newtypeid;
3309 : int32 inhtypmod,
3310 : newtypmod;
3311 : Oid inhcollid,
3312 : newcollid;
3313 :
3314 251 : if (exist_attno == newcol_attno)
3315 229 : ereport(NOTICE,
3316 : (errmsg("merging column \"%s\" with inherited definition",
3317 : attributeName)));
3318 : else
3319 22 : ereport(NOTICE,
3320 : (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
3321 : errdetail("User-specified column moved to the position of the inherited column.")));
3322 :
3323 251 : inhdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
3324 :
3325 : /*
3326 : * Must have the same type and typmod
3327 : */
3328 251 : typenameTypeIdAndMod(NULL, inhdef->typeName, &inhtypeid, &inhtypmod);
3329 251 : typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
3330 251 : if (inhtypeid != newtypeid || inhtypmod != newtypmod)
3331 8 : ereport(ERROR,
3332 : (errcode(ERRCODE_DATATYPE_MISMATCH),
3333 : errmsg("column \"%s\" has a type conflict",
3334 : attributeName),
3335 : errdetail("%s versus %s",
3336 : format_type_with_typemod(inhtypeid, inhtypmod),
3337 : format_type_with_typemod(newtypeid, newtypmod))));
3338 :
3339 : /*
3340 : * Must have the same collation
3341 : */
3342 243 : inhcollid = GetColumnDefCollation(NULL, inhdef, inhtypeid);
3343 243 : newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
3344 243 : if (inhcollid != newcollid)
3345 4 : ereport(ERROR,
3346 : (errcode(ERRCODE_COLLATION_MISMATCH),
3347 : errmsg("column \"%s\" has a collation conflict",
3348 : attributeName),
3349 : errdetail("\"%s\" versus \"%s\"",
3350 : get_collation_name(inhcollid),
3351 : get_collation_name(newcollid))));
3352 :
3353 : /*
3354 : * Identity is never inherited by a regular inheritance child. Pick
3355 : * child's identity definition if there's one.
3356 : */
3357 239 : inhdef->identity = newdef->identity;
3358 :
3359 : /*
3360 : * Copy storage parameter
3361 : */
3362 239 : if (inhdef->storage == 0)
3363 0 : inhdef->storage = newdef->storage;
3364 239 : else if (newdef->storage != 0 && inhdef->storage != newdef->storage)
3365 4 : ereport(ERROR,
3366 : (errcode(ERRCODE_DATATYPE_MISMATCH),
3367 : errmsg("column \"%s\" has a storage parameter conflict",
3368 : attributeName),
3369 : errdetail("%s versus %s",
3370 : storage_name(inhdef->storage),
3371 : storage_name(newdef->storage))));
3372 :
3373 : /*
3374 : * Copy compression parameter
3375 : */
3376 235 : if (inhdef->compression == NULL)
3377 231 : inhdef->compression = newdef->compression;
3378 4 : else if (newdef->compression != NULL)
3379 : {
3380 4 : if (strcmp(inhdef->compression, newdef->compression) != 0)
3381 4 : ereport(ERROR,
3382 : (errcode(ERRCODE_DATATYPE_MISMATCH),
3383 : errmsg("column \"%s\" has a compression method conflict",
3384 : attributeName),
3385 : errdetail("%s versus %s", inhdef->compression, newdef->compression)));
3386 : }
3387 :
3388 : /*
3389 : * Merge of not-null constraints = OR 'em together
3390 : */
3391 231 : inhdef->is_not_null |= newdef->is_not_null;
3392 :
3393 : /*
3394 : * Check for conflicts related to generated columns.
3395 : *
3396 : * If the parent column is generated, the child column will be made a
3397 : * generated column if it isn't already. If it is a generated column,
3398 : * we'll take its generation expression in preference to the parent's. We
3399 : * must check that the child column doesn't specify a default value or
3400 : * identity, which matches the rules for a single column in
3401 : * parse_utilcmd.c.
3402 : *
3403 : * Conversely, if the parent column is not generated, the child column
3404 : * can't be either. (We used to allow that, but it results in being able
3405 : * to override the generation expression via UPDATEs through the parent.)
3406 : */
3407 231 : if (inhdef->generated)
3408 : {
3409 41 : if (newdef->raw_default && !newdef->generated)
3410 8 : ereport(ERROR,
3411 : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3412 : errmsg("column \"%s\" inherits from generated column but specifies default",
3413 : inhdef->colname)));
3414 33 : if (newdef->identity)
3415 8 : ereport(ERROR,
3416 : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3417 : errmsg("column \"%s\" inherits from generated column but specifies identity",
3418 : inhdef->colname)));
3419 : }
3420 : else
3421 : {
3422 190 : if (newdef->generated)
3423 8 : ereport(ERROR,
3424 : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3425 : errmsg("child column \"%s\" specifies generation expression",
3426 : inhdef->colname),
3427 : errhint("A child table column cannot be generated unless its parent column is.")));
3428 : }
3429 :
3430 207 : if (inhdef->generated && newdef->generated && newdef->generated != inhdef->generated)
3431 8 : ereport(ERROR,
3432 : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3433 : errmsg("column \"%s\" inherits from generated column of different kind",
3434 : inhdef->colname),
3435 : errdetail("Parent column is %s, child column is %s.",
3436 : inhdef->generated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL",
3437 : newdef->generated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL")));
3438 :
3439 : /*
3440 : * If new def has a default, override previous default
3441 : */
3442 199 : if (newdef->raw_default != NULL)
3443 : {
3444 20 : inhdef->raw_default = newdef->raw_default;
3445 20 : inhdef->cooked_default = newdef->cooked_default;
3446 : }
3447 :
3448 : /* Mark the column as locally defined */
3449 199 : inhdef->is_local = true;
3450 199 : }
3451 :
3452 : /*
3453 : * MergeInheritedAttribute
3454 : * Merge given parent attribute definition into specified attribute
3455 : * inherited from the previous parents.
3456 : *
3457 : * Input arguments:
3458 : * 'inh_columns' is the list of previously inherited ColumnDefs.
3459 : * 'exist_attno' is the number the existing matching attribute in inh_columns.
3460 : * 'newdef' is the new parent column/attribute definition to be merged.
3461 : *
3462 : * The matching ColumnDef in 'inh_columns' list is modified and returned.
3463 : *
3464 : * Notes:
3465 : * - The attribute is merged according to the rules laid out in the prologue
3466 : * of MergeAttributes().
3467 : * - If matching inherited attribute exists but the new attribute can not be
3468 : * merged into it, the function throws respective errors.
3469 : * - A partition inherits from only a single parent. Hence this function is
3470 : * applicable only to a regular inheritance.
3471 : */
3472 : static ColumnDef *
3473 245 : MergeInheritedAttribute(List *inh_columns,
3474 : int exist_attno,
3475 : const ColumnDef *newdef)
3476 : {
3477 245 : char *attributeName = newdef->colname;
3478 : ColumnDef *prevdef;
3479 : Oid prevtypeid,
3480 : newtypeid;
3481 : int32 prevtypmod,
3482 : newtypmod;
3483 : Oid prevcollid,
3484 : newcollid;
3485 :
3486 245 : ereport(NOTICE,
3487 : (errmsg("merging multiple inherited definitions of column \"%s\"",
3488 : attributeName)));
3489 245 : prevdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
3490 :
3491 : /*
3492 : * Must have the same type and typmod
3493 : */
3494 245 : typenameTypeIdAndMod(NULL, prevdef->typeName, &prevtypeid, &prevtypmod);
3495 245 : typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
3496 245 : if (prevtypeid != newtypeid || prevtypmod != newtypmod)
3497 0 : ereport(ERROR,
3498 : (errcode(ERRCODE_DATATYPE_MISMATCH),
3499 : errmsg("inherited column \"%s\" has a type conflict",
3500 : attributeName),
3501 : errdetail("%s versus %s",
3502 : format_type_with_typemod(prevtypeid, prevtypmod),
3503 : format_type_with_typemod(newtypeid, newtypmod))));
3504 :
3505 : /*
3506 : * Must have the same collation
3507 : */
3508 245 : prevcollid = GetColumnDefCollation(NULL, prevdef, prevtypeid);
3509 245 : newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
3510 245 : if (prevcollid != newcollid)
3511 0 : ereport(ERROR,
3512 : (errcode(ERRCODE_COLLATION_MISMATCH),
3513 : errmsg("inherited column \"%s\" has a collation conflict",
3514 : attributeName),
3515 : errdetail("\"%s\" versus \"%s\"",
3516 : get_collation_name(prevcollid),
3517 : get_collation_name(newcollid))));
3518 :
3519 : /*
3520 : * Copy/check storage parameter
3521 : */
3522 245 : if (prevdef->storage == 0)
3523 0 : prevdef->storage = newdef->storage;
3524 245 : else if (prevdef->storage != newdef->storage)
3525 4 : ereport(ERROR,
3526 : (errcode(ERRCODE_DATATYPE_MISMATCH),
3527 : errmsg("inherited column \"%s\" has a storage parameter conflict",
3528 : attributeName),
3529 : errdetail("%s versus %s",
3530 : storage_name(prevdef->storage),
3531 : storage_name(newdef->storage))));
3532 :
3533 : /*
3534 : * Copy/check compression parameter
3535 : */
3536 241 : if (prevdef->compression == NULL)
3537 229 : prevdef->compression = newdef->compression;
3538 12 : else if (newdef->compression != NULL)
3539 : {
3540 4 : if (strcmp(prevdef->compression, newdef->compression) != 0)
3541 4 : ereport(ERROR,
3542 : (errcode(ERRCODE_DATATYPE_MISMATCH),
3543 : errmsg("column \"%s\" has a compression method conflict",
3544 : attributeName),
3545 : errdetail("%s versus %s",
3546 : prevdef->compression, newdef->compression)));
3547 : }
3548 :
3549 : /*
3550 : * Check for GENERATED conflicts
3551 : */
3552 237 : if (prevdef->generated != newdef->generated)
3553 16 : ereport(ERROR,
3554 : (errcode(ERRCODE_DATATYPE_MISMATCH),
3555 : errmsg("inherited column \"%s\" has a generation conflict",
3556 : attributeName)));
3557 :
3558 : /*
3559 : * Default and other constraints are handled by the caller.
3560 : */
3561 :
3562 221 : if (pg_add_s16_overflow(prevdef->inhcount, 1,
3563 : &prevdef->inhcount))
3564 0 : ereport(ERROR,
3565 : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
3566 : errmsg("too many inheritance parents"));
3567 :
3568 221 : return prevdef;
3569 : }
3570 :
3571 : /*
3572 : * StoreCatalogInheritance
3573 : * Updates the system catalogs with proper inheritance information.
3574 : *
3575 : * supers is a list of the OIDs of the new relation's direct ancestors.
3576 : */
3577 : static void
3578 41356 : StoreCatalogInheritance(Oid relationId, List *supers,
3579 : bool child_is_partition)
3580 : {
3581 : Relation relation;
3582 : int32 seqNumber;
3583 : ListCell *entry;
3584 :
3585 : /*
3586 : * sanity checks
3587 : */
3588 : Assert(OidIsValid(relationId));
3589 :
3590 41356 : if (supers == NIL)
3591 33825 : return;
3592 :
3593 : /*
3594 : * Store INHERITS information in pg_inherits using direct ancestors only.
3595 : * Also enter dependencies on the direct ancestors, and make sure they are
3596 : * marked with relhassubclass = true.
3597 : *
3598 : * (Once upon a time, both direct and indirect ancestors were found here
3599 : * and then entered into pg_ipl. Since that catalog doesn't exist
3600 : * anymore, there's no need to look for indirect ancestors.)
3601 : */
3602 7531 : relation = table_open(InheritsRelationId, RowExclusiveLock);
3603 :
3604 7531 : seqNumber = 1;
3605 15288 : foreach(entry, supers)
3606 : {
3607 7757 : Oid parentOid = lfirst_oid(entry);
3608 :
3609 7757 : StoreCatalogInheritance1(relationId, parentOid, seqNumber, relation,
3610 : child_is_partition);
3611 7757 : seqNumber++;
3612 : }
3613 :
3614 7531 : table_close(relation, RowExclusiveLock);
3615 : }
3616 :
3617 : /*
3618 : * Make catalog entries showing relationId as being an inheritance child
3619 : * of parentOid. inhRelation is the already-opened pg_inherits catalog.
3620 : */
3621 : static void
3622 9874 : StoreCatalogInheritance1(Oid relationId, Oid parentOid,
3623 : int32 seqNumber, Relation inhRelation,
3624 : bool child_is_partition)
3625 : {
3626 : ObjectAddress childobject,
3627 : parentobject;
3628 :
3629 : /* store the pg_inherits row */
3630 9874 : StoreSingleInheritance(relationId, parentOid, seqNumber);
3631 :
3632 : /*
3633 : * Store a dependency too
3634 : */
3635 9874 : parentobject.classId = RelationRelationId;
3636 9874 : parentobject.objectId = parentOid;
3637 9874 : parentobject.objectSubId = 0;
3638 9874 : childobject.classId = RelationRelationId;
3639 9874 : childobject.objectId = relationId;
3640 9874 : childobject.objectSubId = 0;
3641 :
3642 9874 : recordDependencyOn(&childobject, &parentobject,
3643 : child_dependency_type(child_is_partition));
3644 :
3645 : /*
3646 : * Post creation hook of this inheritance. Since object_access_hook
3647 : * doesn't take multiple object identifiers, we relay oid of parent
3648 : * relation using auxiliary_id argument.
3649 : */
3650 9874 : InvokeObjectPostAlterHookArg(InheritsRelationId,
3651 : relationId, 0,
3652 : parentOid, false);
3653 :
3654 : /*
3655 : * Mark the parent as having subclasses.
3656 : */
3657 9874 : SetRelationHasSubclass(parentOid, true);
3658 9874 : }
3659 :
3660 : /*
3661 : * Look for an existing column entry with the given name.
3662 : *
3663 : * Returns the index (starting with 1) if attribute already exists in columns,
3664 : * 0 if it doesn't.
3665 : */
3666 : static int
3667 16803 : findAttrByName(const char *attributeName, const List *columns)
3668 : {
3669 : ListCell *lc;
3670 16803 : int i = 1;
3671 :
3672 30454 : foreach(lc, columns)
3673 : {
3674 14147 : if (strcmp(attributeName, lfirst_node(ColumnDef, lc)->colname) == 0)
3675 496 : return i;
3676 :
3677 13651 : i++;
3678 : }
3679 16307 : return 0;
3680 : }
3681 :
3682 :
3683 : /*
3684 : * SetRelationHasSubclass
3685 : * Set the value of the relation's relhassubclass field in pg_class.
3686 : *
3687 : * It's always safe to set this field to true, because all SQL commands are
3688 : * ready to see true and then find no children. On the other hand, commands
3689 : * generally assume zero children if this is false.
3690 : *
3691 : * Caller must hold any self-exclusive lock until end of transaction. If the
3692 : * new value is false, caller must have acquired that lock before reading the
3693 : * evidence that justified the false value. That way, it properly waits if
3694 : * another backend is simultaneously concluding no need to change the tuple
3695 : * (new and old values are true).
3696 : *
3697 : * NOTE: an important side-effect of this operation is that an SI invalidation
3698 : * message is sent out to all backends --- including me --- causing plans
3699 : * referencing the relation to be rebuilt with the new list of children.
3700 : * This must happen even if we find that no change is needed in the pg_class
3701 : * row.
3702 : */
3703 : void
3704 12368 : SetRelationHasSubclass(Oid relationId, bool relhassubclass)
3705 : {
3706 : Relation relationRelation;
3707 : HeapTuple tuple;
3708 : Form_pg_class classtuple;
3709 :
3710 : Assert(CheckRelationOidLockedByMe(relationId,
3711 : ShareUpdateExclusiveLock, false) ||
3712 : CheckRelationOidLockedByMe(relationId,
3713 : ShareRowExclusiveLock, true));
3714 :
3715 : /*
3716 : * Fetch a modifiable copy of the tuple, modify it, update pg_class.
3717 : */
3718 12368 : relationRelation = table_open(RelationRelationId, RowExclusiveLock);
3719 12368 : tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relationId));
3720 12368 : if (!HeapTupleIsValid(tuple))
3721 0 : elog(ERROR, "cache lookup failed for relation %u", relationId);
3722 12368 : classtuple = (Form_pg_class) GETSTRUCT(tuple);
3723 :
3724 12368 : if (classtuple->relhassubclass != relhassubclass)
3725 : {
3726 5574 : classtuple->relhassubclass = relhassubclass;
3727 5574 : CatalogTupleUpdate(relationRelation, &tuple->t_self, tuple);
3728 : }
3729 : else
3730 : {
3731 : /* no need to change tuple, but force relcache rebuild anyway */
3732 6794 : CacheInvalidateRelcacheByTuple(tuple);
3733 : }
3734 :
3735 12368 : heap_freetuple(tuple);
3736 12368 : table_close(relationRelation, RowExclusiveLock);
3737 12368 : }
3738 :
3739 : /*
3740 : * CheckRelationTableSpaceMove
3741 : * Check if relation can be moved to new tablespace.
3742 : *
3743 : * NOTE: The caller must hold AccessExclusiveLock on the relation.
3744 : *
3745 : * Returns true if the relation can be moved to the new tablespace; raises
3746 : * an error if it is not possible to do the move; returns false if the move
3747 : * would have no effect.
3748 : */
3749 : bool
3750 143 : CheckRelationTableSpaceMove(Relation rel, Oid newTableSpaceId)
3751 : {
3752 : Oid oldTableSpaceId;
3753 :
3754 : /*
3755 : * No work if no change in tablespace. Note that MyDatabaseTableSpace is
3756 : * stored as 0.
3757 : */
3758 143 : oldTableSpaceId = rel->rd_rel->reltablespace;
3759 143 : if (newTableSpaceId == oldTableSpaceId ||
3760 138 : (newTableSpaceId == MyDatabaseTableSpace && oldTableSpaceId == 0))
3761 10 : return false;
3762 :
3763 : /*
3764 : * We cannot support moving mapped relations into different tablespaces.
3765 : * (In particular this eliminates all shared catalogs.)
3766 : */
3767 133 : if (RelationIsMapped(rel))
3768 0 : ereport(ERROR,
3769 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3770 : errmsg("cannot move system relation \"%s\"",
3771 : RelationGetRelationName(rel))));
3772 :
3773 : /* Cannot move a non-shared relation into pg_global */
3774 133 : if (newTableSpaceId == GLOBALTABLESPACE_OID)
3775 8 : ereport(ERROR,
3776 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
3777 : errmsg("only shared relations can be placed in pg_global tablespace")));
3778 :
3779 : /*
3780 : * Do not allow moving temp tables of other backends ... their local
3781 : * buffer manager is not going to cope.
3782 : */
3783 125 : if (RELATION_IS_OTHER_TEMP(rel))
3784 0 : ereport(ERROR,
3785 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3786 : errmsg("cannot move temporary tables of other sessions")));
3787 :
3788 125 : return true;
3789 : }
3790 :
3791 : /*
3792 : * SetRelationTableSpace
3793 : * Set new reltablespace and relfilenumber in pg_class entry.
3794 : *
3795 : * newTableSpaceId is the new tablespace for the relation, and
3796 : * newRelFilenumber its new filenumber. If newRelFilenumber is
3797 : * InvalidRelFileNumber, this field is not updated.
3798 : *
3799 : * NOTE: The caller must hold AccessExclusiveLock on the relation.
3800 : *
3801 : * The caller of this routine had better check if a relation can be
3802 : * moved to this new tablespace by calling CheckRelationTableSpaceMove()
3803 : * first, and is responsible for making the change visible with
3804 : * CommandCounterIncrement().
3805 : */
3806 : void
3807 125 : SetRelationTableSpace(Relation rel,
3808 : Oid newTableSpaceId,
3809 : RelFileNumber newRelFilenumber)
3810 : {
3811 : Relation pg_class;
3812 : HeapTuple tuple;
3813 : ItemPointerData otid;
3814 : Form_pg_class rd_rel;
3815 125 : Oid reloid = RelationGetRelid(rel);
3816 :
3817 : Assert(CheckRelationTableSpaceMove(rel, newTableSpaceId));
3818 :
3819 : /* Get a modifiable copy of the relation's pg_class row. */
3820 125 : pg_class = table_open(RelationRelationId, RowExclusiveLock);
3821 :
3822 125 : tuple = SearchSysCacheLockedCopy1(RELOID, ObjectIdGetDatum(reloid));
3823 125 : if (!HeapTupleIsValid(tuple))
3824 0 : elog(ERROR, "cache lookup failed for relation %u", reloid);
3825 125 : otid = tuple->t_self;
3826 125 : rd_rel = (Form_pg_class) GETSTRUCT(tuple);
3827 :
3828 : /* Update the pg_class row. */
3829 250 : rd_rel->reltablespace = (newTableSpaceId == MyDatabaseTableSpace) ?
3830 125 : InvalidOid : newTableSpaceId;
3831 125 : if (RelFileNumberIsValid(newRelFilenumber))
3832 96 : rd_rel->relfilenode = newRelFilenumber;
3833 125 : CatalogTupleUpdate(pg_class, &otid, tuple);
3834 125 : UnlockTuple(pg_class, &otid, InplaceUpdateTupleLock);
3835 :
3836 : /*
3837 : * Record dependency on tablespace. This is only required for relations
3838 : * that have no physical storage.
3839 : */
3840 125 : if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
3841 20 : changeDependencyOnTablespace(RelationRelationId, reloid,
3842 : rd_rel->reltablespace);
3843 :
3844 125 : heap_freetuple(tuple);
3845 125 : table_close(pg_class, RowExclusiveLock);
3846 125 : }
3847 :
3848 : /*
3849 : * renameatt_check - basic sanity checks before attribute rename
3850 : */
3851 : static void
3852 671 : renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
3853 : {
3854 671 : char relkind = classform->relkind;
3855 :
3856 671 : if (classform->reloftype && !recursing)
3857 4 : ereport(ERROR,
3858 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
3859 : errmsg("cannot rename column of typed table")));
3860 :
3861 : /*
3862 : * Renaming the columns of sequences or toast tables doesn't actually
3863 : * break anything from the system's point of view, since internal
3864 : * references are by attnum. But it doesn't seem right to allow users to
3865 : * change names that are hardcoded into the system, hence the following
3866 : * restriction.
3867 : */
3868 667 : if (relkind != RELKIND_RELATION &&
3869 56 : relkind != RELKIND_VIEW &&
3870 56 : relkind != RELKIND_MATVIEW &&
3871 24 : relkind != RELKIND_COMPOSITE_TYPE &&
3872 24 : relkind != RELKIND_INDEX &&
3873 24 : relkind != RELKIND_PARTITIONED_INDEX &&
3874 0 : relkind != RELKIND_FOREIGN_TABLE &&
3875 : relkind != RELKIND_PARTITIONED_TABLE)
3876 0 : ereport(ERROR,
3877 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
3878 : errmsg("cannot rename columns of relation \"%s\"",
3879 : NameStr(classform->relname)),
3880 : errdetail_relkind_not_supported(relkind)));
3881 :
3882 : /*
3883 : * permissions checking. only the owner of a class can change its schema.
3884 : */
3885 667 : if (!object_ownercheck(RelationRelationId, myrelid, GetUserId()))
3886 0 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(myrelid)),
3887 0 : NameStr(classform->relname));
3888 667 : if (!allowSystemTableMods && IsSystemClass(myrelid, classform))
3889 1 : ereport(ERROR,
3890 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
3891 : errmsg("permission denied: \"%s\" is a system catalog",
3892 : NameStr(classform->relname))));
3893 666 : }
3894 :
3895 : /*
3896 : * renameatt_internal - workhorse for renameatt
3897 : *
3898 : * Return value is the attribute number in the 'myrelid' relation.
3899 : */
3900 : static AttrNumber
3901 367 : renameatt_internal(Oid myrelid,
3902 : const char *oldattname,
3903 : const char *newattname,
3904 : bool recurse,
3905 : bool recursing,
3906 : int expected_parents,
3907 : DropBehavior behavior)
3908 : {
3909 : Relation targetrelation;
3910 : Relation attrelation;
3911 : HeapTuple atttup;
3912 : Form_pg_attribute attform;
3913 : AttrNumber attnum;
3914 :
3915 : /*
3916 : * Grab an exclusive lock on the target table, which we will NOT release
3917 : * until end of transaction.
3918 : */
3919 367 : targetrelation = relation_open(myrelid, AccessExclusiveLock);
3920 367 : renameatt_check(myrelid, RelationGetForm(targetrelation), recursing);
3921 :
3922 : /*
3923 : * if the 'recurse' flag is set then we are supposed to rename this
3924 : * attribute in all classes that inherit from 'relname' (as well as in
3925 : * 'relname').
3926 : *
3927 : * any permissions or problems with duplicate attributes will cause the
3928 : * whole transaction to abort, which is what we want -- all or nothing.
3929 : */
3930 367 : if (recurse)
3931 : {
3932 : List *child_oids,
3933 : *child_numparents;
3934 : ListCell *lo,
3935 : *li;
3936 :
3937 : /*
3938 : * we need the number of parents for each child so that the recursive
3939 : * calls to renameatt() can determine whether there are any parents
3940 : * outside the inheritance hierarchy being processed.
3941 : */
3942 165 : child_oids = find_all_inheritors(myrelid, AccessExclusiveLock,
3943 : &child_numparents);
3944 :
3945 : /*
3946 : * find_all_inheritors does the recursive search of the inheritance
3947 : * hierarchy, so all we have to do is process all of the relids in the
3948 : * list that it returns.
3949 : */
3950 488 : forboth(lo, child_oids, li, child_numparents)
3951 : {
3952 343 : Oid childrelid = lfirst_oid(lo);
3953 343 : int numparents = lfirst_int(li);
3954 :
3955 343 : if (childrelid == myrelid)
3956 165 : continue;
3957 : /* note we need not recurse again */
3958 178 : renameatt_internal(childrelid, oldattname, newattname, false, true, numparents, behavior);
3959 : }
3960 : }
3961 : else
3962 : {
3963 : /*
3964 : * If we are told not to recurse, there had better not be any child
3965 : * tables; else the rename would put them out of step.
3966 : *
3967 : * expected_parents will only be 0 if we are not already recursing.
3968 : */
3969 226 : if (expected_parents == 0 &&
3970 24 : find_inheritance_children(myrelid, NoLock) != NIL)
3971 8 : ereport(ERROR,
3972 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
3973 : errmsg("inherited column \"%s\" must be renamed in child tables too",
3974 : oldattname)));
3975 : }
3976 :
3977 : /* rename attributes in typed tables of composite type */
3978 339 : if (targetrelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
3979 : {
3980 : List *child_oids;
3981 : ListCell *lo;
3982 :
3983 16 : child_oids = find_typed_table_dependencies(targetrelation->rd_rel->reltype,
3984 16 : RelationGetRelationName(targetrelation),
3985 : behavior);
3986 :
3987 16 : foreach(lo, child_oids)
3988 4 : renameatt_internal(lfirst_oid(lo), oldattname, newattname, true, true, 0, behavior);
3989 : }
3990 :
3991 335 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
3992 :
3993 335 : atttup = SearchSysCacheCopyAttName(myrelid, oldattname);
3994 335 : if (!HeapTupleIsValid(atttup))
3995 16 : ereport(ERROR,
3996 : (errcode(ERRCODE_UNDEFINED_COLUMN),
3997 : errmsg("column \"%s\" does not exist",
3998 : oldattname)));
3999 319 : attform = (Form_pg_attribute) GETSTRUCT(atttup);
4000 :
4001 319 : attnum = attform->attnum;
4002 319 : if (attnum <= 0)
4003 0 : ereport(ERROR,
4004 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
4005 : errmsg("cannot rename system column \"%s\"",
4006 : oldattname)));
4007 :
4008 : /*
4009 : * if the attribute is inherited, forbid the renaming. if this is a
4010 : * top-level call to renameatt(), then expected_parents will be 0, so the
4011 : * effect of this code will be to prohibit the renaming if the attribute
4012 : * is inherited at all. if this is a recursive call to renameatt(),
4013 : * expected_parents will be the number of parents the current relation has
4014 : * within the inheritance hierarchy being processed, so we'll prohibit the
4015 : * renaming only if there are additional parents from elsewhere.
4016 : */
4017 319 : if (attform->attinhcount > expected_parents)
4018 20 : ereport(ERROR,
4019 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
4020 : errmsg("cannot rename inherited column \"%s\"",
4021 : oldattname)));
4022 :
4023 : /* new name should not already exist */
4024 299 : (void) check_for_column_name_collision(targetrelation, newattname, false);
4025 :
4026 : /* apply the update */
4027 291 : namestrcpy(&(attform->attname), newattname);
4028 :
4029 291 : CatalogTupleUpdate(attrelation, &atttup->t_self, atttup);
4030 :
4031 291 : InvokeObjectPostAlterHook(RelationRelationId, myrelid, attnum);
4032 :
4033 291 : heap_freetuple(atttup);
4034 :
4035 291 : table_close(attrelation, RowExclusiveLock);
4036 :
4037 291 : relation_close(targetrelation, NoLock); /* close rel but keep lock */
4038 :
4039 291 : return attnum;
4040 : }
4041 :
4042 : /*
4043 : * Perform permissions and integrity checks before acquiring a relation lock.
4044 : */
4045 : static void
4046 274 : RangeVarCallbackForRenameAttribute(const RangeVar *rv, Oid relid, Oid oldrelid,
4047 : void *arg)
4048 : {
4049 : HeapTuple tuple;
4050 : Form_pg_class form;
4051 :
4052 274 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
4053 274 : if (!HeapTupleIsValid(tuple))
4054 26 : return; /* concurrently dropped */
4055 248 : form = (Form_pg_class) GETSTRUCT(tuple);
4056 248 : renameatt_check(relid, form, false);
4057 243 : ReleaseSysCache(tuple);
4058 : }
4059 :
4060 : /*
4061 : * renameatt - changes the name of an attribute in a relation
4062 : *
4063 : * The returned ObjectAddress is that of the renamed column.
4064 : */
4065 : ObjectAddress
4066 210 : renameatt(RenameStmt *stmt)
4067 : {
4068 : Oid relid;
4069 : AttrNumber attnum;
4070 : ObjectAddress address;
4071 :
4072 : /* lock level taken here should match renameatt_internal */
4073 210 : relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
4074 210 : stmt->missing_ok ? RVR_MISSING_OK : 0,
4075 : RangeVarCallbackForRenameAttribute,
4076 : NULL);
4077 :
4078 201 : if (!OidIsValid(relid))
4079 : {
4080 16 : ereport(NOTICE,
4081 : (errmsg("relation \"%s\" does not exist, skipping",
4082 : stmt->relation->relname)));
4083 16 : return InvalidObjectAddress;
4084 : }
4085 :
4086 : attnum =
4087 185 : renameatt_internal(relid,
4088 185 : stmt->subname, /* old att name */
4089 185 : stmt->newname, /* new att name */
4090 185 : stmt->relation->inh, /* recursive? */
4091 : false, /* recursing? */
4092 : 0, /* expected inhcount */
4093 : stmt->behavior);
4094 :
4095 129 : ObjectAddressSubSet(address, RelationRelationId, relid, attnum);
4096 :
4097 129 : return address;
4098 : }
4099 :
4100 : /*
4101 : * same logic as renameatt_internal
4102 : */
4103 : static ObjectAddress
4104 60 : rename_constraint_internal(Oid myrelid,
4105 : Oid mytypid,
4106 : const char *oldconname,
4107 : const char *newconname,
4108 : bool recurse,
4109 : bool recursing,
4110 : int expected_parents)
4111 : {
4112 60 : Relation targetrelation = NULL;
4113 : Oid constraintOid;
4114 : HeapTuple tuple;
4115 : Form_pg_constraint con;
4116 : ObjectAddress address;
4117 :
4118 : Assert(!myrelid || !mytypid);
4119 :
4120 60 : if (mytypid)
4121 : {
4122 4 : constraintOid = get_domain_constraint_oid(mytypid, oldconname, false);
4123 : }
4124 : else
4125 : {
4126 56 : targetrelation = relation_open(myrelid, AccessExclusiveLock);
4127 :
4128 : /*
4129 : * don't tell it whether we're recursing; we allow changing typed
4130 : * tables here
4131 : */
4132 56 : renameatt_check(myrelid, RelationGetForm(targetrelation), false);
4133 :
4134 56 : constraintOid = get_relation_constraint_oid(myrelid, oldconname, false);
4135 : }
4136 :
4137 60 : tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraintOid));
4138 60 : if (!HeapTupleIsValid(tuple))
4139 0 : elog(ERROR, "cache lookup failed for constraint %u",
4140 : constraintOid);
4141 60 : con = (Form_pg_constraint) GETSTRUCT(tuple);
4142 :
4143 60 : if (myrelid &&
4144 56 : (con->contype == CONSTRAINT_CHECK ||
4145 16 : con->contype == CONSTRAINT_NOTNULL) &&
4146 44 : !con->connoinherit)
4147 : {
4148 36 : if (recurse)
4149 : {
4150 : List *child_oids,
4151 : *child_numparents;
4152 : ListCell *lo,
4153 : *li;
4154 :
4155 24 : child_oids = find_all_inheritors(myrelid, AccessExclusiveLock,
4156 : &child_numparents);
4157 :
4158 56 : forboth(lo, child_oids, li, child_numparents)
4159 : {
4160 32 : Oid childrelid = lfirst_oid(lo);
4161 32 : int numparents = lfirst_int(li);
4162 :
4163 32 : if (childrelid == myrelid)
4164 24 : continue;
4165 :
4166 8 : rename_constraint_internal(childrelid, InvalidOid, oldconname, newconname, false, true, numparents);
4167 : }
4168 : }
4169 : else
4170 : {
4171 16 : if (expected_parents == 0 &&
4172 4 : find_inheritance_children(myrelid, NoLock) != NIL)
4173 4 : ereport(ERROR,
4174 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
4175 : errmsg("inherited constraint \"%s\" must be renamed in child tables too",
4176 : oldconname)));
4177 : }
4178 :
4179 32 : if (con->coninhcount > expected_parents)
4180 4 : ereport(ERROR,
4181 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
4182 : errmsg("cannot rename inherited constraint \"%s\"",
4183 : oldconname)));
4184 : }
4185 :
4186 52 : if (con->conindid
4187 12 : && (con->contype == CONSTRAINT_PRIMARY
4188 4 : || con->contype == CONSTRAINT_UNIQUE
4189 0 : || con->contype == CONSTRAINT_EXCLUSION))
4190 : /* rename the index; this renames the constraint as well */
4191 12 : RenameRelationInternal(con->conindid, newconname, false, true);
4192 : else
4193 40 : RenameConstraintById(constraintOid, newconname);
4194 :
4195 52 : ObjectAddressSet(address, ConstraintRelationId, constraintOid);
4196 :
4197 52 : ReleaseSysCache(tuple);
4198 :
4199 52 : if (targetrelation)
4200 : {
4201 : /*
4202 : * Invalidate relcache so as others can see the new constraint name.
4203 : */
4204 48 : CacheInvalidateRelcache(targetrelation);
4205 :
4206 48 : relation_close(targetrelation, NoLock); /* close rel but keep lock */
4207 : }
4208 :
4209 52 : return address;
4210 : }
4211 :
4212 : ObjectAddress
4213 56 : RenameConstraint(RenameStmt *stmt)
4214 : {
4215 56 : Oid relid = InvalidOid;
4216 56 : Oid typid = InvalidOid;
4217 :
4218 56 : if (stmt->renameType == OBJECT_DOMCONSTRAINT)
4219 : {
4220 : Relation rel;
4221 : HeapTuple tup;
4222 :
4223 4 : typid = typenameTypeId(NULL, makeTypeNameFromNameList(castNode(List, stmt->object)));
4224 4 : rel = table_open(TypeRelationId, RowExclusiveLock);
4225 4 : tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
4226 4 : if (!HeapTupleIsValid(tup))
4227 0 : elog(ERROR, "cache lookup failed for type %u", typid);
4228 4 : checkDomainOwner(tup);
4229 4 : ReleaseSysCache(tup);
4230 4 : table_close(rel, NoLock);
4231 : }
4232 : else
4233 : {
4234 : /* lock level taken here should match rename_constraint_internal */
4235 52 : relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
4236 52 : stmt->missing_ok ? RVR_MISSING_OK : 0,
4237 : RangeVarCallbackForRenameAttribute,
4238 : NULL);
4239 52 : if (!OidIsValid(relid))
4240 : {
4241 4 : ereport(NOTICE,
4242 : (errmsg("relation \"%s\" does not exist, skipping",
4243 : stmt->relation->relname)));
4244 4 : return InvalidObjectAddress;
4245 : }
4246 : }
4247 :
4248 : return
4249 52 : rename_constraint_internal(relid, typid,
4250 52 : stmt->subname,
4251 52 : stmt->newname,
4252 100 : (stmt->relation &&
4253 48 : stmt->relation->inh), /* recursive? */
4254 : false, /* recursing? */
4255 52 : 0 /* expected inhcount */ );
4256 : }
4257 :
4258 : /*
4259 : * Execute ALTER TABLE/INDEX/SEQUENCE/VIEW/MATERIALIZED VIEW/FOREIGN TABLE/PROPERTY GRAPH
4260 : * RENAME
4261 : */
4262 : ObjectAddress
4263 327 : RenameRelation(RenameStmt *stmt)
4264 : {
4265 327 : bool is_index_stmt = stmt->renameType == OBJECT_INDEX;
4266 : Oid relid;
4267 : ObjectAddress address;
4268 :
4269 : /*
4270 : * Grab an exclusive lock on the target table, index, sequence, view,
4271 : * materialized view, or foreign table, which we will NOT release until
4272 : * end of transaction.
4273 : *
4274 : * Lock level used here should match RenameRelationInternal, to avoid lock
4275 : * escalation. However, because ALTER INDEX can be used with any relation
4276 : * type, we mustn't believe without verification.
4277 : */
4278 : for (;;)
4279 8 : {
4280 : LOCKMODE lockmode;
4281 : char relkind;
4282 : bool obj_is_index;
4283 :
4284 335 : lockmode = is_index_stmt ? ShareUpdateExclusiveLock : AccessExclusiveLock;
4285 :
4286 335 : relid = RangeVarGetRelidExtended(stmt->relation, lockmode,
4287 335 : stmt->missing_ok ? RVR_MISSING_OK : 0,
4288 : RangeVarCallbackForAlterRelation,
4289 : stmt);
4290 :
4291 298 : if (!OidIsValid(relid))
4292 : {
4293 12 : ereport(NOTICE,
4294 : (errmsg("relation \"%s\" does not exist, skipping",
4295 : stmt->relation->relname)));
4296 12 : return InvalidObjectAddress;
4297 : }
4298 :
4299 : /*
4300 : * We allow mismatched statement and object types (e.g., ALTER INDEX
4301 : * to rename a table), but we might've used the wrong lock level. If
4302 : * that happens, retry with the correct lock level. We don't bother
4303 : * if we already acquired AccessExclusiveLock with an index, however.
4304 : */
4305 286 : relkind = get_rel_relkind(relid);
4306 286 : obj_is_index = (relkind == RELKIND_INDEX ||
4307 : relkind == RELKIND_PARTITIONED_INDEX);
4308 286 : if (obj_is_index || is_index_stmt == obj_is_index)
4309 : break;
4310 :
4311 8 : UnlockRelationOid(relid, lockmode);
4312 8 : is_index_stmt = obj_is_index;
4313 : }
4314 :
4315 : /* Do the work */
4316 278 : RenameRelationInternal(relid, stmt->newname, false, is_index_stmt);
4317 :
4318 266 : ObjectAddressSet(address, RelationRelationId, relid);
4319 :
4320 266 : return address;
4321 : }
4322 :
4323 : /*
4324 : * RenameRelationInternal - change the name of a relation
4325 : */
4326 : void
4327 1124 : RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bool is_index)
4328 : {
4329 : Relation targetrelation;
4330 : Relation relrelation; /* for RELATION relation */
4331 : ItemPointerData otid;
4332 : HeapTuple reltup;
4333 : Form_pg_class relform;
4334 : Oid namespaceId;
4335 :
4336 : /*
4337 : * Grab a lock on the target relation, which we will NOT release until end
4338 : * of transaction. We need at least a self-exclusive lock so that
4339 : * concurrent DDL doesn't overwrite the rename if they start updating
4340 : * while still seeing the old version. The lock also guards against
4341 : * triggering relcache reloads in concurrent sessions, which might not
4342 : * handle this information changing under them. For indexes, we can use a
4343 : * reduced lock level because RelationReloadIndexInfo() handles indexes
4344 : * specially.
4345 : */
4346 1124 : targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
4347 1124 : namespaceId = RelationGetNamespace(targetrelation);
4348 :
4349 : /*
4350 : * Find relation's pg_class tuple, and make sure newrelname isn't in use.
4351 : */
4352 1124 : relrelation = table_open(RelationRelationId, RowExclusiveLock);
4353 :
4354 1124 : reltup = SearchSysCacheLockedCopy1(RELOID, ObjectIdGetDatum(myrelid));
4355 1124 : if (!HeapTupleIsValid(reltup)) /* shouldn't happen */
4356 0 : elog(ERROR, "cache lookup failed for relation %u", myrelid);
4357 1124 : otid = reltup->t_self;
4358 1124 : relform = (Form_pg_class) GETSTRUCT(reltup);
4359 :
4360 1124 : if (get_relname_relid(newrelname, namespaceId) != InvalidOid)
4361 12 : ereport(ERROR,
4362 : (errcode(ERRCODE_DUPLICATE_TABLE),
4363 : errmsg("relation \"%s\" already exists",
4364 : newrelname)));
4365 :
4366 : /*
4367 : * RenameRelation is careful not to believe the caller's idea of the
4368 : * relation kind being handled. We don't have to worry about this, but
4369 : * let's not be totally oblivious to it. We can process an index as
4370 : * not-an-index, but not the other way around.
4371 : */
4372 : Assert(!is_index ||
4373 : is_index == (targetrelation->rd_rel->relkind == RELKIND_INDEX ||
4374 : targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX));
4375 :
4376 : /*
4377 : * Update pg_class tuple with new relname. (Scribbling on reltup is OK
4378 : * because it's a copy...)
4379 : */
4380 1112 : namestrcpy(&(relform->relname), newrelname);
4381 :
4382 1112 : CatalogTupleUpdate(relrelation, &otid, reltup);
4383 1112 : UnlockTuple(relrelation, &otid, InplaceUpdateTupleLock);
4384 :
4385 1112 : InvokeObjectPostAlterHookArg(RelationRelationId, myrelid, 0,
4386 : InvalidOid, is_internal);
4387 :
4388 1112 : heap_freetuple(reltup);
4389 1112 : table_close(relrelation, RowExclusiveLock);
4390 :
4391 : /*
4392 : * Also rename the associated type, if any.
4393 : */
4394 1112 : if (OidIsValid(targetrelation->rd_rel->reltype))
4395 128 : RenameTypeInternal(targetrelation->rd_rel->reltype,
4396 : newrelname, namespaceId);
4397 :
4398 : /*
4399 : * Also rename the associated constraint, if any.
4400 : */
4401 1112 : if (targetrelation->rd_rel->relkind == RELKIND_INDEX ||
4402 614 : targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
4403 : {
4404 510 : Oid constraintId = get_index_constraint(myrelid);
4405 :
4406 510 : if (OidIsValid(constraintId))
4407 24 : RenameConstraintById(constraintId, newrelname);
4408 : }
4409 :
4410 : /*
4411 : * Close rel, but keep lock!
4412 : */
4413 1112 : relation_close(targetrelation, NoLock);
4414 1112 : }
4415 :
4416 : /*
4417 : * ResetRelRewrite - reset relrewrite
4418 : */
4419 : void
4420 389 : ResetRelRewrite(Oid myrelid)
4421 : {
4422 : Relation relrelation; /* for RELATION relation */
4423 : HeapTuple reltup;
4424 : Form_pg_class relform;
4425 :
4426 : /*
4427 : * Find relation's pg_class tuple.
4428 : */
4429 389 : relrelation = table_open(RelationRelationId, RowExclusiveLock);
4430 :
4431 389 : reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
4432 389 : if (!HeapTupleIsValid(reltup)) /* shouldn't happen */
4433 0 : elog(ERROR, "cache lookup failed for relation %u", myrelid);
4434 389 : relform = (Form_pg_class) GETSTRUCT(reltup);
4435 :
4436 : /*
4437 : * Update pg_class tuple.
4438 : */
4439 389 : relform->relrewrite = InvalidOid;
4440 :
4441 389 : CatalogTupleUpdate(relrelation, &reltup->t_self, reltup);
4442 :
4443 389 : heap_freetuple(reltup);
4444 389 : table_close(relrelation, RowExclusiveLock);
4445 389 : }
4446 :
4447 : /*
4448 : * Disallow ALTER TABLE (and similar commands) when the current backend has
4449 : * any open reference to the target table besides the one just acquired by
4450 : * the calling command; this implies there's an open cursor or active plan.
4451 : * We need this check because our lock doesn't protect us against stomping
4452 : * on our own foot, only other people's feet!
4453 : *
4454 : * For ALTER TABLE, the only case known to cause serious trouble is ALTER
4455 : * COLUMN TYPE, and some changes are obviously pretty benign, so this could
4456 : * possibly be relaxed to only error out for certain types of alterations.
4457 : * But the use-case for allowing any of these things is not obvious, so we
4458 : * won't work hard at it for now.
4459 : *
4460 : * We also reject these commands if there are any pending AFTER trigger events
4461 : * for the rel. This is certainly necessary for the rewriting variants of
4462 : * ALTER TABLE, because they don't preserve tuple TIDs and so the pending
4463 : * events would try to fetch the wrong tuples. It might be overly cautious
4464 : * in other cases, but again it seems better to err on the side of paranoia.
4465 : *
4466 : * REINDEX calls this with "rel" referencing the index to be rebuilt; here
4467 : * we are worried about active indexscans on the index. The trigger-event
4468 : * check can be skipped, since we are doing no damage to the parent table.
4469 : *
4470 : * The statement name (eg, "ALTER TABLE") is passed for use in error messages.
4471 : */
4472 : void
4473 116210 : CheckTableNotInUse(Relation rel, const char *stmt)
4474 : {
4475 : int expected_refcnt;
4476 :
4477 116210 : expected_refcnt = rel->rd_isnailed ? 2 : 1;
4478 116210 : if (rel->rd_refcnt != expected_refcnt)
4479 28 : ereport(ERROR,
4480 : (errcode(ERRCODE_OBJECT_IN_USE),
4481 : /* translator: first %s is a SQL command, eg ALTER TABLE */
4482 : errmsg("cannot %s \"%s\" because it is being used by active queries in this session",
4483 : stmt, RelationGetRelationName(rel))));
4484 :
4485 116182 : if (rel->rd_rel->relkind != RELKIND_INDEX &&
4486 190165 : rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
4487 94335 : AfterTriggerPendingOnRel(RelationGetRelid(rel)))
4488 12 : ereport(ERROR,
4489 : (errcode(ERRCODE_OBJECT_IN_USE),
4490 : /* translator: first %s is a SQL command, eg ALTER TABLE */
4491 : errmsg("cannot %s \"%s\" because it has pending trigger events",
4492 : stmt, RelationGetRelationName(rel))));
4493 116170 : }
4494 :
4495 : /*
4496 : * CheckAlterTableIsSafe
4497 : * Verify that it's safe to allow ALTER TABLE on this relation.
4498 : *
4499 : * This consists of CheckTableNotInUse() plus a check that the relation
4500 : * isn't another session's temp table. We must split out the temp-table
4501 : * check because there are callers of CheckTableNotInUse() that don't want
4502 : * that, notably DROP TABLE. (We must allow DROP or we couldn't clean out
4503 : * an orphaned temp schema.) Compare truncate_check_activity().
4504 : */
4505 : static void
4506 41022 : CheckAlterTableIsSafe(Relation rel)
4507 : {
4508 : /*
4509 : * Don't allow ALTER on temp tables of other backends. Their local buffer
4510 : * manager is not going to cope if we need to change the table's contents.
4511 : * Even if we don't, there may be optimizations that assume temp tables
4512 : * aren't subject to such interference.
4513 : */
4514 41022 : if (RELATION_IS_OTHER_TEMP(rel))
4515 2 : ereport(ERROR,
4516 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
4517 : errmsg("cannot alter temporary tables of other sessions")));
4518 :
4519 : /*
4520 : * Also check for active uses of the relation in the current transaction,
4521 : * including open scans and pending AFTER trigger events.
4522 : */
4523 41020 : CheckTableNotInUse(rel, "ALTER TABLE");
4524 40996 : }
4525 :
4526 : /*
4527 : * AlterTableLookupRelation
4528 : * Look up, and lock, the OID for the relation named by an alter table
4529 : * statement.
4530 : */
4531 : Oid
4532 21754 : AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
4533 : {
4534 43443 : return RangeVarGetRelidExtended(stmt->relation, lockmode,
4535 21754 : stmt->missing_ok ? RVR_MISSING_OK : 0,
4536 : RangeVarCallbackForAlterRelation,
4537 : stmt);
4538 : }
4539 :
4540 : /*
4541 : * AlterTable
4542 : * Execute ALTER TABLE, which can be a list of subcommands
4543 : *
4544 : * ALTER TABLE is performed in three phases:
4545 : * 1. Examine subcommands and perform pre-transformation checking.
4546 : * 2. Validate and transform subcommands, and update system catalogs.
4547 : * 3. Scan table(s) to check new constraints, and optionally recopy
4548 : * the data into new table(s).
4549 : * Phase 3 is not performed unless one or more of the subcommands requires
4550 : * it. The intention of this design is to allow multiple independent
4551 : * updates of the table schema to be performed with only one pass over the
4552 : * data.
4553 : *
4554 : * ATPrepCmd performs phase 1. A "work queue" entry is created for
4555 : * each table to be affected (there may be multiple affected tables if the
4556 : * commands traverse a table inheritance hierarchy). Also we do preliminary
4557 : * validation of the subcommands. Because earlier subcommands may change
4558 : * the catalog state seen by later commands, there are limits to what can
4559 : * be done in this phase. Generally, this phase acquires table locks,
4560 : * checks permissions and relkind, and recurses to find child tables.
4561 : *
4562 : * ATRewriteCatalogs performs phase 2 for each affected table.
4563 : * Certain subcommands need to be performed before others to avoid
4564 : * unnecessary conflicts; for example, DROP COLUMN should come before
4565 : * ADD COLUMN. Therefore phase 1 divides the subcommands into multiple
4566 : * lists, one for each logical "pass" of phase 2.
4567 : *
4568 : * ATRewriteTables performs phase 3 for those tables that need it.
4569 : *
4570 : * For most subcommand types, phases 2 and 3 do no explicit recursion,
4571 : * since phase 1 already does it. However, for certain subcommand types
4572 : * it is only possible to determine how to recurse at phase 2 time; for
4573 : * those cases, phase 1 sets the cmd->recurse flag.
4574 : *
4575 : * Thanks to the magic of MVCC, an error anywhere along the way rolls back
4576 : * the whole operation; we don't have to do anything special to clean up.
4577 : *
4578 : * The caller must lock the relation, with an appropriate lock level
4579 : * for the subcommands requested, using AlterTableGetLockLevel(stmt->cmds)
4580 : * or higher. We pass the lock level down
4581 : * so that we can apply it recursively to inherited tables. Note that the
4582 : * lock level we want as we recurse might well be higher than required for
4583 : * that specific subcommand. So we pass down the overall lock requirement,
4584 : * rather than reassess it at lower levels.
4585 : *
4586 : * The caller also provides a "context" which is to be passed back to
4587 : * utility.c when we need to execute a subcommand such as CREATE INDEX.
4588 : * Some of the fields therein, such as the relid, are used here as well.
4589 : */
4590 : void
4591 21581 : AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
4592 : AlterTableUtilityContext *context)
4593 : {
4594 : Relation rel;
4595 :
4596 : /* Caller is required to provide an adequate lock. */
4597 21581 : rel = relation_open(context->relid, NoLock);
4598 :
4599 21581 : CheckAlterTableIsSafe(rel);
4600 :
4601 21567 : ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
4602 18678 : }
4603 :
4604 : /*
4605 : * AlterTableInternal
4606 : *
4607 : * ALTER TABLE with target specified by OID
4608 : *
4609 : * We do not reject if the relation is already open, because it's quite
4610 : * likely that one or more layers of caller have it open. That means it
4611 : * is unsafe to use this entry point for alterations that could break
4612 : * existing query plans. On the assumption it's not used for such, we
4613 : * don't have to reject pending AFTER triggers, either.
4614 : *
4615 : * Also, since we don't have an AlterTableUtilityContext, this cannot be
4616 : * used for any subcommand types that require parse transformation or
4617 : * could generate subcommands that have to be passed to ProcessUtility.
4618 : */
4619 : void
4620 188 : AlterTableInternal(Oid relid, List *cmds, bool recurse)
4621 : {
4622 : Relation rel;
4623 188 : LOCKMODE lockmode = AlterTableGetLockLevel(cmds);
4624 :
4625 188 : rel = relation_open(relid, lockmode);
4626 :
4627 188 : EventTriggerAlterTableRelid(relid);
4628 :
4629 188 : ATController(NULL, rel, cmds, recurse, lockmode, NULL);
4630 188 : }
4631 :
4632 : /*
4633 : * AlterTableGetLockLevel
4634 : *
4635 : * Sets the overall lock level required for the supplied list of subcommands.
4636 : * Policy for doing this set according to needs of AlterTable(), see
4637 : * comments there for overall explanation.
4638 : *
4639 : * Function is called before and after parsing, so it must give same
4640 : * answer each time it is called. Some subcommands are transformed
4641 : * into other subcommand types, so the transform must never be made to a
4642 : * lower lock level than previously assigned. All transforms are noted below.
4643 : *
4644 : * Since this is called before we lock the table we cannot use table metadata
4645 : * to influence the type of lock we acquire.
4646 : *
4647 : * There should be no lockmodes hardcoded into the subcommand functions. All
4648 : * lockmode decisions for ALTER TABLE are made here only. The one exception is
4649 : * ALTER TABLE RENAME which is treated as a different statement type T_RenameStmt
4650 : * and does not travel through this section of code and cannot be combined with
4651 : * any of the subcommands given here.
4652 : *
4653 : * Note that Hot Standby only knows about AccessExclusiveLocks on the primary
4654 : * so any changes that might affect SELECTs running on standbys need to use
4655 : * AccessExclusiveLocks even if you think a lesser lock would do, unless you
4656 : * have a solution for that also.
4657 : *
4658 : * Also note that pg_dump uses only an AccessShareLock, meaning that anything
4659 : * that takes a lock less than AccessExclusiveLock can change object definitions
4660 : * while pg_dump is running. Be careful to check that the appropriate data is
4661 : * derived by pg_dump using an MVCC snapshot, rather than syscache lookups,
4662 : * otherwise we might end up with an inconsistent dump that can't restore.
4663 : */
4664 : LOCKMODE
4665 21942 : AlterTableGetLockLevel(List *cmds)
4666 : {
4667 : /*
4668 : * This only works if we read catalog tables using MVCC snapshots.
4669 : */
4670 : ListCell *lcmd;
4671 21942 : LOCKMODE lockmode = ShareUpdateExclusiveLock;
4672 :
4673 44750 : foreach(lcmd, cmds)
4674 : {
4675 22808 : AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
4676 22808 : LOCKMODE cmd_lockmode = AccessExclusiveLock; /* default for compiler */
4677 :
4678 22808 : switch (cmd->subtype)
4679 : {
4680 : /*
4681 : * These subcommands rewrite the heap, so require full locks.
4682 : */
4683 2619 : case AT_AddColumn: /* may rewrite heap, in some cases and visible
4684 : * to SELECT */
4685 : case AT_SetAccessMethod: /* must rewrite heap */
4686 : case AT_SetTableSpace: /* must rewrite heap */
4687 : case AT_AlterColumnType: /* must rewrite heap */
4688 2619 : cmd_lockmode = AccessExclusiveLock;
4689 2619 : break;
4690 :
4691 : /*
4692 : * These subcommands may require addition of toast tables. If
4693 : * we add a toast table to a table currently being scanned, we
4694 : * might miss data added to the new toast table by concurrent
4695 : * insert transactions.
4696 : */
4697 167 : case AT_SetStorage: /* may add toast tables, see
4698 : * ATRewriteCatalogs() */
4699 167 : cmd_lockmode = AccessExclusiveLock;
4700 167 : break;
4701 :
4702 : /*
4703 : * Removing constraints can affect SELECTs that have been
4704 : * optimized assuming the constraint holds true. See also
4705 : * CloneFkReferenced.
4706 : */
4707 784 : case AT_DropConstraint: /* as DROP INDEX */
4708 : case AT_DropNotNull: /* may change some SQL plans */
4709 784 : cmd_lockmode = AccessExclusiveLock;
4710 784 : break;
4711 :
4712 : /*
4713 : * Subcommands that may be visible to concurrent SELECTs
4714 : */
4715 1221 : case AT_DropColumn: /* change visible to SELECT */
4716 : case AT_AddColumnToView: /* CREATE VIEW */
4717 : case AT_DropOids: /* used to equiv to DropColumn */
4718 : case AT_EnableAlwaysRule: /* may change SELECT rules */
4719 : case AT_EnableReplicaRule: /* may change SELECT rules */
4720 : case AT_EnableRule: /* may change SELECT rules */
4721 : case AT_DisableRule: /* may change SELECT rules */
4722 1221 : cmd_lockmode = AccessExclusiveLock;
4723 1221 : break;
4724 :
4725 : /*
4726 : * Changing owner may remove implicit SELECT privileges
4727 : */
4728 1157 : case AT_ChangeOwner: /* change visible to SELECT */
4729 1157 : cmd_lockmode = AccessExclusiveLock;
4730 1157 : break;
4731 :
4732 : /*
4733 : * Changing foreign table options may affect optimization.
4734 : */
4735 143 : case AT_GenericOptions:
4736 : case AT_AlterColumnGenericOptions:
4737 143 : cmd_lockmode = AccessExclusiveLock;
4738 143 : break;
4739 :
4740 : /*
4741 : * These subcommands affect write operations only.
4742 : */
4743 191 : case AT_EnableTrig:
4744 : case AT_EnableAlwaysTrig:
4745 : case AT_EnableReplicaTrig:
4746 : case AT_EnableTrigAll:
4747 : case AT_EnableTrigUser:
4748 : case AT_DisableTrig:
4749 : case AT_DisableTrigAll:
4750 : case AT_DisableTrigUser:
4751 191 : cmd_lockmode = ShareRowExclusiveLock;
4752 191 : break;
4753 :
4754 : /*
4755 : * These subcommands affect write operations only. XXX
4756 : * Theoretically, these could be ShareRowExclusiveLock.
4757 : */
4758 2056 : case AT_ColumnDefault:
4759 : case AT_CookedColumnDefault:
4760 : case AT_AlterConstraint:
4761 : case AT_AddIndex: /* from ADD CONSTRAINT */
4762 : case AT_AddIndexConstraint:
4763 : case AT_ReplicaIdentity:
4764 : case AT_SetNotNull:
4765 : case AT_EnableRowSecurity:
4766 : case AT_DisableRowSecurity:
4767 : case AT_ForceRowSecurity:
4768 : case AT_NoForceRowSecurity:
4769 : case AT_AddIdentity:
4770 : case AT_DropIdentity:
4771 : case AT_SetIdentity:
4772 : case AT_SetExpression:
4773 : case AT_DropExpression:
4774 : case AT_SetCompression:
4775 2056 : cmd_lockmode = AccessExclusiveLock;
4776 2056 : break;
4777 :
4778 10097 : case AT_AddConstraint:
4779 : case AT_ReAddConstraint: /* becomes AT_AddConstraint */
4780 : case AT_ReAddDomainConstraint: /* becomes AT_AddConstraint */
4781 10097 : if (IsA(cmd->def, Constraint))
4782 : {
4783 10097 : Constraint *con = (Constraint *) cmd->def;
4784 :
4785 10097 : switch (con->contype)
4786 : {
4787 7456 : case CONSTR_EXCLUSION:
4788 : case CONSTR_PRIMARY:
4789 : case CONSTR_UNIQUE:
4790 :
4791 : /*
4792 : * Cases essentially the same as CREATE INDEX. We
4793 : * could reduce the lock strength to ShareLock if
4794 : * we can work out how to allow concurrent catalog
4795 : * updates. XXX Might be set down to
4796 : * ShareRowExclusiveLock but requires further
4797 : * analysis.
4798 : */
4799 7456 : cmd_lockmode = AccessExclusiveLock;
4800 7456 : break;
4801 1806 : case CONSTR_FOREIGN:
4802 :
4803 : /*
4804 : * We add triggers to both tables when we add a
4805 : * Foreign Key, so the lock level must be at least
4806 : * as strong as CREATE TRIGGER.
4807 : */
4808 1806 : cmd_lockmode = ShareRowExclusiveLock;
4809 1806 : break;
4810 :
4811 835 : default:
4812 835 : cmd_lockmode = AccessExclusiveLock;
4813 : }
4814 : }
4815 10097 : break;
4816 :
4817 : /*
4818 : * These subcommands affect inheritance behaviour. Queries
4819 : * started before us will continue to see the old inheritance
4820 : * behaviour, while queries started after we commit will see
4821 : * new behaviour. No need to prevent reads or writes to the
4822 : * subtable while we hook it up though. Changing the TupDesc
4823 : * may be a problem, so keep highest lock.
4824 : */
4825 386 : case AT_AddInherit:
4826 : case AT_DropInherit:
4827 386 : cmd_lockmode = AccessExclusiveLock;
4828 386 : break;
4829 :
4830 : /*
4831 : * These subcommands affect implicit row type conversion. They
4832 : * have affects similar to CREATE/DROP CAST on queries. don't
4833 : * provide for invalidating parse trees as a result of such
4834 : * changes, so we keep these at AccessExclusiveLock.
4835 : */
4836 46 : case AT_AddOf:
4837 : case AT_DropOf:
4838 46 : cmd_lockmode = AccessExclusiveLock;
4839 46 : break;
4840 :
4841 : /*
4842 : * Only used by CREATE OR REPLACE VIEW which must conflict
4843 : * with an SELECTs currently using the view.
4844 : */
4845 137 : case AT_ReplaceRelOptions:
4846 137 : cmd_lockmode = AccessExclusiveLock;
4847 137 : break;
4848 :
4849 : /*
4850 : * These subcommands affect general strategies for performance
4851 : * and maintenance, though don't change the semantic results
4852 : * from normal data reads and writes. Delaying an ALTER TABLE
4853 : * behind currently active writes only delays the point where
4854 : * the new strategy begins to take effect, so there is no
4855 : * benefit in waiting. In this case the minimum restriction
4856 : * applies: we don't currently allow concurrent catalog
4857 : * updates.
4858 : */
4859 158 : case AT_SetStatistics: /* Uses MVCC in getTableAttrs() */
4860 : case AT_ClusterOn: /* Uses MVCC in getIndexes() */
4861 : case AT_DropCluster: /* Uses MVCC in getIndexes() */
4862 : case AT_SetOptions: /* Uses MVCC in getTableAttrs() */
4863 : case AT_ResetOptions: /* Uses MVCC in getTableAttrs() */
4864 158 : cmd_lockmode = ShareUpdateExclusiveLock;
4865 158 : break;
4866 :
4867 75 : case AT_SetLogged:
4868 : case AT_SetUnLogged:
4869 75 : cmd_lockmode = AccessExclusiveLock;
4870 75 : break;
4871 :
4872 275 : case AT_ValidateConstraint: /* Uses MVCC in getConstraints() */
4873 275 : cmd_lockmode = ShareUpdateExclusiveLock;
4874 275 : break;
4875 :
4876 : /*
4877 : * Rel options are more complex than first appears. Options
4878 : * are set here for tables, views and indexes; for historical
4879 : * reasons these can all be used with ALTER TABLE, so we can't
4880 : * decide between them using the basic grammar.
4881 : */
4882 498 : case AT_SetRelOptions: /* Uses MVCC in getIndexes() and
4883 : * getTables() */
4884 : case AT_ResetRelOptions: /* Uses MVCC in getIndexes() and
4885 : * getTables() */
4886 498 : cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
4887 498 : break;
4888 :
4889 1905 : case AT_AttachPartition:
4890 1905 : cmd_lockmode = ShareUpdateExclusiveLock;
4891 1905 : break;
4892 :
4893 385 : case AT_DetachPartition:
4894 385 : if (((PartitionCmd *) cmd->def)->concurrent)
4895 87 : cmd_lockmode = ShareUpdateExclusiveLock;
4896 : else
4897 298 : cmd_lockmode = AccessExclusiveLock;
4898 385 : break;
4899 :
4900 11 : case AT_DetachPartitionFinalize:
4901 11 : cmd_lockmode = ShareUpdateExclusiveLock;
4902 11 : break;
4903 :
4904 497 : case AT_MergePartitions:
4905 : case AT_SplitPartition:
4906 497 : cmd_lockmode = AccessExclusiveLock;
4907 497 : break;
4908 :
4909 0 : default: /* oops */
4910 0 : elog(ERROR, "unrecognized alter table type: %d",
4911 : (int) cmd->subtype);
4912 : break;
4913 : }
4914 :
4915 : /*
4916 : * Take the greatest lockmode from any subcommand
4917 : */
4918 22808 : if (cmd_lockmode > lockmode)
4919 19126 : lockmode = cmd_lockmode;
4920 : }
4921 :
4922 21942 : return lockmode;
4923 : }
4924 :
4925 : /*
4926 : * ATController provides top level control over the phases.
4927 : *
4928 : * parsetree is passed in to allow it to be passed to event triggers
4929 : * when requested.
4930 : */
4931 : static void
4932 21755 : ATController(AlterTableStmt *parsetree,
4933 : Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
4934 : AlterTableUtilityContext *context)
4935 : {
4936 21755 : List *wqueue = NIL;
4937 : ListCell *lcmd;
4938 :
4939 : /* Phase 1: preliminary examination of commands, create work queue */
4940 44075 : foreach(lcmd, cmds)
4941 : {
4942 22617 : AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
4943 :
4944 22617 : ATPrepCmd(&wqueue, rel, cmd, recurse, false, lockmode, context);
4945 : }
4946 :
4947 : /* Close the relation, but keep lock until commit */
4948 21458 : relation_close(rel, NoLock);
4949 :
4950 : /* Phase 2: update system catalogs */
4951 21458 : ATRewriteCatalogs(&wqueue, lockmode, context);
4952 :
4953 : /* Phase 3: scan/rewrite tables as needed, and run afterStmts */
4954 19259 : ATRewriteTables(parsetree, &wqueue, lockmode, context);
4955 18866 : }
4956 :
4957 : /*
4958 : * ATPrepCmd
4959 : *
4960 : * Traffic cop for ALTER TABLE Phase 1 operations, including simple
4961 : * recursion and permission checks.
4962 : *
4963 : * Caller must have acquired appropriate lock type on relation already.
4964 : * This lock should be held until commit.
4965 : */
4966 : static void
4967 23236 : ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
4968 : bool recurse, bool recursing, LOCKMODE lockmode,
4969 : AlterTableUtilityContext *context)
4970 : {
4971 : AlteredTableInfo *tab;
4972 23236 : AlterTablePass pass = AT_PASS_UNSET;
4973 :
4974 : /* Find or create work queue entry for this table */
4975 23236 : tab = ATGetQueueEntry(wqueue, rel);
4976 :
4977 : /*
4978 : * Disallow any ALTER TABLE other than ALTER TABLE DETACH FINALIZE on
4979 : * partitions that are pending detach.
4980 : */
4981 23236 : if (rel->rd_rel->relispartition &&
4982 1806 : cmd->subtype != AT_DetachPartitionFinalize &&
4983 903 : PartitionHasPendingDetach(RelationGetRelid(rel)))
4984 1 : ereport(ERROR,
4985 : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
4986 : errmsg("cannot alter partition \"%s\" with an incomplete detach",
4987 : RelationGetRelationName(rel)),
4988 : errhint("Use ALTER TABLE ... DETACH PARTITION ... FINALIZE to complete the pending detach operation."));
4989 :
4990 : /*
4991 : * Copy the original subcommand for each table, so we can scribble on it.
4992 : * This avoids conflicts when different child tables need to make
4993 : * different parse transformations (for example, the same column may have
4994 : * different column numbers in different children).
4995 : */
4996 23235 : cmd = copyObject(cmd);
4997 :
4998 : /*
4999 : * Do permissions and relkind checking, recursion to child tables if
5000 : * needed, and any additional phase-1 processing needed. (But beware of
5001 : * adding any processing that looks at table details that another
5002 : * subcommand could change. In some cases we reject multiple subcommands
5003 : * that could try to change the same state in contrary ways.)
5004 : */
5005 23235 : switch (cmd->subtype)
5006 : {
5007 1589 : case AT_AddColumn: /* ADD COLUMN */
5008 1589 : ATSimplePermissions(cmd->subtype, rel,
5009 : ATT_TABLE | ATT_PARTITIONED_TABLE |
5010 : ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
5011 1589 : ATPrepAddColumn(wqueue, rel, recurse, recursing, false, cmd,
5012 : lockmode, context);
5013 : /* Recursion occurs during execution phase */
5014 1581 : pass = AT_PASS_ADD_COL;
5015 1581 : break;
5016 21 : case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */
5017 21 : ATSimplePermissions(cmd->subtype, rel, ATT_VIEW);
5018 21 : ATPrepAddColumn(wqueue, rel, recurse, recursing, true, cmd,
5019 : lockmode, context);
5020 : /* Recursion occurs during execution phase */
5021 21 : pass = AT_PASS_ADD_COL;
5022 21 : break;
5023 407 : case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */
5024 :
5025 : /*
5026 : * We allow defaults on views so that INSERT into a view can have
5027 : * default-ish behavior. This works because the rewriter
5028 : * substitutes default values into INSERTs before it expands
5029 : * rules.
5030 : */
5031 407 : ATSimplePermissions(cmd->subtype, rel,
5032 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
5033 : ATT_FOREIGN_TABLE);
5034 407 : ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
5035 : /* No command-specific prep needed */
5036 407 : pass = cmd->def ? AT_PASS_ADD_OTHERCONSTR : AT_PASS_DROP;
5037 407 : break;
5038 53 : case AT_CookedColumnDefault: /* add a pre-cooked default */
5039 : /* This is currently used only in CREATE TABLE */
5040 : /* (so the permission check really isn't necessary) */
5041 53 : ATSimplePermissions(cmd->subtype, rel,
5042 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5043 : /* This command never recurses */
5044 53 : pass = AT_PASS_ADD_OTHERCONSTR;
5045 53 : break;
5046 107 : case AT_AddIdentity:
5047 107 : ATSimplePermissions(cmd->subtype, rel,
5048 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
5049 : ATT_FOREIGN_TABLE);
5050 : /* Set up recursion for phase 2; no other prep needed */
5051 107 : if (recurse)
5052 103 : cmd->recurse = true;
5053 107 : pass = AT_PASS_ADD_OTHERCONSTR;
5054 107 : break;
5055 41 : case AT_SetIdentity:
5056 41 : ATSimplePermissions(cmd->subtype, rel,
5057 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
5058 : ATT_FOREIGN_TABLE);
5059 : /* Set up recursion for phase 2; no other prep needed */
5060 41 : if (recurse)
5061 37 : cmd->recurse = true;
5062 : /* This should run after AddIdentity, so do it in MISC pass */
5063 41 : pass = AT_PASS_MISC;
5064 41 : break;
5065 37 : case AT_DropIdentity:
5066 37 : ATSimplePermissions(cmd->subtype, rel,
5067 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
5068 : ATT_FOREIGN_TABLE);
5069 : /* Set up recursion for phase 2; no other prep needed */
5070 37 : if (recurse)
5071 33 : cmd->recurse = true;
5072 37 : pass = AT_PASS_DROP;
5073 37 : break;
5074 181 : case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
5075 181 : ATSimplePermissions(cmd->subtype, rel,
5076 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5077 : /* Set up recursion for phase 2; no other prep needed */
5078 177 : if (recurse)
5079 165 : cmd->recurse = true;
5080 177 : pass = AT_PASS_DROP;
5081 177 : break;
5082 276 : case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
5083 276 : ATSimplePermissions(cmd->subtype, rel,
5084 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5085 : /* Set up recursion for phase 2; no other prep needed */
5086 272 : if (recurse)
5087 252 : cmd->recurse = true;
5088 272 : pass = AT_PASS_COL_ATTRS;
5089 272 : break;
5090 169 : case AT_SetExpression: /* ALTER COLUMN SET EXPRESSION */
5091 169 : ATSimplePermissions(cmd->subtype, rel,
5092 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5093 169 : ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
5094 169 : pass = AT_PASS_SET_EXPRESSION;
5095 169 : break;
5096 57 : case AT_DropExpression: /* ALTER COLUMN DROP EXPRESSION */
5097 57 : ATSimplePermissions(cmd->subtype, rel,
5098 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5099 57 : ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
5100 57 : ATPrepDropExpression(rel, cmd, recurse, recursing, lockmode);
5101 41 : pass = AT_PASS_DROP;
5102 41 : break;
5103 111 : case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */
5104 111 : ATSimplePermissions(cmd->subtype, rel,
5105 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_MATVIEW |
5106 : ATT_INDEX | ATT_PARTITIONED_INDEX | ATT_FOREIGN_TABLE);
5107 111 : ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
5108 : /* No command-specific prep needed */
5109 111 : pass = AT_PASS_MISC;
5110 111 : break;
5111 29 : case AT_SetOptions: /* ALTER COLUMN SET ( options ) */
5112 : case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */
5113 29 : ATSimplePermissions(cmd->subtype, rel,
5114 : ATT_TABLE | ATT_PARTITIONED_TABLE |
5115 : ATT_MATVIEW | ATT_FOREIGN_TABLE);
5116 : /* This command never recurses */
5117 21 : pass = AT_PASS_MISC;
5118 21 : break;
5119 181 : case AT_SetStorage: /* ALTER COLUMN SET STORAGE */
5120 181 : ATSimplePermissions(cmd->subtype, rel,
5121 : ATT_TABLE | ATT_PARTITIONED_TABLE |
5122 : ATT_MATVIEW | ATT_FOREIGN_TABLE);
5123 181 : ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
5124 : /* No command-specific prep needed */
5125 181 : pass = AT_PASS_MISC;
5126 181 : break;
5127 48 : case AT_SetCompression: /* ALTER COLUMN SET COMPRESSION */
5128 48 : ATSimplePermissions(cmd->subtype, rel,
5129 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_MATVIEW);
5130 : /* This command never recurses */
5131 : /* No command-specific prep needed */
5132 48 : pass = AT_PASS_MISC;
5133 48 : break;
5134 1147 : case AT_DropColumn: /* DROP COLUMN */
5135 1147 : ATSimplePermissions(cmd->subtype, rel,
5136 : ATT_TABLE | ATT_PARTITIONED_TABLE |
5137 : ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
5138 1143 : ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd,
5139 : lockmode, context);
5140 : /* Recursion occurs during execution phase */
5141 1135 : pass = AT_PASS_DROP;
5142 1135 : break;
5143 0 : case AT_AddIndex: /* ADD INDEX */
5144 0 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_PARTITIONED_TABLE);
5145 : /* This command never recurses */
5146 : /* No command-specific prep needed */
5147 0 : pass = AT_PASS_ADD_INDEX;
5148 0 : break;
5149 10394 : case AT_AddConstraint: /* ADD CONSTRAINT */
5150 10394 : ATSimplePermissions(cmd->subtype, rel,
5151 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5152 10394 : ATPrepAddPrimaryKey(wqueue, rel, cmd, recurse, lockmode, context);
5153 10374 : if (recurse)
5154 : {
5155 : /* recurses at exec time; lock descendants and set flag */
5156 10140 : (void) find_all_inheritors(RelationGetRelid(rel), lockmode, NULL);
5157 10140 : cmd->recurse = true;
5158 : }
5159 10374 : pass = AT_PASS_ADD_CONSTR;
5160 10374 : break;
5161 0 : case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */
5162 0 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_PARTITIONED_TABLE);
5163 : /* This command never recurses */
5164 : /* No command-specific prep needed */
5165 0 : pass = AT_PASS_ADD_INDEXCONSTR;
5166 0 : break;
5167 578 : case AT_DropConstraint: /* DROP CONSTRAINT */
5168 578 : ATSimplePermissions(cmd->subtype, rel,
5169 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5170 578 : ATCheckPartitionsNotInUse(rel, lockmode);
5171 : /* Other recursion occurs during execution phase */
5172 : /* No command-specific prep needed except saving recurse flag */
5173 574 : if (recurse)
5174 550 : cmd->recurse = true;
5175 574 : pass = AT_PASS_DROP;
5176 574 : break;
5177 951 : case AT_AlterColumnType: /* ALTER COLUMN TYPE */
5178 951 : ATSimplePermissions(cmd->subtype, rel,
5179 : ATT_TABLE | ATT_PARTITIONED_TABLE |
5180 : ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
5181 : /* See comments for ATPrepAlterColumnType */
5182 951 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, recurse, lockmode,
5183 : AT_PASS_UNSET, context);
5184 : Assert(cmd != NULL);
5185 : /* Performs own recursion */
5186 947 : ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd,
5187 : lockmode, context);
5188 816 : pass = AT_PASS_ALTER_TYPE;
5189 816 : break;
5190 94 : case AT_AlterColumnGenericOptions:
5191 94 : ATSimplePermissions(cmd->subtype, rel, ATT_FOREIGN_TABLE);
5192 : /* This command never recurses */
5193 : /* No command-specific prep needed */
5194 94 : pass = AT_PASS_MISC;
5195 94 : break;
5196 1141 : case AT_ChangeOwner: /* ALTER OWNER */
5197 : /* This command never recurses */
5198 : /* No command-specific prep needed */
5199 1141 : pass = AT_PASS_MISC;
5200 1141 : break;
5201 43 : case AT_ClusterOn: /* CLUSTER ON */
5202 : case AT_DropCluster: /* SET WITHOUT CLUSTER */
5203 43 : ATSimplePermissions(cmd->subtype, rel,
5204 : ATT_TABLE | ATT_MATVIEW);
5205 : /* These commands never recurse */
5206 : /* No command-specific prep needed */
5207 35 : pass = AT_PASS_MISC;
5208 35 : break;
5209 75 : case AT_SetLogged: /* SET LOGGED */
5210 : case AT_SetUnLogged: /* SET UNLOGGED */
5211 75 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_SEQUENCE);
5212 67 : if (tab->chgPersistence)
5213 0 : ereport(ERROR,
5214 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5215 : errmsg("cannot change persistence setting twice")));
5216 67 : ATPrepChangePersistence(tab, rel, cmd->subtype == AT_SetLogged);
5217 59 : pass = AT_PASS_MISC;
5218 59 : break;
5219 4 : case AT_DropOids: /* SET WITHOUT OIDS */
5220 4 : ATSimplePermissions(cmd->subtype, rel,
5221 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5222 4 : pass = AT_PASS_DROP;
5223 4 : break;
5224 85 : case AT_SetAccessMethod: /* SET ACCESS METHOD */
5225 85 : ATSimplePermissions(cmd->subtype, rel,
5226 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_MATVIEW);
5227 :
5228 : /* check if another access method change was already requested */
5229 85 : if (tab->chgAccessMethod)
5230 12 : ereport(ERROR,
5231 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5232 : errmsg("cannot have multiple SET ACCESS METHOD subcommands")));
5233 :
5234 73 : ATPrepSetAccessMethod(tab, rel, cmd->name);
5235 73 : pass = AT_PASS_MISC; /* does not matter; no work in Phase 2 */
5236 73 : break;
5237 99 : case AT_SetTableSpace: /* SET TABLESPACE */
5238 99 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_PARTITIONED_TABLE |
5239 : ATT_MATVIEW | ATT_INDEX | ATT_PARTITIONED_INDEX);
5240 : /* This command never recurses */
5241 99 : ATPrepSetTableSpace(tab, rel, cmd->name, lockmode);
5242 99 : pass = AT_PASS_MISC; /* doesn't actually matter */
5243 99 : break;
5244 633 : case AT_SetRelOptions: /* SET (...) */
5245 : case AT_ResetRelOptions: /* RESET (...) */
5246 : case AT_ReplaceRelOptions: /* reset them all, then set just these */
5247 633 : ATSimplePermissions(cmd->subtype, rel,
5248 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
5249 : ATT_MATVIEW | ATT_INDEX);
5250 : /* This command never recurses */
5251 : /* No command-specific prep needed */
5252 632 : pass = AT_PASS_MISC;
5253 632 : break;
5254 305 : case AT_AddInherit: /* INHERIT */
5255 305 : ATSimplePermissions(cmd->subtype, rel,
5256 : ATT_TABLE | ATT_FOREIGN_TABLE);
5257 : /* This command never recurses */
5258 301 : ATPrepChangeInherit(rel);
5259 289 : pass = AT_PASS_MISC;
5260 289 : break;
5261 81 : case AT_DropInherit: /* NO INHERIT */
5262 81 : ATSimplePermissions(cmd->subtype, rel,
5263 : ATT_TABLE | ATT_FOREIGN_TABLE);
5264 : /* This command never recurses */
5265 77 : ATPrepChangeInherit(rel);
5266 69 : pass = AT_PASS_MISC;
5267 69 : break;
5268 300 : case AT_AlterConstraint: /* ALTER CONSTRAINT */
5269 300 : ATSimplePermissions(cmd->subtype, rel,
5270 : ATT_TABLE | ATT_PARTITIONED_TABLE);
5271 : /* Recursion occurs during execution phase */
5272 296 : if (recurse)
5273 288 : cmd->recurse = true;
5274 296 : pass = AT_PASS_MISC;
5275 296 : break;
5276 275 : case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
5277 275 : ATSimplePermissions(cmd->subtype, rel,
5278 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5279 : /* Recursion occurs during execution phase */
5280 : /* No command-specific prep needed except saving recurse flag */
5281 275 : if (recurse)
5282 275 : cmd->recurse = true;
5283 275 : pass = AT_PASS_MISC;
5284 275 : break;
5285 306 : case AT_ReplicaIdentity: /* REPLICA IDENTITY ... */
5286 306 : ATSimplePermissions(cmd->subtype, rel,
5287 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_MATVIEW);
5288 306 : pass = AT_PASS_MISC;
5289 : /* This command never recurses */
5290 : /* No command-specific prep needed */
5291 306 : break;
5292 191 : case AT_EnableTrig: /* ENABLE TRIGGER variants */
5293 : case AT_EnableAlwaysTrig:
5294 : case AT_EnableReplicaTrig:
5295 : case AT_EnableTrigAll:
5296 : case AT_EnableTrigUser:
5297 : case AT_DisableTrig: /* DISABLE TRIGGER variants */
5298 : case AT_DisableTrigAll:
5299 : case AT_DisableTrigUser:
5300 191 : ATSimplePermissions(cmd->subtype, rel,
5301 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5302 : /* Set up recursion for phase 2; no other prep needed */
5303 191 : if (recurse)
5304 174 : cmd->recurse = true;
5305 191 : pass = AT_PASS_MISC;
5306 191 : break;
5307 411 : case AT_EnableRule: /* ENABLE/DISABLE RULE variants */
5308 : case AT_EnableAlwaysRule:
5309 : case AT_EnableReplicaRule:
5310 : case AT_DisableRule:
5311 : case AT_AddOf: /* OF */
5312 : case AT_DropOf: /* NOT OF */
5313 : case AT_EnableRowSecurity:
5314 : case AT_DisableRowSecurity:
5315 : case AT_ForceRowSecurity:
5316 : case AT_NoForceRowSecurity:
5317 411 : ATSimplePermissions(cmd->subtype, rel,
5318 : ATT_TABLE | ATT_PARTITIONED_TABLE);
5319 : /* These commands never recurse */
5320 : /* No command-specific prep needed */
5321 411 : pass = AT_PASS_MISC;
5322 411 : break;
5323 33 : case AT_GenericOptions:
5324 33 : ATSimplePermissions(cmd->subtype, rel, ATT_FOREIGN_TABLE);
5325 : /* No command-specific prep needed */
5326 33 : pass = AT_PASS_MISC;
5327 33 : break;
5328 1897 : case AT_AttachPartition:
5329 1897 : ATSimplePermissions(cmd->subtype, rel,
5330 : ATT_PARTITIONED_TABLE | ATT_PARTITIONED_INDEX);
5331 : /* No command-specific prep needed */
5332 1893 : pass = AT_PASS_MISC;
5333 1893 : break;
5334 385 : case AT_DetachPartition:
5335 385 : ATSimplePermissions(cmd->subtype, rel, ATT_PARTITIONED_TABLE);
5336 : /* No command-specific prep needed */
5337 373 : pass = AT_PASS_MISC;
5338 373 : break;
5339 11 : case AT_DetachPartitionFinalize:
5340 11 : ATSimplePermissions(cmd->subtype, rel, ATT_PARTITIONED_TABLE);
5341 : /* No command-specific prep needed */
5342 7 : pass = AT_PASS_MISC;
5343 7 : break;
5344 489 : case AT_MergePartitions:
5345 : case AT_SplitPartition:
5346 489 : ATSimplePermissions(cmd->subtype, rel, ATT_PARTITIONED_TABLE);
5347 : /* No command-specific prep needed */
5348 485 : pass = AT_PASS_MISC;
5349 485 : break;
5350 0 : default: /* oops */
5351 0 : elog(ERROR, "unrecognized alter table type: %d",
5352 : (int) cmd->subtype);
5353 : pass = AT_PASS_UNSET; /* keep compiler quiet */
5354 : break;
5355 : }
5356 : Assert(pass > AT_PASS_UNSET);
5357 :
5358 : /* Add the subcommand to the appropriate list for phase 2 */
5359 22931 : tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd);
5360 22931 : }
5361 :
5362 : /*
5363 : * ATRewriteCatalogs
5364 : *
5365 : * Traffic cop for ALTER TABLE Phase 2 operations. Subcommands are
5366 : * dispatched in a "safe" execution order (designed to avoid unnecessary
5367 : * conflicts).
5368 : */
5369 : static void
5370 21458 : ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
5371 : AlterTableUtilityContext *context)
5372 : {
5373 : ListCell *ltab;
5374 :
5375 : /*
5376 : * We process all the tables "in parallel", one pass at a time. This is
5377 : * needed because we may have to propagate work from one table to another
5378 : * (specifically, ALTER TYPE on a foreign key's PK has to dispatch the
5379 : * re-adding of the foreign key constraint to the other table). Work can
5380 : * only be propagated into later passes, however.
5381 : */
5382 269512 : for (AlterTablePass pass = 0; pass < AT_NUM_PASSES; pass++)
5383 : {
5384 : /* Go through each table that needs to be processed */
5385 509770 : foreach(ltab, *wqueue)
5386 : {
5387 261716 : AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
5388 261716 : List *subcmds = tab->subcmds[pass];
5389 : ListCell *lcmd;
5390 :
5391 261716 : if (subcmds == NIL)
5392 225067 : continue;
5393 :
5394 : /*
5395 : * Open the relation and store it in tab. This allows subroutines
5396 : * close and reopen, if necessary. Appropriate lock was obtained
5397 : * by phase 1, needn't get it again.
5398 : */
5399 36649 : tab->rel = relation_open(tab->relid, NoLock);
5400 :
5401 73497 : foreach(lcmd, subcmds)
5402 39047 : ATExecCmd(wqueue, tab,
5403 39047 : lfirst_node(AlterTableCmd, lcmd),
5404 : lockmode, pass, context);
5405 :
5406 : /*
5407 : * After the ALTER TYPE or SET EXPRESSION pass, do cleanup work
5408 : * (this is not done in ATExecAlterColumnType since it should be
5409 : * done only once if multiple columns of a table are altered).
5410 : */
5411 34450 : if (pass == AT_PASS_ALTER_TYPE || pass == AT_PASS_SET_EXPRESSION)
5412 889 : ATPostAlterTypeCleanup(wqueue, tab, lockmode);
5413 :
5414 34450 : if (tab->rel)
5415 : {
5416 34450 : relation_close(tab->rel, NoLock);
5417 34450 : tab->rel = NULL;
5418 : }
5419 : }
5420 : }
5421 :
5422 : /* Check to see if a toast table must be added. */
5423 41568 : foreach(ltab, *wqueue)
5424 : {
5425 22309 : AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
5426 :
5427 : /*
5428 : * If the table is source table of ATTACH PARTITION command, we did
5429 : * not modify anything about it that will change its toasting
5430 : * requirement, so no need to check.
5431 : */
5432 22309 : if (((tab->relkind == RELKIND_RELATION ||
5433 4376 : tab->relkind == RELKIND_PARTITIONED_TABLE) &&
5434 21081 : tab->partition_constraint == NULL) ||
5435 2635 : tab->relkind == RELKIND_MATVIEW)
5436 19703 : AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
5437 : }
5438 19259 : }
5439 :
5440 : /*
5441 : * ATExecCmd: dispatch a subcommand to appropriate execution routine
5442 : */
5443 : static void
5444 39047 : ATExecCmd(List **wqueue, AlteredTableInfo *tab,
5445 : AlterTableCmd *cmd, LOCKMODE lockmode, AlterTablePass cur_pass,
5446 : AlterTableUtilityContext *context)
5447 : {
5448 39047 : ObjectAddress address = InvalidObjectAddress;
5449 39047 : Relation rel = tab->rel;
5450 :
5451 39047 : switch (cmd->subtype)
5452 : {
5453 1598 : case AT_AddColumn: /* ADD COLUMN */
5454 : case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */
5455 1598 : address = ATExecAddColumn(wqueue, tab, rel, &cmd,
5456 1598 : cmd->recurse, false,
5457 : lockmode, cur_pass, context);
5458 1430 : break;
5459 383 : case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */
5460 383 : address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
5461 339 : break;
5462 53 : case AT_CookedColumnDefault: /* add a pre-cooked default */
5463 53 : address = ATExecCookedColumnDefault(rel, cmd->num, cmd->def);
5464 53 : break;
5465 107 : case AT_AddIdentity:
5466 107 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5467 : cur_pass, context);
5468 : Assert(cmd != NULL);
5469 99 : address = ATExecAddIdentity(rel, cmd->name, cmd->def, lockmode, cmd->recurse, false);
5470 63 : break;
5471 41 : case AT_SetIdentity:
5472 41 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5473 : cur_pass, context);
5474 : Assert(cmd != NULL);
5475 41 : address = ATExecSetIdentity(rel, cmd->name, cmd->def, lockmode, cmd->recurse, false);
5476 25 : break;
5477 37 : case AT_DropIdentity:
5478 37 : address = ATExecDropIdentity(rel, cmd->name, cmd->missing_ok, lockmode, cmd->recurse, false);
5479 25 : break;
5480 177 : case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
5481 177 : address = ATExecDropNotNull(rel, cmd->name, cmd->recurse, lockmode);
5482 109 : break;
5483 272 : case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
5484 272 : address = ATExecSetNotNull(wqueue, rel, NULL, cmd->name,
5485 272 : cmd->recurse, false, lockmode);
5486 252 : break;
5487 169 : case AT_SetExpression:
5488 169 : address = ATExecSetExpression(tab, rel, cmd->name, cmd->def, lockmode);
5489 157 : break;
5490 37 : case AT_DropExpression:
5491 37 : address = ATExecDropExpression(rel, cmd->name, cmd->missing_ok, lockmode);
5492 21 : break;
5493 111 : case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */
5494 111 : address = ATExecSetStatistics(rel, cmd->name, cmd->num, cmd->def, lockmode);
5495 79 : break;
5496 17 : case AT_SetOptions: /* ALTER COLUMN SET ( options ) */
5497 17 : address = ATExecSetOptions(rel, cmd->name, cmd->def, false, lockmode);
5498 17 : break;
5499 4 : case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */
5500 4 : address = ATExecSetOptions(rel, cmd->name, cmd->def, true, lockmode);
5501 4 : break;
5502 181 : case AT_SetStorage: /* ALTER COLUMN SET STORAGE */
5503 181 : address = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
5504 173 : break;
5505 48 : case AT_SetCompression: /* ALTER COLUMN SET COMPRESSION */
5506 48 : address = ATExecSetCompression(rel, cmd->name, cmd->def,
5507 : lockmode);
5508 44 : break;
5509 1135 : case AT_DropColumn: /* DROP COLUMN */
5510 1135 : address = ATExecDropColumn(wqueue, rel, cmd->name,
5511 1135 : cmd->behavior, cmd->recurse, false,
5512 1135 : cmd->missing_ok, lockmode,
5513 : NULL);
5514 1007 : break;
5515 752 : case AT_AddIndex: /* ADD INDEX */
5516 752 : address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
5517 : lockmode);
5518 639 : break;
5519 306 : case AT_ReAddIndex: /* ADD INDEX */
5520 306 : address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true,
5521 : lockmode);
5522 306 : break;
5523 53 : case AT_ReAddStatistics: /* ADD STATISTICS */
5524 53 : address = ATExecAddStatistics(tab, rel, (CreateStatsStmt *) cmd->def,
5525 : true, lockmode);
5526 53 : break;
5527 18452 : case AT_AddConstraint: /* ADD CONSTRAINT */
5528 : /* Transform the command only during initial examination */
5529 18452 : if (cur_pass == AT_PASS_ADD_CONSTR)
5530 10354 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd,
5531 10374 : cmd->recurse, lockmode,
5532 : cur_pass, context);
5533 : /* Depending on constraint type, might be no more work to do now */
5534 18432 : if (cmd != NULL)
5535 : address =
5536 8078 : ATExecAddConstraint(wqueue, tab, rel,
5537 8078 : (Constraint *) cmd->def,
5538 8078 : cmd->recurse, false, lockmode);
5539 17975 : break;
5540 257 : case AT_ReAddConstraint: /* Re-add pre-existing check constraint */
5541 : address =
5542 257 : ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
5543 : true, true, lockmode);
5544 249 : break;
5545 9 : case AT_ReAddDomainConstraint: /* Re-add pre-existing domain check
5546 : * constraint */
5547 : address =
5548 9 : AlterDomainAddConstraint(((AlterDomainStmt *) cmd->def)->typeName,
5549 9 : ((AlterDomainStmt *) cmd->def)->def,
5550 : NULL);
5551 5 : break;
5552 52 : case AT_ReAddComment: /* Re-add existing comment */
5553 52 : address = CommentObject((CommentStmt *) cmd->def);
5554 52 : break;
5555 6653 : case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */
5556 6653 : address = ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def,
5557 : lockmode);
5558 6645 : break;
5559 296 : case AT_AlterConstraint: /* ALTER CONSTRAINT */
5560 296 : address = ATExecAlterConstraint(wqueue, rel,
5561 296 : castNode(ATAlterConstraint, cmd->def),
5562 296 : cmd->recurse, lockmode);
5563 240 : break;
5564 275 : case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
5565 275 : address = ATExecValidateConstraint(wqueue, rel, cmd->name, cmd->recurse,
5566 : false, lockmode);
5567 271 : break;
5568 574 : case AT_DropConstraint: /* DROP CONSTRAINT */
5569 574 : ATExecDropConstraint(rel, cmd->name, cmd->behavior,
5570 574 : cmd->recurse,
5571 574 : cmd->missing_ok, lockmode);
5572 434 : break;
5573 792 : case AT_AlterColumnType: /* ALTER COLUMN TYPE */
5574 : /* parse transformation was done earlier */
5575 792 : address = ATExecAlterColumnType(tab, rel, cmd, lockmode);
5576 764 : break;
5577 94 : case AT_AlterColumnGenericOptions: /* ALTER COLUMN OPTIONS */
5578 : address =
5579 94 : ATExecAlterColumnGenericOptions(rel, cmd->name,
5580 94 : (List *) cmd->def, lockmode);
5581 90 : break;
5582 1141 : case AT_ChangeOwner: /* ALTER OWNER */
5583 1138 : ATExecChangeOwner(RelationGetRelid(rel),
5584 1141 : get_rolespec_oid(cmd->newowner, false),
5585 : false, lockmode);
5586 1130 : break;
5587 39 : case AT_ClusterOn: /* CLUSTER ON */
5588 39 : address = ATExecClusterOn(rel, cmd->name, lockmode);
5589 39 : break;
5590 8 : case AT_DropCluster: /* SET WITHOUT CLUSTER */
5591 8 : ATExecDropCluster(rel, lockmode);
5592 8 : break;
5593 59 : case AT_SetLogged: /* SET LOGGED */
5594 : case AT_SetUnLogged: /* SET UNLOGGED */
5595 59 : break;
5596 4 : case AT_DropOids: /* SET WITHOUT OIDS */
5597 : /* nothing to do here, oid columns don't exist anymore */
5598 4 : break;
5599 61 : case AT_SetAccessMethod: /* SET ACCESS METHOD */
5600 :
5601 : /*
5602 : * Only do this for partitioned tables, for which this is just a
5603 : * catalog change. Tables with storage are handled by Phase 3.
5604 : */
5605 61 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
5606 33 : tab->chgAccessMethod)
5607 29 : ATExecSetAccessMethodNoStorage(rel, tab->newAccessMethod);
5608 61 : break;
5609 99 : case AT_SetTableSpace: /* SET TABLESPACE */
5610 :
5611 : /*
5612 : * Only do this for partitioned tables and indexes, for which this
5613 : * is just a catalog change. Other relation types which have
5614 : * storage are handled by Phase 3.
5615 : */
5616 99 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
5617 91 : rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
5618 24 : ATExecSetTableSpaceNoStorage(rel, tab->newTableSpace);
5619 :
5620 95 : break;
5621 632 : case AT_SetRelOptions: /* SET (...) */
5622 : case AT_ResetRelOptions: /* RESET (...) */
5623 : case AT_ReplaceRelOptions: /* replace entire option list */
5624 632 : ATExecSetRelOptions(rel, (List *) cmd->def, cmd->subtype, lockmode);
5625 598 : break;
5626 65 : case AT_EnableTrig: /* ENABLE TRIGGER name */
5627 65 : ATExecEnableDisableTrigger(rel, cmd->name,
5628 : TRIGGER_FIRES_ON_ORIGIN, false,
5629 65 : cmd->recurse,
5630 : lockmode);
5631 65 : break;
5632 27 : case AT_EnableAlwaysTrig: /* ENABLE ALWAYS TRIGGER name */
5633 27 : ATExecEnableDisableTrigger(rel, cmd->name,
5634 : TRIGGER_FIRES_ALWAYS, false,
5635 27 : cmd->recurse,
5636 : lockmode);
5637 27 : break;
5638 8 : case AT_EnableReplicaTrig: /* ENABLE REPLICA TRIGGER name */
5639 8 : ATExecEnableDisableTrigger(rel, cmd->name,
5640 : TRIGGER_FIRES_ON_REPLICA, false,
5641 8 : cmd->recurse,
5642 : lockmode);
5643 8 : break;
5644 75 : case AT_DisableTrig: /* DISABLE TRIGGER name */
5645 75 : ATExecEnableDisableTrigger(rel, cmd->name,
5646 : TRIGGER_DISABLED, false,
5647 75 : cmd->recurse,
5648 : lockmode);
5649 75 : break;
5650 0 : case AT_EnableTrigAll: /* ENABLE TRIGGER ALL */
5651 0 : ATExecEnableDisableTrigger(rel, NULL,
5652 : TRIGGER_FIRES_ON_ORIGIN, false,
5653 0 : cmd->recurse,
5654 : lockmode);
5655 0 : break;
5656 8 : case AT_DisableTrigAll: /* DISABLE TRIGGER ALL */
5657 8 : ATExecEnableDisableTrigger(rel, NULL,
5658 : TRIGGER_DISABLED, false,
5659 8 : cmd->recurse,
5660 : lockmode);
5661 8 : break;
5662 0 : case AT_EnableTrigUser: /* ENABLE TRIGGER USER */
5663 0 : ATExecEnableDisableTrigger(rel, NULL,
5664 : TRIGGER_FIRES_ON_ORIGIN, true,
5665 0 : cmd->recurse,
5666 : lockmode);
5667 0 : break;
5668 8 : case AT_DisableTrigUser: /* DISABLE TRIGGER USER */
5669 8 : ATExecEnableDisableTrigger(rel, NULL,
5670 : TRIGGER_DISABLED, true,
5671 8 : cmd->recurse,
5672 : lockmode);
5673 8 : break;
5674 :
5675 5 : case AT_EnableRule: /* ENABLE RULE name */
5676 5 : ATExecEnableDisableRule(rel, cmd->name,
5677 : RULE_FIRES_ON_ORIGIN, lockmode);
5678 5 : break;
5679 0 : case AT_EnableAlwaysRule: /* ENABLE ALWAYS RULE name */
5680 0 : ATExecEnableDisableRule(rel, cmd->name,
5681 : RULE_FIRES_ALWAYS, lockmode);
5682 0 : break;
5683 4 : case AT_EnableReplicaRule: /* ENABLE REPLICA RULE name */
5684 4 : ATExecEnableDisableRule(rel, cmd->name,
5685 : RULE_FIRES_ON_REPLICA, lockmode);
5686 4 : break;
5687 20 : case AT_DisableRule: /* DISABLE RULE name */
5688 20 : ATExecEnableDisableRule(rel, cmd->name,
5689 : RULE_DISABLED, lockmode);
5690 20 : break;
5691 :
5692 289 : case AT_AddInherit:
5693 289 : address = ATExecAddInherit(rel, (RangeVar *) cmd->def, lockmode);
5694 209 : break;
5695 69 : case AT_DropInherit:
5696 69 : address = ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode);
5697 65 : break;
5698 42 : case AT_AddOf:
5699 42 : address = ATExecAddOf(rel, (TypeName *) cmd->def, lockmode);
5700 18 : break;
5701 4 : case AT_DropOf:
5702 4 : ATExecDropOf(rel, lockmode);
5703 4 : break;
5704 318 : case AT_ReplicaIdentity:
5705 318 : ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
5706 286 : break;
5707 240 : case AT_EnableRowSecurity:
5708 240 : ATExecSetRowSecurity(rel, true);
5709 240 : break;
5710 6 : case AT_DisableRowSecurity:
5711 6 : ATExecSetRowSecurity(rel, false);
5712 6 : break;
5713 70 : case AT_ForceRowSecurity:
5714 70 : ATExecForceNoForceRowSecurity(rel, true);
5715 70 : break;
5716 20 : case AT_NoForceRowSecurity:
5717 20 : ATExecForceNoForceRowSecurity(rel, false);
5718 20 : break;
5719 33 : case AT_GenericOptions:
5720 33 : ATExecGenericOptions(rel, (List *) cmd->def);
5721 32 : break;
5722 1893 : case AT_AttachPartition:
5723 1893 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5724 : cur_pass, context);
5725 : Assert(cmd != NULL);
5726 1877 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
5727 1608 : address = ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def,
5728 : context);
5729 : else
5730 269 : address = ATExecAttachPartitionIdx(wqueue, rel,
5731 269 : ((PartitionCmd *) cmd->def)->name);
5732 1613 : break;
5733 373 : case AT_DetachPartition:
5734 373 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5735 : cur_pass, context);
5736 : Assert(cmd != NULL);
5737 : /* ATPrepCmd ensures it must be a table */
5738 : Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
5739 373 : address = ATExecDetachPartition(wqueue, tab, rel,
5740 373 : ((PartitionCmd *) cmd->def)->name,
5741 373 : ((PartitionCmd *) cmd->def)->concurrent);
5742 296 : break;
5743 7 : case AT_DetachPartitionFinalize:
5744 7 : address = ATExecDetachPartitionFinalize(rel, ((PartitionCmd *) cmd->def)->name);
5745 7 : break;
5746 200 : case AT_MergePartitions:
5747 200 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5748 : cur_pass, context);
5749 : Assert(cmd != NULL);
5750 : Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
5751 144 : ATExecMergePartitions(wqueue, tab, rel, (PartitionCmd *) cmd->def,
5752 : context);
5753 106 : break;
5754 285 : case AT_SplitPartition:
5755 285 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5756 : cur_pass, context);
5757 : Assert(cmd != NULL);
5758 : Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
5759 153 : ATExecSplitPartition(wqueue, tab, rel, (PartitionCmd *) cmd->def,
5760 : context);
5761 141 : break;
5762 0 : default: /* oops */
5763 0 : elog(ERROR, "unrecognized alter table type: %d",
5764 : (int) cmd->subtype);
5765 : break;
5766 : }
5767 :
5768 : /*
5769 : * Report the subcommand to interested event triggers.
5770 : */
5771 36848 : if (cmd)
5772 26494 : EventTriggerCollectAlterTableSubcmd((Node *) cmd, address);
5773 :
5774 : /*
5775 : * Bump the command counter to ensure the next subcommand in the sequence
5776 : * can see the changes so far
5777 : */
5778 36848 : CommandCounterIncrement();
5779 36848 : }
5780 :
5781 : /*
5782 : * ATParseTransformCmd: perform parse transformation for one subcommand
5783 : *
5784 : * Returns the transformed subcommand tree, if there is one, else NULL.
5785 : *
5786 : * The parser may hand back additional AlterTableCmd(s) and/or other
5787 : * utility statements, either before or after the original subcommand.
5788 : * Other AlterTableCmds are scheduled into the appropriate slot of the
5789 : * AlteredTableInfo (they had better be for later passes than the current one).
5790 : * Utility statements that are supposed to happen before the AlterTableCmd
5791 : * are executed immediately. Those that are supposed to happen afterwards
5792 : * are added to the tab->afterStmts list to be done at the very end.
5793 : */
5794 : static AlterTableCmd *
5795 15729 : ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
5796 : AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
5797 : AlterTablePass cur_pass, AlterTableUtilityContext *context)
5798 : {
5799 15729 : AlterTableCmd *newcmd = NULL;
5800 15729 : AlterTableStmt *atstmt = makeNode(AlterTableStmt);
5801 : List *beforeStmts;
5802 : List *afterStmts;
5803 : ListCell *lc;
5804 :
5805 : /* Gin up an AlterTableStmt with just this subcommand and this table */
5806 15729 : atstmt->relation =
5807 15729 : makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
5808 15729 : pstrdup(RelationGetRelationName(rel)),
5809 : -1);
5810 15729 : atstmt->relation->inh = recurse;
5811 15729 : atstmt->cmds = list_make1(cmd);
5812 15729 : atstmt->objtype = OBJECT_TABLE; /* needn't be picky here */
5813 15729 : atstmt->missing_ok = false;
5814 :
5815 : /* Transform the AlterTableStmt */
5816 15729 : atstmt = transformAlterTableStmt(RelationGetRelid(rel),
5817 : atstmt,
5818 : context->queryString,
5819 : &beforeStmts,
5820 : &afterStmts);
5821 :
5822 : /* Execute any statements that should happen before these subcommand(s) */
5823 15804 : foreach(lc, beforeStmts)
5824 : {
5825 315 : Node *stmt = (Node *) lfirst(lc);
5826 :
5827 315 : ProcessUtilityForAlterTable(stmt, context);
5828 307 : CommandCounterIncrement();
5829 : }
5830 :
5831 : /* Examine the transformed subcommands and schedule them appropriately */
5832 36131 : foreach(lc, atstmt->cmds)
5833 : {
5834 20642 : AlterTableCmd *cmd2 = lfirst_node(AlterTableCmd, lc);
5835 : AlterTablePass pass;
5836 :
5837 : /*
5838 : * This switch need only cover the subcommand types that can be added
5839 : * by parse_utilcmd.c; otherwise, we'll use the default strategy of
5840 : * executing the subcommand immediately, as a substitute for the
5841 : * original subcommand. (Note, however, that this does cause
5842 : * AT_AddConstraint subcommands to be rescheduled into later passes,
5843 : * which is important for index and foreign key constraints.)
5844 : *
5845 : * We assume we needn't do any phase-1 checks for added subcommands.
5846 : */
5847 20642 : switch (cmd2->subtype)
5848 : {
5849 768 : case AT_AddIndex:
5850 768 : pass = AT_PASS_ADD_INDEX;
5851 768 : break;
5852 6653 : case AT_AddIndexConstraint:
5853 6653 : pass = AT_PASS_ADD_INDEXCONSTR;
5854 6653 : break;
5855 8086 : case AT_AddConstraint:
5856 : /* Recursion occurs during execution phase */
5857 8086 : if (recurse)
5858 8049 : cmd2->recurse = true;
5859 8086 : switch (castNode(Constraint, cmd2->def)->contype)
5860 : {
5861 5576 : case CONSTR_NOTNULL:
5862 5576 : pass = AT_PASS_COL_ATTRS;
5863 5576 : break;
5864 0 : case CONSTR_PRIMARY:
5865 : case CONSTR_UNIQUE:
5866 : case CONSTR_EXCLUSION:
5867 0 : pass = AT_PASS_ADD_INDEXCONSTR;
5868 0 : break;
5869 2510 : default:
5870 2510 : pass = AT_PASS_ADD_OTHERCONSTR;
5871 2510 : break;
5872 : }
5873 8086 : break;
5874 0 : case AT_AlterColumnGenericOptions:
5875 : /* This command never recurses */
5876 : /* No command-specific prep needed */
5877 0 : pass = AT_PASS_MISC;
5878 0 : break;
5879 5135 : default:
5880 5135 : pass = cur_pass;
5881 5135 : break;
5882 : }
5883 :
5884 20642 : if (pass < cur_pass)
5885 : {
5886 : /* Cannot schedule into a pass we already finished */
5887 0 : elog(ERROR, "ALTER TABLE scheduling failure: too late for pass %d",
5888 : pass);
5889 : }
5890 20642 : else if (pass > cur_pass)
5891 : {
5892 : /* OK, queue it up for later */
5893 15507 : tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd2);
5894 : }
5895 : else
5896 : {
5897 : /*
5898 : * We should see at most one subcommand for the current pass,
5899 : * which is the transformed version of the original subcommand.
5900 : */
5901 5135 : if (newcmd == NULL && cmd->subtype == cmd2->subtype)
5902 : {
5903 : /* Found the transformed version of our subcommand */
5904 5135 : newcmd = cmd2;
5905 : }
5906 : else
5907 0 : elog(ERROR, "ALTER TABLE scheduling failure: bogus item for pass %d",
5908 : pass);
5909 : }
5910 : }
5911 :
5912 : /* Queue up any after-statements to happen at the end */
5913 15489 : tab->afterStmts = list_concat(tab->afterStmts, afterStmts);
5914 :
5915 15489 : return newcmd;
5916 : }
5917 :
5918 : /*
5919 : * ATRewriteTables: ALTER TABLE phase 3
5920 : */
5921 : static void
5922 19259 : ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
5923 : AlterTableUtilityContext *context)
5924 : {
5925 : ListCell *ltab;
5926 :
5927 : /* Go through each table that needs to be checked or rewritten */
5928 41162 : foreach(ltab, *wqueue)
5929 : {
5930 22225 : AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
5931 :
5932 : /* Relations without storage may be ignored here */
5933 22225 : if (!RELKIND_HAS_STORAGE(tab->relkind))
5934 4198 : continue;
5935 :
5936 : /*
5937 : * If we change column data types, the operation has to be propagated
5938 : * to tables that use this table's rowtype as a column type.
5939 : * tab->newvals will also be non-NULL in the case where we're adding a
5940 : * column with a default. We choose to forbid that case as well,
5941 : * since composite types might eventually support defaults.
5942 : *
5943 : * (Eventually we'll probably need to check for composite type
5944 : * dependencies even when we're just scanning the table without a
5945 : * rewrite, but at the moment a composite type does not enforce any
5946 : * constraints, so it's not necessary/appropriate to enforce them just
5947 : * during ALTER.)
5948 : */
5949 18027 : if (tab->newvals != NIL || tab->rewrite > 0)
5950 : {
5951 : Relation rel;
5952 :
5953 1301 : rel = table_open(tab->relid, NoLock);
5954 1301 : find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
5955 1265 : table_close(rel, NoLock);
5956 : }
5957 :
5958 : /*
5959 : * We only need to rewrite the table if at least one column needs to
5960 : * be recomputed, or we are changing its persistence or access method.
5961 : *
5962 : * There are two reasons for requiring a rewrite when changing
5963 : * persistence: on one hand, we need to ensure that the buffers
5964 : * belonging to each of the two relations are marked with or without
5965 : * BM_PERMANENT properly. On the other hand, since rewriting creates
5966 : * and assigns a new relfilenumber, we automatically create or drop an
5967 : * init fork for the relation as appropriate.
5968 : */
5969 17991 : if (tab->rewrite > 0 && tab->relkind != RELKIND_SEQUENCE)
5970 716 : {
5971 : /* Build a temporary relation and copy data */
5972 : Relation OldHeap;
5973 : Oid OIDNewHeap;
5974 : Oid NewAccessMethod;
5975 : Oid NewTableSpace;
5976 : char persistence;
5977 :
5978 773 : OldHeap = table_open(tab->relid, NoLock);
5979 :
5980 : /*
5981 : * We don't support rewriting of system catalogs; there are too
5982 : * many corner cases and too little benefit. In particular this
5983 : * is certainly not going to work for mapped catalogs.
5984 : */
5985 773 : if (IsSystemRelation(OldHeap))
5986 0 : ereport(ERROR,
5987 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5988 : errmsg("cannot rewrite system relation \"%s\"",
5989 : RelationGetRelationName(OldHeap))));
5990 :
5991 773 : if (RelationIsUsedAsCatalogTable(OldHeap))
5992 1 : ereport(ERROR,
5993 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5994 : errmsg("cannot rewrite table \"%s\" used as a catalog table",
5995 : RelationGetRelationName(OldHeap))));
5996 :
5997 : /*
5998 : * Don't allow rewrite on temp tables of other backends ... their
5999 : * local buffer manager is not going to cope. (This is redundant
6000 : * with the check in CheckAlterTableIsSafe, but for safety we'll
6001 : * check here too.)
6002 : */
6003 772 : if (RELATION_IS_OTHER_TEMP(OldHeap))
6004 0 : ereport(ERROR,
6005 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
6006 : errmsg("cannot rewrite temporary tables of other sessions")));
6007 :
6008 : /*
6009 : * Select destination tablespace (same as original unless user
6010 : * requested a change)
6011 : */
6012 772 : if (tab->newTableSpace)
6013 0 : NewTableSpace = tab->newTableSpace;
6014 : else
6015 772 : NewTableSpace = OldHeap->rd_rel->reltablespace;
6016 :
6017 : /*
6018 : * Select destination access method (same as original unless user
6019 : * requested a change)
6020 : */
6021 772 : if (tab->chgAccessMethod)
6022 24 : NewAccessMethod = tab->newAccessMethod;
6023 : else
6024 748 : NewAccessMethod = OldHeap->rd_rel->relam;
6025 :
6026 : /*
6027 : * Select persistence of transient table (same as original unless
6028 : * user requested a change)
6029 : */
6030 772 : persistence = tab->chgPersistence ?
6031 737 : tab->newrelpersistence : OldHeap->rd_rel->relpersistence;
6032 :
6033 772 : table_close(OldHeap, NoLock);
6034 :
6035 : /*
6036 : * Fire off an Event Trigger now, before actually rewriting the
6037 : * table.
6038 : *
6039 : * We don't support Event Trigger for nested commands anywhere,
6040 : * here included, and parsetree is given NULL when coming from
6041 : * AlterTableInternal.
6042 : *
6043 : * And fire it only once.
6044 : */
6045 772 : if (parsetree)
6046 772 : EventTriggerTableRewrite((Node *) parsetree,
6047 : tab->relid,
6048 : tab->rewrite);
6049 :
6050 : /*
6051 : * Create transient table that will receive the modified data.
6052 : *
6053 : * Ensure it is marked correctly as logged or unlogged. We have
6054 : * to do this here so that buffers for the new relfilenumber will
6055 : * have the right persistence set, and at the same time ensure
6056 : * that the original filenumbers's buffers will get read in with
6057 : * the correct setting (i.e. the original one). Otherwise a
6058 : * rollback after the rewrite would possibly result with buffers
6059 : * for the original filenumbers having the wrong persistence
6060 : * setting.
6061 : *
6062 : * NB: This relies on swap_relation_files() also swapping the
6063 : * persistence. That wouldn't work for pg_class, but that can't be
6064 : * unlogged anyway.
6065 : */
6066 768 : OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, NewAccessMethod,
6067 : persistence, lockmode);
6068 :
6069 : /*
6070 : * Copy the heap data into the new table with the desired
6071 : * modifications, and test the current data within the table
6072 : * against new constraints generated by ALTER TABLE commands.
6073 : */
6074 768 : ATRewriteTable(tab, OIDNewHeap);
6075 :
6076 : /*
6077 : * Swap the physical files of the old and new heaps, then rebuild
6078 : * indexes and discard the old heap. We can use RecentXmin for
6079 : * the table's new relfrozenxid because we rewrote all the tuples
6080 : * in ATRewriteTable, so no older Xid remains in the table. Also,
6081 : * we never try to swap toast tables by content, since we have no
6082 : * interest in letting this code work on system catalogs.
6083 : */
6084 720 : finish_heap_swap(tab->relid, OIDNewHeap,
6085 : false, false, true,
6086 720 : !OidIsValid(tab->newTableSpace),
6087 : true, /* reindex */
6088 : RecentXmin,
6089 : ReadNextMultiXactId(),
6090 : persistence);
6091 :
6092 716 : InvokeObjectPostAlterHook(RelationRelationId, tab->relid, 0);
6093 : }
6094 17218 : else if (tab->rewrite > 0 && tab->relkind == RELKIND_SEQUENCE)
6095 : {
6096 16 : if (tab->chgPersistence)
6097 16 : SequenceChangePersistence(tab->relid, tab->newrelpersistence);
6098 : }
6099 : else
6100 : {
6101 : /*
6102 : * If required, test the current data within the table against new
6103 : * constraints generated by ALTER TABLE commands, but don't
6104 : * rebuild data.
6105 : */
6106 17202 : if (tab->constraints != NIL || tab->verify_new_notnull ||
6107 15008 : tab->partition_constraint != NULL)
6108 3503 : ATRewriteTable(tab, InvalidOid);
6109 :
6110 : /*
6111 : * If we had SET TABLESPACE but no reason to reconstruct tuples,
6112 : * just do a block-by-block copy.
6113 : */
6114 16973 : if (tab->newTableSpace)
6115 75 : ATExecSetTableSpace(tab->relid, tab->newTableSpace, lockmode);
6116 : }
6117 :
6118 : /*
6119 : * Also change persistence of owned sequences, so that it matches the
6120 : * table persistence.
6121 : */
6122 17705 : if (tab->chgPersistence)
6123 : {
6124 51 : List *seqlist = getOwnedSequences(tab->relid);
6125 : ListCell *lc;
6126 :
6127 83 : foreach(lc, seqlist)
6128 : {
6129 32 : Oid seq_relid = lfirst_oid(lc);
6130 :
6131 32 : SequenceChangePersistence(seq_relid, tab->newrelpersistence);
6132 : }
6133 : }
6134 : }
6135 :
6136 : /*
6137 : * Foreign key constraints are checked in a final pass, since (a) it's
6138 : * generally best to examine each one separately, and (b) it's at least
6139 : * theoretically possible that we have changed both relations of the
6140 : * foreign key, and we'd better have finished both rewrites before we try
6141 : * to read the tables.
6142 : */
6143 40591 : foreach(ltab, *wqueue)
6144 : {
6145 21725 : AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
6146 21725 : Relation rel = NULL;
6147 : ListCell *lcon;
6148 :
6149 : /* Relations without storage may be ignored here too */
6150 21725 : if (!RELKIND_HAS_STORAGE(tab->relkind))
6151 4093 : continue;
6152 :
6153 19015 : foreach(lcon, tab->constraints)
6154 : {
6155 1454 : NewConstraint *con = lfirst(lcon);
6156 :
6157 1454 : if (con->contype == CONSTR_FOREIGN)
6158 : {
6159 832 : Constraint *fkconstraint = (Constraint *) con->qual;
6160 : Relation refrel;
6161 :
6162 832 : if (rel == NULL)
6163 : {
6164 : /* Long since locked, no need for another */
6165 824 : rel = table_open(tab->relid, NoLock);
6166 : }
6167 :
6168 832 : refrel = table_open(con->refrelid, RowShareLock);
6169 :
6170 832 : validateForeignKeyConstraint(fkconstraint->conname, rel, refrel,
6171 : con->refindid,
6172 : con->conid,
6173 832 : con->conwithperiod);
6174 :
6175 : /*
6176 : * No need to mark the constraint row as validated, we did
6177 : * that when we inserted the row earlier.
6178 : */
6179 :
6180 761 : table_close(refrel, NoLock);
6181 : }
6182 : }
6183 :
6184 17561 : if (rel)
6185 753 : table_close(rel, NoLock);
6186 : }
6187 :
6188 : /* Finally, run any afterStmts that were queued up */
6189 40493 : foreach(ltab, *wqueue)
6190 : {
6191 21627 : AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
6192 : ListCell *lc;
6193 :
6194 21683 : foreach(lc, tab->afterStmts)
6195 : {
6196 56 : Node *stmt = (Node *) lfirst(lc);
6197 :
6198 56 : ProcessUtilityForAlterTable(stmt, context);
6199 56 : CommandCounterIncrement();
6200 : }
6201 : }
6202 18866 : }
6203 :
6204 : /*
6205 : * ATRewriteTable: scan or rewrite one table
6206 : *
6207 : * A rewrite is requested by passing a valid OIDNewHeap; in that case, caller
6208 : * must already hold AccessExclusiveLock on it.
6209 : */
6210 : static void
6211 4271 : ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
6212 : {
6213 : Relation oldrel;
6214 : Relation newrel;
6215 : TupleDesc oldTupDesc;
6216 : TupleDesc newTupDesc;
6217 4271 : bool needscan = false;
6218 : List *notnull_attrs;
6219 : List *notnull_virtual_attrs;
6220 : int i;
6221 : ListCell *l;
6222 : EState *estate;
6223 : CommandId mycid;
6224 : BulkInsertState bistate;
6225 : uint32 ti_options;
6226 4271 : ExprState *partqualstate = NULL;
6227 :
6228 : /*
6229 : * Open the relation(s). We have surely already locked the existing
6230 : * table.
6231 : */
6232 4271 : oldrel = table_open(tab->relid, NoLock);
6233 4271 : oldTupDesc = tab->oldDesc;
6234 4271 : newTupDesc = RelationGetDescr(oldrel); /* includes all mods */
6235 :
6236 4271 : if (OidIsValid(OIDNewHeap))
6237 : {
6238 : Assert(CheckRelationOidLockedByMe(OIDNewHeap, AccessExclusiveLock,
6239 : false));
6240 768 : newrel = table_open(OIDNewHeap, NoLock);
6241 : }
6242 : else
6243 3503 : newrel = NULL;
6244 :
6245 : /*
6246 : * Prepare a BulkInsertState and options for table_tuple_insert. The FSM
6247 : * is empty, so don't bother using it.
6248 : */
6249 4271 : if (newrel)
6250 : {
6251 768 : mycid = GetCurrentCommandId(true);
6252 768 : bistate = GetBulkInsertState();
6253 768 : ti_options = TABLE_INSERT_SKIP_FSM;
6254 : }
6255 : else
6256 : {
6257 : /* keep compiler quiet about using these uninitialized */
6258 3503 : mycid = 0;
6259 3503 : bistate = NULL;
6260 3503 : ti_options = 0;
6261 : }
6262 :
6263 : /*
6264 : * Generate the constraint and default execution states
6265 : */
6266 :
6267 4271 : estate = CreateExecutorState();
6268 :
6269 : /* Build the needed expression execution states */
6270 5881 : foreach(l, tab->constraints)
6271 : {
6272 1610 : NewConstraint *con = lfirst(l);
6273 :
6274 1610 : switch (con->contype)
6275 : {
6276 774 : case CONSTR_CHECK:
6277 774 : needscan = true;
6278 774 : con->qualstate = ExecPrepareExpr((Expr *) expand_generated_columns_in_expr(con->qual, oldrel, 1), estate);
6279 774 : break;
6280 836 : case CONSTR_FOREIGN:
6281 : /* Nothing to do here */
6282 836 : break;
6283 0 : default:
6284 0 : elog(ERROR, "unrecognized constraint type: %d",
6285 : (int) con->contype);
6286 : }
6287 : }
6288 :
6289 : /* Build expression execution states for partition check quals */
6290 4271 : if (tab->partition_constraint)
6291 : {
6292 1403 : needscan = true;
6293 1403 : partqualstate = ExecPrepareExpr(tab->partition_constraint, estate);
6294 : }
6295 :
6296 5100 : foreach(l, tab->newvals)
6297 : {
6298 829 : NewColumnValue *ex = lfirst(l);
6299 :
6300 : /* expr already planned */
6301 829 : ex->exprstate = ExecInitExpr(ex->expr, NULL);
6302 : }
6303 :
6304 4271 : notnull_attrs = notnull_virtual_attrs = NIL;
6305 4271 : if (newrel || tab->verify_new_notnull)
6306 : {
6307 : /*
6308 : * If we are rebuilding the tuples OR if we added any new but not
6309 : * verified not-null constraints, check all *valid* not-null
6310 : * constraints. This is a bit of overkill but it minimizes risk of
6311 : * bugs.
6312 : *
6313 : * notnull_attrs does *not* collect attribute numbers for valid
6314 : * not-null constraints over virtual generated columns; instead, they
6315 : * are collected in notnull_virtual_attrs for verification elsewhere.
6316 : */
6317 5438 : for (i = 0; i < newTupDesc->natts; i++)
6318 : {
6319 3973 : CompactAttribute *attr = TupleDescCompactAttr(newTupDesc, i);
6320 :
6321 3973 : if (attr->attnullability == ATTNULLABLE_VALID &&
6322 1500 : !attr->attisdropped)
6323 : {
6324 1500 : Form_pg_attribute wholeatt = TupleDescAttr(newTupDesc, i);
6325 :
6326 1500 : if (wholeatt->attgenerated != ATTRIBUTE_GENERATED_VIRTUAL)
6327 1436 : notnull_attrs = lappend_int(notnull_attrs, wholeatt->attnum);
6328 : else
6329 64 : notnull_virtual_attrs = lappend_int(notnull_virtual_attrs,
6330 64 : wholeatt->attnum);
6331 : }
6332 : }
6333 1465 : if (notnull_attrs || notnull_virtual_attrs)
6334 1087 : needscan = true;
6335 : }
6336 :
6337 4271 : if (newrel || needscan)
6338 : {
6339 : ExprContext *econtext;
6340 : TupleTableSlot *oldslot;
6341 : TupleTableSlot *newslot;
6342 : TableScanDesc scan;
6343 : MemoryContext oldCxt;
6344 3565 : List *dropped_attrs = NIL;
6345 : ListCell *lc;
6346 : Snapshot snapshot;
6347 3565 : ResultRelInfo *rInfo = NULL;
6348 :
6349 : /*
6350 : * When adding or changing a virtual generated column with a not-null
6351 : * constraint, we need to evaluate whether the generation expression
6352 : * is null. For that, we borrow ExecRelGenVirtualNotNull(). Here, we
6353 : * prepare a dummy ResultRelInfo.
6354 : */
6355 3565 : if (notnull_virtual_attrs != NIL)
6356 : {
6357 : MemoryContext oldcontext;
6358 :
6359 : Assert(newTupDesc->constr->has_generated_virtual);
6360 : Assert(newTupDesc->constr->has_not_null);
6361 44 : oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
6362 44 : rInfo = makeNode(ResultRelInfo);
6363 44 : InitResultRelInfo(rInfo,
6364 : oldrel,
6365 : 0, /* dummy rangetable index */
6366 : NULL,
6367 : estate->es_instrument);
6368 44 : MemoryContextSwitchTo(oldcontext);
6369 : }
6370 :
6371 3565 : if (newrel)
6372 768 : ereport(DEBUG1,
6373 : (errmsg_internal("rewriting table \"%s\"",
6374 : RelationGetRelationName(oldrel))));
6375 : else
6376 2797 : ereport(DEBUG1,
6377 : (errmsg_internal("verifying table \"%s\"",
6378 : RelationGetRelationName(oldrel))));
6379 :
6380 3565 : if (newrel)
6381 : {
6382 : /*
6383 : * All predicate locks on the tuples or pages are about to be made
6384 : * invalid, because we move tuples around. Promote them to
6385 : * relation locks.
6386 : */
6387 768 : TransferPredicateLocksToHeapRelation(oldrel);
6388 : }
6389 :
6390 3565 : econtext = GetPerTupleExprContext(estate);
6391 :
6392 : /*
6393 : * Create necessary tuple slots. When rewriting, two slots are needed,
6394 : * otherwise one suffices. In the case where one slot suffices, we
6395 : * need to use the new tuple descriptor, otherwise some constraints
6396 : * can't be evaluated. Note that even when the tuple layout is the
6397 : * same and no rewrite is required, the tupDescs might not be
6398 : * (consider ADD COLUMN without a default).
6399 : */
6400 3565 : if (tab->rewrite)
6401 : {
6402 : Assert(newrel != NULL);
6403 768 : oldslot = MakeSingleTupleTableSlot(oldTupDesc,
6404 : table_slot_callbacks(oldrel));
6405 768 : newslot = MakeSingleTupleTableSlot(newTupDesc,
6406 : table_slot_callbacks(newrel));
6407 :
6408 : /*
6409 : * Set all columns in the new slot to NULL initially, to ensure
6410 : * columns added as part of the rewrite are initialized to NULL.
6411 : * That is necessary as tab->newvals will not contain an
6412 : * expression for columns with a NULL default, e.g. when adding a
6413 : * column without a default together with a column with a default
6414 : * requiring an actual rewrite.
6415 : */
6416 768 : ExecStoreAllNullTuple(newslot);
6417 : }
6418 : else
6419 : {
6420 2797 : oldslot = MakeSingleTupleTableSlot(newTupDesc,
6421 : table_slot_callbacks(oldrel));
6422 2797 : newslot = NULL;
6423 : }
6424 :
6425 : /*
6426 : * Any attributes that are dropped according to the new tuple
6427 : * descriptor can be set to NULL. We precompute the list of dropped
6428 : * attributes to avoid needing to do so in the per-tuple loop.
6429 : */
6430 12638 : for (i = 0; i < newTupDesc->natts; i++)
6431 : {
6432 9073 : if (TupleDescAttr(newTupDesc, i)->attisdropped)
6433 616 : dropped_attrs = lappend_int(dropped_attrs, i);
6434 : }
6435 :
6436 : /*
6437 : * Scan through the rows, generating a new row if needed and then
6438 : * checking all the constraints.
6439 : */
6440 3565 : snapshot = RegisterSnapshot(GetLatestSnapshot());
6441 3565 : scan = table_beginscan(oldrel, snapshot, 0, NULL,
6442 : SO_NONE);
6443 :
6444 : /*
6445 : * Switch to per-tuple memory context and reset it for each tuple
6446 : * produced, so we don't leak memory.
6447 : */
6448 3565 : oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
6449 :
6450 491201 : while (table_scan_getnextslot(scan, ForwardScanDirection, oldslot))
6451 : {
6452 : TupleTableSlot *insertslot;
6453 :
6454 484348 : if (tab->rewrite > 0)
6455 : {
6456 : /* Extract data from old tuple */
6457 67459 : slot_getallattrs(oldslot);
6458 67459 : ExecClearTuple(newslot);
6459 :
6460 : /* copy attributes */
6461 67459 : memcpy(newslot->tts_values, oldslot->tts_values,
6462 67459 : sizeof(Datum) * oldslot->tts_nvalid);
6463 67459 : memcpy(newslot->tts_isnull, oldslot->tts_isnull,
6464 67459 : sizeof(bool) * oldslot->tts_nvalid);
6465 :
6466 : /* Set dropped attributes to null in new tuple */
6467 67544 : foreach(lc, dropped_attrs)
6468 85 : newslot->tts_isnull[lfirst_int(lc)] = true;
6469 :
6470 : /*
6471 : * Constraints and GENERATED expressions might reference the
6472 : * tableoid column, so fill tts_tableOid with the desired
6473 : * value. (We must do this each time, because it gets
6474 : * overwritten with newrel's OID during storing.)
6475 : */
6476 67459 : newslot->tts_tableOid = RelationGetRelid(oldrel);
6477 :
6478 : /*
6479 : * Process supplied expressions to replace selected columns.
6480 : *
6481 : * First, evaluate expressions whose inputs come from the old
6482 : * tuple.
6483 : */
6484 67459 : econtext->ecxt_scantuple = oldslot;
6485 :
6486 137910 : foreach(l, tab->newvals)
6487 : {
6488 70459 : NewColumnValue *ex = lfirst(l);
6489 :
6490 70459 : if (ex->is_generated)
6491 264 : continue;
6492 :
6493 70195 : newslot->tts_values[ex->attnum - 1]
6494 70187 : = ExecEvalExpr(ex->exprstate,
6495 : econtext,
6496 70195 : &newslot->tts_isnull[ex->attnum - 1]);
6497 : }
6498 :
6499 67451 : ExecStoreVirtualTuple(newslot);
6500 :
6501 : /*
6502 : * Now, evaluate any expressions whose inputs come from the
6503 : * new tuple. We assume these columns won't reference each
6504 : * other, so that there's no ordering dependency.
6505 : */
6506 67451 : econtext->ecxt_scantuple = newslot;
6507 :
6508 137902 : foreach(l, tab->newvals)
6509 : {
6510 70451 : NewColumnValue *ex = lfirst(l);
6511 :
6512 70451 : if (!ex->is_generated)
6513 70187 : continue;
6514 :
6515 264 : newslot->tts_values[ex->attnum - 1]
6516 264 : = ExecEvalExpr(ex->exprstate,
6517 : econtext,
6518 264 : &newslot->tts_isnull[ex->attnum - 1]);
6519 : }
6520 :
6521 67451 : insertslot = newslot;
6522 : }
6523 : else
6524 : {
6525 : /*
6526 : * If there's no rewrite, old and new table are guaranteed to
6527 : * have the same AM, so we can just use the old slot to verify
6528 : * new constraints etc.
6529 : */
6530 416889 : insertslot = oldslot;
6531 : }
6532 :
6533 : /* Now check any constraints on the possibly-changed tuple */
6534 484340 : econtext->ecxt_scantuple = insertslot;
6535 :
6536 2579542 : foreach_int(attn, notnull_attrs)
6537 : {
6538 1611038 : if (slot_attisnull(insertslot, attn))
6539 : {
6540 88 : Form_pg_attribute attr = TupleDescAttr(newTupDesc, attn - 1);
6541 :
6542 88 : ereport(ERROR,
6543 : (errcode(ERRCODE_NOT_NULL_VIOLATION),
6544 : errmsg("column \"%s\" of relation \"%s\" contains null values",
6545 : NameStr(attr->attname),
6546 : RelationGetRelationName(oldrel)),
6547 : errtablecol(oldrel, attn)));
6548 : }
6549 : }
6550 :
6551 484252 : if (notnull_virtual_attrs != NIL)
6552 : {
6553 : AttrNumber attnum;
6554 :
6555 60 : attnum = ExecRelGenVirtualNotNull(rInfo, insertslot,
6556 : estate,
6557 : notnull_virtual_attrs);
6558 60 : if (attnum != InvalidAttrNumber)
6559 : {
6560 20 : Form_pg_attribute attr = TupleDescAttr(newTupDesc, attnum - 1);
6561 :
6562 20 : ereport(ERROR,
6563 : errcode(ERRCODE_NOT_NULL_VIOLATION),
6564 : errmsg("column \"%s\" of relation \"%s\" contains null values",
6565 : NameStr(attr->attname),
6566 : RelationGetRelationName(oldrel)),
6567 : errtablecol(oldrel, attnum));
6568 : }
6569 : }
6570 :
6571 489573 : foreach(l, tab->constraints)
6572 : {
6573 5453 : NewConstraint *con = lfirst(l);
6574 :
6575 5453 : switch (con->contype)
6576 : {
6577 5387 : case CONSTR_CHECK:
6578 5387 : if (!ExecCheck(con->qualstate, econtext))
6579 112 : ereport(ERROR,
6580 : (errcode(ERRCODE_CHECK_VIOLATION),
6581 : errmsg("check constraint \"%s\" of relation \"%s\" is violated by some row",
6582 : con->name,
6583 : RelationGetRelationName(oldrel)),
6584 : errtableconstraint(oldrel, con->name)));
6585 5275 : break;
6586 66 : case CONSTR_NOTNULL:
6587 : case CONSTR_FOREIGN:
6588 : /* Nothing to do here */
6589 66 : break;
6590 0 : default:
6591 0 : elog(ERROR, "unrecognized constraint type: %d",
6592 : (int) con->contype);
6593 : }
6594 : }
6595 :
6596 484120 : if (partqualstate && !ExecCheck(partqualstate, econtext))
6597 : {
6598 49 : if (tab->validate_default)
6599 17 : ereport(ERROR,
6600 : (errcode(ERRCODE_CHECK_VIOLATION),
6601 : errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
6602 : RelationGetRelationName(oldrel)),
6603 : errtable(oldrel)));
6604 : else
6605 32 : ereport(ERROR,
6606 : (errcode(ERRCODE_CHECK_VIOLATION),
6607 : errmsg("partition constraint of relation \"%s\" is violated by some row",
6608 : RelationGetRelationName(oldrel)),
6609 : errtable(oldrel)));
6610 : }
6611 :
6612 : /* Write the tuple out to the new relation */
6613 484071 : if (newrel)
6614 67411 : table_tuple_insert(newrel, insertslot, mycid,
6615 : ti_options, bistate);
6616 :
6617 484071 : ResetExprContext(econtext);
6618 :
6619 484071 : CHECK_FOR_INTERRUPTS();
6620 : }
6621 :
6622 3288 : MemoryContextSwitchTo(oldCxt);
6623 3288 : table_endscan(scan);
6624 3288 : UnregisterSnapshot(snapshot);
6625 :
6626 3288 : ExecDropSingleTupleTableSlot(oldslot);
6627 3288 : if (newslot)
6628 720 : ExecDropSingleTupleTableSlot(newslot);
6629 : }
6630 :
6631 3994 : FreeExecutorState(estate);
6632 :
6633 3994 : table_close(oldrel, NoLock);
6634 3994 : if (newrel)
6635 : {
6636 720 : FreeBulkInsertState(bistate);
6637 :
6638 720 : table_finish_bulk_insert(newrel, ti_options);
6639 :
6640 720 : table_close(newrel, NoLock);
6641 : }
6642 3994 : }
6643 :
6644 : /*
6645 : * ATGetQueueEntry: find or create an entry in the ALTER TABLE work queue
6646 : */
6647 : static AlteredTableInfo *
6648 29319 : ATGetQueueEntry(List **wqueue, Relation rel)
6649 : {
6650 29319 : Oid relid = RelationGetRelid(rel);
6651 : AlteredTableInfo *tab;
6652 : ListCell *ltab;
6653 :
6654 37911 : foreach(ltab, *wqueue)
6655 : {
6656 12530 : tab = (AlteredTableInfo *) lfirst(ltab);
6657 12530 : if (tab->relid == relid)
6658 3938 : return tab;
6659 : }
6660 :
6661 : /*
6662 : * Not there, so add it. Note that we make a copy of the relation's
6663 : * existing descriptor before anything interesting can happen to it.
6664 : */
6665 25381 : tab = palloc0_object(AlteredTableInfo);
6666 25381 : tab->relid = relid;
6667 25381 : tab->rel = NULL; /* set later */
6668 25381 : tab->relkind = rel->rd_rel->relkind;
6669 25381 : tab->oldDesc = CreateTupleDescCopyConstr(RelationGetDescr(rel));
6670 25381 : tab->newAccessMethod = InvalidOid;
6671 25381 : tab->chgAccessMethod = false;
6672 25381 : tab->newTableSpace = InvalidOid;
6673 25381 : tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
6674 25381 : tab->chgPersistence = false;
6675 :
6676 25381 : *wqueue = lappend(*wqueue, tab);
6677 :
6678 25381 : return tab;
6679 : }
6680 :
6681 : static const char *
6682 73 : alter_table_type_to_string(AlterTableType cmdtype)
6683 : {
6684 73 : switch (cmdtype)
6685 : {
6686 0 : case AT_AddColumn:
6687 : case AT_AddColumnToView:
6688 0 : return "ADD COLUMN";
6689 0 : case AT_ColumnDefault:
6690 : case AT_CookedColumnDefault:
6691 0 : return "ALTER COLUMN ... SET DEFAULT";
6692 4 : case AT_DropNotNull:
6693 4 : return "ALTER COLUMN ... DROP NOT NULL";
6694 4 : case AT_SetNotNull:
6695 4 : return "ALTER COLUMN ... SET NOT NULL";
6696 0 : case AT_SetExpression:
6697 0 : return "ALTER COLUMN ... SET EXPRESSION";
6698 0 : case AT_DropExpression:
6699 0 : return "ALTER COLUMN ... DROP EXPRESSION";
6700 0 : case AT_SetStatistics:
6701 0 : return "ALTER COLUMN ... SET STATISTICS";
6702 8 : case AT_SetOptions:
6703 8 : return "ALTER COLUMN ... SET";
6704 0 : case AT_ResetOptions:
6705 0 : return "ALTER COLUMN ... RESET";
6706 0 : case AT_SetStorage:
6707 0 : return "ALTER COLUMN ... SET STORAGE";
6708 0 : case AT_SetCompression:
6709 0 : return "ALTER COLUMN ... SET COMPRESSION";
6710 4 : case AT_DropColumn:
6711 4 : return "DROP COLUMN";
6712 0 : case AT_AddIndex:
6713 : case AT_ReAddIndex:
6714 0 : return NULL; /* not real grammar */
6715 0 : case AT_AddConstraint:
6716 : case AT_ReAddConstraint:
6717 : case AT_ReAddDomainConstraint:
6718 : case AT_AddIndexConstraint:
6719 0 : return "ADD CONSTRAINT";
6720 4 : case AT_AlterConstraint:
6721 4 : return "ALTER CONSTRAINT";
6722 0 : case AT_ValidateConstraint:
6723 0 : return "VALIDATE CONSTRAINT";
6724 0 : case AT_DropConstraint:
6725 0 : return "DROP CONSTRAINT";
6726 0 : case AT_ReAddComment:
6727 0 : return NULL; /* not real grammar */
6728 0 : case AT_AlterColumnType:
6729 0 : return "ALTER COLUMN ... SET DATA TYPE";
6730 0 : case AT_AlterColumnGenericOptions:
6731 0 : return "ALTER COLUMN ... OPTIONS";
6732 0 : case AT_ChangeOwner:
6733 0 : return "OWNER TO";
6734 4 : case AT_ClusterOn:
6735 4 : return "CLUSTER ON";
6736 4 : case AT_DropCluster:
6737 4 : return "SET WITHOUT CLUSTER";
6738 0 : case AT_SetAccessMethod:
6739 0 : return "SET ACCESS METHOD";
6740 4 : case AT_SetLogged:
6741 4 : return "SET LOGGED";
6742 4 : case AT_SetUnLogged:
6743 4 : return "SET UNLOGGED";
6744 0 : case AT_DropOids:
6745 0 : return "SET WITHOUT OIDS";
6746 0 : case AT_SetTableSpace:
6747 0 : return "SET TABLESPACE";
6748 1 : case AT_SetRelOptions:
6749 1 : return "SET";
6750 0 : case AT_ResetRelOptions:
6751 0 : return "RESET";
6752 0 : case AT_ReplaceRelOptions:
6753 0 : return NULL; /* not real grammar */
6754 0 : case AT_EnableTrig:
6755 0 : return "ENABLE TRIGGER";
6756 0 : case AT_EnableAlwaysTrig:
6757 0 : return "ENABLE ALWAYS TRIGGER";
6758 0 : case AT_EnableReplicaTrig:
6759 0 : return "ENABLE REPLICA TRIGGER";
6760 0 : case AT_DisableTrig:
6761 0 : return "DISABLE TRIGGER";
6762 0 : case AT_EnableTrigAll:
6763 0 : return "ENABLE TRIGGER ALL";
6764 0 : case AT_DisableTrigAll:
6765 0 : return "DISABLE TRIGGER ALL";
6766 0 : case AT_EnableTrigUser:
6767 0 : return "ENABLE TRIGGER USER";
6768 0 : case AT_DisableTrigUser:
6769 0 : return "DISABLE TRIGGER USER";
6770 0 : case AT_EnableRule:
6771 0 : return "ENABLE RULE";
6772 0 : case AT_EnableAlwaysRule:
6773 0 : return "ENABLE ALWAYS RULE";
6774 0 : case AT_EnableReplicaRule:
6775 0 : return "ENABLE REPLICA RULE";
6776 0 : case AT_DisableRule:
6777 0 : return "DISABLE RULE";
6778 4 : case AT_AddInherit:
6779 4 : return "INHERIT";
6780 4 : case AT_DropInherit:
6781 4 : return "NO INHERIT";
6782 0 : case AT_AddOf:
6783 0 : return "OF";
6784 0 : case AT_DropOf:
6785 0 : return "NOT OF";
6786 0 : case AT_ReplicaIdentity:
6787 0 : return "REPLICA IDENTITY";
6788 0 : case AT_EnableRowSecurity:
6789 0 : return "ENABLE ROW SECURITY";
6790 0 : case AT_DisableRowSecurity:
6791 0 : return "DISABLE ROW SECURITY";
6792 0 : case AT_ForceRowSecurity:
6793 0 : return "FORCE ROW SECURITY";
6794 0 : case AT_NoForceRowSecurity:
6795 0 : return "NO FORCE ROW SECURITY";
6796 0 : case AT_GenericOptions:
6797 0 : return "OPTIONS";
6798 4 : case AT_AttachPartition:
6799 4 : return "ATTACH PARTITION";
6800 12 : case AT_DetachPartition:
6801 12 : return "DETACH PARTITION";
6802 4 : case AT_DetachPartitionFinalize:
6803 4 : return "DETACH PARTITION ... FINALIZE";
6804 0 : case AT_MergePartitions:
6805 0 : return "MERGE PARTITIONS";
6806 4 : case AT_SplitPartition:
6807 4 : return "SPLIT PARTITION";
6808 0 : case AT_AddIdentity:
6809 0 : return "ALTER COLUMN ... ADD IDENTITY";
6810 0 : case AT_SetIdentity:
6811 0 : return "ALTER COLUMN ... SET";
6812 0 : case AT_DropIdentity:
6813 0 : return "ALTER COLUMN ... DROP IDENTITY";
6814 0 : case AT_ReAddStatistics:
6815 0 : return NULL; /* not real grammar */
6816 : }
6817 :
6818 0 : return NULL;
6819 : }
6820 :
6821 : /*
6822 : * ATSimplePermissions
6823 : *
6824 : * - Ensure that it is a relation (or possibly a view)
6825 : * - Ensure this user is the owner
6826 : * - Ensure that it is not a system table
6827 : */
6828 : static void
6829 25913 : ATSimplePermissions(AlterTableType cmdtype, Relation rel, int allowed_targets)
6830 : {
6831 : int actual_target;
6832 :
6833 25913 : switch (rel->rd_rel->relkind)
6834 : {
6835 20044 : case RELKIND_RELATION:
6836 20044 : actual_target = ATT_TABLE;
6837 20044 : break;
6838 4366 : case RELKIND_PARTITIONED_TABLE:
6839 4366 : actual_target = ATT_PARTITIONED_TABLE;
6840 4366 : break;
6841 280 : case RELKIND_VIEW:
6842 280 : actual_target = ATT_VIEW;
6843 280 : break;
6844 29 : case RELKIND_MATVIEW:
6845 29 : actual_target = ATT_MATVIEW;
6846 29 : break;
6847 140 : case RELKIND_INDEX:
6848 140 : actual_target = ATT_INDEX;
6849 140 : break;
6850 297 : case RELKIND_PARTITIONED_INDEX:
6851 297 : actual_target = ATT_PARTITIONED_INDEX;
6852 297 : break;
6853 145 : case RELKIND_COMPOSITE_TYPE:
6854 145 : actual_target = ATT_COMPOSITE_TYPE;
6855 145 : break;
6856 595 : case RELKIND_FOREIGN_TABLE:
6857 595 : actual_target = ATT_FOREIGN_TABLE;
6858 595 : break;
6859 16 : case RELKIND_SEQUENCE:
6860 16 : actual_target = ATT_SEQUENCE;
6861 16 : break;
6862 1 : default:
6863 1 : actual_target = 0;
6864 1 : break;
6865 : }
6866 :
6867 : /* Wrong target type? */
6868 25913 : if ((actual_target & allowed_targets) == 0)
6869 : {
6870 73 : const char *action_str = alter_table_type_to_string(cmdtype);
6871 :
6872 73 : if (action_str)
6873 73 : ereport(ERROR,
6874 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
6875 : /* translator: %s is a group of some SQL keywords */
6876 : errmsg("ALTER action %s cannot be performed on relation \"%s\"",
6877 : action_str, RelationGetRelationName(rel)),
6878 : errdetail_relkind_not_supported(rel->rd_rel->relkind)));
6879 : else
6880 : /* internal error? */
6881 0 : elog(ERROR, "invalid ALTER action attempted on relation \"%s\"",
6882 : RelationGetRelationName(rel));
6883 : }
6884 :
6885 : /* Permissions checks */
6886 25840 : if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId()))
6887 8 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
6888 8 : RelationGetRelationName(rel));
6889 :
6890 25832 : if (!allowSystemTableMods && IsSystemRelation(rel))
6891 0 : ereport(ERROR,
6892 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
6893 : errmsg("permission denied: \"%s\" is a system catalog",
6894 : RelationGetRelationName(rel))));
6895 25832 : }
6896 :
6897 : /*
6898 : * ATSimpleRecursion
6899 : *
6900 : * Simple table recursion sufficient for most ALTER TABLE operations.
6901 : * All direct and indirect children are processed in an unspecified order.
6902 : * Note that if a child inherits from the original table via multiple
6903 : * inheritance paths, it will be visited just once.
6904 : */
6905 : static void
6906 925 : ATSimpleRecursion(List **wqueue, Relation rel,
6907 : AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
6908 : AlterTableUtilityContext *context)
6909 : {
6910 : /*
6911 : * Propagate to children, if desired and if there are (or might be) any
6912 : * children.
6913 : */
6914 925 : if (recurse && rel->rd_rel->relhassubclass)
6915 : {
6916 59 : Oid relid = RelationGetRelid(rel);
6917 : ListCell *child;
6918 : List *children;
6919 :
6920 59 : children = find_all_inheritors(relid, lockmode, NULL);
6921 :
6922 : /*
6923 : * find_all_inheritors does the recursive search of the inheritance
6924 : * hierarchy, so all we have to do is process all of the relids in the
6925 : * list that it returns.
6926 : */
6927 256 : foreach(child, children)
6928 : {
6929 197 : Oid childrelid = lfirst_oid(child);
6930 : Relation childrel;
6931 :
6932 197 : if (childrelid == relid)
6933 59 : continue;
6934 : /* find_all_inheritors already got lock */
6935 138 : childrel = relation_open(childrelid, NoLock);
6936 138 : CheckAlterTableIsSafe(childrel);
6937 138 : ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, context);
6938 138 : relation_close(childrel, NoLock);
6939 : }
6940 : }
6941 925 : }
6942 :
6943 : /*
6944 : * Obtain list of partitions of the given table, locking them all at the given
6945 : * lockmode and ensuring that they all pass CheckAlterTableIsSafe.
6946 : *
6947 : * This function is a no-op if the given relation is not a partitioned table;
6948 : * in particular, nothing is done if it's a legacy inheritance parent.
6949 : */
6950 : static void
6951 578 : ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode)
6952 : {
6953 578 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
6954 : {
6955 : List *inh;
6956 : ListCell *cell;
6957 :
6958 121 : inh = find_all_inheritors(RelationGetRelid(rel), lockmode, NULL);
6959 : /* first element is the parent rel; must ignore it */
6960 398 : for_each_from(cell, inh, 1)
6961 : {
6962 : Relation childrel;
6963 :
6964 : /* find_all_inheritors already got lock */
6965 281 : childrel = table_open(lfirst_oid(cell), NoLock);
6966 281 : CheckAlterTableIsSafe(childrel);
6967 277 : table_close(childrel, NoLock);
6968 : }
6969 117 : list_free(inh);
6970 : }
6971 574 : }
6972 :
6973 : /*
6974 : * ATTypedTableRecursion
6975 : *
6976 : * Propagate ALTER TYPE operations to the typed tables of that type.
6977 : * Also check the RESTRICT/CASCADE behavior. Given CASCADE, also permit
6978 : * recursion to inheritance children of the typed tables.
6979 : */
6980 : static void
6981 129 : ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
6982 : LOCKMODE lockmode, AlterTableUtilityContext *context)
6983 : {
6984 : ListCell *child;
6985 : List *children;
6986 :
6987 : Assert(rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE);
6988 :
6989 129 : children = find_typed_table_dependencies(rel->rd_rel->reltype,
6990 129 : RelationGetRelationName(rel),
6991 : cmd->behavior);
6992 :
6993 137 : foreach(child, children)
6994 : {
6995 20 : Oid childrelid = lfirst_oid(child);
6996 : Relation childrel;
6997 :
6998 20 : childrel = relation_open(childrelid, lockmode);
6999 20 : CheckAlterTableIsSafe(childrel);
7000 20 : ATPrepCmd(wqueue, childrel, cmd, true, true, lockmode, context);
7001 20 : relation_close(childrel, NoLock);
7002 : }
7003 117 : }
7004 :
7005 :
7006 : /*
7007 : * find_composite_type_dependencies
7008 : *
7009 : * Check to see if the type "typeOid" is being used as a column in some table
7010 : * (possibly nested several levels deep in composite types, arrays, etc!).
7011 : * Eventually, we'd like to propagate the check or rewrite operation
7012 : * into such tables, but for now, just error out if we find any.
7013 : *
7014 : * Caller should provide either the associated relation of a rowtype,
7015 : * or a type name (not both) for use in the error message, if any.
7016 : *
7017 : * Note that "typeOid" is not necessarily a composite type; it could also be
7018 : * another container type such as an array or range, or a domain over one of
7019 : * these things. The name of this function is therefore somewhat historical,
7020 : * but it's not worth changing.
7021 : *
7022 : * We assume that functions and views depending on the type are not reasons
7023 : * to reject the ALTER. (How safe is this really?)
7024 : */
7025 : void
7026 3304 : find_composite_type_dependencies(Oid typeOid, Relation origRelation,
7027 : const char *origTypeName)
7028 : {
7029 : Relation depRel;
7030 : ScanKeyData key[2];
7031 : SysScanDesc depScan;
7032 : HeapTuple depTup;
7033 :
7034 : /* since this function recurses, it could be driven to stack overflow */
7035 3304 : check_stack_depth();
7036 :
7037 : /*
7038 : * We scan pg_depend to find those things that depend on the given type.
7039 : * (We assume we can ignore refobjsubid for a type.)
7040 : */
7041 3304 : depRel = table_open(DependRelationId, AccessShareLock);
7042 :
7043 3304 : ScanKeyInit(&key[0],
7044 : Anum_pg_depend_refclassid,
7045 : BTEqualStrategyNumber, F_OIDEQ,
7046 : ObjectIdGetDatum(TypeRelationId));
7047 3304 : ScanKeyInit(&key[1],
7048 : Anum_pg_depend_refobjid,
7049 : BTEqualStrategyNumber, F_OIDEQ,
7050 : ObjectIdGetDatum(typeOid));
7051 :
7052 3304 : depScan = systable_beginscan(depRel, DependReferenceIndexId, true,
7053 : NULL, 2, key);
7054 :
7055 5067 : while (HeapTupleIsValid(depTup = systable_getnext(depScan)))
7056 : {
7057 1867 : Form_pg_depend pg_depend = (Form_pg_depend) GETSTRUCT(depTup);
7058 : Relation rel;
7059 : TupleDesc tupleDesc;
7060 : Form_pg_attribute att;
7061 :
7062 : /* Check for directly dependent types */
7063 1867 : if (pg_depend->classid == TypeRelationId)
7064 : {
7065 : /*
7066 : * This must be an array, domain, or range containing the given
7067 : * type, so recursively check for uses of this type. Note that
7068 : * any error message will mention the original type not the
7069 : * container; this is intentional.
7070 : */
7071 1597 : find_composite_type_dependencies(pg_depend->objid,
7072 : origRelation, origTypeName);
7073 1581 : continue;
7074 : }
7075 :
7076 : /* Else, ignore dependees that aren't relations */
7077 270 : if (pg_depend->classid != RelationRelationId)
7078 81 : continue;
7079 :
7080 189 : rel = relation_open(pg_depend->objid, AccessShareLock);
7081 189 : tupleDesc = RelationGetDescr(rel);
7082 :
7083 : /*
7084 : * If objsubid identifies a specific column, refer to that in error
7085 : * messages. Otherwise, search to see if there's a user column of the
7086 : * type. (We assume system columns are never of interesting types.)
7087 : * The search is needed because an index containing an expression
7088 : * column of the target type will just be recorded as a whole-relation
7089 : * dependency. If we do not find a column of the type, the dependency
7090 : * must indicate that the type is transiently referenced in an index
7091 : * expression but not stored on disk, which we assume is OK, just as
7092 : * we do for references in views. (It could also be that the target
7093 : * type is embedded in some container type that is stored in an index
7094 : * column, but the previous recursion should catch such cases.)
7095 : */
7096 189 : if (pg_depend->objsubid > 0 && pg_depend->objsubid <= tupleDesc->natts)
7097 84 : att = TupleDescAttr(tupleDesc, pg_depend->objsubid - 1);
7098 : else
7099 : {
7100 105 : att = NULL;
7101 270 : for (int attno = 1; attno <= tupleDesc->natts; attno++)
7102 : {
7103 169 : att = TupleDescAttr(tupleDesc, attno - 1);
7104 169 : if (att->atttypid == typeOid && !att->attisdropped)
7105 4 : break;
7106 165 : att = NULL;
7107 : }
7108 105 : if (att == NULL)
7109 : {
7110 : /* No such column, so assume OK */
7111 101 : relation_close(rel, AccessShareLock);
7112 101 : continue;
7113 : }
7114 : }
7115 :
7116 : /*
7117 : * We definitely should reject if the relation has storage. If it's
7118 : * partitioned, then perhaps we don't have to reject: if there are
7119 : * partitions then we'll fail when we find one, else there is no
7120 : * stored data to worry about. However, it's possible that the type
7121 : * change would affect conclusions about whether the type is sortable
7122 : * or hashable and thus (if it's a partitioning column) break the
7123 : * partitioning rule. For now, reject for partitioned rels too.
7124 : */
7125 88 : if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind) ||
7126 0 : RELKIND_HAS_PARTITIONS(rel->rd_rel->relkind))
7127 : {
7128 88 : if (origTypeName)
7129 20 : ereport(ERROR,
7130 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
7131 : errmsg("cannot alter type \"%s\" because column \"%s.%s\" uses it",
7132 : origTypeName,
7133 : RelationGetRelationName(rel),
7134 : NameStr(att->attname))));
7135 68 : else if (origRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
7136 12 : ereport(ERROR,
7137 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
7138 : errmsg("cannot alter type \"%s\" because column \"%s.%s\" uses it",
7139 : RelationGetRelationName(origRelation),
7140 : RelationGetRelationName(rel),
7141 : NameStr(att->attname))));
7142 56 : else if (origRelation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
7143 4 : ereport(ERROR,
7144 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
7145 : errmsg("cannot alter foreign table \"%s\" because column \"%s.%s\" uses its row type",
7146 : RelationGetRelationName(origRelation),
7147 : RelationGetRelationName(rel),
7148 : NameStr(att->attname))));
7149 : else
7150 52 : ereport(ERROR,
7151 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
7152 : errmsg("cannot alter table \"%s\" because column \"%s.%s\" uses its row type",
7153 : RelationGetRelationName(origRelation),
7154 : RelationGetRelationName(rel),
7155 : NameStr(att->attname))));
7156 : }
7157 0 : else if (OidIsValid(rel->rd_rel->reltype))
7158 : {
7159 : /*
7160 : * A view or composite type itself isn't a problem, but we must
7161 : * recursively check for indirect dependencies via its rowtype.
7162 : */
7163 0 : find_composite_type_dependencies(rel->rd_rel->reltype,
7164 : origRelation, origTypeName);
7165 : }
7166 :
7167 0 : relation_close(rel, AccessShareLock);
7168 : }
7169 :
7170 3200 : systable_endscan(depScan);
7171 :
7172 3200 : relation_close(depRel, AccessShareLock);
7173 3200 : }
7174 :
7175 :
7176 : /*
7177 : * find_typed_table_dependencies
7178 : *
7179 : * Check to see if a composite type is being used as the type of a
7180 : * typed table. Abort if any are found and behavior is RESTRICT.
7181 : * Else return the list of tables.
7182 : */
7183 : static List *
7184 145 : find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior behavior)
7185 : {
7186 : Relation classRel;
7187 : ScanKeyData key[1];
7188 : TableScanDesc scan;
7189 : HeapTuple tuple;
7190 145 : List *result = NIL;
7191 :
7192 145 : classRel = table_open(RelationRelationId, AccessShareLock);
7193 :
7194 145 : ScanKeyInit(&key[0],
7195 : Anum_pg_class_reloftype,
7196 : BTEqualStrategyNumber, F_OIDEQ,
7197 : ObjectIdGetDatum(typeOid));
7198 :
7199 145 : scan = table_beginscan_catalog(classRel, 1, key);
7200 :
7201 169 : while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
7202 : {
7203 40 : Form_pg_class classform = (Form_pg_class) GETSTRUCT(tuple);
7204 :
7205 40 : if (behavior == DROP_RESTRICT)
7206 16 : ereport(ERROR,
7207 : (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
7208 : errmsg("cannot alter type \"%s\" because it is the type of a typed table",
7209 : typeName),
7210 : errhint("Use ALTER ... CASCADE to alter the typed tables too.")));
7211 : else
7212 24 : result = lappend_oid(result, classform->oid);
7213 : }
7214 :
7215 129 : table_endscan(scan);
7216 129 : table_close(classRel, AccessShareLock);
7217 :
7218 129 : return result;
7219 : }
7220 :
7221 :
7222 : /*
7223 : * check_of_type
7224 : *
7225 : * Check whether a type is suitable for CREATE TABLE OF/ALTER TABLE OF. If it
7226 : * isn't suitable, throw an error. Currently, we require that the type
7227 : * originated with CREATE TYPE AS. We could support any row type, but doing so
7228 : * would require handling a number of extra corner cases in the DDL commands.
7229 : * (Also, allowing domain-over-composite would open up a can of worms about
7230 : * whether and how the domain's constraints should apply to derived tables.)
7231 : */
7232 : void
7233 119 : check_of_type(HeapTuple typetuple)
7234 : {
7235 119 : Form_pg_type typ = (Form_pg_type) GETSTRUCT(typetuple);
7236 119 : bool typeOk = false;
7237 :
7238 119 : if (typ->typtype == TYPTYPE_COMPOSITE)
7239 : {
7240 : Relation typeRelation;
7241 :
7242 : Assert(OidIsValid(typ->typrelid));
7243 115 : typeRelation = relation_open(typ->typrelid, AccessShareLock);
7244 115 : typeOk = (typeRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE);
7245 :
7246 : /*
7247 : * Close the parent rel, but keep our AccessShareLock on it until xact
7248 : * commit. That will prevent someone else from deleting or ALTERing
7249 : * the type before the typed table creation/conversion commits.
7250 : */
7251 115 : relation_close(typeRelation, NoLock);
7252 :
7253 115 : if (!typeOk)
7254 4 : ereport(ERROR,
7255 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
7256 : errmsg("type %s is the row type of another table",
7257 : format_type_be(typ->oid)),
7258 : errdetail("A typed table must use a stand-alone composite type created with CREATE TYPE.")));
7259 : }
7260 : else
7261 4 : ereport(ERROR,
7262 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
7263 : errmsg("type %s is not a composite type",
7264 : format_type_be(typ->oid))));
7265 111 : }
7266 :
7267 :
7268 : /*
7269 : * ALTER TABLE ADD COLUMN
7270 : *
7271 : * Adds an additional attribute to a relation making the assumption that
7272 : * CHECK, NOT NULL, and FOREIGN KEY constraints will be removed from the
7273 : * AT_AddColumn AlterTableCmd by parse_utilcmd.c and added as independent
7274 : * AlterTableCmd's.
7275 : *
7276 : * ADD COLUMN cannot use the normal ALTER TABLE recursion mechanism, because we
7277 : * have to decide at runtime whether to recurse or not depending on whether we
7278 : * actually add a column or merely merge with an existing column. (We can't
7279 : * check this in a static pre-pass because it won't handle multiple inheritance
7280 : * situations correctly.)
7281 : */
7282 : static void
7283 1610 : ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
7284 : bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode,
7285 : AlterTableUtilityContext *context)
7286 : {
7287 1610 : if (rel->rd_rel->reloftype && !recursing)
7288 4 : ereport(ERROR,
7289 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
7290 : errmsg("cannot add column to typed table")));
7291 :
7292 1606 : if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
7293 42 : ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);
7294 :
7295 1602 : if (recurse && !is_view)
7296 1527 : cmd->recurse = true;
7297 1602 : }
7298 :
7299 : /*
7300 : * Add a column to a table. The return value is the address of the
7301 : * new column in the parent relation.
7302 : *
7303 : * cmd is pass-by-ref so that we can replace it with the parse-transformed
7304 : * copy (but that happens only after we check for IF NOT EXISTS).
7305 : */
7306 : static ObjectAddress
7307 2112 : ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
7308 : AlterTableCmd **cmd, bool recurse, bool recursing,
7309 : LOCKMODE lockmode, AlterTablePass cur_pass,
7310 : AlterTableUtilityContext *context)
7311 : {
7312 2112 : Oid myrelid = RelationGetRelid(rel);
7313 2112 : ColumnDef *colDef = castNode(ColumnDef, (*cmd)->def);
7314 2112 : bool if_not_exists = (*cmd)->missing_ok;
7315 : Relation pgclass,
7316 : attrdesc;
7317 : HeapTuple reltup;
7318 : Form_pg_class relform;
7319 : Form_pg_attribute attribute;
7320 : int newattnum;
7321 : char relkind;
7322 : Expr *defval;
7323 : List *children;
7324 : ListCell *child;
7325 : AlterTableCmd *childcmd;
7326 : ObjectAddress address;
7327 : TupleDesc tupdesc;
7328 :
7329 : /* since this function recurses, it could be driven to stack overflow */
7330 2112 : check_stack_depth();
7331 :
7332 : /* At top level, permission check was done in ATPrepCmd, else do it */
7333 2112 : if (recursing)
7334 514 : ATSimplePermissions((*cmd)->subtype, rel,
7335 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
7336 :
7337 2112 : if (rel->rd_rel->relispartition && !recursing)
7338 8 : ereport(ERROR,
7339 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
7340 : errmsg("cannot add column to a partition")));
7341 :
7342 2104 : attrdesc = table_open(AttributeRelationId, RowExclusiveLock);
7343 :
7344 : /*
7345 : * Are we adding the column to a recursion child? If so, check whether to
7346 : * merge with an existing definition for the column. If we do merge, we
7347 : * must not recurse. Children will already have the column, and recursing
7348 : * into them would mess up attinhcount.
7349 : */
7350 2104 : if (colDef->inhcount > 0)
7351 : {
7352 : HeapTuple tuple;
7353 :
7354 : /* Does child already have a column by this name? */
7355 514 : tuple = SearchSysCacheCopyAttName(myrelid, colDef->colname);
7356 514 : if (HeapTupleIsValid(tuple))
7357 : {
7358 40 : Form_pg_attribute childatt = (Form_pg_attribute) GETSTRUCT(tuple);
7359 : Oid ctypeId;
7360 : int32 ctypmod;
7361 : Oid ccollid;
7362 :
7363 : /* Child column must match on type, typmod, and collation */
7364 40 : typenameTypeIdAndMod(NULL, colDef->typeName, &ctypeId, &ctypmod);
7365 40 : if (ctypeId != childatt->atttypid ||
7366 40 : ctypmod != childatt->atttypmod)
7367 0 : ereport(ERROR,
7368 : (errcode(ERRCODE_DATATYPE_MISMATCH),
7369 : errmsg("child table \"%s\" has different type for column \"%s\"",
7370 : RelationGetRelationName(rel), colDef->colname)));
7371 40 : ccollid = GetColumnDefCollation(NULL, colDef, ctypeId);
7372 40 : if (ccollid != childatt->attcollation)
7373 0 : ereport(ERROR,
7374 : (errcode(ERRCODE_COLLATION_MISMATCH),
7375 : errmsg("child table \"%s\" has different collation for column \"%s\"",
7376 : RelationGetRelationName(rel), colDef->colname),
7377 : errdetail("\"%s\" versus \"%s\"",
7378 : get_collation_name(ccollid),
7379 : get_collation_name(childatt->attcollation))));
7380 :
7381 : /* Bump the existing child att's inhcount */
7382 40 : if (pg_add_s16_overflow(childatt->attinhcount, 1,
7383 : &childatt->attinhcount))
7384 0 : ereport(ERROR,
7385 : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
7386 : errmsg("too many inheritance parents"));
7387 40 : CatalogTupleUpdate(attrdesc, &tuple->t_self, tuple);
7388 :
7389 40 : heap_freetuple(tuple);
7390 :
7391 : /* Inform the user about the merge */
7392 40 : ereport(NOTICE,
7393 : (errmsg("merging definition of column \"%s\" for child \"%s\"",
7394 : colDef->colname, RelationGetRelationName(rel))));
7395 :
7396 40 : table_close(attrdesc, RowExclusiveLock);
7397 :
7398 : /* Make the child column change visible */
7399 40 : CommandCounterIncrement();
7400 :
7401 40 : return InvalidObjectAddress;
7402 : }
7403 : }
7404 :
7405 : /* skip if the name already exists and if_not_exists is true */
7406 2064 : if (!check_for_column_name_collision(rel, colDef->colname, if_not_exists))
7407 : {
7408 44 : table_close(attrdesc, RowExclusiveLock);
7409 44 : return InvalidObjectAddress;
7410 : }
7411 :
7412 : /*
7413 : * Okay, we need to add the column, so go ahead and do parse
7414 : * transformation. This can result in queueing up, or even immediately
7415 : * executing, subsidiary operations (such as creation of unique indexes);
7416 : * so we mustn't do it until we have made the if_not_exists check.
7417 : *
7418 : * When recursing, the command was already transformed and we needn't do
7419 : * so again. Also, if context isn't given we can't transform. (That
7420 : * currently happens only for AT_AddColumnToView; we expect that view.c
7421 : * passed us a ColumnDef that doesn't need work.)
7422 : */
7423 2000 : if (context != NULL && !recursing)
7424 : {
7425 1505 : *cmd = ATParseTransformCmd(wqueue, tab, rel, *cmd, recurse, lockmode,
7426 : cur_pass, context);
7427 : Assert(*cmd != NULL);
7428 1501 : colDef = castNode(ColumnDef, (*cmd)->def);
7429 : }
7430 :
7431 : /*
7432 : * Regular inheritance children are independent enough not to inherit the
7433 : * identity column from parent hence cannot recursively add identity
7434 : * column if the table has inheritance children.
7435 : *
7436 : * Partitions, on the other hand, are integral part of a partitioned table
7437 : * and inherit identity column. Hence propagate identity column down the
7438 : * partition hierarchy.
7439 : */
7440 1996 : if (colDef->identity &&
7441 36 : recurse &&
7442 68 : rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
7443 32 : find_inheritance_children(myrelid, NoLock) != NIL)
7444 4 : ereport(ERROR,
7445 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
7446 : errmsg("cannot recursively add identity column to table that has child tables")));
7447 :
7448 1992 : pgclass = table_open(RelationRelationId, RowExclusiveLock);
7449 :
7450 1992 : reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
7451 1992 : if (!HeapTupleIsValid(reltup))
7452 0 : elog(ERROR, "cache lookup failed for relation %u", myrelid);
7453 1992 : relform = (Form_pg_class) GETSTRUCT(reltup);
7454 1992 : relkind = relform->relkind;
7455 :
7456 : /* Determine the new attribute's number */
7457 1992 : newattnum = relform->relnatts + 1;
7458 1992 : if (newattnum > MaxHeapAttributeNumber)
7459 0 : ereport(ERROR,
7460 : (errcode(ERRCODE_TOO_MANY_COLUMNS),
7461 : errmsg("tables can have at most %d columns",
7462 : MaxHeapAttributeNumber)));
7463 :
7464 : /*
7465 : * Construct new attribute's pg_attribute entry.
7466 : */
7467 1992 : tupdesc = BuildDescForRelation(list_make1(colDef));
7468 :
7469 1984 : attribute = TupleDescAttr(tupdesc, 0);
7470 :
7471 : /* Fix up attribute number */
7472 1984 : attribute->attnum = newattnum;
7473 :
7474 : /* make sure datatype is legal for a column */
7475 3968 : CheckAttributeType(NameStr(attribute->attname), attribute->atttypid, attribute->attcollation,
7476 1984 : list_make1_oid(rel->rd_rel->reltype),
7477 1984 : (attribute->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL ? CHKATYPE_IS_VIRTUAL : 0));
7478 :
7479 1956 : InsertPgAttributeTuples(attrdesc, tupdesc, myrelid, NULL, NULL);
7480 :
7481 1956 : table_close(attrdesc, RowExclusiveLock);
7482 :
7483 : /*
7484 : * Update pg_class tuple as appropriate
7485 : */
7486 1956 : relform->relnatts = newattnum;
7487 :
7488 1956 : CatalogTupleUpdate(pgclass, &reltup->t_self, reltup);
7489 :
7490 1956 : heap_freetuple(reltup);
7491 :
7492 : /* Post creation hook for new attribute */
7493 1956 : InvokeObjectPostCreateHook(RelationRelationId, myrelid, newattnum);
7494 :
7495 1956 : table_close(pgclass, RowExclusiveLock);
7496 :
7497 : /* Make the attribute's catalog entry visible */
7498 1956 : CommandCounterIncrement();
7499 :
7500 : /*
7501 : * Store the DEFAULT, if any, in the catalogs
7502 : */
7503 1956 : if (colDef->raw_default)
7504 : {
7505 : RawColumnDefault *rawEnt;
7506 :
7507 758 : rawEnt = palloc_object(RawColumnDefault);
7508 758 : rawEnt->attnum = attribute->attnum;
7509 758 : rawEnt->raw_default = copyObject(colDef->raw_default);
7510 758 : rawEnt->generated = colDef->generated;
7511 :
7512 : /*
7513 : * This function is intended for CREATE TABLE, so it processes a
7514 : * _list_ of defaults, but we just do one.
7515 : */
7516 758 : AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
7517 : false, true, false, NULL);
7518 :
7519 : /* Make the additional catalog changes visible */
7520 670 : CommandCounterIncrement();
7521 : }
7522 :
7523 : /*
7524 : * Tell Phase 3 to fill in the default expression, if there is one.
7525 : *
7526 : * If there is no default, Phase 3 doesn't have to do anything, because
7527 : * that effectively means that the default is NULL. The heap tuple access
7528 : * routines always check for attnum > # of attributes in tuple, and return
7529 : * NULL if so, so without any modification of the tuple data we will get
7530 : * the effect of NULL values in the new column.
7531 : *
7532 : * An exception occurs when the new column is of a domain type: the domain
7533 : * might have a not-null constraint, or a check constraint that indirectly
7534 : * rejects nulls. If there are any domain constraints then we construct
7535 : * an explicit NULL default value that will be passed through
7536 : * CoerceToDomain processing. (This is a tad inefficient, since it causes
7537 : * rewriting the table which we really wouldn't have to do; but we do it
7538 : * to preserve the historical behavior that such a failure will be raised
7539 : * only if the table currently contains some rows.)
7540 : *
7541 : * Note: we use build_column_default, and not just the cooked default
7542 : * returned by AddRelationNewConstraints, so that the right thing happens
7543 : * when a datatype's default applies.
7544 : *
7545 : * Note: it might seem that this should happen at the end of Phase 2, so
7546 : * that the effects of subsequent subcommands can be taken into account.
7547 : * It's intentional that we do it now, though. The new column should be
7548 : * filled according to what is said in the ADD COLUMN subcommand, so that
7549 : * the effects are the same as if this subcommand had been run by itself
7550 : * and the later subcommands had been issued in new ALTER TABLE commands.
7551 : *
7552 : * We can skip this entirely for relations without storage, since Phase 3
7553 : * is certainly not going to touch them.
7554 : */
7555 1868 : if (RELKIND_HAS_STORAGE(relkind))
7556 : {
7557 : bool has_domain_constraints;
7558 1598 : bool has_missing = false;
7559 :
7560 : /*
7561 : * For an identity column, we can't use build_column_default(),
7562 : * because the sequence ownership isn't set yet. So do it manually.
7563 : */
7564 1598 : if (colDef->identity)
7565 : {
7566 28 : NextValueExpr *nve = makeNode(NextValueExpr);
7567 :
7568 28 : nve->seqid = RangeVarGetRelid(colDef->identitySequence, NoLock, false);
7569 28 : nve->typeId = attribute->atttypid;
7570 :
7571 28 : defval = (Expr *) nve;
7572 : }
7573 : else
7574 1570 : defval = (Expr *) build_column_default(rel, attribute->attnum);
7575 :
7576 : /* Build CoerceToDomain(NULL) expression if needed */
7577 1598 : has_domain_constraints = DomainHasConstraints(attribute->atttypid, NULL);
7578 1598 : if (!defval && has_domain_constraints)
7579 : {
7580 : Oid baseTypeId;
7581 : int32 baseTypeMod;
7582 : Oid baseTypeColl;
7583 :
7584 4 : baseTypeMod = attribute->atttypmod;
7585 4 : baseTypeId = getBaseTypeAndTypmod(attribute->atttypid, &baseTypeMod);
7586 4 : baseTypeColl = get_typcollation(baseTypeId);
7587 4 : defval = (Expr *) makeNullConst(baseTypeId, baseTypeMod, baseTypeColl);
7588 4 : defval = (Expr *) coerce_to_target_type(NULL,
7589 : (Node *) defval,
7590 : baseTypeId,
7591 : attribute->atttypid,
7592 : attribute->atttypmod,
7593 : COERCION_ASSIGNMENT,
7594 : COERCE_IMPLICIT_CAST,
7595 : -1);
7596 4 : if (defval == NULL) /* should not happen */
7597 0 : elog(ERROR, "failed to coerce base type to domain");
7598 : }
7599 :
7600 1598 : if (defval)
7601 : {
7602 : NewColumnValue *newval;
7603 :
7604 : /* Prepare defval for execution, either here or in Phase 3 */
7605 607 : defval = expression_planner(defval);
7606 :
7607 : /* Add the new default to the newvals list */
7608 607 : newval = palloc0_object(NewColumnValue);
7609 607 : newval->attnum = attribute->attnum;
7610 607 : newval->expr = defval;
7611 607 : newval->is_generated = (colDef->generated != '\0');
7612 :
7613 607 : tab->newvals = lappend(tab->newvals, newval);
7614 :
7615 : /*
7616 : * Attempt to skip a complete table rewrite by storing the
7617 : * specified DEFAULT value outside of the heap. This is only
7618 : * allowed for plain relations and non-generated columns, and the
7619 : * default expression can't be volatile (stable is OK). Note that
7620 : * contain_volatile_functions deems CoerceToDomain immutable, but
7621 : * here we consider that coercion to a domain with constraints is
7622 : * volatile; else it might fail even when the table is empty.
7623 : */
7624 607 : if (rel->rd_rel->relkind == RELKIND_RELATION &&
7625 607 : !colDef->generated &&
7626 470 : !has_domain_constraints &&
7627 462 : !contain_volatile_functions((Node *) defval))
7628 352 : {
7629 : EState *estate;
7630 : ExprState *exprState;
7631 : Datum missingval;
7632 : bool missingIsNull;
7633 :
7634 : /* Evaluate the default expression */
7635 352 : estate = CreateExecutorState();
7636 352 : exprState = ExecPrepareExpr(defval, estate);
7637 352 : missingval = ExecEvalExpr(exprState,
7638 352 : GetPerTupleExprContext(estate),
7639 : &missingIsNull);
7640 : /* If it turns out NULL, nothing to do; else store it */
7641 352 : if (!missingIsNull)
7642 : {
7643 352 : StoreAttrMissingVal(rel, attribute->attnum, missingval);
7644 : /* Make the additional catalog change visible */
7645 352 : CommandCounterIncrement();
7646 352 : has_missing = true;
7647 : }
7648 352 : FreeExecutorState(estate);
7649 : }
7650 : else
7651 : {
7652 : /*
7653 : * Failed to use missing mode. We have to do a table rewrite
7654 : * to install the value --- unless it's a virtual generated
7655 : * column.
7656 : */
7657 255 : if (colDef->generated != ATTRIBUTE_GENERATED_VIRTUAL)
7658 182 : tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
7659 : }
7660 : }
7661 :
7662 1598 : if (!has_missing)
7663 : {
7664 : /*
7665 : * If the new column is NOT NULL, and there is no missing value,
7666 : * tell Phase 3 it needs to check for NULLs.
7667 : */
7668 1246 : tab->verify_new_notnull |= colDef->is_not_null;
7669 : }
7670 : }
7671 :
7672 : /*
7673 : * Add needed dependency entries for the new column.
7674 : */
7675 1868 : add_column_datatype_dependency(myrelid, newattnum, attribute->atttypid);
7676 1868 : add_column_collation_dependency(myrelid, newattnum, attribute->attcollation);
7677 :
7678 : /*
7679 : * Propagate to children as appropriate. Unlike most other ALTER
7680 : * routines, we have to do this one level of recursion at a time; we can't
7681 : * use find_all_inheritors to do it in one pass.
7682 : */
7683 : children =
7684 1868 : find_inheritance_children(RelationGetRelid(rel), lockmode);
7685 :
7686 : /*
7687 : * If we are told not to recurse, there had better not be any child
7688 : * tables; else the addition would put them out of step.
7689 : */
7690 1868 : if (children && !recurse)
7691 8 : ereport(ERROR,
7692 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
7693 : errmsg("column must be added to child tables too")));
7694 :
7695 : /* Children should see column as singly inherited */
7696 1860 : if (!recursing)
7697 : {
7698 1386 : childcmd = copyObject(*cmd);
7699 1386 : colDef = castNode(ColumnDef, childcmd->def);
7700 1386 : colDef->inhcount = 1;
7701 1386 : colDef->is_local = false;
7702 : }
7703 : else
7704 474 : childcmd = *cmd; /* no need to copy again */
7705 :
7706 2374 : foreach(child, children)
7707 : {
7708 514 : Oid childrelid = lfirst_oid(child);
7709 : Relation childrel;
7710 : AlteredTableInfo *childtab;
7711 :
7712 : /* find_inheritance_children already got lock */
7713 514 : childrel = table_open(childrelid, NoLock);
7714 514 : CheckAlterTableIsSafe(childrel);
7715 :
7716 : /* Find or create work queue entry for this table */
7717 514 : childtab = ATGetQueueEntry(wqueue, childrel);
7718 :
7719 : /* Recurse to child; return value is ignored */
7720 514 : ATExecAddColumn(wqueue, childtab, childrel,
7721 : &childcmd, recurse, true,
7722 : lockmode, cur_pass, context);
7723 :
7724 514 : table_close(childrel, NoLock);
7725 : }
7726 :
7727 1860 : ObjectAddressSubSet(address, RelationRelationId, myrelid, newattnum);
7728 1860 : return address;
7729 : }
7730 :
7731 : /*
7732 : * If a new or renamed column will collide with the name of an existing
7733 : * column and if_not_exists is false then error out, else do nothing.
7734 : */
7735 : static bool
7736 2363 : check_for_column_name_collision(Relation rel, const char *colname,
7737 : bool if_not_exists)
7738 : {
7739 : HeapTuple attTuple;
7740 : int attnum;
7741 :
7742 : /*
7743 : * this test is deliberately not attisdropped-aware, since if one tries to
7744 : * add a column matching a dropped column name, it's gonna fail anyway.
7745 : */
7746 2363 : attTuple = SearchSysCache2(ATTNAME,
7747 : ObjectIdGetDatum(RelationGetRelid(rel)),
7748 : PointerGetDatum(colname));
7749 2363 : if (!HeapTupleIsValid(attTuple))
7750 2291 : return true;
7751 :
7752 72 : attnum = ((Form_pg_attribute) GETSTRUCT(attTuple))->attnum;
7753 72 : ReleaseSysCache(attTuple);
7754 :
7755 : /*
7756 : * We throw a different error message for conflicts with system column
7757 : * names, since they are normally not shown and the user might otherwise
7758 : * be confused about the reason for the conflict.
7759 : */
7760 72 : if (attnum <= 0)
7761 8 : ereport(ERROR,
7762 : (errcode(ERRCODE_DUPLICATE_COLUMN),
7763 : errmsg("column name \"%s\" conflicts with a system column name",
7764 : colname)));
7765 : else
7766 : {
7767 64 : if (if_not_exists)
7768 : {
7769 44 : ereport(NOTICE,
7770 : (errcode(ERRCODE_DUPLICATE_COLUMN),
7771 : errmsg("column \"%s\" of relation \"%s\" already exists, skipping",
7772 : colname, RelationGetRelationName(rel))));
7773 44 : return false;
7774 : }
7775 :
7776 20 : ereport(ERROR,
7777 : (errcode(ERRCODE_DUPLICATE_COLUMN),
7778 : errmsg("column \"%s\" of relation \"%s\" already exists",
7779 : colname, RelationGetRelationName(rel))));
7780 : }
7781 :
7782 : return true;
7783 : }
7784 :
7785 : /*
7786 : * Install a column's dependency on its datatype.
7787 : */
7788 : static void
7789 2632 : add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid)
7790 : {
7791 : ObjectAddress myself,
7792 : referenced;
7793 :
7794 2632 : myself.classId = RelationRelationId;
7795 2632 : myself.objectId = relid;
7796 2632 : myself.objectSubId = attnum;
7797 2632 : referenced.classId = TypeRelationId;
7798 2632 : referenced.objectId = typid;
7799 2632 : referenced.objectSubId = 0;
7800 2632 : recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
7801 2632 : }
7802 :
7803 : /*
7804 : * Install a column's dependency on its collation.
7805 : */
7806 : static void
7807 2632 : add_column_collation_dependency(Oid relid, int32 attnum, Oid collid)
7808 : {
7809 : ObjectAddress myself,
7810 : referenced;
7811 :
7812 : /* We know the default collation is pinned, so don't bother recording it */
7813 2632 : if (OidIsValid(collid) && collid != DEFAULT_COLLATION_OID)
7814 : {
7815 12 : myself.classId = RelationRelationId;
7816 12 : myself.objectId = relid;
7817 12 : myself.objectSubId = attnum;
7818 12 : referenced.classId = CollationRelationId;
7819 12 : referenced.objectId = collid;
7820 12 : referenced.objectSubId = 0;
7821 12 : recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
7822 : }
7823 2632 : }
7824 :
7825 : /*
7826 : * ALTER TABLE ALTER COLUMN DROP NOT NULL
7827 : *
7828 : * Return the address of the modified column. If the column was already
7829 : * nullable, InvalidObjectAddress is returned.
7830 : */
7831 : static ObjectAddress
7832 177 : ATExecDropNotNull(Relation rel, const char *colName, bool recurse,
7833 : LOCKMODE lockmode)
7834 : {
7835 : HeapTuple tuple;
7836 : HeapTuple conTup;
7837 : Form_pg_attribute attTup;
7838 : AttrNumber attnum;
7839 : Relation attr_rel;
7840 : ObjectAddress address;
7841 :
7842 : /*
7843 : * lookup the attribute
7844 : */
7845 177 : attr_rel = table_open(AttributeRelationId, RowExclusiveLock);
7846 :
7847 177 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
7848 177 : if (!HeapTupleIsValid(tuple))
7849 12 : ereport(ERROR,
7850 : (errcode(ERRCODE_UNDEFINED_COLUMN),
7851 : errmsg("column \"%s\" of relation \"%s\" does not exist",
7852 : colName, RelationGetRelationName(rel))));
7853 165 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
7854 165 : attnum = attTup->attnum;
7855 165 : ObjectAddressSubSet(address, RelationRelationId,
7856 : RelationGetRelid(rel), attnum);
7857 :
7858 : /* If the column is already nullable there's nothing to do. */
7859 165 : if (!attTup->attnotnull)
7860 : {
7861 0 : table_close(attr_rel, RowExclusiveLock);
7862 0 : return InvalidObjectAddress;
7863 : }
7864 :
7865 : /* Prevent them from altering a system attribute */
7866 165 : if (attnum <= 0)
7867 0 : ereport(ERROR,
7868 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
7869 : errmsg("cannot alter system column \"%s\"",
7870 : colName)));
7871 :
7872 165 : if (attTup->attidentity)
7873 12 : ereport(ERROR,
7874 : (errcode(ERRCODE_SYNTAX_ERROR),
7875 : errmsg("column \"%s\" of relation \"%s\" is an identity column",
7876 : colName, RelationGetRelationName(rel))));
7877 :
7878 : /*
7879 : * If rel is partition, shouldn't drop NOT NULL if parent has the same.
7880 : */
7881 153 : if (rel->rd_rel->relispartition)
7882 : {
7883 8 : Oid parentId = get_partition_parent(RelationGetRelid(rel), false);
7884 8 : Relation parent = table_open(parentId, AccessShareLock);
7885 8 : TupleDesc tupDesc = RelationGetDescr(parent);
7886 : AttrNumber parent_attnum;
7887 :
7888 8 : parent_attnum = get_attnum(parentId, colName);
7889 8 : if (TupleDescAttr(tupDesc, parent_attnum - 1)->attnotnull)
7890 8 : ereport(ERROR,
7891 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
7892 : errmsg("column \"%s\" is marked NOT NULL in parent table",
7893 : colName)));
7894 0 : table_close(parent, AccessShareLock);
7895 : }
7896 :
7897 : /*
7898 : * Find the constraint that makes this column NOT NULL, and drop it.
7899 : * dropconstraint_internal() resets attnotnull.
7900 : */
7901 145 : conTup = findNotNullConstraintAttnum(RelationGetRelid(rel), attnum);
7902 145 : if (conTup == NULL)
7903 0 : elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation \"%s\"",
7904 : colName, RelationGetRelationName(rel));
7905 :
7906 : /* The normal case: we have a pg_constraint row, remove it */
7907 145 : dropconstraint_internal(rel, conTup, DROP_RESTRICT, recurse, false,
7908 : false, lockmode);
7909 109 : heap_freetuple(conTup);
7910 :
7911 109 : InvokeObjectPostAlterHook(RelationRelationId,
7912 : RelationGetRelid(rel), attnum);
7913 :
7914 109 : table_close(attr_rel, RowExclusiveLock);
7915 :
7916 109 : return address;
7917 : }
7918 :
7919 : /*
7920 : * set_attnotnull
7921 : * Helper to update/validate the pg_attribute status of a not-null
7922 : * constraint
7923 : *
7924 : * pg_attribute.attnotnull is set true, if it isn't already.
7925 : * If queue_validation is true, also set up wqueue to validate the constraint.
7926 : * wqueue may be given as NULL when validation is not needed (e.g., on table
7927 : * creation).
7928 : */
7929 : static void
7930 16570 : set_attnotnull(List **wqueue, Relation rel, AttrNumber attnum,
7931 : bool is_valid, bool queue_validation)
7932 : {
7933 : Form_pg_attribute attr;
7934 : CompactAttribute *thisatt;
7935 :
7936 : Assert(!queue_validation || wqueue);
7937 :
7938 16570 : CheckAlterTableIsSafe(rel);
7939 :
7940 : /*
7941 : * Exit quickly by testing attnotnull from the tupledesc's copy of the
7942 : * attribute.
7943 : */
7944 16570 : attr = TupleDescAttr(RelationGetDescr(rel), attnum - 1);
7945 16570 : if (attr->attisdropped)
7946 0 : return;
7947 :
7948 16570 : if (!attr->attnotnull)
7949 : {
7950 : Relation attr_rel;
7951 : HeapTuple tuple;
7952 :
7953 983 : attr_rel = table_open(AttributeRelationId, RowExclusiveLock);
7954 :
7955 983 : tuple = SearchSysCacheCopyAttNum(RelationGetRelid(rel), attnum);
7956 983 : if (!HeapTupleIsValid(tuple))
7957 0 : elog(ERROR, "cache lookup failed for attribute %d of relation %u",
7958 : attnum, RelationGetRelid(rel));
7959 :
7960 983 : thisatt = TupleDescCompactAttr(RelationGetDescr(rel), attnum - 1);
7961 983 : thisatt->attnullability = ATTNULLABLE_VALID;
7962 :
7963 983 : attr = (Form_pg_attribute) GETSTRUCT(tuple);
7964 :
7965 983 : attr->attnotnull = true;
7966 983 : CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple);
7967 :
7968 : /*
7969 : * If the nullness isn't already proven by validated constraints, have
7970 : * ALTER TABLE phase 3 test for it.
7971 : */
7972 983 : if (queue_validation && wqueue &&
7973 816 : !NotNullImpliedByRelConstraints(rel, attr))
7974 : {
7975 : AlteredTableInfo *tab;
7976 :
7977 784 : tab = ATGetQueueEntry(wqueue, rel);
7978 784 : tab->verify_new_notnull = true;
7979 : }
7980 :
7981 983 : CommandCounterIncrement();
7982 :
7983 983 : table_close(attr_rel, RowExclusiveLock);
7984 983 : heap_freetuple(tuple);
7985 : }
7986 : else
7987 : {
7988 15587 : CacheInvalidateRelcache(rel);
7989 : }
7990 : }
7991 :
7992 : /*
7993 : * ALTER TABLE ALTER COLUMN SET NOT NULL
7994 : *
7995 : * Add a not-null constraint to a single table and its children. Returns
7996 : * the address of the constraint added to the parent relation, if one gets
7997 : * added, or InvalidObjectAddress otherwise.
7998 : *
7999 : * We must recurse to child tables during execution, rather than using
8000 : * ALTER TABLE's normal prep-time recursion.
8001 : */
8002 : static ObjectAddress
8003 469 : ATExecSetNotNull(List **wqueue, Relation rel, char *conName, char *colName,
8004 : bool recurse, bool recursing, LOCKMODE lockmode)
8005 : {
8006 : HeapTuple tuple;
8007 : AttrNumber attnum;
8008 : ObjectAddress address;
8009 : Constraint *constraint;
8010 : CookedConstraint *ccon;
8011 : List *cooked;
8012 469 : bool is_no_inherit = false;
8013 :
8014 : /* Guard against stack overflow due to overly deep inheritance tree. */
8015 469 : check_stack_depth();
8016 :
8017 : /* At top level, permission check was done in ATPrepCmd, else do it */
8018 469 : if (recursing)
8019 : {
8020 197 : ATSimplePermissions(AT_AddConstraint, rel,
8021 : ATT_PARTITIONED_TABLE | ATT_TABLE | ATT_FOREIGN_TABLE);
8022 : Assert(conName != NULL);
8023 : }
8024 :
8025 469 : attnum = get_attnum(RelationGetRelid(rel), colName);
8026 469 : if (attnum == InvalidAttrNumber)
8027 12 : ereport(ERROR,
8028 : (errcode(ERRCODE_UNDEFINED_COLUMN),
8029 : errmsg("column \"%s\" of relation \"%s\" does not exist",
8030 : colName, RelationGetRelationName(rel))));
8031 :
8032 : /* Prevent them from altering a system attribute */
8033 457 : if (attnum <= 0)
8034 0 : ereport(ERROR,
8035 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8036 : errmsg("cannot alter system column \"%s\"",
8037 : colName)));
8038 :
8039 : /* See if there's already a constraint */
8040 457 : tuple = findNotNullConstraintAttnum(RelationGetRelid(rel), attnum);
8041 457 : if (HeapTupleIsValid(tuple))
8042 : {
8043 105 : Form_pg_constraint conForm = (Form_pg_constraint) GETSTRUCT(tuple);
8044 105 : bool changed = false;
8045 :
8046 : /*
8047 : * Don't let a NO INHERIT constraint be changed into inherit.
8048 : */
8049 105 : if (conForm->connoinherit && recurse)
8050 8 : ereport(ERROR,
8051 : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8052 : errmsg("cannot change NO INHERIT status of NOT NULL constraint \"%s\" on relation \"%s\"",
8053 : NameStr(conForm->conname),
8054 : RelationGetRelationName(rel)));
8055 :
8056 : /*
8057 : * If we find an appropriate constraint, we're almost done, but just
8058 : * need to change some properties on it: if we're recursing, increment
8059 : * coninhcount; if not, set conislocal if not already set.
8060 : */
8061 97 : if (recursing)
8062 : {
8063 68 : if (pg_add_s16_overflow(conForm->coninhcount, 1,
8064 : &conForm->coninhcount))
8065 0 : ereport(ERROR,
8066 : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
8067 : errmsg("too many inheritance parents"));
8068 68 : changed = true;
8069 : }
8070 29 : else if (!conForm->conislocal)
8071 : {
8072 0 : conForm->conislocal = true;
8073 0 : changed = true;
8074 : }
8075 29 : else if (!conForm->convalidated)
8076 : {
8077 : /*
8078 : * Flip attnotnull and convalidated, and also validate the
8079 : * constraint.
8080 : */
8081 16 : return ATExecValidateConstraint(wqueue, rel, NameStr(conForm->conname),
8082 : recurse, recursing, lockmode);
8083 : }
8084 :
8085 81 : if (changed)
8086 : {
8087 : Relation constr_rel;
8088 :
8089 68 : constr_rel = table_open(ConstraintRelationId, RowExclusiveLock);
8090 :
8091 68 : CatalogTupleUpdate(constr_rel, &tuple->t_self, tuple);
8092 68 : ObjectAddressSet(address, ConstraintRelationId, conForm->oid);
8093 68 : table_close(constr_rel, RowExclusiveLock);
8094 : }
8095 :
8096 81 : if (changed)
8097 68 : return address;
8098 : else
8099 13 : return InvalidObjectAddress;
8100 : }
8101 :
8102 : /*
8103 : * If we're asked not to recurse, and children exist, raise an error for
8104 : * partitioned tables. For inheritance, we act as if NO INHERIT had been
8105 : * specified.
8106 : */
8107 372 : if (!recurse &&
8108 20 : find_inheritance_children(RelationGetRelid(rel),
8109 : NoLock) != NIL)
8110 : {
8111 12 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
8112 4 : ereport(ERROR,
8113 : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8114 : errmsg("constraint must be added to child tables too"),
8115 : errhint("Do not specify the ONLY keyword."));
8116 : else
8117 8 : is_no_inherit = true;
8118 : }
8119 :
8120 : /*
8121 : * No constraint exists; we must add one. First determine a name to use,
8122 : * if we haven't already.
8123 : */
8124 348 : if (!recursing)
8125 : {
8126 : Assert(conName == NULL);
8127 223 : conName = ChooseConstraintName(RelationGetRelationName(rel),
8128 : colName, "not_null",
8129 223 : RelationGetNamespace(rel),
8130 : NIL);
8131 : }
8132 :
8133 348 : constraint = makeNotNullConstraint(makeString(colName));
8134 348 : constraint->is_no_inherit = is_no_inherit;
8135 348 : constraint->conname = conName;
8136 :
8137 : /* and do it */
8138 348 : cooked = AddRelationNewConstraints(rel, NIL, list_make1(constraint),
8139 348 : false, !recursing, false, NULL);
8140 348 : ccon = linitial(cooked);
8141 348 : ObjectAddressSet(address, ConstraintRelationId, ccon->conoid);
8142 :
8143 : /* Mark pg_attribute.attnotnull for the column and queue validation */
8144 348 : set_attnotnull(wqueue, rel, attnum, true, true);
8145 :
8146 348 : InvokeObjectPostAlterHook(RelationRelationId,
8147 : RelationGetRelid(rel), attnum);
8148 :
8149 : /*
8150 : * Recurse to propagate the constraint to children that don't have one.
8151 : */
8152 348 : if (recurse)
8153 : {
8154 : List *children;
8155 :
8156 332 : children = find_inheritance_children(RelationGetRelid(rel),
8157 : lockmode);
8158 :
8159 817 : foreach_oid(childoid, children)
8160 : {
8161 161 : Relation childrel = table_open(childoid, NoLock);
8162 :
8163 161 : CommandCounterIncrement();
8164 :
8165 161 : ATExecSetNotNull(wqueue, childrel, conName, colName,
8166 : recurse, true, lockmode);
8167 157 : table_close(childrel, NoLock);
8168 : }
8169 : }
8170 :
8171 344 : return address;
8172 : }
8173 :
8174 : /*
8175 : * NotNullImpliedByRelConstraints
8176 : * Does rel's existing constraints imply NOT NULL for the given attribute?
8177 : */
8178 : static bool
8179 816 : NotNullImpliedByRelConstraints(Relation rel, Form_pg_attribute attr)
8180 : {
8181 816 : NullTest *nnulltest = makeNode(NullTest);
8182 :
8183 1632 : nnulltest->arg = (Expr *) makeVar(1,
8184 816 : attr->attnum,
8185 : attr->atttypid,
8186 : attr->atttypmod,
8187 : attr->attcollation,
8188 : 0);
8189 816 : nnulltest->nulltesttype = IS_NOT_NULL;
8190 :
8191 : /*
8192 : * argisrow = false is correct even for a composite column, because
8193 : * attnotnull does not represent a SQL-spec IS NOT NULL test in such a
8194 : * case, just IS DISTINCT FROM NULL.
8195 : */
8196 816 : nnulltest->argisrow = false;
8197 816 : nnulltest->location = -1;
8198 :
8199 816 : if (ConstraintImpliedByRelConstraint(rel, list_make1(nnulltest), NIL))
8200 : {
8201 32 : ereport(DEBUG1,
8202 : (errmsg_internal("existing constraints on column \"%s.%s\" are sufficient to prove that it does not contain nulls",
8203 : RelationGetRelationName(rel), NameStr(attr->attname))));
8204 32 : return true;
8205 : }
8206 :
8207 784 : return false;
8208 : }
8209 :
8210 : /*
8211 : * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
8212 : *
8213 : * Return the address of the affected column.
8214 : */
8215 : static ObjectAddress
8216 383 : ATExecColumnDefault(Relation rel, const char *colName,
8217 : Node *newDefault, LOCKMODE lockmode)
8218 : {
8219 383 : TupleDesc tupdesc = RelationGetDescr(rel);
8220 : AttrNumber attnum;
8221 : ObjectAddress address;
8222 :
8223 : /*
8224 : * get the number of the attribute
8225 : */
8226 383 : attnum = get_attnum(RelationGetRelid(rel), colName);
8227 383 : if (attnum == InvalidAttrNumber)
8228 20 : ereport(ERROR,
8229 : (errcode(ERRCODE_UNDEFINED_COLUMN),
8230 : errmsg("column \"%s\" of relation \"%s\" does not exist",
8231 : colName, RelationGetRelationName(rel))));
8232 :
8233 : /* Prevent them from altering a system attribute */
8234 363 : if (attnum <= 0)
8235 0 : ereport(ERROR,
8236 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8237 : errmsg("cannot alter system column \"%s\"",
8238 : colName)));
8239 :
8240 363 : if (TupleDescAttr(tupdesc, attnum - 1)->attidentity)
8241 12 : ereport(ERROR,
8242 : (errcode(ERRCODE_SYNTAX_ERROR),
8243 : errmsg("column \"%s\" of relation \"%s\" is an identity column",
8244 : colName, RelationGetRelationName(rel)),
8245 : /* translator: %s is an SQL ALTER command */
8246 : newDefault ? 0 : errhint("Use %s instead.",
8247 : "ALTER TABLE ... ALTER COLUMN ... DROP IDENTITY")));
8248 :
8249 351 : if (TupleDescAttr(tupdesc, attnum - 1)->attgenerated)
8250 8 : ereport(ERROR,
8251 : (errcode(ERRCODE_SYNTAX_ERROR),
8252 : errmsg("column \"%s\" of relation \"%s\" is a generated column",
8253 : colName, RelationGetRelationName(rel)),
8254 : newDefault ?
8255 : /* translator: %s is an SQL ALTER command */
8256 : errhint("Use %s instead.", "ALTER TABLE ... ALTER COLUMN ... SET EXPRESSION") :
8257 : (TupleDescAttr(tupdesc, attnum - 1)->attgenerated == ATTRIBUTE_GENERATED_STORED ?
8258 : errhint("Use %s instead.", "ALTER TABLE ... ALTER COLUMN ... DROP EXPRESSION") : 0)));
8259 :
8260 : /*
8261 : * Remove any old default for the column. We use RESTRICT here for
8262 : * safety, but at present we do not expect anything to depend on the
8263 : * default.
8264 : *
8265 : * We treat removing the existing default as an internal operation when it
8266 : * is preparatory to adding a new default, but as a user-initiated
8267 : * operation when the user asked for a drop.
8268 : */
8269 343 : RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false,
8270 : newDefault != NULL);
8271 :
8272 343 : if (newDefault)
8273 : {
8274 : /* SET DEFAULT */
8275 : RawColumnDefault *rawEnt;
8276 :
8277 227 : rawEnt = palloc_object(RawColumnDefault);
8278 227 : rawEnt->attnum = attnum;
8279 227 : rawEnt->raw_default = newDefault;
8280 227 : rawEnt->generated = '\0';
8281 :
8282 : /*
8283 : * This function is intended for CREATE TABLE, so it processes a
8284 : * _list_ of defaults, but we just do one.
8285 : */
8286 227 : AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
8287 : false, true, false, NULL);
8288 : }
8289 :
8290 339 : ObjectAddressSubSet(address, RelationRelationId,
8291 : RelationGetRelid(rel), attnum);
8292 339 : return address;
8293 : }
8294 :
8295 : /*
8296 : * Add a pre-cooked default expression.
8297 : *
8298 : * Return the address of the affected column.
8299 : */
8300 : static ObjectAddress
8301 53 : ATExecCookedColumnDefault(Relation rel, AttrNumber attnum,
8302 : Node *newDefault)
8303 : {
8304 : ObjectAddress address;
8305 :
8306 : /* We assume no checking is required */
8307 :
8308 : /*
8309 : * Remove any old default for the column. We use RESTRICT here for
8310 : * safety, but at present we do not expect anything to depend on the
8311 : * default. (In ordinary cases, there could not be a default in place
8312 : * anyway, but it's possible when combining LIKE with inheritance.)
8313 : */
8314 53 : RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false,
8315 : true);
8316 :
8317 53 : (void) StoreAttrDefault(rel, attnum, newDefault, true);
8318 :
8319 53 : ObjectAddressSubSet(address, RelationRelationId,
8320 : RelationGetRelid(rel), attnum);
8321 53 : return address;
8322 : }
8323 :
8324 : /*
8325 : * ALTER TABLE ALTER COLUMN ADD IDENTITY
8326 : *
8327 : * Return the address of the affected column.
8328 : */
8329 : static ObjectAddress
8330 103 : ATExecAddIdentity(Relation rel, const char *colName,
8331 : Node *def, LOCKMODE lockmode, bool recurse, bool recursing)
8332 : {
8333 : Relation attrelation;
8334 : HeapTuple tuple;
8335 : Form_pg_attribute attTup;
8336 : AttrNumber attnum;
8337 : ObjectAddress address;
8338 103 : ColumnDef *cdef = castNode(ColumnDef, def);
8339 : bool ispartitioned;
8340 :
8341 103 : ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
8342 103 : if (ispartitioned && !recurse)
8343 4 : ereport(ERROR,
8344 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8345 : errmsg("cannot add identity to a column of only the partitioned table"),
8346 : errhint("Do not specify the ONLY keyword.")));
8347 :
8348 99 : if (rel->rd_rel->relispartition && !recursing)
8349 8 : ereport(ERROR,
8350 : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8351 : errmsg("cannot add identity to a column of a partition"));
8352 :
8353 91 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
8354 :
8355 91 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
8356 91 : if (!HeapTupleIsValid(tuple))
8357 0 : ereport(ERROR,
8358 : (errcode(ERRCODE_UNDEFINED_COLUMN),
8359 : errmsg("column \"%s\" of relation \"%s\" does not exist",
8360 : colName, RelationGetRelationName(rel))));
8361 91 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8362 91 : attnum = attTup->attnum;
8363 :
8364 : /* Can't alter a system attribute */
8365 91 : if (attnum <= 0)
8366 0 : ereport(ERROR,
8367 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8368 : errmsg("cannot alter system column \"%s\"",
8369 : colName)));
8370 :
8371 : /*
8372 : * Creating a column as identity implies NOT NULL, so adding the identity
8373 : * to an existing column that is not NOT NULL would create a state that
8374 : * cannot be reproduced without contortions.
8375 : */
8376 91 : if (!attTup->attnotnull)
8377 4 : ereport(ERROR,
8378 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8379 : errmsg("column \"%s\" of relation \"%s\" must be declared NOT NULL before identity can be added",
8380 : colName, RelationGetRelationName(rel))));
8381 :
8382 : /*
8383 : * On the other hand, if a not-null constraint exists, then verify that
8384 : * it's compatible.
8385 : */
8386 87 : if (attTup->attnotnull)
8387 : {
8388 : HeapTuple contup;
8389 : Form_pg_constraint conForm;
8390 :
8391 87 : contup = findNotNullConstraintAttnum(RelationGetRelid(rel),
8392 : attnum);
8393 87 : if (!HeapTupleIsValid(contup))
8394 0 : elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation \"%s\"",
8395 : colName, RelationGetRelationName(rel));
8396 :
8397 87 : conForm = (Form_pg_constraint) GETSTRUCT(contup);
8398 87 : if (!conForm->convalidated)
8399 4 : ereport(ERROR,
8400 : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8401 : errmsg("incompatible NOT VALID constraint \"%s\" on relation \"%s\"",
8402 : NameStr(conForm->conname), RelationGetRelationName(rel)),
8403 : errhint("You might need to validate it using %s.",
8404 : "ALTER TABLE ... VALIDATE CONSTRAINT"));
8405 : }
8406 :
8407 83 : if (attTup->attidentity)
8408 12 : ereport(ERROR,
8409 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8410 : errmsg("column \"%s\" of relation \"%s\" is already an identity column",
8411 : colName, RelationGetRelationName(rel))));
8412 :
8413 71 : if (attTup->atthasdef)
8414 4 : ereport(ERROR,
8415 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8416 : errmsg("column \"%s\" of relation \"%s\" already has a default value",
8417 : colName, RelationGetRelationName(rel))));
8418 :
8419 67 : attTup->attidentity = cdef->identity;
8420 67 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
8421 :
8422 67 : InvokeObjectPostAlterHook(RelationRelationId,
8423 : RelationGetRelid(rel),
8424 : attTup->attnum);
8425 67 : ObjectAddressSubSet(address, RelationRelationId,
8426 : RelationGetRelid(rel), attnum);
8427 67 : heap_freetuple(tuple);
8428 :
8429 67 : table_close(attrelation, RowExclusiveLock);
8430 :
8431 : /*
8432 : * Recurse to propagate the identity column to partitions. Identity is
8433 : * not inherited in regular inheritance children.
8434 : */
8435 67 : if (recurse && ispartitioned)
8436 : {
8437 : List *children;
8438 : ListCell *lc;
8439 :
8440 6 : children = find_inheritance_children(RelationGetRelid(rel), lockmode);
8441 :
8442 10 : foreach(lc, children)
8443 : {
8444 : Relation childrel;
8445 :
8446 4 : childrel = table_open(lfirst_oid(lc), NoLock);
8447 4 : ATExecAddIdentity(childrel, colName, def, lockmode, recurse, true);
8448 4 : table_close(childrel, NoLock);
8449 : }
8450 : }
8451 :
8452 67 : return address;
8453 : }
8454 :
8455 : /*
8456 : * ALTER TABLE ALTER COLUMN SET { GENERATED or sequence options }
8457 : *
8458 : * Return the address of the affected column.
8459 : */
8460 : static ObjectAddress
8461 49 : ATExecSetIdentity(Relation rel, const char *colName, Node *def,
8462 : LOCKMODE lockmode, bool recurse, bool recursing)
8463 : {
8464 : ListCell *option;
8465 49 : DefElem *generatedEl = NULL;
8466 : HeapTuple tuple;
8467 : Form_pg_attribute attTup;
8468 : AttrNumber attnum;
8469 : Relation attrelation;
8470 : ObjectAddress address;
8471 : bool ispartitioned;
8472 :
8473 49 : ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
8474 49 : if (ispartitioned && !recurse)
8475 4 : ereport(ERROR,
8476 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8477 : errmsg("cannot change identity column of only the partitioned table"),
8478 : errhint("Do not specify the ONLY keyword.")));
8479 :
8480 45 : if (rel->rd_rel->relispartition && !recursing)
8481 8 : ereport(ERROR,
8482 : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8483 : errmsg("cannot change identity column of a partition"));
8484 :
8485 66 : foreach(option, castNode(List, def))
8486 : {
8487 29 : DefElem *defel = lfirst_node(DefElem, option);
8488 :
8489 29 : if (strcmp(defel->defname, "generated") == 0)
8490 : {
8491 29 : if (generatedEl)
8492 0 : ereport(ERROR,
8493 : (errcode(ERRCODE_SYNTAX_ERROR),
8494 : errmsg("conflicting or redundant options")));
8495 29 : generatedEl = defel;
8496 : }
8497 : else
8498 0 : elog(ERROR, "option \"%s\" not recognized",
8499 : defel->defname);
8500 : }
8501 :
8502 : /*
8503 : * Even if there is nothing to change here, we run all the checks. There
8504 : * will be a subsequent ALTER SEQUENCE that relies on everything being
8505 : * there.
8506 : */
8507 :
8508 37 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
8509 37 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
8510 37 : if (!HeapTupleIsValid(tuple))
8511 0 : ereport(ERROR,
8512 : (errcode(ERRCODE_UNDEFINED_COLUMN),
8513 : errmsg("column \"%s\" of relation \"%s\" does not exist",
8514 : colName, RelationGetRelationName(rel))));
8515 :
8516 37 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8517 37 : attnum = attTup->attnum;
8518 :
8519 37 : if (attnum <= 0)
8520 0 : ereport(ERROR,
8521 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8522 : errmsg("cannot alter system column \"%s\"",
8523 : colName)));
8524 :
8525 37 : if (!attTup->attidentity)
8526 4 : ereport(ERROR,
8527 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8528 : errmsg("column \"%s\" of relation \"%s\" is not an identity column",
8529 : colName, RelationGetRelationName(rel))));
8530 :
8531 33 : if (generatedEl)
8532 : {
8533 29 : attTup->attidentity = defGetInt32(generatedEl);
8534 29 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
8535 :
8536 29 : InvokeObjectPostAlterHook(RelationRelationId,
8537 : RelationGetRelid(rel),
8538 : attTup->attnum);
8539 29 : ObjectAddressSubSet(address, RelationRelationId,
8540 : RelationGetRelid(rel), attnum);
8541 : }
8542 : else
8543 4 : address = InvalidObjectAddress;
8544 :
8545 33 : heap_freetuple(tuple);
8546 33 : table_close(attrelation, RowExclusiveLock);
8547 :
8548 : /*
8549 : * Recurse to propagate the identity change to partitions. Identity is not
8550 : * inherited in regular inheritance children.
8551 : */
8552 33 : if (generatedEl && recurse && ispartitioned)
8553 : {
8554 : List *children;
8555 : ListCell *lc;
8556 :
8557 4 : children = find_inheritance_children(RelationGetRelid(rel), lockmode);
8558 :
8559 12 : foreach(lc, children)
8560 : {
8561 : Relation childrel;
8562 :
8563 8 : childrel = table_open(lfirst_oid(lc), NoLock);
8564 8 : ATExecSetIdentity(childrel, colName, def, lockmode, recurse, true);
8565 8 : table_close(childrel, NoLock);
8566 : }
8567 : }
8568 :
8569 33 : return address;
8570 : }
8571 :
8572 : /*
8573 : * ALTER TABLE ALTER COLUMN DROP IDENTITY
8574 : *
8575 : * Return the address of the affected column.
8576 : */
8577 : static ObjectAddress
8578 61 : ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode,
8579 : bool recurse, bool recursing)
8580 : {
8581 : HeapTuple tuple;
8582 : Form_pg_attribute attTup;
8583 : AttrNumber attnum;
8584 : Relation attrelation;
8585 : ObjectAddress address;
8586 : Oid seqid;
8587 : ObjectAddress seqaddress;
8588 : bool ispartitioned;
8589 :
8590 61 : ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
8591 61 : if (ispartitioned && !recurse)
8592 4 : ereport(ERROR,
8593 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8594 : errmsg("cannot drop identity from a column of only the partitioned table"),
8595 : errhint("Do not specify the ONLY keyword.")));
8596 :
8597 57 : if (rel->rd_rel->relispartition && !recursing)
8598 4 : ereport(ERROR,
8599 : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8600 : errmsg("cannot drop identity from a column of a partition"));
8601 :
8602 53 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
8603 53 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
8604 53 : if (!HeapTupleIsValid(tuple))
8605 0 : ereport(ERROR,
8606 : (errcode(ERRCODE_UNDEFINED_COLUMN),
8607 : errmsg("column \"%s\" of relation \"%s\" does not exist",
8608 : colName, RelationGetRelationName(rel))));
8609 :
8610 53 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8611 53 : attnum = attTup->attnum;
8612 :
8613 53 : if (attnum <= 0)
8614 0 : ereport(ERROR,
8615 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8616 : errmsg("cannot alter system column \"%s\"",
8617 : colName)));
8618 :
8619 53 : if (!attTup->attidentity)
8620 : {
8621 8 : if (!missing_ok)
8622 4 : ereport(ERROR,
8623 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8624 : errmsg("column \"%s\" of relation \"%s\" is not an identity column",
8625 : colName, RelationGetRelationName(rel))));
8626 : else
8627 : {
8628 4 : ereport(NOTICE,
8629 : (errmsg("column \"%s\" of relation \"%s\" is not an identity column, skipping",
8630 : colName, RelationGetRelationName(rel))));
8631 4 : heap_freetuple(tuple);
8632 4 : table_close(attrelation, RowExclusiveLock);
8633 4 : return InvalidObjectAddress;
8634 : }
8635 : }
8636 :
8637 45 : attTup->attidentity = '\0';
8638 45 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
8639 :
8640 45 : InvokeObjectPostAlterHook(RelationRelationId,
8641 : RelationGetRelid(rel),
8642 : attTup->attnum);
8643 45 : ObjectAddressSubSet(address, RelationRelationId,
8644 : RelationGetRelid(rel), attnum);
8645 45 : heap_freetuple(tuple);
8646 :
8647 45 : table_close(attrelation, RowExclusiveLock);
8648 :
8649 : /*
8650 : * Recurse to drop the identity from column in partitions. Identity is
8651 : * not inherited in regular inheritance children so ignore them.
8652 : */
8653 45 : if (recurse && ispartitioned)
8654 : {
8655 : List *children;
8656 : ListCell *lc;
8657 :
8658 4 : children = find_inheritance_children(RelationGetRelid(rel), lockmode);
8659 :
8660 8 : foreach(lc, children)
8661 : {
8662 : Relation childrel;
8663 :
8664 4 : childrel = table_open(lfirst_oid(lc), NoLock);
8665 4 : ATExecDropIdentity(childrel, colName, false, lockmode, recurse, true);
8666 4 : table_close(childrel, NoLock);
8667 : }
8668 : }
8669 :
8670 45 : if (!recursing)
8671 : {
8672 : /* drop the internal sequence */
8673 21 : seqid = getIdentitySequence(rel, attnum, false);
8674 21 : deleteDependencyRecordsForClass(RelationRelationId, seqid,
8675 : RelationRelationId, DEPENDENCY_INTERNAL);
8676 21 : CommandCounterIncrement();
8677 21 : seqaddress.classId = RelationRelationId;
8678 21 : seqaddress.objectId = seqid;
8679 21 : seqaddress.objectSubId = 0;
8680 21 : performDeletion(&seqaddress, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
8681 : }
8682 :
8683 45 : return address;
8684 : }
8685 :
8686 : /*
8687 : * ALTER TABLE ALTER COLUMN SET EXPRESSION
8688 : *
8689 : * Return the address of the affected column.
8690 : */
8691 : static ObjectAddress
8692 169 : ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
8693 : Node *newExpr, LOCKMODE lockmode)
8694 : {
8695 : HeapTuple tuple;
8696 : Form_pg_attribute attTup;
8697 : AttrNumber attnum;
8698 : char attgenerated;
8699 : bool rewrite;
8700 : Oid attrdefoid;
8701 : ObjectAddress address;
8702 : Expr *defval;
8703 : NewColumnValue *newval;
8704 : RawColumnDefault *rawEnt;
8705 :
8706 169 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
8707 169 : if (!HeapTupleIsValid(tuple))
8708 0 : ereport(ERROR,
8709 : (errcode(ERRCODE_UNDEFINED_COLUMN),
8710 : errmsg("column \"%s\" of relation \"%s\" does not exist",
8711 : colName, RelationGetRelationName(rel))));
8712 :
8713 169 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8714 :
8715 169 : attnum = attTup->attnum;
8716 169 : if (attnum <= 0)
8717 0 : ereport(ERROR,
8718 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8719 : errmsg("cannot alter system column \"%s\"",
8720 : colName)));
8721 :
8722 169 : attgenerated = attTup->attgenerated;
8723 169 : if (!attgenerated)
8724 8 : ereport(ERROR,
8725 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8726 : errmsg("column \"%s\" of relation \"%s\" is not a generated column",
8727 : colName, RelationGetRelationName(rel))));
8728 :
8729 161 : if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL && attTup->attnotnull)
8730 16 : tab->verify_new_notnull = true;
8731 :
8732 : /*
8733 : * We need to prevent this because a change of expression could affect a
8734 : * row filter and inject expressions that are not permitted in a row
8735 : * filter. XXX We could try to have a more precise check to catch only
8736 : * publications with row filters, or even re-verify the row filter
8737 : * expressions.
8738 : */
8739 237 : if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL &&
8740 76 : GetRelationIncludedPublications(RelationGetRelid(rel)) != NIL)
8741 4 : ereport(ERROR,
8742 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8743 : errmsg("ALTER TABLE / SET EXPRESSION is not supported for virtual generated columns in tables that are part of a publication"),
8744 : errdetail("Column \"%s\" of relation \"%s\" is a virtual generated column.",
8745 : colName, RelationGetRelationName(rel))));
8746 :
8747 157 : rewrite = (attgenerated == ATTRIBUTE_GENERATED_STORED);
8748 :
8749 157 : ReleaseSysCache(tuple);
8750 :
8751 157 : if (rewrite)
8752 : {
8753 : /*
8754 : * Clear all the missing values if we're rewriting the table, since
8755 : * this renders them pointless.
8756 : */
8757 85 : RelationClearMissing(rel);
8758 :
8759 : /* make sure we don't conflict with later attribute modifications */
8760 85 : CommandCounterIncrement();
8761 : }
8762 :
8763 : /*
8764 : * Find everything that depends on the column (constraints, indexes, etc),
8765 : * and record enough information to let us recreate the objects.
8766 : */
8767 157 : RememberAllDependentForRebuilding(tab, AT_SetExpression, rel, attnum, colName);
8768 :
8769 : /*
8770 : * Drop the dependency records of the GENERATED expression, in particular
8771 : * its INTERNAL dependency on the column, which would otherwise cause
8772 : * dependency.c to refuse to perform the deletion.
8773 : */
8774 157 : attrdefoid = GetAttrDefaultOid(RelationGetRelid(rel), attnum);
8775 157 : if (!OidIsValid(attrdefoid))
8776 0 : elog(ERROR, "could not find attrdef tuple for relation %u attnum %d",
8777 : RelationGetRelid(rel), attnum);
8778 157 : (void) deleteDependencyRecordsFor(AttrDefaultRelationId, attrdefoid, false);
8779 :
8780 : /* Make above changes visible */
8781 157 : CommandCounterIncrement();
8782 :
8783 : /*
8784 : * Get rid of the GENERATED expression itself. We use RESTRICT here for
8785 : * safety, but at present we do not expect anything to depend on the
8786 : * expression.
8787 : */
8788 157 : RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT,
8789 : false, false);
8790 :
8791 : /* Prepare to store the new expression, in the catalogs */
8792 157 : rawEnt = palloc_object(RawColumnDefault);
8793 157 : rawEnt->attnum = attnum;
8794 157 : rawEnt->raw_default = newExpr;
8795 157 : rawEnt->generated = attgenerated;
8796 :
8797 : /* Store the generated expression */
8798 157 : AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
8799 : false, true, false, NULL);
8800 :
8801 : /* Make above new expression visible */
8802 157 : CommandCounterIncrement();
8803 :
8804 157 : if (rewrite)
8805 : {
8806 : /* Prepare for table rewrite */
8807 85 : defval = (Expr *) build_column_default(rel, attnum);
8808 :
8809 85 : newval = palloc0_object(NewColumnValue);
8810 85 : newval->attnum = attnum;
8811 85 : newval->expr = expression_planner(defval);
8812 85 : newval->is_generated = true;
8813 :
8814 85 : tab->newvals = lappend(tab->newvals, newval);
8815 85 : tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
8816 : }
8817 :
8818 : /* Drop any pg_statistic entry for the column */
8819 157 : RemoveStatistics(RelationGetRelid(rel), attnum);
8820 :
8821 157 : InvokeObjectPostAlterHook(RelationRelationId,
8822 : RelationGetRelid(rel), attnum);
8823 :
8824 157 : ObjectAddressSubSet(address, RelationRelationId,
8825 : RelationGetRelid(rel), attnum);
8826 157 : return address;
8827 : }
8828 :
8829 : /*
8830 : * ALTER TABLE ALTER COLUMN DROP EXPRESSION
8831 : */
8832 : static void
8833 57 : ATPrepDropExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, LOCKMODE lockmode)
8834 : {
8835 : /*
8836 : * Reject ONLY if there are child tables. We could implement this, but it
8837 : * is a bit complicated. GENERATED clauses must be attached to the column
8838 : * definition and cannot be added later like DEFAULT, so if a child table
8839 : * has a generation expression that the parent does not have, the child
8840 : * column will necessarily be an attislocal column. So to implement ONLY
8841 : * here, we'd need extra code to update attislocal of the direct child
8842 : * tables, somewhat similar to how DROP COLUMN does it, so that the
8843 : * resulting state can be properly dumped and restored.
8844 : */
8845 73 : if (!recurse &&
8846 16 : find_inheritance_children(RelationGetRelid(rel), lockmode))
8847 8 : ereport(ERROR,
8848 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8849 : errmsg("ALTER TABLE / DROP EXPRESSION must be applied to child tables too")));
8850 :
8851 : /*
8852 : * Cannot drop generation expression from inherited columns.
8853 : */
8854 49 : if (!recursing)
8855 : {
8856 : HeapTuple tuple;
8857 : Form_pg_attribute attTup;
8858 :
8859 41 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), cmd->name);
8860 41 : if (!HeapTupleIsValid(tuple))
8861 0 : ereport(ERROR,
8862 : (errcode(ERRCODE_UNDEFINED_COLUMN),
8863 : errmsg("column \"%s\" of relation \"%s\" does not exist",
8864 : cmd->name, RelationGetRelationName(rel))));
8865 :
8866 41 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8867 :
8868 41 : if (attTup->attinhcount > 0)
8869 8 : ereport(ERROR,
8870 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8871 : errmsg("cannot drop generation expression from inherited column")));
8872 : }
8873 41 : }
8874 :
8875 : /*
8876 : * Return the address of the affected column.
8877 : */
8878 : static ObjectAddress
8879 37 : ATExecDropExpression(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode)
8880 : {
8881 : HeapTuple tuple;
8882 : Form_pg_attribute attTup;
8883 : AttrNumber attnum;
8884 : Relation attrelation;
8885 : Oid attrdefoid;
8886 : ObjectAddress address;
8887 :
8888 37 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
8889 37 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
8890 37 : if (!HeapTupleIsValid(tuple))
8891 0 : ereport(ERROR,
8892 : (errcode(ERRCODE_UNDEFINED_COLUMN),
8893 : errmsg("column \"%s\" of relation \"%s\" does not exist",
8894 : colName, RelationGetRelationName(rel))));
8895 :
8896 37 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8897 37 : attnum = attTup->attnum;
8898 :
8899 37 : if (attnum <= 0)
8900 0 : ereport(ERROR,
8901 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8902 : errmsg("cannot alter system column \"%s\"",
8903 : colName)));
8904 :
8905 : /*
8906 : * TODO: This could be done, but it would need a table rewrite to
8907 : * materialize the generated values. Note that for the time being, we
8908 : * still error with missing_ok, so that we don't silently leave the column
8909 : * as generated.
8910 : */
8911 37 : if (attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
8912 8 : ereport(ERROR,
8913 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8914 : errmsg("ALTER TABLE / DROP EXPRESSION is not supported for virtual generated columns"),
8915 : errdetail("Column \"%s\" of relation \"%s\" is a virtual generated column.",
8916 : colName, RelationGetRelationName(rel))));
8917 :
8918 29 : if (!attTup->attgenerated)
8919 : {
8920 16 : if (!missing_ok)
8921 8 : ereport(ERROR,
8922 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8923 : errmsg("column \"%s\" of relation \"%s\" is not a generated column",
8924 : colName, RelationGetRelationName(rel))));
8925 : else
8926 : {
8927 8 : ereport(NOTICE,
8928 : (errmsg("column \"%s\" of relation \"%s\" is not a generated column, skipping",
8929 : colName, RelationGetRelationName(rel))));
8930 8 : heap_freetuple(tuple);
8931 8 : table_close(attrelation, RowExclusiveLock);
8932 8 : return InvalidObjectAddress;
8933 : }
8934 : }
8935 :
8936 : /*
8937 : * Mark the column as no longer generated. (The atthasdef flag needs to
8938 : * get cleared too, but RemoveAttrDefault will handle that.)
8939 : */
8940 13 : attTup->attgenerated = '\0';
8941 13 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
8942 :
8943 13 : InvokeObjectPostAlterHook(RelationRelationId,
8944 : RelationGetRelid(rel),
8945 : attnum);
8946 13 : heap_freetuple(tuple);
8947 :
8948 13 : table_close(attrelation, RowExclusiveLock);
8949 :
8950 : /*
8951 : * Drop the dependency records of the GENERATED expression, in particular
8952 : * its INTERNAL dependency on the column, which would otherwise cause
8953 : * dependency.c to refuse to perform the deletion.
8954 : */
8955 13 : attrdefoid = GetAttrDefaultOid(RelationGetRelid(rel), attnum);
8956 13 : if (!OidIsValid(attrdefoid))
8957 0 : elog(ERROR, "could not find attrdef tuple for relation %u attnum %d",
8958 : RelationGetRelid(rel), attnum);
8959 13 : (void) deleteDependencyRecordsFor(AttrDefaultRelationId, attrdefoid, false);
8960 :
8961 : /* Make above changes visible */
8962 13 : CommandCounterIncrement();
8963 :
8964 : /*
8965 : * Get rid of the GENERATED expression itself. We use RESTRICT here for
8966 : * safety, but at present we do not expect anything to depend on the
8967 : * default.
8968 : */
8969 13 : RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT,
8970 : false, false);
8971 :
8972 13 : ObjectAddressSubSet(address, RelationRelationId,
8973 : RelationGetRelid(rel), attnum);
8974 13 : return address;
8975 : }
8976 :
8977 : /*
8978 : * ALTER TABLE ALTER COLUMN SET STATISTICS
8979 : *
8980 : * Return value is the address of the modified column
8981 : */
8982 : static ObjectAddress
8983 111 : ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newValue, LOCKMODE lockmode)
8984 : {
8985 111 : int newtarget = 0;
8986 : bool newtarget_default;
8987 : Relation attrelation;
8988 : HeapTuple tuple,
8989 : newtuple;
8990 : Form_pg_attribute attrtuple;
8991 : AttrNumber attnum;
8992 : ObjectAddress address;
8993 : Datum repl_val[Natts_pg_attribute];
8994 : bool repl_null[Natts_pg_attribute];
8995 : bool repl_repl[Natts_pg_attribute];
8996 :
8997 : /*
8998 : * We allow referencing columns by numbers only for indexes, since table
8999 : * column numbers could contain gaps if columns are later dropped.
9000 : */
9001 111 : if (rel->rd_rel->relkind != RELKIND_INDEX &&
9002 69 : rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
9003 : !colName)
9004 0 : ereport(ERROR,
9005 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9006 : errmsg("cannot refer to non-index column by number")));
9007 :
9008 : /* -1 was used in previous versions for the default setting */
9009 111 : if (newValue && intVal(newValue) != -1)
9010 : {
9011 80 : newtarget = intVal(newValue);
9012 80 : newtarget_default = false;
9013 : }
9014 : else
9015 31 : newtarget_default = true;
9016 :
9017 111 : if (!newtarget_default)
9018 : {
9019 : /*
9020 : * Limit target to a sane range
9021 : */
9022 80 : if (newtarget < 0)
9023 : {
9024 0 : ereport(ERROR,
9025 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
9026 : errmsg("statistics target %d is too low",
9027 : newtarget)));
9028 : }
9029 80 : else if (newtarget > MAX_STATISTICS_TARGET)
9030 : {
9031 0 : newtarget = MAX_STATISTICS_TARGET;
9032 0 : ereport(WARNING,
9033 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
9034 : errmsg("lowering statistics target to %d",
9035 : newtarget)));
9036 : }
9037 : }
9038 :
9039 111 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
9040 :
9041 111 : if (colName)
9042 : {
9043 69 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
9044 :
9045 69 : if (!HeapTupleIsValid(tuple))
9046 8 : ereport(ERROR,
9047 : (errcode(ERRCODE_UNDEFINED_COLUMN),
9048 : errmsg("column \"%s\" of relation \"%s\" does not exist",
9049 : colName, RelationGetRelationName(rel))));
9050 : }
9051 : else
9052 : {
9053 42 : tuple = SearchSysCacheAttNum(RelationGetRelid(rel), colNum);
9054 :
9055 42 : if (!HeapTupleIsValid(tuple))
9056 8 : ereport(ERROR,
9057 : (errcode(ERRCODE_UNDEFINED_COLUMN),
9058 : errmsg("column number %d of relation \"%s\" does not exist",
9059 : colNum, RelationGetRelationName(rel))));
9060 : }
9061 :
9062 95 : attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
9063 :
9064 95 : attnum = attrtuple->attnum;
9065 95 : if (attnum <= 0)
9066 0 : ereport(ERROR,
9067 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9068 : errmsg("cannot alter system column \"%s\"",
9069 : colName)));
9070 :
9071 : /*
9072 : * Prevent this as long as the ANALYZE code skips virtual generated
9073 : * columns.
9074 : */
9075 95 : if (attrtuple->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
9076 0 : ereport(ERROR,
9077 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9078 : errmsg("cannot alter statistics on virtual generated column \"%s\"",
9079 : colName)));
9080 :
9081 95 : if (rel->rd_rel->relkind == RELKIND_INDEX ||
9082 61 : rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
9083 : {
9084 34 : if (attnum > rel->rd_index->indnkeyatts)
9085 4 : ereport(ERROR,
9086 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9087 : errmsg("cannot alter statistics on included column \"%s\" of index \"%s\"",
9088 : NameStr(attrtuple->attname), RelationGetRelationName(rel))));
9089 30 : else if (rel->rd_index->indkey.values[attnum - 1] != 0)
9090 12 : ereport(ERROR,
9091 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9092 : errmsg("cannot alter statistics on non-expression column \"%s\" of index \"%s\"",
9093 : NameStr(attrtuple->attname), RelationGetRelationName(rel)),
9094 : errhint("Alter statistics on table column instead.")));
9095 : }
9096 :
9097 : /* Build new tuple. */
9098 79 : memset(repl_null, false, sizeof(repl_null));
9099 79 : memset(repl_repl, false, sizeof(repl_repl));
9100 79 : if (!newtarget_default)
9101 48 : repl_val[Anum_pg_attribute_attstattarget - 1] = Int16GetDatum(newtarget);
9102 : else
9103 31 : repl_null[Anum_pg_attribute_attstattarget - 1] = true;
9104 79 : repl_repl[Anum_pg_attribute_attstattarget - 1] = true;
9105 79 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(attrelation),
9106 : repl_val, repl_null, repl_repl);
9107 79 : CatalogTupleUpdate(attrelation, &tuple->t_self, newtuple);
9108 :
9109 79 : InvokeObjectPostAlterHook(RelationRelationId,
9110 : RelationGetRelid(rel),
9111 : attrtuple->attnum);
9112 79 : ObjectAddressSubSet(address, RelationRelationId,
9113 : RelationGetRelid(rel), attnum);
9114 :
9115 79 : heap_freetuple(newtuple);
9116 :
9117 79 : ReleaseSysCache(tuple);
9118 :
9119 79 : table_close(attrelation, RowExclusiveLock);
9120 :
9121 79 : return address;
9122 : }
9123 :
9124 : /*
9125 : * Return value is the address of the modified column
9126 : */
9127 : static ObjectAddress
9128 21 : ATExecSetOptions(Relation rel, const char *colName, Node *options,
9129 : bool isReset, LOCKMODE lockmode)
9130 : {
9131 : Relation attrelation;
9132 : HeapTuple tuple,
9133 : newtuple;
9134 : Form_pg_attribute attrtuple;
9135 : AttrNumber attnum;
9136 : Datum datum,
9137 : newOptions;
9138 : bool isnull;
9139 : ObjectAddress address;
9140 : Datum repl_val[Natts_pg_attribute];
9141 : bool repl_null[Natts_pg_attribute];
9142 : bool repl_repl[Natts_pg_attribute];
9143 :
9144 21 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
9145 :
9146 21 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
9147 :
9148 21 : if (!HeapTupleIsValid(tuple))
9149 0 : ereport(ERROR,
9150 : (errcode(ERRCODE_UNDEFINED_COLUMN),
9151 : errmsg("column \"%s\" of relation \"%s\" does not exist",
9152 : colName, RelationGetRelationName(rel))));
9153 21 : attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
9154 :
9155 21 : attnum = attrtuple->attnum;
9156 21 : if (attnum <= 0)
9157 0 : ereport(ERROR,
9158 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9159 : errmsg("cannot alter system column \"%s\"",
9160 : colName)));
9161 :
9162 : /* Generate new proposed attoptions (text array) */
9163 21 : datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions,
9164 : &isnull);
9165 21 : newOptions = transformRelOptions(isnull ? (Datum) 0 : datum,
9166 : castNode(List, options), NULL, NULL,
9167 : false, isReset);
9168 : /* Validate new options */
9169 21 : (void) attribute_reloptions(newOptions, true);
9170 :
9171 : /* Build new tuple. */
9172 21 : memset(repl_null, false, sizeof(repl_null));
9173 21 : memset(repl_repl, false, sizeof(repl_repl));
9174 21 : if (newOptions != (Datum) 0)
9175 21 : repl_val[Anum_pg_attribute_attoptions - 1] = newOptions;
9176 : else
9177 0 : repl_null[Anum_pg_attribute_attoptions - 1] = true;
9178 21 : repl_repl[Anum_pg_attribute_attoptions - 1] = true;
9179 21 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(attrelation),
9180 : repl_val, repl_null, repl_repl);
9181 :
9182 : /* Update system catalog. */
9183 21 : CatalogTupleUpdate(attrelation, &newtuple->t_self, newtuple);
9184 :
9185 21 : InvokeObjectPostAlterHook(RelationRelationId,
9186 : RelationGetRelid(rel),
9187 : attrtuple->attnum);
9188 21 : ObjectAddressSubSet(address, RelationRelationId,
9189 : RelationGetRelid(rel), attnum);
9190 :
9191 21 : heap_freetuple(newtuple);
9192 :
9193 21 : ReleaseSysCache(tuple);
9194 :
9195 21 : table_close(attrelation, RowExclusiveLock);
9196 :
9197 21 : return address;
9198 : }
9199 :
9200 : /*
9201 : * Helper function for ATExecSetStorage and ATExecSetCompression
9202 : *
9203 : * Set the attstorage and/or attcompression fields for index columns
9204 : * associated with the specified table column.
9205 : */
9206 : static void
9207 217 : SetIndexStorageProperties(Relation rel, Relation attrelation,
9208 : AttrNumber attnum,
9209 : bool setstorage, char newstorage,
9210 : bool setcompression, char newcompression,
9211 : LOCKMODE lockmode)
9212 : {
9213 : ListCell *lc;
9214 :
9215 275 : foreach(lc, RelationGetIndexList(rel))
9216 : {
9217 58 : Oid indexoid = lfirst_oid(lc);
9218 : Relation indrel;
9219 58 : AttrNumber indattnum = 0;
9220 : HeapTuple tuple;
9221 :
9222 58 : indrel = index_open(indexoid, lockmode);
9223 :
9224 97 : for (int i = 0; i < indrel->rd_index->indnatts; i++)
9225 : {
9226 62 : if (indrel->rd_index->indkey.values[i] == attnum)
9227 : {
9228 23 : indattnum = i + 1;
9229 23 : break;
9230 : }
9231 : }
9232 :
9233 58 : if (indattnum == 0)
9234 : {
9235 35 : index_close(indrel, lockmode);
9236 35 : continue;
9237 : }
9238 :
9239 23 : tuple = SearchSysCacheCopyAttNum(RelationGetRelid(indrel), indattnum);
9240 :
9241 23 : if (HeapTupleIsValid(tuple))
9242 : {
9243 23 : Form_pg_attribute attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
9244 :
9245 23 : if (setstorage)
9246 15 : attrtuple->attstorage = newstorage;
9247 :
9248 23 : if (setcompression)
9249 8 : attrtuple->attcompression = newcompression;
9250 :
9251 23 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
9252 :
9253 23 : InvokeObjectPostAlterHook(RelationRelationId,
9254 : RelationGetRelid(rel),
9255 : attrtuple->attnum);
9256 :
9257 23 : heap_freetuple(tuple);
9258 : }
9259 :
9260 23 : index_close(indrel, lockmode);
9261 : }
9262 217 : }
9263 :
9264 : /*
9265 : * ALTER TABLE ALTER COLUMN SET STORAGE
9266 : *
9267 : * Return value is the address of the modified column
9268 : */
9269 : static ObjectAddress
9270 181 : ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode)
9271 : {
9272 : Relation attrelation;
9273 : HeapTuple tuple;
9274 : Form_pg_attribute attrtuple;
9275 : AttrNumber attnum;
9276 : ObjectAddress address;
9277 :
9278 181 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
9279 :
9280 181 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
9281 :
9282 181 : if (!HeapTupleIsValid(tuple))
9283 8 : ereport(ERROR,
9284 : (errcode(ERRCODE_UNDEFINED_COLUMN),
9285 : errmsg("column \"%s\" of relation \"%s\" does not exist",
9286 : colName, RelationGetRelationName(rel))));
9287 173 : attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
9288 :
9289 173 : attnum = attrtuple->attnum;
9290 173 : if (attnum <= 0)
9291 0 : ereport(ERROR,
9292 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9293 : errmsg("cannot alter system column \"%s\"",
9294 : colName)));
9295 :
9296 173 : attrtuple->attstorage = GetAttributeStorage(attrtuple->atttypid, strVal(newValue));
9297 :
9298 173 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
9299 :
9300 173 : InvokeObjectPostAlterHook(RelationRelationId,
9301 : RelationGetRelid(rel),
9302 : attrtuple->attnum);
9303 :
9304 : /*
9305 : * Apply the change to indexes as well (only for simple index columns,
9306 : * matching behavior of index.c ConstructTupleDescriptor()).
9307 : */
9308 173 : SetIndexStorageProperties(rel, attrelation, attnum,
9309 173 : true, attrtuple->attstorage,
9310 : false, 0,
9311 : lockmode);
9312 :
9313 173 : heap_freetuple(tuple);
9314 :
9315 173 : table_close(attrelation, RowExclusiveLock);
9316 :
9317 173 : ObjectAddressSubSet(address, RelationRelationId,
9318 : RelationGetRelid(rel), attnum);
9319 173 : return address;
9320 : }
9321 :
9322 :
9323 : /*
9324 : * ALTER TABLE DROP COLUMN
9325 : *
9326 : * DROP COLUMN cannot use the normal ALTER TABLE recursion mechanism,
9327 : * because we have to decide at runtime whether to recurse or not depending
9328 : * on whether attinhcount goes to zero or not. (We can't check this in a
9329 : * static pre-pass because it won't handle multiple inheritance situations
9330 : * correctly.)
9331 : */
9332 : static void
9333 1143 : ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
9334 : AlterTableCmd *cmd, LOCKMODE lockmode,
9335 : AlterTableUtilityContext *context)
9336 : {
9337 1143 : if (rel->rd_rel->reloftype && !recursing)
9338 4 : ereport(ERROR,
9339 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
9340 : errmsg("cannot drop column from typed table")));
9341 :
9342 1139 : if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
9343 54 : ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);
9344 :
9345 1135 : if (recurse)
9346 977 : cmd->recurse = true;
9347 1135 : }
9348 :
9349 : /*
9350 : * Drops column 'colName' from relation 'rel' and returns the address of the
9351 : * dropped column. The column is also dropped (or marked as no longer
9352 : * inherited from relation) from the relation's inheritance children, if any.
9353 : *
9354 : * In the recursive invocations for inheritance child relations, instead of
9355 : * dropping the column directly (if to be dropped at all), its object address
9356 : * is added to 'addrs', which must be non-NULL in such invocations. All
9357 : * columns are dropped at the same time after all the children have been
9358 : * checked recursively.
9359 : */
9360 : static ObjectAddress
9361 1508 : ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
9362 : DropBehavior behavior,
9363 : bool recurse, bool recursing,
9364 : bool missing_ok, LOCKMODE lockmode,
9365 : ObjectAddresses *addrs)
9366 : {
9367 : HeapTuple tuple;
9368 : Form_pg_attribute targetatt;
9369 : AttrNumber attnum;
9370 : List *children;
9371 : ObjectAddress object;
9372 : bool is_expr;
9373 :
9374 : /* At top level, permission check was done in ATPrepCmd, else do it */
9375 1508 : if (recursing)
9376 373 : ATSimplePermissions(AT_DropColumn, rel,
9377 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
9378 :
9379 : /* Initialize addrs on the first invocation */
9380 : Assert(!recursing || addrs != NULL);
9381 :
9382 : /* since this function recurses, it could be driven to stack overflow */
9383 1508 : check_stack_depth();
9384 :
9385 1508 : if (!recursing)
9386 1135 : addrs = new_object_addresses();
9387 :
9388 : /*
9389 : * get the number of the attribute
9390 : */
9391 1508 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
9392 1508 : if (!HeapTupleIsValid(tuple))
9393 : {
9394 40 : if (!missing_ok)
9395 : {
9396 24 : ereport(ERROR,
9397 : (errcode(ERRCODE_UNDEFINED_COLUMN),
9398 : errmsg("column \"%s\" of relation \"%s\" does not exist",
9399 : colName, RelationGetRelationName(rel))));
9400 : }
9401 : else
9402 : {
9403 16 : ereport(NOTICE,
9404 : (errmsg("column \"%s\" of relation \"%s\" does not exist, skipping",
9405 : colName, RelationGetRelationName(rel))));
9406 16 : return InvalidObjectAddress;
9407 : }
9408 : }
9409 1468 : targetatt = (Form_pg_attribute) GETSTRUCT(tuple);
9410 :
9411 1468 : attnum = targetatt->attnum;
9412 :
9413 : /* Can't drop a system attribute */
9414 1468 : if (attnum <= 0)
9415 4 : ereport(ERROR,
9416 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9417 : errmsg("cannot drop system column \"%s\"",
9418 : colName)));
9419 :
9420 : /*
9421 : * Don't drop inherited columns, unless recursing (presumably from a drop
9422 : * of the parent column)
9423 : */
9424 1464 : if (targetatt->attinhcount > 0 && !recursing)
9425 32 : ereport(ERROR,
9426 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
9427 : errmsg("cannot drop inherited column \"%s\"",
9428 : colName)));
9429 :
9430 : /*
9431 : * Don't drop columns used in the partition key, either. (If we let this
9432 : * go through, the key column's dependencies would cause a cascaded drop
9433 : * of the whole table, which is surely not what the user expected.)
9434 : */
9435 1432 : if (has_partition_attrs(rel,
9436 : bms_make_singleton(attnum - FirstLowInvalidHeapAttributeNumber),
9437 : &is_expr))
9438 20 : ereport(ERROR,
9439 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
9440 : errmsg("cannot drop column \"%s\" because it is part of the partition key of relation \"%s\"",
9441 : colName, RelationGetRelationName(rel))));
9442 :
9443 1412 : ReleaseSysCache(tuple);
9444 :
9445 : /*
9446 : * Propagate to children as appropriate. Unlike most other ALTER
9447 : * routines, we have to do this one level of recursion at a time; we can't
9448 : * use find_all_inheritors to do it in one pass.
9449 : */
9450 : children =
9451 1412 : find_inheritance_children(RelationGetRelid(rel), lockmode);
9452 :
9453 1412 : if (children)
9454 : {
9455 : Relation attr_rel;
9456 : ListCell *child;
9457 :
9458 : /*
9459 : * In case of a partitioned table, the column must be dropped from the
9460 : * partitions as well.
9461 : */
9462 204 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
9463 4 : ereport(ERROR,
9464 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
9465 : errmsg("cannot drop column from only the partitioned table when partitions exist"),
9466 : errhint("Do not specify the ONLY keyword.")));
9467 :
9468 200 : attr_rel = table_open(AttributeRelationId, RowExclusiveLock);
9469 593 : foreach(child, children)
9470 : {
9471 397 : Oid childrelid = lfirst_oid(child);
9472 : Relation childrel;
9473 : Form_pg_attribute childatt;
9474 :
9475 : /* find_inheritance_children already got lock */
9476 397 : childrel = table_open(childrelid, NoLock);
9477 397 : CheckAlterTableIsSafe(childrel);
9478 :
9479 397 : tuple = SearchSysCacheCopyAttName(childrelid, colName);
9480 397 : if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
9481 0 : elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
9482 : colName, childrelid);
9483 397 : childatt = (Form_pg_attribute) GETSTRUCT(tuple);
9484 :
9485 397 : if (childatt->attinhcount <= 0) /* shouldn't happen */
9486 0 : elog(ERROR, "relation %u has non-inherited attribute \"%s\"",
9487 : childrelid, colName);
9488 :
9489 397 : if (recurse)
9490 : {
9491 : /*
9492 : * If the child column has other definition sources, just
9493 : * decrement its inheritance count; if not, recurse to delete
9494 : * it.
9495 : */
9496 381 : if (childatt->attinhcount == 1 && !childatt->attislocal)
9497 : {
9498 : /* Time to delete this child column, too */
9499 373 : ATExecDropColumn(wqueue, childrel, colName,
9500 : behavior, true, true,
9501 : false, lockmode, addrs);
9502 : }
9503 : else
9504 : {
9505 : /* Child column must survive my deletion */
9506 8 : childatt->attinhcount--;
9507 :
9508 8 : CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple);
9509 :
9510 : /* Make update visible */
9511 8 : CommandCounterIncrement();
9512 : }
9513 : }
9514 : else
9515 : {
9516 : /*
9517 : * If we were told to drop ONLY in this table (no recursion),
9518 : * we need to mark the inheritors' attributes as locally
9519 : * defined rather than inherited.
9520 : */
9521 16 : childatt->attinhcount--;
9522 16 : childatt->attislocal = true;
9523 :
9524 16 : CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple);
9525 :
9526 : /* Make update visible */
9527 16 : CommandCounterIncrement();
9528 : }
9529 :
9530 393 : heap_freetuple(tuple);
9531 :
9532 393 : table_close(childrel, NoLock);
9533 : }
9534 196 : table_close(attr_rel, RowExclusiveLock);
9535 : }
9536 :
9537 : /* Add object to delete */
9538 1404 : object.classId = RelationRelationId;
9539 1404 : object.objectId = RelationGetRelid(rel);
9540 1404 : object.objectSubId = attnum;
9541 1404 : add_exact_object_address(&object, addrs);
9542 :
9543 1404 : if (!recursing)
9544 : {
9545 : /* Recursion has ended, drop everything that was collected */
9546 1035 : performMultipleDeletions(addrs, behavior, 0);
9547 991 : free_object_addresses(addrs);
9548 : }
9549 :
9550 1360 : return object;
9551 : }
9552 :
9553 : /*
9554 : * Prepare to add a primary key on a table, by adding not-null constraints
9555 : * on all columns.
9556 : *
9557 : * The not-null constraints for a primary key must cover the whole inheritance
9558 : * hierarchy (failing to ensure that leads to funny corner cases). For the
9559 : * normal case where we're asked to recurse, this routine checks if the
9560 : * not-null constraints exist already, and if not queues a requirement for
9561 : * them to be created by phase 2.
9562 : *
9563 : * For the case where we're asked not to recurse, we verify that a not-null
9564 : * constraint exists on each column of each (direct) child table, throwing an
9565 : * error if not. Not throwing an error would also work, because a not-null
9566 : * constraint would be created anyway, but it'd cause a silent scan of the
9567 : * child table to verify absence of nulls. We prefer to let the user know so
9568 : * that they can add the constraint manually without having to hold
9569 : * AccessExclusiveLock while at it.
9570 : *
9571 : * However, it's also important that we do not acquire locks on children if
9572 : * the not-null constraints already exist on the parent, to avoid risking
9573 : * deadlocks during parallel pg_restore of PKs on partitioned tables.
9574 : */
9575 : static void
9576 10394 : ATPrepAddPrimaryKey(List **wqueue, Relation rel, AlterTableCmd *cmd,
9577 : bool recurse, LOCKMODE lockmode,
9578 : AlterTableUtilityContext *context)
9579 : {
9580 : Constraint *pkconstr;
9581 10394 : List *children = NIL;
9582 10394 : bool got_children = false;
9583 :
9584 10394 : pkconstr = castNode(Constraint, cmd->def);
9585 10394 : if (pkconstr->contype != CONSTR_PRIMARY)
9586 6137 : return;
9587 :
9588 : /* Verify that columns are not-null, or request that they be made so */
9589 9123 : foreach_node(String, column, pkconstr->keys)
9590 : {
9591 : AlterTableCmd *newcmd;
9592 : Constraint *nnconstr;
9593 : HeapTuple tuple;
9594 :
9595 : /*
9596 : * First check if a suitable constraint exists. If it does, we don't
9597 : * need to request another one. We do need to bail out if it's not
9598 : * valid, though.
9599 : */
9600 649 : tuple = findNotNullConstraint(RelationGetRelid(rel), strVal(column));
9601 649 : if (tuple != NULL)
9602 : {
9603 322 : verifyNotNullPKCompatible(tuple, strVal(column));
9604 :
9605 : /* All good with this one; don't request another */
9606 314 : heap_freetuple(tuple);
9607 314 : continue;
9608 : }
9609 327 : else if (!recurse)
9610 : {
9611 : /*
9612 : * No constraint on this column. Asked not to recurse, we won't
9613 : * create one here, but verify that all children have one.
9614 : */
9615 24 : if (!got_children)
9616 : {
9617 24 : children = find_inheritance_children(RelationGetRelid(rel),
9618 : lockmode);
9619 : /* only search for children on the first time through */
9620 24 : got_children = true;
9621 : }
9622 :
9623 48 : foreach_oid(childrelid, children)
9624 : {
9625 : HeapTuple tup;
9626 :
9627 24 : tup = findNotNullConstraint(childrelid, strVal(column));
9628 24 : if (!tup)
9629 4 : ereport(ERROR,
9630 : errmsg("column \"%s\" of table \"%s\" is not marked NOT NULL",
9631 : strVal(column), get_rel_name(childrelid)));
9632 : /* verify it's good enough */
9633 20 : verifyNotNullPKCompatible(tup, strVal(column));
9634 : }
9635 : }
9636 :
9637 : /* This column is not already not-null, so add it to the queue */
9638 315 : nnconstr = makeNotNullConstraint(column);
9639 :
9640 315 : newcmd = makeNode(AlterTableCmd);
9641 315 : newcmd->subtype = AT_AddConstraint;
9642 : /* note we force recurse=true here; see above */
9643 315 : newcmd->recurse = true;
9644 315 : newcmd->def = (Node *) nnconstr;
9645 :
9646 315 : ATPrepCmd(wqueue, rel, newcmd, true, false, lockmode, context);
9647 : }
9648 : }
9649 :
9650 : /*
9651 : * Verify whether the given not-null constraint is compatible with a
9652 : * primary key. If not, an error is thrown.
9653 : */
9654 : static void
9655 342 : verifyNotNullPKCompatible(HeapTuple tuple, const char *colname)
9656 : {
9657 342 : Form_pg_constraint conForm = (Form_pg_constraint) GETSTRUCT(tuple);
9658 :
9659 342 : if (conForm->contype != CONSTRAINT_NOTNULL)
9660 0 : elog(ERROR, "constraint %u is not a not-null constraint", conForm->oid);
9661 :
9662 : /* a NO INHERIT constraint is no good */
9663 342 : if (conForm->connoinherit)
9664 8 : ereport(ERROR,
9665 : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
9666 : errmsg("cannot create primary key on column \"%s\"", colname),
9667 : /*- translator: fourth %s is a constraint characteristic such as NOT VALID */
9668 : errdetail("The constraint \"%s\" on column \"%s\" of table \"%s\", marked %s, is incompatible with a primary key.",
9669 : NameStr(conForm->conname), colname,
9670 : get_rel_name(conForm->conrelid), "NO INHERIT"),
9671 : errhint("You might need to make the existing constraint inheritable using %s.",
9672 : "ALTER TABLE ... ALTER CONSTRAINT ... INHERIT"));
9673 :
9674 : /* an unvalidated constraint is no good */
9675 334 : if (!conForm->convalidated)
9676 8 : ereport(ERROR,
9677 : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
9678 : errmsg("cannot create primary key on column \"%s\"", colname),
9679 : /*- translator: fourth %s is a constraint characteristic such as NOT VALID */
9680 : errdetail("The constraint \"%s\" on column \"%s\" of table \"%s\", marked %s, is incompatible with a primary key.",
9681 : NameStr(conForm->conname), colname,
9682 : get_rel_name(conForm->conrelid), "NOT VALID"),
9683 : errhint("You might need to validate it using %s.",
9684 : "ALTER TABLE ... VALIDATE CONSTRAINT"));
9685 326 : }
9686 :
9687 : /*
9688 : * ALTER TABLE ADD INDEX
9689 : *
9690 : * There is no such command in the grammar, but parse_utilcmd.c converts
9691 : * UNIQUE and PRIMARY KEY constraints into AT_AddIndex subcommands. This lets
9692 : * us schedule creation of the index at the appropriate time during ALTER.
9693 : *
9694 : * Return value is the address of the new index.
9695 : */
9696 : static ObjectAddress
9697 1058 : ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
9698 : IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
9699 : {
9700 : bool check_rights;
9701 : bool skip_build;
9702 : bool quiet;
9703 : ObjectAddress address;
9704 :
9705 : Assert(IsA(stmt, IndexStmt));
9706 : Assert(!stmt->concurrent);
9707 :
9708 : /* The IndexStmt has already been through transformIndexStmt */
9709 : Assert(stmt->transformed);
9710 :
9711 : /* suppress schema rights check when rebuilding existing index */
9712 1058 : check_rights = !is_rebuild;
9713 : /* skip index build if phase 3 will do it or we're reusing an old one */
9714 1058 : skip_build = tab->rewrite > 0 || RelFileNumberIsValid(stmt->oldNumber);
9715 : /* suppress notices when rebuilding existing index */
9716 1058 : quiet = is_rebuild;
9717 :
9718 1058 : address = DefineIndex(NULL,
9719 : RelationGetRelid(rel),
9720 : stmt,
9721 : InvalidOid, /* no predefined OID */
9722 : InvalidOid, /* no parent index */
9723 : InvalidOid, /* no parent constraint */
9724 : -1, /* total_parts unknown */
9725 : true, /* is_alter_table */
9726 : check_rights,
9727 : false, /* check_not_in_use - we did it already */
9728 : skip_build,
9729 : quiet);
9730 :
9731 : /*
9732 : * If TryReuseIndex() stashed a relfilenumber for us, we used it for the
9733 : * new index instead of building from scratch. Restore associated fields.
9734 : * This may store InvalidSubTransactionId in both fields, in which case
9735 : * relcache.c will assume it can rebuild the relcache entry. Hence, do
9736 : * this after the CCI that made catalog rows visible to any rebuild. The
9737 : * DROP of the old edition of this index will have scheduled the storage
9738 : * for deletion at commit, so cancel that pending deletion.
9739 : */
9740 945 : if (RelFileNumberIsValid(stmt->oldNumber))
9741 : {
9742 49 : Relation irel = index_open(address.objectId, NoLock);
9743 :
9744 49 : irel->rd_createSubid = stmt->oldCreateSubid;
9745 49 : irel->rd_firstRelfilelocatorSubid = stmt->oldFirstRelfilelocatorSubid;
9746 49 : RelationPreserveStorage(irel->rd_locator, true);
9747 49 : index_close(irel, NoLock);
9748 : }
9749 :
9750 945 : return address;
9751 : }
9752 :
9753 : /*
9754 : * ALTER TABLE ADD STATISTICS
9755 : *
9756 : * This is no such command in the grammar, but we use this internally to add
9757 : * AT_ReAddStatistics subcommands to rebuild extended statistics after a table
9758 : * column type change.
9759 : */
9760 : static ObjectAddress
9761 53 : ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
9762 : CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
9763 : {
9764 : ObjectAddress address;
9765 :
9766 : Assert(IsA(stmt, CreateStatsStmt));
9767 :
9768 : /* The CreateStatsStmt has already been through transformStatsStmt */
9769 : Assert(stmt->transformed);
9770 :
9771 53 : address = CreateStatistics(stmt, !is_rebuild);
9772 :
9773 53 : return address;
9774 : }
9775 :
9776 : /*
9777 : * ALTER TABLE ADD CONSTRAINT USING INDEX
9778 : *
9779 : * Returns the address of the new constraint.
9780 : */
9781 : static ObjectAddress
9782 6653 : ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
9783 : IndexStmt *stmt, LOCKMODE lockmode)
9784 : {
9785 6653 : Oid index_oid = stmt->indexOid;
9786 : Relation indexRel;
9787 : char *indexName;
9788 : IndexInfo *indexInfo;
9789 : char *constraintName;
9790 : char constraintType;
9791 : ObjectAddress address;
9792 : uint16 flags;
9793 :
9794 : Assert(IsA(stmt, IndexStmt));
9795 : Assert(OidIsValid(index_oid));
9796 : Assert(stmt->isconstraint);
9797 :
9798 : /*
9799 : * Doing this on partitioned tables is not a simple feature to implement,
9800 : * so let's punt for now.
9801 : */
9802 6653 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
9803 4 : ereport(ERROR,
9804 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9805 : errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX is not supported on partitioned tables")));
9806 :
9807 6649 : indexRel = index_open(index_oid, AccessShareLock);
9808 :
9809 6649 : indexName = pstrdup(RelationGetRelationName(indexRel));
9810 :
9811 6649 : indexInfo = BuildIndexInfo(indexRel);
9812 :
9813 : /* this should have been checked at parse time */
9814 6649 : if (!indexInfo->ii_Unique)
9815 0 : elog(ERROR, "index \"%s\" is not unique", indexName);
9816 :
9817 : /*
9818 : * Determine name to assign to constraint. We require a constraint to
9819 : * have the same name as the underlying index; therefore, use the index's
9820 : * existing name as the default constraint name, and if the user
9821 : * explicitly gives some other name for the constraint, rename the index
9822 : * to match.
9823 : */
9824 6649 : constraintName = stmt->idxname;
9825 6649 : if (constraintName == NULL)
9826 6632 : constraintName = indexName;
9827 17 : else if (strcmp(constraintName, indexName) != 0)
9828 : {
9829 13 : ereport(NOTICE,
9830 : (errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index \"%s\" to \"%s\"",
9831 : indexName, constraintName)));
9832 13 : RenameRelationInternal(index_oid, constraintName, false, true);
9833 : }
9834 :
9835 : /* Extra checks needed if making primary key */
9836 6649 : if (stmt->primary)
9837 3721 : index_check_primary_key(rel, indexInfo, true, stmt);
9838 :
9839 : /* Note we currently don't support EXCLUSION constraints here */
9840 6645 : if (stmt->primary)
9841 3717 : constraintType = CONSTRAINT_PRIMARY;
9842 : else
9843 2928 : constraintType = CONSTRAINT_UNIQUE;
9844 :
9845 : /* Create the catalog entries for the constraint */
9846 6645 : flags = INDEX_CONSTR_CREATE_UPDATE_INDEX |
9847 : INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS |
9848 13290 : (stmt->initdeferred ? INDEX_CONSTR_CREATE_INIT_DEFERRED : 0) |
9849 6645 : (stmt->deferrable ? INDEX_CONSTR_CREATE_DEFERRABLE : 0) |
9850 6645 : (stmt->primary ? INDEX_CONSTR_CREATE_MARK_AS_PRIMARY : 0);
9851 :
9852 6645 : address = index_constraint_create(rel,
9853 : index_oid,
9854 : InvalidOid,
9855 : indexInfo,
9856 : constraintName,
9857 : constraintType,
9858 : flags,
9859 : allowSystemTableMods,
9860 : false); /* is_internal */
9861 :
9862 6645 : index_close(indexRel, NoLock);
9863 :
9864 6645 : return address;
9865 : }
9866 :
9867 : /*
9868 : * ALTER TABLE ADD CONSTRAINT
9869 : *
9870 : * Return value is the address of the new constraint; if no constraint was
9871 : * added, InvalidObjectAddress is returned.
9872 : */
9873 : static ObjectAddress
9874 8335 : ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
9875 : Constraint *newConstraint, bool recurse, bool is_readd,
9876 : LOCKMODE lockmode)
9877 : {
9878 8335 : ObjectAddress address = InvalidObjectAddress;
9879 :
9880 : Assert(IsA(newConstraint, Constraint));
9881 :
9882 : /*
9883 : * Currently, we only expect to see CONSTR_CHECK, CONSTR_NOTNULL and
9884 : * CONSTR_FOREIGN nodes arriving here (see the preprocessing done in
9885 : * parse_utilcmd.c).
9886 : */
9887 8335 : switch (newConstraint->contype)
9888 : {
9889 6477 : case CONSTR_CHECK:
9890 : case CONSTR_NOTNULL:
9891 : address =
9892 6477 : ATAddCheckNNConstraint(wqueue, tab, rel,
9893 : newConstraint, recurse, false, is_readd,
9894 : lockmode);
9895 6377 : break;
9896 :
9897 1858 : case CONSTR_FOREIGN:
9898 :
9899 : /*
9900 : * Assign or validate constraint name
9901 : */
9902 1858 : if (newConstraint->conname)
9903 : {
9904 761 : if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
9905 : RelationGetRelid(rel),
9906 761 : newConstraint->conname))
9907 0 : ereport(ERROR,
9908 : (errcode(ERRCODE_DUPLICATE_OBJECT),
9909 : errmsg("constraint \"%s\" for relation \"%s\" already exists",
9910 : newConstraint->conname,
9911 : RelationGetRelationName(rel))));
9912 : }
9913 : else
9914 1097 : newConstraint->conname =
9915 1097 : ChooseConstraintName(RelationGetRelationName(rel),
9916 1097 : ChooseForeignKeyConstraintNameAddition(newConstraint->fk_attrs),
9917 : "fkey",
9918 1097 : RelationGetNamespace(rel),
9919 : NIL);
9920 :
9921 1858 : address = ATAddForeignKeyConstraint(wqueue, tab, rel,
9922 : newConstraint,
9923 : recurse, false,
9924 : lockmode);
9925 1493 : break;
9926 :
9927 0 : default:
9928 0 : elog(ERROR, "unrecognized constraint type: %d",
9929 : (int) newConstraint->contype);
9930 : }
9931 :
9932 7870 : return address;
9933 : }
9934 :
9935 : /*
9936 : * Generate the column-name portion of the constraint name for a new foreign
9937 : * key given the list of column names that reference the referenced
9938 : * table. This will be passed to ChooseConstraintName along with the parent
9939 : * table name and the "fkey" suffix.
9940 : *
9941 : * We know that less than NAMEDATALEN characters will actually be used, so we
9942 : * can truncate the result once we've generated that many.
9943 : *
9944 : * XXX see also ChooseExtendedStatisticNameAddition and
9945 : * ChooseIndexNameAddition.
9946 : */
9947 : static char *
9948 1097 : ChooseForeignKeyConstraintNameAddition(List *colnames)
9949 : {
9950 : char buf[NAMEDATALEN * 2];
9951 1097 : int buflen = 0;
9952 : ListCell *lc;
9953 :
9954 1097 : buf[0] = '\0';
9955 2488 : foreach(lc, colnames)
9956 : {
9957 1391 : const char *name = strVal(lfirst(lc));
9958 :
9959 1391 : if (buflen > 0)
9960 294 : buf[buflen++] = '_'; /* insert _ between names */
9961 :
9962 : /*
9963 : * At this point we have buflen <= NAMEDATALEN. name should be less
9964 : * than NAMEDATALEN already, but use strlcpy for paranoia.
9965 : */
9966 1391 : strlcpy(buf + buflen, name, NAMEDATALEN);
9967 1391 : buflen += strlen(buf + buflen);
9968 1391 : if (buflen >= NAMEDATALEN)
9969 0 : break;
9970 : }
9971 1097 : return pstrdup(buf);
9972 : }
9973 :
9974 : /*
9975 : * Add a check or not-null constraint to a single table and its children.
9976 : * Returns the address of the constraint added to the parent relation,
9977 : * if one gets added, or InvalidObjectAddress otherwise.
9978 : *
9979 : * Subroutine for ATExecAddConstraint.
9980 : *
9981 : * We must recurse to child tables during execution, rather than using
9982 : * ALTER TABLE's normal prep-time recursion. The reason is that all the
9983 : * constraints *must* be given the same name, else they won't be seen as
9984 : * related later. If the user didn't explicitly specify a name, then
9985 : * AddRelationNewConstraints would normally assign different names to the
9986 : * child constraints. To fix that, we must capture the name assigned at
9987 : * the parent table and pass that down.
9988 : */
9989 : static ObjectAddress
9990 7160 : ATAddCheckNNConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
9991 : Constraint *constr, bool recurse, bool recursing,
9992 : bool is_readd, LOCKMODE lockmode)
9993 : {
9994 : List *newcons;
9995 : ListCell *lcon;
9996 : List *children;
9997 : ListCell *child;
9998 7160 : ObjectAddress address = InvalidObjectAddress;
9999 :
10000 : /* Guard against stack overflow due to overly deep inheritance tree. */
10001 7160 : check_stack_depth();
10002 :
10003 : /* At top level, permission check was done in ATPrepCmd, else do it */
10004 7160 : if (recursing)
10005 683 : ATSimplePermissions(AT_AddConstraint, rel,
10006 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
10007 :
10008 : /*
10009 : * Call AddRelationNewConstraints to do the work, making sure it works on
10010 : * a copy of the Constraint so transformExpr can't modify the original. It
10011 : * returns a list of cooked constraints.
10012 : *
10013 : * If the constraint ends up getting merged with a pre-existing one, it's
10014 : * omitted from the returned list, which is what we want: we do not need
10015 : * to do any validation work. That can only happen at child tables,
10016 : * though, since we disallow merging at the top level.
10017 : */
10018 7160 : newcons = AddRelationNewConstraints(rel, NIL,
10019 : list_make1(copyObject(constr)),
10020 7160 : recursing || is_readd, /* allow_merge */
10021 : !recursing, /* is_local */
10022 : is_readd, /* is_internal */
10023 14320 : NULL); /* queryString not available
10024 : * here */
10025 :
10026 : /* we don't expect more than one constraint here */
10027 : Assert(list_length(newcons) <= 1);
10028 :
10029 : /* Add each to-be-validated constraint to Phase 3's queue */
10030 13998 : foreach(lcon, newcons)
10031 : {
10032 6934 : CookedConstraint *ccon = (CookedConstraint *) lfirst(lcon);
10033 :
10034 6934 : if (!ccon->skip_validation && ccon->contype != CONSTR_NOTNULL)
10035 : {
10036 : NewConstraint *newcon;
10037 :
10038 771 : newcon = palloc0_object(NewConstraint);
10039 771 : newcon->name = ccon->name;
10040 771 : newcon->contype = ccon->contype;
10041 771 : newcon->qual = ccon->expr;
10042 :
10043 771 : tab->constraints = lappend(tab->constraints, newcon);
10044 : }
10045 :
10046 : /* Save the actually assigned name if it was defaulted */
10047 6934 : if (constr->conname == NULL)
10048 5512 : constr->conname = ccon->name;
10049 :
10050 : /*
10051 : * If adding a valid not-null constraint, set the pg_attribute flag
10052 : * and tell phase 3 to verify existing rows, if needed. For an
10053 : * invalid constraint, just set attnotnull, without queueing
10054 : * verification.
10055 : */
10056 6934 : if (constr->contype == CONSTR_NOTNULL)
10057 5858 : set_attnotnull(wqueue, rel, ccon->attnum,
10058 5858 : !constr->skip_validation,
10059 5858 : !constr->skip_validation);
10060 :
10061 6934 : ObjectAddressSet(address, ConstraintRelationId, ccon->conoid);
10062 : }
10063 :
10064 : /* At this point we must have a locked-down name to use */
10065 : Assert(newcons == NIL || constr->conname != NULL);
10066 :
10067 : /* Advance command counter in case same table is visited multiple times */
10068 7064 : CommandCounterIncrement();
10069 :
10070 : /*
10071 : * If the constraint got merged with an existing constraint, we're done.
10072 : * We mustn't recurse to child tables in this case, because they've
10073 : * already got the constraint, and visiting them again would lead to an
10074 : * incorrect value for coninhcount.
10075 : */
10076 7064 : if (newcons == NIL)
10077 130 : return address;
10078 :
10079 : /*
10080 : * If adding a NO INHERIT constraint, no need to find our children.
10081 : */
10082 6934 : if (constr->is_no_inherit)
10083 56 : return address;
10084 :
10085 : /*
10086 : * Propagate to children as appropriate. Unlike most other ALTER
10087 : * routines, we have to do this one level of recursion at a time; we can't
10088 : * use find_all_inheritors to do it in one pass.
10089 : */
10090 : children =
10091 6878 : find_inheritance_children(RelationGetRelid(rel), lockmode);
10092 :
10093 : /*
10094 : * Check if ONLY was specified with ALTER TABLE. If so, allow the
10095 : * constraint creation only if there are no children currently. Error out
10096 : * otherwise.
10097 : */
10098 6878 : if (!recurse && children != NIL)
10099 4 : ereport(ERROR,
10100 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
10101 : errmsg("constraint must be added to child tables too")));
10102 :
10103 : /*
10104 : * Recurse to create the constraint on each child.
10105 : */
10106 7537 : foreach(child, children)
10107 : {
10108 683 : Oid childrelid = lfirst_oid(child);
10109 : Relation childrel;
10110 : AlteredTableInfo *childtab;
10111 :
10112 : /* find_inheritance_children already got lock */
10113 683 : childrel = table_open(childrelid, NoLock);
10114 683 : CheckAlterTableIsSafe(childrel);
10115 :
10116 : /* Find or create work queue entry for this table */
10117 683 : childtab = ATGetQueueEntry(wqueue, childrel);
10118 :
10119 : /* Recurse to this child */
10120 683 : ATAddCheckNNConstraint(wqueue, childtab, childrel,
10121 : constr, recurse, true, is_readd, lockmode);
10122 :
10123 663 : table_close(childrel, NoLock);
10124 : }
10125 :
10126 6854 : return address;
10127 : }
10128 :
10129 : /*
10130 : * Add a foreign-key constraint to a single table; return the new constraint's
10131 : * address.
10132 : *
10133 : * Subroutine for ATExecAddConstraint. Must already hold exclusive
10134 : * lock on the rel, and have done appropriate validity checks for it.
10135 : * We do permissions checks here, however.
10136 : *
10137 : * When the referenced or referencing tables (or both) are partitioned,
10138 : * multiple pg_constraint rows are required -- one for each partitioned table
10139 : * and each partition on each side (fortunately, not one for every combination
10140 : * thereof). We also need action triggers on each leaf partition on the
10141 : * referenced side, and check triggers on each leaf partition on the
10142 : * referencing side.
10143 : */
10144 : static ObjectAddress
10145 1858 : ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
10146 : Constraint *fkconstraint,
10147 : bool recurse, bool recursing, LOCKMODE lockmode)
10148 : {
10149 : Relation pkrel;
10150 1858 : int16 pkattnum[INDEX_MAX_KEYS] = {0};
10151 1858 : int16 fkattnum[INDEX_MAX_KEYS] = {0};
10152 1858 : Oid pktypoid[INDEX_MAX_KEYS] = {0};
10153 1858 : Oid fktypoid[INDEX_MAX_KEYS] = {0};
10154 1858 : Oid pkcolloid[INDEX_MAX_KEYS] = {0};
10155 1858 : Oid fkcolloid[INDEX_MAX_KEYS] = {0};
10156 1858 : Oid opclasses[INDEX_MAX_KEYS] = {0};
10157 1858 : Oid pfeqoperators[INDEX_MAX_KEYS] = {0};
10158 1858 : Oid ppeqoperators[INDEX_MAX_KEYS] = {0};
10159 1858 : Oid ffeqoperators[INDEX_MAX_KEYS] = {0};
10160 1858 : int16 fkdelsetcols[INDEX_MAX_KEYS] = {0};
10161 : bool with_period;
10162 : bool pk_has_without_overlaps;
10163 : int i;
10164 : int numfks,
10165 : numpks,
10166 : numfkdelsetcols;
10167 : Oid indexOid;
10168 : bool old_check_ok;
10169 : ObjectAddress address;
10170 1858 : ListCell *old_pfeqop_item = list_head(fkconstraint->old_conpfeqop);
10171 :
10172 : /*
10173 : * Grab ShareRowExclusiveLock on the pk table, so that someone doesn't
10174 : * delete rows out from under us.
10175 : */
10176 1858 : if (OidIsValid(fkconstraint->old_pktable_oid))
10177 48 : pkrel = table_open(fkconstraint->old_pktable_oid, ShareRowExclusiveLock);
10178 : else
10179 1810 : pkrel = table_openrv(fkconstraint->pktable, ShareRowExclusiveLock);
10180 :
10181 : /*
10182 : * Validity checks (permission checks wait till we have the column
10183 : * numbers)
10184 : */
10185 1854 : if (!recurse && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
10186 4 : ereport(ERROR,
10187 : errcode(ERRCODE_WRONG_OBJECT_TYPE),
10188 : errmsg("cannot use ONLY for foreign key on partitioned table \"%s\" referencing relation \"%s\"",
10189 : RelationGetRelationName(rel),
10190 : RelationGetRelationName(pkrel)));
10191 :
10192 1850 : if (pkrel->rd_rel->relkind != RELKIND_RELATION &&
10193 236 : pkrel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
10194 0 : ereport(ERROR,
10195 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
10196 : errmsg("referenced relation \"%s\" is not a table",
10197 : RelationGetRelationName(pkrel))));
10198 :
10199 1850 : if (!allowSystemTableMods && IsSystemRelation(pkrel))
10200 1 : ereport(ERROR,
10201 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
10202 : errmsg("permission denied: \"%s\" is a system catalog",
10203 : RelationGetRelationName(pkrel))));
10204 :
10205 : /*
10206 : * References from permanent or unlogged tables to temp tables, and from
10207 : * permanent tables to unlogged tables, are disallowed because the
10208 : * referenced data can vanish out from under us. References from temp
10209 : * tables to any other table type are also disallowed, because other
10210 : * backends might need to run the RI triggers on the perm table, but they
10211 : * can't reliably see tuples in the local buffers of other backends.
10212 : */
10213 1849 : switch (rel->rd_rel->relpersistence)
10214 : {
10215 1656 : case RELPERSISTENCE_PERMANENT:
10216 1656 : if (!RelationIsPermanent(pkrel))
10217 0 : ereport(ERROR,
10218 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
10219 : errmsg("constraints on permanent tables may reference only permanent tables")));
10220 1656 : break;
10221 8 : case RELPERSISTENCE_UNLOGGED:
10222 8 : if (!RelationIsPermanent(pkrel)
10223 8 : && pkrel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED)
10224 0 : ereport(ERROR,
10225 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
10226 : errmsg("constraints on unlogged tables may reference only permanent or unlogged tables")));
10227 8 : break;
10228 185 : case RELPERSISTENCE_TEMP:
10229 185 : if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
10230 0 : ereport(ERROR,
10231 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
10232 : errmsg("constraints on temporary tables may reference only temporary tables")));
10233 185 : if (!pkrel->rd_islocaltemp || !rel->rd_islocaltemp)
10234 0 : ereport(ERROR,
10235 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
10236 : errmsg("constraints on temporary tables must involve temporary tables of this session")));
10237 185 : break;
10238 : }
10239 :
10240 : /*
10241 : * Look up the referencing attributes to make sure they exist, and record
10242 : * their attnums and type and collation OIDs.
10243 : */
10244 1849 : numfks = transformColumnNameList(RelationGetRelid(rel),
10245 : fkconstraint->fk_attrs,
10246 : fkattnum, fktypoid, fkcolloid);
10247 1829 : with_period = fkconstraint->fk_with_period || fkconstraint->pk_with_period;
10248 1829 : if (with_period && !fkconstraint->fk_with_period)
10249 16 : ereport(ERROR,
10250 : errcode(ERRCODE_INVALID_FOREIGN_KEY),
10251 : errmsg("foreign key uses PERIOD on the referenced table but not the referencing table"));
10252 :
10253 1813 : numfkdelsetcols = transformColumnNameList(RelationGetRelid(rel),
10254 : fkconstraint->fk_del_set_cols,
10255 : fkdelsetcols, NULL, NULL);
10256 1809 : numfkdelsetcols = validateFkOnDeleteSetColumns(numfks, fkattnum,
10257 : numfkdelsetcols,
10258 : fkdelsetcols,
10259 : fkconstraint->fk_del_set_cols);
10260 :
10261 : /*
10262 : * If the attribute list for the referenced table was omitted, lookup the
10263 : * definition of the primary key and use it. Otherwise, validate the
10264 : * supplied attribute list. In either case, discover the index OID and
10265 : * index opclasses, and the attnums and type and collation OIDs of the
10266 : * attributes.
10267 : */
10268 1805 : if (fkconstraint->pk_attrs == NIL)
10269 : {
10270 900 : numpks = transformFkeyGetPrimaryKey(pkrel, &indexOid,
10271 : &fkconstraint->pk_attrs,
10272 : pkattnum, pktypoid, pkcolloid,
10273 : opclasses, &pk_has_without_overlaps);
10274 :
10275 : /* If the primary key uses WITHOUT OVERLAPS, the fk must use PERIOD */
10276 900 : if (pk_has_without_overlaps && !fkconstraint->fk_with_period)
10277 16 : ereport(ERROR,
10278 : errcode(ERRCODE_INVALID_FOREIGN_KEY),
10279 : errmsg("foreign key uses PERIOD on the referenced table but not the referencing table"));
10280 : }
10281 : else
10282 : {
10283 905 : numpks = transformColumnNameList(RelationGetRelid(pkrel),
10284 : fkconstraint->pk_attrs,
10285 : pkattnum, pktypoid, pkcolloid);
10286 :
10287 : /* Since we got pk_attrs, one should be a period. */
10288 885 : if (with_period && !fkconstraint->pk_with_period)
10289 16 : ereport(ERROR,
10290 : errcode(ERRCODE_INVALID_FOREIGN_KEY),
10291 : errmsg("foreign key uses PERIOD on the referencing table but not the referenced table"));
10292 :
10293 : /* Look for an index matching the column list */
10294 869 : indexOid = transformFkeyCheckAttrs(pkrel, numpks, pkattnum,
10295 : with_period, opclasses, &pk_has_without_overlaps);
10296 : }
10297 :
10298 : /*
10299 : * If the referenced primary key has WITHOUT OVERLAPS, the foreign key
10300 : * must use PERIOD.
10301 : */
10302 1729 : if (pk_has_without_overlaps && !with_period)
10303 8 : ereport(ERROR,
10304 : errcode(ERRCODE_INVALID_FOREIGN_KEY),
10305 : errmsg("foreign key must use PERIOD when referencing a primary key using WITHOUT OVERLAPS"));
10306 :
10307 : /*
10308 : * Now we can check permissions.
10309 : */
10310 1721 : checkFkeyPermissions(pkrel, pkattnum, numpks);
10311 :
10312 : /*
10313 : * Check some things for generated columns.
10314 : */
10315 4023 : for (i = 0; i < numfks; i++)
10316 : {
10317 2322 : char attgenerated = TupleDescAttr(RelationGetDescr(rel), fkattnum[i] - 1)->attgenerated;
10318 :
10319 2322 : if (attgenerated)
10320 : {
10321 : /*
10322 : * Check restrictions on UPDATE/DELETE actions, per SQL standard
10323 : */
10324 32 : if (fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETNULL ||
10325 32 : fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETDEFAULT ||
10326 32 : fkconstraint->fk_upd_action == FKCONSTR_ACTION_CASCADE)
10327 8 : ereport(ERROR,
10328 : (errcode(ERRCODE_SYNTAX_ERROR),
10329 : errmsg("invalid %s action for foreign key constraint containing generated column",
10330 : "ON UPDATE")));
10331 24 : if (fkconstraint->fk_del_action == FKCONSTR_ACTION_SETNULL ||
10332 16 : fkconstraint->fk_del_action == FKCONSTR_ACTION_SETDEFAULT)
10333 8 : ereport(ERROR,
10334 : (errcode(ERRCODE_SYNTAX_ERROR),
10335 : errmsg("invalid %s action for foreign key constraint containing generated column",
10336 : "ON DELETE")));
10337 : }
10338 :
10339 : /*
10340 : * FKs on virtual columns are not supported. This would require
10341 : * various additional support in ri_triggers.c, including special
10342 : * handling in ri_NullCheck(), ri_KeysEqual(),
10343 : * RI_FKey_fk_upd_check_required() (since all virtual columns appear
10344 : * as NULL there). Also not really practical as long as you can't
10345 : * index virtual columns.
10346 : */
10347 2306 : if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
10348 4 : ereport(ERROR,
10349 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
10350 : errmsg("foreign key constraints on virtual generated columns are not supported")));
10351 : }
10352 :
10353 : /*
10354 : * Some actions are currently unsupported for foreign keys using PERIOD.
10355 : */
10356 1701 : if (fkconstraint->fk_with_period)
10357 : {
10358 178 : if (fkconstraint->fk_upd_action == FKCONSTR_ACTION_RESTRICT ||
10359 170 : fkconstraint->fk_upd_action == FKCONSTR_ACTION_CASCADE ||
10360 158 : fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETNULL ||
10361 146 : fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETDEFAULT)
10362 44 : ereport(ERROR,
10363 : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
10364 : errmsg("unsupported %s action for foreign key constraint using PERIOD",
10365 : "ON UPDATE"));
10366 :
10367 134 : if (fkconstraint->fk_del_action == FKCONSTR_ACTION_RESTRICT ||
10368 130 : fkconstraint->fk_del_action == FKCONSTR_ACTION_CASCADE ||
10369 130 : fkconstraint->fk_del_action == FKCONSTR_ACTION_SETNULL ||
10370 130 : fkconstraint->fk_del_action == FKCONSTR_ACTION_SETDEFAULT)
10371 4 : ereport(ERROR,
10372 : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
10373 : errmsg("unsupported %s action for foreign key constraint using PERIOD",
10374 : "ON DELETE"));
10375 : }
10376 :
10377 : /*
10378 : * Look up the equality operators to use in the constraint.
10379 : *
10380 : * Note that we have to be careful about the difference between the actual
10381 : * PK column type and the opclass' declared input type, which might be
10382 : * only binary-compatible with it. The declared opcintype is the right
10383 : * thing to probe pg_amop with.
10384 : */
10385 1653 : if (numfks != numpks)
10386 0 : ereport(ERROR,
10387 : (errcode(ERRCODE_INVALID_FOREIGN_KEY),
10388 : errmsg("number of referencing and referenced columns for foreign key disagree")));
10389 :
10390 : /*
10391 : * On the strength of a previous constraint, we might avoid scanning
10392 : * tables to validate this one. See below.
10393 : */
10394 1653 : old_check_ok = (fkconstraint->old_conpfeqop != NIL);
10395 : Assert(!old_check_ok || numfks == list_length(fkconstraint->old_conpfeqop));
10396 :
10397 3603 : for (i = 0; i < numpks; i++)
10398 : {
10399 2110 : Oid pktype = pktypoid[i];
10400 2110 : Oid fktype = fktypoid[i];
10401 : Oid fktyped;
10402 2110 : Oid pkcoll = pkcolloid[i];
10403 2110 : Oid fkcoll = fkcolloid[i];
10404 : HeapTuple cla_ht;
10405 : Form_pg_opclass cla_tup;
10406 : Oid amid;
10407 : Oid opfamily;
10408 : Oid opcintype;
10409 : bool for_overlaps;
10410 : CompareType cmptype;
10411 : Oid pfeqop;
10412 : Oid ppeqop;
10413 : Oid ffeqop;
10414 : int16 eqstrategy;
10415 : Oid pfeqop_right;
10416 :
10417 : /* We need several fields out of the pg_opclass entry */
10418 2110 : cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclasses[i]));
10419 2110 : if (!HeapTupleIsValid(cla_ht))
10420 0 : elog(ERROR, "cache lookup failed for opclass %u", opclasses[i]);
10421 2110 : cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
10422 2110 : amid = cla_tup->opcmethod;
10423 2110 : opfamily = cla_tup->opcfamily;
10424 2110 : opcintype = cla_tup->opcintype;
10425 2110 : ReleaseSysCache(cla_ht);
10426 :
10427 : /*
10428 : * Get strategy number from index AM.
10429 : *
10430 : * For a normal foreign-key constraint, this should not fail, since we
10431 : * already checked that the index is unique and should therefore have
10432 : * appropriate equal operators. For a period foreign key, this could
10433 : * fail if we selected a non-matching exclusion constraint earlier.
10434 : * (XXX Maybe we should do these lookups earlier so we don't end up
10435 : * doing that.)
10436 : */
10437 2110 : for_overlaps = with_period && i == numpks - 1;
10438 2110 : cmptype = for_overlaps ? COMPARE_OVERLAP : COMPARE_EQ;
10439 2110 : eqstrategy = IndexAmTranslateCompareType(cmptype, amid, opfamily, true);
10440 2110 : if (eqstrategy == InvalidStrategy)
10441 0 : ereport(ERROR,
10442 : errcode(ERRCODE_UNDEFINED_OBJECT),
10443 : for_overlaps
10444 : ? errmsg("could not identify an overlaps operator for foreign key")
10445 : : errmsg("could not identify an equality operator for foreign key"),
10446 : errdetail("Could not translate compare type %d for operator family \"%s\" of access method \"%s\".",
10447 : cmptype, get_opfamily_name(opfamily, false), get_am_name(amid)));
10448 :
10449 : /*
10450 : * There had better be a primary equality operator for the index.
10451 : * We'll use it for PK = PK comparisons.
10452 : */
10453 2110 : ppeqop = get_opfamily_member(opfamily, opcintype, opcintype,
10454 : eqstrategy);
10455 :
10456 2110 : if (!OidIsValid(ppeqop))
10457 0 : elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
10458 : eqstrategy, opcintype, opcintype, opfamily);
10459 :
10460 : /*
10461 : * Are there equality operators that take exactly the FK type? Assume
10462 : * we should look through any domain here.
10463 : */
10464 2110 : fktyped = getBaseType(fktype);
10465 :
10466 2110 : pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
10467 : eqstrategy);
10468 2110 : if (OidIsValid(pfeqop))
10469 : {
10470 1657 : pfeqop_right = fktyped;
10471 1657 : ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
10472 : eqstrategy);
10473 : }
10474 : else
10475 : {
10476 : /* keep compiler quiet */
10477 453 : pfeqop_right = InvalidOid;
10478 453 : ffeqop = InvalidOid;
10479 : }
10480 :
10481 2110 : if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
10482 : {
10483 : /*
10484 : * Otherwise, look for an implicit cast from the FK type to the
10485 : * opcintype, and if found, use the primary equality operator.
10486 : * This is a bit tricky because opcintype might be a polymorphic
10487 : * type such as ANYARRAY or ANYENUM; so what we have to test is
10488 : * whether the two actual column types can be concurrently cast to
10489 : * that type. (Otherwise, we'd fail to reject combinations such
10490 : * as int[] and point[].)
10491 : */
10492 : Oid input_typeids[2];
10493 : Oid target_typeids[2];
10494 :
10495 453 : input_typeids[0] = pktype;
10496 453 : input_typeids[1] = fktype;
10497 453 : target_typeids[0] = opcintype;
10498 453 : target_typeids[1] = opcintype;
10499 453 : if (can_coerce_type(2, input_typeids, target_typeids,
10500 : COERCION_IMPLICIT))
10501 : {
10502 301 : pfeqop = ffeqop = ppeqop;
10503 301 : pfeqop_right = opcintype;
10504 : }
10505 : }
10506 :
10507 2110 : if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
10508 152 : ereport(ERROR,
10509 : (errcode(ERRCODE_DATATYPE_MISMATCH),
10510 : errmsg("foreign key constraint \"%s\" cannot be implemented",
10511 : fkconstraint->conname),
10512 : errdetail("Key columns \"%s\" of the referencing table and \"%s\" of the referenced table "
10513 : "are of incompatible types: %s and %s.",
10514 : strVal(list_nth(fkconstraint->fk_attrs, i)),
10515 : strVal(list_nth(fkconstraint->pk_attrs, i)),
10516 : format_type_be(fktype),
10517 : format_type_be(pktype))));
10518 :
10519 : /*
10520 : * This shouldn't be possible, but better check to make sure we have a
10521 : * consistent state for the check below.
10522 : */
10523 1958 : if ((OidIsValid(pkcoll) && !OidIsValid(fkcoll)) || (!OidIsValid(pkcoll) && OidIsValid(fkcoll)))
10524 0 : elog(ERROR, "key columns are not both collatable");
10525 :
10526 1958 : if (OidIsValid(pkcoll) && OidIsValid(fkcoll))
10527 : {
10528 : bool pkcolldet;
10529 : bool fkcolldet;
10530 :
10531 73 : pkcolldet = get_collation_isdeterministic(pkcoll);
10532 73 : fkcolldet = get_collation_isdeterministic(fkcoll);
10533 :
10534 : /*
10535 : * SQL requires that both collations are the same. This is
10536 : * because we need a consistent notion of equality on both
10537 : * columns. We relax this by allowing different collations if
10538 : * they are both deterministic. (This is also for backward
10539 : * compatibility, because PostgreSQL has always allowed this.)
10540 : */
10541 73 : if ((!pkcolldet || !fkcolldet) && pkcoll != fkcoll)
10542 8 : ereport(ERROR,
10543 : (errcode(ERRCODE_COLLATION_MISMATCH),
10544 : errmsg("foreign key constraint \"%s\" cannot be implemented", fkconstraint->conname),
10545 : errdetail("Key columns \"%s\" of the referencing table and \"%s\" of the referenced table "
10546 : "have incompatible collations: \"%s\" and \"%s\". "
10547 : "If either collation is nondeterministic, then both collations have to be the same.",
10548 : strVal(list_nth(fkconstraint->fk_attrs, i)),
10549 : strVal(list_nth(fkconstraint->pk_attrs, i)),
10550 : get_collation_name(fkcoll),
10551 : get_collation_name(pkcoll))));
10552 : }
10553 :
10554 1950 : if (old_check_ok)
10555 : {
10556 : /*
10557 : * When a pfeqop changes, revalidate the constraint. We could
10558 : * permit intra-opfamily changes, but that adds subtle complexity
10559 : * without any concrete benefit for core types. We need not
10560 : * assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
10561 : */
10562 4 : old_check_ok = (pfeqop == lfirst_oid(old_pfeqop_item));
10563 4 : old_pfeqop_item = lnext(fkconstraint->old_conpfeqop,
10564 : old_pfeqop_item);
10565 : }
10566 1950 : if (old_check_ok)
10567 : {
10568 : Oid old_fktype;
10569 : Oid new_fktype;
10570 : CoercionPathType old_pathtype;
10571 : CoercionPathType new_pathtype;
10572 : Oid old_castfunc;
10573 : Oid new_castfunc;
10574 : Oid old_fkcoll;
10575 : Oid new_fkcoll;
10576 4 : Form_pg_attribute attr = TupleDescAttr(tab->oldDesc,
10577 4 : fkattnum[i] - 1);
10578 :
10579 : /*
10580 : * Identify coercion pathways from each of the old and new FK-side
10581 : * column types to the right (foreign) operand type of the pfeqop.
10582 : * We may assume that pg_constraint.conkey is not changing.
10583 : */
10584 4 : old_fktype = attr->atttypid;
10585 4 : new_fktype = fktype;
10586 4 : old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
10587 : &old_castfunc);
10588 4 : new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
10589 : &new_castfunc);
10590 :
10591 4 : old_fkcoll = attr->attcollation;
10592 4 : new_fkcoll = fkcoll;
10593 :
10594 : /*
10595 : * Upon a change to the cast from the FK column to its pfeqop
10596 : * operand, revalidate the constraint. For this evaluation, a
10597 : * binary coercion cast is equivalent to no cast at all. While
10598 : * type implementors should design implicit casts with an eye
10599 : * toward consistency of operations like equality, we cannot
10600 : * assume here that they have done so.
10601 : *
10602 : * A function with a polymorphic argument could change behavior
10603 : * arbitrarily in response to get_fn_expr_argtype(). Therefore,
10604 : * when the cast destination is polymorphic, we only avoid
10605 : * revalidation if the input type has not changed at all. Given
10606 : * just the core data types and operator classes, this requirement
10607 : * prevents no would-be optimizations.
10608 : *
10609 : * If the cast converts from a base type to a domain thereon, then
10610 : * that domain type must be the opcintype of the unique index.
10611 : * Necessarily, the primary key column must then be of the domain
10612 : * type. Since the constraint was previously valid, all values on
10613 : * the foreign side necessarily exist on the primary side and in
10614 : * turn conform to the domain. Consequently, we need not treat
10615 : * domains specially here.
10616 : *
10617 : * If the collation changes, revalidation is required, unless both
10618 : * collations are deterministic, because those share the same
10619 : * notion of equality (because texteq reduces to bitwise
10620 : * equality).
10621 : *
10622 : * We need not directly consider the PK type. It's necessarily
10623 : * binary coercible to the opcintype of the unique index column,
10624 : * and ri_triggers.c will only deal with PK datums in terms of
10625 : * that opcintype. Changing the opcintype also changes pfeqop.
10626 : */
10627 4 : old_check_ok = (new_pathtype == old_pathtype &&
10628 4 : new_castfunc == old_castfunc &&
10629 4 : (!IsPolymorphicType(pfeqop_right) ||
10630 8 : new_fktype == old_fktype) &&
10631 0 : (new_fkcoll == old_fkcoll ||
10632 0 : (get_collation_isdeterministic(old_fkcoll) && get_collation_isdeterministic(new_fkcoll))));
10633 : }
10634 :
10635 1950 : pfeqoperators[i] = pfeqop;
10636 1950 : ppeqoperators[i] = ppeqop;
10637 1950 : ffeqoperators[i] = ffeqop;
10638 : }
10639 :
10640 : /*
10641 : * For FKs with PERIOD we need additional operators to check whether the
10642 : * referencing row's range is contained by the aggregated ranges of the
10643 : * referenced row(s). For rangetypes and multirangetypes this is
10644 : * fk.periodatt <@ range_agg(pk.periodatt). Those are the only types we
10645 : * support for now. FKs will look these up at "runtime", but we should
10646 : * make sure the lookup works here, even if we don't use the values.
10647 : */
10648 1493 : if (with_period)
10649 : {
10650 : Oid periodoperoid;
10651 : Oid aggedperiodoperoid;
10652 : Oid intersectoperoid;
10653 :
10654 118 : FindFKPeriodOpers(opclasses[numpks - 1], &periodoperoid, &aggedperiodoperoid,
10655 : &intersectoperoid);
10656 : }
10657 :
10658 : /* First, create the constraint catalog entry itself. */
10659 1493 : address = addFkConstraint(addFkBothSides,
10660 : fkconstraint->conname, fkconstraint, rel, pkrel,
10661 : indexOid,
10662 : InvalidOid, /* no parent constraint */
10663 : numfks,
10664 : pkattnum,
10665 : fkattnum,
10666 : pfeqoperators,
10667 : ppeqoperators,
10668 : ffeqoperators,
10669 : numfkdelsetcols,
10670 : fkdelsetcols,
10671 : false,
10672 : with_period);
10673 :
10674 : /* Next process the action triggers at the referenced side and recurse */
10675 1493 : addFkRecurseReferenced(fkconstraint, rel, pkrel,
10676 : indexOid,
10677 : address.objectId,
10678 : numfks,
10679 : pkattnum,
10680 : fkattnum,
10681 : pfeqoperators,
10682 : ppeqoperators,
10683 : ffeqoperators,
10684 : numfkdelsetcols,
10685 : fkdelsetcols,
10686 : old_check_ok,
10687 : InvalidOid, InvalidOid,
10688 : with_period);
10689 :
10690 : /* Lastly create the check triggers at the referencing side and recurse */
10691 1493 : addFkRecurseReferencing(wqueue, fkconstraint, rel, pkrel,
10692 : indexOid,
10693 : address.objectId,
10694 : numfks,
10695 : pkattnum,
10696 : fkattnum,
10697 : pfeqoperators,
10698 : ppeqoperators,
10699 : ffeqoperators,
10700 : numfkdelsetcols,
10701 : fkdelsetcols,
10702 : old_check_ok,
10703 : lockmode,
10704 : InvalidOid, InvalidOid,
10705 : with_period);
10706 :
10707 : /*
10708 : * Done. Close pk table, but keep lock until we've committed.
10709 : */
10710 1493 : table_close(pkrel, NoLock);
10711 :
10712 1493 : return address;
10713 : }
10714 :
10715 : /*
10716 : * validateFkOnDeleteSetColumns
10717 : * Verifies that columns used in ON DELETE SET NULL/DEFAULT (...)
10718 : * column lists are valid.
10719 : *
10720 : * If there are duplicates in the fksetcolsattnums[] array, this silently
10721 : * removes the dups. The new count of numfksetcols is returned.
10722 : */
10723 : static int
10724 1809 : validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
10725 : int numfksetcols, int16 *fksetcolsattnums,
10726 : List *fksetcols)
10727 : {
10728 1809 : int numcolsout = 0;
10729 :
10730 1829 : for (int i = 0; i < numfksetcols; i++)
10731 : {
10732 24 : int16 setcol_attnum = fksetcolsattnums[i];
10733 24 : bool seen = false;
10734 :
10735 : /* Make sure it's in fkattnums[] */
10736 44 : for (int j = 0; j < numfks; j++)
10737 : {
10738 40 : if (fkattnums[j] == setcol_attnum)
10739 : {
10740 20 : seen = true;
10741 20 : break;
10742 : }
10743 : }
10744 :
10745 24 : if (!seen)
10746 : {
10747 4 : char *col = strVal(list_nth(fksetcols, i));
10748 :
10749 4 : ereport(ERROR,
10750 : (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
10751 : errmsg("column \"%s\" referenced in ON DELETE SET action must be part of foreign key", col)));
10752 : }
10753 :
10754 : /* Now check for dups */
10755 20 : seen = false;
10756 20 : for (int j = 0; j < numcolsout; j++)
10757 : {
10758 4 : if (fksetcolsattnums[j] == setcol_attnum)
10759 : {
10760 4 : seen = true;
10761 4 : break;
10762 : }
10763 : }
10764 20 : if (!seen)
10765 16 : fksetcolsattnums[numcolsout++] = setcol_attnum;
10766 : }
10767 1805 : return numcolsout;
10768 : }
10769 :
10770 : /*
10771 : * addFkConstraint
10772 : * Install pg_constraint entries to implement a foreign key constraint.
10773 : * Caller must separately invoke addFkRecurseReferenced and
10774 : * addFkRecurseReferencing, as appropriate, to install pg_trigger entries
10775 : * and (for partitioned tables) recurse to partitions.
10776 : *
10777 : * fkside: the side of the FK (or both) to create. Caller should
10778 : * call addFkRecurseReferenced if this is addFkReferencedSide,
10779 : * addFkRecurseReferencing if it's addFkReferencingSide, or both if it's
10780 : * addFkBothSides.
10781 : * constraintname: the base name for the constraint being added,
10782 : * copied to fkconstraint->conname if the latter is not set
10783 : * fkconstraint: the constraint being added
10784 : * rel: the root referencing relation
10785 : * pkrel: the referenced relation; might be a partition, if recursing
10786 : * indexOid: the OID of the index (on pkrel) implementing this constraint
10787 : * parentConstr: the OID of a parent constraint; InvalidOid if this is a
10788 : * top-level constraint
10789 : * numfks: the number of columns in the foreign key
10790 : * pkattnum: the attnum array of referenced attributes
10791 : * fkattnum: the attnum array of referencing attributes
10792 : * pf/pp/ffeqoperators: OID array of operators between columns
10793 : * numfkdelsetcols: the number of columns in the ON DELETE SET NULL/DEFAULT
10794 : * (...) clause
10795 : * fkdelsetcols: the attnum array of the columns in the ON DELETE SET
10796 : * NULL/DEFAULT clause
10797 : * with_period: true if this is a temporal FK
10798 : */
10799 : static ObjectAddress
10800 2860 : addFkConstraint(addFkConstraintSides fkside,
10801 : char *constraintname, Constraint *fkconstraint,
10802 : Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
10803 : int numfks, int16 *pkattnum,
10804 : int16 *fkattnum, Oid *pfeqoperators, Oid *ppeqoperators,
10805 : Oid *ffeqoperators, int numfkdelsetcols, int16 *fkdelsetcols,
10806 : bool is_internal, bool with_period)
10807 : {
10808 : ObjectAddress address;
10809 : Oid constrOid;
10810 : char *conname;
10811 : bool conislocal;
10812 : int16 coninhcount;
10813 : bool connoinherit;
10814 :
10815 : /*
10816 : * Verify relkind for each referenced partition. At the top level, this
10817 : * is redundant with a previous check, but we need it when recursing.
10818 : */
10819 2860 : if (pkrel->rd_rel->relkind != RELKIND_RELATION &&
10820 589 : pkrel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
10821 0 : ereport(ERROR,
10822 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
10823 : errmsg("referenced relation \"%s\" is not a table",
10824 : RelationGetRelationName(pkrel))));
10825 :
10826 : /*
10827 : * Caller supplies us with a constraint name; however, it may be used in
10828 : * this partition, so come up with a different one in that case. Unless
10829 : * truncation to NAMEDATALEN dictates otherwise, the new name will be the
10830 : * supplied name with an underscore and digit(s) appended.
10831 : */
10832 2860 : if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
10833 : RelationGetRelid(rel),
10834 : constraintname))
10835 811 : conname = ChooseConstraintName(constraintname,
10836 : NULL,
10837 : "",
10838 811 : RelationGetNamespace(rel), NIL);
10839 : else
10840 2049 : conname = constraintname;
10841 :
10842 2860 : if (fkconstraint->conname == NULL)
10843 283 : fkconstraint->conname = pstrdup(conname);
10844 :
10845 2860 : if (OidIsValid(parentConstr))
10846 : {
10847 1367 : conislocal = false;
10848 1367 : coninhcount = 1;
10849 1367 : connoinherit = false;
10850 : }
10851 : else
10852 : {
10853 1493 : conislocal = true;
10854 1493 : coninhcount = 0;
10855 :
10856 : /*
10857 : * always inherit for partitioned tables, never for legacy inheritance
10858 : */
10859 1493 : connoinherit = rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE;
10860 : }
10861 :
10862 : /*
10863 : * Record the FK constraint in pg_constraint.
10864 : */
10865 2860 : constrOid = CreateConstraintEntry(conname,
10866 2860 : RelationGetNamespace(rel),
10867 : CONSTRAINT_FOREIGN,
10868 2860 : fkconstraint->deferrable,
10869 2860 : fkconstraint->initdeferred,
10870 2860 : fkconstraint->is_enforced,
10871 2860 : fkconstraint->initially_valid,
10872 : parentConstr,
10873 : RelationGetRelid(rel),
10874 : fkattnum,
10875 : numfks,
10876 : numfks,
10877 : InvalidOid, /* not a domain constraint */
10878 : indexOid,
10879 : RelationGetRelid(pkrel),
10880 : pkattnum,
10881 : pfeqoperators,
10882 : ppeqoperators,
10883 : ffeqoperators,
10884 : numfks,
10885 2860 : fkconstraint->fk_upd_action,
10886 2860 : fkconstraint->fk_del_action,
10887 : fkdelsetcols,
10888 : numfkdelsetcols,
10889 2860 : fkconstraint->fk_matchtype,
10890 : NULL, /* no exclusion constraint */
10891 : NULL, /* no check constraint */
10892 : NULL,
10893 : conislocal, /* islocal */
10894 : coninhcount, /* inhcount */
10895 : connoinherit, /* conNoInherit */
10896 : with_period, /* conPeriod */
10897 : is_internal); /* is_internal */
10898 :
10899 2860 : ObjectAddressSet(address, ConstraintRelationId, constrOid);
10900 :
10901 : /*
10902 : * In partitioning cases, create the dependency entries for this
10903 : * constraint. (For non-partitioned cases, relevant entries were created
10904 : * by CreateConstraintEntry.)
10905 : *
10906 : * On the referenced side, we need the constraint to have an internal
10907 : * dependency on its parent constraint; this means that this constraint
10908 : * cannot be dropped on its own -- only through the parent constraint. It
10909 : * also means the containing partition cannot be dropped on its own, but
10910 : * it can be detached, at which point this dependency is removed (after
10911 : * verifying that no rows are referenced via this FK.)
10912 : *
10913 : * When processing the referencing side, we link the constraint via the
10914 : * special partitioning dependencies: the parent constraint is the primary
10915 : * dependent, and the partition on which the foreign key exists is the
10916 : * secondary dependency. That way, this constraint is dropped if either
10917 : * of these objects is.
10918 : *
10919 : * Note that this is only necessary for the subsidiary pg_constraint rows
10920 : * in partitions; the topmost row doesn't need any of this.
10921 : */
10922 2860 : if (OidIsValid(parentConstr))
10923 : {
10924 : ObjectAddress referenced;
10925 :
10926 1367 : ObjectAddressSet(referenced, ConstraintRelationId, parentConstr);
10927 :
10928 : Assert(fkside != addFkBothSides);
10929 1367 : if (fkside == addFkReferencedSide)
10930 807 : recordDependencyOn(&address, &referenced, DEPENDENCY_INTERNAL);
10931 : else
10932 : {
10933 560 : recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_PRI);
10934 560 : ObjectAddressSet(referenced, RelationRelationId, RelationGetRelid(rel));
10935 560 : recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_SEC);
10936 : }
10937 : }
10938 :
10939 : /* make new constraint visible, in case we add more */
10940 2860 : CommandCounterIncrement();
10941 :
10942 2860 : return address;
10943 : }
10944 :
10945 : /*
10946 : * addFkRecurseReferenced
10947 : * Recursive helper for the referenced side of foreign key creation,
10948 : * which creates the action triggers and recurses
10949 : *
10950 : * If the referenced relation is a plain relation, create the necessary action
10951 : * triggers that implement the constraint. If the referenced relation is a
10952 : * partitioned table, then we create a pg_constraint row referencing the parent
10953 : * of the referencing side for it and recurse on this routine for each
10954 : * partition.
10955 : *
10956 : * fkconstraint: the constraint being added
10957 : * rel: the root referencing relation
10958 : * pkrel: the referenced relation; might be a partition, if recursing
10959 : * indexOid: the OID of the index (on pkrel) implementing this constraint
10960 : * parentConstr: the OID of a parent constraint; InvalidOid if this is a
10961 : * top-level constraint
10962 : * numfks: the number of columns in the foreign key
10963 : * pkattnum: the attnum array of referenced attributes
10964 : * fkattnum: the attnum array of referencing attributes
10965 : * numfkdelsetcols: the number of columns in the ON DELETE SET
10966 : * NULL/DEFAULT (...) clause
10967 : * fkdelsetcols: the attnum array of the columns in the ON DELETE SET
10968 : * NULL/DEFAULT clause
10969 : * pf/pp/ffeqoperators: OID array of operators between columns
10970 : * old_check_ok: true if this constraint replaces an existing one that
10971 : * was already validated (thus this one doesn't need validation)
10972 : * parentDelTrigger and parentUpdTrigger: when recursively called on a
10973 : * partition, the OIDs of the parent action triggers for DELETE and
10974 : * UPDATE respectively.
10975 : * with_period: true if this is a temporal FK
10976 : */
10977 : static void
10978 2372 : addFkRecurseReferenced(Constraint *fkconstraint, Relation rel,
10979 : Relation pkrel, Oid indexOid, Oid parentConstr,
10980 : int numfks,
10981 : int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators,
10982 : Oid *ppeqoperators, Oid *ffeqoperators,
10983 : int numfkdelsetcols, int16 *fkdelsetcols,
10984 : bool old_check_ok,
10985 : Oid parentDelTrigger, Oid parentUpdTrigger,
10986 : bool with_period)
10987 : {
10988 2372 : Oid deleteTriggerOid = InvalidOid,
10989 2372 : updateTriggerOid = InvalidOid;
10990 :
10991 : Assert(CheckRelationLockedByMe(pkrel, ShareRowExclusiveLock, true));
10992 : Assert(CheckRelationLockedByMe(rel, ShareRowExclusiveLock, true));
10993 :
10994 : /*
10995 : * Create action triggers to enforce the constraint, or skip them if the
10996 : * constraint is NOT ENFORCED.
10997 : */
10998 2372 : if (fkconstraint->is_enforced)
10999 2319 : createForeignKeyActionTriggers(RelationGetRelid(rel),
11000 : RelationGetRelid(pkrel),
11001 : fkconstraint,
11002 : parentConstr, indexOid,
11003 : parentDelTrigger, parentUpdTrigger,
11004 : &deleteTriggerOid, &updateTriggerOid);
11005 :
11006 : /*
11007 : * If the referenced table is partitioned, recurse on ourselves to handle
11008 : * each partition. We need one pg_constraint row created for each
11009 : * partition in addition to the pg_constraint row for the parent table.
11010 : */
11011 2372 : if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
11012 : {
11013 373 : PartitionDesc pd = RelationGetPartitionDesc(pkrel, true);
11014 :
11015 1048 : for (int i = 0; i < pd->nparts; i++)
11016 : {
11017 : Relation partRel;
11018 : AttrMap *map;
11019 : AttrNumber *mapped_pkattnum;
11020 : Oid partIndexId;
11021 : ObjectAddress address;
11022 :
11023 : /* XXX would it be better to acquire these locks beforehand? */
11024 675 : partRel = table_open(pd->oids[i], ShareRowExclusiveLock);
11025 :
11026 : /*
11027 : * Map the attribute numbers in the referenced side of the FK
11028 : * definition to match the partition's column layout.
11029 : */
11030 675 : map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
11031 : RelationGetDescr(pkrel),
11032 : false);
11033 675 : if (map)
11034 : {
11035 89 : mapped_pkattnum = palloc_array(AttrNumber, numfks);
11036 186 : for (int j = 0; j < numfks; j++)
11037 97 : mapped_pkattnum[j] = map->attnums[pkattnum[j] - 1];
11038 : }
11039 : else
11040 586 : mapped_pkattnum = pkattnum;
11041 :
11042 : /* Determine the index to use at this level */
11043 675 : partIndexId = index_get_partition(partRel, indexOid);
11044 675 : if (!OidIsValid(partIndexId))
11045 0 : elog(ERROR, "index for %u not found in partition %s",
11046 : indexOid, RelationGetRelationName(partRel));
11047 :
11048 : /* Create entry at this level ... */
11049 675 : address = addFkConstraint(addFkReferencedSide,
11050 : fkconstraint->conname, fkconstraint, rel,
11051 : partRel, partIndexId, parentConstr,
11052 : numfks, mapped_pkattnum,
11053 : fkattnum, pfeqoperators, ppeqoperators,
11054 : ffeqoperators, numfkdelsetcols,
11055 : fkdelsetcols, true, with_period);
11056 : /* ... and recurse to our children */
11057 675 : addFkRecurseReferenced(fkconstraint, rel, partRel,
11058 : partIndexId, address.objectId, numfks,
11059 : mapped_pkattnum, fkattnum,
11060 : pfeqoperators, ppeqoperators, ffeqoperators,
11061 : numfkdelsetcols, fkdelsetcols,
11062 : old_check_ok,
11063 : deleteTriggerOid, updateTriggerOid,
11064 : with_period);
11065 :
11066 : /* Done -- clean up (but keep the lock) */
11067 675 : table_close(partRel, NoLock);
11068 675 : if (map)
11069 : {
11070 89 : pfree(mapped_pkattnum);
11071 89 : free_attrmap(map);
11072 : }
11073 : }
11074 : }
11075 2372 : }
11076 :
11077 : /*
11078 : * addFkRecurseReferencing
11079 : * Recursive helper for the referencing side of foreign key creation,
11080 : * which creates the check triggers and recurses
11081 : *
11082 : * If the referencing relation is a plain relation, create the necessary check
11083 : * triggers that implement the constraint, and set up for Phase 3 constraint
11084 : * verification. If the referencing relation is a partitioned table, then
11085 : * we create a pg_constraint row for it and recurse on this routine for each
11086 : * partition.
11087 : *
11088 : * We assume that the referenced relation is locked against concurrent
11089 : * deletions. If it's a partitioned relation, every partition must be so
11090 : * locked.
11091 : *
11092 : * wqueue: the ALTER TABLE work queue; NULL when not running as part
11093 : * of an ALTER TABLE sequence.
11094 : * fkconstraint: the constraint being added
11095 : * rel: the referencing relation; might be a partition, if recursing
11096 : * pkrel: the root referenced relation
11097 : * indexOid: the OID of the index (on pkrel) implementing this constraint
11098 : * parentConstr: the OID of the parent constraint (there is always one)
11099 : * numfks: the number of columns in the foreign key
11100 : * pkattnum: the attnum array of referenced attributes
11101 : * fkattnum: the attnum array of referencing attributes
11102 : * pf/pp/ffeqoperators: OID array of operators between columns
11103 : * numfkdelsetcols: the number of columns in the ON DELETE SET NULL/DEFAULT
11104 : * (...) clause
11105 : * fkdelsetcols: the attnum array of the columns in the ON DELETE SET
11106 : * NULL/DEFAULT clause
11107 : * old_check_ok: true if this constraint replaces an existing one that
11108 : * was already validated (thus this one doesn't need validation)
11109 : * lockmode: the lockmode to acquire on partitions when recursing
11110 : * parentInsTrigger and parentUpdTrigger: when being recursively called on
11111 : * a partition, the OIDs of the parent check triggers for INSERT and
11112 : * UPDATE respectively.
11113 : * with_period: true if this is a temporal FK
11114 : */
11115 : static void
11116 2053 : addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
11117 : Relation pkrel, Oid indexOid, Oid parentConstr,
11118 : int numfks, int16 *pkattnum, int16 *fkattnum,
11119 : Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
11120 : int numfkdelsetcols, int16 *fkdelsetcols,
11121 : bool old_check_ok, LOCKMODE lockmode,
11122 : Oid parentInsTrigger, Oid parentUpdTrigger,
11123 : bool with_period)
11124 : {
11125 2053 : Oid insertTriggerOid = InvalidOid,
11126 2053 : updateTriggerOid = InvalidOid;
11127 :
11128 : Assert(OidIsValid(parentConstr));
11129 : Assert(CheckRelationLockedByMe(rel, ShareRowExclusiveLock, true));
11130 : Assert(CheckRelationLockedByMe(pkrel, ShareRowExclusiveLock, true));
11131 :
11132 2053 : if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
11133 0 : ereport(ERROR,
11134 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
11135 : errmsg("foreign key constraints are not supported on foreign tables")));
11136 :
11137 : /*
11138 : * Add check triggers if the constraint is ENFORCED, and if needed,
11139 : * schedule them to be checked in Phase 3.
11140 : *
11141 : * If the relation is partitioned, drill down to do it to its partitions.
11142 : */
11143 2053 : if (fkconstraint->is_enforced)
11144 2016 : createForeignKeyCheckTriggers(RelationGetRelid(rel),
11145 : RelationGetRelid(pkrel),
11146 : fkconstraint,
11147 : parentConstr,
11148 : indexOid,
11149 : parentInsTrigger, parentUpdTrigger,
11150 : &insertTriggerOid, &updateTriggerOid);
11151 :
11152 2053 : if (rel->rd_rel->relkind == RELKIND_RELATION)
11153 : {
11154 : /*
11155 : * Tell Phase 3 to check that the constraint is satisfied by existing
11156 : * rows. We can skip this during table creation, when constraint is
11157 : * specified as NOT ENFORCED, or when requested explicitly by
11158 : * specifying NOT VALID in an ADD FOREIGN KEY command, and when we're
11159 : * recreating a constraint following a SET DATA TYPE operation that
11160 : * did not impugn its validity.
11161 : */
11162 1730 : if (wqueue && !old_check_ok && !fkconstraint->skip_validation &&
11163 603 : fkconstraint->is_enforced)
11164 : {
11165 : NewConstraint *newcon;
11166 : AlteredTableInfo *tab;
11167 :
11168 603 : tab = ATGetQueueEntry(wqueue, rel);
11169 :
11170 603 : newcon = palloc0_object(NewConstraint);
11171 603 : newcon->name = get_constraint_name(parentConstr);
11172 603 : newcon->contype = CONSTR_FOREIGN;
11173 603 : newcon->refrelid = RelationGetRelid(pkrel);
11174 603 : newcon->refindid = indexOid;
11175 603 : newcon->conid = parentConstr;
11176 603 : newcon->conwithperiod = fkconstraint->fk_with_period;
11177 603 : newcon->qual = (Node *) fkconstraint;
11178 :
11179 603 : tab->constraints = lappend(tab->constraints, newcon);
11180 : }
11181 : }
11182 323 : else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
11183 : {
11184 323 : PartitionDesc pd = RelationGetPartitionDesc(rel, true);
11185 : Relation trigrel;
11186 :
11187 : /*
11188 : * Triggers of the foreign keys will be manipulated a bunch of times
11189 : * in the loop below. To avoid repeatedly opening/closing the trigger
11190 : * catalog relation, we open it here and pass it to the subroutines
11191 : * called below.
11192 : */
11193 323 : trigrel = table_open(TriggerRelationId, RowExclusiveLock);
11194 :
11195 : /*
11196 : * Recurse to take appropriate action on each partition; either we
11197 : * find an existing constraint to reparent to ours, or we create a new
11198 : * one.
11199 : */
11200 608 : for (int i = 0; i < pd->nparts; i++)
11201 : {
11202 289 : Relation partition = table_open(pd->oids[i], lockmode);
11203 : List *partFKs;
11204 : AttrMap *attmap;
11205 : AttrNumber mapped_fkattnum[INDEX_MAX_KEYS];
11206 : bool attached;
11207 : ObjectAddress address;
11208 :
11209 289 : CheckAlterTableIsSafe(partition);
11210 :
11211 285 : attmap = build_attrmap_by_name(RelationGetDescr(partition),
11212 : RelationGetDescr(rel),
11213 : false);
11214 714 : for (int j = 0; j < numfks; j++)
11215 429 : mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
11216 :
11217 : /* Check whether an existing constraint can be repurposed */
11218 285 : partFKs = copyObject(RelationGetFKeyList(partition));
11219 285 : attached = false;
11220 581 : foreach_node(ForeignKeyCacheInfo, fk, partFKs)
11221 : {
11222 19 : if (tryAttachPartitionForeignKey(wqueue,
11223 : fk,
11224 : partition,
11225 : parentConstr,
11226 : numfks,
11227 : mapped_fkattnum,
11228 : pkattnum,
11229 : pfeqoperators,
11230 : insertTriggerOid,
11231 : updateTriggerOid,
11232 : trigrel))
11233 : {
11234 8 : attached = true;
11235 8 : break;
11236 : }
11237 : }
11238 285 : if (attached)
11239 : {
11240 8 : table_close(partition, NoLock);
11241 8 : continue;
11242 : }
11243 :
11244 : /*
11245 : * No luck finding a good constraint to reuse; create our own.
11246 : */
11247 277 : address = addFkConstraint(addFkReferencingSide,
11248 : fkconstraint->conname, fkconstraint,
11249 : partition, pkrel, indexOid, parentConstr,
11250 : numfks, pkattnum,
11251 : mapped_fkattnum, pfeqoperators,
11252 : ppeqoperators, ffeqoperators,
11253 : numfkdelsetcols, fkdelsetcols, true,
11254 : with_period);
11255 :
11256 : /* call ourselves to finalize the creation and we're done */
11257 277 : addFkRecurseReferencing(wqueue, fkconstraint, partition, pkrel,
11258 : indexOid,
11259 : address.objectId,
11260 : numfks,
11261 : pkattnum,
11262 : mapped_fkattnum,
11263 : pfeqoperators,
11264 : ppeqoperators,
11265 : ffeqoperators,
11266 : numfkdelsetcols,
11267 : fkdelsetcols,
11268 : old_check_ok,
11269 : lockmode,
11270 : insertTriggerOid,
11271 : updateTriggerOid,
11272 : with_period);
11273 :
11274 277 : table_close(partition, NoLock);
11275 : }
11276 :
11277 319 : table_close(trigrel, RowExclusiveLock);
11278 : }
11279 2049 : }
11280 :
11281 : /*
11282 : * CloneForeignKeyConstraints
11283 : * Clone foreign keys from a partitioned table to a newly acquired
11284 : * partition.
11285 : *
11286 : * partitionRel is a partition of parentRel, so we can be certain that it has
11287 : * the same columns with the same datatypes. The columns may be in different
11288 : * order, though.
11289 : *
11290 : * wqueue must be passed to set up phase 3 constraint checking, unless the
11291 : * referencing-side partition is known to be empty (such as in CREATE TABLE /
11292 : * PARTITION OF).
11293 : */
11294 : static void
11295 8120 : CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
11296 : Relation partitionRel)
11297 : {
11298 : /* This only works for declarative partitioning */
11299 : Assert(parentRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
11300 :
11301 : /*
11302 : * First, clone constraints where the parent is on the referencing side.
11303 : */
11304 8120 : CloneFkReferencing(wqueue, parentRel, partitionRel);
11305 :
11306 : /*
11307 : * Clone constraints for which the parent is on the referenced side.
11308 : */
11309 8108 : CloneFkReferenced(parentRel, partitionRel);
11310 8108 : }
11311 :
11312 : /*
11313 : * CloneFkReferenced
11314 : * Subroutine for CloneForeignKeyConstraints
11315 : *
11316 : * Find all the FKs that have the parent relation on the referenced side;
11317 : * clone those constraints to the given partition. This is to be called
11318 : * when the partition is being created or attached.
11319 : *
11320 : * This recurses to partitions, if the relation being attached is partitioned.
11321 : * Recursion is done by calling addFkRecurseReferenced.
11322 : */
11323 : static void
11324 8108 : CloneFkReferenced(Relation parentRel, Relation partitionRel)
11325 : {
11326 : Relation pg_constraint;
11327 : AttrMap *attmap;
11328 : ListCell *cell;
11329 : SysScanDesc scan;
11330 : ScanKeyData key[2];
11331 : HeapTuple tuple;
11332 8108 : List *clone = NIL;
11333 : Relation trigrel;
11334 :
11335 : /*
11336 : * Search for any constraints where this partition's parent is in the
11337 : * referenced side. However, we must not clone any constraint whose
11338 : * parent constraint is also going to be cloned, to avoid duplicates. So
11339 : * do it in two steps: first construct the list of constraints to clone,
11340 : * then go over that list cloning those whose parents are not in the list.
11341 : * (We must not rely on the parent being seen first, since the catalog
11342 : * scan could return children first.)
11343 : */
11344 8108 : pg_constraint = table_open(ConstraintRelationId, RowShareLock);
11345 8108 : ScanKeyInit(&key[0],
11346 : Anum_pg_constraint_confrelid, BTEqualStrategyNumber,
11347 : F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(parentRel)));
11348 8108 : ScanKeyInit(&key[1],
11349 : Anum_pg_constraint_contype, BTEqualStrategyNumber,
11350 : F_CHAREQ, CharGetDatum(CONSTRAINT_FOREIGN));
11351 : /* This is a seqscan, as we don't have a usable index ... */
11352 8108 : scan = systable_beginscan(pg_constraint, InvalidOid, true,
11353 : NULL, 2, key);
11354 8388 : while ((tuple = systable_getnext(scan)) != NULL)
11355 : {
11356 280 : Form_pg_constraint constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
11357 :
11358 280 : clone = lappend_oid(clone, constrForm->oid);
11359 : }
11360 8108 : systable_endscan(scan);
11361 8108 : table_close(pg_constraint, RowShareLock);
11362 :
11363 : /*
11364 : * Triggers of the foreign keys will be manipulated a bunch of times in
11365 : * the loop below. To avoid repeatedly opening/closing the trigger
11366 : * catalog relation, we open it here and pass it to the subroutines called
11367 : * below.
11368 : */
11369 8108 : trigrel = table_open(TriggerRelationId, RowExclusiveLock);
11370 :
11371 8108 : attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
11372 : RelationGetDescr(parentRel),
11373 : false);
11374 8388 : foreach(cell, clone)
11375 : {
11376 280 : Oid constrOid = lfirst_oid(cell);
11377 : Form_pg_constraint constrForm;
11378 : Relation fkRel;
11379 : Oid indexOid;
11380 : Oid partIndexId;
11381 : int numfks;
11382 : AttrNumber conkey[INDEX_MAX_KEYS];
11383 : AttrNumber mapped_confkey[INDEX_MAX_KEYS];
11384 : AttrNumber confkey[INDEX_MAX_KEYS];
11385 : Oid conpfeqop[INDEX_MAX_KEYS];
11386 : Oid conppeqop[INDEX_MAX_KEYS];
11387 : Oid conffeqop[INDEX_MAX_KEYS];
11388 : int numfkdelsetcols;
11389 : AttrNumber confdelsetcols[INDEX_MAX_KEYS];
11390 : Constraint *fkconstraint;
11391 : ObjectAddress address;
11392 280 : Oid deleteTriggerOid = InvalidOid,
11393 280 : updateTriggerOid = InvalidOid;
11394 :
11395 280 : tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
11396 280 : if (!HeapTupleIsValid(tuple))
11397 0 : elog(ERROR, "cache lookup failed for constraint %u", constrOid);
11398 280 : constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
11399 :
11400 : /*
11401 : * As explained above: don't try to clone a constraint for which we're
11402 : * going to clone the parent.
11403 : */
11404 280 : if (list_member_oid(clone, constrForm->conparentid))
11405 : {
11406 148 : ReleaseSysCache(tuple);
11407 148 : continue;
11408 : }
11409 :
11410 : /* We need the same lock level that CreateTrigger will acquire */
11411 132 : fkRel = table_open(constrForm->conrelid, ShareRowExclusiveLock);
11412 :
11413 132 : indexOid = constrForm->conindid;
11414 132 : DeconstructFkConstraintRow(tuple,
11415 : &numfks,
11416 : conkey,
11417 : confkey,
11418 : conpfeqop,
11419 : conppeqop,
11420 : conffeqop,
11421 : &numfkdelsetcols,
11422 : confdelsetcols);
11423 :
11424 292 : for (int i = 0; i < numfks; i++)
11425 160 : mapped_confkey[i] = attmap->attnums[confkey[i] - 1];
11426 :
11427 132 : fkconstraint = makeNode(Constraint);
11428 132 : fkconstraint->contype = CONSTRAINT_FOREIGN;
11429 132 : fkconstraint->conname = NameStr(constrForm->conname);
11430 132 : fkconstraint->deferrable = constrForm->condeferrable;
11431 132 : fkconstraint->initdeferred = constrForm->condeferred;
11432 132 : fkconstraint->location = -1;
11433 132 : fkconstraint->pktable = NULL;
11434 : /* ->fk_attrs determined below */
11435 132 : fkconstraint->pk_attrs = NIL;
11436 132 : fkconstraint->fk_matchtype = constrForm->confmatchtype;
11437 132 : fkconstraint->fk_upd_action = constrForm->confupdtype;
11438 132 : fkconstraint->fk_del_action = constrForm->confdeltype;
11439 132 : fkconstraint->fk_del_set_cols = NIL;
11440 132 : fkconstraint->old_conpfeqop = NIL;
11441 132 : fkconstraint->old_pktable_oid = InvalidOid;
11442 132 : fkconstraint->is_enforced = constrForm->conenforced;
11443 132 : fkconstraint->skip_validation = false;
11444 132 : fkconstraint->initially_valid = constrForm->convalidated;
11445 :
11446 : /* set up colnames that are used to generate the constraint name */
11447 292 : for (int i = 0; i < numfks; i++)
11448 : {
11449 : Form_pg_attribute att;
11450 :
11451 160 : att = TupleDescAttr(RelationGetDescr(fkRel),
11452 160 : conkey[i] - 1);
11453 160 : fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
11454 160 : makeString(NameStr(att->attname)));
11455 : }
11456 :
11457 : /*
11458 : * Add the new foreign key constraint pointing to the new partition.
11459 : * Because this new partition appears in the referenced side of the
11460 : * constraint, we don't need to set up for Phase 3 check.
11461 : */
11462 132 : partIndexId = index_get_partition(partitionRel, indexOid);
11463 132 : if (!OidIsValid(partIndexId))
11464 0 : elog(ERROR, "index for %u not found in partition %s",
11465 : indexOid, RelationGetRelationName(partitionRel));
11466 :
11467 : /*
11468 : * Get the "action" triggers belonging to the constraint to pass as
11469 : * parent OIDs for similar triggers that will be created on the
11470 : * partition in addFkRecurseReferenced().
11471 : */
11472 132 : if (constrForm->conenforced)
11473 128 : GetForeignKeyActionTriggers(trigrel, constrOid,
11474 : constrForm->confrelid, constrForm->conrelid,
11475 : &deleteTriggerOid, &updateTriggerOid);
11476 :
11477 : /* Add this constraint ... */
11478 132 : address = addFkConstraint(addFkReferencedSide,
11479 : fkconstraint->conname, fkconstraint, fkRel,
11480 : partitionRel, partIndexId, constrOid,
11481 : numfks, mapped_confkey,
11482 : conkey, conpfeqop, conppeqop, conffeqop,
11483 : numfkdelsetcols, confdelsetcols, false,
11484 132 : constrForm->conperiod);
11485 : /* ... and recurse */
11486 132 : addFkRecurseReferenced(fkconstraint,
11487 : fkRel,
11488 : partitionRel,
11489 : partIndexId,
11490 : address.objectId,
11491 : numfks,
11492 : mapped_confkey,
11493 : conkey,
11494 : conpfeqop,
11495 : conppeqop,
11496 : conffeqop,
11497 : numfkdelsetcols,
11498 : confdelsetcols,
11499 : true,
11500 : deleteTriggerOid,
11501 : updateTriggerOid,
11502 132 : constrForm->conperiod);
11503 :
11504 132 : table_close(fkRel, NoLock);
11505 132 : ReleaseSysCache(tuple);
11506 : }
11507 :
11508 8108 : table_close(trigrel, RowExclusiveLock);
11509 8108 : }
11510 :
11511 : /*
11512 : * CloneFkReferencing
11513 : * Subroutine for CloneForeignKeyConstraints
11514 : *
11515 : * For each FK constraint of the parent relation in the given list, find an
11516 : * equivalent constraint in its partition relation that can be reparented;
11517 : * if one cannot be found, create a new constraint in the partition as its
11518 : * child.
11519 : *
11520 : * If wqueue is given, it is used to set up phase-3 verification for each
11521 : * cloned constraint; omit it if such verification is not needed
11522 : * (example: the partition is being created anew).
11523 : */
11524 : static void
11525 8120 : CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
11526 : {
11527 : AttrMap *attmap;
11528 : List *partFKs;
11529 8120 : List *clone = NIL;
11530 : ListCell *cell;
11531 : Relation trigrel;
11532 :
11533 : /* obtain a list of constraints that we need to clone */
11534 8979 : foreach(cell, RelationGetFKeyList(parentRel))
11535 : {
11536 863 : ForeignKeyCacheInfo *fk = lfirst(cell);
11537 :
11538 : /*
11539 : * Refuse to attach a table as partition that this partitioned table
11540 : * already has a foreign key to. This isn't useful schema, which is
11541 : * proven by the fact that there have been no user complaints that
11542 : * it's already impossible to achieve this in the opposite direction,
11543 : * i.e., creating a foreign key that references a partition. This
11544 : * restriction allows us to dodge some complexities around
11545 : * pg_constraint and pg_trigger row creations that would be needed
11546 : * during ATTACH/DETACH for this kind of relationship.
11547 : */
11548 863 : if (fk->confrelid == RelationGetRelid(partRel))
11549 4 : ereport(ERROR,
11550 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
11551 : errmsg("cannot attach table \"%s\" as a partition because it is referenced by foreign key \"%s\"",
11552 : RelationGetRelationName(partRel),
11553 : get_constraint_name(fk->conoid))));
11554 :
11555 859 : clone = lappend_oid(clone, fk->conoid);
11556 : }
11557 :
11558 : /*
11559 : * Silently do nothing if there's nothing to do. In particular, this
11560 : * avoids throwing a spurious error for foreign tables.
11561 : */
11562 8116 : if (clone == NIL)
11563 7761 : return;
11564 :
11565 355 : if (partRel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
11566 0 : ereport(ERROR,
11567 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
11568 : errmsg("foreign key constraints are not supported on foreign tables")));
11569 :
11570 : /*
11571 : * Triggers of the foreign keys will be manipulated a bunch of times in
11572 : * the loop below. To avoid repeatedly opening/closing the trigger
11573 : * catalog relation, we open it here and pass it to the subroutines called
11574 : * below.
11575 : */
11576 355 : trigrel = table_open(TriggerRelationId, RowExclusiveLock);
11577 :
11578 : /*
11579 : * The constraint key may differ, if the columns in the partition are
11580 : * different. This map is used to convert them.
11581 : */
11582 355 : attmap = build_attrmap_by_name(RelationGetDescr(partRel),
11583 : RelationGetDescr(parentRel),
11584 : false);
11585 :
11586 355 : partFKs = copyObject(RelationGetFKeyList(partRel));
11587 :
11588 1206 : foreach(cell, clone)
11589 : {
11590 859 : Oid parentConstrOid = lfirst_oid(cell);
11591 : Form_pg_constraint constrForm;
11592 : Relation pkrel;
11593 : HeapTuple tuple;
11594 : int numfks;
11595 : AttrNumber conkey[INDEX_MAX_KEYS];
11596 : AttrNumber mapped_conkey[INDEX_MAX_KEYS];
11597 : AttrNumber confkey[INDEX_MAX_KEYS];
11598 : Oid conpfeqop[INDEX_MAX_KEYS];
11599 : Oid conppeqop[INDEX_MAX_KEYS];
11600 : Oid conffeqop[INDEX_MAX_KEYS];
11601 : int numfkdelsetcols;
11602 : AttrNumber confdelsetcols[INDEX_MAX_KEYS];
11603 : Constraint *fkconstraint;
11604 : bool attached;
11605 : Oid indexOid;
11606 : ObjectAddress address;
11607 : ListCell *lc;
11608 859 : Oid insertTriggerOid = InvalidOid,
11609 859 : updateTriggerOid = InvalidOid;
11610 : bool with_period;
11611 :
11612 859 : tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(parentConstrOid));
11613 859 : if (!HeapTupleIsValid(tuple))
11614 0 : elog(ERROR, "cache lookup failed for constraint %u",
11615 : parentConstrOid);
11616 859 : constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
11617 :
11618 : /* Don't clone constraints whose parents are being cloned */
11619 859 : if (list_member_oid(clone, constrForm->conparentid))
11620 : {
11621 472 : ReleaseSysCache(tuple);
11622 572 : continue;
11623 : }
11624 :
11625 : /*
11626 : * Need to prevent concurrent deletions. If pkrel is a partitioned
11627 : * relation, that means to lock all partitions.
11628 : */
11629 387 : pkrel = table_open(constrForm->confrelid, ShareRowExclusiveLock);
11630 387 : if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
11631 164 : (void) find_all_inheritors(RelationGetRelid(pkrel),
11632 : ShareRowExclusiveLock, NULL);
11633 :
11634 387 : DeconstructFkConstraintRow(tuple, &numfks, conkey, confkey,
11635 : conpfeqop, conppeqop, conffeqop,
11636 : &numfkdelsetcols, confdelsetcols);
11637 930 : for (int i = 0; i < numfks; i++)
11638 543 : mapped_conkey[i] = attmap->attnums[conkey[i] - 1];
11639 :
11640 : /*
11641 : * Get the "check" triggers belonging to the constraint, if it is
11642 : * ENFORCED, to pass as parent OIDs for similar triggers that will be
11643 : * created on the partition in addFkRecurseReferencing(). They are
11644 : * also passed to tryAttachPartitionForeignKey() below to simply
11645 : * assign as parents to the partition's existing "check" triggers,
11646 : * that is, if the corresponding constraints is deemed attachable to
11647 : * the parent constraint.
11648 : */
11649 387 : if (constrForm->conenforced)
11650 379 : GetForeignKeyCheckTriggers(trigrel, constrForm->oid,
11651 : constrForm->confrelid, constrForm->conrelid,
11652 : &insertTriggerOid, &updateTriggerOid);
11653 :
11654 : /*
11655 : * Before creating a new constraint, see whether any existing FKs are
11656 : * fit for the purpose. If one is, attach the parent constraint to
11657 : * it, and don't clone anything. This way we avoid the expensive
11658 : * verification step and don't end up with a duplicate FK, and we
11659 : * don't need to recurse to partitions for this constraint.
11660 : */
11661 387 : attached = false;
11662 447 : foreach(lc, partFKs)
11663 : {
11664 164 : ForeignKeyCacheInfo *fk = lfirst_node(ForeignKeyCacheInfo, lc);
11665 :
11666 164 : if (tryAttachPartitionForeignKey(wqueue,
11667 : fk,
11668 : partRel,
11669 : parentConstrOid,
11670 : numfks,
11671 : mapped_conkey,
11672 : confkey,
11673 : conpfeqop,
11674 : insertTriggerOid,
11675 : updateTriggerOid,
11676 : trigrel))
11677 : {
11678 100 : attached = true;
11679 100 : table_close(pkrel, NoLock);
11680 100 : break;
11681 : }
11682 : }
11683 383 : if (attached)
11684 : {
11685 100 : ReleaseSysCache(tuple);
11686 100 : continue;
11687 : }
11688 :
11689 : /* No dice. Set up to create our own constraint */
11690 283 : fkconstraint = makeNode(Constraint);
11691 283 : fkconstraint->contype = CONSTRAINT_FOREIGN;
11692 : /* ->conname determined below */
11693 283 : fkconstraint->deferrable = constrForm->condeferrable;
11694 283 : fkconstraint->initdeferred = constrForm->condeferred;
11695 283 : fkconstraint->location = -1;
11696 283 : fkconstraint->pktable = NULL;
11697 : /* ->fk_attrs determined below */
11698 283 : fkconstraint->pk_attrs = NIL;
11699 283 : fkconstraint->fk_matchtype = constrForm->confmatchtype;
11700 283 : fkconstraint->fk_upd_action = constrForm->confupdtype;
11701 283 : fkconstraint->fk_del_action = constrForm->confdeltype;
11702 283 : fkconstraint->fk_del_set_cols = NIL;
11703 283 : fkconstraint->old_conpfeqop = NIL;
11704 283 : fkconstraint->old_pktable_oid = InvalidOid;
11705 283 : fkconstraint->is_enforced = constrForm->conenforced;
11706 283 : fkconstraint->skip_validation = false;
11707 283 : fkconstraint->initially_valid = constrForm->convalidated;
11708 646 : for (int i = 0; i < numfks; i++)
11709 : {
11710 : Form_pg_attribute att;
11711 :
11712 363 : att = TupleDescAttr(RelationGetDescr(partRel),
11713 363 : mapped_conkey[i] - 1);
11714 363 : fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
11715 363 : makeString(NameStr(att->attname)));
11716 : }
11717 :
11718 283 : indexOid = constrForm->conindid;
11719 283 : with_period = constrForm->conperiod;
11720 :
11721 : /* Create the pg_constraint entry at this level */
11722 283 : address = addFkConstraint(addFkReferencingSide,
11723 283 : NameStr(constrForm->conname), fkconstraint,
11724 : partRel, pkrel, indexOid, parentConstrOid,
11725 : numfks, confkey,
11726 : mapped_conkey, conpfeqop,
11727 : conppeqop, conffeqop,
11728 : numfkdelsetcols, confdelsetcols,
11729 : false, with_period);
11730 :
11731 : /* Done with the cloned constraint's tuple */
11732 283 : ReleaseSysCache(tuple);
11733 :
11734 : /* Create the check triggers, and recurse to partitions, if any */
11735 283 : addFkRecurseReferencing(wqueue,
11736 : fkconstraint,
11737 : partRel,
11738 : pkrel,
11739 : indexOid,
11740 : address.objectId,
11741 : numfks,
11742 : confkey,
11743 : mapped_conkey,
11744 : conpfeqop,
11745 : conppeqop,
11746 : conffeqop,
11747 : numfkdelsetcols,
11748 : confdelsetcols,
11749 : false, /* no old check exists */
11750 : AccessExclusiveLock,
11751 : insertTriggerOid,
11752 : updateTriggerOid,
11753 : with_period);
11754 279 : table_close(pkrel, NoLock);
11755 : }
11756 :
11757 347 : table_close(trigrel, RowExclusiveLock);
11758 : }
11759 :
11760 : /*
11761 : * When the parent of a partition receives [the referencing side of] a foreign
11762 : * key, we must propagate that foreign key to the partition. However, the
11763 : * partition might already have an equivalent foreign key; this routine
11764 : * compares the given ForeignKeyCacheInfo (in the partition) to the FK defined
11765 : * by the other parameters. If they are equivalent, create the link between
11766 : * the two constraints and return true.
11767 : *
11768 : * If the given FK does not match the one defined by rest of the params,
11769 : * return false.
11770 : */
11771 : static bool
11772 183 : tryAttachPartitionForeignKey(List **wqueue,
11773 : ForeignKeyCacheInfo *fk,
11774 : Relation partition,
11775 : Oid parentConstrOid,
11776 : int numfks,
11777 : AttrNumber *mapped_conkey,
11778 : AttrNumber *confkey,
11779 : Oid *conpfeqop,
11780 : Oid parentInsTrigger,
11781 : Oid parentUpdTrigger,
11782 : Relation trigrel)
11783 : {
11784 : HeapTuple parentConstrTup;
11785 : Form_pg_constraint parentConstr;
11786 : HeapTuple partcontup;
11787 : Form_pg_constraint partConstr;
11788 :
11789 183 : parentConstrTup = SearchSysCache1(CONSTROID,
11790 : ObjectIdGetDatum(parentConstrOid));
11791 183 : if (!HeapTupleIsValid(parentConstrTup))
11792 0 : elog(ERROR, "cache lookup failed for constraint %u", parentConstrOid);
11793 183 : parentConstr = (Form_pg_constraint) GETSTRUCT(parentConstrTup);
11794 :
11795 : /*
11796 : * Do some quick & easy initial checks. If any of these fail, we cannot
11797 : * use this constraint.
11798 : */
11799 183 : if (fk->confrelid != parentConstr->confrelid || fk->nkeys != numfks)
11800 : {
11801 0 : ReleaseSysCache(parentConstrTup);
11802 0 : return false;
11803 : }
11804 505 : for (int i = 0; i < numfks; i++)
11805 : {
11806 324 : if (fk->conkey[i] != mapped_conkey[i] ||
11807 322 : fk->confkey[i] != confkey[i] ||
11808 322 : fk->conpfeqop[i] != conpfeqop[i])
11809 : {
11810 2 : ReleaseSysCache(parentConstrTup);
11811 2 : return false;
11812 : }
11813 : }
11814 :
11815 : /* Looks good so far; perform more extensive checks. */
11816 181 : partcontup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
11817 181 : if (!HeapTupleIsValid(partcontup))
11818 0 : elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
11819 181 : partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
11820 :
11821 : /*
11822 : * An error should be raised if the constraint enforceability is
11823 : * different. Returning false without raising an error, as we do for other
11824 : * attributes, could lead to a duplicate constraint with the same
11825 : * enforceability as the parent. While this may be acceptable, it may not
11826 : * be ideal. Therefore, it's better to raise an error and allow the user
11827 : * to correct the enforceability before proceeding.
11828 : */
11829 181 : if (partConstr->conenforced != parentConstr->conenforced)
11830 4 : ereport(ERROR,
11831 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
11832 : errmsg("constraint \"%s\" enforceability conflicts with constraint \"%s\" on relation \"%s\"",
11833 : NameStr(parentConstr->conname),
11834 : NameStr(partConstr->conname),
11835 : RelationGetRelationName(partition))));
11836 :
11837 177 : if (OidIsValid(partConstr->conparentid) ||
11838 158 : partConstr->condeferrable != parentConstr->condeferrable ||
11839 140 : partConstr->condeferred != parentConstr->condeferred ||
11840 140 : partConstr->confupdtype != parentConstr->confupdtype ||
11841 117 : partConstr->confdeltype != parentConstr->confdeltype ||
11842 117 : partConstr->confmatchtype != parentConstr->confmatchtype)
11843 : {
11844 69 : ReleaseSysCache(parentConstrTup);
11845 69 : ReleaseSysCache(partcontup);
11846 69 : return false;
11847 : }
11848 :
11849 108 : ReleaseSysCache(parentConstrTup);
11850 108 : ReleaseSysCache(partcontup);
11851 :
11852 : /* Looks good! Attach this constraint. */
11853 108 : AttachPartitionForeignKey(wqueue, partition, fk->conoid,
11854 : parentConstrOid, parentInsTrigger,
11855 : parentUpdTrigger, trigrel);
11856 :
11857 108 : return true;
11858 : }
11859 :
11860 : /*
11861 : * AttachPartitionForeignKey
11862 : *
11863 : * The subroutine for tryAttachPartitionForeignKey performs the final tasks of
11864 : * attaching the constraint, removing redundant triggers and entries from
11865 : * pg_constraint, and setting the constraint's parent.
11866 : */
11867 : static void
11868 108 : AttachPartitionForeignKey(List **wqueue,
11869 : Relation partition,
11870 : Oid partConstrOid,
11871 : Oid parentConstrOid,
11872 : Oid parentInsTrigger,
11873 : Oid parentUpdTrigger,
11874 : Relation trigrel)
11875 : {
11876 : HeapTuple parentConstrTup;
11877 : Form_pg_constraint parentConstr;
11878 : HeapTuple partcontup;
11879 : Form_pg_constraint partConstr;
11880 : bool queueValidation;
11881 : Oid partConstrFrelid;
11882 : Oid partConstrRelid;
11883 : bool parentConstrIsEnforced;
11884 :
11885 : /* Fetch the parent constraint tuple */
11886 108 : parentConstrTup = SearchSysCache1(CONSTROID,
11887 : ObjectIdGetDatum(parentConstrOid));
11888 108 : if (!HeapTupleIsValid(parentConstrTup))
11889 0 : elog(ERROR, "cache lookup failed for constraint %u", parentConstrOid);
11890 108 : parentConstr = (Form_pg_constraint) GETSTRUCT(parentConstrTup);
11891 108 : parentConstrIsEnforced = parentConstr->conenforced;
11892 :
11893 : /* Fetch the child constraint tuple */
11894 108 : partcontup = SearchSysCache1(CONSTROID,
11895 : ObjectIdGetDatum(partConstrOid));
11896 108 : if (!HeapTupleIsValid(partcontup))
11897 0 : elog(ERROR, "cache lookup failed for constraint %u", partConstrOid);
11898 108 : partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
11899 108 : partConstrFrelid = partConstr->confrelid;
11900 108 : partConstrRelid = partConstr->conrelid;
11901 :
11902 : /*
11903 : * If the referenced table is partitioned, then the partition we're
11904 : * attaching now has extra pg_constraint rows and action triggers that are
11905 : * no longer needed. Remove those.
11906 : */
11907 108 : if (get_rel_relkind(partConstrFrelid) == RELKIND_PARTITIONED_TABLE)
11908 : {
11909 24 : Relation pg_constraint = table_open(ConstraintRelationId, RowShareLock);
11910 :
11911 24 : RemoveInheritedConstraint(pg_constraint, trigrel, partConstrOid,
11912 : partConstrRelid);
11913 :
11914 24 : table_close(pg_constraint, RowShareLock);
11915 : }
11916 :
11917 : /*
11918 : * Will we need to validate this constraint? A valid parent constraint
11919 : * implies that all child constraints have been validated, so if this one
11920 : * isn't, we must trigger phase 3 validation.
11921 : */
11922 108 : queueValidation = parentConstr->convalidated && !partConstr->convalidated;
11923 :
11924 108 : ReleaseSysCache(partcontup);
11925 108 : ReleaseSysCache(parentConstrTup);
11926 :
11927 : /*
11928 : * The action triggers in the new partition become redundant -- the parent
11929 : * table already has equivalent ones, and those will be able to reach the
11930 : * partition. Remove the ones in the partition. We identify them because
11931 : * they have our constraint OID, as well as being on the referenced rel.
11932 : */
11933 108 : DropForeignKeyConstraintTriggers(trigrel, partConstrOid, partConstrFrelid,
11934 : partConstrRelid);
11935 :
11936 108 : ConstraintSetParentConstraint(partConstrOid, parentConstrOid,
11937 : RelationGetRelid(partition));
11938 :
11939 : /*
11940 : * Like the constraint, attach partition's "check" triggers to the
11941 : * corresponding parent triggers if the constraint is ENFORCED. NOT
11942 : * ENFORCED constraints do not have these triggers.
11943 : */
11944 108 : if (parentConstrIsEnforced)
11945 : {
11946 : Oid insertTriggerOid,
11947 : updateTriggerOid;
11948 :
11949 100 : GetForeignKeyCheckTriggers(trigrel,
11950 : partConstrOid, partConstrFrelid, partConstrRelid,
11951 : &insertTriggerOid, &updateTriggerOid);
11952 : Assert(OidIsValid(insertTriggerOid) && OidIsValid(parentInsTrigger));
11953 100 : TriggerSetParentTrigger(trigrel, insertTriggerOid, parentInsTrigger,
11954 : RelationGetRelid(partition));
11955 : Assert(OidIsValid(updateTriggerOid) && OidIsValid(parentUpdTrigger));
11956 100 : TriggerSetParentTrigger(trigrel, updateTriggerOid, parentUpdTrigger,
11957 : RelationGetRelid(partition));
11958 : }
11959 :
11960 : /*
11961 : * We updated this pg_constraint row above to set its parent; validating
11962 : * it will cause its convalidated flag to change, so we need CCI here. In
11963 : * addition, we need it unconditionally for the rare case where the parent
11964 : * table has *two* identical constraints; when reaching this function for
11965 : * the second one, we must have made our changes visible, otherwise we
11966 : * would try to attach both to this one.
11967 : */
11968 108 : CommandCounterIncrement();
11969 :
11970 : /* If validation is needed, put it in the queue now. */
11971 108 : if (queueValidation)
11972 : {
11973 : Relation conrel;
11974 : Oid confrelid;
11975 :
11976 12 : conrel = table_open(ConstraintRelationId, RowExclusiveLock);
11977 :
11978 12 : partcontup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(partConstrOid));
11979 12 : if (!HeapTupleIsValid(partcontup))
11980 0 : elog(ERROR, "cache lookup failed for constraint %u", partConstrOid);
11981 :
11982 12 : confrelid = ((Form_pg_constraint) GETSTRUCT(partcontup))->confrelid;
11983 :
11984 : /* Use the same lock as for AT_ValidateConstraint */
11985 12 : QueueFKConstraintValidation(wqueue, conrel, partition, confrelid,
11986 : partcontup, ShareUpdateExclusiveLock);
11987 12 : ReleaseSysCache(partcontup);
11988 12 : table_close(conrel, RowExclusiveLock);
11989 : }
11990 108 : }
11991 :
11992 : /*
11993 : * RemoveInheritedConstraint
11994 : *
11995 : * Removes the constraint and its associated trigger from the specified
11996 : * relation, which inherited the given constraint.
11997 : */
11998 : static void
11999 24 : RemoveInheritedConstraint(Relation conrel, Relation trigrel, Oid conoid,
12000 : Oid conrelid)
12001 : {
12002 : ObjectAddresses *objs;
12003 : HeapTuple consttup;
12004 : ScanKeyData key;
12005 : SysScanDesc scan;
12006 : HeapTuple trigtup;
12007 :
12008 24 : ScanKeyInit(&key,
12009 : Anum_pg_constraint_conrelid,
12010 : BTEqualStrategyNumber, F_OIDEQ,
12011 : ObjectIdGetDatum(conrelid));
12012 :
12013 24 : scan = systable_beginscan(conrel,
12014 : ConstraintRelidTypidNameIndexId,
12015 : true, NULL, 1, &key);
12016 24 : objs = new_object_addresses();
12017 216 : while ((consttup = systable_getnext(scan)) != NULL)
12018 : {
12019 192 : Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(consttup);
12020 :
12021 192 : if (conform->conparentid != conoid)
12022 140 : continue;
12023 : else
12024 : {
12025 : ObjectAddress addr;
12026 : SysScanDesc scan2;
12027 : ScanKeyData key2;
12028 : int n PG_USED_FOR_ASSERTS_ONLY;
12029 :
12030 52 : ObjectAddressSet(addr, ConstraintRelationId, conform->oid);
12031 52 : add_exact_object_address(&addr, objs);
12032 :
12033 : /*
12034 : * First we must delete the dependency record that binds the
12035 : * constraint records together.
12036 : */
12037 52 : n = deleteDependencyRecordsForSpecific(ConstraintRelationId,
12038 : conform->oid,
12039 : DEPENDENCY_INTERNAL,
12040 : ConstraintRelationId,
12041 : conoid);
12042 : Assert(n == 1); /* actually only one is expected */
12043 :
12044 : /*
12045 : * Now search for the triggers for this constraint and set them up
12046 : * for deletion too
12047 : */
12048 52 : ScanKeyInit(&key2,
12049 : Anum_pg_trigger_tgconstraint,
12050 : BTEqualStrategyNumber, F_OIDEQ,
12051 : ObjectIdGetDatum(conform->oid));
12052 52 : scan2 = systable_beginscan(trigrel, TriggerConstraintIndexId,
12053 : true, NULL, 1, &key2);
12054 156 : while ((trigtup = systable_getnext(scan2)) != NULL)
12055 : {
12056 104 : ObjectAddressSet(addr, TriggerRelationId,
12057 : ((Form_pg_trigger) GETSTRUCT(trigtup))->oid);
12058 104 : add_exact_object_address(&addr, objs);
12059 : }
12060 52 : systable_endscan(scan2);
12061 : }
12062 : }
12063 : /* make the dependency deletions visible */
12064 24 : CommandCounterIncrement();
12065 24 : performMultipleDeletions(objs, DROP_RESTRICT,
12066 : PERFORM_DELETION_INTERNAL);
12067 24 : systable_endscan(scan);
12068 24 : }
12069 :
12070 : /*
12071 : * DropForeignKeyConstraintTriggers
12072 : *
12073 : * The subroutine for tryAttachPartitionForeignKey handles the deletion of
12074 : * action triggers for the foreign key constraint.
12075 : *
12076 : * If valid confrelid and conrelid values are not provided, the respective
12077 : * trigger check will be skipped, and the trigger will be considered for
12078 : * removal.
12079 : */
12080 : static void
12081 160 : DropForeignKeyConstraintTriggers(Relation trigrel, Oid conoid, Oid confrelid,
12082 : Oid conrelid)
12083 : {
12084 : ScanKeyData key;
12085 : SysScanDesc scan;
12086 : HeapTuple trigtup;
12087 :
12088 160 : ScanKeyInit(&key,
12089 : Anum_pg_trigger_tgconstraint,
12090 : BTEqualStrategyNumber, F_OIDEQ,
12091 : ObjectIdGetDatum(conoid));
12092 160 : scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
12093 : NULL, 1, &key);
12094 696 : while ((trigtup = systable_getnext(scan)) != NULL)
12095 : {
12096 536 : Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
12097 : ObjectAddress trigger;
12098 :
12099 : /* Invalid if trigger is not for a referential integrity constraint */
12100 536 : if (!OidIsValid(trgform->tgconstrrelid))
12101 200 : continue;
12102 536 : if (OidIsValid(conrelid) && trgform->tgconstrrelid != conrelid)
12103 200 : continue;
12104 336 : if (OidIsValid(confrelid) && trgform->tgrelid != confrelid)
12105 0 : continue;
12106 :
12107 : /* We should be dropping trigger related to foreign key constraint */
12108 : Assert(trgform->tgfoid == F_RI_FKEY_CHECK_INS ||
12109 : trgform->tgfoid == F_RI_FKEY_CHECK_UPD ||
12110 : trgform->tgfoid == F_RI_FKEY_CASCADE_DEL ||
12111 : trgform->tgfoid == F_RI_FKEY_CASCADE_UPD ||
12112 : trgform->tgfoid == F_RI_FKEY_RESTRICT_DEL ||
12113 : trgform->tgfoid == F_RI_FKEY_RESTRICT_UPD ||
12114 : trgform->tgfoid == F_RI_FKEY_SETNULL_DEL ||
12115 : trgform->tgfoid == F_RI_FKEY_SETNULL_UPD ||
12116 : trgform->tgfoid == F_RI_FKEY_SETDEFAULT_DEL ||
12117 : trgform->tgfoid == F_RI_FKEY_SETDEFAULT_UPD ||
12118 : trgform->tgfoid == F_RI_FKEY_NOACTION_DEL ||
12119 : trgform->tgfoid == F_RI_FKEY_NOACTION_UPD);
12120 :
12121 : /*
12122 : * The constraint is originally set up to contain this trigger as an
12123 : * implementation object, so there's a dependency record that links
12124 : * the two; however, since the trigger is no longer needed, we remove
12125 : * the dependency link in order to be able to drop the trigger while
12126 : * keeping the constraint intact.
12127 : */
12128 336 : deleteDependencyRecordsFor(TriggerRelationId,
12129 : trgform->oid,
12130 : false);
12131 : /* make dependency deletion visible to performDeletion */
12132 336 : CommandCounterIncrement();
12133 336 : ObjectAddressSet(trigger, TriggerRelationId,
12134 : trgform->oid);
12135 336 : performDeletion(&trigger, DROP_RESTRICT, 0);
12136 : /* make trigger drop visible, in case the loop iterates */
12137 336 : CommandCounterIncrement();
12138 : }
12139 :
12140 160 : systable_endscan(scan);
12141 160 : }
12142 :
12143 : /*
12144 : * GetForeignKeyActionTriggers
12145 : * Returns delete and update "action" triggers of the given relation
12146 : * belonging to the given constraint
12147 : */
12148 : static void
12149 128 : GetForeignKeyActionTriggers(Relation trigrel,
12150 : Oid conoid, Oid confrelid, Oid conrelid,
12151 : Oid *deleteTriggerOid,
12152 : Oid *updateTriggerOid)
12153 : {
12154 : ScanKeyData key;
12155 : SysScanDesc scan;
12156 : HeapTuple trigtup;
12157 :
12158 128 : *deleteTriggerOid = *updateTriggerOid = InvalidOid;
12159 128 : ScanKeyInit(&key,
12160 : Anum_pg_trigger_tgconstraint,
12161 : BTEqualStrategyNumber, F_OIDEQ,
12162 : ObjectIdGetDatum(conoid));
12163 :
12164 128 : scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
12165 : NULL, 1, &key);
12166 268 : while ((trigtup = systable_getnext(scan)) != NULL)
12167 : {
12168 268 : Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
12169 :
12170 268 : if (trgform->tgconstrrelid != conrelid)
12171 10 : continue;
12172 258 : if (trgform->tgrelid != confrelid)
12173 0 : continue;
12174 : /* Only ever look at "action" triggers on the PK side. */
12175 258 : if (RI_FKey_trigger_type(trgform->tgfoid) != RI_TRIGGER_PK)
12176 2 : continue;
12177 256 : if (TRIGGER_FOR_DELETE(trgform->tgtype))
12178 : {
12179 : Assert(*deleteTriggerOid == InvalidOid);
12180 128 : *deleteTriggerOid = trgform->oid;
12181 : }
12182 128 : else if (TRIGGER_FOR_UPDATE(trgform->tgtype))
12183 : {
12184 : Assert(*updateTriggerOid == InvalidOid);
12185 128 : *updateTriggerOid = trgform->oid;
12186 : }
12187 : #ifndef USE_ASSERT_CHECKING
12188 : /* In an assert-enabled build, continue looking to find duplicates */
12189 256 : if (OidIsValid(*deleteTriggerOid) && OidIsValid(*updateTriggerOid))
12190 128 : break;
12191 : #endif
12192 : }
12193 :
12194 128 : if (!OidIsValid(*deleteTriggerOid))
12195 0 : elog(ERROR, "could not find ON DELETE action trigger of foreign key constraint %u",
12196 : conoid);
12197 128 : if (!OidIsValid(*updateTriggerOid))
12198 0 : elog(ERROR, "could not find ON UPDATE action trigger of foreign key constraint %u",
12199 : conoid);
12200 :
12201 128 : systable_endscan(scan);
12202 128 : }
12203 :
12204 : /*
12205 : * GetForeignKeyCheckTriggers
12206 : * Returns insert and update "check" triggers of the given relation
12207 : * belonging to the given constraint
12208 : */
12209 : static void
12210 551 : GetForeignKeyCheckTriggers(Relation trigrel,
12211 : Oid conoid, Oid confrelid, Oid conrelid,
12212 : Oid *insertTriggerOid,
12213 : Oid *updateTriggerOid)
12214 : {
12215 : ScanKeyData key;
12216 : SysScanDesc scan;
12217 : HeapTuple trigtup;
12218 :
12219 551 : *insertTriggerOid = *updateTriggerOid = InvalidOid;
12220 551 : ScanKeyInit(&key,
12221 : Anum_pg_trigger_tgconstraint,
12222 : BTEqualStrategyNumber, F_OIDEQ,
12223 : ObjectIdGetDatum(conoid));
12224 :
12225 551 : scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
12226 : NULL, 1, &key);
12227 1772 : while ((trigtup = systable_getnext(scan)) != NULL)
12228 : {
12229 1772 : Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
12230 :
12231 1772 : if (trgform->tgconstrrelid != confrelid)
12232 600 : continue;
12233 1172 : if (trgform->tgrelid != conrelid)
12234 0 : continue;
12235 : /* Only ever look at "check" triggers on the FK side. */
12236 1172 : if (RI_FKey_trigger_type(trgform->tgfoid) != RI_TRIGGER_FK)
12237 70 : continue;
12238 1102 : if (TRIGGER_FOR_INSERT(trgform->tgtype))
12239 : {
12240 : Assert(*insertTriggerOid == InvalidOid);
12241 551 : *insertTriggerOid = trgform->oid;
12242 : }
12243 551 : else if (TRIGGER_FOR_UPDATE(trgform->tgtype))
12244 : {
12245 : Assert(*updateTriggerOid == InvalidOid);
12246 551 : *updateTriggerOid = trgform->oid;
12247 : }
12248 : #ifndef USE_ASSERT_CHECKING
12249 : /* In an assert-enabled build, continue looking to find duplicates. */
12250 1102 : if (OidIsValid(*insertTriggerOid) && OidIsValid(*updateTriggerOid))
12251 551 : break;
12252 : #endif
12253 : }
12254 :
12255 551 : if (!OidIsValid(*insertTriggerOid))
12256 0 : elog(ERROR, "could not find ON INSERT check triggers of foreign key constraint %u",
12257 : conoid);
12258 551 : if (!OidIsValid(*updateTriggerOid))
12259 0 : elog(ERROR, "could not find ON UPDATE check triggers of foreign key constraint %u",
12260 : conoid);
12261 :
12262 551 : systable_endscan(scan);
12263 551 : }
12264 :
12265 : /*
12266 : * ALTER TABLE ALTER CONSTRAINT
12267 : *
12268 : * Update the attributes of a constraint.
12269 : *
12270 : * Currently works for Foreign Key, Check, and not null constraints.
12271 : *
12272 : * If the constraint is modified, returns its address; otherwise, return
12273 : * InvalidObjectAddress.
12274 : */
12275 : static ObjectAddress
12276 296 : ATExecAlterConstraint(List **wqueue, Relation rel, ATAlterConstraint *cmdcon,
12277 : bool recurse, LOCKMODE lockmode)
12278 : {
12279 : Relation conrel;
12280 : Relation tgrel;
12281 : SysScanDesc scan;
12282 : ScanKeyData skey[3];
12283 : HeapTuple contuple;
12284 : Form_pg_constraint currcon;
12285 : ObjectAddress address;
12286 :
12287 : /*
12288 : * Disallow altering ONLY a partitioned table, as it would make no sense.
12289 : * This is okay for legacy inheritance.
12290 : */
12291 296 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
12292 0 : ereport(ERROR,
12293 : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
12294 : errmsg("constraint must be altered in child tables too"),
12295 : errhint("Do not specify the ONLY keyword."));
12296 :
12297 :
12298 296 : conrel = table_open(ConstraintRelationId, RowExclusiveLock);
12299 296 : tgrel = table_open(TriggerRelationId, RowExclusiveLock);
12300 :
12301 : /*
12302 : * Find and check the target constraint
12303 : */
12304 296 : ScanKeyInit(&skey[0],
12305 : Anum_pg_constraint_conrelid,
12306 : BTEqualStrategyNumber, F_OIDEQ,
12307 : ObjectIdGetDatum(RelationGetRelid(rel)));
12308 296 : ScanKeyInit(&skey[1],
12309 : Anum_pg_constraint_contypid,
12310 : BTEqualStrategyNumber, F_OIDEQ,
12311 : ObjectIdGetDatum(InvalidOid));
12312 296 : ScanKeyInit(&skey[2],
12313 : Anum_pg_constraint_conname,
12314 : BTEqualStrategyNumber, F_NAMEEQ,
12315 296 : CStringGetDatum(cmdcon->conname));
12316 296 : scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId,
12317 : true, NULL, 3, skey);
12318 :
12319 : /* There can be at most one matching row */
12320 296 : if (!HeapTupleIsValid(contuple = systable_getnext(scan)))
12321 4 : ereport(ERROR,
12322 : (errcode(ERRCODE_UNDEFINED_OBJECT),
12323 : errmsg("constraint \"%s\" of relation \"%s\" does not exist",
12324 : cmdcon->conname, RelationGetRelationName(rel))));
12325 :
12326 292 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
12327 292 : if (cmdcon->alterDeferrability && currcon->contype != CONSTRAINT_FOREIGN)
12328 0 : ereport(ERROR,
12329 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
12330 : errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint",
12331 : cmdcon->conname, RelationGetRelationName(rel))));
12332 292 : if (cmdcon->alterEnforceability &&
12333 156 : (currcon->contype != CONSTRAINT_FOREIGN && currcon->contype != CONSTRAINT_CHECK))
12334 8 : ereport(ERROR,
12335 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
12336 : errmsg("cannot alter enforceability of constraint \"%s\" of relation \"%s\"",
12337 : cmdcon->conname, RelationGetRelationName(rel)),
12338 : errhint("Only foreign key and check constraints can change enforceability.")));
12339 284 : if (cmdcon->alterInheritability &&
12340 64 : currcon->contype != CONSTRAINT_NOTNULL)
12341 16 : ereport(ERROR,
12342 : errcode(ERRCODE_WRONG_OBJECT_TYPE),
12343 : errmsg("constraint \"%s\" of relation \"%s\" is not a not-null constraint",
12344 : cmdcon->conname, RelationGetRelationName(rel)));
12345 268 : if (cmdcon->alterInheritability &&
12346 48 : cmdcon->noinherit && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
12347 4 : ereport(ERROR,
12348 : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
12349 : errmsg("not-null constraint \"%s\" on partitioned table \"%s\" cannot be NO INHERIT",
12350 : cmdcon->conname, RelationGetRelationName(rel)));
12351 :
12352 : /* Refuse to modify inheritability of inherited constraints */
12353 264 : if (cmdcon->alterInheritability &&
12354 44 : cmdcon->noinherit && currcon->coninhcount > 0)
12355 4 : ereport(ERROR,
12356 : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
12357 : errmsg("cannot alter inherited constraint \"%s\" on relation \"%s\"",
12358 : NameStr(currcon->conname),
12359 : RelationGetRelationName(rel)));
12360 :
12361 : /*
12362 : * If it's not the topmost constraint, raise an error.
12363 : *
12364 : * Altering a non-topmost constraint leaves some triggers untouched, since
12365 : * they are not directly connected to this constraint; also, pg_dump would
12366 : * ignore the deferrability status of the individual constraint, since it
12367 : * only dumps topmost constraints. Avoid these problems by refusing this
12368 : * operation and telling the user to alter the parent constraint instead.
12369 : */
12370 260 : if (OidIsValid(currcon->conparentid))
12371 : {
12372 : HeapTuple tp;
12373 8 : Oid parent = currcon->conparentid;
12374 8 : char *ancestorname = NULL;
12375 8 : char *ancestortable = NULL;
12376 :
12377 : /* Loop to find the topmost constraint */
12378 16 : while (HeapTupleIsValid(tp = SearchSysCache1(CONSTROID, ObjectIdGetDatum(parent))))
12379 : {
12380 16 : Form_pg_constraint contup = (Form_pg_constraint) GETSTRUCT(tp);
12381 :
12382 : /* If no parent, this is the constraint we want */
12383 16 : if (!OidIsValid(contup->conparentid))
12384 : {
12385 8 : ancestorname = pstrdup(NameStr(contup->conname));
12386 8 : ancestortable = get_rel_name(contup->conrelid);
12387 8 : ReleaseSysCache(tp);
12388 8 : break;
12389 : }
12390 :
12391 8 : parent = contup->conparentid;
12392 8 : ReleaseSysCache(tp);
12393 : }
12394 :
12395 8 : ereport(ERROR,
12396 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
12397 : errmsg("cannot alter constraint \"%s\" on relation \"%s\"",
12398 : cmdcon->conname, RelationGetRelationName(rel)),
12399 : ancestorname && ancestortable ?
12400 : errdetail("Constraint \"%s\" is derived from constraint \"%s\" of relation \"%s\".",
12401 : cmdcon->conname, ancestorname, ancestortable) : 0,
12402 : errhint("You may alter the constraint it derives from instead.")));
12403 : }
12404 :
12405 252 : address = InvalidObjectAddress;
12406 :
12407 : /*
12408 : * Do the actual catalog work, and recurse if necessary.
12409 : */
12410 252 : if (ATExecAlterConstraintInternal(wqueue, cmdcon, conrel, tgrel, rel,
12411 : contuple, recurse, lockmode))
12412 232 : ObjectAddressSet(address, ConstraintRelationId, currcon->oid);
12413 :
12414 240 : systable_endscan(scan);
12415 :
12416 240 : table_close(tgrel, RowExclusiveLock);
12417 240 : table_close(conrel, RowExclusiveLock);
12418 :
12419 240 : return address;
12420 : }
12421 :
12422 : /*
12423 : * A subroutine of ATExecAlterConstraint that calls the respective routines for
12424 : * altering constraint's enforceability, deferrability or inheritability.
12425 : */
12426 : static bool
12427 252 : ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon,
12428 : Relation conrel, Relation tgrel, Relation rel,
12429 : HeapTuple contuple, bool recurse,
12430 : LOCKMODE lockmode)
12431 : {
12432 : Form_pg_constraint currcon;
12433 252 : bool changed = false;
12434 252 : List *otherrelids = NIL;
12435 :
12436 252 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
12437 :
12438 : /*
12439 : * Do the catalog work for the enforceability or deferrability change,
12440 : * recurse if necessary.
12441 : *
12442 : * Note that even if deferrability is requested to be altered along with
12443 : * enforceability, we don't need to explicitly update multiple entries in
12444 : * pg_trigger related to deferrability.
12445 : *
12446 : * Modifying foreign key enforceability involves either creating or
12447 : * dropping the trigger, during which the deferrability setting will be
12448 : * adjusted automatically.
12449 : */
12450 252 : if (cmdcon->alterEnforceability)
12451 : {
12452 148 : if (currcon->contype == CONSTRAINT_FOREIGN)
12453 60 : changed = ATExecAlterFKConstrEnforceability(wqueue, cmdcon, conrel, tgrel,
12454 : currcon->conrelid,
12455 : currcon->confrelid,
12456 : contuple, lockmode,
12457 : InvalidOid, InvalidOid,
12458 : InvalidOid, InvalidOid);
12459 88 : else if (currcon->contype == CONSTRAINT_CHECK)
12460 88 : changed = ATExecAlterCheckConstrEnforceability(wqueue, cmdcon, conrel,
12461 : contuple, recurse, false,
12462 : lockmode);
12463 : }
12464 168 : else if (cmdcon->alterDeferrability &&
12465 64 : ATExecAlterConstrDeferrability(wqueue, cmdcon, conrel, tgrel, rel,
12466 : contuple, recurse, &otherrelids,
12467 : lockmode))
12468 : {
12469 : /*
12470 : * AlterConstrUpdateConstraintEntry already invalidated relcache for
12471 : * the relations having the constraint itself; here we also invalidate
12472 : * for relations that have any triggers that are part of the
12473 : * constraint.
12474 : */
12475 204 : foreach_oid(relid, otherrelids)
12476 76 : CacheInvalidateRelcacheByRelid(relid);
12477 :
12478 64 : changed = true;
12479 : }
12480 :
12481 : /*
12482 : * Do the catalog work for the inheritability change.
12483 : */
12484 280 : if (cmdcon->alterInheritability &&
12485 40 : ATExecAlterConstrInheritability(wqueue, cmdcon, conrel, rel, contuple,
12486 : lockmode))
12487 36 : changed = true;
12488 :
12489 240 : return changed;
12490 : }
12491 :
12492 : /*
12493 : * Returns true if the foreign key constraint's enforceability is altered.
12494 : *
12495 : * Depending on whether the constraint is being set to ENFORCED or NOT
12496 : * ENFORCED, it creates or drops the trigger accordingly.
12497 : *
12498 : * Note that we must recurse even when trying to change a constraint to not
12499 : * enforced if it is already not enforced, in case descendant constraints
12500 : * might be enforced and need to be changed to not enforced. Conversely, we
12501 : * should do nothing if a constraint is being set to enforced and is already
12502 : * enforced, as descendant constraints cannot be different in that case.
12503 : */
12504 : static bool
12505 128 : ATExecAlterFKConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon,
12506 : Relation conrel, Relation tgrel,
12507 : Oid fkrelid, Oid pkrelid,
12508 : HeapTuple contuple, LOCKMODE lockmode,
12509 : Oid ReferencedParentDelTrigger,
12510 : Oid ReferencedParentUpdTrigger,
12511 : Oid ReferencingParentInsTrigger,
12512 : Oid ReferencingParentUpdTrigger)
12513 : {
12514 : Form_pg_constraint currcon;
12515 : Oid conoid;
12516 : Relation rel;
12517 128 : bool changed = false;
12518 :
12519 : /* Since this function recurses, it could be driven to stack overflow */
12520 128 : check_stack_depth();
12521 :
12522 : Assert(cmdcon->alterEnforceability);
12523 :
12524 128 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
12525 128 : conoid = currcon->oid;
12526 :
12527 : /* Should be foreign key constraint */
12528 : Assert(currcon->contype == CONSTRAINT_FOREIGN);
12529 :
12530 128 : rel = table_open(currcon->conrelid, lockmode);
12531 :
12532 128 : if (currcon->conenforced != cmdcon->is_enforced)
12533 : {
12534 124 : AlterConstrUpdateConstraintEntry(cmdcon, conrel, contuple);
12535 124 : changed = true;
12536 : }
12537 :
12538 : /* Drop triggers */
12539 128 : if (!cmdcon->is_enforced)
12540 : {
12541 : /*
12542 : * When setting a constraint to NOT ENFORCED, the constraint triggers
12543 : * need to be dropped. Therefore, we must process the child relations
12544 : * first, followed by the parent, to account for dependencies.
12545 : */
12546 92 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
12547 40 : get_rel_relkind(currcon->confrelid) == RELKIND_PARTITIONED_TABLE)
12548 12 : AlterFKConstrEnforceabilityRecurse(wqueue, cmdcon, conrel, tgrel,
12549 : fkrelid, pkrelid, contuple,
12550 : lockmode, InvalidOid, InvalidOid,
12551 : InvalidOid, InvalidOid);
12552 :
12553 : /* Drop all the triggers */
12554 52 : DropForeignKeyConstraintTriggers(tgrel, conoid, InvalidOid, InvalidOid);
12555 : }
12556 76 : else if (changed) /* Create triggers */
12557 : {
12558 76 : Oid ReferencedDelTriggerOid = InvalidOid,
12559 76 : ReferencedUpdTriggerOid = InvalidOid,
12560 76 : ReferencingInsTriggerOid = InvalidOid,
12561 76 : ReferencingUpdTriggerOid = InvalidOid;
12562 :
12563 : /* Prepare the minimal information required for trigger creation. */
12564 76 : Constraint *fkconstraint = makeNode(Constraint);
12565 :
12566 76 : fkconstraint->conname = pstrdup(NameStr(currcon->conname));
12567 76 : fkconstraint->fk_matchtype = currcon->confmatchtype;
12568 76 : fkconstraint->fk_upd_action = currcon->confupdtype;
12569 76 : fkconstraint->fk_del_action = currcon->confdeltype;
12570 76 : fkconstraint->deferrable = currcon->condeferrable;
12571 76 : fkconstraint->initdeferred = currcon->condeferred;
12572 :
12573 : /* Create referenced triggers */
12574 76 : if (currcon->conrelid == fkrelid)
12575 48 : createForeignKeyActionTriggers(currcon->conrelid,
12576 : currcon->confrelid,
12577 : fkconstraint,
12578 : conoid,
12579 : currcon->conindid,
12580 : ReferencedParentDelTrigger,
12581 : ReferencedParentUpdTrigger,
12582 : &ReferencedDelTriggerOid,
12583 : &ReferencedUpdTriggerOid);
12584 :
12585 : /* Create referencing triggers */
12586 76 : if (currcon->confrelid == pkrelid)
12587 64 : createForeignKeyCheckTriggers(currcon->conrelid,
12588 : pkrelid,
12589 : fkconstraint,
12590 : conoid,
12591 : currcon->conindid,
12592 : ReferencingParentInsTrigger,
12593 : ReferencingParentUpdTrigger,
12594 : &ReferencingInsTriggerOid,
12595 : &ReferencingUpdTriggerOid);
12596 :
12597 : /*
12598 : * Tell Phase 3 to check that the constraint is satisfied by existing
12599 : * rows. Only applies to leaf partitions, and (for constraints that
12600 : * reference a partitioned table) only if this is not one of the
12601 : * pg_constraint rows that exist solely to support action triggers.
12602 : */
12603 76 : if (rel->rd_rel->relkind == RELKIND_RELATION &&
12604 64 : currcon->confrelid == pkrelid)
12605 : {
12606 : AlteredTableInfo *tab;
12607 : NewConstraint *newcon;
12608 :
12609 52 : newcon = palloc0_object(NewConstraint);
12610 52 : newcon->name = fkconstraint->conname;
12611 52 : newcon->contype = CONSTR_FOREIGN;
12612 52 : newcon->refrelid = currcon->confrelid;
12613 52 : newcon->refindid = currcon->conindid;
12614 52 : newcon->conid = currcon->oid;
12615 52 : newcon->qual = (Node *) fkconstraint;
12616 :
12617 : /* Find or create work queue entry for this table */
12618 52 : tab = ATGetQueueEntry(wqueue, rel);
12619 52 : tab->constraints = lappend(tab->constraints, newcon);
12620 : }
12621 :
12622 : /*
12623 : * If the table at either end of the constraint is partitioned, we
12624 : * need to recurse and create triggers for each constraint that is a
12625 : * child of this one.
12626 : */
12627 140 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
12628 64 : get_rel_relkind(currcon->confrelid) == RELKIND_PARTITIONED_TABLE)
12629 20 : AlterFKConstrEnforceabilityRecurse(wqueue, cmdcon, conrel, tgrel,
12630 : fkrelid, pkrelid, contuple,
12631 : lockmode,
12632 : ReferencedDelTriggerOid,
12633 : ReferencedUpdTriggerOid,
12634 : ReferencingInsTriggerOid,
12635 : ReferencingUpdTriggerOid);
12636 : }
12637 :
12638 128 : table_close(rel, NoLock);
12639 :
12640 128 : return changed;
12641 : }
12642 :
12643 : /*
12644 : * Returns true if the CHECK constraint's enforceability is altered.
12645 : */
12646 : static bool
12647 236 : ATExecAlterCheckConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon,
12648 : Relation conrel, HeapTuple contuple,
12649 : bool recurse, bool recursing, LOCKMODE lockmode)
12650 : {
12651 : Form_pg_constraint currcon;
12652 : Relation rel;
12653 236 : bool changed = false;
12654 236 : List *children = NIL;
12655 :
12656 : /* Since this function recurses, it could be driven to stack overflow */
12657 236 : check_stack_depth();
12658 :
12659 : Assert(cmdcon->alterEnforceability);
12660 :
12661 236 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
12662 :
12663 : Assert(currcon->contype == CONSTRAINT_CHECK);
12664 :
12665 : /*
12666 : * Parent relation already locked by caller, children will be locked by
12667 : * find_all_inheritors. So NoLock is fine here.
12668 : */
12669 236 : rel = table_open(currcon->conrelid, NoLock);
12670 :
12671 236 : if (currcon->conenforced != cmdcon->is_enforced)
12672 : {
12673 196 : AlterConstrUpdateConstraintEntry(cmdcon, conrel, contuple);
12674 196 : changed = true;
12675 : }
12676 :
12677 : /*
12678 : * Note that we must recurse even when trying to change a check constraint
12679 : * to not enforced if it is already not enforced, in case descendant
12680 : * constraints might be enforced and need to be changed to not enforced.
12681 : * Conversely, we should do nothing if a constraint is being set to
12682 : * enforced and is already enforced, as descendant constraints cannot be
12683 : * different in that case.
12684 : */
12685 236 : if (!cmdcon->is_enforced || changed)
12686 : {
12687 : /*
12688 : * If we're recursing, the parent has already done this, so skip it.
12689 : * Also, if the constraint is a NO INHERIT constraint, we shouldn't
12690 : * try to look for it in the children.
12691 : */
12692 220 : if (!recursing && !currcon->connoinherit)
12693 84 : children = find_all_inheritors(RelationGetRelid(rel),
12694 : lockmode, NULL);
12695 :
12696 664 : foreach_oid(childoid, children)
12697 : {
12698 240 : if (childoid == RelationGetRelid(rel))
12699 84 : continue;
12700 :
12701 : /*
12702 : * If we are told not to recurse, there had better not be any
12703 : * child tables, because we can't change constraint enforceability
12704 : * on the parent unless we have changed enforceability for all
12705 : * child.
12706 : */
12707 156 : if (!recurse)
12708 8 : ereport(ERROR,
12709 : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
12710 : errmsg("constraint must be altered on child tables too"),
12711 : errhint("Do not specify the ONLY keyword."));
12712 :
12713 148 : AlterCheckConstrEnforceabilityRecurse(wqueue, cmdcon, conrel,
12714 : childoid, false, true,
12715 : lockmode);
12716 : }
12717 : }
12718 :
12719 : /*
12720 : * Tell Phase 3 to check that the constraint is satisfied by existing
12721 : * rows. We only need do this when altering the constraint from NOT
12722 : * ENFORCED to ENFORCED.
12723 : */
12724 228 : if (rel->rd_rel->relkind == RELKIND_RELATION &&
12725 180 : !currcon->conenforced &&
12726 128 : cmdcon->is_enforced)
12727 : {
12728 : AlteredTableInfo *tab;
12729 : NewConstraint *newcon;
12730 : Datum val;
12731 : char *conbin;
12732 :
12733 116 : newcon = palloc0_object(NewConstraint);
12734 116 : newcon->name = pstrdup(NameStr(currcon->conname));
12735 116 : newcon->contype = CONSTR_CHECK;
12736 :
12737 116 : val = SysCacheGetAttrNotNull(CONSTROID, contuple,
12738 : Anum_pg_constraint_conbin);
12739 116 : conbin = TextDatumGetCString(val);
12740 116 : newcon->qual = expand_generated_columns_in_expr(stringToNode(conbin), rel, 1);
12741 :
12742 : /* Find or create work queue entry for this table */
12743 116 : tab = ATGetQueueEntry(wqueue, rel);
12744 116 : tab->constraints = lappend(tab->constraints, newcon);
12745 : }
12746 :
12747 228 : table_close(rel, NoLock);
12748 :
12749 228 : return changed;
12750 : }
12751 :
12752 : /*
12753 : * Invokes ATExecAlterCheckConstrEnforceability for each CHECK constraint that
12754 : * is a child of the specified constraint.
12755 : *
12756 : * We rely on the parent and child tables having identical CHECK constraint
12757 : * names to retrieve the child's pg_constraint tuple.
12758 : *
12759 : * The arguments to this function have the same meaning as the arguments to
12760 : * ATExecAlterCheckConstrEnforceability.
12761 : */
12762 : static void
12763 148 : AlterCheckConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
12764 : Relation conrel, Oid conrelid,
12765 : bool recurse, bool recursing,
12766 : LOCKMODE lockmode)
12767 : {
12768 : SysScanDesc pscan;
12769 : HeapTuple childtup;
12770 : ScanKeyData skey[3];
12771 :
12772 148 : ScanKeyInit(&skey[0],
12773 : Anum_pg_constraint_conrelid,
12774 : BTEqualStrategyNumber, F_OIDEQ,
12775 : ObjectIdGetDatum(conrelid));
12776 148 : ScanKeyInit(&skey[1],
12777 : Anum_pg_constraint_contypid,
12778 : BTEqualStrategyNumber, F_OIDEQ,
12779 : ObjectIdGetDatum(InvalidOid));
12780 148 : ScanKeyInit(&skey[2],
12781 : Anum_pg_constraint_conname,
12782 : BTEqualStrategyNumber, F_NAMEEQ,
12783 148 : CStringGetDatum(cmdcon->conname));
12784 :
12785 148 : pscan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId, true,
12786 : NULL, 3, skey);
12787 :
12788 148 : if (!HeapTupleIsValid(childtup = systable_getnext(pscan)))
12789 0 : ereport(ERROR,
12790 : errcode(ERRCODE_UNDEFINED_OBJECT),
12791 : errmsg("constraint \"%s\" of relation \"%s\" does not exist",
12792 : cmdcon->conname, get_rel_name(conrelid)));
12793 :
12794 148 : ATExecAlterCheckConstrEnforceability(wqueue, cmdcon, conrel, childtup,
12795 : recurse, recursing, lockmode);
12796 :
12797 148 : systable_endscan(pscan);
12798 148 : }
12799 :
12800 : /*
12801 : * Returns true if the constraint's deferrability is altered.
12802 : *
12803 : * *otherrelids is appended OIDs of relations containing affected triggers.
12804 : *
12805 : * Note that we must recurse even when the values are correct, in case
12806 : * indirect descendants have had their constraints altered locally.
12807 : * (This could be avoided if we forbade altering constraints in partitions
12808 : * but existing releases don't do that.)
12809 : */
12810 : static bool
12811 108 : ATExecAlterConstrDeferrability(List **wqueue, ATAlterConstraint *cmdcon,
12812 : Relation conrel, Relation tgrel, Relation rel,
12813 : HeapTuple contuple, bool recurse,
12814 : List **otherrelids, LOCKMODE lockmode)
12815 : {
12816 : Form_pg_constraint currcon;
12817 : Oid refrelid;
12818 108 : bool changed = false;
12819 :
12820 : /* since this function recurses, it could be driven to stack overflow */
12821 108 : check_stack_depth();
12822 :
12823 : Assert(cmdcon->alterDeferrability);
12824 :
12825 108 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
12826 108 : refrelid = currcon->confrelid;
12827 :
12828 : /* Should be foreign key constraint */
12829 : Assert(currcon->contype == CONSTRAINT_FOREIGN);
12830 :
12831 : /*
12832 : * If called to modify a constraint that's already in the desired state,
12833 : * silently do nothing.
12834 : */
12835 108 : if (currcon->condeferrable != cmdcon->deferrable ||
12836 4 : currcon->condeferred != cmdcon->initdeferred)
12837 : {
12838 108 : AlterConstrUpdateConstraintEntry(cmdcon, conrel, contuple);
12839 108 : changed = true;
12840 :
12841 : /*
12842 : * Now we need to update the multiple entries in pg_trigger that
12843 : * implement the constraint.
12844 : */
12845 108 : AlterConstrTriggerDeferrability(currcon->oid, tgrel, rel,
12846 108 : cmdcon->deferrable,
12847 108 : cmdcon->initdeferred, otherrelids);
12848 : }
12849 :
12850 : /*
12851 : * If the table at either end of the constraint is partitioned, we need to
12852 : * handle every constraint that is a child of this one.
12853 : */
12854 108 : if (recurse && changed &&
12855 200 : (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
12856 92 : get_rel_relkind(refrelid) == RELKIND_PARTITIONED_TABLE))
12857 28 : AlterConstrDeferrabilityRecurse(wqueue, cmdcon, conrel, tgrel, rel,
12858 : contuple, recurse, otherrelids,
12859 : lockmode);
12860 :
12861 108 : return changed;
12862 : }
12863 :
12864 : /*
12865 : * Returns true if the constraint's inheritability is altered.
12866 : */
12867 : static bool
12868 40 : ATExecAlterConstrInheritability(List **wqueue, ATAlterConstraint *cmdcon,
12869 : Relation conrel, Relation rel,
12870 : HeapTuple contuple, LOCKMODE lockmode)
12871 : {
12872 : Form_pg_constraint currcon;
12873 : AttrNumber colNum;
12874 : char *colName;
12875 : List *children;
12876 :
12877 : Assert(cmdcon->alterInheritability);
12878 :
12879 40 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
12880 :
12881 : /* The current implementation only works for NOT NULL constraints */
12882 : Assert(currcon->contype == CONSTRAINT_NOTNULL);
12883 :
12884 : /*
12885 : * If called to modify a constraint that's already in the desired state,
12886 : * silently do nothing.
12887 : */
12888 40 : if (cmdcon->noinherit == currcon->connoinherit)
12889 0 : return false;
12890 :
12891 40 : AlterConstrUpdateConstraintEntry(cmdcon, conrel, contuple);
12892 40 : CommandCounterIncrement();
12893 :
12894 : /* Fetch the column number and name */
12895 40 : colNum = extractNotNullColumn(contuple);
12896 40 : colName = get_attname(currcon->conrelid, colNum, false);
12897 :
12898 : /*
12899 : * Propagate the change to children. For this subcommand type we don't
12900 : * recursively affect children, just the immediate level.
12901 : */
12902 40 : children = find_inheritance_children(RelationGetRelid(rel),
12903 : lockmode);
12904 128 : foreach_oid(childoid, children)
12905 : {
12906 : ObjectAddress addr;
12907 :
12908 56 : if (cmdcon->noinherit)
12909 : {
12910 : HeapTuple childtup;
12911 : Form_pg_constraint childcon;
12912 :
12913 20 : childtup = findNotNullConstraint(childoid, colName);
12914 20 : if (!childtup)
12915 0 : elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation %u",
12916 : colName, childoid);
12917 20 : childcon = (Form_pg_constraint) GETSTRUCT(childtup);
12918 : Assert(childcon->coninhcount > 0);
12919 20 : childcon->coninhcount--;
12920 20 : childcon->conislocal = true;
12921 20 : CatalogTupleUpdate(conrel, &childtup->t_self, childtup);
12922 20 : heap_freetuple(childtup);
12923 : }
12924 : else
12925 : {
12926 36 : Relation childrel = table_open(childoid, NoLock);
12927 :
12928 36 : addr = ATExecSetNotNull(wqueue, childrel, NameStr(currcon->conname),
12929 : colName, true, true, lockmode);
12930 32 : if (OidIsValid(addr.objectId))
12931 32 : CommandCounterIncrement();
12932 32 : table_close(childrel, NoLock);
12933 : }
12934 : }
12935 :
12936 36 : return true;
12937 : }
12938 :
12939 : /*
12940 : * A subroutine of ATExecAlterConstrDeferrability that updated constraint
12941 : * trigger's deferrability.
12942 : *
12943 : * The arguments to this function have the same meaning as the arguments to
12944 : * ATExecAlterConstrDeferrability.
12945 : */
12946 : static void
12947 108 : AlterConstrTriggerDeferrability(Oid conoid, Relation tgrel, Relation rel,
12948 : bool deferrable, bool initdeferred,
12949 : List **otherrelids)
12950 : {
12951 : HeapTuple tgtuple;
12952 : ScanKeyData tgkey;
12953 : SysScanDesc tgscan;
12954 :
12955 108 : ScanKeyInit(&tgkey,
12956 : Anum_pg_trigger_tgconstraint,
12957 : BTEqualStrategyNumber, F_OIDEQ,
12958 : ObjectIdGetDatum(conoid));
12959 108 : tgscan = systable_beginscan(tgrel, TriggerConstraintIndexId, true,
12960 : NULL, 1, &tgkey);
12961 420 : while (HeapTupleIsValid(tgtuple = systable_getnext(tgscan)))
12962 : {
12963 312 : Form_pg_trigger tgform = (Form_pg_trigger) GETSTRUCT(tgtuple);
12964 : Form_pg_trigger copy_tg;
12965 : HeapTuple tgCopyTuple;
12966 :
12967 : /*
12968 : * Remember OIDs of other relation(s) involved in FK constraint.
12969 : * (Note: it's likely that we could skip forcing a relcache inval for
12970 : * other rels that don't have a trigger whose properties change, but
12971 : * let's be conservative.)
12972 : */
12973 312 : if (tgform->tgrelid != RelationGetRelid(rel))
12974 152 : *otherrelids = list_append_unique_oid(*otherrelids,
12975 : tgform->tgrelid);
12976 :
12977 : /*
12978 : * Update enable status and deferrability of RI_FKey_noaction_del,
12979 : * RI_FKey_noaction_upd, RI_FKey_check_ins and RI_FKey_check_upd
12980 : * triggers, but not others; see createForeignKeyActionTriggers and
12981 : * CreateFKCheckTrigger.
12982 : */
12983 312 : if (tgform->tgfoid != F_RI_FKEY_NOACTION_DEL &&
12984 248 : tgform->tgfoid != F_RI_FKEY_NOACTION_UPD &&
12985 172 : tgform->tgfoid != F_RI_FKEY_CHECK_INS &&
12986 92 : tgform->tgfoid != F_RI_FKEY_CHECK_UPD)
12987 12 : continue;
12988 :
12989 300 : tgCopyTuple = heap_copytuple(tgtuple);
12990 300 : copy_tg = (Form_pg_trigger) GETSTRUCT(tgCopyTuple);
12991 :
12992 300 : copy_tg->tgdeferrable = deferrable;
12993 300 : copy_tg->tginitdeferred = initdeferred;
12994 300 : CatalogTupleUpdate(tgrel, &tgCopyTuple->t_self, tgCopyTuple);
12995 :
12996 300 : InvokeObjectPostAlterHook(TriggerRelationId, tgform->oid, 0);
12997 :
12998 300 : heap_freetuple(tgCopyTuple);
12999 : }
13000 :
13001 108 : systable_endscan(tgscan);
13002 108 : }
13003 :
13004 : /*
13005 : * Invokes ATExecAlterFKConstrEnforceability for each foreign key constraint
13006 : * that is a child of the specified constraint.
13007 : *
13008 : * Note that this doesn't handle recursion the normal way, viz. by scanning the
13009 : * list of child relations and recursing; instead it uses the conparentid
13010 : * relationships. This may need to be reconsidered.
13011 : *
13012 : * The arguments to this function have the same meaning as the arguments to
13013 : * ATExecAlterFKConstrEnforceability.
13014 : */
13015 : static void
13016 32 : AlterFKConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
13017 : Relation conrel, Relation tgrel,
13018 : Oid fkrelid, Oid pkrelid,
13019 : HeapTuple contuple, LOCKMODE lockmode,
13020 : Oid ReferencedParentDelTrigger,
13021 : Oid ReferencedParentUpdTrigger,
13022 : Oid ReferencingParentInsTrigger,
13023 : Oid ReferencingParentUpdTrigger)
13024 : {
13025 : Form_pg_constraint currcon;
13026 : Oid conoid;
13027 : ScanKeyData pkey;
13028 : SysScanDesc pscan;
13029 : HeapTuple childtup;
13030 :
13031 32 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
13032 32 : conoid = currcon->oid;
13033 :
13034 32 : ScanKeyInit(&pkey,
13035 : Anum_pg_constraint_conparentid,
13036 : BTEqualStrategyNumber, F_OIDEQ,
13037 : ObjectIdGetDatum(conoid));
13038 :
13039 32 : pscan = systable_beginscan(conrel, ConstraintParentIndexId,
13040 : true, NULL, 1, &pkey);
13041 :
13042 100 : while (HeapTupleIsValid(childtup = systable_getnext(pscan)))
13043 68 : ATExecAlterFKConstrEnforceability(wqueue, cmdcon, conrel, tgrel, fkrelid,
13044 : pkrelid, childtup, lockmode,
13045 : ReferencedParentDelTrigger,
13046 : ReferencedParentUpdTrigger,
13047 : ReferencingParentInsTrigger,
13048 : ReferencingParentUpdTrigger);
13049 :
13050 32 : systable_endscan(pscan);
13051 32 : }
13052 :
13053 : /*
13054 : * Invokes ATExecAlterConstrDeferrability for each constraint that is a child of
13055 : * the specified constraint.
13056 : *
13057 : * Note that this doesn't handle recursion the normal way, viz. by scanning the
13058 : * list of child relations and recursing; instead it uses the conparentid
13059 : * relationships. This may need to be reconsidered.
13060 : *
13061 : * The arguments to this function have the same meaning as the arguments to
13062 : * ATExecAlterConstrDeferrability.
13063 : */
13064 : static void
13065 28 : AlterConstrDeferrabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
13066 : Relation conrel, Relation tgrel, Relation rel,
13067 : HeapTuple contuple, bool recurse,
13068 : List **otherrelids, LOCKMODE lockmode)
13069 : {
13070 : Form_pg_constraint currcon;
13071 : Oid conoid;
13072 : ScanKeyData pkey;
13073 : SysScanDesc pscan;
13074 : HeapTuple childtup;
13075 :
13076 28 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
13077 28 : conoid = currcon->oid;
13078 :
13079 28 : ScanKeyInit(&pkey,
13080 : Anum_pg_constraint_conparentid,
13081 : BTEqualStrategyNumber, F_OIDEQ,
13082 : ObjectIdGetDatum(conoid));
13083 :
13084 28 : pscan = systable_beginscan(conrel, ConstraintParentIndexId,
13085 : true, NULL, 1, &pkey);
13086 :
13087 72 : while (HeapTupleIsValid(childtup = systable_getnext(pscan)))
13088 : {
13089 44 : Form_pg_constraint childcon = (Form_pg_constraint) GETSTRUCT(childtup);
13090 : Relation childrel;
13091 :
13092 44 : childrel = table_open(childcon->conrelid, lockmode);
13093 :
13094 44 : ATExecAlterConstrDeferrability(wqueue, cmdcon, conrel, tgrel, childrel,
13095 : childtup, recurse, otherrelids, lockmode);
13096 44 : table_close(childrel, NoLock);
13097 : }
13098 :
13099 28 : systable_endscan(pscan);
13100 28 : }
13101 :
13102 : /*
13103 : * Update the constraint entry for the given ATAlterConstraint command, and
13104 : * invoke the appropriate hooks.
13105 : */
13106 : static void
13107 468 : AlterConstrUpdateConstraintEntry(ATAlterConstraint *cmdcon, Relation conrel,
13108 : HeapTuple contuple)
13109 : {
13110 : HeapTuple copyTuple;
13111 : Form_pg_constraint copy_con;
13112 :
13113 : Assert(cmdcon->alterEnforceability || cmdcon->alterDeferrability ||
13114 : cmdcon->alterInheritability);
13115 :
13116 468 : copyTuple = heap_copytuple(contuple);
13117 468 : copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
13118 :
13119 468 : if (cmdcon->alterEnforceability)
13120 : {
13121 320 : copy_con->conenforced = cmdcon->is_enforced;
13122 :
13123 : /*
13124 : * NB: The convalidated status is irrelevant when the constraint is
13125 : * set to NOT ENFORCED, but for consistency, it should still be set
13126 : * appropriately. Similarly, if the constraint is later changed to
13127 : * ENFORCED, validation will be performed during phase 3, so it makes
13128 : * sense to mark it as valid in that case.
13129 : */
13130 320 : copy_con->convalidated = cmdcon->is_enforced;
13131 : }
13132 468 : if (cmdcon->alterDeferrability)
13133 : {
13134 112 : copy_con->condeferrable = cmdcon->deferrable;
13135 112 : copy_con->condeferred = cmdcon->initdeferred;
13136 : }
13137 468 : if (cmdcon->alterInheritability)
13138 40 : copy_con->connoinherit = cmdcon->noinherit;
13139 :
13140 468 : CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);
13141 468 : InvokeObjectPostAlterHook(ConstraintRelationId, copy_con->oid, 0);
13142 :
13143 : /* Make new constraint flags visible to others */
13144 468 : CacheInvalidateRelcacheByRelid(copy_con->conrelid);
13145 :
13146 468 : heap_freetuple(copyTuple);
13147 468 : }
13148 :
13149 : /*
13150 : * ALTER TABLE VALIDATE CONSTRAINT
13151 : *
13152 : * XXX The reason we handle recursion here rather than at Phase 1 is because
13153 : * there's no good way to skip recursing when handling foreign keys: there is
13154 : * no need to lock children in that case, yet we wouldn't be able to avoid
13155 : * doing so at that level.
13156 : *
13157 : * Return value is the address of the validated constraint. If the constraint
13158 : * was already validated, InvalidObjectAddress is returned.
13159 : */
13160 : static ObjectAddress
13161 347 : ATExecValidateConstraint(List **wqueue, Relation rel, char *constrName,
13162 : bool recurse, bool recursing, LOCKMODE lockmode)
13163 : {
13164 : Relation conrel;
13165 : SysScanDesc scan;
13166 : ScanKeyData skey[3];
13167 : HeapTuple tuple;
13168 : Form_pg_constraint con;
13169 : ObjectAddress address;
13170 :
13171 347 : conrel = table_open(ConstraintRelationId, RowExclusiveLock);
13172 :
13173 : /*
13174 : * Find and check the target constraint
13175 : */
13176 347 : ScanKeyInit(&skey[0],
13177 : Anum_pg_constraint_conrelid,
13178 : BTEqualStrategyNumber, F_OIDEQ,
13179 : ObjectIdGetDatum(RelationGetRelid(rel)));
13180 347 : ScanKeyInit(&skey[1],
13181 : Anum_pg_constraint_contypid,
13182 : BTEqualStrategyNumber, F_OIDEQ,
13183 : ObjectIdGetDatum(InvalidOid));
13184 347 : ScanKeyInit(&skey[2],
13185 : Anum_pg_constraint_conname,
13186 : BTEqualStrategyNumber, F_NAMEEQ,
13187 : CStringGetDatum(constrName));
13188 347 : scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId,
13189 : true, NULL, 3, skey);
13190 :
13191 : /* There can be at most one matching row */
13192 347 : if (!HeapTupleIsValid(tuple = systable_getnext(scan)))
13193 0 : ereport(ERROR,
13194 : (errcode(ERRCODE_UNDEFINED_OBJECT),
13195 : errmsg("constraint \"%s\" of relation \"%s\" does not exist",
13196 : constrName, RelationGetRelationName(rel))));
13197 :
13198 347 : con = (Form_pg_constraint) GETSTRUCT(tuple);
13199 347 : if (con->contype != CONSTRAINT_FOREIGN &&
13200 170 : con->contype != CONSTRAINT_CHECK &&
13201 74 : con->contype != CONSTRAINT_NOTNULL)
13202 0 : ereport(ERROR,
13203 : errcode(ERRCODE_WRONG_OBJECT_TYPE),
13204 : errmsg("cannot validate constraint \"%s\" of relation \"%s\"",
13205 : constrName, RelationGetRelationName(rel)),
13206 : errdetail("This operation is not supported for this type of constraint."));
13207 :
13208 347 : if (!con->conenforced)
13209 4 : ereport(ERROR,
13210 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
13211 : errmsg("cannot validate NOT ENFORCED constraint")));
13212 :
13213 343 : if (!con->convalidated)
13214 : {
13215 331 : if (con->contype == CONSTRAINT_FOREIGN)
13216 : {
13217 173 : QueueFKConstraintValidation(wqueue, conrel, rel, con->confrelid,
13218 : tuple, lockmode);
13219 : }
13220 158 : else if (con->contype == CONSTRAINT_CHECK)
13221 : {
13222 84 : QueueCheckConstraintValidation(wqueue, conrel, rel, constrName,
13223 : tuple, recurse, recursing, lockmode);
13224 : }
13225 74 : else if (con->contype == CONSTRAINT_NOTNULL)
13226 : {
13227 74 : QueueNNConstraintValidation(wqueue, conrel, rel,
13228 : tuple, recurse, recursing, lockmode);
13229 : }
13230 :
13231 331 : ObjectAddressSet(address, ConstraintRelationId, con->oid);
13232 : }
13233 : else
13234 12 : address = InvalidObjectAddress; /* already validated */
13235 :
13236 343 : systable_endscan(scan);
13237 :
13238 343 : table_close(conrel, RowExclusiveLock);
13239 :
13240 343 : return address;
13241 : }
13242 :
13243 : /*
13244 : * QueueFKConstraintValidation
13245 : *
13246 : * Add an entry to the wqueue to validate the given foreign key constraint in
13247 : * Phase 3 and update the convalidated field in the pg_constraint catalog
13248 : * for the specified relation and all its children.
13249 : */
13250 : static void
13251 225 : QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation fkrel,
13252 : Oid pkrelid, HeapTuple contuple, LOCKMODE lockmode)
13253 : {
13254 : Form_pg_constraint con;
13255 : AlteredTableInfo *tab;
13256 : HeapTuple copyTuple;
13257 : Form_pg_constraint copy_con;
13258 :
13259 : /* since this function recurses, it could be driven to stack overflow */
13260 225 : check_stack_depth();
13261 :
13262 225 : con = (Form_pg_constraint) GETSTRUCT(contuple);
13263 : Assert(con->contype == CONSTRAINT_FOREIGN);
13264 : Assert(!con->convalidated);
13265 :
13266 : /*
13267 : * Add the validation to phase 3's queue; not needed for partitioned
13268 : * tables themselves, only for their partitions.
13269 : *
13270 : * When the referenced table (pkrelid) is partitioned, the referencing
13271 : * table (fkrel) has one pg_constraint row pointing to each partition
13272 : * thereof. These rows are there only to support action triggers and no
13273 : * table scan is needed, therefore skip this for them as well.
13274 : */
13275 225 : if (fkrel->rd_rel->relkind == RELKIND_RELATION &&
13276 193 : con->confrelid == pkrelid)
13277 : {
13278 : NewConstraint *newcon;
13279 : Constraint *fkconstraint;
13280 :
13281 : /* Queue validation for phase 3 */
13282 181 : fkconstraint = makeNode(Constraint);
13283 : /* for now this is all we need */
13284 181 : fkconstraint->conname = pstrdup(NameStr(con->conname));
13285 :
13286 181 : newcon = palloc0_object(NewConstraint);
13287 181 : newcon->name = fkconstraint->conname;
13288 181 : newcon->contype = CONSTR_FOREIGN;
13289 181 : newcon->refrelid = con->confrelid;
13290 181 : newcon->refindid = con->conindid;
13291 181 : newcon->conid = con->oid;
13292 181 : newcon->qual = (Node *) fkconstraint;
13293 :
13294 : /* Find or create work queue entry for this table */
13295 181 : tab = ATGetQueueEntry(wqueue, fkrel);
13296 181 : tab->constraints = lappend(tab->constraints, newcon);
13297 : }
13298 :
13299 : /*
13300 : * If the table at either end of the constraint is partitioned, we need to
13301 : * recurse and handle every unvalidated constraint that is a child of this
13302 : * constraint.
13303 : */
13304 418 : if (fkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
13305 193 : get_rel_relkind(con->confrelid) == RELKIND_PARTITIONED_TABLE)
13306 : {
13307 : ScanKeyData pkey;
13308 : SysScanDesc pscan;
13309 : HeapTuple childtup;
13310 :
13311 52 : ScanKeyInit(&pkey,
13312 : Anum_pg_constraint_conparentid,
13313 : BTEqualStrategyNumber, F_OIDEQ,
13314 : ObjectIdGetDatum(con->oid));
13315 :
13316 52 : pscan = systable_beginscan(conrel, ConstraintParentIndexId,
13317 : true, NULL, 1, &pkey);
13318 :
13319 104 : while (HeapTupleIsValid(childtup = systable_getnext(pscan)))
13320 : {
13321 : Form_pg_constraint childcon;
13322 : Relation childrel;
13323 :
13324 52 : childcon = (Form_pg_constraint) GETSTRUCT(childtup);
13325 :
13326 : /*
13327 : * If the child constraint has already been validated, no further
13328 : * action is required for it or its descendants, as they are all
13329 : * valid.
13330 : */
13331 52 : if (childcon->convalidated)
13332 12 : continue;
13333 :
13334 40 : childrel = table_open(childcon->conrelid, lockmode);
13335 :
13336 : /*
13337 : * NB: Note that pkrelid should be passed as-is during recursion,
13338 : * as it is required to identify the root referenced table.
13339 : */
13340 40 : QueueFKConstraintValidation(wqueue, conrel, childrel, pkrelid,
13341 : childtup, lockmode);
13342 40 : table_close(childrel, NoLock);
13343 : }
13344 :
13345 52 : systable_endscan(pscan);
13346 : }
13347 :
13348 : /*
13349 : * Now mark the pg_constraint row as validated (even if we didn't check,
13350 : * notably the ones for partitions on the referenced side).
13351 : *
13352 : * We rely on transaction abort to roll back this change if phase 3
13353 : * ultimately finds violating rows. This is a bit ugly.
13354 : */
13355 225 : copyTuple = heap_copytuple(contuple);
13356 225 : copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
13357 225 : copy_con->convalidated = true;
13358 225 : CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);
13359 :
13360 225 : InvokeObjectPostAlterHook(ConstraintRelationId, con->oid, 0);
13361 :
13362 225 : heap_freetuple(copyTuple);
13363 225 : }
13364 :
13365 : /*
13366 : * QueueCheckConstraintValidation
13367 : *
13368 : * Add an entry to the wqueue to validate the given check constraint in Phase 3
13369 : * and update the convalidated field in the pg_constraint catalog for the
13370 : * specified relation and all its inheriting children.
13371 : */
13372 : static void
13373 84 : QueueCheckConstraintValidation(List **wqueue, Relation conrel, Relation rel,
13374 : char *constrName, HeapTuple contuple,
13375 : bool recurse, bool recursing, LOCKMODE lockmode)
13376 : {
13377 : Form_pg_constraint con;
13378 : AlteredTableInfo *tab;
13379 : HeapTuple copyTuple;
13380 : Form_pg_constraint copy_con;
13381 :
13382 84 : List *children = NIL;
13383 : ListCell *child;
13384 : NewConstraint *newcon;
13385 : Datum val;
13386 : char *conbin;
13387 :
13388 84 : con = (Form_pg_constraint) GETSTRUCT(contuple);
13389 : Assert(con->contype == CONSTRAINT_CHECK);
13390 :
13391 : /*
13392 : * If we're recursing, the parent has already done this, so skip it. Also,
13393 : * if the constraint is a NO INHERIT constraint, we shouldn't try to look
13394 : * for it in the children.
13395 : */
13396 84 : if (!recursing && !con->connoinherit)
13397 48 : children = find_all_inheritors(RelationGetRelid(rel),
13398 : lockmode, NULL);
13399 :
13400 : /*
13401 : * For CHECK constraints, we must ensure that we only mark the constraint
13402 : * as validated on the parent if it's already validated on the children.
13403 : *
13404 : * We recurse before validating on the parent, to reduce risk of
13405 : * deadlocks.
13406 : */
13407 164 : foreach(child, children)
13408 : {
13409 80 : Oid childoid = lfirst_oid(child);
13410 : Relation childrel;
13411 :
13412 80 : if (childoid == RelationGetRelid(rel))
13413 48 : continue;
13414 :
13415 : /*
13416 : * If we are told not to recurse, there had better not be any child
13417 : * tables, because we can't mark the constraint on the parent valid
13418 : * unless it is valid for all child tables.
13419 : */
13420 32 : if (!recurse)
13421 0 : ereport(ERROR,
13422 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
13423 : errmsg("constraint must be validated on child tables too")));
13424 :
13425 : /* find_all_inheritors already got lock */
13426 32 : childrel = table_open(childoid, NoLock);
13427 :
13428 32 : ATExecValidateConstraint(wqueue, childrel, constrName, false,
13429 : true, lockmode);
13430 32 : table_close(childrel, NoLock);
13431 : }
13432 :
13433 : /* Queue validation for phase 3 */
13434 84 : newcon = palloc0_object(NewConstraint);
13435 84 : newcon->name = constrName;
13436 84 : newcon->contype = CONSTR_CHECK;
13437 84 : newcon->refrelid = InvalidOid;
13438 84 : newcon->refindid = InvalidOid;
13439 84 : newcon->conid = con->oid;
13440 :
13441 84 : val = SysCacheGetAttrNotNull(CONSTROID, contuple,
13442 : Anum_pg_constraint_conbin);
13443 84 : conbin = TextDatumGetCString(val);
13444 84 : newcon->qual = expand_generated_columns_in_expr(stringToNode(conbin), rel, 1);
13445 :
13446 : /* Find or create work queue entry for this table */
13447 84 : tab = ATGetQueueEntry(wqueue, rel);
13448 84 : tab->constraints = lappend(tab->constraints, newcon);
13449 :
13450 : /*
13451 : * Invalidate relcache so that others see the new validated constraint.
13452 : */
13453 84 : CacheInvalidateRelcache(rel);
13454 :
13455 : /*
13456 : * Now update the catalog, while we have the door open.
13457 : */
13458 84 : copyTuple = heap_copytuple(contuple);
13459 84 : copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
13460 84 : copy_con->convalidated = true;
13461 84 : CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);
13462 :
13463 84 : InvokeObjectPostAlterHook(ConstraintRelationId, con->oid, 0);
13464 :
13465 84 : heap_freetuple(copyTuple);
13466 84 : }
13467 :
13468 : /*
13469 : * QueueNNConstraintValidation
13470 : *
13471 : * Add an entry to the wqueue to validate the given not-null constraint in
13472 : * Phase 3 and update the convalidated field in the pg_constraint catalog for
13473 : * the specified relation and all its inheriting children.
13474 : */
13475 : static void
13476 74 : QueueNNConstraintValidation(List **wqueue, Relation conrel, Relation rel,
13477 : HeapTuple contuple, bool recurse, bool recursing,
13478 : LOCKMODE lockmode)
13479 : {
13480 : Form_pg_constraint con;
13481 : AlteredTableInfo *tab;
13482 : HeapTuple copyTuple;
13483 : Form_pg_constraint copy_con;
13484 74 : List *children = NIL;
13485 : AttrNumber attnum;
13486 : char *colname;
13487 :
13488 74 : con = (Form_pg_constraint) GETSTRUCT(contuple);
13489 : Assert(con->contype == CONSTRAINT_NOTNULL);
13490 :
13491 74 : attnum = extractNotNullColumn(contuple);
13492 :
13493 : /*
13494 : * If we're recursing, we've already done this for parent, so skip it.
13495 : * Also, if the constraint is a NO INHERIT constraint, we shouldn't try to
13496 : * look for it in the children.
13497 : *
13498 : * We recurse before validating on the parent, to reduce risk of
13499 : * deadlocks.
13500 : */
13501 74 : if (!recursing && !con->connoinherit)
13502 50 : children = find_all_inheritors(RelationGetRelid(rel), lockmode, NULL);
13503 :
13504 74 : colname = get_attname(RelationGetRelid(rel), attnum, false);
13505 250 : foreach_oid(childoid, children)
13506 : {
13507 : Relation childrel;
13508 : HeapTuple contup;
13509 : Form_pg_constraint childcon;
13510 : char *conname;
13511 :
13512 102 : if (childoid == RelationGetRelid(rel))
13513 50 : continue;
13514 :
13515 : /*
13516 : * If we are told not to recurse, there had better not be any child
13517 : * tables, because we can't mark the constraint on the parent valid
13518 : * unless it is valid for all child tables.
13519 : */
13520 52 : if (!recurse)
13521 0 : ereport(ERROR,
13522 : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
13523 : errmsg("constraint must be validated on child tables too"));
13524 :
13525 : /*
13526 : * The column on child might have a different attnum, so search by
13527 : * column name.
13528 : */
13529 52 : contup = findNotNullConstraint(childoid, colname);
13530 52 : if (!contup)
13531 0 : elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation \"%s\"",
13532 : colname, get_rel_name(childoid));
13533 52 : childcon = (Form_pg_constraint) GETSTRUCT(contup);
13534 52 : if (childcon->convalidated)
13535 28 : continue;
13536 :
13537 : /* find_all_inheritors already got lock */
13538 24 : childrel = table_open(childoid, NoLock);
13539 24 : conname = pstrdup(NameStr(childcon->conname));
13540 :
13541 : /* XXX improve ATExecValidateConstraint API to avoid double search */
13542 24 : ATExecValidateConstraint(wqueue, childrel, conname,
13543 : false, true, lockmode);
13544 24 : table_close(childrel, NoLock);
13545 : }
13546 :
13547 : /* Set attnotnull appropriately without queueing another validation */
13548 74 : set_attnotnull(NULL, rel, attnum, true, false);
13549 :
13550 74 : tab = ATGetQueueEntry(wqueue, rel);
13551 74 : tab->verify_new_notnull = true;
13552 :
13553 : /*
13554 : * Invalidate relcache so that others see the new validated constraint.
13555 : */
13556 74 : CacheInvalidateRelcache(rel);
13557 :
13558 : /*
13559 : * Now update the catalogs, while we have the door open.
13560 : */
13561 74 : copyTuple = heap_copytuple(contuple);
13562 74 : copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
13563 74 : copy_con->convalidated = true;
13564 74 : CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);
13565 :
13566 74 : InvokeObjectPostAlterHook(ConstraintRelationId, con->oid, 0);
13567 :
13568 74 : heap_freetuple(copyTuple);
13569 74 : }
13570 :
13571 : /*
13572 : * transformColumnNameList - transform list of column names
13573 : *
13574 : * Lookup each name and return its attnum and, optionally, type and collation
13575 : * OIDs
13576 : *
13577 : * Note: the name of this function suggests that it's general-purpose,
13578 : * but actually it's only used to look up names appearing in foreign-key
13579 : * clauses. The error messages would need work to use it in other cases,
13580 : * and perhaps the validity checks as well.
13581 : */
13582 : static int
13583 4567 : transformColumnNameList(Oid relId, List *colList,
13584 : int16 *attnums, Oid *atttypids, Oid *attcollids)
13585 : {
13586 : ListCell *l;
13587 : int attnum;
13588 :
13589 4567 : attnum = 0;
13590 8303 : foreach(l, colList)
13591 : {
13592 3780 : char *attname = strVal(lfirst(l));
13593 : HeapTuple atttuple;
13594 : Form_pg_attribute attform;
13595 :
13596 3780 : atttuple = SearchSysCacheAttName(relId, attname);
13597 3780 : if (!HeapTupleIsValid(atttuple))
13598 36 : ereport(ERROR,
13599 : (errcode(ERRCODE_UNDEFINED_COLUMN),
13600 : errmsg("column \"%s\" referenced in foreign key constraint does not exist",
13601 : attname)));
13602 3744 : attform = (Form_pg_attribute) GETSTRUCT(atttuple);
13603 3744 : if (attform->attnum < 0)
13604 8 : ereport(ERROR,
13605 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
13606 : errmsg("system columns cannot be used in foreign keys")));
13607 3736 : if (attnum >= INDEX_MAX_KEYS)
13608 0 : ereport(ERROR,
13609 : (errcode(ERRCODE_TOO_MANY_COLUMNS),
13610 : errmsg("cannot have more than %d keys in a foreign key",
13611 : INDEX_MAX_KEYS)));
13612 3736 : attnums[attnum] = attform->attnum;
13613 3736 : if (atttypids != NULL)
13614 3712 : atttypids[attnum] = attform->atttypid;
13615 3736 : if (attcollids != NULL)
13616 3712 : attcollids[attnum] = attform->attcollation;
13617 3736 : ReleaseSysCache(atttuple);
13618 3736 : attnum++;
13619 : }
13620 :
13621 4523 : return attnum;
13622 : }
13623 :
13624 : /*
13625 : * transformFkeyGetPrimaryKey -
13626 : *
13627 : * Look up the names, attnums, types, and collations of the primary key attributes
13628 : * for the pkrel. Also return the index OID and index opclasses of the
13629 : * index supporting the primary key. Also return whether the index has
13630 : * WITHOUT OVERLAPS.
13631 : *
13632 : * All parameters except pkrel are output parameters. Also, the function
13633 : * return value is the number of attributes in the primary key.
13634 : *
13635 : * Used when the column list in the REFERENCES specification is omitted.
13636 : */
13637 : static int
13638 900 : transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
13639 : List **attnamelist,
13640 : int16 *attnums, Oid *atttypids, Oid *attcollids,
13641 : Oid *opclasses, bool *pk_has_without_overlaps)
13642 : {
13643 : List *indexoidlist;
13644 : ListCell *indexoidscan;
13645 900 : HeapTuple indexTuple = NULL;
13646 900 : Form_pg_index indexStruct = NULL;
13647 : Datum indclassDatum;
13648 : oidvector *indclass;
13649 : int i;
13650 :
13651 : /*
13652 : * Get the list of index OIDs for the table from the relcache, and look up
13653 : * each one in the pg_index syscache until we find one marked primary key
13654 : * (hopefully there isn't more than one such). Insist it's valid, too.
13655 : */
13656 900 : *indexOid = InvalidOid;
13657 :
13658 900 : indexoidlist = RelationGetIndexList(pkrel);
13659 :
13660 904 : foreach(indexoidscan, indexoidlist)
13661 : {
13662 904 : Oid indexoid = lfirst_oid(indexoidscan);
13663 :
13664 904 : indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
13665 904 : if (!HeapTupleIsValid(indexTuple))
13666 0 : elog(ERROR, "cache lookup failed for index %u", indexoid);
13667 904 : indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
13668 904 : if (indexStruct->indisprimary && indexStruct->indisvalid)
13669 : {
13670 : /*
13671 : * Refuse to use a deferrable primary key. This is per SQL spec,
13672 : * and there would be a lot of interesting semantic problems if we
13673 : * tried to allow it.
13674 : */
13675 900 : if (!indexStruct->indimmediate)
13676 0 : ereport(ERROR,
13677 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
13678 : errmsg("cannot use a deferrable primary key for referenced table \"%s\"",
13679 : RelationGetRelationName(pkrel))));
13680 :
13681 900 : *indexOid = indexoid;
13682 900 : break;
13683 : }
13684 4 : ReleaseSysCache(indexTuple);
13685 : }
13686 :
13687 900 : list_free(indexoidlist);
13688 :
13689 : /*
13690 : * Check that we found it
13691 : */
13692 900 : if (!OidIsValid(*indexOid))
13693 0 : ereport(ERROR,
13694 : (errcode(ERRCODE_UNDEFINED_OBJECT),
13695 : errmsg("there is no primary key for referenced table \"%s\"",
13696 : RelationGetRelationName(pkrel))));
13697 :
13698 : /* Must get indclass the hard way */
13699 900 : indclassDatum = SysCacheGetAttrNotNull(INDEXRELID, indexTuple,
13700 : Anum_pg_index_indclass);
13701 900 : indclass = (oidvector *) DatumGetPointer(indclassDatum);
13702 :
13703 : /*
13704 : * Now build the list of PK attributes from the indkey definition (we
13705 : * assume a primary key cannot have expressional elements)
13706 : */
13707 900 : *attnamelist = NIL;
13708 2116 : for (i = 0; i < indexStruct->indnkeyatts; i++)
13709 : {
13710 1216 : int pkattno = indexStruct->indkey.values[i];
13711 :
13712 1216 : attnums[i] = pkattno;
13713 1216 : atttypids[i] = attnumTypeId(pkrel, pkattno);
13714 1216 : attcollids[i] = attnumCollationId(pkrel, pkattno);
13715 1216 : opclasses[i] = indclass->values[i];
13716 1216 : *attnamelist = lappend(*attnamelist,
13717 1216 : makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno)))));
13718 : }
13719 :
13720 900 : *pk_has_without_overlaps = indexStruct->indisexclusion;
13721 :
13722 900 : ReleaseSysCache(indexTuple);
13723 :
13724 900 : return i;
13725 : }
13726 :
13727 : /*
13728 : * transformFkeyCheckAttrs -
13729 : *
13730 : * Validate that the 'attnums' columns in the 'pkrel' relation are valid to
13731 : * reference as part of a foreign key constraint.
13732 : *
13733 : * Returns the OID of the unique index supporting the constraint and
13734 : * populates the caller-provided 'opclasses' array with the opclasses
13735 : * associated with the index columns. Also sets whether the index
13736 : * uses WITHOUT OVERLAPS.
13737 : *
13738 : * Raises an ERROR on validation failure.
13739 : */
13740 : static Oid
13741 869 : transformFkeyCheckAttrs(Relation pkrel,
13742 : int numattrs, int16 *attnums,
13743 : bool with_period, Oid *opclasses,
13744 : bool *pk_has_without_overlaps)
13745 : {
13746 869 : Oid indexoid = InvalidOid;
13747 869 : bool found = false;
13748 869 : bool found_deferrable = false;
13749 : List *indexoidlist;
13750 : ListCell *indexoidscan;
13751 : int i,
13752 : j;
13753 :
13754 : /*
13755 : * Reject duplicate appearances of columns in the referenced-columns list.
13756 : * Such a case is forbidden by the SQL standard, and even if we thought it
13757 : * useful to allow it, there would be ambiguity about how to match the
13758 : * list to unique indexes (in particular, it'd be unclear which index
13759 : * opclass goes with which FK column).
13760 : */
13761 2031 : for (i = 0; i < numattrs; i++)
13762 : {
13763 1537 : for (j = i + 1; j < numattrs; j++)
13764 : {
13765 375 : if (attnums[i] == attnums[j])
13766 16 : ereport(ERROR,
13767 : (errcode(ERRCODE_INVALID_FOREIGN_KEY),
13768 : errmsg("foreign key referenced-columns list must not contain duplicates")));
13769 : }
13770 : }
13771 :
13772 : /*
13773 : * Get the list of index OIDs for the table from the relcache, and look up
13774 : * each one in the pg_index syscache, and match unique indexes to the list
13775 : * of attnums we are given.
13776 : */
13777 853 : indexoidlist = RelationGetIndexList(pkrel);
13778 :
13779 972 : foreach(indexoidscan, indexoidlist)
13780 : {
13781 : HeapTuple indexTuple;
13782 : Form_pg_index indexStruct;
13783 :
13784 964 : indexoid = lfirst_oid(indexoidscan);
13785 964 : indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
13786 964 : if (!HeapTupleIsValid(indexTuple))
13787 0 : elog(ERROR, "cache lookup failed for index %u", indexoid);
13788 964 : indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
13789 :
13790 : /*
13791 : * Must have the right number of columns; must be unique (or if
13792 : * temporal then exclusion instead) and not a partial index; forget it
13793 : * if there are any expressions, too. Invalid indexes are out as well.
13794 : */
13795 964 : if (indexStruct->indnkeyatts == numattrs &&
13796 883 : (with_period ? indexStruct->indisexclusion : indexStruct->indisunique) &&
13797 1766 : indexStruct->indisvalid &&
13798 1766 : heap_attisnull(indexTuple, Anum_pg_index_indpred, NULL) &&
13799 883 : heap_attisnull(indexTuple, Anum_pg_index_indexprs, NULL))
13800 : {
13801 : Datum indclassDatum;
13802 : oidvector *indclass;
13803 :
13804 : /* Must get indclass the hard way */
13805 883 : indclassDatum = SysCacheGetAttrNotNull(INDEXRELID, indexTuple,
13806 : Anum_pg_index_indclass);
13807 883 : indclass = (oidvector *) DatumGetPointer(indclassDatum);
13808 :
13809 : /*
13810 : * The given attnum list may match the index columns in any order.
13811 : * Check for a match, and extract the appropriate opclasses while
13812 : * we're at it.
13813 : *
13814 : * We know that attnums[] is duplicate-free per the test at the
13815 : * start of this function, and we checked above that the number of
13816 : * index columns agrees, so if we find a match for each attnums[]
13817 : * entry then we must have a one-to-one match in some order.
13818 : */
13819 2037 : for (i = 0; i < numattrs; i++)
13820 : {
13821 1192 : found = false;
13822 1589 : for (j = 0; j < numattrs; j++)
13823 : {
13824 1551 : if (attnums[i] == indexStruct->indkey.values[j])
13825 : {
13826 1154 : opclasses[i] = indclass->values[j];
13827 1154 : found = true;
13828 1154 : break;
13829 : }
13830 : }
13831 1192 : if (!found)
13832 38 : break;
13833 : }
13834 : /* The last attribute in the index must be the PERIOD FK part */
13835 883 : if (found && with_period)
13836 : {
13837 80 : int16 periodattnum = attnums[numattrs - 1];
13838 :
13839 80 : found = (periodattnum == indexStruct->indkey.values[numattrs - 1]);
13840 : }
13841 :
13842 : /*
13843 : * Refuse to use a deferrable unique/primary key. This is per SQL
13844 : * spec, and there would be a lot of interesting semantic problems
13845 : * if we tried to allow it.
13846 : */
13847 883 : if (found && !indexStruct->indimmediate)
13848 : {
13849 : /*
13850 : * Remember that we found an otherwise matching index, so that
13851 : * we can generate a more appropriate error message.
13852 : */
13853 0 : found_deferrable = true;
13854 0 : found = false;
13855 : }
13856 :
13857 : /* We need to know whether the index has WITHOUT OVERLAPS */
13858 883 : if (found)
13859 845 : *pk_has_without_overlaps = indexStruct->indisexclusion;
13860 : }
13861 964 : ReleaseSysCache(indexTuple);
13862 964 : if (found)
13863 845 : break;
13864 : }
13865 :
13866 853 : if (!found)
13867 : {
13868 8 : if (found_deferrable)
13869 0 : ereport(ERROR,
13870 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
13871 : errmsg("cannot use a deferrable unique constraint for referenced table \"%s\"",
13872 : RelationGetRelationName(pkrel))));
13873 : else
13874 8 : ereport(ERROR,
13875 : (errcode(ERRCODE_INVALID_FOREIGN_KEY),
13876 : errmsg("there is no unique constraint matching given keys for referenced table \"%s\"",
13877 : RelationGetRelationName(pkrel))));
13878 : }
13879 :
13880 845 : list_free(indexoidlist);
13881 :
13882 845 : return indexoid;
13883 : }
13884 :
13885 : /*
13886 : * findFkeyCast -
13887 : *
13888 : * Wrapper around find_coercion_pathway() for ATAddForeignKeyConstraint().
13889 : * Caller has equal regard for binary coercibility and for an exact match.
13890 : */
13891 : static CoercionPathType
13892 8 : findFkeyCast(Oid targetTypeId, Oid sourceTypeId, Oid *funcid)
13893 : {
13894 : CoercionPathType ret;
13895 :
13896 8 : if (targetTypeId == sourceTypeId)
13897 : {
13898 8 : ret = COERCION_PATH_RELABELTYPE;
13899 8 : *funcid = InvalidOid;
13900 : }
13901 : else
13902 : {
13903 0 : ret = find_coercion_pathway(targetTypeId, sourceTypeId,
13904 : COERCION_IMPLICIT, funcid);
13905 0 : if (ret == COERCION_PATH_NONE)
13906 : /* A previously-relied-upon cast is now gone. */
13907 0 : elog(ERROR, "could not find cast from %u to %u",
13908 : sourceTypeId, targetTypeId);
13909 : }
13910 :
13911 8 : return ret;
13912 : }
13913 :
13914 : /*
13915 : * Permissions checks on the referenced table for ADD FOREIGN KEY
13916 : *
13917 : * Note: we have already checked that the user owns the referencing table,
13918 : * else we'd have failed much earlier; no additional checks are needed for it.
13919 : */
13920 : static void
13921 1721 : checkFkeyPermissions(Relation rel, int16 *attnums, int natts)
13922 : {
13923 1721 : Oid roleid = GetUserId();
13924 : AclResult aclresult;
13925 : int i;
13926 :
13927 : /* Okay if we have relation-level REFERENCES permission */
13928 1721 : aclresult = pg_class_aclcheck(RelationGetRelid(rel), roleid,
13929 : ACL_REFERENCES);
13930 1721 : if (aclresult == ACLCHECK_OK)
13931 1721 : return;
13932 : /* Else we must have REFERENCES on each column */
13933 0 : for (i = 0; i < natts; i++)
13934 : {
13935 0 : aclresult = pg_attribute_aclcheck(RelationGetRelid(rel), attnums[i],
13936 : roleid, ACL_REFERENCES);
13937 0 : if (aclresult != ACLCHECK_OK)
13938 0 : aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
13939 0 : RelationGetRelationName(rel));
13940 : }
13941 : }
13942 :
13943 : /*
13944 : * Scan the existing rows in a table to verify they meet a proposed FK
13945 : * constraint.
13946 : *
13947 : * Caller must have opened and locked both relations appropriately.
13948 : */
13949 : static void
13950 832 : validateForeignKeyConstraint(char *conname,
13951 : Relation rel,
13952 : Relation pkrel,
13953 : Oid pkindOid,
13954 : Oid constraintOid,
13955 : bool hasperiod)
13956 : {
13957 : TupleTableSlot *slot;
13958 : TableScanDesc scan;
13959 832 : Trigger trig = {0};
13960 : Snapshot snapshot;
13961 : MemoryContext oldcxt;
13962 : MemoryContext perTupCxt;
13963 :
13964 832 : ereport(DEBUG1,
13965 : (errmsg_internal("validating foreign key constraint \"%s\"", conname)));
13966 :
13967 : /*
13968 : * Build a trigger call structure; we'll need it either way.
13969 : */
13970 832 : trig.tgoid = InvalidOid;
13971 832 : trig.tgname = conname;
13972 832 : trig.tgenabled = TRIGGER_FIRES_ON_ORIGIN;
13973 832 : trig.tgisinternal = true;
13974 832 : trig.tgconstrrelid = RelationGetRelid(pkrel);
13975 832 : trig.tgconstrindid = pkindOid;
13976 832 : trig.tgconstraint = constraintOid;
13977 832 : trig.tgdeferrable = false;
13978 832 : trig.tginitdeferred = false;
13979 : /* we needn't fill in remaining fields */
13980 :
13981 : /*
13982 : * See if we can do it with a single LEFT JOIN query. A false result
13983 : * indicates we must proceed with the fire-the-trigger method. We can't do
13984 : * a LEFT JOIN for temporal FKs yet, but we can once we support temporal
13985 : * left joins.
13986 : */
13987 832 : if (!hasperiod && RI_Initial_Check(&trig, rel, pkrel))
13988 702 : return;
13989 :
13990 : /*
13991 : * Scan through each tuple, calling RI_FKey_check_ins (insert trigger) as
13992 : * if that tuple had just been inserted. If any of those fail, it should
13993 : * ereport(ERROR) and that's that.
13994 : */
13995 71 : snapshot = RegisterSnapshot(GetLatestSnapshot());
13996 71 : slot = table_slot_create(rel, NULL);
13997 71 : scan = table_beginscan(rel, snapshot, 0, NULL,
13998 : SO_NONE);
13999 71 : perTupCxt = AllocSetContextCreate(CurrentMemoryContext,
14000 : "validateForeignKeyConstraint",
14001 : ALLOCSET_SMALL_SIZES);
14002 71 : oldcxt = MemoryContextSwitchTo(perTupCxt);
14003 :
14004 127 : while (table_scan_getnextslot(scan, ForwardScanDirection, slot))
14005 : {
14006 68 : LOCAL_FCINFO(fcinfo, 0);
14007 68 : TriggerData trigdata = {0};
14008 :
14009 68 : CHECK_FOR_INTERRUPTS();
14010 :
14011 : /*
14012 : * Make a call to the trigger function
14013 : *
14014 : * No parameters are passed, but we do set a context
14015 : */
14016 340 : MemSet(fcinfo, 0, SizeForFunctionCallInfo(0));
14017 :
14018 : /*
14019 : * We assume RI_FKey_check_ins won't look at flinfo...
14020 : */
14021 68 : trigdata.type = T_TriggerData;
14022 68 : trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
14023 68 : trigdata.tg_relation = rel;
14024 68 : trigdata.tg_trigtuple = ExecFetchSlotHeapTuple(slot, false, NULL);
14025 68 : trigdata.tg_trigslot = slot;
14026 68 : trigdata.tg_trigger = &trig;
14027 :
14028 68 : fcinfo->context = (Node *) &trigdata;
14029 :
14030 68 : RI_FKey_check_ins(fcinfo);
14031 :
14032 56 : MemoryContextReset(perTupCxt);
14033 : }
14034 :
14035 59 : MemoryContextSwitchTo(oldcxt);
14036 59 : MemoryContextDelete(perTupCxt);
14037 59 : table_endscan(scan);
14038 59 : UnregisterSnapshot(snapshot);
14039 59 : ExecDropSingleTupleTableSlot(slot);
14040 : }
14041 :
14042 : /*
14043 : * CreateFKCheckTrigger
14044 : * Creates the insert (on_insert=true) or update "check" trigger that
14045 : * implements a given foreign key
14046 : *
14047 : * Returns the OID of the so created trigger.
14048 : */
14049 : static Oid
14050 4160 : CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
14051 : Oid constraintOid, Oid indexOid, Oid parentTrigOid,
14052 : bool on_insert)
14053 : {
14054 : ObjectAddress trigAddress;
14055 : CreateTrigStmt *fk_trigger;
14056 :
14057 : /*
14058 : * Note: for a self-referential FK (referencing and referenced tables are
14059 : * the same), it is important that the ON UPDATE action fires before the
14060 : * CHECK action, since both triggers will fire on the same row during an
14061 : * UPDATE event; otherwise the CHECK trigger will be checking a non-final
14062 : * state of the row. Triggers fire in name order, so we ensure this by
14063 : * using names like "RI_ConstraintTrigger_a_NNNN" for the action triggers
14064 : * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
14065 : */
14066 4160 : fk_trigger = makeNode(CreateTrigStmt);
14067 4160 : fk_trigger->replace = false;
14068 4160 : fk_trigger->isconstraint = true;
14069 4160 : fk_trigger->trigname = "RI_ConstraintTrigger_c";
14070 4160 : fk_trigger->relation = NULL;
14071 :
14072 : /* Either ON INSERT or ON UPDATE */
14073 4160 : if (on_insert)
14074 : {
14075 2080 : fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
14076 2080 : fk_trigger->events = TRIGGER_TYPE_INSERT;
14077 : }
14078 : else
14079 : {
14080 2080 : fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
14081 2080 : fk_trigger->events = TRIGGER_TYPE_UPDATE;
14082 : }
14083 :
14084 4160 : fk_trigger->args = NIL;
14085 4160 : fk_trigger->row = true;
14086 4160 : fk_trigger->timing = TRIGGER_TYPE_AFTER;
14087 4160 : fk_trigger->columns = NIL;
14088 4160 : fk_trigger->whenClause = NULL;
14089 4160 : fk_trigger->transitionRels = NIL;
14090 4160 : fk_trigger->deferrable = fkconstraint->deferrable;
14091 4160 : fk_trigger->initdeferred = fkconstraint->initdeferred;
14092 4160 : fk_trigger->constrrel = NULL;
14093 :
14094 4160 : trigAddress = CreateTrigger(fk_trigger, NULL, myRelOid, refRelOid,
14095 : constraintOid, indexOid, InvalidOid,
14096 : parentTrigOid, NULL, true, false);
14097 :
14098 : /* Make changes-so-far visible */
14099 4160 : CommandCounterIncrement();
14100 :
14101 4160 : return trigAddress.objectId;
14102 : }
14103 :
14104 : /*
14105 : * createForeignKeyActionTriggers
14106 : * Create the referenced-side "action" triggers that implement a foreign
14107 : * key.
14108 : *
14109 : * Returns the OIDs of the so created triggers in *deleteTrigOid and
14110 : * *updateTrigOid.
14111 : */
14112 : static void
14113 2367 : createForeignKeyActionTriggers(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
14114 : Oid constraintOid, Oid indexOid,
14115 : Oid parentDelTrigger, Oid parentUpdTrigger,
14116 : Oid *deleteTrigOid, Oid *updateTrigOid)
14117 : {
14118 : CreateTrigStmt *fk_trigger;
14119 : ObjectAddress trigAddress;
14120 :
14121 : /*
14122 : * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
14123 : * DELETE action on the referenced table.
14124 : */
14125 2367 : fk_trigger = makeNode(CreateTrigStmt);
14126 2367 : fk_trigger->replace = false;
14127 2367 : fk_trigger->isconstraint = true;
14128 2367 : fk_trigger->trigname = "RI_ConstraintTrigger_a";
14129 2367 : fk_trigger->relation = NULL;
14130 2367 : fk_trigger->args = NIL;
14131 2367 : fk_trigger->row = true;
14132 2367 : fk_trigger->timing = TRIGGER_TYPE_AFTER;
14133 2367 : fk_trigger->events = TRIGGER_TYPE_DELETE;
14134 2367 : fk_trigger->columns = NIL;
14135 2367 : fk_trigger->whenClause = NULL;
14136 2367 : fk_trigger->transitionRels = NIL;
14137 2367 : fk_trigger->constrrel = NULL;
14138 :
14139 2367 : switch (fkconstraint->fk_del_action)
14140 : {
14141 1938 : case FKCONSTR_ACTION_NOACTION:
14142 1938 : fk_trigger->deferrable = fkconstraint->deferrable;
14143 1938 : fk_trigger->initdeferred = fkconstraint->initdeferred;
14144 1938 : fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
14145 1938 : break;
14146 20 : case FKCONSTR_ACTION_RESTRICT:
14147 20 : fk_trigger->deferrable = false;
14148 20 : fk_trigger->initdeferred = false;
14149 20 : fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
14150 20 : break;
14151 306 : case FKCONSTR_ACTION_CASCADE:
14152 306 : fk_trigger->deferrable = false;
14153 306 : fk_trigger->initdeferred = false;
14154 306 : fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
14155 306 : break;
14156 63 : case FKCONSTR_ACTION_SETNULL:
14157 63 : fk_trigger->deferrable = false;
14158 63 : fk_trigger->initdeferred = false;
14159 63 : fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
14160 63 : break;
14161 40 : case FKCONSTR_ACTION_SETDEFAULT:
14162 40 : fk_trigger->deferrable = false;
14163 40 : fk_trigger->initdeferred = false;
14164 40 : fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
14165 40 : break;
14166 0 : default:
14167 0 : elog(ERROR, "unrecognized FK action type: %d",
14168 : (int) fkconstraint->fk_del_action);
14169 : break;
14170 : }
14171 :
14172 2367 : trigAddress = CreateTrigger(fk_trigger, NULL, refRelOid, myRelOid,
14173 : constraintOid, indexOid, InvalidOid,
14174 : parentDelTrigger, NULL, true, false);
14175 2367 : if (deleteTrigOid)
14176 2367 : *deleteTrigOid = trigAddress.objectId;
14177 :
14178 : /* Make changes-so-far visible */
14179 2367 : CommandCounterIncrement();
14180 :
14181 : /*
14182 : * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
14183 : * UPDATE action on the referenced table.
14184 : */
14185 2367 : fk_trigger = makeNode(CreateTrigStmt);
14186 2367 : fk_trigger->replace = false;
14187 2367 : fk_trigger->isconstraint = true;
14188 2367 : fk_trigger->trigname = "RI_ConstraintTrigger_a";
14189 2367 : fk_trigger->relation = NULL;
14190 2367 : fk_trigger->args = NIL;
14191 2367 : fk_trigger->row = true;
14192 2367 : fk_trigger->timing = TRIGGER_TYPE_AFTER;
14193 2367 : fk_trigger->events = TRIGGER_TYPE_UPDATE;
14194 2367 : fk_trigger->columns = NIL;
14195 2367 : fk_trigger->whenClause = NULL;
14196 2367 : fk_trigger->transitionRels = NIL;
14197 2367 : fk_trigger->constrrel = NULL;
14198 :
14199 2367 : switch (fkconstraint->fk_upd_action)
14200 : {
14201 2063 : case FKCONSTR_ACTION_NOACTION:
14202 2063 : fk_trigger->deferrable = fkconstraint->deferrable;
14203 2063 : fk_trigger->initdeferred = fkconstraint->initdeferred;
14204 2063 : fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
14205 2063 : break;
14206 24 : case FKCONSTR_ACTION_RESTRICT:
14207 24 : fk_trigger->deferrable = false;
14208 24 : fk_trigger->initdeferred = false;
14209 24 : fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
14210 24 : break;
14211 211 : case FKCONSTR_ACTION_CASCADE:
14212 211 : fk_trigger->deferrable = false;
14213 211 : fk_trigger->initdeferred = false;
14214 211 : fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
14215 211 : break;
14216 41 : case FKCONSTR_ACTION_SETNULL:
14217 41 : fk_trigger->deferrable = false;
14218 41 : fk_trigger->initdeferred = false;
14219 41 : fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
14220 41 : break;
14221 28 : case FKCONSTR_ACTION_SETDEFAULT:
14222 28 : fk_trigger->deferrable = false;
14223 28 : fk_trigger->initdeferred = false;
14224 28 : fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
14225 28 : break;
14226 0 : default:
14227 0 : elog(ERROR, "unrecognized FK action type: %d",
14228 : (int) fkconstraint->fk_upd_action);
14229 : break;
14230 : }
14231 :
14232 2367 : trigAddress = CreateTrigger(fk_trigger, NULL, refRelOid, myRelOid,
14233 : constraintOid, indexOid, InvalidOid,
14234 : parentUpdTrigger, NULL, true, false);
14235 2367 : if (updateTrigOid)
14236 2367 : *updateTrigOid = trigAddress.objectId;
14237 2367 : }
14238 :
14239 : /*
14240 : * createForeignKeyCheckTriggers
14241 : * Create the referencing-side "check" triggers that implement a foreign
14242 : * key.
14243 : *
14244 : * Returns the OIDs of the so created triggers in *insertTrigOid and
14245 : * *updateTrigOid.
14246 : */
14247 : static void
14248 2080 : createForeignKeyCheckTriggers(Oid myRelOid, Oid refRelOid,
14249 : Constraint *fkconstraint, Oid constraintOid,
14250 : Oid indexOid,
14251 : Oid parentInsTrigger, Oid parentUpdTrigger,
14252 : Oid *insertTrigOid, Oid *updateTrigOid)
14253 : {
14254 2080 : *insertTrigOid = CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint,
14255 : constraintOid, indexOid,
14256 : parentInsTrigger, true);
14257 2080 : *updateTrigOid = CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint,
14258 : constraintOid, indexOid,
14259 : parentUpdTrigger, false);
14260 2080 : }
14261 :
14262 : /*
14263 : * ALTER TABLE DROP CONSTRAINT
14264 : *
14265 : * Like DROP COLUMN, we can't use the normal ALTER TABLE recursion mechanism.
14266 : */
14267 : static void
14268 574 : ATExecDropConstraint(Relation rel, const char *constrName,
14269 : DropBehavior behavior, bool recurse,
14270 : bool missing_ok, LOCKMODE lockmode)
14271 : {
14272 : Relation conrel;
14273 : SysScanDesc scan;
14274 : ScanKeyData skey[3];
14275 : HeapTuple tuple;
14276 574 : bool found = false;
14277 :
14278 574 : conrel = table_open(ConstraintRelationId, RowExclusiveLock);
14279 :
14280 : /*
14281 : * Find and drop the target constraint
14282 : */
14283 574 : ScanKeyInit(&skey[0],
14284 : Anum_pg_constraint_conrelid,
14285 : BTEqualStrategyNumber, F_OIDEQ,
14286 : ObjectIdGetDatum(RelationGetRelid(rel)));
14287 574 : ScanKeyInit(&skey[1],
14288 : Anum_pg_constraint_contypid,
14289 : BTEqualStrategyNumber, F_OIDEQ,
14290 : ObjectIdGetDatum(InvalidOid));
14291 574 : ScanKeyInit(&skey[2],
14292 : Anum_pg_constraint_conname,
14293 : BTEqualStrategyNumber, F_NAMEEQ,
14294 : CStringGetDatum(constrName));
14295 574 : scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId,
14296 : true, NULL, 3, skey);
14297 :
14298 : /* There can be at most one matching row */
14299 574 : if (HeapTupleIsValid(tuple = systable_getnext(scan)))
14300 : {
14301 550 : dropconstraint_internal(rel, tuple, behavior, recurse, false,
14302 : missing_ok, lockmode);
14303 426 : found = true;
14304 : }
14305 :
14306 450 : systable_endscan(scan);
14307 :
14308 450 : if (!found)
14309 : {
14310 24 : if (!missing_ok)
14311 16 : ereport(ERROR,
14312 : errcode(ERRCODE_UNDEFINED_OBJECT),
14313 : errmsg("constraint \"%s\" of relation \"%s\" does not exist",
14314 : constrName, RelationGetRelationName(rel)));
14315 : else
14316 8 : ereport(NOTICE,
14317 : errmsg("constraint \"%s\" of relation \"%s\" does not exist, skipping",
14318 : constrName, RelationGetRelationName(rel)));
14319 : }
14320 :
14321 434 : table_close(conrel, RowExclusiveLock);
14322 434 : }
14323 :
14324 : /*
14325 : * Remove a constraint, using its pg_constraint tuple
14326 : *
14327 : * Implementation for ALTER TABLE DROP CONSTRAINT and ALTER TABLE ALTER COLUMN
14328 : * DROP NOT NULL.
14329 : *
14330 : * Returns the address of the constraint being removed.
14331 : */
14332 : static ObjectAddress
14333 854 : dropconstraint_internal(Relation rel, HeapTuple constraintTup, DropBehavior behavior,
14334 : bool recurse, bool recursing, bool missing_ok,
14335 : LOCKMODE lockmode)
14336 : {
14337 : Relation conrel;
14338 : Form_pg_constraint con;
14339 : ObjectAddress conobj;
14340 : List *children;
14341 854 : bool is_no_inherit_constraint = false;
14342 : char *constrName;
14343 854 : char *colname = NULL;
14344 :
14345 : /* Guard against stack overflow due to overly deep inheritance tree. */
14346 854 : check_stack_depth();
14347 :
14348 : /* At top level, permission check was done in ATPrepCmd, else do it */
14349 854 : if (recursing)
14350 159 : ATSimplePermissions(AT_DropConstraint, rel,
14351 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
14352 :
14353 850 : conrel = table_open(ConstraintRelationId, RowExclusiveLock);
14354 :
14355 850 : con = (Form_pg_constraint) GETSTRUCT(constraintTup);
14356 850 : constrName = NameStr(con->conname);
14357 :
14358 : /* Don't allow drop of inherited constraints */
14359 850 : if (con->coninhcount > 0 && !recursing)
14360 104 : ereport(ERROR,
14361 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
14362 : errmsg("cannot drop inherited constraint \"%s\" of relation \"%s\"",
14363 : constrName, RelationGetRelationName(rel))));
14364 :
14365 : /*
14366 : * Reset pg_constraint.attnotnull, if this is a not-null constraint.
14367 : *
14368 : * While doing that, we're in a good position to disallow dropping a not-
14369 : * null constraint underneath a primary key, a replica identity index, or
14370 : * a generated identity column.
14371 : */
14372 746 : if (con->contype == CONSTRAINT_NOTNULL)
14373 : {
14374 207 : Relation attrel = table_open(AttributeRelationId, RowExclusiveLock);
14375 207 : AttrNumber attnum = extractNotNullColumn(constraintTup);
14376 : Bitmapset *pkattrs;
14377 : Bitmapset *irattrs;
14378 : HeapTuple atttup;
14379 : Form_pg_attribute attForm;
14380 :
14381 : /* save column name for recursion step */
14382 207 : colname = get_attname(RelationGetRelid(rel), attnum, false);
14383 :
14384 : /*
14385 : * Disallow if it's in the primary key. For partitioned tables we
14386 : * cannot rely solely on RelationGetIndexAttrBitmap, because it'll
14387 : * return NULL if the primary key is invalid; but we still need to
14388 : * protect not-null constraints under such a constraint, so check the
14389 : * slow way.
14390 : */
14391 207 : pkattrs = RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_PRIMARY_KEY);
14392 :
14393 207 : if (pkattrs == NULL &&
14394 187 : rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
14395 : {
14396 12 : Oid pkindex = RelationGetPrimaryKeyIndex(rel, true);
14397 :
14398 12 : if (OidIsValid(pkindex))
14399 : {
14400 0 : Relation pk = relation_open(pkindex, AccessShareLock);
14401 :
14402 0 : pkattrs = NULL;
14403 0 : for (int i = 0; i < pk->rd_index->indnkeyatts; i++)
14404 0 : pkattrs = bms_add_member(pkattrs, pk->rd_index->indkey.values[i]);
14405 :
14406 0 : relation_close(pk, AccessShareLock);
14407 : }
14408 : }
14409 :
14410 227 : if (pkattrs &&
14411 20 : bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber, pkattrs))
14412 16 : ereport(ERROR,
14413 : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
14414 : errmsg("column \"%s\" is in a primary key",
14415 : get_attname(RelationGetRelid(rel), attnum, false)));
14416 :
14417 : /* Disallow if it's in the replica identity */
14418 191 : irattrs = RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_IDENTITY_KEY);
14419 191 : if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber, irattrs))
14420 8 : ereport(ERROR,
14421 : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
14422 : errmsg("column \"%s\" is in index used as replica identity",
14423 : get_attname(RelationGetRelid(rel), attnum, false)));
14424 :
14425 : /* Disallow if it's a GENERATED AS IDENTITY column */
14426 183 : atttup = SearchSysCacheCopyAttNum(RelationGetRelid(rel), attnum);
14427 183 : if (!HeapTupleIsValid(atttup))
14428 0 : elog(ERROR, "cache lookup failed for attribute %d of relation %u",
14429 : attnum, RelationGetRelid(rel));
14430 183 : attForm = (Form_pg_attribute) GETSTRUCT(atttup);
14431 183 : if (attForm->attidentity != '\0')
14432 0 : ereport(ERROR,
14433 : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
14434 : errmsg("column \"%s\" of relation \"%s\" is an identity column",
14435 : get_attname(RelationGetRelid(rel), attnum,
14436 : false),
14437 : RelationGetRelationName(rel)));
14438 :
14439 : /* All good -- reset attnotnull if needed */
14440 183 : if (attForm->attnotnull)
14441 : {
14442 183 : attForm->attnotnull = false;
14443 183 : CatalogTupleUpdate(attrel, &atttup->t_self, atttup);
14444 : }
14445 :
14446 183 : table_close(attrel, RowExclusiveLock);
14447 : }
14448 :
14449 722 : is_no_inherit_constraint = con->connoinherit;
14450 :
14451 : /*
14452 : * If it's a foreign-key constraint, we'd better lock the referenced table
14453 : * and check that that's not in use, just as we've already done for the
14454 : * constrained table (else we might, eg, be dropping a trigger that has
14455 : * unfired events). But we can/must skip that in the self-referential
14456 : * case.
14457 : */
14458 722 : if (con->contype == CONSTRAINT_FOREIGN &&
14459 112 : con->confrelid != RelationGetRelid(rel))
14460 : {
14461 : Relation frel;
14462 :
14463 : /* Must match lock taken by RemoveTriggerById: */
14464 112 : frel = table_open(con->confrelid, AccessExclusiveLock);
14465 112 : CheckAlterTableIsSafe(frel);
14466 108 : table_close(frel, NoLock);
14467 : }
14468 :
14469 : /*
14470 : * Perform the actual constraint deletion
14471 : */
14472 718 : ObjectAddressSet(conobj, ConstraintRelationId, con->oid);
14473 718 : performDeletion(&conobj, behavior, 0);
14474 :
14475 : /*
14476 : * For partitioned tables, non-CHECK, non-NOT-NULL inherited constraints
14477 : * are dropped via the dependency mechanism, so we're done here.
14478 : */
14479 694 : if (con->contype != CONSTRAINT_CHECK &&
14480 417 : con->contype != CONSTRAINT_NOTNULL &&
14481 234 : rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
14482 : {
14483 52 : table_close(conrel, RowExclusiveLock);
14484 52 : return conobj;
14485 : }
14486 :
14487 : /*
14488 : * Propagate to children as appropriate. Unlike most other ALTER
14489 : * routines, we have to do this one level of recursion at a time; we can't
14490 : * use find_all_inheritors to do it in one pass.
14491 : */
14492 642 : if (!is_no_inherit_constraint)
14493 452 : children = find_inheritance_children(RelationGetRelid(rel), lockmode);
14494 : else
14495 190 : children = NIL;
14496 :
14497 1559 : foreach_oid(childrelid, children)
14498 : {
14499 : Relation childrel;
14500 : HeapTuple tuple;
14501 : Form_pg_constraint childcon;
14502 :
14503 : /* find_inheritance_children already got lock */
14504 283 : childrel = table_open(childrelid, NoLock);
14505 283 : CheckAlterTableIsSafe(childrel);
14506 :
14507 : /*
14508 : * We search for not-null constraints by column name, and others by
14509 : * constraint name.
14510 : */
14511 283 : if (con->contype == CONSTRAINT_NOTNULL)
14512 : {
14513 98 : tuple = findNotNullConstraint(childrelid, colname);
14514 98 : if (!HeapTupleIsValid(tuple))
14515 0 : elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation %u",
14516 : colname, RelationGetRelid(childrel));
14517 : }
14518 : else
14519 : {
14520 : SysScanDesc scan;
14521 : ScanKeyData skey[3];
14522 :
14523 185 : ScanKeyInit(&skey[0],
14524 : Anum_pg_constraint_conrelid,
14525 : BTEqualStrategyNumber, F_OIDEQ,
14526 : ObjectIdGetDatum(childrelid));
14527 185 : ScanKeyInit(&skey[1],
14528 : Anum_pg_constraint_contypid,
14529 : BTEqualStrategyNumber, F_OIDEQ,
14530 : ObjectIdGetDatum(InvalidOid));
14531 185 : ScanKeyInit(&skey[2],
14532 : Anum_pg_constraint_conname,
14533 : BTEqualStrategyNumber, F_NAMEEQ,
14534 : CStringGetDatum(constrName));
14535 185 : scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId,
14536 : true, NULL, 3, skey);
14537 : /* There can only be one, so no need to loop */
14538 185 : tuple = systable_getnext(scan);
14539 185 : if (!HeapTupleIsValid(tuple))
14540 0 : ereport(ERROR,
14541 : (errcode(ERRCODE_UNDEFINED_OBJECT),
14542 : errmsg("constraint \"%s\" of relation \"%s\" does not exist",
14543 : constrName,
14544 : RelationGetRelationName(childrel))));
14545 185 : tuple = heap_copytuple(tuple);
14546 185 : systable_endscan(scan);
14547 : }
14548 :
14549 283 : childcon = (Form_pg_constraint) GETSTRUCT(tuple);
14550 :
14551 : /* Right now only CHECK and not-null constraints can be inherited */
14552 283 : if (childcon->contype != CONSTRAINT_CHECK &&
14553 98 : childcon->contype != CONSTRAINT_NOTNULL)
14554 0 : elog(ERROR, "inherited constraint is not a CHECK or not-null constraint");
14555 :
14556 283 : if (childcon->coninhcount <= 0) /* shouldn't happen */
14557 0 : elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
14558 : childrelid, NameStr(childcon->conname));
14559 :
14560 283 : if (recurse)
14561 : {
14562 : /*
14563 : * If the child constraint has other definition sources, just
14564 : * decrement its inheritance count; if not, recurse to delete it.
14565 : */
14566 215 : if (childcon->coninhcount == 1 && !childcon->conislocal)
14567 : {
14568 : /* Time to delete this child constraint, too */
14569 159 : dropconstraint_internal(childrel, tuple, behavior,
14570 : recurse, true, missing_ok,
14571 : lockmode);
14572 : }
14573 : else
14574 : {
14575 : /* Child constraint must survive my deletion */
14576 56 : childcon->coninhcount--;
14577 56 : CatalogTupleUpdate(conrel, &tuple->t_self, tuple);
14578 :
14579 : /* Make update visible */
14580 56 : CommandCounterIncrement();
14581 : }
14582 : }
14583 : else
14584 : {
14585 : /*
14586 : * If we were told to drop ONLY in this table (no recursion) and
14587 : * there are no further parents for this constraint, we need to
14588 : * mark the inheritors' constraints as locally defined rather than
14589 : * inherited.
14590 : */
14591 68 : childcon->coninhcount--;
14592 68 : if (childcon->coninhcount == 0)
14593 68 : childcon->conislocal = true;
14594 :
14595 68 : CatalogTupleUpdate(conrel, &tuple->t_self, tuple);
14596 :
14597 : /* Make update visible */
14598 68 : CommandCounterIncrement();
14599 : }
14600 :
14601 279 : heap_freetuple(tuple);
14602 :
14603 279 : table_close(childrel, NoLock);
14604 : }
14605 :
14606 638 : table_close(conrel, RowExclusiveLock);
14607 :
14608 638 : return conobj;
14609 : }
14610 :
14611 : /*
14612 : * ALTER COLUMN TYPE
14613 : *
14614 : * Unlike other subcommand types, we do parse transformation for ALTER COLUMN
14615 : * TYPE during phase 1 --- the AlterTableCmd passed in here is already
14616 : * transformed (and must be, because we rely on some transformed fields).
14617 : *
14618 : * The point of this is that the execution of all ALTER COLUMN TYPEs for a
14619 : * table will be done "in parallel" during phase 3, so all the USING
14620 : * expressions should be parsed assuming the original column types. Also,
14621 : * this allows a USING expression to refer to a field that will be dropped.
14622 : *
14623 : * To make this work safely, AT_PASS_DROP then AT_PASS_ALTER_TYPE must be
14624 : * the first two execution steps in phase 2; they must not see the effects
14625 : * of any other subcommand types, since the USING expressions are parsed
14626 : * against the unmodified table's state.
14627 : */
14628 : static void
14629 947 : ATPrepAlterColumnType(List **wqueue,
14630 : AlteredTableInfo *tab, Relation rel,
14631 : bool recurse, bool recursing,
14632 : AlterTableCmd *cmd, LOCKMODE lockmode,
14633 : AlterTableUtilityContext *context)
14634 : {
14635 947 : char *colName = cmd->name;
14636 947 : ColumnDef *def = (ColumnDef *) cmd->def;
14637 947 : TypeName *typeName = def->typeName;
14638 947 : Node *transform = def->cooked_default;
14639 : HeapTuple tuple;
14640 : Form_pg_attribute attTup;
14641 : AttrNumber attnum;
14642 : Oid targettype;
14643 : int32 targettypmod;
14644 : Oid targetcollid;
14645 : NewColumnValue *newval;
14646 947 : ParseState *pstate = make_parsestate(NULL);
14647 : AclResult aclresult;
14648 : bool is_expr;
14649 :
14650 947 : pstate->p_sourcetext = context->queryString;
14651 :
14652 947 : if (rel->rd_rel->reloftype && !recursing)
14653 4 : ereport(ERROR,
14654 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
14655 : errmsg("cannot alter column type of typed table"),
14656 : parser_errposition(pstate, def->location)));
14657 :
14658 : /* lookup the attribute so we can check inheritance status */
14659 943 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
14660 943 : if (!HeapTupleIsValid(tuple))
14661 0 : ereport(ERROR,
14662 : (errcode(ERRCODE_UNDEFINED_COLUMN),
14663 : errmsg("column \"%s\" of relation \"%s\" does not exist",
14664 : colName, RelationGetRelationName(rel)),
14665 : parser_errposition(pstate, def->location)));
14666 943 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
14667 943 : attnum = attTup->attnum;
14668 :
14669 : /* Can't alter a system attribute */
14670 943 : if (attnum <= 0)
14671 4 : ereport(ERROR,
14672 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
14673 : errmsg("cannot alter system column \"%s\"", colName),
14674 : parser_errposition(pstate, def->location)));
14675 :
14676 : /*
14677 : * Cannot specify USING when altering type of a generated column, because
14678 : * that would violate the generation expression.
14679 : */
14680 939 : if (attTup->attgenerated && def->cooked_default)
14681 8 : ereport(ERROR,
14682 : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
14683 : errmsg("cannot specify USING when altering type of generated column"),
14684 : errdetail("Column \"%s\" is a generated column.", colName),
14685 : parser_errposition(pstate, def->location)));
14686 :
14687 : /*
14688 : * Don't alter inherited columns. At outer level, there had better not be
14689 : * any inherited definition; when recursing, we assume this was checked at
14690 : * the parent level (see below).
14691 : */
14692 931 : if (attTup->attinhcount > 0 && !recursing)
14693 4 : ereport(ERROR,
14694 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
14695 : errmsg("cannot alter inherited column \"%s\"", colName),
14696 : parser_errposition(pstate, def->location)));
14697 :
14698 : /* Don't alter columns used in the partition key */
14699 927 : if (has_partition_attrs(rel,
14700 : bms_make_singleton(attnum - FirstLowInvalidHeapAttributeNumber),
14701 : &is_expr))
14702 12 : ereport(ERROR,
14703 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
14704 : errmsg("cannot alter column \"%s\" because it is part of the partition key of relation \"%s\"",
14705 : colName, RelationGetRelationName(rel)),
14706 : parser_errposition(pstate, def->location)));
14707 :
14708 : /* Look up the target type */
14709 915 : typenameTypeIdAndMod(pstate, typeName, &targettype, &targettypmod);
14710 :
14711 911 : aclresult = object_aclcheck(TypeRelationId, targettype, GetUserId(), ACL_USAGE);
14712 911 : if (aclresult != ACLCHECK_OK)
14713 8 : aclcheck_error_type(aclresult, targettype);
14714 :
14715 : /* And the collation */
14716 903 : targetcollid = GetColumnDefCollation(pstate, def, targettype);
14717 :
14718 : /* make sure datatype is legal for a column */
14719 1798 : CheckAttributeType(colName, targettype, targetcollid,
14720 899 : list_make1_oid(rel->rd_rel->reltype),
14721 899 : (attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL ? CHKATYPE_IS_VIRTUAL : 0));
14722 :
14723 891 : if (attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
14724 : {
14725 : /* do nothing */
14726 : }
14727 867 : else if (tab->relkind == RELKIND_RELATION ||
14728 133 : tab->relkind == RELKIND_PARTITIONED_TABLE)
14729 : {
14730 : /*
14731 : * Set up an expression to transform the old data value to the new
14732 : * type. If a USING option was given, use the expression as
14733 : * transformed by transformAlterTableStmt, else just take the old
14734 : * value and try to coerce it. We do this first so that type
14735 : * incompatibility can be detected before we waste effort, and because
14736 : * we need the expression to be parsed against the original table row
14737 : * type.
14738 : */
14739 778 : if (!transform)
14740 : {
14741 627 : transform = (Node *) makeVar(1, attnum,
14742 : attTup->atttypid, attTup->atttypmod,
14743 : attTup->attcollation,
14744 : 0);
14745 : }
14746 :
14747 778 : transform = coerce_to_target_type(pstate,
14748 : transform, exprType(transform),
14749 : targettype, targettypmod,
14750 : COERCION_ASSIGNMENT,
14751 : COERCE_IMPLICIT_CAST,
14752 : -1);
14753 778 : if (transform == NULL)
14754 : {
14755 : /* error text depends on whether USING was specified or not */
14756 15 : if (def->cooked_default != NULL)
14757 4 : ereport(ERROR,
14758 : (errcode(ERRCODE_DATATYPE_MISMATCH),
14759 : errmsg("result of USING clause for column \"%s\""
14760 : " cannot be cast automatically to type %s",
14761 : colName, format_type_be(targettype)),
14762 : errhint("You might need to add an explicit cast.")));
14763 : else
14764 11 : ereport(ERROR,
14765 : (errcode(ERRCODE_DATATYPE_MISMATCH),
14766 : errmsg("column \"%s\" cannot be cast automatically to type %s",
14767 : colName, format_type_be(targettype)),
14768 : !attTup->attgenerated ?
14769 : /* translator: USING is SQL, don't translate it */
14770 : errhint("You might need to specify \"USING %s::%s\".",
14771 : quote_identifier(colName),
14772 : format_type_with_typemod(targettype,
14773 : targettypmod)) : 0));
14774 : }
14775 :
14776 : /* Fix collations after all else */
14777 763 : assign_expr_collations(pstate, transform);
14778 :
14779 : /* Expand virtual generated columns in the expr. */
14780 763 : transform = expand_generated_columns_in_expr(transform, rel, 1);
14781 :
14782 : /* Plan the expr now so we can accurately assess the need to rewrite. */
14783 763 : transform = (Node *) expression_planner((Expr *) transform);
14784 :
14785 : /*
14786 : * Add a work queue item to make ATRewriteTable update the column
14787 : * contents.
14788 : */
14789 763 : newval = palloc0_object(NewColumnValue);
14790 763 : newval->attnum = attnum;
14791 763 : newval->expr = (Expr *) transform;
14792 763 : newval->is_generated = false;
14793 :
14794 763 : tab->newvals = lappend(tab->newvals, newval);
14795 1394 : if (ATColumnChangeRequiresRewrite(transform, attnum))
14796 631 : tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
14797 : }
14798 89 : else if (transform)
14799 8 : ereport(ERROR,
14800 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
14801 : errmsg("\"%s\" is not a table",
14802 : RelationGetRelationName(rel))));
14803 :
14804 868 : if (!RELKIND_HAS_STORAGE(tab->relkind) || attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
14805 : {
14806 : /*
14807 : * For relations or columns without storage, do this check now.
14808 : * Regular tables will check it later when the table is being
14809 : * rewritten.
14810 : */
14811 149 : find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
14812 : }
14813 :
14814 836 : ReleaseSysCache(tuple);
14815 :
14816 : /*
14817 : * Recurse manually by queueing a new command for each child, if
14818 : * necessary. We cannot apply ATSimpleRecursion here because we need to
14819 : * remap attribute numbers in the USING expression, if any.
14820 : *
14821 : * If we are told not to recurse, there had better not be any child
14822 : * tables; else the alter would put them out of step.
14823 : */
14824 836 : if (recurse)
14825 : {
14826 665 : Oid relid = RelationGetRelid(rel);
14827 : List *child_oids,
14828 : *child_numparents;
14829 : ListCell *lo,
14830 : *li;
14831 :
14832 665 : child_oids = find_all_inheritors(relid, lockmode,
14833 : &child_numparents);
14834 :
14835 : /*
14836 : * find_all_inheritors does the recursive search of the inheritance
14837 : * hierarchy, so all we have to do is process all of the relids in the
14838 : * list that it returns.
14839 : */
14840 1468 : forboth(lo, child_oids, li, child_numparents)
14841 : {
14842 819 : Oid childrelid = lfirst_oid(lo);
14843 819 : int numparents = lfirst_int(li);
14844 : Relation childrel;
14845 : HeapTuple childtuple;
14846 : Form_pg_attribute childattTup;
14847 :
14848 819 : if (childrelid == relid)
14849 665 : continue;
14850 :
14851 : /* find_all_inheritors already got lock */
14852 154 : childrel = relation_open(childrelid, NoLock);
14853 154 : CheckAlterTableIsSafe(childrel);
14854 :
14855 : /*
14856 : * Verify that the child doesn't have any inherited definitions of
14857 : * this column that came from outside this inheritance hierarchy.
14858 : * (renameatt makes a similar test, though in a different way
14859 : * because of its different recursion mechanism.)
14860 : */
14861 154 : childtuple = SearchSysCacheAttName(RelationGetRelid(childrel),
14862 : colName);
14863 154 : if (!HeapTupleIsValid(childtuple))
14864 0 : ereport(ERROR,
14865 : (errcode(ERRCODE_UNDEFINED_COLUMN),
14866 : errmsg("column \"%s\" of relation \"%s\" does not exist",
14867 : colName, RelationGetRelationName(childrel))));
14868 154 : childattTup = (Form_pg_attribute) GETSTRUCT(childtuple);
14869 :
14870 154 : if (childattTup->attinhcount > numparents)
14871 4 : ereport(ERROR,
14872 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
14873 : errmsg("cannot alter inherited column \"%s\" of relation \"%s\"",
14874 : colName, RelationGetRelationName(childrel))));
14875 :
14876 150 : ReleaseSysCache(childtuple);
14877 :
14878 : /*
14879 : * Remap the attribute numbers. If no USING expression was
14880 : * specified, there is no need for this step.
14881 : */
14882 150 : if (def->cooked_default)
14883 : {
14884 : AttrMap *attmap;
14885 : bool found_whole_row;
14886 :
14887 : /* create a copy to scribble on */
14888 52 : cmd = copyObject(cmd);
14889 :
14890 52 : attmap = build_attrmap_by_name(RelationGetDescr(childrel),
14891 : RelationGetDescr(rel),
14892 : false);
14893 104 : ((ColumnDef *) cmd->def)->cooked_default =
14894 52 : map_variable_attnos(def->cooked_default,
14895 : 1, 0,
14896 : attmap,
14897 : InvalidOid, &found_whole_row);
14898 52 : if (found_whole_row)
14899 4 : ereport(ERROR,
14900 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
14901 : errmsg("cannot convert whole-row table reference"),
14902 : errdetail("USING expression contains a whole-row table reference.")));
14903 48 : pfree(attmap);
14904 : }
14905 146 : ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, context);
14906 138 : relation_close(childrel, NoLock);
14907 : }
14908 : }
14909 204 : else if (!recursing &&
14910 33 : find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
14911 0 : ereport(ERROR,
14912 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
14913 : errmsg("type of inherited column \"%s\" must be changed in child tables too",
14914 : colName)));
14915 :
14916 820 : if (tab->relkind == RELKIND_COMPOSITE_TYPE)
14917 33 : ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);
14918 816 : }
14919 :
14920 : /*
14921 : * When the data type of a column is changed, a rewrite might not be required
14922 : * if the new type is sufficiently identical to the old one, and the USING
14923 : * clause isn't trying to insert some other value. It's safe to skip the
14924 : * rewrite in these cases:
14925 : *
14926 : * - the old type is binary coercible to the new type
14927 : * - the new type is an unconstrained domain over the old type
14928 : * - {NEW,OLD} or {OLD,NEW} is {timestamptz,timestamp} and the timezone is UTC
14929 : *
14930 : * In the case of a constrained domain, we could get by with scanning the
14931 : * table and checking the constraint rather than actually rewriting it, but we
14932 : * don't currently try to do that.
14933 : */
14934 : static bool
14935 763 : ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno)
14936 : {
14937 : Assert(expr != NULL);
14938 :
14939 : for (;;)
14940 : {
14941 : /* only one varno, so no need to check that */
14942 839 : if (IsA(expr, Var) && ((Var *) expr)->varattno == varattno)
14943 132 : return false;
14944 707 : else if (IsA(expr, RelabelType))
14945 68 : expr = (Node *) ((RelabelType *) expr)->arg;
14946 639 : else if (IsA(expr, CoerceToDomain))
14947 : {
14948 4 : CoerceToDomain *d = (CoerceToDomain *) expr;
14949 :
14950 4 : if (DomainHasConstraints(d->resulttype, NULL))
14951 4 : return true;
14952 0 : expr = (Node *) d->arg;
14953 : }
14954 635 : else if (IsA(expr, FuncExpr))
14955 : {
14956 502 : FuncExpr *f = (FuncExpr *) expr;
14957 :
14958 502 : switch (f->funcid)
14959 : {
14960 12 : case F_TIMESTAMPTZ_TIMESTAMP:
14961 : case F_TIMESTAMP_TIMESTAMPTZ:
14962 12 : if (TimestampTimestampTzRequiresRewrite())
14963 4 : return true;
14964 : else
14965 8 : expr = linitial(f->args);
14966 8 : break;
14967 490 : default:
14968 490 : return true;
14969 : }
14970 : }
14971 : else
14972 133 : return true;
14973 : }
14974 : }
14975 :
14976 : /*
14977 : * ALTER COLUMN .. SET DATA TYPE
14978 : *
14979 : * Return the address of the modified column.
14980 : */
14981 : static ObjectAddress
14982 792 : ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
14983 : AlterTableCmd *cmd, LOCKMODE lockmode)
14984 : {
14985 792 : char *colName = cmd->name;
14986 792 : ColumnDef *def = (ColumnDef *) cmd->def;
14987 792 : TypeName *typeName = def->typeName;
14988 : HeapTuple heapTup;
14989 : Form_pg_attribute attTup,
14990 : attOldTup;
14991 : AttrNumber attnum;
14992 : HeapTuple typeTuple;
14993 : Form_pg_type tform;
14994 : Oid targettype;
14995 : int32 targettypmod;
14996 : Oid targetcollid;
14997 : Node *defaultexpr;
14998 : Relation attrelation;
14999 : Relation depRel;
15000 : ScanKeyData key[3];
15001 : SysScanDesc scan;
15002 : HeapTuple depTup;
15003 : ObjectAddress address;
15004 :
15005 : /*
15006 : * Clear all the missing values if we're rewriting the table, since this
15007 : * renders them pointless.
15008 : */
15009 792 : if (tab->rewrite)
15010 : {
15011 : Relation newrel;
15012 :
15013 591 : newrel = table_open(RelationGetRelid(rel), NoLock);
15014 591 : RelationClearMissing(newrel);
15015 591 : relation_close(newrel, NoLock);
15016 : /* make sure we don't conflict with later attribute modifications */
15017 591 : CommandCounterIncrement();
15018 : }
15019 :
15020 792 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
15021 :
15022 : /* Look up the target column */
15023 792 : heapTup = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
15024 792 : if (!HeapTupleIsValid(heapTup)) /* shouldn't happen */
15025 0 : ereport(ERROR,
15026 : (errcode(ERRCODE_UNDEFINED_COLUMN),
15027 : errmsg("column \"%s\" of relation \"%s\" does not exist",
15028 : colName, RelationGetRelationName(rel))));
15029 792 : attTup = (Form_pg_attribute) GETSTRUCT(heapTup);
15030 792 : attnum = attTup->attnum;
15031 792 : attOldTup = TupleDescAttr(tab->oldDesc, attnum - 1);
15032 :
15033 : /* Check for multiple ALTER TYPE on same column --- can't cope */
15034 792 : if (attTup->atttypid != attOldTup->atttypid ||
15035 792 : attTup->atttypmod != attOldTup->atttypmod)
15036 0 : ereport(ERROR,
15037 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15038 : errmsg("cannot alter type of column \"%s\" twice",
15039 : colName)));
15040 :
15041 : /* Look up the target type (should not fail, since prep found it) */
15042 792 : typeTuple = typenameType(NULL, typeName, &targettypmod);
15043 792 : tform = (Form_pg_type) GETSTRUCT(typeTuple);
15044 792 : targettype = tform->oid;
15045 : /* And the collation */
15046 792 : targetcollid = GetColumnDefCollation(NULL, def, targettype);
15047 :
15048 : /*
15049 : * If there is a default expression for the column, get it and ensure we
15050 : * can coerce it to the new datatype. (We must do this before changing
15051 : * the column type, because build_column_default itself will try to
15052 : * coerce, and will not issue the error message we want if it fails.)
15053 : *
15054 : * We remove any implicit coercion steps at the top level of the old
15055 : * default expression; this has been agreed to satisfy the principle of
15056 : * least surprise. (The conversion to the new column type should act like
15057 : * it started from what the user sees as the stored expression, and the
15058 : * implicit coercions aren't going to be shown.)
15059 : */
15060 792 : if (attTup->atthasdef)
15061 : {
15062 64 : defaultexpr = build_column_default(rel, attnum);
15063 : Assert(defaultexpr);
15064 64 : defaultexpr = strip_implicit_coercions(defaultexpr);
15065 64 : defaultexpr = coerce_to_target_type(NULL, /* no UNKNOWN params */
15066 : defaultexpr, exprType(defaultexpr),
15067 : targettype, targettypmod,
15068 : COERCION_ASSIGNMENT,
15069 : COERCE_IMPLICIT_CAST,
15070 : -1);
15071 64 : if (defaultexpr == NULL)
15072 : {
15073 4 : if (attTup->attgenerated)
15074 0 : ereport(ERROR,
15075 : (errcode(ERRCODE_DATATYPE_MISMATCH),
15076 : errmsg("generation expression for column \"%s\" cannot be cast automatically to type %s",
15077 : colName, format_type_be(targettype))));
15078 : else
15079 4 : ereport(ERROR,
15080 : (errcode(ERRCODE_DATATYPE_MISMATCH),
15081 : errmsg("default for column \"%s\" cannot be cast automatically to type %s",
15082 : colName, format_type_be(targettype))));
15083 : }
15084 : }
15085 : else
15086 728 : defaultexpr = NULL;
15087 :
15088 : /*
15089 : * Find everything that depends on the column (constraints, indexes, etc),
15090 : * and record enough information to let us recreate the objects.
15091 : *
15092 : * The actual recreation does not happen here, but only after we have
15093 : * performed all the individual ALTER TYPE operations. We have to save
15094 : * the info before executing ALTER TYPE, though, else the deparser will
15095 : * get confused.
15096 : */
15097 788 : RememberAllDependentForRebuilding(tab, AT_AlterColumnType, rel, attnum, colName);
15098 :
15099 : /*
15100 : * Now scan for dependencies of this column on other things. The only
15101 : * things we should find are the dependency on the column datatype and
15102 : * possibly a collation dependency. Those can be removed.
15103 : */
15104 764 : depRel = table_open(DependRelationId, RowExclusiveLock);
15105 :
15106 764 : ScanKeyInit(&key[0],
15107 : Anum_pg_depend_classid,
15108 : BTEqualStrategyNumber, F_OIDEQ,
15109 : ObjectIdGetDatum(RelationRelationId));
15110 764 : ScanKeyInit(&key[1],
15111 : Anum_pg_depend_objid,
15112 : BTEqualStrategyNumber, F_OIDEQ,
15113 : ObjectIdGetDatum(RelationGetRelid(rel)));
15114 764 : ScanKeyInit(&key[2],
15115 : Anum_pg_depend_objsubid,
15116 : BTEqualStrategyNumber, F_INT4EQ,
15117 : Int32GetDatum((int32) attnum));
15118 :
15119 764 : scan = systable_beginscan(depRel, DependDependerIndexId, true,
15120 : NULL, 3, key);
15121 :
15122 766 : while (HeapTupleIsValid(depTup = systable_getnext(scan)))
15123 : {
15124 2 : Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup);
15125 : ObjectAddress foundObject;
15126 :
15127 2 : foundObject.classId = foundDep->refclassid;
15128 2 : foundObject.objectId = foundDep->refobjid;
15129 2 : foundObject.objectSubId = foundDep->refobjsubid;
15130 :
15131 2 : if (foundDep->deptype != DEPENDENCY_NORMAL)
15132 0 : elog(ERROR, "found unexpected dependency type '%c'",
15133 : foundDep->deptype);
15134 2 : if (!(foundDep->refclassid == TypeRelationId &&
15135 2 : foundDep->refobjid == attTup->atttypid) &&
15136 0 : !(foundDep->refclassid == CollationRelationId &&
15137 0 : foundDep->refobjid == attTup->attcollation))
15138 0 : elog(ERROR, "found unexpected dependency for column: %s",
15139 : getObjectDescription(&foundObject, false));
15140 :
15141 2 : CatalogTupleDelete(depRel, &depTup->t_self);
15142 : }
15143 :
15144 764 : systable_endscan(scan);
15145 :
15146 764 : table_close(depRel, RowExclusiveLock);
15147 :
15148 : /*
15149 : * Here we go --- change the recorded column type and collation. (Note
15150 : * heapTup is a copy of the syscache entry, so okay to scribble on.) First
15151 : * fix up the missing value if any.
15152 : */
15153 764 : if (attTup->atthasmissing)
15154 : {
15155 : Datum missingval;
15156 : bool missingNull;
15157 :
15158 : /* if rewrite is true the missing value should already be cleared */
15159 : Assert(tab->rewrite == 0);
15160 :
15161 : /* Get the missing value datum */
15162 4 : missingval = heap_getattr(heapTup,
15163 : Anum_pg_attribute_attmissingval,
15164 : attrelation->rd_att,
15165 : &missingNull);
15166 :
15167 : /* if it's a null array there is nothing to do */
15168 :
15169 4 : if (!missingNull)
15170 : {
15171 : /*
15172 : * Get the datum out of the array and repack it in a new array
15173 : * built with the new type data. We assume that since the table
15174 : * doesn't need rewriting, the actual Datum doesn't need to be
15175 : * changed, only the array metadata.
15176 : */
15177 :
15178 4 : int one = 1;
15179 : bool isNull;
15180 4 : Datum valuesAtt[Natts_pg_attribute] = {0};
15181 4 : bool nullsAtt[Natts_pg_attribute] = {0};
15182 4 : bool replacesAtt[Natts_pg_attribute] = {0};
15183 : HeapTuple newTup;
15184 :
15185 8 : missingval = array_get_element(missingval,
15186 : 1,
15187 : &one,
15188 : 0,
15189 4 : attTup->attlen,
15190 4 : attTup->attbyval,
15191 4 : attTup->attalign,
15192 : &isNull);
15193 4 : missingval = PointerGetDatum(construct_array(&missingval,
15194 : 1,
15195 : targettype,
15196 : tform->typlen,
15197 : tform->typbyval,
15198 : tform->typalign));
15199 :
15200 4 : valuesAtt[Anum_pg_attribute_attmissingval - 1] = missingval;
15201 4 : replacesAtt[Anum_pg_attribute_attmissingval - 1] = true;
15202 4 : nullsAtt[Anum_pg_attribute_attmissingval - 1] = false;
15203 :
15204 4 : newTup = heap_modify_tuple(heapTup, RelationGetDescr(attrelation),
15205 : valuesAtt, nullsAtt, replacesAtt);
15206 4 : heap_freetuple(heapTup);
15207 4 : heapTup = newTup;
15208 4 : attTup = (Form_pg_attribute) GETSTRUCT(heapTup);
15209 : }
15210 : }
15211 :
15212 764 : attTup->atttypid = targettype;
15213 764 : attTup->atttypmod = targettypmod;
15214 764 : attTup->attcollation = targetcollid;
15215 764 : if (list_length(typeName->arrayBounds) > PG_INT16_MAX)
15216 0 : ereport(ERROR,
15217 : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
15218 : errmsg("too many array dimensions"));
15219 764 : attTup->attndims = list_length(typeName->arrayBounds);
15220 764 : attTup->attlen = tform->typlen;
15221 764 : attTup->attbyval = tform->typbyval;
15222 764 : attTup->attalign = tform->typalign;
15223 764 : attTup->attstorage = tform->typstorage;
15224 764 : attTup->attcompression = InvalidCompressionMethod;
15225 :
15226 764 : ReleaseSysCache(typeTuple);
15227 :
15228 764 : CatalogTupleUpdate(attrelation, &heapTup->t_self, heapTup);
15229 :
15230 764 : table_close(attrelation, RowExclusiveLock);
15231 :
15232 : /* Install dependencies on new datatype and collation */
15233 764 : add_column_datatype_dependency(RelationGetRelid(rel), attnum, targettype);
15234 764 : add_column_collation_dependency(RelationGetRelid(rel), attnum, targetcollid);
15235 :
15236 : /*
15237 : * Drop any pg_statistic entry for the column, since it's now wrong type
15238 : */
15239 764 : RemoveStatistics(RelationGetRelid(rel), attnum);
15240 :
15241 764 : InvokeObjectPostAlterHook(RelationRelationId,
15242 : RelationGetRelid(rel), attnum);
15243 :
15244 : /*
15245 : * Update the default, if present, by brute force --- remove and re-add
15246 : * the default. Probably unsafe to take shortcuts, since the new version
15247 : * may well have additional dependencies. (It's okay to do this now,
15248 : * rather than after other ALTER TYPE commands, since the default won't
15249 : * depend on other column types.)
15250 : */
15251 764 : if (defaultexpr)
15252 : {
15253 : /*
15254 : * If it's a GENERATED default, drop its dependency records, in
15255 : * particular its INTERNAL dependency on the column, which would
15256 : * otherwise cause dependency.c to refuse to perform the deletion.
15257 : */
15258 60 : if (attTup->attgenerated)
15259 : {
15260 28 : Oid attrdefoid = GetAttrDefaultOid(RelationGetRelid(rel), attnum);
15261 :
15262 28 : if (!OidIsValid(attrdefoid))
15263 0 : elog(ERROR, "could not find attrdef tuple for relation %u attnum %d",
15264 : RelationGetRelid(rel), attnum);
15265 28 : (void) deleteDependencyRecordsFor(AttrDefaultRelationId, attrdefoid, false);
15266 : }
15267 :
15268 : /*
15269 : * Make updates-so-far visible, particularly the new pg_attribute row
15270 : * which will be updated again.
15271 : */
15272 60 : CommandCounterIncrement();
15273 :
15274 : /*
15275 : * We use RESTRICT here for safety, but at present we do not expect
15276 : * anything to depend on the default.
15277 : */
15278 60 : RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, true,
15279 : true);
15280 :
15281 60 : (void) StoreAttrDefault(rel, attnum, defaultexpr, true);
15282 : }
15283 :
15284 764 : ObjectAddressSubSet(address, RelationRelationId,
15285 : RelationGetRelid(rel), attnum);
15286 :
15287 : /* Cleanup */
15288 764 : heap_freetuple(heapTup);
15289 :
15290 764 : return address;
15291 : }
15292 :
15293 : /*
15294 : * Subroutine for ATExecAlterColumnType and ATExecSetExpression: Find everything
15295 : * that depends on the column (constraints, indexes, etc), and record enough
15296 : * information to let us recreate the objects.
15297 : */
15298 : static void
15299 945 : RememberAllDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype,
15300 : Relation rel, AttrNumber attnum, const char *colName)
15301 : {
15302 : Relation depRel;
15303 : ScanKeyData key[3];
15304 : SysScanDesc scan;
15305 : HeapTuple depTup;
15306 :
15307 : Assert(subtype == AT_AlterColumnType || subtype == AT_SetExpression);
15308 :
15309 945 : depRel = table_open(DependRelationId, RowExclusiveLock);
15310 :
15311 945 : ScanKeyInit(&key[0],
15312 : Anum_pg_depend_refclassid,
15313 : BTEqualStrategyNumber, F_OIDEQ,
15314 : ObjectIdGetDatum(RelationRelationId));
15315 945 : ScanKeyInit(&key[1],
15316 : Anum_pg_depend_refobjid,
15317 : BTEqualStrategyNumber, F_OIDEQ,
15318 : ObjectIdGetDatum(RelationGetRelid(rel)));
15319 945 : ScanKeyInit(&key[2],
15320 : Anum_pg_depend_refobjsubid,
15321 : BTEqualStrategyNumber, F_INT4EQ,
15322 : Int32GetDatum((int32) attnum));
15323 :
15324 945 : scan = systable_beginscan(depRel, DependReferenceIndexId, true,
15325 : NULL, 3, key);
15326 :
15327 1923 : while (HeapTupleIsValid(depTup = systable_getnext(scan)))
15328 : {
15329 1002 : Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup);
15330 : ObjectAddress foundObject;
15331 :
15332 1002 : foundObject.classId = foundDep->classid;
15333 1002 : foundObject.objectId = foundDep->objid;
15334 1002 : foundObject.objectSubId = foundDep->objsubid;
15335 :
15336 1002 : switch (foundObject.classId)
15337 : {
15338 192 : case RelationRelationId:
15339 : {
15340 192 : char relKind = get_rel_relkind(foundObject.objectId);
15341 :
15342 192 : if (relKind == RELKIND_INDEX ||
15343 : relKind == RELKIND_PARTITIONED_INDEX)
15344 : {
15345 : Assert(foundObject.objectSubId == 0);
15346 167 : RememberIndexForRebuilding(foundObject.objectId, tab);
15347 : }
15348 25 : else if (relKind == RELKIND_SEQUENCE)
15349 : {
15350 : /*
15351 : * This must be a SERIAL column's sequence. We need
15352 : * not do anything to it.
15353 : */
15354 : Assert(foundObject.objectSubId == 0);
15355 : }
15356 : else
15357 : {
15358 : /* Not expecting any other direct dependencies... */
15359 0 : elog(ERROR, "unexpected object depending on column: %s",
15360 : getObjectDescription(&foundObject, false));
15361 : }
15362 192 : break;
15363 : }
15364 :
15365 516 : case ConstraintRelationId:
15366 : Assert(foundObject.objectSubId == 0);
15367 516 : RememberConstraintForRebuilding(foundObject.objectId, tab);
15368 516 : break;
15369 :
15370 0 : case ProcedureRelationId:
15371 :
15372 : /*
15373 : * A new-style SQL function can depend on a column, if that
15374 : * column is referenced in the parsed function body. Ideally
15375 : * we'd automatically update the function by deparsing and
15376 : * reparsing it, but that's risky and might well fail anyhow.
15377 : * FIXME someday.
15378 : *
15379 : * This is only a problem for AT_AlterColumnType, not
15380 : * AT_SetExpression.
15381 : */
15382 0 : if (subtype == AT_AlterColumnType)
15383 0 : ereport(ERROR,
15384 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15385 : errmsg("cannot alter type of a column used by a function or procedure"),
15386 : errdetail("%s depends on column \"%s\"",
15387 : getObjectDescription(&foundObject, false),
15388 : colName)));
15389 0 : break;
15390 :
15391 8 : case RewriteRelationId:
15392 :
15393 : /*
15394 : * View/rule bodies have pretty much the same issues as
15395 : * function bodies. FIXME someday.
15396 : */
15397 8 : if (subtype == AT_AlterColumnType)
15398 8 : ereport(ERROR,
15399 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15400 : errmsg("cannot alter type of a column used by a view or rule"),
15401 : errdetail("%s depends on column \"%s\"",
15402 : getObjectDescription(&foundObject, false),
15403 : colName)));
15404 0 : break;
15405 :
15406 0 : case TriggerRelationId:
15407 :
15408 : /*
15409 : * A trigger can depend on a column because the column is
15410 : * specified as an update target, or because the column is
15411 : * used in the trigger's WHEN condition. The first case would
15412 : * not require any extra work, but the second case would
15413 : * require updating the WHEN expression, which has the same
15414 : * issues as above. Since we can't easily tell which case
15415 : * applies, we punt for both. FIXME someday.
15416 : */
15417 0 : if (subtype == AT_AlterColumnType)
15418 0 : ereport(ERROR,
15419 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15420 : errmsg("cannot alter type of a column used in a trigger definition"),
15421 : errdetail("%s depends on column \"%s\"",
15422 : getObjectDescription(&foundObject, false),
15423 : colName)));
15424 0 : break;
15425 :
15426 0 : case PolicyRelationId:
15427 :
15428 : /*
15429 : * A policy can depend on a column because the column is
15430 : * specified in the policy's USING or WITH CHECK qual
15431 : * expressions. It might be possible to rewrite and recheck
15432 : * the policy expression, but punt for now. It's certainly
15433 : * easy enough to remove and recreate the policy; still, FIXME
15434 : * someday.
15435 : */
15436 0 : if (subtype == AT_AlterColumnType)
15437 0 : ereport(ERROR,
15438 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15439 : errmsg("cannot alter type of a column used in a policy definition"),
15440 : errdetail("%s depends on column \"%s\"",
15441 : getObjectDescription(&foundObject, false),
15442 : colName)));
15443 0 : break;
15444 :
15445 233 : case AttrDefaultRelationId:
15446 : {
15447 233 : ObjectAddress col = GetAttrDefaultColumnAddress(foundObject.objectId);
15448 :
15449 450 : if (col.objectId == RelationGetRelid(rel) &&
15450 233 : col.objectSubId == attnum)
15451 : {
15452 : /*
15453 : * Ignore the column's own default expression. The
15454 : * caller deals with it.
15455 : */
15456 : }
15457 : else
15458 : {
15459 : /*
15460 : * This must be a reference from the expression of a
15461 : * generated column elsewhere in the same table.
15462 : * Changing the type/generated expression of a column
15463 : * that is used by a generated column is not allowed
15464 : * by SQL standard, so just punt for now. It might be
15465 : * doable with some thinking and effort.
15466 : */
15467 16 : if (subtype == AT_AlterColumnType)
15468 16 : ereport(ERROR,
15469 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15470 : errmsg("cannot alter type of a column used by a generated column"),
15471 : errdetail("Column \"%s\" is used by generated column \"%s\".",
15472 : colName,
15473 : get_attname(col.objectId,
15474 : col.objectSubId,
15475 : false))));
15476 : }
15477 217 : break;
15478 : }
15479 :
15480 53 : case StatisticExtRelationId:
15481 :
15482 : /*
15483 : * Give the extended-stats machinery a chance to fix anything
15484 : * that this column type change would break.
15485 : */
15486 53 : RememberStatisticsForRebuilding(foundObject.objectId, tab);
15487 53 : break;
15488 :
15489 0 : case PublicationRelRelationId:
15490 :
15491 : /*
15492 : * Column reference in a PUBLICATION ... FOR TABLE ... WHERE
15493 : * clause. Same issues as above. FIXME someday.
15494 : */
15495 0 : if (subtype == AT_AlterColumnType)
15496 0 : ereport(ERROR,
15497 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15498 : errmsg("cannot alter type of a column used by a publication WHERE clause"),
15499 : errdetail("%s depends on column \"%s\"",
15500 : getObjectDescription(&foundObject, false),
15501 : colName)));
15502 0 : break;
15503 :
15504 0 : default:
15505 :
15506 : /*
15507 : * We don't expect any other sorts of objects to depend on a
15508 : * column.
15509 : */
15510 0 : elog(ERROR, "unexpected object depending on column: %s",
15511 : getObjectDescription(&foundObject, false));
15512 : break;
15513 : }
15514 : }
15515 :
15516 921 : systable_endscan(scan);
15517 921 : table_close(depRel, NoLock);
15518 921 : }
15519 :
15520 : /*
15521 : * Subroutine for ATExecAlterColumnType: remember that a replica identity
15522 : * needs to be reset.
15523 : */
15524 : static void
15525 306 : RememberReplicaIdentityForRebuilding(Oid indoid, AlteredTableInfo *tab)
15526 : {
15527 306 : if (!get_index_isreplident(indoid))
15528 294 : return;
15529 :
15530 12 : if (tab->replicaIdentityIndex)
15531 0 : elog(ERROR, "relation %u has multiple indexes marked as replica identity", tab->relid);
15532 :
15533 12 : tab->replicaIdentityIndex = get_rel_name(indoid);
15534 : }
15535 :
15536 : /*
15537 : * Subroutine for ATExecAlterColumnType: remember any clustered index.
15538 : */
15539 : static void
15540 306 : RememberClusterOnForRebuilding(Oid indoid, AlteredTableInfo *tab)
15541 : {
15542 306 : if (!get_index_isclustered(indoid))
15543 294 : return;
15544 :
15545 12 : if (tab->clusterOnIndex)
15546 0 : elog(ERROR, "relation %u has multiple clustered indexes", tab->relid);
15547 :
15548 12 : tab->clusterOnIndex = get_rel_name(indoid);
15549 : }
15550 :
15551 : /*
15552 : * Subroutine for ATExecAlterColumnType: remember that a constraint needs
15553 : * to be rebuilt (which we might already know).
15554 : */
15555 : static void
15556 524 : RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab)
15557 : {
15558 : /*
15559 : * This de-duplication check is critical for two independent reasons: we
15560 : * mustn't try to recreate the same constraint twice, and if a constraint
15561 : * depends on more than one column whose type is to be altered, we must
15562 : * capture its definition string before applying any of the column type
15563 : * changes. ruleutils.c will get confused if we ask again later.
15564 : */
15565 524 : if (!list_member_oid(tab->changedConstraintOids, conoid))
15566 : {
15567 : /* OK, capture the constraint's existing definition string */
15568 452 : char *defstring = pg_get_constraintdef_command(conoid);
15569 : Oid indoid;
15570 :
15571 : /*
15572 : * It is critical to create not-null constraints ahead of primary key
15573 : * indexes; otherwise, the not-null constraint would be created by the
15574 : * primary key, and the constraint name would be wrong.
15575 : */
15576 452 : if (get_constraint_type(conoid) == CONSTRAINT_NOTNULL)
15577 : {
15578 163 : tab->changedConstraintOids = lcons_oid(conoid,
15579 : tab->changedConstraintOids);
15580 163 : tab->changedConstraintDefs = lcons(defstring,
15581 : tab->changedConstraintDefs);
15582 : }
15583 : else
15584 : {
15585 :
15586 289 : tab->changedConstraintOids = lappend_oid(tab->changedConstraintOids,
15587 : conoid);
15588 289 : tab->changedConstraintDefs = lappend(tab->changedConstraintDefs,
15589 : defstring);
15590 : }
15591 :
15592 : /*
15593 : * For the index of a constraint, if any, remember if it is used for
15594 : * the table's replica identity or if it is a clustered index, so that
15595 : * ATPostAlterTypeCleanup() can queue up commands necessary to restore
15596 : * those properties.
15597 : */
15598 452 : indoid = get_constraint_index(conoid);
15599 452 : if (OidIsValid(indoid))
15600 : {
15601 152 : RememberReplicaIdentityForRebuilding(indoid, tab);
15602 152 : RememberClusterOnForRebuilding(indoid, tab);
15603 : }
15604 : }
15605 524 : }
15606 :
15607 : /*
15608 : * Subroutine for ATExecAlterColumnType: remember that an index needs
15609 : * to be rebuilt (which we might already know).
15610 : */
15611 : static void
15612 167 : RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab)
15613 : {
15614 : /*
15615 : * This de-duplication check is critical for two independent reasons: we
15616 : * mustn't try to recreate the same index twice, and if an index depends
15617 : * on more than one column whose type is to be altered, we must capture
15618 : * its definition string before applying any of the column type changes.
15619 : * ruleutils.c will get confused if we ask again later.
15620 : */
15621 167 : if (!list_member_oid(tab->changedIndexOids, indoid))
15622 : {
15623 : /*
15624 : * Before adding it as an index-to-rebuild, we'd better see if it
15625 : * belongs to a constraint, and if so rebuild the constraint instead.
15626 : * Typically this check fails, because constraint indexes normally
15627 : * have only dependencies on their constraint. But it's possible for
15628 : * such an index to also have direct dependencies on table columns,
15629 : * for example with a partial exclusion constraint.
15630 : */
15631 162 : Oid conoid = get_index_constraint(indoid);
15632 :
15633 162 : if (OidIsValid(conoid))
15634 : {
15635 8 : RememberConstraintForRebuilding(conoid, tab);
15636 : }
15637 : else
15638 : {
15639 : /* OK, capture the index's existing definition string */
15640 154 : char *defstring = pg_get_indexdef_string(indoid);
15641 :
15642 154 : tab->changedIndexOids = lappend_oid(tab->changedIndexOids,
15643 : indoid);
15644 154 : tab->changedIndexDefs = lappend(tab->changedIndexDefs,
15645 : defstring);
15646 :
15647 : /*
15648 : * Remember if this index is used for the table's replica identity
15649 : * or if it is a clustered index, so that ATPostAlterTypeCleanup()
15650 : * can queue up commands necessary to restore those properties.
15651 : */
15652 154 : RememberReplicaIdentityForRebuilding(indoid, tab);
15653 154 : RememberClusterOnForRebuilding(indoid, tab);
15654 : }
15655 : }
15656 167 : }
15657 :
15658 : /*
15659 : * Subroutine for ATExecAlterColumnType: remember that a statistics object
15660 : * needs to be rebuilt (which we might already know).
15661 : */
15662 : static void
15663 53 : RememberStatisticsForRebuilding(Oid stxoid, AlteredTableInfo *tab)
15664 : {
15665 : /*
15666 : * This de-duplication check is critical for two independent reasons: we
15667 : * mustn't try to recreate the same statistics object twice, and if the
15668 : * statistics object depends on more than one column whose type is to be
15669 : * altered, we must capture its definition string before applying any of
15670 : * the type changes. ruleutils.c will get confused if we ask again later.
15671 : */
15672 53 : if (!list_member_oid(tab->changedStatisticsOids, stxoid))
15673 : {
15674 : /* OK, capture the statistics object's existing definition string */
15675 53 : char *defstring = pg_get_statisticsobjdef_string(stxoid);
15676 :
15677 53 : tab->changedStatisticsOids = lappend_oid(tab->changedStatisticsOids,
15678 : stxoid);
15679 53 : tab->changedStatisticsDefs = lappend(tab->changedStatisticsDefs,
15680 : defstring);
15681 : }
15682 53 : }
15683 :
15684 : /*
15685 : * Cleanup after we've finished all the ALTER TYPE or SET EXPRESSION
15686 : * operations for a particular relation. We have to drop and recreate all the
15687 : * indexes and constraints that depend on the altered columns. We do the
15688 : * actual dropping here, but re-creation is managed by adding work queue
15689 : * entries to do those steps later.
15690 : */
15691 : static void
15692 889 : ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
15693 : {
15694 : ObjectAddress obj;
15695 : ObjectAddresses *objects;
15696 : ListCell *def_item;
15697 : ListCell *oid_item;
15698 :
15699 : /*
15700 : * Collect all the constraints and indexes to drop so we can process them
15701 : * in a single call. That way we don't have to worry about dependencies
15702 : * among them.
15703 : */
15704 889 : objects = new_object_addresses();
15705 :
15706 : /*
15707 : * Re-parse the index and constraint definitions, and attach them to the
15708 : * appropriate work queue entries. We do this before dropping because in
15709 : * the case of a constraint on another table, we might not yet have
15710 : * exclusive lock on the table the constraint is attached to, and we need
15711 : * to get that before reparsing/dropping. (That's possible at least for
15712 : * FOREIGN KEY, CHECK, and EXCLUSION constraints; in non-FK cases it
15713 : * requires a dependency on the target table's composite type in the other
15714 : * table's constraint expressions.)
15715 : *
15716 : * We can't rely on the output of deparsing to tell us which relation to
15717 : * operate on, because concurrent activity might have made the name
15718 : * resolve differently. Instead, we've got to use the OID of the
15719 : * constraint or index we're processing to figure out which relation to
15720 : * operate on.
15721 : */
15722 1341 : forboth(oid_item, tab->changedConstraintOids,
15723 : def_item, tab->changedConstraintDefs)
15724 : {
15725 452 : Oid oldId = lfirst_oid(oid_item);
15726 : HeapTuple tup;
15727 : Form_pg_constraint con;
15728 : Oid relid;
15729 : Oid confrelid;
15730 : bool conislocal;
15731 :
15732 452 : tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(oldId));
15733 452 : if (!HeapTupleIsValid(tup)) /* should not happen */
15734 0 : elog(ERROR, "cache lookup failed for constraint %u", oldId);
15735 452 : con = (Form_pg_constraint) GETSTRUCT(tup);
15736 452 : if (OidIsValid(con->conrelid))
15737 443 : relid = con->conrelid;
15738 : else
15739 : {
15740 : /* must be a domain constraint */
15741 9 : relid = get_typ_typrelid(getBaseType(con->contypid));
15742 9 : if (!OidIsValid(relid))
15743 0 : elog(ERROR, "could not identify relation associated with constraint %u", oldId);
15744 : }
15745 452 : confrelid = con->confrelid;
15746 452 : conislocal = con->conislocal;
15747 452 : ReleaseSysCache(tup);
15748 :
15749 452 : ObjectAddressSet(obj, ConstraintRelationId, oldId);
15750 452 : add_exact_object_address(&obj, objects);
15751 :
15752 : /*
15753 : * If the constraint is inherited (only), we don't want to inject a
15754 : * new definition here; it'll get recreated when
15755 : * ATAddCheckNNConstraint recurses from adding the parent table's
15756 : * constraint. But we had to carry the info this far so that we can
15757 : * drop the constraint below.
15758 : */
15759 452 : if (!conislocal)
15760 34 : continue;
15761 :
15762 : /*
15763 : * When rebuilding another table's constraint that references the
15764 : * table we're modifying, we might not yet have any lock on the other
15765 : * table, so get one now. We'll need AccessExclusiveLock for the DROP
15766 : * CONSTRAINT step, so there's no value in asking for anything weaker.
15767 : */
15768 418 : if (relid != tab->relid)
15769 36 : LockRelationOid(relid, AccessExclusiveLock);
15770 :
15771 418 : ATPostAlterTypeParse(oldId, relid, confrelid,
15772 418 : (char *) lfirst(def_item),
15773 418 : wqueue, lockmode, tab->rewrite);
15774 : }
15775 1043 : forboth(oid_item, tab->changedIndexOids,
15776 : def_item, tab->changedIndexDefs)
15777 : {
15778 154 : Oid oldId = lfirst_oid(oid_item);
15779 : Oid relid;
15780 :
15781 154 : relid = IndexGetRelation(oldId, false);
15782 :
15783 : /*
15784 : * As above, make sure we have lock on the index's table if it's not
15785 : * the same table.
15786 : */
15787 154 : if (relid != tab->relid)
15788 12 : LockRelationOid(relid, AccessExclusiveLock);
15789 :
15790 154 : ATPostAlterTypeParse(oldId, relid, InvalidOid,
15791 154 : (char *) lfirst(def_item),
15792 154 : wqueue, lockmode, tab->rewrite);
15793 :
15794 154 : ObjectAddressSet(obj, RelationRelationId, oldId);
15795 154 : add_exact_object_address(&obj, objects);
15796 : }
15797 :
15798 : /* add dependencies for new statistics */
15799 942 : forboth(oid_item, tab->changedStatisticsOids,
15800 : def_item, tab->changedStatisticsDefs)
15801 : {
15802 53 : Oid oldId = lfirst_oid(oid_item);
15803 : Oid relid;
15804 :
15805 53 : relid = StatisticsGetRelation(oldId, false);
15806 :
15807 : /*
15808 : * As above, make sure we have lock on the statistics object's table
15809 : * if it's not the same table. However, we take
15810 : * ShareUpdateExclusiveLock here, aligning with the lock level used in
15811 : * CreateStatistics and RemoveStatisticsById.
15812 : *
15813 : * CAUTION: this should be done after all cases that grab
15814 : * AccessExclusiveLock, else we risk causing deadlock due to needing
15815 : * to promote our table lock.
15816 : */
15817 53 : if (relid != tab->relid)
15818 12 : LockRelationOid(relid, ShareUpdateExclusiveLock);
15819 :
15820 53 : ATPostAlterTypeParse(oldId, relid, InvalidOid,
15821 53 : (char *) lfirst(def_item),
15822 53 : wqueue, lockmode, tab->rewrite);
15823 :
15824 53 : ObjectAddressSet(obj, StatisticExtRelationId, oldId);
15825 53 : add_exact_object_address(&obj, objects);
15826 : }
15827 :
15828 : /*
15829 : * Queue up command to restore replica identity index marking
15830 : */
15831 889 : if (tab->replicaIdentityIndex)
15832 : {
15833 12 : AlterTableCmd *cmd = makeNode(AlterTableCmd);
15834 12 : ReplicaIdentityStmt *subcmd = makeNode(ReplicaIdentityStmt);
15835 :
15836 12 : subcmd->identity_type = REPLICA_IDENTITY_INDEX;
15837 12 : subcmd->name = tab->replicaIdentityIndex;
15838 12 : cmd->subtype = AT_ReplicaIdentity;
15839 12 : cmd->def = (Node *) subcmd;
15840 :
15841 : /* do it after indexes and constraints */
15842 12 : tab->subcmds[AT_PASS_OLD_CONSTR] =
15843 12 : lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
15844 : }
15845 :
15846 : /*
15847 : * Queue up command to restore marking of index used for cluster.
15848 : */
15849 889 : if (tab->clusterOnIndex)
15850 : {
15851 12 : AlterTableCmd *cmd = makeNode(AlterTableCmd);
15852 :
15853 12 : cmd->subtype = AT_ClusterOn;
15854 12 : cmd->name = tab->clusterOnIndex;
15855 :
15856 : /* do it after indexes and constraints */
15857 12 : tab->subcmds[AT_PASS_OLD_CONSTR] =
15858 12 : lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
15859 : }
15860 :
15861 : /*
15862 : * It should be okay to use DROP_RESTRICT here, since nothing else should
15863 : * be depending on these objects.
15864 : */
15865 889 : performMultipleDeletions(objects, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
15866 :
15867 889 : free_object_addresses(objects);
15868 :
15869 : /*
15870 : * The objects will get recreated during subsequent passes over the work
15871 : * queue.
15872 : */
15873 889 : }
15874 :
15875 : /*
15876 : * Parse the previously-saved definition string for a constraint, index or
15877 : * statistics object against the newly-established column data type(s), and
15878 : * queue up the resulting command parsetrees for execution.
15879 : *
15880 : * This might fail if, for example, you have a WHERE clause that uses an
15881 : * operator that's not available for the new column type.
15882 : */
15883 : static void
15884 625 : ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
15885 : List **wqueue, LOCKMODE lockmode, bool rewrite)
15886 : {
15887 : List *raw_parsetree_list;
15888 : List *querytree_list;
15889 : ListCell *list_item;
15890 : Relation rel;
15891 :
15892 : /*
15893 : * We expect that we will get only ALTER TABLE and CREATE INDEX
15894 : * statements. Hence, there is no need to pass them through
15895 : * parse_analyze_*() or the rewriter, but instead we need to pass them
15896 : * through parse_utilcmd.c to make them ready for execution.
15897 : */
15898 625 : raw_parsetree_list = raw_parser(cmd, RAW_PARSE_DEFAULT);
15899 625 : querytree_list = NIL;
15900 1250 : foreach(list_item, raw_parsetree_list)
15901 : {
15902 625 : RawStmt *rs = lfirst_node(RawStmt, list_item);
15903 625 : Node *stmt = rs->stmt;
15904 :
15905 625 : if (IsA(stmt, IndexStmt))
15906 154 : querytree_list = lappend(querytree_list,
15907 154 : transformIndexStmt(oldRelId,
15908 : (IndexStmt *) stmt,
15909 : cmd));
15910 471 : else if (IsA(stmt, AlterTableStmt))
15911 : {
15912 : List *beforeStmts;
15913 : List *afterStmts;
15914 :
15915 409 : stmt = (Node *) transformAlterTableStmt(oldRelId,
15916 : (AlterTableStmt *) stmt,
15917 : cmd,
15918 : &beforeStmts,
15919 : &afterStmts);
15920 409 : querytree_list = list_concat(querytree_list, beforeStmts);
15921 409 : querytree_list = lappend(querytree_list, stmt);
15922 409 : querytree_list = list_concat(querytree_list, afterStmts);
15923 : }
15924 62 : else if (IsA(stmt, CreateStatsStmt))
15925 53 : querytree_list = lappend(querytree_list,
15926 53 : transformStatsStmt(oldRelId,
15927 : (CreateStatsStmt *) stmt,
15928 : cmd));
15929 : else
15930 9 : querytree_list = lappend(querytree_list, stmt);
15931 : }
15932 :
15933 : /* Caller should already have acquired whatever lock we need. */
15934 625 : rel = relation_open(oldRelId, NoLock);
15935 :
15936 : /*
15937 : * Attach each generated command to the proper place in the work queue.
15938 : * Note this could result in creation of entirely new work-queue entries.
15939 : *
15940 : * Also note that we have to tweak the command subtypes, because it turns
15941 : * out that re-creation of indexes and constraints has to act a bit
15942 : * differently from initial creation.
15943 : */
15944 1250 : foreach(list_item, querytree_list)
15945 : {
15946 625 : Node *stm = (Node *) lfirst(list_item);
15947 : AlteredTableInfo *tab;
15948 :
15949 625 : tab = ATGetQueueEntry(wqueue, rel);
15950 :
15951 625 : if (IsA(stm, IndexStmt))
15952 : {
15953 154 : IndexStmt *stmt = (IndexStmt *) stm;
15954 : AlterTableCmd *newcmd;
15955 :
15956 154 : if (!rewrite)
15957 41 : TryReuseIndex(oldId, stmt);
15958 154 : stmt->reset_default_tblspc = true;
15959 : /* keep the index's comment */
15960 154 : stmt->idxcomment = GetComment(oldId, RelationRelationId, 0);
15961 :
15962 154 : newcmd = makeNode(AlterTableCmd);
15963 154 : newcmd->subtype = AT_ReAddIndex;
15964 154 : newcmd->def = (Node *) stmt;
15965 154 : tab->subcmds[AT_PASS_OLD_INDEX] =
15966 154 : lappend(tab->subcmds[AT_PASS_OLD_INDEX], newcmd);
15967 : }
15968 471 : else if (IsA(stm, AlterTableStmt))
15969 : {
15970 409 : AlterTableStmt *stmt = (AlterTableStmt *) stm;
15971 : ListCell *lcmd;
15972 :
15973 818 : foreach(lcmd, stmt->cmds)
15974 : {
15975 409 : AlterTableCmd *cmd = lfirst_node(AlterTableCmd, lcmd);
15976 :
15977 409 : if (cmd->subtype == AT_AddIndex)
15978 : {
15979 : IndexStmt *indstmt;
15980 : Oid indoid;
15981 :
15982 152 : indstmt = castNode(IndexStmt, cmd->def);
15983 152 : indoid = get_constraint_index(oldId);
15984 :
15985 152 : if (!rewrite)
15986 32 : TryReuseIndex(indoid, indstmt);
15987 : /* keep any comment on the index */
15988 152 : indstmt->idxcomment = GetComment(indoid,
15989 : RelationRelationId, 0);
15990 152 : indstmt->reset_default_tblspc = true;
15991 :
15992 152 : cmd->subtype = AT_ReAddIndex;
15993 152 : tab->subcmds[AT_PASS_OLD_INDEX] =
15994 152 : lappend(tab->subcmds[AT_PASS_OLD_INDEX], cmd);
15995 :
15996 : /* recreate any comment on the constraint */
15997 152 : RebuildConstraintComment(tab,
15998 : AT_PASS_OLD_INDEX,
15999 : oldId,
16000 : rel,
16001 : NIL,
16002 152 : indstmt->idxname);
16003 : }
16004 257 : else if (cmd->subtype == AT_AddConstraint)
16005 : {
16006 257 : Constraint *con = castNode(Constraint, cmd->def);
16007 :
16008 257 : con->old_pktable_oid = refRelId;
16009 : /* rewriting neither side of a FK */
16010 257 : if (con->contype == CONSTR_FOREIGN &&
16011 48 : !rewrite && tab->rewrite == 0)
16012 4 : TryReuseForeignKey(oldId, con);
16013 257 : con->reset_default_tblspc = true;
16014 257 : cmd->subtype = AT_ReAddConstraint;
16015 257 : tab->subcmds[AT_PASS_OLD_CONSTR] =
16016 257 : lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
16017 :
16018 : /*
16019 : * Recreate any comment on the constraint. If we have
16020 : * recreated a primary key, then transformTableConstraint
16021 : * has added an unnamed not-null constraint here; skip
16022 : * this in that case.
16023 : */
16024 257 : if (con->conname)
16025 257 : RebuildConstraintComment(tab,
16026 : AT_PASS_OLD_CONSTR,
16027 : oldId,
16028 : rel,
16029 : NIL,
16030 257 : con->conname);
16031 : else
16032 : Assert(con->contype == CONSTR_NOTNULL);
16033 : }
16034 : else
16035 0 : elog(ERROR, "unexpected statement subtype: %d",
16036 : (int) cmd->subtype);
16037 : }
16038 : }
16039 62 : else if (IsA(stm, AlterDomainStmt))
16040 : {
16041 9 : AlterDomainStmt *stmt = (AlterDomainStmt *) stm;
16042 :
16043 9 : if (stmt->subtype == AD_AddConstraint)
16044 : {
16045 9 : Constraint *con = castNode(Constraint, stmt->def);
16046 9 : AlterTableCmd *cmd = makeNode(AlterTableCmd);
16047 :
16048 9 : cmd->subtype = AT_ReAddDomainConstraint;
16049 9 : cmd->def = (Node *) stmt;
16050 9 : tab->subcmds[AT_PASS_OLD_CONSTR] =
16051 9 : lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
16052 :
16053 : /* recreate any comment on the constraint */
16054 9 : RebuildConstraintComment(tab,
16055 : AT_PASS_OLD_CONSTR,
16056 : oldId,
16057 : NULL,
16058 : stmt->typeName,
16059 9 : con->conname);
16060 : }
16061 : else
16062 0 : elog(ERROR, "unexpected statement subtype: %d",
16063 : (int) stmt->subtype);
16064 : }
16065 53 : else if (IsA(stm, CreateStatsStmt))
16066 : {
16067 53 : CreateStatsStmt *stmt = (CreateStatsStmt *) stm;
16068 : AlterTableCmd *newcmd;
16069 :
16070 : /* keep the statistics object's comment */
16071 53 : stmt->stxcomment = GetComment(oldId, StatisticExtRelationId, 0);
16072 :
16073 53 : newcmd = makeNode(AlterTableCmd);
16074 53 : newcmd->subtype = AT_ReAddStatistics;
16075 53 : newcmd->def = (Node *) stmt;
16076 53 : tab->subcmds[AT_PASS_MISC] =
16077 53 : lappend(tab->subcmds[AT_PASS_MISC], newcmd);
16078 : }
16079 : else
16080 0 : elog(ERROR, "unexpected statement type: %d",
16081 : (int) nodeTag(stm));
16082 : }
16083 :
16084 625 : relation_close(rel, NoLock);
16085 625 : }
16086 :
16087 : /*
16088 : * Subroutine for ATPostAlterTypeParse() to recreate any existing comment
16089 : * for a table or domain constraint that is being rebuilt.
16090 : *
16091 : * objid is the OID of the constraint.
16092 : * Pass "rel" for a table constraint, or "domname" (domain's qualified name
16093 : * as a string list) for a domain constraint.
16094 : * (We could dig that info, as well as the conname, out of the pg_constraint
16095 : * entry; but callers already have them so might as well pass them.)
16096 : */
16097 : static void
16098 418 : RebuildConstraintComment(AlteredTableInfo *tab, AlterTablePass pass, Oid objid,
16099 : Relation rel, List *domname,
16100 : const char *conname)
16101 : {
16102 : CommentStmt *cmd;
16103 : char *comment_str;
16104 : AlterTableCmd *newcmd;
16105 :
16106 : /* Look for comment for object wanted, and leave if none */
16107 418 : comment_str = GetComment(objid, ConstraintRelationId, 0);
16108 418 : if (comment_str == NULL)
16109 358 : return;
16110 :
16111 : /* Build CommentStmt node, copying all input data for safety */
16112 60 : cmd = makeNode(CommentStmt);
16113 60 : if (rel)
16114 : {
16115 52 : cmd->objtype = OBJECT_TABCONSTRAINT;
16116 52 : cmd->object = (Node *)
16117 52 : list_make3(makeString(get_namespace_name(RelationGetNamespace(rel))),
16118 : makeString(pstrdup(RelationGetRelationName(rel))),
16119 : makeString(pstrdup(conname)));
16120 : }
16121 : else
16122 : {
16123 8 : cmd->objtype = OBJECT_DOMCONSTRAINT;
16124 8 : cmd->object = (Node *)
16125 8 : list_make2(makeTypeNameFromNameList(copyObject(domname)),
16126 : makeString(pstrdup(conname)));
16127 : }
16128 60 : cmd->comment = comment_str;
16129 :
16130 : /* Append it to list of commands */
16131 60 : newcmd = makeNode(AlterTableCmd);
16132 60 : newcmd->subtype = AT_ReAddComment;
16133 60 : newcmd->def = (Node *) cmd;
16134 60 : tab->subcmds[pass] = lappend(tab->subcmds[pass], newcmd);
16135 : }
16136 :
16137 : /*
16138 : * Subroutine for ATPostAlterTypeParse(). Calls out to CheckIndexCompatible()
16139 : * for the real analysis, then mutates the IndexStmt based on that verdict.
16140 : */
16141 : static void
16142 73 : TryReuseIndex(Oid oldId, IndexStmt *stmt)
16143 : {
16144 73 : if (CheckIndexCompatible(oldId,
16145 73 : stmt->accessMethod,
16146 73 : stmt->indexParams,
16147 73 : stmt->excludeOpNames,
16148 73 : stmt->iswithoutoverlaps))
16149 : {
16150 69 : Relation irel = index_open(oldId, NoLock);
16151 :
16152 : /* If it's a partitioned index, there is no storage to share. */
16153 69 : if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
16154 : {
16155 49 : stmt->oldNumber = irel->rd_locator.relNumber;
16156 49 : stmt->oldCreateSubid = irel->rd_createSubid;
16157 49 : stmt->oldFirstRelfilelocatorSubid = irel->rd_firstRelfilelocatorSubid;
16158 : }
16159 69 : index_close(irel, NoLock);
16160 : }
16161 73 : }
16162 :
16163 : /*
16164 : * Subroutine for ATPostAlterTypeParse().
16165 : *
16166 : * Stash the old P-F equality operator into the Constraint node, for possible
16167 : * use by ATAddForeignKeyConstraint() in determining whether revalidation of
16168 : * this constraint can be skipped.
16169 : */
16170 : static void
16171 4 : TryReuseForeignKey(Oid oldId, Constraint *con)
16172 : {
16173 : HeapTuple tup;
16174 : Datum adatum;
16175 : ArrayType *arr;
16176 : Oid *rawarr;
16177 : int numkeys;
16178 : int i;
16179 :
16180 : Assert(con->contype == CONSTR_FOREIGN);
16181 : Assert(con->old_conpfeqop == NIL); /* already prepared this node */
16182 :
16183 4 : tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(oldId));
16184 4 : if (!HeapTupleIsValid(tup)) /* should not happen */
16185 0 : elog(ERROR, "cache lookup failed for constraint %u", oldId);
16186 :
16187 4 : adatum = SysCacheGetAttrNotNull(CONSTROID, tup,
16188 : Anum_pg_constraint_conpfeqop);
16189 4 : arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
16190 4 : numkeys = ARR_DIMS(arr)[0];
16191 : /* test follows the one in ri_FetchConstraintInfo() */
16192 4 : if (ARR_NDIM(arr) != 1 ||
16193 4 : ARR_HASNULL(arr) ||
16194 4 : ARR_ELEMTYPE(arr) != OIDOID)
16195 0 : elog(ERROR, "conpfeqop is not a 1-D Oid array");
16196 4 : rawarr = (Oid *) ARR_DATA_PTR(arr);
16197 :
16198 : /* stash a List of the operator Oids in our Constraint node */
16199 8 : for (i = 0; i < numkeys; i++)
16200 4 : con->old_conpfeqop = lappend_oid(con->old_conpfeqop, rawarr[i]);
16201 :
16202 4 : ReleaseSysCache(tup);
16203 4 : }
16204 :
16205 : /*
16206 : * ALTER COLUMN .. OPTIONS ( ... )
16207 : *
16208 : * Returns the address of the modified column
16209 : */
16210 : static ObjectAddress
16211 94 : ATExecAlterColumnGenericOptions(Relation rel,
16212 : const char *colName,
16213 : List *options,
16214 : LOCKMODE lockmode)
16215 : {
16216 : Relation ftrel;
16217 : Relation attrel;
16218 : ForeignServer *server;
16219 : ForeignDataWrapper *fdw;
16220 : HeapTuple tuple;
16221 : HeapTuple newtuple;
16222 : bool isnull;
16223 : Datum repl_val[Natts_pg_attribute];
16224 : bool repl_null[Natts_pg_attribute];
16225 : bool repl_repl[Natts_pg_attribute];
16226 : Datum datum;
16227 : Form_pg_foreign_table fttableform;
16228 : Form_pg_attribute atttableform;
16229 : AttrNumber attnum;
16230 : ObjectAddress address;
16231 :
16232 94 : if (options == NIL)
16233 0 : return InvalidObjectAddress;
16234 :
16235 : /* First, determine FDW validator associated to the foreign table. */
16236 94 : ftrel = table_open(ForeignTableRelationId, AccessShareLock);
16237 94 : tuple = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(rel->rd_id));
16238 94 : if (!HeapTupleIsValid(tuple))
16239 0 : ereport(ERROR,
16240 : (errcode(ERRCODE_UNDEFINED_OBJECT),
16241 : errmsg("foreign table \"%s\" does not exist",
16242 : RelationGetRelationName(rel))));
16243 94 : fttableform = (Form_pg_foreign_table) GETSTRUCT(tuple);
16244 94 : server = GetForeignServer(fttableform->ftserver);
16245 94 : fdw = GetForeignDataWrapper(server->fdwid);
16246 :
16247 94 : table_close(ftrel, AccessShareLock);
16248 94 : ReleaseSysCache(tuple);
16249 :
16250 94 : attrel = table_open(AttributeRelationId, RowExclusiveLock);
16251 94 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
16252 94 : if (!HeapTupleIsValid(tuple))
16253 0 : ereport(ERROR,
16254 : (errcode(ERRCODE_UNDEFINED_COLUMN),
16255 : errmsg("column \"%s\" of relation \"%s\" does not exist",
16256 : colName, RelationGetRelationName(rel))));
16257 :
16258 : /* Prevent them from altering a system attribute */
16259 94 : atttableform = (Form_pg_attribute) GETSTRUCT(tuple);
16260 94 : attnum = atttableform->attnum;
16261 94 : if (attnum <= 0)
16262 4 : ereport(ERROR,
16263 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
16264 : errmsg("cannot alter system column \"%s\"", colName)));
16265 :
16266 :
16267 : /* Initialize buffers for new tuple values */
16268 90 : memset(repl_val, 0, sizeof(repl_val));
16269 90 : memset(repl_null, false, sizeof(repl_null));
16270 90 : memset(repl_repl, false, sizeof(repl_repl));
16271 :
16272 : /* Extract the current options */
16273 90 : datum = SysCacheGetAttr(ATTNAME,
16274 : tuple,
16275 : Anum_pg_attribute_attfdwoptions,
16276 : &isnull);
16277 90 : if (isnull)
16278 84 : datum = PointerGetDatum(NULL);
16279 :
16280 : /* Transform the options */
16281 90 : datum = transformGenericOptions(AttributeRelationId,
16282 : datum,
16283 : options,
16284 : fdw->fdwvalidator);
16285 :
16286 90 : if (DatumGetPointer(datum) != NULL)
16287 90 : repl_val[Anum_pg_attribute_attfdwoptions - 1] = datum;
16288 : else
16289 0 : repl_null[Anum_pg_attribute_attfdwoptions - 1] = true;
16290 :
16291 90 : repl_repl[Anum_pg_attribute_attfdwoptions - 1] = true;
16292 :
16293 : /* Everything looks good - update the tuple */
16294 :
16295 90 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(attrel),
16296 : repl_val, repl_null, repl_repl);
16297 :
16298 90 : CatalogTupleUpdate(attrel, &newtuple->t_self, newtuple);
16299 :
16300 90 : InvokeObjectPostAlterHook(RelationRelationId,
16301 : RelationGetRelid(rel),
16302 : atttableform->attnum);
16303 90 : ObjectAddressSubSet(address, RelationRelationId,
16304 : RelationGetRelid(rel), attnum);
16305 :
16306 90 : ReleaseSysCache(tuple);
16307 :
16308 90 : table_close(attrel, RowExclusiveLock);
16309 :
16310 90 : heap_freetuple(newtuple);
16311 :
16312 90 : return address;
16313 : }
16314 :
16315 : /*
16316 : * ALTER TABLE OWNER
16317 : *
16318 : * recursing is true if we are recursing from a table to its indexes,
16319 : * sequences, or toast table. We don't allow the ownership of those things to
16320 : * be changed separately from the parent table. Also, we can skip permission
16321 : * checks (this is necessary not just an optimization, else we'd fail to
16322 : * handle toast tables properly).
16323 : *
16324 : * recursing is also true if ALTER TYPE OWNER is calling us to fix up a
16325 : * free-standing composite type.
16326 : */
16327 : void
16328 1356 : ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lockmode)
16329 : {
16330 : Relation target_rel;
16331 : Relation class_rel;
16332 : HeapTuple tuple;
16333 : Form_pg_class tuple_class;
16334 :
16335 : /*
16336 : * Get exclusive lock till end of transaction on the target table. Use
16337 : * relation_open so that we can work on indexes and sequences.
16338 : */
16339 1356 : target_rel = relation_open(relationOid, lockmode);
16340 :
16341 : /* Get its pg_class tuple, too */
16342 1356 : class_rel = table_open(RelationRelationId, RowExclusiveLock);
16343 :
16344 1356 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relationOid));
16345 1356 : if (!HeapTupleIsValid(tuple))
16346 0 : elog(ERROR, "cache lookup failed for relation %u", relationOid);
16347 1356 : tuple_class = (Form_pg_class) GETSTRUCT(tuple);
16348 :
16349 : /* Can we change the ownership of this tuple? */
16350 1356 : switch (tuple_class->relkind)
16351 : {
16352 1138 : case RELKIND_RELATION:
16353 : case RELKIND_VIEW:
16354 : case RELKIND_MATVIEW:
16355 : case RELKIND_FOREIGN_TABLE:
16356 : case RELKIND_PARTITIONED_TABLE:
16357 : case RELKIND_PROPGRAPH:
16358 : /* ok to change owner */
16359 1138 : break;
16360 91 : case RELKIND_INDEX:
16361 91 : if (!recursing)
16362 : {
16363 : /*
16364 : * Because ALTER INDEX OWNER used to be allowed, and in fact
16365 : * is generated by old versions of pg_dump, we give a warning
16366 : * and do nothing rather than erroring out. Also, to avoid
16367 : * unnecessary chatter while restoring those old dumps, say
16368 : * nothing at all if the command would be a no-op anyway.
16369 : */
16370 0 : if (tuple_class->relowner != newOwnerId)
16371 0 : ereport(WARNING,
16372 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
16373 : errmsg("cannot change owner of index \"%s\"",
16374 : NameStr(tuple_class->relname)),
16375 : errhint("Change the ownership of the index's table instead.")));
16376 : /* quick hack to exit via the no-op path */
16377 0 : newOwnerId = tuple_class->relowner;
16378 : }
16379 91 : break;
16380 14 : case RELKIND_PARTITIONED_INDEX:
16381 14 : if (recursing)
16382 14 : break;
16383 0 : ereport(ERROR,
16384 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
16385 : errmsg("cannot change owner of index \"%s\"",
16386 : NameStr(tuple_class->relname)),
16387 : errhint("Change the ownership of the index's table instead.")));
16388 : break;
16389 65 : case RELKIND_SEQUENCE:
16390 65 : if (!recursing &&
16391 35 : tuple_class->relowner != newOwnerId)
16392 : {
16393 : /* if it's an owned sequence, disallow changing it by itself */
16394 : Oid tableId;
16395 : int32 colId;
16396 :
16397 0 : if (sequenceIsOwned(relationOid, DEPENDENCY_AUTO, &tableId, &colId) ||
16398 0 : sequenceIsOwned(relationOid, DEPENDENCY_INTERNAL, &tableId, &colId))
16399 0 : ereport(ERROR,
16400 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
16401 : errmsg("cannot change owner of sequence \"%s\"",
16402 : NameStr(tuple_class->relname)),
16403 : errdetail("Sequence \"%s\" is linked to table \"%s\".",
16404 : NameStr(tuple_class->relname),
16405 : get_rel_name(tableId))));
16406 : }
16407 65 : break;
16408 5 : case RELKIND_COMPOSITE_TYPE:
16409 5 : if (recursing)
16410 5 : break;
16411 0 : ereport(ERROR,
16412 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
16413 : errmsg("\"%s\" is a composite type",
16414 : NameStr(tuple_class->relname)),
16415 : /* translator: %s is an SQL ALTER command */
16416 : errhint("Use %s instead.",
16417 : "ALTER TYPE")));
16418 : break;
16419 43 : case RELKIND_TOASTVALUE:
16420 43 : if (recursing)
16421 43 : break;
16422 : pg_fallthrough;
16423 : default:
16424 0 : ereport(ERROR,
16425 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
16426 : errmsg("cannot change owner of relation \"%s\"",
16427 : NameStr(tuple_class->relname)),
16428 : errdetail_relkind_not_supported(tuple_class->relkind)));
16429 : }
16430 :
16431 : /*
16432 : * If the new owner is the same as the existing owner, consider the
16433 : * command to have succeeded. This is for dump restoration purposes.
16434 : */
16435 1356 : if (tuple_class->relowner != newOwnerId)
16436 : {
16437 : Datum repl_val[Natts_pg_class];
16438 : bool repl_null[Natts_pg_class];
16439 : bool repl_repl[Natts_pg_class];
16440 : Acl *newAcl;
16441 : Datum aclDatum;
16442 : bool isNull;
16443 : HeapTuple newtuple;
16444 :
16445 : /* skip permission checks when recursing to index or toast table */
16446 424 : if (!recursing)
16447 : {
16448 : /* Superusers can always do it */
16449 215 : if (!superuser())
16450 : {
16451 28 : Oid namespaceOid = tuple_class->relnamespace;
16452 : AclResult aclresult;
16453 :
16454 : /* Otherwise, must be owner of the existing object */
16455 28 : if (!object_ownercheck(RelationRelationId, relationOid, GetUserId()))
16456 0 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relationOid)),
16457 0 : RelationGetRelationName(target_rel));
16458 :
16459 : /* Must be able to become new owner */
16460 28 : check_can_set_role(GetUserId(), newOwnerId);
16461 :
16462 : /* New owner must have CREATE privilege on namespace */
16463 20 : aclresult = object_aclcheck(NamespaceRelationId, namespaceOid, newOwnerId,
16464 : ACL_CREATE);
16465 20 : if (aclresult != ACLCHECK_OK)
16466 0 : aclcheck_error(aclresult, OBJECT_SCHEMA,
16467 0 : get_namespace_name(namespaceOid));
16468 : }
16469 : }
16470 :
16471 416 : memset(repl_null, false, sizeof(repl_null));
16472 416 : memset(repl_repl, false, sizeof(repl_repl));
16473 :
16474 416 : repl_repl[Anum_pg_class_relowner - 1] = true;
16475 416 : repl_val[Anum_pg_class_relowner - 1] = ObjectIdGetDatum(newOwnerId);
16476 :
16477 : /*
16478 : * Determine the modified ACL for the new owner. This is only
16479 : * necessary when the ACL is non-null.
16480 : */
16481 416 : aclDatum = SysCacheGetAttr(RELOID, tuple,
16482 : Anum_pg_class_relacl,
16483 : &isNull);
16484 416 : if (!isNull)
16485 : {
16486 60 : newAcl = aclnewowner(DatumGetAclP(aclDatum),
16487 : tuple_class->relowner, newOwnerId);
16488 60 : repl_repl[Anum_pg_class_relacl - 1] = true;
16489 60 : repl_val[Anum_pg_class_relacl - 1] = PointerGetDatum(newAcl);
16490 : }
16491 :
16492 416 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(class_rel), repl_val, repl_null, repl_repl);
16493 :
16494 416 : CatalogTupleUpdate(class_rel, &newtuple->t_self, newtuple);
16495 :
16496 416 : heap_freetuple(newtuple);
16497 :
16498 : /*
16499 : * We must similarly update any per-column ACLs to reflect the new
16500 : * owner; for neatness reasons that's split out as a subroutine.
16501 : */
16502 416 : change_owner_fix_column_acls(relationOid,
16503 : tuple_class->relowner,
16504 : newOwnerId);
16505 :
16506 : /*
16507 : * Update owner dependency reference, if any. A composite type has
16508 : * none, because it's tracked for the pg_type entry instead of here;
16509 : * indexes and TOAST tables don't have their own entries either.
16510 : */
16511 416 : if (tuple_class->relkind != RELKIND_COMPOSITE_TYPE &&
16512 411 : tuple_class->relkind != RELKIND_INDEX &&
16513 320 : tuple_class->relkind != RELKIND_PARTITIONED_INDEX &&
16514 306 : tuple_class->relkind != RELKIND_TOASTVALUE)
16515 263 : changeDependencyOnOwner(RelationRelationId, relationOid,
16516 : newOwnerId);
16517 :
16518 : /*
16519 : * Also change the ownership of the table's row type, if it has one
16520 : */
16521 416 : if (OidIsValid(tuple_class->reltype))
16522 243 : AlterTypeOwnerInternal(tuple_class->reltype, newOwnerId);
16523 :
16524 : /*
16525 : * If we are operating on a table or materialized view, also change
16526 : * the ownership of any indexes and sequences that belong to the
16527 : * relation, as well as its toast table (if it has one).
16528 : */
16529 416 : if (tuple_class->relkind == RELKIND_RELATION ||
16530 229 : tuple_class->relkind == RELKIND_PARTITIONED_TABLE ||
16531 192 : tuple_class->relkind == RELKIND_MATVIEW ||
16532 192 : tuple_class->relkind == RELKIND_TOASTVALUE)
16533 : {
16534 : List *index_oid_list;
16535 : ListCell *i;
16536 :
16537 : /* Find all the indexes belonging to this relation */
16538 267 : index_oid_list = RelationGetIndexList(target_rel);
16539 :
16540 : /* For each index, recursively change its ownership */
16541 372 : foreach(i, index_oid_list)
16542 105 : ATExecChangeOwner(lfirst_oid(i), newOwnerId, true, lockmode);
16543 :
16544 267 : list_free(index_oid_list);
16545 : }
16546 :
16547 : /* If it has a toast table, recurse to change its ownership */
16548 416 : if (tuple_class->reltoastrelid != InvalidOid)
16549 43 : ATExecChangeOwner(tuple_class->reltoastrelid, newOwnerId,
16550 : true, lockmode);
16551 :
16552 : /* If it has dependent sequences, recurse to change them too */
16553 416 : change_owner_recurse_to_sequences(relationOid, newOwnerId, lockmode);
16554 : }
16555 :
16556 1348 : InvokeObjectPostAlterHook(RelationRelationId, relationOid, 0);
16557 :
16558 1348 : ReleaseSysCache(tuple);
16559 1348 : table_close(class_rel, RowExclusiveLock);
16560 1348 : relation_close(target_rel, NoLock);
16561 1348 : }
16562 :
16563 : /*
16564 : * change_owner_fix_column_acls
16565 : *
16566 : * Helper function for ATExecChangeOwner. Scan the columns of the table
16567 : * and fix any non-null column ACLs to reflect the new owner.
16568 : */
16569 : static void
16570 416 : change_owner_fix_column_acls(Oid relationOid, Oid oldOwnerId, Oid newOwnerId)
16571 : {
16572 : Relation attRelation;
16573 : SysScanDesc scan;
16574 : ScanKeyData key[1];
16575 : HeapTuple attributeTuple;
16576 :
16577 416 : attRelation = table_open(AttributeRelationId, RowExclusiveLock);
16578 416 : ScanKeyInit(&key[0],
16579 : Anum_pg_attribute_attrelid,
16580 : BTEqualStrategyNumber, F_OIDEQ,
16581 : ObjectIdGetDatum(relationOid));
16582 416 : scan = systable_beginscan(attRelation, AttributeRelidNumIndexId,
16583 : true, NULL, 1, key);
16584 2937 : while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
16585 : {
16586 2521 : Form_pg_attribute att = (Form_pg_attribute) GETSTRUCT(attributeTuple);
16587 : Datum repl_val[Natts_pg_attribute];
16588 : bool repl_null[Natts_pg_attribute];
16589 : bool repl_repl[Natts_pg_attribute];
16590 : Acl *newAcl;
16591 : Datum aclDatum;
16592 : bool isNull;
16593 : HeapTuple newtuple;
16594 :
16595 : /* Ignore dropped columns */
16596 2521 : if (att->attisdropped)
16597 2520 : continue;
16598 :
16599 2521 : aclDatum = heap_getattr(attributeTuple,
16600 : Anum_pg_attribute_attacl,
16601 : RelationGetDescr(attRelation),
16602 : &isNull);
16603 : /* Null ACLs do not require changes */
16604 2521 : if (isNull)
16605 2520 : continue;
16606 :
16607 1 : memset(repl_null, false, sizeof(repl_null));
16608 1 : memset(repl_repl, false, sizeof(repl_repl));
16609 :
16610 1 : newAcl = aclnewowner(DatumGetAclP(aclDatum),
16611 : oldOwnerId, newOwnerId);
16612 1 : repl_repl[Anum_pg_attribute_attacl - 1] = true;
16613 1 : repl_val[Anum_pg_attribute_attacl - 1] = PointerGetDatum(newAcl);
16614 :
16615 1 : newtuple = heap_modify_tuple(attributeTuple,
16616 : RelationGetDescr(attRelation),
16617 : repl_val, repl_null, repl_repl);
16618 :
16619 1 : CatalogTupleUpdate(attRelation, &newtuple->t_self, newtuple);
16620 :
16621 1 : heap_freetuple(newtuple);
16622 : }
16623 416 : systable_endscan(scan);
16624 416 : table_close(attRelation, RowExclusiveLock);
16625 416 : }
16626 :
16627 : /*
16628 : * change_owner_recurse_to_sequences
16629 : *
16630 : * Helper function for ATExecChangeOwner. Examines pg_depend searching
16631 : * for sequences that are dependent on serial columns, and changes their
16632 : * ownership.
16633 : */
16634 : static void
16635 416 : change_owner_recurse_to_sequences(Oid relationOid, Oid newOwnerId, LOCKMODE lockmode)
16636 : {
16637 : Relation depRel;
16638 : SysScanDesc scan;
16639 : ScanKeyData key[2];
16640 : HeapTuple tup;
16641 :
16642 : /*
16643 : * SERIAL sequences are those having an auto dependency on one of the
16644 : * table's columns (we don't care *which* column, exactly).
16645 : */
16646 416 : depRel = table_open(DependRelationId, AccessShareLock);
16647 :
16648 416 : ScanKeyInit(&key[0],
16649 : Anum_pg_depend_refclassid,
16650 : BTEqualStrategyNumber, F_OIDEQ,
16651 : ObjectIdGetDatum(RelationRelationId));
16652 416 : ScanKeyInit(&key[1],
16653 : Anum_pg_depend_refobjid,
16654 : BTEqualStrategyNumber, F_OIDEQ,
16655 : ObjectIdGetDatum(relationOid));
16656 : /* we leave refobjsubid unspecified */
16657 :
16658 416 : scan = systable_beginscan(depRel, DependReferenceIndexId, true,
16659 : NULL, 2, key);
16660 :
16661 1344 : while (HeapTupleIsValid(tup = systable_getnext(scan)))
16662 : {
16663 928 : Form_pg_depend depForm = (Form_pg_depend) GETSTRUCT(tup);
16664 : Relation seqRel;
16665 :
16666 : /* skip dependencies other than auto dependencies on columns */
16667 928 : if (depForm->refobjsubid == 0 ||
16668 399 : depForm->classid != RelationRelationId ||
16669 123 : depForm->objsubid != 0 ||
16670 123 : !(depForm->deptype == DEPENDENCY_AUTO || depForm->deptype == DEPENDENCY_INTERNAL))
16671 805 : continue;
16672 :
16673 : /* Use relation_open just in case it's an index */
16674 123 : seqRel = relation_open(depForm->objid, lockmode);
16675 :
16676 : /* skip non-sequence relations */
16677 123 : if (RelationGetForm(seqRel)->relkind != RELKIND_SEQUENCE)
16678 : {
16679 : /* No need to keep the lock */
16680 106 : relation_close(seqRel, lockmode);
16681 106 : continue;
16682 : }
16683 :
16684 : /* We don't need to close the sequence while we alter it. */
16685 17 : ATExecChangeOwner(depForm->objid, newOwnerId, true, lockmode);
16686 :
16687 : /* Now we can close it. Keep the lock till end of transaction. */
16688 17 : relation_close(seqRel, NoLock);
16689 : }
16690 :
16691 416 : systable_endscan(scan);
16692 :
16693 416 : relation_close(depRel, AccessShareLock);
16694 416 : }
16695 :
16696 : /*
16697 : * ALTER TABLE CLUSTER ON
16698 : *
16699 : * The only thing we have to do is to change the indisclustered bits.
16700 : *
16701 : * Return the address of the new clustering index.
16702 : */
16703 : static ObjectAddress
16704 39 : ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode)
16705 : {
16706 : Oid indexOid;
16707 : ObjectAddress address;
16708 :
16709 39 : indexOid = get_relname_relid(indexName, rel->rd_rel->relnamespace);
16710 :
16711 39 : if (!OidIsValid(indexOid))
16712 0 : ereport(ERROR,
16713 : (errcode(ERRCODE_UNDEFINED_OBJECT),
16714 : errmsg("index \"%s\" for table \"%s\" does not exist",
16715 : indexName, RelationGetRelationName(rel))));
16716 :
16717 : /* Check index is valid to cluster on */
16718 39 : check_index_is_clusterable(rel, indexOid, lockmode);
16719 :
16720 : /* And do the work */
16721 39 : mark_index_clustered(rel, indexOid, false);
16722 :
16723 39 : ObjectAddressSet(address,
16724 : RelationRelationId, indexOid);
16725 :
16726 39 : return address;
16727 : }
16728 :
16729 : /*
16730 : * ALTER TABLE SET WITHOUT CLUSTER
16731 : *
16732 : * We have to find any indexes on the table that have indisclustered bit
16733 : * set and turn it off.
16734 : */
16735 : static void
16736 8 : ATExecDropCluster(Relation rel, LOCKMODE lockmode)
16737 : {
16738 8 : mark_index_clustered(rel, InvalidOid, false);
16739 8 : }
16740 :
16741 : /*
16742 : * Preparation phase for SET ACCESS METHOD
16743 : *
16744 : * Check that the access method exists and determine whether a change is
16745 : * actually needed.
16746 : */
16747 : static void
16748 73 : ATPrepSetAccessMethod(AlteredTableInfo *tab, Relation rel, const char *amname)
16749 : {
16750 : Oid amoid;
16751 :
16752 : /*
16753 : * Look up the access method name and check that it differs from the
16754 : * table's current AM. If DEFAULT was specified for a partitioned table
16755 : * (amname is NULL), set it to InvalidOid to reset the catalogued AM.
16756 : */
16757 73 : if (amname != NULL)
16758 49 : amoid = get_table_am_oid(amname, false);
16759 24 : else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
16760 12 : amoid = InvalidOid;
16761 : else
16762 12 : amoid = get_table_am_oid(default_table_access_method, false);
16763 :
16764 : /* if it's a match, phase 3 doesn't need to do anything */
16765 73 : if (rel->rd_rel->relam == amoid)
16766 8 : return;
16767 :
16768 : /* Save info for Phase 3 to do the real work */
16769 65 : tab->rewrite |= AT_REWRITE_ACCESS_METHOD;
16770 65 : tab->newAccessMethod = amoid;
16771 65 : tab->chgAccessMethod = true;
16772 : }
16773 :
16774 : /*
16775 : * Special handling of ALTER TABLE SET ACCESS METHOD for relations with no
16776 : * storage that have an interest in preserving AM.
16777 : *
16778 : * Since these have no storage, setting the access method is a catalog only
16779 : * operation.
16780 : */
16781 : static void
16782 29 : ATExecSetAccessMethodNoStorage(Relation rel, Oid newAccessMethodId)
16783 : {
16784 : Relation pg_class;
16785 : Oid oldAccessMethodId;
16786 : HeapTuple tuple;
16787 : Form_pg_class rd_rel;
16788 29 : Oid reloid = RelationGetRelid(rel);
16789 :
16790 : /*
16791 : * Shouldn't be called on relations having storage; these are processed in
16792 : * phase 3.
16793 : */
16794 : Assert(!RELKIND_HAS_STORAGE(rel->rd_rel->relkind));
16795 :
16796 : /* Get a modifiable copy of the relation's pg_class row. */
16797 29 : pg_class = table_open(RelationRelationId, RowExclusiveLock);
16798 :
16799 29 : tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid));
16800 29 : if (!HeapTupleIsValid(tuple))
16801 0 : elog(ERROR, "cache lookup failed for relation %u", reloid);
16802 29 : rd_rel = (Form_pg_class) GETSTRUCT(tuple);
16803 :
16804 : /* Update the pg_class row. */
16805 29 : oldAccessMethodId = rd_rel->relam;
16806 29 : rd_rel->relam = newAccessMethodId;
16807 :
16808 : /* Leave if no update required */
16809 29 : if (rd_rel->relam == oldAccessMethodId)
16810 : {
16811 0 : heap_freetuple(tuple);
16812 0 : table_close(pg_class, RowExclusiveLock);
16813 0 : return;
16814 : }
16815 :
16816 29 : CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
16817 :
16818 : /*
16819 : * Update the dependency on the new access method. No dependency is added
16820 : * if the new access method is InvalidOid (default case). Be very careful
16821 : * that this has to compare the previous value stored in pg_class with the
16822 : * new one.
16823 : */
16824 29 : if (!OidIsValid(oldAccessMethodId) && OidIsValid(rd_rel->relam))
16825 13 : {
16826 : ObjectAddress relobj,
16827 : referenced;
16828 :
16829 : /*
16830 : * New access method is defined and there was no dependency
16831 : * previously, so record a new one.
16832 : */
16833 13 : ObjectAddressSet(relobj, RelationRelationId, reloid);
16834 13 : ObjectAddressSet(referenced, AccessMethodRelationId, rd_rel->relam);
16835 13 : recordDependencyOn(&relobj, &referenced, DEPENDENCY_NORMAL);
16836 : }
16837 16 : else if (OidIsValid(oldAccessMethodId) &&
16838 16 : !OidIsValid(rd_rel->relam))
16839 : {
16840 : /*
16841 : * There was an access method defined, and no new one, so just remove
16842 : * the existing dependency.
16843 : */
16844 8 : deleteDependencyRecordsForClass(RelationRelationId, reloid,
16845 : AccessMethodRelationId,
16846 : DEPENDENCY_NORMAL);
16847 : }
16848 : else
16849 : {
16850 : Assert(OidIsValid(oldAccessMethodId) &&
16851 : OidIsValid(rd_rel->relam));
16852 :
16853 : /* Both are valid, so update the dependency */
16854 8 : changeDependencyFor(RelationRelationId, reloid,
16855 : AccessMethodRelationId,
16856 : oldAccessMethodId, rd_rel->relam);
16857 : }
16858 :
16859 : /* make the relam and dependency changes visible */
16860 29 : CommandCounterIncrement();
16861 :
16862 29 : InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);
16863 :
16864 29 : heap_freetuple(tuple);
16865 29 : table_close(pg_class, RowExclusiveLock);
16866 : }
16867 :
16868 : /*
16869 : * ALTER TABLE SET TABLESPACE
16870 : */
16871 : static void
16872 99 : ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, const char *tablespacename, LOCKMODE lockmode)
16873 : {
16874 : Oid tablespaceId;
16875 :
16876 : /* Check that the tablespace exists */
16877 99 : tablespaceId = get_tablespace_oid(tablespacename, false);
16878 :
16879 : /* Check permissions except when moving to database's default */
16880 99 : if (OidIsValid(tablespaceId) && tablespaceId != MyDatabaseTableSpace)
16881 : {
16882 : AclResult aclresult;
16883 :
16884 44 : aclresult = object_aclcheck(TableSpaceRelationId, tablespaceId, GetUserId(), ACL_CREATE);
16885 44 : if (aclresult != ACLCHECK_OK)
16886 0 : aclcheck_error(aclresult, OBJECT_TABLESPACE, tablespacename);
16887 : }
16888 :
16889 : /* Save info for Phase 3 to do the real work */
16890 99 : if (OidIsValid(tab->newTableSpace))
16891 0 : ereport(ERROR,
16892 : (errcode(ERRCODE_SYNTAX_ERROR),
16893 : errmsg("cannot have multiple SET TABLESPACE subcommands")));
16894 :
16895 99 : tab->newTableSpace = tablespaceId;
16896 99 : }
16897 :
16898 : /*
16899 : * Set, reset, or replace reloptions.
16900 : */
16901 : static void
16902 632 : ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
16903 : LOCKMODE lockmode)
16904 : {
16905 : Oid relid;
16906 : Relation pgclass;
16907 : HeapTuple tuple;
16908 : HeapTuple newtuple;
16909 : Datum datum;
16910 : Datum newOptions;
16911 : Datum repl_val[Natts_pg_class];
16912 : bool repl_null[Natts_pg_class];
16913 : bool repl_repl[Natts_pg_class];
16914 632 : const char *const validnsps[] = HEAP_RELOPT_NAMESPACES;
16915 :
16916 632 : if (defList == NIL && operation != AT_ReplaceRelOptions)
16917 0 : return; /* nothing to do */
16918 :
16919 632 : pgclass = table_open(RelationRelationId, RowExclusiveLock);
16920 :
16921 : /* Fetch heap tuple */
16922 632 : relid = RelationGetRelid(rel);
16923 632 : tuple = SearchSysCacheLocked1(RELOID, ObjectIdGetDatum(relid));
16924 632 : if (!HeapTupleIsValid(tuple))
16925 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
16926 :
16927 632 : if (operation == AT_ReplaceRelOptions)
16928 : {
16929 : /*
16930 : * If we're supposed to replace the reloptions list, we just pretend
16931 : * there were none before.
16932 : */
16933 137 : datum = (Datum) 0;
16934 : }
16935 : else
16936 : {
16937 : bool isnull;
16938 :
16939 : /* Get the old reloptions */
16940 495 : datum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions,
16941 : &isnull);
16942 495 : if (isnull)
16943 306 : datum = (Datum) 0;
16944 : }
16945 :
16946 : /* Generate new proposed reloptions (text array) */
16947 632 : newOptions = transformRelOptions(datum, defList, NULL, validnsps, false,
16948 : operation == AT_ResetRelOptions);
16949 :
16950 : /* Validate */
16951 628 : switch (rel->rd_rel->relkind)
16952 : {
16953 348 : case RELKIND_RELATION:
16954 : case RELKIND_MATVIEW:
16955 348 : (void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
16956 348 : break;
16957 4 : case RELKIND_PARTITIONED_TABLE:
16958 4 : (void) partitioned_table_reloptions(newOptions, true);
16959 0 : break;
16960 205 : case RELKIND_VIEW:
16961 205 : (void) view_reloptions(newOptions, true);
16962 193 : break;
16963 71 : case RELKIND_INDEX:
16964 : case RELKIND_PARTITIONED_INDEX:
16965 71 : (void) index_reloptions(rel->rd_indam->amoptions, newOptions, true);
16966 57 : break;
16967 0 : case RELKIND_TOASTVALUE:
16968 : /* fall through to error -- shouldn't ever get here */
16969 : default:
16970 0 : ereport(ERROR,
16971 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
16972 : errmsg("cannot set options for relation \"%s\"",
16973 : RelationGetRelationName(rel)),
16974 : errdetail_relkind_not_supported(rel->rd_rel->relkind)));
16975 : break;
16976 : }
16977 :
16978 : /* Special-case validation of view options */
16979 598 : if (rel->rd_rel->relkind == RELKIND_VIEW)
16980 : {
16981 193 : Query *view_query = get_view_query(rel);
16982 193 : List *view_options = untransformRelOptions(newOptions);
16983 : ListCell *cell;
16984 193 : bool check_option = false;
16985 :
16986 261 : foreach(cell, view_options)
16987 : {
16988 68 : DefElem *defel = (DefElem *) lfirst(cell);
16989 :
16990 68 : if (strcmp(defel->defname, "check_option") == 0)
16991 16 : check_option = true;
16992 : }
16993 :
16994 : /*
16995 : * If the check option is specified, look to see if the view is
16996 : * actually auto-updatable or not.
16997 : */
16998 193 : if (check_option)
16999 : {
17000 : const char *view_updatable_error =
17001 16 : view_query_is_auto_updatable(view_query, true);
17002 :
17003 16 : if (view_updatable_error)
17004 0 : ereport(ERROR,
17005 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
17006 : errmsg("WITH CHECK OPTION is supported only on automatically updatable views"),
17007 : errhint("%s", _(view_updatable_error))));
17008 : }
17009 : }
17010 :
17011 : /*
17012 : * All we need do here is update the pg_class row; the new options will be
17013 : * propagated into relcaches during post-commit cache inval.
17014 : */
17015 598 : memset(repl_val, 0, sizeof(repl_val));
17016 598 : memset(repl_null, false, sizeof(repl_null));
17017 598 : memset(repl_repl, false, sizeof(repl_repl));
17018 :
17019 598 : if (newOptions != (Datum) 0)
17020 396 : repl_val[Anum_pg_class_reloptions - 1] = newOptions;
17021 : else
17022 202 : repl_null[Anum_pg_class_reloptions - 1] = true;
17023 :
17024 598 : repl_repl[Anum_pg_class_reloptions - 1] = true;
17025 :
17026 598 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(pgclass),
17027 : repl_val, repl_null, repl_repl);
17028 :
17029 598 : CatalogTupleUpdate(pgclass, &newtuple->t_self, newtuple);
17030 598 : UnlockTuple(pgclass, &tuple->t_self, InplaceUpdateTupleLock);
17031 :
17032 598 : InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);
17033 :
17034 598 : heap_freetuple(newtuple);
17035 :
17036 598 : ReleaseSysCache(tuple);
17037 :
17038 : /* repeat the whole exercise for the toast table, if there's one */
17039 598 : if (OidIsValid(rel->rd_rel->reltoastrelid))
17040 : {
17041 : Relation toastrel;
17042 182 : Oid toastid = rel->rd_rel->reltoastrelid;
17043 :
17044 182 : toastrel = table_open(toastid, lockmode);
17045 :
17046 : /* Fetch heap tuple */
17047 182 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(toastid));
17048 182 : if (!HeapTupleIsValid(tuple))
17049 0 : elog(ERROR, "cache lookup failed for relation %u", toastid);
17050 :
17051 182 : if (operation == AT_ReplaceRelOptions)
17052 : {
17053 : /*
17054 : * If we're supposed to replace the reloptions list, we just
17055 : * pretend there were none before.
17056 : */
17057 0 : datum = (Datum) 0;
17058 : }
17059 : else
17060 : {
17061 : bool isnull;
17062 :
17063 : /* Get the old reloptions */
17064 182 : datum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions,
17065 : &isnull);
17066 182 : if (isnull)
17067 158 : datum = (Datum) 0;
17068 : }
17069 :
17070 182 : newOptions = transformRelOptions(datum, defList, "toast", validnsps,
17071 : false, operation == AT_ResetRelOptions);
17072 :
17073 182 : (void) heap_reloptions(RELKIND_TOASTVALUE, newOptions, true);
17074 :
17075 182 : memset(repl_val, 0, sizeof(repl_val));
17076 182 : memset(repl_null, false, sizeof(repl_null));
17077 182 : memset(repl_repl, false, sizeof(repl_repl));
17078 :
17079 182 : if (newOptions != (Datum) 0)
17080 28 : repl_val[Anum_pg_class_reloptions - 1] = newOptions;
17081 : else
17082 154 : repl_null[Anum_pg_class_reloptions - 1] = true;
17083 :
17084 182 : repl_repl[Anum_pg_class_reloptions - 1] = true;
17085 :
17086 182 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(pgclass),
17087 : repl_val, repl_null, repl_repl);
17088 :
17089 182 : CatalogTupleUpdate(pgclass, &newtuple->t_self, newtuple);
17090 :
17091 182 : InvokeObjectPostAlterHookArg(RelationRelationId,
17092 : RelationGetRelid(toastrel), 0,
17093 : InvalidOid, true);
17094 :
17095 182 : heap_freetuple(newtuple);
17096 :
17097 182 : ReleaseSysCache(tuple);
17098 :
17099 182 : table_close(toastrel, NoLock);
17100 : }
17101 :
17102 598 : table_close(pgclass, RowExclusiveLock);
17103 : }
17104 :
17105 : /*
17106 : * Execute ALTER TABLE SET TABLESPACE for cases where there is no tuple
17107 : * rewriting to be done, so we just want to copy the data as fast as possible.
17108 : */
17109 : static void
17110 101 : ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
17111 : {
17112 : Relation rel;
17113 : Oid reltoastrelid;
17114 : RelFileNumber newrelfilenumber;
17115 : RelFileLocator newrlocator;
17116 101 : List *reltoastidxids = NIL;
17117 : ListCell *lc;
17118 :
17119 : /*
17120 : * Need lock here in case we are recursing to toast table or index
17121 : */
17122 101 : rel = relation_open(tableOid, lockmode);
17123 :
17124 : /* Check first if relation can be moved to new tablespace */
17125 101 : if (!CheckRelationTableSpaceMove(rel, newTableSpace))
17126 : {
17127 5 : InvokeObjectPostAlterHook(RelationRelationId,
17128 : RelationGetRelid(rel), 0);
17129 5 : relation_close(rel, NoLock);
17130 5 : return;
17131 : }
17132 :
17133 96 : reltoastrelid = rel->rd_rel->reltoastrelid;
17134 : /* Fetch the list of indexes on toast relation if necessary */
17135 96 : if (OidIsValid(reltoastrelid))
17136 : {
17137 13 : Relation toastRel = relation_open(reltoastrelid, lockmode);
17138 :
17139 13 : reltoastidxids = RelationGetIndexList(toastRel);
17140 13 : relation_close(toastRel, lockmode);
17141 : }
17142 :
17143 : /*
17144 : * Relfilenumbers are not unique in databases across tablespaces, so we
17145 : * need to allocate a new one in the new tablespace.
17146 : */
17147 96 : newrelfilenumber = GetNewRelFileNumber(newTableSpace, NULL,
17148 96 : rel->rd_rel->relpersistence);
17149 :
17150 : /* Open old and new relation */
17151 96 : newrlocator = rel->rd_locator;
17152 96 : newrlocator.relNumber = newrelfilenumber;
17153 96 : newrlocator.spcOid = newTableSpace;
17154 :
17155 : /* hand off to AM to actually create new rel storage and copy the data */
17156 96 : if (rel->rd_rel->relkind == RELKIND_INDEX)
17157 : {
17158 36 : index_copy_data(rel, newrlocator);
17159 : }
17160 : else
17161 : {
17162 : Assert(RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind));
17163 60 : table_relation_copy_data(rel, &newrlocator);
17164 : }
17165 :
17166 : /*
17167 : * Update the pg_class row.
17168 : *
17169 : * NB: This wouldn't work if ATExecSetTableSpace() were allowed to be
17170 : * executed on pg_class or its indexes (the above copy wouldn't contain
17171 : * the updated pg_class entry), but that's forbidden with
17172 : * CheckRelationTableSpaceMove().
17173 : */
17174 96 : SetRelationTableSpace(rel, newTableSpace, newrelfilenumber);
17175 :
17176 96 : InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);
17177 :
17178 96 : RelationAssumeNewRelfilelocator(rel);
17179 :
17180 96 : relation_close(rel, NoLock);
17181 :
17182 : /* Make sure the reltablespace change is visible */
17183 96 : CommandCounterIncrement();
17184 :
17185 : /* Move associated toast relation and/or indexes, too */
17186 96 : if (OidIsValid(reltoastrelid))
17187 13 : ATExecSetTableSpace(reltoastrelid, newTableSpace, lockmode);
17188 109 : foreach(lc, reltoastidxids)
17189 13 : ATExecSetTableSpace(lfirst_oid(lc), newTableSpace, lockmode);
17190 :
17191 : /* Clean up */
17192 96 : list_free(reltoastidxids);
17193 : }
17194 :
17195 : /*
17196 : * Special handling of ALTER TABLE SET TABLESPACE for relations with no
17197 : * storage that have an interest in preserving tablespace.
17198 : *
17199 : * Since these have no storage the tablespace can be updated with a simple
17200 : * metadata only operation to update the tablespace.
17201 : */
17202 : static void
17203 24 : ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace)
17204 : {
17205 : /*
17206 : * Shouldn't be called on relations having storage; these are processed in
17207 : * phase 3.
17208 : */
17209 : Assert(!RELKIND_HAS_STORAGE(rel->rd_rel->relkind));
17210 :
17211 : /* check if relation can be moved to its new tablespace */
17212 24 : if (!CheckRelationTableSpaceMove(rel, newTableSpace))
17213 : {
17214 0 : InvokeObjectPostAlterHook(RelationRelationId,
17215 : RelationGetRelid(rel),
17216 : 0);
17217 0 : return;
17218 : }
17219 :
17220 : /* Update can be done, so change reltablespace */
17221 20 : SetRelationTableSpace(rel, newTableSpace, InvalidOid);
17222 :
17223 20 : InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);
17224 :
17225 : /* Make sure the reltablespace change is visible */
17226 20 : CommandCounterIncrement();
17227 : }
17228 :
17229 : /*
17230 : * Alter Table ALL ... SET TABLESPACE
17231 : *
17232 : * Allows a user to move all objects of some type in a given tablespace in the
17233 : * current database to another tablespace. Objects can be chosen based on the
17234 : * owner of the object also, to allow users to move only their objects.
17235 : * The user must have CREATE rights on the new tablespace, as usual. The main
17236 : * permissions handling is done by the lower-level table move function.
17237 : *
17238 : * All to-be-moved objects are locked first. If NOWAIT is specified and the
17239 : * lock can't be acquired then we ereport(ERROR).
17240 : */
17241 : Oid
17242 15 : AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
17243 : {
17244 15 : List *relations = NIL;
17245 : ListCell *l;
17246 : ScanKeyData key[1];
17247 : Relation rel;
17248 : TableScanDesc scan;
17249 : HeapTuple tuple;
17250 : Oid orig_tablespaceoid;
17251 : Oid new_tablespaceoid;
17252 15 : List *role_oids = roleSpecsToIds(stmt->roles);
17253 :
17254 : /* Ensure we were not asked to move something we can't */
17255 15 : if (stmt->objtype != OBJECT_TABLE && stmt->objtype != OBJECT_INDEX &&
17256 6 : stmt->objtype != OBJECT_MATVIEW)
17257 0 : ereport(ERROR,
17258 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
17259 : errmsg("only tables, indexes, and materialized views exist in tablespaces")));
17260 :
17261 : /* Get the orig and new tablespace OIDs */
17262 15 : orig_tablespaceoid = get_tablespace_oid(stmt->orig_tablespacename, false);
17263 15 : new_tablespaceoid = get_tablespace_oid(stmt->new_tablespacename, false);
17264 :
17265 : /* Can't move shared relations in to or out of pg_global */
17266 : /* This is also checked by ATExecSetTableSpace, but nice to stop earlier */
17267 15 : if (orig_tablespaceoid == GLOBALTABLESPACE_OID ||
17268 : new_tablespaceoid == GLOBALTABLESPACE_OID)
17269 0 : ereport(ERROR,
17270 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
17271 : errmsg("cannot move relations in to or out of pg_global tablespace")));
17272 :
17273 : /*
17274 : * Must have CREATE rights on the new tablespace, unless it is the
17275 : * database default tablespace (which all users implicitly have CREATE
17276 : * rights on).
17277 : */
17278 15 : if (OidIsValid(new_tablespaceoid) && new_tablespaceoid != MyDatabaseTableSpace)
17279 : {
17280 : AclResult aclresult;
17281 :
17282 0 : aclresult = object_aclcheck(TableSpaceRelationId, new_tablespaceoid, GetUserId(),
17283 : ACL_CREATE);
17284 0 : if (aclresult != ACLCHECK_OK)
17285 0 : aclcheck_error(aclresult, OBJECT_TABLESPACE,
17286 0 : get_tablespace_name(new_tablespaceoid));
17287 : }
17288 :
17289 : /*
17290 : * Now that the checks are done, check if we should set either to
17291 : * InvalidOid because it is our database's default tablespace.
17292 : */
17293 15 : if (orig_tablespaceoid == MyDatabaseTableSpace)
17294 0 : orig_tablespaceoid = InvalidOid;
17295 :
17296 15 : if (new_tablespaceoid == MyDatabaseTableSpace)
17297 15 : new_tablespaceoid = InvalidOid;
17298 :
17299 : /* no-op */
17300 15 : if (orig_tablespaceoid == new_tablespaceoid)
17301 0 : return new_tablespaceoid;
17302 :
17303 : /*
17304 : * Walk the list of objects in the tablespace and move them. This will
17305 : * only find objects in our database, of course.
17306 : */
17307 15 : ScanKeyInit(&key[0],
17308 : Anum_pg_class_reltablespace,
17309 : BTEqualStrategyNumber, F_OIDEQ,
17310 : ObjectIdGetDatum(orig_tablespaceoid));
17311 :
17312 15 : rel = table_open(RelationRelationId, AccessShareLock);
17313 15 : scan = table_beginscan_catalog(rel, 1, key);
17314 66 : while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
17315 : {
17316 51 : Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
17317 51 : Oid relOid = relForm->oid;
17318 :
17319 : /*
17320 : * Do not move objects in pg_catalog as part of this, if an admin
17321 : * really wishes to do so, they can issue the individual ALTER
17322 : * commands directly.
17323 : *
17324 : * Also, explicitly avoid any shared tables, temp tables, or TOAST
17325 : * (TOAST will be moved with the main table).
17326 : */
17327 51 : if (IsCatalogNamespace(relForm->relnamespace) ||
17328 102 : relForm->relisshared ||
17329 102 : isAnyTempNamespace(relForm->relnamespace) ||
17330 51 : IsToastNamespace(relForm->relnamespace))
17331 0 : continue;
17332 :
17333 : /* Only move the object type requested */
17334 51 : if ((stmt->objtype == OBJECT_TABLE &&
17335 30 : relForm->relkind != RELKIND_RELATION &&
17336 18 : relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
17337 33 : (stmt->objtype == OBJECT_INDEX &&
17338 18 : relForm->relkind != RELKIND_INDEX &&
17339 3 : relForm->relkind != RELKIND_PARTITIONED_INDEX) ||
17340 30 : (stmt->objtype == OBJECT_MATVIEW &&
17341 3 : relForm->relkind != RELKIND_MATVIEW))
17342 21 : continue;
17343 :
17344 : /* Check if we are only moving objects owned by certain roles */
17345 30 : if (role_oids != NIL && !list_member_oid(role_oids, relForm->relowner))
17346 0 : continue;
17347 :
17348 : /*
17349 : * Handle permissions-checking here since we are locking the tables
17350 : * and also to avoid doing a bunch of work only to fail part-way. Note
17351 : * that permissions will also be checked by AlterTableInternal().
17352 : *
17353 : * Caller must be considered an owner on the table to move it.
17354 : */
17355 30 : if (!object_ownercheck(RelationRelationId, relOid, GetUserId()))
17356 0 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relOid)),
17357 0 : NameStr(relForm->relname));
17358 :
17359 30 : if (stmt->nowait &&
17360 0 : !ConditionalLockRelationOid(relOid, AccessExclusiveLock))
17361 0 : ereport(ERROR,
17362 : (errcode(ERRCODE_OBJECT_IN_USE),
17363 : errmsg("aborting because lock on relation \"%s.%s\" is not available",
17364 : get_namespace_name(relForm->relnamespace),
17365 : NameStr(relForm->relname))));
17366 : else
17367 30 : LockRelationOid(relOid, AccessExclusiveLock);
17368 :
17369 : /* Add to our list of objects to move */
17370 30 : relations = lappend_oid(relations, relOid);
17371 : }
17372 :
17373 15 : table_endscan(scan);
17374 15 : table_close(rel, AccessShareLock);
17375 :
17376 15 : if (relations == NIL)
17377 6 : ereport(NOTICE,
17378 : (errcode(ERRCODE_NO_DATA_FOUND),
17379 : errmsg("no matching relations in tablespace \"%s\" found",
17380 : orig_tablespaceoid == InvalidOid ? "(database default)" :
17381 : get_tablespace_name(orig_tablespaceoid))));
17382 :
17383 : /* Everything is locked, loop through and move all of the relations. */
17384 45 : foreach(l, relations)
17385 : {
17386 30 : List *cmds = NIL;
17387 30 : AlterTableCmd *cmd = makeNode(AlterTableCmd);
17388 :
17389 30 : cmd->subtype = AT_SetTableSpace;
17390 30 : cmd->name = stmt->new_tablespacename;
17391 :
17392 30 : cmds = lappend(cmds, cmd);
17393 :
17394 30 : EventTriggerAlterTableStart((Node *) stmt);
17395 : /* OID is set by AlterTableInternal */
17396 30 : AlterTableInternal(lfirst_oid(l), cmds, false);
17397 30 : EventTriggerAlterTableEnd();
17398 : }
17399 :
17400 15 : return new_tablespaceoid;
17401 : }
17402 :
17403 : static void
17404 36 : index_copy_data(Relation rel, RelFileLocator newrlocator)
17405 : {
17406 : SMgrRelation dstrel;
17407 :
17408 : /*
17409 : * Since we copy the file directly without looking at the shared buffers,
17410 : * we'd better first flush out any pages of the source relation that are
17411 : * in shared buffers. We assume no new changes will be made while we are
17412 : * holding exclusive lock on the rel.
17413 : */
17414 36 : FlushRelationBuffers(rel);
17415 :
17416 : /*
17417 : * Create and copy all forks of the relation, and schedule unlinking of
17418 : * old physical files.
17419 : *
17420 : * NOTE: any conflict in relfilenumber value will be caught in
17421 : * RelationCreateStorage().
17422 : */
17423 36 : dstrel = RelationCreateStorage(newrlocator, rel->rd_rel->relpersistence, true);
17424 :
17425 : /* copy main fork */
17426 36 : RelationCopyStorage(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
17427 36 : rel->rd_rel->relpersistence);
17428 :
17429 : /* copy those extra forks that exist */
17430 36 : for (ForkNumber forkNum = MAIN_FORKNUM + 1;
17431 144 : forkNum <= MAX_FORKNUM; forkNum++)
17432 : {
17433 108 : if (smgrexists(RelationGetSmgr(rel), forkNum))
17434 : {
17435 0 : smgrcreate(dstrel, forkNum, false);
17436 :
17437 : /*
17438 : * WAL log creation if the relation is persistent, or this is the
17439 : * init fork of an unlogged relation.
17440 : */
17441 0 : if (RelationIsPermanent(rel) ||
17442 0 : (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
17443 : forkNum == INIT_FORKNUM))
17444 0 : log_smgrcreate(&newrlocator, forkNum);
17445 0 : RelationCopyStorage(RelationGetSmgr(rel), dstrel, forkNum,
17446 0 : rel->rd_rel->relpersistence);
17447 : }
17448 : }
17449 :
17450 : /* drop old relation, and close new one */
17451 36 : RelationDropStorage(rel);
17452 36 : smgrclose(dstrel);
17453 36 : }
17454 :
17455 : /*
17456 : * ALTER TABLE ENABLE/DISABLE TRIGGER
17457 : *
17458 : * We just pass this off to trigger.c.
17459 : */
17460 : static void
17461 191 : ATExecEnableDisableTrigger(Relation rel, const char *trigname,
17462 : char fires_when, bool skip_system, bool recurse,
17463 : LOCKMODE lockmode)
17464 : {
17465 191 : EnableDisableTrigger(rel, trigname, InvalidOid,
17466 : fires_when, skip_system, recurse,
17467 : lockmode);
17468 :
17469 191 : InvokeObjectPostAlterHook(RelationRelationId,
17470 : RelationGetRelid(rel), 0);
17471 191 : }
17472 :
17473 : /*
17474 : * ALTER TABLE ENABLE/DISABLE RULE
17475 : *
17476 : * We just pass this off to rewriteDefine.c.
17477 : */
17478 : static void
17479 29 : ATExecEnableDisableRule(Relation rel, const char *rulename,
17480 : char fires_when, LOCKMODE lockmode)
17481 : {
17482 29 : EnableDisableRule(rel, rulename, fires_when);
17483 :
17484 29 : InvokeObjectPostAlterHook(RelationRelationId,
17485 : RelationGetRelid(rel), 0);
17486 29 : }
17487 :
17488 : /*
17489 : * Preparation phase of [NO] INHERIT
17490 : *
17491 : * Check the relation defined as a child.
17492 : */
17493 : static void
17494 378 : ATPrepChangeInherit(Relation child_rel)
17495 : {
17496 378 : if (child_rel->rd_rel->reloftype)
17497 8 : ereport(ERROR,
17498 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17499 : errmsg("cannot change inheritance of typed table")));
17500 :
17501 370 : if (child_rel->rd_rel->relispartition)
17502 12 : ereport(ERROR,
17503 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17504 : errmsg("cannot change inheritance of a partition")));
17505 358 : }
17506 :
17507 : /*
17508 : * ALTER TABLE INHERIT
17509 : *
17510 : * Return the address of the new parent relation.
17511 : */
17512 : static ObjectAddress
17513 289 : ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
17514 : {
17515 : Relation parent_rel;
17516 : List *children;
17517 : ObjectAddress address;
17518 : const char *trigger_name;
17519 :
17520 : /*
17521 : * A self-exclusive lock is needed here. See the similar case in
17522 : * MergeAttributes() for a full explanation.
17523 : */
17524 289 : parent_rel = table_openrv(parent, ShareUpdateExclusiveLock);
17525 :
17526 : /*
17527 : * Must be owner of both parent and child -- child was checked by
17528 : * ATSimplePermissions call in ATPrepCmd
17529 : */
17530 289 : ATSimplePermissions(AT_AddInherit, parent_rel,
17531 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
17532 :
17533 : /* Permanent rels cannot inherit from temporary ones */
17534 289 : if (parent_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
17535 4 : child_rel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
17536 0 : ereport(ERROR,
17537 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17538 : errmsg("cannot inherit from temporary relation \"%s\"",
17539 : RelationGetRelationName(parent_rel))));
17540 :
17541 : /* If parent rel is temp, it must belong to this session */
17542 289 : if (RELATION_IS_OTHER_TEMP(parent_rel))
17543 0 : ereport(ERROR,
17544 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17545 : errmsg("cannot inherit from temporary relation of another session")));
17546 :
17547 : /* Ditto for the child */
17548 289 : if (RELATION_IS_OTHER_TEMP(child_rel))
17549 0 : ereport(ERROR,
17550 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17551 : errmsg("cannot inherit to temporary relation of another session")));
17552 :
17553 : /* Prevent partitioned tables from becoming inheritance parents */
17554 289 : if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
17555 4 : ereport(ERROR,
17556 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17557 : errmsg("cannot inherit from partitioned table \"%s\"",
17558 : parent->relname)));
17559 :
17560 : /* Likewise for partitions */
17561 285 : if (parent_rel->rd_rel->relispartition)
17562 4 : ereport(ERROR,
17563 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17564 : errmsg("cannot inherit from a partition")));
17565 :
17566 : /*
17567 : * Prevent circularity by seeing if proposed parent inherits from child.
17568 : * (In particular, this disallows making a rel inherit from itself.)
17569 : *
17570 : * This is not completely bulletproof because of race conditions: in
17571 : * multi-level inheritance trees, someone else could concurrently be
17572 : * making another inheritance link that closes the loop but does not join
17573 : * either of the rels we have locked. Preventing that seems to require
17574 : * exclusive locks on the entire inheritance tree, which is a cure worse
17575 : * than the disease. find_all_inheritors() will cope with circularity
17576 : * anyway, so don't sweat it too much.
17577 : *
17578 : * We use weakest lock we can on child's children, namely AccessShareLock.
17579 : */
17580 281 : children = find_all_inheritors(RelationGetRelid(child_rel),
17581 : AccessShareLock, NULL);
17582 :
17583 281 : if (list_member_oid(children, RelationGetRelid(parent_rel)))
17584 8 : ereport(ERROR,
17585 : (errcode(ERRCODE_DUPLICATE_TABLE),
17586 : errmsg("circular inheritance not allowed"),
17587 : errdetail("\"%s\" is already a child of \"%s\".",
17588 : parent->relname,
17589 : RelationGetRelationName(child_rel))));
17590 :
17591 : /*
17592 : * If child_rel has row-level triggers with transition tables, we
17593 : * currently don't allow it to become an inheritance child. See also
17594 : * prohibitions in ATExecAttachPartition() and CreateTrigger().
17595 : */
17596 273 : trigger_name = FindTriggerIncompatibleWithInheritance(child_rel->trigdesc);
17597 273 : if (trigger_name != NULL)
17598 4 : ereport(ERROR,
17599 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
17600 : errmsg("trigger \"%s\" prevents table \"%s\" from becoming an inheritance child",
17601 : trigger_name, RelationGetRelationName(child_rel)),
17602 : errdetail("ROW triggers with transition tables are not supported in inheritance hierarchies.")));
17603 :
17604 : /* OK to create inheritance */
17605 269 : CreateInheritance(child_rel, parent_rel, false);
17606 :
17607 209 : ObjectAddressSet(address, RelationRelationId,
17608 : RelationGetRelid(parent_rel));
17609 :
17610 : /* keep our lock on the parent relation until commit */
17611 209 : table_close(parent_rel, NoLock);
17612 :
17613 209 : return address;
17614 : }
17615 :
17616 : /*
17617 : * CreateInheritance
17618 : * Catalog manipulation portion of creating inheritance between a child
17619 : * table and a parent table.
17620 : *
17621 : * This verifies that all the columns and check constraints of the parent
17622 : * appear in the child and that they have the same data types and expressions.
17623 : *
17624 : * Common to ATExecAddInherit() and ATExecAttachPartition().
17625 : */
17626 : static void
17627 2249 : CreateInheritance(Relation child_rel, Relation parent_rel, bool ispartition)
17628 : {
17629 : Relation catalogRelation;
17630 : SysScanDesc scan;
17631 : ScanKeyData key;
17632 : HeapTuple inheritsTuple;
17633 : int32 inhseqno;
17634 :
17635 : /* Note: get RowExclusiveLock because we will write pg_inherits below. */
17636 2249 : catalogRelation = table_open(InheritsRelationId, RowExclusiveLock);
17637 :
17638 : /*
17639 : * Check for duplicates in the list of parents, and determine the highest
17640 : * inhseqno already present; we'll use the next one for the new parent.
17641 : * Also, if proposed child is a partition, it cannot already be
17642 : * inheriting.
17643 : *
17644 : * Note: we do not reject the case where the child already inherits from
17645 : * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
17646 : */
17647 2249 : ScanKeyInit(&key,
17648 : Anum_pg_inherits_inhrelid,
17649 : BTEqualStrategyNumber, F_OIDEQ,
17650 : ObjectIdGetDatum(RelationGetRelid(child_rel)));
17651 2249 : scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
17652 : true, NULL, 1, &key);
17653 :
17654 : /* inhseqno sequences start at 1 */
17655 2249 : inhseqno = 0;
17656 2292 : while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
17657 : {
17658 47 : Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
17659 :
17660 47 : if (inh->inhparent == RelationGetRelid(parent_rel))
17661 4 : ereport(ERROR,
17662 : (errcode(ERRCODE_DUPLICATE_TABLE),
17663 : errmsg("relation \"%s\" would be inherited from more than once",
17664 : RelationGetRelationName(parent_rel))));
17665 :
17666 43 : if (inh->inhseqno > inhseqno)
17667 43 : inhseqno = inh->inhseqno;
17668 : }
17669 2245 : systable_endscan(scan);
17670 :
17671 : /* Match up the columns and bump attinhcount as needed */
17672 2245 : MergeAttributesIntoExisting(child_rel, parent_rel, ispartition);
17673 :
17674 : /* Match up the constraints and bump coninhcount as needed */
17675 2157 : MergeConstraintsIntoExisting(child_rel, parent_rel);
17676 :
17677 : /*
17678 : * OK, it looks valid. Make the catalog entries that show inheritance.
17679 : */
17680 2117 : StoreCatalogInheritance1(RelationGetRelid(child_rel),
17681 : RelationGetRelid(parent_rel),
17682 : inhseqno + 1,
17683 : catalogRelation,
17684 2117 : parent_rel->rd_rel->relkind ==
17685 : RELKIND_PARTITIONED_TABLE);
17686 :
17687 : /* Now we're done with pg_inherits */
17688 2117 : table_close(catalogRelation, RowExclusiveLock);
17689 2117 : }
17690 :
17691 : /*
17692 : * Obtain the source-text form of the constraint expression for a check
17693 : * constraint, given its pg_constraint tuple
17694 : */
17695 : static char *
17696 272 : decompile_conbin(HeapTuple contup, TupleDesc tupdesc)
17697 : {
17698 : Form_pg_constraint con;
17699 : bool isnull;
17700 : Datum attr;
17701 : Datum expr;
17702 :
17703 272 : con = (Form_pg_constraint) GETSTRUCT(contup);
17704 272 : attr = heap_getattr(contup, Anum_pg_constraint_conbin, tupdesc, &isnull);
17705 272 : if (isnull)
17706 0 : elog(ERROR, "null conbin for constraint %u", con->oid);
17707 :
17708 272 : expr = DirectFunctionCall2(pg_get_expr, attr,
17709 : ObjectIdGetDatum(con->conrelid));
17710 272 : return TextDatumGetCString(expr);
17711 : }
17712 :
17713 : /*
17714 : * Determine whether two check constraints are functionally equivalent
17715 : *
17716 : * The test we apply is to see whether they reverse-compile to the same
17717 : * source string. This insulates us from issues like whether attributes
17718 : * have the same physical column numbers in parent and child relations.
17719 : *
17720 : * Note that we ignore enforceability as there are cases where constraints
17721 : * with differing enforceability are allowed.
17722 : */
17723 : static bool
17724 136 : constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
17725 : {
17726 136 : Form_pg_constraint acon = (Form_pg_constraint) GETSTRUCT(a);
17727 136 : Form_pg_constraint bcon = (Form_pg_constraint) GETSTRUCT(b);
17728 :
17729 136 : if (acon->condeferrable != bcon->condeferrable ||
17730 136 : acon->condeferred != bcon->condeferred ||
17731 136 : strcmp(decompile_conbin(a, tupleDesc),
17732 136 : decompile_conbin(b, tupleDesc)) != 0)
17733 4 : return false;
17734 : else
17735 132 : return true;
17736 : }
17737 :
17738 : /*
17739 : * Check columns in child table match up with columns in parent, and increment
17740 : * their attinhcount.
17741 : *
17742 : * Called by CreateInheritance
17743 : *
17744 : * Currently all parent columns must be found in child. Missing columns are an
17745 : * error. One day we might consider creating new columns like CREATE TABLE
17746 : * does. However, that is widely unpopular --- in the common use case of
17747 : * partitioned tables it's a foot-gun.
17748 : *
17749 : * The data type must match exactly. If the parent column is NOT NULL then
17750 : * the child must be as well. Defaults are not compared, however.
17751 : */
17752 : static void
17753 2245 : MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel, bool ispartition)
17754 : {
17755 : Relation attrrel;
17756 : TupleDesc parent_desc;
17757 :
17758 2245 : attrrel = table_open(AttributeRelationId, RowExclusiveLock);
17759 2245 : parent_desc = RelationGetDescr(parent_rel);
17760 :
17761 7345 : for (AttrNumber parent_attno = 1; parent_attno <= parent_desc->natts; parent_attno++)
17762 : {
17763 5188 : Form_pg_attribute parent_att = TupleDescAttr(parent_desc, parent_attno - 1);
17764 5188 : char *parent_attname = NameStr(parent_att->attname);
17765 : HeapTuple tuple;
17766 :
17767 : /* Ignore dropped columns in the parent. */
17768 5188 : if (parent_att->attisdropped)
17769 180 : continue;
17770 :
17771 : /* Find same column in child (matching on column name). */
17772 5008 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(child_rel), parent_attname);
17773 5008 : if (HeapTupleIsValid(tuple))
17774 : {
17775 5000 : Form_pg_attribute child_att = (Form_pg_attribute) GETSTRUCT(tuple);
17776 :
17777 5000 : if (parent_att->atttypid != child_att->atttypid ||
17778 4996 : parent_att->atttypmod != child_att->atttypmod)
17779 8 : ereport(ERROR,
17780 : (errcode(ERRCODE_DATATYPE_MISMATCH),
17781 : errmsg("child table \"%s\" has different type for column \"%s\"",
17782 : RelationGetRelationName(child_rel), parent_attname)));
17783 :
17784 4992 : if (parent_att->attcollation != child_att->attcollation)
17785 4 : ereport(ERROR,
17786 : (errcode(ERRCODE_COLLATION_MISMATCH),
17787 : errmsg("child table \"%s\" has different collation for column \"%s\"",
17788 : RelationGetRelationName(child_rel), parent_attname)));
17789 :
17790 : /*
17791 : * If the parent has a not-null constraint that's not NO INHERIT,
17792 : * make sure the child has one too.
17793 : *
17794 : * Other constraints are checked elsewhere.
17795 : */
17796 4988 : if (parent_att->attnotnull && !child_att->attnotnull)
17797 : {
17798 : HeapTuple contup;
17799 :
17800 32 : contup = findNotNullConstraintAttnum(RelationGetRelid(parent_rel),
17801 32 : parent_att->attnum);
17802 32 : if (HeapTupleIsValid(contup) &&
17803 32 : !((Form_pg_constraint) GETSTRUCT(contup))->connoinherit)
17804 20 : ereport(ERROR,
17805 : errcode(ERRCODE_DATATYPE_MISMATCH),
17806 : errmsg("column \"%s\" in child table \"%s\" must be marked NOT NULL",
17807 : parent_attname, RelationGetRelationName(child_rel)));
17808 : }
17809 :
17810 : /*
17811 : * Child column must be generated if and only if parent column is.
17812 : */
17813 4968 : if (parent_att->attgenerated && !child_att->attgenerated)
17814 24 : ereport(ERROR,
17815 : (errcode(ERRCODE_DATATYPE_MISMATCH),
17816 : errmsg("column \"%s\" in child table must be a generated column", parent_attname)));
17817 4944 : if (child_att->attgenerated && !parent_att->attgenerated)
17818 16 : ereport(ERROR,
17819 : (errcode(ERRCODE_DATATYPE_MISMATCH),
17820 : errmsg("column \"%s\" in child table must not be a generated column", parent_attname)));
17821 :
17822 4928 : if (parent_att->attgenerated && child_att->attgenerated && child_att->attgenerated != parent_att->attgenerated)
17823 8 : ereport(ERROR,
17824 : (errcode(ERRCODE_DATATYPE_MISMATCH),
17825 : errmsg("column \"%s\" inherits from generated column of different kind", parent_attname),
17826 : errdetail("Parent column is %s, child column is %s.",
17827 : parent_att->attgenerated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL",
17828 : child_att->attgenerated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL")));
17829 :
17830 : /*
17831 : * Regular inheritance children are independent enough not to
17832 : * inherit identity columns. But partitions are integral part of
17833 : * a partitioned table and inherit identity column.
17834 : */
17835 4920 : if (ispartition)
17836 4443 : child_att->attidentity = parent_att->attidentity;
17837 :
17838 : /*
17839 : * OK, bump the child column's inheritance count. (If we fail
17840 : * later on, this change will just roll back.)
17841 : */
17842 4920 : if (pg_add_s16_overflow(child_att->attinhcount, 1,
17843 : &child_att->attinhcount))
17844 0 : ereport(ERROR,
17845 : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
17846 : errmsg("too many inheritance parents"));
17847 :
17848 : /*
17849 : * In case of partitions, we must enforce that value of attislocal
17850 : * is same in all partitions. (Note: there are only inherited
17851 : * attributes in partitions)
17852 : */
17853 4920 : if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
17854 : {
17855 : Assert(child_att->attinhcount == 1);
17856 4443 : child_att->attislocal = false;
17857 : }
17858 :
17859 4920 : CatalogTupleUpdate(attrrel, &tuple->t_self, tuple);
17860 4920 : heap_freetuple(tuple);
17861 : }
17862 : else
17863 : {
17864 8 : ereport(ERROR,
17865 : (errcode(ERRCODE_DATATYPE_MISMATCH),
17866 : errmsg("child table is missing column \"%s\"", parent_attname)));
17867 : }
17868 : }
17869 :
17870 2157 : table_close(attrrel, RowExclusiveLock);
17871 2157 : }
17872 :
17873 : /*
17874 : * Check constraints in child table match up with constraints in parent,
17875 : * and increment their coninhcount.
17876 : *
17877 : * Constraints that are marked ONLY in the parent are ignored.
17878 : *
17879 : * Called by CreateInheritance
17880 : *
17881 : * Currently all constraints in parent must be present in the child. One day we
17882 : * may consider adding new constraints like CREATE TABLE does.
17883 : *
17884 : * XXX This is O(N^2) which may be an issue with tables with hundreds of
17885 : * constraints. As long as tables have more like 10 constraints it shouldn't be
17886 : * a problem though. Even 100 constraints ought not be the end of the world.
17887 : *
17888 : * XXX See MergeWithExistingConstraint too if you change this code.
17889 : */
17890 : static void
17891 2157 : MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
17892 : {
17893 : Relation constraintrel;
17894 : SysScanDesc parent_scan;
17895 : ScanKeyData parent_key;
17896 : HeapTuple parent_tuple;
17897 2157 : Oid parent_relid = RelationGetRelid(parent_rel);
17898 : AttrMap *attmap;
17899 :
17900 2157 : constraintrel = table_open(ConstraintRelationId, RowExclusiveLock);
17901 :
17902 : /* Outer loop scans through the parent's constraint definitions */
17903 2157 : ScanKeyInit(&parent_key,
17904 : Anum_pg_constraint_conrelid,
17905 : BTEqualStrategyNumber, F_OIDEQ,
17906 : ObjectIdGetDatum(parent_relid));
17907 2157 : parent_scan = systable_beginscan(constraintrel, ConstraintRelidTypidNameIndexId,
17908 : true, NULL, 1, &parent_key);
17909 :
17910 2157 : attmap = build_attrmap_by_name(RelationGetDescr(parent_rel),
17911 : RelationGetDescr(child_rel),
17912 : true);
17913 :
17914 3768 : while (HeapTupleIsValid(parent_tuple = systable_getnext(parent_scan)))
17915 : {
17916 1651 : Form_pg_constraint parent_con = (Form_pg_constraint) GETSTRUCT(parent_tuple);
17917 : SysScanDesc child_scan;
17918 : ScanKeyData child_key;
17919 : HeapTuple child_tuple;
17920 : AttrNumber parent_attno;
17921 1651 : bool found = false;
17922 :
17923 1651 : if (parent_con->contype != CONSTRAINT_CHECK &&
17924 1483 : parent_con->contype != CONSTRAINT_NOTNULL)
17925 770 : continue;
17926 :
17927 : /* if the parent's constraint is marked NO INHERIT, it's not inherited */
17928 913 : if (parent_con->connoinherit)
17929 32 : continue;
17930 :
17931 881 : if (parent_con->contype == CONSTRAINT_NOTNULL)
17932 729 : parent_attno = extractNotNullColumn(parent_tuple);
17933 : else
17934 152 : parent_attno = InvalidAttrNumber;
17935 :
17936 : /* Search for a child constraint matching this one */
17937 881 : ScanKeyInit(&child_key,
17938 : Anum_pg_constraint_conrelid,
17939 : BTEqualStrategyNumber, F_OIDEQ,
17940 : ObjectIdGetDatum(RelationGetRelid(child_rel)));
17941 881 : child_scan = systable_beginscan(constraintrel, ConstraintRelidTypidNameIndexId,
17942 : true, NULL, 1, &child_key);
17943 :
17944 1408 : while (HeapTupleIsValid(child_tuple = systable_getnext(child_scan)))
17945 : {
17946 1392 : Form_pg_constraint child_con = (Form_pg_constraint) GETSTRUCT(child_tuple);
17947 : HeapTuple child_copy;
17948 :
17949 1392 : if (child_con->contype != parent_con->contype)
17950 274 : continue;
17951 :
17952 : /*
17953 : * CHECK constraint are matched by constraint name, NOT NULL ones
17954 : * by attribute number.
17955 : */
17956 1118 : if (child_con->contype == CONSTRAINT_CHECK)
17957 : {
17958 262 : if (strcmp(NameStr(parent_con->conname),
17959 199 : NameStr(child_con->conname)) != 0)
17960 63 : continue;
17961 : }
17962 919 : else if (child_con->contype == CONSTRAINT_NOTNULL)
17963 : {
17964 : Form_pg_attribute parent_attr;
17965 : Form_pg_attribute child_attr;
17966 : AttrNumber child_attno;
17967 :
17968 919 : parent_attr = TupleDescAttr(parent_rel->rd_att, parent_attno - 1);
17969 919 : child_attno = extractNotNullColumn(child_tuple);
17970 919 : if (parent_attno != attmap->attnums[child_attno - 1])
17971 190 : continue;
17972 :
17973 729 : child_attr = TupleDescAttr(child_rel->rd_att, child_attno - 1);
17974 : /* there shouldn't be constraints on dropped columns */
17975 729 : if (parent_attr->attisdropped || child_attr->attisdropped)
17976 0 : elog(ERROR, "found not-null constraint on dropped columns");
17977 : }
17978 :
17979 865 : if (child_con->contype == CONSTRAINT_CHECK &&
17980 136 : !constraints_equivalent(parent_tuple, child_tuple, RelationGetDescr(constraintrel)))
17981 4 : ereport(ERROR,
17982 : (errcode(ERRCODE_DATATYPE_MISMATCH),
17983 : errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
17984 : RelationGetRelationName(child_rel), NameStr(parent_con->conname))));
17985 :
17986 : /*
17987 : * If the child constraint is "no inherit" then cannot merge
17988 : */
17989 861 : if (child_con->connoinherit)
17990 8 : ereport(ERROR,
17991 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
17992 : errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
17993 : NameStr(child_con->conname), RelationGetRelationName(child_rel))));
17994 :
17995 : /*
17996 : * If the child constraint is "not valid" then cannot merge with a
17997 : * valid parent constraint
17998 : */
17999 853 : if (parent_con->convalidated && child_con->conenforced &&
18000 773 : !child_con->convalidated)
18001 8 : ereport(ERROR,
18002 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
18003 : errmsg("constraint \"%s\" conflicts with NOT VALID constraint on child table \"%s\"",
18004 : NameStr(child_con->conname), RelationGetRelationName(child_rel))));
18005 :
18006 : /*
18007 : * A NOT ENFORCED child constraint cannot be merged with an
18008 : * ENFORCED parent constraint. However, the reverse is allowed,
18009 : * where the child constraint is ENFORCED.
18010 : */
18011 845 : if (parent_con->conenforced && !child_con->conenforced)
18012 4 : ereport(ERROR,
18013 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
18014 : errmsg("constraint \"%s\" conflicts with NOT ENFORCED constraint on child table \"%s\"",
18015 : NameStr(child_con->conname), RelationGetRelationName(child_rel))));
18016 :
18017 : /*
18018 : * OK, bump the child constraint's inheritance count. (If we fail
18019 : * later on, this change will just roll back.)
18020 : */
18021 841 : child_copy = heap_copytuple(child_tuple);
18022 841 : child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
18023 :
18024 841 : if (pg_add_s16_overflow(child_con->coninhcount, 1,
18025 : &child_con->coninhcount))
18026 0 : ereport(ERROR,
18027 : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
18028 : errmsg("too many inheritance parents"));
18029 :
18030 : /*
18031 : * In case of partitions, an inherited constraint must be
18032 : * inherited only once since it cannot have multiple parents and
18033 : * it is never considered local.
18034 : */
18035 841 : if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
18036 : {
18037 : Assert(child_con->coninhcount == 1);
18038 750 : child_con->conislocal = false;
18039 : }
18040 :
18041 841 : CatalogTupleUpdate(constraintrel, &child_copy->t_self, child_copy);
18042 841 : heap_freetuple(child_copy);
18043 :
18044 841 : found = true;
18045 841 : break;
18046 : }
18047 :
18048 857 : systable_endscan(child_scan);
18049 :
18050 857 : if (!found)
18051 : {
18052 16 : if (parent_con->contype == CONSTRAINT_NOTNULL)
18053 0 : ereport(ERROR,
18054 : errcode(ERRCODE_DATATYPE_MISMATCH),
18055 : errmsg("column \"%s\" in child table \"%s\" must be marked NOT NULL",
18056 : get_attname(parent_relid,
18057 : extractNotNullColumn(parent_tuple),
18058 : false),
18059 : RelationGetRelationName(child_rel)));
18060 :
18061 16 : ereport(ERROR,
18062 : (errcode(ERRCODE_DATATYPE_MISMATCH),
18063 : errmsg("child table is missing constraint \"%s\"",
18064 : NameStr(parent_con->conname))));
18065 : }
18066 : }
18067 :
18068 2117 : systable_endscan(parent_scan);
18069 2117 : table_close(constraintrel, RowExclusiveLock);
18070 2117 : }
18071 :
18072 : /*
18073 : * ALTER TABLE NO INHERIT
18074 : *
18075 : * Return value is the address of the relation that is no longer parent.
18076 : */
18077 : static ObjectAddress
18078 69 : ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
18079 : {
18080 : ObjectAddress address;
18081 : Relation parent_rel;
18082 :
18083 : /*
18084 : * AccessShareLock on the parent is probably enough, seeing that DROP
18085 : * TABLE doesn't lock parent tables at all. We need some lock since we'll
18086 : * be inspecting the parent's schema.
18087 : */
18088 69 : parent_rel = table_openrv(parent, AccessShareLock);
18089 :
18090 : /*
18091 : * We don't bother to check ownership of the parent table --- ownership of
18092 : * the child is presumed enough rights.
18093 : */
18094 :
18095 : /* Off to RemoveInheritance() where most of the work happens */
18096 69 : RemoveInheritance(rel, parent_rel, false);
18097 :
18098 65 : ObjectAddressSet(address, RelationRelationId,
18099 : RelationGetRelid(parent_rel));
18100 :
18101 : /* keep our lock on the parent relation until commit */
18102 65 : table_close(parent_rel, NoLock);
18103 :
18104 65 : return address;
18105 : }
18106 :
18107 : /*
18108 : * MarkInheritDetached
18109 : *
18110 : * Set inhdetachpending for a partition, for ATExecDetachPartition
18111 : * in concurrent mode. While at it, verify that no other partition is
18112 : * already pending detach.
18113 : */
18114 : static void
18115 75 : MarkInheritDetached(Relation child_rel, Relation parent_rel)
18116 : {
18117 : Relation catalogRelation;
18118 : SysScanDesc scan;
18119 : ScanKeyData key;
18120 : HeapTuple inheritsTuple;
18121 75 : bool found = false;
18122 :
18123 : Assert(parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
18124 :
18125 : /*
18126 : * Find pg_inherits entries by inhparent. (We need to scan them all in
18127 : * order to verify that no other partition is pending detach.)
18128 : */
18129 75 : catalogRelation = table_open(InheritsRelationId, RowExclusiveLock);
18130 75 : ScanKeyInit(&key,
18131 : Anum_pg_inherits_inhparent,
18132 : BTEqualStrategyNumber, F_OIDEQ,
18133 : ObjectIdGetDatum(RelationGetRelid(parent_rel)));
18134 75 : scan = systable_beginscan(catalogRelation, InheritsParentIndexId,
18135 : true, NULL, 1, &key);
18136 :
18137 294 : while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
18138 : {
18139 : Form_pg_inherits inhForm;
18140 :
18141 145 : inhForm = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
18142 145 : if (inhForm->inhdetachpending)
18143 1 : ereport(ERROR,
18144 : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
18145 : errmsg("partition \"%s\" already pending detach in partitioned table \"%s.%s\"",
18146 : get_rel_name(inhForm->inhrelid),
18147 : get_namespace_name(parent_rel->rd_rel->relnamespace),
18148 : RelationGetRelationName(parent_rel)),
18149 : errhint("Use ALTER TABLE ... DETACH PARTITION ... FINALIZE to complete the pending detach operation."));
18150 :
18151 144 : if (inhForm->inhrelid == RelationGetRelid(child_rel))
18152 : {
18153 : HeapTuple newtup;
18154 :
18155 74 : newtup = heap_copytuple(inheritsTuple);
18156 74 : ((Form_pg_inherits) GETSTRUCT(newtup))->inhdetachpending = true;
18157 :
18158 74 : CatalogTupleUpdate(catalogRelation,
18159 74 : &inheritsTuple->t_self,
18160 : newtup);
18161 74 : found = true;
18162 74 : heap_freetuple(newtup);
18163 : /* keep looking, to ensure we catch others pending detach */
18164 : }
18165 : }
18166 :
18167 : /* Done */
18168 74 : systable_endscan(scan);
18169 74 : table_close(catalogRelation, RowExclusiveLock);
18170 :
18171 74 : if (!found)
18172 0 : ereport(ERROR,
18173 : (errcode(ERRCODE_UNDEFINED_TABLE),
18174 : errmsg("relation \"%s\" is not a partition of relation \"%s\"",
18175 : RelationGetRelationName(child_rel),
18176 : RelationGetRelationName(parent_rel))));
18177 74 : }
18178 :
18179 : /*
18180 : * RemoveInheritance
18181 : *
18182 : * Drop a parent from the child's parents. This just adjusts the attinhcount
18183 : * and attislocal of the columns and removes the pg_inherit and pg_depend
18184 : * entries. expect_detached is passed down to DeleteInheritsTuple, q.v..
18185 : *
18186 : * If attinhcount goes to 0 then attislocal gets set to true. If it goes back
18187 : * up attislocal stays true, which means if a child is ever removed from a
18188 : * parent then its columns will never be automatically dropped which may
18189 : * surprise. But at least we'll never surprise by dropping columns someone
18190 : * isn't expecting to be dropped which would actually mean data loss.
18191 : *
18192 : * coninhcount and conislocal for inherited constraints are adjusted in
18193 : * exactly the same way.
18194 : *
18195 : * Common to ATExecDropInherit() and ATExecDetachPartition().
18196 : */
18197 : static void
18198 842 : RemoveInheritance(Relation child_rel, Relation parent_rel, bool expect_detached)
18199 : {
18200 : Relation catalogRelation;
18201 : SysScanDesc scan;
18202 : ScanKeyData key[3];
18203 : HeapTuple attributeTuple,
18204 : constraintTuple;
18205 : AttrMap *attmap;
18206 : List *connames;
18207 : List *nncolumns;
18208 : bool found;
18209 : bool is_partitioning;
18210 :
18211 842 : is_partitioning = (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
18212 :
18213 842 : found = DeleteInheritsTuple(RelationGetRelid(child_rel),
18214 : RelationGetRelid(parent_rel),
18215 : expect_detached,
18216 842 : RelationGetRelationName(child_rel));
18217 842 : if (!found)
18218 : {
18219 16 : if (is_partitioning)
18220 12 : ereport(ERROR,
18221 : (errcode(ERRCODE_UNDEFINED_TABLE),
18222 : errmsg("relation \"%s\" is not a partition of relation \"%s\"",
18223 : RelationGetRelationName(child_rel),
18224 : RelationGetRelationName(parent_rel))));
18225 : else
18226 4 : ereport(ERROR,
18227 : (errcode(ERRCODE_UNDEFINED_TABLE),
18228 : errmsg("relation \"%s\" is not a parent of relation \"%s\"",
18229 : RelationGetRelationName(parent_rel),
18230 : RelationGetRelationName(child_rel))));
18231 : }
18232 :
18233 : /*
18234 : * Search through child columns looking for ones matching parent rel
18235 : */
18236 826 : catalogRelation = table_open(AttributeRelationId, RowExclusiveLock);
18237 826 : ScanKeyInit(&key[0],
18238 : Anum_pg_attribute_attrelid,
18239 : BTEqualStrategyNumber, F_OIDEQ,
18240 : ObjectIdGetDatum(RelationGetRelid(child_rel)));
18241 826 : scan = systable_beginscan(catalogRelation, AttributeRelidNumIndexId,
18242 : true, NULL, 1, key);
18243 7578 : while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
18244 : {
18245 6752 : Form_pg_attribute att = (Form_pg_attribute) GETSTRUCT(attributeTuple);
18246 :
18247 : /* Ignore if dropped or not inherited */
18248 6752 : if (att->attisdropped)
18249 28 : continue;
18250 6724 : if (att->attinhcount <= 0)
18251 4984 : continue;
18252 :
18253 1740 : if (SearchSysCacheExistsAttName(RelationGetRelid(parent_rel),
18254 1740 : NameStr(att->attname)))
18255 : {
18256 : /* Decrement inhcount and possibly set islocal to true */
18257 1704 : HeapTuple copyTuple = heap_copytuple(attributeTuple);
18258 1704 : Form_pg_attribute copy_att = (Form_pg_attribute) GETSTRUCT(copyTuple);
18259 :
18260 1704 : copy_att->attinhcount--;
18261 1704 : if (copy_att->attinhcount == 0)
18262 1684 : copy_att->attislocal = true;
18263 :
18264 1704 : CatalogTupleUpdate(catalogRelation, ©Tuple->t_self, copyTuple);
18265 1704 : heap_freetuple(copyTuple);
18266 : }
18267 : }
18268 826 : systable_endscan(scan);
18269 826 : table_close(catalogRelation, RowExclusiveLock);
18270 :
18271 : /*
18272 : * Likewise, find inherited check and not-null constraints and disinherit
18273 : * them. To do this, we first need a list of the names of the parent's
18274 : * check constraints. (We cheat a bit by only checking for name matches,
18275 : * assuming that the expressions will match.)
18276 : *
18277 : * For NOT NULL columns, we store column numbers to match, mapping them in
18278 : * to the child rel's attribute numbers.
18279 : */
18280 826 : attmap = build_attrmap_by_name(RelationGetDescr(child_rel),
18281 : RelationGetDescr(parent_rel),
18282 : false);
18283 :
18284 826 : catalogRelation = table_open(ConstraintRelationId, RowExclusiveLock);
18285 826 : ScanKeyInit(&key[0],
18286 : Anum_pg_constraint_conrelid,
18287 : BTEqualStrategyNumber, F_OIDEQ,
18288 : ObjectIdGetDatum(RelationGetRelid(parent_rel)));
18289 826 : scan = systable_beginscan(catalogRelation, ConstraintRelidTypidNameIndexId,
18290 : true, NULL, 1, key);
18291 :
18292 826 : connames = NIL;
18293 826 : nncolumns = NIL;
18294 :
18295 1523 : while (HeapTupleIsValid(constraintTuple = systable_getnext(scan)))
18296 : {
18297 697 : Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(constraintTuple);
18298 :
18299 697 : if (con->connoinherit)
18300 152 : continue;
18301 :
18302 545 : if (con->contype == CONSTRAINT_CHECK)
18303 76 : connames = lappend(connames, pstrdup(NameStr(con->conname)));
18304 545 : if (con->contype == CONSTRAINT_NOTNULL)
18305 : {
18306 249 : AttrNumber parent_attno = extractNotNullColumn(constraintTuple);
18307 :
18308 249 : nncolumns = lappend_int(nncolumns, attmap->attnums[parent_attno - 1]);
18309 : }
18310 : }
18311 :
18312 826 : systable_endscan(scan);
18313 :
18314 : /* Now scan the child's constraints to find matches */
18315 826 : ScanKeyInit(&key[0],
18316 : Anum_pg_constraint_conrelid,
18317 : BTEqualStrategyNumber, F_OIDEQ,
18318 : ObjectIdGetDatum(RelationGetRelid(child_rel)));
18319 826 : scan = systable_beginscan(catalogRelation, ConstraintRelidTypidNameIndexId,
18320 : true, NULL, 1, key);
18321 :
18322 1519 : while (HeapTupleIsValid(constraintTuple = systable_getnext(scan)))
18323 : {
18324 693 : Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(constraintTuple);
18325 693 : bool match = false;
18326 :
18327 : /*
18328 : * Match CHECK constraints by name, not-null constraints by column
18329 : * number, and ignore all others.
18330 : */
18331 693 : if (con->contype == CONSTRAINT_CHECK)
18332 : {
18333 228 : foreach_ptr(char, chkname, connames)
18334 : {
18335 80 : if (con->contype == CONSTRAINT_CHECK &&
18336 80 : strcmp(NameStr(con->conname), chkname) == 0)
18337 : {
18338 76 : match = true;
18339 76 : connames = foreach_delete_current(connames, chkname);
18340 76 : break;
18341 : }
18342 : }
18343 : }
18344 581 : else if (con->contype == CONSTRAINT_NOTNULL)
18345 : {
18346 289 : AttrNumber child_attno = extractNotNullColumn(constraintTuple);
18347 :
18348 582 : foreach_int(prevattno, nncolumns)
18349 : {
18350 253 : if (prevattno == child_attno)
18351 : {
18352 249 : match = true;
18353 249 : nncolumns = foreach_delete_current(nncolumns, prevattno);
18354 249 : break;
18355 : }
18356 : }
18357 : }
18358 : else
18359 292 : continue;
18360 :
18361 401 : if (match)
18362 : {
18363 : /* Decrement inhcount and possibly set islocal to true */
18364 325 : HeapTuple copyTuple = heap_copytuple(constraintTuple);
18365 325 : Form_pg_constraint copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
18366 :
18367 325 : if (copy_con->coninhcount <= 0) /* shouldn't happen */
18368 0 : elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
18369 : RelationGetRelid(child_rel), NameStr(copy_con->conname));
18370 :
18371 325 : copy_con->coninhcount--;
18372 325 : if (copy_con->coninhcount == 0)
18373 313 : copy_con->conislocal = true;
18374 :
18375 325 : CatalogTupleUpdate(catalogRelation, ©Tuple->t_self, copyTuple);
18376 325 : heap_freetuple(copyTuple);
18377 : }
18378 : }
18379 :
18380 : /* We should have matched all constraints */
18381 826 : if (connames != NIL || nncolumns != NIL)
18382 0 : elog(ERROR, "%d unmatched constraints while removing inheritance from \"%s\" to \"%s\"",
18383 : list_length(connames) + list_length(nncolumns),
18384 : RelationGetRelationName(child_rel), RelationGetRelationName(parent_rel));
18385 :
18386 826 : systable_endscan(scan);
18387 826 : table_close(catalogRelation, RowExclusiveLock);
18388 :
18389 826 : drop_parent_dependency(RelationGetRelid(child_rel),
18390 : RelationRelationId,
18391 : RelationGetRelid(parent_rel),
18392 : child_dependency_type(is_partitioning));
18393 :
18394 : /*
18395 : * Post alter hook of this inherits. Since object_access_hook doesn't take
18396 : * multiple object identifiers, we relay oid of parent relation using
18397 : * auxiliary_id argument.
18398 : */
18399 826 : InvokeObjectPostAlterHookArg(InheritsRelationId,
18400 : RelationGetRelid(child_rel), 0,
18401 : RelationGetRelid(parent_rel), false);
18402 826 : }
18403 :
18404 : /*
18405 : * Drop the dependency created by StoreCatalogInheritance1 (CREATE TABLE
18406 : * INHERITS/ALTER TABLE INHERIT -- refclassid will be RelationRelationId) or
18407 : * heap_create_with_catalog (CREATE TABLE OF/ALTER TABLE OF -- refclassid will
18408 : * be TypeRelationId). There's no convenient way to do this, so go trawling
18409 : * through pg_depend.
18410 : */
18411 : static void
18412 834 : drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid,
18413 : DependencyType deptype)
18414 : {
18415 : Relation catalogRelation;
18416 : SysScanDesc scan;
18417 : ScanKeyData key[3];
18418 : HeapTuple depTuple;
18419 :
18420 834 : catalogRelation = table_open(DependRelationId, RowExclusiveLock);
18421 :
18422 834 : ScanKeyInit(&key[0],
18423 : Anum_pg_depend_classid,
18424 : BTEqualStrategyNumber, F_OIDEQ,
18425 : ObjectIdGetDatum(RelationRelationId));
18426 834 : ScanKeyInit(&key[1],
18427 : Anum_pg_depend_objid,
18428 : BTEqualStrategyNumber, F_OIDEQ,
18429 : ObjectIdGetDatum(relid));
18430 834 : ScanKeyInit(&key[2],
18431 : Anum_pg_depend_objsubid,
18432 : BTEqualStrategyNumber, F_INT4EQ,
18433 : Int32GetDatum(0));
18434 :
18435 834 : scan = systable_beginscan(catalogRelation, DependDependerIndexId, true,
18436 : NULL, 3, key);
18437 :
18438 2555 : while (HeapTupleIsValid(depTuple = systable_getnext(scan)))
18439 : {
18440 1721 : Form_pg_depend dep = (Form_pg_depend) GETSTRUCT(depTuple);
18441 :
18442 1721 : if (dep->refclassid == refclassid &&
18443 862 : dep->refobjid == refobjid &&
18444 834 : dep->refobjsubid == 0 &&
18445 834 : dep->deptype == deptype)
18446 834 : CatalogTupleDelete(catalogRelation, &depTuple->t_self);
18447 : }
18448 :
18449 834 : systable_endscan(scan);
18450 834 : table_close(catalogRelation, RowExclusiveLock);
18451 834 : }
18452 :
18453 : /*
18454 : * ALTER TABLE OF
18455 : *
18456 : * Attach a table to a composite type, as though it had been created with CREATE
18457 : * TABLE OF. All attname, atttypid, atttypmod and attcollation must match. The
18458 : * subject table must not have inheritance parents. These restrictions ensure
18459 : * that you cannot create a configuration impossible with CREATE TABLE OF alone.
18460 : *
18461 : * The address of the type is returned.
18462 : */
18463 : static ObjectAddress
18464 42 : ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
18465 : {
18466 42 : Oid relid = RelationGetRelid(rel);
18467 : Type typetuple;
18468 : Form_pg_type typeform;
18469 : Oid typeid;
18470 : Relation inheritsRelation,
18471 : relationRelation;
18472 : SysScanDesc scan;
18473 : ScanKeyData key;
18474 : AttrNumber table_attno,
18475 : type_attno;
18476 : TupleDesc typeTupleDesc,
18477 : tableTupleDesc;
18478 : ObjectAddress tableobj,
18479 : typeobj;
18480 : HeapTuple classtuple;
18481 :
18482 : /* Validate the type. */
18483 42 : typetuple = typenameType(NULL, ofTypename, NULL);
18484 42 : check_of_type(typetuple);
18485 42 : typeform = (Form_pg_type) GETSTRUCT(typetuple);
18486 42 : typeid = typeform->oid;
18487 :
18488 : /* Fail if the table has any inheritance parents. */
18489 42 : inheritsRelation = table_open(InheritsRelationId, AccessShareLock);
18490 42 : ScanKeyInit(&key,
18491 : Anum_pg_inherits_inhrelid,
18492 : BTEqualStrategyNumber, F_OIDEQ,
18493 : ObjectIdGetDatum(relid));
18494 42 : scan = systable_beginscan(inheritsRelation, InheritsRelidSeqnoIndexId,
18495 : true, NULL, 1, &key);
18496 42 : if (HeapTupleIsValid(systable_getnext(scan)))
18497 4 : ereport(ERROR,
18498 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18499 : errmsg("typed tables cannot inherit")));
18500 38 : systable_endscan(scan);
18501 38 : table_close(inheritsRelation, AccessShareLock);
18502 :
18503 : /*
18504 : * Check the tuple descriptors for compatibility. Unlike inheritance, we
18505 : * require that the order also match. However, attnotnull need not match.
18506 : */
18507 38 : typeTupleDesc = lookup_rowtype_tupdesc(typeid, -1);
18508 38 : tableTupleDesc = RelationGetDescr(rel);
18509 38 : table_attno = 1;
18510 121 : for (type_attno = 1; type_attno <= typeTupleDesc->natts; type_attno++)
18511 : {
18512 : Form_pg_attribute type_attr,
18513 : table_attr;
18514 : const char *type_attname,
18515 : *table_attname;
18516 :
18517 : /* Get the next non-dropped type attribute. */
18518 99 : type_attr = TupleDescAttr(typeTupleDesc, type_attno - 1);
18519 99 : if (type_attr->attisdropped)
18520 29 : continue;
18521 70 : type_attname = NameStr(type_attr->attname);
18522 :
18523 : /* Get the next non-dropped table attribute. */
18524 : do
18525 : {
18526 78 : if (table_attno > tableTupleDesc->natts)
18527 4 : ereport(ERROR,
18528 : (errcode(ERRCODE_DATATYPE_MISMATCH),
18529 : errmsg("table is missing column \"%s\"",
18530 : type_attname)));
18531 74 : table_attr = TupleDescAttr(tableTupleDesc, table_attno - 1);
18532 74 : table_attno++;
18533 74 : } while (table_attr->attisdropped);
18534 66 : table_attname = NameStr(table_attr->attname);
18535 :
18536 : /* Compare name. */
18537 66 : if (strncmp(table_attname, type_attname, NAMEDATALEN) != 0)
18538 4 : ereport(ERROR,
18539 : (errcode(ERRCODE_DATATYPE_MISMATCH),
18540 : errmsg("table has column \"%s\" where type requires \"%s\"",
18541 : table_attname, type_attname)));
18542 :
18543 : /* Compare type. */
18544 62 : if (table_attr->atttypid != type_attr->atttypid ||
18545 58 : table_attr->atttypmod != type_attr->atttypmod ||
18546 54 : table_attr->attcollation != type_attr->attcollation)
18547 8 : ereport(ERROR,
18548 : (errcode(ERRCODE_DATATYPE_MISMATCH),
18549 : errmsg("table \"%s\" has different type for column \"%s\"",
18550 : RelationGetRelationName(rel), type_attname)));
18551 : }
18552 22 : ReleaseTupleDesc(typeTupleDesc);
18553 :
18554 : /* Any remaining columns at the end of the table had better be dropped. */
18555 22 : for (; table_attno <= tableTupleDesc->natts; table_attno++)
18556 : {
18557 4 : Form_pg_attribute table_attr = TupleDescAttr(tableTupleDesc,
18558 : table_attno - 1);
18559 :
18560 4 : if (!table_attr->attisdropped)
18561 4 : ereport(ERROR,
18562 : (errcode(ERRCODE_DATATYPE_MISMATCH),
18563 : errmsg("table has extra column \"%s\"",
18564 : NameStr(table_attr->attname))));
18565 : }
18566 :
18567 : /* If the table was already typed, drop the existing dependency. */
18568 18 : if (rel->rd_rel->reloftype)
18569 4 : drop_parent_dependency(relid, TypeRelationId, rel->rd_rel->reloftype,
18570 : DEPENDENCY_NORMAL);
18571 :
18572 : /* Record a dependency on the new type. */
18573 18 : tableobj.classId = RelationRelationId;
18574 18 : tableobj.objectId = relid;
18575 18 : tableobj.objectSubId = 0;
18576 18 : typeobj.classId = TypeRelationId;
18577 18 : typeobj.objectId = typeid;
18578 18 : typeobj.objectSubId = 0;
18579 18 : recordDependencyOn(&tableobj, &typeobj, DEPENDENCY_NORMAL);
18580 :
18581 : /* Update pg_class.reloftype */
18582 18 : relationRelation = table_open(RelationRelationId, RowExclusiveLock);
18583 18 : classtuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
18584 18 : if (!HeapTupleIsValid(classtuple))
18585 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
18586 18 : ((Form_pg_class) GETSTRUCT(classtuple))->reloftype = typeid;
18587 18 : CatalogTupleUpdate(relationRelation, &classtuple->t_self, classtuple);
18588 :
18589 18 : InvokeObjectPostAlterHook(RelationRelationId, relid, 0);
18590 :
18591 18 : heap_freetuple(classtuple);
18592 18 : table_close(relationRelation, RowExclusiveLock);
18593 :
18594 18 : ReleaseSysCache(typetuple);
18595 :
18596 18 : return typeobj;
18597 : }
18598 :
18599 : /*
18600 : * ALTER TABLE NOT OF
18601 : *
18602 : * Detach a typed table from its originating type. Just clear reloftype and
18603 : * remove the dependency.
18604 : */
18605 : static void
18606 4 : ATExecDropOf(Relation rel, LOCKMODE lockmode)
18607 : {
18608 4 : Oid relid = RelationGetRelid(rel);
18609 : Relation relationRelation;
18610 : HeapTuple tuple;
18611 :
18612 4 : if (!OidIsValid(rel->rd_rel->reloftype))
18613 0 : ereport(ERROR,
18614 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18615 : errmsg("\"%s\" is not a typed table",
18616 : RelationGetRelationName(rel))));
18617 :
18618 : /*
18619 : * We don't bother to check ownership of the type --- ownership of the
18620 : * table is presumed enough rights. No lock required on the type, either.
18621 : */
18622 :
18623 4 : drop_parent_dependency(relid, TypeRelationId, rel->rd_rel->reloftype,
18624 : DEPENDENCY_NORMAL);
18625 :
18626 : /* Clear pg_class.reloftype */
18627 4 : relationRelation = table_open(RelationRelationId, RowExclusiveLock);
18628 4 : tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
18629 4 : if (!HeapTupleIsValid(tuple))
18630 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
18631 4 : ((Form_pg_class) GETSTRUCT(tuple))->reloftype = InvalidOid;
18632 4 : CatalogTupleUpdate(relationRelation, &tuple->t_self, tuple);
18633 :
18634 4 : InvokeObjectPostAlterHook(RelationRelationId, relid, 0);
18635 :
18636 4 : heap_freetuple(tuple);
18637 4 : table_close(relationRelation, RowExclusiveLock);
18638 4 : }
18639 :
18640 : /*
18641 : * relation_mark_replica_identity: Update a table's replica identity
18642 : *
18643 : * Iff ri_type = REPLICA_IDENTITY_INDEX, indexOid must be the Oid of a suitable
18644 : * index. Otherwise, it must be InvalidOid.
18645 : *
18646 : * Caller had better hold an exclusive lock on the relation, as the results
18647 : * of running two of these concurrently wouldn't be pretty.
18648 : */
18649 : static void
18650 286 : relation_mark_replica_identity(Relation rel, char ri_type, Oid indexOid,
18651 : bool is_internal)
18652 : {
18653 : Relation pg_index;
18654 : Relation pg_class;
18655 : HeapTuple pg_class_tuple;
18656 : HeapTuple pg_index_tuple;
18657 : Form_pg_class pg_class_form;
18658 : Form_pg_index pg_index_form;
18659 : ListCell *index;
18660 :
18661 : /*
18662 : * Check whether relreplident has changed, and update it if so.
18663 : */
18664 286 : pg_class = table_open(RelationRelationId, RowExclusiveLock);
18665 286 : pg_class_tuple = SearchSysCacheCopy1(RELOID,
18666 : ObjectIdGetDatum(RelationGetRelid(rel)));
18667 286 : if (!HeapTupleIsValid(pg_class_tuple))
18668 0 : elog(ERROR, "cache lookup failed for relation \"%s\"",
18669 : RelationGetRelationName(rel));
18670 286 : pg_class_form = (Form_pg_class) GETSTRUCT(pg_class_tuple);
18671 286 : if (pg_class_form->relreplident != ri_type)
18672 : {
18673 253 : pg_class_form->relreplident = ri_type;
18674 253 : CatalogTupleUpdate(pg_class, &pg_class_tuple->t_self, pg_class_tuple);
18675 : }
18676 286 : table_close(pg_class, RowExclusiveLock);
18677 286 : heap_freetuple(pg_class_tuple);
18678 :
18679 : /*
18680 : * Update the per-index indisreplident flags correctly.
18681 : */
18682 286 : pg_index = table_open(IndexRelationId, RowExclusiveLock);
18683 757 : foreach(index, RelationGetIndexList(rel))
18684 : {
18685 471 : Oid thisIndexOid = lfirst_oid(index);
18686 471 : bool dirty = false;
18687 :
18688 471 : pg_index_tuple = SearchSysCacheCopy1(INDEXRELID,
18689 : ObjectIdGetDatum(thisIndexOid));
18690 471 : if (!HeapTupleIsValid(pg_index_tuple))
18691 0 : elog(ERROR, "cache lookup failed for index %u", thisIndexOid);
18692 471 : pg_index_form = (Form_pg_index) GETSTRUCT(pg_index_tuple);
18693 :
18694 471 : if (thisIndexOid == indexOid)
18695 : {
18696 : /* Set the bit if not already set. */
18697 153 : if (!pg_index_form->indisreplident)
18698 : {
18699 141 : dirty = true;
18700 141 : pg_index_form->indisreplident = true;
18701 : }
18702 : }
18703 : else
18704 : {
18705 : /* Unset the bit if set. */
18706 318 : if (pg_index_form->indisreplident)
18707 : {
18708 34 : dirty = true;
18709 34 : pg_index_form->indisreplident = false;
18710 : }
18711 : }
18712 :
18713 471 : if (dirty)
18714 : {
18715 175 : CatalogTupleUpdate(pg_index, &pg_index_tuple->t_self, pg_index_tuple);
18716 175 : InvokeObjectPostAlterHookArg(IndexRelationId, thisIndexOid, 0,
18717 : InvalidOid, is_internal);
18718 :
18719 : /*
18720 : * Invalidate the relcache for the table, so that after we commit
18721 : * all sessions will refresh the table's replica identity index
18722 : * before attempting any UPDATE or DELETE on the table. (If we
18723 : * changed the table's pg_class row above, then a relcache inval
18724 : * is already queued due to that; but we might not have.)
18725 : */
18726 175 : CacheInvalidateRelcache(rel);
18727 : }
18728 471 : heap_freetuple(pg_index_tuple);
18729 : }
18730 :
18731 286 : table_close(pg_index, RowExclusiveLock);
18732 286 : }
18733 :
18734 : /*
18735 : * ALTER TABLE <name> REPLICA IDENTITY ...
18736 : */
18737 : static void
18738 318 : ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode)
18739 : {
18740 : Oid indexOid;
18741 : Relation indexRel;
18742 : int key;
18743 :
18744 318 : if (stmt->identity_type == REPLICA_IDENTITY_DEFAULT)
18745 : {
18746 5 : relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
18747 5 : return;
18748 : }
18749 313 : else if (stmt->identity_type == REPLICA_IDENTITY_FULL)
18750 : {
18751 98 : relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
18752 98 : return;
18753 : }
18754 215 : else if (stmt->identity_type == REPLICA_IDENTITY_NOTHING)
18755 : {
18756 30 : relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
18757 30 : return;
18758 : }
18759 185 : else if (stmt->identity_type == REPLICA_IDENTITY_INDEX)
18760 : {
18761 : /* fallthrough */ ;
18762 : }
18763 : else
18764 0 : elog(ERROR, "unexpected identity type %u", stmt->identity_type);
18765 :
18766 : /* Check that the index exists */
18767 185 : indexOid = get_relname_relid(stmt->name, rel->rd_rel->relnamespace);
18768 185 : if (!OidIsValid(indexOid))
18769 0 : ereport(ERROR,
18770 : (errcode(ERRCODE_UNDEFINED_OBJECT),
18771 : errmsg("index \"%s\" for table \"%s\" does not exist",
18772 : stmt->name, RelationGetRelationName(rel))));
18773 :
18774 185 : indexRel = index_open(indexOid, ShareLock);
18775 :
18776 : /* Check that the index is on the relation we're altering. */
18777 185 : if (indexRel->rd_index == NULL ||
18778 185 : indexRel->rd_index->indrelid != RelationGetRelid(rel))
18779 4 : ereport(ERROR,
18780 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18781 : errmsg("\"%s\" is not an index for table \"%s\"",
18782 : RelationGetRelationName(indexRel),
18783 : RelationGetRelationName(rel))));
18784 :
18785 : /*
18786 : * The AM must support uniqueness, and the index must in fact be unique.
18787 : * If we have a WITHOUT OVERLAPS constraint (identified by uniqueness +
18788 : * exclusion), we can use that too.
18789 : */
18790 181 : if ((!indexRel->rd_indam->amcanunique ||
18791 167 : !indexRel->rd_index->indisunique) &&
18792 18 : !(indexRel->rd_index->indisunique && indexRel->rd_index->indisexclusion))
18793 8 : ereport(ERROR,
18794 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18795 : errmsg("cannot use non-unique index \"%s\" as replica identity",
18796 : RelationGetRelationName(indexRel))));
18797 : /* Deferred indexes are not guaranteed to be always unique. */
18798 173 : if (!indexRel->rd_index->indimmediate)
18799 8 : ereport(ERROR,
18800 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
18801 : errmsg("cannot use non-immediate index \"%s\" as replica identity",
18802 : RelationGetRelationName(indexRel))));
18803 : /* Expression indexes aren't supported. */
18804 165 : if (RelationGetIndexExpressions(indexRel) != NIL)
18805 4 : ereport(ERROR,
18806 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
18807 : errmsg("cannot use expression index \"%s\" as replica identity",
18808 : RelationGetRelationName(indexRel))));
18809 : /* Predicate indexes aren't supported. */
18810 161 : if (RelationGetIndexPredicate(indexRel) != NIL)
18811 4 : ereport(ERROR,
18812 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
18813 : errmsg("cannot use partial index \"%s\" as replica identity",
18814 : RelationGetRelationName(indexRel))));
18815 :
18816 : /* Check index for nullable columns. */
18817 354 : for (key = 0; key < IndexRelationGetNumberOfKeyAttributes(indexRel); key++)
18818 : {
18819 201 : int16 attno = indexRel->rd_index->indkey.values[key];
18820 : Form_pg_attribute attr;
18821 :
18822 : /*
18823 : * Reject any other system columns. (Going forward, we'll disallow
18824 : * indexes containing such columns in the first place, but they might
18825 : * exist in older branches.)
18826 : */
18827 201 : if (attno <= 0)
18828 0 : ereport(ERROR,
18829 : (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
18830 : errmsg("index \"%s\" cannot be used as replica identity because column %d is a system column",
18831 : RelationGetRelationName(indexRel), attno)));
18832 :
18833 201 : attr = TupleDescAttr(rel->rd_att, attno - 1);
18834 201 : if (!attr->attnotnull)
18835 4 : ereport(ERROR,
18836 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18837 : errmsg("index \"%s\" cannot be used as replica identity because column \"%s\" is nullable",
18838 : RelationGetRelationName(indexRel),
18839 : NameStr(attr->attname))));
18840 : }
18841 :
18842 : /* This index is suitable for use as a replica identity. Mark it. */
18843 153 : relation_mark_replica_identity(rel, stmt->identity_type, indexOid, true);
18844 :
18845 153 : index_close(indexRel, NoLock);
18846 : }
18847 :
18848 : /*
18849 : * ALTER TABLE ENABLE/DISABLE ROW LEVEL SECURITY
18850 : */
18851 : static void
18852 246 : ATExecSetRowSecurity(Relation rel, bool rls)
18853 : {
18854 : Relation pg_class;
18855 : Oid relid;
18856 : HeapTuple tuple;
18857 :
18858 246 : relid = RelationGetRelid(rel);
18859 :
18860 : /* Pull the record for this relation and update it */
18861 246 : pg_class = table_open(RelationRelationId, RowExclusiveLock);
18862 :
18863 246 : tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
18864 :
18865 246 : if (!HeapTupleIsValid(tuple))
18866 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
18867 :
18868 246 : ((Form_pg_class) GETSTRUCT(tuple))->relrowsecurity = rls;
18869 246 : CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
18870 :
18871 246 : InvokeObjectPostAlterHook(RelationRelationId,
18872 : RelationGetRelid(rel), 0);
18873 :
18874 246 : table_close(pg_class, RowExclusiveLock);
18875 246 : heap_freetuple(tuple);
18876 246 : }
18877 :
18878 : /*
18879 : * ALTER TABLE FORCE/NO FORCE ROW LEVEL SECURITY
18880 : */
18881 : static void
18882 90 : ATExecForceNoForceRowSecurity(Relation rel, bool force_rls)
18883 : {
18884 : Relation pg_class;
18885 : Oid relid;
18886 : HeapTuple tuple;
18887 :
18888 90 : relid = RelationGetRelid(rel);
18889 :
18890 90 : pg_class = table_open(RelationRelationId, RowExclusiveLock);
18891 :
18892 90 : tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
18893 :
18894 90 : if (!HeapTupleIsValid(tuple))
18895 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
18896 :
18897 90 : ((Form_pg_class) GETSTRUCT(tuple))->relforcerowsecurity = force_rls;
18898 90 : CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
18899 :
18900 90 : InvokeObjectPostAlterHook(RelationRelationId,
18901 : RelationGetRelid(rel), 0);
18902 :
18903 90 : table_close(pg_class, RowExclusiveLock);
18904 90 : heap_freetuple(tuple);
18905 90 : }
18906 :
18907 : /*
18908 : * ALTER FOREIGN TABLE <name> OPTIONS (...)
18909 : */
18910 : static void
18911 33 : ATExecGenericOptions(Relation rel, List *options)
18912 : {
18913 : Relation ftrel;
18914 : ForeignServer *server;
18915 : ForeignDataWrapper *fdw;
18916 : HeapTuple tuple;
18917 : bool isnull;
18918 : Datum repl_val[Natts_pg_foreign_table];
18919 : bool repl_null[Natts_pg_foreign_table];
18920 : bool repl_repl[Natts_pg_foreign_table];
18921 : Datum datum;
18922 : Form_pg_foreign_table tableform;
18923 :
18924 33 : if (options == NIL)
18925 0 : return;
18926 :
18927 33 : ftrel = table_open(ForeignTableRelationId, RowExclusiveLock);
18928 :
18929 33 : tuple = SearchSysCacheCopy1(FOREIGNTABLEREL,
18930 : ObjectIdGetDatum(rel->rd_id));
18931 33 : if (!HeapTupleIsValid(tuple))
18932 0 : ereport(ERROR,
18933 : (errcode(ERRCODE_UNDEFINED_OBJECT),
18934 : errmsg("foreign table \"%s\" does not exist",
18935 : RelationGetRelationName(rel))));
18936 33 : tableform = (Form_pg_foreign_table) GETSTRUCT(tuple);
18937 33 : server = GetForeignServer(tableform->ftserver);
18938 33 : fdw = GetForeignDataWrapper(server->fdwid);
18939 :
18940 33 : memset(repl_val, 0, sizeof(repl_val));
18941 33 : memset(repl_null, false, sizeof(repl_null));
18942 33 : memset(repl_repl, false, sizeof(repl_repl));
18943 :
18944 : /* Extract the current options */
18945 33 : datum = SysCacheGetAttr(FOREIGNTABLEREL,
18946 : tuple,
18947 : Anum_pg_foreign_table_ftoptions,
18948 : &isnull);
18949 33 : if (isnull)
18950 2 : datum = PointerGetDatum(NULL);
18951 :
18952 : /* Transform the options */
18953 33 : datum = transformGenericOptions(ForeignTableRelationId,
18954 : datum,
18955 : options,
18956 : fdw->fdwvalidator);
18957 :
18958 32 : if (DatumGetPointer(datum) != NULL)
18959 32 : repl_val[Anum_pg_foreign_table_ftoptions - 1] = datum;
18960 : else
18961 0 : repl_null[Anum_pg_foreign_table_ftoptions - 1] = true;
18962 :
18963 32 : repl_repl[Anum_pg_foreign_table_ftoptions - 1] = true;
18964 :
18965 : /* Everything looks good - update the tuple */
18966 :
18967 32 : tuple = heap_modify_tuple(tuple, RelationGetDescr(ftrel),
18968 : repl_val, repl_null, repl_repl);
18969 :
18970 32 : CatalogTupleUpdate(ftrel, &tuple->t_self, tuple);
18971 :
18972 : /*
18973 : * Invalidate relcache so that all sessions will refresh any cached plans
18974 : * that might depend on the old options.
18975 : */
18976 32 : CacheInvalidateRelcache(rel);
18977 :
18978 32 : InvokeObjectPostAlterHook(ForeignTableRelationId,
18979 : RelationGetRelid(rel), 0);
18980 :
18981 32 : table_close(ftrel, RowExclusiveLock);
18982 :
18983 32 : heap_freetuple(tuple);
18984 : }
18985 :
18986 : /*
18987 : * ALTER TABLE ALTER COLUMN SET COMPRESSION
18988 : *
18989 : * Return value is the address of the modified column
18990 : */
18991 : static ObjectAddress
18992 48 : ATExecSetCompression(Relation rel,
18993 : const char *column,
18994 : Node *newValue,
18995 : LOCKMODE lockmode)
18996 : {
18997 : Relation attrel;
18998 : HeapTuple tuple;
18999 : Form_pg_attribute atttableform;
19000 : AttrNumber attnum;
19001 : char *compression;
19002 : char cmethod;
19003 : ObjectAddress address;
19004 :
19005 48 : compression = strVal(newValue);
19006 :
19007 48 : attrel = table_open(AttributeRelationId, RowExclusiveLock);
19008 :
19009 : /* copy the cache entry so we can scribble on it below */
19010 48 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), column);
19011 48 : if (!HeapTupleIsValid(tuple))
19012 0 : ereport(ERROR,
19013 : (errcode(ERRCODE_UNDEFINED_COLUMN),
19014 : errmsg("column \"%s\" of relation \"%s\" does not exist",
19015 : column, RelationGetRelationName(rel))));
19016 :
19017 : /* prevent them from altering a system attribute */
19018 48 : atttableform = (Form_pg_attribute) GETSTRUCT(tuple);
19019 48 : attnum = atttableform->attnum;
19020 48 : if (attnum <= 0)
19021 0 : ereport(ERROR,
19022 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
19023 : errmsg("cannot alter system column \"%s\"", column)));
19024 :
19025 : /*
19026 : * Check that column type is compressible, then get the attribute
19027 : * compression method code
19028 : */
19029 48 : cmethod = GetAttributeCompression(atttableform->atttypid, compression);
19030 :
19031 : /* update pg_attribute entry */
19032 44 : atttableform->attcompression = cmethod;
19033 44 : CatalogTupleUpdate(attrel, &tuple->t_self, tuple);
19034 :
19035 44 : InvokeObjectPostAlterHook(RelationRelationId,
19036 : RelationGetRelid(rel),
19037 : attnum);
19038 :
19039 : /*
19040 : * Apply the change to indexes as well (only for simple index columns,
19041 : * matching behavior of index.c ConstructTupleDescriptor()).
19042 : */
19043 44 : SetIndexStorageProperties(rel, attrel, attnum,
19044 : false, 0,
19045 : true, cmethod,
19046 : lockmode);
19047 :
19048 44 : heap_freetuple(tuple);
19049 :
19050 44 : table_close(attrel, RowExclusiveLock);
19051 :
19052 : /* make changes visible */
19053 44 : CommandCounterIncrement();
19054 :
19055 44 : ObjectAddressSubSet(address, RelationRelationId,
19056 : RelationGetRelid(rel), attnum);
19057 44 : return address;
19058 : }
19059 :
19060 :
19061 : /*
19062 : * Preparation phase for SET LOGGED/UNLOGGED
19063 : *
19064 : * This verifies that we're not trying to change a temp table. Also,
19065 : * existing foreign key constraints are checked to avoid ending up with
19066 : * permanent tables referencing unlogged tables.
19067 : */
19068 : static void
19069 67 : ATPrepChangePersistence(AlteredTableInfo *tab, Relation rel, bool toLogged)
19070 : {
19071 : Relation pg_constraint;
19072 : HeapTuple tuple;
19073 : SysScanDesc scan;
19074 : ScanKeyData skey[1];
19075 :
19076 : /*
19077 : * Disallow changing status for a temp table. Also verify whether we can
19078 : * get away with doing nothing; in such cases we don't need to run the
19079 : * checks below, either.
19080 : */
19081 67 : switch (rel->rd_rel->relpersistence)
19082 : {
19083 0 : case RELPERSISTENCE_TEMP:
19084 0 : ereport(ERROR,
19085 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
19086 : errmsg("cannot change logged status of table \"%s\" because it is temporary",
19087 : RelationGetRelationName(rel)),
19088 : errtable(rel)));
19089 : break;
19090 37 : case RELPERSISTENCE_PERMANENT:
19091 37 : if (toLogged)
19092 : /* nothing to do */
19093 8 : return;
19094 33 : break;
19095 30 : case RELPERSISTENCE_UNLOGGED:
19096 30 : if (!toLogged)
19097 : /* nothing to do */
19098 4 : return;
19099 26 : break;
19100 : }
19101 :
19102 : /*
19103 : * Check that the table is not part of any publication when changing to
19104 : * UNLOGGED, as UNLOGGED tables can't be published.
19105 : */
19106 92 : if (!toLogged &&
19107 33 : GetRelationIncludedPublications(RelationGetRelid(rel)) != NIL)
19108 0 : ereport(ERROR,
19109 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
19110 : errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
19111 : RelationGetRelationName(rel)),
19112 : errdetail("Unlogged relations cannot be replicated.")));
19113 :
19114 : /*
19115 : * Check existing foreign key constraints to preserve the invariant that
19116 : * permanent tables cannot reference unlogged ones. Self-referencing
19117 : * foreign keys can safely be ignored.
19118 : */
19119 59 : pg_constraint = table_open(ConstraintRelationId, AccessShareLock);
19120 :
19121 : /*
19122 : * Scan conrelid if changing to permanent, else confrelid. This also
19123 : * determines whether a useful index exists.
19124 : */
19125 59 : ScanKeyInit(&skey[0],
19126 : toLogged ? Anum_pg_constraint_conrelid :
19127 : Anum_pg_constraint_confrelid,
19128 : BTEqualStrategyNumber, F_OIDEQ,
19129 : ObjectIdGetDatum(RelationGetRelid(rel)));
19130 59 : scan = systable_beginscan(pg_constraint,
19131 : toLogged ? ConstraintRelidTypidNameIndexId : InvalidOid,
19132 : true, NULL, 1, skey);
19133 :
19134 95 : while (HeapTupleIsValid(tuple = systable_getnext(scan)))
19135 : {
19136 44 : Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
19137 :
19138 44 : if (con->contype == CONSTRAINT_FOREIGN)
19139 : {
19140 : Oid foreignrelid;
19141 : Relation foreignrel;
19142 :
19143 : /* the opposite end of what we used as scankey */
19144 20 : foreignrelid = toLogged ? con->confrelid : con->conrelid;
19145 :
19146 : /* ignore if self-referencing */
19147 20 : if (RelationGetRelid(rel) == foreignrelid)
19148 8 : continue;
19149 :
19150 12 : foreignrel = relation_open(foreignrelid, AccessShareLock);
19151 :
19152 12 : if (toLogged)
19153 : {
19154 4 : if (!RelationIsPermanent(foreignrel))
19155 4 : ereport(ERROR,
19156 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
19157 : errmsg("could not change table \"%s\" to logged because it references unlogged table \"%s\"",
19158 : RelationGetRelationName(rel),
19159 : RelationGetRelationName(foreignrel)),
19160 : errtableconstraint(rel, NameStr(con->conname))));
19161 : }
19162 : else
19163 : {
19164 8 : if (RelationIsPermanent(foreignrel))
19165 4 : ereport(ERROR,
19166 : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
19167 : errmsg("could not change table \"%s\" to unlogged because it references logged table \"%s\"",
19168 : RelationGetRelationName(rel),
19169 : RelationGetRelationName(foreignrel)),
19170 : errtableconstraint(rel, NameStr(con->conname))));
19171 : }
19172 :
19173 4 : relation_close(foreignrel, AccessShareLock);
19174 : }
19175 : }
19176 :
19177 51 : systable_endscan(scan);
19178 :
19179 51 : table_close(pg_constraint, AccessShareLock);
19180 :
19181 : /* force rewrite if necessary; see comment in ATRewriteTables */
19182 51 : tab->rewrite |= AT_REWRITE_ALTER_PERSISTENCE;
19183 51 : if (toLogged)
19184 22 : tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
19185 : else
19186 29 : tab->newrelpersistence = RELPERSISTENCE_UNLOGGED;
19187 51 : tab->chgPersistence = true;
19188 : }
19189 :
19190 : /*
19191 : * Execute ALTER TABLE SET SCHEMA
19192 : */
19193 : ObjectAddress
19194 88 : AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
19195 : {
19196 : Relation rel;
19197 : Oid relid;
19198 : Oid oldNspOid;
19199 : Oid nspOid;
19200 : RangeVar *newrv;
19201 : ObjectAddresses *objsMoved;
19202 : ObjectAddress myself;
19203 :
19204 88 : relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
19205 88 : stmt->missing_ok ? RVR_MISSING_OK : 0,
19206 : RangeVarCallbackForAlterRelation,
19207 : stmt);
19208 :
19209 83 : if (!OidIsValid(relid))
19210 : {
19211 12 : ereport(NOTICE,
19212 : (errmsg("relation \"%s\" does not exist, skipping",
19213 : stmt->relation->relname)));
19214 12 : return InvalidObjectAddress;
19215 : }
19216 :
19217 71 : rel = relation_open(relid, NoLock);
19218 :
19219 71 : oldNspOid = RelationGetNamespace(rel);
19220 :
19221 : /* If it's an owned sequence, disallow moving it by itself. */
19222 71 : if (rel->rd_rel->relkind == RELKIND_SEQUENCE)
19223 : {
19224 : Oid tableId;
19225 : int32 colId;
19226 :
19227 6 : if (sequenceIsOwned(relid, DEPENDENCY_AUTO, &tableId, &colId) ||
19228 1 : sequenceIsOwned(relid, DEPENDENCY_INTERNAL, &tableId, &colId))
19229 4 : ereport(ERROR,
19230 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
19231 : errmsg("cannot move an owned sequence into another schema"),
19232 : errdetail("Sequence \"%s\" is linked to table \"%s\".",
19233 : RelationGetRelationName(rel),
19234 : get_rel_name(tableId))));
19235 : }
19236 :
19237 : /* Get and lock schema OID and check its permissions. */
19238 67 : newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1);
19239 67 : nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL);
19240 :
19241 : /* common checks on switching namespaces */
19242 67 : CheckSetNamespace(oldNspOid, nspOid);
19243 :
19244 67 : objsMoved = new_object_addresses();
19245 67 : AlterTableNamespaceInternal(rel, oldNspOid, nspOid, objsMoved);
19246 63 : free_object_addresses(objsMoved);
19247 :
19248 63 : ObjectAddressSet(myself, RelationRelationId, relid);
19249 :
19250 63 : if (oldschema)
19251 63 : *oldschema = oldNspOid;
19252 :
19253 : /* close rel, but keep lock until commit */
19254 63 : relation_close(rel, NoLock);
19255 :
19256 63 : return myself;
19257 : }
19258 :
19259 : /*
19260 : * The guts of relocating a table or materialized view to another namespace:
19261 : * besides moving the relation itself, its dependent objects are relocated to
19262 : * the new schema.
19263 : */
19264 : void
19265 68 : AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
19266 : ObjectAddresses *objsMoved)
19267 : {
19268 : Relation classRel;
19269 :
19270 : Assert(objsMoved != NULL);
19271 :
19272 : /* OK, modify the pg_class row and pg_depend entry */
19273 68 : classRel = table_open(RelationRelationId, RowExclusiveLock);
19274 :
19275 68 : AlterRelationNamespaceInternal(classRel, RelationGetRelid(rel), oldNspOid,
19276 : nspOid, true, objsMoved);
19277 :
19278 : /* Fix the table's row type too, if it has one */
19279 64 : if (OidIsValid(rel->rd_rel->reltype))
19280 55 : AlterTypeNamespaceInternal(rel->rd_rel->reltype, nspOid,
19281 : false, /* isImplicitArray */
19282 : false, /* ignoreDependent */
19283 : false, /* errorOnTableType */
19284 : objsMoved);
19285 :
19286 : /* Fix other dependent stuff */
19287 64 : AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
19288 64 : AlterSeqNamespaces(classRel, rel, oldNspOid, nspOid,
19289 : objsMoved, AccessExclusiveLock);
19290 64 : AlterConstraintNamespaces(RelationGetRelid(rel), oldNspOid, nspOid,
19291 : false, objsMoved);
19292 :
19293 64 : table_close(classRel, RowExclusiveLock);
19294 64 : }
19295 :
19296 : /*
19297 : * The guts of relocating a relation to another namespace: fix the pg_class
19298 : * entry, and the pg_depend entry if any. Caller must already have
19299 : * opened and write-locked pg_class.
19300 : */
19301 : void
19302 135 : AlterRelationNamespaceInternal(Relation classRel, Oid relOid,
19303 : Oid oldNspOid, Oid newNspOid,
19304 : bool hasDependEntry,
19305 : ObjectAddresses *objsMoved)
19306 : {
19307 : HeapTuple classTup;
19308 : Form_pg_class classForm;
19309 : ObjectAddress thisobj;
19310 135 : bool already_done = false;
19311 :
19312 : /* no rel lock for relkind=c so use LOCKTAG_TUPLE */
19313 135 : classTup = SearchSysCacheLockedCopy1(RELOID, ObjectIdGetDatum(relOid));
19314 135 : if (!HeapTupleIsValid(classTup))
19315 0 : elog(ERROR, "cache lookup failed for relation %u", relOid);
19316 135 : classForm = (Form_pg_class) GETSTRUCT(classTup);
19317 :
19318 : Assert(classForm->relnamespace == oldNspOid);
19319 :
19320 135 : thisobj.classId = RelationRelationId;
19321 135 : thisobj.objectId = relOid;
19322 135 : thisobj.objectSubId = 0;
19323 :
19324 : /*
19325 : * If the object has already been moved, don't move it again. If it's
19326 : * already in the right place, don't move it, but still fire the object
19327 : * access hook.
19328 : */
19329 135 : already_done = object_address_present(&thisobj, objsMoved);
19330 135 : if (!already_done && oldNspOid != newNspOid)
19331 103 : {
19332 107 : ItemPointerData otid = classTup->t_self;
19333 :
19334 : /* check for duplicate name (more friendly than unique-index failure) */
19335 107 : if (get_relname_relid(NameStr(classForm->relname),
19336 : newNspOid) != InvalidOid)
19337 4 : ereport(ERROR,
19338 : (errcode(ERRCODE_DUPLICATE_TABLE),
19339 : errmsg("relation \"%s\" already exists in schema \"%s\"",
19340 : NameStr(classForm->relname),
19341 : get_namespace_name(newNspOid))));
19342 :
19343 : /* classTup is a copy, so OK to scribble on */
19344 103 : classForm->relnamespace = newNspOid;
19345 :
19346 103 : CatalogTupleUpdate(classRel, &otid, classTup);
19347 103 : UnlockTuple(classRel, &otid, InplaceUpdateTupleLock);
19348 :
19349 :
19350 : /* Update dependency on schema if caller said so */
19351 179 : if (hasDependEntry &&
19352 76 : changeDependencyFor(RelationRelationId,
19353 : relOid,
19354 : NamespaceRelationId,
19355 : oldNspOid,
19356 : newNspOid) != 1)
19357 0 : elog(ERROR, "could not change schema dependency for relation \"%s\"",
19358 : NameStr(classForm->relname));
19359 : }
19360 : else
19361 28 : UnlockTuple(classRel, &classTup->t_self, InplaceUpdateTupleLock);
19362 131 : if (!already_done)
19363 : {
19364 131 : add_exact_object_address(&thisobj, objsMoved);
19365 :
19366 131 : InvokeObjectPostAlterHook(RelationRelationId, relOid, 0);
19367 : }
19368 :
19369 131 : heap_freetuple(classTup);
19370 131 : }
19371 :
19372 : /*
19373 : * Move all indexes for the specified relation to another namespace.
19374 : *
19375 : * Note: we assume adequate permission checking was done by the caller,
19376 : * and that the caller has a suitable lock on the owning relation.
19377 : */
19378 : static void
19379 64 : AlterIndexNamespaces(Relation classRel, Relation rel,
19380 : Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved)
19381 : {
19382 : List *indexList;
19383 : ListCell *l;
19384 :
19385 64 : indexList = RelationGetIndexList(rel);
19386 :
19387 94 : foreach(l, indexList)
19388 : {
19389 30 : Oid indexOid = lfirst_oid(l);
19390 : ObjectAddress thisobj;
19391 :
19392 30 : thisobj.classId = RelationRelationId;
19393 30 : thisobj.objectId = indexOid;
19394 30 : thisobj.objectSubId = 0;
19395 :
19396 : /*
19397 : * Note: currently, the index will not have its own dependency on the
19398 : * namespace, so we don't need to do changeDependencyFor(). There's no
19399 : * row type in pg_type, either.
19400 : *
19401 : * XXX this objsMoved test may be pointless -- surely we have a single
19402 : * dependency link from a relation to each index?
19403 : */
19404 30 : if (!object_address_present(&thisobj, objsMoved))
19405 : {
19406 30 : AlterRelationNamespaceInternal(classRel, indexOid,
19407 : oldNspOid, newNspOid,
19408 : false, objsMoved);
19409 30 : add_exact_object_address(&thisobj, objsMoved);
19410 : }
19411 : }
19412 :
19413 64 : list_free(indexList);
19414 64 : }
19415 :
19416 : /*
19417 : * Move all identity and SERIAL-column sequences of the specified relation to another
19418 : * namespace.
19419 : *
19420 : * Note: we assume adequate permission checking was done by the caller,
19421 : * and that the caller has a suitable lock on the owning relation.
19422 : */
19423 : static void
19424 64 : AlterSeqNamespaces(Relation classRel, Relation rel,
19425 : Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved,
19426 : LOCKMODE lockmode)
19427 : {
19428 : Relation depRel;
19429 : SysScanDesc scan;
19430 : ScanKeyData key[2];
19431 : HeapTuple tup;
19432 :
19433 : /*
19434 : * SERIAL sequences are those having an auto dependency on one of the
19435 : * table's columns (we don't care *which* column, exactly).
19436 : */
19437 64 : depRel = table_open(DependRelationId, AccessShareLock);
19438 :
19439 64 : ScanKeyInit(&key[0],
19440 : Anum_pg_depend_refclassid,
19441 : BTEqualStrategyNumber, F_OIDEQ,
19442 : ObjectIdGetDatum(RelationRelationId));
19443 64 : ScanKeyInit(&key[1],
19444 : Anum_pg_depend_refobjid,
19445 : BTEqualStrategyNumber, F_OIDEQ,
19446 : ObjectIdGetDatum(RelationGetRelid(rel)));
19447 : /* we leave refobjsubid unspecified */
19448 :
19449 64 : scan = systable_beginscan(depRel, DependReferenceIndexId, true,
19450 : NULL, 2, key);
19451 :
19452 417 : while (HeapTupleIsValid(tup = systable_getnext(scan)))
19453 : {
19454 353 : Form_pg_depend depForm = (Form_pg_depend) GETSTRUCT(tup);
19455 : Relation seqRel;
19456 :
19457 : /* skip dependencies other than auto dependencies on columns */
19458 353 : if (depForm->refobjsubid == 0 ||
19459 252 : depForm->classid != RelationRelationId ||
19460 28 : depForm->objsubid != 0 ||
19461 28 : !(depForm->deptype == DEPENDENCY_AUTO || depForm->deptype == DEPENDENCY_INTERNAL))
19462 325 : continue;
19463 :
19464 : /* Use relation_open just in case it's an index */
19465 28 : seqRel = relation_open(depForm->objid, lockmode);
19466 :
19467 : /* skip non-sequence relations */
19468 28 : if (RelationGetForm(seqRel)->relkind != RELKIND_SEQUENCE)
19469 : {
19470 : /* No need to keep the lock */
19471 0 : relation_close(seqRel, lockmode);
19472 0 : continue;
19473 : }
19474 :
19475 : /* Fix the pg_class and pg_depend entries */
19476 28 : AlterRelationNamespaceInternal(classRel, depForm->objid,
19477 : oldNspOid, newNspOid,
19478 : true, objsMoved);
19479 :
19480 : /*
19481 : * Sequences used to have entries in pg_type, but no longer do. If we
19482 : * ever re-instate that, we'll need to move the pg_type entry to the
19483 : * new namespace, too (using AlterTypeNamespaceInternal).
19484 : */
19485 : Assert(RelationGetForm(seqRel)->reltype == InvalidOid);
19486 :
19487 : /* Now we can close it. Keep the lock till end of transaction. */
19488 28 : relation_close(seqRel, NoLock);
19489 : }
19490 :
19491 64 : systable_endscan(scan);
19492 :
19493 64 : relation_close(depRel, AccessShareLock);
19494 64 : }
19495 :
19496 :
19497 : /*
19498 : * This code supports
19499 : * CREATE TEMP TABLE ... ON COMMIT { DROP | PRESERVE ROWS | DELETE ROWS }
19500 : *
19501 : * Because we only support this for TEMP tables, it's sufficient to remember
19502 : * the state in a backend-local data structure.
19503 : */
19504 :
19505 : /*
19506 : * Register a newly-created relation's ON COMMIT action.
19507 : */
19508 : void
19509 120 : register_on_commit_action(Oid relid, OnCommitAction action)
19510 : {
19511 : OnCommitItem *oc;
19512 : MemoryContext oldcxt;
19513 :
19514 : /*
19515 : * We needn't bother registering the relation unless there is an ON COMMIT
19516 : * action we need to take.
19517 : */
19518 120 : if (action == ONCOMMIT_NOOP || action == ONCOMMIT_PRESERVE_ROWS)
19519 16 : return;
19520 :
19521 104 : oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
19522 :
19523 104 : oc = palloc_object(OnCommitItem);
19524 104 : oc->relid = relid;
19525 104 : oc->oncommit = action;
19526 104 : oc->creating_subid = GetCurrentSubTransactionId();
19527 104 : oc->deleting_subid = InvalidSubTransactionId;
19528 :
19529 : /*
19530 : * We use lcons() here so that ON COMMIT actions are processed in reverse
19531 : * order of registration. That might not be essential but it seems
19532 : * reasonable.
19533 : */
19534 104 : on_commits = lcons(oc, on_commits);
19535 :
19536 104 : MemoryContextSwitchTo(oldcxt);
19537 : }
19538 :
19539 : /*
19540 : * Unregister any ON COMMIT action when a relation is deleted.
19541 : *
19542 : * Actually, we only mark the OnCommitItem entry as to be deleted after commit.
19543 : */
19544 : void
19545 33889 : remove_on_commit_action(Oid relid)
19546 : {
19547 : ListCell *l;
19548 :
19549 34002 : foreach(l, on_commits)
19550 : {
19551 205 : OnCommitItem *oc = (OnCommitItem *) lfirst(l);
19552 :
19553 205 : if (oc->relid == relid)
19554 : {
19555 92 : oc->deleting_subid = GetCurrentSubTransactionId();
19556 92 : break;
19557 : }
19558 : }
19559 33889 : }
19560 :
19561 : /*
19562 : * Perform ON COMMIT actions.
19563 : *
19564 : * This is invoked just before actually committing, since it's possible
19565 : * to encounter errors.
19566 : */
19567 : void
19568 609525 : PreCommit_on_commit_actions(void)
19569 : {
19570 : ListCell *l;
19571 609525 : List *oids_to_truncate = NIL;
19572 609525 : List *oids_to_drop = NIL;
19573 :
19574 610074 : foreach(l, on_commits)
19575 : {
19576 549 : OnCommitItem *oc = (OnCommitItem *) lfirst(l);
19577 :
19578 : /* Ignore entry if already dropped in this xact */
19579 549 : if (oc->deleting_subid != InvalidSubTransactionId)
19580 49 : continue;
19581 :
19582 500 : switch (oc->oncommit)
19583 : {
19584 0 : case ONCOMMIT_NOOP:
19585 : case ONCOMMIT_PRESERVE_ROWS:
19586 : /* Do nothing (there shouldn't be such entries, actually) */
19587 0 : break;
19588 465 : case ONCOMMIT_DELETE_ROWS:
19589 :
19590 : /*
19591 : * If this transaction hasn't accessed any temporary
19592 : * relations, we can skip truncating ON COMMIT DELETE ROWS
19593 : * tables, as they must still be empty.
19594 : */
19595 465 : if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
19596 298 : oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
19597 465 : break;
19598 35 : case ONCOMMIT_DROP:
19599 35 : oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
19600 35 : break;
19601 : }
19602 : }
19603 :
19604 : /*
19605 : * Truncate relations before dropping so that all dependencies between
19606 : * relations are removed after they are worked on. Doing it like this
19607 : * might be a waste as it is possible that a relation being truncated will
19608 : * be dropped anyway due to its parent being dropped, but this makes the
19609 : * code more robust because of not having to re-check that the relation
19610 : * exists at truncation time.
19611 : */
19612 609525 : if (oids_to_truncate != NIL)
19613 254 : heap_truncate(oids_to_truncate);
19614 :
19615 609521 : if (oids_to_drop != NIL)
19616 : {
19617 31 : ObjectAddresses *targetObjects = new_object_addresses();
19618 :
19619 66 : foreach(l, oids_to_drop)
19620 : {
19621 : ObjectAddress object;
19622 :
19623 35 : object.classId = RelationRelationId;
19624 35 : object.objectId = lfirst_oid(l);
19625 35 : object.objectSubId = 0;
19626 :
19627 : Assert(!object_address_present(&object, targetObjects));
19628 :
19629 35 : add_exact_object_address(&object, targetObjects);
19630 : }
19631 :
19632 : /*
19633 : * Object deletion might involve toast table access (to clean up
19634 : * toasted catalog entries), so ensure we have a valid snapshot.
19635 : */
19636 31 : PushActiveSnapshot(GetTransactionSnapshot());
19637 :
19638 : /*
19639 : * Since this is an automatic drop, rather than one directly initiated
19640 : * by the user, we pass the PERFORM_DELETION_INTERNAL flag.
19641 : */
19642 31 : performMultipleDeletions(targetObjects, DROP_CASCADE,
19643 : PERFORM_DELETION_INTERNAL | PERFORM_DELETION_QUIETLY);
19644 :
19645 31 : PopActiveSnapshot();
19646 :
19647 : #ifdef USE_ASSERT_CHECKING
19648 :
19649 : /*
19650 : * Note that table deletion will call remove_on_commit_action, so the
19651 : * entry should get marked as deleted.
19652 : */
19653 : foreach(l, on_commits)
19654 : {
19655 : OnCommitItem *oc = (OnCommitItem *) lfirst(l);
19656 :
19657 : if (oc->oncommit != ONCOMMIT_DROP)
19658 : continue;
19659 :
19660 : Assert(oc->deleting_subid != InvalidSubTransactionId);
19661 : }
19662 : #endif
19663 : }
19664 609521 : }
19665 :
19666 : /*
19667 : * Post-commit or post-abort cleanup for ON COMMIT management.
19668 : *
19669 : * All we do here is remove no-longer-needed OnCommitItem entries.
19670 : *
19671 : * During commit, remove entries that were deleted during this transaction;
19672 : * during abort, remove those created during this transaction.
19673 : */
19674 : void
19675 644804 : AtEOXact_on_commit_actions(bool isCommit)
19676 : {
19677 : ListCell *cur_item;
19678 :
19679 645377 : foreach(cur_item, on_commits)
19680 : {
19681 573 : OnCommitItem *oc = (OnCommitItem *) lfirst(cur_item);
19682 :
19683 645 : if (isCommit ? oc->deleting_subid != InvalidSubTransactionId :
19684 72 : oc->creating_subid != InvalidSubTransactionId)
19685 : {
19686 : /* cur_item must be removed */
19687 104 : on_commits = foreach_delete_current(on_commits, cur_item);
19688 104 : pfree(oc);
19689 : }
19690 : else
19691 : {
19692 : /* cur_item must be preserved */
19693 469 : oc->creating_subid = InvalidSubTransactionId;
19694 469 : oc->deleting_subid = InvalidSubTransactionId;
19695 : }
19696 : }
19697 644804 : }
19698 :
19699 : /*
19700 : * Post-subcommit or post-subabort cleanup for ON COMMIT management.
19701 : *
19702 : * During subabort, we can immediately remove entries created during this
19703 : * subtransaction. During subcommit, just relabel entries marked during
19704 : * this subtransaction as being the parent's responsibility.
19705 : */
19706 : void
19707 11070 : AtEOSubXact_on_commit_actions(bool isCommit, SubTransactionId mySubid,
19708 : SubTransactionId parentSubid)
19709 : {
19710 : ListCell *cur_item;
19711 :
19712 11070 : foreach(cur_item, on_commits)
19713 : {
19714 0 : OnCommitItem *oc = (OnCommitItem *) lfirst(cur_item);
19715 :
19716 0 : if (!isCommit && oc->creating_subid == mySubid)
19717 : {
19718 : /* cur_item must be removed */
19719 0 : on_commits = foreach_delete_current(on_commits, cur_item);
19720 0 : pfree(oc);
19721 : }
19722 : else
19723 : {
19724 : /* cur_item must be preserved */
19725 0 : if (oc->creating_subid == mySubid)
19726 0 : oc->creating_subid = parentSubid;
19727 0 : if (oc->deleting_subid == mySubid)
19728 0 : oc->deleting_subid = isCommit ? parentSubid : InvalidSubTransactionId;
19729 : }
19730 : }
19731 11070 : }
19732 :
19733 : /*
19734 : * This is intended as a callback for RangeVarGetRelidExtended(). It allows
19735 : * the relation to be locked only if (1) it's a plain or partitioned table,
19736 : * materialized view, or TOAST table and (2) the current user is the owner (or
19737 : * the superuser) or has been granted MAINTAIN. This meets the
19738 : * permission-checking needs of CLUSTER, REINDEX TABLE, and REFRESH
19739 : * MATERIALIZED VIEW; we expose it here so that it can be used by all.
19740 : */
19741 : void
19742 725 : RangeVarCallbackMaintainsTable(const RangeVar *relation,
19743 : Oid relId, Oid oldRelId, void *arg)
19744 : {
19745 : char relkind;
19746 : AclResult aclresult;
19747 :
19748 : /* Nothing to do if the relation was not found. */
19749 725 : if (!OidIsValid(relId))
19750 4 : return;
19751 :
19752 : /*
19753 : * If the relation does exist, check whether it's an index. But note that
19754 : * the relation might have been dropped between the time we did the name
19755 : * lookup and now. In that case, there's nothing to do.
19756 : */
19757 721 : relkind = get_rel_relkind(relId);
19758 721 : if (!relkind)
19759 0 : return;
19760 721 : if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
19761 102 : relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
19762 18 : ereport(ERROR,
19763 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19764 : errmsg("\"%s\" is not a table or materialized view", relation->relname)));
19765 :
19766 : /* Check permissions */
19767 703 : aclresult = pg_class_aclcheck(relId, GetUserId(), ACL_MAINTAIN);
19768 703 : if (aclresult != ACLCHECK_OK)
19769 20 : aclcheck_error(aclresult,
19770 20 : get_relkind_objtype(get_rel_relkind(relId)),
19771 20 : relation->relname);
19772 : }
19773 :
19774 : /*
19775 : * Callback to RangeVarGetRelidExtended() for TRUNCATE processing.
19776 : */
19777 : static void
19778 1451 : RangeVarCallbackForTruncate(const RangeVar *relation,
19779 : Oid relId, Oid oldRelId, void *arg)
19780 : {
19781 : HeapTuple tuple;
19782 :
19783 : /* Nothing to do if the relation was not found. */
19784 1451 : if (!OidIsValid(relId))
19785 0 : return;
19786 :
19787 1451 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relId));
19788 1451 : if (!HeapTupleIsValid(tuple)) /* should not happen */
19789 0 : elog(ERROR, "cache lookup failed for relation %u", relId);
19790 :
19791 1451 : truncate_check_rel(relId, (Form_pg_class) GETSTRUCT(tuple));
19792 1448 : truncate_check_perms(relId, (Form_pg_class) GETSTRUCT(tuple));
19793 :
19794 1428 : ReleaseSysCache(tuple);
19795 : }
19796 :
19797 : /*
19798 : * Callback for RangeVarGetRelidExtended(). Checks that the current user is
19799 : * the owner of the relation, or superuser.
19800 : */
19801 : void
19802 12112 : RangeVarCallbackOwnsRelation(const RangeVar *relation,
19803 : Oid relId, Oid oldRelId, void *arg)
19804 : {
19805 : HeapTuple tuple;
19806 :
19807 : /* Nothing to do if the relation was not found. */
19808 12112 : if (!OidIsValid(relId))
19809 19 : return;
19810 :
19811 12093 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relId));
19812 12093 : if (!HeapTupleIsValid(tuple)) /* should not happen */
19813 0 : elog(ERROR, "cache lookup failed for relation %u", relId);
19814 :
19815 12093 : if (!object_ownercheck(RelationRelationId, relId, GetUserId()))
19816 16 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relId)),
19817 16 : relation->relname);
19818 :
19819 24094 : if (!allowSystemTableMods &&
19820 12017 : IsSystemClass(relId, (Form_pg_class) GETSTRUCT(tuple)))
19821 1 : ereport(ERROR,
19822 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
19823 : errmsg("permission denied: \"%s\" is a system catalog",
19824 : relation->relname)));
19825 :
19826 12076 : ReleaseSysCache(tuple);
19827 : }
19828 :
19829 : /*
19830 : * Common RangeVarGetRelid callback for rename, set schema, and alter table
19831 : * processing.
19832 : */
19833 : static void
19834 22946 : RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
19835 : void *arg)
19836 : {
19837 22946 : Node *stmt = (Node *) arg;
19838 : ObjectType reltype;
19839 : HeapTuple tuple;
19840 : Form_pg_class classform;
19841 : AclResult aclresult;
19842 : char relkind;
19843 :
19844 22946 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
19845 22946 : if (!HeapTupleIsValid(tuple))
19846 166 : return; /* concurrently dropped */
19847 22780 : classform = (Form_pg_class) GETSTRUCT(tuple);
19848 22780 : relkind = classform->relkind;
19849 :
19850 : /* Must own relation. */
19851 22780 : if (!object_ownercheck(RelationRelationId, relid, GetUserId()))
19852 56 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
19853 :
19854 : /* No system table modifications unless explicitly allowed. */
19855 22724 : if (!allowSystemTableMods && IsSystemClass(relid, classform))
19856 18 : ereport(ERROR,
19857 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
19858 : errmsg("permission denied: \"%s\" is a system catalog",
19859 : rv->relname)));
19860 :
19861 : /*
19862 : * Extract the specified relation type from the statement parse tree.
19863 : *
19864 : * Also, for ALTER .. RENAME, check permissions: the user must (still)
19865 : * have CREATE rights on the containing namespace.
19866 : */
19867 22706 : if (IsA(stmt, RenameStmt))
19868 : {
19869 316 : aclresult = object_aclcheck(NamespaceRelationId, classform->relnamespace,
19870 : GetUserId(), ACL_CREATE);
19871 316 : if (aclresult != ACLCHECK_OK)
19872 0 : aclcheck_error(aclresult, OBJECT_SCHEMA,
19873 0 : get_namespace_name(classform->relnamespace));
19874 316 : reltype = ((RenameStmt *) stmt)->renameType;
19875 : }
19876 22390 : else if (IsA(stmt, AlterObjectSchemaStmt))
19877 72 : reltype = ((AlterObjectSchemaStmt *) stmt)->objectType;
19878 :
19879 22318 : else if (IsA(stmt, AlterTableStmt))
19880 22318 : reltype = ((AlterTableStmt *) stmt)->objtype;
19881 : else
19882 : {
19883 0 : elog(ERROR, "unrecognized node type: %d", (int) nodeTag(stmt));
19884 : reltype = OBJECT_TABLE; /* placate compiler */
19885 : }
19886 :
19887 : /*
19888 : * For compatibility with prior releases, we allow ALTER TABLE to be used
19889 : * with most other types of relations (but not composite types). We allow
19890 : * similar flexibility for ALTER INDEX in the case of RENAME, but not
19891 : * otherwise. Otherwise, the user must select the correct form of the
19892 : * command for the relation at issue.
19893 : */
19894 22706 : if (reltype == OBJECT_SEQUENCE && relkind != RELKIND_SEQUENCE)
19895 0 : ereport(ERROR,
19896 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19897 : errmsg("\"%s\" is not a sequence", rv->relname)));
19898 :
19899 22706 : if (reltype == OBJECT_VIEW && relkind != RELKIND_VIEW)
19900 0 : ereport(ERROR,
19901 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19902 : errmsg("\"%s\" is not a view", rv->relname)));
19903 :
19904 22706 : if (reltype == OBJECT_MATVIEW && relkind != RELKIND_MATVIEW)
19905 0 : ereport(ERROR,
19906 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19907 : errmsg("\"%s\" is not a materialized view", rv->relname)));
19908 :
19909 22706 : if (reltype == OBJECT_FOREIGN_TABLE && relkind != RELKIND_FOREIGN_TABLE)
19910 0 : ereport(ERROR,
19911 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19912 : errmsg("\"%s\" is not a foreign table", rv->relname)));
19913 :
19914 22706 : if (reltype == OBJECT_TYPE && relkind != RELKIND_COMPOSITE_TYPE)
19915 0 : ereport(ERROR,
19916 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19917 : errmsg("\"%s\" is not a composite type", rv->relname)));
19918 :
19919 22706 : if (reltype == OBJECT_PROPGRAPH && relkind != RELKIND_PROPGRAPH)
19920 0 : ereport(ERROR,
19921 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19922 : errmsg("\"%s\" is not a property graph", rv->relname)));
19923 :
19924 22706 : if (reltype == OBJECT_INDEX && relkind != RELKIND_INDEX &&
19925 : relkind != RELKIND_PARTITIONED_INDEX
19926 24 : && !IsA(stmt, RenameStmt))
19927 4 : ereport(ERROR,
19928 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19929 : errmsg("\"%s\" is not an index", rv->relname)));
19930 :
19931 : /*
19932 : * Don't allow ALTER TABLE on composite types. We want people to use ALTER
19933 : * TYPE for that.
19934 : */
19935 22702 : if (reltype != OBJECT_TYPE && relkind == RELKIND_COMPOSITE_TYPE)
19936 0 : ereport(ERROR,
19937 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19938 : errmsg("\"%s\" is a composite type", rv->relname),
19939 : /* translator: %s is an SQL ALTER command */
19940 : errhint("Use %s instead.",
19941 : "ALTER TYPE")));
19942 :
19943 : /*
19944 : * Don't allow ALTER TABLE .. SET SCHEMA on relations that can't be moved
19945 : * to a different schema, such as indexes and TOAST tables.
19946 : */
19947 22702 : if (IsA(stmt, AlterObjectSchemaStmt))
19948 : {
19949 72 : if (relkind == RELKIND_INDEX || relkind == RELKIND_PARTITIONED_INDEX)
19950 0 : ereport(ERROR,
19951 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19952 : errmsg("cannot change schema of index \"%s\"",
19953 : rv->relname),
19954 : errhint("Change the schema of the table instead.")));
19955 72 : else if (relkind == RELKIND_COMPOSITE_TYPE)
19956 0 : ereport(ERROR,
19957 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19958 : errmsg("cannot change schema of composite type \"%s\"",
19959 : rv->relname),
19960 : /* translator: %s is an SQL ALTER command */
19961 : errhint("Use %s instead.",
19962 : "ALTER TYPE")));
19963 72 : else if (relkind == RELKIND_TOASTVALUE)
19964 0 : ereport(ERROR,
19965 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19966 : errmsg("cannot change schema of TOAST table \"%s\"",
19967 : rv->relname),
19968 : errhint("Change the schema of the table instead.")));
19969 : }
19970 :
19971 22702 : ReleaseSysCache(tuple);
19972 : }
19973 :
19974 : /*
19975 : * Transform any expressions present in the partition key
19976 : *
19977 : * Returns a transformed PartitionSpec.
19978 : */
19979 : static PartitionSpec *
19980 3639 : transformPartitionSpec(Relation rel, PartitionSpec *partspec)
19981 : {
19982 : PartitionSpec *newspec;
19983 : ParseState *pstate;
19984 : ParseNamespaceItem *nsitem;
19985 : ListCell *l;
19986 :
19987 3639 : newspec = makeNode(PartitionSpec);
19988 :
19989 3639 : newspec->strategy = partspec->strategy;
19990 3639 : newspec->partParams = NIL;
19991 3639 : newspec->location = partspec->location;
19992 :
19993 : /* Check valid number of columns for strategy */
19994 5263 : if (partspec->strategy == PARTITION_STRATEGY_LIST &&
19995 1624 : list_length(partspec->partParams) != 1)
19996 4 : ereport(ERROR,
19997 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
19998 : errmsg("cannot use \"list\" partition strategy with more than one column")));
19999 :
20000 : /*
20001 : * Create a dummy ParseState and insert the target relation as its sole
20002 : * rangetable entry. We need a ParseState for transformExpr.
20003 : */
20004 3635 : pstate = make_parsestate(NULL);
20005 3635 : nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock,
20006 : NULL, false, true);
20007 3635 : addNSItemToQuery(pstate, nsitem, true, true, true);
20008 :
20009 : /* take care of any partition expressions */
20010 7572 : foreach(l, partspec->partParams)
20011 : {
20012 3953 : PartitionElem *pelem = lfirst_node(PartitionElem, l);
20013 :
20014 3953 : if (pelem->expr)
20015 : {
20016 : /* Copy, to avoid scribbling on the input */
20017 232 : pelem = copyObject(pelem);
20018 :
20019 : /* Now do parse transformation of the expression */
20020 232 : pelem->expr = transformExpr(pstate, pelem->expr,
20021 : EXPR_KIND_PARTITION_EXPRESSION);
20022 :
20023 : /* we have to fix its collations too */
20024 216 : assign_expr_collations(pstate, pelem->expr);
20025 : }
20026 :
20027 3937 : newspec->partParams = lappend(newspec->partParams, pelem);
20028 : }
20029 :
20030 3619 : return newspec;
20031 : }
20032 :
20033 : /*
20034 : * Compute per-partition-column information from a list of PartitionElems.
20035 : * Expressions in the PartitionElems must be parse-analyzed already.
20036 : */
20037 : static void
20038 3619 : ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNumber *partattrs,
20039 : List **partexprs, Oid *partopclass, Oid *partcollation,
20040 : PartitionStrategy strategy)
20041 : {
20042 : int attn;
20043 : ListCell *lc;
20044 : Oid am_oid;
20045 :
20046 3619 : attn = 0;
20047 7468 : foreach(lc, partParams)
20048 : {
20049 3937 : PartitionElem *pelem = lfirst_node(PartitionElem, lc);
20050 : Oid atttype;
20051 : Oid attcollation;
20052 :
20053 3937 : if (pelem->name != NULL)
20054 : {
20055 : /* Simple attribute reference */
20056 : HeapTuple atttuple;
20057 : Form_pg_attribute attform;
20058 :
20059 3721 : atttuple = SearchSysCacheAttName(RelationGetRelid(rel),
20060 3721 : pelem->name);
20061 3721 : if (!HeapTupleIsValid(atttuple))
20062 8 : ereport(ERROR,
20063 : (errcode(ERRCODE_UNDEFINED_COLUMN),
20064 : errmsg("column \"%s\" named in partition key does not exist",
20065 : pelem->name),
20066 : parser_errposition(pstate, pelem->location)));
20067 3713 : attform = (Form_pg_attribute) GETSTRUCT(atttuple);
20068 :
20069 3713 : if (attform->attnum <= 0)
20070 4 : ereport(ERROR,
20071 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
20072 : errmsg("cannot use system column \"%s\" in partition key",
20073 : pelem->name),
20074 : parser_errposition(pstate, pelem->location)));
20075 :
20076 : /*
20077 : * Stored generated columns cannot work: They are computed after
20078 : * BEFORE triggers, but partition routing is done before all
20079 : * triggers. Maybe virtual generated columns could be made to
20080 : * work, but then they would need to be handled as an expression
20081 : * below.
20082 : */
20083 3709 : if (attform->attgenerated)
20084 8 : ereport(ERROR,
20085 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
20086 : errmsg("cannot use generated column in partition key"),
20087 : errdetail("Column \"%s\" is a generated column.",
20088 : pelem->name),
20089 : parser_errposition(pstate, pelem->location)));
20090 :
20091 3701 : partattrs[attn] = attform->attnum;
20092 3701 : atttype = attform->atttypid;
20093 3701 : attcollation = attform->attcollation;
20094 3701 : ReleaseSysCache(atttuple);
20095 : }
20096 : else
20097 : {
20098 : /* Expression */
20099 216 : Node *expr = pelem->expr;
20100 : char partattname[16];
20101 216 : Bitmapset *expr_attrs = NULL;
20102 : int i;
20103 :
20104 : Assert(expr != NULL);
20105 216 : atttype = exprType(expr);
20106 216 : attcollation = exprCollation(expr);
20107 :
20108 : /*
20109 : * The expression must be of a storable type (e.g., not RECORD).
20110 : * The test is the same as for whether a table column is of a safe
20111 : * type (which is why we needn't check for the non-expression
20112 : * case).
20113 : */
20114 216 : snprintf(partattname, sizeof(partattname), "%d", attn + 1);
20115 216 : CheckAttributeType(partattname,
20116 : atttype, attcollation,
20117 : NIL, CHKATYPE_IS_PARTKEY);
20118 :
20119 : /*
20120 : * Strip any top-level COLLATE clause. This ensures that we treat
20121 : * "x COLLATE y" and "(x COLLATE y)" alike.
20122 : */
20123 208 : while (IsA(expr, CollateExpr))
20124 0 : expr = (Node *) ((CollateExpr *) expr)->arg;
20125 :
20126 : /*
20127 : * Examine all the columns in the partition key expression. When
20128 : * the whole-row reference is present, examine all the columns of
20129 : * the partitioned table.
20130 : */
20131 208 : pull_varattnos(expr, 1, &expr_attrs);
20132 208 : if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber, expr_attrs))
20133 : {
20134 40 : expr_attrs = bms_add_range(expr_attrs,
20135 : 1 - FirstLowInvalidHeapAttributeNumber,
20136 20 : RelationGetNumberOfAttributes(rel) - FirstLowInvalidHeapAttributeNumber);
20137 20 : expr_attrs = bms_del_member(expr_attrs, 0 - FirstLowInvalidHeapAttributeNumber);
20138 : }
20139 :
20140 208 : i = -1;
20141 457 : while ((i = bms_next_member(expr_attrs, i)) >= 0)
20142 : {
20143 281 : AttrNumber attno = i + FirstLowInvalidHeapAttributeNumber;
20144 :
20145 : Assert(attno != 0);
20146 :
20147 : /*
20148 : * Cannot allow system column references, since that would
20149 : * make partition routing impossible: their values won't be
20150 : * known yet when we need to do that.
20151 : */
20152 281 : if (attno < 0)
20153 0 : ereport(ERROR,
20154 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
20155 : errmsg("partition key expressions cannot contain system column references")));
20156 :
20157 : /*
20158 : * Stored generated columns cannot work: They are computed
20159 : * after BEFORE triggers, but partition routing is done before
20160 : * all triggers. Virtual generated columns could probably
20161 : * work, but it would require more work elsewhere (for example
20162 : * SET EXPRESSION would need to check whether the column is
20163 : * used in partition keys). Seems safer to prohibit for now.
20164 : */
20165 281 : if (TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated)
20166 32 : ereport(ERROR,
20167 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
20168 : errmsg("cannot use generated column in partition key"),
20169 : errdetail("Column \"%s\" is a generated column.",
20170 : get_attname(RelationGetRelid(rel), attno, false)),
20171 : parser_errposition(pstate, pelem->location)));
20172 : }
20173 :
20174 176 : if (IsA(expr, Var) &&
20175 8 : ((Var *) expr)->varattno > 0)
20176 : {
20177 :
20178 : /*
20179 : * User wrote "(column)" or "(column COLLATE something)".
20180 : * Treat it like simple attribute anyway.
20181 : */
20182 4 : partattrs[attn] = ((Var *) expr)->varattno;
20183 : }
20184 : else
20185 : {
20186 172 : partattrs[attn] = 0; /* marks the column as expression */
20187 172 : *partexprs = lappend(*partexprs, expr);
20188 :
20189 : /*
20190 : * transformPartitionSpec() should have already rejected
20191 : * subqueries, aggregates, window functions, and SRFs, based
20192 : * on the EXPR_KIND_ for partition expressions.
20193 : */
20194 :
20195 : /*
20196 : * Preprocess the expression before checking for mutability.
20197 : * This is essential for the reasons described in
20198 : * contain_mutable_functions_after_planning. However, we call
20199 : * expression_planner for ourselves rather than using that
20200 : * function, because if constant-folding reduces the
20201 : * expression to a constant, we'd like to know that so we can
20202 : * complain below.
20203 : *
20204 : * Like contain_mutable_functions_after_planning, assume that
20205 : * expression_planner won't scribble on its input, so this
20206 : * won't affect the partexprs entry we saved above.
20207 : */
20208 172 : expr = (Node *) expression_planner((Expr *) expr);
20209 :
20210 : /*
20211 : * Partition expressions cannot contain mutable functions,
20212 : * because a given row must always map to the same partition
20213 : * as long as there is no change in the partition boundary
20214 : * structure.
20215 : */
20216 172 : if (contain_mutable_functions(expr))
20217 4 : ereport(ERROR,
20218 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
20219 : errmsg("functions in partition key expression must be marked IMMUTABLE")));
20220 :
20221 : /*
20222 : * While it is not exactly *wrong* for a partition expression
20223 : * to be a constant, it seems better to reject such keys.
20224 : */
20225 168 : if (IsA(expr, Const))
20226 8 : ereport(ERROR,
20227 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
20228 : errmsg("cannot use constant expression as partition key")));
20229 : }
20230 : }
20231 :
20232 : /*
20233 : * Apply collation override if any
20234 : */
20235 3865 : if (pelem->collation)
20236 36 : attcollation = get_collation_oid(pelem->collation, false);
20237 :
20238 : /*
20239 : * Check we have a collation iff it's a collatable type. The only
20240 : * expected failures here are (1) COLLATE applied to a noncollatable
20241 : * type, or (2) partition expression had an unresolved collation. But
20242 : * we might as well code this to be a complete consistency check.
20243 : */
20244 3865 : if (type_is_collatable(atttype))
20245 : {
20246 444 : if (!OidIsValid(attcollation))
20247 0 : ereport(ERROR,
20248 : (errcode(ERRCODE_INDETERMINATE_COLLATION),
20249 : errmsg("could not determine which collation to use for partition expression"),
20250 : errhint("Use the COLLATE clause to set the collation explicitly.")));
20251 : }
20252 : else
20253 : {
20254 3421 : if (OidIsValid(attcollation))
20255 0 : ereport(ERROR,
20256 : (errcode(ERRCODE_DATATYPE_MISMATCH),
20257 : errmsg("collations are not supported by type %s",
20258 : format_type_be(atttype))));
20259 : }
20260 :
20261 3865 : partcollation[attn] = attcollation;
20262 :
20263 : /*
20264 : * Identify the appropriate operator class. For list and range
20265 : * partitioning, we use a btree operator class; hash partitioning uses
20266 : * a hash operator class.
20267 : */
20268 3865 : if (strategy == PARTITION_STRATEGY_HASH)
20269 215 : am_oid = HASH_AM_OID;
20270 : else
20271 3650 : am_oid = BTREE_AM_OID;
20272 :
20273 3865 : if (!pelem->opclass)
20274 : {
20275 3773 : partopclass[attn] = GetDefaultOpClass(atttype, am_oid);
20276 :
20277 3773 : if (!OidIsValid(partopclass[attn]))
20278 : {
20279 8 : if (strategy == PARTITION_STRATEGY_HASH)
20280 0 : ereport(ERROR,
20281 : (errcode(ERRCODE_UNDEFINED_OBJECT),
20282 : errmsg("data type %s has no default operator class for access method \"%s\"",
20283 : format_type_be(atttype), "hash"),
20284 : errhint("You must specify a hash operator class or define a default hash operator class for the data type.")));
20285 : else
20286 8 : ereport(ERROR,
20287 : (errcode(ERRCODE_UNDEFINED_OBJECT),
20288 : errmsg("data type %s has no default operator class for access method \"%s\"",
20289 : format_type_be(atttype), "btree"),
20290 : errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
20291 : }
20292 : }
20293 : else
20294 92 : partopclass[attn] = ResolveOpClass(pelem->opclass,
20295 : atttype,
20296 : am_oid == HASH_AM_OID ? "hash" : "btree",
20297 : am_oid);
20298 :
20299 3849 : attn++;
20300 : }
20301 3531 : }
20302 :
20303 : /*
20304 : * PartConstraintImpliedByRelConstraint
20305 : * Do scanrel's existing constraints imply the partition constraint?
20306 : *
20307 : * "Existing constraints" include its check constraints and column-level
20308 : * not-null constraints. partConstraint describes the partition constraint,
20309 : * in implicit-AND form.
20310 : */
20311 : bool
20312 2066 : PartConstraintImpliedByRelConstraint(Relation scanrel,
20313 : List *partConstraint)
20314 : {
20315 2066 : List *existConstraint = NIL;
20316 2066 : TupleConstr *constr = RelationGetDescr(scanrel)->constr;
20317 : int i;
20318 :
20319 2066 : if (constr && constr->has_not_null)
20320 : {
20321 544 : int natts = scanrel->rd_att->natts;
20322 :
20323 1886 : for (i = 1; i <= natts; i++)
20324 : {
20325 1342 : CompactAttribute *att = TupleDescCompactAttr(scanrel->rd_att, i - 1);
20326 :
20327 : /* invalid not-null constraint must be ignored here */
20328 1342 : if (att->attnullability == ATTNULLABLE_VALID && !att->attisdropped)
20329 : {
20330 747 : Form_pg_attribute wholeatt = TupleDescAttr(scanrel->rd_att, i - 1);
20331 747 : NullTest *ntest = makeNode(NullTest);
20332 :
20333 747 : ntest->arg = (Expr *) makeVar(1,
20334 : i,
20335 : wholeatt->atttypid,
20336 : wholeatt->atttypmod,
20337 : wholeatt->attcollation,
20338 : 0);
20339 747 : ntest->nulltesttype = IS_NOT_NULL;
20340 :
20341 : /*
20342 : * argisrow=false is correct even for a composite column,
20343 : * because attnotnull does not represent a SQL-spec IS NOT
20344 : * NULL test in such a case, just IS DISTINCT FROM NULL.
20345 : */
20346 747 : ntest->argisrow = false;
20347 747 : ntest->location = -1;
20348 747 : existConstraint = lappend(existConstraint, ntest);
20349 : }
20350 : }
20351 : }
20352 :
20353 2066 : return ConstraintImpliedByRelConstraint(scanrel, partConstraint, existConstraint);
20354 : }
20355 :
20356 : /*
20357 : * ConstraintImpliedByRelConstraint
20358 : * Do scanrel's existing constraints imply the given constraint?
20359 : *
20360 : * testConstraint is the constraint to validate. provenConstraint is a
20361 : * caller-provided list of conditions which this function may assume
20362 : * to be true. Both provenConstraint and testConstraint must be in
20363 : * implicit-AND form, must only contain immutable clauses, and must
20364 : * contain only Vars with varno = 1.
20365 : */
20366 : bool
20367 2882 : ConstraintImpliedByRelConstraint(Relation scanrel, List *testConstraint, List *provenConstraint)
20368 : {
20369 2882 : List *existConstraint = list_copy(provenConstraint);
20370 2882 : TupleConstr *constr = RelationGetDescr(scanrel)->constr;
20371 : int num_check,
20372 : i;
20373 :
20374 2882 : num_check = (constr != NULL) ? constr->num_check : 0;
20375 3218 : for (i = 0; i < num_check; i++)
20376 : {
20377 : Node *cexpr;
20378 :
20379 : /*
20380 : * If this constraint hasn't been fully validated yet, we must ignore
20381 : * it here.
20382 : */
20383 336 : if (!constr->check[i].ccvalid)
20384 12 : continue;
20385 :
20386 : /*
20387 : * NOT ENFORCED constraints are always marked as invalid, which should
20388 : * have been ignored.
20389 : */
20390 : Assert(constr->check[i].ccenforced);
20391 :
20392 324 : cexpr = stringToNode(constr->check[i].ccbin);
20393 :
20394 : /*
20395 : * Run each expression through const-simplification and
20396 : * canonicalization. It is necessary, because we will be comparing it
20397 : * to similarly-processed partition constraint expressions, and may
20398 : * fail to detect valid matches without this.
20399 : */
20400 324 : cexpr = eval_const_expressions(NULL, cexpr);
20401 324 : cexpr = (Node *) canonicalize_qual((Expr *) cexpr, true);
20402 :
20403 324 : existConstraint = list_concat(existConstraint,
20404 324 : make_ands_implicit((Expr *) cexpr));
20405 : }
20406 :
20407 : /*
20408 : * Try to make the proof. Since we are comparing CHECK constraints, we
20409 : * need to use weak implication, i.e., we assume existConstraint is
20410 : * not-false and try to prove the same for testConstraint.
20411 : *
20412 : * Note that predicate_implied_by assumes its first argument is known
20413 : * immutable. That should always be true for both NOT NULL and partition
20414 : * constraints, so we don't test it here.
20415 : */
20416 2882 : return predicate_implied_by(testConstraint, existConstraint, true);
20417 : }
20418 :
20419 : /*
20420 : * QueuePartitionConstraintValidation
20421 : *
20422 : * Add an entry to wqueue to have the given partition constraint validated by
20423 : * Phase 3, for the given relation, and all its children.
20424 : *
20425 : * We first verify whether the given constraint is implied by pre-existing
20426 : * relation constraints; if it is, there's no need to scan the table to
20427 : * validate, so don't queue in that case.
20428 : */
20429 : static void
20430 1737 : QueuePartitionConstraintValidation(List **wqueue, Relation scanrel,
20431 : List *partConstraint,
20432 : bool validate_default)
20433 : {
20434 : /*
20435 : * Based on the table's existing constraints, determine whether or not we
20436 : * may skip scanning the table.
20437 : */
20438 1737 : if (PartConstraintImpliedByRelConstraint(scanrel, partConstraint))
20439 : {
20440 55 : if (!validate_default)
20441 41 : ereport(DEBUG1,
20442 : (errmsg_internal("partition constraint for table \"%s\" is implied by existing constraints",
20443 : RelationGetRelationName(scanrel))));
20444 : else
20445 14 : ereport(DEBUG1,
20446 : (errmsg_internal("updated partition constraint for default partition \"%s\" is implied by existing constraints",
20447 : RelationGetRelationName(scanrel))));
20448 55 : return;
20449 : }
20450 :
20451 : /*
20452 : * Constraints proved insufficient. For plain relations, queue a
20453 : * validation item now; for partitioned tables, recurse to process each
20454 : * partition.
20455 : */
20456 1682 : if (scanrel->rd_rel->relkind == RELKIND_RELATION)
20457 : {
20458 : AlteredTableInfo *tab;
20459 :
20460 : /* Grab a work queue entry. */
20461 1407 : tab = ATGetQueueEntry(wqueue, scanrel);
20462 : Assert(tab->partition_constraint == NULL);
20463 1407 : tab->partition_constraint = (Expr *) linitial(partConstraint);
20464 1407 : tab->validate_default = validate_default;
20465 : }
20466 275 : else if (scanrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
20467 : {
20468 244 : PartitionDesc partdesc = RelationGetPartitionDesc(scanrel, true);
20469 : int i;
20470 :
20471 524 : for (i = 0; i < partdesc->nparts; i++)
20472 : {
20473 : Relation part_rel;
20474 : List *thisPartConstraint;
20475 :
20476 : /*
20477 : * This is the minimum lock we need to prevent deadlocks.
20478 : */
20479 280 : part_rel = table_open(partdesc->oids[i], AccessExclusiveLock);
20480 :
20481 : /*
20482 : * Adjust the constraint for scanrel so that it matches this
20483 : * partition's attribute numbers.
20484 : */
20485 : thisPartConstraint =
20486 280 : map_partition_varattnos(partConstraint, 1,
20487 : part_rel, scanrel);
20488 :
20489 280 : QueuePartitionConstraintValidation(wqueue, part_rel,
20490 : thisPartConstraint,
20491 : validate_default);
20492 280 : table_close(part_rel, NoLock); /* keep lock till commit */
20493 : }
20494 : }
20495 : }
20496 :
20497 : /*
20498 : * attachPartitionTable: attach a new partition to the partitioned table
20499 : *
20500 : * wqueue: the ALTER TABLE work queue; can be NULL when not running as part
20501 : * of an ALTER TABLE sequence.
20502 : * rel: partitioned relation;
20503 : * attachrel: relation of attached partition;
20504 : * bound: bounds of attached relation.
20505 : */
20506 : static void
20507 1980 : attachPartitionTable(List **wqueue, Relation rel, Relation attachrel, PartitionBoundSpec *bound)
20508 : {
20509 : /*
20510 : * Create an inheritance; the relevant checks are performed inside the
20511 : * function.
20512 : */
20513 1980 : CreateInheritance(attachrel, rel, true);
20514 :
20515 : /* Update the pg_class entry. */
20516 1908 : StorePartitionBound(attachrel, rel, bound);
20517 :
20518 : /* Ensure there exists a correct set of indexes in the partition. */
20519 1908 : AttachPartitionEnsureIndexes(wqueue, rel, attachrel);
20520 :
20521 : /* and triggers */
20522 1888 : CloneRowTriggersToPartition(rel, attachrel);
20523 :
20524 : /*
20525 : * Clone foreign key constraints. Callee is responsible for setting up
20526 : * for phase 3 constraint verification.
20527 : */
20528 1884 : CloneForeignKeyConstraints(wqueue, rel, attachrel);
20529 1872 : }
20530 :
20531 : /*
20532 : * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
20533 : *
20534 : * Return the address of the newly attached partition.
20535 : */
20536 : static ObjectAddress
20537 1608 : ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
20538 : AlterTableUtilityContext *context)
20539 : {
20540 : Relation attachrel,
20541 : catalog;
20542 : List *attachrel_children;
20543 : List *partConstraint;
20544 : SysScanDesc scan;
20545 : ScanKeyData skey;
20546 : AttrNumber attno;
20547 : int natts;
20548 : TupleDesc tupleDesc;
20549 : ObjectAddress address;
20550 : const char *trigger_name;
20551 : Oid defaultPartOid;
20552 : List *partBoundConstraint;
20553 1608 : List *exceptpuboids = NIL;
20554 1608 : ParseState *pstate = make_parsestate(NULL);
20555 :
20556 1608 : pstate->p_sourcetext = context->queryString;
20557 :
20558 : /*
20559 : * We must lock the default partition if one exists, because attaching a
20560 : * new partition will change its partition constraint.
20561 : */
20562 : defaultPartOid =
20563 1608 : get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
20564 1608 : if (OidIsValid(defaultPartOid))
20565 117 : LockRelationOid(defaultPartOid, AccessExclusiveLock);
20566 :
20567 1608 : attachrel = table_openrv(cmd->name, AccessExclusiveLock);
20568 :
20569 : /*
20570 : * XXX I think it'd be a good idea to grab locks on all tables referenced
20571 : * by FKs at this point also.
20572 : */
20573 :
20574 : /*
20575 : * Must be owner of both parent and source table -- parent was checked by
20576 : * ATSimplePermissions call in ATPrepCmd
20577 : */
20578 1604 : ATSimplePermissions(AT_AttachPartition, attachrel,
20579 : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
20580 :
20581 : /* A partition can only have one parent */
20582 1600 : if (attachrel->rd_rel->relispartition)
20583 4 : ereport(ERROR,
20584 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20585 : errmsg("\"%s\" is already a partition",
20586 : RelationGetRelationName(attachrel))));
20587 :
20588 1596 : if (OidIsValid(attachrel->rd_rel->reloftype))
20589 4 : ereport(ERROR,
20590 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20591 : errmsg("cannot attach a typed table as partition")));
20592 :
20593 : /*
20594 : * Disallow attaching a partition if the table is referenced in a
20595 : * publication EXCEPT clause. Changing the partition hierarchy could alter
20596 : * the effective publication membership.
20597 : */
20598 1592 : exceptpuboids = GetRelationExcludedPublications(RelationGetRelid(attachrel));
20599 1592 : if (exceptpuboids != NIL)
20600 : {
20601 4 : bool first = true;
20602 : StringInfoData pubnames;
20603 :
20604 4 : initStringInfo(&pubnames);
20605 :
20606 12 : foreach_oid(pubid, exceptpuboids)
20607 : {
20608 4 : char *pubname = get_publication_name(pubid, false);
20609 :
20610 4 : if (first)
20611 4 : appendStringInfo(&pubnames, _("\"%s\""), pubname);
20612 : else
20613 0 : appendStringInfo(&pubnames, _(", \"%s\""), pubname);
20614 4 : first = false;
20615 : }
20616 :
20617 4 : ereport(ERROR,
20618 : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
20619 : errmsg_plural("cannot attach table \"%s\" as partition because it is referenced in publication %s EXCEPT clause",
20620 : "cannot attach table \"%s\" as partition because it is referenced in publications %s EXCEPT clause",
20621 : list_length(exceptpuboids),
20622 : RelationGetRelationName(attachrel),
20623 : pubnames.data),
20624 : errdetail("The publication EXCEPT clause cannot contain tables that are partitions."),
20625 : errhint("Change the publication's EXCEPT clause using ALTER PUBLICATION ... SET ALL TABLES."));
20626 : }
20627 :
20628 1588 : list_free(exceptpuboids);
20629 :
20630 : /*
20631 : * Table being attached should not already be part of inheritance; either
20632 : * as a child table...
20633 : */
20634 1588 : catalog = table_open(InheritsRelationId, AccessShareLock);
20635 1588 : ScanKeyInit(&skey,
20636 : Anum_pg_inherits_inhrelid,
20637 : BTEqualStrategyNumber, F_OIDEQ,
20638 : ObjectIdGetDatum(RelationGetRelid(attachrel)));
20639 1588 : scan = systable_beginscan(catalog, InheritsRelidSeqnoIndexId, true,
20640 : NULL, 1, &skey);
20641 1588 : if (HeapTupleIsValid(systable_getnext(scan)))
20642 4 : ereport(ERROR,
20643 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20644 : errmsg("cannot attach inheritance child as partition")));
20645 1584 : systable_endscan(scan);
20646 :
20647 : /* ...or as a parent table (except the case when it is partitioned) */
20648 1584 : ScanKeyInit(&skey,
20649 : Anum_pg_inherits_inhparent,
20650 : BTEqualStrategyNumber, F_OIDEQ,
20651 : ObjectIdGetDatum(RelationGetRelid(attachrel)));
20652 1584 : scan = systable_beginscan(catalog, InheritsParentIndexId, true, NULL,
20653 : 1, &skey);
20654 1584 : if (HeapTupleIsValid(systable_getnext(scan)) &&
20655 184 : attachrel->rd_rel->relkind == RELKIND_RELATION)
20656 4 : ereport(ERROR,
20657 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20658 : errmsg("cannot attach inheritance parent as partition")));
20659 1580 : systable_endscan(scan);
20660 1580 : table_close(catalog, AccessShareLock);
20661 :
20662 : /*
20663 : * Prevent circularity by seeing if rel is a partition of attachrel. (In
20664 : * particular, this disallows making a rel a partition of itself.)
20665 : *
20666 : * We do that by checking if rel is a member of the list of attachrel's
20667 : * partitions provided the latter is partitioned at all. We want to avoid
20668 : * having to construct this list again, so we request the strongest lock
20669 : * on all partitions. We need the strongest lock, because we may decide
20670 : * to scan them if we find out that the table being attached (or its leaf
20671 : * partitions) may contain rows that violate the partition constraint. If
20672 : * the table has a constraint that would prevent such rows, which by
20673 : * definition is present in all the partitions, we need not scan the
20674 : * table, nor its partitions. But we cannot risk a deadlock by taking a
20675 : * weaker lock now and the stronger one only when needed.
20676 : */
20677 1580 : attachrel_children = find_all_inheritors(RelationGetRelid(attachrel),
20678 : AccessExclusiveLock, NULL);
20679 1580 : if (list_member_oid(attachrel_children, RelationGetRelid(rel)))
20680 8 : ereport(ERROR,
20681 : (errcode(ERRCODE_DUPLICATE_TABLE),
20682 : errmsg("circular inheritance not allowed"),
20683 : errdetail("\"%s\" is already a child of \"%s\".",
20684 : RelationGetRelationName(rel),
20685 : RelationGetRelationName(attachrel))));
20686 :
20687 : /* If the parent is permanent, so must be all of its partitions. */
20688 1572 : if (rel->rd_rel->relpersistence != RELPERSISTENCE_TEMP &&
20689 1544 : attachrel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
20690 4 : ereport(ERROR,
20691 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20692 : errmsg("cannot attach a temporary relation as partition of permanent relation \"%s\"",
20693 : RelationGetRelationName(rel))));
20694 :
20695 : /* Temp parent cannot have a partition that is itself not a temp */
20696 1568 : if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
20697 28 : attachrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
20698 12 : ereport(ERROR,
20699 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20700 : errmsg("cannot attach a permanent relation as partition of temporary relation \"%s\"",
20701 : RelationGetRelationName(rel))));
20702 :
20703 : /* If the parent is temp, it must belong to this session */
20704 1556 : if (RELATION_IS_OTHER_TEMP(rel))
20705 0 : ereport(ERROR,
20706 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20707 : errmsg("cannot attach as partition of temporary relation of another session")));
20708 :
20709 : /* Ditto for the partition */
20710 1556 : if (RELATION_IS_OTHER_TEMP(attachrel))
20711 0 : ereport(ERROR,
20712 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20713 : errmsg("cannot attach temporary relation of another session as partition")));
20714 :
20715 : /*
20716 : * Check if attachrel has any identity columns or any columns that aren't
20717 : * in the parent.
20718 : */
20719 1556 : tupleDesc = RelationGetDescr(attachrel);
20720 1556 : natts = tupleDesc->natts;
20721 5394 : for (attno = 1; attno <= natts; attno++)
20722 : {
20723 3866 : Form_pg_attribute attribute = TupleDescAttr(tupleDesc, attno - 1);
20724 3866 : char *attributeName = NameStr(attribute->attname);
20725 :
20726 : /* Ignore dropped */
20727 3866 : if (attribute->attisdropped)
20728 416 : continue;
20729 :
20730 3450 : if (attribute->attidentity)
20731 16 : ereport(ERROR,
20732 : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
20733 : errmsg("table \"%s\" being attached contains an identity column \"%s\"",
20734 : RelationGetRelationName(attachrel), attributeName),
20735 : errdetail("The new partition may not contain an identity column."));
20736 :
20737 : /* Try to find the column in parent (matching on column name) */
20738 3434 : if (!SearchSysCacheExists2(ATTNAME,
20739 : ObjectIdGetDatum(RelationGetRelid(rel)),
20740 : CStringGetDatum(attributeName)))
20741 12 : ereport(ERROR,
20742 : (errcode(ERRCODE_DATATYPE_MISMATCH),
20743 : errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
20744 : RelationGetRelationName(attachrel), attributeName,
20745 : RelationGetRelationName(rel)),
20746 : errdetail("The new partition may contain only the columns present in parent.")));
20747 : }
20748 :
20749 : /*
20750 : * If child_rel has row-level triggers with transition tables, we
20751 : * currently don't allow it to become a partition. See also prohibitions
20752 : * in ATExecAddInherit() and CreateTrigger().
20753 : */
20754 1528 : trigger_name = FindTriggerIncompatibleWithInheritance(attachrel->trigdesc);
20755 1528 : if (trigger_name != NULL)
20756 4 : ereport(ERROR,
20757 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
20758 : errmsg("trigger \"%s\" prevents table \"%s\" from becoming a partition",
20759 : trigger_name, RelationGetRelationName(attachrel)),
20760 : errdetail("ROW triggers with transition tables are not supported on partitions.")));
20761 :
20762 : /*
20763 : * Check that the new partition's bound is valid and does not overlap any
20764 : * of existing partitions of the parent - note that it does not return on
20765 : * error.
20766 : */
20767 1524 : check_new_partition_bound(RelationGetRelationName(attachrel), rel,
20768 : cmd->bound, pstate);
20769 :
20770 1500 : attachPartitionTable(wqueue, rel, attachrel, cmd->bound);
20771 :
20772 : /*
20773 : * Generate a partition constraint from the partition bound specification.
20774 : * If the parent itself is a partition, make sure to include its
20775 : * constraint as well.
20776 : */
20777 1392 : partBoundConstraint = get_qual_from_partbound(rel, cmd->bound);
20778 :
20779 : /*
20780 : * Use list_concat_copy() to avoid modifying partBoundConstraint in place,
20781 : * since it's needed later to construct the constraint expression for
20782 : * validating against the default partition, if any.
20783 : */
20784 1392 : partConstraint = list_concat_copy(partBoundConstraint,
20785 1392 : RelationGetPartitionQual(rel));
20786 :
20787 : /* Skip validation if there are no constraints to validate. */
20788 1392 : if (partConstraint)
20789 : {
20790 : /*
20791 : * Run the partition quals through const-simplification similar to
20792 : * check constraints. We skip canonicalize_qual, though, because
20793 : * partition quals should be in canonical form already.
20794 : */
20795 : partConstraint =
20796 1364 : (List *) eval_const_expressions(NULL,
20797 : (Node *) partConstraint);
20798 :
20799 : /* XXX this sure looks wrong */
20800 1364 : partConstraint = list_make1(make_ands_explicit(partConstraint));
20801 :
20802 : /*
20803 : * Adjust the generated constraint to match this partition's attribute
20804 : * numbers.
20805 : */
20806 1364 : partConstraint = map_partition_varattnos(partConstraint, 1, attachrel,
20807 : rel);
20808 :
20809 : /* Validate partition constraints against the table being attached. */
20810 1364 : QueuePartitionConstraintValidation(wqueue, attachrel, partConstraint,
20811 : false);
20812 : }
20813 :
20814 : /*
20815 : * If we're attaching a partition other than the default partition and a
20816 : * default one exists, then that partition's partition constraint changes,
20817 : * so add an entry to the work queue to validate it, too. (We must not do
20818 : * this when the partition being attached is the default one; we already
20819 : * did it above!)
20820 : */
20821 1392 : if (OidIsValid(defaultPartOid))
20822 : {
20823 : Relation defaultrel;
20824 : List *defPartConstraint;
20825 :
20826 : Assert(!cmd->bound->is_default);
20827 :
20828 : /* we already hold a lock on the default partition */
20829 93 : defaultrel = table_open(defaultPartOid, NoLock);
20830 : defPartConstraint =
20831 93 : get_proposed_default_constraint(partBoundConstraint);
20832 :
20833 : /*
20834 : * Map the Vars in the constraint expression from rel's attnos to
20835 : * defaultrel's.
20836 : */
20837 : defPartConstraint =
20838 93 : map_partition_varattnos(defPartConstraint,
20839 : 1, defaultrel, rel);
20840 93 : QueuePartitionConstraintValidation(wqueue, defaultrel,
20841 : defPartConstraint, true);
20842 :
20843 : /* keep our lock until commit. */
20844 93 : table_close(defaultrel, NoLock);
20845 : }
20846 :
20847 1392 : ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel));
20848 :
20849 : /*
20850 : * If the partition we just attached is partitioned itself, invalidate
20851 : * relcache for all descendent partitions too to ensure that their
20852 : * rd_partcheck expression trees are rebuilt; partitions already locked at
20853 : * the beginning of this function.
20854 : */
20855 1392 : if (attachrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
20856 : {
20857 : ListCell *l;
20858 :
20859 711 : foreach(l, attachrel_children)
20860 : {
20861 482 : CacheInvalidateRelcacheByRelid(lfirst_oid(l));
20862 : }
20863 : }
20864 :
20865 : /* keep our lock until commit */
20866 1392 : table_close(attachrel, NoLock);
20867 :
20868 1392 : return address;
20869 : }
20870 :
20871 : /*
20872 : * AttachPartitionEnsureIndexes
20873 : * subroutine for ATExecAttachPartition to create/match indexes
20874 : *
20875 : * Enforce the indexing rule for partitioned tables during ALTER TABLE / ATTACH
20876 : * PARTITION: every partition must have an index attached to each index on the
20877 : * partitioned table.
20878 : */
20879 : static void
20880 1908 : AttachPartitionEnsureIndexes(List **wqueue, Relation rel, Relation attachrel)
20881 : {
20882 : List *idxes;
20883 : List *attachRelIdxs;
20884 : Relation *attachrelIdxRels;
20885 : IndexInfo **attachInfos;
20886 : ListCell *cell;
20887 : MemoryContext cxt;
20888 : MemoryContext oldcxt;
20889 :
20890 1908 : cxt = AllocSetContextCreate(CurrentMemoryContext,
20891 : "AttachPartitionEnsureIndexes",
20892 : ALLOCSET_DEFAULT_SIZES);
20893 1908 : oldcxt = MemoryContextSwitchTo(cxt);
20894 :
20895 1908 : idxes = RelationGetIndexList(rel);
20896 1908 : attachRelIdxs = RelationGetIndexList(attachrel);
20897 1908 : attachrelIdxRels = palloc_array(Relation, list_length(attachRelIdxs));
20898 1908 : attachInfos = palloc_array(IndexInfo *, list_length(attachRelIdxs));
20899 :
20900 : /* Build arrays of all existing indexes and their IndexInfos */
20901 4085 : foreach_oid(cldIdxId, attachRelIdxs)
20902 : {
20903 269 : int i = foreach_current_index(cldIdxId);
20904 :
20905 269 : attachrelIdxRels[i] = index_open(cldIdxId, AccessShareLock);
20906 269 : attachInfos[i] = BuildIndexInfo(attachrelIdxRels[i]);
20907 : }
20908 :
20909 : /*
20910 : * If we're attaching a foreign table, we must fail if any of the indexes
20911 : * is a constraint index; otherwise, there's nothing to do here. Do this
20912 : * before starting work, to avoid wasting the effort of building a few
20913 : * non-unique indexes before coming across a unique one.
20914 : */
20915 1908 : if (attachrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
20916 : {
20917 55 : foreach(cell, idxes)
20918 : {
20919 24 : Oid idx = lfirst_oid(cell);
20920 24 : Relation idxRel = index_open(idx, AccessShareLock);
20921 :
20922 24 : if (idxRel->rd_index->indisunique ||
20923 16 : idxRel->rd_index->indisprimary)
20924 8 : ereport(ERROR,
20925 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20926 : errmsg("cannot attach foreign table \"%s\" as partition of partitioned table \"%s\"",
20927 : RelationGetRelationName(attachrel),
20928 : RelationGetRelationName(rel)),
20929 : errdetail("Partitioned table \"%s\" contains unique indexes.",
20930 : RelationGetRelationName(rel))));
20931 16 : index_close(idxRel, AccessShareLock);
20932 : }
20933 :
20934 31 : goto out;
20935 : }
20936 :
20937 : /*
20938 : * For each index on the partitioned table, find a matching one in the
20939 : * partition-to-be; if one is not found, create one.
20940 : */
20941 2350 : foreach(cell, idxes)
20942 : {
20943 493 : Oid idx = lfirst_oid(cell);
20944 493 : Relation idxRel = index_open(idx, AccessShareLock);
20945 : IndexInfo *info;
20946 : AttrMap *attmap;
20947 493 : bool found = false;
20948 : Oid constraintOid;
20949 :
20950 : /*
20951 : * Ignore indexes in the partitioned table other than partitioned
20952 : * indexes.
20953 : */
20954 493 : if (idxRel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
20955 : {
20956 0 : index_close(idxRel, AccessShareLock);
20957 0 : continue;
20958 : }
20959 :
20960 : /* construct an indexinfo to compare existing indexes against */
20961 493 : info = BuildIndexInfo(idxRel);
20962 493 : attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
20963 : RelationGetDescr(rel),
20964 : false);
20965 493 : constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
20966 :
20967 : /*
20968 : * Scan the list of existing indexes in the partition-to-be, and mark
20969 : * the first matching, valid, unattached one we find, if any, as
20970 : * partition of the parent index. If we find one, we're done.
20971 : */
20972 533 : for (int i = 0; i < list_length(attachRelIdxs); i++)
20973 : {
20974 197 : Oid cldIdxId = RelationGetRelid(attachrelIdxRels[i]);
20975 197 : Oid cldConstrOid = InvalidOid;
20976 :
20977 : /* does this index have a parent? if so, can't use it */
20978 197 : if (attachrelIdxRels[i]->rd_rel->relispartition)
20979 8 : continue;
20980 :
20981 : /* If this index is invalid, can't use it */
20982 189 : if (!attachrelIdxRels[i]->rd_index->indisvalid)
20983 4 : continue;
20984 :
20985 185 : if (CompareIndexInfo(attachInfos[i], info,
20986 185 : attachrelIdxRels[i]->rd_indcollation,
20987 185 : idxRel->rd_indcollation,
20988 185 : attachrelIdxRels[i]->rd_opfamily,
20989 185 : idxRel->rd_opfamily,
20990 : attmap))
20991 : {
20992 : /*
20993 : * If this index is being created in the parent because of a
20994 : * constraint, then the child needs to have a constraint also,
20995 : * so look for one. If there is no such constraint, this
20996 : * index is no good, so keep looking.
20997 : */
20998 161 : if (OidIsValid(constraintOid))
20999 : {
21000 : cldConstrOid =
21001 96 : get_relation_idx_constraint_oid(RelationGetRelid(attachrel),
21002 : cldIdxId);
21003 : /* no dice */
21004 96 : if (!OidIsValid(cldConstrOid))
21005 4 : continue;
21006 :
21007 : /* Ensure they're both the same type of constraint */
21008 184 : if (get_constraint_type(constraintOid) !=
21009 92 : get_constraint_type(cldConstrOid))
21010 0 : continue;
21011 : }
21012 :
21013 : /* bingo. */
21014 157 : IndexSetParentIndex(attachrelIdxRels[i], idx);
21015 157 : if (OidIsValid(constraintOid))
21016 92 : ConstraintSetParentConstraint(cldConstrOid, constraintOid,
21017 : RelationGetRelid(attachrel));
21018 157 : found = true;
21019 :
21020 157 : CommandCounterIncrement();
21021 157 : break;
21022 : }
21023 : }
21024 :
21025 : /*
21026 : * If no suitable index was found in the partition-to-be, create one
21027 : * now. Note that if this is a PK, not-null constraints must already
21028 : * exist.
21029 : */
21030 493 : if (!found)
21031 : {
21032 : IndexStmt *stmt;
21033 : Oid conOid;
21034 :
21035 336 : stmt = generateClonedIndexStmt(NULL,
21036 : idxRel, attmap,
21037 : &conOid);
21038 336 : DefineIndex(NULL,
21039 : RelationGetRelid(attachrel), stmt, InvalidOid,
21040 : RelationGetRelid(idxRel),
21041 : conOid,
21042 : -1,
21043 : true, false, false, false, false);
21044 : }
21045 :
21046 481 : index_close(idxRel, AccessShareLock);
21047 : }
21048 :
21049 1888 : out:
21050 : /* Clean up. */
21051 2149 : for (int i = 0; i < list_length(attachRelIdxs); i++)
21052 261 : index_close(attachrelIdxRels[i], AccessShareLock);
21053 1888 : MemoryContextSwitchTo(oldcxt);
21054 1888 : MemoryContextDelete(cxt);
21055 1888 : }
21056 :
21057 : /*
21058 : * CloneRowTriggersToPartition
21059 : * subroutine for ATExecAttachPartition/DefineRelation to create row
21060 : * triggers on partitions
21061 : */
21062 : static void
21063 2148 : CloneRowTriggersToPartition(Relation parent, Relation partition)
21064 : {
21065 : Relation pg_trigger;
21066 : ScanKeyData key;
21067 : SysScanDesc scan;
21068 : HeapTuple tuple;
21069 : MemoryContext perTupCxt;
21070 :
21071 2148 : ScanKeyInit(&key, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber,
21072 : F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(parent)));
21073 2148 : pg_trigger = table_open(TriggerRelationId, RowExclusiveLock);
21074 2148 : scan = systable_beginscan(pg_trigger, TriggerRelidNameIndexId,
21075 : true, NULL, 1, &key);
21076 :
21077 2148 : perTupCxt = AllocSetContextCreate(CurrentMemoryContext,
21078 : "clone trig", ALLOCSET_SMALL_SIZES);
21079 :
21080 3377 : while (HeapTupleIsValid(tuple = systable_getnext(scan)))
21081 : {
21082 1233 : Form_pg_trigger trigForm = (Form_pg_trigger) GETSTRUCT(tuple);
21083 : CreateTrigStmt *trigStmt;
21084 1233 : Node *qual = NULL;
21085 : Datum value;
21086 : bool isnull;
21087 1233 : List *cols = NIL;
21088 1233 : List *trigargs = NIL;
21089 : MemoryContext oldcxt;
21090 :
21091 : /*
21092 : * Ignore statement-level triggers; those are not cloned.
21093 : */
21094 1233 : if (!TRIGGER_FOR_ROW(trigForm->tgtype))
21095 1102 : continue;
21096 :
21097 : /*
21098 : * Don't clone internal triggers, because the constraint cloning code
21099 : * will.
21100 : */
21101 1205 : if (trigForm->tgisinternal)
21102 1074 : continue;
21103 :
21104 : /*
21105 : * Complain if we find an unexpected trigger type.
21106 : */
21107 131 : if (!TRIGGER_FOR_BEFORE(trigForm->tgtype) &&
21108 107 : !TRIGGER_FOR_AFTER(trigForm->tgtype))
21109 0 : elog(ERROR, "unexpected trigger \"%s\" found",
21110 : NameStr(trigForm->tgname));
21111 :
21112 : /* Use short-lived context for CREATE TRIGGER */
21113 131 : oldcxt = MemoryContextSwitchTo(perTupCxt);
21114 :
21115 : /*
21116 : * If there is a WHEN clause, generate a 'cooked' version of it that's
21117 : * appropriate for the partition.
21118 : */
21119 131 : value = heap_getattr(tuple, Anum_pg_trigger_tgqual,
21120 : RelationGetDescr(pg_trigger), &isnull);
21121 131 : if (!isnull)
21122 : {
21123 4 : qual = stringToNode(TextDatumGetCString(value));
21124 4 : qual = (Node *) map_partition_varattnos((List *) qual, PRS2_OLD_VARNO,
21125 : partition, parent);
21126 4 : qual = (Node *) map_partition_varattnos((List *) qual, PRS2_NEW_VARNO,
21127 : partition, parent);
21128 : }
21129 :
21130 : /*
21131 : * If there is a column list, transform it to a list of column names.
21132 : * Note we don't need to map this list in any way ...
21133 : */
21134 131 : if (trigForm->tgattr.dim1 > 0)
21135 : {
21136 : int i;
21137 :
21138 8 : for (i = 0; i < trigForm->tgattr.dim1; i++)
21139 : {
21140 : Form_pg_attribute col;
21141 :
21142 4 : col = TupleDescAttr(parent->rd_att,
21143 4 : trigForm->tgattr.values[i] - 1);
21144 4 : cols = lappend(cols,
21145 4 : makeString(pstrdup(NameStr(col->attname))));
21146 : }
21147 : }
21148 :
21149 : /* Reconstruct trigger arguments list. */
21150 131 : if (trigForm->tgnargs > 0)
21151 : {
21152 : char *p;
21153 :
21154 36 : value = heap_getattr(tuple, Anum_pg_trigger_tgargs,
21155 : RelationGetDescr(pg_trigger), &isnull);
21156 36 : if (isnull)
21157 0 : elog(ERROR, "tgargs is null for trigger \"%s\" in partition \"%s\"",
21158 : NameStr(trigForm->tgname), RelationGetRelationName(partition));
21159 :
21160 36 : p = (char *) VARDATA_ANY(DatumGetByteaPP(value));
21161 :
21162 80 : for (int i = 0; i < trigForm->tgnargs; i++)
21163 : {
21164 44 : trigargs = lappend(trigargs, makeString(pstrdup(p)));
21165 44 : p += strlen(p) + 1;
21166 : }
21167 : }
21168 :
21169 131 : trigStmt = makeNode(CreateTrigStmt);
21170 131 : trigStmt->replace = false;
21171 131 : trigStmt->isconstraint = OidIsValid(trigForm->tgconstraint);
21172 131 : trigStmt->trigname = NameStr(trigForm->tgname);
21173 131 : trigStmt->relation = NULL;
21174 131 : trigStmt->funcname = NULL; /* passed separately */
21175 131 : trigStmt->args = trigargs;
21176 131 : trigStmt->row = true;
21177 131 : trigStmt->timing = trigForm->tgtype & TRIGGER_TYPE_TIMING_MASK;
21178 131 : trigStmt->events = trigForm->tgtype & TRIGGER_TYPE_EVENT_MASK;
21179 131 : trigStmt->columns = cols;
21180 131 : trigStmt->whenClause = NULL; /* passed separately */
21181 131 : trigStmt->transitionRels = NIL; /* not supported at present */
21182 131 : trigStmt->deferrable = trigForm->tgdeferrable;
21183 131 : trigStmt->initdeferred = trigForm->tginitdeferred;
21184 131 : trigStmt->constrrel = NULL; /* passed separately */
21185 :
21186 131 : CreateTriggerFiringOn(trigStmt, NULL, RelationGetRelid(partition),
21187 : trigForm->tgconstrrelid, InvalidOid, InvalidOid,
21188 : trigForm->tgfoid, trigForm->oid, qual,
21189 131 : false, true, trigForm->tgenabled);
21190 :
21191 127 : MemoryContextSwitchTo(oldcxt);
21192 127 : MemoryContextReset(perTupCxt);
21193 : }
21194 :
21195 2144 : MemoryContextDelete(perTupCxt);
21196 :
21197 2144 : systable_endscan(scan);
21198 2144 : table_close(pg_trigger, RowExclusiveLock);
21199 2144 : }
21200 :
21201 : /*
21202 : * ALTER TABLE DETACH PARTITION
21203 : *
21204 : * Return the address of the relation that is no longer a partition of rel.
21205 : *
21206 : * If concurrent mode is requested, we run in two transactions. A side-
21207 : * effect is that this command cannot run in a multi-part ALTER TABLE.
21208 : * Currently, that's enforced by the grammar.
21209 : *
21210 : * The strategy for concurrency is to first modify the partition's
21211 : * pg_inherit catalog row to make it visible to everyone that the
21212 : * partition is detached, lock the partition against writes, and commit
21213 : * the transaction; anyone who requests the partition descriptor from
21214 : * that point onwards has to ignore such a partition. In a second
21215 : * transaction, we wait until all transactions that could have seen the
21216 : * partition as attached are gone, then we remove the rest of partition
21217 : * metadata (pg_inherits and pg_class.relpartbounds).
21218 : */
21219 : static ObjectAddress
21220 373 : ATExecDetachPartition(List **wqueue, AlteredTableInfo *tab, Relation rel,
21221 : RangeVar *name, bool concurrent)
21222 : {
21223 : Relation partRel;
21224 : ObjectAddress address;
21225 : Oid defaultPartOid;
21226 :
21227 : /*
21228 : * We must lock the default partition, because detaching this partition
21229 : * will change its partition constraint.
21230 : */
21231 : defaultPartOid =
21232 373 : get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
21233 373 : if (OidIsValid(defaultPartOid))
21234 : {
21235 : /*
21236 : * Concurrent detaching when a default partition exists is not
21237 : * supported. The main problem is that the default partition
21238 : * constraint would change. And there's a definitional problem: what
21239 : * should happen to the tuples that are being inserted that belong to
21240 : * the partition being detached? Putting them on the partition being
21241 : * detached would be wrong, since they'd become "lost" after the
21242 : * detaching completes but we cannot put them in the default partition
21243 : * either until we alter its partition constraint.
21244 : *
21245 : * I think we could solve this problem if we effected the constraint
21246 : * change before committing the first transaction. But the lock would
21247 : * have to remain AEL and it would cause concurrent query planning to
21248 : * be blocked, so changing it that way would be even worse.
21249 : */
21250 74 : if (concurrent)
21251 8 : ereport(ERROR,
21252 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
21253 : errmsg("cannot detach partitions concurrently when a default partition exists")));
21254 66 : LockRelationOid(defaultPartOid, AccessExclusiveLock);
21255 : }
21256 :
21257 : /*
21258 : * In concurrent mode, the partition is locked with share-update-exclusive
21259 : * in the first transaction. This allows concurrent transactions to be
21260 : * doing DML to the partition.
21261 : */
21262 365 : partRel = table_openrv(name, concurrent ? ShareUpdateExclusiveLock :
21263 : AccessExclusiveLock);
21264 :
21265 : /*
21266 : * Check inheritance conditions and either delete the pg_inherits row (in
21267 : * non-concurrent mode) or just set the inhdetachpending flag.
21268 : */
21269 357 : if (!concurrent)
21270 282 : RemoveInheritance(partRel, rel, false);
21271 : else
21272 75 : MarkInheritDetached(partRel, rel);
21273 :
21274 : /*
21275 : * Ensure that foreign keys still hold after this detach. This keeps
21276 : * locks on the referencing tables, which prevents concurrent transactions
21277 : * from adding rows that we wouldn't see. For this to work in concurrent
21278 : * mode, it is critical that the partition appears as no longer attached
21279 : * for the RI queries as soon as the first transaction commits.
21280 : */
21281 344 : ATDetachCheckNoForeignKeyRefs(partRel);
21282 :
21283 : /*
21284 : * Concurrent mode has to work harder; first we add a new constraint to
21285 : * the partition that matches the partition constraint. Then we close our
21286 : * existing transaction, and in a new one wait for all processes to catch
21287 : * up on the catalog updates we've done so far; at that point we can
21288 : * complete the operation.
21289 : */
21290 322 : if (concurrent)
21291 : {
21292 : Oid partrelid,
21293 : parentrelid;
21294 : LOCKTAG tag;
21295 : char *parentrelname;
21296 : char *partrelname;
21297 :
21298 : /*
21299 : * We're almost done now; the only traces that remain are the
21300 : * pg_inherits tuple and the partition's relpartbounds. Before we can
21301 : * remove those, we need to wait until all transactions that know that
21302 : * this is a partition are gone.
21303 : */
21304 :
21305 : /*
21306 : * Remember relation OIDs to re-acquire them later; and relation names
21307 : * too, for error messages if something is dropped in between.
21308 : */
21309 72 : partrelid = RelationGetRelid(partRel);
21310 72 : parentrelid = RelationGetRelid(rel);
21311 72 : parentrelname = MemoryContextStrdup(PortalContext,
21312 72 : RelationGetRelationName(rel));
21313 72 : partrelname = MemoryContextStrdup(PortalContext,
21314 72 : RelationGetRelationName(partRel));
21315 :
21316 : /* Invalidate relcache entries for the parent -- must be before close */
21317 72 : CacheInvalidateRelcache(rel);
21318 :
21319 72 : table_close(partRel, NoLock);
21320 72 : table_close(rel, NoLock);
21321 72 : tab->rel = NULL;
21322 :
21323 : /* Make updated catalog entry visible */
21324 72 : PopActiveSnapshot();
21325 72 : CommitTransactionCommand();
21326 :
21327 72 : StartTransactionCommand();
21328 :
21329 : /*
21330 : * Now wait. This ensures that all queries that were planned
21331 : * including the partition are finished before we remove the rest of
21332 : * catalog entries. We don't need or indeed want to acquire this
21333 : * lock, though -- that would block later queries.
21334 : *
21335 : * We don't need to concern ourselves with waiting for a lock on the
21336 : * partition itself, since we will acquire AccessExclusiveLock below.
21337 : */
21338 72 : SET_LOCKTAG_RELATION(tag, MyDatabaseId, parentrelid);
21339 72 : WaitForLockersMultiple(list_make1(&tag), AccessExclusiveLock, false);
21340 :
21341 : /*
21342 : * Now acquire locks in both relations again. Note they may have been
21343 : * removed in the meantime, so care is required.
21344 : */
21345 47 : rel = try_relation_open(parentrelid, ShareUpdateExclusiveLock);
21346 47 : partRel = try_relation_open(partrelid, AccessExclusiveLock);
21347 :
21348 : /* If the relations aren't there, something bad happened; bail out */
21349 47 : if (rel == NULL)
21350 : {
21351 0 : if (partRel != NULL) /* shouldn't happen */
21352 0 : elog(WARNING, "dangling partition \"%s\" remains, can't fix",
21353 : partrelname);
21354 0 : ereport(ERROR,
21355 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
21356 : errmsg("partitioned table \"%s\" was removed concurrently",
21357 : parentrelname)));
21358 : }
21359 47 : if (partRel == NULL)
21360 0 : ereport(ERROR,
21361 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
21362 : errmsg("partition \"%s\" was removed concurrently", partrelname)));
21363 :
21364 47 : tab->rel = rel;
21365 : }
21366 :
21367 : /*
21368 : * Detaching the partition might involve TOAST table access, so ensure we
21369 : * have a valid snapshot.
21370 : */
21371 297 : PushActiveSnapshot(GetTransactionSnapshot());
21372 :
21373 : /* Do the final part of detaching */
21374 297 : DetachPartitionFinalize(rel, partRel, concurrent, defaultPartOid);
21375 :
21376 296 : PopActiveSnapshot();
21377 :
21378 296 : ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
21379 :
21380 : /* keep our lock until commit */
21381 296 : table_close(partRel, NoLock);
21382 :
21383 296 : return address;
21384 : }
21385 :
21386 : /*
21387 : * Second part of ALTER TABLE .. DETACH.
21388 : *
21389 : * This is separate so that it can be run independently when the second
21390 : * transaction of the concurrent algorithm fails (crash or abort).
21391 : */
21392 : static void
21393 741 : DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
21394 : Oid defaultPartOid)
21395 : {
21396 : Relation classRel;
21397 : List *fks;
21398 : ListCell *cell;
21399 : List *indexes;
21400 : Datum new_val[Natts_pg_class];
21401 : bool new_null[Natts_pg_class],
21402 : new_repl[Natts_pg_class];
21403 : HeapTuple tuple,
21404 : newtuple;
21405 741 : Relation trigrel = NULL;
21406 741 : List *fkoids = NIL;
21407 :
21408 741 : if (concurrent)
21409 : {
21410 : /*
21411 : * We can remove the pg_inherits row now. (In the non-concurrent case,
21412 : * this was already done).
21413 : */
21414 54 : RemoveInheritance(partRel, rel, true);
21415 : }
21416 :
21417 : /* Drop any triggers that were cloned on creation/attach. */
21418 741 : DropClonedTriggersFromPartition(RelationGetRelid(partRel));
21419 :
21420 : /*
21421 : * Detach any foreign keys that are inherited. This includes creating
21422 : * additional action triggers.
21423 : */
21424 741 : fks = copyObject(RelationGetFKeyList(partRel));
21425 741 : if (fks != NIL)
21426 60 : trigrel = table_open(TriggerRelationId, RowExclusiveLock);
21427 :
21428 : /*
21429 : * It's possible that the partition being detached has a foreign key that
21430 : * references a partitioned table. When that happens, there are multiple
21431 : * pg_constraint rows for the partition: one points to the partitioned
21432 : * table itself, while the others point to each of its partitions. Only
21433 : * the topmost one is to be considered here; the child constraints must be
21434 : * left alone, because conceptually those aren't coming from our parent
21435 : * partitioned table, but from this partition itself.
21436 : *
21437 : * We implement this by collecting all the constraint OIDs in a first scan
21438 : * of the FK array, and skipping in the loop below those constraints whose
21439 : * parents are listed here.
21440 : */
21441 1598 : foreach_node(ForeignKeyCacheInfo, fk, fks)
21442 116 : fkoids = lappend_oid(fkoids, fk->conoid);
21443 :
21444 857 : foreach(cell, fks)
21445 : {
21446 116 : ForeignKeyCacheInfo *fk = lfirst(cell);
21447 : HeapTuple contup;
21448 : Form_pg_constraint conform;
21449 :
21450 116 : contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
21451 116 : if (!HeapTupleIsValid(contup))
21452 0 : elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
21453 116 : conform = (Form_pg_constraint) GETSTRUCT(contup);
21454 :
21455 : /*
21456 : * Consider only inherited foreign keys, and only if their parents
21457 : * aren't in the list.
21458 : */
21459 116 : if (conform->contype != CONSTRAINT_FOREIGN ||
21460 216 : !OidIsValid(conform->conparentid) ||
21461 100 : list_member_oid(fkoids, conform->conparentid))
21462 : {
21463 44 : ReleaseSysCache(contup);
21464 44 : continue;
21465 : }
21466 :
21467 : /*
21468 : * The constraint on this table must be marked no longer a child of
21469 : * the parent's constraint, as do its check triggers.
21470 : */
21471 72 : ConstraintSetParentConstraint(fk->conoid, InvalidOid, InvalidOid);
21472 :
21473 : /*
21474 : * Also, look up the partition's "check" triggers corresponding to the
21475 : * ENFORCED constraint being detached and detach them from the parent
21476 : * triggers. NOT ENFORCED constraints do not have these triggers;
21477 : * therefore, this step is not needed.
21478 : */
21479 72 : if (fk->conenforced)
21480 : {
21481 : Oid insertTriggerOid,
21482 : updateTriggerOid;
21483 :
21484 72 : GetForeignKeyCheckTriggers(trigrel,
21485 : fk->conoid, fk->confrelid, fk->conrelid,
21486 : &insertTriggerOid, &updateTriggerOid);
21487 : Assert(OidIsValid(insertTriggerOid));
21488 72 : TriggerSetParentTrigger(trigrel, insertTriggerOid, InvalidOid,
21489 : RelationGetRelid(partRel));
21490 : Assert(OidIsValid(updateTriggerOid));
21491 72 : TriggerSetParentTrigger(trigrel, updateTriggerOid, InvalidOid,
21492 : RelationGetRelid(partRel));
21493 : }
21494 :
21495 : /*
21496 : * Lastly, create the action triggers on the referenced table, using
21497 : * addFkRecurseReferenced, which requires some elaborate setup (so put
21498 : * it in a separate block). While at it, if the table is partitioned,
21499 : * that function will recurse to create the pg_constraint rows and
21500 : * action triggers for each partition.
21501 : *
21502 : * Note there's no need to do addFkConstraint() here, because the
21503 : * pg_constraint row already exists.
21504 : */
21505 : {
21506 : Constraint *fkconstraint;
21507 : int numfks;
21508 : AttrNumber conkey[INDEX_MAX_KEYS];
21509 : AttrNumber confkey[INDEX_MAX_KEYS];
21510 : Oid conpfeqop[INDEX_MAX_KEYS];
21511 : Oid conppeqop[INDEX_MAX_KEYS];
21512 : Oid conffeqop[INDEX_MAX_KEYS];
21513 : int numfkdelsetcols;
21514 : AttrNumber confdelsetcols[INDEX_MAX_KEYS];
21515 : Relation refdRel;
21516 :
21517 72 : DeconstructFkConstraintRow(contup,
21518 : &numfks,
21519 : conkey,
21520 : confkey,
21521 : conpfeqop,
21522 : conppeqop,
21523 : conffeqop,
21524 : &numfkdelsetcols,
21525 : confdelsetcols);
21526 :
21527 : /* Create a synthetic node we'll use throughout */
21528 72 : fkconstraint = makeNode(Constraint);
21529 72 : fkconstraint->contype = CONSTRAINT_FOREIGN;
21530 72 : fkconstraint->conname = pstrdup(NameStr(conform->conname));
21531 72 : fkconstraint->deferrable = conform->condeferrable;
21532 72 : fkconstraint->initdeferred = conform->condeferred;
21533 72 : fkconstraint->is_enforced = conform->conenforced;
21534 72 : fkconstraint->skip_validation = true;
21535 72 : fkconstraint->initially_valid = conform->convalidated;
21536 : /* a few irrelevant fields omitted here */
21537 72 : fkconstraint->pktable = NULL;
21538 72 : fkconstraint->fk_attrs = NIL;
21539 72 : fkconstraint->pk_attrs = NIL;
21540 72 : fkconstraint->fk_matchtype = conform->confmatchtype;
21541 72 : fkconstraint->fk_upd_action = conform->confupdtype;
21542 72 : fkconstraint->fk_del_action = conform->confdeltype;
21543 72 : fkconstraint->fk_del_set_cols = NIL;
21544 72 : fkconstraint->old_conpfeqop = NIL;
21545 72 : fkconstraint->old_pktable_oid = InvalidOid;
21546 72 : fkconstraint->location = -1;
21547 :
21548 : /* set up colnames, used to generate the constraint name */
21549 176 : for (int i = 0; i < numfks; i++)
21550 : {
21551 : Form_pg_attribute att;
21552 :
21553 104 : att = TupleDescAttr(RelationGetDescr(partRel),
21554 104 : conkey[i] - 1);
21555 :
21556 104 : fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
21557 104 : makeString(NameStr(att->attname)));
21558 : }
21559 :
21560 72 : refdRel = table_open(fk->confrelid, ShareRowExclusiveLock);
21561 :
21562 72 : addFkRecurseReferenced(fkconstraint, partRel,
21563 : refdRel,
21564 : conform->conindid,
21565 : fk->conoid,
21566 : numfks,
21567 : confkey,
21568 : conkey,
21569 : conpfeqop,
21570 : conppeqop,
21571 : conffeqop,
21572 : numfkdelsetcols,
21573 : confdelsetcols,
21574 : true,
21575 : InvalidOid, InvalidOid,
21576 72 : conform->conperiod);
21577 72 : table_close(refdRel, NoLock); /* keep lock till end of xact */
21578 : }
21579 :
21580 72 : ReleaseSysCache(contup);
21581 : }
21582 741 : list_free_deep(fks);
21583 741 : if (trigrel)
21584 60 : table_close(trigrel, RowExclusiveLock);
21585 :
21586 : /*
21587 : * Any sub-constraints that are in the referenced-side of a larger
21588 : * constraint have to be removed. This partition is no longer part of the
21589 : * key space of the constraint.
21590 : */
21591 796 : foreach(cell, GetParentedForeignKeyRefs(partRel))
21592 : {
21593 56 : Oid constrOid = lfirst_oid(cell);
21594 : ObjectAddress constraint;
21595 :
21596 56 : ConstraintSetParentConstraint(constrOid, InvalidOid, InvalidOid);
21597 56 : deleteDependencyRecordsForClass(ConstraintRelationId,
21598 : constrOid,
21599 : ConstraintRelationId,
21600 : DEPENDENCY_INTERNAL);
21601 56 : CommandCounterIncrement();
21602 :
21603 56 : ObjectAddressSet(constraint, ConstraintRelationId, constrOid);
21604 56 : performDeletion(&constraint, DROP_RESTRICT, 0);
21605 : }
21606 :
21607 : /* Now we can detach indexes */
21608 740 : indexes = RelationGetIndexList(partRel);
21609 1047 : foreach(cell, indexes)
21610 : {
21611 307 : Oid idxid = lfirst_oid(cell);
21612 : Oid parentidx;
21613 : Relation idx;
21614 : Oid constrOid;
21615 : Oid parentConstrOid;
21616 :
21617 307 : if (!has_superclass(idxid))
21618 9 : continue;
21619 :
21620 298 : parentidx = get_partition_parent(idxid, false);
21621 : Assert((IndexGetRelation(parentidx, false) == RelationGetRelid(rel)));
21622 :
21623 298 : idx = index_open(idxid, AccessExclusiveLock);
21624 298 : IndexSetParentIndex(idx, InvalidOid);
21625 :
21626 : /*
21627 : * If there's a constraint associated with the index, detach it too.
21628 : * Careful: it is possible for a constraint index in a partition to be
21629 : * the child of a non-constraint index, so verify whether the parent
21630 : * index does actually have a constraint.
21631 : */
21632 298 : constrOid = get_relation_idx_constraint_oid(RelationGetRelid(partRel),
21633 : idxid);
21634 298 : parentConstrOid = get_relation_idx_constraint_oid(RelationGetRelid(rel),
21635 : parentidx);
21636 298 : if (OidIsValid(parentConstrOid) && OidIsValid(constrOid))
21637 135 : ConstraintSetParentConstraint(constrOid, InvalidOid, InvalidOid);
21638 :
21639 298 : index_close(idx, NoLock);
21640 : }
21641 :
21642 : /* Update pg_class tuple */
21643 740 : classRel = table_open(RelationRelationId, RowExclusiveLock);
21644 740 : tuple = SearchSysCacheCopy1(RELOID,
21645 : ObjectIdGetDatum(RelationGetRelid(partRel)));
21646 740 : if (!HeapTupleIsValid(tuple))
21647 0 : elog(ERROR, "cache lookup failed for relation %u",
21648 : RelationGetRelid(partRel));
21649 : Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
21650 :
21651 : /* Clear relpartbound and reset relispartition */
21652 740 : memset(new_val, 0, sizeof(new_val));
21653 740 : memset(new_null, false, sizeof(new_null));
21654 740 : memset(new_repl, false, sizeof(new_repl));
21655 740 : new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
21656 740 : new_null[Anum_pg_class_relpartbound - 1] = true;
21657 740 : new_repl[Anum_pg_class_relpartbound - 1] = true;
21658 740 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
21659 : new_val, new_null, new_repl);
21660 :
21661 740 : ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
21662 740 : CatalogTupleUpdate(classRel, &newtuple->t_self, newtuple);
21663 740 : heap_freetuple(newtuple);
21664 740 : table_close(classRel, RowExclusiveLock);
21665 :
21666 : /*
21667 : * Drop identity property from all identity columns of partition.
21668 : */
21669 2328 : for (int attno = 0; attno < RelationGetNumberOfAttributes(partRel); attno++)
21670 : {
21671 1588 : Form_pg_attribute attr = TupleDescAttr(partRel->rd_att, attno);
21672 :
21673 1588 : if (!attr->attisdropped && attr->attidentity)
21674 20 : ATExecDropIdentity(partRel, NameStr(attr->attname), false,
21675 : AccessExclusiveLock, true, true);
21676 : }
21677 :
21678 740 : if (OidIsValid(defaultPartOid))
21679 : {
21680 : /*
21681 : * If the relation being detached is the default partition itself,
21682 : * remove it from the parent's pg_partitioned_table entry.
21683 : *
21684 : * If not, we must invalidate default partition's relcache entry, as
21685 : * in StorePartitionBound: its partition constraint depends on every
21686 : * other partition's partition constraint.
21687 : */
21688 167 : if (RelationGetRelid(partRel) == defaultPartOid)
21689 33 : update_default_partition_oid(RelationGetRelid(rel), InvalidOid);
21690 : else
21691 134 : CacheInvalidateRelcacheByRelid(defaultPartOid);
21692 : }
21693 :
21694 : /*
21695 : * Invalidate the parent's relcache so that the partition is no longer
21696 : * included in its partition descriptor.
21697 : */
21698 740 : CacheInvalidateRelcache(rel);
21699 :
21700 : /*
21701 : * If the partition we just detached is partitioned itself, invalidate
21702 : * relcache for all descendent partitions too to ensure that their
21703 : * rd_partcheck expression trees are rebuilt; must lock partitions before
21704 : * doing so, using the same lockmode as what partRel has been locked with
21705 : * by the caller.
21706 : */
21707 740 : if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
21708 : {
21709 : List *children;
21710 :
21711 41 : children = find_all_inheritors(RelationGetRelid(partRel),
21712 : AccessExclusiveLock, NULL);
21713 135 : foreach(cell, children)
21714 : {
21715 94 : CacheInvalidateRelcacheByRelid(lfirst_oid(cell));
21716 : }
21717 : }
21718 740 : }
21719 :
21720 : /*
21721 : * ALTER TABLE ... DETACH PARTITION ... FINALIZE
21722 : *
21723 : * To use when a DETACH PARTITION command previously did not run to
21724 : * completion; this completes the detaching process.
21725 : */
21726 : static ObjectAddress
21727 7 : ATExecDetachPartitionFinalize(Relation rel, RangeVar *name)
21728 : {
21729 : Relation partRel;
21730 : ObjectAddress address;
21731 7 : Snapshot snap = GetActiveSnapshot();
21732 :
21733 7 : partRel = table_openrv(name, AccessExclusiveLock);
21734 :
21735 : /*
21736 : * Wait until existing snapshots are gone. This is important if the
21737 : * second transaction of DETACH PARTITION CONCURRENTLY is canceled: the
21738 : * user could immediately run DETACH FINALIZE without actually waiting for
21739 : * existing transactions. We must not complete the detach action until
21740 : * all such queries are complete (otherwise we would present them with an
21741 : * inconsistent view of catalogs).
21742 : */
21743 7 : WaitForOlderSnapshots(snap->xmin, false);
21744 :
21745 7 : DetachPartitionFinalize(rel, partRel, true, InvalidOid);
21746 :
21747 7 : ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
21748 :
21749 7 : table_close(partRel, NoLock);
21750 :
21751 7 : return address;
21752 : }
21753 :
21754 : /*
21755 : * DropClonedTriggersFromPartition
21756 : * subroutine for ATExecDetachPartition to remove any triggers that were
21757 : * cloned to the partition when it was created-as-partition or attached.
21758 : * This undoes what CloneRowTriggersToPartition did.
21759 : */
21760 : static void
21761 741 : DropClonedTriggersFromPartition(Oid partitionId)
21762 : {
21763 : ScanKeyData skey;
21764 : SysScanDesc scan;
21765 : HeapTuple trigtup;
21766 : Relation tgrel;
21767 : ObjectAddresses *objects;
21768 :
21769 741 : objects = new_object_addresses();
21770 :
21771 : /*
21772 : * Scan pg_trigger to search for all triggers on this rel.
21773 : */
21774 741 : ScanKeyInit(&skey, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber,
21775 : F_OIDEQ, ObjectIdGetDatum(partitionId));
21776 741 : tgrel = table_open(TriggerRelationId, RowExclusiveLock);
21777 741 : scan = systable_beginscan(tgrel, TriggerRelidNameIndexId,
21778 : true, NULL, 1, &skey);
21779 1077 : while (HeapTupleIsValid(trigtup = systable_getnext(scan)))
21780 : {
21781 336 : Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(trigtup);
21782 : ObjectAddress trig;
21783 :
21784 : /* Ignore triggers that weren't cloned */
21785 336 : if (!OidIsValid(pg_trigger->tgparentid))
21786 296 : continue;
21787 :
21788 : /*
21789 : * Ignore internal triggers that are implementation objects of foreign
21790 : * keys, because these will be detached when the foreign keys
21791 : * themselves are.
21792 : */
21793 280 : if (OidIsValid(pg_trigger->tgconstrrelid))
21794 240 : continue;
21795 :
21796 : /*
21797 : * This is ugly, but necessary: remove the dependency markings on the
21798 : * trigger so that it can be removed.
21799 : */
21800 40 : deleteDependencyRecordsForClass(TriggerRelationId, pg_trigger->oid,
21801 : TriggerRelationId,
21802 : DEPENDENCY_PARTITION_PRI);
21803 40 : deleteDependencyRecordsForClass(TriggerRelationId, pg_trigger->oid,
21804 : RelationRelationId,
21805 : DEPENDENCY_PARTITION_SEC);
21806 :
21807 : /* remember this trigger to remove it below */
21808 40 : ObjectAddressSet(trig, TriggerRelationId, pg_trigger->oid);
21809 40 : add_exact_object_address(&trig, objects);
21810 : }
21811 :
21812 : /* make the dependency removal visible to the deletion below */
21813 741 : CommandCounterIncrement();
21814 741 : performMultipleDeletions(objects, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
21815 :
21816 : /* done */
21817 741 : free_object_addresses(objects);
21818 741 : systable_endscan(scan);
21819 741 : table_close(tgrel, RowExclusiveLock);
21820 741 : }
21821 :
21822 : /*
21823 : * Before acquiring lock on an index, acquire the same lock on the owning
21824 : * table.
21825 : */
21826 : struct AttachIndexCallbackState
21827 : {
21828 : Oid partitionOid;
21829 : Oid parentTblOid;
21830 : bool lockedParentTbl;
21831 : };
21832 :
21833 : static void
21834 278 : RangeVarCallbackForAttachIndex(const RangeVar *rv, Oid relOid, Oid oldRelOid,
21835 : void *arg)
21836 : {
21837 : struct AttachIndexCallbackState *state;
21838 : Form_pg_class classform;
21839 : HeapTuple tuple;
21840 :
21841 278 : state = (struct AttachIndexCallbackState *) arg;
21842 :
21843 278 : if (!state->lockedParentTbl)
21844 : {
21845 269 : LockRelationOid(state->parentTblOid, AccessShareLock);
21846 269 : state->lockedParentTbl = true;
21847 : }
21848 :
21849 : /*
21850 : * If we previously locked some other heap, and the name we're looking up
21851 : * no longer refers to an index on that relation, release the now-useless
21852 : * lock. XXX maybe we should do *after* we verify whether the index does
21853 : * not actually belong to the same relation ...
21854 : */
21855 278 : if (relOid != oldRelOid && OidIsValid(state->partitionOid))
21856 : {
21857 0 : UnlockRelationOid(state->partitionOid, AccessShareLock);
21858 0 : state->partitionOid = InvalidOid;
21859 : }
21860 :
21861 : /* Didn't find a relation, so no need for locking or permission checks. */
21862 278 : if (!OidIsValid(relOid))
21863 4 : return;
21864 :
21865 274 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
21866 274 : if (!HeapTupleIsValid(tuple))
21867 0 : return; /* concurrently dropped, so nothing to do */
21868 274 : classform = (Form_pg_class) GETSTRUCT(tuple);
21869 274 : if (classform->relkind != RELKIND_PARTITIONED_INDEX &&
21870 207 : classform->relkind != RELKIND_INDEX)
21871 4 : ereport(ERROR,
21872 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
21873 : errmsg("\"%s\" is not an index", rv->relname)));
21874 270 : ReleaseSysCache(tuple);
21875 :
21876 : /*
21877 : * Since we need only examine the heap's tupledesc, an access share lock
21878 : * on it (preventing any DDL) is sufficient.
21879 : */
21880 270 : state->partitionOid = IndexGetRelation(relOid, false);
21881 270 : LockRelationOid(state->partitionOid, AccessShareLock);
21882 : }
21883 :
21884 : /*
21885 : * ALTER INDEX i1 ATTACH PARTITION i2
21886 : */
21887 : static ObjectAddress
21888 269 : ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
21889 : {
21890 : Relation partIdx;
21891 : Relation partTbl;
21892 : Relation parentTbl;
21893 : ObjectAddress address;
21894 : Oid partIdxId;
21895 : Oid currParent;
21896 : struct AttachIndexCallbackState state;
21897 :
21898 : /*
21899 : * We need to obtain lock on the index 'name' to modify it, but we also
21900 : * need to read its owning table's tuple descriptor -- so we need to lock
21901 : * both. To avoid deadlocks, obtain lock on the table before doing so on
21902 : * the index. Furthermore, we need to examine the parent table of the
21903 : * partition, so lock that one too.
21904 : */
21905 269 : state.partitionOid = InvalidOid;
21906 269 : state.parentTblOid = parentIdx->rd_index->indrelid;
21907 269 : state.lockedParentTbl = false;
21908 : partIdxId =
21909 269 : RangeVarGetRelidExtended(name, AccessExclusiveLock, 0,
21910 : RangeVarCallbackForAttachIndex,
21911 : &state);
21912 : /* Not there? */
21913 261 : if (!OidIsValid(partIdxId))
21914 0 : ereport(ERROR,
21915 : (errcode(ERRCODE_UNDEFINED_OBJECT),
21916 : errmsg("index \"%s\" does not exist", name->relname)));
21917 :
21918 : /* no deadlock risk: RangeVarGetRelidExtended already acquired the lock */
21919 261 : partIdx = relation_open(partIdxId, AccessExclusiveLock);
21920 :
21921 : /* we already hold locks on both tables, so this is safe: */
21922 261 : parentTbl = relation_open(parentIdx->rd_index->indrelid, AccessShareLock);
21923 261 : partTbl = relation_open(partIdx->rd_index->indrelid, NoLock);
21924 :
21925 261 : ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partIdx));
21926 :
21927 : /*
21928 : * Check if the index is already attached to the correct parent,
21929 : * ultimately attempting one round of validation if already the case.
21930 : */
21931 522 : currParent = partIdx->rd_rel->relispartition ?
21932 261 : get_partition_parent(partIdxId, false) : InvalidOid;
21933 261 : if (currParent != RelationGetRelid(parentIdx))
21934 : {
21935 : IndexInfo *childInfo;
21936 : IndexInfo *parentInfo;
21937 : AttrMap *attmap;
21938 : bool found;
21939 : int i;
21940 : PartitionDesc partDesc;
21941 : Oid constraintOid,
21942 237 : cldConstrId = InvalidOid;
21943 :
21944 : /*
21945 : * If this partition already has an index attached, refuse the
21946 : * operation.
21947 : */
21948 237 : refuseDupeIndexAttach(parentIdx, partIdx, partTbl);
21949 :
21950 233 : if (OidIsValid(currParent))
21951 0 : ereport(ERROR,
21952 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
21953 : errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
21954 : RelationGetRelationName(partIdx),
21955 : RelationGetRelationName(parentIdx)),
21956 : errdetail("Index \"%s\" is already attached to another index.",
21957 : RelationGetRelationName(partIdx))));
21958 :
21959 : /* Make sure it indexes a partition of the other index's table */
21960 233 : partDesc = RelationGetPartitionDesc(parentTbl, true);
21961 233 : found = false;
21962 352 : for (i = 0; i < partDesc->nparts; i++)
21963 : {
21964 348 : if (partDesc->oids[i] == state.partitionOid)
21965 : {
21966 229 : found = true;
21967 229 : break;
21968 : }
21969 : }
21970 233 : if (!found)
21971 4 : ereport(ERROR,
21972 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
21973 : errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
21974 : RelationGetRelationName(partIdx),
21975 : RelationGetRelationName(parentIdx)),
21976 : errdetail("Index \"%s\" is not an index on any partition of table \"%s\".",
21977 : RelationGetRelationName(partIdx),
21978 : RelationGetRelationName(parentTbl))));
21979 :
21980 : /* Ensure the indexes are compatible */
21981 229 : childInfo = BuildIndexInfo(partIdx);
21982 229 : parentInfo = BuildIndexInfo(parentIdx);
21983 229 : attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
21984 : RelationGetDescr(parentTbl),
21985 : false);
21986 229 : if (!CompareIndexInfo(childInfo, parentInfo,
21987 229 : partIdx->rd_indcollation,
21988 229 : parentIdx->rd_indcollation,
21989 229 : partIdx->rd_opfamily,
21990 229 : parentIdx->rd_opfamily,
21991 : attmap))
21992 28 : ereport(ERROR,
21993 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
21994 : errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
21995 : RelationGetRelationName(partIdx),
21996 : RelationGetRelationName(parentIdx)),
21997 : errdetail("The index definitions do not match.")));
21998 :
21999 : /*
22000 : * If there is a constraint in the parent, make sure there is one in
22001 : * the child too.
22002 : */
22003 201 : constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(parentTbl),
22004 : RelationGetRelid(parentIdx));
22005 :
22006 201 : if (OidIsValid(constraintOid))
22007 : {
22008 65 : cldConstrId = get_relation_idx_constraint_oid(RelationGetRelid(partTbl),
22009 : partIdxId);
22010 65 : if (!OidIsValid(cldConstrId))
22011 4 : ereport(ERROR,
22012 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
22013 : errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
22014 : RelationGetRelationName(partIdx),
22015 : RelationGetRelationName(parentIdx)),
22016 : errdetail("The index \"%s\" belongs to a constraint in table \"%s\" but no constraint exists for index \"%s\".",
22017 : RelationGetRelationName(parentIdx),
22018 : RelationGetRelationName(parentTbl),
22019 : RelationGetRelationName(partIdx))));
22020 : }
22021 :
22022 : /*
22023 : * If it's a primary key, make sure the columns in the partition are
22024 : * NOT NULL.
22025 : */
22026 197 : if (parentIdx->rd_index->indisprimary)
22027 55 : verifyPartitionIndexNotNull(childInfo, partTbl);
22028 :
22029 : /* All good -- do it */
22030 197 : IndexSetParentIndex(partIdx, RelationGetRelid(parentIdx));
22031 197 : if (OidIsValid(constraintOid))
22032 61 : ConstraintSetParentConstraint(cldConstrId, constraintOid,
22033 : RelationGetRelid(partTbl));
22034 :
22035 197 : free_attrmap(attmap);
22036 :
22037 197 : validatePartitionedIndex(parentIdx, parentTbl);
22038 : }
22039 24 : else if (!parentIdx->rd_index->indisvalid)
22040 : {
22041 : /*
22042 : * The index is attached, but the parent is still invalid; see if it
22043 : * can be validated now.
22044 : */
22045 12 : validatePartitionedIndex(parentIdx, parentTbl);
22046 : }
22047 :
22048 221 : relation_close(parentTbl, AccessShareLock);
22049 : /* keep these locks till commit */
22050 221 : relation_close(partTbl, NoLock);
22051 221 : relation_close(partIdx, NoLock);
22052 :
22053 221 : return address;
22054 : }
22055 :
22056 : /*
22057 : * Verify whether the given partition already contains an index attached
22058 : * to the given partitioned index. If so, raise an error.
22059 : */
22060 : static void
22061 237 : refuseDupeIndexAttach(Relation parentIdx, Relation partIdx, Relation partitionTbl)
22062 : {
22063 : Oid existingIdx;
22064 :
22065 237 : existingIdx = index_get_partition(partitionTbl,
22066 : RelationGetRelid(parentIdx));
22067 237 : if (OidIsValid(existingIdx))
22068 4 : ereport(ERROR,
22069 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
22070 : errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
22071 : RelationGetRelationName(partIdx),
22072 : RelationGetRelationName(parentIdx)),
22073 : errdetail("Another index \"%s\" is already attached for partition \"%s\".",
22074 : get_rel_name(existingIdx),
22075 : RelationGetRelationName(partitionTbl))));
22076 233 : }
22077 :
22078 : /*
22079 : * Verify whether the set of attached partition indexes to a parent index on
22080 : * a partitioned table is complete. If it is, mark the parent index valid.
22081 : *
22082 : * This should be called each time a partition index is attached.
22083 : */
22084 : static void
22085 241 : validatePartitionedIndex(Relation partedIdx, Relation partedTbl)
22086 : {
22087 : Relation inheritsRel;
22088 : SysScanDesc scan;
22089 : ScanKeyData key;
22090 241 : int tuples = 0;
22091 : HeapTuple inhTup;
22092 241 : bool updated = false;
22093 :
22094 : Assert(partedIdx->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
22095 :
22096 : /*
22097 : * Scan pg_inherits for this parent index. Count each valid index we find
22098 : * (verifying the pg_index entry for each), and if we reach the total
22099 : * amount we expect, we can mark this parent index as valid.
22100 : */
22101 241 : inheritsRel = table_open(InheritsRelationId, AccessShareLock);
22102 241 : ScanKeyInit(&key, Anum_pg_inherits_inhparent,
22103 : BTEqualStrategyNumber, F_OIDEQ,
22104 : ObjectIdGetDatum(RelationGetRelid(partedIdx)));
22105 241 : scan = systable_beginscan(inheritsRel, InheritsParentIndexId, true,
22106 : NULL, 1, &key);
22107 611 : while ((inhTup = systable_getnext(scan)) != NULL)
22108 : {
22109 370 : Form_pg_inherits inhForm = (Form_pg_inherits) GETSTRUCT(inhTup);
22110 : HeapTuple indTup;
22111 : Form_pg_index indexForm;
22112 :
22113 370 : indTup = SearchSysCache1(INDEXRELID,
22114 : ObjectIdGetDatum(inhForm->inhrelid));
22115 370 : if (!HeapTupleIsValid(indTup))
22116 0 : elog(ERROR, "cache lookup failed for index %u", inhForm->inhrelid);
22117 370 : indexForm = (Form_pg_index) GETSTRUCT(indTup);
22118 370 : if (indexForm->indisvalid)
22119 304 : tuples += 1;
22120 370 : ReleaseSysCache(indTup);
22121 : }
22122 :
22123 : /* Done with pg_inherits */
22124 241 : systable_endscan(scan);
22125 241 : table_close(inheritsRel, AccessShareLock);
22126 :
22127 : /*
22128 : * If we found as many inherited indexes as the partitioned table has
22129 : * partitions, we're good; update pg_index to set indisvalid.
22130 : */
22131 241 : if (tuples == RelationGetPartitionDesc(partedTbl, true)->nparts)
22132 : {
22133 : Relation idxRel;
22134 : HeapTuple indTup;
22135 : Form_pg_index indexForm;
22136 :
22137 114 : idxRel = table_open(IndexRelationId, RowExclusiveLock);
22138 114 : indTup = SearchSysCacheCopy1(INDEXRELID,
22139 : ObjectIdGetDatum(RelationGetRelid(partedIdx)));
22140 114 : if (!HeapTupleIsValid(indTup))
22141 0 : elog(ERROR, "cache lookup failed for index %u",
22142 : RelationGetRelid(partedIdx));
22143 114 : indexForm = (Form_pg_index) GETSTRUCT(indTup);
22144 :
22145 114 : indexForm->indisvalid = true;
22146 114 : updated = true;
22147 :
22148 114 : CatalogTupleUpdate(idxRel, &indTup->t_self, indTup);
22149 :
22150 114 : table_close(idxRel, RowExclusiveLock);
22151 114 : heap_freetuple(indTup);
22152 : }
22153 :
22154 : /*
22155 : * If this index is in turn a partition of a larger index, validating it
22156 : * might cause the parent to become valid also. Try that.
22157 : */
22158 241 : if (updated && partedIdx->rd_rel->relispartition)
22159 : {
22160 : Oid parentIdxId,
22161 : parentTblId;
22162 : Relation parentIdx,
22163 : parentTbl;
22164 :
22165 : /* make sure we see the validation we just did */
22166 32 : CommandCounterIncrement();
22167 :
22168 32 : parentIdxId = get_partition_parent(RelationGetRelid(partedIdx), false);
22169 32 : parentTblId = get_partition_parent(RelationGetRelid(partedTbl), false);
22170 32 : parentIdx = relation_open(parentIdxId, AccessExclusiveLock);
22171 32 : parentTbl = relation_open(parentTblId, AccessExclusiveLock);
22172 : Assert(!parentIdx->rd_index->indisvalid);
22173 :
22174 32 : validatePartitionedIndex(parentIdx, parentTbl);
22175 :
22176 32 : relation_close(parentIdx, AccessExclusiveLock);
22177 32 : relation_close(parentTbl, AccessExclusiveLock);
22178 : }
22179 241 : }
22180 :
22181 : /*
22182 : * When attaching an index as a partition of a partitioned index which is a
22183 : * primary key, verify that all the columns in the partition are marked NOT
22184 : * NULL.
22185 : */
22186 : static void
22187 55 : verifyPartitionIndexNotNull(IndexInfo *iinfo, Relation partition)
22188 : {
22189 111 : for (int i = 0; i < iinfo->ii_NumIndexKeyAttrs; i++)
22190 : {
22191 56 : Form_pg_attribute att = TupleDescAttr(RelationGetDescr(partition),
22192 56 : iinfo->ii_IndexAttrNumbers[i] - 1);
22193 :
22194 56 : if (!att->attnotnull)
22195 0 : ereport(ERROR,
22196 : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
22197 : errmsg("invalid primary key definition"),
22198 : errdetail("Column \"%s\" of relation \"%s\" is not marked NOT NULL.",
22199 : NameStr(att->attname),
22200 : RelationGetRelationName(partition)));
22201 : }
22202 55 : }
22203 :
22204 : /*
22205 : * Return an OID list of constraints that reference the given relation
22206 : * that are marked as having a parent constraints.
22207 : */
22208 : static List *
22209 1085 : GetParentedForeignKeyRefs(Relation partition)
22210 : {
22211 : Relation pg_constraint;
22212 : HeapTuple tuple;
22213 : SysScanDesc scan;
22214 : ScanKeyData key[2];
22215 1085 : List *constraints = NIL;
22216 :
22217 : /*
22218 : * If no indexes, or no columns are referenceable by FKs, we can avoid the
22219 : * scan.
22220 : */
22221 1517 : if (RelationGetIndexList(partition) == NIL ||
22222 432 : bms_is_empty(RelationGetIndexAttrBitmap(partition,
22223 : INDEX_ATTR_BITMAP_KEY)))
22224 826 : return NIL;
22225 :
22226 : /* Search for constraints referencing this table */
22227 259 : pg_constraint = table_open(ConstraintRelationId, AccessShareLock);
22228 259 : ScanKeyInit(&key[0],
22229 : Anum_pg_constraint_confrelid, BTEqualStrategyNumber,
22230 : F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(partition)));
22231 259 : ScanKeyInit(&key[1],
22232 : Anum_pg_constraint_contype, BTEqualStrategyNumber,
22233 : F_CHAREQ, CharGetDatum(CONSTRAINT_FOREIGN));
22234 :
22235 : /* XXX This is a seqscan, as we don't have a usable index */
22236 259 : scan = systable_beginscan(pg_constraint, InvalidOid, true, NULL, 2, key);
22237 380 : while ((tuple = systable_getnext(scan)) != NULL)
22238 : {
22239 121 : Form_pg_constraint constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
22240 :
22241 : /*
22242 : * We only need to process constraints that are part of larger ones.
22243 : */
22244 121 : if (!OidIsValid(constrForm->conparentid))
22245 0 : continue;
22246 :
22247 121 : constraints = lappend_oid(constraints, constrForm->oid);
22248 : }
22249 :
22250 259 : systable_endscan(scan);
22251 259 : table_close(pg_constraint, AccessShareLock);
22252 :
22253 259 : return constraints;
22254 : }
22255 :
22256 : /*
22257 : * During DETACH PARTITION, verify that any foreign keys pointing to the
22258 : * partitioned table would not become invalid. An error is raised if any
22259 : * referenced values exist.
22260 : */
22261 : static void
22262 344 : ATDetachCheckNoForeignKeyRefs(Relation partition)
22263 : {
22264 : List *constraints;
22265 : ListCell *cell;
22266 :
22267 344 : constraints = GetParentedForeignKeyRefs(partition);
22268 :
22269 387 : foreach(cell, constraints)
22270 : {
22271 65 : Oid constrOid = lfirst_oid(cell);
22272 : HeapTuple tuple;
22273 : Form_pg_constraint constrForm;
22274 : Relation rel;
22275 65 : Trigger trig = {0};
22276 :
22277 65 : tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
22278 65 : if (!HeapTupleIsValid(tuple))
22279 0 : elog(ERROR, "cache lookup failed for constraint %u", constrOid);
22280 65 : constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
22281 :
22282 : Assert(OidIsValid(constrForm->conparentid));
22283 : Assert(constrForm->confrelid == RelationGetRelid(partition));
22284 :
22285 : /* prevent data changes into the referencing table until commit */
22286 65 : rel = table_open(constrForm->conrelid, ShareLock);
22287 :
22288 65 : trig.tgoid = InvalidOid;
22289 65 : trig.tgname = NameStr(constrForm->conname);
22290 65 : trig.tgenabled = TRIGGER_FIRES_ON_ORIGIN;
22291 65 : trig.tgisinternal = true;
22292 65 : trig.tgconstrrelid = RelationGetRelid(partition);
22293 65 : trig.tgconstrindid = constrForm->conindid;
22294 65 : trig.tgconstraint = constrForm->oid;
22295 65 : trig.tgdeferrable = false;
22296 65 : trig.tginitdeferred = false;
22297 : /* we needn't fill in remaining fields */
22298 :
22299 65 : RI_PartitionRemove_Check(&trig, rel, partition);
22300 :
22301 43 : ReleaseSysCache(tuple);
22302 :
22303 43 : table_close(rel, NoLock);
22304 : }
22305 322 : }
22306 :
22307 : /*
22308 : * resolve column compression specification to compression method.
22309 : */
22310 : static char
22311 170448 : GetAttributeCompression(Oid atttypid, const char *compression)
22312 : {
22313 : char cmethod;
22314 :
22315 170448 : if (compression == NULL || strcmp(compression, "default") == 0)
22316 170309 : return InvalidCompressionMethod;
22317 :
22318 : /*
22319 : * To specify a nondefault method, the column data type must be toastable.
22320 : * Note this says nothing about whether the column's attstorage setting
22321 : * permits compression; we intentionally allow attstorage and
22322 : * attcompression to be independent. But with a non-toastable type,
22323 : * attstorage could not be set to a value that would permit compression.
22324 : *
22325 : * We don't actually need to enforce this, since nothing bad would happen
22326 : * if attcompression were non-default; it would never be consulted. But
22327 : * it seems more user-friendly to complain about a certainly-useless
22328 : * attempt to set the property.
22329 : */
22330 139 : if (!TypeIsToastable(atttypid))
22331 4 : ereport(ERROR,
22332 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
22333 : errmsg("column data type %s does not support compression",
22334 : format_type_be(atttypid))));
22335 :
22336 135 : cmethod = CompressionNameToMethod(compression);
22337 135 : if (!CompressionMethodIsValid(cmethod))
22338 8 : ereport(ERROR,
22339 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
22340 : errmsg("invalid compression method \"%s\"", compression)));
22341 :
22342 127 : return cmethod;
22343 : }
22344 :
22345 : /*
22346 : * resolve column storage specification
22347 : */
22348 : static char
22349 212 : GetAttributeStorage(Oid atttypid, const char *storagemode)
22350 : {
22351 212 : char cstorage = 0;
22352 :
22353 212 : if (pg_strcasecmp(storagemode, "plain") == 0)
22354 37 : cstorage = TYPSTORAGE_PLAIN;
22355 175 : else if (pg_strcasecmp(storagemode, "external") == 0)
22356 112 : cstorage = TYPSTORAGE_EXTERNAL;
22357 63 : else if (pg_strcasecmp(storagemode, "extended") == 0)
22358 26 : cstorage = TYPSTORAGE_EXTENDED;
22359 37 : else if (pg_strcasecmp(storagemode, "main") == 0)
22360 33 : cstorage = TYPSTORAGE_MAIN;
22361 4 : else if (pg_strcasecmp(storagemode, "default") == 0)
22362 4 : cstorage = get_typstorage(atttypid);
22363 : else
22364 0 : ereport(ERROR,
22365 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
22366 : errmsg("invalid storage type \"%s\"",
22367 : storagemode)));
22368 :
22369 : /*
22370 : * safety check: do not allow toasted storage modes unless column datatype
22371 : * is TOAST-aware.
22372 : */
22373 212 : if (!(cstorage == TYPSTORAGE_PLAIN || TypeIsToastable(atttypid)))
22374 4 : ereport(ERROR,
22375 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
22376 : errmsg("column data type %s can only have storage PLAIN",
22377 : format_type_be(atttypid))));
22378 :
22379 208 : return cstorage;
22380 : }
22381 :
22382 : /*
22383 : * buildExpressionExecutionStates: build the needed expression execution states
22384 : * for new partition (newPartRel) checks and initialize expressions for
22385 : * generated columns. All expressions should be created in "tab"
22386 : * (AlteredTableInfo structure).
22387 : */
22388 : static void
22389 480 : buildExpressionExecutionStates(AlteredTableInfo *tab, Relation newPartRel, EState *estate)
22390 : {
22391 : /*
22392 : * Build the needed expression execution states. Here, we expect only NOT
22393 : * NULL and CHECK constraint.
22394 : */
22395 976 : foreach_ptr(NewConstraint, con, tab->constraints)
22396 : {
22397 16 : switch (con->contype)
22398 : {
22399 16 : case CONSTR_CHECK:
22400 :
22401 : /*
22402 : * We already expanded virtual expression in
22403 : * createTableConstraints.
22404 : */
22405 16 : con->qualstate = ExecPrepareExpr((Expr *) con->qual, estate);
22406 16 : break;
22407 0 : case CONSTR_NOTNULL:
22408 : /* Nothing to do here. */
22409 0 : break;
22410 0 : default:
22411 0 : elog(ERROR, "unrecognized constraint type: %d",
22412 : (int) con->contype);
22413 : }
22414 : }
22415 :
22416 : /* Expression already planned in createTableConstraints */
22417 1004 : foreach_ptr(NewColumnValue, ex, tab->newvals)
22418 44 : ex->exprstate = ExecInitExpr((Expr *) ex->expr, NULL);
22419 480 : }
22420 :
22421 : /*
22422 : * evaluateGeneratedExpressionsAndCheckConstraints: evaluate any generated
22423 : * expressions for "tab" (AlteredTableInfo structure) whose inputs come from
22424 : * the new tuple (insertslot) of the new partition (newPartRel).
22425 : */
22426 : static void
22427 912 : evaluateGeneratedExpressionsAndCheckConstraints(AlteredTableInfo *tab,
22428 : Relation newPartRel,
22429 : TupleTableSlot *insertslot,
22430 : ExprContext *econtext)
22431 : {
22432 912 : econtext->ecxt_scantuple = insertslot;
22433 :
22434 1888 : foreach_ptr(NewColumnValue, ex, tab->newvals)
22435 : {
22436 64 : if (!ex->is_generated)
22437 0 : continue;
22438 :
22439 64 : insertslot->tts_values[ex->attnum - 1]
22440 64 : = ExecEvalExpr(ex->exprstate,
22441 : econtext,
22442 64 : &insertslot->tts_isnull[ex->attnum - 1]);
22443 : }
22444 :
22445 1848 : foreach_ptr(NewConstraint, con, tab->constraints)
22446 : {
22447 24 : switch (con->contype)
22448 : {
22449 24 : case CONSTR_CHECK:
22450 24 : if (!ExecCheck(con->qualstate, econtext))
22451 0 : ereport(ERROR,
22452 : errcode(ERRCODE_CHECK_VIOLATION),
22453 : errmsg("check constraint \"%s\" of relation \"%s\" is violated by some row",
22454 : con->name, RelationGetRelationName(newPartRel)),
22455 : errtableconstraint(newPartRel, con->name));
22456 24 : break;
22457 0 : case CONSTR_NOTNULL:
22458 : case CONSTR_FOREIGN:
22459 : /* Nothing to do here */
22460 0 : break;
22461 0 : default:
22462 0 : elog(ERROR, "unrecognized constraint type: %d",
22463 : (int) con->contype);
22464 : }
22465 : }
22466 912 : }
22467 :
22468 : /*
22469 : * getAttributesList: build a list of columns (ColumnDef) based on parent_rel
22470 : */
22471 : static List *
22472 508 : getAttributesList(Relation parent_rel)
22473 : {
22474 : AttrNumber parent_attno;
22475 : TupleDesc modelDesc;
22476 508 : List *colList = NIL;
22477 :
22478 508 : modelDesc = RelationGetDescr(parent_rel);
22479 :
22480 1721 : for (parent_attno = 1; parent_attno <= modelDesc->natts;
22481 1213 : parent_attno++)
22482 : {
22483 1213 : Form_pg_attribute attribute = TupleDescAttr(modelDesc,
22484 : parent_attno - 1);
22485 : ColumnDef *def;
22486 :
22487 : /* Ignore dropped columns in the parent. */
22488 1213 : if (attribute->attisdropped)
22489 0 : continue;
22490 :
22491 1213 : def = makeColumnDef(NameStr(attribute->attname), attribute->atttypid,
22492 : attribute->atttypmod, attribute->attcollation);
22493 :
22494 1213 : def->is_not_null = attribute->attnotnull;
22495 :
22496 : /* Copy identity. */
22497 1213 : def->identity = attribute->attidentity;
22498 :
22499 : /* Copy attgenerated. */
22500 1213 : def->generated = attribute->attgenerated;
22501 :
22502 1213 : def->storage = attribute->attstorage;
22503 :
22504 : /* Likewise, copy compression. */
22505 1213 : if (CompressionMethodIsValid(attribute->attcompression))
22506 12 : def->compression =
22507 12 : pstrdup(GetCompressionMethodName(attribute->attcompression));
22508 : else
22509 1201 : def->compression = NULL;
22510 :
22511 : /* Add to column list. */
22512 1213 : colList = lappend(colList, def);
22513 : }
22514 :
22515 508 : return colList;
22516 : }
22517 :
22518 : /*
22519 : * createTableConstraints:
22520 : * create check constraints, default values, and generated values for newRel
22521 : * based on parent_rel. tab is pending-work queue for newRel, we may need it in
22522 : * MergePartitionsMoveRows.
22523 : */
22524 : static void
22525 480 : createTableConstraints(List **wqueue, AlteredTableInfo *tab,
22526 : Relation parent_rel, Relation newRel)
22527 : {
22528 : TupleDesc tupleDesc;
22529 : TupleConstr *constr;
22530 : AttrMap *attmap;
22531 : AttrNumber parent_attno;
22532 : int ccnum;
22533 480 : List *constraints = NIL;
22534 480 : List *cookedConstraints = NIL;
22535 :
22536 480 : tupleDesc = RelationGetDescr(parent_rel);
22537 480 : constr = tupleDesc->constr;
22538 :
22539 480 : if (!constr)
22540 324 : return;
22541 :
22542 : /*
22543 : * Construct a map from the parent relation's attnos to the child rel's.
22544 : * This re-checks type match, etc, although it shouldn't be possible to
22545 : * have a failure since both tables are locked.
22546 : */
22547 156 : attmap = build_attrmap_by_name(RelationGetDescr(newRel),
22548 : tupleDesc,
22549 : false);
22550 :
22551 : /* Cycle for default values. */
22552 592 : for (parent_attno = 1; parent_attno <= tupleDesc->natts; parent_attno++)
22553 : {
22554 436 : Form_pg_attribute attribute = TupleDescAttr(tupleDesc,
22555 : parent_attno - 1);
22556 :
22557 : /* Ignore dropped columns in the parent. */
22558 436 : if (attribute->attisdropped)
22559 0 : continue;
22560 :
22561 : /* Copy the default, if present, and it should be copied. */
22562 436 : if (attribute->atthasdef)
22563 : {
22564 100 : Node *this_default = NULL;
22565 : bool found_whole_row;
22566 : AttrNumber num;
22567 : Node *def;
22568 : NewColumnValue *newval;
22569 :
22570 100 : if (attribute->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
22571 4 : this_default = build_generation_expression(parent_rel, attribute->attnum);
22572 : else
22573 : {
22574 96 : this_default = TupleDescGetDefault(tupleDesc, attribute->attnum);
22575 96 : if (this_default == NULL)
22576 0 : elog(ERROR, "default expression not found for attribute %d of relation \"%s\"",
22577 : attribute->attnum, RelationGetRelationName(parent_rel));
22578 : }
22579 :
22580 100 : num = attmap->attnums[parent_attno - 1];
22581 100 : def = map_variable_attnos(this_default, 1, 0, attmap, InvalidOid, &found_whole_row);
22582 :
22583 100 : if (found_whole_row && attribute->attgenerated != '\0')
22584 0 : elog(ERROR, "cannot convert whole-row table reference");
22585 :
22586 : /* Add a pre-cooked default expression. */
22587 100 : StoreAttrDefault(newRel, num, def, true);
22588 :
22589 : /*
22590 : * Stored generated column expressions in parent_rel might
22591 : * reference the tableoid. newRel, parent_rel tableoid clear is
22592 : * not the same. If so, these stored generated columns require
22593 : * recomputation for newRel within MergePartitionsMoveRows.
22594 : */
22595 100 : if (attribute->attgenerated == ATTRIBUTE_GENERATED_STORED)
22596 : {
22597 44 : newval = palloc0_object(NewColumnValue);
22598 44 : newval->attnum = num;
22599 44 : newval->expr = expression_planner((Expr *) def);
22600 44 : newval->is_generated = (attribute->attgenerated != '\0');
22601 44 : tab->newvals = lappend(tab->newvals, newval);
22602 : }
22603 : }
22604 : }
22605 :
22606 : /* Cycle for CHECK constraints. */
22607 224 : for (ccnum = 0; ccnum < constr->num_check; ccnum++)
22608 : {
22609 68 : char *ccname = constr->check[ccnum].ccname;
22610 68 : char *ccbin = constr->check[ccnum].ccbin;
22611 68 : bool ccenforced = constr->check[ccnum].ccenforced;
22612 68 : bool ccnoinherit = constr->check[ccnum].ccnoinherit;
22613 68 : bool ccvalid = constr->check[ccnum].ccvalid;
22614 : Node *ccbin_node;
22615 : bool found_whole_row;
22616 : Constraint *con;
22617 :
22618 : /*
22619 : * The partitioned table can not have a NO INHERIT check constraint
22620 : * (see StoreRelCheck function for details).
22621 : */
22622 : Assert(!ccnoinherit);
22623 :
22624 68 : ccbin_node = map_variable_attnos(stringToNode(ccbin),
22625 : 1, 0,
22626 : attmap,
22627 : InvalidOid, &found_whole_row);
22628 :
22629 : /*
22630 : * For the moment we have to reject whole-row variables (as for CREATE
22631 : * TABLE LIKE and inheritances).
22632 : */
22633 68 : if (found_whole_row)
22634 0 : elog(ERROR, "Constraint \"%s\" contains a whole-row reference to table \"%s\".",
22635 : ccname,
22636 : RelationGetRelationName(parent_rel));
22637 :
22638 68 : con = makeNode(Constraint);
22639 68 : con->contype = CONSTR_CHECK;
22640 68 : con->conname = pstrdup(ccname);
22641 68 : con->deferrable = false;
22642 68 : con->initdeferred = false;
22643 68 : con->is_enforced = ccenforced;
22644 68 : con->skip_validation = !ccvalid;
22645 68 : con->initially_valid = ccvalid;
22646 68 : con->is_no_inherit = ccnoinherit;
22647 68 : con->raw_expr = NULL;
22648 68 : con->cooked_expr = nodeToString(ccbin_node);
22649 68 : con->location = -1;
22650 68 : constraints = lappend(constraints, con);
22651 : }
22652 :
22653 : /* Install all CHECK constraints. */
22654 156 : cookedConstraints = AddRelationNewConstraints(newRel, NIL, constraints,
22655 : false, true, true, NULL);
22656 :
22657 : /* Make the additional catalog changes visible. */
22658 156 : CommandCounterIncrement();
22659 :
22660 : /*
22661 : * parent_rel check constraint expression may reference tableoid, so later
22662 : * in MergePartitionsMoveRows, we need to evaluate the check constraint
22663 : * again for the newRel. We can check whether the check constraint
22664 : * contains a tableoid reference via pull_varattnos.
22665 : */
22666 380 : foreach_ptr(CookedConstraint, ccon, cookedConstraints)
22667 : {
22668 68 : if (!ccon->skip_validation)
22669 : {
22670 : Node *qual;
22671 44 : Bitmapset *attnums = NULL;
22672 :
22673 : Assert(ccon->contype == CONSTR_CHECK);
22674 44 : qual = expand_generated_columns_in_expr(ccon->expr, newRel, 1);
22675 44 : pull_varattnos(qual, 1, &attnums);
22676 :
22677 : /*
22678 : * Add a check only if it contains a tableoid
22679 : * (TableOidAttributeNumber).
22680 : */
22681 44 : if (bms_is_member(TableOidAttributeNumber - FirstLowInvalidHeapAttributeNumber,
22682 : attnums))
22683 : {
22684 : NewConstraint *newcon;
22685 :
22686 16 : newcon = palloc0_object(NewConstraint);
22687 16 : newcon->name = ccon->name;
22688 16 : newcon->contype = CONSTR_CHECK;
22689 16 : newcon->qual = qual;
22690 :
22691 16 : tab->constraints = lappend(tab->constraints, newcon);
22692 : }
22693 : }
22694 : }
22695 :
22696 : /* Don't need the cookedConstraints anymore. */
22697 156 : list_free_deep(cookedConstraints);
22698 :
22699 : /* Reproduce not-null constraints. */
22700 156 : if (constr->has_not_null)
22701 : {
22702 : List *nnconstraints;
22703 :
22704 : /*
22705 : * The "include_noinh" argument is false because a partitioned table
22706 : * can't have NO INHERIT constraint.
22707 : */
22708 108 : nnconstraints = RelationGetNotNullConstraints(RelationGetRelid(parent_rel),
22709 : false, false);
22710 :
22711 : Assert(list_length(nnconstraints) > 0);
22712 :
22713 : /*
22714 : * We already set pg_attribute.attnotnull in createPartitionTable. No
22715 : * need call set_attnotnull again.
22716 : */
22717 108 : AddRelationNewConstraints(newRel, NIL, nnconstraints, false, true, true, NULL);
22718 : }
22719 : }
22720 :
22721 : /*
22722 : * createPartitionTable:
22723 : *
22724 : * Create a new partition (newPartName) for the partitioned table (parent_rel).
22725 : * ownerId is determined by the partition on which the operation is performed,
22726 : * so it is passed separately. The new partition will inherit the access method
22727 : * and persistence type from the parent table.
22728 : *
22729 : * Returns the created relation (locked in AccessExclusiveLock mode).
22730 : */
22731 : static Relation
22732 508 : createPartitionTable(List **wqueue, RangeVar *newPartName,
22733 : Relation parent_rel, Oid ownerId)
22734 : {
22735 : Relation newRel;
22736 : Oid newRelId;
22737 : Oid existingRelid;
22738 : Oid tablespaceId;
22739 : TupleDesc descriptor;
22740 508 : List *colList = NIL;
22741 : Oid relamId;
22742 : Oid namespaceId;
22743 : AlteredTableInfo *new_partrel_tab;
22744 508 : Form_pg_class parent_relform = parent_rel->rd_rel;
22745 :
22746 : /* If the existing rel is temp, it must belong to this session. */
22747 508 : if (RELATION_IS_OTHER_TEMP(parent_rel))
22748 0 : ereport(ERROR,
22749 : errcode(ERRCODE_WRONG_OBJECT_TYPE),
22750 : errmsg("cannot create as partition of temporary relation of another session"));
22751 :
22752 : /* Look up inheritance ancestors and generate the relation schema. */
22753 508 : colList = getAttributesList(parent_rel);
22754 :
22755 : /* Create a tuple descriptor from the relation schema. */
22756 508 : descriptor = BuildDescForRelation(colList);
22757 :
22758 : /* Look up the access method for the new relation. */
22759 508 : relamId = (parent_relform->relam != InvalidOid) ? parent_relform->relam : HEAP_TABLE_AM_OID;
22760 :
22761 : /* Look up the namespace in which we are supposed to create the relation. */
22762 : namespaceId =
22763 508 : RangeVarGetAndCheckCreationNamespace(newPartName, NoLock, &existingRelid);
22764 508 : if (OidIsValid(existingRelid))
22765 0 : ereport(ERROR,
22766 : errcode(ERRCODE_DUPLICATE_TABLE),
22767 : errmsg("relation \"%s\" already exists", newPartName->relname));
22768 :
22769 : /*
22770 : * We intended to create the partition with the same persistence as the
22771 : * parent table, but we still need to recheck because that might be
22772 : * affected by the search_path. If the parent is permanent, so must be
22773 : * all of its partitions.
22774 : */
22775 508 : if (parent_relform->relpersistence != RELPERSISTENCE_TEMP &&
22776 472 : newPartName->relpersistence == RELPERSISTENCE_TEMP)
22777 8 : ereport(ERROR,
22778 : errcode(ERRCODE_WRONG_OBJECT_TYPE),
22779 : errmsg("cannot create a temporary relation as partition of permanent relation \"%s\"",
22780 : RelationGetRelationName(parent_rel)));
22781 :
22782 : /* Permanent rels cannot be partitions belonging to a temporary parent. */
22783 500 : if (newPartName->relpersistence != RELPERSISTENCE_TEMP &&
22784 476 : parent_relform->relpersistence == RELPERSISTENCE_TEMP)
22785 12 : ereport(ERROR,
22786 : errcode(ERRCODE_WRONG_OBJECT_TYPE),
22787 : errmsg("cannot create a permanent relation as partition of temporary relation \"%s\"",
22788 : RelationGetRelationName(parent_rel)));
22789 :
22790 : /*
22791 : * Select the tablespace for the new partition. Mirror the logic that
22792 : * CREATE TABLE foo PARTITION OF ... uses in DefineRelation: take the
22793 : * partitioned parent's explicit tablespace if it has one, otherwise take
22794 : * default_tablespace into account, and finally use the database default.
22795 : */
22796 488 : tablespaceId = parent_relform->reltablespace;
22797 488 : if (!OidIsValid(tablespaceId))
22798 464 : tablespaceId = GetDefaultTablespace(newPartName->relpersistence, false);
22799 :
22800 : /* Check permissions except when using database's default */
22801 488 : if (OidIsValid(tablespaceId) && tablespaceId != MyDatabaseTableSpace)
22802 : {
22803 : AclResult aclresult;
22804 :
22805 44 : aclresult = object_aclcheck(TableSpaceRelationId, tablespaceId,
22806 : GetUserId(), ACL_CREATE);
22807 44 : if (aclresult != ACLCHECK_OK)
22808 0 : aclcheck_error(aclresult, OBJECT_TABLESPACE,
22809 0 : get_tablespace_name(tablespaceId));
22810 : }
22811 :
22812 : /* In all cases disallow placing user relations in pg_global */
22813 488 : if (tablespaceId == GLOBALTABLESPACE_OID)
22814 8 : ereport(ERROR,
22815 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
22816 : errmsg("only shared relations can be placed in pg_global tablespace")));
22817 :
22818 : /* Create the relation. */
22819 480 : newRelId = heap_create_with_catalog(newPartName->relname,
22820 : namespaceId,
22821 : tablespaceId,
22822 : InvalidOid,
22823 : InvalidOid,
22824 : InvalidOid,
22825 : ownerId,
22826 : relamId,
22827 : descriptor,
22828 : NIL,
22829 : RELKIND_RELATION,
22830 480 : newPartName->relpersistence,
22831 : false,
22832 : false,
22833 : ONCOMMIT_NOOP,
22834 : (Datum) 0,
22835 : true,
22836 : allowSystemTableMods,
22837 : true,
22838 : InvalidOid,
22839 : NULL);
22840 :
22841 : /*
22842 : * We must bump the command counter to make the newly-created relation
22843 : * tuple visible for opening.
22844 : */
22845 480 : CommandCounterIncrement();
22846 :
22847 : /*
22848 : * Create a TOAST table if the table needs one. MERGE/SPLIT PARTITION
22849 : * moves rows from existing partition(s) into new partition(s), which may
22850 : * carry out-of-line varlena values that the new relation must be able to
22851 : * store. Also, the new partition must be able to receive out-of-line
22852 : * varlena values after the DDL operation is complete.
22853 : */
22854 480 : NewRelationCreateToastTable(newRelId, (Datum) 0);
22855 :
22856 : /*
22857 : * Open the new partition with no lock, because we already have an
22858 : * AccessExclusiveLock placed there after creation.
22859 : */
22860 480 : newRel = table_open(newRelId, NoLock);
22861 :
22862 : /* Find or create a work queue entry for the newly created table. */
22863 480 : new_partrel_tab = ATGetQueueEntry(wqueue, newRel);
22864 :
22865 : /* Create constraints, default values, and generated values. */
22866 480 : createTableConstraints(wqueue, new_partrel_tab, parent_rel, newRel);
22867 :
22868 : /*
22869 : * Need to call CommandCounterIncrement, so a fresh relcache entry has
22870 : * newly installed constraint info.
22871 : */
22872 480 : CommandCounterIncrement();
22873 :
22874 480 : return newRel;
22875 : }
22876 :
22877 : /*
22878 : * MergePartitionsMoveRows: scan partitions to be merged (mergingPartitions)
22879 : * of the partitioned table and move rows into the new partition
22880 : * (newPartRel). We also verify check constraints against these rows.
22881 : */
22882 : static void
22883 106 : MergePartitionsMoveRows(List **wqueue, List *mergingPartitions, Relation newPartRel)
22884 : {
22885 : CommandId mycid;
22886 : EState *estate;
22887 : AlteredTableInfo *tab;
22888 : ListCell *ltab;
22889 :
22890 : /* The FSM is empty, so don't bother using it. */
22891 106 : uint32 ti_options = TABLE_INSERT_SKIP_FSM;
22892 : BulkInsertState bistate; /* state of bulk inserts for partition */
22893 : TupleTableSlot *dstslot;
22894 :
22895 : /* Find the work queue entry for the new partition table: newPartRel. */
22896 106 : tab = ATGetQueueEntry(wqueue, newPartRel);
22897 :
22898 : /* Generate the constraint and default execution states. */
22899 106 : estate = CreateExecutorState();
22900 :
22901 106 : buildExpressionExecutionStates(tab, newPartRel, estate);
22902 :
22903 106 : mycid = GetCurrentCommandId(true);
22904 :
22905 : /* Prepare a BulkInsertState for table_tuple_insert. */
22906 106 : bistate = GetBulkInsertState();
22907 :
22908 : /* Create the necessary tuple slot. */
22909 106 : dstslot = table_slot_create(newPartRel, NULL);
22910 :
22911 452 : foreach_oid(merging_oid, mergingPartitions)
22912 : {
22913 : ExprContext *econtext;
22914 : TupleTableSlot *srcslot;
22915 : TupleConversionMap *tuple_map;
22916 : TableScanDesc scan;
22917 : MemoryContext oldCxt;
22918 : Snapshot snapshot;
22919 : Relation mergingPartition;
22920 :
22921 240 : econtext = GetPerTupleExprContext(estate);
22922 :
22923 : /*
22924 : * Partition is already locked in the transformPartitionCmdForMerge
22925 : * function.
22926 : */
22927 240 : mergingPartition = table_open(merging_oid, NoLock);
22928 :
22929 : /* Create a source tuple slot for the partition being merged. */
22930 240 : srcslot = table_slot_create(mergingPartition, NULL);
22931 :
22932 : /*
22933 : * Map computing for moving attributes of the merged partition to the
22934 : * new partition.
22935 : */
22936 240 : tuple_map = convert_tuples_by_name(RelationGetDescr(mergingPartition),
22937 : RelationGetDescr(newPartRel));
22938 :
22939 : /* Scan through the rows. */
22940 240 : snapshot = RegisterSnapshot(GetLatestSnapshot());
22941 240 : scan = table_beginscan(mergingPartition, snapshot, 0, NULL,
22942 : SO_NONE);
22943 :
22944 : /*
22945 : * Switch to per-tuple memory context and reset it for each tuple
22946 : * produced, so we don't leak memory.
22947 : */
22948 240 : oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
22949 :
22950 605 : while (table_scan_getnextslot(scan, ForwardScanDirection, srcslot))
22951 : {
22952 : TupleTableSlot *insertslot;
22953 :
22954 365 : CHECK_FOR_INTERRUPTS();
22955 :
22956 365 : if (tuple_map)
22957 : {
22958 : /* Need to use a map to copy attributes. */
22959 28 : insertslot = execute_attr_map_slot(tuple_map->attrMap, srcslot, dstslot);
22960 : }
22961 : else
22962 : {
22963 337 : slot_getallattrs(srcslot);
22964 :
22965 : /* Copy attributes directly. */
22966 337 : insertslot = dstslot;
22967 :
22968 337 : ExecClearTuple(insertslot);
22969 :
22970 337 : memcpy(insertslot->tts_values, srcslot->tts_values,
22971 337 : sizeof(Datum) * srcslot->tts_nvalid);
22972 337 : memcpy(insertslot->tts_isnull, srcslot->tts_isnull,
22973 337 : sizeof(bool) * srcslot->tts_nvalid);
22974 :
22975 337 : ExecStoreVirtualTuple(insertslot);
22976 : }
22977 :
22978 : /*
22979 : * Constraints and GENERATED expressions might reference the
22980 : * tableoid column, so fill tts_tableOid with the desired value.
22981 : * (We must do this each time, because it gets overwritten with
22982 : * newrel's OID during storing.)
22983 : */
22984 365 : insertslot->tts_tableOid = RelationGetRelid(newPartRel);
22985 :
22986 : /*
22987 : * Now, evaluate any generated expressions whose inputs come from
22988 : * the new tuple. We assume these columns won't reference each
22989 : * other, so that there's no ordering dependency.
22990 : */
22991 365 : evaluateGeneratedExpressionsAndCheckConstraints(tab, newPartRel,
22992 : insertslot, econtext);
22993 :
22994 : /* Write the tuple out to the new relation. */
22995 365 : table_tuple_insert(newPartRel, insertslot, mycid,
22996 : ti_options, bistate);
22997 :
22998 365 : ResetExprContext(econtext);
22999 : }
23000 :
23001 240 : MemoryContextSwitchTo(oldCxt);
23002 240 : table_endscan(scan);
23003 240 : UnregisterSnapshot(snapshot);
23004 :
23005 240 : if (tuple_map)
23006 20 : free_conversion_map(tuple_map);
23007 :
23008 240 : ExecDropSingleTupleTableSlot(srcslot);
23009 240 : table_close(mergingPartition, NoLock);
23010 : }
23011 :
23012 106 : FreeExecutorState(estate);
23013 106 : ExecDropSingleTupleTableSlot(dstslot);
23014 106 : FreeBulkInsertState(bistate);
23015 :
23016 106 : table_finish_bulk_insert(newPartRel, ti_options);
23017 :
23018 : /*
23019 : * We don't need to process this newPartRel since we already processed it
23020 : * here, so delete the ALTER TABLE queue for it.
23021 : */
23022 212 : foreach(ltab, *wqueue)
23023 : {
23024 212 : tab = (AlteredTableInfo *) lfirst(ltab);
23025 212 : if (tab->relid == RelationGetRelid(newPartRel))
23026 : {
23027 106 : *wqueue = list_delete_cell(*wqueue, ltab);
23028 106 : break;
23029 : }
23030 : }
23031 106 : }
23032 :
23033 : /*
23034 : * detachPartitionTable: detach partition "child_rel" from partitioned table
23035 : * "parent_rel" with default partition identifier "defaultPartOid"
23036 : */
23037 : static void
23038 437 : detachPartitionTable(Relation parent_rel, Relation child_rel, Oid defaultPartOid)
23039 : {
23040 : /* Remove the pg_inherits row first. */
23041 437 : RemoveInheritance(child_rel, parent_rel, false);
23042 :
23043 : /*
23044 : * Detaching the partition might involve TOAST table access, so ensure we
23045 : * have a valid snapshot.
23046 : */
23047 437 : PushActiveSnapshot(GetTransactionSnapshot());
23048 :
23049 : /* Do the final part of detaching. */
23050 437 : DetachPartitionFinalize(parent_rel, child_rel, false, defaultPartOid);
23051 :
23052 437 : PopActiveSnapshot();
23053 437 : }
23054 :
23055 : /*
23056 : * equal_oid_lists: return true if two OID lists, each sorted in ascending
23057 : * order, contain the same OIDs in the same order.
23058 : */
23059 : static bool
23060 86 : equal_oid_lists(const List *a, const List *b)
23061 : {
23062 : ListCell *la,
23063 : *lb;
23064 :
23065 86 : if (list_length(a) != list_length(b))
23066 4 : return false;
23067 :
23068 86 : forboth(la, a, lb, b)
23069 : {
23070 6 : if (lfirst_oid(la) != lfirst_oid(lb))
23071 2 : return false;
23072 : }
23073 80 : return true;
23074 : }
23075 :
23076 : /*
23077 : * Comparator for list_sort() on a list of PartitionIndexExtDepEntry *.
23078 : * Orders by parentIndexOid, then by indexOid as a tiebreaker so conflict
23079 : * reports for different parent indexes are deterministic.
23080 : */
23081 : static int
23082 145 : cmp_partition_index_ext_dep(const ListCell *a, const ListCell *b)
23083 : {
23084 145 : const PartitionIndexExtDepEntry *ea = lfirst(a);
23085 145 : const PartitionIndexExtDepEntry *eb = lfirst(b);
23086 :
23087 145 : if (ea->parentIndexOid != eb->parentIndexOid)
23088 53 : return pg_cmp_u32(ea->parentIndexOid, eb->parentIndexOid);
23089 92 : return pg_cmp_u32(ea->indexOid, eb->indexOid);
23090 : }
23091 :
23092 : /*
23093 : * collectPartitionIndexExtDeps: collect extension dependencies from indexes
23094 : * on the given partitions.
23095 : *
23096 : * For each partition index that has a parent partitioned index, we collect
23097 : * extension dependencies. All source partition indexes sharing the same
23098 : * parent partitioned index must depend on exactly the same set of
23099 : * extensions; otherwise an error is raised so that we neither silently drop
23100 : * nor silently add dependencies on the merged partition's index.
23101 : *
23102 : * Indexes that don't have a parent partitioned index (i.e., indexes created
23103 : * directly on a partition without a corresponding parent index) are skipped.
23104 : *
23105 : * The returned list is sorted by parentIndexOid with exactly one entry per
23106 : * parent partitioned index, so applyPartitionIndexExtDeps() can scan it
23107 : * linearly.
23108 : */
23109 : static List *
23110 285 : collectPartitionIndexExtDeps(List *partitionOids)
23111 : {
23112 285 : List *collected = NIL;
23113 285 : List *result = NIL;
23114 285 : PartitionIndexExtDepEntry *prev = NULL;
23115 :
23116 : /*
23117 : * Phase 1: collect one entry per (partition index -> parent index) pair,
23118 : * with its extension dependency OIDs sorted ascending.
23119 : */
23120 1019 : foreach_oid(partOid, partitionOids)
23121 : {
23122 : Relation partRel;
23123 : List *indexList;
23124 :
23125 : /*
23126 : * Use NoLock since the caller already holds AccessExclusiveLock on
23127 : * these partitions.
23128 : */
23129 449 : partRel = table_open(partOid, NoLock);
23130 449 : indexList = RelationGetIndexList(partRel);
23131 :
23132 1097 : foreach_oid(indexOid, indexList)
23133 : {
23134 : Oid parentIndexOid;
23135 : PartitionIndexExtDepEntry *entry;
23136 :
23137 199 : if (!get_rel_relispartition(indexOid))
23138 1 : continue;
23139 :
23140 198 : parentIndexOid = get_partition_parent(indexOid, true);
23141 198 : if (!OidIsValid(parentIndexOid))
23142 0 : continue;
23143 :
23144 198 : entry = palloc(sizeof(PartitionIndexExtDepEntry));
23145 198 : entry->parentIndexOid = parentIndexOid;
23146 198 : entry->indexOid = indexOid;
23147 198 : entry->extensionOids = getAutoExtensionsOfObject(RelationRelationId,
23148 : indexOid);
23149 198 : list_sort(entry->extensionOids, list_oid_cmp);
23150 :
23151 198 : collected = lappend(collected, entry);
23152 : }
23153 :
23154 449 : list_free(indexList);
23155 449 : table_close(partRel, NoLock);
23156 : }
23157 :
23158 : /*
23159 : * Phase 2: sort by parentIndexOid so entries sharing a parent index sit
23160 : * adjacent.
23161 : */
23162 285 : list_sort(collected, cmp_partition_index_ext_dep);
23163 :
23164 : /*
23165 : * Phase 3: single linear pass verifying that adjacent entries sharing a
23166 : * parent index have identical extension dependencies, and keeping one
23167 : * representative entry per parent index.
23168 : */
23169 744 : foreach_ptr(PartitionIndexExtDepEntry, entry, collected)
23170 : {
23171 186 : if (prev != NULL && prev->parentIndexOid == entry->parentIndexOid)
23172 : {
23173 86 : if (!equal_oid_lists(prev->extensionOids, entry->extensionOids))
23174 6 : ereport(ERROR,
23175 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
23176 : errmsg("cannot merge partitions with conflicting extension dependencies"),
23177 : errdetail("Partition indexes \"%s\" and \"%s\" depend on different extensions.",
23178 : get_rel_name(prev->indexOid),
23179 : get_rel_name(entry->indexOid))));
23180 :
23181 : /* Duplicate entry for the same parent index; discard. */
23182 80 : list_free(entry->extensionOids);
23183 80 : pfree(entry);
23184 80 : continue;
23185 : }
23186 :
23187 100 : result = lappend(result, entry);
23188 100 : prev = entry;
23189 : }
23190 :
23191 279 : list_free(collected);
23192 :
23193 279 : return result;
23194 : }
23195 :
23196 : /*
23197 : * applyPartitionIndexExtDeps: apply collected extension dependencies to
23198 : * indexes on a new partition.
23199 : *
23200 : * For each index on the new partition, look up its parent index in the
23201 : * extDepState list. If found, record extension dependencies on the new index.
23202 : * extDepState is sorted by parentIndexOid, so the inner scan can bail out
23203 : * as soon as it passes the target OID.
23204 : */
23205 : static void
23206 480 : applyPartitionIndexExtDeps(Oid newPartOid, List *extDepState)
23207 : {
23208 : Relation partRel;
23209 : List *indexList;
23210 :
23211 480 : if (extDepState == NIL)
23212 332 : return;
23213 :
23214 : /*
23215 : * Use NoLock since the caller already holds AccessExclusiveLock on the
23216 : * new partition.
23217 : */
23218 148 : partRel = table_open(newPartOid, NoLock);
23219 148 : indexList = RelationGetIndexList(partRel);
23220 :
23221 468 : foreach_oid(indexOid, indexList)
23222 : {
23223 : Oid parentIdxOid;
23224 :
23225 172 : if (!get_rel_relispartition(indexOid))
23226 0 : continue;
23227 :
23228 172 : parentIdxOid = get_partition_parent(indexOid, true);
23229 172 : if (!OidIsValid(parentIdxOid))
23230 0 : continue;
23231 :
23232 368 : foreach_ptr(PartitionIndexExtDepEntry, entry, extDepState)
23233 : {
23234 : ObjectAddress indexAddr;
23235 :
23236 196 : if (entry->parentIndexOid > parentIdxOid)
23237 172 : break;
23238 196 : if (entry->parentIndexOid < parentIdxOid)
23239 24 : continue;
23240 :
23241 172 : ObjectAddressSet(indexAddr, RelationRelationId, indexOid);
23242 :
23243 352 : foreach_oid(extOid, entry->extensionOids)
23244 : {
23245 : ObjectAddress extAddr;
23246 :
23247 8 : ObjectAddressSet(extAddr, ExtensionRelationId, extOid);
23248 8 : recordDependencyOn(&indexAddr, &extAddr,
23249 : DEPENDENCY_AUTO_EXTENSION);
23250 : }
23251 172 : break;
23252 : }
23253 : }
23254 :
23255 148 : list_free(indexList);
23256 148 : table_close(partRel, NoLock);
23257 : }
23258 :
23259 : /*
23260 : * freePartitionIndexExtDeps: free memory allocated by collectPartitionIndexExtDeps.
23261 : */
23262 : static void
23263 247 : freePartitionIndexExtDeps(List *extDepState)
23264 : {
23265 588 : foreach_ptr(PartitionIndexExtDepEntry, entry, extDepState)
23266 : {
23267 94 : list_free(entry->extensionOids);
23268 94 : pfree(entry);
23269 : }
23270 247 : list_free(extDepState);
23271 247 : }
23272 :
23273 : /*
23274 : * ALTER TABLE <name> MERGE PARTITIONS <partition-list> INTO <partition-name>
23275 : */
23276 : static void
23277 144 : ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
23278 : PartitionCmd *cmd, AlterTableUtilityContext *context)
23279 : {
23280 : Relation newPartRel;
23281 144 : List *mergingPartitions = NIL;
23282 144 : List *extDepState = NIL;
23283 : Oid defaultPartOid;
23284 : Oid existingRelid;
23285 144 : Oid ownerId = InvalidOid;
23286 : Oid save_userid;
23287 : int save_sec_context;
23288 : int save_nestlevel;
23289 :
23290 : /*
23291 : * Check ownership of merged partitions - partitions with different owners
23292 : * cannot be merged. Also, collect the OIDs of these partitions during the
23293 : * check.
23294 : */
23295 600 : foreach_node(RangeVar, name, cmd->partlist)
23296 : {
23297 : Relation mergingPartition;
23298 :
23299 : /*
23300 : * We are going to detach and remove this partition. We already took
23301 : * AccessExclusiveLock lock on transformPartitionCmdForMerge, so here,
23302 : * NoLock is fine.
23303 : */
23304 320 : mergingPartition = table_openrv_extended(name, NoLock, false);
23305 : Assert(CheckRelationLockedByMe(mergingPartition, AccessExclusiveLock, false));
23306 :
23307 320 : if (OidIsValid(ownerId))
23308 : {
23309 : /* Do the partitions being merged have different owners? */
23310 176 : if (ownerId != mergingPartition->rd_rel->relowner)
23311 4 : ereport(ERROR,
23312 : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
23313 : errmsg("partitions being merged have different owners"));
23314 : }
23315 : else
23316 144 : ownerId = mergingPartition->rd_rel->relowner;
23317 :
23318 : /* Store the next merging partition into the list. */
23319 316 : mergingPartitions = lappend_oid(mergingPartitions,
23320 : RelationGetRelid(mergingPartition));
23321 :
23322 316 : table_close(mergingPartition, NoLock);
23323 : }
23324 :
23325 : /* Look up the existing relation by the new partition name. */
23326 140 : RangeVarGetAndCheckCreationNamespace(cmd->name, NoLock, &existingRelid);
23327 :
23328 : /*
23329 : * Check if this name is already taken. This helps us to detect the
23330 : * situation when one of the merging partitions has the same name as the
23331 : * new partition. Otherwise, this would fail later on anyway, but
23332 : * catching this here allows us to emit a nicer error message.
23333 : */
23334 140 : if (OidIsValid(existingRelid))
23335 : {
23336 17 : if (list_member_oid(mergingPartitions, existingRelid))
23337 : {
23338 : /*
23339 : * The new partition has the same name as one of the merging
23340 : * partitions.
23341 : */
23342 : char tmpRelName[NAMEDATALEN];
23343 :
23344 : /* Generate a temporary name. */
23345 13 : sprintf(tmpRelName, "merge-%u-%X-tmp", RelationGetRelid(rel), MyProcPid);
23346 :
23347 : /*
23348 : * Rename the existing partition with a temporary name, leaving it
23349 : * free for the new partition. We don't need to care about this
23350 : * in the future because we're going to eventually drop the
23351 : * existing partition anyway.
23352 : */
23353 13 : RenameRelationInternal(existingRelid, tmpRelName, true, false);
23354 :
23355 : /*
23356 : * We must bump the command counter to make the new partition
23357 : * tuple visible for rename.
23358 : */
23359 13 : CommandCounterIncrement();
23360 : }
23361 : else
23362 : {
23363 4 : ereport(ERROR,
23364 : errcode(ERRCODE_DUPLICATE_TABLE),
23365 : errmsg("relation \"%s\" already exists", cmd->name->relname));
23366 : }
23367 : }
23368 :
23369 : defaultPartOid =
23370 136 : get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
23371 :
23372 : /*
23373 : * Collect extension dependencies from indexes on the merging partitions.
23374 : * We must do this before detaching them, so we can restore the
23375 : * dependencies on the new partition's indexes later.
23376 : */
23377 136 : extDepState = collectPartitionIndexExtDeps(mergingPartitions);
23378 :
23379 : /* Detach all merging partitions. */
23380 548 : foreach_oid(mergingPartitionOid, mergingPartitions)
23381 : {
23382 : Relation child_rel;
23383 :
23384 288 : child_rel = table_open(mergingPartitionOid, NoLock);
23385 :
23386 288 : detachPartitionTable(rel, child_rel, defaultPartOid);
23387 :
23388 288 : table_close(child_rel, NoLock);
23389 : }
23390 :
23391 : /*
23392 : * Perform a preliminary check to determine whether it's safe to drop all
23393 : * merging partitions before we actually do so later. After merging rows
23394 : * into the new partitions via MergePartitionsMoveRows, all old partitions
23395 : * need to be dropped. However, since the drop behavior is DROP_RESTRICT
23396 : * and the merge process (MergePartitionsMoveRows) can be time-consuming,
23397 : * performing an early check on the drop eligibility of old partitions is
23398 : * preferable.
23399 : */
23400 536 : foreach_oid(mergingPartitionOid, mergingPartitions)
23401 : {
23402 : ObjectAddress object;
23403 :
23404 : /* Get oid of the later to be dropped relation. */
23405 284 : object.objectId = mergingPartitionOid;
23406 284 : object.classId = RelationRelationId;
23407 284 : object.objectSubId = 0;
23408 :
23409 284 : performDeletionCheck(&object, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
23410 : }
23411 :
23412 : /*
23413 : * Create a table for the new partition, using the partitioned table as a
23414 : * model.
23415 : */
23416 : Assert(OidIsValid(ownerId));
23417 126 : newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
23418 :
23419 : /*
23420 : * Switch to the table owner's userid, so that any index functions are run
23421 : * as that user. Also, lockdown security-restricted operations and
23422 : * arrange to make GUC variable changes local to this command.
23423 : *
23424 : * Need to do it after determining the namespace in the
23425 : * createPartitionTable() call.
23426 : */
23427 106 : GetUserIdAndSecContext(&save_userid, &save_sec_context);
23428 106 : SetUserIdAndSecContext(ownerId,
23429 : save_sec_context | SECURITY_RESTRICTED_OPERATION);
23430 106 : save_nestlevel = NewGUCNestLevel();
23431 106 : RestrictSearchPath();
23432 :
23433 : /* Copy data from merged partitions to the new partition. */
23434 106 : MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
23435 :
23436 : /* Drop the current partitions before attaching the new one. */
23437 452 : foreach_oid(mergingPartitionOid, mergingPartitions)
23438 : {
23439 : ObjectAddress object;
23440 :
23441 240 : object.objectId = mergingPartitionOid;
23442 240 : object.classId = RelationRelationId;
23443 240 : object.objectSubId = 0;
23444 :
23445 240 : performDeletion(&object, DROP_RESTRICT, 0);
23446 : }
23447 :
23448 106 : list_free(mergingPartitions);
23449 :
23450 : /*
23451 : * Attach a new partition to the partitioned table. wqueue = NULL:
23452 : * verification for each cloned constraint is not needed.
23453 : */
23454 106 : attachPartitionTable(NULL, rel, newPartRel, cmd->bound);
23455 :
23456 : /*
23457 : * Apply extension dependencies to the new partition's indexes. This
23458 : * preserves any "DEPENDS ON EXTENSION" settings from the merged
23459 : * partitions.
23460 : */
23461 106 : applyPartitionIndexExtDeps(RelationGetRelid(newPartRel), extDepState);
23462 :
23463 106 : freePartitionIndexExtDeps(extDepState);
23464 :
23465 : /* Keep the lock until commit. */
23466 106 : table_close(newPartRel, NoLock);
23467 :
23468 : /* Roll back any GUC changes executed by index functions. */
23469 106 : AtEOXact_GUC(false, save_nestlevel);
23470 :
23471 : /* Restore the userid and security context. */
23472 106 : SetUserIdAndSecContext(save_userid, save_sec_context);
23473 106 : }
23474 :
23475 : /*
23476 : * Struct with the context of the new partition for inserting rows from the
23477 : * split partition.
23478 : */
23479 : typedef struct SplitPartitionContext
23480 : {
23481 : ExprState *partqualstate; /* expression for checking a slot for a
23482 : * partition (NULL for DEFAULT partition) */
23483 : BulkInsertState bistate; /* state of bulk inserts for partition */
23484 : TupleTableSlot *dstslot; /* slot for inserting row into partition */
23485 : AlteredTableInfo *tab; /* structure with generated column expressions
23486 : * and check constraint expressions. */
23487 : Relation partRel; /* relation for partition */
23488 : } SplitPartitionContext;
23489 :
23490 : /*
23491 : * createSplitPartitionContext: create context for partition and fill it
23492 : */
23493 : static SplitPartitionContext *
23494 374 : createSplitPartitionContext(Relation partRel)
23495 : {
23496 : SplitPartitionContext *pc;
23497 :
23498 374 : pc = palloc0_object(SplitPartitionContext);
23499 374 : pc->partRel = partRel;
23500 :
23501 : /*
23502 : * Prepare a BulkInsertState for table_tuple_insert. The FSM is empty, so
23503 : * don't bother using it.
23504 : */
23505 374 : pc->bistate = GetBulkInsertState();
23506 :
23507 : /* Create a destination tuple slot for the new partition. */
23508 374 : pc->dstslot = table_slot_create(pc->partRel, NULL);
23509 :
23510 374 : return pc;
23511 : }
23512 :
23513 : /*
23514 : * deleteSplitPartitionContext: delete context for partition
23515 : */
23516 : static void
23517 374 : deleteSplitPartitionContext(SplitPartitionContext *pc, List **wqueue, uint32 ti_options)
23518 : {
23519 : ListCell *ltab;
23520 :
23521 374 : ExecDropSingleTupleTableSlot(pc->dstslot);
23522 374 : FreeBulkInsertState(pc->bistate);
23523 :
23524 374 : table_finish_bulk_insert(pc->partRel, ti_options);
23525 :
23526 : /*
23527 : * We don't need to process this pc->partRel so delete the ALTER TABLE
23528 : * queue of it.
23529 : */
23530 748 : foreach(ltab, *wqueue)
23531 : {
23532 748 : AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
23533 :
23534 748 : if (tab->relid == RelationGetRelid(pc->partRel))
23535 : {
23536 374 : *wqueue = list_delete_cell(*wqueue, ltab);
23537 374 : break;
23538 : }
23539 : }
23540 :
23541 374 : pfree(pc);
23542 374 : }
23543 :
23544 : /*
23545 : * SplitPartitionMoveRows: scan split partition (splitRel) of partitioned table
23546 : * (rel) and move rows into new partitions.
23547 : *
23548 : * New partitions description:
23549 : * partlist: list of pointers to SinglePartitionSpec structures. It contains
23550 : * the partition specification details for all new partitions.
23551 : * newPartRels: list of Relations, new partitions created in
23552 : * ATExecSplitPartition.
23553 : */
23554 : static void
23555 141 : SplitPartitionMoveRows(List **wqueue, Relation rel, Relation splitRel,
23556 : List *partlist, List *newPartRels)
23557 : {
23558 : /* The FSM is empty, so don't bother using it. */
23559 141 : uint32 ti_options = TABLE_INSERT_SKIP_FSM;
23560 : CommandId mycid;
23561 : EState *estate;
23562 : ListCell *listptr,
23563 : *listptr2;
23564 : TupleTableSlot *srcslot;
23565 : ExprContext *econtext;
23566 : TableScanDesc scan;
23567 : Snapshot snapshot;
23568 : MemoryContext oldCxt;
23569 141 : List *partContexts = NIL;
23570 : TupleConversionMap *tuple_map;
23571 141 : SplitPartitionContext *defaultPartCtx = NULL,
23572 : *pc;
23573 :
23574 141 : mycid = GetCurrentCommandId(true);
23575 :
23576 141 : estate = CreateExecutorState();
23577 :
23578 515 : forboth(listptr, partlist, listptr2, newPartRels)
23579 : {
23580 374 : SinglePartitionSpec *sps = (SinglePartitionSpec *) lfirst(listptr);
23581 :
23582 374 : pc = createSplitPartitionContext((Relation) lfirst(listptr2));
23583 :
23584 : /* Find the work queue entry for the new partition table: newPartRel. */
23585 374 : pc->tab = ATGetQueueEntry(wqueue, pc->partRel);
23586 :
23587 374 : buildExpressionExecutionStates(pc->tab, pc->partRel, estate);
23588 :
23589 374 : if (sps->bound->is_default)
23590 : {
23591 : /*
23592 : * We should not create a structure to check the partition
23593 : * constraint for the new DEFAULT partition.
23594 : */
23595 32 : defaultPartCtx = pc;
23596 : }
23597 : else
23598 : {
23599 : List *partConstraint;
23600 :
23601 : /* Build expression execution states for partition check quals. */
23602 342 : partConstraint = get_qual_from_partbound(rel, sps->bound);
23603 : partConstraint =
23604 342 : (List *) eval_const_expressions(NULL,
23605 : (Node *) partConstraint);
23606 : /* Make a boolean expression for ExecCheck(). */
23607 342 : partConstraint = list_make1(make_ands_explicit(partConstraint));
23608 :
23609 : /*
23610 : * Map the vars in the constraint expression from rel's attnos to
23611 : * splitRel's.
23612 : */
23613 342 : partConstraint = map_partition_varattnos(partConstraint,
23614 : 1, splitRel, rel);
23615 :
23616 342 : pc->partqualstate =
23617 342 : ExecPrepareExpr((Expr *) linitial(partConstraint), estate);
23618 : Assert(pc->partqualstate != NULL);
23619 : }
23620 :
23621 : /* Store partition context into a list. */
23622 374 : partContexts = lappend(partContexts, pc);
23623 : }
23624 :
23625 141 : econtext = GetPerTupleExprContext(estate);
23626 :
23627 : /* Create the necessary tuple slot. */
23628 141 : srcslot = table_slot_create(splitRel, NULL);
23629 :
23630 : /*
23631 : * Map computing for moving attributes of the split partition to the new
23632 : * partition (for the first new partition, but other new partitions can
23633 : * use the same map).
23634 : */
23635 141 : pc = (SplitPartitionContext *) lfirst(list_head(partContexts));
23636 141 : tuple_map = convert_tuples_by_name(RelationGetDescr(splitRel),
23637 141 : RelationGetDescr(pc->partRel));
23638 :
23639 : /* Scan through the rows. */
23640 141 : snapshot = RegisterSnapshot(GetLatestSnapshot());
23641 141 : scan = table_beginscan(splitRel, snapshot, 0, NULL,
23642 : SO_NONE);
23643 :
23644 : /*
23645 : * Switch to per-tuple memory context and reset it for each tuple
23646 : * produced, so we don't leak memory.
23647 : */
23648 141 : oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
23649 :
23650 688 : while (table_scan_getnextslot(scan, ForwardScanDirection, srcslot))
23651 : {
23652 547 : bool found = false;
23653 : TupleTableSlot *insertslot;
23654 :
23655 547 : CHECK_FOR_INTERRUPTS();
23656 :
23657 547 : econtext->ecxt_scantuple = srcslot;
23658 :
23659 : /* Search partition for the current slot, srcslot. */
23660 1322 : foreach(listptr, partContexts)
23661 : {
23662 1246 : pc = (SplitPartitionContext *) lfirst(listptr);
23663 :
23664 : /* skip DEFAULT partition */
23665 1246 : if (pc->partqualstate && ExecCheck(pc->partqualstate, econtext))
23666 : {
23667 471 : found = true;
23668 471 : break;
23669 : }
23670 : }
23671 547 : if (!found)
23672 : {
23673 : /* Use the DEFAULT partition if it exists. */
23674 76 : if (defaultPartCtx)
23675 76 : pc = defaultPartCtx;
23676 : else
23677 0 : ereport(ERROR,
23678 : errcode(ERRCODE_CHECK_VIOLATION),
23679 : errmsg("cannot find partition for split partition row"),
23680 : errtable(splitRel));
23681 : }
23682 :
23683 547 : if (tuple_map)
23684 : {
23685 : /* Need to use a map to copy attributes. */
23686 16 : insertslot = execute_attr_map_slot(tuple_map->attrMap, srcslot, pc->dstslot);
23687 : }
23688 : else
23689 : {
23690 : /* Extract data from the old tuple. */
23691 531 : slot_getallattrs(srcslot);
23692 :
23693 : /* Copy attributes directly. */
23694 531 : insertslot = pc->dstslot;
23695 :
23696 531 : ExecClearTuple(insertslot);
23697 :
23698 531 : memcpy(insertslot->tts_values, srcslot->tts_values,
23699 531 : sizeof(Datum) * srcslot->tts_nvalid);
23700 531 : memcpy(insertslot->tts_isnull, srcslot->tts_isnull,
23701 531 : sizeof(bool) * srcslot->tts_nvalid);
23702 :
23703 531 : ExecStoreVirtualTuple(insertslot);
23704 : }
23705 :
23706 : /*
23707 : * Constraints and GENERATED expressions might reference the tableoid
23708 : * column, so fill tts_tableOid with the desired value. (We must do
23709 : * this each time, because it gets overwritten with newrel's OID
23710 : * during storing.)
23711 : */
23712 547 : insertslot->tts_tableOid = RelationGetRelid(pc->partRel);
23713 :
23714 : /*
23715 : * Now, evaluate any generated expressions whose inputs come from the
23716 : * new tuple. We assume these columns won't reference each other, so
23717 : * that there's no ordering dependency.
23718 : */
23719 547 : evaluateGeneratedExpressionsAndCheckConstraints(pc->tab, pc->partRel,
23720 : insertslot, econtext);
23721 :
23722 : /* Write the tuple out to the new relation. */
23723 547 : table_tuple_insert(pc->partRel, insertslot, mycid,
23724 547 : ti_options, pc->bistate);
23725 :
23726 547 : ResetExprContext(econtext);
23727 : }
23728 :
23729 141 : MemoryContextSwitchTo(oldCxt);
23730 :
23731 141 : table_endscan(scan);
23732 141 : UnregisterSnapshot(snapshot);
23733 :
23734 141 : if (tuple_map)
23735 4 : free_conversion_map(tuple_map);
23736 :
23737 141 : ExecDropSingleTupleTableSlot(srcslot);
23738 :
23739 141 : FreeExecutorState(estate);
23740 :
23741 656 : foreach_ptr(SplitPartitionContext, spc, partContexts)
23742 374 : deleteSplitPartitionContext(spc, wqueue, ti_options);
23743 141 : }
23744 :
23745 : /*
23746 : * ALTER TABLE <name> SPLIT PARTITION <partition-name> INTO <partition-list>
23747 : */
23748 : static void
23749 153 : ATExecSplitPartition(List **wqueue, AlteredTableInfo *tab, Relation rel,
23750 : PartitionCmd *cmd, AlterTableUtilityContext *context)
23751 : {
23752 : Relation splitRel;
23753 : Oid splitRelOid;
23754 : ListCell *listptr,
23755 : *listptr2;
23756 153 : bool isSameName = false;
23757 : char tmpRelName[NAMEDATALEN];
23758 153 : List *newPartRels = NIL;
23759 153 : List *extDepState = NIL;
23760 : ObjectAddress object;
23761 : Oid defaultPartOid;
23762 : Oid save_userid;
23763 : int save_sec_context;
23764 : int save_nestlevel;
23765 : List *splitPartList;
23766 :
23767 153 : defaultPartOid = get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
23768 :
23769 : /*
23770 : * Partition is already locked in the transformPartitionCmdForSplit
23771 : * function.
23772 : */
23773 153 : splitRel = table_openrv(cmd->name, NoLock);
23774 :
23775 153 : splitRelOid = RelationGetRelid(splitRel);
23776 :
23777 : /* Check descriptions of new partitions. */
23778 692 : foreach_node(SinglePartitionSpec, sps, cmd->partlist)
23779 : {
23780 : Oid existingRelid;
23781 :
23782 : /* Look up the existing relation by the new partition name. */
23783 394 : RangeVarGetAndCheckCreationNamespace(sps->name, NoLock, &existingRelid);
23784 :
23785 : /*
23786 : * This would fail later on anyway if the relation already exists. But
23787 : * by catching it here, we can emit a nicer error message.
23788 : */
23789 394 : if (existingRelid == splitRelOid && !isSameName)
23790 : /* One new partition can have the same name as a split partition. */
23791 29 : isSameName = true;
23792 365 : else if (OidIsValid(existingRelid))
23793 4 : ereport(ERROR,
23794 : errcode(ERRCODE_DUPLICATE_TABLE),
23795 : errmsg("relation \"%s\" already exists", sps->name->relname));
23796 : }
23797 :
23798 : /*
23799 : * Collect extension dependencies from indexes on the split partition. We
23800 : * must do this before detaching it, so we can restore the dependencies on
23801 : * the new partitions' indexes later.
23802 : */
23803 149 : splitPartList = list_make1_oid(splitRelOid);
23804 :
23805 149 : extDepState = collectPartitionIndexExtDeps(splitPartList);
23806 149 : list_free(splitPartList);
23807 :
23808 : /* Detach the split partition. */
23809 149 : detachPartitionTable(rel, splitRel, defaultPartOid);
23810 :
23811 : /*
23812 : * Perform a preliminary check to determine whether it's safe to drop the
23813 : * split partition before we actually do so later. After merging rows into
23814 : * the new partitions via SplitPartitionMoveRows, all old partitions need
23815 : * to be dropped. However, since the drop behavior is DROP_RESTRICT and
23816 : * the merge process (SplitPartitionMoveRows) can be time-consuming,
23817 : * performing an early check on the drop eligibility of old partitions is
23818 : * preferable.
23819 : */
23820 149 : object.objectId = splitRelOid;
23821 149 : object.classId = RelationRelationId;
23822 149 : object.objectSubId = 0;
23823 149 : performDeletionCheck(&object, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
23824 :
23825 : /*
23826 : * If a new partition has the same name as the split partition, then we
23827 : * should rename the split partition to reuse its name.
23828 : */
23829 149 : if (isSameName)
23830 : {
23831 : /*
23832 : * We must bump the command counter to make the split partition tuple
23833 : * visible for renaming.
23834 : */
23835 29 : CommandCounterIncrement();
23836 : /* Rename partition. */
23837 29 : sprintf(tmpRelName, "split-%u-%X-tmp", RelationGetRelid(rel), MyProcPid);
23838 29 : RenameRelationInternal(splitRelOid, tmpRelName, true, false);
23839 :
23840 : /*
23841 : * We must bump the command counter to make the split partition tuple
23842 : * visible after renaming.
23843 : */
23844 29 : CommandCounterIncrement();
23845 : }
23846 :
23847 : /* Create new partitions (like a split partition), without indexes. */
23848 664 : foreach_node(SinglePartitionSpec, sps, cmd->partlist)
23849 : {
23850 : Relation newPartRel;
23851 :
23852 382 : newPartRel = createPartitionTable(wqueue, sps->name, rel,
23853 382 : splitRel->rd_rel->relowner);
23854 374 : newPartRels = lappend(newPartRels, newPartRel);
23855 : }
23856 :
23857 : /*
23858 : * Switch to the table owner's userid, so that any index functions are run
23859 : * as that user. Also, lockdown security-restricted operations and
23860 : * arrange to make GUC variable changes local to this command.
23861 : *
23862 : * Need to do it after determining the namespace in the
23863 : * createPartitionTable() call.
23864 : */
23865 141 : GetUserIdAndSecContext(&save_userid, &save_sec_context);
23866 141 : SetUserIdAndSecContext(splitRel->rd_rel->relowner,
23867 : save_sec_context | SECURITY_RESTRICTED_OPERATION);
23868 141 : save_nestlevel = NewGUCNestLevel();
23869 141 : RestrictSearchPath();
23870 :
23871 : /* Copy data from the split partition to the new partitions. */
23872 141 : SplitPartitionMoveRows(wqueue, rel, splitRel, cmd->partlist, newPartRels);
23873 : /* Keep the lock until commit. */
23874 141 : table_close(splitRel, NoLock);
23875 :
23876 : /* Attach new partitions to the partitioned table. */
23877 515 : forboth(listptr, cmd->partlist, listptr2, newPartRels)
23878 : {
23879 374 : SinglePartitionSpec *sps = (SinglePartitionSpec *) lfirst(listptr);
23880 374 : Relation newPartRel = (Relation) lfirst(listptr2);
23881 :
23882 : /*
23883 : * wqueue = NULL: verification for each cloned constraint is not
23884 : * needed.
23885 : */
23886 374 : attachPartitionTable(NULL, rel, newPartRel, sps->bound);
23887 :
23888 : /*
23889 : * Apply extension dependencies to the new partition's indexes. This
23890 : * preserves any "DEPENDS ON EXTENSION" settings from the split
23891 : * partition.
23892 : */
23893 374 : applyPartitionIndexExtDeps(RelationGetRelid(newPartRel), extDepState);
23894 :
23895 : /* Keep the lock until commit. */
23896 374 : table_close(newPartRel, NoLock);
23897 : }
23898 :
23899 141 : freePartitionIndexExtDeps(extDepState);
23900 :
23901 : /* Drop the split partition. */
23902 141 : object.classId = RelationRelationId;
23903 141 : object.objectId = splitRelOid;
23904 141 : object.objectSubId = 0;
23905 : /* Probably DROP_CASCADE is not needed. */
23906 141 : performDeletion(&object, DROP_RESTRICT, 0);
23907 :
23908 : /* Roll back any GUC changes executed by index functions. */
23909 141 : AtEOXact_GUC(false, save_nestlevel);
23910 :
23911 : /* Restore the userid and security context. */
23912 141 : SetUserIdAndSecContext(save_userid, save_sec_context);
23913 141 : }
|