Branch data 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 : : List *changedStatisticsOwners; /* owners of same */
215 : : } AlteredTableInfo;
216 : :
217 : : /* Struct describing one new constraint to check in Phase 3 scan */
218 : : /* Note: new not-null constraints are handled elsewhere */
219 : : typedef struct NewConstraint
220 : : {
221 : : char *name; /* Constraint name, or NULL if none */
222 : : ConstrType contype; /* CHECK or FOREIGN */
223 : : Oid refrelid; /* PK rel, if FOREIGN */
224 : : Oid refindid; /* OID of PK's index, if FOREIGN */
225 : : bool conwithperiod; /* Whether the new FOREIGN KEY uses PERIOD */
226 : : Oid conid; /* OID of pg_constraint entry, if FOREIGN */
227 : : Node *qual; /* Check expr or CONSTR_FOREIGN Constraint */
228 : : ExprState *qualstate; /* Execution state for CHECK expr */
229 : : } NewConstraint;
230 : :
231 : : /*
232 : : * Struct describing one new column value that needs to be computed during
233 : : * Phase 3 copy (this could be either a new column with a non-null default, or
234 : : * a column that we're changing the type of). Columns without such an entry
235 : : * are just copied from the old table during ATRewriteTable. Note that the
236 : : * expr is an expression over *old* table values, except when is_generated
237 : : * is true; then it is an expression over columns of the *new* tuple.
238 : : */
239 : : typedef struct NewColumnValue
240 : : {
241 : : AttrNumber attnum; /* which column */
242 : : Expr *expr; /* expression to compute */
243 : : ExprState *exprstate; /* execution state */
244 : : bool is_generated; /* is it a GENERATED expression? */
245 : : } NewColumnValue;
246 : :
247 : : /*
248 : : * Error-reporting support for RemoveRelations
249 : : */
250 : : struct dropmsgstrings
251 : : {
252 : : char kind;
253 : : int nonexistent_code;
254 : : const char *nonexistent_msg;
255 : : const char *skipping_msg;
256 : : const char *nota_msg;
257 : : const char *drophint_msg;
258 : : };
259 : :
260 : : static const struct dropmsgstrings dropmsgstringarray[] = {
261 : : {RELKIND_RELATION,
262 : : ERRCODE_UNDEFINED_TABLE,
263 : : gettext_noop("table \"%s\" does not exist"),
264 : : gettext_noop("table \"%s\" does not exist, skipping"),
265 : : gettext_noop("\"%s\" is not a table"),
266 : : gettext_noop("Use DROP TABLE to remove a table.")},
267 : : {RELKIND_SEQUENCE,
268 : : ERRCODE_UNDEFINED_TABLE,
269 : : gettext_noop("sequence \"%s\" does not exist"),
270 : : gettext_noop("sequence \"%s\" does not exist, skipping"),
271 : : gettext_noop("\"%s\" is not a sequence"),
272 : : gettext_noop("Use DROP SEQUENCE to remove a sequence.")},
273 : : {RELKIND_VIEW,
274 : : ERRCODE_UNDEFINED_TABLE,
275 : : gettext_noop("view \"%s\" does not exist"),
276 : : gettext_noop("view \"%s\" does not exist, skipping"),
277 : : gettext_noop("\"%s\" is not a view"),
278 : : gettext_noop("Use DROP VIEW to remove a view.")},
279 : : {RELKIND_MATVIEW,
280 : : ERRCODE_UNDEFINED_TABLE,
281 : : gettext_noop("materialized view \"%s\" does not exist"),
282 : : gettext_noop("materialized view \"%s\" does not exist, skipping"),
283 : : gettext_noop("\"%s\" is not a materialized view"),
284 : : gettext_noop("Use DROP MATERIALIZED VIEW to remove a materialized view.")},
285 : : {RELKIND_INDEX,
286 : : ERRCODE_UNDEFINED_OBJECT,
287 : : gettext_noop("index \"%s\" does not exist"),
288 : : gettext_noop("index \"%s\" does not exist, skipping"),
289 : : gettext_noop("\"%s\" is not an index"),
290 : : gettext_noop("Use DROP INDEX to remove an index.")},
291 : : {RELKIND_COMPOSITE_TYPE,
292 : : ERRCODE_UNDEFINED_OBJECT,
293 : : gettext_noop("type \"%s\" does not exist"),
294 : : gettext_noop("type \"%s\" does not exist, skipping"),
295 : : gettext_noop("\"%s\" is not a type"),
296 : : gettext_noop("Use DROP TYPE to remove a type.")},
297 : : {RELKIND_FOREIGN_TABLE,
298 : : ERRCODE_UNDEFINED_OBJECT,
299 : : gettext_noop("foreign table \"%s\" does not exist"),
300 : : gettext_noop("foreign table \"%s\" does not exist, skipping"),
301 : : gettext_noop("\"%s\" is not a foreign table"),
302 : : gettext_noop("Use DROP FOREIGN TABLE to remove a foreign table.")},
303 : : {RELKIND_PARTITIONED_TABLE,
304 : : ERRCODE_UNDEFINED_TABLE,
305 : : gettext_noop("table \"%s\" does not exist"),
306 : : gettext_noop("table \"%s\" does not exist, skipping"),
307 : : gettext_noop("\"%s\" is not a table"),
308 : : gettext_noop("Use DROP TABLE to remove a table.")},
309 : : {RELKIND_PARTITIONED_INDEX,
310 : : ERRCODE_UNDEFINED_OBJECT,
311 : : gettext_noop("index \"%s\" does not exist"),
312 : : gettext_noop("index \"%s\" does not exist, skipping"),
313 : : gettext_noop("\"%s\" is not an index"),
314 : : gettext_noop("Use DROP INDEX to remove an index.")},
315 : : {RELKIND_PROPGRAPH,
316 : : ERRCODE_UNDEFINED_OBJECT,
317 : : gettext_noop("property graph \"%s\" does not exist"),
318 : : gettext_noop("property graph \"%s\" does not exist, skipping"),
319 : : gettext_noop("\"%s\" is not a property graph"),
320 : : gettext_noop("Use DROP PROPERTY GRAPH to remove a property graph.")},
321 : : {'\0', 0, NULL, NULL, NULL, NULL}
322 : : };
323 : :
324 : : /* communication between RemoveRelations and RangeVarCallbackForDropRelation */
325 : : struct DropRelationCallbackState
326 : : {
327 : : /* These fields are set by RemoveRelations: */
328 : : char expected_relkind;
329 : : LOCKMODE heap_lockmode;
330 : : /* These fields are state to track which subsidiary locks are held: */
331 : : Oid heapOid;
332 : : Oid partParentOid;
333 : : /* These fields are passed back by RangeVarCallbackForDropRelation: */
334 : : char actual_relkind;
335 : : char actual_relpersistence;
336 : : };
337 : :
338 : : /* Alter table target-type flags for ATSimplePermissions */
339 : : #define ATT_TABLE 0x0001
340 : : #define ATT_VIEW 0x0002
341 : : #define ATT_MATVIEW 0x0004
342 : : #define ATT_INDEX 0x0008
343 : : #define ATT_COMPOSITE_TYPE 0x0010
344 : : #define ATT_FOREIGN_TABLE 0x0020
345 : : #define ATT_PARTITIONED_INDEX 0x0040
346 : : #define ATT_SEQUENCE 0x0080
347 : : #define ATT_PARTITIONED_TABLE 0x0100
348 : :
349 : : /*
350 : : * ForeignTruncateInfo
351 : : *
352 : : * Information related to truncation of foreign tables. This is used for
353 : : * the elements in a hash table. It uses the server OID as lookup key,
354 : : * and includes a per-server list of all foreign tables involved in the
355 : : * truncation.
356 : : */
357 : : typedef struct ForeignTruncateInfo
358 : : {
359 : : Oid serverid;
360 : : List *rels;
361 : : } ForeignTruncateInfo;
362 : :
363 : : /* Partial or complete FK creation in addFkConstraint() */
364 : : typedef enum addFkConstraintSides
365 : : {
366 : : addFkReferencedSide,
367 : : addFkReferencingSide,
368 : : addFkBothSides,
369 : : } addFkConstraintSides;
370 : :
371 : : /*
372 : : * Hold extension dependencies of one partition index, during
373 : : * MERGE/SPLIT PARTITION processing.
374 : : *
375 : : * collectPartitionIndexExtDeps() builds a list of these entries sorted by
376 : : * parentIndexOid with exactly one entry per parent partitioned index; the
377 : : * list is then consumed by applyPartitionIndexExtDeps() to re-record the
378 : : * same dependencies on the newly created partition's indexes.
379 : : *
380 : : * extensionOids is kept sorted ascending so that equality checks between
381 : : * entries from different partitions can be done in a single pass.
382 : : * indexOid is carried only so that conflict errors can cite specific
383 : : * partition index names.
384 : : */
385 : : typedef struct PartitionIndexExtDepEntry
386 : : {
387 : : Oid parentIndexOid; /* OID of the parent partitioned index */
388 : : Oid indexOid; /* OID of a representative partition index */
389 : : List *extensionOids; /* OIDs of dependent extensions, sorted asc */
390 : : } PartitionIndexExtDepEntry;
391 : :
392 : : /*
393 : : * Partition tables are expected to be dropped when the parent partitioned
394 : : * table gets dropped. Hence for partitioning we use AUTO dependency.
395 : : * Otherwise, for regular inheritance use NORMAL dependency.
396 : : */
397 : : #define child_dependency_type(child_is_partition) \
398 : : ((child_is_partition) ? DEPENDENCY_AUTO : DEPENDENCY_NORMAL)
399 : :
400 : : static void truncate_check_rel(Oid relid, Form_pg_class reltuple);
401 : : static void truncate_check_perms(Oid relid, Form_pg_class reltuple);
402 : : static void truncate_check_activity(Relation rel);
403 : : static void RangeVarCallbackForTruncate(const RangeVar *relation,
404 : : Oid relId, Oid oldRelId, void *arg);
405 : : static List *MergeAttributes(List *columns, const List *supers, char relpersistence,
406 : : bool is_partition, List **supconstr,
407 : : List **supnotnulls);
408 : : static List *MergeCheckConstraint(List *constraints, const char *name, Node *expr, bool is_enforced);
409 : : static void MergeChildAttribute(List *inh_columns, int exist_attno, int newcol_attno, const ColumnDef *newdef);
410 : : static ColumnDef *MergeInheritedAttribute(List *inh_columns, int exist_attno, const ColumnDef *newdef);
411 : : static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel, bool ispartition);
412 : : static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
413 : : static void StoreCatalogInheritance(Oid relationId, List *supers,
414 : : bool child_is_partition);
415 : : static void StoreCatalogInheritance1(Oid relationId, Oid parentOid,
416 : : int32 seqNumber, Relation inhRelation,
417 : : bool child_is_partition);
418 : : static int findAttrByName(const char *attributeName, const List *columns);
419 : : static void AlterIndexNamespaces(Relation classRel, Relation rel,
420 : : Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved);
421 : : static void AlterSeqNamespaces(Relation classRel, Relation rel,
422 : : Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved,
423 : : LOCKMODE lockmode);
424 : : static ObjectAddress ATExecAlterConstraint(List **wqueue, Relation rel,
425 : : ATAlterConstraint *cmdcon,
426 : : bool recurse, LOCKMODE lockmode);
427 : : static bool ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon, Relation conrel,
428 : : Relation tgrel, Relation rel, HeapTuple contuple,
429 : : bool recurse, LOCKMODE lockmode);
430 : : static bool ATExecAlterFKConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon,
431 : : Relation conrel, Relation tgrel,
432 : : Oid fkrelid, Oid pkrelid,
433 : : HeapTuple contuple, LOCKMODE lockmode,
434 : : Oid ReferencedParentDelTrigger,
435 : : Oid ReferencedParentUpdTrigger,
436 : : Oid ReferencingParentInsTrigger,
437 : : Oid ReferencingParentUpdTrigger);
438 : : static bool ATExecAlterCheckConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon,
439 : : Relation conrel, HeapTuple contuple,
440 : : bool recurse, bool recursing,
441 : : List *changing_conids,
442 : : LOCKMODE lockmode);
443 : : static bool ATExecAlterConstrDeferrability(List **wqueue, ATAlterConstraint *cmdcon,
444 : : Relation conrel, Relation tgrel, Relation rel,
445 : : HeapTuple contuple, bool recurse,
446 : : List **otherrelids, LOCKMODE lockmode);
447 : : static bool ATExecAlterConstrInheritability(List **wqueue, ATAlterConstraint *cmdcon,
448 : : Relation conrel, Relation rel,
449 : : HeapTuple contuple, LOCKMODE lockmode);
450 : : static void AlterConstrTriggerDeferrability(Oid conoid, Relation tgrel, Relation rel,
451 : : bool deferrable, bool initdeferred,
452 : : List **otherrelids);
453 : : static void AlterFKConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
454 : : Relation conrel, Relation tgrel,
455 : : Oid fkrelid, Oid pkrelid,
456 : : HeapTuple contuple, LOCKMODE lockmode,
457 : : Oid ReferencedParentDelTrigger,
458 : : Oid ReferencedParentUpdTrigger,
459 : : Oid ReferencingParentInsTrigger,
460 : : Oid ReferencingParentUpdTrigger);
461 : : static void AlterCheckConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
462 : : Relation conrel, Oid conrelid,
463 : : bool recurse, bool recursing,
464 : : List *changing_conids,
465 : : LOCKMODE lockmode);
466 : : static void AlterConstrDeferrabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
467 : : Relation conrel, Relation tgrel, Relation rel,
468 : : HeapTuple contuple, bool recurse,
469 : : List **otherrelids, LOCKMODE lockmode);
470 : : static void AlterConstrUpdateConstraintEntry(ATAlterConstraint *cmdcon, Relation conrel,
471 : : HeapTuple contuple);
472 : : static bool ATCheckCheckConstrHasEnforcedParent(Relation conrel, Relation rel,
473 : : HeapTuple contuple,
474 : : List *changing_conids,
475 : : Oid *enforced_parentoid);
476 : : static ObjectAddress ATExecValidateConstraint(List **wqueue,
477 : : Relation rel, char *constrName,
478 : : bool recurse, bool recursing, LOCKMODE lockmode);
479 : : static void QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation fkrel,
480 : : Oid pkrelid, HeapTuple contuple, LOCKMODE lockmode);
481 : : static void QueueCheckConstraintValidation(List **wqueue, Relation conrel, Relation rel,
482 : : char *constrName, HeapTuple contuple,
483 : : bool recurse, bool recursing, LOCKMODE lockmode);
484 : : static void QueueNNConstraintValidation(List **wqueue, Relation conrel, Relation rel,
485 : : HeapTuple contuple, bool recurse, bool recursing,
486 : : LOCKMODE lockmode);
487 : : static bool constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc);
488 : : static int transformColumnNameList(Oid relId, List *colList,
489 : : int16 *attnums, Oid *atttypids, Oid *attcollids);
490 : : static int transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
491 : : List **attnamelist,
492 : : int16 *attnums, Oid *atttypids, Oid *attcollids,
493 : : Oid *opclasses, bool *pk_has_without_overlaps);
494 : : static Oid transformFkeyCheckAttrs(Relation pkrel,
495 : : int numattrs, int16 *attnums,
496 : : bool with_period, Oid *opclasses,
497 : : bool *pk_has_without_overlaps);
498 : : static void checkFkeyPermissions(Relation rel, int16 *attnums, int natts);
499 : : static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId,
500 : : Oid *funcid);
501 : : static void validateForeignKeyConstraint(char *conname,
502 : : Relation rel, Relation pkrel,
503 : : Oid pkindOid, Oid constraintOid, bool hasperiod);
504 : : static void CheckAlterTableIsSafe(Relation rel);
505 : : static void ATController(AlterTableStmt *parsetree,
506 : : Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
507 : : AlterTableUtilityContext *context);
508 : : static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
509 : : bool recurse, bool recursing, LOCKMODE lockmode,
510 : : AlterTableUtilityContext *context);
511 : : static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
512 : : AlterTableUtilityContext *context);
513 : : static void ATExecCmd(List **wqueue, AlteredTableInfo *tab,
514 : : AlterTableCmd *cmd, LOCKMODE lockmode, AlterTablePass cur_pass,
515 : : AlterTableUtilityContext *context);
516 : : static AlterTableCmd *ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab,
517 : : Relation rel, AlterTableCmd *cmd,
518 : : bool recurse, LOCKMODE lockmode,
519 : : AlterTablePass cur_pass,
520 : : AlterTableUtilityContext *context);
521 : : static void ATRewriteTables(AlterTableStmt *parsetree,
522 : : List **wqueue, LOCKMODE lockmode,
523 : : AlterTableUtilityContext *context);
524 : : static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap);
525 : : static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
526 : : static void ATSimplePermissions(AlterTableType cmdtype, Relation rel, int allowed_targets);
527 : : static void ATSimpleRecursion(List **wqueue, Relation rel,
528 : : AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
529 : : AlterTableUtilityContext *context);
530 : : static void ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode);
531 : : static void ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
532 : : LOCKMODE lockmode,
533 : : AlterTableUtilityContext *context);
534 : : static List *find_typed_table_dependencies(Oid typeOid, const char *typeName,
535 : : DropBehavior behavior);
536 : : static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
537 : : bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode,
538 : : AlterTableUtilityContext *context);
539 : : static ObjectAddress ATExecAddColumn(List **wqueue, AlteredTableInfo *tab,
540 : : Relation rel, AlterTableCmd **cmd,
541 : : bool recurse, bool recursing,
542 : : LOCKMODE lockmode, AlterTablePass cur_pass,
543 : : AlterTableUtilityContext *context);
544 : : static bool check_for_column_name_collision(Relation rel, const char *colname,
545 : : bool if_not_exists);
546 : : static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
547 : : static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
548 : : static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, bool recurse,
549 : : LOCKMODE lockmode);
550 : : static void set_attnotnull(List **wqueue, Relation rel, AttrNumber attnum,
551 : : bool queue_validation);
552 : : static ObjectAddress ATExecSetNotNull(List **wqueue, Relation rel,
553 : : char *conName, char *colName,
554 : : bool recurse, bool recursing,
555 : : LOCKMODE lockmode);
556 : : static bool NotNullImpliedByRelConstraints(Relation rel, Form_pg_attribute attr);
557 : : static bool ConstraintImpliedByRelConstraint(Relation scanrel,
558 : : List *testConstraint, List *provenConstraint);
559 : : static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
560 : : Node *newDefault, LOCKMODE lockmode);
561 : : static ObjectAddress ATExecCookedColumnDefault(Relation rel, AttrNumber attnum,
562 : : Node *newDefault);
563 : : static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName,
564 : : Node *def, LOCKMODE lockmode, bool recurse, bool recursing);
565 : : static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName,
566 : : Node *def, LOCKMODE lockmode, bool recurse, bool recursing);
567 : : static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode,
568 : : bool recurse, bool recursing);
569 : : static ObjectAddress ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
570 : : Node *newExpr, LOCKMODE lockmode);
571 : : static void ATPrepDropExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, LOCKMODE lockmode);
572 : : static ObjectAddress ATExecDropExpression(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode);
573 : : static ObjectAddress ATExecSetStatistics(Relation rel, const char *colName, int16 colNum,
574 : : Node *newValue, LOCKMODE lockmode);
575 : : static ObjectAddress ATExecSetOptions(Relation rel, const char *colName,
576 : : Node *options, bool isReset, LOCKMODE lockmode);
577 : : static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
578 : : Node *newValue, LOCKMODE lockmode);
579 : : static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
580 : : AlterTableCmd *cmd, LOCKMODE lockmode,
581 : : AlterTableUtilityContext *context);
582 : : static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
583 : : DropBehavior behavior,
584 : : bool recurse, bool recursing,
585 : : bool missing_ok, LOCKMODE lockmode,
586 : : ObjectAddresses *addrs);
587 : : static void ATPrepAddPrimaryKey(List **wqueue, Relation rel, AlterTableCmd *cmd,
588 : : bool recurse, LOCKMODE lockmode,
589 : : AlterTableUtilityContext *context);
590 : : static void verifyNotNullPKCompatible(HeapTuple tuple, const char *colname);
591 : : static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
592 : : IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
593 : : static ObjectAddress ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
594 : : CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
595 : : static ObjectAddress ATExecAddConstraint(List **wqueue,
596 : : AlteredTableInfo *tab, Relation rel,
597 : : Constraint *newConstraint, bool recurse, bool is_readd,
598 : : LOCKMODE lockmode);
599 : : static char *ChooseForeignKeyConstraintNameAddition(List *colnames);
600 : : static ObjectAddress ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
601 : : IndexStmt *stmt, LOCKMODE lockmode);
602 : : static ObjectAddress ATAddCheckNNConstraint(List **wqueue,
603 : : AlteredTableInfo *tab, Relation rel,
604 : : Constraint *constr,
605 : : bool recurse, bool recursing, bool is_readd,
606 : : LOCKMODE lockmode);
607 : : static ObjectAddress ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab,
608 : : Relation rel, Constraint *fkconstraint,
609 : : bool recurse, bool recursing,
610 : : LOCKMODE lockmode);
611 : : static int validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
612 : : int numfksetcols, int16 *fksetcolsattnums,
613 : : List *fksetcols);
614 : : static ObjectAddress addFkConstraint(addFkConstraintSides fkside,
615 : : char *constraintname,
616 : : Constraint *fkconstraint, Relation rel,
617 : : Relation pkrel, Oid indexOid,
618 : : Oid parentConstr,
619 : : int numfks, int16 *pkattnum, int16 *fkattnum,
620 : : Oid *pfeqoperators, Oid *ppeqoperators,
621 : : Oid *ffeqoperators, int numfkdelsetcols,
622 : : int16 *fkdelsetcols, bool is_internal,
623 : : bool with_period);
624 : : static void addFkRecurseReferenced(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,
630 : : Oid parentDelTrigger, Oid parentUpdTrigger,
631 : : bool with_period);
632 : : static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint,
633 : : Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
634 : : int numfks, int16 *pkattnum, int16 *fkattnum,
635 : : Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
636 : : int numfkdelsetcols, int16 *fkdelsetcols,
637 : : bool old_check_ok, LOCKMODE lockmode,
638 : : Oid parentInsTrigger, Oid parentUpdTrigger,
639 : : bool with_period);
640 : : static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
641 : : Relation partitionRel);
642 : : static void CloneFkReferenced(Relation parentRel, Relation partitionRel);
643 : : static void CloneFkReferencing(List **wqueue, Relation parentRel,
644 : : Relation partRel);
645 : : static void createForeignKeyCheckTriggers(Oid myRelOid, Oid refRelOid,
646 : : Constraint *fkconstraint, Oid constraintOid,
647 : : Oid indexOid,
648 : : Oid parentInsTrigger, Oid parentUpdTrigger,
649 : : Oid *insertTrigOid, Oid *updateTrigOid);
650 : : static void createForeignKeyActionTriggers(Oid myRelOid, Oid refRelOid,
651 : : Constraint *fkconstraint, Oid constraintOid,
652 : : Oid indexOid,
653 : : Oid parentDelTrigger, Oid parentUpdTrigger,
654 : : Oid *deleteTrigOid, Oid *updateTrigOid);
655 : : static bool tryAttachPartitionForeignKey(List **wqueue,
656 : : ForeignKeyCacheInfo *fk,
657 : : Relation partition,
658 : : Oid parentConstrOid, int numfks,
659 : : AttrNumber *mapped_conkey, AttrNumber *confkey,
660 : : Oid *conpfeqop,
661 : : Oid parentInsTrigger,
662 : : Oid parentUpdTrigger,
663 : : Relation trigrel);
664 : : static void AttachPartitionForeignKey(List **wqueue, Relation partition,
665 : : Oid partConstrOid, Oid parentConstrOid,
666 : : Oid parentInsTrigger, Oid parentUpdTrigger,
667 : : Relation trigrel);
668 : : static void RemoveInheritedConstraint(Relation conrel, Relation trigrel,
669 : : Oid conoid, Oid conrelid);
670 : : static void DropForeignKeyConstraintTriggers(Relation trigrel, Oid conoid,
671 : : Oid confrelid, Oid conrelid);
672 : : static void GetForeignKeyActionTriggers(Relation trigrel,
673 : : Oid conoid, Oid confrelid, Oid conrelid,
674 : : Oid *deleteTriggerOid,
675 : : Oid *updateTriggerOid);
676 : : static void GetForeignKeyCheckTriggers(Relation trigrel,
677 : : Oid conoid, Oid confrelid, Oid conrelid,
678 : : Oid *insertTriggerOid,
679 : : Oid *updateTriggerOid);
680 : : static void ATExecDropConstraint(Relation rel, const char *constrName,
681 : : DropBehavior behavior, bool recurse,
682 : : bool missing_ok, LOCKMODE lockmode);
683 : : static ObjectAddress dropconstraint_internal(Relation rel,
684 : : HeapTuple constraintTup, DropBehavior behavior,
685 : : bool recurse, bool recursing,
686 : : bool missing_ok, LOCKMODE lockmode);
687 : : static void ATPrepAlterColumnType(List **wqueue,
688 : : AlteredTableInfo *tab, Relation rel,
689 : : bool recurse, bool recursing,
690 : : AlterTableCmd *cmd, LOCKMODE lockmode,
691 : : AlterTableUtilityContext *context);
692 : : static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno);
693 : : static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
694 : : AlterTableCmd *cmd, LOCKMODE lockmode);
695 : : static void RememberAllDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype,
696 : : Relation rel, AttrNumber attnum, const char *colName);
697 : : static void RememberWholeRowDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype, Relation rel);
698 : : static void RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab);
699 : : static void RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab);
700 : : static void RememberStatisticsForRebuilding(Oid stxoid, AlteredTableInfo *tab);
701 : : static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab,
702 : : LOCKMODE lockmode);
703 : : static void ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, Oid ownerId,
704 : : char *cmd, List **wqueue, LOCKMODE lockmode,
705 : : bool rewrite);
706 : : static void RebuildConstraintComment(AlteredTableInfo *tab, AlterTablePass pass,
707 : : Oid objid, Relation rel, List *domname,
708 : : const char *conname);
709 : : static void TryReuseIndex(Oid oldId, IndexStmt *stmt);
710 : : static void TryReuseForeignKey(Oid oldId, Constraint *con);
711 : : static ObjectAddress ATExecAlterColumnGenericOptions(Relation rel, const char *colName,
712 : : List *options, LOCKMODE lockmode);
713 : : static void change_owner_fix_column_acls(Oid relationOid,
714 : : Oid oldOwnerId, Oid newOwnerId);
715 : : static void change_owner_recurse_to_sequences(Oid relationOid,
716 : : Oid newOwnerId, LOCKMODE lockmode);
717 : : static ObjectAddress ATExecClusterOn(Relation rel, const char *indexName,
718 : : LOCKMODE lockmode);
719 : : static void ATExecDropCluster(Relation rel, LOCKMODE lockmode);
720 : : static void ATPrepSetAccessMethod(AlteredTableInfo *tab, Relation rel, const char *amname);
721 : : static void ATExecSetAccessMethodNoStorage(Relation rel, Oid newAccessMethodId);
722 : : static void ATPrepChangePersistence(AlteredTableInfo *tab, Relation rel,
723 : : bool toLogged);
724 : : static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
725 : : const char *tablespacename, LOCKMODE lockmode);
726 : : static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
727 : : static void ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace);
728 : : static void ATExecSetRelOptions(Relation rel, List *defList,
729 : : AlterTableType operation,
730 : : LOCKMODE lockmode);
731 : : static void ATExecEnableDisableTrigger(Relation rel, const char *trigname,
732 : : char fires_when, bool skip_system, bool recurse,
733 : : LOCKMODE lockmode);
734 : : static void ATExecEnableDisableRule(Relation rel, const char *rulename,
735 : : char fires_when, LOCKMODE lockmode);
736 : : static void ATPrepChangeInherit(Relation child_rel);
737 : : static ObjectAddress ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode);
738 : : static ObjectAddress ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode);
739 : : static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid,
740 : : DependencyType deptype);
741 : : static ObjectAddress ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
742 : : static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
743 : : static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode);
744 : : static void ATExecGenericOptions(Relation rel, List *options);
745 : : static void ATExecSetRowSecurity(Relation rel, bool rls);
746 : : static void ATExecForceNoForceRowSecurity(Relation rel, bool force_rls);
747 : : static ObjectAddress ATExecSetCompression(Relation rel,
748 : : const char *column, Node *newValue, LOCKMODE lockmode);
749 : :
750 : : static void index_copy_data(Relation rel, RelFileLocator newrlocator);
751 : : static const char *storage_name(char c);
752 : :
753 : : static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
754 : : Oid oldRelOid, void *arg);
755 : : static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
756 : : Oid oldrelid, void *arg);
757 : : static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec);
758 : : static void ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNumber *partattrs,
759 : : List **partexprs, Oid *partopclass, Oid *partcollation,
760 : : PartitionStrategy strategy);
761 : : static void CreateInheritance(Relation child_rel, Relation parent_rel, bool ispartition);
762 : : static void RemoveInheritance(Relation child_rel, Relation parent_rel,
763 : : bool expect_detached);
764 : : static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
765 : : PartitionCmd *cmd,
766 : : AlterTableUtilityContext *context);
767 : : static void AttachPartitionEnsureIndexes(List **wqueue, Relation rel, Relation attachrel);
768 : : static void QueuePartitionConstraintValidation(List **wqueue, Relation scanrel,
769 : : List *partConstraint,
770 : : bool validate_default);
771 : : static void CloneRowTriggersToPartition(Relation parent, Relation partition);
772 : : static void DropClonedTriggersFromPartition(Oid partitionId);
773 : : static ObjectAddress ATExecDetachPartition(List **wqueue, AlteredTableInfo *tab,
774 : : Relation rel, RangeVar *name,
775 : : bool concurrent);
776 : : static void DetachPartitionFinalize(Relation rel, Relation partRel,
777 : : bool concurrent, Oid defaultPartOid);
778 : : static ObjectAddress ATExecDetachPartitionFinalize(Relation rel, RangeVar *name);
779 : : static ObjectAddress ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx,
780 : : RangeVar *name);
781 : : static void validatePartitionedIndex(Relation partedIdx, Relation partedTbl);
782 : : static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
783 : : Relation partitionTbl);
784 : : static void verifyPartitionIndexNotNull(IndexInfo *iinfo, Relation partition);
785 : : static List *GetParentedForeignKeyRefs(Relation partition);
786 : : static void ATDetachCheckNoForeignKeyRefs(Relation partition);
787 : : static char GetAttributeCompression(Oid atttypid, const char *compression);
788 : : static char GetAttributeStorage(Oid atttypid, const char *storagemode);
789 : :
790 : : static void ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
791 : : PartitionCmd *cmd, AlterTableUtilityContext *context);
792 : : static void ATExecSplitPartition(List **wqueue, AlteredTableInfo *tab,
793 : : Relation rel, PartitionCmd *cmd,
794 : : AlterTableUtilityContext *context);
795 : : static List *collectPartitionIndexExtDeps(List *partitionOids);
796 : : static void applyPartitionIndexExtDeps(Oid newPartOid, List *extDepState);
797 : : static void freePartitionIndexExtDeps(List *extDepState);
798 : :
799 : : /* ----------------------------------------------------------------
800 : : * DefineRelation
801 : : * Creates a new relation.
802 : : *
803 : : * stmt carries parsetree information from an ordinary CREATE TABLE statement.
804 : : * The other arguments are used to extend the behavior for other cases:
805 : : * relkind: relkind to assign to the new relation
806 : : * ownerId: if not InvalidOid, use this as the new relation's owner.
807 : : * typaddress: if not null, it's set to the pg_type entry's address.
808 : : * queryString: for error reporting
809 : : *
810 : : * Note that permissions checks are done against current user regardless of
811 : : * ownerId. A nonzero ownerId is used when someone is creating a relation
812 : : * "on behalf of" someone else, so we still want to see that the current user
813 : : * has permissions to do it.
814 : : *
815 : : * If successful, returns the address of the new relation.
816 : : * ----------------------------------------------------------------
817 : : */
818 : : ObjectAddress
819 : 42566 : DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
820 : : ObjectAddress *typaddress, const char *queryString)
821 : : {
822 : : char relname[NAMEDATALEN];
823 : : Oid namespaceId;
824 : : Oid relationId;
825 : : Oid tablespaceId;
826 : : Relation rel;
827 : : TupleDesc descriptor;
828 : : List *inheritOids;
829 : : List *old_constraints;
830 : : List *old_notnulls;
831 : : List *rawDefaults;
832 : : List *cookedDefaults;
833 : : List *nncols;
834 : 42566 : List *connames = NIL;
835 : : Datum reloptions;
836 : : ListCell *listptr;
837 : : AttrNumber attnum;
838 : : bool partitioned;
839 : 42566 : const char *const validnsps[] = HEAP_RELOPT_NAMESPACES;
840 : : Oid ofTypeId;
841 : : ObjectAddress address;
842 : : LOCKMODE parentLockmode;
843 : 42566 : Oid accessMethodId = InvalidOid;
844 : :
845 : : /*
846 : : * Truncate relname to appropriate length (probably a waste of time, as
847 : : * parser should have done this already).
848 : : */
849 : 42566 : strlcpy(relname, stmt->relation->relname, NAMEDATALEN);
850 : :
851 : : /*
852 : : * Check consistency of arguments
853 : : */
854 [ + + ]: 42566 : if (stmt->oncommit != ONCOMMIT_NOOP
855 [ + + ]: 128 : && stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
856 [ + - ]: 8 : ereport(ERROR,
857 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
858 : : errmsg("ON COMMIT can only be used on temporary tables")));
859 : :
860 [ + + ]: 42558 : if (stmt->partspec != NULL)
861 : : {
862 [ - + ]: 3688 : if (relkind != RELKIND_RELATION)
863 [ # # ]: 0 : elog(ERROR, "unexpected relkind: %d", (int) relkind);
864 : :
865 : 3688 : relkind = RELKIND_PARTITIONED_TABLE;
866 : 3688 : partitioned = true;
867 : : }
868 : : else
869 : 38870 : partitioned = false;
870 : :
871 [ + + ]: 42558 : if (relkind == RELKIND_PARTITIONED_TABLE &&
872 [ + + ]: 3688 : stmt->relation->relpersistence == RELPERSISTENCE_UNLOGGED)
873 [ + - ]: 4 : ereport(ERROR,
874 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
875 : : errmsg("partitioned tables cannot be unlogged")));
876 : :
877 : : /*
878 : : * Look up the namespace in which we are supposed to create the relation,
879 : : * check we have permission to create there, lock it against concurrent
880 : : * drop, and mark stmt->relation as RELPERSISTENCE_TEMP if a temporary
881 : : * namespace is selected.
882 : : */
883 : : namespaceId =
884 : 42554 : RangeVarGetAndCheckCreationNamespace(stmt->relation, NoLock, NULL);
885 : :
886 : : /*
887 : : * Security check: disallow creating temp tables from security-restricted
888 : : * code. This is needed because calling code might not expect untrusted
889 : : * tables to appear in pg_temp at the front of its search path.
890 : : */
891 [ + + ]: 42554 : if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
892 [ - + ]: 2358 : && InSecurityRestrictedOperation())
893 [ # # ]: 0 : ereport(ERROR,
894 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
895 : : errmsg("cannot create temporary table within security-restricted operation")));
896 : :
897 : : /*
898 : : * Determine the lockmode to use when scanning parents. A self-exclusive
899 : : * lock is needed here.
900 : : *
901 : : * For regular inheritance, if two backends attempt to add children to the
902 : : * same parent simultaneously, and that parent has no pre-existing
903 : : * children, then both will attempt to update the parent's relhassubclass
904 : : * field, leading to a "tuple concurrently updated" error. Also, this
905 : : * interlocks against a concurrent ANALYZE on the parent table, which
906 : : * might otherwise be attempting to clear the parent's relhassubclass
907 : : * field, if its previous children were recently dropped.
908 : : *
909 : : * If the child table is a partition, then we instead grab an exclusive
910 : : * lock on the parent because its partition descriptor will be changed by
911 : : * addition of the new partition.
912 : : */
913 [ + + ]: 42554 : parentLockmode = (stmt->partbound != NULL ? AccessExclusiveLock :
914 : : ShareUpdateExclusiveLock);
915 : :
916 : : /* Determine the list of OIDs of the parents. */
917 : 42554 : inheritOids = NIL;
918 [ + + + + : 50827 : foreach(listptr, stmt->inhRelations)
+ + ]
919 : : {
920 : 8273 : RangeVar *rv = (RangeVar *) lfirst(listptr);
921 : : Oid parentOid;
922 : :
923 : 8273 : parentOid = RangeVarGetRelid(rv, parentLockmode, false);
924 : :
925 : : /*
926 : : * Reject duplications in the list of parents.
927 : : */
928 [ - + ]: 8273 : if (list_member_oid(inheritOids, parentOid))
929 [ # # ]: 0 : ereport(ERROR,
930 : : (errcode(ERRCODE_DUPLICATE_TABLE),
931 : : errmsg("relation \"%s\" would be inherited from more than once",
932 : : get_rel_name(parentOid))));
933 : :
934 : 8273 : inheritOids = lappend_oid(inheritOids, parentOid);
935 : : }
936 : :
937 : : /*
938 : : * Select tablespace to use: an explicitly indicated one, or (in the case
939 : : * of a partitioned table) the parent's, if it has one.
940 : : */
941 [ + + ]: 42554 : if (stmt->tablespacename)
942 : : {
943 : 92 : tablespaceId = get_tablespace_oid(stmt->tablespacename, false);
944 : :
945 [ + + + + ]: 88 : if (partitioned && tablespaceId == MyDatabaseTableSpace)
946 [ + - ]: 4 : ereport(ERROR,
947 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
948 : : errmsg("cannot specify default tablespace for partitioned relations")));
949 : : }
950 [ + + ]: 42462 : else if (stmt->partbound)
951 : : {
952 : : Assert(list_length(inheritOids) == 1);
953 : 6568 : tablespaceId = get_rel_tablespace(linitial_oid(inheritOids));
954 : : }
955 : : else
956 : 35894 : tablespaceId = InvalidOid;
957 : :
958 : : /* still nothing? use the default */
959 [ + + ]: 42546 : if (!OidIsValid(tablespaceId))
960 : 42424 : tablespaceId = GetDefaultTablespace(stmt->relation->relpersistence,
961 : : partitioned);
962 : :
963 : : /* Check permissions except when using database's default */
964 [ + + + + ]: 42542 : if (OidIsValid(tablespaceId) && tablespaceId != MyDatabaseTableSpace)
965 : : {
966 : : AclResult aclresult;
967 : :
968 : 138 : aclresult = object_aclcheck(TableSpaceRelationId, tablespaceId, GetUserId(),
969 : : ACL_CREATE);
970 [ + + ]: 138 : if (aclresult != ACLCHECK_OK)
971 : 3 : aclcheck_error(aclresult, OBJECT_TABLESPACE,
972 : 3 : get_tablespace_name(tablespaceId));
973 : : }
974 : :
975 : : /* In all cases disallow placing user relations in pg_global */
976 [ + + ]: 42539 : if (tablespaceId == GLOBALTABLESPACE_OID)
977 [ + - ]: 12 : ereport(ERROR,
978 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
979 : : errmsg("only shared relations can be placed in pg_global tablespace")));
980 : :
981 : : /* Identify user ID that will own the table */
982 [ + + ]: 42527 : if (!OidIsValid(ownerId))
983 : 42372 : ownerId = GetUserId();
984 : :
985 : : /*
986 : : * Parse and validate reloptions, if any.
987 : : */
988 : 42527 : reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
989 : : true, false);
990 : :
991 [ + + + ]: 42515 : switch (relkind)
992 : : {
993 : 10849 : case RELKIND_VIEW:
994 : 10849 : (void) view_reloptions(reloptions, true);
995 : 10837 : break;
996 : 3672 : case RELKIND_PARTITIONED_TABLE:
997 : 3672 : (void) partitioned_table_reloptions(reloptions, true);
998 : 3668 : break;
999 : 27994 : default:
1000 : 27994 : (void) heap_reloptions(relkind, reloptions, true);
1001 : : }
1002 : :
1003 [ + + ]: 42435 : if (stmt->ofTypename)
1004 : : {
1005 : : AclResult aclresult;
1006 : :
1007 : 57 : ofTypeId = typenameTypeId(NULL, stmt->ofTypename);
1008 : :
1009 : 57 : aclresult = object_aclcheck(TypeRelationId, ofTypeId, GetUserId(), ACL_USAGE);
1010 [ + + ]: 57 : if (aclresult != ACLCHECK_OK)
1011 : 4 : aclcheck_error_type(aclresult, ofTypeId);
1012 : : }
1013 : : else
1014 : 42378 : ofTypeId = InvalidOid;
1015 : :
1016 : : /*
1017 : : * Look up inheritance ancestors and generate relation schema, including
1018 : : * inherited attributes. (Note that stmt->tableElts is destructively
1019 : : * modified by MergeAttributes.)
1020 : : */
1021 : 42267 : stmt->tableElts =
1022 : 42431 : MergeAttributes(stmt->tableElts, inheritOids,
1023 : 42431 : stmt->relation->relpersistence,
1024 : 42431 : stmt->partbound != NULL,
1025 : : &old_constraints, &old_notnulls);
1026 : :
1027 : : /*
1028 : : * NB: The defaults and constraints we just inherited are already cooked,
1029 : : * so they don't get the USAGE checks that AddRelationNewConstraints()
1030 : : * applies to raw ones. That's intentional: the parent's own catalog
1031 : : * entries already depend on those types, so copying them pins nothing
1032 : : * new, and creating a child requires owning the parent, anyway.
1033 : : */
1034 : :
1035 : : /*
1036 : : * Create a tuple descriptor from the relation schema. Note that this
1037 : : * deals with column names, types, and in-descriptor NOT NULL flags, but
1038 : : * not default values, NOT NULL or CHECK constraints; we handle those
1039 : : * below.
1040 : : */
1041 : 42267 : descriptor = BuildDescForRelation(stmt->tableElts);
1042 : :
1043 : : /*
1044 : : * Find columns with default values and prepare for insertion of the
1045 : : * defaults. Pre-cooked (that is, inherited) defaults go into a list of
1046 : : * CookedConstraint structs that we'll pass to heap_create_with_catalog,
1047 : : * while raw defaults go into a list of RawColumnDefault structs that will
1048 : : * be processed by AddRelationNewConstraints. (We can't deal with raw
1049 : : * expressions until we can do transformExpr.)
1050 : : */
1051 : 42235 : rawDefaults = NIL;
1052 : 42235 : cookedDefaults = NIL;
1053 : 42235 : attnum = 0;
1054 : :
1055 [ + + + + : 210239 : foreach(listptr, stmt->tableElts)
+ + ]
1056 : : {
1057 : 168004 : ColumnDef *colDef = lfirst(listptr);
1058 : :
1059 : 168004 : attnum++;
1060 [ + + ]: 168004 : if (colDef->raw_default != NULL)
1061 : : {
1062 : : RawColumnDefault *rawEnt;
1063 : :
1064 : : Assert(colDef->cooked_default == NULL);
1065 : :
1066 : 2253 : rawEnt = palloc_object(RawColumnDefault);
1067 : 2253 : rawEnt->attnum = attnum;
1068 : 2253 : rawEnt->raw_default = colDef->raw_default;
1069 : 2253 : rawEnt->generated = colDef->generated;
1070 : 2253 : rawDefaults = lappend(rawDefaults, rawEnt);
1071 : : }
1072 [ + + ]: 165751 : else if (colDef->cooked_default != NULL)
1073 : : {
1074 : : CookedConstraint *cooked;
1075 : :
1076 : 374 : cooked = palloc_object(CookedConstraint);
1077 : 374 : cooked->contype = CONSTR_DEFAULT;
1078 : 374 : cooked->conoid = InvalidOid; /* until created */
1079 : 374 : cooked->name = NULL;
1080 : 374 : cooked->attnum = attnum;
1081 : 374 : cooked->expr = colDef->cooked_default;
1082 : 374 : cooked->is_enforced = true;
1083 : 374 : cooked->skip_validation = false;
1084 : 374 : cooked->is_local = true; /* not used for defaults */
1085 : 374 : cooked->inhcount = 0; /* ditto */
1086 : 374 : cooked->is_no_inherit = false;
1087 : 374 : cookedDefaults = lappend(cookedDefaults, cooked);
1088 : : }
1089 : : }
1090 : :
1091 : 42235 : TupleDescFinalize(descriptor);
1092 : :
1093 : : /*
1094 : : * For relations with table AM and partitioned tables, select access
1095 : : * method to use: an explicitly indicated one, or (in the case of a
1096 : : * partitioned table) the parent's, if it has one.
1097 : : */
1098 [ + + ]: 42235 : if (stmt->accessMethod != NULL)
1099 : : {
1100 : : Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE);
1101 : 87 : accessMethodId = get_table_am_oid(stmt->accessMethod, false);
1102 : : }
1103 [ + + + - : 42148 : else if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE)
+ + + + ]
1104 : : {
1105 [ + + ]: 27241 : if (stmt->partbound)
1106 : : {
1107 : : Assert(list_length(inheritOids) == 1);
1108 : 6452 : accessMethodId = get_rel_relam(linitial_oid(inheritOids));
1109 : : }
1110 : :
1111 [ + + + - : 27241 : if (RELKIND_HAS_TABLE_AM(relkind) && !OidIsValid(accessMethodId))
+ + + + ]
1112 : 23560 : accessMethodId = get_table_am_oid(default_table_access_method, false);
1113 : : }
1114 : :
1115 : : /*
1116 : : * Create the relation. Inherited defaults and CHECK constraints are
1117 : : * passed in for immediate handling --- since they don't need parsing,
1118 : : * they can be stored immediately.
1119 : : */
1120 : 42223 : relationId = heap_create_with_catalog(relname,
1121 : : namespaceId,
1122 : : tablespaceId,
1123 : : InvalidOid,
1124 : : InvalidOid,
1125 : : ofTypeId,
1126 : : ownerId,
1127 : : accessMethodId,
1128 : : descriptor,
1129 : : list_concat(cookedDefaults,
1130 : : old_constraints),
1131 : : relkind,
1132 : 42223 : stmt->relation->relpersistence,
1133 : : false,
1134 : : false,
1135 : : stmt->oncommit,
1136 : : reloptions,
1137 : : true,
1138 : : allowSystemTableMods,
1139 : : false,
1140 : : InvalidOid,
1141 : : typaddress);
1142 : :
1143 : : /*
1144 : : * We must bump the command counter to make the newly-created relation
1145 : : * tuple visible for opening.
1146 : : */
1147 : 42187 : CommandCounterIncrement();
1148 : :
1149 : : /*
1150 : : * Open the new relation and acquire exclusive lock on it. This isn't
1151 : : * really necessary for locking out other backends (since they can't see
1152 : : * the new rel anyway until we commit), but it keeps the lock manager from
1153 : : * complaining about deadlock risks.
1154 : : */
1155 : 42187 : rel = relation_open(relationId, AccessExclusiveLock);
1156 : :
1157 : : /*
1158 : : * Now add any newly specified column default and generation expressions
1159 : : * to the new relation. These are passed to us in the form of raw
1160 : : * parsetrees; we need to transform them to executable expression trees
1161 : : * before they can be added. The most convenient way to do that is to
1162 : : * apply the parser's transformExpr routine, but transformExpr doesn't
1163 : : * work unless we have a pre-existing relation. So, the transformation has
1164 : : * to be postponed to this final step of CREATE TABLE.
1165 : : *
1166 : : * This needs to be before processing the partitioning clauses because
1167 : : * those could refer to generated columns.
1168 : : */
1169 [ + + ]: 42187 : if (rawDefaults)
1170 : 1904 : AddRelationNewConstraints(rel, rawDefaults, NIL,
1171 : : true, true, false, queryString);
1172 : :
1173 : : /*
1174 : : * Make column generation expressions visible for use by partitioning.
1175 : : */
1176 : 42055 : CommandCounterIncrement();
1177 : :
1178 : : /* Process and store partition bound, if any. */
1179 [ + + ]: 42055 : if (stmt->partbound)
1180 : : {
1181 : : PartitionBoundSpec *bound;
1182 : : ParseState *pstate;
1183 : 6516 : Oid parentId = linitial_oid(inheritOids),
1184 : : defaultPartOid;
1185 : : Relation parent,
1186 : 6516 : defaultRel = NULL;
1187 : : ParseNamespaceItem *nsitem;
1188 : :
1189 : : /* Already have strong enough lock on the parent */
1190 : 6516 : parent = table_open(parentId, NoLock);
1191 : :
1192 : : /*
1193 : : * We are going to try to validate the partition bound specification
1194 : : * against the partition key of parentRel, so it better have one.
1195 : : */
1196 [ + + ]: 6516 : if (parent->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
1197 [ + - ]: 12 : ereport(ERROR,
1198 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
1199 : : errmsg("\"%s\" is not partitioned",
1200 : : RelationGetRelationName(parent))));
1201 : :
1202 : : /*
1203 : : * The partition constraint of the default partition depends on the
1204 : : * partition bounds of every other partition. It is possible that
1205 : : * another backend might be about to execute a query on the default
1206 : : * partition table, and that the query relies on previously cached
1207 : : * default partition constraints. We must therefore take a table lock
1208 : : * strong enough to prevent all queries on the default partition from
1209 : : * proceeding until we commit and send out a shared-cache-inval notice
1210 : : * that will make them update their index lists.
1211 : : *
1212 : : * Order of locking: The relation being added won't be visible to
1213 : : * other backends until it is committed, hence here in
1214 : : * DefineRelation() the order of locking the default partition and the
1215 : : * relation being added does not matter. But at all other places we
1216 : : * need to lock the default relation before we lock the relation being
1217 : : * added or removed i.e. we should take the lock in same order at all
1218 : : * the places such that lock parent, lock default partition and then
1219 : : * lock the partition so as to avoid a deadlock.
1220 : : */
1221 : : defaultPartOid =
1222 : 6504 : get_default_oid_from_partdesc(RelationGetPartitionDesc(parent,
1223 : : true));
1224 [ + + ]: 6504 : if (OidIsValid(defaultPartOid))
1225 : 255 : defaultRel = table_open(defaultPartOid, AccessExclusiveLock);
1226 : :
1227 : : /* Transform the bound values */
1228 : 6504 : pstate = make_parsestate(NULL);
1229 : 6504 : pstate->p_sourcetext = queryString;
1230 : :
1231 : : /*
1232 : : * Add an nsitem containing this relation, so that transformExpr
1233 : : * called on partition bound expressions is able to report errors
1234 : : * using a proper context.
1235 : : */
1236 : 6504 : nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock,
1237 : : NULL, false, false);
1238 : 6504 : addNSItemToQuery(pstate, nsitem, false, true, true);
1239 : :
1240 : 6504 : bound = transformPartitionBound(pstate, parent, stmt->partbound);
1241 : :
1242 : : /*
1243 : : * Check first that the new partition's bound is valid and does not
1244 : : * overlap with any of existing partitions of the parent.
1245 : : */
1246 : 6368 : check_new_partition_bound(relname, parent, bound, pstate);
1247 : :
1248 : : /*
1249 : : * If the default partition exists, its partition constraints will
1250 : : * change after the addition of this new partition such that it won't
1251 : : * allow any row that qualifies for this new partition. So, check that
1252 : : * the existing data in the default partition satisfies the constraint
1253 : : * as it will exist after adding this partition.
1254 : : */
1255 [ + + ]: 6292 : if (OidIsValid(defaultPartOid))
1256 : : {
1257 : 235 : check_default_partition_contents(parent, defaultRel, bound);
1258 : : /* Keep the lock until commit. */
1259 : 223 : table_close(defaultRel, NoLock);
1260 : : }
1261 : :
1262 : : /* Update the pg_class entry. */
1263 : 6280 : StorePartitionBound(rel, parent, bound);
1264 : :
1265 : 6280 : table_close(parent, NoLock);
1266 : : }
1267 : :
1268 : : /* Store inheritance information for new rel. */
1269 : 41819 : StoreCatalogInheritance(relationId, inheritOids, stmt->partbound != NULL);
1270 : :
1271 : : /*
1272 : : * Process the partitioning specification (if any) and store the partition
1273 : : * key information into the catalog.
1274 : : */
1275 [ + + ]: 41819 : if (partitioned)
1276 : : {
1277 : : ParseState *pstate;
1278 : : int partnatts;
1279 : : AttrNumber partattrs[PARTITION_MAX_KEYS];
1280 : : Oid partopclass[PARTITION_MAX_KEYS];
1281 : : Oid partcollation[PARTITION_MAX_KEYS];
1282 : 3668 : List *partexprs = NIL;
1283 : :
1284 : 3668 : pstate = make_parsestate(NULL);
1285 : 3668 : pstate->p_sourcetext = queryString;
1286 : :
1287 : 3668 : partnatts = list_length(stmt->partspec->partParams);
1288 : :
1289 : : /* Protect fixed-size arrays here and in executor */
1290 [ - + ]: 3668 : if (partnatts > PARTITION_MAX_KEYS)
1291 [ # # ]: 0 : ereport(ERROR,
1292 : : (errcode(ERRCODE_TOO_MANY_COLUMNS),
1293 : : errmsg("cannot partition using more than %d columns",
1294 : : PARTITION_MAX_KEYS)));
1295 : :
1296 : : /*
1297 : : * We need to transform the raw parsetrees corresponding to partition
1298 : : * expressions into executable expression trees. Like column defaults
1299 : : * and CHECK constraints, we could not have done the transformation
1300 : : * earlier.
1301 : : */
1302 : 3668 : stmt->partspec = transformPartitionSpec(rel, stmt->partspec);
1303 : :
1304 : 3648 : ComputePartitionAttrs(pstate, rel, stmt->partspec->partParams,
1305 : : partattrs, &partexprs, partopclass,
1306 : 3648 : partcollation, stmt->partspec->strategy);
1307 : :
1308 : 3560 : StorePartitionKey(rel, stmt->partspec->strategy, partnatts, partattrs,
1309 : : partexprs,
1310 : : partopclass, partcollation);
1311 : :
1312 : : /* make it all visible */
1313 : 3556 : CommandCounterIncrement();
1314 : : }
1315 : :
1316 : : /*
1317 : : * If we're creating a partition, create now all the indexes, triggers,
1318 : : * FKs defined in the parent.
1319 : : *
1320 : : * We can't do it earlier, because DefineIndex wants to know the partition
1321 : : * key which we just stored.
1322 : : */
1323 [ + + ]: 41707 : if (stmt->partbound)
1324 : : {
1325 : 6276 : Oid parentId = linitial_oid(inheritOids);
1326 : : Relation parent;
1327 : : List *idxlist;
1328 : : ListCell *cell;
1329 : :
1330 : : /* Already have strong enough lock on the parent */
1331 : 6276 : parent = table_open(parentId, NoLock);
1332 : 6276 : idxlist = RelationGetIndexList(parent);
1333 : :
1334 : : /*
1335 : : * For each index in the parent table, create one in the partition
1336 : : */
1337 [ + + + + : 7366 : foreach(cell, idxlist)
+ + ]
1338 : : {
1339 : 1102 : Relation idxRel = index_open(lfirst_oid(cell), AccessShareLock);
1340 : : AttrMap *attmap;
1341 : : IndexStmt *idxstmt;
1342 : : Oid constraintOid;
1343 : :
1344 [ + + ]: 1102 : if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
1345 : : {
1346 [ + + ]: 24 : if (idxRel->rd_index->indisunique)
1347 [ + - ]: 8 : ereport(ERROR,
1348 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
1349 : : errmsg("cannot create foreign partition of partitioned table \"%s\"",
1350 : : RelationGetRelationName(parent)),
1351 : : errdetail("Table \"%s\" contains indexes that are unique.",
1352 : : RelationGetRelationName(parent))));
1353 : : else
1354 : : {
1355 : 16 : index_close(idxRel, AccessShareLock);
1356 : 16 : continue;
1357 : : }
1358 : : }
1359 : :
1360 : 1078 : attmap = build_attrmap_by_name(RelationGetDescr(rel),
1361 : : RelationGetDescr(parent),
1362 : : false);
1363 : : idxstmt =
1364 : 1078 : generateClonedIndexStmt(NULL, idxRel,
1365 : : attmap, &constraintOid);
1366 : 1078 : DefineIndex(NULL,
1367 : : RelationGetRelid(rel),
1368 : : idxstmt,
1369 : : InvalidOid,
1370 : : RelationGetRelid(idxRel),
1371 : : constraintOid,
1372 : : -1,
1373 : : false, false, false, false, false);
1374 : :
1375 : 1074 : index_close(idxRel, AccessShareLock);
1376 : : }
1377 : :
1378 : 6264 : list_free(idxlist);
1379 : :
1380 : : /*
1381 : : * If there are any row-level triggers, clone them to the new
1382 : : * partition.
1383 : : */
1384 [ + + ]: 6264 : if (parent->trigdesc != NULL)
1385 : 260 : CloneRowTriggersToPartition(parent, rel);
1386 : :
1387 : : /*
1388 : : * And foreign keys too. Note that because we're freshly creating the
1389 : : * table, there is no need to verify these new constraints.
1390 : : */
1391 : 6264 : CloneForeignKeyConstraints(NULL, parent, rel);
1392 : :
1393 : 6264 : table_close(parent, NoLock);
1394 : : }
1395 : :
1396 : : /*
1397 : : * Now add any newly specified CHECK constraints to the new relation. Same
1398 : : * as for defaults above, but these need to come after partitioning is set
1399 : : * up. We save the constraint names that were used, to avoid dupes below.
1400 : : */
1401 [ + + ]: 41695 : if (stmt->constraints)
1402 : : {
1403 : : List *conlist;
1404 : :
1405 : 526 : conlist = AddRelationNewConstraints(rel, NIL, stmt->constraints,
1406 : : true, true, false, queryString);
1407 [ + + + + : 1581 : foreach_ptr(CookedConstraint, cons, conlist)
+ + ]
1408 : : {
1409 [ + - ]: 577 : if (cons->name != NULL)
1410 : 577 : connames = lappend(connames, cons->name);
1411 : : }
1412 : : }
1413 : :
1414 : : /*
1415 : : * Finally, merge the not-null constraints that are declared directly with
1416 : : * those that come from parent relations (making sure to count inheritance
1417 : : * appropriately for each), create them, and set the attnotnull flag on
1418 : : * columns that don't yet have it.
1419 : : */
1420 : 41671 : nncols = AddRelationNotNullConstraints(rel, stmt->nnconstraints,
1421 : : old_notnulls, connames);
1422 [ + + + + : 93555 : foreach_int(attrnum, nncols)
+ + ]
1423 : 10317 : set_attnotnull(NULL, rel, attrnum, false);
1424 : :
1425 : 41619 : ObjectAddressSet(address, RelationRelationId, relationId);
1426 : :
1427 : : /*
1428 : : * Clean up. We keep lock on new relation (although it shouldn't be
1429 : : * visible to anyone else anyway, until commit).
1430 : : */
1431 : 41619 : relation_close(rel, NoLock);
1432 : :
1433 : 41619 : return address;
1434 : : }
1435 : :
1436 : : /*
1437 : : * BuildDescForRelation
1438 : : *
1439 : : * Given a list of ColumnDef nodes, build a TupleDesc.
1440 : : *
1441 : : * Note: This is only for the limited purpose of table and view creation. Not
1442 : : * everything is filled in. A real tuple descriptor should be obtained from
1443 : : * the relcache.
1444 : : */
1445 : : TupleDesc
1446 : 44932 : BuildDescForRelation(const List *columns)
1447 : : {
1448 : : int natts;
1449 : : AttrNumber attnum;
1450 : : ListCell *l;
1451 : : TupleDesc desc;
1452 : : char *attname;
1453 : : Oid atttypid;
1454 : : int32 atttypmod;
1455 : : Oid attcollation;
1456 : : int attdim;
1457 : :
1458 : : /*
1459 : : * allocate a new tuple descriptor
1460 : : */
1461 : 44932 : natts = list_length(columns);
1462 : 44932 : desc = CreateTemplateTupleDesc(natts);
1463 : :
1464 : 44932 : attnum = 0;
1465 : :
1466 [ + + + + : 216530 : foreach(l, columns)
+ + ]
1467 : : {
1468 : 171638 : ColumnDef *entry = lfirst(l);
1469 : : AclResult aclresult;
1470 : : Form_pg_attribute att;
1471 : :
1472 : : /*
1473 : : * for each entry in the list, get the name and type information from
1474 : : * the list and have TupleDescInitEntry fill in the attribute
1475 : : * information we need.
1476 : : */
1477 : 171638 : attnum++;
1478 : :
1479 : 171638 : attname = entry->colname;
1480 : 171638 : typenameTypeIdAndMod(NULL, entry->typeName, &atttypid, &atttypmod);
1481 : :
1482 : 171638 : aclresult = object_aclcheck(TypeRelationId, atttypid, GetUserId(), ACL_USAGE);
1483 [ + + ]: 171638 : if (aclresult != ACLCHECK_OK)
1484 : 28 : aclcheck_error_type(aclresult, atttypid);
1485 : :
1486 : 171610 : attcollation = GetColumnDefCollation(NULL, entry, atttypid);
1487 : 171610 : attdim = list_length(entry->typeName->arrayBounds);
1488 [ - + ]: 171610 : if (attdim > PG_INT16_MAX)
1489 [ # # ]: 0 : ereport(ERROR,
1490 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
1491 : : errmsg("too many array dimensions"));
1492 : :
1493 [ - + ]: 171610 : if (entry->typeName->setof)
1494 [ # # ]: 0 : ereport(ERROR,
1495 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
1496 : : errmsg("column \"%s\" cannot be declared SETOF",
1497 : : attname)));
1498 : :
1499 : 171610 : TupleDescInitEntry(desc, attnum, attname,
1500 : : atttypid, atttypmod, attdim);
1501 : 171610 : att = TupleDescAttr(desc, attnum - 1);
1502 : :
1503 : : /* Override TupleDescInitEntry's settings as requested */
1504 : 171610 : TupleDescInitEntryCollation(desc, attnum, attcollation);
1505 : :
1506 : : /* Fill in additional stuff not handled by TupleDescInitEntry */
1507 : 171610 : att->attnotnull = entry->is_not_null;
1508 : 171610 : att->attislocal = entry->is_local;
1509 : 171610 : att->attinhcount = entry->inhcount;
1510 : 171610 : att->attidentity = entry->identity;
1511 : 171610 : att->attgenerated = entry->generated;
1512 : 171610 : att->attcompression = GetAttributeCompression(att->atttypid, entry->compression);
1513 [ + + ]: 171602 : if (entry->storage)
1514 : 17017 : att->attstorage = entry->storage;
1515 [ + + ]: 154585 : else if (entry->storage_name)
1516 : 39 : att->attstorage = GetAttributeStorage(att->atttypid, entry->storage_name);
1517 : :
1518 : 171598 : populate_compact_attribute(desc, attnum - 1);
1519 : : }
1520 : :
1521 : 44892 : TupleDescFinalize(desc);
1522 : :
1523 : 44892 : return desc;
1524 : : }
1525 : :
1526 : : /*
1527 : : * Emit the right error or warning message for a "DROP" command issued on a
1528 : : * non-existent relation
1529 : : */
1530 : : static void
1531 : 641 : DropErrorMsgNonExistent(RangeVar *rel, char rightkind, bool missing_ok)
1532 : : {
1533 : : const struct dropmsgstrings *rentry;
1534 : :
1535 [ + + + + ]: 721 : if (rel->schemaname != NULL &&
1536 : 80 : !OidIsValid(LookupNamespaceNoError(rel->schemaname)))
1537 : : {
1538 [ - + ]: 28 : if (!missing_ok)
1539 : : {
1540 [ # # ]: 0 : ereport(ERROR,
1541 : : (errcode(ERRCODE_UNDEFINED_SCHEMA),
1542 : : errmsg("schema \"%s\" does not exist", rel->schemaname)));
1543 : : }
1544 : : else
1545 : : {
1546 [ + - ]: 28 : ereport(NOTICE,
1547 : : (errmsg("schema \"%s\" does not exist, skipping",
1548 : : rel->schemaname)));
1549 : : }
1550 : 28 : return;
1551 : : }
1552 : :
1553 [ + - ]: 898 : for (rentry = dropmsgstringarray; rentry->kind != '\0'; rentry++)
1554 : : {
1555 [ + + ]: 898 : if (rentry->kind == rightkind)
1556 : : {
1557 [ + + ]: 613 : if (!missing_ok)
1558 : : {
1559 [ + - ]: 90 : ereport(ERROR,
1560 : : (errcode(rentry->nonexistent_code),
1561 : : errmsg(rentry->nonexistent_msg, rel->relname)));
1562 : : }
1563 : : else
1564 : : {
1565 [ + - ]: 523 : ereport(NOTICE, (errmsg(rentry->skipping_msg, rel->relname)));
1566 : 523 : break;
1567 : : }
1568 : : }
1569 : : }
1570 : :
1571 : : Assert(rentry->kind != '\0'); /* Should be impossible */
1572 : : }
1573 : :
1574 : : /*
1575 : : * Emit the right error message for a "DROP" command issued on a
1576 : : * relation of the wrong type
1577 : : */
1578 : : static void
1579 : 4 : DropErrorMsgWrongType(const char *relname, char wrongkind, char rightkind)
1580 : : {
1581 : : const struct dropmsgstrings *rentry;
1582 : : const struct dropmsgstrings *wentry;
1583 : :
1584 [ + - ]: 4 : for (rentry = dropmsgstringarray; rentry->kind != '\0'; rentry++)
1585 [ + - ]: 4 : if (rentry->kind == rightkind)
1586 : 4 : break;
1587 : : Assert(rentry->kind != '\0');
1588 : :
1589 [ + - ]: 40 : for (wentry = dropmsgstringarray; wentry->kind != '\0'; wentry++)
1590 [ + + ]: 40 : if (wentry->kind == wrongkind)
1591 : 4 : break;
1592 : : /* wrongkind could be something we don't have in our table... */
1593 : :
1594 [ + - + - ]: 4 : ereport(ERROR,
1595 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
1596 : : errmsg(rentry->nota_msg, relname),
1597 : : (wentry->kind != '\0') ? errhint("%s", _(wentry->drophint_msg)) : 0));
1598 : : }
1599 : :
1600 : : /*
1601 : : * RemoveRelations
1602 : : * Implements DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW,
1603 : : * DROP MATERIALIZED VIEW, DROP FOREIGN TABLE, DROP PROPERTY GRAPH
1604 : : */
1605 : : void
1606 : 12001 : RemoveRelations(DropStmt *drop)
1607 : : {
1608 : : ObjectAddresses *objects;
1609 : : char relkind;
1610 : : ListCell *cell;
1611 : 12001 : int flags = 0;
1612 : 12001 : LOCKMODE lockmode = AccessExclusiveLock;
1613 : :
1614 : : /* DROP CONCURRENTLY uses a weaker lock, and has some restrictions */
1615 [ + + ]: 12001 : if (drop->concurrent)
1616 : : {
1617 : : /*
1618 : : * Note that for temporary relations this lock may get upgraded later
1619 : : * on, but as no other session can access a temporary relation, this
1620 : : * is actually fine.
1621 : : */
1622 : 111 : lockmode = ShareUpdateExclusiveLock;
1623 : : Assert(drop->removeType == OBJECT_INDEX);
1624 [ + + ]: 111 : if (list_length(drop->objects) != 1)
1625 [ + - ]: 4 : ereport(ERROR,
1626 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1627 : : errmsg("DROP INDEX CONCURRENTLY does not support dropping multiple objects")));
1628 [ - + ]: 107 : if (drop->behavior == DROP_CASCADE)
1629 [ # # ]: 0 : ereport(ERROR,
1630 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1631 : : errmsg("DROP INDEX CONCURRENTLY does not support CASCADE")));
1632 : : }
1633 : :
1634 : : /*
1635 : : * First we identify all the relations, then we delete them in a single
1636 : : * performMultipleDeletions() call. This is to avoid unwanted DROP
1637 : : * RESTRICT errors if one of the relations depends on another.
1638 : : */
1639 : :
1640 : : /* Determine required relkind */
1641 [ + + + + : 11997 : switch (drop->removeType)
+ + + - ]
1642 : : {
1643 : 10374 : case OBJECT_TABLE:
1644 : 10374 : relkind = RELKIND_RELATION;
1645 : 10374 : break;
1646 : :
1647 : 557 : case OBJECT_INDEX:
1648 : 557 : relkind = RELKIND_INDEX;
1649 : 557 : break;
1650 : :
1651 : 121 : case OBJECT_SEQUENCE:
1652 : 121 : relkind = RELKIND_SEQUENCE;
1653 : 121 : break;
1654 : :
1655 : 704 : case OBJECT_VIEW:
1656 : 704 : relkind = RELKIND_VIEW;
1657 : 704 : break;
1658 : :
1659 : 83 : case OBJECT_MATVIEW:
1660 : 83 : relkind = RELKIND_MATVIEW;
1661 : 83 : break;
1662 : :
1663 : 108 : case OBJECT_FOREIGN_TABLE:
1664 : 108 : relkind = RELKIND_FOREIGN_TABLE;
1665 : 108 : break;
1666 : :
1667 : 50 : case OBJECT_PROPGRAPH:
1668 : 50 : relkind = RELKIND_PROPGRAPH;
1669 : 50 : break;
1670 : :
1671 : 0 : default:
1672 [ # # ]: 0 : elog(ERROR, "unrecognized drop object type: %d",
1673 : : (int) drop->removeType);
1674 : : relkind = 0; /* keep compiler quiet */
1675 : : break;
1676 : : }
1677 : :
1678 : : /* Lock and validate each relation; build a list of object addresses */
1679 : 11997 : objects = new_object_addresses();
1680 : :
1681 [ + - + + : 26431 : foreach(cell, drop->objects)
+ + ]
1682 : : {
1683 : 14549 : RangeVar *rel = makeRangeVarFromNameList((List *) lfirst(cell));
1684 : : Oid relOid;
1685 : : ObjectAddress obj;
1686 : : struct DropRelationCallbackState state;
1687 : :
1688 : : /*
1689 : : * These next few steps are a great deal like relation_openrv, but we
1690 : : * don't bother building a relcache entry since we don't need it.
1691 : : *
1692 : : * Check for shared-cache-inval messages before trying to access the
1693 : : * relation. This is needed to cover the case where the name
1694 : : * identifies a rel that has been dropped and recreated since the
1695 : : * start of our transaction: if we don't flush the old syscache entry,
1696 : : * then we'll latch onto that entry and suffer an error later.
1697 : : */
1698 : 14549 : AcceptInvalidationMessages();
1699 : :
1700 : : /* Look up the appropriate relation using namespace search. */
1701 : 14549 : state.expected_relkind = relkind;
1702 : 29098 : state.heap_lockmode = drop->concurrent ?
1703 [ + + ]: 14549 : ShareUpdateExclusiveLock : AccessExclusiveLock;
1704 : : /* We must initialize these fields to show that no locks are held: */
1705 : 14549 : state.heapOid = InvalidOid;
1706 : 14549 : state.partParentOid = InvalidOid;
1707 : :
1708 : 14549 : relOid = RangeVarGetRelidExtended(rel, lockmode, RVR_MISSING_OK,
1709 : : RangeVarCallbackForDropRelation,
1710 : : &state);
1711 : :
1712 : : /* Not there? */
1713 [ + + ]: 14528 : if (!OidIsValid(relOid))
1714 : : {
1715 : 641 : DropErrorMsgNonExistent(rel, relkind, drop->missing_ok);
1716 : 551 : continue;
1717 : : }
1718 : :
1719 : : /*
1720 : : * Decide if concurrent mode needs to be used here or not. The
1721 : : * callback retrieved the rel's persistence for us.
1722 : : */
1723 [ + + ]: 13887 : if (drop->concurrent &&
1724 [ + + ]: 103 : state.actual_relpersistence != RELPERSISTENCE_TEMP)
1725 : : {
1726 : : Assert(list_length(drop->objects) == 1 &&
1727 : : drop->removeType == OBJECT_INDEX);
1728 : 91 : flags |= PERFORM_DELETION_CONCURRENTLY;
1729 : : }
1730 : :
1731 : : /*
1732 : : * Concurrent index drop cannot be used with partitioned indexes,
1733 : : * either.
1734 : : */
1735 [ + + ]: 13887 : if ((flags & PERFORM_DELETION_CONCURRENTLY) != 0 &&
1736 [ + + ]: 91 : state.actual_relkind == RELKIND_PARTITIONED_INDEX)
1737 [ + - ]: 4 : ereport(ERROR,
1738 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1739 : : errmsg("cannot drop partitioned index \"%s\" concurrently",
1740 : : rel->relname)));
1741 : :
1742 : : /*
1743 : : * If we're told to drop a partitioned index, we must acquire lock on
1744 : : * all the children of its parent partitioned table before proceeding.
1745 : : * Otherwise we'd try to lock the child index partitions before their
1746 : : * tables, leading to potential deadlock against other sessions that
1747 : : * will lock those objects in the other order.
1748 : : */
1749 [ + + ]: 13883 : if (state.actual_relkind == RELKIND_PARTITIONED_INDEX)
1750 : 50 : (void) find_all_inheritors(state.heapOid,
1751 : : state.heap_lockmode,
1752 : : NULL);
1753 : :
1754 : : /* OK, we're ready to delete this one */
1755 : 13883 : obj.classId = RelationRelationId;
1756 : 13883 : obj.objectId = relOid;
1757 : 13883 : obj.objectSubId = 0;
1758 : :
1759 : 13883 : add_exact_object_address(&obj, objects);
1760 : : }
1761 : :
1762 : 11882 : performMultipleDeletions(objects, drop->behavior, flags);
1763 : :
1764 : 11779 : free_object_addresses(objects);
1765 : 11779 : }
1766 : :
1767 : : /*
1768 : : * Before acquiring a table lock, check whether we have sufficient rights.
1769 : : * In the case of DROP INDEX, also try to lock the table before the index.
1770 : : * Also, if the table to be dropped is a partition, we try to lock the parent
1771 : : * first.
1772 : : */
1773 : : static void
1774 : 14775 : RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
1775 : : void *arg)
1776 : : {
1777 : : HeapTuple tuple;
1778 : : struct DropRelationCallbackState *state;
1779 : : char expected_relkind;
1780 : : bool is_partition;
1781 : : Form_pg_class classform;
1782 : : LOCKMODE heap_lockmode;
1783 : 14775 : bool invalid_system_index = false;
1784 : :
1785 : 14775 : state = (struct DropRelationCallbackState *) arg;
1786 : 14775 : heap_lockmode = state->heap_lockmode;
1787 : :
1788 : : /*
1789 : : * If we previously locked some other index's heap, and the name we're
1790 : : * looking up no longer refers to that relation, release the now-useless
1791 : : * lock.
1792 : : */
1793 [ + + - + ]: 14775 : if (relOid != oldRelOid && OidIsValid(state->heapOid))
1794 : : {
1795 : 0 : UnlockRelationOid(state->heapOid, heap_lockmode);
1796 : 0 : state->heapOid = InvalidOid;
1797 : : }
1798 : :
1799 : : /*
1800 : : * Similarly, if we previously locked some other partition's heap, and the
1801 : : * name we're looking up no longer refers to that relation, release the
1802 : : * now-useless lock.
1803 : : */
1804 [ + + - + ]: 14775 : if (relOid != oldRelOid && OidIsValid(state->partParentOid))
1805 : : {
1806 : 0 : UnlockRelationOid(state->partParentOid, AccessExclusiveLock);
1807 : 0 : state->partParentOid = InvalidOid;
1808 : : }
1809 : :
1810 : : /* Didn't find a relation, so no need for locking or permission checks. */
1811 [ + + ]: 14775 : if (!OidIsValid(relOid))
1812 : 643 : return;
1813 : :
1814 : 14132 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
1815 [ - + ]: 14132 : if (!HeapTupleIsValid(tuple))
1816 : 0 : return; /* concurrently dropped, so nothing to do */
1817 : 14132 : classform = (Form_pg_class) GETSTRUCT(tuple);
1818 : 14132 : is_partition = classform->relispartition;
1819 : :
1820 : : /* Pass back some data to save lookups in RemoveRelations */
1821 : 14132 : state->actual_relkind = classform->relkind;
1822 : 14132 : state->actual_relpersistence = classform->relpersistence;
1823 : :
1824 : : /*
1825 : : * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
1826 : : * but RemoveRelations() can only pass one relkind for a given relation.
1827 : : * It chooses RELKIND_RELATION for both regular and partitioned tables.
1828 : : * That means we must be careful before giving the wrong type error when
1829 : : * the relation is RELKIND_PARTITIONED_TABLE. An equivalent problem
1830 : : * exists with indexes.
1831 : : */
1832 [ + + ]: 14132 : if (classform->relkind == RELKIND_PARTITIONED_TABLE)
1833 : 2261 : expected_relkind = RELKIND_RELATION;
1834 [ + + ]: 11871 : else if (classform->relkind == RELKIND_PARTITIONED_INDEX)
1835 : 56 : expected_relkind = RELKIND_INDEX;
1836 : : else
1837 : 11815 : expected_relkind = classform->relkind;
1838 : :
1839 [ + + ]: 14132 : if (state->expected_relkind != expected_relkind)
1840 : 4 : DropErrorMsgWrongType(rel->relname, classform->relkind,
1841 : 4 : state->expected_relkind);
1842 : :
1843 : : /* Allow DROP to either table owner or schema owner */
1844 [ + + ]: 14128 : if (!object_ownercheck(RelationRelationId, relOid, GetUserId()) &&
1845 [ + - ]: 12 : !object_ownercheck(NamespaceRelationId, classform->relnamespace, GetUserId()))
1846 : 12 : aclcheck_error(ACLCHECK_NOT_OWNER,
1847 : 12 : get_relkind_objtype(classform->relkind),
1848 : 12 : rel->relname);
1849 : :
1850 : : /*
1851 : : * Check the case of a system index that might have been invalidated by a
1852 : : * failed concurrent process and allow its drop. For the time being, this
1853 : : * only concerns indexes of toast relations that became invalid during a
1854 : : * REINDEX CONCURRENTLY process.
1855 : : */
1856 [ + + - + ]: 14116 : if (IsSystemClass(relOid, classform) && classform->relkind == RELKIND_INDEX)
1857 : : {
1858 : : HeapTuple locTuple;
1859 : : Form_pg_index indexform;
1860 : : bool indisvalid;
1861 : :
1862 : 0 : locTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(relOid));
1863 [ # # ]: 0 : if (!HeapTupleIsValid(locTuple))
1864 : : {
1865 : 0 : ReleaseSysCache(tuple);
1866 : 0 : return;
1867 : : }
1868 : :
1869 : 0 : indexform = (Form_pg_index) GETSTRUCT(locTuple);
1870 : 0 : indisvalid = indexform->indisvalid;
1871 : 0 : ReleaseSysCache(locTuple);
1872 : :
1873 : : /* Mark object as being an invalid index of system catalogs */
1874 [ # # ]: 0 : if (!indisvalid)
1875 : 0 : invalid_system_index = true;
1876 : : }
1877 : :
1878 : : /* In the case of an invalid index, it is fine to bypass this check */
1879 [ + - + + : 14116 : if (!invalid_system_index && !allowSystemTableMods && IsSystemClass(relOid, classform))
+ + ]
1880 [ + - ]: 5 : ereport(ERROR,
1881 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1882 : : errmsg("permission denied: \"%s\" is a system catalog",
1883 : : rel->relname)));
1884 : :
1885 : 14111 : ReleaseSysCache(tuple);
1886 : :
1887 : : /*
1888 : : * In DROP INDEX, attempt to acquire lock on the parent table before
1889 : : * locking the index. index_drop() will need this anyway, and since
1890 : : * regular queries lock tables before their indexes, we risk deadlock if
1891 : : * we do it the other way around. No error if we don't find a pg_index
1892 : : * entry, though --- the relation may have been dropped. Note that this
1893 : : * code will execute for either plain or partitioned indexes.
1894 : : */
1895 [ + + + + ]: 14111 : if (expected_relkind == RELKIND_INDEX &&
1896 : : relOid != oldRelOid)
1897 : : {
1898 : 549 : state->heapOid = IndexGetRelation(relOid, true);
1899 [ + - ]: 549 : if (OidIsValid(state->heapOid))
1900 : 549 : LockRelationOid(state->heapOid, heap_lockmode);
1901 : : }
1902 : :
1903 : : /*
1904 : : * Similarly, if the relation is a partition, we must acquire lock on its
1905 : : * parent before locking the partition. That's because queries lock the
1906 : : * parent before its partitions, so we risk deadlock if we do it the other
1907 : : * way around.
1908 : : */
1909 [ + + + + ]: 14111 : if (is_partition && relOid != oldRelOid)
1910 : : {
1911 : 397 : state->partParentOid = get_partition_parent(relOid, true);
1912 [ + - ]: 397 : if (OidIsValid(state->partParentOid))
1913 : 397 : LockRelationOid(state->partParentOid, AccessExclusiveLock);
1914 : : }
1915 : : }
1916 : :
1917 : : /*
1918 : : * ExecuteTruncate
1919 : : * Executes a TRUNCATE command.
1920 : : *
1921 : : * This is a multi-relation truncate. We first open and grab exclusive
1922 : : * lock on all relations involved, checking permissions and otherwise
1923 : : * verifying that the relation is OK for truncation. Note that if relations
1924 : : * are foreign tables, at this stage, we have not yet checked that their
1925 : : * foreign data in external data sources are OK for truncation. These are
1926 : : * checked when foreign data are actually truncated later. In CASCADE mode,
1927 : : * relations having FK references to the targeted relations are automatically
1928 : : * added to the group; in RESTRICT mode, we check that all FK references are
1929 : : * internal to the group that's being truncated. Finally all the relations
1930 : : * are truncated and reindexed.
1931 : : */
1932 : : void
1933 : 1186 : ExecuteTruncate(TruncateStmt *stmt)
1934 : : {
1935 : 1186 : List *rels = NIL;
1936 : 1186 : List *relids = NIL;
1937 : 1186 : List *relids_logged = NIL;
1938 : : ListCell *cell;
1939 : :
1940 : : /*
1941 : : * Open, exclusive-lock, and check all the explicitly-specified relations
1942 : : */
1943 [ + - + + : 2532 : foreach(cell, stmt->relations)
+ + ]
1944 : : {
1945 : 1382 : RangeVar *rv = lfirst(cell);
1946 : : Relation rel;
1947 : 1382 : bool recurse = rv->inh;
1948 : : Oid myrelid;
1949 : 1382 : LOCKMODE lockmode = AccessExclusiveLock;
1950 : :
1951 : 1382 : myrelid = RangeVarGetRelidExtended(rv, lockmode,
1952 : : 0, RangeVarCallbackForTruncate,
1953 : : NULL);
1954 : :
1955 : : /* don't throw error for "TRUNCATE foo, foo" */
1956 [ + + ]: 1359 : if (list_member_oid(relids, myrelid))
1957 : 1 : continue;
1958 : :
1959 : : /* open the relation, we already hold a lock on it */
1960 : 1358 : rel = table_open(myrelid, NoLock);
1961 : :
1962 : : /*
1963 : : * RangeVarGetRelidExtended() has done most checks with its callback,
1964 : : * but other checks with the now-opened Relation remain.
1965 : : */
1966 : 1358 : truncate_check_activity(rel);
1967 : :
1968 : 1353 : rels = lappend(rels, rel);
1969 : 1353 : relids = lappend_oid(relids, myrelid);
1970 : :
1971 : : /* Log this relation only if needed for logical decoding */
1972 [ + + - + : 1353 : if (RelationIsLogicallyLogged(rel))
+ - - + -
- - - + -
+ + ]
1973 : 39 : relids_logged = lappend_oid(relids_logged, myrelid);
1974 : :
1975 [ + + ]: 1353 : if (recurse)
1976 : : {
1977 : : ListCell *child;
1978 : : List *children;
1979 : :
1980 : 1317 : children = find_all_inheritors(myrelid, lockmode, NULL);
1981 : :
1982 [ + - + + : 3866 : foreach(child, children)
+ + ]
1983 : : {
1984 : 2549 : Oid childrelid = lfirst_oid(child);
1985 : :
1986 [ + + ]: 2549 : if (list_member_oid(relids, childrelid))
1987 : 1329 : continue;
1988 : :
1989 : : /* find_all_inheritors already got lock */
1990 : 1220 : rel = table_open(childrelid, NoLock);
1991 : :
1992 : : /*
1993 : : * It is possible that the parent table has children that are
1994 : : * temp tables of other backends. We cannot safely access
1995 : : * such tables (because of buffering issues), and the best
1996 : : * thing to do is to silently ignore them. Note that this
1997 : : * check is the same as one of the checks done in
1998 : : * truncate_check_activity() called below, still it is kept
1999 : : * here for simplicity.
2000 : : */
2001 [ + + + + ]: 1220 : if (RELATION_IS_OTHER_TEMP(rel))
2002 : : {
2003 : 4 : table_close(rel, lockmode);
2004 : 4 : continue;
2005 : : }
2006 : :
2007 : : /*
2008 : : * Inherited TRUNCATE commands perform access permission
2009 : : * checks on the parent table only. So we skip checking the
2010 : : * children's permissions and don't call
2011 : : * truncate_check_perms() here.
2012 : : */
2013 : 1216 : truncate_check_rel(RelationGetRelid(rel), rel->rd_rel);
2014 : 1216 : truncate_check_activity(rel);
2015 : :
2016 : 1216 : rels = lappend(rels, rel);
2017 : 1216 : relids = lappend_oid(relids, childrelid);
2018 : :
2019 : : /* Log this relation only if needed for logical decoding */
2020 [ + + - + : 1216 : if (RelationIsLogicallyLogged(rel))
+ - - + -
- - - + -
+ - ]
2021 : 11 : relids_logged = lappend_oid(relids_logged, childrelid);
2022 : : }
2023 : : }
2024 [ + + ]: 36 : else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
2025 [ + - ]: 8 : ereport(ERROR,
2026 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2027 : : errmsg("cannot truncate only a partitioned table"),
2028 : : errhint("Do not specify the ONLY keyword, or use TRUNCATE ONLY on the partitions directly.")));
2029 : : }
2030 : :
2031 : 1150 : ExecuteTruncateGuts(rels, relids, relids_logged,
2032 : 1150 : stmt->behavior, stmt->restart_seqs, false);
2033 : :
2034 : : /* And close the rels */
2035 [ + - + + : 3557 : foreach(cell, rels)
+ + ]
2036 : : {
2037 : 2460 : Relation rel = (Relation) lfirst(cell);
2038 : :
2039 : 2460 : table_close(rel, NoLock);
2040 : : }
2041 : 1097 : }
2042 : :
2043 : : /*
2044 : : * ExecuteTruncateGuts
2045 : : *
2046 : : * Internal implementation of TRUNCATE. This is called by the actual TRUNCATE
2047 : : * command (see above) as well as replication subscribers that execute a
2048 : : * replicated TRUNCATE action.
2049 : : *
2050 : : * explicit_rels is the list of Relations to truncate that the command
2051 : : * specified. relids is the list of Oids corresponding to explicit_rels.
2052 : : * relids_logged is the list of Oids (a subset of relids) that require
2053 : : * WAL-logging. This is all a bit redundant, but the existing callers have
2054 : : * this information handy in this form.
2055 : : */
2056 : : void
2057 : 1170 : ExecuteTruncateGuts(List *explicit_rels,
2058 : : List *relids,
2059 : : List *relids_logged,
2060 : : DropBehavior behavior, bool restart_seqs,
2061 : : bool run_as_table_owner)
2062 : : {
2063 : : List *rels;
2064 : 1170 : List *seq_relids = NIL;
2065 : 1170 : HTAB *ft_htab = NULL;
2066 : : EState *estate;
2067 : : ResultRelInfo *resultRelInfos;
2068 : : ResultRelInfo *resultRelInfo;
2069 : : SubTransactionId mySubid;
2070 : : ListCell *cell;
2071 : : Oid *logrelids;
2072 : :
2073 : : /*
2074 : : * Check the explicitly-specified relations.
2075 : : *
2076 : : * In CASCADE mode, suck in all referencing relations as well. This
2077 : : * requires multiple iterations to find indirectly-dependent relations. At
2078 : : * each phase, we need to exclusive-lock new rels before looking for their
2079 : : * dependencies, else we might miss something. Also, we check each rel as
2080 : : * soon as we open it, to avoid a faux pas such as holding lock for a long
2081 : : * time on a rel we have no permissions for.
2082 : : */
2083 : 1170 : rels = list_copy(explicit_rels);
2084 [ + + ]: 1170 : if (behavior == DROP_CASCADE)
2085 : : {
2086 : : for (;;)
2087 : 26 : {
2088 : : List *newrelids;
2089 : :
2090 : 51 : newrelids = heap_truncate_find_FKs(relids);
2091 [ + + ]: 51 : if (newrelids == NIL)
2092 : 25 : break; /* nothing else to add */
2093 : :
2094 [ + - + + : 88 : foreach(cell, newrelids)
+ + ]
2095 : : {
2096 : 62 : Oid relid = lfirst_oid(cell);
2097 : : Relation rel;
2098 : :
2099 : 62 : rel = table_open(relid, AccessExclusiveLock);
2100 [ + - ]: 62 : ereport(NOTICE,
2101 : : (errmsg("truncate cascades to table \"%s\"",
2102 : : RelationGetRelationName(rel))));
2103 : 62 : truncate_check_rel(relid, rel->rd_rel);
2104 : 62 : truncate_check_perms(relid, rel->rd_rel);
2105 : 62 : truncate_check_activity(rel);
2106 : 62 : rels = lappend(rels, rel);
2107 : 62 : relids = lappend_oid(relids, relid);
2108 : :
2109 : : /* Log this relation only if needed for logical decoding */
2110 [ + - - + : 62 : if (RelationIsLogicallyLogged(rel))
- - - - -
- - - - -
- - ]
2111 : 0 : relids_logged = lappend_oid(relids_logged, relid);
2112 : : }
2113 : : }
2114 : : }
2115 : :
2116 : : /*
2117 : : * Check foreign key references. In CASCADE mode, this should be
2118 : : * unnecessary since we just pulled in all the references; but as a
2119 : : * cross-check, do it anyway if in an Assert-enabled build.
2120 : : */
2121 : : #ifdef USE_ASSERT_CHECKING
2122 : : heap_truncate_check_FKs(rels, false);
2123 : : #else
2124 [ + + ]: 1170 : if (behavior == DROP_RESTRICT)
2125 : 1145 : heap_truncate_check_FKs(rels, false);
2126 : : #endif
2127 : :
2128 : : /*
2129 : : * If we are asked to restart sequences, find all the sequences, lock them
2130 : : * (we need AccessExclusiveLock for ResetSequence), and check permissions.
2131 : : * We want to do this early since it's pointless to do all the truncation
2132 : : * work only to fail on sequence permissions.
2133 : : */
2134 [ + + ]: 1121 : if (restart_seqs)
2135 : : {
2136 [ + - + + : 32 : foreach(cell, rels)
+ + ]
2137 : : {
2138 : 16 : Relation rel = (Relation) lfirst(cell);
2139 : 16 : List *seqlist = getOwnedSequences(RelationGetRelid(rel));
2140 : : ListCell *seqcell;
2141 : :
2142 [ + + + + : 39 : foreach(seqcell, seqlist)
+ + ]
2143 : : {
2144 : 23 : Oid seq_relid = lfirst_oid(seqcell);
2145 : : Relation seq_rel;
2146 : :
2147 : 23 : seq_rel = relation_open(seq_relid, AccessExclusiveLock);
2148 : :
2149 : : /* This check must match AlterSequence! */
2150 [ - + ]: 23 : if (!object_ownercheck(RelationRelationId, seq_relid, GetUserId()))
2151 : 0 : aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SEQUENCE,
2152 : 0 : RelationGetRelationName(seq_rel));
2153 : :
2154 : 23 : seq_relids = lappend_oid(seq_relids, seq_relid);
2155 : :
2156 : 23 : relation_close(seq_rel, NoLock);
2157 : : }
2158 : : }
2159 : : }
2160 : :
2161 : : /* Prepare to catch AFTER triggers. */
2162 : 1121 : AfterTriggerBeginQuery();
2163 : :
2164 : : /*
2165 : : * To fire triggers, we'll need an EState as well as a ResultRelInfo for
2166 : : * each relation. We don't need to call ExecOpenIndices, though.
2167 : : *
2168 : : * We put the ResultRelInfos in the es_opened_result_relations list, even
2169 : : * though we don't have a range table and don't populate the
2170 : : * es_result_relations array. That's a bit bogus, but it's enough to make
2171 : : * ExecGetTriggerResultRel() find them.
2172 : : */
2173 : 1121 : estate = CreateExecutorState();
2174 : : resultRelInfos = (ResultRelInfo *)
2175 : 1121 : palloc(list_length(rels) * sizeof(ResultRelInfo));
2176 : 1121 : resultRelInfo = resultRelInfos;
2177 [ + - + + : 3683 : foreach(cell, rels)
+ + ]
2178 : : {
2179 : 2562 : Relation rel = (Relation) lfirst(cell);
2180 : :
2181 : 2562 : InitResultRelInfo(resultRelInfo,
2182 : : rel,
2183 : : 0, /* dummy rangetable index */
2184 : : NULL,
2185 : : 0);
2186 : 2562 : estate->es_opened_result_relations =
2187 : 2562 : lappend(estate->es_opened_result_relations, resultRelInfo);
2188 : 2562 : resultRelInfo++;
2189 : : }
2190 : :
2191 : : /*
2192 : : * Process all BEFORE STATEMENT TRUNCATE triggers before we begin
2193 : : * truncating (this is because one of them might throw an error). Also, if
2194 : : * we were to allow them to prevent statement execution, that would need
2195 : : * to be handled here.
2196 : : */
2197 : 1121 : resultRelInfo = resultRelInfos;
2198 [ + - + + : 3683 : foreach(cell, rels)
+ + ]
2199 : : {
2200 : : UserContext ucxt;
2201 : :
2202 [ + + ]: 2562 : if (run_as_table_owner)
2203 : 36 : SwitchToUntrustedUser(resultRelInfo->ri_RelationDesc->rd_rel->relowner,
2204 : : &ucxt);
2205 : 2562 : ExecBSTruncateTriggers(estate, resultRelInfo);
2206 [ + + ]: 2562 : if (run_as_table_owner)
2207 : 36 : RestoreUserContext(&ucxt);
2208 : 2562 : resultRelInfo++;
2209 : : }
2210 : :
2211 : : /*
2212 : : * OK, truncate each table.
2213 : : */
2214 : 1121 : mySubid = GetCurrentSubTransactionId();
2215 : :
2216 [ + - + + : 3683 : foreach(cell, rels)
+ + ]
2217 : : {
2218 : 2562 : Relation rel = (Relation) lfirst(cell);
2219 : :
2220 : : /* Skip partitioned tables as there is nothing to do */
2221 [ + + ]: 2562 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
2222 : 477 : continue;
2223 : :
2224 : : /*
2225 : : * Build the lists of foreign tables belonging to each foreign server
2226 : : * and pass each list to the foreign data wrapper's callback function,
2227 : : * so that each server can truncate its all foreign tables in bulk.
2228 : : * Each list is saved as a single entry in a hash table that uses the
2229 : : * server OID as lookup key.
2230 : : */
2231 [ + + ]: 2085 : if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
2232 : 17 : {
2233 : 17 : Oid serverid = GetForeignServerIdByRelId(RelationGetRelid(rel));
2234 : : bool found;
2235 : : ForeignTruncateInfo *ft_info;
2236 : :
2237 : : /* First time through, initialize hashtable for foreign tables */
2238 [ + + ]: 17 : if (!ft_htab)
2239 : : {
2240 : : HASHCTL hctl;
2241 : :
2242 : 15 : memset(&hctl, 0, sizeof(HASHCTL));
2243 : 15 : hctl.keysize = sizeof(Oid);
2244 : 15 : hctl.entrysize = sizeof(ForeignTruncateInfo);
2245 : 15 : hctl.hcxt = CurrentMemoryContext;
2246 : :
2247 : 15 : ft_htab = hash_create("TRUNCATE for Foreign Tables",
2248 : : 32, /* start small and extend */
2249 : : &hctl,
2250 : : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
2251 : : }
2252 : :
2253 : : /* Find or create cached entry for the foreign table */
2254 : 17 : ft_info = hash_search(ft_htab, &serverid, HASH_ENTER, &found);
2255 [ + + ]: 17 : if (!found)
2256 : 15 : ft_info->rels = NIL;
2257 : :
2258 : : /*
2259 : : * Save the foreign table in the entry of the server that the
2260 : : * foreign table belongs to.
2261 : : */
2262 : 17 : ft_info->rels = lappend(ft_info->rels, rel);
2263 : 17 : continue;
2264 : : }
2265 : :
2266 : : /*
2267 : : * Normally, we need a transaction-safe truncation here. However, if
2268 : : * the table was either created in the current (sub)transaction or has
2269 : : * a new relfilenumber in the current (sub)transaction, then we can
2270 : : * just truncate it in-place, because a rollback would cause the whole
2271 : : * table or the current physical file to be thrown away anyway.
2272 : : */
2273 [ + + ]: 2068 : if (rel->rd_createSubid == mySubid ||
2274 [ + + ]: 2050 : rel->rd_newRelfilelocatorSubid == mySubid)
2275 : : {
2276 : : /* Immediate, non-rollbackable truncation is OK */
2277 : 50 : heap_truncate_one_rel(rel);
2278 : : }
2279 : : else
2280 : : {
2281 : : Oid heap_relid;
2282 : : Oid toast_relid;
2283 : 2018 : ReindexParams reindex_params = {0};
2284 : :
2285 : : /*
2286 : : * This effectively deletes all rows in the table, and may be done
2287 : : * in a serializable transaction. In that case we must record a
2288 : : * rw-conflict in to this transaction from each transaction
2289 : : * holding a predicate lock on the table.
2290 : : */
2291 : 2018 : CheckTableForSerializableConflictIn(rel);
2292 : :
2293 : : /*
2294 : : * Need the full transaction-safe pushups.
2295 : : *
2296 : : * Create a new empty storage file for the relation, and assign it
2297 : : * as the relfilenumber value. The old storage file is scheduled
2298 : : * for deletion at commit.
2299 : : */
2300 : 2018 : RelationSetNewRelfilenumber(rel, rel->rd_rel->relpersistence);
2301 : :
2302 : 2018 : heap_relid = RelationGetRelid(rel);
2303 : :
2304 : : /*
2305 : : * The same for the toast table, if any.
2306 : : */
2307 : 2018 : toast_relid = rel->rd_rel->reltoastrelid;
2308 [ + + ]: 2018 : if (OidIsValid(toast_relid))
2309 : : {
2310 : 1237 : Relation toastrel = relation_open(toast_relid,
2311 : : AccessExclusiveLock);
2312 : :
2313 : 1237 : RelationSetNewRelfilenumber(toastrel,
2314 : 1237 : toastrel->rd_rel->relpersistence);
2315 : 1237 : table_close(toastrel, NoLock);
2316 : : }
2317 : :
2318 : : /*
2319 : : * Reconstruct the indexes to match, and we're done.
2320 : : */
2321 : 2018 : reindex_relation(NULL, heap_relid, REINDEX_REL_PROCESS_TOAST,
2322 : : &reindex_params);
2323 : : }
2324 : :
2325 : 2068 : pgstat_count_truncate(rel);
2326 : : }
2327 : :
2328 : : /* Now go through the hash table, and truncate foreign tables */
2329 [ + + ]: 1121 : if (ft_htab)
2330 : : {
2331 : : ForeignTruncateInfo *ft_info;
2332 : : HASH_SEQ_STATUS seq;
2333 : :
2334 : 15 : hash_seq_init(&seq, ft_htab);
2335 : :
2336 [ + + ]: 15 : PG_TRY();
2337 : : {
2338 [ + + ]: 26 : while ((ft_info = hash_seq_search(&seq)) != NULL)
2339 : : {
2340 : 15 : FdwRoutine *routine = GetFdwRoutineByServerId(ft_info->serverid);
2341 : :
2342 : : /* truncate_check_rel() has checked that already */
2343 : : Assert(routine->ExecForeignTruncate != NULL);
2344 : :
2345 : 15 : routine->ExecForeignTruncate(ft_info->rels,
2346 : : behavior,
2347 : : restart_seqs);
2348 : : }
2349 : : }
2350 : 4 : PG_FINALLY();
2351 : : {
2352 : 15 : hash_destroy(ft_htab);
2353 : : }
2354 [ + + ]: 15 : PG_END_TRY();
2355 : : }
2356 : :
2357 : : /*
2358 : : * Restart owned sequences if we were asked to.
2359 : : */
2360 [ + + + + : 1140 : foreach(cell, seq_relids)
+ + ]
2361 : : {
2362 : 23 : Oid seq_relid = lfirst_oid(cell);
2363 : :
2364 : 23 : ResetSequence(seq_relid);
2365 : : }
2366 : :
2367 : : /*
2368 : : * Write a WAL record to allow this set of actions to be logically
2369 : : * decoded.
2370 : : *
2371 : : * Assemble an array of relids so we can write a single WAL record for the
2372 : : * whole action.
2373 : : */
2374 [ + + ]: 1117 : if (relids_logged != NIL)
2375 : : {
2376 : : xl_heap_truncate xlrec;
2377 : 33 : int i = 0;
2378 : :
2379 : : /* should only get here if effective_wal_level is 'logical' */
2380 : : Assert(XLogLogicalInfoActive());
2381 : :
2382 : 33 : logrelids = palloc(list_length(relids_logged) * sizeof(Oid));
2383 [ + - + + : 84 : foreach(cell, relids_logged)
+ + ]
2384 : 51 : logrelids[i++] = lfirst_oid(cell);
2385 : :
2386 : 33 : xlrec.dbId = MyDatabaseId;
2387 : 33 : xlrec.nrelids = list_length(relids_logged);
2388 : 33 : xlrec.flags = 0;
2389 [ + + ]: 33 : if (behavior == DROP_CASCADE)
2390 : 1 : xlrec.flags |= XLH_TRUNCATE_CASCADE;
2391 [ + + ]: 33 : if (restart_seqs)
2392 : 2 : xlrec.flags |= XLH_TRUNCATE_RESTART_SEQS;
2393 : :
2394 : 33 : XLogBeginInsert();
2395 : 33 : XLogRegisterData(&xlrec, SizeOfHeapTruncate);
2396 : 33 : XLogRegisterData(logrelids, list_length(relids_logged) * sizeof(Oid));
2397 : :
2398 : 33 : XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
2399 : :
2400 : 33 : (void) XLogInsert(RM_HEAP_ID, XLOG_HEAP_TRUNCATE);
2401 : : }
2402 : :
2403 : : /*
2404 : : * Process all AFTER STATEMENT TRUNCATE triggers.
2405 : : */
2406 : 1117 : resultRelInfo = resultRelInfos;
2407 [ + - + + : 3675 : foreach(cell, rels)
+ + ]
2408 : : {
2409 : : UserContext ucxt;
2410 : :
2411 [ + + ]: 2558 : if (run_as_table_owner)
2412 : 36 : SwitchToUntrustedUser(resultRelInfo->ri_RelationDesc->rd_rel->relowner,
2413 : : &ucxt);
2414 : 2558 : ExecASTruncateTriggers(estate, resultRelInfo);
2415 [ + + ]: 2558 : if (run_as_table_owner)
2416 : 36 : RestoreUserContext(&ucxt);
2417 : 2558 : resultRelInfo++;
2418 : : }
2419 : :
2420 : : /* Handle queued AFTER triggers */
2421 : 1117 : AfterTriggerEndQuery(estate);
2422 : :
2423 : : /* We can clean up the EState now */
2424 : 1117 : FreeExecutorState(estate);
2425 : :
2426 : : /*
2427 : : * Close any rels opened by CASCADE (can't do this while EState still
2428 : : * holds refs)
2429 : : */
2430 : 1117 : rels = list_difference_ptr(rels, explicit_rels);
2431 [ + + + + : 1179 : foreach(cell, rels)
+ + ]
2432 : : {
2433 : 62 : Relation rel = (Relation) lfirst(cell);
2434 : :
2435 : 62 : table_close(rel, NoLock);
2436 : : }
2437 : 1117 : }
2438 : :
2439 : : /*
2440 : : * Check that a given relation is safe to truncate. Subroutine for
2441 : : * ExecuteTruncate() and RangeVarCallbackForTruncate().
2442 : : */
2443 : : static void
2444 : 2757 : truncate_check_rel(Oid relid, Form_pg_class reltuple)
2445 : : {
2446 : 2757 : char *relname = NameStr(reltuple->relname);
2447 : :
2448 : : /*
2449 : : * Only allow truncate on regular tables, foreign tables using foreign
2450 : : * data wrappers supporting TRUNCATE and partitioned tables (although, the
2451 : : * latter are only being included here for the following checks; no
2452 : : * physical truncation will occur in their case.).
2453 : : */
2454 [ + + ]: 2757 : if (reltuple->relkind == RELKIND_FOREIGN_TABLE)
2455 : : {
2456 : 19 : Oid serverid = GetForeignServerIdByRelId(relid);
2457 : 19 : FdwRoutine *fdwroutine = GetFdwRoutineByServerId(serverid);
2458 : :
2459 [ + + ]: 18 : if (!fdwroutine->ExecForeignTruncate)
2460 [ + - ]: 1 : ereport(ERROR,
2461 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2462 : : errmsg("cannot truncate foreign table \"%s\"",
2463 : : relname)));
2464 : : }
2465 [ + + ]: 2738 : else if (reltuple->relkind != RELKIND_RELATION &&
2466 [ - + ]: 487 : reltuple->relkind != RELKIND_PARTITIONED_TABLE)
2467 [ # # ]: 0 : ereport(ERROR,
2468 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2469 : : errmsg("\"%s\" is not a table", relname)));
2470 : :
2471 : : /*
2472 : : * Most system catalogs can't be truncated at all, or at least not unless
2473 : : * allow_system_table_mods=on. As an exception, however, we allow
2474 : : * pg_largeobject and pg_largeobject_metadata to be truncated as part of
2475 : : * pg_upgrade, because we need to change its relfilenode to match the old
2476 : : * cluster, and allowing a TRUNCATE command to be executed is the easiest
2477 : : * way of doing that. We also allow TRUNCATE on the conflict log tables,
2478 : : * to permit users to manually prune conflict data to manage disk space.
2479 : : */
2480 [ + + + + ]: 2755 : if (!allowSystemTableMods && IsSystemClass(relid, reltuple) &&
2481 [ + + ]: 73 : !IsConflictLogTableClass(reltuple)
2482 [ + + + + ]: 65 : && (!IsBinaryUpgrade ||
2483 [ - + ]: 32 : (relid != LargeObjectRelationId &&
2484 : : relid != LargeObjectMetadataRelationId)))
2485 [ + - ]: 1 : ereport(ERROR,
2486 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
2487 : : errmsg("permission denied: \"%s\" is a system catalog",
2488 : : relname)));
2489 : :
2490 [ - + ]: 2754 : InvokeObjectTruncateHook(relid);
2491 : 2754 : }
2492 : :
2493 : : /*
2494 : : * Check that current user has the permission to truncate given relation.
2495 : : */
2496 : : static void
2497 : 1538 : truncate_check_perms(Oid relid, Form_pg_class reltuple)
2498 : : {
2499 : 1538 : char *relname = NameStr(reltuple->relname);
2500 : : AclResult aclresult;
2501 : :
2502 : : /* Permissions checks */
2503 : 1538 : aclresult = pg_class_aclcheck(relid, GetUserId(), ACL_TRUNCATE);
2504 [ + + ]: 1538 : if (aclresult != ACLCHECK_OK)
2505 : 20 : aclcheck_error(aclresult, get_relkind_objtype(reltuple->relkind),
2506 : : relname);
2507 : 1518 : }
2508 : :
2509 : : /*
2510 : : * Set of extra sanity checks to check if a given relation is safe to
2511 : : * truncate. This is split with truncate_check_rel() as
2512 : : * RangeVarCallbackForTruncate() cannot open a Relation yet.
2513 : : */
2514 : : static void
2515 : 2636 : truncate_check_activity(Relation rel)
2516 : : {
2517 : : /*
2518 : : * Don't allow truncate on temp tables of other backends ... their local
2519 : : * buffer manager is not going to cope.
2520 : : */
2521 [ + + + + ]: 2636 : if (RELATION_IS_OTHER_TEMP(rel))
2522 [ + - ]: 1 : ereport(ERROR,
2523 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2524 : : errmsg("cannot truncate temporary tables of other sessions")));
2525 : :
2526 : : /*
2527 : : * Also check for active uses of the relation in the current transaction,
2528 : : * including open scans and pending AFTER trigger events.
2529 : : */
2530 : 2635 : CheckTableNotInUse(rel, "TRUNCATE");
2531 : 2631 : }
2532 : :
2533 : : /*
2534 : : * storage_name
2535 : : * returns the name corresponding to a typstorage/attstorage enum value
2536 : : */
2537 : : static const char *
2538 : 16 : storage_name(char c)
2539 : : {
2540 [ - - + + : 16 : switch (c)
- ]
2541 : : {
2542 : 0 : case TYPSTORAGE_PLAIN:
2543 : 0 : return "PLAIN";
2544 : 0 : case TYPSTORAGE_EXTERNAL:
2545 : 0 : return "EXTERNAL";
2546 : 8 : case TYPSTORAGE_EXTENDED:
2547 : 8 : return "EXTENDED";
2548 : 8 : case TYPSTORAGE_MAIN:
2549 : 8 : return "MAIN";
2550 : 0 : default:
2551 : 0 : return "???";
2552 : : }
2553 : : }
2554 : :
2555 : : /*----------
2556 : : * MergeAttributes
2557 : : * Returns new schema given initial schema and superclasses.
2558 : : *
2559 : : * Input arguments:
2560 : : * 'columns' is the column/attribute definition for the table. (It's a list
2561 : : * of ColumnDef's.) It is destructively changed.
2562 : : * 'supers' is a list of OIDs of parent relations, already locked by caller.
2563 : : * 'relpersistence' is the persistence type of the table.
2564 : : * 'is_partition' tells if the table is a partition.
2565 : : *
2566 : : * Output arguments:
2567 : : * 'supconstr' receives a list of CookedConstraint representing
2568 : : * CHECK constraints belonging to parent relations, updated as
2569 : : * necessary to be valid for the child.
2570 : : * 'supnotnulls' receives a list of CookedConstraint representing
2571 : : * not-null constraints based on those from parent relations.
2572 : : *
2573 : : * Return value:
2574 : : * Completed schema list.
2575 : : *
2576 : : * Notes:
2577 : : * The order in which the attributes are inherited is very important.
2578 : : * Intuitively, the inherited attributes should come first. If a table
2579 : : * inherits from multiple parents, the order of those attributes are
2580 : : * according to the order of the parents specified in CREATE TABLE.
2581 : : *
2582 : : * Here's an example:
2583 : : *
2584 : : * create table person (name text, age int4, location point);
2585 : : * create table emp (salary int4, manager text) inherits(person);
2586 : : * create table student (gpa float8) inherits (person);
2587 : : * create table stud_emp (percent int4) inherits (emp, student);
2588 : : *
2589 : : * The order of the attributes of stud_emp is:
2590 : : *
2591 : : * person {1:name, 2:age, 3:location}
2592 : : * / \
2593 : : * {6:gpa} student emp {4:salary, 5:manager}
2594 : : * \ /
2595 : : * stud_emp {7:percent}
2596 : : *
2597 : : * If the same attribute name appears multiple times, then it appears
2598 : : * in the result table in the proper location for its first appearance.
2599 : : *
2600 : : * Constraints (including not-null constraints) for the child table
2601 : : * are the union of all relevant constraints, from both the child schema
2602 : : * and parent tables. In addition, in legacy inheritance, each column that
2603 : : * appears in a primary key in any of the parents also gets a NOT NULL
2604 : : * constraint (partitioning doesn't need this, because the PK itself gets
2605 : : * inherited.)
2606 : : *
2607 : : * The default value for a child column is defined as:
2608 : : * (1) If the child schema specifies a default, that value is used.
2609 : : * (2) If neither the child nor any parent specifies a default, then
2610 : : * the column will not have a default.
2611 : : * (3) If conflicting defaults are inherited from different parents
2612 : : * (and not overridden by the child), an error is raised.
2613 : : * (4) Otherwise the inherited default is used.
2614 : : *
2615 : : * Note that the default-value infrastructure is used for generated
2616 : : * columns' expressions too, so most of the preceding paragraph applies
2617 : : * to generation expressions too. We insist that a child column be
2618 : : * generated if and only if its parent(s) are, but it need not have
2619 : : * the same generation expression.
2620 : : *----------
2621 : : */
2622 : : static List *
2623 : 42431 : MergeAttributes(List *columns, const List *supers, char relpersistence,
2624 : : bool is_partition, List **supconstr, List **supnotnulls)
2625 : : {
2626 : 42431 : List *inh_columns = NIL;
2627 : 42431 : List *constraints = NIL;
2628 : 42431 : List *nnconstraints = NIL;
2629 : 42431 : bool have_bogus_defaults = false;
2630 : : int child_attno;
2631 : : static Node bogus_marker = {0}; /* marks conflicting defaults */
2632 : 42431 : List *saved_columns = NIL;
2633 : : ListCell *lc;
2634 : :
2635 : : /*
2636 : : * Check for and reject tables with too many columns. We perform this
2637 : : * check relatively early for two reasons: (a) we don't run the risk of
2638 : : * overflowing an AttrNumber in subsequent code (b) an O(n^2) algorithm is
2639 : : * okay if we're processing <= 1600 columns, but could take minutes to
2640 : : * execute if the user attempts to create a table with hundreds of
2641 : : * thousands of columns.
2642 : : *
2643 : : * Note that we also need to check that we do not exceed this figure after
2644 : : * including columns from inherited relations.
2645 : : */
2646 [ - + ]: 42431 : if (list_length(columns) > MaxHeapAttributeNumber)
2647 [ # # ]: 0 : ereport(ERROR,
2648 : : (errcode(ERRCODE_TOO_MANY_COLUMNS),
2649 : : errmsg("tables can have at most %d columns",
2650 : : MaxHeapAttributeNumber)));
2651 : :
2652 : : /*
2653 : : * Check for duplicate names in the explicit list of attributes.
2654 : : *
2655 : : * Although we might consider merging such entries in the same way that we
2656 : : * handle name conflicts for inherited attributes, it seems to make more
2657 : : * sense to assume such conflicts are errors.
2658 : : *
2659 : : * We don't use foreach() here because we have two nested loops over the
2660 : : * columns list, with possible element deletions in the inner one. If we
2661 : : * used foreach_delete_current() it could only fix up the state of one of
2662 : : * the loops, so it seems cleaner to use looping over list indexes for
2663 : : * both loops. Note that any deletion will happen beyond where the outer
2664 : : * loop is, so its index never needs adjustment.
2665 : : */
2666 [ + + ]: 195219 : for (int coldefpos = 0; coldefpos < list_length(columns); coldefpos++)
2667 : : {
2668 : 152804 : ColumnDef *coldef = list_nth_node(ColumnDef, columns, coldefpos);
2669 : :
2670 [ + + + + ]: 152804 : if (!is_partition && coldef->typeName == NULL)
2671 : : {
2672 : : /*
2673 : : * Typed table column option that does not belong to a column from
2674 : : * the type. This works because the columns from the type come
2675 : : * first in the list. (We omit this check for partition column
2676 : : * lists; those are processed separately below.)
2677 : : */
2678 [ + - ]: 4 : ereport(ERROR,
2679 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
2680 : : errmsg("column \"%s\" does not exist",
2681 : : coldef->colname)));
2682 : : }
2683 : :
2684 : : /* restpos scans all entries beyond coldef; incr is in loop body */
2685 [ + + ]: 4112497 : for (int restpos = coldefpos + 1; restpos < list_length(columns);)
2686 : : {
2687 : 3959709 : ColumnDef *restdef = list_nth_node(ColumnDef, columns, restpos);
2688 : :
2689 [ + + ]: 3959709 : if (strcmp(coldef->colname, restdef->colname) == 0)
2690 : : {
2691 [ + + ]: 33 : if (coldef->is_from_type)
2692 : : {
2693 : : /*
2694 : : * merge the column options into the column from the type
2695 : : */
2696 : 21 : coldef->is_not_null = restdef->is_not_null;
2697 : 21 : coldef->raw_default = restdef->raw_default;
2698 : 21 : coldef->cooked_default = restdef->cooked_default;
2699 : 21 : coldef->constraints = restdef->constraints;
2700 : 21 : coldef->is_from_type = false;
2701 : 21 : columns = list_delete_nth_cell(columns, restpos);
2702 : : }
2703 : : else
2704 [ + - ]: 12 : ereport(ERROR,
2705 : : (errcode(ERRCODE_DUPLICATE_COLUMN),
2706 : : errmsg("column \"%s\" specified more than once",
2707 : : coldef->colname)));
2708 : : }
2709 : : else
2710 : 3959676 : restpos++;
2711 : : }
2712 : : }
2713 : :
2714 : : /*
2715 : : * In case of a partition, there are no new column definitions, only dummy
2716 : : * ColumnDefs created for column constraints. Set them aside for now and
2717 : : * process them at the end.
2718 : : */
2719 [ + + ]: 42415 : if (is_partition)
2720 : : {
2721 : 6560 : saved_columns = columns;
2722 : 6560 : columns = NIL;
2723 : : }
2724 : :
2725 : : /*
2726 : : * Scan the parents left-to-right, and merge their attributes to form a
2727 : : * list of inherited columns (inh_columns).
2728 : : */
2729 : 42415 : child_attno = 0;
2730 [ + + + + : 50612 : foreach(lc, supers)
+ + ]
2731 : : {
2732 : 8257 : Oid parent = lfirst_oid(lc);
2733 : : Relation relation;
2734 : : TupleDesc tupleDesc;
2735 : : TupleConstr *constr;
2736 : : AttrMap *newattmap;
2737 : : List *inherited_defaults;
2738 : : List *cols_with_defaults;
2739 : : List *nnconstrs;
2740 : : ListCell *lc1;
2741 : : ListCell *lc2;
2742 : 8257 : Bitmapset *nncols = NULL;
2743 : :
2744 : : /* caller already got lock */
2745 : 8257 : relation = table_open(parent, NoLock);
2746 : :
2747 : : /*
2748 : : * Check for active uses of the parent partitioned table in the
2749 : : * current transaction, such as being used in some manner by an
2750 : : * enclosing command.
2751 : : */
2752 [ + + ]: 8257 : if (is_partition)
2753 : 6560 : CheckTableNotInUse(relation, "CREATE TABLE .. PARTITION OF");
2754 : :
2755 : : /*
2756 : : * We do not allow partitioned tables and partitions to participate in
2757 : : * regular inheritance.
2758 : : */
2759 [ + + + + ]: 8253 : if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !is_partition)
2760 [ + - ]: 4 : ereport(ERROR,
2761 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2762 : : errmsg("cannot inherit from partitioned table \"%s\"",
2763 : : RelationGetRelationName(relation))));
2764 [ + + + + ]: 8249 : if (relation->rd_rel->relispartition && !is_partition)
2765 [ + - ]: 4 : ereport(ERROR,
2766 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2767 : : errmsg("cannot inherit from partition \"%s\"",
2768 : : RelationGetRelationName(relation))));
2769 : :
2770 : : /*
2771 : : * Conflict log tables are managed by the system for logical
2772 : : * replication and should not be used as parent tables, as inheritance
2773 : : * could interfere with the logging behavior.
2774 : : */
2775 [ + + ]: 8245 : if (IsConflictLogTableNamespace(relation->rd_rel->relnamespace))
2776 [ + - ]: 4 : ereport(ERROR,
2777 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2778 : : errmsg("cannot inherit from conflict log table \"%s\"",
2779 : : RelationGetRelationName(relation)),
2780 : : errdetail("Conflict log tables are system-managed tables for logical replication conflicts.")));
2781 : :
2782 [ + + ]: 8241 : if (relation->rd_rel->relkind != RELKIND_RELATION &&
2783 [ + + ]: 6556 : relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
2784 [ - + ]: 6544 : relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
2785 [ # # ]: 0 : ereport(ERROR,
2786 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2787 : : errmsg("inherited relation \"%s\" is not a table or foreign table",
2788 : : RelationGetRelationName(relation))));
2789 : :
2790 : : /*
2791 : : * If the parent is permanent, so must be all of its partitions. Note
2792 : : * that inheritance allows that case.
2793 : : */
2794 [ + + ]: 8241 : if (is_partition &&
2795 [ + + + + ]: 6556 : relation->rd_rel->relpersistence != RELPERSISTENCE_TEMP &&
2796 : : relpersistence == RELPERSISTENCE_TEMP)
2797 [ + - ]: 4 : ereport(ERROR,
2798 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2799 : : errmsg("cannot create a temporary relation as partition of permanent relation \"%s\"",
2800 : : RelationGetRelationName(relation))));
2801 : :
2802 : : /* Permanent rels cannot inherit from temporary ones */
2803 [ + + ]: 8237 : if (relpersistence != RELPERSISTENCE_TEMP &&
2804 [ + + ]: 7967 : relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
2805 [ + - + + ]: 16 : ereport(ERROR,
2806 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2807 : : errmsg(!is_partition
2808 : : ? "cannot inherit from temporary relation \"%s\""
2809 : : : "cannot create a permanent relation as partition of temporary relation \"%s\"",
2810 : : RelationGetRelationName(relation))));
2811 : :
2812 : : /* If existing rel is temp, it must belong to this session */
2813 [ + + - + ]: 8221 : if (RELATION_IS_OTHER_TEMP(relation))
2814 [ # # # # ]: 0 : ereport(ERROR,
2815 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2816 : : errmsg(!is_partition
2817 : : ? "cannot inherit from temporary relation of another session"
2818 : : : "cannot create as partition of temporary relation of another session")));
2819 : :
2820 : : /*
2821 : : * We should have an UNDER permission flag for this, but for now,
2822 : : * demand that creator of a child table own the parent.
2823 : : */
2824 [ - + ]: 8221 : if (!object_ownercheck(RelationRelationId, RelationGetRelid(relation), GetUserId()))
2825 : 0 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(relation->rd_rel->relkind),
2826 : 0 : RelationGetRelationName(relation));
2827 : :
2828 : 8221 : tupleDesc = RelationGetDescr(relation);
2829 : 8221 : constr = tupleDesc->constr;
2830 : :
2831 : : /*
2832 : : * newattmap->attnums[] will contain the child-table attribute numbers
2833 : : * for the attributes of this parent table. (They are not the same
2834 : : * for parents after the first one, nor if we have dropped columns.)
2835 : : */
2836 : 8221 : newattmap = make_attrmap(tupleDesc->natts);
2837 : :
2838 : : /* We can't process inherited defaults until newattmap is complete. */
2839 : 8221 : inherited_defaults = cols_with_defaults = NIL;
2840 : :
2841 : : /*
2842 : : * Request attnotnull on columns that have a not-null constraint
2843 : : * that's not marked NO INHERIT (even if not valid).
2844 : : */
2845 : 8221 : nnconstrs = RelationGetNotNullConstraints(RelationGetRelid(relation),
2846 : : true, false);
2847 [ + + + + : 18186 : foreach_ptr(CookedConstraint, cc, nnconstrs)
+ + ]
2848 : 1744 : nncols = bms_add_member(nncols, cc->attnum);
2849 : :
2850 [ + + ]: 24560 : for (AttrNumber parent_attno = 1; parent_attno <= tupleDesc->natts;
2851 : 16339 : parent_attno++)
2852 : : {
2853 : 16363 : Form_pg_attribute attribute = TupleDescAttr(tupleDesc,
2854 : : parent_attno - 1);
2855 : 16363 : char *attributeName = NameStr(attribute->attname);
2856 : : int exist_attno;
2857 : : ColumnDef *newdef;
2858 : : ColumnDef *mergeddef;
2859 : :
2860 : : /*
2861 : : * Ignore dropped columns in the parent.
2862 : : */
2863 [ + + ]: 16363 : if (attribute->attisdropped)
2864 : 132 : continue; /* leave newattmap->attnums entry as zero */
2865 : :
2866 : : /*
2867 : : * Create new column definition
2868 : : */
2869 : 16231 : newdef = makeColumnDef(attributeName, attribute->atttypid,
2870 : : attribute->atttypmod, attribute->attcollation);
2871 : 16231 : newdef->storage = attribute->attstorage;
2872 : 16231 : newdef->generated = attribute->attgenerated;
2873 [ + + ]: 16231 : if (CompressionMethodIsValid(attribute->attcompression))
2874 : 24 : newdef->compression =
2875 : 24 : pstrdup(GetCompressionMethodName(attribute->attcompression));
2876 : :
2877 : : /*
2878 : : * Regular inheritance children are independent enough not to
2879 : : * inherit identity columns. But partitions are integral part of
2880 : : * a partitioned table and inherit identity column.
2881 : : */
2882 [ + + ]: 16231 : if (is_partition)
2883 : 13189 : newdef->identity = attribute->attidentity;
2884 : :
2885 : : /*
2886 : : * Does it match some previously considered column from another
2887 : : * parent?
2888 : : */
2889 : 16231 : exist_attno = findAttrByName(attributeName, inh_columns);
2890 [ + + ]: 16231 : if (exist_attno > 0)
2891 : : {
2892 : : /*
2893 : : * Yes, try to merge the two column definitions.
2894 : : */
2895 : 253 : mergeddef = MergeInheritedAttribute(inh_columns, exist_attno, newdef);
2896 : :
2897 : 229 : newattmap->attnums[parent_attno - 1] = exist_attno;
2898 : :
2899 : : /*
2900 : : * Partitions have only one parent, so conflict should never
2901 : : * occur.
2902 : : */
2903 : : Assert(!is_partition);
2904 : : }
2905 : : else
2906 : : {
2907 : : /*
2908 : : * No, create a new inherited column
2909 : : */
2910 : 15978 : newdef->inhcount = 1;
2911 : 15978 : newdef->is_local = false;
2912 : 15978 : inh_columns = lappend(inh_columns, newdef);
2913 : :
2914 : 15978 : newattmap->attnums[parent_attno - 1] = ++child_attno;
2915 : 15978 : mergeddef = newdef;
2916 : : }
2917 : :
2918 : : /*
2919 : : * mark attnotnull if parent has it
2920 : : */
2921 [ + + ]: 16207 : if (bms_is_member(parent_attno, nncols))
2922 : 1744 : mergeddef->is_not_null = true;
2923 : :
2924 : : /*
2925 : : * Locate default/generation expression if any
2926 : : */
2927 [ + + ]: 16207 : if (attribute->atthasdef)
2928 : : {
2929 : : Node *this_default;
2930 : :
2931 : 566 : this_default = TupleDescGetDefault(tupleDesc, parent_attno);
2932 [ - + ]: 566 : if (this_default == NULL)
2933 [ # # ]: 0 : elog(ERROR, "default expression not found for attribute %d of relation \"%s\"",
2934 : : parent_attno, RelationGetRelationName(relation));
2935 : :
2936 : : /*
2937 : : * If it's a GENERATED default, it might contain Vars that
2938 : : * need to be mapped to the inherited column(s)' new numbers.
2939 : : * We can't do that till newattmap is ready, so just remember
2940 : : * all the inherited default expressions for the moment.
2941 : : */
2942 : 566 : inherited_defaults = lappend(inherited_defaults, this_default);
2943 : 566 : cols_with_defaults = lappend(cols_with_defaults, mergeddef);
2944 : : }
2945 : : }
2946 : :
2947 : : /*
2948 : : * Now process any inherited default expressions, adjusting attnos
2949 : : * using the completed newattmap map.
2950 : : */
2951 [ + + + + : 8763 : forboth(lc1, inherited_defaults, lc2, cols_with_defaults)
+ + + + +
+ + - +
+ ]
2952 : : {
2953 : 566 : Node *this_default = (Node *) lfirst(lc1);
2954 : 566 : ColumnDef *def = (ColumnDef *) lfirst(lc2);
2955 : : bool found_whole_row;
2956 : :
2957 : : /* Adjust Vars to match new table's column numbering */
2958 : 566 : this_default = map_variable_attnos(this_default,
2959 : : 1, 0,
2960 : : newattmap,
2961 : : InvalidOid, &found_whole_row);
2962 : :
2963 : : /*
2964 : : * For the moment we have to reject whole-row variables. We could
2965 : : * convert them, if we knew the new table's rowtype OID, but that
2966 : : * hasn't been assigned yet. (A variable could only appear in a
2967 : : * generation expression, so the error message is correct.)
2968 : : */
2969 [ - + ]: 566 : if (found_whole_row)
2970 [ # # ]: 0 : ereport(ERROR,
2971 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2972 : : errmsg("cannot convert whole-row table reference"),
2973 : : errdetail("Generation expression for column \"%s\" contains a whole-row reference to table \"%s\".",
2974 : : def->colname,
2975 : : RelationGetRelationName(relation))));
2976 : :
2977 : : /*
2978 : : * If we already had a default from some prior parent, check to
2979 : : * see if they are the same. If so, no problem; if not, mark the
2980 : : * column as having a bogus default. Below, we will complain if
2981 : : * the bogus default isn't overridden by the child columns.
2982 : : */
2983 : : Assert(def->raw_default == NULL);
2984 [ + + ]: 566 : if (def->cooked_default == NULL)
2985 : 538 : def->cooked_default = this_default;
2986 [ + + ]: 28 : else if (!equal(def->cooked_default, this_default))
2987 : : {
2988 : 24 : def->cooked_default = &bogus_marker;
2989 : 24 : have_bogus_defaults = true;
2990 : : }
2991 : : }
2992 : :
2993 : : /*
2994 : : * Now copy the CHECK constraints of this parent, adjusting attnos
2995 : : * using the completed newattmap map. Identically named constraints
2996 : : * are merged if possible, else we throw error.
2997 : : */
2998 [ + + + + ]: 8197 : if (constr && constr->num_check > 0)
2999 : : {
3000 : 293 : ConstrCheck *check = constr->check;
3001 : :
3002 [ + + ]: 866 : for (int i = 0; i < constr->num_check; i++)
3003 : : {
3004 : 573 : char *name = check[i].ccname;
3005 : : Node *expr;
3006 : : bool found_whole_row;
3007 : :
3008 : : /* ignore if the constraint is non-inheritable */
3009 [ + + ]: 573 : if (check[i].ccnoinherit)
3010 : 32 : continue;
3011 : :
3012 : : /* Adjust Vars to match new table's column numbering */
3013 : 541 : expr = map_variable_attnos(stringToNode(check[i].ccbin),
3014 : : 1, 0,
3015 : : newattmap,
3016 : : InvalidOid, &found_whole_row);
3017 : :
3018 : : /*
3019 : : * For the moment we have to reject whole-row variables. We
3020 : : * could convert them, if we knew the new table's rowtype OID,
3021 : : * but that hasn't been assigned yet.
3022 : : */
3023 [ - + ]: 541 : if (found_whole_row)
3024 [ # # ]: 0 : ereport(ERROR,
3025 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3026 : : errmsg("cannot convert whole-row table reference"),
3027 : : errdetail("Constraint \"%s\" contains a whole-row reference to table \"%s\".",
3028 : : name,
3029 : : RelationGetRelationName(relation))));
3030 : :
3031 : 541 : constraints = MergeCheckConstraint(constraints, name, expr,
3032 : 541 : check[i].ccenforced);
3033 : : }
3034 : : }
3035 : :
3036 : : /*
3037 : : * Also copy the not-null constraints from this parent. The
3038 : : * attnotnull markings were already installed above.
3039 : : */
3040 [ + + + + : 18138 : foreach_ptr(CookedConstraint, nn, nnconstrs)
+ + ]
3041 : : {
3042 : : Assert(nn->contype == CONSTR_NOTNULL);
3043 : :
3044 : 1744 : nn->attnum = newattmap->attnums[nn->attnum - 1];
3045 : :
3046 : 1744 : nnconstraints = lappend(nnconstraints, nn);
3047 : : }
3048 : :
3049 : 8197 : free_attrmap(newattmap);
3050 : :
3051 : : /*
3052 : : * Close the parent rel, but keep our lock on it until xact commit.
3053 : : * That will prevent someone else from deleting or ALTERing the parent
3054 : : * before the child is committed.
3055 : : */
3056 : 8197 : table_close(relation, NoLock);
3057 : : }
3058 : :
3059 : : /*
3060 : : * If we had no inherited attributes, the result columns are just the
3061 : : * explicitly declared columns. Otherwise, we need to merge the declared
3062 : : * columns into the inherited column list. Although, we never have any
3063 : : * explicitly declared columns if the table is a partition.
3064 : : */
3065 [ + + ]: 42355 : if (inh_columns != NIL)
3066 : : {
3067 : 7895 : int newcol_attno = 0;
3068 : :
3069 [ + + + + : 8547 : foreach(lc, columns)
+ + ]
3070 : : {
3071 : 704 : ColumnDef *newdef = lfirst_node(ColumnDef, lc);
3072 : 704 : char *attributeName = newdef->colname;
3073 : : int exist_attno;
3074 : :
3075 : : /*
3076 : : * Partitions have only one parent and have no column definitions
3077 : : * of their own, so conflict should never occur.
3078 : : */
3079 : : Assert(!is_partition);
3080 : :
3081 : 704 : newcol_attno++;
3082 : :
3083 : : /*
3084 : : * Does it match some inherited column?
3085 : : */
3086 : 704 : exist_attno = findAttrByName(attributeName, inh_columns);
3087 [ + + ]: 704 : if (exist_attno > 0)
3088 : : {
3089 : : /*
3090 : : * Yes, try to merge the two column definitions.
3091 : : */
3092 : 251 : MergeChildAttribute(inh_columns, exist_attno, newcol_attno, newdef);
3093 : : }
3094 : : else
3095 : : {
3096 : : /*
3097 : : * No, attach new column unchanged to result columns.
3098 : : */
3099 : 453 : inh_columns = lappend(inh_columns, newdef);
3100 : : }
3101 : : }
3102 : :
3103 : 7843 : columns = inh_columns;
3104 : :
3105 : : /*
3106 : : * Check that we haven't exceeded the legal # of columns after merging
3107 : : * in inherited columns.
3108 : : */
3109 [ - + ]: 7843 : if (list_length(columns) > MaxHeapAttributeNumber)
3110 [ # # ]: 0 : ereport(ERROR,
3111 : : (errcode(ERRCODE_TOO_MANY_COLUMNS),
3112 : : errmsg("tables can have at most %d columns",
3113 : : MaxHeapAttributeNumber)));
3114 : : }
3115 : :
3116 : : /*
3117 : : * Now that we have the column definition list for a partition, we can
3118 : : * check whether the columns referenced in the column constraint specs
3119 : : * actually exist. Also, merge column defaults.
3120 : : */
3121 [ + + ]: 42303 : if (is_partition)
3122 : : {
3123 [ + + + + : 6667 : foreach(lc, saved_columns)
+ + ]
3124 : : {
3125 : 151 : ColumnDef *restdef = lfirst(lc);
3126 : 151 : bool found = false;
3127 : : ListCell *l;
3128 : :
3129 [ + - + + : 549 : foreach(l, columns)
+ + ]
3130 : : {
3131 : 422 : ColumnDef *coldef = lfirst(l);
3132 : :
3133 [ + + ]: 422 : if (strcmp(coldef->colname, restdef->colname) == 0)
3134 : : {
3135 : 151 : found = true;
3136 : :
3137 : : /*
3138 : : * Check for conflicts related to generated columns.
3139 : : *
3140 : : * Same rules as above: generated-ness has to match the
3141 : : * parent, but the contents of the generation expression
3142 : : * can be different.
3143 : : */
3144 [ + + ]: 151 : if (coldef->generated)
3145 : : {
3146 [ + - + + ]: 80 : if (restdef->raw_default && !restdef->generated)
3147 [ + - ]: 8 : ereport(ERROR,
3148 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3149 : : errmsg("column \"%s\" inherits from generated column but specifies default",
3150 : : restdef->colname)));
3151 [ - + ]: 72 : if (restdef->identity)
3152 [ # # ]: 0 : ereport(ERROR,
3153 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3154 : : errmsg("column \"%s\" inherits from generated column but specifies identity",
3155 : : restdef->colname)));
3156 : : }
3157 : : else
3158 : : {
3159 [ + + ]: 71 : if (restdef->generated)
3160 [ + - ]: 8 : ereport(ERROR,
3161 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3162 : : errmsg("child column \"%s\" specifies generation expression",
3163 : : restdef->colname),
3164 : : errhint("A child table column cannot be generated unless its parent column is.")));
3165 : : }
3166 : :
3167 [ + + + - : 135 : if (coldef->generated && restdef->generated && coldef->generated != restdef->generated)
+ + ]
3168 [ + - + + : 8 : ereport(ERROR,
+ + ]
3169 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3170 : : errmsg("column \"%s\" inherits from generated column of different kind",
3171 : : restdef->colname),
3172 : : errdetail("Parent column is %s, child column is %s.",
3173 : : coldef->generated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL",
3174 : : restdef->generated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL")));
3175 : :
3176 : : /*
3177 : : * Override the parent's default value for this column
3178 : : * (coldef->cooked_default) with the partition's local
3179 : : * definition (restdef->raw_default), if there's one. It
3180 : : * should be physically impossible to get a cooked default
3181 : : * in the local definition or a raw default in the
3182 : : * inherited definition, but make sure they're nulls, for
3183 : : * future-proofing.
3184 : : */
3185 : : Assert(restdef->cooked_default == NULL);
3186 : : Assert(coldef->raw_default == NULL);
3187 [ + + ]: 127 : if (restdef->raw_default)
3188 : : {
3189 : 79 : coldef->raw_default = restdef->raw_default;
3190 : 79 : coldef->cooked_default = NULL;
3191 : : }
3192 : : }
3193 : : }
3194 : :
3195 : : /* complain for constraints on columns not in parent */
3196 [ - + ]: 127 : if (!found)
3197 [ # # ]: 0 : ereport(ERROR,
3198 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
3199 : : errmsg("column \"%s\" does not exist",
3200 : : restdef->colname)));
3201 : : }
3202 : : }
3203 : :
3204 : : /*
3205 : : * If we found any conflicting parent default values, check to make sure
3206 : : * they were overridden by the child.
3207 : : */
3208 [ + + ]: 42279 : if (have_bogus_defaults)
3209 : : {
3210 [ + - + + : 60 : foreach(lc, columns)
+ + ]
3211 : : {
3212 : 48 : ColumnDef *def = lfirst(lc);
3213 : :
3214 [ + + ]: 48 : if (def->cooked_default == &bogus_marker)
3215 : : {
3216 [ + + ]: 12 : if (def->generated)
3217 [ + - ]: 8 : ereport(ERROR,
3218 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3219 : : errmsg("column \"%s\" inherits conflicting generation expressions",
3220 : : def->colname),
3221 : : errhint("To resolve the conflict, specify a generation expression explicitly.")));
3222 : : else
3223 [ + - ]: 4 : ereport(ERROR,
3224 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3225 : : errmsg("column \"%s\" inherits conflicting default values",
3226 : : def->colname),
3227 : : errhint("To resolve the conflict, specify a default explicitly.")));
3228 : : }
3229 : : }
3230 : : }
3231 : :
3232 : 42267 : *supconstr = constraints;
3233 : 42267 : *supnotnulls = nnconstraints;
3234 : :
3235 : 42267 : return columns;
3236 : : }
3237 : :
3238 : :
3239 : : /*
3240 : : * MergeCheckConstraint
3241 : : * Try to merge an inherited CHECK constraint with previous ones
3242 : : *
3243 : : * If we inherit identically-named constraints from multiple parents, we must
3244 : : * merge them, or throw an error if they don't have identical definitions.
3245 : : *
3246 : : * constraints is a list of CookedConstraint structs for previous constraints.
3247 : : *
3248 : : * If the new constraint matches an existing one, then the existing
3249 : : * constraint's inheritance count is updated. If there is a conflict (same
3250 : : * name but different expression), throw an error. If the constraint neither
3251 : : * matches nor conflicts with an existing one, a new constraint is appended to
3252 : : * the list.
3253 : : */
3254 : : static List *
3255 : 541 : MergeCheckConstraint(List *constraints, const char *name, Node *expr, bool is_enforced)
3256 : : {
3257 : : ListCell *lc;
3258 : : CookedConstraint *newcon;
3259 : :
3260 [ + + + + : 1569 : foreach(lc, constraints)
+ + ]
3261 : : {
3262 : 1136 : CookedConstraint *ccon = (CookedConstraint *) lfirst(lc);
3263 : :
3264 : : Assert(ccon->contype == CONSTR_CHECK);
3265 : :
3266 : : /* Non-matching names never conflict */
3267 [ + + ]: 1136 : if (strcmp(ccon->name, name) != 0)
3268 : 1028 : continue;
3269 : :
3270 [ + - ]: 108 : if (equal(expr, ccon->expr))
3271 : : {
3272 : : /* OK to merge constraint with existing */
3273 [ - + ]: 108 : if (pg_add_s16_overflow(ccon->inhcount, 1,
3274 : : &ccon->inhcount))
3275 [ # # ]: 0 : ereport(ERROR,
3276 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
3277 : : errmsg("too many inheritance parents"));
3278 : :
3279 : : /*
3280 : : * When enforceability differs, the merged constraint should be
3281 : : * marked as ENFORCED because one of the parents is ENFORCED.
3282 : : */
3283 [ + + + + ]: 108 : if (!ccon->is_enforced && is_enforced)
3284 : : {
3285 : 32 : ccon->is_enforced = true;
3286 : 32 : ccon->skip_validation = false;
3287 : : }
3288 : :
3289 : 108 : return constraints;
3290 : : }
3291 : :
3292 [ # # ]: 0 : ereport(ERROR,
3293 : : (errcode(ERRCODE_DUPLICATE_OBJECT),
3294 : : errmsg("check constraint name \"%s\" appears multiple times but with different expressions",
3295 : : name)));
3296 : : }
3297 : :
3298 : : /*
3299 : : * Constraint couldn't be merged with an existing one and also didn't
3300 : : * conflict with an existing one, so add it as a new one to the list.
3301 : : */
3302 : 433 : newcon = palloc0_object(CookedConstraint);
3303 : 433 : newcon->contype = CONSTR_CHECK;
3304 : 433 : newcon->name = pstrdup(name);
3305 : 433 : newcon->expr = expr;
3306 : 433 : newcon->inhcount = 1;
3307 : 433 : newcon->is_enforced = is_enforced;
3308 : 433 : newcon->skip_validation = !is_enforced;
3309 : 433 : return lappend(constraints, newcon);
3310 : : }
3311 : :
3312 : : /*
3313 : : * MergeChildAttribute
3314 : : * Merge given child attribute definition into given inherited attribute.
3315 : : *
3316 : : * Input arguments:
3317 : : * 'inh_columns' is the list of inherited ColumnDefs.
3318 : : * 'exist_attno' is the number of the inherited attribute in inh_columns
3319 : : * 'newcol_attno' is the attribute number in child table's schema definition
3320 : : * 'newdef' is the column/attribute definition from the child table.
3321 : : *
3322 : : * The ColumnDef in 'inh_columns' list is modified. The child attribute's
3323 : : * ColumnDef remains unchanged.
3324 : : *
3325 : : * Notes:
3326 : : * - The attribute is merged according to the rules laid out in the prologue
3327 : : * of MergeAttributes().
3328 : : * - If matching inherited attribute exists but the child attribute can not be
3329 : : * merged into it, the function throws respective errors.
3330 : : * - A partition can not have its own column definitions. Hence this function
3331 : : * is applicable only to a regular inheritance child.
3332 : : */
3333 : : static void
3334 : 251 : MergeChildAttribute(List *inh_columns, int exist_attno, int newcol_attno, const ColumnDef *newdef)
3335 : : {
3336 : 251 : char *attributeName = newdef->colname;
3337 : : ColumnDef *inhdef;
3338 : : Oid inhtypeid,
3339 : : newtypeid;
3340 : : int32 inhtypmod,
3341 : : newtypmod;
3342 : : Oid inhcollid,
3343 : : newcollid;
3344 : :
3345 [ + + ]: 251 : if (exist_attno == newcol_attno)
3346 [ + - ]: 229 : ereport(NOTICE,
3347 : : (errmsg("merging column \"%s\" with inherited definition",
3348 : : attributeName)));
3349 : : else
3350 [ + - ]: 22 : ereport(NOTICE,
3351 : : (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
3352 : : errdetail("User-specified column moved to the position of the inherited column.")));
3353 : :
3354 : 251 : inhdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
3355 : :
3356 : : /*
3357 : : * Must have the same type and typmod
3358 : : */
3359 : 251 : typenameTypeIdAndMod(NULL, inhdef->typeName, &inhtypeid, &inhtypmod);
3360 : 251 : typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
3361 [ + + + + ]: 251 : if (inhtypeid != newtypeid || inhtypmod != newtypmod)
3362 [ + - ]: 8 : ereport(ERROR,
3363 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
3364 : : errmsg("column \"%s\" has a type conflict",
3365 : : attributeName),
3366 : : errdetail("%s versus %s",
3367 : : format_type_with_typemod(inhtypeid, inhtypmod),
3368 : : format_type_with_typemod(newtypeid, newtypmod))));
3369 : :
3370 : : /*
3371 : : * Must have the same collation
3372 : : */
3373 : 243 : inhcollid = GetColumnDefCollation(NULL, inhdef, inhtypeid);
3374 : 243 : newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
3375 [ + + ]: 243 : if (inhcollid != newcollid)
3376 [ + - ]: 4 : ereport(ERROR,
3377 : : (errcode(ERRCODE_COLLATION_MISMATCH),
3378 : : errmsg("column \"%s\" has a collation conflict",
3379 : : attributeName),
3380 : : errdetail("\"%s\" versus \"%s\"",
3381 : : get_collation_name(inhcollid),
3382 : : get_collation_name(newcollid))));
3383 : :
3384 : : /*
3385 : : * Identity is never inherited by a regular inheritance child. Pick
3386 : : * child's identity definition if there's one.
3387 : : */
3388 : 239 : inhdef->identity = newdef->identity;
3389 : :
3390 : : /*
3391 : : * Copy storage parameter
3392 : : */
3393 [ - + ]: 239 : if (inhdef->storage == 0)
3394 : 0 : inhdef->storage = newdef->storage;
3395 [ + + + + ]: 239 : else if (newdef->storage != 0 && inhdef->storage != newdef->storage)
3396 [ + - ]: 4 : ereport(ERROR,
3397 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
3398 : : errmsg("column \"%s\" has a storage parameter conflict",
3399 : : attributeName),
3400 : : errdetail("%s versus %s",
3401 : : storage_name(inhdef->storage),
3402 : : storage_name(newdef->storage))));
3403 : :
3404 : : /*
3405 : : * Copy compression parameter
3406 : : */
3407 [ + + ]: 235 : if (inhdef->compression == NULL)
3408 : 231 : inhdef->compression = newdef->compression;
3409 [ + - ]: 4 : else if (newdef->compression != NULL)
3410 : : {
3411 [ + - ]: 4 : if (strcmp(inhdef->compression, newdef->compression) != 0)
3412 [ + - ]: 4 : ereport(ERROR,
3413 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
3414 : : errmsg("column \"%s\" has a compression method conflict",
3415 : : attributeName),
3416 : : errdetail("%s versus %s", inhdef->compression, newdef->compression)));
3417 : : }
3418 : :
3419 : : /*
3420 : : * Merge of not-null constraints = OR 'em together
3421 : : */
3422 : 231 : inhdef->is_not_null |= newdef->is_not_null;
3423 : :
3424 : : /*
3425 : : * Check for conflicts related to generated columns.
3426 : : *
3427 : : * If the parent column is generated, the child column will be made a
3428 : : * generated column if it isn't already. If it is a generated column,
3429 : : * we'll take its generation expression in preference to the parent's. We
3430 : : * must check that the child column doesn't specify a default value or
3431 : : * identity, which matches the rules for a single column in
3432 : : * parse_utilcmd.c.
3433 : : *
3434 : : * Conversely, if the parent column is not generated, the child column
3435 : : * can't be either. (We used to allow that, but it results in being able
3436 : : * to override the generation expression via UPDATEs through the parent.)
3437 : : */
3438 [ + + ]: 231 : if (inhdef->generated)
3439 : : {
3440 [ + + + + ]: 41 : if (newdef->raw_default && !newdef->generated)
3441 [ + - ]: 8 : ereport(ERROR,
3442 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3443 : : errmsg("column \"%s\" inherits from generated column but specifies default",
3444 : : inhdef->colname)));
3445 [ + + ]: 33 : if (newdef->identity)
3446 [ + - ]: 8 : ereport(ERROR,
3447 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3448 : : errmsg("column \"%s\" inherits from generated column but specifies identity",
3449 : : inhdef->colname)));
3450 : : }
3451 : : else
3452 : : {
3453 [ + + ]: 190 : if (newdef->generated)
3454 [ + - ]: 8 : ereport(ERROR,
3455 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3456 : : errmsg("child column \"%s\" specifies generation expression",
3457 : : inhdef->colname),
3458 : : errhint("A child table column cannot be generated unless its parent column is.")));
3459 : : }
3460 : :
3461 [ + + + + : 207 : if (inhdef->generated && newdef->generated && newdef->generated != inhdef->generated)
+ + ]
3462 [ + - + + : 8 : ereport(ERROR,
+ + ]
3463 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3464 : : errmsg("column \"%s\" inherits from generated column of different kind",
3465 : : inhdef->colname),
3466 : : errdetail("Parent column is %s, child column is %s.",
3467 : : inhdef->generated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL",
3468 : : newdef->generated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL")));
3469 : :
3470 : : /*
3471 : : * If new def has a default, override previous default
3472 : : */
3473 [ + + ]: 199 : if (newdef->raw_default != NULL)
3474 : : {
3475 : 20 : inhdef->raw_default = newdef->raw_default;
3476 : 20 : inhdef->cooked_default = newdef->cooked_default;
3477 : : }
3478 : :
3479 : : /* Mark the column as locally defined */
3480 : 199 : inhdef->is_local = true;
3481 : 199 : }
3482 : :
3483 : : /*
3484 : : * MergeInheritedAttribute
3485 : : * Merge given parent attribute definition into specified attribute
3486 : : * inherited from the previous parents.
3487 : : *
3488 : : * Input arguments:
3489 : : * 'inh_columns' is the list of previously inherited ColumnDefs.
3490 : : * 'exist_attno' is the number the existing matching attribute in inh_columns.
3491 : : * 'newdef' is the new parent column/attribute definition to be merged.
3492 : : *
3493 : : * The matching ColumnDef in 'inh_columns' list is modified and returned.
3494 : : *
3495 : : * Notes:
3496 : : * - The attribute is merged according to the rules laid out in the prologue
3497 : : * of MergeAttributes().
3498 : : * - If matching inherited attribute exists but the new attribute can not be
3499 : : * merged into it, the function throws respective errors.
3500 : : * - A partition inherits from only a single parent. Hence this function is
3501 : : * applicable only to a regular inheritance.
3502 : : */
3503 : : static ColumnDef *
3504 : 253 : MergeInheritedAttribute(List *inh_columns,
3505 : : int exist_attno,
3506 : : const ColumnDef *newdef)
3507 : : {
3508 : 253 : char *attributeName = newdef->colname;
3509 : : ColumnDef *prevdef;
3510 : : Oid prevtypeid,
3511 : : newtypeid;
3512 : : int32 prevtypmod,
3513 : : newtypmod;
3514 : : Oid prevcollid,
3515 : : newcollid;
3516 : :
3517 [ + - ]: 253 : ereport(NOTICE,
3518 : : (errmsg("merging multiple inherited definitions of column \"%s\"",
3519 : : attributeName)));
3520 : 253 : prevdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
3521 : :
3522 : : /*
3523 : : * Must have the same type and typmod
3524 : : */
3525 : 253 : typenameTypeIdAndMod(NULL, prevdef->typeName, &prevtypeid, &prevtypmod);
3526 : 253 : typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
3527 [ + - - + ]: 253 : if (prevtypeid != newtypeid || prevtypmod != newtypmod)
3528 [ # # ]: 0 : ereport(ERROR,
3529 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
3530 : : errmsg("inherited column \"%s\" has a type conflict",
3531 : : attributeName),
3532 : : errdetail("%s versus %s",
3533 : : format_type_with_typemod(prevtypeid, prevtypmod),
3534 : : format_type_with_typemod(newtypeid, newtypmod))));
3535 : :
3536 : : /*
3537 : : * Must have the same collation
3538 : : */
3539 : 253 : prevcollid = GetColumnDefCollation(NULL, prevdef, prevtypeid);
3540 : 253 : newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
3541 [ - + ]: 253 : if (prevcollid != newcollid)
3542 [ # # ]: 0 : ereport(ERROR,
3543 : : (errcode(ERRCODE_COLLATION_MISMATCH),
3544 : : errmsg("inherited column \"%s\" has a collation conflict",
3545 : : attributeName),
3546 : : errdetail("\"%s\" versus \"%s\"",
3547 : : get_collation_name(prevcollid),
3548 : : get_collation_name(newcollid))));
3549 : :
3550 : : /*
3551 : : * Copy/check storage parameter
3552 : : */
3553 [ - + ]: 253 : if (prevdef->storage == 0)
3554 : 0 : prevdef->storage = newdef->storage;
3555 [ + + ]: 253 : else if (prevdef->storage != newdef->storage)
3556 [ + - ]: 4 : ereport(ERROR,
3557 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
3558 : : errmsg("inherited column \"%s\" has a storage parameter conflict",
3559 : : attributeName),
3560 : : errdetail("%s versus %s",
3561 : : storage_name(prevdef->storage),
3562 : : storage_name(newdef->storage))));
3563 : :
3564 : : /*
3565 : : * Copy/check compression parameter
3566 : : */
3567 [ + + ]: 249 : if (prevdef->compression == NULL)
3568 : 237 : prevdef->compression = newdef->compression;
3569 [ + + ]: 12 : else if (newdef->compression != NULL)
3570 : : {
3571 [ + - ]: 4 : if (strcmp(prevdef->compression, newdef->compression) != 0)
3572 [ + - ]: 4 : ereport(ERROR,
3573 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
3574 : : errmsg("column \"%s\" has a compression method conflict",
3575 : : attributeName),
3576 : : errdetail("%s versus %s",
3577 : : prevdef->compression, newdef->compression)));
3578 : : }
3579 : :
3580 : : /*
3581 : : * Check for GENERATED conflicts
3582 : : */
3583 [ + + ]: 245 : if (prevdef->generated != newdef->generated)
3584 [ + - ]: 16 : ereport(ERROR,
3585 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
3586 : : errmsg("inherited column \"%s\" has a generation conflict",
3587 : : attributeName)));
3588 : :
3589 : : /*
3590 : : * Default and other constraints are handled by the caller.
3591 : : */
3592 : :
3593 [ - + ]: 229 : if (pg_add_s16_overflow(prevdef->inhcount, 1,
3594 : : &prevdef->inhcount))
3595 [ # # ]: 0 : ereport(ERROR,
3596 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
3597 : : errmsg("too many inheritance parents"));
3598 : :
3599 : 229 : return prevdef;
3600 : : }
3601 : :
3602 : : /*
3603 : : * StoreCatalogInheritance
3604 : : * Updates the system catalogs with proper inheritance information.
3605 : : *
3606 : : * supers is a list of the OIDs of the new relation's direct ancestors.
3607 : : */
3608 : : static void
3609 : 41819 : StoreCatalogInheritance(Oid relationId, List *supers,
3610 : : bool child_is_partition)
3611 : : {
3612 : : Relation relation;
3613 : : int32 seqNumber;
3614 : : ListCell *entry;
3615 : :
3616 : : /*
3617 : : * sanity checks
3618 : : */
3619 : : Assert(OidIsValid(relationId));
3620 : :
3621 [ + + ]: 41819 : if (supers == NIL)
3622 : 34216 : return;
3623 : :
3624 : : /*
3625 : : * Store INHERITS information in pg_inherits using direct ancestors only.
3626 : : * Also enter dependencies on the direct ancestors, and make sure they are
3627 : : * marked with relhassubclass = true.
3628 : : *
3629 : : * (Once upon a time, both direct and indirect ancestors were found here
3630 : : * and then entered into pg_ipl. Since that catalog doesn't exist
3631 : : * anymore, there's no need to look for indirect ancestors.)
3632 : : */
3633 : 7603 : relation = table_open(InheritsRelationId, RowExclusiveLock);
3634 : :
3635 : 7603 : seqNumber = 1;
3636 [ + - + + : 15440 : foreach(entry, supers)
+ + ]
3637 : : {
3638 : 7837 : Oid parentOid = lfirst_oid(entry);
3639 : :
3640 : 7837 : StoreCatalogInheritance1(relationId, parentOid, seqNumber, relation,
3641 : : child_is_partition);
3642 : 7837 : seqNumber++;
3643 : : }
3644 : :
3645 : 7603 : table_close(relation, RowExclusiveLock);
3646 : : }
3647 : :
3648 : : /*
3649 : : * Make catalog entries showing relationId as being an inheritance child
3650 : : * of parentOid. inhRelation is the already-opened pg_inherits catalog.
3651 : : */
3652 : : static void
3653 : 9967 : StoreCatalogInheritance1(Oid relationId, Oid parentOid,
3654 : : int32 seqNumber, Relation inhRelation,
3655 : : bool child_is_partition)
3656 : : {
3657 : : ObjectAddress childobject,
3658 : : parentobject;
3659 : :
3660 : : /* store the pg_inherits row */
3661 : 9967 : StoreSingleInheritance(relationId, parentOid, seqNumber);
3662 : :
3663 : : /*
3664 : : * Store a dependency too
3665 : : */
3666 : 9967 : parentobject.classId = RelationRelationId;
3667 : 9967 : parentobject.objectId = parentOid;
3668 : 9967 : parentobject.objectSubId = 0;
3669 : 9967 : childobject.classId = RelationRelationId;
3670 : 9967 : childobject.objectId = relationId;
3671 : 9967 : childobject.objectSubId = 0;
3672 : :
3673 [ + + ]: 9967 : recordDependencyOn(&childobject, &parentobject,
3674 : : child_dependency_type(child_is_partition));
3675 : :
3676 : : /*
3677 : : * Post creation hook of this inheritance. Since object_access_hook
3678 : : * doesn't take multiple object identifiers, we relay oid of parent
3679 : : * relation using auxiliary_id argument.
3680 : : */
3681 [ - + ]: 9967 : InvokeObjectPostAlterHookArg(InheritsRelationId,
3682 : : relationId, 0,
3683 : : parentOid, false);
3684 : :
3685 : : /*
3686 : : * Mark the parent as having subclasses.
3687 : : */
3688 : 9967 : SetRelationHasSubclass(parentOid, true);
3689 : 9967 : }
3690 : :
3691 : : /*
3692 : : * Look for an existing column entry with the given name.
3693 : : *
3694 : : * Returns the index (starting with 1) if attribute already exists in columns,
3695 : : * 0 if it doesn't.
3696 : : */
3697 : : static int
3698 : 16935 : findAttrByName(const char *attributeName, const List *columns)
3699 : : {
3700 : : ListCell *lc;
3701 : 16935 : int i = 1;
3702 : :
3703 [ + + + + : 30694 : foreach(lc, columns)
+ + ]
3704 : : {
3705 [ + + ]: 14263 : if (strcmp(attributeName, lfirst_node(ColumnDef, lc)->colname) == 0)
3706 : 504 : return i;
3707 : :
3708 : 13759 : i++;
3709 : : }
3710 : 16431 : return 0;
3711 : : }
3712 : :
3713 : :
3714 : : /*
3715 : : * SetRelationHasSubclass
3716 : : * Set the value of the relation's relhassubclass field in pg_class.
3717 : : *
3718 : : * It's always safe to set this field to true, because all SQL commands are
3719 : : * ready to see true and then find no children. On the other hand, commands
3720 : : * generally assume zero children if this is false.
3721 : : *
3722 : : * Caller must hold any self-exclusive lock until end of transaction. If the
3723 : : * new value is false, caller must have acquired that lock before reading the
3724 : : * evidence that justified the false value. That way, it properly waits if
3725 : : * another backend is simultaneously concluding no need to change the tuple
3726 : : * (new and old values are true).
3727 : : *
3728 : : * NOTE: an important side-effect of this operation is that an SI invalidation
3729 : : * message is sent out to all backends --- including me --- causing plans
3730 : : * referencing the relation to be rebuilt with the new list of children.
3731 : : * This must happen even if we find that no change is needed in the pg_class
3732 : : * row.
3733 : : */
3734 : : void
3735 : 12472 : SetRelationHasSubclass(Oid relationId, bool relhassubclass)
3736 : : {
3737 : : Relation relationRelation;
3738 : : HeapTuple tuple;
3739 : : Form_pg_class classtuple;
3740 : :
3741 : : Assert(CheckRelationOidLockedByMe(relationId,
3742 : : ShareUpdateExclusiveLock, false) ||
3743 : : CheckRelationOidLockedByMe(relationId,
3744 : : ShareRowExclusiveLock, true));
3745 : :
3746 : : /*
3747 : : * Fetch a modifiable copy of the tuple, modify it, update pg_class.
3748 : : */
3749 : 12472 : relationRelation = table_open(RelationRelationId, RowExclusiveLock);
3750 : 12472 : tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relationId));
3751 [ - + ]: 12472 : if (!HeapTupleIsValid(tuple))
3752 [ # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", relationId);
3753 : 12472 : classtuple = (Form_pg_class) GETSTRUCT(tuple);
3754 : :
3755 [ + + ]: 12472 : if (classtuple->relhassubclass != relhassubclass)
3756 : : {
3757 : 5646 : classtuple->relhassubclass = relhassubclass;
3758 : 5646 : CatalogTupleUpdate(relationRelation, &tuple->t_self, tuple);
3759 : : }
3760 : : else
3761 : : {
3762 : : /* no need to change tuple, but force relcache rebuild anyway */
3763 : 6826 : CacheInvalidateRelcacheByTuple(tuple);
3764 : : }
3765 : :
3766 : 12472 : heap_freetuple(tuple);
3767 : 12472 : table_close(relationRelation, RowExclusiveLock);
3768 : 12472 : }
3769 : :
3770 : : /*
3771 : : * CheckRelationTableSpaceMove
3772 : : * Check if relation can be moved to new tablespace.
3773 : : *
3774 : : * NOTE: The caller must hold AccessExclusiveLock on the relation.
3775 : : *
3776 : : * Returns true if the relation can be moved to the new tablespace; raises
3777 : : * an error if it is not possible to do the move; returns false if the move
3778 : : * would have no effect.
3779 : : */
3780 : : bool
3781 : 157 : CheckRelationTableSpaceMove(Relation rel, Oid newTableSpaceId)
3782 : : {
3783 : : Oid oldTableSpaceId;
3784 : :
3785 : : /*
3786 : : * No work if no change in tablespace. Note that MyDatabaseTableSpace is
3787 : : * stored as 0.
3788 : : */
3789 : 157 : oldTableSpaceId = rel->rd_rel->reltablespace;
3790 [ + + ]: 157 : if (newTableSpaceId == oldTableSpaceId ||
3791 [ + + + + ]: 152 : (newTableSpaceId == MyDatabaseTableSpace && oldTableSpaceId == 0))
3792 : 10 : return false;
3793 : :
3794 : : /*
3795 : : * We cannot support moving mapped relations into different tablespaces.
3796 : : * (In particular this eliminates all shared catalogs.)
3797 : : */
3798 [ + + + + : 147 : if (RelationIsMapped(rel))
+ - + + +
+ - + ]
3799 [ # # ]: 0 : ereport(ERROR,
3800 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3801 : : errmsg("cannot move system relation \"%s\"",
3802 : : RelationGetRelationName(rel))));
3803 : :
3804 : : /* Cannot move a non-shared relation into pg_global */
3805 [ + + ]: 147 : if (newTableSpaceId == GLOBALTABLESPACE_OID)
3806 [ + - ]: 8 : ereport(ERROR,
3807 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
3808 : : errmsg("only shared relations can be placed in pg_global tablespace")));
3809 : :
3810 : : /*
3811 : : * Do not allow moving temp tables of other backends ... their local
3812 : : * buffer manager is not going to cope.
3813 : : */
3814 [ + + - + ]: 139 : if (RELATION_IS_OTHER_TEMP(rel))
3815 [ # # ]: 0 : ereport(ERROR,
3816 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3817 : : errmsg("cannot move temporary tables of other sessions")));
3818 : :
3819 : 139 : return true;
3820 : : }
3821 : :
3822 : : /*
3823 : : * SetRelationTableSpace
3824 : : * Set new reltablespace and relfilenumber in pg_class entry.
3825 : : *
3826 : : * newTableSpaceId is the new tablespace for the relation, and
3827 : : * newRelFilenumber its new filenumber. If newRelFilenumber is
3828 : : * InvalidRelFileNumber, this field is not updated.
3829 : : *
3830 : : * NOTE: The caller must hold AccessExclusiveLock on the relation.
3831 : : *
3832 : : * The caller of this routine had better check if a relation can be
3833 : : * moved to this new tablespace by calling CheckRelationTableSpaceMove()
3834 : : * first, and is responsible for making the change visible with
3835 : : * CommandCounterIncrement().
3836 : : */
3837 : : void
3838 : 139 : SetRelationTableSpace(Relation rel,
3839 : : Oid newTableSpaceId,
3840 : : RelFileNumber newRelFilenumber)
3841 : : {
3842 : : Relation pg_class;
3843 : : HeapTuple tuple;
3844 : : ItemPointerData otid;
3845 : : Form_pg_class rd_rel;
3846 : 139 : Oid reloid = RelationGetRelid(rel);
3847 : :
3848 : : Assert(CheckRelationTableSpaceMove(rel, newTableSpaceId));
3849 : :
3850 : : /* Get a modifiable copy of the relation's pg_class row. */
3851 : 139 : pg_class = table_open(RelationRelationId, RowExclusiveLock);
3852 : :
3853 : 139 : tuple = SearchSysCacheLockedCopy1(RELOID, ObjectIdGetDatum(reloid));
3854 [ - + ]: 139 : if (!HeapTupleIsValid(tuple))
3855 [ # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", reloid);
3856 : 139 : otid = tuple->t_self;
3857 : 139 : rd_rel = (Form_pg_class) GETSTRUCT(tuple);
3858 : :
3859 : : /* Update the pg_class row. */
3860 : 278 : rd_rel->reltablespace = (newTableSpaceId == MyDatabaseTableSpace) ?
3861 [ + + ]: 139 : InvalidOid : newTableSpaceId;
3862 [ + + ]: 139 : if (RelFileNumberIsValid(newRelFilenumber))
3863 : 110 : rd_rel->relfilenode = newRelFilenumber;
3864 : 139 : CatalogTupleUpdate(pg_class, &otid, tuple);
3865 : 139 : UnlockTuple(pg_class, &otid, InplaceUpdateTupleLock);
3866 : :
3867 : : /*
3868 : : * Record dependency on tablespace. This is only required for relations
3869 : : * that have no physical storage.
3870 : : */
3871 [ + + + + : 139 : if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
+ - + + +
+ ]
3872 : 20 : changeDependencyOnTablespace(RelationRelationId, reloid,
3873 : : rd_rel->reltablespace);
3874 : :
3875 : 139 : heap_freetuple(tuple);
3876 : 139 : table_close(pg_class, RowExclusiveLock);
3877 : 139 : }
3878 : :
3879 : : /*
3880 : : * renameatt_check - basic sanity checks before attribute rename
3881 : : */
3882 : : static void
3883 : 692 : renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
3884 : : {
3885 : 692 : char relkind = classform->relkind;
3886 : :
3887 [ + + + + ]: 692 : if (classform->reloftype && !recursing)
3888 [ + - ]: 4 : ereport(ERROR,
3889 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
3890 : : errmsg("cannot rename column of typed table")));
3891 : :
3892 : : /*
3893 : : * Renaming the columns of sequences or toast tables doesn't actually
3894 : : * break anything from the system's point of view, since internal
3895 : : * references are by attnum. But it doesn't seem right to allow users to
3896 : : * change names that are hardcoded into the system, hence the following
3897 : : * restriction.
3898 : : */
3899 [ + + + + ]: 688 : if (relkind != RELKIND_RELATION &&
3900 [ + - ]: 56 : relkind != RELKIND_VIEW &&
3901 [ + + ]: 56 : relkind != RELKIND_MATVIEW &&
3902 [ + - ]: 24 : relkind != RELKIND_COMPOSITE_TYPE &&
3903 [ + - ]: 24 : relkind != RELKIND_INDEX &&
3904 [ - + ]: 24 : relkind != RELKIND_PARTITIONED_INDEX &&
3905 [ # # ]: 0 : relkind != RELKIND_FOREIGN_TABLE &&
3906 : : relkind != RELKIND_PARTITIONED_TABLE)
3907 [ # # ]: 0 : ereport(ERROR,
3908 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
3909 : : errmsg("cannot rename columns of relation \"%s\"",
3910 : : NameStr(classform->relname)),
3911 : : errdetail_relkind_not_supported(relkind)));
3912 : :
3913 : : /*
3914 : : * permissions checking. only the owner of a class can change its schema.
3915 : : */
3916 [ - + ]: 688 : if (!object_ownercheck(RelationRelationId, myrelid, GetUserId()))
3917 : 0 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(myrelid)),
3918 : 0 : NameStr(classform->relname));
3919 : :
3920 : : /*
3921 : : * Conflict log tables are used internally for logical replication
3922 : : * conflict logging and should not be modified directly, as it could
3923 : : * disrupt conflict logging.
3924 : : */
3925 [ + + ]: 688 : if (IsConflictLogTableClass(classform))
3926 [ + - ]: 4 : ereport(ERROR,
3927 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
3928 : : errmsg("cannot rename columns of conflict log table \"%s\"",
3929 : : NameStr(classform->relname)),
3930 : : errdetail("Conflict log tables are system-managed tables for logical replication conflicts.")));
3931 : :
3932 [ + + + + ]: 684 : if (!allowSystemTableMods && IsSystemClass(myrelid, classform))
3933 [ + - ]: 1 : ereport(ERROR,
3934 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
3935 : : errmsg("permission denied: \"%s\" is a system catalog",
3936 : : NameStr(classform->relname))));
3937 : 683 : }
3938 : :
3939 : : /*
3940 : : * renameatt_internal - workhorse for renameatt
3941 : : *
3942 : : * Return value is the attribute number in the 'myrelid' relation.
3943 : : */
3944 : : static AttrNumber
3945 : 375 : renameatt_internal(Oid myrelid,
3946 : : const char *oldattname,
3947 : : const char *newattname,
3948 : : bool recurse,
3949 : : bool recursing,
3950 : : int expected_parents,
3951 : : DropBehavior behavior)
3952 : : {
3953 : : Relation targetrelation;
3954 : : Relation attrelation;
3955 : : HeapTuple atttup;
3956 : : Form_pg_attribute attform;
3957 : : AttrNumber attnum;
3958 : :
3959 : : /*
3960 : : * Grab an exclusive lock on the target table, which we will NOT release
3961 : : * until end of transaction.
3962 : : */
3963 : 375 : targetrelation = relation_open(myrelid, AccessExclusiveLock);
3964 : 375 : renameatt_check(myrelid, RelationGetForm(targetrelation), recursing);
3965 : :
3966 : : /*
3967 : : * if the 'recurse' flag is set then we are supposed to rename this
3968 : : * attribute in all classes that inherit from 'relname' (as well as in
3969 : : * 'relname').
3970 : : *
3971 : : * any permissions or problems with duplicate attributes will cause the
3972 : : * whole transaction to abort, which is what we want -- all or nothing.
3973 : : */
3974 [ + + ]: 375 : if (recurse)
3975 : : {
3976 : : List *child_oids,
3977 : : *child_numparents;
3978 : : ListCell *lo,
3979 : : *li;
3980 : :
3981 : : /*
3982 : : * we need the number of parents for each child so that the recursive
3983 : : * calls to renameatt() can determine whether there are any parents
3984 : : * outside the inheritance hierarchy being processed.
3985 : : */
3986 : 173 : child_oids = find_all_inheritors(myrelid, AccessExclusiveLock,
3987 : : &child_numparents);
3988 : :
3989 : : /*
3990 : : * find_all_inheritors does the recursive search of the inheritance
3991 : : * hierarchy, so all we have to do is process all of the relids in the
3992 : : * list that it returns.
3993 : : */
3994 [ + - + + : 504 : forboth(lo, child_oids, li, child_numparents)
+ - + + +
+ + - +
+ ]
3995 : : {
3996 : 351 : Oid childrelid = lfirst_oid(lo);
3997 : 351 : int numparents = lfirst_int(li);
3998 : :
3999 [ + + ]: 351 : if (childrelid == myrelid)
4000 : 173 : continue;
4001 : : /* note we need not recurse again */
4002 : 178 : renameatt_internal(childrelid, oldattname, newattname, false, true, numparents, behavior);
4003 : : }
4004 : : }
4005 : : else
4006 : : {
4007 : : /*
4008 : : * If we are told not to recurse, there had better not be any child
4009 : : * tables; else the rename would put them out of step.
4010 : : *
4011 : : * expected_parents will only be 0 if we are not already recursing.
4012 : : */
4013 [ + + + + ]: 226 : if (expected_parents == 0 &&
4014 : 24 : find_inheritance_children(myrelid, NoLock) != NIL)
4015 [ + - ]: 8 : ereport(ERROR,
4016 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
4017 : : errmsg("inherited column \"%s\" must be renamed in child tables too",
4018 : : oldattname)));
4019 : : }
4020 : :
4021 : : /* rename attributes in typed tables of composite type */
4022 [ + + ]: 347 : if (targetrelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
4023 : : {
4024 : : List *child_oids;
4025 : : ListCell *lo;
4026 : :
4027 : 16 : child_oids = find_typed_table_dependencies(targetrelation->rd_rel->reltype,
4028 : 16 : RelationGetRelationName(targetrelation),
4029 : : behavior);
4030 : :
4031 [ + + + + : 16 : foreach(lo, child_oids)
+ + ]
4032 : 4 : renameatt_internal(lfirst_oid(lo), oldattname, newattname, true, true, 0, behavior);
4033 : : }
4034 : :
4035 : 343 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
4036 : :
4037 : 343 : atttup = SearchSysCacheCopyAttName(myrelid, oldattname);
4038 [ + + ]: 343 : if (!HeapTupleIsValid(atttup))
4039 [ + - ]: 16 : ereport(ERROR,
4040 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
4041 : : errmsg("column \"%s\" does not exist",
4042 : : oldattname)));
4043 : 327 : attform = (Form_pg_attribute) GETSTRUCT(atttup);
4044 : :
4045 : 327 : attnum = attform->attnum;
4046 [ - + ]: 327 : if (attnum <= 0)
4047 [ # # ]: 0 : ereport(ERROR,
4048 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
4049 : : errmsg("cannot rename system column \"%s\"",
4050 : : oldattname)));
4051 : :
4052 : : /*
4053 : : * if the attribute is inherited, forbid the renaming. if this is a
4054 : : * top-level call to renameatt(), then expected_parents will be 0, so the
4055 : : * effect of this code will be to prohibit the renaming if the attribute
4056 : : * is inherited at all. if this is a recursive call to renameatt(),
4057 : : * expected_parents will be the number of parents the current relation has
4058 : : * within the inheritance hierarchy being processed, so we'll prohibit the
4059 : : * renaming only if there are additional parents from elsewhere.
4060 : : */
4061 [ + + ]: 327 : if (attform->attinhcount > expected_parents)
4062 [ + - ]: 20 : ereport(ERROR,
4063 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
4064 : : errmsg("cannot rename inherited column \"%s\"",
4065 : : oldattname)));
4066 : :
4067 : : /* new name should not already exist */
4068 : 307 : (void) check_for_column_name_collision(targetrelation, newattname, false);
4069 : :
4070 : : /* apply the update */
4071 : 299 : namestrcpy(&(attform->attname), newattname);
4072 : :
4073 : 299 : CatalogTupleUpdate(attrelation, &atttup->t_self, atttup);
4074 : :
4075 [ - + ]: 299 : InvokeObjectPostAlterHook(RelationRelationId, myrelid, attnum);
4076 : :
4077 : 299 : heap_freetuple(atttup);
4078 : :
4079 : 299 : table_close(attrelation, RowExclusiveLock);
4080 : :
4081 : 299 : relation_close(targetrelation, NoLock); /* close rel but keep lock */
4082 : :
4083 : 299 : return attnum;
4084 : : }
4085 : :
4086 : : /*
4087 : : * Perform permissions and integrity checks before acquiring a relation lock.
4088 : : */
4089 : : static void
4090 : 286 : RangeVarCallbackForRenameAttribute(const RangeVar *rv, Oid relid, Oid oldrelid,
4091 : : void *arg)
4092 : : {
4093 : : HeapTuple tuple;
4094 : : Form_pg_class form;
4095 : :
4096 : 286 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
4097 [ + + ]: 286 : if (!HeapTupleIsValid(tuple))
4098 : 25 : return; /* concurrently dropped */
4099 : 261 : form = (Form_pg_class) GETSTRUCT(tuple);
4100 : 261 : renameatt_check(relid, form, false);
4101 : 252 : ReleaseSysCache(tuple);
4102 : : }
4103 : :
4104 : : /*
4105 : : * renameatt - changes the name of an attribute in a relation
4106 : : *
4107 : : * The returned ObjectAddress is that of the renamed column.
4108 : : */
4109 : : ObjectAddress
4110 : 222 : renameatt(RenameStmt *stmt)
4111 : : {
4112 : : Oid relid;
4113 : : AttrNumber attnum;
4114 : : ObjectAddress address;
4115 : :
4116 : : /* lock level taken here should match renameatt_internal */
4117 : 222 : relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
4118 : 222 : stmt->missing_ok ? RVR_MISSING_OK : 0,
4119 : : RangeVarCallbackForRenameAttribute,
4120 : : NULL);
4121 : :
4122 [ + + ]: 209 : if (!OidIsValid(relid))
4123 : : {
4124 [ + - ]: 16 : ereport(NOTICE,
4125 : : (errmsg("relation \"%s\" does not exist, skipping",
4126 : : stmt->relation->relname)));
4127 : 16 : return InvalidObjectAddress;
4128 : : }
4129 : :
4130 : : attnum =
4131 : 193 : renameatt_internal(relid,
4132 : 193 : stmt->subname, /* old att name */
4133 : 193 : stmt->newname, /* new att name */
4134 : 193 : stmt->relation->inh, /* recursive? */
4135 : : false, /* recursing? */
4136 : : 0, /* expected inhcount */
4137 : : stmt->behavior);
4138 : :
4139 : 137 : ObjectAddressSubSet(address, RelationRelationId, relid, attnum);
4140 : :
4141 : 137 : return address;
4142 : : }
4143 : :
4144 : : /*
4145 : : * same logic as renameatt_internal
4146 : : */
4147 : : static ObjectAddress
4148 : 60 : rename_constraint_internal(Oid myrelid,
4149 : : Oid mytypid,
4150 : : const char *oldconname,
4151 : : const char *newconname,
4152 : : bool recurse,
4153 : : bool recursing,
4154 : : int expected_parents)
4155 : : {
4156 : 60 : Relation targetrelation = NULL;
4157 : : Oid constraintOid;
4158 : : HeapTuple tuple;
4159 : : Form_pg_constraint con;
4160 : : ObjectAddress address;
4161 : :
4162 : : Assert(!myrelid || !mytypid);
4163 : :
4164 [ + + ]: 60 : if (mytypid)
4165 : : {
4166 : 4 : constraintOid = get_domain_constraint_oid(mytypid, oldconname, false);
4167 : : }
4168 : : else
4169 : : {
4170 : 56 : targetrelation = relation_open(myrelid, AccessExclusiveLock);
4171 : :
4172 : : /*
4173 : : * don't tell it whether we're recursing; we allow changing typed
4174 : : * tables here
4175 : : */
4176 : 56 : renameatt_check(myrelid, RelationGetForm(targetrelation), false);
4177 : :
4178 : 56 : constraintOid = get_relation_constraint_oid(myrelid, oldconname, false);
4179 : : }
4180 : :
4181 : 60 : tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraintOid));
4182 [ - + ]: 60 : if (!HeapTupleIsValid(tuple))
4183 [ # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u",
4184 : : constraintOid);
4185 : 60 : con = (Form_pg_constraint) GETSTRUCT(tuple);
4186 : :
4187 [ + + ]: 60 : if (myrelid &&
4188 [ + + ]: 56 : (con->contype == CONSTRAINT_CHECK ||
4189 [ + + ]: 16 : con->contype == CONSTRAINT_NOTNULL) &&
4190 [ + + ]: 44 : !con->connoinherit)
4191 : : {
4192 [ + + ]: 36 : if (recurse)
4193 : : {
4194 : : List *child_oids,
4195 : : *child_numparents;
4196 : : ListCell *lo,
4197 : : *li;
4198 : :
4199 : 24 : child_oids = find_all_inheritors(myrelid, AccessExclusiveLock,
4200 : : &child_numparents);
4201 : :
4202 [ + - + + : 56 : forboth(lo, child_oids, li, child_numparents)
+ - + + +
+ + - +
+ ]
4203 : : {
4204 : 32 : Oid childrelid = lfirst_oid(lo);
4205 : 32 : int numparents = lfirst_int(li);
4206 : :
4207 [ + + ]: 32 : if (childrelid == myrelid)
4208 : 24 : continue;
4209 : :
4210 : 8 : rename_constraint_internal(childrelid, InvalidOid, oldconname, newconname, false, true, numparents);
4211 : : }
4212 : : }
4213 : : else
4214 : : {
4215 [ + + + - ]: 16 : if (expected_parents == 0 &&
4216 : 4 : find_inheritance_children(myrelid, NoLock) != NIL)
4217 [ + - ]: 4 : ereport(ERROR,
4218 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
4219 : : errmsg("inherited constraint \"%s\" must be renamed in child tables too",
4220 : : oldconname)));
4221 : : }
4222 : :
4223 [ + + ]: 32 : if (con->coninhcount > expected_parents)
4224 [ + - ]: 4 : ereport(ERROR,
4225 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
4226 : : errmsg("cannot rename inherited constraint \"%s\"",
4227 : : oldconname)));
4228 : : }
4229 : :
4230 [ + + ]: 52 : if (con->conindid
4231 [ + + ]: 12 : && (con->contype == CONSTRAINT_PRIMARY
4232 [ - + ]: 4 : || con->contype == CONSTRAINT_UNIQUE
4233 [ # # ]: 0 : || con->contype == CONSTRAINT_EXCLUSION))
4234 : : /* rename the index; this renames the constraint as well */
4235 : 12 : RenameRelationInternal(con->conindid, newconname, false, true);
4236 : : else
4237 : 40 : RenameConstraintById(constraintOid, newconname);
4238 : :
4239 : 52 : ObjectAddressSet(address, ConstraintRelationId, constraintOid);
4240 : :
4241 : 52 : ReleaseSysCache(tuple);
4242 : :
4243 [ + + ]: 52 : if (targetrelation)
4244 : : {
4245 : : /*
4246 : : * Invalidate relcache so as others can see the new constraint name.
4247 : : */
4248 : 48 : CacheInvalidateRelcache(targetrelation);
4249 : :
4250 : 48 : relation_close(targetrelation, NoLock); /* close rel but keep lock */
4251 : : }
4252 : :
4253 : 52 : return address;
4254 : : }
4255 : :
4256 : : ObjectAddress
4257 : 56 : RenameConstraint(RenameStmt *stmt)
4258 : : {
4259 : 56 : Oid relid = InvalidOid;
4260 : 56 : Oid typid = InvalidOid;
4261 : :
4262 [ + + ]: 56 : if (stmt->renameType == OBJECT_DOMCONSTRAINT)
4263 : : {
4264 : : Relation rel;
4265 : : HeapTuple tup;
4266 : :
4267 : 4 : typid = typenameTypeId(NULL, makeTypeNameFromNameList(castNode(List, stmt->object)));
4268 : 4 : rel = table_open(TypeRelationId, RowExclusiveLock);
4269 : 4 : tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
4270 [ - + ]: 4 : if (!HeapTupleIsValid(tup))
4271 [ # # ]: 0 : elog(ERROR, "cache lookup failed for type %u", typid);
4272 : 4 : checkDomainOwner(tup);
4273 : 4 : ReleaseSysCache(tup);
4274 : 4 : table_close(rel, NoLock);
4275 : : }
4276 : : else
4277 : : {
4278 : : /* lock level taken here should match rename_constraint_internal */
4279 : 52 : relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
4280 : 52 : stmt->missing_ok ? RVR_MISSING_OK : 0,
4281 : : RangeVarCallbackForRenameAttribute,
4282 : : NULL);
4283 [ + + ]: 52 : if (!OidIsValid(relid))
4284 : : {
4285 [ + - ]: 4 : ereport(NOTICE,
4286 : : (errmsg("relation \"%s\" does not exist, skipping",
4287 : : stmt->relation->relname)));
4288 : 4 : return InvalidObjectAddress;
4289 : : }
4290 : : }
4291 : :
4292 : : return
4293 : 52 : rename_constraint_internal(relid, typid,
4294 : 52 : stmt->subname,
4295 : 52 : stmt->newname,
4296 [ + + ]: 100 : (stmt->relation &&
4297 [ + + ]: 48 : stmt->relation->inh), /* recursive? */
4298 : : false, /* recursing? */
4299 : 52 : 0 /* expected inhcount */ );
4300 : : }
4301 : :
4302 : : /*
4303 : : * Execute ALTER TABLE/INDEX/SEQUENCE/VIEW/MATERIALIZED VIEW/FOREIGN TABLE/PROPERTY GRAPH
4304 : : * RENAME
4305 : : */
4306 : : ObjectAddress
4307 : 327 : RenameRelation(RenameStmt *stmt)
4308 : : {
4309 : 327 : bool is_index_stmt = stmt->renameType == OBJECT_INDEX;
4310 : : Oid relid;
4311 : : ObjectAddress address;
4312 : :
4313 : : /*
4314 : : * Grab an exclusive lock on the target table, index, sequence, view,
4315 : : * materialized view, or foreign table, which we will NOT release until
4316 : : * end of transaction.
4317 : : *
4318 : : * Lock level used here should match RenameRelationInternal, to avoid lock
4319 : : * escalation. However, because ALTER INDEX can be used with any relation
4320 : : * type, we mustn't believe without verification.
4321 : : */
4322 : : for (;;)
4323 : 8 : {
4324 : : LOCKMODE lockmode;
4325 : : char relkind;
4326 : : bool obj_is_index;
4327 : :
4328 [ + + ]: 335 : lockmode = is_index_stmt ? ShareUpdateExclusiveLock : AccessExclusiveLock;
4329 : :
4330 : 335 : relid = RangeVarGetRelidExtended(stmt->relation, lockmode,
4331 : 335 : stmt->missing_ok ? RVR_MISSING_OK : 0,
4332 : : RangeVarCallbackForAlterRelation,
4333 : : stmt);
4334 : :
4335 [ + + ]: 298 : if (!OidIsValid(relid))
4336 : : {
4337 [ + - ]: 12 : ereport(NOTICE,
4338 : : (errmsg("relation \"%s\" does not exist, skipping",
4339 : : stmt->relation->relname)));
4340 : 12 : return InvalidObjectAddress;
4341 : : }
4342 : :
4343 : : /*
4344 : : * We allow mismatched statement and object types (e.g., ALTER INDEX
4345 : : * to rename a table), but we might've used the wrong lock level. If
4346 : : * that happens, retry with the correct lock level. We don't bother
4347 : : * if we already acquired AccessExclusiveLock with an index, however.
4348 : : */
4349 : 286 : relkind = get_rel_relkind(relid);
4350 [ + + + + ]: 286 : obj_is_index = (relkind == RELKIND_INDEX ||
4351 : : relkind == RELKIND_PARTITIONED_INDEX);
4352 [ + + + + ]: 286 : if (obj_is_index || is_index_stmt == obj_is_index)
4353 : : break;
4354 : :
4355 : 8 : UnlockRelationOid(relid, lockmode);
4356 : 8 : is_index_stmt = obj_is_index;
4357 : : }
4358 : :
4359 : : /* Do the work */
4360 : 278 : RenameRelationInternal(relid, stmt->newname, false, is_index_stmt);
4361 : :
4362 : 266 : ObjectAddressSet(address, RelationRelationId, relid);
4363 : :
4364 : 266 : return address;
4365 : : }
4366 : :
4367 : : /*
4368 : : * RenameRelationInternal - change the name of a relation
4369 : : */
4370 : : void
4371 : 1124 : RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bool is_index)
4372 : : {
4373 : : Relation targetrelation;
4374 : : Relation relrelation; /* for RELATION relation */
4375 : : ItemPointerData otid;
4376 : : HeapTuple reltup;
4377 : : Form_pg_class relform;
4378 : : Oid namespaceId;
4379 : :
4380 : : /*
4381 : : * Grab a lock on the target relation, which we will NOT release until end
4382 : : * of transaction. We need at least a self-exclusive lock so that
4383 : : * concurrent DDL doesn't overwrite the rename if they start updating
4384 : : * while still seeing the old version. The lock also guards against
4385 : : * triggering relcache reloads in concurrent sessions, which might not
4386 : : * handle this information changing under them. For indexes, we can use a
4387 : : * reduced lock level because RelationReloadIndexInfo() handles indexes
4388 : : * specially.
4389 : : */
4390 [ + + ]: 1124 : targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
4391 : 1124 : namespaceId = RelationGetNamespace(targetrelation);
4392 : :
4393 : : /*
4394 : : * Find relation's pg_class tuple, and make sure newrelname isn't in use.
4395 : : */
4396 : 1124 : relrelation = table_open(RelationRelationId, RowExclusiveLock);
4397 : :
4398 : 1124 : reltup = SearchSysCacheLockedCopy1(RELOID, ObjectIdGetDatum(myrelid));
4399 [ - + ]: 1124 : if (!HeapTupleIsValid(reltup)) /* shouldn't happen */
4400 [ # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", myrelid);
4401 : 1124 : otid = reltup->t_self;
4402 : 1124 : relform = (Form_pg_class) GETSTRUCT(reltup);
4403 : :
4404 [ + + ]: 1124 : if (get_relname_relid(newrelname, namespaceId) != InvalidOid)
4405 [ + - ]: 12 : ereport(ERROR,
4406 : : (errcode(ERRCODE_DUPLICATE_TABLE),
4407 : : errmsg("relation \"%s\" already exists",
4408 : : newrelname)));
4409 : :
4410 : : /*
4411 : : * RenameRelation is careful not to believe the caller's idea of the
4412 : : * relation kind being handled. We don't have to worry about this, but
4413 : : * let's not be totally oblivious to it. We can process an index as
4414 : : * not-an-index, but not the other way around.
4415 : : */
4416 : : Assert(!is_index ||
4417 : : is_index == (targetrelation->rd_rel->relkind == RELKIND_INDEX ||
4418 : : targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX));
4419 : :
4420 : : /*
4421 : : * Update pg_class tuple with new relname. (Scribbling on reltup is OK
4422 : : * because it's a copy...)
4423 : : */
4424 : 1112 : namestrcpy(&(relform->relname), newrelname);
4425 : :
4426 : 1112 : CatalogTupleUpdate(relrelation, &otid, reltup);
4427 : 1112 : UnlockTuple(relrelation, &otid, InplaceUpdateTupleLock);
4428 : :
4429 [ - + ]: 1112 : InvokeObjectPostAlterHookArg(RelationRelationId, myrelid, 0,
4430 : : InvalidOid, is_internal);
4431 : :
4432 : 1112 : heap_freetuple(reltup);
4433 : 1112 : table_close(relrelation, RowExclusiveLock);
4434 : :
4435 : : /*
4436 : : * Also rename the associated type, if any.
4437 : : */
4438 [ + + ]: 1112 : if (OidIsValid(targetrelation->rd_rel->reltype))
4439 : 128 : RenameTypeInternal(targetrelation->rd_rel->reltype,
4440 : : newrelname, namespaceId);
4441 : :
4442 : : /*
4443 : : * Also rename the associated constraint, if any.
4444 : : */
4445 [ + + ]: 1112 : if (targetrelation->rd_rel->relkind == RELKIND_INDEX ||
4446 [ + + ]: 614 : targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
4447 : : {
4448 : 510 : Oid constraintId = get_index_constraint(myrelid);
4449 : :
4450 [ + + ]: 510 : if (OidIsValid(constraintId))
4451 : 24 : RenameConstraintById(constraintId, newrelname);
4452 : : }
4453 : :
4454 : : /*
4455 : : * Close rel, but keep lock!
4456 : : */
4457 : 1112 : relation_close(targetrelation, NoLock);
4458 : 1112 : }
4459 : :
4460 : : /*
4461 : : * ResetRelRewrite - reset relrewrite
4462 : : */
4463 : : void
4464 : 389 : ResetRelRewrite(Oid myrelid)
4465 : : {
4466 : : Relation relrelation; /* for RELATION relation */
4467 : : HeapTuple reltup;
4468 : : Form_pg_class relform;
4469 : :
4470 : : /*
4471 : : * Find relation's pg_class tuple.
4472 : : */
4473 : 389 : relrelation = table_open(RelationRelationId, RowExclusiveLock);
4474 : :
4475 : 389 : reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
4476 [ - + ]: 389 : if (!HeapTupleIsValid(reltup)) /* shouldn't happen */
4477 [ # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", myrelid);
4478 : 389 : relform = (Form_pg_class) GETSTRUCT(reltup);
4479 : :
4480 : : /*
4481 : : * Update pg_class tuple.
4482 : : */
4483 : 389 : relform->relrewrite = InvalidOid;
4484 : :
4485 : 389 : CatalogTupleUpdate(relrelation, &reltup->t_self, reltup);
4486 : :
4487 : 389 : heap_freetuple(reltup);
4488 : 389 : table_close(relrelation, RowExclusiveLock);
4489 : 389 : }
4490 : :
4491 : : /*
4492 : : * Disallow ALTER TABLE (and similar commands) when the current backend has
4493 : : * any open reference to the target table besides the one just acquired by
4494 : : * the calling command; this implies there's an open cursor or active plan.
4495 : : * We need this check because our lock doesn't protect us against stomping
4496 : : * on our own foot, only other people's feet!
4497 : : *
4498 : : * For ALTER TABLE, the only case known to cause serious trouble is ALTER
4499 : : * COLUMN TYPE, and some changes are obviously pretty benign, so this could
4500 : : * possibly be relaxed to only error out for certain types of alterations.
4501 : : * But the use-case for allowing any of these things is not obvious, so we
4502 : : * won't work hard at it for now.
4503 : : *
4504 : : * We also reject these commands if there are any pending AFTER trigger events
4505 : : * for the rel. This is certainly necessary for the rewriting variants of
4506 : : * ALTER TABLE, because they don't preserve tuple TIDs and so the pending
4507 : : * events would try to fetch the wrong tuples. It might be overly cautious
4508 : : * in other cases, but again it seems better to err on the side of paranoia.
4509 : : *
4510 : : * REINDEX calls this with "rel" referencing the index to be rebuilt; here
4511 : : * we are worried about active indexscans on the index. The trigger-event
4512 : : * check can be skipped, since we are doing no damage to the parent table.
4513 : : *
4514 : : * The statement name (eg, "ALTER TABLE") is passed for use in error messages.
4515 : : */
4516 : : void
4517 : 117241 : CheckTableNotInUse(Relation rel, const char *stmt)
4518 : : {
4519 : : int expected_refcnt;
4520 : :
4521 [ + + ]: 117241 : expected_refcnt = rel->rd_isnailed ? 2 : 1;
4522 [ + + ]: 117241 : if (rel->rd_refcnt != expected_refcnt)
4523 [ + - ]: 28 : ereport(ERROR,
4524 : : (errcode(ERRCODE_OBJECT_IN_USE),
4525 : : /* translator: first %s is a SQL command, eg ALTER TABLE */
4526 : : errmsg("cannot %s \"%s\" because it is being used by active queries in this session",
4527 : : stmt, RelationGetRelationName(rel))));
4528 : :
4529 [ + + ]: 117213 : if (rel->rd_rel->relkind != RELKIND_INDEX &&
4530 [ + + + + ]: 191846 : rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
4531 : 95168 : AfterTriggerPendingOnRel(RelationGetRelid(rel)))
4532 [ + - ]: 12 : ereport(ERROR,
4533 : : (errcode(ERRCODE_OBJECT_IN_USE),
4534 : : /* translator: first %s is a SQL command, eg ALTER TABLE */
4535 : : errmsg("cannot %s \"%s\" because it has pending trigger events",
4536 : : stmt, RelationGetRelationName(rel))));
4537 : 117201 : }
4538 : :
4539 : : /*
4540 : : * CheckAlterTableIsSafe
4541 : : * Verify that it's safe to allow ALTER TABLE on this relation.
4542 : : *
4543 : : * This consists of CheckTableNotInUse() plus a check that the relation
4544 : : * isn't another session's temp table. We must split out the temp-table
4545 : : * check because there are callers of CheckTableNotInUse() that don't want
4546 : : * that, notably DROP TABLE. (We must allow DROP or we couldn't clean out
4547 : : * an orphaned temp schema.) Compare truncate_check_activity().
4548 : : */
4549 : : static void
4550 : 41319 : CheckAlterTableIsSafe(Relation rel)
4551 : : {
4552 : : /*
4553 : : * Don't allow ALTER on temp tables of other backends. Their local buffer
4554 : : * manager is not going to cope if we need to change the table's contents.
4555 : : * Even if we don't, there may be optimizations that assume temp tables
4556 : : * aren't subject to such interference.
4557 : : */
4558 [ + + + + ]: 41319 : if (RELATION_IS_OTHER_TEMP(rel))
4559 [ + - ]: 2 : ereport(ERROR,
4560 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
4561 : : errmsg("cannot alter temporary tables of other sessions")));
4562 : :
4563 : : /*
4564 : : * Also check for active uses of the relation in the current transaction,
4565 : : * including open scans and pending AFTER trigger events.
4566 : : */
4567 : 41317 : CheckTableNotInUse(rel, "ALTER TABLE");
4568 : 41293 : }
4569 : :
4570 : : /*
4571 : : * AlterTableLookupRelation
4572 : : * Look up, and lock, the OID for the relation named by an alter table
4573 : : * statement.
4574 : : */
4575 : : Oid
4576 : 21989 : AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
4577 : : {
4578 : 43904 : return RangeVarGetRelidExtended(stmt->relation, lockmode,
4579 : 21989 : stmt->missing_ok ? RVR_MISSING_OK : 0,
4580 : : RangeVarCallbackForAlterRelation,
4581 : : stmt);
4582 : : }
4583 : :
4584 : : /*
4585 : : * AlterTable
4586 : : * Execute ALTER TABLE, which can be a list of subcommands
4587 : : *
4588 : : * ALTER TABLE is performed in three phases:
4589 : : * 1. Examine subcommands and perform pre-transformation checking.
4590 : : * 2. Validate and transform subcommands, and update system catalogs.
4591 : : * 3. Scan table(s) to check new constraints, and optionally recopy
4592 : : * the data into new table(s).
4593 : : * Phase 3 is not performed unless one or more of the subcommands requires
4594 : : * it. The intention of this design is to allow multiple independent
4595 : : * updates of the table schema to be performed with only one pass over the
4596 : : * data.
4597 : : *
4598 : : * ATPrepCmd performs phase 1. A "work queue" entry is created for
4599 : : * each table to be affected (there may be multiple affected tables if the
4600 : : * commands traverse a table inheritance hierarchy). Also we do preliminary
4601 : : * validation of the subcommands. Because earlier subcommands may change
4602 : : * the catalog state seen by later commands, there are limits to what can
4603 : : * be done in this phase. Generally, this phase acquires table locks,
4604 : : * checks permissions and relkind, and recurses to find child tables.
4605 : : *
4606 : : * ATRewriteCatalogs performs phase 2 for each affected table.
4607 : : * Certain subcommands need to be performed before others to avoid
4608 : : * unnecessary conflicts; for example, DROP COLUMN should come before
4609 : : * ADD COLUMN. Therefore phase 1 divides the subcommands into multiple
4610 : : * lists, one for each logical "pass" of phase 2.
4611 : : *
4612 : : * ATRewriteTables performs phase 3 for those tables that need it.
4613 : : *
4614 : : * For most subcommand types, phases 2 and 3 do no explicit recursion,
4615 : : * since phase 1 already does it. However, for certain subcommand types
4616 : : * it is only possible to determine how to recurse at phase 2 time; for
4617 : : * those cases, phase 1 sets the cmd->recurse flag.
4618 : : *
4619 : : * Thanks to the magic of MVCC, an error anywhere along the way rolls back
4620 : : * the whole operation; we don't have to do anything special to clean up.
4621 : : *
4622 : : * The caller must lock the relation, with an appropriate lock level
4623 : : * for the subcommands requested, using AlterTableGetLockLevel(stmt->cmds)
4624 : : * or higher. We pass the lock level down
4625 : : * so that we can apply it recursively to inherited tables. Note that the
4626 : : * lock level we want as we recurse might well be higher than required for
4627 : : * that specific subcommand. So we pass down the overall lock requirement,
4628 : : * rather than reassess it at lower levels.
4629 : : *
4630 : : * The caller also provides a "context" which is to be passed back to
4631 : : * utility.c when we need to execute a subcommand such as CREATE INDEX.
4632 : : * Some of the fields therein, such as the relid, are used here as well.
4633 : : */
4634 : : void
4635 : 21807 : AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
4636 : : AlterTableUtilityContext *context)
4637 : : {
4638 : : Relation rel;
4639 : :
4640 : : /* Caller is required to provide an adequate lock. */
4641 : 21807 : rel = relation_open(context->relid, NoLock);
4642 : :
4643 : 21807 : CheckAlterTableIsSafe(rel);
4644 : :
4645 : 21793 : ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
4646 : 18844 : }
4647 : :
4648 : : /*
4649 : : * AlterTableInternal
4650 : : *
4651 : : * ALTER TABLE with target specified by OID
4652 : : *
4653 : : * We do not reject if the relation is already open, because it's quite
4654 : : * likely that one or more layers of caller have it open. That means it
4655 : : * is unsafe to use this entry point for alterations that could break
4656 : : * existing query plans. On the assumption it's not used for such, we
4657 : : * don't have to reject pending AFTER triggers, either.
4658 : : *
4659 : : * Also, since we don't have an AlterTableUtilityContext, this cannot be
4660 : : * used for any subcommand types that require parse transformation or
4661 : : * could generate subcommands that have to be passed to ProcessUtility.
4662 : : */
4663 : : void
4664 : 193 : AlterTableInternal(Oid relid, List *cmds, bool recurse)
4665 : : {
4666 : : Relation rel;
4667 : 193 : LOCKMODE lockmode = AlterTableGetLockLevel(cmds);
4668 : :
4669 : 193 : rel = relation_open(relid, lockmode);
4670 : :
4671 : 193 : EventTriggerAlterTableRelid(relid);
4672 : :
4673 : 193 : ATController(NULL, rel, cmds, recurse, lockmode, NULL);
4674 : 193 : }
4675 : :
4676 : : /*
4677 : : * AlterTableGetLockLevel
4678 : : *
4679 : : * Sets the overall lock level required for the supplied list of subcommands.
4680 : : * Policy for doing this set according to needs of AlterTable(), see
4681 : : * comments there for overall explanation.
4682 : : *
4683 : : * Function is called before and after parsing, so it must give same
4684 : : * answer each time it is called. Some subcommands are transformed
4685 : : * into other subcommand types, so the transform must never be made to a
4686 : : * lower lock level than previously assigned. All transforms are noted below.
4687 : : *
4688 : : * Since this is called before we lock the table we cannot use table metadata
4689 : : * to influence the type of lock we acquire.
4690 : : *
4691 : : * There should be no lockmodes hardcoded into the subcommand functions. All
4692 : : * lockmode decisions for ALTER TABLE are made here only. The one exception is
4693 : : * ALTER TABLE RENAME which is treated as a different statement type T_RenameStmt
4694 : : * and does not travel through this section of code and cannot be combined with
4695 : : * any of the subcommands given here.
4696 : : *
4697 : : * Note that Hot Standby only knows about AccessExclusiveLocks on the primary
4698 : : * so any changes that might affect SELECTs running on standbys need to use
4699 : : * AccessExclusiveLocks even if you think a lesser lock would do, unless you
4700 : : * have a solution for that also.
4701 : : *
4702 : : * Also note that pg_dump uses only an AccessShareLock, meaning that anything
4703 : : * that takes a lock less than AccessExclusiveLock can change object definitions
4704 : : * while pg_dump is running. Be careful to check that the appropriate data is
4705 : : * derived by pg_dump using an MVCC snapshot, rather than syscache lookups,
4706 : : * otherwise we might end up with an inconsistent dump that can't restore.
4707 : : */
4708 : : LOCKMODE
4709 : 22182 : AlterTableGetLockLevel(List *cmds)
4710 : : {
4711 : : /*
4712 : : * This only works if we read catalog tables using MVCC snapshots.
4713 : : */
4714 : : ListCell *lcmd;
4715 : 22182 : LOCKMODE lockmode = ShareUpdateExclusiveLock;
4716 : :
4717 [ + - + + : 45230 : foreach(lcmd, cmds)
+ + ]
4718 : : {
4719 : 23048 : AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
4720 : 23048 : LOCKMODE cmd_lockmode = AccessExclusiveLock; /* default for compiler */
4721 : :
4722 [ + + + + : 23048 : switch (cmd->subtype)
+ + + + +
+ + + + +
+ + + + +
+ - ]
4723 : : {
4724 : : /*
4725 : : * These subcommands rewrite the heap, so require full locks.
4726 : : */
4727 : 2650 : case AT_AddColumn: /* may rewrite heap, in some cases and visible
4728 : : * to SELECT */
4729 : : case AT_SetAccessMethod: /* must rewrite heap */
4730 : : case AT_SetTableSpace: /* must rewrite heap */
4731 : : case AT_AlterColumnType: /* must rewrite heap */
4732 : 2650 : cmd_lockmode = AccessExclusiveLock;
4733 : 2650 : break;
4734 : :
4735 : : /*
4736 : : * These subcommands may require addition of toast tables. If
4737 : : * we add a toast table to a table currently being scanned, we
4738 : : * might miss data added to the new toast table by concurrent
4739 : : * insert transactions.
4740 : : */
4741 : 168 : case AT_SetStorage: /* may add toast tables, see
4742 : : * ATRewriteCatalogs() */
4743 : 168 : cmd_lockmode = AccessExclusiveLock;
4744 : 168 : break;
4745 : :
4746 : : /*
4747 : : * Removing constraints can affect SELECTs that have been
4748 : : * optimized assuming the constraint holds true. See also
4749 : : * CloneFkReferenced.
4750 : : */
4751 : 788 : case AT_DropConstraint: /* as DROP INDEX */
4752 : : case AT_DropNotNull: /* may change some SQL plans */
4753 : 788 : cmd_lockmode = AccessExclusiveLock;
4754 : 788 : break;
4755 : :
4756 : : /*
4757 : : * Subcommands that may be visible to concurrent SELECTs
4758 : : */
4759 : 1221 : case AT_DropColumn: /* change visible to SELECT */
4760 : : case AT_AddColumnToView: /* CREATE VIEW */
4761 : : case AT_DropOids: /* used to equiv to DropColumn */
4762 : : case AT_EnableAlwaysRule: /* may change SELECT rules */
4763 : : case AT_EnableReplicaRule: /* may change SELECT rules */
4764 : : case AT_EnableRule: /* may change SELECT rules */
4765 : : case AT_DisableRule: /* may change SELECT rules */
4766 : 1221 : cmd_lockmode = AccessExclusiveLock;
4767 : 1221 : break;
4768 : :
4769 : : /*
4770 : : * Changing owner may remove implicit SELECT privileges
4771 : : */
4772 : 1169 : case AT_ChangeOwner: /* change visible to SELECT */
4773 : 1169 : cmd_lockmode = AccessExclusiveLock;
4774 : 1169 : break;
4775 : :
4776 : : /*
4777 : : * Changing foreign table options may affect optimization.
4778 : : */
4779 : 143 : case AT_GenericOptions:
4780 : : case AT_AlterColumnGenericOptions:
4781 : 143 : cmd_lockmode = AccessExclusiveLock;
4782 : 143 : break;
4783 : :
4784 : : /*
4785 : : * These subcommands affect write operations only.
4786 : : */
4787 : 191 : case AT_EnableTrig:
4788 : : case AT_EnableAlwaysTrig:
4789 : : case AT_EnableReplicaTrig:
4790 : : case AT_EnableTrigAll:
4791 : : case AT_EnableTrigUser:
4792 : : case AT_DisableTrig:
4793 : : case AT_DisableTrigAll:
4794 : : case AT_DisableTrigUser:
4795 : 191 : cmd_lockmode = ShareRowExclusiveLock;
4796 : 191 : break;
4797 : :
4798 : : /*
4799 : : * These subcommands affect write operations only. XXX
4800 : : * Theoretically, these could be ShareRowExclusiveLock.
4801 : : */
4802 : 2164 : case AT_ColumnDefault:
4803 : : case AT_CookedColumnDefault:
4804 : : case AT_AlterConstraint:
4805 : : case AT_AddIndex: /* from ADD CONSTRAINT */
4806 : : case AT_AddIndexConstraint:
4807 : : case AT_ReplicaIdentity:
4808 : : case AT_SetNotNull:
4809 : : case AT_EnableRowSecurity:
4810 : : case AT_DisableRowSecurity:
4811 : : case AT_ForceRowSecurity:
4812 : : case AT_NoForceRowSecurity:
4813 : : case AT_AddIdentity:
4814 : : case AT_DropIdentity:
4815 : : case AT_SetIdentity:
4816 : : case AT_SetExpression:
4817 : : case AT_DropExpression:
4818 : : case AT_SetCompression:
4819 : 2164 : cmd_lockmode = AccessExclusiveLock;
4820 : 2164 : break;
4821 : :
4822 : 10141 : case AT_AddConstraint:
4823 : : case AT_ReAddConstraint: /* becomes AT_AddConstraint */
4824 : : case AT_ReAddDomainConstraint: /* becomes AT_AddConstraint */
4825 [ + - ]: 10141 : if (IsA(cmd->def, Constraint))
4826 : : {
4827 : 10141 : Constraint *con = (Constraint *) cmd->def;
4828 : :
4829 [ + + + ]: 10141 : switch (con->contype)
4830 : : {
4831 : 7482 : case CONSTR_EXCLUSION:
4832 : : case CONSTR_PRIMARY:
4833 : : case CONSTR_UNIQUE:
4834 : :
4835 : : /*
4836 : : * Cases essentially the same as CREATE INDEX. We
4837 : : * could reduce the lock strength to ShareLock if
4838 : : * we can work out how to allow concurrent catalog
4839 : : * updates. XXX Might be set down to
4840 : : * ShareRowExclusiveLock but requires further
4841 : : * analysis.
4842 : : */
4843 : 7482 : cmd_lockmode = AccessExclusiveLock;
4844 : 7482 : break;
4845 : 1820 : case CONSTR_FOREIGN:
4846 : :
4847 : : /*
4848 : : * We add triggers to both tables when we add a
4849 : : * Foreign Key, so the lock level must be at least
4850 : : * as strong as CREATE TRIGGER.
4851 : : */
4852 : 1820 : cmd_lockmode = ShareRowExclusiveLock;
4853 : 1820 : break;
4854 : :
4855 : 839 : default:
4856 : 839 : cmd_lockmode = AccessExclusiveLock;
4857 : : }
4858 : : }
4859 : 10141 : break;
4860 : :
4861 : : /*
4862 : : * These subcommands affect inheritance behaviour. Queries
4863 : : * started before us will continue to see the old inheritance
4864 : : * behaviour, while queries started after we commit will see
4865 : : * new behaviour. No need to prevent reads or writes to the
4866 : : * subtable while we hook it up though. Changing the TupDesc
4867 : : * may be a problem, so keep highest lock.
4868 : : */
4869 : 394 : case AT_AddInherit:
4870 : : case AT_DropInherit:
4871 : 394 : cmd_lockmode = AccessExclusiveLock;
4872 : 394 : break;
4873 : :
4874 : : /*
4875 : : * These subcommands affect implicit row type conversion. They
4876 : : * have affects similar to CREATE/DROP CAST on queries. don't
4877 : : * provide for invalidating parse trees as a result of such
4878 : : * changes, so we keep these at AccessExclusiveLock.
4879 : : */
4880 : 54 : case AT_AddOf:
4881 : : case AT_DropOf:
4882 : 54 : cmd_lockmode = AccessExclusiveLock;
4883 : 54 : break;
4884 : :
4885 : : /*
4886 : : * Only used by CREATE OR REPLACE VIEW which must conflict
4887 : : * with an SELECTs currently using the view.
4888 : : */
4889 : 137 : case AT_ReplaceRelOptions:
4890 : 137 : cmd_lockmode = AccessExclusiveLock;
4891 : 137 : break;
4892 : :
4893 : : /*
4894 : : * These subcommands affect general strategies for performance
4895 : : * and maintenance, though don't change the semantic results
4896 : : * from normal data reads and writes. Delaying an ALTER TABLE
4897 : : * behind currently active writes only delays the point where
4898 : : * the new strategy begins to take effect, so there is no
4899 : : * benefit in waiting. In this case the minimum restriction
4900 : : * applies: we don't currently allow concurrent catalog
4901 : : * updates.
4902 : : */
4903 : 158 : case AT_SetStatistics: /* Uses MVCC in getTableAttrs() */
4904 : : case AT_ClusterOn: /* Uses MVCC in getIndexes() */
4905 : : case AT_DropCluster: /* Uses MVCC in getIndexes() */
4906 : : case AT_SetOptions: /* Uses MVCC in getTableAttrs() */
4907 : : case AT_ResetOptions: /* Uses MVCC in getTableAttrs() */
4908 : 158 : cmd_lockmode = ShareUpdateExclusiveLock;
4909 : 158 : break;
4910 : :
4911 : 75 : case AT_SetLogged:
4912 : : case AT_SetUnLogged:
4913 : 75 : cmd_lockmode = AccessExclusiveLock;
4914 : 75 : break;
4915 : :
4916 : 279 : case AT_ValidateConstraint: /* Uses MVCC in getConstraints() */
4917 : 279 : cmd_lockmode = ShareUpdateExclusiveLock;
4918 : 279 : break;
4919 : :
4920 : : /*
4921 : : * Rel options are more complex than first appears. Options
4922 : : * are set here for tables, views and indexes; for historical
4923 : : * reasons these can all be used with ALTER TABLE, so we can't
4924 : : * decide between them using the basic grammar.
4925 : : */
4926 : 498 : case AT_SetRelOptions: /* Uses MVCC in getIndexes() and
4927 : : * getTables() */
4928 : : case AT_ResetRelOptions: /* Uses MVCC in getIndexes() and
4929 : : * getTables() */
4930 : 498 : cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
4931 : 498 : break;
4932 : :
4933 : 1925 : case AT_AttachPartition:
4934 : 1925 : cmd_lockmode = ShareUpdateExclusiveLock;
4935 : 1925 : break;
4936 : :
4937 : 385 : case AT_DetachPartition:
4938 [ + + ]: 385 : if (((PartitionCmd *) cmd->def)->concurrent)
4939 : 87 : cmd_lockmode = ShareUpdateExclusiveLock;
4940 : : else
4941 : 298 : cmd_lockmode = AccessExclusiveLock;
4942 : 385 : break;
4943 : :
4944 : 11 : case AT_DetachPartitionFinalize:
4945 : 11 : cmd_lockmode = ShareUpdateExclusiveLock;
4946 : 11 : break;
4947 : :
4948 : 497 : case AT_MergePartitions:
4949 : : case AT_SplitPartition:
4950 : 497 : cmd_lockmode = AccessExclusiveLock;
4951 : 497 : break;
4952 : :
4953 : 0 : default: /* oops */
4954 [ # # ]: 0 : elog(ERROR, "unrecognized alter table type: %d",
4955 : : (int) cmd->subtype);
4956 : : break;
4957 : : }
4958 : :
4959 : : /*
4960 : : * Take the greatest lockmode from any subcommand
4961 : : */
4962 [ + + ]: 23048 : if (cmd_lockmode > lockmode)
4963 : 19342 : lockmode = cmd_lockmode;
4964 : : }
4965 : :
4966 : 22182 : return lockmode;
4967 : : }
4968 : :
4969 : : /*
4970 : : * ATController provides top level control over the phases.
4971 : : *
4972 : : * parsetree is passed in to allow it to be passed to event triggers
4973 : : * when requested.
4974 : : */
4975 : : static void
4976 : 21986 : ATController(AlterTableStmt *parsetree,
4977 : : Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
4978 : : AlterTableUtilityContext *context)
4979 : : {
4980 : 21986 : List *wqueue = NIL;
4981 : : ListCell *lcmd;
4982 : :
4983 : : /* Phase 1: preliminary examination of commands, create work queue */
4984 [ + - + + : 44529 : foreach(lcmd, cmds)
+ + ]
4985 : : {
4986 : 22848 : AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
4987 : :
4988 : 22848 : ATPrepCmd(&wqueue, rel, cmd, recurse, false, lockmode, context);
4989 : : }
4990 : :
4991 : : /* Close the relation, but keep lock until commit */
4992 : 21681 : relation_close(rel, NoLock);
4993 : :
4994 : : /* Phase 2: update system catalogs */
4995 : 21681 : ATRewriteCatalogs(&wqueue, lockmode, context);
4996 : :
4997 : : /* Phase 3: scan/rewrite tables as needed, and run afterStmts */
4998 : 19438 : ATRewriteTables(parsetree, &wqueue, lockmode, context);
4999 : 19037 : }
5000 : :
5001 : : /*
5002 : : * ATPrepCmd
5003 : : *
5004 : : * Traffic cop for ALTER TABLE Phase 1 operations, including simple
5005 : : * recursion and permission checks.
5006 : : *
5007 : : * Caller must have acquired appropriate lock type on relation already.
5008 : : * This lock should be held until commit.
5009 : : */
5010 : : static void
5011 : 23495 : ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
5012 : : bool recurse, bool recursing, LOCKMODE lockmode,
5013 : : AlterTableUtilityContext *context)
5014 : : {
5015 : : AlteredTableInfo *tab;
5016 : 23495 : AlterTablePass pass = AT_PASS_UNSET;
5017 : :
5018 : : /* Find or create work queue entry for this table */
5019 : 23495 : tab = ATGetQueueEntry(wqueue, rel);
5020 : :
5021 : : /*
5022 : : * Disallow any ALTER TABLE other than ALTER TABLE DETACH FINALIZE on
5023 : : * partitions that are pending detach.
5024 : : */
5025 [ + + ]: 23495 : if (rel->rd_rel->relispartition &&
5026 [ + - + + ]: 1884 : cmd->subtype != AT_DetachPartitionFinalize &&
5027 : 942 : PartitionHasPendingDetach(RelationGetRelid(rel)))
5028 [ + - ]: 1 : ereport(ERROR,
5029 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
5030 : : errmsg("cannot alter partition \"%s\" with an incomplete detach",
5031 : : RelationGetRelationName(rel)),
5032 : : errhint("Use ALTER TABLE ... DETACH PARTITION ... FINALIZE to complete the pending detach operation."));
5033 : :
5034 : : /*
5035 : : * Copy the original subcommand for each table, so we can scribble on it.
5036 : : * This avoids conflicts when different child tables need to make
5037 : : * different parse transformations (for example, the same column may have
5038 : : * different column numbers in different children).
5039 : : */
5040 : 23494 : cmd = copyObject(cmd);
5041 : :
5042 : : /*
5043 : : * Do permissions and relkind checking, recursion to child tables if
5044 : : * needed, and any additional phase-1 processing needed. (But beware of
5045 : : * adding any processing that looks at table details that another
5046 : : * subcommand could change. In some cases we reject multiple subcommands
5047 : : * that could try to change the same state in contrary ways.)
5048 : : */
5049 [ + + + + : 23494 : switch (cmd->subtype)
+ + + + +
+ + + + +
+ + - + -
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + - ]
5050 : : {
5051 : 1597 : case AT_AddColumn: /* ADD COLUMN */
5052 : 1597 : ATSimplePermissions(cmd->subtype, rel,
5053 : : ATT_TABLE | ATT_PARTITIONED_TABLE |
5054 : : ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
5055 : 1597 : ATPrepAddColumn(wqueue, rel, recurse, recursing, false, cmd,
5056 : : lockmode, context);
5057 : : /* Recursion occurs during execution phase */
5058 : 1589 : pass = AT_PASS_ADD_COL;
5059 : 1589 : break;
5060 : 21 : case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */
5061 : 21 : ATSimplePermissions(cmd->subtype, rel, ATT_VIEW);
5062 : 21 : ATPrepAddColumn(wqueue, rel, recurse, recursing, true, cmd,
5063 : : lockmode, context);
5064 : : /* Recursion occurs during execution phase */
5065 : 21 : pass = AT_PASS_ADD_COL;
5066 : 21 : break;
5067 : 411 : case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */
5068 : :
5069 : : /*
5070 : : * We allow defaults on views so that INSERT into a view can have
5071 : : * default-ish behavior. This works because the rewriter
5072 : : * substitutes default values into INSERTs before it expands
5073 : : * rules.
5074 : : */
5075 : 411 : ATSimplePermissions(cmd->subtype, rel,
5076 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
5077 : : ATT_FOREIGN_TABLE);
5078 : 411 : ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
5079 : : /* No command-specific prep needed */
5080 [ + + ]: 411 : pass = cmd->def ? AT_PASS_ADD_OTHERCONSTR : AT_PASS_DROP;
5081 : 411 : break;
5082 : 57 : case AT_CookedColumnDefault: /* add a pre-cooked default */
5083 : : /* This is currently used only in CREATE TABLE */
5084 : : /* (so the permission check really isn't necessary) */
5085 : 57 : ATSimplePermissions(cmd->subtype, rel,
5086 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5087 : : /* This command never recurses */
5088 : 57 : pass = AT_PASS_ADD_OTHERCONSTR;
5089 : 57 : break;
5090 : 107 : case AT_AddIdentity:
5091 : 107 : ATSimplePermissions(cmd->subtype, rel,
5092 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
5093 : : ATT_FOREIGN_TABLE);
5094 : : /* Set up recursion for phase 2; no other prep needed */
5095 [ + + ]: 107 : if (recurse)
5096 : 103 : cmd->recurse = true;
5097 : 107 : pass = AT_PASS_ADD_OTHERCONSTR;
5098 : 107 : break;
5099 : 41 : case AT_SetIdentity:
5100 : 41 : ATSimplePermissions(cmd->subtype, rel,
5101 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
5102 : : ATT_FOREIGN_TABLE);
5103 : : /* Set up recursion for phase 2; no other prep needed */
5104 [ + + ]: 41 : if (recurse)
5105 : 37 : cmd->recurse = true;
5106 : : /* This should run after AddIdentity, so do it in MISC pass */
5107 : 41 : pass = AT_PASS_MISC;
5108 : 41 : break;
5109 : 37 : case AT_DropIdentity:
5110 : 37 : ATSimplePermissions(cmd->subtype, rel,
5111 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
5112 : : ATT_FOREIGN_TABLE);
5113 : : /* Set up recursion for phase 2; no other prep needed */
5114 [ + + ]: 37 : if (recurse)
5115 : 33 : cmd->recurse = true;
5116 : 37 : pass = AT_PASS_DROP;
5117 : 37 : break;
5118 : 181 : case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
5119 : 181 : ATSimplePermissions(cmd->subtype, rel,
5120 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5121 : : /* Set up recursion for phase 2; no other prep needed */
5122 [ + + ]: 177 : if (recurse)
5123 : 165 : cmd->recurse = true;
5124 : 177 : pass = AT_PASS_DROP;
5125 : 177 : break;
5126 : 276 : case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
5127 : 276 : ATSimplePermissions(cmd->subtype, rel,
5128 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5129 : : /* Set up recursion for phase 2; no other prep needed */
5130 [ + + ]: 272 : if (recurse)
5131 : 252 : cmd->recurse = true;
5132 : 272 : pass = AT_PASS_COL_ATTRS;
5133 : 272 : break;
5134 : 193 : case AT_SetExpression: /* ALTER COLUMN SET EXPRESSION */
5135 : 193 : ATSimplePermissions(cmd->subtype, rel,
5136 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5137 : 193 : ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
5138 : 193 : pass = AT_PASS_SET_EXPRESSION;
5139 : 193 : break;
5140 : 97 : case AT_DropExpression: /* ALTER COLUMN DROP EXPRESSION */
5141 : 97 : ATSimplePermissions(cmd->subtype, rel,
5142 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5143 : 97 : ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
5144 : 97 : ATPrepDropExpression(rel, cmd, recurse, recursing, lockmode);
5145 : 73 : pass = AT_PASS_DROP;
5146 : 73 : break;
5147 : 111 : case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */
5148 : 111 : ATSimplePermissions(cmd->subtype, rel,
5149 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_MATVIEW |
5150 : : ATT_INDEX | ATT_PARTITIONED_INDEX | ATT_FOREIGN_TABLE);
5151 : 111 : ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
5152 : : /* No command-specific prep needed */
5153 : 111 : pass = AT_PASS_MISC;
5154 : 111 : break;
5155 : 29 : case AT_SetOptions: /* ALTER COLUMN SET ( options ) */
5156 : : case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */
5157 : 29 : ATSimplePermissions(cmd->subtype, rel,
5158 : : ATT_TABLE | ATT_PARTITIONED_TABLE |
5159 : : ATT_MATVIEW | ATT_FOREIGN_TABLE);
5160 : : /* This command never recurses */
5161 : 21 : pass = AT_PASS_MISC;
5162 : 21 : break;
5163 : 182 : case AT_SetStorage: /* ALTER COLUMN SET STORAGE */
5164 : 182 : ATSimplePermissions(cmd->subtype, rel,
5165 : : ATT_TABLE | ATT_PARTITIONED_TABLE |
5166 : : ATT_MATVIEW | ATT_FOREIGN_TABLE);
5167 : 182 : ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
5168 : : /* No command-specific prep needed */
5169 : 182 : pass = AT_PASS_MISC;
5170 : 182 : break;
5171 : 48 : case AT_SetCompression: /* ALTER COLUMN SET COMPRESSION */
5172 : 48 : ATSimplePermissions(cmd->subtype, rel,
5173 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_MATVIEW);
5174 : : /* This command never recurses */
5175 : : /* No command-specific prep needed */
5176 : 48 : pass = AT_PASS_MISC;
5177 : 48 : break;
5178 : 1147 : case AT_DropColumn: /* DROP COLUMN */
5179 : 1147 : ATSimplePermissions(cmd->subtype, rel,
5180 : : ATT_TABLE | ATT_PARTITIONED_TABLE |
5181 : : ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
5182 : 1143 : ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd,
5183 : : lockmode, context);
5184 : : /* Recursion occurs during execution phase */
5185 : 1135 : pass = AT_PASS_DROP;
5186 : 1135 : break;
5187 : 0 : case AT_AddIndex: /* ADD INDEX */
5188 : 0 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_PARTITIONED_TABLE);
5189 : : /* This command never recurses */
5190 : : /* No command-specific prep needed */
5191 : 0 : pass = AT_PASS_ADD_INDEX;
5192 : 0 : break;
5193 : 10446 : case AT_AddConstraint: /* ADD CONSTRAINT */
5194 : 10446 : ATSimplePermissions(cmd->subtype, rel,
5195 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5196 : 10446 : ATPrepAddPrimaryKey(wqueue, rel, cmd, recurse, lockmode, context);
5197 [ + + ]: 10426 : if (recurse)
5198 : : {
5199 : : /* recurses at exec time; lock descendants and set flag */
5200 : 10178 : (void) find_all_inheritors(RelationGetRelid(rel), lockmode, NULL);
5201 : 10178 : cmd->recurse = true;
5202 : : }
5203 : 10426 : pass = AT_PASS_ADD_CONSTR;
5204 : 10426 : break;
5205 : 0 : case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */
5206 : 0 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_PARTITIONED_TABLE);
5207 : : /* This command never recurses */
5208 : : /* No command-specific prep needed */
5209 : 0 : pass = AT_PASS_ADD_INDEXCONSTR;
5210 : 0 : break;
5211 : 582 : case AT_DropConstraint: /* DROP CONSTRAINT */
5212 : 582 : ATSimplePermissions(cmd->subtype, rel,
5213 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5214 : 582 : ATCheckPartitionsNotInUse(rel, lockmode);
5215 : : /* Other recursion occurs during execution phase */
5216 : : /* No command-specific prep needed except saving recurse flag */
5217 [ + + ]: 578 : if (recurse)
5218 : 554 : cmd->recurse = true;
5219 : 578 : pass = AT_PASS_DROP;
5220 : 578 : break;
5221 : 963 : case AT_AlterColumnType: /* ALTER COLUMN TYPE */
5222 : 963 : ATSimplePermissions(cmd->subtype, rel,
5223 : : ATT_TABLE | ATT_PARTITIONED_TABLE |
5224 : : ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
5225 : : /* See comments for ATPrepAlterColumnType */
5226 : 963 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, recurse, lockmode,
5227 : : AT_PASS_UNSET, context);
5228 : : Assert(cmd != NULL);
5229 : : /* Performs own recursion */
5230 : 959 : ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd,
5231 : : lockmode, context);
5232 : 828 : pass = AT_PASS_ALTER_TYPE;
5233 : 828 : break;
5234 : 94 : case AT_AlterColumnGenericOptions:
5235 : 94 : ATSimplePermissions(cmd->subtype, rel, ATT_FOREIGN_TABLE);
5236 : : /* This command never recurses */
5237 : : /* No command-specific prep needed */
5238 : 94 : pass = AT_PASS_MISC;
5239 : 94 : break;
5240 : 1149 : case AT_ChangeOwner: /* ALTER OWNER */
5241 : : /* This command never recurses */
5242 : : /* No command-specific prep needed */
5243 : 1149 : pass = AT_PASS_MISC;
5244 : 1149 : break;
5245 : 43 : case AT_ClusterOn: /* CLUSTER ON */
5246 : : case AT_DropCluster: /* SET WITHOUT CLUSTER */
5247 : 43 : ATSimplePermissions(cmd->subtype, rel,
5248 : : ATT_TABLE | ATT_MATVIEW);
5249 : : /* These commands never recurse */
5250 : : /* No command-specific prep needed */
5251 : 35 : pass = AT_PASS_MISC;
5252 : 35 : break;
5253 : 75 : case AT_SetLogged: /* SET LOGGED */
5254 : : case AT_SetUnLogged: /* SET UNLOGGED */
5255 : 75 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_SEQUENCE);
5256 [ - + ]: 67 : if (tab->chgPersistence)
5257 [ # # ]: 0 : ereport(ERROR,
5258 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5259 : : errmsg("cannot change persistence setting twice")));
5260 : 67 : ATPrepChangePersistence(tab, rel, cmd->subtype == AT_SetLogged);
5261 : 59 : pass = AT_PASS_MISC;
5262 : 59 : break;
5263 : 4 : case AT_DropOids: /* SET WITHOUT OIDS */
5264 : 4 : ATSimplePermissions(cmd->subtype, rel,
5265 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5266 : 4 : pass = AT_PASS_DROP;
5267 : 4 : break;
5268 : 85 : case AT_SetAccessMethod: /* SET ACCESS METHOD */
5269 : 85 : ATSimplePermissions(cmd->subtype, rel,
5270 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_MATVIEW);
5271 : :
5272 : : /* check if another access method change was already requested */
5273 [ + + ]: 85 : if (tab->chgAccessMethod)
5274 [ + - ]: 12 : ereport(ERROR,
5275 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5276 : : errmsg("cannot have multiple SET ACCESS METHOD subcommands")));
5277 : :
5278 : 73 : ATPrepSetAccessMethod(tab, rel, cmd->name);
5279 : 73 : pass = AT_PASS_MISC; /* does not matter; no work in Phase 2 */
5280 : 73 : break;
5281 : 105 : case AT_SetTableSpace: /* SET TABLESPACE */
5282 : 105 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_PARTITIONED_TABLE |
5283 : : ATT_MATVIEW | ATT_INDEX | ATT_PARTITIONED_INDEX);
5284 : : /* This command never recurses */
5285 : 105 : ATPrepSetTableSpace(tab, rel, cmd->name, lockmode);
5286 : 105 : pass = AT_PASS_MISC; /* doesn't actually matter */
5287 : 105 : break;
5288 : 633 : case AT_SetRelOptions: /* SET (...) */
5289 : : case AT_ResetRelOptions: /* RESET (...) */
5290 : : case AT_ReplaceRelOptions: /* reset them all, then set just these */
5291 : 633 : ATSimplePermissions(cmd->subtype, rel,
5292 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
5293 : : ATT_MATVIEW | ATT_INDEX);
5294 : : /* This command never recurses */
5295 : : /* No command-specific prep needed */
5296 : 632 : pass = AT_PASS_MISC;
5297 : 632 : break;
5298 : 313 : case AT_AddInherit: /* INHERIT */
5299 : 313 : ATSimplePermissions(cmd->subtype, rel,
5300 : : ATT_TABLE | ATT_FOREIGN_TABLE);
5301 : : /* This command never recurses */
5302 : 309 : ATPrepChangeInherit(rel);
5303 : 297 : pass = AT_PASS_MISC;
5304 : 297 : break;
5305 : 81 : case AT_DropInherit: /* NO INHERIT */
5306 : 81 : ATSimplePermissions(cmd->subtype, rel,
5307 : : ATT_TABLE | ATT_FOREIGN_TABLE);
5308 : : /* This command never recurses */
5309 : 77 : ATPrepChangeInherit(rel);
5310 : 69 : pass = AT_PASS_MISC;
5311 : 69 : break;
5312 : 340 : case AT_AlterConstraint: /* ALTER CONSTRAINT */
5313 : 340 : ATSimplePermissions(cmd->subtype, rel,
5314 : : ATT_TABLE | ATT_PARTITIONED_TABLE);
5315 : : /* Recursion occurs during execution phase */
5316 [ + + ]: 336 : if (recurse)
5317 : 324 : cmd->recurse = true;
5318 : 336 : pass = AT_PASS_MISC;
5319 : 336 : break;
5320 : 279 : case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
5321 : 279 : ATSimplePermissions(cmd->subtype, rel,
5322 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5323 : : /* Recursion occurs during execution phase */
5324 : : /* No command-specific prep needed except saving recurse flag */
5325 [ + - ]: 279 : if (recurse)
5326 : 279 : cmd->recurse = true;
5327 : 279 : pass = AT_PASS_MISC;
5328 : 279 : break;
5329 : 314 : case AT_ReplicaIdentity: /* REPLICA IDENTITY ... */
5330 : 314 : ATSimplePermissions(cmd->subtype, rel,
5331 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_MATVIEW);
5332 : 314 : pass = AT_PASS_MISC;
5333 : : /* This command never recurses */
5334 : : /* No command-specific prep needed */
5335 : 314 : break;
5336 : 191 : case AT_EnableTrig: /* ENABLE TRIGGER variants */
5337 : : case AT_EnableAlwaysTrig:
5338 : : case AT_EnableReplicaTrig:
5339 : : case AT_EnableTrigAll:
5340 : : case AT_EnableTrigUser:
5341 : : case AT_DisableTrig: /* DISABLE TRIGGER variants */
5342 : : case AT_DisableTrigAll:
5343 : : case AT_DisableTrigUser:
5344 : 191 : ATSimplePermissions(cmd->subtype, rel,
5345 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5346 : : /* Set up recursion for phase 2; no other prep needed */
5347 [ + + ]: 191 : if (recurse)
5348 : 174 : cmd->recurse = true;
5349 : 191 : pass = AT_PASS_MISC;
5350 : 191 : break;
5351 : 427 : case AT_EnableRule: /* ENABLE/DISABLE RULE variants */
5352 : : case AT_EnableAlwaysRule:
5353 : : case AT_EnableReplicaRule:
5354 : : case AT_DisableRule:
5355 : : case AT_AddOf: /* OF */
5356 : : case AT_DropOf: /* NOT OF */
5357 : : case AT_EnableRowSecurity:
5358 : : case AT_DisableRowSecurity:
5359 : : case AT_ForceRowSecurity:
5360 : : case AT_NoForceRowSecurity:
5361 : 427 : ATSimplePermissions(cmd->subtype, rel,
5362 : : ATT_TABLE | ATT_PARTITIONED_TABLE);
5363 : : /* These commands never recurse */
5364 : : /* No command-specific prep needed */
5365 : 427 : pass = AT_PASS_MISC;
5366 : 427 : break;
5367 : 33 : case AT_GenericOptions:
5368 : 33 : ATSimplePermissions(cmd->subtype, rel, ATT_FOREIGN_TABLE);
5369 : : /* No command-specific prep needed */
5370 : 33 : pass = AT_PASS_MISC;
5371 : 33 : break;
5372 : 1917 : case AT_AttachPartition:
5373 : 1917 : ATSimplePermissions(cmd->subtype, rel,
5374 : : ATT_PARTITIONED_TABLE | ATT_PARTITIONED_INDEX);
5375 : : /* No command-specific prep needed */
5376 : 1913 : pass = AT_PASS_MISC;
5377 : 1913 : break;
5378 : 385 : case AT_DetachPartition:
5379 : 385 : ATSimplePermissions(cmd->subtype, rel, ATT_PARTITIONED_TABLE);
5380 : : /* No command-specific prep needed */
5381 : 373 : pass = AT_PASS_MISC;
5382 : 373 : break;
5383 : 11 : case AT_DetachPartitionFinalize:
5384 : 11 : ATSimplePermissions(cmd->subtype, rel, ATT_PARTITIONED_TABLE);
5385 : : /* No command-specific prep needed */
5386 : 7 : pass = AT_PASS_MISC;
5387 : 7 : break;
5388 : 489 : case AT_MergePartitions:
5389 : : case AT_SplitPartition:
5390 : 489 : ATSimplePermissions(cmd->subtype, rel, ATT_PARTITIONED_TABLE);
5391 : : /* No command-specific prep needed */
5392 : 485 : pass = AT_PASS_MISC;
5393 : 485 : break;
5394 : 0 : default: /* oops */
5395 [ # # ]: 0 : elog(ERROR, "unrecognized alter table type: %d",
5396 : : (int) cmd->subtype);
5397 : : pass = AT_PASS_UNSET; /* keep compiler quiet */
5398 : : break;
5399 : : }
5400 : : Assert(pass > AT_PASS_UNSET);
5401 : :
5402 : : /* Add the subcommand to the appropriate list for phase 2 */
5403 : 23182 : tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd);
5404 : 23182 : }
5405 : :
5406 : : /*
5407 : : * ATRewriteCatalogs
5408 : : *
5409 : : * Traffic cop for ALTER TABLE Phase 2 operations. Subcommands are
5410 : : * dispatched in a "safe" execution order (designed to avoid unnecessary
5411 : : * conflicts).
5412 : : */
5413 : : static void
5414 : 21681 : ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
5415 : : AlterTableUtilityContext *context)
5416 : : {
5417 : : ListCell *ltab;
5418 : :
5419 : : /*
5420 : : * We process all the tables "in parallel", one pass at a time. This is
5421 : : * needed because we may have to propagate work from one table to another
5422 : : * (specifically, ALTER TYPE on a foreign key's PK has to dispatch the
5423 : : * re-adding of the foreign key constraint to the other table). Work can
5424 : : * only be propagated into later passes, however.
5425 : : */
5426 [ + + ]: 272355 : for (AlterTablePass pass = 0; pass < AT_NUM_PASSES; pass++)
5427 : : {
5428 : : /* Go through each table that needs to be processed */
5429 [ + - + + : 515251 : foreach(ltab, *wqueue)
+ + ]
5430 : : {
5431 : 264577 : AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
5432 : 264577 : List *subcmds = tab->subcmds[pass];
5433 : : ListCell *lcmd;
5434 : :
5435 [ + + ]: 264577 : if (subcmds == NIL)
5436 : 227597 : continue;
5437 : :
5438 : : /*
5439 : : * Open the relation and store it in tab. This allows subroutines
5440 : : * close and reopen, if necessary. Appropriate lock was obtained
5441 : : * by phase 1, needn't get it again.
5442 : : */
5443 : 36980 : tab->rel = relation_open(tab->relid, NoLock);
5444 : :
5445 [ + - + + : 74135 : foreach(lcmd, subcmds)
+ + ]
5446 : 39398 : ATExecCmd(wqueue, tab,
5447 : 39398 : lfirst_node(AlterTableCmd, lcmd),
5448 : : lockmode, pass, context);
5449 : :
5450 : : /*
5451 : : * After the ALTER TYPE or SET EXPRESSION pass, do cleanup work
5452 : : * (this is not done in ATExecAlterColumnType since it should be
5453 : : * done only once if multiple columns of a table are altered).
5454 : : */
5455 [ + + + + ]: 34737 : if (pass == AT_PASS_ALTER_TYPE || pass == AT_PASS_SET_EXPRESSION)
5456 : 925 : ATPostAlterTypeCleanup(wqueue, tab, lockmode);
5457 : :
5458 [ + - ]: 34737 : if (tab->rel)
5459 : : {
5460 : 34737 : relation_close(tab->rel, NoLock);
5461 : 34737 : tab->rel = NULL;
5462 : : }
5463 : : }
5464 : : }
5465 : :
5466 : : /* Check to see if a toast table must be added. */
5467 [ + - + + : 41947 : foreach(ltab, *wqueue)
+ + ]
5468 : : {
5469 : 22509 : AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
5470 : :
5471 : : /*
5472 : : * If the table is source table of ATTACH PARTITION command, we did
5473 : : * not modify anything about it that will change its toasting
5474 : : * requirement, so no need to check.
5475 : : */
5476 [ + + ]: 22509 : if (((tab->relkind == RELKIND_RELATION ||
5477 [ + + ]: 4412 : tab->relkind == RELKIND_PARTITIONED_TABLE) &&
5478 [ + + ]: 21266 : tab->partition_constraint == NULL) ||
5479 [ + + ]: 2655 : tab->relkind == RELKIND_MATVIEW)
5480 : 19883 : AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
5481 : : }
5482 : 19438 : }
5483 : :
5484 : : /*
5485 : : * ATExecCmd: dispatch a subcommand to appropriate execution routine
5486 : : */
5487 : : static void
5488 : 39398 : ATExecCmd(List **wqueue, AlteredTableInfo *tab,
5489 : : AlterTableCmd *cmd, LOCKMODE lockmode, AlterTablePass cur_pass,
5490 : : AlterTableUtilityContext *context)
5491 : : {
5492 : 39398 : ObjectAddress address = InvalidObjectAddress;
5493 : 39398 : Relation rel = tab->rel;
5494 : :
5495 [ + + + + : 39398 : switch (cmd->subtype)
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + - + -
+ + - + +
+ + + + +
+ + + + +
+ + + + +
- ]
5496 : : {
5497 : 1606 : case AT_AddColumn: /* ADD COLUMN */
5498 : : case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */
5499 : 1606 : address = ATExecAddColumn(wqueue, tab, rel, &cmd,
5500 : 1606 : cmd->recurse, false,
5501 : : lockmode, cur_pass, context);
5502 : 1438 : break;
5503 : 387 : case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */
5504 : 387 : address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
5505 : 339 : break;
5506 : 57 : case AT_CookedColumnDefault: /* add a pre-cooked default */
5507 : 57 : address = ATExecCookedColumnDefault(rel, cmd->num, cmd->def);
5508 : 53 : break;
5509 : 107 : case AT_AddIdentity:
5510 : 107 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5511 : : cur_pass, context);
5512 : : Assert(cmd != NULL);
5513 : 99 : address = ATExecAddIdentity(rel, cmd->name, cmd->def, lockmode, cmd->recurse, false);
5514 : 63 : break;
5515 : 41 : case AT_SetIdentity:
5516 : 41 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5517 : : cur_pass, context);
5518 : : Assert(cmd != NULL);
5519 : 41 : address = ATExecSetIdentity(rel, cmd->name, cmd->def, lockmode, cmd->recurse, false);
5520 : 25 : break;
5521 : 37 : case AT_DropIdentity:
5522 : 37 : address = ATExecDropIdentity(rel, cmd->name, cmd->missing_ok, lockmode, cmd->recurse, false);
5523 : 25 : break;
5524 : 177 : case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
5525 : 177 : address = ATExecDropNotNull(rel, cmd->name, cmd->recurse, lockmode);
5526 : 109 : break;
5527 : 272 : case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
5528 : 272 : address = ATExecSetNotNull(wqueue, rel, NULL, cmd->name,
5529 : 272 : cmd->recurse, false, lockmode);
5530 : 252 : break;
5531 : 193 : case AT_SetExpression:
5532 : 193 : address = ATExecSetExpression(tab, rel, cmd->name, cmd->def, lockmode);
5533 : 181 : break;
5534 : 65 : case AT_DropExpression:
5535 : 65 : address = ATExecDropExpression(rel, cmd->name, cmd->missing_ok, lockmode);
5536 : 49 : break;
5537 : 111 : case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */
5538 : 111 : address = ATExecSetStatistics(rel, cmd->name, cmd->num, cmd->def, lockmode);
5539 : 79 : break;
5540 : 17 : case AT_SetOptions: /* ALTER COLUMN SET ( options ) */
5541 : 17 : address = ATExecSetOptions(rel, cmd->name, cmd->def, false, lockmode);
5542 : 17 : break;
5543 : 4 : case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */
5544 : 4 : address = ATExecSetOptions(rel, cmd->name, cmd->def, true, lockmode);
5545 : 4 : break;
5546 : 182 : case AT_SetStorage: /* ALTER COLUMN SET STORAGE */
5547 : 182 : address = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
5548 : 174 : break;
5549 : 48 : case AT_SetCompression: /* ALTER COLUMN SET COMPRESSION */
5550 : 48 : address = ATExecSetCompression(rel, cmd->name, cmd->def,
5551 : : lockmode);
5552 : 44 : break;
5553 : 1135 : case AT_DropColumn: /* DROP COLUMN */
5554 : 1135 : address = ATExecDropColumn(wqueue, rel, cmd->name,
5555 : 1135 : cmd->behavior, cmd->recurse, false,
5556 : 1135 : cmd->missing_ok, lockmode,
5557 : : NULL);
5558 : 1007 : break;
5559 : 778 : case AT_AddIndex: /* ADD INDEX */
5560 : 778 : address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
5561 : : lockmode);
5562 : 665 : break;
5563 : 330 : case AT_ReAddIndex: /* ADD INDEX */
5564 : 330 : address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true,
5565 : : lockmode);
5566 : 330 : break;
5567 : 57 : case AT_ReAddStatistics: /* ADD STATISTICS */
5568 : 57 : address = ATExecAddStatistics(tab, rel, (CreateStatsStmt *) cmd->def,
5569 : : true, lockmode);
5570 : 57 : break;
5571 : 18538 : case AT_AddConstraint: /* ADD CONSTRAINT */
5572 : : /* Transform the command only during initial examination */
5573 [ + + ]: 18538 : if (cur_pass == AT_PASS_ADD_CONSTR)
5574 : 10406 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd,
5575 : 10426 : cmd->recurse, lockmode,
5576 : : cur_pass, context);
5577 : : /* Depending on constraint type, might be no more work to do now */
5578 [ + + ]: 18518 : if (cmd != NULL)
5579 : : address =
5580 : 8112 : ATExecAddConstraint(wqueue, tab, rel,
5581 : 8112 : (Constraint *) cmd->def,
5582 : 8112 : cmd->recurse, false, lockmode);
5583 : 18057 : break;
5584 : 269 : case AT_ReAddConstraint: /* Re-add pre-existing check constraint */
5585 : : address =
5586 : 269 : ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
5587 : : true, true, lockmode);
5588 : 261 : break;
5589 : 13 : case AT_ReAddDomainConstraint: /* Re-add pre-existing domain check
5590 : : * constraint */
5591 : : address =
5592 : 13 : AlterDomainAddConstraint(((AlterDomainStmt *) cmd->def)->typeName,
5593 : 13 : ((AlterDomainStmt *) cmd->def)->def,
5594 : : NULL, true);
5595 : 9 : break;
5596 : 52 : case AT_ReAddComment: /* Re-add existing comment */
5597 : 52 : address = CommentObject((CommentStmt *) cmd->def);
5598 : 52 : break;
5599 : 6653 : case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */
5600 : 6653 : address = ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def,
5601 : : lockmode);
5602 : 6645 : break;
5603 : 336 : case AT_AlterConstraint: /* ALTER CONSTRAINT */
5604 : 336 : address = ATExecAlterConstraint(wqueue, rel,
5605 : 336 : castNode(ATAlterConstraint, cmd->def),
5606 : 336 : cmd->recurse, lockmode);
5607 : 260 : break;
5608 : 279 : case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
5609 : 279 : address = ATExecValidateConstraint(wqueue, rel, cmd->name, cmd->recurse,
5610 : : false, lockmode);
5611 : 275 : break;
5612 : 578 : case AT_DropConstraint: /* DROP CONSTRAINT */
5613 : 578 : ATExecDropConstraint(rel, cmd->name, cmd->behavior,
5614 : 578 : cmd->recurse,
5615 : 578 : cmd->missing_ok, lockmode);
5616 : 438 : break;
5617 : 804 : case AT_AlterColumnType: /* ALTER COLUMN TYPE */
5618 : : /* parse transformation was done earlier */
5619 : 804 : address = ATExecAlterColumnType(tab, rel, cmd, lockmode);
5620 : 776 : break;
5621 : 94 : case AT_AlterColumnGenericOptions: /* ALTER COLUMN OPTIONS */
5622 : : address =
5623 : 94 : ATExecAlterColumnGenericOptions(rel, cmd->name,
5624 : 94 : (List *) cmd->def, lockmode);
5625 : 90 : break;
5626 : 1149 : case AT_ChangeOwner: /* ALTER OWNER */
5627 : 1146 : ATExecChangeOwner(RelationGetRelid(rel),
5628 : 1149 : get_rolespec_oid(cmd->newowner, false),
5629 : : false, lockmode);
5630 : 1138 : break;
5631 : 39 : case AT_ClusterOn: /* CLUSTER ON */
5632 : 39 : address = ATExecClusterOn(rel, cmd->name, lockmode);
5633 : 39 : break;
5634 : 8 : case AT_DropCluster: /* SET WITHOUT CLUSTER */
5635 : 8 : ATExecDropCluster(rel, lockmode);
5636 : 8 : break;
5637 : 59 : case AT_SetLogged: /* SET LOGGED */
5638 : : case AT_SetUnLogged: /* SET UNLOGGED */
5639 : 59 : break;
5640 : 4 : case AT_DropOids: /* SET WITHOUT OIDS */
5641 : : /* nothing to do here, oid columns don't exist anymore */
5642 : 4 : break;
5643 : 61 : case AT_SetAccessMethod: /* SET ACCESS METHOD */
5644 : :
5645 : : /*
5646 : : * Only do this for partitioned tables, for which this is just a
5647 : : * catalog change. Tables with storage are handled by Phase 3.
5648 : : */
5649 [ + + ]: 61 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
5650 [ + + ]: 33 : tab->chgAccessMethod)
5651 : 29 : ATExecSetAccessMethodNoStorage(rel, tab->newAccessMethod);
5652 : 61 : break;
5653 : 105 : case AT_SetTableSpace: /* SET TABLESPACE */
5654 : :
5655 : : /*
5656 : : * Only do this for partitioned tables and indexes, for which this
5657 : : * is just a catalog change. Other relation types which have
5658 : : * storage are handled by Phase 3.
5659 : : */
5660 [ + + ]: 105 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
5661 [ + + ]: 97 : rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
5662 : 24 : ATExecSetTableSpaceNoStorage(rel, tab->newTableSpace);
5663 : :
5664 : 101 : break;
5665 : 632 : case AT_SetRelOptions: /* SET (...) */
5666 : : case AT_ResetRelOptions: /* RESET (...) */
5667 : : case AT_ReplaceRelOptions: /* replace entire option list */
5668 : 632 : ATExecSetRelOptions(rel, (List *) cmd->def, cmd->subtype, lockmode);
5669 : 598 : break;
5670 : 65 : case AT_EnableTrig: /* ENABLE TRIGGER name */
5671 : 65 : ATExecEnableDisableTrigger(rel, cmd->name,
5672 : : TRIGGER_FIRES_ON_ORIGIN, false,
5673 : 65 : cmd->recurse,
5674 : : lockmode);
5675 : 65 : break;
5676 : 27 : case AT_EnableAlwaysTrig: /* ENABLE ALWAYS TRIGGER name */
5677 : 27 : ATExecEnableDisableTrigger(rel, cmd->name,
5678 : : TRIGGER_FIRES_ALWAYS, false,
5679 : 27 : cmd->recurse,
5680 : : lockmode);
5681 : 27 : break;
5682 : 8 : case AT_EnableReplicaTrig: /* ENABLE REPLICA TRIGGER name */
5683 : 8 : ATExecEnableDisableTrigger(rel, cmd->name,
5684 : : TRIGGER_FIRES_ON_REPLICA, false,
5685 : 8 : cmd->recurse,
5686 : : lockmode);
5687 : 8 : break;
5688 : 75 : case AT_DisableTrig: /* DISABLE TRIGGER name */
5689 : 75 : ATExecEnableDisableTrigger(rel, cmd->name,
5690 : : TRIGGER_DISABLED, false,
5691 : 75 : cmd->recurse,
5692 : : lockmode);
5693 : 75 : break;
5694 : 0 : case AT_EnableTrigAll: /* ENABLE TRIGGER ALL */
5695 : 0 : ATExecEnableDisableTrigger(rel, NULL,
5696 : : TRIGGER_FIRES_ON_ORIGIN, false,
5697 : 0 : cmd->recurse,
5698 : : lockmode);
5699 : 0 : break;
5700 : 8 : case AT_DisableTrigAll: /* DISABLE TRIGGER ALL */
5701 : 8 : ATExecEnableDisableTrigger(rel, NULL,
5702 : : TRIGGER_DISABLED, false,
5703 : 8 : cmd->recurse,
5704 : : lockmode);
5705 : 8 : break;
5706 : 0 : case AT_EnableTrigUser: /* ENABLE TRIGGER USER */
5707 : 0 : ATExecEnableDisableTrigger(rel, NULL,
5708 : : TRIGGER_FIRES_ON_ORIGIN, true,
5709 : 0 : cmd->recurse,
5710 : : lockmode);
5711 : 0 : break;
5712 : 8 : case AT_DisableTrigUser: /* DISABLE TRIGGER USER */
5713 : 8 : ATExecEnableDisableTrigger(rel, NULL,
5714 : : TRIGGER_DISABLED, true,
5715 : 8 : cmd->recurse,
5716 : : lockmode);
5717 : 8 : break;
5718 : :
5719 : 5 : case AT_EnableRule: /* ENABLE RULE name */
5720 : 5 : ATExecEnableDisableRule(rel, cmd->name,
5721 : : RULE_FIRES_ON_ORIGIN, lockmode);
5722 : 5 : break;
5723 : 0 : case AT_EnableAlwaysRule: /* ENABLE ALWAYS RULE name */
5724 : 0 : ATExecEnableDisableRule(rel, cmd->name,
5725 : : RULE_FIRES_ALWAYS, lockmode);
5726 : 0 : break;
5727 : 4 : case AT_EnableReplicaRule: /* ENABLE REPLICA RULE name */
5728 : 4 : ATExecEnableDisableRule(rel, cmd->name,
5729 : : RULE_FIRES_ON_REPLICA, lockmode);
5730 : 4 : break;
5731 : 20 : case AT_DisableRule: /* DISABLE RULE name */
5732 : 20 : ATExecEnableDisableRule(rel, cmd->name,
5733 : : RULE_DISABLED, lockmode);
5734 : 20 : break;
5735 : :
5736 : 297 : case AT_AddInherit:
5737 : 297 : address = ATExecAddInherit(rel, (RangeVar *) cmd->def, lockmode);
5738 : 217 : break;
5739 : 69 : case AT_DropInherit:
5740 : 69 : address = ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode);
5741 : 65 : break;
5742 : 50 : case AT_AddOf:
5743 : 50 : address = ATExecAddOf(rel, (TypeName *) cmd->def, lockmode);
5744 : 22 : break;
5745 : 4 : case AT_DropOf:
5746 : 4 : ATExecDropOf(rel, lockmode);
5747 : 4 : break;
5748 : 326 : case AT_ReplicaIdentity:
5749 : 326 : ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
5750 : 290 : break;
5751 : 248 : case AT_EnableRowSecurity:
5752 : 248 : ATExecSetRowSecurity(rel, true);
5753 : 248 : break;
5754 : 6 : case AT_DisableRowSecurity:
5755 : 6 : ATExecSetRowSecurity(rel, false);
5756 : 6 : break;
5757 : 70 : case AT_ForceRowSecurity:
5758 : 70 : ATExecForceNoForceRowSecurity(rel, true);
5759 : 70 : break;
5760 : 20 : case AT_NoForceRowSecurity:
5761 : 20 : ATExecForceNoForceRowSecurity(rel, false);
5762 : 20 : break;
5763 : 33 : case AT_GenericOptions:
5764 : 33 : ATExecGenericOptions(rel, (List *) cmd->def);
5765 : 32 : break;
5766 : 1913 : case AT_AttachPartition:
5767 : 1913 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5768 : : cur_pass, context);
5769 : : Assert(cmd != NULL);
5770 [ + + ]: 1897 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
5771 : 1613 : address = ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def,
5772 : : context);
5773 : : else
5774 : 284 : address = ATExecAttachPartitionIdx(wqueue, rel,
5775 : 284 : ((PartitionCmd *) cmd->def)->name);
5776 : 1629 : break;
5777 : 373 : case AT_DetachPartition:
5778 : 373 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5779 : : cur_pass, context);
5780 : : Assert(cmd != NULL);
5781 : : /* ATPrepCmd ensures it must be a table */
5782 : : Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
5783 : 373 : address = ATExecDetachPartition(wqueue, tab, rel,
5784 : 373 : ((PartitionCmd *) cmd->def)->name,
5785 : 373 : ((PartitionCmd *) cmd->def)->concurrent);
5786 : 296 : break;
5787 : 7 : case AT_DetachPartitionFinalize:
5788 : 7 : address = ATExecDetachPartitionFinalize(rel, ((PartitionCmd *) cmd->def)->name);
5789 : 7 : break;
5790 : 200 : case AT_MergePartitions:
5791 : 200 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5792 : : cur_pass, context);
5793 : : Assert(cmd != NULL);
5794 : : Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
5795 : 144 : ATExecMergePartitions(wqueue, tab, rel, (PartitionCmd *) cmd->def,
5796 : : context);
5797 : 106 : break;
5798 : 285 : case AT_SplitPartition:
5799 : 285 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5800 : : cur_pass, context);
5801 : : Assert(cmd != NULL);
5802 : : Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
5803 : 153 : ATExecSplitPartition(wqueue, tab, rel, (PartitionCmd *) cmd->def,
5804 : : context);
5805 : 141 : break;
5806 : 0 : default: /* oops */
5807 [ # # ]: 0 : elog(ERROR, "unrecognized alter table type: %d",
5808 : : (int) cmd->subtype);
5809 : : break;
5810 : : }
5811 : :
5812 : : /*
5813 : : * Report the subcommand to interested event triggers.
5814 : : */
5815 [ + + ]: 37155 : if (cmd)
5816 : 26749 : EventTriggerCollectAlterTableSubcmd((Node *) cmd, address);
5817 : :
5818 : : /*
5819 : : * Bump the command counter to ensure the next subcommand in the sequence
5820 : : * can see the changes so far
5821 : : */
5822 : 37155 : CommandCounterIncrement();
5823 : 37155 : }
5824 : :
5825 : : /*
5826 : : * ATParseTransformCmd: perform parse transformation for one subcommand
5827 : : *
5828 : : * Returns the transformed subcommand tree, if there is one, else NULL.
5829 : : *
5830 : : * The parser may hand back additional AlterTableCmd(s) and/or other
5831 : : * utility statements, either before or after the original subcommand.
5832 : : * Other AlterTableCmds are scheduled into the appropriate slot of the
5833 : : * AlteredTableInfo (they had better be for later passes than the current one).
5834 : : * Utility statements that are supposed to happen before the AlterTableCmd
5835 : : * are executed immediately. Those that are supposed to happen afterwards
5836 : : * are added to the tab->afterStmts list to be done at the very end.
5837 : : */
5838 : : static AlterTableCmd *
5839 : 15821 : ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
5840 : : AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
5841 : : AlterTablePass cur_pass, AlterTableUtilityContext *context)
5842 : : {
5843 : 15821 : AlterTableCmd *newcmd = NULL;
5844 : 15821 : AlterTableStmt *atstmt = makeNode(AlterTableStmt);
5845 : : List *beforeStmts;
5846 : : List *afterStmts;
5847 : : ListCell *lc;
5848 : :
5849 : : /* Gin up an AlterTableStmt with just this subcommand and this table */
5850 : 15821 : atstmt->relation =
5851 : 15821 : makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
5852 : 15821 : pstrdup(RelationGetRelationName(rel)),
5853 : : -1);
5854 : 15821 : atstmt->relation->inh = recurse;
5855 : 15821 : atstmt->cmds = list_make1(cmd);
5856 : 15821 : atstmt->objtype = OBJECT_TABLE; /* needn't be picky here */
5857 : 15821 : atstmt->missing_ok = false;
5858 : :
5859 : : /* Transform the AlterTableStmt */
5860 : 15821 : atstmt = transformAlterTableStmt(RelationGetRelid(rel),
5861 : : atstmt,
5862 : : context->queryString,
5863 : : &beforeStmts,
5864 : : &afterStmts);
5865 : :
5866 : : /* Execute any statements that should happen before these subcommand(s) */
5867 [ + + + + : 15896 : foreach(lc, beforeStmts)
+ + ]
5868 : : {
5869 : 315 : Node *stmt = (Node *) lfirst(lc);
5870 : :
5871 : 315 : ProcessUtilityForAlterTable(stmt, context);
5872 : 307 : CommandCounterIncrement();
5873 : : }
5874 : :
5875 : : /* Examine the transformed subcommands and schedule them appropriately */
5876 [ + - + + : 36323 : foreach(lc, atstmt->cmds)
+ + ]
5877 : : {
5878 : 20742 : AlterTableCmd *cmd2 = lfirst_node(AlterTableCmd, lc);
5879 : : AlterTablePass pass;
5880 : :
5881 : : /*
5882 : : * This switch need only cover the subcommand types that can be added
5883 : : * by parse_utilcmd.c; otherwise, we'll use the default strategy of
5884 : : * executing the subcommand immediately, as a substitute for the
5885 : : * original subcommand. (Note, however, that this does cause
5886 : : * AT_AddConstraint subcommands to be rescheduled into later passes,
5887 : : * which is important for index and foreign key constraints.)
5888 : : *
5889 : : * We assume we needn't do any phase-1 checks for added subcommands.
5890 : : */
5891 [ + + + - : 20742 : switch (cmd2->subtype)
+ ]
5892 : : {
5893 : 794 : case AT_AddIndex:
5894 : 794 : pass = AT_PASS_ADD_INDEX;
5895 : 794 : break;
5896 : 6653 : case AT_AddIndexConstraint:
5897 : 6653 : pass = AT_PASS_ADD_INDEXCONSTR;
5898 : 6653 : break;
5899 : 8120 : case AT_AddConstraint:
5900 : : /* Recursion occurs during execution phase */
5901 [ + + ]: 8120 : if (recurse)
5902 : 8083 : cmd2->recurse = true;
5903 [ + - + ]: 8120 : switch (castNode(Constraint, cmd2->def)->contype)
5904 : : {
5905 : 5596 : case CONSTR_NOTNULL:
5906 : 5596 : pass = AT_PASS_COL_ATTRS;
5907 : 5596 : break;
5908 : 0 : case CONSTR_PRIMARY:
5909 : : case CONSTR_UNIQUE:
5910 : : case CONSTR_EXCLUSION:
5911 : 0 : pass = AT_PASS_ADD_INDEXCONSTR;
5912 : 0 : break;
5913 : 2524 : default:
5914 : 2524 : pass = AT_PASS_ADD_OTHERCONSTR;
5915 : 2524 : break;
5916 : : }
5917 : 8120 : break;
5918 : 0 : case AT_AlterColumnGenericOptions:
5919 : : /* This command never recurses */
5920 : : /* No command-specific prep needed */
5921 : 0 : pass = AT_PASS_MISC;
5922 : 0 : break;
5923 : 5175 : default:
5924 : 5175 : pass = cur_pass;
5925 : 5175 : break;
5926 : : }
5927 : :
5928 [ - + ]: 20742 : if (pass < cur_pass)
5929 : : {
5930 : : /* Cannot schedule into a pass we already finished */
5931 [ # # ]: 0 : elog(ERROR, "ALTER TABLE scheduling failure: too late for pass %d",
5932 : : pass);
5933 : : }
5934 [ + + ]: 20742 : else if (pass > cur_pass)
5935 : : {
5936 : : /* OK, queue it up for later */
5937 : 15567 : tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd2);
5938 : : }
5939 : : else
5940 : : {
5941 : : /*
5942 : : * We should see at most one subcommand for the current pass,
5943 : : * which is the transformed version of the original subcommand.
5944 : : */
5945 [ + - + - ]: 5175 : if (newcmd == NULL && cmd->subtype == cmd2->subtype)
5946 : : {
5947 : : /* Found the transformed version of our subcommand */
5948 : 5175 : newcmd = cmd2;
5949 : : }
5950 : : else
5951 [ # # ]: 0 : elog(ERROR, "ALTER TABLE scheduling failure: bogus item for pass %d",
5952 : : pass);
5953 : : }
5954 : : }
5955 : :
5956 : : /* Queue up any after-statements to happen at the end */
5957 : 15581 : tab->afterStmts = list_concat(tab->afterStmts, afterStmts);
5958 : :
5959 : 15581 : return newcmd;
5960 : : }
5961 : :
5962 : : /*
5963 : : * ATRewriteTables: ALTER TABLE phase 3
5964 : : */
5965 : : static void
5966 : 19438 : ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
5967 : : AlterTableUtilityContext *context)
5968 : : {
5969 : : ListCell *ltab;
5970 : :
5971 : : /* Go through each table that needs to be checked or rewritten */
5972 [ + - + + : 41533 : foreach(ltab, *wqueue)
+ + ]
5973 : : {
5974 : 22425 : AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
5975 : :
5976 : : /* Relations without storage may be ignored here */
5977 [ + + + + : 22425 : if (!RELKIND_HAS_STORAGE(tab->relkind))
+ + + - +
+ ]
5978 : 4234 : continue;
5979 : :
5980 : : /*
5981 : : * If we change column data types, the operation has to be propagated
5982 : : * to tables that use this table's rowtype as a column type.
5983 : : * tab->newvals will also be non-NULL in the case where we're adding a
5984 : : * column with a default. We choose to forbid that case as well,
5985 : : * since composite types might eventually support defaults.
5986 : : *
5987 : : * (Eventually we'll probably need to check for composite type
5988 : : * dependencies even when we're just scanning the table without a
5989 : : * rewrite, but at the moment a composite type does not enforce any
5990 : : * constraints, so it's not necessary/appropriate to enforce them just
5991 : : * during ALTER.)
5992 : : */
5993 [ + + + + ]: 18191 : if (tab->newvals != NIL || tab->rewrite > 0)
5994 : : {
5995 : : Relation rel;
5996 : :
5997 : 1329 : rel = table_open(tab->relid, NoLock);
5998 : 1329 : find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
5999 : 1293 : table_close(rel, NoLock);
6000 : : }
6001 : :
6002 : : /*
6003 : : * We only need to rewrite the table if at least one column needs to
6004 : : * be recomputed, or we are changing its persistence or access method.
6005 : : *
6006 : : * There are two reasons for requiring a rewrite when changing
6007 : : * persistence: on one hand, we need to ensure that the buffers
6008 : : * belonging to each of the two relations are marked with or without
6009 : : * BM_PERMANENT properly. On the other hand, since rewriting creates
6010 : : * and assigns a new relfilenumber, we automatically create or drop an
6011 : : * init fork for the relation as appropriate.
6012 : : */
6013 [ + + + + ]: 18155 : if (tab->rewrite > 0 && tab->relkind != RELKIND_SEQUENCE)
6014 : 732 : {
6015 : : /* Build a temporary relation and copy data */
6016 : : Relation OldHeap;
6017 : : Oid OIDNewHeap;
6018 : : Oid NewAccessMethod;
6019 : : Oid NewTableSpace;
6020 : : char persistence;
6021 : :
6022 : 793 : OldHeap = table_open(tab->relid, NoLock);
6023 : :
6024 : : /*
6025 : : * We don't support rewriting of system catalogs; there are too
6026 : : * many corner cases and too little benefit. In particular this
6027 : : * is certainly not going to work for mapped catalogs.
6028 : : */
6029 [ - + ]: 793 : if (IsSystemRelation(OldHeap))
6030 [ # # ]: 0 : ereport(ERROR,
6031 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
6032 : : errmsg("cannot rewrite system relation \"%s\"",
6033 : : RelationGetRelationName(OldHeap))));
6034 : :
6035 [ + + - + : 793 : if (RelationIsUsedAsCatalogTable(OldHeap))
- - + + ]
6036 [ + - ]: 1 : ereport(ERROR,
6037 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
6038 : : errmsg("cannot rewrite table \"%s\" used as a catalog table",
6039 : : RelationGetRelationName(OldHeap))));
6040 : :
6041 : : /*
6042 : : * Don't allow rewrite on temp tables of other backends ... their
6043 : : * local buffer manager is not going to cope. (This is redundant
6044 : : * with the check in CheckAlterTableIsSafe, but for safety we'll
6045 : : * check here too.)
6046 : : */
6047 [ + + - + ]: 792 : if (RELATION_IS_OTHER_TEMP(OldHeap))
6048 [ # # ]: 0 : ereport(ERROR,
6049 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
6050 : : errmsg("cannot rewrite temporary tables of other sessions")));
6051 : :
6052 : : /*
6053 : : * Select destination tablespace (same as original unless user
6054 : : * requested a change)
6055 : : */
6056 [ - + ]: 792 : if (tab->newTableSpace)
6057 : 0 : NewTableSpace = tab->newTableSpace;
6058 : : else
6059 : 792 : NewTableSpace = OldHeap->rd_rel->reltablespace;
6060 : :
6061 : : /*
6062 : : * Select destination access method (same as original unless user
6063 : : * requested a change)
6064 : : */
6065 [ + + ]: 792 : if (tab->chgAccessMethod)
6066 : 24 : NewAccessMethod = tab->newAccessMethod;
6067 : : else
6068 : 768 : NewAccessMethod = OldHeap->rd_rel->relam;
6069 : :
6070 : : /*
6071 : : * Select persistence of transient table (same as original unless
6072 : : * user requested a change)
6073 : : */
6074 [ + + ]: 792 : persistence = tab->chgPersistence ?
6075 : 757 : tab->newrelpersistence : OldHeap->rd_rel->relpersistence;
6076 : :
6077 : 792 : table_close(OldHeap, NoLock);
6078 : :
6079 : : /*
6080 : : * Fire off an Event Trigger now, before actually rewriting the
6081 : : * table.
6082 : : *
6083 : : * We don't support Event Trigger for nested commands anywhere,
6084 : : * here included, and parsetree is given NULL when coming from
6085 : : * AlterTableInternal.
6086 : : *
6087 : : * And fire it only once.
6088 : : */
6089 [ + - ]: 792 : if (parsetree)
6090 : 792 : EventTriggerTableRewrite((Node *) parsetree,
6091 : : tab->relid,
6092 : : tab->rewrite);
6093 : :
6094 : : /*
6095 : : * Create transient table that will receive the modified data.
6096 : : *
6097 : : * Ensure it is marked correctly as logged or unlogged. We have
6098 : : * to do this here so that buffers for the new relfilenumber will
6099 : : * have the right persistence set, and at the same time ensure
6100 : : * that the original filenumbers's buffers will get read in with
6101 : : * the correct setting (i.e. the original one). Otherwise a
6102 : : * rollback after the rewrite would possibly result with buffers
6103 : : * for the original filenumbers having the wrong persistence
6104 : : * setting.
6105 : : *
6106 : : * NB: This relies on swap_relation_files() also swapping the
6107 : : * persistence. That wouldn't work for pg_class, but that can't be
6108 : : * unlogged anyway.
6109 : : */
6110 : 788 : OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, NewAccessMethod,
6111 : : persistence, lockmode);
6112 : :
6113 : : /*
6114 : : * Copy the heap data into the new table with the desired
6115 : : * modifications, and test the current data within the table
6116 : : * against new constraints generated by ALTER TABLE commands.
6117 : : */
6118 : 788 : ATRewriteTable(tab, OIDNewHeap);
6119 : :
6120 : : /*
6121 : : * Swap the physical files of the old and new heaps, then rebuild
6122 : : * indexes and discard the old heap. We can use RecentXmin for
6123 : : * the table's new relfrozenxid because we rewrote all the tuples
6124 : : * in ATRewriteTable, so no older Xid remains in the table. Also,
6125 : : * we never try to swap toast tables by content, since we have no
6126 : : * interest in letting this code work on system catalogs.
6127 : : */
6128 : 736 : finish_heap_swap(tab->relid, OIDNewHeap,
6129 : : false, false, true,
6130 : 736 : !OidIsValid(tab->newTableSpace),
6131 : : true, /* reindex */
6132 : : RecentXmin,
6133 : : ReadNextMultiXactId(),
6134 : : persistence);
6135 : :
6136 [ - + ]: 732 : InvokeObjectPostAlterHook(RelationRelationId, tab->relid, 0);
6137 : : }
6138 [ + + + - ]: 17362 : else if (tab->rewrite > 0 && tab->relkind == RELKIND_SEQUENCE)
6139 : : {
6140 [ + - ]: 16 : if (tab->chgPersistence)
6141 : 16 : SequenceChangePersistence(tab->relid, tab->newrelpersistence);
6142 : : }
6143 : : else
6144 : : {
6145 : : /*
6146 : : * If required, test the current data within the table against new
6147 : : * constraints generated by ALTER TABLE commands, but don't
6148 : : * rebuild data.
6149 : : */
6150 [ + + + + ]: 17346 : if (tab->constraints != NIL || tab->verify_new_notnull ||
6151 [ + + ]: 15140 : tab->partition_constraint != NULL)
6152 : 3520 : ATRewriteTable(tab, InvalidOid);
6153 : :
6154 : : /*
6155 : : * If we had SET TABLESPACE but no reason to reconstruct tuples,
6156 : : * just do a block-by-block copy.
6157 : : */
6158 [ + + ]: 17113 : if (tab->newTableSpace)
6159 : 81 : ATExecSetTableSpace(tab->relid, tab->newTableSpace, lockmode);
6160 : : }
6161 : :
6162 : : /*
6163 : : * Also change persistence of owned sequences, so that it matches the
6164 : : * table persistence.
6165 : : */
6166 [ + + ]: 17861 : if (tab->chgPersistence)
6167 : : {
6168 : 51 : List *seqlist = getOwnedSequences(tab->relid);
6169 : : ListCell *lc;
6170 : :
6171 [ + + + + : 83 : foreach(lc, seqlist)
+ + ]
6172 : : {
6173 : 32 : Oid seq_relid = lfirst_oid(lc);
6174 : :
6175 : 32 : SequenceChangePersistence(seq_relid, tab->newrelpersistence);
6176 : : }
6177 : : }
6178 : : }
6179 : :
6180 : : /*
6181 : : * Foreign key constraints are checked in a final pass, since (a) it's
6182 : : * generally best to examine each one separately, and (b) it's at least
6183 : : * theoretically possible that we have changed both relations of the
6184 : : * foreign key, and we'd better have finished both rewrites before we try
6185 : : * to read the tables.
6186 : : */
6187 [ + - + + : 40954 : foreach(ltab, *wqueue)
+ + ]
6188 : : {
6189 : 21917 : AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
6190 : 21917 : Relation rel = NULL;
6191 : : ListCell *lcon;
6192 : :
6193 : : /* Relations without storage may be ignored here too */
6194 [ + + + + : 21917 : if (!RELKIND_HAS_STORAGE(tab->relkind))
+ + + - +
+ ]
6195 : 4129 : continue;
6196 : :
6197 [ + + + + : 19175 : foreach(lcon, tab->constraints)
+ + ]
6198 : : {
6199 : 1458 : NewConstraint *con = lfirst(lcon);
6200 : :
6201 [ + + ]: 1458 : if (con->contype == CONSTR_FOREIGN)
6202 : : {
6203 : 832 : Constraint *fkconstraint = (Constraint *) con->qual;
6204 : : Relation refrel;
6205 : :
6206 [ + + ]: 832 : if (rel == NULL)
6207 : : {
6208 : : /* Long since locked, no need for another */
6209 : 824 : rel = table_open(tab->relid, NoLock);
6210 : : }
6211 : :
6212 : 832 : refrel = table_open(con->refrelid, RowShareLock);
6213 : :
6214 : 832 : validateForeignKeyConstraint(fkconstraint->conname, rel, refrel,
6215 : : con->refindid,
6216 : : con->conid,
6217 : 832 : con->conwithperiod);
6218 : :
6219 : : /*
6220 : : * No need to mark the constraint row as validated, we did
6221 : : * that when we inserted the row earlier.
6222 : : */
6223 : :
6224 : 761 : table_close(refrel, NoLock);
6225 : : }
6226 : : }
6227 : :
6228 [ + + ]: 17717 : if (rel)
6229 : 753 : table_close(rel, NoLock);
6230 : : }
6231 : :
6232 : : /* Finally, run any afterStmts that were queued up */
6233 [ + - + + : 40856 : foreach(ltab, *wqueue)
+ + ]
6234 : : {
6235 : 21819 : AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
6236 : : ListCell *lc;
6237 : :
6238 [ + + + + : 21875 : foreach(lc, tab->afterStmts)
+ + ]
6239 : : {
6240 : 56 : Node *stmt = (Node *) lfirst(lc);
6241 : :
6242 : 56 : ProcessUtilityForAlterTable(stmt, context);
6243 : 56 : CommandCounterIncrement();
6244 : : }
6245 : : }
6246 : 19037 : }
6247 : :
6248 : : /*
6249 : : * ATRewriteTable: scan or rewrite one table
6250 : : *
6251 : : * A rewrite is requested by passing a valid OIDNewHeap; in that case, caller
6252 : : * must already hold AccessExclusiveLock on it.
6253 : : */
6254 : : static void
6255 : 4308 : ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
6256 : : {
6257 : : Relation oldrel;
6258 : : Relation newrel;
6259 : : TupleDesc oldTupDesc;
6260 : : TupleDesc newTupDesc;
6261 : 4308 : bool needscan = false;
6262 : : List *notnull_attrs;
6263 : : List *notnull_virtual_attrs;
6264 : : int i;
6265 : : ListCell *l;
6266 : : EState *estate;
6267 : : CommandId mycid;
6268 : : BulkInsertState bistate;
6269 : : uint32 ti_options;
6270 : 4308 : ExprState *partqualstate = NULL;
6271 : :
6272 : : /*
6273 : : * Open the relation(s). We have surely already locked the existing
6274 : : * table.
6275 : : */
6276 : 4308 : oldrel = table_open(tab->relid, NoLock);
6277 : 4308 : oldTupDesc = tab->oldDesc;
6278 : 4308 : newTupDesc = RelationGetDescr(oldrel); /* includes all mods */
6279 : :
6280 [ + + ]: 4308 : if (OidIsValid(OIDNewHeap))
6281 : : {
6282 : : Assert(CheckRelationOidLockedByMe(OIDNewHeap, AccessExclusiveLock,
6283 : : false));
6284 : 788 : newrel = table_open(OIDNewHeap, NoLock);
6285 : : }
6286 : : else
6287 : 3520 : newrel = NULL;
6288 : :
6289 : : /*
6290 : : * Prepare a BulkInsertState and options for table_tuple_insert. The FSM
6291 : : * is empty, so don't bother using it.
6292 : : */
6293 [ + + ]: 4308 : if (newrel)
6294 : : {
6295 : 788 : mycid = GetCurrentCommandId(true);
6296 : 788 : bistate = GetBulkInsertState();
6297 : 788 : ti_options = TABLE_INSERT_SKIP_FSM;
6298 : : }
6299 : : else
6300 : : {
6301 : : /* keep compiler quiet about using these uninitialized */
6302 : 3520 : mycid = 0;
6303 : 3520 : bistate = NULL;
6304 : 3520 : ti_options = 0;
6305 : : }
6306 : :
6307 : : /*
6308 : : * Generate the constraint and default execution states
6309 : : */
6310 : :
6311 : 4308 : estate = CreateExecutorState();
6312 : :
6313 : : /* Build the needed expression execution states */
6314 [ + + + + : 5930 : foreach(l, tab->constraints)
+ + ]
6315 : : {
6316 : 1622 : NewConstraint *con = lfirst(l);
6317 : :
6318 [ + + - ]: 1622 : switch (con->contype)
6319 : : {
6320 : 786 : case CONSTR_CHECK:
6321 : 786 : needscan = true;
6322 : 786 : con->qualstate = ExecPrepareExpr((Expr *) expand_generated_columns_in_expr(con->qual, oldrel, 1), estate);
6323 : 786 : break;
6324 : 836 : case CONSTR_FOREIGN:
6325 : : /* Nothing to do here */
6326 : 836 : break;
6327 : 0 : default:
6328 [ # # ]: 0 : elog(ERROR, "unrecognized constraint type: %d",
6329 : : (int) con->contype);
6330 : : }
6331 : : }
6332 : :
6333 : : /* Build expression execution states for partition check quals */
6334 [ + + ]: 4308 : if (tab->partition_constraint)
6335 : : {
6336 : 1408 : needscan = true;
6337 : 1408 : partqualstate = ExecPrepareExpr(tab->partition_constraint, estate);
6338 : : }
6339 : :
6340 [ + + + + : 5157 : foreach(l, tab->newvals)
+ + ]
6341 : : {
6342 : 849 : NewColumnValue *ex = lfirst(l);
6343 : :
6344 : : /* expr already planned */
6345 : 849 : ex->exprstate = ExecInitExpr(ex->expr, NULL);
6346 : : }
6347 : :
6348 : 4308 : notnull_attrs = notnull_virtual_attrs = NIL;
6349 [ + + + + ]: 4308 : if (newrel || tab->verify_new_notnull)
6350 : : {
6351 : : /*
6352 : : * If we are rebuilding the tuples OR if we added any new but not
6353 : : * verified not-null constraints, check all *valid* not-null
6354 : : * constraints. This is a bit of overkill but it minimizes risk of
6355 : : * bugs.
6356 : : *
6357 : : * notnull_attrs does *not* collect attribute numbers for valid
6358 : : * not-null constraints over virtual generated columns; instead, they
6359 : : * are collected in notnull_virtual_attrs for verification elsewhere.
6360 : : */
6361 [ + + ]: 5518 : for (i = 0; i < newTupDesc->natts; i++)
6362 : : {
6363 : 4025 : CompactAttribute *attr = TupleDescCompactAttr(newTupDesc, i);
6364 : :
6365 [ + + ]: 4025 : if (attr->attnullability == ATTNULLABLE_VALID &&
6366 [ + - ]: 1512 : !attr->attisdropped)
6367 : : {
6368 : 1512 : Form_pg_attribute wholeatt = TupleDescAttr(newTupDesc, i);
6369 : :
6370 [ + + ]: 1512 : if (wholeatt->attgenerated != ATTRIBUTE_GENERATED_VIRTUAL)
6371 : 1448 : notnull_attrs = lappend_int(notnull_attrs, wholeatt->attnum);
6372 : : else
6373 : 64 : notnull_virtual_attrs = lappend_int(notnull_virtual_attrs,
6374 : 64 : wholeatt->attnum);
6375 : : }
6376 : : }
6377 [ + + + + ]: 1493 : if (notnull_attrs || notnull_virtual_attrs)
6378 : 1095 : needscan = true;
6379 : : }
6380 : :
6381 [ + + + + ]: 4308 : if (newrel || needscan)
6382 : : {
6383 : : ExprContext *econtext;
6384 : : TupleTableSlot *oldslot;
6385 : : TupleTableSlot *newslot;
6386 : : TableScanDesc scan;
6387 : : MemoryContext oldCxt;
6388 : 3602 : List *dropped_attrs = NIL;
6389 : : ListCell *lc;
6390 : : Snapshot snapshot;
6391 : 3602 : ResultRelInfo *rInfo = NULL;
6392 : :
6393 : : /*
6394 : : * When adding or changing a virtual generated column with a not-null
6395 : : * constraint, we need to evaluate whether the generation expression
6396 : : * is null. For that, we borrow ExecRelGenVirtualNotNull(). Here, we
6397 : : * prepare a dummy ResultRelInfo.
6398 : : */
6399 [ + + ]: 3602 : if (notnull_virtual_attrs != NIL)
6400 : : {
6401 : : MemoryContext oldcontext;
6402 : :
6403 : : Assert(newTupDesc->constr->has_generated_virtual);
6404 : : Assert(newTupDesc->constr->has_not_null);
6405 : 44 : oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
6406 : 44 : rInfo = makeNode(ResultRelInfo);
6407 : 44 : InitResultRelInfo(rInfo,
6408 : : oldrel,
6409 : : 0, /* dummy rangetable index */
6410 : : NULL,
6411 : : estate->es_instrument);
6412 : 44 : MemoryContextSwitchTo(oldcontext);
6413 : : }
6414 : :
6415 [ + + ]: 3602 : if (newrel)
6416 [ - + ]: 788 : ereport(DEBUG1,
6417 : : (errmsg_internal("rewriting table \"%s\"",
6418 : : RelationGetRelationName(oldrel))));
6419 : : else
6420 [ + + ]: 2814 : ereport(DEBUG1,
6421 : : (errmsg_internal("verifying table \"%s\"",
6422 : : RelationGetRelationName(oldrel))));
6423 : :
6424 [ + + ]: 3602 : if (newrel)
6425 : : {
6426 : : /*
6427 : : * All predicate locks on the tuples or pages are about to be made
6428 : : * invalid, because we move tuples around. Promote them to
6429 : : * relation locks.
6430 : : */
6431 : 788 : TransferPredicateLocksToHeapRelation(oldrel);
6432 : : }
6433 : :
6434 [ - + ]: 3602 : econtext = GetPerTupleExprContext(estate);
6435 : :
6436 : : /*
6437 : : * Create necessary tuple slots. When rewriting, two slots are needed,
6438 : : * otherwise one suffices. In the case where one slot suffices, we
6439 : : * need to use the new tuple descriptor, otherwise some constraints
6440 : : * can't be evaluated. Note that even when the tuple layout is the
6441 : : * same and no rewrite is required, the tupDescs might not be
6442 : : * (consider ADD COLUMN without a default).
6443 : : */
6444 [ + + ]: 3602 : if (tab->rewrite)
6445 : : {
6446 : : Assert(newrel != NULL);
6447 : 788 : oldslot = MakeSingleTupleTableSlot(oldTupDesc,
6448 : : table_slot_callbacks(oldrel));
6449 : 788 : newslot = MakeSingleTupleTableSlot(newTupDesc,
6450 : : table_slot_callbacks(newrel));
6451 : :
6452 : : /*
6453 : : * Set all columns in the new slot to NULL initially, to ensure
6454 : : * columns added as part of the rewrite are initialized to NULL.
6455 : : * That is necessary as tab->newvals will not contain an
6456 : : * expression for columns with a NULL default, e.g. when adding a
6457 : : * column without a default together with a column with a default
6458 : : * requiring an actual rewrite.
6459 : : */
6460 : 788 : ExecStoreAllNullTuple(newslot);
6461 : : }
6462 : : else
6463 : : {
6464 : 2814 : oldslot = MakeSingleTupleTableSlot(newTupDesc,
6465 : : table_slot_callbacks(oldrel));
6466 : 2814 : newslot = NULL;
6467 : : }
6468 : :
6469 : : /*
6470 : : * Any attributes that are dropped according to the new tuple
6471 : : * descriptor can be set to NULL. We precompute the list of dropped
6472 : : * attributes to avoid needing to do so in the per-tuple loop.
6473 : : */
6474 [ + + ]: 12745 : for (i = 0; i < newTupDesc->natts; i++)
6475 : : {
6476 [ + + ]: 9143 : if (TupleDescAttr(newTupDesc, i)->attisdropped)
6477 : 616 : dropped_attrs = lappend_int(dropped_attrs, i);
6478 : : }
6479 : :
6480 : : /*
6481 : : * Scan through the rows, generating a new row if needed and then
6482 : : * checking all the constraints.
6483 : : */
6484 : 3602 : snapshot = RegisterSnapshot(GetLatestSnapshot());
6485 : 3602 : scan = table_beginscan(oldrel, snapshot, 0, NULL,
6486 : : SO_NONE);
6487 : :
6488 : : /*
6489 : : * Switch to per-tuple memory context and reset it for each tuple
6490 : : * produced, so we don't leak memory.
6491 : : */
6492 [ + - ]: 3602 : oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
6493 : :
6494 [ + + ]: 491295 : while (table_scan_getnextslot(scan, ForwardScanDirection, oldslot))
6495 : : {
6496 : : TupleTableSlot *insertslot;
6497 : :
6498 [ + + ]: 484376 : if (tab->rewrite > 0)
6499 : : {
6500 : : /* Extract data from old tuple */
6501 : 67479 : slot_getallattrs(oldslot);
6502 : 67479 : ExecClearTuple(newslot);
6503 : :
6504 : : /* copy attributes */
6505 : 67479 : memcpy(newslot->tts_values, oldslot->tts_values,
6506 : 67479 : sizeof(Datum) * oldslot->tts_nvalid);
6507 : 67479 : memcpy(newslot->tts_isnull, oldslot->tts_isnull,
6508 : 67479 : sizeof(bool) * oldslot->tts_nvalid);
6509 : :
6510 : : /* Set dropped attributes to null in new tuple */
6511 [ + + + + : 67564 : foreach(lc, dropped_attrs)
+ + ]
6512 : 85 : newslot->tts_isnull[lfirst_int(lc)] = true;
6513 : :
6514 : : /*
6515 : : * Constraints and GENERATED expressions might reference the
6516 : : * tableoid column, so fill tts_tableOid with the desired
6517 : : * value. (We must do this each time, because it gets
6518 : : * overwritten with newrel's OID during storing.)
6519 : : */
6520 : 67479 : newslot->tts_tableOid = RelationGetRelid(oldrel);
6521 : :
6522 : : /*
6523 : : * Process supplied expressions to replace selected columns.
6524 : : *
6525 : : * First, evaluate expressions whose inputs come from the old
6526 : : * tuple.
6527 : : */
6528 : 67479 : econtext->ecxt_scantuple = oldslot;
6529 : :
6530 [ + + + + : 137950 : foreach(l, tab->newvals)
+ + ]
6531 : : {
6532 : 70479 : NewColumnValue *ex = lfirst(l);
6533 : :
6534 [ + + ]: 70479 : if (ex->is_generated)
6535 : 284 : continue;
6536 : :
6537 : 70195 : newslot->tts_values[ex->attnum - 1]
6538 : 70187 : = ExecEvalExpr(ex->exprstate,
6539 : : econtext,
6540 : 70195 : &newslot->tts_isnull[ex->attnum - 1]);
6541 : : }
6542 : :
6543 : 67471 : ExecStoreVirtualTuple(newslot);
6544 : :
6545 : : /*
6546 : : * Now, evaluate any expressions whose inputs come from the
6547 : : * new tuple. We assume these columns won't reference each
6548 : : * other, so that there's no ordering dependency.
6549 : : */
6550 : 67471 : econtext->ecxt_scantuple = newslot;
6551 : :
6552 [ + + + + : 137942 : foreach(l, tab->newvals)
+ + ]
6553 : : {
6554 : 70471 : NewColumnValue *ex = lfirst(l);
6555 : :
6556 [ + + ]: 70471 : if (!ex->is_generated)
6557 : 70187 : continue;
6558 : :
6559 : 284 : newslot->tts_values[ex->attnum - 1]
6560 : 284 : = ExecEvalExpr(ex->exprstate,
6561 : : econtext,
6562 : 284 : &newslot->tts_isnull[ex->attnum - 1]);
6563 : : }
6564 : :
6565 : 67471 : insertslot = newslot;
6566 : : }
6567 : : else
6568 : : {
6569 : : /*
6570 : : * If there's no rewrite, old and new table are guaranteed to
6571 : : * have the same AM, so we can just use the old slot to verify
6572 : : * new constraints etc.
6573 : : */
6574 : 416897 : insertslot = oldslot;
6575 : : }
6576 : :
6577 : : /* Now check any constraints on the possibly-changed tuple */
6578 : 484368 : econtext->ecxt_scantuple = insertslot;
6579 : :
6580 [ + + + + : 2579602 : foreach_int(attn, notnull_attrs)
+ + ]
6581 : : {
6582 [ + + ]: 1611042 : if (slot_attisnull(insertslot, attn))
6583 : : {
6584 : 88 : Form_pg_attribute attr = TupleDescAttr(newTupDesc, attn - 1);
6585 : :
6586 [ + - ]: 88 : ereport(ERROR,
6587 : : (errcode(ERRCODE_NOT_NULL_VIOLATION),
6588 : : errmsg("column \"%s\" of relation \"%s\" contains null values",
6589 : : NameStr(attr->attname),
6590 : : RelationGetRelationName(oldrel)),
6591 : : errtablecol(oldrel, attn)));
6592 : : }
6593 : : }
6594 : :
6595 [ + + ]: 484280 : if (notnull_virtual_attrs != NIL)
6596 : : {
6597 : : AttrNumber attnum;
6598 : :
6599 : 60 : attnum = ExecRelGenVirtualNotNull(rInfo, insertslot,
6600 : : estate,
6601 : : notnull_virtual_attrs);
6602 [ + + ]: 60 : if (attnum != InvalidAttrNumber)
6603 : : {
6604 : 20 : Form_pg_attribute attr = TupleDescAttr(newTupDesc, attnum - 1);
6605 : :
6606 [ + - ]: 20 : ereport(ERROR,
6607 : : errcode(ERRCODE_NOT_NULL_VIOLATION),
6608 : : errmsg("column \"%s\" of relation \"%s\" contains null values",
6609 : : NameStr(attr->attname),
6610 : : RelationGetRelationName(oldrel)),
6611 : : errtablecol(oldrel, attnum));
6612 : : }
6613 : : }
6614 : :
6615 [ + + + + : 489601 : foreach(l, tab->constraints)
+ + ]
6616 : : {
6617 : 5461 : NewConstraint *con = lfirst(l);
6618 : :
6619 [ + + - ]: 5461 : switch (con->contype)
6620 : : {
6621 : 5395 : case CONSTR_CHECK:
6622 [ + + ]: 5395 : if (!ExecCheck(con->qualstate, econtext))
6623 [ + - ]: 120 : ereport(ERROR,
6624 : : (errcode(ERRCODE_CHECK_VIOLATION),
6625 : : errmsg("check constraint \"%s\" of relation \"%s\" is violated by some row",
6626 : : con->name,
6627 : : RelationGetRelationName(oldrel)),
6628 : : errtableconstraint(oldrel, con->name)));
6629 : 5275 : break;
6630 : 66 : case CONSTR_NOTNULL:
6631 : : case CONSTR_FOREIGN:
6632 : : /* Nothing to do here */
6633 : 66 : break;
6634 : 0 : default:
6635 [ # # ]: 0 : elog(ERROR, "unrecognized constraint type: %d",
6636 : : (int) con->contype);
6637 : : }
6638 : : }
6639 : :
6640 [ + + + + ]: 484140 : if (partqualstate && !ExecCheck(partqualstate, econtext))
6641 : : {
6642 [ + + ]: 49 : if (tab->validate_default)
6643 [ + - ]: 17 : ereport(ERROR,
6644 : : (errcode(ERRCODE_CHECK_VIOLATION),
6645 : : errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
6646 : : RelationGetRelationName(oldrel)),
6647 : : errtable(oldrel)));
6648 : : else
6649 [ + - ]: 32 : ereport(ERROR,
6650 : : (errcode(ERRCODE_CHECK_VIOLATION),
6651 : : errmsg("partition constraint of relation \"%s\" is violated by some row",
6652 : : RelationGetRelationName(oldrel)),
6653 : : errtable(oldrel)));
6654 : : }
6655 : :
6656 : : /* Write the tuple out to the new relation */
6657 [ + + ]: 484091 : if (newrel)
6658 : 67427 : table_tuple_insert(newrel, insertslot, mycid,
6659 : : ti_options, bistate);
6660 : :
6661 : 484091 : ResetExprContext(econtext);
6662 : :
6663 [ - + ]: 484091 : CHECK_FOR_INTERRUPTS();
6664 : : }
6665 : :
6666 : 3317 : MemoryContextSwitchTo(oldCxt);
6667 : 3317 : table_endscan(scan);
6668 : 3317 : UnregisterSnapshot(snapshot);
6669 : :
6670 : 3317 : ExecDropSingleTupleTableSlot(oldslot);
6671 [ + + ]: 3317 : if (newslot)
6672 : 736 : ExecDropSingleTupleTableSlot(newslot);
6673 : : }
6674 : :
6675 : 4023 : FreeExecutorState(estate);
6676 : :
6677 : 4023 : table_close(oldrel, NoLock);
6678 [ + + ]: 4023 : if (newrel)
6679 : : {
6680 : 736 : FreeBulkInsertState(bistate);
6681 : :
6682 : 736 : table_finish_bulk_insert(newrel, ti_options);
6683 : :
6684 : 736 : table_close(newrel, NoLock);
6685 : : }
6686 : 4023 : }
6687 : :
6688 : : /*
6689 : : * ATGetQueueEntry: find or create an entry in the ALTER TABLE work queue
6690 : : */
6691 : : static AlteredTableInfo *
6692 : 29639 : ATGetQueueEntry(List **wqueue, Relation rel)
6693 : : {
6694 : 29639 : Oid relid = RelationGetRelid(rel);
6695 : : AlteredTableInfo *tab;
6696 : : ListCell *ltab;
6697 : :
6698 [ + + + + : 38264 : foreach(ltab, *wqueue)
+ + ]
6699 : : {
6700 : 12627 : tab = (AlteredTableInfo *) lfirst(ltab);
6701 [ + + ]: 12627 : if (tab->relid == relid)
6702 : 4002 : return tab;
6703 : : }
6704 : :
6705 : : /*
6706 : : * Not there, so add it. Note that we make a copy of the relation's
6707 : : * existing descriptor before anything interesting can happen to it.
6708 : : */
6709 : 25637 : tab = palloc0_object(AlteredTableInfo);
6710 : 25637 : tab->relid = relid;
6711 : 25637 : tab->rel = NULL; /* set later */
6712 : 25637 : tab->relkind = rel->rd_rel->relkind;
6713 : 25637 : tab->oldDesc = CreateTupleDescCopyConstr(RelationGetDescr(rel));
6714 : 25637 : tab->newAccessMethod = InvalidOid;
6715 : 25637 : tab->chgAccessMethod = false;
6716 : 25637 : tab->newTableSpace = InvalidOid;
6717 : 25637 : tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
6718 : 25637 : tab->chgPersistence = false;
6719 : :
6720 : 25637 : *wqueue = lappend(*wqueue, tab);
6721 : :
6722 : 25637 : return tab;
6723 : : }
6724 : :
6725 : : static const char *
6726 : 73 : alter_table_type_to_string(AlterTableType cmdtype)
6727 : : {
6728 [ - - + + : 73 : switch (cmdtype)
- - - + -
- - + - -
+ - - - -
- - + + -
+ + - - +
- - - - -
- - - - -
- - - - +
+ - - - -
- - - - +
+ + - + -
- - - - ]
6729 : : {
6730 : 0 : case AT_AddColumn:
6731 : : case AT_AddColumnToView:
6732 : 0 : return "ADD COLUMN";
6733 : 0 : case AT_ColumnDefault:
6734 : : case AT_CookedColumnDefault:
6735 : 0 : return "ALTER COLUMN ... SET DEFAULT";
6736 : 4 : case AT_DropNotNull:
6737 : 4 : return "ALTER COLUMN ... DROP NOT NULL";
6738 : 4 : case AT_SetNotNull:
6739 : 4 : return "ALTER COLUMN ... SET NOT NULL";
6740 : 0 : case AT_SetExpression:
6741 : 0 : return "ALTER COLUMN ... SET EXPRESSION";
6742 : 0 : case AT_DropExpression:
6743 : 0 : return "ALTER COLUMN ... DROP EXPRESSION";
6744 : 0 : case AT_SetStatistics:
6745 : 0 : return "ALTER COLUMN ... SET STATISTICS";
6746 : 8 : case AT_SetOptions:
6747 : 8 : return "ALTER COLUMN ... SET";
6748 : 0 : case AT_ResetOptions:
6749 : 0 : return "ALTER COLUMN ... RESET";
6750 : 0 : case AT_SetStorage:
6751 : 0 : return "ALTER COLUMN ... SET STORAGE";
6752 : 0 : case AT_SetCompression:
6753 : 0 : return "ALTER COLUMN ... SET COMPRESSION";
6754 : 4 : case AT_DropColumn:
6755 : 4 : return "DROP COLUMN";
6756 : 0 : case AT_AddIndex:
6757 : : case AT_ReAddIndex:
6758 : 0 : return NULL; /* not real grammar */
6759 : 0 : case AT_AddConstraint:
6760 : : case AT_ReAddConstraint:
6761 : : case AT_ReAddDomainConstraint:
6762 : : case AT_AddIndexConstraint:
6763 : 0 : return "ADD CONSTRAINT";
6764 : 4 : case AT_AlterConstraint:
6765 : 4 : return "ALTER CONSTRAINT";
6766 : 0 : case AT_ValidateConstraint:
6767 : 0 : return "VALIDATE CONSTRAINT";
6768 : 0 : case AT_DropConstraint:
6769 : 0 : return "DROP CONSTRAINT";
6770 : 0 : case AT_ReAddComment:
6771 : 0 : return NULL; /* not real grammar */
6772 : 0 : case AT_AlterColumnType:
6773 : 0 : return "ALTER COLUMN ... SET DATA TYPE";
6774 : 0 : case AT_AlterColumnGenericOptions:
6775 : 0 : return "ALTER COLUMN ... OPTIONS";
6776 : 0 : case AT_ChangeOwner:
6777 : 0 : return "OWNER TO";
6778 : 4 : case AT_ClusterOn:
6779 : 4 : return "CLUSTER ON";
6780 : 4 : case AT_DropCluster:
6781 : 4 : return "SET WITHOUT CLUSTER";
6782 : 0 : case AT_SetAccessMethod:
6783 : 0 : return "SET ACCESS METHOD";
6784 : 4 : case AT_SetLogged:
6785 : 4 : return "SET LOGGED";
6786 : 4 : case AT_SetUnLogged:
6787 : 4 : return "SET UNLOGGED";
6788 : 0 : case AT_DropOids:
6789 : 0 : return "SET WITHOUT OIDS";
6790 : 0 : case AT_SetTableSpace:
6791 : 0 : return "SET TABLESPACE";
6792 : 1 : case AT_SetRelOptions:
6793 : 1 : return "SET";
6794 : 0 : case AT_ResetRelOptions:
6795 : 0 : return "RESET";
6796 : 0 : case AT_ReplaceRelOptions:
6797 : 0 : return NULL; /* not real grammar */
6798 : 0 : case AT_EnableTrig:
6799 : 0 : return "ENABLE TRIGGER";
6800 : 0 : case AT_EnableAlwaysTrig:
6801 : 0 : return "ENABLE ALWAYS TRIGGER";
6802 : 0 : case AT_EnableReplicaTrig:
6803 : 0 : return "ENABLE REPLICA TRIGGER";
6804 : 0 : case AT_DisableTrig:
6805 : 0 : return "DISABLE TRIGGER";
6806 : 0 : case AT_EnableTrigAll:
6807 : 0 : return "ENABLE TRIGGER ALL";
6808 : 0 : case AT_DisableTrigAll:
6809 : 0 : return "DISABLE TRIGGER ALL";
6810 : 0 : case AT_EnableTrigUser:
6811 : 0 : return "ENABLE TRIGGER USER";
6812 : 0 : case AT_DisableTrigUser:
6813 : 0 : return "DISABLE TRIGGER USER";
6814 : 0 : case AT_EnableRule:
6815 : 0 : return "ENABLE RULE";
6816 : 0 : case AT_EnableAlwaysRule:
6817 : 0 : return "ENABLE ALWAYS RULE";
6818 : 0 : case AT_EnableReplicaRule:
6819 : 0 : return "ENABLE REPLICA RULE";
6820 : 0 : case AT_DisableRule:
6821 : 0 : return "DISABLE RULE";
6822 : 4 : case AT_AddInherit:
6823 : 4 : return "INHERIT";
6824 : 4 : case AT_DropInherit:
6825 : 4 : return "NO INHERIT";
6826 : 0 : case AT_AddOf:
6827 : 0 : return "OF";
6828 : 0 : case AT_DropOf:
6829 : 0 : return "NOT OF";
6830 : 0 : case AT_ReplicaIdentity:
6831 : 0 : return "REPLICA IDENTITY";
6832 : 0 : case AT_EnableRowSecurity:
6833 : 0 : return "ENABLE ROW SECURITY";
6834 : 0 : case AT_DisableRowSecurity:
6835 : 0 : return "DISABLE ROW SECURITY";
6836 : 0 : case AT_ForceRowSecurity:
6837 : 0 : return "FORCE ROW SECURITY";
6838 : 0 : case AT_NoForceRowSecurity:
6839 : 0 : return "NO FORCE ROW SECURITY";
6840 : 0 : case AT_GenericOptions:
6841 : 0 : return "OPTIONS";
6842 : 4 : case AT_AttachPartition:
6843 : 4 : return "ATTACH PARTITION";
6844 : 12 : case AT_DetachPartition:
6845 : 12 : return "DETACH PARTITION";
6846 : 4 : case AT_DetachPartitionFinalize:
6847 : 4 : return "DETACH PARTITION ... FINALIZE";
6848 : 0 : case AT_MergePartitions:
6849 : 0 : return "MERGE PARTITIONS";
6850 : 4 : case AT_SplitPartition:
6851 : 4 : return "SPLIT PARTITION";
6852 : 0 : case AT_AddIdentity:
6853 : 0 : return "ALTER COLUMN ... ADD IDENTITY";
6854 : 0 : case AT_SetIdentity:
6855 : 0 : return "ALTER COLUMN ... SET";
6856 : 0 : case AT_DropIdentity:
6857 : 0 : return "ALTER COLUMN ... DROP IDENTITY";
6858 : 0 : case AT_ReAddStatistics:
6859 : 0 : return NULL; /* not real grammar */
6860 : : }
6861 : :
6862 : 0 : return NULL;
6863 : : }
6864 : :
6865 : : /*
6866 : : * ATSimplePermissions
6867 : : *
6868 : : * - Ensure that it is a relation (or possibly a view)
6869 : : * - Ensure this user is the owner
6870 : : * - Ensure that it is not a system table
6871 : : */
6872 : : static void
6873 : 26177 : ATSimplePermissions(AlterTableType cmdtype, Relation rel, int allowed_targets)
6874 : : {
6875 : : int actual_target;
6876 : :
6877 [ + + + + : 26177 : switch (rel->rd_rel->relkind)
+ + + + +
+ ]
6878 : : {
6879 : 20261 : case RELKIND_RELATION:
6880 : 20261 : actual_target = ATT_TABLE;
6881 : 20261 : break;
6882 : 4394 : case RELKIND_PARTITIONED_TABLE:
6883 : 4394 : actual_target = ATT_PARTITIONED_TABLE;
6884 : 4394 : break;
6885 : 280 : case RELKIND_VIEW:
6886 : 280 : actual_target = ATT_VIEW;
6887 : 280 : break;
6888 : 29 : case RELKIND_MATVIEW:
6889 : 29 : actual_target = ATT_MATVIEW;
6890 : 29 : break;
6891 : 140 : case RELKIND_INDEX:
6892 : 140 : actual_target = ATT_INDEX;
6893 : 140 : break;
6894 : 312 : case RELKIND_PARTITIONED_INDEX:
6895 : 312 : actual_target = ATT_PARTITIONED_INDEX;
6896 : 312 : break;
6897 : 149 : case RELKIND_COMPOSITE_TYPE:
6898 : 149 : actual_target = ATT_COMPOSITE_TYPE;
6899 : 149 : break;
6900 : 595 : case RELKIND_FOREIGN_TABLE:
6901 : 595 : actual_target = ATT_FOREIGN_TABLE;
6902 : 595 : break;
6903 : 16 : case RELKIND_SEQUENCE:
6904 : 16 : actual_target = ATT_SEQUENCE;
6905 : 16 : break;
6906 : 1 : default:
6907 : 1 : actual_target = 0;
6908 : 1 : break;
6909 : : }
6910 : :
6911 : : /* Wrong target type? */
6912 [ + + ]: 26177 : if ((actual_target & allowed_targets) == 0)
6913 : : {
6914 : 73 : const char *action_str = alter_table_type_to_string(cmdtype);
6915 : :
6916 [ + - ]: 73 : if (action_str)
6917 [ + - ]: 73 : ereport(ERROR,
6918 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
6919 : : /* translator: %s is a group of some SQL keywords */
6920 : : errmsg("ALTER action %s cannot be performed on relation \"%s\"",
6921 : : action_str, RelationGetRelationName(rel)),
6922 : : errdetail_relkind_not_supported(rel->rd_rel->relkind)));
6923 : : else
6924 : : /* internal error? */
6925 [ # # ]: 0 : elog(ERROR, "invalid ALTER action attempted on relation \"%s\"",
6926 : : RelationGetRelationName(rel));
6927 : : }
6928 : :
6929 : : /* Permissions checks */
6930 [ + + ]: 26104 : if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId()))
6931 : 8 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
6932 : 8 : RelationGetRelationName(rel));
6933 : :
6934 : : /*
6935 : : * Conflict log tables are used internally for logical replication
6936 : : * conflict logging and should not be altered directly, as it could
6937 : : * disrupt conflict logging. Direct ALTER commands are already rejected
6938 : : * during relation lookup in RangeVarCallbackForAlterRelation(), and
6939 : : * AlterTableMoveAll() skips these tables, so a conflict log table does
6940 : : * not normally reach here; this check guards any internal caller that
6941 : : * arrives via AlterTableInternal().
6942 : : */
6943 [ - + ]: 26096 : if (IsConflictLogTableClass(rel->rd_rel))
6944 [ # # ]: 0 : ereport(ERROR,
6945 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
6946 : : errmsg("cannot alter conflict log table \"%s\"",
6947 : : RelationGetRelationName(rel)),
6948 : : errdetail("Conflict log tables are system-managed tables for logical replication conflicts.")));
6949 : :
6950 [ + + - + ]: 26096 : if (!allowSystemTableMods && IsSystemRelation(rel))
6951 [ # # ]: 0 : ereport(ERROR,
6952 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
6953 : : errmsg("permission denied: \"%s\" is a system catalog",
6954 : : RelationGetRelationName(rel))));
6955 : 26096 : }
6956 : :
6957 : : /*
6958 : : * ATSimpleRecursion
6959 : : *
6960 : : * Simple table recursion sufficient for most ALTER TABLE operations.
6961 : : * All direct and indirect children are processed in an unspecified order.
6962 : : * Note that if a child inherits from the original table via multiple
6963 : : * inheritance paths, it will be visited just once.
6964 : : */
6965 : : static void
6966 : 994 : ATSimpleRecursion(List **wqueue, Relation rel,
6967 : : AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
6968 : : AlterTableUtilityContext *context)
6969 : : {
6970 : : /*
6971 : : * Propagate to children, if desired and if there are (or might be) any
6972 : : * children.
6973 : : */
6974 [ + + + + ]: 994 : if (recurse && rel->rd_rel->relhassubclass)
6975 : : {
6976 : 71 : Oid relid = RelationGetRelid(rel);
6977 : : ListCell *child;
6978 : : List *children;
6979 : :
6980 : 71 : children = find_all_inheritors(relid, lockmode, NULL);
6981 : :
6982 : : /*
6983 : : * find_all_inheritors does the recursive search of the inheritance
6984 : : * hierarchy, so all we have to do is process all of the relids in the
6985 : : * list that it returns.
6986 : : */
6987 [ + - + + : 300 : foreach(child, children)
+ + ]
6988 : : {
6989 : 229 : Oid childrelid = lfirst_oid(child);
6990 : : Relation childrel;
6991 : :
6992 [ + + ]: 229 : if (childrelid == relid)
6993 : 71 : continue;
6994 : : /* find_all_inheritors already got lock */
6995 : 158 : childrel = relation_open(childrelid, NoLock);
6996 : 158 : CheckAlterTableIsSafe(childrel);
6997 : 158 : ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, context);
6998 : 158 : relation_close(childrel, NoLock);
6999 : : }
7000 : : }
7001 : 994 : }
7002 : :
7003 : : /*
7004 : : * Obtain list of partitions of the given table, locking them all at the given
7005 : : * lockmode and ensuring that they all pass CheckAlterTableIsSafe.
7006 : : *
7007 : : * This function is a no-op if the given relation is not a partitioned table;
7008 : : * in particular, nothing is done if it's a legacy inheritance parent.
7009 : : */
7010 : : static void
7011 : 582 : ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode)
7012 : : {
7013 [ + + ]: 582 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
7014 : : {
7015 : : List *inh;
7016 : : ListCell *cell;
7017 : :
7018 : 121 : inh = find_all_inheritors(RelationGetRelid(rel), lockmode, NULL);
7019 : : /* first element is the parent rel; must ignore it */
7020 [ + - + + : 398 : for_each_from(cell, inh, 1)
+ + ]
7021 : : {
7022 : : Relation childrel;
7023 : :
7024 : : /* find_all_inheritors already got lock */
7025 : 281 : childrel = table_open(lfirst_oid(cell), NoLock);
7026 : 281 : CheckAlterTableIsSafe(childrel);
7027 : 277 : table_close(childrel, NoLock);
7028 : : }
7029 : 117 : list_free(inh);
7030 : : }
7031 : 578 : }
7032 : :
7033 : : /*
7034 : : * ATTypedTableRecursion
7035 : : *
7036 : : * Propagate ALTER TYPE operations to the typed tables of that type.
7037 : : * Also check the RESTRICT/CASCADE behavior. Given CASCADE, also permit
7038 : : * recursion to inheritance children of the typed tables.
7039 : : */
7040 : : static void
7041 : 133 : ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
7042 : : LOCKMODE lockmode, AlterTableUtilityContext *context)
7043 : : {
7044 : : ListCell *child;
7045 : : List *children;
7046 : :
7047 : : Assert(rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE);
7048 : :
7049 : 133 : children = find_typed_table_dependencies(rel->rd_rel->reltype,
7050 : 133 : RelationGetRelationName(rel),
7051 : : cmd->behavior);
7052 : :
7053 [ + + + + : 141 : foreach(child, children)
+ + ]
7054 : : {
7055 : 20 : Oid childrelid = lfirst_oid(child);
7056 : : Relation childrel;
7057 : :
7058 : 20 : childrel = relation_open(childrelid, lockmode);
7059 : 20 : CheckAlterTableIsSafe(childrel);
7060 : 20 : ATPrepCmd(wqueue, childrel, cmd, true, true, lockmode, context);
7061 : 20 : relation_close(childrel, NoLock);
7062 : : }
7063 : 121 : }
7064 : :
7065 : :
7066 : : /*
7067 : : * find_composite_type_dependencies
7068 : : *
7069 : : * Check to see if the type "typeOid" is being used as a column in some table
7070 : : * (possibly nested several levels deep in composite types, arrays, etc!).
7071 : : * Eventually, we'd like to propagate the check or rewrite operation
7072 : : * into such tables, but for now, just error out if we find any.
7073 : : *
7074 : : * Caller should provide either the associated relation of a rowtype,
7075 : : * or a type name (not both) for use in the error message, if any.
7076 : : *
7077 : : * Note that "typeOid" is not necessarily a composite type; it could also be
7078 : : * another container type such as an array or range, or a domain over one of
7079 : : * these things. The name of this function is therefore somewhat historical,
7080 : : * but it's not worth changing.
7081 : : *
7082 : : * We assume that functions and views depending on the type are not reasons
7083 : : * to reject the ALTER. (How safe is this really?)
7084 : : */
7085 : : void
7086 : 3380 : find_composite_type_dependencies(Oid typeOid, Relation origRelation,
7087 : : const char *origTypeName)
7088 : : {
7089 : : Relation depRel;
7090 : : ScanKeyData key[2];
7091 : : SysScanDesc depScan;
7092 : : HeapTuple depTup;
7093 : :
7094 : : /* since this function recurses, it could be driven to stack overflow */
7095 : 3380 : check_stack_depth();
7096 : :
7097 : : /*
7098 : : * We scan pg_depend to find those things that depend on the given type.
7099 : : * (We assume we can ignore refobjsubid for a type.)
7100 : : */
7101 : 3380 : depRel = table_open(DependRelationId, AccessShareLock);
7102 : :
7103 : 3380 : ScanKeyInit(&key[0],
7104 : : Anum_pg_depend_refclassid,
7105 : : BTEqualStrategyNumber, F_OIDEQ,
7106 : : ObjectIdGetDatum(TypeRelationId));
7107 : 3380 : ScanKeyInit(&key[1],
7108 : : Anum_pg_depend_refobjid,
7109 : : BTEqualStrategyNumber, F_OIDEQ,
7110 : : ObjectIdGetDatum(typeOid));
7111 : :
7112 : 3380 : depScan = systable_beginscan(depRel, DependReferenceIndexId, true,
7113 : : NULL, 2, key);
7114 : :
7115 [ + + ]: 5191 : while (HeapTupleIsValid(depTup = systable_getnext(depScan)))
7116 : : {
7117 : 1915 : Form_pg_depend pg_depend = (Form_pg_depend) GETSTRUCT(depTup);
7118 : : Relation rel;
7119 : : TupleDesc tupleDesc;
7120 : : Form_pg_attribute att;
7121 : :
7122 : : /* Check for directly dependent types */
7123 [ + + ]: 1915 : if (pg_depend->classid == TypeRelationId)
7124 : : {
7125 : : /*
7126 : : * This must be an array, domain, or range containing the given
7127 : : * type, so recursively check for uses of this type. Note that
7128 : : * any error message will mention the original type not the
7129 : : * container; this is intentional.
7130 : : */
7131 : 1637 : find_composite_type_dependencies(pg_depend->objid,
7132 : : origRelation, origTypeName);
7133 : 1621 : continue;
7134 : : }
7135 : :
7136 : : /* Else, ignore dependees that aren't relations */
7137 [ + + ]: 278 : if (pg_depend->classid != RelationRelationId)
7138 : 85 : continue;
7139 : :
7140 : 193 : rel = relation_open(pg_depend->objid, AccessShareLock);
7141 : 193 : tupleDesc = RelationGetDescr(rel);
7142 : :
7143 : : /*
7144 : : * If objsubid identifies a specific column, refer to that in error
7145 : : * messages. Otherwise, search to see if there's a user column of the
7146 : : * type. (We assume system columns are never of interesting types.)
7147 : : * The search is needed because an index containing an expression
7148 : : * column of the target type will just be recorded as a whole-relation
7149 : : * dependency. If we do not find a column of the type, the dependency
7150 : : * must indicate that the type is transiently referenced in an index
7151 : : * expression but not stored on disk, which we assume is OK, just as
7152 : : * we do for references in views. (It could also be that the target
7153 : : * type is embedded in some container type that is stored in an index
7154 : : * column, but the previous recursion should catch such cases.)
7155 : : */
7156 [ + + + - ]: 193 : if (pg_depend->objsubid > 0 && pg_depend->objsubid <= tupleDesc->natts)
7157 : 84 : att = TupleDescAttr(tupleDesc, pg_depend->objsubid - 1);
7158 : : else
7159 : : {
7160 : 109 : att = NULL;
7161 [ + + ]: 286 : for (int attno = 1; attno <= tupleDesc->natts; attno++)
7162 : : {
7163 : 181 : att = TupleDescAttr(tupleDesc, attno - 1);
7164 [ + + + - ]: 181 : if (att->atttypid == typeOid && !att->attisdropped)
7165 : 4 : break;
7166 : 177 : att = NULL;
7167 : : }
7168 [ + + ]: 109 : if (att == NULL)
7169 : : {
7170 : : /* No such column, so assume OK */
7171 : 105 : relation_close(rel, AccessShareLock);
7172 : 105 : continue;
7173 : : }
7174 : : }
7175 : :
7176 : : /*
7177 : : * We definitely should reject if the relation has storage. If it's
7178 : : * partitioned, then perhaps we don't have to reject: if there are
7179 : : * partitions then we'll fail when we find one, else there is no
7180 : : * stored data to worry about. However, it's possible that the type
7181 : : * change would affect conclusions about whether the type is sortable
7182 : : * or hashable and thus (if it's a partitioning column) break the
7183 : : * partitioning rule. For now, reject for partitioned rels too.
7184 : : */
7185 [ + + - + : 88 : if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind) ||
- - - - -
- ]
7186 [ # # # # ]: 0 : RELKIND_HAS_PARTITIONS(rel->rd_rel->relkind))
7187 : : {
7188 [ + + ]: 88 : if (origTypeName)
7189 [ + - ]: 20 : ereport(ERROR,
7190 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
7191 : : errmsg("cannot alter type \"%s\" because column \"%s.%s\" uses it",
7192 : : origTypeName,
7193 : : RelationGetRelationName(rel),
7194 : : NameStr(att->attname))));
7195 [ + + ]: 68 : else if (origRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
7196 [ + - ]: 12 : ereport(ERROR,
7197 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
7198 : : errmsg("cannot alter type \"%s\" because column \"%s.%s\" uses it",
7199 : : RelationGetRelationName(origRelation),
7200 : : RelationGetRelationName(rel),
7201 : : NameStr(att->attname))));
7202 [ + + ]: 56 : else if (origRelation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
7203 [ + - ]: 4 : ereport(ERROR,
7204 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
7205 : : errmsg("cannot alter foreign table \"%s\" because column \"%s.%s\" uses its row type",
7206 : : RelationGetRelationName(origRelation),
7207 : : RelationGetRelationName(rel),
7208 : : NameStr(att->attname))));
7209 : : else
7210 [ + - ]: 52 : ereport(ERROR,
7211 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
7212 : : errmsg("cannot alter table \"%s\" because column \"%s.%s\" uses its row type",
7213 : : RelationGetRelationName(origRelation),
7214 : : RelationGetRelationName(rel),
7215 : : NameStr(att->attname))));
7216 : : }
7217 [ # # ]: 0 : else if (OidIsValid(rel->rd_rel->reltype))
7218 : : {
7219 : : /*
7220 : : * A view or composite type itself isn't a problem, but we must
7221 : : * recursively check for indirect dependencies via its rowtype.
7222 : : */
7223 : 0 : find_composite_type_dependencies(rel->rd_rel->reltype,
7224 : : origRelation, origTypeName);
7225 : : }
7226 : :
7227 : 0 : relation_close(rel, AccessShareLock);
7228 : : }
7229 : :
7230 : 3276 : systable_endscan(depScan);
7231 : :
7232 : 3276 : relation_close(depRel, AccessShareLock);
7233 : 3276 : }
7234 : :
7235 : :
7236 : : /*
7237 : : * find_typed_table_dependencies
7238 : : *
7239 : : * Check to see if a composite type is being used as the type of a
7240 : : * typed table. Abort if any are found and behavior is RESTRICT.
7241 : : * Else return the list of tables.
7242 : : */
7243 : : static List *
7244 : 149 : find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior behavior)
7245 : : {
7246 : : Relation classRel;
7247 : : ScanKeyData key[1];
7248 : : TableScanDesc scan;
7249 : : HeapTuple tuple;
7250 : 149 : List *result = NIL;
7251 : :
7252 : 149 : classRel = table_open(RelationRelationId, AccessShareLock);
7253 : :
7254 : 149 : ScanKeyInit(&key[0],
7255 : : Anum_pg_class_reloftype,
7256 : : BTEqualStrategyNumber, F_OIDEQ,
7257 : : ObjectIdGetDatum(typeOid));
7258 : :
7259 : 149 : scan = table_beginscan_catalog(classRel, 1, key);
7260 : :
7261 [ + + ]: 173 : while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
7262 : : {
7263 : 40 : Form_pg_class classform = (Form_pg_class) GETSTRUCT(tuple);
7264 : :
7265 [ + + ]: 40 : if (behavior == DROP_RESTRICT)
7266 [ + - ]: 16 : ereport(ERROR,
7267 : : (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
7268 : : errmsg("cannot alter type \"%s\" because it is the type of a typed table",
7269 : : typeName),
7270 : : errhint("Use ALTER ... CASCADE to alter the typed tables too.")));
7271 : : else
7272 : 24 : result = lappend_oid(result, classform->oid);
7273 : : }
7274 : :
7275 : 133 : table_endscan(scan);
7276 : 133 : table_close(classRel, AccessShareLock);
7277 : :
7278 : 133 : return result;
7279 : : }
7280 : :
7281 : :
7282 : : /*
7283 : : * check_of_type
7284 : : *
7285 : : * Check whether a type is suitable for CREATE TABLE OF/ALTER TABLE OF. If it
7286 : : * isn't suitable, throw an error. Currently, we require that the type
7287 : : * originated with CREATE TYPE AS. We could support any row type, but doing so
7288 : : * would require handling a number of extra corner cases in the DDL commands.
7289 : : * (Also, allowing domain-over-composite would open up a can of worms about
7290 : : * whether and how the domain's constraints should apply to derived tables.)
7291 : : */
7292 : : void
7293 : 127 : check_of_type(HeapTuple typetuple)
7294 : : {
7295 : 127 : Form_pg_type typ = (Form_pg_type) GETSTRUCT(typetuple);
7296 : 127 : bool typeOk = false;
7297 : :
7298 [ + + ]: 127 : if (typ->typtype == TYPTYPE_COMPOSITE)
7299 : : {
7300 : : Relation typeRelation;
7301 : :
7302 : : Assert(OidIsValid(typ->typrelid));
7303 : 123 : typeRelation = relation_open(typ->typrelid, AccessShareLock);
7304 : 123 : typeOk = (typeRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE);
7305 : :
7306 : : /*
7307 : : * Close the parent rel, but keep our AccessShareLock on it until xact
7308 : : * commit. That will prevent someone else from deleting or ALTERing
7309 : : * the type before the typed table creation/conversion commits.
7310 : : */
7311 : 123 : relation_close(typeRelation, NoLock);
7312 : :
7313 [ + + ]: 123 : if (!typeOk)
7314 [ + - ]: 4 : ereport(ERROR,
7315 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
7316 : : errmsg("type %s is the row type of another table",
7317 : : format_type_be(typ->oid)),
7318 : : errdetail("A typed table must use a stand-alone composite type created with CREATE TYPE.")));
7319 : : }
7320 : : else
7321 [ + - ]: 4 : ereport(ERROR,
7322 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
7323 : : errmsg("type %s is not a composite type",
7324 : : format_type_be(typ->oid))));
7325 : 119 : }
7326 : :
7327 : :
7328 : : /*
7329 : : * ALTER TABLE ADD COLUMN
7330 : : *
7331 : : * Adds an additional attribute to a relation making the assumption that
7332 : : * CHECK, NOT NULL, and FOREIGN KEY constraints will be removed from the
7333 : : * AT_AddColumn AlterTableCmd by parse_utilcmd.c and added as independent
7334 : : * AlterTableCmd's.
7335 : : *
7336 : : * ADD COLUMN cannot use the normal ALTER TABLE recursion mechanism, because we
7337 : : * have to decide at runtime whether to recurse or not depending on whether we
7338 : : * actually add a column or merely merge with an existing column. (We can't
7339 : : * check this in a static pre-pass because it won't handle multiple inheritance
7340 : : * situations correctly.)
7341 : : */
7342 : : static void
7343 : 1618 : ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
7344 : : bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode,
7345 : : AlterTableUtilityContext *context)
7346 : : {
7347 [ + + + + ]: 1618 : if (rel->rd_rel->reloftype && !recursing)
7348 [ + - ]: 4 : ereport(ERROR,
7349 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
7350 : : errmsg("cannot add column to typed table")));
7351 : :
7352 [ + + ]: 1614 : if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
7353 : 42 : ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);
7354 : :
7355 [ + + + + ]: 1610 : if (recurse && !is_view)
7356 : 1535 : cmd->recurse = true;
7357 : 1610 : }
7358 : :
7359 : : /*
7360 : : * Add a column to a table. The return value is the address of the
7361 : : * new column in the parent relation.
7362 : : *
7363 : : * cmd is pass-by-ref so that we can replace it with the parse-transformed
7364 : : * copy (but that happens only after we check for IF NOT EXISTS).
7365 : : */
7366 : : static ObjectAddress
7367 : 2120 : ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
7368 : : AlterTableCmd **cmd, bool recurse, bool recursing,
7369 : : LOCKMODE lockmode, AlterTablePass cur_pass,
7370 : : AlterTableUtilityContext *context)
7371 : : {
7372 : 2120 : Oid myrelid = RelationGetRelid(rel);
7373 : 2120 : ColumnDef *colDef = castNode(ColumnDef, (*cmd)->def);
7374 : 2120 : bool if_not_exists = (*cmd)->missing_ok;
7375 : : Relation pgclass,
7376 : : attrdesc;
7377 : : HeapTuple reltup;
7378 : : Form_pg_class relform;
7379 : : Form_pg_attribute attribute;
7380 : : int newattnum;
7381 : : char relkind;
7382 : : Expr *defval;
7383 : : List *children;
7384 : : ListCell *child;
7385 : : AlterTableCmd *childcmd;
7386 : : ObjectAddress address;
7387 : : TupleDesc tupdesc;
7388 : :
7389 : : /* since this function recurses, it could be driven to stack overflow */
7390 : 2120 : check_stack_depth();
7391 : :
7392 : : /* At top level, permission check was done in ATPrepCmd, else do it */
7393 [ + + ]: 2120 : if (recursing)
7394 : 514 : ATSimplePermissions((*cmd)->subtype, rel,
7395 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
7396 : :
7397 [ + + + + ]: 2120 : if (rel->rd_rel->relispartition && !recursing)
7398 [ + - ]: 8 : ereport(ERROR,
7399 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
7400 : : errmsg("cannot add column to a partition")));
7401 : :
7402 : 2112 : attrdesc = table_open(AttributeRelationId, RowExclusiveLock);
7403 : :
7404 : : /*
7405 : : * Are we adding the column to a recursion child? If so, check whether to
7406 : : * merge with an existing definition for the column. If we do merge, we
7407 : : * must not recurse. Children will already have the column, and recursing
7408 : : * into them would mess up attinhcount.
7409 : : */
7410 [ + + ]: 2112 : if (colDef->inhcount > 0)
7411 : : {
7412 : : HeapTuple tuple;
7413 : :
7414 : : /* Does child already have a column by this name? */
7415 : 514 : tuple = SearchSysCacheCopyAttName(myrelid, colDef->colname);
7416 [ + + ]: 514 : if (HeapTupleIsValid(tuple))
7417 : : {
7418 : 40 : Form_pg_attribute childatt = (Form_pg_attribute) GETSTRUCT(tuple);
7419 : : Oid ctypeId;
7420 : : int32 ctypmod;
7421 : : Oid ccollid;
7422 : :
7423 : : /* Child column must match on type, typmod, and collation */
7424 : 40 : typenameTypeIdAndMod(NULL, colDef->typeName, &ctypeId, &ctypmod);
7425 [ + - ]: 40 : if (ctypeId != childatt->atttypid ||
7426 [ - + ]: 40 : ctypmod != childatt->atttypmod)
7427 [ # # ]: 0 : ereport(ERROR,
7428 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
7429 : : errmsg("child table \"%s\" has different type for column \"%s\"",
7430 : : RelationGetRelationName(rel), colDef->colname)));
7431 : 40 : ccollid = GetColumnDefCollation(NULL, colDef, ctypeId);
7432 [ - + ]: 40 : if (ccollid != childatt->attcollation)
7433 [ # # ]: 0 : ereport(ERROR,
7434 : : (errcode(ERRCODE_COLLATION_MISMATCH),
7435 : : errmsg("child table \"%s\" has different collation for column \"%s\"",
7436 : : RelationGetRelationName(rel), colDef->colname),
7437 : : errdetail("\"%s\" versus \"%s\"",
7438 : : get_collation_name(ccollid),
7439 : : get_collation_name(childatt->attcollation))));
7440 : :
7441 : : /* Bump the existing child att's inhcount */
7442 [ - + ]: 40 : if (pg_add_s16_overflow(childatt->attinhcount, 1,
7443 : : &childatt->attinhcount))
7444 [ # # ]: 0 : ereport(ERROR,
7445 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
7446 : : errmsg("too many inheritance parents"));
7447 : 40 : CatalogTupleUpdate(attrdesc, &tuple->t_self, tuple);
7448 : :
7449 : 40 : heap_freetuple(tuple);
7450 : :
7451 : : /* Inform the user about the merge */
7452 [ + - ]: 40 : ereport(NOTICE,
7453 : : (errmsg("merging definition of column \"%s\" for child \"%s\"",
7454 : : colDef->colname, RelationGetRelationName(rel))));
7455 : :
7456 : 40 : table_close(attrdesc, RowExclusiveLock);
7457 : :
7458 : : /* Make the child column change visible */
7459 : 40 : CommandCounterIncrement();
7460 : :
7461 : 40 : return InvalidObjectAddress;
7462 : : }
7463 : : }
7464 : :
7465 : : /* skip if the name already exists and if_not_exists is true */
7466 [ + + ]: 2072 : if (!check_for_column_name_collision(rel, colDef->colname, if_not_exists))
7467 : : {
7468 : 44 : table_close(attrdesc, RowExclusiveLock);
7469 : 44 : return InvalidObjectAddress;
7470 : : }
7471 : :
7472 : : /*
7473 : : * Okay, we need to add the column, so go ahead and do parse
7474 : : * transformation. This can result in queueing up, or even immediately
7475 : : * executing, subsidiary operations (such as creation of unique indexes);
7476 : : * so we mustn't do it until we have made the if_not_exists check.
7477 : : *
7478 : : * When recursing, the command was already transformed and we needn't do
7479 : : * so again. Also, if context isn't given we can't transform. (That
7480 : : * currently happens only for AT_AddColumnToView; we expect that view.c
7481 : : * passed us a ColumnDef that doesn't need work.)
7482 : : */
7483 [ + + + + ]: 2008 : if (context != NULL && !recursing)
7484 : : {
7485 : 1513 : *cmd = ATParseTransformCmd(wqueue, tab, rel, *cmd, recurse, lockmode,
7486 : : cur_pass, context);
7487 : : Assert(*cmd != NULL);
7488 : 1509 : colDef = castNode(ColumnDef, (*cmd)->def);
7489 : : }
7490 : :
7491 : : /*
7492 : : * Regular inheritance children are independent enough not to inherit the
7493 : : * identity column from parent hence cannot recursively add identity
7494 : : * column if the table has inheritance children.
7495 : : *
7496 : : * Partitions, on the other hand, are integral part of a partitioned table
7497 : : * and inherit identity column. Hence propagate identity column down the
7498 : : * partition hierarchy.
7499 : : */
7500 [ + + + - ]: 2004 : if (colDef->identity &&
7501 : 36 : recurse &&
7502 [ + + + + ]: 68 : rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
7503 : 32 : find_inheritance_children(myrelid, NoLock) != NIL)
7504 [ + - ]: 4 : ereport(ERROR,
7505 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
7506 : : errmsg("cannot recursively add identity column to table that has child tables")));
7507 : :
7508 : 2000 : pgclass = table_open(RelationRelationId, RowExclusiveLock);
7509 : :
7510 : 2000 : reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
7511 [ - + ]: 2000 : if (!HeapTupleIsValid(reltup))
7512 [ # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", myrelid);
7513 : 2000 : relform = (Form_pg_class) GETSTRUCT(reltup);
7514 : 2000 : relkind = relform->relkind;
7515 : :
7516 : : /* Determine the new attribute's number */
7517 : 2000 : newattnum = relform->relnatts + 1;
7518 [ - + ]: 2000 : if (newattnum > MaxHeapAttributeNumber)
7519 [ # # ]: 0 : ereport(ERROR,
7520 : : (errcode(ERRCODE_TOO_MANY_COLUMNS),
7521 : : errmsg("tables can have at most %d columns",
7522 : : MaxHeapAttributeNumber)));
7523 : :
7524 : : /*
7525 : : * Construct new attribute's pg_attribute entry.
7526 : : */
7527 : 2000 : tupdesc = BuildDescForRelation(list_make1(colDef));
7528 : :
7529 : 1992 : attribute = TupleDescAttr(tupdesc, 0);
7530 : :
7531 : : /* Fix up attribute number */
7532 : 1992 : attribute->attnum = newattnum;
7533 : :
7534 : : /* make sure datatype is legal for a column */
7535 : 3984 : CheckAttributeType(NameStr(attribute->attname), attribute->atttypid, attribute->attcollation,
7536 : 1992 : list_make1_oid(rel->rd_rel->reltype),
7537 [ + + ]: 1992 : (attribute->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL ? CHKATYPE_IS_VIRTUAL : 0));
7538 : :
7539 : 1964 : InsertPgAttributeTuples(attrdesc, tupdesc, myrelid, NULL, NULL);
7540 : :
7541 : 1964 : table_close(attrdesc, RowExclusiveLock);
7542 : :
7543 : : /*
7544 : : * Update pg_class tuple as appropriate
7545 : : */
7546 : 1964 : relform->relnatts = newattnum;
7547 : :
7548 : 1964 : CatalogTupleUpdate(pgclass, &reltup->t_self, reltup);
7549 : :
7550 : 1964 : heap_freetuple(reltup);
7551 : :
7552 : : /* Post creation hook for new attribute */
7553 [ - + ]: 1964 : InvokeObjectPostCreateHook(RelationRelationId, myrelid, newattnum);
7554 : :
7555 : 1964 : table_close(pgclass, RowExclusiveLock);
7556 : :
7557 : : /* Make the attribute's catalog entry visible */
7558 : 1964 : CommandCounterIncrement();
7559 : :
7560 : : /*
7561 : : * Store the DEFAULT, if any, in the catalogs
7562 : : */
7563 [ + + ]: 1964 : if (colDef->raw_default)
7564 : : {
7565 : : RawColumnDefault *rawEnt;
7566 : :
7567 : 766 : rawEnt = palloc_object(RawColumnDefault);
7568 : 766 : rawEnt->attnum = attribute->attnum;
7569 : 766 : rawEnt->raw_default = copyObject(colDef->raw_default);
7570 : 766 : rawEnt->generated = colDef->generated;
7571 : :
7572 : : /*
7573 : : * This function is intended for CREATE TABLE, so it processes a
7574 : : * _list_ of defaults, but we just do one.
7575 : : */
7576 : 766 : AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
7577 : : false, true, false, NULL);
7578 : :
7579 : : /* Make the additional catalog changes visible */
7580 : 678 : CommandCounterIncrement();
7581 : : }
7582 : :
7583 : : /*
7584 : : * Tell Phase 3 to fill in the default expression, if there is one.
7585 : : *
7586 : : * If there is no default, Phase 3 doesn't have to do anything, because
7587 : : * that effectively means that the default is NULL. The heap tuple access
7588 : : * routines always check for attnum > # of attributes in tuple, and return
7589 : : * NULL if so, so without any modification of the tuple data we will get
7590 : : * the effect of NULL values in the new column.
7591 : : *
7592 : : * An exception occurs when the new column is of a domain type: the domain
7593 : : * might have a not-null constraint, or a check constraint that indirectly
7594 : : * rejects nulls. If there are any domain constraints then we construct
7595 : : * an explicit NULL default value that will be passed through
7596 : : * CoerceToDomain processing. (This is a tad inefficient, since it causes
7597 : : * rewriting the table which we really wouldn't have to do; but we do it
7598 : : * to preserve the historical behavior that such a failure will be raised
7599 : : * only if the table currently contains some rows.)
7600 : : *
7601 : : * Note: we use build_column_default, and not just the cooked default
7602 : : * returned by AddRelationNewConstraints, so that the right thing happens
7603 : : * when a datatype's default applies.
7604 : : *
7605 : : * Note: it might seem that this should happen at the end of Phase 2, so
7606 : : * that the effects of subsequent subcommands can be taken into account.
7607 : : * It's intentional that we do it now, though. The new column should be
7608 : : * filled according to what is said in the ADD COLUMN subcommand, so that
7609 : : * the effects are the same as if this subcommand had been run by itself
7610 : : * and the later subcommands had been issued in new ALTER TABLE commands.
7611 : : *
7612 : : * We can skip this entirely for relations without storage, since Phase 3
7613 : : * is certainly not going to touch them.
7614 : : */
7615 [ + + + - : 1876 : if (RELKIND_HAS_STORAGE(relkind))
+ - + - -
+ ]
7616 : : {
7617 : : bool has_domain_constraints;
7618 : 1606 : bool has_missing = false;
7619 : :
7620 : : /*
7621 : : * For an identity column, we can't use build_column_default(),
7622 : : * because the sequence ownership isn't set yet. So do it manually.
7623 : : */
7624 [ + + ]: 1606 : if (colDef->identity)
7625 : : {
7626 : 28 : NextValueExpr *nve = makeNode(NextValueExpr);
7627 : :
7628 : 28 : nve->seqid = RangeVarGetRelid(colDef->identitySequence, NoLock, false);
7629 : 28 : nve->typeId = attribute->atttypid;
7630 : :
7631 : 28 : defval = (Expr *) nve;
7632 : : }
7633 : : else
7634 : 1578 : defval = (Expr *) build_column_default(rel, attribute->attnum);
7635 : :
7636 : : /* Build CoerceToDomain(NULL) expression if needed */
7637 : 1606 : has_domain_constraints = DomainHasConstraints(attribute->atttypid, NULL);
7638 [ + + + + ]: 1606 : if (!defval && has_domain_constraints)
7639 : : {
7640 : : Oid baseTypeId;
7641 : : int32 baseTypeMod;
7642 : : Oid baseTypeColl;
7643 : :
7644 : 4 : baseTypeMod = attribute->atttypmod;
7645 : 4 : baseTypeId = getBaseTypeAndTypmod(attribute->atttypid, &baseTypeMod);
7646 : 4 : baseTypeColl = get_typcollation(baseTypeId);
7647 : 4 : defval = (Expr *) makeNullConst(baseTypeId, baseTypeMod, baseTypeColl);
7648 : 4 : defval = (Expr *) coerce_to_target_type(NULL,
7649 : : (Node *) defval,
7650 : : baseTypeId,
7651 : : attribute->atttypid,
7652 : : attribute->atttypmod,
7653 : : COERCION_ASSIGNMENT,
7654 : : COERCE_IMPLICIT_CAST,
7655 : : -1);
7656 [ - + ]: 4 : if (defval == NULL) /* should not happen */
7657 [ # # ]: 0 : elog(ERROR, "failed to coerce base type to domain");
7658 : : }
7659 : :
7660 [ + + ]: 1606 : if (defval)
7661 : : {
7662 : : NewColumnValue *newval;
7663 : :
7664 : : /* Prepare defval for execution, either here or in Phase 3 */
7665 : 615 : defval = expression_planner(defval);
7666 : :
7667 : : /* Add the new default to the newvals list */
7668 : 615 : newval = palloc0_object(NewColumnValue);
7669 : 615 : newval->attnum = attribute->attnum;
7670 : 615 : newval->expr = defval;
7671 : 615 : newval->is_generated = (colDef->generated != '\0');
7672 : :
7673 : 615 : tab->newvals = lappend(tab->newvals, newval);
7674 : :
7675 : : /*
7676 : : * Attempt to skip a complete table rewrite by storing the
7677 : : * specified DEFAULT value outside of the heap. This is only
7678 : : * allowed for plain relations and non-generated columns, and the
7679 : : * default expression can't be volatile (stable is OK). Note that
7680 : : * contain_volatile_functions deems CoerceToDomain immutable, but
7681 : : * here we consider that coercion to a domain with constraints is
7682 : : * volatile; else it might fail even when the table is empty.
7683 : : */
7684 [ + - ]: 615 : if (rel->rd_rel->relkind == RELKIND_RELATION &&
7685 [ + + ]: 615 : !colDef->generated &&
7686 [ + + ]: 478 : !has_domain_constraints &&
7687 [ + + ]: 470 : !contain_volatile_functions((Node *) defval))
7688 : 360 : {
7689 : : EState *estate;
7690 : : ExprState *exprState;
7691 : : Datum missingval;
7692 : : bool missingIsNull;
7693 : :
7694 : : /* Evaluate the default expression */
7695 : 360 : estate = CreateExecutorState();
7696 : 360 : exprState = ExecPrepareExpr(defval, estate);
7697 : 360 : missingval = ExecEvalExpr(exprState,
7698 [ - + ]: 360 : GetPerTupleExprContext(estate),
7699 : : &missingIsNull);
7700 : : /* If it turns out NULL, nothing to do; else store it */
7701 [ + - ]: 360 : if (!missingIsNull)
7702 : : {
7703 : 360 : StoreAttrMissingVal(rel, attribute->attnum, missingval);
7704 : : /* Make the additional catalog change visible */
7705 : 360 : CommandCounterIncrement();
7706 : 360 : has_missing = true;
7707 : : }
7708 : 360 : FreeExecutorState(estate);
7709 : : }
7710 : : else
7711 : : {
7712 : : /*
7713 : : * Failed to use missing mode. We have to do a table rewrite
7714 : : * to install the value --- unless it's a virtual generated
7715 : : * column.
7716 : : */
7717 [ + + ]: 255 : if (colDef->generated != ATTRIBUTE_GENERATED_VIRTUAL)
7718 : 182 : tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
7719 : : }
7720 : : }
7721 : :
7722 [ + + ]: 1606 : if (!has_missing)
7723 : : {
7724 : : /*
7725 : : * If the new column is NOT NULL, and there is no missing value,
7726 : : * tell Phase 3 it needs to check for NULLs.
7727 : : */
7728 : 1246 : tab->verify_new_notnull |= colDef->is_not_null;
7729 : : }
7730 : : }
7731 : :
7732 : : /*
7733 : : * Add needed dependency entries for the new column.
7734 : : */
7735 : 1876 : add_column_datatype_dependency(myrelid, newattnum, attribute->atttypid);
7736 : 1876 : add_column_collation_dependency(myrelid, newattnum, attribute->attcollation);
7737 : :
7738 : : /*
7739 : : * Propagate to children as appropriate. Unlike most other ALTER
7740 : : * routines, we have to do this one level of recursion at a time; we can't
7741 : : * use find_all_inheritors to do it in one pass.
7742 : : */
7743 : : children =
7744 : 1876 : find_inheritance_children(RelationGetRelid(rel), lockmode);
7745 : :
7746 : : /*
7747 : : * If we are told not to recurse, there had better not be any child
7748 : : * tables; else the addition would put them out of step.
7749 : : */
7750 [ + + + + ]: 1876 : if (children && !recurse)
7751 [ + - ]: 8 : ereport(ERROR,
7752 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
7753 : : errmsg("column must be added to child tables too")));
7754 : :
7755 : : /* Children should see column as singly inherited */
7756 [ + + ]: 1868 : if (!recursing)
7757 : : {
7758 : 1394 : childcmd = copyObject(*cmd);
7759 : 1394 : colDef = castNode(ColumnDef, childcmd->def);
7760 : 1394 : colDef->inhcount = 1;
7761 : 1394 : colDef->is_local = false;
7762 : : }
7763 : : else
7764 : 474 : childcmd = *cmd; /* no need to copy again */
7765 : :
7766 [ + + + + : 2382 : foreach(child, children)
+ + ]
7767 : : {
7768 : 514 : Oid childrelid = lfirst_oid(child);
7769 : : Relation childrel;
7770 : : AlteredTableInfo *childtab;
7771 : :
7772 : : /* find_inheritance_children already got lock */
7773 : 514 : childrel = table_open(childrelid, NoLock);
7774 : 514 : CheckAlterTableIsSafe(childrel);
7775 : :
7776 : : /* Find or create work queue entry for this table */
7777 : 514 : childtab = ATGetQueueEntry(wqueue, childrel);
7778 : :
7779 : : /* Recurse to child; return value is ignored */
7780 : 514 : ATExecAddColumn(wqueue, childtab, childrel,
7781 : : &childcmd, recurse, true,
7782 : : lockmode, cur_pass, context);
7783 : :
7784 : 514 : table_close(childrel, NoLock);
7785 : : }
7786 : :
7787 : 1868 : ObjectAddressSubSet(address, RelationRelationId, myrelid, newattnum);
7788 : 1868 : return address;
7789 : : }
7790 : :
7791 : : /*
7792 : : * If a new or renamed column will collide with the name of an existing
7793 : : * column and if_not_exists is false then error out, else do nothing.
7794 : : */
7795 : : static bool
7796 : 2379 : check_for_column_name_collision(Relation rel, const char *colname,
7797 : : bool if_not_exists)
7798 : : {
7799 : : HeapTuple attTuple;
7800 : : int attnum;
7801 : :
7802 : : /*
7803 : : * this test is deliberately not attisdropped-aware, since if one tries to
7804 : : * add a column matching a dropped column name, it's gonna fail anyway.
7805 : : */
7806 : 2379 : attTuple = SearchSysCache2(ATTNAME,
7807 : : ObjectIdGetDatum(RelationGetRelid(rel)),
7808 : : PointerGetDatum(colname));
7809 [ + + ]: 2379 : if (!HeapTupleIsValid(attTuple))
7810 : 2307 : return true;
7811 : :
7812 : 72 : attnum = ((Form_pg_attribute) GETSTRUCT(attTuple))->attnum;
7813 : 72 : ReleaseSysCache(attTuple);
7814 : :
7815 : : /*
7816 : : * We throw a different error message for conflicts with system column
7817 : : * names, since they are normally not shown and the user might otherwise
7818 : : * be confused about the reason for the conflict.
7819 : : */
7820 [ + + ]: 72 : if (attnum <= 0)
7821 [ + - ]: 8 : ereport(ERROR,
7822 : : (errcode(ERRCODE_DUPLICATE_COLUMN),
7823 : : errmsg("column name \"%s\" conflicts with a system column name",
7824 : : colname)));
7825 : : else
7826 : : {
7827 [ + + ]: 64 : if (if_not_exists)
7828 : : {
7829 [ + - ]: 44 : ereport(NOTICE,
7830 : : (errcode(ERRCODE_DUPLICATE_COLUMN),
7831 : : errmsg("column \"%s\" of relation \"%s\" already exists, skipping",
7832 : : colname, RelationGetRelationName(rel))));
7833 : 44 : return false;
7834 : : }
7835 : :
7836 [ + - ]: 20 : ereport(ERROR,
7837 : : (errcode(ERRCODE_DUPLICATE_COLUMN),
7838 : : errmsg("column \"%s\" of relation \"%s\" already exists",
7839 : : colname, RelationGetRelationName(rel))));
7840 : : }
7841 : :
7842 : : return true;
7843 : : }
7844 : :
7845 : : /*
7846 : : * Install a column's dependency on its datatype.
7847 : : */
7848 : : static void
7849 : 2652 : add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid)
7850 : : {
7851 : : ObjectAddress myself,
7852 : : referenced;
7853 : :
7854 : 2652 : myself.classId = RelationRelationId;
7855 : 2652 : myself.objectId = relid;
7856 : 2652 : myself.objectSubId = attnum;
7857 : 2652 : referenced.classId = TypeRelationId;
7858 : 2652 : referenced.objectId = typid;
7859 : 2652 : referenced.objectSubId = 0;
7860 : 2652 : recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
7861 : 2652 : }
7862 : :
7863 : : /*
7864 : : * Install a column's dependency on its collation.
7865 : : */
7866 : : static void
7867 : 2652 : add_column_collation_dependency(Oid relid, int32 attnum, Oid collid)
7868 : : {
7869 : : ObjectAddress myself,
7870 : : referenced;
7871 : :
7872 : : /* We know the default collation is pinned, so don't bother recording it */
7873 [ + + + + ]: 2652 : if (OidIsValid(collid) && collid != DEFAULT_COLLATION_OID)
7874 : : {
7875 : 12 : myself.classId = RelationRelationId;
7876 : 12 : myself.objectId = relid;
7877 : 12 : myself.objectSubId = attnum;
7878 : 12 : referenced.classId = CollationRelationId;
7879 : 12 : referenced.objectId = collid;
7880 : 12 : referenced.objectSubId = 0;
7881 : 12 : recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
7882 : : }
7883 : 2652 : }
7884 : :
7885 : : /*
7886 : : * ALTER TABLE ALTER COLUMN DROP NOT NULL
7887 : : *
7888 : : * Return the address of the modified column. If the column was already
7889 : : * nullable, InvalidObjectAddress is returned.
7890 : : */
7891 : : static ObjectAddress
7892 : 177 : ATExecDropNotNull(Relation rel, const char *colName, bool recurse,
7893 : : LOCKMODE lockmode)
7894 : : {
7895 : : HeapTuple tuple;
7896 : : HeapTuple conTup;
7897 : : Form_pg_attribute attTup;
7898 : : AttrNumber attnum;
7899 : : Relation attr_rel;
7900 : : ObjectAddress address;
7901 : :
7902 : : /*
7903 : : * lookup the attribute
7904 : : */
7905 : 177 : attr_rel = table_open(AttributeRelationId, RowExclusiveLock);
7906 : :
7907 : 177 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
7908 [ + + ]: 177 : if (!HeapTupleIsValid(tuple))
7909 [ + - ]: 12 : ereport(ERROR,
7910 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
7911 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
7912 : : colName, RelationGetRelationName(rel))));
7913 : 165 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
7914 : 165 : attnum = attTup->attnum;
7915 : 165 : ObjectAddressSubSet(address, RelationRelationId,
7916 : : RelationGetRelid(rel), attnum);
7917 : :
7918 : : /* If the column is already nullable there's nothing to do. */
7919 [ - + ]: 165 : if (!attTup->attnotnull)
7920 : : {
7921 : 0 : table_close(attr_rel, RowExclusiveLock);
7922 : 0 : return InvalidObjectAddress;
7923 : : }
7924 : :
7925 : : /* Prevent them from altering a system attribute */
7926 [ - + ]: 165 : if (attnum <= 0)
7927 [ # # ]: 0 : ereport(ERROR,
7928 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
7929 : : errmsg("cannot alter system column \"%s\"",
7930 : : colName)));
7931 : :
7932 [ + + ]: 165 : if (attTup->attidentity)
7933 [ + - ]: 12 : ereport(ERROR,
7934 : : (errcode(ERRCODE_SYNTAX_ERROR),
7935 : : errmsg("column \"%s\" of relation \"%s\" is an identity column",
7936 : : colName, RelationGetRelationName(rel))));
7937 : :
7938 : : /*
7939 : : * If rel is partition, shouldn't drop NOT NULL if parent has the same.
7940 : : */
7941 [ + + ]: 153 : if (rel->rd_rel->relispartition)
7942 : : {
7943 : 8 : Oid parentId = get_partition_parent(RelationGetRelid(rel), false);
7944 : 8 : Relation parent = table_open(parentId, AccessShareLock);
7945 : 8 : TupleDesc tupDesc = RelationGetDescr(parent);
7946 : : AttrNumber parent_attnum;
7947 : :
7948 : 8 : parent_attnum = get_attnum(parentId, colName);
7949 [ + - ]: 8 : if (TupleDescAttr(tupDesc, parent_attnum - 1)->attnotnull)
7950 [ + - ]: 8 : ereport(ERROR,
7951 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
7952 : : errmsg("column \"%s\" is marked NOT NULL in parent table",
7953 : : colName)));
7954 : 0 : table_close(parent, AccessShareLock);
7955 : : }
7956 : :
7957 : : /*
7958 : : * Find the constraint that makes this column NOT NULL, and drop it.
7959 : : * dropconstraint_internal() resets attnotnull.
7960 : : */
7961 : 145 : conTup = findNotNullConstraintAttnum(RelationGetRelid(rel), attnum);
7962 [ - + ]: 145 : if (conTup == NULL)
7963 [ # # ]: 0 : elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation \"%s\"",
7964 : : colName, RelationGetRelationName(rel));
7965 : :
7966 : : /* The normal case: we have a pg_constraint row, remove it */
7967 : 145 : dropconstraint_internal(rel, conTup, DROP_RESTRICT, recurse, false,
7968 : : false, lockmode);
7969 : 109 : heap_freetuple(conTup);
7970 : :
7971 [ - + ]: 109 : InvokeObjectPostAlterHook(RelationRelationId,
7972 : : RelationGetRelid(rel), attnum);
7973 : :
7974 : 109 : table_close(attr_rel, RowExclusiveLock);
7975 : :
7976 : 109 : return address;
7977 : : }
7978 : :
7979 : : /*
7980 : : * set_attnotnull
7981 : : * Helper to update/validate the pg_attribute status of a not-null
7982 : : * constraint
7983 : : *
7984 : : * pg_attribute.attnotnull is set true, if it isn't already.
7985 : : * If queue_validation is true, also set up wqueue to validate the constraint.
7986 : : * wqueue may be given as NULL when validation is not needed (e.g., on table
7987 : : * creation).
7988 : : */
7989 : : static void
7990 : 16621 : set_attnotnull(List **wqueue, Relation rel, AttrNumber attnum,
7991 : : bool queue_validation)
7992 : : {
7993 : : Form_pg_attribute attr;
7994 : :
7995 : : Assert(!queue_validation || wqueue);
7996 : :
7997 : 16621 : CheckAlterTableIsSafe(rel);
7998 : :
7999 : : /*
8000 : : * Exit quickly by testing attnotnull from the tupledesc's copy of the
8001 : : * attribute.
8002 : : */
8003 : 16621 : attr = TupleDescAttr(RelationGetDescr(rel), attnum - 1);
8004 [ - + ]: 16621 : if (attr->attisdropped)
8005 : 0 : return;
8006 : :
8007 [ + + ]: 16621 : if (!attr->attnotnull)
8008 : : {
8009 : : Relation attr_rel;
8010 : : HeapTuple tuple;
8011 : :
8012 : 995 : attr_rel = table_open(AttributeRelationId, RowExclusiveLock);
8013 : :
8014 : 995 : tuple = SearchSysCacheCopyAttNum(RelationGetRelid(rel), attnum);
8015 [ - + ]: 995 : if (!HeapTupleIsValid(tuple))
8016 [ # # ]: 0 : elog(ERROR, "cache lookup failed for attribute %d of relation %u",
8017 : : attnum, RelationGetRelid(rel));
8018 : :
8019 : 995 : attr = (Form_pg_attribute) GETSTRUCT(tuple);
8020 : :
8021 : 995 : attr->attnotnull = true;
8022 : 995 : CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple);
8023 : :
8024 : : /*
8025 : : * If the nullness isn't already proven by validated constraints, have
8026 : : * ALTER TABLE phase 3 test for it.
8027 : : */
8028 [ + + + - ]: 995 : if (queue_validation && wqueue &&
8029 [ + + ]: 824 : !NotNullImpliedByRelConstraints(rel, attr))
8030 : : {
8031 : : AlteredTableInfo *tab;
8032 : :
8033 : 792 : tab = ATGetQueueEntry(wqueue, rel);
8034 : 792 : tab->verify_new_notnull = true;
8035 : : }
8036 : :
8037 : 995 : CommandCounterIncrement();
8038 : :
8039 : 995 : table_close(attr_rel, RowExclusiveLock);
8040 : 995 : heap_freetuple(tuple);
8041 : : }
8042 : : else
8043 : : {
8044 : 15626 : CacheInvalidateRelcache(rel);
8045 : : }
8046 : : }
8047 : :
8048 : : /*
8049 : : * ALTER TABLE ALTER COLUMN SET NOT NULL
8050 : : *
8051 : : * Add a not-null constraint to a single table and its children. Returns
8052 : : * the address of the constraint added to the parent relation, if one gets
8053 : : * added, or InvalidObjectAddress otherwise.
8054 : : *
8055 : : * We must recurse to child tables during execution, rather than using
8056 : : * ALTER TABLE's normal prep-time recursion.
8057 : : */
8058 : : static ObjectAddress
8059 : 469 : ATExecSetNotNull(List **wqueue, Relation rel, char *conName, char *colName,
8060 : : bool recurse, bool recursing, LOCKMODE lockmode)
8061 : : {
8062 : : HeapTuple tuple;
8063 : : AttrNumber attnum;
8064 : : ObjectAddress address;
8065 : : Constraint *constraint;
8066 : : CookedConstraint *ccon;
8067 : : List *cooked;
8068 : 469 : bool is_no_inherit = false;
8069 : :
8070 : : /* Guard against stack overflow due to overly deep inheritance tree. */
8071 : 469 : check_stack_depth();
8072 : :
8073 : : /* At top level, permission check was done in ATPrepCmd, else do it */
8074 [ + + ]: 469 : if (recursing)
8075 : : {
8076 : 197 : ATSimplePermissions(AT_AddConstraint, rel,
8077 : : ATT_PARTITIONED_TABLE | ATT_TABLE | ATT_FOREIGN_TABLE);
8078 : : Assert(conName != NULL);
8079 : : }
8080 : :
8081 : 469 : attnum = get_attnum(RelationGetRelid(rel), colName);
8082 [ + + ]: 469 : if (attnum == InvalidAttrNumber)
8083 [ + - ]: 12 : ereport(ERROR,
8084 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8085 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8086 : : colName, RelationGetRelationName(rel))));
8087 : :
8088 : : /* Prevent them from altering a system attribute */
8089 [ - + ]: 457 : if (attnum <= 0)
8090 [ # # ]: 0 : ereport(ERROR,
8091 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8092 : : errmsg("cannot alter system column \"%s\"",
8093 : : colName)));
8094 : :
8095 : : /* See if there's already a constraint */
8096 : 457 : tuple = findNotNullConstraintAttnum(RelationGetRelid(rel), attnum);
8097 [ + + ]: 457 : if (HeapTupleIsValid(tuple))
8098 : : {
8099 : 105 : Form_pg_constraint conForm = (Form_pg_constraint) GETSTRUCT(tuple);
8100 : 105 : bool changed = false;
8101 : :
8102 : : /*
8103 : : * Don't let a NO INHERIT constraint be changed into inherit.
8104 : : */
8105 [ + + + - ]: 105 : if (conForm->connoinherit && recurse)
8106 [ + - ]: 8 : ereport(ERROR,
8107 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8108 : : errmsg("cannot change NO INHERIT status of NOT NULL constraint \"%s\" on relation \"%s\"",
8109 : : NameStr(conForm->conname),
8110 : : RelationGetRelationName(rel)));
8111 : :
8112 : : /*
8113 : : * If we find an appropriate constraint, we're almost done, but just
8114 : : * need to change some properties on it: if we're recursing, increment
8115 : : * coninhcount; if not, set conislocal if not already set.
8116 : : */
8117 [ + + ]: 97 : if (recursing)
8118 : : {
8119 [ - + ]: 68 : if (pg_add_s16_overflow(conForm->coninhcount, 1,
8120 : : &conForm->coninhcount))
8121 [ # # ]: 0 : ereport(ERROR,
8122 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
8123 : : errmsg("too many inheritance parents"));
8124 : 68 : changed = true;
8125 : : }
8126 [ - + ]: 29 : else if (!conForm->conislocal)
8127 : : {
8128 : 0 : conForm->conislocal = true;
8129 : 0 : changed = true;
8130 : : }
8131 [ + + ]: 29 : else if (!conForm->convalidated)
8132 : : {
8133 : : /*
8134 : : * Flip attnotnull and convalidated, and also validate the
8135 : : * constraint.
8136 : : */
8137 : 16 : return ATExecValidateConstraint(wqueue, rel, NameStr(conForm->conname),
8138 : : recurse, recursing, lockmode);
8139 : : }
8140 : :
8141 [ + + ]: 81 : if (changed)
8142 : : {
8143 : : Relation constr_rel;
8144 : :
8145 : 68 : constr_rel = table_open(ConstraintRelationId, RowExclusiveLock);
8146 : :
8147 : 68 : CatalogTupleUpdate(constr_rel, &tuple->t_self, tuple);
8148 : 68 : ObjectAddressSet(address, ConstraintRelationId, conForm->oid);
8149 : 68 : table_close(constr_rel, RowExclusiveLock);
8150 : : }
8151 : :
8152 [ + + ]: 81 : if (changed)
8153 : 68 : return address;
8154 : : else
8155 : 13 : return InvalidObjectAddress;
8156 : : }
8157 : :
8158 : : /*
8159 : : * If we're asked not to recurse, and children exist, raise an error for
8160 : : * partitioned tables. For inheritance, we act as if NO INHERIT had been
8161 : : * specified.
8162 : : */
8163 [ + + + + ]: 372 : if (!recurse &&
8164 : 20 : find_inheritance_children(RelationGetRelid(rel),
8165 : : NoLock) != NIL)
8166 : : {
8167 [ + + ]: 12 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
8168 [ + - ]: 4 : ereport(ERROR,
8169 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8170 : : errmsg("constraint must be added to child tables too"),
8171 : : errhint("Do not specify the ONLY keyword."));
8172 : : else
8173 : 8 : is_no_inherit = true;
8174 : : }
8175 : :
8176 : : /*
8177 : : * No constraint exists; we must add one. First determine a name to use,
8178 : : * if we haven't already.
8179 : : */
8180 [ + + ]: 348 : if (!recursing)
8181 : : {
8182 : : Assert(conName == NULL);
8183 : 223 : conName = ChooseConstraintName(RelationGetRelationName(rel),
8184 : : colName, "not_null",
8185 : 223 : RelationGetNamespace(rel),
8186 : : NIL);
8187 : : }
8188 : :
8189 : 348 : constraint = makeNotNullConstraint(makeString(colName));
8190 : 348 : constraint->is_no_inherit = is_no_inherit;
8191 : 348 : constraint->conname = conName;
8192 : :
8193 : : /* and do it */
8194 : 348 : cooked = AddRelationNewConstraints(rel, NIL, list_make1(constraint),
8195 : 348 : false, !recursing, false, NULL);
8196 : 348 : ccon = linitial(cooked);
8197 : 348 : ObjectAddressSet(address, ConstraintRelationId, ccon->conoid);
8198 : :
8199 : : /* Mark pg_attribute.attnotnull for the column and queue validation */
8200 : 348 : set_attnotnull(wqueue, rel, attnum, true);
8201 : :
8202 [ - + ]: 348 : InvokeObjectPostAlterHook(RelationRelationId,
8203 : : RelationGetRelid(rel), attnum);
8204 : :
8205 : : /*
8206 : : * Recurse to propagate the constraint to children that don't have one.
8207 : : */
8208 [ + + ]: 348 : if (recurse)
8209 : : {
8210 : : List *children;
8211 : :
8212 : 332 : children = find_inheritance_children(RelationGetRelid(rel),
8213 : : lockmode);
8214 : :
8215 [ + + + + : 817 : foreach_oid(childoid, children)
+ + ]
8216 : : {
8217 : 161 : Relation childrel = table_open(childoid, NoLock);
8218 : :
8219 : 161 : CommandCounterIncrement();
8220 : :
8221 : 161 : ATExecSetNotNull(wqueue, childrel, conName, colName,
8222 : : recurse, true, lockmode);
8223 : 157 : table_close(childrel, NoLock);
8224 : : }
8225 : : }
8226 : :
8227 : 344 : return address;
8228 : : }
8229 : :
8230 : : /*
8231 : : * NotNullImpliedByRelConstraints
8232 : : * Does rel's existing constraints imply NOT NULL for the given attribute?
8233 : : */
8234 : : static bool
8235 : 824 : NotNullImpliedByRelConstraints(Relation rel, Form_pg_attribute attr)
8236 : : {
8237 : 824 : NullTest *nnulltest = makeNode(NullTest);
8238 : :
8239 : 1648 : nnulltest->arg = (Expr *) makeVar(1,
8240 : 824 : attr->attnum,
8241 : : attr->atttypid,
8242 : : attr->atttypmod,
8243 : : attr->attcollation,
8244 : : 0);
8245 : 824 : nnulltest->nulltesttype = IS_NOT_NULL;
8246 : :
8247 : : /*
8248 : : * argisrow = false is correct even for a composite column, because
8249 : : * attnotnull does not represent a SQL-spec IS NOT NULL test in such a
8250 : : * case, just IS DISTINCT FROM NULL.
8251 : : */
8252 : 824 : nnulltest->argisrow = false;
8253 : 824 : nnulltest->location = -1;
8254 : :
8255 [ + + ]: 824 : if (ConstraintImpliedByRelConstraint(rel, list_make1(nnulltest), NIL))
8256 : : {
8257 [ + + ]: 32 : ereport(DEBUG1,
8258 : : (errmsg_internal("existing constraints on column \"%s.%s\" are sufficient to prove that it does not contain nulls",
8259 : : RelationGetRelationName(rel), NameStr(attr->attname))));
8260 : 32 : return true;
8261 : : }
8262 : :
8263 : 792 : return false;
8264 : : }
8265 : :
8266 : : /*
8267 : : * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
8268 : : *
8269 : : * Return the address of the affected column.
8270 : : */
8271 : : static ObjectAddress
8272 : 387 : ATExecColumnDefault(Relation rel, const char *colName,
8273 : : Node *newDefault, LOCKMODE lockmode)
8274 : : {
8275 : 387 : TupleDesc tupdesc = RelationGetDescr(rel);
8276 : : AttrNumber attnum;
8277 : : ObjectAddress address;
8278 : :
8279 : : /*
8280 : : * get the number of the attribute
8281 : : */
8282 : 387 : attnum = get_attnum(RelationGetRelid(rel), colName);
8283 [ + + ]: 387 : if (attnum == InvalidAttrNumber)
8284 [ + - ]: 20 : ereport(ERROR,
8285 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8286 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8287 : : colName, RelationGetRelationName(rel))));
8288 : :
8289 : : /* Prevent them from altering a system attribute */
8290 [ - + ]: 367 : if (attnum <= 0)
8291 [ # # ]: 0 : ereport(ERROR,
8292 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8293 : : errmsg("cannot alter system column \"%s\"",
8294 : : colName)));
8295 : :
8296 [ + + ]: 367 : if (TupleDescAttr(tupdesc, attnum - 1)->attidentity)
8297 [ + - - + ]: 12 : ereport(ERROR,
8298 : : (errcode(ERRCODE_SYNTAX_ERROR),
8299 : : errmsg("column \"%s\" of relation \"%s\" is an identity column",
8300 : : colName, RelationGetRelationName(rel)),
8301 : : /* translator: %s is an SQL ALTER command */
8302 : : newDefault ? 0 : errhint("Use %s instead.",
8303 : : "ALTER TABLE ... ALTER COLUMN ... DROP IDENTITY")));
8304 : :
8305 [ + + ]: 355 : if (TupleDescAttr(tupdesc, attnum - 1)->attgenerated)
8306 [ + - - + : 8 : ereport(ERROR,
+ + ]
8307 : : (errcode(ERRCODE_SYNTAX_ERROR),
8308 : : errmsg("column \"%s\" of relation \"%s\" is a generated column",
8309 : : colName, RelationGetRelationName(rel)),
8310 : : newDefault ?
8311 : : /* translator: %s is an SQL ALTER command */
8312 : : errhint("Use %s instead.", "ALTER TABLE ... ALTER COLUMN ... SET EXPRESSION") :
8313 : : (TupleDescAttr(tupdesc, attnum - 1)->attgenerated == ATTRIBUTE_GENERATED_STORED ?
8314 : : errhint("Use %s instead.", "ALTER TABLE ... ALTER COLUMN ... DROP EXPRESSION") : 0)));
8315 : :
8316 : : /*
8317 : : * Remove any old default for the column. We use RESTRICT here for
8318 : : * safety, but at present we do not expect anything to depend on the
8319 : : * default.
8320 : : *
8321 : : * We treat removing the existing default as an internal operation when it
8322 : : * is preparatory to adding a new default, but as a user-initiated
8323 : : * operation when the user asked for a drop.
8324 : : */
8325 : 347 : RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false,
8326 : : newDefault != NULL);
8327 : :
8328 [ + + ]: 347 : if (newDefault)
8329 : : {
8330 : : /* SET DEFAULT */
8331 : : RawColumnDefault *rawEnt;
8332 : :
8333 : 231 : rawEnt = palloc_object(RawColumnDefault);
8334 : 231 : rawEnt->attnum = attnum;
8335 : 231 : rawEnt->raw_default = newDefault;
8336 : 231 : rawEnt->generated = '\0';
8337 : :
8338 : : /*
8339 : : * This function is intended for CREATE TABLE, so it processes a
8340 : : * _list_ of defaults, but we just do one.
8341 : : */
8342 : 231 : AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
8343 : : false, true, false, NULL);
8344 : : }
8345 : :
8346 : 339 : ObjectAddressSubSet(address, RelationRelationId,
8347 : : RelationGetRelid(rel), attnum);
8348 : 339 : return address;
8349 : : }
8350 : :
8351 : : /*
8352 : : * Add a pre-cooked default expression.
8353 : : *
8354 : : * Return the address of the affected column.
8355 : : */
8356 : : static ObjectAddress
8357 : 57 : ATExecCookedColumnDefault(Relation rel, AttrNumber attnum,
8358 : : Node *newDefault)
8359 : : {
8360 : : ObjectAddress address;
8361 : :
8362 : : /*
8363 : : * This is used for a cooked default copied by CREATE TABLE ... LIKE,
8364 : : * which adds new type dependencies. Such a default doesn't go through
8365 : : * AddRelationNewConstraints(), and StoreAttrDefault() leaves the
8366 : : * privilege checks to its caller, so we must check for USAGE on the types
8367 : : * here.
8368 : : */
8369 : 57 : CheckUsageOnTypesInSingleRelExpr(newDefault, RelationGetRelid(rel), GetUserId());
8370 : :
8371 : : /*
8372 : : * Remove any old default for the column. We use RESTRICT here for
8373 : : * safety, but at present we do not expect anything to depend on the
8374 : : * default. (In ordinary cases, there could not be a default in place
8375 : : * anyway, but it's possible when combining LIKE with inheritance.)
8376 : : */
8377 : 53 : RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false,
8378 : : true);
8379 : :
8380 : 53 : (void) StoreAttrDefault(rel, attnum, newDefault, true);
8381 : :
8382 : 53 : ObjectAddressSubSet(address, RelationRelationId,
8383 : : RelationGetRelid(rel), attnum);
8384 : 53 : return address;
8385 : : }
8386 : :
8387 : : /*
8388 : : * ALTER TABLE ALTER COLUMN ADD IDENTITY
8389 : : *
8390 : : * Return the address of the affected column.
8391 : : */
8392 : : static ObjectAddress
8393 : 103 : ATExecAddIdentity(Relation rel, const char *colName,
8394 : : Node *def, LOCKMODE lockmode, bool recurse, bool recursing)
8395 : : {
8396 : : Relation attrelation;
8397 : : HeapTuple tuple;
8398 : : Form_pg_attribute attTup;
8399 : : AttrNumber attnum;
8400 : : ObjectAddress address;
8401 : 103 : ColumnDef *cdef = castNode(ColumnDef, def);
8402 : : bool ispartitioned;
8403 : :
8404 : 103 : ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
8405 [ + + + + ]: 103 : if (ispartitioned && !recurse)
8406 [ + - ]: 4 : ereport(ERROR,
8407 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8408 : : errmsg("cannot add identity to a column of only the partitioned table"),
8409 : : errhint("Do not specify the ONLY keyword.")));
8410 : :
8411 [ + + + + ]: 99 : if (rel->rd_rel->relispartition && !recursing)
8412 [ + - ]: 8 : ereport(ERROR,
8413 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8414 : : errmsg("cannot add identity to a column of a partition"));
8415 : :
8416 : 91 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
8417 : :
8418 : 91 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
8419 [ - + ]: 91 : if (!HeapTupleIsValid(tuple))
8420 [ # # ]: 0 : ereport(ERROR,
8421 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8422 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8423 : : colName, RelationGetRelationName(rel))));
8424 : 91 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8425 : 91 : attnum = attTup->attnum;
8426 : :
8427 : : /* Can't alter a system attribute */
8428 [ - + ]: 91 : if (attnum <= 0)
8429 [ # # ]: 0 : ereport(ERROR,
8430 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8431 : : errmsg("cannot alter system column \"%s\"",
8432 : : colName)));
8433 : :
8434 : : /*
8435 : : * Creating a column as identity implies NOT NULL, so adding the identity
8436 : : * to an existing column that is not NOT NULL would create a state that
8437 : : * cannot be reproduced without contortions.
8438 : : */
8439 [ + + ]: 91 : if (!attTup->attnotnull)
8440 [ + - ]: 4 : ereport(ERROR,
8441 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8442 : : errmsg("column \"%s\" of relation \"%s\" must be declared NOT NULL before identity can be added",
8443 : : colName, RelationGetRelationName(rel))));
8444 : :
8445 : : /*
8446 : : * On the other hand, if a not-null constraint exists, then verify that
8447 : : * it's compatible.
8448 : : */
8449 [ + - ]: 87 : if (attTup->attnotnull)
8450 : : {
8451 : : HeapTuple contup;
8452 : : Form_pg_constraint conForm;
8453 : :
8454 : 87 : contup = findNotNullConstraintAttnum(RelationGetRelid(rel),
8455 : : attnum);
8456 [ - + ]: 87 : if (!HeapTupleIsValid(contup))
8457 [ # # ]: 0 : elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation \"%s\"",
8458 : : colName, RelationGetRelationName(rel));
8459 : :
8460 : 87 : conForm = (Form_pg_constraint) GETSTRUCT(contup);
8461 [ + + ]: 87 : if (!conForm->convalidated)
8462 [ + - ]: 4 : ereport(ERROR,
8463 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8464 : : errmsg("incompatible NOT VALID constraint \"%s\" on relation \"%s\"",
8465 : : NameStr(conForm->conname), RelationGetRelationName(rel)),
8466 : : errhint("You might need to validate it using %s.",
8467 : : "ALTER TABLE ... VALIDATE CONSTRAINT"));
8468 : : }
8469 : :
8470 [ + + ]: 83 : if (attTup->attidentity)
8471 [ + - ]: 12 : ereport(ERROR,
8472 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8473 : : errmsg("column \"%s\" of relation \"%s\" is already an identity column",
8474 : : colName, RelationGetRelationName(rel))));
8475 : :
8476 [ + + ]: 71 : if (attTup->atthasdef)
8477 [ + - ]: 4 : ereport(ERROR,
8478 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8479 : : errmsg("column \"%s\" of relation \"%s\" already has a default value",
8480 : : colName, RelationGetRelationName(rel))));
8481 : :
8482 : 67 : attTup->attidentity = cdef->identity;
8483 : 67 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
8484 : :
8485 [ - + ]: 67 : InvokeObjectPostAlterHook(RelationRelationId,
8486 : : RelationGetRelid(rel),
8487 : : attTup->attnum);
8488 : 67 : ObjectAddressSubSet(address, RelationRelationId,
8489 : : RelationGetRelid(rel), attnum);
8490 : 67 : heap_freetuple(tuple);
8491 : :
8492 : 67 : table_close(attrelation, RowExclusiveLock);
8493 : :
8494 : : /*
8495 : : * Recurse to propagate the identity column to partitions. Identity is
8496 : : * not inherited in regular inheritance children.
8497 : : */
8498 [ + - + + ]: 67 : if (recurse && ispartitioned)
8499 : : {
8500 : : List *children;
8501 : : ListCell *lc;
8502 : :
8503 : 6 : children = find_inheritance_children(RelationGetRelid(rel), lockmode);
8504 : :
8505 [ + + + + : 10 : foreach(lc, children)
+ + ]
8506 : : {
8507 : : Relation childrel;
8508 : :
8509 : 4 : childrel = table_open(lfirst_oid(lc), NoLock);
8510 : 4 : ATExecAddIdentity(childrel, colName, def, lockmode, recurse, true);
8511 : 4 : table_close(childrel, NoLock);
8512 : : }
8513 : : }
8514 : :
8515 : 67 : return address;
8516 : : }
8517 : :
8518 : : /*
8519 : : * ALTER TABLE ALTER COLUMN SET { GENERATED or sequence options }
8520 : : *
8521 : : * Return the address of the affected column.
8522 : : */
8523 : : static ObjectAddress
8524 : 49 : ATExecSetIdentity(Relation rel, const char *colName, Node *def,
8525 : : LOCKMODE lockmode, bool recurse, bool recursing)
8526 : : {
8527 : : ListCell *option;
8528 : 49 : DefElem *generatedEl = NULL;
8529 : : HeapTuple tuple;
8530 : : Form_pg_attribute attTup;
8531 : : AttrNumber attnum;
8532 : : Relation attrelation;
8533 : : ObjectAddress address;
8534 : : bool ispartitioned;
8535 : :
8536 : 49 : ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
8537 [ + + + + ]: 49 : if (ispartitioned && !recurse)
8538 [ + - ]: 4 : ereport(ERROR,
8539 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8540 : : errmsg("cannot change identity column of only the partitioned table"),
8541 : : errhint("Do not specify the ONLY keyword.")));
8542 : :
8543 [ + + + + ]: 45 : if (rel->rd_rel->relispartition && !recursing)
8544 [ + - ]: 8 : ereport(ERROR,
8545 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8546 : : errmsg("cannot change identity column of a partition"));
8547 : :
8548 [ + + + + : 66 : foreach(option, castNode(List, def))
+ + ]
8549 : : {
8550 : 29 : DefElem *defel = lfirst_node(DefElem, option);
8551 : :
8552 [ + - ]: 29 : if (strcmp(defel->defname, "generated") == 0)
8553 : : {
8554 [ - + ]: 29 : if (generatedEl)
8555 [ # # ]: 0 : ereport(ERROR,
8556 : : (errcode(ERRCODE_SYNTAX_ERROR),
8557 : : errmsg("conflicting or redundant options")));
8558 : 29 : generatedEl = defel;
8559 : : }
8560 : : else
8561 [ # # ]: 0 : elog(ERROR, "option \"%s\" not recognized",
8562 : : defel->defname);
8563 : : }
8564 : :
8565 : : /*
8566 : : * Even if there is nothing to change here, we run all the checks. There
8567 : : * will be a subsequent ALTER SEQUENCE that relies on everything being
8568 : : * there.
8569 : : */
8570 : :
8571 : 37 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
8572 : 37 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
8573 [ - + ]: 37 : if (!HeapTupleIsValid(tuple))
8574 [ # # ]: 0 : ereport(ERROR,
8575 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8576 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8577 : : colName, RelationGetRelationName(rel))));
8578 : :
8579 : 37 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8580 : 37 : attnum = attTup->attnum;
8581 : :
8582 [ - + ]: 37 : if (attnum <= 0)
8583 [ # # ]: 0 : ereport(ERROR,
8584 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8585 : : errmsg("cannot alter system column \"%s\"",
8586 : : colName)));
8587 : :
8588 [ + + ]: 37 : if (!attTup->attidentity)
8589 [ + - ]: 4 : ereport(ERROR,
8590 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8591 : : errmsg("column \"%s\" of relation \"%s\" is not an identity column",
8592 : : colName, RelationGetRelationName(rel))));
8593 : :
8594 [ + + ]: 33 : if (generatedEl)
8595 : : {
8596 : 29 : attTup->attidentity = defGetInt32(generatedEl);
8597 : 29 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
8598 : :
8599 [ - + ]: 29 : InvokeObjectPostAlterHook(RelationRelationId,
8600 : : RelationGetRelid(rel),
8601 : : attTup->attnum);
8602 : 29 : ObjectAddressSubSet(address, RelationRelationId,
8603 : : RelationGetRelid(rel), attnum);
8604 : : }
8605 : : else
8606 : 4 : address = InvalidObjectAddress;
8607 : :
8608 : 33 : heap_freetuple(tuple);
8609 : 33 : table_close(attrelation, RowExclusiveLock);
8610 : :
8611 : : /*
8612 : : * Recurse to propagate the identity change to partitions. Identity is not
8613 : : * inherited in regular inheritance children.
8614 : : */
8615 [ + + + - : 33 : if (generatedEl && recurse && ispartitioned)
+ + ]
8616 : : {
8617 : : List *children;
8618 : : ListCell *lc;
8619 : :
8620 : 4 : children = find_inheritance_children(RelationGetRelid(rel), lockmode);
8621 : :
8622 [ + - + + : 12 : foreach(lc, children)
+ + ]
8623 : : {
8624 : : Relation childrel;
8625 : :
8626 : 8 : childrel = table_open(lfirst_oid(lc), NoLock);
8627 : 8 : ATExecSetIdentity(childrel, colName, def, lockmode, recurse, true);
8628 : 8 : table_close(childrel, NoLock);
8629 : : }
8630 : : }
8631 : :
8632 : 33 : return address;
8633 : : }
8634 : :
8635 : : /*
8636 : : * ALTER TABLE ALTER COLUMN DROP IDENTITY
8637 : : *
8638 : : * Return the address of the affected column.
8639 : : */
8640 : : static ObjectAddress
8641 : 61 : ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode,
8642 : : bool recurse, bool recursing)
8643 : : {
8644 : : HeapTuple tuple;
8645 : : Form_pg_attribute attTup;
8646 : : AttrNumber attnum;
8647 : : Relation attrelation;
8648 : : ObjectAddress address;
8649 : : Oid seqid;
8650 : : ObjectAddress seqaddress;
8651 : : bool ispartitioned;
8652 : :
8653 : 61 : ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
8654 [ + + + + ]: 61 : if (ispartitioned && !recurse)
8655 [ + - ]: 4 : ereport(ERROR,
8656 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8657 : : errmsg("cannot drop identity from a column of only the partitioned table"),
8658 : : errhint("Do not specify the ONLY keyword.")));
8659 : :
8660 [ + + + + ]: 57 : if (rel->rd_rel->relispartition && !recursing)
8661 [ + - ]: 4 : ereport(ERROR,
8662 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8663 : : errmsg("cannot drop identity from a column of a partition"));
8664 : :
8665 : 53 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
8666 : 53 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
8667 [ - + ]: 53 : if (!HeapTupleIsValid(tuple))
8668 [ # # ]: 0 : ereport(ERROR,
8669 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8670 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8671 : : colName, RelationGetRelationName(rel))));
8672 : :
8673 : 53 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8674 : 53 : attnum = attTup->attnum;
8675 : :
8676 [ - + ]: 53 : if (attnum <= 0)
8677 [ # # ]: 0 : ereport(ERROR,
8678 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8679 : : errmsg("cannot alter system column \"%s\"",
8680 : : colName)));
8681 : :
8682 [ + + ]: 53 : if (!attTup->attidentity)
8683 : : {
8684 [ + + ]: 8 : if (!missing_ok)
8685 [ + - ]: 4 : ereport(ERROR,
8686 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8687 : : errmsg("column \"%s\" of relation \"%s\" is not an identity column",
8688 : : colName, RelationGetRelationName(rel))));
8689 : : else
8690 : : {
8691 [ + - ]: 4 : ereport(NOTICE,
8692 : : (errmsg("column \"%s\" of relation \"%s\" is not an identity column, skipping",
8693 : : colName, RelationGetRelationName(rel))));
8694 : 4 : heap_freetuple(tuple);
8695 : 4 : table_close(attrelation, RowExclusiveLock);
8696 : 4 : return InvalidObjectAddress;
8697 : : }
8698 : : }
8699 : :
8700 : 45 : attTup->attidentity = '\0';
8701 : 45 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
8702 : :
8703 [ - + ]: 45 : InvokeObjectPostAlterHook(RelationRelationId,
8704 : : RelationGetRelid(rel),
8705 : : attTup->attnum);
8706 : 45 : ObjectAddressSubSet(address, RelationRelationId,
8707 : : RelationGetRelid(rel), attnum);
8708 : 45 : heap_freetuple(tuple);
8709 : :
8710 : 45 : table_close(attrelation, RowExclusiveLock);
8711 : :
8712 : : /*
8713 : : * Recurse to drop the identity from column in partitions. Identity is
8714 : : * not inherited in regular inheritance children so ignore them.
8715 : : */
8716 [ + - + + ]: 45 : if (recurse && ispartitioned)
8717 : : {
8718 : : List *children;
8719 : : ListCell *lc;
8720 : :
8721 : 4 : children = find_inheritance_children(RelationGetRelid(rel), lockmode);
8722 : :
8723 [ + - + + : 8 : foreach(lc, children)
+ + ]
8724 : : {
8725 : : Relation childrel;
8726 : :
8727 : 4 : childrel = table_open(lfirst_oid(lc), NoLock);
8728 : 4 : ATExecDropIdentity(childrel, colName, false, lockmode, recurse, true);
8729 : 4 : table_close(childrel, NoLock);
8730 : : }
8731 : : }
8732 : :
8733 [ + + ]: 45 : if (!recursing)
8734 : : {
8735 : : /* drop the internal sequence */
8736 : 21 : seqid = getIdentitySequence(rel, attnum, false);
8737 : 21 : deleteDependencyRecordsForClass(RelationRelationId, seqid,
8738 : : RelationRelationId, DEPENDENCY_INTERNAL);
8739 : 21 : CommandCounterIncrement();
8740 : 21 : seqaddress.classId = RelationRelationId;
8741 : 21 : seqaddress.objectId = seqid;
8742 : 21 : seqaddress.objectSubId = 0;
8743 : 21 : performDeletion(&seqaddress, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
8744 : : }
8745 : :
8746 : 45 : return address;
8747 : : }
8748 : :
8749 : : /*
8750 : : * ALTER TABLE ALTER COLUMN SET EXPRESSION
8751 : : *
8752 : : * Return the address of the affected column.
8753 : : */
8754 : : static ObjectAddress
8755 : 193 : ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
8756 : : Node *newExpr, LOCKMODE lockmode)
8757 : : {
8758 : : HeapTuple tuple;
8759 : : Form_pg_attribute attTup;
8760 : : AttrNumber attnum;
8761 : : char attgenerated;
8762 : : bool rewrite;
8763 : : Oid attrdefoid;
8764 : : ObjectAddress address;
8765 : : Expr *defval;
8766 : : NewColumnValue *newval;
8767 : : RawColumnDefault *rawEnt;
8768 : :
8769 : 193 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
8770 [ - + ]: 193 : if (!HeapTupleIsValid(tuple))
8771 [ # # ]: 0 : ereport(ERROR,
8772 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8773 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8774 : : colName, RelationGetRelationName(rel))));
8775 : :
8776 : 193 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8777 : :
8778 : 193 : attnum = attTup->attnum;
8779 [ - + ]: 193 : if (attnum <= 0)
8780 [ # # ]: 0 : ereport(ERROR,
8781 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8782 : : errmsg("cannot alter system column \"%s\"",
8783 : : colName)));
8784 : :
8785 : 193 : attgenerated = attTup->attgenerated;
8786 [ + + ]: 193 : if (!attgenerated)
8787 [ + - ]: 8 : ereport(ERROR,
8788 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8789 : : errmsg("column \"%s\" of relation \"%s\" is not a generated column",
8790 : : colName, RelationGetRelationName(rel))));
8791 : :
8792 [ + + + + ]: 185 : if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL && attTup->attnotnull)
8793 : 16 : tab->verify_new_notnull = true;
8794 : :
8795 : : /*
8796 : : * We need to prevent this because a change of expression could affect a
8797 : : * row filter and inject expressions that are not permitted in a row
8798 : : * filter. XXX We could try to have a more precise check to catch only
8799 : : * publications with row filters, or even re-verify the row filter
8800 : : * expressions.
8801 : : */
8802 [ + + + + ]: 273 : if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL &&
8803 : 88 : GetRelationIncludedPublications(RelationGetRelid(rel)) != NIL)
8804 [ + - ]: 4 : ereport(ERROR,
8805 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8806 : : errmsg("ALTER TABLE / SET EXPRESSION is not supported for virtual generated columns in tables that are part of a publication"),
8807 : : errdetail("Column \"%s\" of relation \"%s\" is a virtual generated column.",
8808 : : colName, RelationGetRelationName(rel))));
8809 : :
8810 : 181 : rewrite = (attgenerated == ATTRIBUTE_GENERATED_STORED);
8811 : :
8812 : 181 : ReleaseSysCache(tuple);
8813 : :
8814 [ + + ]: 181 : if (rewrite)
8815 : : {
8816 : : /*
8817 : : * Clear all the missing values if we're rewriting the table, since
8818 : : * this renders them pointless.
8819 : : */
8820 : 97 : RelationClearMissing(rel);
8821 : :
8822 : : /* make sure we don't conflict with later attribute modifications */
8823 : 97 : CommandCounterIncrement();
8824 : : }
8825 : :
8826 : : /*
8827 : : * Find everything that depends on the column (constraints, indexes, etc),
8828 : : * and record enough information to let us recreate the objects.
8829 : : */
8830 : 181 : RememberAllDependentForRebuilding(tab, AT_SetExpression, rel, attnum, colName);
8831 : :
8832 : : /*
8833 : : * Find whole-row referenced objects that depend on the column
8834 : : * (constraints, indexes, etc.), and record enough information to let us
8835 : : * recreate the objects.
8836 : : */
8837 : 181 : RememberWholeRowDependentForRebuilding(tab, AT_SetExpression, rel);
8838 : :
8839 : : /*
8840 : : * Drop the dependency records of the GENERATED expression, in particular
8841 : : * its INTERNAL dependency on the column, which would otherwise cause
8842 : : * dependency.c to refuse to perform the deletion.
8843 : : */
8844 : 181 : attrdefoid = GetAttrDefaultOid(RelationGetRelid(rel), attnum);
8845 [ - + ]: 181 : if (!OidIsValid(attrdefoid))
8846 [ # # ]: 0 : elog(ERROR, "could not find attrdef tuple for relation %u attnum %d",
8847 : : RelationGetRelid(rel), attnum);
8848 : 181 : (void) deleteDependencyRecordsFor(AttrDefaultRelationId, attrdefoid, false);
8849 : :
8850 : : /* Make above changes visible */
8851 : 181 : CommandCounterIncrement();
8852 : :
8853 : : /*
8854 : : * Get rid of the GENERATED expression itself. We use RESTRICT here for
8855 : : * safety, but at present we do not expect anything to depend on the
8856 : : * expression.
8857 : : */
8858 : 181 : RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT,
8859 : : false, false);
8860 : :
8861 : : /* Prepare to store the new expression, in the catalogs */
8862 : 181 : rawEnt = palloc_object(RawColumnDefault);
8863 : 181 : rawEnt->attnum = attnum;
8864 : 181 : rawEnt->raw_default = newExpr;
8865 : 181 : rawEnt->generated = attgenerated;
8866 : :
8867 : : /* Store the generated expression */
8868 : 181 : AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
8869 : : false, true, false, NULL);
8870 : :
8871 : : /* Make above new expression visible */
8872 : 181 : CommandCounterIncrement();
8873 : :
8874 [ + + ]: 181 : if (rewrite)
8875 : : {
8876 : : /* Prepare for table rewrite */
8877 : 97 : defval = (Expr *) build_column_default(rel, attnum);
8878 : :
8879 : 97 : newval = palloc0_object(NewColumnValue);
8880 : 97 : newval->attnum = attnum;
8881 : 97 : newval->expr = expression_planner(defval);
8882 : 97 : newval->is_generated = true;
8883 : :
8884 : 97 : tab->newvals = lappend(tab->newvals, newval);
8885 : 97 : tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
8886 : : }
8887 : :
8888 : : /* Drop any pg_statistic entry for the column */
8889 : 181 : RemoveStatistics(RelationGetRelid(rel), attnum);
8890 : :
8891 [ - + ]: 181 : InvokeObjectPostAlterHook(RelationRelationId,
8892 : : RelationGetRelid(rel), attnum);
8893 : :
8894 : 181 : ObjectAddressSubSet(address, RelationRelationId,
8895 : : RelationGetRelid(rel), attnum);
8896 : 181 : return address;
8897 : : }
8898 : :
8899 : : /*
8900 : : * ALTER TABLE ALTER COLUMN DROP EXPRESSION
8901 : : */
8902 : : static void
8903 : 97 : ATPrepDropExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, LOCKMODE lockmode)
8904 : : {
8905 : : /*
8906 : : * Reject ONLY if there are child tables -- but only, of course, at the
8907 : : * top of the tree, otherwise it'd be impossible to run this command with
8908 : : * trees deeper than two levels. Caller already got lock.
8909 : : */
8910 [ + + + + : 109 : if (!recurse && !recursing &&
+ - ]
8911 : 12 : find_inheritance_children(RelationGetRelid(rel), NoLock))
8912 [ + - ]: 12 : ereport(ERROR,
8913 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8914 : : errmsg("ALTER TABLE / DROP EXPRESSION must be applied to child tables too")));
8915 : :
8916 : : /*
8917 : : * Cannot drop generation expression from inherited columns.
8918 : : */
8919 [ + + ]: 85 : if (!recursing)
8920 : : {
8921 : : HeapTuple tuple;
8922 : : Form_pg_attribute attTup;
8923 : :
8924 : 57 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), cmd->name);
8925 [ - + ]: 57 : if (!HeapTupleIsValid(tuple))
8926 [ # # ]: 0 : ereport(ERROR,
8927 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8928 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8929 : : cmd->name, RelationGetRelationName(rel))));
8930 : :
8931 : 57 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8932 : :
8933 [ + + ]: 57 : if (attTup->attinhcount > 0)
8934 [ + - ]: 12 : ereport(ERROR,
8935 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8936 : : errmsg("cannot drop generation expression from inherited column")));
8937 : : }
8938 : 73 : }
8939 : :
8940 : : /*
8941 : : * Return the address of the affected column.
8942 : : */
8943 : : static ObjectAddress
8944 : 65 : ATExecDropExpression(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode)
8945 : : {
8946 : : HeapTuple tuple;
8947 : : Form_pg_attribute attTup;
8948 : : AttrNumber attnum;
8949 : : Relation attrelation;
8950 : : Oid attrdefoid;
8951 : : ObjectAddress address;
8952 : :
8953 : 65 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
8954 : 65 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
8955 [ - + ]: 65 : if (!HeapTupleIsValid(tuple))
8956 [ # # ]: 0 : ereport(ERROR,
8957 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8958 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8959 : : colName, RelationGetRelationName(rel))));
8960 : :
8961 : 65 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8962 : 65 : attnum = attTup->attnum;
8963 : :
8964 [ - + ]: 65 : if (attnum <= 0)
8965 [ # # ]: 0 : ereport(ERROR,
8966 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8967 : : errmsg("cannot alter system column \"%s\"",
8968 : : colName)));
8969 : :
8970 : : /*
8971 : : * TODO: This could be done, but it would need a table rewrite to
8972 : : * materialize the generated values. Note that for the time being, we
8973 : : * still error with missing_ok, so that we don't silently leave the column
8974 : : * as generated.
8975 : : */
8976 [ + + ]: 65 : if (attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
8977 [ + - ]: 8 : ereport(ERROR,
8978 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8979 : : errmsg("ALTER TABLE / DROP EXPRESSION is not supported for virtual generated columns"),
8980 : : errdetail("Column \"%s\" of relation \"%s\" is a virtual generated column.",
8981 : : colName, RelationGetRelationName(rel))));
8982 : :
8983 [ + + ]: 57 : if (!attTup->attgenerated)
8984 : : {
8985 [ + + ]: 16 : if (!missing_ok)
8986 [ + - ]: 8 : ereport(ERROR,
8987 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8988 : : errmsg("column \"%s\" of relation \"%s\" is not a generated column",
8989 : : colName, RelationGetRelationName(rel))));
8990 : : else
8991 : : {
8992 [ + - ]: 8 : ereport(NOTICE,
8993 : : (errmsg("column \"%s\" of relation \"%s\" is not a generated column, skipping",
8994 : : colName, RelationGetRelationName(rel))));
8995 : 8 : heap_freetuple(tuple);
8996 : 8 : table_close(attrelation, RowExclusiveLock);
8997 : 8 : return InvalidObjectAddress;
8998 : : }
8999 : : }
9000 : :
9001 : : /*
9002 : : * Mark the column as no longer generated. (The atthasdef flag needs to
9003 : : * get cleared too, but RemoveAttrDefault will handle that.)
9004 : : */
9005 : 41 : attTup->attgenerated = '\0';
9006 : 41 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
9007 : :
9008 [ - + ]: 41 : InvokeObjectPostAlterHook(RelationRelationId,
9009 : : RelationGetRelid(rel),
9010 : : attnum);
9011 : 41 : heap_freetuple(tuple);
9012 : :
9013 : 41 : table_close(attrelation, RowExclusiveLock);
9014 : :
9015 : : /*
9016 : : * Drop the dependency records of the GENERATED expression, in particular
9017 : : * its INTERNAL dependency on the column, which would otherwise cause
9018 : : * dependency.c to refuse to perform the deletion.
9019 : : */
9020 : 41 : attrdefoid = GetAttrDefaultOid(RelationGetRelid(rel), attnum);
9021 [ - + ]: 41 : if (!OidIsValid(attrdefoid))
9022 [ # # ]: 0 : elog(ERROR, "could not find attrdef tuple for relation %u attnum %d",
9023 : : RelationGetRelid(rel), attnum);
9024 : 41 : (void) deleteDependencyRecordsFor(AttrDefaultRelationId, attrdefoid, false);
9025 : :
9026 : : /* Make above changes visible */
9027 : 41 : CommandCounterIncrement();
9028 : :
9029 : : /*
9030 : : * Get rid of the GENERATED expression itself. We use RESTRICT here for
9031 : : * safety, but at present we do not expect anything to depend on the
9032 : : * default.
9033 : : */
9034 : 41 : RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT,
9035 : : false, false);
9036 : :
9037 : 41 : ObjectAddressSubSet(address, RelationRelationId,
9038 : : RelationGetRelid(rel), attnum);
9039 : 41 : return address;
9040 : : }
9041 : :
9042 : : /*
9043 : : * ALTER TABLE ALTER COLUMN SET STATISTICS
9044 : : *
9045 : : * Return value is the address of the modified column
9046 : : */
9047 : : static ObjectAddress
9048 : 111 : ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newValue, LOCKMODE lockmode)
9049 : : {
9050 : 111 : int newtarget = 0;
9051 : : bool newtarget_default;
9052 : : Relation attrelation;
9053 : : HeapTuple tuple,
9054 : : newtuple;
9055 : : Form_pg_attribute attrtuple;
9056 : : AttrNumber attnum;
9057 : : ObjectAddress address;
9058 : : Datum repl_val[Natts_pg_attribute];
9059 : : bool repl_null[Natts_pg_attribute];
9060 : : bool repl_repl[Natts_pg_attribute];
9061 : :
9062 : : /*
9063 : : * We allow referencing columns by numbers only for indexes, since table
9064 : : * column numbers could contain gaps if columns are later dropped.
9065 : : */
9066 [ + + ]: 111 : if (rel->rd_rel->relkind != RELKIND_INDEX &&
9067 [ + - - + ]: 69 : rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
9068 : : !colName)
9069 [ # # ]: 0 : ereport(ERROR,
9070 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9071 : : errmsg("cannot refer to non-index column by number")));
9072 : :
9073 : : /* -1 was used in previous versions for the default setting */
9074 [ + + + + ]: 111 : if (newValue && intVal(newValue) != -1)
9075 : : {
9076 : 80 : newtarget = intVal(newValue);
9077 : 80 : newtarget_default = false;
9078 : : }
9079 : : else
9080 : 31 : newtarget_default = true;
9081 : :
9082 [ + + ]: 111 : if (!newtarget_default)
9083 : : {
9084 : : /*
9085 : : * Limit target to a sane range
9086 : : */
9087 [ - + ]: 80 : if (newtarget < 0)
9088 : : {
9089 [ # # ]: 0 : ereport(ERROR,
9090 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
9091 : : errmsg("statistics target %d is too low",
9092 : : newtarget)));
9093 : : }
9094 [ - + ]: 80 : else if (newtarget > MAX_STATISTICS_TARGET)
9095 : : {
9096 : 0 : newtarget = MAX_STATISTICS_TARGET;
9097 [ # # ]: 0 : ereport(WARNING,
9098 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
9099 : : errmsg("lowering statistics target to %d",
9100 : : newtarget)));
9101 : : }
9102 : : }
9103 : :
9104 : 111 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
9105 : :
9106 [ + + ]: 111 : if (colName)
9107 : : {
9108 : 69 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
9109 : :
9110 [ + + ]: 69 : if (!HeapTupleIsValid(tuple))
9111 [ + - ]: 8 : ereport(ERROR,
9112 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
9113 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
9114 : : colName, RelationGetRelationName(rel))));
9115 : : }
9116 : : else
9117 : : {
9118 : 42 : tuple = SearchSysCacheAttNum(RelationGetRelid(rel), colNum);
9119 : :
9120 [ + + ]: 42 : if (!HeapTupleIsValid(tuple))
9121 [ + - ]: 8 : ereport(ERROR,
9122 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
9123 : : errmsg("column number %d of relation \"%s\" does not exist",
9124 : : colNum, RelationGetRelationName(rel))));
9125 : : }
9126 : :
9127 : 95 : attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
9128 : :
9129 : 95 : attnum = attrtuple->attnum;
9130 [ - + ]: 95 : if (attnum <= 0)
9131 [ # # ]: 0 : ereport(ERROR,
9132 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9133 : : errmsg("cannot alter system column \"%s\"",
9134 : : colName)));
9135 : :
9136 : : /*
9137 : : * Prevent this as long as the ANALYZE code skips virtual generated
9138 : : * columns.
9139 : : */
9140 [ - + ]: 95 : if (attrtuple->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
9141 [ # # ]: 0 : ereport(ERROR,
9142 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9143 : : errmsg("cannot alter statistics on virtual generated column \"%s\"",
9144 : : colName)));
9145 : :
9146 [ + + ]: 95 : if (rel->rd_rel->relkind == RELKIND_INDEX ||
9147 [ - + ]: 61 : rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
9148 : : {
9149 [ + + ]: 34 : if (attnum > rel->rd_index->indnkeyatts)
9150 [ + - ]: 4 : ereport(ERROR,
9151 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9152 : : errmsg("cannot alter statistics on included column \"%s\" of index \"%s\"",
9153 : : NameStr(attrtuple->attname), RelationGetRelationName(rel))));
9154 [ + + ]: 30 : else if (rel->rd_index->indkey.values[attnum - 1] != 0)
9155 [ + - ]: 12 : ereport(ERROR,
9156 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9157 : : errmsg("cannot alter statistics on non-expression column \"%s\" of index \"%s\"",
9158 : : NameStr(attrtuple->attname), RelationGetRelationName(rel)),
9159 : : errhint("Alter statistics on table column instead.")));
9160 : : }
9161 : :
9162 : : /* Build new tuple. */
9163 : 79 : memset(repl_null, false, sizeof(repl_null));
9164 : 79 : memset(repl_repl, false, sizeof(repl_repl));
9165 [ + + ]: 79 : if (!newtarget_default)
9166 : 48 : repl_val[Anum_pg_attribute_attstattarget - 1] = Int16GetDatum(newtarget);
9167 : : else
9168 : 31 : repl_null[Anum_pg_attribute_attstattarget - 1] = true;
9169 : 79 : repl_repl[Anum_pg_attribute_attstattarget - 1] = true;
9170 : 79 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(attrelation),
9171 : : repl_val, repl_null, repl_repl);
9172 : 79 : CatalogTupleUpdate(attrelation, &tuple->t_self, newtuple);
9173 : :
9174 [ - + ]: 79 : InvokeObjectPostAlterHook(RelationRelationId,
9175 : : RelationGetRelid(rel),
9176 : : attrtuple->attnum);
9177 : 79 : ObjectAddressSubSet(address, RelationRelationId,
9178 : : RelationGetRelid(rel), attnum);
9179 : :
9180 : 79 : heap_freetuple(newtuple);
9181 : :
9182 : 79 : ReleaseSysCache(tuple);
9183 : :
9184 : 79 : table_close(attrelation, RowExclusiveLock);
9185 : :
9186 : 79 : return address;
9187 : : }
9188 : :
9189 : : /*
9190 : : * Return value is the address of the modified column
9191 : : */
9192 : : static ObjectAddress
9193 : 21 : ATExecSetOptions(Relation rel, const char *colName, Node *options,
9194 : : bool isReset, LOCKMODE lockmode)
9195 : : {
9196 : : Relation attrelation;
9197 : : HeapTuple tuple,
9198 : : newtuple;
9199 : : Form_pg_attribute attrtuple;
9200 : : AttrNumber attnum;
9201 : : Datum datum,
9202 : : newOptions;
9203 : : bool isnull;
9204 : : ObjectAddress address;
9205 : : Datum repl_val[Natts_pg_attribute];
9206 : : bool repl_null[Natts_pg_attribute];
9207 : : bool repl_repl[Natts_pg_attribute];
9208 : :
9209 : 21 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
9210 : :
9211 : 21 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
9212 : :
9213 [ - + ]: 21 : if (!HeapTupleIsValid(tuple))
9214 [ # # ]: 0 : ereport(ERROR,
9215 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
9216 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
9217 : : colName, RelationGetRelationName(rel))));
9218 : 21 : attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
9219 : :
9220 : 21 : attnum = attrtuple->attnum;
9221 [ - + ]: 21 : if (attnum <= 0)
9222 [ # # ]: 0 : ereport(ERROR,
9223 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9224 : : errmsg("cannot alter system column \"%s\"",
9225 : : colName)));
9226 : :
9227 : : /* Generate new proposed attoptions (text array) */
9228 : 21 : datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions,
9229 : : &isnull);
9230 [ + + ]: 21 : newOptions = transformRelOptions(isnull ? (Datum) 0 : datum,
9231 : : castNode(List, options), NULL, NULL,
9232 : : false, isReset);
9233 : : /* Validate new options */
9234 : 21 : (void) attribute_reloptions(newOptions, true);
9235 : :
9236 : : /* Build new tuple. */
9237 : 21 : memset(repl_null, false, sizeof(repl_null));
9238 : 21 : memset(repl_repl, false, sizeof(repl_repl));
9239 [ + - ]: 21 : if (newOptions != (Datum) 0)
9240 : 21 : repl_val[Anum_pg_attribute_attoptions - 1] = newOptions;
9241 : : else
9242 : 0 : repl_null[Anum_pg_attribute_attoptions - 1] = true;
9243 : 21 : repl_repl[Anum_pg_attribute_attoptions - 1] = true;
9244 : 21 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(attrelation),
9245 : : repl_val, repl_null, repl_repl);
9246 : :
9247 : : /* Update system catalog. */
9248 : 21 : CatalogTupleUpdate(attrelation, &newtuple->t_self, newtuple);
9249 : :
9250 [ - + ]: 21 : InvokeObjectPostAlterHook(RelationRelationId,
9251 : : RelationGetRelid(rel),
9252 : : attrtuple->attnum);
9253 : 21 : ObjectAddressSubSet(address, RelationRelationId,
9254 : : RelationGetRelid(rel), attnum);
9255 : :
9256 : 21 : heap_freetuple(newtuple);
9257 : :
9258 : 21 : ReleaseSysCache(tuple);
9259 : :
9260 : 21 : table_close(attrelation, RowExclusiveLock);
9261 : :
9262 : 21 : return address;
9263 : : }
9264 : :
9265 : : /*
9266 : : * Helper function for ATExecSetStorage and ATExecSetCompression
9267 : : *
9268 : : * Set the attstorage and/or attcompression fields for index columns
9269 : : * associated with the specified table column.
9270 : : */
9271 : : static void
9272 : 218 : SetIndexStorageProperties(Relation rel, Relation attrelation,
9273 : : AttrNumber attnum,
9274 : : bool setstorage, char newstorage,
9275 : : bool setcompression, char newcompression,
9276 : : LOCKMODE lockmode)
9277 : : {
9278 : : ListCell *lc;
9279 : :
9280 [ + + + + : 276 : foreach(lc, RelationGetIndexList(rel))
+ + ]
9281 : : {
9282 : 58 : Oid indexoid = lfirst_oid(lc);
9283 : : Relation indrel;
9284 : 58 : AttrNumber indattnum = 0;
9285 : : HeapTuple tuple;
9286 : :
9287 : 58 : indrel = index_open(indexoid, lockmode);
9288 : :
9289 [ + + ]: 97 : for (int i = 0; i < indrel->rd_index->indnatts; i++)
9290 : : {
9291 [ + + ]: 62 : if (indrel->rd_index->indkey.values[i] == attnum)
9292 : : {
9293 : 23 : indattnum = i + 1;
9294 : 23 : break;
9295 : : }
9296 : : }
9297 : :
9298 [ + + ]: 58 : if (indattnum == 0)
9299 : : {
9300 : 35 : index_close(indrel, lockmode);
9301 : 35 : continue;
9302 : : }
9303 : :
9304 : 23 : tuple = SearchSysCacheCopyAttNum(RelationGetRelid(indrel), indattnum);
9305 : :
9306 [ + - ]: 23 : if (HeapTupleIsValid(tuple))
9307 : : {
9308 : 23 : Form_pg_attribute attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
9309 : :
9310 [ + + ]: 23 : if (setstorage)
9311 : 15 : attrtuple->attstorage = newstorage;
9312 : :
9313 [ + + ]: 23 : if (setcompression)
9314 : 8 : attrtuple->attcompression = newcompression;
9315 : :
9316 : 23 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
9317 : :
9318 [ - + ]: 23 : InvokeObjectPostAlterHook(RelationRelationId,
9319 : : RelationGetRelid(rel),
9320 : : attrtuple->attnum);
9321 : :
9322 : 23 : heap_freetuple(tuple);
9323 : : }
9324 : :
9325 : 23 : index_close(indrel, lockmode);
9326 : : }
9327 : 218 : }
9328 : :
9329 : : /*
9330 : : * ALTER TABLE ALTER COLUMN SET STORAGE
9331 : : *
9332 : : * Return value is the address of the modified column
9333 : : */
9334 : : static ObjectAddress
9335 : 182 : ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode)
9336 : : {
9337 : : Relation attrelation;
9338 : : HeapTuple tuple;
9339 : : Form_pg_attribute attrtuple;
9340 : : AttrNumber attnum;
9341 : : ObjectAddress address;
9342 : :
9343 : 182 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
9344 : :
9345 : 182 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
9346 : :
9347 [ + + ]: 182 : if (!HeapTupleIsValid(tuple))
9348 [ + - ]: 8 : ereport(ERROR,
9349 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
9350 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
9351 : : colName, RelationGetRelationName(rel))));
9352 : 174 : attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
9353 : :
9354 : 174 : attnum = attrtuple->attnum;
9355 [ - + ]: 174 : if (attnum <= 0)
9356 [ # # ]: 0 : ereport(ERROR,
9357 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9358 : : errmsg("cannot alter system column \"%s\"",
9359 : : colName)));
9360 : :
9361 : 174 : attrtuple->attstorage = GetAttributeStorage(attrtuple->atttypid, strVal(newValue));
9362 : :
9363 : 174 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
9364 : :
9365 [ - + ]: 174 : InvokeObjectPostAlterHook(RelationRelationId,
9366 : : RelationGetRelid(rel),
9367 : : attrtuple->attnum);
9368 : :
9369 : : /*
9370 : : * Apply the change to indexes as well (only for simple index columns,
9371 : : * matching behavior of index.c ConstructTupleDescriptor()).
9372 : : */
9373 : 174 : SetIndexStorageProperties(rel, attrelation, attnum,
9374 : 174 : true, attrtuple->attstorage,
9375 : : false, 0,
9376 : : lockmode);
9377 : :
9378 : 174 : heap_freetuple(tuple);
9379 : :
9380 : 174 : table_close(attrelation, RowExclusiveLock);
9381 : :
9382 : 174 : ObjectAddressSubSet(address, RelationRelationId,
9383 : : RelationGetRelid(rel), attnum);
9384 : 174 : return address;
9385 : : }
9386 : :
9387 : :
9388 : : /*
9389 : : * ALTER TABLE DROP COLUMN
9390 : : *
9391 : : * DROP COLUMN cannot use the normal ALTER TABLE recursion mechanism,
9392 : : * because we have to decide at runtime whether to recurse or not depending
9393 : : * on whether attinhcount goes to zero or not. (We can't check this in a
9394 : : * static pre-pass because it won't handle multiple inheritance situations
9395 : : * correctly.)
9396 : : */
9397 : : static void
9398 : 1143 : ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
9399 : : AlterTableCmd *cmd, LOCKMODE lockmode,
9400 : : AlterTableUtilityContext *context)
9401 : : {
9402 [ + + + + ]: 1143 : if (rel->rd_rel->reloftype && !recursing)
9403 [ + - ]: 4 : ereport(ERROR,
9404 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
9405 : : errmsg("cannot drop column from typed table")));
9406 : :
9407 [ + + ]: 1139 : if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
9408 : 54 : ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);
9409 : :
9410 [ + + ]: 1135 : if (recurse)
9411 : 977 : cmd->recurse = true;
9412 : 1135 : }
9413 : :
9414 : : /*
9415 : : * Drops column 'colName' from relation 'rel' and returns the address of the
9416 : : * dropped column. The column is also dropped (or marked as no longer
9417 : : * inherited from relation) from the relation's inheritance children, if any.
9418 : : *
9419 : : * In the recursive invocations for inheritance child relations, instead of
9420 : : * dropping the column directly (if to be dropped at all), its object address
9421 : : * is added to 'addrs', which must be non-NULL in such invocations. All
9422 : : * columns are dropped at the same time after all the children have been
9423 : : * checked recursively.
9424 : : */
9425 : : static ObjectAddress
9426 : 1508 : ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
9427 : : DropBehavior behavior,
9428 : : bool recurse, bool recursing,
9429 : : bool missing_ok, LOCKMODE lockmode,
9430 : : ObjectAddresses *addrs)
9431 : : {
9432 : : HeapTuple tuple;
9433 : : Form_pg_attribute targetatt;
9434 : : AttrNumber attnum;
9435 : : List *children;
9436 : : ObjectAddress object;
9437 : : bool is_expr;
9438 : :
9439 : : /* At top level, permission check was done in ATPrepCmd, else do it */
9440 [ + + ]: 1508 : if (recursing)
9441 : 373 : ATSimplePermissions(AT_DropColumn, rel,
9442 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
9443 : :
9444 : : /* Initialize addrs on the first invocation */
9445 : : Assert(!recursing || addrs != NULL);
9446 : :
9447 : : /* since this function recurses, it could be driven to stack overflow */
9448 : 1508 : check_stack_depth();
9449 : :
9450 [ + + ]: 1508 : if (!recursing)
9451 : 1135 : addrs = new_object_addresses();
9452 : :
9453 : : /*
9454 : : * get the number of the attribute
9455 : : */
9456 : 1508 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
9457 [ + + ]: 1508 : if (!HeapTupleIsValid(tuple))
9458 : : {
9459 [ + + ]: 40 : if (!missing_ok)
9460 : : {
9461 [ + - ]: 24 : ereport(ERROR,
9462 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
9463 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
9464 : : colName, RelationGetRelationName(rel))));
9465 : : }
9466 : : else
9467 : : {
9468 [ + - ]: 16 : ereport(NOTICE,
9469 : : (errmsg("column \"%s\" of relation \"%s\" does not exist, skipping",
9470 : : colName, RelationGetRelationName(rel))));
9471 : 16 : return InvalidObjectAddress;
9472 : : }
9473 : : }
9474 : 1468 : targetatt = (Form_pg_attribute) GETSTRUCT(tuple);
9475 : :
9476 : 1468 : attnum = targetatt->attnum;
9477 : :
9478 : : /* Can't drop a system attribute */
9479 [ + + ]: 1468 : if (attnum <= 0)
9480 [ + - ]: 4 : ereport(ERROR,
9481 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9482 : : errmsg("cannot drop system column \"%s\"",
9483 : : colName)));
9484 : :
9485 : : /*
9486 : : * Don't drop inherited columns, unless recursing (presumably from a drop
9487 : : * of the parent column)
9488 : : */
9489 [ + + + + ]: 1464 : if (targetatt->attinhcount > 0 && !recursing)
9490 [ + - ]: 32 : ereport(ERROR,
9491 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
9492 : : errmsg("cannot drop inherited column \"%s\"",
9493 : : colName)));
9494 : :
9495 : : /*
9496 : : * Don't drop columns used in the partition key, either. (If we let this
9497 : : * go through, the key column's dependencies would cause a cascaded drop
9498 : : * of the whole table, which is surely not what the user expected.)
9499 : : */
9500 [ + + ]: 1432 : if (has_partition_attrs(rel,
9501 : : bms_make_singleton(attnum - FirstLowInvalidHeapAttributeNumber),
9502 : : &is_expr))
9503 [ + - ]: 20 : ereport(ERROR,
9504 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
9505 : : errmsg("cannot drop column \"%s\" because it is part of the partition key of relation \"%s\"",
9506 : : colName, RelationGetRelationName(rel))));
9507 : :
9508 : 1412 : ReleaseSysCache(tuple);
9509 : :
9510 : : /*
9511 : : * Propagate to children as appropriate. Unlike most other ALTER
9512 : : * routines, we have to do this one level of recursion at a time; we can't
9513 : : * use find_all_inheritors to do it in one pass.
9514 : : */
9515 : : children =
9516 : 1412 : find_inheritance_children(RelationGetRelid(rel), lockmode);
9517 : :
9518 [ + + ]: 1412 : if (children)
9519 : : {
9520 : : Relation attr_rel;
9521 : : ListCell *child;
9522 : :
9523 : : /*
9524 : : * In case of a partitioned table, the column must be dropped from the
9525 : : * partitions as well.
9526 : : */
9527 [ + + + + ]: 204 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
9528 [ + - ]: 4 : ereport(ERROR,
9529 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
9530 : : errmsg("cannot drop column from only the partitioned table when partitions exist"),
9531 : : errhint("Do not specify the ONLY keyword.")));
9532 : :
9533 : 200 : attr_rel = table_open(AttributeRelationId, RowExclusiveLock);
9534 [ + - + + : 593 : foreach(child, children)
+ + ]
9535 : : {
9536 : 397 : Oid childrelid = lfirst_oid(child);
9537 : : Relation childrel;
9538 : : Form_pg_attribute childatt;
9539 : :
9540 : : /* find_inheritance_children already got lock */
9541 : 397 : childrel = table_open(childrelid, NoLock);
9542 : 397 : CheckAlterTableIsSafe(childrel);
9543 : :
9544 : 397 : tuple = SearchSysCacheCopyAttName(childrelid, colName);
9545 [ - + ]: 397 : if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
9546 [ # # ]: 0 : elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
9547 : : colName, childrelid);
9548 : 397 : childatt = (Form_pg_attribute) GETSTRUCT(tuple);
9549 : :
9550 [ - + ]: 397 : if (childatt->attinhcount <= 0) /* shouldn't happen */
9551 [ # # ]: 0 : elog(ERROR, "relation %u has non-inherited attribute \"%s\"",
9552 : : childrelid, colName);
9553 : :
9554 [ + + ]: 397 : if (recurse)
9555 : : {
9556 : : /*
9557 : : * If the child column has other definition sources, just
9558 : : * decrement its inheritance count; if not, recurse to delete
9559 : : * it.
9560 : : */
9561 [ + - + + ]: 381 : if (childatt->attinhcount == 1 && !childatt->attislocal)
9562 : : {
9563 : : /* Time to delete this child column, too */
9564 : 373 : ATExecDropColumn(wqueue, childrel, colName,
9565 : : behavior, true, true,
9566 : : false, lockmode, addrs);
9567 : : }
9568 : : else
9569 : : {
9570 : : /* Child column must survive my deletion */
9571 : 8 : childatt->attinhcount--;
9572 : :
9573 : 8 : CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple);
9574 : :
9575 : : /* Make update visible */
9576 : 8 : CommandCounterIncrement();
9577 : : }
9578 : : }
9579 : : else
9580 : : {
9581 : : /*
9582 : : * If we were told to drop ONLY in this table (no recursion),
9583 : : * we need to mark the inheritors' attributes as locally
9584 : : * defined rather than inherited.
9585 : : */
9586 : 16 : childatt->attinhcount--;
9587 : 16 : childatt->attislocal = true;
9588 : :
9589 : 16 : CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple);
9590 : :
9591 : : /* Make update visible */
9592 : 16 : CommandCounterIncrement();
9593 : : }
9594 : :
9595 : 393 : heap_freetuple(tuple);
9596 : :
9597 : 393 : table_close(childrel, NoLock);
9598 : : }
9599 : 196 : table_close(attr_rel, RowExclusiveLock);
9600 : : }
9601 : :
9602 : : /* Add object to delete */
9603 : 1404 : object.classId = RelationRelationId;
9604 : 1404 : object.objectId = RelationGetRelid(rel);
9605 : 1404 : object.objectSubId = attnum;
9606 : 1404 : add_exact_object_address(&object, addrs);
9607 : :
9608 [ + + ]: 1404 : if (!recursing)
9609 : : {
9610 : : /* Recursion has ended, drop everything that was collected */
9611 : 1035 : performMultipleDeletions(addrs, behavior, 0);
9612 : 991 : free_object_addresses(addrs);
9613 : : }
9614 : :
9615 : 1360 : return object;
9616 : : }
9617 : :
9618 : : /*
9619 : : * Prepare to add a primary key on a table, by adding not-null constraints
9620 : : * on all columns.
9621 : : *
9622 : : * The not-null constraints for a primary key must cover the whole inheritance
9623 : : * hierarchy (failing to ensure that leads to funny corner cases). For the
9624 : : * normal case where we're asked to recurse, this routine checks if the
9625 : : * not-null constraints exist already, and if not queues a requirement for
9626 : : * them to be created by phase 2.
9627 : : *
9628 : : * For the case where we're asked not to recurse, we verify that a not-null
9629 : : * constraint exists on each column of each (direct) child table, throwing an
9630 : : * error if not. Not throwing an error would also work, because a not-null
9631 : : * constraint would be created anyway, but it'd cause a silent scan of the
9632 : : * child table to verify absence of nulls. We prefer to let the user know so
9633 : : * that they can add the constraint manually without having to hold
9634 : : * AccessExclusiveLock while at it.
9635 : : *
9636 : : * However, it's also important that we do not acquire locks on children if
9637 : : * the not-null constraints already exist on the parent, to avoid risking
9638 : : * deadlocks during parallel pg_restore of PKs on partitioned tables.
9639 : : */
9640 : : static void
9641 : 10446 : ATPrepAddPrimaryKey(List **wqueue, Relation rel, AlterTableCmd *cmd,
9642 : : bool recurse, LOCKMODE lockmode,
9643 : : AlterTableUtilityContext *context)
9644 : : {
9645 : : Constraint *pkconstr;
9646 : 10446 : List *children = NIL;
9647 : 10446 : bool got_children = false;
9648 : :
9649 : 10446 : pkconstr = castNode(Constraint, cmd->def);
9650 [ + + ]: 10446 : if (pkconstr->contype != CONSTR_PRIMARY)
9651 : 6185 : return;
9652 : :
9653 : : /* Verify that columns are not-null, or request that they be made so */
9654 [ + + + + : 9139 : foreach_node(String, column, pkconstr->keys)
+ + ]
9655 : : {
9656 : : AlterTableCmd *newcmd;
9657 : : Constraint *nnconstr;
9658 : : HeapTuple tuple;
9659 : :
9660 : : /*
9661 : : * First check if a suitable constraint exists. If it does, we don't
9662 : : * need to request another one. We do need to bail out if it's not
9663 : : * valid, though.
9664 : : */
9665 : 657 : tuple = findNotNullConstraint(RelationGetRelid(rel), strVal(column));
9666 [ + + ]: 657 : if (tuple != NULL)
9667 : : {
9668 : 322 : verifyNotNullPKCompatible(tuple, strVal(column));
9669 : :
9670 : : /* All good with this one; don't request another */
9671 : 314 : heap_freetuple(tuple);
9672 : 314 : continue;
9673 : : }
9674 [ + + ]: 335 : else if (!recurse)
9675 : : {
9676 : : /*
9677 : : * No constraint on this column. Asked not to recurse, we won't
9678 : : * create one here, but verify that all children have one.
9679 : : */
9680 [ + - ]: 24 : if (!got_children)
9681 : : {
9682 : 24 : children = find_inheritance_children(RelationGetRelid(rel),
9683 : : lockmode);
9684 : : /* only search for children on the first time through */
9685 : 24 : got_children = true;
9686 : : }
9687 : :
9688 [ + - + + : 48 : foreach_oid(childrelid, children)
+ + ]
9689 : : {
9690 : : HeapTuple tup;
9691 : :
9692 : 24 : tup = findNotNullConstraint(childrelid, strVal(column));
9693 [ + + ]: 24 : if (!tup)
9694 [ + - ]: 4 : ereport(ERROR,
9695 : : errmsg("column \"%s\" of table \"%s\" is not marked NOT NULL",
9696 : : strVal(column), get_rel_name(childrelid)));
9697 : : /* verify it's good enough */
9698 : 20 : verifyNotNullPKCompatible(tup, strVal(column));
9699 : : }
9700 : : }
9701 : :
9702 : : /* This column is not already not-null, so add it to the queue */
9703 : 323 : nnconstr = makeNotNullConstraint(column);
9704 : :
9705 : 323 : newcmd = makeNode(AlterTableCmd);
9706 : 323 : newcmd->subtype = AT_AddConstraint;
9707 : : /* note we force recurse=true here; see above */
9708 : 323 : newcmd->recurse = true;
9709 : 323 : newcmd->def = (Node *) nnconstr;
9710 : :
9711 : 323 : ATPrepCmd(wqueue, rel, newcmd, true, false, lockmode, context);
9712 : : }
9713 : : }
9714 : :
9715 : : /*
9716 : : * Verify whether the given not-null constraint is compatible with a
9717 : : * primary key. If not, an error is thrown.
9718 : : */
9719 : : static void
9720 : 342 : verifyNotNullPKCompatible(HeapTuple tuple, const char *colname)
9721 : : {
9722 : 342 : Form_pg_constraint conForm = (Form_pg_constraint) GETSTRUCT(tuple);
9723 : :
9724 [ - + ]: 342 : if (conForm->contype != CONSTRAINT_NOTNULL)
9725 [ # # ]: 0 : elog(ERROR, "constraint %u is not a not-null constraint", conForm->oid);
9726 : :
9727 : : /* a NO INHERIT constraint is no good */
9728 [ + + ]: 342 : if (conForm->connoinherit)
9729 [ + - ]: 8 : ereport(ERROR,
9730 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
9731 : : errmsg("cannot create primary key on column \"%s\"", colname),
9732 : : /*- translator: fourth %s is a constraint characteristic such as NOT VALID */
9733 : : errdetail("The constraint \"%s\" on column \"%s\" of table \"%s\", marked %s, is incompatible with a primary key.",
9734 : : NameStr(conForm->conname), colname,
9735 : : get_rel_name(conForm->conrelid), "NO INHERIT"),
9736 : : errhint("You might need to make the existing constraint inheritable using %s.",
9737 : : "ALTER TABLE ... ALTER CONSTRAINT ... INHERIT"));
9738 : :
9739 : : /* an unvalidated constraint is no good */
9740 [ + + ]: 334 : if (!conForm->convalidated)
9741 [ + - ]: 8 : ereport(ERROR,
9742 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
9743 : : errmsg("cannot create primary key on column \"%s\"", colname),
9744 : : /*- translator: fourth %s is a constraint characteristic such as NOT VALID */
9745 : : errdetail("The constraint \"%s\" on column \"%s\" of table \"%s\", marked %s, is incompatible with a primary key.",
9746 : : NameStr(conForm->conname), colname,
9747 : : get_rel_name(conForm->conrelid), "NOT VALID"),
9748 : : errhint("You might need to validate it using %s.",
9749 : : "ALTER TABLE ... VALIDATE CONSTRAINT"));
9750 : 326 : }
9751 : :
9752 : : /*
9753 : : * ALTER TABLE ADD INDEX
9754 : : *
9755 : : * There is no such command in the grammar, but parse_utilcmd.c converts
9756 : : * UNIQUE and PRIMARY KEY constraints into AT_AddIndex subcommands. This lets
9757 : : * us schedule creation of the index at the appropriate time during ALTER.
9758 : : *
9759 : : * Return value is the address of the new index.
9760 : : */
9761 : : static ObjectAddress
9762 : 1108 : ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
9763 : : IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
9764 : : {
9765 : : bool check_rights;
9766 : : bool skip_build;
9767 : : bool quiet;
9768 : : ObjectAddress address;
9769 : :
9770 : : Assert(IsA(stmt, IndexStmt));
9771 : : Assert(!stmt->concurrent);
9772 : :
9773 : : /* The IndexStmt has already been through transformIndexStmt */
9774 : : Assert(stmt->transformed);
9775 : :
9776 : : /* suppress schema rights check when rebuilding existing index */
9777 : 1108 : check_rights = !is_rebuild;
9778 : : /* skip index build if phase 3 will do it or we're reusing an old one */
9779 [ + + + + ]: 1108 : skip_build = tab->rewrite > 0 || RelFileNumberIsValid(stmt->oldNumber);
9780 : : /* suppress notices when rebuilding existing index */
9781 : 1108 : quiet = is_rebuild;
9782 : :
9783 : 1108 : address = DefineIndex(NULL,
9784 : : RelationGetRelid(rel),
9785 : : stmt,
9786 : : InvalidOid, /* no predefined OID */
9787 : : InvalidOid, /* no parent index */
9788 : : InvalidOid, /* no parent constraint */
9789 : : -1, /* total_parts unknown */
9790 : : true, /* is_alter_table */
9791 : : check_rights,
9792 : : false, /* check_not_in_use - we did it already */
9793 : : skip_build,
9794 : : quiet);
9795 : :
9796 : : /*
9797 : : * If TryReuseIndex() stashed a relfilenumber for us, we used it for the
9798 : : * new index instead of building from scratch. Restore associated fields.
9799 : : * This may store InvalidSubTransactionId in both fields, in which case
9800 : : * relcache.c will assume it can rebuild the relcache entry. Hence, do
9801 : : * this after the CCI that made catalog rows visible to any rebuild. The
9802 : : * DROP of the old edition of this index will have scheduled the storage
9803 : : * for deletion at commit, so cancel that pending deletion.
9804 : : */
9805 [ + + ]: 995 : if (RelFileNumberIsValid(stmt->oldNumber))
9806 : : {
9807 : 49 : Relation irel = index_open(address.objectId, NoLock);
9808 : :
9809 : 49 : irel->rd_createSubid = stmt->oldCreateSubid;
9810 : 49 : irel->rd_firstRelfilelocatorSubid = stmt->oldFirstRelfilelocatorSubid;
9811 : 49 : RelationPreserveStorage(irel->rd_locator, true);
9812 : 49 : index_close(irel, NoLock);
9813 : : }
9814 : :
9815 : 995 : return address;
9816 : : }
9817 : :
9818 : : /*
9819 : : * ALTER TABLE ADD STATISTICS
9820 : : *
9821 : : * This is no such command in the grammar, but we use this internally to add
9822 : : * AT_ReAddStatistics subcommands to rebuild extended statistics after a table
9823 : : * column type change.
9824 : : */
9825 : : static ObjectAddress
9826 : 57 : ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
9827 : : CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
9828 : : {
9829 : : ObjectAddress address;
9830 : :
9831 : : Assert(IsA(stmt, CreateStatsStmt));
9832 : :
9833 : : /* The CreateStatsStmt has already been through transformStatsStmt */
9834 : : Assert(stmt->transformed);
9835 : :
9836 : : /* The owner must be set to the original statistics owner */
9837 : : Assert(OidIsValid(stmt->owner));
9838 : :
9839 : 57 : address = CreateStatistics(list_make1_oid(RelationGetRelid(rel)),
9840 : 57 : stmt, !is_rebuild);
9841 : :
9842 : 57 : return address;
9843 : : }
9844 : :
9845 : : /*
9846 : : * ALTER TABLE ADD CONSTRAINT USING INDEX
9847 : : *
9848 : : * Returns the address of the new constraint.
9849 : : */
9850 : : static ObjectAddress
9851 : 6653 : ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
9852 : : IndexStmt *stmt, LOCKMODE lockmode)
9853 : : {
9854 : 6653 : Oid index_oid = stmt->indexOid;
9855 : : Relation indexRel;
9856 : : char *indexName;
9857 : : IndexInfo *indexInfo;
9858 : : char *constraintName;
9859 : : char constraintType;
9860 : : ObjectAddress address;
9861 : : uint16 flags;
9862 : :
9863 : : Assert(IsA(stmt, IndexStmt));
9864 : : Assert(OidIsValid(index_oid));
9865 : : Assert(stmt->isconstraint);
9866 : :
9867 : : /*
9868 : : * Doing this on partitioned tables is not a simple feature to implement,
9869 : : * so let's punt for now.
9870 : : */
9871 [ + + ]: 6653 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
9872 [ + - ]: 4 : ereport(ERROR,
9873 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9874 : : errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX is not supported on partitioned tables")));
9875 : :
9876 : 6649 : indexRel = index_open(index_oid, AccessShareLock);
9877 : :
9878 : 6649 : indexName = pstrdup(RelationGetRelationName(indexRel));
9879 : :
9880 : 6649 : indexInfo = BuildIndexInfo(indexRel);
9881 : :
9882 : : /* this should have been checked at parse time */
9883 [ - + ]: 6649 : if (!indexInfo->ii_Unique)
9884 [ # # ]: 0 : elog(ERROR, "index \"%s\" is not unique", indexName);
9885 : :
9886 : : /*
9887 : : * Determine name to assign to constraint. We require a constraint to
9888 : : * have the same name as the underlying index; therefore, use the index's
9889 : : * existing name as the default constraint name, and if the user
9890 : : * explicitly gives some other name for the constraint, rename the index
9891 : : * to match.
9892 : : */
9893 : 6649 : constraintName = stmt->idxname;
9894 [ + + ]: 6649 : if (constraintName == NULL)
9895 : 6632 : constraintName = indexName;
9896 [ + + ]: 17 : else if (strcmp(constraintName, indexName) != 0)
9897 : : {
9898 [ + - ]: 13 : ereport(NOTICE,
9899 : : (errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index \"%s\" to \"%s\"",
9900 : : indexName, constraintName)));
9901 : 13 : RenameRelationInternal(index_oid, constraintName, false, true);
9902 : : }
9903 : :
9904 : : /* Extra checks needed if making primary key */
9905 [ + + ]: 6649 : if (stmt->primary)
9906 : 3721 : index_check_primary_key(rel, indexInfo, true, stmt);
9907 : :
9908 : : /* Note we currently don't support EXCLUSION constraints here */
9909 [ + + ]: 6645 : if (stmt->primary)
9910 : 3717 : constraintType = CONSTRAINT_PRIMARY;
9911 : : else
9912 : 2928 : constraintType = CONSTRAINT_UNIQUE;
9913 : :
9914 : : /* Create the catalog entries for the constraint */
9915 : 6645 : flags = INDEX_CONSTR_CREATE_UPDATE_INDEX |
9916 : : INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS |
9917 [ - + ]: 13290 : (stmt->initdeferred ? INDEX_CONSTR_CREATE_INIT_DEFERRED : 0) |
9918 [ - + ]: 6645 : (stmt->deferrable ? INDEX_CONSTR_CREATE_DEFERRABLE : 0) |
9919 : 6645 : (stmt->primary ? INDEX_CONSTR_CREATE_MARK_AS_PRIMARY : 0);
9920 : :
9921 : 6645 : address = index_constraint_create(rel,
9922 : : index_oid,
9923 : : InvalidOid,
9924 : : indexInfo,
9925 : : constraintName,
9926 : : constraintType,
9927 : : flags,
9928 : : allowSystemTableMods,
9929 : : false); /* is_internal */
9930 : :
9931 : 6645 : index_close(indexRel, NoLock);
9932 : :
9933 : 6645 : return address;
9934 : : }
9935 : :
9936 : : /*
9937 : : * ALTER TABLE ADD CONSTRAINT
9938 : : *
9939 : : * Return value is the address of the new constraint; if no constraint was
9940 : : * added, InvalidObjectAddress is returned.
9941 : : */
9942 : : static ObjectAddress
9943 : 8381 : ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
9944 : : Constraint *newConstraint, bool recurse, bool is_readd,
9945 : : LOCKMODE lockmode)
9946 : : {
9947 : 8381 : ObjectAddress address = InvalidObjectAddress;
9948 : :
9949 : : Assert(IsA(newConstraint, Constraint));
9950 : :
9951 : : /*
9952 : : * Currently, we only expect to see CONSTR_CHECK, CONSTR_NOTNULL and
9953 : : * CONSTR_FOREIGN nodes arriving here (see the preprocessing done in
9954 : : * parse_utilcmd.c).
9955 : : */
9956 [ + + - ]: 8381 : switch (newConstraint->contype)
9957 : : {
9958 : 6509 : case CONSTR_CHECK:
9959 : : case CONSTR_NOTNULL:
9960 : : address =
9961 : 6509 : ATAddCheckNNConstraint(wqueue, tab, rel,
9962 : : newConstraint, recurse, false, is_readd,
9963 : : lockmode);
9964 : 6409 : break;
9965 : :
9966 : 1872 : case CONSTR_FOREIGN:
9967 : :
9968 : : /*
9969 : : * Assign or validate constraint name
9970 : : */
9971 [ + + ]: 1872 : if (newConstraint->conname)
9972 : : {
9973 [ - + ]: 761 : if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
9974 : : RelationGetRelid(rel),
9975 : 761 : newConstraint->conname))
9976 [ # # ]: 0 : ereport(ERROR,
9977 : : (errcode(ERRCODE_DUPLICATE_OBJECT),
9978 : : errmsg("constraint \"%s\" for relation \"%s\" already exists",
9979 : : newConstraint->conname,
9980 : : RelationGetRelationName(rel))));
9981 : : }
9982 : : else
9983 : 1111 : newConstraint->conname =
9984 : 1111 : ChooseConstraintName(RelationGetRelationName(rel),
9985 : 1111 : ChooseForeignKeyConstraintNameAddition(newConstraint->fk_attrs),
9986 : : "fkey",
9987 : 1111 : RelationGetNamespace(rel),
9988 : : NIL);
9989 : :
9990 : 1872 : address = ATAddForeignKeyConstraint(wqueue, tab, rel,
9991 : : newConstraint,
9992 : : recurse, false,
9993 : : lockmode);
9994 : 1503 : break;
9995 : :
9996 : 0 : default:
9997 [ # # ]: 0 : elog(ERROR, "unrecognized constraint type: %d",
9998 : : (int) newConstraint->contype);
9999 : : }
10000 : :
10001 : 7912 : return address;
10002 : : }
10003 : :
10004 : : /*
10005 : : * Generate the column-name portion of the constraint name for a new foreign
10006 : : * key given the list of column names that reference the referenced
10007 : : * table. This will be passed to ChooseConstraintName along with the parent
10008 : : * table name and the "fkey" suffix.
10009 : : *
10010 : : * We know that less than NAMEDATALEN characters will actually be used, so we
10011 : : * can truncate the result once we've generated that many.
10012 : : *
10013 : : * XXX see also ChooseExtendedStatisticNameAddition and
10014 : : * ChooseIndexNameAddition.
10015 : : */
10016 : : static char *
10017 : 1111 : ChooseForeignKeyConstraintNameAddition(List *colnames)
10018 : : {
10019 : : char buf[NAMEDATALEN * 2];
10020 : 1111 : int buflen = 0;
10021 : : ListCell *lc;
10022 : :
10023 : 1111 : buf[0] = '\0';
10024 [ + - + + : 2516 : foreach(lc, colnames)
+ + ]
10025 : : {
10026 : 1405 : const char *name = strVal(lfirst(lc));
10027 : :
10028 [ + + ]: 1405 : if (buflen > 0)
10029 : 294 : buf[buflen++] = '_'; /* insert _ between names */
10030 : :
10031 : : /*
10032 : : * At this point we have buflen <= NAMEDATALEN. name should be less
10033 : : * than NAMEDATALEN already, but use strlcpy for paranoia.
10034 : : */
10035 : 1405 : strlcpy(buf + buflen, name, NAMEDATALEN);
10036 : 1405 : buflen += strlen(buf + buflen);
10037 [ - + ]: 1405 : if (buflen >= NAMEDATALEN)
10038 : 0 : break;
10039 : : }
10040 : 1111 : return pstrdup(buf);
10041 : : }
10042 : :
10043 : : /*
10044 : : * Add a check or not-null constraint to a single table and its children.
10045 : : * Returns the address of the constraint added to the parent relation,
10046 : : * if one gets added, or InvalidObjectAddress otherwise.
10047 : : *
10048 : : * Subroutine for ATExecAddConstraint.
10049 : : *
10050 : : * We must recurse to child tables during execution, rather than using
10051 : : * ALTER TABLE's normal prep-time recursion. The reason is that all the
10052 : : * constraints *must* be given the same name, else they won't be seen as
10053 : : * related later. If the user didn't explicitly specify a name, then
10054 : : * AddRelationNewConstraints would normally assign different names to the
10055 : : * child constraints. To fix that, we must capture the name assigned at
10056 : : * the parent table and pass that down.
10057 : : */
10058 : : static ObjectAddress
10059 : 7192 : ATAddCheckNNConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
10060 : : Constraint *constr, bool recurse, bool recursing,
10061 : : bool is_readd, LOCKMODE lockmode)
10062 : : {
10063 : : List *newcons;
10064 : : ListCell *lcon;
10065 : : List *children;
10066 : : ListCell *child;
10067 : 7192 : ObjectAddress address = InvalidObjectAddress;
10068 : :
10069 : : /* Guard against stack overflow due to overly deep inheritance tree. */
10070 : 7192 : check_stack_depth();
10071 : :
10072 : : /* At top level, permission check was done in ATPrepCmd, else do it */
10073 [ + + ]: 7192 : if (recursing)
10074 : 683 : ATSimplePermissions(AT_AddConstraint, rel,
10075 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
10076 : :
10077 : : /*
10078 : : * Call AddRelationNewConstraints to do the work, making sure it works on
10079 : : * a copy of the Constraint so transformExpr can't modify the original. It
10080 : : * returns a list of cooked constraints.
10081 : : *
10082 : : * If the constraint ends up getting merged with a pre-existing one, it's
10083 : : * omitted from the returned list, which is what we want: we do not need
10084 : : * to do any validation work. That can only happen at child tables,
10085 : : * though, since we disallow merging at the top level.
10086 : : */
10087 : 7192 : newcons = AddRelationNewConstraints(rel, NIL,
10088 : : list_make1(copyObject(constr)),
10089 [ + + ]: 7192 : recursing || is_readd, /* allow_merge */
10090 : : !recursing, /* is_local */
10091 : : is_readd, /* is_internal */
10092 [ + + ]: 14384 : NULL); /* queryString not available
10093 : : * here */
10094 : :
10095 : : /* we don't expect more than one constraint here */
10096 : : Assert(list_length(newcons) <= 1);
10097 : :
10098 : : /* Add each to-be-validated constraint to Phase 3's queue */
10099 [ + + + + : 14062 : foreach(lcon, newcons)
+ + ]
10100 : : {
10101 : 6966 : CookedConstraint *ccon = (CookedConstraint *) lfirst(lcon);
10102 : :
10103 [ + + + + ]: 6966 : if (!ccon->skip_validation && ccon->contype != CONSTR_NOTNULL)
10104 : : {
10105 : : NewConstraint *newcon;
10106 : :
10107 : 783 : newcon = palloc0_object(NewConstraint);
10108 : 783 : newcon->name = ccon->name;
10109 : 783 : newcon->contype = ccon->contype;
10110 : 783 : newcon->qual = ccon->expr;
10111 : :
10112 : 783 : tab->constraints = lappend(tab->constraints, newcon);
10113 : : }
10114 : :
10115 : : /* Save the actually assigned name if it was defaulted */
10116 [ + + ]: 6966 : if (constr->conname == NULL)
10117 : 5528 : constr->conname = ccon->name;
10118 : :
10119 : : /*
10120 : : * If adding a valid not-null constraint, set the pg_attribute flag
10121 : : * and tell phase 3 to verify existing rows, if needed. For an
10122 : : * invalid constraint, just set attnotnull, without queueing
10123 : : * verification.
10124 : : */
10125 [ + + ]: 6966 : if (constr->contype == CONSTR_NOTNULL)
10126 : 5878 : set_attnotnull(wqueue, rel, ccon->attnum,
10127 : 5878 : !constr->skip_validation);
10128 : :
10129 : 6966 : ObjectAddressSet(address, ConstraintRelationId, ccon->conoid);
10130 : : }
10131 : :
10132 : : /* At this point we must have a locked-down name to use */
10133 : : Assert(newcons == NIL || constr->conname != NULL);
10134 : :
10135 : : /* Advance command counter in case same table is visited multiple times */
10136 : 7096 : CommandCounterIncrement();
10137 : :
10138 : : /*
10139 : : * If the constraint got merged with an existing constraint, we're done.
10140 : : * We mustn't recurse to child tables in this case, because they've
10141 : : * already got the constraint, and visiting them again would lead to an
10142 : : * incorrect value for coninhcount.
10143 : : */
10144 [ + + ]: 7096 : if (newcons == NIL)
10145 : 130 : return address;
10146 : :
10147 : : /*
10148 : : * If adding a NO INHERIT constraint, no need to find our children.
10149 : : */
10150 [ + + ]: 6966 : if (constr->is_no_inherit)
10151 : 56 : return address;
10152 : :
10153 : : /*
10154 : : * Propagate to children as appropriate. Unlike most other ALTER
10155 : : * routines, we have to do this one level of recursion at a time; we can't
10156 : : * use find_all_inheritors to do it in one pass.
10157 : : */
10158 : : children =
10159 : 6910 : find_inheritance_children(RelationGetRelid(rel), lockmode);
10160 : :
10161 : : /*
10162 : : * Check if ONLY was specified with ALTER TABLE. If so, allow the
10163 : : * constraint creation only if there are no children currently. Error out
10164 : : * otherwise.
10165 : : */
10166 [ + + + + ]: 6910 : if (!recurse && children != NIL)
10167 [ + - ]: 4 : ereport(ERROR,
10168 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
10169 : : errmsg("constraint must be added to child tables too")));
10170 : :
10171 : : /*
10172 : : * Recurse to create the constraint on each child.
10173 : : */
10174 [ + + + + : 7569 : foreach(child, children)
+ + ]
10175 : : {
10176 : 683 : Oid childrelid = lfirst_oid(child);
10177 : : Relation childrel;
10178 : : AlteredTableInfo *childtab;
10179 : :
10180 : : /* find_inheritance_children already got lock */
10181 : 683 : childrel = table_open(childrelid, NoLock);
10182 : 683 : CheckAlterTableIsSafe(childrel);
10183 : :
10184 : : /* Find or create work queue entry for this table */
10185 : 683 : childtab = ATGetQueueEntry(wqueue, childrel);
10186 : :
10187 : : /* Recurse to this child */
10188 : 683 : ATAddCheckNNConstraint(wqueue, childtab, childrel,
10189 : : constr, recurse, true, is_readd, lockmode);
10190 : :
10191 : 663 : table_close(childrel, NoLock);
10192 : : }
10193 : :
10194 : 6886 : return address;
10195 : : }
10196 : :
10197 : : /*
10198 : : * Add a foreign-key constraint to a single table; return the new constraint's
10199 : : * address.
10200 : : *
10201 : : * Subroutine for ATExecAddConstraint. Must already hold exclusive
10202 : : * lock on the rel, and have done appropriate validity checks for it.
10203 : : * We do permissions checks here, however.
10204 : : *
10205 : : * When the referenced or referencing tables (or both) are partitioned,
10206 : : * multiple pg_constraint rows are required -- one for each partitioned table
10207 : : * and each partition on each side (fortunately, not one for every combination
10208 : : * thereof). We also need action triggers on each leaf partition on the
10209 : : * referenced side, and check triggers on each leaf partition on the
10210 : : * referencing side.
10211 : : */
10212 : : static ObjectAddress
10213 : 1872 : ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
10214 : : Constraint *fkconstraint,
10215 : : bool recurse, bool recursing, LOCKMODE lockmode)
10216 : : {
10217 : : Relation pkrel;
10218 : 1872 : int16 pkattnum[INDEX_MAX_KEYS] = {0};
10219 : 1872 : int16 fkattnum[INDEX_MAX_KEYS] = {0};
10220 : 1872 : Oid pktypoid[INDEX_MAX_KEYS] = {0};
10221 : 1872 : Oid fktypoid[INDEX_MAX_KEYS] = {0};
10222 : 1872 : Oid pkcolloid[INDEX_MAX_KEYS] = {0};
10223 : 1872 : Oid fkcolloid[INDEX_MAX_KEYS] = {0};
10224 : 1872 : Oid opclasses[INDEX_MAX_KEYS] = {0};
10225 : 1872 : Oid pfeqoperators[INDEX_MAX_KEYS] = {0};
10226 : 1872 : Oid ppeqoperators[INDEX_MAX_KEYS] = {0};
10227 : 1872 : Oid ffeqoperators[INDEX_MAX_KEYS] = {0};
10228 : 1872 : int16 fkdelsetcols[INDEX_MAX_KEYS] = {0};
10229 : : bool with_period;
10230 : : bool pk_has_without_overlaps;
10231 : : int i;
10232 : : int numfks,
10233 : : numpks,
10234 : : numfkdelsetcols;
10235 : : Oid indexOid;
10236 : : bool old_check_ok;
10237 : : ObjectAddress address;
10238 : 1872 : ListCell *old_pfeqop_item = list_head(fkconstraint->old_conpfeqop);
10239 : :
10240 : : /*
10241 : : * Grab ShareRowExclusiveLock on the pk table, so that someone doesn't
10242 : : * delete rows out from under us.
10243 : : */
10244 [ + + ]: 1872 : if (OidIsValid(fkconstraint->old_pktable_oid))
10245 : 48 : pkrel = table_open(fkconstraint->old_pktable_oid, ShareRowExclusiveLock);
10246 : : else
10247 : 1824 : pkrel = table_openrv(fkconstraint->pktable, ShareRowExclusiveLock);
10248 : :
10249 : : /*
10250 : : * Validity checks (permission checks wait till we have the column
10251 : : * numbers)
10252 : : */
10253 [ + + + + ]: 1868 : if (!recurse && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
10254 [ + - ]: 4 : ereport(ERROR,
10255 : : errcode(ERRCODE_WRONG_OBJECT_TYPE),
10256 : : errmsg("cannot use ONLY for foreign key on partitioned table \"%s\" referencing relation \"%s\"",
10257 : : RelationGetRelationName(rel),
10258 : : RelationGetRelationName(pkrel)));
10259 : :
10260 [ + + ]: 1864 : if (pkrel->rd_rel->relkind != RELKIND_RELATION &&
10261 [ - + ]: 236 : pkrel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
10262 [ # # ]: 0 : ereport(ERROR,
10263 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
10264 : : errmsg("referenced relation \"%s\" is not a table",
10265 : : RelationGetRelationName(pkrel))));
10266 : :
10267 : : /*
10268 : : * Conflict log tables are used internally for logical replication
10269 : : * conflict logging and should not be referenced by foreign keys, as it
10270 : : * could disrupt conflict logging.
10271 : : */
10272 [ + + ]: 1864 : if (IsConflictLogTableClass(pkrel->rd_rel))
10273 [ + - ]: 4 : ereport(ERROR,
10274 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
10275 : : errmsg("cannot reference conflict log table \"%s\"",
10276 : : RelationGetRelationName(pkrel)),
10277 : : errdetail("Conflict log tables are system-managed tables for logical replication conflicts.")));
10278 : :
10279 [ + + + + ]: 1860 : if (!allowSystemTableMods && IsSystemRelation(pkrel))
10280 [ + - ]: 1 : ereport(ERROR,
10281 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
10282 : : errmsg("permission denied: \"%s\" is a system catalog",
10283 : : RelationGetRelationName(pkrel))));
10284 : :
10285 : : /*
10286 : : * References from permanent or unlogged tables to temp tables, and from
10287 : : * permanent tables to unlogged tables, are disallowed because the
10288 : : * referenced data can vanish out from under us. References from temp
10289 : : * tables to any other table type are also disallowed, because other
10290 : : * backends might need to run the RI triggers on the perm table, but they
10291 : : * can't reliably see tuples in the local buffers of other backends.
10292 : : */
10293 [ + + + - ]: 1859 : switch (rel->rd_rel->relpersistence)
10294 : : {
10295 : 1666 : case RELPERSISTENCE_PERMANENT:
10296 [ - + ]: 1666 : if (!RelationIsPermanent(pkrel))
10297 [ # # ]: 0 : ereport(ERROR,
10298 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
10299 : : errmsg("constraints on permanent tables may reference only permanent tables")));
10300 : 1666 : break;
10301 : 8 : case RELPERSISTENCE_UNLOGGED:
10302 [ + - ]: 8 : if (!RelationIsPermanent(pkrel)
10303 [ - + ]: 8 : && pkrel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED)
10304 [ # # ]: 0 : ereport(ERROR,
10305 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
10306 : : errmsg("constraints on unlogged tables may reference only permanent or unlogged tables")));
10307 : 8 : break;
10308 : 185 : case RELPERSISTENCE_TEMP:
10309 [ - + ]: 185 : if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
10310 [ # # ]: 0 : ereport(ERROR,
10311 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
10312 : : errmsg("constraints on temporary tables may reference only temporary tables")));
10313 [ + - - + ]: 185 : if (!pkrel->rd_islocaltemp || !rel->rd_islocaltemp)
10314 [ # # ]: 0 : ereport(ERROR,
10315 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
10316 : : errmsg("constraints on temporary tables must involve temporary tables of this session")));
10317 : 185 : break;
10318 : : }
10319 : :
10320 : : /*
10321 : : * Look up the referencing attributes to make sure they exist, and record
10322 : : * their attnums and type and collation OIDs.
10323 : : */
10324 : 1859 : numfks = transformColumnNameList(RelationGetRelid(rel),
10325 : : fkconstraint->fk_attrs,
10326 : : fkattnum, fktypoid, fkcolloid);
10327 [ + + + + ]: 1839 : with_period = fkconstraint->fk_with_period || fkconstraint->pk_with_period;
10328 [ + + + + ]: 1839 : if (with_period && !fkconstraint->fk_with_period)
10329 [ + - ]: 16 : ereport(ERROR,
10330 : : errcode(ERRCODE_INVALID_FOREIGN_KEY),
10331 : : errmsg("foreign key uses PERIOD on the referenced table but not the referencing table"));
10332 : :
10333 : 1823 : numfkdelsetcols = transformColumnNameList(RelationGetRelid(rel),
10334 : : fkconstraint->fk_del_set_cols,
10335 : : fkdelsetcols, NULL, NULL);
10336 : 1819 : numfkdelsetcols = validateFkOnDeleteSetColumns(numfks, fkattnum,
10337 : : numfkdelsetcols,
10338 : : fkdelsetcols,
10339 : : fkconstraint->fk_del_set_cols);
10340 : :
10341 : : /*
10342 : : * If the attribute list for the referenced table was omitted, lookup the
10343 : : * definition of the primary key and use it. Otherwise, validate the
10344 : : * supplied attribute list. In either case, discover the index OID and
10345 : : * index opclasses, and the attnums and type and collation OIDs of the
10346 : : * attributes.
10347 : : */
10348 [ + + ]: 1815 : if (fkconstraint->pk_attrs == NIL)
10349 : : {
10350 : 900 : numpks = transformFkeyGetPrimaryKey(pkrel, &indexOid,
10351 : : &fkconstraint->pk_attrs,
10352 : : pkattnum, pktypoid, pkcolloid,
10353 : : opclasses, &pk_has_without_overlaps);
10354 : :
10355 : : /* If the primary key uses WITHOUT OVERLAPS, the fk must use PERIOD */
10356 [ + + + + ]: 900 : if (pk_has_without_overlaps && !fkconstraint->fk_with_period)
10357 [ + - ]: 16 : ereport(ERROR,
10358 : : errcode(ERRCODE_INVALID_FOREIGN_KEY),
10359 : : errmsg("foreign key uses PERIOD on the referenced table but not the referencing table"));
10360 : : }
10361 : : else
10362 : : {
10363 : 915 : numpks = transformColumnNameList(RelationGetRelid(pkrel),
10364 : : fkconstraint->pk_attrs,
10365 : : pkattnum, pktypoid, pkcolloid);
10366 : :
10367 : : /* Since we got pk_attrs, one should be a period. */
10368 [ + + + + ]: 895 : if (with_period && !fkconstraint->pk_with_period)
10369 [ + - ]: 16 : ereport(ERROR,
10370 : : errcode(ERRCODE_INVALID_FOREIGN_KEY),
10371 : : errmsg("foreign key uses PERIOD on the referencing table but not the referenced table"));
10372 : :
10373 : : /* Look for an index matching the column list */
10374 : 879 : indexOid = transformFkeyCheckAttrs(pkrel, numpks, pkattnum,
10375 : : with_period, opclasses, &pk_has_without_overlaps);
10376 : : }
10377 : :
10378 : : /*
10379 : : * If the referenced primary key has WITHOUT OVERLAPS, the foreign key
10380 : : * must use PERIOD.
10381 : : */
10382 [ + + + + ]: 1739 : if (pk_has_without_overlaps && !with_period)
10383 [ + - ]: 8 : ereport(ERROR,
10384 : : errcode(ERRCODE_INVALID_FOREIGN_KEY),
10385 : : errmsg("foreign key must use PERIOD when referencing a primary key using WITHOUT OVERLAPS"));
10386 : :
10387 : : /*
10388 : : * Now we can check permissions.
10389 : : */
10390 : 1731 : checkFkeyPermissions(pkrel, pkattnum, numpks);
10391 : :
10392 : : /*
10393 : : * Check some things for generated columns.
10394 : : */
10395 [ + + ]: 4043 : for (i = 0; i < numfks; i++)
10396 : : {
10397 : 2332 : char attgenerated = TupleDescAttr(RelationGetDescr(rel), fkattnum[i] - 1)->attgenerated;
10398 : :
10399 [ + + ]: 2332 : if (attgenerated)
10400 : : {
10401 : : /*
10402 : : * Check restrictions on UPDATE/DELETE actions, per SQL standard
10403 : : */
10404 [ + - ]: 32 : if (fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETNULL ||
10405 [ + - ]: 32 : fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETDEFAULT ||
10406 [ + + ]: 32 : fkconstraint->fk_upd_action == FKCONSTR_ACTION_CASCADE)
10407 [ + - ]: 8 : ereport(ERROR,
10408 : : (errcode(ERRCODE_SYNTAX_ERROR),
10409 : : errmsg("invalid %s action for foreign key constraint containing generated column",
10410 : : "ON UPDATE")));
10411 [ + + ]: 24 : if (fkconstraint->fk_del_action == FKCONSTR_ACTION_SETNULL ||
10412 [ - + ]: 16 : fkconstraint->fk_del_action == FKCONSTR_ACTION_SETDEFAULT)
10413 [ + - ]: 8 : ereport(ERROR,
10414 : : (errcode(ERRCODE_SYNTAX_ERROR),
10415 : : errmsg("invalid %s action for foreign key constraint containing generated column",
10416 : : "ON DELETE")));
10417 : : }
10418 : :
10419 : : /*
10420 : : * FKs on virtual columns are not supported. This would require
10421 : : * various additional support in ri_triggers.c, including special
10422 : : * handling in ri_NullCheck(), ri_KeysEqual(),
10423 : : * RI_FKey_fk_upd_check_required() (since all virtual columns appear
10424 : : * as NULL there). Also not really practical as long as you can't
10425 : : * index virtual columns.
10426 : : */
10427 [ + + ]: 2316 : if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
10428 [ + - ]: 4 : ereport(ERROR,
10429 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
10430 : : errmsg("foreign key constraints on virtual generated columns are not supported")));
10431 : : }
10432 : :
10433 : : /*
10434 : : * Some actions are currently unsupported for foreign keys using PERIOD.
10435 : : */
10436 [ + + ]: 1711 : if (fkconstraint->fk_with_period)
10437 : : {
10438 [ + + ]: 178 : if (fkconstraint->fk_upd_action == FKCONSTR_ACTION_RESTRICT ||
10439 [ + + ]: 170 : fkconstraint->fk_upd_action == FKCONSTR_ACTION_CASCADE ||
10440 [ + + ]: 158 : fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETNULL ||
10441 [ + + ]: 146 : fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETDEFAULT)
10442 [ + - ]: 44 : ereport(ERROR,
10443 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
10444 : : errmsg("unsupported %s action for foreign key constraint using PERIOD",
10445 : : "ON UPDATE"));
10446 : :
10447 [ + + ]: 134 : if (fkconstraint->fk_del_action == FKCONSTR_ACTION_RESTRICT ||
10448 [ + - ]: 130 : fkconstraint->fk_del_action == FKCONSTR_ACTION_CASCADE ||
10449 [ + - ]: 130 : fkconstraint->fk_del_action == FKCONSTR_ACTION_SETNULL ||
10450 [ - + ]: 130 : fkconstraint->fk_del_action == FKCONSTR_ACTION_SETDEFAULT)
10451 [ + - ]: 4 : ereport(ERROR,
10452 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
10453 : : errmsg("unsupported %s action for foreign key constraint using PERIOD",
10454 : : "ON DELETE"));
10455 : : }
10456 : :
10457 : : /*
10458 : : * Look up the equality operators to use in the constraint.
10459 : : *
10460 : : * Note that we have to be careful about the difference between the actual
10461 : : * PK column type and the opclass' declared input type, which might be
10462 : : * only binary-compatible with it. The declared opcintype is the right
10463 : : * thing to probe pg_amop with.
10464 : : */
10465 [ - + ]: 1663 : if (numfks != numpks)
10466 [ # # ]: 0 : ereport(ERROR,
10467 : : (errcode(ERRCODE_INVALID_FOREIGN_KEY),
10468 : : errmsg("number of referencing and referenced columns for foreign key disagree")));
10469 : :
10470 : : /*
10471 : : * On the strength of a previous constraint, we might avoid scanning
10472 : : * tables to validate this one. See below.
10473 : : */
10474 : 1663 : old_check_ok = (fkconstraint->old_conpfeqop != NIL);
10475 : : Assert(!old_check_ok || numfks == list_length(fkconstraint->old_conpfeqop));
10476 : :
10477 [ + + ]: 3623 : for (i = 0; i < numpks; i++)
10478 : : {
10479 : 2120 : Oid pktype = pktypoid[i];
10480 : 2120 : Oid fktype = fktypoid[i];
10481 : : Oid fktyped;
10482 : 2120 : Oid pkcoll = pkcolloid[i];
10483 : 2120 : Oid fkcoll = fkcolloid[i];
10484 : : HeapTuple cla_ht;
10485 : : Form_pg_opclass cla_tup;
10486 : : Oid amid;
10487 : : Oid opfamily;
10488 : : Oid opcintype;
10489 : : bool for_overlaps;
10490 : : CompareType cmptype;
10491 : : Oid pfeqop;
10492 : : Oid ppeqop;
10493 : : Oid ffeqop;
10494 : : int16 eqstrategy;
10495 : : Oid pfeqop_right;
10496 : :
10497 : : /* We need several fields out of the pg_opclass entry */
10498 : 2120 : cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclasses[i]));
10499 [ - + ]: 2120 : if (!HeapTupleIsValid(cla_ht))
10500 [ # # ]: 0 : elog(ERROR, "cache lookup failed for opclass %u", opclasses[i]);
10501 : 2120 : cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
10502 : 2120 : amid = cla_tup->opcmethod;
10503 : 2120 : opfamily = cla_tup->opcfamily;
10504 : 2120 : opcintype = cla_tup->opcintype;
10505 : 2120 : ReleaseSysCache(cla_ht);
10506 : :
10507 : : /*
10508 : : * Get strategy number from index AM.
10509 : : *
10510 : : * For a normal foreign-key constraint, this should not fail, since we
10511 : : * already checked that the index is unique and should therefore have
10512 : : * appropriate equal operators. For a period foreign key, this could
10513 : : * fail if we selected a non-matching exclusion constraint earlier.
10514 : : * (XXX Maybe we should do these lookups earlier so we don't end up
10515 : : * doing that.)
10516 : : */
10517 [ + + + + ]: 2120 : for_overlaps = with_period && i == numpks - 1;
10518 [ + + ]: 2120 : cmptype = for_overlaps ? COMPARE_OVERLAP : COMPARE_EQ;
10519 : 2120 : eqstrategy = IndexAmTranslateCompareType(cmptype, amid, opfamily, true);
10520 [ - + ]: 2120 : if (eqstrategy == InvalidStrategy)
10521 [ # # # # ]: 0 : ereport(ERROR,
10522 : : errcode(ERRCODE_UNDEFINED_OBJECT),
10523 : : for_overlaps
10524 : : ? errmsg("could not identify an overlaps operator for foreign key")
10525 : : : errmsg("could not identify an equality operator for foreign key"),
10526 : : errdetail("Could not translate compare type %d for operator family \"%s\" of access method \"%s\".",
10527 : : cmptype, get_opfamily_name(opfamily, false), get_am_name(amid)));
10528 : :
10529 : : /*
10530 : : * There had better be a primary equality operator for the index.
10531 : : * We'll use it for PK = PK comparisons.
10532 : : */
10533 : 2120 : ppeqop = get_opfamily_member(opfamily, opcintype, opcintype,
10534 : : eqstrategy);
10535 : :
10536 [ - + ]: 2120 : if (!OidIsValid(ppeqop))
10537 [ # # ]: 0 : elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
10538 : : eqstrategy, opcintype, opcintype, opfamily);
10539 : :
10540 : : /*
10541 : : * Are there equality operators that take exactly the FK type? Assume
10542 : : * we should look through any domain here.
10543 : : */
10544 : 2120 : fktyped = getBaseType(fktype);
10545 : :
10546 : 2120 : pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
10547 : : eqstrategy);
10548 [ + + ]: 2120 : if (OidIsValid(pfeqop))
10549 : : {
10550 : 1663 : pfeqop_right = fktyped;
10551 : 1663 : ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
10552 : : eqstrategy);
10553 : : }
10554 : : else
10555 : : {
10556 : : /* keep compiler quiet */
10557 : 457 : pfeqop_right = InvalidOid;
10558 : 457 : ffeqop = InvalidOid;
10559 : : }
10560 : :
10561 [ + + - + ]: 2120 : if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
10562 : : {
10563 : : /*
10564 : : * Otherwise, look for an implicit cast from the FK type to the
10565 : : * opcintype, and if found, use the primary equality operator.
10566 : : * This is a bit tricky because opcintype might be a polymorphic
10567 : : * type such as ANYARRAY or ANYENUM; so what we have to test is
10568 : : * whether the two actual column types can be concurrently cast to
10569 : : * that type. (Otherwise, we'd fail to reject combinations such
10570 : : * as int[] and point[].)
10571 : : */
10572 : : Oid input_typeids[2];
10573 : : Oid target_typeids[2];
10574 : :
10575 : 457 : input_typeids[0] = pktype;
10576 : 457 : input_typeids[1] = fktype;
10577 : 457 : target_typeids[0] = opcintype;
10578 : 457 : target_typeids[1] = opcintype;
10579 [ + + ]: 457 : if (can_coerce_type(2, input_typeids, target_typeids,
10580 : : COERCION_IMPLICIT))
10581 : : {
10582 : 305 : pfeqop = ffeqop = ppeqop;
10583 : 305 : pfeqop_right = opcintype;
10584 : : }
10585 : : }
10586 : :
10587 [ + + - + ]: 2120 : if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
10588 [ + - ]: 152 : ereport(ERROR,
10589 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
10590 : : errmsg("foreign key constraint \"%s\" cannot be implemented",
10591 : : fkconstraint->conname),
10592 : : errdetail("Key columns \"%s\" of the referencing table and \"%s\" of the referenced table "
10593 : : "are of incompatible types: %s and %s.",
10594 : : strVal(list_nth(fkconstraint->fk_attrs, i)),
10595 : : strVal(list_nth(fkconstraint->pk_attrs, i)),
10596 : : format_type_be(fktype),
10597 : : format_type_be(pktype))));
10598 : :
10599 : : /*
10600 : : * This shouldn't be possible, but better check to make sure we have a
10601 : : * consistent state for the check below.
10602 : : */
10603 [ + + + - : 1968 : if ((OidIsValid(pkcoll) && !OidIsValid(fkcoll)) || (!OidIsValid(pkcoll) && OidIsValid(fkcoll)))
+ + - + ]
10604 [ # # ]: 0 : elog(ERROR, "key columns are not both collatable");
10605 : :
10606 [ + + + - ]: 1968 : if (OidIsValid(pkcoll) && OidIsValid(fkcoll))
10607 : : {
10608 : : bool pkcolldet;
10609 : : bool fkcolldet;
10610 : :
10611 : 73 : pkcolldet = get_collation_isdeterministic(pkcoll);
10612 : 73 : fkcolldet = get_collation_isdeterministic(fkcoll);
10613 : :
10614 : : /*
10615 : : * SQL requires that both collations are the same. This is
10616 : : * because we need a consistent notion of equality on both
10617 : : * columns. We relax this by allowing different collations if
10618 : : * they are both deterministic. (This is also for backward
10619 : : * compatibility, because PostgreSQL has always allowed this.)
10620 : : */
10621 [ + + + + : 73 : if ((!pkcolldet || !fkcolldet) && pkcoll != fkcoll)
+ + ]
10622 [ + - ]: 8 : ereport(ERROR,
10623 : : (errcode(ERRCODE_COLLATION_MISMATCH),
10624 : : errmsg("foreign key constraint \"%s\" cannot be implemented", fkconstraint->conname),
10625 : : errdetail("Key columns \"%s\" of the referencing table and \"%s\" of the referenced table "
10626 : : "have incompatible collations: \"%s\" and \"%s\". "
10627 : : "If either collation is nondeterministic, then both collations have to be the same.",
10628 : : strVal(list_nth(fkconstraint->fk_attrs, i)),
10629 : : strVal(list_nth(fkconstraint->pk_attrs, i)),
10630 : : get_collation_name(fkcoll),
10631 : : get_collation_name(pkcoll))));
10632 : : }
10633 : :
10634 [ + + ]: 1960 : if (old_check_ok)
10635 : : {
10636 : : /*
10637 : : * When a pfeqop changes, revalidate the constraint. We could
10638 : : * permit intra-opfamily changes, but that adds subtle complexity
10639 : : * without any concrete benefit for core types. We need not
10640 : : * assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
10641 : : */
10642 : 4 : old_check_ok = (pfeqop == lfirst_oid(old_pfeqop_item));
10643 : 4 : old_pfeqop_item = lnext(fkconstraint->old_conpfeqop,
10644 : : old_pfeqop_item);
10645 : : }
10646 [ + + ]: 1960 : if (old_check_ok)
10647 : : {
10648 : : Oid old_fktype;
10649 : : Oid new_fktype;
10650 : : CoercionPathType old_pathtype;
10651 : : CoercionPathType new_pathtype;
10652 : : Oid old_castfunc;
10653 : : Oid new_castfunc;
10654 : : Oid old_fkcoll;
10655 : : Oid new_fkcoll;
10656 : 4 : Form_pg_attribute attr = TupleDescAttr(tab->oldDesc,
10657 : 4 : fkattnum[i] - 1);
10658 : :
10659 : : /*
10660 : : * Identify coercion pathways from each of the old and new FK-side
10661 : : * column types to the right (foreign) operand type of the pfeqop.
10662 : : * We may assume that pg_constraint.conkey is not changing.
10663 : : */
10664 : 4 : old_fktype = attr->atttypid;
10665 : 4 : new_fktype = fktype;
10666 : 4 : old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
10667 : : &old_castfunc);
10668 : 4 : new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
10669 : : &new_castfunc);
10670 : :
10671 : 4 : old_fkcoll = attr->attcollation;
10672 : 4 : new_fkcoll = fkcoll;
10673 : :
10674 : : /*
10675 : : * Upon a change to the cast from the FK column to its pfeqop
10676 : : * operand, revalidate the constraint. For this evaluation, a
10677 : : * binary coercion cast is equivalent to no cast at all. While
10678 : : * type implementors should design implicit casts with an eye
10679 : : * toward consistency of operations like equality, we cannot
10680 : : * assume here that they have done so.
10681 : : *
10682 : : * A function with a polymorphic argument could change behavior
10683 : : * arbitrarily in response to get_fn_expr_argtype(). Therefore,
10684 : : * when the cast destination is polymorphic, we only avoid
10685 : : * revalidation if the input type has not changed at all. Given
10686 : : * just the core data types and operator classes, this requirement
10687 : : * prevents no would-be optimizations.
10688 : : *
10689 : : * If the cast converts from a base type to a domain thereon, then
10690 : : * that domain type must be the opcintype of the unique index.
10691 : : * Necessarily, the primary key column must then be of the domain
10692 : : * type. Since the constraint was previously valid, all values on
10693 : : * the foreign side necessarily exist on the primary side and in
10694 : : * turn conform to the domain. Consequently, we need not treat
10695 : : * domains specially here.
10696 : : *
10697 : : * If the collation changes, revalidation is required, unless both
10698 : : * collations are deterministic, because those share the same
10699 : : * notion of equality (because texteq reduces to bitwise
10700 : : * equality).
10701 : : *
10702 : : * We need not directly consider the PK type. It's necessarily
10703 : : * binary coercible to the opcintype of the unique index column,
10704 : : * and ri_triggers.c will only deal with PK datums in terms of
10705 : : * that opcintype. Changing the opcintype also changes pfeqop.
10706 : : */
10707 : 4 : old_check_ok = (new_pathtype == old_pathtype &&
10708 [ + - + - ]: 4 : new_castfunc == old_castfunc &&
10709 [ + - + - : 4 : (!IsPolymorphicType(pfeqop_right) ||
+ - + - +
- + - + -
+ - + - -
+ - - ]
10710 [ + - - + ]: 8 : new_fktype == old_fktype) &&
10711 [ # # ]: 0 : (new_fkcoll == old_fkcoll ||
10712 [ # # ]: 0 : (get_collation_isdeterministic(old_fkcoll) && get_collation_isdeterministic(new_fkcoll))));
10713 : : }
10714 : :
10715 : 1960 : pfeqoperators[i] = pfeqop;
10716 : 1960 : ppeqoperators[i] = ppeqop;
10717 : 1960 : ffeqoperators[i] = ffeqop;
10718 : : }
10719 : :
10720 : : /*
10721 : : * For FKs with PERIOD we need additional operators to check whether the
10722 : : * referencing row's range is contained by the aggregated ranges of the
10723 : : * referenced row(s). For rangetypes and multirangetypes this is
10724 : : * fk.periodatt <@ range_agg(pk.periodatt). Those are the only types we
10725 : : * support for now. FKs will look these up at "runtime", but we should
10726 : : * make sure the lookup works here, even if we don't use the values.
10727 : : */
10728 [ + + ]: 1503 : if (with_period)
10729 : : {
10730 : : Oid periodoperoid;
10731 : : Oid aggedperiodoperoid;
10732 : : Oid intersectoperoid;
10733 : :
10734 : 118 : FindFKPeriodOpers(opclasses[numpks - 1], &periodoperoid, &aggedperiodoperoid,
10735 : : &intersectoperoid);
10736 : : }
10737 : :
10738 : : /* First, create the constraint catalog entry itself. */
10739 : 1503 : address = addFkConstraint(addFkBothSides,
10740 : : fkconstraint->conname, fkconstraint, rel, pkrel,
10741 : : indexOid,
10742 : : InvalidOid, /* no parent constraint */
10743 : : numfks,
10744 : : pkattnum,
10745 : : fkattnum,
10746 : : pfeqoperators,
10747 : : ppeqoperators,
10748 : : ffeqoperators,
10749 : : numfkdelsetcols,
10750 : : fkdelsetcols,
10751 : : false,
10752 : : with_period);
10753 : :
10754 : : /* Next process the action triggers at the referenced side and recurse */
10755 : 1503 : addFkRecurseReferenced(fkconstraint, rel, pkrel,
10756 : : indexOid,
10757 : : address.objectId,
10758 : : numfks,
10759 : : pkattnum,
10760 : : fkattnum,
10761 : : pfeqoperators,
10762 : : ppeqoperators,
10763 : : ffeqoperators,
10764 : : numfkdelsetcols,
10765 : : fkdelsetcols,
10766 : : old_check_ok,
10767 : : InvalidOid, InvalidOid,
10768 : : with_period);
10769 : :
10770 : : /* Lastly create the check triggers at the referencing side and recurse */
10771 : 1503 : addFkRecurseReferencing(wqueue, fkconstraint, rel, pkrel,
10772 : : indexOid,
10773 : : address.objectId,
10774 : : numfks,
10775 : : pkattnum,
10776 : : fkattnum,
10777 : : pfeqoperators,
10778 : : ppeqoperators,
10779 : : ffeqoperators,
10780 : : numfkdelsetcols,
10781 : : fkdelsetcols,
10782 : : old_check_ok,
10783 : : lockmode,
10784 : : InvalidOid, InvalidOid,
10785 : : with_period);
10786 : :
10787 : : /*
10788 : : * Done. Close pk table, but keep lock until we've committed.
10789 : : */
10790 : 1503 : table_close(pkrel, NoLock);
10791 : :
10792 : 1503 : return address;
10793 : : }
10794 : :
10795 : : /*
10796 : : * validateFkOnDeleteSetColumns
10797 : : * Verifies that columns used in ON DELETE SET NULL/DEFAULT (...)
10798 : : * column lists are valid.
10799 : : *
10800 : : * If there are duplicates in the fksetcolsattnums[] array, this silently
10801 : : * removes the dups. The new count of numfksetcols is returned.
10802 : : */
10803 : : static int
10804 : 1819 : validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
10805 : : int numfksetcols, int16 *fksetcolsattnums,
10806 : : List *fksetcols)
10807 : : {
10808 : 1819 : int numcolsout = 0;
10809 : :
10810 [ + + ]: 1839 : for (int i = 0; i < numfksetcols; i++)
10811 : : {
10812 : 24 : int16 setcol_attnum = fksetcolsattnums[i];
10813 : 24 : bool seen = false;
10814 : :
10815 : : /* Make sure it's in fkattnums[] */
10816 [ + + ]: 44 : for (int j = 0; j < numfks; j++)
10817 : : {
10818 [ + + ]: 40 : if (fkattnums[j] == setcol_attnum)
10819 : : {
10820 : 20 : seen = true;
10821 : 20 : break;
10822 : : }
10823 : : }
10824 : :
10825 [ + + ]: 24 : if (!seen)
10826 : : {
10827 : 4 : char *col = strVal(list_nth(fksetcols, i));
10828 : :
10829 [ + - ]: 4 : ereport(ERROR,
10830 : : (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
10831 : : errmsg("column \"%s\" referenced in ON DELETE SET action must be part of foreign key", col)));
10832 : : }
10833 : :
10834 : : /* Now check for dups */
10835 : 20 : seen = false;
10836 [ + + ]: 20 : for (int j = 0; j < numcolsout; j++)
10837 : : {
10838 [ + - ]: 4 : if (fksetcolsattnums[j] == setcol_attnum)
10839 : : {
10840 : 4 : seen = true;
10841 : 4 : break;
10842 : : }
10843 : : }
10844 [ + + ]: 20 : if (!seen)
10845 : 16 : fksetcolsattnums[numcolsout++] = setcol_attnum;
10846 : : }
10847 : 1815 : return numcolsout;
10848 : : }
10849 : :
10850 : : /*
10851 : : * addFkConstraint
10852 : : * Install pg_constraint entries to implement a foreign key constraint.
10853 : : * Caller must separately invoke addFkRecurseReferenced and
10854 : : * addFkRecurseReferencing, as appropriate, to install pg_trigger entries
10855 : : * and (for partitioned tables) recurse to partitions.
10856 : : *
10857 : : * fkside: the side of the FK (or both) to create. Caller should
10858 : : * call addFkRecurseReferenced if this is addFkReferencedSide,
10859 : : * addFkRecurseReferencing if it's addFkReferencingSide, or both if it's
10860 : : * addFkBothSides.
10861 : : * constraintname: the base name for the constraint being added,
10862 : : * copied to fkconstraint->conname if the latter is not set
10863 : : * fkconstraint: the constraint being added
10864 : : * rel: the root referencing relation
10865 : : * pkrel: the referenced relation; might be a partition, if recursing
10866 : : * indexOid: the OID of the index (on pkrel) implementing this constraint
10867 : : * parentConstr: the OID of a parent constraint; InvalidOid if this is a
10868 : : * top-level constraint
10869 : : * numfks: the number of columns in the foreign key
10870 : : * pkattnum: the attnum array of referenced attributes
10871 : : * fkattnum: the attnum array of referencing attributes
10872 : : * pf/pp/ffeqoperators: OID array of operators between columns
10873 : : * numfkdelsetcols: the number of columns in the ON DELETE SET NULL/DEFAULT
10874 : : * (...) clause
10875 : : * fkdelsetcols: the attnum array of the columns in the ON DELETE SET
10876 : : * NULL/DEFAULT clause
10877 : : * with_period: true if this is a temporal FK
10878 : : */
10879 : : static ObjectAddress
10880 : 2870 : addFkConstraint(addFkConstraintSides fkside,
10881 : : char *constraintname, Constraint *fkconstraint,
10882 : : Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
10883 : : int numfks, int16 *pkattnum,
10884 : : int16 *fkattnum, Oid *pfeqoperators, Oid *ppeqoperators,
10885 : : Oid *ffeqoperators, int numfkdelsetcols, int16 *fkdelsetcols,
10886 : : bool is_internal, bool with_period)
10887 : : {
10888 : : ObjectAddress address;
10889 : : Oid constrOid;
10890 : : char *conname;
10891 : : bool conislocal;
10892 : : int16 coninhcount;
10893 : : bool connoinherit;
10894 : :
10895 : : /*
10896 : : * Verify relkind for each referenced partition. At the top level, this
10897 : : * is redundant with a previous check, but we need it when recursing.
10898 : : */
10899 [ + + ]: 2870 : if (pkrel->rd_rel->relkind != RELKIND_RELATION &&
10900 [ - + ]: 589 : pkrel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
10901 [ # # ]: 0 : ereport(ERROR,
10902 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
10903 : : errmsg("referenced relation \"%s\" is not a table",
10904 : : RelationGetRelationName(pkrel))));
10905 : :
10906 : : /*
10907 : : * Caller supplies us with a constraint name; however, it may be used in
10908 : : * this partition, so come up with a different one in that case. Unless
10909 : : * truncation to NAMEDATALEN dictates otherwise, the new name will be the
10910 : : * supplied name with an underscore and digit(s) appended.
10911 : : */
10912 [ + + ]: 2870 : if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
10913 : : RelationGetRelid(rel),
10914 : : constraintname))
10915 : 811 : conname = ChooseConstraintName(constraintname,
10916 : : NULL,
10917 : : "",
10918 : 811 : RelationGetNamespace(rel), NIL);
10919 : : else
10920 : 2059 : conname = constraintname;
10921 : :
10922 [ + + ]: 2870 : if (fkconstraint->conname == NULL)
10923 : 283 : fkconstraint->conname = pstrdup(conname);
10924 : :
10925 [ + + ]: 2870 : if (OidIsValid(parentConstr))
10926 : : {
10927 : 1367 : conislocal = false;
10928 : 1367 : coninhcount = 1;
10929 : 1367 : connoinherit = false;
10930 : : }
10931 : : else
10932 : : {
10933 : 1503 : conislocal = true;
10934 : 1503 : coninhcount = 0;
10935 : :
10936 : : /*
10937 : : * always inherit for partitioned tables, never for legacy inheritance
10938 : : */
10939 : 1503 : connoinherit = rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE;
10940 : : }
10941 : :
10942 : : /*
10943 : : * Record the FK constraint in pg_constraint.
10944 : : */
10945 : 2870 : constrOid = CreateConstraintEntry(conname,
10946 : 2870 : RelationGetNamespace(rel),
10947 : : CONSTRAINT_FOREIGN,
10948 : 2870 : fkconstraint->deferrable,
10949 : 2870 : fkconstraint->initdeferred,
10950 : 2870 : fkconstraint->is_enforced,
10951 : 2870 : fkconstraint->initially_valid,
10952 : : parentConstr,
10953 : : RelationGetRelid(rel),
10954 : : fkattnum,
10955 : : numfks,
10956 : : numfks,
10957 : : InvalidOid, /* not a domain constraint */
10958 : : indexOid,
10959 : : RelationGetRelid(pkrel),
10960 : : pkattnum,
10961 : : pfeqoperators,
10962 : : ppeqoperators,
10963 : : ffeqoperators,
10964 : : numfks,
10965 : 2870 : fkconstraint->fk_upd_action,
10966 : 2870 : fkconstraint->fk_del_action,
10967 : : fkdelsetcols,
10968 : : numfkdelsetcols,
10969 : 2870 : fkconstraint->fk_matchtype,
10970 : : NULL, /* no exclusion constraint */
10971 : : NULL, /* no check constraint */
10972 : : NULL,
10973 : : conislocal, /* islocal */
10974 : : coninhcount, /* inhcount */
10975 : : connoinherit, /* conNoInherit */
10976 : : with_period, /* conPeriod */
10977 : : is_internal); /* is_internal */
10978 : :
10979 : 2870 : ObjectAddressSet(address, ConstraintRelationId, constrOid);
10980 : :
10981 : : /*
10982 : : * In partitioning cases, create the dependency entries for this
10983 : : * constraint. (For non-partitioned cases, relevant entries were created
10984 : : * by CreateConstraintEntry.)
10985 : : *
10986 : : * On the referenced side, we need the constraint to have an internal
10987 : : * dependency on its parent constraint; this means that this constraint
10988 : : * cannot be dropped on its own -- only through the parent constraint. It
10989 : : * also means the containing partition cannot be dropped on its own, but
10990 : : * it can be detached, at which point this dependency is removed (after
10991 : : * verifying that no rows are referenced via this FK.)
10992 : : *
10993 : : * When processing the referencing side, we link the constraint via the
10994 : : * special partitioning dependencies: the parent constraint is the primary
10995 : : * dependent, and the partition on which the foreign key exists is the
10996 : : * secondary dependency. That way, this constraint is dropped if either
10997 : : * of these objects is.
10998 : : *
10999 : : * Note that this is only necessary for the subsidiary pg_constraint rows
11000 : : * in partitions; the topmost row doesn't need any of this.
11001 : : */
11002 [ + + ]: 2870 : if (OidIsValid(parentConstr))
11003 : : {
11004 : : ObjectAddress referenced;
11005 : :
11006 : 1367 : ObjectAddressSet(referenced, ConstraintRelationId, parentConstr);
11007 : :
11008 : : Assert(fkside != addFkBothSides);
11009 [ + + ]: 1367 : if (fkside == addFkReferencedSide)
11010 : 807 : recordDependencyOn(&address, &referenced, DEPENDENCY_INTERNAL);
11011 : : else
11012 : : {
11013 : 560 : recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_PRI);
11014 : 560 : ObjectAddressSet(referenced, RelationRelationId, RelationGetRelid(rel));
11015 : 560 : recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_SEC);
11016 : : }
11017 : : }
11018 : :
11019 : : /* make new constraint visible, in case we add more */
11020 : 2870 : CommandCounterIncrement();
11021 : :
11022 : 2870 : return address;
11023 : : }
11024 : :
11025 : : /*
11026 : : * addFkRecurseReferenced
11027 : : * Recursive helper for the referenced side of foreign key creation,
11028 : : * which creates the action triggers and recurses
11029 : : *
11030 : : * If the referenced relation is a plain relation, create the necessary action
11031 : : * triggers that implement the constraint. If the referenced relation is a
11032 : : * partitioned table, then we create a pg_constraint row referencing the parent
11033 : : * of the referencing side for it and recurse on this routine for each
11034 : : * partition.
11035 : : *
11036 : : * fkconstraint: the constraint being added
11037 : : * rel: the root referencing relation
11038 : : * pkrel: the referenced relation; might be a partition, if recursing
11039 : : * indexOid: the OID of the index (on pkrel) implementing this constraint
11040 : : * parentConstr: the OID of a parent constraint; InvalidOid if this is a
11041 : : * top-level constraint
11042 : : * numfks: the number of columns in the foreign key
11043 : : * pkattnum: the attnum array of referenced attributes
11044 : : * fkattnum: the attnum array of referencing attributes
11045 : : * numfkdelsetcols: the number of columns in the ON DELETE SET
11046 : : * NULL/DEFAULT (...) clause
11047 : : * fkdelsetcols: the attnum array of the columns in the ON DELETE SET
11048 : : * NULL/DEFAULT clause
11049 : : * pf/pp/ffeqoperators: OID array of operators between columns
11050 : : * old_check_ok: true if this constraint replaces an existing one that
11051 : : * was already validated (thus this one doesn't need validation)
11052 : : * parentDelTrigger and parentUpdTrigger: when recursively called on a
11053 : : * partition, the OIDs of the parent action triggers for DELETE and
11054 : : * UPDATE respectively.
11055 : : * with_period: true if this is a temporal FK
11056 : : */
11057 : : static void
11058 : 2382 : addFkRecurseReferenced(Constraint *fkconstraint, Relation rel,
11059 : : Relation pkrel, Oid indexOid, Oid parentConstr,
11060 : : int numfks,
11061 : : int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators,
11062 : : Oid *ppeqoperators, Oid *ffeqoperators,
11063 : : int numfkdelsetcols, int16 *fkdelsetcols,
11064 : : bool old_check_ok,
11065 : : Oid parentDelTrigger, Oid parentUpdTrigger,
11066 : : bool with_period)
11067 : : {
11068 : 2382 : Oid deleteTriggerOid = InvalidOid,
11069 : 2382 : updateTriggerOid = InvalidOid;
11070 : :
11071 : : Assert(CheckRelationLockedByMe(pkrel, ShareRowExclusiveLock, true));
11072 : : Assert(CheckRelationLockedByMe(rel, ShareRowExclusiveLock, true));
11073 : :
11074 : : /*
11075 : : * Create action triggers to enforce the constraint, or skip them if the
11076 : : * constraint is NOT ENFORCED.
11077 : : */
11078 [ + + ]: 2382 : if (fkconstraint->is_enforced)
11079 : 2329 : createForeignKeyActionTriggers(RelationGetRelid(rel),
11080 : : RelationGetRelid(pkrel),
11081 : : fkconstraint,
11082 : : parentConstr, indexOid,
11083 : : parentDelTrigger, parentUpdTrigger,
11084 : : &deleteTriggerOid, &updateTriggerOid);
11085 : :
11086 : : /*
11087 : : * If the referenced table is partitioned, recurse on ourselves to handle
11088 : : * each partition. We need one pg_constraint row created for each
11089 : : * partition in addition to the pg_constraint row for the parent table.
11090 : : */
11091 [ + + ]: 2382 : if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
11092 : : {
11093 : 373 : PartitionDesc pd = RelationGetPartitionDesc(pkrel, true);
11094 : :
11095 [ + + ]: 1048 : for (int i = 0; i < pd->nparts; i++)
11096 : : {
11097 : : Relation partRel;
11098 : : AttrMap *map;
11099 : : AttrNumber *mapped_pkattnum;
11100 : : Oid partIndexId;
11101 : : ObjectAddress address;
11102 : :
11103 : : /* XXX would it be better to acquire these locks beforehand? */
11104 : 675 : partRel = table_open(pd->oids[i], ShareRowExclusiveLock);
11105 : :
11106 : : /*
11107 : : * Map the attribute numbers in the referenced side of the FK
11108 : : * definition to match the partition's column layout.
11109 : : */
11110 : 675 : map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
11111 : : RelationGetDescr(pkrel),
11112 : : false);
11113 [ + + ]: 675 : if (map)
11114 : : {
11115 : 89 : mapped_pkattnum = palloc_array(AttrNumber, numfks);
11116 [ + + ]: 186 : for (int j = 0; j < numfks; j++)
11117 : 97 : mapped_pkattnum[j] = map->attnums[pkattnum[j] - 1];
11118 : : }
11119 : : else
11120 : 586 : mapped_pkattnum = pkattnum;
11121 : :
11122 : : /* Determine the index to use at this level */
11123 : 675 : partIndexId = index_get_partition(partRel, indexOid);
11124 [ - + ]: 675 : if (!OidIsValid(partIndexId))
11125 [ # # ]: 0 : elog(ERROR, "index for %u not found in partition %s",
11126 : : indexOid, RelationGetRelationName(partRel));
11127 : :
11128 : : /* Create entry at this level ... */
11129 : 675 : address = addFkConstraint(addFkReferencedSide,
11130 : : fkconstraint->conname, fkconstraint, rel,
11131 : : partRel, partIndexId, parentConstr,
11132 : : numfks, mapped_pkattnum,
11133 : : fkattnum, pfeqoperators, ppeqoperators,
11134 : : ffeqoperators, numfkdelsetcols,
11135 : : fkdelsetcols, true, with_period);
11136 : : /* ... and recurse to our children */
11137 : 675 : addFkRecurseReferenced(fkconstraint, rel, partRel,
11138 : : partIndexId, address.objectId, numfks,
11139 : : mapped_pkattnum, fkattnum,
11140 : : pfeqoperators, ppeqoperators, ffeqoperators,
11141 : : numfkdelsetcols, fkdelsetcols,
11142 : : old_check_ok,
11143 : : deleteTriggerOid, updateTriggerOid,
11144 : : with_period);
11145 : :
11146 : : /* Done -- clean up (but keep the lock) */
11147 : 675 : table_close(partRel, NoLock);
11148 [ + + ]: 675 : if (map)
11149 : : {
11150 : 89 : pfree(mapped_pkattnum);
11151 : 89 : free_attrmap(map);
11152 : : }
11153 : : }
11154 : : }
11155 : 2382 : }
11156 : :
11157 : : /*
11158 : : * addFkRecurseReferencing
11159 : : * Recursive helper for the referencing side of foreign key creation,
11160 : : * which creates the check triggers and recurses
11161 : : *
11162 : : * If the referencing relation is a plain relation, create the necessary check
11163 : : * triggers that implement the constraint, and set up for Phase 3 constraint
11164 : : * verification. If the referencing relation is a partitioned table, then
11165 : : * we create a pg_constraint row for it and recurse on this routine for each
11166 : : * partition.
11167 : : *
11168 : : * We assume that the referenced relation is locked against concurrent
11169 : : * deletions. If it's a partitioned relation, every partition must be so
11170 : : * locked.
11171 : : *
11172 : : * wqueue: the ALTER TABLE work queue; NULL when not running as part
11173 : : * of an ALTER TABLE sequence.
11174 : : * fkconstraint: the constraint being added
11175 : : * rel: the referencing relation; might be a partition, if recursing
11176 : : * pkrel: the root referenced relation
11177 : : * indexOid: the OID of the index (on pkrel) implementing this constraint
11178 : : * parentConstr: the OID of the parent constraint (there is always one)
11179 : : * numfks: the number of columns in the foreign key
11180 : : * pkattnum: the attnum array of referenced attributes
11181 : : * fkattnum: the attnum array of referencing attributes
11182 : : * pf/pp/ffeqoperators: OID array of operators between columns
11183 : : * numfkdelsetcols: the number of columns in the ON DELETE SET NULL/DEFAULT
11184 : : * (...) clause
11185 : : * fkdelsetcols: the attnum array of the columns in the ON DELETE SET
11186 : : * NULL/DEFAULT clause
11187 : : * old_check_ok: true if this constraint replaces an existing one that
11188 : : * was already validated (thus this one doesn't need validation)
11189 : : * lockmode: the lockmode to acquire on partitions when recursing
11190 : : * parentInsTrigger and parentUpdTrigger: when being recursively called on
11191 : : * a partition, the OIDs of the parent check triggers for INSERT and
11192 : : * UPDATE respectively.
11193 : : * with_period: true if this is a temporal FK
11194 : : */
11195 : : static void
11196 : 2063 : addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
11197 : : Relation pkrel, Oid indexOid, Oid parentConstr,
11198 : : int numfks, int16 *pkattnum, int16 *fkattnum,
11199 : : Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
11200 : : int numfkdelsetcols, int16 *fkdelsetcols,
11201 : : bool old_check_ok, LOCKMODE lockmode,
11202 : : Oid parentInsTrigger, Oid parentUpdTrigger,
11203 : : bool with_period)
11204 : : {
11205 : 2063 : Oid insertTriggerOid = InvalidOid,
11206 : 2063 : updateTriggerOid = InvalidOid;
11207 : :
11208 : : Assert(OidIsValid(parentConstr));
11209 : : Assert(CheckRelationLockedByMe(rel, ShareRowExclusiveLock, true));
11210 : : Assert(CheckRelationLockedByMe(pkrel, ShareRowExclusiveLock, true));
11211 : :
11212 [ - + ]: 2063 : if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
11213 [ # # ]: 0 : ereport(ERROR,
11214 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
11215 : : errmsg("foreign key constraints are not supported on foreign tables")));
11216 : :
11217 : : /*
11218 : : * Add check triggers if the constraint is ENFORCED, and if needed,
11219 : : * schedule them to be checked in Phase 3.
11220 : : *
11221 : : * If the relation is partitioned, drill down to do it to its partitions.
11222 : : */
11223 [ + + ]: 2063 : if (fkconstraint->is_enforced)
11224 : 2026 : createForeignKeyCheckTriggers(RelationGetRelid(rel),
11225 : : RelationGetRelid(pkrel),
11226 : : fkconstraint,
11227 : : parentConstr,
11228 : : indexOid,
11229 : : parentInsTrigger, parentUpdTrigger,
11230 : : &insertTriggerOid, &updateTriggerOid);
11231 : :
11232 [ + + ]: 2063 : if (rel->rd_rel->relkind == RELKIND_RELATION)
11233 : : {
11234 : : /*
11235 : : * Tell Phase 3 to check that the constraint is satisfied by existing
11236 : : * rows. We can skip this during table creation, when constraint is
11237 : : * specified as NOT ENFORCED, or when requested explicitly by
11238 : : * specifying NOT VALID in an ADD FOREIGN KEY command, and when we're
11239 : : * recreating a constraint following a SET DATA TYPE operation that
11240 : : * did not impugn its validity.
11241 : : */
11242 [ + + + + : 1740 : if (wqueue && !old_check_ok && !fkconstraint->skip_validation &&
+ + ]
11243 [ + - ]: 603 : fkconstraint->is_enforced)
11244 : : {
11245 : : NewConstraint *newcon;
11246 : : AlteredTableInfo *tab;
11247 : :
11248 : 603 : tab = ATGetQueueEntry(wqueue, rel);
11249 : :
11250 : 603 : newcon = palloc0_object(NewConstraint);
11251 : 603 : newcon->name = get_constraint_name(parentConstr);
11252 : 603 : newcon->contype = CONSTR_FOREIGN;
11253 : 603 : newcon->refrelid = RelationGetRelid(pkrel);
11254 : 603 : newcon->refindid = indexOid;
11255 : 603 : newcon->conid = parentConstr;
11256 : 603 : newcon->conwithperiod = fkconstraint->fk_with_period;
11257 : 603 : newcon->qual = (Node *) fkconstraint;
11258 : :
11259 : 603 : tab->constraints = lappend(tab->constraints, newcon);
11260 : : }
11261 : : }
11262 [ + - ]: 323 : else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
11263 : : {
11264 : 323 : PartitionDesc pd = RelationGetPartitionDesc(rel, true);
11265 : : Relation trigrel;
11266 : :
11267 : : /*
11268 : : * Triggers of the foreign keys will be manipulated a bunch of times
11269 : : * in the loop below. To avoid repeatedly opening/closing the trigger
11270 : : * catalog relation, we open it here and pass it to the subroutines
11271 : : * called below.
11272 : : */
11273 : 323 : trigrel = table_open(TriggerRelationId, RowExclusiveLock);
11274 : :
11275 : : /*
11276 : : * Recurse to take appropriate action on each partition; either we
11277 : : * find an existing constraint to reparent to ours, or we create a new
11278 : : * one.
11279 : : */
11280 [ + + ]: 608 : for (int i = 0; i < pd->nparts; i++)
11281 : : {
11282 : 289 : Relation partition = table_open(pd->oids[i], lockmode);
11283 : : List *partFKs;
11284 : : AttrMap *attmap;
11285 : : AttrNumber mapped_fkattnum[INDEX_MAX_KEYS];
11286 : : bool attached;
11287 : : ObjectAddress address;
11288 : :
11289 : 289 : CheckAlterTableIsSafe(partition);
11290 : :
11291 : 285 : attmap = build_attrmap_by_name(RelationGetDescr(partition),
11292 : : RelationGetDescr(rel),
11293 : : false);
11294 [ + + ]: 714 : for (int j = 0; j < numfks; j++)
11295 : 429 : mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
11296 : :
11297 : : /* Check whether an existing constraint can be repurposed */
11298 : 285 : partFKs = copyObject(RelationGetFKeyList(partition));
11299 : 285 : attached = false;
11300 [ + + + + : 581 : foreach_node(ForeignKeyCacheInfo, fk, partFKs)
+ + ]
11301 : : {
11302 [ + + ]: 19 : if (tryAttachPartitionForeignKey(wqueue,
11303 : : fk,
11304 : : partition,
11305 : : parentConstr,
11306 : : numfks,
11307 : : mapped_fkattnum,
11308 : : pkattnum,
11309 : : pfeqoperators,
11310 : : insertTriggerOid,
11311 : : updateTriggerOid,
11312 : : trigrel))
11313 : : {
11314 : 8 : attached = true;
11315 : 8 : break;
11316 : : }
11317 : : }
11318 [ + + ]: 285 : if (attached)
11319 : : {
11320 : 8 : table_close(partition, NoLock);
11321 : 8 : continue;
11322 : : }
11323 : :
11324 : : /*
11325 : : * No luck finding a good constraint to reuse; create our own.
11326 : : */
11327 : 277 : address = addFkConstraint(addFkReferencingSide,
11328 : : fkconstraint->conname, fkconstraint,
11329 : : partition, pkrel, indexOid, parentConstr,
11330 : : numfks, pkattnum,
11331 : : mapped_fkattnum, pfeqoperators,
11332 : : ppeqoperators, ffeqoperators,
11333 : : numfkdelsetcols, fkdelsetcols, true,
11334 : : with_period);
11335 : :
11336 : : /* call ourselves to finalize the creation and we're done */
11337 : 277 : addFkRecurseReferencing(wqueue, fkconstraint, partition, pkrel,
11338 : : indexOid,
11339 : : address.objectId,
11340 : : numfks,
11341 : : pkattnum,
11342 : : mapped_fkattnum,
11343 : : pfeqoperators,
11344 : : ppeqoperators,
11345 : : ffeqoperators,
11346 : : numfkdelsetcols,
11347 : : fkdelsetcols,
11348 : : old_check_ok,
11349 : : lockmode,
11350 : : insertTriggerOid,
11351 : : updateTriggerOid,
11352 : : with_period);
11353 : :
11354 : 277 : table_close(partition, NoLock);
11355 : : }
11356 : :
11357 : 319 : table_close(trigrel, RowExclusiveLock);
11358 : : }
11359 : 2059 : }
11360 : :
11361 : : /*
11362 : : * CloneForeignKeyConstraints
11363 : : * Clone foreign keys from a partitioned table to a newly acquired
11364 : : * partition.
11365 : : *
11366 : : * partitionRel is a partition of parentRel, so we can be certain that it has
11367 : : * the same columns with the same datatypes. The columns may be in different
11368 : : * order, though.
11369 : : *
11370 : : * wqueue must be passed to set up phase 3 constraint checking, unless the
11371 : : * referencing-side partition is known to be empty (such as in CREATE TABLE /
11372 : : * PARTITION OF).
11373 : : */
11374 : : static void
11375 : 8153 : CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
11376 : : Relation partitionRel)
11377 : : {
11378 : : /* This only works for declarative partitioning */
11379 : : Assert(parentRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
11380 : :
11381 : : /*
11382 : : * First, clone constraints where the parent is on the referencing side.
11383 : : */
11384 : 8153 : CloneFkReferencing(wqueue, parentRel, partitionRel);
11385 : :
11386 : : /*
11387 : : * Clone constraints for which the parent is on the referenced side.
11388 : : */
11389 : 8141 : CloneFkReferenced(parentRel, partitionRel);
11390 : 8141 : }
11391 : :
11392 : : /*
11393 : : * CloneFkReferenced
11394 : : * Subroutine for CloneForeignKeyConstraints
11395 : : *
11396 : : * Find all the FKs that have the parent relation on the referenced side;
11397 : : * clone those constraints to the given partition. This is to be called
11398 : : * when the partition is being created or attached.
11399 : : *
11400 : : * This recurses to partitions, if the relation being attached is partitioned.
11401 : : * Recursion is done by calling addFkRecurseReferenced.
11402 : : */
11403 : : static void
11404 : 8141 : CloneFkReferenced(Relation parentRel, Relation partitionRel)
11405 : : {
11406 : : Relation pg_constraint;
11407 : : AttrMap *attmap;
11408 : : ListCell *cell;
11409 : : SysScanDesc scan;
11410 : : ScanKeyData key[2];
11411 : : HeapTuple tuple;
11412 : 8141 : List *clone = NIL;
11413 : : Relation trigrel;
11414 : :
11415 : : /*
11416 : : * Search for any constraints where this partition's parent is in the
11417 : : * referenced side. However, we must not clone any constraint whose
11418 : : * parent constraint is also going to be cloned, to avoid duplicates. So
11419 : : * do it in two steps: first construct the list of constraints to clone,
11420 : : * then go over that list cloning those whose parents are not in the list.
11421 : : * (We must not rely on the parent being seen first, since the catalog
11422 : : * scan could return children first.)
11423 : : */
11424 : 8141 : pg_constraint = table_open(ConstraintRelationId, RowShareLock);
11425 : 8141 : ScanKeyInit(&key[0],
11426 : : Anum_pg_constraint_confrelid, BTEqualStrategyNumber,
11427 : : F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(parentRel)));
11428 : 8141 : ScanKeyInit(&key[1],
11429 : : Anum_pg_constraint_contype, BTEqualStrategyNumber,
11430 : : F_CHAREQ, CharGetDatum(CONSTRAINT_FOREIGN));
11431 : : /* This is a seqscan, as we don't have a usable index ... */
11432 : 8141 : scan = systable_beginscan(pg_constraint, InvalidOid, true,
11433 : : NULL, 2, key);
11434 [ + + ]: 8421 : while ((tuple = systable_getnext(scan)) != NULL)
11435 : : {
11436 : 280 : Form_pg_constraint constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
11437 : :
11438 : 280 : clone = lappend_oid(clone, constrForm->oid);
11439 : : }
11440 : 8141 : systable_endscan(scan);
11441 : 8141 : table_close(pg_constraint, RowShareLock);
11442 : :
11443 : : /*
11444 : : * Triggers of the foreign keys will be manipulated a bunch of times in
11445 : : * the loop below. To avoid repeatedly opening/closing the trigger
11446 : : * catalog relation, we open it here and pass it to the subroutines called
11447 : : * below.
11448 : : */
11449 : 8141 : trigrel = table_open(TriggerRelationId, RowExclusiveLock);
11450 : :
11451 : 8141 : attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
11452 : : RelationGetDescr(parentRel),
11453 : : false);
11454 [ + + + + : 8421 : foreach(cell, clone)
+ + ]
11455 : : {
11456 : 280 : Oid constrOid = lfirst_oid(cell);
11457 : : Form_pg_constraint constrForm;
11458 : : Relation fkRel;
11459 : : Oid indexOid;
11460 : : Oid partIndexId;
11461 : : int numfks;
11462 : : AttrNumber conkey[INDEX_MAX_KEYS];
11463 : : AttrNumber mapped_confkey[INDEX_MAX_KEYS];
11464 : : AttrNumber confkey[INDEX_MAX_KEYS];
11465 : : Oid conpfeqop[INDEX_MAX_KEYS];
11466 : : Oid conppeqop[INDEX_MAX_KEYS];
11467 : : Oid conffeqop[INDEX_MAX_KEYS];
11468 : : int numfkdelsetcols;
11469 : : AttrNumber confdelsetcols[INDEX_MAX_KEYS];
11470 : : Constraint *fkconstraint;
11471 : : ObjectAddress address;
11472 : 280 : Oid deleteTriggerOid = InvalidOid,
11473 : 280 : updateTriggerOid = InvalidOid;
11474 : :
11475 : 280 : tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
11476 [ - + ]: 280 : if (!HeapTupleIsValid(tuple))
11477 [ # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u", constrOid);
11478 : 280 : constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
11479 : :
11480 : : /*
11481 : : * As explained above: don't try to clone a constraint for which we're
11482 : : * going to clone the parent.
11483 : : */
11484 [ + + ]: 280 : if (list_member_oid(clone, constrForm->conparentid))
11485 : : {
11486 : 148 : ReleaseSysCache(tuple);
11487 : 148 : continue;
11488 : : }
11489 : :
11490 : : /* We need the same lock level that CreateTrigger will acquire */
11491 : 132 : fkRel = table_open(constrForm->conrelid, ShareRowExclusiveLock);
11492 : :
11493 : 132 : indexOid = constrForm->conindid;
11494 : 132 : DeconstructFkConstraintRow(tuple,
11495 : : &numfks,
11496 : : conkey,
11497 : : confkey,
11498 : : conpfeqop,
11499 : : conppeqop,
11500 : : conffeqop,
11501 : : &numfkdelsetcols,
11502 : : confdelsetcols);
11503 : :
11504 [ + + ]: 292 : for (int i = 0; i < numfks; i++)
11505 : 160 : mapped_confkey[i] = attmap->attnums[confkey[i] - 1];
11506 : :
11507 : 132 : fkconstraint = makeNode(Constraint);
11508 : 132 : fkconstraint->contype = CONSTRAINT_FOREIGN;
11509 : 132 : fkconstraint->conname = NameStr(constrForm->conname);
11510 : 132 : fkconstraint->deferrable = constrForm->condeferrable;
11511 : 132 : fkconstraint->initdeferred = constrForm->condeferred;
11512 : 132 : fkconstraint->location = -1;
11513 : 132 : fkconstraint->pktable = NULL;
11514 : : /* ->fk_attrs determined below */
11515 : 132 : fkconstraint->pk_attrs = NIL;
11516 : 132 : fkconstraint->fk_matchtype = constrForm->confmatchtype;
11517 : 132 : fkconstraint->fk_upd_action = constrForm->confupdtype;
11518 : 132 : fkconstraint->fk_del_action = constrForm->confdeltype;
11519 : 132 : fkconstraint->fk_del_set_cols = NIL;
11520 : 132 : fkconstraint->old_conpfeqop = NIL;
11521 : 132 : fkconstraint->old_pktable_oid = InvalidOid;
11522 : 132 : fkconstraint->is_enforced = constrForm->conenforced;
11523 : 132 : fkconstraint->skip_validation = false;
11524 : 132 : fkconstraint->initially_valid = constrForm->convalidated;
11525 : :
11526 : : /* set up colnames that are used to generate the constraint name */
11527 [ + + ]: 292 : for (int i = 0; i < numfks; i++)
11528 : : {
11529 : : Form_pg_attribute att;
11530 : :
11531 : 160 : att = TupleDescAttr(RelationGetDescr(fkRel),
11532 : 160 : conkey[i] - 1);
11533 : 160 : fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
11534 : 160 : makeString(NameStr(att->attname)));
11535 : : }
11536 : :
11537 : : /*
11538 : : * Add the new foreign key constraint pointing to the new partition.
11539 : : * Because this new partition appears in the referenced side of the
11540 : : * constraint, we don't need to set up for Phase 3 check.
11541 : : */
11542 : 132 : partIndexId = index_get_partition(partitionRel, indexOid);
11543 [ - + ]: 132 : if (!OidIsValid(partIndexId))
11544 [ # # ]: 0 : elog(ERROR, "index for %u not found in partition %s",
11545 : : indexOid, RelationGetRelationName(partitionRel));
11546 : :
11547 : : /*
11548 : : * Get the "action" triggers belonging to the constraint to pass as
11549 : : * parent OIDs for similar triggers that will be created on the
11550 : : * partition in addFkRecurseReferenced().
11551 : : */
11552 [ + + ]: 132 : if (constrForm->conenforced)
11553 : 128 : GetForeignKeyActionTriggers(trigrel, constrOid,
11554 : : constrForm->confrelid, constrForm->conrelid,
11555 : : &deleteTriggerOid, &updateTriggerOid);
11556 : :
11557 : : /* Add this constraint ... */
11558 : 132 : address = addFkConstraint(addFkReferencedSide,
11559 : : fkconstraint->conname, fkconstraint, fkRel,
11560 : : partitionRel, partIndexId, constrOid,
11561 : : numfks, mapped_confkey,
11562 : : conkey, conpfeqop, conppeqop, conffeqop,
11563 : : numfkdelsetcols, confdelsetcols, false,
11564 : 132 : constrForm->conperiod);
11565 : : /* ... and recurse */
11566 : 132 : addFkRecurseReferenced(fkconstraint,
11567 : : fkRel,
11568 : : partitionRel,
11569 : : partIndexId,
11570 : : address.objectId,
11571 : : numfks,
11572 : : mapped_confkey,
11573 : : conkey,
11574 : : conpfeqop,
11575 : : conppeqop,
11576 : : conffeqop,
11577 : : numfkdelsetcols,
11578 : : confdelsetcols,
11579 : : true,
11580 : : deleteTriggerOid,
11581 : : updateTriggerOid,
11582 : 132 : constrForm->conperiod);
11583 : :
11584 : 132 : table_close(fkRel, NoLock);
11585 : 132 : ReleaseSysCache(tuple);
11586 : : }
11587 : :
11588 : 8141 : table_close(trigrel, RowExclusiveLock);
11589 : 8141 : }
11590 : :
11591 : : /*
11592 : : * CloneFkReferencing
11593 : : * Subroutine for CloneForeignKeyConstraints
11594 : : *
11595 : : * For each FK constraint of the parent relation in the given list, find an
11596 : : * equivalent constraint in its partition relation that can be reparented;
11597 : : * if one cannot be found, create a new constraint in the partition as its
11598 : : * child.
11599 : : *
11600 : : * If wqueue is given, it is used to set up phase-3 verification for each
11601 : : * cloned constraint; omit it if such verification is not needed
11602 : : * (example: the partition is being created anew).
11603 : : */
11604 : : static void
11605 : 8153 : CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
11606 : : {
11607 : : AttrMap *attmap;
11608 : : List *partFKs;
11609 : 8153 : List *clone = NIL;
11610 : : ListCell *cell;
11611 : : Relation trigrel;
11612 : :
11613 : : /* obtain a list of constraints that we need to clone */
11614 [ + + + + : 9012 : foreach(cell, RelationGetFKeyList(parentRel))
+ + ]
11615 : : {
11616 : 863 : ForeignKeyCacheInfo *fk = lfirst(cell);
11617 : :
11618 : : /*
11619 : : * Refuse to attach a table as partition that this partitioned table
11620 : : * already has a foreign key to. This isn't useful schema, which is
11621 : : * proven by the fact that there have been no user complaints that
11622 : : * it's already impossible to achieve this in the opposite direction,
11623 : : * i.e., creating a foreign key that references a partition. This
11624 : : * restriction allows us to dodge some complexities around
11625 : : * pg_constraint and pg_trigger row creations that would be needed
11626 : : * during ATTACH/DETACH for this kind of relationship.
11627 : : */
11628 [ + + ]: 863 : if (fk->confrelid == RelationGetRelid(partRel))
11629 [ + - ]: 4 : ereport(ERROR,
11630 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
11631 : : errmsg("cannot attach table \"%s\" as a partition because it is referenced by foreign key \"%s\"",
11632 : : RelationGetRelationName(partRel),
11633 : : get_constraint_name(fk->conoid))));
11634 : :
11635 : 859 : clone = lappend_oid(clone, fk->conoid);
11636 : : }
11637 : :
11638 : : /*
11639 : : * Silently do nothing if there's nothing to do. In particular, this
11640 : : * avoids throwing a spurious error for foreign tables.
11641 : : */
11642 [ + + ]: 8149 : if (clone == NIL)
11643 : 7794 : return;
11644 : :
11645 [ - + ]: 355 : if (partRel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
11646 [ # # ]: 0 : ereport(ERROR,
11647 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
11648 : : errmsg("foreign key constraints are not supported on foreign tables")));
11649 : :
11650 : : /*
11651 : : * Triggers of the foreign keys will be manipulated a bunch of times in
11652 : : * the loop below. To avoid repeatedly opening/closing the trigger
11653 : : * catalog relation, we open it here and pass it to the subroutines called
11654 : : * below.
11655 : : */
11656 : 355 : trigrel = table_open(TriggerRelationId, RowExclusiveLock);
11657 : :
11658 : : /*
11659 : : * The constraint key may differ, if the columns in the partition are
11660 : : * different. This map is used to convert them.
11661 : : */
11662 : 355 : attmap = build_attrmap_by_name(RelationGetDescr(partRel),
11663 : : RelationGetDescr(parentRel),
11664 : : false);
11665 : :
11666 : 355 : partFKs = copyObject(RelationGetFKeyList(partRel));
11667 : :
11668 [ + - + + : 1206 : foreach(cell, clone)
+ + ]
11669 : : {
11670 : 859 : Oid parentConstrOid = lfirst_oid(cell);
11671 : : Form_pg_constraint constrForm;
11672 : : Relation pkrel;
11673 : : HeapTuple tuple;
11674 : : int numfks;
11675 : : AttrNumber conkey[INDEX_MAX_KEYS];
11676 : : AttrNumber mapped_conkey[INDEX_MAX_KEYS];
11677 : : AttrNumber confkey[INDEX_MAX_KEYS];
11678 : : Oid conpfeqop[INDEX_MAX_KEYS];
11679 : : Oid conppeqop[INDEX_MAX_KEYS];
11680 : : Oid conffeqop[INDEX_MAX_KEYS];
11681 : : int numfkdelsetcols;
11682 : : AttrNumber confdelsetcols[INDEX_MAX_KEYS];
11683 : : Constraint *fkconstraint;
11684 : : bool attached;
11685 : : Oid indexOid;
11686 : : ObjectAddress address;
11687 : : ListCell *lc;
11688 : 859 : Oid insertTriggerOid = InvalidOid,
11689 : 859 : updateTriggerOid = InvalidOid;
11690 : : bool with_period;
11691 : :
11692 : 859 : tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(parentConstrOid));
11693 [ - + ]: 859 : if (!HeapTupleIsValid(tuple))
11694 [ # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u",
11695 : : parentConstrOid);
11696 : 859 : constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
11697 : :
11698 : : /* Don't clone constraints whose parents are being cloned */
11699 [ + + ]: 859 : if (list_member_oid(clone, constrForm->conparentid))
11700 : : {
11701 : 472 : ReleaseSysCache(tuple);
11702 : 572 : continue;
11703 : : }
11704 : :
11705 : : /*
11706 : : * Need to prevent concurrent deletions. If pkrel is a partitioned
11707 : : * relation, that means to lock all partitions.
11708 : : */
11709 : 387 : pkrel = table_open(constrForm->confrelid, ShareRowExclusiveLock);
11710 [ + + ]: 387 : if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
11711 : 164 : (void) find_all_inheritors(RelationGetRelid(pkrel),
11712 : : ShareRowExclusiveLock, NULL);
11713 : :
11714 : 387 : DeconstructFkConstraintRow(tuple, &numfks, conkey, confkey,
11715 : : conpfeqop, conppeqop, conffeqop,
11716 : : &numfkdelsetcols, confdelsetcols);
11717 [ + + ]: 930 : for (int i = 0; i < numfks; i++)
11718 : 543 : mapped_conkey[i] = attmap->attnums[conkey[i] - 1];
11719 : :
11720 : : /*
11721 : : * Get the "check" triggers belonging to the constraint, if it is
11722 : : * ENFORCED, to pass as parent OIDs for similar triggers that will be
11723 : : * created on the partition in addFkRecurseReferencing(). They are
11724 : : * also passed to tryAttachPartitionForeignKey() below to simply
11725 : : * assign as parents to the partition's existing "check" triggers,
11726 : : * that is, if the corresponding constraints is deemed attachable to
11727 : : * the parent constraint.
11728 : : */
11729 [ + + ]: 387 : if (constrForm->conenforced)
11730 : 379 : GetForeignKeyCheckTriggers(trigrel, constrForm->oid,
11731 : : constrForm->confrelid, constrForm->conrelid,
11732 : : &insertTriggerOid, &updateTriggerOid);
11733 : :
11734 : : /*
11735 : : * Before creating a new constraint, see whether any existing FKs are
11736 : : * fit for the purpose. If one is, attach the parent constraint to
11737 : : * it, and don't clone anything. This way we avoid the expensive
11738 : : * verification step and don't end up with a duplicate FK, and we
11739 : : * don't need to recurse to partitions for this constraint.
11740 : : */
11741 : 387 : attached = false;
11742 [ + + + + : 447 : foreach(lc, partFKs)
+ + ]
11743 : : {
11744 : 164 : ForeignKeyCacheInfo *fk = lfirst_node(ForeignKeyCacheInfo, lc);
11745 : :
11746 [ + + ]: 164 : if (tryAttachPartitionForeignKey(wqueue,
11747 : : fk,
11748 : : partRel,
11749 : : parentConstrOid,
11750 : : numfks,
11751 : : mapped_conkey,
11752 : : confkey,
11753 : : conpfeqop,
11754 : : insertTriggerOid,
11755 : : updateTriggerOid,
11756 : : trigrel))
11757 : : {
11758 : 100 : attached = true;
11759 : 100 : table_close(pkrel, NoLock);
11760 : 100 : break;
11761 : : }
11762 : : }
11763 [ + + ]: 383 : if (attached)
11764 : : {
11765 : 100 : ReleaseSysCache(tuple);
11766 : 100 : continue;
11767 : : }
11768 : :
11769 : : /* No dice. Set up to create our own constraint */
11770 : 283 : fkconstraint = makeNode(Constraint);
11771 : 283 : fkconstraint->contype = CONSTRAINT_FOREIGN;
11772 : : /* ->conname determined below */
11773 : 283 : fkconstraint->deferrable = constrForm->condeferrable;
11774 : 283 : fkconstraint->initdeferred = constrForm->condeferred;
11775 : 283 : fkconstraint->location = -1;
11776 : 283 : fkconstraint->pktable = NULL;
11777 : : /* ->fk_attrs determined below */
11778 : 283 : fkconstraint->pk_attrs = NIL;
11779 : 283 : fkconstraint->fk_matchtype = constrForm->confmatchtype;
11780 : 283 : fkconstraint->fk_upd_action = constrForm->confupdtype;
11781 : 283 : fkconstraint->fk_del_action = constrForm->confdeltype;
11782 : 283 : fkconstraint->fk_del_set_cols = NIL;
11783 : 283 : fkconstraint->old_conpfeqop = NIL;
11784 : 283 : fkconstraint->old_pktable_oid = InvalidOid;
11785 : 283 : fkconstraint->is_enforced = constrForm->conenforced;
11786 : 283 : fkconstraint->skip_validation = false;
11787 : 283 : fkconstraint->initially_valid = constrForm->convalidated;
11788 [ + + ]: 646 : for (int i = 0; i < numfks; i++)
11789 : : {
11790 : : Form_pg_attribute att;
11791 : :
11792 : 363 : att = TupleDescAttr(RelationGetDescr(partRel),
11793 : 363 : mapped_conkey[i] - 1);
11794 : 363 : fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
11795 : 363 : makeString(NameStr(att->attname)));
11796 : : }
11797 : :
11798 : 283 : indexOid = constrForm->conindid;
11799 : 283 : with_period = constrForm->conperiod;
11800 : :
11801 : : /* Create the pg_constraint entry at this level */
11802 : 283 : address = addFkConstraint(addFkReferencingSide,
11803 : 283 : NameStr(constrForm->conname), fkconstraint,
11804 : : partRel, pkrel, indexOid, parentConstrOid,
11805 : : numfks, confkey,
11806 : : mapped_conkey, conpfeqop,
11807 : : conppeqop, conffeqop,
11808 : : numfkdelsetcols, confdelsetcols,
11809 : : false, with_period);
11810 : :
11811 : : /* Done with the cloned constraint's tuple */
11812 : 283 : ReleaseSysCache(tuple);
11813 : :
11814 : : /* Create the check triggers, and recurse to partitions, if any */
11815 : 283 : addFkRecurseReferencing(wqueue,
11816 : : fkconstraint,
11817 : : partRel,
11818 : : pkrel,
11819 : : indexOid,
11820 : : address.objectId,
11821 : : numfks,
11822 : : confkey,
11823 : : mapped_conkey,
11824 : : conpfeqop,
11825 : : conppeqop,
11826 : : conffeqop,
11827 : : numfkdelsetcols,
11828 : : confdelsetcols,
11829 : : false, /* no old check exists */
11830 : : AccessExclusiveLock,
11831 : : insertTriggerOid,
11832 : : updateTriggerOid,
11833 : : with_period);
11834 : 279 : table_close(pkrel, NoLock);
11835 : : }
11836 : :
11837 : 347 : table_close(trigrel, RowExclusiveLock);
11838 : : }
11839 : :
11840 : : /*
11841 : : * When the parent of a partition receives [the referencing side of] a foreign
11842 : : * key, we must propagate that foreign key to the partition. However, the
11843 : : * partition might already have an equivalent foreign key; this routine
11844 : : * compares the given ForeignKeyCacheInfo (in the partition) to the FK defined
11845 : : * by the other parameters. If they are equivalent, create the link between
11846 : : * the two constraints and return true.
11847 : : *
11848 : : * If the given FK does not match the one defined by rest of the params,
11849 : : * return false.
11850 : : */
11851 : : static bool
11852 : 183 : tryAttachPartitionForeignKey(List **wqueue,
11853 : : ForeignKeyCacheInfo *fk,
11854 : : Relation partition,
11855 : : Oid parentConstrOid,
11856 : : int numfks,
11857 : : AttrNumber *mapped_conkey,
11858 : : AttrNumber *confkey,
11859 : : Oid *conpfeqop,
11860 : : Oid parentInsTrigger,
11861 : : Oid parentUpdTrigger,
11862 : : Relation trigrel)
11863 : : {
11864 : : HeapTuple parentConstrTup;
11865 : : Form_pg_constraint parentConstr;
11866 : : HeapTuple partcontup;
11867 : : Form_pg_constraint partConstr;
11868 : :
11869 : 183 : parentConstrTup = SearchSysCache1(CONSTROID,
11870 : : ObjectIdGetDatum(parentConstrOid));
11871 [ - + ]: 183 : if (!HeapTupleIsValid(parentConstrTup))
11872 [ # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u", parentConstrOid);
11873 : 183 : parentConstr = (Form_pg_constraint) GETSTRUCT(parentConstrTup);
11874 : :
11875 : : /*
11876 : : * Do some quick & easy initial checks. If any of these fail, we cannot
11877 : : * use this constraint.
11878 : : */
11879 [ + - - + ]: 183 : if (fk->confrelid != parentConstr->confrelid || fk->nkeys != numfks)
11880 : : {
11881 : 0 : ReleaseSysCache(parentConstrTup);
11882 : 0 : return false;
11883 : : }
11884 [ + + ]: 505 : for (int i = 0; i < numfks; i++)
11885 : : {
11886 [ + + ]: 324 : if (fk->conkey[i] != mapped_conkey[i] ||
11887 [ + - ]: 322 : fk->confkey[i] != confkey[i] ||
11888 [ - + ]: 322 : fk->conpfeqop[i] != conpfeqop[i])
11889 : : {
11890 : 2 : ReleaseSysCache(parentConstrTup);
11891 : 2 : return false;
11892 : : }
11893 : : }
11894 : :
11895 : : /* Looks good so far; perform more extensive checks. */
11896 : 181 : partcontup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
11897 [ - + ]: 181 : if (!HeapTupleIsValid(partcontup))
11898 [ # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
11899 : 181 : partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
11900 : :
11901 : : /*
11902 : : * An error should be raised if the constraint enforceability is
11903 : : * different. Returning false without raising an error, as we do for other
11904 : : * attributes, could lead to a duplicate constraint with the same
11905 : : * enforceability as the parent. While this may be acceptable, it may not
11906 : : * be ideal. Therefore, it's better to raise an error and allow the user
11907 : : * to correct the enforceability before proceeding.
11908 : : */
11909 [ + + ]: 181 : if (partConstr->conenforced != parentConstr->conenforced)
11910 [ + - ]: 4 : ereport(ERROR,
11911 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
11912 : : errmsg("constraint \"%s\" enforceability conflicts with constraint \"%s\" on relation \"%s\"",
11913 : : NameStr(parentConstr->conname),
11914 : : NameStr(partConstr->conname),
11915 : : RelationGetRelationName(partition))));
11916 : :
11917 [ + + ]: 177 : if (OidIsValid(partConstr->conparentid) ||
11918 [ + + ]: 158 : partConstr->condeferrable != parentConstr->condeferrable ||
11919 [ + - ]: 140 : partConstr->condeferred != parentConstr->condeferred ||
11920 [ + + ]: 140 : partConstr->confupdtype != parentConstr->confupdtype ||
11921 [ + - ]: 117 : partConstr->confdeltype != parentConstr->confdeltype ||
11922 [ + + ]: 117 : partConstr->confmatchtype != parentConstr->confmatchtype)
11923 : : {
11924 : 69 : ReleaseSysCache(parentConstrTup);
11925 : 69 : ReleaseSysCache(partcontup);
11926 : 69 : return false;
11927 : : }
11928 : :
11929 : 108 : ReleaseSysCache(parentConstrTup);
11930 : 108 : ReleaseSysCache(partcontup);
11931 : :
11932 : : /* Looks good! Attach this constraint. */
11933 : 108 : AttachPartitionForeignKey(wqueue, partition, fk->conoid,
11934 : : parentConstrOid, parentInsTrigger,
11935 : : parentUpdTrigger, trigrel);
11936 : :
11937 : 108 : return true;
11938 : : }
11939 : :
11940 : : /*
11941 : : * AttachPartitionForeignKey
11942 : : *
11943 : : * The subroutine for tryAttachPartitionForeignKey performs the final tasks of
11944 : : * attaching the constraint, removing redundant triggers and entries from
11945 : : * pg_constraint, and setting the constraint's parent.
11946 : : */
11947 : : static void
11948 : 108 : AttachPartitionForeignKey(List **wqueue,
11949 : : Relation partition,
11950 : : Oid partConstrOid,
11951 : : Oid parentConstrOid,
11952 : : Oid parentInsTrigger,
11953 : : Oid parentUpdTrigger,
11954 : : Relation trigrel)
11955 : : {
11956 : : HeapTuple parentConstrTup;
11957 : : Form_pg_constraint parentConstr;
11958 : : HeapTuple partcontup;
11959 : : Form_pg_constraint partConstr;
11960 : : bool queueValidation;
11961 : : Oid partConstrFrelid;
11962 : : Oid partConstrRelid;
11963 : : bool parentConstrIsEnforced;
11964 : :
11965 : : /* Fetch the parent constraint tuple */
11966 : 108 : parentConstrTup = SearchSysCache1(CONSTROID,
11967 : : ObjectIdGetDatum(parentConstrOid));
11968 [ - + ]: 108 : if (!HeapTupleIsValid(parentConstrTup))
11969 [ # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u", parentConstrOid);
11970 : 108 : parentConstr = (Form_pg_constraint) GETSTRUCT(parentConstrTup);
11971 : 108 : parentConstrIsEnforced = parentConstr->conenforced;
11972 : :
11973 : : /* Fetch the child constraint tuple */
11974 : 108 : partcontup = SearchSysCache1(CONSTROID,
11975 : : ObjectIdGetDatum(partConstrOid));
11976 [ - + ]: 108 : if (!HeapTupleIsValid(partcontup))
11977 [ # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u", partConstrOid);
11978 : 108 : partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
11979 : 108 : partConstrFrelid = partConstr->confrelid;
11980 : 108 : partConstrRelid = partConstr->conrelid;
11981 : :
11982 : : /*
11983 : : * If the referenced table is partitioned, then the partition we're
11984 : : * attaching now has extra pg_constraint rows and action triggers that are
11985 : : * no longer needed. Remove those.
11986 : : */
11987 [ + + ]: 108 : if (get_rel_relkind(partConstrFrelid) == RELKIND_PARTITIONED_TABLE)
11988 : : {
11989 : 24 : Relation pg_constraint = table_open(ConstraintRelationId, RowShareLock);
11990 : :
11991 : 24 : RemoveInheritedConstraint(pg_constraint, trigrel, partConstrOid,
11992 : : partConstrRelid);
11993 : :
11994 : 24 : table_close(pg_constraint, RowShareLock);
11995 : : }
11996 : :
11997 : : /*
11998 : : * Will we need to validate this constraint? A valid parent constraint
11999 : : * implies that all child constraints have been validated, so if this one
12000 : : * isn't, we must trigger phase 3 validation.
12001 : : */
12002 [ + + + + ]: 108 : queueValidation = parentConstr->convalidated && !partConstr->convalidated;
12003 : :
12004 : 108 : ReleaseSysCache(partcontup);
12005 : 108 : ReleaseSysCache(parentConstrTup);
12006 : :
12007 : : /*
12008 : : * The action triggers in the new partition become redundant -- the parent
12009 : : * table already has equivalent ones, and those will be able to reach the
12010 : : * partition. Remove the ones in the partition. We identify them because
12011 : : * they have our constraint OID, as well as being on the referenced rel.
12012 : : */
12013 : 108 : DropForeignKeyConstraintTriggers(trigrel, partConstrOid, partConstrFrelid,
12014 : : partConstrRelid);
12015 : :
12016 : 108 : ConstraintSetParentConstraint(partConstrOid, parentConstrOid,
12017 : : RelationGetRelid(partition));
12018 : :
12019 : : /*
12020 : : * Like the constraint, attach partition's "check" triggers to the
12021 : : * corresponding parent triggers if the constraint is ENFORCED. NOT
12022 : : * ENFORCED constraints do not have these triggers.
12023 : : */
12024 [ + + ]: 108 : if (parentConstrIsEnforced)
12025 : : {
12026 : : Oid insertTriggerOid,
12027 : : updateTriggerOid;
12028 : :
12029 : 100 : GetForeignKeyCheckTriggers(trigrel,
12030 : : partConstrOid, partConstrFrelid, partConstrRelid,
12031 : : &insertTriggerOid, &updateTriggerOid);
12032 : : Assert(OidIsValid(insertTriggerOid) && OidIsValid(parentInsTrigger));
12033 : 100 : TriggerSetParentTrigger(trigrel, insertTriggerOid, parentInsTrigger,
12034 : : RelationGetRelid(partition));
12035 : : Assert(OidIsValid(updateTriggerOid) && OidIsValid(parentUpdTrigger));
12036 : 100 : TriggerSetParentTrigger(trigrel, updateTriggerOid, parentUpdTrigger,
12037 : : RelationGetRelid(partition));
12038 : : }
12039 : :
12040 : : /*
12041 : : * We updated this pg_constraint row above to set its parent; validating
12042 : : * it will cause its convalidated flag to change, so we need CCI here. In
12043 : : * addition, we need it unconditionally for the rare case where the parent
12044 : : * table has *two* identical constraints; when reaching this function for
12045 : : * the second one, we must have made our changes visible, otherwise we
12046 : : * would try to attach both to this one.
12047 : : */
12048 : 108 : CommandCounterIncrement();
12049 : :
12050 : : /* If validation is needed, put it in the queue now. */
12051 [ + + ]: 108 : if (queueValidation)
12052 : : {
12053 : : Relation conrel;
12054 : : Oid confrelid;
12055 : :
12056 : 12 : conrel = table_open(ConstraintRelationId, RowExclusiveLock);
12057 : :
12058 : 12 : partcontup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(partConstrOid));
12059 [ - + ]: 12 : if (!HeapTupleIsValid(partcontup))
12060 [ # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u", partConstrOid);
12061 : :
12062 : 12 : confrelid = ((Form_pg_constraint) GETSTRUCT(partcontup))->confrelid;
12063 : :
12064 : : /* Use the same lock as for AT_ValidateConstraint */
12065 : 12 : QueueFKConstraintValidation(wqueue, conrel, partition, confrelid,
12066 : : partcontup, ShareUpdateExclusiveLock);
12067 : 12 : ReleaseSysCache(partcontup);
12068 : 12 : table_close(conrel, RowExclusiveLock);
12069 : : }
12070 : 108 : }
12071 : :
12072 : : /*
12073 : : * RemoveInheritedConstraint
12074 : : *
12075 : : * Removes the constraint and its associated trigger from the specified
12076 : : * relation, which inherited the given constraint.
12077 : : */
12078 : : static void
12079 : 24 : RemoveInheritedConstraint(Relation conrel, Relation trigrel, Oid conoid,
12080 : : Oid conrelid)
12081 : : {
12082 : : ObjectAddresses *objs;
12083 : : HeapTuple consttup;
12084 : : ScanKeyData key;
12085 : : SysScanDesc scan;
12086 : : HeapTuple trigtup;
12087 : :
12088 : 24 : ScanKeyInit(&key,
12089 : : Anum_pg_constraint_conrelid,
12090 : : BTEqualStrategyNumber, F_OIDEQ,
12091 : : ObjectIdGetDatum(conrelid));
12092 : :
12093 : 24 : scan = systable_beginscan(conrel,
12094 : : ConstraintRelidTypidNameIndexId,
12095 : : true, NULL, 1, &key);
12096 : 24 : objs = new_object_addresses();
12097 [ + + ]: 216 : while ((consttup = systable_getnext(scan)) != NULL)
12098 : : {
12099 : 192 : Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(consttup);
12100 : :
12101 [ + + ]: 192 : if (conform->conparentid != conoid)
12102 : 140 : continue;
12103 : : else
12104 : : {
12105 : : ObjectAddress addr;
12106 : : SysScanDesc scan2;
12107 : : ScanKeyData key2;
12108 : : int n PG_USED_FOR_ASSERTS_ONLY;
12109 : :
12110 : 52 : ObjectAddressSet(addr, ConstraintRelationId, conform->oid);
12111 : 52 : add_exact_object_address(&addr, objs);
12112 : :
12113 : : /*
12114 : : * First we must delete the dependency record that binds the
12115 : : * constraint records together.
12116 : : */
12117 : 52 : n = deleteDependencyRecordsForSpecific(ConstraintRelationId,
12118 : : conform->oid,
12119 : : DEPENDENCY_INTERNAL,
12120 : : ConstraintRelationId,
12121 : : conoid);
12122 : : Assert(n == 1); /* actually only one is expected */
12123 : :
12124 : : /*
12125 : : * Now search for the triggers for this constraint and set them up
12126 : : * for deletion too
12127 : : */
12128 : 52 : ScanKeyInit(&key2,
12129 : : Anum_pg_trigger_tgconstraint,
12130 : : BTEqualStrategyNumber, F_OIDEQ,
12131 : : ObjectIdGetDatum(conform->oid));
12132 : 52 : scan2 = systable_beginscan(trigrel, TriggerConstraintIndexId,
12133 : : true, NULL, 1, &key2);
12134 [ + + ]: 156 : while ((trigtup = systable_getnext(scan2)) != NULL)
12135 : : {
12136 : 104 : ObjectAddressSet(addr, TriggerRelationId,
12137 : : ((Form_pg_trigger) GETSTRUCT(trigtup))->oid);
12138 : 104 : add_exact_object_address(&addr, objs);
12139 : : }
12140 : 52 : systable_endscan(scan2);
12141 : : }
12142 : : }
12143 : : /* make the dependency deletions visible */
12144 : 24 : CommandCounterIncrement();
12145 : 24 : performMultipleDeletions(objs, DROP_RESTRICT,
12146 : : PERFORM_DELETION_INTERNAL);
12147 : 24 : systable_endscan(scan);
12148 : 24 : }
12149 : :
12150 : : /*
12151 : : * DropForeignKeyConstraintTriggers
12152 : : *
12153 : : * The subroutine for tryAttachPartitionForeignKey handles the deletion of
12154 : : * action triggers for the foreign key constraint.
12155 : : *
12156 : : * If valid confrelid and conrelid values are not provided, the respective
12157 : : * trigger check will be skipped, and the trigger will be considered for
12158 : : * removal.
12159 : : */
12160 : : static void
12161 : 160 : DropForeignKeyConstraintTriggers(Relation trigrel, Oid conoid, Oid confrelid,
12162 : : Oid conrelid)
12163 : : {
12164 : : ScanKeyData key;
12165 : : SysScanDesc scan;
12166 : : HeapTuple trigtup;
12167 : :
12168 : 160 : ScanKeyInit(&key,
12169 : : Anum_pg_trigger_tgconstraint,
12170 : : BTEqualStrategyNumber, F_OIDEQ,
12171 : : ObjectIdGetDatum(conoid));
12172 : 160 : scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
12173 : : NULL, 1, &key);
12174 [ + + ]: 696 : while ((trigtup = systable_getnext(scan)) != NULL)
12175 : : {
12176 : 536 : Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
12177 : : ObjectAddress trigger;
12178 : :
12179 : : /* Invalid if trigger is not for a referential integrity constraint */
12180 [ - + ]: 536 : if (!OidIsValid(trgform->tgconstrrelid))
12181 : 200 : continue;
12182 [ + + + + ]: 536 : if (OidIsValid(conrelid) && trgform->tgconstrrelid != conrelid)
12183 : 200 : continue;
12184 [ + + - + ]: 336 : if (OidIsValid(confrelid) && trgform->tgrelid != confrelid)
12185 : 0 : continue;
12186 : :
12187 : : /* We should be dropping trigger related to foreign key constraint */
12188 : : Assert(trgform->tgfoid == F_RI_FKEY_CHECK_INS ||
12189 : : trgform->tgfoid == F_RI_FKEY_CHECK_UPD ||
12190 : : trgform->tgfoid == F_RI_FKEY_CASCADE_DEL ||
12191 : : trgform->tgfoid == F_RI_FKEY_CASCADE_UPD ||
12192 : : trgform->tgfoid == F_RI_FKEY_RESTRICT_DEL ||
12193 : : trgform->tgfoid == F_RI_FKEY_RESTRICT_UPD ||
12194 : : trgform->tgfoid == F_RI_FKEY_SETNULL_DEL ||
12195 : : trgform->tgfoid == F_RI_FKEY_SETNULL_UPD ||
12196 : : trgform->tgfoid == F_RI_FKEY_SETDEFAULT_DEL ||
12197 : : trgform->tgfoid == F_RI_FKEY_SETDEFAULT_UPD ||
12198 : : trgform->tgfoid == F_RI_FKEY_NOACTION_DEL ||
12199 : : trgform->tgfoid == F_RI_FKEY_NOACTION_UPD);
12200 : :
12201 : : /*
12202 : : * The constraint is originally set up to contain this trigger as an
12203 : : * implementation object, so there's a dependency record that links
12204 : : * the two; however, since the trigger is no longer needed, we remove
12205 : : * the dependency link in order to be able to drop the trigger while
12206 : : * keeping the constraint intact.
12207 : : */
12208 : 336 : deleteDependencyRecordsFor(TriggerRelationId,
12209 : : trgform->oid,
12210 : : false);
12211 : : /* make dependency deletion visible to performDeletion */
12212 : 336 : CommandCounterIncrement();
12213 : 336 : ObjectAddressSet(trigger, TriggerRelationId,
12214 : : trgform->oid);
12215 : 336 : performDeletion(&trigger, DROP_RESTRICT, 0);
12216 : : /* make trigger drop visible, in case the loop iterates */
12217 : 336 : CommandCounterIncrement();
12218 : : }
12219 : :
12220 : 160 : systable_endscan(scan);
12221 : 160 : }
12222 : :
12223 : : /*
12224 : : * GetForeignKeyActionTriggers
12225 : : * Returns delete and update "action" triggers of the given relation
12226 : : * belonging to the given constraint
12227 : : */
12228 : : static void
12229 : 128 : GetForeignKeyActionTriggers(Relation trigrel,
12230 : : Oid conoid, Oid confrelid, Oid conrelid,
12231 : : Oid *deleteTriggerOid,
12232 : : Oid *updateTriggerOid)
12233 : : {
12234 : : ScanKeyData key;
12235 : : SysScanDesc scan;
12236 : : HeapTuple trigtup;
12237 : :
12238 : 128 : *deleteTriggerOid = *updateTriggerOid = InvalidOid;
12239 : 128 : ScanKeyInit(&key,
12240 : : Anum_pg_trigger_tgconstraint,
12241 : : BTEqualStrategyNumber, F_OIDEQ,
12242 : : ObjectIdGetDatum(conoid));
12243 : :
12244 : 128 : scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
12245 : : NULL, 1, &key);
12246 [ + - ]: 259 : while ((trigtup = systable_getnext(scan)) != NULL)
12247 : : {
12248 : 259 : Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
12249 : :
12250 [ + + ]: 259 : if (trgform->tgconstrrelid != conrelid)
12251 : 3 : continue;
12252 [ - + ]: 256 : if (trgform->tgrelid != confrelid)
12253 : 0 : continue;
12254 : : /* Only ever look at "action" triggers on the PK side. */
12255 [ - + ]: 256 : if (RI_FKey_trigger_type(trgform->tgfoid) != RI_TRIGGER_PK)
12256 : 0 : continue;
12257 [ + + ]: 256 : if (TRIGGER_FOR_DELETE(trgform->tgtype))
12258 : : {
12259 : : Assert(*deleteTriggerOid == InvalidOid);
12260 : 128 : *deleteTriggerOid = trgform->oid;
12261 : : }
12262 [ + - ]: 128 : else if (TRIGGER_FOR_UPDATE(trgform->tgtype))
12263 : : {
12264 : : Assert(*updateTriggerOid == InvalidOid);
12265 : 128 : *updateTriggerOid = trgform->oid;
12266 : : }
12267 : : #ifndef USE_ASSERT_CHECKING
12268 : : /* In an assert-enabled build, continue looking to find duplicates */
12269 [ + - + + ]: 256 : if (OidIsValid(*deleteTriggerOid) && OidIsValid(*updateTriggerOid))
12270 : 128 : break;
12271 : : #endif
12272 : : }
12273 : :
12274 [ - + ]: 128 : if (!OidIsValid(*deleteTriggerOid))
12275 [ # # ]: 0 : elog(ERROR, "could not find ON DELETE action trigger of foreign key constraint %u",
12276 : : conoid);
12277 [ - + ]: 128 : if (!OidIsValid(*updateTriggerOid))
12278 [ # # ]: 0 : elog(ERROR, "could not find ON UPDATE action trigger of foreign key constraint %u",
12279 : : conoid);
12280 : :
12281 : 128 : systable_endscan(scan);
12282 : 128 : }
12283 : :
12284 : : /*
12285 : : * GetForeignKeyCheckTriggers
12286 : : * Returns insert and update "check" triggers of the given relation
12287 : : * belonging to the given constraint
12288 : : */
12289 : : static void
12290 : 551 : GetForeignKeyCheckTriggers(Relation trigrel,
12291 : : Oid conoid, Oid confrelid, Oid conrelid,
12292 : : Oid *insertTriggerOid,
12293 : : Oid *updateTriggerOid)
12294 : : {
12295 : : ScanKeyData key;
12296 : : SysScanDesc scan;
12297 : : HeapTuple trigtup;
12298 : :
12299 : 551 : *insertTriggerOid = *updateTriggerOid = InvalidOid;
12300 : 551 : ScanKeyInit(&key,
12301 : : Anum_pg_trigger_tgconstraint,
12302 : : BTEqualStrategyNumber, F_OIDEQ,
12303 : : ObjectIdGetDatum(conoid));
12304 : :
12305 : 551 : scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
12306 : : NULL, 1, &key);
12307 [ + - ]: 1771 : while ((trigtup = systable_getnext(scan)) != NULL)
12308 : : {
12309 : 1771 : Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
12310 : :
12311 [ + + ]: 1771 : if (trgform->tgconstrrelid != confrelid)
12312 : 597 : continue;
12313 [ - + ]: 1174 : if (trgform->tgrelid != conrelid)
12314 : 0 : continue;
12315 : : /* Only ever look at "check" triggers on the FK side. */
12316 [ + + ]: 1174 : if (RI_FKey_trigger_type(trgform->tgfoid) != RI_TRIGGER_FK)
12317 : 72 : continue;
12318 [ + + ]: 1102 : if (TRIGGER_FOR_INSERT(trgform->tgtype))
12319 : : {
12320 : : Assert(*insertTriggerOid == InvalidOid);
12321 : 551 : *insertTriggerOid = trgform->oid;
12322 : : }
12323 [ + - ]: 551 : else if (TRIGGER_FOR_UPDATE(trgform->tgtype))
12324 : : {
12325 : : Assert(*updateTriggerOid == InvalidOid);
12326 : 551 : *updateTriggerOid = trgform->oid;
12327 : : }
12328 : : #ifndef USE_ASSERT_CHECKING
12329 : : /* In an assert-enabled build, continue looking to find duplicates. */
12330 [ + + + + ]: 1102 : if (OidIsValid(*insertTriggerOid) && OidIsValid(*updateTriggerOid))
12331 : 551 : break;
12332 : : #endif
12333 : : }
12334 : :
12335 [ - + ]: 551 : if (!OidIsValid(*insertTriggerOid))
12336 [ # # ]: 0 : elog(ERROR, "could not find ON INSERT check triggers of foreign key constraint %u",
12337 : : conoid);
12338 [ - + ]: 551 : if (!OidIsValid(*updateTriggerOid))
12339 [ # # ]: 0 : elog(ERROR, "could not find ON UPDATE check triggers of foreign key constraint %u",
12340 : : conoid);
12341 : :
12342 : 551 : systable_endscan(scan);
12343 : 551 : }
12344 : :
12345 : : /*
12346 : : * ALTER TABLE ALTER CONSTRAINT
12347 : : *
12348 : : * Update the attributes of a constraint.
12349 : : *
12350 : : * Currently works for Foreign Key, Check, and not null constraints.
12351 : : *
12352 : : * If the constraint is modified, returns its address; otherwise, return
12353 : : * InvalidObjectAddress.
12354 : : */
12355 : : static ObjectAddress
12356 : 336 : ATExecAlterConstraint(List **wqueue, Relation rel, ATAlterConstraint *cmdcon,
12357 : : bool recurse, LOCKMODE lockmode)
12358 : : {
12359 : : Relation conrel;
12360 : : Relation tgrel;
12361 : : SysScanDesc scan;
12362 : : ScanKeyData skey[3];
12363 : : HeapTuple contuple;
12364 : : Form_pg_constraint currcon;
12365 : : ObjectAddress address;
12366 : :
12367 : : /*
12368 : : * Disallow altering ONLY a partitioned table, as it would make no sense.
12369 : : * This is okay for legacy inheritance.
12370 : : */
12371 [ + + - + ]: 336 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
12372 [ # # ]: 0 : ereport(ERROR,
12373 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
12374 : : errmsg("constraint must be altered in child tables too"),
12375 : : errhint("Do not specify the ONLY keyword."));
12376 : :
12377 : :
12378 : 336 : conrel = table_open(ConstraintRelationId, RowExclusiveLock);
12379 : 336 : tgrel = table_open(TriggerRelationId, RowExclusiveLock);
12380 : :
12381 : : /*
12382 : : * Find and check the target constraint
12383 : : */
12384 : 336 : ScanKeyInit(&skey[0],
12385 : : Anum_pg_constraint_conrelid,
12386 : : BTEqualStrategyNumber, F_OIDEQ,
12387 : : ObjectIdGetDatum(RelationGetRelid(rel)));
12388 : 336 : ScanKeyInit(&skey[1],
12389 : : Anum_pg_constraint_contypid,
12390 : : BTEqualStrategyNumber, F_OIDEQ,
12391 : : ObjectIdGetDatum(InvalidOid));
12392 : 336 : ScanKeyInit(&skey[2],
12393 : : Anum_pg_constraint_conname,
12394 : : BTEqualStrategyNumber, F_NAMEEQ,
12395 : 336 : CStringGetDatum(cmdcon->conname));
12396 : 336 : scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId,
12397 : : true, NULL, 3, skey);
12398 : :
12399 : : /* There can be at most one matching row */
12400 [ + + ]: 336 : if (!HeapTupleIsValid(contuple = systable_getnext(scan)))
12401 [ + - ]: 4 : ereport(ERROR,
12402 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
12403 : : errmsg("constraint \"%s\" of relation \"%s\" does not exist",
12404 : : cmdcon->conname, RelationGetRelationName(rel))));
12405 : :
12406 : 332 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
12407 [ + + - + ]: 332 : if (cmdcon->alterDeferrability && currcon->contype != CONSTRAINT_FOREIGN)
12408 [ # # ]: 0 : ereport(ERROR,
12409 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
12410 : : errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint",
12411 : : cmdcon->conname, RelationGetRelationName(rel))));
12412 [ + + ]: 332 : if (cmdcon->alterEnforceability &&
12413 [ + + + + ]: 196 : (currcon->contype != CONSTRAINT_FOREIGN && currcon->contype != CONSTRAINT_CHECK))
12414 [ + - ]: 8 : ereport(ERROR,
12415 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
12416 : : errmsg("cannot alter enforceability of constraint \"%s\" of relation \"%s\"",
12417 : : cmdcon->conname, RelationGetRelationName(rel)),
12418 : : errhint("Only foreign key and check constraints can change enforceability.")));
12419 [ + + ]: 324 : if (cmdcon->alterInheritability &&
12420 [ + + ]: 64 : currcon->contype != CONSTRAINT_NOTNULL)
12421 [ + - ]: 16 : ereport(ERROR,
12422 : : errcode(ERRCODE_WRONG_OBJECT_TYPE),
12423 : : errmsg("constraint \"%s\" of relation \"%s\" is not a not-null constraint",
12424 : : cmdcon->conname, RelationGetRelationName(rel)));
12425 [ + + ]: 308 : if (cmdcon->alterInheritability &&
12426 [ + + + + ]: 48 : cmdcon->noinherit && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
12427 [ + - ]: 4 : ereport(ERROR,
12428 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
12429 : : errmsg("not-null constraint \"%s\" on partitioned table \"%s\" cannot be NO INHERIT",
12430 : : cmdcon->conname, RelationGetRelationName(rel)));
12431 : :
12432 : : /* Refuse to modify inheritability of inherited constraints */
12433 [ + + ]: 304 : if (cmdcon->alterInheritability &&
12434 [ + + + + ]: 44 : cmdcon->noinherit && currcon->coninhcount > 0)
12435 [ + - ]: 4 : ereport(ERROR,
12436 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
12437 : : errmsg("cannot alter inherited constraint \"%s\" on relation \"%s\"",
12438 : : NameStr(currcon->conname),
12439 : : RelationGetRelationName(rel)));
12440 : :
12441 : : /*
12442 : : * If it's not the topmost constraint, raise an error.
12443 : : *
12444 : : * Altering a non-topmost constraint leaves some triggers untouched, since
12445 : : * they are not directly connected to this constraint; also, pg_dump would
12446 : : * ignore the deferrability status of the individual constraint, since it
12447 : : * only dumps topmost constraints. Avoid these problems by refusing this
12448 : : * operation and telling the user to alter the parent constraint instead.
12449 : : */
12450 [ + + ]: 300 : if (OidIsValid(currcon->conparentid))
12451 : : {
12452 : : HeapTuple tp;
12453 : 8 : Oid parent = currcon->conparentid;
12454 : 8 : char *ancestorname = NULL;
12455 : 8 : char *ancestortable = NULL;
12456 : :
12457 : : /* Loop to find the topmost constraint */
12458 [ + - ]: 16 : while (HeapTupleIsValid(tp = SearchSysCache1(CONSTROID, ObjectIdGetDatum(parent))))
12459 : : {
12460 : 16 : Form_pg_constraint contup = (Form_pg_constraint) GETSTRUCT(tp);
12461 : :
12462 : : /* If no parent, this is the constraint we want */
12463 [ + + ]: 16 : if (!OidIsValid(contup->conparentid))
12464 : : {
12465 : 8 : ancestorname = pstrdup(NameStr(contup->conname));
12466 : 8 : ancestortable = get_rel_name(contup->conrelid);
12467 : 8 : ReleaseSysCache(tp);
12468 : 8 : break;
12469 : : }
12470 : :
12471 : 8 : parent = contup->conparentid;
12472 : 8 : ReleaseSysCache(tp);
12473 : : }
12474 : :
12475 [ + - + - : 8 : ereport(ERROR,
+ - ]
12476 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
12477 : : errmsg("cannot alter constraint \"%s\" on relation \"%s\"",
12478 : : cmdcon->conname, RelationGetRelationName(rel)),
12479 : : ancestorname && ancestortable ?
12480 : : errdetail("Constraint \"%s\" is derived from constraint \"%s\" of relation \"%s\".",
12481 : : cmdcon->conname, ancestorname, ancestortable) : 0,
12482 : : errhint("You may alter the constraint it derives from instead.")));
12483 : : }
12484 : :
12485 : 292 : address = InvalidObjectAddress;
12486 : :
12487 : : /*
12488 : : * Do the actual catalog work, and recurse if necessary.
12489 : : */
12490 [ + + ]: 292 : if (ATExecAlterConstraintInternal(wqueue, cmdcon, conrel, tgrel, rel,
12491 : : contuple, recurse, lockmode))
12492 : 244 : ObjectAddressSet(address, ConstraintRelationId, currcon->oid);
12493 : :
12494 : 260 : systable_endscan(scan);
12495 : :
12496 : 260 : table_close(tgrel, RowExclusiveLock);
12497 : 260 : table_close(conrel, RowExclusiveLock);
12498 : :
12499 : 260 : return address;
12500 : : }
12501 : :
12502 : : /*
12503 : : * A subroutine of ATExecAlterConstraint that calls the respective routines for
12504 : : * altering constraint's enforceability, deferrability or inheritability.
12505 : : */
12506 : : static bool
12507 : 292 : ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon,
12508 : : Relation conrel, Relation tgrel, Relation rel,
12509 : : HeapTuple contuple, bool recurse,
12510 : : LOCKMODE lockmode)
12511 : : {
12512 : : Form_pg_constraint currcon;
12513 : 292 : bool changed = false;
12514 : 292 : List *otherrelids = NIL;
12515 : :
12516 : 292 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
12517 : :
12518 : : /*
12519 : : * Do the catalog work for the enforceability or deferrability change,
12520 : : * recurse if necessary.
12521 : : *
12522 : : * Note that even if deferrability is requested to be altered along with
12523 : : * enforceability, we don't need to explicitly update multiple entries in
12524 : : * pg_trigger related to deferrability.
12525 : : *
12526 : : * Modifying foreign key enforceability involves either creating or
12527 : : * dropping the trigger, during which the deferrability setting will be
12528 : : * adjusted automatically.
12529 : : */
12530 [ + + ]: 292 : if (cmdcon->alterEnforceability)
12531 : : {
12532 [ + + ]: 188 : if (currcon->contype == CONSTRAINT_FOREIGN)
12533 : 60 : changed = ATExecAlterFKConstrEnforceability(wqueue, cmdcon, conrel, tgrel,
12534 : : currcon->conrelid,
12535 : : currcon->confrelid,
12536 : : contuple, lockmode,
12537 : : InvalidOid, InvalidOid,
12538 : : InvalidOid, InvalidOid);
12539 [ + - ]: 128 : else if (currcon->contype == CONSTRAINT_CHECK)
12540 : 128 : changed = ATExecAlterCheckConstrEnforceability(wqueue, cmdcon, conrel,
12541 : : contuple, recurse, false,
12542 : : NIL, lockmode);
12543 : : }
12544 [ + + + - ]: 168 : else if (cmdcon->alterDeferrability &&
12545 : 64 : ATExecAlterConstrDeferrability(wqueue, cmdcon, conrel, tgrel, rel,
12546 : : contuple, recurse, &otherrelids,
12547 : : lockmode))
12548 : : {
12549 : : /*
12550 : : * AlterConstrUpdateConstraintEntry already invalidated relcache for
12551 : : * the relations having the constraint itself; here we also invalidate
12552 : : * for relations that have any triggers that are part of the
12553 : : * constraint.
12554 : : */
12555 [ + + + + : 204 : foreach_oid(relid, otherrelids)
+ + ]
12556 : 76 : CacheInvalidateRelcacheByRelid(relid);
12557 : :
12558 : 64 : changed = true;
12559 : : }
12560 : :
12561 : : /*
12562 : : * Do the catalog work for the inheritability change.
12563 : : */
12564 [ + + + - ]: 300 : if (cmdcon->alterInheritability &&
12565 : 40 : ATExecAlterConstrInheritability(wqueue, cmdcon, conrel, rel, contuple,
12566 : : lockmode))
12567 : 36 : changed = true;
12568 : :
12569 : 260 : return changed;
12570 : : }
12571 : :
12572 : : /*
12573 : : * Returns true if the foreign key constraint's enforceability is altered.
12574 : : *
12575 : : * Depending on whether the constraint is being set to ENFORCED or NOT
12576 : : * ENFORCED, it creates or drops the trigger accordingly.
12577 : : *
12578 : : * Note that we must recurse even when trying to change a constraint to not
12579 : : * enforced if it is already not enforced, in case descendant constraints
12580 : : * might be enforced and need to be changed to not enforced. Conversely, we
12581 : : * should do nothing if a constraint is being set to enforced and is already
12582 : : * enforced, as descendant constraints cannot be different in that case.
12583 : : */
12584 : : static bool
12585 : 128 : ATExecAlterFKConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon,
12586 : : Relation conrel, Relation tgrel,
12587 : : Oid fkrelid, Oid pkrelid,
12588 : : HeapTuple contuple, LOCKMODE lockmode,
12589 : : Oid ReferencedParentDelTrigger,
12590 : : Oid ReferencedParentUpdTrigger,
12591 : : Oid ReferencingParentInsTrigger,
12592 : : Oid ReferencingParentUpdTrigger)
12593 : : {
12594 : : Form_pg_constraint currcon;
12595 : : Oid conoid;
12596 : : Relation rel;
12597 : 128 : bool changed = false;
12598 : :
12599 : : /* Since this function recurses, it could be driven to stack overflow */
12600 : 128 : check_stack_depth();
12601 : :
12602 : : Assert(cmdcon->alterEnforceability);
12603 : :
12604 : 128 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
12605 : 128 : conoid = currcon->oid;
12606 : :
12607 : : /* Should be foreign key constraint */
12608 : : Assert(currcon->contype == CONSTRAINT_FOREIGN);
12609 : :
12610 : 128 : rel = table_open(currcon->conrelid, lockmode);
12611 : :
12612 [ + + ]: 128 : if (currcon->conenforced != cmdcon->is_enforced)
12613 : : {
12614 : 124 : AlterConstrUpdateConstraintEntry(cmdcon, conrel, contuple);
12615 : 124 : changed = true;
12616 : : }
12617 : :
12618 : : /* Drop triggers */
12619 [ + + ]: 128 : if (!cmdcon->is_enforced)
12620 : : {
12621 : : /*
12622 : : * When setting a constraint to NOT ENFORCED, the constraint triggers
12623 : : * need to be dropped. Therefore, we must process the child relations
12624 : : * first, followed by the parent, to account for dependencies.
12625 : : */
12626 [ + + - + ]: 92 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
12627 : 40 : get_rel_relkind(currcon->confrelid) == RELKIND_PARTITIONED_TABLE)
12628 : 12 : AlterFKConstrEnforceabilityRecurse(wqueue, cmdcon, conrel, tgrel,
12629 : : fkrelid, pkrelid, contuple,
12630 : : lockmode, InvalidOid, InvalidOid,
12631 : : InvalidOid, InvalidOid);
12632 : :
12633 : : /* Drop all the triggers */
12634 : 52 : DropForeignKeyConstraintTriggers(tgrel, conoid, InvalidOid, InvalidOid);
12635 : : }
12636 [ + - ]: 76 : else if (changed) /* Create triggers */
12637 : : {
12638 : 76 : Oid ReferencedDelTriggerOid = InvalidOid,
12639 : 76 : ReferencedUpdTriggerOid = InvalidOid,
12640 : 76 : ReferencingInsTriggerOid = InvalidOid,
12641 : 76 : ReferencingUpdTriggerOid = InvalidOid;
12642 : :
12643 : : /* Prepare the minimal information required for trigger creation. */
12644 : 76 : Constraint *fkconstraint = makeNode(Constraint);
12645 : :
12646 : 76 : fkconstraint->conname = pstrdup(NameStr(currcon->conname));
12647 : 76 : fkconstraint->fk_matchtype = currcon->confmatchtype;
12648 : 76 : fkconstraint->fk_upd_action = currcon->confupdtype;
12649 : 76 : fkconstraint->fk_del_action = currcon->confdeltype;
12650 : 76 : fkconstraint->deferrable = currcon->condeferrable;
12651 : 76 : fkconstraint->initdeferred = currcon->condeferred;
12652 : :
12653 : : /* Create referenced triggers */
12654 [ + + ]: 76 : if (currcon->conrelid == fkrelid)
12655 : 48 : createForeignKeyActionTriggers(currcon->conrelid,
12656 : : currcon->confrelid,
12657 : : fkconstraint,
12658 : : conoid,
12659 : : currcon->conindid,
12660 : : ReferencedParentDelTrigger,
12661 : : ReferencedParentUpdTrigger,
12662 : : &ReferencedDelTriggerOid,
12663 : : &ReferencedUpdTriggerOid);
12664 : :
12665 : : /* Create referencing triggers */
12666 [ + + ]: 76 : if (currcon->confrelid == pkrelid)
12667 : 64 : createForeignKeyCheckTriggers(currcon->conrelid,
12668 : : pkrelid,
12669 : : fkconstraint,
12670 : : conoid,
12671 : : currcon->conindid,
12672 : : ReferencingParentInsTrigger,
12673 : : ReferencingParentUpdTrigger,
12674 : : &ReferencingInsTriggerOid,
12675 : : &ReferencingUpdTriggerOid);
12676 : :
12677 : : /*
12678 : : * Tell Phase 3 to check that the constraint is satisfied by existing
12679 : : * rows. Only applies to leaf partitions, and (for constraints that
12680 : : * reference a partitioned table) only if this is not one of the
12681 : : * pg_constraint rows that exist solely to support action triggers.
12682 : : */
12683 [ + + ]: 76 : if (rel->rd_rel->relkind == RELKIND_RELATION &&
12684 [ + + ]: 64 : currcon->confrelid == pkrelid)
12685 : : {
12686 : : AlteredTableInfo *tab;
12687 : : NewConstraint *newcon;
12688 : :
12689 : 52 : newcon = palloc0_object(NewConstraint);
12690 : 52 : newcon->name = fkconstraint->conname;
12691 : 52 : newcon->contype = CONSTR_FOREIGN;
12692 : 52 : newcon->refrelid = currcon->confrelid;
12693 : 52 : newcon->refindid = currcon->conindid;
12694 : 52 : newcon->conid = currcon->oid;
12695 : 52 : newcon->qual = (Node *) fkconstraint;
12696 : :
12697 : : /* Find or create work queue entry for this table */
12698 : 52 : tab = ATGetQueueEntry(wqueue, rel);
12699 : 52 : tab->constraints = lappend(tab->constraints, newcon);
12700 : : }
12701 : :
12702 : : /*
12703 : : * If the table at either end of the constraint is partitioned, we
12704 : : * need to recurse and create triggers for each constraint that is a
12705 : : * child of this one.
12706 : : */
12707 [ + + + + ]: 140 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
12708 : 64 : get_rel_relkind(currcon->confrelid) == RELKIND_PARTITIONED_TABLE)
12709 : 20 : AlterFKConstrEnforceabilityRecurse(wqueue, cmdcon, conrel, tgrel,
12710 : : fkrelid, pkrelid, contuple,
12711 : : lockmode,
12712 : : ReferencedDelTriggerOid,
12713 : : ReferencedUpdTriggerOid,
12714 : : ReferencingInsTriggerOid,
12715 : : ReferencingUpdTriggerOid);
12716 : : }
12717 : :
12718 : 128 : table_close(rel, NoLock);
12719 : :
12720 : 128 : return changed;
12721 : : }
12722 : :
12723 : : /*
12724 : : * Returns true if the CHECK constraint's enforceability is altered.
12725 : : */
12726 : : static bool
12727 : 336 : ATExecAlterCheckConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon,
12728 : : Relation conrel, HeapTuple contuple,
12729 : : bool recurse, bool recursing,
12730 : : List *changing_conids,
12731 : : LOCKMODE lockmode)
12732 : : {
12733 : : Form_pg_constraint currcon;
12734 : : Relation rel;
12735 : 336 : bool changed = false;
12736 : 336 : List *children = NIL;
12737 : 336 : bool target_enforced = cmdcon->is_enforced;
12738 : 336 : Oid enforced_parentoid = InvalidOid;
12739 : :
12740 : : /* Since this function recurses, it could be driven to stack overflow */
12741 : 336 : check_stack_depth();
12742 : :
12743 : : Assert(cmdcon->alterEnforceability);
12744 : :
12745 : 336 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
12746 : :
12747 : : Assert(currcon->contype == CONSTRAINT_CHECK);
12748 : :
12749 : : /*
12750 : : * Parent relation already locked by caller, children will be locked by
12751 : : * find_all_inheritors. So NoLock is fine here.
12752 : : */
12753 : 336 : rel = table_open(currcon->conrelid, NoLock);
12754 : :
12755 : : /*
12756 : : * When setting a constraint to NOT ENFORCED, check whether any matching
12757 : : * parent constraint remains ENFORCED and is not part of this ALTER.
12758 : : *
12759 : : * For a direct ALTER of an inherited constraint, reject the command,
12760 : : * because the child cannot be weakened while its parent remains enforced.
12761 : : *
12762 : : * During recursion, another parent outside this ALTER may still enforce
12763 : : * the same constraint in a regular inheritance hierarchy. In that case,
12764 : : * keep the child constraint ENFORCED so that its merged enforceability
12765 : : * still reflects the remaining enforced parent. Partitions do not need
12766 : : * this recursive parent check because a partition can have only one
12767 : : * direct parent.
12768 : : */
12769 [ + + ]: 336 : if (!cmdcon->is_enforced &&
12770 [ + + + + : 304 : (!recursing || !rel->rd_rel->relispartition) &&
+ + ]
12771 : 144 : ATCheckCheckConstrHasEnforcedParent(conrel, rel, contuple,
12772 : : changing_conids,
12773 : : &enforced_parentoid))
12774 : : {
12775 [ + + ]: 32 : if (!recursing)
12776 [ + - ]: 20 : ereport(ERROR,
12777 : : errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
12778 : : errmsg("cannot mark inherited constraint \"%s\" as %s",
12779 : : NameStr(currcon->conname),
12780 : : "NOT ENFORCED"),
12781 : : errdetail("The matching constraint on parent table \"%s\" is %s.",
12782 : : get_rel_name(enforced_parentoid), "ENFORCED"));
12783 : :
12784 : 12 : target_enforced = true;
12785 : : }
12786 : :
12787 : : /*
12788 : : * Update to the merged enforceability if needed. This may differ from the
12789 : : * requested enforceability when another matching parent constraint
12790 : : * remains enforced.
12791 : : */
12792 [ + + ]: 316 : if (currcon->conenforced != target_enforced)
12793 : : {
12794 : 248 : ATAlterConstraint updatecon = *cmdcon;
12795 : :
12796 : 248 : updatecon.is_enforced = target_enforced;
12797 : 248 : AlterConstrUpdateConstraintEntry(&updatecon, conrel, contuple);
12798 : 248 : changed = true;
12799 : : }
12800 : :
12801 : : /*
12802 : : * Note that we must recurse even when trying to change a check constraint
12803 : : * to not enforced if it is already not enforced, in case descendant
12804 : : * constraints might be enforced and need to be changed to not enforced,
12805 : : * unless they still inherit an enforced constraint from another parent.
12806 : : * Conversely, we should do nothing if a constraint is being set to
12807 : : * enforced and is already enforced, as descendant constraints cannot be
12808 : : * different in that case.
12809 : : */
12810 [ + + + + ]: 316 : if (!cmdcon->is_enforced || changed)
12811 : : {
12812 : : /*
12813 : : * If we're recursing, the parent has already done this, so skip it.
12814 : : * Also, if the constraint is a NO INHERIT constraint, we shouldn't
12815 : : * try to look for it in the children.
12816 : : */
12817 [ + + + + ]: 300 : if (!recursing && !currcon->connoinherit)
12818 : : {
12819 : : Assert(changing_conids == NIL);
12820 : :
12821 : 104 : children = find_all_inheritors(RelationGetRelid(rel),
12822 : : lockmode, NULL);
12823 : :
12824 : : /*
12825 : : * When setting NOT ENFORCED, build the set of equivalent CHECK
12826 : : * constraints that this command will attempt to change before
12827 : : * visiting descendants. The root itself has already been checked
12828 : : * above.
12829 : : */
12830 [ + + ]: 104 : if (!cmdcon->is_enforced)
12831 : 48 : changing_conids = list_make1_oid(currcon->oid);
12832 : :
12833 [ + - + + : 512 : foreach_oid(childoid, children)
+ + ]
12834 : : {
12835 [ + + ]: 320 : if (childoid == RelationGetRelid(rel))
12836 : 104 : continue;
12837 : :
12838 : : /*
12839 : : * If we are told not to recurse, there had better not be any
12840 : : * child tables, because we can't change constraint
12841 : : * enforceability on the parent unless we have changed
12842 : : * enforceability for all child.
12843 : : */
12844 [ + + ]: 216 : if (!recurse)
12845 [ + - ]: 8 : ereport(ERROR,
12846 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
12847 : : errmsg("constraint must be altered on child tables too"),
12848 : : errhint("Do not specify the ONLY keyword."));
12849 : :
12850 : : /*
12851 : : * It is sufficient to look up the constraint by name here.
12852 : : * Supported DDL ensures that inheritable CHECK constraints
12853 : : * with the same name have equivalent definitions when they
12854 : : * are propagated to children or when inheritance is
12855 : : * established. All descendants returned by
12856 : : * find_all_inheritors must have this constraint: inherited
12857 : : * CHECK constraints propagate to all children at
12858 : : * inheritance-link creation time and cannot be dropped
12859 : : * independently on child tables.
12860 : : */
12861 [ + + ]: 208 : if (!cmdcon->is_enforced)
12862 : : changing_conids =
12863 : 92 : list_append_unique_oid(changing_conids,
12864 : : get_relation_constraint_oid(childoid,
12865 : 92 : cmdcon->conname,
12866 : : false));
12867 : : }
12868 : : }
12869 : :
12870 [ + + + + : 888 : foreach_oid(childoid, children)
+ + ]
12871 : : {
12872 [ + + ]: 304 : if (childoid == RelationGetRelid(rel))
12873 : 96 : continue;
12874 : :
12875 : 208 : AlterCheckConstrEnforceabilityRecurse(wqueue, cmdcon, conrel,
12876 : : childoid, false, true,
12877 : : changing_conids,
12878 : : lockmode);
12879 : : }
12880 : : }
12881 : :
12882 : : /*
12883 : : * Tell Phase 3 to check that the constraint is satisfied by existing
12884 : : * rows. We only need do this when altering the constraint from NOT
12885 : : * ENFORCED to ENFORCED.
12886 : : */
12887 [ + + ]: 308 : if (rel->rd_rel->relkind == RELKIND_RELATION &&
12888 [ + + + + ]: 260 : !currcon->conenforced &&
12889 : : target_enforced)
12890 : : {
12891 : : AlteredTableInfo *tab;
12892 : : NewConstraint *newcon;
12893 : : Datum val;
12894 : : char *conbin;
12895 : :
12896 : 116 : newcon = palloc0_object(NewConstraint);
12897 : 116 : newcon->name = pstrdup(NameStr(currcon->conname));
12898 : 116 : newcon->contype = CONSTR_CHECK;
12899 : :
12900 : 116 : val = SysCacheGetAttrNotNull(CONSTROID, contuple,
12901 : : Anum_pg_constraint_conbin);
12902 : 116 : conbin = TextDatumGetCString(val);
12903 : 116 : newcon->qual = expand_generated_columns_in_expr(stringToNode(conbin), rel, 1);
12904 : :
12905 : : /* Find or create work queue entry for this table */
12906 : 116 : tab = ATGetQueueEntry(wqueue, rel);
12907 : 116 : tab->constraints = lappend(tab->constraints, newcon);
12908 : : }
12909 : :
12910 : 308 : table_close(rel, NoLock);
12911 : :
12912 : 308 : return changed;
12913 : : }
12914 : :
12915 : : /*
12916 : : * Invokes ATExecAlterCheckConstrEnforceability for each CHECK constraint that
12917 : : * is a child of the specified constraint.
12918 : : *
12919 : : * We rely on the parent and child tables having identical CHECK constraint
12920 : : * names to retrieve the child's pg_constraint tuple.
12921 : : *
12922 : : * The arguments to this function have the same meaning as the arguments to
12923 : : * ATExecAlterCheckConstrEnforceability.
12924 : : */
12925 : : static void
12926 : 208 : AlterCheckConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
12927 : : Relation conrel, Oid conrelid,
12928 : : bool recurse, bool recursing,
12929 : : List *changing_conids,
12930 : : LOCKMODE lockmode)
12931 : : {
12932 : : SysScanDesc pscan;
12933 : : HeapTuple childtup;
12934 : : ScanKeyData skey[3];
12935 : :
12936 : 208 : ScanKeyInit(&skey[0],
12937 : : Anum_pg_constraint_conrelid,
12938 : : BTEqualStrategyNumber, F_OIDEQ,
12939 : : ObjectIdGetDatum(conrelid));
12940 : 208 : ScanKeyInit(&skey[1],
12941 : : Anum_pg_constraint_contypid,
12942 : : BTEqualStrategyNumber, F_OIDEQ,
12943 : : ObjectIdGetDatum(InvalidOid));
12944 : 208 : ScanKeyInit(&skey[2],
12945 : : Anum_pg_constraint_conname,
12946 : : BTEqualStrategyNumber, F_NAMEEQ,
12947 : 208 : CStringGetDatum(cmdcon->conname));
12948 : :
12949 : 208 : pscan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId, true,
12950 : : NULL, 3, skey);
12951 : :
12952 [ - + ]: 208 : if (!HeapTupleIsValid(childtup = systable_getnext(pscan)))
12953 [ # # ]: 0 : ereport(ERROR,
12954 : : errcode(ERRCODE_UNDEFINED_OBJECT),
12955 : : errmsg("constraint \"%s\" of relation \"%s\" does not exist",
12956 : : cmdcon->conname, get_rel_name(conrelid)));
12957 : :
12958 : 208 : ATExecAlterCheckConstrEnforceability(wqueue, cmdcon, conrel, childtup,
12959 : : recurse, recursing, changing_conids,
12960 : : lockmode);
12961 : :
12962 : 208 : systable_endscan(pscan);
12963 : 208 : }
12964 : :
12965 : : /*
12966 : : * When setting an inherited CHECK constraint to NOT ENFORCED, look for a
12967 : : * matching parent constraint that remains ENFORCED and is not part of the same
12968 : : * ALTER.
12969 : : */
12970 : : static bool
12971 : 268 : ATCheckCheckConstrHasEnforcedParent(Relation conrel, Relation rel,
12972 : : HeapTuple contuple,
12973 : : List *changing_conids,
12974 : : Oid *enforced_parentoid)
12975 : : {
12976 : : Form_pg_constraint currcon;
12977 : : Relation inhrel;
12978 : : SysScanDesc scan;
12979 : : ScanKeyData skey;
12980 : : HeapTuple inheritsTuple;
12981 : :
12982 : : /* Since this function recurses, it could be driven to stack overflow */
12983 : 268 : check_stack_depth();
12984 : :
12985 : 268 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
12986 : : Assert(currcon->contype == CONSTRAINT_CHECK);
12987 : :
12988 [ + + ]: 268 : if (currcon->coninhcount <= 0)
12989 : 124 : return false;
12990 : :
12991 : 144 : inhrel = table_open(InheritsRelationId, AccessShareLock);
12992 : :
12993 : 144 : ScanKeyInit(&skey,
12994 : : Anum_pg_inherits_inhrelid,
12995 : : BTEqualStrategyNumber, F_OIDEQ,
12996 : : ObjectIdGetDatum(RelationGetRelid(rel)));
12997 : 144 : scan = systable_beginscan(inhrel, InheritsRelidSeqnoIndexId,
12998 : : true, NULL, 1, &skey);
12999 : :
13000 [ + + ]: 304 : while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
13001 : : {
13002 : : Oid parentoid;
13003 : 200 : Relation parentrel = NULL;
13004 : : SysScanDesc pscan;
13005 : : ScanKeyData pkey[3];
13006 : : HeapTuple parenttup;
13007 : :
13008 : 200 : parentoid = ((Form_pg_inherits) GETSTRUCT(inheritsTuple))->inhparent;
13009 : :
13010 : 200 : ScanKeyInit(&pkey[0],
13011 : : Anum_pg_constraint_conrelid,
13012 : : BTEqualStrategyNumber, F_OIDEQ,
13013 : : ObjectIdGetDatum(parentoid));
13014 : 200 : ScanKeyInit(&pkey[1],
13015 : : Anum_pg_constraint_contypid,
13016 : : BTEqualStrategyNumber, F_OIDEQ,
13017 : : ObjectIdGetDatum(InvalidOid));
13018 : 200 : ScanKeyInit(&pkey[2],
13019 : : Anum_pg_constraint_conname,
13020 : : BTEqualStrategyNumber, F_NAMEEQ,
13021 : 200 : NameGetDatum(&currcon->conname));
13022 : :
13023 : 200 : pscan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId,
13024 : : true, NULL, 3, pkey);
13025 : :
13026 : : /*
13027 : : * ConstraintRelidTypidNameIndexId is unique on (conrelid, contypid,
13028 : : * conname), so this loop body executes at most once per parent.
13029 : : */
13030 [ + + ]: 360 : while (HeapTupleIsValid(parenttup = systable_getnext(pscan)))
13031 : : {
13032 : : Form_pg_constraint parentcon;
13033 : :
13034 : 200 : parentcon = (Form_pg_constraint) GETSTRUCT(parenttup);
13035 : :
13036 [ + - ]: 200 : if (parentcon->contype != CONSTRAINT_CHECK ||
13037 [ + - ]: 200 : parentcon->connoinherit ||
13038 [ + + ]: 200 : !parentcon->conenforced)
13039 : 44 : continue;
13040 : :
13041 [ - + ]: 156 : if (!constraints_equivalent(parenttup, contuple,
13042 : : RelationGetDescr(conrel)))
13043 [ # # ]: 0 : elog(ERROR, "child table \"%s\" has different definition for check constraint \"%s\"",
13044 : : RelationGetRelationName(rel),
13045 : : NameStr(parentcon->conname));
13046 : :
13047 : : /*
13048 : : * A parent listed in changing_conids is being changed by the same
13049 : : * ALTER, but it may not have been updated yet. For regular
13050 : : * inheritance, recurse upward to check whether an equivalent
13051 : : * enforced parent outside the ALTER will make it remain enforced.
13052 : : * Partitions cannot have multiple parents, so they do not need
13053 : : * this check.
13054 : : */
13055 [ + + + + ]: 300 : if (!rel->rd_rel->relispartition &&
13056 : 144 : list_member_oid(changing_conids, parentcon->oid))
13057 : : {
13058 : 124 : Oid parent_enforced_parentoid = InvalidOid;
13059 : :
13060 [ + - ]: 124 : if (parentrel == NULL)
13061 : 124 : parentrel = table_open(parentoid, NoLock);
13062 : :
13063 [ + + ]: 124 : if (!ATCheckCheckConstrHasEnforcedParent(conrel,
13064 : : parentrel,
13065 : : parenttup,
13066 : : changing_conids,
13067 : : &parent_enforced_parentoid))
13068 : 116 : continue;
13069 : : }
13070 : :
13071 : 40 : *enforced_parentoid = parentoid;
13072 [ + + ]: 40 : if (parentrel != NULL)
13073 : 8 : table_close(parentrel, NoLock);
13074 : 40 : systable_endscan(pscan);
13075 : 40 : systable_endscan(scan);
13076 : 40 : table_close(inhrel, AccessShareLock);
13077 : 40 : return true;
13078 : : }
13079 : :
13080 [ + + ]: 160 : if (parentrel != NULL)
13081 : 116 : table_close(parentrel, NoLock);
13082 : 160 : systable_endscan(pscan);
13083 : : }
13084 : :
13085 : 104 : systable_endscan(scan);
13086 : 104 : table_close(inhrel, AccessShareLock);
13087 : :
13088 : 104 : return false;
13089 : : }
13090 : :
13091 : : /*
13092 : : * Returns true if the constraint's deferrability is altered.
13093 : : *
13094 : : * *otherrelids is appended OIDs of relations containing affected triggers.
13095 : : *
13096 : : * Note that we must recurse even when the values are correct, in case
13097 : : * indirect descendants have had their constraints altered locally.
13098 : : * (This could be avoided if we forbade altering constraints in partitions
13099 : : * but existing releases don't do that.)
13100 : : */
13101 : : static bool
13102 : 108 : ATExecAlterConstrDeferrability(List **wqueue, ATAlterConstraint *cmdcon,
13103 : : Relation conrel, Relation tgrel, Relation rel,
13104 : : HeapTuple contuple, bool recurse,
13105 : : List **otherrelids, LOCKMODE lockmode)
13106 : : {
13107 : : Form_pg_constraint currcon;
13108 : : Oid refrelid;
13109 : 108 : bool changed = false;
13110 : :
13111 : : /* since this function recurses, it could be driven to stack overflow */
13112 : 108 : check_stack_depth();
13113 : :
13114 : : Assert(cmdcon->alterDeferrability);
13115 : :
13116 : 108 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
13117 : 108 : refrelid = currcon->confrelid;
13118 : :
13119 : : /* Should be foreign key constraint */
13120 : : Assert(currcon->contype == CONSTRAINT_FOREIGN);
13121 : :
13122 : : /*
13123 : : * If called to modify a constraint that's already in the desired state,
13124 : : * silently do nothing.
13125 : : */
13126 [ + + ]: 108 : if (currcon->condeferrable != cmdcon->deferrable ||
13127 [ + - ]: 4 : currcon->condeferred != cmdcon->initdeferred)
13128 : : {
13129 : 108 : AlterConstrUpdateConstraintEntry(cmdcon, conrel, contuple);
13130 : 108 : changed = true;
13131 : :
13132 : : /*
13133 : : * Now we need to update the multiple entries in pg_trigger that
13134 : : * implement the constraint.
13135 : : */
13136 : 108 : AlterConstrTriggerDeferrability(currcon->oid, tgrel, rel,
13137 : 108 : cmdcon->deferrable,
13138 : 108 : cmdcon->initdeferred, otherrelids);
13139 : : }
13140 : :
13141 : : /*
13142 : : * If the table at either end of the constraint is partitioned, we need to
13143 : : * handle every constraint that is a child of this one.
13144 : : */
13145 [ + - + - ]: 108 : if (recurse && changed &&
13146 [ + + + + ]: 200 : (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
13147 : 92 : get_rel_relkind(refrelid) == RELKIND_PARTITIONED_TABLE))
13148 : 28 : AlterConstrDeferrabilityRecurse(wqueue, cmdcon, conrel, tgrel, rel,
13149 : : contuple, recurse, otherrelids,
13150 : : lockmode);
13151 : :
13152 : 108 : return changed;
13153 : : }
13154 : :
13155 : : /*
13156 : : * Returns true if the constraint's inheritability is altered.
13157 : : */
13158 : : static bool
13159 : 40 : ATExecAlterConstrInheritability(List **wqueue, ATAlterConstraint *cmdcon,
13160 : : Relation conrel, Relation rel,
13161 : : HeapTuple contuple, LOCKMODE lockmode)
13162 : : {
13163 : : Form_pg_constraint currcon;
13164 : : AttrNumber colNum;
13165 : : char *colName;
13166 : : List *children;
13167 : :
13168 : : Assert(cmdcon->alterInheritability);
13169 : :
13170 : 40 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
13171 : :
13172 : : /* The current implementation only works for NOT NULL constraints */
13173 : : Assert(currcon->contype == CONSTRAINT_NOTNULL);
13174 : :
13175 : : /*
13176 : : * If called to modify a constraint that's already in the desired state,
13177 : : * silently do nothing.
13178 : : */
13179 [ - + ]: 40 : if (cmdcon->noinherit == currcon->connoinherit)
13180 : 0 : return false;
13181 : :
13182 : 40 : AlterConstrUpdateConstraintEntry(cmdcon, conrel, contuple);
13183 : 40 : CommandCounterIncrement();
13184 : :
13185 : : /* Fetch the column number and name */
13186 : 40 : colNum = extractNotNullColumn(contuple);
13187 : 40 : colName = get_attname(currcon->conrelid, colNum, false);
13188 : :
13189 : : /*
13190 : : * Propagate the change to children. For this subcommand type we don't
13191 : : * recursively affect children, just the immediate level.
13192 : : */
13193 : 40 : children = find_inheritance_children(RelationGetRelid(rel),
13194 : : lockmode);
13195 [ + + + + : 128 : foreach_oid(childoid, children)
+ + ]
13196 : : {
13197 : : ObjectAddress addr;
13198 : :
13199 [ + + ]: 56 : if (cmdcon->noinherit)
13200 : : {
13201 : : HeapTuple childtup;
13202 : : Form_pg_constraint childcon;
13203 : :
13204 : 20 : childtup = findNotNullConstraint(childoid, colName);
13205 [ - + ]: 20 : if (!childtup)
13206 [ # # ]: 0 : elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation %u",
13207 : : colName, childoid);
13208 : 20 : childcon = (Form_pg_constraint) GETSTRUCT(childtup);
13209 : : Assert(childcon->coninhcount > 0);
13210 : 20 : childcon->coninhcount--;
13211 : 20 : childcon->conislocal = true;
13212 : 20 : CatalogTupleUpdate(conrel, &childtup->t_self, childtup);
13213 : 20 : heap_freetuple(childtup);
13214 : : }
13215 : : else
13216 : : {
13217 : 36 : Relation childrel = table_open(childoid, NoLock);
13218 : :
13219 : 36 : addr = ATExecSetNotNull(wqueue, childrel, NameStr(currcon->conname),
13220 : : colName, true, true, lockmode);
13221 [ + - ]: 32 : if (OidIsValid(addr.objectId))
13222 : 32 : CommandCounterIncrement();
13223 : 32 : table_close(childrel, NoLock);
13224 : : }
13225 : : }
13226 : :
13227 : 36 : return true;
13228 : : }
13229 : :
13230 : : /*
13231 : : * A subroutine of ATExecAlterConstrDeferrability that updated constraint
13232 : : * trigger's deferrability.
13233 : : *
13234 : : * The arguments to this function have the same meaning as the arguments to
13235 : : * ATExecAlterConstrDeferrability.
13236 : : */
13237 : : static void
13238 : 108 : AlterConstrTriggerDeferrability(Oid conoid, Relation tgrel, Relation rel,
13239 : : bool deferrable, bool initdeferred,
13240 : : List **otherrelids)
13241 : : {
13242 : : HeapTuple tgtuple;
13243 : : ScanKeyData tgkey;
13244 : : SysScanDesc tgscan;
13245 : :
13246 : 108 : ScanKeyInit(&tgkey,
13247 : : Anum_pg_trigger_tgconstraint,
13248 : : BTEqualStrategyNumber, F_OIDEQ,
13249 : : ObjectIdGetDatum(conoid));
13250 : 108 : tgscan = systable_beginscan(tgrel, TriggerConstraintIndexId, true,
13251 : : NULL, 1, &tgkey);
13252 [ + + ]: 420 : while (HeapTupleIsValid(tgtuple = systable_getnext(tgscan)))
13253 : : {
13254 : 312 : Form_pg_trigger tgform = (Form_pg_trigger) GETSTRUCT(tgtuple);
13255 : : Form_pg_trigger copy_tg;
13256 : : HeapTuple tgCopyTuple;
13257 : :
13258 : : /*
13259 : : * Remember OIDs of other relation(s) involved in FK constraint.
13260 : : * (Note: it's likely that we could skip forcing a relcache inval for
13261 : : * other rels that don't have a trigger whose properties change, but
13262 : : * let's be conservative.)
13263 : : */
13264 [ + + ]: 312 : if (tgform->tgrelid != RelationGetRelid(rel))
13265 : 152 : *otherrelids = list_append_unique_oid(*otherrelids,
13266 : : tgform->tgrelid);
13267 : :
13268 : : /*
13269 : : * Update enable status and deferrability of RI_FKey_noaction_del,
13270 : : * RI_FKey_noaction_upd, RI_FKey_check_ins and RI_FKey_check_upd
13271 : : * triggers, but not others; see createForeignKeyActionTriggers and
13272 : : * CreateFKCheckTrigger.
13273 : : */
13274 [ + + ]: 312 : if (tgform->tgfoid != F_RI_FKEY_NOACTION_DEL &&
13275 [ + + ]: 248 : tgform->tgfoid != F_RI_FKEY_NOACTION_UPD &&
13276 [ + + ]: 172 : tgform->tgfoid != F_RI_FKEY_CHECK_INS &&
13277 [ + + ]: 92 : tgform->tgfoid != F_RI_FKEY_CHECK_UPD)
13278 : 12 : continue;
13279 : :
13280 : 300 : tgCopyTuple = heap_copytuple(tgtuple);
13281 : 300 : copy_tg = (Form_pg_trigger) GETSTRUCT(tgCopyTuple);
13282 : :
13283 : 300 : copy_tg->tgdeferrable = deferrable;
13284 : 300 : copy_tg->tginitdeferred = initdeferred;
13285 : 300 : CatalogTupleUpdate(tgrel, &tgCopyTuple->t_self, tgCopyTuple);
13286 : :
13287 [ - + ]: 300 : InvokeObjectPostAlterHook(TriggerRelationId, tgform->oid, 0);
13288 : :
13289 : 300 : heap_freetuple(tgCopyTuple);
13290 : : }
13291 : :
13292 : 108 : systable_endscan(tgscan);
13293 : 108 : }
13294 : :
13295 : : /*
13296 : : * Invokes ATExecAlterFKConstrEnforceability for each foreign key constraint
13297 : : * that is a child of the specified constraint.
13298 : : *
13299 : : * Note that this doesn't handle recursion the normal way, viz. by scanning the
13300 : : * list of child relations and recursing; instead it uses the conparentid
13301 : : * relationships. This may need to be reconsidered.
13302 : : *
13303 : : * The arguments to this function have the same meaning as the arguments to
13304 : : * ATExecAlterFKConstrEnforceability.
13305 : : */
13306 : : static void
13307 : 32 : AlterFKConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
13308 : : Relation conrel, Relation tgrel,
13309 : : Oid fkrelid, Oid pkrelid,
13310 : : HeapTuple contuple, LOCKMODE lockmode,
13311 : : Oid ReferencedParentDelTrigger,
13312 : : Oid ReferencedParentUpdTrigger,
13313 : : Oid ReferencingParentInsTrigger,
13314 : : Oid ReferencingParentUpdTrigger)
13315 : : {
13316 : : Form_pg_constraint currcon;
13317 : : Oid conoid;
13318 : : ScanKeyData pkey;
13319 : : SysScanDesc pscan;
13320 : : HeapTuple childtup;
13321 : :
13322 : 32 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
13323 : 32 : conoid = currcon->oid;
13324 : :
13325 : 32 : ScanKeyInit(&pkey,
13326 : : Anum_pg_constraint_conparentid,
13327 : : BTEqualStrategyNumber, F_OIDEQ,
13328 : : ObjectIdGetDatum(conoid));
13329 : :
13330 : 32 : pscan = systable_beginscan(conrel, ConstraintParentIndexId,
13331 : : true, NULL, 1, &pkey);
13332 : :
13333 [ + + ]: 100 : while (HeapTupleIsValid(childtup = systable_getnext(pscan)))
13334 : 68 : ATExecAlterFKConstrEnforceability(wqueue, cmdcon, conrel, tgrel, fkrelid,
13335 : : pkrelid, childtup, lockmode,
13336 : : ReferencedParentDelTrigger,
13337 : : ReferencedParentUpdTrigger,
13338 : : ReferencingParentInsTrigger,
13339 : : ReferencingParentUpdTrigger);
13340 : :
13341 : 32 : systable_endscan(pscan);
13342 : 32 : }
13343 : :
13344 : : /*
13345 : : * Invokes ATExecAlterConstrDeferrability for each constraint that is a child of
13346 : : * the specified constraint.
13347 : : *
13348 : : * Note that this doesn't handle recursion the normal way, viz. by scanning the
13349 : : * list of child relations and recursing; instead it uses the conparentid
13350 : : * relationships. This may need to be reconsidered.
13351 : : *
13352 : : * The arguments to this function have the same meaning as the arguments to
13353 : : * ATExecAlterConstrDeferrability.
13354 : : */
13355 : : static void
13356 : 28 : AlterConstrDeferrabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
13357 : : Relation conrel, Relation tgrel, Relation rel,
13358 : : HeapTuple contuple, bool recurse,
13359 : : List **otherrelids, LOCKMODE lockmode)
13360 : : {
13361 : : Form_pg_constraint currcon;
13362 : : Oid conoid;
13363 : : ScanKeyData pkey;
13364 : : SysScanDesc pscan;
13365 : : HeapTuple childtup;
13366 : :
13367 : 28 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
13368 : 28 : conoid = currcon->oid;
13369 : :
13370 : 28 : ScanKeyInit(&pkey,
13371 : : Anum_pg_constraint_conparentid,
13372 : : BTEqualStrategyNumber, F_OIDEQ,
13373 : : ObjectIdGetDatum(conoid));
13374 : :
13375 : 28 : pscan = systable_beginscan(conrel, ConstraintParentIndexId,
13376 : : true, NULL, 1, &pkey);
13377 : :
13378 [ + + ]: 72 : while (HeapTupleIsValid(childtup = systable_getnext(pscan)))
13379 : : {
13380 : 44 : Form_pg_constraint childcon = (Form_pg_constraint) GETSTRUCT(childtup);
13381 : : Relation childrel;
13382 : :
13383 : 44 : childrel = table_open(childcon->conrelid, lockmode);
13384 : :
13385 : 44 : ATExecAlterConstrDeferrability(wqueue, cmdcon, conrel, tgrel, childrel,
13386 : : childtup, recurse, otherrelids, lockmode);
13387 : 44 : table_close(childrel, NoLock);
13388 : : }
13389 : :
13390 : 28 : systable_endscan(pscan);
13391 : 28 : }
13392 : :
13393 : : /*
13394 : : * Update the constraint entry for the given ATAlterConstraint command, and
13395 : : * invoke the appropriate hooks.
13396 : : */
13397 : : static void
13398 : 520 : AlterConstrUpdateConstraintEntry(ATAlterConstraint *cmdcon, Relation conrel,
13399 : : HeapTuple contuple)
13400 : : {
13401 : : HeapTuple copyTuple;
13402 : : Form_pg_constraint copy_con;
13403 : :
13404 : : Assert(cmdcon->alterEnforceability || cmdcon->alterDeferrability ||
13405 : : cmdcon->alterInheritability);
13406 : :
13407 : 520 : copyTuple = heap_copytuple(contuple);
13408 : 520 : copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
13409 : :
13410 [ + + ]: 520 : if (cmdcon->alterEnforceability)
13411 : : {
13412 : 372 : copy_con->conenforced = cmdcon->is_enforced;
13413 : :
13414 : : /*
13415 : : * NB: The convalidated status is irrelevant when the constraint is
13416 : : * set to NOT ENFORCED, but for consistency, it should still be set
13417 : : * appropriately. Similarly, if the constraint is later changed to
13418 : : * ENFORCED, validation will be performed during phase 3, so it makes
13419 : : * sense to mark it as valid in that case.
13420 : : */
13421 : 372 : copy_con->convalidated = cmdcon->is_enforced;
13422 : : }
13423 [ + + ]: 520 : if (cmdcon->alterDeferrability)
13424 : : {
13425 : 112 : copy_con->condeferrable = cmdcon->deferrable;
13426 : 112 : copy_con->condeferred = cmdcon->initdeferred;
13427 : : }
13428 [ + + ]: 520 : if (cmdcon->alterInheritability)
13429 : 40 : copy_con->connoinherit = cmdcon->noinherit;
13430 : :
13431 : 520 : CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);
13432 [ - + ]: 520 : InvokeObjectPostAlterHook(ConstraintRelationId, copy_con->oid, 0);
13433 : :
13434 : : /* Make new constraint flags visible to others */
13435 : 520 : CacheInvalidateRelcacheByRelid(copy_con->conrelid);
13436 : :
13437 : 520 : heap_freetuple(copyTuple);
13438 : 520 : }
13439 : :
13440 : : /*
13441 : : * ALTER TABLE VALIDATE CONSTRAINT
13442 : : *
13443 : : * XXX The reason we handle recursion here rather than at Phase 1 is because
13444 : : * there's no good way to skip recursing when handling foreign keys: there is
13445 : : * no need to lock children in that case, yet we wouldn't be able to avoid
13446 : : * doing so at that level.
13447 : : *
13448 : : * Return value is the address of the validated constraint. If the constraint
13449 : : * was already validated, InvalidObjectAddress is returned.
13450 : : */
13451 : : static ObjectAddress
13452 : 351 : ATExecValidateConstraint(List **wqueue, Relation rel, char *constrName,
13453 : : bool recurse, bool recursing, LOCKMODE lockmode)
13454 : : {
13455 : : Relation conrel;
13456 : : SysScanDesc scan;
13457 : : ScanKeyData skey[3];
13458 : : HeapTuple tuple;
13459 : : Form_pg_constraint con;
13460 : : ObjectAddress address;
13461 : :
13462 : 351 : conrel = table_open(ConstraintRelationId, RowExclusiveLock);
13463 : :
13464 : : /*
13465 : : * Find and check the target constraint
13466 : : */
13467 : 351 : ScanKeyInit(&skey[0],
13468 : : Anum_pg_constraint_conrelid,
13469 : : BTEqualStrategyNumber, F_OIDEQ,
13470 : : ObjectIdGetDatum(RelationGetRelid(rel)));
13471 : 351 : ScanKeyInit(&skey[1],
13472 : : Anum_pg_constraint_contypid,
13473 : : BTEqualStrategyNumber, F_OIDEQ,
13474 : : ObjectIdGetDatum(InvalidOid));
13475 : 351 : ScanKeyInit(&skey[2],
13476 : : Anum_pg_constraint_conname,
13477 : : BTEqualStrategyNumber, F_NAMEEQ,
13478 : : CStringGetDatum(constrName));
13479 : 351 : scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId,
13480 : : true, NULL, 3, skey);
13481 : :
13482 : : /* There can be at most one matching row */
13483 [ - + ]: 351 : if (!HeapTupleIsValid(tuple = systable_getnext(scan)))
13484 [ # # ]: 0 : ereport(ERROR,
13485 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
13486 : : errmsg("constraint \"%s\" of relation \"%s\" does not exist",
13487 : : constrName, RelationGetRelationName(rel))));
13488 : :
13489 : 351 : con = (Form_pg_constraint) GETSTRUCT(tuple);
13490 [ + + ]: 351 : if (con->contype != CONSTRAINT_FOREIGN &&
13491 [ + + ]: 174 : con->contype != CONSTRAINT_CHECK &&
13492 [ - + ]: 78 : con->contype != CONSTRAINT_NOTNULL)
13493 [ # # ]: 0 : ereport(ERROR,
13494 : : errcode(ERRCODE_WRONG_OBJECT_TYPE),
13495 : : errmsg("cannot validate constraint \"%s\" of relation \"%s\"",
13496 : : constrName, RelationGetRelationName(rel)),
13497 : : errdetail("This operation is not supported for this type of constraint."));
13498 : :
13499 [ + + ]: 351 : if (!con->conenforced)
13500 [ + - ]: 4 : ereport(ERROR,
13501 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
13502 : : errmsg("cannot validate NOT ENFORCED constraint")));
13503 : :
13504 [ + + ]: 347 : if (!con->convalidated)
13505 : : {
13506 [ + + ]: 335 : if (con->contype == CONSTRAINT_FOREIGN)
13507 : : {
13508 : 173 : QueueFKConstraintValidation(wqueue, conrel, rel, con->confrelid,
13509 : : tuple, lockmode);
13510 : : }
13511 [ + + ]: 162 : else if (con->contype == CONSTRAINT_CHECK)
13512 : : {
13513 : 84 : QueueCheckConstraintValidation(wqueue, conrel, rel, constrName,
13514 : : tuple, recurse, recursing, lockmode);
13515 : : }
13516 [ + - ]: 78 : else if (con->contype == CONSTRAINT_NOTNULL)
13517 : : {
13518 : 78 : QueueNNConstraintValidation(wqueue, conrel, rel,
13519 : : tuple, recurse, recursing, lockmode);
13520 : : }
13521 : :
13522 : 335 : ObjectAddressSet(address, ConstraintRelationId, con->oid);
13523 : : }
13524 : : else
13525 : 12 : address = InvalidObjectAddress; /* already validated */
13526 : :
13527 : 347 : systable_endscan(scan);
13528 : :
13529 : 347 : table_close(conrel, RowExclusiveLock);
13530 : :
13531 : 347 : return address;
13532 : : }
13533 : :
13534 : : /*
13535 : : * QueueFKConstraintValidation
13536 : : *
13537 : : * Add an entry to the wqueue to validate the given foreign key constraint in
13538 : : * Phase 3 and update the convalidated field in the pg_constraint catalog
13539 : : * for the specified relation and all its children.
13540 : : */
13541 : : static void
13542 : 225 : QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation fkrel,
13543 : : Oid pkrelid, HeapTuple contuple, LOCKMODE lockmode)
13544 : : {
13545 : : Form_pg_constraint con;
13546 : : AlteredTableInfo *tab;
13547 : : HeapTuple copyTuple;
13548 : : Form_pg_constraint copy_con;
13549 : :
13550 : : /* since this function recurses, it could be driven to stack overflow */
13551 : 225 : check_stack_depth();
13552 : :
13553 : 225 : con = (Form_pg_constraint) GETSTRUCT(contuple);
13554 : : Assert(con->contype == CONSTRAINT_FOREIGN);
13555 : : Assert(!con->convalidated);
13556 : :
13557 : : /*
13558 : : * Add the validation to phase 3's queue; not needed for partitioned
13559 : : * tables themselves, only for their partitions.
13560 : : *
13561 : : * When the referenced table (pkrelid) is partitioned, the referencing
13562 : : * table (fkrel) has one pg_constraint row pointing to each partition
13563 : : * thereof. These rows are there only to support action triggers and no
13564 : : * table scan is needed, therefore skip this for them as well.
13565 : : */
13566 [ + + ]: 225 : if (fkrel->rd_rel->relkind == RELKIND_RELATION &&
13567 [ + + ]: 193 : con->confrelid == pkrelid)
13568 : : {
13569 : : NewConstraint *newcon;
13570 : : Constraint *fkconstraint;
13571 : :
13572 : : /* Queue validation for phase 3 */
13573 : 181 : fkconstraint = makeNode(Constraint);
13574 : : /* for now this is all we need */
13575 : 181 : fkconstraint->conname = pstrdup(NameStr(con->conname));
13576 : :
13577 : 181 : newcon = palloc0_object(NewConstraint);
13578 : 181 : newcon->name = fkconstraint->conname;
13579 : 181 : newcon->contype = CONSTR_FOREIGN;
13580 : 181 : newcon->refrelid = con->confrelid;
13581 : 181 : newcon->refindid = con->conindid;
13582 : 181 : newcon->conid = con->oid;
13583 : 181 : newcon->qual = (Node *) fkconstraint;
13584 : :
13585 : : /* Find or create work queue entry for this table */
13586 : 181 : tab = ATGetQueueEntry(wqueue, fkrel);
13587 : 181 : tab->constraints = lappend(tab->constraints, newcon);
13588 : : }
13589 : :
13590 : : /*
13591 : : * If the table at either end of the constraint is partitioned, we need to
13592 : : * recurse and handle every unvalidated constraint that is a child of this
13593 : : * constraint.
13594 : : */
13595 [ + + + + ]: 418 : if (fkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
13596 : 193 : get_rel_relkind(con->confrelid) == RELKIND_PARTITIONED_TABLE)
13597 : : {
13598 : : ScanKeyData pkey;
13599 : : SysScanDesc pscan;
13600 : : HeapTuple childtup;
13601 : :
13602 : 52 : ScanKeyInit(&pkey,
13603 : : Anum_pg_constraint_conparentid,
13604 : : BTEqualStrategyNumber, F_OIDEQ,
13605 : : ObjectIdGetDatum(con->oid));
13606 : :
13607 : 52 : pscan = systable_beginscan(conrel, ConstraintParentIndexId,
13608 : : true, NULL, 1, &pkey);
13609 : :
13610 [ + + ]: 104 : while (HeapTupleIsValid(childtup = systable_getnext(pscan)))
13611 : : {
13612 : : Form_pg_constraint childcon;
13613 : : Relation childrel;
13614 : :
13615 : 52 : childcon = (Form_pg_constraint) GETSTRUCT(childtup);
13616 : :
13617 : : /*
13618 : : * If the child constraint has already been validated, no further
13619 : : * action is required for it or its descendants, as they are all
13620 : : * valid.
13621 : : */
13622 [ + + ]: 52 : if (childcon->convalidated)
13623 : 12 : continue;
13624 : :
13625 : 40 : childrel = table_open(childcon->conrelid, lockmode);
13626 : :
13627 : : /*
13628 : : * NB: Note that pkrelid should be passed as-is during recursion,
13629 : : * as it is required to identify the root referenced table.
13630 : : */
13631 : 40 : QueueFKConstraintValidation(wqueue, conrel, childrel, pkrelid,
13632 : : childtup, lockmode);
13633 : 40 : table_close(childrel, NoLock);
13634 : : }
13635 : :
13636 : 52 : systable_endscan(pscan);
13637 : : }
13638 : :
13639 : : /*
13640 : : * Now mark the pg_constraint row as validated (even if we didn't check,
13641 : : * notably the ones for partitions on the referenced side).
13642 : : *
13643 : : * We rely on transaction abort to roll back this change if phase 3
13644 : : * ultimately finds violating rows. This is a bit ugly.
13645 : : */
13646 : 225 : copyTuple = heap_copytuple(contuple);
13647 : 225 : copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
13648 : 225 : copy_con->convalidated = true;
13649 : 225 : CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);
13650 : :
13651 [ - + ]: 225 : InvokeObjectPostAlterHook(ConstraintRelationId, con->oid, 0);
13652 : :
13653 : 225 : heap_freetuple(copyTuple);
13654 : 225 : }
13655 : :
13656 : : /*
13657 : : * QueueCheckConstraintValidation
13658 : : *
13659 : : * Add an entry to the wqueue to validate the given check constraint in Phase 3
13660 : : * and update the convalidated field in the pg_constraint catalog for the
13661 : : * specified relation and all its inheriting children.
13662 : : */
13663 : : static void
13664 : 84 : QueueCheckConstraintValidation(List **wqueue, Relation conrel, Relation rel,
13665 : : char *constrName, HeapTuple contuple,
13666 : : bool recurse, bool recursing, LOCKMODE lockmode)
13667 : : {
13668 : : Form_pg_constraint con;
13669 : : AlteredTableInfo *tab;
13670 : : HeapTuple copyTuple;
13671 : : Form_pg_constraint copy_con;
13672 : :
13673 : 84 : List *children = NIL;
13674 : : ListCell *child;
13675 : : NewConstraint *newcon;
13676 : : Datum val;
13677 : : char *conbin;
13678 : :
13679 : 84 : con = (Form_pg_constraint) GETSTRUCT(contuple);
13680 : : Assert(con->contype == CONSTRAINT_CHECK);
13681 : :
13682 : : /*
13683 : : * If we're recursing, the parent has already done this, so skip it. Also,
13684 : : * if the constraint is a NO INHERIT constraint, we shouldn't try to look
13685 : : * for it in the children.
13686 : : */
13687 [ + + + + ]: 84 : if (!recursing && !con->connoinherit)
13688 : 48 : children = find_all_inheritors(RelationGetRelid(rel),
13689 : : lockmode, NULL);
13690 : :
13691 : : /*
13692 : : * For CHECK constraints, we must ensure that we only mark the constraint
13693 : : * as validated on the parent if it's already validated on the children.
13694 : : *
13695 : : * We recurse before validating on the parent, to reduce risk of
13696 : : * deadlocks.
13697 : : */
13698 [ + + + + : 164 : foreach(child, children)
+ + ]
13699 : : {
13700 : 80 : Oid childoid = lfirst_oid(child);
13701 : : Relation childrel;
13702 : :
13703 [ + + ]: 80 : if (childoid == RelationGetRelid(rel))
13704 : 48 : continue;
13705 : :
13706 : : /*
13707 : : * If we are told not to recurse, there had better not be any child
13708 : : * tables, because we can't mark the constraint on the parent valid
13709 : : * unless it is valid for all child tables.
13710 : : */
13711 [ - + ]: 32 : if (!recurse)
13712 [ # # ]: 0 : ereport(ERROR,
13713 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
13714 : : errmsg("constraint must be validated on child tables too")));
13715 : :
13716 : : /* find_all_inheritors already got lock */
13717 : 32 : childrel = table_open(childoid, NoLock);
13718 : :
13719 : 32 : ATExecValidateConstraint(wqueue, childrel, constrName, false,
13720 : : true, lockmode);
13721 : 32 : table_close(childrel, NoLock);
13722 : : }
13723 : :
13724 : : /* Queue validation for phase 3 */
13725 : 84 : newcon = palloc0_object(NewConstraint);
13726 : 84 : newcon->name = constrName;
13727 : 84 : newcon->contype = CONSTR_CHECK;
13728 : 84 : newcon->refrelid = InvalidOid;
13729 : 84 : newcon->refindid = InvalidOid;
13730 : 84 : newcon->conid = con->oid;
13731 : :
13732 : 84 : val = SysCacheGetAttrNotNull(CONSTROID, contuple,
13733 : : Anum_pg_constraint_conbin);
13734 : 84 : conbin = TextDatumGetCString(val);
13735 : 84 : newcon->qual = expand_generated_columns_in_expr(stringToNode(conbin), rel, 1);
13736 : :
13737 : : /* Find or create work queue entry for this table */
13738 : 84 : tab = ATGetQueueEntry(wqueue, rel);
13739 : 84 : tab->constraints = lappend(tab->constraints, newcon);
13740 : :
13741 : : /*
13742 : : * Invalidate relcache so that others see the new validated constraint.
13743 : : */
13744 : 84 : CacheInvalidateRelcache(rel);
13745 : :
13746 : : /*
13747 : : * Now update the catalog, while we have the door open.
13748 : : */
13749 : 84 : copyTuple = heap_copytuple(contuple);
13750 : 84 : copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
13751 : 84 : copy_con->convalidated = true;
13752 : 84 : CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);
13753 : :
13754 [ - + ]: 84 : InvokeObjectPostAlterHook(ConstraintRelationId, con->oid, 0);
13755 : :
13756 : 84 : heap_freetuple(copyTuple);
13757 : 84 : }
13758 : :
13759 : : /*
13760 : : * QueueNNConstraintValidation
13761 : : *
13762 : : * Add an entry to the wqueue to validate the given not-null constraint in
13763 : : * Phase 3 and update the convalidated field in the pg_constraint catalog for
13764 : : * the specified relation and all its inheriting children.
13765 : : */
13766 : : static void
13767 : 78 : QueueNNConstraintValidation(List **wqueue, Relation conrel, Relation rel,
13768 : : HeapTuple contuple, bool recurse, bool recursing,
13769 : : LOCKMODE lockmode)
13770 : : {
13771 : : Form_pg_constraint con;
13772 : : AlteredTableInfo *tab;
13773 : : HeapTuple copyTuple;
13774 : : Form_pg_constraint copy_con;
13775 : 78 : List *children = NIL;
13776 : : AttrNumber attnum;
13777 : : char *colname;
13778 : :
13779 : 78 : con = (Form_pg_constraint) GETSTRUCT(contuple);
13780 : : Assert(con->contype == CONSTRAINT_NOTNULL);
13781 : :
13782 : 78 : attnum = extractNotNullColumn(contuple);
13783 : :
13784 : : /*
13785 : : * If we're recursing, we've already done this for parent, so skip it.
13786 : : * Also, if the constraint is a NO INHERIT constraint, we shouldn't try to
13787 : : * look for it in the children.
13788 : : *
13789 : : * We recurse before validating on the parent, to reduce risk of
13790 : : * deadlocks.
13791 : : */
13792 [ + + + - ]: 78 : if (!recursing && !con->connoinherit)
13793 : 54 : children = find_all_inheritors(RelationGetRelid(rel), lockmode, NULL);
13794 : :
13795 : 78 : colname = get_attname(RelationGetRelid(rel), attnum, false);
13796 [ + + + + : 262 : foreach_oid(childoid, children)
+ + ]
13797 : : {
13798 : : Relation childrel;
13799 : : HeapTuple contup;
13800 : : Form_pg_constraint childcon;
13801 : : char *conname;
13802 : :
13803 [ + + ]: 106 : if (childoid == RelationGetRelid(rel))
13804 : 54 : continue;
13805 : :
13806 : : /*
13807 : : * If we are told not to recurse, there had better not be any child
13808 : : * tables, because we can't mark the constraint on the parent valid
13809 : : * unless it is valid for all child tables.
13810 : : */
13811 [ - + ]: 52 : if (!recurse)
13812 [ # # ]: 0 : ereport(ERROR,
13813 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
13814 : : errmsg("constraint must be validated on child tables too"));
13815 : :
13816 : : /*
13817 : : * The column on child might have a different attnum, so search by
13818 : : * column name.
13819 : : */
13820 : 52 : contup = findNotNullConstraint(childoid, colname);
13821 [ - + ]: 52 : if (!contup)
13822 [ # # ]: 0 : elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation \"%s\"",
13823 : : colname, get_rel_name(childoid));
13824 : 52 : childcon = (Form_pg_constraint) GETSTRUCT(contup);
13825 [ + + ]: 52 : if (childcon->convalidated)
13826 : 28 : continue;
13827 : :
13828 : : /* find_all_inheritors already got lock */
13829 : 24 : childrel = table_open(childoid, NoLock);
13830 : 24 : conname = pstrdup(NameStr(childcon->conname));
13831 : :
13832 : : /* XXX improve ATExecValidateConstraint API to avoid double search */
13833 : 24 : ATExecValidateConstraint(wqueue, childrel, conname,
13834 : : false, true, lockmode);
13835 : 24 : table_close(childrel, NoLock);
13836 : : }
13837 : :
13838 : : /* Set attnotnull appropriately without queueing another validation */
13839 : 78 : set_attnotnull(NULL, rel, attnum, false);
13840 : :
13841 : 78 : tab = ATGetQueueEntry(wqueue, rel);
13842 : 78 : tab->verify_new_notnull = true;
13843 : :
13844 : : /*
13845 : : * Invalidate relcache so that others see the new validated constraint.
13846 : : */
13847 : 78 : CacheInvalidateRelcache(rel);
13848 : :
13849 : : /*
13850 : : * Now update the catalogs, while we have the door open.
13851 : : */
13852 : 78 : copyTuple = heap_copytuple(contuple);
13853 : 78 : copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
13854 : 78 : copy_con->convalidated = true;
13855 : 78 : CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);
13856 : :
13857 [ - + ]: 78 : InvokeObjectPostAlterHook(ConstraintRelationId, con->oid, 0);
13858 : :
13859 : 78 : heap_freetuple(copyTuple);
13860 : 78 : }
13861 : :
13862 : : /*
13863 : : * transformColumnNameList - transform list of column names
13864 : : *
13865 : : * Lookup each name and return its attnum and, optionally, type and collation
13866 : : * OIDs
13867 : : *
13868 : : * Note: the name of this function suggests that it's general-purpose,
13869 : : * but actually it's only used to look up names appearing in foreign-key
13870 : : * clauses. The error messages would need work to use it in other cases,
13871 : : * and perhaps the validity checks as well.
13872 : : */
13873 : : static int
13874 : 4597 : transformColumnNameList(Oid relId, List *colList,
13875 : : int16 *attnums, Oid *atttypids, Oid *attcollids)
13876 : : {
13877 : : ListCell *l;
13878 : : int attnum;
13879 : :
13880 : 4597 : attnum = 0;
13881 [ + + + + : 8353 : foreach(l, colList)
+ + ]
13882 : : {
13883 : 3800 : char *attname = strVal(lfirst(l));
13884 : : HeapTuple atttuple;
13885 : : Form_pg_attribute attform;
13886 : :
13887 : 3800 : atttuple = SearchSysCacheAttName(relId, attname);
13888 [ + + ]: 3800 : if (!HeapTupleIsValid(atttuple))
13889 [ + - ]: 36 : ereport(ERROR,
13890 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
13891 : : errmsg("column \"%s\" referenced in foreign key constraint does not exist",
13892 : : attname)));
13893 : 3764 : attform = (Form_pg_attribute) GETSTRUCT(atttuple);
13894 [ + + ]: 3764 : if (attform->attnum < 0)
13895 [ + - ]: 8 : ereport(ERROR,
13896 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
13897 : : errmsg("system columns cannot be used in foreign keys")));
13898 [ - + ]: 3756 : if (attnum >= INDEX_MAX_KEYS)
13899 [ # # ]: 0 : ereport(ERROR,
13900 : : (errcode(ERRCODE_TOO_MANY_COLUMNS),
13901 : : errmsg("cannot have more than %d keys in a foreign key",
13902 : : INDEX_MAX_KEYS)));
13903 : 3756 : attnums[attnum] = attform->attnum;
13904 [ + + ]: 3756 : if (atttypids != NULL)
13905 : 3732 : atttypids[attnum] = attform->atttypid;
13906 [ + + ]: 3756 : if (attcollids != NULL)
13907 : 3732 : attcollids[attnum] = attform->attcollation;
13908 : 3756 : ReleaseSysCache(atttuple);
13909 : 3756 : attnum++;
13910 : : }
13911 : :
13912 : 4553 : return attnum;
13913 : : }
13914 : :
13915 : : /*
13916 : : * transformFkeyGetPrimaryKey -
13917 : : *
13918 : : * Look up the names, attnums, types, and collations of the primary key attributes
13919 : : * for the pkrel. Also return the index OID and index opclasses of the
13920 : : * index supporting the primary key. Also return whether the index has
13921 : : * WITHOUT OVERLAPS.
13922 : : *
13923 : : * All parameters except pkrel are output parameters. Also, the function
13924 : : * return value is the number of attributes in the primary key.
13925 : : *
13926 : : * Used when the column list in the REFERENCES specification is omitted.
13927 : : */
13928 : : static int
13929 : 900 : transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
13930 : : List **attnamelist,
13931 : : int16 *attnums, Oid *atttypids, Oid *attcollids,
13932 : : Oid *opclasses, bool *pk_has_without_overlaps)
13933 : : {
13934 : : List *indexoidlist;
13935 : : ListCell *indexoidscan;
13936 : 900 : HeapTuple indexTuple = NULL;
13937 : 900 : Form_pg_index indexStruct = NULL;
13938 : : Datum indclassDatum;
13939 : : oidvector *indclass;
13940 : : int i;
13941 : :
13942 : : /*
13943 : : * Get the list of index OIDs for the table from the relcache, and look up
13944 : : * each one in the pg_index syscache until we find one marked primary key
13945 : : * (hopefully there isn't more than one such). Insist it's valid, too.
13946 : : */
13947 : 900 : *indexOid = InvalidOid;
13948 : :
13949 : 900 : indexoidlist = RelationGetIndexList(pkrel);
13950 : :
13951 [ + - + - : 904 : foreach(indexoidscan, indexoidlist)
+ - ]
13952 : : {
13953 : 904 : Oid indexoid = lfirst_oid(indexoidscan);
13954 : :
13955 : 904 : indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
13956 [ - + ]: 904 : if (!HeapTupleIsValid(indexTuple))
13957 [ # # ]: 0 : elog(ERROR, "cache lookup failed for index %u", indexoid);
13958 : 904 : indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
13959 [ + + + - ]: 904 : if (indexStruct->indisprimary && indexStruct->indisvalid)
13960 : : {
13961 : : /*
13962 : : * Refuse to use a deferrable primary key. This is per SQL spec,
13963 : : * and there would be a lot of interesting semantic problems if we
13964 : : * tried to allow it.
13965 : : */
13966 [ - + ]: 900 : if (!indexStruct->indimmediate)
13967 [ # # ]: 0 : ereport(ERROR,
13968 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
13969 : : errmsg("cannot use a deferrable primary key for referenced table \"%s\"",
13970 : : RelationGetRelationName(pkrel))));
13971 : :
13972 : 900 : *indexOid = indexoid;
13973 : 900 : break;
13974 : : }
13975 : 4 : ReleaseSysCache(indexTuple);
13976 : : }
13977 : :
13978 : 900 : list_free(indexoidlist);
13979 : :
13980 : : /*
13981 : : * Check that we found it
13982 : : */
13983 [ - + ]: 900 : if (!OidIsValid(*indexOid))
13984 [ # # ]: 0 : ereport(ERROR,
13985 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
13986 : : errmsg("there is no primary key for referenced table \"%s\"",
13987 : : RelationGetRelationName(pkrel))));
13988 : :
13989 : : /* Must get indclass the hard way */
13990 : 900 : indclassDatum = SysCacheGetAttrNotNull(INDEXRELID, indexTuple,
13991 : : Anum_pg_index_indclass);
13992 : 900 : indclass = (oidvector *) DatumGetPointer(indclassDatum);
13993 : :
13994 : : /*
13995 : : * Now build the list of PK attributes from the indkey definition (we
13996 : : * assume a primary key cannot have expressional elements)
13997 : : */
13998 : 900 : *attnamelist = NIL;
13999 [ + + ]: 2116 : for (i = 0; i < indexStruct->indnkeyatts; i++)
14000 : : {
14001 : 1216 : int pkattno = indexStruct->indkey.values[i];
14002 : :
14003 : 1216 : attnums[i] = pkattno;
14004 : 1216 : atttypids[i] = attnumTypeId(pkrel, pkattno);
14005 : 1216 : attcollids[i] = attnumCollationId(pkrel, pkattno);
14006 : 1216 : opclasses[i] = indclass->values[i];
14007 : 1216 : *attnamelist = lappend(*attnamelist,
14008 : 1216 : makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno)))));
14009 : : }
14010 : :
14011 : 900 : *pk_has_without_overlaps = indexStruct->indisexclusion;
14012 : :
14013 : 900 : ReleaseSysCache(indexTuple);
14014 : :
14015 : 900 : return i;
14016 : : }
14017 : :
14018 : : /*
14019 : : * transformFkeyCheckAttrs -
14020 : : *
14021 : : * Validate that the 'attnums' columns in the 'pkrel' relation are valid to
14022 : : * reference as part of a foreign key constraint.
14023 : : *
14024 : : * Returns the OID of the unique index supporting the constraint and
14025 : : * populates the caller-provided 'opclasses' array with the opclasses
14026 : : * associated with the index columns. Also sets whether the index
14027 : : * uses WITHOUT OVERLAPS.
14028 : : *
14029 : : * Raises an ERROR on validation failure.
14030 : : */
14031 : : static Oid
14032 : 879 : transformFkeyCheckAttrs(Relation pkrel,
14033 : : int numattrs, int16 *attnums,
14034 : : bool with_period, Oid *opclasses,
14035 : : bool *pk_has_without_overlaps)
14036 : : {
14037 : 879 : Oid indexoid = InvalidOid;
14038 : 879 : bool found = false;
14039 : 879 : bool found_deferrable = false;
14040 : : List *indexoidlist;
14041 : : ListCell *indexoidscan;
14042 : : int i,
14043 : : j;
14044 : :
14045 : : /*
14046 : : * Reject duplicate appearances of columns in the referenced-columns list.
14047 : : * Such a case is forbidden by the SQL standard, and even if we thought it
14048 : : * useful to allow it, there would be ambiguity about how to match the
14049 : : * list to unique indexes (in particular, it'd be unclear which index
14050 : : * opclass goes with which FK column).
14051 : : */
14052 [ + + ]: 2051 : for (i = 0; i < numattrs; i++)
14053 : : {
14054 [ + + ]: 1547 : for (j = i + 1; j < numattrs; j++)
14055 : : {
14056 [ + + ]: 375 : if (attnums[i] == attnums[j])
14057 [ + - ]: 16 : ereport(ERROR,
14058 : : (errcode(ERRCODE_INVALID_FOREIGN_KEY),
14059 : : errmsg("foreign key referenced-columns list must not contain duplicates")));
14060 : : }
14061 : : }
14062 : :
14063 : : /*
14064 : : * Get the list of index OIDs for the table from the relcache, and look up
14065 : : * each one in the pg_index syscache, and match unique indexes to the list
14066 : : * of attnums we are given.
14067 : : */
14068 : 863 : indexoidlist = RelationGetIndexList(pkrel);
14069 : :
14070 [ + - + + : 982 : foreach(indexoidscan, indexoidlist)
+ + ]
14071 : : {
14072 : : HeapTuple indexTuple;
14073 : : Form_pg_index indexStruct;
14074 : :
14075 : 974 : indexoid = lfirst_oid(indexoidscan);
14076 : 974 : indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
14077 [ - + ]: 974 : if (!HeapTupleIsValid(indexTuple))
14078 [ # # ]: 0 : elog(ERROR, "cache lookup failed for index %u", indexoid);
14079 : 974 : indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
14080 : :
14081 : : /*
14082 : : * Must have the right number of columns; must be unique (or if
14083 : : * temporal then exclusion instead) and not a partial index; forget it
14084 : : * if there are any expressions, too. Invalid indexes are out as well.
14085 : : */
14086 [ + + + + : 974 : if (indexStruct->indnkeyatts == numattrs &&
+ + ]
14087 : 893 : (with_period ? indexStruct->indisexclusion : indexStruct->indisunique) &&
14088 [ + - + - ]: 1786 : indexStruct->indisvalid &&
14089 [ + - ]: 1786 : heap_attisnull(indexTuple, Anum_pg_index_indpred, NULL) &&
14090 : 893 : heap_attisnull(indexTuple, Anum_pg_index_indexprs, NULL))
14091 : : {
14092 : : Datum indclassDatum;
14093 : : oidvector *indclass;
14094 : :
14095 : : /* Must get indclass the hard way */
14096 : 893 : indclassDatum = SysCacheGetAttrNotNull(INDEXRELID, indexTuple,
14097 : : Anum_pg_index_indclass);
14098 : 893 : indclass = (oidvector *) DatumGetPointer(indclassDatum);
14099 : :
14100 : : /*
14101 : : * The given attnum list may match the index columns in any order.
14102 : : * Check for a match, and extract the appropriate opclasses while
14103 : : * we're at it.
14104 : : *
14105 : : * We know that attnums[] is duplicate-free per the test at the
14106 : : * start of this function, and we checked above that the number of
14107 : : * index columns agrees, so if we find a match for each attnums[]
14108 : : * entry then we must have a one-to-one match in some order.
14109 : : */
14110 [ + + ]: 2057 : for (i = 0; i < numattrs; i++)
14111 : : {
14112 : 1202 : found = false;
14113 [ + + ]: 1599 : for (j = 0; j < numattrs; j++)
14114 : : {
14115 [ + + ]: 1561 : if (attnums[i] == indexStruct->indkey.values[j])
14116 : : {
14117 : 1164 : opclasses[i] = indclass->values[j];
14118 : 1164 : found = true;
14119 : 1164 : break;
14120 : : }
14121 : : }
14122 [ + + ]: 1202 : if (!found)
14123 : 38 : break;
14124 : : }
14125 : : /* The last attribute in the index must be the PERIOD FK part */
14126 [ + + + + ]: 893 : if (found && with_period)
14127 : : {
14128 : 80 : int16 periodattnum = attnums[numattrs - 1];
14129 : :
14130 : 80 : found = (periodattnum == indexStruct->indkey.values[numattrs - 1]);
14131 : : }
14132 : :
14133 : : /*
14134 : : * Refuse to use a deferrable unique/primary key. This is per SQL
14135 : : * spec, and there would be a lot of interesting semantic problems
14136 : : * if we tried to allow it.
14137 : : */
14138 [ + + - + ]: 893 : if (found && !indexStruct->indimmediate)
14139 : : {
14140 : : /*
14141 : : * Remember that we found an otherwise matching index, so that
14142 : : * we can generate a more appropriate error message.
14143 : : */
14144 : 0 : found_deferrable = true;
14145 : 0 : found = false;
14146 : : }
14147 : :
14148 : : /* We need to know whether the index has WITHOUT OVERLAPS */
14149 [ + + ]: 893 : if (found)
14150 : 855 : *pk_has_without_overlaps = indexStruct->indisexclusion;
14151 : : }
14152 : 974 : ReleaseSysCache(indexTuple);
14153 [ + + ]: 974 : if (found)
14154 : 855 : break;
14155 : : }
14156 : :
14157 [ + + ]: 863 : if (!found)
14158 : : {
14159 [ - + ]: 8 : if (found_deferrable)
14160 [ # # ]: 0 : ereport(ERROR,
14161 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
14162 : : errmsg("cannot use a deferrable unique constraint for referenced table \"%s\"",
14163 : : RelationGetRelationName(pkrel))));
14164 : : else
14165 [ + - ]: 8 : ereport(ERROR,
14166 : : (errcode(ERRCODE_INVALID_FOREIGN_KEY),
14167 : : errmsg("there is no unique constraint matching given keys for referenced table \"%s\"",
14168 : : RelationGetRelationName(pkrel))));
14169 : : }
14170 : :
14171 : 855 : list_free(indexoidlist);
14172 : :
14173 : 855 : return indexoid;
14174 : : }
14175 : :
14176 : : /*
14177 : : * findFkeyCast -
14178 : : *
14179 : : * Wrapper around find_coercion_pathway() for ATAddForeignKeyConstraint().
14180 : : * Caller has equal regard for binary coercibility and for an exact match.
14181 : : */
14182 : : static CoercionPathType
14183 : 8 : findFkeyCast(Oid targetTypeId, Oid sourceTypeId, Oid *funcid)
14184 : : {
14185 : : CoercionPathType ret;
14186 : :
14187 [ + - ]: 8 : if (targetTypeId == sourceTypeId)
14188 : : {
14189 : 8 : ret = COERCION_PATH_RELABELTYPE;
14190 : 8 : *funcid = InvalidOid;
14191 : : }
14192 : : else
14193 : : {
14194 : 0 : ret = find_coercion_pathway(targetTypeId, sourceTypeId,
14195 : : COERCION_IMPLICIT, funcid);
14196 [ # # ]: 0 : if (ret == COERCION_PATH_NONE)
14197 : : /* A previously-relied-upon cast is now gone. */
14198 [ # # ]: 0 : elog(ERROR, "could not find cast from %u to %u",
14199 : : sourceTypeId, targetTypeId);
14200 : : }
14201 : :
14202 : 8 : return ret;
14203 : : }
14204 : :
14205 : : /*
14206 : : * Permissions checks on the referenced table for ADD FOREIGN KEY
14207 : : *
14208 : : * Note: we have already checked that the user owns the referencing table,
14209 : : * else we'd have failed much earlier; no additional checks are needed for it.
14210 : : */
14211 : : static void
14212 : 1731 : checkFkeyPermissions(Relation rel, int16 *attnums, int natts)
14213 : : {
14214 : 1731 : Oid roleid = GetUserId();
14215 : : AclResult aclresult;
14216 : : int i;
14217 : :
14218 : : /* Okay if we have relation-level REFERENCES permission */
14219 : 1731 : aclresult = pg_class_aclcheck(RelationGetRelid(rel), roleid,
14220 : : ACL_REFERENCES);
14221 [ + - ]: 1731 : if (aclresult == ACLCHECK_OK)
14222 : 1731 : return;
14223 : : /* Else we must have REFERENCES on each column */
14224 [ # # ]: 0 : for (i = 0; i < natts; i++)
14225 : : {
14226 : 0 : aclresult = pg_attribute_aclcheck(RelationGetRelid(rel), attnums[i],
14227 : : roleid, ACL_REFERENCES);
14228 [ # # ]: 0 : if (aclresult != ACLCHECK_OK)
14229 : 0 : aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
14230 : 0 : RelationGetRelationName(rel));
14231 : : }
14232 : : }
14233 : :
14234 : : /*
14235 : : * Scan the existing rows in a table to verify they meet a proposed FK
14236 : : * constraint.
14237 : : *
14238 : : * Caller must have opened and locked both relations appropriately.
14239 : : */
14240 : : static void
14241 : 832 : validateForeignKeyConstraint(char *conname,
14242 : : Relation rel,
14243 : : Relation pkrel,
14244 : : Oid pkindOid,
14245 : : Oid constraintOid,
14246 : : bool hasperiod)
14247 : : {
14248 : : TupleTableSlot *slot;
14249 : : TableScanDesc scan;
14250 : 832 : Trigger trig = {0};
14251 : : Snapshot snapshot;
14252 : : MemoryContext oldcxt;
14253 : : MemoryContext perTupCxt;
14254 : :
14255 [ - + ]: 832 : ereport(DEBUG1,
14256 : : (errmsg_internal("validating foreign key constraint \"%s\"", conname)));
14257 : :
14258 : : /*
14259 : : * Build a trigger call structure; we'll need it either way.
14260 : : */
14261 : 832 : trig.tgoid = InvalidOid;
14262 : 832 : trig.tgname = conname;
14263 : 832 : trig.tgenabled = TRIGGER_FIRES_ON_ORIGIN;
14264 : 832 : trig.tgisinternal = true;
14265 : 832 : trig.tgconstrrelid = RelationGetRelid(pkrel);
14266 : 832 : trig.tgconstrindid = pkindOid;
14267 : 832 : trig.tgconstraint = constraintOid;
14268 : 832 : trig.tgdeferrable = false;
14269 : 832 : trig.tginitdeferred = false;
14270 : : /* we needn't fill in remaining fields */
14271 : :
14272 : : /*
14273 : : * See if we can do it with a single LEFT JOIN query. A false result
14274 : : * indicates we must proceed with the fire-the-trigger method. We can't do
14275 : : * a LEFT JOIN for temporal FKs yet, but we can once we support temporal
14276 : : * left joins.
14277 : : */
14278 [ + + + + ]: 832 : if (!hasperiod && RI_Initial_Check(&trig, rel, pkrel))
14279 : 702 : return;
14280 : :
14281 : : /*
14282 : : * Scan through each tuple, calling RI_FKey_check_ins (insert trigger) as
14283 : : * if that tuple had just been inserted. If any of those fail, it should
14284 : : * ereport(ERROR) and that's that.
14285 : : */
14286 : 71 : snapshot = RegisterSnapshot(GetLatestSnapshot());
14287 : 71 : slot = table_slot_create(rel, NULL);
14288 : 71 : scan = table_beginscan(rel, snapshot, 0, NULL,
14289 : : SO_NONE);
14290 : 71 : perTupCxt = AllocSetContextCreate(CurrentMemoryContext,
14291 : : "validateForeignKeyConstraint",
14292 : : ALLOCSET_SMALL_SIZES);
14293 : 71 : oldcxt = MemoryContextSwitchTo(perTupCxt);
14294 : :
14295 [ + + ]: 127 : while (table_scan_getnextslot(scan, ForwardScanDirection, slot))
14296 : : {
14297 : 68 : LOCAL_FCINFO(fcinfo, 0);
14298 : 68 : TriggerData trigdata = {0};
14299 : :
14300 [ - + ]: 68 : CHECK_FOR_INTERRUPTS();
14301 : :
14302 : : /*
14303 : : * Make a call to the trigger function
14304 : : *
14305 : : * No parameters are passed, but we do set a context
14306 : : */
14307 [ + - + - : 340 : MemSet(fcinfo, 0, SizeForFunctionCallInfo(0));
+ - + - +
+ ]
14308 : :
14309 : : /*
14310 : : * We assume RI_FKey_check_ins won't look at flinfo...
14311 : : */
14312 : 68 : trigdata.type = T_TriggerData;
14313 : 68 : trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
14314 : 68 : trigdata.tg_relation = rel;
14315 : 68 : trigdata.tg_trigtuple = ExecFetchSlotHeapTuple(slot, false, NULL);
14316 : 68 : trigdata.tg_trigslot = slot;
14317 : 68 : trigdata.tg_trigger = &trig;
14318 : :
14319 : 68 : fcinfo->context = (Node *) &trigdata;
14320 : :
14321 : 68 : RI_FKey_check_ins(fcinfo);
14322 : :
14323 : 56 : MemoryContextReset(perTupCxt);
14324 : : }
14325 : :
14326 : 59 : MemoryContextSwitchTo(oldcxt);
14327 : 59 : MemoryContextDelete(perTupCxt);
14328 : 59 : table_endscan(scan);
14329 : 59 : UnregisterSnapshot(snapshot);
14330 : 59 : ExecDropSingleTupleTableSlot(slot);
14331 : : }
14332 : :
14333 : : /*
14334 : : * CreateFKCheckTrigger
14335 : : * Creates the insert (on_insert=true) or update "check" trigger that
14336 : : * implements a given foreign key
14337 : : *
14338 : : * Returns the OID of the so created trigger.
14339 : : */
14340 : : static Oid
14341 : 4180 : CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
14342 : : Oid constraintOid, Oid indexOid, Oid parentTrigOid,
14343 : : bool on_insert)
14344 : : {
14345 : : ObjectAddress trigAddress;
14346 : : CreateTrigStmt *fk_trigger;
14347 : :
14348 : : /*
14349 : : * Note: for a self-referential FK (referencing and referenced tables are
14350 : : * the same), it is important that the ON UPDATE action fires before the
14351 : : * CHECK action, since both triggers will fire on the same row during an
14352 : : * UPDATE event; otherwise the CHECK trigger will be checking a non-final
14353 : : * state of the row. Triggers fire in name order, so we ensure this by
14354 : : * using names like "RI_ConstraintTrigger_a_NNNN" for the action triggers
14355 : : * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
14356 : : */
14357 : 4180 : fk_trigger = makeNode(CreateTrigStmt);
14358 : 4180 : fk_trigger->replace = false;
14359 : 4180 : fk_trigger->isconstraint = true;
14360 : 4180 : fk_trigger->trigname = "RI_ConstraintTrigger_c";
14361 : 4180 : fk_trigger->relation = NULL;
14362 : :
14363 : : /* Either ON INSERT or ON UPDATE */
14364 [ + + ]: 4180 : if (on_insert)
14365 : : {
14366 : 2090 : fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
14367 : 2090 : fk_trigger->events = TRIGGER_TYPE_INSERT;
14368 : : }
14369 : : else
14370 : : {
14371 : 2090 : fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
14372 : 2090 : fk_trigger->events = TRIGGER_TYPE_UPDATE;
14373 : : }
14374 : :
14375 : 4180 : fk_trigger->args = NIL;
14376 : 4180 : fk_trigger->row = true;
14377 : 4180 : fk_trigger->timing = TRIGGER_TYPE_AFTER;
14378 : 4180 : fk_trigger->columns = NIL;
14379 : 4180 : fk_trigger->whenClause = NULL;
14380 : 4180 : fk_trigger->transitionRels = NIL;
14381 : 4180 : fk_trigger->deferrable = fkconstraint->deferrable;
14382 : 4180 : fk_trigger->initdeferred = fkconstraint->initdeferred;
14383 : 4180 : fk_trigger->constrrel = NULL;
14384 : :
14385 : 4180 : trigAddress = CreateTrigger(fk_trigger, NULL, myRelOid, refRelOid,
14386 : : constraintOid, indexOid, InvalidOid,
14387 : : parentTrigOid, NULL, true, false);
14388 : :
14389 : : /* Make changes-so-far visible */
14390 : 4180 : CommandCounterIncrement();
14391 : :
14392 : 4180 : return trigAddress.objectId;
14393 : : }
14394 : :
14395 : : /*
14396 : : * createForeignKeyActionTriggers
14397 : : * Create the referenced-side "action" triggers that implement a foreign
14398 : : * key.
14399 : : *
14400 : : * Returns the OIDs of the so created triggers in *deleteTrigOid and
14401 : : * *updateTrigOid.
14402 : : */
14403 : : static void
14404 : 2377 : createForeignKeyActionTriggers(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
14405 : : Oid constraintOid, Oid indexOid,
14406 : : Oid parentDelTrigger, Oid parentUpdTrigger,
14407 : : Oid *deleteTrigOid, Oid *updateTrigOid)
14408 : : {
14409 : : CreateTrigStmt *fk_trigger;
14410 : : ObjectAddress trigAddress;
14411 : :
14412 : : /*
14413 : : * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
14414 : : * DELETE action on the referenced table.
14415 : : */
14416 : 2377 : fk_trigger = makeNode(CreateTrigStmt);
14417 : 2377 : fk_trigger->replace = false;
14418 : 2377 : fk_trigger->isconstraint = true;
14419 : 2377 : fk_trigger->trigname = "RI_ConstraintTrigger_a";
14420 : 2377 : fk_trigger->relation = NULL;
14421 : 2377 : fk_trigger->args = NIL;
14422 : 2377 : fk_trigger->row = true;
14423 : 2377 : fk_trigger->timing = TRIGGER_TYPE_AFTER;
14424 : 2377 : fk_trigger->events = TRIGGER_TYPE_DELETE;
14425 : 2377 : fk_trigger->columns = NIL;
14426 : 2377 : fk_trigger->whenClause = NULL;
14427 : 2377 : fk_trigger->transitionRels = NIL;
14428 : 2377 : fk_trigger->constrrel = NULL;
14429 : :
14430 [ + + + + : 2377 : switch (fkconstraint->fk_del_action)
+ - ]
14431 : : {
14432 : 1948 : case FKCONSTR_ACTION_NOACTION:
14433 : 1948 : fk_trigger->deferrable = fkconstraint->deferrable;
14434 : 1948 : fk_trigger->initdeferred = fkconstraint->initdeferred;
14435 : 1948 : fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
14436 : 1948 : break;
14437 : 20 : case FKCONSTR_ACTION_RESTRICT:
14438 : 20 : fk_trigger->deferrable = false;
14439 : 20 : fk_trigger->initdeferred = false;
14440 : 20 : fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
14441 : 20 : break;
14442 : 306 : case FKCONSTR_ACTION_CASCADE:
14443 : 306 : fk_trigger->deferrable = false;
14444 : 306 : fk_trigger->initdeferred = false;
14445 : 306 : fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
14446 : 306 : break;
14447 : 63 : case FKCONSTR_ACTION_SETNULL:
14448 : 63 : fk_trigger->deferrable = false;
14449 : 63 : fk_trigger->initdeferred = false;
14450 : 63 : fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
14451 : 63 : break;
14452 : 40 : case FKCONSTR_ACTION_SETDEFAULT:
14453 : 40 : fk_trigger->deferrable = false;
14454 : 40 : fk_trigger->initdeferred = false;
14455 : 40 : fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
14456 : 40 : break;
14457 : 0 : default:
14458 [ # # ]: 0 : elog(ERROR, "unrecognized FK action type: %d",
14459 : : (int) fkconstraint->fk_del_action);
14460 : : break;
14461 : : }
14462 : :
14463 : 2377 : trigAddress = CreateTrigger(fk_trigger, NULL, refRelOid, myRelOid,
14464 : : constraintOid, indexOid, InvalidOid,
14465 : : parentDelTrigger, NULL, true, false);
14466 [ + - ]: 2377 : if (deleteTrigOid)
14467 : 2377 : *deleteTrigOid = trigAddress.objectId;
14468 : :
14469 : : /* Make changes-so-far visible */
14470 : 2377 : CommandCounterIncrement();
14471 : :
14472 : : /*
14473 : : * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
14474 : : * UPDATE action on the referenced table.
14475 : : */
14476 : 2377 : fk_trigger = makeNode(CreateTrigStmt);
14477 : 2377 : fk_trigger->replace = false;
14478 : 2377 : fk_trigger->isconstraint = true;
14479 : 2377 : fk_trigger->trigname = "RI_ConstraintTrigger_a";
14480 : 2377 : fk_trigger->relation = NULL;
14481 : 2377 : fk_trigger->args = NIL;
14482 : 2377 : fk_trigger->row = true;
14483 : 2377 : fk_trigger->timing = TRIGGER_TYPE_AFTER;
14484 : 2377 : fk_trigger->events = TRIGGER_TYPE_UPDATE;
14485 : 2377 : fk_trigger->columns = NIL;
14486 : 2377 : fk_trigger->whenClause = NULL;
14487 : 2377 : fk_trigger->transitionRels = NIL;
14488 : 2377 : fk_trigger->constrrel = NULL;
14489 : :
14490 [ + + + + : 2377 : switch (fkconstraint->fk_upd_action)
+ - ]
14491 : : {
14492 : 2073 : case FKCONSTR_ACTION_NOACTION:
14493 : 2073 : fk_trigger->deferrable = fkconstraint->deferrable;
14494 : 2073 : fk_trigger->initdeferred = fkconstraint->initdeferred;
14495 : 2073 : fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
14496 : 2073 : break;
14497 : 24 : case FKCONSTR_ACTION_RESTRICT:
14498 : 24 : fk_trigger->deferrable = false;
14499 : 24 : fk_trigger->initdeferred = false;
14500 : 24 : fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
14501 : 24 : break;
14502 : 211 : case FKCONSTR_ACTION_CASCADE:
14503 : 211 : fk_trigger->deferrable = false;
14504 : 211 : fk_trigger->initdeferred = false;
14505 : 211 : fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
14506 : 211 : break;
14507 : 41 : case FKCONSTR_ACTION_SETNULL:
14508 : 41 : fk_trigger->deferrable = false;
14509 : 41 : fk_trigger->initdeferred = false;
14510 : 41 : fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
14511 : 41 : break;
14512 : 28 : case FKCONSTR_ACTION_SETDEFAULT:
14513 : 28 : fk_trigger->deferrable = false;
14514 : 28 : fk_trigger->initdeferred = false;
14515 : 28 : fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
14516 : 28 : break;
14517 : 0 : default:
14518 [ # # ]: 0 : elog(ERROR, "unrecognized FK action type: %d",
14519 : : (int) fkconstraint->fk_upd_action);
14520 : : break;
14521 : : }
14522 : :
14523 : 2377 : trigAddress = CreateTrigger(fk_trigger, NULL, refRelOid, myRelOid,
14524 : : constraintOid, indexOid, InvalidOid,
14525 : : parentUpdTrigger, NULL, true, false);
14526 [ + - ]: 2377 : if (updateTrigOid)
14527 : 2377 : *updateTrigOid = trigAddress.objectId;
14528 : 2377 : }
14529 : :
14530 : : /*
14531 : : * createForeignKeyCheckTriggers
14532 : : * Create the referencing-side "check" triggers that implement a foreign
14533 : : * key.
14534 : : *
14535 : : * Returns the OIDs of the so created triggers in *insertTrigOid and
14536 : : * *updateTrigOid.
14537 : : */
14538 : : static void
14539 : 2090 : createForeignKeyCheckTriggers(Oid myRelOid, Oid refRelOid,
14540 : : Constraint *fkconstraint, Oid constraintOid,
14541 : : Oid indexOid,
14542 : : Oid parentInsTrigger, Oid parentUpdTrigger,
14543 : : Oid *insertTrigOid, Oid *updateTrigOid)
14544 : : {
14545 : 2090 : *insertTrigOid = CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint,
14546 : : constraintOid, indexOid,
14547 : : parentInsTrigger, true);
14548 : 2090 : *updateTrigOid = CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint,
14549 : : constraintOid, indexOid,
14550 : : parentUpdTrigger, false);
14551 : 2090 : }
14552 : :
14553 : : /*
14554 : : * ALTER TABLE DROP CONSTRAINT
14555 : : *
14556 : : * Like DROP COLUMN, we can't use the normal ALTER TABLE recursion mechanism.
14557 : : */
14558 : : static void
14559 : 578 : ATExecDropConstraint(Relation rel, const char *constrName,
14560 : : DropBehavior behavior, bool recurse,
14561 : : bool missing_ok, LOCKMODE lockmode)
14562 : : {
14563 : : Relation conrel;
14564 : : SysScanDesc scan;
14565 : : ScanKeyData skey[3];
14566 : : HeapTuple tuple;
14567 : 578 : bool found = false;
14568 : :
14569 : 578 : conrel = table_open(ConstraintRelationId, RowExclusiveLock);
14570 : :
14571 : : /*
14572 : : * Find and drop the target constraint
14573 : : */
14574 : 578 : ScanKeyInit(&skey[0],
14575 : : Anum_pg_constraint_conrelid,
14576 : : BTEqualStrategyNumber, F_OIDEQ,
14577 : : ObjectIdGetDatum(RelationGetRelid(rel)));
14578 : 578 : ScanKeyInit(&skey[1],
14579 : : Anum_pg_constraint_contypid,
14580 : : BTEqualStrategyNumber, F_OIDEQ,
14581 : : ObjectIdGetDatum(InvalidOid));
14582 : 578 : ScanKeyInit(&skey[2],
14583 : : Anum_pg_constraint_conname,
14584 : : BTEqualStrategyNumber, F_NAMEEQ,
14585 : : CStringGetDatum(constrName));
14586 : 578 : scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId,
14587 : : true, NULL, 3, skey);
14588 : :
14589 : : /* There can be at most one matching row */
14590 [ + + ]: 578 : if (HeapTupleIsValid(tuple = systable_getnext(scan)))
14591 : : {
14592 : 554 : dropconstraint_internal(rel, tuple, behavior, recurse, false,
14593 : : missing_ok, lockmode);
14594 : 430 : found = true;
14595 : : }
14596 : :
14597 : 454 : systable_endscan(scan);
14598 : :
14599 [ + + ]: 454 : if (!found)
14600 : : {
14601 [ + + ]: 24 : if (!missing_ok)
14602 [ + - ]: 16 : ereport(ERROR,
14603 : : errcode(ERRCODE_UNDEFINED_OBJECT),
14604 : : errmsg("constraint \"%s\" of relation \"%s\" does not exist",
14605 : : constrName, RelationGetRelationName(rel)));
14606 : : else
14607 [ + - ]: 8 : ereport(NOTICE,
14608 : : errmsg("constraint \"%s\" of relation \"%s\" does not exist, skipping",
14609 : : constrName, RelationGetRelationName(rel)));
14610 : : }
14611 : :
14612 : 438 : table_close(conrel, RowExclusiveLock);
14613 : 438 : }
14614 : :
14615 : : /*
14616 : : * Remove a constraint, using its pg_constraint tuple
14617 : : *
14618 : : * Implementation for ALTER TABLE DROP CONSTRAINT and ALTER TABLE ALTER COLUMN
14619 : : * DROP NOT NULL.
14620 : : *
14621 : : * Returns the address of the constraint being removed.
14622 : : */
14623 : : static ObjectAddress
14624 : 858 : dropconstraint_internal(Relation rel, HeapTuple constraintTup, DropBehavior behavior,
14625 : : bool recurse, bool recursing, bool missing_ok,
14626 : : LOCKMODE lockmode)
14627 : : {
14628 : : Relation conrel;
14629 : : Form_pg_constraint con;
14630 : : ObjectAddress conobj;
14631 : : List *children;
14632 : 858 : bool is_no_inherit_constraint = false;
14633 : : char *constrName;
14634 : 858 : char *colname = NULL;
14635 : :
14636 : : /* Guard against stack overflow due to overly deep inheritance tree. */
14637 : 858 : check_stack_depth();
14638 : :
14639 : : /* At top level, permission check was done in ATPrepCmd, else do it */
14640 [ + + ]: 858 : if (recursing)
14641 : 159 : ATSimplePermissions(AT_DropConstraint, rel,
14642 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
14643 : :
14644 : 854 : conrel = table_open(ConstraintRelationId, RowExclusiveLock);
14645 : :
14646 : 854 : con = (Form_pg_constraint) GETSTRUCT(constraintTup);
14647 : 854 : constrName = NameStr(con->conname);
14648 : :
14649 : : /* Don't allow drop of inherited constraints */
14650 [ + + + + ]: 854 : if (con->coninhcount > 0 && !recursing)
14651 [ + - ]: 104 : ereport(ERROR,
14652 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
14653 : : errmsg("cannot drop inherited constraint \"%s\" of relation \"%s\"",
14654 : : constrName, RelationGetRelationName(rel))));
14655 : :
14656 : : /*
14657 : : * Reset pg_constraint.attnotnull, if this is a not-null constraint.
14658 : : *
14659 : : * While doing that, we're in a good position to disallow dropping a not-
14660 : : * null constraint underneath a primary key, a replica identity index, or
14661 : : * a generated identity column.
14662 : : */
14663 [ + + ]: 750 : if (con->contype == CONSTRAINT_NOTNULL)
14664 : : {
14665 : 207 : Relation attrel = table_open(AttributeRelationId, RowExclusiveLock);
14666 : 207 : AttrNumber attnum = extractNotNullColumn(constraintTup);
14667 : : Bitmapset *pkattrs;
14668 : : Bitmapset *irattrs;
14669 : : HeapTuple atttup;
14670 : : Form_pg_attribute attForm;
14671 : :
14672 : : /* save column name for recursion step */
14673 : 207 : colname = get_attname(RelationGetRelid(rel), attnum, false);
14674 : :
14675 : : /*
14676 : : * Disallow if it's in the primary key. For partitioned tables we
14677 : : * cannot rely solely on RelationGetIndexAttrBitmap, because it'll
14678 : : * return NULL if the primary key is invalid; but we still need to
14679 : : * protect not-null constraints under such a constraint, so check the
14680 : : * slow way.
14681 : : */
14682 : 207 : pkattrs = RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_PRIMARY_KEY);
14683 : :
14684 [ + + ]: 207 : if (pkattrs == NULL &&
14685 [ + + ]: 187 : rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
14686 : : {
14687 : 12 : Oid pkindex = RelationGetPrimaryKeyIndex(rel, true);
14688 : :
14689 [ - + ]: 12 : if (OidIsValid(pkindex))
14690 : : {
14691 : 0 : Relation pk = relation_open(pkindex, AccessShareLock);
14692 : :
14693 : 0 : pkattrs = NULL;
14694 [ # # ]: 0 : for (int i = 0; i < pk->rd_index->indnkeyatts; i++)
14695 : 0 : pkattrs = bms_add_member(pkattrs, pk->rd_index->indkey.values[i]);
14696 : :
14697 : 0 : relation_close(pk, AccessShareLock);
14698 : : }
14699 : : }
14700 : :
14701 [ + + + + ]: 227 : if (pkattrs &&
14702 : 20 : bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber, pkattrs))
14703 [ + - ]: 16 : ereport(ERROR,
14704 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
14705 : : errmsg("column \"%s\" is in a primary key",
14706 : : get_attname(RelationGetRelid(rel), attnum, false)));
14707 : :
14708 : : /* Disallow if it's in the replica identity */
14709 : 191 : irattrs = RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_IDENTITY_KEY);
14710 [ + + ]: 191 : if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber, irattrs))
14711 [ + - ]: 8 : ereport(ERROR,
14712 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
14713 : : errmsg("column \"%s\" is in index used as replica identity",
14714 : : get_attname(RelationGetRelid(rel), attnum, false)));
14715 : :
14716 : : /* Disallow if it's a GENERATED AS IDENTITY column */
14717 : 183 : atttup = SearchSysCacheCopyAttNum(RelationGetRelid(rel), attnum);
14718 [ - + ]: 183 : if (!HeapTupleIsValid(atttup))
14719 [ # # ]: 0 : elog(ERROR, "cache lookup failed for attribute %d of relation %u",
14720 : : attnum, RelationGetRelid(rel));
14721 : 183 : attForm = (Form_pg_attribute) GETSTRUCT(atttup);
14722 [ - + ]: 183 : if (attForm->attidentity != '\0')
14723 [ # # ]: 0 : ereport(ERROR,
14724 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
14725 : : errmsg("column \"%s\" of relation \"%s\" is an identity column",
14726 : : get_attname(RelationGetRelid(rel), attnum,
14727 : : false),
14728 : : RelationGetRelationName(rel)));
14729 : :
14730 : : /* All good -- reset attnotnull if needed */
14731 [ + - ]: 183 : if (attForm->attnotnull)
14732 : : {
14733 : 183 : attForm->attnotnull = false;
14734 : 183 : CatalogTupleUpdate(attrel, &atttup->t_self, atttup);
14735 : : }
14736 : :
14737 : 183 : table_close(attrel, RowExclusiveLock);
14738 : : }
14739 : :
14740 : 726 : is_no_inherit_constraint = con->connoinherit;
14741 : :
14742 : : /*
14743 : : * If it's a foreign-key constraint, we'd better lock the referenced table
14744 : : * and check that that's not in use, just as we've already done for the
14745 : : * constrained table (else we might, eg, be dropping a trigger that has
14746 : : * unfired events). But we can/must skip that in the self-referential
14747 : : * case.
14748 : : */
14749 [ + + ]: 726 : if (con->contype == CONSTRAINT_FOREIGN &&
14750 [ + - ]: 112 : con->confrelid != RelationGetRelid(rel))
14751 : : {
14752 : : Relation frel;
14753 : :
14754 : : /* Must match lock taken by RemoveTriggerById: */
14755 : 112 : frel = table_open(con->confrelid, AccessExclusiveLock);
14756 : 112 : CheckAlterTableIsSafe(frel);
14757 : 108 : table_close(frel, NoLock);
14758 : : }
14759 : :
14760 : : /*
14761 : : * Perform the actual constraint deletion
14762 : : */
14763 : 722 : ObjectAddressSet(conobj, ConstraintRelationId, con->oid);
14764 : 722 : performDeletion(&conobj, behavior, 0);
14765 : :
14766 : : /*
14767 : : * For partitioned tables, non-CHECK, non-NOT-NULL inherited constraints
14768 : : * are dropped via the dependency mechanism, so we're done here.
14769 : : */
14770 [ + + ]: 698 : if (con->contype != CONSTRAINT_CHECK &&
14771 [ + + ]: 421 : con->contype != CONSTRAINT_NOTNULL &&
14772 [ + + ]: 238 : rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
14773 : : {
14774 : 52 : table_close(conrel, RowExclusiveLock);
14775 : 52 : return conobj;
14776 : : }
14777 : :
14778 : : /*
14779 : : * Propagate to children as appropriate. Unlike most other ALTER
14780 : : * routines, we have to do this one level of recursion at a time; we can't
14781 : : * use find_all_inheritors to do it in one pass.
14782 : : */
14783 [ + + ]: 646 : if (!is_no_inherit_constraint)
14784 : 452 : children = find_inheritance_children(RelationGetRelid(rel), lockmode);
14785 : : else
14786 : 194 : children = NIL;
14787 : :
14788 [ + + + + : 1567 : foreach_oid(childrelid, children)
+ + ]
14789 : : {
14790 : : Relation childrel;
14791 : : HeapTuple tuple;
14792 : : Form_pg_constraint childcon;
14793 : :
14794 : : /* find_inheritance_children already got lock */
14795 : 283 : childrel = table_open(childrelid, NoLock);
14796 : 283 : CheckAlterTableIsSafe(childrel);
14797 : :
14798 : : /*
14799 : : * We search for not-null constraints by column name, and others by
14800 : : * constraint name.
14801 : : */
14802 [ + + ]: 283 : if (con->contype == CONSTRAINT_NOTNULL)
14803 : : {
14804 : 98 : tuple = findNotNullConstraint(childrelid, colname);
14805 [ - + ]: 98 : if (!HeapTupleIsValid(tuple))
14806 [ # # ]: 0 : elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation %u",
14807 : : colname, RelationGetRelid(childrel));
14808 : : }
14809 : : else
14810 : : {
14811 : : SysScanDesc scan;
14812 : : ScanKeyData skey[3];
14813 : :
14814 : 185 : ScanKeyInit(&skey[0],
14815 : : Anum_pg_constraint_conrelid,
14816 : : BTEqualStrategyNumber, F_OIDEQ,
14817 : : ObjectIdGetDatum(childrelid));
14818 : 185 : ScanKeyInit(&skey[1],
14819 : : Anum_pg_constraint_contypid,
14820 : : BTEqualStrategyNumber, F_OIDEQ,
14821 : : ObjectIdGetDatum(InvalidOid));
14822 : 185 : ScanKeyInit(&skey[2],
14823 : : Anum_pg_constraint_conname,
14824 : : BTEqualStrategyNumber, F_NAMEEQ,
14825 : : CStringGetDatum(constrName));
14826 : 185 : scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId,
14827 : : true, NULL, 3, skey);
14828 : : /* There can only be one, so no need to loop */
14829 : 185 : tuple = systable_getnext(scan);
14830 [ - + ]: 185 : if (!HeapTupleIsValid(tuple))
14831 [ # # ]: 0 : ereport(ERROR,
14832 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
14833 : : errmsg("constraint \"%s\" of relation \"%s\" does not exist",
14834 : : constrName,
14835 : : RelationGetRelationName(childrel))));
14836 : 185 : tuple = heap_copytuple(tuple);
14837 : 185 : systable_endscan(scan);
14838 : : }
14839 : :
14840 : 283 : childcon = (Form_pg_constraint) GETSTRUCT(tuple);
14841 : :
14842 : : /* Right now only CHECK and not-null constraints can be inherited */
14843 [ + + ]: 283 : if (childcon->contype != CONSTRAINT_CHECK &&
14844 [ - + ]: 98 : childcon->contype != CONSTRAINT_NOTNULL)
14845 [ # # ]: 0 : elog(ERROR, "inherited constraint is not a CHECK or not-null constraint");
14846 : :
14847 [ - + ]: 283 : if (childcon->coninhcount <= 0) /* shouldn't happen */
14848 [ # # ]: 0 : elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
14849 : : childrelid, NameStr(childcon->conname));
14850 : :
14851 [ + + ]: 283 : if (recurse)
14852 : : {
14853 : : /*
14854 : : * If the child constraint has other definition sources, just
14855 : : * decrement its inheritance count; if not, recurse to delete it.
14856 : : */
14857 [ + + + + ]: 215 : if (childcon->coninhcount == 1 && !childcon->conislocal)
14858 : : {
14859 : : /* Time to delete this child constraint, too */
14860 : 159 : dropconstraint_internal(childrel, tuple, behavior,
14861 : : recurse, true, missing_ok,
14862 : : lockmode);
14863 : : }
14864 : : else
14865 : : {
14866 : : /* Child constraint must survive my deletion */
14867 : 56 : childcon->coninhcount--;
14868 : 56 : CatalogTupleUpdate(conrel, &tuple->t_self, tuple);
14869 : :
14870 : : /* Make update visible */
14871 : 56 : CommandCounterIncrement();
14872 : : }
14873 : : }
14874 : : else
14875 : : {
14876 : : /*
14877 : : * If we were told to drop ONLY in this table (no recursion) and
14878 : : * there are no further parents for this constraint, we need to
14879 : : * mark the inheritors' constraints as locally defined rather than
14880 : : * inherited.
14881 : : */
14882 : 68 : childcon->coninhcount--;
14883 [ + - ]: 68 : if (childcon->coninhcount == 0)
14884 : 68 : childcon->conislocal = true;
14885 : :
14886 : 68 : CatalogTupleUpdate(conrel, &tuple->t_self, tuple);
14887 : :
14888 : : /* Make update visible */
14889 : 68 : CommandCounterIncrement();
14890 : : }
14891 : :
14892 : 279 : heap_freetuple(tuple);
14893 : :
14894 : 279 : table_close(childrel, NoLock);
14895 : : }
14896 : :
14897 : 642 : table_close(conrel, RowExclusiveLock);
14898 : :
14899 : 642 : return conobj;
14900 : : }
14901 : :
14902 : : /*
14903 : : * ALTER COLUMN TYPE
14904 : : *
14905 : : * Unlike other subcommand types, we do parse transformation for ALTER COLUMN
14906 : : * TYPE during phase 1 --- the AlterTableCmd passed in here is already
14907 : : * transformed (and must be, because we rely on some transformed fields).
14908 : : *
14909 : : * The point of this is that the execution of all ALTER COLUMN TYPEs for a
14910 : : * table will be done "in parallel" during phase 3, so all the USING
14911 : : * expressions should be parsed assuming the original column types. Also,
14912 : : * this allows a USING expression to refer to a field that will be dropped.
14913 : : *
14914 : : * To make this work safely, AT_PASS_DROP then AT_PASS_ALTER_TYPE must be
14915 : : * the first two execution steps in phase 2; they must not see the effects
14916 : : * of any other subcommand types, since the USING expressions are parsed
14917 : : * against the unmodified table's state.
14918 : : */
14919 : : static void
14920 : 959 : ATPrepAlterColumnType(List **wqueue,
14921 : : AlteredTableInfo *tab, Relation rel,
14922 : : bool recurse, bool recursing,
14923 : : AlterTableCmd *cmd, LOCKMODE lockmode,
14924 : : AlterTableUtilityContext *context)
14925 : : {
14926 : 959 : char *colName = cmd->name;
14927 : 959 : ColumnDef *def = (ColumnDef *) cmd->def;
14928 : 959 : TypeName *typeName = def->typeName;
14929 : 959 : Node *transform = def->cooked_default;
14930 : : HeapTuple tuple;
14931 : : Form_pg_attribute attTup;
14932 : : AttrNumber attnum;
14933 : : Oid targettype;
14934 : : int32 targettypmod;
14935 : : Oid targetcollid;
14936 : : NewColumnValue *newval;
14937 : 959 : ParseState *pstate = make_parsestate(NULL);
14938 : : AclResult aclresult;
14939 : : bool is_expr;
14940 : :
14941 : 959 : pstate->p_sourcetext = context->queryString;
14942 : :
14943 [ + + + + ]: 959 : if (rel->rd_rel->reloftype && !recursing)
14944 [ + - ]: 4 : ereport(ERROR,
14945 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
14946 : : errmsg("cannot alter column type of typed table"),
14947 : : parser_errposition(pstate, def->location)));
14948 : :
14949 : : /* lookup the attribute so we can check inheritance status */
14950 : 955 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
14951 [ - + ]: 955 : if (!HeapTupleIsValid(tuple))
14952 [ # # ]: 0 : ereport(ERROR,
14953 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
14954 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
14955 : : colName, RelationGetRelationName(rel)),
14956 : : parser_errposition(pstate, def->location)));
14957 : 955 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
14958 : 955 : attnum = attTup->attnum;
14959 : :
14960 : : /* Can't alter a system attribute */
14961 [ + + ]: 955 : if (attnum <= 0)
14962 [ + - ]: 4 : ereport(ERROR,
14963 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
14964 : : errmsg("cannot alter system column \"%s\"", colName),
14965 : : parser_errposition(pstate, def->location)));
14966 : :
14967 : : /*
14968 : : * Cannot specify USING when altering type of a generated column, because
14969 : : * that would violate the generation expression.
14970 : : */
14971 [ + + + + ]: 951 : if (attTup->attgenerated && def->cooked_default)
14972 [ + - ]: 8 : ereport(ERROR,
14973 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
14974 : : errmsg("cannot specify USING when altering type of generated column"),
14975 : : errdetail("Column \"%s\" is a generated column.", colName),
14976 : : parser_errposition(pstate, def->location)));
14977 : :
14978 : : /*
14979 : : * Don't alter inherited columns. At outer level, there had better not be
14980 : : * any inherited definition; when recursing, we assume this was checked at
14981 : : * the parent level (see below).
14982 : : */
14983 [ + + + + ]: 943 : if (attTup->attinhcount > 0 && !recursing)
14984 [ + - ]: 4 : ereport(ERROR,
14985 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
14986 : : errmsg("cannot alter inherited column \"%s\"", colName),
14987 : : parser_errposition(pstate, def->location)));
14988 : :
14989 : : /* Don't alter columns used in the partition key */
14990 [ + + ]: 939 : if (has_partition_attrs(rel,
14991 : : bms_make_singleton(attnum - FirstLowInvalidHeapAttributeNumber),
14992 : : &is_expr))
14993 [ + - ]: 12 : ereport(ERROR,
14994 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
14995 : : errmsg("cannot alter column \"%s\" because it is part of the partition key of relation \"%s\"",
14996 : : colName, RelationGetRelationName(rel)),
14997 : : parser_errposition(pstate, def->location)));
14998 : :
14999 : : /* Look up the target type */
15000 : 927 : typenameTypeIdAndMod(pstate, typeName, &targettype, &targettypmod);
15001 : :
15002 : 923 : aclresult = object_aclcheck(TypeRelationId, targettype, GetUserId(), ACL_USAGE);
15003 [ + + ]: 923 : if (aclresult != ACLCHECK_OK)
15004 : 8 : aclcheck_error_type(aclresult, targettype);
15005 : :
15006 : : /* And the collation */
15007 : 915 : targetcollid = GetColumnDefCollation(pstate, def, targettype);
15008 : :
15009 : : /* make sure datatype is legal for a column */
15010 : 1822 : CheckAttributeType(colName, targettype, targetcollid,
15011 : 911 : list_make1_oid(rel->rd_rel->reltype),
15012 [ + + ]: 911 : (attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL ? CHKATYPE_IS_VIRTUAL : 0));
15013 : :
15014 [ + + ]: 903 : if (attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
15015 : : {
15016 : : /* do nothing */
15017 : : }
15018 [ + + ]: 879 : else if (tab->relkind == RELKIND_RELATION ||
15019 [ + + ]: 137 : tab->relkind == RELKIND_PARTITIONED_TABLE)
15020 : : {
15021 : : /*
15022 : : * Set up an expression to transform the old data value to the new
15023 : : * type. If a USING option was given, use the expression as
15024 : : * transformed by transformAlterTableStmt, else just take the old
15025 : : * value and try to coerce it. We do this first so that type
15026 : : * incompatibility can be detected before we waste effort, and because
15027 : : * we need the expression to be parsed against the original table row
15028 : : * type.
15029 : : */
15030 [ + + ]: 786 : if (!transform)
15031 : : {
15032 : 635 : transform = (Node *) makeVar(1, attnum,
15033 : : attTup->atttypid, attTup->atttypmod,
15034 : : attTup->attcollation,
15035 : : 0);
15036 : : }
15037 : :
15038 : 786 : transform = coerce_to_target_type(pstate,
15039 : : transform, exprType(transform),
15040 : : targettype, targettypmod,
15041 : : COERCION_ASSIGNMENT,
15042 : : COERCE_IMPLICIT_CAST,
15043 : : -1);
15044 [ + + ]: 786 : if (transform == NULL)
15045 : : {
15046 : : /* error text depends on whether USING was specified or not */
15047 [ + + ]: 15 : if (def->cooked_default != NULL)
15048 [ + - ]: 4 : ereport(ERROR,
15049 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
15050 : : errmsg("result of USING clause for column \"%s\""
15051 : : " cannot be cast automatically to type %s",
15052 : : colName, format_type_be(targettype)),
15053 : : errhint("You might need to add an explicit cast.")));
15054 : : else
15055 [ + - + - ]: 11 : ereport(ERROR,
15056 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
15057 : : errmsg("column \"%s\" cannot be cast automatically to type %s",
15058 : : colName, format_type_be(targettype)),
15059 : : !attTup->attgenerated ?
15060 : : /* translator: USING is SQL, don't translate it */
15061 : : errhint("You might need to specify \"USING %s::%s\".",
15062 : : quote_identifier(colName),
15063 : : format_type_with_typemod(targettype,
15064 : : targettypmod)) : 0));
15065 : : }
15066 : :
15067 : : /* Fix collations after all else */
15068 : 771 : assign_expr_collations(pstate, transform);
15069 : :
15070 : : /* Expand virtual generated columns in the expr. */
15071 : 771 : transform = expand_generated_columns_in_expr(transform, rel, 1);
15072 : :
15073 : : /* Plan the expr now so we can accurately assess the need to rewrite. */
15074 : 771 : transform = (Node *) expression_planner((Expr *) transform);
15075 : :
15076 : : /*
15077 : : * Add a work queue item to make ATRewriteTable update the column
15078 : : * contents.
15079 : : */
15080 : 771 : newval = palloc0_object(NewColumnValue);
15081 : 771 : newval->attnum = attnum;
15082 : 771 : newval->expr = (Expr *) transform;
15083 : 771 : newval->is_generated = false;
15084 : :
15085 : 771 : tab->newvals = lappend(tab->newvals, newval);
15086 [ + + ]: 1410 : if (ATColumnChangeRequiresRewrite(transform, attnum))
15087 : 639 : tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
15088 : : }
15089 [ + + ]: 93 : else if (transform)
15090 [ + - ]: 8 : ereport(ERROR,
15091 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
15092 : : errmsg("\"%s\" is not a table",
15093 : : RelationGetRelationName(rel))));
15094 : :
15095 [ + + + - : 880 : if (!RELKIND_HAS_STORAGE(tab->relkind) || attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+ - + - -
+ + + ]
15096 : : {
15097 : : /*
15098 : : * For relations or columns without storage, do this check now.
15099 : : * Regular tables will check it later when the table is being
15100 : : * rewritten.
15101 : : */
15102 : 153 : find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
15103 : : }
15104 : :
15105 : 848 : ReleaseSysCache(tuple);
15106 : :
15107 : : /*
15108 : : * Recurse manually by queueing a new command for each child, if
15109 : : * necessary. We cannot apply ATSimpleRecursion here because we need to
15110 : : * remap attribute numbers in the USING expression, if any.
15111 : : *
15112 : : * If we are told not to recurse, there had better not be any child
15113 : : * tables; else the alter would put them out of step.
15114 : : */
15115 [ + + ]: 848 : if (recurse)
15116 : : {
15117 : 673 : Oid relid = RelationGetRelid(rel);
15118 : : List *child_oids,
15119 : : *child_numparents;
15120 : : ListCell *lo,
15121 : : *li;
15122 : :
15123 : 673 : child_oids = find_all_inheritors(relid, lockmode,
15124 : : &child_numparents);
15125 : :
15126 : : /*
15127 : : * find_all_inheritors does the recursive search of the inheritance
15128 : : * hierarchy, so all we have to do is process all of the relids in the
15129 : : * list that it returns.
15130 : : */
15131 [ + - + + : 1484 : forboth(lo, child_oids, li, child_numparents)
+ - + + +
+ + - +
+ ]
15132 : : {
15133 : 827 : Oid childrelid = lfirst_oid(lo);
15134 : 827 : int numparents = lfirst_int(li);
15135 : : Relation childrel;
15136 : : HeapTuple childtuple;
15137 : : Form_pg_attribute childattTup;
15138 : :
15139 [ + + ]: 827 : if (childrelid == relid)
15140 : 673 : continue;
15141 : :
15142 : : /* find_all_inheritors already got lock */
15143 : 154 : childrel = relation_open(childrelid, NoLock);
15144 : 154 : CheckAlterTableIsSafe(childrel);
15145 : :
15146 : : /*
15147 : : * Verify that the child doesn't have any inherited definitions of
15148 : : * this column that came from outside this inheritance hierarchy.
15149 : : * (renameatt makes a similar test, though in a different way
15150 : : * because of its different recursion mechanism.)
15151 : : */
15152 : 154 : childtuple = SearchSysCacheAttName(RelationGetRelid(childrel),
15153 : : colName);
15154 [ - + ]: 154 : if (!HeapTupleIsValid(childtuple))
15155 [ # # ]: 0 : ereport(ERROR,
15156 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
15157 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
15158 : : colName, RelationGetRelationName(childrel))));
15159 : 154 : childattTup = (Form_pg_attribute) GETSTRUCT(childtuple);
15160 : :
15161 [ + + ]: 154 : if (childattTup->attinhcount > numparents)
15162 [ + - ]: 4 : ereport(ERROR,
15163 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
15164 : : errmsg("cannot alter inherited column \"%s\" of relation \"%s\"",
15165 : : colName, RelationGetRelationName(childrel))));
15166 : :
15167 : 150 : ReleaseSysCache(childtuple);
15168 : :
15169 : : /*
15170 : : * Remap the attribute numbers. If no USING expression was
15171 : : * specified, there is no need for this step.
15172 : : */
15173 [ + + ]: 150 : if (def->cooked_default)
15174 : : {
15175 : : AttrMap *attmap;
15176 : : bool found_whole_row;
15177 : :
15178 : : /* create a copy to scribble on */
15179 : 52 : cmd = copyObject(cmd);
15180 : :
15181 : 52 : attmap = build_attrmap_by_name(RelationGetDescr(childrel),
15182 : : RelationGetDescr(rel),
15183 : : false);
15184 : 104 : ((ColumnDef *) cmd->def)->cooked_default =
15185 : 52 : map_variable_attnos(def->cooked_default,
15186 : : 1, 0,
15187 : : attmap,
15188 : : InvalidOid, &found_whole_row);
15189 [ + + ]: 52 : if (found_whole_row)
15190 [ + - ]: 4 : ereport(ERROR,
15191 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15192 : : errmsg("cannot convert whole-row table reference"),
15193 : : errdetail("USING expression contains a whole-row table reference.")));
15194 : 48 : pfree(attmap);
15195 : : }
15196 : 146 : ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, context);
15197 : 138 : relation_close(childrel, NoLock);
15198 : : }
15199 : : }
15200 [ + + - + ]: 212 : else if (!recursing &&
15201 : 37 : find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
15202 [ # # ]: 0 : ereport(ERROR,
15203 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
15204 : : errmsg("type of inherited column \"%s\" must be changed in child tables too",
15205 : : colName)));
15206 : :
15207 [ + + ]: 832 : if (tab->relkind == RELKIND_COMPOSITE_TYPE)
15208 : 37 : ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);
15209 : 828 : }
15210 : :
15211 : : /*
15212 : : * When the data type of a column is changed, a rewrite might not be required
15213 : : * if the new type is sufficiently identical to the old one, and the USING
15214 : : * clause isn't trying to insert some other value. It's safe to skip the
15215 : : * rewrite in these cases:
15216 : : *
15217 : : * - the old type is binary coercible to the new type
15218 : : * - the new type is an unconstrained domain over the old type
15219 : : * - {NEW,OLD} or {OLD,NEW} is {timestamptz,timestamp} and the timezone is UTC
15220 : : *
15221 : : * In the case of a constrained domain, we could get by with scanning the
15222 : : * table and checking the constraint rather than actually rewriting it, but we
15223 : : * don't currently try to do that.
15224 : : */
15225 : : static bool
15226 : 771 : ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno)
15227 : : {
15228 : : Assert(expr != NULL);
15229 : :
15230 : : for (;;)
15231 : : {
15232 : : /* only one varno, so no need to check that */
15233 [ + + + - ]: 847 : if (IsA(expr, Var) && ((Var *) expr)->varattno == varattno)
15234 : 132 : return false;
15235 [ + + ]: 715 : else if (IsA(expr, RelabelType))
15236 : 68 : expr = (Node *) ((RelabelType *) expr)->arg;
15237 [ + + ]: 647 : else if (IsA(expr, CoerceToDomain))
15238 : : {
15239 : 4 : CoerceToDomain *d = (CoerceToDomain *) expr;
15240 : :
15241 [ + - ]: 4 : if (DomainHasConstraints(d->resulttype, NULL))
15242 : 4 : return true;
15243 : 0 : expr = (Node *) d->arg;
15244 : : }
15245 [ + + ]: 643 : else if (IsA(expr, FuncExpr))
15246 : : {
15247 : 510 : FuncExpr *f = (FuncExpr *) expr;
15248 : :
15249 [ + + ]: 510 : switch (f->funcid)
15250 : : {
15251 : 12 : case F_TIMESTAMPTZ_TIMESTAMP:
15252 : : case F_TIMESTAMP_TIMESTAMPTZ:
15253 [ + + ]: 12 : if (TimestampTimestampTzRequiresRewrite())
15254 : 4 : return true;
15255 : : else
15256 : 8 : expr = linitial(f->args);
15257 : 8 : break;
15258 : 498 : default:
15259 : 498 : return true;
15260 : : }
15261 : : }
15262 : : else
15263 : 133 : return true;
15264 : : }
15265 : : }
15266 : :
15267 : : /*
15268 : : * ALTER COLUMN .. SET DATA TYPE
15269 : : *
15270 : : * Return the address of the modified column.
15271 : : */
15272 : : static ObjectAddress
15273 : 804 : ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
15274 : : AlterTableCmd *cmd, LOCKMODE lockmode)
15275 : : {
15276 : 804 : char *colName = cmd->name;
15277 : 804 : ColumnDef *def = (ColumnDef *) cmd->def;
15278 : 804 : TypeName *typeName = def->typeName;
15279 : : HeapTuple heapTup;
15280 : : Form_pg_attribute attTup,
15281 : : attOldTup;
15282 : : AttrNumber attnum;
15283 : : HeapTuple typeTuple;
15284 : : Form_pg_type tform;
15285 : : Oid targettype;
15286 : : int32 targettypmod;
15287 : : Oid targetcollid;
15288 : : Node *defaultexpr;
15289 : : Relation attrelation;
15290 : : Relation depRel;
15291 : : ScanKeyData key[3];
15292 : : SysScanDesc scan;
15293 : : HeapTuple depTup;
15294 : : ObjectAddress address;
15295 : :
15296 : : /*
15297 : : * Clear all the missing values if we're rewriting the table, since this
15298 : : * renders them pointless.
15299 : : */
15300 [ + + ]: 804 : if (tab->rewrite)
15301 : : {
15302 : : Relation newrel;
15303 : :
15304 : 599 : newrel = table_open(RelationGetRelid(rel), NoLock);
15305 : 599 : RelationClearMissing(newrel);
15306 : 599 : relation_close(newrel, NoLock);
15307 : : /* make sure we don't conflict with later attribute modifications */
15308 : 599 : CommandCounterIncrement();
15309 : : }
15310 : :
15311 : 804 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
15312 : :
15313 : : /* Look up the target column */
15314 : 804 : heapTup = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
15315 [ - + ]: 804 : if (!HeapTupleIsValid(heapTup)) /* shouldn't happen */
15316 [ # # ]: 0 : ereport(ERROR,
15317 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
15318 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
15319 : : colName, RelationGetRelationName(rel))));
15320 : 804 : attTup = (Form_pg_attribute) GETSTRUCT(heapTup);
15321 : 804 : attnum = attTup->attnum;
15322 : 804 : attOldTup = TupleDescAttr(tab->oldDesc, attnum - 1);
15323 : :
15324 : : /* Check for multiple ALTER TYPE on same column --- can't cope */
15325 [ + - ]: 804 : if (attTup->atttypid != attOldTup->atttypid ||
15326 [ - + ]: 804 : attTup->atttypmod != attOldTup->atttypmod)
15327 [ # # ]: 0 : ereport(ERROR,
15328 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15329 : : errmsg("cannot alter type of column \"%s\" twice",
15330 : : colName)));
15331 : :
15332 : : /* Look up the target type (should not fail, since prep found it) */
15333 : 804 : typeTuple = typenameType(NULL, typeName, &targettypmod);
15334 : 804 : tform = (Form_pg_type) GETSTRUCT(typeTuple);
15335 : 804 : targettype = tform->oid;
15336 : : /* And the collation */
15337 : 804 : targetcollid = GetColumnDefCollation(NULL, def, targettype);
15338 : :
15339 : : /*
15340 : : * If there is a default expression for the column, get it and ensure we
15341 : : * can coerce it to the new datatype. (We must do this before changing
15342 : : * the column type, because build_column_default itself will try to
15343 : : * coerce, and will not issue the error message we want if it fails.)
15344 : : *
15345 : : * We remove any implicit coercion steps at the top level of the old
15346 : : * default expression; this has been agreed to satisfy the principle of
15347 : : * least surprise. (The conversion to the new column type should act like
15348 : : * it started from what the user sees as the stored expression, and the
15349 : : * implicit coercions aren't going to be shown.)
15350 : : */
15351 [ + + ]: 804 : if (attTup->atthasdef)
15352 : : {
15353 : 68 : defaultexpr = build_column_default(rel, attnum);
15354 : : Assert(defaultexpr);
15355 : 68 : defaultexpr = strip_implicit_coercions(defaultexpr);
15356 : 68 : defaultexpr = coerce_to_target_type(NULL, /* no UNKNOWN params */
15357 : : defaultexpr, exprType(defaultexpr),
15358 : : targettype, targettypmod,
15359 : : COERCION_ASSIGNMENT,
15360 : : COERCE_IMPLICIT_CAST,
15361 : : -1);
15362 [ + + ]: 68 : if (defaultexpr == NULL)
15363 : : {
15364 [ - + ]: 4 : if (attTup->attgenerated)
15365 [ # # ]: 0 : ereport(ERROR,
15366 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
15367 : : errmsg("generation expression for column \"%s\" cannot be cast automatically to type %s",
15368 : : colName, format_type_be(targettype))));
15369 : : else
15370 [ + - ]: 4 : ereport(ERROR,
15371 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
15372 : : errmsg("default for column \"%s\" cannot be cast automatically to type %s",
15373 : : colName, format_type_be(targettype))));
15374 : : }
15375 : : }
15376 : : else
15377 : 736 : defaultexpr = NULL;
15378 : :
15379 : : /*
15380 : : * Find everything that depends on the column (constraints, indexes, etc),
15381 : : * and record enough information to let us recreate the objects.
15382 : : *
15383 : : * The actual recreation does not happen here, but only after we have
15384 : : * performed all the individual ALTER TYPE operations. We have to save
15385 : : * the info before executing ALTER TYPE, though, else the deparser will
15386 : : * get confused.
15387 : : */
15388 : 800 : RememberAllDependentForRebuilding(tab, AT_AlterColumnType, rel, attnum, colName);
15389 : :
15390 : : /*
15391 : : * Now scan for dependencies of this column on other things. The only
15392 : : * things we should find are the dependency on the column datatype and
15393 : : * possibly a collation dependency. Those can be removed.
15394 : : */
15395 : 776 : depRel = table_open(DependRelationId, RowExclusiveLock);
15396 : :
15397 : 776 : ScanKeyInit(&key[0],
15398 : : Anum_pg_depend_classid,
15399 : : BTEqualStrategyNumber, F_OIDEQ,
15400 : : ObjectIdGetDatum(RelationRelationId));
15401 : 776 : ScanKeyInit(&key[1],
15402 : : Anum_pg_depend_objid,
15403 : : BTEqualStrategyNumber, F_OIDEQ,
15404 : : ObjectIdGetDatum(RelationGetRelid(rel)));
15405 : 776 : ScanKeyInit(&key[2],
15406 : : Anum_pg_depend_objsubid,
15407 : : BTEqualStrategyNumber, F_INT4EQ,
15408 : : Int32GetDatum((int32) attnum));
15409 : :
15410 : 776 : scan = systable_beginscan(depRel, DependDependerIndexId, true,
15411 : : NULL, 3, key);
15412 : :
15413 [ + + ]: 778 : while (HeapTupleIsValid(depTup = systable_getnext(scan)))
15414 : : {
15415 : 2 : Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup);
15416 : : ObjectAddress foundObject;
15417 : :
15418 : 2 : foundObject.classId = foundDep->refclassid;
15419 : 2 : foundObject.objectId = foundDep->refobjid;
15420 : 2 : foundObject.objectSubId = foundDep->refobjsubid;
15421 : :
15422 [ - + ]: 2 : if (foundDep->deptype != DEPENDENCY_NORMAL)
15423 [ # # ]: 0 : elog(ERROR, "found unexpected dependency type '%c'",
15424 : : foundDep->deptype);
15425 [ + - ]: 2 : if (!(foundDep->refclassid == TypeRelationId &&
15426 [ - + ]: 2 : foundDep->refobjid == attTup->atttypid) &&
15427 [ # # ]: 0 : !(foundDep->refclassid == CollationRelationId &&
15428 [ # # ]: 0 : foundDep->refobjid == attTup->attcollation))
15429 [ # # ]: 0 : elog(ERROR, "found unexpected dependency for column: %s",
15430 : : getObjectDescription(&foundObject, false));
15431 : :
15432 : 2 : CatalogTupleDelete(depRel, &depTup->t_self);
15433 : : }
15434 : :
15435 : 776 : systable_endscan(scan);
15436 : :
15437 : 776 : table_close(depRel, RowExclusiveLock);
15438 : :
15439 : : /*
15440 : : * Here we go --- change the recorded column type and collation. (Note
15441 : : * heapTup is a copy of the syscache entry, so okay to scribble on.) First
15442 : : * fix up the missing value if any.
15443 : : */
15444 [ + + ]: 776 : if (attTup->atthasmissing)
15445 : : {
15446 : : Datum missingval;
15447 : : bool missingNull;
15448 : :
15449 : : /* if rewrite is true the missing value should already be cleared */
15450 : : Assert(tab->rewrite == 0);
15451 : :
15452 : : /* Get the missing value datum */
15453 : 4 : missingval = heap_getattr(heapTup,
15454 : : Anum_pg_attribute_attmissingval,
15455 : : attrelation->rd_att,
15456 : : &missingNull);
15457 : :
15458 : : /* if it's a null array there is nothing to do */
15459 : :
15460 [ + - ]: 4 : if (!missingNull)
15461 : : {
15462 : : /*
15463 : : * Get the datum out of the array and repack it in a new array
15464 : : * built with the new type data. We assume that since the table
15465 : : * doesn't need rewriting, the actual Datum doesn't need to be
15466 : : * changed, only the array metadata.
15467 : : */
15468 : :
15469 : 4 : int one = 1;
15470 : : bool isNull;
15471 : 4 : Datum valuesAtt[Natts_pg_attribute] = {0};
15472 : 4 : bool nullsAtt[Natts_pg_attribute] = {0};
15473 : 4 : bool replacesAtt[Natts_pg_attribute] = {0};
15474 : : HeapTuple newTup;
15475 : :
15476 : 8 : missingval = array_get_element(missingval,
15477 : : 1,
15478 : : &one,
15479 : : 0,
15480 : 4 : attTup->attlen,
15481 : 4 : attTup->attbyval,
15482 : 4 : attTup->attalign,
15483 : : &isNull);
15484 : 4 : missingval = PointerGetDatum(construct_array(&missingval,
15485 : : 1,
15486 : : targettype,
15487 : : tform->typlen,
15488 : : tform->typbyval,
15489 : : tform->typalign));
15490 : :
15491 : 4 : valuesAtt[Anum_pg_attribute_attmissingval - 1] = missingval;
15492 : 4 : replacesAtt[Anum_pg_attribute_attmissingval - 1] = true;
15493 : 4 : nullsAtt[Anum_pg_attribute_attmissingval - 1] = false;
15494 : :
15495 : 4 : newTup = heap_modify_tuple(heapTup, RelationGetDescr(attrelation),
15496 : : valuesAtt, nullsAtt, replacesAtt);
15497 : 4 : heap_freetuple(heapTup);
15498 : 4 : heapTup = newTup;
15499 : 4 : attTup = (Form_pg_attribute) GETSTRUCT(heapTup);
15500 : : }
15501 : : }
15502 : :
15503 : 776 : attTup->atttypid = targettype;
15504 : 776 : attTup->atttypmod = targettypmod;
15505 : 776 : attTup->attcollation = targetcollid;
15506 [ - + ]: 776 : if (list_length(typeName->arrayBounds) > PG_INT16_MAX)
15507 [ # # ]: 0 : ereport(ERROR,
15508 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
15509 : : errmsg("too many array dimensions"));
15510 : 776 : attTup->attndims = list_length(typeName->arrayBounds);
15511 : 776 : attTup->attlen = tform->typlen;
15512 : 776 : attTup->attbyval = tform->typbyval;
15513 : 776 : attTup->attalign = tform->typalign;
15514 : 776 : attTup->attstorage = tform->typstorage;
15515 : 776 : attTup->attcompression = InvalidCompressionMethod;
15516 : :
15517 : 776 : ReleaseSysCache(typeTuple);
15518 : :
15519 : 776 : CatalogTupleUpdate(attrelation, &heapTup->t_self, heapTup);
15520 : :
15521 : 776 : table_close(attrelation, RowExclusiveLock);
15522 : :
15523 : : /* Install dependencies on new datatype and collation */
15524 : 776 : add_column_datatype_dependency(RelationGetRelid(rel), attnum, targettype);
15525 : 776 : add_column_collation_dependency(RelationGetRelid(rel), attnum, targetcollid);
15526 : :
15527 : : /*
15528 : : * Drop any pg_statistic entry for the column, since it's now wrong type
15529 : : */
15530 : 776 : RemoveStatistics(RelationGetRelid(rel), attnum);
15531 : :
15532 [ - + ]: 776 : InvokeObjectPostAlterHook(RelationRelationId,
15533 : : RelationGetRelid(rel), attnum);
15534 : :
15535 : : /*
15536 : : * Update the default, if present, by brute force --- remove and re-add
15537 : : * the default. Probably unsafe to take shortcuts, since the new version
15538 : : * may well have additional dependencies. (It's okay to do this now,
15539 : : * rather than after other ALTER TYPE commands, since the default won't
15540 : : * depend on other column types.)
15541 : : */
15542 [ + + ]: 776 : if (defaultexpr)
15543 : : {
15544 : : /*
15545 : : * If it's a GENERATED default, drop its dependency records, in
15546 : : * particular its INTERNAL dependency on the column, which would
15547 : : * otherwise cause dependency.c to refuse to perform the deletion.
15548 : : */
15549 [ + + ]: 64 : if (attTup->attgenerated)
15550 : : {
15551 : 28 : Oid attrdefoid = GetAttrDefaultOid(RelationGetRelid(rel), attnum);
15552 : :
15553 [ - + ]: 28 : if (!OidIsValid(attrdefoid))
15554 [ # # ]: 0 : elog(ERROR, "could not find attrdef tuple for relation %u attnum %d",
15555 : : RelationGetRelid(rel), attnum);
15556 : 28 : (void) deleteDependencyRecordsFor(AttrDefaultRelationId, attrdefoid, false);
15557 : : }
15558 : :
15559 : : /*
15560 : : * Make updates-so-far visible, particularly the new pg_attribute row
15561 : : * which will be updated again.
15562 : : */
15563 : 64 : CommandCounterIncrement();
15564 : :
15565 : : /*
15566 : : * We use RESTRICT here for safety, but at present we do not expect
15567 : : * anything to depend on the default.
15568 : : */
15569 : 64 : RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, true,
15570 : : true);
15571 : :
15572 : 64 : (void) StoreAttrDefault(rel, attnum, defaultexpr, true);
15573 : : }
15574 : :
15575 : 776 : ObjectAddressSubSet(address, RelationRelationId,
15576 : : RelationGetRelid(rel), attnum);
15577 : :
15578 : : /* Cleanup */
15579 : 776 : heap_freetuple(heapTup);
15580 : :
15581 : 776 : return address;
15582 : : }
15583 : :
15584 : : /*
15585 : : * Subroutine for ATExecAlterColumnType and ATExecSetExpression: Find everything
15586 : : * that depends on the column (constraints, indexes, etc), and record enough
15587 : : * information to let us recreate the objects.
15588 : : */
15589 : : static void
15590 : 981 : RememberAllDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype,
15591 : : Relation rel, AttrNumber attnum, const char *colName)
15592 : : {
15593 : : Relation depRel;
15594 : : ScanKeyData key[3];
15595 : : SysScanDesc scan;
15596 : : HeapTuple depTup;
15597 : :
15598 : : Assert(subtype == AT_AlterColumnType || subtype == AT_SetExpression);
15599 : :
15600 : 981 : depRel = table_open(DependRelationId, RowExclusiveLock);
15601 : :
15602 : 981 : ScanKeyInit(&key[0],
15603 : : Anum_pg_depend_refclassid,
15604 : : BTEqualStrategyNumber, F_OIDEQ,
15605 : : ObjectIdGetDatum(RelationRelationId));
15606 : 981 : ScanKeyInit(&key[1],
15607 : : Anum_pg_depend_refobjid,
15608 : : BTEqualStrategyNumber, F_OIDEQ,
15609 : : ObjectIdGetDatum(RelationGetRelid(rel)));
15610 : 981 : ScanKeyInit(&key[2],
15611 : : Anum_pg_depend_refobjsubid,
15612 : : BTEqualStrategyNumber, F_INT4EQ,
15613 : : Int32GetDatum((int32) attnum));
15614 : :
15615 : 981 : scan = systable_beginscan(depRel, DependReferenceIndexId, true,
15616 : : NULL, 3, key);
15617 : :
15618 [ + + ]: 2003 : while (HeapTupleIsValid(depTup = systable_getnext(scan)))
15619 : : {
15620 : 1046 : Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup);
15621 : : ObjectAddress foundObject;
15622 : :
15623 : 1046 : foundObject.classId = foundDep->classid;
15624 : 1046 : foundObject.objectId = foundDep->objid;
15625 : 1046 : foundObject.objectSubId = foundDep->objsubid;
15626 : :
15627 [ + + - + : 1046 : switch (foundObject.classId)
- - + + -
- ]
15628 : : {
15629 : 192 : case RelationRelationId:
15630 : : {
15631 : 192 : char relKind = get_rel_relkind(foundObject.objectId);
15632 : :
15633 [ + + + + ]: 192 : if (relKind == RELKIND_INDEX ||
15634 : : relKind == RELKIND_PARTITIONED_INDEX)
15635 : : {
15636 : : Assert(foundObject.objectSubId == 0);
15637 : 167 : RememberIndexForRebuilding(foundObject.objectId, tab);
15638 : : }
15639 [ - + ]: 25 : else if (relKind == RELKIND_SEQUENCE)
15640 : : {
15641 : : /*
15642 : : * This must be a SERIAL column's sequence. We need
15643 : : * not do anything to it.
15644 : : */
15645 : : Assert(foundObject.objectSubId == 0);
15646 : : }
15647 : : else
15648 : : {
15649 : : /* Not expecting any other direct dependencies... */
15650 [ # # ]: 0 : elog(ERROR, "unexpected object depending on column: %s",
15651 : : getObjectDescription(&foundObject, false));
15652 : : }
15653 : 192 : break;
15654 : : }
15655 : :
15656 : 528 : case ConstraintRelationId:
15657 : : Assert(foundObject.objectSubId == 0);
15658 : 528 : RememberConstraintForRebuilding(foundObject.objectId, tab);
15659 : 528 : break;
15660 : :
15661 : 0 : case ProcedureRelationId:
15662 : :
15663 : : /*
15664 : : * A new-style SQL function can depend on a column, if that
15665 : : * column is referenced in the parsed function body. Ideally
15666 : : * we'd automatically update the function by deparsing and
15667 : : * reparsing it, but that's risky and might well fail anyhow.
15668 : : * FIXME someday.
15669 : : *
15670 : : * This is only a problem for AT_AlterColumnType, not
15671 : : * AT_SetExpression.
15672 : : */
15673 [ # # ]: 0 : if (subtype == AT_AlterColumnType)
15674 [ # # ]: 0 : ereport(ERROR,
15675 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15676 : : errmsg("cannot alter type of a column used by a function or procedure"),
15677 : : errdetail("%s depends on column \"%s\".",
15678 : : getObjectDescription(&foundObject, false),
15679 : : colName)));
15680 : 0 : break;
15681 : :
15682 : 8 : case RewriteRelationId:
15683 : :
15684 : : /*
15685 : : * View/rule bodies have pretty much the same issues as
15686 : : * function bodies. FIXME someday.
15687 : : */
15688 [ + - ]: 8 : if (subtype == AT_AlterColumnType)
15689 [ + - ]: 8 : ereport(ERROR,
15690 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15691 : : errmsg("cannot alter type of a column used by a view or rule"),
15692 : : errdetail("%s depends on column \"%s\".",
15693 : : getObjectDescription(&foundObject, false),
15694 : : colName)));
15695 : 0 : break;
15696 : :
15697 : 0 : case TriggerRelationId:
15698 : :
15699 : : /*
15700 : : * A trigger can depend on a column because the column is
15701 : : * specified as an update target, or because the column is
15702 : : * used in the trigger's WHEN condition. The first case would
15703 : : * not require any extra work, but the second case would
15704 : : * require updating the WHEN expression, which has the same
15705 : : * issues as above. Since we can't easily tell which case
15706 : : * applies, we punt for both. FIXME someday.
15707 : : */
15708 [ # # ]: 0 : if (subtype == AT_AlterColumnType)
15709 [ # # ]: 0 : ereport(ERROR,
15710 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15711 : : errmsg("cannot alter type of a column used in a trigger definition"),
15712 : : errdetail("%s depends on column \"%s\".",
15713 : : getObjectDescription(&foundObject, false),
15714 : : colName)));
15715 : 0 : break;
15716 : :
15717 : 0 : case PolicyRelationId:
15718 : :
15719 : : /*
15720 : : * A policy can depend on a column because the column is
15721 : : * specified in the policy's USING or WITH CHECK qual
15722 : : * expressions. It might be possible to rewrite and recheck
15723 : : * the policy expression, but punt for now. It's certainly
15724 : : * easy enough to remove and recreate the policy; still, FIXME
15725 : : * someday.
15726 : : */
15727 [ # # ]: 0 : if (subtype == AT_AlterColumnType)
15728 [ # # ]: 0 : ereport(ERROR,
15729 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15730 : : errmsg("cannot alter type of a column used in a policy definition"),
15731 : : errdetail("%s depends on column \"%s\".",
15732 : : getObjectDescription(&foundObject, false),
15733 : : colName)));
15734 : 0 : break;
15735 : :
15736 : 261 : case AttrDefaultRelationId:
15737 : : {
15738 : 261 : ObjectAddress col = GetAttrDefaultColumnAddress(foundObject.objectId);
15739 : :
15740 [ + - ]: 506 : if (col.objectId == RelationGetRelid(rel) &&
15741 [ + + ]: 261 : col.objectSubId == attnum)
15742 : : {
15743 : : /*
15744 : : * Ignore the column's own default expression. The
15745 : : * caller deals with it.
15746 : : */
15747 : : }
15748 : : else
15749 : : {
15750 : : /*
15751 : : * This must be a reference from the expression of a
15752 : : * generated column elsewhere in the same table.
15753 : : * Changing the type/generated expression of a column
15754 : : * that is used by a generated column is not allowed
15755 : : * by SQL standard, so just punt for now. It might be
15756 : : * doable with some thinking and effort.
15757 : : */
15758 [ + - ]: 16 : if (subtype == AT_AlterColumnType)
15759 [ + - ]: 16 : ereport(ERROR,
15760 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15761 : : errmsg("cannot alter type of a column used by a generated column"),
15762 : : errdetail("Column \"%s\" is used by generated column \"%s\".",
15763 : : colName,
15764 : : get_attname(col.objectId,
15765 : : col.objectSubId,
15766 : : false))));
15767 : : }
15768 : 245 : break;
15769 : : }
15770 : :
15771 : 57 : case StatisticExtRelationId:
15772 : :
15773 : : /*
15774 : : * Give the extended-stats machinery a chance to fix anything
15775 : : * that this column type change would break.
15776 : : */
15777 : 57 : RememberStatisticsForRebuilding(foundObject.objectId, tab);
15778 : 57 : break;
15779 : :
15780 : 0 : case PublicationRelRelationId:
15781 : :
15782 : : /*
15783 : : * Column reference in a PUBLICATION ... FOR TABLE ... WHERE
15784 : : * clause. Same issues as above. FIXME someday.
15785 : : */
15786 [ # # ]: 0 : if (subtype == AT_AlterColumnType)
15787 [ # # ]: 0 : ereport(ERROR,
15788 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15789 : : errmsg("cannot alter type of a column used by a publication WHERE clause"),
15790 : : errdetail("%s depends on column \"%s\".",
15791 : : getObjectDescription(&foundObject, false),
15792 : : colName)));
15793 : 0 : break;
15794 : :
15795 : 0 : default:
15796 : :
15797 : : /*
15798 : : * We don't expect any other sorts of objects to depend on a
15799 : : * column.
15800 : : */
15801 [ # # ]: 0 : elog(ERROR, "unexpected object depending on column: %s",
15802 : : getObjectDescription(&foundObject, false));
15803 : : break;
15804 : : }
15805 : : }
15806 : :
15807 : 957 : systable_endscan(scan);
15808 : 957 : table_close(depRel, NoLock);
15809 : 957 : }
15810 : :
15811 : : /*
15812 : : * Record information about dependencies between objects with whole-row Var
15813 : : * references (indexes, check constraints, etc.) and the relation.
15814 : : *
15815 : : * See also RememberAllDependentForRebuilding, which handles non-whole-row Var
15816 : : * references.
15817 : : *
15818 : : * This function currently applies only to ALTER COLUMN SET EXPRESSION.
15819 : : */
15820 : : static void
15821 : 181 : RememberWholeRowDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype, Relation rel)
15822 : : {
15823 : : ScanKeyData skey;
15824 : : Relation pg_constraint;
15825 : : Relation pg_index;
15826 : : SysScanDesc conscan;
15827 : : SysScanDesc indscan;
15828 : : HeapTuple constrTuple;
15829 : : HeapTuple indexTuple;
15830 : : bool isnull;
15831 : :
15832 : : Assert(subtype == AT_SetExpression);
15833 : :
15834 : : /*
15835 : : * Check CHECK constraints with whole-row references first.
15836 : : */
15837 [ + - ]: 181 : if (RelationGetDescr(rel)->constr &&
15838 [ + + ]: 181 : RelationGetDescr(rel)->constr->num_check > 0)
15839 : : {
15840 : 28 : pg_constraint = table_open(ConstraintRelationId, AccessShareLock);
15841 : :
15842 : 28 : ScanKeyInit(&skey,
15843 : : Anum_pg_constraint_conrelid,
15844 : : BTEqualStrategyNumber, F_OIDEQ,
15845 : : ObjectIdGetDatum(RelationGetRelid(rel)));
15846 : :
15847 : 28 : conscan = systable_beginscan(pg_constraint,
15848 : : ConstraintRelidTypidNameIndexId,
15849 : : true,
15850 : : NULL,
15851 : : 1,
15852 : : &skey);
15853 : :
15854 [ + + ]: 96 : while (HeapTupleIsValid(constrTuple = systable_getnext(conscan)))
15855 : : {
15856 : 68 : Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(constrTuple);
15857 : : Datum exprDatum;
15858 : :
15859 [ + + ]: 68 : if (conform->contype != CONSTRAINT_CHECK)
15860 : 40 : continue;
15861 : :
15862 : 28 : exprDatum = heap_getattr(constrTuple,
15863 : : Anum_pg_constraint_conbin,
15864 : : RelationGetDescr(pg_constraint),
15865 : : &isnull);
15866 [ - + ]: 28 : if (isnull)
15867 [ # # ]: 0 : elog(ERROR, "null conbin for relation \"%s\"",
15868 : : RelationGetRelationName(rel));
15869 : : else
15870 : : {
15871 : : char *exprString;
15872 : : Node *expr;
15873 : 28 : Bitmapset *expr_attrs = NULL;
15874 : :
15875 : 28 : exprString = TextDatumGetCString(exprDatum);
15876 : 28 : expr = stringToNode(exprString);
15877 : 28 : pfree(exprString);
15878 : :
15879 : : /* Find all attributes referenced */
15880 : 28 : pull_varattnos(expr, 1, &expr_attrs);
15881 : :
15882 : : /*
15883 : : * If the CHECK constraint contains whole-row reference then
15884 : : * remember it.
15885 : : */
15886 [ + + ]: 28 : if (bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, expr_attrs))
15887 : : {
15888 : 8 : RememberConstraintForRebuilding(conform->oid, tab);
15889 : : }
15890 : : }
15891 : : }
15892 : 28 : systable_endscan(conscan);
15893 : 28 : table_close(pg_constraint, AccessShareLock);
15894 : : }
15895 : :
15896 : : /*
15897 : : * Now check indexes with whole-row references. Prepare to scan pg_index
15898 : : * for entries having indrelid matching this relation.
15899 : : */
15900 : 181 : ScanKeyInit(&skey,
15901 : : Anum_pg_index_indrelid,
15902 : : BTEqualStrategyNumber, F_OIDEQ,
15903 : : ObjectIdGetDatum(RelationGetRelid(rel)));
15904 : :
15905 : 181 : pg_index = table_open(IndexRelationId, AccessShareLock);
15906 : :
15907 : 181 : indscan = systable_beginscan(pg_index,
15908 : : IndexIndrelidIndexId,
15909 : : true,
15910 : : NULL,
15911 : : 1,
15912 : : &skey);
15913 : :
15914 [ + + ]: 261 : while (HeapTupleIsValid(indexTuple = systable_getnext(indscan)))
15915 : : {
15916 : 80 : Form_pg_index index = (Form_pg_index) GETSTRUCT(indexTuple);
15917 : : Datum exprDatum;
15918 : :
15919 : 80 : exprDatum = heap_getattr(indexTuple,
15920 : : Anum_pg_index_indexprs,
15921 : : RelationGetDescr(pg_index),
15922 : : &isnull);
15923 [ + + ]: 80 : if (!isnull)
15924 : : {
15925 : : char *exprString;
15926 : : Node *expr;
15927 : 12 : Bitmapset *expr_attrs = NULL;
15928 : :
15929 : 12 : exprString = TextDatumGetCString(exprDatum);
15930 : 12 : expr = stringToNode(exprString);
15931 : 12 : pfree(exprString);
15932 : :
15933 : : /* Find all attributes referenced */
15934 : 12 : pull_varattnos(expr, 1, &expr_attrs);
15935 : :
15936 : : /*
15937 : : * If the index expression contains a whole-row reference then
15938 : : * remember it.
15939 : : */
15940 [ + + ]: 12 : if (bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, expr_attrs))
15941 : : {
15942 : 8 : RememberIndexForRebuilding(index->indexrelid, tab);
15943 : 8 : continue;
15944 : : }
15945 : : }
15946 : :
15947 : 72 : exprDatum = heap_getattr(indexTuple,
15948 : : Anum_pg_index_indpred,
15949 : : RelationGetDescr(pg_index),
15950 : : &isnull);
15951 [ + + ]: 72 : if (!isnull)
15952 : : {
15953 : : char *exprString;
15954 : : Node *expr;
15955 : 20 : Bitmapset *expr_attrs = NULL;
15956 : :
15957 : 20 : exprString = TextDatumGetCString(exprDatum);
15958 : 20 : expr = stringToNode(exprString);
15959 : 20 : pfree(exprString);
15960 : :
15961 : : /* Find all attributes referenced */
15962 : 20 : pull_varattnos(expr, 1, &expr_attrs);
15963 : :
15964 : : /*
15965 : : * If the index predicate expression contains a whole-row
15966 : : * reference then remember it.
15967 : : */
15968 [ + + ]: 20 : if (bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber, expr_attrs))
15969 : : {
15970 : 16 : RememberIndexForRebuilding(index->indexrelid, tab);
15971 : : }
15972 : : }
15973 : : }
15974 : :
15975 : 181 : systable_endscan(indscan);
15976 : 181 : table_close(pg_index, AccessShareLock);
15977 : 181 : }
15978 : :
15979 : : /*
15980 : : * Subroutine for ATExecAlterColumnType: remember that a replica identity
15981 : : * needs to be reset.
15982 : : */
15983 : : static void
15984 : 330 : RememberReplicaIdentityForRebuilding(Oid indoid, AlteredTableInfo *tab)
15985 : : {
15986 [ + + ]: 330 : if (!get_index_isreplident(indoid))
15987 : 318 : return;
15988 : :
15989 [ - + ]: 12 : if (tab->replicaIdentityIndex)
15990 [ # # ]: 0 : elog(ERROR, "relation %u has multiple indexes marked as replica identity", tab->relid);
15991 : :
15992 : 12 : tab->replicaIdentityIndex = get_rel_name(indoid);
15993 : : }
15994 : :
15995 : : /*
15996 : : * Subroutine for ATExecAlterColumnType: remember any clustered index.
15997 : : */
15998 : : static void
15999 : 330 : RememberClusterOnForRebuilding(Oid indoid, AlteredTableInfo *tab)
16000 : : {
16001 [ + + ]: 330 : if (!get_index_isclustered(indoid))
16002 : 318 : return;
16003 : :
16004 [ - + ]: 12 : if (tab->clusterOnIndex)
16005 [ # # ]: 0 : elog(ERROR, "relation %u has multiple clustered indexes", tab->relid);
16006 : :
16007 : 12 : tab->clusterOnIndex = get_rel_name(indoid);
16008 : : }
16009 : :
16010 : : /*
16011 : : * Subroutine for ATExecAlterColumnType: remember that a constraint needs
16012 : : * to be rebuilt (which we might already know).
16013 : : */
16014 : : static void
16015 : 544 : RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab)
16016 : : {
16017 : : /*
16018 : : * This de-duplication check is critical for two independent reasons: we
16019 : : * mustn't try to recreate the same constraint twice, and if a constraint
16020 : : * depends on more than one column whose type is to be altered, we must
16021 : : * capture its definition string before applying any of the column type
16022 : : * changes. ruleutils.c will get confused if we ask again later.
16023 : : */
16024 [ + + ]: 544 : if (!list_member_oid(tab->changedConstraintOids, conoid))
16025 : : {
16026 : : /* OK, capture the constraint's existing definition string */
16027 : 468 : char *defstring = pg_get_constraintdef_command(conoid);
16028 : : Oid indoid;
16029 : :
16030 : : /*
16031 : : * It is critical to create not-null constraints ahead of primary key
16032 : : * indexes; otherwise, the not-null constraint would be created by the
16033 : : * primary key, and the constraint name would be wrong.
16034 : : */
16035 [ + + ]: 468 : if (get_constraint_type(conoid) == CONSTRAINT_NOTNULL)
16036 : : {
16037 : 163 : tab->changedConstraintOids = lcons_oid(conoid,
16038 : : tab->changedConstraintOids);
16039 : 163 : tab->changedConstraintDefs = lcons(defstring,
16040 : : tab->changedConstraintDefs);
16041 : : }
16042 : : else
16043 : : {
16044 : :
16045 : 305 : tab->changedConstraintOids = lappend_oid(tab->changedConstraintOids,
16046 : : conoid);
16047 : 305 : tab->changedConstraintDefs = lappend(tab->changedConstraintDefs,
16048 : : defstring);
16049 : : }
16050 : :
16051 : : /*
16052 : : * For the index of a constraint, if any, remember if it is used for
16053 : : * the table's replica identity or if it is a clustered index, so that
16054 : : * ATPostAlterTypeCleanup() can queue up commands necessary to restore
16055 : : * those properties.
16056 : : */
16057 : 468 : indoid = get_constraint_index(conoid);
16058 [ + + ]: 468 : if (OidIsValid(indoid))
16059 : : {
16060 : 152 : RememberReplicaIdentityForRebuilding(indoid, tab);
16061 : 152 : RememberClusterOnForRebuilding(indoid, tab);
16062 : : }
16063 : : }
16064 : 544 : }
16065 : :
16066 : : /*
16067 : : * Subroutine for ATExecAlterColumnType: remember that an index needs
16068 : : * to be rebuilt (which we might already know).
16069 : : */
16070 : : static void
16071 : 191 : RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab)
16072 : : {
16073 : : /*
16074 : : * This de-duplication check is critical for two independent reasons: we
16075 : : * mustn't try to recreate the same index twice, and if an index depends
16076 : : * on more than one column whose type is to be altered, we must capture
16077 : : * its definition string before applying any of the column type changes.
16078 : : * ruleutils.c will get confused if we ask again later.
16079 : : */
16080 [ + + ]: 191 : if (!list_member_oid(tab->changedIndexOids, indoid))
16081 : : {
16082 : : /*
16083 : : * Before adding it as an index-to-rebuild, we'd better see if it
16084 : : * belongs to a constraint, and if so rebuild the constraint instead.
16085 : : * Typically this check fails, because constraint indexes normally
16086 : : * have only dependencies on their constraint. But it's possible for
16087 : : * such an index to also have direct dependencies on table columns,
16088 : : * for example with a partial exclusion constraint.
16089 : : */
16090 : 186 : Oid conoid = get_index_constraint(indoid);
16091 : :
16092 [ + + ]: 186 : if (OidIsValid(conoid))
16093 : : {
16094 : 8 : RememberConstraintForRebuilding(conoid, tab);
16095 : : }
16096 : : else
16097 : : {
16098 : : /* OK, capture the index's existing definition string */
16099 : 178 : char *defstring = pg_get_indexdef_string(indoid);
16100 : :
16101 : 178 : tab->changedIndexOids = lappend_oid(tab->changedIndexOids,
16102 : : indoid);
16103 : 178 : tab->changedIndexDefs = lappend(tab->changedIndexDefs,
16104 : : defstring);
16105 : :
16106 : : /*
16107 : : * Remember if this index is used for the table's replica identity
16108 : : * or if it is a clustered index, so that ATPostAlterTypeCleanup()
16109 : : * can queue up commands necessary to restore those properties.
16110 : : */
16111 : 178 : RememberReplicaIdentityForRebuilding(indoid, tab);
16112 : 178 : RememberClusterOnForRebuilding(indoid, tab);
16113 : : }
16114 : : }
16115 : 191 : }
16116 : :
16117 : : /*
16118 : : * Subroutine for ATExecAlterColumnType: remember that a statistics object
16119 : : * needs to be rebuilt (which we might already know).
16120 : : */
16121 : : static void
16122 : 57 : RememberStatisticsForRebuilding(Oid stxoid, AlteredTableInfo *tab)
16123 : : {
16124 : : /*
16125 : : * This de-duplication check is critical for two independent reasons: we
16126 : : * mustn't try to recreate the same statistics object twice, and if the
16127 : : * statistics object depends on more than one column whose type is to be
16128 : : * altered, we must capture its definition string before applying any of
16129 : : * the type changes. ruleutils.c will get confused if we ask again later.
16130 : : */
16131 [ + - ]: 57 : if (!list_member_oid(tab->changedStatisticsOids, stxoid))
16132 : : {
16133 : : /* OK, capture the statistics object's existing definition string */
16134 : 57 : char *defstring = pg_get_statisticsobjdef_string(stxoid);
16135 : : HeapTuple tup;
16136 : : Form_pg_statistic_ext statext;
16137 : :
16138 : 57 : tup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(stxoid));
16139 : :
16140 [ - + ]: 57 : if (!HeapTupleIsValid(tup)) /* should not happen */
16141 [ # # ]: 0 : elog(ERROR, "cache lookup failed for statistics object %u", stxoid);
16142 : :
16143 : 57 : statext = (Form_pg_statistic_ext) GETSTRUCT(tup);
16144 : :
16145 : 57 : tab->changedStatisticsOids = lappend_oid(tab->changedStatisticsOids,
16146 : : stxoid);
16147 : 57 : tab->changedStatisticsDefs = lappend(tab->changedStatisticsDefs,
16148 : : defstring);
16149 : :
16150 : 57 : tab->changedStatisticsOwners = lappend_oid(tab->changedStatisticsOwners,
16151 : : statext->stxowner);
16152 : :
16153 : 57 : ReleaseSysCache(tup);
16154 : : }
16155 : 57 : }
16156 : :
16157 : : /*
16158 : : * Cleanup after we've finished all the ALTER TYPE or SET EXPRESSION
16159 : : * operations for a particular relation. We have to drop and recreate all the
16160 : : * indexes and constraints that depend on the altered columns. We do the
16161 : : * actual dropping here, but re-creation is managed by adding work queue
16162 : : * entries to do those steps later.
16163 : : */
16164 : : static void
16165 : 925 : ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
16166 : : {
16167 : : ObjectAddress obj;
16168 : : ObjectAddresses *objects;
16169 : : ListCell *def_item;
16170 : : ListCell *oid_item;
16171 : : ListCell *owner_item;
16172 : :
16173 : : /*
16174 : : * Collect all the constraints and indexes to drop so we can process them
16175 : : * in a single call. That way we don't have to worry about dependencies
16176 : : * among them.
16177 : : */
16178 : 925 : objects = new_object_addresses();
16179 : :
16180 : : /*
16181 : : * Re-parse the index and constraint definitions, and attach them to the
16182 : : * appropriate work queue entries. We do this before dropping because in
16183 : : * the case of a constraint on another table, we might not yet have
16184 : : * exclusive lock on the table the constraint is attached to, and we need
16185 : : * to get that before reparsing/dropping. (That's possible at least for
16186 : : * FOREIGN KEY, CHECK, and EXCLUSION constraints; in non-FK cases it
16187 : : * requires a dependency on the target table's composite type in the other
16188 : : * table's constraint expressions.)
16189 : : *
16190 : : * We can't rely on the output of deparsing to tell us which relation to
16191 : : * operate on, because concurrent activity might have made the name
16192 : : * resolve differently. Instead, we've got to use the OID of the
16193 : : * constraint or index we're processing to figure out which relation to
16194 : : * operate on.
16195 : : */
16196 [ + + + + : 1393 : forboth(oid_item, tab->changedConstraintOids,
+ + + + +
+ + - +
+ ]
16197 : : def_item, tab->changedConstraintDefs)
16198 : : {
16199 : 468 : Oid oldId = lfirst_oid(oid_item);
16200 : : HeapTuple tup;
16201 : : Form_pg_constraint con;
16202 : : Oid relid;
16203 : : Oid confrelid;
16204 : : bool conislocal;
16205 : :
16206 : 468 : tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(oldId));
16207 [ - + ]: 468 : if (!HeapTupleIsValid(tup)) /* should not happen */
16208 [ # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u", oldId);
16209 : 468 : con = (Form_pg_constraint) GETSTRUCT(tup);
16210 [ + + ]: 468 : if (OidIsValid(con->conrelid))
16211 : 455 : relid = con->conrelid;
16212 : : else
16213 : : {
16214 : : /* must be a domain constraint */
16215 : 13 : relid = get_typ_typrelid(getBaseType(con->contypid));
16216 [ - + ]: 13 : if (!OidIsValid(relid))
16217 [ # # ]: 0 : elog(ERROR, "could not identify relation associated with constraint %u", oldId);
16218 : : }
16219 : 468 : confrelid = con->confrelid;
16220 : 468 : conislocal = con->conislocal;
16221 : 468 : ReleaseSysCache(tup);
16222 : :
16223 : 468 : ObjectAddressSet(obj, ConstraintRelationId, oldId);
16224 : 468 : add_exact_object_address(&obj, objects);
16225 : :
16226 : : /*
16227 : : * If the constraint is inherited (only), we don't want to inject a
16228 : : * new definition here; it'll get recreated when
16229 : : * ATAddCheckNNConstraint recurses from adding the parent table's
16230 : : * constraint. But we had to carry the info this far so that we can
16231 : : * drop the constraint below.
16232 : : */
16233 [ + + ]: 468 : if (!conislocal)
16234 : 34 : continue;
16235 : :
16236 : : /*
16237 : : * When rebuilding another table's constraint that references the
16238 : : * table we're modifying, we might not yet have any lock on the other
16239 : : * table, so get one now. We'll need AccessExclusiveLock for the DROP
16240 : : * CONSTRAINT step, so there's no value in asking for anything weaker.
16241 : : */
16242 [ + + ]: 434 : if (relid != tab->relid)
16243 : 36 : LockRelationOid(relid, AccessExclusiveLock);
16244 : :
16245 : 434 : ATPostAlterTypeParse(oldId, relid, confrelid, InvalidOid,
16246 : 434 : (char *) lfirst(def_item),
16247 : 434 : wqueue, lockmode, tab->rewrite);
16248 : : }
16249 [ + + + + : 1103 : forboth(oid_item, tab->changedIndexOids,
+ + + + +
+ + - +
+ ]
16250 : : def_item, tab->changedIndexDefs)
16251 : : {
16252 : 178 : Oid oldId = lfirst_oid(oid_item);
16253 : : Oid relid;
16254 : :
16255 : 178 : relid = IndexGetRelation(oldId, false);
16256 : :
16257 : : /*
16258 : : * As above, make sure we have lock on the index's table if it's not
16259 : : * the same table.
16260 : : */
16261 [ + + ]: 178 : if (relid != tab->relid)
16262 : 12 : LockRelationOid(relid, AccessExclusiveLock);
16263 : :
16264 : 178 : ATPostAlterTypeParse(oldId, relid, InvalidOid, InvalidOid,
16265 : 178 : (char *) lfirst(def_item),
16266 : 178 : wqueue, lockmode, tab->rewrite);
16267 : :
16268 : 178 : ObjectAddressSet(obj, RelationRelationId, oldId);
16269 : 178 : add_exact_object_address(&obj, objects);
16270 : : }
16271 : :
16272 : : /* add dependencies for new statistics */
16273 [ + + + + : 982 : forthree(oid_item, tab->changedStatisticsOids,
+ + + + +
+ + + + +
+ - + - +
+ ]
16274 : : def_item, tab->changedStatisticsDefs,
16275 : : owner_item, tab->changedStatisticsOwners)
16276 : : {
16277 : 57 : Oid oldId = lfirst_oid(oid_item);
16278 : : Oid relid;
16279 : :
16280 : 57 : relid = StatisticsGetRelation(oldId, false);
16281 : :
16282 : : /*
16283 : : * As above, make sure we have lock on the statistics object's table
16284 : : * if it's not the same table. However, we take
16285 : : * ShareUpdateExclusiveLock here, aligning with the lock level used in
16286 : : * CreateStatistics and RemoveStatisticsById.
16287 : : *
16288 : : * CAUTION: this should be done after all cases that grab
16289 : : * AccessExclusiveLock, else we risk causing deadlock due to needing
16290 : : * to promote our table lock.
16291 : : */
16292 [ + + ]: 57 : if (relid != tab->relid)
16293 : 12 : LockRelationOid(relid, ShareUpdateExclusiveLock);
16294 : :
16295 : 57 : ATPostAlterTypeParse(oldId, relid, InvalidOid, lfirst_oid(owner_item),
16296 : 57 : (char *) lfirst(def_item),
16297 : 57 : wqueue, lockmode, tab->rewrite);
16298 : :
16299 : 57 : ObjectAddressSet(obj, StatisticExtRelationId, oldId);
16300 : 57 : add_exact_object_address(&obj, objects);
16301 : : }
16302 : :
16303 : : /*
16304 : : * Queue up command to restore replica identity index marking
16305 : : */
16306 [ + + ]: 925 : if (tab->replicaIdentityIndex)
16307 : : {
16308 : 12 : AlterTableCmd *cmd = makeNode(AlterTableCmd);
16309 : 12 : ReplicaIdentityStmt *subcmd = makeNode(ReplicaIdentityStmt);
16310 : :
16311 : 12 : subcmd->identity_type = REPLICA_IDENTITY_INDEX;
16312 : 12 : subcmd->name = tab->replicaIdentityIndex;
16313 : 12 : cmd->subtype = AT_ReplicaIdentity;
16314 : 12 : cmd->def = (Node *) subcmd;
16315 : :
16316 : : /* do it after indexes and constraints */
16317 : 12 : tab->subcmds[AT_PASS_OLD_CONSTR] =
16318 : 12 : lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
16319 : : }
16320 : :
16321 : : /*
16322 : : * Queue up command to restore marking of index used for cluster.
16323 : : */
16324 [ + + ]: 925 : if (tab->clusterOnIndex)
16325 : : {
16326 : 12 : AlterTableCmd *cmd = makeNode(AlterTableCmd);
16327 : :
16328 : 12 : cmd->subtype = AT_ClusterOn;
16329 : 12 : cmd->name = tab->clusterOnIndex;
16330 : :
16331 : : /* do it after indexes and constraints */
16332 : 12 : tab->subcmds[AT_PASS_OLD_CONSTR] =
16333 : 12 : lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
16334 : : }
16335 : :
16336 : : /*
16337 : : * It should be okay to use DROP_RESTRICT here, since nothing else should
16338 : : * be depending on these objects.
16339 : : */
16340 : 925 : performMultipleDeletions(objects, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
16341 : :
16342 : 925 : free_object_addresses(objects);
16343 : :
16344 : : /*
16345 : : * The objects will get recreated during subsequent passes over the work
16346 : : * queue.
16347 : : */
16348 : 925 : }
16349 : :
16350 : : /*
16351 : : * Parse the previously-saved definition string for a constraint, index or
16352 : : * statistics object against the newly-established column data type(s), and
16353 : : * queue up the resulting command parsetrees for execution.
16354 : : *
16355 : : * This might fail if, for example, you have a WHERE clause that uses an
16356 : : * operator that's not available for the new column type.
16357 : : */
16358 : : static void
16359 : 669 : ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, Oid ownerId,
16360 : : char *cmd, List **wqueue, LOCKMODE lockmode,
16361 : : bool rewrite)
16362 : : {
16363 : : List *raw_parsetree_list;
16364 : : List *querytree_list;
16365 : : ListCell *list_item;
16366 : : Relation rel;
16367 : :
16368 : : /*
16369 : : * We expect that we will get only ALTER TABLE and CREATE INDEX
16370 : : * statements. Hence, there is no need to pass them through
16371 : : * parse_analyze_*() or the rewriter, but instead we need to pass them
16372 : : * through parse_utilcmd.c to make them ready for execution.
16373 : : */
16374 : 669 : raw_parsetree_list = raw_parser(cmd, RAW_PARSE_DEFAULT);
16375 : 669 : querytree_list = NIL;
16376 [ + - + + : 1338 : foreach(list_item, raw_parsetree_list)
+ + ]
16377 : : {
16378 : 669 : RawStmt *rs = lfirst_node(RawStmt, list_item);
16379 : 669 : Node *stmt = rs->stmt;
16380 : :
16381 [ + + ]: 669 : if (IsA(stmt, IndexStmt))
16382 : 178 : querytree_list = lappend(querytree_list,
16383 : 178 : transformIndexStmt(oldRelId,
16384 : : (IndexStmt *) stmt,
16385 : : cmd));
16386 [ + + ]: 491 : else if (IsA(stmt, AlterTableStmt))
16387 : : {
16388 : : List *beforeStmts;
16389 : : List *afterStmts;
16390 : :
16391 : 421 : stmt = (Node *) transformAlterTableStmt(oldRelId,
16392 : : (AlterTableStmt *) stmt,
16393 : : cmd,
16394 : : &beforeStmts,
16395 : : &afterStmts);
16396 : 421 : querytree_list = list_concat(querytree_list, beforeStmts);
16397 : 421 : querytree_list = lappend(querytree_list, stmt);
16398 : 421 : querytree_list = list_concat(querytree_list, afterStmts);
16399 : : }
16400 [ + + ]: 70 : else if (IsA(stmt, CreateStatsStmt))
16401 : : {
16402 : : CreateStatsStmt *csstmt;
16403 : :
16404 : 57 : csstmt = transformStatsStmt(oldRelId, (CreateStatsStmt *) stmt, cmd);
16405 : 57 : csstmt->owner = ownerId;
16406 : :
16407 : 57 : querytree_list = lappend(querytree_list, csstmt);
16408 : : }
16409 : : else
16410 : 13 : querytree_list = lappend(querytree_list, stmt);
16411 : : }
16412 : :
16413 : : /* Caller should already have acquired whatever lock we need. */
16414 : 669 : rel = relation_open(oldRelId, NoLock);
16415 : :
16416 : : /*
16417 : : * Attach each generated command to the proper place in the work queue.
16418 : : * Note this could result in creation of entirely new work-queue entries.
16419 : : *
16420 : : * Also note that we have to tweak the command subtypes, because it turns
16421 : : * out that re-creation of indexes and constraints has to act a bit
16422 : : * differently from initial creation.
16423 : : */
16424 [ + - + + : 1338 : foreach(list_item, querytree_list)
+ + ]
16425 : : {
16426 : 669 : Node *stm = (Node *) lfirst(list_item);
16427 : : AlteredTableInfo *tab;
16428 : :
16429 : 669 : tab = ATGetQueueEntry(wqueue, rel);
16430 : :
16431 [ + + ]: 669 : if (IsA(stm, IndexStmt))
16432 : : {
16433 : 178 : IndexStmt *stmt = (IndexStmt *) stm;
16434 : : AlterTableCmd *newcmd;
16435 : :
16436 [ + + ]: 178 : if (!rewrite)
16437 : 53 : TryReuseIndex(oldId, stmt);
16438 : 178 : stmt->reset_default_tblspc = true;
16439 : : /* keep the index's comment */
16440 : 178 : stmt->idxcomment = GetComment(oldId, RelationRelationId, 0);
16441 : :
16442 : 178 : newcmd = makeNode(AlterTableCmd);
16443 : 178 : newcmd->subtype = AT_ReAddIndex;
16444 : 178 : newcmd->def = (Node *) stmt;
16445 : 178 : tab->subcmds[AT_PASS_OLD_INDEX] =
16446 : 178 : lappend(tab->subcmds[AT_PASS_OLD_INDEX], newcmd);
16447 : : }
16448 [ + + ]: 491 : else if (IsA(stm, AlterTableStmt))
16449 : : {
16450 : 421 : AlterTableStmt *stmt = (AlterTableStmt *) stm;
16451 : : ListCell *lcmd;
16452 : :
16453 [ + - + + : 842 : foreach(lcmd, stmt->cmds)
+ + ]
16454 : : {
16455 : 421 : AlterTableCmd *cmd = lfirst_node(AlterTableCmd, lcmd);
16456 : :
16457 [ + + ]: 421 : if (cmd->subtype == AT_AddIndex)
16458 : : {
16459 : : IndexStmt *indstmt;
16460 : : Oid indoid;
16461 : :
16462 : 152 : indstmt = castNode(IndexStmt, cmd->def);
16463 : 152 : indoid = get_constraint_index(oldId);
16464 : :
16465 [ + + ]: 152 : if (!rewrite)
16466 : 32 : TryReuseIndex(indoid, indstmt);
16467 : : /* keep any comment on the index */
16468 : 152 : indstmt->idxcomment = GetComment(indoid,
16469 : : RelationRelationId, 0);
16470 : 152 : indstmt->reset_default_tblspc = true;
16471 : :
16472 : 152 : cmd->subtype = AT_ReAddIndex;
16473 : 152 : tab->subcmds[AT_PASS_OLD_INDEX] =
16474 : 152 : lappend(tab->subcmds[AT_PASS_OLD_INDEX], cmd);
16475 : :
16476 : : /* recreate any comment on the constraint */
16477 : 152 : RebuildConstraintComment(tab,
16478 : : AT_PASS_OLD_INDEX,
16479 : : oldId,
16480 : : rel,
16481 : : NIL,
16482 : 152 : indstmt->idxname);
16483 : : }
16484 [ + - ]: 269 : else if (cmd->subtype == AT_AddConstraint)
16485 : : {
16486 : 269 : Constraint *con = castNode(Constraint, cmd->def);
16487 : :
16488 : 269 : con->old_pktable_oid = refRelId;
16489 : : /* rewriting neither side of a FK */
16490 [ + + ]: 269 : if (con->contype == CONSTR_FOREIGN &&
16491 [ + + + - ]: 48 : !rewrite && tab->rewrite == 0)
16492 : 4 : TryReuseForeignKey(oldId, con);
16493 : 269 : con->reset_default_tblspc = true;
16494 : 269 : cmd->subtype = AT_ReAddConstraint;
16495 : 269 : tab->subcmds[AT_PASS_OLD_CONSTR] =
16496 : 269 : lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
16497 : :
16498 : : /*
16499 : : * Recreate any comment on the constraint. If we have
16500 : : * recreated a primary key, then transformTableConstraint
16501 : : * has added an unnamed not-null constraint here; skip
16502 : : * this in that case.
16503 : : */
16504 [ + - ]: 269 : if (con->conname)
16505 : 269 : RebuildConstraintComment(tab,
16506 : : AT_PASS_OLD_CONSTR,
16507 : : oldId,
16508 : : rel,
16509 : : NIL,
16510 : 269 : con->conname);
16511 : : else
16512 : : Assert(con->contype == CONSTR_NOTNULL);
16513 : : }
16514 : : else
16515 [ # # ]: 0 : elog(ERROR, "unexpected statement subtype: %d",
16516 : : (int) cmd->subtype);
16517 : : }
16518 : : }
16519 [ + + ]: 70 : else if (IsA(stm, AlterDomainStmt))
16520 : : {
16521 : 13 : AlterDomainStmt *stmt = (AlterDomainStmt *) stm;
16522 : :
16523 [ + - ]: 13 : if (stmt->subtype == AD_AddConstraint)
16524 : : {
16525 : 13 : Constraint *con = castNode(Constraint, stmt->def);
16526 : 13 : AlterTableCmd *cmd = makeNode(AlterTableCmd);
16527 : :
16528 : 13 : cmd->subtype = AT_ReAddDomainConstraint;
16529 : 13 : cmd->def = (Node *) stmt;
16530 : 13 : tab->subcmds[AT_PASS_OLD_CONSTR] =
16531 : 13 : lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
16532 : :
16533 : : /* recreate any comment on the constraint */
16534 : 13 : RebuildConstraintComment(tab,
16535 : : AT_PASS_OLD_CONSTR,
16536 : : oldId,
16537 : : NULL,
16538 : : stmt->typeName,
16539 : 13 : con->conname);
16540 : : }
16541 : : else
16542 [ # # ]: 0 : elog(ERROR, "unexpected statement subtype: %d",
16543 : : (int) stmt->subtype);
16544 : : }
16545 [ + - ]: 57 : else if (IsA(stm, CreateStatsStmt))
16546 : : {
16547 : 57 : CreateStatsStmt *stmt = (CreateStatsStmt *) stm;
16548 : : AlterTableCmd *newcmd;
16549 : :
16550 : : /* keep the statistics object's comment */
16551 : 57 : stmt->stxcomment = GetComment(oldId, StatisticExtRelationId, 0);
16552 : :
16553 : 57 : newcmd = makeNode(AlterTableCmd);
16554 : 57 : newcmd->subtype = AT_ReAddStatistics;
16555 : 57 : newcmd->def = (Node *) stmt;
16556 : 57 : tab->subcmds[AT_PASS_MISC] =
16557 : 57 : lappend(tab->subcmds[AT_PASS_MISC], newcmd);
16558 : : }
16559 : : else
16560 [ # # ]: 0 : elog(ERROR, "unexpected statement type: %d",
16561 : : (int) nodeTag(stm));
16562 : : }
16563 : :
16564 : 669 : relation_close(rel, NoLock);
16565 : 669 : }
16566 : :
16567 : : /*
16568 : : * Subroutine for ATPostAlterTypeParse() to recreate any existing comment
16569 : : * for a table or domain constraint that is being rebuilt.
16570 : : *
16571 : : * objid is the OID of the constraint.
16572 : : * Pass "rel" for a table constraint, or "domname" (domain's qualified name
16573 : : * as a string list) for a domain constraint.
16574 : : * (We could dig that info, as well as the conname, out of the pg_constraint
16575 : : * entry; but callers already have them so might as well pass them.)
16576 : : */
16577 : : static void
16578 : 434 : RebuildConstraintComment(AlteredTableInfo *tab, AlterTablePass pass, Oid objid,
16579 : : Relation rel, List *domname,
16580 : : const char *conname)
16581 : : {
16582 : : CommentStmt *cmd;
16583 : : char *comment_str;
16584 : : AlterTableCmd *newcmd;
16585 : :
16586 : : /* Look for comment for object wanted, and leave if none */
16587 : 434 : comment_str = GetComment(objid, ConstraintRelationId, 0);
16588 [ + + ]: 434 : if (comment_str == NULL)
16589 : 374 : return;
16590 : :
16591 : : /* Build CommentStmt node, copying all input data for safety */
16592 : 60 : cmd = makeNode(CommentStmt);
16593 [ + + ]: 60 : if (rel)
16594 : : {
16595 : 52 : cmd->objtype = OBJECT_TABCONSTRAINT;
16596 : 52 : cmd->object = (Node *)
16597 : 52 : list_make3(makeString(get_namespace_name(RelationGetNamespace(rel))),
16598 : : makeString(pstrdup(RelationGetRelationName(rel))),
16599 : : makeString(pstrdup(conname)));
16600 : : }
16601 : : else
16602 : : {
16603 : 8 : cmd->objtype = OBJECT_DOMCONSTRAINT;
16604 : 8 : cmd->object = (Node *)
16605 : 8 : list_make2(makeTypeNameFromNameList(copyObject(domname)),
16606 : : makeString(pstrdup(conname)));
16607 : : }
16608 : 60 : cmd->comment = comment_str;
16609 : :
16610 : : /* Append it to list of commands */
16611 : 60 : newcmd = makeNode(AlterTableCmd);
16612 : 60 : newcmd->subtype = AT_ReAddComment;
16613 : 60 : newcmd->def = (Node *) cmd;
16614 : 60 : tab->subcmds[pass] = lappend(tab->subcmds[pass], newcmd);
16615 : : }
16616 : :
16617 : : /*
16618 : : * Subroutine for ATPostAlterTypeParse(). Calls out to CheckIndexCompatible()
16619 : : * for the real analysis, then mutates the IndexStmt based on that verdict.
16620 : : */
16621 : : static void
16622 : 85 : TryReuseIndex(Oid oldId, IndexStmt *stmt)
16623 : : {
16624 [ + + ]: 85 : if (CheckIndexCompatible(oldId,
16625 : 85 : stmt->accessMethod,
16626 : 85 : stmt->indexParams,
16627 : 85 : stmt->excludeOpNames,
16628 : 85 : stmt->iswithoutoverlaps))
16629 : : {
16630 : 69 : Relation irel = index_open(oldId, NoLock);
16631 : :
16632 : : /* If it's a partitioned index, there is no storage to share. */
16633 [ + + ]: 69 : if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
16634 : : {
16635 : 49 : stmt->oldNumber = irel->rd_locator.relNumber;
16636 : 49 : stmt->oldCreateSubid = irel->rd_createSubid;
16637 : 49 : stmt->oldFirstRelfilelocatorSubid = irel->rd_firstRelfilelocatorSubid;
16638 : : }
16639 : 69 : index_close(irel, NoLock);
16640 : : }
16641 : 85 : }
16642 : :
16643 : : /*
16644 : : * Subroutine for ATPostAlterTypeParse().
16645 : : *
16646 : : * Stash the old P-F equality operator into the Constraint node, for possible
16647 : : * use by ATAddForeignKeyConstraint() in determining whether revalidation of
16648 : : * this constraint can be skipped.
16649 : : */
16650 : : static void
16651 : 4 : TryReuseForeignKey(Oid oldId, Constraint *con)
16652 : : {
16653 : : HeapTuple tup;
16654 : : Datum adatum;
16655 : : ArrayType *arr;
16656 : : Oid *rawarr;
16657 : : int numkeys;
16658 : : int i;
16659 : :
16660 : : Assert(con->contype == CONSTR_FOREIGN);
16661 : : Assert(con->old_conpfeqop == NIL); /* already prepared this node */
16662 : :
16663 : 4 : tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(oldId));
16664 [ - + ]: 4 : if (!HeapTupleIsValid(tup)) /* should not happen */
16665 [ # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u", oldId);
16666 : :
16667 : 4 : adatum = SysCacheGetAttrNotNull(CONSTROID, tup,
16668 : : Anum_pg_constraint_conpfeqop);
16669 : 4 : arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
16670 : 4 : numkeys = ARR_DIMS(arr)[0];
16671 : : /* test follows the one in ri_FetchConstraintInfo() */
16672 [ + - ]: 4 : if (ARR_NDIM(arr) != 1 ||
16673 [ + - ]: 4 : ARR_HASNULL(arr) ||
16674 [ - + ]: 4 : ARR_ELEMTYPE(arr) != OIDOID)
16675 [ # # ]: 0 : elog(ERROR, "conpfeqop is not a 1-D Oid array");
16676 [ - + ]: 4 : rawarr = (Oid *) ARR_DATA_PTR(arr);
16677 : :
16678 : : /* stash a List of the operator Oids in our Constraint node */
16679 [ + + ]: 8 : for (i = 0; i < numkeys; i++)
16680 : 4 : con->old_conpfeqop = lappend_oid(con->old_conpfeqop, rawarr[i]);
16681 : :
16682 : 4 : ReleaseSysCache(tup);
16683 : 4 : }
16684 : :
16685 : : /*
16686 : : * ALTER COLUMN .. OPTIONS ( ... )
16687 : : *
16688 : : * Returns the address of the modified column
16689 : : */
16690 : : static ObjectAddress
16691 : 94 : ATExecAlterColumnGenericOptions(Relation rel,
16692 : : const char *colName,
16693 : : List *options,
16694 : : LOCKMODE lockmode)
16695 : : {
16696 : : Relation ftrel;
16697 : : Relation attrel;
16698 : : ForeignServer *server;
16699 : : ForeignDataWrapper *fdw;
16700 : : HeapTuple tuple;
16701 : : HeapTuple newtuple;
16702 : : bool isnull;
16703 : : Datum repl_val[Natts_pg_attribute];
16704 : : bool repl_null[Natts_pg_attribute];
16705 : : bool repl_repl[Natts_pg_attribute];
16706 : : Datum datum;
16707 : : Form_pg_foreign_table fttableform;
16708 : : Form_pg_attribute atttableform;
16709 : : AttrNumber attnum;
16710 : : ObjectAddress address;
16711 : :
16712 [ - + ]: 94 : if (options == NIL)
16713 : 0 : return InvalidObjectAddress;
16714 : :
16715 : : /* First, determine FDW validator associated to the foreign table. */
16716 : 94 : ftrel = table_open(ForeignTableRelationId, AccessShareLock);
16717 : 94 : tuple = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(rel->rd_id));
16718 [ - + ]: 94 : if (!HeapTupleIsValid(tuple))
16719 [ # # ]: 0 : ereport(ERROR,
16720 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
16721 : : errmsg("foreign table \"%s\" does not exist",
16722 : : RelationGetRelationName(rel))));
16723 : 94 : fttableform = (Form_pg_foreign_table) GETSTRUCT(tuple);
16724 : 94 : server = GetForeignServer(fttableform->ftserver);
16725 : 94 : fdw = GetForeignDataWrapper(server->fdwid);
16726 : :
16727 : 94 : table_close(ftrel, AccessShareLock);
16728 : 94 : ReleaseSysCache(tuple);
16729 : :
16730 : 94 : attrel = table_open(AttributeRelationId, RowExclusiveLock);
16731 : 94 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
16732 [ - + ]: 94 : if (!HeapTupleIsValid(tuple))
16733 [ # # ]: 0 : ereport(ERROR,
16734 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
16735 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
16736 : : colName, RelationGetRelationName(rel))));
16737 : :
16738 : : /* Prevent them from altering a system attribute */
16739 : 94 : atttableform = (Form_pg_attribute) GETSTRUCT(tuple);
16740 : 94 : attnum = atttableform->attnum;
16741 [ + + ]: 94 : if (attnum <= 0)
16742 [ + - ]: 4 : ereport(ERROR,
16743 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
16744 : : errmsg("cannot alter system column \"%s\"", colName)));
16745 : :
16746 : :
16747 : : /* Initialize buffers for new tuple values */
16748 : 90 : memset(repl_val, 0, sizeof(repl_val));
16749 : 90 : memset(repl_null, false, sizeof(repl_null));
16750 : 90 : memset(repl_repl, false, sizeof(repl_repl));
16751 : :
16752 : : /* Extract the current options */
16753 : 90 : datum = SysCacheGetAttr(ATTNAME,
16754 : : tuple,
16755 : : Anum_pg_attribute_attfdwoptions,
16756 : : &isnull);
16757 [ + + ]: 90 : if (isnull)
16758 : 84 : datum = PointerGetDatum(NULL);
16759 : :
16760 : : /* Transform the options */
16761 : 90 : datum = transformGenericOptions(AttributeRelationId,
16762 : : datum,
16763 : : options,
16764 : : fdw->fdwvalidator);
16765 : :
16766 [ + - ]: 90 : if (DatumGetPointer(datum) != NULL)
16767 : 90 : repl_val[Anum_pg_attribute_attfdwoptions - 1] = datum;
16768 : : else
16769 : 0 : repl_null[Anum_pg_attribute_attfdwoptions - 1] = true;
16770 : :
16771 : 90 : repl_repl[Anum_pg_attribute_attfdwoptions - 1] = true;
16772 : :
16773 : : /* Everything looks good - update the tuple */
16774 : :
16775 : 90 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(attrel),
16776 : : repl_val, repl_null, repl_repl);
16777 : :
16778 : 90 : CatalogTupleUpdate(attrel, &newtuple->t_self, newtuple);
16779 : :
16780 [ - + ]: 90 : InvokeObjectPostAlterHook(RelationRelationId,
16781 : : RelationGetRelid(rel),
16782 : : atttableform->attnum);
16783 : 90 : ObjectAddressSubSet(address, RelationRelationId,
16784 : : RelationGetRelid(rel), attnum);
16785 : :
16786 : 90 : ReleaseSysCache(tuple);
16787 : :
16788 : 90 : table_close(attrel, RowExclusiveLock);
16789 : :
16790 : 90 : heap_freetuple(newtuple);
16791 : :
16792 : 90 : return address;
16793 : : }
16794 : :
16795 : : /*
16796 : : * ALTER TABLE OWNER
16797 : : *
16798 : : * recursing is true if we are recursing from a table to its indexes,
16799 : : * sequences, or toast table. We don't allow the ownership of those things to
16800 : : * be changed separately from the parent table. Also, we can skip permission
16801 : : * checks (this is necessary not just an optimization, else we'd fail to
16802 : : * handle toast tables properly).
16803 : : *
16804 : : * recursing is also true if ALTER TYPE OWNER is calling us to fix up a
16805 : : * free-standing composite type.
16806 : : */
16807 : : void
16808 : 1388 : ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lockmode)
16809 : : {
16810 : : Relation target_rel;
16811 : : Relation class_rel;
16812 : : HeapTuple tuple;
16813 : : Form_pg_class tuple_class;
16814 : :
16815 : : /*
16816 : : * Get exclusive lock till end of transaction on the target table. Use
16817 : : * relation_open so that we can work on indexes and sequences.
16818 : : */
16819 : 1388 : target_rel = relation_open(relationOid, lockmode);
16820 : :
16821 : : /* Get its pg_class tuple, too */
16822 : 1388 : class_rel = table_open(RelationRelationId, RowExclusiveLock);
16823 : :
16824 : 1388 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relationOid));
16825 [ - + ]: 1388 : if (!HeapTupleIsValid(tuple))
16826 [ # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", relationOid);
16827 : 1388 : tuple_class = (Form_pg_class) GETSTRUCT(tuple);
16828 : :
16829 : : /* Can we change the ownership of this tuple? */
16830 [ + + + + : 1388 : switch (tuple_class->relkind)
+ + - ]
16831 : : {
16832 : 1154 : case RELKIND_RELATION:
16833 : : case RELKIND_VIEW:
16834 : : case RELKIND_MATVIEW:
16835 : : case RELKIND_FOREIGN_TABLE:
16836 : : case RELKIND_PARTITIONED_TABLE:
16837 : : case RELKIND_PROPGRAPH:
16838 : : /* ok to change owner */
16839 : 1154 : break;
16840 : 99 : case RELKIND_INDEX:
16841 [ - + ]: 99 : if (!recursing)
16842 : : {
16843 : : /*
16844 : : * Because ALTER INDEX OWNER used to be allowed, and in fact
16845 : : * is generated by old versions of pg_dump, we give a warning
16846 : : * and do nothing rather than erroring out. Also, to avoid
16847 : : * unnecessary chatter while restoring those old dumps, say
16848 : : * nothing at all if the command would be a no-op anyway.
16849 : : */
16850 [ # # ]: 0 : if (tuple_class->relowner != newOwnerId)
16851 [ # # ]: 0 : ereport(WARNING,
16852 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
16853 : : errmsg("cannot change owner of index \"%s\"",
16854 : : NameStr(tuple_class->relname)),
16855 : : errhint("Change the ownership of the index's table instead.")));
16856 : : /* quick hack to exit via the no-op path */
16857 : 0 : newOwnerId = tuple_class->relowner;
16858 : : }
16859 : 99 : break;
16860 : 14 : case RELKIND_PARTITIONED_INDEX:
16861 [ + - ]: 14 : if (recursing)
16862 : 14 : break;
16863 [ # # ]: 0 : ereport(ERROR,
16864 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
16865 : : errmsg("cannot change owner of index \"%s\"",
16866 : : NameStr(tuple_class->relname)),
16867 : : errhint("Change the ownership of the index's table instead.")));
16868 : : break;
16869 : 65 : case RELKIND_SEQUENCE:
16870 [ + + ]: 65 : if (!recursing &&
16871 [ - + ]: 35 : tuple_class->relowner != newOwnerId)
16872 : : {
16873 : : /* if it's an owned sequence, disallow changing it by itself */
16874 : : Oid tableId;
16875 : : int32 colId;
16876 : :
16877 [ # # # # ]: 0 : if (sequenceIsOwned(relationOid, DEPENDENCY_AUTO, &tableId, &colId) ||
16878 : 0 : sequenceIsOwned(relationOid, DEPENDENCY_INTERNAL, &tableId, &colId))
16879 [ # # ]: 0 : ereport(ERROR,
16880 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
16881 : : errmsg("cannot change owner of sequence \"%s\"",
16882 : : NameStr(tuple_class->relname)),
16883 : : errdetail("Sequence \"%s\" is linked to table \"%s\".",
16884 : : NameStr(tuple_class->relname),
16885 : : get_rel_name(tableId))));
16886 : : }
16887 : 65 : break;
16888 : 5 : case RELKIND_COMPOSITE_TYPE:
16889 [ + - ]: 5 : if (recursing)
16890 : 5 : break;
16891 [ # # ]: 0 : ereport(ERROR,
16892 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
16893 : : errmsg("\"%s\" is a composite type",
16894 : : NameStr(tuple_class->relname)),
16895 : : /* translator: %s is an SQL ALTER command */
16896 : : errhint("Use %s instead.",
16897 : : "ALTER TYPE")));
16898 : : break;
16899 : 51 : case RELKIND_TOASTVALUE:
16900 [ + - ]: 51 : if (recursing)
16901 : 51 : break;
16902 : : pg_fallthrough;
16903 : : default:
16904 [ # # ]: 0 : ereport(ERROR,
16905 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
16906 : : errmsg("cannot change owner of relation \"%s\"",
16907 : : NameStr(tuple_class->relname)),
16908 : : errdetail_relkind_not_supported(tuple_class->relkind)));
16909 : : }
16910 : :
16911 : : /*
16912 : : * If the new owner is the same as the existing owner, consider the
16913 : : * command to have succeeded. This is for dump restoration purposes.
16914 : : */
16915 [ + + ]: 1388 : if (tuple_class->relowner != newOwnerId)
16916 : : {
16917 : : Datum repl_val[Natts_pg_class];
16918 : : bool repl_null[Natts_pg_class];
16919 : : bool repl_repl[Natts_pg_class];
16920 : : Acl *newAcl;
16921 : : Datum aclDatum;
16922 : : bool isNull;
16923 : : HeapTuple newtuple;
16924 : :
16925 : : /* skip permission checks when recursing to index or toast table */
16926 [ + + ]: 452 : if (!recursing)
16927 : : {
16928 : : /* Superusers can always do it */
16929 [ + + ]: 219 : if (!superuser())
16930 : : {
16931 : 28 : Oid namespaceOid = tuple_class->relnamespace;
16932 : : AclResult aclresult;
16933 : :
16934 : : /* Otherwise, must be owner of the existing object */
16935 [ - + ]: 28 : if (!object_ownercheck(RelationRelationId, relationOid, GetUserId()))
16936 : 0 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relationOid)),
16937 : 0 : RelationGetRelationName(target_rel));
16938 : :
16939 : : /* Must be able to become new owner */
16940 : 28 : check_can_set_role(GetUserId(), newOwnerId);
16941 : :
16942 : : /* New owner must have CREATE privilege on namespace */
16943 : 20 : aclresult = object_aclcheck(NamespaceRelationId, namespaceOid, newOwnerId,
16944 : : ACL_CREATE);
16945 [ - + ]: 20 : if (aclresult != ACLCHECK_OK)
16946 : 0 : aclcheck_error(aclresult, OBJECT_SCHEMA,
16947 : 0 : get_namespace_name(namespaceOid));
16948 : : }
16949 : : }
16950 : :
16951 : 444 : memset(repl_null, false, sizeof(repl_null));
16952 : 444 : memset(repl_repl, false, sizeof(repl_repl));
16953 : :
16954 : 444 : repl_repl[Anum_pg_class_relowner - 1] = true;
16955 : 444 : repl_val[Anum_pg_class_relowner - 1] = ObjectIdGetDatum(newOwnerId);
16956 : :
16957 : : /*
16958 : : * Determine the modified ACL for the new owner. This is only
16959 : : * necessary when the ACL is non-null.
16960 : : */
16961 : 444 : aclDatum = SysCacheGetAttr(RELOID, tuple,
16962 : : Anum_pg_class_relacl,
16963 : : &isNull);
16964 [ + + ]: 444 : if (!isNull)
16965 : : {
16966 : 60 : newAcl = aclnewowner(DatumGetAclP(aclDatum),
16967 : : tuple_class->relowner, newOwnerId);
16968 : 60 : repl_repl[Anum_pg_class_relacl - 1] = true;
16969 : 60 : repl_val[Anum_pg_class_relacl - 1] = PointerGetDatum(newAcl);
16970 : : }
16971 : :
16972 : 444 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(class_rel), repl_val, repl_null, repl_repl);
16973 : :
16974 : 444 : CatalogTupleUpdate(class_rel, &newtuple->t_self, newtuple);
16975 : :
16976 : 444 : heap_freetuple(newtuple);
16977 : :
16978 : : /*
16979 : : * We must similarly update any per-column ACLs to reflect the new
16980 : : * owner; for neatness reasons that's split out as a subroutine.
16981 : : */
16982 : 444 : change_owner_fix_column_acls(relationOid,
16983 : : tuple_class->relowner,
16984 : : newOwnerId);
16985 : :
16986 : : /*
16987 : : * Update owner dependency reference, if any. A composite type has
16988 : : * none, because it's tracked for the pg_type entry instead of here;
16989 : : * indexes and TOAST tables don't have their own entries either.
16990 : : */
16991 [ + + ]: 444 : if (tuple_class->relkind != RELKIND_COMPOSITE_TYPE &&
16992 [ + + ]: 439 : tuple_class->relkind != RELKIND_INDEX &&
16993 [ + + ]: 340 : tuple_class->relkind != RELKIND_PARTITIONED_INDEX &&
16994 [ + + ]: 326 : tuple_class->relkind != RELKIND_TOASTVALUE)
16995 : 275 : changeDependencyOnOwner(RelationRelationId, relationOid,
16996 : : newOwnerId);
16997 : :
16998 : : /*
16999 : : * Also change the ownership of the table's row type, if it has one
17000 : : */
17001 [ + + ]: 444 : if (OidIsValid(tuple_class->reltype))
17002 : 255 : AlterTypeOwnerInternal(tuple_class->reltype, newOwnerId);
17003 : :
17004 : : /*
17005 : : * If we are operating on a table or materialized view, also change
17006 : : * the ownership of any indexes and sequences that belong to the
17007 : : * relation, as well as its toast table (if it has one).
17008 : : */
17009 [ + + ]: 444 : if (tuple_class->relkind == RELKIND_RELATION ||
17010 [ + + ]: 245 : tuple_class->relkind == RELKIND_PARTITIONED_TABLE ||
17011 [ + - ]: 208 : tuple_class->relkind == RELKIND_MATVIEW ||
17012 [ + + ]: 208 : tuple_class->relkind == RELKIND_TOASTVALUE)
17013 : : {
17014 : : List *index_oid_list;
17015 : : ListCell *i;
17016 : :
17017 : : /* Find all the indexes belonging to this relation */
17018 : 287 : index_oid_list = RelationGetIndexList(target_rel);
17019 : :
17020 : : /* For each index, recursively change its ownership */
17021 [ + + + + : 400 : foreach(i, index_oid_list)
+ + ]
17022 : 113 : ATExecChangeOwner(lfirst_oid(i), newOwnerId, true, lockmode);
17023 : :
17024 : 287 : list_free(index_oid_list);
17025 : : }
17026 : :
17027 : : /* If it has a toast table, recurse to change its ownership */
17028 [ + + ]: 444 : if (tuple_class->reltoastrelid != InvalidOid)
17029 : 51 : ATExecChangeOwner(tuple_class->reltoastrelid, newOwnerId,
17030 : : true, lockmode);
17031 : :
17032 : : /* If it has dependent sequences, recurse to change them too */
17033 : 444 : change_owner_recurse_to_sequences(relationOid, newOwnerId, lockmode);
17034 : : }
17035 : :
17036 [ - + ]: 1380 : InvokeObjectPostAlterHook(RelationRelationId, relationOid, 0);
17037 : :
17038 : 1380 : ReleaseSysCache(tuple);
17039 : 1380 : table_close(class_rel, RowExclusiveLock);
17040 : 1380 : relation_close(target_rel, NoLock);
17041 : 1380 : }
17042 : :
17043 : : /*
17044 : : * change_owner_fix_column_acls
17045 : : *
17046 : : * Helper function for ATExecChangeOwner. Scan the columns of the table
17047 : : * and fix any non-null column ACLs to reflect the new owner.
17048 : : */
17049 : : static void
17050 : 444 : change_owner_fix_column_acls(Oid relationOid, Oid oldOwnerId, Oid newOwnerId)
17051 : : {
17052 : : Relation attRelation;
17053 : : SysScanDesc scan;
17054 : : ScanKeyData key[1];
17055 : : HeapTuple attributeTuple;
17056 : :
17057 : 444 : attRelation = table_open(AttributeRelationId, RowExclusiveLock);
17058 : 444 : ScanKeyInit(&key[0],
17059 : : Anum_pg_attribute_attrelid,
17060 : : BTEqualStrategyNumber, F_OIDEQ,
17061 : : ObjectIdGetDatum(relationOid));
17062 : 444 : scan = systable_beginscan(attRelation, AttributeRelidNumIndexId,
17063 : : true, NULL, 1, key);
17064 [ + + ]: 3229 : while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
17065 : : {
17066 : 2785 : Form_pg_attribute att = (Form_pg_attribute) GETSTRUCT(attributeTuple);
17067 : : Datum repl_val[Natts_pg_attribute];
17068 : : bool repl_null[Natts_pg_attribute];
17069 : : bool repl_repl[Natts_pg_attribute];
17070 : : Acl *newAcl;
17071 : : Datum aclDatum;
17072 : : bool isNull;
17073 : : HeapTuple newtuple;
17074 : :
17075 : : /* Ignore dropped columns */
17076 [ - + ]: 2785 : if (att->attisdropped)
17077 : 2784 : continue;
17078 : :
17079 : 2785 : aclDatum = heap_getattr(attributeTuple,
17080 : : Anum_pg_attribute_attacl,
17081 : : RelationGetDescr(attRelation),
17082 : : &isNull);
17083 : : /* Null ACLs do not require changes */
17084 [ + + ]: 2785 : if (isNull)
17085 : 2784 : continue;
17086 : :
17087 : 1 : memset(repl_null, false, sizeof(repl_null));
17088 : 1 : memset(repl_repl, false, sizeof(repl_repl));
17089 : :
17090 : 1 : newAcl = aclnewowner(DatumGetAclP(aclDatum),
17091 : : oldOwnerId, newOwnerId);
17092 : 1 : repl_repl[Anum_pg_attribute_attacl - 1] = true;
17093 : 1 : repl_val[Anum_pg_attribute_attacl - 1] = PointerGetDatum(newAcl);
17094 : :
17095 : 1 : newtuple = heap_modify_tuple(attributeTuple,
17096 : : RelationGetDescr(attRelation),
17097 : : repl_val, repl_null, repl_repl);
17098 : :
17099 : 1 : CatalogTupleUpdate(attRelation, &newtuple->t_self, newtuple);
17100 : :
17101 : 1 : heap_freetuple(newtuple);
17102 : : }
17103 : 444 : systable_endscan(scan);
17104 : 444 : table_close(attRelation, RowExclusiveLock);
17105 : 444 : }
17106 : :
17107 : : /*
17108 : : * change_owner_recurse_to_sequences
17109 : : *
17110 : : * Helper function for ATExecChangeOwner. Examines pg_depend searching
17111 : : * for sequences that are dependent on serial columns, and changes their
17112 : : * ownership.
17113 : : */
17114 : : static void
17115 : 444 : change_owner_recurse_to_sequences(Oid relationOid, Oid newOwnerId, LOCKMODE lockmode)
17116 : : {
17117 : : Relation depRel;
17118 : : SysScanDesc scan;
17119 : : ScanKeyData key[2];
17120 : : HeapTuple tup;
17121 : :
17122 : : /*
17123 : : * SERIAL sequences are those having an auto dependency on one of the
17124 : : * table's columns (we don't care *which* column, exactly).
17125 : : */
17126 : 444 : depRel = table_open(DependRelationId, AccessShareLock);
17127 : :
17128 : 444 : ScanKeyInit(&key[0],
17129 : : Anum_pg_depend_refclassid,
17130 : : BTEqualStrategyNumber, F_OIDEQ,
17131 : : ObjectIdGetDatum(RelationRelationId));
17132 : 444 : ScanKeyInit(&key[1],
17133 : : Anum_pg_depend_refobjid,
17134 : : BTEqualStrategyNumber, F_OIDEQ,
17135 : : ObjectIdGetDatum(relationOid));
17136 : : /* we leave refobjsubid unspecified */
17137 : :
17138 : 444 : scan = systable_beginscan(depRel, DependReferenceIndexId, true,
17139 : : NULL, 2, key);
17140 : :
17141 [ + + ]: 1408 : while (HeapTupleIsValid(tup = systable_getnext(scan)))
17142 : : {
17143 : 964 : Form_pg_depend depForm = (Form_pg_depend) GETSTRUCT(tup);
17144 : : Relation seqRel;
17145 : :
17146 : : /* skip dependencies other than auto dependencies on columns */
17147 [ + + ]: 964 : if (depForm->refobjsubid == 0 ||
17148 [ + + ]: 415 : depForm->classid != RelationRelationId ||
17149 [ + - ]: 139 : depForm->objsubid != 0 ||
17150 [ + + - + ]: 139 : !(depForm->deptype == DEPENDENCY_AUTO || depForm->deptype == DEPENDENCY_INTERNAL))
17151 : 825 : continue;
17152 : :
17153 : : /* Use relation_open just in case it's an index */
17154 : 139 : seqRel = relation_open(depForm->objid, lockmode);
17155 : :
17156 : : /* skip non-sequence relations */
17157 [ + + ]: 139 : if (RelationGetForm(seqRel)->relkind != RELKIND_SEQUENCE)
17158 : : {
17159 : : /* No need to keep the lock */
17160 : 122 : relation_close(seqRel, lockmode);
17161 : 122 : continue;
17162 : : }
17163 : :
17164 : : /* We don't need to close the sequence while we alter it. */
17165 : 17 : ATExecChangeOwner(depForm->objid, newOwnerId, true, lockmode);
17166 : :
17167 : : /* Now we can close it. Keep the lock till end of transaction. */
17168 : 17 : relation_close(seqRel, NoLock);
17169 : : }
17170 : :
17171 : 444 : systable_endscan(scan);
17172 : :
17173 : 444 : relation_close(depRel, AccessShareLock);
17174 : 444 : }
17175 : :
17176 : : /*
17177 : : * ALTER TABLE CLUSTER ON
17178 : : *
17179 : : * The only thing we have to do is to change the indisclustered bits.
17180 : : *
17181 : : * Return the address of the new clustering index.
17182 : : */
17183 : : static ObjectAddress
17184 : 39 : ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode)
17185 : : {
17186 : : Oid indexOid;
17187 : : ObjectAddress address;
17188 : :
17189 : 39 : indexOid = get_relname_relid(indexName, rel->rd_rel->relnamespace);
17190 : :
17191 [ - + ]: 39 : if (!OidIsValid(indexOid))
17192 [ # # ]: 0 : ereport(ERROR,
17193 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
17194 : : errmsg("index \"%s\" for table \"%s\" does not exist",
17195 : : indexName, RelationGetRelationName(rel))));
17196 : :
17197 : : /* Check index is valid to cluster on */
17198 : 39 : check_index_is_clusterable(rel, indexOid, lockmode);
17199 : :
17200 : : /* And do the work */
17201 : 39 : mark_index_clustered(rel, indexOid, false);
17202 : :
17203 : 39 : ObjectAddressSet(address,
17204 : : RelationRelationId, indexOid);
17205 : :
17206 : 39 : return address;
17207 : : }
17208 : :
17209 : : /*
17210 : : * ALTER TABLE SET WITHOUT CLUSTER
17211 : : *
17212 : : * We have to find any indexes on the table that have indisclustered bit
17213 : : * set and turn it off.
17214 : : */
17215 : : static void
17216 : 8 : ATExecDropCluster(Relation rel, LOCKMODE lockmode)
17217 : : {
17218 : 8 : mark_index_clustered(rel, InvalidOid, false);
17219 : 8 : }
17220 : :
17221 : : /*
17222 : : * Preparation phase for SET ACCESS METHOD
17223 : : *
17224 : : * Check that the access method exists and determine whether a change is
17225 : : * actually needed.
17226 : : */
17227 : : static void
17228 : 73 : ATPrepSetAccessMethod(AlteredTableInfo *tab, Relation rel, const char *amname)
17229 : : {
17230 : : Oid amoid;
17231 : :
17232 : : /*
17233 : : * Look up the access method name and check that it differs from the
17234 : : * table's current AM. If DEFAULT was specified for a partitioned table
17235 : : * (amname is NULL), set it to InvalidOid to reset the catalogued AM.
17236 : : */
17237 [ + + ]: 73 : if (amname != NULL)
17238 : 49 : amoid = get_table_am_oid(amname, false);
17239 [ + + ]: 24 : else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
17240 : 12 : amoid = InvalidOid;
17241 : : else
17242 : 12 : amoid = get_table_am_oid(default_table_access_method, false);
17243 : :
17244 : : /* if it's a match, phase 3 doesn't need to do anything */
17245 [ + + ]: 73 : if (rel->rd_rel->relam == amoid)
17246 : 8 : return;
17247 : :
17248 : : /* Save info for Phase 3 to do the real work */
17249 : 65 : tab->rewrite |= AT_REWRITE_ACCESS_METHOD;
17250 : 65 : tab->newAccessMethod = amoid;
17251 : 65 : tab->chgAccessMethod = true;
17252 : : }
17253 : :
17254 : : /*
17255 : : * Special handling of ALTER TABLE SET ACCESS METHOD for relations with no
17256 : : * storage that have an interest in preserving AM.
17257 : : *
17258 : : * Since these have no storage, setting the access method is a catalog only
17259 : : * operation.
17260 : : */
17261 : : static void
17262 : 29 : ATExecSetAccessMethodNoStorage(Relation rel, Oid newAccessMethodId)
17263 : : {
17264 : : Relation pg_class;
17265 : : Oid oldAccessMethodId;
17266 : : HeapTuple tuple;
17267 : : Form_pg_class rd_rel;
17268 : 29 : Oid reloid = RelationGetRelid(rel);
17269 : :
17270 : : /*
17271 : : * Shouldn't be called on relations having storage; these are processed in
17272 : : * phase 3.
17273 : : */
17274 : : Assert(!RELKIND_HAS_STORAGE(rel->rd_rel->relkind));
17275 : :
17276 : : /* Get a modifiable copy of the relation's pg_class row. */
17277 : 29 : pg_class = table_open(RelationRelationId, RowExclusiveLock);
17278 : :
17279 : 29 : tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid));
17280 [ - + ]: 29 : if (!HeapTupleIsValid(tuple))
17281 [ # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", reloid);
17282 : 29 : rd_rel = (Form_pg_class) GETSTRUCT(tuple);
17283 : :
17284 : : /* Update the pg_class row. */
17285 : 29 : oldAccessMethodId = rd_rel->relam;
17286 : 29 : rd_rel->relam = newAccessMethodId;
17287 : :
17288 : : /* Leave if no update required */
17289 [ - + ]: 29 : if (rd_rel->relam == oldAccessMethodId)
17290 : : {
17291 : 0 : heap_freetuple(tuple);
17292 : 0 : table_close(pg_class, RowExclusiveLock);
17293 : 0 : return;
17294 : : }
17295 : :
17296 : 29 : CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
17297 : :
17298 : : /*
17299 : : * Update the dependency on the new access method. No dependency is added
17300 : : * if the new access method is InvalidOid (default case). Be very careful
17301 : : * that this has to compare the previous value stored in pg_class with the
17302 : : * new one.
17303 : : */
17304 [ + + + - ]: 29 : if (!OidIsValid(oldAccessMethodId) && OidIsValid(rd_rel->relam))
17305 : 13 : {
17306 : : ObjectAddress relobj,
17307 : : referenced;
17308 : :
17309 : : /*
17310 : : * New access method is defined and there was no dependency
17311 : : * previously, so record a new one.
17312 : : */
17313 : 13 : ObjectAddressSet(relobj, RelationRelationId, reloid);
17314 : 13 : ObjectAddressSet(referenced, AccessMethodRelationId, rd_rel->relam);
17315 : 13 : recordDependencyOn(&relobj, &referenced, DEPENDENCY_NORMAL);
17316 : : }
17317 [ + - ]: 16 : else if (OidIsValid(oldAccessMethodId) &&
17318 [ + + ]: 16 : !OidIsValid(rd_rel->relam))
17319 : : {
17320 : : /*
17321 : : * There was an access method defined, and no new one, so just remove
17322 : : * the existing dependency.
17323 : : */
17324 : 8 : deleteDependencyRecordsForClass(RelationRelationId, reloid,
17325 : : AccessMethodRelationId,
17326 : : DEPENDENCY_NORMAL);
17327 : : }
17328 : : else
17329 : : {
17330 : : Assert(OidIsValid(oldAccessMethodId) &&
17331 : : OidIsValid(rd_rel->relam));
17332 : :
17333 : : /* Both are valid, so update the dependency */
17334 : 8 : changeDependencyFor(RelationRelationId, reloid,
17335 : : AccessMethodRelationId,
17336 : : oldAccessMethodId, rd_rel->relam);
17337 : : }
17338 : :
17339 : : /* make the relam and dependency changes visible */
17340 : 29 : CommandCounterIncrement();
17341 : :
17342 [ - + ]: 29 : InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);
17343 : :
17344 : 29 : heap_freetuple(tuple);
17345 : 29 : table_close(pg_class, RowExclusiveLock);
17346 : : }
17347 : :
17348 : : /*
17349 : : * ALTER TABLE SET TABLESPACE
17350 : : */
17351 : : static void
17352 : 105 : ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, const char *tablespacename, LOCKMODE lockmode)
17353 : : {
17354 : : Oid tablespaceId;
17355 : :
17356 : : /* Check that the tablespace exists */
17357 : 105 : tablespaceId = get_tablespace_oid(tablespacename, false);
17358 : :
17359 : : /* Check permissions except when moving to database's default */
17360 [ + - + + ]: 105 : if (OidIsValid(tablespaceId) && tablespaceId != MyDatabaseTableSpace)
17361 : : {
17362 : : AclResult aclresult;
17363 : :
17364 : 48 : aclresult = object_aclcheck(TableSpaceRelationId, tablespaceId, GetUserId(), ACL_CREATE);
17365 [ - + ]: 48 : if (aclresult != ACLCHECK_OK)
17366 : 0 : aclcheck_error(aclresult, OBJECT_TABLESPACE, tablespacename);
17367 : : }
17368 : :
17369 : : /* Save info for Phase 3 to do the real work */
17370 [ - + ]: 105 : if (OidIsValid(tab->newTableSpace))
17371 [ # # ]: 0 : ereport(ERROR,
17372 : : (errcode(ERRCODE_SYNTAX_ERROR),
17373 : : errmsg("cannot have multiple SET TABLESPACE subcommands")));
17374 : :
17375 : 105 : tab->newTableSpace = tablespaceId;
17376 : 105 : }
17377 : :
17378 : : /*
17379 : : * Set, reset, or replace reloptions.
17380 : : */
17381 : : static void
17382 : 632 : ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
17383 : : LOCKMODE lockmode)
17384 : : {
17385 : : Oid relid;
17386 : : Relation pgclass;
17387 : : HeapTuple tuple;
17388 : : HeapTuple newtuple;
17389 : : Datum datum;
17390 : : Datum newOptions;
17391 : : Datum repl_val[Natts_pg_class];
17392 : : bool repl_null[Natts_pg_class];
17393 : : bool repl_repl[Natts_pg_class];
17394 : 632 : const char *const validnsps[] = HEAP_RELOPT_NAMESPACES;
17395 : :
17396 [ + + - + ]: 632 : if (defList == NIL && operation != AT_ReplaceRelOptions)
17397 : 0 : return; /* nothing to do */
17398 : :
17399 : 632 : pgclass = table_open(RelationRelationId, RowExclusiveLock);
17400 : :
17401 : : /* Fetch heap tuple */
17402 : 632 : relid = RelationGetRelid(rel);
17403 : 632 : tuple = SearchSysCacheLocked1(RELOID, ObjectIdGetDatum(relid));
17404 [ - + ]: 632 : if (!HeapTupleIsValid(tuple))
17405 [ # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
17406 : :
17407 [ + + ]: 632 : if (operation == AT_ReplaceRelOptions)
17408 : : {
17409 : : /*
17410 : : * If we're supposed to replace the reloptions list, we just pretend
17411 : : * there were none before.
17412 : : */
17413 : 137 : datum = (Datum) 0;
17414 : : }
17415 : : else
17416 : : {
17417 : : bool isnull;
17418 : :
17419 : : /* Get the old reloptions */
17420 : 495 : datum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions,
17421 : : &isnull);
17422 [ + + ]: 495 : if (isnull)
17423 : 306 : datum = (Datum) 0;
17424 : : }
17425 : :
17426 : : /* Generate new proposed reloptions (text array) */
17427 : 632 : newOptions = transformRelOptions(datum, defList, NULL, validnsps, false,
17428 : : operation == AT_ResetRelOptions);
17429 : :
17430 : : /* Validate */
17431 [ + + + + : 628 : switch (rel->rd_rel->relkind)
- ]
17432 : : {
17433 : 348 : case RELKIND_RELATION:
17434 : : case RELKIND_MATVIEW:
17435 : 348 : (void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
17436 : 348 : break;
17437 : 4 : case RELKIND_PARTITIONED_TABLE:
17438 : 4 : (void) partitioned_table_reloptions(newOptions, true);
17439 : 0 : break;
17440 : 205 : case RELKIND_VIEW:
17441 : 205 : (void) view_reloptions(newOptions, true);
17442 : 193 : break;
17443 : 71 : case RELKIND_INDEX:
17444 : : case RELKIND_PARTITIONED_INDEX:
17445 : 71 : (void) index_reloptions(rel->rd_indam->amoptions, newOptions, true);
17446 : 57 : break;
17447 : 0 : case RELKIND_TOASTVALUE:
17448 : : /* fall through to error -- shouldn't ever get here */
17449 : : default:
17450 [ # # ]: 0 : ereport(ERROR,
17451 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17452 : : errmsg("cannot set options for relation \"%s\"",
17453 : : RelationGetRelationName(rel)),
17454 : : errdetail_relkind_not_supported(rel->rd_rel->relkind)));
17455 : : break;
17456 : : }
17457 : :
17458 : : /* Special-case validation of view options */
17459 [ + + ]: 598 : if (rel->rd_rel->relkind == RELKIND_VIEW)
17460 : : {
17461 : 193 : Query *view_query = get_view_query(rel);
17462 : 193 : List *view_options = untransformRelOptions(newOptions);
17463 : : ListCell *cell;
17464 : 193 : bool check_option = false;
17465 : :
17466 [ + + + + : 261 : foreach(cell, view_options)
+ + ]
17467 : : {
17468 : 68 : DefElem *defel = (DefElem *) lfirst(cell);
17469 : :
17470 [ + + ]: 68 : if (strcmp(defel->defname, "check_option") == 0)
17471 : 16 : check_option = true;
17472 : : }
17473 : :
17474 : : /*
17475 : : * If the check option is specified, look to see if the view is
17476 : : * actually auto-updatable or not.
17477 : : */
17478 [ + + ]: 193 : if (check_option)
17479 : : {
17480 : : const char *view_updatable_error =
17481 : 16 : view_query_is_auto_updatable(view_query, true);
17482 : :
17483 [ - + ]: 16 : if (view_updatable_error)
17484 [ # # ]: 0 : ereport(ERROR,
17485 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
17486 : : errmsg("WITH CHECK OPTION is supported only on automatically updatable views"),
17487 : : errhint("%s", _(view_updatable_error))));
17488 : : }
17489 : : }
17490 : :
17491 : : /*
17492 : : * All we need do here is update the pg_class row; the new options will be
17493 : : * propagated into relcaches during post-commit cache inval.
17494 : : */
17495 : 598 : memset(repl_val, 0, sizeof(repl_val));
17496 : 598 : memset(repl_null, false, sizeof(repl_null));
17497 : 598 : memset(repl_repl, false, sizeof(repl_repl));
17498 : :
17499 [ + + ]: 598 : if (newOptions != (Datum) 0)
17500 : 396 : repl_val[Anum_pg_class_reloptions - 1] = newOptions;
17501 : : else
17502 : 202 : repl_null[Anum_pg_class_reloptions - 1] = true;
17503 : :
17504 : 598 : repl_repl[Anum_pg_class_reloptions - 1] = true;
17505 : :
17506 : 598 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(pgclass),
17507 : : repl_val, repl_null, repl_repl);
17508 : :
17509 : 598 : CatalogTupleUpdate(pgclass, &newtuple->t_self, newtuple);
17510 : 598 : UnlockTuple(pgclass, &tuple->t_self, InplaceUpdateTupleLock);
17511 : :
17512 [ - + ]: 598 : InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);
17513 : :
17514 : 598 : heap_freetuple(newtuple);
17515 : :
17516 : 598 : ReleaseSysCache(tuple);
17517 : :
17518 : : /* repeat the whole exercise for the toast table, if there's one */
17519 [ + + ]: 598 : if (OidIsValid(rel->rd_rel->reltoastrelid))
17520 : : {
17521 : : Relation toastrel;
17522 : 182 : Oid toastid = rel->rd_rel->reltoastrelid;
17523 : :
17524 : 182 : toastrel = table_open(toastid, lockmode);
17525 : :
17526 : : /* Fetch heap tuple */
17527 : 182 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(toastid));
17528 [ - + ]: 182 : if (!HeapTupleIsValid(tuple))
17529 [ # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", toastid);
17530 : :
17531 [ - + ]: 182 : if (operation == AT_ReplaceRelOptions)
17532 : : {
17533 : : /*
17534 : : * If we're supposed to replace the reloptions list, we just
17535 : : * pretend there were none before.
17536 : : */
17537 : 0 : datum = (Datum) 0;
17538 : : }
17539 : : else
17540 : : {
17541 : : bool isnull;
17542 : :
17543 : : /* Get the old reloptions */
17544 : 182 : datum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions,
17545 : : &isnull);
17546 [ + + ]: 182 : if (isnull)
17547 : 158 : datum = (Datum) 0;
17548 : : }
17549 : :
17550 : 182 : newOptions = transformRelOptions(datum, defList, "toast", validnsps,
17551 : : false, operation == AT_ResetRelOptions);
17552 : :
17553 : 182 : (void) heap_reloptions(RELKIND_TOASTVALUE, newOptions, true);
17554 : :
17555 : 182 : memset(repl_val, 0, sizeof(repl_val));
17556 : 182 : memset(repl_null, false, sizeof(repl_null));
17557 : 182 : memset(repl_repl, false, sizeof(repl_repl));
17558 : :
17559 [ + + ]: 182 : if (newOptions != (Datum) 0)
17560 : 28 : repl_val[Anum_pg_class_reloptions - 1] = newOptions;
17561 : : else
17562 : 154 : repl_null[Anum_pg_class_reloptions - 1] = true;
17563 : :
17564 : 182 : repl_repl[Anum_pg_class_reloptions - 1] = true;
17565 : :
17566 : 182 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(pgclass),
17567 : : repl_val, repl_null, repl_repl);
17568 : :
17569 : 182 : CatalogTupleUpdate(pgclass, &newtuple->t_self, newtuple);
17570 : :
17571 [ - + ]: 182 : InvokeObjectPostAlterHookArg(RelationRelationId,
17572 : : RelationGetRelid(toastrel), 0,
17573 : : InvalidOid, true);
17574 : :
17575 : 182 : heap_freetuple(newtuple);
17576 : :
17577 : 182 : ReleaseSysCache(tuple);
17578 : :
17579 : 182 : table_close(toastrel, NoLock);
17580 : : }
17581 : :
17582 : 598 : table_close(pgclass, RowExclusiveLock);
17583 : : }
17584 : :
17585 : : /*
17586 : : * Execute ALTER TABLE SET TABLESPACE for cases where there is no tuple
17587 : : * rewriting to be done, so we just want to copy the data as fast as possible.
17588 : : */
17589 : : static void
17590 : 115 : ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
17591 : : {
17592 : : Relation rel;
17593 : : Oid reltoastrelid;
17594 : : RelFileNumber newrelfilenumber;
17595 : : RelFileLocator newrlocator;
17596 : 115 : List *reltoastidxids = NIL;
17597 : : ListCell *lc;
17598 : :
17599 : : /*
17600 : : * Need lock here in case we are recursing to toast table or index
17601 : : */
17602 : 115 : rel = relation_open(tableOid, lockmode);
17603 : :
17604 : : /* Check first if relation can be moved to new tablespace */
17605 [ + + ]: 115 : if (!CheckRelationTableSpaceMove(rel, newTableSpace))
17606 : : {
17607 [ - + ]: 5 : InvokeObjectPostAlterHook(RelationRelationId,
17608 : : RelationGetRelid(rel), 0);
17609 : 5 : relation_close(rel, NoLock);
17610 : 5 : return;
17611 : : }
17612 : :
17613 : 110 : reltoastrelid = rel->rd_rel->reltoastrelid;
17614 : : /* Fetch the list of indexes on toast relation if necessary */
17615 [ + + ]: 110 : if (OidIsValid(reltoastrelid))
17616 : : {
17617 : 17 : Relation toastRel = relation_open(reltoastrelid, lockmode);
17618 : :
17619 : 17 : reltoastidxids = RelationGetIndexList(toastRel);
17620 : 17 : relation_close(toastRel, lockmode);
17621 : : }
17622 : :
17623 : : /*
17624 : : * Relfilenumbers are not unique in databases across tablespaces, so we
17625 : : * need to allocate a new one in the new tablespace.
17626 : : */
17627 : 110 : newrelfilenumber = GetNewRelFileNumber(newTableSpace, NULL,
17628 : 110 : rel->rd_rel->relpersistence);
17629 : :
17630 : : /* Open old and new relation */
17631 : 110 : newrlocator = rel->rd_locator;
17632 : 110 : newrlocator.relNumber = newrelfilenumber;
17633 : 110 : newrlocator.spcOid = newTableSpace;
17634 : :
17635 : : /* hand off to AM to actually create new rel storage and copy the data */
17636 [ + + ]: 110 : if (rel->rd_rel->relkind == RELKIND_INDEX)
17637 : : {
17638 : 40 : index_copy_data(rel, newrlocator);
17639 : : }
17640 : : else
17641 : : {
17642 : : Assert(RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind));
17643 : 70 : table_relation_copy_data(rel, &newrlocator);
17644 : : }
17645 : :
17646 : : /*
17647 : : * Update the pg_class row.
17648 : : *
17649 : : * NB: This wouldn't work if ATExecSetTableSpace() were allowed to be
17650 : : * executed on pg_class or its indexes (the above copy wouldn't contain
17651 : : * the updated pg_class entry), but that's forbidden with
17652 : : * CheckRelationTableSpaceMove().
17653 : : */
17654 : 110 : SetRelationTableSpace(rel, newTableSpace, newrelfilenumber);
17655 : :
17656 [ - + ]: 110 : InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);
17657 : :
17658 : 110 : RelationAssumeNewRelfilelocator(rel);
17659 : :
17660 : 110 : relation_close(rel, NoLock);
17661 : :
17662 : : /* Make sure the reltablespace change is visible */
17663 : 110 : CommandCounterIncrement();
17664 : :
17665 : : /* Move associated toast relation and/or indexes, too */
17666 [ + + ]: 110 : if (OidIsValid(reltoastrelid))
17667 : 17 : ATExecSetTableSpace(reltoastrelid, newTableSpace, lockmode);
17668 [ + + + + : 127 : foreach(lc, reltoastidxids)
+ + ]
17669 : 17 : ATExecSetTableSpace(lfirst_oid(lc), newTableSpace, lockmode);
17670 : :
17671 : : /* Clean up */
17672 : 110 : list_free(reltoastidxids);
17673 : : }
17674 : :
17675 : : /*
17676 : : * Special handling of ALTER TABLE SET TABLESPACE for relations with no
17677 : : * storage that have an interest in preserving tablespace.
17678 : : *
17679 : : * Since these have no storage the tablespace can be updated with a simple
17680 : : * metadata only operation to update the tablespace.
17681 : : */
17682 : : static void
17683 : 24 : ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace)
17684 : : {
17685 : : /*
17686 : : * Shouldn't be called on relations having storage; these are processed in
17687 : : * phase 3.
17688 : : */
17689 : : Assert(!RELKIND_HAS_STORAGE(rel->rd_rel->relkind));
17690 : :
17691 : : /* check if relation can be moved to its new tablespace */
17692 [ - + ]: 24 : if (!CheckRelationTableSpaceMove(rel, newTableSpace))
17693 : : {
17694 [ # # ]: 0 : InvokeObjectPostAlterHook(RelationRelationId,
17695 : : RelationGetRelid(rel),
17696 : : 0);
17697 : 0 : return;
17698 : : }
17699 : :
17700 : : /* Update can be done, so change reltablespace */
17701 : 20 : SetRelationTableSpace(rel, newTableSpace, InvalidOid);
17702 : :
17703 [ - + ]: 20 : InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);
17704 : :
17705 : : /* Make sure the reltablespace change is visible */
17706 : 20 : CommandCounterIncrement();
17707 : : }
17708 : :
17709 : : /*
17710 : : * Alter Table ALL ... SET TABLESPACE
17711 : : *
17712 : : * Allows a user to move all objects of some type in a given tablespace in the
17713 : : * current database to another tablespace. Objects can be chosen based on the
17714 : : * owner of the object also, to allow users to move only their objects.
17715 : : * The user must have CREATE rights on the new tablespace, as usual. The main
17716 : : * permissions handling is done by the lower-level table move function.
17717 : : *
17718 : : * All to-be-moved objects are locked first. If NOWAIT is specified and the
17719 : : * lock can't be acquired then we ereport(ERROR).
17720 : : */
17721 : : Oid
17722 : 16 : AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
17723 : : {
17724 : 16 : List *relations = NIL;
17725 : : ListCell *l;
17726 : : ScanKeyData key[1];
17727 : : Relation rel;
17728 : : TableScanDesc scan;
17729 : : HeapTuple tuple;
17730 : : Oid orig_tablespaceoid;
17731 : : Oid new_tablespaceoid;
17732 : 16 : List *role_oids = roleSpecsToIds(stmt->roles);
17733 : :
17734 : : /* Ensure we were not asked to move something we can't */
17735 [ + + + + ]: 16 : if (stmt->objtype != OBJECT_TABLE && stmt->objtype != OBJECT_INDEX &&
17736 [ - + ]: 6 : stmt->objtype != OBJECT_MATVIEW)
17737 [ # # ]: 0 : ereport(ERROR,
17738 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
17739 : : errmsg("only tables, indexes, and materialized views exist in tablespaces")));
17740 : :
17741 : : /* Get the orig and new tablespace OIDs */
17742 : 16 : orig_tablespaceoid = get_tablespace_oid(stmt->orig_tablespacename, false);
17743 : 16 : new_tablespaceoid = get_tablespace_oid(stmt->new_tablespacename, false);
17744 : :
17745 : : /* Can't move shared relations in to or out of pg_global */
17746 : : /* This is also checked by ATExecSetTableSpace, but nice to stop earlier */
17747 [ + - - + ]: 16 : if (orig_tablespaceoid == GLOBALTABLESPACE_OID ||
17748 : : new_tablespaceoid == GLOBALTABLESPACE_OID)
17749 [ # # ]: 0 : ereport(ERROR,
17750 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
17751 : : errmsg("cannot move relations in to or out of pg_global tablespace")));
17752 : :
17753 : : /*
17754 : : * Must have CREATE rights on the new tablespace, unless it is the
17755 : : * database default tablespace (which all users implicitly have CREATE
17756 : : * rights on).
17757 : : */
17758 [ + - + + ]: 16 : if (OidIsValid(new_tablespaceoid) && new_tablespaceoid != MyDatabaseTableSpace)
17759 : : {
17760 : : AclResult aclresult;
17761 : :
17762 : 1 : aclresult = object_aclcheck(TableSpaceRelationId, new_tablespaceoid, GetUserId(),
17763 : : ACL_CREATE);
17764 [ - + ]: 1 : if (aclresult != ACLCHECK_OK)
17765 : 0 : aclcheck_error(aclresult, OBJECT_TABLESPACE,
17766 : 0 : get_tablespace_name(new_tablespaceoid));
17767 : : }
17768 : :
17769 : : /*
17770 : : * Now that the checks are done, check if we should set either to
17771 : : * InvalidOid because it is our database's default tablespace.
17772 : : */
17773 [ + + ]: 16 : if (orig_tablespaceoid == MyDatabaseTableSpace)
17774 : 1 : orig_tablespaceoid = InvalidOid;
17775 : :
17776 [ + + ]: 16 : if (new_tablespaceoid == MyDatabaseTableSpace)
17777 : 15 : new_tablespaceoid = InvalidOid;
17778 : :
17779 : : /* no-op */
17780 [ - + ]: 16 : if (orig_tablespaceoid == new_tablespaceoid)
17781 : 0 : return new_tablespaceoid;
17782 : :
17783 : : /*
17784 : : * Walk the list of objects in the tablespace and move them. This will
17785 : : * only find objects in our database, of course.
17786 : : */
17787 : 16 : ScanKeyInit(&key[0],
17788 : : Anum_pg_class_reltablespace,
17789 : : BTEqualStrategyNumber, F_OIDEQ,
17790 : : ObjectIdGetDatum(orig_tablespaceoid));
17791 : :
17792 : 16 : rel = table_open(RelationRelationId, AccessShareLock);
17793 : 16 : scan = table_beginscan_catalog(rel, 1, key);
17794 [ + + ]: 478 : while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
17795 : : {
17796 : 462 : Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
17797 : 462 : Oid relOid = relForm->oid;
17798 : :
17799 : : /*
17800 : : * Do not move objects in pg_catalog as part of this, if an admin
17801 : : * really wishes to do so, they can issue the individual ALTER
17802 : : * commands directly.
17803 : : *
17804 : : * Also, explicitly avoid any shared tables, temp tables, TOAST (TOAST
17805 : : * will be moved with the main table).
17806 : : *
17807 : : * Conflict log tables are system-managed for logical replication and
17808 : : * cannot be altered directly, so skip them as well; otherwise a
17809 : : * single such table in the source tablespace would abort the whole
17810 : : * bulk move.
17811 : : */
17812 [ + + ]: 462 : if (IsCatalogNamespace(relForm->relnamespace) ||
17813 [ + - + - ]: 404 : relForm->relisshared ||
17814 [ + + ]: 404 : isAnyTempNamespace(relForm->relnamespace) ||
17815 [ + + ]: 334 : IsToastNamespace(relForm->relnamespace) ||
17816 : 132 : IsConflictLogTableNamespace(relForm->relnamespace))
17817 : 331 : continue;
17818 : :
17819 : : /* Only move the object type requested */
17820 [ + + ]: 131 : if ((stmt->objtype == OBJECT_TABLE &&
17821 [ + + ]: 110 : relForm->relkind != RELKIND_RELATION &&
17822 [ - + ]: 93 : relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
17823 [ + + ]: 38 : (stmt->objtype == OBJECT_INDEX &&
17824 [ + + ]: 18 : relForm->relkind != RELKIND_INDEX &&
17825 [ - + ]: 3 : relForm->relkind != RELKIND_PARTITIONED_INDEX) ||
17826 [ + + ]: 35 : (stmt->objtype == OBJECT_MATVIEW &&
17827 [ - + ]: 3 : relForm->relkind != RELKIND_MATVIEW))
17828 : 96 : continue;
17829 : :
17830 : : /* Check if we are only moving objects owned by certain roles */
17831 [ - + - - ]: 35 : if (role_oids != NIL && !list_member_oid(role_oids, relForm->relowner))
17832 : 0 : continue;
17833 : :
17834 : : /*
17835 : : * Handle permissions-checking here since we are locking the tables
17836 : : * and also to avoid doing a bunch of work only to fail part-way. Note
17837 : : * that permissions will also be checked by AlterTableInternal().
17838 : : *
17839 : : * Caller must be considered an owner on the table to move it.
17840 : : */
17841 [ - + ]: 35 : if (!object_ownercheck(RelationRelationId, relOid, GetUserId()))
17842 : 0 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relOid)),
17843 : 0 : NameStr(relForm->relname));
17844 : :
17845 [ - + ]: 35 : if (stmt->nowait &&
17846 [ # # ]: 0 : !ConditionalLockRelationOid(relOid, AccessExclusiveLock))
17847 [ # # ]: 0 : ereport(ERROR,
17848 : : (errcode(ERRCODE_OBJECT_IN_USE),
17849 : : errmsg("aborting because lock on relation \"%s.%s\" is not available",
17850 : : get_namespace_name(relForm->relnamespace),
17851 : : NameStr(relForm->relname))));
17852 : : else
17853 : 35 : LockRelationOid(relOid, AccessExclusiveLock);
17854 : :
17855 : : /* Add to our list of objects to move */
17856 : 35 : relations = lappend_oid(relations, relOid);
17857 : : }
17858 : :
17859 : 16 : table_endscan(scan);
17860 : 16 : table_close(rel, AccessShareLock);
17861 : :
17862 [ + + ]: 16 : if (relations == NIL)
17863 [ + - + - ]: 6 : ereport(NOTICE,
17864 : : (errcode(ERRCODE_NO_DATA_FOUND),
17865 : : errmsg("no matching relations in tablespace \"%s\" found",
17866 : : orig_tablespaceoid == InvalidOid ? "(database default)" :
17867 : : get_tablespace_name(orig_tablespaceoid))));
17868 : :
17869 : : /* Everything is locked, loop through and move all of the relations. */
17870 [ + + + + : 51 : foreach(l, relations)
+ + ]
17871 : : {
17872 : 35 : List *cmds = NIL;
17873 : 35 : AlterTableCmd *cmd = makeNode(AlterTableCmd);
17874 : :
17875 : 35 : cmd->subtype = AT_SetTableSpace;
17876 : 35 : cmd->name = stmt->new_tablespacename;
17877 : :
17878 : 35 : cmds = lappend(cmds, cmd);
17879 : :
17880 : 35 : EventTriggerAlterTableStart((Node *) stmt);
17881 : : /* OID is set by AlterTableInternal */
17882 : 35 : AlterTableInternal(lfirst_oid(l), cmds, false);
17883 : 35 : EventTriggerAlterTableEnd();
17884 : : }
17885 : :
17886 : 16 : return new_tablespaceoid;
17887 : : }
17888 : :
17889 : : static void
17890 : 40 : index_copy_data(Relation rel, RelFileLocator newrlocator)
17891 : : {
17892 : : SMgrRelation dstrel;
17893 : :
17894 : : /*
17895 : : * Since we copy the file directly without looking at the shared buffers,
17896 : : * we'd better first flush out any pages of the source relation that are
17897 : : * in shared buffers. We assume no new changes will be made while we are
17898 : : * holding exclusive lock on the rel.
17899 : : */
17900 : 40 : FlushRelationBuffers(rel);
17901 : :
17902 : : /*
17903 : : * Create and copy all forks of the relation, and schedule unlinking of
17904 : : * old physical files.
17905 : : *
17906 : : * NOTE: any conflict in relfilenumber value will be caught in
17907 : : * RelationCreateStorage().
17908 : : */
17909 : 40 : dstrel = RelationCreateStorage(newrlocator, rel->rd_rel->relpersistence, true);
17910 : :
17911 : : /* copy main fork */
17912 : 40 : RelationCopyStorage(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
17913 : 40 : rel->rd_rel->relpersistence);
17914 : :
17915 : : /* copy those extra forks that exist */
17916 : 40 : for (ForkNumber forkNum = MAIN_FORKNUM + 1;
17917 [ + + ]: 160 : forkNum <= MAX_FORKNUM; forkNum++)
17918 : : {
17919 [ - + ]: 120 : if (smgrexists(RelationGetSmgr(rel), forkNum))
17920 : : {
17921 : 0 : smgrcreate(dstrel, forkNum, false);
17922 : :
17923 : : /*
17924 : : * WAL log creation if the relation is persistent, or this is the
17925 : : * init fork of an unlogged relation.
17926 : : */
17927 [ # # ]: 0 : if (RelationIsPermanent(rel) ||
17928 [ # # # # ]: 0 : (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
17929 : : forkNum == INIT_FORKNUM))
17930 : 0 : log_smgrcreate(&newrlocator, forkNum);
17931 : 0 : RelationCopyStorage(RelationGetSmgr(rel), dstrel, forkNum,
17932 : 0 : rel->rd_rel->relpersistence);
17933 : : }
17934 : : }
17935 : :
17936 : : /* drop old relation, and close new one */
17937 : 40 : RelationDropStorage(rel);
17938 : 40 : smgrclose(dstrel);
17939 : 40 : }
17940 : :
17941 : : /*
17942 : : * ALTER TABLE ENABLE/DISABLE TRIGGER
17943 : : *
17944 : : * We just pass this off to trigger.c.
17945 : : */
17946 : : static void
17947 : 191 : ATExecEnableDisableTrigger(Relation rel, const char *trigname,
17948 : : char fires_when, bool skip_system, bool recurse,
17949 : : LOCKMODE lockmode)
17950 : : {
17951 : 191 : EnableDisableTrigger(rel, trigname, InvalidOid,
17952 : : fires_when, skip_system, recurse,
17953 : : lockmode);
17954 : :
17955 [ + + ]: 191 : InvokeObjectPostAlterHook(RelationRelationId,
17956 : : RelationGetRelid(rel), 0);
17957 : 191 : }
17958 : :
17959 : : /*
17960 : : * ALTER TABLE ENABLE/DISABLE RULE
17961 : : *
17962 : : * We just pass this off to rewriteDefine.c.
17963 : : */
17964 : : static void
17965 : 29 : ATExecEnableDisableRule(Relation rel, const char *rulename,
17966 : : char fires_when, LOCKMODE lockmode)
17967 : : {
17968 : 29 : EnableDisableRule(rel, rulename, fires_when);
17969 : :
17970 [ + + ]: 29 : InvokeObjectPostAlterHook(RelationRelationId,
17971 : : RelationGetRelid(rel), 0);
17972 : 29 : }
17973 : :
17974 : : /*
17975 : : * Preparation phase of [NO] INHERIT
17976 : : *
17977 : : * Check the relation defined as a child.
17978 : : */
17979 : : static void
17980 : 386 : ATPrepChangeInherit(Relation child_rel)
17981 : : {
17982 [ + + ]: 386 : if (child_rel->rd_rel->reloftype)
17983 [ + - ]: 8 : ereport(ERROR,
17984 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17985 : : errmsg("cannot change inheritance of typed table")));
17986 : :
17987 [ + + ]: 378 : if (child_rel->rd_rel->relispartition)
17988 [ + - ]: 12 : ereport(ERROR,
17989 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17990 : : errmsg("cannot change inheritance of a partition")));
17991 : 366 : }
17992 : :
17993 : : /*
17994 : : * ALTER TABLE INHERIT
17995 : : *
17996 : : * Return the address of the new parent relation.
17997 : : */
17998 : : static ObjectAddress
17999 : 297 : ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
18000 : : {
18001 : : Relation parent_rel;
18002 : : List *children;
18003 : : ObjectAddress address;
18004 : : const char *trigger_name;
18005 : :
18006 : : /*
18007 : : * A self-exclusive lock is needed here. See the similar case in
18008 : : * MergeAttributes() for a full explanation.
18009 : : */
18010 : 297 : parent_rel = table_openrv(parent, ShareUpdateExclusiveLock);
18011 : :
18012 : : /*
18013 : : * Must be owner of both parent and child -- child was checked by
18014 : : * ATSimplePermissions call in ATPrepCmd
18015 : : */
18016 : 297 : ATSimplePermissions(AT_AddInherit, parent_rel,
18017 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
18018 : :
18019 : : /* Permanent rels cannot inherit from temporary ones */
18020 [ + + ]: 297 : if (parent_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
18021 [ - + ]: 4 : child_rel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
18022 [ # # ]: 0 : ereport(ERROR,
18023 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18024 : : errmsg("cannot inherit from temporary relation \"%s\"",
18025 : : RelationGetRelationName(parent_rel))));
18026 : :
18027 : : /* If parent rel is temp, it must belong to this session */
18028 [ + + - + ]: 297 : if (RELATION_IS_OTHER_TEMP(parent_rel))
18029 [ # # ]: 0 : ereport(ERROR,
18030 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18031 : : errmsg("cannot inherit from temporary relation of another session")));
18032 : :
18033 : : /* Ditto for the child */
18034 [ + + - + ]: 297 : if (RELATION_IS_OTHER_TEMP(child_rel))
18035 [ # # ]: 0 : ereport(ERROR,
18036 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18037 : : errmsg("cannot inherit to temporary relation of another session")));
18038 : :
18039 : : /* Prevent partitioned tables from becoming inheritance parents */
18040 [ + + ]: 297 : if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
18041 [ + - ]: 4 : ereport(ERROR,
18042 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18043 : : errmsg("cannot inherit from partitioned table \"%s\"",
18044 : : parent->relname)));
18045 : :
18046 : : /* Likewise for partitions */
18047 [ + + ]: 293 : if (parent_rel->rd_rel->relispartition)
18048 [ + - ]: 4 : ereport(ERROR,
18049 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18050 : : errmsg("cannot inherit from a partition")));
18051 : :
18052 : : /*
18053 : : * Prevent circularity by seeing if proposed parent inherits from child.
18054 : : * (In particular, this disallows making a rel inherit from itself.)
18055 : : *
18056 : : * This is not completely bulletproof because of race conditions: in
18057 : : * multi-level inheritance trees, someone else could concurrently be
18058 : : * making another inheritance link that closes the loop but does not join
18059 : : * either of the rels we have locked. Preventing that seems to require
18060 : : * exclusive locks on the entire inheritance tree, which is a cure worse
18061 : : * than the disease. find_all_inheritors() will cope with circularity
18062 : : * anyway, so don't sweat it too much.
18063 : : *
18064 : : * We use weakest lock we can on child's children, namely AccessShareLock.
18065 : : */
18066 : 289 : children = find_all_inheritors(RelationGetRelid(child_rel),
18067 : : AccessShareLock, NULL);
18068 : :
18069 [ + + ]: 289 : if (list_member_oid(children, RelationGetRelid(parent_rel)))
18070 [ + - ]: 8 : ereport(ERROR,
18071 : : (errcode(ERRCODE_DUPLICATE_TABLE),
18072 : : errmsg("circular inheritance not allowed"),
18073 : : errdetail("\"%s\" is already a child of \"%s\".",
18074 : : parent->relname,
18075 : : RelationGetRelationName(child_rel))));
18076 : :
18077 : : /*
18078 : : * If child_rel has row-level triggers with transition tables, we
18079 : : * currently don't allow it to become an inheritance child. See also
18080 : : * prohibitions in ATExecAttachPartition() and CreateTrigger().
18081 : : */
18082 : 281 : trigger_name = FindTriggerIncompatibleWithInheritance(child_rel->trigdesc);
18083 [ + + ]: 281 : if (trigger_name != NULL)
18084 [ + - ]: 4 : ereport(ERROR,
18085 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
18086 : : errmsg("trigger \"%s\" prevents table \"%s\" from becoming an inheritance child",
18087 : : trigger_name, RelationGetRelationName(child_rel)),
18088 : : errdetail("ROW triggers with transition tables are not supported in inheritance hierarchies.")));
18089 : :
18090 : : /* OK to create inheritance */
18091 : 277 : CreateInheritance(child_rel, parent_rel, false);
18092 : :
18093 : 217 : ObjectAddressSet(address, RelationRelationId,
18094 : : RelationGetRelid(parent_rel));
18095 : :
18096 : : /* keep our lock on the parent relation until commit */
18097 : 217 : table_close(parent_rel, NoLock);
18098 : :
18099 : 217 : return address;
18100 : : }
18101 : :
18102 : : /*
18103 : : * CreateInheritance
18104 : : * Catalog manipulation portion of creating inheritance between a child
18105 : : * table and a parent table.
18106 : : *
18107 : : * This verifies that all the columns and check constraints of the parent
18108 : : * appear in the child and that they have the same data types and expressions.
18109 : : *
18110 : : * Common to ATExecAddInherit() and ATExecAttachPartition().
18111 : : */
18112 : : static void
18113 : 2262 : CreateInheritance(Relation child_rel, Relation parent_rel, bool ispartition)
18114 : : {
18115 : : Relation catalogRelation;
18116 : : SysScanDesc scan;
18117 : : ScanKeyData key;
18118 : : HeapTuple inheritsTuple;
18119 : : int32 inhseqno;
18120 : :
18121 : : /* Note: get RowExclusiveLock because we will write pg_inherits below. */
18122 : 2262 : catalogRelation = table_open(InheritsRelationId, RowExclusiveLock);
18123 : :
18124 : : /*
18125 : : * Check for duplicates in the list of parents, and determine the highest
18126 : : * inhseqno already present; we'll use the next one for the new parent.
18127 : : * Also, if proposed child is a partition, it cannot already be
18128 : : * inheriting.
18129 : : *
18130 : : * Note: we do not reject the case where the child already inherits from
18131 : : * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
18132 : : */
18133 : 2262 : ScanKeyInit(&key,
18134 : : Anum_pg_inherits_inhrelid,
18135 : : BTEqualStrategyNumber, F_OIDEQ,
18136 : : ObjectIdGetDatum(RelationGetRelid(child_rel)));
18137 : 2262 : scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
18138 : : true, NULL, 1, &key);
18139 : :
18140 : : /* inhseqno sequences start at 1 */
18141 : 2262 : inhseqno = 0;
18142 [ + + ]: 2313 : while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
18143 : : {
18144 : 55 : Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
18145 : :
18146 [ + + ]: 55 : if (inh->inhparent == RelationGetRelid(parent_rel))
18147 [ + - ]: 4 : ereport(ERROR,
18148 : : (errcode(ERRCODE_DUPLICATE_TABLE),
18149 : : errmsg("relation \"%s\" would be inherited from more than once",
18150 : : RelationGetRelationName(parent_rel))));
18151 : :
18152 [ + - ]: 51 : if (inh->inhseqno > inhseqno)
18153 : 51 : inhseqno = inh->inhseqno;
18154 : : }
18155 : 2258 : systable_endscan(scan);
18156 : :
18157 : : /* Match up the columns and bump attinhcount as needed */
18158 : 2258 : MergeAttributesIntoExisting(child_rel, parent_rel, ispartition);
18159 : :
18160 : : /* Match up the constraints and bump coninhcount as needed */
18161 : 2170 : MergeConstraintsIntoExisting(child_rel, parent_rel);
18162 : :
18163 : : /*
18164 : : * OK, it looks valid. Make the catalog entries that show inheritance.
18165 : : */
18166 : 2130 : StoreCatalogInheritance1(RelationGetRelid(child_rel),
18167 : : RelationGetRelid(parent_rel),
18168 : : inhseqno + 1,
18169 : : catalogRelation,
18170 : 2130 : parent_rel->rd_rel->relkind ==
18171 : : RELKIND_PARTITIONED_TABLE);
18172 : :
18173 : : /* Now we're done with pg_inherits */
18174 : 2130 : table_close(catalogRelation, RowExclusiveLock);
18175 : 2130 : }
18176 : :
18177 : : /*
18178 : : * Obtain the source-text form of the constraint expression for a check
18179 : : * constraint, given its pg_constraint tuple
18180 : : */
18181 : : static char *
18182 : 600 : decompile_conbin(HeapTuple contup, TupleDesc tupdesc)
18183 : : {
18184 : : Form_pg_constraint con;
18185 : : bool isnull;
18186 : : Datum attr;
18187 : : Datum expr;
18188 : :
18189 : 600 : con = (Form_pg_constraint) GETSTRUCT(contup);
18190 : 600 : attr = heap_getattr(contup, Anum_pg_constraint_conbin, tupdesc, &isnull);
18191 [ - + ]: 600 : if (isnull)
18192 [ # # ]: 0 : elog(ERROR, "null conbin for constraint %u", con->oid);
18193 : :
18194 : 600 : expr = DirectFunctionCall2(pg_get_expr, attr,
18195 : : ObjectIdGetDatum(con->conrelid));
18196 : 600 : return TextDatumGetCString(expr);
18197 : : }
18198 : :
18199 : : /*
18200 : : * Determine whether two check constraints are functionally equivalent
18201 : : *
18202 : : * The test we apply is to see whether they reverse-compile to the same
18203 : : * source string. This insulates us from issues like whether attributes
18204 : : * have the same physical column numbers in parent and child relations.
18205 : : *
18206 : : * Note that we ignore enforceability as there are cases where constraints
18207 : : * with differing enforceability are allowed.
18208 : : */
18209 : : static bool
18210 : 300 : constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
18211 : : {
18212 : 300 : Form_pg_constraint acon = (Form_pg_constraint) GETSTRUCT(a);
18213 : 300 : Form_pg_constraint bcon = (Form_pg_constraint) GETSTRUCT(b);
18214 : :
18215 [ + - ]: 300 : if (acon->condeferrable != bcon->condeferrable ||
18216 [ + - ]: 300 : acon->condeferred != bcon->condeferred ||
18217 [ + + ]: 300 : strcmp(decompile_conbin(a, tupleDesc),
18218 : 300 : decompile_conbin(b, tupleDesc)) != 0)
18219 : 4 : return false;
18220 : : else
18221 : 296 : return true;
18222 : : }
18223 : :
18224 : : /*
18225 : : * Check columns in child table match up with columns in parent, and increment
18226 : : * their attinhcount.
18227 : : *
18228 : : * Called by CreateInheritance
18229 : : *
18230 : : * Currently all parent columns must be found in child. Missing columns are an
18231 : : * error. One day we might consider creating new columns like CREATE TABLE
18232 : : * does. However, that is widely unpopular --- in the common use case of
18233 : : * partitioned tables it's a foot-gun.
18234 : : *
18235 : : * The data type must match exactly. If the parent column is NOT NULL then
18236 : : * the child must be as well. Defaults are not compared, however.
18237 : : */
18238 : : static void
18239 : 2258 : MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel, bool ispartition)
18240 : : {
18241 : : Relation attrrel;
18242 : : TupleDesc parent_desc;
18243 : :
18244 : 2258 : attrrel = table_open(AttributeRelationId, RowExclusiveLock);
18245 : 2258 : parent_desc = RelationGetDescr(parent_rel);
18246 : :
18247 [ + + ]: 7378 : for (AttrNumber parent_attno = 1; parent_attno <= parent_desc->natts; parent_attno++)
18248 : : {
18249 : 5208 : Form_pg_attribute parent_att = TupleDescAttr(parent_desc, parent_attno - 1);
18250 : 5208 : char *parent_attname = NameStr(parent_att->attname);
18251 : : HeapTuple tuple;
18252 : :
18253 : : /* Ignore dropped columns in the parent. */
18254 [ + + ]: 5208 : if (parent_att->attisdropped)
18255 : 180 : continue;
18256 : :
18257 : : /* Find same column in child (matching on column name). */
18258 : 5028 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(child_rel), parent_attname);
18259 [ + + ]: 5028 : if (HeapTupleIsValid(tuple))
18260 : : {
18261 : 5020 : Form_pg_attribute child_att = (Form_pg_attribute) GETSTRUCT(tuple);
18262 : :
18263 [ + + ]: 5020 : if (parent_att->atttypid != child_att->atttypid ||
18264 [ + + ]: 5016 : parent_att->atttypmod != child_att->atttypmod)
18265 [ + - ]: 8 : ereport(ERROR,
18266 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
18267 : : errmsg("child table \"%s\" has different type for column \"%s\"",
18268 : : RelationGetRelationName(child_rel), parent_attname)));
18269 : :
18270 [ + + ]: 5012 : if (parent_att->attcollation != child_att->attcollation)
18271 [ + - ]: 4 : ereport(ERROR,
18272 : : (errcode(ERRCODE_COLLATION_MISMATCH),
18273 : : errmsg("child table \"%s\" has different collation for column \"%s\"",
18274 : : RelationGetRelationName(child_rel), parent_attname)));
18275 : :
18276 : : /*
18277 : : * If the parent has a not-null constraint that's not NO INHERIT,
18278 : : * make sure the child has one too.
18279 : : *
18280 : : * Other constraints are checked elsewhere.
18281 : : */
18282 [ + + + + ]: 5008 : if (parent_att->attnotnull && !child_att->attnotnull)
18283 : : {
18284 : : HeapTuple contup;
18285 : :
18286 : 32 : contup = findNotNullConstraintAttnum(RelationGetRelid(parent_rel),
18287 : 32 : parent_att->attnum);
18288 [ + - ]: 32 : if (HeapTupleIsValid(contup) &&
18289 [ + + ]: 32 : !((Form_pg_constraint) GETSTRUCT(contup))->connoinherit)
18290 [ + - ]: 20 : ereport(ERROR,
18291 : : errcode(ERRCODE_DATATYPE_MISMATCH),
18292 : : errmsg("column \"%s\" in child table \"%s\" must be marked NOT NULL",
18293 : : parent_attname, RelationGetRelationName(child_rel)));
18294 : : }
18295 : :
18296 : : /*
18297 : : * Child column must be generated if and only if parent column is.
18298 : : */
18299 [ + + + + ]: 4988 : if (parent_att->attgenerated && !child_att->attgenerated)
18300 [ + - ]: 24 : ereport(ERROR,
18301 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
18302 : : errmsg("column \"%s\" in child table must be a generated column", parent_attname)));
18303 [ + + + + ]: 4964 : if (child_att->attgenerated && !parent_att->attgenerated)
18304 [ + - ]: 16 : ereport(ERROR,
18305 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
18306 : : errmsg("column \"%s\" in child table must not be a generated column", parent_attname)));
18307 : :
18308 [ + + + - : 4948 : if (parent_att->attgenerated && child_att->attgenerated && child_att->attgenerated != parent_att->attgenerated)
+ + ]
18309 [ + - + + : 8 : ereport(ERROR,
+ + ]
18310 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
18311 : : errmsg("column \"%s\" inherits from generated column of different kind", parent_attname),
18312 : : errdetail("Parent column is %s, child column is %s.",
18313 : : parent_att->attgenerated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL",
18314 : : child_att->attgenerated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL")));
18315 : :
18316 : : /*
18317 : : * Regular inheritance children are independent enough not to
18318 : : * inherit identity columns. But partitions are integral part of
18319 : : * a partitioned table and inherit identity column.
18320 : : */
18321 [ + + ]: 4940 : if (ispartition)
18322 : 4453 : child_att->attidentity = parent_att->attidentity;
18323 : :
18324 : : /*
18325 : : * OK, bump the child column's inheritance count. (If we fail
18326 : : * later on, this change will just roll back.)
18327 : : */
18328 [ - + ]: 4940 : if (pg_add_s16_overflow(child_att->attinhcount, 1,
18329 : : &child_att->attinhcount))
18330 [ # # ]: 0 : ereport(ERROR,
18331 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
18332 : : errmsg("too many inheritance parents"));
18333 : :
18334 : : /*
18335 : : * In case of partitions, we must enforce that value of attislocal
18336 : : * is same in all partitions. (Note: there are only inherited
18337 : : * attributes in partitions)
18338 : : */
18339 [ + + ]: 4940 : if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
18340 : : {
18341 : : Assert(child_att->attinhcount == 1);
18342 : 4453 : child_att->attislocal = false;
18343 : : }
18344 : :
18345 : 4940 : CatalogTupleUpdate(attrrel, &tuple->t_self, tuple);
18346 : 4940 : heap_freetuple(tuple);
18347 : : }
18348 : : else
18349 : : {
18350 [ + - ]: 8 : ereport(ERROR,
18351 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
18352 : : errmsg("child table is missing column \"%s\"", parent_attname)));
18353 : : }
18354 : : }
18355 : :
18356 : 2170 : table_close(attrrel, RowExclusiveLock);
18357 : 2170 : }
18358 : :
18359 : : /*
18360 : : * Check constraints in child table match up with constraints in parent,
18361 : : * and increment their coninhcount.
18362 : : *
18363 : : * Constraints that are marked ONLY in the parent are ignored.
18364 : : *
18365 : : * Called by CreateInheritance
18366 : : *
18367 : : * Currently all constraints in parent must be present in the child. One day we
18368 : : * may consider adding new constraints like CREATE TABLE does.
18369 : : *
18370 : : * XXX This is O(N^2) which may be an issue with tables with hundreds of
18371 : : * constraints. As long as tables have more like 10 constraints it shouldn't be
18372 : : * a problem though. Even 100 constraints ought not be the end of the world.
18373 : : *
18374 : : * XXX See MergeWithExistingConstraint too if you change this code.
18375 : : */
18376 : : static void
18377 : 2170 : MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
18378 : : {
18379 : : Relation constraintrel;
18380 : : SysScanDesc parent_scan;
18381 : : ScanKeyData parent_key;
18382 : : HeapTuple parent_tuple;
18383 : 2170 : Oid parent_relid = RelationGetRelid(parent_rel);
18384 : : AttrMap *attmap;
18385 : :
18386 : 2170 : constraintrel = table_open(ConstraintRelationId, RowExclusiveLock);
18387 : :
18388 : : /* Outer loop scans through the parent's constraint definitions */
18389 : 2170 : ScanKeyInit(&parent_key,
18390 : : Anum_pg_constraint_conrelid,
18391 : : BTEqualStrategyNumber, F_OIDEQ,
18392 : : ObjectIdGetDatum(parent_relid));
18393 : 2170 : parent_scan = systable_beginscan(constraintrel, ConstraintRelidTypidNameIndexId,
18394 : : true, NULL, 1, &parent_key);
18395 : :
18396 : 2170 : attmap = build_attrmap_by_name(RelationGetDescr(parent_rel),
18397 : : RelationGetDescr(child_rel),
18398 : : true);
18399 : :
18400 [ + + ]: 3789 : while (HeapTupleIsValid(parent_tuple = systable_getnext(parent_scan)))
18401 : : {
18402 : 1659 : Form_pg_constraint parent_con = (Form_pg_constraint) GETSTRUCT(parent_tuple);
18403 : : SysScanDesc child_scan;
18404 : : ScanKeyData child_key;
18405 : : HeapTuple child_tuple;
18406 : : AttrNumber parent_attno;
18407 : 1659 : bool found = false;
18408 : :
18409 [ + + ]: 1659 : if (parent_con->contype != CONSTRAINT_CHECK &&
18410 [ + + ]: 1483 : parent_con->contype != CONSTRAINT_NOTNULL)
18411 : 770 : continue;
18412 : :
18413 : : /* if the parent's constraint is marked NO INHERIT, it's not inherited */
18414 [ + + ]: 921 : if (parent_con->connoinherit)
18415 : 32 : continue;
18416 : :
18417 [ + + ]: 889 : if (parent_con->contype == CONSTRAINT_NOTNULL)
18418 : 729 : parent_attno = extractNotNullColumn(parent_tuple);
18419 : : else
18420 : 160 : parent_attno = InvalidAttrNumber;
18421 : :
18422 : : /* Search for a child constraint matching this one */
18423 : 889 : ScanKeyInit(&child_key,
18424 : : Anum_pg_constraint_conrelid,
18425 : : BTEqualStrategyNumber, F_OIDEQ,
18426 : : ObjectIdGetDatum(RelationGetRelid(child_rel)));
18427 : 889 : child_scan = systable_beginscan(constraintrel, ConstraintRelidTypidNameIndexId,
18428 : : true, NULL, 1, &child_key);
18429 : :
18430 [ + + ]: 1416 : while (HeapTupleIsValid(child_tuple = systable_getnext(child_scan)))
18431 : : {
18432 : 1400 : Form_pg_constraint child_con = (Form_pg_constraint) GETSTRUCT(child_tuple);
18433 : : HeapTuple child_copy;
18434 : :
18435 [ + + ]: 1400 : if (child_con->contype != parent_con->contype)
18436 : 274 : continue;
18437 : :
18438 : : /*
18439 : : * CHECK constraint are matched by constraint name, NOT NULL ones
18440 : : * by attribute number.
18441 : : */
18442 [ + + ]: 1126 : if (child_con->contype == CONSTRAINT_CHECK)
18443 : : {
18444 : 270 : if (strcmp(NameStr(parent_con->conname),
18445 [ + + ]: 207 : NameStr(child_con->conname)) != 0)
18446 : 63 : continue;
18447 : : }
18448 [ + - ]: 919 : else if (child_con->contype == CONSTRAINT_NOTNULL)
18449 : : {
18450 : : Form_pg_attribute parent_attr;
18451 : : Form_pg_attribute child_attr;
18452 : : AttrNumber child_attno;
18453 : :
18454 : 919 : parent_attr = TupleDescAttr(parent_rel->rd_att, parent_attno - 1);
18455 : 919 : child_attno = extractNotNullColumn(child_tuple);
18456 [ + + ]: 919 : if (parent_attno != attmap->attnums[child_attno - 1])
18457 : 190 : continue;
18458 : :
18459 : 729 : child_attr = TupleDescAttr(child_rel->rd_att, child_attno - 1);
18460 : : /* there shouldn't be constraints on dropped columns */
18461 [ + - - + ]: 729 : if (parent_attr->attisdropped || child_attr->attisdropped)
18462 [ # # ]: 0 : elog(ERROR, "found not-null constraint on dropped columns");
18463 : : }
18464 : :
18465 [ + + ]: 873 : if (child_con->contype == CONSTRAINT_CHECK &&
18466 [ + + ]: 144 : !constraints_equivalent(parent_tuple, child_tuple, RelationGetDescr(constraintrel)))
18467 [ + - ]: 4 : ereport(ERROR,
18468 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
18469 : : errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
18470 : : RelationGetRelationName(child_rel), NameStr(parent_con->conname))));
18471 : :
18472 : : /*
18473 : : * If the child constraint is "no inherit" then cannot merge
18474 : : */
18475 [ + + ]: 869 : if (child_con->connoinherit)
18476 [ + - ]: 8 : ereport(ERROR,
18477 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
18478 : : errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
18479 : : NameStr(child_con->conname), RelationGetRelationName(child_rel))));
18480 : :
18481 : : /*
18482 : : * If the child constraint is "not valid" then cannot merge with a
18483 : : * valid parent constraint
18484 : : */
18485 [ + + + + ]: 861 : if (parent_con->convalidated && child_con->conenforced &&
18486 [ + + ]: 781 : !child_con->convalidated)
18487 [ + - ]: 8 : ereport(ERROR,
18488 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
18489 : : errmsg("constraint \"%s\" conflicts with NOT VALID constraint on child table \"%s\"",
18490 : : NameStr(child_con->conname), RelationGetRelationName(child_rel))));
18491 : :
18492 : : /*
18493 : : * A NOT ENFORCED child constraint cannot be merged with an
18494 : : * ENFORCED parent constraint. However, the reverse is allowed,
18495 : : * where the child constraint is ENFORCED.
18496 : : */
18497 [ + + + + ]: 853 : if (parent_con->conenforced && !child_con->conenforced)
18498 [ + - ]: 4 : ereport(ERROR,
18499 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
18500 : : errmsg("constraint \"%s\" conflicts with NOT ENFORCED constraint on child table \"%s\"",
18501 : : NameStr(child_con->conname), RelationGetRelationName(child_rel))));
18502 : :
18503 : : /*
18504 : : * OK, bump the child constraint's inheritance count. (If we fail
18505 : : * later on, this change will just roll back.)
18506 : : */
18507 : 849 : child_copy = heap_copytuple(child_tuple);
18508 : 849 : child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
18509 : :
18510 [ - + ]: 849 : if (pg_add_s16_overflow(child_con->coninhcount, 1,
18511 : : &child_con->coninhcount))
18512 [ # # ]: 0 : ereport(ERROR,
18513 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
18514 : : errmsg("too many inheritance parents"));
18515 : :
18516 : : /*
18517 : : * In case of partitions, an inherited constraint must be
18518 : : * inherited only once since it cannot have multiple parents and
18519 : : * it is never considered local.
18520 : : */
18521 [ + + ]: 849 : if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
18522 : : {
18523 : : Assert(child_con->coninhcount == 1);
18524 : 750 : child_con->conislocal = false;
18525 : : }
18526 : :
18527 : 849 : CatalogTupleUpdate(constraintrel, &child_copy->t_self, child_copy);
18528 : 849 : heap_freetuple(child_copy);
18529 : :
18530 : 849 : found = true;
18531 : 849 : break;
18532 : : }
18533 : :
18534 : 865 : systable_endscan(child_scan);
18535 : :
18536 [ + + ]: 865 : if (!found)
18537 : : {
18538 [ - + ]: 16 : if (parent_con->contype == CONSTRAINT_NOTNULL)
18539 [ # # ]: 0 : ereport(ERROR,
18540 : : errcode(ERRCODE_DATATYPE_MISMATCH),
18541 : : errmsg("column \"%s\" in child table \"%s\" must be marked NOT NULL",
18542 : : get_attname(parent_relid,
18543 : : extractNotNullColumn(parent_tuple),
18544 : : false),
18545 : : RelationGetRelationName(child_rel)));
18546 : :
18547 [ + - ]: 16 : ereport(ERROR,
18548 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
18549 : : errmsg("child table is missing constraint \"%s\"",
18550 : : NameStr(parent_con->conname))));
18551 : : }
18552 : : }
18553 : :
18554 : 2130 : systable_endscan(parent_scan);
18555 : 2130 : table_close(constraintrel, RowExclusiveLock);
18556 : 2130 : }
18557 : :
18558 : : /*
18559 : : * ALTER TABLE NO INHERIT
18560 : : *
18561 : : * Return value is the address of the relation that is no longer parent.
18562 : : */
18563 : : static ObjectAddress
18564 : 69 : ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
18565 : : {
18566 : : ObjectAddress address;
18567 : : Relation parent_rel;
18568 : :
18569 : : /*
18570 : : * AccessShareLock on the parent is probably enough, seeing that DROP
18571 : : * TABLE doesn't lock parent tables at all. We need some lock since we'll
18572 : : * be inspecting the parent's schema.
18573 : : */
18574 : 69 : parent_rel = table_openrv(parent, AccessShareLock);
18575 : :
18576 : : /*
18577 : : * We don't bother to check ownership of the parent table --- ownership of
18578 : : * the child is presumed enough rights.
18579 : : */
18580 : :
18581 : : /* Off to RemoveInheritance() where most of the work happens */
18582 : 69 : RemoveInheritance(rel, parent_rel, false);
18583 : :
18584 : 65 : ObjectAddressSet(address, RelationRelationId,
18585 : : RelationGetRelid(parent_rel));
18586 : :
18587 : : /* keep our lock on the parent relation until commit */
18588 : 65 : table_close(parent_rel, NoLock);
18589 : :
18590 : 65 : return address;
18591 : : }
18592 : :
18593 : : /*
18594 : : * MarkInheritDetached
18595 : : *
18596 : : * Set inhdetachpending for a partition, for ATExecDetachPartition
18597 : : * in concurrent mode. While at it, verify that no other partition is
18598 : : * already pending detach.
18599 : : */
18600 : : static void
18601 : 75 : MarkInheritDetached(Relation child_rel, Relation parent_rel)
18602 : : {
18603 : : Relation catalogRelation;
18604 : : SysScanDesc scan;
18605 : : ScanKeyData key;
18606 : : HeapTuple inheritsTuple;
18607 : 75 : bool found = false;
18608 : :
18609 : : Assert(parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
18610 : :
18611 : : /*
18612 : : * Find pg_inherits entries by inhparent. (We need to scan them all in
18613 : : * order to verify that no other partition is pending detach.)
18614 : : */
18615 : 75 : catalogRelation = table_open(InheritsRelationId, RowExclusiveLock);
18616 : 75 : ScanKeyInit(&key,
18617 : : Anum_pg_inherits_inhparent,
18618 : : BTEqualStrategyNumber, F_OIDEQ,
18619 : : ObjectIdGetDatum(RelationGetRelid(parent_rel)));
18620 : 75 : scan = systable_beginscan(catalogRelation, InheritsParentIndexId,
18621 : : true, NULL, 1, &key);
18622 : :
18623 [ + + ]: 294 : while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
18624 : : {
18625 : : Form_pg_inherits inhForm;
18626 : :
18627 : 145 : inhForm = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
18628 [ + + ]: 145 : if (inhForm->inhdetachpending)
18629 [ + - ]: 1 : ereport(ERROR,
18630 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
18631 : : errmsg("partition \"%s\" already pending detach in partitioned table \"%s.%s\"",
18632 : : get_rel_name(inhForm->inhrelid),
18633 : : get_namespace_name(parent_rel->rd_rel->relnamespace),
18634 : : RelationGetRelationName(parent_rel)),
18635 : : errhint("Use ALTER TABLE ... DETACH PARTITION ... FINALIZE to complete the pending detach operation."));
18636 : :
18637 [ + + ]: 144 : if (inhForm->inhrelid == RelationGetRelid(child_rel))
18638 : : {
18639 : : HeapTuple newtup;
18640 : :
18641 : 74 : newtup = heap_copytuple(inheritsTuple);
18642 : 74 : ((Form_pg_inherits) GETSTRUCT(newtup))->inhdetachpending = true;
18643 : :
18644 : 74 : CatalogTupleUpdate(catalogRelation,
18645 : 74 : &inheritsTuple->t_self,
18646 : : newtup);
18647 : 74 : found = true;
18648 : 74 : heap_freetuple(newtup);
18649 : : /* keep looking, to ensure we catch others pending detach */
18650 : : }
18651 : : }
18652 : :
18653 : : /* Done */
18654 : 74 : systable_endscan(scan);
18655 : 74 : table_close(catalogRelation, RowExclusiveLock);
18656 : :
18657 [ - + ]: 74 : if (!found)
18658 [ # # ]: 0 : ereport(ERROR,
18659 : : (errcode(ERRCODE_UNDEFINED_TABLE),
18660 : : errmsg("relation \"%s\" is not a partition of relation \"%s\"",
18661 : : RelationGetRelationName(child_rel),
18662 : : RelationGetRelationName(parent_rel))));
18663 : 74 : }
18664 : :
18665 : : /*
18666 : : * RemoveInheritance
18667 : : *
18668 : : * Drop a parent from the child's parents. This just adjusts the attinhcount
18669 : : * and attislocal of the columns and removes the pg_inherit and pg_depend
18670 : : * entries. expect_detached is passed down to DeleteInheritsTuple, q.v..
18671 : : *
18672 : : * If attinhcount goes to 0 then attislocal gets set to true. If it goes back
18673 : : * up attislocal stays true, which means if a child is ever removed from a
18674 : : * parent then its columns will never be automatically dropped which may
18675 : : * surprise. But at least we'll never surprise by dropping columns someone
18676 : : * isn't expecting to be dropped which would actually mean data loss.
18677 : : *
18678 : : * coninhcount and conislocal for inherited constraints are adjusted in
18679 : : * exactly the same way.
18680 : : *
18681 : : * Common to ATExecDropInherit() and ATExecDetachPartition().
18682 : : */
18683 : : static void
18684 : 842 : RemoveInheritance(Relation child_rel, Relation parent_rel, bool expect_detached)
18685 : : {
18686 : : Relation catalogRelation;
18687 : : SysScanDesc scan;
18688 : : ScanKeyData key[3];
18689 : : HeapTuple attributeTuple,
18690 : : constraintTuple;
18691 : : AttrMap *attmap;
18692 : : List *connames;
18693 : : List *nncolumns;
18694 : : bool found;
18695 : : bool is_partitioning;
18696 : :
18697 : 842 : is_partitioning = (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
18698 : :
18699 : 842 : found = DeleteInheritsTuple(RelationGetRelid(child_rel),
18700 : : RelationGetRelid(parent_rel),
18701 : : expect_detached,
18702 : 842 : RelationGetRelationName(child_rel));
18703 [ + + ]: 842 : if (!found)
18704 : : {
18705 [ + + ]: 16 : if (is_partitioning)
18706 [ + - ]: 12 : ereport(ERROR,
18707 : : (errcode(ERRCODE_UNDEFINED_TABLE),
18708 : : errmsg("relation \"%s\" is not a partition of relation \"%s\"",
18709 : : RelationGetRelationName(child_rel),
18710 : : RelationGetRelationName(parent_rel))));
18711 : : else
18712 [ + - ]: 4 : ereport(ERROR,
18713 : : (errcode(ERRCODE_UNDEFINED_TABLE),
18714 : : errmsg("relation \"%s\" is not a parent of relation \"%s\"",
18715 : : RelationGetRelationName(parent_rel),
18716 : : RelationGetRelationName(child_rel))));
18717 : : }
18718 : :
18719 : : /*
18720 : : * Search through child columns looking for ones matching parent rel
18721 : : */
18722 : 826 : catalogRelation = table_open(AttributeRelationId, RowExclusiveLock);
18723 : 826 : ScanKeyInit(&key[0],
18724 : : Anum_pg_attribute_attrelid,
18725 : : BTEqualStrategyNumber, F_OIDEQ,
18726 : : ObjectIdGetDatum(RelationGetRelid(child_rel)));
18727 : 826 : scan = systable_beginscan(catalogRelation, AttributeRelidNumIndexId,
18728 : : true, NULL, 1, key);
18729 [ + + ]: 7578 : while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
18730 : : {
18731 : 6752 : Form_pg_attribute att = (Form_pg_attribute) GETSTRUCT(attributeTuple);
18732 : :
18733 : : /* Ignore if dropped or not inherited */
18734 [ + + ]: 6752 : if (att->attisdropped)
18735 : 28 : continue;
18736 [ + + ]: 6724 : if (att->attinhcount <= 0)
18737 : 4984 : continue;
18738 : :
18739 [ + + ]: 1740 : if (SearchSysCacheExistsAttName(RelationGetRelid(parent_rel),
18740 : 1740 : NameStr(att->attname)))
18741 : : {
18742 : : /* Decrement inhcount and possibly set islocal to true */
18743 : 1704 : HeapTuple copyTuple = heap_copytuple(attributeTuple);
18744 : 1704 : Form_pg_attribute copy_att = (Form_pg_attribute) GETSTRUCT(copyTuple);
18745 : :
18746 : 1704 : copy_att->attinhcount--;
18747 [ + + ]: 1704 : if (copy_att->attinhcount == 0)
18748 : 1684 : copy_att->attislocal = true;
18749 : :
18750 : 1704 : CatalogTupleUpdate(catalogRelation, ©Tuple->t_self, copyTuple);
18751 : 1704 : heap_freetuple(copyTuple);
18752 : : }
18753 : : }
18754 : 826 : systable_endscan(scan);
18755 : 826 : table_close(catalogRelation, RowExclusiveLock);
18756 : :
18757 : : /*
18758 : : * Likewise, find inherited check and not-null constraints and disinherit
18759 : : * them. To do this, we first need a list of the names of the parent's
18760 : : * check constraints. (We cheat a bit by only checking for name matches,
18761 : : * assuming that the expressions will match.)
18762 : : *
18763 : : * For NOT NULL columns, we store column numbers to match, mapping them in
18764 : : * to the child rel's attribute numbers.
18765 : : */
18766 : 826 : attmap = build_attrmap_by_name(RelationGetDescr(child_rel),
18767 : : RelationGetDescr(parent_rel),
18768 : : false);
18769 : :
18770 : 826 : catalogRelation = table_open(ConstraintRelationId, RowExclusiveLock);
18771 : 826 : ScanKeyInit(&key[0],
18772 : : Anum_pg_constraint_conrelid,
18773 : : BTEqualStrategyNumber, F_OIDEQ,
18774 : : ObjectIdGetDatum(RelationGetRelid(parent_rel)));
18775 : 826 : scan = systable_beginscan(catalogRelation, ConstraintRelidTypidNameIndexId,
18776 : : true, NULL, 1, key);
18777 : :
18778 : 826 : connames = NIL;
18779 : 826 : nncolumns = NIL;
18780 : :
18781 [ + + ]: 1523 : while (HeapTupleIsValid(constraintTuple = systable_getnext(scan)))
18782 : : {
18783 : 697 : Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(constraintTuple);
18784 : :
18785 [ + + ]: 697 : if (con->connoinherit)
18786 : 152 : continue;
18787 : :
18788 [ + + ]: 545 : if (con->contype == CONSTRAINT_CHECK)
18789 : 76 : connames = lappend(connames, pstrdup(NameStr(con->conname)));
18790 [ + + ]: 545 : if (con->contype == CONSTRAINT_NOTNULL)
18791 : : {
18792 : 249 : AttrNumber parent_attno = extractNotNullColumn(constraintTuple);
18793 : :
18794 : 249 : nncolumns = lappend_int(nncolumns, attmap->attnums[parent_attno - 1]);
18795 : : }
18796 : : }
18797 : :
18798 : 826 : systable_endscan(scan);
18799 : :
18800 : : /* Now scan the child's constraints to find matches */
18801 : 826 : ScanKeyInit(&key[0],
18802 : : Anum_pg_constraint_conrelid,
18803 : : BTEqualStrategyNumber, F_OIDEQ,
18804 : : ObjectIdGetDatum(RelationGetRelid(child_rel)));
18805 : 826 : scan = systable_beginscan(catalogRelation, ConstraintRelidTypidNameIndexId,
18806 : : true, NULL, 1, key);
18807 : :
18808 [ + + ]: 1519 : while (HeapTupleIsValid(constraintTuple = systable_getnext(scan)))
18809 : : {
18810 : 693 : Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(constraintTuple);
18811 : 693 : bool match = false;
18812 : :
18813 : : /*
18814 : : * Match CHECK constraints by name, not-null constraints by column
18815 : : * number, and ignore all others.
18816 : : */
18817 [ + + ]: 693 : if (con->contype == CONSTRAINT_CHECK)
18818 : : {
18819 [ + + + + : 228 : foreach_ptr(char, chkname, connames)
+ + ]
18820 : : {
18821 [ + - ]: 80 : if (con->contype == CONSTRAINT_CHECK &&
18822 [ + + ]: 80 : strcmp(NameStr(con->conname), chkname) == 0)
18823 : : {
18824 : 76 : match = true;
18825 : 76 : connames = foreach_delete_current(connames, chkname);
18826 : 76 : break;
18827 : : }
18828 : : }
18829 : : }
18830 [ + + ]: 581 : else if (con->contype == CONSTRAINT_NOTNULL)
18831 : : {
18832 : 289 : AttrNumber child_attno = extractNotNullColumn(constraintTuple);
18833 : :
18834 [ + + + + : 582 : foreach_int(prevattno, nncolumns)
+ + ]
18835 : : {
18836 [ + + ]: 253 : if (prevattno == child_attno)
18837 : : {
18838 : 249 : match = true;
18839 : 249 : nncolumns = foreach_delete_current(nncolumns, prevattno);
18840 : 249 : break;
18841 : : }
18842 : : }
18843 : : }
18844 : : else
18845 : 292 : continue;
18846 : :
18847 [ + + ]: 401 : if (match)
18848 : : {
18849 : : /* Decrement inhcount and possibly set islocal to true */
18850 : 325 : HeapTuple copyTuple = heap_copytuple(constraintTuple);
18851 : 325 : Form_pg_constraint copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
18852 : :
18853 [ - + ]: 325 : if (copy_con->coninhcount <= 0) /* shouldn't happen */
18854 [ # # ]: 0 : elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
18855 : : RelationGetRelid(child_rel), NameStr(copy_con->conname));
18856 : :
18857 : 325 : copy_con->coninhcount--;
18858 [ + + ]: 325 : if (copy_con->coninhcount == 0)
18859 : 313 : copy_con->conislocal = true;
18860 : :
18861 : 325 : CatalogTupleUpdate(catalogRelation, ©Tuple->t_self, copyTuple);
18862 : 325 : heap_freetuple(copyTuple);
18863 : : }
18864 : : }
18865 : :
18866 : : /* We should have matched all constraints */
18867 [ + - - + ]: 826 : if (connames != NIL || nncolumns != NIL)
18868 [ # # ]: 0 : elog(ERROR, "%d unmatched constraints while removing inheritance from \"%s\" to \"%s\"",
18869 : : list_length(connames) + list_length(nncolumns),
18870 : : RelationGetRelationName(child_rel), RelationGetRelationName(parent_rel));
18871 : :
18872 : 826 : systable_endscan(scan);
18873 : 826 : table_close(catalogRelation, RowExclusiveLock);
18874 : :
18875 [ + + ]: 826 : drop_parent_dependency(RelationGetRelid(child_rel),
18876 : : RelationRelationId,
18877 : : RelationGetRelid(parent_rel),
18878 : : child_dependency_type(is_partitioning));
18879 : :
18880 : : /*
18881 : : * Post alter hook of this inherits. Since object_access_hook doesn't take
18882 : : * multiple object identifiers, we relay oid of parent relation using
18883 : : * auxiliary_id argument.
18884 : : */
18885 [ - + ]: 826 : InvokeObjectPostAlterHookArg(InheritsRelationId,
18886 : : RelationGetRelid(child_rel), 0,
18887 : : RelationGetRelid(parent_rel), false);
18888 : 826 : }
18889 : :
18890 : : /*
18891 : : * Drop the dependency created by StoreCatalogInheritance1 (CREATE TABLE
18892 : : * INHERITS/ALTER TABLE INHERIT -- refclassid will be RelationRelationId) or
18893 : : * heap_create_with_catalog (CREATE TABLE OF/ALTER TABLE OF -- refclassid will
18894 : : * be TypeRelationId). There's no convenient way to do this, so go trawling
18895 : : * through pg_depend.
18896 : : */
18897 : : static void
18898 : 834 : drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid,
18899 : : DependencyType deptype)
18900 : : {
18901 : : Relation catalogRelation;
18902 : : SysScanDesc scan;
18903 : : ScanKeyData key[3];
18904 : : HeapTuple depTuple;
18905 : :
18906 : 834 : catalogRelation = table_open(DependRelationId, RowExclusiveLock);
18907 : :
18908 : 834 : ScanKeyInit(&key[0],
18909 : : Anum_pg_depend_classid,
18910 : : BTEqualStrategyNumber, F_OIDEQ,
18911 : : ObjectIdGetDatum(RelationRelationId));
18912 : 834 : ScanKeyInit(&key[1],
18913 : : Anum_pg_depend_objid,
18914 : : BTEqualStrategyNumber, F_OIDEQ,
18915 : : ObjectIdGetDatum(relid));
18916 : 834 : ScanKeyInit(&key[2],
18917 : : Anum_pg_depend_objsubid,
18918 : : BTEqualStrategyNumber, F_INT4EQ,
18919 : : Int32GetDatum(0));
18920 : :
18921 : 834 : scan = systable_beginscan(catalogRelation, DependDependerIndexId, true,
18922 : : NULL, 3, key);
18923 : :
18924 [ + + ]: 2555 : while (HeapTupleIsValid(depTuple = systable_getnext(scan)))
18925 : : {
18926 : 1721 : Form_pg_depend dep = (Form_pg_depend) GETSTRUCT(depTuple);
18927 : :
18928 [ + + ]: 1721 : if (dep->refclassid == refclassid &&
18929 [ + + ]: 862 : dep->refobjid == refobjid &&
18930 [ + - ]: 834 : dep->refobjsubid == 0 &&
18931 [ + - ]: 834 : dep->deptype == deptype)
18932 : 834 : CatalogTupleDelete(catalogRelation, &depTuple->t_self);
18933 : : }
18934 : :
18935 : 834 : systable_endscan(scan);
18936 : 834 : table_close(catalogRelation, RowExclusiveLock);
18937 : 834 : }
18938 : :
18939 : : /*
18940 : : * ALTER TABLE OF
18941 : : *
18942 : : * Attach a table to a composite type, as though it had been created with CREATE
18943 : : * TABLE OF. All attname, atttypid, atttypmod and attcollation must match. The
18944 : : * subject table must not have inheritance parents. These restrictions ensure
18945 : : * that you cannot create a configuration impossible with CREATE TABLE OF alone.
18946 : : *
18947 : : * The address of the type is returned.
18948 : : */
18949 : : static ObjectAddress
18950 : 50 : ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
18951 : : {
18952 : 50 : Oid relid = RelationGetRelid(rel);
18953 : : Type typetuple;
18954 : : Form_pg_type typeform;
18955 : : Oid typeid;
18956 : : Relation inheritsRelation,
18957 : : relationRelation;
18958 : : SysScanDesc scan;
18959 : : ScanKeyData key;
18960 : : AttrNumber table_attno,
18961 : : type_attno;
18962 : : TupleDesc typeTupleDesc,
18963 : : tableTupleDesc;
18964 : : ObjectAddress tableobj,
18965 : : typeobj;
18966 : : HeapTuple classtuple;
18967 : : AclResult aclresult;
18968 : :
18969 : : /* Validate the type. */
18970 : 50 : typetuple = typenameType(NULL, ofTypename, NULL);
18971 : 50 : check_of_type(typetuple);
18972 : 50 : typeform = (Form_pg_type) GETSTRUCT(typetuple);
18973 : 50 : typeid = typeform->oid;
18974 : :
18975 : 50 : aclresult = object_aclcheck(TypeRelationId, typeid, GetUserId(), ACL_USAGE);
18976 [ + + ]: 50 : if (aclresult != ACLCHECK_OK)
18977 : 4 : aclcheck_error_type(aclresult, typeid);
18978 : :
18979 : : /* Fail if the table has any inheritance parents. */
18980 : 46 : inheritsRelation = table_open(InheritsRelationId, AccessShareLock);
18981 : 46 : ScanKeyInit(&key,
18982 : : Anum_pg_inherits_inhrelid,
18983 : : BTEqualStrategyNumber, F_OIDEQ,
18984 : : ObjectIdGetDatum(relid));
18985 : 46 : scan = systable_beginscan(inheritsRelation, InheritsRelidSeqnoIndexId,
18986 : : true, NULL, 1, &key);
18987 [ + + ]: 46 : if (HeapTupleIsValid(systable_getnext(scan)))
18988 [ + - ]: 4 : ereport(ERROR,
18989 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18990 : : errmsg("typed tables cannot inherit")));
18991 : 42 : systable_endscan(scan);
18992 : 42 : table_close(inheritsRelation, AccessShareLock);
18993 : :
18994 : : /*
18995 : : * Check the tuple descriptors for compatibility. Unlike inheritance, we
18996 : : * require that the order also match. However, attnotnull need not match.
18997 : : */
18998 : 42 : typeTupleDesc = lookup_rowtype_tupdesc(typeid, -1);
18999 : 42 : tableTupleDesc = RelationGetDescr(rel);
19000 : 42 : table_attno = 1;
19001 [ + + ]: 133 : for (type_attno = 1; type_attno <= typeTupleDesc->natts; type_attno++)
19002 : : {
19003 : : Form_pg_attribute type_attr,
19004 : : table_attr;
19005 : : const char *type_attname,
19006 : : *table_attname;
19007 : :
19008 : : /* Get the next non-dropped type attribute. */
19009 : 107 : type_attr = TupleDescAttr(typeTupleDesc, type_attno - 1);
19010 [ + + ]: 107 : if (type_attr->attisdropped)
19011 : 29 : continue;
19012 : 78 : type_attname = NameStr(type_attr->attname);
19013 : :
19014 : : /* Get the next non-dropped table attribute. */
19015 : : do
19016 : : {
19017 [ + + ]: 86 : if (table_attno > tableTupleDesc->natts)
19018 [ + - ]: 4 : ereport(ERROR,
19019 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
19020 : : errmsg("table is missing column \"%s\"",
19021 : : type_attname)));
19022 : 82 : table_attr = TupleDescAttr(tableTupleDesc, table_attno - 1);
19023 : 82 : table_attno++;
19024 [ + + ]: 82 : } while (table_attr->attisdropped);
19025 : 74 : table_attname = NameStr(table_attr->attname);
19026 : :
19027 : : /* Compare name. */
19028 [ + + ]: 74 : if (strncmp(table_attname, type_attname, NAMEDATALEN) != 0)
19029 [ + - ]: 4 : ereport(ERROR,
19030 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
19031 : : errmsg("table has column \"%s\" where type requires \"%s\"",
19032 : : table_attname, type_attname)));
19033 : :
19034 : : /* Compare type. */
19035 [ + + ]: 70 : if (table_attr->atttypid != type_attr->atttypid ||
19036 [ + + ]: 66 : table_attr->atttypmod != type_attr->atttypmod ||
19037 [ - + ]: 62 : table_attr->attcollation != type_attr->attcollation)
19038 [ + - ]: 8 : ereport(ERROR,
19039 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
19040 : : errmsg("table \"%s\" has different type for column \"%s\"",
19041 : : RelationGetRelationName(rel), type_attname)));
19042 : : }
19043 [ + - ]: 26 : ReleaseTupleDesc(typeTupleDesc);
19044 : :
19045 : : /* Any remaining columns at the end of the table had better be dropped. */
19046 [ + + ]: 26 : for (; table_attno <= tableTupleDesc->natts; table_attno++)
19047 : : {
19048 : 4 : Form_pg_attribute table_attr = TupleDescAttr(tableTupleDesc,
19049 : : table_attno - 1);
19050 : :
19051 [ + - ]: 4 : if (!table_attr->attisdropped)
19052 [ + - ]: 4 : ereport(ERROR,
19053 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
19054 : : errmsg("table has extra column \"%s\"",
19055 : : NameStr(table_attr->attname))));
19056 : : }
19057 : :
19058 : : /* If the table was already typed, drop the existing dependency. */
19059 [ + + ]: 22 : if (rel->rd_rel->reloftype)
19060 : 4 : drop_parent_dependency(relid, TypeRelationId, rel->rd_rel->reloftype,
19061 : : DEPENDENCY_NORMAL);
19062 : :
19063 : : /* Record a dependency on the new type. */
19064 : 22 : tableobj.classId = RelationRelationId;
19065 : 22 : tableobj.objectId = relid;
19066 : 22 : tableobj.objectSubId = 0;
19067 : 22 : typeobj.classId = TypeRelationId;
19068 : 22 : typeobj.objectId = typeid;
19069 : 22 : typeobj.objectSubId = 0;
19070 : 22 : recordDependencyOn(&tableobj, &typeobj, DEPENDENCY_NORMAL);
19071 : :
19072 : : /* Update pg_class.reloftype */
19073 : 22 : relationRelation = table_open(RelationRelationId, RowExclusiveLock);
19074 : 22 : classtuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
19075 [ - + ]: 22 : if (!HeapTupleIsValid(classtuple))
19076 [ # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
19077 : 22 : ((Form_pg_class) GETSTRUCT(classtuple))->reloftype = typeid;
19078 : 22 : CatalogTupleUpdate(relationRelation, &classtuple->t_self, classtuple);
19079 : :
19080 [ - + ]: 22 : InvokeObjectPostAlterHook(RelationRelationId, relid, 0);
19081 : :
19082 : 22 : heap_freetuple(classtuple);
19083 : 22 : table_close(relationRelation, RowExclusiveLock);
19084 : :
19085 : 22 : ReleaseSysCache(typetuple);
19086 : :
19087 : 22 : return typeobj;
19088 : : }
19089 : :
19090 : : /*
19091 : : * ALTER TABLE NOT OF
19092 : : *
19093 : : * Detach a typed table from its originating type. Just clear reloftype and
19094 : : * remove the dependency.
19095 : : */
19096 : : static void
19097 : 4 : ATExecDropOf(Relation rel, LOCKMODE lockmode)
19098 : : {
19099 : 4 : Oid relid = RelationGetRelid(rel);
19100 : : Relation relationRelation;
19101 : : HeapTuple tuple;
19102 : :
19103 [ - + ]: 4 : if (!OidIsValid(rel->rd_rel->reloftype))
19104 [ # # ]: 0 : ereport(ERROR,
19105 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19106 : : errmsg("\"%s\" is not a typed table",
19107 : : RelationGetRelationName(rel))));
19108 : :
19109 : : /*
19110 : : * We don't bother to check ownership of the type --- ownership of the
19111 : : * table is presumed enough rights. No lock required on the type, either.
19112 : : */
19113 : :
19114 : 4 : drop_parent_dependency(relid, TypeRelationId, rel->rd_rel->reloftype,
19115 : : DEPENDENCY_NORMAL);
19116 : :
19117 : : /* Clear pg_class.reloftype */
19118 : 4 : relationRelation = table_open(RelationRelationId, RowExclusiveLock);
19119 : 4 : tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
19120 [ - + ]: 4 : if (!HeapTupleIsValid(tuple))
19121 [ # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
19122 : 4 : ((Form_pg_class) GETSTRUCT(tuple))->reloftype = InvalidOid;
19123 : 4 : CatalogTupleUpdate(relationRelation, &tuple->t_self, tuple);
19124 : :
19125 [ - + ]: 4 : InvokeObjectPostAlterHook(RelationRelationId, relid, 0);
19126 : :
19127 : 4 : heap_freetuple(tuple);
19128 : 4 : table_close(relationRelation, RowExclusiveLock);
19129 : 4 : }
19130 : :
19131 : : /*
19132 : : * relation_mark_replica_identity: Update a table's replica identity
19133 : : *
19134 : : * Iff ri_type = REPLICA_IDENTITY_INDEX, indexOid must be the Oid of a suitable
19135 : : * index. Otherwise, it must be InvalidOid.
19136 : : *
19137 : : * Caller had better hold an exclusive lock on the relation, as the results
19138 : : * of running two of these concurrently wouldn't be pretty.
19139 : : */
19140 : : static void
19141 : 290 : relation_mark_replica_identity(Relation rel, char ri_type, Oid indexOid,
19142 : : bool is_internal)
19143 : : {
19144 : : Relation pg_index;
19145 : : Relation pg_class;
19146 : : HeapTuple pg_class_tuple;
19147 : : HeapTuple pg_index_tuple;
19148 : : Form_pg_class pg_class_form;
19149 : : Form_pg_index pg_index_form;
19150 : : ListCell *index;
19151 : :
19152 : : /*
19153 : : * Check whether relreplident has changed, and update it if so.
19154 : : */
19155 : 290 : pg_class = table_open(RelationRelationId, RowExclusiveLock);
19156 : 290 : pg_class_tuple = SearchSysCacheCopy1(RELOID,
19157 : : ObjectIdGetDatum(RelationGetRelid(rel)));
19158 [ - + ]: 290 : if (!HeapTupleIsValid(pg_class_tuple))
19159 [ # # ]: 0 : elog(ERROR, "cache lookup failed for relation \"%s\"",
19160 : : RelationGetRelationName(rel));
19161 : 290 : pg_class_form = (Form_pg_class) GETSTRUCT(pg_class_tuple);
19162 [ + + ]: 290 : if (pg_class_form->relreplident != ri_type)
19163 : : {
19164 : 257 : pg_class_form->relreplident = ri_type;
19165 : 257 : CatalogTupleUpdate(pg_class, &pg_class_tuple->t_self, pg_class_tuple);
19166 : : }
19167 : 290 : table_close(pg_class, RowExclusiveLock);
19168 : 290 : heap_freetuple(pg_class_tuple);
19169 : :
19170 : : /*
19171 : : * Update the per-index indisreplident flags correctly.
19172 : : */
19173 : 290 : pg_index = table_open(IndexRelationId, RowExclusiveLock);
19174 [ + + + + : 765 : foreach(index, RelationGetIndexList(rel))
+ + ]
19175 : : {
19176 : 475 : Oid thisIndexOid = lfirst_oid(index);
19177 : 475 : bool dirty = false;
19178 : :
19179 : 475 : pg_index_tuple = SearchSysCacheCopy1(INDEXRELID,
19180 : : ObjectIdGetDatum(thisIndexOid));
19181 [ - + ]: 475 : if (!HeapTupleIsValid(pg_index_tuple))
19182 [ # # ]: 0 : elog(ERROR, "cache lookup failed for index %u", thisIndexOid);
19183 : 475 : pg_index_form = (Form_pg_index) GETSTRUCT(pg_index_tuple);
19184 : :
19185 [ + + ]: 475 : if (thisIndexOid == indexOid)
19186 : : {
19187 : : /* Set the bit if not already set. */
19188 [ + + ]: 157 : if (!pg_index_form->indisreplident)
19189 : : {
19190 : 145 : dirty = true;
19191 : 145 : pg_index_form->indisreplident = true;
19192 : : }
19193 : : }
19194 : : else
19195 : : {
19196 : : /* Unset the bit if set. */
19197 [ + + ]: 318 : if (pg_index_form->indisreplident)
19198 : : {
19199 : 34 : dirty = true;
19200 : 34 : pg_index_form->indisreplident = false;
19201 : : }
19202 : : }
19203 : :
19204 [ + + ]: 475 : if (dirty)
19205 : : {
19206 : 179 : CatalogTupleUpdate(pg_index, &pg_index_tuple->t_self, pg_index_tuple);
19207 [ - + ]: 179 : InvokeObjectPostAlterHookArg(IndexRelationId, thisIndexOid, 0,
19208 : : InvalidOid, is_internal);
19209 : :
19210 : : /*
19211 : : * Invalidate the relcache for the table, so that after we commit
19212 : : * all sessions will refresh the table's replica identity index
19213 : : * before attempting any UPDATE or DELETE on the table. (If we
19214 : : * changed the table's pg_class row above, then a relcache inval
19215 : : * is already queued due to that; but we might not have.)
19216 : : */
19217 : 179 : CacheInvalidateRelcache(rel);
19218 : : }
19219 : 475 : heap_freetuple(pg_index_tuple);
19220 : : }
19221 : :
19222 : 290 : table_close(pg_index, RowExclusiveLock);
19223 : 290 : }
19224 : :
19225 : : /*
19226 : : * ALTER TABLE <name> REPLICA IDENTITY ...
19227 : : */
19228 : : static void
19229 : 326 : ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode)
19230 : : {
19231 : : Oid indexOid;
19232 : : Relation indexRel;
19233 : : int key;
19234 : :
19235 [ + + ]: 326 : if (stmt->identity_type == REPLICA_IDENTITY_DEFAULT)
19236 : : {
19237 : 5 : relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
19238 : 5 : return;
19239 : : }
19240 [ + + ]: 321 : else if (stmt->identity_type == REPLICA_IDENTITY_FULL)
19241 : : {
19242 : 98 : relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
19243 : 98 : return;
19244 : : }
19245 [ + + ]: 223 : else if (stmt->identity_type == REPLICA_IDENTITY_NOTHING)
19246 : : {
19247 : 30 : relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
19248 : 30 : return;
19249 : : }
19250 [ - + ]: 193 : else if (stmt->identity_type == REPLICA_IDENTITY_INDEX)
19251 : : {
19252 : : /* fallthrough */ ;
19253 : : }
19254 : : else
19255 [ # # ]: 0 : elog(ERROR, "unexpected identity type %u", stmt->identity_type);
19256 : :
19257 : : /* Check that the index exists */
19258 : 193 : indexOid = get_relname_relid(stmt->name, rel->rd_rel->relnamespace);
19259 [ - + ]: 193 : if (!OidIsValid(indexOid))
19260 [ # # ]: 0 : ereport(ERROR,
19261 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
19262 : : errmsg("index \"%s\" for table \"%s\" does not exist",
19263 : : stmt->name, RelationGetRelationName(rel))));
19264 : :
19265 : 193 : indexRel = index_open(indexOid, ShareLock);
19266 : :
19267 : : /* Check that the index is on the relation we're altering. */
19268 [ + - ]: 193 : if (indexRel->rd_index == NULL ||
19269 [ + + ]: 193 : indexRel->rd_index->indrelid != RelationGetRelid(rel))
19270 [ + - ]: 4 : ereport(ERROR,
19271 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19272 : : errmsg("\"%s\" is not an index for table \"%s\"",
19273 : : RelationGetRelationName(indexRel),
19274 : : RelationGetRelationName(rel))));
19275 : :
19276 : : /*
19277 : : * The AM must support uniqueness, and the index must in fact be unique.
19278 : : * If we have a WITHOUT OVERLAPS constraint (identified by uniqueness +
19279 : : * exclusion), we can use that too.
19280 : : */
19281 [ + + ]: 189 : if ((!indexRel->rd_indam->amcanunique ||
19282 [ + + ]: 175 : !indexRel->rd_index->indisunique) &&
19283 [ + + - + ]: 18 : !(indexRel->rd_index->indisunique && indexRel->rd_index->indisexclusion))
19284 [ + - ]: 8 : ereport(ERROR,
19285 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19286 : : errmsg("cannot use non-unique index \"%s\" as replica identity",
19287 : : RelationGetRelationName(indexRel))));
19288 : : /* Deferred indexes are not guaranteed to be always unique. */
19289 [ + + ]: 181 : if (!indexRel->rd_index->indimmediate)
19290 [ + - ]: 8 : ereport(ERROR,
19291 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
19292 : : errmsg("cannot use non-immediate index \"%s\" as replica identity",
19293 : : RelationGetRelationName(indexRel))));
19294 : : /* Expression indexes aren't supported. */
19295 [ + + ]: 173 : if (RelationGetIndexExpressions(indexRel) != NIL)
19296 [ + - ]: 4 : ereport(ERROR,
19297 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
19298 : : errmsg("cannot use expression index \"%s\" as replica identity",
19299 : : RelationGetRelationName(indexRel))));
19300 : : /* Predicate indexes aren't supported. */
19301 [ + + ]: 169 : if (RelationGetIndexPredicate(indexRel) != NIL)
19302 [ + - ]: 4 : ereport(ERROR,
19303 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
19304 : : errmsg("cannot use partial index \"%s\" as replica identity",
19305 : : RelationGetRelationName(indexRel))));
19306 : :
19307 : : /* Check index for nullable columns. */
19308 [ + + ]: 366 : for (key = 0; key < IndexRelationGetNumberOfKeyAttributes(indexRel); key++)
19309 : : {
19310 : 209 : int16 attno = indexRel->rd_index->indkey.values[key];
19311 : : Form_pg_attribute attr;
19312 : : HeapTuple contup;
19313 : : Form_pg_constraint conForm;
19314 : :
19315 : : /*
19316 : : * Reject any other system columns. (Going forward, we'll disallow
19317 : : * indexes containing such columns in the first place, but they might
19318 : : * exist in older branches.)
19319 : : */
19320 [ - + ]: 209 : if (attno <= 0)
19321 [ # # ]: 0 : ereport(ERROR,
19322 : : (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
19323 : : errmsg("index \"%s\" cannot be used as replica identity because column %d is a system column",
19324 : : RelationGetRelationName(indexRel), attno)));
19325 : :
19326 : 209 : attr = TupleDescAttr(rel->rd_att, attno - 1);
19327 [ + + ]: 209 : if (!attr->attnotnull)
19328 [ + - ]: 4 : ereport(ERROR,
19329 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19330 : : errmsg("index \"%s\" cannot be used as replica identity because column \"%s\" is nullable",
19331 : : RelationGetRelationName(indexRel),
19332 : : NameStr(attr->attname))));
19333 : :
19334 : : /*
19335 : : * Verify that the not-null constraint for the column is valid.
19336 : : */
19337 : 205 : contup = findNotNullConstraintAttnum(RelationGetRelid(rel), attno);
19338 [ - + ]: 205 : if (!HeapTupleIsValid(contup))
19339 [ # # ]: 0 : elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation \"%s\"",
19340 : : NameStr(attr->attname), RelationGetRelationName(rel));
19341 : 205 : conForm = (Form_pg_constraint) GETSTRUCT(contup);
19342 [ + + ]: 205 : if (!conForm->convalidated)
19343 [ + - ]: 4 : ereport(ERROR,
19344 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
19345 : : errmsg("cannot use index \"%s\" as replica identity",
19346 : : RelationGetRelationName(indexRel)),
19347 : : /*- translator: third %s is a constraint characteristic such as NOT VALID */
19348 : : errdetail("The constraint \"%s\" on column \"%s\" is marked %s.",
19349 : : NameStr(conForm->conname), NameStr(attr->attname), "NOT VALID"),
19350 : : errhint("You might need to validate it using %s.",
19351 : : "ALTER TABLE ... VALIDATE CONSTRAINT"));
19352 : 201 : heap_freetuple(contup);
19353 : : }
19354 : :
19355 : : /* This index is suitable for use as a replica identity. Mark it. */
19356 : 157 : relation_mark_replica_identity(rel, stmt->identity_type, indexOid, true);
19357 : :
19358 : 157 : index_close(indexRel, NoLock);
19359 : : }
19360 : :
19361 : : /*
19362 : : * ALTER TABLE ENABLE/DISABLE ROW LEVEL SECURITY
19363 : : */
19364 : : static void
19365 : 254 : ATExecSetRowSecurity(Relation rel, bool rls)
19366 : : {
19367 : : Relation pg_class;
19368 : : Oid relid;
19369 : : HeapTuple tuple;
19370 : :
19371 : 254 : relid = RelationGetRelid(rel);
19372 : :
19373 : : /* Pull the record for this relation and update it */
19374 : 254 : pg_class = table_open(RelationRelationId, RowExclusiveLock);
19375 : :
19376 : 254 : tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
19377 : :
19378 [ - + ]: 254 : if (!HeapTupleIsValid(tuple))
19379 [ # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
19380 : :
19381 : 254 : ((Form_pg_class) GETSTRUCT(tuple))->relrowsecurity = rls;
19382 : 254 : CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
19383 : :
19384 [ + + ]: 254 : InvokeObjectPostAlterHook(RelationRelationId,
19385 : : RelationGetRelid(rel), 0);
19386 : :
19387 : 254 : table_close(pg_class, RowExclusiveLock);
19388 : 254 : heap_freetuple(tuple);
19389 : 254 : }
19390 : :
19391 : : /*
19392 : : * ALTER TABLE FORCE/NO FORCE ROW LEVEL SECURITY
19393 : : */
19394 : : static void
19395 : 90 : ATExecForceNoForceRowSecurity(Relation rel, bool force_rls)
19396 : : {
19397 : : Relation pg_class;
19398 : : Oid relid;
19399 : : HeapTuple tuple;
19400 : :
19401 : 90 : relid = RelationGetRelid(rel);
19402 : :
19403 : 90 : pg_class = table_open(RelationRelationId, RowExclusiveLock);
19404 : :
19405 : 90 : tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
19406 : :
19407 [ - + ]: 90 : if (!HeapTupleIsValid(tuple))
19408 [ # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
19409 : :
19410 : 90 : ((Form_pg_class) GETSTRUCT(tuple))->relforcerowsecurity = force_rls;
19411 : 90 : CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
19412 : :
19413 [ + + ]: 90 : InvokeObjectPostAlterHook(RelationRelationId,
19414 : : RelationGetRelid(rel), 0);
19415 : :
19416 : 90 : table_close(pg_class, RowExclusiveLock);
19417 : 90 : heap_freetuple(tuple);
19418 : 90 : }
19419 : :
19420 : : /*
19421 : : * ALTER FOREIGN TABLE <name> OPTIONS (...)
19422 : : */
19423 : : static void
19424 : 33 : ATExecGenericOptions(Relation rel, List *options)
19425 : : {
19426 : : Relation ftrel;
19427 : : ForeignServer *server;
19428 : : ForeignDataWrapper *fdw;
19429 : : HeapTuple tuple;
19430 : : bool isnull;
19431 : : Datum repl_val[Natts_pg_foreign_table];
19432 : : bool repl_null[Natts_pg_foreign_table];
19433 : : bool repl_repl[Natts_pg_foreign_table];
19434 : : Datum datum;
19435 : : Form_pg_foreign_table tableform;
19436 : :
19437 [ - + ]: 33 : if (options == NIL)
19438 : 0 : return;
19439 : :
19440 : 33 : ftrel = table_open(ForeignTableRelationId, RowExclusiveLock);
19441 : :
19442 : 33 : tuple = SearchSysCacheCopy1(FOREIGNTABLEREL,
19443 : : ObjectIdGetDatum(rel->rd_id));
19444 [ - + ]: 33 : if (!HeapTupleIsValid(tuple))
19445 [ # # ]: 0 : ereport(ERROR,
19446 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
19447 : : errmsg("foreign table \"%s\" does not exist",
19448 : : RelationGetRelationName(rel))));
19449 : 33 : tableform = (Form_pg_foreign_table) GETSTRUCT(tuple);
19450 : 33 : server = GetForeignServer(tableform->ftserver);
19451 : 33 : fdw = GetForeignDataWrapper(server->fdwid);
19452 : :
19453 : 33 : memset(repl_val, 0, sizeof(repl_val));
19454 : 33 : memset(repl_null, false, sizeof(repl_null));
19455 : 33 : memset(repl_repl, false, sizeof(repl_repl));
19456 : :
19457 : : /* Extract the current options */
19458 : 33 : datum = SysCacheGetAttr(FOREIGNTABLEREL,
19459 : : tuple,
19460 : : Anum_pg_foreign_table_ftoptions,
19461 : : &isnull);
19462 [ + + ]: 33 : if (isnull)
19463 : 2 : datum = PointerGetDatum(NULL);
19464 : :
19465 : : /* Transform the options */
19466 : 33 : datum = transformGenericOptions(ForeignTableRelationId,
19467 : : datum,
19468 : : options,
19469 : : fdw->fdwvalidator);
19470 : :
19471 [ + - ]: 32 : if (DatumGetPointer(datum) != NULL)
19472 : 32 : repl_val[Anum_pg_foreign_table_ftoptions - 1] = datum;
19473 : : else
19474 : 0 : repl_null[Anum_pg_foreign_table_ftoptions - 1] = true;
19475 : :
19476 : 32 : repl_repl[Anum_pg_foreign_table_ftoptions - 1] = true;
19477 : :
19478 : : /* Everything looks good - update the tuple */
19479 : :
19480 : 32 : tuple = heap_modify_tuple(tuple, RelationGetDescr(ftrel),
19481 : : repl_val, repl_null, repl_repl);
19482 : :
19483 : 32 : CatalogTupleUpdate(ftrel, &tuple->t_self, tuple);
19484 : :
19485 : : /*
19486 : : * Invalidate relcache so that all sessions will refresh any cached plans
19487 : : * that might depend on the old options.
19488 : : */
19489 : 32 : CacheInvalidateRelcache(rel);
19490 : :
19491 [ - + ]: 32 : InvokeObjectPostAlterHook(ForeignTableRelationId,
19492 : : RelationGetRelid(rel), 0);
19493 : :
19494 : 32 : table_close(ftrel, RowExclusiveLock);
19495 : :
19496 : 32 : heap_freetuple(tuple);
19497 : : }
19498 : :
19499 : : /*
19500 : : * ALTER TABLE ALTER COLUMN SET COMPRESSION
19501 : : *
19502 : : * Return value is the address of the modified column
19503 : : */
19504 : : static ObjectAddress
19505 : 48 : ATExecSetCompression(Relation rel,
19506 : : const char *column,
19507 : : Node *newValue,
19508 : : LOCKMODE lockmode)
19509 : : {
19510 : : Relation attrel;
19511 : : HeapTuple tuple;
19512 : : Form_pg_attribute atttableform;
19513 : : AttrNumber attnum;
19514 : : char *compression;
19515 : : char cmethod;
19516 : : ObjectAddress address;
19517 : :
19518 : 48 : compression = strVal(newValue);
19519 : :
19520 : 48 : attrel = table_open(AttributeRelationId, RowExclusiveLock);
19521 : :
19522 : : /* copy the cache entry so we can scribble on it below */
19523 : 48 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), column);
19524 [ - + ]: 48 : if (!HeapTupleIsValid(tuple))
19525 [ # # ]: 0 : ereport(ERROR,
19526 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
19527 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
19528 : : column, RelationGetRelationName(rel))));
19529 : :
19530 : : /* prevent them from altering a system attribute */
19531 : 48 : atttableform = (Form_pg_attribute) GETSTRUCT(tuple);
19532 : 48 : attnum = atttableform->attnum;
19533 [ - + ]: 48 : if (attnum <= 0)
19534 [ # # ]: 0 : ereport(ERROR,
19535 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
19536 : : errmsg("cannot alter system column \"%s\"", column)));
19537 : :
19538 : : /*
19539 : : * Check that column type is compressible, then get the attribute
19540 : : * compression method code
19541 : : */
19542 : 48 : cmethod = GetAttributeCompression(atttableform->atttypid, compression);
19543 : :
19544 : : /* update pg_attribute entry */
19545 : 44 : atttableform->attcompression = cmethod;
19546 : 44 : CatalogTupleUpdate(attrel, &tuple->t_self, tuple);
19547 : :
19548 [ - + ]: 44 : InvokeObjectPostAlterHook(RelationRelationId,
19549 : : RelationGetRelid(rel),
19550 : : attnum);
19551 : :
19552 : : /*
19553 : : * Apply the change to indexes as well (only for simple index columns,
19554 : : * matching behavior of index.c ConstructTupleDescriptor()).
19555 : : */
19556 : 44 : SetIndexStorageProperties(rel, attrel, attnum,
19557 : : false, 0,
19558 : : true, cmethod,
19559 : : lockmode);
19560 : :
19561 : 44 : heap_freetuple(tuple);
19562 : :
19563 : 44 : table_close(attrel, RowExclusiveLock);
19564 : :
19565 : : /* make changes visible */
19566 : 44 : CommandCounterIncrement();
19567 : :
19568 : 44 : ObjectAddressSubSet(address, RelationRelationId,
19569 : : RelationGetRelid(rel), attnum);
19570 : 44 : return address;
19571 : : }
19572 : :
19573 : :
19574 : : /*
19575 : : * Preparation phase for SET LOGGED/UNLOGGED
19576 : : *
19577 : : * This verifies that we're not trying to change a temp table. Also,
19578 : : * existing foreign key constraints are checked to avoid ending up with
19579 : : * permanent tables referencing unlogged tables.
19580 : : */
19581 : : static void
19582 : 67 : ATPrepChangePersistence(AlteredTableInfo *tab, Relation rel, bool toLogged)
19583 : : {
19584 : : Relation pg_constraint;
19585 : : HeapTuple tuple;
19586 : : SysScanDesc scan;
19587 : : ScanKeyData skey[1];
19588 : :
19589 : : /*
19590 : : * Disallow changing status for a temp table. Also verify whether we can
19591 : : * get away with doing nothing; in such cases we don't need to run the
19592 : : * checks below, either.
19593 : : */
19594 [ - + + - ]: 67 : switch (rel->rd_rel->relpersistence)
19595 : : {
19596 : 0 : case RELPERSISTENCE_TEMP:
19597 [ # # ]: 0 : ereport(ERROR,
19598 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
19599 : : errmsg("cannot change logged status of table \"%s\" because it is temporary",
19600 : : RelationGetRelationName(rel)),
19601 : : errtable(rel)));
19602 : : break;
19603 : 37 : case RELPERSISTENCE_PERMANENT:
19604 [ + + ]: 37 : if (toLogged)
19605 : : /* nothing to do */
19606 : 8 : return;
19607 : 33 : break;
19608 : 30 : case RELPERSISTENCE_UNLOGGED:
19609 [ + + ]: 30 : if (!toLogged)
19610 : : /* nothing to do */
19611 : 4 : return;
19612 : 26 : break;
19613 : : }
19614 : :
19615 : : /*
19616 : : * Check that the table is not part of any publication when changing to
19617 : : * UNLOGGED, as UNLOGGED tables can't be published.
19618 : : */
19619 [ + + - + ]: 92 : if (!toLogged &&
19620 : 33 : GetRelationIncludedPublications(RelationGetRelid(rel)) != NIL)
19621 [ # # ]: 0 : ereport(ERROR,
19622 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
19623 : : errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
19624 : : RelationGetRelationName(rel)),
19625 : : errdetail("Unlogged relations cannot be replicated.")));
19626 : :
19627 : : /*
19628 : : * Check existing foreign key constraints to preserve the invariant that
19629 : : * permanent tables cannot reference unlogged ones. Self-referencing
19630 : : * foreign keys can safely be ignored.
19631 : : */
19632 : 59 : pg_constraint = table_open(ConstraintRelationId, AccessShareLock);
19633 : :
19634 : : /*
19635 : : * Scan conrelid if changing to permanent, else confrelid. This also
19636 : : * determines whether a useful index exists.
19637 : : */
19638 [ + + ]: 59 : ScanKeyInit(&skey[0],
19639 : : toLogged ? Anum_pg_constraint_conrelid :
19640 : : Anum_pg_constraint_confrelid,
19641 : : BTEqualStrategyNumber, F_OIDEQ,
19642 : : ObjectIdGetDatum(RelationGetRelid(rel)));
19643 [ + + ]: 59 : scan = systable_beginscan(pg_constraint,
19644 : : toLogged ? ConstraintRelidTypidNameIndexId : InvalidOid,
19645 : : true, NULL, 1, skey);
19646 : :
19647 [ + + ]: 95 : while (HeapTupleIsValid(tuple = systable_getnext(scan)))
19648 : : {
19649 : 44 : Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
19650 : :
19651 [ + + ]: 44 : if (con->contype == CONSTRAINT_FOREIGN)
19652 : : {
19653 : : Oid foreignrelid;
19654 : : Relation foreignrel;
19655 : :
19656 : : /* the opposite end of what we used as scankey */
19657 [ + + ]: 20 : foreignrelid = toLogged ? con->confrelid : con->conrelid;
19658 : :
19659 : : /* ignore if self-referencing */
19660 [ + + ]: 20 : if (RelationGetRelid(rel) == foreignrelid)
19661 : 8 : continue;
19662 : :
19663 : 12 : foreignrel = relation_open(foreignrelid, AccessShareLock);
19664 : :
19665 [ + + ]: 12 : if (toLogged)
19666 : : {
19667 [ + - ]: 4 : if (!RelationIsPermanent(foreignrel))
19668 [ + - ]: 4 : ereport(ERROR,
19669 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
19670 : : errmsg("could not change table \"%s\" to logged because it references unlogged table \"%s\"",
19671 : : RelationGetRelationName(rel),
19672 : : RelationGetRelationName(foreignrel)),
19673 : : errtableconstraint(rel, NameStr(con->conname))));
19674 : : }
19675 : : else
19676 : : {
19677 [ + + ]: 8 : if (RelationIsPermanent(foreignrel))
19678 [ + - ]: 4 : ereport(ERROR,
19679 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
19680 : : errmsg("could not change table \"%s\" to unlogged because it references logged table \"%s\"",
19681 : : RelationGetRelationName(rel),
19682 : : RelationGetRelationName(foreignrel)),
19683 : : errtableconstraint(rel, NameStr(con->conname))));
19684 : : }
19685 : :
19686 : 4 : relation_close(foreignrel, AccessShareLock);
19687 : : }
19688 : : }
19689 : :
19690 : 51 : systable_endscan(scan);
19691 : :
19692 : 51 : table_close(pg_constraint, AccessShareLock);
19693 : :
19694 : : /* force rewrite if necessary; see comment in ATRewriteTables */
19695 : 51 : tab->rewrite |= AT_REWRITE_ALTER_PERSISTENCE;
19696 [ + + ]: 51 : if (toLogged)
19697 : 22 : tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
19698 : : else
19699 : 29 : tab->newrelpersistence = RELPERSISTENCE_UNLOGGED;
19700 : 51 : tab->chgPersistence = true;
19701 : : }
19702 : :
19703 : : /*
19704 : : * Execute ALTER TABLE SET SCHEMA
19705 : : */
19706 : : ObjectAddress
19707 : 96 : AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
19708 : : {
19709 : : Relation rel;
19710 : : Oid relid;
19711 : : Oid oldNspOid;
19712 : : Oid nspOid;
19713 : : RangeVar *newrv;
19714 : : ObjectAddresses *objsMoved;
19715 : : ObjectAddress myself;
19716 : :
19717 : 96 : relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
19718 : 96 : stmt->missing_ok ? RVR_MISSING_OK : 0,
19719 : : RangeVarCallbackForAlterRelation,
19720 : : stmt);
19721 : :
19722 [ + + ]: 87 : if (!OidIsValid(relid))
19723 : : {
19724 [ + - ]: 12 : ereport(NOTICE,
19725 : : (errmsg("relation \"%s\" does not exist, skipping",
19726 : : stmt->relation->relname)));
19727 : 12 : return InvalidObjectAddress;
19728 : : }
19729 : :
19730 : 75 : rel = relation_open(relid, NoLock);
19731 : :
19732 : 75 : oldNspOid = RelationGetNamespace(rel);
19733 : :
19734 : : /* If it's an owned sequence, disallow moving it by itself. */
19735 [ + + ]: 75 : if (rel->rd_rel->relkind == RELKIND_SEQUENCE)
19736 : : {
19737 : : Oid tableId;
19738 : : int32 colId;
19739 : :
19740 [ + + - + ]: 6 : if (sequenceIsOwned(relid, DEPENDENCY_AUTO, &tableId, &colId) ||
19741 : 1 : sequenceIsOwned(relid, DEPENDENCY_INTERNAL, &tableId, &colId))
19742 [ + - ]: 4 : ereport(ERROR,
19743 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
19744 : : errmsg("cannot move an owned sequence into another schema"),
19745 : : errdetail("Sequence \"%s\" is linked to table \"%s\".",
19746 : : RelationGetRelationName(rel),
19747 : : get_rel_name(tableId))));
19748 : : }
19749 : :
19750 : : /* Get and lock schema OID and check its permissions. */
19751 : 71 : newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1);
19752 : 71 : nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL);
19753 : :
19754 : : /* common checks on switching namespaces */
19755 : 67 : CheckSetNamespace(oldNspOid, nspOid);
19756 : :
19757 : 67 : objsMoved = new_object_addresses();
19758 : 67 : AlterTableNamespaceInternal(rel, oldNspOid, nspOid, objsMoved);
19759 : 63 : free_object_addresses(objsMoved);
19760 : :
19761 : 63 : ObjectAddressSet(myself, RelationRelationId, relid);
19762 : :
19763 [ + - ]: 63 : if (oldschema)
19764 : 63 : *oldschema = oldNspOid;
19765 : :
19766 : : /* close rel, but keep lock until commit */
19767 : 63 : relation_close(rel, NoLock);
19768 : :
19769 : 63 : return myself;
19770 : : }
19771 : :
19772 : : /*
19773 : : * The guts of relocating a table or materialized view to another namespace:
19774 : : * besides moving the relation itself, its dependent objects are relocated to
19775 : : * the new schema.
19776 : : */
19777 : : void
19778 : 68 : AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
19779 : : ObjectAddresses *objsMoved)
19780 : : {
19781 : : Relation classRel;
19782 : :
19783 : : Assert(objsMoved != NULL);
19784 : :
19785 : : /* OK, modify the pg_class row and pg_depend entry */
19786 : 68 : classRel = table_open(RelationRelationId, RowExclusiveLock);
19787 : :
19788 : 68 : AlterRelationNamespaceInternal(classRel, RelationGetRelid(rel), oldNspOid,
19789 : : nspOid, true, objsMoved);
19790 : :
19791 : : /* Fix the table's row type too, if it has one */
19792 [ + + ]: 64 : if (OidIsValid(rel->rd_rel->reltype))
19793 : 55 : AlterTypeNamespaceInternal(rel->rd_rel->reltype, nspOid,
19794 : : false, /* isImplicitArray */
19795 : : false, /* ignoreDependent */
19796 : : false, /* errorOnTableType */
19797 : : objsMoved);
19798 : :
19799 : : /* Fix other dependent stuff */
19800 : 64 : AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
19801 : 64 : AlterSeqNamespaces(classRel, rel, oldNspOid, nspOid,
19802 : : objsMoved, AccessExclusiveLock);
19803 : 64 : AlterConstraintNamespaces(RelationGetRelid(rel), oldNspOid, nspOid,
19804 : : false, objsMoved);
19805 : :
19806 : 64 : table_close(classRel, RowExclusiveLock);
19807 : 64 : }
19808 : :
19809 : : /*
19810 : : * The guts of relocating a relation to another namespace: fix the pg_class
19811 : : * entry, and the pg_depend entry if any. Caller must already have
19812 : : * opened and write-locked pg_class.
19813 : : */
19814 : : void
19815 : 135 : AlterRelationNamespaceInternal(Relation classRel, Oid relOid,
19816 : : Oid oldNspOid, Oid newNspOid,
19817 : : bool hasDependEntry,
19818 : : ObjectAddresses *objsMoved)
19819 : : {
19820 : : HeapTuple classTup;
19821 : : Form_pg_class classForm;
19822 : : ObjectAddress thisobj;
19823 : 135 : bool already_done = false;
19824 : :
19825 : : /* no rel lock for relkind=c so use LOCKTAG_TUPLE */
19826 : 135 : classTup = SearchSysCacheLockedCopy1(RELOID, ObjectIdGetDatum(relOid));
19827 [ - + ]: 135 : if (!HeapTupleIsValid(classTup))
19828 [ # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", relOid);
19829 : 135 : classForm = (Form_pg_class) GETSTRUCT(classTup);
19830 : :
19831 : : Assert(classForm->relnamespace == oldNspOid);
19832 : :
19833 : 135 : thisobj.classId = RelationRelationId;
19834 : 135 : thisobj.objectId = relOid;
19835 : 135 : thisobj.objectSubId = 0;
19836 : :
19837 : : /*
19838 : : * If the object has already been moved, don't move it again. If it's
19839 : : * already in the right place, don't move it, but still fire the object
19840 : : * access hook.
19841 : : */
19842 : 135 : already_done = object_address_present(&thisobj, objsMoved);
19843 [ + - + + ]: 135 : if (!already_done && oldNspOid != newNspOid)
19844 : 103 : {
19845 : 107 : ItemPointerData otid = classTup->t_self;
19846 : :
19847 : : /* check for duplicate name (more friendly than unique-index failure) */
19848 [ + + ]: 107 : if (get_relname_relid(NameStr(classForm->relname),
19849 : : newNspOid) != InvalidOid)
19850 [ + - ]: 4 : ereport(ERROR,
19851 : : (errcode(ERRCODE_DUPLICATE_TABLE),
19852 : : errmsg("relation \"%s\" already exists in schema \"%s\"",
19853 : : NameStr(classForm->relname),
19854 : : get_namespace_name(newNspOid))));
19855 : :
19856 : : /* classTup is a copy, so OK to scribble on */
19857 : 103 : classForm->relnamespace = newNspOid;
19858 : :
19859 : 103 : CatalogTupleUpdate(classRel, &otid, classTup);
19860 : 103 : UnlockTuple(classRel, &otid, InplaceUpdateTupleLock);
19861 : :
19862 : :
19863 : : /* Update dependency on schema if caller said so */
19864 [ + + - + ]: 179 : if (hasDependEntry &&
19865 : 76 : changeDependencyFor(RelationRelationId,
19866 : : relOid,
19867 : : NamespaceRelationId,
19868 : : oldNspOid,
19869 : : newNspOid) != 1)
19870 [ # # ]: 0 : elog(ERROR, "could not change schema dependency for relation \"%s\"",
19871 : : NameStr(classForm->relname));
19872 : : }
19873 : : else
19874 : 28 : UnlockTuple(classRel, &classTup->t_self, InplaceUpdateTupleLock);
19875 [ + - ]: 131 : if (!already_done)
19876 : : {
19877 : 131 : add_exact_object_address(&thisobj, objsMoved);
19878 : :
19879 [ - + ]: 131 : InvokeObjectPostAlterHook(RelationRelationId, relOid, 0);
19880 : : }
19881 : :
19882 : 131 : heap_freetuple(classTup);
19883 : 131 : }
19884 : :
19885 : : /*
19886 : : * Move all indexes for the specified relation to another namespace.
19887 : : *
19888 : : * Note: we assume adequate permission checking was done by the caller,
19889 : : * and that the caller has a suitable lock on the owning relation.
19890 : : */
19891 : : static void
19892 : 64 : AlterIndexNamespaces(Relation classRel, Relation rel,
19893 : : Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved)
19894 : : {
19895 : : List *indexList;
19896 : : ListCell *l;
19897 : :
19898 : 64 : indexList = RelationGetIndexList(rel);
19899 : :
19900 [ + + + + : 94 : foreach(l, indexList)
+ + ]
19901 : : {
19902 : 30 : Oid indexOid = lfirst_oid(l);
19903 : : ObjectAddress thisobj;
19904 : :
19905 : 30 : thisobj.classId = RelationRelationId;
19906 : 30 : thisobj.objectId = indexOid;
19907 : 30 : thisobj.objectSubId = 0;
19908 : :
19909 : : /*
19910 : : * Note: currently, the index will not have its own dependency on the
19911 : : * namespace, so we don't need to do changeDependencyFor(). There's no
19912 : : * row type in pg_type, either.
19913 : : *
19914 : : * XXX this objsMoved test may be pointless -- surely we have a single
19915 : : * dependency link from a relation to each index?
19916 : : */
19917 [ + - ]: 30 : if (!object_address_present(&thisobj, objsMoved))
19918 : : {
19919 : 30 : AlterRelationNamespaceInternal(classRel, indexOid,
19920 : : oldNspOid, newNspOid,
19921 : : false, objsMoved);
19922 : 30 : add_exact_object_address(&thisobj, objsMoved);
19923 : : }
19924 : : }
19925 : :
19926 : 64 : list_free(indexList);
19927 : 64 : }
19928 : :
19929 : : /*
19930 : : * Move all identity and SERIAL-column sequences of the specified relation to another
19931 : : * namespace.
19932 : : *
19933 : : * Note: we assume adequate permission checking was done by the caller,
19934 : : * and that the caller has a suitable lock on the owning relation.
19935 : : */
19936 : : static void
19937 : 64 : AlterSeqNamespaces(Relation classRel, Relation rel,
19938 : : Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved,
19939 : : LOCKMODE lockmode)
19940 : : {
19941 : : Relation depRel;
19942 : : SysScanDesc scan;
19943 : : ScanKeyData key[2];
19944 : : HeapTuple tup;
19945 : :
19946 : : /*
19947 : : * SERIAL sequences are those having an auto dependency on one of the
19948 : : * table's columns (we don't care *which* column, exactly).
19949 : : */
19950 : 64 : depRel = table_open(DependRelationId, AccessShareLock);
19951 : :
19952 : 64 : ScanKeyInit(&key[0],
19953 : : Anum_pg_depend_refclassid,
19954 : : BTEqualStrategyNumber, F_OIDEQ,
19955 : : ObjectIdGetDatum(RelationRelationId));
19956 : 64 : ScanKeyInit(&key[1],
19957 : : Anum_pg_depend_refobjid,
19958 : : BTEqualStrategyNumber, F_OIDEQ,
19959 : : ObjectIdGetDatum(RelationGetRelid(rel)));
19960 : : /* we leave refobjsubid unspecified */
19961 : :
19962 : 64 : scan = systable_beginscan(depRel, DependReferenceIndexId, true,
19963 : : NULL, 2, key);
19964 : :
19965 [ + + ]: 417 : while (HeapTupleIsValid(tup = systable_getnext(scan)))
19966 : : {
19967 : 353 : Form_pg_depend depForm = (Form_pg_depend) GETSTRUCT(tup);
19968 : : Relation seqRel;
19969 : :
19970 : : /* skip dependencies other than auto dependencies on columns */
19971 [ + + ]: 353 : if (depForm->refobjsubid == 0 ||
19972 [ + + ]: 252 : depForm->classid != RelationRelationId ||
19973 [ + - ]: 28 : depForm->objsubid != 0 ||
19974 [ - + - - ]: 28 : !(depForm->deptype == DEPENDENCY_AUTO || depForm->deptype == DEPENDENCY_INTERNAL))
19975 : 325 : continue;
19976 : :
19977 : : /* Use relation_open just in case it's an index */
19978 : 28 : seqRel = relation_open(depForm->objid, lockmode);
19979 : :
19980 : : /* skip non-sequence relations */
19981 [ - + ]: 28 : if (RelationGetForm(seqRel)->relkind != RELKIND_SEQUENCE)
19982 : : {
19983 : : /* No need to keep the lock */
19984 : 0 : relation_close(seqRel, lockmode);
19985 : 0 : continue;
19986 : : }
19987 : :
19988 : : /* Fix the pg_class and pg_depend entries */
19989 : 28 : AlterRelationNamespaceInternal(classRel, depForm->objid,
19990 : : oldNspOid, newNspOid,
19991 : : true, objsMoved);
19992 : :
19993 : : /*
19994 : : * Sequences used to have entries in pg_type, but no longer do. If we
19995 : : * ever re-instate that, we'll need to move the pg_type entry to the
19996 : : * new namespace, too (using AlterTypeNamespaceInternal).
19997 : : */
19998 : : Assert(RelationGetForm(seqRel)->reltype == InvalidOid);
19999 : :
20000 : : /* Now we can close it. Keep the lock till end of transaction. */
20001 : 28 : relation_close(seqRel, NoLock);
20002 : : }
20003 : :
20004 : 64 : systable_endscan(scan);
20005 : :
20006 : 64 : relation_close(depRel, AccessShareLock);
20007 : 64 : }
20008 : :
20009 : :
20010 : : /*
20011 : : * This code supports
20012 : : * CREATE TEMP TABLE ... ON COMMIT { DROP | PRESERVE ROWS | DELETE ROWS }
20013 : : *
20014 : : * Because we only support this for TEMP tables, it's sufficient to remember
20015 : : * the state in a backend-local data structure.
20016 : : */
20017 : :
20018 : : /*
20019 : : * Register a newly-created relation's ON COMMIT action.
20020 : : */
20021 : : void
20022 : 120 : register_on_commit_action(Oid relid, OnCommitAction action)
20023 : : {
20024 : : OnCommitItem *oc;
20025 : : MemoryContext oldcxt;
20026 : :
20027 : : /*
20028 : : * We needn't bother registering the relation unless there is an ON COMMIT
20029 : : * action we need to take.
20030 : : */
20031 [ + - + + ]: 120 : if (action == ONCOMMIT_NOOP || action == ONCOMMIT_PRESERVE_ROWS)
20032 : 16 : return;
20033 : :
20034 : 104 : oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
20035 : :
20036 : 104 : oc = palloc_object(OnCommitItem);
20037 : 104 : oc->relid = relid;
20038 : 104 : oc->oncommit = action;
20039 : 104 : oc->creating_subid = GetCurrentSubTransactionId();
20040 : 104 : oc->deleting_subid = InvalidSubTransactionId;
20041 : :
20042 : : /*
20043 : : * We use lcons() here so that ON COMMIT actions are processed in reverse
20044 : : * order of registration. That might not be essential but it seems
20045 : : * reasonable.
20046 : : */
20047 : 104 : on_commits = lcons(oc, on_commits);
20048 : :
20049 : 104 : MemoryContextSwitchTo(oldcxt);
20050 : : }
20051 : :
20052 : : /*
20053 : : * Unregister any ON COMMIT action when a relation is deleted.
20054 : : *
20055 : : * Actually, we only mark the OnCommitItem entry as to be deleted after commit.
20056 : : */
20057 : : void
20058 : 34312 : remove_on_commit_action(Oid relid)
20059 : : {
20060 : : ListCell *l;
20061 : :
20062 [ + + + + : 34425 : foreach(l, on_commits)
+ + ]
20063 : : {
20064 : 205 : OnCommitItem *oc = (OnCommitItem *) lfirst(l);
20065 : :
20066 [ + + ]: 205 : if (oc->relid == relid)
20067 : : {
20068 : 92 : oc->deleting_subid = GetCurrentSubTransactionId();
20069 : 92 : break;
20070 : : }
20071 : : }
20072 : 34312 : }
20073 : :
20074 : : /*
20075 : : * Perform ON COMMIT actions.
20076 : : *
20077 : : * This is invoked just before actually committing, since it's possible
20078 : : * to encounter errors.
20079 : : */
20080 : : void
20081 : 625210 : PreCommit_on_commit_actions(void)
20082 : : {
20083 : : ListCell *l;
20084 : 625210 : List *oids_to_truncate = NIL;
20085 : 625210 : List *oids_to_drop = NIL;
20086 : :
20087 [ + + + + : 625756 : foreach(l, on_commits)
+ + ]
20088 : : {
20089 : 546 : OnCommitItem *oc = (OnCommitItem *) lfirst(l);
20090 : :
20091 : : /* Ignore entry if already dropped in this xact */
20092 [ + + ]: 546 : if (oc->deleting_subid != InvalidSubTransactionId)
20093 : 49 : continue;
20094 : :
20095 [ - + + - ]: 497 : switch (oc->oncommit)
20096 : : {
20097 : 0 : case ONCOMMIT_NOOP:
20098 : : case ONCOMMIT_PRESERVE_ROWS:
20099 : : /* Do nothing (there shouldn't be such entries, actually) */
20100 : 0 : break;
20101 : 462 : case ONCOMMIT_DELETE_ROWS:
20102 : :
20103 : : /*
20104 : : * If this transaction hasn't accessed any temporary
20105 : : * relations, we can skip truncating ON COMMIT DELETE ROWS
20106 : : * tables, as they must still be empty.
20107 : : */
20108 [ + + ]: 462 : if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
20109 : 298 : oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
20110 : 462 : break;
20111 : 35 : case ONCOMMIT_DROP:
20112 : 35 : oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
20113 : 35 : break;
20114 : : }
20115 : : }
20116 : :
20117 : : /*
20118 : : * Truncate relations before dropping so that all dependencies between
20119 : : * relations are removed after they are worked on. Doing it like this
20120 : : * might be a waste as it is possible that a relation being truncated will
20121 : : * be dropped anyway due to its parent being dropped, but this makes the
20122 : : * code more robust because of not having to re-check that the relation
20123 : : * exists at truncation time.
20124 : : */
20125 [ + + ]: 625210 : if (oids_to_truncate != NIL)
20126 : 254 : heap_truncate(oids_to_truncate);
20127 : :
20128 [ + + ]: 625206 : if (oids_to_drop != NIL)
20129 : : {
20130 : 31 : ObjectAddresses *targetObjects = new_object_addresses();
20131 : :
20132 [ + - + + : 66 : foreach(l, oids_to_drop)
+ + ]
20133 : : {
20134 : : ObjectAddress object;
20135 : :
20136 : 35 : object.classId = RelationRelationId;
20137 : 35 : object.objectId = lfirst_oid(l);
20138 : 35 : object.objectSubId = 0;
20139 : :
20140 : : Assert(!object_address_present(&object, targetObjects));
20141 : :
20142 : 35 : add_exact_object_address(&object, targetObjects);
20143 : : }
20144 : :
20145 : : /*
20146 : : * Object deletion might involve toast table access (to clean up
20147 : : * toasted catalog entries), so ensure we have a valid snapshot.
20148 : : */
20149 : 31 : PushActiveSnapshot(GetTransactionSnapshot());
20150 : :
20151 : : /*
20152 : : * Since this is an automatic drop, rather than one directly initiated
20153 : : * by the user, we pass the PERFORM_DELETION_INTERNAL flag.
20154 : : */
20155 : 31 : performMultipleDeletions(targetObjects, DROP_CASCADE,
20156 : : PERFORM_DELETION_INTERNAL | PERFORM_DELETION_QUIETLY);
20157 : :
20158 : 31 : PopActiveSnapshot();
20159 : :
20160 : : #ifdef USE_ASSERT_CHECKING
20161 : :
20162 : : /*
20163 : : * Note that table deletion will call remove_on_commit_action, so the
20164 : : * entry should get marked as deleted.
20165 : : */
20166 : : foreach(l, on_commits)
20167 : : {
20168 : : OnCommitItem *oc = (OnCommitItem *) lfirst(l);
20169 : :
20170 : : if (oc->oncommit != ONCOMMIT_DROP)
20171 : : continue;
20172 : :
20173 : : Assert(oc->deleting_subid != InvalidSubTransactionId);
20174 : : }
20175 : : #endif
20176 : : }
20177 : 625206 : }
20178 : :
20179 : : /*
20180 : : * Post-commit or post-abort cleanup for ON COMMIT management.
20181 : : *
20182 : : * All we do here is remove no-longer-needed OnCommitItem entries.
20183 : : *
20184 : : * During commit, remove entries that were deleted during this transaction;
20185 : : * during abort, remove those created during this transaction.
20186 : : */
20187 : : void
20188 : 661206 : AtEOXact_on_commit_actions(bool isCommit)
20189 : : {
20190 : : ListCell *cur_item;
20191 : :
20192 [ + + + + : 661776 : foreach(cur_item, on_commits)
+ + ]
20193 : : {
20194 : 570 : OnCommitItem *oc = (OnCommitItem *) lfirst(cur_item);
20195 : :
20196 [ + + + + ]: 642 : if (isCommit ? oc->deleting_subid != InvalidSubTransactionId :
20197 : 72 : oc->creating_subid != InvalidSubTransactionId)
20198 : : {
20199 : : /* cur_item must be removed */
20200 : 104 : on_commits = foreach_delete_current(on_commits, cur_item);
20201 : 104 : pfree(oc);
20202 : : }
20203 : : else
20204 : : {
20205 : : /* cur_item must be preserved */
20206 : 466 : oc->creating_subid = InvalidSubTransactionId;
20207 : 466 : oc->deleting_subid = InvalidSubTransactionId;
20208 : : }
20209 : : }
20210 : 661206 : }
20211 : :
20212 : : /*
20213 : : * Post-subcommit or post-subabort cleanup for ON COMMIT management.
20214 : : *
20215 : : * During subabort, we can immediately remove entries created during this
20216 : : * subtransaction. During subcommit, just relabel entries marked during
20217 : : * this subtransaction as being the parent's responsibility.
20218 : : */
20219 : : void
20220 : 12698 : AtEOSubXact_on_commit_actions(bool isCommit, SubTransactionId mySubid,
20221 : : SubTransactionId parentSubid)
20222 : : {
20223 : : ListCell *cur_item;
20224 : :
20225 [ - + - - : 12698 : foreach(cur_item, on_commits)
- + ]
20226 : : {
20227 : 0 : OnCommitItem *oc = (OnCommitItem *) lfirst(cur_item);
20228 : :
20229 [ # # # # ]: 0 : if (!isCommit && oc->creating_subid == mySubid)
20230 : : {
20231 : : /* cur_item must be removed */
20232 : 0 : on_commits = foreach_delete_current(on_commits, cur_item);
20233 : 0 : pfree(oc);
20234 : : }
20235 : : else
20236 : : {
20237 : : /* cur_item must be preserved */
20238 [ # # ]: 0 : if (oc->creating_subid == mySubid)
20239 : 0 : oc->creating_subid = parentSubid;
20240 [ # # ]: 0 : if (oc->deleting_subid == mySubid)
20241 [ # # ]: 0 : oc->deleting_subid = isCommit ? parentSubid : InvalidSubTransactionId;
20242 : : }
20243 : : }
20244 : 12698 : }
20245 : :
20246 : : /*
20247 : : * This is intended as a callback for RangeVarGetRelidExtended(). It allows
20248 : : * the relation to be locked only if (1) it's a plain or partitioned table,
20249 : : * materialized view, or TOAST table and (2) the current user is the owner (or
20250 : : * the superuser) or has been granted MAINTAIN. This meets the
20251 : : * permission-checking needs of CLUSTER, REINDEX TABLE, and REFRESH
20252 : : * MATERIALIZED VIEW; we expose it here so that it can be used by all.
20253 : : */
20254 : : void
20255 : 719 : RangeVarCallbackMaintainsTable(const RangeVar *relation,
20256 : : Oid relId, Oid oldRelId, void *arg)
20257 : : {
20258 : : char relkind;
20259 : : AclResult aclresult;
20260 : :
20261 : : /* Nothing to do if the relation was not found. */
20262 [ + + ]: 719 : if (!OidIsValid(relId))
20263 : 3 : return;
20264 : :
20265 : : /*
20266 : : * If the relation does exist, check whether it's an index. But note that
20267 : : * the relation might have been dropped between the time we did the name
20268 : : * lookup and now. In that case, there's nothing to do.
20269 : : */
20270 : 716 : relkind = get_rel_relkind(relId);
20271 [ - + ]: 716 : if (!relkind)
20272 : 0 : return;
20273 [ + + + + : 716 : if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
+ + ]
20274 [ + + ]: 102 : relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
20275 [ + - ]: 18 : ereport(ERROR,
20276 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20277 : : errmsg("\"%s\" is not a table or materialized view", relation->relname)));
20278 : :
20279 : : /* Check permissions */
20280 : 698 : aclresult = pg_class_aclcheck(relId, GetUserId(), ACL_MAINTAIN);
20281 [ + + ]: 698 : if (aclresult != ACLCHECK_OK)
20282 : 20 : aclcheck_error(aclresult,
20283 : 20 : get_relkind_objtype(get_rel_relkind(relId)),
20284 : 20 : relation->relname);
20285 : : }
20286 : :
20287 : : /*
20288 : : * Callback to RangeVarGetRelidExtended() for TRUNCATE processing.
20289 : : */
20290 : : static void
20291 : 1479 : RangeVarCallbackForTruncate(const RangeVar *relation,
20292 : : Oid relId, Oid oldRelId, void *arg)
20293 : : {
20294 : : HeapTuple tuple;
20295 : :
20296 : : /* Nothing to do if the relation was not found. */
20297 [ - + ]: 1479 : if (!OidIsValid(relId))
20298 : 0 : return;
20299 : :
20300 : 1479 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relId));
20301 [ - + ]: 1479 : if (!HeapTupleIsValid(tuple)) /* should not happen */
20302 [ # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", relId);
20303 : :
20304 : 1479 : truncate_check_rel(relId, (Form_pg_class) GETSTRUCT(tuple));
20305 : 1476 : truncate_check_perms(relId, (Form_pg_class) GETSTRUCT(tuple));
20306 : :
20307 : 1456 : ReleaseSysCache(tuple);
20308 : : }
20309 : :
20310 : : /*
20311 : : * Callback for RangeVarGetRelidExtended(). Checks that the current user is
20312 : : * the owner of the relation, or superuser.
20313 : : */
20314 : : void
20315 : 12249 : RangeVarCallbackOwnsRelation(const RangeVar *relation,
20316 : : Oid relId, Oid oldRelId, void *arg)
20317 : : {
20318 : : HeapTuple tuple;
20319 : :
20320 : : /* Nothing to do if the relation was not found. */
20321 [ + + ]: 12249 : if (!OidIsValid(relId))
20322 : 16 : return;
20323 : :
20324 : 12233 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relId));
20325 [ - + ]: 12233 : if (!HeapTupleIsValid(tuple)) /* should not happen */
20326 [ # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", relId);
20327 : :
20328 [ + + ]: 12233 : if (!object_ownercheck(RelationRelationId, relId, GetUserId()))
20329 : 16 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relId)),
20330 : 16 : relation->relname);
20331 : :
20332 : : /*
20333 : : * Conflict log tables are used internally for logical replication
20334 : : * conflict logging and should not be modified directly, as it could
20335 : : * disrupt conflict logging.
20336 : : */
20337 [ + + ]: 12217 : if (IsConflictLogTableClass((Form_pg_class) GETSTRUCT(tuple)))
20338 [ + - ]: 4 : ereport(ERROR,
20339 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20340 : : errmsg("cannot change conflict log table \"%s\"",
20341 : : relation->relname),
20342 : : errdetail("Conflict log tables are system-managed tables for logical replication conflicts.")));
20343 : :
20344 [ + + + + ]: 24366 : if (!allowSystemTableMods &&
20345 : 12153 : IsSystemClass(relId, (Form_pg_class) GETSTRUCT(tuple)))
20346 [ + - ]: 1 : ereport(ERROR,
20347 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
20348 : : errmsg("permission denied: \"%s\" is a system catalog",
20349 : : relation->relname)));
20350 : :
20351 : 12212 : ReleaseSysCache(tuple);
20352 : : }
20353 : :
20354 : : /*
20355 : : * Common RangeVarGetRelid callback for rename, set schema, and alter table
20356 : : * processing.
20357 : : */
20358 : : static void
20359 : 23169 : RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
20360 : : void *arg)
20361 : : {
20362 : 23169 : Node *stmt = (Node *) arg;
20363 : : ObjectType reltype;
20364 : : HeapTuple tuple;
20365 : : Form_pg_class classform;
20366 : : AclResult aclresult;
20367 : : char relkind;
20368 : :
20369 : 23169 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
20370 [ + + ]: 23169 : if (!HeapTupleIsValid(tuple))
20371 : 162 : return; /* concurrently dropped */
20372 : 23007 : classform = (Form_pg_class) GETSTRUCT(tuple);
20373 : 23007 : relkind = classform->relkind;
20374 : :
20375 : : /* Must own relation. */
20376 [ + + ]: 23007 : if (!object_ownercheck(RelationRelationId, relid, GetUserId()))
20377 : 56 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
20378 : :
20379 : : /*
20380 : : * Conflict log tables are used internally for logical replication
20381 : : * conflict logging and should not be altered directly, as it could
20382 : : * disrupt conflict logging.
20383 : : */
20384 [ + + ]: 22951 : if (IsConflictLogTableClass(classform))
20385 [ + - ]: 13 : ereport(ERROR,
20386 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20387 : : errmsg("cannot alter conflict log table \"%s\"",
20388 : : rv->relname),
20389 : : errdetail("Conflict log tables are system-managed tables for logical replication conflicts.")));
20390 : :
20391 : : /* No system table modifications unless explicitly allowed. */
20392 [ + + + + ]: 22938 : if (!allowSystemTableMods && IsSystemClass(relid, classform))
20393 [ + - ]: 18 : ereport(ERROR,
20394 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
20395 : : errmsg("permission denied: \"%s\" is a system catalog",
20396 : : rv->relname)));
20397 : :
20398 : : /*
20399 : : * Extract the specified relation type from the statement parse tree.
20400 : : *
20401 : : * Also, for ALTER .. RENAME, check permissions: the user must (still)
20402 : : * have CREATE rights on the containing namespace.
20403 : : */
20404 [ + + ]: 22920 : if (IsA(stmt, RenameStmt))
20405 : : {
20406 : 309 : aclresult = object_aclcheck(NamespaceRelationId, classform->relnamespace,
20407 : : GetUserId(), ACL_CREATE);
20408 [ - + ]: 309 : if (aclresult != ACLCHECK_OK)
20409 : 0 : aclcheck_error(aclresult, OBJECT_SCHEMA,
20410 : 0 : get_namespace_name(classform->relnamespace));
20411 : 309 : reltype = ((RenameStmt *) stmt)->renameType;
20412 : : }
20413 [ + + ]: 22611 : else if (IsA(stmt, AlterObjectSchemaStmt))
20414 : 75 : reltype = ((AlterObjectSchemaStmt *) stmt)->objectType;
20415 : :
20416 [ + - ]: 22536 : else if (IsA(stmt, AlterTableStmt))
20417 : 22536 : reltype = ((AlterTableStmt *) stmt)->objtype;
20418 : : else
20419 : : {
20420 [ # # ]: 0 : elog(ERROR, "unrecognized node type: %d", (int) nodeTag(stmt));
20421 : : reltype = OBJECT_TABLE; /* placate compiler */
20422 : : }
20423 : :
20424 : : /*
20425 : : * For compatibility with prior releases, we allow ALTER TABLE to be used
20426 : : * with most other types of relations (but not composite types). We allow
20427 : : * similar flexibility for ALTER INDEX in the case of RENAME, but not
20428 : : * otherwise. Otherwise, the user must select the correct form of the
20429 : : * command for the relation at issue.
20430 : : */
20431 [ + + - + ]: 22920 : if (reltype == OBJECT_SEQUENCE && relkind != RELKIND_SEQUENCE)
20432 [ # # ]: 0 : ereport(ERROR,
20433 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20434 : : errmsg("\"%s\" is not a sequence", rv->relname)));
20435 : :
20436 [ + + - + ]: 22920 : if (reltype == OBJECT_VIEW && relkind != RELKIND_VIEW)
20437 [ # # ]: 0 : ereport(ERROR,
20438 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20439 : : errmsg("\"%s\" is not a view", rv->relname)));
20440 : :
20441 [ + + - + ]: 22920 : if (reltype == OBJECT_MATVIEW && relkind != RELKIND_MATVIEW)
20442 [ # # ]: 0 : ereport(ERROR,
20443 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20444 : : errmsg("\"%s\" is not a materialized view", rv->relname)));
20445 : :
20446 [ + + - + ]: 22920 : if (reltype == OBJECT_FOREIGN_TABLE && relkind != RELKIND_FOREIGN_TABLE)
20447 [ # # ]: 0 : ereport(ERROR,
20448 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20449 : : errmsg("\"%s\" is not a foreign table", rv->relname)));
20450 : :
20451 [ + + - + ]: 22920 : if (reltype == OBJECT_TYPE && relkind != RELKIND_COMPOSITE_TYPE)
20452 [ # # ]: 0 : ereport(ERROR,
20453 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20454 : : errmsg("\"%s\" is not a composite type", rv->relname)));
20455 : :
20456 [ + + - + ]: 22920 : if (reltype == OBJECT_PROPGRAPH && relkind != RELKIND_PROPGRAPH)
20457 [ # # ]: 0 : ereport(ERROR,
20458 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20459 : : errmsg("\"%s\" is not a property graph", rv->relname)));
20460 : :
20461 [ + + + + : 22920 : if (reltype == OBJECT_INDEX && relkind != RELKIND_INDEX &&
+ + ]
20462 : : relkind != RELKIND_PARTITIONED_INDEX
20463 [ + + ]: 24 : && !IsA(stmt, RenameStmt))
20464 [ + - ]: 4 : ereport(ERROR,
20465 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20466 : : errmsg("\"%s\" is not an index", rv->relname)));
20467 : :
20468 : : /*
20469 : : * Don't allow ALTER TABLE on composite types. We want people to use ALTER
20470 : : * TYPE for that.
20471 : : */
20472 [ + + - + ]: 22916 : if (reltype != OBJECT_TYPE && relkind == RELKIND_COMPOSITE_TYPE)
20473 [ # # ]: 0 : ereport(ERROR,
20474 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20475 : : errmsg("\"%s\" is a composite type", rv->relname),
20476 : : /* translator: %s is an SQL ALTER command */
20477 : : errhint("Use %s instead.",
20478 : : "ALTER TYPE")));
20479 : :
20480 : : /*
20481 : : * Don't allow ALTER TABLE .. SET SCHEMA on relations that can't be moved
20482 : : * to a different schema, such as indexes and TOAST tables.
20483 : : */
20484 [ + + ]: 22916 : if (IsA(stmt, AlterObjectSchemaStmt))
20485 : : {
20486 [ + - - + ]: 75 : if (relkind == RELKIND_INDEX || relkind == RELKIND_PARTITIONED_INDEX)
20487 [ # # ]: 0 : ereport(ERROR,
20488 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20489 : : errmsg("cannot change schema of index \"%s\"",
20490 : : rv->relname),
20491 : : errhint("Change the schema of the table instead.")));
20492 [ - + ]: 75 : else if (relkind == RELKIND_COMPOSITE_TYPE)
20493 [ # # ]: 0 : ereport(ERROR,
20494 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20495 : : errmsg("cannot change schema of composite type \"%s\"",
20496 : : rv->relname),
20497 : : /* translator: %s is an SQL ALTER command */
20498 : : errhint("Use %s instead.",
20499 : : "ALTER TYPE")));
20500 [ - + ]: 75 : else if (relkind == RELKIND_TOASTVALUE)
20501 [ # # ]: 0 : ereport(ERROR,
20502 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20503 : : errmsg("cannot change schema of TOAST table \"%s\"",
20504 : : rv->relname),
20505 : : errhint("Change the schema of the table instead.")));
20506 : : }
20507 : :
20508 : 22916 : ReleaseSysCache(tuple);
20509 : : }
20510 : :
20511 : : /*
20512 : : * Transform any expressions present in the partition key
20513 : : *
20514 : : * Returns a transformed PartitionSpec.
20515 : : */
20516 : : static PartitionSpec *
20517 : 3668 : transformPartitionSpec(Relation rel, PartitionSpec *partspec)
20518 : : {
20519 : : PartitionSpec *newspec;
20520 : : ParseState *pstate;
20521 : : ParseNamespaceItem *nsitem;
20522 : : ListCell *l;
20523 : :
20524 : 3668 : newspec = makeNode(PartitionSpec);
20525 : :
20526 : 3668 : newspec->strategy = partspec->strategy;
20527 : 3668 : newspec->partParams = NIL;
20528 : 3668 : newspec->location = partspec->location;
20529 : :
20530 : : /* Check valid number of columns for strategy */
20531 [ + + + + ]: 5306 : if (partspec->strategy == PARTITION_STRATEGY_LIST &&
20532 : 1638 : list_length(partspec->partParams) != 1)
20533 [ + - ]: 4 : ereport(ERROR,
20534 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
20535 : : errmsg("cannot use \"list\" partition strategy with more than one column")));
20536 : :
20537 : : /*
20538 : : * Create a dummy ParseState and insert the target relation as its sole
20539 : : * rangetable entry. We need a ParseState for transformExpr.
20540 : : */
20541 : 3664 : pstate = make_parsestate(NULL);
20542 : 3664 : nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock,
20543 : : NULL, false, true);
20544 : 3664 : addNSItemToQuery(pstate, nsitem, true, true, true);
20545 : :
20546 : : /* take care of any partition expressions */
20547 [ + - + + : 7630 : foreach(l, partspec->partParams)
+ + ]
20548 : : {
20549 : 3982 : PartitionElem *pelem = lfirst_node(PartitionElem, l);
20550 : :
20551 [ + + ]: 3982 : if (pelem->expr)
20552 : : {
20553 : : /* Copy, to avoid scribbling on the input */
20554 : 240 : pelem = copyObject(pelem);
20555 : :
20556 : : /* Now do parse transformation of the expression */
20557 : 240 : pelem->expr = transformExpr(pstate, pelem->expr,
20558 : : EXPR_KIND_PARTITION_EXPRESSION);
20559 : :
20560 : : /* we have to fix its collations too */
20561 : 224 : assign_expr_collations(pstate, pelem->expr);
20562 : : }
20563 : :
20564 : 3966 : newspec->partParams = lappend(newspec->partParams, pelem);
20565 : : }
20566 : :
20567 : 3648 : return newspec;
20568 : : }
20569 : :
20570 : : /*
20571 : : * Compute per-partition-column information from a list of PartitionElems.
20572 : : * Expressions in the PartitionElems must be parse-analyzed already.
20573 : : */
20574 : : static void
20575 : 3648 : ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNumber *partattrs,
20576 : : List **partexprs, Oid *partopclass, Oid *partcollation,
20577 : : PartitionStrategy strategy)
20578 : : {
20579 : : int attn;
20580 : : ListCell *lc;
20581 : : Oid am_oid;
20582 : :
20583 : 3648 : attn = 0;
20584 [ + - + + : 7526 : foreach(lc, partParams)
+ + ]
20585 : : {
20586 : 3966 : PartitionElem *pelem = lfirst_node(PartitionElem, lc);
20587 : : Oid atttype;
20588 : : Oid attcollation;
20589 : :
20590 [ + + ]: 3966 : if (pelem->name != NULL)
20591 : : {
20592 : : /* Simple attribute reference */
20593 : : HeapTuple atttuple;
20594 : : Form_pg_attribute attform;
20595 : :
20596 : 3742 : atttuple = SearchSysCacheAttName(RelationGetRelid(rel),
20597 : 3742 : pelem->name);
20598 [ + + ]: 3742 : if (!HeapTupleIsValid(atttuple))
20599 [ + - ]: 8 : ereport(ERROR,
20600 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
20601 : : errmsg("column \"%s\" named in partition key does not exist",
20602 : : pelem->name),
20603 : : parser_errposition(pstate, pelem->location)));
20604 : 3734 : attform = (Form_pg_attribute) GETSTRUCT(atttuple);
20605 : :
20606 [ + + ]: 3734 : if (attform->attnum <= 0)
20607 [ + - ]: 4 : ereport(ERROR,
20608 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
20609 : : errmsg("cannot use system column \"%s\" in partition key",
20610 : : pelem->name),
20611 : : parser_errposition(pstate, pelem->location)));
20612 : :
20613 : : /*
20614 : : * Stored generated columns cannot work: They are computed after
20615 : : * BEFORE triggers, but partition routing is done before all
20616 : : * triggers. Maybe virtual generated columns could be made to
20617 : : * work, but then they would need to be handled as an expression
20618 : : * below.
20619 : : */
20620 [ + + ]: 3730 : if (attform->attgenerated)
20621 [ + - ]: 8 : ereport(ERROR,
20622 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
20623 : : errmsg("cannot use generated column in partition key"),
20624 : : errdetail("Column \"%s\" is a generated column.",
20625 : : pelem->name),
20626 : : parser_errposition(pstate, pelem->location)));
20627 : :
20628 : 3722 : partattrs[attn] = attform->attnum;
20629 : 3722 : atttype = attform->atttypid;
20630 : 3722 : attcollation = attform->attcollation;
20631 : 3722 : ReleaseSysCache(atttuple);
20632 : : }
20633 : : else
20634 : : {
20635 : : /* Expression */
20636 : 224 : Node *expr = pelem->expr;
20637 : : char partattname[16];
20638 : 224 : Bitmapset *expr_attrs = NULL;
20639 : : int i;
20640 : :
20641 : : Assert(expr != NULL);
20642 : 224 : atttype = exprType(expr);
20643 : 224 : attcollation = exprCollation(expr);
20644 : :
20645 : : /*
20646 : : * The expression must be of a storable type (e.g., not RECORD).
20647 : : * The test is the same as for whether a table column is of a safe
20648 : : * type (which is why we needn't check for the non-expression
20649 : : * case).
20650 : : */
20651 : 224 : snprintf(partattname, sizeof(partattname), "%d", attn + 1);
20652 : 224 : CheckAttributeType(partattname,
20653 : : atttype, attcollation,
20654 : : NIL, CHKATYPE_IS_PARTKEY);
20655 : :
20656 : : /*
20657 : : * Strip any top-level COLLATE clause. This ensures that we treat
20658 : : * "x COLLATE y" and "(x COLLATE y)" alike.
20659 : : */
20660 [ - + ]: 216 : while (IsA(expr, CollateExpr))
20661 : 0 : expr = (Node *) ((CollateExpr *) expr)->arg;
20662 : :
20663 : : /*
20664 : : * Examine all the columns in the partition key expression. When
20665 : : * the whole-row reference is present, examine all the columns of
20666 : : * the partitioned table.
20667 : : */
20668 : 216 : pull_varattnos(expr, 1, &expr_attrs);
20669 [ + + ]: 216 : if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber, expr_attrs))
20670 : : {
20671 : 40 : expr_attrs = bms_add_range(expr_attrs,
20672 : : 1 - FirstLowInvalidHeapAttributeNumber,
20673 : 20 : RelationGetNumberOfAttributes(rel) - FirstLowInvalidHeapAttributeNumber);
20674 : 20 : expr_attrs = bms_del_member(expr_attrs, 0 - FirstLowInvalidHeapAttributeNumber);
20675 : : }
20676 : :
20677 : 216 : i = -1;
20678 [ + + ]: 473 : while ((i = bms_next_member(expr_attrs, i)) >= 0)
20679 : : {
20680 : 289 : AttrNumber attno = i + FirstLowInvalidHeapAttributeNumber;
20681 : :
20682 : : Assert(attno != 0);
20683 : :
20684 : : /*
20685 : : * Cannot allow system column references, since that would
20686 : : * make partition routing impossible: their values won't be
20687 : : * known yet when we need to do that.
20688 : : */
20689 [ - + ]: 289 : if (attno < 0)
20690 [ # # ]: 0 : ereport(ERROR,
20691 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
20692 : : errmsg("partition key expressions cannot contain system column references")));
20693 : :
20694 : : /*
20695 : : * Stored generated columns cannot work: They are computed
20696 : : * after BEFORE triggers, but partition routing is done before
20697 : : * all triggers. Virtual generated columns could probably
20698 : : * work, but it would require more work elsewhere (for example
20699 : : * SET EXPRESSION would need to check whether the column is
20700 : : * used in partition keys). Seems safer to prohibit for now.
20701 : : */
20702 [ + + ]: 289 : if (TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated)
20703 [ + - ]: 32 : ereport(ERROR,
20704 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
20705 : : errmsg("cannot use generated column in partition key"),
20706 : : errdetail("Column \"%s\" is a generated column.",
20707 : : get_attname(RelationGetRelid(rel), attno, false)),
20708 : : parser_errposition(pstate, pelem->location)));
20709 : : }
20710 : :
20711 [ + + ]: 184 : if (IsA(expr, Var) &&
20712 [ + + ]: 8 : ((Var *) expr)->varattno > 0)
20713 : : {
20714 : :
20715 : : /*
20716 : : * User wrote "(column)" or "(column COLLATE something)".
20717 : : * Treat it like simple attribute anyway.
20718 : : */
20719 : 4 : partattrs[attn] = ((Var *) expr)->varattno;
20720 : : }
20721 : : else
20722 : : {
20723 : 180 : partattrs[attn] = 0; /* marks the column as expression */
20724 : 180 : *partexprs = lappend(*partexprs, expr);
20725 : :
20726 : : /*
20727 : : * transformPartitionSpec() should have already rejected
20728 : : * subqueries, aggregates, window functions, and SRFs, based
20729 : : * on the EXPR_KIND_ for partition expressions.
20730 : : */
20731 : :
20732 : : /*
20733 : : * Preprocess the expression before checking for mutability.
20734 : : * This is essential for the reasons described in
20735 : : * contain_mutable_functions_after_planning. However, we call
20736 : : * expression_planner for ourselves rather than using that
20737 : : * function, because if constant-folding reduces the
20738 : : * expression to a constant, we'd like to know that so we can
20739 : : * complain below.
20740 : : *
20741 : : * Like contain_mutable_functions_after_planning, assume that
20742 : : * expression_planner won't scribble on its input, so this
20743 : : * won't affect the partexprs entry we saved above.
20744 : : */
20745 : 180 : expr = (Node *) expression_planner((Expr *) expr);
20746 : :
20747 : : /*
20748 : : * Partition expressions cannot contain mutable functions,
20749 : : * because a given row must always map to the same partition
20750 : : * as long as there is no change in the partition boundary
20751 : : * structure.
20752 : : */
20753 [ + + ]: 180 : if (contain_mutable_functions(expr))
20754 [ + - ]: 4 : ereport(ERROR,
20755 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
20756 : : errmsg("functions in partition key expression must be marked IMMUTABLE")));
20757 : :
20758 : : /*
20759 : : * While it is not exactly *wrong* for a partition expression
20760 : : * to be a constant, it seems better to reject such keys.
20761 : : */
20762 [ + + ]: 176 : if (IsA(expr, Const))
20763 [ + - ]: 8 : ereport(ERROR,
20764 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
20765 : : errmsg("cannot use constant expression as partition key")));
20766 : : }
20767 : : }
20768 : :
20769 : : /*
20770 : : * Apply collation override if any
20771 : : */
20772 [ + + ]: 3894 : if (pelem->collation)
20773 : 36 : attcollation = get_collation_oid(pelem->collation, false);
20774 : :
20775 : : /*
20776 : : * Check we have a collation iff it's a collatable type. The only
20777 : : * expected failures here are (1) COLLATE applied to a noncollatable
20778 : : * type, or (2) partition expression had an unresolved collation. But
20779 : : * we might as well code this to be a complete consistency check.
20780 : : */
20781 [ + + ]: 3894 : if (type_is_collatable(atttype))
20782 : : {
20783 [ - + ]: 444 : if (!OidIsValid(attcollation))
20784 [ # # ]: 0 : ereport(ERROR,
20785 : : (errcode(ERRCODE_INDETERMINATE_COLLATION),
20786 : : errmsg("could not determine which collation to use for partition expression"),
20787 : : errhint("Use the COLLATE clause to set the collation explicitly.")));
20788 : : }
20789 : : else
20790 : : {
20791 [ - + ]: 3450 : if (OidIsValid(attcollation))
20792 [ # # ]: 0 : ereport(ERROR,
20793 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
20794 : : errmsg("collations are not supported by type %s",
20795 : : format_type_be(atttype))));
20796 : : }
20797 : :
20798 : 3894 : partcollation[attn] = attcollation;
20799 : :
20800 : : /*
20801 : : * Identify the appropriate operator class. For list and range
20802 : : * partitioning, we use a btree operator class; hash partitioning uses
20803 : : * a hash operator class.
20804 : : */
20805 [ + + ]: 3894 : if (strategy == PARTITION_STRATEGY_HASH)
20806 : 215 : am_oid = HASH_AM_OID;
20807 : : else
20808 : 3679 : am_oid = BTREE_AM_OID;
20809 : :
20810 [ + + ]: 3894 : if (!pelem->opclass)
20811 : : {
20812 : 3802 : partopclass[attn] = GetDefaultOpClass(atttype, am_oid);
20813 : :
20814 [ + + ]: 3802 : if (!OidIsValid(partopclass[attn]))
20815 : : {
20816 [ - + ]: 8 : if (strategy == PARTITION_STRATEGY_HASH)
20817 [ # # ]: 0 : ereport(ERROR,
20818 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
20819 : : errmsg("data type %s has no default operator class for access method \"%s\"",
20820 : : format_type_be(atttype), "hash"),
20821 : : errhint("You must specify a hash operator class or define a default hash operator class for the data type.")));
20822 : : else
20823 [ + - ]: 8 : ereport(ERROR,
20824 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
20825 : : errmsg("data type %s has no default operator class for access method \"%s\"",
20826 : : format_type_be(atttype), "btree"),
20827 : : errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
20828 : : }
20829 : : }
20830 : : else
20831 [ + + ]: 92 : partopclass[attn] = ResolveOpClass(pelem->opclass,
20832 : : atttype,
20833 : : am_oid == HASH_AM_OID ? "hash" : "btree",
20834 : : am_oid);
20835 : :
20836 : 3878 : attn++;
20837 : : }
20838 : 3560 : }
20839 : :
20840 : : /*
20841 : : * PartConstraintImpliedByRelConstraint
20842 : : * Do scanrel's existing constraints imply the partition constraint?
20843 : : *
20844 : : * "Existing constraints" include its check constraints and column-level
20845 : : * not-null constraints. partConstraint describes the partition constraint,
20846 : : * in implicit-AND form.
20847 : : */
20848 : : bool
20849 : 2071 : PartConstraintImpliedByRelConstraint(Relation scanrel,
20850 : : List *partConstraint)
20851 : : {
20852 : 2071 : List *existConstraint = NIL;
20853 : 2071 : TupleConstr *constr = RelationGetDescr(scanrel)->constr;
20854 : : int i;
20855 : :
20856 [ + + + + ]: 2071 : if (constr && constr->has_not_null)
20857 : : {
20858 : 544 : int natts = scanrel->rd_att->natts;
20859 : :
20860 [ + + ]: 1886 : for (i = 1; i <= natts; i++)
20861 : : {
20862 : 1342 : CompactAttribute *att = TupleDescCompactAttr(scanrel->rd_att, i - 1);
20863 : :
20864 : : /* invalid not-null constraint must be ignored here */
20865 [ + + + - ]: 1342 : if (att->attnullability == ATTNULLABLE_VALID && !att->attisdropped)
20866 : : {
20867 : 747 : Form_pg_attribute wholeatt = TupleDescAttr(scanrel->rd_att, i - 1);
20868 : 747 : NullTest *ntest = makeNode(NullTest);
20869 : :
20870 : 747 : ntest->arg = (Expr *) makeVar(1,
20871 : : i,
20872 : : wholeatt->atttypid,
20873 : : wholeatt->atttypmod,
20874 : : wholeatt->attcollation,
20875 : : 0);
20876 : 747 : ntest->nulltesttype = IS_NOT_NULL;
20877 : :
20878 : : /*
20879 : : * argisrow=false is correct even for a composite column,
20880 : : * because attnotnull does not represent a SQL-spec IS NOT
20881 : : * NULL test in such a case, just IS DISTINCT FROM NULL.
20882 : : */
20883 : 747 : ntest->argisrow = false;
20884 : 747 : ntest->location = -1;
20885 : 747 : existConstraint = lappend(existConstraint, ntest);
20886 : : }
20887 : : }
20888 : : }
20889 : :
20890 : 2071 : return ConstraintImpliedByRelConstraint(scanrel, partConstraint, existConstraint);
20891 : : }
20892 : :
20893 : : /*
20894 : : * ConstraintImpliedByRelConstraint
20895 : : * Do scanrel's existing constraints imply the given constraint?
20896 : : *
20897 : : * testConstraint is the constraint to validate. provenConstraint is a
20898 : : * caller-provided list of conditions which this function may assume
20899 : : * to be true. Both provenConstraint and testConstraint must be in
20900 : : * implicit-AND form, must only contain immutable clauses, and must
20901 : : * contain only Vars with varno = 1.
20902 : : */
20903 : : bool
20904 : 2895 : ConstraintImpliedByRelConstraint(Relation scanrel, List *testConstraint, List *provenConstraint)
20905 : : {
20906 : 2895 : List *existConstraint = list_copy(provenConstraint);
20907 : 2895 : TupleConstr *constr = RelationGetDescr(scanrel)->constr;
20908 : : int num_check,
20909 : : i;
20910 : :
20911 [ + + ]: 2895 : num_check = (constr != NULL) ? constr->num_check : 0;
20912 [ + + ]: 3231 : for (i = 0; i < num_check; i++)
20913 : : {
20914 : : Node *cexpr;
20915 : :
20916 : : /*
20917 : : * If this constraint hasn't been fully validated yet, we must ignore
20918 : : * it here.
20919 : : */
20920 [ + + ]: 336 : if (!constr->check[i].ccvalid)
20921 : 12 : continue;
20922 : :
20923 : : /*
20924 : : * NOT ENFORCED constraints are always marked as invalid, which should
20925 : : * have been ignored.
20926 : : */
20927 : : Assert(constr->check[i].ccenforced);
20928 : :
20929 : 324 : cexpr = stringToNode(constr->check[i].ccbin);
20930 : :
20931 : : /*
20932 : : * Run each expression through const-simplification and
20933 : : * canonicalization. It is necessary, because we will be comparing it
20934 : : * to similarly-processed partition constraint expressions, and may
20935 : : * fail to detect valid matches without this.
20936 : : */
20937 : 324 : cexpr = eval_const_expressions(NULL, cexpr);
20938 : 324 : cexpr = (Node *) canonicalize_qual((Expr *) cexpr, true);
20939 : :
20940 : 324 : existConstraint = list_concat(existConstraint,
20941 : 324 : make_ands_implicit((Expr *) cexpr));
20942 : : }
20943 : :
20944 : : /*
20945 : : * Try to make the proof. Since we are comparing CHECK constraints, we
20946 : : * need to use weak implication, i.e., we assume existConstraint is
20947 : : * not-false and try to prove the same for testConstraint.
20948 : : *
20949 : : * Note that predicate_implied_by assumes its first argument is known
20950 : : * immutable. That should always be true for both NOT NULL and partition
20951 : : * constraints, so we don't test it here.
20952 : : */
20953 : 2895 : return predicate_implied_by(testConstraint, existConstraint, true);
20954 : : }
20955 : :
20956 : : /*
20957 : : * QueuePartitionConstraintValidation
20958 : : *
20959 : : * Add an entry to wqueue to have the given partition constraint validated by
20960 : : * Phase 3, for the given relation, and all its children.
20961 : : *
20962 : : * We first verify whether the given constraint is implied by pre-existing
20963 : : * relation constraints; if it is, there's no need to scan the table to
20964 : : * validate, so don't queue in that case.
20965 : : */
20966 : : static void
20967 : 1742 : QueuePartitionConstraintValidation(List **wqueue, Relation scanrel,
20968 : : List *partConstraint,
20969 : : bool validate_default)
20970 : : {
20971 : : /*
20972 : : * Based on the table's existing constraints, determine whether or not we
20973 : : * may skip scanning the table.
20974 : : */
20975 [ + + ]: 1742 : if (PartConstraintImpliedByRelConstraint(scanrel, partConstraint))
20976 : : {
20977 [ + + ]: 55 : if (!validate_default)
20978 [ + + ]: 41 : ereport(DEBUG1,
20979 : : (errmsg_internal("partition constraint for table \"%s\" is implied by existing constraints",
20980 : : RelationGetRelationName(scanrel))));
20981 : : else
20982 [ + + ]: 14 : ereport(DEBUG1,
20983 : : (errmsg_internal("updated partition constraint for default partition \"%s\" is implied by existing constraints",
20984 : : RelationGetRelationName(scanrel))));
20985 : 55 : return;
20986 : : }
20987 : :
20988 : : /*
20989 : : * Constraints proved insufficient. For plain relations, queue a
20990 : : * validation item now; for partitioned tables, recurse to process each
20991 : : * partition.
20992 : : */
20993 [ + + ]: 1687 : if (scanrel->rd_rel->relkind == RELKIND_RELATION)
20994 : : {
20995 : : AlteredTableInfo *tab;
20996 : :
20997 : : /* Grab a work queue entry. */
20998 : 1412 : tab = ATGetQueueEntry(wqueue, scanrel);
20999 : : Assert(tab->partition_constraint == NULL);
21000 : 1412 : tab->partition_constraint = (Expr *) linitial(partConstraint);
21001 : 1412 : tab->validate_default = validate_default;
21002 : : }
21003 [ + + ]: 275 : else if (scanrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
21004 : : {
21005 : 244 : PartitionDesc partdesc = RelationGetPartitionDesc(scanrel, true);
21006 : : int i;
21007 : :
21008 [ + + ]: 524 : for (i = 0; i < partdesc->nparts; i++)
21009 : : {
21010 : : Relation part_rel;
21011 : : List *thisPartConstraint;
21012 : :
21013 : : /*
21014 : : * This is the minimum lock we need to prevent deadlocks.
21015 : : */
21016 : 280 : part_rel = table_open(partdesc->oids[i], AccessExclusiveLock);
21017 : :
21018 : : /*
21019 : : * Adjust the constraint for scanrel so that it matches this
21020 : : * partition's attribute numbers.
21021 : : */
21022 : : thisPartConstraint =
21023 : 280 : map_partition_varattnos(partConstraint, 1,
21024 : : part_rel, scanrel);
21025 : :
21026 : 280 : QueuePartitionConstraintValidation(wqueue, part_rel,
21027 : : thisPartConstraint,
21028 : : validate_default);
21029 : 280 : table_close(part_rel, NoLock); /* keep lock till commit */
21030 : : }
21031 : : }
21032 : : }
21033 : :
21034 : : /*
21035 : : * attachPartitionTable: attach a new partition to the partitioned table
21036 : : *
21037 : : * wqueue: the ALTER TABLE work queue; can be NULL when not running as part
21038 : : * of an ALTER TABLE sequence.
21039 : : * rel: partitioned relation;
21040 : : * attachrel: relation of attached partition;
21041 : : * bound: bounds of attached relation.
21042 : : */
21043 : : static void
21044 : 1985 : attachPartitionTable(List **wqueue, Relation rel, Relation attachrel, PartitionBoundSpec *bound)
21045 : : {
21046 : : /*
21047 : : * Create an inheritance; the relevant checks are performed inside the
21048 : : * function.
21049 : : */
21050 : 1985 : CreateInheritance(attachrel, rel, true);
21051 : :
21052 : : /* Update the pg_class entry. */
21053 : 1913 : StorePartitionBound(attachrel, rel, bound);
21054 : :
21055 : : /* Ensure there exists a correct set of indexes in the partition. */
21056 : 1913 : AttachPartitionEnsureIndexes(wqueue, rel, attachrel);
21057 : :
21058 : : /* and triggers */
21059 : 1893 : CloneRowTriggersToPartition(rel, attachrel);
21060 : :
21061 : : /*
21062 : : * Clone foreign key constraints. Callee is responsible for setting up
21063 : : * for phase 3 constraint verification.
21064 : : */
21065 : 1889 : CloneForeignKeyConstraints(wqueue, rel, attachrel);
21066 : 1877 : }
21067 : :
21068 : : /*
21069 : : * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
21070 : : *
21071 : : * Return the address of the newly attached partition.
21072 : : */
21073 : : static ObjectAddress
21074 : 1613 : ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
21075 : : AlterTableUtilityContext *context)
21076 : : {
21077 : : Relation attachrel,
21078 : : catalog;
21079 : : List *attachrel_children;
21080 : : List *partConstraint;
21081 : : SysScanDesc scan;
21082 : : ScanKeyData skey;
21083 : : AttrNumber attno;
21084 : : int natts;
21085 : : TupleDesc tupleDesc;
21086 : : ObjectAddress address;
21087 : : const char *trigger_name;
21088 : : Oid defaultPartOid;
21089 : : List *partBoundConstraint;
21090 : 1613 : List *exceptpuboids = NIL;
21091 : 1613 : ParseState *pstate = make_parsestate(NULL);
21092 : :
21093 : 1613 : pstate->p_sourcetext = context->queryString;
21094 : :
21095 : : /*
21096 : : * We must lock the default partition if one exists, because attaching a
21097 : : * new partition will change its partition constraint.
21098 : : */
21099 : : defaultPartOid =
21100 : 1613 : get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
21101 [ + + ]: 1613 : if (OidIsValid(defaultPartOid))
21102 : 117 : LockRelationOid(defaultPartOid, AccessExclusiveLock);
21103 : :
21104 : 1613 : attachrel = table_openrv(cmd->name, AccessExclusiveLock);
21105 : :
21106 : : /*
21107 : : * XXX I think it'd be a good idea to grab locks on all tables referenced
21108 : : * by FKs at this point also.
21109 : : */
21110 : :
21111 : : /*
21112 : : * Must be owner of both parent and source table -- parent was checked by
21113 : : * ATSimplePermissions call in ATPrepCmd
21114 : : */
21115 : 1609 : ATSimplePermissions(AT_AttachPartition, attachrel,
21116 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
21117 : :
21118 : : /* A partition can only have one parent */
21119 [ + + ]: 1605 : if (attachrel->rd_rel->relispartition)
21120 [ + - ]: 4 : ereport(ERROR,
21121 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
21122 : : errmsg("\"%s\" is already a partition",
21123 : : RelationGetRelationName(attachrel))));
21124 : :
21125 [ + + ]: 1601 : if (OidIsValid(attachrel->rd_rel->reloftype))
21126 [ + - ]: 4 : ereport(ERROR,
21127 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
21128 : : errmsg("cannot attach a typed table as partition")));
21129 : :
21130 : : /*
21131 : : * Disallow attaching a partition if the table is referenced in a
21132 : : * publication EXCEPT clause. Changing the partition hierarchy could alter
21133 : : * the effective publication membership.
21134 : : */
21135 : 1597 : exceptpuboids = GetRelationExcludedPublications(RelationGetRelid(attachrel));
21136 [ + + ]: 1597 : if (exceptpuboids != NIL)
21137 : : {
21138 : 4 : bool first = true;
21139 : : StringInfoData pubnames;
21140 : :
21141 : 4 : initStringInfo(&pubnames);
21142 : :
21143 [ + - + + : 12 : foreach_oid(pubid, exceptpuboids)
+ + ]
21144 : : {
21145 : 4 : char *pubname = get_publication_name(pubid, false);
21146 : :
21147 [ + - ]: 4 : if (first)
21148 : 4 : appendStringInfo(&pubnames, _("\"%s\""), pubname);
21149 : : else
21150 : 0 : appendStringInfo(&pubnames, _(", \"%s\""), pubname);
21151 : 4 : first = false;
21152 : : }
21153 : :
21154 [ + - ]: 4 : ereport(ERROR,
21155 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
21156 : : errmsg_plural("cannot attach table \"%s\" as partition because it is referenced in publication %s EXCEPT clause",
21157 : : "cannot attach table \"%s\" as partition because it is referenced in publications %s EXCEPT clause",
21158 : : list_length(exceptpuboids),
21159 : : RelationGetRelationName(attachrel),
21160 : : pubnames.data),
21161 : : errdetail("The publication EXCEPT clause cannot contain tables that are partitions."),
21162 : : errhint("Change the publication's EXCEPT clause using ALTER PUBLICATION ... SET ALL TABLES."));
21163 : : }
21164 : :
21165 : 1593 : list_free(exceptpuboids);
21166 : :
21167 : : /*
21168 : : * Table being attached should not already be part of inheritance; either
21169 : : * as a child table...
21170 : : */
21171 : 1593 : catalog = table_open(InheritsRelationId, AccessShareLock);
21172 : 1593 : ScanKeyInit(&skey,
21173 : : Anum_pg_inherits_inhrelid,
21174 : : BTEqualStrategyNumber, F_OIDEQ,
21175 : : ObjectIdGetDatum(RelationGetRelid(attachrel)));
21176 : 1593 : scan = systable_beginscan(catalog, InheritsRelidSeqnoIndexId, true,
21177 : : NULL, 1, &skey);
21178 [ + + ]: 1593 : if (HeapTupleIsValid(systable_getnext(scan)))
21179 [ + - ]: 4 : ereport(ERROR,
21180 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
21181 : : errmsg("cannot attach inheritance child as partition")));
21182 : 1589 : systable_endscan(scan);
21183 : :
21184 : : /* ...or as a parent table (except the case when it is partitioned) */
21185 : 1589 : ScanKeyInit(&skey,
21186 : : Anum_pg_inherits_inhparent,
21187 : : BTEqualStrategyNumber, F_OIDEQ,
21188 : : ObjectIdGetDatum(RelationGetRelid(attachrel)));
21189 : 1589 : scan = systable_beginscan(catalog, InheritsParentIndexId, true, NULL,
21190 : : 1, &skey);
21191 [ + + ]: 1589 : if (HeapTupleIsValid(systable_getnext(scan)) &&
21192 [ + + ]: 184 : attachrel->rd_rel->relkind == RELKIND_RELATION)
21193 [ + - ]: 4 : ereport(ERROR,
21194 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
21195 : : errmsg("cannot attach inheritance parent as partition")));
21196 : 1585 : systable_endscan(scan);
21197 : 1585 : table_close(catalog, AccessShareLock);
21198 : :
21199 : : /*
21200 : : * Prevent circularity by seeing if rel is a partition of attachrel. (In
21201 : : * particular, this disallows making a rel a partition of itself.)
21202 : : *
21203 : : * We do that by checking if rel is a member of the list of attachrel's
21204 : : * partitions provided the latter is partitioned at all. We want to avoid
21205 : : * having to construct this list again, so we request the strongest lock
21206 : : * on all partitions. We need the strongest lock, because we may decide
21207 : : * to scan them if we find out that the table being attached (or its leaf
21208 : : * partitions) may contain rows that violate the partition constraint. If
21209 : : * the table has a constraint that would prevent such rows, which by
21210 : : * definition is present in all the partitions, we need not scan the
21211 : : * table, nor its partitions. But we cannot risk a deadlock by taking a
21212 : : * weaker lock now and the stronger one only when needed.
21213 : : */
21214 : 1585 : attachrel_children = find_all_inheritors(RelationGetRelid(attachrel),
21215 : : AccessExclusiveLock, NULL);
21216 [ + + ]: 1585 : if (list_member_oid(attachrel_children, RelationGetRelid(rel)))
21217 [ + - ]: 8 : ereport(ERROR,
21218 : : (errcode(ERRCODE_DUPLICATE_TABLE),
21219 : : errmsg("circular inheritance not allowed"),
21220 : : errdetail("\"%s\" is already a child of \"%s\".",
21221 : : RelationGetRelationName(rel),
21222 : : RelationGetRelationName(attachrel))));
21223 : :
21224 : : /* If the parent is permanent, so must be all of its partitions. */
21225 [ + + ]: 1577 : if (rel->rd_rel->relpersistence != RELPERSISTENCE_TEMP &&
21226 [ + + ]: 1549 : attachrel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
21227 [ + - ]: 4 : ereport(ERROR,
21228 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
21229 : : errmsg("cannot attach a temporary relation as partition of permanent relation \"%s\"",
21230 : : RelationGetRelationName(rel))));
21231 : :
21232 : : /* Temp parent cannot have a partition that is itself not a temp */
21233 [ + + ]: 1573 : if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
21234 [ + + ]: 28 : attachrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
21235 [ + - ]: 12 : ereport(ERROR,
21236 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
21237 : : errmsg("cannot attach a permanent relation as partition of temporary relation \"%s\"",
21238 : : RelationGetRelationName(rel))));
21239 : :
21240 : : /* If the parent is temp, it must belong to this session */
21241 [ + + - + ]: 1561 : if (RELATION_IS_OTHER_TEMP(rel))
21242 [ # # ]: 0 : ereport(ERROR,
21243 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
21244 : : errmsg("cannot attach as partition of temporary relation of another session")));
21245 : :
21246 : : /* Ditto for the partition */
21247 [ + + - + ]: 1561 : if (RELATION_IS_OTHER_TEMP(attachrel))
21248 [ # # ]: 0 : ereport(ERROR,
21249 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
21250 : : errmsg("cannot attach temporary relation of another session as partition")));
21251 : :
21252 : : /*
21253 : : * Check if attachrel has any identity columns or any columns that aren't
21254 : : * in the parent.
21255 : : */
21256 : 1561 : tupleDesc = RelationGetDescr(attachrel);
21257 : 1561 : natts = tupleDesc->natts;
21258 [ + + ]: 5409 : for (attno = 1; attno <= natts; attno++)
21259 : : {
21260 : 3876 : Form_pg_attribute attribute = TupleDescAttr(tupleDesc, attno - 1);
21261 : 3876 : char *attributeName = NameStr(attribute->attname);
21262 : :
21263 : : /* Ignore dropped */
21264 [ + + ]: 3876 : if (attribute->attisdropped)
21265 : 416 : continue;
21266 : :
21267 [ + + ]: 3460 : if (attribute->attidentity)
21268 [ + - ]: 16 : ereport(ERROR,
21269 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
21270 : : errmsg("table \"%s\" being attached contains an identity column \"%s\"",
21271 : : RelationGetRelationName(attachrel), attributeName),
21272 : : errdetail("The new partition may not contain an identity column."));
21273 : :
21274 : : /* Try to find the column in parent (matching on column name) */
21275 [ + + ]: 3444 : if (!SearchSysCacheExists2(ATTNAME,
21276 : : ObjectIdGetDatum(RelationGetRelid(rel)),
21277 : : CStringGetDatum(attributeName)))
21278 [ + - ]: 12 : ereport(ERROR,
21279 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
21280 : : errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
21281 : : RelationGetRelationName(attachrel), attributeName,
21282 : : RelationGetRelationName(rel)),
21283 : : errdetail("The new partition may contain only the columns present in parent.")));
21284 : : }
21285 : :
21286 : : /*
21287 : : * If child_rel has row-level triggers with transition tables, we
21288 : : * currently don't allow it to become a partition. See also prohibitions
21289 : : * in ATExecAddInherit() and CreateTrigger().
21290 : : */
21291 : 1533 : trigger_name = FindTriggerIncompatibleWithInheritance(attachrel->trigdesc);
21292 [ + + ]: 1533 : if (trigger_name != NULL)
21293 [ + - ]: 4 : ereport(ERROR,
21294 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
21295 : : errmsg("trigger \"%s\" prevents table \"%s\" from becoming a partition",
21296 : : trigger_name, RelationGetRelationName(attachrel)),
21297 : : errdetail("ROW triggers with transition tables are not supported on partitions.")));
21298 : :
21299 : : /*
21300 : : * Check that the new partition's bound is valid and does not overlap any
21301 : : * of existing partitions of the parent - note that it does not return on
21302 : : * error.
21303 : : */
21304 : 1529 : check_new_partition_bound(RelationGetRelationName(attachrel), rel,
21305 : : cmd->bound, pstate);
21306 : :
21307 : 1505 : attachPartitionTable(wqueue, rel, attachrel, cmd->bound);
21308 : :
21309 : : /*
21310 : : * Generate a partition constraint from the partition bound specification.
21311 : : * If the parent itself is a partition, make sure to include its
21312 : : * constraint as well.
21313 : : */
21314 : 1397 : partBoundConstraint = get_qual_from_partbound(rel, cmd->bound);
21315 : :
21316 : : /*
21317 : : * Use list_concat_copy() to avoid modifying partBoundConstraint in place,
21318 : : * since it's needed later to construct the constraint expression for
21319 : : * validating against the default partition, if any.
21320 : : */
21321 : 1397 : partConstraint = list_concat_copy(partBoundConstraint,
21322 : 1397 : RelationGetPartitionQual(rel));
21323 : :
21324 : : /* Skip validation if there are no constraints to validate. */
21325 [ + + ]: 1397 : if (partConstraint)
21326 : : {
21327 : : /*
21328 : : * Run the partition quals through const-simplification similar to
21329 : : * check constraints. We skip canonicalize_qual, though, because
21330 : : * partition quals should be in canonical form already.
21331 : : */
21332 : : partConstraint =
21333 : 1369 : (List *) eval_const_expressions(NULL,
21334 : : (Node *) partConstraint);
21335 : :
21336 : : /* XXX this sure looks wrong */
21337 : 1369 : partConstraint = list_make1(make_ands_explicit(partConstraint));
21338 : :
21339 : : /*
21340 : : * Adjust the generated constraint to match this partition's attribute
21341 : : * numbers.
21342 : : */
21343 : 1369 : partConstraint = map_partition_varattnos(partConstraint, 1, attachrel,
21344 : : rel);
21345 : :
21346 : : /* Validate partition constraints against the table being attached. */
21347 : 1369 : QueuePartitionConstraintValidation(wqueue, attachrel, partConstraint,
21348 : : false);
21349 : : }
21350 : :
21351 : : /*
21352 : : * If we're attaching a partition other than the default partition and a
21353 : : * default one exists, then that partition's partition constraint changes,
21354 : : * so add an entry to the work queue to validate it, too. (We must not do
21355 : : * this when the partition being attached is the default one; we already
21356 : : * did it above!)
21357 : : */
21358 [ + + ]: 1397 : if (OidIsValid(defaultPartOid))
21359 : : {
21360 : : Relation defaultrel;
21361 : : List *defPartConstraint;
21362 : :
21363 : : Assert(!cmd->bound->is_default);
21364 : :
21365 : : /* we already hold a lock on the default partition */
21366 : 93 : defaultrel = table_open(defaultPartOid, NoLock);
21367 : : defPartConstraint =
21368 : 93 : get_proposed_default_constraint(partBoundConstraint);
21369 : :
21370 : : /*
21371 : : * Map the Vars in the constraint expression from rel's attnos to
21372 : : * defaultrel's.
21373 : : */
21374 : : defPartConstraint =
21375 : 93 : map_partition_varattnos(defPartConstraint,
21376 : : 1, defaultrel, rel);
21377 : 93 : QueuePartitionConstraintValidation(wqueue, defaultrel,
21378 : : defPartConstraint, true);
21379 : :
21380 : : /* keep our lock until commit. */
21381 : 93 : table_close(defaultrel, NoLock);
21382 : : }
21383 : :
21384 : 1397 : ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel));
21385 : :
21386 : : /*
21387 : : * If the partition we just attached is partitioned itself, invalidate
21388 : : * relcache for all descendent partitions too to ensure that their
21389 : : * rd_partcheck expression trees are rebuilt; partitions already locked at
21390 : : * the beginning of this function.
21391 : : */
21392 [ + + ]: 1397 : if (attachrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
21393 : : {
21394 : : ListCell *l;
21395 : :
21396 [ + - + + : 711 : foreach(l, attachrel_children)
+ + ]
21397 : : {
21398 : 482 : CacheInvalidateRelcacheByRelid(lfirst_oid(l));
21399 : : }
21400 : : }
21401 : :
21402 : : /* keep our lock until commit */
21403 : 1397 : table_close(attachrel, NoLock);
21404 : :
21405 : 1397 : return address;
21406 : : }
21407 : :
21408 : : /*
21409 : : * AttachPartitionEnsureIndexes
21410 : : * subroutine for ATExecAttachPartition to create/match indexes
21411 : : *
21412 : : * Enforce the indexing rule for partitioned tables during ALTER TABLE / ATTACH
21413 : : * PARTITION: every partition must have an index attached to each index on the
21414 : : * partitioned table.
21415 : : */
21416 : : static void
21417 : 1913 : AttachPartitionEnsureIndexes(List **wqueue, Relation rel, Relation attachrel)
21418 : : {
21419 : : List *idxes;
21420 : : List *attachRelIdxs;
21421 : : Relation *attachrelIdxRels;
21422 : : IndexInfo **attachInfos;
21423 : : ListCell *cell;
21424 : : MemoryContext cxt;
21425 : : MemoryContext oldcxt;
21426 : :
21427 : 1913 : cxt = AllocSetContextCreate(CurrentMemoryContext,
21428 : : "AttachPartitionEnsureIndexes",
21429 : : ALLOCSET_DEFAULT_SIZES);
21430 : 1913 : oldcxt = MemoryContextSwitchTo(cxt);
21431 : :
21432 : 1913 : idxes = RelationGetIndexList(rel);
21433 : 1913 : attachRelIdxs = RelationGetIndexList(attachrel);
21434 : 1913 : attachrelIdxRels = palloc_array(Relation, list_length(attachRelIdxs));
21435 : 1913 : attachInfos = palloc_array(IndexInfo *, list_length(attachRelIdxs));
21436 : :
21437 : : /* Build arrays of all existing indexes and their IndexInfos */
21438 [ + + + + : 4095 : foreach_oid(cldIdxId, attachRelIdxs)
+ + ]
21439 : : {
21440 : 269 : int i = foreach_current_index(cldIdxId);
21441 : :
21442 : 269 : attachrelIdxRels[i] = index_open(cldIdxId, AccessShareLock);
21443 : 269 : attachInfos[i] = BuildIndexInfo(attachrelIdxRels[i]);
21444 : : }
21445 : :
21446 : : /*
21447 : : * If we're attaching a foreign table, we must fail if any of the indexes
21448 : : * is a constraint index; otherwise, there's nothing to do here. Do this
21449 : : * before starting work, to avoid wasting the effort of building a few
21450 : : * non-unique indexes before coming across a unique one.
21451 : : */
21452 [ + + ]: 1913 : if (attachrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
21453 : : {
21454 [ + + + + : 55 : foreach(cell, idxes)
+ + ]
21455 : : {
21456 : 24 : Oid idx = lfirst_oid(cell);
21457 : 24 : Relation idxRel = index_open(idx, AccessShareLock);
21458 : :
21459 [ + + ]: 24 : if (idxRel->rd_index->indisunique ||
21460 [ - + ]: 16 : idxRel->rd_index->indisprimary)
21461 [ + - ]: 8 : ereport(ERROR,
21462 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
21463 : : errmsg("cannot attach foreign table \"%s\" as partition of partitioned table \"%s\"",
21464 : : RelationGetRelationName(attachrel),
21465 : : RelationGetRelationName(rel)),
21466 : : errdetail("Partitioned table \"%s\" contains unique indexes.",
21467 : : RelationGetRelationName(rel))));
21468 : 16 : index_close(idxRel, AccessShareLock);
21469 : : }
21470 : :
21471 : 31 : goto out;
21472 : : }
21473 : :
21474 : : /*
21475 : : * For each index on the partitioned table, find a matching one in the
21476 : : * partition-to-be; if one is not found, create one.
21477 : : */
21478 [ + + + + : 2355 : foreach(cell, idxes)
+ + ]
21479 : : {
21480 : 493 : Oid idx = lfirst_oid(cell);
21481 : 493 : Relation idxRel = index_open(idx, AccessShareLock);
21482 : : IndexInfo *info;
21483 : : AttrMap *attmap;
21484 : 493 : bool found = false;
21485 : : Oid constraintOid;
21486 : :
21487 : : /*
21488 : : * Ignore indexes in the partitioned table other than partitioned
21489 : : * indexes.
21490 : : */
21491 [ - + ]: 493 : if (idxRel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
21492 : : {
21493 : 0 : index_close(idxRel, AccessShareLock);
21494 : 0 : continue;
21495 : : }
21496 : :
21497 : : /* construct an indexinfo to compare existing indexes against */
21498 : 493 : info = BuildIndexInfo(idxRel);
21499 : 493 : attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
21500 : : RelationGetDescr(rel),
21501 : : false);
21502 : 493 : constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
21503 : :
21504 : : /*
21505 : : * Scan the list of existing indexes in the partition-to-be, and mark
21506 : : * the first matching, valid, unattached one we find, if any, as
21507 : : * partition of the parent index. If we find one, we're done.
21508 : : */
21509 [ + + ]: 533 : for (int i = 0; i < list_length(attachRelIdxs); i++)
21510 : : {
21511 : 197 : Oid cldIdxId = RelationGetRelid(attachrelIdxRels[i]);
21512 : 197 : Oid cldConstrOid = InvalidOid;
21513 : :
21514 : : /* does this index have a parent? if so, can't use it */
21515 [ + + ]: 197 : if (attachrelIdxRels[i]->rd_rel->relispartition)
21516 : 8 : continue;
21517 : :
21518 : : /* If this index is invalid, can't use it */
21519 [ + + ]: 189 : if (!attachrelIdxRels[i]->rd_index->indisvalid)
21520 : 4 : continue;
21521 : :
21522 [ + + ]: 185 : if (CompareIndexInfo(attachInfos[i], info,
21523 : 185 : attachrelIdxRels[i]->rd_indcollation,
21524 : 185 : idxRel->rd_indcollation,
21525 : 185 : attachrelIdxRels[i]->rd_opfamily,
21526 : 185 : idxRel->rd_opfamily,
21527 : : attmap))
21528 : : {
21529 : : /*
21530 : : * If this index is being created in the parent because of a
21531 : : * constraint, then the child needs to have a constraint also,
21532 : : * so look for one. If there is no such constraint, this
21533 : : * index is no good, so keep looking.
21534 : : */
21535 [ + + ]: 161 : if (OidIsValid(constraintOid))
21536 : : {
21537 : : cldConstrOid =
21538 : 96 : get_relation_idx_constraint_oid(RelationGetRelid(attachrel),
21539 : : cldIdxId);
21540 : : /* no dice */
21541 [ + + ]: 96 : if (!OidIsValid(cldConstrOid))
21542 : 4 : continue;
21543 : :
21544 : : /* Ensure they're both the same type of constraint */
21545 [ - + ]: 184 : if (get_constraint_type(constraintOid) !=
21546 : 92 : get_constraint_type(cldConstrOid))
21547 : 0 : continue;
21548 : : }
21549 : :
21550 : : /* bingo. */
21551 : 157 : IndexSetParentIndex(attachrelIdxRels[i], idx);
21552 [ + + ]: 157 : if (OidIsValid(constraintOid))
21553 : 92 : ConstraintSetParentConstraint(cldConstrOid, constraintOid,
21554 : : RelationGetRelid(attachrel));
21555 : 157 : found = true;
21556 : :
21557 : 157 : CommandCounterIncrement();
21558 : 157 : break;
21559 : : }
21560 : : }
21561 : :
21562 : : /*
21563 : : * If no suitable index was found in the partition-to-be, create one
21564 : : * now. Note that if this is a PK, not-null constraints must already
21565 : : * exist.
21566 : : */
21567 [ + + ]: 493 : if (!found)
21568 : : {
21569 : : IndexStmt *stmt;
21570 : : Oid conOid;
21571 : :
21572 : 336 : stmt = generateClonedIndexStmt(NULL,
21573 : : idxRel, attmap,
21574 : : &conOid);
21575 : 336 : DefineIndex(NULL,
21576 : : RelationGetRelid(attachrel), stmt, InvalidOid,
21577 : : RelationGetRelid(idxRel),
21578 : : conOid,
21579 : : -1,
21580 : : true, false, false, false, false);
21581 : : }
21582 : :
21583 : 481 : index_close(idxRel, AccessShareLock);
21584 : : }
21585 : :
21586 : 1893 : out:
21587 : : /* Clean up. */
21588 [ + + ]: 2154 : for (int i = 0; i < list_length(attachRelIdxs); i++)
21589 : 261 : index_close(attachrelIdxRels[i], AccessShareLock);
21590 : 1893 : MemoryContextSwitchTo(oldcxt);
21591 : 1893 : MemoryContextDelete(cxt);
21592 : 1893 : }
21593 : :
21594 : : /*
21595 : : * CloneRowTriggersToPartition
21596 : : * subroutine for ATExecAttachPartition/DefineRelation to create row
21597 : : * triggers on partitions
21598 : : */
21599 : : static void
21600 : 2153 : CloneRowTriggersToPartition(Relation parent, Relation partition)
21601 : : {
21602 : : Relation pg_trigger;
21603 : : ScanKeyData key;
21604 : : SysScanDesc scan;
21605 : : HeapTuple tuple;
21606 : : MemoryContext perTupCxt;
21607 : :
21608 : 2153 : ScanKeyInit(&key, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber,
21609 : : F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(parent)));
21610 : 2153 : pg_trigger = table_open(TriggerRelationId, RowExclusiveLock);
21611 : 2153 : scan = systable_beginscan(pg_trigger, TriggerRelidNameIndexId,
21612 : : true, NULL, 1, &key);
21613 : :
21614 : 2153 : perTupCxt = AllocSetContextCreate(CurrentMemoryContext,
21615 : : "clone trig", ALLOCSET_SMALL_SIZES);
21616 : :
21617 [ + + ]: 3382 : while (HeapTupleIsValid(tuple = systable_getnext(scan)))
21618 : : {
21619 : 1233 : Form_pg_trigger trigForm = (Form_pg_trigger) GETSTRUCT(tuple);
21620 : : CreateTrigStmt *trigStmt;
21621 : 1233 : Node *qual = NULL;
21622 : : Datum value;
21623 : : bool isnull;
21624 : 1233 : List *cols = NIL;
21625 : 1233 : List *trigargs = NIL;
21626 : : MemoryContext oldcxt;
21627 : :
21628 : : /*
21629 : : * Ignore statement-level triggers; those are not cloned.
21630 : : */
21631 [ + + ]: 1233 : if (!TRIGGER_FOR_ROW(trigForm->tgtype))
21632 : 1102 : continue;
21633 : :
21634 : : /*
21635 : : * Don't clone internal triggers, because the constraint cloning code
21636 : : * will.
21637 : : */
21638 [ + + ]: 1205 : if (trigForm->tgisinternal)
21639 : 1074 : continue;
21640 : :
21641 : : /*
21642 : : * Complain if we find an unexpected trigger type.
21643 : : */
21644 [ + + ]: 131 : if (!TRIGGER_FOR_BEFORE(trigForm->tgtype) &&
21645 [ - + ]: 107 : !TRIGGER_FOR_AFTER(trigForm->tgtype))
21646 [ # # ]: 0 : elog(ERROR, "unexpected trigger \"%s\" found",
21647 : : NameStr(trigForm->tgname));
21648 : :
21649 : : /* Use short-lived context for CREATE TRIGGER */
21650 : 131 : oldcxt = MemoryContextSwitchTo(perTupCxt);
21651 : :
21652 : : /*
21653 : : * If there is a WHEN clause, generate a 'cooked' version of it that's
21654 : : * appropriate for the partition.
21655 : : */
21656 : 131 : value = heap_getattr(tuple, Anum_pg_trigger_tgqual,
21657 : : RelationGetDescr(pg_trigger), &isnull);
21658 [ + + ]: 131 : if (!isnull)
21659 : : {
21660 : 4 : qual = stringToNode(TextDatumGetCString(value));
21661 : 4 : qual = (Node *) map_partition_varattnos((List *) qual, PRS2_OLD_VARNO,
21662 : : partition, parent);
21663 : 4 : qual = (Node *) map_partition_varattnos((List *) qual, PRS2_NEW_VARNO,
21664 : : partition, parent);
21665 : : }
21666 : :
21667 : : /*
21668 : : * If there is a column list, transform it to a list of column names.
21669 : : * Note we don't need to map this list in any way ...
21670 : : */
21671 [ + + ]: 131 : if (trigForm->tgattr.dim1 > 0)
21672 : : {
21673 : : int i;
21674 : :
21675 [ + + ]: 8 : for (i = 0; i < trigForm->tgattr.dim1; i++)
21676 : : {
21677 : : Form_pg_attribute col;
21678 : :
21679 : 4 : col = TupleDescAttr(parent->rd_att,
21680 : 4 : trigForm->tgattr.values[i] - 1);
21681 : 4 : cols = lappend(cols,
21682 : 4 : makeString(pstrdup(NameStr(col->attname))));
21683 : : }
21684 : : }
21685 : :
21686 : : /* Reconstruct trigger arguments list. */
21687 [ + + ]: 131 : if (trigForm->tgnargs > 0)
21688 : : {
21689 : : char *p;
21690 : :
21691 : 36 : value = heap_getattr(tuple, Anum_pg_trigger_tgargs,
21692 : : RelationGetDescr(pg_trigger), &isnull);
21693 [ - + ]: 36 : if (isnull)
21694 [ # # ]: 0 : elog(ERROR, "tgargs is null for trigger \"%s\" in partition \"%s\"",
21695 : : NameStr(trigForm->tgname), RelationGetRelationName(partition));
21696 : :
21697 : 36 : p = (char *) VARDATA_ANY(DatumGetByteaPP(value));
21698 : :
21699 [ + + ]: 80 : for (int i = 0; i < trigForm->tgnargs; i++)
21700 : : {
21701 : 44 : trigargs = lappend(trigargs, makeString(pstrdup(p)));
21702 : 44 : p += strlen(p) + 1;
21703 : : }
21704 : : }
21705 : :
21706 : 131 : trigStmt = makeNode(CreateTrigStmt);
21707 : 131 : trigStmt->replace = false;
21708 : 131 : trigStmt->isconstraint = OidIsValid(trigForm->tgconstraint);
21709 : 131 : trigStmt->trigname = NameStr(trigForm->tgname);
21710 : 131 : trigStmt->relation = NULL;
21711 : 131 : trigStmt->funcname = NULL; /* passed separately */
21712 : 131 : trigStmt->args = trigargs;
21713 : 131 : trigStmt->row = true;
21714 : 131 : trigStmt->timing = trigForm->tgtype & TRIGGER_TYPE_TIMING_MASK;
21715 : 131 : trigStmt->events = trigForm->tgtype & TRIGGER_TYPE_EVENT_MASK;
21716 : 131 : trigStmt->columns = cols;
21717 : 131 : trigStmt->whenClause = NULL; /* passed separately */
21718 : 131 : trigStmt->transitionRels = NIL; /* not supported at present */
21719 : 131 : trigStmt->deferrable = trigForm->tgdeferrable;
21720 : 131 : trigStmt->initdeferred = trigForm->tginitdeferred;
21721 : 131 : trigStmt->constrrel = NULL; /* passed separately */
21722 : :
21723 : 131 : CreateTriggerFiringOn(trigStmt, NULL, RelationGetRelid(partition),
21724 : : trigForm->tgconstrrelid, InvalidOid, InvalidOid,
21725 : : trigForm->tgfoid, trigForm->oid, qual,
21726 : 131 : false, true, trigForm->tgenabled);
21727 : :
21728 : 127 : MemoryContextSwitchTo(oldcxt);
21729 : 127 : MemoryContextReset(perTupCxt);
21730 : : }
21731 : :
21732 : 2149 : MemoryContextDelete(perTupCxt);
21733 : :
21734 : 2149 : systable_endscan(scan);
21735 : 2149 : table_close(pg_trigger, RowExclusiveLock);
21736 : 2149 : }
21737 : :
21738 : : /*
21739 : : * ALTER TABLE DETACH PARTITION
21740 : : *
21741 : : * Return the address of the relation that is no longer a partition of rel.
21742 : : *
21743 : : * If concurrent mode is requested, we run in two transactions. A side-
21744 : : * effect is that this command cannot run in a multi-part ALTER TABLE.
21745 : : * Currently, that's enforced by the grammar.
21746 : : *
21747 : : * The strategy for concurrency is to first modify the partition's
21748 : : * pg_inherit catalog row to make it visible to everyone that the
21749 : : * partition is detached, lock the partition against writes, and commit
21750 : : * the transaction; anyone who requests the partition descriptor from
21751 : : * that point onwards has to ignore such a partition. In a second
21752 : : * transaction, we wait until all transactions that could have seen the
21753 : : * partition as attached are gone, then we remove the rest of partition
21754 : : * metadata (pg_inherits and pg_class.relpartbounds).
21755 : : */
21756 : : static ObjectAddress
21757 : 373 : ATExecDetachPartition(List **wqueue, AlteredTableInfo *tab, Relation rel,
21758 : : RangeVar *name, bool concurrent)
21759 : : {
21760 : : Relation partRel;
21761 : : ObjectAddress address;
21762 : : Oid defaultPartOid;
21763 : :
21764 : : /*
21765 : : * We must lock the default partition, because detaching this partition
21766 : : * will change its partition constraint.
21767 : : */
21768 : : defaultPartOid =
21769 : 373 : get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
21770 [ + + ]: 373 : if (OidIsValid(defaultPartOid))
21771 : : {
21772 : : /*
21773 : : * Concurrent detaching when a default partition exists is not
21774 : : * supported. The main problem is that the default partition
21775 : : * constraint would change. And there's a definitional problem: what
21776 : : * should happen to the tuples that are being inserted that belong to
21777 : : * the partition being detached? Putting them on the partition being
21778 : : * detached would be wrong, since they'd become "lost" after the
21779 : : * detaching completes but we cannot put them in the default partition
21780 : : * either until we alter its partition constraint.
21781 : : *
21782 : : * I think we could solve this problem if we effected the constraint
21783 : : * change before committing the first transaction. But the lock would
21784 : : * have to remain AEL and it would cause concurrent query planning to
21785 : : * be blocked, so changing it that way would be even worse.
21786 : : */
21787 [ + + ]: 74 : if (concurrent)
21788 [ + - ]: 8 : ereport(ERROR,
21789 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
21790 : : errmsg("cannot detach partitions concurrently when a default partition exists")));
21791 : 66 : LockRelationOid(defaultPartOid, AccessExclusiveLock);
21792 : : }
21793 : :
21794 : : /*
21795 : : * In concurrent mode, the partition is locked with share-update-exclusive
21796 : : * in the first transaction. This allows concurrent transactions to be
21797 : : * doing DML to the partition.
21798 : : */
21799 [ + + ]: 365 : partRel = table_openrv(name, concurrent ? ShareUpdateExclusiveLock :
21800 : : AccessExclusiveLock);
21801 : :
21802 : : /*
21803 : : * Check inheritance conditions and either delete the pg_inherits row (in
21804 : : * non-concurrent mode) or just set the inhdetachpending flag.
21805 : : */
21806 [ + + ]: 357 : if (!concurrent)
21807 : 282 : RemoveInheritance(partRel, rel, false);
21808 : : else
21809 : 75 : MarkInheritDetached(partRel, rel);
21810 : :
21811 : : /*
21812 : : * Ensure that foreign keys still hold after this detach. This keeps
21813 : : * locks on the referencing tables, which prevents concurrent transactions
21814 : : * from adding rows that we wouldn't see. For this to work in concurrent
21815 : : * mode, it is critical that the partition appears as no longer attached
21816 : : * for the RI queries as soon as the first transaction commits.
21817 : : */
21818 : 344 : ATDetachCheckNoForeignKeyRefs(partRel);
21819 : :
21820 : : /*
21821 : : * Concurrent mode has to work harder; first we add a new constraint to
21822 : : * the partition that matches the partition constraint. Then we close our
21823 : : * existing transaction, and in a new one wait for all processes to catch
21824 : : * up on the catalog updates we've done so far; at that point we can
21825 : : * complete the operation.
21826 : : */
21827 [ + + ]: 322 : if (concurrent)
21828 : : {
21829 : : Oid partrelid,
21830 : : parentrelid;
21831 : : LOCKTAG tag;
21832 : : char *parentrelname;
21833 : : char *partrelname;
21834 : :
21835 : : /*
21836 : : * We're almost done now; the only traces that remain are the
21837 : : * pg_inherits tuple and the partition's relpartbounds. Before we can
21838 : : * remove those, we need to wait until all transactions that know that
21839 : : * this is a partition are gone.
21840 : : */
21841 : :
21842 : : /*
21843 : : * Remember relation OIDs to re-acquire them later; and relation names
21844 : : * too, for error messages if something is dropped in between.
21845 : : */
21846 : 72 : partrelid = RelationGetRelid(partRel);
21847 : 72 : parentrelid = RelationGetRelid(rel);
21848 : 72 : parentrelname = MemoryContextStrdup(PortalContext,
21849 : 72 : RelationGetRelationName(rel));
21850 : 72 : partrelname = MemoryContextStrdup(PortalContext,
21851 : 72 : RelationGetRelationName(partRel));
21852 : :
21853 : : /* Invalidate relcache entries for the parent -- must be before close */
21854 : 72 : CacheInvalidateRelcache(rel);
21855 : :
21856 : 72 : table_close(partRel, NoLock);
21857 : 72 : table_close(rel, NoLock);
21858 : 72 : tab->rel = NULL;
21859 : :
21860 : : /* Make updated catalog entry visible */
21861 : 72 : PopActiveSnapshot();
21862 : 72 : CommitTransactionCommand();
21863 : :
21864 : 72 : StartTransactionCommand();
21865 : :
21866 : : /*
21867 : : * Now wait. This ensures that all queries that were planned
21868 : : * including the partition are finished before we remove the rest of
21869 : : * catalog entries. We don't need or indeed want to acquire this
21870 : : * lock, though -- that would block later queries.
21871 : : *
21872 : : * We don't need to concern ourselves with waiting for a lock on the
21873 : : * partition itself, since we will acquire AccessExclusiveLock below.
21874 : : */
21875 : 72 : SET_LOCKTAG_RELATION(tag, MyDatabaseId, parentrelid);
21876 : 72 : WaitForLockersMultiple(list_make1(&tag), AccessExclusiveLock, false);
21877 : :
21878 : : /*
21879 : : * Now acquire locks in both relations again. Note they may have been
21880 : : * removed in the meantime, so care is required.
21881 : : */
21882 : 47 : rel = try_relation_open(parentrelid, ShareUpdateExclusiveLock);
21883 : 47 : partRel = try_relation_open(partrelid, AccessExclusiveLock);
21884 : :
21885 : : /* If the relations aren't there, something bad happened; bail out */
21886 [ - + ]: 47 : if (rel == NULL)
21887 : : {
21888 [ # # ]: 0 : if (partRel != NULL) /* shouldn't happen */
21889 [ # # ]: 0 : elog(WARNING, "dangling partition \"%s\" remains, can't fix",
21890 : : partrelname);
21891 [ # # ]: 0 : ereport(ERROR,
21892 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
21893 : : errmsg("partitioned table \"%s\" was removed concurrently",
21894 : : parentrelname)));
21895 : : }
21896 [ - + ]: 47 : if (partRel == NULL)
21897 [ # # ]: 0 : ereport(ERROR,
21898 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
21899 : : errmsg("partition \"%s\" was removed concurrently", partrelname)));
21900 : :
21901 : 47 : tab->rel = rel;
21902 : : }
21903 : :
21904 : : /*
21905 : : * Detaching the partition might involve TOAST table access, so ensure we
21906 : : * have a valid snapshot.
21907 : : */
21908 : 297 : PushActiveSnapshot(GetTransactionSnapshot());
21909 : :
21910 : : /* Do the final part of detaching */
21911 : 297 : DetachPartitionFinalize(rel, partRel, concurrent, defaultPartOid);
21912 : :
21913 : 296 : PopActiveSnapshot();
21914 : :
21915 : 296 : ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
21916 : :
21917 : : /* keep our lock until commit */
21918 : 296 : table_close(partRel, NoLock);
21919 : :
21920 : 296 : return address;
21921 : : }
21922 : :
21923 : : /*
21924 : : * Second part of ALTER TABLE .. DETACH.
21925 : : *
21926 : : * This is separate so that it can be run independently when the second
21927 : : * transaction of the concurrent algorithm fails (crash or abort).
21928 : : */
21929 : : static void
21930 : 741 : DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
21931 : : Oid defaultPartOid)
21932 : : {
21933 : : Relation classRel;
21934 : : List *fks;
21935 : : ListCell *cell;
21936 : : List *indexes;
21937 : : Datum new_val[Natts_pg_class];
21938 : : bool new_null[Natts_pg_class],
21939 : : new_repl[Natts_pg_class];
21940 : : HeapTuple tuple,
21941 : : newtuple;
21942 : 741 : Relation trigrel = NULL;
21943 : 741 : List *fkoids = NIL;
21944 : :
21945 [ + + ]: 741 : if (concurrent)
21946 : : {
21947 : : /*
21948 : : * We can remove the pg_inherits row now. (In the non-concurrent case,
21949 : : * this was already done).
21950 : : */
21951 : 54 : RemoveInheritance(partRel, rel, true);
21952 : : }
21953 : :
21954 : : /* Drop any triggers that were cloned on creation/attach. */
21955 : 741 : DropClonedTriggersFromPartition(RelationGetRelid(partRel));
21956 : :
21957 : : /*
21958 : : * Detach any foreign keys that are inherited. This includes creating
21959 : : * additional action triggers.
21960 : : */
21961 : 741 : fks = copyObject(RelationGetFKeyList(partRel));
21962 [ + + ]: 741 : if (fks != NIL)
21963 : 60 : trigrel = table_open(TriggerRelationId, RowExclusiveLock);
21964 : :
21965 : : /*
21966 : : * It's possible that the partition being detached has a foreign key that
21967 : : * references a partitioned table. When that happens, there are multiple
21968 : : * pg_constraint rows for the partition: one points to the partitioned
21969 : : * table itself, while the others point to each of its partitions. Only
21970 : : * the topmost one is to be considered here; the child constraints must be
21971 : : * left alone, because conceptually those aren't coming from our parent
21972 : : * partitioned table, but from this partition itself.
21973 : : *
21974 : : * We implement this by collecting all the constraint OIDs in a first scan
21975 : : * of the FK array, and skipping in the loop below those constraints whose
21976 : : * parents are listed here.
21977 : : */
21978 [ + + + + : 1598 : foreach_node(ForeignKeyCacheInfo, fk, fks)
+ + ]
21979 : 116 : fkoids = lappend_oid(fkoids, fk->conoid);
21980 : :
21981 [ + + + + : 857 : foreach(cell, fks)
+ + ]
21982 : : {
21983 : 116 : ForeignKeyCacheInfo *fk = lfirst(cell);
21984 : : HeapTuple contup;
21985 : : Form_pg_constraint conform;
21986 : :
21987 : 116 : contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
21988 [ - + ]: 116 : if (!HeapTupleIsValid(contup))
21989 [ # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
21990 : 116 : conform = (Form_pg_constraint) GETSTRUCT(contup);
21991 : :
21992 : : /*
21993 : : * Consider only inherited foreign keys, and only if their parents
21994 : : * aren't in the list.
21995 : : */
21996 [ + - ]: 116 : if (conform->contype != CONSTRAINT_FOREIGN ||
21997 [ + + + + ]: 216 : !OidIsValid(conform->conparentid) ||
21998 : 100 : list_member_oid(fkoids, conform->conparentid))
21999 : : {
22000 : 44 : ReleaseSysCache(contup);
22001 : 44 : continue;
22002 : : }
22003 : :
22004 : : /*
22005 : : * The constraint on this table must be marked no longer a child of
22006 : : * the parent's constraint, as do its check triggers.
22007 : : */
22008 : 72 : ConstraintSetParentConstraint(fk->conoid, InvalidOid, InvalidOid);
22009 : :
22010 : : /*
22011 : : * Also, look up the partition's "check" triggers corresponding to the
22012 : : * ENFORCED constraint being detached and detach them from the parent
22013 : : * triggers. NOT ENFORCED constraints do not have these triggers;
22014 : : * therefore, this step is not needed.
22015 : : */
22016 [ + - ]: 72 : if (fk->conenforced)
22017 : : {
22018 : : Oid insertTriggerOid,
22019 : : updateTriggerOid;
22020 : :
22021 : 72 : GetForeignKeyCheckTriggers(trigrel,
22022 : : fk->conoid, fk->confrelid, fk->conrelid,
22023 : : &insertTriggerOid, &updateTriggerOid);
22024 : : Assert(OidIsValid(insertTriggerOid));
22025 : 72 : TriggerSetParentTrigger(trigrel, insertTriggerOid, InvalidOid,
22026 : : RelationGetRelid(partRel));
22027 : : Assert(OidIsValid(updateTriggerOid));
22028 : 72 : TriggerSetParentTrigger(trigrel, updateTriggerOid, InvalidOid,
22029 : : RelationGetRelid(partRel));
22030 : : }
22031 : :
22032 : : /*
22033 : : * Lastly, create the action triggers on the referenced table, using
22034 : : * addFkRecurseReferenced, which requires some elaborate setup (so put
22035 : : * it in a separate block). While at it, if the table is partitioned,
22036 : : * that function will recurse to create the pg_constraint rows and
22037 : : * action triggers for each partition.
22038 : : *
22039 : : * Note there's no need to do addFkConstraint() here, because the
22040 : : * pg_constraint row already exists.
22041 : : */
22042 : : {
22043 : : Constraint *fkconstraint;
22044 : : int numfks;
22045 : : AttrNumber conkey[INDEX_MAX_KEYS];
22046 : : AttrNumber confkey[INDEX_MAX_KEYS];
22047 : : Oid conpfeqop[INDEX_MAX_KEYS];
22048 : : Oid conppeqop[INDEX_MAX_KEYS];
22049 : : Oid conffeqop[INDEX_MAX_KEYS];
22050 : : int numfkdelsetcols;
22051 : : AttrNumber confdelsetcols[INDEX_MAX_KEYS];
22052 : : Relation refdRel;
22053 : :
22054 : 72 : DeconstructFkConstraintRow(contup,
22055 : : &numfks,
22056 : : conkey,
22057 : : confkey,
22058 : : conpfeqop,
22059 : : conppeqop,
22060 : : conffeqop,
22061 : : &numfkdelsetcols,
22062 : : confdelsetcols);
22063 : :
22064 : : /* Create a synthetic node we'll use throughout */
22065 : 72 : fkconstraint = makeNode(Constraint);
22066 : 72 : fkconstraint->contype = CONSTRAINT_FOREIGN;
22067 : 72 : fkconstraint->conname = pstrdup(NameStr(conform->conname));
22068 : 72 : fkconstraint->deferrable = conform->condeferrable;
22069 : 72 : fkconstraint->initdeferred = conform->condeferred;
22070 : 72 : fkconstraint->is_enforced = conform->conenforced;
22071 : 72 : fkconstraint->skip_validation = true;
22072 : 72 : fkconstraint->initially_valid = conform->convalidated;
22073 : : /* a few irrelevant fields omitted here */
22074 : 72 : fkconstraint->pktable = NULL;
22075 : 72 : fkconstraint->fk_attrs = NIL;
22076 : 72 : fkconstraint->pk_attrs = NIL;
22077 : 72 : fkconstraint->fk_matchtype = conform->confmatchtype;
22078 : 72 : fkconstraint->fk_upd_action = conform->confupdtype;
22079 : 72 : fkconstraint->fk_del_action = conform->confdeltype;
22080 : 72 : fkconstraint->fk_del_set_cols = NIL;
22081 : 72 : fkconstraint->old_conpfeqop = NIL;
22082 : 72 : fkconstraint->old_pktable_oid = InvalidOid;
22083 : 72 : fkconstraint->location = -1;
22084 : :
22085 : : /* set up colnames, used to generate the constraint name */
22086 [ + + ]: 176 : for (int i = 0; i < numfks; i++)
22087 : : {
22088 : : Form_pg_attribute att;
22089 : :
22090 : 104 : att = TupleDescAttr(RelationGetDescr(partRel),
22091 : 104 : conkey[i] - 1);
22092 : :
22093 : 104 : fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
22094 : 104 : makeString(NameStr(att->attname)));
22095 : : }
22096 : :
22097 : 72 : refdRel = table_open(fk->confrelid, ShareRowExclusiveLock);
22098 : :
22099 : 72 : addFkRecurseReferenced(fkconstraint, partRel,
22100 : : refdRel,
22101 : : conform->conindid,
22102 : : fk->conoid,
22103 : : numfks,
22104 : : confkey,
22105 : : conkey,
22106 : : conpfeqop,
22107 : : conppeqop,
22108 : : conffeqop,
22109 : : numfkdelsetcols,
22110 : : confdelsetcols,
22111 : : true,
22112 : : InvalidOid, InvalidOid,
22113 : 72 : conform->conperiod);
22114 : 72 : table_close(refdRel, NoLock); /* keep lock till end of xact */
22115 : : }
22116 : :
22117 : 72 : ReleaseSysCache(contup);
22118 : : }
22119 : 741 : list_free_deep(fks);
22120 [ + + ]: 741 : if (trigrel)
22121 : 60 : table_close(trigrel, RowExclusiveLock);
22122 : :
22123 : : /*
22124 : : * Any sub-constraints that are in the referenced-side of a larger
22125 : : * constraint have to be removed. This partition is no longer part of the
22126 : : * key space of the constraint.
22127 : : */
22128 [ + + + + : 796 : foreach(cell, GetParentedForeignKeyRefs(partRel))
+ + ]
22129 : : {
22130 : 56 : Oid constrOid = lfirst_oid(cell);
22131 : : ObjectAddress constraint;
22132 : :
22133 : 56 : ConstraintSetParentConstraint(constrOid, InvalidOid, InvalidOid);
22134 : 56 : deleteDependencyRecordsForClass(ConstraintRelationId,
22135 : : constrOid,
22136 : : ConstraintRelationId,
22137 : : DEPENDENCY_INTERNAL);
22138 : 56 : CommandCounterIncrement();
22139 : :
22140 : 56 : ObjectAddressSet(constraint, ConstraintRelationId, constrOid);
22141 : 56 : performDeletion(&constraint, DROP_RESTRICT, 0);
22142 : : }
22143 : :
22144 : : /* Now we can detach indexes */
22145 : 740 : indexes = RelationGetIndexList(partRel);
22146 [ + + + + : 1047 : foreach(cell, indexes)
+ + ]
22147 : : {
22148 : 307 : Oid idxid = lfirst_oid(cell);
22149 : : Oid parentidx;
22150 : : Relation idx;
22151 : : Oid constrOid;
22152 : : Oid parentConstrOid;
22153 : :
22154 [ + + ]: 307 : if (!has_superclass(idxid))
22155 : 9 : continue;
22156 : :
22157 : 298 : parentidx = get_partition_parent(idxid, false);
22158 : : Assert((IndexGetRelation(parentidx, false) == RelationGetRelid(rel)));
22159 : :
22160 : 298 : idx = index_open(idxid, AccessExclusiveLock);
22161 : 298 : IndexSetParentIndex(idx, InvalidOid);
22162 : :
22163 : : /*
22164 : : * If there's a constraint associated with the index, detach it too.
22165 : : * Careful: it is possible for a constraint index in a partition to be
22166 : : * the child of a non-constraint index, so verify whether the parent
22167 : : * index does actually have a constraint.
22168 : : */
22169 : 298 : constrOid = get_relation_idx_constraint_oid(RelationGetRelid(partRel),
22170 : : idxid);
22171 : 298 : parentConstrOid = get_relation_idx_constraint_oid(RelationGetRelid(rel),
22172 : : parentidx);
22173 [ + + + - ]: 298 : if (OidIsValid(parentConstrOid) && OidIsValid(constrOid))
22174 : 135 : ConstraintSetParentConstraint(constrOid, InvalidOid, InvalidOid);
22175 : :
22176 : 298 : index_close(idx, NoLock);
22177 : : }
22178 : :
22179 : : /* Update pg_class tuple */
22180 : 740 : classRel = table_open(RelationRelationId, RowExclusiveLock);
22181 : 740 : tuple = SearchSysCacheCopy1(RELOID,
22182 : : ObjectIdGetDatum(RelationGetRelid(partRel)));
22183 [ - + ]: 740 : if (!HeapTupleIsValid(tuple))
22184 [ # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u",
22185 : : RelationGetRelid(partRel));
22186 : : Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
22187 : :
22188 : : /* Clear relpartbound and reset relispartition */
22189 : 740 : memset(new_val, 0, sizeof(new_val));
22190 : 740 : memset(new_null, false, sizeof(new_null));
22191 : 740 : memset(new_repl, false, sizeof(new_repl));
22192 : 740 : new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
22193 : 740 : new_null[Anum_pg_class_relpartbound - 1] = true;
22194 : 740 : new_repl[Anum_pg_class_relpartbound - 1] = true;
22195 : 740 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
22196 : : new_val, new_null, new_repl);
22197 : :
22198 : 740 : ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
22199 : 740 : CatalogTupleUpdate(classRel, &newtuple->t_self, newtuple);
22200 : 740 : heap_freetuple(newtuple);
22201 : 740 : table_close(classRel, RowExclusiveLock);
22202 : :
22203 : : /*
22204 : : * Drop identity property from all identity columns of partition.
22205 : : */
22206 [ + + ]: 2328 : for (int attno = 0; attno < RelationGetNumberOfAttributes(partRel); attno++)
22207 : : {
22208 : 1588 : Form_pg_attribute attr = TupleDescAttr(partRel->rd_att, attno);
22209 : :
22210 [ + + + + ]: 1588 : if (!attr->attisdropped && attr->attidentity)
22211 : 20 : ATExecDropIdentity(partRel, NameStr(attr->attname), false,
22212 : : AccessExclusiveLock, true, true);
22213 : : }
22214 : :
22215 [ + + ]: 740 : if (OidIsValid(defaultPartOid))
22216 : : {
22217 : : /*
22218 : : * If the relation being detached is the default partition itself,
22219 : : * remove it from the parent's pg_partitioned_table entry.
22220 : : *
22221 : : * If not, we must invalidate default partition's relcache entry, as
22222 : : * in StorePartitionBound: its partition constraint depends on every
22223 : : * other partition's partition constraint.
22224 : : */
22225 [ + + ]: 167 : if (RelationGetRelid(partRel) == defaultPartOid)
22226 : 33 : update_default_partition_oid(RelationGetRelid(rel), InvalidOid);
22227 : : else
22228 : 134 : CacheInvalidateRelcacheByRelid(defaultPartOid);
22229 : : }
22230 : :
22231 : : /*
22232 : : * Invalidate the parent's relcache so that the partition is no longer
22233 : : * included in its partition descriptor.
22234 : : */
22235 : 740 : CacheInvalidateRelcache(rel);
22236 : :
22237 : : /*
22238 : : * If the partition we just detached is partitioned itself, invalidate
22239 : : * relcache for all descendent partitions too to ensure that their
22240 : : * rd_partcheck expression trees are rebuilt; must lock partitions before
22241 : : * doing so, using the same lockmode as what partRel has been locked with
22242 : : * by the caller.
22243 : : */
22244 [ + + ]: 740 : if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
22245 : : {
22246 : : List *children;
22247 : :
22248 : 41 : children = find_all_inheritors(RelationGetRelid(partRel),
22249 : : AccessExclusiveLock, NULL);
22250 [ + - + + : 135 : foreach(cell, children)
+ + ]
22251 : : {
22252 : 94 : CacheInvalidateRelcacheByRelid(lfirst_oid(cell));
22253 : : }
22254 : : }
22255 : 740 : }
22256 : :
22257 : : /*
22258 : : * ALTER TABLE ... DETACH PARTITION ... FINALIZE
22259 : : *
22260 : : * To use when a DETACH PARTITION command previously did not run to
22261 : : * completion; this completes the detaching process.
22262 : : */
22263 : : static ObjectAddress
22264 : 7 : ATExecDetachPartitionFinalize(Relation rel, RangeVar *name)
22265 : : {
22266 : : Relation partRel;
22267 : : ObjectAddress address;
22268 : 7 : Snapshot snap = GetActiveSnapshot();
22269 : :
22270 : 7 : partRel = table_openrv(name, AccessExclusiveLock);
22271 : :
22272 : : /*
22273 : : * Wait until existing snapshots are gone. This is important if the
22274 : : * second transaction of DETACH PARTITION CONCURRENTLY is canceled: the
22275 : : * user could immediately run DETACH FINALIZE without actually waiting for
22276 : : * existing transactions. We must not complete the detach action until
22277 : : * all such queries are complete (otherwise we would present them with an
22278 : : * inconsistent view of catalogs).
22279 : : */
22280 : 7 : WaitForOlderSnapshots(snap->xmin, false);
22281 : :
22282 : 7 : DetachPartitionFinalize(rel, partRel, true, InvalidOid);
22283 : :
22284 : 7 : ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
22285 : :
22286 : 7 : table_close(partRel, NoLock);
22287 : :
22288 : 7 : return address;
22289 : : }
22290 : :
22291 : : /*
22292 : : * DropClonedTriggersFromPartition
22293 : : * subroutine for ATExecDetachPartition to remove any triggers that were
22294 : : * cloned to the partition when it was created-as-partition or attached.
22295 : : * This undoes what CloneRowTriggersToPartition did.
22296 : : */
22297 : : static void
22298 : 741 : DropClonedTriggersFromPartition(Oid partitionId)
22299 : : {
22300 : : ScanKeyData skey;
22301 : : SysScanDesc scan;
22302 : : HeapTuple trigtup;
22303 : : Relation tgrel;
22304 : : ObjectAddresses *objects;
22305 : :
22306 : 741 : objects = new_object_addresses();
22307 : :
22308 : : /*
22309 : : * Scan pg_trigger to search for all triggers on this rel.
22310 : : */
22311 : 741 : ScanKeyInit(&skey, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber,
22312 : : F_OIDEQ, ObjectIdGetDatum(partitionId));
22313 : 741 : tgrel = table_open(TriggerRelationId, RowExclusiveLock);
22314 : 741 : scan = systable_beginscan(tgrel, TriggerRelidNameIndexId,
22315 : : true, NULL, 1, &skey);
22316 [ + + ]: 1077 : while (HeapTupleIsValid(trigtup = systable_getnext(scan)))
22317 : : {
22318 : 336 : Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(trigtup);
22319 : : ObjectAddress trig;
22320 : :
22321 : : /* Ignore triggers that weren't cloned */
22322 [ + + ]: 336 : if (!OidIsValid(pg_trigger->tgparentid))
22323 : 296 : continue;
22324 : :
22325 : : /*
22326 : : * Ignore internal triggers that are implementation objects of foreign
22327 : : * keys, because these will be detached when the foreign keys
22328 : : * themselves are.
22329 : : */
22330 [ + + ]: 280 : if (OidIsValid(pg_trigger->tgconstrrelid))
22331 : 240 : continue;
22332 : :
22333 : : /*
22334 : : * This is ugly, but necessary: remove the dependency markings on the
22335 : : * trigger so that it can be removed.
22336 : : */
22337 : 40 : deleteDependencyRecordsForClass(TriggerRelationId, pg_trigger->oid,
22338 : : TriggerRelationId,
22339 : : DEPENDENCY_PARTITION_PRI);
22340 : 40 : deleteDependencyRecordsForClass(TriggerRelationId, pg_trigger->oid,
22341 : : RelationRelationId,
22342 : : DEPENDENCY_PARTITION_SEC);
22343 : :
22344 : : /* remember this trigger to remove it below */
22345 : 40 : ObjectAddressSet(trig, TriggerRelationId, pg_trigger->oid);
22346 : 40 : add_exact_object_address(&trig, objects);
22347 : : }
22348 : :
22349 : : /* make the dependency removal visible to the deletion below */
22350 : 741 : CommandCounterIncrement();
22351 : 741 : performMultipleDeletions(objects, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
22352 : :
22353 : : /* done */
22354 : 741 : free_object_addresses(objects);
22355 : 741 : systable_endscan(scan);
22356 : 741 : table_close(tgrel, RowExclusiveLock);
22357 : 741 : }
22358 : :
22359 : : /*
22360 : : * Before acquiring lock on an index, acquire the same lock on the owning
22361 : : * table.
22362 : : */
22363 : : struct AttachIndexCallbackState
22364 : : {
22365 : : Oid partitionOid;
22366 : : Oid parentTblOid;
22367 : : bool lockedParentTbl;
22368 : : };
22369 : :
22370 : : static void
22371 : 293 : RangeVarCallbackForAttachIndex(const RangeVar *rv, Oid relOid, Oid oldRelOid,
22372 : : void *arg)
22373 : : {
22374 : : struct AttachIndexCallbackState *state;
22375 : : Form_pg_class classform;
22376 : : HeapTuple tuple;
22377 : :
22378 : 293 : state = (struct AttachIndexCallbackState *) arg;
22379 : :
22380 [ + + ]: 293 : if (!state->lockedParentTbl)
22381 : : {
22382 : 284 : LockRelationOid(state->parentTblOid, AccessShareLock);
22383 : 284 : state->lockedParentTbl = true;
22384 : : }
22385 : :
22386 : : /*
22387 : : * If we previously locked some other heap, and the name we're looking up
22388 : : * no longer refers to an index on that relation, release the now-useless
22389 : : * lock. XXX maybe we should do *after* we verify whether the index does
22390 : : * not actually belong to the same relation ...
22391 : : */
22392 [ + + - + ]: 293 : if (relOid != oldRelOid && OidIsValid(state->partitionOid))
22393 : : {
22394 : 0 : UnlockRelationOid(state->partitionOid, AccessShareLock);
22395 : 0 : state->partitionOid = InvalidOid;
22396 : : }
22397 : :
22398 : : /* Didn't find a relation, so no need for locking or permission checks. */
22399 [ + + ]: 293 : if (!OidIsValid(relOid))
22400 : 4 : return;
22401 : :
22402 : 289 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
22403 [ - + ]: 289 : if (!HeapTupleIsValid(tuple))
22404 : 0 : return; /* concurrently dropped, so nothing to do */
22405 : 289 : classform = (Form_pg_class) GETSTRUCT(tuple);
22406 [ + + ]: 289 : if (classform->relkind != RELKIND_PARTITIONED_INDEX &&
22407 [ + + ]: 223 : classform->relkind != RELKIND_INDEX)
22408 [ + - ]: 4 : ereport(ERROR,
22409 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
22410 : : errmsg("\"%s\" is not an index", rv->relname)));
22411 : 285 : ReleaseSysCache(tuple);
22412 : :
22413 : : /*
22414 : : * Since we need only examine the heap's tupledesc, an access share lock
22415 : : * on it (preventing any DDL) is sufficient.
22416 : : */
22417 : 285 : state->partitionOid = IndexGetRelation(relOid, false);
22418 : 285 : LockRelationOid(state->partitionOid, AccessShareLock);
22419 : : }
22420 : :
22421 : : /*
22422 : : * ALTER INDEX i1 ATTACH PARTITION i2
22423 : : */
22424 : : static ObjectAddress
22425 : 284 : ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
22426 : : {
22427 : : Relation partIdx;
22428 : : Relation partTbl;
22429 : : Relation parentTbl;
22430 : : ObjectAddress address;
22431 : : Oid partIdxId;
22432 : : Oid currParent;
22433 : : struct AttachIndexCallbackState state;
22434 : :
22435 : : /*
22436 : : * We need to obtain lock on the index 'name' to modify it, but we also
22437 : : * need to read its owning table's tuple descriptor -- so we need to lock
22438 : : * both. To avoid deadlocks, obtain lock on the table before doing so on
22439 : : * the index. Furthermore, we need to examine the parent table of the
22440 : : * partition, so lock that one too.
22441 : : */
22442 : 284 : state.partitionOid = InvalidOid;
22443 : 284 : state.parentTblOid = parentIdx->rd_index->indrelid;
22444 : 284 : state.lockedParentTbl = false;
22445 : : partIdxId =
22446 : 284 : RangeVarGetRelidExtended(name, AccessExclusiveLock, 0,
22447 : : RangeVarCallbackForAttachIndex,
22448 : : &state);
22449 : : /* Not there? */
22450 [ - + ]: 276 : if (!OidIsValid(partIdxId))
22451 [ # # ]: 0 : ereport(ERROR,
22452 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
22453 : : errmsg("index \"%s\" does not exist", name->relname)));
22454 : :
22455 : : /* no deadlock risk: RangeVarGetRelidExtended already acquired the lock */
22456 : 276 : partIdx = relation_open(partIdxId, AccessExclusiveLock);
22457 : :
22458 : : /* we already hold locks on both tables, so this is safe: */
22459 : 276 : parentTbl = relation_open(parentIdx->rd_index->indrelid, AccessShareLock);
22460 : 276 : partTbl = relation_open(partIdx->rd_index->indrelid, NoLock);
22461 : :
22462 : 276 : ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partIdx));
22463 : :
22464 : : /*
22465 : : * Check if the index is already attached to the correct parent,
22466 : : * ultimately attempting one round of validation if already the case.
22467 : : */
22468 : 552 : currParent = partIdx->rd_rel->relispartition ?
22469 [ + + ]: 276 : get_partition_parent(partIdxId, false) : InvalidOid;
22470 [ + + ]: 276 : if (currParent != RelationGetRelid(parentIdx))
22471 : : {
22472 : : IndexInfo *childInfo;
22473 : : IndexInfo *parentInfo;
22474 : : AttrMap *attmap;
22475 : : bool found;
22476 : : int i;
22477 : : PartitionDesc partDesc;
22478 : : Oid constraintOid,
22479 : 252 : cldConstrId = InvalidOid;
22480 : :
22481 : : /*
22482 : : * If this partition already has an index attached, refuse the
22483 : : * operation.
22484 : : */
22485 : 252 : refuseDupeIndexAttach(parentIdx, partIdx, partTbl);
22486 : :
22487 [ - + ]: 248 : if (OidIsValid(currParent))
22488 [ # # ]: 0 : ereport(ERROR,
22489 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
22490 : : errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
22491 : : RelationGetRelationName(partIdx),
22492 : : RelationGetRelationName(parentIdx)),
22493 : : errdetail("Index \"%s\" is already attached to another index.",
22494 : : RelationGetRelationName(partIdx))));
22495 : :
22496 : : /* Make sure it indexes a partition of the other index's table */
22497 : 248 : partDesc = RelationGetPartitionDesc(parentTbl, true);
22498 : 248 : found = false;
22499 [ + + ]: 373 : for (i = 0; i < partDesc->nparts; i++)
22500 : : {
22501 [ + + ]: 369 : if (partDesc->oids[i] == state.partitionOid)
22502 : : {
22503 : 244 : found = true;
22504 : 244 : break;
22505 : : }
22506 : : }
22507 [ + + ]: 248 : if (!found)
22508 [ + - ]: 4 : ereport(ERROR,
22509 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
22510 : : errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
22511 : : RelationGetRelationName(partIdx),
22512 : : RelationGetRelationName(parentIdx)),
22513 : : errdetail("Index \"%s\" is not an index on any partition of table \"%s\".",
22514 : : RelationGetRelationName(partIdx),
22515 : : RelationGetRelationName(parentTbl))));
22516 : :
22517 : : /* Ensure the indexes are compatible */
22518 : 244 : childInfo = BuildIndexInfo(partIdx);
22519 : 244 : parentInfo = BuildIndexInfo(parentIdx);
22520 : 244 : attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
22521 : : RelationGetDescr(parentTbl),
22522 : : false);
22523 [ + + ]: 244 : if (!CompareIndexInfo(childInfo, parentInfo,
22524 : 244 : partIdx->rd_indcollation,
22525 : 244 : parentIdx->rd_indcollation,
22526 : 244 : partIdx->rd_opfamily,
22527 : 244 : parentIdx->rd_opfamily,
22528 : : attmap))
22529 [ + - ]: 32 : ereport(ERROR,
22530 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
22531 : : errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
22532 : : RelationGetRelationName(partIdx),
22533 : : RelationGetRelationName(parentIdx)),
22534 : : errdetail("The index definitions do not match.")));
22535 : :
22536 : : /*
22537 : : * If there is a constraint in the parent, make sure there is one in
22538 : : * the child too.
22539 : : */
22540 : 212 : constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(parentTbl),
22541 : : RelationGetRelid(parentIdx));
22542 : :
22543 [ + + ]: 212 : if (OidIsValid(constraintOid))
22544 : : {
22545 : 76 : cldConstrId = get_relation_idx_constraint_oid(RelationGetRelid(partTbl),
22546 : : partIdxId);
22547 [ + + ]: 76 : if (!OidIsValid(cldConstrId))
22548 [ + - ]: 4 : ereport(ERROR,
22549 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
22550 : : errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
22551 : : RelationGetRelationName(partIdx),
22552 : : RelationGetRelationName(parentIdx)),
22553 : : errdetail("The index \"%s\" belongs to a constraint in table \"%s\" but no constraint exists for index \"%s\".",
22554 : : RelationGetRelationName(parentIdx),
22555 : : RelationGetRelationName(parentTbl),
22556 : : RelationGetRelationName(partIdx))));
22557 : : }
22558 : :
22559 : : /*
22560 : : * If it's a primary key, make sure the columns in the partition are
22561 : : * NOT NULL.
22562 : : */
22563 [ + + ]: 208 : if (parentIdx->rd_index->indisprimary)
22564 : 55 : verifyPartitionIndexNotNull(childInfo, partTbl);
22565 : :
22566 : : /* All good -- do it */
22567 : 208 : IndexSetParentIndex(partIdx, RelationGetRelid(parentIdx));
22568 [ + + ]: 208 : if (OidIsValid(constraintOid))
22569 : 72 : ConstraintSetParentConstraint(cldConstrId, constraintOid,
22570 : : RelationGetRelid(partTbl));
22571 : :
22572 : 208 : free_attrmap(attmap);
22573 : :
22574 : 208 : validatePartitionedIndex(parentIdx, parentTbl);
22575 : : }
22576 [ + + ]: 24 : else if (!parentIdx->rd_index->indisvalid)
22577 : : {
22578 : : /*
22579 : : * The index is attached, but the parent is still invalid; see if it
22580 : : * can be validated now.
22581 : : */
22582 : 12 : validatePartitionedIndex(parentIdx, parentTbl);
22583 : : }
22584 : :
22585 : 232 : relation_close(parentTbl, AccessShareLock);
22586 : : /* keep these locks till commit */
22587 : 232 : relation_close(partTbl, NoLock);
22588 : 232 : relation_close(partIdx, NoLock);
22589 : :
22590 : 232 : return address;
22591 : : }
22592 : :
22593 : : /*
22594 : : * Verify whether the given partition already contains an index attached
22595 : : * to the given partitioned index. If so, raise an error.
22596 : : */
22597 : : static void
22598 : 252 : refuseDupeIndexAttach(Relation parentIdx, Relation partIdx, Relation partitionTbl)
22599 : : {
22600 : : Oid existingIdx;
22601 : :
22602 : 252 : existingIdx = index_get_partition(partitionTbl,
22603 : : RelationGetRelid(parentIdx));
22604 [ + + ]: 252 : if (OidIsValid(existingIdx))
22605 [ + - ]: 4 : ereport(ERROR,
22606 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
22607 : : errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
22608 : : RelationGetRelationName(partIdx),
22609 : : RelationGetRelationName(parentIdx)),
22610 : : errdetail("Another index \"%s\" is already attached for partition \"%s\".",
22611 : : get_rel_name(existingIdx),
22612 : : RelationGetRelationName(partitionTbl))));
22613 : 248 : }
22614 : :
22615 : : /*
22616 : : * Verify whether the set of attached partition indexes to a parent index on
22617 : : * a partitioned table is complete. If it is, mark the parent index valid.
22618 : : *
22619 : : * This should be called each time a partition index is attached.
22620 : : */
22621 : : static void
22622 : 252 : validatePartitionedIndex(Relation partedIdx, Relation partedTbl)
22623 : : {
22624 : : Relation inheritsRel;
22625 : : SysScanDesc scan;
22626 : : ScanKeyData key;
22627 : 252 : int tuples = 0;
22628 : : HeapTuple inhTup;
22629 : 252 : bool updated = false;
22630 : :
22631 : : Assert(partedIdx->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
22632 : :
22633 : : /*
22634 : : * Scan pg_inherits for this parent index. Count each valid index we find
22635 : : * (verifying the pg_index entry for each), and if we reach the total
22636 : : * amount we expect, we can mark this parent index as valid.
22637 : : */
22638 : 252 : inheritsRel = table_open(InheritsRelationId, AccessShareLock);
22639 : 252 : ScanKeyInit(&key, Anum_pg_inherits_inhparent,
22640 : : BTEqualStrategyNumber, F_OIDEQ,
22641 : : ObjectIdGetDatum(RelationGetRelid(partedIdx)));
22642 : 252 : scan = systable_beginscan(inheritsRel, InheritsParentIndexId, true,
22643 : : NULL, 1, &key);
22644 [ + + ]: 639 : while ((inhTup = systable_getnext(scan)) != NULL)
22645 : : {
22646 : 387 : Form_pg_inherits inhForm = (Form_pg_inherits) GETSTRUCT(inhTup);
22647 : : HeapTuple indTup;
22648 : : Form_pg_index indexForm;
22649 : :
22650 : 387 : indTup = SearchSysCache1(INDEXRELID,
22651 : : ObjectIdGetDatum(inhForm->inhrelid));
22652 [ - + ]: 387 : if (!HeapTupleIsValid(indTup))
22653 [ # # ]: 0 : elog(ERROR, "cache lookup failed for index %u", inhForm->inhrelid);
22654 : 387 : indexForm = (Form_pg_index) GETSTRUCT(indTup);
22655 [ + + ]: 387 : if (indexForm->indisvalid)
22656 : 321 : tuples += 1;
22657 : 387 : ReleaseSysCache(indTup);
22658 : : }
22659 : :
22660 : : /* Done with pg_inherits */
22661 : 252 : systable_endscan(scan);
22662 : 252 : table_close(inheritsRel, AccessShareLock);
22663 : :
22664 : : /*
22665 : : * If we found as many inherited indexes as the partitioned table has
22666 : : * partitions, we're good; update pg_index to set indisvalid.
22667 : : */
22668 [ + + ]: 252 : if (tuples == RelationGetPartitionDesc(partedTbl, true)->nparts)
22669 : : {
22670 : : Relation idxRel;
22671 : : HeapTuple indTup;
22672 : : Form_pg_index indexForm;
22673 : :
22674 : 121 : idxRel = table_open(IndexRelationId, RowExclusiveLock);
22675 : 121 : indTup = SearchSysCacheCopy1(INDEXRELID,
22676 : : ObjectIdGetDatum(RelationGetRelid(partedIdx)));
22677 [ - + ]: 121 : if (!HeapTupleIsValid(indTup))
22678 [ # # ]: 0 : elog(ERROR, "cache lookup failed for index %u",
22679 : : RelationGetRelid(partedIdx));
22680 : 121 : indexForm = (Form_pg_index) GETSTRUCT(indTup);
22681 : :
22682 : 121 : indexForm->indisvalid = true;
22683 : 121 : updated = true;
22684 : :
22685 : 121 : CatalogTupleUpdate(idxRel, &indTup->t_self, indTup);
22686 : :
22687 : 121 : table_close(idxRel, RowExclusiveLock);
22688 : 121 : heap_freetuple(indTup);
22689 : : }
22690 : :
22691 : : /*
22692 : : * If this index is in turn a partition of a larger index, validating it
22693 : : * might cause the parent to become valid also. Try that.
22694 : : */
22695 [ + + + + ]: 252 : if (updated && partedIdx->rd_rel->relispartition)
22696 : : {
22697 : : Oid parentIdxId,
22698 : : parentTblId;
22699 : : Relation parentIdx,
22700 : : parentTbl;
22701 : :
22702 : : /* make sure we see the validation we just did */
22703 : 32 : CommandCounterIncrement();
22704 : :
22705 : 32 : parentIdxId = get_partition_parent(RelationGetRelid(partedIdx), false);
22706 : 32 : parentTblId = get_partition_parent(RelationGetRelid(partedTbl), false);
22707 : 32 : parentIdx = relation_open(parentIdxId, AccessExclusiveLock);
22708 : 32 : parentTbl = relation_open(parentTblId, AccessExclusiveLock);
22709 : : Assert(!parentIdx->rd_index->indisvalid);
22710 : :
22711 : 32 : validatePartitionedIndex(parentIdx, parentTbl);
22712 : :
22713 : 32 : relation_close(parentIdx, AccessExclusiveLock);
22714 : 32 : relation_close(parentTbl, AccessExclusiveLock);
22715 : : }
22716 : 252 : }
22717 : :
22718 : : /*
22719 : : * When attaching an index as a partition of a partitioned index which is a
22720 : : * primary key, verify that all the columns in the partition are marked NOT
22721 : : * NULL.
22722 : : */
22723 : : static void
22724 : 55 : verifyPartitionIndexNotNull(IndexInfo *iinfo, Relation partition)
22725 : : {
22726 [ + + ]: 111 : for (int i = 0; i < iinfo->ii_NumIndexKeyAttrs; i++)
22727 : : {
22728 : 56 : Form_pg_attribute att = TupleDescAttr(RelationGetDescr(partition),
22729 : 56 : iinfo->ii_IndexAttrNumbers[i] - 1);
22730 : :
22731 [ - + ]: 56 : if (!att->attnotnull)
22732 [ # # ]: 0 : ereport(ERROR,
22733 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
22734 : : errmsg("invalid primary key definition"),
22735 : : errdetail("Column \"%s\" of relation \"%s\" is not marked NOT NULL.",
22736 : : NameStr(att->attname),
22737 : : RelationGetRelationName(partition)));
22738 : : }
22739 : 55 : }
22740 : :
22741 : : /*
22742 : : * Return an OID list of constraints that reference the given relation
22743 : : * that are marked as having a parent constraints.
22744 : : */
22745 : : static List *
22746 : 1085 : GetParentedForeignKeyRefs(Relation partition)
22747 : : {
22748 : : Relation pg_constraint;
22749 : : HeapTuple tuple;
22750 : : SysScanDesc scan;
22751 : : ScanKeyData key[2];
22752 : 1085 : List *constraints = NIL;
22753 : :
22754 : : /*
22755 : : * If no indexes, or no columns are referenceable by FKs, we can avoid the
22756 : : * scan.
22757 : : */
22758 [ + + + + ]: 1517 : if (RelationGetIndexList(partition) == NIL ||
22759 : 432 : bms_is_empty(RelationGetIndexAttrBitmap(partition,
22760 : : INDEX_ATTR_BITMAP_KEY)))
22761 : 826 : return NIL;
22762 : :
22763 : : /* Search for constraints referencing this table */
22764 : 259 : pg_constraint = table_open(ConstraintRelationId, AccessShareLock);
22765 : 259 : ScanKeyInit(&key[0],
22766 : : Anum_pg_constraint_confrelid, BTEqualStrategyNumber,
22767 : : F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(partition)));
22768 : 259 : ScanKeyInit(&key[1],
22769 : : Anum_pg_constraint_contype, BTEqualStrategyNumber,
22770 : : F_CHAREQ, CharGetDatum(CONSTRAINT_FOREIGN));
22771 : :
22772 : : /* XXX This is a seqscan, as we don't have a usable index */
22773 : 259 : scan = systable_beginscan(pg_constraint, InvalidOid, true, NULL, 2, key);
22774 [ + + ]: 380 : while ((tuple = systable_getnext(scan)) != NULL)
22775 : : {
22776 : 121 : Form_pg_constraint constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
22777 : :
22778 : : /*
22779 : : * We only need to process constraints that are part of larger ones.
22780 : : */
22781 [ - + ]: 121 : if (!OidIsValid(constrForm->conparentid))
22782 : 0 : continue;
22783 : :
22784 : 121 : constraints = lappend_oid(constraints, constrForm->oid);
22785 : : }
22786 : :
22787 : 259 : systable_endscan(scan);
22788 : 259 : table_close(pg_constraint, AccessShareLock);
22789 : :
22790 : 259 : return constraints;
22791 : : }
22792 : :
22793 : : /*
22794 : : * During DETACH PARTITION, verify that any foreign keys pointing to the
22795 : : * partitioned table would not become invalid. An error is raised if any
22796 : : * referenced values exist.
22797 : : */
22798 : : static void
22799 : 344 : ATDetachCheckNoForeignKeyRefs(Relation partition)
22800 : : {
22801 : : List *constraints;
22802 : : ListCell *cell;
22803 : :
22804 : 344 : constraints = GetParentedForeignKeyRefs(partition);
22805 : :
22806 [ + + + + : 387 : foreach(cell, constraints)
+ + ]
22807 : : {
22808 : 65 : Oid constrOid = lfirst_oid(cell);
22809 : : HeapTuple tuple;
22810 : : Form_pg_constraint constrForm;
22811 : : Relation rel;
22812 : 65 : Trigger trig = {0};
22813 : :
22814 : 65 : tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
22815 [ - + ]: 65 : if (!HeapTupleIsValid(tuple))
22816 [ # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u", constrOid);
22817 : 65 : constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
22818 : :
22819 : : Assert(OidIsValid(constrForm->conparentid));
22820 : : Assert(constrForm->confrelid == RelationGetRelid(partition));
22821 : :
22822 : : /* prevent data changes into the referencing table until commit */
22823 : 65 : rel = table_open(constrForm->conrelid, ShareLock);
22824 : :
22825 : 65 : trig.tgoid = InvalidOid;
22826 : 65 : trig.tgname = NameStr(constrForm->conname);
22827 : 65 : trig.tgenabled = TRIGGER_FIRES_ON_ORIGIN;
22828 : 65 : trig.tgisinternal = true;
22829 : 65 : trig.tgconstrrelid = RelationGetRelid(partition);
22830 : 65 : trig.tgconstrindid = constrForm->conindid;
22831 : 65 : trig.tgconstraint = constrForm->oid;
22832 : 65 : trig.tgdeferrable = false;
22833 : 65 : trig.tginitdeferred = false;
22834 : : /* we needn't fill in remaining fields */
22835 : :
22836 : 65 : RI_PartitionRemove_Check(&trig, rel, partition);
22837 : :
22838 : 43 : ReleaseSysCache(tuple);
22839 : :
22840 : 43 : table_close(rel, NoLock);
22841 : : }
22842 : 322 : }
22843 : :
22844 : : /*
22845 : : * resolve column compression specification to compression method.
22846 : : */
22847 : : static char
22848 : 171658 : GetAttributeCompression(Oid atttypid, const char *compression)
22849 : : {
22850 : : char cmethod;
22851 : :
22852 [ + + + + ]: 171658 : if (compression == NULL || strcmp(compression, "default") == 0)
22853 : 171519 : return InvalidCompressionMethod;
22854 : :
22855 : : /*
22856 : : * To specify a nondefault method, the column data type must be toastable.
22857 : : * Note this says nothing about whether the column's attstorage setting
22858 : : * permits compression; we intentionally allow attstorage and
22859 : : * attcompression to be independent. But with a non-toastable type,
22860 : : * attstorage could not be set to a value that would permit compression.
22861 : : *
22862 : : * We don't actually need to enforce this, since nothing bad would happen
22863 : : * if attcompression were non-default; it would never be consulted. But
22864 : : * it seems more user-friendly to complain about a certainly-useless
22865 : : * attempt to set the property.
22866 : : */
22867 [ + + ]: 139 : if (!TypeIsToastable(atttypid))
22868 [ + - ]: 4 : ereport(ERROR,
22869 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
22870 : : errmsg("column data type %s does not support compression",
22871 : : format_type_be(atttypid))));
22872 : :
22873 : 135 : cmethod = CompressionNameToMethod(compression);
22874 [ + + ]: 135 : if (!CompressionMethodIsValid(cmethod))
22875 [ + - ]: 8 : ereport(ERROR,
22876 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
22877 : : errmsg("invalid compression method \"%s\"", compression)));
22878 : :
22879 : 127 : return cmethod;
22880 : : }
22881 : :
22882 : : /*
22883 : : * resolve column storage specification
22884 : : */
22885 : : static char
22886 : 213 : GetAttributeStorage(Oid atttypid, const char *storagemode)
22887 : : {
22888 : 213 : char cstorage = 0;
22889 : :
22890 [ + + ]: 213 : if (pg_strcasecmp(storagemode, "plain") == 0)
22891 : 38 : cstorage = TYPSTORAGE_PLAIN;
22892 [ + + ]: 175 : else if (pg_strcasecmp(storagemode, "external") == 0)
22893 : 112 : cstorage = TYPSTORAGE_EXTERNAL;
22894 [ + + ]: 63 : else if (pg_strcasecmp(storagemode, "extended") == 0)
22895 : 26 : cstorage = TYPSTORAGE_EXTENDED;
22896 [ + + ]: 37 : else if (pg_strcasecmp(storagemode, "main") == 0)
22897 : 33 : cstorage = TYPSTORAGE_MAIN;
22898 [ + - ]: 4 : else if (pg_strcasecmp(storagemode, "default") == 0)
22899 : 4 : cstorage = get_typstorage(atttypid);
22900 : : else
22901 [ # # ]: 0 : ereport(ERROR,
22902 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
22903 : : errmsg("invalid storage type \"%s\"",
22904 : : storagemode)));
22905 : :
22906 : : /*
22907 : : * safety check: do not allow toasted storage modes unless column datatype
22908 : : * is TOAST-aware.
22909 : : */
22910 [ + + + + ]: 213 : if (!(cstorage == TYPSTORAGE_PLAIN || TypeIsToastable(atttypid)))
22911 [ + - ]: 4 : ereport(ERROR,
22912 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
22913 : : errmsg("column data type %s can only have storage PLAIN",
22914 : : format_type_be(atttypid))));
22915 : :
22916 : 209 : return cstorage;
22917 : : }
22918 : :
22919 : : /*
22920 : : * buildExpressionExecutionStates: build the needed expression execution states
22921 : : * for new partition (newPartRel) checks and initialize expressions for
22922 : : * generated columns. All expressions should be created in "tab"
22923 : : * (AlteredTableInfo structure).
22924 : : */
22925 : : static void
22926 : 480 : buildExpressionExecutionStates(AlteredTableInfo *tab, Relation newPartRel, EState *estate)
22927 : : {
22928 : : /*
22929 : : * Build the needed expression execution states. Here, we expect only NOT
22930 : : * NULL and CHECK constraint.
22931 : : */
22932 [ + + + + : 976 : foreach_ptr(NewConstraint, con, tab->constraints)
+ + ]
22933 : : {
22934 [ + - - ]: 16 : switch (con->contype)
22935 : : {
22936 : 16 : case CONSTR_CHECK:
22937 : :
22938 : : /*
22939 : : * We already expanded virtual expression in
22940 : : * createTableConstraints.
22941 : : */
22942 : 16 : con->qualstate = ExecPrepareExpr((Expr *) con->qual, estate);
22943 : 16 : break;
22944 : 0 : case CONSTR_NOTNULL:
22945 : : /* Nothing to do here. */
22946 : 0 : break;
22947 : 0 : default:
22948 [ # # ]: 0 : elog(ERROR, "unrecognized constraint type: %d",
22949 : : (int) con->contype);
22950 : : }
22951 : : }
22952 : :
22953 : : /* Expression already planned in createTableConstraints */
22954 [ + + + + : 1004 : foreach_ptr(NewColumnValue, ex, tab->newvals)
+ + ]
22955 : 44 : ex->exprstate = ExecInitExpr((Expr *) ex->expr, NULL);
22956 : 480 : }
22957 : :
22958 : : /*
22959 : : * evaluateGeneratedExpressionsAndCheckConstraints: evaluate any generated
22960 : : * expressions for "tab" (AlteredTableInfo structure) whose inputs come from
22961 : : * the new tuple (insertslot) of the new partition (newPartRel).
22962 : : */
22963 : : static void
22964 : 912 : evaluateGeneratedExpressionsAndCheckConstraints(AlteredTableInfo *tab,
22965 : : Relation newPartRel,
22966 : : TupleTableSlot *insertslot,
22967 : : ExprContext *econtext)
22968 : : {
22969 : 912 : econtext->ecxt_scantuple = insertslot;
22970 : :
22971 [ + + + + : 1888 : foreach_ptr(NewColumnValue, ex, tab->newvals)
+ + ]
22972 : : {
22973 [ - + ]: 64 : if (!ex->is_generated)
22974 : 0 : continue;
22975 : :
22976 : 64 : insertslot->tts_values[ex->attnum - 1]
22977 : 64 : = ExecEvalExpr(ex->exprstate,
22978 : : econtext,
22979 : 64 : &insertslot->tts_isnull[ex->attnum - 1]);
22980 : : }
22981 : :
22982 [ + + + + : 1848 : foreach_ptr(NewConstraint, con, tab->constraints)
+ + ]
22983 : : {
22984 [ + - - ]: 24 : switch (con->contype)
22985 : : {
22986 : 24 : case CONSTR_CHECK:
22987 [ - + ]: 24 : if (!ExecCheck(con->qualstate, econtext))
22988 [ # # ]: 0 : ereport(ERROR,
22989 : : errcode(ERRCODE_CHECK_VIOLATION),
22990 : : errmsg("check constraint \"%s\" of relation \"%s\" is violated by some row",
22991 : : con->name, RelationGetRelationName(newPartRel)),
22992 : : errtableconstraint(newPartRel, con->name));
22993 : 24 : break;
22994 : 0 : case CONSTR_NOTNULL:
22995 : : case CONSTR_FOREIGN:
22996 : : /* Nothing to do here */
22997 : 0 : break;
22998 : 0 : default:
22999 [ # # ]: 0 : elog(ERROR, "unrecognized constraint type: %d",
23000 : : (int) con->contype);
23001 : : }
23002 : : }
23003 : 912 : }
23004 : :
23005 : : /*
23006 : : * getAttributesList: build a list of columns (ColumnDef) based on parent_rel
23007 : : */
23008 : : static List *
23009 : 508 : getAttributesList(Relation parent_rel)
23010 : : {
23011 : : AttrNumber parent_attno;
23012 : : TupleDesc modelDesc;
23013 : 508 : List *colList = NIL;
23014 : :
23015 : 508 : modelDesc = RelationGetDescr(parent_rel);
23016 : :
23017 [ + + ]: 1721 : for (parent_attno = 1; parent_attno <= modelDesc->natts;
23018 : 1213 : parent_attno++)
23019 : : {
23020 : 1213 : Form_pg_attribute attribute = TupleDescAttr(modelDesc,
23021 : : parent_attno - 1);
23022 : : ColumnDef *def;
23023 : :
23024 : : /* Ignore dropped columns in the parent. */
23025 [ - + ]: 1213 : if (attribute->attisdropped)
23026 : 0 : continue;
23027 : :
23028 : 1213 : def = makeColumnDef(NameStr(attribute->attname), attribute->atttypid,
23029 : : attribute->atttypmod, attribute->attcollation);
23030 : :
23031 : 1213 : def->is_not_null = attribute->attnotnull;
23032 : :
23033 : : /* Copy identity. */
23034 : 1213 : def->identity = attribute->attidentity;
23035 : :
23036 : : /* Copy attgenerated. */
23037 : 1213 : def->generated = attribute->attgenerated;
23038 : :
23039 : 1213 : def->storage = attribute->attstorage;
23040 : :
23041 : : /* Likewise, copy compression. */
23042 [ + + ]: 1213 : if (CompressionMethodIsValid(attribute->attcompression))
23043 : 12 : def->compression =
23044 : 12 : pstrdup(GetCompressionMethodName(attribute->attcompression));
23045 : : else
23046 : 1201 : def->compression = NULL;
23047 : :
23048 : : /* Add to column list. */
23049 : 1213 : colList = lappend(colList, def);
23050 : : }
23051 : :
23052 : 508 : return colList;
23053 : : }
23054 : :
23055 : : /*
23056 : : * createTableConstraints:
23057 : : * create check constraints, default values, and generated values for newRel
23058 : : * based on parent_rel. tab is pending-work queue for newRel, we may need it in
23059 : : * MergePartitionsMoveRows.
23060 : : */
23061 : : static void
23062 : 480 : createTableConstraints(List **wqueue, AlteredTableInfo *tab,
23063 : : Relation parent_rel, Relation newRel)
23064 : : {
23065 : : TupleDesc tupleDesc;
23066 : : TupleConstr *constr;
23067 : : AttrMap *attmap;
23068 : : AttrNumber parent_attno;
23069 : : int ccnum;
23070 : 480 : List *constraints = NIL;
23071 : 480 : List *cookedConstraints = NIL;
23072 : :
23073 : 480 : tupleDesc = RelationGetDescr(parent_rel);
23074 : 480 : constr = tupleDesc->constr;
23075 : :
23076 [ + + ]: 480 : if (!constr)
23077 : 324 : return;
23078 : :
23079 : : /*
23080 : : * Construct a map from the parent relation's attnos to the child rel's.
23081 : : * This re-checks type match, etc, although it shouldn't be possible to
23082 : : * have a failure since both tables are locked.
23083 : : */
23084 : 156 : attmap = build_attrmap_by_name(RelationGetDescr(newRel),
23085 : : tupleDesc,
23086 : : false);
23087 : :
23088 : : /* Cycle for default values. */
23089 [ + + ]: 592 : for (parent_attno = 1; parent_attno <= tupleDesc->natts; parent_attno++)
23090 : : {
23091 : 436 : Form_pg_attribute attribute = TupleDescAttr(tupleDesc,
23092 : : parent_attno - 1);
23093 : :
23094 : : /* Ignore dropped columns in the parent. */
23095 [ - + ]: 436 : if (attribute->attisdropped)
23096 : 0 : continue;
23097 : :
23098 : : /* Copy the default, if present, and it should be copied. */
23099 [ + + ]: 436 : if (attribute->atthasdef)
23100 : : {
23101 : 100 : Node *this_default = NULL;
23102 : : bool found_whole_row;
23103 : : AttrNumber num;
23104 : : Node *def;
23105 : : NewColumnValue *newval;
23106 : :
23107 [ + + ]: 100 : if (attribute->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
23108 : 4 : this_default = build_generation_expression(parent_rel, attribute->attnum);
23109 : : else
23110 : : {
23111 : 96 : this_default = TupleDescGetDefault(tupleDesc, attribute->attnum);
23112 [ - + ]: 96 : if (this_default == NULL)
23113 [ # # ]: 0 : elog(ERROR, "default expression not found for attribute %d of relation \"%s\"",
23114 : : attribute->attnum, RelationGetRelationName(parent_rel));
23115 : : }
23116 : :
23117 : 100 : num = attmap->attnums[parent_attno - 1];
23118 : 100 : def = map_variable_attnos(this_default, 1, 0, attmap, InvalidOid, &found_whole_row);
23119 : :
23120 [ - + - - ]: 100 : if (found_whole_row && attribute->attgenerated != '\0')
23121 [ # # ]: 0 : elog(ERROR, "cannot convert whole-row table reference");
23122 : :
23123 : : /* Add a pre-cooked default expression. */
23124 : 100 : StoreAttrDefault(newRel, num, def, false);
23125 : :
23126 : : /*
23127 : : * Stored generated column expressions in parent_rel might
23128 : : * reference the tableoid. newRel, parent_rel tableoid clear is
23129 : : * not the same. If so, these stored generated columns require
23130 : : * recomputation for newRel within MergePartitionsMoveRows.
23131 : : */
23132 [ + + ]: 100 : if (attribute->attgenerated == ATTRIBUTE_GENERATED_STORED)
23133 : : {
23134 : 44 : newval = palloc0_object(NewColumnValue);
23135 : 44 : newval->attnum = num;
23136 : 44 : newval->expr = expression_planner((Expr *) def);
23137 : 44 : newval->is_generated = (attribute->attgenerated != '\0');
23138 : 44 : tab->newvals = lappend(tab->newvals, newval);
23139 : : }
23140 : : }
23141 : : }
23142 : :
23143 : : /* Cycle for CHECK constraints. */
23144 [ + + ]: 224 : for (ccnum = 0; ccnum < constr->num_check; ccnum++)
23145 : : {
23146 : 68 : char *ccname = constr->check[ccnum].ccname;
23147 : 68 : char *ccbin = constr->check[ccnum].ccbin;
23148 : 68 : bool ccenforced = constr->check[ccnum].ccenforced;
23149 : 68 : bool ccnoinherit = constr->check[ccnum].ccnoinherit;
23150 : 68 : bool ccvalid = constr->check[ccnum].ccvalid;
23151 : : Node *ccbin_node;
23152 : : bool found_whole_row;
23153 : : Constraint *con;
23154 : :
23155 : : /*
23156 : : * The partitioned table can not have a NO INHERIT check constraint
23157 : : * (see StoreRelCheck function for details).
23158 : : */
23159 : : Assert(!ccnoinherit);
23160 : :
23161 : 68 : ccbin_node = map_variable_attnos(stringToNode(ccbin),
23162 : : 1, 0,
23163 : : attmap,
23164 : : InvalidOid, &found_whole_row);
23165 : :
23166 : : /*
23167 : : * For the moment we have to reject whole-row variables (as for CREATE
23168 : : * TABLE LIKE and inheritances).
23169 : : */
23170 [ - + ]: 68 : if (found_whole_row)
23171 [ # # ]: 0 : elog(ERROR, "Constraint \"%s\" contains a whole-row reference to table \"%s\".",
23172 : : ccname,
23173 : : RelationGetRelationName(parent_rel));
23174 : :
23175 : 68 : con = makeNode(Constraint);
23176 : 68 : con->contype = CONSTR_CHECK;
23177 : 68 : con->conname = pstrdup(ccname);
23178 : 68 : con->deferrable = false;
23179 : 68 : con->initdeferred = false;
23180 : 68 : con->is_enforced = ccenforced;
23181 : 68 : con->skip_validation = !ccvalid;
23182 : 68 : con->initially_valid = ccvalid;
23183 : 68 : con->is_no_inherit = ccnoinherit;
23184 : 68 : con->raw_expr = NULL;
23185 : 68 : con->cooked_expr = nodeToString(ccbin_node);
23186 : 68 : con->location = -1;
23187 : 68 : constraints = lappend(constraints, con);
23188 : : }
23189 : :
23190 : : /* Install all CHECK constraints. */
23191 : 156 : cookedConstraints = AddRelationNewConstraints(newRel, NIL, constraints,
23192 : : false, true, false, NULL);
23193 : :
23194 : : /* Make the additional catalog changes visible. */
23195 : 156 : CommandCounterIncrement();
23196 : :
23197 : : /*
23198 : : * parent_rel check constraint expression may reference tableoid, so later
23199 : : * in MergePartitionsMoveRows, we need to evaluate the check constraint
23200 : : * again for the newRel. We can check whether the check constraint
23201 : : * contains a tableoid reference via pull_varattnos.
23202 : : */
23203 [ + + + + : 380 : foreach_ptr(CookedConstraint, ccon, cookedConstraints)
+ + ]
23204 : : {
23205 [ + + ]: 68 : if (!ccon->skip_validation)
23206 : : {
23207 : : Node *qual;
23208 : 44 : Bitmapset *attnums = NULL;
23209 : :
23210 : : Assert(ccon->contype == CONSTR_CHECK);
23211 : 44 : qual = expand_generated_columns_in_expr(ccon->expr, newRel, 1);
23212 : 44 : pull_varattnos(qual, 1, &attnums);
23213 : :
23214 : : /*
23215 : : * Add a check only if it contains a tableoid
23216 : : * (TableOidAttributeNumber).
23217 : : */
23218 [ + + ]: 44 : if (bms_is_member(TableOidAttributeNumber - FirstLowInvalidHeapAttributeNumber,
23219 : : attnums))
23220 : : {
23221 : : NewConstraint *newcon;
23222 : :
23223 : 16 : newcon = palloc0_object(NewConstraint);
23224 : 16 : newcon->name = ccon->name;
23225 : 16 : newcon->contype = CONSTR_CHECK;
23226 : 16 : newcon->qual = qual;
23227 : :
23228 : 16 : tab->constraints = lappend(tab->constraints, newcon);
23229 : : }
23230 : : }
23231 : : }
23232 : :
23233 : : /* Don't need the cookedConstraints anymore. */
23234 : 156 : list_free_deep(cookedConstraints);
23235 : :
23236 : : /* Reproduce not-null constraints. */
23237 [ + + ]: 156 : if (constr->has_not_null)
23238 : : {
23239 : : List *nnconstraints;
23240 : :
23241 : : /*
23242 : : * The "include_noinh" argument is false because a partitioned table
23243 : : * can't have NO INHERIT constraint.
23244 : : */
23245 : 108 : nnconstraints = RelationGetNotNullConstraints(RelationGetRelid(parent_rel),
23246 : : false, false);
23247 : :
23248 : : Assert(list_length(nnconstraints) > 0);
23249 : :
23250 : : /*
23251 : : * We already set pg_attribute.attnotnull in createPartitionTable. No
23252 : : * need call set_attnotnull again.
23253 : : */
23254 : 108 : AddRelationNewConstraints(newRel, NIL, nnconstraints, false, true, false, NULL);
23255 : : }
23256 : : }
23257 : :
23258 : : /*
23259 : : * createPartitionTable:
23260 : : *
23261 : : * Create a new partition (newPartName) for the partitioned table (parent_rel).
23262 : : * ownerId is determined by the partition on which the operation is performed,
23263 : : * so it is passed separately. The new partition will inherit the access method
23264 : : * and persistence type from the parent table.
23265 : : *
23266 : : * Returns the created relation (locked in AccessExclusiveLock mode).
23267 : : */
23268 : : static Relation
23269 : 508 : createPartitionTable(List **wqueue, RangeVar *newPartName,
23270 : : Relation parent_rel, Oid ownerId)
23271 : : {
23272 : : Relation newRel;
23273 : : Oid newRelId;
23274 : : Oid existingRelid;
23275 : : Oid tablespaceId;
23276 : : TupleDesc descriptor;
23277 : 508 : List *colList = NIL;
23278 : : Oid relamId;
23279 : : Oid namespaceId;
23280 : : AlteredTableInfo *new_partrel_tab;
23281 : 508 : Form_pg_class parent_relform = parent_rel->rd_rel;
23282 : :
23283 : : /* If the existing rel is temp, it must belong to this session. */
23284 [ + + - + ]: 508 : if (RELATION_IS_OTHER_TEMP(parent_rel))
23285 [ # # ]: 0 : ereport(ERROR,
23286 : : errcode(ERRCODE_WRONG_OBJECT_TYPE),
23287 : : errmsg("cannot create as partition of temporary relation of another session"));
23288 : :
23289 : : /* Look up inheritance ancestors and generate the relation schema. */
23290 : 508 : colList = getAttributesList(parent_rel);
23291 : :
23292 : : /* Create a tuple descriptor from the relation schema. */
23293 : 508 : descriptor = BuildDescForRelation(colList);
23294 : :
23295 : : /* Look up the access method for the new relation. */
23296 [ + + ]: 508 : relamId = (parent_relform->relam != InvalidOid) ? parent_relform->relam : HEAP_TABLE_AM_OID;
23297 : :
23298 : : /* Look up the namespace in which we are supposed to create the relation. */
23299 : : namespaceId =
23300 : 508 : RangeVarGetAndCheckCreationNamespace(newPartName, NoLock, &existingRelid);
23301 [ - + ]: 508 : if (OidIsValid(existingRelid))
23302 [ # # ]: 0 : ereport(ERROR,
23303 : : errcode(ERRCODE_DUPLICATE_TABLE),
23304 : : errmsg("relation \"%s\" already exists", newPartName->relname));
23305 : :
23306 : : /*
23307 : : * We intended to create the partition with the same persistence as the
23308 : : * parent table, but we still need to recheck because that might be
23309 : : * affected by the search_path. If the parent is permanent, so must be
23310 : : * all of its partitions.
23311 : : */
23312 [ + + ]: 508 : if (parent_relform->relpersistence != RELPERSISTENCE_TEMP &&
23313 [ + + ]: 472 : newPartName->relpersistence == RELPERSISTENCE_TEMP)
23314 [ + - ]: 8 : ereport(ERROR,
23315 : : errcode(ERRCODE_WRONG_OBJECT_TYPE),
23316 : : errmsg("cannot create a temporary relation as partition of permanent relation \"%s\"",
23317 : : RelationGetRelationName(parent_rel)));
23318 : :
23319 : : /* Permanent rels cannot be partitions belonging to a temporary parent. */
23320 [ + + ]: 500 : if (newPartName->relpersistence != RELPERSISTENCE_TEMP &&
23321 [ + + ]: 476 : parent_relform->relpersistence == RELPERSISTENCE_TEMP)
23322 [ + - ]: 12 : ereport(ERROR,
23323 : : errcode(ERRCODE_WRONG_OBJECT_TYPE),
23324 : : errmsg("cannot create a permanent relation as partition of temporary relation \"%s\"",
23325 : : RelationGetRelationName(parent_rel)));
23326 : :
23327 : : /*
23328 : : * Select the tablespace for the new partition. Mirror the logic that
23329 : : * CREATE TABLE foo PARTITION OF ... uses in DefineRelation: take the
23330 : : * partitioned parent's explicit tablespace if it has one, otherwise take
23331 : : * default_tablespace into account, and finally use the database default.
23332 : : */
23333 : 488 : tablespaceId = parent_relform->reltablespace;
23334 [ + + ]: 488 : if (!OidIsValid(tablespaceId))
23335 : 464 : tablespaceId = GetDefaultTablespace(newPartName->relpersistence, false);
23336 : :
23337 : : /* Check permissions except when using database's default */
23338 [ + + + - ]: 488 : if (OidIsValid(tablespaceId) && tablespaceId != MyDatabaseTableSpace)
23339 : : {
23340 : : AclResult aclresult;
23341 : :
23342 : 44 : aclresult = object_aclcheck(TableSpaceRelationId, tablespaceId,
23343 : : GetUserId(), ACL_CREATE);
23344 [ - + ]: 44 : if (aclresult != ACLCHECK_OK)
23345 : 0 : aclcheck_error(aclresult, OBJECT_TABLESPACE,
23346 : 0 : get_tablespace_name(tablespaceId));
23347 : : }
23348 : :
23349 : : /* In all cases disallow placing user relations in pg_global */
23350 [ + + ]: 488 : if (tablespaceId == GLOBALTABLESPACE_OID)
23351 [ + - ]: 8 : ereport(ERROR,
23352 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
23353 : : errmsg("only shared relations can be placed in pg_global tablespace")));
23354 : :
23355 : : /* Create the relation. */
23356 : 480 : newRelId = heap_create_with_catalog(newPartName->relname,
23357 : : namespaceId,
23358 : : tablespaceId,
23359 : : InvalidOid,
23360 : : InvalidOid,
23361 : : InvalidOid,
23362 : : ownerId,
23363 : : relamId,
23364 : : descriptor,
23365 : : NIL,
23366 : : RELKIND_RELATION,
23367 : 480 : newPartName->relpersistence,
23368 : : false,
23369 : : false,
23370 : : ONCOMMIT_NOOP,
23371 : : (Datum) 0,
23372 : : true,
23373 : : allowSystemTableMods,
23374 : : false, /* is_internal */
23375 : : InvalidOid,
23376 : : NULL);
23377 : :
23378 : : /*
23379 : : * We must bump the command counter to make the newly-created relation
23380 : : * tuple visible for opening.
23381 : : */
23382 : 480 : CommandCounterIncrement();
23383 : :
23384 : : /*
23385 : : * Create a TOAST table if the table needs one. MERGE/SPLIT PARTITION
23386 : : * moves rows from existing partition(s) into new partition(s), which may
23387 : : * carry out-of-line varlena values that the new relation must be able to
23388 : : * store. Also, the new partition must be able to receive out-of-line
23389 : : * varlena values after the DDL operation is complete.
23390 : : */
23391 : 480 : NewRelationCreateToastTable(newRelId, (Datum) 0);
23392 : :
23393 : : /*
23394 : : * Open the new partition with no lock, because we already have an
23395 : : * AccessExclusiveLock placed there after creation.
23396 : : */
23397 : 480 : newRel = table_open(newRelId, NoLock);
23398 : :
23399 : : /* Find or create a work queue entry for the newly created table. */
23400 : 480 : new_partrel_tab = ATGetQueueEntry(wqueue, newRel);
23401 : :
23402 : : /* Create constraints, default values, and generated values. */
23403 : 480 : createTableConstraints(wqueue, new_partrel_tab, parent_rel, newRel);
23404 : :
23405 : : /*
23406 : : * Need to call CommandCounterIncrement, so a fresh relcache entry has
23407 : : * newly installed constraint info.
23408 : : */
23409 : 480 : CommandCounterIncrement();
23410 : :
23411 : 480 : return newRel;
23412 : : }
23413 : :
23414 : : /*
23415 : : * MergePartitionsMoveRows: scan partitions to be merged (mergingPartitions)
23416 : : * of the partitioned table and move rows into the new partition
23417 : : * (newPartRel). We also verify check constraints against these rows.
23418 : : */
23419 : : static void
23420 : 106 : MergePartitionsMoveRows(List **wqueue, List *mergingPartitions, Relation newPartRel)
23421 : : {
23422 : : CommandId mycid;
23423 : : EState *estate;
23424 : : AlteredTableInfo *tab;
23425 : : ListCell *ltab;
23426 : :
23427 : : /* The FSM is empty, so don't bother using it. */
23428 : 106 : uint32 ti_options = TABLE_INSERT_SKIP_FSM;
23429 : : BulkInsertState bistate; /* state of bulk inserts for partition */
23430 : : TupleTableSlot *dstslot;
23431 : :
23432 : : /* Find the work queue entry for the new partition table: newPartRel. */
23433 : 106 : tab = ATGetQueueEntry(wqueue, newPartRel);
23434 : :
23435 : : /* Generate the constraint and default execution states. */
23436 : 106 : estate = CreateExecutorState();
23437 : :
23438 : 106 : buildExpressionExecutionStates(tab, newPartRel, estate);
23439 : :
23440 : 106 : mycid = GetCurrentCommandId(true);
23441 : :
23442 : : /* Prepare a BulkInsertState for table_tuple_insert. */
23443 : 106 : bistate = GetBulkInsertState();
23444 : :
23445 : : /* Create the necessary tuple slot. */
23446 : 106 : dstslot = table_slot_create(newPartRel, NULL);
23447 : :
23448 [ + - + + : 452 : foreach_oid(merging_oid, mergingPartitions)
+ + ]
23449 : : {
23450 : : ExprContext *econtext;
23451 : : TupleTableSlot *srcslot;
23452 : : TupleConversionMap *tuple_map;
23453 : : TableScanDesc scan;
23454 : : MemoryContext oldCxt;
23455 : : Snapshot snapshot;
23456 : : Relation mergingPartition;
23457 : :
23458 [ + + ]: 240 : econtext = GetPerTupleExprContext(estate);
23459 : :
23460 : : /*
23461 : : * Partition is already locked in the transformPartitionCmdForMerge
23462 : : * function.
23463 : : */
23464 : 240 : mergingPartition = table_open(merging_oid, NoLock);
23465 : :
23466 : : /* Create a source tuple slot for the partition being merged. */
23467 : 240 : srcslot = table_slot_create(mergingPartition, NULL);
23468 : :
23469 : : /*
23470 : : * Map computing for moving attributes of the merged partition to the
23471 : : * new partition.
23472 : : */
23473 : 240 : tuple_map = convert_tuples_by_name(RelationGetDescr(mergingPartition),
23474 : : RelationGetDescr(newPartRel));
23475 : :
23476 : : /* Scan through the rows. */
23477 : 240 : snapshot = RegisterSnapshot(GetLatestSnapshot());
23478 : 240 : scan = table_beginscan(mergingPartition, snapshot, 0, NULL,
23479 : : SO_NONE);
23480 : :
23481 : : /*
23482 : : * Switch to per-tuple memory context and reset it for each tuple
23483 : : * produced, so we don't leak memory.
23484 : : */
23485 [ + - ]: 240 : oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
23486 : :
23487 [ + + ]: 605 : while (table_scan_getnextslot(scan, ForwardScanDirection, srcslot))
23488 : : {
23489 : : TupleTableSlot *insertslot;
23490 : :
23491 [ - + ]: 365 : CHECK_FOR_INTERRUPTS();
23492 : :
23493 [ + + ]: 365 : if (tuple_map)
23494 : : {
23495 : : /* Need to use a map to copy attributes. */
23496 : 28 : insertslot = execute_attr_map_slot(tuple_map->attrMap, srcslot, dstslot);
23497 : : }
23498 : : else
23499 : : {
23500 : 337 : slot_getallattrs(srcslot);
23501 : :
23502 : : /* Copy attributes directly. */
23503 : 337 : insertslot = dstslot;
23504 : :
23505 : 337 : ExecClearTuple(insertslot);
23506 : :
23507 : 337 : memcpy(insertslot->tts_values, srcslot->tts_values,
23508 : 337 : sizeof(Datum) * srcslot->tts_nvalid);
23509 : 337 : memcpy(insertslot->tts_isnull, srcslot->tts_isnull,
23510 : 337 : sizeof(bool) * srcslot->tts_nvalid);
23511 : :
23512 : 337 : ExecStoreVirtualTuple(insertslot);
23513 : : }
23514 : :
23515 : : /*
23516 : : * Constraints and GENERATED expressions might reference the
23517 : : * tableoid column, so fill tts_tableOid with the desired value.
23518 : : * (We must do this each time, because it gets overwritten with
23519 : : * newrel's OID during storing.)
23520 : : */
23521 : 365 : insertslot->tts_tableOid = RelationGetRelid(newPartRel);
23522 : :
23523 : : /*
23524 : : * Now, evaluate any generated expressions whose inputs come from
23525 : : * the new tuple. We assume these columns won't reference each
23526 : : * other, so that there's no ordering dependency.
23527 : : */
23528 : 365 : evaluateGeneratedExpressionsAndCheckConstraints(tab, newPartRel,
23529 : : insertslot, econtext);
23530 : :
23531 : : /* Write the tuple out to the new relation. */
23532 : 365 : table_tuple_insert(newPartRel, insertslot, mycid,
23533 : : ti_options, bistate);
23534 : :
23535 : 365 : ResetExprContext(econtext);
23536 : : }
23537 : :
23538 : 240 : MemoryContextSwitchTo(oldCxt);
23539 : 240 : table_endscan(scan);
23540 : 240 : UnregisterSnapshot(snapshot);
23541 : :
23542 [ + + ]: 240 : if (tuple_map)
23543 : 20 : free_conversion_map(tuple_map);
23544 : :
23545 : 240 : ExecDropSingleTupleTableSlot(srcslot);
23546 : 240 : table_close(mergingPartition, NoLock);
23547 : : }
23548 : :
23549 : 106 : FreeExecutorState(estate);
23550 : 106 : ExecDropSingleTupleTableSlot(dstslot);
23551 : 106 : FreeBulkInsertState(bistate);
23552 : :
23553 : 106 : table_finish_bulk_insert(newPartRel, ti_options);
23554 : :
23555 : : /*
23556 : : * We don't need to process this newPartRel since we already processed it
23557 : : * here, so delete the ALTER TABLE queue for it.
23558 : : */
23559 [ + - + - : 212 : foreach(ltab, *wqueue)
+ - ]
23560 : : {
23561 : 212 : tab = (AlteredTableInfo *) lfirst(ltab);
23562 [ + + ]: 212 : if (tab->relid == RelationGetRelid(newPartRel))
23563 : : {
23564 : 106 : *wqueue = list_delete_cell(*wqueue, ltab);
23565 : 106 : break;
23566 : : }
23567 : : }
23568 : 106 : }
23569 : :
23570 : : /*
23571 : : * detachPartitionTable: detach partition "child_rel" from partitioned table
23572 : : * "parent_rel" with default partition identifier "defaultPartOid"
23573 : : */
23574 : : static void
23575 : 437 : detachPartitionTable(Relation parent_rel, Relation child_rel, Oid defaultPartOid)
23576 : : {
23577 : : /* Remove the pg_inherits row first. */
23578 : 437 : RemoveInheritance(child_rel, parent_rel, false);
23579 : :
23580 : : /*
23581 : : * Detaching the partition might involve TOAST table access, so ensure we
23582 : : * have a valid snapshot.
23583 : : */
23584 : 437 : PushActiveSnapshot(GetTransactionSnapshot());
23585 : :
23586 : : /* Do the final part of detaching. */
23587 : 437 : DetachPartitionFinalize(parent_rel, child_rel, false, defaultPartOid);
23588 : :
23589 : 437 : PopActiveSnapshot();
23590 : 437 : }
23591 : :
23592 : : /*
23593 : : * equal_oid_lists: return true if two OID lists, each sorted in ascending
23594 : : * order, contain the same OIDs in the same order.
23595 : : */
23596 : : static bool
23597 : 86 : equal_oid_lists(const List *a, const List *b)
23598 : : {
23599 : : ListCell *la,
23600 : : *lb;
23601 : :
23602 [ + + ]: 86 : if (list_length(a) != list_length(b))
23603 : 4 : return false;
23604 : :
23605 [ + + + + : 86 : forboth(la, a, lb, b)
+ + + + +
+ + - +
+ ]
23606 : : {
23607 [ + + ]: 6 : if (lfirst_oid(la) != lfirst_oid(lb))
23608 : 2 : return false;
23609 : : }
23610 : 80 : return true;
23611 : : }
23612 : :
23613 : : /*
23614 : : * Comparator for list_sort() on a list of PartitionIndexExtDepEntry *.
23615 : : * Orders by parentIndexOid, then by indexOid as a tiebreaker so conflict
23616 : : * reports for different parent indexes are deterministic.
23617 : : */
23618 : : static int
23619 : 145 : cmp_partition_index_ext_dep(const ListCell *a, const ListCell *b)
23620 : : {
23621 : 145 : const PartitionIndexExtDepEntry *ea = lfirst(a);
23622 : 145 : const PartitionIndexExtDepEntry *eb = lfirst(b);
23623 : :
23624 [ + + ]: 145 : if (ea->parentIndexOid != eb->parentIndexOid)
23625 : 53 : return pg_cmp_u32(ea->parentIndexOid, eb->parentIndexOid);
23626 : 92 : return pg_cmp_u32(ea->indexOid, eb->indexOid);
23627 : : }
23628 : :
23629 : : /*
23630 : : * collectPartitionIndexExtDeps: collect extension dependencies from indexes
23631 : : * on the given partitions.
23632 : : *
23633 : : * For each partition index that has a parent partitioned index, we collect
23634 : : * extension dependencies. All source partition indexes sharing the same
23635 : : * parent partitioned index must depend on exactly the same set of
23636 : : * extensions; otherwise an error is raised so that we neither silently drop
23637 : : * nor silently add dependencies on the merged partition's index.
23638 : : *
23639 : : * Indexes that don't have a parent partitioned index (i.e., indexes created
23640 : : * directly on a partition without a corresponding parent index) are skipped.
23641 : : *
23642 : : * The returned list is sorted by parentIndexOid with exactly one entry per
23643 : : * parent partitioned index, so applyPartitionIndexExtDeps() can scan it
23644 : : * linearly.
23645 : : */
23646 : : static List *
23647 : 285 : collectPartitionIndexExtDeps(List *partitionOids)
23648 : : {
23649 : 285 : List *collected = NIL;
23650 : 285 : List *result = NIL;
23651 : 285 : PartitionIndexExtDepEntry *prev = NULL;
23652 : :
23653 : : /*
23654 : : * Phase 1: collect one entry per (partition index -> parent index) pair,
23655 : : * with its extension dependency OIDs sorted ascending.
23656 : : */
23657 [ + - + + : 1019 : foreach_oid(partOid, partitionOids)
+ + ]
23658 : : {
23659 : : Relation partRel;
23660 : : List *indexList;
23661 : :
23662 : : /*
23663 : : * Use NoLock since the caller already holds AccessExclusiveLock on
23664 : : * these partitions.
23665 : : */
23666 : 449 : partRel = table_open(partOid, NoLock);
23667 : 449 : indexList = RelationGetIndexList(partRel);
23668 : :
23669 [ + + + + : 1097 : foreach_oid(indexOid, indexList)
+ + ]
23670 : : {
23671 : : Oid parentIndexOid;
23672 : : PartitionIndexExtDepEntry *entry;
23673 : :
23674 [ + + ]: 199 : if (!get_rel_relispartition(indexOid))
23675 : 1 : continue;
23676 : :
23677 : 198 : parentIndexOid = get_partition_parent(indexOid, true);
23678 [ - + ]: 198 : if (!OidIsValid(parentIndexOid))
23679 : 0 : continue;
23680 : :
23681 : 198 : entry = palloc(sizeof(PartitionIndexExtDepEntry));
23682 : 198 : entry->parentIndexOid = parentIndexOid;
23683 : 198 : entry->indexOid = indexOid;
23684 : 198 : entry->extensionOids = getAutoExtensionsOfObject(RelationRelationId,
23685 : : indexOid);
23686 : 198 : list_sort(entry->extensionOids, list_oid_cmp);
23687 : :
23688 : 198 : collected = lappend(collected, entry);
23689 : : }
23690 : :
23691 : 449 : list_free(indexList);
23692 : 449 : table_close(partRel, NoLock);
23693 : : }
23694 : :
23695 : : /*
23696 : : * Phase 2: sort by parentIndexOid so entries sharing a parent index sit
23697 : : * adjacent.
23698 : : */
23699 : 285 : list_sort(collected, cmp_partition_index_ext_dep);
23700 : :
23701 : : /*
23702 : : * Phase 3: single linear pass verifying that adjacent entries sharing a
23703 : : * parent index have identical extension dependencies, and keeping one
23704 : : * representative entry per parent index.
23705 : : */
23706 [ + + + + : 744 : foreach_ptr(PartitionIndexExtDepEntry, entry, collected)
+ + ]
23707 : : {
23708 [ + + + + ]: 186 : if (prev != NULL && prev->parentIndexOid == entry->parentIndexOid)
23709 : : {
23710 [ + + ]: 86 : if (!equal_oid_lists(prev->extensionOids, entry->extensionOids))
23711 [ + - ]: 6 : ereport(ERROR,
23712 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
23713 : : errmsg("cannot merge partitions with conflicting extension dependencies"),
23714 : : errdetail("Partition indexes \"%s\" and \"%s\" depend on different extensions.",
23715 : : get_rel_name(prev->indexOid),
23716 : : get_rel_name(entry->indexOid))));
23717 : :
23718 : : /* Duplicate entry for the same parent index; discard. */
23719 : 80 : list_free(entry->extensionOids);
23720 : 80 : pfree(entry);
23721 : 80 : continue;
23722 : : }
23723 : :
23724 : 100 : result = lappend(result, entry);
23725 : 100 : prev = entry;
23726 : : }
23727 : :
23728 : 279 : list_free(collected);
23729 : :
23730 : 279 : return result;
23731 : : }
23732 : :
23733 : : /*
23734 : : * applyPartitionIndexExtDeps: apply collected extension dependencies to
23735 : : * indexes on a new partition.
23736 : : *
23737 : : * For each index on the new partition, look up its parent index in the
23738 : : * extDepState list. If found, record extension dependencies on the new index.
23739 : : * extDepState is sorted by parentIndexOid, so the inner scan can bail out
23740 : : * as soon as it passes the target OID.
23741 : : */
23742 : : static void
23743 : 480 : applyPartitionIndexExtDeps(Oid newPartOid, List *extDepState)
23744 : : {
23745 : : Relation partRel;
23746 : : List *indexList;
23747 : :
23748 [ + + ]: 480 : if (extDepState == NIL)
23749 : 332 : return;
23750 : :
23751 : : /*
23752 : : * Use NoLock since the caller already holds AccessExclusiveLock on the
23753 : : * new partition.
23754 : : */
23755 : 148 : partRel = table_open(newPartOid, NoLock);
23756 : 148 : indexList = RelationGetIndexList(partRel);
23757 : :
23758 [ + - + + : 468 : foreach_oid(indexOid, indexList)
+ + ]
23759 : : {
23760 : : Oid parentIdxOid;
23761 : :
23762 [ - + ]: 172 : if (!get_rel_relispartition(indexOid))
23763 : 0 : continue;
23764 : :
23765 : 172 : parentIdxOid = get_partition_parent(indexOid, true);
23766 [ - + ]: 172 : if (!OidIsValid(parentIdxOid))
23767 : 0 : continue;
23768 : :
23769 [ + - + - : 368 : foreach_ptr(PartitionIndexExtDepEntry, entry, extDepState)
+ + ]
23770 : : {
23771 : : ObjectAddress indexAddr;
23772 : :
23773 [ - + ]: 196 : if (entry->parentIndexOid > parentIdxOid)
23774 : 172 : break;
23775 [ + + ]: 196 : if (entry->parentIndexOid < parentIdxOid)
23776 : 24 : continue;
23777 : :
23778 : 172 : ObjectAddressSet(indexAddr, RelationRelationId, indexOid);
23779 : :
23780 [ + + + + : 352 : foreach_oid(extOid, entry->extensionOids)
+ + ]
23781 : : {
23782 : : ObjectAddress extAddr;
23783 : :
23784 : 8 : ObjectAddressSet(extAddr, ExtensionRelationId, extOid);
23785 : 8 : recordDependencyOn(&indexAddr, &extAddr,
23786 : : DEPENDENCY_AUTO_EXTENSION);
23787 : : }
23788 : 172 : break;
23789 : : }
23790 : : }
23791 : :
23792 : 148 : list_free(indexList);
23793 : 148 : table_close(partRel, NoLock);
23794 : : }
23795 : :
23796 : : /*
23797 : : * freePartitionIndexExtDeps: free memory allocated by collectPartitionIndexExtDeps.
23798 : : */
23799 : : static void
23800 : 247 : freePartitionIndexExtDeps(List *extDepState)
23801 : : {
23802 [ + + + + : 588 : foreach_ptr(PartitionIndexExtDepEntry, entry, extDepState)
+ + ]
23803 : : {
23804 : 94 : list_free(entry->extensionOids);
23805 : 94 : pfree(entry);
23806 : : }
23807 : 247 : list_free(extDepState);
23808 : 247 : }
23809 : :
23810 : : /*
23811 : : * ALTER TABLE <name> MERGE PARTITIONS <partition-list> INTO <partition-name>
23812 : : */
23813 : : static void
23814 : 144 : ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
23815 : : PartitionCmd *cmd, AlterTableUtilityContext *context)
23816 : : {
23817 : : Relation newPartRel;
23818 : 144 : List *mergingPartitions = NIL;
23819 : 144 : List *extDepState = NIL;
23820 : : Oid defaultPartOid;
23821 : : Oid existingRelid;
23822 : 144 : Oid ownerId = InvalidOid;
23823 : : Oid save_userid;
23824 : : int save_sec_context;
23825 : : int save_nestlevel;
23826 : :
23827 : : /*
23828 : : * Check ownership of merged partitions - partitions with different owners
23829 : : * cannot be merged. Also, collect the OIDs of these partitions during the
23830 : : * check.
23831 : : */
23832 [ + - + + : 600 : foreach_node(RangeVar, name, cmd->partlist)
+ + ]
23833 : : {
23834 : : Relation mergingPartition;
23835 : :
23836 : : /*
23837 : : * We are going to detach and remove this partition. We already took
23838 : : * AccessExclusiveLock lock on transformPartitionCmdForMerge, so here,
23839 : : * NoLock is fine.
23840 : : */
23841 : 320 : mergingPartition = table_openrv_extended(name, NoLock, false);
23842 : : Assert(CheckRelationLockedByMe(mergingPartition, AccessExclusiveLock, false));
23843 : :
23844 [ + + ]: 320 : if (OidIsValid(ownerId))
23845 : : {
23846 : : /* Do the partitions being merged have different owners? */
23847 [ + + ]: 176 : if (ownerId != mergingPartition->rd_rel->relowner)
23848 [ + - ]: 4 : ereport(ERROR,
23849 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
23850 : : errmsg("partitions being merged have different owners"));
23851 : : }
23852 : : else
23853 : 144 : ownerId = mergingPartition->rd_rel->relowner;
23854 : :
23855 : : /* Store the next merging partition into the list. */
23856 : 316 : mergingPartitions = lappend_oid(mergingPartitions,
23857 : : RelationGetRelid(mergingPartition));
23858 : :
23859 : 316 : table_close(mergingPartition, NoLock);
23860 : : }
23861 : :
23862 : : /* Look up the existing relation by the new partition name. */
23863 : 140 : RangeVarGetAndCheckCreationNamespace(cmd->name, NoLock, &existingRelid);
23864 : :
23865 : : /*
23866 : : * Check if this name is already taken. This helps us to detect the
23867 : : * situation when one of the merging partitions has the same name as the
23868 : : * new partition. Otherwise, this would fail later on anyway, but
23869 : : * catching this here allows us to emit a nicer error message.
23870 : : */
23871 [ + + ]: 140 : if (OidIsValid(existingRelid))
23872 : : {
23873 [ + + ]: 17 : if (list_member_oid(mergingPartitions, existingRelid))
23874 : : {
23875 : : /*
23876 : : * The new partition has the same name as one of the merging
23877 : : * partitions.
23878 : : */
23879 : : char tmpRelName[NAMEDATALEN];
23880 : :
23881 : : /* Generate a temporary name. */
23882 : 13 : sprintf(tmpRelName, "merge-%u-%X-tmp", RelationGetRelid(rel), MyProcPid);
23883 : :
23884 : : /*
23885 : : * Rename the existing partition with a temporary name, leaving it
23886 : : * free for the new partition. We don't need to care about this
23887 : : * in the future because we're going to eventually drop the
23888 : : * existing partition anyway.
23889 : : */
23890 : 13 : RenameRelationInternal(existingRelid, tmpRelName, true, false);
23891 : :
23892 : : /*
23893 : : * We must bump the command counter to make the new partition
23894 : : * tuple visible for rename.
23895 : : */
23896 : 13 : CommandCounterIncrement();
23897 : : }
23898 : : else
23899 : : {
23900 [ + - ]: 4 : ereport(ERROR,
23901 : : errcode(ERRCODE_DUPLICATE_TABLE),
23902 : : errmsg("relation \"%s\" already exists", cmd->name->relname));
23903 : : }
23904 : : }
23905 : :
23906 : : defaultPartOid =
23907 : 136 : get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
23908 : :
23909 : : /*
23910 : : * Collect extension dependencies from indexes on the merging partitions.
23911 : : * We must do this before detaching them, so we can restore the
23912 : : * dependencies on the new partition's indexes later.
23913 : : */
23914 : 136 : extDepState = collectPartitionIndexExtDeps(mergingPartitions);
23915 : :
23916 : : /* Detach all merging partitions. */
23917 [ + - + + : 548 : foreach_oid(mergingPartitionOid, mergingPartitions)
+ + ]
23918 : : {
23919 : : Relation child_rel;
23920 : :
23921 : 288 : child_rel = table_open(mergingPartitionOid, NoLock);
23922 : :
23923 : 288 : detachPartitionTable(rel, child_rel, defaultPartOid);
23924 : :
23925 : 288 : table_close(child_rel, NoLock);
23926 : : }
23927 : :
23928 : : /*
23929 : : * Perform a preliminary check to determine whether it's safe to drop all
23930 : : * merging partitions before we actually do so later. After merging rows
23931 : : * into the new partitions via MergePartitionsMoveRows, all old partitions
23932 : : * need to be dropped. However, since the drop behavior is DROP_RESTRICT
23933 : : * and the merge process (MergePartitionsMoveRows) can be time-consuming,
23934 : : * performing an early check on the drop eligibility of old partitions is
23935 : : * preferable.
23936 : : */
23937 [ + - + + : 536 : foreach_oid(mergingPartitionOid, mergingPartitions)
+ + ]
23938 : : {
23939 : : ObjectAddress object;
23940 : :
23941 : : /* Get oid of the later to be dropped relation. */
23942 : 284 : object.objectId = mergingPartitionOid;
23943 : 284 : object.classId = RelationRelationId;
23944 : 284 : object.objectSubId = 0;
23945 : :
23946 : 284 : performDeletionCheck(&object, DROP_RESTRICT, 0);
23947 : : }
23948 : :
23949 : : /*
23950 : : * Create a table for the new partition, using the partitioned table as a
23951 : : * model.
23952 : : */
23953 : : Assert(OidIsValid(ownerId));
23954 : 126 : newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
23955 : :
23956 : : /*
23957 : : * Switch to the table owner's userid, so that any index functions are run
23958 : : * as that user. Also, lockdown security-restricted operations and
23959 : : * arrange to make GUC variable changes local to this command.
23960 : : *
23961 : : * Need to do it after determining the namespace in the
23962 : : * createPartitionTable() call.
23963 : : */
23964 : 106 : GetUserIdAndSecContext(&save_userid, &save_sec_context);
23965 : 106 : SetUserIdAndSecContext(ownerId,
23966 : : save_sec_context | SECURITY_RESTRICTED_OPERATION);
23967 : 106 : save_nestlevel = NewGUCNestLevel();
23968 : 106 : RestrictSearchPath();
23969 : :
23970 : : /* Copy data from merged partitions to the new partition. */
23971 : 106 : MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
23972 : :
23973 : : /* Drop the current partitions before attaching the new one. */
23974 [ + - + + : 452 : foreach_oid(mergingPartitionOid, mergingPartitions)
+ + ]
23975 : : {
23976 : : ObjectAddress object;
23977 : :
23978 : 240 : object.objectId = mergingPartitionOid;
23979 : 240 : object.classId = RelationRelationId;
23980 : 240 : object.objectSubId = 0;
23981 : :
23982 : 240 : performDeletion(&object, DROP_RESTRICT, 0);
23983 : : }
23984 : :
23985 : 106 : list_free(mergingPartitions);
23986 : :
23987 : : /*
23988 : : * Attach a new partition to the partitioned table. wqueue = NULL:
23989 : : * verification for each cloned constraint is not needed.
23990 : : */
23991 : 106 : attachPartitionTable(NULL, rel, newPartRel, cmd->bound);
23992 : :
23993 : : /*
23994 : : * Apply extension dependencies to the new partition's indexes. This
23995 : : * preserves any "DEPENDS ON EXTENSION" settings from the merged
23996 : : * partitions.
23997 : : */
23998 : 106 : applyPartitionIndexExtDeps(RelationGetRelid(newPartRel), extDepState);
23999 : :
24000 : 106 : freePartitionIndexExtDeps(extDepState);
24001 : :
24002 : : /* Keep the lock until commit. */
24003 : 106 : table_close(newPartRel, NoLock);
24004 : :
24005 : : /* Roll back any GUC changes executed by index functions. */
24006 : 106 : AtEOXact_GUC(false, save_nestlevel);
24007 : :
24008 : : /* Restore the userid and security context. */
24009 : 106 : SetUserIdAndSecContext(save_userid, save_sec_context);
24010 : 106 : }
24011 : :
24012 : : /*
24013 : : * Struct with the context of the new partition for inserting rows from the
24014 : : * split partition.
24015 : : */
24016 : : typedef struct SplitPartitionContext
24017 : : {
24018 : : ExprState *partqualstate; /* expression for checking a slot for a
24019 : : * partition (NULL for DEFAULT partition) */
24020 : : BulkInsertState bistate; /* state of bulk inserts for partition */
24021 : : TupleTableSlot *dstslot; /* slot for inserting row into partition */
24022 : : AlteredTableInfo *tab; /* structure with generated column expressions
24023 : : * and check constraint expressions. */
24024 : : Relation partRel; /* relation for partition */
24025 : : } SplitPartitionContext;
24026 : :
24027 : : /*
24028 : : * createSplitPartitionContext: create context for partition and fill it
24029 : : */
24030 : : static SplitPartitionContext *
24031 : 374 : createSplitPartitionContext(Relation partRel)
24032 : : {
24033 : : SplitPartitionContext *pc;
24034 : :
24035 : 374 : pc = palloc0_object(SplitPartitionContext);
24036 : 374 : pc->partRel = partRel;
24037 : :
24038 : : /*
24039 : : * Prepare a BulkInsertState for table_tuple_insert. The FSM is empty, so
24040 : : * don't bother using it.
24041 : : */
24042 : 374 : pc->bistate = GetBulkInsertState();
24043 : :
24044 : : /* Create a destination tuple slot for the new partition. */
24045 : 374 : pc->dstslot = table_slot_create(pc->partRel, NULL);
24046 : :
24047 : 374 : return pc;
24048 : : }
24049 : :
24050 : : /*
24051 : : * deleteSplitPartitionContext: delete context for partition
24052 : : */
24053 : : static void
24054 : 374 : deleteSplitPartitionContext(SplitPartitionContext *pc, List **wqueue, uint32 ti_options)
24055 : : {
24056 : : ListCell *ltab;
24057 : :
24058 : 374 : ExecDropSingleTupleTableSlot(pc->dstslot);
24059 : 374 : FreeBulkInsertState(pc->bistate);
24060 : :
24061 : 374 : table_finish_bulk_insert(pc->partRel, ti_options);
24062 : :
24063 : : /*
24064 : : * We don't need to process this pc->partRel so delete the ALTER TABLE
24065 : : * queue of it.
24066 : : */
24067 [ + - + - : 748 : foreach(ltab, *wqueue)
+ - ]
24068 : : {
24069 : 748 : AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
24070 : :
24071 [ + + ]: 748 : if (tab->relid == RelationGetRelid(pc->partRel))
24072 : : {
24073 : 374 : *wqueue = list_delete_cell(*wqueue, ltab);
24074 : 374 : break;
24075 : : }
24076 : : }
24077 : :
24078 : 374 : pfree(pc);
24079 : 374 : }
24080 : :
24081 : : /*
24082 : : * SplitPartitionMoveRows: scan split partition (splitRel) of partitioned table
24083 : : * (rel) and move rows into new partitions.
24084 : : *
24085 : : * New partitions description:
24086 : : * partlist: list of pointers to SinglePartitionSpec structures. It contains
24087 : : * the partition specification details for all new partitions.
24088 : : * newPartRels: list of Relations, new partitions created in
24089 : : * ATExecSplitPartition.
24090 : : */
24091 : : static void
24092 : 141 : SplitPartitionMoveRows(List **wqueue, Relation rel, Relation splitRel,
24093 : : List *partlist, List *newPartRels)
24094 : : {
24095 : : /* The FSM is empty, so don't bother using it. */
24096 : 141 : uint32 ti_options = TABLE_INSERT_SKIP_FSM;
24097 : : CommandId mycid;
24098 : : EState *estate;
24099 : : ListCell *listptr,
24100 : : *listptr2;
24101 : : TupleTableSlot *srcslot;
24102 : : ExprContext *econtext;
24103 : : TableScanDesc scan;
24104 : : Snapshot snapshot;
24105 : : MemoryContext oldCxt;
24106 : 141 : List *partContexts = NIL;
24107 : : TupleConversionMap *tuple_map;
24108 : 141 : SplitPartitionContext *defaultPartCtx = NULL,
24109 : : *pc;
24110 : :
24111 : 141 : mycid = GetCurrentCommandId(true);
24112 : :
24113 : 141 : estate = CreateExecutorState();
24114 : :
24115 [ + - + + : 515 : forboth(listptr, partlist, listptr2, newPartRels)
+ - + + +
+ + - +
+ ]
24116 : : {
24117 : 374 : SinglePartitionSpec *sps = (SinglePartitionSpec *) lfirst(listptr);
24118 : :
24119 : 374 : pc = createSplitPartitionContext((Relation) lfirst(listptr2));
24120 : :
24121 : : /* Find the work queue entry for the new partition table: newPartRel. */
24122 : 374 : pc->tab = ATGetQueueEntry(wqueue, pc->partRel);
24123 : :
24124 : 374 : buildExpressionExecutionStates(pc->tab, pc->partRel, estate);
24125 : :
24126 [ + + ]: 374 : if (sps->bound->is_default)
24127 : : {
24128 : : /*
24129 : : * We should not create a structure to check the partition
24130 : : * constraint for the new DEFAULT partition.
24131 : : */
24132 : 32 : defaultPartCtx = pc;
24133 : : }
24134 : : else
24135 : : {
24136 : : List *partConstraint;
24137 : :
24138 : : /* Build expression execution states for partition check quals. */
24139 : 342 : partConstraint = get_qual_from_partbound(rel, sps->bound);
24140 : : partConstraint =
24141 : 342 : (List *) eval_const_expressions(NULL,
24142 : : (Node *) partConstraint);
24143 : : /* Make a boolean expression for ExecCheck(). */
24144 : 342 : partConstraint = list_make1(make_ands_explicit(partConstraint));
24145 : :
24146 : : /*
24147 : : * Map the vars in the constraint expression from rel's attnos to
24148 : : * splitRel's.
24149 : : */
24150 : 342 : partConstraint = map_partition_varattnos(partConstraint,
24151 : : 1, splitRel, rel);
24152 : :
24153 : 342 : pc->partqualstate =
24154 : 342 : ExecPrepareExpr((Expr *) linitial(partConstraint), estate);
24155 : : Assert(pc->partqualstate != NULL);
24156 : : }
24157 : :
24158 : : /* Store partition context into a list. */
24159 : 374 : partContexts = lappend(partContexts, pc);
24160 : : }
24161 : :
24162 [ - + ]: 141 : econtext = GetPerTupleExprContext(estate);
24163 : :
24164 : : /* Create the necessary tuple slot. */
24165 : 141 : srcslot = table_slot_create(splitRel, NULL);
24166 : :
24167 : : /*
24168 : : * Map computing for moving attributes of the split partition to the new
24169 : : * partition (for the first new partition, but other new partitions can
24170 : : * use the same map).
24171 : : */
24172 : 141 : pc = (SplitPartitionContext *) lfirst(list_head(partContexts));
24173 : 141 : tuple_map = convert_tuples_by_name(RelationGetDescr(splitRel),
24174 : 141 : RelationGetDescr(pc->partRel));
24175 : :
24176 : : /* Scan through the rows. */
24177 : 141 : snapshot = RegisterSnapshot(GetLatestSnapshot());
24178 : 141 : scan = table_beginscan(splitRel, snapshot, 0, NULL,
24179 : : SO_NONE);
24180 : :
24181 : : /*
24182 : : * Switch to per-tuple memory context and reset it for each tuple
24183 : : * produced, so we don't leak memory.
24184 : : */
24185 [ + - ]: 141 : oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
24186 : :
24187 [ + + ]: 688 : while (table_scan_getnextslot(scan, ForwardScanDirection, srcslot))
24188 : : {
24189 : 547 : bool found = false;
24190 : : TupleTableSlot *insertslot;
24191 : :
24192 [ - + ]: 547 : CHECK_FOR_INTERRUPTS();
24193 : :
24194 : 547 : econtext->ecxt_scantuple = srcslot;
24195 : :
24196 : : /* Search partition for the current slot, srcslot. */
24197 [ + - + + : 1322 : foreach(listptr, partContexts)
+ + ]
24198 : : {
24199 : 1246 : pc = (SplitPartitionContext *) lfirst(listptr);
24200 : :
24201 : : /* skip DEFAULT partition */
24202 [ + + + + ]: 1246 : if (pc->partqualstate && ExecCheck(pc->partqualstate, econtext))
24203 : : {
24204 : 471 : found = true;
24205 : 471 : break;
24206 : : }
24207 : : }
24208 [ + + ]: 547 : if (!found)
24209 : : {
24210 : : /* Use the DEFAULT partition if it exists. */
24211 [ + - ]: 76 : if (defaultPartCtx)
24212 : 76 : pc = defaultPartCtx;
24213 : : else
24214 [ # # ]: 0 : ereport(ERROR,
24215 : : errcode(ERRCODE_CHECK_VIOLATION),
24216 : : errmsg("cannot find partition for split partition row"),
24217 : : errtable(splitRel));
24218 : : }
24219 : :
24220 [ + + ]: 547 : if (tuple_map)
24221 : : {
24222 : : /* Need to use a map to copy attributes. */
24223 : 16 : insertslot = execute_attr_map_slot(tuple_map->attrMap, srcslot, pc->dstslot);
24224 : : }
24225 : : else
24226 : : {
24227 : : /* Extract data from the old tuple. */
24228 : 531 : slot_getallattrs(srcslot);
24229 : :
24230 : : /* Copy attributes directly. */
24231 : 531 : insertslot = pc->dstslot;
24232 : :
24233 : 531 : ExecClearTuple(insertslot);
24234 : :
24235 : 531 : memcpy(insertslot->tts_values, srcslot->tts_values,
24236 : 531 : sizeof(Datum) * srcslot->tts_nvalid);
24237 : 531 : memcpy(insertslot->tts_isnull, srcslot->tts_isnull,
24238 : 531 : sizeof(bool) * srcslot->tts_nvalid);
24239 : :
24240 : 531 : ExecStoreVirtualTuple(insertslot);
24241 : : }
24242 : :
24243 : : /*
24244 : : * Constraints and GENERATED expressions might reference the tableoid
24245 : : * column, so fill tts_tableOid with the desired value. (We must do
24246 : : * this each time, because it gets overwritten with newrel's OID
24247 : : * during storing.)
24248 : : */
24249 : 547 : insertslot->tts_tableOid = RelationGetRelid(pc->partRel);
24250 : :
24251 : : /*
24252 : : * Now, evaluate any generated expressions whose inputs come from the
24253 : : * new tuple. We assume these columns won't reference each other, so
24254 : : * that there's no ordering dependency.
24255 : : */
24256 : 547 : evaluateGeneratedExpressionsAndCheckConstraints(pc->tab, pc->partRel,
24257 : : insertslot, econtext);
24258 : :
24259 : : /* Write the tuple out to the new relation. */
24260 : 547 : table_tuple_insert(pc->partRel, insertslot, mycid,
24261 : 547 : ti_options, pc->bistate);
24262 : :
24263 : 547 : ResetExprContext(econtext);
24264 : : }
24265 : :
24266 : 141 : MemoryContextSwitchTo(oldCxt);
24267 : :
24268 : 141 : table_endscan(scan);
24269 : 141 : UnregisterSnapshot(snapshot);
24270 : :
24271 [ + + ]: 141 : if (tuple_map)
24272 : 4 : free_conversion_map(tuple_map);
24273 : :
24274 : 141 : ExecDropSingleTupleTableSlot(srcslot);
24275 : :
24276 : 141 : FreeExecutorState(estate);
24277 : :
24278 [ + - + + : 656 : foreach_ptr(SplitPartitionContext, spc, partContexts)
+ + ]
24279 : 374 : deleteSplitPartitionContext(spc, wqueue, ti_options);
24280 : 141 : }
24281 : :
24282 : : /*
24283 : : * ALTER TABLE <name> SPLIT PARTITION <partition-name> INTO <partition-list>
24284 : : */
24285 : : static void
24286 : 153 : ATExecSplitPartition(List **wqueue, AlteredTableInfo *tab, Relation rel,
24287 : : PartitionCmd *cmd, AlterTableUtilityContext *context)
24288 : : {
24289 : : Relation splitRel;
24290 : : Oid splitRelOid;
24291 : : ListCell *listptr,
24292 : : *listptr2;
24293 : 153 : bool isSameName = false;
24294 : : char tmpRelName[NAMEDATALEN];
24295 : 153 : List *newPartRels = NIL;
24296 : 153 : List *extDepState = NIL;
24297 : : ObjectAddress object;
24298 : : Oid defaultPartOid;
24299 : : Oid save_userid;
24300 : : int save_sec_context;
24301 : : int save_nestlevel;
24302 : : List *splitPartList;
24303 : :
24304 : 153 : defaultPartOid = get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
24305 : :
24306 : : /*
24307 : : * Partition is already locked in the transformPartitionCmdForSplit
24308 : : * function.
24309 : : */
24310 : 153 : splitRel = table_openrv(cmd->name, NoLock);
24311 : :
24312 : 153 : splitRelOid = RelationGetRelid(splitRel);
24313 : :
24314 : : /* Check descriptions of new partitions. */
24315 [ + - + + : 692 : foreach_node(SinglePartitionSpec, sps, cmd->partlist)
+ + ]
24316 : : {
24317 : : Oid existingRelid;
24318 : :
24319 : : /* Look up the existing relation by the new partition name. */
24320 : 394 : RangeVarGetAndCheckCreationNamespace(sps->name, NoLock, &existingRelid);
24321 : :
24322 : : /*
24323 : : * This would fail later on anyway if the relation already exists. But
24324 : : * by catching it here, we can emit a nicer error message.
24325 : : */
24326 [ + + + - ]: 394 : if (existingRelid == splitRelOid && !isSameName)
24327 : : /* One new partition can have the same name as a split partition. */
24328 : 29 : isSameName = true;
24329 [ + + ]: 365 : else if (OidIsValid(existingRelid))
24330 [ + - ]: 4 : ereport(ERROR,
24331 : : errcode(ERRCODE_DUPLICATE_TABLE),
24332 : : errmsg("relation \"%s\" already exists", sps->name->relname));
24333 : : }
24334 : :
24335 : : /*
24336 : : * Collect extension dependencies from indexes on the split partition. We
24337 : : * must do this before detaching it, so we can restore the dependencies on
24338 : : * the new partitions' indexes later.
24339 : : */
24340 : 149 : splitPartList = list_make1_oid(splitRelOid);
24341 : :
24342 : 149 : extDepState = collectPartitionIndexExtDeps(splitPartList);
24343 : 149 : list_free(splitPartList);
24344 : :
24345 : : /* Detach the split partition. */
24346 : 149 : detachPartitionTable(rel, splitRel, defaultPartOid);
24347 : :
24348 : : /*
24349 : : * Perform a preliminary check to determine whether it's safe to drop the
24350 : : * split partition before we actually do so later. After merging rows into
24351 : : * the new partitions via SplitPartitionMoveRows, all old partitions need
24352 : : * to be dropped. However, since the drop behavior is DROP_RESTRICT and
24353 : : * the merge process (SplitPartitionMoveRows) can be time-consuming,
24354 : : * performing an early check on the drop eligibility of old partitions is
24355 : : * preferable.
24356 : : */
24357 : 149 : object.objectId = splitRelOid;
24358 : 149 : object.classId = RelationRelationId;
24359 : 149 : object.objectSubId = 0;
24360 : 149 : performDeletionCheck(&object, DROP_RESTRICT, 0);
24361 : :
24362 : : /*
24363 : : * If a new partition has the same name as the split partition, then we
24364 : : * should rename the split partition to reuse its name.
24365 : : */
24366 [ + + ]: 149 : if (isSameName)
24367 : : {
24368 : : /*
24369 : : * We must bump the command counter to make the split partition tuple
24370 : : * visible for renaming.
24371 : : */
24372 : 29 : CommandCounterIncrement();
24373 : : /* Rename partition. */
24374 : 29 : sprintf(tmpRelName, "split-%u-%X-tmp", RelationGetRelid(rel), MyProcPid);
24375 : 29 : RenameRelationInternal(splitRelOid, tmpRelName, true, false);
24376 : :
24377 : : /*
24378 : : * We must bump the command counter to make the split partition tuple
24379 : : * visible after renaming.
24380 : : */
24381 : 29 : CommandCounterIncrement();
24382 : : }
24383 : :
24384 : : /* Create new partitions (like a split partition), without indexes. */
24385 [ + - + + : 664 : foreach_node(SinglePartitionSpec, sps, cmd->partlist)
+ + ]
24386 : : {
24387 : : Relation newPartRel;
24388 : :
24389 : 382 : newPartRel = createPartitionTable(wqueue, sps->name, rel,
24390 : 382 : splitRel->rd_rel->relowner);
24391 : 374 : newPartRels = lappend(newPartRels, newPartRel);
24392 : : }
24393 : :
24394 : : /*
24395 : : * Switch to the table owner's userid, so that any index functions are run
24396 : : * as that user. Also, lockdown security-restricted operations and
24397 : : * arrange to make GUC variable changes local to this command.
24398 : : *
24399 : : * Need to do it after determining the namespace in the
24400 : : * createPartitionTable() call.
24401 : : */
24402 : 141 : GetUserIdAndSecContext(&save_userid, &save_sec_context);
24403 : 141 : SetUserIdAndSecContext(splitRel->rd_rel->relowner,
24404 : : save_sec_context | SECURITY_RESTRICTED_OPERATION);
24405 : 141 : save_nestlevel = NewGUCNestLevel();
24406 : 141 : RestrictSearchPath();
24407 : :
24408 : : /* Copy data from the split partition to the new partitions. */
24409 : 141 : SplitPartitionMoveRows(wqueue, rel, splitRel, cmd->partlist, newPartRels);
24410 : : /* Keep the lock until commit. */
24411 : 141 : table_close(splitRel, NoLock);
24412 : :
24413 : : /* Attach new partitions to the partitioned table. */
24414 [ + - + + : 515 : forboth(listptr, cmd->partlist, listptr2, newPartRels)
+ - + + +
+ + - +
+ ]
24415 : : {
24416 : 374 : SinglePartitionSpec *sps = (SinglePartitionSpec *) lfirst(listptr);
24417 : 374 : Relation newPartRel = (Relation) lfirst(listptr2);
24418 : :
24419 : : /*
24420 : : * wqueue = NULL: verification for each cloned constraint is not
24421 : : * needed.
24422 : : */
24423 : 374 : attachPartitionTable(NULL, rel, newPartRel, sps->bound);
24424 : :
24425 : : /*
24426 : : * Apply extension dependencies to the new partition's indexes. This
24427 : : * preserves any "DEPENDS ON EXTENSION" settings from the split
24428 : : * partition.
24429 : : */
24430 : 374 : applyPartitionIndexExtDeps(RelationGetRelid(newPartRel), extDepState);
24431 : :
24432 : : /* Keep the lock until commit. */
24433 : 374 : table_close(newPartRel, NoLock);
24434 : : }
24435 : :
24436 : 141 : freePartitionIndexExtDeps(extDepState);
24437 : :
24438 : : /* Drop the split partition. */
24439 : 141 : object.classId = RelationRelationId;
24440 : 141 : object.objectId = splitRelOid;
24441 : 141 : object.objectSubId = 0;
24442 : : /* Probably DROP_CASCADE is not needed. */
24443 : 141 : performDeletion(&object, DROP_RESTRICT, 0);
24444 : :
24445 : : /* Roll back any GUC changes executed by index functions. */
24446 : 141 : AtEOXact_GUC(false, save_nestlevel);
24447 : :
24448 : : /* Restore the userid and security context. */
24449 : 141 : SetUserIdAndSecContext(save_userid, save_sec_context);
24450 : 141 : }
|