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/xact.h"
29 : : #include "access/xlog.h"
30 : : #include "access/xloginsert.h"
31 : : #include "catalog/catalog.h"
32 : : #include "catalog/heap.h"
33 : : #include "catalog/index.h"
34 : : #include "catalog/namespace.h"
35 : : #include "catalog/objectaccess.h"
36 : : #include "catalog/partition.h"
37 : : #include "catalog/pg_am.h"
38 : : #include "catalog/pg_attrdef.h"
39 : : #include "catalog/pg_collation.h"
40 : : #include "catalog/pg_constraint.h"
41 : : #include "catalog/pg_depend.h"
42 : : #include "catalog/pg_foreign_table.h"
43 : : #include "catalog/pg_inherits.h"
44 : : #include "catalog/pg_largeobject.h"
45 : : #include "catalog/pg_largeobject_metadata.h"
46 : : #include "catalog/pg_namespace.h"
47 : : #include "catalog/pg_opclass.h"
48 : : #include "catalog/pg_policy.h"
49 : : #include "catalog/pg_proc.h"
50 : : #include "catalog/pg_publication_rel.h"
51 : : #include "catalog/pg_rewrite.h"
52 : : #include "catalog/pg_statistic_ext.h"
53 : : #include "catalog/pg_tablespace.h"
54 : : #include "catalog/pg_trigger.h"
55 : : #include "catalog/pg_type.h"
56 : : #include "catalog/storage.h"
57 : : #include "catalog/storage_xlog.h"
58 : : #include "catalog/toasting.h"
59 : : #include "commands/cluster.h"
60 : : #include "commands/comment.h"
61 : : #include "commands/defrem.h"
62 : : #include "commands/event_trigger.h"
63 : : #include "commands/sequence.h"
64 : : #include "commands/tablecmds.h"
65 : : #include "commands/tablespace.h"
66 : : #include "commands/trigger.h"
67 : : #include "commands/typecmds.h"
68 : : #include "commands/user.h"
69 : : #include "commands/vacuum.h"
70 : : #include "common/int.h"
71 : : #include "executor/executor.h"
72 : : #include "foreign/fdwapi.h"
73 : : #include "foreign/foreign.h"
74 : : #include "miscadmin.h"
75 : : #include "nodes/makefuncs.h"
76 : : #include "nodes/nodeFuncs.h"
77 : : #include "nodes/parsenodes.h"
78 : : #include "optimizer/optimizer.h"
79 : : #include "parser/parse_coerce.h"
80 : : #include "parser/parse_collate.h"
81 : : #include "parser/parse_expr.h"
82 : : #include "parser/parse_relation.h"
83 : : #include "parser/parse_type.h"
84 : : #include "parser/parse_utilcmd.h"
85 : : #include "parser/parser.h"
86 : : #include "partitioning/partbounds.h"
87 : : #include "partitioning/partdesc.h"
88 : : #include "pgstat.h"
89 : : #include "rewrite/rewriteDefine.h"
90 : : #include "rewrite/rewriteHandler.h"
91 : : #include "rewrite/rewriteManip.h"
92 : : #include "storage/bufmgr.h"
93 : : #include "storage/lmgr.h"
94 : : #include "storage/lock.h"
95 : : #include "storage/predicate.h"
96 : : #include "storage/smgr.h"
97 : : #include "tcop/utility.h"
98 : : #include "utils/acl.h"
99 : : #include "utils/builtins.h"
100 : : #include "utils/fmgroids.h"
101 : : #include "utils/inval.h"
102 : : #include "utils/lsyscache.h"
103 : : #include "utils/memutils.h"
104 : : #include "utils/partcache.h"
105 : : #include "utils/relcache.h"
106 : : #include "utils/ruleutils.h"
107 : : #include "utils/snapmgr.h"
108 : : #include "utils/syscache.h"
109 : : #include "utils/timestamp.h"
110 : : #include "utils/typcache.h"
111 : : #include "utils/usercontext.h"
112 : :
113 : : /*
114 : : * ON COMMIT action list
115 : : */
116 : : typedef struct OnCommitItem
117 : : {
118 : : Oid relid; /* relid of relation */
119 : : OnCommitAction oncommit; /* what to do at end of xact */
120 : :
121 : : /*
122 : : * If this entry was created during the current transaction,
123 : : * creating_subid is the ID of the creating subxact; if created in a prior
124 : : * transaction, creating_subid is zero. If deleted during the current
125 : : * transaction, deleting_subid is the ID of the deleting subxact; if no
126 : : * deletion request is pending, deleting_subid is zero.
127 : : */
128 : : SubTransactionId creating_subid;
129 : : SubTransactionId deleting_subid;
130 : : } OnCommitItem;
131 : :
132 : : static List *on_commits = NIL;
133 : :
134 : :
135 : : /*
136 : : * State information for ALTER TABLE
137 : : *
138 : : * The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo
139 : : * structs, one for each table modified by the operation (the named table
140 : : * plus any child tables that are affected). We save lists of subcommands
141 : : * to apply to this table (possibly modified by parse transformation steps);
142 : : * these lists will be executed in Phase 2. If a Phase 3 step is needed,
143 : : * necessary information is stored in the constraints and newvals lists.
144 : : *
145 : : * Phase 2 is divided into multiple passes; subcommands are executed in
146 : : * a pass determined by subcommand type.
147 : : */
148 : :
149 : : typedef enum AlterTablePass
150 : : {
151 : : AT_PASS_UNSET = -1, /* UNSET will cause ERROR */
152 : : AT_PASS_DROP, /* DROP (all flavors) */
153 : : AT_PASS_ALTER_TYPE, /* ALTER COLUMN TYPE */
154 : : AT_PASS_ADD_COL, /* ADD COLUMN */
155 : : AT_PASS_SET_EXPRESSION, /* ALTER SET EXPRESSION */
156 : : AT_PASS_OLD_INDEX, /* re-add existing indexes */
157 : : AT_PASS_OLD_CONSTR, /* re-add existing constraints */
158 : : /* We could support a RENAME COLUMN pass here, but not currently used */
159 : : AT_PASS_ADD_CONSTR, /* ADD constraints (initial examination) */
160 : : AT_PASS_COL_ATTRS, /* set column attributes, eg NOT NULL */
161 : : AT_PASS_ADD_INDEXCONSTR, /* ADD index-based constraints */
162 : : AT_PASS_ADD_INDEX, /* ADD indexes */
163 : : AT_PASS_ADD_OTHERCONSTR, /* ADD other constraints, defaults */
164 : : AT_PASS_MISC, /* other stuff */
165 : : } AlterTablePass;
166 : :
167 : : #define AT_NUM_PASSES (AT_PASS_MISC + 1)
168 : :
169 : : typedef struct AlteredTableInfo
170 : : {
171 : : /* Information saved before any work commences: */
172 : : Oid relid; /* Relation to work on */
173 : : char relkind; /* Its relkind */
174 : : TupleDesc oldDesc; /* Pre-modification tuple descriptor */
175 : :
176 : : /*
177 : : * Transiently set during Phase 2, normally set to NULL.
178 : : *
179 : : * ATRewriteCatalogs sets this when it starts, and closes when ATExecCmd
180 : : * returns control. This can be exploited by ATExecCmd subroutines to
181 : : * close/reopen across transaction boundaries.
182 : : */
183 : : Relation rel;
184 : :
185 : : /* Information saved by Phase 1 for Phase 2: */
186 : : List *subcmds[AT_NUM_PASSES]; /* Lists of AlterTableCmd */
187 : : /* Information saved by Phases 1/2 for Phase 3: */
188 : : List *constraints; /* List of NewConstraint */
189 : : List *newvals; /* List of NewColumnValue */
190 : : List *afterStmts; /* List of utility command parsetrees */
191 : : bool verify_new_notnull; /* T if we should recheck NOT NULL */
192 : : int rewrite; /* Reason for forced rewrite, if any */
193 : : bool chgAccessMethod; /* T if SET ACCESS METHOD is used */
194 : : Oid newAccessMethod; /* new access method; 0 means no change,
195 : : * if above is true */
196 : : Oid newTableSpace; /* new tablespace; 0 means no change */
197 : : bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
198 : : char newrelpersistence; /* if above is true */
199 : : Expr *partition_constraint; /* for attach partition validation */
200 : : /* true, if validating default due to some other attach/detach */
201 : : bool validate_default;
202 : : /* Objects to rebuild after completing ALTER TYPE operations */
203 : : List *changedConstraintOids; /* OIDs of constraints to rebuild */
204 : : List *changedConstraintDefs; /* string definitions of same */
205 : : List *changedIndexOids; /* OIDs of indexes to rebuild */
206 : : List *changedIndexDefs; /* string definitions of same */
207 : : char *replicaIdentityIndex; /* index to reset as REPLICA IDENTITY */
208 : : char *clusterOnIndex; /* index to use for CLUSTER */
209 : : List *changedStatisticsOids; /* OIDs of statistics to rebuild */
210 : : List *changedStatisticsDefs; /* string definitions of same */
211 : : } AlteredTableInfo;
212 : :
213 : : /* Struct describing one new constraint to check in Phase 3 scan */
214 : : /* Note: new not-null constraints are handled elsewhere */
215 : : typedef struct NewConstraint
216 : : {
217 : : char *name; /* Constraint name, or NULL if none */
218 : : ConstrType contype; /* CHECK or FOREIGN */
219 : : Oid refrelid; /* PK rel, if FOREIGN */
220 : : Oid refindid; /* OID of PK's index, if FOREIGN */
221 : : bool conwithperiod; /* Whether the new FOREIGN KEY uses PERIOD */
222 : : Oid conid; /* OID of pg_constraint entry, if FOREIGN */
223 : : Node *qual; /* Check expr or CONSTR_FOREIGN Constraint */
224 : : ExprState *qualstate; /* Execution state for CHECK expr */
225 : : } NewConstraint;
226 : :
227 : : /*
228 : : * Struct describing one new column value that needs to be computed during
229 : : * Phase 3 copy (this could be either a new column with a non-null default, or
230 : : * a column that we're changing the type of). Columns without such an entry
231 : : * are just copied from the old table during ATRewriteTable. Note that the
232 : : * expr is an expression over *old* table values, except when is_generated
233 : : * is true; then it is an expression over columns of the *new* tuple.
234 : : */
235 : : typedef struct NewColumnValue
236 : : {
237 : : AttrNumber attnum; /* which column */
238 : : Expr *expr; /* expression to compute */
239 : : ExprState *exprstate; /* execution state */
240 : : bool is_generated; /* is it a GENERATED expression? */
241 : : } NewColumnValue;
242 : :
243 : : /*
244 : : * Error-reporting support for RemoveRelations
245 : : */
246 : : struct dropmsgstrings
247 : : {
248 : : char kind;
249 : : int nonexistent_code;
250 : : const char *nonexistent_msg;
251 : : const char *skipping_msg;
252 : : const char *nota_msg;
253 : : const char *drophint_msg;
254 : : };
255 : :
256 : : static const struct dropmsgstrings dropmsgstringarray[] = {
257 : : {RELKIND_RELATION,
258 : : ERRCODE_UNDEFINED_TABLE,
259 : : gettext_noop("table \"%s\" does not exist"),
260 : : gettext_noop("table \"%s\" does not exist, skipping"),
261 : : gettext_noop("\"%s\" is not a table"),
262 : : gettext_noop("Use DROP TABLE to remove a table.")},
263 : : {RELKIND_SEQUENCE,
264 : : ERRCODE_UNDEFINED_TABLE,
265 : : gettext_noop("sequence \"%s\" does not exist"),
266 : : gettext_noop("sequence \"%s\" does not exist, skipping"),
267 : : gettext_noop("\"%s\" is not a sequence"),
268 : : gettext_noop("Use DROP SEQUENCE to remove a sequence.")},
269 : : {RELKIND_VIEW,
270 : : ERRCODE_UNDEFINED_TABLE,
271 : : gettext_noop("view \"%s\" does not exist"),
272 : : gettext_noop("view \"%s\" does not exist, skipping"),
273 : : gettext_noop("\"%s\" is not a view"),
274 : : gettext_noop("Use DROP VIEW to remove a view.")},
275 : : {RELKIND_MATVIEW,
276 : : ERRCODE_UNDEFINED_TABLE,
277 : : gettext_noop("materialized view \"%s\" does not exist"),
278 : : gettext_noop("materialized view \"%s\" does not exist, skipping"),
279 : : gettext_noop("\"%s\" is not a materialized view"),
280 : : gettext_noop("Use DROP MATERIALIZED VIEW to remove a materialized view.")},
281 : : {RELKIND_INDEX,
282 : : ERRCODE_UNDEFINED_OBJECT,
283 : : gettext_noop("index \"%s\" does not exist"),
284 : : gettext_noop("index \"%s\" does not exist, skipping"),
285 : : gettext_noop("\"%s\" is not an index"),
286 : : gettext_noop("Use DROP INDEX to remove an index.")},
287 : : {RELKIND_COMPOSITE_TYPE,
288 : : ERRCODE_UNDEFINED_OBJECT,
289 : : gettext_noop("type \"%s\" does not exist"),
290 : : gettext_noop("type \"%s\" does not exist, skipping"),
291 : : gettext_noop("\"%s\" is not a type"),
292 : : gettext_noop("Use DROP TYPE to remove a type.")},
293 : : {RELKIND_FOREIGN_TABLE,
294 : : ERRCODE_UNDEFINED_OBJECT,
295 : : gettext_noop("foreign table \"%s\" does not exist"),
296 : : gettext_noop("foreign table \"%s\" does not exist, skipping"),
297 : : gettext_noop("\"%s\" is not a foreign table"),
298 : : gettext_noop("Use DROP FOREIGN TABLE to remove a foreign table.")},
299 : : {RELKIND_PARTITIONED_TABLE,
300 : : ERRCODE_UNDEFINED_TABLE,
301 : : gettext_noop("table \"%s\" does not exist"),
302 : : gettext_noop("table \"%s\" does not exist, skipping"),
303 : : gettext_noop("\"%s\" is not a table"),
304 : : gettext_noop("Use DROP TABLE to remove a table.")},
305 : : {RELKIND_PARTITIONED_INDEX,
306 : : ERRCODE_UNDEFINED_OBJECT,
307 : : gettext_noop("index \"%s\" does not exist"),
308 : : gettext_noop("index \"%s\" does not exist, skipping"),
309 : : gettext_noop("\"%s\" is not an index"),
310 : : gettext_noop("Use DROP INDEX to remove an index.")},
311 : : {'\0', 0, NULL, NULL, NULL, NULL}
312 : : };
313 : :
314 : : /* communication between RemoveRelations and RangeVarCallbackForDropRelation */
315 : : struct DropRelationCallbackState
316 : : {
317 : : /* These fields are set by RemoveRelations: */
318 : : char expected_relkind;
319 : : LOCKMODE heap_lockmode;
320 : : /* These fields are state to track which subsidiary locks are held: */
321 : : Oid heapOid;
322 : : Oid partParentOid;
323 : : /* These fields are passed back by RangeVarCallbackForDropRelation: */
324 : : char actual_relkind;
325 : : char actual_relpersistence;
326 : : };
327 : :
328 : : /* Alter table target-type flags for ATSimplePermissions */
329 : : #define ATT_TABLE 0x0001
330 : : #define ATT_VIEW 0x0002
331 : : #define ATT_MATVIEW 0x0004
332 : : #define ATT_INDEX 0x0008
333 : : #define ATT_COMPOSITE_TYPE 0x0010
334 : : #define ATT_FOREIGN_TABLE 0x0020
335 : : #define ATT_PARTITIONED_INDEX 0x0040
336 : : #define ATT_SEQUENCE 0x0080
337 : : #define ATT_PARTITIONED_TABLE 0x0100
338 : :
339 : : /*
340 : : * ForeignTruncateInfo
341 : : *
342 : : * Information related to truncation of foreign tables. This is used for
343 : : * the elements in a hash table. It uses the server OID as lookup key,
344 : : * and includes a per-server list of all foreign tables involved in the
345 : : * truncation.
346 : : */
347 : : typedef struct ForeignTruncateInfo
348 : : {
349 : : Oid serverid;
350 : : List *rels;
351 : : } ForeignTruncateInfo;
352 : :
353 : : /* Partial or complete FK creation in addFkConstraint() */
354 : : typedef enum addFkConstraintSides
355 : : {
356 : : addFkReferencedSide,
357 : : addFkReferencingSide,
358 : : addFkBothSides,
359 : : } addFkConstraintSides;
360 : :
361 : : /*
362 : : * Partition tables are expected to be dropped when the parent partitioned
363 : : * table gets dropped. Hence for partitioning we use AUTO dependency.
364 : : * Otherwise, for regular inheritance use NORMAL dependency.
365 : : */
366 : : #define child_dependency_type(child_is_partition) \
367 : : ((child_is_partition) ? DEPENDENCY_AUTO : DEPENDENCY_NORMAL)
368 : :
369 : : static void truncate_check_rel(Oid relid, Form_pg_class reltuple);
370 : : static void truncate_check_perms(Oid relid, Form_pg_class reltuple);
371 : : static void truncate_check_activity(Relation rel);
372 : : static void RangeVarCallbackForTruncate(const RangeVar *relation,
373 : : Oid relId, Oid oldRelId, void *arg);
374 : : static List *MergeAttributes(List *columns, const List *supers, char relpersistence,
375 : : bool is_partition, List **supconstr,
376 : : List **supnotnulls);
377 : : static List *MergeCheckConstraint(List *constraints, const char *name, Node *expr, bool is_enforced);
378 : : static void MergeChildAttribute(List *inh_columns, int exist_attno, int newcol_attno, const ColumnDef *newdef);
379 : : static ColumnDef *MergeInheritedAttribute(List *inh_columns, int exist_attno, const ColumnDef *newdef);
380 : : static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel, bool ispartition);
381 : : static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
382 : : static void StoreCatalogInheritance(Oid relationId, List *supers,
383 : : bool child_is_partition);
384 : : static void StoreCatalogInheritance1(Oid relationId, Oid parentOid,
385 : : int32 seqNumber, Relation inhRelation,
386 : : bool child_is_partition);
387 : : static int findAttrByName(const char *attributeName, const List *columns);
388 : : static void AlterIndexNamespaces(Relation classRel, Relation rel,
389 : : Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved);
390 : : static void AlterSeqNamespaces(Relation classRel, Relation rel,
391 : : Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved,
392 : : LOCKMODE lockmode);
393 : : static ObjectAddress ATExecAlterConstraint(List **wqueue, Relation rel,
394 : : ATAlterConstraint *cmdcon,
395 : : bool recurse, LOCKMODE lockmode);
396 : : static bool ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon, Relation conrel,
397 : : Relation tgrel, Relation rel, HeapTuple contuple,
398 : : bool recurse, LOCKMODE lockmode);
399 : : static bool ATExecAlterConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon,
400 : : Relation conrel, Relation tgrel,
401 : : Oid fkrelid, Oid pkrelid,
402 : : HeapTuple contuple, LOCKMODE lockmode,
403 : : Oid ReferencedParentDelTrigger,
404 : : Oid ReferencedParentUpdTrigger,
405 : : Oid ReferencingParentInsTrigger,
406 : : Oid ReferencingParentUpdTrigger);
407 : : static bool ATExecAlterConstrDeferrability(List **wqueue, ATAlterConstraint *cmdcon,
408 : : Relation conrel, Relation tgrel, Relation rel,
409 : : HeapTuple contuple, bool recurse,
410 : : List **otherrelids, LOCKMODE lockmode);
411 : : static bool ATExecAlterConstrInheritability(List **wqueue, ATAlterConstraint *cmdcon,
412 : : Relation conrel, Relation rel,
413 : : HeapTuple contuple, LOCKMODE lockmode);
414 : : static void AlterConstrTriggerDeferrability(Oid conoid, Relation tgrel, Relation rel,
415 : : bool deferrable, bool initdeferred,
416 : : List **otherrelids);
417 : : static void AlterConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
418 : : Relation conrel, Relation tgrel,
419 : : Oid fkrelid, Oid pkrelid,
420 : : HeapTuple contuple, LOCKMODE lockmode,
421 : : Oid ReferencedParentDelTrigger,
422 : : Oid ReferencedParentUpdTrigger,
423 : : Oid ReferencingParentInsTrigger,
424 : : Oid ReferencingParentUpdTrigger);
425 : : static void AlterConstrDeferrabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
426 : : Relation conrel, Relation tgrel, Relation rel,
427 : : HeapTuple contuple, bool recurse,
428 : : List **otherrelids, LOCKMODE lockmode);
429 : : static void AlterConstrUpdateConstraintEntry(ATAlterConstraint *cmdcon, Relation conrel,
430 : : HeapTuple contuple);
431 : : static ObjectAddress ATExecValidateConstraint(List **wqueue,
432 : : Relation rel, char *constrName,
433 : : bool recurse, bool recursing, LOCKMODE lockmode);
434 : : static void QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation fkrel,
435 : : Oid pkrelid, HeapTuple contuple, LOCKMODE lockmode);
436 : : static void QueueCheckConstraintValidation(List **wqueue, Relation conrel, Relation rel,
437 : : char *constrName, HeapTuple contuple,
438 : : bool recurse, bool recursing, LOCKMODE lockmode);
439 : : static void QueueNNConstraintValidation(List **wqueue, Relation conrel, Relation rel,
440 : : HeapTuple contuple, bool recurse, bool recursing,
441 : : LOCKMODE lockmode);
442 : : static int transformColumnNameList(Oid relId, List *colList,
443 : : int16 *attnums, Oid *atttypids, Oid *attcollids);
444 : : static int transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
445 : : List **attnamelist,
446 : : int16 *attnums, Oid *atttypids, Oid *attcollids,
447 : : Oid *opclasses, bool *pk_has_without_overlaps);
448 : : static Oid transformFkeyCheckAttrs(Relation pkrel,
449 : : int numattrs, int16 *attnums,
450 : : bool with_period, Oid *opclasses,
451 : : bool *pk_has_without_overlaps);
452 : : static void checkFkeyPermissions(Relation rel, int16 *attnums, int natts);
453 : : static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId,
454 : : Oid *funcid);
455 : : static void validateForeignKeyConstraint(char *conname,
456 : : Relation rel, Relation pkrel,
457 : : Oid pkindOid, Oid constraintOid, bool hasperiod);
458 : : static void CheckAlterTableIsSafe(Relation rel);
459 : : static void ATController(AlterTableStmt *parsetree,
460 : : Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
461 : : AlterTableUtilityContext *context);
462 : : static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
463 : : bool recurse, bool recursing, LOCKMODE lockmode,
464 : : AlterTableUtilityContext *context);
465 : : static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
466 : : AlterTableUtilityContext *context);
467 : : static void ATExecCmd(List **wqueue, AlteredTableInfo *tab,
468 : : AlterTableCmd *cmd, LOCKMODE lockmode, AlterTablePass cur_pass,
469 : : AlterTableUtilityContext *context);
470 : : static AlterTableCmd *ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab,
471 : : Relation rel, AlterTableCmd *cmd,
472 : : bool recurse, LOCKMODE lockmode,
473 : : AlterTablePass cur_pass,
474 : : AlterTableUtilityContext *context);
475 : : static void ATRewriteTables(AlterTableStmt *parsetree,
476 : : List **wqueue, LOCKMODE lockmode,
477 : : AlterTableUtilityContext *context);
478 : : static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap);
479 : : static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
480 : : static void ATSimplePermissions(AlterTableType cmdtype, Relation rel, int allowed_targets);
481 : : static void ATSimpleRecursion(List **wqueue, Relation rel,
482 : : AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
483 : : AlterTableUtilityContext *context);
484 : : static void ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode);
485 : : static void ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
486 : : LOCKMODE lockmode,
487 : : AlterTableUtilityContext *context);
488 : : static List *find_typed_table_dependencies(Oid typeOid, const char *typeName,
489 : : DropBehavior behavior);
490 : : static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
491 : : bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode,
492 : : AlterTableUtilityContext *context);
493 : : static ObjectAddress ATExecAddColumn(List **wqueue, AlteredTableInfo *tab,
494 : : Relation rel, AlterTableCmd **cmd,
495 : : bool recurse, bool recursing,
496 : : LOCKMODE lockmode, AlterTablePass cur_pass,
497 : : AlterTableUtilityContext *context);
498 : : static bool check_for_column_name_collision(Relation rel, const char *colname,
499 : : bool if_not_exists);
500 : : static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
501 : : static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
502 : : static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, bool recurse,
503 : : LOCKMODE lockmode);
504 : : static void set_attnotnull(List **wqueue, Relation rel, AttrNumber attnum,
505 : : bool is_valid, bool queue_validation);
506 : : static ObjectAddress ATExecSetNotNull(List **wqueue, Relation rel,
507 : : char *conName, char *colName,
508 : : bool recurse, bool recursing,
509 : : LOCKMODE lockmode);
510 : : static bool NotNullImpliedByRelConstraints(Relation rel, Form_pg_attribute attr);
511 : : static bool ConstraintImpliedByRelConstraint(Relation scanrel,
512 : : List *testConstraint, List *provenConstraint);
513 : : static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
514 : : Node *newDefault, LOCKMODE lockmode);
515 : : static ObjectAddress ATExecCookedColumnDefault(Relation rel, AttrNumber attnum,
516 : : Node *newDefault);
517 : : static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName,
518 : : Node *def, LOCKMODE lockmode, bool recurse, bool recursing);
519 : : static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName,
520 : : Node *def, LOCKMODE lockmode, bool recurse, bool recursing);
521 : : static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode,
522 : : bool recurse, bool recursing);
523 : : static ObjectAddress ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
524 : : Node *newExpr, LOCKMODE lockmode);
525 : : static void ATPrepDropExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, LOCKMODE lockmode);
526 : : static ObjectAddress ATExecDropExpression(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode);
527 : : static ObjectAddress ATExecSetStatistics(Relation rel, const char *colName, int16 colNum,
528 : : Node *newValue, LOCKMODE lockmode);
529 : : static ObjectAddress ATExecSetOptions(Relation rel, const char *colName,
530 : : Node *options, bool isReset, LOCKMODE lockmode);
531 : : static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
532 : : Node *newValue, LOCKMODE lockmode);
533 : : static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
534 : : AlterTableCmd *cmd, LOCKMODE lockmode,
535 : : AlterTableUtilityContext *context);
536 : : static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
537 : : DropBehavior behavior,
538 : : bool recurse, bool recursing,
539 : : bool missing_ok, LOCKMODE lockmode,
540 : : ObjectAddresses *addrs);
541 : : static void ATPrepAddPrimaryKey(List **wqueue, Relation rel, AlterTableCmd *cmd,
542 : : bool recurse, LOCKMODE lockmode,
543 : : AlterTableUtilityContext *context);
544 : : static void verifyNotNullPKCompatible(HeapTuple tuple, const char *colname);
545 : : static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
546 : : IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
547 : : static ObjectAddress ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
548 : : CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
549 : : static ObjectAddress ATExecAddConstraint(List **wqueue,
550 : : AlteredTableInfo *tab, Relation rel,
551 : : Constraint *newConstraint, bool recurse, bool is_readd,
552 : : LOCKMODE lockmode);
553 : : static char *ChooseForeignKeyConstraintNameAddition(List *colnames);
554 : : static ObjectAddress ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
555 : : IndexStmt *stmt, LOCKMODE lockmode);
556 : : static ObjectAddress ATAddCheckNNConstraint(List **wqueue,
557 : : AlteredTableInfo *tab, Relation rel,
558 : : Constraint *constr,
559 : : bool recurse, bool recursing, bool is_readd,
560 : : LOCKMODE lockmode);
561 : : static ObjectAddress ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab,
562 : : Relation rel, Constraint *fkconstraint,
563 : : bool recurse, bool recursing,
564 : : LOCKMODE lockmode);
565 : : static int validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
566 : : int numfksetcols, int16 *fksetcolsattnums,
567 : : List *fksetcols);
568 : : static ObjectAddress addFkConstraint(addFkConstraintSides fkside,
569 : : char *constraintname,
570 : : Constraint *fkconstraint, Relation rel,
571 : : Relation pkrel, Oid indexOid,
572 : : Oid parentConstr,
573 : : int numfks, int16 *pkattnum, int16 *fkattnum,
574 : : Oid *pfeqoperators, Oid *ppeqoperators,
575 : : Oid *ffeqoperators, int numfkdelsetcols,
576 : : int16 *fkdelsetcols, bool is_internal,
577 : : bool with_period);
578 : : static void addFkRecurseReferenced(Constraint *fkconstraint,
579 : : Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
580 : : int numfks, int16 *pkattnum, int16 *fkattnum,
581 : : Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
582 : : int numfkdelsetcols, int16 *fkdelsetcols,
583 : : bool old_check_ok,
584 : : Oid parentDelTrigger, Oid parentUpdTrigger,
585 : : bool with_period);
586 : : static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint,
587 : : Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
588 : : int numfks, int16 *pkattnum, int16 *fkattnum,
589 : : Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
590 : : int numfkdelsetcols, int16 *fkdelsetcols,
591 : : bool old_check_ok, LOCKMODE lockmode,
592 : : Oid parentInsTrigger, Oid parentUpdTrigger,
593 : : bool with_period);
594 : : static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
595 : : Relation partitionRel);
596 : : static void CloneFkReferenced(Relation parentRel, Relation partitionRel);
597 : : static void CloneFkReferencing(List **wqueue, Relation parentRel,
598 : : Relation partRel);
599 : : static void createForeignKeyCheckTriggers(Oid myRelOid, Oid refRelOid,
600 : : Constraint *fkconstraint, Oid constraintOid,
601 : : Oid indexOid,
602 : : Oid parentInsTrigger, Oid parentUpdTrigger,
603 : : Oid *insertTrigOid, Oid *updateTrigOid);
604 : : static void createForeignKeyActionTriggers(Oid myRelOid, Oid refRelOid,
605 : : Constraint *fkconstraint, Oid constraintOid,
606 : : Oid indexOid,
607 : : Oid parentDelTrigger, Oid parentUpdTrigger,
608 : : Oid *deleteTrigOid, Oid *updateTrigOid);
609 : : static bool tryAttachPartitionForeignKey(List **wqueue,
610 : : ForeignKeyCacheInfo *fk,
611 : : Relation partition,
612 : : Oid parentConstrOid, int numfks,
613 : : AttrNumber *mapped_conkey, AttrNumber *confkey,
614 : : Oid *conpfeqop,
615 : : Oid parentInsTrigger,
616 : : Oid parentUpdTrigger,
617 : : Relation trigrel);
618 : : static void AttachPartitionForeignKey(List **wqueue, Relation partition,
619 : : Oid partConstrOid, Oid parentConstrOid,
620 : : Oid parentInsTrigger, Oid parentUpdTrigger,
621 : : Relation trigrel);
622 : : static void RemoveInheritedConstraint(Relation conrel, Relation trigrel,
623 : : Oid conoid, Oid conrelid);
624 : : static void DropForeignKeyConstraintTriggers(Relation trigrel, Oid conoid,
625 : : Oid confrelid, Oid conrelid);
626 : : static void GetForeignKeyActionTriggers(Relation trigrel,
627 : : Oid conoid, Oid confrelid, Oid conrelid,
628 : : Oid *deleteTriggerOid,
629 : : Oid *updateTriggerOid);
630 : : static void GetForeignKeyCheckTriggers(Relation trigrel,
631 : : Oid conoid, Oid confrelid, Oid conrelid,
632 : : Oid *insertTriggerOid,
633 : : Oid *updateTriggerOid);
634 : : static void ATExecDropConstraint(Relation rel, const char *constrName,
635 : : DropBehavior behavior, bool recurse,
636 : : bool missing_ok, LOCKMODE lockmode);
637 : : static ObjectAddress dropconstraint_internal(Relation rel,
638 : : HeapTuple constraintTup, DropBehavior behavior,
639 : : bool recurse, bool recursing,
640 : : bool missing_ok, LOCKMODE lockmode);
641 : : static void ATPrepAlterColumnType(List **wqueue,
642 : : AlteredTableInfo *tab, Relation rel,
643 : : bool recurse, bool recursing,
644 : : AlterTableCmd *cmd, LOCKMODE lockmode,
645 : : AlterTableUtilityContext *context);
646 : : static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno);
647 : : static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
648 : : AlterTableCmd *cmd, LOCKMODE lockmode);
649 : : static void RememberAllDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype,
650 : : Relation rel, AttrNumber attnum, const char *colName);
651 : : static void RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab);
652 : : static void RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab);
653 : : static void RememberStatisticsForRebuilding(Oid stxoid, AlteredTableInfo *tab);
654 : : static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab,
655 : : LOCKMODE lockmode);
656 : : static void ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId,
657 : : char *cmd, List **wqueue, LOCKMODE lockmode,
658 : : bool rewrite);
659 : : static void RebuildConstraintComment(AlteredTableInfo *tab, AlterTablePass pass,
660 : : Oid objid, Relation rel, List *domname,
661 : : const char *conname);
662 : : static void TryReuseIndex(Oid oldId, IndexStmt *stmt);
663 : : static void TryReuseForeignKey(Oid oldId, Constraint *con);
664 : : static ObjectAddress ATExecAlterColumnGenericOptions(Relation rel, const char *colName,
665 : : List *options, LOCKMODE lockmode);
666 : : static void change_owner_fix_column_acls(Oid relationOid,
667 : : Oid oldOwnerId, Oid newOwnerId);
668 : : static void change_owner_recurse_to_sequences(Oid relationOid,
669 : : Oid newOwnerId, LOCKMODE lockmode);
670 : : static ObjectAddress ATExecClusterOn(Relation rel, const char *indexName,
671 : : LOCKMODE lockmode);
672 : : static void ATExecDropCluster(Relation rel, LOCKMODE lockmode);
673 : : static void ATPrepSetAccessMethod(AlteredTableInfo *tab, Relation rel, const char *amname);
674 : : static void ATExecSetAccessMethodNoStorage(Relation rel, Oid newAccessMethodId);
675 : : static void ATPrepChangePersistence(AlteredTableInfo *tab, Relation rel,
676 : : bool toLogged);
677 : : static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
678 : : const char *tablespacename, LOCKMODE lockmode);
679 : : static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
680 : : static void ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace);
681 : : static void ATExecSetRelOptions(Relation rel, List *defList,
682 : : AlterTableType operation,
683 : : LOCKMODE lockmode);
684 : : static void ATExecEnableDisableTrigger(Relation rel, const char *trigname,
685 : : char fires_when, bool skip_system, bool recurse,
686 : : LOCKMODE lockmode);
687 : : static void ATExecEnableDisableRule(Relation rel, const char *rulename,
688 : : char fires_when, LOCKMODE lockmode);
689 : : static void ATPrepAddInherit(Relation child_rel);
690 : : static ObjectAddress ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode);
691 : : static ObjectAddress ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode);
692 : : static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid,
693 : : DependencyType deptype);
694 : : static ObjectAddress ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
695 : : static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
696 : : static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode);
697 : : static void ATExecGenericOptions(Relation rel, List *options);
698 : : static void ATExecSetRowSecurity(Relation rel, bool rls);
699 : : static void ATExecForceNoForceRowSecurity(Relation rel, bool force_rls);
700 : : static ObjectAddress ATExecSetCompression(Relation rel,
701 : : const char *column, Node *newValue, LOCKMODE lockmode);
702 : :
703 : : static void index_copy_data(Relation rel, RelFileLocator newrlocator);
704 : : static const char *storage_name(char c);
705 : :
706 : : static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
707 : : Oid oldRelOid, void *arg);
708 : : static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
709 : : Oid oldrelid, void *arg);
710 : : static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec);
711 : : static void ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNumber *partattrs,
712 : : List **partexprs, Oid *partopclass, Oid *partcollation,
713 : : PartitionStrategy strategy);
714 : : static void CreateInheritance(Relation child_rel, Relation parent_rel, bool ispartition);
715 : : static void RemoveInheritance(Relation child_rel, Relation parent_rel,
716 : : bool expect_detached);
717 : : static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
718 : : PartitionCmd *cmd,
719 : : AlterTableUtilityContext *context);
720 : : static void AttachPartitionEnsureIndexes(List **wqueue, Relation rel, Relation attachrel);
721 : : static void QueuePartitionConstraintValidation(List **wqueue, Relation scanrel,
722 : : List *partConstraint,
723 : : bool validate_default);
724 : : static void CloneRowTriggersToPartition(Relation parent, Relation partition);
725 : : static void DropClonedTriggersFromPartition(Oid partitionId);
726 : : static ObjectAddress ATExecDetachPartition(List **wqueue, AlteredTableInfo *tab,
727 : : Relation rel, RangeVar *name,
728 : : bool concurrent);
729 : : static void DetachPartitionFinalize(Relation rel, Relation partRel,
730 : : bool concurrent, Oid defaultPartOid);
731 : : static ObjectAddress ATExecDetachPartitionFinalize(Relation rel, RangeVar *name);
732 : : static ObjectAddress ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx,
733 : : RangeVar *name);
734 : : static void validatePartitionedIndex(Relation partedIdx, Relation partedTbl);
735 : : static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
736 : : Relation partitionTbl);
737 : : static void verifyPartitionIndexNotNull(IndexInfo *iinfo, Relation partition);
738 : : static List *GetParentedForeignKeyRefs(Relation partition);
739 : : static void ATDetachCheckNoForeignKeyRefs(Relation partition);
740 : : static char GetAttributeCompression(Oid atttypid, const char *compression);
741 : : static char GetAttributeStorage(Oid atttypid, const char *storagemode);
742 : :
743 : : static void ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
744 : : PartitionCmd *cmd, AlterTableUtilityContext *context);
745 : : static void ATExecSplitPartition(List **wqueue, AlteredTableInfo *tab,
746 : : Relation rel, PartitionCmd *cmd,
747 : : AlterTableUtilityContext *context);
748 : :
749 : : /* ----------------------------------------------------------------
750 : : * DefineRelation
751 : : * Creates a new relation.
752 : : *
753 : : * stmt carries parsetree information from an ordinary CREATE TABLE statement.
754 : : * The other arguments are used to extend the behavior for other cases:
755 : : * relkind: relkind to assign to the new relation
756 : : * ownerId: if not InvalidOid, use this as the new relation's owner.
757 : : * typaddress: if not null, it's set to the pg_type entry's address.
758 : : * queryString: for error reporting
759 : : *
760 : : * Note that permissions checks are done against current user regardless of
761 : : * ownerId. A nonzero ownerId is used when someone is creating a relation
762 : : * "on behalf of" someone else, so we still want to see that the current user
763 : : * has permissions to do it.
764 : : *
765 : : * If successful, returns the address of the new relation.
766 : : * ----------------------------------------------------------------
767 : : */
768 : : ObjectAddress
769 : 5512 : DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
770 : : ObjectAddress *typaddress, const char *queryString)
771 : : {
772 : 5512 : char relname[NAMEDATALEN];
773 : 5512 : Oid namespaceId;
774 : 5512 : Oid relationId;
775 : 5512 : Oid tablespaceId;
776 : 5512 : Relation rel;
777 : 5512 : TupleDesc descriptor;
778 : 5512 : List *inheritOids;
779 : 5512 : List *old_constraints;
780 : 5512 : List *old_notnulls;
781 : 5512 : List *rawDefaults;
782 : 5512 : List *cookedDefaults;
783 : 5512 : List *nncols;
784 : 5512 : Datum reloptions;
785 : 5512 : ListCell *listptr;
786 : 5512 : AttrNumber attnum;
787 : 5512 : bool partitioned;
788 : 5512 : const char *const validnsps[] = HEAP_RELOPT_NAMESPACES;
789 : 5512 : Oid ofTypeId;
790 : : ObjectAddress address;
791 : 5512 : LOCKMODE parentLockmode;
792 : 5512 : Oid accessMethodId = InvalidOid;
793 : :
794 : : /*
795 : : * Truncate relname to appropriate length (probably a waste of time, as
796 : : * parser should have done this already).
797 : : */
798 : 5512 : strlcpy(relname, stmt->relation->relname, NAMEDATALEN);
799 : :
800 : : /*
801 : : * Check consistency of arguments
802 : : */
803 : 5512 : if (stmt->oncommit != ONCOMMIT_NOOP
804 [ + + + + ]: 5512 : && stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
805 [ + - + - ]: 2 : ereport(ERROR,
806 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
807 : : errmsg("ON COMMIT can only be used on temporary tables")));
808 : :
809 [ + + ]: 5510 : if (stmt->partspec != NULL)
810 : : {
811 [ + - ]: 721 : if (relkind != RELKIND_RELATION)
812 [ # # # # ]: 0 : elog(ERROR, "unexpected relkind: %d", (int) relkind);
813 : :
814 : 721 : relkind = RELKIND_PARTITIONED_TABLE;
815 : 721 : partitioned = true;
816 : 721 : }
817 : : else
818 : 4789 : partitioned = false;
819 : :
820 [ + + + + ]: 5510 : if (relkind == RELKIND_PARTITIONED_TABLE &&
821 : 727 : stmt->relation->relpersistence == RELPERSISTENCE_UNLOGGED)
822 [ + - + - ]: 1 : ereport(ERROR,
823 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
824 : : errmsg("partitioned tables cannot be unlogged")));
825 : :
826 : : /*
827 : : * Look up the namespace in which we are supposed to create the relation,
828 : : * check we have permission to create there, lock it against concurrent
829 : : * drop, and mark stmt->relation as RELPERSISTENCE_TEMP if a temporary
830 : : * namespace is selected.
831 : : */
832 : 5509 : namespaceId =
833 : 5509 : RangeVarGetAndCheckCreationNamespace(stmt->relation, NoLock, NULL);
834 : :
835 : : /*
836 : : * Security check: disallow creating temp tables from security-restricted
837 : : * code. This is needed because calling code might not expect untrusted
838 : : * tables to appear in pg_temp at the front of its search path.
839 : : */
840 : 5509 : if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
841 [ + + + - ]: 5509 : && InSecurityRestrictedOperation())
842 [ # # # # ]: 0 : ereport(ERROR,
843 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
844 : : errmsg("cannot create temporary table within security-restricted operation")));
845 : :
846 : : /*
847 : : * Determine the lockmode to use when scanning parents. A self-exclusive
848 : : * lock is needed here.
849 : : *
850 : : * For regular inheritance, if two backends attempt to add children to the
851 : : * same parent simultaneously, and that parent has no pre-existing
852 : : * children, then both will attempt to update the parent's relhassubclass
853 : : * field, leading to a "tuple concurrently updated" error. Also, this
854 : : * interlocks against a concurrent ANALYZE on the parent table, which
855 : : * might otherwise be attempting to clear the parent's relhassubclass
856 : : * field, if its previous children were recently dropped.
857 : : *
858 : : * If the child table is a partition, then we instead grab an exclusive
859 : : * lock on the parent because its partition descriptor will be changed by
860 : : * addition of the new partition.
861 : : */
862 : 5509 : parentLockmode = (stmt->partbound != NULL ? AccessExclusiveLock :
863 : : ShareUpdateExclusiveLock);
864 : :
865 : : /* Determine the list of OIDs of the parents. */
866 : 5509 : inheritOids = NIL;
867 [ + + + + : 7088 : foreach(listptr, stmt->inhRelations)
+ + ]
868 : : {
869 : 1579 : RangeVar *rv = (RangeVar *) lfirst(listptr);
870 : 1579 : Oid parentOid;
871 : :
872 : 1579 : parentOid = RangeVarGetRelid(rv, parentLockmode, false);
873 : :
874 : : /*
875 : : * Reject duplications in the list of parents.
876 : : */
877 [ + - ]: 1579 : if (list_member_oid(inheritOids, parentOid))
878 [ # # # # ]: 0 : ereport(ERROR,
879 : : (errcode(ERRCODE_DUPLICATE_TABLE),
880 : : errmsg("relation \"%s\" would be inherited from more than once",
881 : : get_rel_name(parentOid))));
882 : :
883 : 1579 : inheritOids = lappend_oid(inheritOids, parentOid);
884 : 1579 : }
885 : :
886 : : /*
887 : : * Select tablespace to use: an explicitly indicated one, or (in the case
888 : : * of a partitioned table) the parent's, if it has one.
889 : : */
890 [ + + ]: 5509 : if (stmt->tablespacename)
891 : : {
892 : 9 : tablespaceId = get_tablespace_oid(stmt->tablespacename, false);
893 : :
894 [ + + + + ]: 9 : if (partitioned && tablespaceId == MyDatabaseTableSpace)
895 [ + - + - ]: 1 : ereport(ERROR,
896 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
897 : : errmsg("cannot specify default tablespace for partitioned relations")));
898 : 8 : }
899 [ + + ]: 5500 : else if (stmt->partbound)
900 : : {
901 [ + - ]: 1223 : Assert(list_length(inheritOids) == 1);
902 : 1223 : tablespaceId = get_rel_tablespace(linitial_oid(inheritOids));
903 : 1223 : }
904 : : else
905 : 4277 : tablespaceId = InvalidOid;
906 : :
907 : : /* still nothing? use the default */
908 [ + + ]: 5508 : if (!OidIsValid(tablespaceId))
909 : 10988 : tablespaceId = GetDefaultTablespace(stmt->relation->relpersistence,
910 : 5494 : partitioned);
911 : :
912 : : /* Check permissions except when using database's default */
913 [ + + + + ]: 5508 : if (OidIsValid(tablespaceId) && tablespaceId != MyDatabaseTableSpace)
914 : : {
915 : 21 : AclResult aclresult;
916 : :
917 : 21 : aclresult = object_aclcheck(TableSpaceRelationId, tablespaceId, GetUserId(),
918 : : ACL_CREATE);
919 [ + + ]: 21 : if (aclresult != ACLCHECK_OK)
920 : 2 : aclcheck_error(aclresult, OBJECT_TABLESPACE,
921 : 1 : get_tablespace_name(tablespaceId));
922 : 21 : }
923 : :
924 : : /* In all cases disallow placing user relations in pg_global */
925 [ + + ]: 5508 : if (tablespaceId == GLOBALTABLESPACE_OID)
926 [ + - + - ]: 3 : ereport(ERROR,
927 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
928 : : errmsg("only shared relations can be placed in pg_global tablespace")));
929 : :
930 : : /* Identify user ID that will own the table */
931 [ + + ]: 5505 : if (!OidIsValid(ownerId))
932 : 5476 : ownerId = GetUserId();
933 : :
934 : : /*
935 : : * Parse and validate reloptions, if any.
936 : : */
937 : 5505 : reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
938 : : true, false);
939 : :
940 [ + + + ]: 5505 : switch (relkind)
941 : : {
942 : : case RELKIND_VIEW:
943 : 547 : (void) view_reloptions(reloptions, true);
944 : 547 : break;
945 : : case RELKIND_PARTITIONED_TABLE:
946 : 723 : (void) partitioned_table_reloptions(reloptions, true);
947 : 723 : break;
948 : : default:
949 : 4235 : (void) heap_reloptions(relkind, reloptions, true);
950 : 4235 : }
951 : :
952 [ + + ]: 5505 : if (stmt->ofTypename)
953 : : {
954 : 34 : AclResult aclresult;
955 : :
956 : 34 : ofTypeId = typenameTypeId(NULL, stmt->ofTypename);
957 : :
958 : 34 : aclresult = object_aclcheck(TypeRelationId, ofTypeId, GetUserId(), ACL_USAGE);
959 [ + + ]: 34 : if (aclresult != ACLCHECK_OK)
960 : 1 : aclcheck_error_type(aclresult, ofTypeId);
961 : 34 : }
962 : : else
963 : 5471 : ofTypeId = InvalidOid;
964 : :
965 : : /*
966 : : * Look up inheritance ancestors and generate relation schema, including
967 : : * inherited attributes. (Note that stmt->tableElts is destructively
968 : : * modified by MergeAttributes.)
969 : : */
970 : 5505 : stmt->tableElts =
971 : 11010 : MergeAttributes(stmt->tableElts, inheritOids,
972 : 5505 : stmt->relation->relpersistence,
973 : 5505 : stmt->partbound != NULL,
974 : : &old_constraints, &old_notnulls);
975 : :
976 : : /*
977 : : * Create a tuple descriptor from the relation schema. Note that this
978 : : * deals with column names, types, and in-descriptor NOT NULL flags, but
979 : : * not default values, NOT NULL or CHECK constraints; we handle those
980 : : * below.
981 : : */
982 : 5505 : descriptor = BuildDescForRelation(stmt->tableElts);
983 : :
984 : : /*
985 : : * Find columns with default values and prepare for insertion of the
986 : : * defaults. Pre-cooked (that is, inherited) defaults go into a list of
987 : : * CookedConstraint structs that we'll pass to heap_create_with_catalog,
988 : : * while raw defaults go into a list of RawColumnDefault structs that will
989 : : * be processed by AddRelationNewConstraints. (We can't deal with raw
990 : : * expressions until we can do transformExpr.)
991 : : */
992 : 5505 : rawDefaults = NIL;
993 : 5505 : cookedDefaults = NIL;
994 : 5505 : attnum = 0;
995 : :
996 [ + + + + : 19787 : foreach(listptr, stmt->tableElts)
+ + ]
997 : : {
998 : 14282 : ColumnDef *colDef = lfirst(listptr);
999 : :
1000 : 14282 : attnum++;
1001 [ + + ]: 14282 : if (colDef->raw_default != NULL)
1002 : : {
1003 : 401 : RawColumnDefault *rawEnt;
1004 : :
1005 [ + - ]: 401 : Assert(colDef->cooked_default == NULL);
1006 : :
1007 : 401 : rawEnt = palloc_object(RawColumnDefault);
1008 : 401 : rawEnt->attnum = attnum;
1009 : 401 : rawEnt->raw_default = colDef->raw_default;
1010 : 401 : rawEnt->generated = colDef->generated;
1011 : 401 : rawDefaults = lappend(rawDefaults, rawEnt);
1012 : 401 : }
1013 [ + + ]: 13881 : else if (colDef->cooked_default != NULL)
1014 : : {
1015 : 67 : CookedConstraint *cooked;
1016 : :
1017 : 67 : cooked = palloc_object(CookedConstraint);
1018 : 67 : cooked->contype = CONSTR_DEFAULT;
1019 : 67 : cooked->conoid = InvalidOid; /* until created */
1020 : 67 : cooked->name = NULL;
1021 : 67 : cooked->attnum = attnum;
1022 : 67 : cooked->expr = colDef->cooked_default;
1023 : 67 : cooked->is_enforced = true;
1024 : 67 : cooked->skip_validation = false;
1025 : 67 : cooked->is_local = true; /* not used for defaults */
1026 : 67 : cooked->inhcount = 0; /* ditto */
1027 : 67 : cooked->is_no_inherit = false;
1028 : 67 : cookedDefaults = lappend(cookedDefaults, cooked);
1029 : 67 : }
1030 : 14282 : }
1031 : :
1032 : : /*
1033 : : * For relations with table AM and partitioned tables, select access
1034 : : * method to use: an explicitly indicated one, or (in the case of a
1035 : : * partitioned table) the parent's, if it has one.
1036 : : */
1037 [ + + ]: 5505 : if (stmt->accessMethod != NULL)
1038 : : {
1039 [ + + + - : 89 : Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE);
+ + + - ]
1040 : 89 : accessMethodId = get_table_am_oid(stmt->accessMethod, false);
1041 : 89 : }
1042 [ + + + - : 5416 : else if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE)
+ + + + ]
1043 : : {
1044 [ + + ]: 4519 : if (stmt->partbound)
1045 : : {
1046 [ + - ]: 1200 : Assert(list_length(inheritOids) == 1);
1047 : 1200 : accessMethodId = get_rel_relam(linitial_oid(inheritOids));
1048 : 1200 : }
1049 : :
1050 [ + + + - : 4519 : if (RELKIND_HAS_TABLE_AM(relkind) && !OidIsValid(accessMethodId))
+ + ]
1051 : 3794 : accessMethodId = get_table_am_oid(default_table_access_method, false);
1052 : 4519 : }
1053 : :
1054 : : /*
1055 : : * Create the relation. Inherited defaults and CHECK constraints are
1056 : : * passed in for immediate handling --- since they don't need parsing,
1057 : : * they can be stored immediately.
1058 : : */
1059 : 11010 : relationId = heap_create_with_catalog(relname,
1060 : 5505 : namespaceId,
1061 : 5505 : tablespaceId,
1062 : : InvalidOid,
1063 : : InvalidOid,
1064 : 5505 : ofTypeId,
1065 : 5505 : ownerId,
1066 : 5505 : accessMethodId,
1067 : 5505 : descriptor,
1068 : 11010 : list_concat(cookedDefaults,
1069 : 5505 : old_constraints),
1070 : 5505 : relkind,
1071 : 5505 : stmt->relation->relpersistence,
1072 : : false,
1073 : : false,
1074 : 5505 : stmt->oncommit,
1075 : 5505 : reloptions,
1076 : : true,
1077 : 5505 : allowSystemTableMods,
1078 : : false,
1079 : : InvalidOid,
1080 : 5505 : typaddress);
1081 : :
1082 : : /*
1083 : : * We must bump the command counter to make the newly-created relation
1084 : : * tuple visible for opening.
1085 : : */
1086 : 5505 : CommandCounterIncrement();
1087 : :
1088 : : /*
1089 : : * Open the new relation and acquire exclusive lock on it. This isn't
1090 : : * really necessary for locking out other backends (since they can't see
1091 : : * the new rel anyway until we commit), but it keeps the lock manager from
1092 : : * complaining about deadlock risks.
1093 : : */
1094 : 5505 : rel = relation_open(relationId, AccessExclusiveLock);
1095 : :
1096 : : /*
1097 : : * Now add any newly specified column default and generation expressions
1098 : : * to the new relation. These are passed to us in the form of raw
1099 : : * parsetrees; we need to transform them to executable expression trees
1100 : : * before they can be added. The most convenient way to do that is to
1101 : : * apply the parser's transformExpr routine, but transformExpr doesn't
1102 : : * work unless we have a pre-existing relation. So, the transformation has
1103 : : * to be postponed to this final step of CREATE TABLE.
1104 : : *
1105 : : * This needs to be before processing the partitioning clauses because
1106 : : * those could refer to generated columns.
1107 : : */
1108 [ + + ]: 5505 : if (rawDefaults)
1109 : 672 : AddRelationNewConstraints(rel, rawDefaults, NIL,
1110 : 336 : true, true, false, queryString);
1111 : :
1112 : : /*
1113 : : * Make column generation expressions visible for use by partitioning.
1114 : : */
1115 : 5505 : CommandCounterIncrement();
1116 : :
1117 : : /* Process and store partition bound, if any. */
1118 [ + + ]: 5505 : if (stmt->partbound)
1119 : : {
1120 : 1154 : PartitionBoundSpec *bound;
1121 : 1154 : ParseState *pstate;
1122 : 1154 : Oid parentId = linitial_oid(inheritOids),
1123 : : defaultPartOid;
1124 : 1154 : Relation parent,
1125 : 1154 : defaultRel = NULL;
1126 : 1154 : ParseNamespaceItem *nsitem;
1127 : :
1128 : : /* Already have strong enough lock on the parent */
1129 : 1154 : parent = table_open(parentId, NoLock);
1130 : :
1131 : : /*
1132 : : * We are going to try to validate the partition bound specification
1133 : : * against the partition key of parentRel, so it better have one.
1134 : : */
1135 [ + + ]: 1154 : if (parent->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
1136 [ + - + - ]: 3 : ereport(ERROR,
1137 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
1138 : : errmsg("\"%s\" is not partitioned",
1139 : : RelationGetRelationName(parent))));
1140 : :
1141 : : /*
1142 : : * The partition constraint of the default partition depends on the
1143 : : * partition bounds of every other partition. It is possible that
1144 : : * another backend might be about to execute a query on the default
1145 : : * partition table, and that the query relies on previously cached
1146 : : * default partition constraints. We must therefore take a table lock
1147 : : * strong enough to prevent all queries on the default partition from
1148 : : * proceeding until we commit and send out a shared-cache-inval notice
1149 : : * that will make them update their index lists.
1150 : : *
1151 : : * Order of locking: The relation being added won't be visible to
1152 : : * other backends until it is committed, hence here in
1153 : : * DefineRelation() the order of locking the default partition and the
1154 : : * relation being added does not matter. But at all other places we
1155 : : * need to lock the default relation before we lock the relation being
1156 : : * added or removed i.e. we should take the lock in same order at all
1157 : : * the places such that lock parent, lock default partition and then
1158 : : * lock the partition so as to avoid a deadlock.
1159 : : */
1160 : 1151 : defaultPartOid =
1161 : 1151 : get_default_oid_from_partdesc(RelationGetPartitionDesc(parent,
1162 : : true));
1163 [ + + ]: 1151 : if (OidIsValid(defaultPartOid))
1164 : 62 : defaultRel = table_open(defaultPartOid, AccessExclusiveLock);
1165 : :
1166 : : /* Transform the bound values */
1167 : 1151 : pstate = make_parsestate(NULL);
1168 : 1151 : pstate->p_sourcetext = queryString;
1169 : :
1170 : : /*
1171 : : * Add an nsitem containing this relation, so that transformExpr
1172 : : * called on partition bound expressions is able to report errors
1173 : : * using a proper context.
1174 : : */
1175 : 1151 : nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock,
1176 : : NULL, false, false);
1177 : 1151 : addNSItemToQuery(pstate, nsitem, false, true, true);
1178 : :
1179 : 1151 : bound = transformPartitionBound(pstate, parent, stmt->partbound);
1180 : :
1181 : : /*
1182 : : * Check first that the new partition's bound is valid and does not
1183 : : * overlap with any of existing partitions of the parent.
1184 : : */
1185 : 1151 : check_new_partition_bound(relname, parent, bound, pstate);
1186 : :
1187 : : /*
1188 : : * If the default partition exists, its partition constraints will
1189 : : * change after the addition of this new partition such that it won't
1190 : : * allow any row that qualifies for this new partition. So, check that
1191 : : * the existing data in the default partition satisfies the constraint
1192 : : * as it will exist after adding this partition.
1193 : : */
1194 [ + + ]: 1151 : if (OidIsValid(defaultPartOid))
1195 : : {
1196 : 57 : check_default_partition_contents(parent, defaultRel, bound);
1197 : : /* Keep the lock until commit. */
1198 : 57 : table_close(defaultRel, NoLock);
1199 : 57 : }
1200 : :
1201 : : /* Update the pg_class entry. */
1202 : 1151 : StorePartitionBound(rel, parent, bound);
1203 : :
1204 : 1151 : table_close(parent, NoLock);
1205 : 1151 : }
1206 : :
1207 : : /* Store inheritance information for new rel. */
1208 : 5502 : StoreCatalogInheritance(relationId, inheritOids, stmt->partbound != NULL);
1209 : :
1210 : : /*
1211 : : * Process the partitioning specification (if any) and store the partition
1212 : : * key information into the catalog.
1213 : : */
1214 [ + + ]: 5502 : if (partitioned)
1215 : : {
1216 : 722 : ParseState *pstate;
1217 : 722 : int partnatts;
1218 : 722 : AttrNumber partattrs[PARTITION_MAX_KEYS];
1219 : 722 : Oid partopclass[PARTITION_MAX_KEYS];
1220 : 722 : Oid partcollation[PARTITION_MAX_KEYS];
1221 : 722 : List *partexprs = NIL;
1222 : :
1223 : 722 : pstate = make_parsestate(NULL);
1224 : 722 : pstate->p_sourcetext = queryString;
1225 : :
1226 : 722 : partnatts = list_length(stmt->partspec->partParams);
1227 : :
1228 : : /* Protect fixed-size arrays here and in executor */
1229 [ + - ]: 722 : if (partnatts > PARTITION_MAX_KEYS)
1230 [ # # # # ]: 0 : ereport(ERROR,
1231 : : (errcode(ERRCODE_TOO_MANY_COLUMNS),
1232 : : errmsg("cannot partition using more than %d columns",
1233 : : PARTITION_MAX_KEYS)));
1234 : :
1235 : : /*
1236 : : * We need to transform the raw parsetrees corresponding to partition
1237 : : * expressions into executable expression trees. Like column defaults
1238 : : * and CHECK constraints, we could not have done the transformation
1239 : : * earlier.
1240 : : */
1241 : 722 : stmt->partspec = transformPartitionSpec(rel, stmt->partspec);
1242 : :
1243 : 1444 : ComputePartitionAttrs(pstate, rel, stmt->partspec->partParams,
1244 : 722 : partattrs, &partexprs, partopclass,
1245 : 722 : partcollation, stmt->partspec->strategy);
1246 : :
1247 : 1444 : StorePartitionKey(rel, stmt->partspec->strategy, partnatts, partattrs,
1248 : 722 : partexprs,
1249 : 722 : partopclass, partcollation);
1250 : :
1251 : : /* make it all visible */
1252 : 722 : CommandCounterIncrement();
1253 : 722 : }
1254 : :
1255 : : /*
1256 : : * If we're creating a partition, create now all the indexes, triggers,
1257 : : * FKs defined in the parent.
1258 : : *
1259 : : * We can't do it earlier, because DefineIndex wants to know the partition
1260 : : * key which we just stored.
1261 : : */
1262 [ + + ]: 5502 : if (stmt->partbound)
1263 : : {
1264 : 1149 : Oid parentId = linitial_oid(inheritOids);
1265 : 1149 : Relation parent;
1266 : 1149 : List *idxlist;
1267 : 1149 : ListCell *cell;
1268 : :
1269 : : /* Already have strong enough lock on the parent */
1270 : 1149 : parent = table_open(parentId, NoLock);
1271 : 1149 : idxlist = RelationGetIndexList(parent);
1272 : :
1273 : : /*
1274 : : * For each index in the parent table, create one in the partition
1275 : : */
1276 [ + + + + : 1373 : foreach(cell, idxlist)
+ + ]
1277 : : {
1278 : 226 : Relation idxRel = index_open(lfirst_oid(cell), AccessShareLock);
1279 : 226 : AttrMap *attmap;
1280 : 226 : IndexStmt *idxstmt;
1281 : 226 : Oid constraintOid;
1282 : :
1283 [ + + ]: 226 : if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
1284 : : {
1285 [ + + ]: 6 : if (idxRel->rd_index->indisunique)
1286 [ + - + - ]: 2 : ereport(ERROR,
1287 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
1288 : : errmsg("cannot create foreign partition of partitioned table \"%s\"",
1289 : : RelationGetRelationName(parent)),
1290 : : errdetail("Table \"%s\" contains indexes that are unique.",
1291 : : RelationGetRelationName(parent))));
1292 : : else
1293 : : {
1294 : 4 : index_close(idxRel, AccessShareLock);
1295 : 4 : continue;
1296 : : }
1297 : 0 : }
1298 : :
1299 : 440 : attmap = build_attrmap_by_name(RelationGetDescr(rel),
1300 : 220 : RelationGetDescr(parent),
1301 : : false);
1302 : 220 : idxstmt =
1303 : 440 : generateClonedIndexStmt(NULL, idxRel,
1304 : 220 : attmap, &constraintOid);
1305 : 220 : DefineIndex(NULL,
1306 : 220 : RelationGetRelid(rel),
1307 : 220 : idxstmt,
1308 : : InvalidOid,
1309 : 220 : RelationGetRelid(idxRel),
1310 : 220 : constraintOid,
1311 : : -1,
1312 : : false, false, false, false, false);
1313 : :
1314 : 220 : index_close(idxRel, AccessShareLock);
1315 [ - + + ]: 224 : }
1316 : :
1317 : 1147 : list_free(idxlist);
1318 : :
1319 : : /*
1320 : : * If there are any row-level triggers, clone them to the new
1321 : : * partition.
1322 : : */
1323 [ + + ]: 1147 : if (parent->trigdesc != NULL)
1324 : 71 : CloneRowTriggersToPartition(parent, rel);
1325 : :
1326 : : /*
1327 : : * And foreign keys too. Note that because we're freshly creating the
1328 : : * table, there is no need to verify these new constraints.
1329 : : */
1330 : 1147 : CloneForeignKeyConstraints(NULL, parent, rel);
1331 : :
1332 : 1147 : table_close(parent, NoLock);
1333 : 1147 : }
1334 : :
1335 : : /*
1336 : : * Now add any newly specified CHECK constraints to the new relation. Same
1337 : : * as for defaults above, but these need to come after partitioning is set
1338 : : * up.
1339 : : */
1340 [ + + ]: 5500 : if (stmt->constraints)
1341 : 204 : AddRelationNewConstraints(rel, NIL, stmt->constraints,
1342 : 102 : true, true, false, queryString);
1343 : :
1344 : : /*
1345 : : * Finally, merge the not-null constraints that are declared directly with
1346 : : * those that come from parent relations (making sure to count inheritance
1347 : : * appropriately for each), create them, and set the attnotnull flag on
1348 : : * columns that don't yet have it.
1349 : : */
1350 : 11000 : nncols = AddRelationNotNullConstraints(rel, stmt->nnconstraints,
1351 : 5500 : old_notnulls);
1352 [ + + + + : 12191 : foreach_int(attrnum, nncols)
+ + + + ]
1353 : 6691 : set_attnotnull(NULL, rel, attrnum, true, false);
1354 : :
1355 : 5500 : ObjectAddressSet(address, RelationRelationId, relationId);
1356 : :
1357 : : /*
1358 : : * Clean up. We keep lock on new relation (although it shouldn't be
1359 : : * visible to anyone else anyway, until commit).
1360 : : */
1361 : 5500 : relation_close(rel, NoLock);
1362 : :
1363 : : return address;
1364 : 5500 : }
1365 : :
1366 : : /*
1367 : : * BuildDescForRelation
1368 : : *
1369 : : * Given a list of ColumnDef nodes, build a TupleDesc.
1370 : : *
1371 : : * Note: This is only for the limited purpose of table and view creation. Not
1372 : : * everything is filled in. A real tuple descriptor should be obtained from
1373 : : * the relcache.
1374 : : */
1375 : : TupleDesc
1376 : 5968 : BuildDescForRelation(const List *columns)
1377 : : {
1378 : 5968 : int natts;
1379 : 5968 : AttrNumber attnum;
1380 : 5968 : ListCell *l;
1381 : 5968 : TupleDesc desc;
1382 : 5968 : char *attname;
1383 : 5968 : Oid atttypid;
1384 : 5968 : int32 atttypmod;
1385 : 5968 : Oid attcollation;
1386 : 5968 : int attdim;
1387 : :
1388 : : /*
1389 : : * allocate a new tuple descriptor
1390 : : */
1391 : 5968 : natts = list_length(columns);
1392 : 5968 : desc = CreateTemplateTupleDesc(natts);
1393 : :
1394 : 5968 : attnum = 0;
1395 : :
1396 [ + + + + : 20995 : foreach(l, columns)
+ + ]
1397 : : {
1398 : 15027 : ColumnDef *entry = lfirst(l);
1399 : 15027 : AclResult aclresult;
1400 : 15027 : Form_pg_attribute att;
1401 : :
1402 : : /*
1403 : : * for each entry in the list, get the name and type information from
1404 : : * the list and have TupleDescInitEntry fill in the attribute
1405 : : * information we need.
1406 : : */
1407 : 15027 : attnum++;
1408 : :
1409 : 15027 : attname = entry->colname;
1410 : 15027 : typenameTypeIdAndMod(NULL, entry->typeName, &atttypid, &atttypmod);
1411 : :
1412 : 15027 : aclresult = object_aclcheck(TypeRelationId, atttypid, GetUserId(), ACL_USAGE);
1413 [ + + ]: 15027 : if (aclresult != ACLCHECK_OK)
1414 : 7 : aclcheck_error_type(aclresult, atttypid);
1415 : :
1416 : 15027 : attcollation = GetColumnDefCollation(NULL, entry, atttypid);
1417 : 15027 : attdim = list_length(entry->typeName->arrayBounds);
1418 [ + - ]: 15027 : if (attdim > PG_INT16_MAX)
1419 [ # # # # ]: 0 : ereport(ERROR,
1420 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
1421 : : errmsg("too many array dimensions"));
1422 : :
1423 [ + - ]: 15027 : if (entry->typeName->setof)
1424 [ # # # # ]: 0 : ereport(ERROR,
1425 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
1426 : : errmsg("column \"%s\" cannot be declared SETOF",
1427 : : attname)));
1428 : :
1429 : 30054 : TupleDescInitEntry(desc, attnum, attname,
1430 : 15027 : atttypid, atttypmod, attdim);
1431 : 15027 : att = TupleDescAttr(desc, attnum - 1);
1432 : :
1433 : : /* Override TupleDescInitEntry's settings as requested */
1434 : 15027 : TupleDescInitEntryCollation(desc, attnum, attcollation);
1435 : :
1436 : : /* Fill in additional stuff not handled by TupleDescInitEntry */
1437 : 15027 : att->attnotnull = entry->is_not_null;
1438 : 15027 : att->attislocal = entry->is_local;
1439 : 15027 : att->attinhcount = entry->inhcount;
1440 : 15027 : att->attidentity = entry->identity;
1441 : 15027 : att->attgenerated = entry->generated;
1442 : 15027 : att->attcompression = GetAttributeCompression(att->atttypid, entry->compression);
1443 [ + + ]: 15027 : if (entry->storage)
1444 : 3286 : att->attstorage = entry->storage;
1445 [ + + ]: 11741 : else if (entry->storage_name)
1446 : 10 : att->attstorage = GetAttributeStorage(att->atttypid, entry->storage_name);
1447 : :
1448 : 15027 : populate_compact_attribute(desc, attnum - 1);
1449 : 15027 : }
1450 : :
1451 : 11936 : return desc;
1452 : 5968 : }
1453 : :
1454 : : /*
1455 : : * Emit the right error or warning message for a "DROP" command issued on a
1456 : : * non-existent relation
1457 : : */
1458 : : static void
1459 : 57 : DropErrorMsgNonExistent(RangeVar *rel, char rightkind, bool missing_ok)
1460 : : {
1461 : 57 : const struct dropmsgstrings *rentry;
1462 : :
1463 [ + + + + ]: 57 : if (rel->schemaname != NULL &&
1464 : 20 : !OidIsValid(LookupNamespaceNoError(rel->schemaname)))
1465 : : {
1466 [ + - ]: 7 : if (!missing_ok)
1467 : : {
1468 [ # # # # ]: 0 : ereport(ERROR,
1469 : : (errcode(ERRCODE_UNDEFINED_SCHEMA),
1470 : : errmsg("schema \"%s\" does not exist", rel->schemaname)));
1471 : 0 : }
1472 : : else
1473 : : {
1474 [ - + + - ]: 7 : ereport(NOTICE,
1475 : : (errmsg("schema \"%s\" does not exist, skipping",
1476 : : rel->schemaname)));
1477 : : }
1478 : 7 : return;
1479 : : }
1480 : :
1481 [ - + ]: 103 : for (rentry = dropmsgstringarray; rentry->kind != '\0'; rentry++)
1482 : : {
1483 [ + + ]: 103 : if (rentry->kind == rightkind)
1484 : : {
1485 [ + + ]: 50 : if (!missing_ok)
1486 : : {
1487 [ + - + - ]: 23 : ereport(ERROR,
1488 : : (errcode(rentry->nonexistent_code),
1489 : : errmsg(rentry->nonexistent_msg, rel->relname)));
1490 : 0 : }
1491 : : else
1492 : : {
1493 [ - + + - ]: 27 : ereport(NOTICE, (errmsg(rentry->skipping_msg, rel->relname)));
1494 : 27 : break;
1495 : : }
1496 : 0 : }
1497 : 53 : }
1498 : :
1499 [ - + ]: 27 : Assert(rentry->kind != '\0'); /* Should be impossible */
1500 [ - + ]: 34 : }
1501 : :
1502 : : /*
1503 : : * Emit the right error message for a "DROP" command issued on a
1504 : : * relation of the wrong type
1505 : : */
1506 : : static void
1507 : 0 : DropErrorMsgWrongType(const char *relname, char wrongkind, char rightkind)
1508 : : {
1509 : 0 : const struct dropmsgstrings *rentry;
1510 : 0 : const struct dropmsgstrings *wentry;
1511 : :
1512 [ # # ]: 0 : for (rentry = dropmsgstringarray; rentry->kind != '\0'; rentry++)
1513 [ # # ]: 0 : if (rentry->kind == rightkind)
1514 : 0 : break;
1515 [ # # ]: 0 : Assert(rentry->kind != '\0');
1516 : :
1517 [ # # ]: 0 : for (wentry = dropmsgstringarray; wentry->kind != '\0'; wentry++)
1518 [ # # ]: 0 : if (wentry->kind == wrongkind)
1519 : 0 : break;
1520 : : /* wrongkind could be something we don't have in our table... */
1521 : :
1522 [ # # # # : 0 : ereport(ERROR,
# # ]
1523 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
1524 : : errmsg(rentry->nota_msg, relname),
1525 : : (wentry->kind != '\0') ? errhint("%s", _(wentry->drophint_msg)) : 0));
1526 : 0 : }
1527 : :
1528 : : /*
1529 : : * RemoveRelations
1530 : : * Implements DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW,
1531 : : * DROP MATERIALIZED VIEW, DROP FOREIGN TABLE
1532 : : */
1533 : : void
1534 : 1932 : RemoveRelations(DropStmt *drop)
1535 : : {
1536 : 1932 : ObjectAddresses *objects;
1537 : 1932 : char relkind;
1538 : 1932 : ListCell *cell;
1539 : 1932 : int flags = 0;
1540 : 1932 : LOCKMODE lockmode = AccessExclusiveLock;
1541 : :
1542 : : /* DROP CONCURRENTLY uses a weaker lock, and has some restrictions */
1543 [ + + ]: 1932 : if (drop->concurrent)
1544 : : {
1545 : : /*
1546 : : * Note that for temporary relations this lock may get upgraded later
1547 : : * on, but as no other session can access a temporary relation, this
1548 : : * is actually fine.
1549 : : */
1550 : 13 : lockmode = ShareUpdateExclusiveLock;
1551 [ + - ]: 13 : Assert(drop->removeType == OBJECT_INDEX);
1552 [ + + ]: 13 : if (list_length(drop->objects) != 1)
1553 [ + - + - ]: 1 : ereport(ERROR,
1554 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1555 : : errmsg("DROP INDEX CONCURRENTLY does not support dropping multiple objects")));
1556 [ + - ]: 12 : if (drop->behavior == DROP_CASCADE)
1557 [ # # # # ]: 0 : ereport(ERROR,
1558 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1559 : : errmsg("DROP INDEX CONCURRENTLY does not support CASCADE")));
1560 : 12 : }
1561 : :
1562 : : /*
1563 : : * First we identify all the relations, then we delete them in a single
1564 : : * performMultipleDeletions() call. This is to avoid unwanted DROP
1565 : : * RESTRICT errors if one of the relations depends on another.
1566 : : */
1567 : :
1568 : : /* Determine required relkind */
1569 [ + + + + : 1931 : switch (drop->removeType)
+ + - ]
1570 : : {
1571 : : case OBJECT_TABLE:
1572 : 1612 : relkind = RELKIND_RELATION;
1573 : 1612 : break;
1574 : :
1575 : : case OBJECT_INDEX:
1576 : 114 : relkind = RELKIND_INDEX;
1577 : 114 : break;
1578 : :
1579 : : case OBJECT_SEQUENCE:
1580 : 26 : relkind = RELKIND_SEQUENCE;
1581 : 26 : break;
1582 : :
1583 : : case OBJECT_VIEW:
1584 : 145 : relkind = RELKIND_VIEW;
1585 : 145 : break;
1586 : :
1587 : : case OBJECT_MATVIEW:
1588 : 16 : relkind = RELKIND_MATVIEW;
1589 : 16 : break;
1590 : :
1591 : : case OBJECT_FOREIGN_TABLE:
1592 : 18 : relkind = RELKIND_FOREIGN_TABLE;
1593 : 18 : break;
1594 : :
1595 : : default:
1596 [ # # # # ]: 0 : elog(ERROR, "unrecognized drop object type: %d",
1597 : : (int) drop->removeType);
1598 : 0 : relkind = 0; /* keep compiler quiet */
1599 : 0 : break;
1600 : : }
1601 : :
1602 : : /* Lock and validate each relation; build a list of object addresses */
1603 : 1931 : objects = new_object_addresses();
1604 : :
1605 [ + + + + : 4173 : foreach(cell, drop->objects)
+ + ]
1606 : : {
1607 : 2243 : RangeVar *rel = makeRangeVarFromNameList((List *) lfirst(cell));
1608 : 2243 : Oid relOid;
1609 : 2243 : ObjectAddress obj;
1610 : 2243 : struct DropRelationCallbackState state;
1611 : :
1612 : : /*
1613 : : * These next few steps are a great deal like relation_openrv, but we
1614 : : * don't bother building a relcache entry since we don't need it.
1615 : : *
1616 : : * Check for shared-cache-inval messages before trying to access the
1617 : : * relation. This is needed to cover the case where the name
1618 : : * identifies a rel that has been dropped and recreated since the
1619 : : * start of our transaction: if we don't flush the old syscache entry,
1620 : : * then we'll latch onto that entry and suffer an error later.
1621 : : */
1622 : 2243 : AcceptInvalidationMessages();
1623 : :
1624 : : /* Look up the appropriate relation using namespace search. */
1625 : 2243 : state.expected_relkind = relkind;
1626 : 2243 : state.heap_lockmode = drop->concurrent ?
1627 : : ShareUpdateExclusiveLock : AccessExclusiveLock;
1628 : : /* We must initialize these fields to show that no locks are held: */
1629 : 2243 : state.heapOid = InvalidOid;
1630 : 2243 : state.partParentOid = InvalidOid;
1631 : :
1632 : 2243 : relOid = RangeVarGetRelidExtended(rel, lockmode, RVR_MISSING_OK,
1633 : : RangeVarCallbackForDropRelation,
1634 : : &state);
1635 : :
1636 : : /* Not there? */
1637 [ + + ]: 2243 : if (!OidIsValid(relOid))
1638 : : {
1639 : 57 : DropErrorMsgNonExistent(rel, relkind, drop->missing_ok);
1640 : 57 : continue;
1641 : : }
1642 : :
1643 : : /*
1644 : : * Decide if concurrent mode needs to be used here or not. The
1645 : : * callback retrieved the rel's persistence for us.
1646 : : */
1647 [ + + + + ]: 2186 : if (drop->concurrent &&
1648 : 11 : state.actual_relpersistence != RELPERSISTENCE_TEMP)
1649 : : {
1650 [ + - ]: 8 : Assert(list_length(drop->objects) == 1 &&
1651 : : drop->removeType == OBJECT_INDEX);
1652 : 8 : flags |= PERFORM_DELETION_CONCURRENTLY;
1653 : 8 : }
1654 : :
1655 : : /*
1656 : : * Concurrent index drop cannot be used with partitioned indexes,
1657 : : * either.
1658 : : */
1659 [ + + + + ]: 2186 : if ((flags & PERFORM_DELETION_CONCURRENTLY) != 0 &&
1660 : 8 : state.actual_relkind == RELKIND_PARTITIONED_INDEX)
1661 [ + - + - ]: 1 : ereport(ERROR,
1662 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1663 : : errmsg("cannot drop partitioned index \"%s\" concurrently",
1664 : : rel->relname)));
1665 : :
1666 : : /*
1667 : : * If we're told to drop a partitioned index, we must acquire lock on
1668 : : * all the children of its parent partitioned table before proceeding.
1669 : : * Otherwise we'd try to lock the child index partitions before their
1670 : : * tables, leading to potential deadlock against other sessions that
1671 : : * will lock those objects in the other order.
1672 : : */
1673 [ + + ]: 2185 : if (state.actual_relkind == RELKIND_PARTITIONED_INDEX)
1674 : 24 : (void) find_all_inheritors(state.heapOid,
1675 : 12 : state.heap_lockmode,
1676 : : NULL);
1677 : :
1678 : : /* OK, we're ready to delete this one */
1679 : 2185 : obj.classId = RelationRelationId;
1680 : 2185 : obj.objectId = relOid;
1681 : 2185 : obj.objectSubId = 0;
1682 : :
1683 : 2185 : add_exact_object_address(&obj, objects);
1684 [ - + + ]: 2242 : }
1685 : :
1686 : 1930 : performMultipleDeletions(objects, drop->behavior, flags);
1687 : :
1688 : 1930 : free_object_addresses(objects);
1689 : 1930 : }
1690 : :
1691 : : /*
1692 : : * Before acquiring a table lock, check whether we have sufficient rights.
1693 : : * In the case of DROP INDEX, also try to lock the table before the index.
1694 : : * Also, if the table to be dropped is a partition, we try to lock the parent
1695 : : * first.
1696 : : */
1697 : : static void
1698 : 2302 : RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
1699 : : void *arg)
1700 : : {
1701 : 2302 : HeapTuple tuple;
1702 : 2302 : struct DropRelationCallbackState *state;
1703 : 2302 : char expected_relkind;
1704 : 2302 : bool is_partition;
1705 : 2302 : Form_pg_class classform;
1706 : 2302 : LOCKMODE heap_lockmode;
1707 : 2302 : bool invalid_system_index = false;
1708 : :
1709 : 2302 : state = (struct DropRelationCallbackState *) arg;
1710 : 2302 : heap_lockmode = state->heap_lockmode;
1711 : :
1712 : : /*
1713 : : * If we previously locked some other index's heap, and the name we're
1714 : : * looking up no longer refers to that relation, release the now-useless
1715 : : * lock.
1716 : : */
1717 [ + + + - ]: 2302 : if (relOid != oldRelOid && OidIsValid(state->heapOid))
1718 : : {
1719 : 0 : UnlockRelationOid(state->heapOid, heap_lockmode);
1720 : 0 : state->heapOid = InvalidOid;
1721 : 0 : }
1722 : :
1723 : : /*
1724 : : * Similarly, if we previously locked some other partition's heap, and the
1725 : : * name we're looking up no longer refers to that relation, release the
1726 : : * now-useless lock.
1727 : : */
1728 [ + + + - ]: 2302 : if (relOid != oldRelOid && OidIsValid(state->partParentOid))
1729 : : {
1730 : 0 : UnlockRelationOid(state->partParentOid, AccessExclusiveLock);
1731 : 0 : state->partParentOid = InvalidOid;
1732 : 0 : }
1733 : :
1734 : : /* Didn't find a relation, so no need for locking or permission checks. */
1735 [ + + ]: 2302 : if (!OidIsValid(relOid))
1736 : 58 : return;
1737 : :
1738 : 2244 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
1739 [ + - ]: 2244 : if (!HeapTupleIsValid(tuple))
1740 : 0 : return; /* concurrently dropped, so nothing to do */
1741 : 2244 : classform = (Form_pg_class) GETSTRUCT(tuple);
1742 : 2244 : is_partition = classform->relispartition;
1743 : :
1744 : : /* Pass back some data to save lookups in RemoveRelations */
1745 : 2244 : state->actual_relkind = classform->relkind;
1746 : 2244 : state->actual_relpersistence = classform->relpersistence;
1747 : :
1748 : : /*
1749 : : * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
1750 : : * but RemoveRelations() can only pass one relkind for a given relation.
1751 : : * It chooses RELKIND_RELATION for both regular and partitioned tables.
1752 : : * That means we must be careful before giving the wrong type error when
1753 : : * the relation is RELKIND_PARTITIONED_TABLE. An equivalent problem
1754 : : * exists with indexes.
1755 : : */
1756 [ + + ]: 2244 : if (classform->relkind == RELKIND_PARTITIONED_TABLE)
1757 : 424 : expected_relkind = RELKIND_RELATION;
1758 [ + + ]: 1820 : else if (classform->relkind == RELKIND_PARTITIONED_INDEX)
1759 : 13 : expected_relkind = RELKIND_INDEX;
1760 : : else
1761 : 1807 : expected_relkind = classform->relkind;
1762 : :
1763 [ + - ]: 2244 : if (state->expected_relkind != expected_relkind)
1764 : 0 : DropErrorMsgWrongType(rel->relname, classform->relkind,
1765 : 0 : state->expected_relkind);
1766 : :
1767 : : /* Allow DROP to either table owner or schema owner */
1768 [ + + - + ]: 2244 : if (!object_ownercheck(RelationRelationId, relOid, GetUserId()) &&
1769 : 3 : !object_ownercheck(NamespaceRelationId, classform->relnamespace, GetUserId()))
1770 : 3 : aclcheck_error(ACLCHECK_NOT_OWNER,
1771 : 3 : get_relkind_objtype(classform->relkind),
1772 : 3 : rel->relname);
1773 : :
1774 : : /*
1775 : : * Check the case of a system index that might have been invalidated by a
1776 : : * failed concurrent process and allow its drop. For the time being, this
1777 : : * only concerns indexes of toast relations that became invalid during a
1778 : : * REINDEX CONCURRENTLY process.
1779 : : */
1780 [ - + # # ]: 2244 : if (IsSystemClass(relOid, classform) && classform->relkind == RELKIND_INDEX)
1781 : : {
1782 : 0 : HeapTuple locTuple;
1783 : 0 : Form_pg_index indexform;
1784 : 0 : bool indisvalid;
1785 : :
1786 : 0 : locTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(relOid));
1787 [ # # ]: 0 : if (!HeapTupleIsValid(locTuple))
1788 : : {
1789 : 0 : ReleaseSysCache(tuple);
1790 : 0 : return;
1791 : : }
1792 : :
1793 : 0 : indexform = (Form_pg_index) GETSTRUCT(locTuple);
1794 : 0 : indisvalid = indexform->indisvalid;
1795 : 0 : ReleaseSysCache(locTuple);
1796 : :
1797 : : /* Mark object as being an invalid index of system catalogs */
1798 [ # # ]: 0 : if (!indisvalid)
1799 : 0 : invalid_system_index = true;
1800 [ # # ]: 0 : }
1801 : :
1802 : : /* In the case of an invalid index, it is fine to bypass this check */
1803 [ + + + - : 2244 : if (!invalid_system_index && !allowSystemTableMods && IsSystemClass(relOid, classform))
+ - ]
1804 [ # # # # ]: 0 : ereport(ERROR,
1805 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1806 : : errmsg("permission denied: \"%s\" is a system catalog",
1807 : : rel->relname)));
1808 : :
1809 : 2244 : ReleaseSysCache(tuple);
1810 : :
1811 : : /*
1812 : : * In DROP INDEX, attempt to acquire lock on the parent table before
1813 : : * locking the index. index_drop() will need this anyway, and since
1814 : : * regular queries lock tables before their indexes, we risk deadlock if
1815 : : * we do it the other way around. No error if we don't find a pg_index
1816 : : * entry, though --- the relation may have been dropped. Note that this
1817 : : * code will execute for either plain or partitioned indexes.
1818 : : */
1819 [ + + + + ]: 2244 : if (expected_relkind == RELKIND_INDEX &&
1820 : 117 : relOid != oldRelOid)
1821 : : {
1822 : 112 : state->heapOid = IndexGetRelation(relOid, true);
1823 [ - + ]: 112 : if (OidIsValid(state->heapOid))
1824 : 112 : LockRelationOid(state->heapOid, heap_lockmode);
1825 : 112 : }
1826 : :
1827 : : /*
1828 : : * Similarly, if the relation is a partition, we must acquire lock on its
1829 : : * parent before locking the partition. That's because queries lock the
1830 : : * parent before its partitions, so we risk deadlock if we do it the other
1831 : : * way around.
1832 : : */
1833 [ + + + + ]: 2244 : if (is_partition && relOid != oldRelOid)
1834 : : {
1835 : 79 : state->partParentOid = get_partition_parent(relOid, true);
1836 [ - + ]: 79 : if (OidIsValid(state->partParentOid))
1837 : 79 : LockRelationOid(state->partParentOid, AccessExclusiveLock);
1838 : 79 : }
1839 [ - + ]: 2302 : }
1840 : :
1841 : : /*
1842 : : * ExecuteTruncate
1843 : : * Executes a TRUNCATE command.
1844 : : *
1845 : : * This is a multi-relation truncate. We first open and grab exclusive
1846 : : * lock on all relations involved, checking permissions and otherwise
1847 : : * verifying that the relation is OK for truncation. Note that if relations
1848 : : * are foreign tables, at this stage, we have not yet checked that their
1849 : : * foreign data in external data sources are OK for truncation. These are
1850 : : * checked when foreign data are actually truncated later. In CASCADE mode,
1851 : : * relations having FK references to the targeted relations are automatically
1852 : : * added to the group; in RESTRICT mode, we check that all FK references are
1853 : : * internal to the group that's being truncated. Finally all the relations
1854 : : * are truncated and reindexed.
1855 : : */
1856 : : void
1857 : 208 : ExecuteTruncate(TruncateStmt *stmt)
1858 : : {
1859 : 208 : List *rels = NIL;
1860 : 208 : List *relids = NIL;
1861 : 208 : List *relids_logged = NIL;
1862 : 208 : ListCell *cell;
1863 : :
1864 : : /*
1865 : : * Open, exclusive-lock, and check all the explicitly-specified relations
1866 : : */
1867 [ + + + + : 442 : foreach(cell, stmt->relations)
+ + ]
1868 : : {
1869 : 236 : RangeVar *rv = lfirst(cell);
1870 : 236 : Relation rel;
1871 : 236 : bool recurse = rv->inh;
1872 : 236 : Oid myrelid;
1873 : 236 : LOCKMODE lockmode = AccessExclusiveLock;
1874 : :
1875 : 236 : myrelid = RangeVarGetRelidExtended(rv, lockmode,
1876 : : 0, RangeVarCallbackForTruncate,
1877 : : NULL);
1878 : :
1879 : : /* don't throw error for "TRUNCATE foo, foo" */
1880 [ - + ]: 236 : if (list_member_oid(relids, myrelid))
1881 : 0 : continue;
1882 : :
1883 : : /* open the relation, we already hold a lock on it */
1884 : 236 : rel = table_open(myrelid, NoLock);
1885 : :
1886 : : /*
1887 : : * RangeVarGetRelidExtended() has done most checks with its callback,
1888 : : * but other checks with the now-opened Relation remain.
1889 : : */
1890 : 236 : truncate_check_activity(rel);
1891 : :
1892 : 236 : rels = lappend(rels, rel);
1893 : 236 : relids = lappend_oid(relids, myrelid);
1894 : :
1895 : : /* Log this relation only if needed for logical decoding */
1896 [ + - - + : 236 : if (RelationIsLogicallyLogged(rel))
# # # # #
# # # ]
1897 : 0 : relids_logged = lappend_oid(relids_logged, myrelid);
1898 : :
1899 [ + + ]: 236 : if (recurse)
1900 : : {
1901 : 230 : ListCell *child;
1902 : 230 : List *children;
1903 : :
1904 : 230 : children = find_all_inheritors(myrelid, lockmode, NULL);
1905 : :
1906 [ + - + + : 749 : foreach(child, children)
+ + ]
1907 : : {
1908 : 519 : Oid childrelid = lfirst_oid(child);
1909 : :
1910 [ + + ]: 519 : if (list_member_oid(relids, childrelid))
1911 : 230 : continue;
1912 : :
1913 : : /* find_all_inheritors already got lock */
1914 : 289 : rel = table_open(childrelid, NoLock);
1915 : :
1916 : : /*
1917 : : * It is possible that the parent table has children that are
1918 : : * temp tables of other backends. We cannot safely access
1919 : : * such tables (because of buffering issues), and the best
1920 : : * thing to do is to silently ignore them. Note that this
1921 : : * check is the same as one of the checks done in
1922 : : * truncate_check_activity() called below, still it is kept
1923 : : * here for simplicity.
1924 : : */
1925 [ - + # # ]: 289 : if (RELATION_IS_OTHER_TEMP(rel))
1926 : : {
1927 : 0 : table_close(rel, lockmode);
1928 : 0 : continue;
1929 : : }
1930 : :
1931 : : /*
1932 : : * Inherited TRUNCATE commands perform access permission
1933 : : * checks on the parent table only. So we skip checking the
1934 : : * children's permissions and don't call
1935 : : * truncate_check_perms() here.
1936 : : */
1937 : 289 : truncate_check_rel(RelationGetRelid(rel), rel->rd_rel);
1938 : 289 : truncate_check_activity(rel);
1939 : :
1940 : 289 : rels = lappend(rels, rel);
1941 : 289 : relids = lappend_oid(relids, childrelid);
1942 : :
1943 : : /* Log this relation only if needed for logical decoding */
1944 [ + - - + : 289 : if (RelationIsLogicallyLogged(rel))
# # # # #
# # # ]
1945 : 0 : relids_logged = lappend_oid(relids_logged, childrelid);
1946 [ + + ]: 519 : }
1947 : 230 : }
1948 [ + + ]: 6 : else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
1949 [ + - + - ]: 2 : ereport(ERROR,
1950 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
1951 : : errmsg("cannot truncate only a partitioned table"),
1952 : : errhint("Do not specify the ONLY keyword, or use TRUNCATE ONLY on the partitions directly.")));
1953 [ - + ]: 234 : }
1954 : :
1955 : 392 : ExecuteTruncateGuts(rels, relids, relids_logged,
1956 : 196 : stmt->behavior, stmt->restart_seqs, false);
1957 : :
1958 : : /* And close the rels */
1959 [ + - + + : 683 : foreach(cell, rels)
+ + ]
1960 : : {
1961 : 487 : Relation rel = (Relation) lfirst(cell);
1962 : :
1963 : 487 : table_close(rel, NoLock);
1964 : 487 : }
1965 : 196 : }
1966 : :
1967 : : /*
1968 : : * ExecuteTruncateGuts
1969 : : *
1970 : : * Internal implementation of TRUNCATE. This is called by the actual TRUNCATE
1971 : : * command (see above) as well as replication subscribers that execute a
1972 : : * replicated TRUNCATE action.
1973 : : *
1974 : : * explicit_rels is the list of Relations to truncate that the command
1975 : : * specified. relids is the list of Oids corresponding to explicit_rels.
1976 : : * relids_logged is the list of Oids (a subset of relids) that require
1977 : : * WAL-logging. This is all a bit redundant, but the existing callers have
1978 : : * this information handy in this form.
1979 : : */
1980 : : void
1981 : 184 : ExecuteTruncateGuts(List *explicit_rels,
1982 : : List *relids,
1983 : : List *relids_logged,
1984 : : DropBehavior behavior, bool restart_seqs,
1985 : : bool run_as_table_owner)
1986 : : {
1987 : 184 : List *rels;
1988 : 184 : List *seq_relids = NIL;
1989 : 184 : HTAB *ft_htab = NULL;
1990 : 184 : EState *estate;
1991 : 184 : ResultRelInfo *resultRelInfos;
1992 : 184 : ResultRelInfo *resultRelInfo;
1993 : 184 : SubTransactionId mySubid;
1994 : 184 : ListCell *cell;
1995 : 184 : Oid *logrelids;
1996 : :
1997 : : /*
1998 : : * Check the explicitly-specified relations.
1999 : : *
2000 : : * In CASCADE mode, suck in all referencing relations as well. This
2001 : : * requires multiple iterations to find indirectly-dependent relations. At
2002 : : * each phase, we need to exclusive-lock new rels before looking for their
2003 : : * dependencies, else we might miss something. Also, we check each rel as
2004 : : * soon as we open it, to avoid a faux pas such as holding lock for a long
2005 : : * time on a rel we have no permissions for.
2006 : : */
2007 : 184 : rels = list_copy(explicit_rels);
2008 [ + + ]: 184 : if (behavior == DROP_CASCADE)
2009 : : {
2010 : 11 : for (;;)
2011 : : {
2012 : 11 : List *newrelids;
2013 : :
2014 : 11 : newrelids = heap_truncate_find_FKs(relids);
2015 [ + + ]: 11 : if (newrelids == NIL)
2016 : 5 : break; /* nothing else to add */
2017 : :
2018 [ + - + + : 21 : foreach(cell, newrelids)
+ + ]
2019 : : {
2020 : 15 : Oid relid = lfirst_oid(cell);
2021 : 15 : Relation rel;
2022 : :
2023 : 15 : rel = table_open(relid, AccessExclusiveLock);
2024 [ - + + - ]: 15 : ereport(NOTICE,
2025 : : (errmsg("truncate cascades to table \"%s\"",
2026 : : RelationGetRelationName(rel))));
2027 : 15 : truncate_check_rel(relid, rel->rd_rel);
2028 : 15 : truncate_check_perms(relid, rel->rd_rel);
2029 : 15 : truncate_check_activity(rel);
2030 : 15 : rels = lappend(rels, rel);
2031 : 15 : relids = lappend_oid(relids, relid);
2032 : :
2033 : : /* Log this relation only if needed for logical decoding */
2034 [ + - - + : 15 : if (RelationIsLogicallyLogged(rel))
# # # # #
# # # ]
2035 : 0 : relids_logged = lappend_oid(relids_logged, relid);
2036 : 15 : }
2037 [ + + ]: 11 : }
2038 : 5 : }
2039 : :
2040 : : /*
2041 : : * Check foreign key references. In CASCADE mode, this should be
2042 : : * unnecessary since we just pulled in all the references; but as a
2043 : : * cross-check, do it anyway if in an Assert-enabled build.
2044 : : */
2045 : : #ifdef USE_ASSERT_CHECKING
2046 : 184 : heap_truncate_check_FKs(rels, false);
2047 : : #else
2048 : : if (behavior == DROP_RESTRICT)
2049 : : heap_truncate_check_FKs(rels, false);
2050 : : #endif
2051 : :
2052 : : /*
2053 : : * If we are asked to restart sequences, find all the sequences, lock them
2054 : : * (we need AccessExclusiveLock for ResetSequence), and check permissions.
2055 : : * We want to do this early since it's pointless to do all the truncation
2056 : : * work only to fail on sequence permissions.
2057 : : */
2058 [ + + ]: 184 : if (restart_seqs)
2059 : : {
2060 [ + - + + : 6 : foreach(cell, rels)
+ + ]
2061 : : {
2062 : 3 : Relation rel = (Relation) lfirst(cell);
2063 : 3 : List *seqlist = getOwnedSequences(RelationGetRelid(rel));
2064 : 3 : ListCell *seqcell;
2065 : :
2066 [ + - + + : 8 : foreach(seqcell, seqlist)
+ + ]
2067 : : {
2068 : 5 : Oid seq_relid = lfirst_oid(seqcell);
2069 : 5 : Relation seq_rel;
2070 : :
2071 : 5 : seq_rel = relation_open(seq_relid, AccessExclusiveLock);
2072 : :
2073 : : /* This check must match AlterSequence! */
2074 [ + - ]: 5 : if (!object_ownercheck(RelationRelationId, seq_relid, GetUserId()))
2075 : 0 : aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SEQUENCE,
2076 : 0 : RelationGetRelationName(seq_rel));
2077 : :
2078 : 5 : seq_relids = lappend_oid(seq_relids, seq_relid);
2079 : :
2080 : 5 : relation_close(seq_rel, NoLock);
2081 : 5 : }
2082 : 3 : }
2083 : 3 : }
2084 : :
2085 : : /* Prepare to catch AFTER triggers. */
2086 : 184 : AfterTriggerBeginQuery();
2087 : :
2088 : : /*
2089 : : * To fire triggers, we'll need an EState as well as a ResultRelInfo for
2090 : : * each relation. We don't need to call ExecOpenIndices, though.
2091 : : *
2092 : : * We put the ResultRelInfos in the es_opened_result_relations list, even
2093 : : * though we don't have a range table and don't populate the
2094 : : * es_result_relations array. That's a bit bogus, but it's enough to make
2095 : : * ExecGetTriggerResultRel() find them.
2096 : : */
2097 : 184 : estate = CreateExecutorState();
2098 : 184 : resultRelInfos = (ResultRelInfo *)
2099 : 184 : palloc(list_length(rels) * sizeof(ResultRelInfo));
2100 : 184 : resultRelInfo = resultRelInfos;
2101 [ + - + + : 698 : foreach(cell, rels)
+ + ]
2102 : : {
2103 : 514 : Relation rel = (Relation) lfirst(cell);
2104 : :
2105 : 1028 : InitResultRelInfo(resultRelInfo,
2106 : 514 : rel,
2107 : : 0, /* dummy rangetable index */
2108 : : NULL,
2109 : : 0);
2110 : 514 : estate->es_opened_result_relations =
2111 : 514 : lappend(estate->es_opened_result_relations, resultRelInfo);
2112 : 514 : resultRelInfo++;
2113 : 514 : }
2114 : :
2115 : : /*
2116 : : * Process all BEFORE STATEMENT TRUNCATE triggers before we begin
2117 : : * truncating (this is because one of them might throw an error). Also, if
2118 : : * we were to allow them to prevent statement execution, that would need
2119 : : * to be handled here.
2120 : : */
2121 : 184 : resultRelInfo = resultRelInfos;
2122 [ + - + + : 698 : foreach(cell, rels)
+ + ]
2123 : : {
2124 : 514 : UserContext ucxt;
2125 : :
2126 [ - + ]: 514 : if (run_as_table_owner)
2127 : 0 : SwitchToUntrustedUser(resultRelInfo->ri_RelationDesc->rd_rel->relowner,
2128 : : &ucxt);
2129 : 514 : ExecBSTruncateTriggers(estate, resultRelInfo);
2130 [ - + ]: 514 : if (run_as_table_owner)
2131 : 0 : RestoreUserContext(&ucxt);
2132 : 514 : resultRelInfo++;
2133 : 514 : }
2134 : :
2135 : : /*
2136 : : * OK, truncate each table.
2137 : : */
2138 : 184 : mySubid = GetCurrentSubTransactionId();
2139 : :
2140 [ + - + + : 698 : foreach(cell, rels)
+ + ]
2141 : : {
2142 : 514 : Relation rel = (Relation) lfirst(cell);
2143 : :
2144 : : /* Skip partitioned tables as there is nothing to do */
2145 [ + + ]: 514 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
2146 : 112 : continue;
2147 : :
2148 : : /*
2149 : : * Build the lists of foreign tables belonging to each foreign server
2150 : : * and pass each list to the foreign data wrapper's callback function,
2151 : : * so that each server can truncate its all foreign tables in bulk.
2152 : : * Each list is saved as a single entry in a hash table that uses the
2153 : : * server OID as lookup key.
2154 : : */
2155 [ - + ]: 402 : if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
2156 : : {
2157 : 0 : Oid serverid = GetForeignServerIdByRelId(RelationGetRelid(rel));
2158 : 0 : bool found;
2159 : 0 : ForeignTruncateInfo *ft_info;
2160 : :
2161 : : /* First time through, initialize hashtable for foreign tables */
2162 [ # # ]: 0 : if (!ft_htab)
2163 : : {
2164 : 0 : HASHCTL hctl;
2165 : :
2166 : 0 : memset(&hctl, 0, sizeof(HASHCTL));
2167 : 0 : hctl.keysize = sizeof(Oid);
2168 : 0 : hctl.entrysize = sizeof(ForeignTruncateInfo);
2169 : 0 : hctl.hcxt = CurrentMemoryContext;
2170 : :
2171 : 0 : ft_htab = hash_create("TRUNCATE for Foreign Tables",
2172 : : 32, /* start small and extend */
2173 : : &hctl,
2174 : : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
2175 : 0 : }
2176 : :
2177 : : /* Find or create cached entry for the foreign table */
2178 : 0 : ft_info = hash_search(ft_htab, &serverid, HASH_ENTER, &found);
2179 [ # # ]: 0 : if (!found)
2180 : 0 : ft_info->rels = NIL;
2181 : :
2182 : : /*
2183 : : * Save the foreign table in the entry of the server that the
2184 : : * foreign table belongs to.
2185 : : */
2186 : 0 : ft_info->rels = lappend(ft_info->rels, rel);
2187 : : continue;
2188 : 0 : }
2189 : :
2190 : : /*
2191 : : * Normally, we need a transaction-safe truncation here. However, if
2192 : : * the table was either created in the current (sub)transaction or has
2193 : : * a new relfilenumber in the current (sub)transaction, then we can
2194 : : * just truncate it in-place, because a rollback would cause the whole
2195 : : * table or the current physical file to be thrown away anyway.
2196 : : */
2197 [ + + - + ]: 402 : if (rel->rd_createSubid == mySubid ||
2198 : 401 : rel->rd_newRelfilelocatorSubid == mySubid)
2199 : : {
2200 : : /* Immediate, non-rollbackable truncation is OK */
2201 : 1 : heap_truncate_one_rel(rel);
2202 : 1 : }
2203 : : else
2204 : : {
2205 : 401 : Oid heap_relid;
2206 : 401 : Oid toast_relid;
2207 : 401 : ReindexParams reindex_params = {0};
2208 : :
2209 : : /*
2210 : : * This effectively deletes all rows in the table, and may be done
2211 : : * in a serializable transaction. In that case we must record a
2212 : : * rw-conflict in to this transaction from each transaction
2213 : : * holding a predicate lock on the table.
2214 : : */
2215 : 401 : CheckTableForSerializableConflictIn(rel);
2216 : :
2217 : : /*
2218 : : * Need the full transaction-safe pushups.
2219 : : *
2220 : : * Create a new empty storage file for the relation, and assign it
2221 : : * as the relfilenumber value. The old storage file is scheduled
2222 : : * for deletion at commit.
2223 : : */
2224 : 401 : RelationSetNewRelfilenumber(rel, rel->rd_rel->relpersistence);
2225 : :
2226 : 401 : heap_relid = RelationGetRelid(rel);
2227 : :
2228 : : /*
2229 : : * The same for the toast table, if any.
2230 : : */
2231 : 401 : toast_relid = rel->rd_rel->reltoastrelid;
2232 [ + + ]: 401 : if (OidIsValid(toast_relid))
2233 : : {
2234 : 271 : Relation toastrel = relation_open(toast_relid,
2235 : : AccessExclusiveLock);
2236 : :
2237 : 542 : RelationSetNewRelfilenumber(toastrel,
2238 : 271 : toastrel->rd_rel->relpersistence);
2239 : 271 : table_close(toastrel, NoLock);
2240 : 271 : }
2241 : :
2242 : : /*
2243 : : * Reconstruct the indexes to match, and we're done.
2244 : : */
2245 : 401 : reindex_relation(NULL, heap_relid, REINDEX_REL_PROCESS_TOAST,
2246 : : &reindex_params);
2247 : 401 : }
2248 : :
2249 : 402 : pgstat_count_truncate(rel);
2250 [ + + ]: 514 : }
2251 : :
2252 : : /* Now go through the hash table, and truncate foreign tables */
2253 [ + - ]: 184 : if (ft_htab)
2254 : : {
2255 : 0 : ForeignTruncateInfo *ft_info;
2256 : 0 : HASH_SEQ_STATUS seq;
2257 : :
2258 : 0 : hash_seq_init(&seq, ft_htab);
2259 : :
2260 [ # # ]: 0 : PG_TRY();
2261 : : {
2262 [ # # ]: 0 : while ((ft_info = hash_seq_search(&seq)) != NULL)
2263 : : {
2264 : 0 : FdwRoutine *routine = GetFdwRoutineByServerId(ft_info->serverid);
2265 : :
2266 : : /* truncate_check_rel() has checked that already */
2267 [ # # ]: 0 : Assert(routine->ExecForeignTruncate != NULL);
2268 : :
2269 : 0 : routine->ExecForeignTruncate(ft_info->rels,
2270 : 0 : behavior,
2271 : 0 : restart_seqs);
2272 : 0 : }
2273 : : }
2274 : 0 : PG_FINALLY();
2275 : : {
2276 : 0 : hash_destroy(ft_htab);
2277 : : }
2278 [ # # ]: 0 : PG_END_TRY();
2279 : 0 : }
2280 : :
2281 : : /*
2282 : : * Restart owned sequences if we were asked to.
2283 : : */
2284 [ + + + + : 189 : foreach(cell, seq_relids)
+ + ]
2285 : : {
2286 : 5 : Oid seq_relid = lfirst_oid(cell);
2287 : :
2288 : 5 : ResetSequence(seq_relid);
2289 : 5 : }
2290 : :
2291 : : /*
2292 : : * Write a WAL record to allow this set of actions to be logically
2293 : : * decoded.
2294 : : *
2295 : : * Assemble an array of relids so we can write a single WAL record for the
2296 : : * whole action.
2297 : : */
2298 [ + - ]: 184 : if (relids_logged != NIL)
2299 : : {
2300 : 0 : xl_heap_truncate xlrec;
2301 : 0 : int i = 0;
2302 : :
2303 : : /* should only get here if effective_wal_level is 'logical' */
2304 [ # # # # ]: 0 : Assert(XLogLogicalInfoActive());
2305 : :
2306 : 0 : logrelids = palloc(list_length(relids_logged) * sizeof(Oid));
2307 [ # # # # : 0 : foreach(cell, relids_logged)
# # ]
2308 : 0 : logrelids[i++] = lfirst_oid(cell);
2309 : :
2310 : 0 : xlrec.dbId = MyDatabaseId;
2311 : 0 : xlrec.nrelids = list_length(relids_logged);
2312 : 0 : xlrec.flags = 0;
2313 [ # # ]: 0 : if (behavior == DROP_CASCADE)
2314 : 0 : xlrec.flags |= XLH_TRUNCATE_CASCADE;
2315 [ # # ]: 0 : if (restart_seqs)
2316 : 0 : xlrec.flags |= XLH_TRUNCATE_RESTART_SEQS;
2317 : :
2318 : 0 : XLogBeginInsert();
2319 : 0 : XLogRegisterData(&xlrec, SizeOfHeapTruncate);
2320 : 0 : XLogRegisterData(logrelids, list_length(relids_logged) * sizeof(Oid));
2321 : :
2322 : 0 : XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
2323 : :
2324 : 0 : (void) XLogInsert(RM_HEAP_ID, XLOG_HEAP_TRUNCATE);
2325 : 0 : }
2326 : :
2327 : : /*
2328 : : * Process all AFTER STATEMENT TRUNCATE triggers.
2329 : : */
2330 : 184 : resultRelInfo = resultRelInfos;
2331 [ + - + + : 698 : foreach(cell, rels)
+ + ]
2332 : : {
2333 : 514 : UserContext ucxt;
2334 : :
2335 [ + - ]: 514 : if (run_as_table_owner)
2336 : 0 : SwitchToUntrustedUser(resultRelInfo->ri_RelationDesc->rd_rel->relowner,
2337 : : &ucxt);
2338 : 514 : ExecASTruncateTriggers(estate, resultRelInfo);
2339 [ + - ]: 514 : if (run_as_table_owner)
2340 : 0 : RestoreUserContext(&ucxt);
2341 : 514 : resultRelInfo++;
2342 : 514 : }
2343 : :
2344 : : /* Handle queued AFTER triggers */
2345 : 184 : AfterTriggerEndQuery(estate);
2346 : :
2347 : : /* We can clean up the EState now */
2348 : 184 : FreeExecutorState(estate);
2349 : :
2350 : : /*
2351 : : * Close any rels opened by CASCADE (can't do this while EState still
2352 : : * holds refs)
2353 : : */
2354 : 184 : rels = list_difference_ptr(rels, explicit_rels);
2355 [ + + + + : 199 : foreach(cell, rels)
+ + ]
2356 : : {
2357 : 15 : Relation rel = (Relation) lfirst(cell);
2358 : :
2359 : 15 : table_close(rel, NoLock);
2360 : 15 : }
2361 : 184 : }
2362 : :
2363 : : /*
2364 : : * Check that a given relation is safe to truncate. Subroutine for
2365 : : * ExecuteTruncate() and RangeVarCallbackForTruncate().
2366 : : */
2367 : : static void
2368 : 580 : truncate_check_rel(Oid relid, Form_pg_class reltuple)
2369 : : {
2370 : 580 : char *relname = NameStr(reltuple->relname);
2371 : :
2372 : : /*
2373 : : * Only allow truncate on regular tables, foreign tables using foreign
2374 : : * data wrappers supporting TRUNCATE and partitioned tables (although, the
2375 : : * latter are only being included here for the following checks; no
2376 : : * physical truncation will occur in their case.).
2377 : : */
2378 [ - + ]: 580 : if (reltuple->relkind == RELKIND_FOREIGN_TABLE)
2379 : : {
2380 : 0 : Oid serverid = GetForeignServerIdByRelId(relid);
2381 : 0 : FdwRoutine *fdwroutine = GetFdwRoutineByServerId(serverid);
2382 : :
2383 [ # # ]: 0 : if (!fdwroutine->ExecForeignTruncate)
2384 [ # # # # ]: 0 : ereport(ERROR,
2385 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2386 : : errmsg("cannot truncate foreign table \"%s\"",
2387 : : relname)));
2388 : 0 : }
2389 [ + + + - ]: 580 : else if (reltuple->relkind != RELKIND_RELATION &&
2390 : 118 : reltuple->relkind != RELKIND_PARTITIONED_TABLE)
2391 [ # # # # ]: 0 : ereport(ERROR,
2392 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2393 : : errmsg("\"%s\" is not a table", relname)));
2394 : :
2395 : : /*
2396 : : * Most system catalogs can't be truncated at all, or at least not unless
2397 : : * allow_system_table_mods=on. As an exception, however, we allow
2398 : : * pg_largeobject and pg_largeobject_metadata to be truncated as part of
2399 : : * pg_upgrade, because we need to change its relfilenode to match the old
2400 : : * cluster, and allowing a TRUNCATE command to be executed is the easiest
2401 : : * way of doing that.
2402 : : */
2403 [ + - ]: 580 : if (!allowSystemTableMods && IsSystemClass(relid, reltuple)
2404 [ + - # # ]: 580 : && (!IsBinaryUpgrade ||
2405 [ # # ]: 0 : (relid != LargeObjectRelationId &&
2406 : 0 : relid != LargeObjectMetadataRelationId)))
2407 [ # # # # ]: 0 : ereport(ERROR,
2408 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
2409 : : errmsg("permission denied: \"%s\" is a system catalog",
2410 : : relname)));
2411 : :
2412 [ + - ]: 580 : InvokeObjectTruncateHook(relid);
2413 : 580 : }
2414 : :
2415 : : /*
2416 : : * Check that current user has the permission to truncate given relation.
2417 : : */
2418 : : static void
2419 : 291 : truncate_check_perms(Oid relid, Form_pg_class reltuple)
2420 : : {
2421 : 291 : char *relname = NameStr(reltuple->relname);
2422 : 291 : AclResult aclresult;
2423 : :
2424 : : /* Permissions checks */
2425 : 291 : aclresult = pg_class_aclcheck(relid, GetUserId(), ACL_TRUNCATE);
2426 [ + + ]: 291 : if (aclresult != ACLCHECK_OK)
2427 : 8 : aclcheck_error(aclresult, get_relkind_objtype(reltuple->relkind),
2428 : 4 : relname);
2429 : 291 : }
2430 : :
2431 : : /*
2432 : : * Set of extra sanity checks to check if a given relation is safe to
2433 : : * truncate. This is split with truncate_check_rel() as
2434 : : * RangeVarCallbackForTruncate() cannot open a Relation yet.
2435 : : */
2436 : : static void
2437 : 541 : truncate_check_activity(Relation rel)
2438 : : {
2439 : : /*
2440 : : * Don't allow truncate on temp tables of other backends ... their local
2441 : : * buffer manager is not going to cope.
2442 : : */
2443 [ + + + - ]: 541 : if (RELATION_IS_OTHER_TEMP(rel))
2444 [ # # # # ]: 0 : ereport(ERROR,
2445 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2446 : : errmsg("cannot truncate temporary tables of other sessions")));
2447 : :
2448 : : /*
2449 : : * Also check for active uses of the relation in the current transaction,
2450 : : * including open scans and pending AFTER trigger events.
2451 : : */
2452 : 541 : CheckTableNotInUse(rel, "TRUNCATE");
2453 : 541 : }
2454 : :
2455 : : /*
2456 : : * storage_name
2457 : : * returns the name corresponding to a typstorage/attstorage enum value
2458 : : */
2459 : : static const char *
2460 : 4 : storage_name(char c)
2461 : : {
2462 [ + - - - : 4 : switch (c)
+ ]
2463 : : {
2464 : : case TYPSTORAGE_PLAIN:
2465 : 0 : return "PLAIN";
2466 : : case TYPSTORAGE_EXTERNAL:
2467 : 0 : return "EXTERNAL";
2468 : : case TYPSTORAGE_EXTENDED:
2469 : 2 : return "EXTENDED";
2470 : : case TYPSTORAGE_MAIN:
2471 : 2 : return "MAIN";
2472 : : default:
2473 : 0 : return "???";
2474 : : }
2475 : 4 : }
2476 : :
2477 : : /*----------
2478 : : * MergeAttributes
2479 : : * Returns new schema given initial schema and superclasses.
2480 : : *
2481 : : * Input arguments:
2482 : : * 'columns' is the column/attribute definition for the table. (It's a list
2483 : : * of ColumnDef's.) It is destructively changed.
2484 : : * 'supers' is a list of OIDs of parent relations, already locked by caller.
2485 : : * 'relpersistence' is the persistence type of the table.
2486 : : * 'is_partition' tells if the table is a partition.
2487 : : *
2488 : : * Output arguments:
2489 : : * 'supconstr' receives a list of CookedConstraint representing
2490 : : * CHECK constraints belonging to parent relations, updated as
2491 : : * necessary to be valid for the child.
2492 : : * 'supnotnulls' receives a list of CookedConstraint representing
2493 : : * not-null constraints based on those from parent relations.
2494 : : *
2495 : : * Return value:
2496 : : * Completed schema list.
2497 : : *
2498 : : * Notes:
2499 : : * The order in which the attributes are inherited is very important.
2500 : : * Intuitively, the inherited attributes should come first. If a table
2501 : : * inherits from multiple parents, the order of those attributes are
2502 : : * according to the order of the parents specified in CREATE TABLE.
2503 : : *
2504 : : * Here's an example:
2505 : : *
2506 : : * create table person (name text, age int4, location point);
2507 : : * create table emp (salary int4, manager text) inherits(person);
2508 : : * create table student (gpa float8) inherits (person);
2509 : : * create table stud_emp (percent int4) inherits (emp, student);
2510 : : *
2511 : : * The order of the attributes of stud_emp is:
2512 : : *
2513 : : * person {1:name, 2:age, 3:location}
2514 : : * / \
2515 : : * {6:gpa} student emp {4:salary, 5:manager}
2516 : : * \ /
2517 : : * stud_emp {7:percent}
2518 : : *
2519 : : * If the same attribute name appears multiple times, then it appears
2520 : : * in the result table in the proper location for its first appearance.
2521 : : *
2522 : : * Constraints (including not-null constraints) for the child table
2523 : : * are the union of all relevant constraints, from both the child schema
2524 : : * and parent tables. In addition, in legacy inheritance, each column that
2525 : : * appears in a primary key in any of the parents also gets a NOT NULL
2526 : : * constraint (partitioning doesn't need this, because the PK itself gets
2527 : : * inherited.)
2528 : : *
2529 : : * The default value for a child column is defined as:
2530 : : * (1) If the child schema specifies a default, that value is used.
2531 : : * (2) If neither the child nor any parent specifies a default, then
2532 : : * the column will not have a default.
2533 : : * (3) If conflicting defaults are inherited from different parents
2534 : : * (and not overridden by the child), an error is raised.
2535 : : * (4) Otherwise the inherited default is used.
2536 : : *
2537 : : * Note that the default-value infrastructure is used for generated
2538 : : * columns' expressions too, so most of the preceding paragraph applies
2539 : : * to generation expressions too. We insist that a child column be
2540 : : * generated if and only if its parent(s) are, but it need not have
2541 : : * the same generation expression.
2542 : : *----------
2543 : : */
2544 : : static List *
2545 : 5484 : MergeAttributes(List *columns, const List *supers, char relpersistence,
2546 : : bool is_partition, List **supconstr, List **supnotnulls)
2547 : : {
2548 : 5484 : List *inh_columns = NIL;
2549 : 5484 : List *constraints = NIL;
2550 : 5484 : List *nnconstraints = NIL;
2551 : 5484 : bool have_bogus_defaults = false;
2552 : 5484 : int child_attno;
2553 : : static Node bogus_marker = {0}; /* marks conflicting defaults */
2554 : 5484 : List *saved_columns = NIL;
2555 : 5484 : ListCell *lc;
2556 : :
2557 : : /*
2558 : : * Check for and reject tables with too many columns. We perform this
2559 : : * check relatively early for two reasons: (a) we don't run the risk of
2560 : : * overflowing an AttrNumber in subsequent code (b) an O(n^2) algorithm is
2561 : : * okay if we're processing <= 1600 columns, but could take minutes to
2562 : : * execute if the user attempts to create a table with hundreds of
2563 : : * thousands of columns.
2564 : : *
2565 : : * Note that we also need to check that we do not exceed this figure after
2566 : : * including columns from inherited relations.
2567 : : */
2568 [ + - ]: 5484 : if (list_length(columns) > MaxHeapAttributeNumber)
2569 [ # # # # ]: 0 : ereport(ERROR,
2570 : : (errcode(ERRCODE_TOO_MANY_COLUMNS),
2571 : : errmsg("tables can have at most %d columns",
2572 : : MaxHeapAttributeNumber)));
2573 : :
2574 : : /*
2575 : : * Check for duplicate names in the explicit list of attributes.
2576 : : *
2577 : : * Although we might consider merging such entries in the same way that we
2578 : : * handle name conflicts for inherited attributes, it seems to make more
2579 : : * sense to assume such conflicts are errors.
2580 : : *
2581 : : * We don't use foreach() here because we have two nested loops over the
2582 : : * columns list, with possible element deletions in the inner one. If we
2583 : : * used foreach_delete_current() it could only fix up the state of one of
2584 : : * the loops, so it seems cleaner to use looping over list indexes for
2585 : : * both loops. Note that any deletion will happen beyond where the outer
2586 : : * loop is, so its index never needs adjustment.
2587 : : */
2588 [ + + ]: 16877 : for (int coldefpos = 0; coldefpos < list_length(columns); coldefpos++)
2589 : : {
2590 : 11397 : ColumnDef *coldef = list_nth_node(ColumnDef, columns, coldefpos);
2591 : :
2592 [ + + + + ]: 11397 : if (!is_partition && coldef->typeName == NULL)
2593 : : {
2594 : : /*
2595 : : * Typed table column option that does not belong to a column from
2596 : : * the type. This works because the columns from the type come
2597 : : * first in the list. (We omit this check for partition column
2598 : : * lists; those are processed separately below.)
2599 : : */
2600 [ + - + - ]: 1 : ereport(ERROR,
2601 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
2602 : : errmsg("column \"%s\" does not exist",
2603 : : coldef->colname)));
2604 : 0 : }
2605 : :
2606 : : /* restpos scans all entries beyond coldef; incr is in loop body */
2607 [ + + ]: 646934 : for (int restpos = coldefpos + 1; restpos < list_length(columns);)
2608 : : {
2609 : 635541 : ColumnDef *restdef = list_nth_node(ColumnDef, columns, restpos);
2610 : :
2611 [ + + ]: 635541 : if (strcmp(coldef->colname, restdef->colname) == 0)
2612 : : {
2613 [ + + ]: 8 : if (coldef->is_from_type)
2614 : : {
2615 : : /*
2616 : : * merge the column options into the column from the type
2617 : : */
2618 : 5 : coldef->is_not_null = restdef->is_not_null;
2619 : 5 : coldef->raw_default = restdef->raw_default;
2620 : 5 : coldef->cooked_default = restdef->cooked_default;
2621 : 5 : coldef->constraints = restdef->constraints;
2622 : 5 : coldef->is_from_type = false;
2623 : 5 : columns = list_delete_nth_cell(columns, restpos);
2624 : 5 : }
2625 : : else
2626 [ + - + - ]: 3 : ereport(ERROR,
2627 : : (errcode(ERRCODE_DUPLICATE_COLUMN),
2628 : : errmsg("column \"%s\" specified more than once",
2629 : : coldef->colname)));
2630 : 5 : }
2631 : : else
2632 : 635533 : restpos++;
2633 : 635538 : }
2634 : 11393 : }
2635 : :
2636 : : /*
2637 : : * In case of a partition, there are no new column definitions, only dummy
2638 : : * ColumnDefs created for column constraints. Set them aside for now and
2639 : : * process them at the end.
2640 : : */
2641 [ + + ]: 5480 : if (is_partition)
2642 : : {
2643 : 1221 : saved_columns = columns;
2644 : 1221 : columns = NIL;
2645 : 1221 : }
2646 : :
2647 : : /*
2648 : : * Scan the parents left-to-right, and merge their attributes to form a
2649 : : * list of inherited columns (inh_columns).
2650 : : */
2651 : 5480 : child_attno = 0;
2652 [ + + + + : 7035 : foreach(lc, supers)
+ + ]
2653 : : {
2654 : 1562 : Oid parent = lfirst_oid(lc);
2655 : 1562 : Relation relation;
2656 : 1562 : TupleDesc tupleDesc;
2657 : 1562 : TupleConstr *constr;
2658 : 1562 : AttrMap *newattmap;
2659 : 1562 : List *inherited_defaults;
2660 : 1562 : List *cols_with_defaults;
2661 : 1562 : List *nnconstrs;
2662 : 1562 : ListCell *lc1;
2663 : 1562 : ListCell *lc2;
2664 : 1562 : Bitmapset *nncols = NULL;
2665 : :
2666 : : /* caller already got lock */
2667 : 1562 : relation = table_open(parent, NoLock);
2668 : :
2669 : : /*
2670 : : * Check for active uses of the parent partitioned table in the
2671 : : * current transaction, such as being used in some manner by an
2672 : : * enclosing command.
2673 : : */
2674 [ + + ]: 1562 : if (is_partition)
2675 : 1221 : CheckTableNotInUse(relation, "CREATE TABLE .. PARTITION OF");
2676 : :
2677 : : /*
2678 : : * We do not allow partitioned tables and partitions to participate in
2679 : : * regular inheritance.
2680 : : */
2681 [ + + + + ]: 1562 : if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !is_partition)
2682 [ + - + - ]: 1 : ereport(ERROR,
2683 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2684 : : errmsg("cannot inherit from partitioned table \"%s\"",
2685 : : RelationGetRelationName(relation))));
2686 [ + + + + ]: 1561 : if (relation->rd_rel->relispartition && !is_partition)
2687 [ + - + - ]: 1 : ereport(ERROR,
2688 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2689 : : errmsg("cannot inherit from partition \"%s\"",
2690 : : RelationGetRelationName(relation))));
2691 : :
2692 [ + + ]: 1560 : if (relation->rd_rel->relkind != RELKIND_RELATION &&
2693 [ + + + - ]: 1219 : relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
2694 : 1217 : relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
2695 [ # # # # ]: 0 : ereport(ERROR,
2696 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2697 : : errmsg("inherited relation \"%s\" is not a table or foreign table",
2698 : : RelationGetRelationName(relation))));
2699 : :
2700 : : /*
2701 : : * If the parent is permanent, so must be all of its partitions. Note
2702 : : * that inheritance allows that case.
2703 : : */
2704 [ + + ]: 1560 : if (is_partition &&
2705 [ + + + + ]: 1220 : relation->rd_rel->relpersistence != RELPERSISTENCE_TEMP &&
2706 : 1178 : relpersistence == RELPERSISTENCE_TEMP)
2707 [ + - + - ]: 1 : ereport(ERROR,
2708 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2709 : : errmsg("cannot create a temporary relation as partition of permanent relation \"%s\"",
2710 : : RelationGetRelationName(relation))));
2711 : :
2712 : : /* Permanent rels cannot inherit from temporary ones */
2713 [ + + + + ]: 1559 : if (relpersistence != RELPERSISTENCE_TEMP &&
2714 : 1502 : relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
2715 [ + - + - ]: 4 : ereport(ERROR,
2716 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2717 : : errmsg(!is_partition
2718 : : ? "cannot inherit from temporary relation \"%s\""
2719 : : : "cannot create a permanent relation as partition of temporary relation \"%s\"",
2720 : : RelationGetRelationName(relation))));
2721 : :
2722 : : /* If existing rel is temp, it must belong to this session */
2723 [ + + + - ]: 1555 : if (RELATION_IS_OTHER_TEMP(relation))
2724 [ # # # # ]: 0 : ereport(ERROR,
2725 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2726 : : errmsg(!is_partition
2727 : : ? "cannot inherit from temporary relation of another session"
2728 : : : "cannot create as partition of temporary relation of another session")));
2729 : :
2730 : : /*
2731 : : * We should have an UNDER permission flag for this, but for now,
2732 : : * demand that creator of a child table own the parent.
2733 : : */
2734 [ + - ]: 1555 : if (!object_ownercheck(RelationRelationId, RelationGetRelid(relation), GetUserId()))
2735 : 0 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(relation->rd_rel->relkind),
2736 : 0 : RelationGetRelationName(relation));
2737 : :
2738 : 1555 : tupleDesc = RelationGetDescr(relation);
2739 : 1555 : constr = tupleDesc->constr;
2740 : :
2741 : : /*
2742 : : * newattmap->attnums[] will contain the child-table attribute numbers
2743 : : * for the attributes of this parent table. (They are not the same
2744 : : * for parents after the first one, nor if we have dropped columns.)
2745 : : */
2746 : 1555 : newattmap = make_attrmap(tupleDesc->natts);
2747 : :
2748 : : /* We can't process inherited defaults until newattmap is complete. */
2749 : 1555 : inherited_defaults = cols_with_defaults = NIL;
2750 : :
2751 : : /*
2752 : : * Request attnotnull on columns that have a not-null constraint
2753 : : * that's not marked NO INHERIT (even if not valid).
2754 : : */
2755 : 1555 : nnconstrs = RelationGetNotNullConstraints(RelationGetRelid(relation),
2756 : : true, false);
2757 [ + + + + : 3499 : foreach_ptr(CookedConstraint, cc, nnconstrs)
+ + + + ]
2758 : 1944 : nncols = bms_add_member(nncols, cc->attnum);
2759 : :
2760 [ + + ]: 4705 : for (AttrNumber parent_attno = 1; parent_attno <= tupleDesc->natts;
2761 : 3150 : parent_attno++)
2762 : : {
2763 : 6300 : Form_pg_attribute attribute = TupleDescAttr(tupleDesc,
2764 : 3150 : parent_attno - 1);
2765 : 3150 : char *attributeName = NameStr(attribute->attname);
2766 : 3150 : int exist_attno;
2767 : 3150 : ColumnDef *newdef;
2768 : 3150 : ColumnDef *mergeddef;
2769 : :
2770 : : /*
2771 : : * Ignore dropped columns in the parent.
2772 : : */
2773 [ + + ]: 3150 : if (attribute->attisdropped)
2774 : 33 : continue; /* leave newattmap->attnums entry as zero */
2775 : :
2776 : : /*
2777 : : * Create new column definition
2778 : : */
2779 : 6234 : newdef = makeColumnDef(attributeName, attribute->atttypid,
2780 : 3117 : attribute->atttypmod, attribute->attcollation);
2781 : 3117 : newdef->storage = attribute->attstorage;
2782 : 3117 : newdef->generated = attribute->attgenerated;
2783 [ + + ]: 3117 : if (CompressionMethodIsValid(attribute->attcompression))
2784 : 6 : newdef->compression =
2785 : 6 : pstrdup(GetCompressionMethodName(attribute->attcompression));
2786 : :
2787 : : /*
2788 : : * Regular inheritance children are independent enough not to
2789 : : * inherit identity columns. But partitions are integral part of
2790 : : * a partitioned table and inherit identity column.
2791 : : */
2792 [ + + ]: 3117 : if (is_partition)
2793 : 2545 : newdef->identity = attribute->attidentity;
2794 : :
2795 : : /*
2796 : : * Does it match some previously considered column from another
2797 : : * parent?
2798 : : */
2799 : 3117 : exist_attno = findAttrByName(attributeName, inh_columns);
2800 [ + + ]: 3117 : if (exist_attno > 0)
2801 : : {
2802 : : /*
2803 : : * Yes, try to merge the two column definitions.
2804 : : */
2805 : 54 : mergeddef = MergeInheritedAttribute(inh_columns, exist_attno, newdef);
2806 : :
2807 : 54 : newattmap->attnums[parent_attno - 1] = exist_attno;
2808 : :
2809 : : /*
2810 : : * Partitions have only one parent, so conflict should never
2811 : : * occur.
2812 : : */
2813 [ + - ]: 54 : Assert(!is_partition);
2814 : 54 : }
2815 : : else
2816 : : {
2817 : : /*
2818 : : * No, create a new inherited column
2819 : : */
2820 : 3063 : newdef->inhcount = 1;
2821 : 3063 : newdef->is_local = false;
2822 : 3063 : inh_columns = lappend(inh_columns, newdef);
2823 : :
2824 : 3063 : newattmap->attnums[parent_attno - 1] = ++child_attno;
2825 : 3063 : mergeddef = newdef;
2826 : : }
2827 : :
2828 : : /*
2829 : : * mark attnotnull if parent has it
2830 : : */
2831 [ + + ]: 3117 : if (bms_is_member(parent_attno, nncols))
2832 : 383 : mergeddef->is_not_null = true;
2833 : :
2834 : : /*
2835 : : * Locate default/generation expression if any
2836 : : */
2837 [ + + ]: 3117 : if (attribute->atthasdef)
2838 : : {
2839 : 101 : Node *this_default;
2840 : :
2841 : 101 : this_default = TupleDescGetDefault(tupleDesc, parent_attno);
2842 [ + - ]: 101 : if (this_default == NULL)
2843 [ # # # # ]: 0 : elog(ERROR, "default expression not found for attribute %d of relation \"%s\"",
2844 : : parent_attno, RelationGetRelationName(relation));
2845 : :
2846 : : /*
2847 : : * If it's a GENERATED default, it might contain Vars that
2848 : : * need to be mapped to the inherited column(s)' new numbers.
2849 : : * We can't do that till newattmap is ready, so just remember
2850 : : * all the inherited default expressions for the moment.
2851 : : */
2852 : 101 : inherited_defaults = lappend(inherited_defaults, this_default);
2853 : 101 : cols_with_defaults = lappend(cols_with_defaults, mergeddef);
2854 : 101 : }
2855 [ + + ]: 3150 : }
2856 : :
2857 : : /*
2858 : : * Now process any inherited default expressions, adjusting attnos
2859 : : * using the completed newattmap map.
2860 : : */
2861 [ + + + + : 1656 : forboth(lc1, inherited_defaults, lc2, cols_with_defaults)
+ + + + +
+ + + ]
2862 : : {
2863 : 101 : Node *this_default = (Node *) lfirst(lc1);
2864 : 101 : ColumnDef *def = (ColumnDef *) lfirst(lc2);
2865 : 101 : bool found_whole_row;
2866 : :
2867 : : /* Adjust Vars to match new table's column numbering */
2868 : 202 : this_default = map_variable_attnos(this_default,
2869 : : 1, 0,
2870 : 101 : newattmap,
2871 : : InvalidOid, &found_whole_row);
2872 : :
2873 : : /*
2874 : : * For the moment we have to reject whole-row variables. We could
2875 : : * convert them, if we knew the new table's rowtype OID, but that
2876 : : * hasn't been assigned yet. (A variable could only appear in a
2877 : : * generation expression, so the error message is correct.)
2878 : : */
2879 [ + - ]: 101 : if (found_whole_row)
2880 [ # # # # ]: 0 : ereport(ERROR,
2881 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2882 : : errmsg("cannot convert whole-row table reference"),
2883 : : errdetail("Generation expression for column \"%s\" contains a whole-row reference to table \"%s\".",
2884 : : def->colname,
2885 : : RelationGetRelationName(relation))));
2886 : :
2887 : : /*
2888 : : * If we already had a default from some prior parent, check to
2889 : : * see if they are the same. If so, no problem; if not, mark the
2890 : : * column as having a bogus default. Below, we will complain if
2891 : : * the bogus default isn't overridden by the child columns.
2892 : : */
2893 [ + - ]: 101 : Assert(def->raw_default == NULL);
2894 [ + + ]: 101 : if (def->cooked_default == NULL)
2895 : 94 : def->cooked_default = this_default;
2896 [ + + ]: 7 : else if (!equal(def->cooked_default, this_default))
2897 : : {
2898 : 6 : def->cooked_default = &bogus_marker;
2899 : 6 : have_bogus_defaults = true;
2900 : 6 : }
2901 : 101 : }
2902 : :
2903 : : /*
2904 : : * Now copy the CHECK constraints of this parent, adjusting attnos
2905 : : * using the completed newattmap map. Identically named constraints
2906 : : * are merged if possible, else we throw error.
2907 : : */
2908 [ + + + + ]: 1555 : if (constr && constr->num_check > 0)
2909 : : {
2910 : 57 : ConstrCheck *check = constr->check;
2911 : :
2912 [ + + ]: 181 : for (int i = 0; i < constr->num_check; i++)
2913 : : {
2914 : 124 : char *name = check[i].ccname;
2915 : 124 : Node *expr;
2916 : 124 : bool found_whole_row;
2917 : :
2918 : : /* ignore if the constraint is non-inheritable */
2919 [ + + ]: 124 : if (check[i].ccnoinherit)
2920 : 8 : continue;
2921 : :
2922 : : /* Adjust Vars to match new table's column numbering */
2923 : 232 : expr = map_variable_attnos(stringToNode(check[i].ccbin),
2924 : : 1, 0,
2925 : 116 : newattmap,
2926 : : InvalidOid, &found_whole_row);
2927 : :
2928 : : /*
2929 : : * For the moment we have to reject whole-row variables. We
2930 : : * could convert them, if we knew the new table's rowtype OID,
2931 : : * but that hasn't been assigned yet.
2932 : : */
2933 [ + - ]: 116 : if (found_whole_row)
2934 [ # # # # ]: 0 : ereport(ERROR,
2935 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2936 : : errmsg("cannot convert whole-row table reference"),
2937 : : errdetail("Constraint \"%s\" contains a whole-row reference to table \"%s\".",
2938 : : name,
2939 : : RelationGetRelationName(relation))));
2940 : :
2941 : 232 : constraints = MergeCheckConstraint(constraints, name, expr,
2942 : 116 : check[i].ccenforced);
2943 [ + + ]: 124 : }
2944 : 57 : }
2945 : :
2946 : : /*
2947 : : * Also copy the not-null constraints from this parent. The
2948 : : * attnotnull markings were already installed above.
2949 : : */
2950 [ + + + + : 3493 : foreach_ptr(CookedConstraint, nn, nnconstrs)
+ + + + ]
2951 : : {
2952 [ + - ]: 383 : Assert(nn->contype == CONSTR_NOTNULL);
2953 : :
2954 : 383 : nn->attnum = newattmap->attnums[nn->attnum - 1];
2955 : :
2956 : 383 : nnconstraints = lappend(nnconstraints, nn);
2957 : 1938 : }
2958 : :
2959 : 1555 : free_attrmap(newattmap);
2960 : :
2961 : : /*
2962 : : * Close the parent rel, but keep our lock on it until xact commit.
2963 : : * That will prevent someone else from deleting or ALTERing the parent
2964 : : * before the child is committed.
2965 : : */
2966 : 1555 : table_close(relation, NoLock);
2967 : 1555 : }
2968 : :
2969 : : /*
2970 : : * If we had no inherited attributes, the result columns are just the
2971 : : * explicitly declared columns. Otherwise, we need to merge the declared
2972 : : * columns into the inherited column list. Although, we never have any
2973 : : * explicitly declared columns if the table is a partition.
2974 : : */
2975 [ + + ]: 5473 : if (inh_columns != NIL)
2976 : : {
2977 : 1470 : int newcol_attno = 0;
2978 : :
2979 [ + + + + : 1640 : foreach(lc, columns)
+ + ]
2980 : : {
2981 : 170 : ColumnDef *newdef = lfirst_node(ColumnDef, lc);
2982 : 170 : char *attributeName = newdef->colname;
2983 : 170 : int exist_attno;
2984 : :
2985 : : /*
2986 : : * Partitions have only one parent and have no column definitions
2987 : : * of their own, so conflict should never occur.
2988 : : */
2989 [ + - ]: 170 : Assert(!is_partition);
2990 : :
2991 : 170 : newcol_attno++;
2992 : :
2993 : : /*
2994 : : * Does it match some inherited column?
2995 : : */
2996 : 170 : exist_attno = findAttrByName(attributeName, inh_columns);
2997 [ + + ]: 170 : if (exist_attno > 0)
2998 : : {
2999 : : /*
3000 : : * Yes, try to merge the two column definitions.
3001 : : */
3002 : 61 : MergeChildAttribute(inh_columns, exist_attno, newcol_attno, newdef);
3003 : 61 : }
3004 : : else
3005 : : {
3006 : : /*
3007 : : * No, attach new column unchanged to result columns.
3008 : : */
3009 : 109 : inh_columns = lappend(inh_columns, newdef);
3010 : : }
3011 : 170 : }
3012 : :
3013 : 1470 : columns = inh_columns;
3014 : :
3015 : : /*
3016 : : * Check that we haven't exceeded the legal # of columns after merging
3017 : : * in inherited columns.
3018 : : */
3019 [ + - ]: 1470 : if (list_length(columns) > MaxHeapAttributeNumber)
3020 [ # # # # ]: 0 : ereport(ERROR,
3021 : : (errcode(ERRCODE_TOO_MANY_COLUMNS),
3022 : : errmsg("tables can have at most %d columns",
3023 : : MaxHeapAttributeNumber)));
3024 : 1470 : }
3025 : :
3026 : : /*
3027 : : * Now that we have the column definition list for a partition, we can
3028 : : * check whether the columns referenced in the column constraint specs
3029 : : * actually exist. Also, merge column defaults.
3030 : : */
3031 [ + + ]: 5473 : if (is_partition)
3032 : : {
3033 [ + + + + : 1233 : foreach(lc, saved_columns)
+ + ]
3034 : : {
3035 : 23 : ColumnDef *restdef = lfirst(lc);
3036 : 23 : bool found = false;
3037 : 23 : ListCell *l;
3038 : :
3039 [ + - + + : 64 : foreach(l, columns)
+ + ]
3040 : : {
3041 : 47 : ColumnDef *coldef = lfirst(l);
3042 : :
3043 [ + + ]: 47 : if (strcmp(coldef->colname, restdef->colname) == 0)
3044 : : {
3045 : 23 : found = true;
3046 : :
3047 : : /*
3048 : : * Check for conflicts related to generated columns.
3049 : : *
3050 : : * Same rules as above: generated-ness has to match the
3051 : : * parent, but the contents of the generation expression
3052 : : * can be different.
3053 : : */
3054 [ + + ]: 23 : if (coldef->generated)
3055 : : {
3056 [ + - + + ]: 6 : if (restdef->raw_default && !restdef->generated)
3057 [ + - + - ]: 2 : ereport(ERROR,
3058 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3059 : : errmsg("column \"%s\" inherits from generated column but specifies default",
3060 : : restdef->colname)));
3061 [ + - ]: 4 : if (restdef->identity)
3062 [ # # # # ]: 0 : ereport(ERROR,
3063 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3064 : : errmsg("column \"%s\" inherits from generated column but specifies identity",
3065 : : restdef->colname)));
3066 : 4 : }
3067 : : else
3068 : : {
3069 [ + + ]: 17 : if (restdef->generated)
3070 [ + - + - ]: 2 : ereport(ERROR,
3071 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3072 : : errmsg("child column \"%s\" specifies generation expression",
3073 : : restdef->colname),
3074 : : errhint("A child table column cannot be generated unless its parent column is.")));
3075 : : }
3076 : :
3077 [ + + + - : 19 : if (coldef->generated && restdef->generated && coldef->generated != restdef->generated)
+ + ]
3078 [ + - + - ]: 2 : ereport(ERROR,
3079 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3080 : : errmsg("column \"%s\" inherits from generated column of different kind",
3081 : : restdef->colname),
3082 : : errdetail("Parent column is %s, child column is %s.",
3083 : : coldef->generated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL",
3084 : : restdef->generated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL")));
3085 : :
3086 : : /*
3087 : : * Override the parent's default value for this column
3088 : : * (coldef->cooked_default) with the partition's local
3089 : : * definition (restdef->raw_default), if there's one. It
3090 : : * should be physically impossible to get a cooked default
3091 : : * in the local definition or a raw default in the
3092 : : * inherited definition, but make sure they're nulls, for
3093 : : * future-proofing.
3094 : : */
3095 [ - + ]: 17 : Assert(restdef->cooked_default == NULL);
3096 [ - + ]: 17 : Assert(coldef->raw_default == NULL);
3097 [ + + ]: 17 : if (restdef->raw_default)
3098 : : {
3099 : 5 : coldef->raw_default = restdef->raw_default;
3100 : 5 : coldef->cooked_default = NULL;
3101 : 5 : }
3102 : 17 : }
3103 : 41 : }
3104 : :
3105 : : /* complain for constraints on columns not in parent */
3106 [ + - ]: 17 : if (!found)
3107 [ # # # # ]: 0 : ereport(ERROR,
3108 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
3109 : : errmsg("column \"%s\" does not exist",
3110 : : restdef->colname)));
3111 : 17 : }
3112 : 1210 : }
3113 : :
3114 : : /*
3115 : : * If we found any conflicting parent default values, check to make sure
3116 : : * they were overridden by the child.
3117 : : */
3118 [ + + ]: 5467 : if (have_bogus_defaults)
3119 : : {
3120 [ + - + + : 15 : foreach(lc, columns)
+ + ]
3121 : : {
3122 : 12 : ColumnDef *def = lfirst(lc);
3123 : :
3124 [ + + ]: 12 : if (def->cooked_default == &bogus_marker)
3125 : : {
3126 [ + + ]: 3 : if (def->generated)
3127 [ + - + - ]: 2 : ereport(ERROR,
3128 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3129 : : errmsg("column \"%s\" inherits conflicting generation expressions",
3130 : : def->colname),
3131 : : errhint("To resolve the conflict, specify a generation expression explicitly.")));
3132 : : else
3133 [ + - + - ]: 1 : ereport(ERROR,
3134 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3135 : : errmsg("column \"%s\" inherits conflicting default values",
3136 : : def->colname),
3137 : : errhint("To resolve the conflict, specify a default explicitly.")));
3138 : 0 : }
3139 : 9 : }
3140 : 3 : }
3141 : :
3142 : 5464 : *supconstr = constraints;
3143 : 5464 : *supnotnulls = nnconstraints;
3144 : :
3145 : 10928 : return columns;
3146 : 5464 : }
3147 : :
3148 : :
3149 : : /*
3150 : : * MergeCheckConstraint
3151 : : * Try to merge an inherited CHECK constraint with previous ones
3152 : : *
3153 : : * If we inherit identically-named constraints from multiple parents, we must
3154 : : * merge them, or throw an error if they don't have identical definitions.
3155 : : *
3156 : : * constraints is a list of CookedConstraint structs for previous constraints.
3157 : : *
3158 : : * If the new constraint matches an existing one, then the existing
3159 : : * constraint's inheritance count is updated. If there is a conflict (same
3160 : : * name but different expression), throw an error. If the constraint neither
3161 : : * matches nor conflicts with an existing one, a new constraint is appended to
3162 : : * the list.
3163 : : */
3164 : : static List *
3165 : 116 : MergeCheckConstraint(List *constraints, const char *name, Node *expr, bool is_enforced)
3166 : : {
3167 : 116 : ListCell *lc;
3168 : 116 : CookedConstraint *newcon;
3169 : :
3170 [ + + + + : 395 : foreach(lc, constraints)
+ + + + ]
3171 : : {
3172 : 279 : CookedConstraint *ccon = (CookedConstraint *) lfirst(lc);
3173 : :
3174 [ - + ]: 279 : Assert(ccon->contype == CONSTR_CHECK);
3175 : :
3176 : : /* Non-matching names never conflict */
3177 [ + + ]: 279 : if (strcmp(ccon->name, name) != 0)
3178 : 254 : continue;
3179 : :
3180 [ + - ]: 25 : if (equal(expr, ccon->expr))
3181 : : {
3182 : : /* OK to merge constraint with existing */
3183 [ + - + - ]: 50 : if (pg_add_s16_overflow(ccon->inhcount, 1,
3184 : 25 : &ccon->inhcount))
3185 [ # # # # ]: 0 : ereport(ERROR,
3186 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
3187 : : errmsg("too many inheritance parents"));
3188 : :
3189 : : /*
3190 : : * When enforceability differs, the merged constraint should be
3191 : : * marked as ENFORCED because one of the parents is ENFORCED.
3192 : : */
3193 [ + + + + ]: 25 : if (!ccon->is_enforced && is_enforced)
3194 : : {
3195 : 8 : ccon->is_enforced = true;
3196 : 8 : ccon->skip_validation = false;
3197 : 8 : }
3198 : :
3199 : 25 : return constraints;
3200 : : }
3201 : :
3202 [ # # # # ]: 0 : ereport(ERROR,
3203 : : (errcode(ERRCODE_DUPLICATE_OBJECT),
3204 : : errmsg("check constraint name \"%s\" appears multiple times but with different expressions",
3205 : : name)));
3206 [ + + - ]: 279 : }
3207 : :
3208 : : /*
3209 : : * Constraint couldn't be merged with an existing one and also didn't
3210 : : * conflict with an existing one, so add it as a new one to the list.
3211 : : */
3212 : 91 : newcon = palloc0_object(CookedConstraint);
3213 : 91 : newcon->contype = CONSTR_CHECK;
3214 : 91 : newcon->name = pstrdup(name);
3215 : 91 : newcon->expr = expr;
3216 : 91 : newcon->inhcount = 1;
3217 : 91 : newcon->is_enforced = is_enforced;
3218 : 91 : newcon->skip_validation = !is_enforced;
3219 : 91 : return lappend(constraints, newcon);
3220 : 116 : }
3221 : :
3222 : : /*
3223 : : * MergeChildAttribute
3224 : : * Merge given child attribute definition into given inherited attribute.
3225 : : *
3226 : : * Input arguments:
3227 : : * 'inh_columns' is the list of inherited ColumnDefs.
3228 : : * 'exist_attno' is the number of the inherited attribute in inh_columns
3229 : : * 'newcol_attno' is the attribute number in child table's schema definition
3230 : : * 'newdef' is the column/attribute definition from the child table.
3231 : : *
3232 : : * The ColumnDef in 'inh_columns' list is modified. The child attribute's
3233 : : * ColumnDef remains unchanged.
3234 : : *
3235 : : * Notes:
3236 : : * - The attribute is merged according to the rules laid out in the prologue
3237 : : * of MergeAttributes().
3238 : : * - If matching inherited attribute exists but the child attribute can not be
3239 : : * merged into it, the function throws respective errors.
3240 : : * - A partition can not have its own column definitions. Hence this function
3241 : : * is applicable only to a regular inheritance child.
3242 : : */
3243 : : static void
3244 : 61 : MergeChildAttribute(List *inh_columns, int exist_attno, int newcol_attno, const ColumnDef *newdef)
3245 : : {
3246 : 61 : char *attributeName = newdef->colname;
3247 : 61 : ColumnDef *inhdef;
3248 : 61 : Oid inhtypeid,
3249 : : newtypeid;
3250 : 61 : int32 inhtypmod,
3251 : : newtypmod;
3252 : 61 : Oid inhcollid,
3253 : : newcollid;
3254 : :
3255 [ + + ]: 61 : if (exist_attno == newcol_attno)
3256 [ - + + - ]: 56 : ereport(NOTICE,
3257 : : (errmsg("merging column \"%s\" with inherited definition",
3258 : : attributeName)));
3259 : : else
3260 [ - + + - ]: 5 : ereport(NOTICE,
3261 : : (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
3262 : : errdetail("User-specified column moved to the position of the inherited column.")));
3263 : :
3264 : 61 : inhdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
3265 : :
3266 : : /*
3267 : : * Must have the same type and typmod
3268 : : */
3269 : 61 : typenameTypeIdAndMod(NULL, inhdef->typeName, &inhtypeid, &inhtypmod);
3270 : 61 : typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
3271 [ + + ]: 61 : if (inhtypeid != newtypeid || inhtypmod != newtypmod)
3272 [ + - + - ]: 2 : ereport(ERROR,
3273 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
3274 : : errmsg("column \"%s\" has a type conflict",
3275 : : attributeName),
3276 : : errdetail("%s versus %s",
3277 : : format_type_with_typemod(inhtypeid, inhtypmod),
3278 : : format_type_with_typemod(newtypeid, newtypmod))));
3279 : :
3280 : : /*
3281 : : * Must have the same collation
3282 : : */
3283 : 59 : inhcollid = GetColumnDefCollation(NULL, inhdef, inhtypeid);
3284 : 59 : newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
3285 [ + + ]: 59 : if (inhcollid != newcollid)
3286 [ + - + - ]: 1 : ereport(ERROR,
3287 : : (errcode(ERRCODE_COLLATION_MISMATCH),
3288 : : errmsg("column \"%s\" has a collation conflict",
3289 : : attributeName),
3290 : : errdetail("\"%s\" versus \"%s\"",
3291 : : get_collation_name(inhcollid),
3292 : : get_collation_name(newcollid))));
3293 : :
3294 : : /*
3295 : : * Identity is never inherited by a regular inheritance child. Pick
3296 : : * child's identity definition if there's one.
3297 : : */
3298 : 58 : inhdef->identity = newdef->identity;
3299 : :
3300 : : /*
3301 : : * Copy storage parameter
3302 : : */
3303 [ + - ]: 58 : if (inhdef->storage == 0)
3304 : 0 : inhdef->storage = newdef->storage;
3305 [ + + + + ]: 58 : else if (newdef->storage != 0 && inhdef->storage != newdef->storage)
3306 [ + - + - ]: 1 : ereport(ERROR,
3307 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
3308 : : errmsg("column \"%s\" has a storage parameter conflict",
3309 : : attributeName),
3310 : : errdetail("%s versus %s",
3311 : : storage_name(inhdef->storage),
3312 : : storage_name(newdef->storage))));
3313 : :
3314 : : /*
3315 : : * Copy compression parameter
3316 : : */
3317 [ + + ]: 57 : if (inhdef->compression == NULL)
3318 : 56 : inhdef->compression = newdef->compression;
3319 [ - + ]: 1 : else if (newdef->compression != NULL)
3320 : : {
3321 [ - + ]: 1 : if (strcmp(inhdef->compression, newdef->compression) != 0)
3322 [ + - + - ]: 1 : ereport(ERROR,
3323 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
3324 : : errmsg("column \"%s\" has a compression method conflict",
3325 : : attributeName),
3326 : : errdetail("%s versus %s", inhdef->compression, newdef->compression)));
3327 : 0 : }
3328 : :
3329 : : /*
3330 : : * Merge of not-null constraints = OR 'em together
3331 : : */
3332 : 56 : inhdef->is_not_null |= newdef->is_not_null;
3333 : :
3334 : : /*
3335 : : * Check for conflicts related to generated columns.
3336 : : *
3337 : : * If the parent column is generated, the child column will be made a
3338 : : * generated column if it isn't already. If it is a generated column,
3339 : : * we'll take its generation expression in preference to the parent's. We
3340 : : * must check that the child column doesn't specify a default value or
3341 : : * identity, which matches the rules for a single column in
3342 : : * parse_utilcmd.c.
3343 : : *
3344 : : * Conversely, if the parent column is not generated, the child column
3345 : : * can't be either. (We used to allow that, but it results in being able
3346 : : * to override the generation expression via UPDATEs through the parent.)
3347 : : */
3348 [ + + ]: 56 : if (inhdef->generated)
3349 : : {
3350 [ + + + + ]: 10 : if (newdef->raw_default && !newdef->generated)
3351 [ + - + - ]: 2 : ereport(ERROR,
3352 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3353 : : errmsg("column \"%s\" inherits from generated column but specifies default",
3354 : : inhdef->colname)));
3355 [ + + ]: 8 : if (newdef->identity)
3356 [ + - + - ]: 2 : ereport(ERROR,
3357 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3358 : : errmsg("column \"%s\" inherits from generated column but specifies identity",
3359 : : inhdef->colname)));
3360 : 6 : }
3361 : : else
3362 : : {
3363 [ + + ]: 46 : if (newdef->generated)
3364 [ + - + - ]: 2 : ereport(ERROR,
3365 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3366 : : errmsg("child column \"%s\" specifies generation expression",
3367 : : inhdef->colname),
3368 : : errhint("A child table column cannot be generated unless its parent column is.")));
3369 : : }
3370 : :
3371 [ + + + - : 50 : if (inhdef->generated && newdef->generated && newdef->generated != inhdef->generated)
+ + ]
3372 [ + - + - ]: 2 : ereport(ERROR,
3373 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3374 : : errmsg("column \"%s\" inherits from generated column of different kind",
3375 : : inhdef->colname),
3376 : : errdetail("Parent column is %s, child column is %s.",
3377 : : inhdef->generated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL",
3378 : : newdef->generated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL")));
3379 : :
3380 : : /*
3381 : : * If new def has a default, override previous default
3382 : : */
3383 [ + + ]: 48 : if (newdef->raw_default != NULL)
3384 : : {
3385 : 5 : inhdef->raw_default = newdef->raw_default;
3386 : 5 : inhdef->cooked_default = newdef->cooked_default;
3387 : 5 : }
3388 : :
3389 : : /* Mark the column as locally defined */
3390 : 48 : inhdef->is_local = true;
3391 : 48 : }
3392 : :
3393 : : /*
3394 : : * MergeInheritedAttribute
3395 : : * Merge given parent attribute definition into specified attribute
3396 : : * inherited from the previous parents.
3397 : : *
3398 : : * Input arguments:
3399 : : * 'inh_columns' is the list of previously inherited ColumnDefs.
3400 : : * 'exist_attno' is the number the existing matching attribute in inh_columns.
3401 : : * 'newdef' is the new parent column/attribute definition to be merged.
3402 : : *
3403 : : * The matching ColumnDef in 'inh_columns' list is modified and returned.
3404 : : *
3405 : : * Notes:
3406 : : * - The attribute is merged according to the rules laid out in the prologue
3407 : : * of MergeAttributes().
3408 : : * - If matching inherited attribute exists but the new attribute can not be
3409 : : * merged into it, the function throws respective errors.
3410 : : * - A partition inherits from only a single parent. Hence this function is
3411 : : * applicable only to a regular inheritance.
3412 : : */
3413 : : static ColumnDef *
3414 : 60 : MergeInheritedAttribute(List *inh_columns,
3415 : : int exist_attno,
3416 : : const ColumnDef *newdef)
3417 : : {
3418 : 60 : char *attributeName = newdef->colname;
3419 : 60 : ColumnDef *prevdef;
3420 : 60 : Oid prevtypeid,
3421 : : newtypeid;
3422 : 60 : int32 prevtypmod,
3423 : : newtypmod;
3424 : 60 : Oid prevcollid,
3425 : : newcollid;
3426 : :
3427 [ - + + - ]: 60 : ereport(NOTICE,
3428 : : (errmsg("merging multiple inherited definitions of column \"%s\"",
3429 : : attributeName)));
3430 : 60 : prevdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
3431 : :
3432 : : /*
3433 : : * Must have the same type and typmod
3434 : : */
3435 : 60 : typenameTypeIdAndMod(NULL, prevdef->typeName, &prevtypeid, &prevtypmod);
3436 : 60 : typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
3437 [ + - ]: 60 : if (prevtypeid != newtypeid || prevtypmod != newtypmod)
3438 [ # # # # ]: 0 : ereport(ERROR,
3439 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
3440 : : errmsg("inherited column \"%s\" has a type conflict",
3441 : : attributeName),
3442 : : errdetail("%s versus %s",
3443 : : format_type_with_typemod(prevtypeid, prevtypmod),
3444 : : format_type_with_typemod(newtypeid, newtypmod))));
3445 : :
3446 : : /*
3447 : : * Must have the same collation
3448 : : */
3449 : 60 : prevcollid = GetColumnDefCollation(NULL, prevdef, prevtypeid);
3450 : 60 : newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
3451 [ + - ]: 60 : if (prevcollid != newcollid)
3452 [ # # # # ]: 0 : ereport(ERROR,
3453 : : (errcode(ERRCODE_COLLATION_MISMATCH),
3454 : : errmsg("inherited column \"%s\" has a collation conflict",
3455 : : attributeName),
3456 : : errdetail("\"%s\" versus \"%s\"",
3457 : : get_collation_name(prevcollid),
3458 : : get_collation_name(newcollid))));
3459 : :
3460 : : /*
3461 : : * Copy/check storage parameter
3462 : : */
3463 [ + - ]: 60 : if (prevdef->storage == 0)
3464 : 0 : prevdef->storage = newdef->storage;
3465 [ + + ]: 60 : else if (prevdef->storage != newdef->storage)
3466 [ + - + - ]: 1 : ereport(ERROR,
3467 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
3468 : : errmsg("inherited column \"%s\" has a storage parameter conflict",
3469 : : attributeName),
3470 : : errdetail("%s versus %s",
3471 : : storage_name(prevdef->storage),
3472 : : storage_name(newdef->storage))));
3473 : :
3474 : : /*
3475 : : * Copy/check compression parameter
3476 : : */
3477 [ + + ]: 59 : if (prevdef->compression == NULL)
3478 : 56 : prevdef->compression = newdef->compression;
3479 [ + + ]: 3 : else if (newdef->compression != NULL)
3480 : : {
3481 [ - + ]: 1 : if (strcmp(prevdef->compression, newdef->compression) != 0)
3482 [ + - + - ]: 1 : ereport(ERROR,
3483 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
3484 : : errmsg("column \"%s\" has a compression method conflict",
3485 : : attributeName),
3486 : : errdetail("%s versus %s",
3487 : : prevdef->compression, newdef->compression)));
3488 : 0 : }
3489 : :
3490 : : /*
3491 : : * Check for GENERATED conflicts
3492 : : */
3493 [ + + ]: 58 : if (prevdef->generated != newdef->generated)
3494 [ + - + - ]: 4 : ereport(ERROR,
3495 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
3496 : : errmsg("inherited column \"%s\" has a generation conflict",
3497 : : attributeName)));
3498 : :
3499 : : /*
3500 : : * Default and other constraints are handled by the caller.
3501 : : */
3502 : :
3503 [ + - + - ]: 108 : if (pg_add_s16_overflow(prevdef->inhcount, 1,
3504 : 54 : &prevdef->inhcount))
3505 [ # # # # ]: 0 : ereport(ERROR,
3506 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
3507 : : errmsg("too many inheritance parents"));
3508 : :
3509 : 108 : return prevdef;
3510 : 54 : }
3511 : :
3512 : : /*
3513 : : * StoreCatalogInheritance
3514 : : * Updates the system catalogs with proper inheritance information.
3515 : : *
3516 : : * supers is a list of the OIDs of the new relation's direct ancestors.
3517 : : */
3518 : : static void
3519 : 5335 : StoreCatalogInheritance(Oid relationId, List *supers,
3520 : : bool child_is_partition)
3521 : : {
3522 : 5335 : Relation relation;
3523 : 5335 : int32 seqNumber;
3524 : 5335 : ListCell *entry;
3525 : :
3526 : : /*
3527 : : * sanity checks
3528 : : */
3529 [ + - ]: 5335 : Assert(OidIsValid(relationId));
3530 : :
3531 [ + + ]: 5335 : if (supers == NIL)
3532 : 3925 : return;
3533 : :
3534 : : /*
3535 : : * Store INHERITS information in pg_inherits using direct ancestors only.
3536 : : * Also enter dependencies on the direct ancestors, and make sure they are
3537 : : * marked with relhassubclass = true.
3538 : : *
3539 : : * (Once upon a time, both direct and indirect ancestors were found here
3540 : : * and then entered into pg_ipl. Since that catalog doesn't exist
3541 : : * anymore, there's no need to look for indirect ancestors.)
3542 : : */
3543 : 1410 : relation = table_open(InheritsRelationId, RowExclusiveLock);
3544 : :
3545 : 1410 : seqNumber = 1;
3546 [ + - + + : 2875 : foreach(entry, supers)
+ + ]
3547 : : {
3548 : 1465 : Oid parentOid = lfirst_oid(entry);
3549 : :
3550 : 2930 : StoreCatalogInheritance1(relationId, parentOid, seqNumber, relation,
3551 : 1465 : child_is_partition);
3552 : 1465 : seqNumber++;
3553 : 1465 : }
3554 : :
3555 : 1410 : table_close(relation, RowExclusiveLock);
3556 [ - + ]: 5335 : }
3557 : :
3558 : : /*
3559 : : * Make catalog entries showing relationId as being an inheritance child
3560 : : * of parentOid. inhRelation is the already-opened pg_inherits catalog.
3561 : : */
3562 : : static void
3563 : 1854 : StoreCatalogInheritance1(Oid relationId, Oid parentOid,
3564 : : int32 seqNumber, Relation inhRelation,
3565 : : bool child_is_partition)
3566 : : {
3567 : 1854 : ObjectAddress childobject,
3568 : : parentobject;
3569 : :
3570 : : /* store the pg_inherits row */
3571 : 1854 : StoreSingleInheritance(relationId, parentOid, seqNumber);
3572 : :
3573 : : /*
3574 : : * Store a dependency too
3575 : : */
3576 : 1854 : parentobject.classId = RelationRelationId;
3577 : 1854 : parentobject.objectId = parentOid;
3578 : 1854 : parentobject.objectSubId = 0;
3579 : 1854 : childobject.classId = RelationRelationId;
3580 : 1854 : childobject.objectId = relationId;
3581 : 1854 : childobject.objectSubId = 0;
3582 : :
3583 : 1854 : recordDependencyOn(&childobject, &parentobject,
3584 : 1854 : child_dependency_type(child_is_partition));
3585 : :
3586 : : /*
3587 : : * Post creation hook of this inheritance. Since object_access_hook
3588 : : * doesn't take multiple object identifiers, we relay oid of parent
3589 : : * relation using auxiliary_id argument.
3590 : : */
3591 [ + - ]: 1854 : InvokeObjectPostAlterHookArg(InheritsRelationId,
3592 : : relationId, 0,
3593 : : parentOid, false);
3594 : :
3595 : : /*
3596 : : * Mark the parent as having subclasses.
3597 : : */
3598 : 1854 : SetRelationHasSubclass(parentOid, true);
3599 : 1854 : }
3600 : :
3601 : : /*
3602 : : * Look for an existing column entry with the given name.
3603 : : *
3604 : : * Returns the index (starting with 1) if attribute already exists in columns,
3605 : : * 0 if it doesn't.
3606 : : */
3607 : : static int
3608 : 3293 : findAttrByName(const char *attributeName, const List *columns)
3609 : : {
3610 : 3293 : ListCell *lc;
3611 : 3293 : int i = 1;
3612 : :
3613 [ + + + + : 6149 : foreach(lc, columns)
+ + + + ]
3614 : : {
3615 [ + + ]: 2856 : if (strcmp(attributeName, lfirst_node(ColumnDef, lc)->colname) == 0)
3616 : 121 : return i;
3617 : :
3618 : 2735 : i++;
3619 : 2735 : }
3620 : 3172 : return 0;
3621 : 3293 : }
3622 : :
3623 : :
3624 : : /*
3625 : : * SetRelationHasSubclass
3626 : : * Set the value of the relation's relhassubclass field in pg_class.
3627 : : *
3628 : : * It's always safe to set this field to true, because all SQL commands are
3629 : : * ready to see true and then find no children. On the other hand, commands
3630 : : * generally assume zero children if this is false.
3631 : : *
3632 : : * Caller must hold any self-exclusive lock until end of transaction. If the
3633 : : * new value is false, caller must have acquired that lock before reading the
3634 : : * evidence that justified the false value. That way, it properly waits if
3635 : : * another backend is simultaneously concluding no need to change the tuple
3636 : : * (new and old values are true).
3637 : : *
3638 : : * NOTE: an important side-effect of this operation is that an SI invalidation
3639 : : * message is sent out to all backends --- including me --- causing plans
3640 : : * referencing the relation to be rebuilt with the new list of children.
3641 : : * This must happen even if we find that no change is needed in the pg_class
3642 : : * row.
3643 : : */
3644 : : void
3645 : 2387 : SetRelationHasSubclass(Oid relationId, bool relhassubclass)
3646 : : {
3647 : 2387 : Relation relationRelation;
3648 : 2387 : HeapTuple tuple;
3649 : 2387 : Form_pg_class classtuple;
3650 : :
3651 [ + + + - ]: 2387 : Assert(CheckRelationOidLockedByMe(relationId,
3652 : : ShareUpdateExclusiveLock, false) ||
3653 : : CheckRelationOidLockedByMe(relationId,
3654 : : ShareRowExclusiveLock, true));
3655 : :
3656 : : /*
3657 : : * Fetch a modifiable copy of the tuple, modify it, update pg_class.
3658 : : */
3659 : 2387 : relationRelation = table_open(RelationRelationId, RowExclusiveLock);
3660 : 2387 : tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relationId));
3661 [ + - ]: 2387 : if (!HeapTupleIsValid(tuple))
3662 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", relationId);
3663 : 2387 : classtuple = (Form_pg_class) GETSTRUCT(tuple);
3664 : :
3665 [ + + ]: 2387 : if (classtuple->relhassubclass != relhassubclass)
3666 : : {
3667 : 1118 : classtuple->relhassubclass = relhassubclass;
3668 : 1118 : CatalogTupleUpdate(relationRelation, &tuple->t_self, tuple);
3669 : 1118 : }
3670 : : else
3671 : : {
3672 : : /* no need to change tuple, but force relcache rebuild anyway */
3673 : 1269 : CacheInvalidateRelcacheByTuple(tuple);
3674 : : }
3675 : :
3676 : 2387 : heap_freetuple(tuple);
3677 : 2387 : table_close(relationRelation, RowExclusiveLock);
3678 : 2387 : }
3679 : :
3680 : : /*
3681 : : * CheckRelationTableSpaceMove
3682 : : * Check if relation can be moved to new tablespace.
3683 : : *
3684 : : * NOTE: The caller must hold AccessExclusiveLock on the relation.
3685 : : *
3686 : : * Returns true if the relation can be moved to the new tablespace; raises
3687 : : * an error if it is not possible to do the move; returns false if the move
3688 : : * would have no effect.
3689 : : */
3690 : : bool
3691 : 66 : CheckRelationTableSpaceMove(Relation rel, Oid newTableSpaceId)
3692 : : {
3693 : 66 : Oid oldTableSpaceId;
3694 : :
3695 : : /*
3696 : : * No work if no change in tablespace. Note that MyDatabaseTableSpace is
3697 : : * stored as 0.
3698 : : */
3699 : 66 : oldTableSpaceId = rel->rd_rel->reltablespace;
3700 [ + + + + ]: 101 : if (newTableSpaceId == oldTableSpaceId ||
3701 [ + + ]: 65 : (newTableSpaceId == MyDatabaseTableSpace && oldTableSpaceId == 0))
3702 : 2 : return false;
3703 : :
3704 : : /*
3705 : : * We cannot support moving mapped relations into different tablespaces.
3706 : : * (In particular this eliminates all shared catalogs.)
3707 : : */
3708 [ + + + + : 64 : if (RelationIsMapped(rel))
+ - + + +
- ]
3709 [ # # # # ]: 0 : ereport(ERROR,
3710 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3711 : : errmsg("cannot move system relation \"%s\"",
3712 : : RelationGetRelationName(rel))));
3713 : :
3714 : : /* Cannot move a non-shared relation into pg_global */
3715 [ + + ]: 64 : if (newTableSpaceId == GLOBALTABLESPACE_OID)
3716 [ + - + - ]: 2 : ereport(ERROR,
3717 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
3718 : : errmsg("only shared relations can be placed in pg_global tablespace")));
3719 : :
3720 : : /*
3721 : : * Do not allow moving temp tables of other backends ... their local
3722 : : * buffer manager is not going to cope.
3723 : : */
3724 [ + + + - ]: 62 : if (RELATION_IS_OTHER_TEMP(rel))
3725 [ # # # # ]: 0 : ereport(ERROR,
3726 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3727 : : errmsg("cannot move temporary tables of other sessions")));
3728 : :
3729 : 62 : return true;
3730 : 64 : }
3731 : :
3732 : : /*
3733 : : * SetRelationTableSpace
3734 : : * Set new reltablespace and relfilenumber in pg_class entry.
3735 : : *
3736 : : * newTableSpaceId is the new tablespace for the relation, and
3737 : : * newRelFilenumber its new filenumber. If newRelFilenumber is
3738 : : * InvalidRelFileNumber, this field is not updated.
3739 : : *
3740 : : * NOTE: The caller must hold AccessExclusiveLock on the relation.
3741 : : *
3742 : : * The caller of this routine had better check if a relation can be
3743 : : * moved to this new tablespace by calling CheckRelationTableSpaceMove()
3744 : : * first, and is responsible for making the change visible with
3745 : : * CommandCounterIncrement().
3746 : : */
3747 : : void
3748 : 31 : SetRelationTableSpace(Relation rel,
3749 : : Oid newTableSpaceId,
3750 : : RelFileNumber newRelFilenumber)
3751 : : {
3752 : 31 : Relation pg_class;
3753 : 31 : HeapTuple tuple;
3754 : 31 : ItemPointerData otid;
3755 : 31 : Form_pg_class rd_rel;
3756 : 31 : Oid reloid = RelationGetRelid(rel);
3757 : :
3758 [ + - ]: 31 : Assert(CheckRelationTableSpaceMove(rel, newTableSpaceId));
3759 : :
3760 : : /* Get a modifiable copy of the relation's pg_class row. */
3761 : 31 : pg_class = table_open(RelationRelationId, RowExclusiveLock);
3762 : :
3763 : 31 : tuple = SearchSysCacheLockedCopy1(RELOID, ObjectIdGetDatum(reloid));
3764 [ + - ]: 31 : if (!HeapTupleIsValid(tuple))
3765 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", reloid);
3766 : 31 : otid = tuple->t_self;
3767 : 31 : rd_rel = (Form_pg_class) GETSTRUCT(tuple);
3768 : :
3769 : : /* Update the pg_class row. */
3770 [ + + ]: 31 : rd_rel->reltablespace = (newTableSpaceId == MyDatabaseTableSpace) ?
3771 : 14 : InvalidOid : newTableSpaceId;
3772 [ + + ]: 31 : if (RelFileNumberIsValid(newRelFilenumber))
3773 : 24 : rd_rel->relfilenode = newRelFilenumber;
3774 : 31 : CatalogTupleUpdate(pg_class, &otid, tuple);
3775 : 31 : UnlockTuple(pg_class, &otid, InplaceUpdateTupleLock);
3776 : :
3777 : : /*
3778 : : * Record dependency on tablespace. This is only required for relations
3779 : : * that have no physical storage.
3780 : : */
3781 [ + + + + : 31 : if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
+ - + + +
+ ]
3782 : 10 : changeDependencyOnTablespace(RelationRelationId, reloid,
3783 : 5 : rd_rel->reltablespace);
3784 : :
3785 : 31 : heap_freetuple(tuple);
3786 : 31 : table_close(pg_class, RowExclusiveLock);
3787 : 31 : }
3788 : :
3789 : : /*
3790 : : * renameatt_check - basic sanity checks before attribute rename
3791 : : */
3792 : : static void
3793 : 164 : renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
3794 : : {
3795 : 164 : char relkind = classform->relkind;
3796 : :
3797 [ + + + + ]: 164 : if (classform->reloftype && !recursing)
3798 [ + - + - ]: 1 : ereport(ERROR,
3799 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
3800 : : errmsg("cannot rename column of typed table")));
3801 : :
3802 : : /*
3803 : : * Renaming the columns of sequences or toast tables doesn't actually
3804 : : * break anything from the system's point of view, since internal
3805 : : * references are by attnum. But it doesn't seem right to allow users to
3806 : : * change names that are hardcoded into the system, hence the following
3807 : : * restriction.
3808 : : */
3809 [ + + ]: 163 : if (relkind != RELKIND_RELATION &&
3810 [ + + ]: 22 : relkind != RELKIND_VIEW &&
3811 [ + - ]: 14 : relkind != RELKIND_MATVIEW &&
3812 [ + + ]: 14 : relkind != RELKIND_COMPOSITE_TYPE &&
3813 [ + - ]: 6 : relkind != RELKIND_INDEX &&
3814 [ + - ]: 6 : relkind != RELKIND_PARTITIONED_INDEX &&
3815 [ - + # # ]: 6 : relkind != RELKIND_FOREIGN_TABLE &&
3816 : 0 : relkind != RELKIND_PARTITIONED_TABLE)
3817 [ # # # # ]: 0 : ereport(ERROR,
3818 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
3819 : : errmsg("cannot rename columns of relation \"%s\"",
3820 : : NameStr(classform->relname)),
3821 : : errdetail_relkind_not_supported(relkind)));
3822 : :
3823 : : /*
3824 : : * permissions checking. only the owner of a class can change its schema.
3825 : : */
3826 [ + - ]: 163 : if (!object_ownercheck(RelationRelationId, myrelid, GetUserId()))
3827 : 0 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(myrelid)),
3828 : 0 : NameStr(classform->relname));
3829 [ + - + - ]: 163 : if (!allowSystemTableMods && IsSystemClass(myrelid, classform))
3830 [ # # # # ]: 0 : ereport(ERROR,
3831 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
3832 : : errmsg("permission denied: \"%s\" is a system catalog",
3833 : : NameStr(classform->relname))));
3834 : 163 : }
3835 : :
3836 : : /*
3837 : : * renameatt_internal - workhorse for renameatt
3838 : : *
3839 : : * Return value is the attribute number in the 'myrelid' relation.
3840 : : */
3841 : : static AttrNumber
3842 : 85 : renameatt_internal(Oid myrelid,
3843 : : const char *oldattname,
3844 : : const char *newattname,
3845 : : bool recurse,
3846 : : bool recursing,
3847 : : int expected_parents,
3848 : : DropBehavior behavior)
3849 : : {
3850 : 85 : Relation targetrelation;
3851 : 85 : Relation attrelation;
3852 : 85 : HeapTuple atttup;
3853 : 85 : Form_pg_attribute attform;
3854 : 85 : AttrNumber attnum;
3855 : :
3856 : : /*
3857 : : * Grab an exclusive lock on the target table, which we will NOT release
3858 : : * until end of transaction.
3859 : : */
3860 : 85 : targetrelation = relation_open(myrelid, AccessExclusiveLock);
3861 : 85 : renameatt_check(myrelid, RelationGetForm(targetrelation), recursing);
3862 : :
3863 : : /*
3864 : : * if the 'recurse' flag is set then we are supposed to rename this
3865 : : * attribute in all classes that inherit from 'relname' (as well as in
3866 : : * 'relname').
3867 : : *
3868 : : * any permissions or problems with duplicate attributes will cause the
3869 : : * whole transaction to abort, which is what we want -- all or nothing.
3870 : : */
3871 [ + + ]: 85 : if (recurse)
3872 : : {
3873 : 35 : List *child_oids,
3874 : : *child_numparents;
3875 : 35 : ListCell *lo,
3876 : : *li;
3877 : :
3878 : : /*
3879 : : * we need the number of parents for each child so that the recursive
3880 : : * calls to renameatt() can determine whether there are any parents
3881 : : * outside the inheritance hierarchy being processed.
3882 : : */
3883 : 35 : child_oids = find_all_inheritors(myrelid, AccessExclusiveLock,
3884 : : &child_numparents);
3885 : :
3886 : : /*
3887 : : * find_all_inheritors does the recursive search of the inheritance
3888 : : * hierarchy, so all we have to do is process all of the relids in the
3889 : : * list that it returns.
3890 : : */
3891 [ + - + + : 119 : forboth(lo, child_oids, li, child_numparents)
+ - + + +
+ + + ]
3892 : : {
3893 : 84 : Oid childrelid = lfirst_oid(lo);
3894 : 84 : int numparents = lfirst_int(li);
3895 : :
3896 [ + + ]: 84 : if (childrelid == myrelid)
3897 : 40 : continue;
3898 : : /* note we need not recurse again */
3899 : 44 : renameatt_internal(childrelid, oldattname, newattname, false, true, numparents, behavior);
3900 [ - + + ]: 84 : }
3901 : 35 : }
3902 : : else
3903 : : {
3904 : : /*
3905 : : * If we are told not to recurse, there had better not be any child
3906 : : * tables; else the rename would put them out of step.
3907 : : *
3908 : : * expected_parents will only be 0 if we are not already recursing.
3909 : : */
3910 [ + + + + ]: 50 : if (expected_parents == 0 &&
3911 : 6 : find_inheritance_children(myrelid, NoLock) != NIL)
3912 [ + - + - ]: 2 : ereport(ERROR,
3913 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
3914 : : errmsg("inherited column \"%s\" must be renamed in child tables too",
3915 : : oldattname)));
3916 : : }
3917 : :
3918 : : /* rename attributes in typed tables of composite type */
3919 [ + + ]: 83 : if (targetrelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
3920 : : {
3921 : 3 : List *child_oids;
3922 : 3 : ListCell *lo;
3923 : :
3924 : 6 : child_oids = find_typed_table_dependencies(targetrelation->rd_rel->reltype,
3925 : 3 : RelationGetRelationName(targetrelation),
3926 : 3 : behavior);
3927 : :
3928 [ + + + + : 4 : foreach(lo, child_oids)
+ + ]
3929 : 1 : renameatt_internal(lfirst_oid(lo), oldattname, newattname, true, true, 0, behavior);
3930 : 3 : }
3931 : :
3932 : 83 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
3933 : :
3934 : 83 : atttup = SearchSysCacheCopyAttName(myrelid, oldattname);
3935 [ + + ]: 83 : if (!HeapTupleIsValid(atttup))
3936 [ + - + - ]: 4 : ereport(ERROR,
3937 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
3938 : : errmsg("column \"%s\" does not exist",
3939 : : oldattname)));
3940 : 79 : attform = (Form_pg_attribute) GETSTRUCT(atttup);
3941 : :
3942 : 79 : attnum = attform->attnum;
3943 [ + - ]: 79 : if (attnum <= 0)
3944 [ # # # # ]: 0 : ereport(ERROR,
3945 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3946 : : errmsg("cannot rename system column \"%s\"",
3947 : : oldattname)));
3948 : :
3949 : : /*
3950 : : * if the attribute is inherited, forbid the renaming. if this is a
3951 : : * top-level call to renameatt(), then expected_parents will be 0, so the
3952 : : * effect of this code will be to prohibit the renaming if the attribute
3953 : : * is inherited at all. if this is a recursive call to renameatt(),
3954 : : * expected_parents will be the number of parents the current relation has
3955 : : * within the inheritance hierarchy being processed, so we'll prohibit the
3956 : : * renaming only if there are additional parents from elsewhere.
3957 : : */
3958 [ + + ]: 79 : if (attform->attinhcount > expected_parents)
3959 [ + - + - ]: 5 : ereport(ERROR,
3960 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
3961 : : errmsg("cannot rename inherited column \"%s\"",
3962 : : oldattname)));
3963 : :
3964 : : /* new name should not already exist */
3965 : 74 : (void) check_for_column_name_collision(targetrelation, newattname, false);
3966 : :
3967 : : /* apply the update */
3968 : 74 : namestrcpy(&(attform->attname), newattname);
3969 : :
3970 : 74 : CatalogTupleUpdate(attrelation, &atttup->t_self, atttup);
3971 : :
3972 [ - + ]: 74 : InvokeObjectPostAlterHook(RelationRelationId, myrelid, attnum);
3973 : :
3974 : 74 : heap_freetuple(atttup);
3975 : :
3976 : 74 : table_close(attrelation, RowExclusiveLock);
3977 : :
3978 : 74 : relation_close(targetrelation, NoLock); /* close rel but keep lock */
3979 : :
3980 : 148 : return attnum;
3981 : 74 : }
3982 : :
3983 : : /*
3984 : : * Perform permissions and integrity checks before acquiring a relation lock.
3985 : : */
3986 : : static void
3987 : 66 : RangeVarCallbackForRenameAttribute(const RangeVar *rv, Oid relid, Oid oldrelid,
3988 : : void *arg)
3989 : : {
3990 : 66 : HeapTuple tuple;
3991 : 66 : Form_pg_class form;
3992 : :
3993 : 66 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
3994 [ + + ]: 66 : if (!HeapTupleIsValid(tuple))
3995 : 6 : return; /* concurrently dropped */
3996 : 60 : form = (Form_pg_class) GETSTRUCT(tuple);
3997 : 60 : renameatt_check(relid, form, false);
3998 : 60 : ReleaseSysCache(tuple);
3999 [ - + ]: 66 : }
4000 : :
4001 : : /*
4002 : : * renameatt - changes the name of an attribute in a relation
4003 : : *
4004 : : * The returned ObjectAddress is that of the renamed column.
4005 : : */
4006 : : ObjectAddress
4007 : 35 : renameatt(RenameStmt *stmt)
4008 : : {
4009 : 35 : Oid relid;
4010 : 35 : AttrNumber attnum;
4011 : 35 : ObjectAddress address;
4012 : :
4013 : : /* lock level taken here should match renameatt_internal */
4014 : 70 : relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
4015 : 35 : stmt->missing_ok ? RVR_MISSING_OK : 0,
4016 : : RangeVarCallbackForRenameAttribute,
4017 : : NULL);
4018 : :
4019 [ + + ]: 35 : if (!OidIsValid(relid))
4020 : : {
4021 [ - + + - ]: 4 : ereport(NOTICE,
4022 : : (errmsg("relation \"%s\" does not exist, skipping",
4023 : : stmt->relation->relname)));
4024 : 4 : return InvalidObjectAddress;
4025 : : }
4026 : :
4027 : 31 : attnum =
4028 : 62 : renameatt_internal(relid,
4029 : 31 : stmt->subname, /* old att name */
4030 : 31 : stmt->newname, /* new att name */
4031 : 31 : stmt->relation->inh, /* recursive? */
4032 : : false, /* recursing? */
4033 : : 0, /* expected inhcount */
4034 : 31 : stmt->behavior);
4035 : :
4036 : 31 : ObjectAddressSubSet(address, RelationRelationId, relid, attnum);
4037 : :
4038 : 31 : return address;
4039 : 35 : }
4040 : :
4041 : : /*
4042 : : * same logic as renameatt_internal
4043 : : */
4044 : : static ObjectAddress
4045 : 15 : rename_constraint_internal(Oid myrelid,
4046 : : Oid mytypid,
4047 : : const char *oldconname,
4048 : : const char *newconname,
4049 : : bool recurse,
4050 : : bool recursing,
4051 : : int expected_parents)
4052 : : {
4053 : 15 : Relation targetrelation = NULL;
4054 : 15 : Oid constraintOid;
4055 : 15 : HeapTuple tuple;
4056 : 15 : Form_pg_constraint con;
4057 : : ObjectAddress address;
4058 : :
4059 [ + + + - ]: 15 : Assert(!myrelid || !mytypid);
4060 : :
4061 [ + + ]: 15 : if (mytypid)
4062 : : {
4063 : 1 : constraintOid = get_domain_constraint_oid(mytypid, oldconname, false);
4064 : 1 : }
4065 : : else
4066 : : {
4067 : 14 : targetrelation = relation_open(myrelid, AccessExclusiveLock);
4068 : :
4069 : : /*
4070 : : * don't tell it whether we're recursing; we allow changing typed
4071 : : * tables here
4072 : : */
4073 : 14 : renameatt_check(myrelid, RelationGetForm(targetrelation), false);
4074 : :
4075 : 14 : constraintOid = get_relation_constraint_oid(myrelid, oldconname, false);
4076 : : }
4077 : :
4078 : 15 : tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraintOid));
4079 [ + - ]: 15 : if (!HeapTupleIsValid(tuple))
4080 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u",
4081 : : constraintOid);
4082 : 15 : con = (Form_pg_constraint) GETSTRUCT(tuple);
4083 : :
4084 [ + + ]: 15 : if (myrelid &&
4085 [ + + ]: 14 : (con->contype == CONSTRAINT_CHECK ||
4086 [ + + ]: 14 : con->contype == CONSTRAINT_NOTNULL) &&
4087 : 14 : !con->connoinherit)
4088 : : {
4089 [ + + ]: 9 : if (recurse)
4090 : : {
4091 : 6 : List *child_oids,
4092 : : *child_numparents;
4093 : 6 : ListCell *lo,
4094 : : *li;
4095 : :
4096 : 6 : child_oids = find_all_inheritors(myrelid, AccessExclusiveLock,
4097 : : &child_numparents);
4098 : :
4099 [ + - + + : 14 : forboth(lo, child_oids, li, child_numparents)
+ - + + +
+ + + ]
4100 : : {
4101 : 8 : Oid childrelid = lfirst_oid(lo);
4102 : 8 : int numparents = lfirst_int(li);
4103 : :
4104 [ + + ]: 8 : if (childrelid == myrelid)
4105 : 6 : continue;
4106 : :
4107 : 2 : rename_constraint_internal(childrelid, InvalidOid, oldconname, newconname, false, true, numparents);
4108 [ - + + ]: 8 : }
4109 : 6 : }
4110 : : else
4111 : : {
4112 [ + + - + ]: 3 : if (expected_parents == 0 &&
4113 : 1 : find_inheritance_children(myrelid, NoLock) != NIL)
4114 [ + - + - ]: 1 : ereport(ERROR,
4115 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
4116 : : errmsg("inherited constraint \"%s\" must be renamed in child tables too",
4117 : : oldconname)));
4118 : : }
4119 : :
4120 [ + + ]: 8 : if (con->coninhcount > expected_parents)
4121 [ + - + - ]: 1 : ereport(ERROR,
4122 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
4123 : : errmsg("cannot rename inherited constraint \"%s\"",
4124 : : oldconname)));
4125 : 7 : }
4126 : :
4127 : 13 : if (con->conindid
4128 [ + + # # ]: 13 : && (con->contype == CONSTRAINT_PRIMARY
4129 [ + + ]: 3 : || con->contype == CONSTRAINT_UNIQUE
4130 [ - + ]: 1 : || con->contype == CONSTRAINT_EXCLUSION))
4131 : : /* rename the index; this renames the constraint as well */
4132 : 3 : RenameRelationInternal(con->conindid, newconname, false, true);
4133 : : else
4134 : 10 : RenameConstraintById(constraintOid, newconname);
4135 : :
4136 : 13 : ObjectAddressSet(address, ConstraintRelationId, constraintOid);
4137 : :
4138 : 13 : ReleaseSysCache(tuple);
4139 : :
4140 [ + + ]: 13 : if (targetrelation)
4141 : : {
4142 : : /*
4143 : : * Invalidate relcache so as others can see the new constraint name.
4144 : : */
4145 : 12 : CacheInvalidateRelcache(targetrelation);
4146 : :
4147 : 12 : relation_close(targetrelation, NoLock); /* close rel but keep lock */
4148 : 12 : }
4149 : :
4150 : : return address;
4151 : 13 : }
4152 : :
4153 : : ObjectAddress
4154 : 14 : RenameConstraint(RenameStmt *stmt)
4155 : : {
4156 : 14 : Oid relid = InvalidOid;
4157 : 14 : Oid typid = InvalidOid;
4158 : :
4159 [ + + ]: 14 : if (stmt->renameType == OBJECT_DOMCONSTRAINT)
4160 : : {
4161 : 1 : Relation rel;
4162 : 1 : HeapTuple tup;
4163 : :
4164 : 1 : typid = typenameTypeId(NULL, makeTypeNameFromNameList(castNode(List, stmt->object)));
4165 : 1 : rel = table_open(TypeRelationId, RowExclusiveLock);
4166 : 1 : tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
4167 [ + - ]: 1 : if (!HeapTupleIsValid(tup))
4168 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for type %u", typid);
4169 : 1 : checkDomainOwner(tup);
4170 : 1 : ReleaseSysCache(tup);
4171 : 1 : table_close(rel, NoLock);
4172 : 1 : }
4173 : : else
4174 : : {
4175 : : /* lock level taken here should match rename_constraint_internal */
4176 : 26 : relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
4177 : 13 : stmt->missing_ok ? RVR_MISSING_OK : 0,
4178 : : RangeVarCallbackForRenameAttribute,
4179 : : NULL);
4180 [ + + ]: 13 : if (!OidIsValid(relid))
4181 : : {
4182 [ - + + - ]: 1 : ereport(NOTICE,
4183 : : (errmsg("relation \"%s\" does not exist, skipping",
4184 : : stmt->relation->relname)));
4185 : 1 : return InvalidObjectAddress;
4186 : : }
4187 : : }
4188 : :
4189 : 13 : return
4190 : 25 : rename_constraint_internal(relid, typid,
4191 : 13 : stmt->subname,
4192 : 13 : stmt->newname,
4193 [ + + ]: 13 : (stmt->relation &&
4194 : 12 : stmt->relation->inh), /* recursive? */
4195 : : false, /* recursing? */
4196 : : 0 /* expected inhcount */ );
4197 : 14 : }
4198 : :
4199 : : /*
4200 : : * Execute ALTER TABLE/INDEX/SEQUENCE/VIEW/MATERIALIZED VIEW/FOREIGN TABLE
4201 : : * RENAME
4202 : : */
4203 : : ObjectAddress
4204 : 43 : RenameRelation(RenameStmt *stmt)
4205 : : {
4206 : 43 : bool is_index_stmt = stmt->renameType == OBJECT_INDEX;
4207 : 43 : Oid relid;
4208 : 43 : ObjectAddress address;
4209 : :
4210 : : /*
4211 : : * Grab an exclusive lock on the target table, index, sequence, view,
4212 : : * materialized view, or foreign table, which we will NOT release until
4213 : : * end of transaction.
4214 : : *
4215 : : * Lock level used here should match RenameRelationInternal, to avoid lock
4216 : : * escalation. However, because ALTER INDEX can be used with any relation
4217 : : * type, we mustn't believe without verification.
4218 : : */
4219 : 49 : for (;;)
4220 : : {
4221 : 49 : LOCKMODE lockmode;
4222 : 49 : char relkind;
4223 : 49 : bool obj_is_index;
4224 : :
4225 : 49 : lockmode = is_index_stmt ? ShareUpdateExclusiveLock : AccessExclusiveLock;
4226 : :
4227 : 98 : relid = RangeVarGetRelidExtended(stmt->relation, lockmode,
4228 : 49 : stmt->missing_ok ? RVR_MISSING_OK : 0,
4229 : : RangeVarCallbackForAlterRelation,
4230 : 49 : stmt);
4231 : :
4232 [ + + ]: 49 : if (!OidIsValid(relid))
4233 : : {
4234 [ - + + - ]: 3 : ereport(NOTICE,
4235 : : (errmsg("relation \"%s\" does not exist, skipping",
4236 : : stmt->relation->relname)));
4237 : 3 : return InvalidObjectAddress;
4238 : : }
4239 : :
4240 : : /*
4241 : : * We allow mismatched statement and object types (e.g., ALTER INDEX
4242 : : * to rename a table), but we might've used the wrong lock level. If
4243 : : * that happens, retry with the correct lock level. We don't bother
4244 : : * if we already acquired AccessExclusiveLock with an index, however.
4245 : : */
4246 : 34 : relkind = get_rel_relkind(relid);
4247 [ + + ]: 34 : obj_is_index = (relkind == RELKIND_INDEX ||
4248 : 28 : relkind == RELKIND_PARTITIONED_INDEX);
4249 [ + + + + ]: 34 : if (obj_is_index || is_index_stmt == obj_is_index)
4250 : 32 : break;
4251 : :
4252 : 2 : UnlockRelationOid(relid, lockmode);
4253 : 2 : is_index_stmt = obj_is_index;
4254 [ + + + ]: 37 : }
4255 : :
4256 : : /* Do the work */
4257 : 30 : RenameRelationInternal(relid, stmt->newname, false, is_index_stmt);
4258 : :
4259 : 30 : ObjectAddressSet(address, RelationRelationId, relid);
4260 : :
4261 : 30 : return address;
4262 : 31 : }
4263 : :
4264 : : /*
4265 : : * RenameRelationInternal - change the name of a relation
4266 : : */
4267 : : void
4268 : 206 : RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bool is_index)
4269 : : {
4270 : 206 : Relation targetrelation;
4271 : 206 : Relation relrelation; /* for RELATION relation */
4272 : 206 : ItemPointerData otid;
4273 : 206 : HeapTuple reltup;
4274 : 206 : Form_pg_class relform;
4275 : 206 : Oid namespaceId;
4276 : :
4277 : : /*
4278 : : * Grab a lock on the target relation, which we will NOT release until end
4279 : : * of transaction. We need at least a self-exclusive lock so that
4280 : : * concurrent DDL doesn't overwrite the rename if they start updating
4281 : : * while still seeing the old version. The lock also guards against
4282 : : * triggering relcache reloads in concurrent sessions, which might not
4283 : : * handle this information changing under them. For indexes, we can use a
4284 : : * reduced lock level because RelationReloadIndexInfo() handles indexes
4285 : : * specially.
4286 : : */
4287 : 206 : targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
4288 : 206 : namespaceId = RelationGetNamespace(targetrelation);
4289 : :
4290 : : /*
4291 : : * Find relation's pg_class tuple, and make sure newrelname isn't in use.
4292 : : */
4293 : 206 : relrelation = table_open(RelationRelationId, RowExclusiveLock);
4294 : :
4295 : 206 : reltup = SearchSysCacheLockedCopy1(RELOID, ObjectIdGetDatum(myrelid));
4296 [ + - ]: 206 : if (!HeapTupleIsValid(reltup)) /* shouldn't happen */
4297 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", myrelid);
4298 : 206 : otid = reltup->t_self;
4299 : 206 : relform = (Form_pg_class) GETSTRUCT(reltup);
4300 : :
4301 [ + + ]: 206 : if (get_relname_relid(newrelname, namespaceId) != InvalidOid)
4302 [ + - + - ]: 2 : ereport(ERROR,
4303 : : (errcode(ERRCODE_DUPLICATE_TABLE),
4304 : : errmsg("relation \"%s\" already exists",
4305 : : newrelname)));
4306 : :
4307 : : /*
4308 : : * RenameRelation is careful not to believe the caller's idea of the
4309 : : * relation kind being handled. We don't have to worry about this, but
4310 : : * let's not be totally oblivious to it. We can process an index as
4311 : : * not-an-index, but not the other way around.
4312 : : */
4313 [ + + + + : 204 : Assert(!is_index ||
+ - ]
4314 : : is_index == (targetrelation->rd_rel->relkind == RELKIND_INDEX ||
4315 : : targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX));
4316 : :
4317 : : /*
4318 : : * Update pg_class tuple with new relname. (Scribbling on reltup is OK
4319 : : * because it's a copy...)
4320 : : */
4321 : 204 : namestrcpy(&(relform->relname), newrelname);
4322 : :
4323 : 204 : CatalogTupleUpdate(relrelation, &otid, reltup);
4324 : 204 : UnlockTuple(relrelation, &otid, InplaceUpdateTupleLock);
4325 : :
4326 [ + - ]: 204 : InvokeObjectPostAlterHookArg(RelationRelationId, myrelid, 0,
4327 : : InvalidOid, is_internal);
4328 : :
4329 : 204 : heap_freetuple(reltup);
4330 : 204 : table_close(relrelation, RowExclusiveLock);
4331 : :
4332 : : /*
4333 : : * Also rename the associated type, if any.
4334 : : */
4335 [ + + ]: 204 : if (OidIsValid(targetrelation->rd_rel->reltype))
4336 : 58 : RenameTypeInternal(targetrelation->rd_rel->reltype,
4337 : 29 : newrelname, namespaceId);
4338 : :
4339 : : /*
4340 : : * Also rename the associated constraint, if any.
4341 : : */
4342 [ + + + + ]: 204 : if (targetrelation->rd_rel->relkind == RELKIND_INDEX ||
4343 : 113 : targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
4344 : : {
4345 : 94 : Oid constraintId = get_index_constraint(myrelid);
4346 : :
4347 [ + + ]: 94 : if (OidIsValid(constraintId))
4348 : 6 : RenameConstraintById(constraintId, newrelname);
4349 : 94 : }
4350 : :
4351 : : /*
4352 : : * Close rel, but keep lock!
4353 : : */
4354 : 204 : relation_close(targetrelation, NoLock);
4355 : 204 : }
4356 : :
4357 : : /*
4358 : : * ResetRelRewrite - reset relrewrite
4359 : : */
4360 : : void
4361 : 79 : ResetRelRewrite(Oid myrelid)
4362 : : {
4363 : 79 : Relation relrelation; /* for RELATION relation */
4364 : 79 : HeapTuple reltup;
4365 : 79 : Form_pg_class relform;
4366 : :
4367 : : /*
4368 : : * Find relation's pg_class tuple.
4369 : : */
4370 : 79 : relrelation = table_open(RelationRelationId, RowExclusiveLock);
4371 : :
4372 : 79 : reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
4373 [ + - ]: 79 : if (!HeapTupleIsValid(reltup)) /* shouldn't happen */
4374 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", myrelid);
4375 : 79 : relform = (Form_pg_class) GETSTRUCT(reltup);
4376 : :
4377 : : /*
4378 : : * Update pg_class tuple.
4379 : : */
4380 : 79 : relform->relrewrite = InvalidOid;
4381 : :
4382 : 79 : CatalogTupleUpdate(relrelation, &reltup->t_self, reltup);
4383 : :
4384 : 79 : heap_freetuple(reltup);
4385 : 79 : table_close(relrelation, RowExclusiveLock);
4386 : 79 : }
4387 : :
4388 : : /*
4389 : : * Disallow ALTER TABLE (and similar commands) when the current backend has
4390 : : * any open reference to the target table besides the one just acquired by
4391 : : * the calling command; this implies there's an open cursor or active plan.
4392 : : * We need this check because our lock doesn't protect us against stomping
4393 : : * on our own foot, only other people's feet!
4394 : : *
4395 : : * For ALTER TABLE, the only case known to cause serious trouble is ALTER
4396 : : * COLUMN TYPE, and some changes are obviously pretty benign, so this could
4397 : : * possibly be relaxed to only error out for certain types of alterations.
4398 : : * But the use-case for allowing any of these things is not obvious, so we
4399 : : * won't work hard at it for now.
4400 : : *
4401 : : * We also reject these commands if there are any pending AFTER trigger events
4402 : : * for the rel. This is certainly necessary for the rewriting variants of
4403 : : * ALTER TABLE, because they don't preserve tuple TIDs and so the pending
4404 : : * events would try to fetch the wrong tuples. It might be overly cautious
4405 : : * in other cases, but again it seems better to err on the side of paranoia.
4406 : : *
4407 : : * REINDEX calls this with "rel" referencing the index to be rebuilt; here
4408 : : * we are worried about active indexscans on the index. The trigger-event
4409 : : * check can be skipped, since we are doing no damage to the parent table.
4410 : : *
4411 : : * The statement name (eg, "ALTER TABLE") is passed for use in error messages.
4412 : : */
4413 : : void
4414 : 17757 : CheckTableNotInUse(Relation rel, const char *stmt)
4415 : : {
4416 : 17757 : int expected_refcnt;
4417 : :
4418 : 17757 : expected_refcnt = rel->rd_isnailed ? 2 : 1;
4419 [ + + ]: 17757 : if (rel->rd_refcnt != expected_refcnt)
4420 [ + - + - ]: 7 : ereport(ERROR,
4421 : : (errcode(ERRCODE_OBJECT_IN_USE),
4422 : : /* translator: first %s is a SQL command, eg ALTER TABLE */
4423 : : errmsg("cannot %s \"%s\" because it is being used by active queries in this session",
4424 : : stmt, RelationGetRelationName(rel))));
4425 : :
4426 [ + + ]: 17750 : if (rel->rd_rel->relkind != RELKIND_INDEX &&
4427 [ + + + + ]: 14568 : rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
4428 : 14250 : AfterTriggerPendingOnRel(RelationGetRelid(rel)))
4429 [ + - + - ]: 3 : ereport(ERROR,
4430 : : (errcode(ERRCODE_OBJECT_IN_USE),
4431 : : /* translator: first %s is a SQL command, eg ALTER TABLE */
4432 : : errmsg("cannot %s \"%s\" because it has pending trigger events",
4433 : : stmt, RelationGetRelationName(rel))));
4434 : 17747 : }
4435 : :
4436 : : /*
4437 : : * CheckAlterTableIsSafe
4438 : : * Verify that it's safe to allow ALTER TABLE on this relation.
4439 : : *
4440 : : * This consists of CheckTableNotInUse() plus a check that the relation
4441 : : * isn't another session's temp table. We must split out the temp-table
4442 : : * check because there are callers of CheckTableNotInUse() that don't want
4443 : : * that, notably DROP TABLE. (We must allow DROP or we couldn't clean out
4444 : : * an orphaned temp schema.) Compare truncate_check_activity().
4445 : : */
4446 : : static void
4447 : 5287 : CheckAlterTableIsSafe(Relation rel)
4448 : : {
4449 : : /*
4450 : : * Don't allow ALTER on temp tables of other backends. Their local buffer
4451 : : * manager is not going to cope if we need to change the table's contents.
4452 : : * Even if we don't, there may be optimizations that assume temp tables
4453 : : * aren't subject to such interference.
4454 : : */
4455 [ + + + - ]: 5287 : if (RELATION_IS_OTHER_TEMP(rel))
4456 [ # # # # ]: 0 : ereport(ERROR,
4457 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
4458 : : errmsg("cannot alter temporary tables of other sessions")));
4459 : :
4460 : : /*
4461 : : * Also check for active uses of the relation in the current transaction,
4462 : : * including open scans and pending AFTER trigger events.
4463 : : */
4464 : 5287 : CheckTableNotInUse(rel, "ALTER TABLE");
4465 : 5287 : }
4466 : :
4467 : : /*
4468 : : * AlterTableLookupRelation
4469 : : * Look up, and lock, the OID for the relation named by an alter table
4470 : : * statement.
4471 : : */
4472 : : Oid
4473 : 2866 : AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
4474 : : {
4475 : 5732 : return RangeVarGetRelidExtended(stmt->relation, lockmode,
4476 : 2866 : stmt->missing_ok ? RVR_MISSING_OK : 0,
4477 : : RangeVarCallbackForAlterRelation,
4478 : 2866 : stmt);
4479 : : }
4480 : :
4481 : : /*
4482 : : * AlterTable
4483 : : * Execute ALTER TABLE, which can be a list of subcommands
4484 : : *
4485 : : * ALTER TABLE is performed in three phases:
4486 : : * 1. Examine subcommands and perform pre-transformation checking.
4487 : : * 2. Validate and transform subcommands, and update system catalogs.
4488 : : * 3. Scan table(s) to check new constraints, and optionally recopy
4489 : : * the data into new table(s).
4490 : : * Phase 3 is not performed unless one or more of the subcommands requires
4491 : : * it. The intention of this design is to allow multiple independent
4492 : : * updates of the table schema to be performed with only one pass over the
4493 : : * data.
4494 : : *
4495 : : * ATPrepCmd performs phase 1. A "work queue" entry is created for
4496 : : * each table to be affected (there may be multiple affected tables if the
4497 : : * commands traverse a table inheritance hierarchy). Also we do preliminary
4498 : : * validation of the subcommands. Because earlier subcommands may change
4499 : : * the catalog state seen by later commands, there are limits to what can
4500 : : * be done in this phase. Generally, this phase acquires table locks,
4501 : : * checks permissions and relkind, and recurses to find child tables.
4502 : : *
4503 : : * ATRewriteCatalogs performs phase 2 for each affected table.
4504 : : * Certain subcommands need to be performed before others to avoid
4505 : : * unnecessary conflicts; for example, DROP COLUMN should come before
4506 : : * ADD COLUMN. Therefore phase 1 divides the subcommands into multiple
4507 : : * lists, one for each logical "pass" of phase 2.
4508 : : *
4509 : : * ATRewriteTables performs phase 3 for those tables that need it.
4510 : : *
4511 : : * For most subcommand types, phases 2 and 3 do no explicit recursion,
4512 : : * since phase 1 already does it. However, for certain subcommand types
4513 : : * it is only possible to determine how to recurse at phase 2 time; for
4514 : : * those cases, phase 1 sets the cmd->recurse flag.
4515 : : *
4516 : : * Thanks to the magic of MVCC, an error anywhere along the way rolls back
4517 : : * the whole operation; we don't have to do anything special to clean up.
4518 : : *
4519 : : * The caller must lock the relation, with an appropriate lock level
4520 : : * for the subcommands requested, using AlterTableGetLockLevel(stmt->cmds)
4521 : : * or higher. We pass the lock level down
4522 : : * so that we can apply it recursively to inherited tables. Note that the
4523 : : * lock level we want as we recurse might well be higher than required for
4524 : : * that specific subcommand. So we pass down the overall lock requirement,
4525 : : * rather than reassess it at lower levels.
4526 : : *
4527 : : * The caller also provides a "context" which is to be passed back to
4528 : : * utility.c when we need to execute a subcommand such as CREATE INDEX.
4529 : : * Some of the fields therein, such as the relid, are used here as well.
4530 : : */
4531 : : void
4532 : 2828 : AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
4533 : : AlterTableUtilityContext *context)
4534 : : {
4535 : 2828 : Relation rel;
4536 : :
4537 : : /* Caller is required to provide an adequate lock. */
4538 : 2828 : rel = relation_open(context->relid, NoLock);
4539 : :
4540 : 2828 : CheckAlterTableIsSafe(rel);
4541 : :
4542 : 2828 : ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
4543 : 2828 : }
4544 : :
4545 : : /*
4546 : : * AlterTableInternal
4547 : : *
4548 : : * ALTER TABLE with target specified by OID
4549 : : *
4550 : : * We do not reject if the relation is already open, because it's quite
4551 : : * likely that one or more layers of caller have it open. That means it
4552 : : * is unsafe to use this entry point for alterations that could break
4553 : : * existing query plans. On the assumption it's not used for such, we
4554 : : * don't have to reject pending AFTER triggers, either.
4555 : : *
4556 : : * Also, since we don't have an AlterTableUtilityContext, this cannot be
4557 : : * used for any subcommand types that require parse transformation or
4558 : : * could generate subcommands that have to be passed to ProcessUtility.
4559 : : */
4560 : : void
4561 : 45 : AlterTableInternal(Oid relid, List *cmds, bool recurse)
4562 : : {
4563 : 45 : Relation rel;
4564 : 45 : LOCKMODE lockmode = AlterTableGetLockLevel(cmds);
4565 : :
4566 : 45 : rel = relation_open(relid, lockmode);
4567 : :
4568 : 45 : EventTriggerAlterTableRelid(relid);
4569 : :
4570 : 45 : ATController(NULL, rel, cmds, recurse, lockmode, NULL);
4571 : 45 : }
4572 : :
4573 : : /*
4574 : : * AlterTableGetLockLevel
4575 : : *
4576 : : * Sets the overall lock level required for the supplied list of subcommands.
4577 : : * Policy for doing this set according to needs of AlterTable(), see
4578 : : * comments there for overall explanation.
4579 : : *
4580 : : * Function is called before and after parsing, so it must give same
4581 : : * answer each time it is called. Some subcommands are transformed
4582 : : * into other subcommand types, so the transform must never be made to a
4583 : : * lower lock level than previously assigned. All transforms are noted below.
4584 : : *
4585 : : * Since this is called before we lock the table we cannot use table metadata
4586 : : * to influence the type of lock we acquire.
4587 : : *
4588 : : * There should be no lockmodes hardcoded into the subcommand functions. All
4589 : : * lockmode decisions for ALTER TABLE are made here only. The one exception is
4590 : : * ALTER TABLE RENAME which is treated as a different statement type T_RenameStmt
4591 : : * and does not travel through this section of code and cannot be combined with
4592 : : * any of the subcommands given here.
4593 : : *
4594 : : * Note that Hot Standby only knows about AccessExclusiveLocks on the primary
4595 : : * so any changes that might affect SELECTs running on standbys need to use
4596 : : * AccessExclusiveLocks even if you think a lesser lock would do, unless you
4597 : : * have a solution for that also.
4598 : : *
4599 : : * Also note that pg_dump uses only an AccessShareLock, meaning that anything
4600 : : * that takes a lock less than AccessExclusiveLock can change object definitions
4601 : : * while pg_dump is running. Be careful to check that the appropriate data is
4602 : : * derived by pg_dump using an MVCC snapshot, rather than syscache lookups,
4603 : : * otherwise we might end up with an inconsistent dump that can't restore.
4604 : : */
4605 : : LOCKMODE
4606 : 2911 : AlterTableGetLockLevel(List *cmds)
4607 : : {
4608 : : /*
4609 : : * This only works if we read catalog tables using MVCC snapshots.
4610 : : */
4611 : 2911 : ListCell *lcmd;
4612 : 2911 : LOCKMODE lockmode = ShareUpdateExclusiveLock;
4613 : :
4614 [ + - + + : 6018 : foreach(lcmd, cmds)
+ + ]
4615 : : {
4616 : 3107 : AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
4617 : 3107 : LOCKMODE cmd_lockmode = AccessExclusiveLock; /* default for compiler */
4618 : :
4619 [ + + + + : 3107 : switch (cmd->subtype)
+ + + + +
+ + + + +
+ + + + +
+ - ]
4620 : : {
4621 : : /*
4622 : : * These subcommands rewrite the heap, so require full locks.
4623 : : */
4624 : : case AT_AddColumn: /* may rewrite heap, in some cases and visible
4625 : : * to SELECT */
4626 : : case AT_SetAccessMethod: /* must rewrite heap */
4627 : : case AT_SetTableSpace: /* must rewrite heap */
4628 : : case AT_AlterColumnType: /* must rewrite heap */
4629 : 545 : cmd_lockmode = AccessExclusiveLock;
4630 : 545 : break;
4631 : :
4632 : : /*
4633 : : * These subcommands may require addition of toast tables. If
4634 : : * we add a toast table to a table currently being scanned, we
4635 : : * might miss data added to the new toast table by concurrent
4636 : : * insert transactions.
4637 : : */
4638 : : case AT_SetStorage: /* may add toast tables, see
4639 : : * ATRewriteCatalogs() */
4640 : 28 : cmd_lockmode = AccessExclusiveLock;
4641 : 28 : break;
4642 : :
4643 : : /*
4644 : : * Removing constraints can affect SELECTs that have been
4645 : : * optimized assuming the constraint holds true. See also
4646 : : * CloneFkReferenced.
4647 : : */
4648 : : case AT_DropConstraint: /* as DROP INDEX */
4649 : : case AT_DropNotNull: /* may change some SQL plans */
4650 : 182 : cmd_lockmode = AccessExclusiveLock;
4651 : 182 : break;
4652 : :
4653 : : /*
4654 : : * Subcommands that may be visible to concurrent SELECTs
4655 : : */
4656 : : case AT_DropColumn: /* change visible to SELECT */
4657 : : case AT_AddColumnToView: /* CREATE VIEW */
4658 : : case AT_DropOids: /* used to equiv to DropColumn */
4659 : : case AT_EnableAlwaysRule: /* may change SELECT rules */
4660 : : case AT_EnableReplicaRule: /* may change SELECT rules */
4661 : : case AT_EnableRule: /* may change SELECT rules */
4662 : : case AT_DisableRule: /* may change SELECT rules */
4663 : 252 : cmd_lockmode = AccessExclusiveLock;
4664 : 252 : break;
4665 : :
4666 : : /*
4667 : : * Changing owner may remove implicit SELECT privileges
4668 : : */
4669 : : case AT_ChangeOwner: /* change visible to SELECT */
4670 : 46 : cmd_lockmode = AccessExclusiveLock;
4671 : 46 : break;
4672 : :
4673 : : /*
4674 : : * Changing foreign table options may affect optimization.
4675 : : */
4676 : : case AT_GenericOptions:
4677 : : case AT_AlterColumnGenericOptions:
4678 : 12 : cmd_lockmode = AccessExclusiveLock;
4679 : 12 : break;
4680 : :
4681 : : /*
4682 : : * These subcommands affect write operations only.
4683 : : */
4684 : : case AT_EnableTrig:
4685 : : case AT_EnableAlwaysTrig:
4686 : : case AT_EnableReplicaTrig:
4687 : : case AT_EnableTrigAll:
4688 : : case AT_EnableTrigUser:
4689 : : case AT_DisableTrig:
4690 : : case AT_DisableTrigAll:
4691 : : case AT_DisableTrigUser:
4692 : 20 : cmd_lockmode = ShareRowExclusiveLock;
4693 : 20 : break;
4694 : :
4695 : : /*
4696 : : * These subcommands affect write operations only. XXX
4697 : : * Theoretically, these could be ShareRowExclusiveLock.
4698 : : */
4699 : : case AT_ColumnDefault:
4700 : : case AT_CookedColumnDefault:
4701 : : case AT_AlterConstraint:
4702 : : case AT_AddIndex: /* from ADD CONSTRAINT */
4703 : : case AT_AddIndexConstraint:
4704 : : case AT_ReplicaIdentity:
4705 : : case AT_SetNotNull:
4706 : : case AT_EnableRowSecurity:
4707 : : case AT_DisableRowSecurity:
4708 : : case AT_ForceRowSecurity:
4709 : : case AT_NoForceRowSecurity:
4710 : : case AT_AddIdentity:
4711 : : case AT_DropIdentity:
4712 : : case AT_SetIdentity:
4713 : : case AT_SetExpression:
4714 : : case AT_DropExpression:
4715 : : case AT_SetCompression:
4716 : 434 : cmd_lockmode = AccessExclusiveLock;
4717 : 434 : break;
4718 : :
4719 : : case AT_AddConstraint:
4720 : : case AT_ReAddConstraint: /* becomes AT_AddConstraint */
4721 : : case AT_ReAddDomainConstraint: /* becomes AT_AddConstraint */
4722 [ - + ]: 765 : if (IsA(cmd->def, Constraint))
4723 : : {
4724 : 765 : Constraint *con = (Constraint *) cmd->def;
4725 : :
4726 [ + + + ]: 765 : switch (con->contype)
4727 : : {
4728 : : case CONSTR_EXCLUSION:
4729 : : case CONSTR_PRIMARY:
4730 : : case CONSTR_UNIQUE:
4731 : :
4732 : : /*
4733 : : * Cases essentially the same as CREATE INDEX. We
4734 : : * could reduce the lock strength to ShareLock if
4735 : : * we can work out how to allow concurrent catalog
4736 : : * updates. XXX Might be set down to
4737 : : * ShareRowExclusiveLock but requires further
4738 : : * analysis.
4739 : : */
4740 : 264 : cmd_lockmode = AccessExclusiveLock;
4741 : 264 : break;
4742 : : case CONSTR_FOREIGN:
4743 : :
4744 : : /*
4745 : : * We add triggers to both tables when we add a
4746 : : * Foreign Key, so the lock level must be at least
4747 : : * as strong as CREATE TRIGGER.
4748 : : */
4749 : 314 : cmd_lockmode = ShareRowExclusiveLock;
4750 : 314 : break;
4751 : :
4752 : : default:
4753 : 187 : cmd_lockmode = AccessExclusiveLock;
4754 : 187 : }
4755 : 765 : }
4756 : 765 : break;
4757 : :
4758 : : /*
4759 : : * These subcommands affect inheritance behaviour. Queries
4760 : : * started before us will continue to see the old inheritance
4761 : : * behaviour, while queries started after we commit will see
4762 : : * new behaviour. No need to prevent reads or writes to the
4763 : : * subtable while we hook it up though. Changing the TupDesc
4764 : : * may be a problem, so keep highest lock.
4765 : : */
4766 : : case AT_AddInherit:
4767 : : case AT_DropInherit:
4768 : 67 : cmd_lockmode = AccessExclusiveLock;
4769 : 67 : break;
4770 : :
4771 : : /*
4772 : : * These subcommands affect implicit row type conversion. They
4773 : : * have affects similar to CREATE/DROP CAST on queries. don't
4774 : : * provide for invalidating parse trees as a result of such
4775 : : * changes, so we keep these at AccessExclusiveLock.
4776 : : */
4777 : : case AT_AddOf:
4778 : : case AT_DropOf:
4779 : 10 : cmd_lockmode = AccessExclusiveLock;
4780 : 10 : break;
4781 : :
4782 : : /*
4783 : : * Only used by CREATE OR REPLACE VIEW which must conflict
4784 : : * with an SELECTs currently using the view.
4785 : : */
4786 : : case AT_ReplaceRelOptions:
4787 : 31 : cmd_lockmode = AccessExclusiveLock;
4788 : 31 : break;
4789 : :
4790 : : /*
4791 : : * These subcommands affect general strategies for performance
4792 : : * and maintenance, though don't change the semantic results
4793 : : * from normal data reads and writes. Delaying an ALTER TABLE
4794 : : * behind currently active writes only delays the point where
4795 : : * the new strategy begins to take effect, so there is no
4796 : : * benefit in waiting. In this case the minimum restriction
4797 : : * applies: we don't currently allow concurrent catalog
4798 : : * updates.
4799 : : */
4800 : : case AT_SetStatistics: /* Uses MVCC in getTableAttrs() */
4801 : : case AT_ClusterOn: /* Uses MVCC in getIndexes() */
4802 : : case AT_DropCluster: /* Uses MVCC in getIndexes() */
4803 : : case AT_SetOptions: /* Uses MVCC in getTableAttrs() */
4804 : : case AT_ResetOptions: /* Uses MVCC in getTableAttrs() */
4805 : 36 : cmd_lockmode = ShareUpdateExclusiveLock;
4806 : 36 : break;
4807 : :
4808 : : case AT_SetLogged:
4809 : : case AT_SetUnLogged:
4810 : 18 : cmd_lockmode = AccessExclusiveLock;
4811 : 18 : break;
4812 : :
4813 : : case AT_ValidateConstraint: /* Uses MVCC in getConstraints() */
4814 : 34 : cmd_lockmode = ShareUpdateExclusiveLock;
4815 : 34 : break;
4816 : :
4817 : : /*
4818 : : * Rel options are more complex than first appears. Options
4819 : : * are set here for tables, views and indexes; for historical
4820 : : * reasons these can all be used with ALTER TABLE, so we can't
4821 : : * decide between them using the basic grammar.
4822 : : */
4823 : : case AT_SetRelOptions: /* Uses MVCC in getIndexes() and
4824 : : * getTables() */
4825 : : case AT_ResetRelOptions: /* Uses MVCC in getIndexes() and
4826 : : * getTables() */
4827 : 89 : cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
4828 : 89 : break;
4829 : :
4830 : : case AT_AttachPartition:
4831 : 359 : cmd_lockmode = ShareUpdateExclusiveLock;
4832 : 359 : break;
4833 : :
4834 : : case AT_DetachPartition:
4835 [ + + ]: 76 : if (((PartitionCmd *) cmd->def)->concurrent)
4836 : 5 : cmd_lockmode = ShareUpdateExclusiveLock;
4837 : : else
4838 : 71 : cmd_lockmode = AccessExclusiveLock;
4839 : 76 : break;
4840 : :
4841 : : case AT_DetachPartitionFinalize:
4842 : 1 : cmd_lockmode = ShareUpdateExclusiveLock;
4843 : 1 : break;
4844 : :
4845 : : case AT_MergePartitions:
4846 : : case AT_SplitPartition:
4847 : 102 : cmd_lockmode = AccessExclusiveLock;
4848 : 102 : break;
4849 : :
4850 : : default: /* oops */
4851 [ # # # # ]: 0 : elog(ERROR, "unrecognized alter table type: %d",
4852 : : (int) cmd->subtype);
4853 : 0 : break;
4854 : : }
4855 : :
4856 : : /*
4857 : : * Take the greatest lockmode from any subcommand
4858 : : */
4859 [ + + ]: 3107 : if (cmd_lockmode > lockmode)
4860 : 2404 : lockmode = cmd_lockmode;
4861 : 3107 : }
4862 : :
4863 : 5822 : return lockmode;
4864 : 2911 : }
4865 : :
4866 : : /*
4867 : : * ATController provides top level control over the phases.
4868 : : *
4869 : : * parsetree is passed in to allow it to be passed to event triggers
4870 : : * when requested.
4871 : : */
4872 : : static void
4873 : 2803 : ATController(AlterTableStmt *parsetree,
4874 : : Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
4875 : : AlterTableUtilityContext *context)
4876 : : {
4877 : 2803 : List *wqueue = NIL;
4878 : 2803 : ListCell *lcmd;
4879 : :
4880 : : /* Phase 1: preliminary examination of commands, create work queue */
4881 [ + - + + : 5868 : foreach(lcmd, cmds)
+ + ]
4882 : : {
4883 : 3065 : AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
4884 : :
4885 : 3065 : ATPrepCmd(&wqueue, rel, cmd, recurse, false, lockmode, context);
4886 : 3065 : }
4887 : :
4888 : : /* Close the relation, but keep lock until commit */
4889 : 2803 : relation_close(rel, NoLock);
4890 : :
4891 : : /* Phase 2: update system catalogs */
4892 : 2803 : ATRewriteCatalogs(&wqueue, lockmode, context);
4893 : :
4894 : : /* Phase 3: scan/rewrite tables as needed, and run afterStmts */
4895 : 2803 : ATRewriteTables(parsetree, &wqueue, lockmode, context);
4896 : 2803 : }
4897 : :
4898 : : /*
4899 : : * ATPrepCmd
4900 : : *
4901 : : * Traffic cop for ALTER TABLE Phase 1 operations, including simple
4902 : : * recursion and permission checks.
4903 : : *
4904 : : * Caller must have acquired appropriate lock type on relation already.
4905 : : * This lock should be held until commit.
4906 : : */
4907 : : static void
4908 : 3194 : ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
4909 : : bool recurse, bool recursing, LOCKMODE lockmode,
4910 : : AlterTableUtilityContext *context)
4911 : : {
4912 : 3194 : AlteredTableInfo *tab;
4913 : 3194 : AlterTablePass pass = AT_PASS_UNSET;
4914 : :
4915 : : /* Find or create work queue entry for this table */
4916 : 3194 : tab = ATGetQueueEntry(wqueue, rel);
4917 : :
4918 : : /*
4919 : : * Disallow any ALTER TABLE other than ALTER TABLE DETACH FINALIZE on
4920 : : * partitions that are pending detach.
4921 : : */
4922 [ + + ]: 3194 : if (rel->rd_rel->relispartition &&
4923 [ + - + - ]: 164 : cmd->subtype != AT_DetachPartitionFinalize &&
4924 : 164 : PartitionHasPendingDetach(RelationGetRelid(rel)))
4925 [ # # # # ]: 0 : ereport(ERROR,
4926 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
4927 : : errmsg("cannot alter partition \"%s\" with an incomplete detach",
4928 : : RelationGetRelationName(rel)),
4929 : : errhint("Use ALTER TABLE ... DETACH PARTITION ... FINALIZE to complete the pending detach operation."));
4930 : :
4931 : : /*
4932 : : * Copy the original subcommand for each table, so we can scribble on it.
4933 : : * This avoids conflicts when different child tables need to make
4934 : : * different parse transformations (for example, the same column may have
4935 : : * different column numbers in different children).
4936 : : */
4937 : 3194 : cmd = copyObject(cmd);
4938 : :
4939 : : /*
4940 : : * Do permissions and relkind checking, recursion to child tables if
4941 : : * needed, and any additional phase-1 processing needed. (But beware of
4942 : : * adding any processing that looks at table details that another
4943 : : * subcommand could change. In some cases we reject multiple subcommands
4944 : : * that could try to change the same state in contrary ways.)
4945 : : */
4946 [ + + + + : 3194 : switch (cmd->subtype)
+ + + + +
+ + + + +
+ + + + +
+ + + - +
- + + + +
+ + + + +
+ + + + +
+ + - ]
4947 : : {
4948 : : case AT_AddColumn: /* ADD COLUMN */
4949 : 303 : ATSimplePermissions(cmd->subtype, rel,
4950 : : ATT_TABLE | ATT_PARTITIONED_TABLE |
4951 : : ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
4952 : 606 : ATPrepAddColumn(wqueue, rel, recurse, recursing, false, cmd,
4953 : 303 : lockmode, context);
4954 : : /* Recursion occurs during execution phase */
4955 : 303 : pass = AT_PASS_ADD_COL;
4956 : 303 : break;
4957 : : case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */
4958 : 4 : ATSimplePermissions(cmd->subtype, rel, ATT_VIEW);
4959 : 8 : ATPrepAddColumn(wqueue, rel, recurse, recursing, true, cmd,
4960 : 4 : lockmode, context);
4961 : : /* Recursion occurs during execution phase */
4962 : 4 : pass = AT_PASS_ADD_COL;
4963 : 4 : break;
4964 : : case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */
4965 : :
4966 : : /*
4967 : : * We allow defaults on views so that INSERT into a view can have
4968 : : * default-ish behavior. This works because the rewriter
4969 : : * substitutes default values into INSERTs before it expands
4970 : : * rules.
4971 : : */
4972 : 97 : ATSimplePermissions(cmd->subtype, rel,
4973 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
4974 : : ATT_FOREIGN_TABLE);
4975 : 97 : ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
4976 : : /* No command-specific prep needed */
4977 : 97 : pass = cmd->def ? AT_PASS_ADD_OTHERCONSTR : AT_PASS_DROP;
4978 : 97 : break;
4979 : : case AT_CookedColumnDefault: /* add a pre-cooked default */
4980 : : /* This is currently used only in CREATE TABLE */
4981 : : /* (so the permission check really isn't necessary) */
4982 : 13 : ATSimplePermissions(cmd->subtype, rel,
4983 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
4984 : : /* This command never recurses */
4985 : 13 : pass = AT_PASS_ADD_OTHERCONSTR;
4986 : 13 : break;
4987 : : case AT_AddIdentity:
4988 : 21 : ATSimplePermissions(cmd->subtype, rel,
4989 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
4990 : : ATT_FOREIGN_TABLE);
4991 : : /* Set up recursion for phase 2; no other prep needed */
4992 [ + + ]: 21 : if (recurse)
4993 : 20 : cmd->recurse = true;
4994 : 21 : pass = AT_PASS_ADD_OTHERCONSTR;
4995 : 21 : break;
4996 : : case AT_SetIdentity:
4997 : 10 : ATSimplePermissions(cmd->subtype, rel,
4998 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
4999 : : ATT_FOREIGN_TABLE);
5000 : : /* Set up recursion for phase 2; no other prep needed */
5001 [ + + ]: 10 : if (recurse)
5002 : 9 : cmd->recurse = true;
5003 : : /* This should run after AddIdentity, so do it in MISC pass */
5004 : 10 : pass = AT_PASS_MISC;
5005 : 10 : break;
5006 : : case AT_DropIdentity:
5007 : 9 : ATSimplePermissions(cmd->subtype, rel,
5008 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
5009 : : ATT_FOREIGN_TABLE);
5010 : : /* Set up recursion for phase 2; no other prep needed */
5011 [ + + ]: 9 : if (recurse)
5012 : 8 : cmd->recurse = true;
5013 : 9 : pass = AT_PASS_DROP;
5014 : 9 : break;
5015 : : case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
5016 : 43 : ATSimplePermissions(cmd->subtype, rel,
5017 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5018 : : /* Set up recursion for phase 2; no other prep needed */
5019 [ + + ]: 43 : if (recurse)
5020 : 40 : cmd->recurse = true;
5021 : 43 : pass = AT_PASS_DROP;
5022 : 43 : break;
5023 : : case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
5024 : 65 : ATSimplePermissions(cmd->subtype, rel,
5025 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5026 : : /* Set up recursion for phase 2; no other prep needed */
5027 [ + + ]: 65 : if (recurse)
5028 : 60 : cmd->recurse = true;
5029 : 65 : pass = AT_PASS_COL_ATTRS;
5030 : 65 : break;
5031 : : case AT_SetExpression: /* ALTER COLUMN SET EXPRESSION */
5032 : 37 : ATSimplePermissions(cmd->subtype, rel,
5033 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5034 : 37 : ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
5035 : 37 : pass = AT_PASS_SET_EXPRESSION;
5036 : 37 : break;
5037 : : case AT_DropExpression: /* ALTER COLUMN DROP EXPRESSION */
5038 : 14 : ATSimplePermissions(cmd->subtype, rel,
5039 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5040 : 14 : ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
5041 : 14 : ATPrepDropExpression(rel, cmd, recurse, recursing, lockmode);
5042 : 14 : pass = AT_PASS_DROP;
5043 : 14 : break;
5044 : : case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */
5045 : 25 : ATSimplePermissions(cmd->subtype, rel,
5046 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_MATVIEW |
5047 : : ATT_INDEX | ATT_PARTITIONED_INDEX | ATT_FOREIGN_TABLE);
5048 : 25 : ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
5049 : : /* No command-specific prep needed */
5050 : 25 : pass = AT_PASS_MISC;
5051 : 25 : break;
5052 : : case AT_SetOptions: /* ALTER COLUMN SET ( options ) */
5053 : : case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */
5054 : 7 : ATSimplePermissions(cmd->subtype, rel,
5055 : : ATT_TABLE | ATT_PARTITIONED_TABLE |
5056 : : ATT_MATVIEW | ATT_FOREIGN_TABLE);
5057 : : /* This command never recurses */
5058 : 7 : pass = AT_PASS_MISC;
5059 : 7 : break;
5060 : : case AT_SetStorage: /* ALTER COLUMN SET STORAGE */
5061 : 31 : ATSimplePermissions(cmd->subtype, rel,
5062 : : ATT_TABLE | ATT_PARTITIONED_TABLE |
5063 : : ATT_MATVIEW | ATT_FOREIGN_TABLE);
5064 : 31 : ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
5065 : : /* No command-specific prep needed */
5066 : 31 : pass = AT_PASS_MISC;
5067 : 31 : break;
5068 : : case AT_SetCompression: /* ALTER COLUMN SET COMPRESSION */
5069 : 8 : ATSimplePermissions(cmd->subtype, rel,
5070 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_MATVIEW);
5071 : : /* This command never recurses */
5072 : : /* No command-specific prep needed */
5073 : 8 : pass = AT_PASS_MISC;
5074 : 8 : break;
5075 : : case AT_DropColumn: /* DROP COLUMN */
5076 : 238 : ATSimplePermissions(cmd->subtype, rel,
5077 : : ATT_TABLE | ATT_PARTITIONED_TABLE |
5078 : : ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
5079 : 476 : ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd,
5080 : 238 : lockmode, context);
5081 : : /* Recursion occurs during execution phase */
5082 : 238 : pass = AT_PASS_DROP;
5083 : 238 : break;
5084 : : case AT_AddIndex: /* ADD INDEX */
5085 : 0 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_PARTITIONED_TABLE);
5086 : : /* This command never recurses */
5087 : : /* No command-specific prep needed */
5088 : 0 : pass = AT_PASS_ADD_INDEX;
5089 : 0 : break;
5090 : : case AT_AddConstraint: /* ADD CONSTRAINT */
5091 : 825 : ATSimplePermissions(cmd->subtype, rel,
5092 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5093 : 825 : ATPrepAddPrimaryKey(wqueue, rel, cmd, recurse, lockmode, context);
5094 [ + + ]: 825 : if (recurse)
5095 : : {
5096 : : /* recurses at exec time; lock descendants and set flag */
5097 : 816 : (void) find_all_inheritors(RelationGetRelid(rel), lockmode, NULL);
5098 : 816 : cmd->recurse = true;
5099 : 816 : }
5100 : 825 : pass = AT_PASS_ADD_CONSTR;
5101 : 825 : break;
5102 : : case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */
5103 : 0 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_PARTITIONED_TABLE);
5104 : : /* This command never recurses */
5105 : : /* No command-specific prep needed */
5106 : 0 : pass = AT_PASS_ADD_INDEXCONSTR;
5107 : 0 : break;
5108 : : case AT_DropConstraint: /* DROP CONSTRAINT */
5109 : 131 : ATSimplePermissions(cmd->subtype, rel,
5110 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5111 : 131 : ATCheckPartitionsNotInUse(rel, lockmode);
5112 : : /* Other recursion occurs during execution phase */
5113 : : /* No command-specific prep needed except saving recurse flag */
5114 [ + + ]: 131 : if (recurse)
5115 : 125 : cmd->recurse = true;
5116 : 131 : pass = AT_PASS_DROP;
5117 : 131 : break;
5118 : : case AT_AlterColumnType: /* ALTER COLUMN TYPE */
5119 : 223 : ATSimplePermissions(cmd->subtype, rel,
5120 : : ATT_TABLE | ATT_PARTITIONED_TABLE |
5121 : : ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
5122 : : /* See comments for ATPrepAlterColumnType */
5123 : 446 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, recurse, lockmode,
5124 : 223 : AT_PASS_UNSET, context);
5125 [ + - ]: 223 : Assert(cmd != NULL);
5126 : : /* Performs own recursion */
5127 : 446 : ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd,
5128 : 223 : lockmode, context);
5129 : 223 : pass = AT_PASS_ALTER_TYPE;
5130 : 223 : break;
5131 : : case AT_AlterColumnGenericOptions:
5132 : 7 : ATSimplePermissions(cmd->subtype, rel, ATT_FOREIGN_TABLE);
5133 : : /* This command never recurses */
5134 : : /* No command-specific prep needed */
5135 : 7 : pass = AT_PASS_MISC;
5136 : 7 : break;
5137 : : case AT_ChangeOwner: /* ALTER OWNER */
5138 : : /* This command never recurses */
5139 : : /* No command-specific prep needed */
5140 : 42 : pass = AT_PASS_MISC;
5141 : 42 : break;
5142 : : case AT_ClusterOn: /* CLUSTER ON */
5143 : : case AT_DropCluster: /* SET WITHOUT CLUSTER */
5144 : 10 : ATSimplePermissions(cmd->subtype, rel,
5145 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_MATVIEW);
5146 : : /* These commands never recurse */
5147 : : /* No command-specific prep needed */
5148 : 10 : pass = AT_PASS_MISC;
5149 : 10 : break;
5150 : : case AT_SetLogged: /* SET LOGGED */
5151 : : case AT_SetUnLogged: /* SET UNLOGGED */
5152 : 16 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_SEQUENCE);
5153 [ + - ]: 16 : if (tab->chgPersistence)
5154 [ # # # # ]: 0 : ereport(ERROR,
5155 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5156 : : errmsg("cannot change persistence setting twice")));
5157 : 16 : ATPrepChangePersistence(tab, rel, cmd->subtype == AT_SetLogged);
5158 : 16 : pass = AT_PASS_MISC;
5159 : 16 : break;
5160 : : case AT_DropOids: /* SET WITHOUT OIDS */
5161 : 1 : ATSimplePermissions(cmd->subtype, rel,
5162 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5163 : 1 : pass = AT_PASS_DROP;
5164 : 1 : break;
5165 : : case AT_SetAccessMethod: /* SET ACCESS METHOD */
5166 : 21 : ATSimplePermissions(cmd->subtype, rel,
5167 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_MATVIEW);
5168 : :
5169 : : /* check if another access method change was already requested */
5170 [ + + ]: 21 : if (tab->chgAccessMethod)
5171 [ + - + - ]: 3 : ereport(ERROR,
5172 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5173 : : errmsg("cannot have multiple SET ACCESS METHOD subcommands")));
5174 : :
5175 : 18 : ATPrepSetAccessMethod(tab, rel, cmd->name);
5176 : 18 : pass = AT_PASS_MISC; /* does not matter; no work in Phase 2 */
5177 : 18 : break;
5178 : : case AT_SetTableSpace: /* SET TABLESPACE */
5179 : 25 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_PARTITIONED_TABLE |
5180 : : ATT_MATVIEW | ATT_INDEX | ATT_PARTITIONED_INDEX);
5181 : : /* This command never recurses */
5182 : 25 : ATPrepSetTableSpace(tab, rel, cmd->name, lockmode);
5183 : 25 : pass = AT_PASS_MISC; /* doesn't actually matter */
5184 : 25 : break;
5185 : : case AT_SetRelOptions: /* SET (...) */
5186 : : case AT_ResetRelOptions: /* RESET (...) */
5187 : : case AT_ReplaceRelOptions: /* reset them all, then set just these */
5188 : 120 : ATSimplePermissions(cmd->subtype, rel,
5189 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_VIEW |
5190 : : ATT_MATVIEW | ATT_INDEX);
5191 : : /* This command never recurses */
5192 : : /* No command-specific prep needed */
5193 : 120 : pass = AT_PASS_MISC;
5194 : 120 : break;
5195 : : case AT_AddInherit: /* INHERIT */
5196 : 53 : ATSimplePermissions(cmd->subtype, rel,
5197 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5198 : : /* This command never recurses */
5199 : 53 : ATPrepAddInherit(rel);
5200 : 53 : pass = AT_PASS_MISC;
5201 : 53 : break;
5202 : : case AT_DropInherit: /* NO INHERIT */
5203 : 14 : ATSimplePermissions(cmd->subtype, rel,
5204 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5205 : : /* This command never recurses */
5206 : : /* No command-specific prep needed */
5207 : 14 : pass = AT_PASS_MISC;
5208 : 14 : break;
5209 : : case AT_AlterConstraint: /* ALTER CONSTRAINT */
5210 : 49 : ATSimplePermissions(cmd->subtype, rel,
5211 : : ATT_TABLE | ATT_PARTITIONED_TABLE);
5212 : : /* Recursion occurs during execution phase */
5213 [ - + ]: 49 : if (recurse)
5214 : 49 : cmd->recurse = true;
5215 : 49 : pass = AT_PASS_MISC;
5216 : 49 : break;
5217 : : case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
5218 : 34 : ATSimplePermissions(cmd->subtype, rel,
5219 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5220 : : /* Recursion occurs during execution phase */
5221 : : /* No command-specific prep needed except saving recurse flag */
5222 [ - + ]: 34 : if (recurse)
5223 : 34 : cmd->recurse = true;
5224 : 34 : pass = AT_PASS_MISC;
5225 : 34 : break;
5226 : : case AT_ReplicaIdentity: /* REPLICA IDENTITY ... */
5227 : 54 : ATSimplePermissions(cmd->subtype, rel,
5228 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_MATVIEW);
5229 : 54 : pass = AT_PASS_MISC;
5230 : : /* This command never recurses */
5231 : : /* No command-specific prep needed */
5232 : 54 : break;
5233 : : case AT_EnableTrig: /* ENABLE TRIGGER variants */
5234 : : case AT_EnableAlwaysTrig:
5235 : : case AT_EnableReplicaTrig:
5236 : : case AT_EnableTrigAll:
5237 : : case AT_EnableTrigUser:
5238 : : case AT_DisableTrig: /* DISABLE TRIGGER variants */
5239 : : case AT_DisableTrigAll:
5240 : : case AT_DisableTrigUser:
5241 : 20 : ATSimplePermissions(cmd->subtype, rel,
5242 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
5243 : : /* Set up recursion for phase 2; no other prep needed */
5244 [ + + ]: 20 : if (recurse)
5245 : 17 : cmd->recurse = true;
5246 : 20 : pass = AT_PASS_MISC;
5247 : 20 : break;
5248 : : case AT_EnableRule: /* ENABLE/DISABLE RULE variants */
5249 : : case AT_EnableAlwaysRule:
5250 : : case AT_EnableReplicaRule:
5251 : : case AT_DisableRule:
5252 : : case AT_AddOf: /* OF */
5253 : : case AT_DropOf: /* NOT OF */
5254 : : case AT_EnableRowSecurity:
5255 : : case AT_DisableRowSecurity:
5256 : : case AT_ForceRowSecurity:
5257 : : case AT_NoForceRowSecurity:
5258 : 89 : ATSimplePermissions(cmd->subtype, rel,
5259 : : ATT_TABLE | ATT_PARTITIONED_TABLE);
5260 : : /* These commands never recurse */
5261 : : /* No command-specific prep needed */
5262 : 89 : pass = AT_PASS_MISC;
5263 : 89 : break;
5264 : : case AT_GenericOptions:
5265 : 1 : ATSimplePermissions(cmd->subtype, rel, ATT_FOREIGN_TABLE);
5266 : : /* No command-specific prep needed */
5267 : 1 : pass = AT_PASS_MISC;
5268 : 1 : break;
5269 : : case AT_AttachPartition:
5270 : 357 : ATSimplePermissions(cmd->subtype, rel,
5271 : : ATT_PARTITIONED_TABLE | ATT_PARTITIONED_INDEX);
5272 : : /* No command-specific prep needed */
5273 : 357 : pass = AT_PASS_MISC;
5274 : 357 : break;
5275 : : case AT_DetachPartition:
5276 : 76 : ATSimplePermissions(cmd->subtype, rel, ATT_PARTITIONED_TABLE);
5277 : : /* No command-specific prep needed */
5278 : 76 : pass = AT_PASS_MISC;
5279 : 76 : break;
5280 : : case AT_DetachPartitionFinalize:
5281 : 1 : ATSimplePermissions(cmd->subtype, rel, ATT_PARTITIONED_TABLE);
5282 : : /* No command-specific prep needed */
5283 : 1 : pass = AT_PASS_MISC;
5284 : 1 : break;
5285 : : case AT_MergePartitions:
5286 : : case AT_SplitPartition:
5287 : 100 : ATSimplePermissions(cmd->subtype, rel, ATT_PARTITIONED_TABLE);
5288 : : /* No command-specific prep needed */
5289 : 100 : pass = AT_PASS_MISC;
5290 : 100 : break;
5291 : : default: /* oops */
5292 [ # # # # ]: 0 : elog(ERROR, "unrecognized alter table type: %d",
5293 : : (int) cmd->subtype);
5294 : 0 : pass = AT_PASS_UNSET; /* keep compiler quiet */
5295 : 0 : break;
5296 : : }
5297 [ + - ]: 3191 : Assert(pass > AT_PASS_UNSET);
5298 : :
5299 : : /* Add the subcommand to the appropriate list for phase 2 */
5300 : 3191 : tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd);
5301 : 3191 : }
5302 : :
5303 : : /*
5304 : : * ATRewriteCatalogs
5305 : : *
5306 : : * Traffic cop for ALTER TABLE Phase 2 operations. Subcommands are
5307 : : * dispatched in a "safe" execution order (designed to avoid unnecessary
5308 : : * conflicts).
5309 : : */
5310 : : static void
5311 : 2358 : ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
5312 : : AlterTableUtilityContext *context)
5313 : : {
5314 : 2358 : ListCell *ltab;
5315 : :
5316 : : /*
5317 : : * We process all the tables "in parallel", one pass at a time. This is
5318 : : * needed because we may have to propagate work from one table to another
5319 : : * (specifically, ALTER TYPE on a foreign key's PK has to dispatch the
5320 : : * re-adding of the foreign key constraint to the other table). Work can
5321 : : * only be propagated into later passes, however.
5322 : : */
5323 [ + + ]: 33856 : for (AlterTablePass pass = 0; pass < AT_NUM_PASSES; pass++)
5324 : : {
5325 : : /* Go through each table that needs to be processed */
5326 [ + - + + : 66045 : foreach(ltab, *wqueue)
+ + ]
5327 : : {
5328 : 34547 : AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
5329 : 34547 : List *subcmds = tab->subcmds[pass];
5330 : 34547 : ListCell *lcmd;
5331 : :
5332 [ + + ]: 34547 : if (subcmds == NIL)
5333 : 30600 : continue;
5334 : :
5335 : : /*
5336 : : * Open the relation and store it in tab. This allows subroutines
5337 : : * close and reopen, if necessary. Appropriate lock was obtained
5338 : : * by phase 1, needn't get it again.
5339 : : */
5340 : 3947 : tab->rel = relation_open(tab->relid, NoLock);
5341 : :
5342 [ + - + + : 7693 : foreach(lcmd, subcmds)
+ + ]
5343 : 7492 : ATExecCmd(wqueue, tab,
5344 : 3746 : lfirst_node(AlterTableCmd, lcmd),
5345 : 3746 : lockmode, pass, context);
5346 : :
5347 : : /*
5348 : : * After the ALTER TYPE or SET EXPRESSION pass, do cleanup work
5349 : : * (this is not done in ATExecAlterColumnType since it should be
5350 : : * done only once if multiple columns of a table are altered).
5351 : : */
5352 [ + + + + ]: 3947 : if (pass == AT_PASS_ALTER_TYPE || pass == AT_PASS_SET_EXPRESSION)
5353 : 1157 : ATPostAlterTypeCleanup(wqueue, tab, lockmode);
5354 : :
5355 [ + + ]: 2993 : if (tab->rel)
5356 : : {
5357 : 3438 : relation_close(tab->rel, NoLock);
5358 : 3438 : tab->rel = NULL;
5359 : 3438 : }
5360 [ - + + ]: 34483 : }
5361 : 31498 : }
5362 : :
5363 : : /* Check to see if a toast table must be added. */
5364 [ + - + + : 5202 : foreach(ltab, *wqueue)
+ + ]
5365 : : {
5366 : 2908 : AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
5367 : :
5368 : : /*
5369 : : * If the table is source table of ATTACH PARTITION command, we did
5370 : : * not modify anything about it that will change its toasting
5371 : : * requirement, so no need to check.
5372 : : */
5373 [ + + ]: 2908 : if (((tab->relkind == RELKIND_RELATION ||
5374 : 802 : tab->relkind == RELKIND_PARTITIONED_TABLE) &&
5375 [ + + ]: 2908 : tab->partition_constraint == NULL) ||
5376 : 2908 : tab->relkind == RELKIND_MATVIEW)
5377 : 2432 : AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
5378 : 2908 : }
5379 : 2294 : }
5380 : :
5381 : : /*
5382 : : * ATExecCmd: dispatch a subcommand to appropriate execution routine
5383 : : */
5384 : : static void
5385 : 4255 : ATExecCmd(List **wqueue, AlteredTableInfo *tab,
5386 : : AlterTableCmd *cmd, LOCKMODE lockmode, AlterTablePass cur_pass,
5387 : : AlterTableUtilityContext *context)
5388 : : {
5389 : 4255 : ObjectAddress address = InvalidObjectAddress;
5390 : 4255 : Relation rel = tab->rel;
5391 : :
5392 [ + + + + : 4255 : switch (cmd->subtype)
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + -
+ - + - +
+ - + + +
+ + + + +
+ + + + -
+ + + + +
+ + + + +
- ]
5393 : : {
5394 : : case AT_AddColumn: /* ADD COLUMN */
5395 : : case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */
5396 : 608 : address = ATExecAddColumn(wqueue, tab, rel, &cmd,
5397 : 304 : cmd->recurse, false,
5398 : 304 : lockmode, cur_pass, context);
5399 : 304 : break;
5400 : : case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */
5401 : 91 : address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
5402 : 91 : break;
5403 : : case AT_CookedColumnDefault: /* add a pre-cooked default */
5404 : 13 : address = ATExecCookedColumnDefault(rel, cmd->num, cmd->def);
5405 : 13 : break;
5406 : : case AT_AddIdentity:
5407 : 38 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5408 : 19 : cur_pass, context);
5409 [ + - ]: 19 : Assert(cmd != NULL);
5410 : 19 : address = ATExecAddIdentity(rel, cmd->name, cmd->def, lockmode, cmd->recurse, false);
5411 : 19 : break;
5412 : : case AT_SetIdentity:
5413 : 20 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5414 : 10 : cur_pass, context);
5415 [ + - ]: 10 : Assert(cmd != NULL);
5416 : 10 : address = ATExecSetIdentity(rel, cmd->name, cmd->def, lockmode, cmd->recurse, false);
5417 : 10 : break;
5418 : : case AT_DropIdentity:
5419 : 9 : address = ATExecDropIdentity(rel, cmd->name, cmd->missing_ok, lockmode, cmd->recurse, false);
5420 : 9 : break;
5421 : : case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
5422 : 43 : address = ATExecDropNotNull(rel, cmd->name, cmd->recurse, lockmode);
5423 : 43 : break;
5424 : : case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
5425 : 130 : address = ATExecSetNotNull(wqueue, rel, NULL, cmd->name,
5426 : 65 : cmd->recurse, false, lockmode);
5427 : 65 : break;
5428 : : case AT_SetExpression:
5429 : 37 : address = ATExecSetExpression(tab, rel, cmd->name, cmd->def, lockmode);
5430 : 37 : break;
5431 : : case AT_DropExpression:
5432 : 9 : address = ATExecDropExpression(rel, cmd->name, cmd->missing_ok, lockmode);
5433 : 9 : break;
5434 : : case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */
5435 : 25 : address = ATExecSetStatistics(rel, cmd->name, cmd->num, cmd->def, lockmode);
5436 : 25 : break;
5437 : : case AT_SetOptions: /* ALTER COLUMN SET ( options ) */
5438 : 4 : address = ATExecSetOptions(rel, cmd->name, cmd->def, false, lockmode);
5439 : 4 : break;
5440 : : case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */
5441 : 1 : address = ATExecSetOptions(rel, cmd->name, cmd->def, true, lockmode);
5442 : 1 : break;
5443 : : case AT_SetStorage: /* ALTER COLUMN SET STORAGE */
5444 : 31 : address = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
5445 : 31 : break;
5446 : : case AT_SetCompression: /* ALTER COLUMN SET COMPRESSION */
5447 : 16 : address = ATExecSetCompression(rel, cmd->name, cmd->def,
5448 : 8 : lockmode);
5449 : 8 : break;
5450 : : case AT_DropColumn: /* DROP COLUMN */
5451 : 470 : address = ATExecDropColumn(wqueue, rel, cmd->name,
5452 : 235 : cmd->behavior, cmd->recurse, false,
5453 : 235 : cmd->missing_ok, lockmode,
5454 : : NULL);
5455 : 235 : break;
5456 : : case AT_AddIndex: /* ADD INDEX */
5457 : 254 : address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
5458 : 127 : lockmode);
5459 : 127 : break;
5460 : : case AT_ReAddIndex: /* ADD INDEX */
5461 : 150 : address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true,
5462 : 75 : lockmode);
5463 : 75 : break;
5464 : : case AT_ReAddStatistics: /* ADD STATISTICS */
5465 : 24 : address = ATExecAddStatistics(tab, rel, (CreateStatsStmt *) cmd->def,
5466 : 12 : true, lockmode);
5467 : 12 : break;
5468 : : case AT_AddConstraint: /* ADD CONSTRAINT */
5469 : : /* Transform the command only during initial examination */
5470 [ + + ]: 1546 : if (cur_pass == AT_PASS_ADD_CONSTR)
5471 : 1650 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd,
5472 : 825 : cmd->recurse, lockmode,
5473 : 825 : cur_pass, context);
5474 : : /* Depending on constraint type, might be no more work to do now */
5475 [ + + ]: 1546 : if (cmd != NULL)
5476 : 721 : address =
5477 : 1442 : ATExecAddConstraint(wqueue, tab, rel,
5478 : 721 : (Constraint *) cmd->def,
5479 : 721 : cmd->recurse, false, lockmode);
5480 : 1546 : break;
5481 : : case AT_ReAddConstraint: /* Re-add pre-existing check constraint */
5482 : : address =
5483 : 112 : ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
5484 : 56 : true, true, lockmode);
5485 : 56 : break;
5486 : : case AT_ReAddDomainConstraint: /* Re-add pre-existing domain check
5487 : : * constraint */
5488 : : address =
5489 : 4 : AlterDomainAddConstraint(((AlterDomainStmt *) cmd->def)->typeName,
5490 : 2 : ((AlterDomainStmt *) cmd->def)->def,
5491 : : NULL);
5492 : 2 : break;
5493 : : case AT_ReAddComment: /* Re-add existing comment */
5494 : 13 : address = CommentObject((CommentStmt *) cmd->def);
5495 : 13 : break;
5496 : : case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */
5497 : 246 : address = ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def,
5498 : 123 : lockmode);
5499 : 123 : break;
5500 : : case AT_AlterConstraint: /* ALTER CONSTRAINT */
5501 : 98 : address = ATExecAlterConstraint(wqueue, rel,
5502 : 49 : castNode(ATAlterConstraint, cmd->def),
5503 : 49 : cmd->recurse, lockmode);
5504 : 49 : break;
5505 : : case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
5506 : 68 : address = ATExecValidateConstraint(wqueue, rel, cmd->name, cmd->recurse,
5507 : 34 : false, lockmode);
5508 : 34 : break;
5509 : : case AT_DropConstraint: /* DROP CONSTRAINT */
5510 : 262 : ATExecDropConstraint(rel, cmd->name, cmd->behavior,
5511 : 131 : cmd->recurse,
5512 : 131 : cmd->missing_ok, lockmode);
5513 : 131 : break;
5514 : : case AT_AlterColumnType: /* ALTER COLUMN TYPE */
5515 : : /* parse transformation was done earlier */
5516 : 185 : address = ATExecAlterColumnType(tab, rel, cmd, lockmode);
5517 : 185 : break;
5518 : : case AT_AlterColumnGenericOptions: /* ALTER COLUMN OPTIONS */
5519 : : address =
5520 : 14 : ATExecAlterColumnGenericOptions(rel, cmd->name,
5521 : 7 : (List *) cmd->def, lockmode);
5522 : 7 : break;
5523 : : case AT_ChangeOwner: /* ALTER OWNER */
5524 : 84 : ATExecChangeOwner(RelationGetRelid(rel),
5525 : 42 : get_rolespec_oid(cmd->newowner, false),
5526 : 42 : false, lockmode);
5527 : 42 : break;
5528 : : case AT_ClusterOn: /* CLUSTER ON */
5529 : 10 : address = ATExecClusterOn(rel, cmd->name, lockmode);
5530 : 10 : break;
5531 : : case AT_DropCluster: /* SET WITHOUT CLUSTER */
5532 : 3 : ATExecDropCluster(rel, lockmode);
5533 : 3 : break;
5534 : : case AT_SetLogged: /* SET LOGGED */
5535 : : case AT_SetUnLogged: /* SET UNLOGGED */
5536 : 14 : break;
5537 : : case AT_DropOids: /* SET WITHOUT OIDS */
5538 : : /* nothing to do here, oid columns don't exist anymore */
5539 : : break;
5540 : : case AT_SetAccessMethod: /* SET ACCESS METHOD */
5541 : :
5542 : : /*
5543 : : * Only do this for partitioned tables, for which this is just a
5544 : : * catalog change. Tables with storage are handled by Phase 3.
5545 : : */
5546 [ + + + + ]: 15 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
5547 : 8 : tab->chgAccessMethod)
5548 : 7 : ATExecSetAccessMethodNoStorage(rel, tab->newAccessMethod);
5549 : 15 : break;
5550 : : case AT_SetTableSpace: /* SET TABLESPACE */
5551 : :
5552 : : /*
5553 : : * Only do this for partitioned tables and indexes, for which this
5554 : : * is just a catalog change. Other relation types which have
5555 : : * storage are handled by Phase 3.
5556 : : */
5557 [ + + + + ]: 25 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
5558 : 23 : rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
5559 : 6 : ATExecSetTableSpaceNoStorage(rel, tab->newTableSpace);
5560 : :
5561 : 25 : break;
5562 : : case AT_SetRelOptions: /* SET (...) */
5563 : : case AT_ResetRelOptions: /* RESET (...) */
5564 : : case AT_ReplaceRelOptions: /* replace entire option list */
5565 : 120 : ATExecSetRelOptions(rel, (List *) cmd->def, cmd->subtype, lockmode);
5566 : 120 : break;
5567 : : case AT_EnableTrig: /* ENABLE TRIGGER name */
5568 : 8 : ATExecEnableDisableTrigger(rel, cmd->name,
5569 : : TRIGGER_FIRES_ON_ORIGIN, false,
5570 : 4 : cmd->recurse,
5571 : 4 : lockmode);
5572 : 4 : break;
5573 : : case AT_EnableAlwaysTrig: /* ENABLE ALWAYS TRIGGER name */
5574 : 12 : ATExecEnableDisableTrigger(rel, cmd->name,
5575 : : TRIGGER_FIRES_ALWAYS, false,
5576 : 6 : cmd->recurse,
5577 : 6 : lockmode);
5578 : 6 : break;
5579 : : case AT_EnableReplicaTrig: /* ENABLE REPLICA TRIGGER name */
5580 : 0 : ATExecEnableDisableTrigger(rel, cmd->name,
5581 : : TRIGGER_FIRES_ON_REPLICA, false,
5582 : 0 : cmd->recurse,
5583 : 0 : lockmode);
5584 : 0 : break;
5585 : : case AT_DisableTrig: /* DISABLE TRIGGER name */
5586 : 12 : ATExecEnableDisableTrigger(rel, cmd->name,
5587 : : TRIGGER_DISABLED, false,
5588 : 6 : cmd->recurse,
5589 : 6 : lockmode);
5590 : 6 : break;
5591 : : case AT_EnableTrigAll: /* ENABLE TRIGGER ALL */
5592 : 0 : ATExecEnableDisableTrigger(rel, NULL,
5593 : : TRIGGER_FIRES_ON_ORIGIN, false,
5594 : 0 : cmd->recurse,
5595 : 0 : lockmode);
5596 : 0 : break;
5597 : : case AT_DisableTrigAll: /* DISABLE TRIGGER ALL */
5598 : 4 : ATExecEnableDisableTrigger(rel, NULL,
5599 : : TRIGGER_DISABLED, false,
5600 : 2 : cmd->recurse,
5601 : 2 : lockmode);
5602 : 2 : break;
5603 : : case AT_EnableTrigUser: /* ENABLE TRIGGER USER */
5604 : 0 : ATExecEnableDisableTrigger(rel, NULL,
5605 : : TRIGGER_FIRES_ON_ORIGIN, true,
5606 : 0 : cmd->recurse,
5607 : 0 : lockmode);
5608 : 0 : break;
5609 : : case AT_DisableTrigUser: /* DISABLE TRIGGER USER */
5610 : 4 : ATExecEnableDisableTrigger(rel, NULL,
5611 : : TRIGGER_DISABLED, true,
5612 : 2 : cmd->recurse,
5613 : 2 : lockmode);
5614 : 2 : break;
5615 : :
5616 : : case AT_EnableRule: /* ENABLE RULE name */
5617 : 2 : ATExecEnableDisableRule(rel, cmd->name,
5618 : 1 : RULE_FIRES_ON_ORIGIN, lockmode);
5619 : 1 : break;
5620 : : case AT_EnableAlwaysRule: /* ENABLE ALWAYS RULE name */
5621 : 0 : ATExecEnableDisableRule(rel, cmd->name,
5622 : 0 : RULE_FIRES_ALWAYS, lockmode);
5623 : 0 : break;
5624 : : case AT_EnableReplicaRule: /* ENABLE REPLICA RULE name */
5625 : 2 : ATExecEnableDisableRule(rel, cmd->name,
5626 : 1 : RULE_FIRES_ON_REPLICA, lockmode);
5627 : 1 : break;
5628 : : case AT_DisableRule: /* DISABLE RULE name */
5629 : 8 : ATExecEnableDisableRule(rel, cmd->name,
5630 : 4 : RULE_DISABLED, lockmode);
5631 : 4 : break;
5632 : :
5633 : : case AT_AddInherit:
5634 : 50 : address = ATExecAddInherit(rel, (RangeVar *) cmd->def, lockmode);
5635 : 50 : break;
5636 : : case AT_DropInherit:
5637 : 14 : address = ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode);
5638 : 14 : break;
5639 : : case AT_AddOf:
5640 : 9 : address = ATExecAddOf(rel, (TypeName *) cmd->def, lockmode);
5641 : 9 : break;
5642 : : case AT_DropOf:
5643 : 1 : ATExecDropOf(rel, lockmode);
5644 : 1 : break;
5645 : : case AT_ReplicaIdentity:
5646 : 57 : ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
5647 : 57 : break;
5648 : : case AT_EnableRowSecurity:
5649 : 53 : ATExecSetRowSecurity(rel, true);
5650 : 53 : break;
5651 : : case AT_DisableRowSecurity:
5652 : 1 : ATExecSetRowSecurity(rel, false);
5653 : 1 : break;
5654 : : case AT_ForceRowSecurity:
5655 : 15 : ATExecForceNoForceRowSecurity(rel, true);
5656 : 15 : break;
5657 : : case AT_NoForceRowSecurity:
5658 : 4 : ATExecForceNoForceRowSecurity(rel, false);
5659 : 4 : break;
5660 : : case AT_GenericOptions:
5661 : 1 : ATExecGenericOptions(rel, (List *) cmd->def);
5662 : 1 : break;
5663 : : case AT_AttachPartition:
5664 : 704 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5665 : 352 : cur_pass, context);
5666 [ + - ]: 352 : Assert(cmd != NULL);
5667 [ + + ]: 352 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
5668 : 614 : address = ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def,
5669 : 307 : context);
5670 : : else
5671 : 90 : address = ATExecAttachPartitionIdx(wqueue, rel,
5672 : 45 : ((PartitionCmd *) cmd->def)->name);
5673 : 352 : break;
5674 : : case AT_DetachPartition:
5675 : 146 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5676 : 73 : cur_pass, context);
5677 [ + - ]: 73 : Assert(cmd != NULL);
5678 : : /* ATPrepCmd ensures it must be a table */
5679 [ + - ]: 73 : Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
5680 : 146 : address = ATExecDetachPartition(wqueue, tab, rel,
5681 : 73 : ((PartitionCmd *) cmd->def)->name,
5682 : 73 : ((PartitionCmd *) cmd->def)->concurrent);
5683 : 73 : break;
5684 : : case AT_DetachPartitionFinalize:
5685 : 0 : address = ATExecDetachPartitionFinalize(rel, ((PartitionCmd *) cmd->def)->name);
5686 : 0 : break;
5687 : : case AT_MergePartitions:
5688 : 52 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5689 : 26 : cur_pass, context);
5690 [ + - ]: 26 : Assert(cmd != NULL);
5691 [ + - ]: 26 : Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
5692 : 52 : ATExecMergePartitions(wqueue, tab, rel, (PartitionCmd *) cmd->def,
5693 : 26 : context);
5694 : 26 : break;
5695 : : case AT_SplitPartition:
5696 : 58 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5697 : 29 : cur_pass, context);
5698 [ + - ]: 29 : Assert(cmd != NULL);
5699 [ + - ]: 29 : Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
5700 : 58 : ATExecSplitPartition(wqueue, tab, rel, (PartitionCmd *) cmd->def,
5701 : 29 : context);
5702 : 29 : break;
5703 : : default: /* oops */
5704 [ # # # # ]: 0 : elog(ERROR, "unrecognized alter table type: %d",
5705 : : (int) cmd->subtype);
5706 : 0 : break;
5707 : : }
5708 : :
5709 : : /*
5710 : : * Report the subcommand to interested event triggers.
5711 : : */
5712 [ + + ]: 4255 : if (cmd)
5713 : 2926 : EventTriggerCollectAlterTableSubcmd((Node *) cmd, address);
5714 : :
5715 : : /*
5716 : : * Bump the command counter to ensure the next subcommand in the sequence
5717 : : * can see the changes so far
5718 : : */
5719 : 4255 : CommandCounterIncrement();
5720 : 4255 : }
5721 : :
5722 : : /*
5723 : : * ATParseTransformCmd: perform parse transformation for one subcommand
5724 : : *
5725 : : * Returns the transformed subcommand tree, if there is one, else NULL.
5726 : : *
5727 : : * The parser may hand back additional AlterTableCmd(s) and/or other
5728 : : * utility statements, either before or after the original subcommand.
5729 : : * Other AlterTableCmds are scheduled into the appropriate slot of the
5730 : : * AlteredTableInfo (they had better be for later passes than the current one).
5731 : : * Utility statements that are supposed to happen before the AlterTableCmd
5732 : : * are executed immediately. Those that are supposed to happen afterwards
5733 : : * are added to the tab->afterStmts list to be done at the very end.
5734 : : */
5735 : : static AlterTableCmd *
5736 : 1835 : ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
5737 : : AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
5738 : : AlterTablePass cur_pass, AlterTableUtilityContext *context)
5739 : : {
5740 : 1835 : AlterTableCmd *newcmd = NULL;
5741 : 1835 : AlterTableStmt *atstmt = makeNode(AlterTableStmt);
5742 : 1835 : List *beforeStmts;
5743 : 1835 : List *afterStmts;
5744 : 1835 : ListCell *lc;
5745 : :
5746 : : /* Gin up an AlterTableStmt with just this subcommand and this table */
5747 : 1835 : atstmt->relation =
5748 : 3670 : makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
5749 : 1835 : pstrdup(RelationGetRelationName(rel)),
5750 : : -1);
5751 : 1835 : atstmt->relation->inh = recurse;
5752 : 1835 : atstmt->cmds = list_make1(cmd);
5753 : 1835 : atstmt->objtype = OBJECT_TABLE; /* needn't be picky here */
5754 : 1835 : atstmt->missing_ok = false;
5755 : :
5756 : : /* Transform the AlterTableStmt */
5757 : 3670 : atstmt = transformAlterTableStmt(RelationGetRelid(rel),
5758 : 1835 : atstmt,
5759 : 1835 : context->queryString,
5760 : : &beforeStmts,
5761 : : &afterStmts);
5762 : :
5763 : : /* Execute any statements that should happen before these subcommand(s) */
5764 [ + + + + : 1901 : foreach(lc, beforeStmts)
+ + ]
5765 : : {
5766 : 66 : Node *stmt = (Node *) lfirst(lc);
5767 : :
5768 : 66 : ProcessUtilityForAlterTable(stmt, context);
5769 : 66 : CommandCounterIncrement();
5770 : 66 : }
5771 : :
5772 : : /* Examine the transformed subcommands and schedule them appropriately */
5773 [ + - + + : 3827 : foreach(lc, atstmt->cmds)
+ + ]
5774 : : {
5775 : 1992 : AlterTableCmd *cmd2 = lfirst_node(AlterTableCmd, lc);
5776 : 1992 : AlterTablePass pass;
5777 : :
5778 : : /*
5779 : : * This switch need only cover the subcommand types that can be added
5780 : : * by parse_utilcmd.c; otherwise, we'll use the default strategy of
5781 : : * executing the subcommand immediately, as a substitute for the
5782 : : * original subcommand. (Note, however, that this does cause
5783 : : * AT_AddConstraint subcommands to be rescheduled into later passes,
5784 : : * which is important for index and foreign key constraints.)
5785 : : *
5786 : : * We assume we needn't do any phase-1 checks for added subcommands.
5787 : : */
5788 [ + + + + : 1992 : switch (cmd2->subtype)
- ]
5789 : : {
5790 : : case AT_AddIndex:
5791 : 131 : pass = AT_PASS_ADD_INDEX;
5792 : 131 : break;
5793 : : case AT_AddIndexConstraint:
5794 : 123 : pass = AT_PASS_ADD_INDEXCONSTR;
5795 : 123 : break;
5796 : : case AT_AddConstraint:
5797 : : /* Recursion occurs during execution phase */
5798 [ + + ]: 723 : if (recurse)
5799 : 720 : cmd2->recurse = true;
5800 [ - + + ]: 723 : switch (castNode(Constraint, cmd2->def)->contype)
5801 : : {
5802 : : case CONSTR_NOTNULL:
5803 : 251 : pass = AT_PASS_COL_ATTRS;
5804 : 251 : break;
5805 : : case CONSTR_PRIMARY:
5806 : : case CONSTR_UNIQUE:
5807 : : case CONSTR_EXCLUSION:
5808 : 0 : pass = AT_PASS_ADD_INDEXCONSTR;
5809 : 0 : break;
5810 : : default:
5811 : 472 : pass = AT_PASS_ADD_OTHERCONSTR;
5812 : 472 : break;
5813 : : }
5814 : 723 : break;
5815 : : case AT_AlterColumnGenericOptions:
5816 : : /* This command never recurses */
5817 : : /* No command-specific prep needed */
5818 : 0 : pass = AT_PASS_MISC;
5819 : 0 : break;
5820 : : default:
5821 : 1015 : pass = cur_pass;
5822 : 1015 : break;
5823 : : }
5824 : :
5825 [ + - ]: 1992 : if (pass < cur_pass)
5826 : : {
5827 : : /* Cannot schedule into a pass we already finished */
5828 [ # # # # ]: 0 : elog(ERROR, "ALTER TABLE scheduling failure: too late for pass %d",
5829 : : pass);
5830 : 0 : }
5831 [ + + ]: 1992 : else if (pass > cur_pass)
5832 : : {
5833 : : /* OK, queue it up for later */
5834 : 977 : tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd2);
5835 : 977 : }
5836 : : else
5837 : : {
5838 : : /*
5839 : : * We should see at most one subcommand for the current pass,
5840 : : * which is the transformed version of the original subcommand.
5841 : : */
5842 [ + - ]: 1015 : if (newcmd == NULL && cmd->subtype == cmd2->subtype)
5843 : : {
5844 : : /* Found the transformed version of our subcommand */
5845 : 1015 : newcmd = cmd2;
5846 : 1015 : }
5847 : : else
5848 [ # # # # ]: 0 : elog(ERROR, "ALTER TABLE scheduling failure: bogus item for pass %d",
5849 : : pass);
5850 : : }
5851 : 1992 : }
5852 : :
5853 : : /* Queue up any after-statements to happen at the end */
5854 : 1835 : tab->afterStmts = list_concat(tab->afterStmts, afterStmts);
5855 : :
5856 : 3670 : return newcmd;
5857 : 1835 : }
5858 : :
5859 : : /*
5860 : : * ATRewriteTables: ALTER TABLE phase 3
5861 : : */
5862 : : static void
5863 : 2354 : ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
5864 : : AlterTableUtilityContext *context)
5865 : : {
5866 : 2354 : ListCell *ltab;
5867 : :
5868 : : /* Go through each table that needs to be checked or rewritten */
5869 [ + + + + : 5197 : foreach(ltab, *wqueue)
+ + ]
5870 : : {
5871 : 2843 : AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
5872 : :
5873 : : /* Relations without storage may be ignored here */
5874 [ + + + + : 2843 : if (!RELKIND_HAS_STORAGE(tab->relkind))
+ + + - +
+ ]
5875 : 776 : continue;
5876 : :
5877 : : /*
5878 : : * If we change column data types, the operation has to be propagated
5879 : : * to tables that use this table's rowtype as a column type.
5880 : : * tab->newvals will also be non-NULL in the case where we're adding a
5881 : : * column with a default. We choose to forbid that case as well,
5882 : : * since composite types might eventually support defaults.
5883 : : *
5884 : : * (Eventually we'll probably need to check for composite type
5885 : : * dependencies even when we're just scanning the table without a
5886 : : * rewrite, but at the moment a composite type does not enforce any
5887 : : * constraints, so it's not necessary/appropriate to enforce them just
5888 : : * during ALTER.)
5889 : : */
5890 [ + + + + ]: 2067 : if (tab->newvals != NIL || tab->rewrite > 0)
5891 : : {
5892 : 276 : Relation rel;
5893 : :
5894 : 276 : rel = table_open(tab->relid, NoLock);
5895 : 276 : find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
5896 : 276 : table_close(rel, NoLock);
5897 : 276 : }
5898 : :
5899 : : /*
5900 : : * We only need to rewrite the table if at least one column needs to
5901 : : * be recomputed, or we are changing its persistence or access method.
5902 : : *
5903 : : * There are two reasons for requiring a rewrite when changing
5904 : : * persistence: on one hand, we need to ensure that the buffers
5905 : : * belonging to each of the two relations are marked with or without
5906 : : * BM_PERMANENT properly. On the other hand, since rewriting creates
5907 : : * and assigns a new relfilenumber, we automatically create or drop an
5908 : : * init fork for the relation as appropriate.
5909 : : */
5910 [ + + + + ]: 2067 : if (tab->rewrite > 0 && tab->relkind != RELKIND_SEQUENCE)
5911 : : {
5912 : : /* Build a temporary relation and copy data */
5913 : 172 : Relation OldHeap;
5914 : 172 : Oid OIDNewHeap;
5915 : 172 : Oid NewAccessMethod;
5916 : 172 : Oid NewTableSpace;
5917 : 172 : char persistence;
5918 : :
5919 : 172 : OldHeap = table_open(tab->relid, NoLock);
5920 : :
5921 : : /*
5922 : : * We don't support rewriting of system catalogs; there are too
5923 : : * many corner cases and too little benefit. In particular this
5924 : : * is certainly not going to work for mapped catalogs.
5925 : : */
5926 [ + - ]: 172 : if (IsSystemRelation(OldHeap))
5927 [ # # # # ]: 0 : ereport(ERROR,
5928 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5929 : : errmsg("cannot rewrite system relation \"%s\"",
5930 : : RelationGetRelationName(OldHeap))));
5931 : :
5932 [ + + - + : 172 : if (RelationIsUsedAsCatalogTable(OldHeap))
+ - ]
5933 [ # # # # ]: 0 : ereport(ERROR,
5934 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5935 : : errmsg("cannot rewrite table \"%s\" used as a catalog table",
5936 : : RelationGetRelationName(OldHeap))));
5937 : :
5938 : : /*
5939 : : * Don't allow rewrite on temp tables of other backends ... their
5940 : : * local buffer manager is not going to cope. (This is redundant
5941 : : * with the check in CheckAlterTableIsSafe, but for safety we'll
5942 : : * check here too.)
5943 : : */
5944 [ + + + - ]: 172 : if (RELATION_IS_OTHER_TEMP(OldHeap))
5945 [ # # # # ]: 0 : ereport(ERROR,
5946 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5947 : : errmsg("cannot rewrite temporary tables of other sessions")));
5948 : :
5949 : : /*
5950 : : * Select destination tablespace (same as original unless user
5951 : : * requested a change)
5952 : : */
5953 [ - + ]: 172 : if (tab->newTableSpace)
5954 : 0 : NewTableSpace = tab->newTableSpace;
5955 : : else
5956 : 172 : NewTableSpace = OldHeap->rd_rel->reltablespace;
5957 : :
5958 : : /*
5959 : : * Select destination access method (same as original unless user
5960 : : * requested a change)
5961 : : */
5962 [ + + ]: 172 : if (tab->chgAccessMethod)
5963 : 6 : NewAccessMethod = tab->newAccessMethod;
5964 : : else
5965 : 166 : NewAccessMethod = OldHeap->rd_rel->relam;
5966 : :
5967 : : /*
5968 : : * Select persistence of transient table (same as original unless
5969 : : * user requested a change)
5970 : : */
5971 [ + + ]: 172 : persistence = tab->chgPersistence ?
5972 : 172 : tab->newrelpersistence : OldHeap->rd_rel->relpersistence;
5973 : :
5974 : 172 : table_close(OldHeap, NoLock);
5975 : :
5976 : : /*
5977 : : * Fire off an Event Trigger now, before actually rewriting the
5978 : : * table.
5979 : : *
5980 : : * We don't support Event Trigger for nested commands anywhere,
5981 : : * here included, and parsetree is given NULL when coming from
5982 : : * AlterTableInternal.
5983 : : *
5984 : : * And fire it only once.
5985 : : */
5986 [ + - ]: 172 : if (parsetree)
5987 : 344 : EventTriggerTableRewrite((Node *) parsetree,
5988 : 172 : tab->relid,
5989 : 172 : tab->rewrite);
5990 : :
5991 : : /*
5992 : : * Create transient table that will receive the modified data.
5993 : : *
5994 : : * Ensure it is marked correctly as logged or unlogged. We have
5995 : : * to do this here so that buffers for the new relfilenumber will
5996 : : * have the right persistence set, and at the same time ensure
5997 : : * that the original filenumbers's buffers will get read in with
5998 : : * the correct setting (i.e. the original one). Otherwise a
5999 : : * rollback after the rewrite would possibly result with buffers
6000 : : * for the original filenumbers having the wrong persistence
6001 : : * setting.
6002 : : *
6003 : : * NB: This relies on swap_relation_files() also swapping the
6004 : : * persistence. That wouldn't work for pg_class, but that can't be
6005 : : * unlogged anyway.
6006 : : */
6007 : 344 : OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, NewAccessMethod,
6008 : 172 : persistence, lockmode);
6009 : :
6010 : : /*
6011 : : * Copy the heap data into the new table with the desired
6012 : : * modifications, and test the current data within the table
6013 : : * against new constraints generated by ALTER TABLE commands.
6014 : : */
6015 : 172 : ATRewriteTable(tab, OIDNewHeap);
6016 : :
6017 : : /*
6018 : : * Swap the physical files of the old and new heaps, then rebuild
6019 : : * indexes and discard the old heap. We can use RecentXmin for
6020 : : * the table's new relfrozenxid because we rewrote all the tuples
6021 : : * in ATRewriteTable, so no older Xid remains in the table. Also,
6022 : : * we never try to swap toast tables by content, since we have no
6023 : : * interest in letting this code work on system catalogs.
6024 : : */
6025 : 344 : finish_heap_swap(tab->relid, OIDNewHeap,
6026 : : false, false, true,
6027 : 172 : !OidIsValid(tab->newTableSpace),
6028 : 172 : RecentXmin,
6029 : 172 : ReadNextMultiXactId(),
6030 : 172 : persistence);
6031 : :
6032 [ - + ]: 172 : InvokeObjectPostAlterHook(RelationRelationId, tab->relid, 0);
6033 : 172 : }
6034 [ + + - + ]: 1895 : else if (tab->rewrite > 0 && tab->relkind == RELKIND_SEQUENCE)
6035 : : {
6036 [ - + ]: 4 : if (tab->chgPersistence)
6037 : 4 : SequenceChangePersistence(tab->relid, tab->newrelpersistence);
6038 : 4 : }
6039 : : else
6040 : : {
6041 : : /*
6042 : : * If required, test the current data within the table against new
6043 : : * constraints generated by ALTER TABLE commands, but don't
6044 : : * rebuild data.
6045 : : */
6046 [ + + + + : 1891 : if (tab->constraints != NIL || tab->verify_new_notnull ||
+ + ]
6047 : 1529 : tab->partition_constraint != NULL)
6048 : 656 : ATRewriteTable(tab, InvalidOid);
6049 : :
6050 : : /*
6051 : : * If we had SET TABLESPACE but no reason to reconstruct tuples,
6052 : : * just do a block-by-block copy.
6053 : : */
6054 [ + + ]: 1891 : if (tab->newTableSpace)
6055 : 19 : ATExecSetTableSpace(tab->relid, tab->newTableSpace, lockmode);
6056 : : }
6057 : :
6058 : : /*
6059 : : * Also change persistence of owned sequences, so that it matches the
6060 : : * table persistence.
6061 : : */
6062 [ + + ]: 2067 : if (tab->chgPersistence)
6063 : : {
6064 : 12 : List *seqlist = getOwnedSequences(tab->relid);
6065 : 12 : ListCell *lc;
6066 : :
6067 [ + + + + : 20 : foreach(lc, seqlist)
+ + ]
6068 : : {
6069 : 8 : Oid seq_relid = lfirst_oid(lc);
6070 : :
6071 : 8 : SequenceChangePersistence(seq_relid, tab->newrelpersistence);
6072 : 8 : }
6073 : 12 : }
6074 [ + + ]: 2843 : }
6075 : :
6076 : : /*
6077 : : * Foreign key constraints are checked in a final pass, since (a) it's
6078 : : * generally best to examine each one separately, and (b) it's at least
6079 : : * theoretically possible that we have changed both relations of the
6080 : : * foreign key, and we'd better have finished both rewrites before we try
6081 : : * to read the tables.
6082 : : */
6083 [ + - + + : 5023 : foreach(ltab, *wqueue)
+ + ]
6084 : : {
6085 : 2807 : AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
6086 : 2807 : Relation rel = NULL;
6087 : 2807 : ListCell *lcon;
6088 : :
6089 : : /* Relations without storage may be ignored here too */
6090 [ + + + + : 2807 : if (!RELKIND_HAS_STORAGE(tab->relkind))
+ + + - +
+ ]
6091 : 759 : continue;
6092 : :
6093 [ + + + + : 2291 : foreach(lcon, tab->constraints)
+ + ]
6094 : : {
6095 : 243 : NewConstraint *con = lfirst(lcon);
6096 : :
6097 [ + + ]: 243 : if (con->contype == CONSTR_FOREIGN)
6098 : : {
6099 : 131 : Constraint *fkconstraint = (Constraint *) con->qual;
6100 : 131 : Relation refrel;
6101 : :
6102 [ + + ]: 131 : if (rel == NULL)
6103 : : {
6104 : : /* Long since locked, no need for another */
6105 : 129 : rel = table_open(tab->relid, NoLock);
6106 : 129 : }
6107 : :
6108 : 131 : refrel = table_open(con->refrelid, RowShareLock);
6109 : :
6110 : 262 : validateForeignKeyConstraint(fkconstraint->conname, rel, refrel,
6111 : 131 : con->refindid,
6112 : 131 : con->conid,
6113 : 131 : con->conwithperiod);
6114 : :
6115 : : /*
6116 : : * No need to mark the constraint row as validated, we did
6117 : : * that when we inserted the row earlier.
6118 : : */
6119 : :
6120 : 131 : table_close(refrel, NoLock);
6121 : 131 : }
6122 : 243 : }
6123 : :
6124 [ + + ]: 2048 : if (rel)
6125 : 114 : table_close(rel, NoLock);
6126 [ + + ]: 2807 : }
6127 : :
6128 : : /* Finally, run any afterStmts that were queued up */
6129 [ + - + + : 5003 : foreach(ltab, *wqueue)
+ + ]
6130 : : {
6131 : 2787 : AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
6132 : 2787 : ListCell *lc;
6133 : :
6134 [ + + + + : 2800 : foreach(lc, tab->afterStmts)
+ + ]
6135 : : {
6136 : 13 : Node *stmt = (Node *) lfirst(lc);
6137 : :
6138 : 13 : ProcessUtilityForAlterTable(stmt, context);
6139 : 13 : CommandCounterIncrement();
6140 : 13 : }
6141 : 2787 : }
6142 : 2216 : }
6143 : :
6144 : : /*
6145 : : * ATRewriteTable: scan or rewrite one table
6146 : : *
6147 : : * A rewrite is requested by passing a valid OIDNewHeap; in that case, caller
6148 : : * must already hold AccessExclusiveLock on it.
6149 : : */
6150 : : static void
6151 : 827 : ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
6152 : : {
6153 : 827 : Relation oldrel;
6154 : 827 : Relation newrel;
6155 : 827 : TupleDesc oldTupDesc;
6156 : 827 : TupleDesc newTupDesc;
6157 : 827 : bool needscan = false;
6158 : 827 : List *notnull_attrs;
6159 : 827 : List *notnull_virtual_attrs;
6160 : 827 : int i;
6161 : 827 : ListCell *l;
6162 : 827 : EState *estate;
6163 : 827 : CommandId mycid;
6164 : 827 : BulkInsertState bistate;
6165 : 827 : int ti_options;
6166 : 827 : ExprState *partqualstate = NULL;
6167 : :
6168 : : /*
6169 : : * Open the relation(s). We have surely already locked the existing
6170 : : * table.
6171 : : */
6172 : 827 : oldrel = table_open(tab->relid, NoLock);
6173 : 827 : oldTupDesc = tab->oldDesc;
6174 : 827 : newTupDesc = RelationGetDescr(oldrel); /* includes all mods */
6175 : :
6176 [ + + ]: 827 : if (OidIsValid(OIDNewHeap))
6177 : : {
6178 [ + - ]: 171 : Assert(CheckRelationOidLockedByMe(OIDNewHeap, AccessExclusiveLock,
6179 : : false));
6180 : 171 : newrel = table_open(OIDNewHeap, NoLock);
6181 : 171 : }
6182 : : else
6183 : 656 : newrel = NULL;
6184 : :
6185 : : /*
6186 : : * Prepare a BulkInsertState and options for table_tuple_insert. The FSM
6187 : : * is empty, so don't bother using it.
6188 : : */
6189 [ + + ]: 827 : if (newrel)
6190 : : {
6191 : 171 : mycid = GetCurrentCommandId(true);
6192 : 171 : bistate = GetBulkInsertState();
6193 : 171 : ti_options = TABLE_INSERT_SKIP_FSM;
6194 : 171 : }
6195 : : else
6196 : : {
6197 : : /* keep compiler quiet about using these uninitialized */
6198 : 656 : mycid = 0;
6199 : 656 : bistate = NULL;
6200 : 656 : ti_options = 0;
6201 : : }
6202 : :
6203 : : /*
6204 : : * Generate the constraint and default execution states
6205 : : */
6206 : :
6207 : 827 : estate = CreateExecutorState();
6208 : :
6209 : : /* Build the needed expression execution states */
6210 [ + + + + : 1105 : foreach(l, tab->constraints)
+ + ]
6211 : : {
6212 : 278 : NewConstraint *con = lfirst(l);
6213 : :
6214 [ + + - ]: 278 : switch (con->contype)
6215 : : {
6216 : : case CONSTR_CHECK:
6217 : 146 : needscan = true;
6218 : 146 : con->qualstate = ExecPrepareExpr((Expr *) expand_generated_columns_in_expr(con->qual, oldrel, 1), estate);
6219 : 146 : break;
6220 : : case CONSTR_FOREIGN:
6221 : : /* Nothing to do here */
6222 : : break;
6223 : : default:
6224 [ # # # # ]: 0 : elog(ERROR, "unrecognized constraint type: %d",
6225 : : (int) con->contype);
6226 : 0 : }
6227 : 278 : }
6228 : :
6229 : : /* Build expression execution states for partition check quals */
6230 [ + + ]: 827 : if (tab->partition_constraint)
6231 : : {
6232 : 269 : needscan = true;
6233 : 269 : partqualstate = ExecPrepareExpr(tab->partition_constraint, estate);
6234 : 269 : }
6235 : :
6236 [ + + + + : 1009 : foreach(l, tab->newvals)
+ + ]
6237 : : {
6238 : 182 : NewColumnValue *ex = lfirst(l);
6239 : :
6240 : : /* expr already planned */
6241 : 182 : ex->exprstate = ExecInitExpr(ex->expr, NULL);
6242 : 182 : }
6243 : :
6244 : 827 : notnull_attrs = notnull_virtual_attrs = NIL;
6245 [ + + + + ]: 827 : if (newrel || tab->verify_new_notnull)
6246 : : {
6247 : : /*
6248 : : * If we are rebuilding the tuples OR if we added any new but not
6249 : : * verified not-null constraints, check all *valid* not-null
6250 : : * constraints. This is a bit of overkill but it minimizes risk of
6251 : : * bugs.
6252 : : *
6253 : : * notnull_attrs does *not* collect attribute numbers for valid
6254 : : * not-null constraints over virtual generated columns; instead, they
6255 : : * are collected in notnull_virtual_attrs for verification elsewhere.
6256 : : */
6257 [ + + ]: 1211 : for (i = 0; i < newTupDesc->natts; i++)
6258 : : {
6259 : 883 : CompactAttribute *attr = TupleDescCompactAttr(newTupDesc, i);
6260 : :
6261 [ + + - + ]: 883 : if (attr->attnullability == ATTNULLABLE_VALID &&
6262 : 328 : !attr->attisdropped)
6263 : : {
6264 : 328 : Form_pg_attribute wholeatt = TupleDescAttr(newTupDesc, i);
6265 : :
6266 [ + + ]: 328 : if (wholeatt->attgenerated != ATTRIBUTE_GENERATED_VIRTUAL)
6267 : 313 : notnull_attrs = lappend_int(notnull_attrs, wholeatt->attnum);
6268 : : else
6269 : 30 : notnull_virtual_attrs = lappend_int(notnull_virtual_attrs,
6270 : 15 : wholeatt->attnum);
6271 : 328 : }
6272 : 883 : }
6273 [ + + + + ]: 328 : if (notnull_attrs || notnull_virtual_attrs)
6274 : 240 : needscan = true;
6275 : 328 : }
6276 : :
6277 [ + + + + ]: 827 : if (newrel || needscan)
6278 : : {
6279 : 724 : ExprContext *econtext;
6280 : 724 : TupleTableSlot *oldslot;
6281 : 724 : TupleTableSlot *newslot;
6282 : 724 : TableScanDesc scan;
6283 : 724 : MemoryContext oldCxt;
6284 : 724 : List *dropped_attrs = NIL;
6285 : 724 : ListCell *lc;
6286 : 724 : Snapshot snapshot;
6287 : 724 : ResultRelInfo *rInfo = NULL;
6288 : :
6289 : : /*
6290 : : * When adding or changing a virtual generated column with a not-null
6291 : : * constraint, we need to evaluate whether the generation expression
6292 : : * is null. For that, we borrow ExecRelGenVirtualNotNull(). Here, we
6293 : : * prepare a dummy ResultRelInfo.
6294 : : */
6295 [ + + ]: 724 : if (notnull_virtual_attrs != NIL)
6296 : : {
6297 : 10 : MemoryContext oldcontext;
6298 : :
6299 [ + - ]: 10 : Assert(newTupDesc->constr->has_generated_virtual);
6300 [ + - ]: 10 : Assert(newTupDesc->constr->has_not_null);
6301 : 10 : oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
6302 : 10 : rInfo = makeNode(ResultRelInfo);
6303 : 20 : InitResultRelInfo(rInfo,
6304 : 10 : oldrel,
6305 : : 0, /* dummy rangetable index */
6306 : : NULL,
6307 : 10 : estate->es_instrument);
6308 : 10 : MemoryContextSwitchTo(oldcontext);
6309 : 10 : }
6310 : :
6311 [ + + ]: 724 : if (newrel)
6312 [ - + - + ]: 171 : ereport(DEBUG1,
6313 : : (errmsg_internal("rewriting table \"%s\"",
6314 : : RelationGetRelationName(oldrel))));
6315 : : else
6316 [ - + - + ]: 553 : ereport(DEBUG1,
6317 : : (errmsg_internal("verifying table \"%s\"",
6318 : : RelationGetRelationName(oldrel))));
6319 : :
6320 [ + + ]: 724 : if (newrel)
6321 : : {
6322 : : /*
6323 : : * All predicate locks on the tuples or pages are about to be made
6324 : : * invalid, because we move tuples around. Promote them to
6325 : : * relation locks.
6326 : : */
6327 : 171 : TransferPredicateLocksToHeapRelation(oldrel);
6328 : 171 : }
6329 : :
6330 [ - + ]: 724 : econtext = GetPerTupleExprContext(estate);
6331 : :
6332 : : /*
6333 : : * Create necessary tuple slots. When rewriting, two slots are needed,
6334 : : * otherwise one suffices. In the case where one slot suffices, we
6335 : : * need to use the new tuple descriptor, otherwise some constraints
6336 : : * can't be evaluated. Note that even when the tuple layout is the
6337 : : * same and no rewrite is required, the tupDescs might not be
6338 : : * (consider ADD COLUMN without a default).
6339 : : */
6340 [ + + ]: 724 : if (tab->rewrite)
6341 : : {
6342 [ + - ]: 171 : Assert(newrel != NULL);
6343 : 342 : oldslot = MakeSingleTupleTableSlot(oldTupDesc,
6344 : 171 : table_slot_callbacks(oldrel));
6345 : 342 : newslot = MakeSingleTupleTableSlot(newTupDesc,
6346 : 171 : table_slot_callbacks(newrel));
6347 : :
6348 : : /*
6349 : : * Set all columns in the new slot to NULL initially, to ensure
6350 : : * columns added as part of the rewrite are initialized to NULL.
6351 : : * That is necessary as tab->newvals will not contain an
6352 : : * expression for columns with a NULL default, e.g. when adding a
6353 : : * column without a default together with a column with a default
6354 : : * requiring an actual rewrite.
6355 : : */
6356 : 171 : ExecStoreAllNullTuple(newslot);
6357 : 171 : }
6358 : : else
6359 : : {
6360 : 1106 : oldslot = MakeSingleTupleTableSlot(newTupDesc,
6361 : 553 : table_slot_callbacks(oldrel));
6362 : 553 : newslot = NULL;
6363 : : }
6364 : :
6365 : : /*
6366 : : * Any attributes that are dropped according to the new tuple
6367 : : * descriptor can be set to NULL. We precompute the list of dropped
6368 : : * attributes to avoid needing to do so in the per-tuple loop.
6369 : : */
6370 [ + + ]: 2572 : for (i = 0; i < newTupDesc->natts; i++)
6371 : : {
6372 [ + + ]: 1848 : if (TupleDescAttr(newTupDesc, i)->attisdropped)
6373 : 119 : dropped_attrs = lappend_int(dropped_attrs, i);
6374 : 1848 : }
6375 : :
6376 : : /*
6377 : : * Scan through the rows, generating a new row if needed and then
6378 : : * checking all the constraints.
6379 : : */
6380 : 724 : snapshot = RegisterSnapshot(GetLatestSnapshot());
6381 : 724 : scan = table_beginscan(oldrel, snapshot, 0, NULL);
6382 : :
6383 : : /*
6384 : : * Switch to per-tuple memory context and reset it for each tuple
6385 : : * produced, so we don't leak memory.
6386 : : */
6387 [ + - ]: 724 : oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
6388 : :
6389 [ + + ]: 100931 : while (table_scan_getnextslot(scan, ForwardScanDirection, oldslot))
6390 : : {
6391 : 100257 : TupleTableSlot *insertslot;
6392 : :
6393 [ + + ]: 100257 : if (tab->rewrite > 0)
6394 : : {
6395 : : /* Extract data from old tuple */
6396 : 16340 : slot_getallattrs(oldslot);
6397 : 16340 : ExecClearTuple(newslot);
6398 : :
6399 : : /* copy attributes */
6400 : 16340 : memcpy(newslot->tts_values, oldslot->tts_values,
6401 : : sizeof(Datum) * oldslot->tts_nvalid);
6402 : 16340 : memcpy(newslot->tts_isnull, oldslot->tts_isnull,
6403 : : sizeof(bool) * oldslot->tts_nvalid);
6404 : :
6405 : : /* Set dropped attributes to null in new tuple */
6406 [ + + + + : 16357 : foreach(lc, dropped_attrs)
+ + ]
6407 : 17 : newslot->tts_isnull[lfirst_int(lc)] = true;
6408 : :
6409 : : /*
6410 : : * Constraints and GENERATED expressions might reference the
6411 : : * tableoid column, so fill tts_tableOid with the desired
6412 : : * value. (We must do this each time, because it gets
6413 : : * overwritten with newrel's OID during storing.)
6414 : : */
6415 : 16340 : newslot->tts_tableOid = RelationGetRelid(oldrel);
6416 : :
6417 : : /*
6418 : : * Process supplied expressions to replace selected columns.
6419 : : *
6420 : : * First, evaluate expressions whose inputs come from the old
6421 : : * tuple.
6422 : : */
6423 : 16340 : econtext->ecxt_scantuple = oldslot;
6424 : :
6425 [ + + + + : 33676 : foreach(l, tab->newvals)
+ + ]
6426 : : {
6427 : 17336 : NewColumnValue *ex = lfirst(l);
6428 : :
6429 [ + + ]: 17336 : if (ex->is_generated)
6430 : 52 : continue;
6431 : :
6432 : 17284 : newslot->tts_values[ex->attnum - 1]
6433 : 51852 : = ExecEvalExpr(ex->exprstate,
6434 : 17284 : econtext,
6435 : 17284 : &newslot->tts_isnull[ex->attnum - 1]);
6436 [ + + ]: 17336 : }
6437 : :
6438 : 16340 : ExecStoreVirtualTuple(newslot);
6439 : :
6440 : : /*
6441 : : * Now, evaluate any expressions whose inputs come from the
6442 : : * new tuple. We assume these columns won't reference each
6443 : : * other, so that there's no ordering dependency.
6444 : : */
6445 : 16340 : econtext->ecxt_scantuple = newslot;
6446 : :
6447 [ + + + + : 33674 : foreach(l, tab->newvals)
+ + ]
6448 : : {
6449 : 17334 : NewColumnValue *ex = lfirst(l);
6450 : :
6451 [ + + ]: 17334 : if (!ex->is_generated)
6452 : 17282 : continue;
6453 : :
6454 : 52 : newslot->tts_values[ex->attnum - 1]
6455 : 156 : = ExecEvalExpr(ex->exprstate,
6456 : 52 : econtext,
6457 : 52 : &newslot->tts_isnull[ex->attnum - 1]);
6458 [ + + ]: 17334 : }
6459 : :
6460 : 16340 : insertslot = newslot;
6461 : 16340 : }
6462 : : else
6463 : : {
6464 : : /*
6465 : : * If there's no rewrite, old and new table are guaranteed to
6466 : : * have the same AM, so we can just use the old slot to verify
6467 : : * new constraints etc.
6468 : : */
6469 : 83917 : insertslot = oldslot;
6470 : : }
6471 : :
6472 : : /* Now check any constraints on the possibly-changed tuple */
6473 : 100257 : econtext->ecxt_scantuple = insertslot;
6474 : :
6475 [ + + + + : 523181 : foreach_int(attn, notnull_attrs)
+ + + + ]
6476 : : {
6477 [ + + ]: 322701 : if (slot_attisnull(insertslot, attn))
6478 : : {
6479 : 17 : Form_pg_attribute attr = TupleDescAttr(newTupDesc, attn - 1);
6480 : :
6481 [ + - + - ]: 17 : ereport(ERROR,
6482 : : (errcode(ERRCODE_NOT_NULL_VIOLATION),
6483 : : errmsg("column \"%s\" of relation \"%s\" contains null values",
6484 : : NameStr(attr->attname),
6485 : : RelationGetRelationName(oldrel)),
6486 : : errtablecol(oldrel, attn)));
6487 : 0 : }
6488 : 422924 : }
6489 : :
6490 [ + + ]: 100240 : if (notnull_virtual_attrs != NIL)
6491 : : {
6492 : 14 : AttrNumber attnum;
6493 : :
6494 : 28 : attnum = ExecRelGenVirtualNotNull(rInfo, insertslot,
6495 : 14 : estate,
6496 : 14 : notnull_virtual_attrs);
6497 [ + + ]: 14 : if (attnum != InvalidAttrNumber)
6498 : : {
6499 : 5 : Form_pg_attribute attr = TupleDescAttr(newTupDesc, attnum - 1);
6500 : :
6501 [ + - + - ]: 5 : ereport(ERROR,
6502 : : errcode(ERRCODE_NOT_NULL_VIOLATION),
6503 : : errmsg("column \"%s\" of relation \"%s\" contains null values",
6504 : : NameStr(attr->attname),
6505 : : RelationGetRelationName(oldrel)),
6506 : : errtablecol(oldrel, attnum));
6507 : 0 : }
6508 : 9 : }
6509 : :
6510 [ + + + + : 101316 : foreach(l, tab->constraints)
+ + ]
6511 : : {
6512 : 1097 : NewConstraint *con = lfirst(l);
6513 : :
6514 [ - + + ]: 1097 : switch (con->contype)
6515 : : {
6516 : : case CONSTR_CHECK:
6517 [ + + ]: 1084 : if (!ExecCheck(con->qualstate, econtext))
6518 [ + - + - ]: 16 : ereport(ERROR,
6519 : : (errcode(ERRCODE_CHECK_VIOLATION),
6520 : : errmsg("check constraint \"%s\" of relation \"%s\" is violated by some row",
6521 : : con->name,
6522 : : RelationGetRelationName(oldrel)),
6523 : : errtableconstraint(oldrel, con->name)));
6524 : 1068 : break;
6525 : : case CONSTR_NOTNULL:
6526 : : case CONSTR_FOREIGN:
6527 : : /* Nothing to do here */
6528 : 13 : break;
6529 : : default:
6530 [ # # # # ]: 0 : elog(ERROR, "unrecognized constraint type: %d",
6531 : : (int) con->contype);
6532 : 0 : }
6533 : 1081 : }
6534 : :
6535 [ + + + + ]: 100219 : if (partqualstate && !ExecCheck(partqualstate, econtext))
6536 : : {
6537 [ + + ]: 12 : if (tab->validate_default)
6538 [ + - + - ]: 4 : ereport(ERROR,
6539 : : (errcode(ERRCODE_CHECK_VIOLATION),
6540 : : errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
6541 : : RelationGetRelationName(oldrel)),
6542 : : errtable(oldrel)));
6543 : : else
6544 [ + - + - ]: 8 : ereport(ERROR,
6545 : : (errcode(ERRCODE_CHECK_VIOLATION),
6546 : : errmsg("partition constraint of relation \"%s\" is violated by some row",
6547 : : RelationGetRelationName(oldrel)),
6548 : : errtable(oldrel)));
6549 : 0 : }
6550 : :
6551 : : /* Write the tuple out to the new relation */
6552 [ + + ]: 100207 : if (newrel)
6553 : 32670 : table_tuple_insert(newrel, insertslot, mycid,
6554 : 16335 : ti_options, bistate);
6555 : :
6556 : 100207 : ResetExprContext(econtext);
6557 : :
6558 [ + - ]: 100207 : CHECK_FOR_INTERRUPTS();
6559 : 100207 : }
6560 : :
6561 : 674 : MemoryContextSwitchTo(oldCxt);
6562 : 674 : table_endscan(scan);
6563 : 674 : UnregisterSnapshot(snapshot);
6564 : :
6565 : 674 : ExecDropSingleTupleTableSlot(oldslot);
6566 [ + + ]: 674 : if (newslot)
6567 : 164 : ExecDropSingleTupleTableSlot(newslot);
6568 : 674 : }
6569 : :
6570 : 777 : FreeExecutorState(estate);
6571 : :
6572 : 777 : table_close(oldrel, NoLock);
6573 [ + + ]: 777 : if (newrel)
6574 : : {
6575 : 164 : FreeBulkInsertState(bistate);
6576 : :
6577 : 164 : table_finish_bulk_insert(newrel, ti_options);
6578 : :
6579 : 164 : table_close(newrel, NoLock);
6580 : 164 : }
6581 : 777 : }
6582 : :
6583 : : /*
6584 : : * ATGetQueueEntry: find or create an entry in the ALTER TABLE work queue
6585 : : */
6586 : : static AlteredTableInfo *
6587 : 4429 : ATGetQueueEntry(List **wqueue, Relation rel)
6588 : : {
6589 : 4429 : Oid relid = RelationGetRelid(rel);
6590 : 4429 : AlteredTableInfo *tab;
6591 : 4429 : ListCell *ltab;
6592 : :
6593 [ + + + + : 7113 : foreach(ltab, *wqueue)
+ + + + ]
6594 : : {
6595 : 2684 : tab = (AlteredTableInfo *) lfirst(ltab);
6596 [ + + ]: 2684 : if (tab->relid == relid)
6597 : 825 : return tab;
6598 : 1859 : }
6599 : :
6600 : : /*
6601 : : * Not there, so add it. Note that we make a copy of the relation's
6602 : : * existing descriptor before anything interesting can happen to it.
6603 : : */
6604 : 3604 : tab = palloc0_object(AlteredTableInfo);
6605 : 3604 : tab->relid = relid;
6606 : 3604 : tab->rel = NULL; /* set later */
6607 : 3604 : tab->relkind = rel->rd_rel->relkind;
6608 : 3604 : tab->oldDesc = CreateTupleDescCopyConstr(RelationGetDescr(rel));
6609 : 3604 : tab->newAccessMethod = InvalidOid;
6610 : 3604 : tab->chgAccessMethod = false;
6611 : 3604 : tab->newTableSpace = InvalidOid;
6612 : 3604 : tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
6613 : 3604 : tab->chgPersistence = false;
6614 : :
6615 : 3604 : *wqueue = lappend(*wqueue, tab);
6616 : :
6617 : 3604 : return tab;
6618 : 4429 : }
6619 : :
6620 : : static const char *
6621 : 14 : alter_table_type_to_string(AlterTableType cmdtype)
6622 : : {
6623 [ + + - - : 14 : switch (cmdtype)
- + - - -
+ + - - -
- - - - -
- + + - -
- - - - -
- - - - -
- - - - -
- - - - -
- - - - -
+ + + - +
- - - - -
- - - - ]
6624 : : {
6625 : : case AT_AddColumn:
6626 : : case AT_AddColumnToView:
6627 : 0 : return "ADD COLUMN";
6628 : : case AT_ColumnDefault:
6629 : : case AT_CookedColumnDefault:
6630 : 0 : return "ALTER COLUMN ... SET DEFAULT";
6631 : : case AT_DropNotNull:
6632 : 1 : return "ALTER COLUMN ... DROP NOT NULL";
6633 : : case AT_SetNotNull:
6634 : 1 : return "ALTER COLUMN ... SET NOT NULL";
6635 : : case AT_SetExpression:
6636 : 0 : return "ALTER COLUMN ... SET EXPRESSION";
6637 : : case AT_DropExpression:
6638 : 0 : return "ALTER COLUMN ... DROP EXPRESSION";
6639 : : case AT_SetStatistics:
6640 : 0 : return "ALTER COLUMN ... SET STATISTICS";
6641 : : case AT_SetOptions:
6642 : 2 : return "ALTER COLUMN ... SET";
6643 : : case AT_ResetOptions:
6644 : 0 : return "ALTER COLUMN ... RESET";
6645 : : case AT_SetStorage:
6646 : 0 : return "ALTER COLUMN ... SET STORAGE";
6647 : : case AT_SetCompression:
6648 : 0 : return "ALTER COLUMN ... SET COMPRESSION";
6649 : : case AT_DropColumn:
6650 : 1 : return "DROP COLUMN";
6651 : : case AT_AddIndex:
6652 : : case AT_ReAddIndex:
6653 : 0 : return NULL; /* not real grammar */
6654 : : case AT_AddConstraint:
6655 : : case AT_ReAddConstraint:
6656 : : case AT_ReAddDomainConstraint:
6657 : : case AT_AddIndexConstraint:
6658 : 0 : return "ADD CONSTRAINT";
6659 : : case AT_AlterConstraint:
6660 : 1 : return "ALTER CONSTRAINT";
6661 : : case AT_ValidateConstraint:
6662 : 0 : return "VALIDATE CONSTRAINT";
6663 : : case AT_DropConstraint:
6664 : 0 : return "DROP CONSTRAINT";
6665 : : case AT_ReAddComment:
6666 : 0 : return NULL; /* not real grammar */
6667 : : case AT_AlterColumnType:
6668 : 0 : return "ALTER COLUMN ... SET DATA TYPE";
6669 : : case AT_AlterColumnGenericOptions:
6670 : 0 : return "ALTER COLUMN ... OPTIONS";
6671 : : case AT_ChangeOwner:
6672 : 0 : return "OWNER TO";
6673 : : case AT_ClusterOn:
6674 : 0 : return "CLUSTER ON";
6675 : : case AT_DropCluster:
6676 : 0 : return "SET WITHOUT CLUSTER";
6677 : : case AT_SetAccessMethod:
6678 : 0 : return "SET ACCESS METHOD";
6679 : : case AT_SetLogged:
6680 : 1 : return "SET LOGGED";
6681 : : case AT_SetUnLogged:
6682 : 1 : return "SET UNLOGGED";
6683 : : case AT_DropOids:
6684 : 0 : return "SET WITHOUT OIDS";
6685 : : case AT_SetTableSpace:
6686 : 0 : return "SET TABLESPACE";
6687 : : case AT_SetRelOptions:
6688 : 0 : return "SET";
6689 : : case AT_ResetRelOptions:
6690 : 0 : return "RESET";
6691 : : case AT_ReplaceRelOptions:
6692 : 0 : return NULL; /* not real grammar */
6693 : : case AT_EnableTrig:
6694 : 0 : return "ENABLE TRIGGER";
6695 : : case AT_EnableAlwaysTrig:
6696 : 0 : return "ENABLE ALWAYS TRIGGER";
6697 : : case AT_EnableReplicaTrig:
6698 : 0 : return "ENABLE REPLICA TRIGGER";
6699 : : case AT_DisableTrig:
6700 : 0 : return "DISABLE TRIGGER";
6701 : : case AT_EnableTrigAll:
6702 : 0 : return "ENABLE TRIGGER ALL";
6703 : : case AT_DisableTrigAll:
6704 : 0 : return "DISABLE TRIGGER ALL";
6705 : : case AT_EnableTrigUser:
6706 : 0 : return "ENABLE TRIGGER USER";
6707 : : case AT_DisableTrigUser:
6708 : 0 : return "DISABLE TRIGGER USER";
6709 : : case AT_EnableRule:
6710 : 0 : return "ENABLE RULE";
6711 : : case AT_EnableAlwaysRule:
6712 : 0 : return "ENABLE ALWAYS RULE";
6713 : : case AT_EnableReplicaRule:
6714 : 0 : return "ENABLE REPLICA RULE";
6715 : : case AT_DisableRule:
6716 : 0 : return "DISABLE RULE";
6717 : : case AT_AddInherit:
6718 : 0 : return "INHERIT";
6719 : : case AT_DropInherit:
6720 : 0 : return "NO INHERIT";
6721 : : case AT_AddOf:
6722 : 0 : return "OF";
6723 : : case AT_DropOf:
6724 : 0 : return "NOT OF";
6725 : : case AT_ReplicaIdentity:
6726 : 0 : return "REPLICA IDENTITY";
6727 : : case AT_EnableRowSecurity:
6728 : 0 : return "ENABLE ROW SECURITY";
6729 : : case AT_DisableRowSecurity:
6730 : 0 : return "DISABLE ROW SECURITY";
6731 : : case AT_ForceRowSecurity:
6732 : 0 : return "FORCE ROW SECURITY";
6733 : : case AT_NoForceRowSecurity:
6734 : 0 : return "NO FORCE ROW SECURITY";
6735 : : case AT_GenericOptions:
6736 : 0 : return "OPTIONS";
6737 : : case AT_AttachPartition:
6738 : 1 : return "ATTACH PARTITION";
6739 : : case AT_DetachPartition:
6740 : 3 : return "DETACH PARTITION";
6741 : : case AT_DetachPartitionFinalize:
6742 : 1 : return "DETACH PARTITION ... FINALIZE";
6743 : : case AT_MergePartitions:
6744 : 0 : return "MERGE PARTITIONS";
6745 : : case AT_SplitPartition:
6746 : 1 : return "SPLIT PARTITION";
6747 : : case AT_AddIdentity:
6748 : 0 : return "ALTER COLUMN ... ADD IDENTITY";
6749 : : case AT_SetIdentity:
6750 : 0 : return "ALTER COLUMN ... SET";
6751 : : case AT_DropIdentity:
6752 : 0 : return "ALTER COLUMN ... DROP IDENTITY";
6753 : : case AT_ReAddStatistics:
6754 : 0 : return NULL; /* not real grammar */
6755 : : }
6756 : :
6757 : 0 : return NULL;
6758 : 14 : }
6759 : :
6760 : : /*
6761 : : * ATSimplePermissions
6762 : : *
6763 : : * - Ensure that it is a relation (or possibly a view)
6764 : : * - Ensure this user is the owner
6765 : : * - Ensure that it is not a system table
6766 : : */
6767 : : static void
6768 : 3957 : ATSimplePermissions(AlterTableType cmdtype, Relation rel, int allowed_targets)
6769 : : {
6770 : 3957 : int actual_target;
6771 : :
6772 [ + - + + : 3957 : switch (rel->rd_rel->relkind)
+ + + + +
+ ]
6773 : : {
6774 : : case RELKIND_RELATION:
6775 : 2767 : actual_target = ATT_TABLE;
6776 : 2767 : break;
6777 : : case RELKIND_PARTITIONED_TABLE:
6778 : 892 : actual_target = ATT_PARTITIONED_TABLE;
6779 : 892 : break;
6780 : : case RELKIND_VIEW:
6781 : 65 : actual_target = ATT_VIEW;
6782 : 65 : break;
6783 : : case RELKIND_MATVIEW:
6784 : 7 : actual_target = ATT_MATVIEW;
6785 : 7 : break;
6786 : : case RELKIND_INDEX:
6787 : 24 : actual_target = ATT_INDEX;
6788 : 24 : break;
6789 : : case RELKIND_PARTITIONED_INDEX:
6790 : 52 : actual_target = ATT_PARTITIONED_INDEX;
6791 : 52 : break;
6792 : : case RELKIND_COMPOSITE_TYPE:
6793 : 33 : actual_target = ATT_COMPOSITE_TYPE;
6794 : 33 : break;
6795 : : case RELKIND_FOREIGN_TABLE:
6796 : 113 : actual_target = ATT_FOREIGN_TABLE;
6797 : 113 : break;
6798 : : case RELKIND_SEQUENCE:
6799 : 4 : actual_target = ATT_SEQUENCE;
6800 : 4 : break;
6801 : : default:
6802 : 0 : actual_target = 0;
6803 : 0 : break;
6804 : : }
6805 : :
6806 : : /* Wrong target type? */
6807 [ + + ]: 3957 : if ((actual_target & allowed_targets) == 0)
6808 : : {
6809 : 14 : const char *action_str = alter_table_type_to_string(cmdtype);
6810 : :
6811 [ + - ]: 14 : if (action_str)
6812 [ + - + - ]: 14 : ereport(ERROR,
6813 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
6814 : : /* translator: %s is a group of some SQL keywords */
6815 : : errmsg("ALTER action %s cannot be performed on relation \"%s\"",
6816 : : action_str, RelationGetRelationName(rel)),
6817 : : errdetail_relkind_not_supported(rel->rd_rel->relkind)));
6818 : : else
6819 : : /* internal error? */
6820 [ # # # # ]: 0 : elog(ERROR, "invalid ALTER action attempted on relation \"%s\"",
6821 : : RelationGetRelationName(rel));
6822 : 0 : }
6823 : :
6824 : : /* Permissions checks */
6825 [ + + ]: 3943 : if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId()))
6826 : 4 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
6827 : 2 : RelationGetRelationName(rel));
6828 : :
6829 [ + + + - ]: 3943 : if (!allowSystemTableMods && IsSystemRelation(rel))
6830 [ # # # # ]: 0 : ereport(ERROR,
6831 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
6832 : : errmsg("permission denied: \"%s\" is a system catalog",
6833 : : RelationGetRelationName(rel))));
6834 : 3943 : }
6835 : :
6836 : : /*
6837 : : * ATSimpleRecursion
6838 : : *
6839 : : * Simple table recursion sufficient for most ALTER TABLE operations.
6840 : : * All direct and indirect children are processed in an unspecified order.
6841 : : * Note that if a child inherits from the original table via multiple
6842 : : * inheritance paths, it will be visited just once.
6843 : : */
6844 : : static void
6845 : 204 : ATSimpleRecursion(List **wqueue, Relation rel,
6846 : : AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
6847 : : AlterTableUtilityContext *context)
6848 : : {
6849 : : /*
6850 : : * Propagate to children, if desired and if there are (or might be) any
6851 : : * children.
6852 : : */
6853 [ + + + + ]: 204 : if (recurse && rel->rd_rel->relhassubclass)
6854 : : {
6855 : 13 : Oid relid = RelationGetRelid(rel);
6856 : 13 : ListCell *child;
6857 : 13 : List *children;
6858 : :
6859 : 13 : children = find_all_inheritors(relid, lockmode, NULL);
6860 : :
6861 : : /*
6862 : : * find_all_inheritors does the recursive search of the inheritance
6863 : : * hierarchy, so all we have to do is process all of the relids in the
6864 : : * list that it returns.
6865 : : */
6866 [ + - + + : 57 : foreach(child, children)
+ + ]
6867 : : {
6868 : 44 : Oid childrelid = lfirst_oid(child);
6869 : 44 : Relation childrel;
6870 : :
6871 [ + + ]: 44 : if (childrelid == relid)
6872 : 13 : continue;
6873 : : /* find_all_inheritors already got lock */
6874 : 31 : childrel = relation_open(childrelid, NoLock);
6875 : 31 : CheckAlterTableIsSafe(childrel);
6876 : 31 : ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, context);
6877 : 31 : relation_close(childrel, NoLock);
6878 [ - + + ]: 44 : }
6879 : 13 : }
6880 : 204 : }
6881 : :
6882 : : /*
6883 : : * Obtain list of partitions of the given table, locking them all at the given
6884 : : * lockmode and ensuring that they all pass CheckAlterTableIsSafe.
6885 : : *
6886 : : * This function is a no-op if the given relation is not a partitioned table;
6887 : : * in particular, nothing is done if it's a legacy inheritance parent.
6888 : : */
6889 : : static void
6890 : 132 : ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode)
6891 : : {
6892 [ + + ]: 132 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
6893 : : {
6894 : 28 : List *inh;
6895 : 28 : ListCell *cell;
6896 : :
6897 : 28 : inh = find_all_inheritors(RelationGetRelid(rel), lockmode, NULL);
6898 : : /* first element is the parent rel; must ignore it */
6899 [ + - + + : 95 : for_each_from(cell, inh, 1)
+ + ]
6900 : : {
6901 : 67 : Relation childrel;
6902 : :
6903 : : /* find_all_inheritors already got lock */
6904 : 67 : childrel = table_open(lfirst_oid(cell), NoLock);
6905 : 67 : CheckAlterTableIsSafe(childrel);
6906 : 67 : table_close(childrel, NoLock);
6907 : 67 : }
6908 : 28 : list_free(inh);
6909 : 28 : }
6910 : 132 : }
6911 : :
6912 : : /*
6913 : : * ATTypedTableRecursion
6914 : : *
6915 : : * Propagate ALTER TYPE operations to the typed tables of that type.
6916 : : * Also check the RESTRICT/CASCADE behavior. Given CASCADE, also permit
6917 : : * recursion to inheritance children of the typed tables.
6918 : : */
6919 : : static void
6920 : 29 : ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
6921 : : LOCKMODE lockmode, AlterTableUtilityContext *context)
6922 : : {
6923 : 29 : ListCell *child;
6924 : 29 : List *children;
6925 : :
6926 [ + - ]: 29 : Assert(rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE);
6927 : :
6928 : 58 : children = find_typed_table_dependencies(rel->rd_rel->reltype,
6929 : 29 : RelationGetRelationName(rel),
6930 : 29 : cmd->behavior);
6931 : :
6932 [ + + + + : 31 : foreach(child, children)
+ + ]
6933 : : {
6934 : 2 : Oid childrelid = lfirst_oid(child);
6935 : 2 : Relation childrel;
6936 : :
6937 : 2 : childrel = relation_open(childrelid, lockmode);
6938 : 2 : CheckAlterTableIsSafe(childrel);
6939 : 2 : ATPrepCmd(wqueue, childrel, cmd, true, true, lockmode, context);
6940 : 2 : relation_close(childrel, NoLock);
6941 : 2 : }
6942 : 29 : }
6943 : :
6944 : :
6945 : : /*
6946 : : * find_composite_type_dependencies
6947 : : *
6948 : : * Check to see if the type "typeOid" is being used as a column in some table
6949 : : * (possibly nested several levels deep in composite types, arrays, etc!).
6950 : : * Eventually, we'd like to propagate the check or rewrite operation
6951 : : * into such tables, but for now, just error out if we find any.
6952 : : *
6953 : : * Caller should provide either the associated relation of a rowtype,
6954 : : * or a type name (not both) for use in the error message, if any.
6955 : : *
6956 : : * Note that "typeOid" is not necessarily a composite type; it could also be
6957 : : * another container type such as an array or range, or a domain over one of
6958 : : * these things. The name of this function is therefore somewhat historical,
6959 : : * but it's not worth changing.
6960 : : *
6961 : : * We assume that functions and views depending on the type are not reasons
6962 : : * to reject the ALTER. (How safe is this really?)
6963 : : */
6964 : : void
6965 : 723 : find_composite_type_dependencies(Oid typeOid, Relation origRelation,
6966 : : const char *origTypeName)
6967 : : {
6968 : 723 : Relation depRel;
6969 : 723 : ScanKeyData key[2];
6970 : 723 : SysScanDesc depScan;
6971 : 723 : HeapTuple depTup;
6972 : :
6973 : : /* since this function recurses, it could be driven to stack overflow */
6974 : 723 : check_stack_depth();
6975 : :
6976 : : /*
6977 : : * We scan pg_depend to find those things that depend on the given type.
6978 : : * (We assume we can ignore refobjsubid for a type.)
6979 : : */
6980 : 723 : depRel = table_open(DependRelationId, AccessShareLock);
6981 : :
6982 : 1446 : ScanKeyInit(&key[0],
6983 : : Anum_pg_depend_refclassid,
6984 : : BTEqualStrategyNumber, F_OIDEQ,
6985 : 723 : ObjectIdGetDatum(TypeRelationId));
6986 : 1446 : ScanKeyInit(&key[1],
6987 : : Anum_pg_depend_refobjid,
6988 : : BTEqualStrategyNumber, F_OIDEQ,
6989 : 723 : ObjectIdGetDatum(typeOid));
6990 : :
6991 : 1446 : depScan = systable_beginscan(depRel, DependReferenceIndexId, true,
6992 : 723 : NULL, 2, key);
6993 : :
6994 [ + + ]: 1116 : while (HeapTupleIsValid(depTup = systable_getnext(depScan)))
6995 : : {
6996 : 415 : Form_pg_depend pg_depend = (Form_pg_depend) GETSTRUCT(depTup);
6997 : 415 : Relation rel;
6998 : 415 : TupleDesc tupleDesc;
6999 : 415 : Form_pg_attribute att;
7000 : :
7001 : : /* Check for directly dependent types */
7002 [ + + ]: 415 : if (pg_depend->classid == TypeRelationId)
7003 : : {
7004 : : /*
7005 : : * This must be an array, domain, or range containing the given
7006 : : * type, so recursively check for uses of this type. Note that
7007 : : * any error message will mention the original type not the
7008 : : * container; this is intentional.
7009 : : */
7010 : 696 : find_composite_type_dependencies(pg_depend->objid,
7011 : 348 : origRelation, origTypeName);
7012 : 348 : continue;
7013 : : }
7014 : :
7015 : : /* Else, ignore dependees that aren't relations */
7016 [ + + ]: 67 : if (pg_depend->classid != RelationRelationId)
7017 : 20 : continue;
7018 : :
7019 : 47 : rel = relation_open(pg_depend->objid, AccessShareLock);
7020 : 47 : tupleDesc = RelationGetDescr(rel);
7021 : :
7022 : : /*
7023 : : * If objsubid identifies a specific column, refer to that in error
7024 : : * messages. Otherwise, search to see if there's a user column of the
7025 : : * type. (We assume system columns are never of interesting types.)
7026 : : * The search is needed because an index containing an expression
7027 : : * column of the target type will just be recorded as a whole-relation
7028 : : * dependency. If we do not find a column of the type, the dependency
7029 : : * must indicate that the type is transiently referenced in an index
7030 : : * expression but not stored on disk, which we assume is OK, just as
7031 : : * we do for references in views. (It could also be that the target
7032 : : * type is embedded in some container type that is stored in an index
7033 : : * column, but the previous recursion should catch such cases.)
7034 : : */
7035 [ + + - + ]: 47 : if (pg_depend->objsubid > 0 && pg_depend->objsubid <= tupleDesc->natts)
7036 : 21 : att = TupleDescAttr(tupleDesc, pg_depend->objsubid - 1);
7037 : : else
7038 : : {
7039 : 26 : att = NULL;
7040 [ + + ]: 68 : for (int attno = 1; attno <= tupleDesc->natts; attno++)
7041 : : {
7042 : 42 : att = TupleDescAttr(tupleDesc, attno - 1);
7043 [ + + + - ]: 42 : if (att->atttypid == typeOid && !att->attisdropped)
7044 : 1 : break;
7045 : 41 : att = NULL;
7046 : 41 : }
7047 [ + + ]: 26 : if (att == NULL)
7048 : : {
7049 : : /* No such column, so assume OK */
7050 : 25 : relation_close(rel, AccessShareLock);
7051 : 25 : continue;
7052 : : }
7053 : : }
7054 : :
7055 : : /*
7056 : : * We definitely should reject if the relation has storage. If it's
7057 : : * partitioned, then perhaps we don't have to reject: if there are
7058 : : * partitions then we'll fail when we find one, else there is no
7059 : : * stored data to worry about. However, it's possible that the type
7060 : : * change would affect conclusions about whether the type is sortable
7061 : : * or hashable and thus (if it's a partitioning column) break the
7062 : : * partitioning rule. For now, reject for partitioned rels too.
7063 : : */
7064 [ - + ]: 22 : if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind) ||
7065 : 0 : RELKIND_HAS_PARTITIONS(rel->rd_rel->relkind))
7066 : : {
7067 [ + + ]: 22 : if (origTypeName)
7068 [ + - + - ]: 5 : ereport(ERROR,
7069 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
7070 : : errmsg("cannot alter type \"%s\" because column \"%s.%s\" uses it",
7071 : : origTypeName,
7072 : : RelationGetRelationName(rel),
7073 : : NameStr(att->attname))));
7074 [ + + ]: 17 : else if (origRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
7075 [ + - + - ]: 3 : ereport(ERROR,
7076 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
7077 : : errmsg("cannot alter type \"%s\" because column \"%s.%s\" uses it",
7078 : : RelationGetRelationName(origRelation),
7079 : : RelationGetRelationName(rel),
7080 : : NameStr(att->attname))));
7081 [ + + ]: 14 : else if (origRelation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
7082 [ + - + - ]: 1 : ereport(ERROR,
7083 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
7084 : : errmsg("cannot alter foreign table \"%s\" because column \"%s.%s\" uses its row type",
7085 : : RelationGetRelationName(origRelation),
7086 : : RelationGetRelationName(rel),
7087 : : NameStr(att->attname))));
7088 : : else
7089 [ + - + - ]: 13 : ereport(ERROR,
7090 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
7091 : : errmsg("cannot alter table \"%s\" because column \"%s.%s\" uses its row type",
7092 : : RelationGetRelationName(origRelation),
7093 : : RelationGetRelationName(rel),
7094 : : NameStr(att->attname))));
7095 : 0 : }
7096 [ # # ]: 0 : else if (OidIsValid(rel->rd_rel->reltype))
7097 : : {
7098 : : /*
7099 : : * A view or composite type itself isn't a problem, but we must
7100 : : * recursively check for indirect dependencies via its rowtype.
7101 : : */
7102 : 0 : find_composite_type_dependencies(rel->rd_rel->reltype,
7103 : 0 : origRelation, origTypeName);
7104 : 0 : }
7105 : :
7106 : 0 : relation_close(rel, AccessShareLock);
7107 [ - + - ]: 393 : }
7108 : :
7109 : 701 : systable_endscan(depScan);
7110 : :
7111 : 701 : relation_close(depRel, AccessShareLock);
7112 : 701 : }
7113 : :
7114 : :
7115 : : /*
7116 : : * find_typed_table_dependencies
7117 : : *
7118 : : * Check to see if a composite type is being used as the type of a
7119 : : * typed table. Abort if any are found and behavior is RESTRICT.
7120 : : * Else return the list of tables.
7121 : : */
7122 : : static List *
7123 : 33 : find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior behavior)
7124 : : {
7125 : 33 : Relation classRel;
7126 : 33 : ScanKeyData key[1];
7127 : 33 : TableScanDesc scan;
7128 : 33 : HeapTuple tuple;
7129 : 33 : List *result = NIL;
7130 : :
7131 : 33 : classRel = table_open(RelationRelationId, AccessShareLock);
7132 : :
7133 : 66 : ScanKeyInit(&key[0],
7134 : : Anum_pg_class_reloftype,
7135 : : BTEqualStrategyNumber, F_OIDEQ,
7136 : 33 : ObjectIdGetDatum(typeOid));
7137 : :
7138 : 33 : scan = table_beginscan_catalog(classRel, 1, key);
7139 : :
7140 [ + + ]: 39 : while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
7141 : : {
7142 : 10 : Form_pg_class classform = (Form_pg_class) GETSTRUCT(tuple);
7143 : :
7144 [ + + ]: 10 : if (behavior == DROP_RESTRICT)
7145 [ + - + - ]: 4 : ereport(ERROR,
7146 : : (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
7147 : : errmsg("cannot alter type \"%s\" because it is the type of a typed table",
7148 : : typeName),
7149 : : errhint("Use ALTER ... CASCADE to alter the typed tables too.")));
7150 : : else
7151 : 6 : result = lappend_oid(result, classform->oid);
7152 : 6 : }
7153 : :
7154 : 29 : table_endscan(scan);
7155 : 29 : table_close(classRel, AccessShareLock);
7156 : :
7157 : 58 : return result;
7158 : 29 : }
7159 : :
7160 : :
7161 : : /*
7162 : : * check_of_type
7163 : : *
7164 : : * Check whether a type is suitable for CREATE TABLE OF/ALTER TABLE OF. If it
7165 : : * isn't suitable, throw an error. Currently, we require that the type
7166 : : * originated with CREATE TYPE AS. We could support any row type, but doing so
7167 : : * would require handling a number of extra corner cases in the DDL commands.
7168 : : * (Also, allowing domain-over-composite would open up a can of worms about
7169 : : * whether and how the domain's constraints should apply to derived tables.)
7170 : : */
7171 : : void
7172 : 28 : check_of_type(HeapTuple typetuple)
7173 : : {
7174 : 28 : Form_pg_type typ = (Form_pg_type) GETSTRUCT(typetuple);
7175 : 28 : bool typeOk = false;
7176 : :
7177 [ + + ]: 28 : if (typ->typtype == TYPTYPE_COMPOSITE)
7178 : : {
7179 : 27 : Relation typeRelation;
7180 : :
7181 [ + - ]: 27 : Assert(OidIsValid(typ->typrelid));
7182 : 27 : typeRelation = relation_open(typ->typrelid, AccessShareLock);
7183 : 27 : typeOk = (typeRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE);
7184 : :
7185 : : /*
7186 : : * Close the parent rel, but keep our AccessShareLock on it until xact
7187 : : * commit. That will prevent someone else from deleting or ALTERing
7188 : : * the type before the typed table creation/conversion commits.
7189 : : */
7190 : 27 : relation_close(typeRelation, NoLock);
7191 : :
7192 [ + + ]: 27 : if (!typeOk)
7193 [ + - + - ]: 1 : ereport(ERROR,
7194 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
7195 : : errmsg("type %s is the row type of another table",
7196 : : format_type_be(typ->oid)),
7197 : : errdetail("A typed table must use a stand-alone composite type created with CREATE TYPE.")));
7198 : 26 : }
7199 : : else
7200 [ + - + - ]: 1 : ereport(ERROR,
7201 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
7202 : : errmsg("type %s is not a composite type",
7203 : : format_type_be(typ->oid))));
7204 : 26 : }
7205 : :
7206 : :
7207 : : /*
7208 : : * ALTER TABLE ADD COLUMN
7209 : : *
7210 : : * Adds an additional attribute to a relation making the assumption that
7211 : : * CHECK, NOT NULL, and FOREIGN KEY constraints will be removed from the
7212 : : * AT_AddColumn AlterTableCmd by parse_utilcmd.c and added as independent
7213 : : * AlterTableCmd's.
7214 : : *
7215 : : * ADD COLUMN cannot use the normal ALTER TABLE recursion mechanism, because we
7216 : : * have to decide at runtime whether to recurse or not depending on whether we
7217 : : * actually add a column or merely merge with an existing column. (We can't
7218 : : * check this in a static pre-pass because it won't handle multiple inheritance
7219 : : * situations correctly.)
7220 : : */
7221 : : static void
7222 : 307 : ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
7223 : : bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode,
7224 : : AlterTableUtilityContext *context)
7225 : : {
7226 [ + + + + ]: 307 : if (rel->rd_rel->reloftype && !recursing)
7227 [ + - + - ]: 1 : ereport(ERROR,
7228 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
7229 : : errmsg("cannot add column to typed table")));
7230 : :
7231 [ + + ]: 306 : if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
7232 : 9 : ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);
7233 : :
7234 [ + + + + ]: 306 : if (recurse && !is_view)
7235 : 289 : cmd->recurse = true;
7236 : 306 : }
7237 : :
7238 : : /*
7239 : : * Add a column to a table. The return value is the address of the
7240 : : * new column in the parent relation.
7241 : : *
7242 : : * cmd is pass-by-ref so that we can replace it with the parse-transformed
7243 : : * copy (but that happens only after we check for IF NOT EXISTS).
7244 : : */
7245 : : static ObjectAddress
7246 : 425 : ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
7247 : : AlterTableCmd **cmd, bool recurse, bool recursing,
7248 : : LOCKMODE lockmode, AlterTablePass cur_pass,
7249 : : AlterTableUtilityContext *context)
7250 : : {
7251 : 425 : Oid myrelid = RelationGetRelid(rel);
7252 : 425 : ColumnDef *colDef = castNode(ColumnDef, (*cmd)->def);
7253 : 425 : bool if_not_exists = (*cmd)->missing_ok;
7254 : 425 : Relation pgclass,
7255 : : attrdesc;
7256 : 425 : HeapTuple reltup;
7257 : 425 : Form_pg_class relform;
7258 : 425 : Form_pg_attribute attribute;
7259 : 425 : int newattnum;
7260 : 425 : char relkind;
7261 : 425 : Expr *defval;
7262 : 425 : List *children;
7263 : 425 : ListCell *child;
7264 : 425 : AlterTableCmd *childcmd;
7265 : 425 : ObjectAddress address;
7266 : 425 : TupleDesc tupdesc;
7267 : :
7268 : : /* since this function recurses, it could be driven to stack overflow */
7269 : 425 : check_stack_depth();
7270 : :
7271 : : /* At top level, permission check was done in ATPrepCmd, else do it */
7272 [ + + ]: 425 : if (recursing)
7273 : 121 : ATSimplePermissions((*cmd)->subtype, rel,
7274 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
7275 : :
7276 [ + + + + ]: 425 : if (rel->rd_rel->relispartition && !recursing)
7277 [ + - + - ]: 2 : ereport(ERROR,
7278 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
7279 : : errmsg("cannot add column to a partition")));
7280 : :
7281 : 423 : attrdesc = table_open(AttributeRelationId, RowExclusiveLock);
7282 : :
7283 : : /*
7284 : : * Are we adding the column to a recursion child? If so, check whether to
7285 : : * merge with an existing definition for the column. If we do merge, we
7286 : : * must not recurse. Children will already have the column, and recursing
7287 : : * into them would mess up attinhcount.
7288 : : */
7289 [ + + ]: 423 : if (colDef->inhcount > 0)
7290 : : {
7291 : 121 : HeapTuple tuple;
7292 : :
7293 : : /* Does child already have a column by this name? */
7294 : 121 : tuple = SearchSysCacheCopyAttName(myrelid, colDef->colname);
7295 [ + + ]: 121 : if (HeapTupleIsValid(tuple))
7296 : : {
7297 : 10 : Form_pg_attribute childatt = (Form_pg_attribute) GETSTRUCT(tuple);
7298 : 10 : Oid ctypeId;
7299 : 10 : int32 ctypmod;
7300 : 10 : Oid ccollid;
7301 : :
7302 : : /* Child column must match on type, typmod, and collation */
7303 : 10 : typenameTypeIdAndMod(NULL, colDef->typeName, &ctypeId, &ctypmod);
7304 [ + - ]: 10 : if (ctypeId != childatt->atttypid ||
7305 : 10 : ctypmod != childatt->atttypmod)
7306 [ # # # # ]: 0 : ereport(ERROR,
7307 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
7308 : : errmsg("child table \"%s\" has different type for column \"%s\"",
7309 : : RelationGetRelationName(rel), colDef->colname)));
7310 : 10 : ccollid = GetColumnDefCollation(NULL, colDef, ctypeId);
7311 [ + - ]: 10 : if (ccollid != childatt->attcollation)
7312 [ # # # # ]: 0 : ereport(ERROR,
7313 : : (errcode(ERRCODE_COLLATION_MISMATCH),
7314 : : errmsg("child table \"%s\" has different collation for column \"%s\"",
7315 : : RelationGetRelationName(rel), colDef->colname),
7316 : : errdetail("\"%s\" versus \"%s\"",
7317 : : get_collation_name(ccollid),
7318 : : get_collation_name(childatt->attcollation))));
7319 : :
7320 : : /* Bump the existing child att's inhcount */
7321 [ + - + - ]: 20 : if (pg_add_s16_overflow(childatt->attinhcount, 1,
7322 : 10 : &childatt->attinhcount))
7323 [ # # # # ]: 0 : ereport(ERROR,
7324 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
7325 : : errmsg("too many inheritance parents"));
7326 : 10 : CatalogTupleUpdate(attrdesc, &tuple->t_self, tuple);
7327 : :
7328 : 10 : heap_freetuple(tuple);
7329 : :
7330 : : /* Inform the user about the merge */
7331 [ - + + - ]: 10 : ereport(NOTICE,
7332 : : (errmsg("merging definition of column \"%s\" for child \"%s\"",
7333 : : colDef->colname, RelationGetRelationName(rel))));
7334 : :
7335 : 10 : table_close(attrdesc, RowExclusiveLock);
7336 : :
7337 : : /* Make the child column change visible */
7338 : 10 : CommandCounterIncrement();
7339 : :
7340 : 10 : return InvalidObjectAddress;
7341 : 10 : }
7342 [ + + ]: 121 : }
7343 : :
7344 : : /* skip if the name already exists and if_not_exists is true */
7345 [ + + ]: 413 : if (!check_for_column_name_collision(rel, colDef->colname, if_not_exists))
7346 : : {
7347 : 9 : table_close(attrdesc, RowExclusiveLock);
7348 : 9 : return InvalidObjectAddress;
7349 : : }
7350 : :
7351 : : /*
7352 : : * Okay, we need to add the column, so go ahead and do parse
7353 : : * transformation. This can result in queueing up, or even immediately
7354 : : * executing, subsidiary operations (such as creation of unique indexes);
7355 : : * so we mustn't do it until we have made the if_not_exists check.
7356 : : *
7357 : : * When recursing, the command was already transformed and we needn't do
7358 : : * so again. Also, if context isn't given we can't transform. (That
7359 : : * currently happens only for AT_AddColumnToView; we expect that view.c
7360 : : * passed us a ColumnDef that doesn't need work.)
7361 : : */
7362 [ + + + + ]: 404 : if (context != NULL && !recursing)
7363 : : {
7364 : 566 : *cmd = ATParseTransformCmd(wqueue, tab, rel, *cmd, recurse, lockmode,
7365 : 283 : cur_pass, context);
7366 [ + - ]: 283 : Assert(*cmd != NULL);
7367 : 283 : colDef = castNode(ColumnDef, (*cmd)->def);
7368 : 283 : }
7369 : :
7370 : : /*
7371 : : * Regular inheritance children are independent enough not to inherit the
7372 : : * identity column from parent hence cannot recursively add identity
7373 : : * column if the table has inheritance children.
7374 : : *
7375 : : * Partitions, on the other hand, are integral part of a partitioned table
7376 : : * and inherit identity column. Hence propagate identity column down the
7377 : : * partition hierarchy.
7378 : : */
7379 [ + + ]: 404 : if (colDef->identity &&
7380 [ + - ]: 9 : recurse &&
7381 [ + + + + ]: 9 : rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
7382 : 8 : find_inheritance_children(myrelid, NoLock) != NIL)
7383 [ + - + - ]: 1 : ereport(ERROR,
7384 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
7385 : : errmsg("cannot recursively add identity column to table that has child tables")));
7386 : :
7387 : 385 : pgclass = table_open(RelationRelationId, RowExclusiveLock);
7388 : :
7389 : 385 : reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
7390 [ + - ]: 385 : if (!HeapTupleIsValid(reltup))
7391 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", myrelid);
7392 : 385 : relform = (Form_pg_class) GETSTRUCT(reltup);
7393 : 385 : relkind = relform->relkind;
7394 : :
7395 : : /* Determine the new attribute's number */
7396 : 385 : newattnum = relform->relnatts + 1;
7397 [ + - ]: 385 : if (newattnum > MaxHeapAttributeNumber)
7398 [ # # # # ]: 0 : ereport(ERROR,
7399 : : (errcode(ERRCODE_TOO_MANY_COLUMNS),
7400 : : errmsg("tables can have at most %d columns",
7401 : : MaxHeapAttributeNumber)));
7402 : :
7403 : : /*
7404 : : * Construct new attribute's pg_attribute entry.
7405 : : */
7406 : 385 : tupdesc = BuildDescForRelation(list_make1(colDef));
7407 : :
7408 : 385 : attribute = TupleDescAttr(tupdesc, 0);
7409 : :
7410 : : /* Fix up attribute number */
7411 : 385 : attribute->attnum = newattnum;
7412 : :
7413 : : /* make sure datatype is legal for a column */
7414 : 770 : CheckAttributeType(NameStr(attribute->attname), attribute->atttypid, attribute->attcollation,
7415 : 385 : list_make1_oid(rel->rd_rel->reltype),
7416 : 385 : (attribute->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL ? CHKATYPE_IS_VIRTUAL : 0));
7417 : :
7418 : 385 : InsertPgAttributeTuples(attrdesc, tupdesc, myrelid, NULL, NULL);
7419 : :
7420 : 385 : table_close(attrdesc, RowExclusiveLock);
7421 : :
7422 : : /*
7423 : : * Update pg_class tuple as appropriate
7424 : : */
7425 : 385 : relform->relnatts = newattnum;
7426 : :
7427 : 385 : CatalogTupleUpdate(pgclass, &reltup->t_self, reltup);
7428 : :
7429 : 385 : heap_freetuple(reltup);
7430 : :
7431 : : /* Post creation hook for new attribute */
7432 [ + - ]: 385 : InvokeObjectPostCreateHook(RelationRelationId, myrelid, newattnum);
7433 : :
7434 : 385 : table_close(pgclass, RowExclusiveLock);
7435 : :
7436 : : /* Make the attribute's catalog entry visible */
7437 : 385 : CommandCounterIncrement();
7438 : :
7439 : : /*
7440 : : * Store the DEFAULT, if any, in the catalogs
7441 : : */
7442 [ + + ]: 385 : if (colDef->raw_default)
7443 : : {
7444 : 116 : RawColumnDefault *rawEnt;
7445 : :
7446 : 116 : rawEnt = palloc_object(RawColumnDefault);
7447 : 116 : rawEnt->attnum = attribute->attnum;
7448 : 116 : rawEnt->raw_default = copyObject(colDef->raw_default);
7449 : 116 : rawEnt->generated = colDef->generated;
7450 : :
7451 : : /*
7452 : : * This function is intended for CREATE TABLE, so it processes a
7453 : : * _list_ of defaults, but we just do one.
7454 : : */
7455 : 116 : AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
7456 : : false, true, false, NULL);
7457 : :
7458 : : /* Make the additional catalog changes visible */
7459 : 116 : CommandCounterIncrement();
7460 : 116 : }
7461 : :
7462 : : /*
7463 : : * Tell Phase 3 to fill in the default expression, if there is one.
7464 : : *
7465 : : * If there is no default, Phase 3 doesn't have to do anything, because
7466 : : * that effectively means that the default is NULL. The heap tuple access
7467 : : * routines always check for attnum > # of attributes in tuple, and return
7468 : : * NULL if so, so without any modification of the tuple data we will get
7469 : : * the effect of NULL values in the new column.
7470 : : *
7471 : : * An exception occurs when the new column is of a domain type: the domain
7472 : : * might have a not-null constraint, or a check constraint that indirectly
7473 : : * rejects nulls. If there are any domain constraints then we construct
7474 : : * an explicit NULL default value that will be passed through
7475 : : * CoerceToDomain processing. (This is a tad inefficient, since it causes
7476 : : * rewriting the table which we really wouldn't have to do; but we do it
7477 : : * to preserve the historical behavior that such a failure will be raised
7478 : : * only if the table currently contains some rows.)
7479 : : *
7480 : : * Note: we use build_column_default, and not just the cooked default
7481 : : * returned by AddRelationNewConstraints, so that the right thing happens
7482 : : * when a datatype's default applies.
7483 : : *
7484 : : * Note: it might seem that this should happen at the end of Phase 2, so
7485 : : * that the effects of subsequent subcommands can be taken into account.
7486 : : * It's intentional that we do it now, though. The new column should be
7487 : : * filled according to what is said in the ADD COLUMN subcommand, so that
7488 : : * the effects are the same as if this subcommand had been run by itself
7489 : : * and the later subcommands had been issued in new ALTER TABLE commands.
7490 : : *
7491 : : * We can skip this entirely for relations without storage, since Phase 3
7492 : : * is certainly not going to touch them.
7493 : : */
7494 [ + + + - : 385 : if (RELKIND_HAS_STORAGE(relkind))
+ - + - -
+ ]
7495 : : {
7496 : 324 : bool has_domain_constraints;
7497 : 324 : bool has_missing = false;
7498 : :
7499 : : /*
7500 : : * For an identity column, we can't use build_column_default(),
7501 : : * because the sequence ownership isn't set yet. So do it manually.
7502 : : */
7503 [ + + ]: 324 : if (colDef->identity)
7504 : : {
7505 : 7 : NextValueExpr *nve = makeNode(NextValueExpr);
7506 : :
7507 : 7 : nve->seqid = RangeVarGetRelid(colDef->identitySequence, NoLock, false);
7508 : 7 : nve->typeId = attribute->atttypid;
7509 : :
7510 : 7 : defval = (Expr *) nve;
7511 : 7 : }
7512 : : else
7513 : 317 : defval = (Expr *) build_column_default(rel, attribute->attnum);
7514 : :
7515 : : /* Build CoerceToDomain(NULL) expression if needed */
7516 : 324 : has_domain_constraints = DomainHasConstraints(attribute->atttypid);
7517 [ + + + + ]: 324 : if (!defval && has_domain_constraints)
7518 : : {
7519 : 1 : Oid baseTypeId;
7520 : 1 : int32 baseTypeMod;
7521 : 1 : Oid baseTypeColl;
7522 : :
7523 : 1 : baseTypeMod = attribute->atttypmod;
7524 : 1 : baseTypeId = getBaseTypeAndTypmod(attribute->atttypid, &baseTypeMod);
7525 : 1 : baseTypeColl = get_typcollation(baseTypeId);
7526 : 1 : defval = (Expr *) makeNullConst(baseTypeId, baseTypeMod, baseTypeColl);
7527 : 1 : defval = (Expr *) coerce_to_target_type(NULL,
7528 : 1 : (Node *) defval,
7529 : 1 : baseTypeId,
7530 : 1 : attribute->atttypid,
7531 : 1 : attribute->atttypmod,
7532 : : COERCION_ASSIGNMENT,
7533 : : COERCE_IMPLICIT_CAST,
7534 : : -1);
7535 [ + - ]: 1 : if (defval == NULL) /* should not happen */
7536 [ # # # # ]: 0 : elog(ERROR, "failed to coerce base type to domain");
7537 : 1 : }
7538 : :
7539 [ + + ]: 324 : if (defval)
7540 : : {
7541 : 114 : NewColumnValue *newval;
7542 : :
7543 : : /* Prepare defval for execution, either here or in Phase 3 */
7544 : 114 : defval = expression_planner(defval);
7545 : :
7546 : : /* Add the new default to the newvals list */
7547 : 114 : newval = palloc0_object(NewColumnValue);
7548 : 114 : newval->attnum = attribute->attnum;
7549 : 114 : newval->expr = defval;
7550 : 114 : newval->is_generated = (colDef->generated != '\0');
7551 : :
7552 : 114 : tab->newvals = lappend(tab->newvals, newval);
7553 : :
7554 : : /*
7555 : : * Attempt to skip a complete table rewrite by storing the
7556 : : * specified DEFAULT value outside of the heap. This is only
7557 : : * allowed for plain relations and non-generated columns, and the
7558 : : * default expression can't be volatile (stable is OK). Note that
7559 : : * contain_volatile_functions deems CoerceToDomain immutable, but
7560 : : * here we consider that coercion to a domain with constraints is
7561 : : * volatile; else it might fail even when the table is empty.
7562 : : */
7563 [ + - ]: 114 : if (rel->rd_rel->relkind == RELKIND_RELATION &&
7564 [ + + ]: 114 : !colDef->generated &&
7565 [ + + + + ]: 93 : !has_domain_constraints &&
7566 : 91 : !contain_volatile_functions((Node *) defval))
7567 : : {
7568 : 65 : EState *estate;
7569 : 65 : ExprState *exprState;
7570 : 65 : Datum missingval;
7571 : 65 : bool missingIsNull;
7572 : :
7573 : : /* Evaluate the default expression */
7574 : 65 : estate = CreateExecutorState();
7575 : 65 : exprState = ExecPrepareExpr(defval, estate);
7576 : 130 : missingval = ExecEvalExpr(exprState,
7577 [ + - ]: 65 : GetPerTupleExprContext(estate),
7578 : : &missingIsNull);
7579 : : /* If it turns out NULL, nothing to do; else store it */
7580 [ - + ]: 65 : if (!missingIsNull)
7581 : : {
7582 : 65 : StoreAttrMissingVal(rel, attribute->attnum, missingval);
7583 : : /* Make the additional catalog change visible */
7584 : 65 : CommandCounterIncrement();
7585 : 65 : has_missing = true;
7586 : 65 : }
7587 : 65 : FreeExecutorState(estate);
7588 : 65 : }
7589 : : else
7590 : : {
7591 : : /*
7592 : : * Failed to use missing mode. We have to do a table rewrite
7593 : : * to install the value --- unless it's a virtual generated
7594 : : * column.
7595 : : */
7596 [ + + ]: 49 : if (colDef->generated != ATTRIBUTE_GENERATED_VIRTUAL)
7597 : 34 : tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
7598 : : }
7599 : 114 : }
7600 : :
7601 [ + + ]: 324 : if (!has_missing)
7602 : : {
7603 : : /*
7604 : : * If the new column is NOT NULL, and there is no missing value,
7605 : : * tell Phase 3 it needs to check for NULLs.
7606 : : */
7607 : 259 : tab->verify_new_notnull |= colDef->is_not_null;
7608 : 259 : }
7609 : 324 : }
7610 : :
7611 : : /*
7612 : : * Add needed dependency entries for the new column.
7613 : : */
7614 : 385 : add_column_datatype_dependency(myrelid, newattnum, attribute->atttypid);
7615 : 385 : add_column_collation_dependency(myrelid, newattnum, attribute->attcollation);
7616 : :
7617 : : /*
7618 : : * Propagate to children as appropriate. Unlike most other ALTER
7619 : : * routines, we have to do this one level of recursion at a time; we can't
7620 : : * use find_all_inheritors to do it in one pass.
7621 : : */
7622 : 385 : children =
7623 : 385 : find_inheritance_children(RelationGetRelid(rel), lockmode);
7624 : :
7625 : : /*
7626 : : * If we are told not to recurse, there had better not be any child
7627 : : * tables; else the addition would put them out of step.
7628 : : */
7629 [ + + + + ]: 385 : if (children && !recurse)
7630 [ + - + - ]: 2 : ereport(ERROR,
7631 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
7632 : : errmsg("column must be added to child tables too")));
7633 : :
7634 : : /* Children should see column as singly inherited */
7635 [ + + ]: 383 : if (!recursing)
7636 : : {
7637 : 272 : childcmd = copyObject(*cmd);
7638 : 272 : colDef = castNode(ColumnDef, childcmd->def);
7639 : 272 : colDef->inhcount = 1;
7640 : 272 : colDef->is_local = false;
7641 : 272 : }
7642 : : else
7643 : 111 : childcmd = *cmd; /* no need to copy again */
7644 : :
7645 [ + + + + : 504 : foreach(child, children)
+ + ]
7646 : : {
7647 : 121 : Oid childrelid = lfirst_oid(child);
7648 : 121 : Relation childrel;
7649 : 121 : AlteredTableInfo *childtab;
7650 : :
7651 : : /* find_inheritance_children already got lock */
7652 : 121 : childrel = table_open(childrelid, NoLock);
7653 : 121 : CheckAlterTableIsSafe(childrel);
7654 : :
7655 : : /* Find or create work queue entry for this table */
7656 : 121 : childtab = ATGetQueueEntry(wqueue, childrel);
7657 : :
7658 : : /* Recurse to child; return value is ignored */
7659 : 242 : ATExecAddColumn(wqueue, childtab, childrel,
7660 : 121 : &childcmd, recurse, true,
7661 : 121 : lockmode, cur_pass, context);
7662 : :
7663 : 121 : table_close(childrel, NoLock);
7664 : 121 : }
7665 : :
7666 : 383 : ObjectAddressSubSet(address, RelationRelationId, myrelid, newattnum);
7667 : 383 : return address;
7668 : 402 : }
7669 : :
7670 : : /*
7671 : : * If a new or renamed column will collide with the name of an existing
7672 : : * column and if_not_exists is false then error out, else do nothing.
7673 : : */
7674 : : static bool
7675 : 486 : check_for_column_name_collision(Relation rel, const char *colname,
7676 : : bool if_not_exists)
7677 : : {
7678 : 486 : HeapTuple attTuple;
7679 : 486 : int attnum;
7680 : :
7681 : : /*
7682 : : * this test is deliberately not attisdropped-aware, since if one tries to
7683 : : * add a column matching a dropped column name, it's gonna fail anyway.
7684 : : */
7685 : 486 : attTuple = SearchSysCache2(ATTNAME,
7686 : 486 : ObjectIdGetDatum(RelationGetRelid(rel)),
7687 : 486 : PointerGetDatum(colname));
7688 [ + + ]: 486 : if (!HeapTupleIsValid(attTuple))
7689 : 470 : return true;
7690 : :
7691 : 16 : attnum = ((Form_pg_attribute) GETSTRUCT(attTuple))->attnum;
7692 : 16 : ReleaseSysCache(attTuple);
7693 : :
7694 : : /*
7695 : : * We throw a different error message for conflicts with system column
7696 : : * names, since they are normally not shown and the user might otherwise
7697 : : * be confused about the reason for the conflict.
7698 : : */
7699 [ + + ]: 16 : if (attnum <= 0)
7700 [ + - + - ]: 2 : ereport(ERROR,
7701 : : (errcode(ERRCODE_DUPLICATE_COLUMN),
7702 : : errmsg("column name \"%s\" conflicts with a system column name",
7703 : : colname)));
7704 : : else
7705 : : {
7706 [ + + ]: 14 : if (if_not_exists)
7707 : : {
7708 [ - + + - ]: 9 : ereport(NOTICE,
7709 : : (errcode(ERRCODE_DUPLICATE_COLUMN),
7710 : : errmsg("column \"%s\" of relation \"%s\" already exists, skipping",
7711 : : colname, RelationGetRelationName(rel))));
7712 : 9 : return false;
7713 : : }
7714 : :
7715 [ + - + - ]: 5 : ereport(ERROR,
7716 : : (errcode(ERRCODE_DUPLICATE_COLUMN),
7717 : : errmsg("column \"%s\" of relation \"%s\" already exists",
7718 : : colname, RelationGetRelationName(rel))));
7719 : : }
7720 : :
7721 : 0 : return true;
7722 : 479 : }
7723 : :
7724 : : /*
7725 : : * Install a column's dependency on its datatype.
7726 : : */
7727 : : static void
7728 : 563 : add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid)
7729 : : {
7730 : 563 : ObjectAddress myself,
7731 : : referenced;
7732 : :
7733 : 563 : myself.classId = RelationRelationId;
7734 : 563 : myself.objectId = relid;
7735 : 563 : myself.objectSubId = attnum;
7736 : 563 : referenced.classId = TypeRelationId;
7737 : 563 : referenced.objectId = typid;
7738 : 563 : referenced.objectSubId = 0;
7739 : 563 : recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
7740 : 563 : }
7741 : :
7742 : : /*
7743 : : * Install a column's dependency on its collation.
7744 : : */
7745 : : static void
7746 : 563 : add_column_collation_dependency(Oid relid, int32 attnum, Oid collid)
7747 : : {
7748 : 563 : ObjectAddress myself,
7749 : : referenced;
7750 : :
7751 : : /* We know the default collation is pinned, so don't bother recording it */
7752 [ + + + + ]: 563 : if (OidIsValid(collid) && collid != DEFAULT_COLLATION_OID)
7753 : : {
7754 : 3 : myself.classId = RelationRelationId;
7755 : 3 : myself.objectId = relid;
7756 : 3 : myself.objectSubId = attnum;
7757 : 3 : referenced.classId = CollationRelationId;
7758 : 3 : referenced.objectId = collid;
7759 : 3 : referenced.objectSubId = 0;
7760 : 3 : recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
7761 : 3 : }
7762 : 563 : }
7763 : :
7764 : : /*
7765 : : * ALTER TABLE ALTER COLUMN DROP NOT NULL
7766 : : *
7767 : : * Return the address of the modified column. If the column was already
7768 : : * nullable, InvalidObjectAddress is returned.
7769 : : */
7770 : : static ObjectAddress
7771 : 34 : ATExecDropNotNull(Relation rel, const char *colName, bool recurse,
7772 : : LOCKMODE lockmode)
7773 : : {
7774 : 34 : HeapTuple tuple;
7775 : 34 : HeapTuple conTup;
7776 : 34 : Form_pg_attribute attTup;
7777 : 34 : AttrNumber attnum;
7778 : 34 : Relation attr_rel;
7779 : 34 : ObjectAddress address;
7780 : :
7781 : : /*
7782 : : * lookup the attribute
7783 : : */
7784 : 34 : attr_rel = table_open(AttributeRelationId, RowExclusiveLock);
7785 : :
7786 : 34 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
7787 [ + + ]: 34 : if (!HeapTupleIsValid(tuple))
7788 [ + - + - ]: 3 : ereport(ERROR,
7789 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
7790 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
7791 : : colName, RelationGetRelationName(rel))));
7792 : 31 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
7793 : 31 : attnum = attTup->attnum;
7794 : 31 : ObjectAddressSubSet(address, RelationRelationId,
7795 : : RelationGetRelid(rel), attnum);
7796 : :
7797 : : /* If the column is already nullable there's nothing to do. */
7798 [ + - ]: 31 : if (!attTup->attnotnull)
7799 : : {
7800 : 0 : table_close(attr_rel, RowExclusiveLock);
7801 : 0 : return InvalidObjectAddress;
7802 : : }
7803 : :
7804 : : /* Prevent them from altering a system attribute */
7805 [ + - ]: 31 : if (attnum <= 0)
7806 [ # # # # ]: 0 : ereport(ERROR,
7807 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
7808 : : errmsg("cannot alter system column \"%s\"",
7809 : : colName)));
7810 : :
7811 [ + + ]: 31 : if (attTup->attidentity)
7812 [ + - + - ]: 3 : ereport(ERROR,
7813 : : (errcode(ERRCODE_SYNTAX_ERROR),
7814 : : errmsg("column \"%s\" of relation \"%s\" is an identity column",
7815 : : colName, RelationGetRelationName(rel))));
7816 : :
7817 : : /*
7818 : : * If rel is partition, shouldn't drop NOT NULL if parent has the same.
7819 : : */
7820 [ + + ]: 28 : if (rel->rd_rel->relispartition)
7821 : : {
7822 : 2 : Oid parentId = get_partition_parent(RelationGetRelid(rel), false);
7823 : 2 : Relation parent = table_open(parentId, AccessShareLock);
7824 : 2 : TupleDesc tupDesc = RelationGetDescr(parent);
7825 : 2 : AttrNumber parent_attnum;
7826 : :
7827 : 2 : parent_attnum = get_attnum(parentId, colName);
7828 [ - + ]: 2 : if (TupleDescAttr(tupDesc, parent_attnum - 1)->attnotnull)
7829 [ + - + - ]: 2 : ereport(ERROR,
7830 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
7831 : : errmsg("column \"%s\" is marked NOT NULL in parent table",
7832 : : colName)));
7833 : 0 : table_close(parent, AccessShareLock);
7834 : 0 : }
7835 : :
7836 : : /*
7837 : : * Find the constraint that makes this column NOT NULL, and drop it.
7838 : : * dropconstraint_internal() resets attnotnull.
7839 : : */
7840 : 26 : conTup = findNotNullConstraintAttnum(RelationGetRelid(rel), attnum);
7841 [ + - ]: 26 : if (conTup == NULL)
7842 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation \"%s\"",
7843 : : colName, RelationGetRelationName(rel));
7844 : :
7845 : : /* The normal case: we have a pg_constraint row, remove it */
7846 : 52 : dropconstraint_internal(rel, conTup, DROP_RESTRICT, recurse, false,
7847 : 26 : false, lockmode);
7848 : 26 : heap_freetuple(conTup);
7849 : :
7850 [ + - ]: 26 : InvokeObjectPostAlterHook(RelationRelationId,
7851 : : RelationGetRelid(rel), attnum);
7852 : :
7853 : 26 : table_close(attr_rel, RowExclusiveLock);
7854 : :
7855 : 26 : return address;
7856 : 26 : }
7857 : :
7858 : : /*
7859 : : * set_attnotnull
7860 : : * Helper to update/validate the pg_attribute status of a not-null
7861 : : * constraint
7862 : : *
7863 : : * pg_attribute.attnotnull is set true, if it isn't already.
7864 : : * If queue_validation is true, also set up wqueue to validate the constraint.
7865 : : * wqueue may be given as NULL when validation is not needed (e.g., on table
7866 : : * creation).
7867 : : */
7868 : : static void
7869 : 1811 : set_attnotnull(List **wqueue, Relation rel, AttrNumber attnum,
7870 : : bool is_valid, bool queue_validation)
7871 : : {
7872 : 1811 : Form_pg_attribute attr;
7873 : 1811 : CompactAttribute *thisatt;
7874 : :
7875 [ + + + - ]: 1811 : Assert(!queue_validation || wqueue);
7876 : :
7877 : 1811 : CheckAlterTableIsSafe(rel);
7878 : :
7879 : : /*
7880 : : * Exit quickly by testing attnotnull from the tupledesc's copy of the
7881 : : * attribute.
7882 : : */
7883 : 1811 : attr = TupleDescAttr(RelationGetDescr(rel), attnum - 1);
7884 [ - + ]: 1811 : if (attr->attisdropped)
7885 : 0 : return;
7886 : :
7887 [ + + ]: 1811 : if (!attr->attnotnull)
7888 : : {
7889 : 227 : Relation attr_rel;
7890 : 227 : HeapTuple tuple;
7891 : :
7892 : 227 : attr_rel = table_open(AttributeRelationId, RowExclusiveLock);
7893 : :
7894 : 227 : tuple = SearchSysCacheCopyAttNum(RelationGetRelid(rel), attnum);
7895 [ + - ]: 227 : if (!HeapTupleIsValid(tuple))
7896 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for attribute %d of relation %u",
7897 : : attnum, RelationGetRelid(rel));
7898 : :
7899 : 227 : thisatt = TupleDescCompactAttr(RelationGetDescr(rel), attnum - 1);
7900 : 227 : thisatt->attnullability = ATTNULLABLE_VALID;
7901 : :
7902 : 227 : attr = (Form_pg_attribute) GETSTRUCT(tuple);
7903 : :
7904 : 227 : attr->attnotnull = true;
7905 : 227 : CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple);
7906 : :
7907 : : /*
7908 : : * If the nullness isn't already proven by validated constraints, have
7909 : : * ALTER TABLE phase 3 test for it.
7910 : : */
7911 [ + + + - : 227 : if (queue_validation && wqueue &&
+ + ]
7912 : 189 : !NotNullImpliedByRelConstraints(rel, attr))
7913 : : {
7914 : 182 : AlteredTableInfo *tab;
7915 : :
7916 : 182 : tab = ATGetQueueEntry(wqueue, rel);
7917 : 182 : tab->verify_new_notnull = true;
7918 : 182 : }
7919 : :
7920 : 227 : CommandCounterIncrement();
7921 : :
7922 : 227 : table_close(attr_rel, RowExclusiveLock);
7923 : 227 : heap_freetuple(tuple);
7924 : 227 : }
7925 : : else
7926 : : {
7927 : 1584 : CacheInvalidateRelcache(rel);
7928 : : }
7929 [ - + ]: 1811 : }
7930 : :
7931 : : /*
7932 : : * ALTER TABLE ALTER COLUMN SET NOT NULL
7933 : : *
7934 : : * Add a not-null constraint to a single table and its children. Returns
7935 : : * the address of the constraint added to the parent relation, if one gets
7936 : : * added, or InvalidObjectAddress otherwise.
7937 : : *
7938 : : * We must recurse to child tables during execution, rather than using
7939 : : * ALTER TABLE's normal prep-time recursion.
7940 : : */
7941 : : static ObjectAddress
7942 : 112 : ATExecSetNotNull(List **wqueue, Relation rel, char *conName, char *colName,
7943 : : bool recurse, bool recursing, LOCKMODE lockmode)
7944 : : {
7945 : 112 : HeapTuple tuple;
7946 : 112 : AttrNumber attnum;
7947 : 112 : ObjectAddress address;
7948 : 112 : Constraint *constraint;
7949 : 112 : CookedConstraint *ccon;
7950 : 112 : List *cooked;
7951 : 112 : bool is_no_inherit = false;
7952 : :
7953 : : /* Guard against stack overflow due to overly deep inheritance tree. */
7954 : 112 : check_stack_depth();
7955 : :
7956 : : /* At top level, permission check was done in ATPrepCmd, else do it */
7957 [ + + ]: 112 : if (recursing)
7958 : : {
7959 : 48 : ATSimplePermissions(AT_AddConstraint, rel,
7960 : : ATT_PARTITIONED_TABLE | ATT_TABLE | ATT_FOREIGN_TABLE);
7961 [ + - ]: 48 : Assert(conName != NULL);
7962 : 48 : }
7963 : :
7964 : 112 : attnum = get_attnum(RelationGetRelid(rel), colName);
7965 [ + + ]: 112 : if (attnum == InvalidAttrNumber)
7966 [ + - + - ]: 3 : ereport(ERROR,
7967 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
7968 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
7969 : : colName, RelationGetRelationName(rel))));
7970 : :
7971 : : /* Prevent them from altering a system attribute */
7972 [ + - ]: 109 : if (attnum <= 0)
7973 [ # # # # ]: 0 : ereport(ERROR,
7974 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
7975 : : errmsg("cannot alter system column \"%s\"",
7976 : : colName)));
7977 : :
7978 : : /* See if there's already a constraint */
7979 : 109 : tuple = findNotNullConstraintAttnum(RelationGetRelid(rel), attnum);
7980 [ + + ]: 109 : if (HeapTupleIsValid(tuple))
7981 : : {
7982 : 26 : Form_pg_constraint conForm = (Form_pg_constraint) GETSTRUCT(tuple);
7983 : 26 : bool changed = false;
7984 : :
7985 : : /*
7986 : : * Don't let a NO INHERIT constraint be changed into inherit.
7987 : : */
7988 [ + + - + ]: 26 : if (conForm->connoinherit && recurse)
7989 [ + - + - ]: 2 : ereport(ERROR,
7990 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
7991 : : errmsg("cannot change NO INHERIT status of NOT NULL constraint \"%s\" on relation \"%s\"",
7992 : : NameStr(conForm->conname),
7993 : : RelationGetRelationName(rel)));
7994 : :
7995 : : /*
7996 : : * If we find an appropriate constraint, we're almost done, but just
7997 : : * need to change some properties on it: if we're recursing, increment
7998 : : * coninhcount; if not, set conislocal if not already set.
7999 : : */
8000 [ + + ]: 24 : if (recursing)
8001 : : {
8002 [ + - + - ]: 34 : if (pg_add_s16_overflow(conForm->coninhcount, 1,
8003 : 17 : &conForm->coninhcount))
8004 [ # # # # ]: 0 : ereport(ERROR,
8005 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
8006 : : errmsg("too many inheritance parents"));
8007 : 17 : changed = true;
8008 : 17 : }
8009 [ + - ]: 7 : else if (!conForm->conislocal)
8010 : : {
8011 : 0 : conForm->conislocal = true;
8012 : 0 : changed = true;
8013 : 0 : }
8014 [ + + ]: 7 : else if (!conForm->convalidated)
8015 : : {
8016 : : /*
8017 : : * Flip attnotnull and convalidated, and also validate the
8018 : : * constraint.
8019 : : */
8020 : 8 : return ATExecValidateConstraint(wqueue, rel, NameStr(conForm->conname),
8021 : 4 : recurse, recursing, lockmode);
8022 : : }
8023 : :
8024 [ + + ]: 20 : if (changed)
8025 : : {
8026 : 17 : Relation constr_rel;
8027 : :
8028 : 17 : constr_rel = table_open(ConstraintRelationId, RowExclusiveLock);
8029 : :
8030 : 17 : CatalogTupleUpdate(constr_rel, &tuple->t_self, tuple);
8031 : 17 : ObjectAddressSet(address, ConstraintRelationId, conForm->oid);
8032 : 17 : table_close(constr_rel, RowExclusiveLock);
8033 : 17 : }
8034 : :
8035 [ + + ]: 20 : if (changed)
8036 : 17 : return address;
8037 : : else
8038 : 3 : return InvalidObjectAddress;
8039 : 24 : }
8040 : :
8041 : : /*
8042 : : * If we're asked not to recurse, and children exist, raise an error for
8043 : : * partitioned tables. For inheritance, we act as if NO INHERIT had been
8044 : : * specified.
8045 : : */
8046 [ + + + + ]: 83 : if (!recurse &&
8047 : 5 : find_inheritance_children(RelationGetRelid(rel),
8048 : 5 : NoLock) != NIL)
8049 : : {
8050 [ + + ]: 3 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
8051 [ + - + - ]: 1 : ereport(ERROR,
8052 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8053 : : errmsg("constraint must be added to child tables too"),
8054 : : errhint("Do not specify the ONLY keyword."));
8055 : : else
8056 : 2 : is_no_inherit = true;
8057 : 2 : }
8058 : :
8059 : : /*
8060 : : * No constraint exists; we must add one. First determine a name to use,
8061 : : * if we haven't already.
8062 : : */
8063 [ + + ]: 82 : if (!recursing)
8064 : : {
8065 [ + - ]: 53 : Assert(conName == NULL);
8066 : 106 : conName = ChooseConstraintName(RelationGetRelationName(rel),
8067 : 53 : colName, "not_null",
8068 : 53 : RelationGetNamespace(rel),
8069 : : NIL);
8070 : 53 : }
8071 : :
8072 : 82 : constraint = makeNotNullConstraint(makeString(colName));
8073 : 82 : constraint->is_no_inherit = is_no_inherit;
8074 : 82 : constraint->conname = conName;
8075 : :
8076 : : /* and do it */
8077 : 164 : cooked = AddRelationNewConstraints(rel, NIL, list_make1(constraint),
8078 : 82 : false, !recursing, false, NULL);
8079 : 82 : ccon = linitial(cooked);
8080 : 82 : ObjectAddressSet(address, ConstraintRelationId, ccon->conoid);
8081 : :
8082 [ + - ]: 82 : InvokeObjectPostAlterHook(RelationRelationId,
8083 : : RelationGetRelid(rel), attnum);
8084 : :
8085 : : /* Mark pg_attribute.attnotnull for the column and queue validation */
8086 : 82 : set_attnotnull(wqueue, rel, attnum, true, true);
8087 : :
8088 : : /*
8089 : : * Recurse to propagate the constraint to children that don't have one.
8090 : : */
8091 [ + + ]: 82 : if (recurse)
8092 : : {
8093 : 78 : List *children;
8094 : :
8095 : 156 : children = find_inheritance_children(RelationGetRelid(rel),
8096 : 78 : lockmode);
8097 : :
8098 [ + + + + : 195 : foreach_oid(childoid, children)
+ + + + ]
8099 : : {
8100 : 38 : Relation childrel = table_open(childoid, NoLock);
8101 : :
8102 : 38 : CommandCounterIncrement();
8103 : :
8104 : 76 : ATExecSetNotNull(wqueue, childrel, conName, colName,
8105 : 38 : recurse, true, lockmode);
8106 : 38 : table_close(childrel, NoLock);
8107 : 117 : }
8108 : 78 : }
8109 : :
8110 : 82 : return address;
8111 : 106 : }
8112 : :
8113 : : /*
8114 : : * NotNullImpliedByRelConstraints
8115 : : * Does rel's existing constraints imply NOT NULL for the given attribute?
8116 : : */
8117 : : static bool
8118 : 189 : NotNullImpliedByRelConstraints(Relation rel, Form_pg_attribute attr)
8119 : : {
8120 : 189 : NullTest *nnulltest = makeNode(NullTest);
8121 : :
8122 : 189 : nnulltest->arg = (Expr *) makeVar(1,
8123 : 189 : attr->attnum,
8124 : 189 : attr->atttypid,
8125 : 189 : attr->atttypmod,
8126 : 189 : attr->attcollation,
8127 : : 0);
8128 : 189 : nnulltest->nulltesttype = IS_NOT_NULL;
8129 : :
8130 : : /*
8131 : : * argisrow = false is correct even for a composite column, because
8132 : : * attnotnull does not represent a SQL-spec IS NOT NULL test in such a
8133 : : * case, just IS DISTINCT FROM NULL.
8134 : : */
8135 : 189 : nnulltest->argisrow = false;
8136 : 189 : nnulltest->location = -1;
8137 : :
8138 [ + + ]: 189 : if (ConstraintImpliedByRelConstraint(rel, list_make1(nnulltest), NIL))
8139 : : {
8140 [ - + - + ]: 7 : ereport(DEBUG1,
8141 : : (errmsg_internal("existing constraints on column \"%s.%s\" are sufficient to prove that it does not contain nulls",
8142 : : RelationGetRelationName(rel), NameStr(attr->attname))));
8143 : 7 : return true;
8144 : : }
8145 : :
8146 : 182 : return false;
8147 : 189 : }
8148 : :
8149 : : /*
8150 : : * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
8151 : : *
8152 : : * Return the address of the affected column.
8153 : : */
8154 : : static ObjectAddress
8155 : 90 : ATExecColumnDefault(Relation rel, const char *colName,
8156 : : Node *newDefault, LOCKMODE lockmode)
8157 : : {
8158 : 90 : TupleDesc tupdesc = RelationGetDescr(rel);
8159 : 90 : AttrNumber attnum;
8160 : : ObjectAddress address;
8161 : :
8162 : : /*
8163 : : * get the number of the attribute
8164 : : */
8165 : 90 : attnum = get_attnum(RelationGetRelid(rel), colName);
8166 [ + + ]: 90 : if (attnum == InvalidAttrNumber)
8167 [ + - + - ]: 5 : ereport(ERROR,
8168 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8169 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8170 : : colName, RelationGetRelationName(rel))));
8171 : :
8172 : : /* Prevent them from altering a system attribute */
8173 [ + - ]: 85 : if (attnum <= 0)
8174 [ # # # # ]: 0 : ereport(ERROR,
8175 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8176 : : errmsg("cannot alter system column \"%s\"",
8177 : : colName)));
8178 : :
8179 [ + + ]: 85 : if (TupleDescAttr(tupdesc, attnum - 1)->attidentity)
8180 [ + - + - : 3 : ereport(ERROR,
+ - ]
8181 : : (errcode(ERRCODE_SYNTAX_ERROR),
8182 : : errmsg("column \"%s\" of relation \"%s\" is an identity column",
8183 : : colName, RelationGetRelationName(rel)),
8184 : : /* translator: %s is an SQL ALTER command */
8185 : : newDefault ? 0 : errhint("Use %s instead.",
8186 : : "ALTER TABLE ... ALTER COLUMN ... DROP IDENTITY")));
8187 : :
8188 [ + + ]: 82 : if (TupleDescAttr(tupdesc, attnum - 1)->attgenerated)
8189 [ + - + - : 2 : ereport(ERROR,
- + + + ]
8190 : : (errcode(ERRCODE_SYNTAX_ERROR),
8191 : : errmsg("column \"%s\" of relation \"%s\" is a generated column",
8192 : : colName, RelationGetRelationName(rel)),
8193 : : newDefault ?
8194 : : /* translator: %s is an SQL ALTER command */
8195 : : errhint("Use %s instead.", "ALTER TABLE ... ALTER COLUMN ... SET EXPRESSION") :
8196 : : (TupleDescAttr(tupdesc, attnum - 1)->attgenerated == ATTRIBUTE_GENERATED_STORED ?
8197 : : errhint("Use %s instead.", "ALTER TABLE ... ALTER COLUMN ... DROP EXPRESSION") : 0)));
8198 : :
8199 : : /*
8200 : : * Remove any old default for the column. We use RESTRICT here for
8201 : : * safety, but at present we do not expect anything to depend on the
8202 : : * default.
8203 : : *
8204 : : * We treat removing the existing default as an internal operation when it
8205 : : * is preparatory to adding a new default, but as a user-initiated
8206 : : * operation when the user asked for a drop.
8207 : : */
8208 : 160 : RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false,
8209 : 80 : newDefault != NULL);
8210 : :
8211 [ + + ]: 80 : if (newDefault)
8212 : : {
8213 : : /* SET DEFAULT */
8214 : 52 : RawColumnDefault *rawEnt;
8215 : :
8216 : 52 : rawEnt = palloc_object(RawColumnDefault);
8217 : 52 : rawEnt->attnum = attnum;
8218 : 52 : rawEnt->raw_default = newDefault;
8219 : 52 : rawEnt->generated = '\0';
8220 : :
8221 : : /*
8222 : : * This function is intended for CREATE TABLE, so it processes a
8223 : : * _list_ of defaults, but we just do one.
8224 : : */
8225 : 52 : AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
8226 : : false, true, false, NULL);
8227 : 52 : }
8228 : :
8229 : 80 : ObjectAddressSubSet(address, RelationRelationId,
8230 : : RelationGetRelid(rel), attnum);
8231 : : return address;
8232 : 80 : }
8233 : :
8234 : : /*
8235 : : * Add a pre-cooked default expression.
8236 : : *
8237 : : * Return the address of the affected column.
8238 : : */
8239 : : static ObjectAddress
8240 : 13 : ATExecCookedColumnDefault(Relation rel, AttrNumber attnum,
8241 : : Node *newDefault)
8242 : : {
8243 : : ObjectAddress address;
8244 : :
8245 : : /* We assume no checking is required */
8246 : :
8247 : : /*
8248 : : * Remove any old default for the column. We use RESTRICT here for
8249 : : * safety, but at present we do not expect anything to depend on the
8250 : : * default. (In ordinary cases, there could not be a default in place
8251 : : * anyway, but it's possible when combining LIKE with inheritance.)
8252 : : */
8253 : 13 : RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false,
8254 : : true);
8255 : :
8256 : 13 : (void) StoreAttrDefault(rel, attnum, newDefault, true);
8257 : :
8258 : 13 : ObjectAddressSubSet(address, RelationRelationId,
8259 : : RelationGetRelid(rel), attnum);
8260 : 13 : return address;
8261 : : }
8262 : :
8263 : : /*
8264 : : * ALTER TABLE ALTER COLUMN ADD IDENTITY
8265 : : *
8266 : : * Return the address of the affected column.
8267 : : */
8268 : : static ObjectAddress
8269 : 20 : ATExecAddIdentity(Relation rel, const char *colName,
8270 : : Node *def, LOCKMODE lockmode, bool recurse, bool recursing)
8271 : : {
8272 : 20 : Relation attrelation;
8273 : 20 : HeapTuple tuple;
8274 : 20 : Form_pg_attribute attTup;
8275 : 20 : AttrNumber attnum;
8276 : : ObjectAddress address;
8277 : 20 : ColumnDef *cdef = castNode(ColumnDef, def);
8278 : 20 : bool ispartitioned;
8279 : :
8280 : 20 : ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
8281 [ + + + + ]: 20 : if (ispartitioned && !recurse)
8282 [ + - + - ]: 1 : ereport(ERROR,
8283 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8284 : : errmsg("cannot add identity to a column of only the partitioned table"),
8285 : : errhint("Do not specify the ONLY keyword.")));
8286 : :
8287 [ + + + + ]: 19 : if (rel->rd_rel->relispartition && !recursing)
8288 [ + - + - ]: 2 : ereport(ERROR,
8289 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8290 : : errmsg("cannot add identity to a column of a partition"));
8291 : :
8292 : 17 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
8293 : :
8294 : 17 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
8295 [ + - ]: 17 : if (!HeapTupleIsValid(tuple))
8296 [ # # # # ]: 0 : ereport(ERROR,
8297 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8298 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8299 : : colName, RelationGetRelationName(rel))));
8300 : 17 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8301 : 17 : attnum = attTup->attnum;
8302 : :
8303 : : /* Can't alter a system attribute */
8304 [ + - ]: 17 : if (attnum <= 0)
8305 [ # # # # ]: 0 : ereport(ERROR,
8306 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8307 : : errmsg("cannot alter system column \"%s\"",
8308 : : colName)));
8309 : :
8310 : : /*
8311 : : * Creating a column as identity implies NOT NULL, so adding the identity
8312 : : * to an existing column that is not NOT NULL would create a state that
8313 : : * cannot be reproduced without contortions.
8314 : : */
8315 [ + + ]: 17 : if (!attTup->attnotnull)
8316 [ + - + - ]: 1 : ereport(ERROR,
8317 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8318 : : errmsg("column \"%s\" of relation \"%s\" must be declared NOT NULL before identity can be added",
8319 : : colName, RelationGetRelationName(rel))));
8320 : :
8321 : : /*
8322 : : * On the other hand, if a not-null constraint exists, then verify that
8323 : : * it's compatible.
8324 : : */
8325 [ - + ]: 16 : if (attTup->attnotnull)
8326 : : {
8327 : 16 : HeapTuple contup;
8328 : 16 : Form_pg_constraint conForm;
8329 : :
8330 : 32 : contup = findNotNullConstraintAttnum(RelationGetRelid(rel),
8331 : 16 : attnum);
8332 [ + - ]: 16 : if (!HeapTupleIsValid(contup))
8333 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation \"%s\"",
8334 : : colName, RelationGetRelationName(rel));
8335 : :
8336 : 16 : conForm = (Form_pg_constraint) GETSTRUCT(contup);
8337 [ + + ]: 16 : if (!conForm->convalidated)
8338 [ + - + - ]: 1 : ereport(ERROR,
8339 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8340 : : errmsg("incompatible NOT VALID constraint \"%s\" on relation \"%s\"",
8341 : : NameStr(conForm->conname), RelationGetRelationName(rel)),
8342 : : errhint("You might need to validate it using %s.",
8343 : : "ALTER TABLE ... VALIDATE CONSTRAINT"));
8344 : 15 : }
8345 : :
8346 [ + + ]: 15 : if (attTup->attidentity)
8347 [ + - + - ]: 3 : ereport(ERROR,
8348 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8349 : : errmsg("column \"%s\" of relation \"%s\" is already an identity column",
8350 : : colName, RelationGetRelationName(rel))));
8351 : :
8352 [ + + ]: 12 : if (attTup->atthasdef)
8353 [ + - + - ]: 1 : ereport(ERROR,
8354 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8355 : : errmsg("column \"%s\" of relation \"%s\" already has a default value",
8356 : : colName, RelationGetRelationName(rel))));
8357 : :
8358 : 11 : attTup->attidentity = cdef->identity;
8359 : 11 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
8360 : :
8361 [ + - ]: 11 : InvokeObjectPostAlterHook(RelationRelationId,
8362 : : RelationGetRelid(rel),
8363 : : attTup->attnum);
8364 : 11 : ObjectAddressSubSet(address, RelationRelationId,
8365 : : RelationGetRelid(rel), attnum);
8366 : 11 : heap_freetuple(tuple);
8367 : :
8368 : 11 : table_close(attrelation, RowExclusiveLock);
8369 : :
8370 : : /*
8371 : : * Recurse to propagate the identity column to partitions. Identity is
8372 : : * not inherited in regular inheritance children.
8373 : : */
8374 [ + - + + ]: 11 : if (recurse && ispartitioned)
8375 : : {
8376 : 1 : List *children;
8377 : 1 : ListCell *lc;
8378 : :
8379 : 1 : children = find_inheritance_children(RelationGetRelid(rel), lockmode);
8380 : :
8381 [ + - + + : 2 : foreach(lc, children)
+ + ]
8382 : : {
8383 : 1 : Relation childrel;
8384 : :
8385 : 1 : childrel = table_open(lfirst_oid(lc), NoLock);
8386 : 1 : ATExecAddIdentity(childrel, colName, def, lockmode, recurse, true);
8387 : 1 : table_close(childrel, NoLock);
8388 : 1 : }
8389 : 1 : }
8390 : :
8391 : : return address;
8392 : 11 : }
8393 : :
8394 : : /*
8395 : : * ALTER TABLE ALTER COLUMN SET { GENERATED or sequence options }
8396 : : *
8397 : : * Return the address of the affected column.
8398 : : */
8399 : : static ObjectAddress
8400 : 12 : ATExecSetIdentity(Relation rel, const char *colName, Node *def,
8401 : : LOCKMODE lockmode, bool recurse, bool recursing)
8402 : : {
8403 : 12 : ListCell *option;
8404 : 12 : DefElem *generatedEl = NULL;
8405 : 12 : HeapTuple tuple;
8406 : 12 : Form_pg_attribute attTup;
8407 : 12 : AttrNumber attnum;
8408 : 12 : Relation attrelation;
8409 : : ObjectAddress address;
8410 : 12 : bool ispartitioned;
8411 : :
8412 : 12 : ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
8413 [ + + + + ]: 12 : if (ispartitioned && !recurse)
8414 [ + - + - ]: 1 : ereport(ERROR,
8415 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8416 : : errmsg("cannot change identity column of only the partitioned table"),
8417 : : errhint("Do not specify the ONLY keyword.")));
8418 : :
8419 [ + + + + ]: 11 : if (rel->rd_rel->relispartition && !recursing)
8420 [ + - + - ]: 2 : ereport(ERROR,
8421 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8422 : : errmsg("cannot change identity column of a partition"));
8423 : :
8424 [ + + + + : 16 : foreach(option, castNode(List, def))
+ + ]
8425 : : {
8426 : 7 : DefElem *defel = lfirst_node(DefElem, option);
8427 : :
8428 [ + - ]: 7 : if (strcmp(defel->defname, "generated") == 0)
8429 : : {
8430 [ + - ]: 7 : if (generatedEl)
8431 [ # # # # ]: 0 : ereport(ERROR,
8432 : : (errcode(ERRCODE_SYNTAX_ERROR),
8433 : : errmsg("conflicting or redundant options")));
8434 : 7 : generatedEl = defel;
8435 : 7 : }
8436 : : else
8437 [ # # # # ]: 0 : elog(ERROR, "option \"%s\" not recognized",
8438 : : defel->defname);
8439 : 7 : }
8440 : :
8441 : : /*
8442 : : * Even if there is nothing to change here, we run all the checks. There
8443 : : * will be a subsequent ALTER SEQUENCE that relies on everything being
8444 : : * there.
8445 : : */
8446 : :
8447 : 9 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
8448 : 9 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
8449 [ + - ]: 9 : if (!HeapTupleIsValid(tuple))
8450 [ # # # # ]: 0 : ereport(ERROR,
8451 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8452 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8453 : : colName, RelationGetRelationName(rel))));
8454 : :
8455 : 9 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8456 : 9 : attnum = attTup->attnum;
8457 : :
8458 [ + - ]: 9 : if (attnum <= 0)
8459 [ # # # # ]: 0 : ereport(ERROR,
8460 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8461 : : errmsg("cannot alter system column \"%s\"",
8462 : : colName)));
8463 : :
8464 [ + + ]: 9 : if (!attTup->attidentity)
8465 [ + - + - ]: 1 : ereport(ERROR,
8466 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8467 : : errmsg("column \"%s\" of relation \"%s\" is not an identity column",
8468 : : colName, RelationGetRelationName(rel))));
8469 : :
8470 [ + + ]: 8 : if (generatedEl)
8471 : : {
8472 : 7 : attTup->attidentity = defGetInt32(generatedEl);
8473 : 7 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
8474 : :
8475 [ + - ]: 7 : InvokeObjectPostAlterHook(RelationRelationId,
8476 : : RelationGetRelid(rel),
8477 : : attTup->attnum);
8478 : 7 : ObjectAddressSubSet(address, RelationRelationId,
8479 : : RelationGetRelid(rel), attnum);
8480 : 7 : }
8481 : : else
8482 : 1 : address = InvalidObjectAddress;
8483 : :
8484 : 8 : heap_freetuple(tuple);
8485 : 8 : table_close(attrelation, RowExclusiveLock);
8486 : :
8487 : : /*
8488 : : * Recurse to propagate the identity change to partitions. Identity is not
8489 : : * inherited in regular inheritance children.
8490 : : */
8491 [ + + + - : 8 : if (generatedEl && recurse && ispartitioned)
+ + ]
8492 : : {
8493 : 1 : List *children;
8494 : 1 : ListCell *lc;
8495 : :
8496 : 1 : children = find_inheritance_children(RelationGetRelid(rel), lockmode);
8497 : :
8498 [ + - + + : 3 : foreach(lc, children)
+ + ]
8499 : : {
8500 : 2 : Relation childrel;
8501 : :
8502 : 2 : childrel = table_open(lfirst_oid(lc), NoLock);
8503 : 2 : ATExecSetIdentity(childrel, colName, def, lockmode, recurse, true);
8504 : 2 : table_close(childrel, NoLock);
8505 : 2 : }
8506 : 1 : }
8507 : :
8508 : : return address;
8509 : 8 : }
8510 : :
8511 : : /*
8512 : : * ALTER TABLE ALTER COLUMN DROP IDENTITY
8513 : : *
8514 : : * Return the address of the affected column.
8515 : : */
8516 : : static ObjectAddress
8517 : 15 : ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode,
8518 : : bool recurse, bool recursing)
8519 : : {
8520 : 15 : HeapTuple tuple;
8521 : 15 : Form_pg_attribute attTup;
8522 : 15 : AttrNumber attnum;
8523 : 15 : Relation attrelation;
8524 : 15 : ObjectAddress address;
8525 : 15 : Oid seqid;
8526 : 15 : ObjectAddress seqaddress;
8527 : 15 : bool ispartitioned;
8528 : :
8529 : 15 : ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
8530 [ + + + + ]: 15 : if (ispartitioned && !recurse)
8531 [ + - + - ]: 1 : ereport(ERROR,
8532 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8533 : : errmsg("cannot drop identity from a column of only the partitioned table"),
8534 : : errhint("Do not specify the ONLY keyword.")));
8535 : :
8536 [ + + + + ]: 14 : if (rel->rd_rel->relispartition && !recursing)
8537 [ + - + - ]: 1 : ereport(ERROR,
8538 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8539 : : errmsg("cannot drop identity from a column of a partition"));
8540 : :
8541 : 13 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
8542 : 13 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
8543 [ + - ]: 13 : if (!HeapTupleIsValid(tuple))
8544 [ # # # # ]: 0 : ereport(ERROR,
8545 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8546 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8547 : : colName, RelationGetRelationName(rel))));
8548 : :
8549 : 13 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8550 : 13 : attnum = attTup->attnum;
8551 : :
8552 [ + - ]: 13 : if (attnum <= 0)
8553 [ # # # # ]: 0 : ereport(ERROR,
8554 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8555 : : errmsg("cannot alter system column \"%s\"",
8556 : : colName)));
8557 : :
8558 [ + + ]: 13 : if (!attTup->attidentity)
8559 : : {
8560 [ + + ]: 2 : if (!missing_ok)
8561 [ + - + - ]: 1 : ereport(ERROR,
8562 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8563 : : errmsg("column \"%s\" of relation \"%s\" is not an identity column",
8564 : : colName, RelationGetRelationName(rel))));
8565 : : else
8566 : : {
8567 [ - + + - ]: 1 : ereport(NOTICE,
8568 : : (errmsg("column \"%s\" of relation \"%s\" is not an identity column, skipping",
8569 : : colName, RelationGetRelationName(rel))));
8570 : 1 : heap_freetuple(tuple);
8571 : 1 : table_close(attrelation, RowExclusiveLock);
8572 : 1 : return InvalidObjectAddress;
8573 : : }
8574 : 0 : }
8575 : :
8576 : 11 : attTup->attidentity = '\0';
8577 : 11 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
8578 : :
8579 [ + - ]: 11 : InvokeObjectPostAlterHook(RelationRelationId,
8580 : : RelationGetRelid(rel),
8581 : : attTup->attnum);
8582 : 11 : ObjectAddressSubSet(address, RelationRelationId,
8583 : : RelationGetRelid(rel), attnum);
8584 : 11 : heap_freetuple(tuple);
8585 : :
8586 : 11 : table_close(attrelation, RowExclusiveLock);
8587 : :
8588 : : /*
8589 : : * Recurse to drop the identity from column in partitions. Identity is
8590 : : * not inherited in regular inheritance children so ignore them.
8591 : : */
8592 [ + - + + ]: 11 : if (recurse && ispartitioned)
8593 : : {
8594 : 1 : List *children;
8595 : 1 : ListCell *lc;
8596 : :
8597 : 1 : children = find_inheritance_children(RelationGetRelid(rel), lockmode);
8598 : :
8599 [ + - + + : 2 : foreach(lc, children)
+ + ]
8600 : : {
8601 : 1 : Relation childrel;
8602 : :
8603 : 1 : childrel = table_open(lfirst_oid(lc), NoLock);
8604 : 1 : ATExecDropIdentity(childrel, colName, false, lockmode, recurse, true);
8605 : 1 : table_close(childrel, NoLock);
8606 : 1 : }
8607 : 1 : }
8608 : :
8609 [ + + ]: 11 : if (!recursing)
8610 : : {
8611 : : /* drop the internal sequence */
8612 : 5 : seqid = getIdentitySequence(rel, attnum, false);
8613 : 5 : deleteDependencyRecordsForClass(RelationRelationId, seqid,
8614 : : RelationRelationId, DEPENDENCY_INTERNAL);
8615 : 5 : CommandCounterIncrement();
8616 : 5 : seqaddress.classId = RelationRelationId;
8617 : 5 : seqaddress.objectId = seqid;
8618 : 5 : seqaddress.objectSubId = 0;
8619 : 5 : performDeletion(&seqaddress, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
8620 : 5 : }
8621 : :
8622 : 11 : return address;
8623 : 12 : }
8624 : :
8625 : : /*
8626 : : * ALTER TABLE ALTER COLUMN SET EXPRESSION
8627 : : *
8628 : : * Return the address of the affected column.
8629 : : */
8630 : : static ObjectAddress
8631 : 37 : ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
8632 : : Node *newExpr, LOCKMODE lockmode)
8633 : : {
8634 : 37 : HeapTuple tuple;
8635 : 37 : Form_pg_attribute attTup;
8636 : 37 : AttrNumber attnum;
8637 : 37 : char attgenerated;
8638 : 37 : bool rewrite;
8639 : 37 : Oid attrdefoid;
8640 : : ObjectAddress address;
8641 : 37 : Expr *defval;
8642 : 37 : NewColumnValue *newval;
8643 : 37 : RawColumnDefault *rawEnt;
8644 : :
8645 : 37 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
8646 [ + - ]: 37 : if (!HeapTupleIsValid(tuple))
8647 [ # # # # ]: 0 : ereport(ERROR,
8648 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8649 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8650 : : colName, RelationGetRelationName(rel))));
8651 : :
8652 : 37 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8653 : :
8654 : 37 : attnum = attTup->attnum;
8655 [ + - ]: 37 : if (attnum <= 0)
8656 [ # # # # ]: 0 : ereport(ERROR,
8657 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8658 : : errmsg("cannot alter system column \"%s\"",
8659 : : colName)));
8660 : :
8661 : 37 : attgenerated = attTup->attgenerated;
8662 [ + + ]: 37 : if (!attgenerated)
8663 [ + - + - ]: 2 : ereport(ERROR,
8664 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8665 : : errmsg("column \"%s\" of relation \"%s\" is not a generated column",
8666 : : colName, RelationGetRelationName(rel))));
8667 : :
8668 : : /*
8669 : : * TODO: This could be done, just need to recheck any constraints
8670 : : * afterwards.
8671 : : */
8672 [ + + ]: 35 : if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL &&
8673 [ + - + + ]: 18 : rel->rd_att->constr && rel->rd_att->constr->num_check > 0)
8674 [ + - + - ]: 2 : ereport(ERROR,
8675 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8676 : : errmsg("ALTER TABLE / SET EXPRESSION is not supported for virtual generated columns in tables with check constraints"),
8677 : : errdetail("Column \"%s\" of relation \"%s\" is a virtual generated column.",
8678 : : colName, RelationGetRelationName(rel))));
8679 : :
8680 [ + + + + ]: 33 : if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL && attTup->attnotnull)
8681 : 4 : tab->verify_new_notnull = true;
8682 : :
8683 : : /*
8684 : : * We need to prevent this because a change of expression could affect a
8685 : : * row filter and inject expressions that are not permitted in a row
8686 : : * filter. XXX We could try to have a more precise check to catch only
8687 : : * publications with row filters, or even re-verify the row filter
8688 : : * expressions.
8689 : : */
8690 [ + + + + ]: 33 : if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL &&
8691 : 16 : GetRelationPublications(RelationGetRelid(rel)) != NIL)
8692 [ + - + - ]: 1 : ereport(ERROR,
8693 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8694 : : errmsg("ALTER TABLE / SET EXPRESSION is not supported for virtual generated columns in tables that are part of a publication"),
8695 : : errdetail("Column \"%s\" of relation \"%s\" is a virtual generated column.",
8696 : : colName, RelationGetRelationName(rel))));
8697 : :
8698 : 32 : rewrite = (attgenerated == ATTRIBUTE_GENERATED_STORED);
8699 : :
8700 : 32 : ReleaseSysCache(tuple);
8701 : :
8702 [ + + ]: 32 : if (rewrite)
8703 : : {
8704 : : /*
8705 : : * Clear all the missing values if we're rewriting the table, since
8706 : : * this renders them pointless.
8707 : : */
8708 : 17 : RelationClearMissing(rel);
8709 : :
8710 : : /* make sure we don't conflict with later attribute modifications */
8711 : 17 : CommandCounterIncrement();
8712 : :
8713 : : /*
8714 : : * Find everything that depends on the column (constraints, indexes,
8715 : : * etc), and record enough information to let us recreate the objects
8716 : : * after rewrite.
8717 : : */
8718 : 17 : RememberAllDependentForRebuilding(tab, AT_SetExpression, rel, attnum, colName);
8719 : 17 : }
8720 : :
8721 : : /*
8722 : : * Drop the dependency records of the GENERATED expression, in particular
8723 : : * its INTERNAL dependency on the column, which would otherwise cause
8724 : : * dependency.c to refuse to perform the deletion.
8725 : : */
8726 : 32 : attrdefoid = GetAttrDefaultOid(RelationGetRelid(rel), attnum);
8727 [ + - ]: 32 : if (!OidIsValid(attrdefoid))
8728 [ # # # # ]: 0 : elog(ERROR, "could not find attrdef tuple for relation %u attnum %d",
8729 : : RelationGetRelid(rel), attnum);
8730 : 32 : (void) deleteDependencyRecordsFor(AttrDefaultRelationId, attrdefoid, false);
8731 : :
8732 : : /* Make above changes visible */
8733 : 32 : CommandCounterIncrement();
8734 : :
8735 : : /*
8736 : : * Get rid of the GENERATED expression itself. We use RESTRICT here for
8737 : : * safety, but at present we do not expect anything to depend on the
8738 : : * expression.
8739 : : */
8740 : 32 : RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT,
8741 : : false, false);
8742 : :
8743 : : /* Prepare to store the new expression, in the catalogs */
8744 : 32 : rawEnt = palloc_object(RawColumnDefault);
8745 : 32 : rawEnt->attnum = attnum;
8746 : 32 : rawEnt->raw_default = newExpr;
8747 : 32 : rawEnt->generated = attgenerated;
8748 : :
8749 : : /* Store the generated expression */
8750 : 32 : AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
8751 : : false, true, false, NULL);
8752 : :
8753 : : /* Make above new expression visible */
8754 : 32 : CommandCounterIncrement();
8755 : :
8756 [ + + ]: 32 : if (rewrite)
8757 : : {
8758 : : /* Prepare for table rewrite */
8759 : 17 : defval = (Expr *) build_column_default(rel, attnum);
8760 : :
8761 : 17 : newval = palloc0_object(NewColumnValue);
8762 : 17 : newval->attnum = attnum;
8763 : 17 : newval->expr = expression_planner(defval);
8764 : 17 : newval->is_generated = true;
8765 : :
8766 : 17 : tab->newvals = lappend(tab->newvals, newval);
8767 : 17 : tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
8768 : 17 : }
8769 : :
8770 : : /* Drop any pg_statistic entry for the column */
8771 : 32 : RemoveStatistics(RelationGetRelid(rel), attnum);
8772 : :
8773 [ + - ]: 32 : InvokeObjectPostAlterHook(RelationRelationId,
8774 : : RelationGetRelid(rel), attnum);
8775 : :
8776 : 32 : ObjectAddressSubSet(address, RelationRelationId,
8777 : : RelationGetRelid(rel), attnum);
8778 : : return address;
8779 : 32 : }
8780 : :
8781 : : /*
8782 : : * ALTER TABLE ALTER COLUMN DROP EXPRESSION
8783 : : */
8784 : : static void
8785 : 14 : ATPrepDropExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, LOCKMODE lockmode)
8786 : : {
8787 : : /*
8788 : : * Reject ONLY if there are child tables. We could implement this, but it
8789 : : * is a bit complicated. GENERATED clauses must be attached to the column
8790 : : * definition and cannot be added later like DEFAULT, so if a child table
8791 : : * has a generation expression that the parent does not have, the child
8792 : : * column will necessarily be an attislocal column. So to implement ONLY
8793 : : * here, we'd need extra code to update attislocal of the direct child
8794 : : * tables, somewhat similar to how DROP COLUMN does it, so that the
8795 : : * resulting state can be properly dumped and restored.
8796 : : */
8797 [ + + + + ]: 14 : if (!recurse &&
8798 : 4 : find_inheritance_children(RelationGetRelid(rel), lockmode))
8799 [ + - + - ]: 2 : ereport(ERROR,
8800 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8801 : : errmsg("ALTER TABLE / DROP EXPRESSION must be applied to child tables too")));
8802 : :
8803 : : /*
8804 : : * Cannot drop generation expression from inherited columns.
8805 : : */
8806 [ + + ]: 12 : if (!recursing)
8807 : : {
8808 : 10 : HeapTuple tuple;
8809 : 10 : Form_pg_attribute attTup;
8810 : :
8811 : 10 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), cmd->name);
8812 [ + - ]: 10 : if (!HeapTupleIsValid(tuple))
8813 [ # # # # ]: 0 : ereport(ERROR,
8814 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8815 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8816 : : cmd->name, RelationGetRelationName(rel))));
8817 : :
8818 : 10 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8819 : :
8820 [ + + ]: 10 : if (attTup->attinhcount > 0)
8821 [ + - + - ]: 2 : ereport(ERROR,
8822 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8823 : : errmsg("cannot drop generation expression from inherited column")));
8824 : 8 : }
8825 : 10 : }
8826 : :
8827 : : /*
8828 : : * Return the address of the affected column.
8829 : : */
8830 : : static ObjectAddress
8831 : 9 : ATExecDropExpression(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode)
8832 : : {
8833 : 9 : HeapTuple tuple;
8834 : 9 : Form_pg_attribute attTup;
8835 : 9 : AttrNumber attnum;
8836 : 9 : Relation attrelation;
8837 : 9 : Oid attrdefoid;
8838 : 9 : ObjectAddress address;
8839 : :
8840 : 9 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
8841 : 9 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
8842 [ + - ]: 9 : if (!HeapTupleIsValid(tuple))
8843 [ # # # # ]: 0 : ereport(ERROR,
8844 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8845 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8846 : : colName, RelationGetRelationName(rel))));
8847 : :
8848 : 9 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8849 : 9 : attnum = attTup->attnum;
8850 : :
8851 [ + - ]: 9 : if (attnum <= 0)
8852 [ # # # # ]: 0 : ereport(ERROR,
8853 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8854 : : errmsg("cannot alter system column \"%s\"",
8855 : : colName)));
8856 : :
8857 : : /*
8858 : : * TODO: This could be done, but it would need a table rewrite to
8859 : : * materialize the generated values. Note that for the time being, we
8860 : : * still error with missing_ok, so that we don't silently leave the column
8861 : : * as generated.
8862 : : */
8863 [ + + ]: 9 : if (attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
8864 [ + - + - ]: 2 : ereport(ERROR,
8865 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8866 : : errmsg("ALTER TABLE / DROP EXPRESSION is not supported for virtual generated columns"),
8867 : : errdetail("Column \"%s\" of relation \"%s\" is a virtual generated column.",
8868 : : colName, RelationGetRelationName(rel))));
8869 : :
8870 [ + + ]: 7 : if (!attTup->attgenerated)
8871 : : {
8872 [ + + ]: 4 : if (!missing_ok)
8873 [ + - + - ]: 2 : ereport(ERROR,
8874 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8875 : : errmsg("column \"%s\" of relation \"%s\" is not a generated column",
8876 : : colName, RelationGetRelationName(rel))));
8877 : : else
8878 : : {
8879 [ - + + - ]: 2 : ereport(NOTICE,
8880 : : (errmsg("column \"%s\" of relation \"%s\" is not a generated column, skipping",
8881 : : colName, RelationGetRelationName(rel))));
8882 : 2 : heap_freetuple(tuple);
8883 : 2 : table_close(attrelation, RowExclusiveLock);
8884 : 2 : return InvalidObjectAddress;
8885 : : }
8886 : 0 : }
8887 : :
8888 : : /*
8889 : : * Mark the column as no longer generated. (The atthasdef flag needs to
8890 : : * get cleared too, but RemoveAttrDefault will handle that.)
8891 : : */
8892 : 3 : attTup->attgenerated = '\0';
8893 : 3 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
8894 : :
8895 [ + - ]: 3 : InvokeObjectPostAlterHook(RelationRelationId,
8896 : : RelationGetRelid(rel),
8897 : : attnum);
8898 : 3 : heap_freetuple(tuple);
8899 : :
8900 : 3 : table_close(attrelation, RowExclusiveLock);
8901 : :
8902 : : /*
8903 : : * Drop the dependency records of the GENERATED expression, in particular
8904 : : * its INTERNAL dependency on the column, which would otherwise cause
8905 : : * dependency.c to refuse to perform the deletion.
8906 : : */
8907 : 3 : attrdefoid = GetAttrDefaultOid(RelationGetRelid(rel), attnum);
8908 [ + - ]: 3 : if (!OidIsValid(attrdefoid))
8909 [ # # # # ]: 0 : elog(ERROR, "could not find attrdef tuple for relation %u attnum %d",
8910 : : RelationGetRelid(rel), attnum);
8911 : 3 : (void) deleteDependencyRecordsFor(AttrDefaultRelationId, attrdefoid, false);
8912 : :
8913 : : /* Make above changes visible */
8914 : 3 : CommandCounterIncrement();
8915 : :
8916 : : /*
8917 : : * Get rid of the GENERATED expression itself. We use RESTRICT here for
8918 : : * safety, but at present we do not expect anything to depend on the
8919 : : * default.
8920 : : */
8921 : 3 : RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT,
8922 : : false, false);
8923 : :
8924 : 3 : ObjectAddressSubSet(address, RelationRelationId,
8925 : : RelationGetRelid(rel), attnum);
8926 : 3 : return address;
8927 : 5 : }
8928 : :
8929 : : /*
8930 : : * ALTER TABLE ALTER COLUMN SET STATISTICS
8931 : : *
8932 : : * Return value is the address of the modified column
8933 : : */
8934 : : static ObjectAddress
8935 : 25 : ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newValue, LOCKMODE lockmode)
8936 : : {
8937 : 25 : int newtarget = 0;
8938 : 25 : bool newtarget_default;
8939 : 25 : Relation attrelation;
8940 : 25 : HeapTuple tuple,
8941 : : newtuple;
8942 : 25 : Form_pg_attribute attrtuple;
8943 : 25 : AttrNumber attnum;
8944 : : ObjectAddress address;
8945 : 25 : Datum repl_val[Natts_pg_attribute];
8946 : 25 : bool repl_null[Natts_pg_attribute];
8947 : 25 : bool repl_repl[Natts_pg_attribute];
8948 : :
8949 : : /*
8950 : : * We allow referencing columns by numbers only for indexes, since table
8951 : : * column numbers could contain gaps if columns are later dropped.
8952 : : */
8953 [ + + ]: 25 : if (rel->rd_rel->relkind != RELKIND_INDEX &&
8954 [ + - + - ]: 15 : rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
8955 : 15 : !colName)
8956 [ # # # # ]: 0 : ereport(ERROR,
8957 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8958 : : errmsg("cannot refer to non-index column by number")));
8959 : :
8960 : : /* -1 was used in previous versions for the default setting */
8961 [ + - + + ]: 25 : if (newValue && intVal(newValue) != -1)
8962 : : {
8963 : 18 : newtarget = intVal(newValue);
8964 : 18 : newtarget_default = false;
8965 : 18 : }
8966 : : else
8967 : 7 : newtarget_default = true;
8968 : :
8969 [ + + ]: 25 : if (!newtarget_default)
8970 : : {
8971 : : /*
8972 : : * Limit target to a sane range
8973 : : */
8974 [ + - ]: 18 : if (newtarget < 0)
8975 : : {
8976 [ # # # # ]: 0 : ereport(ERROR,
8977 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
8978 : : errmsg("statistics target %d is too low",
8979 : : newtarget)));
8980 : 0 : }
8981 [ + - ]: 18 : else if (newtarget > MAX_STATISTICS_TARGET)
8982 : : {
8983 : 0 : newtarget = MAX_STATISTICS_TARGET;
8984 [ # # # # ]: 0 : ereport(WARNING,
8985 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
8986 : : errmsg("lowering statistics target to %d",
8987 : : newtarget)));
8988 : 0 : }
8989 : 18 : }
8990 : :
8991 : 25 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
8992 : :
8993 [ + + ]: 25 : if (colName)
8994 : : {
8995 : 15 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
8996 : :
8997 [ + + ]: 15 : if (!HeapTupleIsValid(tuple))
8998 [ + - + - ]: 2 : ereport(ERROR,
8999 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
9000 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
9001 : : colName, RelationGetRelationName(rel))));
9002 : 13 : }
9003 : : else
9004 : : {
9005 : 10 : tuple = SearchSysCacheAttNum(RelationGetRelid(rel), colNum);
9006 : :
9007 [ + + ]: 10 : if (!HeapTupleIsValid(tuple))
9008 [ + - + - ]: 2 : ereport(ERROR,
9009 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
9010 : : errmsg("column number %d of relation \"%s\" does not exist",
9011 : : colNum, RelationGetRelationName(rel))));
9012 : : }
9013 : :
9014 : 21 : attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
9015 : :
9016 : 21 : attnum = attrtuple->attnum;
9017 [ + - ]: 21 : if (attnum <= 0)
9018 [ # # # # ]: 0 : ereport(ERROR,
9019 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9020 : : errmsg("cannot alter system column \"%s\"",
9021 : : colName)));
9022 : :
9023 : : /*
9024 : : * Prevent this as long as the ANALYZE code skips virtual generated
9025 : : * columns.
9026 : : */
9027 [ + - ]: 21 : if (attrtuple->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
9028 [ # # # # ]: 0 : ereport(ERROR,
9029 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9030 : : errmsg("cannot alter statistics on virtual generated column \"%s\"",
9031 : : colName)));
9032 : :
9033 [ + + - + ]: 21 : if (rel->rd_rel->relkind == RELKIND_INDEX ||
9034 : 13 : rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
9035 : : {
9036 [ + + ]: 8 : if (attnum > rel->rd_index->indnkeyatts)
9037 [ + - + - ]: 1 : ereport(ERROR,
9038 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9039 : : errmsg("cannot alter statistics on included column \"%s\" of index \"%s\"",
9040 : : NameStr(attrtuple->attname), RelationGetRelationName(rel))));
9041 [ + + ]: 7 : else if (rel->rd_index->indkey.values[attnum - 1] != 0)
9042 [ + - + - ]: 3 : ereport(ERROR,
9043 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9044 : : errmsg("cannot alter statistics on non-expression column \"%s\" of index \"%s\"",
9045 : : NameStr(attrtuple->attname), RelationGetRelationName(rel)),
9046 : : errhint("Alter statistics on table column instead.")));
9047 : 4 : }
9048 : :
9049 : : /* Build new tuple. */
9050 : 17 : memset(repl_null, false, sizeof(repl_null));
9051 : 17 : memset(repl_repl, false, sizeof(repl_repl));
9052 [ + + ]: 17 : if (!newtarget_default)
9053 : 10 : repl_val[Anum_pg_attribute_attstattarget - 1] = Int16GetDatum(newtarget);
9054 : : else
9055 : 7 : repl_null[Anum_pg_attribute_attstattarget - 1] = true;
9056 : 17 : repl_repl[Anum_pg_attribute_attstattarget - 1] = true;
9057 : 34 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(attrelation),
9058 : 17 : repl_val, repl_null, repl_repl);
9059 : 17 : CatalogTupleUpdate(attrelation, &tuple->t_self, newtuple);
9060 : :
9061 [ + - ]: 17 : InvokeObjectPostAlterHook(RelationRelationId,
9062 : : RelationGetRelid(rel),
9063 : : attrtuple->attnum);
9064 : 17 : ObjectAddressSubSet(address, RelationRelationId,
9065 : : RelationGetRelid(rel), attnum);
9066 : :
9067 : 17 : heap_freetuple(newtuple);
9068 : :
9069 : 17 : ReleaseSysCache(tuple);
9070 : :
9071 : 17 : table_close(attrelation, RowExclusiveLock);
9072 : :
9073 : : return address;
9074 : 17 : }
9075 : :
9076 : : /*
9077 : : * Return value is the address of the modified column
9078 : : */
9079 : : static ObjectAddress
9080 : 5 : ATExecSetOptions(Relation rel, const char *colName, Node *options,
9081 : : bool isReset, LOCKMODE lockmode)
9082 : : {
9083 : 5 : Relation attrelation;
9084 : 5 : HeapTuple tuple,
9085 : : newtuple;
9086 : 5 : Form_pg_attribute attrtuple;
9087 : 5 : AttrNumber attnum;
9088 : 5 : Datum datum,
9089 : : newOptions;
9090 : 5 : bool isnull;
9091 : : ObjectAddress address;
9092 : 5 : Datum repl_val[Natts_pg_attribute];
9093 : 5 : bool repl_null[Natts_pg_attribute];
9094 : 5 : bool repl_repl[Natts_pg_attribute];
9095 : :
9096 : 5 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
9097 : :
9098 : 5 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
9099 : :
9100 [ + - ]: 5 : if (!HeapTupleIsValid(tuple))
9101 [ # # # # ]: 0 : ereport(ERROR,
9102 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
9103 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
9104 : : colName, RelationGetRelationName(rel))));
9105 : 5 : attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
9106 : :
9107 : 5 : attnum = attrtuple->attnum;
9108 [ + - ]: 5 : if (attnum <= 0)
9109 [ # # # # ]: 0 : ereport(ERROR,
9110 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9111 : : errmsg("cannot alter system column \"%s\"",
9112 : : colName)));
9113 : :
9114 : : /* Generate new proposed attoptions (text array) */
9115 : 5 : datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions,
9116 : : &isnull);
9117 [ + + ]: 5 : newOptions = transformRelOptions(isnull ? (Datum) 0 : datum,
9118 : 5 : castNode(List, options), NULL, NULL,
9119 : 5 : false, isReset);
9120 : : /* Validate new options */
9121 : 5 : (void) attribute_reloptions(newOptions, true);
9122 : :
9123 : : /* Build new tuple. */
9124 : 5 : memset(repl_null, false, sizeof(repl_null));
9125 : 5 : memset(repl_repl, false, sizeof(repl_repl));
9126 [ + - ]: 5 : if (newOptions != (Datum) 0)
9127 : 5 : repl_val[Anum_pg_attribute_attoptions - 1] = newOptions;
9128 : : else
9129 : 0 : repl_null[Anum_pg_attribute_attoptions - 1] = true;
9130 : 5 : repl_repl[Anum_pg_attribute_attoptions - 1] = true;
9131 : 10 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(attrelation),
9132 : 5 : repl_val, repl_null, repl_repl);
9133 : :
9134 : : /* Update system catalog. */
9135 : 5 : CatalogTupleUpdate(attrelation, &newtuple->t_self, newtuple);
9136 : :
9137 [ + - ]: 5 : InvokeObjectPostAlterHook(RelationRelationId,
9138 : : RelationGetRelid(rel),
9139 : : attrtuple->attnum);
9140 : 5 : ObjectAddressSubSet(address, RelationRelationId,
9141 : : RelationGetRelid(rel), attnum);
9142 : :
9143 : 5 : heap_freetuple(newtuple);
9144 : :
9145 : 5 : ReleaseSysCache(tuple);
9146 : :
9147 : 5 : table_close(attrelation, RowExclusiveLock);
9148 : :
9149 : : return address;
9150 : 5 : }
9151 : :
9152 : : /*
9153 : : * Helper function for ATExecSetStorage and ATExecSetCompression
9154 : : *
9155 : : * Set the attstorage and/or attcompression fields for index columns
9156 : : * associated with the specified table column.
9157 : : */
9158 : : static void
9159 : 36 : SetIndexStorageProperties(Relation rel, Relation attrelation,
9160 : : AttrNumber attnum,
9161 : : bool setstorage, char newstorage,
9162 : : bool setcompression, char newcompression,
9163 : : LOCKMODE lockmode)
9164 : : {
9165 : 36 : ListCell *lc;
9166 : :
9167 [ + + + + : 47 : foreach(lc, RelationGetIndexList(rel))
+ + ]
9168 : : {
9169 : 11 : Oid indexoid = lfirst_oid(lc);
9170 : 11 : Relation indrel;
9171 : 11 : AttrNumber indattnum = 0;
9172 : 11 : HeapTuple tuple;
9173 : :
9174 : 11 : indrel = index_open(indexoid, lockmode);
9175 : :
9176 [ + + ]: 23 : for (int i = 0; i < indrel->rd_index->indnatts; i++)
9177 : : {
9178 [ + + ]: 12 : if (indrel->rd_index->indkey.values[i] == attnum)
9179 : : {
9180 : 5 : indattnum = i + 1;
9181 : 5 : break;
9182 : : }
9183 : 7 : }
9184 : :
9185 [ + + ]: 11 : if (indattnum == 0)
9186 : : {
9187 : 6 : index_close(indrel, lockmode);
9188 : 6 : continue;
9189 : : }
9190 : :
9191 : 5 : tuple = SearchSysCacheCopyAttNum(RelationGetRelid(indrel), indattnum);
9192 : :
9193 [ + - ]: 5 : if (HeapTupleIsValid(tuple))
9194 : : {
9195 : 5 : Form_pg_attribute attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
9196 : :
9197 [ + + ]: 5 : if (setstorage)
9198 : 3 : attrtuple->attstorage = newstorage;
9199 : :
9200 [ + + ]: 5 : if (setcompression)
9201 : 2 : attrtuple->attcompression = newcompression;
9202 : :
9203 : 5 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
9204 : :
9205 [ + - ]: 5 : InvokeObjectPostAlterHook(RelationRelationId,
9206 : : RelationGetRelid(rel),
9207 : : attrtuple->attnum);
9208 : :
9209 : 5 : heap_freetuple(tuple);
9210 : 5 : }
9211 : :
9212 : 5 : index_close(indrel, lockmode);
9213 [ + - + ]: 11 : }
9214 : 36 : }
9215 : :
9216 : : /*
9217 : : * ALTER TABLE ALTER COLUMN SET STORAGE
9218 : : *
9219 : : * Return value is the address of the modified column
9220 : : */
9221 : : static ObjectAddress
9222 : 31 : ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode)
9223 : : {
9224 : 31 : Relation attrelation;
9225 : 31 : HeapTuple tuple;
9226 : 31 : Form_pg_attribute attrtuple;
9227 : 31 : AttrNumber attnum;
9228 : : ObjectAddress address;
9229 : :
9230 : 31 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
9231 : :
9232 : 31 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
9233 : :
9234 [ + + ]: 31 : if (!HeapTupleIsValid(tuple))
9235 [ + - + - ]: 2 : ereport(ERROR,
9236 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
9237 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
9238 : : colName, RelationGetRelationName(rel))));
9239 : 29 : attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
9240 : :
9241 : 29 : attnum = attrtuple->attnum;
9242 [ + - ]: 29 : if (attnum <= 0)
9243 [ # # # # ]: 0 : ereport(ERROR,
9244 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9245 : : errmsg("cannot alter system column \"%s\"",
9246 : : colName)));
9247 : :
9248 : 29 : attrtuple->attstorage = GetAttributeStorage(attrtuple->atttypid, strVal(newValue));
9249 : :
9250 : 29 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
9251 : :
9252 [ + - ]: 29 : InvokeObjectPostAlterHook(RelationRelationId,
9253 : : RelationGetRelid(rel),
9254 : : attrtuple->attnum);
9255 : :
9256 : : /*
9257 : : * Apply the change to indexes as well (only for simple index columns,
9258 : : * matching behavior of index.c ConstructTupleDescriptor()).
9259 : : */
9260 : 58 : SetIndexStorageProperties(rel, attrelation, attnum,
9261 : 29 : true, attrtuple->attstorage,
9262 : : false, 0,
9263 : 29 : lockmode);
9264 : :
9265 : 29 : heap_freetuple(tuple);
9266 : :
9267 : 29 : table_close(attrelation, RowExclusiveLock);
9268 : :
9269 : 29 : ObjectAddressSubSet(address, RelationRelationId,
9270 : : RelationGetRelid(rel), attnum);
9271 : : return address;
9272 : 29 : }
9273 : :
9274 : :
9275 : : /*
9276 : : * ALTER TABLE DROP COLUMN
9277 : : *
9278 : : * DROP COLUMN cannot use the normal ALTER TABLE recursion mechanism,
9279 : : * because we have to decide at runtime whether to recurse or not depending
9280 : : * on whether attinhcount goes to zero or not. (We can't check this in a
9281 : : * static pre-pass because it won't handle multiple inheritance situations
9282 : : * correctly.)
9283 : : */
9284 : : static void
9285 : 237 : ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
9286 : : AlterTableCmd *cmd, LOCKMODE lockmode,
9287 : : AlterTableUtilityContext *context)
9288 : : {
9289 [ + + + + ]: 237 : if (rel->rd_rel->reloftype && !recursing)
9290 [ + - + - ]: 1 : ereport(ERROR,
9291 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
9292 : : errmsg("cannot drop column from typed table")));
9293 : :
9294 [ + + ]: 236 : if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
9295 : 12 : ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);
9296 : :
9297 [ + + ]: 236 : if (recurse)
9298 : 218 : cmd->recurse = true;
9299 : 236 : }
9300 : :
9301 : : /*
9302 : : * Drops column 'colName' from relation 'rel' and returns the address of the
9303 : : * dropped column. The column is also dropped (or marked as no longer
9304 : : * inherited from relation) from the relation's inheritance children, if any.
9305 : : *
9306 : : * In the recursive invocations for inheritance child relations, instead of
9307 : : * dropping the column directly (if to be dropped at all), its object address
9308 : : * is added to 'addrs', which must be non-NULL in such invocations. All
9309 : : * columns are dropped at the same time after all the children have been
9310 : : * checked recursively.
9311 : : */
9312 : : static ObjectAddress
9313 : 317 : ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
9314 : : DropBehavior behavior,
9315 : : bool recurse, bool recursing,
9316 : : bool missing_ok, LOCKMODE lockmode,
9317 : : ObjectAddresses *addrs)
9318 : : {
9319 : 317 : HeapTuple tuple;
9320 : 317 : Form_pg_attribute targetatt;
9321 : 317 : AttrNumber attnum;
9322 : 317 : List *children;
9323 : 317 : ObjectAddress object;
9324 : 317 : bool is_expr;
9325 : :
9326 : : /* At top level, permission check was done in ATPrepCmd, else do it */
9327 [ + + ]: 317 : if (recursing)
9328 : 92 : ATSimplePermissions(AT_DropColumn, rel,
9329 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
9330 : :
9331 : : /* Initialize addrs on the first invocation */
9332 [ + + + - ]: 317 : Assert(!recursing || addrs != NULL);
9333 : :
9334 : : /* since this function recurses, it could be driven to stack overflow */
9335 : 317 : check_stack_depth();
9336 : :
9337 [ + + ]: 317 : if (!recursing)
9338 : 235 : addrs = new_object_addresses();
9339 : :
9340 : : /*
9341 : : * get the number of the attribute
9342 : : */
9343 : 317 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
9344 [ + + ]: 317 : if (!HeapTupleIsValid(tuple))
9345 : : {
9346 [ + + ]: 9 : if (!missing_ok)
9347 : : {
9348 [ + - + - ]: 6 : ereport(ERROR,
9349 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
9350 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
9351 : : colName, RelationGetRelationName(rel))));
9352 : 0 : }
9353 : : else
9354 : : {
9355 [ - + + - ]: 3 : ereport(NOTICE,
9356 : : (errmsg("column \"%s\" of relation \"%s\" does not exist, skipping",
9357 : : colName, RelationGetRelationName(rel))));
9358 : 3 : return InvalidObjectAddress;
9359 : : }
9360 : 0 : }
9361 : 308 : targetatt = (Form_pg_attribute) GETSTRUCT(tuple);
9362 : :
9363 : 308 : attnum = targetatt->attnum;
9364 : :
9365 : : /* Can't drop a system attribute */
9366 [ + + ]: 308 : if (attnum <= 0)
9367 [ + - + - ]: 1 : ereport(ERROR,
9368 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9369 : : errmsg("cannot drop system column \"%s\"",
9370 : : colName)));
9371 : :
9372 : : /*
9373 : : * Don't drop inherited columns, unless recursing (presumably from a drop
9374 : : * of the parent column)
9375 : : */
9376 [ + + + + ]: 307 : if (targetatt->attinhcount > 0 && !recursing)
9377 [ + - + - ]: 8 : ereport(ERROR,
9378 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
9379 : : errmsg("cannot drop inherited column \"%s\"",
9380 : : colName)));
9381 : :
9382 : : /*
9383 : : * Don't drop columns used in the partition key, either. (If we let this
9384 : : * go through, the key column's dependencies would cause a cascaded drop
9385 : : * of the whole table, which is surely not what the user expected.)
9386 : : */
9387 [ + + + + ]: 598 : if (has_partition_attrs(rel,
9388 : 299 : bms_make_singleton(attnum - FirstLowInvalidHeapAttributeNumber),
9389 : : &is_expr))
9390 [ + - + - ]: 5 : ereport(ERROR,
9391 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
9392 : : errmsg("cannot drop column \"%s\" because it is part of the partition key of relation \"%s\"",
9393 : : colName, RelationGetRelationName(rel))));
9394 : :
9395 : 294 : ReleaseSysCache(tuple);
9396 : :
9397 : : /*
9398 : : * Propagate to children as appropriate. Unlike most other ALTER
9399 : : * routines, we have to do this one level of recursion at a time; we can't
9400 : : * use find_all_inheritors to do it in one pass.
9401 : : */
9402 : 294 : children =
9403 : 294 : find_inheritance_children(RelationGetRelid(rel), lockmode);
9404 : :
9405 [ + + ]: 294 : if (children)
9406 : : {
9407 : 49 : Relation attr_rel;
9408 : 49 : ListCell *child;
9409 : :
9410 : : /*
9411 : : * In case of a partitioned table, the column must be dropped from the
9412 : : * partitions as well.
9413 : : */
9414 [ + + + + ]: 49 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
9415 [ + - + - ]: 1 : ereport(ERROR,
9416 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
9417 : : errmsg("cannot drop column from only the partitioned table when partitions exist"),
9418 : : errhint("Do not specify the ONLY keyword.")));
9419 : :
9420 : 48 : attr_rel = table_open(AttributeRelationId, RowExclusiveLock);
9421 [ + - + + : 146 : foreach(child, children)
+ + ]
9422 : : {
9423 : 98 : Oid childrelid = lfirst_oid(child);
9424 : 98 : Relation childrel;
9425 : 98 : Form_pg_attribute childatt;
9426 : :
9427 : : /* find_inheritance_children already got lock */
9428 : 98 : childrel = table_open(childrelid, NoLock);
9429 : 98 : CheckAlterTableIsSafe(childrel);
9430 : :
9431 : 98 : tuple = SearchSysCacheCopyAttName(childrelid, colName);
9432 [ + - ]: 98 : if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
9433 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
9434 : : colName, childrelid);
9435 : 98 : childatt = (Form_pg_attribute) GETSTRUCT(tuple);
9436 : :
9437 [ + - ]: 98 : if (childatt->attinhcount <= 0) /* shouldn't happen */
9438 [ # # # # ]: 0 : elog(ERROR, "relation %u has non-inherited attribute \"%s\"",
9439 : : childrelid, colName);
9440 : :
9441 [ + + ]: 98 : if (recurse)
9442 : : {
9443 : : /*
9444 : : * If the child column has other definition sources, just
9445 : : * decrement its inheritance count; if not, recurse to delete
9446 : : * it.
9447 : : */
9448 [ + - + + ]: 94 : if (childatt->attinhcount == 1 && !childatt->attislocal)
9449 : : {
9450 : : /* Time to delete this child column, too */
9451 : 184 : ATExecDropColumn(wqueue, childrel, colName,
9452 : 92 : behavior, true, true,
9453 : 92 : false, lockmode, addrs);
9454 : 92 : }
9455 : : else
9456 : : {
9457 : : /* Child column must survive my deletion */
9458 : 2 : childatt->attinhcount--;
9459 : :
9460 : 2 : CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple);
9461 : :
9462 : : /* Make update visible */
9463 : 2 : CommandCounterIncrement();
9464 : : }
9465 : 94 : }
9466 : : else
9467 : : {
9468 : : /*
9469 : : * If we were told to drop ONLY in this table (no recursion),
9470 : : * we need to mark the inheritors' attributes as locally
9471 : : * defined rather than inherited.
9472 : : */
9473 : 4 : childatt->attinhcount--;
9474 : 4 : childatt->attislocal = true;
9475 : :
9476 : 4 : CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple);
9477 : :
9478 : : /* Make update visible */
9479 : 4 : CommandCounterIncrement();
9480 : : }
9481 : :
9482 : 98 : heap_freetuple(tuple);
9483 : :
9484 : 98 : table_close(childrel, NoLock);
9485 : 98 : }
9486 : 48 : table_close(attr_rel, RowExclusiveLock);
9487 : 48 : }
9488 : :
9489 : : /* Add object to delete */
9490 : 293 : object.classId = RelationRelationId;
9491 : 293 : object.objectId = RelationGetRelid(rel);
9492 : 293 : object.objectSubId = attnum;
9493 : 293 : add_exact_object_address(&object, addrs);
9494 : :
9495 [ + + ]: 293 : if (!recursing)
9496 : : {
9497 : : /* Recursion has ended, drop everything that was collected */
9498 : 211 : performMultipleDeletions(addrs, behavior, 0);
9499 : 211 : free_object_addresses(addrs);
9500 : 211 : }
9501 : :
9502 : 293 : return object;
9503 : 296 : }
9504 : :
9505 : : /*
9506 : : * Prepare to add a primary key on a table, by adding not-null constraints
9507 : : * on all columns.
9508 : : *
9509 : : * The not-null constraints for a primary key must cover the whole inheritance
9510 : : * hierarchy (failing to ensure that leads to funny corner cases). For the
9511 : : * normal case where we're asked to recurse, this routine checks if the
9512 : : * not-null constraints exist already, and if not queues a requirement for
9513 : : * them to be created by phase 2.
9514 : : *
9515 : : * For the case where we're asked not to recurse, we verify that a not-null
9516 : : * constraint exists on each column of each (direct) child table, throwing an
9517 : : * error if not. Not throwing an error would also work, because a not-null
9518 : : * constraint would be created anyway, but it'd cause a silent scan of the
9519 : : * child table to verify absence of nulls. We prefer to let the user know so
9520 : : * that they can add the constraint manually without having to hold
9521 : : * AccessExclusiveLock while at it.
9522 : : *
9523 : : * However, it's also important that we do not acquire locks on children if
9524 : : * the not-null constraints already exist on the parent, to avoid risking
9525 : : * deadlocks during parallel pg_restore of PKs on partitioned tables.
9526 : : */
9527 : : static void
9528 : 830 : ATPrepAddPrimaryKey(List **wqueue, Relation rel, AlterTableCmd *cmd,
9529 : : bool recurse, LOCKMODE lockmode,
9530 : : AlterTableUtilityContext *context)
9531 : : {
9532 : 830 : Constraint *pkconstr;
9533 : 830 : List *children = NIL;
9534 : 830 : bool got_children = false;
9535 : :
9536 : 830 : pkconstr = castNode(Constraint, cmd->def);
9537 [ + + ]: 830 : if (pkconstr->contype != CONSTR_PRIMARY)
9538 : 679 : return;
9539 : :
9540 : : /* Verify that columns are not-null, or request that they be made so */
9541 [ + + + + : 394 : foreach_node(String, column, pkconstr->keys)
+ + + + ]
9542 : : {
9543 : 102 : AlterTableCmd *newcmd;
9544 : 102 : Constraint *nnconstr;
9545 : 102 : HeapTuple tuple;
9546 : :
9547 : : /*
9548 : : * First check if a suitable constraint exists. If it does, we don't
9549 : : * need to request another one. We do need to bail out if it's not
9550 : : * valid, though.
9551 : : */
9552 : 102 : tuple = findNotNullConstraint(RelationGetRelid(rel), strVal(column));
9553 [ + + ]: 102 : if (tuple != NULL)
9554 : : {
9555 : 28 : verifyNotNullPKCompatible(tuple, strVal(column));
9556 : :
9557 : : /* All good with this one; don't request another */
9558 : 28 : heap_freetuple(tuple);
9559 : 28 : continue;
9560 : : }
9561 [ + + ]: 74 : else if (!recurse)
9562 : : {
9563 : : /*
9564 : : * No constraint on this column. Asked not to recurse, we won't
9565 : : * create one here, but verify that all children have one.
9566 : : */
9567 [ + + ]: 8 : if (!got_children)
9568 : : {
9569 : 12 : children = find_inheritance_children(RelationGetRelid(rel),
9570 : 6 : lockmode);
9571 : : /* only search for children on the first time through */
9572 : 6 : got_children = true;
9573 : 6 : }
9574 : :
9575 [ + + + + : 14 : foreach_oid(childrelid, children)
+ + + + ]
9576 : : {
9577 : 4 : HeapTuple tup;
9578 : :
9579 : 4 : tup = findNotNullConstraint(childrelid, strVal(column));
9580 [ + + ]: 4 : if (!tup)
9581 [ + - + - ]: 1 : ereport(ERROR,
9582 : : errmsg("column \"%s\" of table \"%s\" is not marked NOT NULL",
9583 : : strVal(column), get_rel_name(childrelid)));
9584 : : /* verify it's good enough */
9585 : 3 : verifyNotNullPKCompatible(tup, strVal(column));
9586 : 6 : }
9587 : 3 : }
9588 : :
9589 : : /* This column is not already not-null, so add it to the queue */
9590 : 69 : nnconstr = makeNotNullConstraint(column);
9591 : :
9592 : 69 : newcmd = makeNode(AlterTableCmd);
9593 : 69 : newcmd->subtype = AT_AddConstraint;
9594 : : /* note we force recurse=true here; see above */
9595 : 69 : newcmd->recurse = true;
9596 : 69 : newcmd->def = (Node *) nnconstr;
9597 : :
9598 : 69 : ATPrepCmd(wqueue, rel, newcmd, true, false, lockmode, context);
9599 [ + + ]: 243 : }
9600 : 825 : }
9601 : :
9602 : : /*
9603 : : * Verify whether the given not-null constraint is compatible with a
9604 : : * primary key. If not, an error is thrown.
9605 : : */
9606 : : static void
9607 : 33 : verifyNotNullPKCompatible(HeapTuple tuple, const char *colname)
9608 : : {
9609 : 33 : Form_pg_constraint conForm = (Form_pg_constraint) GETSTRUCT(tuple);
9610 : :
9611 [ + - ]: 33 : if (conForm->contype != CONSTRAINT_NOTNULL)
9612 [ # # # # ]: 0 : elog(ERROR, "constraint %u is not a not-null constraint", conForm->oid);
9613 : :
9614 : : /* a NO INHERIT constraint is no good */
9615 [ + + ]: 33 : if (conForm->connoinherit)
9616 [ + - + - ]: 2 : ereport(ERROR,
9617 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
9618 : : errmsg("cannot create primary key on column \"%s\"", colname),
9619 : : /*- translator: fourth %s is a constraint characteristic such as NOT VALID */
9620 : : errdetail("The constraint \"%s\" on column \"%s\" of table \"%s\", marked %s, is incompatible with a primary key.",
9621 : : NameStr(conForm->conname), colname,
9622 : : get_rel_name(conForm->conrelid), "NO INHERIT"),
9623 : : errhint("You might need to make the existing constraint inheritable using %s.",
9624 : : "ALTER TABLE ... ALTER CONSTRAINT ... INHERIT"));
9625 : :
9626 : : /* an unvalidated constraint is no good */
9627 [ + + ]: 31 : if (!conForm->convalidated)
9628 [ + - + - ]: 2 : ereport(ERROR,
9629 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
9630 : : errmsg("cannot create primary key on column \"%s\"", colname),
9631 : : /*- translator: fourth %s is a constraint characteristic such as NOT VALID */
9632 : : errdetail("The constraint \"%s\" on column \"%s\" of table \"%s\", marked %s, is incompatible with a primary key.",
9633 : : NameStr(conForm->conname), colname,
9634 : : get_rel_name(conForm->conrelid), "NOT VALID"),
9635 : : errhint("You might need to validate it using %s.",
9636 : : "ALTER TABLE ... VALIDATE CONSTRAINT"));
9637 : 29 : }
9638 : :
9639 : : /*
9640 : : * ALTER TABLE ADD INDEX
9641 : : *
9642 : : * There is no such command in the grammar, but parse_utilcmd.c converts
9643 : : * UNIQUE and PRIMARY KEY constraints into AT_AddIndex subcommands. This lets
9644 : : * us schedule creation of the index at the appropriate time during ALTER.
9645 : : *
9646 : : * Return value is the address of the new index.
9647 : : */
9648 : : static ObjectAddress
9649 : 202 : ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
9650 : : IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
9651 : : {
9652 : 202 : bool check_rights;
9653 : 202 : bool skip_build;
9654 : 202 : bool quiet;
9655 : : ObjectAddress address;
9656 : :
9657 [ + - ]: 202 : Assert(IsA(stmt, IndexStmt));
9658 [ + - ]: 202 : Assert(!stmt->concurrent);
9659 : :
9660 : : /* The IndexStmt has already been through transformIndexStmt */
9661 [ + - ]: 202 : Assert(stmt->transformed);
9662 : :
9663 : : /* suppress schema rights check when rebuilding existing index */
9664 : 202 : check_rights = !is_rebuild;
9665 : : /* skip index build if phase 3 will do it or we're reusing an old one */
9666 [ + + ]: 202 : skip_build = tab->rewrite > 0 || RelFileNumberIsValid(stmt->oldNumber);
9667 : : /* suppress notices when rebuilding existing index */
9668 : 202 : quiet = is_rebuild;
9669 : :
9670 : 404 : address = DefineIndex(NULL,
9671 : 202 : RelationGetRelid(rel),
9672 : 202 : stmt,
9673 : : InvalidOid, /* no predefined OID */
9674 : : InvalidOid, /* no parent index */
9675 : : InvalidOid, /* no parent constraint */
9676 : : -1, /* total_parts unknown */
9677 : : true, /* is_alter_table */
9678 : 202 : check_rights,
9679 : : false, /* check_not_in_use - we did it already */
9680 : 202 : skip_build,
9681 : 202 : quiet);
9682 : :
9683 : : /*
9684 : : * If TryReuseIndex() stashed a relfilenumber for us, we used it for the
9685 : : * new index instead of building from scratch. Restore associated fields.
9686 : : * This may store InvalidSubTransactionId in both fields, in which case
9687 : : * relcache.c will assume it can rebuild the relcache entry. Hence, do
9688 : : * this after the CCI that made catalog rows visible to any rebuild. The
9689 : : * DROP of the old edition of this index will have scheduled the storage
9690 : : * for deletion at commit, so cancel that pending deletion.
9691 : : */
9692 [ + + ]: 202 : if (RelFileNumberIsValid(stmt->oldNumber))
9693 : : {
9694 : 12 : Relation irel = index_open(address.objectId, NoLock);
9695 : :
9696 : 12 : irel->rd_createSubid = stmt->oldCreateSubid;
9697 : 12 : irel->rd_firstRelfilelocatorSubid = stmt->oldFirstRelfilelocatorSubid;
9698 : 12 : RelationPreserveStorage(irel->rd_locator, true);
9699 : 12 : index_close(irel, NoLock);
9700 : 12 : }
9701 : :
9702 : : return address;
9703 : 202 : }
9704 : :
9705 : : /*
9706 : : * ALTER TABLE ADD STATISTICS
9707 : : *
9708 : : * This is no such command in the grammar, but we use this internally to add
9709 : : * AT_ReAddStatistics subcommands to rebuild extended statistics after a table
9710 : : * column type change.
9711 : : */
9712 : : static ObjectAddress
9713 : 12 : ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
9714 : : CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
9715 : : {
9716 : : ObjectAddress address;
9717 : :
9718 [ + - ]: 12 : Assert(IsA(stmt, CreateStatsStmt));
9719 : :
9720 : : /* The CreateStatsStmt has already been through transformStatsStmt */
9721 [ + - ]: 12 : Assert(stmt->transformed);
9722 : :
9723 : 12 : address = CreateStatistics(stmt, !is_rebuild);
9724 : :
9725 : 12 : return address;
9726 : : }
9727 : :
9728 : : /*
9729 : : * ALTER TABLE ADD CONSTRAINT USING INDEX
9730 : : *
9731 : : * Returns the address of the new constraint.
9732 : : */
9733 : : static ObjectAddress
9734 : 122 : ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
9735 : : IndexStmt *stmt, LOCKMODE lockmode)
9736 : : {
9737 : 122 : Oid index_oid = stmt->indexOid;
9738 : 122 : Relation indexRel;
9739 : 122 : char *indexName;
9740 : 122 : IndexInfo *indexInfo;
9741 : 122 : char *constraintName;
9742 : 122 : char constraintType;
9743 : : ObjectAddress address;
9744 : 122 : bits16 flags;
9745 : :
9746 [ + - ]: 122 : Assert(IsA(stmt, IndexStmt));
9747 [ + - ]: 122 : Assert(OidIsValid(index_oid));
9748 [ + - ]: 122 : Assert(stmt->isconstraint);
9749 : :
9750 : : /*
9751 : : * Doing this on partitioned tables is not a simple feature to implement,
9752 : : * so let's punt for now.
9753 : : */
9754 [ + + ]: 122 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
9755 [ + - + - ]: 1 : ereport(ERROR,
9756 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9757 : : errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX is not supported on partitioned tables")));
9758 : :
9759 : 121 : indexRel = index_open(index_oid, AccessShareLock);
9760 : :
9761 : 121 : indexName = pstrdup(RelationGetRelationName(indexRel));
9762 : :
9763 : 121 : indexInfo = BuildIndexInfo(indexRel);
9764 : :
9765 : : /* this should have been checked at parse time */
9766 [ + - ]: 121 : if (!indexInfo->ii_Unique)
9767 [ # # # # ]: 0 : elog(ERROR, "index \"%s\" is not unique", indexName);
9768 : :
9769 : : /*
9770 : : * Determine name to assign to constraint. We require a constraint to
9771 : : * have the same name as the underlying index; therefore, use the index's
9772 : : * existing name as the default constraint name, and if the user
9773 : : * explicitly gives some other name for the constraint, rename the index
9774 : : * to match.
9775 : : */
9776 : 121 : constraintName = stmt->idxname;
9777 [ + + ]: 121 : if (constraintName == NULL)
9778 : 118 : constraintName = indexName;
9779 [ - + ]: 3 : else if (strcmp(constraintName, indexName) != 0)
9780 : : {
9781 [ - + + - ]: 3 : ereport(NOTICE,
9782 : : (errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index \"%s\" to \"%s\"",
9783 : : indexName, constraintName)));
9784 : 3 : RenameRelationInternal(index_oid, constraintName, false, true);
9785 : 3 : }
9786 : :
9787 : : /* Extra checks needed if making primary key */
9788 [ + + ]: 121 : if (stmt->primary)
9789 : 71 : index_check_primary_key(rel, indexInfo, true, stmt);
9790 : :
9791 : : /* Note we currently don't support EXCLUSION constraints here */
9792 [ + + ]: 121 : if (stmt->primary)
9793 : 70 : constraintType = CONSTRAINT_PRIMARY;
9794 : : else
9795 : 51 : constraintType = CONSTRAINT_UNIQUE;
9796 : :
9797 : : /* Create the catalog entries for the constraint */
9798 : 121 : flags = INDEX_CONSTR_CREATE_UPDATE_INDEX |
9799 : 121 : INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS |
9800 : 242 : (stmt->initdeferred ? INDEX_CONSTR_CREATE_INIT_DEFERRED : 0) |
9801 : 242 : (stmt->deferrable ? INDEX_CONSTR_CREATE_DEFERRABLE : 0) |
9802 : 121 : (stmt->primary ? INDEX_CONSTR_CREATE_MARK_AS_PRIMARY : 0);
9803 : :
9804 : 242 : address = index_constraint_create(rel,
9805 : 121 : index_oid,
9806 : : InvalidOid,
9807 : 121 : indexInfo,
9808 : 121 : constraintName,
9809 : 121 : constraintType,
9810 : 121 : flags,
9811 : 121 : allowSystemTableMods,
9812 : : false); /* is_internal */
9813 : :
9814 : 121 : index_close(indexRel, NoLock);
9815 : :
9816 : : return address;
9817 : 121 : }
9818 : :
9819 : : /*
9820 : : * ALTER TABLE ADD CONSTRAINT
9821 : : *
9822 : : * Return value is the address of the new constraint; if no constraint was
9823 : : * added, InvalidObjectAddress is returned.
9824 : : */
9825 : : static ObjectAddress
9826 : 777 : ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
9827 : : Constraint *newConstraint, bool recurse, bool is_readd,
9828 : : LOCKMODE lockmode)
9829 : : {
9830 : 777 : ObjectAddress address = InvalidObjectAddress;
9831 : :
9832 [ + - ]: 777 : Assert(IsA(newConstraint, Constraint));
9833 : :
9834 : : /*
9835 : : * Currently, we only expect to see CONSTR_CHECK, CONSTR_NOTNULL and
9836 : : * CONSTR_FOREIGN nodes arriving here (see the preprocessing done in
9837 : : * parse_utilcmd.c).
9838 : : */
9839 [ + + - ]: 777 : switch (newConstraint->contype)
9840 : : {
9841 : : case CONSTR_CHECK:
9842 : : case CONSTR_NOTNULL:
9843 : : address =
9844 : 900 : ATAddCheckNNConstraint(wqueue, tab, rel,
9845 : 450 : newConstraint, recurse, false, is_readd,
9846 : 450 : lockmode);
9847 : 450 : break;
9848 : :
9849 : : case CONSTR_FOREIGN:
9850 : :
9851 : : /*
9852 : : * Assign or validate constraint name
9853 : : */
9854 [ + + ]: 327 : if (newConstraint->conname)
9855 : : {
9856 [ + - ]: 130 : if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
9857 : 130 : RelationGetRelid(rel),
9858 : 130 : newConstraint->conname))
9859 [ # # # # ]: 0 : ereport(ERROR,
9860 : : (errcode(ERRCODE_DUPLICATE_OBJECT),
9861 : : errmsg("constraint \"%s\" for relation \"%s\" already exists",
9862 : : newConstraint->conname,
9863 : : RelationGetRelationName(rel))));
9864 : 130 : }
9865 : : else
9866 : 197 : newConstraint->conname =
9867 : 394 : ChooseConstraintName(RelationGetRelationName(rel),
9868 : 197 : ChooseForeignKeyConstraintNameAddition(newConstraint->fk_attrs),
9869 : : "fkey",
9870 : 197 : RelationGetNamespace(rel),
9871 : : NIL);
9872 : :
9873 : 654 : address = ATAddForeignKeyConstraint(wqueue, tab, rel,
9874 : 327 : newConstraint,
9875 : 327 : recurse, false,
9876 : 327 : lockmode);
9877 : 327 : break;
9878 : :
9879 : : default:
9880 [ # # # # ]: 0 : elog(ERROR, "unrecognized constraint type: %d",
9881 : : (int) newConstraint->contype);
9882 : 0 : }
9883 : :
9884 : 777 : return address;
9885 : : }
9886 : :
9887 : : /*
9888 : : * Generate the column-name portion of the constraint name for a new foreign
9889 : : * key given the list of column names that reference the referenced
9890 : : * table. This will be passed to ChooseConstraintName along with the parent
9891 : : * table name and the "fkey" suffix.
9892 : : *
9893 : : * We know that less than NAMEDATALEN characters will actually be used, so we
9894 : : * can truncate the result once we've generated that many.
9895 : : *
9896 : : * XXX see also ChooseExtendedStatisticNameAddition and
9897 : : * ChooseIndexNameAddition.
9898 : : */
9899 : : static char *
9900 : 197 : ChooseForeignKeyConstraintNameAddition(List *colnames)
9901 : : {
9902 : 197 : char buf[NAMEDATALEN * 2];
9903 : 197 : int buflen = 0;
9904 : 197 : ListCell *lc;
9905 : :
9906 : 197 : buf[0] = '\0';
9907 [ + - + + : 458 : foreach(lc, colnames)
+ + ]
9908 : : {
9909 : 261 : const char *name = strVal(lfirst(lc));
9910 : :
9911 [ + + ]: 261 : if (buflen > 0)
9912 : 64 : buf[buflen++] = '_'; /* insert _ between names */
9913 : :
9914 : : /*
9915 : : * At this point we have buflen <= NAMEDATALEN. name should be less
9916 : : * than NAMEDATALEN already, but use strlcpy for paranoia.
9917 : : */
9918 : 261 : strlcpy(buf + buflen, name, NAMEDATALEN);
9919 : 261 : buflen += strlen(buf + buflen);
9920 [ - + ]: 261 : if (buflen >= NAMEDATALEN)
9921 : 0 : break;
9922 [ - + ]: 261 : }
9923 : 394 : return pstrdup(buf);
9924 : 197 : }
9925 : :
9926 : : /*
9927 : : * Add a check or not-null constraint to a single table and its children.
9928 : : * Returns the address of the constraint added to the parent relation,
9929 : : * if one gets added, or InvalidObjectAddress otherwise.
9930 : : *
9931 : : * Subroutine for ATExecAddConstraint.
9932 : : *
9933 : : * We must recurse to child tables during execution, rather than using
9934 : : * ALTER TABLE's normal prep-time recursion. The reason is that all the
9935 : : * constraints *must* be given the same name, else they won't be seen as
9936 : : * related later. If the user didn't explicitly specify a name, then
9937 : : * AddRelationNewConstraints would normally assign different names to the
9938 : : * child constraints. To fix that, we must capture the name assigned at
9939 : : * the parent table and pass that down.
9940 : : */
9941 : : static ObjectAddress
9942 : 564 : ATAddCheckNNConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
9943 : : Constraint *constr, bool recurse, bool recursing,
9944 : : bool is_readd, LOCKMODE lockmode)
9945 : : {
9946 : 564 : List *newcons;
9947 : 564 : ListCell *lcon;
9948 : 564 : List *children;
9949 : 564 : ListCell *child;
9950 : 564 : ObjectAddress address = InvalidObjectAddress;
9951 : :
9952 : : /* Guard against stack overflow due to overly deep inheritance tree. */
9953 : 564 : check_stack_depth();
9954 : :
9955 : : /* At top level, permission check was done in ATPrepCmd, else do it */
9956 [ + + ]: 564 : if (recursing)
9957 : 142 : ATSimplePermissions(AT_AddConstraint, rel,
9958 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
9959 : :
9960 : : /*
9961 : : * Call AddRelationNewConstraints to do the work, making sure it works on
9962 : : * a copy of the Constraint so transformExpr can't modify the original. It
9963 : : * returns a list of cooked constraints.
9964 : : *
9965 : : * If the constraint ends up getting merged with a pre-existing one, it's
9966 : : * omitted from the returned list, which is what we want: we do not need
9967 : : * to do any validation work. That can only happen at child tables,
9968 : : * though, since we disallow merging at the top level.
9969 : : */
9970 : 1014 : newcons = AddRelationNewConstraints(rel, NIL,
9971 : 564 : list_make1(copyObject(constr)),
9972 [ + + ]: 564 : recursing || is_readd, /* allow_merge */
9973 : 564 : !recursing, /* is_local */
9974 : 564 : is_readd, /* is_internal */
9975 : : NULL); /* queryString not available
9976 : : * here */
9977 : :
9978 : : /* we don't expect more than one constraint here */
9979 [ + - ]: 564 : Assert(list_length(newcons) <= 1);
9980 : :
9981 : : /* Add each to-be-validated constraint to Phase 3's queue */
9982 [ + + + + : 1108 : foreach(lcon, newcons)
+ + ]
9983 : : {
9984 : 544 : CookedConstraint *ccon = (CookedConstraint *) lfirst(lcon);
9985 : :
9986 [ + + + + ]: 544 : if (!ccon->skip_validation && ccon->contype != CONSTR_NOTNULL)
9987 : : {
9988 : 160 : NewConstraint *newcon;
9989 : :
9990 : 160 : newcon = palloc0_object(NewConstraint);
9991 : 160 : newcon->name = ccon->name;
9992 : 160 : newcon->contype = ccon->contype;
9993 : 160 : newcon->qual = ccon->expr;
9994 : :
9995 : 160 : tab->constraints = lappend(tab->constraints, newcon);
9996 : 160 : }
9997 : :
9998 : : /* Save the actually assigned name if it was defaulted */
9999 [ + + ]: 544 : if (constr->conname == NULL)
10000 : 238 : constr->conname = ccon->name;
10001 : :
10002 : : /*
10003 : : * If adding a valid not-null constraint, set the pg_attribute flag
10004 : : * and tell phase 3 to verify existing rows, if needed. For an
10005 : : * invalid constraint, just set attnotnull, without queueing
10006 : : * verification.
10007 : : */
10008 [ + + ]: 544 : if (constr->contype == CONSTR_NOTNULL)
10009 : 612 : set_attnotnull(wqueue, rel, ccon->attnum,
10010 : 306 : !constr->skip_validation,
10011 : 306 : !constr->skip_validation);
10012 : :
10013 : 544 : ObjectAddressSet(address, ConstraintRelationId, ccon->conoid);
10014 : 544 : }
10015 : :
10016 : : /* At this point we must have a locked-down name to use */
10017 [ + + + - ]: 564 : Assert(newcons == NIL || constr->conname != NULL);
10018 : :
10019 : : /* Advance command counter in case same table is visited multiple times */
10020 : 564 : CommandCounterIncrement();
10021 : :
10022 : : /*
10023 : : * If the constraint got merged with an existing constraint, we're done.
10024 : : * We mustn't recurse to child tables in this case, because they've
10025 : : * already got the constraint, and visiting them again would lead to an
10026 : : * incorrect value for coninhcount.
10027 : : */
10028 [ + + ]: 564 : if (newcons == NIL)
10029 : 30 : return address;
10030 : :
10031 : : /*
10032 : : * If adding a NO INHERIT constraint, no need to find our children.
10033 : : */
10034 [ + + ]: 534 : if (constr->is_no_inherit)
10035 : 14 : return address;
10036 : :
10037 : : /*
10038 : : * Propagate to children as appropriate. Unlike most other ALTER
10039 : : * routines, we have to do this one level of recursion at a time; we can't
10040 : : * use find_all_inheritors to do it in one pass.
10041 : : */
10042 : 520 : children =
10043 : 520 : find_inheritance_children(RelationGetRelid(rel), lockmode);
10044 : :
10045 : : /*
10046 : : * Check if ONLY was specified with ALTER TABLE. If so, allow the
10047 : : * constraint creation only if there are no children currently. Error out
10048 : : * otherwise.
10049 : : */
10050 [ + + + + ]: 520 : if (!recurse && children != NIL)
10051 [ + - + - ]: 1 : ereport(ERROR,
10052 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
10053 : : errmsg("constraint must be added to child tables too")));
10054 : :
10055 : : /*
10056 : : * Recurse to create the constraint on each child.
10057 : : */
10058 [ + + + + : 661 : foreach(child, children)
+ + ]
10059 : : {
10060 : 142 : Oid childrelid = lfirst_oid(child);
10061 : 142 : Relation childrel;
10062 : 142 : AlteredTableInfo *childtab;
10063 : :
10064 : : /* find_inheritance_children already got lock */
10065 : 142 : childrel = table_open(childrelid, NoLock);
10066 : 142 : CheckAlterTableIsSafe(childrel);
10067 : :
10068 : : /* Find or create work queue entry for this table */
10069 : 142 : childtab = ATGetQueueEntry(wqueue, childrel);
10070 : :
10071 : : /* Recurse to this child */
10072 : 284 : ATAddCheckNNConstraint(wqueue, childtab, childrel,
10073 : 142 : constr, recurse, true, is_readd, lockmode);
10074 : :
10075 : 142 : table_close(childrel, NoLock);
10076 : 142 : }
10077 : :
10078 : 519 : return address;
10079 : 563 : }
10080 : :
10081 : : /*
10082 : : * Add a foreign-key constraint to a single table; return the new constraint's
10083 : : * address.
10084 : : *
10085 : : * Subroutine for ATExecAddConstraint. Must already hold exclusive
10086 : : * lock on the rel, and have done appropriate validity checks for it.
10087 : : * We do permissions checks here, however.
10088 : : *
10089 : : * When the referenced or referencing tables (or both) are partitioned,
10090 : : * multiple pg_constraint rows are required -- one for each partitioned table
10091 : : * and each partition on each side (fortunately, not one for every combination
10092 : : * thereof). We also need action triggers on each leaf partition on the
10093 : : * referenced side, and check triggers on each leaf partition on the
10094 : : * referencing side.
10095 : : */
10096 : : static ObjectAddress
10097 : 338 : ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
10098 : : Constraint *fkconstraint,
10099 : : bool recurse, bool recursing, LOCKMODE lockmode)
10100 : : {
10101 : 338 : Relation pkrel;
10102 : 338 : int16 pkattnum[INDEX_MAX_KEYS] = {0};
10103 : 338 : int16 fkattnum[INDEX_MAX_KEYS] = {0};
10104 : 338 : Oid pktypoid[INDEX_MAX_KEYS] = {0};
10105 : 338 : Oid fktypoid[INDEX_MAX_KEYS] = {0};
10106 : 338 : Oid pkcolloid[INDEX_MAX_KEYS] = {0};
10107 : 338 : Oid fkcolloid[INDEX_MAX_KEYS] = {0};
10108 : 338 : Oid opclasses[INDEX_MAX_KEYS] = {0};
10109 : 338 : Oid pfeqoperators[INDEX_MAX_KEYS] = {0};
10110 : 338 : Oid ppeqoperators[INDEX_MAX_KEYS] = {0};
10111 : 338 : Oid ffeqoperators[INDEX_MAX_KEYS] = {0};
10112 : 338 : int16 fkdelsetcols[INDEX_MAX_KEYS] = {0};
10113 : 338 : bool with_period;
10114 : 338 : bool pk_has_without_overlaps;
10115 : 338 : int i;
10116 : 338 : int numfks,
10117 : : numpks,
10118 : : numfkdelsetcols;
10119 : 338 : Oid indexOid;
10120 : 338 : bool old_check_ok;
10121 : : ObjectAddress address;
10122 : 338 : ListCell *old_pfeqop_item = list_head(fkconstraint->old_conpfeqop);
10123 : :
10124 : : /*
10125 : : * Grab ShareRowExclusiveLock on the pk table, so that someone doesn't
10126 : : * delete rows out from under us.
10127 : : */
10128 [ + + ]: 338 : if (OidIsValid(fkconstraint->old_pktable_oid))
10129 : 23 : pkrel = table_open(fkconstraint->old_pktable_oid, ShareRowExclusiveLock);
10130 : : else
10131 : 315 : pkrel = table_openrv(fkconstraint->pktable, ShareRowExclusiveLock);
10132 : :
10133 : : /*
10134 : : * Validity checks (permission checks wait till we have the column
10135 : : * numbers)
10136 : : */
10137 [ + + - + ]: 338 : if (!recurse && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
10138 [ + - + - ]: 1 : ereport(ERROR,
10139 : : errcode(ERRCODE_WRONG_OBJECT_TYPE),
10140 : : errmsg("cannot use ONLY for foreign key on partitioned table \"%s\" referencing relation \"%s\"",
10141 : : RelationGetRelationName(rel),
10142 : : RelationGetRelationName(pkrel)));
10143 : :
10144 [ + + + - ]: 337 : if (pkrel->rd_rel->relkind != RELKIND_RELATION &&
10145 : 42 : pkrel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
10146 [ # # # # ]: 0 : ereport(ERROR,
10147 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
10148 : : errmsg("referenced relation \"%s\" is not a table",
10149 : : RelationGetRelationName(pkrel))));
10150 : :
10151 [ + + + - ]: 337 : if (!allowSystemTableMods && IsSystemRelation(pkrel))
10152 [ # # # # ]: 0 : ereport(ERROR,
10153 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
10154 : : errmsg("permission denied: \"%s\" is a system catalog",
10155 : : RelationGetRelationName(pkrel))));
10156 : :
10157 : : /*
10158 : : * References from permanent or unlogged tables to temp tables, and from
10159 : : * permanent tables to unlogged tables, are disallowed because the
10160 : : * referenced data can vanish out from under us. References from temp
10161 : : * tables to any other table type are also disallowed, because other
10162 : : * backends might need to run the RI triggers on the perm table, but they
10163 : : * can't reliably see tuples in the local buffers of other backends.
10164 : : */
10165 [ + + + + ]: 337 : switch (rel->rd_rel->relpersistence)
10166 : : {
10167 : : case RELPERSISTENCE_PERMANENT:
10168 [ + - ]: 277 : if (!RelationIsPermanent(pkrel))
10169 [ # # # # ]: 0 : ereport(ERROR,
10170 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
10171 : : errmsg("constraints on permanent tables may reference only permanent tables")));
10172 : 277 : break;
10173 : : case RELPERSISTENCE_UNLOGGED:
10174 : 2 : if (!RelationIsPermanent(pkrel)
10175 [ + - + - ]: 2 : && pkrel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED)
10176 [ # # # # ]: 0 : ereport(ERROR,
10177 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
10178 : : errmsg("constraints on unlogged tables may reference only permanent or unlogged tables")));
10179 : 2 : break;
10180 : : case RELPERSISTENCE_TEMP:
10181 [ + - ]: 46 : if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
10182 [ # # # # ]: 0 : ereport(ERROR,
10183 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
10184 : : errmsg("constraints on temporary tables may reference only temporary tables")));
10185 [ + - ]: 46 : if (!pkrel->rd_islocaltemp || !rel->rd_islocaltemp)
10186 [ # # # # ]: 0 : ereport(ERROR,
10187 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
10188 : : errmsg("constraints on temporary tables must involve temporary tables of this session")));
10189 : 46 : break;
10190 : : }
10191 : :
10192 : : /*
10193 : : * Look up the referencing attributes to make sure they exist, and record
10194 : : * their attnums and type and collation OIDs.
10195 : : */
10196 : 674 : numfks = transformColumnNameList(RelationGetRelid(rel),
10197 : 337 : fkconstraint->fk_attrs,
10198 : 337 : fkattnum, fktypoid, fkcolloid);
10199 [ + + ]: 337 : with_period = fkconstraint->fk_with_period || fkconstraint->pk_with_period;
10200 [ + + + + ]: 313 : if (with_period && !fkconstraint->fk_with_period)
10201 [ + - + - ]: 4 : ereport(ERROR,
10202 : : errcode(ERRCODE_INVALID_FOREIGN_KEY),
10203 : : errmsg("foreign key uses PERIOD on the referenced table but not the referencing table"));
10204 : :
10205 : 618 : numfkdelsetcols = transformColumnNameList(RelationGetRelid(rel),
10206 : 309 : fkconstraint->fk_del_set_cols,
10207 : 309 : fkdelsetcols, NULL, NULL);
10208 : 618 : numfkdelsetcols = validateFkOnDeleteSetColumns(numfks, fkattnum,
10209 : 309 : numfkdelsetcols,
10210 : 309 : fkdelsetcols,
10211 : 309 : fkconstraint->fk_del_set_cols);
10212 : :
10213 : : /*
10214 : : * If the attribute list for the referenced table was omitted, lookup the
10215 : : * definition of the primary key and use it. Otherwise, validate the
10216 : : * supplied attribute list. In either case, discover the index OID and
10217 : : * index opclasses, and the attnums and type and collation OIDs of the
10218 : : * attributes.
10219 : : */
10220 [ + + ]: 309 : if (fkconstraint->pk_attrs == NIL)
10221 : : {
10222 : 338 : numpks = transformFkeyGetPrimaryKey(pkrel, &indexOid,
10223 : 169 : &fkconstraint->pk_attrs,
10224 : 169 : pkattnum, pktypoid, pkcolloid,
10225 : 169 : opclasses, &pk_has_without_overlaps);
10226 : :
10227 : : /* If the primary key uses WITHOUT OVERLAPS, the fk must use PERIOD */
10228 [ + + + + ]: 169 : if (pk_has_without_overlaps && !fkconstraint->fk_with_period)
10229 [ + - + - ]: 4 : ereport(ERROR,
10230 : : errcode(ERRCODE_INVALID_FOREIGN_KEY),
10231 : : errmsg("foreign key uses PERIOD on the referenced table but not the referencing table"));
10232 : 165 : }
10233 : : else
10234 : : {
10235 : 280 : numpks = transformColumnNameList(RelationGetRelid(pkrel),
10236 : 140 : fkconstraint->pk_attrs,
10237 : 140 : pkattnum, pktypoid, pkcolloid);
10238 : :
10239 : : /* Since we got pk_attrs, one should be a period. */
10240 [ + + + + ]: 140 : if (with_period && !fkconstraint->pk_with_period)
10241 [ + - + - ]: 4 : ereport(ERROR,
10242 : : errcode(ERRCODE_INVALID_FOREIGN_KEY),
10243 : : errmsg("foreign key uses PERIOD on the referencing table but not the referenced table"));
10244 : :
10245 : : /* Look for an index matching the column list */
10246 : 272 : indexOid = transformFkeyCheckAttrs(pkrel, numpks, pkattnum,
10247 : 136 : with_period, opclasses, &pk_has_without_overlaps);
10248 : : }
10249 : :
10250 : : /*
10251 : : * If the referenced primary key has WITHOUT OVERLAPS, the foreign key
10252 : : * must use PERIOD.
10253 : : */
10254 [ + + + + ]: 301 : if (pk_has_without_overlaps && !with_period)
10255 [ + - + - ]: 2 : ereport(ERROR,
10256 : : errcode(ERRCODE_INVALID_FOREIGN_KEY),
10257 : : errmsg("foreign key must use PERIOD when referencing a primary key using WITHOUT OVERLAPS"));
10258 : :
10259 : : /*
10260 : : * Now we can check permissions.
10261 : : */
10262 : 299 : checkFkeyPermissions(pkrel, pkattnum, numpks);
10263 : :
10264 : : /*
10265 : : * Check some things for generated columns.
10266 : : */
10267 [ + + ]: 725 : for (i = 0; i < numfks; i++)
10268 : : {
10269 : 431 : char attgenerated = TupleDescAttr(RelationGetDescr(rel), fkattnum[i] - 1)->attgenerated;
10270 : :
10271 [ + + ]: 431 : if (attgenerated)
10272 : : {
10273 : : /*
10274 : : * Check restrictions on UPDATE/DELETE actions, per SQL standard
10275 : : */
10276 [ + + ]: 8 : if (fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETNULL ||
10277 : 6 : fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETDEFAULT ||
10278 : 6 : fkconstraint->fk_upd_action == FKCONSTR_ACTION_CASCADE)
10279 [ + - + - ]: 2 : ereport(ERROR,
10280 : : (errcode(ERRCODE_SYNTAX_ERROR),
10281 : : errmsg("invalid %s action for foreign key constraint containing generated column",
10282 : : "ON UPDATE")));
10283 [ + + ]: 6 : if (fkconstraint->fk_del_action == FKCONSTR_ACTION_SETNULL ||
10284 : 4 : fkconstraint->fk_del_action == FKCONSTR_ACTION_SETDEFAULT)
10285 [ + - + - ]: 2 : ereport(ERROR,
10286 : : (errcode(ERRCODE_SYNTAX_ERROR),
10287 : : errmsg("invalid %s action for foreign key constraint containing generated column",
10288 : : "ON DELETE")));
10289 : 4 : }
10290 : :
10291 : : /*
10292 : : * FKs on virtual columns are not supported. This would require
10293 : : * various additional support in ri_triggers.c, including special
10294 : : * handling in ri_NullCheck(), ri_KeysEqual(),
10295 : : * RI_FKey_fk_upd_check_required() (since all virtual columns appear
10296 : : * as NULL there). Also not really practical as long as you can't
10297 : : * index virtual columns.
10298 : : */
10299 [ + + ]: 427 : if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
10300 [ + - + - ]: 1 : ereport(ERROR,
10301 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
10302 : : errmsg("foreign key constraints on virtual generated columns are not supported")));
10303 : 426 : }
10304 : :
10305 : : /*
10306 : : * Some actions are currently unsupported for foreign keys using PERIOD.
10307 : : */
10308 [ + + ]: 294 : if (fkconstraint->fk_with_period)
10309 : : {
10310 [ + + ]: 39 : if (fkconstraint->fk_upd_action == FKCONSTR_ACTION_RESTRICT ||
10311 : 28 : fkconstraint->fk_upd_action == FKCONSTR_ACTION_CASCADE ||
10312 : 28 : fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETNULL ||
10313 : 28 : fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETDEFAULT)
10314 [ + - + - ]: 11 : ereport(ERROR,
10315 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
10316 : : errmsg("unsupported %s action for foreign key constraint using PERIOD",
10317 : : "ON UPDATE"));
10318 : :
10319 [ + + ]: 28 : if (fkconstraint->fk_del_action == FKCONSTR_ACTION_RESTRICT ||
10320 : 27 : fkconstraint->fk_del_action == FKCONSTR_ACTION_CASCADE ||
10321 : 27 : fkconstraint->fk_del_action == FKCONSTR_ACTION_SETNULL ||
10322 : 27 : fkconstraint->fk_del_action == FKCONSTR_ACTION_SETDEFAULT)
10323 [ + - + - ]: 1 : ereport(ERROR,
10324 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
10325 : : errmsg("unsupported %s action for foreign key constraint using PERIOD",
10326 : : "ON DELETE"));
10327 : 27 : }
10328 : :
10329 : : /*
10330 : : * Look up the equality operators to use in the constraint.
10331 : : *
10332 : : * Note that we have to be careful about the difference between the actual
10333 : : * PK column type and the opclass' declared input type, which might be
10334 : : * only binary-compatible with it. The declared opcintype is the right
10335 : : * thing to probe pg_amop with.
10336 : : */
10337 [ + - ]: 282 : if (numfks != numpks)
10338 [ # # # # ]: 0 : ereport(ERROR,
10339 : : (errcode(ERRCODE_INVALID_FOREIGN_KEY),
10340 : : errmsg("number of referencing and referenced columns for foreign key disagree")));
10341 : :
10342 : : /*
10343 : : * On the strength of a previous constraint, we might avoid scanning
10344 : : * tables to validate this one. See below.
10345 : : */
10346 : 282 : old_check_ok = (fkconstraint->old_conpfeqop != NIL);
10347 [ + + + - ]: 282 : Assert(!old_check_ok || numfks == list_length(fkconstraint->old_conpfeqop));
10348 : :
10349 [ + + ]: 620 : for (i = 0; i < numpks; i++)
10350 : : {
10351 : 378 : Oid pktype = pktypoid[i];
10352 : 378 : Oid fktype = fktypoid[i];
10353 : 378 : Oid fktyped;
10354 : 378 : Oid pkcoll = pkcolloid[i];
10355 : 378 : Oid fkcoll = fkcolloid[i];
10356 : 378 : HeapTuple cla_ht;
10357 : 378 : Form_pg_opclass cla_tup;
10358 : 378 : Oid amid;
10359 : 378 : Oid opfamily;
10360 : 378 : Oid opcintype;
10361 : 378 : bool for_overlaps;
10362 : 378 : CompareType cmptype;
10363 : 378 : Oid pfeqop;
10364 : 378 : Oid ppeqop;
10365 : 378 : Oid ffeqop;
10366 : 378 : int16 eqstrategy;
10367 : 378 : Oid pfeqop_right;
10368 : :
10369 : : /* We need several fields out of the pg_opclass entry */
10370 : 378 : cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclasses[i]));
10371 [ + - ]: 378 : if (!HeapTupleIsValid(cla_ht))
10372 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for opclass %u", opclasses[i]);
10373 : 378 : cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
10374 : 378 : amid = cla_tup->opcmethod;
10375 : 378 : opfamily = cla_tup->opcfamily;
10376 : 378 : opcintype = cla_tup->opcintype;
10377 : 378 : ReleaseSysCache(cla_ht);
10378 : :
10379 : : /*
10380 : : * Get strategy number from index AM.
10381 : : *
10382 : : * For a normal foreign-key constraint, this should not fail, since we
10383 : : * already checked that the index is unique and should therefore have
10384 : : * appropriate equal operators. For a period foreign key, this could
10385 : : * fail if we selected a non-matching exclusion constraint earlier.
10386 : : * (XXX Maybe we should do these lookups earlier so we don't end up
10387 : : * doing that.)
10388 : : */
10389 [ + + ]: 378 : for_overlaps = with_period && i == numpks - 1;
10390 : 378 : cmptype = for_overlaps ? COMPARE_OVERLAP : COMPARE_EQ;
10391 : 378 : eqstrategy = IndexAmTranslateCompareType(cmptype, amid, opfamily, true);
10392 [ + - ]: 378 : if (eqstrategy == InvalidStrategy)
10393 [ # # # # : 0 : ereport(ERROR,
# # ]
10394 : : errcode(ERRCODE_UNDEFINED_OBJECT),
10395 : : for_overlaps
10396 : : ? errmsg("could not identify an overlaps operator for foreign key")
10397 : : : errmsg("could not identify an equality operator for foreign key"),
10398 : : errdetail("Could not translate compare type %d for operator family \"%s\" of access method \"%s\".",
10399 : : cmptype, get_opfamily_name(opfamily, false), get_am_name(amid)));
10400 : :
10401 : : /*
10402 : : * There had better be a primary equality operator for the index.
10403 : : * We'll use it for PK = PK comparisons.
10404 : : */
10405 : 756 : ppeqop = get_opfamily_member(opfamily, opcintype, opcintype,
10406 : 378 : eqstrategy);
10407 : :
10408 [ + - ]: 378 : if (!OidIsValid(ppeqop))
10409 [ # # # # ]: 0 : elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
10410 : : eqstrategy, opcintype, opcintype, opfamily);
10411 : :
10412 : : /*
10413 : : * Are there equality operators that take exactly the FK type? Assume
10414 : : * we should look through any domain here.
10415 : : */
10416 : 378 : fktyped = getBaseType(fktype);
10417 : :
10418 : 756 : pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
10419 : 378 : eqstrategy);
10420 [ + + ]: 378 : if (OidIsValid(pfeqop))
10421 : : {
10422 : 277 : pfeqop_right = fktyped;
10423 : 554 : ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
10424 : 277 : eqstrategy);
10425 : 277 : }
10426 : : else
10427 : : {
10428 : : /* keep compiler quiet */
10429 : 101 : pfeqop_right = InvalidOid;
10430 : 101 : ffeqop = InvalidOid;
10431 : : }
10432 : :
10433 [ + + + - ]: 378 : if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
10434 : : {
10435 : : /*
10436 : : * Otherwise, look for an implicit cast from the FK type to the
10437 : : * opcintype, and if found, use the primary equality operator.
10438 : : * This is a bit tricky because opcintype might be a polymorphic
10439 : : * type such as ANYARRAY or ANYENUM; so what we have to test is
10440 : : * whether the two actual column types can be concurrently cast to
10441 : : * that type. (Otherwise, we'd fail to reject combinations such
10442 : : * as int[] and point[].)
10443 : : */
10444 : 101 : Oid input_typeids[2];
10445 : 101 : Oid target_typeids[2];
10446 : :
10447 : 101 : input_typeids[0] = pktype;
10448 : 101 : input_typeids[1] = fktype;
10449 : 101 : target_typeids[0] = opcintype;
10450 : 101 : target_typeids[1] = opcintype;
10451 [ + + ]: 101 : if (can_coerce_type(2, input_typeids, target_typeids,
10452 : : COERCION_IMPLICIT))
10453 : : {
10454 : 63 : pfeqop = ffeqop = ppeqop;
10455 : 63 : pfeqop_right = opcintype;
10456 : 63 : }
10457 : 101 : }
10458 : :
10459 [ + + ]: 378 : if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
10460 [ + - + - ]: 38 : ereport(ERROR,
10461 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
10462 : : errmsg("foreign key constraint \"%s\" cannot be implemented",
10463 : : fkconstraint->conname),
10464 : : errdetail("Key columns \"%s\" of the referencing table and \"%s\" of the referenced table "
10465 : : "are of incompatible types: %s and %s.",
10466 : : strVal(list_nth(fkconstraint->fk_attrs, i)),
10467 : : strVal(list_nth(fkconstraint->pk_attrs, i)),
10468 : : format_type_be(fktype),
10469 : : format_type_be(pktype))));
10470 : :
10471 : : /*
10472 : : * This shouldn't be possible, but better check to make sure we have a
10473 : : * consistent state for the check below.
10474 : : */
10475 [ + + + - : 340 : if ((OidIsValid(pkcoll) && !OidIsValid(fkcoll)) || (!OidIsValid(pkcoll) && OidIsValid(fkcoll)))
+ + ]
10476 [ # # # # ]: 0 : elog(ERROR, "key columns are not both collatable");
10477 : :
10478 [ + + - + ]: 340 : if (OidIsValid(pkcoll) && OidIsValid(fkcoll))
10479 : : {
10480 : 17 : bool pkcolldet;
10481 : 17 : bool fkcolldet;
10482 : :
10483 : 17 : pkcolldet = get_collation_isdeterministic(pkcoll);
10484 : 17 : fkcolldet = get_collation_isdeterministic(fkcoll);
10485 : :
10486 : : /*
10487 : : * SQL requires that both collations are the same. This is
10488 : : * because we need a consistent notion of equality on both
10489 : : * columns. We relax this by allowing different collations if
10490 : : * they are both deterministic. (This is also for backward
10491 : : * compatibility, because PostgreSQL has always allowed this.)
10492 : : */
10493 [ + + + + ]: 17 : if ((!pkcolldet || !fkcolldet) && pkcoll != fkcoll)
10494 [ + - + - ]: 2 : ereport(ERROR,
10495 : : (errcode(ERRCODE_COLLATION_MISMATCH),
10496 : : errmsg("foreign key constraint \"%s\" cannot be implemented", fkconstraint->conname),
10497 : : errdetail("Key columns \"%s\" of the referencing table and \"%s\" of the referenced table "
10498 : : "have incompatible collations: \"%s\" and \"%s\". "
10499 : : "If either collation is nondeterministic, then both collations have to be the same.",
10500 : : strVal(list_nth(fkconstraint->fk_attrs, i)),
10501 : : strVal(list_nth(fkconstraint->pk_attrs, i)),
10502 : : get_collation_name(fkcoll),
10503 : : get_collation_name(pkcoll))));
10504 : 15 : }
10505 : :
10506 [ + + ]: 338 : if (old_check_ok)
10507 : : {
10508 : : /*
10509 : : * When a pfeqop changes, revalidate the constraint. We could
10510 : : * permit intra-opfamily changes, but that adds subtle complexity
10511 : : * without any concrete benefit for core types. We need not
10512 : : * assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
10513 : : */
10514 : 1 : old_check_ok = (pfeqop == lfirst_oid(old_pfeqop_item));
10515 : 2 : old_pfeqop_item = lnext(fkconstraint->old_conpfeqop,
10516 : 1 : old_pfeqop_item);
10517 : 1 : }
10518 [ + + ]: 338 : if (old_check_ok)
10519 : : {
10520 : 1 : Oid old_fktype;
10521 : 1 : Oid new_fktype;
10522 : 1 : CoercionPathType old_pathtype;
10523 : 1 : CoercionPathType new_pathtype;
10524 : 1 : Oid old_castfunc;
10525 : 1 : Oid new_castfunc;
10526 : 1 : Oid old_fkcoll;
10527 : 1 : Oid new_fkcoll;
10528 : 2 : Form_pg_attribute attr = TupleDescAttr(tab->oldDesc,
10529 : 1 : fkattnum[i] - 1);
10530 : :
10531 : : /*
10532 : : * Identify coercion pathways from each of the old and new FK-side
10533 : : * column types to the right (foreign) operand type of the pfeqop.
10534 : : * We may assume that pg_constraint.conkey is not changing.
10535 : : */
10536 : 1 : old_fktype = attr->atttypid;
10537 : 1 : new_fktype = fktype;
10538 : 1 : old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
10539 : : &old_castfunc);
10540 : 1 : new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
10541 : : &new_castfunc);
10542 : :
10543 : 1 : old_fkcoll = attr->attcollation;
10544 : 1 : new_fkcoll = fkcoll;
10545 : :
10546 : : /*
10547 : : * Upon a change to the cast from the FK column to its pfeqop
10548 : : * operand, revalidate the constraint. For this evaluation, a
10549 : : * binary coercion cast is equivalent to no cast at all. While
10550 : : * type implementors should design implicit casts with an eye
10551 : : * toward consistency of operations like equality, we cannot
10552 : : * assume here that they have done so.
10553 : : *
10554 : : * A function with a polymorphic argument could change behavior
10555 : : * arbitrarily in response to get_fn_expr_argtype(). Therefore,
10556 : : * when the cast destination is polymorphic, we only avoid
10557 : : * revalidation if the input type has not changed at all. Given
10558 : : * just the core data types and operator classes, this requirement
10559 : : * prevents no would-be optimizations.
10560 : : *
10561 : : * If the cast converts from a base type to a domain thereon, then
10562 : : * that domain type must be the opcintype of the unique index.
10563 : : * Necessarily, the primary key column must then be of the domain
10564 : : * type. Since the constraint was previously valid, all values on
10565 : : * the foreign side necessarily exist on the primary side and in
10566 : : * turn conform to the domain. Consequently, we need not treat
10567 : : * domains specially here.
10568 : : *
10569 : : * If the collation changes, revalidation is required, unless both
10570 : : * collations are deterministic, because those share the same
10571 : : * notion of equality (because texteq reduces to bitwise
10572 : : * equality).
10573 : : *
10574 : : * We need not directly consider the PK type. It's necessarily
10575 : : * binary coercible to the opcintype of the unique index column,
10576 : : * and ri_triggers.c will only deal with PK datums in terms of
10577 : : * that opcintype. Changing the opcintype also changes pfeqop.
10578 : : */
10579 [ + - ]: 2 : old_check_ok = (new_pathtype == old_pathtype &&
10580 [ + - ]: 1 : new_castfunc == old_castfunc &&
10581 [ + - + - : 1 : (!IsPolymorphicType(pfeqop_right) ||
+ - + - +
- + - + -
+ - + - +
- ]
10582 [ + - ]: 1 : new_fktype == old_fktype) &&
10583 [ + - ]: 1 : (new_fkcoll == old_fkcoll ||
10584 [ # # ]: 0 : (get_collation_isdeterministic(old_fkcoll) && get_collation_isdeterministic(new_fkcoll))));
10585 : 1 : }
10586 : :
10587 : 338 : pfeqoperators[i] = pfeqop;
10588 : 338 : ppeqoperators[i] = ppeqop;
10589 : 338 : ffeqoperators[i] = ffeqop;
10590 : 338 : }
10591 : :
10592 : : /*
10593 : : * For FKs with PERIOD we need additional operators to check whether the
10594 : : * referencing row's range is contained by the aggregated ranges of the
10595 : : * referenced row(s). For rangetypes and multirangetypes this is
10596 : : * fk.periodatt <@ range_agg(pk.periodatt). Those are the only types we
10597 : : * support for now. FKs will look these up at "runtime", but we should
10598 : : * make sure the lookup works here, even if we don't use the values.
10599 : : */
10600 [ + + ]: 242 : if (with_period)
10601 : : {
10602 : 24 : Oid periodoperoid;
10603 : 24 : Oid aggedperiodoperoid;
10604 : 24 : Oid intersectoperoid;
10605 : :
10606 : 24 : FindFKPeriodOpers(opclasses[numpks - 1], &periodoperoid, &aggedperiodoperoid,
10607 : : &intersectoperoid);
10608 : 24 : }
10609 : :
10610 : : /* First, create the constraint catalog entry itself. */
10611 : 484 : address = addFkConstraint(addFkBothSides,
10612 : 242 : fkconstraint->conname, fkconstraint, rel, pkrel,
10613 : 242 : indexOid,
10614 : : InvalidOid, /* no parent constraint */
10615 : 242 : numfks,
10616 : 242 : pkattnum,
10617 : 242 : fkattnum,
10618 : 242 : pfeqoperators,
10619 : 242 : ppeqoperators,
10620 : 242 : ffeqoperators,
10621 : 242 : numfkdelsetcols,
10622 : 242 : fkdelsetcols,
10623 : : false,
10624 : 242 : with_period);
10625 : :
10626 : : /* Next process the action triggers at the referenced side and recurse */
10627 : 484 : addFkRecurseReferenced(fkconstraint, rel, pkrel,
10628 : 242 : indexOid,
10629 : 242 : address.objectId,
10630 : 242 : numfks,
10631 : 242 : pkattnum,
10632 : 242 : fkattnum,
10633 : 242 : pfeqoperators,
10634 : 242 : ppeqoperators,
10635 : 242 : ffeqoperators,
10636 : 242 : numfkdelsetcols,
10637 : 242 : fkdelsetcols,
10638 : 242 : old_check_ok,
10639 : : InvalidOid, InvalidOid,
10640 : 242 : with_period);
10641 : :
10642 : : /* Lastly create the check triggers at the referencing side and recurse */
10643 : 484 : addFkRecurseReferencing(wqueue, fkconstraint, rel, pkrel,
10644 : 242 : indexOid,
10645 : 242 : address.objectId,
10646 : 242 : numfks,
10647 : 242 : pkattnum,
10648 : 242 : fkattnum,
10649 : 242 : pfeqoperators,
10650 : 242 : ppeqoperators,
10651 : 242 : ffeqoperators,
10652 : 242 : numfkdelsetcols,
10653 : 242 : fkdelsetcols,
10654 : 242 : old_check_ok,
10655 : 242 : lockmode,
10656 : : InvalidOid, InvalidOid,
10657 : 242 : with_period);
10658 : :
10659 : : /*
10660 : : * Done. Close pk table, but keep lock until we've committed.
10661 : : */
10662 : 242 : table_close(pkrel, NoLock);
10663 : :
10664 : : return address;
10665 : 242 : }
10666 : :
10667 : : /*
10668 : : * validateFkOnDeleteSetColumns
10669 : : * Verifies that columns used in ON DELETE SET NULL/DEFAULT (...)
10670 : : * column lists are valid.
10671 : : *
10672 : : * If there are duplicates in the fksetcolsattnums[] array, this silently
10673 : : * removes the dups. The new count of numfksetcols is returned.
10674 : : */
10675 : : static int
10676 : 315 : validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
10677 : : int numfksetcols, int16 *fksetcolsattnums,
10678 : : List *fksetcols)
10679 : : {
10680 : 315 : int numcolsout = 0;
10681 : :
10682 [ + + ]: 320 : for (int i = 0; i < numfksetcols; i++)
10683 : : {
10684 : 6 : int16 setcol_attnum = fksetcolsattnums[i];
10685 : 6 : bool seen = false;
10686 : :
10687 : : /* Make sure it's in fkattnums[] */
10688 [ + + ]: 16 : for (int j = 0; j < numfks; j++)
10689 : : {
10690 [ + + ]: 10 : if (fkattnums[j] == setcol_attnum)
10691 : : {
10692 : 5 : seen = true;
10693 : 5 : break;
10694 : : }
10695 : 5 : }
10696 : :
10697 [ + + ]: 6 : if (!seen)
10698 : : {
10699 : 1 : char *col = strVal(list_nth(fksetcols, i));
10700 : :
10701 [ + - + - ]: 1 : ereport(ERROR,
10702 : : (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
10703 : : errmsg("column \"%s\" referenced in ON DELETE SET action must be part of foreign key", col)));
10704 : 0 : }
10705 : :
10706 : : /* Now check for dups */
10707 : 5 : seen = false;
10708 [ + + ]: 6 : for (int j = 0; j < numcolsout; j++)
10709 : : {
10710 [ + - ]: 1 : if (fksetcolsattnums[j] == setcol_attnum)
10711 : : {
10712 : 1 : seen = true;
10713 : 1 : break;
10714 : : }
10715 : 0 : }
10716 [ + + ]: 5 : if (!seen)
10717 : 4 : fksetcolsattnums[numcolsout++] = setcol_attnum;
10718 : 5 : }
10719 : 628 : return numcolsout;
10720 : 314 : }
10721 : :
10722 : : /*
10723 : : * addFkConstraint
10724 : : * Install pg_constraint entries to implement a foreign key constraint.
10725 : : * Caller must separately invoke addFkRecurseReferenced and
10726 : : * addFkRecurseReferencing, as appropriate, to install pg_trigger entries
10727 : : * and (for partitioned tables) recurse to partitions.
10728 : : *
10729 : : * fkside: the side of the FK (or both) to create. Caller should
10730 : : * call addFkRecurseReferenced if this is addFkReferencedSide,
10731 : : * addFkRecurseReferencing if it's addFkReferencingSide, or both if it's
10732 : : * addFkBothSides.
10733 : : * constraintname: the base name for the constraint being added,
10734 : : * copied to fkconstraint->conname if the latter is not set
10735 : : * fkconstraint: the constraint being added
10736 : : * rel: the root referencing relation
10737 : : * pkrel: the referenced relation; might be a partition, if recursing
10738 : : * indexOid: the OID of the index (on pkrel) implementing this constraint
10739 : : * parentConstr: the OID of a parent constraint; InvalidOid if this is a
10740 : : * top-level constraint
10741 : : * numfks: the number of columns in the foreign key
10742 : : * pkattnum: the attnum array of referenced attributes
10743 : : * fkattnum: the attnum array of referencing attributes
10744 : : * pf/pp/ffeqoperators: OID array of operators between columns
10745 : : * numfkdelsetcols: the number of columns in the ON DELETE SET NULL/DEFAULT
10746 : : * (...) clause
10747 : : * fkdelsetcols: the attnum array of the columns in the ON DELETE SET
10748 : : * NULL/DEFAULT clause
10749 : : * with_period: true if this is a temporal FK
10750 : : */
10751 : : static ObjectAddress
10752 : 524 : addFkConstraint(addFkConstraintSides fkside,
10753 : : char *constraintname, Constraint *fkconstraint,
10754 : : Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
10755 : : int numfks, int16 *pkattnum,
10756 : : int16 *fkattnum, Oid *pfeqoperators, Oid *ppeqoperators,
10757 : : Oid *ffeqoperators, int numfkdelsetcols, int16 *fkdelsetcols,
10758 : : bool is_internal, bool with_period)
10759 : : {
10760 : : ObjectAddress address;
10761 : 524 : Oid constrOid;
10762 : 524 : char *conname;
10763 : 524 : bool conislocal;
10764 : 524 : int16 coninhcount;
10765 : 524 : bool connoinherit;
10766 : :
10767 : : /*
10768 : : * Verify relkind for each referenced partition. At the top level, this
10769 : : * is redundant with a previous check, but we need it when recursing.
10770 : : */
10771 [ + + + - ]: 524 : if (pkrel->rd_rel->relkind != RELKIND_RELATION &&
10772 : 116 : pkrel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
10773 [ # # # # ]: 0 : ereport(ERROR,
10774 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
10775 : : errmsg("referenced relation \"%s\" is not a table",
10776 : : RelationGetRelationName(pkrel))));
10777 : :
10778 : : /*
10779 : : * Caller supplies us with a constraint name; however, it may be used in
10780 : : * this partition, so come up with a different one in that case. Unless
10781 : : * truncation to NAMEDATALEN dictates otherwise, the new name will be the
10782 : : * supplied name with an underscore and digit(s) appended.
10783 : : */
10784 [ + + ]: 524 : if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
10785 : 524 : RelationGetRelid(rel),
10786 : 524 : constraintname))
10787 : 340 : conname = ChooseConstraintName(constraintname,
10788 : : NULL,
10789 : : "",
10790 : 170 : RelationGetNamespace(rel), NIL);
10791 : : else
10792 : 354 : conname = constraintname;
10793 : :
10794 [ + + ]: 524 : if (fkconstraint->conname == NULL)
10795 : 67 : fkconstraint->conname = pstrdup(conname);
10796 : :
10797 [ + + ]: 524 : if (OidIsValid(parentConstr))
10798 : : {
10799 : 288 : conislocal = false;
10800 : 288 : coninhcount = 1;
10801 : 288 : connoinherit = false;
10802 : 288 : }
10803 : : else
10804 : : {
10805 : 236 : conislocal = true;
10806 : 236 : coninhcount = 0;
10807 : :
10808 : : /*
10809 : : * always inherit for partitioned tables, never for legacy inheritance
10810 : : */
10811 : 236 : connoinherit = rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE;
10812 : : }
10813 : :
10814 : : /*
10815 : : * Record the FK constraint in pg_constraint.
10816 : : */
10817 : 1048 : constrOid = CreateConstraintEntry(conname,
10818 : 524 : RelationGetNamespace(rel),
10819 : : CONSTRAINT_FOREIGN,
10820 : 524 : fkconstraint->deferrable,
10821 : 524 : fkconstraint->initdeferred,
10822 : 524 : fkconstraint->is_enforced,
10823 : 524 : fkconstraint->initially_valid,
10824 : 524 : parentConstr,
10825 : 524 : RelationGetRelid(rel),
10826 : 524 : fkattnum,
10827 : 524 : numfks,
10828 : 524 : numfks,
10829 : : InvalidOid, /* not a domain constraint */
10830 : 524 : indexOid,
10831 : 524 : RelationGetRelid(pkrel),
10832 : 524 : pkattnum,
10833 : 524 : pfeqoperators,
10834 : 524 : ppeqoperators,
10835 : 524 : ffeqoperators,
10836 : 524 : numfks,
10837 : 524 : fkconstraint->fk_upd_action,
10838 : 524 : fkconstraint->fk_del_action,
10839 : 524 : fkdelsetcols,
10840 : 524 : numfkdelsetcols,
10841 : 524 : fkconstraint->fk_matchtype,
10842 : : NULL, /* no exclusion constraint */
10843 : : NULL, /* no check constraint */
10844 : : NULL,
10845 : 524 : conislocal, /* islocal */
10846 : 524 : coninhcount, /* inhcount */
10847 : 524 : connoinherit, /* conNoInherit */
10848 : 524 : with_period, /* conPeriod */
10849 : 524 : is_internal); /* is_internal */
10850 : :
10851 : 524 : ObjectAddressSet(address, ConstraintRelationId, constrOid);
10852 : :
10853 : : /*
10854 : : * In partitioning cases, create the dependency entries for this
10855 : : * constraint. (For non-partitioned cases, relevant entries were created
10856 : : * by CreateConstraintEntry.)
10857 : : *
10858 : : * On the referenced side, we need the constraint to have an internal
10859 : : * dependency on its parent constraint; this means that this constraint
10860 : : * cannot be dropped on its own -- only through the parent constraint. It
10861 : : * also means the containing partition cannot be dropped on its own, but
10862 : : * it can be detached, at which point this dependency is removed (after
10863 : : * verifying that no rows are referenced via this FK.)
10864 : : *
10865 : : * When processing the referencing side, we link the constraint via the
10866 : : * special partitioning dependencies: the parent constraint is the primary
10867 : : * dependent, and the partition on which the foreign key exists is the
10868 : : * secondary dependency. That way, this constraint is dropped if either
10869 : : * of these objects is.
10870 : : *
10871 : : * Note that this is only necessary for the subsidiary pg_constraint rows
10872 : : * in partitions; the topmost row doesn't need any of this.
10873 : : */
10874 [ + + ]: 524 : if (OidIsValid(parentConstr))
10875 : : {
10876 : 288 : ObjectAddress referenced;
10877 : :
10878 : 288 : ObjectAddressSet(referenced, ConstraintRelationId, parentConstr);
10879 : :
10880 [ + - ]: 288 : Assert(fkside != addFkBothSides);
10881 [ + + ]: 288 : if (fkside == addFkReferencedSide)
10882 : 169 : recordDependencyOn(&address, &referenced, DEPENDENCY_INTERNAL);
10883 : : else
10884 : : {
10885 : 119 : recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_PRI);
10886 : 119 : ObjectAddressSet(referenced, RelationRelationId, RelationGetRelid(rel));
10887 : 119 : recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_SEC);
10888 : : }
10889 : 288 : }
10890 : :
10891 : : /* make new constraint visible, in case we add more */
10892 : 524 : CommandCounterIncrement();
10893 : :
10894 : : return address;
10895 : 524 : }
10896 : :
10897 : : /*
10898 : : * addFkRecurseReferenced
10899 : : * Recursive helper for the referenced side of foreign key creation,
10900 : : * which creates the action triggers and recurses
10901 : : *
10902 : : * If the referenced relation is a plain relation, create the necessary action
10903 : : * triggers that implement the constraint. If the referenced relation is a
10904 : : * partitioned table, then we create a pg_constraint row referencing the parent
10905 : : * of the referencing side for it and recurse on this routine for each
10906 : : * partition.
10907 : : *
10908 : : * fkconstraint: the constraint being added
10909 : : * rel: the root referencing relation
10910 : : * pkrel: the referenced relation; might be a partition, if recursing
10911 : : * indexOid: the OID of the index (on pkrel) implementing this constraint
10912 : : * parentConstr: the OID of a parent constraint; InvalidOid if this is a
10913 : : * top-level constraint
10914 : : * numfks: the number of columns in the foreign key
10915 : : * pkattnum: the attnum array of referenced attributes
10916 : : * fkattnum: the attnum array of referencing attributes
10917 : : * numfkdelsetcols: the number of columns in the ON DELETE SET
10918 : : * NULL/DEFAULT (...) clause
10919 : : * fkdelsetcols: the attnum array of the columns in the ON DELETE SET
10920 : : * NULL/DEFAULT clause
10921 : : * pf/pp/ffeqoperators: OID array of operators between columns
10922 : : * old_check_ok: true if this constraint replaces an existing one that
10923 : : * was already validated (thus this one doesn't need validation)
10924 : : * parentDelTrigger and parentUpdTrigger: when recursively called on a
10925 : : * partition, the OIDs of the parent action triggers for DELETE and
10926 : : * UPDATE respectively.
10927 : : * with_period: true if this is a temporal FK
10928 : : */
10929 : : static void
10930 : 423 : addFkRecurseReferenced(Constraint *fkconstraint, Relation rel,
10931 : : Relation pkrel, Oid indexOid, Oid parentConstr,
10932 : : int numfks,
10933 : : int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators,
10934 : : Oid *ppeqoperators, Oid *ffeqoperators,
10935 : : int numfkdelsetcols, int16 *fkdelsetcols,
10936 : : bool old_check_ok,
10937 : : Oid parentDelTrigger, Oid parentUpdTrigger,
10938 : : bool with_period)
10939 : : {
10940 : 423 : Oid deleteTriggerOid = InvalidOid,
10941 : 423 : updateTriggerOid = InvalidOid;
10942 : :
10943 [ + - ]: 423 : Assert(CheckRelationLockedByMe(pkrel, ShareRowExclusiveLock, true));
10944 [ + - ]: 423 : Assert(CheckRelationLockedByMe(rel, ShareRowExclusiveLock, true));
10945 : :
10946 : : /*
10947 : : * Create action triggers to enforce the constraint, or skip them if the
10948 : : * constraint is NOT ENFORCED.
10949 : : */
10950 [ + + ]: 423 : if (fkconstraint->is_enforced)
10951 : 822 : createForeignKeyActionTriggers(RelationGetRelid(rel),
10952 : 411 : RelationGetRelid(pkrel),
10953 : 411 : fkconstraint,
10954 : 411 : parentConstr, indexOid,
10955 : 411 : parentDelTrigger, parentUpdTrigger,
10956 : : &deleteTriggerOid, &updateTriggerOid);
10957 : :
10958 : : /*
10959 : : * If the referenced table is partitioned, recurse on ourselves to handle
10960 : : * each partition. We need one pg_constraint row created for each
10961 : : * partition in addition to the pg_constraint row for the parent table.
10962 : : */
10963 [ + + ]: 423 : if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
10964 : : {
10965 : 75 : PartitionDesc pd = RelationGetPartitionDesc(pkrel, true);
10966 : :
10967 [ + + ]: 200 : for (int i = 0; i < pd->nparts; i++)
10968 : : {
10969 : 125 : Relation partRel;
10970 : 125 : AttrMap *map;
10971 : 125 : AttrNumber *mapped_pkattnum;
10972 : 125 : Oid partIndexId;
10973 : 125 : ObjectAddress address;
10974 : :
10975 : : /* XXX would it be better to acquire these locks beforehand? */
10976 : 125 : partRel = table_open(pd->oids[i], ShareRowExclusiveLock);
10977 : :
10978 : : /*
10979 : : * Map the attribute numbers in the referenced side of the FK
10980 : : * definition to match the partition's column layout.
10981 : : */
10982 : 250 : map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
10983 : 125 : RelationGetDescr(pkrel),
10984 : : false);
10985 [ + + ]: 125 : if (map)
10986 : : {
10987 : 21 : mapped_pkattnum = palloc_array(AttrNumber, numfks);
10988 [ + + ]: 44 : for (int j = 0; j < numfks; j++)
10989 : 23 : mapped_pkattnum[j] = map->attnums[pkattnum[j] - 1];
10990 : 21 : }
10991 : : else
10992 : 104 : mapped_pkattnum = pkattnum;
10993 : :
10994 : : /* Determine the index to use at this level */
10995 : 125 : partIndexId = index_get_partition(partRel, indexOid);
10996 [ + - ]: 125 : if (!OidIsValid(partIndexId))
10997 [ # # # # ]: 0 : elog(ERROR, "index for %u not found in partition %s",
10998 : : indexOid, RelationGetRelationName(partRel));
10999 : :
11000 : : /* Create entry at this level ... */
11001 : 250 : address = addFkConstraint(addFkReferencedSide,
11002 : 125 : fkconstraint->conname, fkconstraint, rel,
11003 : 125 : partRel, partIndexId, parentConstr,
11004 : 125 : numfks, mapped_pkattnum,
11005 : 125 : fkattnum, pfeqoperators, ppeqoperators,
11006 : 125 : ffeqoperators, numfkdelsetcols,
11007 : 125 : fkdelsetcols, true, with_period);
11008 : : /* ... and recurse to our children */
11009 : 250 : addFkRecurseReferenced(fkconstraint, rel, partRel,
11010 : 125 : partIndexId, address.objectId, numfks,
11011 : 125 : mapped_pkattnum, fkattnum,
11012 : 125 : pfeqoperators, ppeqoperators, ffeqoperators,
11013 : 125 : numfkdelsetcols, fkdelsetcols,
11014 : 125 : old_check_ok,
11015 : 125 : deleteTriggerOid, updateTriggerOid,
11016 : 125 : with_period);
11017 : :
11018 : : /* Done -- clean up (but keep the lock) */
11019 : 125 : table_close(partRel, NoLock);
11020 [ + + ]: 125 : if (map)
11021 : : {
11022 : 21 : pfree(mapped_pkattnum);
11023 : 21 : free_attrmap(map);
11024 : 21 : }
11025 : 125 : }
11026 : 75 : }
11027 : 423 : }
11028 : :
11029 : : /*
11030 : : * addFkRecurseReferencing
11031 : : * Recursive helper for the referencing side of foreign key creation,
11032 : : * which creates the check triggers and recurses
11033 : : *
11034 : : * If the referencing relation is a plain relation, create the necessary check
11035 : : * triggers that implement the constraint, and set up for Phase 3 constraint
11036 : : * verification. If the referencing relation is a partitioned table, then
11037 : : * we create a pg_constraint row for it and recurse on this routine for each
11038 : : * partition.
11039 : : *
11040 : : * We assume that the referenced relation is locked against concurrent
11041 : : * deletions. If it's a partitioned relation, every partition must be so
11042 : : * locked.
11043 : : *
11044 : : * wqueue: the ALTER TABLE work queue; NULL when not running as part
11045 : : * of an ALTER TABLE sequence.
11046 : : * fkconstraint: the constraint being added
11047 : : * rel: the referencing relation; might be a partition, if recursing
11048 : : * pkrel: the root referenced relation
11049 : : * indexOid: the OID of the index (on pkrel) implementing this constraint
11050 : : * parentConstr: the OID of the parent constraint (there is always one)
11051 : : * numfks: the number of columns in the foreign key
11052 : : * pkattnum: the attnum array of referenced attributes
11053 : : * fkattnum: the attnum array of referencing attributes
11054 : : * pf/pp/ffeqoperators: OID array of operators between columns
11055 : : * numfkdelsetcols: the number of columns in the ON DELETE SET NULL/DEFAULT
11056 : : * (...) clause
11057 : : * fkdelsetcols: the attnum array of the columns in the ON DELETE SET
11058 : : * NULL/DEFAULT clause
11059 : : * old_check_ok: true if this constraint replaces an existing one that
11060 : : * was already validated (thus this one doesn't need validation)
11061 : : * lockmode: the lockmode to acquire on partitions when recursing
11062 : : * parentInsTrigger and parentUpdTrigger: when being recursively called on
11063 : : * a partition, the OIDs of the parent check triggers for INSERT and
11064 : : * UPDATE respectively.
11065 : : * with_period: true if this is a temporal FK
11066 : : */
11067 : : static void
11068 : 354 : addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
11069 : : Relation pkrel, Oid indexOid, Oid parentConstr,
11070 : : int numfks, int16 *pkattnum, int16 *fkattnum,
11071 : : Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
11072 : : int numfkdelsetcols, int16 *fkdelsetcols,
11073 : : bool old_check_ok, LOCKMODE lockmode,
11074 : : Oid parentInsTrigger, Oid parentUpdTrigger,
11075 : : bool with_period)
11076 : : {
11077 : 354 : Oid insertTriggerOid = InvalidOid,
11078 : 354 : updateTriggerOid = InvalidOid;
11079 : :
11080 [ + - ]: 354 : Assert(OidIsValid(parentConstr));
11081 [ + - ]: 354 : Assert(CheckRelationLockedByMe(rel, ShareRowExclusiveLock, true));
11082 [ + - ]: 354 : Assert(CheckRelationLockedByMe(pkrel, ShareRowExclusiveLock, true));
11083 : :
11084 [ + - ]: 354 : if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
11085 [ # # # # ]: 0 : ereport(ERROR,
11086 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
11087 : : errmsg("foreign key constraints are not supported on foreign tables")));
11088 : :
11089 : : /*
11090 : : * Add check triggers if the constraint is ENFORCED, and if needed,
11091 : : * schedule them to be checked in Phase 3.
11092 : : *
11093 : : * If the relation is partitioned, drill down to do it to its partitions.
11094 : : */
11095 [ + + ]: 354 : if (fkconstraint->is_enforced)
11096 : 694 : createForeignKeyCheckTriggers(RelationGetRelid(rel),
11097 : 347 : RelationGetRelid(pkrel),
11098 : 347 : fkconstraint,
11099 : 347 : parentConstr,
11100 : 347 : indexOid,
11101 : 347 : parentInsTrigger, parentUpdTrigger,
11102 : : &insertTriggerOid, &updateTriggerOid);
11103 : :
11104 [ + + ]: 354 : if (rel->rd_rel->relkind == RELKIND_RELATION)
11105 : : {
11106 : : /*
11107 : : * Tell Phase 3 to check that the constraint is satisfied by existing
11108 : : * rows. We can skip this during table creation, when constraint is
11109 : : * specified as NOT ENFORCED, or when requested explicitly by
11110 : : * specifying NOT VALID in an ADD FOREIGN KEY command, and when we're
11111 : : * recreating a constraint following a SET DATA TYPE operation that
11112 : : * did not impugn its validity.
11113 : : */
11114 [ + + + + : 287 : if (wqueue && !old_check_ok && !fkconstraint->skip_validation &&
+ + - + ]
11115 : 109 : fkconstraint->is_enforced)
11116 : : {
11117 : 109 : NewConstraint *newcon;
11118 : 109 : AlteredTableInfo *tab;
11119 : :
11120 : 109 : tab = ATGetQueueEntry(wqueue, rel);
11121 : :
11122 : 109 : newcon = palloc0_object(NewConstraint);
11123 : 109 : newcon->name = get_constraint_name(parentConstr);
11124 : 109 : newcon->contype = CONSTR_FOREIGN;
11125 : 109 : newcon->refrelid = RelationGetRelid(pkrel);
11126 : 109 : newcon->refindid = indexOid;
11127 : 109 : newcon->conid = parentConstr;
11128 : 109 : newcon->conwithperiod = fkconstraint->fk_with_period;
11129 : 109 : newcon->qual = (Node *) fkconstraint;
11130 : :
11131 : 109 : tab->constraints = lappend(tab->constraints, newcon);
11132 : 109 : }
11133 : 287 : }
11134 [ - + ]: 67 : else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
11135 : : {
11136 : 67 : PartitionDesc pd = RelationGetPartitionDesc(rel, true);
11137 : 67 : Relation trigrel;
11138 : :
11139 : : /*
11140 : : * Triggers of the foreign keys will be manipulated a bunch of times
11141 : : * in the loop below. To avoid repeatedly opening/closing the trigger
11142 : : * catalog relation, we open it here and pass it to the subroutines
11143 : : * called below.
11144 : : */
11145 : 67 : trigrel = table_open(TriggerRelationId, RowExclusiveLock);
11146 : :
11147 : : /*
11148 : : * Recurse to take appropriate action on each partition; either we
11149 : : * find an existing constraint to reparent to ours, or we create a new
11150 : : * one.
11151 : : */
11152 [ + + ]: 121 : for (int i = 0; i < pd->nparts; i++)
11153 : : {
11154 : 54 : Relation partition = table_open(pd->oids[i], lockmode);
11155 : 54 : List *partFKs;
11156 : 54 : AttrMap *attmap;
11157 : 54 : AttrNumber mapped_fkattnum[INDEX_MAX_KEYS];
11158 : 54 : bool attached;
11159 : 54 : ObjectAddress address;
11160 : :
11161 : 54 : CheckAlterTableIsSafe(partition);
11162 : :
11163 : 108 : attmap = build_attrmap_by_name(RelationGetDescr(partition),
11164 : 54 : RelationGetDescr(rel),
11165 : : false);
11166 [ + + ]: 141 : for (int j = 0; j < numfks; j++)
11167 : 87 : mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
11168 : :
11169 : : /* Check whether an existing constraint can be repurposed */
11170 : 54 : partFKs = copyObject(RelationGetFKeyList(partition));
11171 : 54 : attached = false;
11172 [ + + + + : 110 : foreach_node(ForeignKeyCacheInfo, fk, partFKs)
- + + + ]
11173 : : {
11174 [ + - + - ]: 4 : if (tryAttachPartitionForeignKey(wqueue,
11175 : 2 : fk,
11176 : 2 : partition,
11177 : 2 : parentConstr,
11178 : 2 : numfks,
11179 : 2 : mapped_fkattnum,
11180 : 2 : pkattnum,
11181 : 2 : pfeqoperators,
11182 : 2 : insertTriggerOid,
11183 : 2 : updateTriggerOid,
11184 : 2 : trigrel))
11185 : : {
11186 : 2 : attached = true;
11187 : 2 : break;
11188 : : }
11189 : 54 : }
11190 [ + + ]: 54 : if (attached)
11191 : : {
11192 : 2 : table_close(partition, NoLock);
11193 : 2 : continue;
11194 : : }
11195 : :
11196 : : /*
11197 : : * No luck finding a good constraint to reuse; create our own.
11198 : : */
11199 : 104 : address = addFkConstraint(addFkReferencingSide,
11200 : 52 : fkconstraint->conname, fkconstraint,
11201 : 52 : partition, pkrel, indexOid, parentConstr,
11202 : 52 : numfks, pkattnum,
11203 : 52 : mapped_fkattnum, pfeqoperators,
11204 : 52 : ppeqoperators, ffeqoperators,
11205 : 52 : numfkdelsetcols, fkdelsetcols, true,
11206 : 52 : with_period);
11207 : :
11208 : : /* call ourselves to finalize the creation and we're done */
11209 : 104 : addFkRecurseReferencing(wqueue, fkconstraint, partition, pkrel,
11210 : 52 : indexOid,
11211 : 52 : address.objectId,
11212 : 52 : numfks,
11213 : 52 : pkattnum,
11214 : 52 : mapped_fkattnum,
11215 : 52 : pfeqoperators,
11216 : 52 : ppeqoperators,
11217 : 52 : ffeqoperators,
11218 : 52 : numfkdelsetcols,
11219 : 52 : fkdelsetcols,
11220 : 52 : old_check_ok,
11221 : 52 : lockmode,
11222 : 52 : insertTriggerOid,
11223 : 52 : updateTriggerOid,
11224 : 52 : with_period);
11225 : :
11226 : 52 : table_close(partition, NoLock);
11227 [ - + + ]: 54 : }
11228 : :
11229 : 67 : table_close(trigrel, RowExclusiveLock);
11230 : 67 : }
11231 : 354 : }
11232 : :
11233 : : /*
11234 : : * CloneForeignKeyConstraints
11235 : : * Clone foreign keys from a partitioned table to a newly acquired
11236 : : * partition.
11237 : : *
11238 : : * partitionRel is a partition of parentRel, so we can be certain that it has
11239 : : * the same columns with the same datatypes. The columns may be in different
11240 : : * order, though.
11241 : : *
11242 : : * wqueue must be passed to set up phase 3 constraint checking, unless the
11243 : : * referencing-side partition is known to be empty (such as in CREATE TABLE /
11244 : : * PARTITION OF).
11245 : : */
11246 : : static void
11247 : 1500 : CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
11248 : : Relation partitionRel)
11249 : : {
11250 : : /* This only works for declarative partitioning */
11251 [ + - ]: 1500 : Assert(parentRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
11252 : :
11253 : : /*
11254 : : * First, clone constraints where the parent is on the referencing side.
11255 : : */
11256 : 1500 : CloneFkReferencing(wqueue, parentRel, partitionRel);
11257 : :
11258 : : /*
11259 : : * Clone constraints for which the parent is on the referenced side.
11260 : : */
11261 : 1500 : CloneFkReferenced(parentRel, partitionRel);
11262 : 1500 : }
11263 : :
11264 : : /*
11265 : : * CloneFkReferenced
11266 : : * Subroutine for CloneForeignKeyConstraints
11267 : : *
11268 : : * Find all the FKs that have the parent relation on the referenced side;
11269 : : * clone those constraints to the given partition. This is to be called
11270 : : * when the partition is being created or attached.
11271 : : *
11272 : : * This recurses to partitions, if the relation being attached is partitioned.
11273 : : * Recursion is done by calling addFkRecurseReferenced.
11274 : : */
11275 : : static void
11276 : 1497 : CloneFkReferenced(Relation parentRel, Relation partitionRel)
11277 : : {
11278 : 1497 : Relation pg_constraint;
11279 : 1497 : AttrMap *attmap;
11280 : 1497 : ListCell *cell;
11281 : 1497 : SysScanDesc scan;
11282 : 1497 : ScanKeyData key[2];
11283 : 1497 : HeapTuple tuple;
11284 : 1497 : List *clone = NIL;
11285 : 1497 : Relation trigrel;
11286 : :
11287 : : /*
11288 : : * Search for any constraints where this partition's parent is in the
11289 : : * referenced side. However, we must not clone any constraint whose
11290 : : * parent constraint is also going to be cloned, to avoid duplicates. So
11291 : : * do it in two steps: first construct the list of constraints to clone,
11292 : : * then go over that list cloning those whose parents are not in the list.
11293 : : * (We must not rely on the parent being seen first, since the catalog
11294 : : * scan could return children first.)
11295 : : */
11296 : 1497 : pg_constraint = table_open(ConstraintRelationId, RowShareLock);
11297 : 2994 : ScanKeyInit(&key[0],
11298 : : Anum_pg_constraint_confrelid, BTEqualStrategyNumber,
11299 : 1497 : F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(parentRel)));
11300 : 2994 : ScanKeyInit(&key[1],
11301 : : Anum_pg_constraint_contype, BTEqualStrategyNumber,
11302 : 1497 : F_CHAREQ, CharGetDatum(CONSTRAINT_FOREIGN));
11303 : : /* This is a seqscan, as we don't have a usable index ... */
11304 : 2994 : scan = systable_beginscan(pg_constraint, InvalidOid, true,
11305 : 1497 : NULL, 2, key);
11306 [ + + ]: 1578 : while ((tuple = systable_getnext(scan)) != NULL)
11307 : : {
11308 : 81 : Form_pg_constraint constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
11309 : :
11310 : 81 : clone = lappend_oid(clone, constrForm->oid);
11311 : 81 : }
11312 : 1497 : systable_endscan(scan);
11313 : 1497 : table_close(pg_constraint, RowShareLock);
11314 : :
11315 : : /*
11316 : : * Triggers of the foreign keys will be manipulated a bunch of times in
11317 : : * the loop below. To avoid repeatedly opening/closing the trigger
11318 : : * catalog relation, we open it here and pass it to the subroutines called
11319 : : * below.
11320 : : */
11321 : 1497 : trigrel = table_open(TriggerRelationId, RowExclusiveLock);
11322 : :
11323 : 2994 : attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
11324 : 1497 : RelationGetDescr(parentRel),
11325 : : false);
11326 [ + + + + : 1578 : foreach(cell, clone)
+ + ]
11327 : : {
11328 : 81 : Oid constrOid = lfirst_oid(cell);
11329 : 81 : Form_pg_constraint constrForm;
11330 : 81 : Relation fkRel;
11331 : 81 : Oid indexOid;
11332 : 81 : Oid partIndexId;
11333 : 81 : int numfks;
11334 : 81 : AttrNumber conkey[INDEX_MAX_KEYS];
11335 : 81 : AttrNumber mapped_confkey[INDEX_MAX_KEYS];
11336 : 81 : AttrNumber confkey[INDEX_MAX_KEYS];
11337 : 81 : Oid conpfeqop[INDEX_MAX_KEYS];
11338 : 81 : Oid conppeqop[INDEX_MAX_KEYS];
11339 : 81 : Oid conffeqop[INDEX_MAX_KEYS];
11340 : 81 : int numfkdelsetcols;
11341 : 81 : AttrNumber confdelsetcols[INDEX_MAX_KEYS];
11342 : 81 : Constraint *fkconstraint;
11343 : 81 : ObjectAddress address;
11344 : 81 : Oid deleteTriggerOid = InvalidOid,
11345 : 81 : updateTriggerOid = InvalidOid;
11346 : :
11347 : 81 : tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
11348 [ + - ]: 81 : if (!HeapTupleIsValid(tuple))
11349 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u", constrOid);
11350 : 81 : constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
11351 : :
11352 : : /*
11353 : : * As explained above: don't try to clone a constraint for which we're
11354 : : * going to clone the parent.
11355 : : */
11356 [ + + ]: 81 : if (list_member_oid(clone, constrForm->conparentid))
11357 : : {
11358 : 37 : ReleaseSysCache(tuple);
11359 : 37 : continue;
11360 : : }
11361 : :
11362 : : /* We need the same lock level that CreateTrigger will acquire */
11363 : 44 : fkRel = table_open(constrForm->conrelid, ShareRowExclusiveLock);
11364 : :
11365 : 44 : indexOid = constrForm->conindid;
11366 : 88 : DeconstructFkConstraintRow(tuple,
11367 : : &numfks,
11368 : 44 : conkey,
11369 : 44 : confkey,
11370 : 44 : conpfeqop,
11371 : 44 : conppeqop,
11372 : 44 : conffeqop,
11373 : : &numfkdelsetcols,
11374 : 44 : confdelsetcols);
11375 : :
11376 [ + + ]: 95 : for (int i = 0; i < numfks; i++)
11377 : 51 : mapped_confkey[i] = attmap->attnums[confkey[i] - 1];
11378 : :
11379 : 44 : fkconstraint = makeNode(Constraint);
11380 : 44 : fkconstraint->contype = CONSTRAINT_FOREIGN;
11381 : 44 : fkconstraint->conname = NameStr(constrForm->conname);
11382 : 44 : fkconstraint->deferrable = constrForm->condeferrable;
11383 : 44 : fkconstraint->initdeferred = constrForm->condeferred;
11384 : 44 : fkconstraint->location = -1;
11385 : 44 : fkconstraint->pktable = NULL;
11386 : : /* ->fk_attrs determined below */
11387 : 44 : fkconstraint->pk_attrs = NIL;
11388 : 44 : fkconstraint->fk_matchtype = constrForm->confmatchtype;
11389 : 44 : fkconstraint->fk_upd_action = constrForm->confupdtype;
11390 : 44 : fkconstraint->fk_del_action = constrForm->confdeltype;
11391 : 44 : fkconstraint->fk_del_set_cols = NIL;
11392 : 44 : fkconstraint->old_conpfeqop = NIL;
11393 : 44 : fkconstraint->old_pktable_oid = InvalidOid;
11394 : 44 : fkconstraint->is_enforced = constrForm->conenforced;
11395 : 44 : fkconstraint->skip_validation = false;
11396 : 44 : fkconstraint->initially_valid = constrForm->convalidated;
11397 : :
11398 : : /* set up colnames that are used to generate the constraint name */
11399 [ + + ]: 95 : for (int i = 0; i < numfks; i++)
11400 : : {
11401 : 51 : Form_pg_attribute att;
11402 : :
11403 : 102 : att = TupleDescAttr(RelationGetDescr(fkRel),
11404 : 51 : conkey[i] - 1);
11405 : 102 : fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
11406 : 51 : makeString(NameStr(att->attname)));
11407 : 51 : }
11408 : :
11409 : : /*
11410 : : * Add the new foreign key constraint pointing to the new partition.
11411 : : * Because this new partition appears in the referenced side of the
11412 : : * constraint, we don't need to set up for Phase 3 check.
11413 : : */
11414 : 44 : partIndexId = index_get_partition(partitionRel, indexOid);
11415 [ + - ]: 44 : if (!OidIsValid(partIndexId))
11416 [ # # # # ]: 0 : elog(ERROR, "index for %u not found in partition %s",
11417 : : indexOid, RelationGetRelationName(partitionRel));
11418 : :
11419 : : /*
11420 : : * Get the "action" triggers belonging to the constraint to pass as
11421 : : * parent OIDs for similar triggers that will be created on the
11422 : : * partition in addFkRecurseReferenced().
11423 : : */
11424 [ + + ]: 44 : if (constrForm->conenforced)
11425 : 86 : GetForeignKeyActionTriggers(trigrel, constrOid,
11426 : 43 : constrForm->confrelid, constrForm->conrelid,
11427 : : &deleteTriggerOid, &updateTriggerOid);
11428 : :
11429 : : /* Add this constraint ... */
11430 : 88 : address = addFkConstraint(addFkReferencedSide,
11431 : 44 : fkconstraint->conname, fkconstraint, fkRel,
11432 : 44 : partitionRel, partIndexId, constrOid,
11433 : 44 : numfks, mapped_confkey,
11434 : 44 : conkey, conpfeqop, conppeqop, conffeqop,
11435 : 44 : numfkdelsetcols, confdelsetcols, false,
11436 : 44 : constrForm->conperiod);
11437 : : /* ... and recurse */
11438 : 88 : addFkRecurseReferenced(fkconstraint,
11439 : 44 : fkRel,
11440 : 44 : partitionRel,
11441 : 44 : partIndexId,
11442 : 44 : address.objectId,
11443 : 44 : numfks,
11444 : 44 : mapped_confkey,
11445 : 44 : conkey,
11446 : 44 : conpfeqop,
11447 : 44 : conppeqop,
11448 : 44 : conffeqop,
11449 : 44 : numfkdelsetcols,
11450 : 44 : confdelsetcols,
11451 : : true,
11452 : 44 : deleteTriggerOid,
11453 : 44 : updateTriggerOid,
11454 : 44 : constrForm->conperiod);
11455 : :
11456 : 44 : table_close(fkRel, NoLock);
11457 : 44 : ReleaseSysCache(tuple);
11458 [ - + + ]: 81 : }
11459 : :
11460 : 1497 : table_close(trigrel, RowExclusiveLock);
11461 : 1497 : }
11462 : :
11463 : : /*
11464 : : * CloneFkReferencing
11465 : : * Subroutine for CloneForeignKeyConstraints
11466 : : *
11467 : : * For each FK constraint of the parent relation in the given list, find an
11468 : : * equivalent constraint in its partition relation that can be reparented;
11469 : : * if one cannot be found, create a new constraint in the partition as its
11470 : : * child.
11471 : : *
11472 : : * If wqueue is given, it is used to set up phase-3 verification for each
11473 : : * cloned constraint; omit it if such verification is not needed
11474 : : * (example: the partition is being created anew).
11475 : : */
11476 : : static void
11477 : 1500 : CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
11478 : : {
11479 : 1500 : AttrMap *attmap;
11480 : 1500 : List *partFKs;
11481 : 1500 : List *clone = NIL;
11482 : 1500 : ListCell *cell;
11483 : 1500 : Relation trigrel;
11484 : :
11485 : : /* obtain a list of constraints that we need to clone */
11486 [ + + + + : 1707 : foreach(cell, RelationGetFKeyList(parentRel))
+ + ]
11487 : : {
11488 : 208 : ForeignKeyCacheInfo *fk = lfirst(cell);
11489 : :
11490 : : /*
11491 : : * Refuse to attach a table as partition that this partitioned table
11492 : : * already has a foreign key to. This isn't useful schema, which is
11493 : : * proven by the fact that there have been no user complaints that
11494 : : * it's already impossible to achieve this in the opposite direction,
11495 : : * i.e., creating a foreign key that references a partition. This
11496 : : * restriction allows us to dodge some complexities around
11497 : : * pg_constraint and pg_trigger row creations that would be needed
11498 : : * during ATTACH/DETACH for this kind of relationship.
11499 : : */
11500 [ + + ]: 208 : if (fk->confrelid == RelationGetRelid(partRel))
11501 [ + - + - ]: 1 : ereport(ERROR,
11502 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
11503 : : errmsg("cannot attach table \"%s\" as a partition because it is referenced by foreign key \"%s\"",
11504 : : RelationGetRelationName(partRel),
11505 : : get_constraint_name(fk->conoid))));
11506 : :
11507 : 207 : clone = lappend_oid(clone, fk->conoid);
11508 : 207 : }
11509 : :
11510 : : /*
11511 : : * Silently do nothing if there's nothing to do. In particular, this
11512 : : * avoids throwing a spurious error for foreign tables.
11513 : : */
11514 [ + + ]: 1499 : if (clone == NIL)
11515 : 1412 : return;
11516 : :
11517 [ + - ]: 87 : if (partRel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
11518 [ # # # # ]: 0 : ereport(ERROR,
11519 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
11520 : : errmsg("foreign key constraints are not supported on foreign tables")));
11521 : :
11522 : : /*
11523 : : * Triggers of the foreign keys will be manipulated a bunch of times in
11524 : : * the loop below. To avoid repeatedly opening/closing the trigger
11525 : : * catalog relation, we open it here and pass it to the subroutines called
11526 : : * below.
11527 : : */
11528 : 87 : trigrel = table_open(TriggerRelationId, RowExclusiveLock);
11529 : :
11530 : : /*
11531 : : * The constraint key may differ, if the columns in the partition are
11532 : : * different. This map is used to convert them.
11533 : : */
11534 : 174 : attmap = build_attrmap_by_name(RelationGetDescr(partRel),
11535 : 87 : RelationGetDescr(parentRel),
11536 : : false);
11537 : :
11538 : 87 : partFKs = copyObject(RelationGetFKeyList(partRel));
11539 : :
11540 [ + + + + : 293 : foreach(cell, clone)
+ + ]
11541 : : {
11542 : 206 : Oid parentConstrOid = lfirst_oid(cell);
11543 : 206 : Form_pg_constraint constrForm;
11544 : 206 : Relation pkrel;
11545 : 206 : HeapTuple tuple;
11546 : 206 : int numfks;
11547 : 206 : AttrNumber conkey[INDEX_MAX_KEYS];
11548 : 206 : AttrNumber mapped_conkey[INDEX_MAX_KEYS];
11549 : 206 : AttrNumber confkey[INDEX_MAX_KEYS];
11550 : 206 : Oid conpfeqop[INDEX_MAX_KEYS];
11551 : 206 : Oid conppeqop[INDEX_MAX_KEYS];
11552 : 206 : Oid conffeqop[INDEX_MAX_KEYS];
11553 : 206 : int numfkdelsetcols;
11554 : 206 : AttrNumber confdelsetcols[INDEX_MAX_KEYS];
11555 : 206 : Constraint *fkconstraint;
11556 : 206 : bool attached;
11557 : 206 : Oid indexOid;
11558 : 206 : ObjectAddress address;
11559 : 206 : ListCell *lc;
11560 : 206 : Oid insertTriggerOid = InvalidOid,
11561 : 206 : updateTriggerOid = InvalidOid;
11562 : 206 : bool with_period;
11563 : :
11564 : 206 : tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(parentConstrOid));
11565 [ + - ]: 206 : if (!HeapTupleIsValid(tuple))
11566 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u",
11567 : : parentConstrOid);
11568 : 206 : constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
11569 : :
11570 : : /* Don't clone constraints whose parents are being cloned */
11571 [ + + ]: 206 : if (list_member_oid(clone, constrForm->conparentid))
11572 : : {
11573 : 114 : ReleaseSysCache(tuple);
11574 : 114 : continue;
11575 : : }
11576 : :
11577 : : /*
11578 : : * Need to prevent concurrent deletions. If pkrel is a partitioned
11579 : : * relation, that means to lock all partitions.
11580 : : */
11581 : 92 : pkrel = table_open(constrForm->confrelid, ShareRowExclusiveLock);
11582 [ + + ]: 92 : if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
11583 : 35 : (void) find_all_inheritors(RelationGetRelid(pkrel),
11584 : : ShareRowExclusiveLock, NULL);
11585 : :
11586 : 184 : DeconstructFkConstraintRow(tuple, &numfks, conkey, confkey,
11587 : 92 : conpfeqop, conppeqop, conffeqop,
11588 : 92 : &numfkdelsetcols, confdelsetcols);
11589 [ + + ]: 224 : for (int i = 0; i < numfks; i++)
11590 : 132 : mapped_conkey[i] = attmap->attnums[conkey[i] - 1];
11591 : :
11592 : : /*
11593 : : * Get the "check" triggers belonging to the constraint, if it is
11594 : : * ENFORCED, to pass as parent OIDs for similar triggers that will be
11595 : : * created on the partition in addFkRecurseReferencing(). They are
11596 : : * also passed to tryAttachPartitionForeignKey() below to simply
11597 : : * assign as parents to the partition's existing "check" triggers,
11598 : : * that is, if the corresponding constraints is deemed attachable to
11599 : : * the parent constraint.
11600 : : */
11601 [ + + ]: 92 : if (constrForm->conenforced)
11602 : 182 : GetForeignKeyCheckTriggers(trigrel, constrForm->oid,
11603 : 91 : constrForm->confrelid, constrForm->conrelid,
11604 : : &insertTriggerOid, &updateTriggerOid);
11605 : :
11606 : : /*
11607 : : * Before creating a new constraint, see whether any existing FKs are
11608 : : * fit for the purpose. If one is, attach the parent constraint to
11609 : : * it, and don't clone anything. This way we avoid the expensive
11610 : : * verification step and don't end up with a duplicate FK, and we
11611 : : * don't need to recurse to partitions for this constraint.
11612 : : */
11613 : 92 : attached = false;
11614 [ + + + + : 132 : foreach(lc, partFKs)
+ + ]
11615 : : {
11616 : 40 : ForeignKeyCacheInfo *fk = lfirst_node(ForeignKeyCacheInfo, lc);
11617 : :
11618 [ + + + + ]: 80 : if (tryAttachPartitionForeignKey(wqueue,
11619 : 40 : fk,
11620 : 40 : partRel,
11621 : 40 : parentConstrOid,
11622 : 40 : numfks,
11623 : 40 : mapped_conkey,
11624 : 40 : confkey,
11625 : 40 : conpfeqop,
11626 : 40 : insertTriggerOid,
11627 : 40 : updateTriggerOid,
11628 : 40 : trigrel))
11629 : : {
11630 : 25 : attached = true;
11631 : 25 : table_close(pkrel, NoLock);
11632 : 25 : break;
11633 : : }
11634 [ + + ]: 40 : }
11635 [ + + ]: 92 : if (attached)
11636 : : {
11637 : 25 : ReleaseSysCache(tuple);
11638 : 25 : continue;
11639 : : }
11640 : :
11641 : : /* No dice. Set up to create our own constraint */
11642 : 67 : fkconstraint = makeNode(Constraint);
11643 : 67 : fkconstraint->contype = CONSTRAINT_FOREIGN;
11644 : : /* ->conname determined below */
11645 : 67 : fkconstraint->deferrable = constrForm->condeferrable;
11646 : 67 : fkconstraint->initdeferred = constrForm->condeferred;
11647 : 67 : fkconstraint->location = -1;
11648 : 67 : fkconstraint->pktable = NULL;
11649 : : /* ->fk_attrs determined below */
11650 : 67 : fkconstraint->pk_attrs = NIL;
11651 : 67 : fkconstraint->fk_matchtype = constrForm->confmatchtype;
11652 : 67 : fkconstraint->fk_upd_action = constrForm->confupdtype;
11653 : 67 : fkconstraint->fk_del_action = constrForm->confdeltype;
11654 : 67 : fkconstraint->fk_del_set_cols = NIL;
11655 : 67 : fkconstraint->old_conpfeqop = NIL;
11656 : 67 : fkconstraint->old_pktable_oid = InvalidOid;
11657 : 67 : fkconstraint->is_enforced = constrForm->conenforced;
11658 : 67 : fkconstraint->skip_validation = false;
11659 : 67 : fkconstraint->initially_valid = constrForm->convalidated;
11660 [ + + ]: 154 : for (int i = 0; i < numfks; i++)
11661 : : {
11662 : 87 : Form_pg_attribute att;
11663 : :
11664 : 174 : att = TupleDescAttr(RelationGetDescr(partRel),
11665 : 87 : mapped_conkey[i] - 1);
11666 : 174 : fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
11667 : 87 : makeString(NameStr(att->attname)));
11668 : 87 : }
11669 : :
11670 : 67 : indexOid = constrForm->conindid;
11671 : 67 : with_period = constrForm->conperiod;
11672 : :
11673 : : /* Create the pg_constraint entry at this level */
11674 : 134 : address = addFkConstraint(addFkReferencingSide,
11675 : 67 : NameStr(constrForm->conname), fkconstraint,
11676 : 67 : partRel, pkrel, indexOid, parentConstrOid,
11677 : 67 : numfks, confkey,
11678 : 67 : mapped_conkey, conpfeqop,
11679 : 67 : conppeqop, conffeqop,
11680 : 67 : numfkdelsetcols, confdelsetcols,
11681 : 67 : false, with_period);
11682 : :
11683 : : /* Done with the cloned constraint's tuple */
11684 : 67 : ReleaseSysCache(tuple);
11685 : :
11686 : : /* Create the check triggers, and recurse to partitions, if any */
11687 : 134 : addFkRecurseReferencing(wqueue,
11688 : 67 : fkconstraint,
11689 : 67 : partRel,
11690 : 67 : pkrel,
11691 : 67 : indexOid,
11692 : 67 : address.objectId,
11693 : 67 : numfks,
11694 : 67 : confkey,
11695 : 67 : mapped_conkey,
11696 : 67 : conpfeqop,
11697 : 67 : conppeqop,
11698 : 67 : conffeqop,
11699 : 67 : numfkdelsetcols,
11700 : 67 : confdelsetcols,
11701 : : false, /* no old check exists */
11702 : : AccessExclusiveLock,
11703 : 67 : insertTriggerOid,
11704 : 67 : updateTriggerOid,
11705 : 67 : with_period);
11706 : 67 : table_close(pkrel, NoLock);
11707 [ + + ]: 206 : }
11708 : :
11709 : 85 : table_close(trigrel, RowExclusiveLock);
11710 : 1497 : }
11711 : :
11712 : : /*
11713 : : * When the parent of a partition receives [the referencing side of] a foreign
11714 : : * key, we must propagate that foreign key to the partition. However, the
11715 : : * partition might already have an equivalent foreign key; this routine
11716 : : * compares the given ForeignKeyCacheInfo (in the partition) to the FK defined
11717 : : * by the other parameters. If they are equivalent, create the link between
11718 : : * the two constraints and return true.
11719 : : *
11720 : : * If the given FK does not match the one defined by rest of the params,
11721 : : * return false.
11722 : : */
11723 : : static bool
11724 : 43 : tryAttachPartitionForeignKey(List **wqueue,
11725 : : ForeignKeyCacheInfo *fk,
11726 : : Relation partition,
11727 : : Oid parentConstrOid,
11728 : : int numfks,
11729 : : AttrNumber *mapped_conkey,
11730 : : AttrNumber *confkey,
11731 : : Oid *conpfeqop,
11732 : : Oid parentInsTrigger,
11733 : : Oid parentUpdTrigger,
11734 : : Relation trigrel)
11735 : : {
11736 : 43 : HeapTuple parentConstrTup;
11737 : 43 : Form_pg_constraint parentConstr;
11738 : 43 : HeapTuple partcontup;
11739 : 43 : Form_pg_constraint partConstr;
11740 : :
11741 : 43 : parentConstrTup = SearchSysCache1(CONSTROID,
11742 : 43 : ObjectIdGetDatum(parentConstrOid));
11743 [ + - ]: 43 : if (!HeapTupleIsValid(parentConstrTup))
11744 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u", parentConstrOid);
11745 : 43 : parentConstr = (Form_pg_constraint) GETSTRUCT(parentConstrTup);
11746 : :
11747 : : /*
11748 : : * Do some quick & easy initial checks. If any of these fail, we cannot
11749 : : * use this constraint.
11750 : : */
11751 [ + - - + ]: 43 : if (fk->confrelid != parentConstr->confrelid || fk->nkeys != numfks)
11752 : : {
11753 : 0 : ReleaseSysCache(parentConstrTup);
11754 : 0 : return false;
11755 : : }
11756 [ + + - + ]: 119 : for (int i = 0; i < numfks; i++)
11757 : : {
11758 [ + - ]: 76 : if (fk->conkey[i] != mapped_conkey[i] ||
11759 [ + - - + ]: 76 : fk->confkey[i] != confkey[i] ||
11760 : 76 : fk->conpfeqop[i] != conpfeqop[i])
11761 : : {
11762 : 0 : ReleaseSysCache(parentConstrTup);
11763 : 0 : return false;
11764 : : }
11765 : 76 : }
11766 : :
11767 : : /* Looks good so far; perform more extensive checks. */
11768 : 43 : partcontup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
11769 [ + - ]: 43 : if (!HeapTupleIsValid(partcontup))
11770 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
11771 : 43 : partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
11772 : :
11773 : : /*
11774 : : * An error should be raised if the constraint enforceability is
11775 : : * different. Returning false without raising an error, as we do for other
11776 : : * attributes, could lead to a duplicate constraint with the same
11777 : : * enforceability as the parent. While this may be acceptable, it may not
11778 : : * be ideal. Therefore, it's better to raise an error and allow the user
11779 : : * to correct the enforceability before proceeding.
11780 : : */
11781 [ + + ]: 43 : if (partConstr->conenforced != parentConstr->conenforced)
11782 [ + - + - ]: 1 : ereport(ERROR,
11783 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
11784 : : errmsg("constraint \"%s\" enforceability conflicts with constraint \"%s\" on relation \"%s\"",
11785 : : NameStr(parentConstr->conname),
11786 : : NameStr(partConstr->conname),
11787 : : RelationGetRelationName(partition))));
11788 : :
11789 [ + + ]: 42 : if (OidIsValid(partConstr->conparentid) ||
11790 [ + + ]: 38 : partConstr->condeferrable != parentConstr->condeferrable ||
11791 [ + - ]: 34 : partConstr->condeferred != parentConstr->condeferred ||
11792 [ + + ]: 34 : partConstr->confupdtype != parentConstr->confupdtype ||
11793 [ + - + + ]: 29 : partConstr->confdeltype != parentConstr->confdeltype ||
11794 : 29 : partConstr->confmatchtype != parentConstr->confmatchtype)
11795 : : {
11796 : 15 : ReleaseSysCache(parentConstrTup);
11797 : 15 : ReleaseSysCache(partcontup);
11798 : 15 : return false;
11799 : : }
11800 : :
11801 : 27 : ReleaseSysCache(parentConstrTup);
11802 : 27 : ReleaseSysCache(partcontup);
11803 : :
11804 : : /* Looks good! Attach this constraint. */
11805 : 54 : AttachPartitionForeignKey(wqueue, partition, fk->conoid,
11806 : 27 : parentConstrOid, parentInsTrigger,
11807 : 27 : parentUpdTrigger, trigrel);
11808 : :
11809 : 27 : return true;
11810 : 42 : }
11811 : :
11812 : : /*
11813 : : * AttachPartitionForeignKey
11814 : : *
11815 : : * The subroutine for tryAttachPartitionForeignKey performs the final tasks of
11816 : : * attaching the constraint, removing redundant triggers and entries from
11817 : : * pg_constraint, and setting the constraint's parent.
11818 : : */
11819 : : static void
11820 : 27 : AttachPartitionForeignKey(List **wqueue,
11821 : : Relation partition,
11822 : : Oid partConstrOid,
11823 : : Oid parentConstrOid,
11824 : : Oid parentInsTrigger,
11825 : : Oid parentUpdTrigger,
11826 : : Relation trigrel)
11827 : : {
11828 : 27 : HeapTuple parentConstrTup;
11829 : 27 : Form_pg_constraint parentConstr;
11830 : 27 : HeapTuple partcontup;
11831 : 27 : Form_pg_constraint partConstr;
11832 : 27 : bool queueValidation;
11833 : 27 : Oid partConstrFrelid;
11834 : 27 : Oid partConstrRelid;
11835 : 27 : bool parentConstrIsEnforced;
11836 : :
11837 : : /* Fetch the parent constraint tuple */
11838 : 27 : parentConstrTup = SearchSysCache1(CONSTROID,
11839 : 27 : ObjectIdGetDatum(parentConstrOid));
11840 [ + - ]: 27 : if (!HeapTupleIsValid(parentConstrTup))
11841 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u", parentConstrOid);
11842 : 27 : parentConstr = (Form_pg_constraint) GETSTRUCT(parentConstrTup);
11843 : 27 : parentConstrIsEnforced = parentConstr->conenforced;
11844 : :
11845 : : /* Fetch the child constraint tuple */
11846 : 27 : partcontup = SearchSysCache1(CONSTROID,
11847 : 27 : ObjectIdGetDatum(partConstrOid));
11848 [ + - ]: 27 : if (!HeapTupleIsValid(partcontup))
11849 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u", partConstrOid);
11850 : 27 : partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
11851 : 27 : partConstrFrelid = partConstr->confrelid;
11852 : 27 : partConstrRelid = partConstr->conrelid;
11853 : :
11854 : : /*
11855 : : * If the referenced table is partitioned, then the partition we're
11856 : : * attaching now has extra pg_constraint rows and action triggers that are
11857 : : * no longer needed. Remove those.
11858 : : */
11859 [ + + ]: 27 : if (get_rel_relkind(partConstrFrelid) == RELKIND_PARTITIONED_TABLE)
11860 : : {
11861 : 6 : Relation pg_constraint = table_open(ConstraintRelationId, RowShareLock);
11862 : :
11863 : 12 : RemoveInheritedConstraint(pg_constraint, trigrel, partConstrOid,
11864 : 6 : partConstrRelid);
11865 : :
11866 : 6 : table_close(pg_constraint, RowShareLock);
11867 : 6 : }
11868 : :
11869 : : /*
11870 : : * Will we need to validate this constraint? A valid parent constraint
11871 : : * implies that all child constraints have been validated, so if this one
11872 : : * isn't, we must trigger phase 3 validation.
11873 : : */
11874 [ + + ]: 27 : queueValidation = parentConstr->convalidated && !partConstr->convalidated;
11875 : :
11876 : 27 : ReleaseSysCache(partcontup);
11877 : 27 : ReleaseSysCache(parentConstrTup);
11878 : :
11879 : : /*
11880 : : * The action triggers in the new partition become redundant -- the parent
11881 : : * table already has equivalent ones, and those will be able to reach the
11882 : : * partition. Remove the ones in the partition. We identify them because
11883 : : * they have our constraint OID, as well as being on the referenced rel.
11884 : : */
11885 : 54 : DropForeignKeyConstraintTriggers(trigrel, partConstrOid, partConstrFrelid,
11886 : 27 : partConstrRelid);
11887 : :
11888 : 54 : ConstraintSetParentConstraint(partConstrOid, parentConstrOid,
11889 : 27 : RelationGetRelid(partition));
11890 : :
11891 : : /*
11892 : : * Like the constraint, attach partition's "check" triggers to the
11893 : : * corresponding parent triggers if the constraint is ENFORCED. NOT
11894 : : * ENFORCED constraints do not have these triggers.
11895 : : */
11896 [ + + ]: 27 : if (parentConstrIsEnforced)
11897 : : {
11898 : 25 : Oid insertTriggerOid,
11899 : : updateTriggerOid;
11900 : :
11901 : 50 : GetForeignKeyCheckTriggers(trigrel,
11902 : 25 : partConstrOid, partConstrFrelid, partConstrRelid,
11903 : : &insertTriggerOid, &updateTriggerOid);
11904 [ + - ]: 25 : Assert(OidIsValid(insertTriggerOid) && OidIsValid(parentInsTrigger));
11905 : 50 : TriggerSetParentTrigger(trigrel, insertTriggerOid, parentInsTrigger,
11906 : 25 : RelationGetRelid(partition));
11907 [ + - ]: 25 : Assert(OidIsValid(updateTriggerOid) && OidIsValid(parentUpdTrigger));
11908 : 50 : TriggerSetParentTrigger(trigrel, updateTriggerOid, parentUpdTrigger,
11909 : 25 : RelationGetRelid(partition));
11910 : 25 : }
11911 : :
11912 : : /*
11913 : : * We updated this pg_constraint row above to set its parent; validating
11914 : : * it will cause its convalidated flag to change, so we need CCI here. In
11915 : : * addition, we need it unconditionally for the rare case where the parent
11916 : : * table has *two* identical constraints; when reaching this function for
11917 : : * the second one, we must have made our changes visible, otherwise we
11918 : : * would try to attach both to this one.
11919 : : */
11920 : 27 : CommandCounterIncrement();
11921 : :
11922 : : /* If validation is needed, put it in the queue now. */
11923 [ + + ]: 27 : if (queueValidation)
11924 : : {
11925 : 3 : Relation conrel;
11926 : 3 : Oid confrelid;
11927 : :
11928 : 3 : conrel = table_open(ConstraintRelationId, RowExclusiveLock);
11929 : :
11930 : 3 : partcontup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(partConstrOid));
11931 [ + - ]: 3 : if (!HeapTupleIsValid(partcontup))
11932 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u", partConstrOid);
11933 : :
11934 : 3 : confrelid = ((Form_pg_constraint) GETSTRUCT(partcontup))->confrelid;
11935 : :
11936 : : /* Use the same lock as for AT_ValidateConstraint */
11937 : 6 : QueueFKConstraintValidation(wqueue, conrel, partition, confrelid,
11938 : 3 : partcontup, ShareUpdateExclusiveLock);
11939 : 3 : ReleaseSysCache(partcontup);
11940 : 3 : table_close(conrel, RowExclusiveLock);
11941 : 3 : }
11942 : 27 : }
11943 : :
11944 : : /*
11945 : : * RemoveInheritedConstraint
11946 : : *
11947 : : * Removes the constraint and its associated trigger from the specified
11948 : : * relation, which inherited the given constraint.
11949 : : */
11950 : : static void
11951 : 6 : RemoveInheritedConstraint(Relation conrel, Relation trigrel, Oid conoid,
11952 : : Oid conrelid)
11953 : : {
11954 : 6 : ObjectAddresses *objs;
11955 : 6 : HeapTuple consttup;
11956 : 6 : ScanKeyData key;
11957 : 6 : SysScanDesc scan;
11958 : 6 : HeapTuple trigtup;
11959 : :
11960 : 6 : ScanKeyInit(&key,
11961 : : Anum_pg_constraint_conrelid,
11962 : : BTEqualStrategyNumber, F_OIDEQ,
11963 : 6 : ObjectIdGetDatum(conrelid));
11964 : :
11965 : 6 : scan = systable_beginscan(conrel,
11966 : : ConstraintRelidTypidNameIndexId,
11967 : : true, NULL, 1, &key);
11968 : 6 : objs = new_object_addresses();
11969 [ + + ]: 54 : while ((consttup = systable_getnext(scan)) != NULL)
11970 : : {
11971 : 48 : Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(consttup);
11972 : :
11973 [ + + ]: 48 : if (conform->conparentid != conoid)
11974 : 35 : continue;
11975 : : else
11976 : : {
11977 : 13 : ObjectAddress addr;
11978 : 13 : SysScanDesc scan2;
11979 : 13 : ScanKeyData key2;
11980 : 13 : int n PG_USED_FOR_ASSERTS_ONLY;
11981 : :
11982 : 13 : ObjectAddressSet(addr, ConstraintRelationId, conform->oid);
11983 : 13 : add_exact_object_address(&addr, objs);
11984 : :
11985 : : /*
11986 : : * First we must delete the dependency record that binds the
11987 : : * constraint records together.
11988 : : */
11989 : 13 : n = deleteDependencyRecordsForSpecific(ConstraintRelationId,
11990 : 13 : conform->oid,
11991 : : DEPENDENCY_INTERNAL,
11992 : : ConstraintRelationId,
11993 : 13 : conoid);
11994 [ - + ]: 13 : Assert(n == 1); /* actually only one is expected */
11995 : :
11996 : : /*
11997 : : * Now search for the triggers for this constraint and set them up
11998 : : * for deletion too
11999 : : */
12000 : 13 : ScanKeyInit(&key2,
12001 : : Anum_pg_trigger_tgconstraint,
12002 : : BTEqualStrategyNumber, F_OIDEQ,
12003 : 13 : ObjectIdGetDatum(conform->oid));
12004 : 13 : scan2 = systable_beginscan(trigrel, TriggerConstraintIndexId,
12005 : : true, NULL, 1, &key2);
12006 [ + + ]: 39 : while ((trigtup = systable_getnext(scan2)) != NULL)
12007 : : {
12008 : 26 : ObjectAddressSet(addr, TriggerRelationId,
12009 : : ((Form_pg_trigger) GETSTRUCT(trigtup))->oid);
12010 : 26 : add_exact_object_address(&addr, objs);
12011 : : }
12012 : 13 : systable_endscan(scan2);
12013 : 13 : }
12014 [ - + + ]: 48 : }
12015 : : /* make the dependency deletions visible */
12016 : 6 : CommandCounterIncrement();
12017 : 6 : performMultipleDeletions(objs, DROP_RESTRICT,
12018 : : PERFORM_DELETION_INTERNAL);
12019 : 6 : systable_endscan(scan);
12020 : 6 : }
12021 : :
12022 : : /*
12023 : : * DropForeignKeyConstraintTriggers
12024 : : *
12025 : : * The subroutine for tryAttachPartitionForeignKey handles the deletion of
12026 : : * action triggers for the foreign key constraint.
12027 : : *
12028 : : * If valid confrelid and conrelid values are not provided, the respective
12029 : : * trigger check will be skipped, and the trigger will be considered for
12030 : : * removal.
12031 : : */
12032 : : static void
12033 : 39 : DropForeignKeyConstraintTriggers(Relation trigrel, Oid conoid, Oid confrelid,
12034 : : Oid conrelid)
12035 : : {
12036 : 39 : ScanKeyData key;
12037 : 39 : SysScanDesc scan;
12038 : 39 : HeapTuple trigtup;
12039 : :
12040 : 39 : ScanKeyInit(&key,
12041 : : Anum_pg_trigger_tgconstraint,
12042 : : BTEqualStrategyNumber, F_OIDEQ,
12043 : 39 : ObjectIdGetDatum(conoid));
12044 : 39 : scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
12045 : : NULL, 1, &key);
12046 [ + + ]: 169 : while ((trigtup = systable_getnext(scan)) != NULL)
12047 : : {
12048 : 130 : Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
12049 : 130 : ObjectAddress trigger;
12050 : :
12051 : : /* Invalid if trigger is not for a referential integrity constraint */
12052 [ + - ]: 130 : if (!OidIsValid(trgform->tgconstrrelid))
12053 : 0 : continue;
12054 [ + + + + ]: 130 : if (OidIsValid(conrelid) && trgform->tgconstrrelid != conrelid)
12055 : 50 : continue;
12056 [ + + + - ]: 80 : if (OidIsValid(confrelid) && trgform->tgrelid != confrelid)
12057 : 0 : continue;
12058 : :
12059 : : /* We should be dropping trigger related to foreign key constraint */
12060 [ + + + + : 80 : Assert(trgform->tgfoid == F_RI_FKEY_CHECK_INS ||
+ + + + +
- + - + -
+ - + - +
- + + -
+ ]
12061 : : trgform->tgfoid == F_RI_FKEY_CHECK_UPD ||
12062 : : trgform->tgfoid == F_RI_FKEY_CASCADE_DEL ||
12063 : : trgform->tgfoid == F_RI_FKEY_CASCADE_UPD ||
12064 : : trgform->tgfoid == F_RI_FKEY_RESTRICT_DEL ||
12065 : : trgform->tgfoid == F_RI_FKEY_RESTRICT_UPD ||
12066 : : trgform->tgfoid == F_RI_FKEY_SETNULL_DEL ||
12067 : : trgform->tgfoid == F_RI_FKEY_SETNULL_UPD ||
12068 : : trgform->tgfoid == F_RI_FKEY_SETDEFAULT_DEL ||
12069 : : trgform->tgfoid == F_RI_FKEY_SETDEFAULT_UPD ||
12070 : : trgform->tgfoid == F_RI_FKEY_NOACTION_DEL ||
12071 : : trgform->tgfoid == F_RI_FKEY_NOACTION_UPD);
12072 : :
12073 : : /*
12074 : : * The constraint is originally set up to contain this trigger as an
12075 : : * implementation object, so there's a dependency record that links
12076 : : * the two; however, since the trigger is no longer needed, we remove
12077 : : * the dependency link in order to be able to drop the trigger while
12078 : : * keeping the constraint intact.
12079 : : */
12080 : 80 : deleteDependencyRecordsFor(TriggerRelationId,
12081 : 80 : trgform->oid,
12082 : : false);
12083 : : /* make dependency deletion visible to performDeletion */
12084 : 80 : CommandCounterIncrement();
12085 : 80 : ObjectAddressSet(trigger, TriggerRelationId,
12086 : : trgform->oid);
12087 : 80 : performDeletion(&trigger, DROP_RESTRICT, 0);
12088 : : /* make trigger drop visible, in case the loop iterates */
12089 : 80 : CommandCounterIncrement();
12090 [ - + + ]: 130 : }
12091 : :
12092 : 39 : systable_endscan(scan);
12093 : 39 : }
12094 : :
12095 : : /*
12096 : : * GetForeignKeyActionTriggers
12097 : : * Returns delete and update "action" triggers of the given relation
12098 : : * belonging to the given constraint
12099 : : */
12100 : : static void
12101 : 43 : GetForeignKeyActionTriggers(Relation trigrel,
12102 : : Oid conoid, Oid confrelid, Oid conrelid,
12103 : : Oid *deleteTriggerOid,
12104 : : Oid *updateTriggerOid)
12105 : : {
12106 : 43 : ScanKeyData key;
12107 : 43 : SysScanDesc scan;
12108 : 43 : HeapTuple trigtup;
12109 : :
12110 : 43 : *deleteTriggerOid = *updateTriggerOid = InvalidOid;
12111 : 43 : ScanKeyInit(&key,
12112 : : Anum_pg_trigger_tgconstraint,
12113 : : BTEqualStrategyNumber, F_OIDEQ,
12114 : 43 : ObjectIdGetDatum(conoid));
12115 : :
12116 : 43 : scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
12117 : : NULL, 1, &key);
12118 [ + + ]: 193 : while ((trigtup = systable_getnext(scan)) != NULL)
12119 : : {
12120 : 150 : Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
12121 : :
12122 [ + + ]: 150 : if (trgform->tgconstrrelid != conrelid)
12123 : 46 : continue;
12124 [ - + ]: 104 : if (trgform->tgrelid != confrelid)
12125 : 0 : continue;
12126 : : /* Only ever look at "action" triggers on the PK side. */
12127 [ + + ]: 104 : if (RI_FKey_trigger_type(trgform->tgfoid) != RI_TRIGGER_PK)
12128 : 18 : continue;
12129 [ + + ]: 86 : if (TRIGGER_FOR_DELETE(trgform->tgtype))
12130 : : {
12131 [ - + ]: 43 : Assert(*deleteTriggerOid == InvalidOid);
12132 : 43 : *deleteTriggerOid = trgform->oid;
12133 : 43 : }
12134 [ - + ]: 43 : else if (TRIGGER_FOR_UPDATE(trgform->tgtype))
12135 : : {
12136 [ - + ]: 43 : Assert(*updateTriggerOid == InvalidOid);
12137 : 43 : *updateTriggerOid = trgform->oid;
12138 : 43 : }
12139 : : #ifndef USE_ASSERT_CHECKING
12140 : : /* In an assert-enabled build, continue looking to find duplicates */
12141 : : if (OidIsValid(*deleteTriggerOid) && OidIsValid(*updateTriggerOid))
12142 : : break;
12143 : : #endif
12144 [ - + + ]: 150 : }
12145 : :
12146 [ + - ]: 43 : if (!OidIsValid(*deleteTriggerOid))
12147 [ # # # # ]: 0 : elog(ERROR, "could not find ON DELETE action trigger of foreign key constraint %u",
12148 : : conoid);
12149 [ + - ]: 43 : if (!OidIsValid(*updateTriggerOid))
12150 [ # # # # ]: 0 : elog(ERROR, "could not find ON UPDATE action trigger of foreign key constraint %u",
12151 : : conoid);
12152 : :
12153 : 43 : systable_endscan(scan);
12154 : 43 : }
12155 : :
12156 : : /*
12157 : : * GetForeignKeyCheckTriggers
12158 : : * Returns insert and update "check" triggers of the given relation
12159 : : * belonging to the given constraint
12160 : : */
12161 : : static void
12162 : 134 : GetForeignKeyCheckTriggers(Relation trigrel,
12163 : : Oid conoid, Oid confrelid, Oid conrelid,
12164 : : Oid *insertTriggerOid,
12165 : : Oid *updateTriggerOid)
12166 : : {
12167 : 134 : ScanKeyData key;
12168 : 134 : SysScanDesc scan;
12169 : 134 : HeapTuple trigtup;
12170 : :
12171 : 134 : *insertTriggerOid = *updateTriggerOid = InvalidOid;
12172 : 134 : ScanKeyInit(&key,
12173 : : Anum_pg_trigger_tgconstraint,
12174 : : BTEqualStrategyNumber, F_OIDEQ,
12175 : 134 : ObjectIdGetDatum(conoid));
12176 : :
12177 : 134 : scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
12178 : : NULL, 1, &key);
12179 [ + + ]: 564 : while ((trigtup = systable_getnext(scan)) != NULL)
12180 : : {
12181 : 430 : Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
12182 : :
12183 [ + + ]: 430 : if (trgform->tgconstrrelid != confrelid)
12184 : 144 : continue;
12185 [ - + ]: 286 : if (trgform->tgrelid != conrelid)
12186 : 0 : continue;
12187 : : /* Only ever look at "check" triggers on the FK side. */
12188 [ + + ]: 286 : if (RI_FKey_trigger_type(trgform->tgfoid) != RI_TRIGGER_FK)
12189 : 18 : continue;
12190 [ + + ]: 268 : if (TRIGGER_FOR_INSERT(trgform->tgtype))
12191 : : {
12192 [ - + ]: 134 : Assert(*insertTriggerOid == InvalidOid);
12193 : 134 : *insertTriggerOid = trgform->oid;
12194 : 134 : }
12195 [ - + ]: 134 : else if (TRIGGER_FOR_UPDATE(trgform->tgtype))
12196 : : {
12197 [ - + ]: 134 : Assert(*updateTriggerOid == InvalidOid);
12198 : 134 : *updateTriggerOid = trgform->oid;
12199 : 134 : }
12200 : : #ifndef USE_ASSERT_CHECKING
12201 : : /* In an assert-enabled build, continue looking to find duplicates. */
12202 : : if (OidIsValid(*insertTriggerOid) && OidIsValid(*updateTriggerOid))
12203 : : break;
12204 : : #endif
12205 [ - + + ]: 430 : }
12206 : :
12207 [ + - ]: 134 : if (!OidIsValid(*insertTriggerOid))
12208 [ # # # # ]: 0 : elog(ERROR, "could not find ON INSERT check triggers of foreign key constraint %u",
12209 : : conoid);
12210 [ + - ]: 134 : if (!OidIsValid(*updateTriggerOid))
12211 [ # # # # ]: 0 : elog(ERROR, "could not find ON UPDATE check triggers of foreign key constraint %u",
12212 : : conoid);
12213 : :
12214 : 134 : systable_endscan(scan);
12215 : 134 : }
12216 : :
12217 : : /*
12218 : : * ALTER TABLE ALTER CONSTRAINT
12219 : : *
12220 : : * Update the attributes of a constraint.
12221 : : *
12222 : : * Currently only works for Foreign Key and not null constraints.
12223 : : *
12224 : : * If the constraint is modified, returns its address; otherwise, return
12225 : : * InvalidObjectAddress.
12226 : : */
12227 : : static ObjectAddress
12228 : 49 : ATExecAlterConstraint(List **wqueue, Relation rel, ATAlterConstraint *cmdcon,
12229 : : bool recurse, LOCKMODE lockmode)
12230 : : {
12231 : 49 : Relation conrel;
12232 : 49 : Relation tgrel;
12233 : 49 : SysScanDesc scan;
12234 : 49 : ScanKeyData skey[3];
12235 : 49 : HeapTuple contuple;
12236 : 49 : Form_pg_constraint currcon;
12237 : : ObjectAddress address;
12238 : :
12239 : : /*
12240 : : * Disallow altering ONLY a partitioned table, as it would make no sense.
12241 : : * This is okay for legacy inheritance.
12242 : : */
12243 [ + + + - ]: 49 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
12244 [ # # # # ]: 0 : ereport(ERROR,
12245 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
12246 : : errmsg("constraint must be altered in child tables too"),
12247 : : errhint("Do not specify the ONLY keyword."));
12248 : :
12249 : :
12250 : 49 : conrel = table_open(ConstraintRelationId, RowExclusiveLock);
12251 : 49 : tgrel = table_open(TriggerRelationId, RowExclusiveLock);
12252 : :
12253 : : /*
12254 : : * Find and check the target constraint
12255 : : */
12256 : 98 : ScanKeyInit(&skey[0],
12257 : : Anum_pg_constraint_conrelid,
12258 : : BTEqualStrategyNumber, F_OIDEQ,
12259 : 49 : ObjectIdGetDatum(RelationGetRelid(rel)));
12260 : 98 : ScanKeyInit(&skey[1],
12261 : : Anum_pg_constraint_contypid,
12262 : : BTEqualStrategyNumber, F_OIDEQ,
12263 : 49 : ObjectIdGetDatum(InvalidOid));
12264 : 98 : ScanKeyInit(&skey[2],
12265 : : Anum_pg_constraint_conname,
12266 : : BTEqualStrategyNumber, F_NAMEEQ,
12267 : 49 : CStringGetDatum(cmdcon->conname));
12268 : 98 : scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId,
12269 : 49 : true, NULL, 3, skey);
12270 : :
12271 : : /* There can be at most one matching row */
12272 [ + + ]: 49 : if (!HeapTupleIsValid(contuple = systable_getnext(scan)))
12273 [ + - + - ]: 1 : ereport(ERROR,
12274 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
12275 : : errmsg("constraint \"%s\" of relation \"%s\" does not exist",
12276 : : cmdcon->conname, RelationGetRelationName(rel))));
12277 : :
12278 : 48 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
12279 [ + + + - ]: 48 : if (cmdcon->alterDeferrability && currcon->contype != CONSTRAINT_FOREIGN)
12280 [ # # # # ]: 0 : ereport(ERROR,
12281 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
12282 : : errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint",
12283 : : cmdcon->conname, RelationGetRelationName(rel))));
12284 [ + + + + ]: 48 : if (cmdcon->alterEnforceability && currcon->contype != CONSTRAINT_FOREIGN)
12285 [ + - + - ]: 2 : ereport(ERROR,
12286 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
12287 : : errmsg("cannot alter enforceability of constraint \"%s\" of relation \"%s\"",
12288 : : cmdcon->conname, RelationGetRelationName(rel))));
12289 [ + + + + ]: 46 : if (cmdcon->alterInheritability &&
12290 : 15 : currcon->contype != CONSTRAINT_NOTNULL)
12291 [ + - + - ]: 4 : ereport(ERROR,
12292 : : errcode(ERRCODE_WRONG_OBJECT_TYPE),
12293 : : errmsg("constraint \"%s\" of relation \"%s\" is not a not-null constraint",
12294 : : cmdcon->conname, RelationGetRelationName(rel)));
12295 : :
12296 : : /* Refuse to modify inheritability of inherited constraints */
12297 [ + + ]: 42 : if (cmdcon->alterInheritability &&
12298 [ + + + + ]: 11 : cmdcon->noinherit && currcon->coninhcount > 0)
12299 [ + - + - ]: 1 : ereport(ERROR,
12300 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
12301 : : errmsg("cannot alter inherited constraint \"%s\" on relation \"%s\"",
12302 : : NameStr(currcon->conname),
12303 : : RelationGetRelationName(rel)));
12304 : :
12305 : : /*
12306 : : * If it's not the topmost constraint, raise an error.
12307 : : *
12308 : : * Altering a non-topmost constraint leaves some triggers untouched, since
12309 : : * they are not directly connected to this constraint; also, pg_dump would
12310 : : * ignore the deferrability status of the individual constraint, since it
12311 : : * only dumps topmost constraints. Avoid these problems by refusing this
12312 : : * operation and telling the user to alter the parent constraint instead.
12313 : : */
12314 [ + + ]: 41 : if (OidIsValid(currcon->conparentid))
12315 : : {
12316 : 2 : HeapTuple tp;
12317 : 2 : Oid parent = currcon->conparentid;
12318 : 2 : char *ancestorname = NULL;
12319 : 2 : char *ancestortable = NULL;
12320 : :
12321 : : /* Loop to find the topmost constraint */
12322 [ + + ]: 6 : while (HeapTupleIsValid(tp = SearchSysCache1(CONSTROID, ObjectIdGetDatum(parent))))
12323 : : {
12324 : 4 : Form_pg_constraint contup = (Form_pg_constraint) GETSTRUCT(tp);
12325 : :
12326 : : /* If no parent, this is the constraint we want */
12327 [ + + ]: 4 : if (!OidIsValid(contup->conparentid))
12328 : : {
12329 : 2 : ancestorname = pstrdup(NameStr(contup->conname));
12330 : 2 : ancestortable = get_rel_name(contup->conrelid);
12331 : 2 : ReleaseSysCache(tp);
12332 : 2 : break;
12333 : : }
12334 : :
12335 : 2 : parent = contup->conparentid;
12336 : 2 : ReleaseSysCache(tp);
12337 [ - + ]: 4 : }
12338 : :
12339 [ + - + - : 2 : ereport(ERROR,
+ - - + ]
12340 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
12341 : : errmsg("cannot alter constraint \"%s\" on relation \"%s\"",
12342 : : cmdcon->conname, RelationGetRelationName(rel)),
12343 : : ancestorname && ancestortable ?
12344 : : errdetail("Constraint \"%s\" is derived from constraint \"%s\" of relation \"%s\".",
12345 : : cmdcon->conname, ancestorname, ancestortable) : 0,
12346 : : errhint("You may alter the constraint it derives from instead.")));
12347 : 0 : }
12348 : :
12349 : 39 : address = InvalidObjectAddress;
12350 : :
12351 : : /*
12352 : : * Do the actual catalog work, and recurse if necessary.
12353 : : */
12354 [ + + + + ]: 78 : if (ATExecAlterConstraintInternal(wqueue, cmdcon, conrel, tgrel, rel,
12355 : 39 : contuple, recurse, lockmode))
12356 : 37 : ObjectAddressSet(address, ConstraintRelationId, currcon->oid);
12357 : :
12358 : 39 : systable_endscan(scan);
12359 : :
12360 : 39 : table_close(tgrel, RowExclusiveLock);
12361 : 39 : table_close(conrel, RowExclusiveLock);
12362 : :
12363 : : return address;
12364 : 39 : }
12365 : :
12366 : : /*
12367 : : * A subroutine of ATExecAlterConstraint that calls the respective routines for
12368 : : * altering constraint's enforceability, deferrability or inheritability.
12369 : : */
12370 : : static bool
12371 : 39 : ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon,
12372 : : Relation conrel, Relation tgrel, Relation rel,
12373 : : HeapTuple contuple, bool recurse,
12374 : : LOCKMODE lockmode)
12375 : : {
12376 : 39 : Form_pg_constraint currcon;
12377 : 39 : bool changed = false;
12378 : 39 : List *otherrelids = NIL;
12379 : :
12380 : 39 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
12381 : :
12382 : : /*
12383 : : * Do the catalog work for the enforceability or deferrability change,
12384 : : * recurse if necessary.
12385 : : *
12386 : : * Note that even if deferrability is requested to be altered along with
12387 : : * enforceability, we don't need to explicitly update multiple entries in
12388 : : * pg_trigger related to deferrability.
12389 : : *
12390 : : * Modifying enforceability involves either creating or dropping the
12391 : : * trigger, during which the deferrability setting will be adjusted
12392 : : * automatically.
12393 : : */
12394 [ + + + + ]: 39 : if (cmdcon->alterEnforceability &&
12395 : 26 : ATExecAlterConstrEnforceability(wqueue, cmdcon, conrel, tgrel,
12396 : 13 : currcon->conrelid, currcon->confrelid,
12397 : 13 : contuple, lockmode, InvalidOid,
12398 : : InvalidOid, InvalidOid, InvalidOid))
12399 : 12 : changed = true;
12400 : :
12401 [ + + - + ]: 27 : else if (cmdcon->alterDeferrability &&
12402 : 32 : ATExecAlterConstrDeferrability(wqueue, cmdcon, conrel, tgrel, rel,
12403 : 16 : contuple, recurse, &otherrelids,
12404 : 16 : lockmode))
12405 : : {
12406 : : /*
12407 : : * AlterConstrUpdateConstraintEntry already invalidated relcache for
12408 : : * the relations having the constraint itself; here we also invalidate
12409 : : * for relations that have any triggers that are part of the
12410 : : * constraint.
12411 : : */
12412 [ + + + + : 51 : foreach_oid(relid, otherrelids)
+ + + + ]
12413 : 35 : CacheInvalidateRelcacheByRelid(relid);
12414 : :
12415 : 16 : changed = true;
12416 : 16 : }
12417 : :
12418 : : /*
12419 : : * Do the catalog work for the inheritability change.
12420 : : */
12421 [ + + + + ]: 39 : if (cmdcon->alterInheritability &&
12422 : 20 : ATExecAlterConstrInheritability(wqueue, cmdcon, conrel, rel, contuple,
12423 : 10 : lockmode))
12424 : 9 : changed = true;
12425 : :
12426 : 78 : return changed;
12427 : 39 : }
12428 : :
12429 : : /*
12430 : : * Returns true if the constraint's enforceability is altered.
12431 : : *
12432 : : * Depending on whether the constraint is being set to ENFORCED or NOT
12433 : : * ENFORCED, it creates or drops the trigger accordingly.
12434 : : *
12435 : : * Note that we must recurse even when trying to change a constraint to not
12436 : : * enforced if it is already not enforced, in case descendant constraints
12437 : : * might be enforced and need to be changed to not enforced. Conversely, we
12438 : : * should do nothing if a constraint is being set to enforced and is already
12439 : : * enforced, as descendant constraints cannot be different in that case.
12440 : : */
12441 : : static bool
12442 : 30 : ATExecAlterConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon,
12443 : : Relation conrel, Relation tgrel,
12444 : : Oid fkrelid, Oid pkrelid,
12445 : : HeapTuple contuple, LOCKMODE lockmode,
12446 : : Oid ReferencedParentDelTrigger,
12447 : : Oid ReferencedParentUpdTrigger,
12448 : : Oid ReferencingParentInsTrigger,
12449 : : Oid ReferencingParentUpdTrigger)
12450 : : {
12451 : 30 : Form_pg_constraint currcon;
12452 : 30 : Oid conoid;
12453 : 30 : Relation rel;
12454 : 30 : bool changed = false;
12455 : :
12456 : : /* Since this function recurses, it could be driven to stack overflow */
12457 : 30 : check_stack_depth();
12458 : :
12459 [ + - ]: 30 : Assert(cmdcon->alterEnforceability);
12460 : :
12461 : 30 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
12462 : 30 : conoid = currcon->oid;
12463 : :
12464 : : /* Should be foreign key constraint */
12465 [ + - ]: 30 : Assert(currcon->contype == CONSTRAINT_FOREIGN);
12466 : :
12467 : 30 : rel = table_open(currcon->conrelid, lockmode);
12468 : :
12469 [ + + ]: 30 : if (currcon->conenforced != cmdcon->is_enforced)
12470 : : {
12471 : 29 : AlterConstrUpdateConstraintEntry(cmdcon, conrel, contuple);
12472 : 29 : changed = true;
12473 : 29 : }
12474 : :
12475 : : /* Drop triggers */
12476 [ + + ]: 30 : if (!cmdcon->is_enforced)
12477 : : {
12478 : : /*
12479 : : * When setting a constraint to NOT ENFORCED, the constraint triggers
12480 : : * need to be dropped. Therefore, we must process the child relations
12481 : : * first, followed by the parent, to account for dependencies.
12482 : : */
12483 [ + + - + ]: 12 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
12484 : 9 : get_rel_relkind(currcon->confrelid) == RELKIND_PARTITIONED_TABLE)
12485 : 6 : AlterConstrEnforceabilityRecurse(wqueue, cmdcon, conrel, tgrel,
12486 : 3 : fkrelid, pkrelid, contuple,
12487 : 3 : lockmode, InvalidOid, InvalidOid,
12488 : : InvalidOid, InvalidOid);
12489 : :
12490 : : /* Drop all the triggers */
12491 : 12 : DropForeignKeyConstraintTriggers(tgrel, conoid, InvalidOid, InvalidOid);
12492 : 12 : }
12493 [ - + ]: 18 : else if (changed) /* Create triggers */
12494 : : {
12495 : 54 : Oid ReferencedDelTriggerOid = InvalidOid,
12496 : 18 : ReferencedUpdTriggerOid = InvalidOid,
12497 : 18 : ReferencingInsTriggerOid = InvalidOid,
12498 : 18 : ReferencingUpdTriggerOid = InvalidOid;
12499 : :
12500 : : /* Prepare the minimal information required for trigger creation. */
12501 : 18 : Constraint *fkconstraint = makeNode(Constraint);
12502 : :
12503 : 18 : fkconstraint->conname = pstrdup(NameStr(currcon->conname));
12504 : 18 : fkconstraint->fk_matchtype = currcon->confmatchtype;
12505 : 18 : fkconstraint->fk_upd_action = currcon->confupdtype;
12506 : 18 : fkconstraint->fk_del_action = currcon->confdeltype;
12507 : :
12508 : : /* Create referenced triggers */
12509 [ + + ]: 18 : if (currcon->conrelid == fkrelid)
12510 : 22 : createForeignKeyActionTriggers(currcon->conrelid,
12511 : 11 : currcon->confrelid,
12512 : 11 : fkconstraint,
12513 : 11 : conoid,
12514 : 11 : currcon->conindid,
12515 : 11 : ReferencedParentDelTrigger,
12516 : 11 : ReferencedParentUpdTrigger,
12517 : : &ReferencedDelTriggerOid,
12518 : : &ReferencedUpdTriggerOid);
12519 : :
12520 : : /* Create referencing triggers */
12521 [ + + ]: 18 : if (currcon->confrelid == pkrelid)
12522 : 30 : createForeignKeyCheckTriggers(currcon->conrelid,
12523 : 15 : pkrelid,
12524 : 15 : fkconstraint,
12525 : 15 : conoid,
12526 : 15 : currcon->conindid,
12527 : 15 : ReferencingParentInsTrigger,
12528 : 15 : ReferencingParentUpdTrigger,
12529 : : &ReferencingInsTriggerOid,
12530 : : &ReferencingUpdTriggerOid);
12531 : :
12532 : : /*
12533 : : * Tell Phase 3 to check that the constraint is satisfied by existing
12534 : : * rows. Only applies to leaf partitions, and (for constraints that
12535 : : * reference a partitioned table) only if this is not one of the
12536 : : * pg_constraint rows that exist solely to support action triggers.
12537 : : */
12538 [ + + + + ]: 18 : if (rel->rd_rel->relkind == RELKIND_RELATION &&
12539 : 15 : currcon->confrelid == pkrelid)
12540 : : {
12541 : 12 : AlteredTableInfo *tab;
12542 : 12 : NewConstraint *newcon;
12543 : :
12544 : 12 : newcon = palloc0_object(NewConstraint);
12545 : 12 : newcon->name = fkconstraint->conname;
12546 : 12 : newcon->contype = CONSTR_FOREIGN;
12547 : 12 : newcon->refrelid = currcon->confrelid;
12548 : 12 : newcon->refindid = currcon->conindid;
12549 : 12 : newcon->conid = currcon->oid;
12550 : 12 : newcon->qual = (Node *) fkconstraint;
12551 : :
12552 : : /* Find or create work queue entry for this table */
12553 : 12 : tab = ATGetQueueEntry(wqueue, rel);
12554 : 12 : tab->constraints = lappend(tab->constraints, newcon);
12555 : 12 : }
12556 : :
12557 : : /*
12558 : : * If the table at either end of the constraint is partitioned, we
12559 : : * need to recurse and create triggers for each constraint that is a
12560 : : * child of this one.
12561 : : */
12562 [ + + + + ]: 18 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
12563 : 15 : get_rel_relkind(currcon->confrelid) == RELKIND_PARTITIONED_TABLE)
12564 : 10 : AlterConstrEnforceabilityRecurse(wqueue, cmdcon, conrel, tgrel,
12565 : 5 : fkrelid, pkrelid, contuple,
12566 : 5 : lockmode, ReferencedDelTriggerOid,
12567 : 5 : ReferencedUpdTriggerOid,
12568 : 5 : ReferencingInsTriggerOid,
12569 : 5 : ReferencingUpdTriggerOid);
12570 : 18 : }
12571 : :
12572 : 30 : table_close(rel, NoLock);
12573 : :
12574 : 60 : return changed;
12575 : 30 : }
12576 : :
12577 : : /*
12578 : : * Returns true if the constraint's deferrability is altered.
12579 : : *
12580 : : * *otherrelids is appended OIDs of relations containing affected triggers.
12581 : : *
12582 : : * Note that we must recurse even when the values are correct, in case
12583 : : * indirect descendants have had their constraints altered locally.
12584 : : * (This could be avoided if we forbade altering constraints in partitions
12585 : : * but existing releases don't do that.)
12586 : : */
12587 : : static bool
12588 : 27 : ATExecAlterConstrDeferrability(List **wqueue, ATAlterConstraint *cmdcon,
12589 : : Relation conrel, Relation tgrel, Relation rel,
12590 : : HeapTuple contuple, bool recurse,
12591 : : List **otherrelids, LOCKMODE lockmode)
12592 : : {
12593 : 27 : Form_pg_constraint currcon;
12594 : 27 : Oid refrelid;
12595 : 27 : bool changed = false;
12596 : :
12597 : : /* since this function recurses, it could be driven to stack overflow */
12598 : 27 : check_stack_depth();
12599 : :
12600 [ + - ]: 27 : Assert(cmdcon->alterDeferrability);
12601 : :
12602 : 27 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
12603 : 27 : refrelid = currcon->confrelid;
12604 : :
12605 : : /* Should be foreign key constraint */
12606 [ + - ]: 27 : Assert(currcon->contype == CONSTRAINT_FOREIGN);
12607 : :
12608 : : /*
12609 : : * If called to modify a constraint that's already in the desired state,
12610 : : * silently do nothing.
12611 : : */
12612 [ + + + - ]: 27 : if (currcon->condeferrable != cmdcon->deferrable ||
12613 : 1 : currcon->condeferred != cmdcon->initdeferred)
12614 : : {
12615 : 27 : AlterConstrUpdateConstraintEntry(cmdcon, conrel, contuple);
12616 : 27 : changed = true;
12617 : :
12618 : : /*
12619 : : * Now we need to update the multiple entries in pg_trigger that
12620 : : * implement the constraint.
12621 : : */
12622 : 54 : AlterConstrTriggerDeferrability(currcon->oid, tgrel, rel,
12623 : 27 : cmdcon->deferrable,
12624 : 27 : cmdcon->initdeferred, otherrelids);
12625 : 27 : }
12626 : :
12627 : : /*
12628 : : * If the table at either end of the constraint is partitioned, we need to
12629 : : * handle every constraint that is a child of this one.
12630 : : */
12631 [ + - + - : 50 : if (recurse && changed &&
+ + ]
12632 [ + + ]: 27 : (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
12633 : 23 : get_rel_relkind(refrelid) == RELKIND_PARTITIONED_TABLE))
12634 : 14 : AlterConstrDeferrabilityRecurse(wqueue, cmdcon, conrel, tgrel, rel,
12635 : 7 : contuple, recurse, otherrelids,
12636 : 7 : lockmode);
12637 : :
12638 : 54 : return changed;
12639 : 27 : }
12640 : :
12641 : : /*
12642 : : * Returns true if the constraint's inheritability is altered.
12643 : : */
12644 : : static bool
12645 : 9 : ATExecAlterConstrInheritability(List **wqueue, ATAlterConstraint *cmdcon,
12646 : : Relation conrel, Relation rel,
12647 : : HeapTuple contuple, LOCKMODE lockmode)
12648 : : {
12649 : 9 : Form_pg_constraint currcon;
12650 : 9 : AttrNumber colNum;
12651 : 9 : char *colName;
12652 : 9 : List *children;
12653 : :
12654 [ + - ]: 9 : Assert(cmdcon->alterInheritability);
12655 : :
12656 : 9 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
12657 : :
12658 : : /* The current implementation only works for NOT NULL constraints */
12659 [ + - ]: 9 : Assert(currcon->contype == CONSTRAINT_NOTNULL);
12660 : :
12661 : : /*
12662 : : * If called to modify a constraint that's already in the desired state,
12663 : : * silently do nothing.
12664 : : */
12665 [ - + ]: 9 : if (cmdcon->noinherit == currcon->connoinherit)
12666 : 0 : return false;
12667 : :
12668 : 9 : AlterConstrUpdateConstraintEntry(cmdcon, conrel, contuple);
12669 : 9 : CommandCounterIncrement();
12670 : :
12671 : : /* Fetch the column number and name */
12672 : 9 : colNum = extractNotNullColumn(contuple);
12673 : 9 : colName = get_attname(currcon->conrelid, colNum, false);
12674 : :
12675 : : /*
12676 : : * Propagate the change to children. For this subcommand type we don't
12677 : : * recursively affect children, just the immediate level.
12678 : : */
12679 : 18 : children = find_inheritance_children(RelationGetRelid(rel),
12680 : 9 : lockmode);
12681 [ + + + - : 31 : foreach_oid(childoid, children)
+ + + + ]
12682 : : {
12683 : 13 : ObjectAddress addr;
12684 : :
12685 [ + + ]: 13 : if (cmdcon->noinherit)
12686 : : {
12687 : 5 : HeapTuple childtup;
12688 : 5 : Form_pg_constraint childcon;
12689 : :
12690 : 5 : childtup = findNotNullConstraint(childoid, colName);
12691 [ + - ]: 5 : if (!childtup)
12692 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation %u",
12693 : : colName, childoid);
12694 : 5 : childcon = (Form_pg_constraint) GETSTRUCT(childtup);
12695 [ - + ]: 5 : Assert(childcon->coninhcount > 0);
12696 : 5 : childcon->coninhcount--;
12697 : 5 : childcon->conislocal = true;
12698 : 5 : CatalogTupleUpdate(conrel, &childtup->t_self, childtup);
12699 : 5 : heap_freetuple(childtup);
12700 : 5 : }
12701 : : else
12702 : : {
12703 : 8 : Relation childrel = table_open(childoid, NoLock);
12704 : :
12705 : 16 : addr = ATExecSetNotNull(wqueue, childrel, NameStr(currcon->conname),
12706 : 8 : colName, true, true, lockmode);
12707 [ - + ]: 8 : if (OidIsValid(addr.objectId))
12708 : 8 : CommandCounterIncrement();
12709 : 8 : table_close(childrel, NoLock);
12710 : 8 : }
12711 : 22 : }
12712 : :
12713 : 9 : return true;
12714 : 9 : }
12715 : :
12716 : : /*
12717 : : * A subroutine of ATExecAlterConstrDeferrability that updated constraint
12718 : : * trigger's deferrability.
12719 : : *
12720 : : * The arguments to this function have the same meaning as the arguments to
12721 : : * ATExecAlterConstrDeferrability.
12722 : : */
12723 : : static void
12724 : 27 : AlterConstrTriggerDeferrability(Oid conoid, Relation tgrel, Relation rel,
12725 : : bool deferrable, bool initdeferred,
12726 : : List **otherrelids)
12727 : : {
12728 : 27 : HeapTuple tgtuple;
12729 : 27 : ScanKeyData tgkey;
12730 : 27 : SysScanDesc tgscan;
12731 : :
12732 : 27 : ScanKeyInit(&tgkey,
12733 : : Anum_pg_trigger_tgconstraint,
12734 : : BTEqualStrategyNumber, F_OIDEQ,
12735 : 27 : ObjectIdGetDatum(conoid));
12736 : 27 : tgscan = systable_beginscan(tgrel, TriggerConstraintIndexId, true,
12737 : : NULL, 1, &tgkey);
12738 [ + + ]: 105 : while (HeapTupleIsValid(tgtuple = systable_getnext(tgscan)))
12739 : : {
12740 : 78 : Form_pg_trigger tgform = (Form_pg_trigger) GETSTRUCT(tgtuple);
12741 : 78 : Form_pg_trigger copy_tg;
12742 : 78 : HeapTuple tgCopyTuple;
12743 : :
12744 : : /*
12745 : : * Remember OIDs of other relation(s) involved in FK constraint.
12746 : : * (Note: it's likely that we could skip forcing a relcache inval for
12747 : : * other rels that don't have a trigger whose properties change, but
12748 : : * let's be conservative.)
12749 : : */
12750 [ + + ]: 78 : if (tgform->tgrelid != RelationGetRelid(rel))
12751 : 76 : *otherrelids = list_append_unique_oid(*otherrelids,
12752 : 38 : tgform->tgrelid);
12753 : :
12754 : : /*
12755 : : * Update enable status and deferrability of RI_FKey_noaction_del,
12756 : : * RI_FKey_noaction_upd, RI_FKey_check_ins and RI_FKey_check_upd
12757 : : * triggers, but not others; see createForeignKeyActionTriggers and
12758 : : * CreateFKCheckTrigger.
12759 : : */
12760 [ + + ]: 78 : if (tgform->tgfoid != F_RI_FKEY_NOACTION_DEL &&
12761 [ + + ]: 62 : tgform->tgfoid != F_RI_FKEY_NOACTION_UPD &&
12762 [ + + + + ]: 43 : tgform->tgfoid != F_RI_FKEY_CHECK_INS &&
12763 : 23 : tgform->tgfoid != F_RI_FKEY_CHECK_UPD)
12764 : 3 : continue;
12765 : :
12766 : 75 : tgCopyTuple = heap_copytuple(tgtuple);
12767 : 75 : copy_tg = (Form_pg_trigger) GETSTRUCT(tgCopyTuple);
12768 : :
12769 : 75 : copy_tg->tgdeferrable = deferrable;
12770 : 75 : copy_tg->tginitdeferred = initdeferred;
12771 : 75 : CatalogTupleUpdate(tgrel, &tgCopyTuple->t_self, tgCopyTuple);
12772 : :
12773 [ + - ]: 75 : InvokeObjectPostAlterHook(TriggerRelationId, tgform->oid, 0);
12774 : :
12775 : 75 : heap_freetuple(tgCopyTuple);
12776 [ - + + ]: 78 : }
12777 : :
12778 : 27 : systable_endscan(tgscan);
12779 : 27 : }
12780 : :
12781 : : /*
12782 : : * Invokes ATExecAlterConstrEnforceability for each constraint that is a child of
12783 : : * the specified constraint.
12784 : : *
12785 : : * Note that this doesn't handle recursion the normal way, viz. by scanning the
12786 : : * list of child relations and recursing; instead it uses the conparentid
12787 : : * relationships. This may need to be reconsidered.
12788 : : *
12789 : : * The arguments to this function have the same meaning as the arguments to
12790 : : * ATExecAlterConstrEnforceability.
12791 : : */
12792 : : static void
12793 : 8 : AlterConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
12794 : : Relation conrel, Relation tgrel,
12795 : : Oid fkrelid, Oid pkrelid,
12796 : : HeapTuple contuple, LOCKMODE lockmode,
12797 : : Oid ReferencedParentDelTrigger,
12798 : : Oid ReferencedParentUpdTrigger,
12799 : : Oid ReferencingParentInsTrigger,
12800 : : Oid ReferencingParentUpdTrigger)
12801 : : {
12802 : 8 : Form_pg_constraint currcon;
12803 : 8 : Oid conoid;
12804 : 8 : ScanKeyData pkey;
12805 : 8 : SysScanDesc pscan;
12806 : 8 : HeapTuple childtup;
12807 : :
12808 : 8 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
12809 : 8 : conoid = currcon->oid;
12810 : :
12811 : 8 : ScanKeyInit(&pkey,
12812 : : Anum_pg_constraint_conparentid,
12813 : : BTEqualStrategyNumber, F_OIDEQ,
12814 : 8 : ObjectIdGetDatum(conoid));
12815 : :
12816 : 8 : pscan = systable_beginscan(conrel, ConstraintParentIndexId,
12817 : : true, NULL, 1, &pkey);
12818 : :
12819 [ + + ]: 25 : while (HeapTupleIsValid(childtup = systable_getnext(pscan)))
12820 : 34 : ATExecAlterConstrEnforceability(wqueue, cmdcon, conrel, tgrel, fkrelid,
12821 : 17 : pkrelid, childtup, lockmode,
12822 : 17 : ReferencedParentDelTrigger,
12823 : 17 : ReferencedParentUpdTrigger,
12824 : 17 : ReferencingParentInsTrigger,
12825 : 17 : ReferencingParentUpdTrigger);
12826 : :
12827 : 8 : systable_endscan(pscan);
12828 : 8 : }
12829 : :
12830 : : /*
12831 : : * Invokes ATExecAlterConstrDeferrability for each constraint that is a child of
12832 : : * the specified constraint.
12833 : : *
12834 : : * Note that this doesn't handle recursion the normal way, viz. by scanning the
12835 : : * list of child relations and recursing; instead it uses the conparentid
12836 : : * relationships. This may need to be reconsidered.
12837 : : *
12838 : : * The arguments to this function have the same meaning as the arguments to
12839 : : * ATExecAlterConstrDeferrability.
12840 : : */
12841 : : static void
12842 : 7 : AlterConstrDeferrabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
12843 : : Relation conrel, Relation tgrel, Relation rel,
12844 : : HeapTuple contuple, bool recurse,
12845 : : List **otherrelids, LOCKMODE lockmode)
12846 : : {
12847 : 7 : Form_pg_constraint currcon;
12848 : 7 : Oid conoid;
12849 : 7 : ScanKeyData pkey;
12850 : 7 : SysScanDesc pscan;
12851 : 7 : HeapTuple childtup;
12852 : :
12853 : 7 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
12854 : 7 : conoid = currcon->oid;
12855 : :
12856 : 7 : ScanKeyInit(&pkey,
12857 : : Anum_pg_constraint_conparentid,
12858 : : BTEqualStrategyNumber, F_OIDEQ,
12859 : 7 : ObjectIdGetDatum(conoid));
12860 : :
12861 : 7 : pscan = systable_beginscan(conrel, ConstraintParentIndexId,
12862 : : true, NULL, 1, &pkey);
12863 : :
12864 [ + + ]: 18 : while (HeapTupleIsValid(childtup = systable_getnext(pscan)))
12865 : : {
12866 : 11 : Form_pg_constraint childcon = (Form_pg_constraint) GETSTRUCT(childtup);
12867 : 11 : Relation childrel;
12868 : :
12869 : 11 : childrel = table_open(childcon->conrelid, lockmode);
12870 : :
12871 : 22 : ATExecAlterConstrDeferrability(wqueue, cmdcon, conrel, tgrel, childrel,
12872 : 11 : childtup, recurse, otherrelids, lockmode);
12873 : 11 : table_close(childrel, NoLock);
12874 : 11 : }
12875 : :
12876 : 7 : systable_endscan(pscan);
12877 : 7 : }
12878 : :
12879 : : /*
12880 : : * Update the constraint entry for the given ATAlterConstraint command, and
12881 : : * invoke the appropriate hooks.
12882 : : */
12883 : : static void
12884 : 66 : AlterConstrUpdateConstraintEntry(ATAlterConstraint *cmdcon, Relation conrel,
12885 : : HeapTuple contuple)
12886 : : {
12887 : 66 : HeapTuple copyTuple;
12888 : 66 : Form_pg_constraint copy_con;
12889 : :
12890 [ + + + + : 66 : Assert(cmdcon->alterEnforceability || cmdcon->alterDeferrability ||
+ - ]
12891 : : cmdcon->alterInheritability);
12892 : :
12893 : 66 : copyTuple = heap_copytuple(contuple);
12894 : 66 : copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
12895 : :
12896 [ + + ]: 66 : if (cmdcon->alterEnforceability)
12897 : : {
12898 : 29 : copy_con->conenforced = cmdcon->is_enforced;
12899 : :
12900 : : /*
12901 : : * NB: The convalidated status is irrelevant when the constraint is
12902 : : * set to NOT ENFORCED, but for consistency, it should still be set
12903 : : * appropriately. Similarly, if the constraint is later changed to
12904 : : * ENFORCED, validation will be performed during phase 3, so it makes
12905 : : * sense to mark it as valid in that case.
12906 : : */
12907 : 29 : copy_con->convalidated = cmdcon->is_enforced;
12908 : 29 : }
12909 [ + + ]: 66 : if (cmdcon->alterDeferrability)
12910 : : {
12911 : 28 : copy_con->condeferrable = cmdcon->deferrable;
12912 : 28 : copy_con->condeferred = cmdcon->initdeferred;
12913 : 28 : }
12914 [ + + ]: 66 : if (cmdcon->alterInheritability)
12915 : 10 : copy_con->connoinherit = cmdcon->noinherit;
12916 : :
12917 : 66 : CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);
12918 [ + - ]: 66 : InvokeObjectPostAlterHook(ConstraintRelationId, copy_con->oid, 0);
12919 : :
12920 : : /* Make new constraint flags visible to others */
12921 : 66 : CacheInvalidateRelcacheByRelid(copy_con->conrelid);
12922 : :
12923 : 66 : heap_freetuple(copyTuple);
12924 : 66 : }
12925 : :
12926 : : /*
12927 : : * ALTER TABLE VALIDATE CONSTRAINT
12928 : : *
12929 : : * XXX The reason we handle recursion here rather than at Phase 1 is because
12930 : : * there's no good way to skip recursing when handling foreign keys: there is
12931 : : * no need to lock children in that case, yet we wouldn't be able to avoid
12932 : : * doing so at that level.
12933 : : *
12934 : : * Return value is the address of the validated constraint. If the constraint
12935 : : * was already validated, InvalidObjectAddress is returned.
12936 : : */
12937 : : static ObjectAddress
12938 : 52 : ATExecValidateConstraint(List **wqueue, Relation rel, char *constrName,
12939 : : bool recurse, bool recursing, LOCKMODE lockmode)
12940 : : {
12941 : 52 : Relation conrel;
12942 : 52 : SysScanDesc scan;
12943 : 52 : ScanKeyData skey[3];
12944 : 52 : HeapTuple tuple;
12945 : 52 : Form_pg_constraint con;
12946 : : ObjectAddress address;
12947 : :
12948 : 52 : conrel = table_open(ConstraintRelationId, RowExclusiveLock);
12949 : :
12950 : : /*
12951 : : * Find and check the target constraint
12952 : : */
12953 : 104 : ScanKeyInit(&skey[0],
12954 : : Anum_pg_constraint_conrelid,
12955 : : BTEqualStrategyNumber, F_OIDEQ,
12956 : 52 : ObjectIdGetDatum(RelationGetRelid(rel)));
12957 : 104 : ScanKeyInit(&skey[1],
12958 : : Anum_pg_constraint_contypid,
12959 : : BTEqualStrategyNumber, F_OIDEQ,
12960 : 52 : ObjectIdGetDatum(InvalidOid));
12961 : 104 : ScanKeyInit(&skey[2],
12962 : : Anum_pg_constraint_conname,
12963 : : BTEqualStrategyNumber, F_NAMEEQ,
12964 : 52 : CStringGetDatum(constrName));
12965 : 104 : scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId,
12966 : 52 : true, NULL, 3, skey);
12967 : :
12968 : : /* There can be at most one matching row */
12969 [ + - ]: 52 : if (!HeapTupleIsValid(tuple = systable_getnext(scan)))
12970 [ # # # # ]: 0 : ereport(ERROR,
12971 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
12972 : : errmsg("constraint \"%s\" of relation \"%s\" does not exist",
12973 : : constrName, RelationGetRelationName(rel))));
12974 : :
12975 : 52 : con = (Form_pg_constraint) GETSTRUCT(tuple);
12976 [ + + ]: 52 : if (con->contype != CONSTRAINT_FOREIGN &&
12977 [ + + + - ]: 42 : con->contype != CONSTRAINT_CHECK &&
12978 : 18 : con->contype != CONSTRAINT_NOTNULL)
12979 [ # # # # ]: 0 : ereport(ERROR,
12980 : : errcode(ERRCODE_WRONG_OBJECT_TYPE),
12981 : : errmsg("cannot validate constraint \"%s\" of relation \"%s\"",
12982 : : constrName, RelationGetRelationName(rel)),
12983 : : errdetail("This operation is not supported for this type of constraint."));
12984 : :
12985 [ + + ]: 52 : if (!con->conenforced)
12986 [ + - + - ]: 1 : ereport(ERROR,
12987 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
12988 : : errmsg("cannot validate NOT ENFORCED constraint")));
12989 : :
12990 [ + + ]: 51 : if (!con->convalidated)
12991 : : {
12992 [ + + ]: 48 : if (con->contype == CONSTRAINT_FOREIGN)
12993 : : {
12994 : 18 : QueueFKConstraintValidation(wqueue, conrel, rel, con->confrelid,
12995 : 9 : tuple, lockmode);
12996 : 9 : }
12997 [ + + ]: 39 : else if (con->contype == CONSTRAINT_CHECK)
12998 : : {
12999 : 42 : QueueCheckConstraintValidation(wqueue, conrel, rel, constrName,
13000 : 21 : tuple, recurse, recursing, lockmode);
13001 : 21 : }
13002 [ - + ]: 18 : else if (con->contype == CONSTRAINT_NOTNULL)
13003 : : {
13004 : 36 : QueueNNConstraintValidation(wqueue, conrel, rel,
13005 : 18 : tuple, recurse, recursing, lockmode);
13006 : 18 : }
13007 : :
13008 : 48 : ObjectAddressSet(address, ConstraintRelationId, con->oid);
13009 : 48 : }
13010 : : else
13011 : 3 : address = InvalidObjectAddress; /* already validated */
13012 : :
13013 : 51 : systable_endscan(scan);
13014 : :
13015 : 51 : table_close(conrel, RowExclusiveLock);
13016 : :
13017 : : return address;
13018 : 51 : }
13019 : :
13020 : : /*
13021 : : * QueueFKConstraintValidation
13022 : : *
13023 : : * Add an entry to the wqueue to validate the given foreign key constraint in
13024 : : * Phase 3 and update the convalidated field in the pg_constraint catalog
13025 : : * for the specified relation and all its children.
13026 : : */
13027 : : static void
13028 : 22 : QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation fkrel,
13029 : : Oid pkrelid, HeapTuple contuple, LOCKMODE lockmode)
13030 : : {
13031 : 22 : Form_pg_constraint con;
13032 : 22 : AlteredTableInfo *tab;
13033 : 22 : HeapTuple copyTuple;
13034 : 22 : Form_pg_constraint copy_con;
13035 : :
13036 : 22 : con = (Form_pg_constraint) GETSTRUCT(contuple);
13037 [ + - ]: 22 : Assert(con->contype == CONSTRAINT_FOREIGN);
13038 [ + - ]: 22 : Assert(!con->convalidated);
13039 : :
13040 : : /*
13041 : : * Add the validation to phase 3's queue; not needed for partitioned
13042 : : * tables themselves, only for their partitions.
13043 : : *
13044 : : * When the referenced table (pkrelid) is partitioned, the referencing
13045 : : * table (fkrel) has one pg_constraint row pointing to each partition
13046 : : * thereof. These rows are there only to support action triggers and no
13047 : : * table scan is needed, therefore skip this for them as well.
13048 : : */
13049 [ + + + + ]: 22 : if (fkrel->rd_rel->relkind == RELKIND_RELATION &&
13050 : 14 : con->confrelid == pkrelid)
13051 : : {
13052 : 11 : NewConstraint *newcon;
13053 : 11 : Constraint *fkconstraint;
13054 : :
13055 : : /* Queue validation for phase 3 */
13056 : 11 : fkconstraint = makeNode(Constraint);
13057 : : /* for now this is all we need */
13058 : 11 : fkconstraint->conname = pstrdup(NameStr(con->conname));
13059 : :
13060 : 11 : newcon = palloc0_object(NewConstraint);
13061 : 11 : newcon->name = fkconstraint->conname;
13062 : 11 : newcon->contype = CONSTR_FOREIGN;
13063 : 11 : newcon->refrelid = con->confrelid;
13064 : 11 : newcon->refindid = con->conindid;
13065 : 11 : newcon->conid = con->oid;
13066 : 11 : newcon->qual = (Node *) fkconstraint;
13067 : :
13068 : : /* Find or create work queue entry for this table */
13069 : 11 : tab = ATGetQueueEntry(wqueue, fkrel);
13070 : 11 : tab->constraints = lappend(tab->constraints, newcon);
13071 : 11 : }
13072 : :
13073 : : /*
13074 : : * If the table at either end of the constraint is partitioned, we need to
13075 : : * recurse and handle every unvalidated constraint that is a child of this
13076 : : * constraint.
13077 : : */
13078 [ + + + + ]: 22 : if (fkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
13079 : 14 : get_rel_relkind(con->confrelid) == RELKIND_PARTITIONED_TABLE)
13080 : : {
13081 : 13 : ScanKeyData pkey;
13082 : 13 : SysScanDesc pscan;
13083 : 13 : HeapTuple childtup;
13084 : :
13085 : 13 : ScanKeyInit(&pkey,
13086 : : Anum_pg_constraint_conparentid,
13087 : : BTEqualStrategyNumber, F_OIDEQ,
13088 : 13 : ObjectIdGetDatum(con->oid));
13089 : :
13090 : 13 : pscan = systable_beginscan(conrel, ConstraintParentIndexId,
13091 : : true, NULL, 1, &pkey);
13092 : :
13093 [ + + ]: 26 : while (HeapTupleIsValid(childtup = systable_getnext(pscan)))
13094 : : {
13095 : 13 : Form_pg_constraint childcon;
13096 : 13 : Relation childrel;
13097 : :
13098 : 13 : childcon = (Form_pg_constraint) GETSTRUCT(childtup);
13099 : :
13100 : : /*
13101 : : * If the child constraint has already been validated, no further
13102 : : * action is required for it or its descendants, as they are all
13103 : : * valid.
13104 : : */
13105 [ + + ]: 13 : if (childcon->convalidated)
13106 : 3 : continue;
13107 : :
13108 : 10 : childrel = table_open(childcon->conrelid, lockmode);
13109 : :
13110 : : /*
13111 : : * NB: Note that pkrelid should be passed as-is during recursion,
13112 : : * as it is required to identify the root referenced table.
13113 : : */
13114 : 20 : QueueFKConstraintValidation(wqueue, conrel, childrel, pkrelid,
13115 : 10 : childtup, lockmode);
13116 : 10 : table_close(childrel, NoLock);
13117 [ - + + ]: 13 : }
13118 : :
13119 : 13 : systable_endscan(pscan);
13120 : 13 : }
13121 : :
13122 : : /*
13123 : : * Now mark the pg_constraint row as validated (even if we didn't check,
13124 : : * notably the ones for partitions on the referenced side).
13125 : : *
13126 : : * We rely on transaction abort to roll back this change if phase 3
13127 : : * ultimately finds violating rows. This is a bit ugly.
13128 : : */
13129 : 22 : copyTuple = heap_copytuple(contuple);
13130 : 22 : copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
13131 : 22 : copy_con->convalidated = true;
13132 : 22 : CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);
13133 : :
13134 [ + - ]: 22 : InvokeObjectPostAlterHook(ConstraintRelationId, con->oid, 0);
13135 : :
13136 : 22 : heap_freetuple(copyTuple);
13137 : 22 : }
13138 : :
13139 : : /*
13140 : : * QueueCheckConstraintValidation
13141 : : *
13142 : : * Add an entry to the wqueue to validate the given check constraint in Phase 3
13143 : : * and update the convalidated field in the pg_constraint catalog for the
13144 : : * specified relation and all its inheriting children.
13145 : : */
13146 : : static void
13147 : 21 : QueueCheckConstraintValidation(List **wqueue, Relation conrel, Relation rel,
13148 : : char *constrName, HeapTuple contuple,
13149 : : bool recurse, bool recursing, LOCKMODE lockmode)
13150 : : {
13151 : 21 : Form_pg_constraint con;
13152 : 21 : AlteredTableInfo *tab;
13153 : 21 : HeapTuple copyTuple;
13154 : 21 : Form_pg_constraint copy_con;
13155 : :
13156 : 21 : List *children = NIL;
13157 : 21 : ListCell *child;
13158 : 21 : NewConstraint *newcon;
13159 : 21 : Datum val;
13160 : 21 : char *conbin;
13161 : :
13162 : 21 : con = (Form_pg_constraint) GETSTRUCT(contuple);
13163 [ + - ]: 21 : Assert(con->contype == CONSTRAINT_CHECK);
13164 : :
13165 : : /*
13166 : : * If we're recursing, the parent has already done this, so skip it. Also,
13167 : : * if the constraint is a NO INHERIT constraint, we shouldn't try to look
13168 : : * for it in the children.
13169 : : */
13170 [ + + + + ]: 21 : if (!recursing && !con->connoinherit)
13171 : 24 : children = find_all_inheritors(RelationGetRelid(rel),
13172 : 12 : lockmode, NULL);
13173 : :
13174 : : /*
13175 : : * For CHECK constraints, we must ensure that we only mark the constraint
13176 : : * as validated on the parent if it's already validated on the children.
13177 : : *
13178 : : * We recurse before validating on the parent, to reduce risk of
13179 : : * deadlocks.
13180 : : */
13181 [ + + + + : 41 : foreach(child, children)
+ + ]
13182 : : {
13183 : 20 : Oid childoid = lfirst_oid(child);
13184 : 20 : Relation childrel;
13185 : :
13186 [ + + ]: 20 : if (childoid == RelationGetRelid(rel))
13187 : 12 : continue;
13188 : :
13189 : : /*
13190 : : * If we are told not to recurse, there had better not be any child
13191 : : * tables, because we can't mark the constraint on the parent valid
13192 : : * unless it is valid for all child tables.
13193 : : */
13194 [ + - ]: 8 : if (!recurse)
13195 [ # # # # ]: 0 : ereport(ERROR,
13196 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
13197 : : errmsg("constraint must be validated on child tables too")));
13198 : :
13199 : : /* find_all_inheritors already got lock */
13200 : 8 : childrel = table_open(childoid, NoLock);
13201 : :
13202 : 16 : ATExecValidateConstraint(wqueue, childrel, constrName, false,
13203 : 8 : true, lockmode);
13204 : 8 : table_close(childrel, NoLock);
13205 [ - + + ]: 20 : }
13206 : :
13207 : : /* Queue validation for phase 3 */
13208 : 21 : newcon = palloc0_object(NewConstraint);
13209 : 21 : newcon->name = constrName;
13210 : 21 : newcon->contype = CONSTR_CHECK;
13211 : 21 : newcon->refrelid = InvalidOid;
13212 : 21 : newcon->refindid = InvalidOid;
13213 : 21 : newcon->conid = con->oid;
13214 : :
13215 : 21 : val = SysCacheGetAttrNotNull(CONSTROID, contuple,
13216 : : Anum_pg_constraint_conbin);
13217 : 21 : conbin = TextDatumGetCString(val);
13218 : 21 : newcon->qual = expand_generated_columns_in_expr(stringToNode(conbin), rel, 1);
13219 : :
13220 : : /* Find or create work queue entry for this table */
13221 : 21 : tab = ATGetQueueEntry(wqueue, rel);
13222 : 21 : tab->constraints = lappend(tab->constraints, newcon);
13223 : :
13224 : : /*
13225 : : * Invalidate relcache so that others see the new validated constraint.
13226 : : */
13227 : 21 : CacheInvalidateRelcache(rel);
13228 : :
13229 : : /*
13230 : : * Now update the catalog, while we have the door open.
13231 : : */
13232 : 21 : copyTuple = heap_copytuple(contuple);
13233 : 21 : copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
13234 : 21 : copy_con->convalidated = true;
13235 : 21 : CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);
13236 : :
13237 [ + - ]: 21 : InvokeObjectPostAlterHook(ConstraintRelationId, con->oid, 0);
13238 : :
13239 : 21 : heap_freetuple(copyTuple);
13240 : 21 : }
13241 : :
13242 : : /*
13243 : : * QueueNNConstraintValidation
13244 : : *
13245 : : * Add an entry to the wqueue to validate the given not-null constraint in
13246 : : * Phase 3 and update the convalidated field in the pg_constraint catalog for
13247 : : * the specified relation and all its inheriting children.
13248 : : */
13249 : : static void
13250 : 18 : QueueNNConstraintValidation(List **wqueue, Relation conrel, Relation rel,
13251 : : HeapTuple contuple, bool recurse, bool recursing,
13252 : : LOCKMODE lockmode)
13253 : : {
13254 : 18 : Form_pg_constraint con;
13255 : 18 : AlteredTableInfo *tab;
13256 : 18 : HeapTuple copyTuple;
13257 : 18 : Form_pg_constraint copy_con;
13258 : 18 : List *children = NIL;
13259 : 18 : AttrNumber attnum;
13260 : 18 : char *colname;
13261 : :
13262 : 18 : con = (Form_pg_constraint) GETSTRUCT(contuple);
13263 [ + - ]: 18 : Assert(con->contype == CONSTRAINT_NOTNULL);
13264 : :
13265 : 18 : attnum = extractNotNullColumn(contuple);
13266 : :
13267 : : /*
13268 : : * If we're recursing, we've already done this for parent, so skip it.
13269 : : * Also, if the constraint is a NO INHERIT constraint, we shouldn't try to
13270 : : * look for it in the children.
13271 : : *
13272 : : * We recurse before validating on the parent, to reduce risk of
13273 : : * deadlocks.
13274 : : */
13275 [ + + - + ]: 18 : if (!recursing && !con->connoinherit)
13276 : 12 : children = find_all_inheritors(RelationGetRelid(rel), lockmode, NULL);
13277 : :
13278 : 18 : colname = get_attname(RelationGetRelid(rel), attnum, false);
13279 [ + + + + : 61 : foreach_oid(childoid, children)
+ + + + ]
13280 : : {
13281 : 25 : Relation childrel;
13282 : 25 : HeapTuple contup;
13283 : 25 : Form_pg_constraint childcon;
13284 : 25 : char *conname;
13285 : :
13286 [ + + ]: 25 : if (childoid == RelationGetRelid(rel))
13287 : 12 : continue;
13288 : :
13289 : : /*
13290 : : * If we are told not to recurse, there had better not be any child
13291 : : * tables, because we can't mark the constraint on the parent valid
13292 : : * unless it is valid for all child tables.
13293 : : */
13294 [ + - ]: 13 : if (!recurse)
13295 [ # # # # ]: 0 : ereport(ERROR,
13296 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
13297 : : errmsg("constraint must be validated on child tables too"));
13298 : :
13299 : : /*
13300 : : * The column on child might have a different attnum, so search by
13301 : : * column name.
13302 : : */
13303 : 13 : contup = findNotNullConstraint(childoid, colname);
13304 [ + - ]: 13 : if (!contup)
13305 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation \"%s\"",
13306 : : colname, get_rel_name(childoid));
13307 : 13 : childcon = (Form_pg_constraint) GETSTRUCT(contup);
13308 [ + + ]: 13 : if (childcon->convalidated)
13309 : 7 : continue;
13310 : :
13311 : : /* find_all_inheritors already got lock */
13312 : 6 : childrel = table_open(childoid, NoLock);
13313 : 6 : conname = pstrdup(NameStr(childcon->conname));
13314 : :
13315 : : /* XXX improve ATExecValidateConstraint API to avoid double search */
13316 : 12 : ATExecValidateConstraint(wqueue, childrel, conname,
13317 : 6 : false, true, lockmode);
13318 : 6 : table_close(childrel, NoLock);
13319 [ - + + ]: 43 : }
13320 : :
13321 : : /* Set attnotnull appropriately without queueing another validation */
13322 : 18 : set_attnotnull(NULL, rel, attnum, true, false);
13323 : :
13324 : 18 : tab = ATGetQueueEntry(wqueue, rel);
13325 : 18 : tab->verify_new_notnull = true;
13326 : :
13327 : : /*
13328 : : * Invalidate relcache so that others see the new validated constraint.
13329 : : */
13330 : 18 : CacheInvalidateRelcache(rel);
13331 : :
13332 : : /*
13333 : : * Now update the catalogs, while we have the door open.
13334 : : */
13335 : 18 : copyTuple = heap_copytuple(contuple);
13336 : 18 : copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
13337 : 18 : copy_con->convalidated = true;
13338 : 18 : CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);
13339 : :
13340 [ - + ]: 18 : InvokeObjectPostAlterHook(ConstraintRelationId, con->oid, 0);
13341 : :
13342 : 18 : heap_freetuple(copyTuple);
13343 : 18 : }
13344 : :
13345 : : /*
13346 : : * transformColumnNameList - transform list of column names
13347 : : *
13348 : : * Lookup each name and return its attnum and, optionally, type and collation
13349 : : * OIDs
13350 : : *
13351 : : * Note: the name of this function suggests that it's general-purpose,
13352 : : * but actually it's only used to look up names appearing in foreign-key
13353 : : * clauses. The error messages would need work to use it in other cases,
13354 : : * and perhaps the validity checks as well.
13355 : : */
13356 : : static int
13357 : 786 : transformColumnNameList(Oid relId, List *colList,
13358 : : int16 *attnums, Oid *atttypids, Oid *attcollids)
13359 : : {
13360 : 786 : ListCell *l;
13361 : 786 : int attnum;
13362 : :
13363 : 786 : attnum = 0;
13364 [ + + + + : 1483 : foreach(l, colList)
+ + ]
13365 : : {
13366 : 708 : char *attname = strVal(lfirst(l));
13367 : 708 : HeapTuple atttuple;
13368 : 708 : Form_pg_attribute attform;
13369 : :
13370 : 708 : atttuple = SearchSysCacheAttName(relId, attname);
13371 [ + + ]: 708 : if (!HeapTupleIsValid(atttuple))
13372 [ + - + - ]: 9 : ereport(ERROR,
13373 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
13374 : : errmsg("column \"%s\" referenced in foreign key constraint does not exist",
13375 : : attname)));
13376 : 699 : attform = (Form_pg_attribute) GETSTRUCT(atttuple);
13377 [ + + ]: 699 : if (attform->attnum < 0)
13378 [ + - + - ]: 2 : ereport(ERROR,
13379 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
13380 : : errmsg("system columns cannot be used in foreign keys")));
13381 [ + - ]: 697 : if (attnum >= INDEX_MAX_KEYS)
13382 [ # # # # ]: 0 : ereport(ERROR,
13383 : : (errcode(ERRCODE_TOO_MANY_COLUMNS),
13384 : : errmsg("cannot have more than %d keys in a foreign key",
13385 : : INDEX_MAX_KEYS)));
13386 : 697 : attnums[attnum] = attform->attnum;
13387 [ + + ]: 697 : if (atttypids != NULL)
13388 : 691 : atttypids[attnum] = attform->atttypid;
13389 [ + + ]: 697 : if (attcollids != NULL)
13390 : 691 : attcollids[attnum] = attform->attcollation;
13391 : 697 : ReleaseSysCache(atttuple);
13392 : 697 : attnum++;
13393 : 697 : }
13394 : :
13395 : 1550 : return attnum;
13396 : 775 : }
13397 : :
13398 : : /*
13399 : : * transformFkeyGetPrimaryKey -
13400 : : *
13401 : : * Look up the names, attnums, types, and collations of the primary key attributes
13402 : : * for the pkrel. Also return the index OID and index opclasses of the
13403 : : * index supporting the primary key. Also return whether the index has
13404 : : * WITHOUT OVERLAPS.
13405 : : *
13406 : : * All parameters except pkrel are output parameters. Also, the function
13407 : : * return value is the number of attributes in the primary key.
13408 : : *
13409 : : * Used when the column list in the REFERENCES specification is omitted.
13410 : : */
13411 : : static int
13412 : 169 : transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
13413 : : List **attnamelist,
13414 : : int16 *attnums, Oid *atttypids, Oid *attcollids,
13415 : : Oid *opclasses, bool *pk_has_without_overlaps)
13416 : : {
13417 : 169 : List *indexoidlist;
13418 : 169 : ListCell *indexoidscan;
13419 : 169 : HeapTuple indexTuple = NULL;
13420 : 169 : Form_pg_index indexStruct = NULL;
13421 : 169 : Datum indclassDatum;
13422 : 169 : oidvector *indclass;
13423 : 169 : int i;
13424 : :
13425 : : /*
13426 : : * Get the list of index OIDs for the table from the relcache, and look up
13427 : : * each one in the pg_index syscache until we find one marked primary key
13428 : : * (hopefully there isn't more than one such). Insist it's valid, too.
13429 : : */
13430 : 169 : *indexOid = InvalidOid;
13431 : :
13432 : 169 : indexoidlist = RelationGetIndexList(pkrel);
13433 : :
13434 [ + - - + : 339 : foreach(indexoidscan, indexoidlist)
+ - ]
13435 : : {
13436 : 170 : Oid indexoid = lfirst_oid(indexoidscan);
13437 : :
13438 : 170 : indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
13439 [ + - ]: 170 : if (!HeapTupleIsValid(indexTuple))
13440 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for index %u", indexoid);
13441 : 170 : indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
13442 [ + + - + ]: 170 : if (indexStruct->indisprimary && indexStruct->indisvalid)
13443 : : {
13444 : : /*
13445 : : * Refuse to use a deferrable primary key. This is per SQL spec,
13446 : : * and there would be a lot of interesting semantic problems if we
13447 : : * tried to allow it.
13448 : : */
13449 [ + - ]: 169 : if (!indexStruct->indimmediate)
13450 [ # # # # ]: 0 : ereport(ERROR,
13451 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
13452 : : errmsg("cannot use a deferrable primary key for referenced table \"%s\"",
13453 : : RelationGetRelationName(pkrel))));
13454 : :
13455 : 169 : *indexOid = indexoid;
13456 : 169 : break;
13457 : : }
13458 : 1 : ReleaseSysCache(indexTuple);
13459 [ + + ]: 170 : }
13460 : :
13461 : 169 : list_free(indexoidlist);
13462 : :
13463 : : /*
13464 : : * Check that we found it
13465 : : */
13466 [ + - ]: 169 : if (!OidIsValid(*indexOid))
13467 [ # # # # ]: 0 : ereport(ERROR,
13468 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
13469 : : errmsg("there is no primary key for referenced table \"%s\"",
13470 : : RelationGetRelationName(pkrel))));
13471 : :
13472 : : /* Must get indclass the hard way */
13473 : 169 : indclassDatum = SysCacheGetAttrNotNull(INDEXRELID, indexTuple,
13474 : : Anum_pg_index_indclass);
13475 : 169 : indclass = (oidvector *) DatumGetPointer(indclassDatum);
13476 : :
13477 : : /*
13478 : : * Now build the list of PK attributes from the indkey definition (we
13479 : : * assume a primary key cannot have expressional elements)
13480 : : */
13481 : 169 : *attnamelist = NIL;
13482 [ + + ]: 411 : for (i = 0; i < indexStruct->indnkeyatts; i++)
13483 : : {
13484 : 242 : int pkattno = indexStruct->indkey.values[i];
13485 : :
13486 : 242 : attnums[i] = pkattno;
13487 : 242 : atttypids[i] = attnumTypeId(pkrel, pkattno);
13488 : 242 : attcollids[i] = attnumCollationId(pkrel, pkattno);
13489 : 242 : opclasses[i] = indclass->values[i];
13490 : 484 : *attnamelist = lappend(*attnamelist,
13491 : 242 : makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno)))));
13492 : 242 : }
13493 : :
13494 : 169 : *pk_has_without_overlaps = indexStruct->indisexclusion;
13495 : :
13496 : 169 : ReleaseSysCache(indexTuple);
13497 : :
13498 : 338 : return i;
13499 : 169 : }
13500 : :
13501 : : /*
13502 : : * transformFkeyCheckAttrs -
13503 : : *
13504 : : * Validate that the 'attnums' columns in the 'pkrel' relation are valid to
13505 : : * reference as part of a foreign key constraint.
13506 : : *
13507 : : * Returns the OID of the unique index supporting the constraint and
13508 : : * populates the caller-provided 'opclasses' array with the opclasses
13509 : : * associated with the index columns. Also sets whether the index
13510 : : * uses WITHOUT OVERLAPS.
13511 : : *
13512 : : * Raises an ERROR on validation failure.
13513 : : */
13514 : : static Oid
13515 : 136 : transformFkeyCheckAttrs(Relation pkrel,
13516 : : int numattrs, int16 *attnums,
13517 : : bool with_period, Oid *opclasses,
13518 : : bool *pk_has_without_overlaps)
13519 : : {
13520 : 136 : Oid indexoid = InvalidOid;
13521 : 136 : bool found = false;
13522 : 136 : bool found_deferrable = false;
13523 : 136 : List *indexoidlist;
13524 : 136 : ListCell *indexoidscan;
13525 : 136 : int i,
13526 : : j;
13527 : :
13528 : : /*
13529 : : * Reject duplicate appearances of columns in the referenced-columns list.
13530 : : * Such a case is forbidden by the SQL standard, and even if we thought it
13531 : : * useful to allow it, there would be ambiguity about how to match the
13532 : : * list to unique indexes (in particular, it'd be unclear which index
13533 : : * opclass goes with which FK column).
13534 : : */
13535 [ + + ]: 339 : for (i = 0; i < numattrs; i++)
13536 : : {
13537 [ + + ]: 289 : for (j = i + 1; j < numattrs; j++)
13538 : : {
13539 [ + + ]: 86 : if (attnums[i] == attnums[j])
13540 [ + - + - ]: 4 : ereport(ERROR,
13541 : : (errcode(ERRCODE_INVALID_FOREIGN_KEY),
13542 : : errmsg("foreign key referenced-columns list must not contain duplicates")));
13543 : 82 : }
13544 : 203 : }
13545 : :
13546 : : /*
13547 : : * Get the list of index OIDs for the table from the relcache, and look up
13548 : : * each one in the pg_index syscache, and match unique indexes to the list
13549 : : * of attnums we are given.
13550 : : */
13551 : 132 : indexoidlist = RelationGetIndexList(pkrel);
13552 : :
13553 [ + - + + : 291 : foreach(indexoidscan, indexoidlist)
+ + ]
13554 : : {
13555 : 159 : HeapTuple indexTuple;
13556 : 159 : Form_pg_index indexStruct;
13557 : :
13558 : 159 : indexoid = lfirst_oid(indexoidscan);
13559 : 159 : indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
13560 [ + - ]: 159 : if (!HeapTupleIsValid(indexTuple))
13561 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for index %u", indexoid);
13562 : 159 : indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
13563 : :
13564 : : /*
13565 : : * Must have the right number of columns; must be unique (or if
13566 : : * temporal then exclusion instead) and not a partial index; forget it
13567 : : * if there are any expressions, too. Invalid indexes are out as well.
13568 : : */
13569 [ + + ]: 159 : if (indexStruct->indnkeyatts == numattrs &&
13570 [ + + + + ]: 141 : (with_period ? indexStruct->indisexclusion : indexStruct->indisunique) &&
13571 : 139 : indexStruct->indisvalid &&
13572 [ + - - + ]: 139 : heap_attisnull(indexTuple, Anum_pg_index_indpred, NULL) &&
13573 : 139 : heap_attisnull(indexTuple, Anum_pg_index_indexprs, NULL))
13574 : : {
13575 : 139 : Datum indclassDatum;
13576 : 139 : oidvector *indclass;
13577 : :
13578 : : /* Must get indclass the hard way */
13579 : 139 : indclassDatum = SysCacheGetAttrNotNull(INDEXRELID, indexTuple,
13580 : : Anum_pg_index_indclass);
13581 : 139 : indclass = (oidvector *) DatumGetPointer(indclassDatum);
13582 : :
13583 : : /*
13584 : : * The given attnum list may match the index columns in any order.
13585 : : * Check for a match, and extract the appropriate opclasses while
13586 : : * we're at it.
13587 : : *
13588 : : * We know that attnums[] is duplicate-free per the test at the
13589 : : * start of this function, and we checked above that the number of
13590 : : * index columns agrees, so if we find a match for each attnums[]
13591 : : * entry then we must have a one-to-one match in some order.
13592 : : */
13593 [ + + ]: 340 : for (i = 0; i < numattrs; i++)
13594 : : {
13595 : 210 : found = false;
13596 [ + + ]: 301 : for (j = 0; j < numattrs; j++)
13597 : : {
13598 [ + + ]: 292 : if (attnums[i] == indexStruct->indkey.values[j])
13599 : : {
13600 : 201 : opclasses[i] = indclass->values[j];
13601 : 201 : found = true;
13602 : 201 : break;
13603 : : }
13604 : 91 : }
13605 [ + + ]: 210 : if (!found)
13606 : 9 : break;
13607 : 201 : }
13608 : : /* The last attribute in the index must be the PERIOD FK part */
13609 [ + + + + ]: 139 : if (found && with_period)
13610 : : {
13611 : 19 : int16 periodattnum = attnums[numattrs - 1];
13612 : :
13613 : 19 : found = (periodattnum == indexStruct->indkey.values[numattrs - 1]);
13614 : 19 : }
13615 : :
13616 : : /*
13617 : : * Refuse to use a deferrable unique/primary key. This is per SQL
13618 : : * spec, and there would be a lot of interesting semantic problems
13619 : : * if we tried to allow it.
13620 : : */
13621 [ + + + - ]: 139 : if (found && !indexStruct->indimmediate)
13622 : : {
13623 : : /*
13624 : : * Remember that we found an otherwise matching index, so that
13625 : : * we can generate a more appropriate error message.
13626 : : */
13627 : 0 : found_deferrable = true;
13628 : 0 : found = false;
13629 : 0 : }
13630 : :
13631 : : /* We need to know whether the index has WITHOUT OVERLAPS */
13632 [ + + ]: 139 : if (found)
13633 : 130 : *pk_has_without_overlaps = indexStruct->indisexclusion;
13634 : 139 : }
13635 : 159 : ReleaseSysCache(indexTuple);
13636 [ + + ]: 159 : if (found)
13637 : 130 : break;
13638 [ + + ]: 159 : }
13639 : :
13640 [ + + ]: 132 : if (!found)
13641 : : {
13642 [ - + ]: 2 : if (found_deferrable)
13643 [ # # # # ]: 0 : ereport(ERROR,
13644 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
13645 : : errmsg("cannot use a deferrable unique constraint for referenced table \"%s\"",
13646 : : RelationGetRelationName(pkrel))));
13647 : : else
13648 [ + - + - ]: 2 : ereport(ERROR,
13649 : : (errcode(ERRCODE_INVALID_FOREIGN_KEY),
13650 : : errmsg("there is no unique constraint matching given keys for referenced table \"%s\"",
13651 : : RelationGetRelationName(pkrel))));
13652 : 0 : }
13653 : :
13654 : 130 : list_free(indexoidlist);
13655 : :
13656 : 260 : return indexoid;
13657 : 130 : }
13658 : :
13659 : : /*
13660 : : * findFkeyCast -
13661 : : *
13662 : : * Wrapper around find_coercion_pathway() for ATAddForeignKeyConstraint().
13663 : : * Caller has equal regard for binary coercibility and for an exact match.
13664 : : */
13665 : : static CoercionPathType
13666 : 2 : findFkeyCast(Oid targetTypeId, Oid sourceTypeId, Oid *funcid)
13667 : : {
13668 : 2 : CoercionPathType ret;
13669 : :
13670 [ + - ]: 2 : if (targetTypeId == sourceTypeId)
13671 : : {
13672 : 2 : ret = COERCION_PATH_RELABELTYPE;
13673 : 2 : *funcid = InvalidOid;
13674 : 2 : }
13675 : : else
13676 : : {
13677 : 0 : ret = find_coercion_pathway(targetTypeId, sourceTypeId,
13678 : 0 : COERCION_IMPLICIT, funcid);
13679 [ # # ]: 0 : if (ret == COERCION_PATH_NONE)
13680 : : /* A previously-relied-upon cast is now gone. */
13681 [ # # # # ]: 0 : elog(ERROR, "could not find cast from %u to %u",
13682 : : sourceTypeId, targetTypeId);
13683 : : }
13684 : :
13685 : 4 : return ret;
13686 : 2 : }
13687 : :
13688 : : /*
13689 : : * Permissions checks on the referenced table for ADD FOREIGN KEY
13690 : : *
13691 : : * Note: we have already checked that the user owns the referencing table,
13692 : : * else we'd have failed much earlier; no additional checks are needed for it.
13693 : : */
13694 : : static void
13695 : 293 : checkFkeyPermissions(Relation rel, int16 *attnums, int natts)
13696 : : {
13697 : 293 : Oid roleid = GetUserId();
13698 : 293 : AclResult aclresult;
13699 : 293 : int i;
13700 : :
13701 : : /* Okay if we have relation-level REFERENCES permission */
13702 : 293 : aclresult = pg_class_aclcheck(RelationGetRelid(rel), roleid,
13703 : : ACL_REFERENCES);
13704 [ - + ]: 293 : if (aclresult == ACLCHECK_OK)
13705 : 293 : return;
13706 : : /* Else we must have REFERENCES on each column */
13707 [ # # ]: 0 : for (i = 0; i < natts; i++)
13708 : : {
13709 : 0 : aclresult = pg_attribute_aclcheck(RelationGetRelid(rel), attnums[i],
13710 : 0 : roleid, ACL_REFERENCES);
13711 [ # # ]: 0 : if (aclresult != ACLCHECK_OK)
13712 : 0 : aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
13713 : 0 : RelationGetRelationName(rel));
13714 : 0 : }
13715 [ - + ]: 293 : }
13716 : :
13717 : : /*
13718 : : * Scan the existing rows in a table to verify they meet a proposed FK
13719 : : * constraint.
13720 : : *
13721 : : * Caller must have opened and locked both relations appropriately.
13722 : : */
13723 : : static void
13724 : 116 : validateForeignKeyConstraint(char *conname,
13725 : : Relation rel,
13726 : : Relation pkrel,
13727 : : Oid pkindOid,
13728 : : Oid constraintOid,
13729 : : bool hasperiod)
13730 : : {
13731 : 116 : TupleTableSlot *slot;
13732 : 116 : TableScanDesc scan;
13733 : 116 : Trigger trig = {0};
13734 : 116 : Snapshot snapshot;
13735 : 116 : MemoryContext oldcxt;
13736 : 116 : MemoryContext perTupCxt;
13737 : :
13738 [ - + - + ]: 116 : ereport(DEBUG1,
13739 : : (errmsg_internal("validating foreign key constraint \"%s\"", conname)));
13740 : :
13741 : : /*
13742 : : * Build a trigger call structure; we'll need it either way.
13743 : : */
13744 : 116 : trig.tgoid = InvalidOid;
13745 : 116 : trig.tgname = conname;
13746 : 116 : trig.tgenabled = TRIGGER_FIRES_ON_ORIGIN;
13747 : 116 : trig.tgisinternal = true;
13748 : 116 : trig.tgconstrrelid = RelationGetRelid(pkrel);
13749 : 116 : trig.tgconstrindid = pkindOid;
13750 : 116 : trig.tgconstraint = constraintOid;
13751 : 116 : trig.tgdeferrable = false;
13752 : 116 : trig.tginitdeferred = false;
13753 : : /* we needn't fill in remaining fields */
13754 : :
13755 : : /*
13756 : : * See if we can do it with a single LEFT JOIN query. A false result
13757 : : * indicates we must proceed with the fire-the-trigger method. We can't do
13758 : : * a LEFT JOIN for temporal FKs yet, but we can once we support temporal
13759 : : * left joins.
13760 : : */
13761 [ + - + + ]: 116 : if (!hasperiod && RI_Initial_Check(&trig, rel, pkrel))
13762 : 102 : return;
13763 : :
13764 : : /*
13765 : : * Scan through each tuple, calling RI_FKey_check_ins (insert trigger) as
13766 : : * if that tuple had just been inserted. If any of those fail, it should
13767 : : * ereport(ERROR) and that's that.
13768 : : */
13769 : 14 : snapshot = RegisterSnapshot(GetLatestSnapshot());
13770 : 14 : slot = table_slot_create(rel, NULL);
13771 : 14 : scan = table_beginscan(rel, snapshot, 0, NULL);
13772 : :
13773 : 14 : perTupCxt = AllocSetContextCreate(CurrentMemoryContext,
13774 : : "validateForeignKeyConstraint",
13775 : : ALLOCSET_SMALL_SIZES);
13776 : 14 : oldcxt = MemoryContextSwitchTo(perTupCxt);
13777 : :
13778 [ + + ]: 31 : while (table_scan_getnextslot(scan, ForwardScanDirection, slot))
13779 : : {
13780 : 17 : LOCAL_FCINFO(fcinfo, 0);
13781 : 17 : TriggerData trigdata = {0};
13782 : :
13783 [ + - ]: 17 : CHECK_FOR_INTERRUPTS();
13784 : :
13785 : : /*
13786 : : * Make a call to the trigger function
13787 : : *
13788 : : * No parameters are passed, but we do set a context
13789 : : */
13790 [ + - + - : 85 : MemSet(fcinfo, 0, SizeForFunctionCallInfo(0));
+ - - + +
+ ]
13791 : :
13792 : : /*
13793 : : * We assume RI_FKey_check_ins won't look at flinfo...
13794 : : */
13795 : 17 : trigdata.type = T_TriggerData;
13796 : 17 : trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
13797 : 17 : trigdata.tg_relation = rel;
13798 : 17 : trigdata.tg_trigtuple = ExecFetchSlotHeapTuple(slot, false, NULL);
13799 : 17 : trigdata.tg_trigslot = slot;
13800 : 17 : trigdata.tg_trigger = &trig;
13801 : :
13802 : 17 : fcinfo->context = (Node *) &trigdata;
13803 : :
13804 : 17 : RI_FKey_check_ins(fcinfo);
13805 : :
13806 : 17 : MemoryContextReset(perTupCxt);
13807 : 17 : }
13808 : :
13809 : 14 : MemoryContextSwitchTo(oldcxt);
13810 : 14 : MemoryContextDelete(perTupCxt);
13811 : 14 : table_endscan(scan);
13812 : 14 : UnregisterSnapshot(snapshot);
13813 : 14 : ExecDropSingleTupleTableSlot(slot);
13814 [ - + ]: 116 : }
13815 : :
13816 : : /*
13817 : : * CreateFKCheckTrigger
13818 : : * Creates the insert (on_insert=true) or update "check" trigger that
13819 : : * implements a given foreign key
13820 : : *
13821 : : * Returns the OID of the so created trigger.
13822 : : */
13823 : : static Oid
13824 : 724 : CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
13825 : : Oid constraintOid, Oid indexOid, Oid parentTrigOid,
13826 : : bool on_insert)
13827 : : {
13828 : 724 : ObjectAddress trigAddress;
13829 : 724 : CreateTrigStmt *fk_trigger;
13830 : :
13831 : : /*
13832 : : * Note: for a self-referential FK (referencing and referenced tables are
13833 : : * the same), it is important that the ON UPDATE action fires before the
13834 : : * CHECK action, since both triggers will fire on the same row during an
13835 : : * UPDATE event; otherwise the CHECK trigger will be checking a non-final
13836 : : * state of the row. Triggers fire in name order, so we ensure this by
13837 : : * using names like "RI_ConstraintTrigger_a_NNNN" for the action triggers
13838 : : * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
13839 : : */
13840 : 724 : fk_trigger = makeNode(CreateTrigStmt);
13841 : 724 : fk_trigger->replace = false;
13842 : 724 : fk_trigger->isconstraint = true;
13843 : 724 : fk_trigger->trigname = "RI_ConstraintTrigger_c";
13844 : 724 : fk_trigger->relation = NULL;
13845 : :
13846 : : /* Either ON INSERT or ON UPDATE */
13847 [ + + ]: 724 : if (on_insert)
13848 : : {
13849 : 362 : fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
13850 : 362 : fk_trigger->events = TRIGGER_TYPE_INSERT;
13851 : 362 : }
13852 : : else
13853 : : {
13854 : 362 : fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
13855 : 362 : fk_trigger->events = TRIGGER_TYPE_UPDATE;
13856 : : }
13857 : :
13858 : 724 : fk_trigger->args = NIL;
13859 : 724 : fk_trigger->row = true;
13860 : 724 : fk_trigger->timing = TRIGGER_TYPE_AFTER;
13861 : 724 : fk_trigger->columns = NIL;
13862 : 724 : fk_trigger->whenClause = NULL;
13863 : 724 : fk_trigger->transitionRels = NIL;
13864 : 724 : fk_trigger->deferrable = fkconstraint->deferrable;
13865 : 724 : fk_trigger->initdeferred = fkconstraint->initdeferred;
13866 : 724 : fk_trigger->constrrel = NULL;
13867 : :
13868 : 1448 : trigAddress = CreateTrigger(fk_trigger, NULL, myRelOid, refRelOid,
13869 : 724 : constraintOid, indexOid, InvalidOid,
13870 : 724 : parentTrigOid, NULL, true, false);
13871 : :
13872 : : /* Make changes-so-far visible */
13873 : 724 : CommandCounterIncrement();
13874 : :
13875 : 1448 : return trigAddress.objectId;
13876 : 724 : }
13877 : :
13878 : : /*
13879 : : * createForeignKeyActionTriggers
13880 : : * Create the referenced-side "action" triggers that implement a foreign
13881 : : * key.
13882 : : *
13883 : : * Returns the OIDs of the so created triggers in *deleteTrigOid and
13884 : : * *updateTrigOid.
13885 : : */
13886 : : static void
13887 : 422 : createForeignKeyActionTriggers(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
13888 : : Oid constraintOid, Oid indexOid,
13889 : : Oid parentDelTrigger, Oid parentUpdTrigger,
13890 : : Oid *deleteTrigOid, Oid *updateTrigOid)
13891 : : {
13892 : 422 : CreateTrigStmt *fk_trigger;
13893 : 422 : ObjectAddress trigAddress;
13894 : :
13895 : : /*
13896 : : * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
13897 : : * DELETE action on the referenced table.
13898 : : */
13899 : 422 : fk_trigger = makeNode(CreateTrigStmt);
13900 : 422 : fk_trigger->replace = false;
13901 : 422 : fk_trigger->isconstraint = true;
13902 : 422 : fk_trigger->trigname = "RI_ConstraintTrigger_a";
13903 : 422 : fk_trigger->relation = NULL;
13904 : 422 : fk_trigger->args = NIL;
13905 : 422 : fk_trigger->row = true;
13906 : 422 : fk_trigger->timing = TRIGGER_TYPE_AFTER;
13907 : 422 : fk_trigger->events = TRIGGER_TYPE_DELETE;
13908 : 422 : fk_trigger->columns = NIL;
13909 : 422 : fk_trigger->whenClause = NULL;
13910 : 422 : fk_trigger->transitionRels = NIL;
13911 : 422 : fk_trigger->constrrel = NULL;
13912 : :
13913 [ + + + + : 422 : switch (fkconstraint->fk_del_action)
+ - ]
13914 : : {
13915 : : case FKCONSTR_ACTION_NOACTION:
13916 : 319 : fk_trigger->deferrable = fkconstraint->deferrable;
13917 : 319 : fk_trigger->initdeferred = fkconstraint->initdeferred;
13918 : 319 : fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
13919 : 319 : break;
13920 : : case FKCONSTR_ACTION_RESTRICT:
13921 : 5 : fk_trigger->deferrable = false;
13922 : 5 : fk_trigger->initdeferred = false;
13923 : 5 : fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
13924 : 5 : break;
13925 : : case FKCONSTR_ACTION_CASCADE:
13926 : 74 : fk_trigger->deferrable = false;
13927 : 74 : fk_trigger->initdeferred = false;
13928 : 74 : fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
13929 : 74 : break;
13930 : : case FKCONSTR_ACTION_SETNULL:
13931 : 14 : fk_trigger->deferrable = false;
13932 : 14 : fk_trigger->initdeferred = false;
13933 : 14 : fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
13934 : 14 : break;
13935 : : case FKCONSTR_ACTION_SETDEFAULT:
13936 : 10 : fk_trigger->deferrable = false;
13937 : 10 : fk_trigger->initdeferred = false;
13938 : 10 : fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
13939 : 10 : break;
13940 : : default:
13941 [ # # # # ]: 0 : elog(ERROR, "unrecognized FK action type: %d",
13942 : : (int) fkconstraint->fk_del_action);
13943 : 0 : break;
13944 : : }
13945 : :
13946 : 844 : trigAddress = CreateTrigger(fk_trigger, NULL, refRelOid, myRelOid,
13947 : 422 : constraintOid, indexOid, InvalidOid,
13948 : 422 : parentDelTrigger, NULL, true, false);
13949 [ - + ]: 422 : if (deleteTrigOid)
13950 : 422 : *deleteTrigOid = trigAddress.objectId;
13951 : :
13952 : : /* Make changes-so-far visible */
13953 : 422 : CommandCounterIncrement();
13954 : :
13955 : : /*
13956 : : * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
13957 : : * UPDATE action on the referenced table.
13958 : : */
13959 : 422 : fk_trigger = makeNode(CreateTrigStmt);
13960 : 422 : fk_trigger->replace = false;
13961 : 422 : fk_trigger->isconstraint = true;
13962 : 422 : fk_trigger->trigname = "RI_ConstraintTrigger_a";
13963 : 422 : fk_trigger->relation = NULL;
13964 : 422 : fk_trigger->args = NIL;
13965 : 422 : fk_trigger->row = true;
13966 : 422 : fk_trigger->timing = TRIGGER_TYPE_AFTER;
13967 : 422 : fk_trigger->events = TRIGGER_TYPE_UPDATE;
13968 : 422 : fk_trigger->columns = NIL;
13969 : 422 : fk_trigger->whenClause = NULL;
13970 : 422 : fk_trigger->transitionRels = NIL;
13971 : 422 : fk_trigger->constrrel = NULL;
13972 : :
13973 [ + + + + : 422 : switch (fkconstraint->fk_upd_action)
+ - ]
13974 : : {
13975 : : case FKCONSTR_ACTION_NOACTION:
13976 : 347 : fk_trigger->deferrable = fkconstraint->deferrable;
13977 : 347 : fk_trigger->initdeferred = fkconstraint->initdeferred;
13978 : 347 : fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
13979 : 347 : break;
13980 : : case FKCONSTR_ACTION_RESTRICT:
13981 : 6 : fk_trigger->deferrable = false;
13982 : 6 : fk_trigger->initdeferred = false;
13983 : 6 : fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
13984 : 6 : break;
13985 : : case FKCONSTR_ACTION_CASCADE:
13986 : 52 : fk_trigger->deferrable = false;
13987 : 52 : fk_trigger->initdeferred = false;
13988 : 52 : fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
13989 : 52 : break;
13990 : : case FKCONSTR_ACTION_SETNULL:
13991 : 10 : fk_trigger->deferrable = false;
13992 : 10 : fk_trigger->initdeferred = false;
13993 : 10 : fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
13994 : 10 : break;
13995 : : case FKCONSTR_ACTION_SETDEFAULT:
13996 : 7 : fk_trigger->deferrable = false;
13997 : 7 : fk_trigger->initdeferred = false;
13998 : 7 : fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
13999 : 7 : break;
14000 : : default:
14001 [ # # # # ]: 0 : elog(ERROR, "unrecognized FK action type: %d",
14002 : : (int) fkconstraint->fk_upd_action);
14003 : 0 : break;
14004 : : }
14005 : :
14006 : 844 : trigAddress = CreateTrigger(fk_trigger, NULL, refRelOid, myRelOid,
14007 : 422 : constraintOid, indexOid, InvalidOid,
14008 : 422 : parentUpdTrigger, NULL, true, false);
14009 [ - + ]: 422 : if (updateTrigOid)
14010 : 422 : *updateTrigOid = trigAddress.objectId;
14011 : 422 : }
14012 : :
14013 : : /*
14014 : : * createForeignKeyCheckTriggers
14015 : : * Create the referencing-side "check" triggers that implement a foreign
14016 : : * key.
14017 : : *
14018 : : * Returns the OIDs of the so created triggers in *insertTrigOid and
14019 : : * *updateTrigOid.
14020 : : */
14021 : : static void
14022 : 362 : createForeignKeyCheckTriggers(Oid myRelOid, Oid refRelOid,
14023 : : Constraint *fkconstraint, Oid constraintOid,
14024 : : Oid indexOid,
14025 : : Oid parentInsTrigger, Oid parentUpdTrigger,
14026 : : Oid *insertTrigOid, Oid *updateTrigOid)
14027 : : {
14028 : 724 : *insertTrigOid = CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint,
14029 : 362 : constraintOid, indexOid,
14030 : 362 : parentInsTrigger, true);
14031 : 724 : *updateTrigOid = CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint,
14032 : 362 : constraintOid, indexOid,
14033 : 362 : parentUpdTrigger, false);
14034 : 362 : }
14035 : :
14036 : : /*
14037 : : * ALTER TABLE DROP CONSTRAINT
14038 : : *
14039 : : * Like DROP COLUMN, we can't use the normal ALTER TABLE recursion mechanism.
14040 : : */
14041 : : static void
14042 : 131 : ATExecDropConstraint(Relation rel, const char *constrName,
14043 : : DropBehavior behavior, bool recurse,
14044 : : bool missing_ok, LOCKMODE lockmode)
14045 : : {
14046 : 131 : Relation conrel;
14047 : 131 : SysScanDesc scan;
14048 : 131 : ScanKeyData skey[3];
14049 : 131 : HeapTuple tuple;
14050 : 131 : bool found = false;
14051 : :
14052 : 131 : conrel = table_open(ConstraintRelationId, RowExclusiveLock);
14053 : :
14054 : : /*
14055 : : * Find and drop the target constraint
14056 : : */
14057 : 262 : ScanKeyInit(&skey[0],
14058 : : Anum_pg_constraint_conrelid,
14059 : : BTEqualStrategyNumber, F_OIDEQ,
14060 : 131 : ObjectIdGetDatum(RelationGetRelid(rel)));
14061 : 262 : ScanKeyInit(&skey[1],
14062 : : Anum_pg_constraint_contypid,
14063 : : BTEqualStrategyNumber, F_OIDEQ,
14064 : 131 : ObjectIdGetDatum(InvalidOid));
14065 : 262 : ScanKeyInit(&skey[2],
14066 : : Anum_pg_constraint_conname,
14067 : : BTEqualStrategyNumber, F_NAMEEQ,
14068 : 131 : CStringGetDatum(constrName));
14069 : 262 : scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId,
14070 : 131 : true, NULL, 3, skey);
14071 : :
14072 : : /* There can be at most one matching row */
14073 [ + + ]: 131 : if (HeapTupleIsValid(tuple = systable_getnext(scan)))
14074 : : {
14075 : 250 : dropconstraint_internal(rel, tuple, behavior, recurse, false,
14076 : 125 : missing_ok, lockmode);
14077 : 125 : found = true;
14078 : 125 : }
14079 : :
14080 : 131 : systable_endscan(scan);
14081 : :
14082 [ + + ]: 131 : if (!found)
14083 : : {
14084 [ + + ]: 6 : if (!missing_ok)
14085 [ + - + - ]: 4 : ereport(ERROR,
14086 : : errcode(ERRCODE_UNDEFINED_OBJECT),
14087 : : errmsg("constraint \"%s\" of relation \"%s\" does not exist",
14088 : : constrName, RelationGetRelationName(rel)));
14089 : : else
14090 [ - + + - ]: 2 : ereport(NOTICE,
14091 : : errmsg("constraint \"%s\" of relation \"%s\" does not exist, skipping",
14092 : : constrName, RelationGetRelationName(rel)));
14093 : 2 : }
14094 : :
14095 : 127 : table_close(conrel, RowExclusiveLock);
14096 : 127 : }
14097 : :
14098 : : /*
14099 : : * Remove a constraint, using its pg_constraint tuple
14100 : : *
14101 : : * Implementation for ALTER TABLE DROP CONSTRAINT and ALTER TABLE ALTER COLUMN
14102 : : * DROP NOT NULL.
14103 : : *
14104 : : * Returns the address of the constraint being removed.
14105 : : */
14106 : : static ObjectAddress
14107 : 186 : dropconstraint_internal(Relation rel, HeapTuple constraintTup, DropBehavior behavior,
14108 : : bool recurse, bool recursing, bool missing_ok,
14109 : : LOCKMODE lockmode)
14110 : : {
14111 : 186 : Relation conrel;
14112 : 186 : Form_pg_constraint con;
14113 : : ObjectAddress conobj;
14114 : 186 : List *children;
14115 : 186 : bool is_no_inherit_constraint = false;
14116 : 186 : char *constrName;
14117 : 186 : char *colname = NULL;
14118 : :
14119 : : /* Guard against stack overflow due to overly deep inheritance tree. */
14120 : 186 : check_stack_depth();
14121 : :
14122 : : /* At top level, permission check was done in ATPrepCmd, else do it */
14123 [ + + ]: 186 : if (recursing)
14124 : 34 : ATSimplePermissions(AT_DropConstraint, rel,
14125 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
14126 : :
14127 : 186 : conrel = table_open(ConstraintRelationId, RowExclusiveLock);
14128 : :
14129 : 186 : con = (Form_pg_constraint) GETSTRUCT(constraintTup);
14130 : 186 : constrName = NameStr(con->conname);
14131 : :
14132 : : /* Don't allow drop of inherited constraints */
14133 [ + + + + ]: 186 : if (con->coninhcount > 0 && !recursing)
14134 [ + - + - ]: 26 : ereport(ERROR,
14135 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
14136 : : errmsg("cannot drop inherited constraint \"%s\" of relation \"%s\"",
14137 : : constrName, RelationGetRelationName(rel))));
14138 : :
14139 : : /*
14140 : : * Reset pg_constraint.attnotnull, if this is a not-null constraint.
14141 : : *
14142 : : * While doing that, we're in a good position to disallow dropping a not-
14143 : : * null constraint underneath a primary key, a replica identity index, or
14144 : : * a generated identity column.
14145 : : */
14146 [ + + ]: 160 : if (con->contype == CONSTRAINT_NOTNULL)
14147 : : {
14148 : 50 : Relation attrel = table_open(AttributeRelationId, RowExclusiveLock);
14149 : 50 : AttrNumber attnum = extractNotNullColumn(constraintTup);
14150 : 50 : Bitmapset *pkattrs;
14151 : 50 : Bitmapset *irattrs;
14152 : 50 : HeapTuple atttup;
14153 : 50 : Form_pg_attribute attForm;
14154 : :
14155 : : /* save column name for recursion step */
14156 : 50 : colname = get_attname(RelationGetRelid(rel), attnum, false);
14157 : :
14158 : : /*
14159 : : * Disallow if it's in the primary key. For partitioned tables we
14160 : : * cannot rely solely on RelationGetIndexAttrBitmap, because it'll
14161 : : * return NULL if the primary key is invalid; but we still need to
14162 : : * protect not-null constraints under such a constraint, so check the
14163 : : * slow way.
14164 : : */
14165 : 50 : pkattrs = RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_PRIMARY_KEY);
14166 : :
14167 [ + + + + ]: 50 : if (pkattrs == NULL &&
14168 : 44 : rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
14169 : : {
14170 : 3 : Oid pkindex = RelationGetPrimaryKeyIndex(rel, true);
14171 : :
14172 [ + - ]: 3 : if (OidIsValid(pkindex))
14173 : : {
14174 : 0 : Relation pk = relation_open(pkindex, AccessShareLock);
14175 : :
14176 : 0 : pkattrs = NULL;
14177 [ # # ]: 0 : for (int i = 0; i < pk->rd_index->indnkeyatts; i++)
14178 : 0 : pkattrs = bms_add_member(pkattrs, pk->rd_index->indkey.values[i]);
14179 : :
14180 : 0 : relation_close(pk, AccessShareLock);
14181 : 0 : }
14182 : 3 : }
14183 : :
14184 [ + + + + ]: 50 : if (pkattrs &&
14185 : 6 : bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber, pkattrs))
14186 [ + - + - ]: 4 : ereport(ERROR,
14187 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
14188 : : errmsg("column \"%s\" is in a primary key",
14189 : : get_attname(RelationGetRelid(rel), attnum, false)));
14190 : :
14191 : : /* Disallow if it's in the replica identity */
14192 : 46 : irattrs = RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_IDENTITY_KEY);
14193 [ + + ]: 46 : if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber, irattrs))
14194 [ + - + - ]: 2 : ereport(ERROR,
14195 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
14196 : : errmsg("column \"%s\" is in index used as replica identity",
14197 : : get_attname(RelationGetRelid(rel), attnum, false)));
14198 : :
14199 : : /* Disallow if it's a GENERATED AS IDENTITY column */
14200 : 44 : atttup = SearchSysCacheCopyAttNum(RelationGetRelid(rel), attnum);
14201 [ + - ]: 44 : if (!HeapTupleIsValid(atttup))
14202 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for attribute %d of relation %u",
14203 : : attnum, RelationGetRelid(rel));
14204 : 44 : attForm = (Form_pg_attribute) GETSTRUCT(atttup);
14205 [ + - ]: 44 : if (attForm->attidentity != '\0')
14206 [ # # # # ]: 0 : ereport(ERROR,
14207 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
14208 : : errmsg("column \"%s\" of relation \"%s\" is an identity column",
14209 : : get_attname(RelationGetRelid(rel), attnum,
14210 : : false),
14211 : : RelationGetRelationName(rel)));
14212 : :
14213 : : /* All good -- reset attnotnull if needed */
14214 [ - + ]: 44 : if (attForm->attnotnull)
14215 : : {
14216 : 44 : attForm->attnotnull = false;
14217 : 44 : CatalogTupleUpdate(attrel, &atttup->t_self, atttup);
14218 : 44 : }
14219 : :
14220 : 44 : table_close(attrel, RowExclusiveLock);
14221 : 44 : }
14222 : :
14223 : 154 : is_no_inherit_constraint = con->connoinherit;
14224 : :
14225 : : /*
14226 : : * If it's a foreign-key constraint, we'd better lock the referenced table
14227 : : * and check that that's not in use, just as we've already done for the
14228 : : * constrained table (else we might, eg, be dropping a trigger that has
14229 : : * unfired events). But we can/must skip that in the self-referential
14230 : : * case.
14231 : : */
14232 [ + + - + ]: 154 : if (con->contype == CONSTRAINT_FOREIGN &&
14233 : 28 : con->confrelid != RelationGetRelid(rel))
14234 : : {
14235 : 28 : Relation frel;
14236 : :
14237 : : /* Must match lock taken by RemoveTriggerById: */
14238 : 28 : frel = table_open(con->confrelid, AccessExclusiveLock);
14239 : 28 : CheckAlterTableIsSafe(frel);
14240 : 28 : table_close(frel, NoLock);
14241 : 28 : }
14242 : :
14243 : : /*
14244 : : * Perform the actual constraint deletion
14245 : : */
14246 : 154 : ObjectAddressSet(conobj, ConstraintRelationId, con->oid);
14247 : 154 : performDeletion(&conobj, behavior, 0);
14248 : :
14249 : : /*
14250 : : * For partitioned tables, non-CHECK, non-NOT-NULL inherited constraints
14251 : : * are dropped via the dependency mechanism, so we're done here.
14252 : : */
14253 [ + + ]: 154 : if (con->contype != CONSTRAINT_CHECK &&
14254 [ + + + + ]: 101 : con->contype != CONSTRAINT_NOTNULL &&
14255 : 57 : rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
14256 : : {
14257 : 13 : table_close(conrel, RowExclusiveLock);
14258 : 13 : return conobj;
14259 : : }
14260 : :
14261 : : /*
14262 : : * Propagate to children as appropriate. Unlike most other ALTER
14263 : : * routines, we have to do this one level of recursion at a time; we can't
14264 : : * use find_all_inheritors to do it in one pass.
14265 : : */
14266 [ + + ]: 141 : if (!is_no_inherit_constraint)
14267 : 95 : children = find_inheritance_children(RelationGetRelid(rel), lockmode);
14268 : : else
14269 : 46 : children = NIL;
14270 : :
14271 [ + + + + : 344 : foreach_oid(childrelid, children)
+ + + + ]
14272 : : {
14273 : 63 : Relation childrel;
14274 : 63 : HeapTuple tuple;
14275 : 63 : Form_pg_constraint childcon;
14276 : :
14277 : : /* find_inheritance_children already got lock */
14278 : 63 : childrel = table_open(childrelid, NoLock);
14279 : 63 : CheckAlterTableIsSafe(childrel);
14280 : :
14281 : : /*
14282 : : * We search for not-null constraints by column name, and others by
14283 : : * constraint name.
14284 : : */
14285 [ + + ]: 63 : if (con->contype == CONSTRAINT_NOTNULL)
14286 : : {
14287 : 24 : tuple = findNotNullConstraint(childrelid, colname);
14288 [ + - ]: 24 : if (!HeapTupleIsValid(tuple))
14289 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation %u",
14290 : : colname, RelationGetRelid(childrel));
14291 : 24 : }
14292 : : else
14293 : : {
14294 : 39 : SysScanDesc scan;
14295 : 39 : ScanKeyData skey[3];
14296 : :
14297 : 78 : ScanKeyInit(&skey[0],
14298 : : Anum_pg_constraint_conrelid,
14299 : : BTEqualStrategyNumber, F_OIDEQ,
14300 : 39 : ObjectIdGetDatum(childrelid));
14301 : 78 : ScanKeyInit(&skey[1],
14302 : : Anum_pg_constraint_contypid,
14303 : : BTEqualStrategyNumber, F_OIDEQ,
14304 : 39 : ObjectIdGetDatum(InvalidOid));
14305 : 78 : ScanKeyInit(&skey[2],
14306 : : Anum_pg_constraint_conname,
14307 : : BTEqualStrategyNumber, F_NAMEEQ,
14308 : 39 : CStringGetDatum(constrName));
14309 : 78 : scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId,
14310 : 39 : true, NULL, 3, skey);
14311 : : /* There can only be one, so no need to loop */
14312 : 39 : tuple = systable_getnext(scan);
14313 [ + - ]: 39 : if (!HeapTupleIsValid(tuple))
14314 [ # # # # ]: 0 : ereport(ERROR,
14315 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
14316 : : errmsg("constraint \"%s\" of relation \"%s\" does not exist",
14317 : : constrName,
14318 : : RelationGetRelationName(childrel))));
14319 : 39 : tuple = heap_copytuple(tuple);
14320 : 39 : systable_endscan(scan);
14321 : 39 : }
14322 : :
14323 : 63 : childcon = (Form_pg_constraint) GETSTRUCT(tuple);
14324 : :
14325 : : /* Right now only CHECK and not-null constraints can be inherited */
14326 [ + + + - ]: 63 : if (childcon->contype != CONSTRAINT_CHECK &&
14327 : 24 : childcon->contype != CONSTRAINT_NOTNULL)
14328 [ # # # # ]: 0 : elog(ERROR, "inherited constraint is not a CHECK or not-null constraint");
14329 : :
14330 [ + - ]: 63 : if (childcon->coninhcount <= 0) /* shouldn't happen */
14331 [ # # # # ]: 0 : elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
14332 : : childrelid, NameStr(childcon->conname));
14333 : :
14334 [ + + ]: 63 : if (recurse)
14335 : : {
14336 : : /*
14337 : : * If the child constraint has other definition sources, just
14338 : : * decrement its inheritance count; if not, recurse to delete it.
14339 : : */
14340 [ + + + + ]: 46 : if (childcon->coninhcount == 1 && !childcon->conislocal)
14341 : : {
14342 : : /* Time to delete this child constraint, too */
14343 : 68 : dropconstraint_internal(childrel, tuple, behavior,
14344 : 34 : recurse, true, missing_ok,
14345 : 34 : lockmode);
14346 : 34 : }
14347 : : else
14348 : : {
14349 : : /* Child constraint must survive my deletion */
14350 : 12 : childcon->coninhcount--;
14351 : 12 : CatalogTupleUpdate(conrel, &tuple->t_self, tuple);
14352 : :
14353 : : /* Make update visible */
14354 : 12 : CommandCounterIncrement();
14355 : : }
14356 : 46 : }
14357 : : else
14358 : : {
14359 : : /*
14360 : : * If we were told to drop ONLY in this table (no recursion) and
14361 : : * there are no further parents for this constraint, we need to
14362 : : * mark the inheritors' constraints as locally defined rather than
14363 : : * inherited.
14364 : : */
14365 : 17 : childcon->coninhcount--;
14366 [ - + ]: 17 : if (childcon->coninhcount == 0)
14367 : 17 : childcon->conislocal = true;
14368 : :
14369 : 17 : CatalogTupleUpdate(conrel, &tuple->t_self, tuple);
14370 : :
14371 : : /* Make update visible */
14372 : 17 : CommandCounterIncrement();
14373 : : }
14374 : :
14375 : 63 : heap_freetuple(tuple);
14376 : :
14377 : 63 : table_close(childrel, NoLock);
14378 : 203 : }
14379 : :
14380 : 141 : table_close(conrel, RowExclusiveLock);
14381 : :
14382 : 141 : return conobj;
14383 : 154 : }
14384 : :
14385 : : /*
14386 : : * ALTER COLUMN TYPE
14387 : : *
14388 : : * Unlike other subcommand types, we do parse transformation for ALTER COLUMN
14389 : : * TYPE during phase 1 --- the AlterTableCmd passed in here is already
14390 : : * transformed (and must be, because we rely on some transformed fields).
14391 : : *
14392 : : * The point of this is that the execution of all ALTER COLUMN TYPEs for a
14393 : : * table will be done "in parallel" during phase 3, so all the USING
14394 : : * expressions should be parsed assuming the original column types. Also,
14395 : : * this allows a USING expression to refer to a field that will be dropped.
14396 : : *
14397 : : * To make this work safely, AT_PASS_DROP then AT_PASS_ALTER_TYPE must be
14398 : : * the first two execution steps in phase 2; they must not see the effects
14399 : : * of any other subcommand types, since the USING expressions are parsed
14400 : : * against the unmodified table's state.
14401 : : */
14402 : : static void
14403 : 215 : ATPrepAlterColumnType(List **wqueue,
14404 : : AlteredTableInfo *tab, Relation rel,
14405 : : bool recurse, bool recursing,
14406 : : AlterTableCmd *cmd, LOCKMODE lockmode,
14407 : : AlterTableUtilityContext *context)
14408 : : {
14409 : 215 : char *colName = cmd->name;
14410 : 215 : ColumnDef *def = (ColumnDef *) cmd->def;
14411 : 215 : TypeName *typeName = def->typeName;
14412 : 215 : Node *transform = def->cooked_default;
14413 : 215 : HeapTuple tuple;
14414 : 215 : Form_pg_attribute attTup;
14415 : 215 : AttrNumber attnum;
14416 : 215 : Oid targettype;
14417 : 215 : int32 targettypmod;
14418 : 215 : Oid targetcollid;
14419 : 215 : NewColumnValue *newval;
14420 : 215 : ParseState *pstate = make_parsestate(NULL);
14421 : 215 : AclResult aclresult;
14422 : 215 : bool is_expr;
14423 : :
14424 : 215 : pstate->p_sourcetext = context->queryString;
14425 : :
14426 [ + + + + ]: 215 : if (rel->rd_rel->reloftype && !recursing)
14427 [ + - + - ]: 1 : ereport(ERROR,
14428 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
14429 : : errmsg("cannot alter column type of typed table"),
14430 : : parser_errposition(pstate, def->location)));
14431 : :
14432 : : /* lookup the attribute so we can check inheritance status */
14433 : 214 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
14434 [ + - ]: 214 : if (!HeapTupleIsValid(tuple))
14435 [ # # # # ]: 0 : ereport(ERROR,
14436 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
14437 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
14438 : : colName, RelationGetRelationName(rel)),
14439 : : parser_errposition(pstate, def->location)));
14440 : 214 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
14441 : 214 : attnum = attTup->attnum;
14442 : :
14443 : : /* Can't alter a system attribute */
14444 [ + + ]: 214 : if (attnum <= 0)
14445 [ + - + - ]: 1 : ereport(ERROR,
14446 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
14447 : : errmsg("cannot alter system column \"%s\"", colName),
14448 : : parser_errposition(pstate, def->location)));
14449 : :
14450 : : /*
14451 : : * Cannot specify USING when altering type of a generated column, because
14452 : : * that would violate the generation expression.
14453 : : */
14454 [ + + + + ]: 213 : if (attTup->attgenerated && def->cooked_default)
14455 [ + - + - ]: 2 : ereport(ERROR,
14456 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
14457 : : errmsg("cannot specify USING when altering type of generated column"),
14458 : : errdetail("Column \"%s\" is a generated column.", colName),
14459 : : parser_errposition(pstate, def->location)));
14460 : :
14461 : : /*
14462 : : * Don't alter inherited columns. At outer level, there had better not be
14463 : : * any inherited definition; when recursing, we assume this was checked at
14464 : : * the parent level (see below).
14465 : : */
14466 [ + + + + ]: 211 : if (attTup->attinhcount > 0 && !recursing)
14467 [ + - + - ]: 1 : ereport(ERROR,
14468 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
14469 : : errmsg("cannot alter inherited column \"%s\"", colName),
14470 : : parser_errposition(pstate, def->location)));
14471 : :
14472 : : /* Don't alter columns used in the partition key */
14473 [ + + + + ]: 420 : if (has_partition_attrs(rel,
14474 : 210 : bms_make_singleton(attnum - FirstLowInvalidHeapAttributeNumber),
14475 : : &is_expr))
14476 [ + - + - ]: 3 : ereport(ERROR,
14477 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
14478 : : errmsg("cannot alter column \"%s\" because it is part of the partition key of relation \"%s\"",
14479 : : colName, RelationGetRelationName(rel)),
14480 : : parser_errposition(pstate, def->location)));
14481 : :
14482 : : /* Look up the target type */
14483 : 207 : typenameTypeIdAndMod(pstate, typeName, &targettype, &targettypmod);
14484 : :
14485 : 207 : aclresult = object_aclcheck(TypeRelationId, targettype, GetUserId(), ACL_USAGE);
14486 [ + + ]: 207 : if (aclresult != ACLCHECK_OK)
14487 : 2 : aclcheck_error_type(aclresult, targettype);
14488 : :
14489 : : /* And the collation */
14490 : 207 : targetcollid = GetColumnDefCollation(pstate, def, targettype);
14491 : :
14492 : : /* make sure datatype is legal for a column */
14493 : 414 : CheckAttributeType(colName, targettype, targetcollid,
14494 : 207 : list_make1_oid(rel->rd_rel->reltype),
14495 : 207 : (attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL ? CHKATYPE_IS_VIRTUAL : 0));
14496 : :
14497 [ + + ]: 207 : if (attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
14498 : : {
14499 : : /* do nothing */
14500 : 4 : }
14501 [ + + + + ]: 203 : else if (tab->relkind == RELKIND_RELATION ||
14502 : 32 : tab->relkind == RELKIND_PARTITIONED_TABLE)
14503 : : {
14504 : : /*
14505 : : * Set up an expression to transform the old data value to the new
14506 : : * type. If a USING option was given, use the expression as
14507 : : * transformed by transformAlterTableStmt, else just take the old
14508 : : * value and try to coerce it. We do this first so that type
14509 : : * incompatibility can be detected before we waste effort, and because
14510 : : * we need the expression to be parsed against the original table row
14511 : : * type.
14512 : : */
14513 [ + + ]: 182 : if (!transform)
14514 : : {
14515 : 290 : transform = (Node *) makeVar(1, attnum,
14516 : 145 : attTup->atttypid, attTup->atttypmod,
14517 : 145 : attTup->attcollation,
14518 : : 0);
14519 : 145 : }
14520 : :
14521 : 364 : transform = coerce_to_target_type(pstate,
14522 : 182 : transform, exprType(transform),
14523 : 182 : targettype, targettypmod,
14524 : : COERCION_ASSIGNMENT,
14525 : : COERCE_IMPLICIT_CAST,
14526 : : -1);
14527 [ + + ]: 182 : if (transform == NULL)
14528 : : {
14529 : : /* error text depends on whether USING was specified or not */
14530 [ + + ]: 3 : if (def->cooked_default != NULL)
14531 [ + - + - ]: 1 : ereport(ERROR,
14532 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
14533 : : errmsg("result of USING clause for column \"%s\""
14534 : : " cannot be cast automatically to type %s",
14535 : : colName, format_type_be(targettype)),
14536 : : errhint("You might need to add an explicit cast.")));
14537 : : else
14538 [ + - + - : 2 : ereport(ERROR,
- + ]
14539 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
14540 : : errmsg("column \"%s\" cannot be cast automatically to type %s",
14541 : : colName, format_type_be(targettype)),
14542 : : !attTup->attgenerated ?
14543 : : /* translator: USING is SQL, don't translate it */
14544 : : errhint("You might need to specify \"USING %s::%s\".",
14545 : : quote_identifier(colName),
14546 : : format_type_with_typemod(targettype,
14547 : : targettypmod)) : 0));
14548 : 0 : }
14549 : :
14550 : : /* Fix collations after all else */
14551 : 179 : assign_expr_collations(pstate, transform);
14552 : :
14553 : : /* Expand virtual generated columns in the expr. */
14554 : 179 : transform = expand_generated_columns_in_expr(transform, rel, 1);
14555 : :
14556 : : /* Plan the expr now so we can accurately assess the need to rewrite. */
14557 : 179 : transform = (Node *) expression_planner((Expr *) transform);
14558 : :
14559 : : /*
14560 : : * Add a work queue item to make ATRewriteTable update the column
14561 : : * contents.
14562 : : */
14563 : 179 : newval = palloc0_object(NewColumnValue);
14564 : 179 : newval->attnum = attnum;
14565 : 179 : newval->expr = (Expr *) transform;
14566 : 179 : newval->is_generated = false;
14567 : :
14568 : 179 : tab->newvals = lappend(tab->newvals, newval);
14569 [ + + ]: 179 : if (ATColumnChangeRequiresRewrite(transform, attnum))
14570 : 148 : tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
14571 : 179 : }
14572 [ + + ]: 21 : else if (transform)
14573 [ + - + - ]: 2 : ereport(ERROR,
14574 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
14575 : : errmsg("\"%s\" is not a table",
14576 : : RelationGetRelationName(rel))));
14577 : :
14578 [ + + + - : 202 : if (!RELKIND_HAS_STORAGE(tab->relkind) || attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+ - + - +
+ ]
14579 : : {
14580 : : /*
14581 : : * For relations or columns without storage, do this check now.
14582 : : * Regular tables will check it later when the table is being
14583 : : * rewritten.
14584 : : */
14585 : 36 : find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
14586 : 36 : }
14587 : :
14588 : 194 : ReleaseSysCache(tuple);
14589 : :
14590 : : /*
14591 : : * Recurse manually by queueing a new command for each child, if
14592 : : * necessary. We cannot apply ATSimpleRecursion here because we need to
14593 : : * remap attribute numbers in the USING expression, if any.
14594 : : *
14595 : : * If we are told not to recurse, there had better not be any child
14596 : : * tables; else the alter would put them out of step.
14597 : : */
14598 [ + + ]: 194 : if (recurse)
14599 : : {
14600 : 152 : Oid relid = RelationGetRelid(rel);
14601 : 152 : List *child_oids,
14602 : : *child_numparents;
14603 : 152 : ListCell *lo,
14604 : : *li;
14605 : :
14606 : 152 : child_oids = find_all_inheritors(relid, lockmode,
14607 : : &child_numparents);
14608 : :
14609 : : /*
14610 : : * find_all_inheritors does the recursive search of the inheritance
14611 : : * hierarchy, so all we have to do is process all of the relids in the
14612 : : * list that it returns.
14613 : : */
14614 [ + - + + : 342 : forboth(lo, child_oids, li, child_numparents)
+ - + + +
+ + + ]
14615 : : {
14616 : 192 : Oid childrelid = lfirst_oid(lo);
14617 : 192 : int numparents = lfirst_int(li);
14618 : 192 : Relation childrel;
14619 : 192 : HeapTuple childtuple;
14620 : 192 : Form_pg_attribute childattTup;
14621 : :
14622 [ + + ]: 192 : if (childrelid == relid)
14623 : 154 : continue;
14624 : :
14625 : : /* find_all_inheritors already got lock */
14626 : 38 : childrel = relation_open(childrelid, NoLock);
14627 : 38 : CheckAlterTableIsSafe(childrel);
14628 : :
14629 : : /*
14630 : : * Verify that the child doesn't have any inherited definitions of
14631 : : * this column that came from outside this inheritance hierarchy.
14632 : : * (renameatt makes a similar test, though in a different way
14633 : : * because of its different recursion mechanism.)
14634 : : */
14635 : 76 : childtuple = SearchSysCacheAttName(RelationGetRelid(childrel),
14636 : 38 : colName);
14637 [ + - ]: 38 : if (!HeapTupleIsValid(childtuple))
14638 [ # # # # ]: 0 : ereport(ERROR,
14639 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
14640 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
14641 : : colName, RelationGetRelationName(childrel))));
14642 : 38 : childattTup = (Form_pg_attribute) GETSTRUCT(childtuple);
14643 : :
14644 [ + + ]: 38 : if (childattTup->attinhcount > numparents)
14645 [ + - + - ]: 1 : ereport(ERROR,
14646 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
14647 : : errmsg("cannot alter inherited column \"%s\" of relation \"%s\"",
14648 : : colName, RelationGetRelationName(childrel))));
14649 : :
14650 : 37 : ReleaseSysCache(childtuple);
14651 : :
14652 : : /*
14653 : : * Remap the attribute numbers. If no USING expression was
14654 : : * specified, there is no need for this step.
14655 : : */
14656 [ + + ]: 37 : if (def->cooked_default)
14657 : : {
14658 : 13 : AttrMap *attmap;
14659 : 13 : bool found_whole_row;
14660 : :
14661 : : /* create a copy to scribble on */
14662 : 13 : cmd = copyObject(cmd);
14663 : :
14664 : 26 : attmap = build_attrmap_by_name(RelationGetDescr(childrel),
14665 : 13 : RelationGetDescr(rel),
14666 : : false);
14667 : 13 : ((ColumnDef *) cmd->def)->cooked_default =
14668 : 26 : map_variable_attnos(def->cooked_default,
14669 : : 1, 0,
14670 : 13 : attmap,
14671 : : InvalidOid, &found_whole_row);
14672 [ + + ]: 13 : if (found_whole_row)
14673 [ + - + - ]: 1 : ereport(ERROR,
14674 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
14675 : : errmsg("cannot convert whole-row table reference"),
14676 : : errdetail("USING expression contains a whole-row table reference.")));
14677 : 12 : pfree(attmap);
14678 : 12 : }
14679 : 36 : ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, context);
14680 : 36 : relation_close(childrel, NoLock);
14681 [ - + + ]: 190 : }
14682 : 150 : }
14683 [ + + + - ]: 42 : else if (!recursing &&
14684 : 8 : find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
14685 [ # # # # ]: 0 : ereport(ERROR,
14686 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
14687 : : errmsg("type of inherited column \"%s\" must be changed in child tables too",
14688 : : colName)));
14689 : :
14690 [ + + ]: 192 : if (tab->relkind == RELKIND_COMPOSITE_TYPE)
14691 : 8 : ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);
14692 : 192 : }
14693 : :
14694 : : /*
14695 : : * When the data type of a column is changed, a rewrite might not be required
14696 : : * if the new type is sufficiently identical to the old one, and the USING
14697 : : * clause isn't trying to insert some other value. It's safe to skip the
14698 : : * rewrite in these cases:
14699 : : *
14700 : : * - the old type is binary coercible to the new type
14701 : : * - the new type is an unconstrained domain over the old type
14702 : : * - {NEW,OLD} or {OLD,NEW} is {timestamptz,timestamp} and the timezone is UTC
14703 : : *
14704 : : * In the case of a constrained domain, we could get by with scanning the
14705 : : * table and checking the constraint rather than actually rewriting it, but we
14706 : : * don't currently try to do that.
14707 : : */
14708 : : static bool
14709 : 179 : ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno)
14710 : : {
14711 [ + - ]: 179 : Assert(expr != NULL);
14712 : :
14713 : 196 : for (;;)
14714 : : {
14715 : : /* only one varno, so no need to check that */
14716 [ + + + - ]: 196 : if (IsA(expr, Var) && ((Var *) expr)->varattno == varattno)
14717 : 31 : return false;
14718 [ + + ]: 165 : else if (IsA(expr, RelabelType))
14719 : 15 : expr = (Node *) ((RelabelType *) expr)->arg;
14720 [ - + ]: 150 : else if (IsA(expr, CoerceToDomain))
14721 : : {
14722 : 0 : CoerceToDomain *d = (CoerceToDomain *) expr;
14723 : :
14724 [ # # ]: 0 : if (DomainHasConstraints(d->resulttype))
14725 : 0 : return true;
14726 : 0 : expr = (Node *) d->arg;
14727 [ # # ]: 0 : }
14728 [ + + ]: 150 : else if (IsA(expr, FuncExpr))
14729 : : {
14730 : 120 : FuncExpr *f = (FuncExpr *) expr;
14731 : :
14732 [ + + ]: 120 : switch (f->funcid)
14733 : : {
14734 : : case F_TIMESTAMPTZ_TIMESTAMP:
14735 : : case F_TIMESTAMP_TIMESTAMPTZ:
14736 [ + + ]: 3 : if (TimestampTimestampTzRequiresRewrite())
14737 : 1 : return true;
14738 : : else
14739 : 2 : expr = linitial(f->args);
14740 : 2 : break;
14741 : : default:
14742 : 117 : return true;
14743 : : }
14744 [ + + ]: 120 : }
14745 : : else
14746 : 30 : return true;
14747 : : }
14748 : 179 : }
14749 : :
14750 : : /*
14751 : : * ALTER COLUMN .. SET DATA TYPE
14752 : : *
14753 : : * Return the address of the modified column.
14754 : : */
14755 : : static ObjectAddress
14756 : 185 : ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
14757 : : AlterTableCmd *cmd, LOCKMODE lockmode)
14758 : : {
14759 : 185 : char *colName = cmd->name;
14760 : 185 : ColumnDef *def = (ColumnDef *) cmd->def;
14761 : 185 : TypeName *typeName = def->typeName;
14762 : 185 : HeapTuple heapTup;
14763 : 185 : Form_pg_attribute attTup,
14764 : : attOldTup;
14765 : 185 : AttrNumber attnum;
14766 : 185 : HeapTuple typeTuple;
14767 : 185 : Form_pg_type tform;
14768 : 185 : Oid targettype;
14769 : 185 : int32 targettypmod;
14770 : 185 : Oid targetcollid;
14771 : 185 : Node *defaultexpr;
14772 : 185 : Relation attrelation;
14773 : 185 : Relation depRel;
14774 : 185 : ScanKeyData key[3];
14775 : 185 : SysScanDesc scan;
14776 : 185 : HeapTuple depTup;
14777 : : ObjectAddress address;
14778 : :
14779 : : /*
14780 : : * Clear all the missing values if we're rewriting the table, since this
14781 : : * renders them pointless.
14782 : : */
14783 [ + + ]: 185 : if (tab->rewrite)
14784 : : {
14785 : 138 : Relation newrel;
14786 : :
14787 : 138 : newrel = table_open(RelationGetRelid(rel), NoLock);
14788 : 138 : RelationClearMissing(newrel);
14789 : 138 : relation_close(newrel, NoLock);
14790 : : /* make sure we don't conflict with later attribute modifications */
14791 : 138 : CommandCounterIncrement();
14792 : 138 : }
14793 : :
14794 : 185 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
14795 : :
14796 : : /* Look up the target column */
14797 : 185 : heapTup = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
14798 [ + - ]: 185 : if (!HeapTupleIsValid(heapTup)) /* shouldn't happen */
14799 [ # # # # ]: 0 : ereport(ERROR,
14800 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
14801 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
14802 : : colName, RelationGetRelationName(rel))));
14803 : 185 : attTup = (Form_pg_attribute) GETSTRUCT(heapTup);
14804 : 185 : attnum = attTup->attnum;
14805 : 185 : attOldTup = TupleDescAttr(tab->oldDesc, attnum - 1);
14806 : :
14807 : : /* Check for multiple ALTER TYPE on same column --- can't cope */
14808 [ + - ]: 185 : if (attTup->atttypid != attOldTup->atttypid ||
14809 : 185 : attTup->atttypmod != attOldTup->atttypmod)
14810 [ # # # # ]: 0 : ereport(ERROR,
14811 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
14812 : : errmsg("cannot alter type of column \"%s\" twice",
14813 : : colName)));
14814 : :
14815 : : /* Look up the target type (should not fail, since prep found it) */
14816 : 185 : typeTuple = typenameType(NULL, typeName, &targettypmod);
14817 : 185 : tform = (Form_pg_type) GETSTRUCT(typeTuple);
14818 : 185 : targettype = tform->oid;
14819 : : /* And the collation */
14820 : 185 : targetcollid = GetColumnDefCollation(NULL, def, targettype);
14821 : :
14822 : : /*
14823 : : * If there is a default expression for the column, get it and ensure we
14824 : : * can coerce it to the new datatype. (We must do this before changing
14825 : : * the column type, because build_column_default itself will try to
14826 : : * coerce, and will not issue the error message we want if it fails.)
14827 : : *
14828 : : * We remove any implicit coercion steps at the top level of the old
14829 : : * default expression; this has been agreed to satisfy the principle of
14830 : : * least surprise. (The conversion to the new column type should act like
14831 : : * it started from what the user sees as the stored expression, and the
14832 : : * implicit coercions aren't going to be shown.)
14833 : : */
14834 [ + + ]: 185 : if (attTup->atthasdef)
14835 : : {
14836 : 14 : defaultexpr = build_column_default(rel, attnum);
14837 [ + - ]: 14 : Assert(defaultexpr);
14838 : 14 : defaultexpr = strip_implicit_coercions(defaultexpr);
14839 : 14 : defaultexpr = coerce_to_target_type(NULL, /* no UNKNOWN params */
14840 : 14 : defaultexpr, exprType(defaultexpr),
14841 : 14 : targettype, targettypmod,
14842 : : COERCION_ASSIGNMENT,
14843 : : COERCE_IMPLICIT_CAST,
14844 : : -1);
14845 [ + + ]: 14 : if (defaultexpr == NULL)
14846 : : {
14847 [ - + ]: 1 : if (attTup->attgenerated)
14848 [ # # # # ]: 0 : ereport(ERROR,
14849 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
14850 : : errmsg("generation expression for column \"%s\" cannot be cast automatically to type %s",
14851 : : colName, format_type_be(targettype))));
14852 : : else
14853 [ + - + - ]: 1 : ereport(ERROR,
14854 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
14855 : : errmsg("default for column \"%s\" cannot be cast automatically to type %s",
14856 : : colName, format_type_be(targettype))));
14857 : 0 : }
14858 : 13 : }
14859 : : else
14860 : 171 : defaultexpr = NULL;
14861 : :
14862 : : /*
14863 : : * Find everything that depends on the column (constraints, indexes, etc),
14864 : : * and record enough information to let us recreate the objects.
14865 : : *
14866 : : * The actual recreation does not happen here, but only after we have
14867 : : * performed all the individual ALTER TYPE operations. We have to save
14868 : : * the info before executing ALTER TYPE, though, else the deparser will
14869 : : * get confused.
14870 : : */
14871 : 184 : RememberAllDependentForRebuilding(tab, AT_AlterColumnType, rel, attnum, colName);
14872 : :
14873 : : /*
14874 : : * Now scan for dependencies of this column on other things. The only
14875 : : * things we should find are the dependency on the column datatype and
14876 : : * possibly a collation dependency. Those can be removed.
14877 : : */
14878 : 184 : depRel = table_open(DependRelationId, RowExclusiveLock);
14879 : :
14880 : 368 : ScanKeyInit(&key[0],
14881 : : Anum_pg_depend_classid,
14882 : : BTEqualStrategyNumber, F_OIDEQ,
14883 : 184 : ObjectIdGetDatum(RelationRelationId));
14884 : 368 : ScanKeyInit(&key[1],
14885 : : Anum_pg_depend_objid,
14886 : : BTEqualStrategyNumber, F_OIDEQ,
14887 : 184 : ObjectIdGetDatum(RelationGetRelid(rel)));
14888 : 368 : ScanKeyInit(&key[2],
14889 : : Anum_pg_depend_objsubid,
14890 : : BTEqualStrategyNumber, F_INT4EQ,
14891 : 184 : Int32GetDatum((int32) attnum));
14892 : :
14893 : 368 : scan = systable_beginscan(depRel, DependDependerIndexId, true,
14894 : 184 : NULL, 3, key);
14895 : :
14896 [ - + ]: 184 : while (HeapTupleIsValid(depTup = systable_getnext(scan)))
14897 : : {
14898 : 0 : Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup);
14899 : 0 : ObjectAddress foundObject;
14900 : :
14901 : 0 : foundObject.classId = foundDep->refclassid;
14902 : 0 : foundObject.objectId = foundDep->refobjid;
14903 : 0 : foundObject.objectSubId = foundDep->refobjsubid;
14904 : :
14905 [ # # ]: 0 : if (foundDep->deptype != DEPENDENCY_NORMAL)
14906 [ # # # # ]: 0 : elog(ERROR, "found unexpected dependency type '%c'",
14907 : : foundDep->deptype);
14908 [ # # ]: 0 : if (!(foundDep->refclassid == TypeRelationId &&
14909 [ # # ]: 0 : foundDep->refobjid == attTup->atttypid) &&
14910 [ # # ]: 0 : !(foundDep->refclassid == CollationRelationId &&
14911 : 0 : foundDep->refobjid == attTup->attcollation))
14912 [ # # # # ]: 0 : elog(ERROR, "found unexpected dependency for column: %s",
14913 : : getObjectDescription(&foundObject, false));
14914 : :
14915 : 0 : CatalogTupleDelete(depRel, &depTup->t_self);
14916 : 0 : }
14917 : :
14918 : 184 : systable_endscan(scan);
14919 : :
14920 : 184 : table_close(depRel, RowExclusiveLock);
14921 : :
14922 : : /*
14923 : : * Here we go --- change the recorded column type and collation. (Note
14924 : : * heapTup is a copy of the syscache entry, so okay to scribble on.) First
14925 : : * fix up the missing value if any.
14926 : : */
14927 [ + + ]: 184 : if (attTup->atthasmissing)
14928 : : {
14929 : 1 : Datum missingval;
14930 : 1 : bool missingNull;
14931 : :
14932 : : /* if rewrite is true the missing value should already be cleared */
14933 [ + - ]: 1 : Assert(tab->rewrite == 0);
14934 : :
14935 : : /* Get the missing value datum */
14936 : 2 : missingval = heap_getattr(heapTup,
14937 : : Anum_pg_attribute_attmissingval,
14938 : 1 : attrelation->rd_att,
14939 : : &missingNull);
14940 : :
14941 : : /* if it's a null array there is nothing to do */
14942 : :
14943 [ - + ]: 1 : if (!missingNull)
14944 : : {
14945 : : /*
14946 : : * Get the datum out of the array and repack it in a new array
14947 : : * built with the new type data. We assume that since the table
14948 : : * doesn't need rewriting, the actual Datum doesn't need to be
14949 : : * changed, only the array metadata.
14950 : : */
14951 : :
14952 : 1 : int one = 1;
14953 : 1 : bool isNull;
14954 : 1 : Datum valuesAtt[Natts_pg_attribute] = {0};
14955 : 1 : bool nullsAtt[Natts_pg_attribute] = {0};
14956 : 1 : bool replacesAtt[Natts_pg_attribute] = {0};
14957 : 1 : HeapTuple newTup;
14958 : :
14959 : 2 : missingval = array_get_element(missingval,
14960 : : 1,
14961 : : &one,
14962 : : 0,
14963 : 1 : attTup->attlen,
14964 : 1 : attTup->attbyval,
14965 : 1 : attTup->attalign,
14966 : : &isNull);
14967 : 1 : missingval = PointerGetDatum(construct_array(&missingval,
14968 : : 1,
14969 : 1 : targettype,
14970 : 1 : tform->typlen,
14971 : 1 : tform->typbyval,
14972 : 1 : tform->typalign));
14973 : :
14974 : 1 : valuesAtt[Anum_pg_attribute_attmissingval - 1] = missingval;
14975 : 1 : replacesAtt[Anum_pg_attribute_attmissingval - 1] = true;
14976 : 1 : nullsAtt[Anum_pg_attribute_attmissingval - 1] = false;
14977 : :
14978 : 2 : newTup = heap_modify_tuple(heapTup, RelationGetDescr(attrelation),
14979 : 1 : valuesAtt, nullsAtt, replacesAtt);
14980 : 1 : heap_freetuple(heapTup);
14981 : 1 : heapTup = newTup;
14982 : 1 : attTup = (Form_pg_attribute) GETSTRUCT(heapTup);
14983 : 1 : }
14984 : 1 : }
14985 : :
14986 : 184 : attTup->atttypid = targettype;
14987 : 184 : attTup->atttypmod = targettypmod;
14988 : 184 : attTup->attcollation = targetcollid;
14989 [ + - ]: 184 : if (list_length(typeName->arrayBounds) > PG_INT16_MAX)
14990 [ # # # # ]: 0 : ereport(ERROR,
14991 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
14992 : : errmsg("too many array dimensions"));
14993 : 184 : attTup->attndims = list_length(typeName->arrayBounds);
14994 : 184 : attTup->attlen = tform->typlen;
14995 : 184 : attTup->attbyval = tform->typbyval;
14996 : 184 : attTup->attalign = tform->typalign;
14997 : 184 : attTup->attstorage = tform->typstorage;
14998 : 184 : attTup->attcompression = InvalidCompressionMethod;
14999 : :
15000 : 184 : ReleaseSysCache(typeTuple);
15001 : :
15002 : 184 : CatalogTupleUpdate(attrelation, &heapTup->t_self, heapTup);
15003 : :
15004 : 184 : table_close(attrelation, RowExclusiveLock);
15005 : :
15006 : : /* Install dependencies on new datatype and collation */
15007 : 184 : add_column_datatype_dependency(RelationGetRelid(rel), attnum, targettype);
15008 : 184 : add_column_collation_dependency(RelationGetRelid(rel), attnum, targetcollid);
15009 : :
15010 : : /*
15011 : : * Drop any pg_statistic entry for the column, since it's now wrong type
15012 : : */
15013 : 184 : RemoveStatistics(RelationGetRelid(rel), attnum);
15014 : :
15015 [ + - ]: 184 : InvokeObjectPostAlterHook(RelationRelationId,
15016 : : RelationGetRelid(rel), attnum);
15017 : :
15018 : : /*
15019 : : * Update the default, if present, by brute force --- remove and re-add
15020 : : * the default. Probably unsafe to take shortcuts, since the new version
15021 : : * may well have additional dependencies. (It's okay to do this now,
15022 : : * rather than after other ALTER TYPE commands, since the default won't
15023 : : * depend on other column types.)
15024 : : */
15025 [ + + ]: 184 : if (defaultexpr)
15026 : : {
15027 : : /*
15028 : : * If it's a GENERATED default, drop its dependency records, in
15029 : : * particular its INTERNAL dependency on the column, which would
15030 : : * otherwise cause dependency.c to refuse to perform the deletion.
15031 : : */
15032 [ + + ]: 13 : if (attTup->attgenerated)
15033 : : {
15034 : 6 : Oid attrdefoid = GetAttrDefaultOid(RelationGetRelid(rel), attnum);
15035 : :
15036 [ + - ]: 6 : if (!OidIsValid(attrdefoid))
15037 [ # # # # ]: 0 : elog(ERROR, "could not find attrdef tuple for relation %u attnum %d",
15038 : : RelationGetRelid(rel), attnum);
15039 : 6 : (void) deleteDependencyRecordsFor(AttrDefaultRelationId, attrdefoid, false);
15040 : 6 : }
15041 : :
15042 : : /*
15043 : : * Make updates-so-far visible, particularly the new pg_attribute row
15044 : : * which will be updated again.
15045 : : */
15046 : 13 : CommandCounterIncrement();
15047 : :
15048 : : /*
15049 : : * We use RESTRICT here for safety, but at present we do not expect
15050 : : * anything to depend on the default.
15051 : : */
15052 : 13 : RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, true,
15053 : : true);
15054 : :
15055 : 13 : (void) StoreAttrDefault(rel, attnum, defaultexpr, true);
15056 : 13 : }
15057 : :
15058 : 184 : ObjectAddressSubSet(address, RelationRelationId,
15059 : : RelationGetRelid(rel), attnum);
15060 : :
15061 : : /* Cleanup */
15062 : 184 : heap_freetuple(heapTup);
15063 : :
15064 : : return address;
15065 : 184 : }
15066 : :
15067 : : /*
15068 : : * Subroutine for ATExecAlterColumnType and ATExecSetExpression: Find everything
15069 : : * that depends on the column (constraints, indexes, etc), and record enough
15070 : : * information to let us recreate the objects.
15071 : : */
15072 : : static void
15073 : 201 : RememberAllDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype,
15074 : : Relation rel, AttrNumber attnum, const char *colName)
15075 : : {
15076 : 201 : Relation depRel;
15077 : 201 : ScanKeyData key[3];
15078 : 201 : SysScanDesc scan;
15079 : 201 : HeapTuple depTup;
15080 : :
15081 [ + + + - ]: 201 : Assert(subtype == AT_AlterColumnType || subtype == AT_SetExpression);
15082 : :
15083 : 201 : depRel = table_open(DependRelationId, RowExclusiveLock);
15084 : :
15085 : 402 : ScanKeyInit(&key[0],
15086 : : Anum_pg_depend_refclassid,
15087 : : BTEqualStrategyNumber, F_OIDEQ,
15088 : 201 : ObjectIdGetDatum(RelationRelationId));
15089 : 402 : ScanKeyInit(&key[1],
15090 : : Anum_pg_depend_refobjid,
15091 : : BTEqualStrategyNumber, F_OIDEQ,
15092 : 201 : ObjectIdGetDatum(RelationGetRelid(rel)));
15093 : 402 : ScanKeyInit(&key[2],
15094 : : Anum_pg_depend_refobjsubid,
15095 : : BTEqualStrategyNumber, F_INT4EQ,
15096 : 201 : Int32GetDatum((int32) attnum));
15097 : :
15098 : 402 : scan = systable_beginscan(depRel, DependReferenceIndexId, true,
15099 : 201 : NULL, 3, key);
15100 : :
15101 [ + + ]: 402 : while (HeapTupleIsValid(depTup = systable_getnext(scan)))
15102 : : {
15103 : 207 : Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup);
15104 : 207 : ObjectAddress foundObject;
15105 : :
15106 : 207 : foundObject.classId = foundDep->classid;
15107 : 207 : foundObject.objectId = foundDep->objid;
15108 : 207 : foundObject.objectSubId = foundDep->objsubid;
15109 : :
15110 [ + + - + : 207 : switch (foundObject.classId)
- - + + -
- ]
15111 : : {
15112 : : case RelationRelationId:
15113 : : {
15114 : 46 : char relKind = get_rel_relkind(foundObject.objectId);
15115 : :
15116 [ + + + + ]: 46 : if (relKind == RELKIND_INDEX ||
15117 : 16 : relKind == RELKIND_PARTITIONED_INDEX)
15118 : : {
15119 [ + - ]: 40 : Assert(foundObject.objectSubId == 0);
15120 : 40 : RememberIndexForRebuilding(foundObject.objectId, tab);
15121 : 40 : }
15122 [ - + ]: 6 : else if (relKind == RELKIND_SEQUENCE)
15123 : : {
15124 : : /*
15125 : : * This must be a SERIAL column's sequence. We need
15126 : : * not do anything to it.
15127 : : */
15128 [ - + ]: 6 : Assert(foundObject.objectSubId == 0);
15129 : 6 : }
15130 : : else
15131 : : {
15132 : : /* Not expecting any other direct dependencies... */
15133 [ # # # # ]: 0 : elog(ERROR, "unexpected object depending on column: %s",
15134 : : getObjectDescription(&foundObject, false));
15135 : : }
15136 : : break;
15137 : 46 : }
15138 : :
15139 : : case ConstraintRelationId:
15140 [ + - ]: 113 : Assert(foundObject.objectSubId == 0);
15141 : 113 : RememberConstraintForRebuilding(foundObject.objectId, tab);
15142 : 113 : break;
15143 : :
15144 : : case ProcedureRelationId:
15145 : :
15146 : : /*
15147 : : * A new-style SQL function can depend on a column, if that
15148 : : * column is referenced in the parsed function body. Ideally
15149 : : * we'd automatically update the function by deparsing and
15150 : : * reparsing it, but that's risky and might well fail anyhow.
15151 : : * FIXME someday.
15152 : : *
15153 : : * This is only a problem for AT_AlterColumnType, not
15154 : : * AT_SetExpression.
15155 : : */
15156 [ # # ]: 0 : if (subtype == AT_AlterColumnType)
15157 [ # # # # ]: 0 : ereport(ERROR,
15158 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15159 : : errmsg("cannot alter type of a column used by a function or procedure"),
15160 : : errdetail("%s depends on column \"%s\"",
15161 : : getObjectDescription(&foundObject, false),
15162 : : colName)));
15163 : 0 : break;
15164 : :
15165 : : case RewriteRelationId:
15166 : :
15167 : : /*
15168 : : * View/rule bodies have pretty much the same issues as
15169 : : * function bodies. FIXME someday.
15170 : : */
15171 [ - + ]: 2 : if (subtype == AT_AlterColumnType)
15172 [ + - + - ]: 2 : ereport(ERROR,
15173 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15174 : : errmsg("cannot alter type of a column used by a view or rule"),
15175 : : errdetail("%s depends on column \"%s\"",
15176 : : getObjectDescription(&foundObject, false),
15177 : : colName)));
15178 : 0 : break;
15179 : :
15180 : : case TriggerRelationId:
15181 : :
15182 : : /*
15183 : : * A trigger can depend on a column because the column is
15184 : : * specified as an update target, or because the column is
15185 : : * used in the trigger's WHEN condition. The first case would
15186 : : * not require any extra work, but the second case would
15187 : : * require updating the WHEN expression, which has the same
15188 : : * issues as above. Since we can't easily tell which case
15189 : : * applies, we punt for both. FIXME someday.
15190 : : */
15191 [ # # ]: 0 : if (subtype == AT_AlterColumnType)
15192 [ # # # # ]: 0 : ereport(ERROR,
15193 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15194 : : errmsg("cannot alter type of a column used in a trigger definition"),
15195 : : errdetail("%s depends on column \"%s\"",
15196 : : getObjectDescription(&foundObject, false),
15197 : : colName)));
15198 : 0 : break;
15199 : :
15200 : : case PolicyRelationId:
15201 : :
15202 : : /*
15203 : : * A policy can depend on a column because the column is
15204 : : * specified in the policy's USING or WITH CHECK qual
15205 : : * expressions. It might be possible to rewrite and recheck
15206 : : * the policy expression, but punt for now. It's certainly
15207 : : * easy enough to remove and recreate the policy; still, FIXME
15208 : : * someday.
15209 : : */
15210 [ # # ]: 0 : if (subtype == AT_AlterColumnType)
15211 [ # # # # ]: 0 : ereport(ERROR,
15212 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15213 : : errmsg("cannot alter type of a column used in a policy definition"),
15214 : : errdetail("%s depends on column \"%s\"",
15215 : : getObjectDescription(&foundObject, false),
15216 : : colName)));
15217 : 0 : break;
15218 : :
15219 : : case AttrDefaultRelationId:
15220 : : {
15221 : 34 : ObjectAddress col = GetAttrDefaultColumnAddress(foundObject.objectId);
15222 : :
15223 [ + - + + ]: 34 : if (col.objectId == RelationGetRelid(rel) &&
15224 : 34 : col.objectSubId == attnum)
15225 : : {
15226 : : /*
15227 : : * Ignore the column's own default expression. The
15228 : : * caller deals with it.
15229 : : */
15230 : 30 : }
15231 : : else
15232 : : {
15233 : : /*
15234 : : * This must be a reference from the expression of a
15235 : : * generated column elsewhere in the same table.
15236 : : * Changing the type/generated expression of a column
15237 : : * that is used by a generated column is not allowed
15238 : : * by SQL standard, so just punt for now. It might be
15239 : : * doable with some thinking and effort.
15240 : : */
15241 [ - + ]: 4 : if (subtype == AT_AlterColumnType)
15242 [ + - + - ]: 4 : ereport(ERROR,
15243 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15244 : : errmsg("cannot alter type of a column used by a generated column"),
15245 : : errdetail("Column \"%s\" is used by generated column \"%s\".",
15246 : : colName,
15247 : : get_attname(col.objectId,
15248 : : col.objectSubId,
15249 : : false))));
15250 : : }
15251 : : break;
15252 : 30 : }
15253 : :
15254 : : case StatisticExtRelationId:
15255 : :
15256 : : /*
15257 : : * Give the extended-stats machinery a chance to fix anything
15258 : : * that this column type change would break.
15259 : : */
15260 : 12 : RememberStatisticsForRebuilding(foundObject.objectId, tab);
15261 : 12 : break;
15262 : :
15263 : : case PublicationRelRelationId:
15264 : :
15265 : : /*
15266 : : * Column reference in a PUBLICATION ... FOR TABLE ... WHERE
15267 : : * clause. Same issues as above. FIXME someday.
15268 : : */
15269 [ # # ]: 0 : if (subtype == AT_AlterColumnType)
15270 [ # # # # ]: 0 : ereport(ERROR,
15271 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15272 : : errmsg("cannot alter type of a column used by a publication WHERE clause"),
15273 : : errdetail("%s depends on column \"%s\"",
15274 : : getObjectDescription(&foundObject, false),
15275 : : colName)));
15276 : 0 : break;
15277 : :
15278 : : default:
15279 : :
15280 : : /*
15281 : : * We don't expect any other sorts of objects to depend on a
15282 : : * column.
15283 : : */
15284 [ # # # # ]: 0 : elog(ERROR, "unexpected object depending on column: %s",
15285 : : getObjectDescription(&foundObject, false));
15286 : 0 : break;
15287 : : }
15288 : 201 : }
15289 : :
15290 : 195 : systable_endscan(scan);
15291 : 195 : table_close(depRel, NoLock);
15292 : 195 : }
15293 : :
15294 : : /*
15295 : : * Subroutine for ATExecAlterColumnType: remember that a replica identity
15296 : : * needs to be reset.
15297 : : */
15298 : : static void
15299 : 75 : RememberReplicaIdentityForRebuilding(Oid indoid, AlteredTableInfo *tab)
15300 : : {
15301 [ + + ]: 75 : if (!get_index_isreplident(indoid))
15302 : 72 : return;
15303 : :
15304 [ + - ]: 3 : if (tab->replicaIdentityIndex)
15305 [ # # # # ]: 0 : elog(ERROR, "relation %u has multiple indexes marked as replica identity", tab->relid);
15306 : :
15307 : 3 : tab->replicaIdentityIndex = get_rel_name(indoid);
15308 : 75 : }
15309 : :
15310 : : /*
15311 : : * Subroutine for ATExecAlterColumnType: remember any clustered index.
15312 : : */
15313 : : static void
15314 : 75 : RememberClusterOnForRebuilding(Oid indoid, AlteredTableInfo *tab)
15315 : : {
15316 [ + + ]: 75 : if (!get_index_isclustered(indoid))
15317 : 72 : return;
15318 : :
15319 [ + - ]: 3 : if (tab->clusterOnIndex)
15320 [ # # # # ]: 0 : elog(ERROR, "relation %u has multiple clustered indexes", tab->relid);
15321 : :
15322 : 3 : tab->clusterOnIndex = get_rel_name(indoid);
15323 : 75 : }
15324 : :
15325 : : /*
15326 : : * Subroutine for ATExecAlterColumnType: remember that a constraint needs
15327 : : * to be rebuilt (which we might already know).
15328 : : */
15329 : : static void
15330 : 115 : RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab)
15331 : : {
15332 : : /*
15333 : : * This de-duplication check is critical for two independent reasons: we
15334 : : * mustn't try to recreate the same constraint twice, and if a constraint
15335 : : * depends on more than one column whose type is to be altered, we must
15336 : : * capture its definition string before applying any of the column type
15337 : : * changes. ruleutils.c will get confused if we ask again later.
15338 : : */
15339 [ + + ]: 115 : if (!list_member_oid(tab->changedConstraintOids, conoid))
15340 : : {
15341 : : /* OK, capture the constraint's existing definition string */
15342 : 100 : char *defstring = pg_get_constraintdef_command(conoid);
15343 : 100 : Oid indoid;
15344 : :
15345 : : /*
15346 : : * It is critical to create not-null constraints ahead of primary key
15347 : : * indexes; otherwise, the not-null constraint would be created by the
15348 : : * primary key, and the constraint name would be wrong.
15349 : : */
15350 [ + + ]: 100 : if (get_constraint_type(conoid) == CONSTRAINT_NOTNULL)
15351 : : {
15352 : 64 : tab->changedConstraintOids = lcons_oid(conoid,
15353 : 32 : tab->changedConstraintOids);
15354 : 64 : tab->changedConstraintDefs = lcons(defstring,
15355 : 32 : tab->changedConstraintDefs);
15356 : 32 : }
15357 : : else
15358 : : {
15359 : :
15360 : 136 : tab->changedConstraintOids = lappend_oid(tab->changedConstraintOids,
15361 : 68 : conoid);
15362 : 136 : tab->changedConstraintDefs = lappend(tab->changedConstraintDefs,
15363 : 68 : defstring);
15364 : : }
15365 : :
15366 : : /*
15367 : : * For the index of a constraint, if any, remember if it is used for
15368 : : * the table's replica identity or if it is a clustered index, so that
15369 : : * ATPostAlterTypeCleanup() can queue up commands necessary to restore
15370 : : * those properties.
15371 : : */
15372 : 100 : indoid = get_constraint_index(conoid);
15373 [ + + ]: 100 : if (OidIsValid(indoid))
15374 : : {
15375 : 38 : RememberReplicaIdentityForRebuilding(indoid, tab);
15376 : 38 : RememberClusterOnForRebuilding(indoid, tab);
15377 : 38 : }
15378 : 100 : }
15379 : 115 : }
15380 : :
15381 : : /*
15382 : : * Subroutine for ATExecAlterColumnType: remember that an index needs
15383 : : * to be rebuilt (which we might already know).
15384 : : */
15385 : : static void
15386 : 40 : RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab)
15387 : : {
15388 : : /*
15389 : : * This de-duplication check is critical for two independent reasons: we
15390 : : * mustn't try to recreate the same index twice, and if an index depends
15391 : : * on more than one column whose type is to be altered, we must capture
15392 : : * its definition string before applying any of the column type changes.
15393 : : * ruleutils.c will get confused if we ask again later.
15394 : : */
15395 [ + + ]: 40 : if (!list_member_oid(tab->changedIndexOids, indoid))
15396 : : {
15397 : : /*
15398 : : * Before adding it as an index-to-rebuild, we'd better see if it
15399 : : * belongs to a constraint, and if so rebuild the constraint instead.
15400 : : * Typically this check fails, because constraint indexes normally
15401 : : * have only dependencies on their constraint. But it's possible for
15402 : : * such an index to also have direct dependencies on table columns,
15403 : : * for example with a partial exclusion constraint.
15404 : : */
15405 : 39 : Oid conoid = get_index_constraint(indoid);
15406 : :
15407 [ + + ]: 39 : if (OidIsValid(conoid))
15408 : : {
15409 : 2 : RememberConstraintForRebuilding(conoid, tab);
15410 : 2 : }
15411 : : else
15412 : : {
15413 : : /* OK, capture the index's existing definition string */
15414 : 37 : char *defstring = pg_get_indexdef_string(indoid);
15415 : :
15416 : 74 : tab->changedIndexOids = lappend_oid(tab->changedIndexOids,
15417 : 37 : indoid);
15418 : 74 : tab->changedIndexDefs = lappend(tab->changedIndexDefs,
15419 : 37 : defstring);
15420 : :
15421 : : /*
15422 : : * Remember if this index is used for the table's replica identity
15423 : : * or if it is a clustered index, so that ATPostAlterTypeCleanup()
15424 : : * can queue up commands necessary to restore those properties.
15425 : : */
15426 : 37 : RememberReplicaIdentityForRebuilding(indoid, tab);
15427 : 37 : RememberClusterOnForRebuilding(indoid, tab);
15428 : 37 : }
15429 : 39 : }
15430 : 40 : }
15431 : :
15432 : : /*
15433 : : * Subroutine for ATExecAlterColumnType: remember that a statistics object
15434 : : * needs to be rebuilt (which we might already know).
15435 : : */
15436 : : static void
15437 : 12 : RememberStatisticsForRebuilding(Oid stxoid, AlteredTableInfo *tab)
15438 : : {
15439 : : /*
15440 : : * This de-duplication check is critical for two independent reasons: we
15441 : : * mustn't try to recreate the same statistics object twice, and if the
15442 : : * statistics object depends on more than one column whose type is to be
15443 : : * altered, we must capture its definition string before applying any of
15444 : : * the type changes. ruleutils.c will get confused if we ask again later.
15445 : : */
15446 [ - + ]: 12 : if (!list_member_oid(tab->changedStatisticsOids, stxoid))
15447 : : {
15448 : : /* OK, capture the statistics object's existing definition string */
15449 : 12 : char *defstring = pg_get_statisticsobjdef_string(stxoid);
15450 : :
15451 : 24 : tab->changedStatisticsOids = lappend_oid(tab->changedStatisticsOids,
15452 : 12 : stxoid);
15453 : 24 : tab->changedStatisticsDefs = lappend(tab->changedStatisticsDefs,
15454 : 12 : defstring);
15455 : 12 : }
15456 : 12 : }
15457 : :
15458 : : /*
15459 : : * Cleanup after we've finished all the ALTER TYPE or SET EXPRESSION
15460 : : * operations for a particular relation. We have to drop and recreate all the
15461 : : * indexes and constraints that depend on the altered columns. We do the
15462 : : * actual dropping here, but re-creation is managed by adding work queue
15463 : : * entries to do those steps later.
15464 : : */
15465 : : static void
15466 : 203 : ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
15467 : : {
15468 : 203 : ObjectAddress obj;
15469 : 203 : ObjectAddresses *objects;
15470 : 203 : ListCell *def_item;
15471 : 203 : ListCell *oid_item;
15472 : :
15473 : : /*
15474 : : * Collect all the constraints and indexes to drop so we can process them
15475 : : * in a single call. That way we don't have to worry about dependencies
15476 : : * among them.
15477 : : */
15478 : 203 : objects = new_object_addresses();
15479 : :
15480 : : /*
15481 : : * Re-parse the index and constraint definitions, and attach them to the
15482 : : * appropriate work queue entries. We do this before dropping because in
15483 : : * the case of a constraint on another table, we might not yet have
15484 : : * exclusive lock on the table the constraint is attached to, and we need
15485 : : * to get that before reparsing/dropping. (That's possible at least for
15486 : : * FOREIGN KEY, CHECK, and EXCLUSION constraints; in non-FK cases it
15487 : : * requires a dependency on the target table's composite type in the other
15488 : : * table's constraint expressions.)
15489 : : *
15490 : : * We can't rely on the output of deparsing to tell us which relation to
15491 : : * operate on, because concurrent activity might have made the name
15492 : : * resolve differently. Instead, we've got to use the OID of the
15493 : : * constraint or index we're processing to figure out which relation to
15494 : : * operate on.
15495 : : */
15496 [ + + + + : 303 : forboth(oid_item, tab->changedConstraintOids,
+ + + + +
+ + + ]
15497 : : def_item, tab->changedConstraintDefs)
15498 : : {
15499 : 100 : Oid oldId = lfirst_oid(oid_item);
15500 : 100 : HeapTuple tup;
15501 : 100 : Form_pg_constraint con;
15502 : 100 : Oid relid;
15503 : 100 : Oid confrelid;
15504 : 100 : bool conislocal;
15505 : :
15506 : 100 : tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(oldId));
15507 [ + - ]: 100 : if (!HeapTupleIsValid(tup)) /* should not happen */
15508 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u", oldId);
15509 : 100 : con = (Form_pg_constraint) GETSTRUCT(tup);
15510 [ + + ]: 100 : if (OidIsValid(con->conrelid))
15511 : 98 : relid = con->conrelid;
15512 : : else
15513 : : {
15514 : : /* must be a domain constraint */
15515 : 2 : relid = get_typ_typrelid(getBaseType(con->contypid));
15516 [ + - ]: 2 : if (!OidIsValid(relid))
15517 [ # # # # ]: 0 : elog(ERROR, "could not identify relation associated with constraint %u", oldId);
15518 : : }
15519 : 100 : confrelid = con->confrelid;
15520 : 100 : conislocal = con->conislocal;
15521 : 100 : ReleaseSysCache(tup);
15522 : :
15523 : 100 : ObjectAddressSet(obj, ConstraintRelationId, oldId);
15524 : 100 : add_exact_object_address(&obj, objects);
15525 : :
15526 : : /*
15527 : : * If the constraint is inherited (only), we don't want to inject a
15528 : : * new definition here; it'll get recreated when
15529 : : * ATAddCheckNNConstraint recurses from adding the parent table's
15530 : : * constraint. But we had to carry the info this far so that we can
15531 : : * drop the constraint below.
15532 : : */
15533 [ + + ]: 100 : if (!conislocal)
15534 : 4 : continue;
15535 : :
15536 : : /*
15537 : : * When rebuilding another table's constraint that references the
15538 : : * table we're modifying, we might not yet have any lock on the other
15539 : : * table, so get one now. We'll need AccessExclusiveLock for the DROP
15540 : : * CONSTRAINT step, so there's no value in asking for anything weaker.
15541 : : */
15542 [ + + ]: 96 : if (relid != tab->relid)
15543 : 8 : LockRelationOid(relid, AccessExclusiveLock);
15544 : :
15545 : 192 : ATPostAlterTypeParse(oldId, relid, confrelid,
15546 : 96 : (char *) lfirst(def_item),
15547 : 96 : wqueue, lockmode, tab->rewrite);
15548 [ - + + ]: 100 : }
15549 [ + + + + : 240 : forboth(oid_item, tab->changedIndexOids,
+ + + + +
+ + + ]
15550 : : def_item, tab->changedIndexDefs)
15551 : : {
15552 : 37 : Oid oldId = lfirst_oid(oid_item);
15553 : 37 : Oid relid;
15554 : :
15555 : 37 : relid = IndexGetRelation(oldId, false);
15556 : :
15557 : : /*
15558 : : * As above, make sure we have lock on the index's table if it's not
15559 : : * the same table.
15560 : : */
15561 [ + + ]: 37 : if (relid != tab->relid)
15562 : 2 : LockRelationOid(relid, AccessExclusiveLock);
15563 : :
15564 : 74 : ATPostAlterTypeParse(oldId, relid, InvalidOid,
15565 : 37 : (char *) lfirst(def_item),
15566 : 37 : wqueue, lockmode, tab->rewrite);
15567 : :
15568 : 37 : ObjectAddressSet(obj, RelationRelationId, oldId);
15569 : 37 : add_exact_object_address(&obj, objects);
15570 : 37 : }
15571 : :
15572 : : /* add dependencies for new statistics */
15573 [ + + + + : 215 : forboth(oid_item, tab->changedStatisticsOids,
+ + + + +
+ + + ]
15574 : : def_item, tab->changedStatisticsDefs)
15575 : : {
15576 : 12 : Oid oldId = lfirst_oid(oid_item);
15577 : 12 : Oid relid;
15578 : :
15579 : 12 : relid = StatisticsGetRelation(oldId, false);
15580 : :
15581 : : /*
15582 : : * As above, make sure we have lock on the statistics object's table
15583 : : * if it's not the same table. However, we take
15584 : : * ShareUpdateExclusiveLock here, aligning with the lock level used in
15585 : : * CreateStatistics and RemoveStatisticsById.
15586 : : *
15587 : : * CAUTION: this should be done after all cases that grab
15588 : : * AccessExclusiveLock, else we risk causing deadlock due to needing
15589 : : * to promote our table lock.
15590 : : */
15591 [ + + ]: 12 : if (relid != tab->relid)
15592 : 2 : LockRelationOid(relid, ShareUpdateExclusiveLock);
15593 : :
15594 : 24 : ATPostAlterTypeParse(oldId, relid, InvalidOid,
15595 : 12 : (char *) lfirst(def_item),
15596 : 12 : wqueue, lockmode, tab->rewrite);
15597 : :
15598 : 12 : ObjectAddressSet(obj, StatisticExtRelationId, oldId);
15599 : 12 : add_exact_object_address(&obj, objects);
15600 : 12 : }
15601 : :
15602 : : /*
15603 : : * Queue up command to restore replica identity index marking
15604 : : */
15605 [ + + ]: 203 : if (tab->replicaIdentityIndex)
15606 : : {
15607 : 3 : AlterTableCmd *cmd = makeNode(AlterTableCmd);
15608 : 3 : ReplicaIdentityStmt *subcmd = makeNode(ReplicaIdentityStmt);
15609 : :
15610 : 3 : subcmd->identity_type = REPLICA_IDENTITY_INDEX;
15611 : 3 : subcmd->name = tab->replicaIdentityIndex;
15612 : 3 : cmd->subtype = AT_ReplicaIdentity;
15613 : 3 : cmd->def = (Node *) subcmd;
15614 : :
15615 : : /* do it after indexes and constraints */
15616 : 3 : tab->subcmds[AT_PASS_OLD_CONSTR] =
15617 : 3 : lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
15618 : 3 : }
15619 : :
15620 : : /*
15621 : : * Queue up command to restore marking of index used for cluster.
15622 : : */
15623 [ + + ]: 203 : if (tab->clusterOnIndex)
15624 : : {
15625 : 3 : AlterTableCmd *cmd = makeNode(AlterTableCmd);
15626 : :
15627 : 3 : cmd->subtype = AT_ClusterOn;
15628 : 3 : cmd->name = tab->clusterOnIndex;
15629 : :
15630 : : /* do it after indexes and constraints */
15631 : 3 : tab->subcmds[AT_PASS_OLD_CONSTR] =
15632 : 3 : lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
15633 : 3 : }
15634 : :
15635 : : /*
15636 : : * It should be okay to use DROP_RESTRICT here, since nothing else should
15637 : : * be depending on these objects.
15638 : : */
15639 : 203 : performMultipleDeletions(objects, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
15640 : :
15641 : 203 : free_object_addresses(objects);
15642 : :
15643 : : /*
15644 : : * The objects will get recreated during subsequent passes over the work
15645 : : * queue.
15646 : : */
15647 : 203 : }
15648 : :
15649 : : /*
15650 : : * Parse the previously-saved definition string for a constraint, index or
15651 : : * statistics object against the newly-established column data type(s), and
15652 : : * queue up the resulting command parsetrees for execution.
15653 : : *
15654 : : * This might fail if, for example, you have a WHERE clause that uses an
15655 : : * operator that's not available for the new column type.
15656 : : */
15657 : : static void
15658 : 145 : ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
15659 : : List **wqueue, LOCKMODE lockmode, bool rewrite)
15660 : : {
15661 : 145 : List *raw_parsetree_list;
15662 : 145 : List *querytree_list;
15663 : 145 : ListCell *list_item;
15664 : 145 : Relation rel;
15665 : :
15666 : : /*
15667 : : * We expect that we will get only ALTER TABLE and CREATE INDEX
15668 : : * statements. Hence, there is no need to pass them through
15669 : : * parse_analyze_*() or the rewriter, but instead we need to pass them
15670 : : * through parse_utilcmd.c to make them ready for execution.
15671 : : */
15672 : 145 : raw_parsetree_list = raw_parser(cmd, RAW_PARSE_DEFAULT);
15673 : 145 : querytree_list = NIL;
15674 [ + - + + : 290 : foreach(list_item, raw_parsetree_list)
+ + ]
15675 : : {
15676 : 145 : RawStmt *rs = lfirst_node(RawStmt, list_item);
15677 : 145 : Node *stmt = rs->stmt;
15678 : :
15679 [ + + ]: 145 : if (IsA(stmt, IndexStmt))
15680 : 74 : querytree_list = lappend(querytree_list,
15681 : 74 : transformIndexStmt(oldRelId,
15682 : 37 : (IndexStmt *) stmt,
15683 : 37 : cmd));
15684 [ + + ]: 108 : else if (IsA(stmt, AlterTableStmt))
15685 : : {
15686 : 94 : List *beforeStmts;
15687 : 94 : List *afterStmts;
15688 : :
15689 : 188 : stmt = (Node *) transformAlterTableStmt(oldRelId,
15690 : 94 : (AlterTableStmt *) stmt,
15691 : 94 : cmd,
15692 : : &beforeStmts,
15693 : : &afterStmts);
15694 : 94 : querytree_list = list_concat(querytree_list, beforeStmts);
15695 : 94 : querytree_list = lappend(querytree_list, stmt);
15696 : 94 : querytree_list = list_concat(querytree_list, afterStmts);
15697 : 94 : }
15698 [ + + ]: 14 : else if (IsA(stmt, CreateStatsStmt))
15699 : 24 : querytree_list = lappend(querytree_list,
15700 : 24 : transformStatsStmt(oldRelId,
15701 : 12 : (CreateStatsStmt *) stmt,
15702 : 12 : cmd));
15703 : : else
15704 : 2 : querytree_list = lappend(querytree_list, stmt);
15705 : 145 : }
15706 : :
15707 : : /* Caller should already have acquired whatever lock we need. */
15708 : 145 : rel = relation_open(oldRelId, NoLock);
15709 : :
15710 : : /*
15711 : : * Attach each generated command to the proper place in the work queue.
15712 : : * Note this could result in creation of entirely new work-queue entries.
15713 : : *
15714 : : * Also note that we have to tweak the command subtypes, because it turns
15715 : : * out that re-creation of indexes and constraints has to act a bit
15716 : : * differently from initial creation.
15717 : : */
15718 [ + - + + : 290 : foreach(list_item, querytree_list)
+ + ]
15719 : : {
15720 : 145 : Node *stm = (Node *) lfirst(list_item);
15721 : 145 : AlteredTableInfo *tab;
15722 : :
15723 : 145 : tab = ATGetQueueEntry(wqueue, rel);
15724 : :
15725 [ + + ]: 145 : if (IsA(stm, IndexStmt))
15726 : : {
15727 : 37 : IndexStmt *stmt = (IndexStmt *) stm;
15728 : 37 : AlterTableCmd *newcmd;
15729 : :
15730 [ + + ]: 37 : if (!rewrite)
15731 : 9 : TryReuseIndex(oldId, stmt);
15732 : 37 : stmt->reset_default_tblspc = true;
15733 : : /* keep the index's comment */
15734 : 37 : stmt->idxcomment = GetComment(oldId, RelationRelationId, 0);
15735 : :
15736 : 37 : newcmd = makeNode(AlterTableCmd);
15737 : 37 : newcmd->subtype = AT_ReAddIndex;
15738 : 37 : newcmd->def = (Node *) stmt;
15739 : 37 : tab->subcmds[AT_PASS_OLD_INDEX] =
15740 : 37 : lappend(tab->subcmds[AT_PASS_OLD_INDEX], newcmd);
15741 : 37 : }
15742 [ + + ]: 108 : else if (IsA(stm, AlterTableStmt))
15743 : : {
15744 : 94 : AlterTableStmt *stmt = (AlterTableStmt *) stm;
15745 : 94 : ListCell *lcmd;
15746 : :
15747 [ + - + + : 188 : foreach(lcmd, stmt->cmds)
+ + ]
15748 : : {
15749 : 94 : AlterTableCmd *cmd = lfirst_node(AlterTableCmd, lcmd);
15750 : :
15751 [ + + ]: 94 : if (cmd->subtype == AT_AddIndex)
15752 : : {
15753 : 38 : IndexStmt *indstmt;
15754 : 38 : Oid indoid;
15755 : :
15756 : 38 : indstmt = castNode(IndexStmt, cmd->def);
15757 : 38 : indoid = get_constraint_index(oldId);
15758 : :
15759 [ + + ]: 38 : if (!rewrite)
15760 : 8 : TryReuseIndex(indoid, indstmt);
15761 : : /* keep any comment on the index */
15762 : 38 : indstmt->idxcomment = GetComment(indoid,
15763 : : RelationRelationId, 0);
15764 : 38 : indstmt->reset_default_tblspc = true;
15765 : :
15766 : 38 : cmd->subtype = AT_ReAddIndex;
15767 : 38 : tab->subcmds[AT_PASS_OLD_INDEX] =
15768 : 38 : lappend(tab->subcmds[AT_PASS_OLD_INDEX], cmd);
15769 : :
15770 : : /* recreate any comment on the constraint */
15771 : 76 : RebuildConstraintComment(tab,
15772 : : AT_PASS_OLD_INDEX,
15773 : 38 : oldId,
15774 : 38 : rel,
15775 : : NIL,
15776 : 38 : indstmt->idxname);
15777 : 38 : }
15778 [ + - ]: 56 : else if (cmd->subtype == AT_AddConstraint)
15779 : : {
15780 : 56 : Constraint *con = castNode(Constraint, cmd->def);
15781 : :
15782 : 56 : con->old_pktable_oid = refRelId;
15783 : : /* rewriting neither side of a FK */
15784 [ + + ]: 56 : if (con->contype == CONSTR_FOREIGN &&
15785 [ + + - + ]: 12 : !rewrite && tab->rewrite == 0)
15786 : 1 : TryReuseForeignKey(oldId, con);
15787 : 56 : con->reset_default_tblspc = true;
15788 : 56 : cmd->subtype = AT_ReAddConstraint;
15789 : 56 : tab->subcmds[AT_PASS_OLD_CONSTR] =
15790 : 56 : lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
15791 : :
15792 : : /*
15793 : : * Recreate any comment on the constraint. If we have
15794 : : * recreated a primary key, then transformTableConstraint
15795 : : * has added an unnamed not-null constraint here; skip
15796 : : * this in that case.
15797 : : */
15798 [ + - ]: 56 : if (con->conname)
15799 : 112 : RebuildConstraintComment(tab,
15800 : : AT_PASS_OLD_CONSTR,
15801 : 56 : oldId,
15802 : 56 : rel,
15803 : : NIL,
15804 : 56 : con->conname);
15805 : : else
15806 [ # # ]: 0 : Assert(con->contype == CONSTR_NOTNULL);
15807 : 56 : }
15808 : : else
15809 [ # # # # ]: 0 : elog(ERROR, "unexpected statement subtype: %d",
15810 : : (int) cmd->subtype);
15811 : 94 : }
15812 : 94 : }
15813 [ + + ]: 14 : else if (IsA(stm, AlterDomainStmt))
15814 : : {
15815 : 2 : AlterDomainStmt *stmt = (AlterDomainStmt *) stm;
15816 : :
15817 [ + - ]: 2 : if (stmt->subtype == AD_AddConstraint)
15818 : : {
15819 : 2 : Constraint *con = castNode(Constraint, stmt->def);
15820 : 2 : AlterTableCmd *cmd = makeNode(AlterTableCmd);
15821 : :
15822 : 2 : cmd->subtype = AT_ReAddDomainConstraint;
15823 : 2 : cmd->def = (Node *) stmt;
15824 : 2 : tab->subcmds[AT_PASS_OLD_CONSTR] =
15825 : 2 : lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
15826 : :
15827 : : /* recreate any comment on the constraint */
15828 : 4 : RebuildConstraintComment(tab,
15829 : : AT_PASS_OLD_CONSTR,
15830 : 2 : oldId,
15831 : : NULL,
15832 : 2 : stmt->typeName,
15833 : 2 : con->conname);
15834 : 2 : }
15835 : : else
15836 [ # # # # ]: 0 : elog(ERROR, "unexpected statement subtype: %d",
15837 : : (int) stmt->subtype);
15838 : 2 : }
15839 [ + - ]: 12 : else if (IsA(stm, CreateStatsStmt))
15840 : : {
15841 : 12 : CreateStatsStmt *stmt = (CreateStatsStmt *) stm;
15842 : 12 : AlterTableCmd *newcmd;
15843 : :
15844 : : /* keep the statistics object's comment */
15845 : 12 : stmt->stxcomment = GetComment(oldId, StatisticExtRelationId, 0);
15846 : :
15847 : 12 : newcmd = makeNode(AlterTableCmd);
15848 : 12 : newcmd->subtype = AT_ReAddStatistics;
15849 : 12 : newcmd->def = (Node *) stmt;
15850 : 12 : tab->subcmds[AT_PASS_MISC] =
15851 : 12 : lappend(tab->subcmds[AT_PASS_MISC], newcmd);
15852 : 12 : }
15853 : : else
15854 [ # # # # ]: 0 : elog(ERROR, "unexpected statement type: %d",
15855 : : (int) nodeTag(stm));
15856 : 145 : }
15857 : :
15858 : 145 : relation_close(rel, NoLock);
15859 : 145 : }
15860 : :
15861 : : /*
15862 : : * Subroutine for ATPostAlterTypeParse() to recreate any existing comment
15863 : : * for a table or domain constraint that is being rebuilt.
15864 : : *
15865 : : * objid is the OID of the constraint.
15866 : : * Pass "rel" for a table constraint, or "domname" (domain's qualified name
15867 : : * as a string list) for a domain constraint.
15868 : : * (We could dig that info, as well as the conname, out of the pg_constraint
15869 : : * entry; but callers already have them so might as well pass them.)
15870 : : */
15871 : : static void
15872 : 96 : RebuildConstraintComment(AlteredTableInfo *tab, AlterTablePass pass, Oid objid,
15873 : : Relation rel, List *domname,
15874 : : const char *conname)
15875 : : {
15876 : 96 : CommentStmt *cmd;
15877 : 96 : char *comment_str;
15878 : 96 : AlterTableCmd *newcmd;
15879 : :
15880 : : /* Look for comment for object wanted, and leave if none */
15881 : 96 : comment_str = GetComment(objid, ConstraintRelationId, 0);
15882 [ + + ]: 96 : if (comment_str == NULL)
15883 : 81 : return;
15884 : :
15885 : : /* Build CommentStmt node, copying all input data for safety */
15886 : 15 : cmd = makeNode(CommentStmt);
15887 [ + + ]: 15 : if (rel)
15888 : : {
15889 : 13 : cmd->objtype = OBJECT_TABCONSTRAINT;
15890 : 13 : cmd->object = (Node *)
15891 : 13 : list_make3(makeString(get_namespace_name(RelationGetNamespace(rel))),
15892 : : makeString(pstrdup(RelationGetRelationName(rel))),
15893 : : makeString(pstrdup(conname)));
15894 : 13 : }
15895 : : else
15896 : : {
15897 : 2 : cmd->objtype = OBJECT_DOMCONSTRAINT;
15898 : 2 : cmd->object = (Node *)
15899 : 2 : list_make2(makeTypeNameFromNameList(copyObject(domname)),
15900 : : makeString(pstrdup(conname)));
15901 : : }
15902 : 15 : cmd->comment = comment_str;
15903 : :
15904 : : /* Append it to list of commands */
15905 : 15 : newcmd = makeNode(AlterTableCmd);
15906 : 15 : newcmd->subtype = AT_ReAddComment;
15907 : 15 : newcmd->def = (Node *) cmd;
15908 : 15 : tab->subcmds[pass] = lappend(tab->subcmds[pass], newcmd);
15909 [ - + ]: 96 : }
15910 : :
15911 : : /*
15912 : : * Subroutine for ATPostAlterTypeParse(). Calls out to CheckIndexCompatible()
15913 : : * for the real analysis, then mutates the IndexStmt based on that verdict.
15914 : : */
15915 : : static void
15916 : 17 : TryReuseIndex(Oid oldId, IndexStmt *stmt)
15917 : : {
15918 [ - + - + ]: 34 : if (CheckIndexCompatible(oldId,
15919 : 17 : stmt->accessMethod,
15920 : 17 : stmt->indexParams,
15921 : 17 : stmt->excludeOpNames,
15922 : 17 : stmt->iswithoutoverlaps))
15923 : : {
15924 : 17 : Relation irel = index_open(oldId, NoLock);
15925 : :
15926 : : /* If it's a partitioned index, there is no storage to share. */
15927 [ + + ]: 17 : if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
15928 : : {
15929 : 12 : stmt->oldNumber = irel->rd_locator.relNumber;
15930 : 12 : stmt->oldCreateSubid = irel->rd_createSubid;
15931 : 12 : stmt->oldFirstRelfilelocatorSubid = irel->rd_firstRelfilelocatorSubid;
15932 : 12 : }
15933 : 17 : index_close(irel, NoLock);
15934 : 17 : }
15935 : 17 : }
15936 : :
15937 : : /*
15938 : : * Subroutine for ATPostAlterTypeParse().
15939 : : *
15940 : : * Stash the old P-F equality operator into the Constraint node, for possible
15941 : : * use by ATAddForeignKeyConstraint() in determining whether revalidation of
15942 : : * this constraint can be skipped.
15943 : : */
15944 : : static void
15945 : 1 : TryReuseForeignKey(Oid oldId, Constraint *con)
15946 : : {
15947 : 1 : HeapTuple tup;
15948 : 1 : Datum adatum;
15949 : 1 : ArrayType *arr;
15950 : 1 : Oid *rawarr;
15951 : 1 : int numkeys;
15952 : 1 : int i;
15953 : :
15954 [ + - ]: 1 : Assert(con->contype == CONSTR_FOREIGN);
15955 [ + - ]: 1 : Assert(con->old_conpfeqop == NIL); /* already prepared this node */
15956 : :
15957 : 1 : tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(oldId));
15958 [ + - ]: 1 : if (!HeapTupleIsValid(tup)) /* should not happen */
15959 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u", oldId);
15960 : :
15961 : 1 : adatum = SysCacheGetAttrNotNull(CONSTROID, tup,
15962 : : Anum_pg_constraint_conpfeqop);
15963 : 1 : arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
15964 : 1 : numkeys = ARR_DIMS(arr)[0];
15965 : : /* test follows the one in ri_FetchConstraintInfo() */
15966 [ + - ]: 1 : if (ARR_NDIM(arr) != 1 ||
15967 : 1 : ARR_HASNULL(arr) ||
15968 : 1 : ARR_ELEMTYPE(arr) != OIDOID)
15969 [ # # # # ]: 0 : elog(ERROR, "conpfeqop is not a 1-D Oid array");
15970 [ - + ]: 1 : rawarr = (Oid *) ARR_DATA_PTR(arr);
15971 : :
15972 : : /* stash a List of the operator Oids in our Constraint node */
15973 [ + + ]: 2 : for (i = 0; i < numkeys; i++)
15974 : 1 : con->old_conpfeqop = lappend_oid(con->old_conpfeqop, rawarr[i]);
15975 : :
15976 : 1 : ReleaseSysCache(tup);
15977 : 1 : }
15978 : :
15979 : : /*
15980 : : * ALTER COLUMN .. OPTIONS ( ... )
15981 : : *
15982 : : * Returns the address of the modified column
15983 : : */
15984 : : static ObjectAddress
15985 : 7 : ATExecAlterColumnGenericOptions(Relation rel,
15986 : : const char *colName,
15987 : : List *options,
15988 : : LOCKMODE lockmode)
15989 : : {
15990 : 7 : Relation ftrel;
15991 : 7 : Relation attrel;
15992 : 7 : ForeignServer *server;
15993 : 7 : ForeignDataWrapper *fdw;
15994 : 7 : HeapTuple tuple;
15995 : 7 : HeapTuple newtuple;
15996 : 7 : bool isnull;
15997 : 7 : Datum repl_val[Natts_pg_attribute];
15998 : 7 : bool repl_null[Natts_pg_attribute];
15999 : 7 : bool repl_repl[Natts_pg_attribute];
16000 : 7 : Datum datum;
16001 : 7 : Form_pg_foreign_table fttableform;
16002 : 7 : Form_pg_attribute atttableform;
16003 : 7 : AttrNumber attnum;
16004 : 7 : ObjectAddress address;
16005 : :
16006 [ + - ]: 7 : if (options == NIL)
16007 : 0 : return InvalidObjectAddress;
16008 : :
16009 : : /* First, determine FDW validator associated to the foreign table. */
16010 : 7 : ftrel = table_open(ForeignTableRelationId, AccessShareLock);
16011 : 7 : tuple = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(rel->rd_id));
16012 [ + - ]: 7 : if (!HeapTupleIsValid(tuple))
16013 [ # # # # ]: 0 : ereport(ERROR,
16014 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
16015 : : errmsg("foreign table \"%s\" does not exist",
16016 : : RelationGetRelationName(rel))));
16017 : 7 : fttableform = (Form_pg_foreign_table) GETSTRUCT(tuple);
16018 : 7 : server = GetForeignServer(fttableform->ftserver);
16019 : 7 : fdw = GetForeignDataWrapper(server->fdwid);
16020 : :
16021 : 7 : table_close(ftrel, AccessShareLock);
16022 : 7 : ReleaseSysCache(tuple);
16023 : :
16024 : 7 : attrel = table_open(AttributeRelationId, RowExclusiveLock);
16025 : 7 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
16026 [ + - ]: 7 : if (!HeapTupleIsValid(tuple))
16027 [ # # # # ]: 0 : ereport(ERROR,
16028 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
16029 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
16030 : : colName, RelationGetRelationName(rel))));
16031 : :
16032 : : /* Prevent them from altering a system attribute */
16033 : 7 : atttableform = (Form_pg_attribute) GETSTRUCT(tuple);
16034 : 7 : attnum = atttableform->attnum;
16035 [ + + ]: 7 : if (attnum <= 0)
16036 [ + - + - ]: 1 : ereport(ERROR,
16037 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
16038 : : errmsg("cannot alter system column \"%s\"", colName)));
16039 : :
16040 : :
16041 : : /* Initialize buffers for new tuple values */
16042 : 6 : memset(repl_val, 0, sizeof(repl_val));
16043 : 6 : memset(repl_null, false, sizeof(repl_null));
16044 : 6 : memset(repl_repl, false, sizeof(repl_repl));
16045 : :
16046 : : /* Extract the current options */
16047 : 6 : datum = SysCacheGetAttr(ATTNAME,
16048 : 6 : tuple,
16049 : : Anum_pg_attribute_attfdwoptions,
16050 : : &isnull);
16051 [ + + ]: 6 : if (isnull)
16052 : 5 : datum = PointerGetDatum(NULL);
16053 : :
16054 : : /* Transform the options */
16055 : 6 : datum = transformGenericOptions(AttributeRelationId,
16056 : 6 : datum,
16057 : 6 : options,
16058 : 6 : fdw->fdwvalidator);
16059 : :
16060 [ + - ]: 6 : if (DatumGetPointer(datum) != NULL)
16061 : 6 : repl_val[Anum_pg_attribute_attfdwoptions - 1] = datum;
16062 : : else
16063 : 0 : repl_null[Anum_pg_attribute_attfdwoptions - 1] = true;
16064 : :
16065 : 6 : repl_repl[Anum_pg_attribute_attfdwoptions - 1] = true;
16066 : :
16067 : : /* Everything looks good - update the tuple */
16068 : :
16069 : 12 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(attrel),
16070 : 6 : repl_val, repl_null, repl_repl);
16071 : :
16072 : 6 : CatalogTupleUpdate(attrel, &newtuple->t_self, newtuple);
16073 : :
16074 [ + - ]: 6 : InvokeObjectPostAlterHook(RelationRelationId,
16075 : : RelationGetRelid(rel),
16076 : : atttableform->attnum);
16077 : 6 : ObjectAddressSubSet(address, RelationRelationId,
16078 : : RelationGetRelid(rel), attnum);
16079 : :
16080 : 6 : ReleaseSysCache(tuple);
16081 : :
16082 : 6 : table_close(attrel, RowExclusiveLock);
16083 : :
16084 : 6 : heap_freetuple(newtuple);
16085 : :
16086 : 6 : return address;
16087 : 6 : }
16088 : :
16089 : : /*
16090 : : * ALTER TABLE OWNER
16091 : : *
16092 : : * recursing is true if we are recursing from a table to its indexes,
16093 : : * sequences, or toast table. We don't allow the ownership of those things to
16094 : : * be changed separately from the parent table. Also, we can skip permission
16095 : : * checks (this is necessary not just an optimization, else we'd fail to
16096 : : * handle toast tables properly).
16097 : : *
16098 : : * recursing is also true if ALTER TYPE OWNER is calling us to fix up a
16099 : : * free-standing composite type.
16100 : : */
16101 : : void
16102 : 73 : ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lockmode)
16103 : : {
16104 : 73 : Relation target_rel;
16105 : 73 : Relation class_rel;
16106 : 73 : HeapTuple tuple;
16107 : 73 : Form_pg_class tuple_class;
16108 : :
16109 : : /*
16110 : : * Get exclusive lock till end of transaction on the target table. Use
16111 : : * relation_open so that we can work on indexes and sequences.
16112 : : */
16113 : 73 : target_rel = relation_open(relationOid, lockmode);
16114 : :
16115 : : /* Get its pg_class tuple, too */
16116 : 73 : class_rel = table_open(RelationRelationId, RowExclusiveLock);
16117 : :
16118 : 73 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relationOid));
16119 [ + - ]: 73 : if (!HeapTupleIsValid(tuple))
16120 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", relationOid);
16121 : 73 : tuple_class = (Form_pg_class) GETSTRUCT(tuple);
16122 : :
16123 : : /* Can we change the ownership of this tuple? */
16124 [ + + + + : 73 : switch (tuple_class->relkind)
+ + - ]
16125 : : {
16126 : : case RELKIND_RELATION:
16127 : : case RELKIND_VIEW:
16128 : : case RELKIND_MATVIEW:
16129 : : case RELKIND_FOREIGN_TABLE:
16130 : : case RELKIND_PARTITIONED_TABLE:
16131 : : /* ok to change owner */
16132 : 44 : break;
16133 : : case RELKIND_INDEX:
16134 [ + - ]: 14 : if (!recursing)
16135 : : {
16136 : : /*
16137 : : * Because ALTER INDEX OWNER used to be allowed, and in fact
16138 : : * is generated by old versions of pg_dump, we give a warning
16139 : : * and do nothing rather than erroring out. Also, to avoid
16140 : : * unnecessary chatter while restoring those old dumps, say
16141 : : * nothing at all if the command would be a no-op anyway.
16142 : : */
16143 [ # # ]: 0 : if (tuple_class->relowner != newOwnerId)
16144 [ # # # # ]: 0 : ereport(WARNING,
16145 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
16146 : : errmsg("cannot change owner of index \"%s\"",
16147 : : NameStr(tuple_class->relname)),
16148 : : errhint("Change the ownership of the index's table instead.")));
16149 : : /* quick hack to exit via the no-op path */
16150 : 0 : newOwnerId = tuple_class->relowner;
16151 : 0 : }
16152 : 14 : break;
16153 : : case RELKIND_PARTITIONED_INDEX:
16154 [ + - ]: 2 : if (recursing)
16155 : 2 : break;
16156 [ # # # # ]: 0 : ereport(ERROR,
16157 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
16158 : : errmsg("cannot change owner of index \"%s\"",
16159 : : NameStr(tuple_class->relname)),
16160 : : errhint("Change the ownership of the index's table instead.")));
16161 : 0 : break;
16162 : : case RELKIND_SEQUENCE:
16163 [ - + # # ]: 6 : if (!recursing &&
16164 : 0 : tuple_class->relowner != newOwnerId)
16165 : : {
16166 : : /* if it's an owned sequence, disallow changing it by itself */
16167 : 0 : Oid tableId;
16168 : 0 : int32 colId;
16169 : :
16170 [ # # ]: 0 : if (sequenceIsOwned(relationOid, DEPENDENCY_AUTO, &tableId, &colId) ||
16171 : 0 : sequenceIsOwned(relationOid, DEPENDENCY_INTERNAL, &tableId, &colId))
16172 [ # # # # ]: 0 : ereport(ERROR,
16173 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
16174 : : errmsg("cannot change owner of sequence \"%s\"",
16175 : : NameStr(tuple_class->relname)),
16176 : : errdetail("Sequence \"%s\" is linked to table \"%s\".",
16177 : : NameStr(tuple_class->relname),
16178 : : get_rel_name(tableId))));
16179 : 0 : }
16180 : 6 : break;
16181 : : case RELKIND_COMPOSITE_TYPE:
16182 [ + - ]: 1 : if (recursing)
16183 : 1 : break;
16184 [ # # # # ]: 0 : ereport(ERROR,
16185 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
16186 : : errmsg("\"%s\" is a composite type",
16187 : : NameStr(tuple_class->relname)),
16188 : : /* translator: %s is an SQL ALTER command */
16189 : : errhint("Use %s instead.",
16190 : : "ALTER TYPE")));
16191 : 0 : break;
16192 : : case RELKIND_TOASTVALUE:
16193 [ + - ]: 6 : if (recursing)
16194 : 6 : break;
16195 : : /* FALL THRU */
16196 : : default:
16197 [ # # # # ]: 0 : ereport(ERROR,
16198 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
16199 : : errmsg("cannot change owner of relation \"%s\"",
16200 : : NameStr(tuple_class->relname)),
16201 : : errdetail_relkind_not_supported(tuple_class->relkind)));
16202 : 0 : }
16203 : :
16204 : : /*
16205 : : * If the new owner is the same as the existing owner, consider the
16206 : : * command to have succeeded. This is for dump restoration purposes.
16207 : : */
16208 [ + + ]: 73 : if (tuple_class->relowner != newOwnerId)
16209 : : {
16210 : 69 : Datum repl_val[Natts_pg_class];
16211 : 69 : bool repl_null[Natts_pg_class];
16212 : 69 : bool repl_repl[Natts_pg_class];
16213 : 69 : Acl *newAcl;
16214 : 69 : Datum aclDatum;
16215 : 69 : bool isNull;
16216 : 69 : HeapTuple newtuple;
16217 : :
16218 : : /* skip permission checks when recursing to index or toast table */
16219 [ + + ]: 69 : if (!recursing)
16220 : : {
16221 : : /* Superusers can always do it */
16222 [ + + ]: 40 : if (!superuser())
16223 : : {
16224 : 5 : Oid namespaceOid = tuple_class->relnamespace;
16225 : 5 : AclResult aclresult;
16226 : :
16227 : : /* Otherwise, must be owner of the existing object */
16228 [ + - ]: 5 : if (!object_ownercheck(RelationRelationId, relationOid, GetUserId()))
16229 : 0 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relationOid)),
16230 : 0 : RelationGetRelationName(target_rel));
16231 : :
16232 : : /* Must be able to become new owner */
16233 : 5 : check_can_set_role(GetUserId(), newOwnerId);
16234 : :
16235 : : /* New owner must have CREATE privilege on namespace */
16236 : 5 : aclresult = object_aclcheck(NamespaceRelationId, namespaceOid, newOwnerId,
16237 : : ACL_CREATE);
16238 [ + - ]: 5 : if (aclresult != ACLCHECK_OK)
16239 : 0 : aclcheck_error(aclresult, OBJECT_SCHEMA,
16240 : 0 : get_namespace_name(namespaceOid));
16241 : 5 : }
16242 : 40 : }
16243 : :
16244 : 69 : memset(repl_null, false, sizeof(repl_null));
16245 : 69 : memset(repl_repl, false, sizeof(repl_repl));
16246 : :
16247 : 69 : repl_repl[Anum_pg_class_relowner - 1] = true;
16248 : 69 : repl_val[Anum_pg_class_relowner - 1] = ObjectIdGetDatum(newOwnerId);
16249 : :
16250 : : /*
16251 : : * Determine the modified ACL for the new owner. This is only
16252 : : * necessary when the ACL is non-null.
16253 : : */
16254 : 69 : aclDatum = SysCacheGetAttr(RELOID, tuple,
16255 : : Anum_pg_class_relacl,
16256 : : &isNull);
16257 [ + + ]: 69 : if (!isNull)
16258 : : {
16259 : 10 : newAcl = aclnewowner(DatumGetAclP(aclDatum),
16260 : 5 : tuple_class->relowner, newOwnerId);
16261 : 5 : repl_repl[Anum_pg_class_relacl - 1] = true;
16262 : 5 : repl_val[Anum_pg_class_relacl - 1] = PointerGetDatum(newAcl);
16263 : 5 : }
16264 : :
16265 : 69 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(class_rel), repl_val, repl_null, repl_repl);
16266 : :
16267 : 69 : CatalogTupleUpdate(class_rel, &newtuple->t_self, newtuple);
16268 : :
16269 : 69 : heap_freetuple(newtuple);
16270 : :
16271 : : /*
16272 : : * We must similarly update any per-column ACLs to reflect the new
16273 : : * owner; for neatness reasons that's split out as a subroutine.
16274 : : */
16275 : 138 : change_owner_fix_column_acls(relationOid,
16276 : 69 : tuple_class->relowner,
16277 : 69 : newOwnerId);
16278 : :
16279 : : /*
16280 : : * Update owner dependency reference, if any. A composite type has
16281 : : * none, because it's tracked for the pg_type entry instead of here;
16282 : : * indexes and TOAST tables don't have their own entries either.
16283 : : */
16284 [ + + ]: 69 : if (tuple_class->relkind != RELKIND_COMPOSITE_TYPE &&
16285 [ + + ]: 68 : tuple_class->relkind != RELKIND_INDEX &&
16286 [ + + + + ]: 54 : tuple_class->relkind != RELKIND_PARTITIONED_INDEX &&
16287 : 52 : tuple_class->relkind != RELKIND_TOASTVALUE)
16288 : 92 : changeDependencyOnOwner(RelationRelationId, relationOid,
16289 : 46 : newOwnerId);
16290 : :
16291 : : /*
16292 : : * Also change the ownership of the table's row type, if it has one
16293 : : */
16294 [ + + ]: 69 : if (OidIsValid(tuple_class->reltype))
16295 : 43 : AlterTypeOwnerInternal(tuple_class->reltype, newOwnerId);
16296 : :
16297 : : /*
16298 : : * If we are operating on a table or materialized view, also change
16299 : : * the ownership of any indexes and sequences that belong to the
16300 : : * relation, as well as its toast table (if it has one).
16301 : : */
16302 [ + + ]: 69 : if (tuple_class->relkind == RELKIND_RELATION ||
16303 [ + + ]: 36 : tuple_class->relkind == RELKIND_PARTITIONED_TABLE ||
16304 [ + - + + ]: 29 : tuple_class->relkind == RELKIND_MATVIEW ||
16305 : 29 : tuple_class->relkind == RELKIND_TOASTVALUE)
16306 : : {
16307 : 46 : List *index_oid_list;
16308 : 46 : ListCell *i;
16309 : :
16310 : : /* Find all the indexes belonging to this relation */
16311 : 46 : index_oid_list = RelationGetIndexList(target_rel);
16312 : :
16313 : : /* For each index, recursively change its ownership */
16314 [ + + + + : 62 : foreach(i, index_oid_list)
+ + ]
16315 : 16 : ATExecChangeOwner(lfirst_oid(i), newOwnerId, true, lockmode);
16316 : :
16317 : 46 : list_free(index_oid_list);
16318 : 46 : }
16319 : :
16320 : : /* If it has a toast table, recurse to change its ownership */
16321 [ + + ]: 69 : if (tuple_class->reltoastrelid != InvalidOid)
16322 : 12 : ATExecChangeOwner(tuple_class->reltoastrelid, newOwnerId,
16323 : 6 : true, lockmode);
16324 : :
16325 : : /* If it has dependent sequences, recurse to change them too */
16326 : 69 : change_owner_recurse_to_sequences(relationOid, newOwnerId, lockmode);
16327 : 69 : }
16328 : :
16329 [ + - ]: 73 : InvokeObjectPostAlterHook(RelationRelationId, relationOid, 0);
16330 : :
16331 : 73 : ReleaseSysCache(tuple);
16332 : 73 : table_close(class_rel, RowExclusiveLock);
16333 : 73 : relation_close(target_rel, NoLock);
16334 : 73 : }
16335 : :
16336 : : /*
16337 : : * change_owner_fix_column_acls
16338 : : *
16339 : : * Helper function for ATExecChangeOwner. Scan the columns of the table
16340 : : * and fix any non-null column ACLs to reflect the new owner.
16341 : : */
16342 : : static void
16343 : 69 : change_owner_fix_column_acls(Oid relationOid, Oid oldOwnerId, Oid newOwnerId)
16344 : : {
16345 : 69 : Relation attRelation;
16346 : 69 : SysScanDesc scan;
16347 : 69 : ScanKeyData key[1];
16348 : 69 : HeapTuple attributeTuple;
16349 : :
16350 : 69 : attRelation = table_open(AttributeRelationId, RowExclusiveLock);
16351 : 138 : ScanKeyInit(&key[0],
16352 : : Anum_pg_attribute_attrelid,
16353 : : BTEqualStrategyNumber, F_OIDEQ,
16354 : 69 : ObjectIdGetDatum(relationOid));
16355 : 138 : scan = systable_beginscan(attRelation, AttributeRelidNumIndexId,
16356 : 69 : true, NULL, 1, key);
16357 [ + + ]: 491 : while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
16358 : : {
16359 : 422 : Form_pg_attribute att = (Form_pg_attribute) GETSTRUCT(attributeTuple);
16360 : 422 : Datum repl_val[Natts_pg_attribute];
16361 : 422 : bool repl_null[Natts_pg_attribute];
16362 : 422 : bool repl_repl[Natts_pg_attribute];
16363 : 422 : Acl *newAcl;
16364 : 422 : Datum aclDatum;
16365 : 422 : bool isNull;
16366 : 422 : HeapTuple newtuple;
16367 : :
16368 : : /* Ignore dropped columns */
16369 [ - + ]: 422 : if (att->attisdropped)
16370 : 0 : continue;
16371 : :
16372 : 844 : aclDatum = heap_getattr(attributeTuple,
16373 : : Anum_pg_attribute_attacl,
16374 : 422 : RelationGetDescr(attRelation),
16375 : : &isNull);
16376 : : /* Null ACLs do not require changes */
16377 [ + - ]: 422 : if (isNull)
16378 : 422 : continue;
16379 : :
16380 : 0 : memset(repl_null, false, sizeof(repl_null));
16381 : 0 : memset(repl_repl, false, sizeof(repl_repl));
16382 : :
16383 : 0 : newAcl = aclnewowner(DatumGetAclP(aclDatum),
16384 : 0 : oldOwnerId, newOwnerId);
16385 : 0 : repl_repl[Anum_pg_attribute_attacl - 1] = true;
16386 : 0 : repl_val[Anum_pg_attribute_attacl - 1] = PointerGetDatum(newAcl);
16387 : :
16388 : 0 : newtuple = heap_modify_tuple(attributeTuple,
16389 : 0 : RelationGetDescr(attRelation),
16390 : 0 : repl_val, repl_null, repl_repl);
16391 : :
16392 : 0 : CatalogTupleUpdate(attRelation, &newtuple->t_self, newtuple);
16393 : :
16394 : 0 : heap_freetuple(newtuple);
16395 [ - + - ]: 422 : }
16396 : 69 : systable_endscan(scan);
16397 : 69 : table_close(attRelation, RowExclusiveLock);
16398 : 69 : }
16399 : :
16400 : : /*
16401 : : * change_owner_recurse_to_sequences
16402 : : *
16403 : : * Helper function for ATExecChangeOwner. Examines pg_depend searching
16404 : : * for sequences that are dependent on serial columns, and changes their
16405 : : * ownership.
16406 : : */
16407 : : static void
16408 : 69 : change_owner_recurse_to_sequences(Oid relationOid, Oid newOwnerId, LOCKMODE lockmode)
16409 : : {
16410 : 69 : Relation depRel;
16411 : 69 : SysScanDesc scan;
16412 : 69 : ScanKeyData key[2];
16413 : 69 : HeapTuple tup;
16414 : :
16415 : : /*
16416 : : * SERIAL sequences are those having an auto dependency on one of the
16417 : : * table's columns (we don't care *which* column, exactly).
16418 : : */
16419 : 69 : depRel = table_open(DependRelationId, AccessShareLock);
16420 : :
16421 : 138 : ScanKeyInit(&key[0],
16422 : : Anum_pg_depend_refclassid,
16423 : : BTEqualStrategyNumber, F_OIDEQ,
16424 : 69 : ObjectIdGetDatum(RelationRelationId));
16425 : 138 : ScanKeyInit(&key[1],
16426 : : Anum_pg_depend_refobjid,
16427 : : BTEqualStrategyNumber, F_OIDEQ,
16428 : 69 : ObjectIdGetDatum(relationOid));
16429 : : /* we leave refobjsubid unspecified */
16430 : :
16431 : 138 : scan = systable_beginscan(depRel, DependReferenceIndexId, true,
16432 : 69 : NULL, 2, key);
16433 : :
16434 [ + + ]: 200 : while (HeapTupleIsValid(tup = systable_getnext(scan)))
16435 : : {
16436 : 131 : Form_pg_depend depForm = (Form_pg_depend) GETSTRUCT(tup);
16437 : 131 : Relation seqRel;
16438 : :
16439 : : /* skip dependencies other than auto dependencies on columns */
16440 [ + + ]: 131 : if (depForm->refobjsubid == 0 ||
16441 [ + + ]: 52 : depForm->classid != RelationRelationId ||
16442 [ + - + - ]: 20 : depForm->objsubid != 0 ||
16443 [ + + ]: 19 : !(depForm->deptype == DEPENDENCY_AUTO || depForm->deptype == DEPENDENCY_INTERNAL))
16444 : 112 : continue;
16445 : :
16446 : : /* Use relation_open just in case it's an index */
16447 : 19 : seqRel = relation_open(depForm->objid, lockmode);
16448 : :
16449 : : /* skip non-sequence relations */
16450 [ + + ]: 19 : if (RelationGetForm(seqRel)->relkind != RELKIND_SEQUENCE)
16451 : : {
16452 : : /* No need to keep the lock */
16453 : 15 : relation_close(seqRel, lockmode);
16454 : 15 : continue;
16455 : : }
16456 : :
16457 : : /* We don't need to close the sequence while we alter it. */
16458 : 4 : ATExecChangeOwner(depForm->objid, newOwnerId, true, lockmode);
16459 : :
16460 : : /* Now we can close it. Keep the lock till end of transaction. */
16461 : 4 : relation_close(seqRel, NoLock);
16462 [ - + + ]: 131 : }
16463 : :
16464 : 69 : systable_endscan(scan);
16465 : :
16466 : 69 : relation_close(depRel, AccessShareLock);
16467 : 69 : }
16468 : :
16469 : : /*
16470 : : * ALTER TABLE CLUSTER ON
16471 : : *
16472 : : * The only thing we have to do is to change the indisclustered bits.
16473 : : *
16474 : : * Return the address of the new clustering index.
16475 : : */
16476 : : static ObjectAddress
16477 : 9 : ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode)
16478 : : {
16479 : 9 : Oid indexOid;
16480 : : ObjectAddress address;
16481 : :
16482 : 9 : indexOid = get_relname_relid(indexName, rel->rd_rel->relnamespace);
16483 : :
16484 [ + - ]: 9 : if (!OidIsValid(indexOid))
16485 [ # # # # ]: 0 : ereport(ERROR,
16486 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
16487 : : errmsg("index \"%s\" for table \"%s\" does not exist",
16488 : : indexName, RelationGetRelationName(rel))));
16489 : :
16490 : : /* Check index is valid to cluster on */
16491 : 9 : check_index_is_clusterable(rel, indexOid, lockmode);
16492 : :
16493 : : /* And do the work */
16494 : 9 : mark_index_clustered(rel, indexOid, false);
16495 : :
16496 : 9 : ObjectAddressSet(address,
16497 : : RelationRelationId, indexOid);
16498 : :
16499 : : return address;
16500 : 9 : }
16501 : :
16502 : : /*
16503 : : * ALTER TABLE SET WITHOUT CLUSTER
16504 : : *
16505 : : * We have to find any indexes on the table that have indisclustered bit
16506 : : * set and turn it off.
16507 : : */
16508 : : static void
16509 : 3 : ATExecDropCluster(Relation rel, LOCKMODE lockmode)
16510 : : {
16511 : 3 : mark_index_clustered(rel, InvalidOid, false);
16512 : 3 : }
16513 : :
16514 : : /*
16515 : : * Preparation phase for SET ACCESS METHOD
16516 : : *
16517 : : * Check that the access method exists and determine whether a change is
16518 : : * actually needed.
16519 : : */
16520 : : static void
16521 : 18 : ATPrepSetAccessMethod(AlteredTableInfo *tab, Relation rel, const char *amname)
16522 : : {
16523 : 18 : Oid amoid;
16524 : :
16525 : : /*
16526 : : * Look up the access method name and check that it differs from the
16527 : : * table's current AM. If DEFAULT was specified for a partitioned table
16528 : : * (amname is NULL), set it to InvalidOid to reset the catalogued AM.
16529 : : */
16530 [ + + ]: 18 : if (amname != NULL)
16531 : 12 : amoid = get_table_am_oid(amname, false);
16532 [ + + ]: 6 : else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
16533 : 3 : amoid = InvalidOid;
16534 : : else
16535 : 3 : amoid = get_table_am_oid(default_table_access_method, false);
16536 : :
16537 : : /* if it's a match, phase 3 doesn't need to do anything */
16538 [ + + ]: 18 : if (rel->rd_rel->relam == amoid)
16539 : 2 : return;
16540 : :
16541 : : /* Save info for Phase 3 to do the real work */
16542 : 16 : tab->rewrite |= AT_REWRITE_ACCESS_METHOD;
16543 : 16 : tab->newAccessMethod = amoid;
16544 : 16 : tab->chgAccessMethod = true;
16545 [ - + ]: 18 : }
16546 : :
16547 : : /*
16548 : : * Special handling of ALTER TABLE SET ACCESS METHOD for relations with no
16549 : : * storage that have an interest in preserving AM.
16550 : : *
16551 : : * Since these have no storage, setting the access method is a catalog only
16552 : : * operation.
16553 : : */
16554 : : static void
16555 : 7 : ATExecSetAccessMethodNoStorage(Relation rel, Oid newAccessMethodId)
16556 : : {
16557 : 7 : Relation pg_class;
16558 : 7 : Oid oldAccessMethodId;
16559 : 7 : HeapTuple tuple;
16560 : 7 : Form_pg_class rd_rel;
16561 : 7 : Oid reloid = RelationGetRelid(rel);
16562 : :
16563 : : /*
16564 : : * Shouldn't be called on relations having storage; these are processed in
16565 : : * phase 3.
16566 : : */
16567 [ + - ]: 7 : Assert(!RELKIND_HAS_STORAGE(rel->rd_rel->relkind));
16568 : :
16569 : : /* Get a modifiable copy of the relation's pg_class row. */
16570 : 7 : pg_class = table_open(RelationRelationId, RowExclusiveLock);
16571 : :
16572 : 7 : tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid));
16573 [ + - ]: 7 : if (!HeapTupleIsValid(tuple))
16574 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", reloid);
16575 : 7 : rd_rel = (Form_pg_class) GETSTRUCT(tuple);
16576 : :
16577 : : /* Update the pg_class row. */
16578 : 7 : oldAccessMethodId = rd_rel->relam;
16579 : 7 : rd_rel->relam = newAccessMethodId;
16580 : :
16581 : : /* Leave if no update required */
16582 [ - + ]: 7 : if (rd_rel->relam == oldAccessMethodId)
16583 : : {
16584 : 0 : heap_freetuple(tuple);
16585 : 0 : table_close(pg_class, RowExclusiveLock);
16586 : 0 : return;
16587 : : }
16588 : :
16589 : 7 : CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
16590 : :
16591 : : /*
16592 : : * Update the dependency on the new access method. No dependency is added
16593 : : * if the new access method is InvalidOid (default case). Be very careful
16594 : : * that this has to compare the previous value stored in pg_class with the
16595 : : * new one.
16596 : : */
16597 [ + + - + ]: 7 : if (!OidIsValid(oldAccessMethodId) && OidIsValid(rd_rel->relam))
16598 : : {
16599 : 3 : ObjectAddress relobj,
16600 : : referenced;
16601 : :
16602 : : /*
16603 : : * New access method is defined and there was no dependency
16604 : : * previously, so record a new one.
16605 : : */
16606 : 3 : ObjectAddressSet(relobj, RelationRelationId, reloid);
16607 : 3 : ObjectAddressSet(referenced, AccessMethodRelationId, rd_rel->relam);
16608 : 3 : recordDependencyOn(&relobj, &referenced, DEPENDENCY_NORMAL);
16609 : 3 : }
16610 [ + - + + ]: 4 : else if (OidIsValid(oldAccessMethodId) &&
16611 : 4 : !OidIsValid(rd_rel->relam))
16612 : : {
16613 : : /*
16614 : : * There was an access method defined, and no new one, so just remove
16615 : : * the existing dependency.
16616 : : */
16617 : 2 : deleteDependencyRecordsForClass(RelationRelationId, reloid,
16618 : : AccessMethodRelationId,
16619 : : DEPENDENCY_NORMAL);
16620 : 2 : }
16621 : : else
16622 : : {
16623 [ + - ]: 2 : Assert(OidIsValid(oldAccessMethodId) &&
16624 : : OidIsValid(rd_rel->relam));
16625 : :
16626 : : /* Both are valid, so update the dependency */
16627 : 4 : changeDependencyFor(RelationRelationId, reloid,
16628 : : AccessMethodRelationId,
16629 : 2 : oldAccessMethodId, rd_rel->relam);
16630 : : }
16631 : :
16632 : : /* make the relam and dependency changes visible */
16633 : 7 : CommandCounterIncrement();
16634 : :
16635 [ - + ]: 7 : InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);
16636 : :
16637 : 7 : heap_freetuple(tuple);
16638 : 7 : table_close(pg_class, RowExclusiveLock);
16639 [ - + ]: 7 : }
16640 : :
16641 : : /*
16642 : : * ALTER TABLE SET TABLESPACE
16643 : : */
16644 : : static void
16645 : 25 : ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, const char *tablespacename, LOCKMODE lockmode)
16646 : : {
16647 : 25 : Oid tablespaceId;
16648 : :
16649 : : /* Check that the tablespace exists */
16650 : 25 : tablespaceId = get_tablespace_oid(tablespacename, false);
16651 : :
16652 : : /* Check permissions except when moving to database's default */
16653 [ + - + + ]: 25 : if (OidIsValid(tablespaceId) && tablespaceId != MyDatabaseTableSpace)
16654 : : {
16655 : 9 : AclResult aclresult;
16656 : :
16657 : 9 : aclresult = object_aclcheck(TableSpaceRelationId, tablespaceId, GetUserId(), ACL_CREATE);
16658 [ + - ]: 9 : if (aclresult != ACLCHECK_OK)
16659 : 0 : aclcheck_error(aclresult, OBJECT_TABLESPACE, tablespacename);
16660 : 9 : }
16661 : :
16662 : : /* Save info for Phase 3 to do the real work */
16663 [ + - ]: 25 : if (OidIsValid(tab->newTableSpace))
16664 [ # # # # ]: 0 : ereport(ERROR,
16665 : : (errcode(ERRCODE_SYNTAX_ERROR),
16666 : : errmsg("cannot have multiple SET TABLESPACE subcommands")));
16667 : :
16668 : 25 : tab->newTableSpace = tablespaceId;
16669 : 25 : }
16670 : :
16671 : : /*
16672 : : * Set, reset, or replace reloptions.
16673 : : */
16674 : : static void
16675 : 119 : ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
16676 : : LOCKMODE lockmode)
16677 : : {
16678 : 119 : Oid relid;
16679 : 119 : Relation pgclass;
16680 : 119 : HeapTuple tuple;
16681 : 119 : HeapTuple newtuple;
16682 : 119 : Datum datum;
16683 : 119 : Datum newOptions;
16684 : 119 : Datum repl_val[Natts_pg_class];
16685 : 119 : bool repl_null[Natts_pg_class];
16686 : 119 : bool repl_repl[Natts_pg_class];
16687 : 119 : const char *const validnsps[] = HEAP_RELOPT_NAMESPACES;
16688 : :
16689 [ + + + - ]: 119 : if (defList == NIL && operation != AT_ReplaceRelOptions)
16690 : 0 : return; /* nothing to do */
16691 : :
16692 : 119 : pgclass = table_open(RelationRelationId, RowExclusiveLock);
16693 : :
16694 : : /* Fetch heap tuple */
16695 : 119 : relid = RelationGetRelid(rel);
16696 : 119 : tuple = SearchSysCacheLocked1(RELOID, ObjectIdGetDatum(relid));
16697 [ + - ]: 119 : if (!HeapTupleIsValid(tuple))
16698 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
16699 : :
16700 [ + + ]: 119 : if (operation == AT_ReplaceRelOptions)
16701 : : {
16702 : : /*
16703 : : * If we're supposed to replace the reloptions list, we just pretend
16704 : : * there were none before.
16705 : : */
16706 : 30 : datum = (Datum) 0;
16707 : 30 : }
16708 : : else
16709 : : {
16710 : 89 : bool isnull;
16711 : :
16712 : : /* Get the old reloptions */
16713 : 89 : datum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions,
16714 : : &isnull);
16715 [ + + ]: 89 : if (isnull)
16716 : 53 : datum = (Datum) 0;
16717 : 89 : }
16718 : :
16719 : : /* Generate new proposed reloptions (text array) */
16720 : 238 : newOptions = transformRelOptions(datum, defList, NULL, validnsps, false,
16721 : 119 : operation == AT_ResetRelOptions);
16722 : :
16723 : : /* Validate */
16724 [ + + + + : 119 : switch (rel->rd_rel->relkind)
- - ]
16725 : : {
16726 : : case RELKIND_RELATION:
16727 : : case RELKIND_MATVIEW:
16728 : 64 : (void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
16729 : 64 : break;
16730 : : case RELKIND_PARTITIONED_TABLE:
16731 : 1 : (void) partitioned_table_reloptions(newOptions, true);
16732 : 1 : break;
16733 : : case RELKIND_VIEW:
16734 : 48 : (void) view_reloptions(newOptions, true);
16735 : 48 : break;
16736 : : case RELKIND_INDEX:
16737 : : case RELKIND_PARTITIONED_INDEX:
16738 : 6 : (void) index_reloptions(rel->rd_indam->amoptions, newOptions, true);
16739 : 6 : break;
16740 : 0 : case RELKIND_TOASTVALUE:
16741 : : /* fall through to error -- shouldn't ever get here */
16742 : : default:
16743 [ # # # # ]: 0 : ereport(ERROR,
16744 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
16745 : : errmsg("cannot set options for relation \"%s\"",
16746 : : RelationGetRelationName(rel)),
16747 : : errdetail_relkind_not_supported(rel->rd_rel->relkind)));
16748 : 0 : break;
16749 : : }
16750 : :
16751 : : /* Special-case validation of view options */
16752 [ + + ]: 119 : if (rel->rd_rel->relkind == RELKIND_VIEW)
16753 : : {
16754 : 45 : Query *view_query = get_view_query(rel);
16755 : 45 : List *view_options = untransformRelOptions(newOptions);
16756 : 45 : ListCell *cell;
16757 : 45 : bool check_option = false;
16758 : :
16759 [ + + + + : 62 : foreach(cell, view_options)
+ + ]
16760 : : {
16761 : 17 : DefElem *defel = (DefElem *) lfirst(cell);
16762 : :
16763 [ + + ]: 17 : if (strcmp(defel->defname, "check_option") == 0)
16764 : 4 : check_option = true;
16765 : 17 : }
16766 : :
16767 : : /*
16768 : : * If the check option is specified, look to see if the view is
16769 : : * actually auto-updatable or not.
16770 : : */
16771 [ + + ]: 45 : if (check_option)
16772 : : {
16773 : 8 : const char *view_updatable_error =
16774 : 4 : view_query_is_auto_updatable(view_query, true);
16775 : :
16776 [ + - ]: 4 : if (view_updatable_error)
16777 [ # # # # ]: 0 : ereport(ERROR,
16778 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
16779 : : errmsg("WITH CHECK OPTION is supported only on automatically updatable views"),
16780 : : errhint("%s", _(view_updatable_error))));
16781 : 4 : }
16782 : 45 : }
16783 : :
16784 : : /*
16785 : : * All we need do here is update the pg_class row; the new options will be
16786 : : * propagated into relcaches during post-commit cache inval.
16787 : : */
16788 : 119 : memset(repl_val, 0, sizeof(repl_val));
16789 : 119 : memset(repl_null, false, sizeof(repl_null));
16790 : 119 : memset(repl_repl, false, sizeof(repl_repl));
16791 : :
16792 [ + + ]: 119 : if (newOptions != (Datum) 0)
16793 : 74 : repl_val[Anum_pg_class_reloptions - 1] = newOptions;
16794 : : else
16795 : 45 : repl_null[Anum_pg_class_reloptions - 1] = true;
16796 : :
16797 : 119 : repl_repl[Anum_pg_class_reloptions - 1] = true;
16798 : :
16799 : 238 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(pgclass),
16800 : 119 : repl_val, repl_null, repl_repl);
16801 : :
16802 : 119 : CatalogTupleUpdate(pgclass, &newtuple->t_self, newtuple);
16803 : 119 : UnlockTuple(pgclass, &tuple->t_self, InplaceUpdateTupleLock);
16804 : :
16805 [ + - ]: 119 : InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);
16806 : :
16807 : 119 : heap_freetuple(newtuple);
16808 : :
16809 : 119 : ReleaseSysCache(tuple);
16810 : :
16811 : : /* repeat the whole exercise for the toast table, if there's one */
16812 [ + + ]: 119 : if (OidIsValid(rel->rd_rel->reltoastrelid))
16813 : : {
16814 : 38 : Relation toastrel;
16815 : 38 : Oid toastid = rel->rd_rel->reltoastrelid;
16816 : :
16817 : 38 : toastrel = table_open(toastid, lockmode);
16818 : :
16819 : : /* Fetch heap tuple */
16820 : 38 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(toastid));
16821 [ + - ]: 38 : if (!HeapTupleIsValid(tuple))
16822 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", toastid);
16823 : :
16824 [ - + ]: 38 : if (operation == AT_ReplaceRelOptions)
16825 : : {
16826 : : /*
16827 : : * If we're supposed to replace the reloptions list, we just
16828 : : * pretend there were none before.
16829 : : */
16830 : 0 : datum = (Datum) 0;
16831 : 0 : }
16832 : : else
16833 : : {
16834 : 38 : bool isnull;
16835 : :
16836 : : /* Get the old reloptions */
16837 : 38 : datum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions,
16838 : : &isnull);
16839 [ + + ]: 38 : if (isnull)
16840 : 32 : datum = (Datum) 0;
16841 : 38 : }
16842 : :
16843 : 76 : newOptions = transformRelOptions(datum, defList, "toast", validnsps,
16844 : 38 : false, operation == AT_ResetRelOptions);
16845 : :
16846 : 38 : (void) heap_reloptions(RELKIND_TOASTVALUE, newOptions, true);
16847 : :
16848 : 38 : memset(repl_val, 0, sizeof(repl_val));
16849 : 38 : memset(repl_null, false, sizeof(repl_null));
16850 : 38 : memset(repl_repl, false, sizeof(repl_repl));
16851 : :
16852 [ + + ]: 38 : if (newOptions != (Datum) 0)
16853 : 7 : repl_val[Anum_pg_class_reloptions - 1] = newOptions;
16854 : : else
16855 : 31 : repl_null[Anum_pg_class_reloptions - 1] = true;
16856 : :
16857 : 38 : repl_repl[Anum_pg_class_reloptions - 1] = true;
16858 : :
16859 : 76 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(pgclass),
16860 : 38 : repl_val, repl_null, repl_repl);
16861 : :
16862 : 38 : CatalogTupleUpdate(pgclass, &newtuple->t_self, newtuple);
16863 : :
16864 [ - + ]: 38 : InvokeObjectPostAlterHookArg(RelationRelationId,
16865 : : RelationGetRelid(toastrel), 0,
16866 : : InvalidOid, true);
16867 : :
16868 : 38 : heap_freetuple(newtuple);
16869 : :
16870 : 38 : ReleaseSysCache(tuple);
16871 : :
16872 : 38 : table_close(toastrel, NoLock);
16873 : 38 : }
16874 : :
16875 : 119 : table_close(pgclass, RowExclusiveLock);
16876 [ - + ]: 119 : }
16877 : :
16878 : : /*
16879 : : * Execute ALTER TABLE SET TABLESPACE for cases where there is no tuple
16880 : : * rewriting to be done, so we just want to copy the data as fast as possible.
16881 : : */
16882 : : static void
16883 : 25 : ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
16884 : : {
16885 : 25 : Relation rel;
16886 : 25 : Oid reltoastrelid;
16887 : 25 : RelFileNumber newrelfilenumber;
16888 : 25 : RelFileLocator newrlocator;
16889 : 25 : List *reltoastidxids = NIL;
16890 : 25 : ListCell *lc;
16891 : :
16892 : : /*
16893 : : * Need lock here in case we are recursing to toast table or index
16894 : : */
16895 : 25 : rel = relation_open(tableOid, lockmode);
16896 : :
16897 : : /* Check first if relation can be moved to new tablespace */
16898 [ + + ]: 25 : if (!CheckRelationTableSpaceMove(rel, newTableSpace))
16899 : : {
16900 [ + - ]: 1 : InvokeObjectPostAlterHook(RelationRelationId,
16901 : : RelationGetRelid(rel), 0);
16902 : 1 : relation_close(rel, NoLock);
16903 : 1 : return;
16904 : : }
16905 : :
16906 : 24 : reltoastrelid = rel->rd_rel->reltoastrelid;
16907 : : /* Fetch the list of indexes on toast relation if necessary */
16908 [ + + ]: 24 : if (OidIsValid(reltoastrelid))
16909 : : {
16910 : 3 : Relation toastRel = relation_open(reltoastrelid, lockmode);
16911 : :
16912 : 3 : reltoastidxids = RelationGetIndexList(toastRel);
16913 : 3 : relation_close(toastRel, lockmode);
16914 : 3 : }
16915 : :
16916 : : /*
16917 : : * Relfilenumbers are not unique in databases across tablespaces, so we
16918 : : * need to allocate a new one in the new tablespace.
16919 : : */
16920 : 48 : newrelfilenumber = GetNewRelFileNumber(newTableSpace, NULL,
16921 : 24 : rel->rd_rel->relpersistence);
16922 : :
16923 : : /* Open old and new relation */
16924 : 24 : newrlocator = rel->rd_locator;
16925 : 24 : newrlocator.relNumber = newrelfilenumber;
16926 : 24 : newrlocator.spcOid = newTableSpace;
16927 : :
16928 : : /* hand off to AM to actually create new rel storage and copy the data */
16929 [ + + ]: 24 : if (rel->rd_rel->relkind == RELKIND_INDEX)
16930 : : {
16931 : 10 : index_copy_data(rel, newrlocator);
16932 : 10 : }
16933 : : else
16934 : : {
16935 [ + + + + : 14 : Assert(RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind));
+ - ]
16936 : 14 : table_relation_copy_data(rel, &newrlocator);
16937 : : }
16938 : :
16939 : : /*
16940 : : * Update the pg_class row.
16941 : : *
16942 : : * NB: This wouldn't work if ATExecSetTableSpace() were allowed to be
16943 : : * executed on pg_class or its indexes (the above copy wouldn't contain
16944 : : * the updated pg_class entry), but that's forbidden with
16945 : : * CheckRelationTableSpaceMove().
16946 : : */
16947 : 24 : SetRelationTableSpace(rel, newTableSpace, newrelfilenumber);
16948 : :
16949 [ + - ]: 24 : InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);
16950 : :
16951 : 24 : RelationAssumeNewRelfilelocator(rel);
16952 : :
16953 : 24 : relation_close(rel, NoLock);
16954 : :
16955 : : /* Make sure the reltablespace change is visible */
16956 : 24 : CommandCounterIncrement();
16957 : :
16958 : : /* Move associated toast relation and/or indexes, too */
16959 [ + + ]: 24 : if (OidIsValid(reltoastrelid))
16960 : 3 : ATExecSetTableSpace(reltoastrelid, newTableSpace, lockmode);
16961 [ + + + + : 27 : foreach(lc, reltoastidxids)
+ + ]
16962 : 3 : ATExecSetTableSpace(lfirst_oid(lc), newTableSpace, lockmode);
16963 : :
16964 : : /* Clean up */
16965 : 24 : list_free(reltoastidxids);
16966 [ - + ]: 25 : }
16967 : :
16968 : : /*
16969 : : * Special handling of ALTER TABLE SET TABLESPACE for relations with no
16970 : : * storage that have an interest in preserving tablespace.
16971 : : *
16972 : : * Since these have no storage the tablespace can be updated with a simple
16973 : : * metadata only operation to update the tablespace.
16974 : : */
16975 : : static void
16976 : 5 : ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace)
16977 : : {
16978 : : /*
16979 : : * Shouldn't be called on relations having storage; these are processed in
16980 : : * phase 3.
16981 : : */
16982 [ + - ]: 5 : Assert(!RELKIND_HAS_STORAGE(rel->rd_rel->relkind));
16983 : :
16984 : : /* check if relation can be moved to its new tablespace */
16985 [ + - ]: 5 : if (!CheckRelationTableSpaceMove(rel, newTableSpace))
16986 : : {
16987 [ # # ]: 0 : InvokeObjectPostAlterHook(RelationRelationId,
16988 : : RelationGetRelid(rel),
16989 : : 0);
16990 : 0 : return;
16991 : : }
16992 : :
16993 : : /* Update can be done, so change reltablespace */
16994 : 5 : SetRelationTableSpace(rel, newTableSpace, InvalidOid);
16995 : :
16996 [ + - ]: 5 : InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);
16997 : :
16998 : : /* Make sure the reltablespace change is visible */
16999 : 5 : CommandCounterIncrement();
17000 : 5 : }
17001 : :
17002 : : /*
17003 : : * Alter Table ALL ... SET TABLESPACE
17004 : : *
17005 : : * Allows a user to move all objects of some type in a given tablespace in the
17006 : : * current database to another tablespace. Objects can be chosen based on the
17007 : : * owner of the object also, to allow users to move only their objects.
17008 : : * The user must have CREATE rights on the new tablespace, as usual. The main
17009 : : * permissions handling is done by the lower-level table move function.
17010 : : *
17011 : : * All to-be-moved objects are locked first. If NOWAIT is specified and the
17012 : : * lock can't be acquired then we ereport(ERROR).
17013 : : */
17014 : : Oid
17015 : 5 : AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
17016 : : {
17017 : 5 : List *relations = NIL;
17018 : 5 : ListCell *l;
17019 : 5 : ScanKeyData key[1];
17020 : 5 : Relation rel;
17021 : 5 : TableScanDesc scan;
17022 : 5 : HeapTuple tuple;
17023 : 5 : Oid orig_tablespaceoid;
17024 : 5 : Oid new_tablespaceoid;
17025 : 5 : List *role_oids = roleSpecsToIds(stmt->roles);
17026 : :
17027 : : /* Ensure we were not asked to move something we can't */
17028 [ + + + + : 5 : if (stmt->objtype != OBJECT_TABLE && stmt->objtype != OBJECT_INDEX &&
+ - ]
17029 : 2 : stmt->objtype != OBJECT_MATVIEW)
17030 [ # # # # ]: 0 : ereport(ERROR,
17031 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
17032 : : errmsg("only tables, indexes, and materialized views exist in tablespaces")));
17033 : :
17034 : : /* Get the orig and new tablespace OIDs */
17035 : 5 : orig_tablespaceoid = get_tablespace_oid(stmt->orig_tablespacename, false);
17036 : 5 : new_tablespaceoid = get_tablespace_oid(stmt->new_tablespacename, false);
17037 : :
17038 : : /* Can't move shared relations in to or out of pg_global */
17039 : : /* This is also checked by ATExecSetTableSpace, but nice to stop earlier */
17040 [ + - ]: 5 : if (orig_tablespaceoid == GLOBALTABLESPACE_OID ||
17041 : 5 : new_tablespaceoid == GLOBALTABLESPACE_OID)
17042 [ # # # # ]: 0 : ereport(ERROR,
17043 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
17044 : : errmsg("cannot move relations in to or out of pg_global tablespace")));
17045 : :
17046 : : /*
17047 : : * Must have CREATE rights on the new tablespace, unless it is the
17048 : : * database default tablespace (which all users implicitly have CREATE
17049 : : * rights on).
17050 : : */
17051 [ + - + - ]: 5 : if (OidIsValid(new_tablespaceoid) && new_tablespaceoid != MyDatabaseTableSpace)
17052 : : {
17053 : 0 : AclResult aclresult;
17054 : :
17055 : 0 : aclresult = object_aclcheck(TableSpaceRelationId, new_tablespaceoid, GetUserId(),
17056 : : ACL_CREATE);
17057 [ # # ]: 0 : if (aclresult != ACLCHECK_OK)
17058 : 0 : aclcheck_error(aclresult, OBJECT_TABLESPACE,
17059 : 0 : get_tablespace_name(new_tablespaceoid));
17060 : 0 : }
17061 : :
17062 : : /*
17063 : : * Now that the checks are done, check if we should set either to
17064 : : * InvalidOid because it is our database's default tablespace.
17065 : : */
17066 [ + - ]: 5 : if (orig_tablespaceoid == MyDatabaseTableSpace)
17067 : 0 : orig_tablespaceoid = InvalidOid;
17068 : :
17069 [ - + ]: 5 : if (new_tablespaceoid == MyDatabaseTableSpace)
17070 : 5 : new_tablespaceoid = InvalidOid;
17071 : :
17072 : : /* no-op */
17073 [ - + ]: 5 : if (orig_tablespaceoid == new_tablespaceoid)
17074 : 0 : return new_tablespaceoid;
17075 : :
17076 : : /*
17077 : : * Walk the list of objects in the tablespace and move them. This will
17078 : : * only find objects in our database, of course.
17079 : : */
17080 : 10 : ScanKeyInit(&key[0],
17081 : : Anum_pg_class_reltablespace,
17082 : : BTEqualStrategyNumber, F_OIDEQ,
17083 : 5 : ObjectIdGetDatum(orig_tablespaceoid));
17084 : :
17085 : 5 : rel = table_open(RelationRelationId, AccessShareLock);
17086 : 5 : scan = table_beginscan_catalog(rel, 1, key);
17087 [ + + ]: 22 : while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
17088 : : {
17089 : 17 : Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
17090 : 17 : Oid relOid = relForm->oid;
17091 : :
17092 : : /*
17093 : : * Do not move objects in pg_catalog as part of this, if an admin
17094 : : * really wishes to do so, they can issue the individual ALTER
17095 : : * commands directly.
17096 : : *
17097 : : * Also, explicitly avoid any shared tables, temp tables, or TOAST
17098 : : * (TOAST will be moved with the main table).
17099 : : */
17100 [ + - ]: 17 : if (IsCatalogNamespace(relForm->relnamespace) ||
17101 [ + - ]: 17 : relForm->relisshared ||
17102 [ + - - + ]: 17 : isAnyTempNamespace(relForm->relnamespace) ||
17103 : 17 : IsToastNamespace(relForm->relnamespace))
17104 : 0 : continue;
17105 : :
17106 : : /* Only move the object type requested */
17107 [ + + ]: 17 : if ((stmt->objtype == OBJECT_TABLE &&
17108 [ + + ]: 10 : relForm->relkind != RELKIND_RELATION &&
17109 : 6 : relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
17110 [ + + ]: 11 : (stmt->objtype == OBJECT_INDEX &&
17111 [ + + ]: 6 : relForm->relkind != RELKIND_INDEX &&
17112 [ - + ]: 2 : relForm->relkind != RELKIND_PARTITIONED_INDEX) ||
17113 [ + + ]: 10 : (stmt->objtype == OBJECT_MATVIEW &&
17114 : 1 : relForm->relkind != RELKIND_MATVIEW))
17115 : 7 : continue;
17116 : :
17117 : : /* Check if we are only moving objects owned by certain roles */
17118 [ - + # # ]: 10 : if (role_oids != NIL && !list_member_oid(role_oids, relForm->relowner))
17119 : 0 : continue;
17120 : :
17121 : : /*
17122 : : * Handle permissions-checking here since we are locking the tables
17123 : : * and also to avoid doing a bunch of work only to fail part-way. Note
17124 : : * that permissions will also be checked by AlterTableInternal().
17125 : : *
17126 : : * Caller must be considered an owner on the table to move it.
17127 : : */
17128 [ + - ]: 10 : if (!object_ownercheck(RelationRelationId, relOid, GetUserId()))
17129 : 0 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relOid)),
17130 : 0 : NameStr(relForm->relname));
17131 : :
17132 [ - + # # ]: 10 : if (stmt->nowait &&
17133 : 0 : !ConditionalLockRelationOid(relOid, AccessExclusiveLock))
17134 [ # # # # ]: 0 : ereport(ERROR,
17135 : : (errcode(ERRCODE_OBJECT_IN_USE),
17136 : : errmsg("aborting because lock on relation \"%s.%s\" is not available",
17137 : : get_namespace_name(relForm->relnamespace),
17138 : : NameStr(relForm->relname))));
17139 : : else
17140 : 10 : LockRelationOid(relOid, AccessExclusiveLock);
17141 : :
17142 : : /* Add to our list of objects to move */
17143 : 10 : relations = lappend_oid(relations, relOid);
17144 [ - + + ]: 17 : }
17145 : :
17146 : 5 : table_endscan(scan);
17147 : 5 : table_close(rel, AccessShareLock);
17148 : :
17149 [ + + ]: 5 : if (relations == NIL)
17150 [ - + + - : 2 : ereport(NOTICE,
+ - ]
17151 : : (errcode(ERRCODE_NO_DATA_FOUND),
17152 : : errmsg("no matching relations in tablespace \"%s\" found",
17153 : : orig_tablespaceoid == InvalidOid ? "(database default)" :
17154 : : get_tablespace_name(orig_tablespaceoid))));
17155 : :
17156 : : /* Everything is locked, loop through and move all of the relations. */
17157 [ + + + + : 15 : foreach(l, relations)
+ + ]
17158 : : {
17159 : 10 : List *cmds = NIL;
17160 : 10 : AlterTableCmd *cmd = makeNode(AlterTableCmd);
17161 : :
17162 : 10 : cmd->subtype = AT_SetTableSpace;
17163 : 10 : cmd->name = stmt->new_tablespacename;
17164 : :
17165 : 10 : cmds = lappend(cmds, cmd);
17166 : :
17167 : 10 : EventTriggerAlterTableStart((Node *) stmt);
17168 : : /* OID is set by AlterTableInternal */
17169 : 10 : AlterTableInternal(lfirst_oid(l), cmds, false);
17170 : 10 : EventTriggerAlterTableEnd();
17171 : 10 : }
17172 : :
17173 : 5 : return new_tablespaceoid;
17174 : 5 : }
17175 : :
17176 : : static void
17177 : 10 : index_copy_data(Relation rel, RelFileLocator newrlocator)
17178 : : {
17179 : 10 : SMgrRelation dstrel;
17180 : :
17181 : : /*
17182 : : * Since we copy the file directly without looking at the shared buffers,
17183 : : * we'd better first flush out any pages of the source relation that are
17184 : : * in shared buffers. We assume no new changes will be made while we are
17185 : : * holding exclusive lock on the rel.
17186 : : */
17187 : 10 : FlushRelationBuffers(rel);
17188 : :
17189 : : /*
17190 : : * Create and copy all forks of the relation, and schedule unlinking of
17191 : : * old physical files.
17192 : : *
17193 : : * NOTE: any conflict in relfilenumber value will be caught in
17194 : : * RelationCreateStorage().
17195 : : */
17196 : 10 : dstrel = RelationCreateStorage(newrlocator, rel->rd_rel->relpersistence, true);
17197 : :
17198 : : /* copy main fork */
17199 : 20 : RelationCopyStorage(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
17200 : 10 : rel->rd_rel->relpersistence);
17201 : :
17202 : : /* copy those extra forks that exist */
17203 [ + + ]: 40 : for (ForkNumber forkNum = MAIN_FORKNUM + 1;
17204 : 40 : forkNum <= MAX_FORKNUM; forkNum++)
17205 : : {
17206 [ + - ]: 30 : if (smgrexists(RelationGetSmgr(rel), forkNum))
17207 : : {
17208 : 0 : smgrcreate(dstrel, forkNum, false);
17209 : :
17210 : : /*
17211 : : * WAL log creation if the relation is persistent, or this is the
17212 : : * init fork of an unlogged relation.
17213 : : */
17214 [ # # # # ]: 0 : if (RelationIsPermanent(rel) ||
17215 [ # # ]: 0 : (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
17216 : 0 : forkNum == INIT_FORKNUM))
17217 : 0 : log_smgrcreate(&newrlocator, forkNum);
17218 : 0 : RelationCopyStorage(RelationGetSmgr(rel), dstrel, forkNum,
17219 : 0 : rel->rd_rel->relpersistence);
17220 : 0 : }
17221 : 30 : }
17222 : :
17223 : : /* drop old relation, and close new one */
17224 : 10 : RelationDropStorage(rel);
17225 : 10 : smgrclose(dstrel);
17226 : 10 : }
17227 : :
17228 : : /*
17229 : : * ALTER TABLE ENABLE/DISABLE TRIGGER
17230 : : *
17231 : : * We just pass this off to trigger.c.
17232 : : */
17233 : : static void
17234 : 20 : ATExecEnableDisableTrigger(Relation rel, const char *trigname,
17235 : : char fires_when, bool skip_system, bool recurse,
17236 : : LOCKMODE lockmode)
17237 : : {
17238 : 40 : EnableDisableTrigger(rel, trigname, InvalidOid,
17239 : 20 : fires_when, skip_system, recurse,
17240 : 20 : lockmode);
17241 : :
17242 [ + - ]: 20 : InvokeObjectPostAlterHook(RelationRelationId,
17243 : : RelationGetRelid(rel), 0);
17244 : 20 : }
17245 : :
17246 : : /*
17247 : : * ALTER TABLE ENABLE/DISABLE RULE
17248 : : *
17249 : : * We just pass this off to rewriteDefine.c.
17250 : : */
17251 : : static void
17252 : 6 : ATExecEnableDisableRule(Relation rel, const char *rulename,
17253 : : char fires_when, LOCKMODE lockmode)
17254 : : {
17255 : 6 : EnableDisableRule(rel, rulename, fires_when);
17256 : :
17257 [ + - ]: 6 : InvokeObjectPostAlterHook(RelationRelationId,
17258 : : RelationGetRelid(rel), 0);
17259 : 6 : }
17260 : :
17261 : : /*
17262 : : * ALTER TABLE INHERIT
17263 : : *
17264 : : * Add a parent to the child's parents. This verifies that all the columns and
17265 : : * check constraints of the parent appear in the child and that they have the
17266 : : * same data types and expressions.
17267 : : */
17268 : : static void
17269 : 53 : ATPrepAddInherit(Relation child_rel)
17270 : : {
17271 [ + + ]: 53 : if (child_rel->rd_rel->reloftype)
17272 [ + - + - ]: 1 : ereport(ERROR,
17273 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17274 : : errmsg("cannot change inheritance of typed table")));
17275 : :
17276 [ + + ]: 52 : if (child_rel->rd_rel->relispartition)
17277 [ + - + - ]: 1 : ereport(ERROR,
17278 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17279 : : errmsg("cannot change inheritance of a partition")));
17280 : :
17281 [ + + ]: 51 : if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
17282 [ + - + - ]: 1 : ereport(ERROR,
17283 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17284 : : errmsg("cannot change inheritance of partitioned table")));
17285 : 50 : }
17286 : :
17287 : : /*
17288 : : * Return the address of the new parent relation.
17289 : : */
17290 : : static ObjectAddress
17291 : 35 : ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
17292 : : {
17293 : 35 : Relation parent_rel;
17294 : 35 : List *children;
17295 : : ObjectAddress address;
17296 : 35 : const char *trigger_name;
17297 : :
17298 : : /*
17299 : : * A self-exclusive lock is needed here. See the similar case in
17300 : : * MergeAttributes() for a full explanation.
17301 : : */
17302 : 35 : parent_rel = table_openrv(parent, ShareUpdateExclusiveLock);
17303 : :
17304 : : /*
17305 : : * Must be owner of both parent and child -- child was checked by
17306 : : * ATSimplePermissions call in ATPrepCmd
17307 : : */
17308 : 35 : ATSimplePermissions(AT_AddInherit, parent_rel,
17309 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
17310 : :
17311 : : /* Permanent rels cannot inherit from temporary ones */
17312 [ + + + - ]: 35 : if (parent_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
17313 : 1 : child_rel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
17314 [ # # # # ]: 0 : ereport(ERROR,
17315 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17316 : : errmsg("cannot inherit from temporary relation \"%s\"",
17317 : : RelationGetRelationName(parent_rel))));
17318 : :
17319 : : /* If parent rel is temp, it must belong to this session */
17320 [ + + + - ]: 35 : if (RELATION_IS_OTHER_TEMP(parent_rel))
17321 [ # # # # ]: 0 : ereport(ERROR,
17322 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17323 : : errmsg("cannot inherit from temporary relation of another session")));
17324 : :
17325 : : /* Ditto for the child */
17326 [ + + + - ]: 35 : if (RELATION_IS_OTHER_TEMP(child_rel))
17327 [ # # # # ]: 0 : ereport(ERROR,
17328 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17329 : : errmsg("cannot inherit to temporary relation of another session")));
17330 : :
17331 : : /* Prevent partitioned tables from becoming inheritance parents */
17332 [ + + ]: 35 : if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
17333 [ + - + - ]: 1 : ereport(ERROR,
17334 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17335 : : errmsg("cannot inherit from partitioned table \"%s\"",
17336 : : parent->relname)));
17337 : :
17338 : : /* Likewise for partitions */
17339 [ + + ]: 34 : if (parent_rel->rd_rel->relispartition)
17340 [ + - + - ]: 1 : ereport(ERROR,
17341 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17342 : : errmsg("cannot inherit from a partition")));
17343 : :
17344 : : /*
17345 : : * Prevent circularity by seeing if proposed parent inherits from child.
17346 : : * (In particular, this disallows making a rel inherit from itself.)
17347 : : *
17348 : : * This is not completely bulletproof because of race conditions: in
17349 : : * multi-level inheritance trees, someone else could concurrently be
17350 : : * making another inheritance link that closes the loop but does not join
17351 : : * either of the rels we have locked. Preventing that seems to require
17352 : : * exclusive locks on the entire inheritance tree, which is a cure worse
17353 : : * than the disease. find_all_inheritors() will cope with circularity
17354 : : * anyway, so don't sweat it too much.
17355 : : *
17356 : : * We use weakest lock we can on child's children, namely AccessShareLock.
17357 : : */
17358 : 33 : children = find_all_inheritors(RelationGetRelid(child_rel),
17359 : : AccessShareLock, NULL);
17360 : :
17361 [ + + ]: 33 : if (list_member_oid(children, RelationGetRelid(parent_rel)))
17362 [ + - + - ]: 2 : ereport(ERROR,
17363 : : (errcode(ERRCODE_DUPLICATE_TABLE),
17364 : : errmsg("circular inheritance not allowed"),
17365 : : errdetail("\"%s\" is already a child of \"%s\".",
17366 : : parent->relname,
17367 : : RelationGetRelationName(child_rel))));
17368 : :
17369 : : /*
17370 : : * If child_rel has row-level triggers with transition tables, we
17371 : : * currently don't allow it to become an inheritance child. See also
17372 : : * prohibitions in ATExecAttachPartition() and CreateTrigger().
17373 : : */
17374 : 31 : trigger_name = FindTriggerIncompatibleWithInheritance(child_rel->trigdesc);
17375 [ + + ]: 31 : if (trigger_name != NULL)
17376 [ + - + - ]: 1 : ereport(ERROR,
17377 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
17378 : : errmsg("trigger \"%s\" prevents table \"%s\" from becoming an inheritance child",
17379 : : trigger_name, RelationGetRelationName(child_rel)),
17380 : : errdetail("ROW triggers with transition tables are not supported in inheritance hierarchies.")));
17381 : :
17382 : : /* OK to create inheritance */
17383 : 30 : CreateInheritance(child_rel, parent_rel, false);
17384 : :
17385 : 30 : ObjectAddressSet(address, RelationRelationId,
17386 : : RelationGetRelid(parent_rel));
17387 : :
17388 : : /* keep our lock on the parent relation until commit */
17389 : 30 : table_close(parent_rel, NoLock);
17390 : :
17391 : : return address;
17392 : 30 : }
17393 : :
17394 : : /*
17395 : : * CreateInheritance
17396 : : * Catalog manipulation portion of creating inheritance between a child
17397 : : * table and a parent table.
17398 : : *
17399 : : * Common to ATExecAddInherit() and ATExecAttachPartition().
17400 : : */
17401 : : static void
17402 : 422 : CreateInheritance(Relation child_rel, Relation parent_rel, bool ispartition)
17403 : : {
17404 : 422 : Relation catalogRelation;
17405 : 422 : SysScanDesc scan;
17406 : 422 : ScanKeyData key;
17407 : 422 : HeapTuple inheritsTuple;
17408 : 422 : int32 inhseqno;
17409 : :
17410 : : /* Note: get RowExclusiveLock because we will write pg_inherits below. */
17411 : 422 : catalogRelation = table_open(InheritsRelationId, RowExclusiveLock);
17412 : :
17413 : : /*
17414 : : * Check for duplicates in the list of parents, and determine the highest
17415 : : * inhseqno already present; we'll use the next one for the new parent.
17416 : : * Also, if proposed child is a partition, it cannot already be
17417 : : * inheriting.
17418 : : *
17419 : : * Note: we do not reject the case where the child already inherits from
17420 : : * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
17421 : : */
17422 : 422 : ScanKeyInit(&key,
17423 : : Anum_pg_inherits_inhrelid,
17424 : : BTEqualStrategyNumber, F_OIDEQ,
17425 : 422 : ObjectIdGetDatum(RelationGetRelid(child_rel)));
17426 : 422 : scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
17427 : : true, NULL, 1, &key);
17428 : :
17429 : : /* inhseqno sequences start at 1 */
17430 : 422 : inhseqno = 0;
17431 [ + + ]: 430 : while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
17432 : : {
17433 : 9 : Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
17434 : :
17435 [ + + ]: 9 : if (inh->inhparent == RelationGetRelid(parent_rel))
17436 [ + - + - ]: 1 : ereport(ERROR,
17437 : : (errcode(ERRCODE_DUPLICATE_TABLE),
17438 : : errmsg("relation \"%s\" would be inherited from more than once",
17439 : : RelationGetRelationName(parent_rel))));
17440 : :
17441 [ - + ]: 8 : if (inh->inhseqno > inhseqno)
17442 : 8 : inhseqno = inh->inhseqno;
17443 : 8 : }
17444 : 421 : systable_endscan(scan);
17445 : :
17446 : : /* Match up the columns and bump attinhcount as needed */
17447 : 421 : MergeAttributesIntoExisting(child_rel, parent_rel, ispartition);
17448 : :
17449 : : /* Match up the constraints and bump coninhcount as needed */
17450 : 421 : MergeConstraintsIntoExisting(child_rel, parent_rel);
17451 : :
17452 : : /*
17453 : : * OK, it looks valid. Make the catalog entries that show inheritance.
17454 : : */
17455 : 842 : StoreCatalogInheritance1(RelationGetRelid(child_rel),
17456 : 421 : RelationGetRelid(parent_rel),
17457 : 421 : inhseqno + 1,
17458 : 421 : catalogRelation,
17459 : 421 : parent_rel->rd_rel->relkind ==
17460 : : RELKIND_PARTITIONED_TABLE);
17461 : :
17462 : : /* Now we're done with pg_inherits */
17463 : 421 : table_close(catalogRelation, RowExclusiveLock);
17464 : 421 : }
17465 : :
17466 : : /*
17467 : : * Obtain the source-text form of the constraint expression for a check
17468 : : * constraint, given its pg_constraint tuple
17469 : : */
17470 : : static char *
17471 : 62 : decompile_conbin(HeapTuple contup, TupleDesc tupdesc)
17472 : : {
17473 : 62 : Form_pg_constraint con;
17474 : 62 : bool isnull;
17475 : 62 : Datum attr;
17476 : 62 : Datum expr;
17477 : :
17478 : 62 : con = (Form_pg_constraint) GETSTRUCT(contup);
17479 : 62 : attr = heap_getattr(contup, Anum_pg_constraint_conbin, tupdesc, &isnull);
17480 [ + - ]: 62 : if (isnull)
17481 [ # # # # ]: 0 : elog(ERROR, "null conbin for constraint %u", con->oid);
17482 : :
17483 : 62 : expr = DirectFunctionCall2(pg_get_expr, attr,
17484 : : ObjectIdGetDatum(con->conrelid));
17485 : 124 : return TextDatumGetCString(expr);
17486 : 62 : }
17487 : :
17488 : : /*
17489 : : * Determine whether two check constraints are functionally equivalent
17490 : : *
17491 : : * The test we apply is to see whether they reverse-compile to the same
17492 : : * source string. This insulates us from issues like whether attributes
17493 : : * have the same physical column numbers in parent and child relations.
17494 : : *
17495 : : * Note that we ignore enforceability as there are cases where constraints
17496 : : * with differing enforceability are allowed.
17497 : : */
17498 : : static bool
17499 : 31 : constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
17500 : : {
17501 : 31 : Form_pg_constraint acon = (Form_pg_constraint) GETSTRUCT(a);
17502 : 31 : Form_pg_constraint bcon = (Form_pg_constraint) GETSTRUCT(b);
17503 : :
17504 [ + - ]: 31 : if (acon->condeferrable != bcon->condeferrable ||
17505 [ + - + + ]: 31 : acon->condeferred != bcon->condeferred ||
17506 : 62 : strcmp(decompile_conbin(a, tupleDesc),
17507 : 62 : decompile_conbin(b, tupleDesc)) != 0)
17508 : 1 : return false;
17509 : : else
17510 : 30 : return true;
17511 : 31 : }
17512 : :
17513 : : /*
17514 : : * Check columns in child table match up with columns in parent, and increment
17515 : : * their attinhcount.
17516 : : *
17517 : : * Called by CreateInheritance
17518 : : *
17519 : : * Currently all parent columns must be found in child. Missing columns are an
17520 : : * error. One day we might consider creating new columns like CREATE TABLE
17521 : : * does. However, that is widely unpopular --- in the common use case of
17522 : : * partitioned tables it's a foot-gun.
17523 : : *
17524 : : * The data type must match exactly. If the parent column is NOT NULL then
17525 : : * the child must be as well. Defaults are not compared, however.
17526 : : */
17527 : : static void
17528 : 421 : MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel, bool ispartition)
17529 : : {
17530 : 421 : Relation attrrel;
17531 : 421 : TupleDesc parent_desc;
17532 : :
17533 : 421 : attrrel = table_open(AttributeRelationId, RowExclusiveLock);
17534 : 421 : parent_desc = RelationGetDescr(parent_rel);
17535 : :
17536 [ + + ]: 1389 : for (AttrNumber parent_attno = 1; parent_attno <= parent_desc->natts; parent_attno++)
17537 : : {
17538 : 990 : Form_pg_attribute parent_att = TupleDescAttr(parent_desc, parent_attno - 1);
17539 : 990 : char *parent_attname = NameStr(parent_att->attname);
17540 : 990 : HeapTuple tuple;
17541 : :
17542 : : /* Ignore dropped columns in the parent. */
17543 [ + + ]: 990 : if (parent_att->attisdropped)
17544 : 32 : continue;
17545 : :
17546 : : /* Find same column in child (matching on column name). */
17547 : 958 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(child_rel), parent_attname);
17548 [ + + ]: 958 : if (HeapTupleIsValid(tuple))
17549 : : {
17550 : 956 : Form_pg_attribute child_att = (Form_pg_attribute) GETSTRUCT(tuple);
17551 : :
17552 [ + + ]: 956 : if (parent_att->atttypid != child_att->atttypid ||
17553 : 954 : parent_att->atttypmod != child_att->atttypmod)
17554 [ + - + - ]: 2 : ereport(ERROR,
17555 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
17556 : : errmsg("child table \"%s\" has different type for column \"%s\"",
17557 : : RelationGetRelationName(child_rel), parent_attname)));
17558 : :
17559 [ + + ]: 954 : if (parent_att->attcollation != child_att->attcollation)
17560 [ + - + - ]: 1 : ereport(ERROR,
17561 : : (errcode(ERRCODE_COLLATION_MISMATCH),
17562 : : errmsg("child table \"%s\" has different collation for column \"%s\"",
17563 : : RelationGetRelationName(child_rel), parent_attname)));
17564 : :
17565 : : /*
17566 : : * If the parent has a not-null constraint that's not NO INHERIT,
17567 : : * make sure the child has one too.
17568 : : *
17569 : : * Other constraints are checked elsewhere.
17570 : : */
17571 [ + + + + ]: 953 : if (parent_att->attnotnull && !child_att->attnotnull)
17572 : : {
17573 : 8 : HeapTuple contup;
17574 : :
17575 : 16 : contup = findNotNullConstraintAttnum(RelationGetRelid(parent_rel),
17576 : 8 : parent_att->attnum);
17577 [ + - + + ]: 8 : if (HeapTupleIsValid(contup) &&
17578 : 8 : !((Form_pg_constraint) GETSTRUCT(contup))->connoinherit)
17579 [ + - + - ]: 5 : ereport(ERROR,
17580 : : errcode(ERRCODE_DATATYPE_MISMATCH),
17581 : : errmsg("column \"%s\" in child table \"%s\" must be marked NOT NULL",
17582 : : parent_attname, RelationGetRelationName(child_rel)));
17583 : 3 : }
17584 : :
17585 : : /*
17586 : : * Child column must be generated if and only if parent column is.
17587 : : */
17588 [ + + + + ]: 948 : if (parent_att->attgenerated && !child_att->attgenerated)
17589 [ + - + - ]: 6 : ereport(ERROR,
17590 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
17591 : : errmsg("column \"%s\" in child table must be a generated column", parent_attname)));
17592 [ + + + + ]: 942 : if (child_att->attgenerated && !parent_att->attgenerated)
17593 [ + - + - ]: 4 : ereport(ERROR,
17594 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
17595 : : errmsg("column \"%s\" in child table must not be a generated column", parent_attname)));
17596 : :
17597 [ + + + - : 938 : if (parent_att->attgenerated && child_att->attgenerated && child_att->attgenerated != parent_att->attgenerated)
+ + ]
17598 [ + - + - ]: 2 : ereport(ERROR,
17599 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
17600 : : errmsg("column \"%s\" inherits from generated column of different kind", parent_attname),
17601 : : errdetail("Parent column is %s, child column is %s.",
17602 : : parent_att->attgenerated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL",
17603 : : child_att->attgenerated == ATTRIBUTE_GENERATED_STORED ? "STORED" : "VIRTUAL")));
17604 : :
17605 : : /*
17606 : : * Regular inheritance children are independent enough not to
17607 : : * inherit identity columns. But partitions are integral part of
17608 : : * a partitioned table and inherit identity column.
17609 : : */
17610 [ + + ]: 936 : if (ispartition)
17611 : 863 : child_att->attidentity = parent_att->attidentity;
17612 : :
17613 : : /*
17614 : : * OK, bump the child column's inheritance count. (If we fail
17615 : : * later on, this change will just roll back.)
17616 : : */
17617 [ + - + - ]: 1872 : if (pg_add_s16_overflow(child_att->attinhcount, 1,
17618 : 936 : &child_att->attinhcount))
17619 [ # # # # ]: 0 : ereport(ERROR,
17620 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
17621 : : errmsg("too many inheritance parents"));
17622 : :
17623 : : /*
17624 : : * In case of partitions, we must enforce that value of attislocal
17625 : : * is same in all partitions. (Note: there are only inherited
17626 : : * attributes in partitions)
17627 : : */
17628 [ + + ]: 936 : if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
17629 : : {
17630 [ - + ]: 863 : Assert(child_att->attinhcount == 1);
17631 : 863 : child_att->attislocal = false;
17632 : 863 : }
17633 : :
17634 : 936 : CatalogTupleUpdate(attrrel, &tuple->t_self, tuple);
17635 : 936 : heap_freetuple(tuple);
17636 : 936 : }
17637 : : else
17638 : : {
17639 [ + - + - ]: 2 : ereport(ERROR,
17640 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
17641 : : errmsg("child table is missing column \"%s\"", parent_attname)));
17642 : : }
17643 [ - + + ]: 968 : }
17644 : :
17645 : 399 : table_close(attrrel, RowExclusiveLock);
17646 : 399 : }
17647 : :
17648 : : /*
17649 : : * Check constraints in child table match up with constraints in parent,
17650 : : * and increment their coninhcount.
17651 : : *
17652 : : * Constraints that are marked ONLY in the parent are ignored.
17653 : : *
17654 : : * Called by CreateInheritance
17655 : : *
17656 : : * Currently all constraints in parent must be present in the child. One day we
17657 : : * may consider adding new constraints like CREATE TABLE does.
17658 : : *
17659 : : * XXX This is O(N^2) which may be an issue with tables with hundreds of
17660 : : * constraints. As long as tables have more like 10 constraints it shouldn't be
17661 : : * a problem though. Even 100 constraints ought not be the end of the world.
17662 : : *
17663 : : * XXX See MergeWithExistingConstraint too if you change this code.
17664 : : */
17665 : : static void
17666 : 399 : MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
17667 : : {
17668 : 399 : Relation constraintrel;
17669 : 399 : SysScanDesc parent_scan;
17670 : 399 : ScanKeyData parent_key;
17671 : 399 : HeapTuple parent_tuple;
17672 : 399 : Oid parent_relid = RelationGetRelid(parent_rel);
17673 : 399 : AttrMap *attmap;
17674 : :
17675 : 399 : constraintrel = table_open(ConstraintRelationId, RowExclusiveLock);
17676 : :
17677 : : /* Outer loop scans through the parent's constraint definitions */
17678 : 399 : ScanKeyInit(&parent_key,
17679 : : Anum_pg_constraint_conrelid,
17680 : : BTEqualStrategyNumber, F_OIDEQ,
17681 : 399 : ObjectIdGetDatum(parent_relid));
17682 : 399 : parent_scan = systable_beginscan(constraintrel, ConstraintRelidTypidNameIndexId,
17683 : : true, NULL, 1, &parent_key);
17684 : :
17685 : 798 : attmap = build_attrmap_by_name(RelationGetDescr(parent_rel),
17686 : 399 : RelationGetDescr(child_rel),
17687 : : true);
17688 : :
17689 [ + + ]: 756 : while (HeapTupleIsValid(parent_tuple = systable_getnext(parent_scan)))
17690 : : {
17691 : 367 : Form_pg_constraint parent_con = (Form_pg_constraint) GETSTRUCT(parent_tuple);
17692 : 367 : SysScanDesc child_scan;
17693 : 367 : ScanKeyData child_key;
17694 : 367 : HeapTuple child_tuple;
17695 : 367 : AttrNumber parent_attno;
17696 : 367 : bool found = false;
17697 : :
17698 [ + + + + ]: 367 : if (parent_con->contype != CONSTRAINT_CHECK &&
17699 : 330 : parent_con->contype != CONSTRAINT_NOTNULL)
17700 : 171 : continue;
17701 : :
17702 : : /* if the parent's constraint is marked NO INHERIT, it's not inherited */
17703 [ + + ]: 196 : if (parent_con->connoinherit)
17704 : 6 : continue;
17705 : :
17706 [ + + ]: 190 : if (parent_con->contype == CONSTRAINT_NOTNULL)
17707 : 155 : parent_attno = extractNotNullColumn(parent_tuple);
17708 : : else
17709 : 35 : parent_attno = InvalidAttrNumber;
17710 : :
17711 : : /* Search for a child constraint matching this one */
17712 : 190 : ScanKeyInit(&child_key,
17713 : : Anum_pg_constraint_conrelid,
17714 : : BTEqualStrategyNumber, F_OIDEQ,
17715 : 190 : ObjectIdGetDatum(RelationGetRelid(child_rel)));
17716 : 190 : child_scan = systable_beginscan(constraintrel, ConstraintRelidTypidNameIndexId,
17717 : : true, NULL, 1, &child_key);
17718 : :
17719 [ + + ]: 497 : while (HeapTupleIsValid(child_tuple = systable_getnext(child_scan)))
17720 : : {
17721 : 313 : Form_pg_constraint child_con = (Form_pg_constraint) GETSTRUCT(child_tuple);
17722 : 313 : HeapTuple child_copy;
17723 : :
17724 [ + + ]: 313 : if (child_con->contype != parent_con->contype)
17725 : 68 : continue;
17726 : :
17727 : : /*
17728 : : * CHECK constraint are matched by constraint name, NOT NULL ones
17729 : : * by attribute number.
17730 : : */
17731 [ + + ]: 245 : if (child_con->contype == CONSTRAINT_CHECK)
17732 : : {
17733 : 90 : if (strcmp(NameStr(parent_con->conname),
17734 [ + + + + ]: 90 : NameStr(child_con->conname)) != 0)
17735 : 14 : continue;
17736 : 31 : }
17737 [ - + ]: 200 : else if (child_con->contype == CONSTRAINT_NOTNULL)
17738 : : {
17739 : 200 : Form_pg_attribute parent_attr;
17740 : 200 : Form_pg_attribute child_attr;
17741 : 200 : AttrNumber child_attno;
17742 : :
17743 : 200 : parent_attr = TupleDescAttr(parent_rel->rd_att, parent_attno - 1);
17744 : 200 : child_attno = extractNotNullColumn(child_tuple);
17745 [ + + ]: 200 : if (parent_attno != attmap->attnums[child_attno - 1])
17746 : 45 : continue;
17747 : :
17748 : 155 : child_attr = TupleDescAttr(child_rel->rd_att, child_attno - 1);
17749 : : /* there shouldn't be constraints on dropped columns */
17750 [ + - ]: 155 : if (parent_attr->attisdropped || child_attr->attisdropped)
17751 [ # # # # ]: 0 : elog(ERROR, "found not-null constraint on dropped columns");
17752 [ + + ]: 200 : }
17753 : :
17754 [ + + + + ]: 186 : if (child_con->contype == CONSTRAINT_CHECK &&
17755 : 31 : !constraints_equivalent(parent_tuple, child_tuple, RelationGetDescr(constraintrel)))
17756 [ - + + - ]: 1 : ereport(ERROR,
17757 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
17758 : : errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
17759 : : RelationGetRelationName(child_rel), NameStr(parent_con->conname))));
17760 : :
17761 : : /*
17762 : : * If the child constraint is "no inherit" then cannot merge
17763 : : */
17764 [ + + ]: 185 : if (child_con->connoinherit)
17765 [ - + + - ]: 2 : ereport(ERROR,
17766 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
17767 : : errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
17768 : : NameStr(child_con->conname), RelationGetRelationName(child_rel))));
17769 : :
17770 : : /*
17771 : : * If the child constraint is "not valid" then cannot merge with a
17772 : : * valid parent constraint
17773 : : */
17774 [ + + + + : 183 : if (parent_con->convalidated && child_con->conenforced &&
+ + ]
17775 : 165 : !child_con->convalidated)
17776 [ + - + - ]: 2 : ereport(ERROR,
17777 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
17778 : : errmsg("constraint \"%s\" conflicts with NOT VALID constraint on child table \"%s\"",
17779 : : NameStr(child_con->conname), RelationGetRelationName(child_rel))));
17780 : :
17781 : : /*
17782 : : * A NOT ENFORCED child constraint cannot be merged with an
17783 : : * ENFORCED parent constraint. However, the reverse is allowed,
17784 : : * where the child constraint is ENFORCED.
17785 : : */
17786 [ + + + + ]: 181 : if (parent_con->conenforced && !child_con->conenforced)
17787 [ - + + - ]: 1 : ereport(ERROR,
17788 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
17789 : : errmsg("constraint \"%s\" conflicts with NOT ENFORCED constraint on child table \"%s\"",
17790 : : NameStr(child_con->conname), RelationGetRelationName(child_rel))));
17791 : :
17792 : : /*
17793 : : * OK, bump the child constraint's inheritance count. (If we fail
17794 : : * later on, this change will just roll back.)
17795 : : */
17796 : 180 : child_copy = heap_copytuple(child_tuple);
17797 : 180 : child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
17798 : :
17799 [ - + - + ]: 360 : if (pg_add_s16_overflow(child_con->coninhcount, 1,
17800 : 180 : &child_con->coninhcount))
17801 [ # # # # ]: 0 : ereport(ERROR,
17802 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
17803 : : errmsg("too many inheritance parents"));
17804 : :
17805 : : /*
17806 : : * In case of partitions, an inherited constraint must be
17807 : : * inherited only once since it cannot have multiple parents and
17808 : : * it is never considered local.
17809 : : */
17810 [ + + ]: 180 : if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
17811 : : {
17812 [ - + ]: 163 : Assert(child_con->coninhcount == 1);
17813 : 163 : child_con->conislocal = false;
17814 : 163 : }
17815 : :
17816 : 180 : CatalogTupleUpdate(constraintrel, &child_copy->t_self, child_copy);
17817 : 180 : heap_freetuple(child_copy);
17818 : :
17819 : 180 : found = true;
17820 : 180 : break;
17821 : 307 : }
17822 : :
17823 : 184 : systable_endscan(child_scan);
17824 : :
17825 [ + + ]: 184 : if (!found)
17826 : : {
17827 [ + - ]: 4 : if (parent_con->contype == CONSTRAINT_NOTNULL)
17828 [ # # # # ]: 0 : ereport(ERROR,
17829 : : errcode(ERRCODE_DATATYPE_MISMATCH),
17830 : : errmsg("column \"%s\" in child table \"%s\" must be marked NOT NULL",
17831 : : get_attname(parent_relid,
17832 : : extractNotNullColumn(parent_tuple),
17833 : : false),
17834 : : RelationGetRelationName(child_rel)));
17835 : :
17836 [ + - + - ]: 4 : ereport(ERROR,
17837 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
17838 : : errmsg("child table is missing constraint \"%s\"",
17839 : : NameStr(parent_con->conname))));
17840 : 0 : }
17841 [ + + ]: 357 : }
17842 : :
17843 : 389 : systable_endscan(parent_scan);
17844 : 389 : table_close(constraintrel, RowExclusiveLock);
17845 : 389 : }
17846 : :
17847 : : /*
17848 : : * ALTER TABLE NO INHERIT
17849 : : *
17850 : : * Return value is the address of the relation that is no longer parent.
17851 : : */
17852 : : static ObjectAddress
17853 : 13 : ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
17854 : : {
17855 : : ObjectAddress address;
17856 : 13 : Relation parent_rel;
17857 : :
17858 [ + - ]: 13 : if (rel->rd_rel->relispartition)
17859 [ # # # # ]: 0 : ereport(ERROR,
17860 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17861 : : errmsg("cannot change inheritance of a partition")));
17862 : :
17863 : : /*
17864 : : * AccessShareLock on the parent is probably enough, seeing that DROP
17865 : : * TABLE doesn't lock parent tables at all. We need some lock since we'll
17866 : : * be inspecting the parent's schema.
17867 : : */
17868 : 13 : parent_rel = table_openrv(parent, AccessShareLock);
17869 : :
17870 : : /*
17871 : : * We don't bother to check ownership of the parent table --- ownership of
17872 : : * the child is presumed enough rights.
17873 : : */
17874 : :
17875 : : /* Off to RemoveInheritance() where most of the work happens */
17876 : 13 : RemoveInheritance(rel, parent_rel, false);
17877 : :
17878 : 13 : ObjectAddressSet(address, RelationRelationId,
17879 : : RelationGetRelid(parent_rel));
17880 : :
17881 : : /* keep our lock on the parent relation until commit */
17882 : 13 : table_close(parent_rel, NoLock);
17883 : :
17884 : : return address;
17885 : 13 : }
17886 : :
17887 : : /*
17888 : : * MarkInheritDetached
17889 : : *
17890 : : * Set inhdetachpending for a partition, for ATExecDetachPartition
17891 : : * in concurrent mode. While at it, verify that no other partition is
17892 : : * already pending detach.
17893 : : */
17894 : : static void
17895 : 2 : MarkInheritDetached(Relation child_rel, Relation parent_rel)
17896 : : {
17897 : 2 : Relation catalogRelation;
17898 : 2 : SysScanDesc scan;
17899 : 2 : ScanKeyData key;
17900 : 2 : HeapTuple inheritsTuple;
17901 : 2 : bool found = false;
17902 : :
17903 [ + - ]: 2 : Assert(parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
17904 : :
17905 : : /*
17906 : : * Find pg_inherits entries by inhparent. (We need to scan them all in
17907 : : * order to verify that no other partition is pending detach.)
17908 : : */
17909 : 2 : catalogRelation = table_open(InheritsRelationId, RowExclusiveLock);
17910 : 2 : ScanKeyInit(&key,
17911 : : Anum_pg_inherits_inhparent,
17912 : : BTEqualStrategyNumber, F_OIDEQ,
17913 : 2 : ObjectIdGetDatum(RelationGetRelid(parent_rel)));
17914 : 2 : scan = systable_beginscan(catalogRelation, InheritsParentIndexId,
17915 : : true, NULL, 1, &key);
17916 : :
17917 [ + + ]: 4 : while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
17918 : : {
17919 : 2 : Form_pg_inherits inhForm;
17920 : :
17921 : 2 : inhForm = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
17922 [ + - ]: 2 : if (inhForm->inhdetachpending)
17923 [ # # # # ]: 0 : ereport(ERROR,
17924 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
17925 : : errmsg("partition \"%s\" already pending detach in partitioned table \"%s.%s\"",
17926 : : get_rel_name(inhForm->inhrelid),
17927 : : get_namespace_name(parent_rel->rd_rel->relnamespace),
17928 : : RelationGetRelationName(parent_rel)),
17929 : : errhint("Use ALTER TABLE ... DETACH PARTITION ... FINALIZE to complete the pending detach operation."));
17930 : :
17931 [ - + ]: 2 : if (inhForm->inhrelid == RelationGetRelid(child_rel))
17932 : : {
17933 : 2 : HeapTuple newtup;
17934 : :
17935 : 2 : newtup = heap_copytuple(inheritsTuple);
17936 : 2 : ((Form_pg_inherits) GETSTRUCT(newtup))->inhdetachpending = true;
17937 : :
17938 : 4 : CatalogTupleUpdate(catalogRelation,
17939 : 2 : &inheritsTuple->t_self,
17940 : 2 : newtup);
17941 : 2 : found = true;
17942 : 2 : heap_freetuple(newtup);
17943 : : /* keep looking, to ensure we catch others pending detach */
17944 : 2 : }
17945 : 2 : }
17946 : :
17947 : : /* Done */
17948 : 2 : systable_endscan(scan);
17949 : 2 : table_close(catalogRelation, RowExclusiveLock);
17950 : :
17951 [ + - ]: 2 : if (!found)
17952 [ # # # # ]: 0 : ereport(ERROR,
17953 : : (errcode(ERRCODE_UNDEFINED_TABLE),
17954 : : errmsg("relation \"%s\" is not a partition of relation \"%s\"",
17955 : : RelationGetRelationName(child_rel),
17956 : : RelationGetRelationName(parent_rel))));
17957 : 2 : }
17958 : :
17959 : : /*
17960 : : * RemoveInheritance
17961 : : *
17962 : : * Drop a parent from the child's parents. This just adjusts the attinhcount
17963 : : * and attislocal of the columns and removes the pg_inherit and pg_depend
17964 : : * entries. expect_detached is passed down to DeleteInheritsTuple, q.v..
17965 : : *
17966 : : * If attinhcount goes to 0 then attislocal gets set to true. If it goes back
17967 : : * up attislocal stays true, which means if a child is ever removed from a
17968 : : * parent then its columns will never be automatically dropped which may
17969 : : * surprise. But at least we'll never surprise by dropping columns someone
17970 : : * isn't expecting to be dropped which would actually mean data loss.
17971 : : *
17972 : : * coninhcount and conislocal for inherited constraints are adjusted in
17973 : : * exactly the same way.
17974 : : *
17975 : : * Common to ATExecDropInherit() and ATExecDetachPartition().
17976 : : */
17977 : : static void
17978 : 166 : RemoveInheritance(Relation child_rel, Relation parent_rel, bool expect_detached)
17979 : : {
17980 : 166 : Relation catalogRelation;
17981 : 166 : SysScanDesc scan;
17982 : 166 : ScanKeyData key[3];
17983 : 166 : HeapTuple attributeTuple,
17984 : : constraintTuple;
17985 : 166 : AttrMap *attmap;
17986 : 166 : List *connames;
17987 : 166 : List *nncolumns;
17988 : 166 : bool found;
17989 : 166 : bool is_partitioning;
17990 : :
17991 : 166 : is_partitioning = (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
17992 : :
17993 : 332 : found = DeleteInheritsTuple(RelationGetRelid(child_rel),
17994 : 166 : RelationGetRelid(parent_rel),
17995 : 166 : expect_detached,
17996 : 166 : RelationGetRelationName(child_rel));
17997 [ + + ]: 166 : if (!found)
17998 : : {
17999 [ + + ]: 4 : if (is_partitioning)
18000 [ + - + - ]: 3 : ereport(ERROR,
18001 : : (errcode(ERRCODE_UNDEFINED_TABLE),
18002 : : errmsg("relation \"%s\" is not a partition of relation \"%s\"",
18003 : : RelationGetRelationName(child_rel),
18004 : : RelationGetRelationName(parent_rel))));
18005 : : else
18006 [ + - + - ]: 1 : ereport(ERROR,
18007 : : (errcode(ERRCODE_UNDEFINED_TABLE),
18008 : : errmsg("relation \"%s\" is not a parent of relation \"%s\"",
18009 : : RelationGetRelationName(parent_rel),
18010 : : RelationGetRelationName(child_rel))));
18011 : 0 : }
18012 : :
18013 : : /*
18014 : : * Search through child columns looking for ones matching parent rel
18015 : : */
18016 : 162 : catalogRelation = table_open(AttributeRelationId, RowExclusiveLock);
18017 : 324 : ScanKeyInit(&key[0],
18018 : : Anum_pg_attribute_attrelid,
18019 : : BTEqualStrategyNumber, F_OIDEQ,
18020 : 162 : ObjectIdGetDatum(RelationGetRelid(child_rel)));
18021 : 324 : scan = systable_beginscan(catalogRelation, AttributeRelidNumIndexId,
18022 : 162 : true, NULL, 1, key);
18023 [ + + ]: 1513 : while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
18024 : : {
18025 : 1351 : Form_pg_attribute att = (Form_pg_attribute) GETSTRUCT(attributeTuple);
18026 : :
18027 : : /* Ignore if dropped or not inherited */
18028 [ + + ]: 1351 : if (att->attisdropped)
18029 : 7 : continue;
18030 [ + + ]: 1344 : if (att->attinhcount <= 0)
18031 : 977 : continue;
18032 : :
18033 [ + + + + ]: 734 : if (SearchSysCacheExistsAttName(RelationGetRelid(parent_rel),
18034 : 367 : NameStr(att->attname)))
18035 : : {
18036 : : /* Decrement inhcount and possibly set islocal to true */
18037 : 358 : HeapTuple copyTuple = heap_copytuple(attributeTuple);
18038 : 358 : Form_pg_attribute copy_att = (Form_pg_attribute) GETSTRUCT(copyTuple);
18039 : :
18040 : 358 : copy_att->attinhcount--;
18041 [ + + ]: 358 : if (copy_att->attinhcount == 0)
18042 : 353 : copy_att->attislocal = true;
18043 : :
18044 : 358 : CatalogTupleUpdate(catalogRelation, ©Tuple->t_self, copyTuple);
18045 : 358 : heap_freetuple(copyTuple);
18046 : 358 : }
18047 [ + + ]: 1351 : }
18048 : 162 : systable_endscan(scan);
18049 : 162 : table_close(catalogRelation, RowExclusiveLock);
18050 : :
18051 : : /*
18052 : : * Likewise, find inherited check and not-null constraints and disinherit
18053 : : * them. To do this, we first need a list of the names of the parent's
18054 : : * check constraints. (We cheat a bit by only checking for name matches,
18055 : : * assuming that the expressions will match.)
18056 : : *
18057 : : * For NOT NULL columns, we store column numbers to match, mapping them in
18058 : : * to the child rel's attribute numbers.
18059 : : */
18060 : 324 : attmap = build_attrmap_by_name(RelationGetDescr(child_rel),
18061 : 162 : RelationGetDescr(parent_rel),
18062 : : false);
18063 : :
18064 : 162 : catalogRelation = table_open(ConstraintRelationId, RowExclusiveLock);
18065 : 324 : ScanKeyInit(&key[0],
18066 : : Anum_pg_constraint_conrelid,
18067 : : BTEqualStrategyNumber, F_OIDEQ,
18068 : 162 : ObjectIdGetDatum(RelationGetRelid(parent_rel)));
18069 : 324 : scan = systable_beginscan(catalogRelation, ConstraintRelidTypidNameIndexId,
18070 : 162 : true, NULL, 1, key);
18071 : :
18072 : 162 : connames = NIL;
18073 : 162 : nncolumns = NIL;
18074 : :
18075 [ + + ]: 326 : while (HeapTupleIsValid(constraintTuple = systable_getnext(scan)))
18076 : : {
18077 : 164 : Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(constraintTuple);
18078 : :
18079 [ + + ]: 164 : if (con->connoinherit)
18080 : 32 : continue;
18081 : :
18082 [ + + ]: 132 : if (con->contype == CONSTRAINT_CHECK)
18083 : 19 : connames = lappend(connames, pstrdup(NameStr(con->conname)));
18084 [ + + ]: 132 : if (con->contype == CONSTRAINT_NOTNULL)
18085 : : {
18086 : 58 : AttrNumber parent_attno = extractNotNullColumn(constraintTuple);
18087 : :
18088 : 58 : nncolumns = lappend_int(nncolumns, attmap->attnums[parent_attno - 1]);
18089 : 58 : }
18090 [ + + ]: 164 : }
18091 : :
18092 : 162 : systable_endscan(scan);
18093 : :
18094 : : /* Now scan the child's constraints to find matches */
18095 : 324 : ScanKeyInit(&key[0],
18096 : : Anum_pg_constraint_conrelid,
18097 : : BTEqualStrategyNumber, F_OIDEQ,
18098 : 162 : ObjectIdGetDatum(RelationGetRelid(child_rel)));
18099 : 324 : scan = systable_beginscan(catalogRelation, ConstraintRelidTypidNameIndexId,
18100 : 162 : true, NULL, 1, key);
18101 : :
18102 [ + + ]: 324 : while (HeapTupleIsValid(constraintTuple = systable_getnext(scan)))
18103 : : {
18104 : 162 : Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(constraintTuple);
18105 : 162 : bool match = false;
18106 : :
18107 : : /*
18108 : : * Match CHECK constraints by name, not-null constraints by column
18109 : : * number, and ignore all others.
18110 : : */
18111 [ + + ]: 162 : if (con->contype == CONSTRAINT_CHECK)
18112 : : {
18113 [ + + + + : 74 : foreach_ptr(char, chkname, connames)
+ + + + ]
18114 : : {
18115 [ + - + + ]: 20 : if (con->contype == CONSTRAINT_CHECK &&
18116 : 20 : strcmp(NameStr(con->conname), chkname) == 0)
18117 : : {
18118 : 19 : match = true;
18119 : 19 : connames = foreach_delete_current(connames, chkname);
18120 : 19 : break;
18121 : : }
18122 : 28 : }
18123 : 27 : }
18124 [ + + ]: 135 : else if (con->contype == CONSTRAINT_NOTNULL)
18125 : : {
18126 : 68 : AttrNumber child_attno = extractNotNullColumn(constraintTuple);
18127 : :
18128 [ + + + + : 195 : foreach_int(prevattno, nncolumns)
+ + + + ]
18129 : : {
18130 [ + + ]: 59 : if (prevattno == child_attno)
18131 : : {
18132 : 58 : match = true;
18133 : 58 : nncolumns = foreach_delete_current(nncolumns, prevattno);
18134 : 58 : break;
18135 : : }
18136 : 69 : }
18137 : 68 : }
18138 : : else
18139 : 67 : continue;
18140 : :
18141 [ + + ]: 95 : if (match)
18142 : : {
18143 : : /* Decrement inhcount and possibly set islocal to true */
18144 : 77 : HeapTuple copyTuple = heap_copytuple(constraintTuple);
18145 : 77 : Form_pg_constraint copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
18146 : :
18147 [ + - ]: 77 : if (copy_con->coninhcount <= 0) /* shouldn't happen */
18148 [ # # # # ]: 0 : elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
18149 : : RelationGetRelid(child_rel), NameStr(copy_con->conname));
18150 : :
18151 : 77 : copy_con->coninhcount--;
18152 [ + + ]: 77 : if (copy_con->coninhcount == 0)
18153 : 74 : copy_con->conislocal = true;
18154 : :
18155 : 77 : CatalogTupleUpdate(catalogRelation, ©Tuple->t_self, copyTuple);
18156 : 77 : heap_freetuple(copyTuple);
18157 : 77 : }
18158 [ + + ]: 162 : }
18159 : :
18160 : : /* We should have matched all constraints */
18161 [ + - ]: 162 : if (connames != NIL || nncolumns != NIL)
18162 [ # # # # ]: 0 : elog(ERROR, "%d unmatched constraints while removing inheritance from \"%s\" to \"%s\"",
18163 : : list_length(connames) + list_length(nncolumns),
18164 : : RelationGetRelationName(child_rel), RelationGetRelationName(parent_rel));
18165 : :
18166 : 162 : systable_endscan(scan);
18167 : 162 : table_close(catalogRelation, RowExclusiveLock);
18168 : :
18169 : 324 : drop_parent_dependency(RelationGetRelid(child_rel),
18170 : : RelationRelationId,
18171 : 162 : RelationGetRelid(parent_rel),
18172 : 162 : child_dependency_type(is_partitioning));
18173 : :
18174 : : /*
18175 : : * Post alter hook of this inherits. Since object_access_hook doesn't take
18176 : : * multiple object identifiers, we relay oid of parent relation using
18177 : : * auxiliary_id argument.
18178 : : */
18179 [ - + ]: 162 : InvokeObjectPostAlterHookArg(InheritsRelationId,
18180 : : RelationGetRelid(child_rel), 0,
18181 : : RelationGetRelid(parent_rel), false);
18182 : 162 : }
18183 : :
18184 : : /*
18185 : : * Drop the dependency created by StoreCatalogInheritance1 (CREATE TABLE
18186 : : * INHERITS/ALTER TABLE INHERIT -- refclassid will be RelationRelationId) or
18187 : : * heap_create_with_catalog (CREATE TABLE OF/ALTER TABLE OF -- refclassid will
18188 : : * be TypeRelationId). There's no convenient way to do this, so go trawling
18189 : : * through pg_depend.
18190 : : */
18191 : : static void
18192 : 164 : drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid,
18193 : : DependencyType deptype)
18194 : : {
18195 : 164 : Relation catalogRelation;
18196 : 164 : SysScanDesc scan;
18197 : 164 : ScanKeyData key[3];
18198 : 164 : HeapTuple depTuple;
18199 : :
18200 : 164 : catalogRelation = table_open(DependRelationId, RowExclusiveLock);
18201 : :
18202 : 328 : ScanKeyInit(&key[0],
18203 : : Anum_pg_depend_classid,
18204 : : BTEqualStrategyNumber, F_OIDEQ,
18205 : 164 : ObjectIdGetDatum(RelationRelationId));
18206 : 328 : ScanKeyInit(&key[1],
18207 : : Anum_pg_depend_objid,
18208 : : BTEqualStrategyNumber, F_OIDEQ,
18209 : 164 : ObjectIdGetDatum(relid));
18210 : 328 : ScanKeyInit(&key[2],
18211 : : Anum_pg_depend_objsubid,
18212 : : BTEqualStrategyNumber, F_INT4EQ,
18213 : 164 : Int32GetDatum(0));
18214 : :
18215 : 328 : scan = systable_beginscan(catalogRelation, DependDependerIndexId, true,
18216 : 164 : NULL, 3, key);
18217 : :
18218 [ + + ]: 504 : while (HeapTupleIsValid(depTuple = systable_getnext(scan)))
18219 : : {
18220 : 340 : Form_pg_depend dep = (Form_pg_depend) GETSTRUCT(depTuple);
18221 : :
18222 [ + + ]: 340 : if (dep->refclassid == refclassid &&
18223 [ + + ]: 171 : dep->refobjid == refobjid &&
18224 [ + - - + ]: 164 : dep->refobjsubid == 0 &&
18225 : 164 : dep->deptype == deptype)
18226 : 164 : CatalogTupleDelete(catalogRelation, &depTuple->t_self);
18227 : 340 : }
18228 : :
18229 : 164 : systable_endscan(scan);
18230 : 164 : table_close(catalogRelation, RowExclusiveLock);
18231 : 164 : }
18232 : :
18233 : : /*
18234 : : * ALTER TABLE OF
18235 : : *
18236 : : * Attach a table to a composite type, as though it had been created with CREATE
18237 : : * TABLE OF. All attname, atttypid, atttypmod and attcollation must match. The
18238 : : * subject table must not have inheritance parents. These restrictions ensure
18239 : : * that you cannot create a configuration impossible with CREATE TABLE OF alone.
18240 : : *
18241 : : * The address of the type is returned.
18242 : : */
18243 : : static ObjectAddress
18244 : 9 : ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
18245 : : {
18246 : 9 : Oid relid = RelationGetRelid(rel);
18247 : 9 : Type typetuple;
18248 : 9 : Form_pg_type typeform;
18249 : 9 : Oid typeid;
18250 : 9 : Relation inheritsRelation,
18251 : : relationRelation;
18252 : 9 : SysScanDesc scan;
18253 : 9 : ScanKeyData key;
18254 : 9 : AttrNumber table_attno,
18255 : : type_attno;
18256 : 9 : TupleDesc typeTupleDesc,
18257 : : tableTupleDesc;
18258 : 9 : ObjectAddress tableobj,
18259 : : typeobj;
18260 : 9 : HeapTuple classtuple;
18261 : :
18262 : : /* Validate the type. */
18263 : 9 : typetuple = typenameType(NULL, ofTypename, NULL);
18264 : 9 : check_of_type(typetuple);
18265 : 9 : typeform = (Form_pg_type) GETSTRUCT(typetuple);
18266 : 9 : typeid = typeform->oid;
18267 : :
18268 : : /* Fail if the table has any inheritance parents. */
18269 : 9 : inheritsRelation = table_open(InheritsRelationId, AccessShareLock);
18270 : 9 : ScanKeyInit(&key,
18271 : : Anum_pg_inherits_inhrelid,
18272 : : BTEqualStrategyNumber, F_OIDEQ,
18273 : 9 : ObjectIdGetDatum(relid));
18274 : 9 : scan = systable_beginscan(inheritsRelation, InheritsRelidSeqnoIndexId,
18275 : : true, NULL, 1, &key);
18276 [ + + ]: 9 : if (HeapTupleIsValid(systable_getnext(scan)))
18277 [ + - + - ]: 1 : ereport(ERROR,
18278 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18279 : : errmsg("typed tables cannot inherit")));
18280 : 8 : systable_endscan(scan);
18281 : 8 : table_close(inheritsRelation, AccessShareLock);
18282 : :
18283 : : /*
18284 : : * Check the tuple descriptors for compatibility. Unlike inheritance, we
18285 : : * require that the order also match. However, attnotnull need not match.
18286 : : */
18287 : 8 : typeTupleDesc = lookup_rowtype_tupdesc(typeid, -1);
18288 : 8 : tableTupleDesc = RelationGetDescr(rel);
18289 : 8 : table_attno = 1;
18290 [ + + ]: 26 : for (type_attno = 1; type_attno <= typeTupleDesc->natts; type_attno++)
18291 : : {
18292 : 22 : Form_pg_attribute type_attr,
18293 : : table_attr;
18294 : 22 : const char *type_attname,
18295 : : *table_attname;
18296 : :
18297 : : /* Get the next non-dropped type attribute. */
18298 : 22 : type_attr = TupleDescAttr(typeTupleDesc, type_attno - 1);
18299 [ + + ]: 22 : if (type_attr->attisdropped)
18300 : 7 : continue;
18301 : 15 : type_attname = NameStr(type_attr->attname);
18302 : :
18303 : : /* Get the next non-dropped table attribute. */
18304 : 15 : do
18305 : : {
18306 [ + + ]: 17 : if (table_attno > tableTupleDesc->natts)
18307 [ + - + - ]: 1 : ereport(ERROR,
18308 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
18309 : : errmsg("table is missing column \"%s\"",
18310 : : type_attname)));
18311 : 16 : table_attr = TupleDescAttr(tableTupleDesc, table_attno - 1);
18312 : 16 : table_attno++;
18313 [ + + ]: 16 : } while (table_attr->attisdropped);
18314 : 14 : table_attname = NameStr(table_attr->attname);
18315 : :
18316 : : /* Compare name. */
18317 [ + + ]: 14 : if (strncmp(table_attname, type_attname, NAMEDATALEN) != 0)
18318 [ - + + - ]: 1 : ereport(ERROR,
18319 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
18320 : : errmsg("table has column \"%s\" where type requires \"%s\"",
18321 : : table_attname, type_attname)));
18322 : :
18323 : : /* Compare type. */
18324 [ + + ]: 13 : if (table_attr->atttypid != type_attr->atttypid ||
18325 : 11 : table_attr->atttypmod != type_attr->atttypmod ||
18326 : 11 : table_attr->attcollation != type_attr->attcollation)
18327 [ + - + - ]: 2 : ereport(ERROR,
18328 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
18329 : : errmsg("table \"%s\" has different type for column \"%s\"",
18330 : : RelationGetRelationName(rel), type_attname)));
18331 [ - + + ]: 18 : }
18332 [ - + ]: 4 : ReleaseTupleDesc(typeTupleDesc);
18333 : :
18334 : : /* Any remaining columns at the end of the table had better be dropped. */
18335 [ + + ]: 4 : for (; table_attno <= tableTupleDesc->natts; table_attno++)
18336 : : {
18337 : 2 : Form_pg_attribute table_attr = TupleDescAttr(tableTupleDesc,
18338 : 1 : table_attno - 1);
18339 : :
18340 [ - + ]: 1 : if (!table_attr->attisdropped)
18341 [ + - + - ]: 1 : ereport(ERROR,
18342 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
18343 : : errmsg("table has extra column \"%s\"",
18344 : : NameStr(table_attr->attname))));
18345 : 0 : }
18346 : :
18347 : : /* If the table was already typed, drop the existing dependency. */
18348 [ + + ]: 3 : if (rel->rd_rel->reloftype)
18349 : 1 : drop_parent_dependency(relid, TypeRelationId, rel->rd_rel->reloftype,
18350 : : DEPENDENCY_NORMAL);
18351 : :
18352 : : /* Record a dependency on the new type. */
18353 : 3 : tableobj.classId = RelationRelationId;
18354 : 3 : tableobj.objectId = relid;
18355 : 3 : tableobj.objectSubId = 0;
18356 : 3 : typeobj.classId = TypeRelationId;
18357 : 3 : typeobj.objectId = typeid;
18358 : 3 : typeobj.objectSubId = 0;
18359 : 3 : recordDependencyOn(&tableobj, &typeobj, DEPENDENCY_NORMAL);
18360 : :
18361 : : /* Update pg_class.reloftype */
18362 : 3 : relationRelation = table_open(RelationRelationId, RowExclusiveLock);
18363 : 3 : classtuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
18364 [ + - ]: 3 : if (!HeapTupleIsValid(classtuple))
18365 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
18366 : 3 : ((Form_pg_class) GETSTRUCT(classtuple))->reloftype = typeid;
18367 : 3 : CatalogTupleUpdate(relationRelation, &classtuple->t_self, classtuple);
18368 : :
18369 [ + - ]: 3 : InvokeObjectPostAlterHook(RelationRelationId, relid, 0);
18370 : :
18371 : 3 : heap_freetuple(classtuple);
18372 : 3 : table_close(relationRelation, RowExclusiveLock);
18373 : :
18374 : 3 : ReleaseSysCache(typetuple);
18375 : :
18376 : : return typeobj;
18377 : 3 : }
18378 : :
18379 : : /*
18380 : : * ALTER TABLE NOT OF
18381 : : *
18382 : : * Detach a typed table from its originating type. Just clear reloftype and
18383 : : * remove the dependency.
18384 : : */
18385 : : static void
18386 : 1 : ATExecDropOf(Relation rel, LOCKMODE lockmode)
18387 : : {
18388 : 1 : Oid relid = RelationGetRelid(rel);
18389 : 1 : Relation relationRelation;
18390 : 1 : HeapTuple tuple;
18391 : :
18392 [ + - ]: 1 : if (!OidIsValid(rel->rd_rel->reloftype))
18393 [ # # # # ]: 0 : ereport(ERROR,
18394 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18395 : : errmsg("\"%s\" is not a typed table",
18396 : : RelationGetRelationName(rel))));
18397 : :
18398 : : /*
18399 : : * We don't bother to check ownership of the type --- ownership of the
18400 : : * table is presumed enough rights. No lock required on the type, either.
18401 : : */
18402 : :
18403 : 1 : drop_parent_dependency(relid, TypeRelationId, rel->rd_rel->reloftype,
18404 : : DEPENDENCY_NORMAL);
18405 : :
18406 : : /* Clear pg_class.reloftype */
18407 : 1 : relationRelation = table_open(RelationRelationId, RowExclusiveLock);
18408 : 1 : tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
18409 [ + - ]: 1 : if (!HeapTupleIsValid(tuple))
18410 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
18411 : 1 : ((Form_pg_class) GETSTRUCT(tuple))->reloftype = InvalidOid;
18412 : 1 : CatalogTupleUpdate(relationRelation, &tuple->t_self, tuple);
18413 : :
18414 [ + - ]: 1 : InvokeObjectPostAlterHook(RelationRelationId, relid, 0);
18415 : :
18416 : 1 : heap_freetuple(tuple);
18417 : 1 : table_close(relationRelation, RowExclusiveLock);
18418 : 1 : }
18419 : :
18420 : : /*
18421 : : * relation_mark_replica_identity: Update a table's replica identity
18422 : : *
18423 : : * Iff ri_type = REPLICA_IDENTITY_INDEX, indexOid must be the Oid of a suitable
18424 : : * index. Otherwise, it must be InvalidOid.
18425 : : *
18426 : : * Caller had better hold an exclusive lock on the relation, as the results
18427 : : * of running two of these concurrently wouldn't be pretty.
18428 : : */
18429 : : static void
18430 : 49 : relation_mark_replica_identity(Relation rel, char ri_type, Oid indexOid,
18431 : : bool is_internal)
18432 : : {
18433 : 49 : Relation pg_index;
18434 : 49 : Relation pg_class;
18435 : 49 : HeapTuple pg_class_tuple;
18436 : 49 : HeapTuple pg_index_tuple;
18437 : 49 : Form_pg_class pg_class_form;
18438 : 49 : Form_pg_index pg_index_form;
18439 : 49 : ListCell *index;
18440 : :
18441 : : /*
18442 : : * Check whether relreplident has changed, and update it if so.
18443 : : */
18444 : 49 : pg_class = table_open(RelationRelationId, RowExclusiveLock);
18445 : 49 : pg_class_tuple = SearchSysCacheCopy1(RELOID,
18446 : : ObjectIdGetDatum(RelationGetRelid(rel)));
18447 [ + - ]: 49 : if (!HeapTupleIsValid(pg_class_tuple))
18448 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for relation \"%s\"",
18449 : : RelationGetRelationName(rel));
18450 : 49 : pg_class_form = (Form_pg_class) GETSTRUCT(pg_class_tuple);
18451 [ + + ]: 49 : if (pg_class_form->relreplident != ri_type)
18452 : : {
18453 : 41 : pg_class_form->relreplident = ri_type;
18454 : 41 : CatalogTupleUpdate(pg_class, &pg_class_tuple->t_self, pg_class_tuple);
18455 : 41 : }
18456 : 49 : table_close(pg_class, RowExclusiveLock);
18457 : 49 : heap_freetuple(pg_class_tuple);
18458 : :
18459 : : /*
18460 : : * Update the per-index indisreplident flags correctly.
18461 : : */
18462 : 49 : pg_index = table_open(IndexRelationId, RowExclusiveLock);
18463 [ + + + + : 153 : foreach(index, RelationGetIndexList(rel))
+ + ]
18464 : : {
18465 : 104 : Oid thisIndexOid = lfirst_oid(index);
18466 : 104 : bool dirty = false;
18467 : :
18468 : 104 : pg_index_tuple = SearchSysCacheCopy1(INDEXRELID,
18469 : : ObjectIdGetDatum(thisIndexOid));
18470 [ + - ]: 104 : if (!HeapTupleIsValid(pg_index_tuple))
18471 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for index %u", thisIndexOid);
18472 : 104 : pg_index_form = (Form_pg_index) GETSTRUCT(pg_index_tuple);
18473 : :
18474 [ + + ]: 104 : if (thisIndexOid == indexOid)
18475 : : {
18476 : : /* Set the bit if not already set. */
18477 [ + + ]: 30 : if (!pg_index_form->indisreplident)
18478 : : {
18479 : 27 : dirty = true;
18480 : 27 : pg_index_form->indisreplident = true;
18481 : 27 : }
18482 : 30 : }
18483 : : else
18484 : : {
18485 : : /* Unset the bit if set. */
18486 [ + + ]: 74 : if (pg_index_form->indisreplident)
18487 : : {
18488 : 8 : dirty = true;
18489 : 8 : pg_index_form->indisreplident = false;
18490 : 8 : }
18491 : : }
18492 : :
18493 [ + + ]: 104 : if (dirty)
18494 : : {
18495 : 35 : CatalogTupleUpdate(pg_index, &pg_index_tuple->t_self, pg_index_tuple);
18496 [ + - ]: 35 : InvokeObjectPostAlterHookArg(IndexRelationId, thisIndexOid, 0,
18497 : : InvalidOid, is_internal);
18498 : :
18499 : : /*
18500 : : * Invalidate the relcache for the table, so that after we commit
18501 : : * all sessions will refresh the table's replica identity index
18502 : : * before attempting any UPDATE or DELETE on the table. (If we
18503 : : * changed the table's pg_class row above, then a relcache inval
18504 : : * is already queued due to that; but we might not have.)
18505 : : */
18506 : 35 : CacheInvalidateRelcache(rel);
18507 : 35 : }
18508 : 104 : heap_freetuple(pg_index_tuple);
18509 : 104 : }
18510 : :
18511 : 49 : table_close(pg_index, RowExclusiveLock);
18512 : 49 : }
18513 : :
18514 : : /*
18515 : : * ALTER TABLE <name> REPLICA IDENTITY ...
18516 : : */
18517 : : static void
18518 : 57 : ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode)
18519 : : {
18520 : 57 : Oid indexOid;
18521 : 57 : Relation indexRel;
18522 : 57 : int key;
18523 : :
18524 [ + + ]: 57 : if (stmt->identity_type == REPLICA_IDENTITY_DEFAULT)
18525 : : {
18526 : 1 : relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
18527 : 1 : return;
18528 : : }
18529 [ + + ]: 56 : else if (stmt->identity_type == REPLICA_IDENTITY_FULL)
18530 : : {
18531 : 13 : relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
18532 : 13 : return;
18533 : : }
18534 [ + + ]: 43 : else if (stmt->identity_type == REPLICA_IDENTITY_NOTHING)
18535 : : {
18536 : 5 : relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
18537 : 5 : return;
18538 : : }
18539 [ + - ]: 38 : else if (stmt->identity_type == REPLICA_IDENTITY_INDEX)
18540 : : {
18541 : : /* fallthrough */ ;
18542 : 38 : }
18543 : : else
18544 [ # # # # ]: 0 : elog(ERROR, "unexpected identity type %u", stmt->identity_type);
18545 : :
18546 : : /* Check that the index exists */
18547 : 38 : indexOid = get_relname_relid(stmt->name, rel->rd_rel->relnamespace);
18548 [ + - ]: 38 : if (!OidIsValid(indexOid))
18549 [ # # # # ]: 0 : ereport(ERROR,
18550 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
18551 : : errmsg("index \"%s\" for table \"%s\" does not exist",
18552 : : stmt->name, RelationGetRelationName(rel))));
18553 : :
18554 : 38 : indexRel = index_open(indexOid, ShareLock);
18555 : :
18556 : : /* Check that the index is on the relation we're altering. */
18557 [ + + ]: 38 : if (indexRel->rd_index == NULL ||
18558 : 37 : indexRel->rd_index->indrelid != RelationGetRelid(rel))
18559 [ + - + - ]: 1 : ereport(ERROR,
18560 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18561 : : errmsg("\"%s\" is not an index for table \"%s\"",
18562 : : RelationGetRelationName(indexRel),
18563 : : RelationGetRelationName(rel))));
18564 : :
18565 : : /*
18566 : : * The AM must support uniqueness, and the index must in fact be unique.
18567 : : * If we have a WITHOUT OVERLAPS constraint (identified by uniqueness +
18568 : : * exclusion), we can use that too.
18569 : : */
18570 [ + + ]: 37 : if ((!indexRel->rd_indam->amcanunique ||
18571 [ + + ]: 36 : !indexRel->rd_index->indisunique) &&
18572 [ + + ]: 3 : !(indexRel->rd_index->indisunique && indexRel->rd_index->indisexclusion))
18573 [ + - + - ]: 2 : ereport(ERROR,
18574 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18575 : : errmsg("cannot use non-unique index \"%s\" as replica identity",
18576 : : RelationGetRelationName(indexRel))));
18577 : : /* Deferred indexes are not guaranteed to be always unique. */
18578 [ + + ]: 35 : if (!indexRel->rd_index->indimmediate)
18579 [ + - + - ]: 2 : ereport(ERROR,
18580 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
18581 : : errmsg("cannot use non-immediate index \"%s\" as replica identity",
18582 : : RelationGetRelationName(indexRel))));
18583 : : /* Expression indexes aren't supported. */
18584 [ + + ]: 33 : if (RelationGetIndexExpressions(indexRel) != NIL)
18585 [ + - + - ]: 1 : ereport(ERROR,
18586 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
18587 : : errmsg("cannot use expression index \"%s\" as replica identity",
18588 : : RelationGetRelationName(indexRel))));
18589 : : /* Predicate indexes aren't supported. */
18590 [ + + ]: 32 : if (RelationGetIndexPredicate(indexRel) != NIL)
18591 [ + - + - ]: 1 : ereport(ERROR,
18592 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
18593 : : errmsg("cannot use partial index \"%s\" as replica identity",
18594 : : RelationGetRelationName(indexRel))));
18595 : :
18596 : : /* Check index for nullable columns. */
18597 [ + + ]: 70 : for (key = 0; key < IndexRelationGetNumberOfKeyAttributes(indexRel); key++)
18598 : : {
18599 : 40 : int16 attno = indexRel->rd_index->indkey.values[key];
18600 : 40 : Form_pg_attribute attr;
18601 : :
18602 : : /*
18603 : : * Reject any other system columns. (Going forward, we'll disallow
18604 : : * indexes containing such columns in the first place, but they might
18605 : : * exist in older branches.)
18606 : : */
18607 [ + - ]: 40 : if (attno <= 0)
18608 [ # # # # ]: 0 : ereport(ERROR,
18609 : : (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
18610 : : errmsg("index \"%s\" cannot be used as replica identity because column %d is a system column",
18611 : : RelationGetRelationName(indexRel), attno)));
18612 : :
18613 : 40 : attr = TupleDescAttr(rel->rd_att, attno - 1);
18614 [ + + ]: 40 : if (!attr->attnotnull)
18615 [ + - + - ]: 1 : ereport(ERROR,
18616 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18617 : : errmsg("index \"%s\" cannot be used as replica identity because column \"%s\" is nullable",
18618 : : RelationGetRelationName(indexRel),
18619 : : NameStr(attr->attname))));
18620 : 39 : }
18621 : :
18622 : : /* This index is suitable for use as a replica identity. Mark it. */
18623 : 30 : relation_mark_replica_identity(rel, stmt->identity_type, indexOid, true);
18624 : :
18625 : 30 : index_close(indexRel, NoLock);
18626 [ - + ]: 49 : }
18627 : :
18628 : : /*
18629 : : * ALTER TABLE ENABLE/DISABLE ROW LEVEL SECURITY
18630 : : */
18631 : : static void
18632 : 54 : ATExecSetRowSecurity(Relation rel, bool rls)
18633 : : {
18634 : 54 : Relation pg_class;
18635 : 54 : Oid relid;
18636 : 54 : HeapTuple tuple;
18637 : :
18638 : 54 : relid = RelationGetRelid(rel);
18639 : :
18640 : : /* Pull the record for this relation and update it */
18641 : 54 : pg_class = table_open(RelationRelationId, RowExclusiveLock);
18642 : :
18643 : 54 : tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
18644 : :
18645 [ + - ]: 54 : if (!HeapTupleIsValid(tuple))
18646 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
18647 : :
18648 : 54 : ((Form_pg_class) GETSTRUCT(tuple))->relrowsecurity = rls;
18649 : 54 : CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
18650 : :
18651 [ + - ]: 54 : InvokeObjectPostAlterHook(RelationRelationId,
18652 : : RelationGetRelid(rel), 0);
18653 : :
18654 : 54 : table_close(pg_class, RowExclusiveLock);
18655 : 54 : heap_freetuple(tuple);
18656 : 54 : }
18657 : :
18658 : : /*
18659 : : * ALTER TABLE FORCE/NO FORCE ROW LEVEL SECURITY
18660 : : */
18661 : : static void
18662 : 19 : ATExecForceNoForceRowSecurity(Relation rel, bool force_rls)
18663 : : {
18664 : 19 : Relation pg_class;
18665 : 19 : Oid relid;
18666 : 19 : HeapTuple tuple;
18667 : :
18668 : 19 : relid = RelationGetRelid(rel);
18669 : :
18670 : 19 : pg_class = table_open(RelationRelationId, RowExclusiveLock);
18671 : :
18672 : 19 : tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
18673 : :
18674 [ + - ]: 19 : if (!HeapTupleIsValid(tuple))
18675 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
18676 : :
18677 : 19 : ((Form_pg_class) GETSTRUCT(tuple))->relforcerowsecurity = force_rls;
18678 : 19 : CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
18679 : :
18680 [ + - ]: 19 : InvokeObjectPostAlterHook(RelationRelationId,
18681 : : RelationGetRelid(rel), 0);
18682 : :
18683 : 19 : table_close(pg_class, RowExclusiveLock);
18684 : 19 : heap_freetuple(tuple);
18685 : 19 : }
18686 : :
18687 : : /*
18688 : : * ALTER FOREIGN TABLE <name> OPTIONS (...)
18689 : : */
18690 : : static void
18691 : 1 : ATExecGenericOptions(Relation rel, List *options)
18692 : : {
18693 : 1 : Relation ftrel;
18694 : 1 : ForeignServer *server;
18695 : 1 : ForeignDataWrapper *fdw;
18696 : 1 : HeapTuple tuple;
18697 : 1 : bool isnull;
18698 : 1 : Datum repl_val[Natts_pg_foreign_table];
18699 : 1 : bool repl_null[Natts_pg_foreign_table];
18700 : 1 : bool repl_repl[Natts_pg_foreign_table];
18701 : 1 : Datum datum;
18702 : 1 : Form_pg_foreign_table tableform;
18703 : :
18704 [ + - ]: 1 : if (options == NIL)
18705 : 0 : return;
18706 : :
18707 : 1 : ftrel = table_open(ForeignTableRelationId, RowExclusiveLock);
18708 : :
18709 : 1 : tuple = SearchSysCacheCopy1(FOREIGNTABLEREL,
18710 : : ObjectIdGetDatum(rel->rd_id));
18711 [ + - ]: 1 : if (!HeapTupleIsValid(tuple))
18712 [ # # # # ]: 0 : ereport(ERROR,
18713 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
18714 : : errmsg("foreign table \"%s\" does not exist",
18715 : : RelationGetRelationName(rel))));
18716 : 1 : tableform = (Form_pg_foreign_table) GETSTRUCT(tuple);
18717 : 1 : server = GetForeignServer(tableform->ftserver);
18718 : 1 : fdw = GetForeignDataWrapper(server->fdwid);
18719 : :
18720 : 1 : memset(repl_val, 0, sizeof(repl_val));
18721 : 1 : memset(repl_null, false, sizeof(repl_null));
18722 : 1 : memset(repl_repl, false, sizeof(repl_repl));
18723 : :
18724 : : /* Extract the current options */
18725 : 1 : datum = SysCacheGetAttr(FOREIGNTABLEREL,
18726 : 1 : tuple,
18727 : : Anum_pg_foreign_table_ftoptions,
18728 : : &isnull);
18729 [ + - ]: 1 : if (isnull)
18730 : 0 : datum = PointerGetDatum(NULL);
18731 : :
18732 : : /* Transform the options */
18733 : 1 : datum = transformGenericOptions(ForeignTableRelationId,
18734 : 1 : datum,
18735 : 1 : options,
18736 : 1 : fdw->fdwvalidator);
18737 : :
18738 [ + - ]: 1 : if (DatumGetPointer(datum) != NULL)
18739 : 1 : repl_val[Anum_pg_foreign_table_ftoptions - 1] = datum;
18740 : : else
18741 : 0 : repl_null[Anum_pg_foreign_table_ftoptions - 1] = true;
18742 : :
18743 : 1 : repl_repl[Anum_pg_foreign_table_ftoptions - 1] = true;
18744 : :
18745 : : /* Everything looks good - update the tuple */
18746 : :
18747 : 2 : tuple = heap_modify_tuple(tuple, RelationGetDescr(ftrel),
18748 : 1 : repl_val, repl_null, repl_repl);
18749 : :
18750 : 1 : CatalogTupleUpdate(ftrel, &tuple->t_self, tuple);
18751 : :
18752 : : /*
18753 : : * Invalidate relcache so that all sessions will refresh any cached plans
18754 : : * that might depend on the old options.
18755 : : */
18756 : 1 : CacheInvalidateRelcache(rel);
18757 : :
18758 [ + - ]: 1 : InvokeObjectPostAlterHook(ForeignTableRelationId,
18759 : : RelationGetRelid(rel), 0);
18760 : :
18761 : 1 : table_close(ftrel, RowExclusiveLock);
18762 : :
18763 : 1 : heap_freetuple(tuple);
18764 [ - + ]: 1 : }
18765 : :
18766 : : /*
18767 : : * ALTER TABLE ALTER COLUMN SET COMPRESSION
18768 : : *
18769 : : * Return value is the address of the modified column
18770 : : */
18771 : : static ObjectAddress
18772 : 7 : ATExecSetCompression(Relation rel,
18773 : : const char *column,
18774 : : Node *newValue,
18775 : : LOCKMODE lockmode)
18776 : : {
18777 : 7 : Relation attrel;
18778 : 7 : HeapTuple tuple;
18779 : 7 : Form_pg_attribute atttableform;
18780 : 7 : AttrNumber attnum;
18781 : 7 : char *compression;
18782 : 7 : char cmethod;
18783 : : ObjectAddress address;
18784 : :
18785 : 7 : compression = strVal(newValue);
18786 : :
18787 : 7 : attrel = table_open(AttributeRelationId, RowExclusiveLock);
18788 : :
18789 : : /* copy the cache entry so we can scribble on it below */
18790 : 7 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), column);
18791 [ + - ]: 7 : if (!HeapTupleIsValid(tuple))
18792 [ # # # # ]: 0 : ereport(ERROR,
18793 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
18794 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
18795 : : column, RelationGetRelationName(rel))));
18796 : :
18797 : : /* prevent them from altering a system attribute */
18798 : 7 : atttableform = (Form_pg_attribute) GETSTRUCT(tuple);
18799 : 7 : attnum = atttableform->attnum;
18800 [ + - ]: 7 : if (attnum <= 0)
18801 [ # # # # ]: 0 : ereport(ERROR,
18802 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
18803 : : errmsg("cannot alter system column \"%s\"", column)));
18804 : :
18805 : : /*
18806 : : * Check that column type is compressible, then get the attribute
18807 : : * compression method code
18808 : : */
18809 : 7 : cmethod = GetAttributeCompression(atttableform->atttypid, compression);
18810 : :
18811 : : /* update pg_attribute entry */
18812 : 7 : atttableform->attcompression = cmethod;
18813 : 7 : CatalogTupleUpdate(attrel, &tuple->t_self, tuple);
18814 : :
18815 [ + - ]: 7 : InvokeObjectPostAlterHook(RelationRelationId,
18816 : : RelationGetRelid(rel),
18817 : : attnum);
18818 : :
18819 : : /*
18820 : : * Apply the change to indexes as well (only for simple index columns,
18821 : : * matching behavior of index.c ConstructTupleDescriptor()).
18822 : : */
18823 : 14 : SetIndexStorageProperties(rel, attrel, attnum,
18824 : : false, 0,
18825 : 7 : true, cmethod,
18826 : 7 : lockmode);
18827 : :
18828 : 7 : heap_freetuple(tuple);
18829 : :
18830 : 7 : table_close(attrel, RowExclusiveLock);
18831 : :
18832 : : /* make changes visible */
18833 : 7 : CommandCounterIncrement();
18834 : :
18835 : 7 : ObjectAddressSubSet(address, RelationRelationId,
18836 : : RelationGetRelid(rel), attnum);
18837 : : return address;
18838 : 7 : }
18839 : :
18840 : :
18841 : : /*
18842 : : * Preparation phase for SET LOGGED/UNLOGGED
18843 : : *
18844 : : * This verifies that we're not trying to change a temp table. Also,
18845 : : * existing foreign key constraints are checked to avoid ending up with
18846 : : * permanent tables referencing unlogged tables.
18847 : : */
18848 : : static void
18849 : 16 : ATPrepChangePersistence(AlteredTableInfo *tab, Relation rel, bool toLogged)
18850 : : {
18851 : 16 : Relation pg_constraint;
18852 : 16 : HeapTuple tuple;
18853 : 16 : SysScanDesc scan;
18854 : 16 : ScanKeyData skey[1];
18855 : :
18856 : : /*
18857 : : * Disallow changing status for a temp table. Also verify whether we can
18858 : : * get away with doing nothing; in such cases we don't need to run the
18859 : : * checks below, either.
18860 : : */
18861 [ - + + - ]: 16 : switch (rel->rd_rel->relpersistence)
18862 : : {
18863 : : case RELPERSISTENCE_TEMP:
18864 [ # # # # ]: 0 : ereport(ERROR,
18865 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
18866 : : errmsg("cannot change logged status of table \"%s\" because it is temporary",
18867 : : RelationGetRelationName(rel)),
18868 : : errtable(rel)));
18869 : 0 : break;
18870 : : case RELPERSISTENCE_PERMANENT:
18871 [ + + ]: 9 : if (toLogged)
18872 : : /* nothing to do */
18873 : 1 : return;
18874 : 8 : break;
18875 : : case RELPERSISTENCE_UNLOGGED:
18876 [ + + ]: 7 : if (!toLogged)
18877 : : /* nothing to do */
18878 : 1 : return;
18879 : 6 : break;
18880 : : }
18881 : :
18882 : : /*
18883 : : * Check that the table is not part of any publication when changing to
18884 : : * UNLOGGED, as UNLOGGED tables can't be published.
18885 : : */
18886 [ + + + - ]: 14 : if (!toLogged &&
18887 : 8 : GetRelationPublications(RelationGetRelid(rel)) != NIL)
18888 [ # # # # ]: 0 : ereport(ERROR,
18889 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
18890 : : errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
18891 : : RelationGetRelationName(rel)),
18892 : : errdetail("Unlogged relations cannot be replicated.")));
18893 : :
18894 : : /*
18895 : : * Check existing foreign key constraints to preserve the invariant that
18896 : : * permanent tables cannot reference unlogged ones. Self-referencing
18897 : : * foreign keys can safely be ignored.
18898 : : */
18899 : 14 : pg_constraint = table_open(ConstraintRelationId, AccessShareLock);
18900 : :
18901 : : /*
18902 : : * Scan conrelid if changing to permanent, else confrelid. This also
18903 : : * determines whether a useful index exists.
18904 : : */
18905 : 28 : ScanKeyInit(&skey[0],
18906 : 14 : toLogged ? Anum_pg_constraint_conrelid :
18907 : : Anum_pg_constraint_confrelid,
18908 : : BTEqualStrategyNumber, F_OIDEQ,
18909 : 14 : ObjectIdGetDatum(RelationGetRelid(rel)));
18910 : 28 : scan = systable_beginscan(pg_constraint,
18911 : 14 : toLogged ? ConstraintRelidTypidNameIndexId : InvalidOid,
18912 : 14 : true, NULL, 1, skey);
18913 : :
18914 [ + + ]: 23 : while (HeapTupleIsValid(tuple = systable_getnext(scan)))
18915 : : {
18916 : 11 : Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
18917 : :
18918 [ + + ]: 11 : if (con->contype == CONSTRAINT_FOREIGN)
18919 : : {
18920 : 5 : Oid foreignrelid;
18921 : 5 : Relation foreignrel;
18922 : :
18923 : : /* the opposite end of what we used as scankey */
18924 [ + + ]: 5 : foreignrelid = toLogged ? con->confrelid : con->conrelid;
18925 : :
18926 : : /* ignore if self-referencing */
18927 [ + + ]: 5 : if (RelationGetRelid(rel) == foreignrelid)
18928 : 2 : continue;
18929 : :
18930 : 3 : foreignrel = relation_open(foreignrelid, AccessShareLock);
18931 : :
18932 [ + + ]: 3 : if (toLogged)
18933 : : {
18934 [ - + ]: 1 : if (!RelationIsPermanent(foreignrel))
18935 [ + - + - ]: 1 : ereport(ERROR,
18936 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
18937 : : errmsg("could not change table \"%s\" to logged because it references unlogged table \"%s\"",
18938 : : RelationGetRelationName(rel),
18939 : : RelationGetRelationName(foreignrel)),
18940 : : errtableconstraint(rel, NameStr(con->conname))));
18941 : 0 : }
18942 : : else
18943 : : {
18944 [ + + ]: 2 : if (RelationIsPermanent(foreignrel))
18945 [ + - + - ]: 1 : ereport(ERROR,
18946 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
18947 : : errmsg("could not change table \"%s\" to unlogged because it references logged table \"%s\"",
18948 : : RelationGetRelationName(rel),
18949 : : RelationGetRelationName(foreignrel)),
18950 : : errtableconstraint(rel, NameStr(con->conname))));
18951 : : }
18952 : :
18953 : 1 : relation_close(foreignrel, AccessShareLock);
18954 [ + + ]: 3 : }
18955 [ + + ]: 9 : }
18956 : :
18957 : 12 : systable_endscan(scan);
18958 : :
18959 : 12 : table_close(pg_constraint, AccessShareLock);
18960 : :
18961 : : /* force rewrite if necessary; see comment in ATRewriteTables */
18962 : 12 : tab->rewrite |= AT_REWRITE_ALTER_PERSISTENCE;
18963 [ + + ]: 12 : if (toLogged)
18964 : 5 : tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
18965 : : else
18966 : 7 : tab->newrelpersistence = RELPERSISTENCE_UNLOGGED;
18967 : 12 : tab->chgPersistence = true;
18968 : 14 : }
18969 : :
18970 : : /*
18971 : : * Execute ALTER TABLE SET SCHEMA
18972 : : */
18973 : : ObjectAddress
18974 : 16 : AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
18975 : : {
18976 : 16 : Relation rel;
18977 : 16 : Oid relid;
18978 : 16 : Oid oldNspOid;
18979 : 16 : Oid nspOid;
18980 : 16 : RangeVar *newrv;
18981 : 16 : ObjectAddresses *objsMoved;
18982 : 16 : ObjectAddress myself;
18983 : :
18984 : 32 : relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
18985 : 16 : stmt->missing_ok ? RVR_MISSING_OK : 0,
18986 : : RangeVarCallbackForAlterRelation,
18987 : 16 : stmt);
18988 : :
18989 [ + + ]: 16 : if (!OidIsValid(relid))
18990 : : {
18991 [ - + + - ]: 2 : ereport(NOTICE,
18992 : : (errmsg("relation \"%s\" does not exist, skipping",
18993 : : stmt->relation->relname)));
18994 : 2 : return InvalidObjectAddress;
18995 : : }
18996 : :
18997 : 14 : rel = relation_open(relid, NoLock);
18998 : :
18999 : 14 : oldNspOid = RelationGetNamespace(rel);
19000 : :
19001 : : /* If it's an owned sequence, disallow moving it by itself. */
19002 [ + + ]: 14 : if (rel->rd_rel->relkind == RELKIND_SEQUENCE)
19003 : : {
19004 : 1 : Oid tableId;
19005 : 1 : int32 colId;
19006 : :
19007 [ - + ]: 1 : if (sequenceIsOwned(relid, DEPENDENCY_AUTO, &tableId, &colId) ||
19008 : 0 : sequenceIsOwned(relid, DEPENDENCY_INTERNAL, &tableId, &colId))
19009 [ + - + - ]: 1 : ereport(ERROR,
19010 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
19011 : : errmsg("cannot move an owned sequence into another schema"),
19012 : : errdetail("Sequence \"%s\" is linked to table \"%s\".",
19013 : : RelationGetRelationName(rel),
19014 : : get_rel_name(tableId))));
19015 : 0 : }
19016 : :
19017 : : /* Get and lock schema OID and check its permissions. */
19018 : 13 : newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1);
19019 : 13 : nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL);
19020 : :
19021 : : /* common checks on switching namespaces */
19022 : 13 : CheckSetNamespace(oldNspOid, nspOid);
19023 : :
19024 : 13 : objsMoved = new_object_addresses();
19025 : 13 : AlterTableNamespaceInternal(rel, oldNspOid, nspOid, objsMoved);
19026 : 13 : free_object_addresses(objsMoved);
19027 : :
19028 : 13 : ObjectAddressSet(myself, RelationRelationId, relid);
19029 : :
19030 [ - + ]: 13 : if (oldschema)
19031 : 13 : *oldschema = oldNspOid;
19032 : :
19033 : : /* close rel, but keep lock until commit */
19034 : 13 : relation_close(rel, NoLock);
19035 : :
19036 : 13 : return myself;
19037 : 15 : }
19038 : :
19039 : : /*
19040 : : * The guts of relocating a table or materialized view to another namespace:
19041 : : * besides moving the relation itself, its dependent objects are relocated to
19042 : : * the new schema.
19043 : : */
19044 : : void
19045 : 13 : AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
19046 : : ObjectAddresses *objsMoved)
19047 : : {
19048 : 13 : Relation classRel;
19049 : :
19050 [ + - ]: 13 : Assert(objsMoved != NULL);
19051 : :
19052 : : /* OK, modify the pg_class row and pg_depend entry */
19053 : 13 : classRel = table_open(RelationRelationId, RowExclusiveLock);
19054 : :
19055 : 26 : AlterRelationNamespaceInternal(classRel, RelationGetRelid(rel), oldNspOid,
19056 : 13 : nspOid, true, objsMoved);
19057 : :
19058 : : /* Fix the table's row type too, if it has one */
19059 [ - + ]: 13 : if (OidIsValid(rel->rd_rel->reltype))
19060 : 26 : AlterTypeNamespaceInternal(rel->rd_rel->reltype, nspOid,
19061 : : false, /* isImplicitArray */
19062 : : false, /* ignoreDependent */
19063 : : false, /* errorOnTableType */
19064 : 13 : objsMoved);
19065 : :
19066 : : /* Fix other dependent stuff */
19067 : 13 : AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
19068 : 26 : AlterSeqNamespaces(classRel, rel, oldNspOid, nspOid,
19069 : 13 : objsMoved, AccessExclusiveLock);
19070 : 26 : AlterConstraintNamespaces(RelationGetRelid(rel), oldNspOid, nspOid,
19071 : 13 : false, objsMoved);
19072 : :
19073 : 13 : table_close(classRel, RowExclusiveLock);
19074 : 13 : }
19075 : :
19076 : : /*
19077 : : * The guts of relocating a relation to another namespace: fix the pg_class
19078 : : * entry, and the pg_depend entry if any. Caller must already have
19079 : : * opened and write-locked pg_class.
19080 : : */
19081 : : void
19082 : 29 : AlterRelationNamespaceInternal(Relation classRel, Oid relOid,
19083 : : Oid oldNspOid, Oid newNspOid,
19084 : : bool hasDependEntry,
19085 : : ObjectAddresses *objsMoved)
19086 : : {
19087 : 29 : HeapTuple classTup;
19088 : 29 : Form_pg_class classForm;
19089 : 29 : ObjectAddress thisobj;
19090 : 29 : bool already_done = false;
19091 : :
19092 : : /* no rel lock for relkind=c so use LOCKTAG_TUPLE */
19093 : 29 : classTup = SearchSysCacheLockedCopy1(RELOID, ObjectIdGetDatum(relOid));
19094 [ + - ]: 29 : if (!HeapTupleIsValid(classTup))
19095 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", relOid);
19096 : 29 : classForm = (Form_pg_class) GETSTRUCT(classTup);
19097 : :
19098 [ + - ]: 29 : Assert(classForm->relnamespace == oldNspOid);
19099 : :
19100 : 29 : thisobj.classId = RelationRelationId;
19101 : 29 : thisobj.objectId = relOid;
19102 : 29 : thisobj.objectSubId = 0;
19103 : :
19104 : : /*
19105 : : * If the object has already been moved, don't move it again. If it's
19106 : : * already in the right place, don't move it, but still fire the object
19107 : : * access hook.
19108 : : */
19109 : 29 : already_done = object_address_present(&thisobj, objsMoved);
19110 [ + - + + ]: 29 : if (!already_done && oldNspOid != newNspOid)
19111 : : {
19112 : 22 : ItemPointerData otid = classTup->t_self;
19113 : :
19114 : : /* check for duplicate name (more friendly than unique-index failure) */
19115 : 44 : if (get_relname_relid(NameStr(classForm->relname),
19116 [ + - + - ]: 44 : newNspOid) != InvalidOid)
19117 [ # # # # ]: 0 : ereport(ERROR,
19118 : : (errcode(ERRCODE_DUPLICATE_TABLE),
19119 : : errmsg("relation \"%s\" already exists in schema \"%s\"",
19120 : : NameStr(classForm->relname),
19121 : : get_namespace_name(newNspOid))));
19122 : :
19123 : : /* classTup is a copy, so OK to scribble on */
19124 : 22 : classForm->relnamespace = newNspOid;
19125 : :
19126 : 22 : CatalogTupleUpdate(classRel, &otid, classTup);
19127 : 22 : UnlockTuple(classRel, &otid, InplaceUpdateTupleLock);
19128 : :
19129 : :
19130 : : /* Update dependency on schema if caller said so */
19131 [ + + + - ]: 22 : if (hasDependEntry &&
19132 : 16 : changeDependencyFor(RelationRelationId,
19133 : 16 : relOid,
19134 : : NamespaceRelationId,
19135 : 16 : oldNspOid,
19136 : 32 : newNspOid) != 1)
19137 [ # # # # ]: 0 : elog(ERROR, "could not change schema dependency for relation \"%s\"",
19138 : : NameStr(classForm->relname));
19139 : 22 : }
19140 : : else
19141 : 7 : UnlockTuple(classRel, &classTup->t_self, InplaceUpdateTupleLock);
19142 [ - + ]: 29 : if (!already_done)
19143 : : {
19144 : 29 : add_exact_object_address(&thisobj, objsMoved);
19145 : :
19146 [ + - ]: 29 : InvokeObjectPostAlterHook(RelationRelationId, relOid, 0);
19147 : 29 : }
19148 : :
19149 : 29 : heap_freetuple(classTup);
19150 : 29 : }
19151 : :
19152 : : /*
19153 : : * Move all indexes for the specified relation to another namespace.
19154 : : *
19155 : : * Note: we assume adequate permission checking was done by the caller,
19156 : : * and that the caller has a suitable lock on the owning relation.
19157 : : */
19158 : : static void
19159 : 13 : AlterIndexNamespaces(Relation classRel, Relation rel,
19160 : : Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved)
19161 : : {
19162 : 13 : List *indexList;
19163 : 13 : ListCell *l;
19164 : :
19165 : 13 : indexList = RelationGetIndexList(rel);
19166 : :
19167 [ + + + + : 20 : foreach(l, indexList)
+ + ]
19168 : : {
19169 : 7 : Oid indexOid = lfirst_oid(l);
19170 : 7 : ObjectAddress thisobj;
19171 : :
19172 : 7 : thisobj.classId = RelationRelationId;
19173 : 7 : thisobj.objectId = indexOid;
19174 : 7 : thisobj.objectSubId = 0;
19175 : :
19176 : : /*
19177 : : * Note: currently, the index will not have its own dependency on the
19178 : : * namespace, so we don't need to do changeDependencyFor(). There's no
19179 : : * row type in pg_type, either.
19180 : : *
19181 : : * XXX this objsMoved test may be pointless -- surely we have a single
19182 : : * dependency link from a relation to each index?
19183 : : */
19184 [ - + ]: 7 : if (!object_address_present(&thisobj, objsMoved))
19185 : : {
19186 : 14 : AlterRelationNamespaceInternal(classRel, indexOid,
19187 : 7 : oldNspOid, newNspOid,
19188 : 7 : false, objsMoved);
19189 : 7 : add_exact_object_address(&thisobj, objsMoved);
19190 : 7 : }
19191 : 7 : }
19192 : :
19193 : 13 : list_free(indexList);
19194 : 13 : }
19195 : :
19196 : : /*
19197 : : * Move all identity and SERIAL-column sequences of the specified relation to another
19198 : : * namespace.
19199 : : *
19200 : : * Note: we assume adequate permission checking was done by the caller,
19201 : : * and that the caller has a suitable lock on the owning relation.
19202 : : */
19203 : : static void
19204 : 13 : AlterSeqNamespaces(Relation classRel, Relation rel,
19205 : : Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved,
19206 : : LOCKMODE lockmode)
19207 : : {
19208 : 13 : Relation depRel;
19209 : 13 : SysScanDesc scan;
19210 : 13 : ScanKeyData key[2];
19211 : 13 : HeapTuple tup;
19212 : :
19213 : : /*
19214 : : * SERIAL sequences are those having an auto dependency on one of the
19215 : : * table's columns (we don't care *which* column, exactly).
19216 : : */
19217 : 13 : depRel = table_open(DependRelationId, AccessShareLock);
19218 : :
19219 : 26 : ScanKeyInit(&key[0],
19220 : : Anum_pg_depend_refclassid,
19221 : : BTEqualStrategyNumber, F_OIDEQ,
19222 : 13 : ObjectIdGetDatum(RelationRelationId));
19223 : 26 : ScanKeyInit(&key[1],
19224 : : Anum_pg_depend_refobjid,
19225 : : BTEqualStrategyNumber, F_OIDEQ,
19226 : 13 : ObjectIdGetDatum(RelationGetRelid(rel)));
19227 : : /* we leave refobjsubid unspecified */
19228 : :
19229 : 26 : scan = systable_beginscan(depRel, DependReferenceIndexId, true,
19230 : 13 : NULL, 2, key);
19231 : :
19232 [ + + ]: 97 : while (HeapTupleIsValid(tup = systable_getnext(scan)))
19233 : : {
19234 : 84 : Form_pg_depend depForm = (Form_pg_depend) GETSTRUCT(tup);
19235 : 84 : Relation seqRel;
19236 : :
19237 : : /* skip dependencies other than auto dependencies on columns */
19238 [ + + ]: 84 : if (depForm->refobjsubid == 0 ||
19239 [ + + ]: 61 : depForm->classid != RelationRelationId ||
19240 [ + - # # ]: 7 : depForm->objsubid != 0 ||
19241 [ - + ]: 7 : !(depForm->deptype == DEPENDENCY_AUTO || depForm->deptype == DEPENDENCY_INTERNAL))
19242 : 77 : continue;
19243 : :
19244 : : /* Use relation_open just in case it's an index */
19245 : 7 : seqRel = relation_open(depForm->objid, lockmode);
19246 : :
19247 : : /* skip non-sequence relations */
19248 [ - + ]: 7 : if (RelationGetForm(seqRel)->relkind != RELKIND_SEQUENCE)
19249 : : {
19250 : : /* No need to keep the lock */
19251 : 0 : relation_close(seqRel, lockmode);
19252 : 0 : continue;
19253 : : }
19254 : :
19255 : : /* Fix the pg_class and pg_depend entries */
19256 : 14 : AlterRelationNamespaceInternal(classRel, depForm->objid,
19257 : 7 : oldNspOid, newNspOid,
19258 : 7 : true, objsMoved);
19259 : :
19260 : : /*
19261 : : * Sequences used to have entries in pg_type, but no longer do. If we
19262 : : * ever re-instate that, we'll need to move the pg_type entry to the
19263 : : * new namespace, too (using AlterTypeNamespaceInternal).
19264 : : */
19265 [ - + ]: 7 : Assert(RelationGetForm(seqRel)->reltype == InvalidOid);
19266 : :
19267 : : /* Now we can close it. Keep the lock till end of transaction. */
19268 : 7 : relation_close(seqRel, NoLock);
19269 [ - + + ]: 84 : }
19270 : :
19271 : 13 : systable_endscan(scan);
19272 : :
19273 : 13 : relation_close(depRel, AccessShareLock);
19274 : 13 : }
19275 : :
19276 : :
19277 : : /*
19278 : : * This code supports
19279 : : * CREATE TEMP TABLE ... ON COMMIT { DROP | PRESERVE ROWS | DELETE ROWS }
19280 : : *
19281 : : * Because we only support this for TEMP tables, it's sufficient to remember
19282 : : * the state in a backend-local data structure.
19283 : : */
19284 : :
19285 : : /*
19286 : : * Register a newly-created relation's ON COMMIT action.
19287 : : */
19288 : : void
19289 : 29 : register_on_commit_action(Oid relid, OnCommitAction action)
19290 : : {
19291 : 29 : OnCommitItem *oc;
19292 : 29 : MemoryContext oldcxt;
19293 : :
19294 : : /*
19295 : : * We needn't bother registering the relation unless there is an ON COMMIT
19296 : : * action we need to take.
19297 : : */
19298 [ + - + + ]: 29 : if (action == ONCOMMIT_NOOP || action == ONCOMMIT_PRESERVE_ROWS)
19299 : 4 : return;
19300 : :
19301 : 25 : oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
19302 : :
19303 : 25 : oc = palloc_object(OnCommitItem);
19304 : 25 : oc->relid = relid;
19305 : 25 : oc->oncommit = action;
19306 : 25 : oc->creating_subid = GetCurrentSubTransactionId();
19307 : 25 : oc->deleting_subid = InvalidSubTransactionId;
19308 : :
19309 : : /*
19310 : : * We use lcons() here so that ON COMMIT actions are processed in reverse
19311 : : * order of registration. That might not be essential but it seems
19312 : : * reasonable.
19313 : : */
19314 : 25 : on_commits = lcons(oc, on_commits);
19315 : :
19316 : 25 : MemoryContextSwitchTo(oldcxt);
19317 [ - + ]: 29 : }
19318 : :
19319 : : /*
19320 : : * Unregister any ON COMMIT action when a relation is deleted.
19321 : : *
19322 : : * Actually, we only mark the OnCommitItem entry as to be deleted after commit.
19323 : : */
19324 : : void
19325 : 5546 : remove_on_commit_action(Oid relid)
19326 : : {
19327 : 5546 : ListCell *l;
19328 : :
19329 [ + + + + : 5596 : foreach(l, on_commits)
+ + ]
19330 : : {
19331 : 50 : OnCommitItem *oc = (OnCommitItem *) lfirst(l);
19332 : :
19333 [ + + ]: 50 : if (oc->relid == relid)
19334 : : {
19335 : 22 : oc->deleting_subid = GetCurrentSubTransactionId();
19336 : 22 : break;
19337 : : }
19338 [ + + ]: 50 : }
19339 : 5546 : }
19340 : :
19341 : : /*
19342 : : * Perform ON COMMIT actions.
19343 : : *
19344 : : * This is invoked just before actually committing, since it's possible
19345 : : * to encounter errors.
19346 : : */
19347 : : void
19348 : 50929 : PreCommit_on_commit_actions(void)
19349 : : {
19350 : 50929 : ListCell *l;
19351 : 50929 : List *oids_to_truncate = NIL;
19352 : 50929 : List *oids_to_drop = NIL;
19353 : :
19354 [ + + + + : 51064 : foreach(l, on_commits)
+ + ]
19355 : : {
19356 : 135 : OnCommitItem *oc = (OnCommitItem *) lfirst(l);
19357 : :
19358 : : /* Ignore entry if already dropped in this xact */
19359 [ + + ]: 135 : if (oc->deleting_subid != InvalidSubTransactionId)
19360 : 12 : continue;
19361 : :
19362 [ - - + + ]: 123 : switch (oc->oncommit)
19363 : : {
19364 : : case ONCOMMIT_NOOP:
19365 : : case ONCOMMIT_PRESERVE_ROWS:
19366 : : /* Do nothing (there shouldn't be such entries, actually) */
19367 : 0 : break;
19368 : : case ONCOMMIT_DELETE_ROWS:
19369 : :
19370 : : /*
19371 : : * If this transaction hasn't accessed any temporary
19372 : : * relations, we can skip truncating ON COMMIT DELETE ROWS
19373 : : * tables, as they must still be empty.
19374 : : */
19375 [ + + ]: 115 : if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
19376 : 74 : oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
19377 : 115 : break;
19378 : : case ONCOMMIT_DROP:
19379 : 8 : oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
19380 : 8 : break;
19381 : : }
19382 [ + + ]: 135 : }
19383 : :
19384 : : /*
19385 : : * Truncate relations before dropping so that all dependencies between
19386 : : * relations are removed after they are worked on. Doing it like this
19387 : : * might be a waste as it is possible that a relation being truncated will
19388 : : * be dropped anyway due to its parent being dropped, but this makes the
19389 : : * code more robust because of not having to re-check that the relation
19390 : : * exists at truncation time.
19391 : : */
19392 [ + + ]: 50929 : if (oids_to_truncate != NIL)
19393 : 63 : heap_truncate(oids_to_truncate);
19394 : :
19395 [ + + ]: 50929 : if (oids_to_drop != NIL)
19396 : : {
19397 : 7 : ObjectAddresses *targetObjects = new_object_addresses();
19398 : :
19399 [ + - + + : 15 : foreach(l, oids_to_drop)
+ + ]
19400 : : {
19401 : 8 : ObjectAddress object;
19402 : :
19403 : 8 : object.classId = RelationRelationId;
19404 : 8 : object.objectId = lfirst_oid(l);
19405 : 8 : object.objectSubId = 0;
19406 : :
19407 [ + - ]: 8 : Assert(!object_address_present(&object, targetObjects));
19408 : :
19409 : 8 : add_exact_object_address(&object, targetObjects);
19410 : 8 : }
19411 : :
19412 : : /*
19413 : : * Object deletion might involve toast table access (to clean up
19414 : : * toasted catalog entries), so ensure we have a valid snapshot.
19415 : : */
19416 : 7 : PushActiveSnapshot(GetTransactionSnapshot());
19417 : :
19418 : : /*
19419 : : * Since this is an automatic drop, rather than one directly initiated
19420 : : * by the user, we pass the PERFORM_DELETION_INTERNAL flag.
19421 : : */
19422 : 7 : performMultipleDeletions(targetObjects, DROP_CASCADE,
19423 : : PERFORM_DELETION_INTERNAL | PERFORM_DELETION_QUIETLY);
19424 : :
19425 : 7 : PopActiveSnapshot();
19426 : :
19427 : : #ifdef USE_ASSERT_CHECKING
19428 : :
19429 : : /*
19430 : : * Note that table deletion will call remove_on_commit_action, so the
19431 : : * entry should get marked as deleted.
19432 : : */
19433 [ + - + + : 23 : foreach(l, on_commits)
+ + ]
19434 : : {
19435 : 16 : OnCommitItem *oc = (OnCommitItem *) lfirst(l);
19436 : :
19437 [ + + ]: 16 : if (oc->oncommit != ONCOMMIT_DROP)
19438 : 8 : continue;
19439 : :
19440 [ - + ]: 8 : Assert(oc->deleting_subid != InvalidSubTransactionId);
19441 [ + + ]: 16 : }
19442 : : #endif
19443 : 7 : }
19444 : 50929 : }
19445 : :
19446 : : /*
19447 : : * Post-commit or post-abort cleanup for ON COMMIT management.
19448 : : *
19449 : : * All we do here is remove no-longer-needed OnCommitItem entries.
19450 : : *
19451 : : * During commit, remove entries that were deleted during this transaction;
19452 : : * during abort, remove those created during this transaction.
19453 : : */
19454 : : void
19455 : 57917 : AtEOXact_on_commit_actions(bool isCommit)
19456 : : {
19457 : 57917 : ListCell *cur_item;
19458 : :
19459 [ + + + + : 58058 : foreach(cur_item, on_commits)
+ + ]
19460 : : {
19461 : 141 : OnCommitItem *oc = (OnCommitItem *) lfirst(cur_item);
19462 : :
19463 [ + + + + ]: 141 : if (isCommit ? oc->deleting_subid != InvalidSubTransactionId :
19464 : 18 : oc->creating_subid != InvalidSubTransactionId)
19465 : : {
19466 : : /* cur_item must be removed */
19467 : 25 : on_commits = foreach_delete_current(on_commits, cur_item);
19468 : 25 : pfree(oc);
19469 : 25 : }
19470 : : else
19471 : : {
19472 : : /* cur_item must be preserved */
19473 : 116 : oc->creating_subid = InvalidSubTransactionId;
19474 : 116 : oc->deleting_subid = InvalidSubTransactionId;
19475 : : }
19476 : 141 : }
19477 : 57917 : }
19478 : :
19479 : : /*
19480 : : * Post-subcommit or post-subabort cleanup for ON COMMIT management.
19481 : : *
19482 : : * During subabort, we can immediately remove entries created during this
19483 : : * subtransaction. During subcommit, just relabel entries marked during
19484 : : * this subtransaction as being the parent's responsibility.
19485 : : */
19486 : : void
19487 : 1665 : AtEOSubXact_on_commit_actions(bool isCommit, SubTransactionId mySubid,
19488 : : SubTransactionId parentSubid)
19489 : : {
19490 : 1665 : ListCell *cur_item;
19491 : :
19492 [ - + # # : 1665 : foreach(cur_item, on_commits)
+ - ]
19493 : : {
19494 : 0 : OnCommitItem *oc = (OnCommitItem *) lfirst(cur_item);
19495 : :
19496 [ # # # # ]: 0 : if (!isCommit && oc->creating_subid == mySubid)
19497 : : {
19498 : : /* cur_item must be removed */
19499 : 0 : on_commits = foreach_delete_current(on_commits, cur_item);
19500 : 0 : pfree(oc);
19501 : 0 : }
19502 : : else
19503 : : {
19504 : : /* cur_item must be preserved */
19505 [ # # ]: 0 : if (oc->creating_subid == mySubid)
19506 : 0 : oc->creating_subid = parentSubid;
19507 [ # # ]: 0 : if (oc->deleting_subid == mySubid)
19508 [ # # ]: 0 : oc->deleting_subid = isCommit ? parentSubid : InvalidSubTransactionId;
19509 : : }
19510 : 0 : }
19511 : 1665 : }
19512 : :
19513 : : /*
19514 : : * This is intended as a callback for RangeVarGetRelidExtended(). It allows
19515 : : * the relation to be locked only if (1) it's a plain or partitioned table,
19516 : : * materialized view, or TOAST table and (2) the current user is the owner (or
19517 : : * the superuser) or has been granted MAINTAIN. This meets the
19518 : : * permission-checking needs of CLUSTER, REINDEX TABLE, and REFRESH
19519 : : * MATERIALIZED VIEW; we expose it here so that it can be used by all.
19520 : : */
19521 : : void
19522 : 131 : RangeVarCallbackMaintainsTable(const RangeVar *relation,
19523 : : Oid relId, Oid oldRelId, void *arg)
19524 : : {
19525 : 131 : char relkind;
19526 : 131 : AclResult aclresult;
19527 : :
19528 : : /* Nothing to do if the relation was not found. */
19529 [ + - ]: 131 : if (!OidIsValid(relId))
19530 : 0 : return;
19531 : :
19532 : : /*
19533 : : * If the relation does exist, check whether it's an index. But note that
19534 : : * the relation might have been dropped between the time we did the name
19535 : : * lookup and now. In that case, there's nothing to do.
19536 : : */
19537 : 131 : relkind = get_rel_relkind(relId);
19538 [ + - ]: 131 : if (!relkind)
19539 : 0 : return;
19540 [ + + + + ]: 131 : if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
19541 [ + + + + ]: 63 : relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
19542 [ + - + - ]: 4 : ereport(ERROR,
19543 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19544 : : errmsg("\"%s\" is not a table or materialized view", relation->relname)));
19545 : :
19546 : : /* Check permissions */
19547 : 127 : aclresult = pg_class_aclcheck(relId, GetUserId(), ACL_MAINTAIN);
19548 [ + + ]: 127 : if (aclresult != ACLCHECK_OK)
19549 : 10 : aclcheck_error(aclresult,
19550 : 5 : get_relkind_objtype(get_rel_relkind(relId)),
19551 : 5 : relation->relname);
19552 [ - + ]: 127 : }
19553 : :
19554 : : /*
19555 : : * Callback to RangeVarGetRelidExtended() for TRUNCATE processing.
19556 : : */
19557 : : static void
19558 : 276 : RangeVarCallbackForTruncate(const RangeVar *relation,
19559 : : Oid relId, Oid oldRelId, void *arg)
19560 : : {
19561 : 276 : HeapTuple tuple;
19562 : :
19563 : : /* Nothing to do if the relation was not found. */
19564 [ + - ]: 276 : if (!OidIsValid(relId))
19565 : 0 : return;
19566 : :
19567 : 276 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relId));
19568 [ + - ]: 276 : if (!HeapTupleIsValid(tuple)) /* should not happen */
19569 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", relId);
19570 : :
19571 : 276 : truncate_check_rel(relId, (Form_pg_class) GETSTRUCT(tuple));
19572 : 276 : truncate_check_perms(relId, (Form_pg_class) GETSTRUCT(tuple));
19573 : :
19574 : 276 : ReleaseSysCache(tuple);
19575 [ - + ]: 276 : }
19576 : :
19577 : : /*
19578 : : * Callback for RangeVarGetRelidExtended(). Checks that the current user is
19579 : : * the owner of the relation, or superuser.
19580 : : */
19581 : : void
19582 : 1903 : RangeVarCallbackOwnsRelation(const RangeVar *relation,
19583 : : Oid relId, Oid oldRelId, void *arg)
19584 : : {
19585 : 1903 : HeapTuple tuple;
19586 : :
19587 : : /* Nothing to do if the relation was not found. */
19588 [ + + ]: 1903 : if (!OidIsValid(relId))
19589 : 3 : return;
19590 : :
19591 : 1900 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relId));
19592 [ + - ]: 1900 : if (!HeapTupleIsValid(tuple)) /* should not happen */
19593 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", relId);
19594 : :
19595 [ + + ]: 1900 : if (!object_ownercheck(RelationRelationId, relId, GetUserId()))
19596 : 8 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relId)),
19597 : 4 : relation->relname);
19598 : :
19599 [ + - + - ]: 1900 : if (!allowSystemTableMods &&
19600 : 1900 : IsSystemClass(relId, (Form_pg_class) GETSTRUCT(tuple)))
19601 [ # # # # ]: 0 : ereport(ERROR,
19602 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
19603 : : errmsg("permission denied: \"%s\" is a system catalog",
19604 : : relation->relname)));
19605 : :
19606 : 1900 : ReleaseSysCache(tuple);
19607 [ - + ]: 1903 : }
19608 : :
19609 : : /*
19610 : : * Common RangeVarGetRelid callback for rename, set schema, and alter table
19611 : : * processing.
19612 : : */
19613 : : static void
19614 : 3080 : RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
19615 : : void *arg)
19616 : : {
19617 : 3080 : Node *stmt = (Node *) arg;
19618 : 3080 : ObjectType reltype;
19619 : 3080 : HeapTuple tuple;
19620 : 3080 : Form_pg_class classform;
19621 : 3080 : AclResult aclresult;
19622 : 3080 : char relkind;
19623 : :
19624 : 3080 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
19625 [ + + ]: 3080 : if (!HeapTupleIsValid(tuple))
19626 : 37 : return; /* concurrently dropped */
19627 : 3043 : classform = (Form_pg_class) GETSTRUCT(tuple);
19628 : 3043 : relkind = classform->relkind;
19629 : :
19630 : : /* Must own relation. */
19631 [ + + ]: 3043 : if (!object_ownercheck(RelationRelationId, relid, GetUserId()))
19632 : 12 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
19633 : :
19634 : : /* No system table modifications unless explicitly allowed. */
19635 [ + + + + ]: 3043 : if (!allowSystemTableMods && IsSystemClass(relid, classform))
19636 [ + - + - ]: 3 : ereport(ERROR,
19637 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
19638 : : errmsg("permission denied: \"%s\" is a system catalog",
19639 : : rv->relname)));
19640 : :
19641 : : /*
19642 : : * Extract the specified relation type from the statement parse tree.
19643 : : *
19644 : : * Also, for ALTER .. RENAME, check permissions: the user must (still)
19645 : : * have CREATE rights on the containing namespace.
19646 : : */
19647 [ + + ]: 3040 : if (IsA(stmt, RenameStmt))
19648 : : {
19649 : 86 : aclresult = object_aclcheck(NamespaceRelationId, classform->relnamespace,
19650 : 43 : GetUserId(), ACL_CREATE);
19651 [ + - ]: 43 : if (aclresult != ACLCHECK_OK)
19652 : 0 : aclcheck_error(aclresult, OBJECT_SCHEMA,
19653 : 0 : get_namespace_name(classform->relnamespace));
19654 : 43 : reltype = ((RenameStmt *) stmt)->renameType;
19655 : 43 : }
19656 [ + + ]: 2997 : else if (IsA(stmt, AlterObjectSchemaStmt))
19657 : 15 : reltype = ((AlterObjectSchemaStmt *) stmt)->objectType;
19658 : :
19659 [ + - ]: 2982 : else if (IsA(stmt, AlterTableStmt))
19660 : 2982 : reltype = ((AlterTableStmt *) stmt)->objtype;
19661 : : else
19662 : : {
19663 [ # # # # ]: 0 : elog(ERROR, "unrecognized node type: %d", (int) nodeTag(stmt));
19664 : 0 : reltype = OBJECT_TABLE; /* placate compiler */
19665 : : }
19666 : :
19667 : : /*
19668 : : * For compatibility with prior releases, we allow ALTER TABLE to be used
19669 : : * with most other types of relations (but not composite types). We allow
19670 : : * similar flexibility for ALTER INDEX in the case of RENAME, but not
19671 : : * otherwise. Otherwise, the user must select the correct form of the
19672 : : * command for the relation at issue.
19673 : : */
19674 [ + + + - ]: 3040 : if (reltype == OBJECT_SEQUENCE && relkind != RELKIND_SEQUENCE)
19675 [ # # # # ]: 0 : ereport(ERROR,
19676 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19677 : : errmsg("\"%s\" is not a sequence", rv->relname)));
19678 : :
19679 [ + + + - ]: 3040 : if (reltype == OBJECT_VIEW && relkind != RELKIND_VIEW)
19680 [ # # # # ]: 0 : ereport(ERROR,
19681 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19682 : : errmsg("\"%s\" is not a view", rv->relname)));
19683 : :
19684 [ + + + - ]: 3040 : if (reltype == OBJECT_MATVIEW && relkind != RELKIND_MATVIEW)
19685 [ # # # # ]: 0 : ereport(ERROR,
19686 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19687 : : errmsg("\"%s\" is not a materialized view", rv->relname)));
19688 : :
19689 [ + + + - ]: 3040 : if (reltype == OBJECT_FOREIGN_TABLE && relkind != RELKIND_FOREIGN_TABLE)
19690 [ # # # # ]: 0 : ereport(ERROR,
19691 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19692 : : errmsg("\"%s\" is not a foreign table", rv->relname)));
19693 : :
19694 [ + + + - ]: 3040 : if (reltype == OBJECT_TYPE && relkind != RELKIND_COMPOSITE_TYPE)
19695 [ # # # # ]: 0 : ereport(ERROR,
19696 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19697 : : errmsg("\"%s\" is not a composite type", rv->relname)));
19698 : :
19699 [ + + + + ]: 3040 : if (reltype == OBJECT_INDEX && relkind != RELKIND_INDEX &&
19700 : 62 : relkind != RELKIND_PARTITIONED_INDEX
19701 [ + + + + ]: 62 : && !IsA(stmt, RenameStmt))
19702 [ + - + - ]: 1 : ereport(ERROR,
19703 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19704 : : errmsg("\"%s\" is not an index", rv->relname)));
19705 : :
19706 : : /*
19707 : : * Don't allow ALTER TABLE on composite types. We want people to use ALTER
19708 : : * TYPE for that.
19709 : : */
19710 [ + + + - ]: 3039 : if (reltype != OBJECT_TYPE && relkind == RELKIND_COMPOSITE_TYPE)
19711 [ # # # # ]: 0 : ereport(ERROR,
19712 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19713 : : errmsg("\"%s\" is a composite type", rv->relname),
19714 : : /* translator: %s is an SQL ALTER command */
19715 : : errhint("Use %s instead.",
19716 : : "ALTER TYPE")));
19717 : :
19718 : : /*
19719 : : * Don't allow ALTER TABLE .. SET SCHEMA on relations that can't be moved
19720 : : * to a different schema, such as indexes and TOAST tables.
19721 : : */
19722 [ + + ]: 3039 : if (IsA(stmt, AlterObjectSchemaStmt))
19723 : : {
19724 [ + - ]: 15 : if (relkind == RELKIND_INDEX || relkind == RELKIND_PARTITIONED_INDEX)
19725 [ # # # # ]: 0 : ereport(ERROR,
19726 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19727 : : errmsg("cannot change schema of index \"%s\"",
19728 : : rv->relname),
19729 : : errhint("Change the schema of the table instead.")));
19730 [ + - ]: 15 : else if (relkind == RELKIND_COMPOSITE_TYPE)
19731 [ # # # # ]: 0 : ereport(ERROR,
19732 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19733 : : errmsg("cannot change schema of composite type \"%s\"",
19734 : : rv->relname),
19735 : : /* translator: %s is an SQL ALTER command */
19736 : : errhint("Use %s instead.",
19737 : : "ALTER TYPE")));
19738 [ + - ]: 15 : else if (relkind == RELKIND_TOASTVALUE)
19739 [ # # # # ]: 0 : ereport(ERROR,
19740 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19741 : : errmsg("cannot change schema of TOAST table \"%s\"",
19742 : : rv->relname),
19743 : : errhint("Change the schema of the table instead.")));
19744 : 15 : }
19745 : :
19746 : 3039 : ReleaseSysCache(tuple);
19747 [ - + ]: 3076 : }
19748 : :
19749 : : /*
19750 : : * Transform any expressions present in the partition key
19751 : : *
19752 : : * Returns a transformed PartitionSpec.
19753 : : */
19754 : : static PartitionSpec *
19755 : 718 : transformPartitionSpec(Relation rel, PartitionSpec *partspec)
19756 : : {
19757 : 718 : PartitionSpec *newspec;
19758 : 718 : ParseState *pstate;
19759 : 718 : ParseNamespaceItem *nsitem;
19760 : 718 : ListCell *l;
19761 : :
19762 : 718 : newspec = makeNode(PartitionSpec);
19763 : :
19764 : 718 : newspec->strategy = partspec->strategy;
19765 : 718 : newspec->partParams = NIL;
19766 : 718 : newspec->location = partspec->location;
19767 : :
19768 : : /* Check valid number of columns for strategy */
19769 [ + + + + ]: 718 : if (partspec->strategy == PARTITION_STRATEGY_LIST &&
19770 : 285 : list_length(partspec->partParams) != 1)
19771 [ + - + - ]: 1 : ereport(ERROR,
19772 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
19773 : : errmsg("cannot use \"list\" partition strategy with more than one column")));
19774 : :
19775 : : /*
19776 : : * Create a dummy ParseState and insert the target relation as its sole
19777 : : * rangetable entry. We need a ParseState for transformExpr.
19778 : : */
19779 : 717 : pstate = make_parsestate(NULL);
19780 : 717 : nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock,
19781 : : NULL, false, true);
19782 : 717 : addNSItemToQuery(pstate, nsitem, true, true, true);
19783 : :
19784 : : /* take care of any partition expressions */
19785 [ + - + + : 1516 : foreach(l, partspec->partParams)
+ + ]
19786 : : {
19787 : 799 : PartitionElem *pelem = lfirst_node(PartitionElem, l);
19788 : :
19789 [ + + ]: 799 : if (pelem->expr)
19790 : : {
19791 : : /* Copy, to avoid scribbling on the input */
19792 : 56 : pelem = copyObject(pelem);
19793 : :
19794 : : /* Now do parse transformation of the expression */
19795 : 56 : pelem->expr = transformExpr(pstate, pelem->expr,
19796 : : EXPR_KIND_PARTITION_EXPRESSION);
19797 : :
19798 : : /* we have to fix its collations too */
19799 : 56 : assign_expr_collations(pstate, pelem->expr);
19800 : 56 : }
19801 : :
19802 : 799 : newspec->partParams = lappend(newspec->partParams, pelem);
19803 : 799 : }
19804 : :
19805 : 1434 : return newspec;
19806 : 717 : }
19807 : :
19808 : : /*
19809 : : * Compute per-partition-column information from a list of PartitionElems.
19810 : : * Expressions in the PartitionElems must be parse-analyzed already.
19811 : : */
19812 : : static void
19813 : 717 : ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNumber *partattrs,
19814 : : List **partexprs, Oid *partopclass, Oid *partcollation,
19815 : : PartitionStrategy strategy)
19816 : : {
19817 : 717 : int attn;
19818 : 717 : ListCell *lc;
19819 : 717 : Oid am_oid;
19820 : :
19821 : 717 : attn = 0;
19822 [ + + + + : 1492 : foreach(lc, partParams)
+ + ]
19823 : : {
19824 : 793 : PartitionElem *pelem = lfirst_node(PartitionElem, lc);
19825 : 793 : Oid atttype;
19826 : 793 : Oid attcollation;
19827 : :
19828 [ + + ]: 793 : if (pelem->name != NULL)
19829 : : {
19830 : : /* Simple attribute reference */
19831 : 743 : HeapTuple atttuple;
19832 : 743 : Form_pg_attribute attform;
19833 : :
19834 : 1486 : atttuple = SearchSysCacheAttName(RelationGetRelid(rel),
19835 : 743 : pelem->name);
19836 [ + + ]: 743 : if (!HeapTupleIsValid(atttuple))
19837 [ + - + - ]: 2 : ereport(ERROR,
19838 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
19839 : : errmsg("column \"%s\" named in partition key does not exist",
19840 : : pelem->name),
19841 : : parser_errposition(pstate, pelem->location)));
19842 : 741 : attform = (Form_pg_attribute) GETSTRUCT(atttuple);
19843 : :
19844 [ + + ]: 741 : if (attform->attnum <= 0)
19845 [ + - + - ]: 1 : ereport(ERROR,
19846 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
19847 : : errmsg("cannot use system column \"%s\" in partition key",
19848 : : pelem->name),
19849 : : parser_errposition(pstate, pelem->location)));
19850 : :
19851 : : /*
19852 : : * Stored generated columns cannot work: They are computed after
19853 : : * BEFORE triggers, but partition routing is done before all
19854 : : * triggers. Maybe virtual generated columns could be made to
19855 : : * work, but then they would need to be handled as an expression
19856 : : * below.
19857 : : */
19858 [ + + ]: 740 : if (attform->attgenerated)
19859 [ + - + - ]: 2 : ereport(ERROR,
19860 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
19861 : : errmsg("cannot use generated column in partition key"),
19862 : : errdetail("Column \"%s\" is a generated column.",
19863 : : pelem->name),
19864 : : parser_errposition(pstate, pelem->location)));
19865 : :
19866 : 738 : partattrs[attn] = attform->attnum;
19867 : 738 : atttype = attform->atttypid;
19868 : 738 : attcollation = attform->attcollation;
19869 : 738 : ReleaseSysCache(atttuple);
19870 : 738 : }
19871 : : else
19872 : : {
19873 : : /* Expression */
19874 : 50 : Node *expr = pelem->expr;
19875 : 50 : char partattname[16];
19876 : 50 : Bitmapset *expr_attrs = NULL;
19877 : 50 : int i;
19878 : :
19879 [ - + ]: 50 : Assert(expr != NULL);
19880 : 50 : atttype = exprType(expr);
19881 : 50 : attcollation = exprCollation(expr);
19882 : :
19883 : : /*
19884 : : * The expression must be of a storable type (e.g., not RECORD).
19885 : : * The test is the same as for whether a table column is of a safe
19886 : : * type (which is why we needn't check for the non-expression
19887 : : * case).
19888 : : */
19889 : 50 : snprintf(partattname, sizeof(partattname), "%d", attn + 1);
19890 : 100 : CheckAttributeType(partattname,
19891 : 50 : atttype, attcollation,
19892 : : NIL, CHKATYPE_IS_PARTKEY);
19893 : :
19894 : : /*
19895 : : * Strip any top-level COLLATE clause. This ensures that we treat
19896 : : * "x COLLATE y" and "(x COLLATE y)" alike.
19897 : : */
19898 [ - + ]: 50 : while (IsA(expr, CollateExpr))
19899 : 0 : expr = (Node *) ((CollateExpr *) expr)->arg;
19900 : :
19901 : : /*
19902 : : * Examine all the columns in the partition key expression. When
19903 : : * the whole-row reference is present, examine all the columns of
19904 : : * the partitioned table.
19905 : : */
19906 : 50 : pull_varattnos(expr, 1, &expr_attrs);
19907 [ + + ]: 50 : if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber, expr_attrs))
19908 : : {
19909 : 10 : expr_attrs = bms_add_range(expr_attrs,
19910 : : 1 - FirstLowInvalidHeapAttributeNumber,
19911 : 5 : RelationGetNumberOfAttributes(rel) - FirstLowInvalidHeapAttributeNumber);
19912 : 5 : expr_attrs = bms_del_member(expr_attrs, 0 - FirstLowInvalidHeapAttributeNumber);
19913 : 5 : }
19914 : :
19915 : 50 : i = -1;
19916 [ + + ]: 109 : while ((i = bms_next_member(expr_attrs, i)) >= 0)
19917 : : {
19918 : 67 : AttrNumber attno = i + FirstLowInvalidHeapAttributeNumber;
19919 : :
19920 [ - + ]: 67 : Assert(attno != 0);
19921 : :
19922 : : /*
19923 : : * Cannot allow system column references, since that would
19924 : : * make partition routing impossible: their values won't be
19925 : : * known yet when we need to do that.
19926 : : */
19927 [ + - ]: 67 : if (attno < 0)
19928 [ # # # # ]: 0 : ereport(ERROR,
19929 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
19930 : : errmsg("partition key expressions cannot contain system column references")));
19931 : :
19932 : : /*
19933 : : * Stored generated columns cannot work: They are computed
19934 : : * after BEFORE triggers, but partition routing is done before
19935 : : * all triggers. Virtual generated columns could probably
19936 : : * work, but it would require more work elsewhere (for example
19937 : : * SET EXPRESSION would need to check whether the column is
19938 : : * used in partition keys). Seems safer to prohibit for now.
19939 : : */
19940 [ + + ]: 67 : if (TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated)
19941 [ + - + - ]: 8 : ereport(ERROR,
19942 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
19943 : : errmsg("cannot use generated column in partition key"),
19944 : : errdetail("Column \"%s\" is a generated column.",
19945 : : get_attname(RelationGetRelid(rel), attno, false)),
19946 : : parser_errposition(pstate, pelem->location)));
19947 : 59 : }
19948 : :
19949 [ + + + + ]: 42 : if (IsA(expr, Var) &&
19950 : 2 : ((Var *) expr)->varattno > 0)
19951 : : {
19952 : :
19953 : : /*
19954 : : * User wrote "(column)" or "(column COLLATE something)".
19955 : : * Treat it like simple attribute anyway.
19956 : : */
19957 : 1 : partattrs[attn] = ((Var *) expr)->varattno;
19958 : 1 : }
19959 : : else
19960 : : {
19961 : 41 : partattrs[attn] = 0; /* marks the column as expression */
19962 : 41 : *partexprs = lappend(*partexprs, expr);
19963 : :
19964 : : /*
19965 : : * transformPartitionSpec() should have already rejected
19966 : : * subqueries, aggregates, window functions, and SRFs, based
19967 : : * on the EXPR_KIND_ for partition expressions.
19968 : : */
19969 : :
19970 : : /*
19971 : : * Preprocess the expression before checking for mutability.
19972 : : * This is essential for the reasons described in
19973 : : * contain_mutable_functions_after_planning. However, we call
19974 : : * expression_planner for ourselves rather than using that
19975 : : * function, because if constant-folding reduces the
19976 : : * expression to a constant, we'd like to know that so we can
19977 : : * complain below.
19978 : : *
19979 : : * Like contain_mutable_functions_after_planning, assume that
19980 : : * expression_planner won't scribble on its input, so this
19981 : : * won't affect the partexprs entry we saved above.
19982 : : */
19983 : 41 : expr = (Node *) expression_planner((Expr *) expr);
19984 : :
19985 : : /*
19986 : : * Partition expressions cannot contain mutable functions,
19987 : : * because a given row must always map to the same partition
19988 : : * as long as there is no change in the partition boundary
19989 : : * structure.
19990 : : */
19991 [ + + ]: 41 : if (contain_mutable_functions(expr))
19992 [ + - + - ]: 1 : ereport(ERROR,
19993 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
19994 : : errmsg("functions in partition key expression must be marked IMMUTABLE")));
19995 : :
19996 : : /*
19997 : : * While it is not exactly *wrong* for a partition expression
19998 : : * to be a constant, it seems better to reject such keys.
19999 : : */
20000 [ + + ]: 40 : if (IsA(expr, Const))
20001 [ + - + - ]: 2 : ereport(ERROR,
20002 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
20003 : : errmsg("cannot use constant expression as partition key")));
20004 : : }
20005 : 39 : }
20006 : :
20007 : : /*
20008 : : * Apply collation override if any
20009 : : */
20010 [ + + ]: 777 : if (pelem->collation)
20011 : 9 : attcollation = get_collation_oid(pelem->collation, false);
20012 : :
20013 : : /*
20014 : : * Check we have a collation iff it's a collatable type. The only
20015 : : * expected failures here are (1) COLLATE applied to a noncollatable
20016 : : * type, or (2) partition expression had an unresolved collation. But
20017 : : * we might as well code this to be a complete consistency check.
20018 : : */
20019 [ + + ]: 777 : if (type_is_collatable(atttype))
20020 : : {
20021 [ + - ]: 101 : if (!OidIsValid(attcollation))
20022 [ # # # # ]: 0 : ereport(ERROR,
20023 : : (errcode(ERRCODE_INDETERMINATE_COLLATION),
20024 : : errmsg("could not determine which collation to use for partition expression"),
20025 : : errhint("Use the COLLATE clause to set the collation explicitly.")));
20026 : 101 : }
20027 : : else
20028 : : {
20029 [ + - ]: 676 : if (OidIsValid(attcollation))
20030 [ # # # # ]: 0 : ereport(ERROR,
20031 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
20032 : : errmsg("collations are not supported by type %s",
20033 : : format_type_be(atttype))));
20034 : : }
20035 : :
20036 : 777 : partcollation[attn] = attcollation;
20037 : :
20038 : : /*
20039 : : * Identify the appropriate operator class. For list and range
20040 : : * partitioning, we use a btree operator class; hash partitioning uses
20041 : : * a hash operator class.
20042 : : */
20043 [ + + ]: 777 : if (strategy == PARTITION_STRATEGY_HASH)
20044 : 49 : am_oid = HASH_AM_OID;
20045 : : else
20046 : 728 : am_oid = BTREE_AM_OID;
20047 : :
20048 [ + + ]: 777 : if (!pelem->opclass)
20049 : : {
20050 : 754 : partopclass[attn] = GetDefaultOpClass(atttype, am_oid);
20051 : :
20052 [ + + ]: 754 : if (!OidIsValid(partopclass[attn]))
20053 : : {
20054 [ - + ]: 2 : if (strategy == PARTITION_STRATEGY_HASH)
20055 [ # # # # ]: 0 : ereport(ERROR,
20056 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
20057 : : errmsg("data type %s has no default operator class for access method \"%s\"",
20058 : : format_type_be(atttype), "hash"),
20059 : : errhint("You must specify a hash operator class or define a default hash operator class for the data type.")));
20060 : : else
20061 [ + - + - ]: 2 : ereport(ERROR,
20062 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
20063 : : errmsg("data type %s has no default operator class for access method \"%s\"",
20064 : : format_type_be(atttype), "btree"),
20065 : : errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
20066 : 0 : }
20067 : 752 : }
20068 : : else
20069 : 46 : partopclass[attn] = ResolveOpClass(pelem->opclass,
20070 : 23 : atttype,
20071 : 23 : am_oid == HASH_AM_OID ? "hash" : "btree",
20072 : 23 : am_oid);
20073 : :
20074 : 775 : attn++;
20075 : 775 : }
20076 : 699 : }
20077 : :
20078 : : /*
20079 : : * PartConstraintImpliedByRelConstraint
20080 : : * Do scanrel's existing constraints imply the partition constraint?
20081 : : *
20082 : : * "Existing constraints" include its check constraints and column-level
20083 : : * not-null constraints. partConstraint describes the partition constraint,
20084 : : * in implicit-AND form.
20085 : : */
20086 : : bool
20087 : 417 : PartConstraintImpliedByRelConstraint(Relation scanrel,
20088 : : List *partConstraint)
20089 : : {
20090 : 417 : List *existConstraint = NIL;
20091 : 417 : TupleConstr *constr = RelationGetDescr(scanrel)->constr;
20092 : 417 : int i;
20093 : :
20094 [ + + + + ]: 417 : if (constr && constr->has_not_null)
20095 : : {
20096 : 109 : int natts = scanrel->rd_att->natts;
20097 : :
20098 [ + + ]: 377 : for (i = 1; i <= natts; i++)
20099 : : {
20100 : 268 : CompactAttribute *att = TupleDescCompactAttr(scanrel->rd_att, i - 1);
20101 : :
20102 : : /* invalid not-null constraint must be ignored here */
20103 [ + + - + ]: 268 : if (att->attnullability == ATTNULLABLE_VALID && !att->attisdropped)
20104 : : {
20105 : 154 : Form_pg_attribute wholeatt = TupleDescAttr(scanrel->rd_att, i - 1);
20106 : 154 : NullTest *ntest = makeNode(NullTest);
20107 : :
20108 : 154 : ntest->arg = (Expr *) makeVar(1,
20109 : 154 : i,
20110 : 154 : wholeatt->atttypid,
20111 : 154 : wholeatt->atttypmod,
20112 : 154 : wholeatt->attcollation,
20113 : : 0);
20114 : 154 : ntest->nulltesttype = IS_NOT_NULL;
20115 : :
20116 : : /*
20117 : : * argisrow=false is correct even for a composite column,
20118 : : * because attnotnull does not represent a SQL-spec IS NOT
20119 : : * NULL test in such a case, just IS DISTINCT FROM NULL.
20120 : : */
20121 : 154 : ntest->argisrow = false;
20122 : 154 : ntest->location = -1;
20123 : 154 : existConstraint = lappend(existConstraint, ntest);
20124 : 154 : }
20125 : 268 : }
20126 : 109 : }
20127 : :
20128 : 834 : return ConstraintImpliedByRelConstraint(scanrel, partConstraint, existConstraint);
20129 : 417 : }
20130 : :
20131 : : /*
20132 : : * ConstraintImpliedByRelConstraint
20133 : : * Do scanrel's existing constraints imply the given constraint?
20134 : : *
20135 : : * testConstraint is the constraint to validate. provenConstraint is a
20136 : : * caller-provided list of conditions which this function may assume
20137 : : * to be true. Both provenConstraint and testConstraint must be in
20138 : : * implicit-AND form, must only contain immutable clauses, and must
20139 : : * contain only Vars with varno = 1.
20140 : : */
20141 : : bool
20142 : 606 : ConstraintImpliedByRelConstraint(Relation scanrel, List *testConstraint, List *provenConstraint)
20143 : : {
20144 : 606 : List *existConstraint = list_copy(provenConstraint);
20145 : 606 : TupleConstr *constr = RelationGetDescr(scanrel)->constr;
20146 : 606 : int num_check,
20147 : : i;
20148 : :
20149 [ + + ]: 606 : num_check = (constr != NULL) ? constr->num_check : 0;
20150 [ + + ]: 676 : for (i = 0; i < num_check; i++)
20151 : : {
20152 : 70 : Node *cexpr;
20153 : :
20154 : : /*
20155 : : * If this constraint hasn't been fully validated yet, we must ignore
20156 : : * it here.
20157 : : */
20158 [ + + ]: 70 : if (!constr->check[i].ccvalid)
20159 : 1 : continue;
20160 : :
20161 : : /*
20162 : : * NOT ENFORCED constraints are always marked as invalid, which should
20163 : : * have been ignored.
20164 : : */
20165 [ - + ]: 69 : Assert(constr->check[i].ccenforced);
20166 : :
20167 : 69 : cexpr = stringToNode(constr->check[i].ccbin);
20168 : :
20169 : : /*
20170 : : * Run each expression through const-simplification and
20171 : : * canonicalization. It is necessary, because we will be comparing it
20172 : : * to similarly-processed partition constraint expressions, and may
20173 : : * fail to detect valid matches without this.
20174 : : */
20175 : 69 : cexpr = eval_const_expressions(NULL, cexpr);
20176 : 69 : cexpr = (Node *) canonicalize_qual((Expr *) cexpr, true);
20177 : :
20178 : 138 : existConstraint = list_concat(existConstraint,
20179 : 69 : make_ands_implicit((Expr *) cexpr));
20180 [ - + + ]: 70 : }
20181 : :
20182 : : /*
20183 : : * Try to make the proof. Since we are comparing CHECK constraints, we
20184 : : * need to use weak implication, i.e., we assume existConstraint is
20185 : : * not-false and try to prove the same for testConstraint.
20186 : : *
20187 : : * Note that predicate_implied_by assumes its first argument is known
20188 : : * immutable. That should always be true for both NOT NULL and partition
20189 : : * constraints, so we don't test it here.
20190 : : */
20191 : 1212 : return predicate_implied_by(testConstraint, existConstraint, true);
20192 : 606 : }
20193 : :
20194 : : /*
20195 : : * QueuePartitionConstraintValidation
20196 : : *
20197 : : * Add an entry to wqueue to have the given partition constraint validated by
20198 : : * Phase 3, for the given relation, and all its children.
20199 : : *
20200 : : * We first verify whether the given constraint is implied by pre-existing
20201 : : * relation constraints; if it is, there's no need to scan the table to
20202 : : * validate, so don't queue in that case.
20203 : : */
20204 : : static void
20205 : 337 : QueuePartitionConstraintValidation(List **wqueue, Relation scanrel,
20206 : : List *partConstraint,
20207 : : bool validate_default)
20208 : : {
20209 : : /*
20210 : : * Based on the table's existing constraints, determine whether or not we
20211 : : * may skip scanning the table.
20212 : : */
20213 [ + + ]: 337 : if (PartConstraintImpliedByRelConstraint(scanrel, partConstraint))
20214 : : {
20215 [ + + ]: 11 : if (!validate_default)
20216 [ - + - + ]: 8 : ereport(DEBUG1,
20217 : : (errmsg_internal("partition constraint for table \"%s\" is implied by existing constraints",
20218 : : RelationGetRelationName(scanrel))));
20219 : : else
20220 [ - + - + ]: 3 : ereport(DEBUG1,
20221 : : (errmsg_internal("updated partition constraint for default partition \"%s\" is implied by existing constraints",
20222 : : RelationGetRelationName(scanrel))));
20223 : 11 : return;
20224 : : }
20225 : :
20226 : : /*
20227 : : * Constraints proved insufficient. For plain relations, queue a
20228 : : * validation item now; for partitioned tables, recurse to process each
20229 : : * partition.
20230 : : */
20231 [ + + ]: 326 : if (scanrel->rd_rel->relkind == RELKIND_RELATION)
20232 : : {
20233 : 270 : AlteredTableInfo *tab;
20234 : :
20235 : : /* Grab a work queue entry. */
20236 : 270 : tab = ATGetQueueEntry(wqueue, scanrel);
20237 [ + - ]: 270 : Assert(tab->partition_constraint == NULL);
20238 : 270 : tab->partition_constraint = (Expr *) linitial(partConstraint);
20239 : 270 : tab->validate_default = validate_default;
20240 : 270 : }
20241 [ + + ]: 56 : else if (scanrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
20242 : : {
20243 : 51 : PartitionDesc partdesc = RelationGetPartitionDesc(scanrel, true);
20244 : 51 : int i;
20245 : :
20246 [ + + ]: 118 : for (i = 0; i < partdesc->nparts; i++)
20247 : : {
20248 : 67 : Relation part_rel;
20249 : 67 : List *thisPartConstraint;
20250 : :
20251 : : /*
20252 : : * This is the minimum lock we need to prevent deadlocks.
20253 : : */
20254 : 67 : part_rel = table_open(partdesc->oids[i], AccessExclusiveLock);
20255 : :
20256 : : /*
20257 : : * Adjust the constraint for scanrel so that it matches this
20258 : : * partition's attribute numbers.
20259 : : */
20260 : 67 : thisPartConstraint =
20261 : 134 : map_partition_varattnos(partConstraint, 1,
20262 : 67 : part_rel, scanrel);
20263 : :
20264 : 134 : QueuePartitionConstraintValidation(wqueue, part_rel,
20265 : 67 : thisPartConstraint,
20266 : 67 : validate_default);
20267 : 67 : table_close(part_rel, NoLock); /* keep lock till commit */
20268 : 67 : }
20269 : 51 : }
20270 : 337 : }
20271 : :
20272 : : /*
20273 : : * attachPartitionTable: attach a new partition to the partitioned table
20274 : : *
20275 : : * wqueue: the ALTER TABLE work queue; can be NULL when not running as part
20276 : : * of an ALTER TABLE sequence.
20277 : : * rel: partitioned relation;
20278 : : * attachrel: relation of attached partition;
20279 : : * bound: bounds of attached relation.
20280 : : */
20281 : : static void
20282 : 377 : attachPartitionTable(List **wqueue, Relation rel, Relation attachrel, PartitionBoundSpec *bound)
20283 : : {
20284 : : /*
20285 : : * Create an inheritance; the relevant checks are performed inside the
20286 : : * function.
20287 : : */
20288 : 377 : CreateInheritance(attachrel, rel, true);
20289 : :
20290 : : /* Update the pg_class entry. */
20291 : 377 : StorePartitionBound(attachrel, rel, bound);
20292 : :
20293 : : /* Ensure there exists a correct set of indexes in the partition. */
20294 : 377 : AttachPartitionEnsureIndexes(wqueue, rel, attachrel);
20295 : :
20296 : : /* and triggers */
20297 : 377 : CloneRowTriggersToPartition(rel, attachrel);
20298 : :
20299 : : /*
20300 : : * Clone foreign key constraints. Callee is responsible for setting up
20301 : : * for phase 3 constraint verification.
20302 : : */
20303 : 377 : CloneForeignKeyConstraints(wqueue, rel, attachrel);
20304 : 377 : }
20305 : :
20306 : : /*
20307 : : * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
20308 : : *
20309 : : * Return the address of the newly attached partition.
20310 : : */
20311 : : static ObjectAddress
20312 : 324 : ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
20313 : : AlterTableUtilityContext *context)
20314 : : {
20315 : 324 : Relation attachrel,
20316 : : catalog;
20317 : 324 : List *attachrel_children;
20318 : 324 : List *partConstraint;
20319 : 324 : SysScanDesc scan;
20320 : 324 : ScanKeyData skey;
20321 : 324 : AttrNumber attno;
20322 : 324 : int natts;
20323 : 324 : TupleDesc tupleDesc;
20324 : : ObjectAddress address;
20325 : 324 : const char *trigger_name;
20326 : 324 : Oid defaultPartOid;
20327 : 324 : List *partBoundConstraint;
20328 : 324 : ParseState *pstate = make_parsestate(NULL);
20329 : :
20330 : 324 : pstate->p_sourcetext = context->queryString;
20331 : :
20332 : : /*
20333 : : * We must lock the default partition if one exists, because attaching a
20334 : : * new partition will change its partition constraint.
20335 : : */
20336 : 324 : defaultPartOid =
20337 : 324 : get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
20338 [ + + ]: 324 : if (OidIsValid(defaultPartOid))
20339 : 26 : LockRelationOid(defaultPartOid, AccessExclusiveLock);
20340 : :
20341 : 324 : attachrel = table_openrv(cmd->name, AccessExclusiveLock);
20342 : :
20343 : : /*
20344 : : * XXX I think it'd be a good idea to grab locks on all tables referenced
20345 : : * by FKs at this point also.
20346 : : */
20347 : :
20348 : : /*
20349 : : * Must be owner of both parent and source table -- parent was checked by
20350 : : * ATSimplePermissions call in ATPrepCmd
20351 : : */
20352 : 324 : ATSimplePermissions(AT_AttachPartition, attachrel,
20353 : : ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
20354 : :
20355 : : /* A partition can only have one parent */
20356 [ + + ]: 324 : if (attachrel->rd_rel->relispartition)
20357 [ + - + - ]: 1 : ereport(ERROR,
20358 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20359 : : errmsg("\"%s\" is already a partition",
20360 : : RelationGetRelationName(attachrel))));
20361 : :
20362 [ + + ]: 323 : if (OidIsValid(attachrel->rd_rel->reloftype))
20363 [ + - + - ]: 1 : ereport(ERROR,
20364 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20365 : : errmsg("cannot attach a typed table as partition")));
20366 : :
20367 : : /*
20368 : : * Table being attached should not already be part of inheritance; either
20369 : : * as a child table...
20370 : : */
20371 : 322 : catalog = table_open(InheritsRelationId, AccessShareLock);
20372 : 322 : ScanKeyInit(&skey,
20373 : : Anum_pg_inherits_inhrelid,
20374 : : BTEqualStrategyNumber, F_OIDEQ,
20375 : 322 : ObjectIdGetDatum(RelationGetRelid(attachrel)));
20376 : 322 : scan = systable_beginscan(catalog, InheritsRelidSeqnoIndexId, true,
20377 : : NULL, 1, &skey);
20378 [ + + ]: 322 : if (HeapTupleIsValid(systable_getnext(scan)))
20379 [ + - + - ]: 1 : ereport(ERROR,
20380 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20381 : : errmsg("cannot attach inheritance child as partition")));
20382 : 321 : systable_endscan(scan);
20383 : :
20384 : : /* ...or as a parent table (except the case when it is partitioned) */
20385 : 321 : ScanKeyInit(&skey,
20386 : : Anum_pg_inherits_inhparent,
20387 : : BTEqualStrategyNumber, F_OIDEQ,
20388 : 321 : ObjectIdGetDatum(RelationGetRelid(attachrel)));
20389 : 321 : scan = systable_beginscan(catalog, InheritsParentIndexId, true, NULL,
20390 : : 1, &skey);
20391 [ + + + + ]: 321 : if (HeapTupleIsValid(systable_getnext(scan)) &&
20392 : 44 : attachrel->rd_rel->relkind == RELKIND_RELATION)
20393 [ + - + - ]: 1 : ereport(ERROR,
20394 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20395 : : errmsg("cannot attach inheritance parent as partition")));
20396 : 320 : systable_endscan(scan);
20397 : 320 : table_close(catalog, AccessShareLock);
20398 : :
20399 : : /*
20400 : : * Prevent circularity by seeing if rel is a partition of attachrel. (In
20401 : : * particular, this disallows making a rel a partition of itself.)
20402 : : *
20403 : : * We do that by checking if rel is a member of the list of attachrel's
20404 : : * partitions provided the latter is partitioned at all. We want to avoid
20405 : : * having to construct this list again, so we request the strongest lock
20406 : : * on all partitions. We need the strongest lock, because we may decide
20407 : : * to scan them if we find out that the table being attached (or its leaf
20408 : : * partitions) may contain rows that violate the partition constraint. If
20409 : : * the table has a constraint that would prevent such rows, which by
20410 : : * definition is present in all the partitions, we need not scan the
20411 : : * table, nor its partitions. But we cannot risk a deadlock by taking a
20412 : : * weaker lock now and the stronger one only when needed.
20413 : : */
20414 : 320 : attachrel_children = find_all_inheritors(RelationGetRelid(attachrel),
20415 : : AccessExclusiveLock, NULL);
20416 [ + + ]: 320 : if (list_member_oid(attachrel_children, RelationGetRelid(rel)))
20417 [ + - + - ]: 2 : ereport(ERROR,
20418 : : (errcode(ERRCODE_DUPLICATE_TABLE),
20419 : : errmsg("circular inheritance not allowed"),
20420 : : errdetail("\"%s\" is already a child of \"%s\".",
20421 : : RelationGetRelationName(rel),
20422 : : RelationGetRelationName(attachrel))));
20423 : :
20424 : : /* If the parent is permanent, so must be all of its partitions. */
20425 [ + + + + ]: 318 : if (rel->rd_rel->relpersistence != RELPERSISTENCE_TEMP &&
20426 : 292 : attachrel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
20427 [ + - + - ]: 1 : ereport(ERROR,
20428 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20429 : : errmsg("cannot attach a temporary relation as partition of permanent relation \"%s\"",
20430 : : RelationGetRelationName(rel))));
20431 : :
20432 : : /* Temp parent cannot have a partition that is itself not a temp */
20433 [ + + + + ]: 317 : if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
20434 : 7 : attachrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
20435 [ + - + - ]: 3 : ereport(ERROR,
20436 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20437 : : errmsg("cannot attach a permanent relation as partition of temporary relation \"%s\"",
20438 : : RelationGetRelationName(rel))));
20439 : :
20440 : : /* If the parent is temp, it must belong to this session */
20441 [ + + + - ]: 262 : if (RELATION_IS_OTHER_TEMP(rel))
20442 [ # # # # ]: 0 : ereport(ERROR,
20443 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20444 : : errmsg("cannot attach as partition of temporary relation of another session")));
20445 : :
20446 : : /* Ditto for the partition */
20447 [ + + + - ]: 262 : if (RELATION_IS_OTHER_TEMP(attachrel))
20448 [ # # # # ]: 0 : ereport(ERROR,
20449 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20450 : : errmsg("cannot attach temporary relation of another session as partition")));
20451 : :
20452 : : /*
20453 : : * Check if attachrel has any identity columns or any columns that aren't
20454 : : * in the parent.
20455 : : */
20456 : 262 : tupleDesc = RelationGetDescr(attachrel);
20457 : 262 : natts = tupleDesc->natts;
20458 [ + + ]: 983 : for (attno = 1; attno <= natts; attno++)
20459 : : {
20460 : 728 : Form_pg_attribute attribute = TupleDescAttr(tupleDesc, attno - 1);
20461 : 728 : char *attributeName = NameStr(attribute->attname);
20462 : :
20463 : : /* Ignore dropped */
20464 [ + + ]: 728 : if (attribute->attisdropped)
20465 : 80 : continue;
20466 : :
20467 [ + + ]: 648 : if (attribute->attidentity)
20468 [ + - + - ]: 4 : ereport(ERROR,
20469 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
20470 : : errmsg("table \"%s\" being attached contains an identity column \"%s\"",
20471 : : RelationGetRelationName(attachrel), attributeName),
20472 : : errdetail("The new partition may not contain an identity column."));
20473 : :
20474 : : /* Try to find the column in parent (matching on column name) */
20475 [ + + ]: 644 : if (!SearchSysCacheExists2(ATTNAME,
20476 : : ObjectIdGetDatum(RelationGetRelid(rel)),
20477 : : CStringGetDatum(attributeName)))
20478 [ + - + - ]: 3 : ereport(ERROR,
20479 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
20480 : : errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
20481 : : RelationGetRelationName(attachrel), attributeName,
20482 : : RelationGetRelationName(rel)),
20483 : : errdetail("The new partition may contain only the columns present in parent.")));
20484 [ - + + ]: 721 : }
20485 : :
20486 : : /*
20487 : : * If child_rel has row-level triggers with transition tables, we
20488 : : * currently don't allow it to become a partition. See also prohibitions
20489 : : * in ATExecAddInherit() and CreateTrigger().
20490 : : */
20491 : 255 : trigger_name = FindTriggerIncompatibleWithInheritance(attachrel->trigdesc);
20492 [ + + ]: 255 : if (trigger_name != NULL)
20493 [ + - + - ]: 1 : ereport(ERROR,
20494 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
20495 : : errmsg("trigger \"%s\" prevents table \"%s\" from becoming a partition",
20496 : : trigger_name, RelationGetRelationName(attachrel)),
20497 : : errdetail("ROW triggers with transition tables are not supported on partitions.")));
20498 : :
20499 : : /*
20500 : : * Check that the new partition's bound is valid and does not overlap any
20501 : : * of existing partitions of the parent - note that it does not return on
20502 : : * error.
20503 : : */
20504 : 508 : check_new_partition_bound(RelationGetRelationName(attachrel), rel,
20505 : 254 : cmd->bound, pstate);
20506 : :
20507 : 254 : attachPartitionTable(wqueue, rel, attachrel, cmd->bound);
20508 : :
20509 : : /*
20510 : : * Generate a partition constraint from the partition bound specification.
20511 : : * If the parent itself is a partition, make sure to include its
20512 : : * constraint as well.
20513 : : */
20514 : 254 : partBoundConstraint = get_qual_from_partbound(rel, cmd->bound);
20515 : :
20516 : : /*
20517 : : * Use list_concat_copy() to avoid modifying partBoundConstraint in place,
20518 : : * since it's needed later to construct the constraint expression for
20519 : : * validating against the default partition, if any.
20520 : : */
20521 : 508 : partConstraint = list_concat_copy(partBoundConstraint,
20522 : 254 : RelationGetPartitionQual(rel));
20523 : :
20524 : : /* Skip validation if there are no constraints to validate. */
20525 [ + + ]: 254 : if (partConstraint)
20526 : : {
20527 : : /*
20528 : : * Run the partition quals through const-simplification similar to
20529 : : * check constraints. We skip canonicalize_qual, though, because
20530 : : * partition quals should be in canonical form already.
20531 : : */
20532 : 250 : partConstraint =
20533 : 250 : (List *) eval_const_expressions(NULL,
20534 : 250 : (Node *) partConstraint);
20535 : :
20536 : : /* XXX this sure looks wrong */
20537 : 250 : partConstraint = list_make1(make_ands_explicit(partConstraint));
20538 : :
20539 : : /*
20540 : : * Adjust the generated constraint to match this partition's attribute
20541 : : * numbers.
20542 : : */
20543 : 500 : partConstraint = map_partition_varattnos(partConstraint, 1, attachrel,
20544 : 250 : rel);
20545 : :
20546 : : /* Validate partition constraints against the table being attached. */
20547 : 250 : QueuePartitionConstraintValidation(wqueue, attachrel, partConstraint,
20548 : : false);
20549 : 250 : }
20550 : :
20551 : : /*
20552 : : * If we're attaching a partition other than the default partition and a
20553 : : * default one exists, then that partition's partition constraint changes,
20554 : : * so add an entry to the work queue to validate it, too. (We must not do
20555 : : * this when the partition being attached is the default one; we already
20556 : : * did it above!)
20557 : : */
20558 [ + + ]: 254 : if (OidIsValid(defaultPartOid))
20559 : : {
20560 : 20 : Relation defaultrel;
20561 : 20 : List *defPartConstraint;
20562 : :
20563 [ + - ]: 20 : Assert(!cmd->bound->is_default);
20564 : :
20565 : : /* we already hold a lock on the default partition */
20566 : 20 : defaultrel = table_open(defaultPartOid, NoLock);
20567 : 20 : defPartConstraint =
20568 : 20 : get_proposed_default_constraint(partBoundConstraint);
20569 : :
20570 : : /*
20571 : : * Map the Vars in the constraint expression from rel's attnos to
20572 : : * defaultrel's.
20573 : : */
20574 : 20 : defPartConstraint =
20575 : 40 : map_partition_varattnos(defPartConstraint,
20576 : 20 : 1, defaultrel, rel);
20577 : 40 : QueuePartitionConstraintValidation(wqueue, defaultrel,
20578 : 20 : defPartConstraint, true);
20579 : :
20580 : : /* keep our lock until commit. */
20581 : 20 : table_close(defaultrel, NoLock);
20582 : 20 : }
20583 : :
20584 : 254 : ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel));
20585 : :
20586 : : /*
20587 : : * If the partition we just attached is partitioned itself, invalidate
20588 : : * relcache for all descendent partitions too to ensure that their
20589 : : * rd_partcheck expression trees are rebuilt; partitions already locked at
20590 : : * the beginning of this function.
20591 : : */
20592 [ + + ]: 254 : if (attachrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
20593 : : {
20594 : 48 : ListCell *l;
20595 : :
20596 [ + - + + : 157 : foreach(l, attachrel_children)
+ + ]
20597 : : {
20598 : 109 : CacheInvalidateRelcacheByRelid(lfirst_oid(l));
20599 : 109 : }
20600 : 48 : }
20601 : :
20602 : : /* keep our lock until commit */
20603 : 254 : table_close(attachrel, NoLock);
20604 : :
20605 : : return address;
20606 : 254 : }
20607 : :
20608 : : /*
20609 : : * AttachPartitionEnsureIndexes
20610 : : * subroutine for ATExecAttachPartition to create/match indexes
20611 : : *
20612 : : * Enforce the indexing rule for partitioned tables during ALTER TABLE / ATTACH
20613 : : * PARTITION: every partition must have an index attached to each index on the
20614 : : * partitioned table.
20615 : : */
20616 : : static void
20617 : 356 : AttachPartitionEnsureIndexes(List **wqueue, Relation rel, Relation attachrel)
20618 : : {
20619 : 356 : List *idxes;
20620 : 356 : List *attachRelIdxs;
20621 : 356 : Relation *attachrelIdxRels;
20622 : 356 : IndexInfo **attachInfos;
20623 : 356 : ListCell *cell;
20624 : 356 : MemoryContext cxt;
20625 : 356 : MemoryContext oldcxt;
20626 : :
20627 : 356 : cxt = AllocSetContextCreate(CurrentMemoryContext,
20628 : : "AttachPartitionEnsureIndexes",
20629 : : ALLOCSET_DEFAULT_SIZES);
20630 : 356 : oldcxt = MemoryContextSwitchTo(cxt);
20631 : :
20632 : 356 : idxes = RelationGetIndexList(rel);
20633 : 356 : attachRelIdxs = RelationGetIndexList(attachrel);
20634 : 356 : attachrelIdxRels = palloc_array(Relation, list_length(attachRelIdxs));
20635 : 356 : attachInfos = palloc_array(IndexInfo *, list_length(attachRelIdxs));
20636 : :
20637 : : /* Build arrays of all existing indexes and their IndexInfos */
20638 [ + + + + : 781 : foreach_oid(cldIdxId, attachRelIdxs)
+ + + + ]
20639 : : {
20640 : 66 : int i = foreach_current_index(cldIdxId);
20641 : :
20642 : 66 : attachrelIdxRels[i] = index_open(cldIdxId, AccessShareLock);
20643 : 66 : attachInfos[i] = BuildIndexInfo(attachrelIdxRels[i]);
20644 : 425 : }
20645 : :
20646 : : /*
20647 : : * If we're attaching a foreign table, we must fail if any of the indexes
20648 : : * is a constraint index; otherwise, there's nothing to do here. Do this
20649 : : * before starting work, to avoid wasting the effort of building a few
20650 : : * non-unique indexes before coming across a unique one.
20651 : : */
20652 [ + + ]: 356 : if (attachrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
20653 : : {
20654 [ + + + + : 11 : foreach(cell, idxes)
+ + ]
20655 : : {
20656 : 6 : Oid idx = lfirst_oid(cell);
20657 : 6 : Relation idxRel = index_open(idx, AccessShareLock);
20658 : :
20659 [ + + ]: 6 : if (idxRel->rd_index->indisunique ||
20660 : 4 : idxRel->rd_index->indisprimary)
20661 [ + - + - ]: 2 : ereport(ERROR,
20662 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
20663 : : errmsg("cannot attach foreign table \"%s\" as partition of partitioned table \"%s\"",
20664 : : RelationGetRelationName(attachrel),
20665 : : RelationGetRelationName(rel)),
20666 : : errdetail("Partitioned table \"%s\" contains unique indexes.",
20667 : : RelationGetRelationName(rel))));
20668 : 4 : index_close(idxRel, AccessShareLock);
20669 : 4 : }
20670 : :
20671 : 5 : goto out;
20672 : : }
20673 : :
20674 : : /*
20675 : : * For each index on the partitioned table, find a matching one in the
20676 : : * partition-to-be; if one is not found, create one.
20677 : : */
20678 [ + + + + : 460 : foreach(cell, idxes)
+ + ]
20679 : : {
20680 : 111 : Oid idx = lfirst_oid(cell);
20681 : 111 : Relation idxRel = index_open(idx, AccessShareLock);
20682 : 111 : IndexInfo *info;
20683 : 111 : AttrMap *attmap;
20684 : 111 : bool found = false;
20685 : 111 : Oid constraintOid;
20686 : :
20687 : : /*
20688 : : * Ignore indexes in the partitioned table other than partitioned
20689 : : * indexes.
20690 : : */
20691 [ - + ]: 111 : if (idxRel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
20692 : : {
20693 : 0 : index_close(idxRel, AccessShareLock);
20694 : 0 : continue;
20695 : : }
20696 : :
20697 : : /* construct an indexinfo to compare existing indexes against */
20698 : 111 : info = BuildIndexInfo(idxRel);
20699 : 222 : attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
20700 : 111 : RelationGetDescr(rel),
20701 : : false);
20702 : 111 : constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
20703 : :
20704 : : /*
20705 : : * Scan the list of existing indexes in the partition-to-be, and mark
20706 : : * the first matching, valid, unattached one we find, if any, as
20707 : : * partition of the parent index. If we find one, we're done.
20708 : : */
20709 [ + + ]: 159 : for (int i = 0; i < list_length(attachRelIdxs); i++)
20710 : : {
20711 : 48 : Oid cldIdxId = RelationGetRelid(attachrelIdxRels[i]);
20712 : 48 : Oid cldConstrOid = InvalidOid;
20713 : :
20714 : : /* does this index have a parent? if so, can't use it */
20715 [ + + ]: 48 : if (attachrelIdxRels[i]->rd_rel->relispartition)
20716 : 2 : continue;
20717 : :
20718 : : /* If this index is invalid, can't use it */
20719 [ + + ]: 46 : if (!attachrelIdxRels[i]->rd_index->indisvalid)
20720 : 1 : continue;
20721 : :
20722 [ + + + + ]: 90 : if (CompareIndexInfo(attachInfos[i], info,
20723 : 45 : attachrelIdxRels[i]->rd_indcollation,
20724 : 45 : idxRel->rd_indcollation,
20725 : 45 : attachrelIdxRels[i]->rd_opfamily,
20726 : 45 : idxRel->rd_opfamily,
20727 : 45 : attmap))
20728 : : {
20729 : : /*
20730 : : * If this index is being created in the parent because of a
20731 : : * constraint, then the child needs to have a constraint also,
20732 : : * so look for one. If there is no such constraint, this
20733 : : * index is no good, so keep looking.
20734 : : */
20735 [ + + ]: 39 : if (OidIsValid(constraintOid))
20736 : : {
20737 : 23 : cldConstrOid =
20738 : 46 : get_relation_idx_constraint_oid(RelationGetRelid(attachrel),
20739 : 23 : cldIdxId);
20740 : : /* no dice */
20741 [ + + ]: 23 : if (!OidIsValid(cldConstrOid))
20742 : 1 : continue;
20743 : :
20744 : : /* Ensure they're both the same type of constraint */
20745 [ - + - + ]: 44 : if (get_constraint_type(constraintOid) !=
20746 : 22 : get_constraint_type(cldConstrOid))
20747 : 0 : continue;
20748 : 22 : }
20749 : :
20750 : : /* bingo. */
20751 : 38 : IndexSetParentIndex(attachrelIdxRels[i], idx);
20752 [ + + ]: 38 : if (OidIsValid(constraintOid))
20753 : 44 : ConstraintSetParentConstraint(cldConstrOid, constraintOid,
20754 : 22 : RelationGetRelid(attachrel));
20755 : 38 : found = true;
20756 : :
20757 : 38 : CommandCounterIncrement();
20758 : 38 : break;
20759 : : }
20760 [ + + + ]: 48 : }
20761 : :
20762 : : /*
20763 : : * If no suitable index was found in the partition-to-be, create one
20764 : : * now. Note that if this is a PK, not-null constraints must already
20765 : : * exist.
20766 : : */
20767 [ + + ]: 111 : if (!found)
20768 : : {
20769 : 76 : IndexStmt *stmt;
20770 : 76 : Oid conOid;
20771 : :
20772 : 76 : stmt = generateClonedIndexStmt(NULL,
20773 : 76 : idxRel, attmap,
20774 : : &conOid);
20775 : 76 : DefineIndex(NULL,
20776 : 76 : RelationGetRelid(attachrel), stmt, InvalidOid,
20777 : 76 : RelationGetRelid(idxRel),
20778 : 76 : conOid,
20779 : : -1,
20780 : : true, false, false, false, false);
20781 : 76 : }
20782 : :
20783 : 111 : index_close(idxRel, AccessShareLock);
20784 [ - - + ]: 460 : }
20785 : :
20786 : : out:
20787 : : /* Clean up. */
20788 [ + + ]: 418 : for (int i = 0; i < list_length(attachRelIdxs); i++)
20789 : 64 : index_close(attachrelIdxRels[i], AccessShareLock);
20790 : 354 : MemoryContextSwitchTo(oldcxt);
20791 : 354 : MemoryContextDelete(cxt);
20792 : 354 : }
20793 : :
20794 : : /*
20795 : : * CloneRowTriggersToPartition
20796 : : * subroutine for ATExecAttachPartition/DefineRelation to create row
20797 : : * triggers on partitions
20798 : : */
20799 : : static void
20800 : 425 : CloneRowTriggersToPartition(Relation parent, Relation partition)
20801 : : {
20802 : 425 : Relation pg_trigger;
20803 : 425 : ScanKeyData key;
20804 : 425 : SysScanDesc scan;
20805 : 425 : HeapTuple tuple;
20806 : 425 : MemoryContext perTupCxt;
20807 : :
20808 : 425 : ScanKeyInit(&key, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber,
20809 : 425 : F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(parent)));
20810 : 425 : pg_trigger = table_open(TriggerRelationId, RowExclusiveLock);
20811 : 425 : scan = systable_beginscan(pg_trigger, TriggerRelidNameIndexId,
20812 : : true, NULL, 1, &key);
20813 : :
20814 : 425 : perTupCxt = AllocSetContextCreate(CurrentMemoryContext,
20815 : : "clone trig", ALLOCSET_SMALL_SIZES);
20816 : :
20817 [ + + ]: 747 : while (HeapTupleIsValid(tuple = systable_getnext(scan)))
20818 : : {
20819 : 322 : Form_pg_trigger trigForm = (Form_pg_trigger) GETSTRUCT(tuple);
20820 : 322 : CreateTrigStmt *trigStmt;
20821 : 322 : Node *qual = NULL;
20822 : 322 : Datum value;
20823 : 322 : bool isnull;
20824 : 322 : List *cols = NIL;
20825 : 322 : List *trigargs = NIL;
20826 : 322 : MemoryContext oldcxt;
20827 : :
20828 : : /*
20829 : : * Ignore statement-level triggers; those are not cloned.
20830 : : */
20831 [ + + ]: 322 : if (!TRIGGER_FOR_ROW(trigForm->tgtype))
20832 : 7 : continue;
20833 : :
20834 : : /*
20835 : : * Don't clone internal triggers, because the constraint cloning code
20836 : : * will.
20837 : : */
20838 [ + + ]: 315 : if (trigForm->tgisinternal)
20839 : 283 : continue;
20840 : :
20841 : : /*
20842 : : * Complain if we find an unexpected trigger type.
20843 : : */
20844 [ + + + - ]: 32 : if (!TRIGGER_FOR_BEFORE(trigForm->tgtype) &&
20845 : 26 : !TRIGGER_FOR_AFTER(trigForm->tgtype))
20846 [ # # # # ]: 0 : elog(ERROR, "unexpected trigger \"%s\" found",
20847 : : NameStr(trigForm->tgname));
20848 : :
20849 : : /* Use short-lived context for CREATE TRIGGER */
20850 : 32 : oldcxt = MemoryContextSwitchTo(perTupCxt);
20851 : :
20852 : : /*
20853 : : * If there is a WHEN clause, generate a 'cooked' version of it that's
20854 : : * appropriate for the partition.
20855 : : */
20856 : 64 : value = heap_getattr(tuple, Anum_pg_trigger_tgqual,
20857 : 32 : RelationGetDescr(pg_trigger), &isnull);
20858 [ + + ]: 32 : if (!isnull)
20859 : : {
20860 : 1 : qual = stringToNode(TextDatumGetCString(value));
20861 : 2 : qual = (Node *) map_partition_varattnos((List *) qual, PRS2_OLD_VARNO,
20862 : 1 : partition, parent);
20863 : 2 : qual = (Node *) map_partition_varattnos((List *) qual, PRS2_NEW_VARNO,
20864 : 1 : partition, parent);
20865 : 1 : }
20866 : :
20867 : : /*
20868 : : * If there is a column list, transform it to a list of column names.
20869 : : * Note we don't need to map this list in any way ...
20870 : : */
20871 [ + + ]: 32 : if (trigForm->tgattr.dim1 > 0)
20872 : : {
20873 : 1 : int i;
20874 : :
20875 [ + + ]: 2 : for (i = 0; i < trigForm->tgattr.dim1; i++)
20876 : : {
20877 : 1 : Form_pg_attribute col;
20878 : :
20879 : 2 : col = TupleDescAttr(parent->rd_att,
20880 : 1 : trigForm->tgattr.values[i] - 1);
20881 : 2 : cols = lappend(cols,
20882 : 1 : makeString(pstrdup(NameStr(col->attname))));
20883 : 1 : }
20884 : 1 : }
20885 : :
20886 : : /* Reconstruct trigger arguments list. */
20887 [ + + ]: 32 : if (trigForm->tgnargs > 0)
20888 : : {
20889 : 9 : char *p;
20890 : :
20891 : 18 : value = heap_getattr(tuple, Anum_pg_trigger_tgargs,
20892 : 9 : RelationGetDescr(pg_trigger), &isnull);
20893 [ + - ]: 9 : if (isnull)
20894 [ # # # # ]: 0 : elog(ERROR, "tgargs is null for trigger \"%s\" in partition \"%s\"",
20895 : : NameStr(trigForm->tgname), RelationGetRelationName(partition));
20896 : :
20897 : 9 : p = (char *) VARDATA_ANY(DatumGetByteaPP(value));
20898 : :
20899 [ + + ]: 20 : for (int i = 0; i < trigForm->tgnargs; i++)
20900 : : {
20901 : 11 : trigargs = lappend(trigargs, makeString(pstrdup(p)));
20902 : 11 : p += strlen(p) + 1;
20903 : 11 : }
20904 : 9 : }
20905 : :
20906 : 32 : trigStmt = makeNode(CreateTrigStmt);
20907 : 32 : trigStmt->replace = false;
20908 : 32 : trigStmt->isconstraint = OidIsValid(trigForm->tgconstraint);
20909 : 32 : trigStmt->trigname = NameStr(trigForm->tgname);
20910 : 32 : trigStmt->relation = NULL;
20911 : 32 : trigStmt->funcname = NULL; /* passed separately */
20912 : 32 : trigStmt->args = trigargs;
20913 : 32 : trigStmt->row = true;
20914 : 32 : trigStmt->timing = trigForm->tgtype & TRIGGER_TYPE_TIMING_MASK;
20915 : 32 : trigStmt->events = trigForm->tgtype & TRIGGER_TYPE_EVENT_MASK;
20916 : 32 : trigStmt->columns = cols;
20917 : 32 : trigStmt->whenClause = NULL; /* passed separately */
20918 : 32 : trigStmt->transitionRels = NIL; /* not supported at present */
20919 : 32 : trigStmt->deferrable = trigForm->tgdeferrable;
20920 : 32 : trigStmt->initdeferred = trigForm->tginitdeferred;
20921 : 32 : trigStmt->constrrel = NULL; /* passed separately */
20922 : :
20923 : 64 : CreateTriggerFiringOn(trigStmt, NULL, RelationGetRelid(partition),
20924 : 32 : trigForm->tgconstrrelid, InvalidOid, InvalidOid,
20925 : 32 : trigForm->tgfoid, trigForm->oid, qual,
20926 : 32 : false, true, trigForm->tgenabled);
20927 : :
20928 : 32 : MemoryContextSwitchTo(oldcxt);
20929 : 32 : MemoryContextReset(perTupCxt);
20930 [ - + + ]: 322 : }
20931 : :
20932 : 425 : MemoryContextDelete(perTupCxt);
20933 : :
20934 : 425 : systable_endscan(scan);
20935 : 425 : table_close(pg_trigger, RowExclusiveLock);
20936 : 425 : }
20937 : :
20938 : : /*
20939 : : * ALTER TABLE DETACH PARTITION
20940 : : *
20941 : : * Return the address of the relation that is no longer a partition of rel.
20942 : : *
20943 : : * If concurrent mode is requested, we run in two transactions. A side-
20944 : : * effect is that this command cannot run in a multi-part ALTER TABLE.
20945 : : * Currently, that's enforced by the grammar.
20946 : : *
20947 : : * The strategy for concurrency is to first modify the partition's
20948 : : * pg_inherit catalog row to make it visible to everyone that the
20949 : : * partition is detached, lock the partition against writes, and commit
20950 : : * the transaction; anyone who requests the partition descriptor from
20951 : : * that point onwards has to ignore such a partition. In a second
20952 : : * transaction, we wait until all transactions that could have seen the
20953 : : * partition as attached are gone, then we remove the rest of partition
20954 : : * metadata (pg_inherits and pg_class.relpartbounds).
20955 : : */
20956 : : static ObjectAddress
20957 : 71 : ATExecDetachPartition(List **wqueue, AlteredTableInfo *tab, Relation rel,
20958 : : RangeVar *name, bool concurrent)
20959 : : {
20960 : 71 : Relation partRel;
20961 : : ObjectAddress address;
20962 : 71 : Oid defaultPartOid;
20963 : :
20964 : : /*
20965 : : * We must lock the default partition, because detaching this partition
20966 : : * will change its partition constraint.
20967 : : */
20968 : 71 : defaultPartOid =
20969 : 71 : get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
20970 [ + + ]: 71 : if (OidIsValid(defaultPartOid))
20971 : : {
20972 : : /*
20973 : : * Concurrent detaching when a default partition exists is not
20974 : : * supported. The main problem is that the default partition
20975 : : * constraint would change. And there's a definitional problem: what
20976 : : * should happen to the tuples that are being inserted that belong to
20977 : : * the partition being detached? Putting them on the partition being
20978 : : * detached would be wrong, since they'd become "lost" after the
20979 : : * detaching completes but we cannot put them in the default partition
20980 : : * either until we alter its partition constraint.
20981 : : *
20982 : : * I think we could solve this problem if we effected the constraint
20983 : : * change before committing the first transaction. But the lock would
20984 : : * have to remain AEL and it would cause concurrent query planning to
20985 : : * be blocked, so changing it that way would be even worse.
20986 : : */
20987 [ + + ]: 18 : if (concurrent)
20988 [ + - + - ]: 2 : ereport(ERROR,
20989 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
20990 : : errmsg("cannot detach partitions concurrently when a default partition exists")));
20991 : 16 : LockRelationOid(defaultPartOid, AccessExclusiveLock);
20992 : 16 : }
20993 : :
20994 : : /*
20995 : : * In concurrent mode, the partition is locked with share-update-exclusive
20996 : : * in the first transaction. This allows concurrent transactions to be
20997 : : * doing DML to the partition.
20998 : : */
20999 : 69 : partRel = table_openrv(name, concurrent ? ShareUpdateExclusiveLock :
21000 : : AccessExclusiveLock);
21001 : :
21002 : : /*
21003 : : * Check inheritance conditions and either delete the pg_inherits row (in
21004 : : * non-concurrent mode) or just set the inhdetachpending flag.
21005 : : */
21006 [ + + ]: 69 : if (!concurrent)
21007 : 67 : RemoveInheritance(partRel, rel, false);
21008 : : else
21009 : 2 : MarkInheritDetached(partRel, rel);
21010 : :
21011 : : /*
21012 : : * Ensure that foreign keys still hold after this detach. This keeps
21013 : : * locks on the referencing tables, which prevents concurrent transactions
21014 : : * from adding rows that we wouldn't see. For this to work in concurrent
21015 : : * mode, it is critical that the partition appears as no longer attached
21016 : : * for the RI queries as soon as the first transaction commits.
21017 : : */
21018 : 69 : ATDetachCheckNoForeignKeyRefs(partRel);
21019 : :
21020 : : /*
21021 : : * Concurrent mode has to work harder; first we add a new constraint to
21022 : : * the partition that matches the partition constraint. Then we close our
21023 : : * existing transaction, and in a new one wait for all processes to catch
21024 : : * up on the catalog updates we've done so far; at that point we can
21025 : : * complete the operation.
21026 : : */
21027 [ + + ]: 69 : if (concurrent)
21028 : : {
21029 : 2 : Oid partrelid,
21030 : : parentrelid;
21031 : 2 : LOCKTAG tag;
21032 : 2 : char *parentrelname;
21033 : 2 : char *partrelname;
21034 : :
21035 : : /*
21036 : : * We're almost done now; the only traces that remain are the
21037 : : * pg_inherits tuple and the partition's relpartbounds. Before we can
21038 : : * remove those, we need to wait until all transactions that know that
21039 : : * this is a partition are gone.
21040 : : */
21041 : :
21042 : : /*
21043 : : * Remember relation OIDs to re-acquire them later; and relation names
21044 : : * too, for error messages if something is dropped in between.
21045 : : */
21046 : 2 : partrelid = RelationGetRelid(partRel);
21047 : 2 : parentrelid = RelationGetRelid(rel);
21048 : 4 : parentrelname = MemoryContextStrdup(PortalContext,
21049 : 2 : RelationGetRelationName(rel));
21050 : 4 : partrelname = MemoryContextStrdup(PortalContext,
21051 : 2 : RelationGetRelationName(partRel));
21052 : :
21053 : : /* Invalidate relcache entries for the parent -- must be before close */
21054 : 2 : CacheInvalidateRelcache(rel);
21055 : :
21056 : 2 : table_close(partRel, NoLock);
21057 : 2 : table_close(rel, NoLock);
21058 : 2 : tab->rel = NULL;
21059 : :
21060 : : /* Make updated catalog entry visible */
21061 : 2 : PopActiveSnapshot();
21062 : 2 : CommitTransactionCommand();
21063 : :
21064 : 2 : StartTransactionCommand();
21065 : :
21066 : : /*
21067 : : * Now wait. This ensures that all queries that were planned
21068 : : * including the partition are finished before we remove the rest of
21069 : : * catalog entries. We don't need or indeed want to acquire this
21070 : : * lock, though -- that would block later queries.
21071 : : *
21072 : : * We don't need to concern ourselves with waiting for a lock on the
21073 : : * partition itself, since we will acquire AccessExclusiveLock below.
21074 : : */
21075 : 2 : SET_LOCKTAG_RELATION(tag, MyDatabaseId, parentrelid);
21076 : 2 : WaitForLockersMultiple(list_make1(&tag), AccessExclusiveLock, false);
21077 : :
21078 : : /*
21079 : : * Now acquire locks in both relations again. Note they may have been
21080 : : * removed in the meantime, so care is required.
21081 : : */
21082 : 2 : rel = try_relation_open(parentrelid, ShareUpdateExclusiveLock);
21083 : 2 : partRel = try_relation_open(partrelid, AccessExclusiveLock);
21084 : :
21085 : : /* If the relations aren't there, something bad happened; bail out */
21086 [ + - ]: 2 : if (rel == NULL)
21087 : : {
21088 [ # # ]: 0 : if (partRel != NULL) /* shouldn't happen */
21089 [ # # # # ]: 0 : elog(WARNING, "dangling partition \"%s\" remains, can't fix",
21090 : : partrelname);
21091 [ # # # # ]: 0 : ereport(ERROR,
21092 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
21093 : : errmsg("partitioned table \"%s\" was removed concurrently",
21094 : : parentrelname)));
21095 : 0 : }
21096 [ + - ]: 2 : if (partRel == NULL)
21097 [ # # # # ]: 0 : ereport(ERROR,
21098 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
21099 : : errmsg("partition \"%s\" was removed concurrently", partrelname)));
21100 : :
21101 : 2 : tab->rel = rel;
21102 : 2 : }
21103 : :
21104 : : /*
21105 : : * Detaching the partition might involve TOAST table access, so ensure we
21106 : : * have a valid snapshot.
21107 : : */
21108 : 69 : PushActiveSnapshot(GetTransactionSnapshot());
21109 : :
21110 : : /* Do the final part of detaching */
21111 : 69 : DetachPartitionFinalize(rel, partRel, concurrent, defaultPartOid);
21112 : :
21113 : 69 : PopActiveSnapshot();
21114 : :
21115 : 69 : ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
21116 : :
21117 : : /* keep our lock until commit */
21118 : 69 : table_close(partRel, NoLock);
21119 : :
21120 : : return address;
21121 : 69 : }
21122 : :
21123 : : /*
21124 : : * Second part of ALTER TABLE .. DETACH.
21125 : : *
21126 : : * This is separate so that it can be run independently when the second
21127 : : * transaction of the concurrent algorithm fails (crash or abort).
21128 : : */
21129 : : static void
21130 : 144 : DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
21131 : : Oid defaultPartOid)
21132 : : {
21133 : 144 : Relation classRel;
21134 : 144 : List *fks;
21135 : 144 : ListCell *cell;
21136 : 144 : List *indexes;
21137 : 144 : Datum new_val[Natts_pg_class];
21138 : 144 : bool new_null[Natts_pg_class],
21139 : : new_repl[Natts_pg_class];
21140 : 144 : HeapTuple tuple,
21141 : : newtuple;
21142 : 144 : Relation trigrel = NULL;
21143 : 144 : List *fkoids = NIL;
21144 : :
21145 [ + + ]: 144 : if (concurrent)
21146 : : {
21147 : : /*
21148 : : * We can remove the pg_inherits row now. (In the non-concurrent case,
21149 : : * this was already done).
21150 : : */
21151 : 2 : RemoveInheritance(partRel, rel, true);
21152 : 2 : }
21153 : :
21154 : : /* Drop any triggers that were cloned on creation/attach. */
21155 : 144 : DropClonedTriggersFromPartition(RelationGetRelid(partRel));
21156 : :
21157 : : /*
21158 : : * Detach any foreign keys that are inherited. This includes creating
21159 : : * additional action triggers.
21160 : : */
21161 : 144 : fks = copyObject(RelationGetFKeyList(partRel));
21162 [ + + ]: 144 : if (fks != NIL)
21163 : 15 : trigrel = table_open(TriggerRelationId, RowExclusiveLock);
21164 : :
21165 : : /*
21166 : : * It's possible that the partition being detached has a foreign key that
21167 : : * references a partitioned table. When that happens, there are multiple
21168 : : * pg_constraint rows for the partition: one points to the partitioned
21169 : : * table itself, while the others point to each of its partitions. Only
21170 : : * the topmost one is to be considered here; the child constraints must be
21171 : : * left alone, because conceptually those aren't coming from our parent
21172 : : * partitioned table, but from this partition itself.
21173 : : *
21174 : : * We implement this by collecting all the constraint OIDs in a first scan
21175 : : * of the FK array, and skipping in the loop below those constraints whose
21176 : : * parents are listed here.
21177 : : */
21178 [ + + + + : 317 : foreach_node(ForeignKeyCacheInfo, fk, fks)
+ + + + ]
21179 : 173 : fkoids = lappend_oid(fkoids, fk->conoid);
21180 : :
21181 [ + + + + : 173 : foreach(cell, fks)
+ + ]
21182 : : {
21183 : 29 : ForeignKeyCacheInfo *fk = lfirst(cell);
21184 : 29 : HeapTuple contup;
21185 : 29 : Form_pg_constraint conform;
21186 : :
21187 : 29 : contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
21188 [ + - ]: 29 : if (!HeapTupleIsValid(contup))
21189 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
21190 : 29 : conform = (Form_pg_constraint) GETSTRUCT(contup);
21191 : :
21192 : : /*
21193 : : * Consider only inherited foreign keys, and only if their parents
21194 : : * aren't in the list.
21195 : : */
21196 [ + - ]: 29 : if (conform->contype != CONSTRAINT_FOREIGN ||
21197 [ + + + + ]: 29 : !OidIsValid(conform->conparentid) ||
21198 : 25 : list_member_oid(fkoids, conform->conparentid))
21199 : : {
21200 : 11 : ReleaseSysCache(contup);
21201 : 11 : continue;
21202 : : }
21203 : :
21204 : : /*
21205 : : * The constraint on this table must be marked no longer a child of
21206 : : * the parent's constraint, as do its check triggers.
21207 : : */
21208 : 18 : ConstraintSetParentConstraint(fk->conoid, InvalidOid, InvalidOid);
21209 : :
21210 : : /*
21211 : : * Also, look up the partition's "check" triggers corresponding to the
21212 : : * ENFORCED constraint being detached and detach them from the parent
21213 : : * triggers. NOT ENFORCED constraints do not have these triggers;
21214 : : * therefore, this step is not needed.
21215 : : */
21216 [ - + ]: 18 : if (fk->conenforced)
21217 : : {
21218 : 18 : Oid insertTriggerOid,
21219 : : updateTriggerOid;
21220 : :
21221 : 36 : GetForeignKeyCheckTriggers(trigrel,
21222 : 18 : fk->conoid, fk->confrelid, fk->conrelid,
21223 : : &insertTriggerOid, &updateTriggerOid);
21224 [ + - ]: 18 : Assert(OidIsValid(insertTriggerOid));
21225 : 36 : TriggerSetParentTrigger(trigrel, insertTriggerOid, InvalidOid,
21226 : 18 : RelationGetRelid(partRel));
21227 [ + - ]: 18 : Assert(OidIsValid(updateTriggerOid));
21228 : 36 : TriggerSetParentTrigger(trigrel, updateTriggerOid, InvalidOid,
21229 : 18 : RelationGetRelid(partRel));
21230 : 18 : }
21231 : :
21232 : : /*
21233 : : * Lastly, create the action triggers on the referenced table, using
21234 : : * addFkRecurseReferenced, which requires some elaborate setup (so put
21235 : : * it in a separate block). While at it, if the table is partitioned,
21236 : : * that function will recurse to create the pg_constraint rows and
21237 : : * action triggers for each partition.
21238 : : *
21239 : : * Note there's no need to do addFkConstraint() here, because the
21240 : : * pg_constraint row already exists.
21241 : : */
21242 : : {
21243 : 18 : Constraint *fkconstraint;
21244 : 18 : int numfks;
21245 : 18 : AttrNumber conkey[INDEX_MAX_KEYS];
21246 : 18 : AttrNumber confkey[INDEX_MAX_KEYS];
21247 : 18 : Oid conpfeqop[INDEX_MAX_KEYS];
21248 : 18 : Oid conppeqop[INDEX_MAX_KEYS];
21249 : 18 : Oid conffeqop[INDEX_MAX_KEYS];
21250 : 18 : int numfkdelsetcols;
21251 : 18 : AttrNumber confdelsetcols[INDEX_MAX_KEYS];
21252 : 18 : Relation refdRel;
21253 : :
21254 : 36 : DeconstructFkConstraintRow(contup,
21255 : : &numfks,
21256 : 18 : conkey,
21257 : 18 : confkey,
21258 : 18 : conpfeqop,
21259 : 18 : conppeqop,
21260 : 18 : conffeqop,
21261 : : &numfkdelsetcols,
21262 : 18 : confdelsetcols);
21263 : :
21264 : : /* Create a synthetic node we'll use throughout */
21265 : 18 : fkconstraint = makeNode(Constraint);
21266 : 18 : fkconstraint->contype = CONSTRAINT_FOREIGN;
21267 : 18 : fkconstraint->conname = pstrdup(NameStr(conform->conname));
21268 : 18 : fkconstraint->deferrable = conform->condeferrable;
21269 : 18 : fkconstraint->initdeferred = conform->condeferred;
21270 : 18 : fkconstraint->is_enforced = conform->conenforced;
21271 : 18 : fkconstraint->skip_validation = true;
21272 : 18 : fkconstraint->initially_valid = conform->convalidated;
21273 : : /* a few irrelevant fields omitted here */
21274 : 18 : fkconstraint->pktable = NULL;
21275 : 18 : fkconstraint->fk_attrs = NIL;
21276 : 18 : fkconstraint->pk_attrs = NIL;
21277 : 18 : fkconstraint->fk_matchtype = conform->confmatchtype;
21278 : 18 : fkconstraint->fk_upd_action = conform->confupdtype;
21279 : 18 : fkconstraint->fk_del_action = conform->confdeltype;
21280 : 18 : fkconstraint->fk_del_set_cols = NIL;
21281 : 18 : fkconstraint->old_conpfeqop = NIL;
21282 : 18 : fkconstraint->old_pktable_oid = InvalidOid;
21283 : 18 : fkconstraint->location = -1;
21284 : :
21285 : : /* set up colnames, used to generate the constraint name */
21286 [ + + ]: 44 : for (int i = 0; i < numfks; i++)
21287 : : {
21288 : 26 : Form_pg_attribute att;
21289 : :
21290 : 52 : att = TupleDescAttr(RelationGetDescr(partRel),
21291 : 26 : conkey[i] - 1);
21292 : :
21293 : 52 : fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
21294 : 26 : makeString(NameStr(att->attname)));
21295 : 26 : }
21296 : :
21297 : 18 : refdRel = table_open(fk->confrelid, ShareRowExclusiveLock);
21298 : :
21299 : 36 : addFkRecurseReferenced(fkconstraint, partRel,
21300 : 18 : refdRel,
21301 : 18 : conform->conindid,
21302 : 18 : fk->conoid,
21303 : 18 : numfks,
21304 : 18 : confkey,
21305 : 18 : conkey,
21306 : 18 : conpfeqop,
21307 : 18 : conppeqop,
21308 : 18 : conffeqop,
21309 : 18 : numfkdelsetcols,
21310 : 18 : confdelsetcols,
21311 : : true,
21312 : : InvalidOid, InvalidOid,
21313 : 18 : conform->conperiod);
21314 : 18 : table_close(refdRel, NoLock); /* keep lock till end of xact */
21315 : 18 : }
21316 : :
21317 : 18 : ReleaseSysCache(contup);
21318 [ + + ]: 29 : }
21319 : 144 : list_free_deep(fks);
21320 [ + + ]: 144 : if (trigrel)
21321 : 15 : table_close(trigrel, RowExclusiveLock);
21322 : :
21323 : : /*
21324 : : * Any sub-constraints that are in the referenced-side of a larger
21325 : : * constraint have to be removed. This partition is no longer part of the
21326 : : * key space of the constraint.
21327 : : */
21328 [ + + + + : 154 : foreach(cell, GetParentedForeignKeyRefs(partRel))
+ + ]
21329 : : {
21330 : 10 : Oid constrOid = lfirst_oid(cell);
21331 : 10 : ObjectAddress constraint;
21332 : :
21333 : 10 : ConstraintSetParentConstraint(constrOid, InvalidOid, InvalidOid);
21334 : 10 : deleteDependencyRecordsForClass(ConstraintRelationId,
21335 : 10 : constrOid,
21336 : : ConstraintRelationId,
21337 : : DEPENDENCY_INTERNAL);
21338 : 10 : CommandCounterIncrement();
21339 : :
21340 : 10 : ObjectAddressSet(constraint, ConstraintRelationId, constrOid);
21341 : 10 : performDeletion(&constraint, DROP_RESTRICT, 0);
21342 : 10 : }
21343 : :
21344 : : /* Now we can detach indexes */
21345 : 144 : indexes = RelationGetIndexList(partRel);
21346 [ + + + + : 212 : foreach(cell, indexes)
+ + ]
21347 : : {
21348 : 68 : Oid idxid = lfirst_oid(cell);
21349 : 68 : Oid parentidx;
21350 : 68 : Relation idx;
21351 : 68 : Oid constrOid;
21352 : 68 : Oid parentConstrOid;
21353 : :
21354 [ + + ]: 68 : if (!has_superclass(idxid))
21355 : 2 : continue;
21356 : :
21357 : 66 : parentidx = get_partition_parent(idxid, false);
21358 [ - + ]: 66 : Assert((IndexGetRelation(parentidx, false) == RelationGetRelid(rel)));
21359 : :
21360 : 66 : idx = index_open(idxid, AccessExclusiveLock);
21361 : 66 : IndexSetParentIndex(idx, InvalidOid);
21362 : :
21363 : : /*
21364 : : * If there's a constraint associated with the index, detach it too.
21365 : : * Careful: it is possible for a constraint index in a partition to be
21366 : : * the child of a non-constraint index, so verify whether the parent
21367 : : * index does actually have a constraint.
21368 : : */
21369 : 132 : constrOid = get_relation_idx_constraint_oid(RelationGetRelid(partRel),
21370 : 66 : idxid);
21371 : 132 : parentConstrOid = get_relation_idx_constraint_oid(RelationGetRelid(rel),
21372 : 66 : parentidx);
21373 [ + + + - ]: 66 : if (OidIsValid(parentConstrOid) && OidIsValid(constrOid))
21374 : 28 : ConstraintSetParentConstraint(constrOid, InvalidOid, InvalidOid);
21375 : :
21376 : 66 : index_close(idx, NoLock);
21377 [ + + ]: 68 : }
21378 : :
21379 : : /* Update pg_class tuple */
21380 : 144 : classRel = table_open(RelationRelationId, RowExclusiveLock);
21381 : 144 : tuple = SearchSysCacheCopy1(RELOID,
21382 : : ObjectIdGetDatum(RelationGetRelid(partRel)));
21383 [ + - ]: 144 : if (!HeapTupleIsValid(tuple))
21384 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u",
21385 : : RelationGetRelid(partRel));
21386 [ + - ]: 144 : Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
21387 : :
21388 : : /* Clear relpartbound and reset relispartition */
21389 : 144 : memset(new_val, 0, sizeof(new_val));
21390 : 144 : memset(new_null, false, sizeof(new_null));
21391 : 144 : memset(new_repl, false, sizeof(new_repl));
21392 : 144 : new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
21393 : 144 : new_null[Anum_pg_class_relpartbound - 1] = true;
21394 : 144 : new_repl[Anum_pg_class_relpartbound - 1] = true;
21395 : 288 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
21396 : 144 : new_val, new_null, new_repl);
21397 : :
21398 : 144 : ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
21399 : 144 : CatalogTupleUpdate(classRel, &newtuple->t_self, newtuple);
21400 : 144 : heap_freetuple(newtuple);
21401 : 144 : table_close(classRel, RowExclusiveLock);
21402 : :
21403 : : /*
21404 : : * Drop identity property from all identity columns of partition.
21405 : : */
21406 [ + + ]: 481 : for (int attno = 0; attno < RelationGetNumberOfAttributes(partRel); attno++)
21407 : : {
21408 : 337 : Form_pg_attribute attr = TupleDescAttr(partRel->rd_att, attno);
21409 : :
21410 [ + + + + ]: 337 : if (!attr->attisdropped && attr->attidentity)
21411 : 5 : ATExecDropIdentity(partRel, NameStr(attr->attname), false,
21412 : : AccessExclusiveLock, true, true);
21413 : 337 : }
21414 : :
21415 [ + + ]: 144 : if (OidIsValid(defaultPartOid))
21416 : : {
21417 : : /*
21418 : : * If the relation being detached is the default partition itself,
21419 : : * remove it from the parent's pg_partitioned_table entry.
21420 : : *
21421 : : * If not, we must invalidate default partition's relcache entry, as
21422 : : * in StorePartitionBound: its partition constraint depends on every
21423 : : * other partition's partition constraint.
21424 : : */
21425 [ + + ]: 31 : if (RelationGetRelid(partRel) == defaultPartOid)
21426 : 7 : update_default_partition_oid(RelationGetRelid(rel), InvalidOid);
21427 : : else
21428 : 24 : CacheInvalidateRelcacheByRelid(defaultPartOid);
21429 : 31 : }
21430 : :
21431 : : /*
21432 : : * Invalidate the parent's relcache so that the partition is no longer
21433 : : * included in its partition descriptor.
21434 : : */
21435 : 144 : CacheInvalidateRelcache(rel);
21436 : :
21437 : : /*
21438 : : * If the partition we just detached is partitioned itself, invalidate
21439 : : * relcache for all descendent partitions too to ensure that their
21440 : : * rd_partcheck expression trees are rebuilt; must lock partitions before
21441 : : * doing so, using the same lockmode as what partRel has been locked with
21442 : : * by the caller.
21443 : : */
21444 [ + + ]: 144 : if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
21445 : : {
21446 : 10 : List *children;
21447 : :
21448 : 10 : children = find_all_inheritors(RelationGetRelid(partRel),
21449 : : AccessExclusiveLock, NULL);
21450 [ + - + + : 33 : foreach(cell, children)
+ + ]
21451 : : {
21452 : 23 : CacheInvalidateRelcacheByRelid(lfirst_oid(cell));
21453 : 23 : }
21454 : 10 : }
21455 : 144 : }
21456 : :
21457 : : /*
21458 : : * ALTER TABLE ... DETACH PARTITION ... FINALIZE
21459 : : *
21460 : : * To use when a DETACH PARTITION command previously did not run to
21461 : : * completion; this completes the detaching process.
21462 : : */
21463 : : static ObjectAddress
21464 : 0 : ATExecDetachPartitionFinalize(Relation rel, RangeVar *name)
21465 : : {
21466 : 0 : Relation partRel;
21467 : : ObjectAddress address;
21468 : 0 : Snapshot snap = GetActiveSnapshot();
21469 : :
21470 : 0 : partRel = table_openrv(name, AccessExclusiveLock);
21471 : :
21472 : : /*
21473 : : * Wait until existing snapshots are gone. This is important if the
21474 : : * second transaction of DETACH PARTITION CONCURRENTLY is canceled: the
21475 : : * user could immediately run DETACH FINALIZE without actually waiting for
21476 : : * existing transactions. We must not complete the detach action until
21477 : : * all such queries are complete (otherwise we would present them with an
21478 : : * inconsistent view of catalogs).
21479 : : */
21480 : 0 : WaitForOlderSnapshots(snap->xmin, false);
21481 : :
21482 : 0 : DetachPartitionFinalize(rel, partRel, true, InvalidOid);
21483 : :
21484 : 0 : ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
21485 : :
21486 : 0 : table_close(partRel, NoLock);
21487 : :
21488 : : return address;
21489 : 0 : }
21490 : :
21491 : : /*
21492 : : * DropClonedTriggersFromPartition
21493 : : * subroutine for ATExecDetachPartition to remove any triggers that were
21494 : : * cloned to the partition when it was created-as-partition or attached.
21495 : : * This undoes what CloneRowTriggersToPartition did.
21496 : : */
21497 : : static void
21498 : 144 : DropClonedTriggersFromPartition(Oid partitionId)
21499 : : {
21500 : 144 : ScanKeyData skey;
21501 : 144 : SysScanDesc scan;
21502 : 144 : HeapTuple trigtup;
21503 : 144 : Relation tgrel;
21504 : 144 : ObjectAddresses *objects;
21505 : :
21506 : 144 : objects = new_object_addresses();
21507 : :
21508 : : /*
21509 : : * Scan pg_trigger to search for all triggers on this rel.
21510 : : */
21511 : 144 : ScanKeyInit(&skey, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber,
21512 : 144 : F_OIDEQ, ObjectIdGetDatum(partitionId));
21513 : 144 : tgrel = table_open(TriggerRelationId, RowExclusiveLock);
21514 : 144 : scan = systable_beginscan(tgrel, TriggerRelidNameIndexId,
21515 : : true, NULL, 1, &skey);
21516 [ + + ]: 220 : while (HeapTupleIsValid(trigtup = systable_getnext(scan)))
21517 : : {
21518 : 76 : Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(trigtup);
21519 : 76 : ObjectAddress trig;
21520 : :
21521 : : /* Ignore triggers that weren't cloned */
21522 [ + + ]: 76 : if (!OidIsValid(pg_trigger->tgparentid))
21523 : 14 : continue;
21524 : :
21525 : : /*
21526 : : * Ignore internal triggers that are implementation objects of foreign
21527 : : * keys, because these will be detached when the foreign keys
21528 : : * themselves are.
21529 : : */
21530 [ + + ]: 62 : if (OidIsValid(pg_trigger->tgconstrrelid))
21531 : 52 : continue;
21532 : :
21533 : : /*
21534 : : * This is ugly, but necessary: remove the dependency markings on the
21535 : : * trigger so that it can be removed.
21536 : : */
21537 : 10 : deleteDependencyRecordsForClass(TriggerRelationId, pg_trigger->oid,
21538 : : TriggerRelationId,
21539 : : DEPENDENCY_PARTITION_PRI);
21540 : 10 : deleteDependencyRecordsForClass(TriggerRelationId, pg_trigger->oid,
21541 : : RelationRelationId,
21542 : : DEPENDENCY_PARTITION_SEC);
21543 : :
21544 : : /* remember this trigger to remove it below */
21545 : 10 : ObjectAddressSet(trig, TriggerRelationId, pg_trigger->oid);
21546 : 10 : add_exact_object_address(&trig, objects);
21547 [ - + + ]: 76 : }
21548 : :
21549 : : /* make the dependency removal visible to the deletion below */
21550 : 144 : CommandCounterIncrement();
21551 : 144 : performMultipleDeletions(objects, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
21552 : :
21553 : : /* done */
21554 : 144 : free_object_addresses(objects);
21555 : 144 : systable_endscan(scan);
21556 : 144 : table_close(tgrel, RowExclusiveLock);
21557 : 144 : }
21558 : :
21559 : : /*
21560 : : * Before acquiring lock on an index, acquire the same lock on the owning
21561 : : * table.
21562 : : */
21563 : : struct AttachIndexCallbackState
21564 : : {
21565 : : Oid partitionOid;
21566 : : Oid parentTblOid;
21567 : : bool lockedParentTbl;
21568 : : };
21569 : :
21570 : : static void
21571 : 46 : RangeVarCallbackForAttachIndex(const RangeVar *rv, Oid relOid, Oid oldRelOid,
21572 : : void *arg)
21573 : : {
21574 : 46 : struct AttachIndexCallbackState *state;
21575 : 46 : Form_pg_class classform;
21576 : 46 : HeapTuple tuple;
21577 : :
21578 : 46 : state = (struct AttachIndexCallbackState *) arg;
21579 : :
21580 [ + + ]: 46 : if (!state->lockedParentTbl)
21581 : : {
21582 : 45 : LockRelationOid(state->parentTblOid, AccessShareLock);
21583 : 45 : state->lockedParentTbl = true;
21584 : 45 : }
21585 : :
21586 : : /*
21587 : : * If we previously locked some other heap, and the name we're looking up
21588 : : * no longer refers to an index on that relation, release the now-useless
21589 : : * lock. XXX maybe we should do *after* we verify whether the index does
21590 : : * not actually belong to the same relation ...
21591 : : */
21592 [ + + + - ]: 46 : if (relOid != oldRelOid && OidIsValid(state->partitionOid))
21593 : : {
21594 : 0 : UnlockRelationOid(state->partitionOid, AccessShareLock);
21595 : 0 : state->partitionOid = InvalidOid;
21596 : 0 : }
21597 : :
21598 : : /* Didn't find a relation, so no need for locking or permission checks. */
21599 [ + + ]: 46 : if (!OidIsValid(relOid))
21600 : 1 : return;
21601 : :
21602 : 45 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
21603 [ + - ]: 45 : if (!HeapTupleIsValid(tuple))
21604 : 0 : return; /* concurrently dropped, so nothing to do */
21605 : 45 : classform = (Form_pg_class) GETSTRUCT(tuple);
21606 [ + + + + ]: 45 : if (classform->relkind != RELKIND_PARTITIONED_INDEX &&
21607 : 34 : classform->relkind != RELKIND_INDEX)
21608 [ + - + - ]: 1 : ereport(ERROR,
21609 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
21610 : : errmsg("\"%s\" is not an index", rv->relname)));
21611 : 44 : ReleaseSysCache(tuple);
21612 : :
21613 : : /*
21614 : : * Since we need only examine the heap's tupledesc, an access share lock
21615 : : * on it (preventing any DDL) is sufficient.
21616 : : */
21617 : 44 : state->partitionOid = IndexGetRelation(relOid, false);
21618 : 44 : LockRelationOid(state->partitionOid, AccessShareLock);
21619 [ - + ]: 45 : }
21620 : :
21621 : : /*
21622 : : * ALTER INDEX i1 ATTACH PARTITION i2
21623 : : */
21624 : : static ObjectAddress
21625 : 43 : ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
21626 : : {
21627 : 43 : Relation partIdx;
21628 : 43 : Relation partTbl;
21629 : 43 : Relation parentTbl;
21630 : : ObjectAddress address;
21631 : 43 : Oid partIdxId;
21632 : 43 : Oid currParent;
21633 : 43 : struct AttachIndexCallbackState state;
21634 : :
21635 : : /*
21636 : : * We need to obtain lock on the index 'name' to modify it, but we also
21637 : : * need to read its owning table's tuple descriptor -- so we need to lock
21638 : : * both. To avoid deadlocks, obtain lock on the table before doing so on
21639 : : * the index. Furthermore, we need to examine the parent table of the
21640 : : * partition, so lock that one too.
21641 : : */
21642 : 43 : state.partitionOid = InvalidOid;
21643 : 43 : state.parentTblOid = parentIdx->rd_index->indrelid;
21644 : 43 : state.lockedParentTbl = false;
21645 : 43 : partIdxId =
21646 : 43 : RangeVarGetRelidExtended(name, AccessExclusiveLock, 0,
21647 : : RangeVarCallbackForAttachIndex,
21648 : : &state);
21649 : : /* Not there? */
21650 [ + - ]: 43 : if (!OidIsValid(partIdxId))
21651 [ # # # # ]: 0 : ereport(ERROR,
21652 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
21653 : : errmsg("index \"%s\" does not exist", name->relname)));
21654 : :
21655 : : /* no deadlock risk: RangeVarGetRelidExtended already acquired the lock */
21656 : 43 : partIdx = relation_open(partIdxId, AccessExclusiveLock);
21657 : :
21658 : : /* we already hold locks on both tables, so this is safe: */
21659 : 43 : parentTbl = relation_open(parentIdx->rd_index->indrelid, AccessShareLock);
21660 : 43 : partTbl = relation_open(partIdx->rd_index->indrelid, NoLock);
21661 : :
21662 : 43 : ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partIdx));
21663 : :
21664 : : /* Silently do nothing if already in the right state */
21665 [ + + ]: 43 : currParent = partIdx->rd_rel->relispartition ?
21666 : 4 : get_partition_parent(partIdxId, false) : InvalidOid;
21667 [ + + ]: 43 : if (currParent != RelationGetRelid(parentIdx))
21668 : : {
21669 : 38 : IndexInfo *childInfo;
21670 : 38 : IndexInfo *parentInfo;
21671 : 38 : AttrMap *attmap;
21672 : 38 : bool found;
21673 : 38 : int i;
21674 : 38 : PartitionDesc partDesc;
21675 : 38 : Oid constraintOid,
21676 : 38 : cldConstrId = InvalidOid;
21677 : :
21678 : : /*
21679 : : * If this partition already has an index attached, refuse the
21680 : : * operation.
21681 : : */
21682 : 38 : refuseDupeIndexAttach(parentIdx, partIdx, partTbl);
21683 : :
21684 [ + - ]: 38 : if (OidIsValid(currParent))
21685 [ # # # # ]: 0 : ereport(ERROR,
21686 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
21687 : : errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
21688 : : RelationGetRelationName(partIdx),
21689 : : RelationGetRelationName(parentIdx)),
21690 : : errdetail("Index \"%s\" is already attached to another index.",
21691 : : RelationGetRelationName(partIdx))));
21692 : :
21693 : : /* Make sure it indexes a partition of the other index's table */
21694 : 38 : partDesc = RelationGetPartitionDesc(parentTbl, true);
21695 : 38 : found = false;
21696 [ + + ]: 52 : for (i = 0; i < partDesc->nparts; i++)
21697 : : {
21698 [ + + ]: 51 : if (partDesc->oids[i] == state.partitionOid)
21699 : : {
21700 : 37 : found = true;
21701 : 37 : break;
21702 : : }
21703 : 14 : }
21704 [ + + ]: 38 : if (!found)
21705 [ + - + - ]: 1 : ereport(ERROR,
21706 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
21707 : : errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
21708 : : RelationGetRelationName(partIdx),
21709 : : RelationGetRelationName(parentIdx)),
21710 : : errdetail("Index \"%s\" is not an index on any partition of table \"%s\".",
21711 : : RelationGetRelationName(partIdx),
21712 : : RelationGetRelationName(parentTbl))));
21713 : :
21714 : : /* Ensure the indexes are compatible */
21715 : 37 : childInfo = BuildIndexInfo(partIdx);
21716 : 37 : parentInfo = BuildIndexInfo(parentIdx);
21717 : 74 : attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
21718 : 37 : RelationGetDescr(parentTbl),
21719 : : false);
21720 [ + + + + ]: 74 : if (!CompareIndexInfo(childInfo, parentInfo,
21721 : 37 : partIdx->rd_indcollation,
21722 : 37 : parentIdx->rd_indcollation,
21723 : 37 : partIdx->rd_opfamily,
21724 : 37 : parentIdx->rd_opfamily,
21725 : 37 : attmap))
21726 [ + - + - ]: 7 : ereport(ERROR,
21727 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
21728 : : errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
21729 : : RelationGetRelationName(partIdx),
21730 : : RelationGetRelationName(parentIdx)),
21731 : : errdetail("The index definitions do not match.")));
21732 : :
21733 : : /*
21734 : : * If there is a constraint in the parent, make sure there is one in
21735 : : * the child too.
21736 : : */
21737 : 60 : constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(parentTbl),
21738 : 30 : RelationGetRelid(parentIdx));
21739 : :
21740 [ + + ]: 30 : if (OidIsValid(constraintOid))
21741 : : {
21742 : 8 : cldConstrId = get_relation_idx_constraint_oid(RelationGetRelid(partTbl),
21743 : 4 : partIdxId);
21744 [ + + ]: 4 : if (!OidIsValid(cldConstrId))
21745 [ + - + - ]: 1 : ereport(ERROR,
21746 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
21747 : : errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
21748 : : RelationGetRelationName(partIdx),
21749 : : RelationGetRelationName(parentIdx)),
21750 : : errdetail("The index \"%s\" belongs to a constraint in table \"%s\" but no constraint exists for index \"%s\".",
21751 : : RelationGetRelationName(parentIdx),
21752 : : RelationGetRelationName(parentTbl),
21753 : : RelationGetRelationName(partIdx))));
21754 : 3 : }
21755 : :
21756 : : /*
21757 : : * If it's a primary key, make sure the columns in the partition are
21758 : : * NOT NULL.
21759 : : */
21760 [ + + ]: 29 : if (parentIdx->rd_index->indisprimary)
21761 : 3 : verifyPartitionIndexNotNull(childInfo, partTbl);
21762 : :
21763 : : /* All good -- do it */
21764 : 29 : IndexSetParentIndex(partIdx, RelationGetRelid(parentIdx));
21765 [ + + ]: 29 : if (OidIsValid(constraintOid))
21766 : 6 : ConstraintSetParentConstraint(cldConstrId, constraintOid,
21767 : 3 : RelationGetRelid(partTbl));
21768 : :
21769 : 29 : free_attrmap(attmap);
21770 : :
21771 : 29 : validatePartitionedIndex(parentIdx, parentTbl);
21772 : 29 : }
21773 : :
21774 : 34 : relation_close(parentTbl, AccessShareLock);
21775 : : /* keep these locks till commit */
21776 : 34 : relation_close(partTbl, NoLock);
21777 : 34 : relation_close(partIdx, NoLock);
21778 : :
21779 : : return address;
21780 : 34 : }
21781 : :
21782 : : /*
21783 : : * Verify whether the given partition already contains an index attached
21784 : : * to the given partitioned index. If so, raise an error.
21785 : : */
21786 : : static void
21787 : 39 : refuseDupeIndexAttach(Relation parentIdx, Relation partIdx, Relation partitionTbl)
21788 : : {
21789 : 39 : Oid existingIdx;
21790 : :
21791 : 78 : existingIdx = index_get_partition(partitionTbl,
21792 : 39 : RelationGetRelid(parentIdx));
21793 [ + + ]: 39 : if (OidIsValid(existingIdx))
21794 [ + - + - ]: 1 : ereport(ERROR,
21795 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
21796 : : errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
21797 : : RelationGetRelationName(partIdx),
21798 : : RelationGetRelationName(parentIdx)),
21799 : : errdetail("Another index \"%s\" is already attached for partition \"%s\".",
21800 : : get_rel_name(existingIdx),
21801 : : RelationGetRelationName(partitionTbl))));
21802 : 38 : }
21803 : :
21804 : : /*
21805 : : * Verify whether the set of attached partition indexes to a parent index on
21806 : : * a partitioned table is complete. If it is, mark the parent index valid.
21807 : : *
21808 : : * This should be called each time a partition index is attached.
21809 : : */
21810 : : static void
21811 : 36 : validatePartitionedIndex(Relation partedIdx, Relation partedTbl)
21812 : : {
21813 : 36 : Relation inheritsRel;
21814 : 36 : SysScanDesc scan;
21815 : 36 : ScanKeyData key;
21816 : 36 : int tuples = 0;
21817 : 36 : HeapTuple inhTup;
21818 : 36 : bool updated = false;
21819 : :
21820 [ + - ]: 36 : Assert(partedIdx->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
21821 : :
21822 : : /*
21823 : : * Scan pg_inherits for this parent index. Count each valid index we find
21824 : : * (verifying the pg_index entry for each), and if we reach the total
21825 : : * amount we expect, we can mark this parent index as valid.
21826 : : */
21827 : 36 : inheritsRel = table_open(InheritsRelationId, AccessShareLock);
21828 : 36 : ScanKeyInit(&key, Anum_pg_inherits_inhparent,
21829 : : BTEqualStrategyNumber, F_OIDEQ,
21830 : 36 : ObjectIdGetDatum(RelationGetRelid(partedIdx)));
21831 : 36 : scan = systable_beginscan(inheritsRel, InheritsParentIndexId, true,
21832 : : NULL, 1, &key);
21833 [ + + ]: 87 : while ((inhTup = systable_getnext(scan)) != NULL)
21834 : : {
21835 : 51 : Form_pg_inherits inhForm = (Form_pg_inherits) GETSTRUCT(inhTup);
21836 : 51 : HeapTuple indTup;
21837 : 51 : Form_pg_index indexForm;
21838 : :
21839 : 51 : indTup = SearchSysCache1(INDEXRELID,
21840 : 51 : ObjectIdGetDatum(inhForm->inhrelid));
21841 [ + - ]: 51 : if (!HeapTupleIsValid(indTup))
21842 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for index %u", inhForm->inhrelid);
21843 : 51 : indexForm = (Form_pg_index) GETSTRUCT(indTup);
21844 [ + + ]: 51 : if (indexForm->indisvalid)
21845 : 42 : tuples += 1;
21846 : 51 : ReleaseSysCache(indTup);
21847 : 51 : }
21848 : :
21849 : : /* Done with pg_inherits */
21850 : 36 : systable_endscan(scan);
21851 : 36 : table_close(inheritsRel, AccessShareLock);
21852 : :
21853 : : /*
21854 : : * If we found as many inherited indexes as the partitioned table has
21855 : : * partitions, we're good; update pg_index to set indisvalid.
21856 : : */
21857 [ + + ]: 36 : if (tuples == RelationGetPartitionDesc(partedTbl, true)->nparts)
21858 : : {
21859 : 20 : Relation idxRel;
21860 : 20 : HeapTuple indTup;
21861 : 20 : Form_pg_index indexForm;
21862 : :
21863 : 20 : idxRel = table_open(IndexRelationId, RowExclusiveLock);
21864 : 20 : indTup = SearchSysCacheCopy1(INDEXRELID,
21865 : : ObjectIdGetDatum(RelationGetRelid(partedIdx)));
21866 [ + - ]: 20 : if (!HeapTupleIsValid(indTup))
21867 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for index %u",
21868 : : RelationGetRelid(partedIdx));
21869 : 20 : indexForm = (Form_pg_index) GETSTRUCT(indTup);
21870 : :
21871 : 20 : indexForm->indisvalid = true;
21872 : 20 : updated = true;
21873 : :
21874 : 20 : CatalogTupleUpdate(idxRel, &indTup->t_self, indTup);
21875 : :
21876 : 20 : table_close(idxRel, RowExclusiveLock);
21877 : 20 : heap_freetuple(indTup);
21878 : 20 : }
21879 : :
21880 : : /*
21881 : : * If this index is in turn a partition of a larger index, validating it
21882 : : * might cause the parent to become valid also. Try that.
21883 : : */
21884 [ + + + + ]: 36 : if (updated && partedIdx->rd_rel->relispartition)
21885 : : {
21886 : 7 : Oid parentIdxId,
21887 : : parentTblId;
21888 : 7 : Relation parentIdx,
21889 : : parentTbl;
21890 : :
21891 : : /* make sure we see the validation we just did */
21892 : 7 : CommandCounterIncrement();
21893 : :
21894 : 7 : parentIdxId = get_partition_parent(RelationGetRelid(partedIdx), false);
21895 : 7 : parentTblId = get_partition_parent(RelationGetRelid(partedTbl), false);
21896 : 7 : parentIdx = relation_open(parentIdxId, AccessExclusiveLock);
21897 : 7 : parentTbl = relation_open(parentTblId, AccessExclusiveLock);
21898 [ + - ]: 7 : Assert(!parentIdx->rd_index->indisvalid);
21899 : :
21900 : 7 : validatePartitionedIndex(parentIdx, parentTbl);
21901 : :
21902 : 7 : relation_close(parentIdx, AccessExclusiveLock);
21903 : 7 : relation_close(parentTbl, AccessExclusiveLock);
21904 : 7 : }
21905 : 36 : }
21906 : :
21907 : : /*
21908 : : * When attaching an index as a partition of a partitioned index which is a
21909 : : * primary key, verify that all the columns in the partition are marked NOT
21910 : : * NULL.
21911 : : */
21912 : : static void
21913 : 3 : verifyPartitionIndexNotNull(IndexInfo *iinfo, Relation partition)
21914 : : {
21915 [ + + ]: 6 : for (int i = 0; i < iinfo->ii_NumIndexKeyAttrs; i++)
21916 : : {
21917 : 6 : Form_pg_attribute att = TupleDescAttr(RelationGetDescr(partition),
21918 : 3 : iinfo->ii_IndexAttrNumbers[i] - 1);
21919 : :
21920 [ + - ]: 3 : if (!att->attnotnull)
21921 [ # # # # ]: 0 : ereport(ERROR,
21922 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
21923 : : errmsg("invalid primary key definition"),
21924 : : errdetail("Column \"%s\" of relation \"%s\" is not marked NOT NULL.",
21925 : : NameStr(att->attname),
21926 : : RelationGetRelationName(partition)));
21927 : 3 : }
21928 : 3 : }
21929 : :
21930 : : /*
21931 : : * Return an OID list of constraints that reference the given relation
21932 : : * that are marked as having a parent constraints.
21933 : : */
21934 : : static List *
21935 : 210 : GetParentedForeignKeyRefs(Relation partition)
21936 : : {
21937 : 210 : Relation pg_constraint;
21938 : 210 : HeapTuple tuple;
21939 : 210 : SysScanDesc scan;
21940 : 210 : ScanKeyData key[2];
21941 : 210 : List *constraints = NIL;
21942 : :
21943 : : /*
21944 : : * If no indexes, or no columns are referenceable by FKs, we can avoid the
21945 : : * scan.
21946 : : */
21947 [ + + + + ]: 210 : if (RelationGetIndexList(partition) == NIL ||
21948 : 92 : bms_is_empty(RelationGetIndexAttrBitmap(partition,
21949 : : INDEX_ATTR_BITMAP_KEY)))
21950 : 160 : return NIL;
21951 : :
21952 : : /* Search for constraints referencing this table */
21953 : 50 : pg_constraint = table_open(ConstraintRelationId, AccessShareLock);
21954 : 100 : ScanKeyInit(&key[0],
21955 : : Anum_pg_constraint_confrelid, BTEqualStrategyNumber,
21956 : 50 : F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(partition)));
21957 : 100 : ScanKeyInit(&key[1],
21958 : : Anum_pg_constraint_contype, BTEqualStrategyNumber,
21959 : 50 : F_CHAREQ, CharGetDatum(CONSTRAINT_FOREIGN));
21960 : :
21961 : : /* XXX This is a seqscan, as we don't have a usable index */
21962 : 50 : scan = systable_beginscan(pg_constraint, InvalidOid, true, NULL, 2, key);
21963 [ + + ]: 70 : while ((tuple = systable_getnext(scan)) != NULL)
21964 : : {
21965 : 20 : Form_pg_constraint constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
21966 : :
21967 : : /*
21968 : : * We only need to process constraints that are part of larger ones.
21969 : : */
21970 [ + - ]: 20 : if (!OidIsValid(constrForm->conparentid))
21971 : 0 : continue;
21972 : :
21973 : 20 : constraints = lappend_oid(constraints, constrForm->oid);
21974 [ - - + ]: 20 : }
21975 : :
21976 : 50 : systable_endscan(scan);
21977 : 50 : table_close(pg_constraint, AccessShareLock);
21978 : :
21979 : 50 : return constraints;
21980 : 210 : }
21981 : :
21982 : : /*
21983 : : * During DETACH PARTITION, verify that any foreign keys pointing to the
21984 : : * partitioned table would not become invalid. An error is raised if any
21985 : : * referenced values exist.
21986 : : */
21987 : : static void
21988 : 66 : ATDetachCheckNoForeignKeyRefs(Relation partition)
21989 : : {
21990 : 66 : List *constraints;
21991 : 66 : ListCell *cell;
21992 : :
21993 : 66 : constraints = GetParentedForeignKeyRefs(partition);
21994 : :
21995 [ + + + + : 71 : foreach(cell, constraints)
+ + ]
21996 : : {
21997 : 5 : Oid constrOid = lfirst_oid(cell);
21998 : 5 : HeapTuple tuple;
21999 : 5 : Form_pg_constraint constrForm;
22000 : 5 : Relation rel;
22001 : 5 : Trigger trig = {0};
22002 : :
22003 : 5 : tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
22004 [ + - ]: 5 : if (!HeapTupleIsValid(tuple))
22005 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for constraint %u", constrOid);
22006 : 5 : constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
22007 : :
22008 [ - + ]: 5 : Assert(OidIsValid(constrForm->conparentid));
22009 [ - + ]: 5 : Assert(constrForm->confrelid == RelationGetRelid(partition));
22010 : :
22011 : : /* prevent data changes into the referencing table until commit */
22012 : 5 : rel = table_open(constrForm->conrelid, ShareLock);
22013 : :
22014 : 5 : trig.tgoid = InvalidOid;
22015 : 5 : trig.tgname = NameStr(constrForm->conname);
22016 : 5 : trig.tgenabled = TRIGGER_FIRES_ON_ORIGIN;
22017 : 5 : trig.tgisinternal = true;
22018 : 5 : trig.tgconstrrelid = RelationGetRelid(partition);
22019 : 5 : trig.tgconstrindid = constrForm->conindid;
22020 : 5 : trig.tgconstraint = constrForm->oid;
22021 : 5 : trig.tgdeferrable = false;
22022 : 5 : trig.tginitdeferred = false;
22023 : : /* we needn't fill in remaining fields */
22024 : :
22025 : 5 : RI_PartitionRemove_Check(&trig, rel, partition);
22026 : :
22027 : 5 : ReleaseSysCache(tuple);
22028 : :
22029 : 5 : table_close(rel, NoLock);
22030 : 5 : }
22031 : 66 : }
22032 : :
22033 : : /*
22034 : : * resolve column compression specification to compression method.
22035 : : */
22036 : : static char
22037 : 15038 : GetAttributeCompression(Oid atttypid, const char *compression)
22038 : : {
22039 : 15038 : char cmethod;
22040 : :
22041 [ + + + + ]: 15038 : if (compression == NULL || strcmp(compression, "default") == 0)
22042 : 15008 : return InvalidCompressionMethod;
22043 : :
22044 : : /*
22045 : : * To specify a nondefault method, the column data type must be toastable.
22046 : : * Note this says nothing about whether the column's attstorage setting
22047 : : * permits compression; we intentionally allow attstorage and
22048 : : * attcompression to be independent. But with a non-toastable type,
22049 : : * attstorage could not be set to a value that would permit compression.
22050 : : *
22051 : : * We don't actually need to enforce this, since nothing bad would happen
22052 : : * if attcompression were non-default; it would never be consulted. But
22053 : : * it seems more user-friendly to complain about a certainly-useless
22054 : : * attempt to set the property.
22055 : : */
22056 [ + + ]: 30 : if (!TypeIsToastable(atttypid))
22057 [ + - + - ]: 1 : ereport(ERROR,
22058 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
22059 : : errmsg("column data type %s does not support compression",
22060 : : format_type_be(atttypid))));
22061 : :
22062 : 29 : cmethod = CompressionNameToMethod(compression);
22063 [ + + ]: 29 : if (!CompressionMethodIsValid(cmethod))
22064 [ + - + - ]: 2 : ereport(ERROR,
22065 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
22066 : : errmsg("invalid compression method \"%s\"", compression)));
22067 : :
22068 : 27 : return cmethod;
22069 : 15035 : }
22070 : :
22071 : : /*
22072 : : * resolve column storage specification
22073 : : */
22074 : : static char
22075 : 39 : GetAttributeStorage(Oid atttypid, const char *storagemode)
22076 : : {
22077 : 39 : char cstorage = 0;
22078 : :
22079 [ + + ]: 39 : if (pg_strcasecmp(storagemode, "plain") == 0)
22080 : 8 : cstorage = TYPSTORAGE_PLAIN;
22081 [ + + ]: 31 : else if (pg_strcasecmp(storagemode, "external") == 0)
22082 : 16 : cstorage = TYPSTORAGE_EXTERNAL;
22083 [ + + ]: 15 : else if (pg_strcasecmp(storagemode, "extended") == 0)
22084 : 6 : cstorage = TYPSTORAGE_EXTENDED;
22085 [ + + ]: 9 : else if (pg_strcasecmp(storagemode, "main") == 0)
22086 : 8 : cstorage = TYPSTORAGE_MAIN;
22087 [ + - ]: 1 : else if (pg_strcasecmp(storagemode, "default") == 0)
22088 : 1 : cstorage = get_typstorage(atttypid);
22089 : : else
22090 [ # # # # ]: 0 : ereport(ERROR,
22091 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
22092 : : errmsg("invalid storage type \"%s\"",
22093 : : storagemode)));
22094 : :
22095 : : /*
22096 : : * safety check: do not allow toasted storage modes unless column datatype
22097 : : * is TOAST-aware.
22098 : : */
22099 [ + + + + ]: 39 : if (!(cstorage == TYPSTORAGE_PLAIN || TypeIsToastable(atttypid)))
22100 [ + - + - ]: 1 : ereport(ERROR,
22101 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
22102 : : errmsg("column data type %s can only have storage PLAIN",
22103 : : format_type_be(atttypid))));
22104 : :
22105 : 76 : return cstorage;
22106 : 38 : }
22107 : :
22108 : : /*
22109 : : * buildExpressionExecutionStates: build the needed expression execution states
22110 : : * for new partition (newPartRel) checks and initialize expressions for
22111 : : * generated columns. All expressions should be created in "tab"
22112 : : * (AlteredTableInfo structure).
22113 : : */
22114 : : static void
22115 : 96 : buildExpressionExecutionStates(AlteredTableInfo *tab, Relation newPartRel, EState *estate)
22116 : : {
22117 : : /*
22118 : : * Build the needed expression execution states. Here, we expect only NOT
22119 : : * NULL and CHECK constraint.
22120 : : */
22121 [ + + + + : 196 : foreach_ptr(NewConstraint, con, tab->constraints)
+ + + + ]
22122 : : {
22123 [ - + - ]: 4 : switch (con->contype)
22124 : : {
22125 : : case CONSTR_CHECK:
22126 : :
22127 : : /*
22128 : : * We already expanded virtual expression in
22129 : : * createTableConstraints.
22130 : : */
22131 : 4 : con->qualstate = ExecPrepareExpr((Expr *) con->qual, estate);
22132 : 4 : break;
22133 : : case CONSTR_NOTNULL:
22134 : : /* Nothing to do here. */
22135 : : break;
22136 : : default:
22137 [ # # # # ]: 0 : elog(ERROR, "unrecognized constraint type: %d",
22138 : : (int) con->contype);
22139 : 0 : }
22140 : 100 : }
22141 : :
22142 : : /* Expression already planned in createTableConstraints */
22143 [ + + + + : 203 : foreach_ptr(NewColumnValue, ex, tab->newvals)
+ + + + ]
22144 : 107 : ex->exprstate = ExecInitExpr((Expr *) ex->expr, NULL);
22145 : 96 : }
22146 : :
22147 : : /*
22148 : : * evaluateGeneratedExpressionsAndCheckConstraints: evaluate any generated
22149 : : * expressions for "tab" (AlteredTableInfo structure) whose inputs come from
22150 : : * the new tuple (insertslot) of the new partition (newPartRel).
22151 : : */
22152 : : static void
22153 : 155 : evaluateGeneratedExpressionsAndCheckConstraints(AlteredTableInfo *tab,
22154 : : Relation newPartRel,
22155 : : TupleTableSlot *insertslot,
22156 : : ExprContext *econtext)
22157 : : {
22158 : 155 : econtext->ecxt_scantuple = insertslot;
22159 : :
22160 [ + + + + : 326 : foreach_ptr(NewColumnValue, ex, tab->newvals)
+ + + + ]
22161 : : {
22162 [ + - ]: 16 : if (!ex->is_generated)
22163 : 0 : continue;
22164 : :
22165 : 16 : insertslot->tts_values[ex->attnum - 1]
22166 : 48 : = ExecEvalExpr(ex->exprstate,
22167 : 16 : econtext,
22168 : 16 : &insertslot->tts_isnull[ex->attnum - 1]);
22169 : 171 : }
22170 : :
22171 [ + + + + : 316 : foreach_ptr(NewConstraint, con, tab->constraints)
+ + + + ]
22172 : : {
22173 [ - - + ]: 6 : switch (con->contype)
22174 : : {
22175 : : case CONSTR_CHECK:
22176 [ + - ]: 6 : if (!ExecCheck(con->qualstate, econtext))
22177 [ # # # # ]: 0 : ereport(ERROR,
22178 : : errcode(ERRCODE_CHECK_VIOLATION),
22179 : : errmsg("check constraint \"%s\" of relation \"%s\" is violated by some row",
22180 : : con->name, RelationGetRelationName(newPartRel)),
22181 : : errtableconstraint(newPartRel, con->name));
22182 : 6 : break;
22183 : : case CONSTR_NOTNULL:
22184 : : case CONSTR_FOREIGN:
22185 : : /* Nothing to do here */
22186 : 0 : break;
22187 : : default:
22188 [ # # # # ]: 0 : elog(ERROR, "unrecognized constraint type: %d",
22189 : : (int) con->contype);
22190 : 0 : }
22191 : 161 : }
22192 : 155 : }
22193 : :
22194 : : /*
22195 : : * getAttributesList: build a list of columns (ColumnDef) based on parent_rel
22196 : : */
22197 : : static List *
22198 : 101 : getAttributesList(Relation parent_rel)
22199 : : {
22200 : 101 : AttrNumber parent_attno;
22201 : 101 : TupleDesc modelDesc;
22202 : 101 : List *colList = NIL;
22203 : :
22204 : 101 : modelDesc = RelationGetDescr(parent_rel);
22205 : :
22206 [ + + ]: 369 : for (parent_attno = 1; parent_attno <= modelDesc->natts;
22207 : 268 : parent_attno++)
22208 : : {
22209 : 536 : Form_pg_attribute attribute = TupleDescAttr(modelDesc,
22210 : 268 : parent_attno - 1);
22211 : 268 : ColumnDef *def;
22212 : :
22213 : : /* Ignore dropped columns in the parent. */
22214 [ - + ]: 268 : if (attribute->attisdropped)
22215 : 0 : continue;
22216 : :
22217 : 536 : def = makeColumnDef(NameStr(attribute->attname), attribute->atttypid,
22218 : 268 : attribute->atttypmod, attribute->attcollation);
22219 : :
22220 : 268 : def->is_not_null = attribute->attnotnull;
22221 : :
22222 : : /* Copy identity. */
22223 : 268 : def->identity = attribute->attidentity;
22224 : :
22225 : : /* Copy attgenerated. */
22226 : 268 : def->generated = attribute->attgenerated;
22227 : :
22228 : 268 : def->storage = attribute->attstorage;
22229 : :
22230 : : /* Likewise, copy compression. */
22231 [ + + ]: 268 : if (CompressionMethodIsValid(attribute->attcompression))
22232 : 3 : def->compression =
22233 : 3 : pstrdup(GetCompressionMethodName(attribute->attcompression));
22234 : : else
22235 : 265 : def->compression = NULL;
22236 : :
22237 : : /* Add to column list. */
22238 : 268 : colList = lappend(colList, def);
22239 [ - - + ]: 268 : }
22240 : :
22241 : 202 : return colList;
22242 : 101 : }
22243 : :
22244 : : /*
22245 : : * createTableConstraints:
22246 : : * create check constraints, default values, and generated values for newRel
22247 : : * based on parent_rel. tab is pending-work queue for newRel, we may need it in
22248 : : * MergePartitionsMoveRows.
22249 : : */
22250 : : static void
22251 : 96 : createTableConstraints(List **wqueue, AlteredTableInfo *tab,
22252 : : Relation parent_rel, Relation newRel)
22253 : : {
22254 : 96 : TupleDesc tupleDesc;
22255 : 96 : TupleConstr *constr;
22256 : 96 : AttrMap *attmap;
22257 : 96 : AttrNumber parent_attno;
22258 : 96 : int ccnum;
22259 : 96 : List *constraints = NIL;
22260 : 96 : List *cookedConstraints = NIL;
22261 : :
22262 : 96 : tupleDesc = RelationGetDescr(parent_rel);
22263 : 96 : constr = tupleDesc->constr;
22264 : :
22265 [ + + ]: 96 : if (!constr)
22266 : 57 : return;
22267 : :
22268 : : /*
22269 : : * Construct a map from the parent relation's attnos to the child rel's.
22270 : : * This re-checks type match, etc, although it shouldn't be possible to
22271 : : * have a failure since both tables are locked.
22272 : : */
22273 : 78 : attmap = build_attrmap_by_name(RelationGetDescr(newRel),
22274 : 39 : tupleDesc,
22275 : : false);
22276 : :
22277 : : /* Cycle for default values. */
22278 [ + + ]: 148 : for (parent_attno = 1; parent_attno <= tupleDesc->natts; parent_attno++)
22279 : : {
22280 : 218 : Form_pg_attribute attribute = TupleDescAttr(tupleDesc,
22281 : 109 : parent_attno - 1);
22282 : :
22283 : : /* Ignore dropped columns in the parent. */
22284 [ - + ]: 109 : if (attribute->attisdropped)
22285 : 0 : continue;
22286 : :
22287 : : /* Copy the default, if present, and it should be copied. */
22288 [ + + ]: 109 : if (attribute->atthasdef)
22289 : : {
22290 : 25 : Node *this_default = NULL;
22291 : 25 : bool found_whole_row;
22292 : 25 : AttrNumber num;
22293 : 25 : Node *def;
22294 : 25 : NewColumnValue *newval;
22295 : :
22296 [ + + ]: 25 : if (attribute->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
22297 : 1 : this_default = build_generation_expression(parent_rel, attribute->attnum);
22298 : : else
22299 : : {
22300 : 24 : this_default = TupleDescGetDefault(tupleDesc, attribute->attnum);
22301 [ + - ]: 24 : if (this_default == NULL)
22302 [ # # # # ]: 0 : elog(ERROR, "default expression not found for attribute %d of relation \"%s\"",
22303 : : attribute->attnum, RelationGetRelationName(parent_rel));
22304 : : }
22305 : :
22306 : 25 : num = attmap->attnums[parent_attno - 1];
22307 : 25 : def = map_variable_attnos(this_default, 1, 0, attmap, InvalidOid, &found_whole_row);
22308 : :
22309 [ - + # # ]: 25 : if (found_whole_row && attribute->attgenerated != '\0')
22310 [ # # # # ]: 0 : elog(ERROR, "cannot convert whole-row table reference");
22311 : :
22312 : : /* Add a pre-cooked default expression. */
22313 : 25 : StoreAttrDefault(newRel, num, def, true);
22314 : :
22315 : : /*
22316 : : * Stored generated column expressions in parent_rel might
22317 : : * reference the tableoid. newRel, parent_rel tableoid clear is
22318 : : * not the same. If so, these stored generated columns require
22319 : : * recomputation for newRel within MergePartitionsMoveRows.
22320 : : */
22321 [ + + ]: 25 : if (attribute->attgenerated == ATTRIBUTE_GENERATED_STORED)
22322 : : {
22323 : 11 : newval = palloc0_object(NewColumnValue);
22324 : 11 : newval->attnum = num;
22325 : 11 : newval->expr = expression_planner((Expr *) def);
22326 : 11 : newval->is_generated = (attribute->attgenerated != '\0');
22327 : 11 : tab->newvals = lappend(tab->newvals, newval);
22328 : 11 : }
22329 : 25 : }
22330 [ - + ]: 109 : }
22331 : :
22332 : : /* Cycle for CHECK constraints. */
22333 [ + + ]: 56 : for (ccnum = 0; ccnum < constr->num_check; ccnum++)
22334 : : {
22335 : 17 : char *ccname = constr->check[ccnum].ccname;
22336 : 17 : char *ccbin = constr->check[ccnum].ccbin;
22337 : 17 : bool ccenforced = constr->check[ccnum].ccenforced;
22338 : 17 : bool ccnoinherit = constr->check[ccnum].ccnoinherit;
22339 : 17 : bool ccvalid = constr->check[ccnum].ccvalid;
22340 : 17 : Node *ccbin_node;
22341 : 17 : bool found_whole_row;
22342 : 17 : Constraint *constr;
22343 : :
22344 : : /*
22345 : : * The partitioned table can not have a NO INHERIT check constraint
22346 : : * (see StoreRelCheck function for details).
22347 : : */
22348 [ + - ]: 17 : Assert(!ccnoinherit);
22349 : :
22350 : 34 : ccbin_node = map_variable_attnos(stringToNode(ccbin),
22351 : : 1, 0,
22352 : 17 : attmap,
22353 : : InvalidOid, &found_whole_row);
22354 : :
22355 : : /*
22356 : : * For the moment we have to reject whole-row variables (as for CREATE
22357 : : * TABLE LIKE and inheritances).
22358 : : */
22359 [ + - ]: 17 : if (found_whole_row)
22360 [ # # # # ]: 0 : elog(ERROR, "Constraint \"%s\" contains a whole-row reference to table \"%s\".",
22361 : : ccname,
22362 : : RelationGetRelationName(parent_rel));
22363 : :
22364 : 17 : constr = makeNode(Constraint);
22365 : 17 : constr->contype = CONSTR_CHECK;
22366 : 17 : constr->conname = pstrdup(ccname);
22367 : 17 : constr->deferrable = false;
22368 : 17 : constr->initdeferred = false;
22369 : 17 : constr->is_enforced = ccenforced;
22370 : 17 : constr->skip_validation = !ccvalid;
22371 : 17 : constr->initially_valid = ccvalid;
22372 : 17 : constr->is_no_inherit = ccnoinherit;
22373 : 17 : constr->raw_expr = NULL;
22374 : 17 : constr->cooked_expr = nodeToString(ccbin_node);
22375 : 17 : constr->location = -1;
22376 : 17 : constraints = lappend(constraints, constr);
22377 : 17 : }
22378 : :
22379 : : /* Install all CHECK constraints. */
22380 : 39 : cookedConstraints = AddRelationNewConstraints(newRel, NIL, constraints,
22381 : : false, true, true, NULL);
22382 : :
22383 : : /* Make the additional catalog changes visible. */
22384 : 39 : CommandCounterIncrement();
22385 : :
22386 : : /*
22387 : : * parent_rel check constraint expression may reference tableoid, so later
22388 : : * in MergePartitionsMoveRows, we need to evaluate the check constraint
22389 : : * again for the newRel. We can check whether the check constraint
22390 : : * contains a tableoid reference via pull_varattnos.
22391 : : */
22392 [ + + + + : 95 : foreach_ptr(CookedConstraint, ccon, cookedConstraints)
+ + + + ]
22393 : : {
22394 [ + + ]: 17 : if (!ccon->skip_validation)
22395 : : {
22396 : 11 : Node *qual;
22397 : 11 : Bitmapset *attnums = NULL;
22398 : :
22399 [ - + ]: 11 : Assert(ccon->contype == CONSTR_CHECK);
22400 : 11 : qual = expand_generated_columns_in_expr(ccon->expr, newRel, 1);
22401 : 11 : pull_varattnos(qual, 1, &attnums);
22402 : :
22403 : : /*
22404 : : * Add a check only if it contains a tableoid
22405 : : * (TableOidAttributeNumber).
22406 : : */
22407 [ + + ]: 11 : if (bms_is_member(TableOidAttributeNumber - FirstLowInvalidHeapAttributeNumber,
22408 : 11 : attnums))
22409 : : {
22410 : 4 : NewConstraint *newcon;
22411 : :
22412 : 4 : newcon = palloc0_object(NewConstraint);
22413 : 4 : newcon->name = ccon->name;
22414 : 4 : newcon->contype = CONSTR_CHECK;
22415 : 4 : newcon->qual = qual;
22416 : :
22417 : 4 : tab->constraints = lappend(tab->constraints, newcon);
22418 : 4 : }
22419 : 11 : }
22420 : 56 : }
22421 : :
22422 : : /* Don't need the cookedConstraints anymore. */
22423 : 39 : list_free_deep(cookedConstraints);
22424 : :
22425 : : /* Reproduce not-null constraints. */
22426 [ + + ]: 39 : if (constr->has_not_null)
22427 : : {
22428 : 27 : List *nnconstraints;
22429 : :
22430 : : /*
22431 : : * The "include_noinh" argument is false because a partitioned table
22432 : : * can't have NO INHERIT constraint.
22433 : : */
22434 : 27 : nnconstraints = RelationGetNotNullConstraints(RelationGetRelid(parent_rel),
22435 : : false, false);
22436 : :
22437 [ + - ]: 27 : Assert(list_length(nnconstraints) > 0);
22438 : :
22439 : : /*
22440 : : * We already set pg_attribute.attnotnull in createPartitionTable. No
22441 : : * need call set_attnotnull again.
22442 : : */
22443 : 27 : AddRelationNewConstraints(newRel, NIL, nnconstraints, false, true, true, NULL);
22444 : 27 : }
22445 : 96 : }
22446 : :
22447 : : /*
22448 : : * createPartitionTable:
22449 : : *
22450 : : * Create a new partition (newPartName) for the partitioned table (parent_rel).
22451 : : * ownerId is determined by the partition on which the operation is performed,
22452 : : * so it is passed separately. The new partition will inherit the access method
22453 : : * and persistence type from the parent table.
22454 : : *
22455 : : * Returns the created relation (locked in AccessExclusiveLock mode).
22456 : : */
22457 : : static Relation
22458 : 101 : createPartitionTable(List **wqueue, RangeVar *newPartName,
22459 : : Relation parent_rel, Oid ownerId)
22460 : : {
22461 : 101 : Relation newRel;
22462 : 101 : Oid newRelId;
22463 : 101 : Oid existingRelid;
22464 : 101 : TupleDesc descriptor;
22465 : 101 : List *colList = NIL;
22466 : 101 : Oid relamId;
22467 : 101 : Oid namespaceId;
22468 : 101 : AlteredTableInfo *new_partrel_tab;
22469 : 101 : Form_pg_class parent_relform = parent_rel->rd_rel;
22470 : :
22471 : : /* If the existing rel is temp, it must belong to this session. */
22472 [ + + + - ]: 101 : if (RELATION_IS_OTHER_TEMP(parent_rel))
22473 [ # # # # ]: 0 : ereport(ERROR,
22474 : : errcode(ERRCODE_WRONG_OBJECT_TYPE),
22475 : : errmsg("cannot create as partition of temporary relation of another session"));
22476 : :
22477 : : /* Look up inheritance ancestors and generate the relation schema. */
22478 : 101 : colList = getAttributesList(parent_rel);
22479 : :
22480 : : /* Create a tuple descriptor from the relation schema. */
22481 : 101 : descriptor = BuildDescForRelation(colList);
22482 : :
22483 : : /* Look up the access method for the new relation. */
22484 [ + + ]: 101 : relamId = (parent_relform->relam != InvalidOid) ? parent_relform->relam : HEAP_TABLE_AM_OID;
22485 : :
22486 : : /* Look up the namespace in which we are supposed to create the relation. */
22487 : 101 : namespaceId =
22488 : 101 : RangeVarGetAndCheckCreationNamespace(newPartName, NoLock, &existingRelid);
22489 [ + - ]: 101 : if (OidIsValid(existingRelid))
22490 [ # # # # ]: 0 : ereport(ERROR,
22491 : : errcode(ERRCODE_DUPLICATE_TABLE),
22492 : : errmsg("relation \"%s\" already exists", newPartName->relname));
22493 : :
22494 : : /*
22495 : : * We intended to create the partition with the same persistence as the
22496 : : * parent table, but we still need to recheck because that might be
22497 : : * affected by the search_path. If the parent is permanent, so must be
22498 : : * all of its partitions.
22499 : : */
22500 [ + + + + ]: 101 : if (parent_relform->relpersistence != RELPERSISTENCE_TEMP &&
22501 : 92 : newPartName->relpersistence == RELPERSISTENCE_TEMP)
22502 [ + - + - ]: 2 : ereport(ERROR,
22503 : : errcode(ERRCODE_WRONG_OBJECT_TYPE),
22504 : : errmsg("cannot create a temporary relation as partition of permanent relation \"%s\"",
22505 : : RelationGetRelationName(parent_rel)));
22506 : :
22507 : : /* Permanent rels cannot be partitions belonging to a temporary parent. */
22508 [ + + + + ]: 99 : if (newPartName->relpersistence != RELPERSISTENCE_TEMP &&
22509 : 93 : parent_relform->relpersistence == RELPERSISTENCE_TEMP)
22510 [ + - + - ]: 3 : ereport(ERROR,
22511 : : errcode(ERRCODE_WRONG_OBJECT_TYPE),
22512 : : errmsg("cannot create a permanent relation as partition of temporary relation \"%s\"",
22513 : : RelationGetRelationName(parent_rel)));
22514 : :
22515 : : /* Create the relation. */
22516 : 192 : newRelId = heap_create_with_catalog(newPartName->relname,
22517 : 96 : namespaceId,
22518 : 96 : parent_relform->reltablespace,
22519 : : InvalidOid,
22520 : : InvalidOid,
22521 : : InvalidOid,
22522 : 96 : ownerId,
22523 : 96 : relamId,
22524 : 96 : descriptor,
22525 : : NIL,
22526 : : RELKIND_RELATION,
22527 : 96 : newPartName->relpersistence,
22528 : : false,
22529 : : false,
22530 : : ONCOMMIT_NOOP,
22531 : : (Datum) 0,
22532 : : true,
22533 : 96 : allowSystemTableMods,
22534 : : true,
22535 : : InvalidOid,
22536 : : NULL);
22537 : :
22538 : : /*
22539 : : * We must bump the command counter to make the newly-created relation
22540 : : * tuple visible for opening.
22541 : : */
22542 : 96 : CommandCounterIncrement();
22543 : :
22544 : : /*
22545 : : * Open the new partition with no lock, because we already have an
22546 : : * AccessExclusiveLock placed there after creation.
22547 : : */
22548 : 96 : newRel = table_open(newRelId, NoLock);
22549 : :
22550 : : /* Find or create a work queue entry for the newly created table. */
22551 : 96 : new_partrel_tab = ATGetQueueEntry(wqueue, newRel);
22552 : :
22553 : : /* Create constraints, default values, and generated values. */
22554 : 96 : createTableConstraints(wqueue, new_partrel_tab, parent_rel, newRel);
22555 : :
22556 : : /*
22557 : : * Need to call CommandCounterIncrement, so a fresh relcache entry has
22558 : : * newly installed constraint info.
22559 : : */
22560 : 96 : CommandCounterIncrement();
22561 : :
22562 : 192 : return newRel;
22563 : 96 : }
22564 : :
22565 : : /*
22566 : : * MergePartitionsMoveRows: scan partitions to be merged (mergingPartitions)
22567 : : * of the partitioned table and move rows into the new partition
22568 : : * (newPartRel). We also verify check constraints against these rows.
22569 : : */
22570 : : static void
22571 : 19 : MergePartitionsMoveRows(List **wqueue, List *mergingPartitions, Relation newPartRel)
22572 : : {
22573 : 19 : CommandId mycid;
22574 : 19 : EState *estate;
22575 : 19 : AlteredTableInfo *tab;
22576 : 19 : ListCell *ltab;
22577 : :
22578 : : /* The FSM is empty, so don't bother using it. */
22579 : 19 : int ti_options = TABLE_INSERT_SKIP_FSM;
22580 : 19 : BulkInsertState bistate; /* state of bulk inserts for partition */
22581 : 19 : TupleTableSlot *dstslot;
22582 : :
22583 : : /* Find the work queue entry for the new partition table: newPartRel. */
22584 : 19 : tab = ATGetQueueEntry(wqueue, newPartRel);
22585 : :
22586 : : /* Generate the constraint and default execution states. */
22587 : 19 : estate = CreateExecutorState();
22588 : :
22589 : 19 : buildExpressionExecutionStates(tab, newPartRel, estate);
22590 : :
22591 : 19 : mycid = GetCurrentCommandId(true);
22592 : :
22593 : : /* Prepare a BulkInsertState for table_tuple_insert. */
22594 : 19 : bistate = GetBulkInsertState();
22595 : :
22596 : : /* Create the necessary tuple slot. */
22597 : 19 : dstslot = table_slot_create(newPartRel, NULL);
22598 : :
22599 [ + + + - : 83 : foreach_oid(merging_oid, mergingPartitions)
+ + + + ]
22600 : : {
22601 : 45 : ExprContext *econtext;
22602 : 45 : TupleTableSlot *srcslot;
22603 : 45 : TupleConversionMap *tuple_map;
22604 : 45 : TableScanDesc scan;
22605 : 45 : MemoryContext oldCxt;
22606 : 45 : Snapshot snapshot;
22607 : 45 : Relation mergingPartition;
22608 : :
22609 [ + + ]: 45 : econtext = GetPerTupleExprContext(estate);
22610 : :
22611 : : /*
22612 : : * Partition is already locked in the transformPartitionCmdForMerge
22613 : : * function.
22614 : : */
22615 : 45 : mergingPartition = table_open(merging_oid, NoLock);
22616 : :
22617 : : /* Create a source tuple slot for the partition being merged. */
22618 : 45 : srcslot = table_slot_create(mergingPartition, NULL);
22619 : :
22620 : : /*
22621 : : * Map computing for moving attributes of the merged partition to the
22622 : : * new partition.
22623 : : */
22624 : 90 : tuple_map = convert_tuples_by_name(RelationGetDescr(mergingPartition),
22625 : 45 : RelationGetDescr(newPartRel));
22626 : :
22627 : : /* Scan through the rows. */
22628 : 45 : snapshot = RegisterSnapshot(GetLatestSnapshot());
22629 : 45 : scan = table_beginscan(mergingPartition, snapshot, 0, NULL);
22630 : :
22631 : : /*
22632 : : * Switch to per-tuple memory context and reset it for each tuple
22633 : : * produced, so we don't leak memory.
22634 : : */
22635 [ + - ]: 45 : oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
22636 : :
22637 [ + + ]: 97 : while (table_scan_getnextslot(scan, ForwardScanDirection, srcslot))
22638 : : {
22639 : 52 : TupleTableSlot *insertslot;
22640 : :
22641 [ + - ]: 52 : CHECK_FOR_INTERRUPTS();
22642 : :
22643 [ + + ]: 52 : if (tuple_map)
22644 : : {
22645 : : /* Need to use a map to copy attributes. */
22646 : 7 : insertslot = execute_attr_map_slot(tuple_map->attrMap, srcslot, dstslot);
22647 : 7 : }
22648 : : else
22649 : : {
22650 : 45 : slot_getallattrs(srcslot);
22651 : :
22652 : : /* Copy attributes directly. */
22653 : 45 : insertslot = dstslot;
22654 : :
22655 : 45 : ExecClearTuple(insertslot);
22656 : :
22657 : 45 : memcpy(insertslot->tts_values, srcslot->tts_values,
22658 : : sizeof(Datum) * srcslot->tts_nvalid);
22659 : 45 : memcpy(insertslot->tts_isnull, srcslot->tts_isnull,
22660 : : sizeof(bool) * srcslot->tts_nvalid);
22661 : :
22662 : 45 : ExecStoreVirtualTuple(insertslot);
22663 : : }
22664 : :
22665 : : /*
22666 : : * Constraints and GENERATED expressions might reference the
22667 : : * tableoid column, so fill tts_tableOid with the desired value.
22668 : : * (We must do this each time, because it gets overwritten with
22669 : : * newrel's OID during storing.)
22670 : : */
22671 : 52 : insertslot->tts_tableOid = RelationGetRelid(newPartRel);
22672 : :
22673 : : /*
22674 : : * Now, evaluate any generated expressions whose inputs come from
22675 : : * the new tuple. We assume these columns won't reference each
22676 : : * other, so that there's no ordering dependency.
22677 : : */
22678 : 104 : evaluateGeneratedExpressionsAndCheckConstraints(tab, newPartRel,
22679 : 52 : insertslot, econtext);
22680 : :
22681 : : /* Write the tuple out to the new relation. */
22682 : 104 : table_tuple_insert(newPartRel, insertslot, mycid,
22683 : 52 : ti_options, bistate);
22684 : :
22685 : 52 : ResetExprContext(econtext);
22686 : 52 : }
22687 : :
22688 : 45 : MemoryContextSwitchTo(oldCxt);
22689 : 45 : table_endscan(scan);
22690 : 45 : UnregisterSnapshot(snapshot);
22691 : :
22692 [ + + ]: 45 : if (tuple_map)
22693 : 5 : free_conversion_map(tuple_map);
22694 : :
22695 : 45 : ExecDropSingleTupleTableSlot(srcslot);
22696 : 45 : table_close(mergingPartition, NoLock);
22697 : 64 : }
22698 : :
22699 : 19 : FreeExecutorState(estate);
22700 : 19 : ExecDropSingleTupleTableSlot(dstslot);
22701 : 19 : FreeBulkInsertState(bistate);
22702 : :
22703 : 19 : table_finish_bulk_insert(newPartRel, ti_options);
22704 : :
22705 : : /*
22706 : : * We don't need to process this newPartRel since we already processed it
22707 : : * here, so delete the ALTER TABLE queue for it.
22708 : : */
22709 [ + - - + : 57 : foreach(ltab, *wqueue)
+ - ]
22710 : : {
22711 : 38 : tab = (AlteredTableInfo *) lfirst(ltab);
22712 [ + + ]: 38 : if (tab->relid == RelationGetRelid(newPartRel))
22713 : : {
22714 : 19 : *wqueue = list_delete_cell(*wqueue, ltab);
22715 : 19 : break;
22716 : : }
22717 : 19 : }
22718 : 19 : }
22719 : :
22720 : : /*
22721 : : * detachPartitionTable: detach partition "child_rel" from partitioned table
22722 : : * "parent_rel" with default partition identifier "defaultPartOid"
22723 : : */
22724 : : static void
22725 : 83 : detachPartitionTable(Relation parent_rel, Relation child_rel, Oid defaultPartOid)
22726 : : {
22727 : : /* Remove the pg_inherits row first. */
22728 : 83 : RemoveInheritance(child_rel, parent_rel, false);
22729 : :
22730 : : /*
22731 : : * Detaching the partition might involve TOAST table access, so ensure we
22732 : : * have a valid snapshot.
22733 : : */
22734 : 83 : PushActiveSnapshot(GetTransactionSnapshot());
22735 : :
22736 : : /* Do the final part of detaching. */
22737 : 83 : DetachPartitionFinalize(parent_rel, child_rel, false, defaultPartOid);
22738 : :
22739 : 83 : PopActiveSnapshot();
22740 : 83 : }
22741 : :
22742 : : /*
22743 : : * ALTER TABLE <name> MERGE PARTITIONS <partition-list> INTO <partition-name>
22744 : : */
22745 : : static void
22746 : 21 : ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
22747 : : PartitionCmd *cmd, AlterTableUtilityContext *context)
22748 : : {
22749 : 21 : Relation newPartRel;
22750 : 21 : List *mergingPartitions = NIL;
22751 : 21 : Oid defaultPartOid;
22752 : 21 : Oid existingRelid;
22753 : 21 : Oid ownerId = InvalidOid;
22754 : 21 : Oid save_userid;
22755 : 21 : int save_sec_context;
22756 : 21 : int save_nestlevel;
22757 : :
22758 : : /*
22759 : : * Check ownership of merged partitions - partitions with different owners
22760 : : * cannot be merged. Also, collect the OIDs of these partitions during the
22761 : : * check.
22762 : : */
22763 [ + + + - : 105 : foreach_node(RangeVar, name, cmd->partlist)
+ + + + ]
22764 : : {
22765 : 60 : Relation mergingPartition;
22766 : :
22767 : : /*
22768 : : * We are going to detach and remove this partition. We already took
22769 : : * AccessExclusiveLock lock on transformPartitionCmdForMerge, so here,
22770 : : * NoLock is fine.
22771 : : */
22772 : 60 : mergingPartition = table_openrv_extended(name, NoLock, false);
22773 [ + - ]: 60 : Assert(CheckRelationLockedByMe(mergingPartition, AccessExclusiveLock, false));
22774 : :
22775 [ + + ]: 60 : if (OidIsValid(ownerId))
22776 : : {
22777 : : /* Do the partitions being merged have different owners? */
22778 [ + + ]: 34 : if (ownerId != mergingPartition->rd_rel->relowner)
22779 [ + - + - ]: 1 : ereport(ERROR,
22780 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
22781 : : errmsg("partitions being merged have different owners"));
22782 : 33 : }
22783 : : else
22784 : 26 : ownerId = mergingPartition->rd_rel->relowner;
22785 : :
22786 : : /* Store the next merging partition into the list. */
22787 : 118 : mergingPartitions = lappend_oid(mergingPartitions,
22788 : 59 : RelationGetRelid(mergingPartition));
22789 : :
22790 : 59 : table_close(mergingPartition, NoLock);
22791 : 84 : }
22792 : :
22793 : : /* Look up the existing relation by the new partition name. */
22794 : 20 : RangeVarGetAndCheckCreationNamespace(cmd->name, NoLock, &existingRelid);
22795 : :
22796 : : /*
22797 : : * Check if this name is already taken. This helps us to detect the
22798 : : * situation when one of the merging partitions has the same name as the
22799 : : * new partition. Otherwise, this would fail later on anyway, but
22800 : : * catching this here allows us to emit a nicer error message.
22801 : : */
22802 [ + + ]: 20 : if (OidIsValid(existingRelid))
22803 : : {
22804 [ + + ]: 4 : if (list_member_oid(mergingPartitions, existingRelid))
22805 : : {
22806 : : /*
22807 : : * The new partition has the same name as one of the merging
22808 : : * partitions.
22809 : : */
22810 : 3 : char tmpRelName[NAMEDATALEN];
22811 : :
22812 : : /* Generate a temporary name. */
22813 : 3 : sprintf(tmpRelName, "merge-%u-%X-tmp", RelationGetRelid(rel), MyProcPid);
22814 : :
22815 : : /*
22816 : : * Rename the existing partition with a temporary name, leaving it
22817 : : * free for the new partition. We don't need to care about this
22818 : : * in the future because we're going to eventually drop the
22819 : : * existing partition anyway.
22820 : : */
22821 : 3 : RenameRelationInternal(existingRelid, tmpRelName, true, false);
22822 : :
22823 : : /*
22824 : : * We must bump the command counter to make the new partition
22825 : : * tuple visible for rename.
22826 : : */
22827 : 3 : CommandCounterIncrement();
22828 : 3 : }
22829 : : else
22830 : : {
22831 [ + - + - ]: 1 : ereport(ERROR,
22832 : : errcode(ERRCODE_DUPLICATE_TABLE),
22833 : : errmsg("relation \"%s\" already exists", cmd->name->relname));
22834 : : }
22835 : 3 : }
22836 : :
22837 : 19 : defaultPartOid =
22838 : 19 : get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
22839 : :
22840 : : /* Detach all merging partitions. */
22841 [ + + + - : 98 : foreach_oid(mergingPartitionOid, mergingPartitions)
+ + + + ]
22842 : : {
22843 : 55 : Relation child_rel;
22844 : :
22845 : 55 : child_rel = table_open(mergingPartitionOid, NoLock);
22846 : :
22847 : 55 : detachPartitionTable(rel, child_rel, defaultPartOid);
22848 : :
22849 : 55 : table_close(child_rel, NoLock);
22850 : 79 : }
22851 : :
22852 : : /*
22853 : : * Perform a preliminary check to determine whether it's safe to drop all
22854 : : * merging partitions before we actually do so later. After merging rows
22855 : : * into the new partitions via MergePartitionsMoveRows, all old partitions
22856 : : * need to be dropped. However, since the drop behavior is DROP_RESTRICT
22857 : : * and the merge process (MergePartitionsMoveRows) can be time-consuming,
22858 : : * performing an early check on the drop eligibility of old partitions is
22859 : : * preferable.
22860 : : */
22861 [ + + + - : 96 : foreach_oid(mergingPartitionOid, mergingPartitions)
+ + + + ]
22862 : : {
22863 : 53 : ObjectAddress object;
22864 : :
22865 : : /* Get oid of the later to be dropped relation. */
22866 : 53 : object.objectId = mergingPartitionOid;
22867 : 53 : object.classId = RelationRelationId;
22868 : 53 : object.objectSubId = 0;
22869 : :
22870 : 53 : performDeletionCheck(&object, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
22871 : 77 : }
22872 : :
22873 : : /*
22874 : : * Create a table for the new partition, using the partitioned table as a
22875 : : * model.
22876 : : */
22877 [ + - ]: 19 : Assert(OidIsValid(ownerId));
22878 : 19 : newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
22879 : :
22880 : : /*
22881 : : * Switch to the table owner's userid, so that any index functions are run
22882 : : * as that user. Also, lockdown security-restricted operations and
22883 : : * arrange to make GUC variable changes local to this command.
22884 : : *
22885 : : * Need to do it after determining the namespace in the
22886 : : * createPartitionTable() call.
22887 : : */
22888 : 19 : GetUserIdAndSecContext(&save_userid, &save_sec_context);
22889 : 38 : SetUserIdAndSecContext(ownerId,
22890 : 19 : save_sec_context | SECURITY_RESTRICTED_OPERATION);
22891 : 19 : save_nestlevel = NewGUCNestLevel();
22892 : 19 : RestrictSearchPath();
22893 : :
22894 : : /* Copy data from merged partitions to the new partition. */
22895 : 19 : MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
22896 : :
22897 : : /* Drop the current partitions before attaching the new one. */
22898 [ + + + - : 83 : foreach_oid(mergingPartitionOid, mergingPartitions)
+ + + + ]
22899 : : {
22900 : 45 : ObjectAddress object;
22901 : :
22902 : 45 : object.objectId = mergingPartitionOid;
22903 : 45 : object.classId = RelationRelationId;
22904 : 45 : object.objectSubId = 0;
22905 : :
22906 : 45 : performDeletion(&object, DROP_RESTRICT, 0);
22907 : 64 : }
22908 : :
22909 : 19 : list_free(mergingPartitions);
22910 : :
22911 : : /*
22912 : : * Attach a new partition to the partitioned table. wqueue = NULL:
22913 : : * verification for each cloned constraint is not needed.
22914 : : */
22915 : 19 : attachPartitionTable(NULL, rel, newPartRel, cmd->bound);
22916 : :
22917 : : /* Keep the lock until commit. */
22918 : 19 : table_close(newPartRel, NoLock);
22919 : :
22920 : : /* Roll back any GUC changes executed by index functions. */
22921 : 19 : AtEOXact_GUC(false, save_nestlevel);
22922 : :
22923 : : /* Restore the userid and security context. */
22924 : 19 : SetUserIdAndSecContext(save_userid, save_sec_context);
22925 : 19 : }
22926 : :
22927 : : /*
22928 : : * Struct with the context of the new partition for inserting rows from the
22929 : : * split partition.
22930 : : */
22931 : : typedef struct SplitPartitionContext
22932 : : {
22933 : : ExprState *partqualstate; /* expression for checking a slot for a
22934 : : * partition (NULL for DEFAULT partition) */
22935 : : BulkInsertState bistate; /* state of bulk inserts for partition */
22936 : : TupleTableSlot *dstslot; /* slot for inserting row into partition */
22937 : : AlteredTableInfo *tab; /* structure with generated column expressions
22938 : : * and check constraint expressions. */
22939 : : Relation partRel; /* relation for partition */
22940 : : } SplitPartitionContext;
22941 : :
22942 : : /*
22943 : : * createSplitPartitionContext: create context for partition and fill it
22944 : : */
22945 : : static SplitPartitionContext *
22946 : 77 : createSplitPartitionContext(Relation partRel)
22947 : : {
22948 : 77 : SplitPartitionContext *pc;
22949 : :
22950 : 77 : pc = palloc0_object(SplitPartitionContext);
22951 : 77 : pc->partRel = partRel;
22952 : :
22953 : : /*
22954 : : * Prepare a BulkInsertState for table_tuple_insert. The FSM is empty, so
22955 : : * don't bother using it.
22956 : : */
22957 : 77 : pc->bistate = GetBulkInsertState();
22958 : :
22959 : : /* Create a destination tuple slot for the new partition. */
22960 : 77 : pc->dstslot = table_slot_create(pc->partRel, NULL);
22961 : :
22962 : 154 : return pc;
22963 : 77 : }
22964 : :
22965 : : /*
22966 : : * deleteSplitPartitionContext: delete context for partition
22967 : : */
22968 : : static void
22969 : 77 : deleteSplitPartitionContext(SplitPartitionContext *pc, List **wqueue, int ti_options)
22970 : : {
22971 : 77 : ListCell *ltab;
22972 : :
22973 : 77 : ExecDropSingleTupleTableSlot(pc->dstslot);
22974 : 77 : FreeBulkInsertState(pc->bistate);
22975 : :
22976 : 77 : table_finish_bulk_insert(pc->partRel, ti_options);
22977 : :
22978 : : /*
22979 : : * We don't need to process this pc->partRel so delete the ALTER TABLE
22980 : : * queue of it.
22981 : : */
22982 [ + - - + : 231 : foreach(ltab, *wqueue)
+ - ]
22983 : : {
22984 : 154 : AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
22985 : :
22986 [ + + ]: 154 : if (tab->relid == RelationGetRelid(pc->partRel))
22987 : : {
22988 : 77 : *wqueue = list_delete_cell(*wqueue, ltab);
22989 : 77 : break;
22990 : : }
22991 [ + + ]: 154 : }
22992 : :
22993 : 77 : pfree(pc);
22994 : 77 : }
22995 : :
22996 : : /*
22997 : : * SplitPartitionMoveRows: scan split partition (splitRel) of partitioned table
22998 : : * (rel) and move rows into new partitions.
22999 : : *
23000 : : * New partitions description:
23001 : : * partlist: list of pointers to SinglePartitionSpec structures. It contains
23002 : : * the partition specification details for all new partitions.
23003 : : * newPartRels: list of Relations, new partitions created in
23004 : : * ATExecSplitPartition.
23005 : : */
23006 : : static void
23007 : 27 : SplitPartitionMoveRows(List **wqueue, Relation rel, Relation splitRel,
23008 : : List *partlist, List *newPartRels)
23009 : : {
23010 : : /* The FSM is empty, so don't bother using it. */
23011 : 27 : int ti_options = TABLE_INSERT_SKIP_FSM;
23012 : 27 : CommandId mycid;
23013 : 27 : EState *estate;
23014 : 27 : ListCell *listptr,
23015 : : *listptr2;
23016 : 27 : TupleTableSlot *srcslot;
23017 : 27 : ExprContext *econtext;
23018 : 27 : TableScanDesc scan;
23019 : 27 : Snapshot snapshot;
23020 : 27 : MemoryContext oldCxt;
23021 : 27 : List *partContexts = NIL;
23022 : 27 : TupleConversionMap *tuple_map;
23023 : 27 : SplitPartitionContext *defaultPartCtx = NULL,
23024 : : *pc;
23025 : :
23026 : 27 : mycid = GetCurrentCommandId(true);
23027 : :
23028 : 27 : estate = CreateExecutorState();
23029 : :
23030 [ + - + + : 104 : forboth(listptr, partlist, listptr2, newPartRels)
+ - + + +
+ + + ]
23031 : : {
23032 : 77 : SinglePartitionSpec *sps = (SinglePartitionSpec *) lfirst(listptr);
23033 : :
23034 : 77 : pc = createSplitPartitionContext((Relation) lfirst(listptr2));
23035 : :
23036 : : /* Find the work queue entry for the new partition table: newPartRel. */
23037 : 77 : pc->tab = ATGetQueueEntry(wqueue, pc->partRel);
23038 : :
23039 : 77 : buildExpressionExecutionStates(pc->tab, pc->partRel, estate);
23040 : :
23041 [ + + ]: 77 : if (sps->bound->is_default)
23042 : : {
23043 : : /*
23044 : : * We should not create a structure to check the partition
23045 : : * constraint for the new DEFAULT partition.
23046 : : */
23047 : 7 : defaultPartCtx = pc;
23048 : 7 : }
23049 : : else
23050 : : {
23051 : 70 : List *partConstraint;
23052 : :
23053 : : /* Build expression execution states for partition check quals. */
23054 : 70 : partConstraint = get_qual_from_partbound(rel, sps->bound);
23055 : 70 : partConstraint =
23056 : 70 : (List *) eval_const_expressions(NULL,
23057 : 70 : (Node *) partConstraint);
23058 : : /* Make a boolean expression for ExecCheck(). */
23059 : 70 : partConstraint = list_make1(make_ands_explicit(partConstraint));
23060 : :
23061 : : /*
23062 : : * Map the vars in the constraint expression from rel's attnos to
23063 : : * splitRel's.
23064 : : */
23065 : 140 : partConstraint = map_partition_varattnos(partConstraint,
23066 : 70 : 1, splitRel, rel);
23067 : :
23068 : 70 : pc->partqualstate =
23069 : 70 : ExecPrepareExpr((Expr *) linitial(partConstraint), estate);
23070 [ - + ]: 70 : Assert(pc->partqualstate != NULL);
23071 : 70 : }
23072 : :
23073 : : /* Store partition context into a list. */
23074 : 77 : partContexts = lappend(partContexts, pc);
23075 : 77 : }
23076 : :
23077 [ - + ]: 27 : econtext = GetPerTupleExprContext(estate);
23078 : :
23079 : : /* Create the necessary tuple slot. */
23080 : 27 : srcslot = table_slot_create(splitRel, NULL);
23081 : :
23082 : : /*
23083 : : * Map computing for moving attributes of the split partition to the new
23084 : : * partition (for the first new partition, but other new partitions can
23085 : : * use the same map).
23086 : : */
23087 : 27 : pc = (SplitPartitionContext *) lfirst(list_head(partContexts));
23088 : 54 : tuple_map = convert_tuples_by_name(RelationGetDescr(splitRel),
23089 : 27 : RelationGetDescr(pc->partRel));
23090 : :
23091 : : /* Scan through the rows. */
23092 : 27 : snapshot = RegisterSnapshot(GetLatestSnapshot());
23093 : 27 : scan = table_beginscan(splitRel, snapshot, 0, NULL);
23094 : :
23095 : : /*
23096 : : * Switch to per-tuple memory context and reset it for each tuple
23097 : : * produced, so we don't leak memory.
23098 : : */
23099 [ + - ]: 27 : oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
23100 : :
23101 [ + + ]: 130 : while (table_scan_getnextslot(scan, ForwardScanDirection, srcslot))
23102 : : {
23103 : 103 : bool found = false;
23104 : 103 : TupleTableSlot *insertslot;
23105 : :
23106 [ + - ]: 103 : CHECK_FOR_INTERRUPTS();
23107 : :
23108 : 103 : econtext->ecxt_scantuple = srcslot;
23109 : :
23110 : : /* Search partition for the current slot, srcslot. */
23111 [ + - + + : 363 : foreach(listptr, partContexts)
+ + ]
23112 : : {
23113 : 260 : pc = (SplitPartitionContext *) lfirst(listptr);
23114 : :
23115 : : /* skip DEFAULT partition */
23116 [ + + + + ]: 260 : if (pc->partqualstate && ExecCheck(pc->partqualstate, econtext))
23117 : : {
23118 : 84 : found = true;
23119 : 84 : break;
23120 : : }
23121 : 176 : }
23122 [ + + ]: 103 : if (!found)
23123 : : {
23124 : : /* Use the DEFAULT partition if it exists. */
23125 [ + - ]: 19 : if (defaultPartCtx)
23126 : 19 : pc = defaultPartCtx;
23127 : : else
23128 [ # # # # ]: 0 : ereport(ERROR,
23129 : : errcode(ERRCODE_CHECK_VIOLATION),
23130 : : errmsg("can not find partition for split partition row"),
23131 : : errtable(splitRel));
23132 : 19 : }
23133 : :
23134 [ + + ]: 103 : if (tuple_map)
23135 : : {
23136 : : /* Need to use a map to copy attributes. */
23137 : 4 : insertslot = execute_attr_map_slot(tuple_map->attrMap, srcslot, pc->dstslot);
23138 : 4 : }
23139 : : else
23140 : : {
23141 : : /* Extract data from the old tuple. */
23142 : 99 : slot_getallattrs(srcslot);
23143 : :
23144 : : /* Copy attributes directly. */
23145 : 99 : insertslot = pc->dstslot;
23146 : :
23147 : 99 : ExecClearTuple(insertslot);
23148 : :
23149 : 99 : memcpy(insertslot->tts_values, srcslot->tts_values,
23150 : : sizeof(Datum) * srcslot->tts_nvalid);
23151 : 99 : memcpy(insertslot->tts_isnull, srcslot->tts_isnull,
23152 : : sizeof(bool) * srcslot->tts_nvalid);
23153 : :
23154 : 99 : ExecStoreVirtualTuple(insertslot);
23155 : : }
23156 : :
23157 : : /*
23158 : : * Constraints and GENERATED expressions might reference the tableoid
23159 : : * column, so fill tts_tableOid with the desired value. (We must do
23160 : : * this each time, because it gets overwritten with newrel's OID
23161 : : * during storing.)
23162 : : */
23163 : 103 : insertslot->tts_tableOid = RelationGetRelid(pc->partRel);
23164 : :
23165 : : /*
23166 : : * Now, evaluate any generated expressions whose inputs come from the
23167 : : * new tuple. We assume these columns won't reference each other, so
23168 : : * that there's no ordering dependency.
23169 : : */
23170 : 206 : evaluateGeneratedExpressionsAndCheckConstraints(pc->tab, pc->partRel,
23171 : 103 : insertslot, econtext);
23172 : :
23173 : : /* Write the tuple out to the new relation. */
23174 : 206 : table_tuple_insert(pc->partRel, insertslot, mycid,
23175 : 103 : ti_options, pc->bistate);
23176 : :
23177 : 103 : ResetExprContext(econtext);
23178 : 103 : }
23179 : :
23180 : 27 : MemoryContextSwitchTo(oldCxt);
23181 : :
23182 : 27 : table_endscan(scan);
23183 : 27 : UnregisterSnapshot(snapshot);
23184 : :
23185 [ + + ]: 27 : if (tuple_map)
23186 : 1 : free_conversion_map(tuple_map);
23187 : :
23188 : 27 : ExecDropSingleTupleTableSlot(srcslot);
23189 : :
23190 : 27 : FreeExecutorState(estate);
23191 : :
23192 [ + + + - : 131 : foreach_ptr(SplitPartitionContext, spc, partContexts)
+ + + + ]
23193 : 104 : deleteSplitPartitionContext(spc, wqueue, ti_options);
23194 : 27 : }
23195 : :
23196 : : /*
23197 : : * ALTER TABLE <name> SPLIT PARTITION <partition-name> INTO <partition-list>
23198 : : */
23199 : : static void
23200 : 28 : ATExecSplitPartition(List **wqueue, AlteredTableInfo *tab, Relation rel,
23201 : : PartitionCmd *cmd, AlterTableUtilityContext *context)
23202 : : {
23203 : 28 : Relation splitRel;
23204 : 28 : Oid splitRelOid;
23205 : 28 : ListCell *listptr,
23206 : : *listptr2;
23207 : 28 : bool isSameName = false;
23208 : 28 : char tmpRelName[NAMEDATALEN];
23209 : 28 : List *newPartRels = NIL;
23210 : 28 : ObjectAddress object;
23211 : 28 : Oid defaultPartOid;
23212 : 28 : Oid save_userid;
23213 : 28 : int save_sec_context;
23214 : 28 : int save_nestlevel;
23215 : :
23216 : 28 : defaultPartOid = get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
23217 : :
23218 : : /*
23219 : : * Partition is already locked in the transformPartitionCmdForSplit
23220 : : * function.
23221 : : */
23222 : 28 : splitRel = table_openrv(cmd->name, NoLock);
23223 : :
23224 : 28 : splitRelOid = RelationGetRelid(splitRel);
23225 : :
23226 : : /* Check descriptions of new partitions. */
23227 [ + + + - : 135 : foreach_node(SinglePartitionSpec, sps, cmd->partlist)
+ + + + ]
23228 : : {
23229 : 80 : Oid existingRelid;
23230 : :
23231 : : /* Look up the existing relation by the new partition name. */
23232 : 80 : RangeVarGetAndCheckCreationNamespace(sps->name, NoLock, &existingRelid);
23233 : :
23234 : : /*
23235 : : * This would fail later on anyway if the relation already exists. But
23236 : : * by catching it here, we can emit a nicer error message.
23237 : : */
23238 [ + + - + ]: 80 : if (existingRelid == splitRelOid && !isSameName)
23239 : : /* One new partition can have the same name as a split partition. */
23240 : 7 : isSameName = true;
23241 [ + + ]: 73 : else if (OidIsValid(existingRelid))
23242 [ + - + - ]: 1 : ereport(ERROR,
23243 : : errcode(ERRCODE_DUPLICATE_TABLE),
23244 : : errmsg("relation \"%s\" already exists", sps->name->relname));
23245 : 107 : }
23246 : :
23247 : : /* Detach the split partition. */
23248 : 27 : detachPartitionTable(rel, splitRel, defaultPartOid);
23249 : :
23250 : : /*
23251 : : * Perform a preliminary check to determine whether it's safe to drop the
23252 : : * split partition before we actually do so later. After merging rows into
23253 : : * the new partitions via SplitPartitionMoveRows, all old partitions need
23254 : : * to be dropped. However, since the drop behavior is DROP_RESTRICT and
23255 : : * the merge process (SplitPartitionMoveRows) can be time-consuming,
23256 : : * performing an early check on the drop eligibility of old partitions is
23257 : : * preferable.
23258 : : */
23259 : 27 : object.objectId = splitRelOid;
23260 : 27 : object.classId = RelationRelationId;
23261 : 27 : object.objectSubId = 0;
23262 : 27 : performDeletionCheck(&object, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
23263 : :
23264 : : /*
23265 : : * If a new partition has the same name as the split partition, then we
23266 : : * should rename the split partition to reuse its name.
23267 : : */
23268 [ + + ]: 27 : if (isSameName)
23269 : : {
23270 : : /*
23271 : : * We must bump the command counter to make the split partition tuple
23272 : : * visible for renaming.
23273 : : */
23274 : 7 : CommandCounterIncrement();
23275 : : /* Rename partition. */
23276 : 7 : sprintf(tmpRelName, "split-%u-%X-tmp", RelationGetRelid(rel), MyProcPid);
23277 : 7 : RenameRelationInternal(splitRelOid, tmpRelName, true, false);
23278 : :
23279 : : /*
23280 : : * We must bump the command counter to make the split partition tuple
23281 : : * visible after renaming.
23282 : : */
23283 : 7 : CommandCounterIncrement();
23284 : 7 : }
23285 : :
23286 : : /* Create new partitions (like a split partition), without indexes. */
23287 [ + + + - : 132 : foreach_node(SinglePartitionSpec, sps, cmd->partlist)
+ + + + ]
23288 : : {
23289 : 77 : Relation newPartRel;
23290 : :
23291 : 154 : newPartRel = createPartitionTable(wqueue, sps->name, rel,
23292 : 77 : splitRel->rd_rel->relowner);
23293 : 77 : newPartRels = lappend(newPartRels, newPartRel);
23294 : 105 : }
23295 : :
23296 : : /*
23297 : : * Switch to the table owner's userid, so that any index functions are run
23298 : : * as that user. Also, lockdown security-restricted operations and
23299 : : * arrange to make GUC variable changes local to this command.
23300 : : *
23301 : : * Need to do it after determining the namespace in the
23302 : : * createPartitionTable() call.
23303 : : */
23304 : 27 : GetUserIdAndSecContext(&save_userid, &save_sec_context);
23305 : 54 : SetUserIdAndSecContext(splitRel->rd_rel->relowner,
23306 : 27 : save_sec_context | SECURITY_RESTRICTED_OPERATION);
23307 : 27 : save_nestlevel = NewGUCNestLevel();
23308 : 27 : RestrictSearchPath();
23309 : :
23310 : : /* Copy data from the split partition to the new partitions. */
23311 : 27 : SplitPartitionMoveRows(wqueue, rel, splitRel, cmd->partlist, newPartRels);
23312 : : /* Keep the lock until commit. */
23313 : 27 : table_close(splitRel, NoLock);
23314 : :
23315 : : /* Attach new partitions to the partitioned table. */
23316 [ + - + + : 104 : forboth(listptr, cmd->partlist, listptr2, newPartRels)
+ - + + +
+ + + ]
23317 : : {
23318 : 77 : SinglePartitionSpec *sps = (SinglePartitionSpec *) lfirst(listptr);
23319 : 77 : Relation newPartRel = (Relation) lfirst(listptr2);
23320 : :
23321 : : /*
23322 : : * wqueue = NULL: verification for each cloned constraint is not
23323 : : * needed.
23324 : : */
23325 : 77 : attachPartitionTable(NULL, rel, newPartRel, sps->bound);
23326 : : /* Keep the lock until commit. */
23327 : 77 : table_close(newPartRel, NoLock);
23328 : 77 : }
23329 : :
23330 : : /* Drop the split partition. */
23331 : 27 : object.classId = RelationRelationId;
23332 : 27 : object.objectId = splitRelOid;
23333 : 27 : object.objectSubId = 0;
23334 : : /* Probably DROP_CASCADE is not needed. */
23335 : 27 : performDeletion(&object, DROP_RESTRICT, 0);
23336 : :
23337 : : /* Roll back any GUC changes executed by index functions. */
23338 : 27 : AtEOXact_GUC(false, save_nestlevel);
23339 : :
23340 : : /* Restore the userid and security context. */
23341 : 27 : SetUserIdAndSecContext(save_userid, save_sec_context);
23342 : 27 : }
|