Age Owner Branch data TLA 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-2024, 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_namespace.h"
46 : : #include "catalog/pg_opclass.h"
47 : : #include "catalog/pg_policy.h"
48 : : #include "catalog/pg_rewrite.h"
49 : : #include "catalog/pg_statistic_ext.h"
50 : : #include "catalog/pg_tablespace.h"
51 : : #include "catalog/pg_trigger.h"
52 : : #include "catalog/pg_type.h"
53 : : #include "catalog/storage.h"
54 : : #include "catalog/storage_xlog.h"
55 : : #include "catalog/toasting.h"
56 : : #include "commands/cluster.h"
57 : : #include "commands/comment.h"
58 : : #include "commands/defrem.h"
59 : : #include "commands/event_trigger.h"
60 : : #include "commands/sequence.h"
61 : : #include "commands/tablecmds.h"
62 : : #include "commands/tablespace.h"
63 : : #include "commands/trigger.h"
64 : : #include "commands/typecmds.h"
65 : : #include "commands/user.h"
66 : : #include "commands/vacuum.h"
67 : : #include "executor/executor.h"
68 : : #include "foreign/fdwapi.h"
69 : : #include "foreign/foreign.h"
70 : : #include "miscadmin.h"
71 : : #include "nodes/makefuncs.h"
72 : : #include "nodes/nodeFuncs.h"
73 : : #include "nodes/parsenodes.h"
74 : : #include "optimizer/optimizer.h"
75 : : #include "parser/parse_coerce.h"
76 : : #include "parser/parse_collate.h"
77 : : #include "parser/parse_expr.h"
78 : : #include "parser/parse_relation.h"
79 : : #include "parser/parse_type.h"
80 : : #include "parser/parse_utilcmd.h"
81 : : #include "parser/parser.h"
82 : : #include "partitioning/partbounds.h"
83 : : #include "partitioning/partdesc.h"
84 : : #include "pgstat.h"
85 : : #include "rewrite/rewriteDefine.h"
86 : : #include "rewrite/rewriteHandler.h"
87 : : #include "rewrite/rewriteManip.h"
88 : : #include "storage/bufmgr.h"
89 : : #include "storage/lmgr.h"
90 : : #include "storage/lock.h"
91 : : #include "storage/predicate.h"
92 : : #include "storage/smgr.h"
93 : : #include "tcop/utility.h"
94 : : #include "utils/acl.h"
95 : : #include "utils/builtins.h"
96 : : #include "utils/fmgroids.h"
97 : : #include "utils/inval.h"
98 : : #include "utils/lsyscache.h"
99 : : #include "utils/memutils.h"
100 : : #include "utils/partcache.h"
101 : : #include "utils/relcache.h"
102 : : #include "utils/ruleutils.h"
103 : : #include "utils/snapmgr.h"
104 : : #include "utils/syscache.h"
105 : : #include "utils/timestamp.h"
106 : : #include "utils/typcache.h"
107 : : #include "utils/usercontext.h"
108 : :
109 : : /*
110 : : * ON COMMIT action list
111 : : */
112 : : typedef struct OnCommitItem
113 : : {
114 : : Oid relid; /* relid of relation */
115 : : OnCommitAction oncommit; /* what to do at end of xact */
116 : :
117 : : /*
118 : : * If this entry was created during the current transaction,
119 : : * creating_subid is the ID of the creating subxact; if created in a prior
120 : : * transaction, creating_subid is zero. If deleted during the current
121 : : * transaction, deleting_subid is the ID of the deleting subxact; if no
122 : : * deletion request is pending, deleting_subid is zero.
123 : : */
124 : : SubTransactionId creating_subid;
125 : : SubTransactionId deleting_subid;
126 : : } OnCommitItem;
127 : :
128 : : static List *on_commits = NIL;
129 : :
130 : :
131 : : /*
132 : : * State information for ALTER TABLE
133 : : *
134 : : * The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo
135 : : * structs, one for each table modified by the operation (the named table
136 : : * plus any child tables that are affected). We save lists of subcommands
137 : : * to apply to this table (possibly modified by parse transformation steps);
138 : : * these lists will be executed in Phase 2. If a Phase 3 step is needed,
139 : : * necessary information is stored in the constraints and newvals lists.
140 : : *
141 : : * Phase 2 is divided into multiple passes; subcommands are executed in
142 : : * a pass determined by subcommand type.
143 : : */
144 : :
145 : : typedef enum AlterTablePass
146 : : {
147 : : AT_PASS_UNSET = -1, /* UNSET will cause ERROR */
148 : : AT_PASS_DROP, /* DROP (all flavors) */
149 : : AT_PASS_ALTER_TYPE, /* ALTER COLUMN TYPE */
150 : : AT_PASS_ADD_COL, /* ADD COLUMN */
151 : : AT_PASS_SET_EXPRESSION, /* ALTER SET EXPRESSION */
152 : : AT_PASS_OLD_INDEX, /* re-add existing indexes */
153 : : AT_PASS_OLD_CONSTR, /* re-add existing constraints */
154 : : /* We could support a RENAME COLUMN pass here, but not currently used */
155 : : AT_PASS_ADD_CONSTR, /* ADD constraints (initial examination) */
156 : : AT_PASS_COL_ATTRS, /* set column attributes, eg NOT NULL */
157 : : AT_PASS_ADD_INDEXCONSTR, /* ADD index-based constraints */
158 : : AT_PASS_ADD_INDEX, /* ADD indexes */
159 : : AT_PASS_ADD_OTHERCONSTR, /* ADD other constraints, defaults */
160 : : AT_PASS_MISC, /* other stuff */
161 : : } AlterTablePass;
162 : :
163 : : #define AT_NUM_PASSES (AT_PASS_MISC + 1)
164 : :
165 : : typedef struct AlteredTableInfo
166 : : {
167 : : /* Information saved before any work commences: */
168 : : Oid relid; /* Relation to work on */
169 : : char relkind; /* Its relkind */
170 : : TupleDesc oldDesc; /* Pre-modification tuple descriptor */
171 : :
172 : : /*
173 : : * Transiently set during Phase 2, normally set to NULL.
174 : : *
175 : : * ATRewriteCatalogs sets this when it starts, and closes when ATExecCmd
176 : : * returns control. This can be exploited by ATExecCmd subroutines to
177 : : * close/reopen across transaction boundaries.
178 : : */
179 : : Relation rel;
180 : :
181 : : /* Information saved by Phase 1 for Phase 2: */
182 : : List *subcmds[AT_NUM_PASSES]; /* Lists of AlterTableCmd */
183 : : /* Information saved by Phases 1/2 for Phase 3: */
184 : : List *constraints; /* List of NewConstraint */
185 : : List *newvals; /* List of NewColumnValue */
186 : : List *afterStmts; /* List of utility command parsetrees */
187 : : bool verify_new_notnull; /* T if we should recheck NOT NULL */
188 : : int rewrite; /* Reason for forced rewrite, if any */
189 : : bool chgAccessMethod; /* T if SET ACCESS METHOD is used */
190 : : Oid newAccessMethod; /* new access method; 0 means no change,
191 : : * if above is true */
192 : : Oid newTableSpace; /* new tablespace; 0 means no change */
193 : : bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
194 : : char newrelpersistence; /* if above is true */
195 : : Expr *partition_constraint; /* for attach partition validation */
196 : : /* true, if validating default due to some other attach/detach */
197 : : bool validate_default;
198 : : /* Objects to rebuild after completing ALTER TYPE operations */
199 : : List *changedConstraintOids; /* OIDs of constraints to rebuild */
200 : : List *changedConstraintDefs; /* string definitions of same */
201 : : List *changedIndexOids; /* OIDs of indexes to rebuild */
202 : : List *changedIndexDefs; /* string definitions of same */
203 : : char *replicaIdentityIndex; /* index to reset as REPLICA IDENTITY */
204 : : char *clusterOnIndex; /* index to use for CLUSTER */
205 : : List *changedStatisticsOids; /* OIDs of statistics to rebuild */
206 : : List *changedStatisticsDefs; /* string definitions of same */
207 : : } AlteredTableInfo;
208 : :
209 : : /* Struct describing one new constraint to check in Phase 3 scan */
210 : : /* Note: new not-null constraints are handled elsewhere */
211 : : typedef struct NewConstraint
212 : : {
213 : : char *name; /* Constraint name, or NULL if none */
214 : : ConstrType contype; /* CHECK or FOREIGN */
215 : : Oid refrelid; /* PK rel, if FOREIGN */
216 : : Oid refindid; /* OID of PK's index, if FOREIGN */
217 : : bool conwithperiod; /* Whether the new FOREIGN KEY uses PERIOD */
218 : : Oid conid; /* OID of pg_constraint entry, if FOREIGN */
219 : : Node *qual; /* Check expr or CONSTR_FOREIGN Constraint */
220 : : ExprState *qualstate; /* Execution state for CHECK expr */
221 : : } NewConstraint;
222 : :
223 : : /*
224 : : * Struct describing one new column value that needs to be computed during
225 : : * Phase 3 copy (this could be either a new column with a non-null default, or
226 : : * a column that we're changing the type of). Columns without such an entry
227 : : * are just copied from the old table during ATRewriteTable. Note that the
228 : : * expr is an expression over *old* table values, except when is_generated
229 : : * is true; then it is an expression over columns of the *new* tuple.
230 : : */
231 : : typedef struct NewColumnValue
232 : : {
233 : : AttrNumber attnum; /* which column */
234 : : Expr *expr; /* expression to compute */
235 : : ExprState *exprstate; /* execution state */
236 : : bool is_generated; /* is it a GENERATED expression? */
237 : : } NewColumnValue;
238 : :
239 : : /*
240 : : * Error-reporting support for RemoveRelations
241 : : */
242 : : struct dropmsgstrings
243 : : {
244 : : char kind;
245 : : int nonexistent_code;
246 : : const char *nonexistent_msg;
247 : : const char *skipping_msg;
248 : : const char *nota_msg;
249 : : const char *drophint_msg;
250 : : };
251 : :
252 : : static const struct dropmsgstrings dropmsgstringarray[] = {
253 : : {RELKIND_RELATION,
254 : : ERRCODE_UNDEFINED_TABLE,
255 : : gettext_noop("table \"%s\" does not exist"),
256 : : gettext_noop("table \"%s\" does not exist, skipping"),
257 : : gettext_noop("\"%s\" is not a table"),
258 : : gettext_noop("Use DROP TABLE to remove a table.")},
259 : : {RELKIND_SEQUENCE,
260 : : ERRCODE_UNDEFINED_TABLE,
261 : : gettext_noop("sequence \"%s\" does not exist"),
262 : : gettext_noop("sequence \"%s\" does not exist, skipping"),
263 : : gettext_noop("\"%s\" is not a sequence"),
264 : : gettext_noop("Use DROP SEQUENCE to remove a sequence.")},
265 : : {RELKIND_VIEW,
266 : : ERRCODE_UNDEFINED_TABLE,
267 : : gettext_noop("view \"%s\" does not exist"),
268 : : gettext_noop("view \"%s\" does not exist, skipping"),
269 : : gettext_noop("\"%s\" is not a view"),
270 : : gettext_noop("Use DROP VIEW to remove a view.")},
271 : : {RELKIND_MATVIEW,
272 : : ERRCODE_UNDEFINED_TABLE,
273 : : gettext_noop("materialized view \"%s\" does not exist"),
274 : : gettext_noop("materialized view \"%s\" does not exist, skipping"),
275 : : gettext_noop("\"%s\" is not a materialized view"),
276 : : gettext_noop("Use DROP MATERIALIZED VIEW to remove a materialized view.")},
277 : : {RELKIND_INDEX,
278 : : ERRCODE_UNDEFINED_OBJECT,
279 : : gettext_noop("index \"%s\" does not exist"),
280 : : gettext_noop("index \"%s\" does not exist, skipping"),
281 : : gettext_noop("\"%s\" is not an index"),
282 : : gettext_noop("Use DROP INDEX to remove an index.")},
283 : : {RELKIND_COMPOSITE_TYPE,
284 : : ERRCODE_UNDEFINED_OBJECT,
285 : : gettext_noop("type \"%s\" does not exist"),
286 : : gettext_noop("type \"%s\" does not exist, skipping"),
287 : : gettext_noop("\"%s\" is not a type"),
288 : : gettext_noop("Use DROP TYPE to remove a type.")},
289 : : {RELKIND_FOREIGN_TABLE,
290 : : ERRCODE_UNDEFINED_OBJECT,
291 : : gettext_noop("foreign table \"%s\" does not exist"),
292 : : gettext_noop("foreign table \"%s\" does not exist, skipping"),
293 : : gettext_noop("\"%s\" is not a foreign table"),
294 : : gettext_noop("Use DROP FOREIGN TABLE to remove a foreign table.")},
295 : : {RELKIND_PARTITIONED_TABLE,
296 : : ERRCODE_UNDEFINED_TABLE,
297 : : gettext_noop("table \"%s\" does not exist"),
298 : : gettext_noop("table \"%s\" does not exist, skipping"),
299 : : gettext_noop("\"%s\" is not a table"),
300 : : gettext_noop("Use DROP TABLE to remove a table.")},
301 : : {RELKIND_PARTITIONED_INDEX,
302 : : ERRCODE_UNDEFINED_OBJECT,
303 : : gettext_noop("index \"%s\" does not exist"),
304 : : gettext_noop("index \"%s\" does not exist, skipping"),
305 : : gettext_noop("\"%s\" is not an index"),
306 : : gettext_noop("Use DROP INDEX to remove an index.")},
307 : : {'\0', 0, NULL, NULL, NULL, NULL}
308 : : };
309 : :
310 : : /* communication between RemoveRelations and RangeVarCallbackForDropRelation */
311 : : struct DropRelationCallbackState
312 : : {
313 : : /* These fields are set by RemoveRelations: */
314 : : char expected_relkind;
315 : : LOCKMODE heap_lockmode;
316 : : /* These fields are state to track which subsidiary locks are held: */
317 : : Oid heapOid;
318 : : Oid partParentOid;
319 : : /* These fields are passed back by RangeVarCallbackForDropRelation: */
320 : : char actual_relkind;
321 : : char actual_relpersistence;
322 : : };
323 : :
324 : : /* Alter table target-type flags for ATSimplePermissions */
325 : : #define ATT_TABLE 0x0001
326 : : #define ATT_VIEW 0x0002
327 : : #define ATT_MATVIEW 0x0004
328 : : #define ATT_INDEX 0x0008
329 : : #define ATT_COMPOSITE_TYPE 0x0010
330 : : #define ATT_FOREIGN_TABLE 0x0020
331 : : #define ATT_PARTITIONED_INDEX 0x0040
332 : : #define ATT_SEQUENCE 0x0080
333 : :
334 : : /*
335 : : * ForeignTruncateInfo
336 : : *
337 : : * Information related to truncation of foreign tables. This is used for
338 : : * the elements in a hash table. It uses the server OID as lookup key,
339 : : * and includes a per-server list of all foreign tables involved in the
340 : : * truncation.
341 : : */
342 : : typedef struct ForeignTruncateInfo
343 : : {
344 : : Oid serverid;
345 : : List *rels;
346 : : } ForeignTruncateInfo;
347 : :
348 : : /*
349 : : * Partition tables are expected to be dropped when the parent partitioned
350 : : * table gets dropped. Hence for partitioning we use AUTO dependency.
351 : : * Otherwise, for regular inheritance use NORMAL dependency.
352 : : */
353 : : #define child_dependency_type(child_is_partition) \
354 : : ((child_is_partition) ? DEPENDENCY_AUTO : DEPENDENCY_NORMAL)
355 : :
356 : : static void truncate_check_rel(Oid relid, Form_pg_class reltuple);
357 : : static void truncate_check_perms(Oid relid, Form_pg_class reltuple);
358 : : static void truncate_check_activity(Relation rel);
359 : : static void RangeVarCallbackForTruncate(const RangeVar *relation,
360 : : Oid relId, Oid oldRelId, void *arg);
361 : : static List *MergeAttributes(List *columns, const List *supers, char relpersistence,
362 : : bool is_partition, List **supconstr,
363 : : List **supnotnulls);
364 : : static List *MergeCheckConstraint(List *constraints, const char *name, Node *expr);
365 : : static void MergeChildAttribute(List *inh_columns, int exist_attno, int newcol_attno, const ColumnDef *newdef);
366 : : static ColumnDef *MergeInheritedAttribute(List *inh_columns, int exist_attno, const ColumnDef *newdef);
367 : : static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel, bool ispartition);
368 : : static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
369 : : static void StoreCatalogInheritance(Oid relationId, List *supers,
370 : : bool child_is_partition);
371 : : static void StoreCatalogInheritance1(Oid relationId, Oid parentOid,
372 : : int32 seqNumber, Relation inhRelation,
373 : : bool child_is_partition);
374 : : static int findAttrByName(const char *attributeName, const List *columns);
375 : : static void AlterIndexNamespaces(Relation classRel, Relation rel,
376 : : Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved);
377 : : static void AlterSeqNamespaces(Relation classRel, Relation rel,
378 : : Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved,
379 : : LOCKMODE lockmode);
380 : : static ObjectAddress ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
381 : : bool recurse, bool recursing, LOCKMODE lockmode);
382 : : static bool ATExecAlterConstrRecurse(Constraint *cmdcon, Relation conrel, Relation tgrel,
383 : : Relation rel, HeapTuple contuple, List **otherrelids,
384 : : LOCKMODE lockmode);
385 : : static ObjectAddress ATExecValidateConstraint(List **wqueue,
386 : : Relation rel, char *constrName,
387 : : bool recurse, bool recursing, LOCKMODE lockmode);
388 : : static int transformColumnNameList(Oid relId, List *colList,
389 : : int16 *attnums, Oid *atttypids);
390 : : static int transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
391 : : List **attnamelist,
392 : : int16 *attnums, Oid *atttypids,
393 : : Oid *opclasses, bool *pk_has_without_overlaps);
394 : : static Oid transformFkeyCheckAttrs(Relation pkrel,
395 : : int numattrs, int16 *attnums,
396 : : bool with_period, Oid *opclasses,
397 : : bool *pk_has_without_overlaps);
398 : : static void checkFkeyPermissions(Relation rel, int16 *attnums, int natts);
399 : : static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId,
400 : : Oid *funcid);
401 : : static void validateForeignKeyConstraint(char *conname,
402 : : Relation rel, Relation pkrel,
403 : : Oid pkindOid, Oid constraintOid, bool hasperiod);
404 : : static void ATController(AlterTableStmt *parsetree,
405 : : Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
406 : : AlterTableUtilityContext *context);
407 : : static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
408 : : bool recurse, bool recursing, LOCKMODE lockmode,
409 : : AlterTableUtilityContext *context);
410 : : static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
411 : : AlterTableUtilityContext *context);
412 : : static void ATExecCmd(List **wqueue, AlteredTableInfo *tab,
413 : : AlterTableCmd *cmd, LOCKMODE lockmode, AlterTablePass cur_pass,
414 : : AlterTableUtilityContext *context);
415 : : static AlterTableCmd *ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab,
416 : : Relation rel, AlterTableCmd *cmd,
417 : : bool recurse, LOCKMODE lockmode,
418 : : AlterTablePass cur_pass,
419 : : AlterTableUtilityContext *context);
420 : : static void ATRewriteTables(AlterTableStmt *parsetree,
421 : : List **wqueue, LOCKMODE lockmode,
422 : : AlterTableUtilityContext *context);
423 : : static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode);
424 : : static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
425 : : static void ATSimplePermissions(AlterTableType cmdtype, Relation rel, int allowed_targets);
426 : : static void ATSimpleRecursion(List **wqueue, Relation rel,
427 : : AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
428 : : AlterTableUtilityContext *context);
429 : : static void ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode);
430 : : static void ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
431 : : LOCKMODE lockmode,
432 : : AlterTableUtilityContext *context);
433 : : static List *find_typed_table_dependencies(Oid typeOid, const char *typeName,
434 : : DropBehavior behavior);
435 : : static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
436 : : bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode,
437 : : AlterTableUtilityContext *context);
438 : : static ObjectAddress ATExecAddColumn(List **wqueue, AlteredTableInfo *tab,
439 : : Relation rel, AlterTableCmd **cmd,
440 : : bool recurse, bool recursing,
441 : : LOCKMODE lockmode, AlterTablePass cur_pass,
442 : : AlterTableUtilityContext *context);
443 : : static bool check_for_column_name_collision(Relation rel, const char *colname,
444 : : bool if_not_exists);
445 : : static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
446 : : static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
447 : : static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, bool recurse,
448 : : LOCKMODE lockmode);
449 : : static bool set_attnotnull(List **wqueue, Relation rel,
450 : : AttrNumber attnum, bool recurse, LOCKMODE lockmode);
451 : : static ObjectAddress ATExecSetNotNull(List **wqueue, Relation rel,
452 : : char *constrname, char *colName,
453 : : bool recurse, bool recursing,
454 : : List **readyRels, LOCKMODE lockmode);
455 : : static ObjectAddress ATExecSetAttNotNull(List **wqueue, Relation rel,
456 : : const char *colName, LOCKMODE lockmode);
457 : : static bool NotNullImpliedByRelConstraints(Relation rel, Form_pg_attribute attr);
458 : : static bool ConstraintImpliedByRelConstraint(Relation scanrel,
459 : : List *testConstraint, List *provenConstraint);
460 : : static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
461 : : Node *newDefault, LOCKMODE lockmode);
462 : : static ObjectAddress ATExecCookedColumnDefault(Relation rel, AttrNumber attnum,
463 : : Node *newDefault);
464 : : static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName,
465 : : Node *def, LOCKMODE lockmode, bool recurse, bool recursing);
466 : : static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName,
467 : : Node *def, LOCKMODE lockmode, bool recurse, bool recursing);
468 : : static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode,
469 : : bool recurse, bool recursing);
470 : : static ObjectAddress ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
471 : : Node *newExpr, LOCKMODE lockmode);
472 : : static void ATPrepDropExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, LOCKMODE lockmode);
473 : : static ObjectAddress ATExecDropExpression(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode);
474 : : static ObjectAddress ATExecSetStatistics(Relation rel, const char *colName, int16 colNum,
475 : : Node *newValue, LOCKMODE lockmode);
476 : : static ObjectAddress ATExecSetOptions(Relation rel, const char *colName,
477 : : Node *options, bool isReset, LOCKMODE lockmode);
478 : : static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
479 : : Node *newValue, LOCKMODE lockmode);
480 : : static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
481 : : AlterTableCmd *cmd, LOCKMODE lockmode,
482 : : AlterTableUtilityContext *context);
483 : : static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
484 : : DropBehavior behavior,
485 : : bool recurse, bool recursing,
486 : : bool missing_ok, LOCKMODE lockmode,
487 : : ObjectAddresses *addrs);
488 : : static void ATPrepAddPrimaryKey(List **wqueue, Relation rel, AlterTableCmd *cmd,
489 : : LOCKMODE lockmode, AlterTableUtilityContext *context);
490 : : static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
491 : : IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
492 : : static ObjectAddress ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
493 : : CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
494 : : static ObjectAddress ATExecAddConstraint(List **wqueue,
495 : : AlteredTableInfo *tab, Relation rel,
496 : : Constraint *newConstraint, bool recurse, bool is_readd,
497 : : LOCKMODE lockmode);
498 : : static char *ChooseForeignKeyConstraintNameAddition(List *colnames);
499 : : static ObjectAddress ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
500 : : IndexStmt *stmt, LOCKMODE lockmode);
501 : : static ObjectAddress ATAddCheckNNConstraint(List **wqueue,
502 : : AlteredTableInfo *tab, Relation rel,
503 : : Constraint *constr,
504 : : bool recurse, bool recursing, bool is_readd,
505 : : LOCKMODE lockmode);
506 : : static ObjectAddress ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab,
507 : : Relation rel, Constraint *fkconstraint,
508 : : bool recurse, bool recursing,
509 : : LOCKMODE lockmode);
510 : : static ObjectAddress addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint,
511 : : Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
512 : : int numfks, int16 *pkattnum, int16 *fkattnum,
513 : : Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
514 : : int numfkdelsetcols, int16 *fkdelsetcols,
515 : : bool old_check_ok,
516 : : Oid parentDelTrigger, Oid parentUpdTrigger,
517 : : bool with_period);
518 : : static void validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
519 : : int numfksetcols, const int16 *fksetcolsattnums,
520 : : List *fksetcols);
521 : : static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint,
522 : : Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
523 : : int numfks, int16 *pkattnum, int16 *fkattnum,
524 : : Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
525 : : int numfkdelsetcols, int16 *fkdelsetcols,
526 : : bool old_check_ok, LOCKMODE lockmode,
527 : : Oid parentInsTrigger, Oid parentUpdTrigger,
528 : : bool with_period);
529 : :
530 : : static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
531 : : Relation partitionRel);
532 : : static void CloneFkReferenced(Relation parentRel, Relation partitionRel);
533 : : static void CloneFkReferencing(List **wqueue, Relation parentRel,
534 : : Relation partRel);
535 : : static void createForeignKeyCheckTriggers(Oid myRelOid, Oid refRelOid,
536 : : Constraint *fkconstraint, Oid constraintOid,
537 : : Oid indexOid,
538 : : Oid parentInsTrigger, Oid parentUpdTrigger,
539 : : Oid *insertTrigOid, Oid *updateTrigOid);
540 : : static void createForeignKeyActionTriggers(Relation rel, Oid refRelOid,
541 : : Constraint *fkconstraint, Oid constraintOid,
542 : : Oid indexOid,
543 : : Oid parentDelTrigger, Oid parentUpdTrigger,
544 : : Oid *deleteTrigOid, Oid *updateTrigOid);
545 : : static bool tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
546 : : Oid partRelid,
547 : : Oid parentConstrOid, int numfks,
548 : : AttrNumber *mapped_conkey, AttrNumber *confkey,
549 : : Oid *conpfeqop,
550 : : Oid parentInsTrigger,
551 : : Oid parentUpdTrigger,
552 : : Relation trigrel);
553 : : static void GetForeignKeyActionTriggers(Relation trigrel,
554 : : Oid conoid, Oid confrelid, Oid conrelid,
555 : : Oid *deleteTriggerOid,
556 : : Oid *updateTriggerOid);
557 : : static void GetForeignKeyCheckTriggers(Relation trigrel,
558 : : Oid conoid, Oid confrelid, Oid conrelid,
559 : : Oid *insertTriggerOid,
560 : : Oid *updateTriggerOid);
561 : : static void ATExecDropConstraint(Relation rel, const char *constrName,
562 : : DropBehavior behavior, bool recurse,
563 : : bool missing_ok, LOCKMODE lockmode);
564 : : static ObjectAddress dropconstraint_internal(Relation rel,
565 : : HeapTuple constraintTup, DropBehavior behavior,
566 : : bool recurse, bool recursing,
567 : : bool missing_ok, List **readyRels,
568 : : LOCKMODE lockmode);
569 : : static void ATPrepAlterColumnType(List **wqueue,
570 : : AlteredTableInfo *tab, Relation rel,
571 : : bool recurse, bool recursing,
572 : : AlterTableCmd *cmd, LOCKMODE lockmode,
573 : : AlterTableUtilityContext *context);
574 : : static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno);
575 : : static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
576 : : AlterTableCmd *cmd, LOCKMODE lockmode);
577 : : static void RememberAllDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype,
578 : : Relation rel, AttrNumber attnum, const char *colName);
579 : : static void RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab);
580 : : static void RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab);
581 : : static void RememberStatisticsForRebuilding(Oid stxoid, AlteredTableInfo *tab);
582 : : static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab,
583 : : LOCKMODE lockmode);
584 : : static void ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId,
585 : : char *cmd, List **wqueue, LOCKMODE lockmode,
586 : : bool rewrite);
587 : : static void RebuildConstraintComment(AlteredTableInfo *tab, AlterTablePass pass,
588 : : Oid objid, Relation rel, List *domname,
589 : : const char *conname);
590 : : static void TryReuseIndex(Oid oldId, IndexStmt *stmt);
591 : : static void TryReuseForeignKey(Oid oldId, Constraint *con);
592 : : static ObjectAddress ATExecAlterColumnGenericOptions(Relation rel, const char *colName,
593 : : List *options, LOCKMODE lockmode);
594 : : static void change_owner_fix_column_acls(Oid relationOid,
595 : : Oid oldOwnerId, Oid newOwnerId);
596 : : static void change_owner_recurse_to_sequences(Oid relationOid,
597 : : Oid newOwnerId, LOCKMODE lockmode);
598 : : static ObjectAddress ATExecClusterOn(Relation rel, const char *indexName,
599 : : LOCKMODE lockmode);
600 : : static void ATExecDropCluster(Relation rel, LOCKMODE lockmode);
601 : : static void ATPrepSetAccessMethod(AlteredTableInfo *tab, Relation rel, const char *amname);
602 : : static void ATExecSetAccessMethodNoStorage(Relation rel, Oid newAccessMethod);
603 : : static bool ATPrepChangePersistence(Relation rel, bool toLogged);
604 : : static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
605 : : const char *tablespacename, LOCKMODE lockmode);
606 : : static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
607 : : static void ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace);
608 : : static void ATExecSetRelOptions(Relation rel, List *defList,
609 : : AlterTableType operation,
610 : : LOCKMODE lockmode);
611 : : static void ATExecEnableDisableTrigger(Relation rel, const char *trigname,
612 : : char fires_when, bool skip_system, bool recurse,
613 : : LOCKMODE lockmode);
614 : : static void ATExecEnableDisableRule(Relation rel, const char *rulename,
615 : : char fires_when, LOCKMODE lockmode);
616 : : static void ATPrepAddInherit(Relation child_rel);
617 : : static ObjectAddress ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode);
618 : : static ObjectAddress ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode);
619 : : static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid,
620 : : DependencyType deptype);
621 : : static ObjectAddress ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
622 : : static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
623 : : static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode);
624 : : static void ATExecGenericOptions(Relation rel, List *options);
625 : : static void ATExecSetRowSecurity(Relation rel, bool rls);
626 : : static void ATExecForceNoForceRowSecurity(Relation rel, bool force_rls);
627 : : static ObjectAddress ATExecSetCompression(Relation rel,
628 : : const char *column, Node *newValue, LOCKMODE lockmode);
629 : :
630 : : static void index_copy_data(Relation rel, RelFileLocator newrlocator);
631 : : static const char *storage_name(char c);
632 : :
633 : : static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
634 : : Oid oldRelOid, void *arg);
635 : : static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
636 : : Oid oldrelid, void *arg);
637 : : static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec);
638 : : static void ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNumber *partattrs,
639 : : List **partexprs, Oid *partopclass, Oid *partcollation,
640 : : PartitionStrategy strategy);
641 : : static void CreateInheritance(Relation child_rel, Relation parent_rel, bool ispartition);
642 : : static void RemoveInheritance(Relation child_rel, Relation parent_rel,
643 : : bool expect_detached);
644 : : static void ATInheritAdjustNotNulls(Relation parent_rel, Relation child_rel,
645 : : int inhcount);
646 : : static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
647 : : PartitionCmd *cmd,
648 : : AlterTableUtilityContext *context);
649 : : static void AttachPartitionEnsureIndexes(List **wqueue, Relation rel, Relation attachrel);
650 : : static void QueuePartitionConstraintValidation(List **wqueue, Relation scanrel,
651 : : List *partConstraint,
652 : : bool validate_default);
653 : : static void CloneRowTriggersToPartition(Relation parent, Relation partition);
654 : : static void DetachAddConstraintIfNeeded(List **wqueue, Relation partRel);
655 : : static void DropClonedTriggersFromPartition(Oid partitionId);
656 : : static ObjectAddress ATExecDetachPartition(List **wqueue, AlteredTableInfo *tab,
657 : : Relation rel, RangeVar *name,
658 : : bool concurrent);
659 : : static void DetachPartitionFinalize(Relation rel, Relation partRel,
660 : : bool concurrent, Oid defaultPartOid);
661 : : static ObjectAddress ATExecDetachPartitionFinalize(Relation rel, RangeVar *name);
662 : : static ObjectAddress ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx,
663 : : RangeVar *name);
664 : : static void validatePartitionedIndex(Relation partedIdx, Relation partedTbl);
665 : : static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
666 : : Relation partitionTbl);
667 : : static void verifyPartitionIndexNotNull(IndexInfo *iinfo, Relation partIdx);
668 : : static List *GetParentedForeignKeyRefs(Relation partition);
669 : : static void ATDetachCheckNoForeignKeyRefs(Relation partition);
670 : : static char GetAttributeCompression(Oid atttypid, const char *compression);
671 : : static char GetAttributeStorage(Oid atttypid, const char *storagemode);
672 : :
673 : : static void ATExecSplitPartition(List **wqueue, AlteredTableInfo *tab,
674 : : Relation rel, PartitionCmd *cmd,
675 : : AlterTableUtilityContext *context);
676 : : static void ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
677 : : PartitionCmd *cmd, AlterTableUtilityContext *context);
678 : :
679 : : /* ----------------------------------------------------------------
680 : : * DefineRelation
681 : : * Creates a new relation.
682 : : *
683 : : * stmt carries parsetree information from an ordinary CREATE TABLE statement.
684 : : * The other arguments are used to extend the behavior for other cases:
685 : : * relkind: relkind to assign to the new relation
686 : : * ownerId: if not InvalidOid, use this as the new relation's owner.
687 : : * typaddress: if not null, it's set to the pg_type entry's address.
688 : : * queryString: for error reporting
689 : : *
690 : : * Note that permissions checks are done against current user regardless of
691 : : * ownerId. A nonzero ownerId is used when someone is creating a relation
692 : : * "on behalf of" someone else, so we still want to see that the current user
693 : : * has permissions to do it.
694 : : *
695 : : * If successful, returns the address of the new relation.
696 : : * ----------------------------------------------------------------
697 : : */
698 : : ObjectAddress
3330 alvherre@alvh.no-ip. 699 :CBC 26364 : DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
700 : : ObjectAddress *typaddress, const char *queryString)
701 : : {
702 : : char relname[NAMEDATALEN];
703 : : Oid namespaceId;
704 : : Oid relationId;
705 : : Oid tablespaceId;
706 : : Relation rel;
707 : : TupleDesc descriptor;
708 : : List *inheritOids;
709 : : List *old_constraints;
710 : : List *old_notnulls;
711 : : List *rawDefaults;
712 : : List *cookedDefaults;
713 : : List *nncols;
714 : : Datum reloptions;
715 : : ListCell *listptr;
716 : : AttrNumber attnum;
717 : : bool partitioned;
718 : : static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
719 : : Oid ofTypeId;
720 : : ObjectAddress address;
721 : : LOCKMODE parentLockmode;
1866 andres@anarazel.de 722 : 26364 : Oid accessMethodId = InvalidOid;
723 : :
724 : : /*
725 : : * Truncate relname to appropriate length (probably a waste of time, as
726 : : * parser should have done this already).
727 : : */
1343 peter@eisentraut.org 728 : 26364 : strlcpy(relname, stmt->relation->relname, NAMEDATALEN);
729 : :
730 : : /*
731 : : * Check consistency of arguments
732 : : */
4753 bruce@momjian.us 733 [ + + ]: 26364 : if (stmt->oncommit != ONCOMMIT_NOOP
4871 rhaas@postgresql.org 734 [ + + ]: 89 : && stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
7574 tgl@sss.pgh.pa.us 735 [ + - ]: 6 : ereport(ERROR,
736 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
737 : : errmsg("ON COMMIT can only be used on temporary tables")));
738 : :
2685 rhaas@postgresql.org 739 [ + + ]: 26358 : if (stmt->partspec != NULL)
740 : : {
741 [ - + ]: 2435 : if (relkind != RELKIND_RELATION)
2685 rhaas@postgresql.org 742 [ # # ]:UBC 0 : elog(ERROR, "unexpected relkind: %d", (int) relkind);
743 : :
2685 rhaas@postgresql.org 744 :CBC 2435 : relkind = RELKIND_PARTITIONED_TABLE;
1816 alvherre@alvh.no-ip. 745 : 2435 : partitioned = true;
746 : : }
747 : : else
748 : 23923 : partitioned = false;
749 : :
750 : : /*
751 : : * Look up the namespace in which we are supposed to create the relation,
752 : : * check we have permission to create there, lock it against concurrent
753 : : * drop, and mark stmt->relation as RELPERSISTENCE_TEMP if a temporary
754 : : * namespace is selected.
755 : : */
756 : : namespaceId =
4472 rhaas@postgresql.org 757 : 26358 : RangeVarGetAndCheckCreationNamespace(stmt->relation, NoLock, NULL);
758 : :
759 : : /*
760 : : * Security check: disallow creating temp tables from security-restricted
761 : : * code. This is needed because calling code might not expect untrusted
762 : : * tables to appear in pg_temp at the front of its search path.
763 : : */
4871 764 [ + + ]: 26358 : if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
765 [ - + ]: 1485 : && InSecurityRestrictedOperation())
5240 tgl@sss.pgh.pa.us 766 [ # # ]:UBC 0 : ereport(ERROR,
767 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
768 : : errmsg("cannot create temporary table within security-restricted operation")));
769 : :
770 : : /*
771 : : * Determine the lockmode to use when scanning parents. A self-exclusive
772 : : * lock is needed here.
773 : : *
774 : : * For regular inheritance, if two backends attempt to add children to the
775 : : * same parent simultaneously, and that parent has no pre-existing
776 : : * children, then both will attempt to update the parent's relhassubclass
777 : : * field, leading to a "tuple concurrently updated" error. Also, this
778 : : * interlocks against a concurrent ANALYZE on the parent table, which
779 : : * might otherwise be attempting to clear the parent's relhassubclass
780 : : * field, if its previous children were recently dropped.
781 : : *
782 : : * If the child table is a partition, then we instead grab an exclusive
783 : : * lock on the parent because its partition descriptor will be changed by
784 : : * addition of the new partition.
785 : : */
1945 alvherre@alvh.no-ip. 786 [ + + ]:CBC 26358 : parentLockmode = (stmt->partbound != NULL ? AccessExclusiveLock :
787 : : ShareUpdateExclusiveLock);
788 : :
789 : : /* Determine the list of OIDs of the parents. */
790 : 26358 : inheritOids = NIL;
791 [ + + + + : 31522 : foreach(listptr, stmt->inhRelations)
+ + ]
792 : : {
793 : 5164 : RangeVar *rv = (RangeVar *) lfirst(listptr);
794 : : Oid parentOid;
795 : :
796 : 5164 : parentOid = RangeVarGetRelid(rv, parentLockmode, false);
797 : :
798 : : /*
799 : : * Reject duplications in the list of parents.
800 : : */
801 [ - + ]: 5164 : if (list_member_oid(inheritOids, parentOid))
1945 alvherre@alvh.no-ip. 802 [ # # ]:UBC 0 : ereport(ERROR,
803 : : (errcode(ERRCODE_DUPLICATE_TABLE),
804 : : errmsg("relation \"%s\" would be inherited from more than once",
805 : : get_rel_name(parentOid))));
806 : :
1945 alvherre@alvh.no-ip. 807 :CBC 5164 : inheritOids = lappend_oid(inheritOids, parentOid);
808 : : }
809 : :
810 : : /*
811 : : * Select tablespace to use: an explicitly indicated one, or (in the case
812 : : * of a partitioned table) the parent's, if it has one.
813 : : */
7240 tgl@sss.pgh.pa.us 814 [ + + ]: 26358 : if (stmt->tablespacename)
815 : : {
5001 rhaas@postgresql.org 816 : 55 : tablespaceId = get_tablespace_oid(stmt->tablespacename, false);
817 : :
1816 alvherre@alvh.no-ip. 818 [ + + + + ]: 52 : if (partitioned && tablespaceId == MyDatabaseTableSpace)
819 [ + - ]: 3 : ereport(ERROR,
820 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
821 : : errmsg("cannot specify default tablespace for partitioned relations")));
822 : : }
1945 823 [ + + ]: 26303 : else if (stmt->partbound)
824 : : {
825 [ - + ]: 4131 : Assert(list_length(inheritOids) == 1);
1816 826 : 4131 : tablespaceId = get_rel_tablespace(linitial_oid(inheritOids));
827 : : }
828 : : else
1773 829 : 22172 : tablespaceId = InvalidOid;
830 : :
831 : : /* still nothing? use the default */
832 [ + + ]: 26352 : if (!OidIsValid(tablespaceId))
1816 833 : 26292 : tablespaceId = GetDefaultTablespace(stmt->relation->relpersistence,
834 : : partitioned);
835 : :
836 : : /* Check permissions except when using database's default */
5911 tgl@sss.pgh.pa.us 837 [ + + + + ]: 26349 : if (OidIsValid(tablespaceId) && tablespaceId != MyDatabaseTableSpace)
838 : : {
839 : : AclResult aclresult;
840 : :
518 peter@eisentraut.org 841 : 69 : aclresult = object_aclcheck(TableSpaceRelationId, tablespaceId, GetUserId(),
842 : : ACL_CREATE);
7240 tgl@sss.pgh.pa.us 843 [ + + ]: 69 : if (aclresult != ACLCHECK_OK)
2325 peter_e@gmx.net 844 : 3 : aclcheck_error(aclresult, OBJECT_TABLESPACE,
7100 tgl@sss.pgh.pa.us 845 : 3 : get_tablespace_name(tablespaceId));
846 : : }
847 : :
848 : : /* In all cases disallow placing user relations in pg_global */
5180 849 [ + + ]: 26346 : if (tablespaceId == GLOBALTABLESPACE_OID)
850 [ + - ]: 9 : ereport(ERROR,
851 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
852 : : errmsg("only shared relations can be placed in pg_global tablespace")));
853 : :
854 : : /* Identify user ID that will own the table */
4988 855 [ + + ]: 26337 : if (!OidIsValid(ownerId))
856 : 26223 : ownerId = GetUserId();
857 : :
858 : : /*
859 : : * Parse and validate reloptions, if any.
860 : : */
5550 alvherre@alvh.no-ip. 861 : 26337 : reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
862 : : true, false);
863 : :
1613 michael@paquier.xyz 864 [ + + + ]: 26328 : switch (relkind)
865 : : {
866 : 6542 : case RELKIND_VIEW:
867 : 6542 : (void) view_reloptions(reloptions, true);
868 : 6533 : break;
869 : 2426 : case RELKIND_PARTITIONED_TABLE:
870 : 2426 : (void) partitioned_table_reloptions(reloptions, true);
871 : 2423 : break;
872 : 17360 : default:
3 akorotkov@postgresql 873 : 17360 : (void) heap_reloptions(relkind, reloptions, true);
874 : : }
875 : :
5190 peter_e@gmx.net 876 [ + + ]: 26268 : if (stmt->ofTypename)
877 : : {
878 : : AclResult aclresult;
879 : :
4920 880 : 43 : ofTypeId = typenameTypeId(NULL, stmt->ofTypename);
881 : :
518 peter@eisentraut.org 882 : 43 : aclresult = object_aclcheck(TypeRelationId, ofTypeId, GetUserId(), ACL_USAGE);
4499 peter_e@gmx.net 883 [ + + ]: 43 : if (aclresult != ACLCHECK_OK)
4321 884 : 3 : aclcheck_error_type(aclresult, ofTypeId);
885 : : }
886 : : else
5190 887 : 26225 : ofTypeId = InvalidOid;
888 : :
889 : : /*
890 : : * Look up inheritance ancestors and generate relation schema, including
891 : : * inherited attributes. (Note that stmt->tableElts is destructively
892 : : * modified by MergeAttributes.)
893 : : */
2522 rhaas@postgresql.org 894 : 26181 : stmt->tableElts =
1945 alvherre@alvh.no-ip. 895 : 26265 : MergeAttributes(stmt->tableElts, inheritOids,
2522 rhaas@postgresql.org 896 : 26265 : stmt->relation->relpersistence,
897 : 26265 : stmt->partbound != NULL,
898 : : &old_constraints, &old_notnulls);
899 : :
900 : : /*
901 : : * Create a tuple descriptor from the relation schema. Note that this
902 : : * deals with column names, types, and in-descriptor NOT NULL flags, but
903 : : * not default values, NOT NULL or CHECK constraints; we handle those
904 : : * below.
905 : : */
906 : 26181 : descriptor = BuildDescForRelation(stmt->tableElts);
907 : :
908 : : /*
909 : : * Find columns with default values and prepare for insertion of the
910 : : * defaults. Pre-cooked (that is, inherited) defaults go into a list of
911 : : * CookedConstraint structs that we'll pass to heap_create_with_catalog,
912 : : * while raw defaults go into a list of RawColumnDefault structs that will
913 : : * be processed by AddRelationNewConstraints. (We can't deal with raw
914 : : * expressions until we can do transformExpr.)
915 : : *
916 : : * We can set the atthasdef flags now in the tuple descriptor; this just
917 : : * saves StoreAttrDefault from having to do an immediate update of the
918 : : * pg_attribute rows.
919 : : */
5819 tgl@sss.pgh.pa.us 920 : 26157 : rawDefaults = NIL;
921 : 26157 : cookedDefaults = NIL;
922 : 26157 : attnum = 0;
923 : :
2522 rhaas@postgresql.org 924 [ + + + + : 131731 : foreach(listptr, stmt->tableElts)
+ + ]
925 : : {
5819 tgl@sss.pgh.pa.us 926 : 105574 : ColumnDef *colDef = lfirst(listptr);
927 : : Form_pg_attribute attr;
928 : :
929 : 105574 : attnum++;
2429 andres@anarazel.de 930 : 105574 : attr = TupleDescAttr(descriptor, attnum - 1);
931 : :
5819 tgl@sss.pgh.pa.us 932 [ + + ]: 105574 : if (colDef->raw_default != NULL)
933 : : {
934 : : RawColumnDefault *rawEnt;
935 : :
936 [ - + ]: 1203 : Assert(colDef->cooked_default == NULL);
937 : :
938 : 1203 : rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
939 : 1203 : rawEnt->attnum = attnum;
940 : 1203 : rawEnt->raw_default = colDef->raw_default;
2209 andrew@dunslane.net 941 : 1203 : rawEnt->missingMode = false;
1842 peter@eisentraut.org 942 : 1203 : rawEnt->generated = colDef->generated;
5819 tgl@sss.pgh.pa.us 943 : 1203 : rawDefaults = lappend(rawDefaults, rawEnt);
2429 andres@anarazel.de 944 : 1203 : attr->atthasdef = true;
945 : : }
5819 tgl@sss.pgh.pa.us 946 [ + + ]: 104371 : else if (colDef->cooked_default != NULL)
947 : : {
948 : : CookedConstraint *cooked;
949 : :
950 : 221 : cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
951 : 221 : cooked->contype = CONSTR_DEFAULT;
2489 952 : 221 : cooked->conoid = InvalidOid; /* until created */
5819 953 : 221 : cooked->name = NULL;
954 : 221 : cooked->attnum = attnum;
5304 955 : 221 : cooked->expr = colDef->cooked_default;
4701 alvherre@alvh.no-ip. 956 : 221 : cooked->skip_validation = false;
5819 tgl@sss.pgh.pa.us 957 : 221 : cooked->is_local = true; /* not used for defaults */
2489 958 : 221 : cooked->inhcount = 0; /* ditto */
4377 alvherre@alvh.no-ip. 959 : 221 : cooked->is_no_inherit = false;
5819 tgl@sss.pgh.pa.us 960 : 221 : cookedDefaults = lappend(cookedDefaults, cooked);
2429 andres@anarazel.de 961 : 221 : attr->atthasdef = true;
962 : : }
963 : : }
964 : :
965 : : /*
966 : : * For relations with table AM and partitioned tables, select access
967 : : * method to use: an explicitly indicated one, or (in the case of a
968 : : * partitioned table) the parent's, if it has one.
969 : : */
3 akorotkov@postgresql 970 [ + + ]: 26157 : if (stmt->accessMethod != NULL)
971 : : {
3 akorotkov@postgresql 972 [ + + + - :GNC 58 : Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE);
+ + - + ]
973 : 58 : accessMethodId = get_table_am_oid(stmt->accessMethod, false);
974 : : }
975 [ + + + - : 26099 : else if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE)
+ + + + ]
976 : : {
977 [ + + ]: 18176 : if (stmt->partbound)
978 : : {
979 [ - + ]: 4052 : Assert(list_length(inheritOids) == 1);
980 : 4052 : accessMethodId = get_rel_relam(linitial_oid(inheritOids));
981 : : }
982 : :
983 [ + + + - : 18176 : if (RELKIND_HAS_TABLE_AM(relkind) && !OidIsValid(accessMethodId))
+ + + + ]
984 : 15743 : accessMethodId = get_table_am_oid(default_table_access_method, false);
985 : : }
986 : :
987 : : /*
988 : : * Create the relation. Inherited defaults and constraints are passed in
989 : : * for immediate handling --- since they don't need parsing, they can be
990 : : * stored immediately.
991 : : */
8024 tgl@sss.pgh.pa.us 992 :CBC 26148 : relationId = heap_create_with_catalog(relname,
993 : : namespaceId,
994 : : tablespaceId,
995 : : InvalidOid,
996 : : InvalidOid,
997 : : ofTypeId,
998 : : ownerId,
999 : : accessMethodId,
1000 : : descriptor,
1001 : : list_concat(cookedDefaults,
1002 : : old_constraints),
1003 : : relkind,
4871 rhaas@postgresql.org 1004 : 26148 : stmt->relation->relpersistence,
1005 : : false,
1006 : : false,
1007 : : stmt->oncommit,
1008 : : reloptions,
1009 : : true,
1010 : : allowSystemTableMods,
1011 : : false,
1012 : : InvalidOid,
1013 : : typaddress);
1014 : :
1015 : : /*
1016 : : * We must bump the command counter to make the newly-created relation
1017 : : * tuple visible for opening.
1018 : : */
8024 tgl@sss.pgh.pa.us 1019 : 26136 : CommandCounterIncrement();
1020 : :
1021 : : /*
1022 : : * Open the new relation and acquire exclusive lock on it. This isn't
1023 : : * really necessary for locking out other backends (since they can't see
1024 : : * the new rel anyway until we commit), but it keeps the lock manager from
1025 : : * complaining about deadlock risks.
1026 : : */
7899 1027 : 26136 : rel = relation_open(relationId, AccessExclusiveLock);
1028 : :
1029 : : /*
1030 : : * Now add any newly specified column default and generation expressions
1031 : : * to the new relation. These are passed to us in the form of raw
1032 : : * parsetrees; we need to transform them to executable expression trees
1033 : : * before they can be added. The most convenient way to do that is to
1034 : : * apply the parser's transformExpr routine, but transformExpr doesn't
1035 : : * work unless we have a pre-existing relation. So, the transformation has
1036 : : * to be postponed to this final step of CREATE TABLE.
1037 : : *
1038 : : * This needs to be before processing the partitioning clauses because
1039 : : * those could refer to generated columns.
1040 : : */
1842 peter@eisentraut.org 1041 [ + + ]: 26136 : if (rawDefaults)
1042 : 1017 : AddRelationNewConstraints(rel, rawDefaults, NIL,
1043 : : true, true, false, queryString);
1044 : :
1045 : : /*
1046 : : * Make column generation expressions visible for use by partitioning.
1047 : : */
1048 : 26085 : CommandCounterIncrement();
1049 : :
1050 : : /* Process and store partition bound, if any. */
2685 rhaas@postgresql.org 1051 [ + + ]: 26085 : if (stmt->partbound)
1052 : : {
1053 : : PartitionBoundSpec *bound;
1054 : : ParseState *pstate;
2410 1055 : 4104 : Oid parentId = linitial_oid(inheritOids),
1056 : : defaultPartOid;
1057 : : Relation parent,
1058 : 4104 : defaultRel = NULL;
1059 : : ParseNamespaceItem *nsitem;
1060 : :
1061 : : /* Already have strong enough lock on the parent */
1910 andres@anarazel.de 1062 : 4104 : parent = table_open(parentId, NoLock);
1063 : :
1064 : : /*
1065 : : * We are going to try to validate the partition bound specification
1066 : : * against the partition key of parentRel, so it better have one.
1067 : : */
2685 rhaas@postgresql.org 1068 [ + + ]: 4104 : if (parent->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
1069 [ + - ]: 9 : ereport(ERROR,
1070 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
1071 : : errmsg("\"%s\" is not partitioned",
1072 : : RelationGetRelationName(parent))));
1073 : :
1074 : : /*
1075 : : * The partition constraint of the default partition depends on the
1076 : : * partition bounds of every other partition. It is possible that
1077 : : * another backend might be about to execute a query on the default
1078 : : * partition table, and that the query relies on previously cached
1079 : : * default partition constraints. We must therefore take a table lock
1080 : : * strong enough to prevent all queries on the default partition from
1081 : : * proceeding until we commit and send out a shared-cache-inval notice
1082 : : * that will make them update their index lists.
1083 : : *
1084 : : * Order of locking: The relation being added won't be visible to
1085 : : * other backends until it is committed, hence here in
1086 : : * DefineRelation() the order of locking the default partition and the
1087 : : * relation being added does not matter. But at all other places we
1088 : : * need to lock the default relation before we lock the relation being
1089 : : * added or removed i.e. we should take the lock in same order at all
1090 : : * the places such that lock parent, lock default partition and then
1091 : : * lock the partition so as to avoid a deadlock.
1092 : : */
1093 : : defaultPartOid =
1116 alvherre@alvh.no-ip. 1094 : 4095 : get_default_oid_from_partdesc(RelationGetPartitionDesc(parent,
1095 : : true));
2410 rhaas@postgresql.org 1096 [ + + ]: 4095 : if (OidIsValid(defaultPartOid))
1910 andres@anarazel.de 1097 : 189 : defaultRel = table_open(defaultPartOid, AccessExclusiveLock);
1098 : :
1099 : : /* Transform the bound values */
2685 rhaas@postgresql.org 1100 : 4095 : pstate = make_parsestate(NULL);
1101 : 4095 : pstate->p_sourcetext = queryString;
1102 : :
1103 : : /*
1104 : : * Add an nsitem containing this relation, so that transformExpr
1105 : : * called on partition bound expressions is able to report errors
1106 : : * using a proper context.
1107 : : */
1564 tgl@sss.pgh.pa.us 1108 : 4095 : nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock,
1109 : : NULL, false, false);
1110 : 4095 : addNSItemToQuery(pstate, nsitem, false, true, true);
1111 : :
2685 rhaas@postgresql.org 1112 : 4095 : bound = transformPartitionBound(pstate, parent, stmt->partbound);
1113 : :
1114 : : /*
1115 : : * Check first that the new partition's bound is valid and does not
1116 : : * overlap with any of existing partitions of the parent.
1117 : : */
1299 tgl@sss.pgh.pa.us 1118 : 3993 : check_new_partition_bound(relname, parent, bound, pstate);
1119 : :
1120 : : /*
1121 : : * If the default partition exists, its partition constraints will
1122 : : * change after the addition of this new partition such that it won't
1123 : : * allow any row that qualifies for this new partition. So, check that
1124 : : * the existing data in the default partition satisfies the constraint
1125 : : * as it will exist after adding this partition.
1126 : : */
2410 rhaas@postgresql.org 1127 [ + + ]: 3936 : if (OidIsValid(defaultPartOid))
1128 : : {
2132 tgl@sss.pgh.pa.us 1129 : 174 : check_default_partition_contents(parent, defaultRel, bound);
1130 : : /* Keep the lock until commit. */
1910 andres@anarazel.de 1131 : 165 : table_close(defaultRel, NoLock);
1132 : : }
1133 : :
1134 : : /* Update the pg_class entry. */
2673 rhaas@postgresql.org 1135 : 3927 : StorePartitionBound(rel, parent, bound);
1136 : :
1910 andres@anarazel.de 1137 : 3927 : table_close(parent, NoLock);
1138 : : }
1139 : :
1140 : : /* Store inheritance information for new rel. */
2048 alvherre@alvh.no-ip. 1141 : 25908 : StoreCatalogInheritance(relationId, inheritOids, stmt->partbound != NULL);
1142 : :
1143 : : /*
1144 : : * Process the partitioning specification (if any) and store the partition
1145 : : * key information into the catalog.
1146 : : */
1816 1147 [ + + ]: 25908 : if (partitioned)
1148 : : {
1149 : : ParseState *pstate;
1150 : : int partnatts;
1151 : : AttrNumber partattrs[PARTITION_MAX_KEYS];
1152 : : Oid partopclass[PARTITION_MAX_KEYS];
1153 : : Oid partcollation[PARTITION_MAX_KEYS];
2679 rhaas@postgresql.org 1154 : 2423 : List *partexprs = NIL;
1155 : :
2062 peter_e@gmx.net 1156 : 2423 : pstate = make_parsestate(NULL);
1157 : 2423 : pstate->p_sourcetext = queryString;
1158 : :
2513 tgl@sss.pgh.pa.us 1159 : 2423 : partnatts = list_length(stmt->partspec->partParams);
1160 : :
1161 : : /* Protect fixed-size arrays here and in executor */
1162 [ - + ]: 2423 : if (partnatts > PARTITION_MAX_KEYS)
2513 tgl@sss.pgh.pa.us 1163 [ # # ]:UBC 0 : ereport(ERROR,
1164 : : (errcode(ERRCODE_TOO_MANY_COLUMNS),
1165 : : errmsg("cannot partition using more than %d columns",
1166 : : PARTITION_MAX_KEYS)));
1167 : :
1168 : : /*
1169 : : * We need to transform the raw parsetrees corresponding to partition
1170 : : * expressions into executable expression trees. Like column defaults
1171 : : * and CHECK constraints, we could not have done the transformation
1172 : : * earlier.
1173 : : */
528 alvherre@alvh.no-ip. 1174 :CBC 2423 : stmt->partspec = transformPartitionSpec(rel, stmt->partspec);
1175 : :
2062 peter_e@gmx.net 1176 : 2408 : ComputePartitionAttrs(pstate, rel, stmt->partspec->partParams,
1177 : : partattrs, &partexprs, partopclass,
528 alvherre@alvh.no-ip. 1178 : 2408 : partcollation, stmt->partspec->strategy);
1179 : :
1180 : 2366 : StorePartitionKey(rel, stmt->partspec->strategy, partnatts, partattrs,
1181 : : partexprs,
1182 : : partopclass, partcollation);
1183 : :
1184 : : /* make it all visible */
2277 1185 : 2366 : CommandCounterIncrement();
1186 : : }
1187 : :
1188 : : /*
1189 : : * If we're creating a partition, create now all the indexes, triggers,
1190 : : * FKs defined in the parent.
1191 : : *
1192 : : * We can't do it earlier, because DefineIndex wants to know the partition
1193 : : * key which we just stored.
1194 : : */
1195 [ + + ]: 25851 : if (stmt->partbound)
1196 : : {
1197 : 3924 : Oid parentId = linitial_oid(inheritOids);
1198 : : Relation parent;
1199 : : List *idxlist;
1200 : : ListCell *cell;
1201 : :
1202 : : /* Already have strong enough lock on the parent */
1910 andres@anarazel.de 1203 : 3924 : parent = table_open(parentId, NoLock);
2277 alvherre@alvh.no-ip. 1204 : 3924 : idxlist = RelationGetIndexList(parent);
1205 : :
1206 : : /*
1207 : : * For each index in the parent table, create one in the partition
1208 : : */
1209 [ + + + + : 4638 : foreach(cell, idxlist)
+ + ]
1210 : : {
1211 : 723 : Relation idxRel = index_open(lfirst_oid(cell), AccessShareLock);
1212 : : AttrMap *attmap;
1213 : : IndexStmt *idxstmt;
1214 : : Oid constraintOid;
1215 : :
1754 1216 [ + + ]: 723 : if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
1217 : : {
1218 [ + + ]: 18 : if (idxRel->rd_index->indisunique)
1219 [ + - ]: 6 : ereport(ERROR,
1220 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
1221 : : errmsg("cannot create foreign partition of partitioned table \"%s\"",
1222 : : RelationGetRelationName(parent)),
1223 : : errdetail("Table \"%s\" contains indexes that are unique.",
1224 : : RelationGetRelationName(parent))));
1225 : : else
1226 : : {
1227 : 12 : index_close(idxRel, AccessShareLock);
1228 : 12 : continue;
1229 : : }
1230 : : }
1231 : :
1579 michael@paquier.xyz 1232 : 705 : attmap = build_attrmap_by_name(RelationGetDescr(rel),
1233 : : RelationGetDescr(parent),
1234 : : false);
1235 : : idxstmt =
1818 tgl@sss.pgh.pa.us 1236 : 705 : generateClonedIndexStmt(NULL, idxRel,
1237 : : attmap, &constraintOid);
2277 alvherre@alvh.no-ip. 1238 : 705 : DefineIndex(RelationGetRelid(rel),
1239 : : idxstmt,
1240 : : InvalidOid,
1241 : : RelationGetRelid(idxRel),
1242 : : constraintOid,
1243 : : -1,
1244 : : false, false, false, false, false);
1245 : :
1246 : 702 : index_close(idxRel, AccessShareLock);
1247 : : }
1248 : :
1249 : 3915 : list_free(idxlist);
1250 : :
1251 : : /*
1252 : : * If there are any row-level triggers, clone them to the new
1253 : : * partition.
1254 : : */
2214 1255 [ + + ]: 3915 : if (parent->trigdesc != NULL)
1256 : 213 : CloneRowTriggersToPartition(parent, rel);
1257 : :
1258 : : /*
1259 : : * And foreign keys too. Note that because we're freshly creating the
1260 : : * table, there is no need to verify these new constraints.
1261 : : */
1838 1262 : 3915 : CloneForeignKeyConstraints(NULL, parent, rel);
1263 : :
1910 andres@anarazel.de 1264 : 3915 : table_close(parent, NoLock);
1265 : : }
1266 : :
1267 : : /*
1268 : : * Now add any newly specified CHECK constraints to the new relation. Same
1269 : : * as for defaults above, but these need to come after partitioning is set
1270 : : * up.
1271 : : */
1842 peter@eisentraut.org 1272 [ + + ]: 25842 : if (stmt->constraints)
1273 : 324 : AddRelationNewConstraints(rel, NIL, stmt->constraints,
1274 : : true, true, false, queryString);
1275 : :
1276 : : /*
1277 : : * Finally, merge the not-null constraints that are declared directly with
1278 : : * those that come from parent relations (making sure to count inheritance
1279 : : * appropriately for each), create them, and set the attnotnull flag on
1280 : : * columns that don't yet have it.
1281 : : */
233 alvherre@alvh.no-ip. 1282 :GNC 25833 : nncols = AddRelationNotNullConstraints(rel, stmt->nnconstraints,
1283 : : old_notnulls);
1284 [ + + + + : 29567 : foreach(listptr, nncols)
+ + ]
1285 : 3740 : set_attnotnull(NULL, rel, lfirst_int(listptr), false, NoLock);
1286 : :
3330 alvherre@alvh.no-ip. 1287 :CBC 25827 : ObjectAddressSet(address, RelationRelationId, relationId);
1288 : :
1289 : : /*
1290 : : * Clean up. We keep lock on new relation (although it shouldn't be
1291 : : * visible to anyone else anyway, until commit).
1292 : : */
7899 tgl@sss.pgh.pa.us 1293 : 25827 : relation_close(rel, NoLock);
1294 : :
3330 alvherre@alvh.no-ip. 1295 : 25827 : return address;
1296 : : }
1297 : :
1298 : : /*
1299 : : * BuildDescForRelation
1300 : : *
1301 : : * Given a list of ColumnDef nodes, build a TupleDesc.
1302 : : *
1303 : : * Note: tdtypeid will need to be filled in later on.
1304 : : */
1305 : : TupleDesc
192 peter@eisentraut.org 1306 :GNC 27517 : BuildDescForRelation(const List *columns)
1307 : : {
1308 : : int natts;
1309 : : AttrNumber attnum;
1310 : : ListCell *l;
1311 : : TupleDesc desc;
1312 : : bool has_not_null;
1313 : : char *attname;
1314 : : Oid atttypid;
1315 : : int32 atttypmod;
1316 : : Oid attcollation;
1317 : : int attdim;
1318 : :
1319 : : /*
1320 : : * allocate a new tuple descriptor
1321 : : */
1322 : 27517 : natts = list_length(columns);
1323 : 27517 : desc = CreateTemplateTupleDesc(natts);
1324 : 27517 : has_not_null = false;
1325 : :
1326 : 27517 : attnum = 0;
1327 : :
1328 [ + + + + : 134559 : foreach(l, columns)
+ + ]
1329 : : {
1330 : 107072 : ColumnDef *entry = lfirst(l);
1331 : : AclResult aclresult;
1332 : : Form_pg_attribute att;
1333 : :
1334 : : /*
1335 : : * for each entry in the list, get the name and type information from
1336 : : * the list and have TupleDescInitEntry fill in the attribute
1337 : : * information we need.
1338 : : */
1339 : 107072 : attnum++;
1340 : :
1341 : 107072 : attname = entry->colname;
1342 : 107072 : typenameTypeIdAndMod(NULL, entry->typeName, &atttypid, &atttypmod);
1343 : :
1344 : 107072 : aclresult = object_aclcheck(TypeRelationId, atttypid, GetUserId(), ACL_USAGE);
1345 [ + + ]: 107072 : if (aclresult != ACLCHECK_OK)
1346 : 21 : aclcheck_error_type(aclresult, atttypid);
1347 : :
1348 : 107051 : attcollation = GetColumnDefCollation(NULL, entry, atttypid);
1349 : 107051 : attdim = list_length(entry->typeName->arrayBounds);
1350 [ - + ]: 107051 : if (attdim > PG_INT16_MAX)
192 peter@eisentraut.org 1351 [ # # ]:UNC 0 : ereport(ERROR,
1352 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
1353 : : errmsg("too many array dimensions"));
1354 : :
192 peter@eisentraut.org 1355 [ - + ]:GNC 107051 : if (entry->typeName->setof)
192 peter@eisentraut.org 1356 [ # # ]:UNC 0 : ereport(ERROR,
1357 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
1358 : : errmsg("column \"%s\" cannot be declared SETOF",
1359 : : attname)));
1360 : :
192 peter@eisentraut.org 1361 :GNC 107051 : TupleDescInitEntry(desc, attnum, attname,
1362 : : atttypid, atttypmod, attdim);
1363 : 107051 : att = TupleDescAttr(desc, attnum - 1);
1364 : :
1365 : : /* Override TupleDescInitEntry's settings as requested */
1366 : 107051 : TupleDescInitEntryCollation(desc, attnum, attcollation);
1367 : :
1368 : : /* Fill in additional stuff not handled by TupleDescInitEntry */
1369 : 107051 : att->attnotnull = entry->is_not_null;
1370 : 107051 : has_not_null |= entry->is_not_null;
1371 : 107051 : att->attislocal = entry->is_local;
1372 : 107051 : att->attinhcount = entry->inhcount;
1373 : 107051 : att->attidentity = entry->identity;
1374 : 107051 : att->attgenerated = entry->generated;
1375 : 107051 : att->attcompression = GetAttributeCompression(att->atttypid, entry->compression);
54 1376 [ + + ]: 107045 : if (entry->storage)
1377 : 11335 : att->attstorage = entry->storage;
1378 [ + + ]: 95710 : else if (entry->storage_name)
192 1379 : 10 : att->attstorage = GetAttributeStorage(att->atttypid, entry->storage_name);
1380 : : }
1381 : :
1382 [ + + ]: 27487 : if (has_not_null)
1383 : : {
1384 : 6037 : TupleConstr *constr = (TupleConstr *) palloc0(sizeof(TupleConstr));
1385 : :
1386 : 6037 : constr->has_not_null = true;
1387 : 6037 : constr->has_generated_stored = false;
1388 : 6037 : constr->defval = NULL;
1389 : 6037 : constr->missing = NULL;
1390 : 6037 : constr->num_defval = 0;
1391 : 6037 : constr->check = NULL;
1392 : 6037 : constr->num_check = 0;
1393 : 6037 : desc->constr = constr;
1394 : : }
1395 : : else
1396 : : {
1397 : 21450 : desc->constr = NULL;
1398 : : }
1399 : :
1400 : 27487 : return desc;
1401 : : }
1402 : :
1403 : : /*
1404 : : * Emit the right error or warning message for a "DROP" command issued on a
1405 : : * non-existent relation
1406 : : */
1407 : : static void
3734 alvherre@alvh.no-ip. 1408 :CBC 538 : DropErrorMsgNonExistent(RangeVar *rel, char rightkind, bool missing_ok)
1409 : : {
1410 : : const struct dropmsgstrings *rentry;
1411 : :
1412 [ + + + + ]: 598 : if (rel->schemaname != NULL &&
1413 : 60 : !OidIsValid(LookupNamespaceNoError(rel->schemaname)))
1414 : : {
1415 [ - + ]: 21 : if (!missing_ok)
1416 : : {
3734 alvherre@alvh.no-ip. 1417 [ # # ]:UBC 0 : ereport(ERROR,
1418 : : (errcode(ERRCODE_UNDEFINED_SCHEMA),
1419 : : errmsg("schema \"%s\" does not exist", rel->schemaname)));
1420 : : }
1421 : : else
1422 : : {
3734 alvherre@alvh.no-ip. 1423 [ + - ]:CBC 21 : ereport(NOTICE,
1424 : : (errmsg("schema \"%s\" does not exist, skipping",
1425 : : rel->schemaname)));
1426 : : }
1427 : 21 : return;
1428 : : }
1429 : :
5783 tgl@sss.pgh.pa.us 1430 [ + - ]: 677 : for (rentry = dropmsgstringarray; rentry->kind != '\0'; rentry++)
1431 : : {
1432 [ + + ]: 677 : if (rentry->kind == rightkind)
1433 : : {
1434 [ + + ]: 517 : if (!missing_ok)
1435 : : {
1436 [ + - ]: 65 : ereport(ERROR,
1437 : : (errcode(rentry->nonexistent_code),
1438 : : errmsg(rentry->nonexistent_msg, rel->relname)));
1439 : : }
1440 : : else
1441 : : {
3734 alvherre@alvh.no-ip. 1442 [ + - ]: 452 : ereport(NOTICE, (errmsg(rentry->skipping_msg, rel->relname)));
5783 tgl@sss.pgh.pa.us 1443 : 452 : break;
1444 : : }
1445 : : }
1446 : : }
1447 : :
2489 1448 [ - + ]: 452 : Assert(rentry->kind != '\0'); /* Should be impossible */
1449 : : }
1450 : :
1451 : : /*
1452 : : * Emit the right error message for a "DROP" command issued on a
1453 : : * relation of the wrong type
1454 : : */
1455 : : static void
5783 tgl@sss.pgh.pa.us 1456 :UBC 0 : DropErrorMsgWrongType(const char *relname, char wrongkind, char rightkind)
1457 : : {
1458 : : const struct dropmsgstrings *rentry;
1459 : : const struct dropmsgstrings *wentry;
1460 : :
1461 [ # # ]: 0 : for (rentry = dropmsgstringarray; rentry->kind != '\0'; rentry++)
1462 [ # # ]: 0 : if (rentry->kind == rightkind)
1463 : 0 : break;
1464 [ # # ]: 0 : Assert(rentry->kind != '\0');
1465 : :
1466 [ # # ]: 0 : for (wentry = dropmsgstringarray; wentry->kind != '\0'; wentry++)
1467 [ # # ]: 0 : if (wentry->kind == wrongkind)
1468 : 0 : break;
1469 : : /* wrongkind could be something we don't have in our table... */
1470 : :
1471 [ # # # # ]: 0 : ereport(ERROR,
1472 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
1473 : : errmsg(rentry->nota_msg, relname),
1474 : : (wentry->kind != '\0') ? errhint("%s", _(wentry->drophint_msg)) : 0));
1475 : : }
1476 : :
1477 : : /*
1478 : : * RemoveRelations
1479 : : * Implements DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW,
1480 : : * DROP MATERIALIZED VIEW, DROP FOREIGN TABLE
1481 : : */
1482 : : void
5783 tgl@sss.pgh.pa.us 1483 :CBC 8043 : RemoveRelations(DropStmt *drop)
1484 : : {
1485 : : ObjectAddresses *objects;
1486 : : char relkind;
1487 : : ListCell *cell;
4391 simon@2ndQuadrant.co 1488 : 8043 : int flags = 0;
1489 : 8043 : LOCKMODE lockmode = AccessExclusiveLock;
1490 : :
1491 : : /* DROP CONCURRENTLY uses a weaker lock, and has some restrictions */
1492 [ + + ]: 8043 : if (drop->concurrent)
1493 : : {
1494 : : /*
1495 : : * Note that for temporary relations this lock may get upgraded later
1496 : : * on, but as no other session can access a temporary relation, this
1497 : : * is actually fine.
1498 : : */
1499 : 70 : lockmode = ShareUpdateExclusiveLock;
4155 tgl@sss.pgh.pa.us 1500 [ - + ]: 70 : Assert(drop->removeType == OBJECT_INDEX);
1501 [ + + ]: 70 : if (list_length(drop->objects) != 1)
4391 simon@2ndQuadrant.co 1502 [ + - ]: 3 : ereport(ERROR,
1503 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1504 : : errmsg("DROP INDEX CONCURRENTLY does not support dropping multiple objects")));
1505 [ - + ]: 67 : if (drop->behavior == DROP_CASCADE)
4391 simon@2ndQuadrant.co 1506 [ # # ]:UBC 0 : ereport(ERROR,
1507 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1508 : : errmsg("DROP INDEX CONCURRENTLY does not support CASCADE")));
1509 : : }
1510 : :
1511 : : /*
1512 : : * First we identify all the relations, then we delete them in a single
1513 : : * performMultipleDeletions() call. This is to avoid unwanted DROP
1514 : : * RESTRICT errors if one of the relations depends on another.
1515 : : */
1516 : :
1517 : : /* Determine required relkind */
5783 tgl@sss.pgh.pa.us 1518 [ + + + + :CBC 8040 : switch (drop->removeType)
+ + - ]
1519 : : {
1520 : 6987 : case OBJECT_TABLE:
1521 : 6987 : relkind = RELKIND_RELATION;
1522 : 6987 : break;
1523 : :
1524 : 393 : case OBJECT_INDEX:
1525 : 393 : relkind = RELKIND_INDEX;
1526 : 393 : break;
1527 : :
1528 : 86 : case OBJECT_SEQUENCE:
1529 : 86 : relkind = RELKIND_SEQUENCE;
1530 : 86 : break;
1531 : :
1532 : 440 : case OBJECT_VIEW:
1533 : 440 : relkind = RELKIND_VIEW;
1534 : 440 : break;
1535 : :
4060 kgrittn@postgresql.o 1536 : 60 : case OBJECT_MATVIEW:
1537 : 60 : relkind = RELKIND_MATVIEW;
1538 : 60 : break;
1539 : :
4852 rhaas@postgresql.org 1540 : 74 : case OBJECT_FOREIGN_TABLE:
1541 : 74 : relkind = RELKIND_FOREIGN_TABLE;
1542 : 74 : break;
1543 : :
5783 tgl@sss.pgh.pa.us 1544 :UBC 0 : default:
1545 [ # # ]: 0 : elog(ERROR, "unrecognized drop object type: %d",
1546 : : (int) drop->removeType);
1547 : : relkind = 0; /* keep compiler quiet */
1548 : : break;
1549 : : }
1550 : :
1551 : : /* Lock and validate each relation; build a list of object addresses */
5783 tgl@sss.pgh.pa.us 1552 :CBC 8040 : objects = new_object_addresses();
1553 : :
1554 [ + - + + : 17822 : foreach(cell, drop->objects)
+ + ]
1555 : : {
1556 : 9860 : RangeVar *rel = makeRangeVarFromNameList((List *) lfirst(cell));
1557 : : Oid relOid;
1558 : : ObjectAddress obj;
1559 : : struct DropRelationCallbackState state;
1560 : :
1561 : : /*
1562 : : * These next few steps are a great deal like relation_openrv, but we
1563 : : * don't bother building a relcache entry since we don't need it.
1564 : : *
1565 : : * Check for shared-cache-inval messages before trying to access the
1566 : : * relation. This is needed to cover the case where the name
1567 : : * identifies a rel that has been dropped and recreated since the
1568 : : * start of our transaction: if we don't flush the old syscache entry,
1569 : : * then we'll latch onto that entry and suffer an error later.
1570 : : */
1571 : 9860 : AcceptInvalidationMessages();
1572 : :
1573 : : /* Look up the appropriate relation using namespace search. */
755 1574 : 9860 : state.expected_relkind = relkind;
1575 : 19720 : state.heap_lockmode = drop->concurrent ?
1576 [ + + ]: 9860 : ShareUpdateExclusiveLock : AccessExclusiveLock;
1577 : : /* We must initialize these fields to show that no locks are held: */
4519 rhaas@postgresql.org 1578 : 9860 : state.heapOid = InvalidOid;
2560 1579 : 9860 : state.partParentOid = InvalidOid;
1580 : :
2207 andres@anarazel.de 1581 : 9860 : relOid = RangeVarGetRelidExtended(rel, lockmode, RVR_MISSING_OK,
1582 : : RangeVarCallbackForDropRelation,
1583 : : (void *) &state);
1584 : :
1585 : : /* Not there? */
5783 tgl@sss.pgh.pa.us 1586 [ + + ]: 9850 : if (!OidIsValid(relOid))
1587 : : {
3734 alvherre@alvh.no-ip. 1588 : 538 : DropErrorMsgNonExistent(rel, relkind, drop->missing_ok);
5783 tgl@sss.pgh.pa.us 1589 : 473 : continue;
1590 : : }
1591 : :
1592 : : /*
1593 : : * Decide if concurrent mode needs to be used here or not. The
1594 : : * callback retrieved the rel's persistence for us.
1595 : : */
1544 michael@paquier.xyz 1596 [ + + ]: 9312 : if (drop->concurrent &&
755 tgl@sss.pgh.pa.us 1597 [ + + ]: 64 : state.actual_relpersistence != RELPERSISTENCE_TEMP)
1598 : : {
1544 michael@paquier.xyz 1599 [ + - - + ]: 55 : Assert(list_length(drop->objects) == 1 &&
1600 : : drop->removeType == OBJECT_INDEX);
1601 : 55 : flags |= PERFORM_DELETION_CONCURRENTLY;
1602 : : }
1603 : :
1604 : : /*
1605 : : * Concurrent index drop cannot be used with partitioned indexes,
1606 : : * either.
1607 : : */
1321 alvherre@alvh.no-ip. 1608 [ + + ]: 9312 : if ((flags & PERFORM_DELETION_CONCURRENTLY) != 0 &&
755 tgl@sss.pgh.pa.us 1609 [ + + ]: 55 : state.actual_relkind == RELKIND_PARTITIONED_INDEX)
1321 alvherre@alvh.no-ip. 1610 [ + - ]: 3 : ereport(ERROR,
1611 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1612 : : errmsg("cannot drop partitioned index \"%s\" concurrently",
1613 : : rel->relname)));
1614 : :
1615 : : /*
1616 : : * If we're told to drop a partitioned index, we must acquire lock on
1617 : : * all the children of its parent partitioned table before proceeding.
1618 : : * Otherwise we'd try to lock the child index partitions before their
1619 : : * tables, leading to potential deadlock against other sessions that
1620 : : * will lock those objects in the other order.
1621 : : */
755 tgl@sss.pgh.pa.us 1622 [ + + ]: 9309 : if (state.actual_relkind == RELKIND_PARTITIONED_INDEX)
1623 : 35 : (void) find_all_inheritors(state.heapOid,
1624 : : state.heap_lockmode,
1625 : : NULL);
1626 : :
1627 : : /* OK, we're ready to delete this one */
5783 1628 : 9309 : obj.classId = RelationRelationId;
1629 : 9309 : obj.objectId = relOid;
1630 : 9309 : obj.objectSubId = 0;
1631 : :
1632 : 9309 : add_exact_object_address(&obj, objects);
1633 : : }
1634 : :
4391 simon@2ndQuadrant.co 1635 : 7962 : performMultipleDeletions(objects, drop->behavior, flags);
1636 : :
5783 tgl@sss.pgh.pa.us 1637 : 7894 : free_object_addresses(objects);
8855 peter_e@gmx.net 1638 : 7894 : }
1639 : :
1640 : : /*
1641 : : * Before acquiring a table lock, check whether we have sufficient rights.
1642 : : * In the case of DROP INDEX, also try to lock the table before the index.
1643 : : * Also, if the table to be dropped is a partition, we try to lock the parent
1644 : : * first.
1645 : : */
1646 : : static void
4519 rhaas@postgresql.org 1647 : 10039 : RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
1648 : : void *arg)
1649 : : {
1650 : : HeapTuple tuple;
1651 : : struct DropRelationCallbackState *state;
1652 : : char expected_relkind;
1653 : : bool is_partition;
1654 : : Form_pg_class classform;
1655 : : LOCKMODE heap_lockmode;
1843 peter@eisentraut.org 1656 : 10039 : bool invalid_system_index = false;
1657 : :
4519 rhaas@postgresql.org 1658 : 10039 : state = (struct DropRelationCallbackState *) arg;
755 tgl@sss.pgh.pa.us 1659 : 10039 : heap_lockmode = state->heap_lockmode;
1660 : :
1661 : : /*
1662 : : * If we previously locked some other index's heap, and the name we're
1663 : : * looking up no longer refers to that relation, release the now-useless
1664 : : * lock.
1665 : : */
4519 rhaas@postgresql.org 1666 [ + + - + ]: 10039 : if (relOid != oldRelOid && OidIsValid(state->heapOid))
1667 : : {
4391 simon@2ndQuadrant.co 1668 :UBC 0 : UnlockRelationOid(state->heapOid, heap_lockmode);
4519 rhaas@postgresql.org 1669 : 0 : state->heapOid = InvalidOid;
1670 : : }
1671 : :
1672 : : /*
1673 : : * Similarly, if we previously locked some other partition's heap, and the
1674 : : * name we're looking up no longer refers to that relation, release the
1675 : : * now-useless lock.
1676 : : */
2560 rhaas@postgresql.org 1677 [ + + - + ]:CBC 10039 : if (relOid != oldRelOid && OidIsValid(state->partParentOid))
1678 : : {
2560 rhaas@postgresql.org 1679 :UBC 0 : UnlockRelationOid(state->partParentOid, AccessExclusiveLock);
1680 : 0 : state->partParentOid = InvalidOid;
1681 : : }
1682 : :
1683 : : /* Didn't find a relation, so no need for locking or permission checks. */
4519 rhaas@postgresql.org 1684 [ + + ]:CBC 10039 : if (!OidIsValid(relOid))
1685 : 546 : return;
1686 : :
1687 : 9493 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
1688 [ - + ]: 9493 : if (!HeapTupleIsValid(tuple))
4519 rhaas@postgresql.org 1689 :UBC 0 : return; /* concurrently dropped, so nothing to do */
4519 rhaas@postgresql.org 1690 :CBC 9493 : classform = (Form_pg_class) GETSTRUCT(tuple);
2560 1691 : 9493 : is_partition = classform->relispartition;
1692 : :
1693 : : /* Pass back some data to save lookups in RemoveRelations */
755 tgl@sss.pgh.pa.us 1694 : 9493 : state->actual_relkind = classform->relkind;
1695 : 9493 : state->actual_relpersistence = classform->relpersistence;
1696 : :
1697 : : /*
1698 : : * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
1699 : : * but RemoveRelations() can only pass one relkind for a given relation.
1700 : : * It chooses RELKIND_RELATION for both regular and partitioned tables.
1701 : : * That means we must be careful before giving the wrong type error when
1702 : : * the relation is RELKIND_PARTITIONED_TABLE. An equivalent problem
1703 : : * exists with indexes.
1704 : : */
2685 rhaas@postgresql.org 1705 [ + + ]: 9493 : if (classform->relkind == RELKIND_PARTITIONED_TABLE)
1706 : 1484 : expected_relkind = RELKIND_RELATION;
2277 alvherre@alvh.no-ip. 1707 [ + + ]: 8009 : else if (classform->relkind == RELKIND_PARTITIONED_INDEX)
1708 : 41 : expected_relkind = RELKIND_INDEX;
1709 : : else
2685 rhaas@postgresql.org 1710 : 7968 : expected_relkind = classform->relkind;
1711 : :
755 tgl@sss.pgh.pa.us 1712 [ - + ]: 9493 : if (state->expected_relkind != expected_relkind)
755 tgl@sss.pgh.pa.us 1713 :UBC 0 : DropErrorMsgWrongType(rel->relname, classform->relkind,
1714 : 0 : state->expected_relkind);
1715 : :
1716 : : /* Allow DROP to either table owner or schema owner */
518 peter@eisentraut.org 1717 [ + + ]:CBC 9493 : if (!object_ownercheck(RelationRelationId, relOid, GetUserId()) &&
1718 [ + - ]: 9 : !object_ownercheck(NamespaceRelationId, classform->relnamespace, GetUserId()))
755 tgl@sss.pgh.pa.us 1719 : 9 : aclcheck_error(ACLCHECK_NOT_OWNER,
1720 : 9 : get_relkind_objtype(classform->relkind),
4519 rhaas@postgresql.org 1721 : 9 : rel->relname);
1722 : :
1723 : : /*
1724 : : * Check the case of a system index that might have been invalidated by a
1725 : : * failed concurrent process and allow its drop. For the time being, this
1726 : : * only concerns indexes of toast relations that became invalid during a
1727 : : * REINDEX CONCURRENTLY process.
1728 : : */
755 tgl@sss.pgh.pa.us 1729 [ + + - + ]: 9484 : if (IsSystemClass(relOid, classform) && classform->relkind == RELKIND_INDEX)
1730 : : {
1731 : : HeapTuple locTuple;
1732 : : Form_pg_index indexform;
1733 : : bool indisvalid;
1734 : :
1843 peter@eisentraut.org 1735 :UBC 0 : locTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(relOid));
1736 [ # # ]: 0 : if (!HeapTupleIsValid(locTuple))
1737 : : {
1738 : 0 : ReleaseSysCache(tuple);
1739 : 0 : return;
1740 : : }
1741 : :
1742 : 0 : indexform = (Form_pg_index) GETSTRUCT(locTuple);
1743 : 0 : indisvalid = indexform->indisvalid;
1744 : 0 : ReleaseSysCache(locTuple);
1745 : :
1746 : : /* Mark object as being an invalid index of system catalogs */
1747 [ # # ]: 0 : if (!indisvalid)
1748 : 0 : invalid_system_index = true;
1749 : : }
1750 : :
1751 : : /* In the case of an invalid index, it is fine to bypass this check */
1843 peter@eisentraut.org 1752 [ + - + + :CBC 9484 : if (!invalid_system_index && !allowSystemTableMods && IsSystemClass(relOid, classform))
+ + ]
4519 rhaas@postgresql.org 1753 [ + - ]: 1 : ereport(ERROR,
1754 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1755 : : errmsg("permission denied: \"%s\" is a system catalog",
1756 : : rel->relname)));
1757 : :
1758 : 9483 : ReleaseSysCache(tuple);
1759 : :
1760 : : /*
1761 : : * In DROP INDEX, attempt to acquire lock on the parent table before
1762 : : * locking the index. index_drop() will need this anyway, and since
1763 : : * regular queries lock tables before their indexes, we risk deadlock if
1764 : : * we do it the other way around. No error if we don't find a pg_index
1765 : : * entry, though --- the relation may have been dropped. Note that this
1766 : : * code will execute for either plain or partitioned indexes.
1767 : : */
755 tgl@sss.pgh.pa.us 1768 [ + + + + ]: 9483 : if (expected_relkind == RELKIND_INDEX &&
1769 : : relOid != oldRelOid)
1770 : : {
4519 rhaas@postgresql.org 1771 : 387 : state->heapOid = IndexGetRelation(relOid, true);
1772 [ + - ]: 387 : if (OidIsValid(state->heapOid))
4391 simon@2ndQuadrant.co 1773 : 387 : LockRelationOid(state->heapOid, heap_lockmode);
1774 : : }
1775 : :
1776 : : /*
1777 : : * Similarly, if the relation is a partition, we must acquire lock on its
1778 : : * parent before locking the partition. That's because queries lock the
1779 : : * parent before its partitions, so we risk deadlock if we do it the other
1780 : : * way around.
1781 : : */
2560 rhaas@postgresql.org 1782 [ + + + + ]: 9483 : if (is_partition && relOid != oldRelOid)
1783 : : {
1116 alvherre@alvh.no-ip. 1784 : 294 : state->partParentOid = get_partition_parent(relOid, true);
2560 rhaas@postgresql.org 1785 [ + - ]: 294 : if (OidIsValid(state->partParentOid))
1786 : 294 : LockRelationOid(state->partParentOid, AccessExclusiveLock);
1787 : : }
1788 : : }
1789 : :
1790 : : /*
1791 : : * ExecuteTruncate
1792 : : * Executes a TRUNCATE command.
1793 : : *
1794 : : * This is a multi-relation truncate. We first open and grab exclusive
1795 : : * lock on all relations involved, checking permissions and otherwise
1796 : : * verifying that the relation is OK for truncation. Note that if relations
1797 : : * are foreign tables, at this stage, we have not yet checked that their
1798 : : * foreign data in external data sources are OK for truncation. These are
1799 : : * checked when foreign data are actually truncated later. In CASCADE mode,
1800 : : * relations having FK references to the targeted relations are automatically
1801 : : * added to the group; in RESTRICT mode, we check that all FK references are
1802 : : * internal to the group that's being truncated. Finally all the relations
1803 : : * are truncated and reindexed.
1804 : : */
1805 : : void
6617 tgl@sss.pgh.pa.us 1806 : 696 : ExecuteTruncate(TruncateStmt *stmt)
1807 : : {
6756 bruce@momjian.us 1808 : 696 : List *rels = NIL;
6617 tgl@sss.pgh.pa.us 1809 : 696 : List *relids = NIL;
2199 peter_e@gmx.net 1810 : 696 : List *relids_logged = NIL;
1811 : : ListCell *cell;
1812 : :
1813 : : /*
1814 : : * Open, exclusive-lock, and check all the explicitly-specified relations
1815 : : */
6617 tgl@sss.pgh.pa.us 1816 [ + - + + : 1505 : foreach(cell, stmt->relations)
+ + ]
1817 : : {
7017 1818 : 833 : RangeVar *rv = lfirst(cell);
1819 : : Relation rel;
2669 1820 : 833 : bool recurse = rv->inh;
1821 : : Oid myrelid;
1935 michael@paquier.xyz 1822 : 833 : LOCKMODE lockmode = AccessExclusiveLock;
1823 : :
1824 : 833 : myrelid = RangeVarGetRelidExtended(rv, lockmode,
1825 : : 0, RangeVarCallbackForTruncate,
1826 : : NULL);
1827 : :
1828 : : /* don't throw error for "TRUNCATE foo, foo" */
5571 peter_e@gmx.net 1829 [ + + ]: 815 : if (list_member_oid(relids, myrelid))
5751 tgl@sss.pgh.pa.us 1830 : 1 : continue;
1831 : :
1832 : : /* open the relation, we already hold a lock on it */
1098 fujii@postgresql.org 1833 : 814 : rel = table_open(myrelid, NoLock);
1834 : :
1835 : : /*
1836 : : * RangeVarGetRelidExtended() has done most checks with its callback,
1837 : : * but other checks with the now-opened Relation remain.
1838 : : */
2074 michael@paquier.xyz 1839 : 814 : truncate_check_activity(rel);
1840 : :
6617 tgl@sss.pgh.pa.us 1841 : 814 : rels = lappend(rels, rel);
5571 peter_e@gmx.net 1842 : 814 : relids = lappend_oid(relids, myrelid);
1843 : :
1844 : : /* Log this relation only if needed for logical decoding */
2199 1845 [ + + + - : 814 : if (RelationIsLogicallyLogged(rel))
- + - - -
- + - +
+ ]
1846 : 32 : relids_logged = lappend_oid(relids_logged, myrelid);
1847 : :
5571 1848 [ + + ]: 814 : if (recurse)
1849 : : {
1850 : : ListCell *child;
1851 : : List *children;
1852 : :
1935 michael@paquier.xyz 1853 : 787 : children = find_all_inheritors(myrelid, lockmode, NULL);
1854 : :
5571 peter_e@gmx.net 1855 [ + - + + : 2440 : foreach(child, children)
+ + ]
1856 : : {
1857 : 1653 : Oid childrelid = lfirst_oid(child);
1858 : :
1859 [ + + ]: 1653 : if (list_member_oid(relids, childrelid))
1860 : 787 : continue;
1861 : :
1862 : : /* find_all_inheritors already got lock */
1910 andres@anarazel.de 1863 : 866 : rel = table_open(childrelid, NoLock);
1864 : :
1865 : : /*
1866 : : * It is possible that the parent table has children that are
1867 : : * temp tables of other backends. We cannot safely access
1868 : : * such tables (because of buffering issues), and the best
1869 : : * thing to do is to silently ignore them. Note that this
1870 : : * check is the same as one of the checks done in
1871 : : * truncate_check_activity() called below, still it is kept
1872 : : * here for simplicity.
1873 : : */
1935 michael@paquier.xyz 1874 [ + + + + ]: 866 : if (RELATION_IS_OTHER_TEMP(rel))
1875 : : {
1910 andres@anarazel.de 1876 : 4 : table_close(rel, lockmode);
1935 michael@paquier.xyz 1877 : 4 : continue;
1878 : : }
1879 : :
1880 : : /*
1881 : : * Inherited TRUNCATE commands perform access permission
1882 : : * checks on the parent table only. So we skip checking the
1883 : : * children's permissions and don't call
1884 : : * truncate_check_perms() here.
1885 : : */
2074 1886 : 862 : truncate_check_rel(RelationGetRelid(rel), rel->rd_rel);
1887 : 862 : truncate_check_activity(rel);
1888 : :
5571 peter_e@gmx.net 1889 : 862 : rels = lappend(rels, rel);
1890 : 862 : relids = lappend_oid(relids, childrelid);
1891 : :
1892 : : /* Log this relation only if needed for logical decoding */
2199 1893 [ + + + - : 862 : if (RelationIsLogicallyLogged(rel))
- + - - -
- + - +
- ]
1894 : 11 : relids_logged = lappend_oid(relids_logged, childrelid);
1895 : : }
1896 : : }
2685 rhaas@postgresql.org 1897 [ + + ]: 27 : else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
1898 [ + - ]: 6 : ereport(ERROR,
1899 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
1900 : : errmsg("cannot truncate only a partitioned table"),
1901 : : errhint("Do not specify the ONLY keyword, or use TRUNCATE ONLY on the partitions directly.")));
1902 : : }
1903 : :
1083 fujii@postgresql.org 1904 : 672 : ExecuteTruncateGuts(rels, relids, relids_logged,
376 rhaas@postgresql.org 1905 : 672 : stmt->behavior, stmt->restart_seqs, false);
1906 : :
1907 : : /* And close the rels */
2199 peter_e@gmx.net 1908 [ + - + + : 2224 : foreach(cell, rels)
+ + ]
1909 : : {
1910 : 1593 : Relation rel = (Relation) lfirst(cell);
1911 : :
1910 andres@anarazel.de 1912 : 1593 : table_close(rel, NoLock);
1913 : : }
2199 peter_e@gmx.net 1914 : 631 : }
1915 : :
1916 : : /*
1917 : : * ExecuteTruncateGuts
1918 : : *
1919 : : * Internal implementation of TRUNCATE. This is called by the actual TRUNCATE
1920 : : * command (see above) as well as replication subscribers that execute a
1921 : : * replicated TRUNCATE action.
1922 : : *
1923 : : * explicit_rels is the list of Relations to truncate that the command
1924 : : * specified. relids is the list of Oids corresponding to explicit_rels.
1925 : : * relids_logged is the list of Oids (a subset of relids) that require
1926 : : * WAL-logging. This is all a bit redundant, but the existing callers have
1927 : : * this information handy in this form.
1928 : : */
1929 : : void
1102 fujii@postgresql.org 1930 : 691 : ExecuteTruncateGuts(List *explicit_rels,
1931 : : List *relids,
1932 : : List *relids_logged,
1933 : : DropBehavior behavior, bool restart_seqs,
1934 : : bool run_as_table_owner)
1935 : : {
1936 : : List *rels;
2199 peter_e@gmx.net 1937 : 691 : List *seq_relids = NIL;
1102 fujii@postgresql.org 1938 : 691 : HTAB *ft_htab = NULL;
1939 : : EState *estate;
1940 : : ResultRelInfo *resultRelInfos;
1941 : : ResultRelInfo *resultRelInfo;
1942 : : SubTransactionId mySubid;
1943 : : ListCell *cell;
1944 : : Oid *logrelids;
1945 : :
1946 : : /*
1947 : : * Check the explicitly-specified relations.
1948 : : *
1949 : : * In CASCADE mode, suck in all referencing relations as well. This
1950 : : * requires multiple iterations to find indirectly-dependent relations. At
1951 : : * each phase, we need to exclusive-lock new rels before looking for their
1952 : : * dependencies, else we might miss something. Also, we check each rel as
1953 : : * soon as we open it, to avoid a faux pas such as holding lock for a long
1954 : : * time on a rel we have no permissions for.
1955 : : */
2199 peter_e@gmx.net 1956 : 691 : rels = list_copy(explicit_rels);
1957 [ + + ]: 691 : if (behavior == DROP_CASCADE)
1958 : : {
1959 : : for (;;)
6617 tgl@sss.pgh.pa.us 1960 : 20 : {
1961 : : List *newrelids;
1962 : :
1963 : 40 : newrelids = heap_truncate_find_FKs(relids);
1964 [ + + ]: 40 : if (newrelids == NIL)
1965 : 20 : break; /* nothing else to add */
1966 : :
1967 [ + - + + : 67 : foreach(cell, newrelids)
+ + ]
1968 : : {
6402 bruce@momjian.us 1969 : 47 : Oid relid = lfirst_oid(cell);
1970 : : Relation rel;
1971 : :
1910 andres@anarazel.de 1972 : 47 : rel = table_open(relid, AccessExclusiveLock);
6617 tgl@sss.pgh.pa.us 1973 [ + - ]: 47 : ereport(NOTICE,
1974 : : (errmsg("truncate cascades to table \"%s\"",
1975 : : RelationGetRelationName(rel))));
2074 michael@paquier.xyz 1976 : 47 : truncate_check_rel(relid, rel->rd_rel);
1535 fujii@postgresql.org 1977 : 47 : truncate_check_perms(relid, rel->rd_rel);
2074 michael@paquier.xyz 1978 : 47 : truncate_check_activity(rel);
6617 tgl@sss.pgh.pa.us 1979 : 47 : rels = lappend(rels, rel);
1980 : 47 : relids = lappend_oid(relids, relid);
1981 : :
1982 : : /* Log this relation only if needed for logical decoding */
2199 peter_e@gmx.net 1983 [ - + - - : 47 : if (RelationIsLogicallyLogged(rel))
- - - - -
- - - -
- ]
2199 peter_e@gmx.net 1984 :UBC 0 : relids_logged = lappend_oid(relids_logged, relid);
1985 : : }
1986 : : }
1987 : : }
1988 : :
1989 : : /*
1990 : : * Check foreign key references. In CASCADE mode, this should be
1991 : : * unnecessary since we just pulled in all the references; but as a
1992 : : * cross-check, do it anyway if in an Assert-enabled build.
1993 : : */
1994 : : #ifdef USE_ASSERT_CHECKING
7017 tgl@sss.pgh.pa.us 1995 :CBC 691 : heap_truncate_check_FKs(rels, false);
1996 : : #else
1997 : : if (behavior == DROP_RESTRICT)
1998 : : heap_truncate_check_FKs(rels, false);
1999 : : #endif
2000 : :
2001 : : /*
2002 : : * If we are asked to restart sequences, find all the sequences, lock them
2003 : : * (we need AccessExclusiveLock for ResetSequence), and check permissions.
2004 : : * We want to do this early since it's pointless to do all the truncation
2005 : : * work only to fail on sequence permissions.
2006 : : */
2199 peter_e@gmx.net 2007 [ + + ]: 654 : if (restart_seqs)
2008 : : {
5812 tgl@sss.pgh.pa.us 2009 [ + - + + : 26 : foreach(cell, rels)
+ + ]
2010 : : {
2011 : 13 : Relation rel = (Relation) lfirst(cell);
1728 peter@eisentraut.org 2012 : 13 : List *seqlist = getOwnedSequences(RelationGetRelid(rel));
2013 : : ListCell *seqcell;
2014 : :
5812 tgl@sss.pgh.pa.us 2015 [ + + + + : 31 : foreach(seqcell, seqlist)
+ + ]
2016 : : {
5421 bruce@momjian.us 2017 : 18 : Oid seq_relid = lfirst_oid(seqcell);
2018 : : Relation seq_rel;
2019 : :
4897 tgl@sss.pgh.pa.us 2020 : 18 : seq_rel = relation_open(seq_relid, AccessExclusiveLock);
2021 : :
2022 : : /* This check must match AlterSequence! */
518 peter@eisentraut.org 2023 [ - + ]: 18 : if (!object_ownercheck(RelationRelationId, seq_relid, GetUserId()))
2325 peter_e@gmx.net 2024 :UBC 0 : aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SEQUENCE,
5812 tgl@sss.pgh.pa.us 2025 : 0 : RelationGetRelationName(seq_rel));
2026 : :
5812 tgl@sss.pgh.pa.us 2027 :CBC 18 : seq_relids = lappend_oid(seq_relids, seq_relid);
2028 : :
2029 : 18 : relation_close(seq_rel, NoLock);
2030 : : }
2031 : : }
2032 : : }
2033 : :
2034 : : /* Prepare to catch AFTER triggers. */
5861 2035 : 654 : AfterTriggerBeginQuery();
2036 : :
2037 : : /*
2038 : : * To fire triggers, we'll need an EState as well as a ResultRelInfo for
2039 : : * each relation. We don't need to call ExecOpenIndices, though.
2040 : : *
2041 : : * We put the ResultRelInfos in the es_opened_result_relations list, even
2042 : : * though we don't have a range table and don't populate the
2043 : : * es_result_relations array. That's a bit bogus, but it's enough to make
2044 : : * ExecGetTriggerResultRel() find them.
2045 : : */
2046 : 654 : estate = CreateExecutorState();
2047 : : resultRelInfos = (ResultRelInfo *)
2048 : 654 : palloc(list_length(rels) * sizeof(ResultRelInfo));
2049 : 654 : resultRelInfo = resultRelInfos;
2050 [ + - + + : 2333 : foreach(cell, rels)
+ + ]
2051 : : {
2052 : 1679 : Relation rel = (Relation) lfirst(cell);
2053 : :
2054 : 1679 : InitResultRelInfo(resultRelInfo,
2055 : : rel,
2056 : : 0, /* dummy rangetable index */
2057 : : NULL,
2058 : : 0);
1279 heikki.linnakangas@i 2059 : 1679 : estate->es_opened_result_relations =
2060 : 1679 : lappend(estate->es_opened_result_relations, resultRelInfo);
5861 tgl@sss.pgh.pa.us 2061 : 1679 : resultRelInfo++;
2062 : : }
2063 : :
2064 : : /*
2065 : : * Process all BEFORE STATEMENT TRUNCATE triggers before we begin
2066 : : * truncating (this is because one of them might throw an error). Also, if
2067 : : * we were to allow them to prevent statement execution, that would need
2068 : : * to be handled here.
2069 : : */
2070 : 654 : resultRelInfo = resultRelInfos;
2071 [ + - + + : 2333 : foreach(cell, rels)
+ + ]
2072 : : {
2073 : : UserContext ucxt;
2074 : :
376 rhaas@postgresql.org 2075 [ + + ]: 1679 : if (run_as_table_owner)
2076 : 35 : SwitchToUntrustedUser(resultRelInfo->ri_RelationDesc->rd_rel->relowner,
2077 : : &ucxt);
5861 tgl@sss.pgh.pa.us 2078 : 1679 : ExecBSTruncateTriggers(estate, resultRelInfo);
376 rhaas@postgresql.org 2079 [ + + ]: 1679 : if (run_as_table_owner)
2080 : 35 : RestoreUserContext(&ucxt);
5861 tgl@sss.pgh.pa.us 2081 : 1679 : resultRelInfo++;
2082 : : }
2083 : :
2084 : : /*
2085 : : * OK, truncate each table.
2086 : : */
5348 2087 : 654 : mySubid = GetCurrentSubTransactionId();
2088 : :
1083 fujii@postgresql.org 2089 [ + - + + : 2333 : foreach(cell, rels)
+ + ]
2090 : : {
2091 : 1679 : Relation rel = (Relation) lfirst(cell);
2092 : :
2093 : : /* Skip partitioned tables as there is nothing to do */
2600 rhaas@postgresql.org 2094 [ + + ]: 1679 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
2095 : 346 : continue;
2096 : :
2097 : : /*
2098 : : * Build the lists of foreign tables belonging to each foreign server
2099 : : * and pass each list to the foreign data wrapper's callback function,
2100 : : * so that each server can truncate its all foreign tables in bulk.
2101 : : * Each list is saved as a single entry in a hash table that uses the
2102 : : * server OID as lookup key.
2103 : : */
1102 fujii@postgresql.org 2104 [ + + ]: 1333 : if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
2105 : 17 : {
2106 : 17 : Oid serverid = GetForeignServerIdByRelId(RelationGetRelid(rel));
2107 : : bool found;
2108 : : ForeignTruncateInfo *ft_info;
2109 : :
2110 : : /* First time through, initialize hashtable for foreign tables */
2111 [ + + ]: 17 : if (!ft_htab)
2112 : : {
2113 : : HASHCTL hctl;
2114 : :
2115 : 15 : memset(&hctl, 0, sizeof(HASHCTL));
2116 : 15 : hctl.keysize = sizeof(Oid);
2117 : 15 : hctl.entrysize = sizeof(ForeignTruncateInfo);
2118 : 15 : hctl.hcxt = CurrentMemoryContext;
2119 : :
2120 : 15 : ft_htab = hash_create("TRUNCATE for Foreign Tables",
2121 : : 32, /* start small and extend */
2122 : : &hctl,
2123 : : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
2124 : : }
2125 : :
2126 : : /* Find or create cached entry for the foreign table */
2127 : 17 : ft_info = hash_search(ft_htab, &serverid, HASH_ENTER, &found);
2128 [ + + ]: 17 : if (!found)
2129 : 15 : ft_info->rels = NIL;
2130 : :
2131 : : /*
2132 : : * Save the foreign table in the entry of the server that the
2133 : : * foreign table belongs to.
2134 : : */
2135 : 17 : ft_info->rels = lappend(ft_info->rels, rel);
2136 : 17 : continue;
2137 : : }
2138 : :
2139 : : /*
2140 : : * Normally, we need a transaction-safe truncation here. However, if
2141 : : * the table was either created in the current (sub)transaction or has
2142 : : * a new relfilenumber in the current (sub)transaction, then we can
2143 : : * just truncate it in-place, because a rollback would cause the whole
2144 : : * table or the current physical file to be thrown away anyway.
2145 : : */
5348 tgl@sss.pgh.pa.us 2146 [ + + ]: 1316 : if (rel->rd_createSubid == mySubid ||
648 rhaas@postgresql.org 2147 [ + + ]: 1308 : rel->rd_newRelfilelocatorSubid == mySubid)
2148 : : {
2149 : : /* Immediate, non-rollbackable truncation is OK */
5348 tgl@sss.pgh.pa.us 2150 : 24 : heap_truncate_one_rel(rel);
2151 : : }
2152 : : else
2153 : : {
2154 : : Oid heap_relid;
2155 : : Oid toast_relid;
1182 michael@paquier.xyz 2156 : 1292 : ReindexParams reindex_params = {0};
2157 : :
2158 : : /*
2159 : : * This effectively deletes all rows in the table, and may be done
2160 : : * in a serializable transaction. In that case we must record a
2161 : : * rw-conflict in to this transaction from each transaction
2162 : : * holding a predicate lock on the table.
2163 : : */
4694 heikki.linnakangas@i 2164 : 1292 : CheckTableForSerializableConflictIn(rel);
2165 : :
2166 : : /*
2167 : : * Need the full transaction-safe pushups.
2168 : : *
2169 : : * Create a new empty storage file for the relation, and assign it
2170 : : * as the relfilenumber value. The old storage file is scheduled
2171 : : * for deletion at commit.
2172 : : */
648 rhaas@postgresql.org 2173 : 1292 : RelationSetNewRelfilenumber(rel, rel->rd_rel->relpersistence);
2174 : :
5348 tgl@sss.pgh.pa.us 2175 : 1292 : heap_relid = RelationGetRelid(rel);
2176 : :
2177 : : /*
2178 : : * The same for the toast table, if any.
2179 : : */
1955 2180 : 1292 : toast_relid = rel->rd_rel->reltoastrelid;
5348 2181 [ + + ]: 1292 : if (OidIsValid(toast_relid))
2182 : : {
1955 2183 : 798 : Relation toastrel = relation_open(toast_relid,
2184 : : AccessExclusiveLock);
2185 : :
648 rhaas@postgresql.org 2186 : 798 : RelationSetNewRelfilenumber(toastrel,
2187 : 798 : toastrel->rd_rel->relpersistence);
1910 andres@anarazel.de 2188 : 798 : table_close(toastrel, NoLock);
2189 : : }
2190 : :
2191 : : /*
2192 : : * Reconstruct the indexes to match, and we're done.
2193 : : */
132 michael@paquier.xyz 2194 :GNC 1292 : reindex_relation(NULL, heap_relid, REINDEX_REL_PROCESS_TOAST,
2195 : : &reindex_params);
2196 : : }
2197 : :
3341 alvherre@alvh.no-ip. 2198 :CBC 1316 : pgstat_count_truncate(rel);
2199 : : }
2200 : :
2201 : : /* Now go through the hash table, and truncate foreign tables */
1102 fujii@postgresql.org 2202 [ + + ]: 654 : if (ft_htab)
2203 : : {
2204 : : ForeignTruncateInfo *ft_info;
2205 : : HASH_SEQ_STATUS seq;
2206 : :
2207 : 15 : hash_seq_init(&seq, ft_htab);
2208 : :
2209 [ + + ]: 15 : PG_TRY();
2210 : : {
2211 [ + + ]: 26 : while ((ft_info = hash_seq_search(&seq)) != NULL)
2212 : : {
2213 : 15 : FdwRoutine *routine = GetFdwRoutineByServerId(ft_info->serverid);
2214 : :
2215 : : /* truncate_check_rel() has checked that already */
2216 [ - + ]: 15 : Assert(routine->ExecForeignTruncate != NULL);
2217 : :
2218 : 15 : routine->ExecForeignTruncate(ft_info->rels,
2219 : : behavior,
2220 : : restart_seqs);
2221 : : }
2222 : : }
2223 : 4 : PG_FINALLY();
2224 : : {
2225 : 15 : hash_destroy(ft_htab);
2226 : : }
2227 [ + + ]: 15 : PG_END_TRY();
2228 : : }
2229 : :
2230 : : /*
2231 : : * Restart owned sequences if we were asked to.
2232 : : */
4897 tgl@sss.pgh.pa.us 2233 [ + + + + : 668 : foreach(cell, seq_relids)
+ + ]
2234 : : {
2235 : 18 : Oid seq_relid = lfirst_oid(cell);
2236 : :
2237 : 18 : ResetSequence(seq_relid);
2238 : : }
2239 : :
2240 : : /*
2241 : : * Write a WAL record to allow this set of actions to be logically
2242 : : * decoded.
2243 : : *
2244 : : * Assemble an array of relids so we can write a single WAL record for the
2245 : : * whole action.
2246 : : */
606 2247 [ + + ]: 650 : if (relids_logged != NIL)
2248 : : {
2249 : : xl_heap_truncate xlrec;
2199 peter_e@gmx.net 2250 : 25 : int i = 0;
2251 : :
2252 : : /* should only get here if wal_level >= logical */
2253 [ - + ]: 25 : Assert(XLogLogicalInfoActive());
2254 : :
2255 : 25 : logrelids = palloc(list_length(relids_logged) * sizeof(Oid));
2180 tgl@sss.pgh.pa.us 2256 [ + - + + : 68 : foreach(cell, relids_logged)
+ + ]
2199 peter_e@gmx.net 2257 : 43 : logrelids[i++] = lfirst_oid(cell);
2258 : :
2259 : 25 : xlrec.dbId = MyDatabaseId;
2260 : 25 : xlrec.nrelids = list_length(relids_logged);
2261 : 25 : xlrec.flags = 0;
2262 [ + + ]: 25 : if (behavior == DROP_CASCADE)
2263 : 1 : xlrec.flags |= XLH_TRUNCATE_CASCADE;
2264 [ + + ]: 25 : if (restart_seqs)
2265 : 2 : xlrec.flags |= XLH_TRUNCATE_RESTART_SEQS;
2266 : :
2267 : 25 : XLogBeginInsert();
2268 : 25 : XLogRegisterData((char *) &xlrec, SizeOfHeapTruncate);
2269 : 25 : XLogRegisterData((char *) logrelids, list_length(relids_logged) * sizeof(Oid));
2270 : :
2271 : 25 : XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);
2272 : :
2273 : 25 : (void) XLogInsert(RM_HEAP_ID, XLOG_HEAP_TRUNCATE);
2274 : : }
2275 : :
2276 : : /*
2277 : : * Process all AFTER STATEMENT TRUNCATE triggers.
2278 : : */
5861 tgl@sss.pgh.pa.us 2279 : 650 : resultRelInfo = resultRelInfos;
2280 [ + - + + : 2325 : foreach(cell, rels)
+ + ]
2281 : : {
2282 : : UserContext ucxt;
2283 : :
376 rhaas@postgresql.org 2284 [ + + ]: 1675 : if (run_as_table_owner)
2285 : 35 : SwitchToUntrustedUser(resultRelInfo->ri_RelationDesc->rd_rel->relowner,
2286 : : &ucxt);
5861 tgl@sss.pgh.pa.us 2287 : 1675 : ExecASTruncateTriggers(estate, resultRelInfo);
376 rhaas@postgresql.org 2288 [ + + ]: 1675 : if (run_as_table_owner)
2289 : 35 : RestoreUserContext(&ucxt);
5861 tgl@sss.pgh.pa.us 2290 : 1675 : resultRelInfo++;
2291 : : }
2292 : :
2293 : : /* Handle queued AFTER triggers */
2294 : 650 : AfterTriggerEndQuery(estate);
2295 : :
2296 : : /* We can clean up the EState now */
2297 : 650 : FreeExecutorState(estate);
2298 : :
2299 : : /*
2300 : : * Close any rels opened by CASCADE (can't do this while EState still
2301 : : * holds refs)
2302 : : */
2199 peter_e@gmx.net 2303 : 650 : rels = list_difference_ptr(rels, explicit_rels);
5858 tgl@sss.pgh.pa.us 2304 [ + + + + : 697 : foreach(cell, rels)
+ + ]
2305 : : {
2306 : 47 : Relation rel = (Relation) lfirst(cell);
2307 : :
1910 andres@anarazel.de 2308 : 47 : table_close(rel, NoLock);
2309 : : }
8024 tgl@sss.pgh.pa.us 2310 : 650 : }
2311 : :
2312 : : /*
2313 : : * Check that a given relation is safe to truncate. Subroutine for
2314 : : * ExecuteTruncate() and RangeVarCallbackForTruncate().
2315 : : */
2316 : : static void
2074 michael@paquier.xyz 2317 : 1826 : truncate_check_rel(Oid relid, Form_pg_class reltuple)
2318 : : {
2319 : 1826 : char *relname = NameStr(reltuple->relname);
2320 : :
2321 : : /*
2322 : : * Only allow truncate on regular tables, foreign tables using foreign
2323 : : * data wrappers supporting TRUNCATE and partitioned tables (although, the
2324 : : * latter are only being included here for the following checks; no
2325 : : * physical truncation will occur in their case.).
2326 : : */
1102 fujii@postgresql.org 2327 [ + + ]: 1826 : if (reltuple->relkind == RELKIND_FOREIGN_TABLE)
2328 : : {
2329 : 18 : Oid serverid = GetForeignServerIdByRelId(relid);
2330 : 18 : FdwRoutine *fdwroutine = GetFdwRoutineByServerId(serverid);
2331 : :
2332 [ + + ]: 18 : if (!fdwroutine->ExecForeignTruncate)
2333 [ + - ]: 1 : ereport(ERROR,
2334 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2335 : : errmsg("cannot truncate foreign table \"%s\"",
2336 : : relname)));
2337 : : }
2338 [ + + ]: 1808 : else if (reltuple->relkind != RELKIND_RELATION &&
2339 [ - + ]: 355 : reltuple->relkind != RELKIND_PARTITIONED_TABLE)
6617 tgl@sss.pgh.pa.us 2340 [ # # ]:UBC 0 : ereport(ERROR,
2341 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2342 : : errmsg("\"%s\" is not a table", relname)));
2343 : :
2344 : : /*
2345 : : * Most system catalogs can't be truncated at all, or at least not unless
2346 : : * allow_system_table_mods=on. As an exception, however, we allow
2347 : : * pg_largeobject to be truncated as part of pg_upgrade, because we need
2348 : : * to change its relfilenode to match the old cluster, and allowing a
2349 : : * TRUNCATE command to be executed is the easiest way of doing that.
2350 : : */
626 rhaas@postgresql.org 2351 [ + + + + ]:CBC 1825 : if (!allowSystemTableMods && IsSystemClass(relid, reltuple)
2352 [ + + - + ]: 11 : && (!IsBinaryUpgrade || relid != LargeObjectRelationId))
6617 tgl@sss.pgh.pa.us 2353 [ + - ]: 1 : ereport(ERROR,
2354 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
2355 : : errmsg("permission denied: \"%s\" is a system catalog",
2356 : : relname)));
2357 : :
1604 mail@joeconway.com 2358 [ - + ]: 1824 : InvokeObjectTruncateHook(relid);
2074 michael@paquier.xyz 2359 : 1824 : }
2360 : :
2361 : : /*
2362 : : * Check that current user has the permission to truncate given relation.
2363 : : */
2364 : : static void
1535 fujii@postgresql.org 2365 : 962 : truncate_check_perms(Oid relid, Form_pg_class reltuple)
2366 : : {
2367 : 962 : char *relname = NameStr(reltuple->relname);
2368 : : AclResult aclresult;
2369 : :
2370 : : /* Permissions checks */
2371 : 962 : aclresult = pg_class_aclcheck(relid, GetUserId(), ACL_TRUNCATE);
2372 [ + + ]: 962 : if (aclresult != ACLCHECK_OK)
2373 : 16 : aclcheck_error(aclresult, get_relkind_objtype(reltuple->relkind),
2374 : : relname);
2375 : 946 : }
2376 : :
2377 : : /*
2378 : : * Set of extra sanity checks to check if a given relation is safe to
2379 : : * truncate. This is split with truncate_check_rel() as
2380 : : * RangeVarCallbackForTruncate() cannot open a Relation yet.
2381 : : */
2382 : : static void
2074 michael@paquier.xyz 2383 : 1723 : truncate_check_activity(Relation rel)
2384 : : {
2385 : : /*
2386 : : * Don't allow truncate on temp tables of other backends ... their local
2387 : : * buffer manager is not going to cope.
2388 : : */
5493 tgl@sss.pgh.pa.us 2389 [ + + - + ]: 1723 : if (RELATION_IS_OTHER_TEMP(rel))
6617 tgl@sss.pgh.pa.us 2390 [ # # ]:UBC 0 : ereport(ERROR,
2391 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2392 : : errmsg("cannot truncate temporary tables of other sessions")));
2393 : :
2394 : : /*
2395 : : * Also check for active uses of the relation in the current transaction,
2396 : : * including open scans and pending AFTER trigger events.
2397 : : */
5919 tgl@sss.pgh.pa.us 2398 :CBC 1723 : CheckTableNotInUse(rel, "TRUNCATE");
6617 2399 : 1723 : }
2400 : :
2401 : : /*
2402 : : * storage_name
2403 : : * returns the name corresponding to a typstorage/attstorage enum value
2404 : : */
2405 : : static const char *
54 peter@eisentraut.org 2406 : 12 : storage_name(char c)
2407 : : {
2408 [ - - + + : 12 : switch (c)
- ]
2409 : : {
54 peter@eisentraut.org 2410 :UBC 0 : case TYPSTORAGE_PLAIN:
2411 : 0 : return "PLAIN";
2412 : 0 : case TYPSTORAGE_EXTERNAL:
2413 : 0 : return "EXTERNAL";
54 peter@eisentraut.org 2414 :CBC 6 : case TYPSTORAGE_EXTENDED:
2415 : 6 : return "EXTENDED";
2416 : 6 : case TYPSTORAGE_MAIN:
2417 : 6 : return "MAIN";
54 peter@eisentraut.org 2418 :UBC 0 : default:
2419 : 0 : return "???";
2420 : : }
2421 : : }
2422 : :
2423 : : /*----------
2424 : : * MergeAttributes
2425 : : * Returns new schema given initial schema and superclasses.
2426 : : *
2427 : : * Input arguments:
2428 : : * 'columns' is the column/attribute definition for the table. (It's a list
2429 : : * of ColumnDef's.) It is destructively changed.
2430 : : * 'supers' is a list of OIDs of parent relations, already locked by caller.
2431 : : * 'relpersistence' is the persistence type of the table.
2432 : : * 'is_partition' tells if the table is a partition.
2433 : : *
2434 : : * Output arguments:
2435 : : * 'supconstr' receives a list of constraints belonging to the parents,
2436 : : * updated as necessary to be valid for the child.
2437 : : * 'supnotnulls' receives a list of CookedConstraints that corresponds to
2438 : : * constraints coming from inheritance parents.
2439 : : *
2440 : : * Return value:
2441 : : * Completed schema list.
2442 : : *
2443 : : * Notes:
2444 : : * The order in which the attributes are inherited is very important.
2445 : : * Intuitively, the inherited attributes should come first. If a table
2446 : : * inherits from multiple parents, the order of those attributes are
2447 : : * according to the order of the parents specified in CREATE TABLE.
2448 : : *
2449 : : * Here's an example:
2450 : : *
2451 : : * create table person (name text, age int4, location point);
2452 : : * create table emp (salary int4, manager text) inherits(person);
2453 : : * create table student (gpa float8) inherits (person);
2454 : : * create table stud_emp (percent int4) inherits (emp, student);
2455 : : *
2456 : : * The order of the attributes of stud_emp is:
2457 : : *
2458 : : * person {1:name, 2:age, 3:location}
2459 : : * / \
2460 : : * {6:gpa} student emp {4:salary, 5:manager}
2461 : : * \ /
2462 : : * stud_emp {7:percent}
2463 : : *
2464 : : * If the same attribute name appears multiple times, then it appears
2465 : : * in the result table in the proper location for its first appearance.
2466 : : *
2467 : : * Constraints (including not-null constraints) for the child table
2468 : : * are the union of all relevant constraints, from both the child schema
2469 : : * and parent tables. In addition, in legacy inheritance, each column that
2470 : : * appears in a primary key in any of the parents also gets a NOT NULL
2471 : : * constraint (partitioning doesn't need this, because the PK itself gets
2472 : : * inherited.)
2473 : : *
2474 : : * The default value for a child column is defined as:
2475 : : * (1) If the child schema specifies a default, that value is used.
2476 : : * (2) If neither the child nor any parent specifies a default, then
2477 : : * the column will not have a default.
2478 : : * (3) If conflicting defaults are inherited from different parents
2479 : : * (and not overridden by the child), an error is raised.
2480 : : * (4) Otherwise the inherited default is used.
2481 : : *
2482 : : * Note that the default-value infrastructure is used for generated
2483 : : * columns' expressions too, so most of the preceding paragraph applies
2484 : : * to generation expressions too. We insist that a child column be
2485 : : * generated if and only if its parent(s) are, but it need not have
2486 : : * the same generation expression.
2487 : : *----------
2488 : : */
2489 : : static List *
201 peter@eisentraut.org 2490 :GNC 26265 : MergeAttributes(List *columns, const List *supers, char relpersistence,
2491 : : bool is_partition, List **supconstr, List **supnotnulls)
2492 : : {
2493 : 26265 : List *inh_columns = NIL;
8024 tgl@sss.pgh.pa.us 2494 :CBC 26265 : List *constraints = NIL;
233 alvherre@alvh.no-ip. 2495 :GNC 26265 : List *nnconstraints = NIL;
54 peter@eisentraut.org 2496 :CBC 26265 : bool have_bogus_defaults = false;
2497 : : int child_attno;
2498 : : static Node bogus_marker = {0}; /* marks conflicting defaults */
201 peter@eisentraut.org 2499 :GNC 26265 : List *saved_columns = NIL;
2500 : : ListCell *lc;
2501 : :
2502 : : /*
2503 : : * Check for and reject tables with too many columns. We perform this
2504 : : * check relatively early for two reasons: (a) we don't run the risk of
2505 : : * overflowing an AttrNumber in subsequent code (b) an O(n^2) algorithm is
2506 : : * okay if we're processing <= 1600 columns, but could take minutes to
2507 : : * execute if the user attempts to create a table with hundreds of
2508 : : * thousands of columns.
2509 : : *
2510 : : * Note that we also need to check that we do not exceed this figure after
2511 : : * including columns from inherited relations.
2512 : : */
2513 [ - + ]: 26265 : if (list_length(columns) > MaxHeapAttributeNumber)
7089 neilc@samurai.com 2514 [ # # ]:UBC 0 : ereport(ERROR,
2515 : : (errcode(ERRCODE_TOO_MANY_COLUMNS),
2516 : : errmsg("tables can have at most %d columns",
2517 : : MaxHeapAttributeNumber)));
2518 : :
2519 : : /*
2520 : : * Check for duplicate names in the explicit list of attributes.
2521 : : *
2522 : : * Although we might consider merging such entries in the same way that we
2523 : : * handle name conflicts for inherited attributes, it seems to make more
2524 : : * sense to assume such conflicts are errors.
2525 : : *
2526 : : * We don't use foreach() here because we have two nested loops over the
2527 : : * columns list, with possible element deletions in the inner one. If we
2528 : : * used foreach_delete_current() it could only fix up the state of one of
2529 : : * the loops, so it seems cleaner to use looping over list indexes for
2530 : : * both loops. Note that any deletion will happen beyond where the outer
2531 : : * loop is, so its index never needs adjustment.
2532 : : */
201 peter@eisentraut.org 2533 [ + + ]:GNC 121576 : for (int coldefpos = 0; coldefpos < list_length(columns); coldefpos++)
2534 : : {
2535 : 95323 : ColumnDef *coldef = list_nth_node(ColumnDef, columns, coldefpos);
2536 : :
1984 alvherre@alvh.no-ip. 2537 [ + + + + ]:CBC 95323 : if (!is_partition && coldef->typeName == NULL)
2538 : : {
2539 : : /*
2540 : : * Typed table column option that does not belong to a column from
2541 : : * the type. This works because the columns from the type come
2542 : : * first in the list. (We omit this check for partition column
2543 : : * lists; those are processed separately below.)
2544 : : */
5190 peter_e@gmx.net 2545 [ + - ]: 3 : ereport(ERROR,
2546 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
2547 : : errmsg("column \"%s\" does not exist",
2548 : : coldef->colname)));
2549 : : }
2550 : :
2551 : : /* restpos scans all entries beyond coldef; incr is in loop body */
201 peter@eisentraut.org 2552 [ + + ]:GNC 3065783 : for (int restpos = coldefpos + 1; restpos < list_length(columns);)
2553 : : {
2554 : 2970472 : ColumnDef *restdef = list_nth_node(ColumnDef, columns, restpos);
2555 : :
8024 tgl@sss.pgh.pa.us 2556 [ + + ]:CBC 2970472 : if (strcmp(coldef->colname, restdef->colname) == 0)
2557 : : {
5190 peter_e@gmx.net 2558 [ + + ]: 25 : if (coldef->is_from_type)
2559 : : {
2560 : : /*
2561 : : * merge the column options into the column from the type
2562 : : */
2563 : 16 : coldef->is_not_null = restdef->is_not_null;
2564 : 16 : coldef->raw_default = restdef->raw_default;
2565 : 16 : coldef->cooked_default = restdef->cooked_default;
2566 : 16 : coldef->constraints = restdef->constraints;
2567 : 16 : coldef->is_from_type = false;
201 peter@eisentraut.org 2568 :GNC 16 : columns = list_delete_nth_cell(columns, restpos);
2569 : : }
2570 : : else
5190 peter_e@gmx.net 2571 [ + - ]:CBC 9 : ereport(ERROR,
2572 : : (errcode(ERRCODE_DUPLICATE_COLUMN),
2573 : : errmsg("column \"%s\" specified more than once",
2574 : : coldef->colname)));
2575 : : }
2576 : : else
1735 tgl@sss.pgh.pa.us 2577 : 2970447 : restpos++;
2578 : : }
2579 : : }
2580 : :
2581 : : /*
2582 : : * In case of a partition, there are no new column definitions, only dummy
2583 : : * ColumnDefs created for column constraints. Set them aside for now and
2584 : : * process them at the end.
2585 : : */
1984 alvherre@alvh.no-ip. 2586 [ + + ]: 26253 : if (is_partition)
2587 : : {
201 peter@eisentraut.org 2588 :GNC 4125 : saved_columns = columns;
2589 : 4125 : columns = NIL;
2590 : : }
2591 : :
2592 : : /*
2593 : : * Scan the parents left-to-right, and merge their attributes to form a
2594 : : * list of inherited columns (inh_columns).
2595 : : */
8024 tgl@sss.pgh.pa.us 2596 :CBC 26253 : child_attno = 0;
201 peter@eisentraut.org 2597 [ + + + + :GNC 31369 : foreach(lc, supers)
+ + ]
2598 : : {
2599 : 5152 : Oid parent = lfirst_oid(lc);
2600 : : Relation relation;
2601 : : TupleDesc tupleDesc;
2602 : : TupleConstr *constr;
2603 : : AttrMap *newattmap;
2604 : : List *inherited_defaults;
2605 : : List *cols_with_defaults;
2606 : : List *nnconstrs;
2607 : : ListCell *lc1;
2608 : : ListCell *lc2;
2609 : : Bitmapset *pkattrs;
233 alvherre@alvh.no-ip. 2610 : 5152 : Bitmapset *nncols = NULL;
2611 : :
2612 : : /* caller already got lock */
1910 andres@anarazel.de 2613 :CBC 5152 : relation = table_open(parent, NoLock);
2614 : :
2615 : : /*
2616 : : * Check for active uses of the parent partitioned table in the
2617 : : * current transaction, such as being used in some manner by an
2618 : : * enclosing command.
2619 : : */
1987 michael@paquier.xyz 2620 [ + + ]: 5152 : if (is_partition)
2621 : 4125 : CheckTableNotInUse(relation, "CREATE TABLE .. PARTITION OF");
2622 : :
2623 : : /*
2624 : : * We do not allow partitioned tables and partitions to participate in
2625 : : * regular inheritance.
2626 : : */
201 peter@eisentraut.org 2627 [ + + + + ]:GNC 5149 : if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !is_partition)
2685 rhaas@postgresql.org 2628 [ + - ]:CBC 3 : ereport(ERROR,
2629 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2630 : : errmsg("cannot inherit from partitioned table \"%s\"",
2631 : : RelationGetRelationName(relation))));
2632 [ + + + + ]: 5146 : if (relation->rd_rel->relispartition && !is_partition)
2633 [ + - ]: 3 : ereport(ERROR,
2634 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2635 : : errmsg("cannot inherit from partition \"%s\"",
2636 : : RelationGetRelationName(relation))));
2637 : :
3311 tgl@sss.pgh.pa.us 2638 [ + + ]: 5143 : if (relation->rd_rel->relkind != RELKIND_RELATION &&
2685 rhaas@postgresql.org 2639 [ + + ]: 4123 : relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
2640 [ - + ]: 4113 : relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
7574 tgl@sss.pgh.pa.us 2641 [ # # ]:UBC 0 : ereport(ERROR,
2642 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2643 : : errmsg("inherited relation \"%s\" is not a table or foreign table",
2644 : : RelationGetRelationName(relation))));
2645 : :
2646 : : /*
2647 : : * If the parent is permanent, so must be all of its partitions. Note
2648 : : * that inheritance allows that case.
2649 : : */
2125 michael@paquier.xyz 2650 [ + + ]:CBC 5143 : if (is_partition &&
2651 [ + + + + ]: 4122 : relation->rd_rel->relpersistence != RELPERSISTENCE_TEMP &&
2652 : : relpersistence == RELPERSISTENCE_TEMP)
2653 [ + - ]: 3 : ereport(ERROR,
2654 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2655 : : errmsg("cannot create a temporary relation as partition of permanent relation \"%s\"",
2656 : : RelationGetRelationName(relation))));
2657 : :
2658 : : /* Permanent rels cannot inherit from temporary ones */
4136 tgl@sss.pgh.pa.us 2659 [ + + ]: 5140 : if (relpersistence != RELPERSISTENCE_TEMP &&
2660 [ + + ]: 4969 : relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
7574 2661 [ + - + + ]: 12 : ereport(ERROR,
2662 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2663 : : errmsg(!is_partition
2664 : : ? "cannot inherit from temporary relation \"%s\""
2665 : : : "cannot create a permanent relation as partition of temporary relation \"%s\"",
2666 : : RelationGetRelationName(relation))));
2667 : :
2668 : : /* If existing rel is temp, it must belong to this session */
4136 2669 [ + + ]: 5128 : if (relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
2670 [ - + ]: 147 : !relation->rd_islocaltemp)
4136 tgl@sss.pgh.pa.us 2671 [ # # # # ]:UBC 0 : ereport(ERROR,
2672 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
2673 : : errmsg(!is_partition
2674 : : ? "cannot inherit from temporary relation of another session"
2675 : : : "cannot create as partition of temporary relation of another session")));
2676 : :
2677 : : /*
2678 : : * We should have an UNDER permission flag for this, but for now,
2679 : : * demand that creator of a child table own the parent.
2680 : : */
518 peter@eisentraut.org 2681 [ - + ]:CBC 5128 : if (!object_ownercheck(RelationRelationId, RelationGetRelid(relation), GetUserId()))
2325 peter_e@gmx.net 2682 :UBC 0 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(relation->rd_rel->relkind),
8023 tgl@sss.pgh.pa.us 2683 : 0 : RelationGetRelationName(relation));
2684 : :
8024 tgl@sss.pgh.pa.us 2685 :CBC 5128 : tupleDesc = RelationGetDescr(relation);
2686 : 5128 : constr = tupleDesc->constr;
2687 : :
2688 : : /*
2689 : : * newattmap->attnums[] will contain the child-table attribute numbers
2690 : : * for the attributes of this parent table. (They are not the same
2691 : : * for parents after the first one, nor if we have dropped columns.)
2692 : : */
1579 michael@paquier.xyz 2693 : 5128 : newattmap = make_attrmap(tupleDesc->natts);
2694 : :
2695 : : /* We can't process inherited defaults until newattmap is complete. */
1332 tgl@sss.pgh.pa.us 2696 : 5128 : inherited_defaults = cols_with_defaults = NIL;
2697 : :
2698 : : /*
2699 : : * All columns that are part of the parent's primary key need to be
2700 : : * NOT NULL; if partition just the attnotnull bit, otherwise a full
2701 : : * constraint (if they don't have one already). Also, we request
2702 : : * attnotnull on columns that have a not-null constraint that's not
2703 : : * marked NO INHERIT.
2704 : : */
233 alvherre@alvh.no-ip. 2705 :GNC 5128 : pkattrs = RelationGetIndexAttrBitmap(relation,
2706 : : INDEX_ATTR_BITMAP_PRIMARY_KEY);
2707 : 5128 : nnconstrs = RelationGetNotNullConstraints(RelationGetRelid(relation), true);
2708 [ + + + + : 5552 : foreach(lc1, nnconstrs)
+ + ]
2709 : 424 : nncols = bms_add_member(nncols,
2710 : 424 : ((CookedConstraint *) lfirst(lc1))->attnum);
2711 : :
201 peter@eisentraut.org 2712 [ + + ]: 16026 : for (AttrNumber parent_attno = 1; parent_attno <= tupleDesc->natts;
8024 tgl@sss.pgh.pa.us 2713 :CBC 10898 : parent_attno++)
2714 : : {
2429 andres@anarazel.de 2715 : 10910 : Form_pg_attribute attribute = TupleDescAttr(tupleDesc,
2716 : : parent_attno - 1);
8024 tgl@sss.pgh.pa.us 2717 : 10910 : char *attributeName = NameStr(attribute->attname);
2718 : : int exist_attno;
2719 : : ColumnDef *newdef;
2720 : : ColumnDef *mergeddef;
2721 : :
2722 : : /*
2723 : : * Ignore dropped columns in the parent.
2724 : : */
7926 2725 [ + + ]: 10910 : if (attribute->attisdropped)
1579 michael@paquier.xyz 2726 : 96 : continue; /* leave newattmap->attnums entry as zero */
2727 : :
2728 : : /*
2729 : : * Create new column definition
2730 : : */
80 peter@eisentraut.org 2731 :GNC 10814 : newdef = makeColumnDef(attributeName, attribute->atttypid,
2732 : : attribute->atttypmod, attribute->attcollation);
54 2733 : 10814 : newdef->storage = attribute->attstorage;
80 2734 : 10814 : newdef->generated = attribute->attgenerated;
2735 [ + + ]: 10814 : if (CompressionMethodIsValid(attribute->attcompression))
54 2736 : 12 : newdef->compression =
2737 : 12 : pstrdup(GetCompressionMethodName(attribute->attcompression));
2738 : :
2739 : : /*
2740 : : * Regular inheritance children are independent enough not to
2741 : : * inherit identity columns. But partitions are integral part of
2742 : : * a partitioned table and inherit identity column.
2743 : : */
80 2744 [ + + ]: 10814 : if (is_partition)
2745 : 8866 : newdef->identity = attribute->attidentity;
2746 : :
2747 : : /*
2748 : : * Does it match some previously considered column from another
2749 : : * parent?
2750 : : */
201 2751 : 10814 : exist_attno = findAttrByName(attributeName, inh_columns);
8024 tgl@sss.pgh.pa.us 2752 [ + + ]:CBC 10814 : if (exist_attno > 0)
2753 : : {
2754 : : /*
2755 : : * Yes, try to merge the two column definitions.
2756 : : */
54 peter@eisentraut.org 2757 :GNC 148 : mergeddef = MergeInheritedAttribute(inh_columns, exist_attno, newdef);
2758 : :
79 peter@eisentraut.org 2759 :CBC 136 : newattmap->attnums[parent_attno - 1] = exist_attno;
2760 : :
2761 : : /*
2762 : : * Partitions have only one parent, so conflict should never
2763 : : * occur.
2764 : : */
79 peter@eisentraut.org 2765 [ - + ]:GNC 136 : Assert(!is_partition);
2766 : : }
2767 : : else
2768 : : {
2769 : : /*
2770 : : * No, create a new inherited column
2771 : : */
80 2772 : 10666 : newdef->inhcount = 1;
2773 : 10666 : newdef->is_local = false;
2774 : 10666 : inh_columns = lappend(inh_columns, newdef);
2775 : :
79 peter@eisentraut.org 2776 :CBC 10666 : newattmap->attnums[parent_attno - 1] = ++child_attno;
2777 : :
79 peter@eisentraut.org 2778 :GNC 10666 : mergeddef = newdef;
2779 : : }
2780 : :
2781 : : /*
2782 : : * mark attnotnull if parent has it and it's not NO INHERIT
2783 : : */
2784 [ + + + + ]: 21180 : if (bms_is_member(parent_attno, nncols) ||
2785 : 10378 : bms_is_member(parent_attno - FirstLowInvalidHeapAttributeNumber,
2786 : : pkattrs))
2787 : 1067 : mergeddef->is_not_null = true;
2788 : :
2789 : : /*
2790 : : * In regular inheritance, columns in the parent's primary key get
2791 : : * an extra not-null constraint. Partitioning doesn't need this,
2792 : : * because the PK itself is going to be cloned to the partition.
2793 : : */
2794 [ + + + + ]: 12738 : if (!is_partition &&
2795 : 1936 : bms_is_member(parent_attno -
2796 : : FirstLowInvalidHeapAttributeNumber,
2797 : : pkattrs))
2798 : : {
2799 : : CookedConstraint *nn;
2800 : :
2801 : 128 : nn = palloc(sizeof(CookedConstraint));
2802 : 128 : nn->contype = CONSTR_NOTNULL;
2803 : 128 : nn->conoid = InvalidOid;
2804 : 128 : nn->name = NULL;
2805 : 128 : nn->attnum = newattmap->attnums[parent_attno - 1];
2806 : 128 : nn->expr = NULL;
2807 : 128 : nn->skip_validation = false;
2808 : 128 : nn->is_local = false;
2809 : 128 : nn->inhcount = 1;
2810 : 128 : nn->is_no_inherit = false;
2811 : :
2812 : 128 : nnconstraints = lappend(nnconstraints, nn);
2813 : : }
2814 : :
2815 : : /*
2816 : : * Locate default/generation expression if any
2817 : : */
8024 tgl@sss.pgh.pa.us 2818 [ + + ]:CBC 10802 : if (attribute->atthasdef)
2819 : : {
2820 : : Node *this_default;
2821 : :
200 peter@eisentraut.org 2822 :GNC 325 : this_default = TupleDescGetDefault(tupleDesc, parent_attno);
1104 tgl@sss.pgh.pa.us 2823 [ - + ]:CBC 325 : if (this_default == NULL)
1104 tgl@sss.pgh.pa.us 2824 [ # # ]:UBC 0 : elog(ERROR, "default expression not found for attribute %d of relation \"%s\"",
2825 : : parent_attno, RelationGetRelationName(relation));
2826 : :
2827 : : /*
2828 : : * If it's a GENERATED default, it might contain Vars that
2829 : : * need to be mapped to the inherited column(s)' new numbers.
2830 : : * We can't do that till newattmap is ready, so just remember
2831 : : * all the inherited default expressions for the moment.
2832 : : */
1332 tgl@sss.pgh.pa.us 2833 :CBC 325 : inherited_defaults = lappend(inherited_defaults, this_default);
79 peter@eisentraut.org 2834 :GNC 325 : cols_with_defaults = lappend(cols_with_defaults, mergeddef);
2835 : : }
2836 : : }
2837 : :
2838 : : /*
2839 : : * Now process any inherited default expressions, adjusting attnos
2840 : : * using the completed newattmap map.
2841 : : */
1332 tgl@sss.pgh.pa.us 2842 [ + + + + :CBC 5441 : forboth(lc1, inherited_defaults, lc2, cols_with_defaults)
+ + + + +
+ + - +
+ ]
2843 : : {
2844 : 325 : Node *this_default = (Node *) lfirst(lc1);
2845 : 325 : ColumnDef *def = (ColumnDef *) lfirst(lc2);
2846 : : bool found_whole_row;
2847 : :
2848 : : /* Adjust Vars to match new table's column numbering */
2849 : 325 : this_default = map_variable_attnos(this_default,
2850 : : 1, 0,
2851 : : newattmap,
2852 : : InvalidOid, &found_whole_row);
2853 : :
2854 : : /*
2855 : : * For the moment we have to reject whole-row variables. We could
2856 : : * convert them, if we knew the new table's rowtype OID, but that
2857 : : * hasn't been assigned yet. (A variable could only appear in a
2858 : : * generation expression, so the error message is correct.)
2859 : : */
2860 [ - + ]: 325 : if (found_whole_row)
1332 tgl@sss.pgh.pa.us 2861 [ # # ]:UBC 0 : ereport(ERROR,
2862 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2863 : : errmsg("cannot convert whole-row table reference"),
2864 : : errdetail("Generation expression for column \"%s\" contains a whole-row reference to table \"%s\".",
2865 : : def->colname,
2866 : : RelationGetRelationName(relation))));
2867 : :
2868 : : /*
2869 : : * If we already had a default from some prior parent, check to
2870 : : * see if they are the same. If so, no problem; if not, mark the
2871 : : * column as having a bogus default. Below, we will complain if
2872 : : * the bogus default isn't overridden by the child columns.
2873 : : */
1332 tgl@sss.pgh.pa.us 2874 [ - + ]:CBC 325 : Assert(def->raw_default == NULL);
2875 [ + + ]: 325 : if (def->cooked_default == NULL)
2876 : 310 : def->cooked_default = this_default;
2877 [ + + ]: 15 : else if (!equal(def->cooked_default, this_default))
2878 : : {
2879 : 12 : def->cooked_default = &bogus_marker;
54 peter@eisentraut.org 2880 : 12 : have_bogus_defaults = true;
2881 : : }
2882 : : }
2883 : :
2884 : : /*
2885 : : * Now copy the CHECK constraints of this parent, adjusting attnos
2886 : : * using the completed newattmap map. Identically named constraints
2887 : : * are merged if possible, else we throw error.
2888 : : */
8024 tgl@sss.pgh.pa.us 2889 [ + + + + ]: 5116 : if (constr && constr->num_check > 0)
2890 : : {
2891 : 152 : ConstrCheck *check = constr->check;
2892 : :
201 peter@eisentraut.org 2893 [ + + ]:GNC 319 : for (int i = 0; i < constr->num_check; i++)
2894 : : {
5819 tgl@sss.pgh.pa.us 2895 :CBC 167 : char *name = check[i].ccname;
2896 : : Node *expr;
2897 : : bool found_whole_row;
2898 : :
2899 : : /* ignore if the constraint is non-inheritable */
4377 alvherre@alvh.no-ip. 2900 [ + + ]: 167 : if (check[i].ccnoinherit)
4514 2901 : 24 : continue;
2902 : :
2903 : : /* Adjust Vars to match new table's column numbering */
4306 tgl@sss.pgh.pa.us 2904 : 143 : expr = map_variable_attnos(stringToNode(check[i].ccbin),
2905 : : 1, 0,
2906 : : newattmap,
2907 : : InvalidOid, &found_whole_row);
2908 : :
2909 : : /*
2910 : : * For the moment we have to reject whole-row variables. We
2911 : : * could convert them, if we knew the new table's rowtype OID,
2912 : : * but that hasn't been assigned yet.
2913 : : */
2914 [ - + ]: 143 : if (found_whole_row)
4306 tgl@sss.pgh.pa.us 2915 [ # # ]:UBC 0 : ereport(ERROR,
2916 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2917 : : errmsg("cannot convert whole-row table reference"),
2918 : : errdetail("Constraint \"%s\" contains a whole-row reference to table \"%s\".",
2919 : : name,
2920 : : RelationGetRelationName(relation))));
2921 : :
201 peter@eisentraut.org 2922 :GNC 143 : constraints = MergeCheckConstraint(constraints, name, expr);
2923 : : }
2924 : : }
2925 : :
2926 : : /*
2927 : : * Also copy the not-null constraints from this parent. The
2928 : : * attnotnull markings were already installed above.
2929 : : */
233 alvherre@alvh.no-ip. 2930 [ + + + + : 5540 : foreach(lc1, nnconstrs)
+ + ]
2931 : : {
2932 : 424 : CookedConstraint *nn = lfirst(lc1);
2933 : :
2934 [ - + ]: 424 : Assert(nn->contype == CONSTR_NOTNULL);
2935 : :
2936 : 424 : nn->attnum = newattmap->attnums[nn->attnum - 1];
2937 : 424 : nn->is_local = false;
2938 : 424 : nn->inhcount = 1;
2939 : :
2940 : 424 : nnconstraints = lappend(nnconstraints, nn);
2941 : : }
2942 : :
1579 michael@paquier.xyz 2943 :CBC 5116 : free_attrmap(newattmap);
2944 : :
2945 : : /*
2946 : : * Close the parent rel, but keep our lock on it until xact commit.
2947 : : * That will prevent someone else from deleting or ALTERing the parent
2948 : : * before the child is committed.
2949 : : */
1910 andres@anarazel.de 2950 : 5116 : table_close(relation, NoLock);
2951 : : }
2952 : :
2953 : : /*
2954 : : * If we had no inherited attributes, the result columns are just the
2955 : : * explicitly declared columns. Otherwise, we need to merge the declared
2956 : : * columns into the inherited column list. Although, we never have any
2957 : : * explicitly declared columns if the table is a partition.
2958 : : */
201 peter@eisentraut.org 2959 [ + + ]:GNC 26217 : if (inh_columns != NIL)
2960 : : {
2961 : 4953 : int newcol_attno = 0;
2962 : :
2963 [ + + + + : 5342 : foreach(lc, columns)
+ + ]
2964 : : {
79 2965 : 413 : ColumnDef *newdef = lfirst_node(ColumnDef, lc);
8024 tgl@sss.pgh.pa.us 2966 :CBC 413 : char *attributeName = newdef->colname;
2967 : : int exist_attno;
2968 : :
2969 : : /*
2970 : : * Partitions have only one parent and have no column definitions
2971 : : * of their own, so conflict should never occur.
2972 : : */
79 peter@eisentraut.org 2973 [ - + ]:GNC 413 : Assert(!is_partition);
2974 : :
201 2975 : 413 : newcol_attno++;
2976 : :
2977 : : /*
2978 : : * Does it match some inherited column?
2979 : : */
2980 : 413 : exist_attno = findAttrByName(attributeName, inh_columns);
8024 tgl@sss.pgh.pa.us 2981 [ + + ]:CBC 413 : if (exist_attno > 0)
2982 : : {
2983 : : /*
2984 : : * Yes, try to merge the two column definitions.
2985 : : */
79 peter@eisentraut.org 2986 :GNC 134 : MergeChildAttribute(inh_columns, exist_attno, newcol_attno, newdef);
2987 : : }
2988 : : else
2989 : : {
2990 : : /*
2991 : : * No, attach new column unchanged to result columns.
2992 : : */
201 2993 : 279 : inh_columns = lappend(inh_columns, newdef);
2994 : : }
2995 : : }
2996 : :
2997 : 4929 : columns = inh_columns;
2998 : :
2999 : : /*
3000 : : * Check that we haven't exceeded the legal # of columns after merging
3001 : : * in inherited columns.
3002 : : */
3003 [ - + ]: 4929 : if (list_length(columns) > MaxHeapAttributeNumber)
7089 neilc@samurai.com 3004 [ # # ]:UBC 0 : ereport(ERROR,
3005 : : (errcode(ERRCODE_TOO_MANY_COLUMNS),
3006 : : errmsg("tables can have at most %d columns",
3007 : : MaxHeapAttributeNumber)));
3008 : : }
3009 : :
3010 : : /*
3011 : : * Now that we have the column definition list for a partition, we can
3012 : : * check whether the columns referenced in the column constraint specs
3013 : : * actually exist. Also, merge column defaults.
3014 : : */
1984 alvherre@alvh.no-ip. 3015 [ + + ]:CBC 26193 : if (is_partition)
3016 : : {
201 peter@eisentraut.org 3017 [ + + + + :GNC 4208 : foreach(lc, saved_columns)
+ + ]
3018 : : {
3019 : 104 : ColumnDef *restdef = lfirst(lc);
1984 alvherre@alvh.no-ip. 3020 :CBC 104 : bool found = false;
3021 : : ListCell *l;
3022 : :
201 peter@eisentraut.org 3023 [ + - + + :GNC 392 : foreach(l, columns)
+ + ]
3024 : : {
1984 alvherre@alvh.no-ip. 3025 :CBC 294 : ColumnDef *coldef = lfirst(l);
3026 : :
2685 rhaas@postgresql.org 3027 [ + + ]: 294 : if (strcmp(coldef->colname, restdef->colname) == 0)
3028 : : {
1984 alvherre@alvh.no-ip. 3029 : 104 : found = true;
3030 : :
3031 : : /*
3032 : : * Check for conflicts related to generated columns.
3033 : : *
3034 : : * Same rules as above: generated-ness has to match the
3035 : : * parent, but the contents of the generation expression
3036 : : * can be different.
3037 : : */
423 tgl@sss.pgh.pa.us 3038 [ + + ]: 104 : if (coldef->generated)
3039 : : {
3040 [ + - + + ]: 53 : if (restdef->raw_default && !restdef->generated)
3041 [ + - ]: 3 : ereport(ERROR,
3042 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3043 : : errmsg("column \"%s\" inherits from generated column but specifies default",
3044 : : restdef->colname)));
3045 [ - + ]: 50 : if (restdef->identity)
423 tgl@sss.pgh.pa.us 3046 [ # # ]:UBC 0 : ereport(ERROR,
3047 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3048 : : errmsg("column \"%s\" inherits from generated column but specifies identity",
3049 : : restdef->colname)));
3050 : : }
3051 : : else
3052 : : {
423 tgl@sss.pgh.pa.us 3053 [ + + ]:CBC 51 : if (restdef->generated)
3054 [ + - ]: 3 : ereport(ERROR,
3055 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3056 : : errmsg("child column \"%s\" specifies generation expression",
3057 : : restdef->colname),
3058 : : errhint("A child table column cannot be generated unless its parent column is.")));
3059 : : }
3060 : :
3061 : : /*
3062 : : * Override the parent's default value for this column
3063 : : * (coldef->cooked_default) with the partition's local
3064 : : * definition (restdef->raw_default), if there's one. It
3065 : : * should be physically impossible to get a cooked default
3066 : : * in the local definition or a raw default in the
3067 : : * inherited definition, but make sure they're nulls, for
3068 : : * future-proofing.
3069 : : */
1984 alvherre@alvh.no-ip. 3070 [ - + ]: 98 : Assert(restdef->cooked_default == NULL);
3071 [ - + ]: 98 : Assert(coldef->raw_default == NULL);
3072 [ + + ]: 98 : if (restdef->raw_default)
3073 : : {
2543 rhaas@postgresql.org 3074 : 62 : coldef->raw_default = restdef->raw_default;
1984 alvherre@alvh.no-ip. 3075 : 62 : coldef->cooked_default = NULL;
3076 : : }
3077 : : }
3078 : : }
3079 : :
3080 : : /* complain for constraints on columns not in parent */
3081 [ - + ]: 98 : if (!found)
1984 alvherre@alvh.no-ip. 3082 [ # # ]:UBC 0 : ereport(ERROR,
3083 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
3084 : : errmsg("column \"%s\" does not exist",
3085 : : restdef->colname)));
3086 : : }
3087 : : }
3088 : :
3089 : : /*
3090 : : * If we found any conflicting parent default values, check to make sure
3091 : : * they were overridden by the child.
3092 : : */
54 peter@eisentraut.org 3093 [ + + ]:CBC 26187 : if (have_bogus_defaults)
3094 : : {
201 peter@eisentraut.org 3095 [ + - + + :GNC 27 : foreach(lc, columns)
+ + ]
3096 : : {
3097 : 21 : ColumnDef *def = lfirst(lc);
3098 : :
5304 tgl@sss.pgh.pa.us 3099 [ + + ]:CBC 21 : if (def->cooked_default == &bogus_marker)
3100 : : {
1439 peter@eisentraut.org 3101 [ + + ]: 6 : if (def->generated)
3102 [ + - ]: 3 : ereport(ERROR,
3103 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3104 : : errmsg("column \"%s\" inherits conflicting generation expressions",
3105 : : def->colname),
3106 : : errhint("To resolve the conflict, specify a generation expression explicitly.")));
3107 : : else
3108 [ + - ]: 3 : ereport(ERROR,
3109 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3110 : : errmsg("column \"%s\" inherits conflicting default values",
3111 : : def->colname),
3112 : : errhint("To resolve the conflict, specify a default explicitly.")));
3113 : : }
3114 : : }
3115 : : }
3116 : :
8024 tgl@sss.pgh.pa.us 3117 : 26181 : *supconstr = constraints;
233 alvherre@alvh.no-ip. 3118 :GNC 26181 : *supnotnulls = nnconstraints;
3119 : :
201 peter@eisentraut.org 3120 : 26181 : return columns;
3121 : : }
3122 : :
3123 : :
3124 : : /*
3125 : : * MergeCheckConstraint
3126 : : * Try to merge an inherited CHECK constraint with previous ones
3127 : : *
3128 : : * If we inherit identically-named constraints from multiple parents, we must
3129 : : * merge them, or throw an error if they don't have identical definitions.
3130 : : *
3131 : : * constraints is a list of CookedConstraint structs for previous constraints.
3132 : : *
3133 : : * If the new constraint matches an existing one, then the existing
3134 : : * constraint's inheritance count is updated. If there is a conflict (same
3135 : : * name but different expression), throw an error. If the constraint neither
3136 : : * matches nor conflicts with an existing one, a new constraint is appended to
3137 : : * the list.
3138 : : */
3139 : : static List *
3140 : 143 : MergeCheckConstraint(List *constraints, const char *name, Node *expr)
3141 : : {
3142 : : ListCell *lc;
3143 : : CookedConstraint *newcon;
3144 : :
5819 tgl@sss.pgh.pa.us 3145 [ + + + + :CBC 158 : foreach(lc, constraints)
+ + ]
3146 : : {
3147 : 36 : CookedConstraint *ccon = (CookedConstraint *) lfirst(lc);
3148 : :
3149 [ - + ]: 36 : Assert(ccon->contype == CONSTR_CHECK);
3150 : :
3151 : : /* Non-matching names never conflict */
3152 [ + + ]: 36 : if (strcmp(ccon->name, name) != 0)
6501 bruce@momjian.us 3153 : 15 : continue;
3154 : :
5819 tgl@sss.pgh.pa.us 3155 [ + - ]: 21 : if (equal(expr, ccon->expr))
3156 : : {
3157 : : /* OK to merge constraint with existing */
3158 : 21 : ccon->inhcount++;
383 peter@eisentraut.org 3159 [ - + ]: 21 : if (ccon->inhcount < 0)
383 peter@eisentraut.org 3160 [ # # ]:UBC 0 : ereport(ERROR,
3161 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
3162 : : errmsg("too many inheritance parents"));
201 peter@eisentraut.org 3163 :GNC 21 : return constraints;
3164 : : }
3165 : :
6395 tgl@sss.pgh.pa.us 3166 [ # # ]:UBC 0 : ereport(ERROR,
3167 : : (errcode(ERRCODE_DUPLICATE_OBJECT),
3168 : : errmsg("check constraint name \"%s\" appears multiple times but with different expressions",
3169 : : name)));
3170 : : }
3171 : :
3172 : : /*
3173 : : * Constraint couldn't be merged with an existing one and also didn't
3174 : : * conflict with an existing one, so add it as a new one to the list.
3175 : : */
201 peter@eisentraut.org 3176 :GNC 122 : newcon = palloc0_object(CookedConstraint);
3177 : 122 : newcon->contype = CONSTR_CHECK;
3178 : 122 : newcon->name = pstrdup(name);
3179 : 122 : newcon->expr = expr;
3180 : 122 : newcon->inhcount = 1;
3181 : 122 : return lappend(constraints, newcon);
3182 : : }
3183 : :
3184 : : /*
3185 : : * MergeChildAttribute
3186 : : * Merge given child attribute definition into given inherited attribute.
3187 : : *
3188 : : * Input arguments:
3189 : : * 'inh_columns' is the list of inherited ColumnDefs.
3190 : : * 'exist_attno' is the number of the inherited attribute in inh_columns
3191 : : * 'newcol_attno' is the attribute number in child table's schema definition
3192 : : * 'newdef' is the column/attribute definition from the child table.
3193 : : *
3194 : : * The ColumnDef in 'inh_columns' list is modified. The child attribute's
3195 : : * ColumnDef remains unchanged.
3196 : : *
3197 : : * Notes:
3198 : : * - The attribute is merged according to the rules laid out in the prologue
3199 : : * of MergeAttributes().
3200 : : * - If matching inherited attribute exists but the child attribute can not be
3201 : : * merged into it, the function throws respective errors.
3202 : : * - A partition can not have its own column definitions. Hence this function
3203 : : * is applicable only to a regular inheritance child.
3204 : : */
3205 : : static void
79 3206 : 134 : MergeChildAttribute(List *inh_columns, int exist_attno, int newcol_attno, const ColumnDef *newdef)
3207 : : {
3208 : 134 : char *attributeName = newdef->colname;
3209 : : ColumnDef *inhdef;
3210 : : Oid inhtypeid,
3211 : : newtypeid;
3212 : : int32 inhtypmod,
3213 : : newtypmod;
3214 : : Oid inhcollid,
3215 : : newcollid;
3216 : :
3217 [ + + ]: 134 : if (exist_attno == newcol_attno)
3218 [ + - ]: 120 : ereport(NOTICE,
3219 : : (errmsg("merging column \"%s\" with inherited definition",
3220 : : attributeName)));
3221 : : else
3222 [ + - ]: 14 : ereport(NOTICE,
3223 : : (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
3224 : : errdetail("User-specified column moved to the position of the inherited column.")));
3225 : :
3226 : 134 : inhdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
3227 : :
3228 : : /*
3229 : : * Must have the same type and typmod
3230 : : */
3231 : 134 : typenameTypeIdAndMod(NULL, inhdef->typeName, &inhtypeid, &inhtypmod);
3232 : 134 : typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
3233 [ + + + + ]: 134 : if (inhtypeid != newtypeid || inhtypmod != newtypmod)
3234 [ + - ]: 6 : ereport(ERROR,
3235 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
3236 : : errmsg("column \"%s\" has a type conflict",
3237 : : attributeName),
3238 : : errdetail("%s versus %s",
3239 : : format_type_with_typemod(inhtypeid, inhtypmod),
3240 : : format_type_with_typemod(newtypeid, newtypmod))));
3241 : :
3242 : : /*
3243 : : * Must have the same collation
3244 : : */
3245 : 128 : inhcollid = GetColumnDefCollation(NULL, inhdef, inhtypeid);
3246 : 128 : newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
3247 [ + + ]: 128 : if (inhcollid != newcollid)
3248 [ + - ]: 3 : ereport(ERROR,
3249 : : (errcode(ERRCODE_COLLATION_MISMATCH),
3250 : : errmsg("column \"%s\" has a collation conflict",
3251 : : attributeName),
3252 : : errdetail("\"%s\" versus \"%s\"",
3253 : : get_collation_name(inhcollid),
3254 : : get_collation_name(newcollid))));
3255 : :
3256 : : /*
3257 : : * Identity is never inherited by a regular inheritance child. Pick
3258 : : * child's identity definition if there's one.
3259 : : */
3260 : 125 : inhdef->identity = newdef->identity;
3261 : :
3262 : : /*
3263 : : * Copy storage parameter
3264 : : */
54 3265 [ - + ]: 125 : if (inhdef->storage == 0)
54 peter@eisentraut.org 3266 :UNC 0 : inhdef->storage = newdef->storage;
54 peter@eisentraut.org 3267 [ + + + + ]:GNC 125 : else if (newdef->storage != 0 && inhdef->storage != newdef->storage)
3268 [ + - ]: 3 : ereport(ERROR,
3269 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
3270 : : errmsg("column \"%s\" has a storage parameter conflict",
3271 : : attributeName),
3272 : : errdetail("%s versus %s",
3273 : : storage_name(inhdef->storage),
3274 : : storage_name(newdef->storage))));
3275 : :
3276 : : /*
3277 : : * Copy compression parameter
3278 : : */
3279 [ + + ]: 122 : if (inhdef->compression == NULL)
79 3280 : 119 : inhdef->compression = newdef->compression;
54 3281 [ + - ]: 3 : else if (newdef->compression != NULL)
3282 : : {
3283 [ + - ]: 3 : if (strcmp(inhdef->compression, newdef->compression) != 0)
3284 [ + - ]: 3 : ereport(ERROR,
3285 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
3286 : : errmsg("column \"%s\" has a compression method conflict",
3287 : : attributeName),
3288 : : errdetail("%s versus %s", inhdef->compression, newdef->compression)));
3289 : : }
3290 : :
3291 : : /*
3292 : : * Merge of not-null constraints = OR 'em together
3293 : : */
79 3294 : 119 : inhdef->is_not_null |= newdef->is_not_null;
3295 : :
3296 : : /*
3297 : : * Check for conflicts related to generated columns.
3298 : : *
3299 : : * If the parent column is generated, the child column will be made a
3300 : : * generated column if it isn't already. If it is a generated column,
3301 : : * we'll take its generation expression in preference to the parent's. We
3302 : : * must check that the child column doesn't specify a default value or
3303 : : * identity, which matches the rules for a single column in
3304 : : * parse_utilcmd.c.
3305 : : *
3306 : : * Conversely, if the parent column is not generated, the child column
3307 : : * can't be either. (We used to allow that, but it results in being able
3308 : : * to override the generation expression via UPDATEs through the parent.)
3309 : : */
3310 [ + + ]: 119 : if (inhdef->generated)
3311 : : {
3312 [ + + + + ]: 13 : if (newdef->raw_default && !newdef->generated)
3313 [ + - ]: 3 : ereport(ERROR,
3314 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3315 : : errmsg("column \"%s\" inherits from generated column but specifies default",
3316 : : inhdef->colname)));
3317 [ + + ]: 10 : if (newdef->identity)
3318 [ + - ]: 3 : ereport(ERROR,
3319 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3320 : : errmsg("column \"%s\" inherits from generated column but specifies identity",
3321 : : inhdef->colname)));
3322 : : }
3323 : : else
3324 : : {
3325 [ + + ]: 106 : if (newdef->generated)
3326 [ + - ]: 3 : ereport(ERROR,
3327 : : (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
3328 : : errmsg("child column \"%s\" specifies generation expression",
3329 : : inhdef->colname),
3330 : : errhint("A child table column cannot be generated unless its parent column is.")));
3331 : : }
3332 : :
3333 : : /*
3334 : : * If new def has a default, override previous default
3335 : : */
3336 [ + + ]: 110 : if (newdef->raw_default != NULL)
3337 : : {
3338 : 9 : inhdef->raw_default = newdef->raw_default;
3339 : 9 : inhdef->cooked_default = newdef->cooked_default;
3340 : : }
3341 : :
3342 : : /* Mark the column as locally defined */
3343 : 110 : inhdef->is_local = true;
3344 : 110 : }
3345 : :
3346 : : /*
3347 : : * MergeInheritedAttribute
3348 : : * Merge given parent attribute definition into specified attribute
3349 : : * inherited from the previous parents.
3350 : : *
3351 : : * Input arguments:
3352 : : * 'inh_columns' is the list of previously inherited ColumnDefs.
3353 : : * 'exist_attno' is the number the existing matching attribute in inh_columns.
3354 : : * 'newdef' is the new parent column/attribute definition to be merged.
3355 : : *
3356 : : * The matching ColumnDef in 'inh_columns' list is modified and returned.
3357 : : *
3358 : : * Notes:
3359 : : * - The attribute is merged according to the rules laid out in the prologue
3360 : : * of MergeAttributes().
3361 : : * - If matching inherited attribute exists but the new attribute can not be
3362 : : * merged into it, the function throws respective errors.
3363 : : * - A partition inherits from only a single parent. Hence this function is
3364 : : * applicable only to a regular inheritance.
3365 : : */
3366 : : static ColumnDef *
3367 : 148 : MergeInheritedAttribute(List *inh_columns,
3368 : : int exist_attno,
3369 : : const ColumnDef *newdef)
3370 : : {
3371 : 148 : char *attributeName = newdef->colname;
3372 : : ColumnDef *prevdef;
3373 : : Oid prevtypeid,
3374 : : newtypeid;
3375 : : int32 prevtypmod,
3376 : : newtypmod;
3377 : : Oid prevcollid,
3378 : : newcollid;
3379 : :
3380 [ + - ]: 148 : ereport(NOTICE,
3381 : : (errmsg("merging multiple inherited definitions of column \"%s\"",
3382 : : attributeName)));
3383 : 148 : prevdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
3384 : :
3385 : : /*
3386 : : * Must have the same type and typmod
3387 : : */
3388 : 148 : typenameTypeIdAndMod(NULL, prevdef->typeName, &prevtypeid, &prevtypmod);
3389 : 148 : typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
3390 [ + - - + ]: 148 : if (prevtypeid != newtypeid || prevtypmod != newtypmod)
79 peter@eisentraut.org 3391 [ # # ]:UNC 0 : ereport(ERROR,
3392 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
3393 : : errmsg("inherited column \"%s\" has a type conflict",
3394 : : attributeName),
3395 : : errdetail("%s versus %s",
3396 : : format_type_with_typemod(prevtypeid, prevtypmod),
3397 : : format_type_with_typemod(newtypeid, newtypmod))));
3398 : :
3399 : : /*
3400 : : * Must have the same collation
3401 : : */
79 peter@eisentraut.org 3402 :GNC 148 : prevcollid = GetColumnDefCollation(NULL, prevdef, prevtypeid);
3403 : 148 : newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
3404 [ - + ]: 148 : if (prevcollid != newcollid)
79 peter@eisentraut.org 3405 [ # # ]:UNC 0 : ereport(ERROR,
3406 : : (errcode(ERRCODE_COLLATION_MISMATCH),
3407 : : errmsg("inherited column \"%s\" has a collation conflict",
3408 : : attributeName),
3409 : : errdetail("\"%s\" versus \"%s\"",
3410 : : get_collation_name(prevcollid),
3411 : : get_collation_name(newcollid))));
3412 : :
3413 : : /*
3414 : : * Copy/check storage parameter
3415 : : */
54 peter@eisentraut.org 3416 [ - + ]:GNC 148 : if (prevdef->storage == 0)
54 peter@eisentraut.org 3417 :UNC 0 : prevdef->storage = newdef->storage;
54 peter@eisentraut.org 3418 [ + + ]:GNC 148 : else if (prevdef->storage != newdef->storage)
3419 [ + - ]: 3 : ereport(ERROR,
3420 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
3421 : : errmsg("inherited column \"%s\" has a storage parameter conflict",
3422 : : attributeName),
3423 : : errdetail("%s versus %s",
3424 : : storage_name(prevdef->storage),
3425 : : storage_name(newdef->storage))));
3426 : :
3427 : : /*
3428 : : * Copy/check compression parameter
3429 : : */
79 3430 [ + + ]: 145 : if (prevdef->compression == NULL)
3431 : 142 : prevdef->compression = newdef->compression;
54 3432 [ + - ]: 3 : else if (strcmp(prevdef->compression, newdef->compression) != 0)
3433 [ + - ]: 3 : ereport(ERROR,
3434 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
3435 : : errmsg("column \"%s\" has a compression method conflict",
3436 : : attributeName),
3437 : : errdetail("%s versus %s", prevdef->compression, newdef->compression)));
3438 : :
3439 : : /*
3440 : : * Check for GENERATED conflicts
3441 : : */
79 3442 [ + + ]: 142 : if (prevdef->generated != newdef->generated)
3443 [ + - ]: 6 : ereport(ERROR,
3444 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
3445 : : errmsg("inherited column \"%s\" has a generation conflict",
3446 : : attributeName)));
3447 : :
3448 : : /*
3449 : : * Default and other constraints are handled by the caller.
3450 : : */
3451 : :
3452 : 136 : prevdef->inhcount++;
3453 [ - + ]: 136 : if (prevdef->inhcount < 0)
79 peter@eisentraut.org 3454 [ # # ]:UNC 0 : ereport(ERROR,
3455 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
3456 : : errmsg("too many inheritance parents"));
3457 : :
79 peter@eisentraut.org 3458 :GNC 136 : return prevdef;
3459 : : }
3460 : :
3461 : : /*
3462 : : * StoreCatalogInheritance
3463 : : * Updates the system catalogs with proper inheritance information.
3464 : : *
3465 : : * supers is a list of the OIDs of the new relation's direct ancestors.
3466 : : */
3467 : : static void
2596 simon@2ndQuadrant.co 3468 :CBC 25908 : StoreCatalogInheritance(Oid relationId, List *supers,
3469 : : bool child_is_partition)
3470 : : {
3471 : : Relation relation;
3472 : : int32 seqNumber;
3473 : : ListCell *entry;
3474 : :
3475 : : /*
3476 : : * sanity checks
3477 : : */
534 peter@eisentraut.org 3478 [ - + ]: 25908 : Assert(OidIsValid(relationId));
3479 : :
8024 tgl@sss.pgh.pa.us 3480 [ + + ]: 25908 : if (supers == NIL)
3481 : 21153 : return;
3482 : :
3483 : : /*
3484 : : * Store INHERITS information in pg_inherits using direct ancestors only.
3485 : : * Also enter dependencies on the direct ancestors, and make sure they are
3486 : : * marked with relhassubclass = true.
3487 : : *
3488 : : * (Once upon a time, both direct and indirect ancestors were found here
3489 : : * and then entered into pg_ipl. Since that catalog doesn't exist
3490 : : * anymore, there's no need to look for indirect ancestors.)
3491 : : */
1910 andres@anarazel.de 3492 : 4755 : relation = table_open(InheritsRelationId, RowExclusiveLock);
3493 : :
8024 tgl@sss.pgh.pa.us 3494 : 4755 : seqNumber = 1;
3495 [ + - + + : 9640 : foreach(entry, supers)
+ + ]
3496 : : {
6393 3497 : 4885 : Oid parentOid = lfirst_oid(entry);
3498 : :
2596 simon@2ndQuadrant.co 3499 : 4885 : StoreCatalogInheritance1(relationId, parentOid, seqNumber, relation,
3500 : : child_is_partition);
6496 neilc@samurai.com 3501 : 4885 : seqNumber++;
3502 : : }
3503 : :
1910 andres@anarazel.de 3504 : 4755 : table_close(relation, RowExclusiveLock);
3505 : : }
3506 : :
3507 : : /*
3508 : : * Make catalog entries showing relationId as being an inheritance child
3509 : : * of parentOid. inhRelation is the already-opened pg_inherits catalog.
3510 : : */
3511 : : static void
6496 neilc@samurai.com 3512 : 6187 : StoreCatalogInheritance1(Oid relationId, Oid parentOid,
3513 : : int32 seqNumber, Relation inhRelation,
3514 : : bool child_is_partition)
3515 : : {
3516 : : ObjectAddress childobject,
3517 : : parentobject;
3518 : :
3519 : : /* store the pg_inherits row */
2277 alvherre@alvh.no-ip. 3520 : 6187 : StoreSingleInheritance(relationId, parentOid, seqNumber);
3521 : :
3522 : : /*
3523 : : * Store a dependency too
3524 : : */
6496 neilc@samurai.com 3525 : 6187 : parentobject.classId = RelationRelationId;
3526 : 6187 : parentobject.objectId = parentOid;
3527 : 6187 : parentobject.objectSubId = 0;
3528 : 6187 : childobject.classId = RelationRelationId;
3529 : 6187 : childobject.objectId = relationId;
3530 : 6187 : childobject.objectSubId = 0;
3531 : :
2497 rhaas@postgresql.org 3532 [ + + ]: 6187 : recordDependencyOn(&childobject, &parentobject,
3533 : : child_dependency_type(child_is_partition));
3534 : :
3535 : : /*
3536 : : * Post creation hook of this inheritance. Since object_access_hook
3537 : : * doesn't take multiple object identifiers, we relay oid of parent
3538 : : * relation using auxiliary_id argument.
3539 : : */
4046 3540 [ - + ]: 6187 : InvokeObjectPostAlterHookArg(InheritsRelationId,
3541 : : relationId, 0,
3542 : : parentOid, false);
3543 : :
3544 : : /*
3545 : : * Mark the parent as having subclasses.
3546 : : */
4608 tgl@sss.pgh.pa.us 3547 : 6187 : SetRelationHasSubclass(parentOid, true);
8024 3548 : 6187 : }
3549 : :
3550 : : /*
3551 : : * Look for an existing column entry with the given name.
3552 : : *
3553 : : * Returns the index (starting with 1) if attribute already exists in columns,
3554 : : * 0 if it doesn't.
3555 : : */
3556 : : static int
201 peter@eisentraut.org 3557 :GNC 11227 : findAttrByName(const char *attributeName, const List *columns)
3558 : : {
3559 : : ListCell *lc;
7263 neilc@samurai.com 3560 :CBC 11227 : int i = 1;
3561 : :
201 peter@eisentraut.org 3562 [ + + + + :GNC 20983 : foreach(lc, columns)
+ + ]
3563 : : {
3564 [ + + ]: 10038 : if (strcmp(attributeName, lfirst_node(ColumnDef, lc)->colname) == 0)
8024 tgl@sss.pgh.pa.us 3565 :CBC 282 : return i;
3566 : :
7263 neilc@samurai.com 3567 : 9756 : i++;
3568 : : }
8024 tgl@sss.pgh.pa.us 3569 : 10945 : return 0;
3570 : : }
3571 : :
3572 : :
3573 : : /*
3574 : : * SetRelationHasSubclass
3575 : : * Set the value of the relation's relhassubclass field in pg_class.
3576 : : *
3577 : : * NOTE: caller must be holding an appropriate lock on the relation.
3578 : : * ShareUpdateExclusiveLock is sufficient.
3579 : : *
3580 : : * NOTE: an important side-effect of this operation is that an SI invalidation
3581 : : * message is sent out to all backends --- including me --- causing plans
3582 : : * referencing the relation to be rebuilt with the new list of children.
3583 : : * This must happen even if we find that no change is needed in the pg_class
3584 : : * row.
3585 : : */
3586 : : void
4608 3587 : 7827 : SetRelationHasSubclass(Oid relationId, bool relhassubclass)
3588 : : {
3589 : : Relation relationRelation;
3590 : : HeapTuple tuple;
3591 : : Form_pg_class classtuple;
3592 : :
3593 : : /*
3594 : : * Fetch a modifiable copy of the tuple, modify it, update pg_class.
3595 : : */
1910 andres@anarazel.de 3596 : 7827 : relationRelation = table_open(RelationRelationId, RowExclusiveLock);
5173 rhaas@postgresql.org 3597 : 7827 : tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relationId));
8024 tgl@sss.pgh.pa.us 3598 [ - + ]: 7827 : if (!HeapTupleIsValid(tuple))
7574 tgl@sss.pgh.pa.us 3599 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", relationId);
7489 tgl@sss.pgh.pa.us 3600 :CBC 7827 : classtuple = (Form_pg_class) GETSTRUCT(tuple);
3601 : :
3602 [ + + ]: 7827 : if (classtuple->relhassubclass != relhassubclass)
3603 : : {
3604 : 3667 : classtuple->relhassubclass = relhassubclass;
2630 alvherre@alvh.no-ip. 3605 : 3667 : CatalogTupleUpdate(relationRelation, &tuple->t_self, tuple);
3606 : : }
3607 : : else
3608 : : {
3609 : : /* no need to change tuple, but force relcache rebuild anyway */
7369 tgl@sss.pgh.pa.us 3610 : 4160 : CacheInvalidateRelcacheByTuple(tuple);
3611 : : }
3612 : :
8024 3613 : 7827 : heap_freetuple(tuple);
1910 andres@anarazel.de 3614 : 7827 : table_close(relationRelation, RowExclusiveLock);
8024 tgl@sss.pgh.pa.us 3615 : 7827 : }
3616 : :
3617 : : /*
3618 : : * CheckRelationTableSpaceMove
3619 : : * Check if relation can be moved to new tablespace.
3620 : : *
3621 : : * NOTE: The caller must hold AccessExclusiveLock on the relation.
3622 : : *
3623 : : * Returns true if the relation can be moved to the new tablespace; raises
3624 : : * an error if it is not possible to do the move; returns false if the move
3625 : : * would have no effect.
3626 : : */
3627 : : bool
1173 michael@paquier.xyz 3628 : 215 : CheckRelationTableSpaceMove(Relation rel, Oid newTableSpaceId)
3629 : : {
3630 : : Oid oldTableSpaceId;
3631 : :
3632 : : /*
3633 : : * No work if no change in tablespace. Note that MyDatabaseTableSpace is
3634 : : * stored as 0.
3635 : : */
3636 : 215 : oldTableSpaceId = rel->rd_rel->reltablespace;
3637 [ + + ]: 215 : if (newTableSpaceId == oldTableSpaceId ||
3638 [ + + + + ]: 211 : (newTableSpaceId == MyDatabaseTableSpace && oldTableSpaceId == 0))
3639 : 5 : return false;
3640 : :
3641 : : /*
3642 : : * We cannot support moving mapped relations into different tablespaces.
3643 : : * (In particular this eliminates all shared catalogs.)
3644 : : */
3645 [ + + + + : 210 : if (RelationIsMapped(rel))
+ - + + +
+ - + ]
1173 michael@paquier.xyz 3646 [ # # ]:UBC 0 : ereport(ERROR,
3647 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3648 : : errmsg("cannot move system relation \"%s\"",
3649 : : RelationGetRelationName(rel))));
3650 : :
3651 : : /* Cannot move a non-shared relation into pg_global */
1173 michael@paquier.xyz 3652 [ + + ]:CBC 210 : if (newTableSpaceId == GLOBALTABLESPACE_OID)
3653 [ + - ]: 6 : ereport(ERROR,
3654 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
3655 : : errmsg("only shared relations can be placed in pg_global tablespace")));
3656 : :
3657 : : /*
3658 : : * Do not allow moving temp tables of other backends ... their local
3659 : : * buffer manager is not going to cope.
3660 : : */
3661 [ + + - + ]: 204 : if (RELATION_IS_OTHER_TEMP(rel))
1173 michael@paquier.xyz 3662 [ # # ]:UBC 0 : ereport(ERROR,
3663 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3664 : : errmsg("cannot move temporary tables of other sessions")));
3665 : :
1173 michael@paquier.xyz 3666 :CBC 204 : return true;
3667 : : }
3668 : :
3669 : : /*
3670 : : * SetRelationTableSpace
3671 : : * Set new reltablespace and relfilenumber in pg_class entry.
3672 : : *
3673 : : * newTableSpaceId is the new tablespace for the relation, and
3674 : : * newRelFilenumber its new filenumber. If newRelFilenumber is
3675 : : * InvalidRelFileNumber, this field is not updated.
3676 : : *
3677 : : * NOTE: The caller must hold AccessExclusiveLock on the relation.
3678 : : *
3679 : : * The caller of this routine had better check if a relation can be
3680 : : * moved to this new tablespace by calling CheckRelationTableSpaceMove()
3681 : : * first, and is responsible for making the change visible with
3682 : : * CommandCounterIncrement().
3683 : : */
3684 : : void
3685 : 102 : SetRelationTableSpace(Relation rel,
3686 : : Oid newTableSpaceId,
3687 : : RelFileNumber newRelFilenumber)
3688 : : {
3689 : : Relation pg_class;
3690 : : HeapTuple tuple;
3691 : : Form_pg_class rd_rel;
3692 : 102 : Oid reloid = RelationGetRelid(rel);
3693 : :
3694 [ - + ]: 102 : Assert(CheckRelationTableSpaceMove(rel, newTableSpaceId));
3695 : :
3696 : : /* Get a modifiable copy of the relation's pg_class row. */
3697 : 102 : pg_class = table_open(RelationRelationId, RowExclusiveLock);
3698 : :
3699 : 102 : tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid));
3700 [ - + ]: 102 : if (!HeapTupleIsValid(tuple))
1173 michael@paquier.xyz 3701 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", reloid);
1173 michael@paquier.xyz 3702 :CBC 102 : rd_rel = (Form_pg_class) GETSTRUCT(tuple);
3703 : :
3704 : : /* Update the pg_class row. */
3705 : 204 : rd_rel->reltablespace = (newTableSpaceId == MyDatabaseTableSpace) ?
3706 [ + + ]: 102 : InvalidOid : newTableSpaceId;
648 rhaas@postgresql.org 3707 [ + + ]: 102 : if (RelFileNumberIsValid(newRelFilenumber))
3708 : 80 : rd_rel->relfilenode = newRelFilenumber;
1173 michael@paquier.xyz 3709 : 102 : CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
3710 : :
3711 : : /*
3712 : : * Record dependency on tablespace. This is only required for relations
3713 : : * that have no physical storage.
3714 : : */
3715 [ + + + + : 102 : if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
+ - + + +
+ ]
3716 : 15 : changeDependencyOnTablespace(RelationRelationId, reloid,
3717 : : rd_rel->reltablespace);
3718 : :
3719 : 102 : heap_freetuple(tuple);
3720 : 102 : table_close(pg_class, RowExclusiveLock);
3721 : 102 : }
3722 : :
3723 : : /*
3724 : : * renameatt_check - basic sanity checks before attribute rename
3725 : : */
3726 : : static void
4504 rhaas@postgresql.org 3727 : 489 : renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
3728 : : {
3729 : 489 : char relkind = classform->relkind;
3730 : :
3731 [ + + + + ]: 489 : if (classform->reloftype && !recursing)
5190 peter_e@gmx.net 3732 [ + - ]: 3 : ereport(ERROR,
3733 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
3734 : : errmsg("cannot rename column of typed table")));
3735 : :
3736 : : /*
3737 : : * Renaming the columns of sequences or toast tables doesn't actually
3738 : : * break anything from the system's point of view, since internal
3739 : : * references are by attnum. But it doesn't seem right to allow users to
3740 : : * change names that are hardcoded into the system, hence the following
3741 : : * restriction.
3742 : : */
5139 rhaas@postgresql.org 3743 [ + + + + ]: 486 : if (relkind != RELKIND_RELATION &&
3744 [ + - ]: 42 : relkind != RELKIND_VIEW &&
4060 kgrittn@postgresql.o 3745 [ + + ]: 42 : relkind != RELKIND_MATVIEW &&
5139 rhaas@postgresql.org 3746 [ + - ]: 18 : relkind != RELKIND_COMPOSITE_TYPE &&
4852 3747 [ + - ]: 18 : relkind != RELKIND_INDEX &&
2277 alvherre@alvh.no-ip. 3748 [ - + ]: 18 : relkind != RELKIND_PARTITIONED_INDEX &&
2685 rhaas@postgresql.org 3749 [ # # ]:UBC 0 : relkind != RELKIND_FOREIGN_TABLE &&
3750 : : relkind != RELKIND_PARTITIONED_TABLE)
5139 3751 [ # # ]: 0 : ereport(ERROR,
3752 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
3753 : : errmsg("cannot rename columns of relation \"%s\"",
3754 : : NameStr(classform->relname)),
3755 : : errdetail_relkind_not_supported(relkind)));
3756 : :
3757 : : /*
3758 : : * permissions checking. only the owner of a class can change its schema.
3759 : : */
518 peter@eisentraut.org 3760 [ - + ]:CBC 486 : if (!object_ownercheck(RelationRelationId, myrelid, GetUserId()))
2325 peter_e@gmx.net 3761 :UBC 0 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(myrelid)),
4504 rhaas@postgresql.org 3762 : 0 : NameStr(classform->relname));
3790 rhaas@postgresql.org 3763 [ + + + + ]:CBC 486 : if (!allowSystemTableMods && IsSystemClass(myrelid, classform))
7574 tgl@sss.pgh.pa.us 3764 [ + - ]: 1 : ereport(ERROR,
3765 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
3766 : : errmsg("permission denied: \"%s\" is a system catalog",
3767 : : NameStr(classform->relname))));
4504 rhaas@postgresql.org 3768 : 485 : }
3769 : :
3770 : : /*
3771 : : * renameatt_internal - workhorse for renameatt
3772 : : *
3773 : : * Return value is the attribute number in the 'myrelid' relation.
3774 : : */
3775 : : static AttrNumber
3776 : 270 : renameatt_internal(Oid myrelid,
3777 : : const char *oldattname,
3778 : : const char *newattname,
3779 : : bool recurse,
3780 : : bool recursing,
3781 : : int expected_parents,
3782 : : DropBehavior behavior)
3783 : : {
3784 : : Relation targetrelation;
3785 : : Relation attrelation;
3786 : : HeapTuple atttup;
3787 : : Form_pg_attribute attform;
3788 : : AttrNumber attnum;
3789 : :
3790 : : /*
3791 : : * Grab an exclusive lock on the target table, which we will NOT release
3792 : : * until end of transaction.
3793 : : */
3794 : 270 : targetrelation = relation_open(myrelid, AccessExclusiveLock);
3795 : 270 : renameatt_check(myrelid, RelationGetForm(targetrelation), recursing);
3796 : :
3797 : : /*
3798 : : * if the 'recurse' flag is set then we are supposed to rename this
3799 : : * attribute in all classes that inherit from 'relname' (as well as in
3800 : : * 'relname').
3801 : : *
3802 : : * any permissions or problems with duplicate attributes will cause the
3803 : : * whole transaction to abort, which is what we want -- all or nothing.
3804 : : */
8024 tgl@sss.pgh.pa.us 3805 [ + + ]: 270 : if (recurse)
3806 : : {
3807 : : List *child_oids,
3808 : : *child_numparents;
3809 : : ListCell *lo,
3810 : : *li;
3811 : :
3812 : : /*
3813 : : * we need the number of parents for each child so that the recursive
3814 : : * calls to renameatt() can determine whether there are any parents
3815 : : * outside the inheritance hierarchy being processed.
3816 : : */
5186 rhaas@postgresql.org 3817 : 118 : child_oids = find_all_inheritors(myrelid, AccessExclusiveLock,
3818 : : &child_numparents);
3819 : :
3820 : : /*
3821 : : * find_all_inheritors does the recursive search of the inheritance
3822 : : * hierarchy, so all we have to do is process all of the relids in the
3823 : : * list that it returns.
3824 : : */
3825 [ + - + + : 355 : forboth(lo, child_oids, li, child_numparents)
+ - + + +
+ + - +
+ ]
3826 : : {
3827 : 252 : Oid childrelid = lfirst_oid(lo);
3828 : 252 : int numparents = lfirst_int(li);
3829 : :
7898 tgl@sss.pgh.pa.us 3830 [ + + ]: 252 : if (childrelid == myrelid)
8220 3831 : 118 : continue;
3832 : : /* note we need not recurse again */
4891 peter_e@gmx.net 3833 : 134 : renameatt_internal(childrelid, oldattname, newattname, false, true, numparents, behavior);
3834 : : }
3835 : : }
3836 : : else
3837 : : {
3838 : : /*
3839 : : * If we are told not to recurse, there had better not be any child
3840 : : * tables; else the rename would put them out of step.
3841 : : *
3842 : : * expected_parents will only be 0 if we are not already recursing.
3843 : : */
5186 rhaas@postgresql.org 3844 [ + + + + ]: 170 : if (expected_parents == 0 &&
1082 alvherre@alvh.no-ip. 3845 : 18 : find_inheritance_children(myrelid, NoLock) != NIL)
7574 tgl@sss.pgh.pa.us 3846 [ + - ]: 6 : ereport(ERROR,
3847 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
3848 : : errmsg("inherited column \"%s\" must be renamed in child tables too",
3849 : : oldattname)));
3850 : : }
3851 : :
3852 : : /* rename attributes in typed tables of composite type */
4891 peter_e@gmx.net 3853 [ + + ]: 249 : if (targetrelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
3854 : : {
3855 : : List *child_oids;
3856 : : ListCell *lo;
3857 : :
3858 : 12 : child_oids = find_typed_table_dependencies(targetrelation->rd_rel->reltype,
2489 tgl@sss.pgh.pa.us 3859 : 12 : RelationGetRelationName(targetrelation),
3860 : : behavior);
3861 : :
4891 peter_e@gmx.net 3862 [ + + + + : 12 : foreach(lo, child_oids)
+ + ]
3863 : 3 : renameatt_internal(lfirst_oid(lo), oldattname, newattname, true, true, 0, behavior);
3864 : : }
3865 : :
1910 andres@anarazel.de 3866 : 246 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
3867 : :
7898 tgl@sss.pgh.pa.us 3868 : 246 : atttup = SearchSysCacheCopyAttName(myrelid, oldattname);
8024 3869 [ + + ]: 246 : if (!HeapTupleIsValid(atttup))
7574 3870 [ + - ]: 12 : ereport(ERROR,
3871 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
3872 : : errmsg("column \"%s\" does not exist",
3873 : : oldattname)));
7898 3874 : 234 : attform = (Form_pg_attribute) GETSTRUCT(atttup);
3875 : :
7627 3876 : 234 : attnum = attform->attnum;
7284 3877 [ - + ]: 234 : if (attnum <= 0)
7574 tgl@sss.pgh.pa.us 3878 [ # # ]:UBC 0 : ereport(ERROR,
3879 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3880 : : errmsg("cannot rename system column \"%s\"",
3881 : : oldattname)));
3882 : :
3883 : : /*
3884 : : * if the attribute is inherited, forbid the renaming. if this is a
3885 : : * top-level call to renameatt(), then expected_parents will be 0, so the
3886 : : * effect of this code will be to prohibit the renaming if the attribute
3887 : : * is inherited at all. if this is a recursive call to renameatt(),
3888 : : * expected_parents will be the number of parents the current relation has
3889 : : * within the inheritance hierarchy being processed, so we'll prohibit the
3890 : : * renaming only if there are additional parents from elsewhere.
3891 : : */
5186 rhaas@postgresql.org 3892 [ + + ]:CBC 234 : if (attform->attinhcount > expected_parents)
7574 tgl@sss.pgh.pa.us 3893 [ + - ]: 15 : ereport(ERROR,
3894 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
3895 : : errmsg("cannot rename inherited column \"%s\"",
3896 : : oldattname)));
3897 : :
3898 : : /* new name should not already exist */
3182 andrew@dunslane.net 3899 : 219 : (void) check_for_column_name_collision(targetrelation, newattname, false);
3900 : :
3901 : : /* apply the update */
7898 tgl@sss.pgh.pa.us 3902 : 213 : namestrcpy(&(attform->attname), newattname);
3903 : :
2630 alvherre@alvh.no-ip. 3904 : 213 : CatalogTupleUpdate(attrelation, &atttup->t_self, atttup);
3905 : :
4046 rhaas@postgresql.org 3906 [ - + ]: 213 : InvokeObjectPostAlterHook(RelationRelationId, myrelid, attnum);
3907 : :
8024 tgl@sss.pgh.pa.us 3908 : 213 : heap_freetuple(atttup);
3909 : :
1910 andres@anarazel.de 3910 : 213 : table_close(attrelation, RowExclusiveLock);
3911 : :
2489 tgl@sss.pgh.pa.us 3912 : 213 : relation_close(targetrelation, NoLock); /* close rel but keep lock */
3913 : :
3330 alvherre@alvh.no-ip. 3914 : 213 : return attnum;
3915 : : }
3916 : :
3917 : : /*
3918 : : * Perform permissions and integrity checks before acquiring a relation lock.
3919 : : */
3920 : : static void
4504 rhaas@postgresql.org 3921 : 198 : RangeVarCallbackForRenameAttribute(const RangeVar *rv, Oid relid, Oid oldrelid,
3922 : : void *arg)
3923 : : {
3924 : : HeapTuple tuple;
3925 : : Form_pg_class form;
3926 : :
3927 : 198 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
3928 [ + + ]: 198 : if (!HeapTupleIsValid(tuple))
4326 bruce@momjian.us 3929 : 18 : return; /* concurrently dropped */
4504 rhaas@postgresql.org 3930 : 180 : form = (Form_pg_class) GETSTRUCT(tuple);
3931 : 180 : renameatt_check(relid, form, false);
3932 : 176 : ReleaseSysCache(tuple);
3933 : : }
3934 : :
3935 : : /*
3936 : : * renameatt - changes the name of an attribute in a relation
3937 : : *
3938 : : * The returned ObjectAddress is that of the renamed column.
3939 : : */
3940 : : ObjectAddress
3941 : 152 : renameatt(RenameStmt *stmt)
3942 : : {
3943 : : Oid relid;
3944 : : AttrNumber attnum;
3945 : : ObjectAddress address;
3946 : :
3947 : : /* lock level taken here should match renameatt_internal */
3948 : 152 : relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
2207 andres@anarazel.de 3949 : 152 : stmt->missing_ok ? RVR_MISSING_OK : 0,
3950 : : RangeVarCallbackForRenameAttribute,
3951 : : NULL);
3952 : :
4465 simon@2ndQuadrant.co 3953 [ + + ]: 145 : if (!OidIsValid(relid))
3954 : : {
3955 [ + - ]: 12 : ereport(NOTICE,
3956 : : (errmsg("relation \"%s\" does not exist, skipping",
3957 : : stmt->relation->relname)));
3330 alvherre@alvh.no-ip. 3958 : 12 : return InvalidObjectAddress;
3959 : : }
3960 : :
3961 : : attnum =
3962 : 133 : renameatt_internal(relid,
2489 tgl@sss.pgh.pa.us 3963 : 133 : stmt->subname, /* old att name */
3964 : 133 : stmt->newname, /* new att name */
2669 3965 : 133 : stmt->relation->inh, /* recursive? */
3966 : : false, /* recursing? */
3967 : : 0, /* expected inhcount */
3968 : : stmt->behavior);
3969 : :
3330 alvherre@alvh.no-ip. 3970 : 91 : ObjectAddressSubSet(address, RelationRelationId, relid, attnum);
3971 : :
3972 : 91 : return address;
3973 : : }
3974 : :
3975 : : /*
3976 : : * same logic as renameatt_internal
3977 : : */
3978 : : static ObjectAddress
4418 peter_e@gmx.net 3979 : 42 : rename_constraint_internal(Oid myrelid,
3980 : : Oid mytypid,
3981 : : const char *oldconname,
3982 : : const char *newconname,
3983 : : bool recurse,
3984 : : bool recursing,
3985 : : int expected_parents)
3986 : : {
4394 3987 : 42 : Relation targetrelation = NULL;
3988 : : Oid constraintOid;
3989 : : HeapTuple tuple;
3990 : : Form_pg_constraint con;
3991 : : ObjectAddress address;
3992 : :
534 peter@eisentraut.org 3993 [ + + - + ]: 42 : Assert(!myrelid || !mytypid);
3994 : :
4394 peter_e@gmx.net 3995 [ + + ]: 42 : if (mytypid)
3996 : : {
3997 : 3 : constraintOid = get_domain_constraint_oid(mytypid, oldconname, false);
3998 : : }
3999 : : else
4000 : : {
4001 : 39 : targetrelation = relation_open(myrelid, AccessExclusiveLock);
4002 : :
4003 : : /*
4004 : : * don't tell it whether we're recursing; we allow changing typed
4005 : : * tables here
4006 : : */
4007 : 39 : renameatt_check(myrelid, RelationGetForm(targetrelation), false);
4008 : :
4009 : 39 : constraintOid = get_relation_constraint_oid(myrelid, oldconname, false);
4010 : : }
4011 : :
4418 4012 : 42 : tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraintOid));
4013 [ - + ]: 42 : if (!HeapTupleIsValid(tuple))
4418 peter_e@gmx.net 4014 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for constraint %u",
4015 : : constraintOid);
4418 peter_e@gmx.net 4016 :CBC 42 : con = (Form_pg_constraint) GETSTRUCT(tuple);
4017 : :
233 alvherre@alvh.no-ip. 4018 [ + + ]:GNC 42 : if (myrelid &&
4019 [ + + ]: 39 : (con->contype == CONSTRAINT_CHECK ||
4020 [ - + ]: 9 : con->contype == CONSTRAINT_NOTNULL) &&
4021 [ + + ]: 30 : !con->connoinherit)
4022 : : {
4418 peter_e@gmx.net 4023 [ + + ]:CBC 24 : if (recurse)
4024 : : {
4025 : : List *child_oids,
4026 : : *child_numparents;
4027 : : ListCell *lo,
4028 : : *li;
4029 : :
4030 : 15 : child_oids = find_all_inheritors(myrelid, AccessExclusiveLock,
4031 : : &child_numparents);
4032 : :
4033 [ + - + + : 36 : forboth(lo, child_oids, li, child_numparents)
+ - + + +
+ + - +
+ ]
4034 : : {
4035 : 21 : Oid childrelid = lfirst_oid(lo);
4036 : 21 : int numparents = lfirst_int(li);
4037 : :
4038 [ + + ]: 21 : if (childrelid == myrelid)
4039 : 15 : continue;
4040 : :
4394 4041 : 6 : rename_constraint_internal(childrelid, InvalidOid, oldconname, newconname, false, true, numparents);
4042 : : }
4043 : : }
4044 : : else
4045 : : {
4418 4046 [ + + + - ]: 12 : if (expected_parents == 0 &&
1082 alvherre@alvh.no-ip. 4047 : 3 : find_inheritance_children(myrelid, NoLock) != NIL)
4418 peter_e@gmx.net 4048 [ + - ]: 3 : ereport(ERROR,
4049 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
4050 : : errmsg("inherited constraint \"%s\" must be renamed in child tables too",
4051 : : oldconname)));
4052 : : }
4053 : :
4054 [ + + ]: 21 : if (con->coninhcount > expected_parents)
4055 [ + - ]: 3 : ereport(ERROR,
4056 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
4057 : : errmsg("cannot rename inherited constraint \"%s\"",
4058 : : oldconname)));
4059 : : }
4060 : :
4061 [ + + ]: 36 : if (con->conindid
4062 [ + + ]: 9 : && (con->contype == CONSTRAINT_PRIMARY
4063 [ - + ]: 3 : || con->contype == CONSTRAINT_UNIQUE
4418 peter_e@gmx.net 4064 [ # # ]:UBC 0 : || con->contype == CONSTRAINT_EXCLUSION))
4065 : : /* rename the index; this renames the constraint as well */
1998 peter_e@gmx.net 4066 :CBC 9 : RenameRelationInternal(con->conindid, newconname, false, true);
4067 : : else
4418 4068 : 27 : RenameConstraintById(constraintOid, newconname);
4069 : :
3330 alvherre@alvh.no-ip. 4070 : 36 : ObjectAddressSet(address, ConstraintRelationId, constraintOid);
4071 : :
4418 peter_e@gmx.net 4072 : 36 : ReleaseSysCache(tuple);
4073 : :
4394 4074 [ + + ]: 36 : if (targetrelation)
4075 : : {
4076 : : /*
4077 : : * Invalidate relcache so as others can see the new constraint name.
4078 : : */
1945 michael@paquier.xyz 4079 : 33 : CacheInvalidateRelcache(targetrelation);
4080 : :
4081 : 33 : relation_close(targetrelation, NoLock); /* close rel but keep lock */
4082 : : }
4083 : :
3330 alvherre@alvh.no-ip. 4084 : 36 : return address;
4085 : : }
4086 : :
4087 : : ObjectAddress
4418 peter_e@gmx.net 4088 : 39 : RenameConstraint(RenameStmt *stmt)
4089 : : {
4394 4090 : 39 : Oid relid = InvalidOid;
4091 : 39 : Oid typid = InvalidOid;
4092 : :
3400 alvherre@alvh.no-ip. 4093 [ + + ]: 39 : if (stmt->renameType == OBJECT_DOMCONSTRAINT)
4094 : : {
4095 : : Relation rel;
4096 : : HeapTuple tup;
4097 : :
2710 peter_e@gmx.net 4098 : 3 : typid = typenameTypeId(NULL, makeTypeNameFromNameList(castNode(List, stmt->object)));
1910 andres@anarazel.de 4099 : 3 : rel = table_open(TypeRelationId, RowExclusiveLock);
4394 peter_e@gmx.net 4100 : 3 : tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
4101 [ - + ]: 3 : if (!HeapTupleIsValid(tup))
4394 peter_e@gmx.net 4102 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for type %u", typid);
4394 peter_e@gmx.net 4103 :CBC 3 : checkDomainOwner(tup);
4104 : 3 : ReleaseSysCache(tup);
1910 andres@anarazel.de 4105 : 3 : table_close(rel, NoLock);
4106 : : }
4107 : : else
4108 : : {
4109 : : /* lock level taken here should match rename_constraint_internal */
4394 peter_e@gmx.net 4110 : 36 : relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
2207 andres@anarazel.de 4111 : 36 : stmt->missing_ok ? RVR_MISSING_OK : 0,
4112 : : RangeVarCallbackForRenameAttribute,
4113 : : NULL);
3309 bruce@momjian.us 4114 [ + + ]: 36 : if (!OidIsValid(relid))
4115 : : {
4116 [ + - ]: 3 : ereport(NOTICE,
4117 : : (errmsg("relation \"%s\" does not exist, skipping",
4118 : : stmt->relation->relname)));
4119 : 3 : return InvalidObjectAddress;
4120 : : }
4121 : : }
4122 : :
4123 : : return
4130 rhaas@postgresql.org 4124 : 36 : rename_constraint_internal(relid, typid,
4125 : 36 : stmt->subname,
4126 : 36 : stmt->newname,
2669 tgl@sss.pgh.pa.us 4127 [ + + ]: 69 : (stmt->relation &&
2489 4128 [ + + ]: 33 : stmt->relation->inh), /* recursive? */
4129 : : false, /* recursing? */
4130 rhaas@postgresql.org 4130 :ECB (36) : 0 /* expected inhcount */ );
4131 : : }
4132 : :
4133 : : /*
4134 : : * Execute ALTER TABLE/INDEX/SEQUENCE/VIEW/MATERIALIZED VIEW/FOREIGN TABLE
4135 : : * RENAME
4136 : : */
4137 : : ObjectAddress
4504 rhaas@postgresql.org 4138 :CBC 255 : RenameRelation(RenameStmt *stmt)
4139 : : {
908 alvherre@alvh.no-ip. 4140 : 255 : bool is_index_stmt = stmt->renameType == OBJECT_INDEX;
4141 : : Oid relid;
4142 : : ObjectAddress address;
4143 : :
4144 : : /*
4145 : : * Grab an exclusive lock on the target table, index, sequence, view,
4146 : : * materialized view, or foreign table, which we will NOT release until
4147 : : * end of transaction.
4148 : : *
4149 : : * Lock level used here should match RenameRelationInternal, to avoid lock
4150 : : * escalation. However, because ALTER INDEX can be used with any relation
4151 : : * type, we mustn't believe without verification.
4152 : : */
4153 : : for (;;)
4465 simon@2ndQuadrant.co 4154 : 6 : {
4155 : : LOCKMODE lockmode;
4156 : : char relkind;
4157 : : bool obj_is_index;
4158 : :
908 alvherre@alvh.no-ip. 4159 [ + + ]: 261 : lockmode = is_index_stmt ? ShareUpdateExclusiveLock : AccessExclusiveLock;
4160 : :
4161 : 261 : relid = RangeVarGetRelidExtended(stmt->relation, lockmode,
4162 : 261 : stmt->missing_ok ? RVR_MISSING_OK : 0,
4163 : : RangeVarCallbackForAlterRelation,
4164 : : (void *) stmt);
4165 : :
4166 [ + + ]: 236 : if (!OidIsValid(relid))
4167 : : {
4168 [ + - ]: 9 : ereport(NOTICE,
4169 : : (errmsg("relation \"%s\" does not exist, skipping",
4170 : : stmt->relation->relname)));
4171 : 9 : return InvalidObjectAddress;
4172 : : }
4173 : :
4174 : : /*
4175 : : * We allow mismatched statement and object types (e.g., ALTER INDEX
4176 : : * to rename a table), but we might've used the wrong lock level. If
4177 : : * that happens, retry with the correct lock level. We don't bother
4178 : : * if we already acquired AccessExclusiveLock with an index, however.
4179 : : */
4180 : 227 : relkind = get_rel_relkind(relid);
4181 [ + + + + ]: 227 : obj_is_index = (relkind == RELKIND_INDEX ||
4182 : : relkind == RELKIND_PARTITIONED_INDEX);
4183 [ + + + + ]: 227 : if (obj_is_index || is_index_stmt == obj_is_index)
4184 : : break;
4185 : :
4186 : 6 : UnlockRelationOid(relid, lockmode);
4187 : 6 : is_index_stmt = obj_is_index;
4188 : : }
4189 : :
4190 : : /* Do the work */
4191 : 221 : RenameRelationInternal(relid, stmt->newname, false, is_index_stmt);
4192 : :
3330 4193 : 215 : ObjectAddressSet(address, RelationRelationId, relid);
4194 : :
4195 : 215 : return address;
4196 : : }
4197 : :
4198 : : /*
4199 : : * RenameRelationInternal - change the name of a relation
4200 : : */
4201 : : void
1998 peter_e@gmx.net 4202 : 665 : RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bool is_index)
4203 : : {
4204 : : Relation targetrelation;
4205 : : Relation relrelation; /* for RELATION relation */
4206 : : HeapTuple reltup;
4207 : : Form_pg_class relform;
4208 : : Oid namespaceId;
4209 : :
4210 : : /*
4211 : : * Grab a lock on the target relation, which we will NOT release until end
4212 : : * of transaction. We need at least a self-exclusive lock so that
4213 : : * concurrent DDL doesn't overwrite the rename if they start updating
4214 : : * while still seeing the old version. The lock also guards against
4215 : : * triggering relcache reloads in concurrent sessions, which might not
4216 : : * handle this information changing under them. For indexes, we can use a
4217 : : * reduced lock level because RelationReloadIndexInfo() handles indexes
4218 : : * specially.
4219 : : */
4220 [ + + ]: 665 : targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
4504 rhaas@postgresql.org 4221 : 665 : namespaceId = RelationGetNamespace(targetrelation);
4222 : :
4223 : : /*
4224 : : * Find relation's pg_class tuple, and make sure newrelname isn't in use.
4225 : : */
1910 andres@anarazel.de 4226 : 665 : relrelation = table_open(RelationRelationId, RowExclusiveLock);
4227 : :
5173 rhaas@postgresql.org 4228 : 665 : reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
2489 tgl@sss.pgh.pa.us 4229 [ - + ]: 665 : if (!HeapTupleIsValid(reltup)) /* shouldn't happen */
7574 tgl@sss.pgh.pa.us 4230 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", myrelid);
6182 tgl@sss.pgh.pa.us 4231 :CBC 665 : relform = (Form_pg_class) GETSTRUCT(reltup);
4232 : :
8024 4233 [ + + ]: 665 : if (get_relname_relid(newrelname, namespaceId) != InvalidOid)
7574 4234 [ + - ]: 6 : ereport(ERROR,
4235 : : (errcode(ERRCODE_DUPLICATE_TABLE),
4236 : : errmsg("relation \"%s\" already exists",
4237 : : newrelname)));
4238 : :
4239 : : /*
4240 : : * RenameRelation is careful not to believe the caller's idea of the
4241 : : * relation kind being handled. We don't have to worry about this, but
4242 : : * let's not be totally oblivious to it. We can process an index as
4243 : : * not-an-index, but not the other way around.
4244 : : */
908 alvherre@alvh.no-ip. 4245 [ + + + + : 659 : Assert(!is_index ||
+ - - + ]
4246 : : is_index == (targetrelation->rd_rel->relkind == RELKIND_INDEX ||
4247 : : targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX));
4248 : :
4249 : : /*
4250 : : * Update pg_class tuple with new relname. (Scribbling on reltup is OK
4251 : : * because it's a copy...)
4252 : : */
6182 tgl@sss.pgh.pa.us 4253 : 659 : namestrcpy(&(relform->relname), newrelname);
4254 : :
2630 alvherre@alvh.no-ip. 4255 : 659 : CatalogTupleUpdate(relrelation, &reltup->t_self, reltup);
4256 : :
4046 rhaas@postgresql.org 4257 [ - + ]: 659 : InvokeObjectPostAlterHookArg(RelationRelationId, myrelid, 0,
4258 : : InvalidOid, is_internal);
4259 : :
8024 tgl@sss.pgh.pa.us 4260 : 659 : heap_freetuple(reltup);
1910 andres@anarazel.de 4261 : 659 : table_close(relrelation, RowExclusiveLock);
4262 : :
4263 : : /*
4264 : : * Also rename the associated type, if any.
4265 : : */
6182 tgl@sss.pgh.pa.us 4266 [ + + ]: 659 : if (OidIsValid(targetrelation->rd_rel->reltype))
5870 4267 : 77 : RenameTypeInternal(targetrelation->rd_rel->reltype,
4268 : : newrelname, namespaceId);
4269 : :
4270 : : /*
4271 : : * Also rename the associated constraint, if any.
4272 : : */
2277 alvherre@alvh.no-ip. 4273 [ + + ]: 659 : if (targetrelation->rd_rel->relkind == RELKIND_INDEX ||
4274 [ + + ]: 359 : targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
4275 : : {
5932 tgl@sss.pgh.pa.us 4276 : 309 : Oid constraintId = get_index_constraint(myrelid);
4277 : :
4278 [ + + ]: 309 : if (OidIsValid(constraintId))
4279 : 18 : RenameConstraintById(constraintId, newrelname);
4280 : : }
4281 : :
4282 : : /*
4283 : : * Close rel, but keep lock!
4284 : : */
8024 4285 : 659 : relation_close(targetrelation, NoLock);
963 akapila@postgresql.o 4286 : 659 : }
4287 : :
4288 : : /*
4289 : : * ResetRelRewrite - reset relrewrite
4290 : : */
4291 : : void
4292 : 206 : ResetRelRewrite(Oid myrelid)
4293 : : {
4294 : : Relation relrelation; /* for RELATION relation */
4295 : : HeapTuple reltup;
4296 : : Form_pg_class relform;
4297 : :
4298 : : /*
4299 : : * Find relation's pg_class tuple.
4300 : : */
4301 : 206 : relrelation = table_open(RelationRelationId, RowExclusiveLock);
4302 : :
4303 : 206 : reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
4304 [ - + ]: 206 : if (!HeapTupleIsValid(reltup)) /* shouldn't happen */
963 akapila@postgresql.o 4305 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", myrelid);
963 akapila@postgresql.o 4306 :CBC 206 : relform = (Form_pg_class) GETSTRUCT(reltup);
4307 : :
4308 : : /*
4309 : : * Update pg_class tuple.
4310 : : */
4311 : 206 : relform->relrewrite = InvalidOid;
4312 : :
4313 : 206 : CatalogTupleUpdate(relrelation, &reltup->t_self, reltup);
4314 : :
4315 : 206 : heap_freetuple(reltup);
4316 : 206 : table_close(relrelation, RowExclusiveLock);
8024 tgl@sss.pgh.pa.us 4317 : 206 : }
4318 : :
4319 : : /*
4320 : : * Disallow ALTER TABLE (and similar commands) when the current backend has
4321 : : * any open reference to the target table besides the one just acquired by
4322 : : * the calling command; this implies there's an open cursor or active plan.
4323 : : * We need this check because our lock doesn't protect us against stomping
4324 : : * on our own foot, only other people's feet!
4325 : : *
4326 : : * For ALTER TABLE, the only case known to cause serious trouble is ALTER
4327 : : * COLUMN TYPE, and some changes are obviously pretty benign, so this could
4328 : : * possibly be relaxed to only error out for certain types of alterations.
4329 : : * But the use-case for allowing any of these things is not obvious, so we
4330 : : * won't work hard at it for now.
4331 : : *
4332 : : * We also reject these commands if there are any pending AFTER trigger events
4333 : : * for the rel. This is certainly necessary for the rewriting variants of
4334 : : * ALTER TABLE, because they don't preserve tuple TIDs and so the pending
4335 : : * events would try to fetch the wrong tuples. It might be overly cautious
4336 : : * in other cases, but again it seems better to err on the side of paranoia.
4337 : : *
4338 : : * REINDEX calls this with "rel" referencing the index to be rebuilt; here
4339 : : * we are worried about active indexscans on the index. The trigger-event
4340 : : * check can be skipped, since we are doing no damage to the parent table.
4341 : : *
4342 : : * The statement name (eg, "ALTER TABLE") is passed for use in error messages.
4343 : : */
4344 : : void
5919 4345 : 68070 : CheckTableNotInUse(Relation rel, const char *stmt)
4346 : : {
4347 : : int expected_refcnt;
4348 : :
4349 [ + + ]: 68070 : expected_refcnt = rel->rd_isnailed ? 2 : 1;
4350 [ + + ]: 68070 : if (rel->rd_refcnt != expected_refcnt)
4351 [ + - ]: 12 : ereport(ERROR,
4352 : : (errcode(ERRCODE_OBJECT_IN_USE),
4353 : : /* translator: first %s is a SQL command, eg ALTER TABLE */
4354 : : errmsg("cannot %s \"%s\" because it is being used by active queries in this session",
4355 : : stmt, RelationGetRelationName(rel))));
4356 : :
4357 [ + + ]: 68058 : if (rel->rd_rel->relkind != RELKIND_INDEX &&
2277 alvherre@alvh.no-ip. 4358 [ + + + + ]: 106631 : rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
5919 tgl@sss.pgh.pa.us 4359 : 52803 : AfterTriggerPendingOnRel(RelationGetRelid(rel)))
4360 [ + - ]: 9 : ereport(ERROR,
4361 : : (errcode(ERRCODE_OBJECT_IN_USE),
4362 : : /* translator: first %s is a SQL command, eg ALTER TABLE */
4363 : : errmsg("cannot %s \"%s\" because it has pending trigger events",
4364 : : stmt, RelationGetRelationName(rel))));
4365 : 68049 : }
4366 : :
4367 : : /*
4368 : : * AlterTableLookupRelation
4369 : : * Look up, and lock, the OID for the relation named by an alter table
4370 : : * statement.
4371 : : */
4372 : : Oid
4482 rhaas@postgresql.org 4373 : 17109 : AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
4374 : : {
2207 andres@anarazel.de 4375 : 34176 : return RangeVarGetRelidExtended(stmt->relation, lockmode,
4376 : 17109 : stmt->missing_ok ? RVR_MISSING_OK : 0,
4377 : : RangeVarCallbackForAlterRelation,
4378 : : (void *) stmt);
4379 : : }
4380 : :
4381 : : /*
4382 : : * AlterTable
4383 : : * Execute ALTER TABLE, which can be a list of subcommands
4384 : : *
4385 : : * ALTER TABLE is performed in three phases:
4386 : : * 1. Examine subcommands and perform pre-transformation checking.
4387 : : * 2. Validate and transform subcommands, and update system catalogs.
4388 : : * 3. Scan table(s) to check new constraints, and optionally recopy
4389 : : * the data into new table(s).
4390 : : * Phase 3 is not performed unless one or more of the subcommands requires
4391 : : * it. The intention of this design is to allow multiple independent
4392 : : * updates of the table schema to be performed with only one pass over the
4393 : : * data.
4394 : : *
4395 : : * ATPrepCmd performs phase 1. A "work queue" entry is created for
4396 : : * each table to be affected (there may be multiple affected tables if the
4397 : : * commands traverse a table inheritance hierarchy). Also we do preliminary
4398 : : * validation of the subcommands. Because earlier subcommands may change
4399 : : * the catalog state seen by later commands, there are limits to what can
4400 : : * be done in this phase. Generally, this phase acquires table locks,
4401 : : * checks permissions and relkind, and recurses to find child tables.
4402 : : *
4403 : : * ATRewriteCatalogs performs phase 2 for each affected table.
4404 : : * Certain subcommands need to be performed before others to avoid
4405 : : * unnecessary conflicts; for example, DROP COLUMN should come before
4406 : : * ADD COLUMN. Therefore phase 1 divides the subcommands into multiple
4407 : : * lists, one for each logical "pass" of phase 2.
4408 : : *
4409 : : * ATRewriteTables performs phase 3 for those tables that need it.
4410 : : *
4411 : : * For most subcommand types, phases 2 and 3 do no explicit recursion,
4412 : : * since phase 1 already does it. However, for certain subcommand types
4413 : : * it is only possible to determine how to recurse at phase 2 time; for
4414 : : * those cases, phase 1 sets the cmd->recurse flag.
4415 : : *
4416 : : * Thanks to the magic of MVCC, an error anywhere along the way rolls back
4417 : : * the whole operation; we don't have to do anything special to clean up.
4418 : : *
4419 : : * The caller must lock the relation, with an appropriate lock level
4420 : : * for the subcommands requested, using AlterTableGetLockLevel(stmt->cmds)
4421 : : * or higher. We pass the lock level down
4422 : : * so that we can apply it recursively to inherited tables. Note that the
4423 : : * lock level we want as we recurse might well be higher than required for
4424 : : * that specific subcommand. So we pass down the overall lock requirement,
4425 : : * rather than reassess it at lower levels.
4426 : : *
4427 : : * The caller also provides a "context" which is to be passed back to
4428 : : * utility.c when we need to execute a subcommand such as CREATE INDEX.
4429 : : * Some of the fields therein, such as the relid, are used here as well.
4430 : : */
4431 : : void
1551 tgl@sss.pgh.pa.us 4432 : 16998 : AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
4433 : : AlterTableUtilityContext *context)
4434 : : {
4435 : : Relation rel;
4436 : :
4437 : : /* Caller is required to provide an adequate lock. */
4438 : 16998 : rel = relation_open(context->relid, NoLock);
4439 : :
5919 4440 : 16998 : CheckTableNotInUse(rel, "ALTER TABLE");
4441 : :
1551 4442 : 16989 : ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
5947 4443 : 15376 : }
4444 : :
4445 : : /*
4446 : : * AlterTableInternal
4447 : : *
4448 : : * ALTER TABLE with target specified by OID
4449 : : *
4450 : : * We do not reject if the relation is already open, because it's quite
4451 : : * likely that one or more layers of caller have it open. That means it
4452 : : * is unsafe to use this entry point for alterations that could break
4453 : : * existing query plans. On the assumption it's not used for such, we
4454 : : * don't have to reject pending AFTER triggers, either.
4455 : : *
4456 : : * Also, since we don't have an AlterTableUtilityContext, this cannot be
4457 : : * used for any subcommand types that require parse transformation or
4458 : : * could generate subcommands that have to be passed to ProcessUtility.
4459 : : */
4460 : : void
7284 4461 : 139 : AlterTableInternal(Oid relid, List *cmds, bool recurse)
4462 : : {
4463 : : Relation rel;
4753 bruce@momjian.us 4464 : 139 : LOCKMODE lockmode = AlterTableGetLockLevel(cmds);
4465 : :
5009 simon@2ndQuadrant.co 4466 : 139 : rel = relation_open(relid, lockmode);
4467 : :
3261 alvherre@alvh.no-ip. 4468 : 139 : EventTriggerAlterTableRelid(relid);
4469 : :
1551 tgl@sss.pgh.pa.us 4470 : 139 : ATController(NULL, rel, cmds, recurse, lockmode, NULL);
5009 simon@2ndQuadrant.co 4471 : 139 : }
4472 : :
4473 : : /*
4474 : : * AlterTableGetLockLevel
4475 : : *
4476 : : * Sets the overall lock level required for the supplied list of subcommands.
4477 : : * Policy for doing this set according to needs of AlterTable(), see
4478 : : * comments there for overall explanation.
4479 : : *
4480 : : * Function is called before and after parsing, so it must give same
4481 : : * answer each time it is called. Some subcommands are transformed
4482 : : * into other subcommand types, so the transform must never be made to a
4483 : : * lower lock level than previously assigned. All transforms are noted below.
4484 : : *
4485 : : * Since this is called before we lock the table we cannot use table metadata
4486 : : * to influence the type of lock we acquire.
4487 : : *
4488 : : * There should be no lockmodes hardcoded into the subcommand functions. All
4489 : : * lockmode decisions for ALTER TABLE are made here only. The one exception is
4490 : : * ALTER TABLE RENAME which is treated as a different statement type T_RenameStmt
4491 : : * and does not travel through this section of code and cannot be combined with
4492 : : * any of the subcommands given here.
4493 : : *
4494 : : * Note that Hot Standby only knows about AccessExclusiveLocks on the primary
4495 : : * so any changes that might affect SELECTs running on standbys need to use
4496 : : * AccessExclusiveLocks even if you think a lesser lock would do, unless you
4497 : : * have a solution for that also.
4498 : : *
4499 : : * Also note that pg_dump uses only an AccessShareLock, meaning that anything
4500 : : * that takes a lock less than AccessExclusiveLock can change object definitions
4501 : : * while pg_dump is running. Be careful to check that the appropriate data is
4502 : : * derived by pg_dump using an MVCC snapshot, rather than syscache lookups,
4503 : : * otherwise we might end up with an inconsistent dump that can't restore.
4504 : : */
4505 : : LOCKMODE
4506 : 17248 : AlterTableGetLockLevel(List *cmds)
4507 : : {
4508 : : /*
4509 : : * This only works if we read catalog tables using MVCC snapshots.
4510 : : */
4511 : : ListCell *lcmd;
4753 bruce@momjian.us 4512 : 17248 : LOCKMODE lockmode = ShareUpdateExclusiveLock;
4513 : :
5009 simon@2ndQuadrant.co 4514 [ + - + + : 35472 : foreach(lcmd, cmds)
+ + ]
4515 : : {
4516 : 18224 : AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
4753 bruce@momjian.us 4517 : 18224 : LOCKMODE cmd_lockmode = AccessExclusiveLock; /* default for compiler */
4518 : :
5009 simon@2ndQuadrant.co 4519 [ + + + + : 18224 : switch (cmd->subtype)
+ + + + +
+ + + + +
+ + + + +
+ + - ]
4520 : : {
4521 : : /*
4522 : : * These subcommands rewrite the heap, so require full locks.
4523 : : */
4753 bruce@momjian.us 4524 : 1606 : case AT_AddColumn: /* may rewrite heap, in some cases and visible
4525 : : * to SELECT */
4526 : : case AT_SetAccessMethod: /* must rewrite heap */
4527 : : case AT_SetTableSpace: /* must rewrite heap */
4528 : : case AT_AlterColumnType: /* must rewrite heap */
3661 simon@2ndQuadrant.co 4529 : 1606 : cmd_lockmode = AccessExclusiveLock;
4530 : 1606 : break;
4531 : :
4532 : : /*
4533 : : * These subcommands may require addition of toast tables. If
4534 : : * we add a toast table to a table currently being scanned, we
4535 : : * might miss data added to the new toast table by concurrent
4536 : : * insert transactions.
4537 : : */
2489 tgl@sss.pgh.pa.us 4538 : 106 : case AT_SetStorage: /* may add toast tables, see
4539 : : * ATRewriteCatalogs() */
3661 simon@2ndQuadrant.co 4540 : 106 : cmd_lockmode = AccessExclusiveLock;
4541 : 106 : break;
4542 : :
4543 : : /*
4544 : : * Removing constraints can affect SELECTs that have been
4545 : : * optimized assuming the constraint holds true. See also
4546 : : * CloneFkReferenced.
4547 : : */
2489 tgl@sss.pgh.pa.us 4548 : 614 : case AT_DropConstraint: /* as DROP INDEX */
4549 : : case AT_DropNotNull: /* may change some SQL plans */
3661 simon@2ndQuadrant.co 4550 : 614 : cmd_lockmode = AccessExclusiveLock;
4551 : 614 : break;
4552 : :
4553 : : /*
4554 : : * Subcommands that may be visible to concurrent SELECTs
4555 : : */
2489 tgl@sss.pgh.pa.us 4556 : 855 : case AT_DropColumn: /* change visible to SELECT */
4557 : : case AT_AddColumnToView: /* CREATE VIEW */
4558 : : case AT_DropOids: /* used to equiv to DropColumn */
4559 : : case AT_EnableAlwaysRule: /* may change SELECT rules */
4560 : : case AT_EnableReplicaRule: /* may change SELECT rules */
4561 : : case AT_EnableRule: /* may change SELECT rules */
4562 : : case AT_DisableRule: /* may change SELECT rules */
3661 simon@2ndQuadrant.co 4563 : 855 : cmd_lockmode = AccessExclusiveLock;
4564 : 855 : break;
4565 : :
4566 : : /*
4567 : : * Changing owner may remove implicit SELECT privileges
4568 : : */
2489 tgl@sss.pgh.pa.us 4569 : 910 : case AT_ChangeOwner: /* change visible to SELECT */
3661 simon@2ndQuadrant.co 4570 : 910 : cmd_lockmode = AccessExclusiveLock;
4571 : 910 : break;
4572 : :
4573 : : /*
4574 : : * Changing foreign table options may affect optimization.
4575 : : */
4852 rhaas@postgresql.org 4576 : 119 : case AT_GenericOptions:
4577 : : case AT_AlterColumnGenericOptions:
5009 simon@2ndQuadrant.co 4578 : 119 : cmd_lockmode = AccessExclusiveLock;
4579 : 119 : break;
4580 : :
4581 : : /*
4582 : : * These subcommands affect write operations only.
4583 : : */
4584 : 170 : case AT_EnableTrig:
4585 : : case AT_EnableAlwaysTrig:
4586 : : case AT_EnableReplicaTrig:
4587 : : case AT_EnableTrigAll:
4588 : : case AT_EnableTrigUser:
4589 : : case AT_DisableTrig:
4590 : : case AT_DisableTrigAll:
4591 : : case AT_DisableTrigUser:
3297 4592 : 170 : cmd_lockmode = ShareRowExclusiveLock;
4593 : 170 : break;
4594 : :
4595 : : /*
4596 : : * These subcommands affect write operations only. XXX
4597 : : * Theoretically, these could be ShareRowExclusiveLock.
4598 : : */
4599 : 4766 : case AT_ColumnDefault:
4600 : : case AT_CookedColumnDefault:
4601 : : case AT_AlterConstraint:
4602 : : case AT_AddIndex: /* from ADD CONSTRAINT */
4603 : : case AT_AddIndexConstraint:
4604 : : case AT_ReplicaIdentity:
4605 : : case AT_SetNotNull:
4606 : : case AT_SetAttNotNull:
4607 : : case AT_EnableRowSecurity:
4608 : : case AT_DisableRowSecurity:
4609 : : case AT_ForceRowSecurity:
4610 : : case AT_NoForceRowSecurity:
4611 : : case AT_AddIdentity:
4612 : : case AT_DropIdentity:
4613 : : case AT_SetIdentity:
4614 : : case AT_SetExpression:
4615 : : case AT_DropExpression:
4616 : : case AT_SetCompression:
3661 4617 : 4766 : cmd_lockmode = AccessExclusiveLock;
5009 4618 : 4766 : break;
4619 : :
4620 : 6283 : case AT_AddConstraint:
4621 : : case AT_ReAddConstraint: /* becomes AT_AddConstraint */
4622 : : case AT_ReAddDomainConstraint: /* becomes AT_AddConstraint */
4623 [ + - ]: 6283 : if (IsA(cmd->def, Constraint))
4624 : : {
4625 : 6283 : Constraint *con = (Constraint *) cmd->def;
4626 : :
4627 [ + + + ]: 6283 : switch (con->contype)
4628 : : {
4629 : 4629 : case CONSTR_EXCLUSION:
4630 : : case CONSTR_PRIMARY:
4631 : : case CONSTR_UNIQUE:
4632 : :
4633 : : /*
4634 : : * Cases essentially the same as CREATE INDEX. We
4635 : : * could reduce the lock strength to ShareLock if
4636 : : * we can work out how to allow concurrent catalog
4637 : : * updates. XXX Might be set down to
4638 : : * ShareRowExclusiveLock but requires further
4639 : : * analysis.
4640 : : */
3661 4641 : 4629 : cmd_lockmode = AccessExclusiveLock;
5009 4642 : 4629 : break;
4643 : 1193 : case CONSTR_FOREIGN:
4644 : :
4645 : : /*
4646 : : * We add triggers to both tables when we add a
4647 : : * Foreign Key, so the lock level must be at least
4648 : : * as strong as CREATE TRIGGER.
4649 : : */
3297 4650 : 1193 : cmd_lockmode = ShareRowExclusiveLock;
5009 4651 : 1193 : break;
4652 : :
4653 : 461 : default:
3661 4654 : 461 : cmd_lockmode = AccessExclusiveLock;
4655 : : }
4656 : : }
5009 4657 : 6283 : break;
4658 : :
4659 : : /*
4660 : : * These subcommands affect inheritance behaviour. Queries
4661 : : * started before us will continue to see the old inheritance
4662 : : * behaviour, while queries started after we commit will see
4663 : : * new behaviour. No need to prevent reads or writes to the
4664 : : * subtable while we hook it up though. Changing the TupDesc
4665 : : * may be a problem, so keep highest lock.
4666 : : */
4667 : 185 : case AT_AddInherit:
4668 : : case AT_DropInherit:
3661 4669 : 185 : cmd_lockmode = AccessExclusiveLock;
5009 4670 : 185 : break;
4671 : :
4672 : : /*
4673 : : * These subcommands affect implicit row type conversion. They
4674 : : * have affects similar to CREATE/DROP CAST on queries. don't
4675 : : * provide for invalidating parse trees as a result of such
4676 : : * changes, so we keep these at AccessExclusiveLock.
4677 : : */
4743 rhaas@postgresql.org 4678 : 36 : case AT_AddOf:
4679 : : case AT_DropOf:
3661 simon@2ndQuadrant.co 4680 : 36 : cmd_lockmode = AccessExclusiveLock;
4681 : 36 : break;
4682 : :
4683 : : /*
4684 : : * Only used by CREATE OR REPLACE VIEW which must conflict
4685 : : * with an SELECTs currently using the view.
4686 : : */
4687 : 97 : case AT_ReplaceRelOptions:
4688 : 97 : cmd_lockmode = AccessExclusiveLock;
4689 : 97 : break;
4690 : :
4691 : : /*
4692 : : * These subcommands affect general strategies for performance
4693 : : * and maintenance, though don't change the semantic results
4694 : : * from normal data reads and writes. Delaying an ALTER TABLE
4695 : : * behind currently active writes only delays the point where
4696 : : * the new strategy begins to take effect, so there is no
4697 : : * benefit in waiting. In this case the minimum restriction
4698 : : * applies: we don't currently allow concurrent catalog
4699 : : * updates.
4700 : : */
2489 tgl@sss.pgh.pa.us 4701 : 117 : case AT_SetStatistics: /* Uses MVCC in getTableAttrs() */
4702 : : case AT_ClusterOn: /* Uses MVCC in getIndexes() */
4703 : : case AT_DropCluster: /* Uses MVCC in getIndexes() */
4704 : : case AT_SetOptions: /* Uses MVCC in getTableAttrs() */
4705 : : case AT_ResetOptions: /* Uses MVCC in getTableAttrs() */
5009 simon@2ndQuadrant.co 4706 : 117 : cmd_lockmode = ShareUpdateExclusiveLock;
4707 : 117 : break;
4708 : :
3523 alvherre@alvh.no-ip. 4709 : 44 : case AT_SetLogged:
4710 : : case AT_SetUnLogged:
4711 : 44 : cmd_lockmode = AccessExclusiveLock;
4712 : 44 : break;
4713 : :
2489 tgl@sss.pgh.pa.us 4714 : 194 : case AT_ValidateConstraint: /* Uses MVCC in getConstraints() */
3661 simon@2ndQuadrant.co 4715 : 194 : cmd_lockmode = ShareUpdateExclusiveLock;
4716 : 194 : break;
4717 : :
4718 : : /*
4719 : : * Rel options are more complex than first appears. Options
4720 : : * are set here for tables, views and indexes; for historical
4721 : : * reasons these can all be used with ALTER TABLE, so we can't
4722 : : * decide between them using the basic grammar.
4723 : : */
2489 tgl@sss.pgh.pa.us 4724 : 370 : case AT_SetRelOptions: /* Uses MVCC in getIndexes() and
4725 : : * getTables() */
4726 : : case AT_ResetRelOptions: /* Uses MVCC in getIndexes() and
4727 : : * getTables() */
3166 simon@2ndQuadrant.co 4728 : 370 : cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
3661 4729 : 370 : break;
4730 : :
2685 rhaas@postgresql.org 4731 : 1295 : case AT_AttachPartition:
1865 4732 : 1295 : cmd_lockmode = ShareUpdateExclusiveLock;
4733 : 1295 : break;
4734 : :
2685 4735 : 264 : case AT_DetachPartition:
1116 alvherre@alvh.no-ip. 4736 [ + + ]: 264 : if (((PartitionCmd *) cmd->def)->concurrent)
4737 : 79 : cmd_lockmode = ShareUpdateExclusiveLock;
4738 : : else
4739 : 185 : cmd_lockmode = AccessExclusiveLock;
4740 : 264 : break;
4741 : :
4742 : 7 : case AT_DetachPartitionFinalize:
4743 : 7 : cmd_lockmode = ShareUpdateExclusiveLock;
2685 rhaas@postgresql.org 4744 : 7 : break;
4745 : :
7 akorotkov@postgresql 4746 :GNC 126 : case AT_SplitPartition:
4747 : 126 : cmd_lockmode = AccessExclusiveLock;
4748 : 126 : break;
4749 : :
4750 : 60 : case AT_MergePartitions:
4751 : 60 : cmd_lockmode = AccessExclusiveLock;
7 akorotkov@postgresql 4752 :GBC 60 : break;
4753 : :
4753 bruce@momjian.us 4754 :UBC 0 : default: /* oops */
5009 simon@2ndQuadrant.co 4755 [ # # ]: 0 : elog(ERROR, "unrecognized alter table type: %d",
4756 : : (int) cmd->subtype);
4757 : : break;
4758 : : }
4759 : :
4760 : : /*
4761 : : * Take the greatest lockmode from any subcommand
4762 : : */
5009 simon@2ndQuadrant.co 4763 [ + + ]:CBC 18224 : if (cmd_lockmode > lockmode)
4764 : 15279 : lockmode = cmd_lockmode;
4765 : : }
4766 : :
4767 : 17248 : return lockmode;
4768 : : }
4769 : :
4770 : : /*
4771 : : * ATController provides top level control over the phases.
4772 : : *
4773 : : * parsetree is passed in to allow it to be passed to event triggers
4774 : : * when requested.
4775 : : */
4776 : : static void
3415 4777 : 17128 : ATController(AlterTableStmt *parsetree,
4778 : : Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
4779 : : AlterTableUtilityContext *context)
4780 : : {
7284 tgl@sss.pgh.pa.us 4781 : 17128 : List *wqueue = NIL;
4782 : : ListCell *lcmd;
4783 : :
4784 : : /* Phase 1: preliminary examination of commands, create work queue */
4785 [ + - + + : 35093 : foreach(lcmd, cmds)
+ + ]
4786 : : {
4787 : 18101 : AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
4788 : :
1551 4789 : 18101 : ATPrepCmd(&wqueue, rel, cmd, recurse, false, lockmode, context);
4790 : : }
4791 : :
4792 : : /* Close the relation, but keep lock until commit */
7284 4793 : 16992 : relation_close(rel, NoLock);
4794 : :
4795 : : /* Phase 2: update system catalogs */
1551 4796 : 16992 : ATRewriteCatalogs(&wqueue, lockmode, context);
4797 : :
4798 : : /* Phase 3: scan/rewrite tables as needed, and run afterStmts */
4799 : 15677 : ATRewriteTables(parsetree, &wqueue, lockmode, context);
7284 4800 : 15515 : }
4801 : :
4802 : : /*
4803 : : * ATPrepCmd
4804 : : *
4805 : : * Traffic cop for ALTER TABLE Phase 1 operations, including simple
4806 : : * recursion and permission checks.
4807 : : *
4808 : : * Caller must have acquired appropriate lock type on relation already.
4809 : : * This lock should be held until commit.
4810 : : */
4811 : : static void
4812 : 18412 : ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
4813 : : bool recurse, bool recursing, LOCKMODE lockmode,
4814 : : AlterTableUtilityContext *context)
4815 : : {
4816 : : AlteredTableInfo *tab;
104 peter@eisentraut.org 4817 :GNC 18412 : AlterTablePass pass = AT_PASS_UNSET;
4818 : :
4819 : : /* Find or create work queue entry for this table */
7284 tgl@sss.pgh.pa.us 4820 :CBC 18412 : tab = ATGetQueueEntry(wqueue, rel);
4821 : :
4822 : : /*
4823 : : * Disallow any ALTER TABLE other than ALTER TABLE DETACH FINALIZE on
4824 : : * partitions that are pending detach.
4825 : : */
1116 alvherre@alvh.no-ip. 4826 [ + + ]: 18412 : if (rel->rd_rel->relispartition &&
4827 [ + - + + ]: 1284 : cmd->subtype != AT_DetachPartitionFinalize &&
4828 : 642 : PartitionHasPendingDetach(RelationGetRelid(rel)))
4829 [ + - ]: 1 : ereport(ERROR,
4830 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
4831 : : errmsg("cannot alter partition \"%s\" with an incomplete detach",
4832 : : RelationGetRelationName(rel)),
4833 : : errhint("Use ALTER TABLE ... DETACH PARTITION ... FINALIZE to complete the pending detach operation."));
4834 : :
4835 : : /*
4836 : : * Copy the original subcommand for each table, so we can scribble on it.
4837 : : * This avoids conflicts when different child tables need to make
4838 : : * different parse transformations (for example, the same column may have
4839 : : * different column numbers in different children).
4840 : : */
7284 tgl@sss.pgh.pa.us 4841 : 18411 : cmd = copyObject(cmd);
4842 : :
4843 : : /*
4844 : : * Do permissions and relkind checking, recursion to child tables if
4845 : : * needed, and any additional phase-1 processing needed. (But beware of
4846 : : * adding any processing that looks at table details that another
4847 : : * subcommand could change. In some cases we reject multiple subcommands
4848 : : * that could try to change the same state in contrary ways.)
4849 : : */
4850 [ + + + + : 18411 : switch (cmd->subtype)
+ + + + +
+ + + + +
+ + + - +
- + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
- ]
4851 : : {
4852 : 978 : case AT_AddColumn: /* ADD COLUMN */
1011 peter@eisentraut.org 4853 : 978 : ATSimplePermissions(cmd->subtype, rel,
4854 : : ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
3334 alvherre@alvh.no-ip. 4855 : 978 : ATPrepAddColumn(wqueue, rel, recurse, recursing, false, cmd,
4856 : : lockmode, context);
4857 : : /* Recursion occurs during execution phase */
7284 tgl@sss.pgh.pa.us 4858 : 972 : pass = AT_PASS_ADD_COL;
4859 : 972 : break;
2489 4860 : 12 : case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */
1011 peter@eisentraut.org 4861 : 12 : ATSimplePermissions(cmd->subtype, rel, ATT_VIEW);
3334 alvherre@alvh.no-ip. 4862 : 12 : ATPrepAddColumn(wqueue, rel, recurse, recursing, true, cmd,
4863 : : lockmode, context);
4864 : : /* Recursion occurs during execution phase */
5608 bruce@momjian.us 4865 : 12 : pass = AT_PASS_ADD_COL;
4866 : 12 : break;
7284 tgl@sss.pgh.pa.us 4867 : 295 : case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */
4868 : :
4869 : : /*
4870 : : * We allow defaults on views so that INSERT into a view can have
4871 : : * default-ish behavior. This works because the rewriter
4872 : : * substitutes default values into INSERTs before it expands
4873 : : * rules.
4874 : : */
1011 peter@eisentraut.org 4875 : 295 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
1551 tgl@sss.pgh.pa.us 4876 : 295 : ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
4877 : : /* No command-specific prep needed */
4878 [ + + ]: 295 : pass = cmd->def ? AT_PASS_ADD_OTHERCONSTR : AT_PASS_DROP;
7284 4879 : 295 : break;
1332 4880 : 55 : case AT_CookedColumnDefault: /* add a pre-cooked default */
4881 : : /* This is currently used only in CREATE TABLE */
4882 : : /* (so the permission check really isn't necessary) */
1011 peter@eisentraut.org 4883 : 55 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
4884 : : /* This command never recurses */
1332 tgl@sss.pgh.pa.us 4885 : 55 : pass = AT_PASS_ADD_OTHERCONSTR;
4886 : 55 : break;
2565 peter_e@gmx.net 4887 : 77 : case AT_AddIdentity:
1011 peter@eisentraut.org 4888 : 77 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
4889 : : /* Set up recursion for phase 2; no other prep needed */
89 peter@eisentraut.org 4890 [ + + ]:GNC 77 : if (recurse)
4891 : 74 : cmd->recurse = true;
1551 tgl@sss.pgh.pa.us 4892 :CBC 77 : pass = AT_PASS_ADD_OTHERCONSTR;
2565 peter_e@gmx.net 4893 : 77 : break;
4894 : 31 : case AT_SetIdentity:
1011 peter@eisentraut.org 4895 : 31 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
4896 : : /* Set up recursion for phase 2; no other prep needed */
89 peter@eisentraut.org 4897 [ + + ]:GNC 31 : if (recurse)
4898 : 28 : cmd->recurse = true;
4899 : : /* This should run after AddIdentity, so do it in MISC pass */
1551 tgl@sss.pgh.pa.us 4900 :CBC 31 : pass = AT_PASS_MISC;
2565 peter_e@gmx.net 4901 : 31 : break;
1727 alvherre@alvh.no-ip. 4902 : 28 : case AT_DropIdentity:
1011 peter@eisentraut.org 4903 : 28 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
4904 : : /* Set up recursion for phase 2; no other prep needed */
89 peter@eisentraut.org 4905 [ + + ]:GNC 28 : if (recurse)
4906 : 25 : cmd->recurse = true;
1727 alvherre@alvh.no-ip. 4907 :CBC 28 : pass = AT_PASS_DROP;
4908 : 28 : break;
7284 tgl@sss.pgh.pa.us 4909 : 122 : case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
1011 peter@eisentraut.org 4910 : 122 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
4911 : : /* Set up recursion for phase 2; no other prep needed */
233 alvherre@alvh.no-ip. 4912 [ + + ]:GNC 119 : if (recurse)
4913 : 113 : cmd->recurse = true;
7284 tgl@sss.pgh.pa.us 4914 :CBC 119 : pass = AT_PASS_DROP;
4915 : 119 : break;
4916 : 183 : case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
1011 peter@eisentraut.org 4917 : 183 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
4918 : : /* Set up recursion for phase 2; no other prep needed */
233 alvherre@alvh.no-ip. 4919 [ + + ]:GNC 180 : if (recurse)
4920 : 165 : cmd->recurse = true;
1818 tgl@sss.pgh.pa.us 4921 :CBC 180 : pass = AT_PASS_COL_ATTRS;
4922 : 180 : break;
233 alvherre@alvh.no-ip. 4923 :GNC 3629 : case AT_SetAttNotNull: /* set pg_attribute.attnotnull without adding
4924 : : * a constraint */
1011 peter@eisentraut.org 4925 : 3629 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
4926 : : /* Need command-specific recursion decision */
1551 tgl@sss.pgh.pa.us 4927 : 3629 : ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
1818 4928 : 3629 : pass = AT_PASS_COL_ATTRS;
7284 4929 : 3629 : break;
101 peter@eisentraut.org 4930 : 42 : case AT_SetExpression: /* ALTER COLUMN SET EXPRESSION */
101 peter@eisentraut.org 4931 :CBC 42 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
4932 : 42 : ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
101 peter@eisentraut.org 4933 :GNC 42 : pass = AT_PASS_SET_EXPRESSION;
101 peter@eisentraut.org 4934 :CBC 42 : break;
1551 tgl@sss.pgh.pa.us 4935 : 22 : case AT_DropExpression: /* ALTER COLUMN DROP EXPRESSION */
1011 peter@eisentraut.org 4936 : 22 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
1551 tgl@sss.pgh.pa.us 4937 : 22 : ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
1258 peter@eisentraut.org 4938 : 22 : ATPrepDropExpression(rel, cmd, recurse, recursing, lockmode);
1552 4939 : 16 : pass = AT_PASS_DROP;
4940 : 16 : break;
5369 tgl@sss.pgh.pa.us 4941 : 82 : case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */
1011 peter@eisentraut.org 4942 : 82 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_PARTITIONED_INDEX | ATT_FOREIGN_TABLE);
1551 tgl@sss.pgh.pa.us 4943 : 82 : ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
4944 : : /* No command-specific prep needed */
5008 simon@2ndQuadrant.co 4945 : 82 : pass = AT_PASS_MISC;
7284 tgl@sss.pgh.pa.us 4946 : 82 : break;
5196 rhaas@postgresql.org 4947 : 22 : case AT_SetOptions: /* ALTER COLUMN SET ( options ) */
4948 : : case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */
908 michael@paquier.xyz 4949 : 22 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE);
4950 : : /* This command never recurses */
5008 simon@2ndQuadrant.co 4951 : 16 : pass = AT_PASS_MISC;
5369 tgl@sss.pgh.pa.us 4952 : 16 : break;
4953 : 117 : case AT_SetStorage: /* ALTER COLUMN SET STORAGE */
1011 peter@eisentraut.org 4954 : 117 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE);
1551 tgl@sss.pgh.pa.us 4955 : 117 : ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
4956 : : /* No command-specific prep needed */
5008 simon@2ndQuadrant.co 4957 : 117 : pass = AT_PASS_MISC;
7284 tgl@sss.pgh.pa.us 4958 : 117 : break;
1068 4959 : 33 : case AT_SetCompression: /* ALTER COLUMN SET COMPRESSION */
1011 peter@eisentraut.org 4960 : 33 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_MATVIEW);
4961 : : /* This command never recurses */
4962 : : /* No command-specific prep needed */
1122 rhaas@postgresql.org 4963 : 33 : pass = AT_PASS_MISC;
4964 : 33 : break;
7284 tgl@sss.pgh.pa.us 4965 : 808 : case AT_DropColumn: /* DROP COLUMN */
1011 peter@eisentraut.org 4966 : 808 : ATSimplePermissions(cmd->subtype, rel,
4967 : : ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
1551 tgl@sss.pgh.pa.us 4968 : 805 : ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd,
4969 : : lockmode, context);
4970 : : /* Recursion occurs during execution phase */
7284 4971 : 799 : pass = AT_PASS_DROP;
4972 : 799 : break;
7284 tgl@sss.pgh.pa.us 4973 :UBC 0 : case AT_AddIndex: /* ADD INDEX */
1011 peter@eisentraut.org 4974 : 0 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE);
4975 : : /* This command never recurses */
4976 : : /* No command-specific prep needed */
7284 tgl@sss.pgh.pa.us 4977 : 0 : pass = AT_PASS_ADD_INDEX;
4978 : 0 : break;
7284 tgl@sss.pgh.pa.us 4979 :CBC 6310 : case AT_AddConstraint: /* ADD CONSTRAINT */
1011 peter@eisentraut.org 4980 : 6310 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
4981 : : /* Recursion occurs during execution phase */
4982 : : /* No command-specific prep needed except saving recurse flag */
5819 tgl@sss.pgh.pa.us 4983 [ + + ]: 6310 : if (recurse)
489 alvherre@alvh.no-ip. 4984 : 6144 : cmd->recurse = true;
7284 tgl@sss.pgh.pa.us 4985 : 6310 : pass = AT_PASS_ADD_CONSTR;
4986 : 6310 : break;
2489 tgl@sss.pgh.pa.us 4987 :UBC 0 : case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */
1011 peter@eisentraut.org 4988 : 0 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE);
4989 : : /* This command never recurses */
4990 : : /* No command-specific prep needed */
1551 tgl@sss.pgh.pa.us 4991 : 0 : pass = AT_PASS_ADD_INDEXCONSTR;
4828 4992 : 0 : break;
2489 tgl@sss.pgh.pa.us 4993 :CBC 473 : case AT_DropConstraint: /* DROP CONSTRAINT */
1011 peter@eisentraut.org 4994 : 473 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
1727 alvherre@alvh.no-ip. 4995 : 473 : ATCheckPartitionsNotInUse(rel, lockmode);
4996 : : /* Other recursion occurs during execution phase */
4997 : : /* No command-specific prep needed except saving recurse flag */
5819 tgl@sss.pgh.pa.us 4998 [ + + ]: 470 : if (recurse)
489 alvherre@alvh.no-ip. 4999 : 354 : cmd->recurse = true;
7284 tgl@sss.pgh.pa.us 5000 : 470 : pass = AT_PASS_DROP;
5001 : 470 : break;
2489 5002 : 562 : case AT_AlterColumnType: /* ALTER COLUMN TYPE */
1011 peter@eisentraut.org 5003 : 562 : ATSimplePermissions(cmd->subtype, rel,
5004 : : ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
5005 : : /* See comments for ATPrepAlterColumnType */
1551 tgl@sss.pgh.pa.us 5006 : 562 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, recurse, lockmode,
5007 : : AT_PASS_UNSET, context);
5008 [ - + ]: 559 : Assert(cmd != NULL);
5009 : : /* Performs own recursion */
5010 : 559 : ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd,
5011 : : lockmode, context);
7284 5012 : 487 : pass = AT_PASS_ALTER_TYPE;
5013 : 487 : break;
4636 rhaas@postgresql.org 5014 : 82 : case AT_AlterColumnGenericOptions:
1011 peter@eisentraut.org 5015 : 82 : ATSimplePermissions(cmd->subtype, rel, ATT_FOREIGN_TABLE);
5016 : : /* This command never recurses */
5017 : : /* No command-specific prep needed */
4636 rhaas@postgresql.org 5018 : 82 : pass = AT_PASS_MISC;
5019 : 82 : break;
7284 tgl@sss.pgh.pa.us 5020 : 898 : case AT_ChangeOwner: /* ALTER OWNER */
5021 : : /* This command never recurses */
5022 : : /* No command-specific prep needed */
5023 : 898 : pass = AT_PASS_MISC;
5024 : 898 : break;
7168 bruce@momjian.us 5025 : 32 : case AT_ClusterOn: /* CLUSTER ON */
5026 : : case AT_DropCluster: /* SET WITHOUT CLUSTER */
1011 peter@eisentraut.org 5027 : 32 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_MATVIEW);
5028 : : /* These commands never recurse */
5029 : : /* No command-specific prep needed */
7284 tgl@sss.pgh.pa.us 5030 : 32 : pass = AT_PASS_MISC;
5031 : 32 : break;
3523 alvherre@alvh.no-ip. 5032 : 19 : case AT_SetLogged: /* SET LOGGED */
738 peter@eisentraut.org 5033 : 19 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_SEQUENCE);
1551 tgl@sss.pgh.pa.us 5034 [ - + ]: 19 : if (tab->chgPersistence)
1551 tgl@sss.pgh.pa.us 5035 [ # # ]:UBC 0 : ereport(ERROR,
5036 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5037 : : errmsg("cannot change persistence setting twice")));
3520 alvherre@alvh.no-ip. 5038 :CBC 19 : tab->chgPersistence = ATPrepChangePersistence(rel, true);
5039 : : /* force rewrite if necessary; see comment in ATRewriteTables */
5040 [ + + ]: 16 : if (tab->chgPersistence)
5041 : : {
3415 simon@2ndQuadrant.co 5042 : 13 : tab->rewrite |= AT_REWRITE_ALTER_PERSISTENCE;
3520 alvherre@alvh.no-ip. 5043 : 13 : tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
5044 : : }
3523 5045 : 16 : pass = AT_PASS_MISC;
5046 : 16 : break;
5047 : 25 : case AT_SetUnLogged: /* SET UNLOGGED */
738 peter@eisentraut.org 5048 : 25 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_SEQUENCE);
1551 tgl@sss.pgh.pa.us 5049 [ - + ]: 25 : if (tab->chgPersistence)
1551 tgl@sss.pgh.pa.us 5050 [ # # ]:UBC 0 : ereport(ERROR,
5051 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5052 : : errmsg("cannot change persistence setting twice")));
3520 alvherre@alvh.no-ip. 5053 :CBC 25 : tab->chgPersistence = ATPrepChangePersistence(rel, false);
5054 : : /* force rewrite if necessary; see comment in ATRewriteTables */
5055 [ + + ]: 22 : if (tab->chgPersistence)
5056 : : {
3415 simon@2ndQuadrant.co 5057 : 19 : tab->rewrite |= AT_REWRITE_ALTER_PERSISTENCE;
3520 alvherre@alvh.no-ip. 5058 : 19 : tab->newrelpersistence = RELPERSISTENCE_UNLOGGED;
5059 : : }
3523 5060 : 22 : pass = AT_PASS_MISC;
5061 : 22 : break;
7168 bruce@momjian.us 5062 : 3 : case AT_DropOids: /* SET WITHOUT OIDS */
1011 peter@eisentraut.org 5063 : 3 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
7284 tgl@sss.pgh.pa.us 5064 : 3 : pass = AT_PASS_DROP;
5065 : 3 : break;
991 michael@paquier.xyz 5066 : 64 : case AT_SetAccessMethod: /* SET ACCESS METHOD */
5067 : 64 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_MATVIEW);
5068 : :
5069 : : /* check if another access method change was already requested */
20 alvherre@alvh.no-ip. 5070 [ + + ]:GNC 64 : if (tab->chgAccessMethod)
991 michael@paquier.xyz 5071 [ + - ]:CBC 9 : ereport(ERROR,
5072 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5073 : : errmsg("cannot have multiple SET ACCESS METHOD subcommands")));
5074 : :
5075 : 55 : ATPrepSetAccessMethod(tab, rel, cmd->name);
5076 : 55 : pass = AT_PASS_MISC; /* does not matter; no work in Phase 2 */
5077 : 55 : break;
7217 tgl@sss.pgh.pa.us 5078 : 79 : case AT_SetTableSpace: /* SET TABLESPACE */
1011 peter@eisentraut.org 5079 : 79 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX |
5080 : : ATT_PARTITIONED_INDEX);
5081 : : /* This command never recurses */
5009 simon@2ndQuadrant.co 5082 : 79 : ATPrepSetTableSpace(tab, rel, cmd->name, lockmode);
7168 bruce@momjian.us 5083 : 79 : pass = AT_PASS_MISC; /* doesn't actually matter */
7217 tgl@sss.pgh.pa.us 5084 : 79 : break;
6402 bruce@momjian.us 5085 : 467 : case AT_SetRelOptions: /* SET (...) */
5086 : : case AT_ResetRelOptions: /* RESET (...) */
5087 : : case AT_ReplaceRelOptions: /* reset them all, then set just these */
1011 peter@eisentraut.org 5088 : 467 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_VIEW | ATT_MATVIEW | ATT_INDEX);
5089 : : /* This command never recurses */
5090 : : /* No command-specific prep needed */
6496 bruce@momjian.us 5091 : 467 : pass = AT_PASS_MISC;
5092 : 467 : break;
5014 peter_e@gmx.net 5093 : 163 : case AT_AddInherit: /* INHERIT */
1011 peter@eisentraut.org 5094 : 163 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
5095 : : /* This command never recurses */
5014 peter_e@gmx.net 5096 : 163 : ATPrepAddInherit(rel);
5097 : 154 : pass = AT_PASS_MISC;
5098 : 154 : break;
3311 tgl@sss.pgh.pa.us 5099 : 22 : case AT_DropInherit: /* NO INHERIT */
1011 peter@eisentraut.org 5100 : 22 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
5101 : : /* This command never recurses */
5102 : : /* No command-specific prep needed */
3311 tgl@sss.pgh.pa.us 5103 : 22 : pass = AT_PASS_MISC;
5104 : 22 : break;
2489 5105 : 66 : case AT_AlterConstraint: /* ALTER CONSTRAINT */
1011 peter@eisentraut.org 5106 : 66 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE);
5107 : : /* Recursion occurs during execution phase */
3942 simon@2ndQuadrant.co 5108 : 63 : pass = AT_PASS_MISC;
5109 : 63 : break;
2489 tgl@sss.pgh.pa.us 5110 : 194 : case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
1011 peter@eisentraut.org 5111 : 194 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
5112 : : /* Recursion occurs during execution phase */
5113 : : /* No command-specific prep needed except saving recurse flag */
4701 alvherre@alvh.no-ip. 5114 [ + - ]: 194 : if (recurse)
489 5115 : 194 : cmd->recurse = true;
4701 5116 : 194 : pass = AT_PASS_MISC;
5117 : 194 : break;
2489 tgl@sss.pgh.pa.us 5118 : 215 : case AT_ReplicaIdentity: /* REPLICA IDENTITY ... */
1011 peter@eisentraut.org 5119 : 215 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_MATVIEW);
3810 rhaas@postgresql.org 5120 : 215 : pass = AT_PASS_MISC;
5121 : : /* This command never recurses */
5122 : : /* No command-specific prep needed */
5123 : 215 : break;
6809 tgl@sss.pgh.pa.us 5124 : 170 : case AT_EnableTrig: /* ENABLE TRIGGER variants */
5125 : : case AT_EnableAlwaysTrig:
5126 : : case AT_EnableReplicaTrig:
5127 : : case AT_EnableTrigAll:
5128 : : case AT_EnableTrigUser:
5129 : : case AT_DisableTrig: /* DISABLE TRIGGER variants */
5130 : : case AT_DisableTrigAll:
5131 : : case AT_DisableTrigUser:
1011 peter@eisentraut.org 5132 : 170 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
5133 : : /* Set up recursion for phase 2; no other prep needed */
619 alvherre@alvh.no-ip. 5134 [ + + ]: 170 : if (recurse)
5135 : 156 : cmd->recurse = true;
3675 noah@leadboat.com 5136 : 170 : pass = AT_PASS_MISC;
5137 : 170 : break;
6236 JanWieck@Yahoo.com 5138 : 260 : case AT_EnableRule: /* ENABLE/DISABLE RULE variants */
5139 : : case AT_EnableAlwaysRule:
5140 : : case AT_EnableReplicaRule:
5141 : : case AT_DisableRule:
5142 : : case AT_AddOf: /* OF */
5143 : : case AT_DropOf: /* NOT OF */
5144 : : case AT_EnableRowSecurity:
5145 : : case AT_DisableRowSecurity:
5146 : : case AT_ForceRowSecurity:
5147 : : case AT_NoForceRowSecurity:
1011 peter@eisentraut.org 5148 : 260 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE);
5149 : : /* These commands never recurse */
5150 : : /* No command-specific prep needed */
4852 rhaas@postgresql.org 5151 : 260 : pass = AT_PASS_MISC;
5152 : 260 : break;
5153 : 25 : case AT_GenericOptions:
1011 peter@eisentraut.org 5154 : 25 : ATSimplePermissions(cmd->subtype, rel, ATT_FOREIGN_TABLE);
5155 : : /* No command-specific prep needed */
4852 rhaas@postgresql.org 5156 : 25 : pass = AT_PASS_MISC;
5157 : 25 : break;
2685 5158 : 1289 : case AT_AttachPartition:
1011 peter@eisentraut.org 5159 : 1289 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_PARTITIONED_INDEX);
5160 : : /* No command-specific prep needed */
2277 alvherre@alvh.no-ip. 5161 : 1289 : pass = AT_PASS_MISC;
5162 : 1289 : break;
2685 rhaas@postgresql.org 5163 : 264 : case AT_DetachPartition:
1011 peter@eisentraut.org 5164 : 264 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE);
5165 : : /* No command-specific prep needed */
2685 rhaas@postgresql.org 5166 : 261 : pass = AT_PASS_MISC;
5167 : 261 : break;
1116 alvherre@alvh.no-ip. 5168 : 7 : case AT_DetachPartitionFinalize:
1011 peter@eisentraut.org 5169 : 7 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE);
5170 : : /* No command-specific prep needed */
1116 alvherre@alvh.no-ip. 5171 : 7 : pass = AT_PASS_MISC;
5172 : 7 : break;
7 akorotkov@postgresql 5173 :GNC 126 : case AT_SplitPartition:
5174 : 126 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE);
5175 : : /* No command-specific prep needed */
5176 : 126 : pass = AT_PASS_MISC;
5177 : 126 : break;
5178 : 60 : case AT_MergePartitions:
5179 : 60 : ATSimplePermissions(cmd->subtype, rel, ATT_TABLE);
5180 : : /* No command-specific prep needed */
5181 : 60 : pass = AT_PASS_MISC;
5182 : 60 : break;
7168 bruce@momjian.us 5183 :UBC 0 : default: /* oops */
7284 tgl@sss.pgh.pa.us 5184 [ # # ]: 0 : elog(ERROR, "unrecognized alter table type: %d",
5185 : : (int) cmd->subtype);
5186 : : pass = AT_PASS_UNSET; /* keep compiler quiet */
5187 : : break;
5188 : : }
3942 simon@2ndQuadrant.co 5189 [ - + ]:CBC 18270 : Assert(pass > AT_PASS_UNSET);
5190 : :
5191 : : /* Add the subcommand to the appropriate list for phase 2 */
7284 tgl@sss.pgh.pa.us 5192 : 18270 : tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd);
5193 : 18270 : }
5194 : :
5195 : : /*
5196 : : * ATRewriteCatalogs
5197 : : *
5198 : : * Traffic cop for ALTER TABLE Phase 2 operations. Subcommands are
5199 : : * dispatched in a "safe" execution order (designed to avoid unnecessary
5200 : : * conflicts).
5201 : : */
5202 : : static void
1551 5203 : 16992 : ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
5204 : : AlterTableUtilityContext *context)
5205 : : {
5206 : : ListCell *ltab;
5207 : :
5208 : : /*
5209 : : * We process all the tables "in parallel", one pass at a time. This is
5210 : : * needed because we may have to propagate work from one table to another
5211 : : * (specifically, ALTER TYPE on a foreign key's PK has to dispatch the
5212 : : * re-adding of the foreign key constraint to the other table). Work can
5213 : : * only be propagated into later passes, however.
5214 : : */
104 peter@eisentraut.org 5215 [ + + ]:GNC 215300 : for (AlterTablePass pass = 0; pass < AT_NUM_PASSES; pass++)
5216 : : {
5217 : : /* Go through each table that needs to be processed */
7284 tgl@sss.pgh.pa.us 5218 [ + - + + :CBC 404813 : foreach(ltab, *wqueue)
+ + ]
5219 : : {
5220 : 206505 : AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
5221 : 206505 : List *subcmds = tab->subcmds[pass];
5222 : : ListCell *lcmd;
5223 : :
5224 [ + + ]: 206505 : if (subcmds == NIL)
8024 5225 : 179758 : continue;
5226 : :
5227 : : /*
5228 : : * Open the relation and store it in tab. This allows subroutines
5229 : : * close and reopen, if necessary. Appropriate lock was obtained
5230 : : * by phase 1, needn't get it again.
5231 : : */
1116 alvherre@alvh.no-ip. 5232 : 26747 : tab->rel = relation_open(tab->relid, NoLock);
5233 : :
7284 tgl@sss.pgh.pa.us 5234 [ + - + + : 53954 : foreach(lcmd, subcmds)
+ + ]
1116 alvherre@alvh.no-ip. 5235 : 28522 : ATExecCmd(wqueue, tab,
1000 peter@eisentraut.org 5236 : 28522 : lfirst_node(AlterTableCmd, lcmd),
5237 : : lockmode, pass, context);
5238 : :
5239 : : /*
5240 : : * After the ALTER TYPE or SET EXPRESSION pass, do cleanup work
5241 : : * (this is not done in ATExecAlterColumnType since it should be
5242 : : * done only once if multiple columns of a table are altered).
5243 : : */
101 peter@eisentraut.org 5244 [ + + + + ]:GNC 25432 : if (pass == AT_PASS_ALTER_TYPE || pass == AT_PASS_SET_EXPRESSION)
5009 simon@2ndQuadrant.co 5245 :CBC 478 : ATPostAlterTypeCleanup(wqueue, tab, lockmode);
5246 : :
1116 alvherre@alvh.no-ip. 5247 [ + - ]: 25432 : if (tab->rel)
5248 : : {
5249 : 25432 : relation_close(tab->rel, NoLock);
5250 : 25432 : tab->rel = NULL;
5251 : : }
5252 : : }
5253 : : }
5254 : :
5255 : : /* Check to see if a toast table must be added. */
7284 tgl@sss.pgh.pa.us 5256 [ + - + + : 33206 : foreach(ltab, *wqueue)
+ + ]
5257 : : {
5258 : 17529 : AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
5259 : :
5260 : : /*
5261 : : * If the table is source table of ATTACH PARTITION command, we did
5262 : : * not modify anything about it that will change its toasting
5263 : : * requirement, so no need to check.
5264 : : */
2685 rhaas@postgresql.org 5265 [ + + ]: 17529 : if (((tab->relkind == RELKIND_RELATION ||
5266 [ + + ]: 3200 : tab->relkind == RELKIND_PARTITIONED_TABLE) &&
2576 tgl@sss.pgh.pa.us 5267 [ + + ]: 16616 : tab->partition_constraint == NULL) ||
4060 kgrittn@postgresql.o 5268 [ + + ]: 1873 : tab->relkind == RELKIND_MATVIEW)
3661 simon@2ndQuadrant.co 5269 : 15681 : AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
5270 : : }
7284 tgl@sss.pgh.pa.us 5271 : 15677 : }
5272 : :
5273 : : /*
5274 : : * ATExecCmd: dispatch a subcommand to appropriate execution routine
5275 : : */
5276 : : static void
1116 alvherre@alvh.no-ip. 5277 : 28522 : ATExecCmd(List **wqueue, AlteredTableInfo *tab,
5278 : : AlterTableCmd *cmd, LOCKMODE lockmode, AlterTablePass cur_pass,
5279 : : AlterTableUtilityContext *context)
5280 : : {
3308 5281 : 28522 : ObjectAddress address = InvalidObjectAddress;
1116 5282 : 28522 : Relation rel = tab->rel;
5283 : :
7284 tgl@sss.pgh.pa.us 5284 [ + + + + : 28522 : switch (cmd->subtype)
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + - +
- + + - +
+ + + + +
+ + + + +
+ + + + +
+ - ]
5285 : : {
5286 : 981 : case AT_AddColumn: /* ADD COLUMN */
5287 : : case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */
1551 5288 : 981 : address = ATExecAddColumn(wqueue, tab, rel, &cmd,
489 alvherre@alvh.no-ip. 5289 : 981 : cmd->recurse, false,
5290 : : lockmode, cur_pass, context);
7284 tgl@sss.pgh.pa.us 5291 : 924 : break;
5292 : 283 : case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */
3308 alvherre@alvh.no-ip. 5293 : 283 : address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
7284 tgl@sss.pgh.pa.us 5294 : 253 : break;
1332 5295 : 55 : case AT_CookedColumnDefault: /* add a pre-cooked default */
5296 : 55 : address = ATExecCookedColumnDefault(rel, cmd->num, cmd->def);
5297 : 55 : break;
2565 peter_e@gmx.net 5298 : 74 : case AT_AddIdentity:
1551 tgl@sss.pgh.pa.us 5299 : 74 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5300 : : cur_pass, context);
5301 [ - + ]: 71 : Assert(cmd != NULL);
89 peter@eisentraut.org 5302 :GNC 71 : address = ATExecAddIdentity(rel, cmd->name, cmd->def, lockmode, cmd->recurse, false);
2565 peter_e@gmx.net 5303 :CBC 50 : break;
5304 : 31 : case AT_SetIdentity:
1551 tgl@sss.pgh.pa.us 5305 : 31 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5306 : : cur_pass, context);
5307 [ - + ]: 31 : Assert(cmd != NULL);
89 peter@eisentraut.org 5308 :GNC 31 : address = ATExecSetIdentity(rel, cmd->name, cmd->def, lockmode, cmd->recurse, false);
2565 peter_e@gmx.net 5309 :CBC 19 : break;
5310 : 28 : case AT_DropIdentity:
89 peter@eisentraut.org 5311 :GNC 28 : address = ATExecDropIdentity(rel, cmd->name, cmd->missing_ok, lockmode, cmd->recurse, false);
2565 peter_e@gmx.net 5312 :CBC 19 : break;
7284 tgl@sss.pgh.pa.us 5313 : 119 : case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
233 alvherre@alvh.no-ip. 5314 :GNC 119 : address = ATExecDropNotNull(rel, cmd->name, cmd->recurse, lockmode);
7284 tgl@sss.pgh.pa.us 5315 :CBC 65 : break;
5316 : 180 : case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
233 alvherre@alvh.no-ip. 5317 :GNC 180 : address = ATExecSetNotNull(wqueue, rel, NULL, cmd->name,
5318 : 180 : cmd->recurse, false, NULL, lockmode);
7284 tgl@sss.pgh.pa.us 5319 :CBC 165 : break;
233 alvherre@alvh.no-ip. 5320 :GNC 7120 : case AT_SetAttNotNull: /* set pg_attribute.attnotnull */
5321 : 7120 : address = ATExecSetAttNotNull(wqueue, rel, cmd->name, lockmode);
1818 tgl@sss.pgh.pa.us 5322 : 7111 : break;
101 peter@eisentraut.org 5323 : 42 : case AT_SetExpression:
5324 : 42 : address = ATExecSetExpression(tab, rel, cmd->name, cmd->def, lockmode);
101 peter@eisentraut.org 5325 :CBC 39 : break;
1552 5326 : 16 : case AT_DropExpression:
5327 : 16 : address = ATExecDropExpression(rel, cmd->name, cmd->missing_ok, lockmode);
5328 : 13 : break;
5369 tgl@sss.pgh.pa.us 5329 : 82 : case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */
2412 simon@2ndQuadrant.co 5330 : 82 : address = ATExecSetStatistics(rel, cmd->name, cmd->num, cmd->def, lockmode);
7284 tgl@sss.pgh.pa.us 5331 : 58 : break;
5196 rhaas@postgresql.org 5332 : 13 : case AT_SetOptions: /* ALTER COLUMN SET ( options ) */
3308 alvherre@alvh.no-ip. 5333 : 13 : address = ATExecSetOptions(rel, cmd->name, cmd->def, false, lockmode);
5196 rhaas@postgresql.org 5334 : 13 : break;
5335 : 3 : case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */
3308 alvherre@alvh.no-ip. 5336 : 3 : address = ATExecSetOptions(rel, cmd->name, cmd->def, true, lockmode);
5369 tgl@sss.pgh.pa.us 5337 : 3 : break;
5338 : 117 : case AT_SetStorage: /* ALTER COLUMN SET STORAGE */
3308 alvherre@alvh.no-ip. 5339 : 117 : address = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
7284 tgl@sss.pgh.pa.us 5340 : 111 : break;
641 peter@eisentraut.org 5341 : 33 : case AT_SetCompression: /* ALTER COLUMN SET COMPRESSION */
5342 : 33 : address = ATExecSetCompression(rel, cmd->name, cmd->def,
5343 : : lockmode);
1122 rhaas@postgresql.org 5344 : 30 : break;
7284 tgl@sss.pgh.pa.us 5345 : 799 : case AT_DropColumn: /* DROP COLUMN */
3308 alvherre@alvh.no-ip. 5346 : 799 : address = ATExecDropColumn(wqueue, rel, cmd->name,
489 5347 : 799 : cmd->behavior, cmd->recurse, false,
1645 michael@paquier.xyz 5348 : 799 : cmd->missing_ok, lockmode,
5349 : : NULL);
7284 tgl@sss.pgh.pa.us 5350 : 712 : break;
5351 : 500 : case AT_AddIndex: /* ADD INDEX */
3308 alvherre@alvh.no-ip. 5352 : 500 : address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
5353 : : lockmode);
7284 tgl@sss.pgh.pa.us 5354 : 436 : break;
5355 : 217 : case AT_ReAddIndex: /* ADD INDEX */
3308 alvherre@alvh.no-ip. 5356 : 217 : address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true,
5357 : : lockmode);
7284 tgl@sss.pgh.pa.us 5358 : 217 : break;
1115 tomas.vondra@postgre 5359 : 7 : case AT_ReAddStatistics: /* ADD STATISTICS */
5360 : 7 : address = ATExecAddStatistics(tab, rel, (CreateStatsStmt *) cmd->def,
5361 : : true, lockmode);
5362 : 7 : break;
7284 tgl@sss.pgh.pa.us 5363 : 8136 : case AT_AddConstraint: /* ADD CONSTRAINT */
5364 : : /* Transform the command only during initial examination */
1331 5365 [ + + ]: 8136 : if (cur_pass == AT_PASS_ADD_CONSTR)
5366 : 6295 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd,
489 alvherre@alvh.no-ip. 5367 : 6310 : cmd->recurse, lockmode,
5368 : : cur_pass, context);
5369 : : /* Depending on constraint type, might be no more work to do now */
1551 tgl@sss.pgh.pa.us 5370 [ + + ]: 8121 : if (cmd != NULL)
5371 : : address =
5372 : 1826 : ATExecAddConstraint(wqueue, tab, rel,
5373 : 1826 : (Constraint *) cmd->def,
489 alvherre@alvh.no-ip. 5374 : 1826 : cmd->recurse, false, lockmode);
4178 tgl@sss.pgh.pa.us 5375 : 7844 : break;
2489 5376 : 94 : case AT_ReAddConstraint: /* Re-add pre-existing check constraint */
5377 : : address =
3308 alvherre@alvh.no-ip. 5378 : 94 : ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
5379 : : true, true, lockmode);
7284 tgl@sss.pgh.pa.us 5380 : 88 : break;
2356 5381 : 7 : case AT_ReAddDomainConstraint: /* Re-add pre-existing domain check
5382 : : * constraint */
5383 : : address =
5384 : 7 : AlterDomainAddConstraint(((AlterDomainStmt *) cmd->def)->typeName,
5385 : 7 : ((AlterDomainStmt *) cmd->def)->def,
5386 : : NULL);
5387 : 4 : break;
3197 heikki.linnakangas@i 5388 : 27 : case AT_ReAddComment: /* Re-add existing comment */
5389 : 27 : address = CommentObject((CommentStmt *) cmd->def);
5390 : 27 : break;
2489 tgl@sss.pgh.pa.us 5391 : 4107 : case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */
3308 alvherre@alvh.no-ip. 5392 : 4107 : address = ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def,
5393 : : lockmode);
4828 tgl@sss.pgh.pa.us 5394 : 4101 : break;
2489 5395 : 63 : case AT_AlterConstraint: /* ALTER CONSTRAINT */
3308 alvherre@alvh.no-ip. 5396 : 63 : address = ATExecAlterConstraint(rel, cmd, false, false, lockmode);
3942 simon@2ndQuadrant.co 5397 : 57 : break;
2489 tgl@sss.pgh.pa.us 5398 : 194 : case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
489 alvherre@alvh.no-ip. 5399 : 194 : address = ATExecValidateConstraint(wqueue, rel, cmd->name, cmd->recurse,
5400 : : false, lockmode);
4814 simon@2ndQuadrant.co 5401 : 194 : break;
2489 tgl@sss.pgh.pa.us 5402 : 470 : case AT_DropConstraint: /* DROP CONSTRAINT */
5382 andrew@dunslane.net 5403 : 470 : ATExecDropConstraint(rel, cmd->name, cmd->behavior,
220 alvherre@alvh.no-ip. 5404 :GNC 470 : cmd->recurse,
5009 simon@2ndQuadrant.co 5405 :CBC 470 : cmd->missing_ok, lockmode);
7284 tgl@sss.pgh.pa.us 5406 : 371 : break;
2489 5407 : 472 : case AT_AlterColumnType: /* ALTER COLUMN TYPE */
5408 : : /* parse transformation was done earlier */
3308 alvherre@alvh.no-ip. 5409 : 472 : address = ATExecAlterColumnType(tab, rel, cmd, lockmode);
7284 tgl@sss.pgh.pa.us 5410 : 454 : break;
2489 5411 : 82 : case AT_AlterColumnGenericOptions: /* ALTER COLUMN OPTIONS */
5412 : : address =
3308 alvherre@alvh.no-ip. 5413 : 82 : ATExecAlterColumnGenericOptions(rel, cmd->name,
5414 : 82 : (List *) cmd->def, lockmode);
4636 rhaas@postgresql.org 5415 : 79 : break;
7284 tgl@sss.pgh.pa.us 5416 : 898 : case AT_ChangeOwner: /* ALTER OWNER */
6865 5417 : 895 : ATExecChangeOwner(RelationGetRelid(rel),
3324 alvherre@alvh.no-ip. 5418 : 898 : get_rolespec_oid(cmd->newowner, false),
5419 : : false, lockmode);
7284 tgl@sss.pgh.pa.us 5420 : 889 : break;
5421 : 32 : case AT_ClusterOn: /* CLUSTER ON */
3308 alvherre@alvh.no-ip. 5422 : 32 : address = ATExecClusterOn(rel, cmd->name, lockmode);
7284 tgl@sss.pgh.pa.us 5423 : 29 : break;
7168 bruce@momjian.us 5424 : 9 : case AT_DropCluster: /* SET WITHOUT CLUSTER */
5009 simon@2ndQuadrant.co 5425 : 9 : ATExecDropCluster(rel, lockmode);
7256 bruce@momjian.us 5426 : 6 : break;
3523 alvherre@alvh.no-ip. 5427 : 38 : case AT_SetLogged: /* SET LOGGED */
5428 : : case AT_SetUnLogged: /* SET UNLOGGED */
5429 : 38 : break;
7284 tgl@sss.pgh.pa.us 5430 : 3 : case AT_DropOids: /* SET WITHOUT OIDS */
5431 : : /* nothing to do here, oid columns don't exist anymore */
5432 : 3 : break;
991 michael@paquier.xyz 5433 : 46 : case AT_SetAccessMethod: /* SET ACCESS METHOD */
5434 : :
5435 : : /*
5436 : : * Only do this for partitioned tables, for which this is just a
5437 : : * catalog change. Tables with storage are handled by Phase 3.
5438 : : */
20 alvherre@alvh.no-ip. 5439 [ + + ]:GNC 46 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
5440 [ + + ]: 25 : tab->chgAccessMethod)
5441 : 22 : ATExecSetAccessMethodNoStorage(rel, tab->newAccessMethod);
991 michael@paquier.xyz 5442 :CBC 46 : break;
7168 bruce@momjian.us 5443 : 79 : case AT_SetTableSpace: /* SET TABLESPACE */
5444 : :
5445 : : /*
5446 : : * Only do this for partitioned tables and indexes, for which this
5447 : : * is just a catalog change. Other relation types which have
5448 : : * storage are handled by Phase 3.
5449 : : */
1945 alvherre@alvh.no-ip. 5450 [ + + ]: 79 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
5451 [ + + ]: 73 : rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
5452 : 18 : ATExecSetTableSpaceNoStorage(rel, tab->newTableSpace);
5453 : :
7217 tgl@sss.pgh.pa.us 5454 : 76 : break;
6402 bruce@momjian.us 5455 : 467 : case AT_SetRelOptions: /* SET (...) */
5456 : : case AT_ResetRelOptions: /* RESET (...) */
5457 : : case AT_ReplaceRelOptions: /* replace entire option list */
4497 rhaas@postgresql.org 5458 : 467 : ATExecSetRelOptions(rel, (List *) cmd->def, cmd->subtype, lockmode);
6496 bruce@momjian.us 5459 : 441 : break;
5995 5460 : 61 : case AT_EnableTrig: /* ENABLE TRIGGER name */
5461 : 61 : ATExecEnableDisableTrigger(rel, cmd->name,
5462 : : TRIGGER_FIRES_ON_ORIGIN, false,
619 alvherre@alvh.no-ip. 5463 : 61 : cmd->recurse,
5464 : : lockmode);
6236 JanWieck@Yahoo.com 5465 : 61 : break;
2489 tgl@sss.pgh.pa.us 5466 : 20 : case AT_EnableAlwaysTrig: /* ENABLE ALWAYS TRIGGER name */
5995 bruce@momjian.us 5467 : 20 : ATExecEnableDisableTrigger(rel, cmd->name,
5468 : : TRIGGER_FIRES_ALWAYS, false,
619 alvherre@alvh.no-ip. 5469 : 20 : cmd->recurse,
5470 : : lockmode);
6236 JanWieck@Yahoo.com 5471 : 20 : break;
2489 tgl@sss.pgh.pa.us 5472 : 8 : case AT_EnableReplicaTrig: /* ENABLE REPLICA TRIGGER name */
5995 bruce@momjian.us 5473 : 8 : ATExecEnableDisableTrigger(rel, cmd->name,
5474 : : TRIGGER_FIRES_ON_REPLICA, false,
619 alvherre@alvh.no-ip. 5475 : 8 : cmd->recurse,
5476 : : lockmode);
6809 tgl@sss.pgh.pa.us 5477 : 8 : break;
5478 : 69 : case AT_DisableTrig: /* DISABLE TRIGGER name */
5995 bruce@momjian.us 5479 : 69 : ATExecEnableDisableTrigger(rel, cmd->name,
5480 : : TRIGGER_DISABLED, false,
619 alvherre@alvh.no-ip. 5481 : 69 : cmd->recurse,
5482 : : lockmode);
6809 tgl@sss.pgh.pa.us 5483 : 69 : break;
6809 tgl@sss.pgh.pa.us 5484 :UBC 0 : case AT_EnableTrigAll: /* ENABLE TRIGGER ALL */
5995 bruce@momjian.us 5485 : 0 : ATExecEnableDisableTrigger(rel, NULL,
5486 : : TRIGGER_FIRES_ON_ORIGIN, false,
619 alvherre@alvh.no-ip. 5487 : 0 : cmd->recurse,
5488 : : lockmode);
6809 tgl@sss.pgh.pa.us 5489 : 0 : break;
2489 tgl@sss.pgh.pa.us 5490 :CBC 6 : case AT_DisableTrigAll: /* DISABLE TRIGGER ALL */
5995 bruce@momjian.us 5491 : 6 : ATExecEnableDisableTrigger(rel, NULL,
5492 : : TRIGGER_DISABLED, false,
619 alvherre@alvh.no-ip. 5493 : 6 : cmd->recurse,
5494 : : lockmode);
6809 tgl@sss.pgh.pa.us 5495 : 6 : break;
2489 tgl@sss.pgh.pa.us 5496 :UBC 0 : case AT_EnableTrigUser: /* ENABLE TRIGGER USER */
5995 bruce@momjian.us 5497 : 0 : ATExecEnableDisableTrigger(rel, NULL,
5498 : : TRIGGER_FIRES_ON_ORIGIN, true,
619 alvherre@alvh.no-ip. 5499 : 0 : cmd->recurse,
5500 : : lockmode);
6809 tgl@sss.pgh.pa.us 5501 : 0 : break;
2489 tgl@sss.pgh.pa.us 5502 :CBC 6 : case AT_DisableTrigUser: /* DISABLE TRIGGER USER */
5995 bruce@momjian.us 5503 : 6 : ATExecEnableDisableTrigger(rel, NULL,
5504 : : TRIGGER_DISABLED, true,
619 alvherre@alvh.no-ip. 5505 : 6 : cmd->recurse,
5506 : : lockmode);
6236 JanWieck@Yahoo.com 5507 : 6 : break;
5508 : :
5995 bruce@momjian.us 5509 : 4 : case AT_EnableRule: /* ENABLE RULE name */
5510 : 4 : ATExecEnableDisableRule(rel, cmd->name,
5511 : : RULE_FIRES_ON_ORIGIN, lockmode);
6236 JanWieck@Yahoo.com 5512 : 4 : break;
2489 tgl@sss.pgh.pa.us 5513 :UBC 0 : case AT_EnableAlwaysRule: /* ENABLE ALWAYS RULE name */
5995 bruce@momjian.us 5514 : 0 : ATExecEnableDisableRule(rel, cmd->name,
5515 : : RULE_FIRES_ALWAYS, lockmode);
6236 JanWieck@Yahoo.com 5516 : 0 : break;
2489 tgl@sss.pgh.pa.us 5517 :CBC 3 : case AT_EnableReplicaRule: /* ENABLE REPLICA RULE name */
5995 bruce@momjian.us 5518 : 3 : ATExecEnableDisableRule(rel, cmd->name,
5519 : : RULE_FIRES_ON_REPLICA, lockmode);
6236 JanWieck@Yahoo.com 5520 : 3 : break;
5521 : 16 : case AT_DisableRule: /* DISABLE RULE name */
5995 bruce@momjian.us 5522 : 16 : ATExecEnableDisableRule(rel, cmd->name,
5523 : : RULE_DISABLED, lockmode);
6809 tgl@sss.pgh.pa.us 5524 : 16 : break;
5525 : :
6393 5526 : 154 : case AT_AddInherit:
3308 alvherre@alvh.no-ip. 5527 : 154 : address = ATExecAddInherit(rel, (RangeVar *) cmd->def, lockmode);
6496 bruce@momjian.us 5528 : 112 : break;
6393 tgl@sss.pgh.pa.us 5529 : 22 : case AT_DropInherit:
3308 alvherre@alvh.no-ip. 5530 : 22 : address = ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode);
6496 bruce@momjian.us 5531 : 19 : break;
4743 rhaas@postgresql.org 5532 : 33 : case AT_AddOf:
3308 alvherre@alvh.no-ip. 5533 : 33 : address = ATExecAddOf(rel, (TypeName *) cmd->def, lockmode);
4743 rhaas@postgresql.org 5534 : 15 : break;
5535 : 3 : case AT_DropOf:
5536 : 3 : ATExecDropOf(rel, lockmode);
5537 : 3 : break;
3810 5538 : 224 : case AT_ReplicaIdentity:
5539 : 224 : ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
5540 : 200 : break;
3495 sfrost@snowman.net 5541 : 139 : case AT_EnableRowSecurity:
1139 michael@paquier.xyz 5542 : 139 : ATExecSetRowSecurity(rel, true);
3495 sfrost@snowman.net 5543 : 139 : break;
5544 : 5 : case AT_DisableRowSecurity:
1139 michael@paquier.xyz 5545 : 5 : ATExecSetRowSecurity(rel, false);
3495 sfrost@snowman.net 5546 : 5 : break;
3115 5547 : 41 : case AT_ForceRowSecurity:
5548 : 41 : ATExecForceNoForceRowSecurity(rel, true);
5549 : 41 : break;
5550 : 16 : case AT_NoForceRowSecurity:
5551 : 16 : ATExecForceNoForceRowSecurity(rel, false);
5552 : 16 : break;
4852 rhaas@postgresql.org 5553 : 25 : case AT_GenericOptions:
5554 : 25 : ATExecGenericOptions(rel, (List *) cmd->def);
5555 : 24 : break;
2685 5556 : 1289 : case AT_AttachPartition:
1551 tgl@sss.pgh.pa.us 5557 : 1289 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5558 : : cur_pass, context);
5559 [ - + ]: 1274 : Assert(cmd != NULL);
2277 alvherre@alvh.no-ip. 5560 [ + + ]: 1274 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
623 michael@paquier.xyz 5561 : 1088 : address = ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def,
5562 : : context);
5563 : : else
5564 : 186 : address = ATExecAttachPartitionIdx(wqueue, rel,
5565 : 186 : ((PartitionCmd *) cmd->def)->name);
2685 rhaas@postgresql.org 5566 : 1103 : break;
5567 : 261 : case AT_DetachPartition:
1551 tgl@sss.pgh.pa.us 5568 : 261 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5569 : : cur_pass, context);
5570 [ - + ]: 258 : Assert(cmd != NULL);
5571 : : /* ATPrepCmd ensures it must be a table */
2277 alvherre@alvh.no-ip. 5572 [ - + ]: 258 : Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
623 michael@paquier.xyz 5573 : 258 : address = ATExecDetachPartition(wqueue, tab, rel,
5574 : 258 : ((PartitionCmd *) cmd->def)->name,
5575 : 258 : ((PartitionCmd *) cmd->def)->concurrent);
1116 alvherre@alvh.no-ip. 5576 : 193 : break;
5577 : 7 : case AT_DetachPartitionFinalize:
623 michael@paquier.xyz 5578 : 7 : address = ATExecDetachPartitionFinalize(rel, ((PartitionCmd *) cmd->def)->name);
2685 rhaas@postgresql.org 5579 : 7 : break;
7 akorotkov@postgresql 5580 :GNC 126 : case AT_SplitPartition:
5581 : 126 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5582 : : cur_pass, context);
5583 [ - + ]: 60 : Assert(cmd != NULL);
5584 [ - + ]: 60 : Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
5585 : 60 : ATExecSplitPartition(wqueue, tab, rel, (PartitionCmd *) cmd->def,
5586 : : context);
5587 : 57 : break;
5588 : 60 : case AT_MergePartitions:
5589 : 60 : cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
5590 : : cur_pass, context);
5591 [ - + ]: 33 : Assert(cmd != NULL);
5592 [ - + ]: 33 : Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
5593 : 33 : ATExecMergePartitions(wqueue, tab, rel, (PartitionCmd *) cmd->def,
5594 : : context);
5595 : 33 : break;
7168 bruce@momjian.us 5596 :UBC 0 : default: /* oops */
7284 tgl@sss.pgh.pa.us 5597 [ # # ]: 0 : elog(ERROR, "unrecognized alter table type: %d",
5598 : : (int) cmd->subtype);
5599 : : break;
5600 : : }
5601 : :
5602 : : /*
5603 : : * Report the subcommand to interested event triggers.
5604 : : */
1551 tgl@sss.pgh.pa.us 5605 [ + + ]:CBC 27207 : if (cmd)
5606 : 20912 : EventTriggerCollectAlterTableSubcmd((Node *) cmd, address);
5607 : :
5608 : : /*
5609 : : * Bump the command counter to ensure the next subcommand in the sequence
5610 : : * can see the changes so far
5611 : : */
7284 5612 : 27207 : CommandCounterIncrement();
5613 : 27207 : }
5614 : :
5615 : : /*
5616 : : * ATParseTransformCmd: perform parse transformation for one subcommand
5617 : : *
5618 : : * Returns the transformed subcommand tree, if there is one, else NULL.
5619 : : *
5620 : : * The parser may hand back additional AlterTableCmd(s) and/or other
5621 : : * utility statements, either before or after the original subcommand.
5622 : : * Other AlterTableCmds are scheduled into the appropriate slot of the
5623 : : * AlteredTableInfo (they had better be for later passes than the current one).
5624 : : * Utility statements that are supposed to happen before the AlterTableCmd
5625 : : * are executed immediately. Those that are supposed to happen afterwards
5626 : : * are added to the tab->afterStmts list to be done at the very end.
5627 : : */
5628 : : static AlterTableCmd *
1551 5629 : 9634 : ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
5630 : : AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
5631 : : AlterTablePass cur_pass, AlterTableUtilityContext *context)
5632 : : {
5633 : 9634 : AlterTableCmd *newcmd = NULL;
5634 : 9634 : AlterTableStmt *atstmt = makeNode(AlterTableStmt);
5635 : : List *beforeStmts;
5636 : : List *afterStmts;
5637 : : ListCell *lc;
5638 : :
5639 : : /* Gin up an AlterTableStmt with just this subcommand and this table */
5640 : 9634 : atstmt->relation =
5641 : 9634 : makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
5642 : 9634 : pstrdup(RelationGetRelationName(rel)),
5643 : : -1);
5644 : 9634 : atstmt->relation->inh = recurse;
5645 : 9634 : atstmt->cmds = list_make1(cmd);
1373 michael@paquier.xyz 5646 : 9634 : atstmt->objtype = OBJECT_TABLE; /* needn't be picky here */
1551 tgl@sss.pgh.pa.us 5647 : 9634 : atstmt->missing_ok = false;
5648 : :
5649 : : /* Transform the AlterTableStmt */
5650 : 9634 : atstmt = transformAlterTableStmt(RelationGetRelid(rel),
5651 : : atstmt,
5652 : : context->queryString,
5653 : : &beforeStmts,
5654 : : &afterStmts);
5655 : :
5656 : : /* Execute any statements that should happen before these subcommand(s) */
5657 [ + + + + : 9724 : foreach(lc, beforeStmts)
+ + ]
5658 : : {
5659 : 222 : Node *stmt = (Node *) lfirst(lc);
5660 : :
5661 : 222 : ProcessUtilityForAlterTable(stmt, context);
5662 : 216 : CommandCounterIncrement();
5663 : : }
5664 : :
5665 : : /* Examine the transformed subcommands and schedule them appropriately */
5666 [ + - + + : 22651 : foreach(lc, atstmt->cmds)
+ + ]
5667 : : {
5668 : 13149 : AlterTableCmd *cmd2 = lfirst_node(AlterTableCmd, lc);
5669 : : AlterTablePass pass;
5670 : :
5671 : : /*
5672 : : * This switch need only cover the subcommand types that can be added
5673 : : * by parse_utilcmd.c; otherwise, we'll use the default strategy of
5674 : : * executing the subcommand immediately, as a substitute for the
5675 : : * original subcommand. (Note, however, that this does cause
5676 : : * AT_AddConstraint subcommands to be rescheduled into later passes,
5677 : : * which is important for index and foreign key constraints.)
5678 : : *
5679 : : * We assume we needn't do any phase-1 checks for added subcommands.
5680 : : */
1331 5681 [ + + + + : 13149 : switch (cmd2->subtype)
- + ]
5682 : : {
233 alvherre@alvh.no-ip. 5683 :GNC 3491 : case AT_SetAttNotNull:
5684 : 3491 : ATSimpleRecursion(wqueue, rel, cmd2, recurse, lockmode, context);
1331 tgl@sss.pgh.pa.us 5685 :CBC 3491 : pass = AT_PASS_COL_ATTRS;
5686 : 3491 : break;
5687 : 509 : case AT_AddIndex:
5688 : :
5689 : : /*
5690 : : * A primary key on a inheritance parent needs supporting NOT
5691 : : * NULL constraint on its children; enqueue commands to create
5692 : : * those or mark them inherited if they already exist.
5693 : : */
233 alvherre@alvh.no-ip. 5694 :GNC 509 : ATPrepAddPrimaryKey(wqueue, rel, cmd2, lockmode, context);
1331 tgl@sss.pgh.pa.us 5695 :CBC 509 : pass = AT_PASS_ADD_INDEX;
5696 : 509 : break;
5697 : 4107 : case AT_AddIndexConstraint:
5698 : : /* as above */
233 alvherre@alvh.no-ip. 5699 :GNC 4107 : ATPrepAddPrimaryKey(wqueue, rel, cmd2, lockmode, context);
1331 tgl@sss.pgh.pa.us 5700 :CBC 4107 : pass = AT_PASS_ADD_INDEXCONSTR;
5701 : 4107 : break;
5702 : 1835 : case AT_AddConstraint:
5703 : : /* Recursion occurs during execution phase */
5704 [ + + ]: 1835 : if (recurse)
489 alvherre@alvh.no-ip. 5705 : 1810 : cmd2->recurse = true;
1331 tgl@sss.pgh.pa.us 5706 [ - + ]: 1835 : switch (castNode(Constraint, cmd2->def)->contype)
5707 : : {
1331 tgl@sss.pgh.pa.us 5708 :UBC 0 : case CONSTR_PRIMARY:
5709 : : case CONSTR_UNIQUE:
5710 : : case CONSTR_EXCLUSION:
5711 : 0 : pass = AT_PASS_ADD_INDEXCONSTR;
5712 : 0 : break;
1331 tgl@sss.pgh.pa.us 5713 :CBC 1835 : default:
5714 : 1835 : pass = AT_PASS_ADD_OTHERCONSTR;
5715 : 1835 : break;
5716 : : }
5717 : 1835 : break;
1331 tgl@sss.pgh.pa.us 5718 :UBC 0 : case AT_AlterColumnGenericOptions:
5719 : : /* This command never recurses */
5720 : : /* No command-specific prep needed */
5721 : 0 : pass = AT_PASS_MISC;
5722 : 0 : break;
1331 tgl@sss.pgh.pa.us 5723 :CBC 3207 : default:
5724 : 3207 : pass = cur_pass;
5725 : 3207 : break;
5726 : : }
5727 : :
5728 [ - + ]: 13149 : if (pass < cur_pass)
5729 : : {
5730 : : /* Cannot schedule into a pass we already finished */
1331 tgl@sss.pgh.pa.us 5731 [ # # ]:UBC 0 : elog(ERROR, "ALTER TABLE scheduling failure: too late for pass %d",
5732 : : pass);
5733 : : }
1331 tgl@sss.pgh.pa.us 5734 [ + + ]:CBC 13149 : else if (pass > cur_pass)
5735 : : {
5736 : : /* OK, queue it up for later */
5737 : 9942 : tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd2);
5738 : : }
5739 : : else
5740 : : {
5741 : : /*
5742 : : * We should see at most one subcommand for the current pass,
5743 : : * which is the transformed version of the original subcommand.
5744 : : */
5745 [ + - + - ]: 3207 : if (newcmd == NULL && cmd->subtype == cmd2->subtype)
5746 : : {
5747 : : /* Found the transformed version of our subcommand */
5748 : 3207 : newcmd = cmd2;
5749 : : }
5750 : : else
1331 tgl@sss.pgh.pa.us 5751 [ # # ]:UBC 0 : elog(ERROR, "ALTER TABLE scheduling failure: bogus item for pass %d",
5752 : : pass);
5753 : : }
5754 : : }
5755 : :
5756 : : /* Queue up any after-statements to happen at the end */
1551 tgl@sss.pgh.pa.us 5757 :CBC 9502 : tab->afterStmts = list_concat(tab->afterStmts, afterStmts);
5758 : :
5759 : 9502 : return newcmd;
5760 : : }
5761 : :
5762 : : /*
5763 : : * ATRewriteTables: ALTER TABLE phase 3
5764 : : */
5765 : : static void
5766 : 15677 : ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
5767 : : AlterTableUtilityContext *context)
5768 : : {
5769 : : ListCell *ltab;
5770 : :
5771 : : /* Go through each table that needs to be checked or rewritten */
7284 5772 [ + - + + : 33078 : foreach(ltab, *wqueue)
+ + ]
5773 : : {
5774 : 17523 : AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
5775 : :
5776 : : /* Relations without storage may be ignored here */
1835 5777 [ + + + + : 17523 : if (!RELKIND_HAS_STORAGE(tab->relkind))
+ + + - +
+ ]
4852 rhaas@postgresql.org 5778 : 3057 : continue;
5779 : :
5780 : : /*
5781 : : * If we change column data types, the operation has to be propagated
5782 : : * to tables that use this table's rowtype as a column type.
5783 : : * tab->newvals will also be non-NULL in the case where we're adding a
5784 : : * column with a default. We choose to forbid that case as well,
5785 : : * since composite types might eventually support defaults.
5786 : : *
5787 : : * (Eventually we'll probably need to check for composite type
5788 : : * dependencies even when we're just scanning the table without a
5789 : : * rewrite, but at the moment a composite type does not enforce any
5790 : : * constraints, so it's not necessary/appropriate to enforce them just
5791 : : * during ALTER.)
5792 : : */
3415 simon@2ndQuadrant.co 5793 [ + + + + ]: 14466 : if (tab->newvals != NIL || tab->rewrite > 0)
5794 : : {
5795 : : Relation rel;
5796 : :
1910 andres@anarazel.de 5797 : 726 : rel = table_open(tab->relid, NoLock);
4810 rhaas@postgresql.org 5798 : 726 : find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
1910 andres@anarazel.de 5799 : 720 : table_close(rel, NoLock);
5800 : : }
5801 : :
5802 : : /*
5803 : : * We only need to rewrite the table if at least one column needs to
5804 : : * be recomputed, or we are changing its persistence or access method.
5805 : : *
5806 : : * There are two reasons for requiring a rewrite when changing
5807 : : * persistence: on one hand, we need to ensure that the buffers
5808 : : * belonging to each of the two relations are marked with or without
5809 : : * BM_PERMANENT properly. On the other hand, since rewriting creates
5810 : : * and assigns a new relfilenumber, we automatically create or drop an
5811 : : * init fork for the relation as appropriate.
5812 : : */
738 peter@eisentraut.org 5813 [ + + + + ]: 14460 : if (tab->rewrite > 0 && tab->relkind != RELKIND_SEQUENCE)
7284 tgl@sss.pgh.pa.us 5814 : 422 : {
5815 : : /* Build a temporary relation and copy data */
5816 : : Relation OldHeap;
5817 : : Oid OIDNewHeap;
5818 : : Oid NewAccessMethod;
5819 : : Oid NewTableSpace;
5820 : : char persistence;
5821 : :
1910 andres@anarazel.de 5822 : 441 : OldHeap = table_open(tab->relid, NoLock);
5823 : :
5824 : : /*
5825 : : * We don't support rewriting of system catalogs; there are too
5826 : : * many corner cases and too little benefit. In particular this
5827 : : * is certainly not going to work for mapped catalogs.
5828 : : */
5180 tgl@sss.pgh.pa.us 5829 [ - + ]: 441 : if (IsSystemRelation(OldHeap))
7281 tgl@sss.pgh.pa.us 5830 [ # # ]:UBC 0 : ereport(ERROR,
5831 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5832 : : errmsg("cannot rewrite system relation \"%s\"",
5833 : : RelationGetRelationName(OldHeap))));
5834 : :
3778 rhaas@postgresql.org 5835 [ + + - + :CBC 441 : if (RelationIsUsedAsCatalogTable(OldHeap))
- - + + ]
5836 [ + - ]: 1 : ereport(ERROR,
5837 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5838 : : errmsg("cannot rewrite table \"%s\" used as a catalog table",
5839 : : RelationGetRelationName(OldHeap))));
5840 : :
5841 : : /*
5842 : : * Don't allow rewrite on temp tables of other backends ... their
5843 : : * local buffer manager is not going to cope.
5844 : : */
5493 tgl@sss.pgh.pa.us 5845 [ + + - + ]: 440 : if (RELATION_IS_OTHER_TEMP(OldHeap))
7281 tgl@sss.pgh.pa.us 5846 [ # # ]:UBC 0 : ereport(ERROR,
5847 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5848 : : errmsg("cannot rewrite temporary tables of other sessions")));
5849 : :
5850 : : /*
5851 : : * Select destination tablespace (same as original unless user
5852 : : * requested a change)
5853 : : */
7217 tgl@sss.pgh.pa.us 5854 [ - + ]:CBC 440 : if (tab->newTableSpace)
7217 tgl@sss.pgh.pa.us 5855 :UBC 0 : NewTableSpace = tab->newTableSpace;
5856 : : else
7217 tgl@sss.pgh.pa.us 5857 :CBC 440 : NewTableSpace = OldHeap->rd_rel->reltablespace;
5858 : :
5859 : : /*
5860 : : * Select destination access method (same as original unless user
5861 : : * requested a change)
5862 : : */
20 alvherre@alvh.no-ip. 5863 [ + + ]:GNC 440 : if (tab->chgAccessMethod)
991 michael@paquier.xyz 5864 :CBC 18 : NewAccessMethod = tab->newAccessMethod;
5865 : : else
5866 : 422 : NewAccessMethod = OldHeap->rd_rel->relam;
5867 : :
5868 : : /*
5869 : : * Select persistence of transient table (same as original unless
5870 : : * user requested a change)
5871 : : */
3520 alvherre@alvh.no-ip. 5872 [ + + ]: 440 : persistence = tab->chgPersistence ?
3523 5873 : 414 : tab->newrelpersistence : OldHeap->rd_rel->relpersistence;
5874 : :
1910 andres@anarazel.de 5875 : 440 : table_close(OldHeap, NoLock);
5876 : :
5877 : : /*
5878 : : * Fire off an Event Trigger now, before actually rewriting the
5879 : : * table.
5880 : : *
5881 : : * We don't support Event Trigger for nested commands anywhere,
5882 : : * here included, and parsetree is given NULL when coming from
5883 : : * AlterTableInternal.
5884 : : *
5885 : : * And fire it only once.
5886 : : */
3415 simon@2ndQuadrant.co 5887 [ + - ]: 440 : if (parsetree)
3249 bruce@momjian.us 5888 : 440 : EventTriggerTableRewrite((Node *) parsetree,
5889 : : tab->relid,
5890 : : tab->rewrite);
5891 : :
5892 : : /*
5893 : : * Create transient table that will receive the modified data.
5894 : : *
5895 : : * Ensure it is marked correctly as logged or unlogged. We have
5896 : : * to do this here so that buffers for the new relfilenumber will
5897 : : * have the right persistence set, and at the same time ensure
5898 : : * that the original filenumbers's buffers will get read in with
5899 : : * the correct setting (i.e. the original one). Otherwise a
5900 : : * rollback after the rewrite would possibly result with buffers
5901 : : * for the original filenumbers having the wrong persistence
5902 : : * setting.
5903 : : *
5904 : : * NB: This relies on swap_relation_files() also swapping the
5905 : : * persistence. That wouldn't work for pg_class, but that can't be
5906 : : * unlogged anyway.
5907 : : */
991 michael@paquier.xyz 5908 : 437 : OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, NewAccessMethod,
5909 : : persistence, lockmode);
5910 : :
5911 : : /*
5912 : : * Copy the heap data into the new table with the desired
5913 : : * modifications, and test the current data within the table
5914 : : * against new constraints generated by ALTER TABLE commands.
5915 : : */
5009 simon@2ndQuadrant.co 5916 : 437 : ATRewriteTable(tab, OIDNewHeap, lockmode);
5917 : :
5918 : : /*
5919 : : * Swap the physical files of the old and new heaps, then rebuild
5920 : : * indexes and discard the old heap. We can use RecentXmin for
5921 : : * the table's new relfrozenxid because we rewrote all the tuples
5922 : : * in ATRewriteTable, so no older Xid remains in the table. Also,
5923 : : * we never try to swap toast tables by content, since we have no
5924 : : * interest in letting this code work on system catalogs.
5925 : : */
4833 rhaas@postgresql.org 5926 : 425 : finish_heap_swap(tab->relid, OIDNewHeap,
5927 : : false, false, true,
4046 5928 : 425 : !OidIsValid(tab->newTableSpace),
5929 : : RecentXmin,
5930 : : ReadNextMultiXactId(),
5931 : : persistence);
5932 : :
978 michael@paquier.xyz 5933 [ - + ]: 422 : InvokeObjectPostAlterHook(RelationRelationId, tab->relid, 0);
5934 : : }
738 peter@eisentraut.org 5935 [ + + + - ]: 14019 : else if (tab->rewrite > 0 && tab->relkind == RELKIND_SEQUENCE)
5936 : : {
5937 [ + - ]: 6 : if (tab->chgPersistence)
5938 : 6 : SequenceChangePersistence(tab->relid, tab->newrelpersistence);
5939 : : }
5940 : : else
5941 : : {
5942 : : /*
5943 : : * If required, test the current data within the table against new
5944 : : * constraints generated by ALTER TABLE commands, but don't
5945 : : * rebuild data.
5946 : : */
1859 rhaas@postgresql.org 5947 [ + + + + ]: 14013 : if (tab->constraints != NIL || tab->verify_new_notnull ||
2576 tgl@sss.pgh.pa.us 5948 [ + + ]: 12680 : tab->partition_constraint != NULL)
5009 simon@2ndQuadrant.co 5949 : 2225 : ATRewriteTable(tab, InvalidOid, lockmode);
5950 : :
5951 : : /*
5952 : : * If we had SET TABLESPACE but no reason to reconstruct tuples,
5953 : : * just do a block-by-block copy.
5954 : : */
7217 tgl@sss.pgh.pa.us 5955 [ + + ]: 13916 : if (tab->newTableSpace)
5009 simon@2ndQuadrant.co 5956 : 61 : ATExecSetTableSpace(tab->relid, tab->newTableSpace, lockmode);
5957 : : }
5958 : :
5959 : : /*
5960 : : * Also change persistence of owned sequences, so that it matches the
5961 : : * table persistence.
5962 : : */
738 peter@eisentraut.org 5963 [ + + ]: 14344 : if (tab->chgPersistence)
5964 : : {
5965 : 32 : List *seqlist = getOwnedSequences(tab->relid);
5966 : : ListCell *lc;
5967 : :
5968 [ + + + + : 56 : foreach(lc, seqlist)
+ + ]
5969 : : {
703 tgl@sss.pgh.pa.us 5970 : 24 : Oid seq_relid = lfirst_oid(lc);
5971 : :
738 peter@eisentraut.org 5972 : 24 : SequenceChangePersistence(seq_relid, tab->newrelpersistence);
5973 : : }
5974 : : }
5975 : : }
5976 : :
5977 : : /*
5978 : : * Foreign key constraints are checked in a final pass, since (a) it's
5979 : : * generally best to examine each one separately, and (b) it's at least
5980 : : * theoretically possible that we have changed both relations of the
5981 : : * foreign key, and we'd better have finished both rewrites before we try
5982 : : * to read the tables.
5983 : : */
7284 tgl@sss.pgh.pa.us 5984 [ + - + + : 32854 : foreach(ltab, *wqueue)
+ + ]
5985 : : {
7168 bruce@momjian.us 5986 : 17339 : AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
5987 : 17339 : Relation rel = NULL;
5988 : : ListCell *lcon;
5989 : :
5990 : : /* Relations without storage may be ignored here too */
1835 tgl@sss.pgh.pa.us 5991 [ + + + + : 17339 : if (!RELKIND_HAS_STORAGE(tab->relkind))
+ + + - +
+ ]
5992 : 3020 : continue;
5993 : :
7284 5994 [ + + + + : 15147 : foreach(lcon, tab->constraints)
+ + ]
5995 : : {
5996 : 868 : NewConstraint *con = lfirst(lcon);
5997 : :
5998 [ + + ]: 868 : if (con->contype == CONSTR_FOREIGN)
5999 : : {
5372 6000 : 542 : Constraint *fkconstraint = (Constraint *) con->qual;
6001 : : Relation refrel;
6002 : :
7284 6003 [ + + ]: 542 : if (rel == NULL)
6004 : : {
6005 : : /* Long since locked, no need for another */
1910 andres@anarazel.de 6006 : 536 : rel = table_open(tab->relid, NoLock);
6007 : : }
6008 : :
6009 : 542 : refrel = table_open(con->refrelid, RowShareLock);
6010 : :
4814 simon@2ndQuadrant.co 6011 : 542 : validateForeignKeyConstraint(fkconstraint->conname, rel, refrel,
6012 : : con->refindid,
6013 : : con->conid,
21 peter@eisentraut.org 6014 :GNC 542 : con->conwithperiod);
6015 : :
6016 : : /*
6017 : : * No need to mark the constraint row as validated, we did
6018 : : * that when we inserted the row earlier.
6019 : : */
6020 : :
1910 andres@anarazel.de 6021 :CBC 502 : table_close(refrel, NoLock);
6022 : : }
6023 : : }
6024 : :
7284 tgl@sss.pgh.pa.us 6025 [ + + ]: 14279 : if (rel)
1910 andres@anarazel.de 6026 : 496 : table_close(rel, NoLock);
6027 : : }
6028 : :
6029 : : /* Finally, run any afterStmts that were queued up */
1551 tgl@sss.pgh.pa.us 6030 [ + - + + : 32795 : foreach(ltab, *wqueue)
+ + ]
6031 : : {
6032 : 17280 : AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
6033 : : ListCell *lc;
6034 : :
6035 [ + + + + : 17323 : foreach(lc, tab->afterStmts)
+ + ]
6036 : : {
6037 : 43 : Node *stmt = (Node *) lfirst(lc);
6038 : :
6039 : 43 : ProcessUtilityForAlterTable(stmt, context);
6040 : 43 : CommandCounterIncrement();
6041 : : }
6042 : : }
7284 6043 : 15515 : }
6044 : :
6045 : : /*
6046 : : * ATRewriteTable: scan or rewrite one table
6047 : : *
6048 : : * OIDNewHeap is InvalidOid if we don't need to rewrite
6049 : : */
6050 : : static void
5009 simon@2ndQuadrant.co 6051 : 2662 : ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
6052 : : {
6053 : : Relation oldrel;
6054 : : Relation newrel;
6055 : : TupleDesc oldTupDesc;
6056 : : TupleDesc newTupDesc;
7284 tgl@sss.pgh.pa.us 6057 : 2662 : bool needscan = false;
6058 : : List *notnull_attrs;
6059 : : int i;
6060 : : ListCell *l;
6061 : : EState *estate;
6062 : : CommandId mycid;
6063 : : BulkInsertState bistate;
6064 : : int ti_options;
2588 andres@anarazel.de 6065 : 2662 : ExprState *partqualstate = NULL;
6066 : :
6067 : : /*
6068 : : * Open the relation(s). We have surely already locked the existing
6069 : : * table.
6070 : : */
1910 6071 : 2662 : oldrel = table_open(tab->relid, NoLock);
7284 tgl@sss.pgh.pa.us 6072 : 2662 : oldTupDesc = tab->oldDesc;
2489 6073 : 2662 : newTupDesc = RelationGetDescr(oldrel); /* includes all mods */
6074 : :
7284 6075 [ + + ]: 2662 : if (OidIsValid(OIDNewHeap))
1910 andres@anarazel.de 6076 : 437 : newrel = table_open(OIDNewHeap, lockmode);
6077 : : else
7284 tgl@sss.pgh.pa.us 6078 : 2225 : newrel = NULL;
6079 : :
6080 : : /*
6081 : : * Prepare a BulkInsertState and options for table_tuple_insert. The FSM
6082 : : * is empty, so don't bother using it.
6083 : : */
5275 heikki.linnakangas@i 6084 [ + + ]: 2662 : if (newrel)
6085 : : {
6086 : 437 : mycid = GetCurrentCommandId(true);
6087 : 437 : bistate = GetBulkInsertState();
1840 andres@anarazel.de 6088 : 437 : ti_options = TABLE_INSERT_SKIP_FSM;
6089 : : }
6090 : : else
6091 : : {
6092 : : /* keep compiler quiet about using these uninitialized */
5275 heikki.linnakangas@i 6093 : 2225 : mycid = 0;
6094 : 2225 : bistate = NULL;
1840 andres@anarazel.de 6095 : 2225 : ti_options = 0;
6096 : : }
6097 : :
6098 : : /*
6099 : : * Generate the constraint and default execution states
6100 : : */
6101 : :
7284 tgl@sss.pgh.pa.us 6102 : 2662 : estate = CreateExecutorState();
6103 : :
6104 : : /* Build the needed expression execution states */
6105 [ + + + + : 3578 : foreach(l, tab->constraints)
+ + ]
6106 : : {
6107 : 916 : NewConstraint *con = lfirst(l);
6108 : :
6109 [ + + - ]: 916 : switch (con->contype)
6110 : : {
6111 : 371 : case CONSTR_CHECK:
6112 : 371 : needscan = true;
2588 andres@anarazel.de 6113 : 371 : con->qualstate = ExecPrepareExpr((Expr *) con->qual, estate);
7284 tgl@sss.pgh.pa.us 6114 : 371 : break;
6115 : 545 : case CONSTR_FOREIGN:
6116 : : /* Nothing to do here */
6117 : 545 : break;
7284 tgl@sss.pgh.pa.us 6118 :UBC 0 : default:
6119 [ # # ]: 0 : elog(ERROR, "unrecognized constraint type: %d",
6120 : : (int) con->contype);
6121 : : }
6122 : : }
6123 : :
6124 : : /* Build expression execution states for partition check quals */
2685 rhaas@postgresql.org 6125 [ + + ]:CBC 2662 : if (tab->partition_constraint)
6126 : : {
6127 : 957 : needscan = true;
2576 tgl@sss.pgh.pa.us 6128 : 957 : partqualstate = ExecPrepareExpr(tab->partition_constraint, estate);
6129 : : }
6130 : :
7284 6131 [ + + + + : 3097 : foreach(l, tab->newvals)
+ + ]
6132 : : {
7168 bruce@momjian.us 6133 : 435 : NewColumnValue *ex = lfirst(l);
6134 : :
6135 : : /* expr already planned */
4681 rhaas@postgresql.org 6136 : 435 : ex->exprstate = ExecInitExpr((Expr *) ex->expr, NULL);
6137 : : }
6138 : :
6488 tgl@sss.pgh.pa.us 6139 : 2662 : notnull_attrs = NIL;
1859 rhaas@postgresql.org 6140 [ + + + + ]: 2662 : if (newrel || tab->verify_new_notnull)
6141 : : {
6142 : : /*
6143 : : * If we are rebuilding the tuples OR if we added any new but not
6144 : : * verified not-null constraints, check all not-null constraints. This
6145 : : * is a bit of overkill but it minimizes risk of bugs, and
6146 : : * heap_attisnull is a pretty cheap test anyway.
6147 : : */
6488 tgl@sss.pgh.pa.us 6148 [ + + ]: 3479 : for (i = 0; i < newTupDesc->natts; i++)
6149 : : {
2429 andres@anarazel.de 6150 : 2552 : Form_pg_attribute attr = TupleDescAttr(newTupDesc, i);
6151 : :
6152 [ + + + - ]: 2552 : if (attr->attnotnull && !attr->attisdropped)
6488 tgl@sss.pgh.pa.us 6153 : 1005 : notnull_attrs = lappend_int(notnull_attrs, i);
6154 : : }
6155 [ + + ]: 927 : if (notnull_attrs)
6156 : 725 : needscan = true;
6157 : : }
6158 : :
5541 6159 [ + + + + ]: 2662 : if (newrel || needscan)
6160 : : {
6161 : : ExprContext *econtext;
6162 : : TupleTableSlot *oldslot;
6163 : : TupleTableSlot *newslot;
6164 : : TableScanDesc scan;
6165 : : MemoryContext oldCxt;
6756 bruce@momjian.us 6166 : 2209 : List *dropped_attrs = NIL;
6167 : : ListCell *lc;
6168 : : Snapshot snapshot;
6169 : :
4810 rhaas@postgresql.org 6170 [ + + ]: 2209 : if (newrel)
6171 [ - + ]: 437 : ereport(DEBUG1,
6172 : : (errmsg_internal("rewriting table \"%s\"",
6173 : : RelationGetRelationName(oldrel))));
6174 : : else
6175 [ + + ]: 1772 : ereport(DEBUG1,
6176 : : (errmsg_internal("verifying table \"%s\"",
6177 : : RelationGetRelationName(oldrel))));
6178 : :
4694 heikki.linnakangas@i 6179 [ + + ]: 2209 : if (newrel)
6180 : : {
6181 : : /*
6182 : : * All predicate locks on the tuples or pages are about to be made
6183 : : * invalid, because we move tuples around. Promote them to
6184 : : * relation locks.
6185 : : */
6186 : 437 : TransferPredicateLocksToHeapRelation(oldrel);
6187 : : }
6188 : :
7284 tgl@sss.pgh.pa.us 6189 [ - + ]: 2209 : econtext = GetPerTupleExprContext(estate);
6190 : :
6191 : : /*
6192 : : * Create necessary tuple slots. When rewriting, two slots are needed,
6193 : : * otherwise one suffices. In the case where one slot suffices, we
6194 : : * need to use the new tuple descriptor, otherwise some constraints
6195 : : * can't be evaluated. Note that even when the tuple layout is the
6196 : : * same and no rewrite is required, the tupDescs might not be
6197 : : * (consider ADD COLUMN without a default).
6198 : : */
1861 andres@anarazel.de 6199 [ + + ]: 2209 : if (tab->rewrite)
6200 : : {
6201 [ - + ]: 437 : Assert(newrel != NULL);
6202 : 437 : oldslot = MakeSingleTupleTableSlot(oldTupDesc,
6203 : : table_slot_callbacks(oldrel));
6204 : 437 : newslot = MakeSingleTupleTableSlot(newTupDesc,
6205 : : table_slot_callbacks(newrel));
6206 : :
6207 : : /*
6208 : : * Set all columns in the new slot to NULL initially, to ensure
6209 : : * columns added as part of the rewrite are initialized to NULL.
6210 : : * That is necessary as tab->newvals will not contain an
6211 : : * expression for columns with a NULL default, e.g. when adding a
6212 : : * column without a default together with a column with a default
6213 : : * requiring an actual rewrite.
6214 : : */
1649 6215 : 437 : ExecStoreAllNullTuple(newslot);
6216 : : }
6217 : : else
6218 : : {
1861 6219 : 1772 : oldslot = MakeSingleTupleTableSlot(newTupDesc,
6220 : : table_slot_callbacks(oldrel));
6221 : 1772 : newslot = NULL;
6222 : : }
6223 : :
6224 : : /*
6225 : : * Any attributes that are dropped according to the new tuple
6226 : : * descriptor can be set to NULL. We precompute the list of dropped
6227 : : * attributes to avoid needing to do so in the per-tuple loop.
6228 : : */
7004 neilc@samurai.com 6229 [ + + ]: 7896 : for (i = 0; i < newTupDesc->natts; i++)
6230 : : {
2429 andres@anarazel.de 6231 [ + + ]: 5687 : if (TupleDescAttr(newTupDesc, i)->attisdropped)
7004 neilc@samurai.com 6232 : 395 : dropped_attrs = lappend_int(dropped_attrs, i);
6233 : : }
6234 : :
6235 : : /*
6236 : : * Scan through the rows, generating a new row if needed and then
6237 : : * checking all the constraints.
6238 : : */
3939 rhaas@postgresql.org 6239 : 2209 : snapshot = RegisterSnapshot(GetLatestSnapshot());
1861 andres@anarazel.de 6240 : 2209 : scan = table_beginscan(oldrel, snapshot, 0, NULL);
6241 : :
6242 : : /*
6243 : : * Switch to per-tuple memory context and reset it for each tuple
6244 : : * produced, so we don't leak memory.
6245 : : */
7004 neilc@samurai.com 6246 [ + - ]: 2209 : oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
6247 : :
1861 andres@anarazel.de 6248 [ + + ]: 385696 : while (table_scan_getnextslot(scan, ForwardScanDirection, oldslot))
6249 : : {
6250 : : TupleTableSlot *insertslot;
6251 : :
3415 simon@2ndQuadrant.co 6252 [ + + ]: 381387 : if (tab->rewrite > 0)
6253 : : {
6254 : : /* Extract data from old tuple */
1861 andres@anarazel.de 6255 : 48776 : slot_getallattrs(oldslot);
6256 : 48776 : ExecClearTuple(newslot);
6257 : :
6258 : : /* copy attributes */
6259 : 48776 : memcpy(newslot->tts_values, oldslot->tts_values,
6260 : 48776 : sizeof(Datum) * oldslot->tts_nvalid);
6261 : 48776 : memcpy(newslot->tts_isnull, oldslot->tts_isnull,
6262 : 48776 : sizeof(bool) * oldslot->tts_nvalid);
6263 : :
6264 : : /* Set dropped attributes to null in new tuple */
6756 bruce@momjian.us 6265 [ + + + + : 48819 : foreach(lc, dropped_attrs)
+ + ]
1861 andres@anarazel.de 6266 : 43 : newslot->tts_isnull[lfirst_int(lc)] = true;
6267 : :
6268 : : /*
6269 : : * Constraints and GENERATED expressions might reference the
6270 : : * tableoid column, so fill tts_tableOid with the desired
6271 : : * value. (We must do this each time, because it gets
6272 : : * overwritten with newrel's OID during storing.)
6273 : : */
1059 tgl@sss.pgh.pa.us 6274 : 48776 : newslot->tts_tableOid = RelationGetRelid(oldrel);
6275 : :
6276 : : /*
6277 : : * Process supplied expressions to replace selected columns.
6278 : : *
6279 : : * First, evaluate expressions whose inputs come from the old
6280 : : * tuple.
6281 : : */
7284 6282 : 48776 : econtext->ecxt_scantuple = oldslot;
6283 : :
6284 [ + + + + : 100462 : foreach(l, tab->newvals)
+ + ]
6285 : : {
7168 bruce@momjian.us 6286 : 51692 : NewColumnValue *ex = lfirst(l);
6287 : :
1558 tgl@sss.pgh.pa.us 6288 [ + + ]: 51692 : if (ex->is_generated)
6289 : 75 : continue;
6290 : :
1861 andres@anarazel.de 6291 : 51617 : newslot->tts_values[ex->attnum - 1]
6292 : 51611 : = ExecEvalExpr(ex->exprstate,
6293 : : econtext,
6294 : 51617 : &newslot->tts_isnull[ex->attnum - 1]);
6295 : : }
6296 : :
6297 : 48770 : ExecStoreVirtualTuple(newslot);
6298 : :
6299 : : /*
6300 : : * Now, evaluate any expressions whose inputs come from the
6301 : : * new tuple. We assume these columns won't reference each
6302 : : * other, so that there's no ordering dependency.
6303 : : */
1558 tgl@sss.pgh.pa.us 6304 : 48770 : econtext->ecxt_scantuple = newslot;
6305 : :
6306 [ + + + + : 100456 : foreach(l, tab->newvals)
+ + ]
6307 : : {
6308 : 51686 : NewColumnValue *ex = lfirst(l);
6309 : :
6310 [ + + ]: 51686 : if (!ex->is_generated)
6311 : 51611 : continue;
6312 : :
6313 : 75 : newslot->tts_values[ex->attnum - 1]
6314 : 75 : = ExecEvalExpr(ex->exprstate,
6315 : : econtext,
6316 : 75 : &newslot->tts_isnull[ex->attnum - 1]);
6317 : : }
6318 : :
1861 andres@anarazel.de 6319 : 48770 : insertslot = newslot;
6320 : : }
6321 : : else
6322 : : {
6323 : : /*
6324 : : * If there's no rewrite, old and new table are guaranteed to
6325 : : * have the same AM, so we can just use the old slot to verify
6326 : : * new constraints etc.
6327 : : */
6328 : 332611 : insertslot = oldslot;
6329 : : }
6330 : :
6331 : : /* Now check any constraints on the possibly-changed tuple */
6332 : 381381 : econtext->ecxt_scantuple = insertslot;
6333 : :
6488 tgl@sss.pgh.pa.us 6334 [ + + + + : 1669532 : foreach(l, notnull_attrs)
+ + ]
6335 : : {
6402 bruce@momjian.us 6336 : 1288181 : int attn = lfirst_int(l);
6337 : :
1861 andres@anarazel.de 6338 [ + + ]: 1288181 : if (slot_attisnull(insertslot, attn + 1))
6339 : : {
2429 6340 : 30 : Form_pg_attribute attr = TupleDescAttr(newTupDesc, attn);
6341 : :
6488 tgl@sss.pgh.pa.us 6342 [ + - ]: 30 : ereport(ERROR,
6343 : : (errcode(ERRCODE_NOT_NULL_VIOLATION),
6344 : : errmsg("column \"%s\" of relation \"%s\" contains null values",
6345 : : NameStr(attr->attname),
6346 : : RelationGetRelationName(oldrel)),
6347 : : errtablecol(oldrel, attn + 1)));
6348 : : }
6349 : : }
6350 : :
7284 6351 [ + + + + : 385401 : foreach(l, tab->constraints)
+ + ]
6352 : : {
6353 : 4086 : NewConstraint *con = lfirst(l);
6354 : :
6355 [ + + - ]: 4086 : switch (con->contype)
6356 : : {
6357 : 4036 : case CONSTR_CHECK:
2588 andres@anarazel.de 6358 [ + + ]: 4036 : if (!ExecCheck(con->qualstate, econtext))
7284 tgl@sss.pgh.pa.us 6359 [ + - ]: 36 : ereport(ERROR,
6360 : : (errcode(ERRCODE_CHECK_VIOLATION),
6361 : : errmsg("check constraint \"%s\" of relation \"%s\" is violated by some row",
6362 : : con->name,
6363 : : RelationGetRelationName(oldrel)),
6364 : : errtableconstraint(oldrel, con->name)));
6365 : 4000 : break;
233 alvherre@alvh.no-ip. 6366 :GNC 50 : case CONSTR_NOTNULL:
7284 tgl@sss.pgh.pa.us 6367 :ECB (44) : case CONSTR_FOREIGN:
6368 : : /* Nothing to do here */
7284 tgl@sss.pgh.pa.us 6369 :CBC 50 : break;
7284 tgl@sss.pgh.pa.us 6370 :UBC 0 : default:
6371 [ # # ]: 0 : elog(ERROR, "unrecognized constraint type: %d",
6372 : : (int) con->contype);
6373 : : }
6374 : : }
6375 : :
2588 andres@anarazel.de 6376 [ + + + + ]:CBC 381315 : if (partqualstate && !ExecCheck(partqualstate, econtext))
6377 : : {
2410 rhaas@postgresql.org 6378 [ + + ]: 37 : if (tab->validate_default)
6379 [ + - ]: 13 : ereport(ERROR,
6380 : : (errcode(ERRCODE_CHECK_VIOLATION),
6381 : : errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
6382 : : RelationGetRelationName(oldrel)),
6383 : : errtable(oldrel)));
6384 : : else
6385 [ + - ]: 24 : ereport(ERROR,
6386 : : (errcode(ERRCODE_CHECK_VIOLATION),
6387 : : errmsg("partition constraint of relation \"%s\" is violated by some row",
6388 : : RelationGetRelationName(oldrel)),
6389 : : errtable(oldrel)));
6390 : : }
6391 : :
6392 : : /* Write the tuple out to the new relation */
7284 tgl@sss.pgh.pa.us 6393 [ + + ]: 381278 : if (newrel)
1788 andres@anarazel.de 6394 : 48764 : table_tuple_insert(newrel, insertslot, mycid,
6395 : : ti_options, bistate);
6396 : :
7284 tgl@sss.pgh.pa.us 6397 : 381278 : ResetExprContext(econtext);
6398 : :
6399 [ - + ]: 381278 : CHECK_FOR_INTERRUPTS();
6400 : : }
6401 : :
7004 neilc@samurai.com 6402 : 2100 : MemoryContextSwitchTo(oldCxt);
1861 andres@anarazel.de 6403 : 2100 : table_endscan(scan);
3939 rhaas@postgresql.org 6404 : 2100 : UnregisterSnapshot(snapshot);
6405 : :
6512 tgl@sss.pgh.pa.us 6406 : 2100 : ExecDropSingleTupleTableSlot(oldslot);
1861 andres@anarazel.de 6407 [ + + ]: 2100 : if (newslot)
6408 : 425 : ExecDropSingleTupleTableSlot(newslot);
6409 : : }
6410 : :
7284 tgl@sss.pgh.pa.us 6411 : 2553 : FreeExecutorState(estate);
6412 : :
1910 andres@anarazel.de 6413 : 2553 : table_close(oldrel, NoLock);
7284 tgl@sss.pgh.pa.us 6414 [ + + ]: 2553 : if (newrel)
6415 : : {
5275 heikki.linnakangas@i 6416 : 425 : FreeBulkInsertState(bistate);
6417 : :
1840 andres@anarazel.de 6418 : 425 : table_finish_bulk_insert(newrel, ti_options);
6419 : :
1910 6420 : 425 : table_close(newrel, NoLock);
6421 : : }
7284 tgl@sss.pgh.pa.us 6422 : 2553 : }
6423 : :
6424 : : /*
6425 : : * ATGetQueueEntry: find or create an entry in the ALTER TABLE work queue
6426 : : */
6427 : : static AlteredTableInfo *
6428 : 21562 : ATGetQueueEntry(List **wqueue, Relation rel)
6429 : : {
6430 : 21562 : Oid relid = RelationGetRelid(rel);
6431 : : AlteredTableInfo *tab;
6432 : : ListCell *ltab;
6433 : :
6434 [ + + + + : 26048 : foreach(ltab, *wqueue)
+ + ]
6435 : : {
6436 : 6979 : tab = (AlteredTableInfo *) lfirst(ltab);
6437 [ + + ]: 6979 : if (tab->relid == relid)
6438 : 2493 : return tab;
6439 : : }
6440 : :
6441 : : /*
6442 : : * Not there, so add it. Note that we make a copy of the relation's
6443 : : * existing descriptor before anything interesting can happen to it.
6444 : : */
6445 : 19069 : tab = (AlteredTableInfo *) palloc0(sizeof(AlteredTableInfo));
6446 : 19069 : tab->relid = relid;
1116 alvherre@alvh.no-ip. 6447 : 19069 : tab->rel = NULL; /* set later */
7281 tgl@sss.pgh.pa.us 6448 : 19069 : tab->relkind = rel->rd_rel->relkind;
2209 andrew@dunslane.net 6449 : 19069 : tab->oldDesc = CreateTupleDescCopyConstr(RelationGetDescr(rel));
991 michael@paquier.xyz 6450 : 19069 : tab->newAccessMethod = InvalidOid;
20 alvherre@alvh.no-ip. 6451 :GNC 19069 : tab->chgAccessMethod = false;
991 michael@paquier.xyz 6452 :CBC 19069 : tab->newTableSpace = InvalidOid;
3523 alvherre@alvh.no-ip. 6453 : 19069 : tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
3520 6454 : 19069 : tab->chgPersistence = false;
6455 : :
7284 tgl@sss.pgh.pa.us 6456 : 19069 : *wqueue = lappend(*wqueue, tab);
6457 : :
6458 : 19069 : return tab;
6459 : : }
6460 : :
6461 : : static const char *
1011 peter@eisentraut.org 6462 : 21 : alter_table_type_to_string(AlterTableType cmdtype)
6463 : : {
6464 [ - - + + : 21 : switch (cmdtype)
- - - - +
- - - + -
- + - - -
- - - - -
- - - - -
- - - - -
- - - - -
- - - - -
- - - - -
- - - - -
- + - - -
- - - -
- ]
6465 : : {
1011 peter@eisentraut.org 6466 :UBC 0 : case AT_AddColumn:
6467 : : case AT_AddColumnToView:
6468 : 0 : return "ADD COLUMN";
6469 : 0 : case AT_ColumnDefault:
6470 : : case AT_CookedColumnDefault:
6471 : 0 : return "ALTER COLUMN ... SET DEFAULT";
1011 peter@eisentraut.org 6472 :CBC 3 : case AT_DropNotNull:
6473 : 3 : return "ALTER COLUMN ... DROP NOT NULL";
6474 : 3 : case AT_SetNotNull:
6475 : 3 : return "ALTER COLUMN ... SET NOT NULL";
233 alvherre@alvh.no-ip. 6476 :UNC 0 : case AT_SetAttNotNull:
6477 : 0 : return NULL; /* not real grammar */
101 peter@eisentraut.org 6478 : 0 : case AT_SetExpression:
6479 : 0 : return "ALTER COLUMN ... SET EXPRESSION";
1011 peter@eisentraut.org 6480 :UBC 0 : case AT_DropExpression:
6481 : 0 : return "ALTER COLUMN ... DROP EXPRESSION";
6482 : 0 : case AT_SetStatistics:
6483 : 0 : return "ALTER COLUMN ... SET STATISTICS";
1011 peter@eisentraut.org 6484 :CBC 6 : case AT_SetOptions:
6485 : 6 : return "ALTER COLUMN ... SET";
1011 peter@eisentraut.org 6486 :UBC 0 : case AT_ResetOptions:
6487 : 0 : return "ALTER COLUMN ... RESET";
6488 : 0 : case AT_SetStorage:
6489 : 0 : return "ALTER COLUMN ... SET STORAGE";
6490 : 0 : case AT_SetCompression:
6491 : 0 : return "ALTER COLUMN ... SET COMPRESSION";
1011 peter@eisentraut.org 6492 :CBC 3 : case AT_DropColumn:
6493 : 3 : return "DROP COLUMN";
1011 peter@eisentraut.org 6494 :UBC 0 : case AT_AddIndex:
6495 : : case AT_ReAddIndex:
1004 tgl@sss.pgh.pa.us 6496 : 0 : return NULL; /* not real grammar */
1011 peter@eisentraut.org 6497 : 0 : case AT_AddConstraint:
6498 : : case AT_ReAddConstraint:
6499 : : case AT_ReAddDomainConstraint:
6500 : : case AT_AddIndexConstraint:
6501 : 0 : return "ADD CONSTRAINT";
1011 peter@eisentraut.org 6502 :CBC 3 : case AT_AlterConstraint:
6503 : 3 : return "ALTER CONSTRAINT";
1011 peter@eisentraut.org 6504 :UBC 0 : case AT_ValidateConstraint:
6505 : 0 : return "VALIDATE CONSTRAINT";
6506 : 0 : case AT_DropConstraint:
6507 : 0 : return "DROP CONSTRAINT";
6508 : 0 : case AT_ReAddComment:
1004 tgl@sss.pgh.pa.us 6509 : 0 : return NULL; /* not real grammar */
1011 peter@eisentraut.org 6510 : 0 : case AT_AlterColumnType:
6511 : 0 : return "ALTER COLUMN ... SET DATA TYPE";
6512 : 0 : case AT_AlterColumnGenericOptions:
6513 : 0 : return "ALTER COLUMN ... OPTIONS";
6514 : 0 : case AT_ChangeOwner:
6515 : 0 : return "OWNER TO";
6516 : 0 : case AT_ClusterOn:
6517 : 0 : return "CLUSTER ON";
6518 : 0 : case AT_DropCluster:
6519 : 0 : return "SET WITHOUT CLUSTER";
991 michael@paquier.xyz 6520 : 0 : case AT_SetAccessMethod:
6521 : 0 : return "SET ACCESS METHOD";
1011 peter@eisentraut.org 6522 : 0 : case AT_SetLogged:
6523 : 0 : return "SET LOGGED";
6524 : 0 : case AT_SetUnLogged:
6525 : 0 : return "SET UNLOGGED";
6526 : 0 : case AT_DropOids:
6527 : 0 : return "SET WITHOUT OIDS";
6528 : 0 : case AT_SetTableSpace:
6529 : 0 : return "SET TABLESPACE";
6530 : 0 : case AT_SetRelOptions:
6531 : 0 : return "SET";
6532 : 0 : case AT_ResetRelOptions:
6533 : 0 : return "RESET";
6534 : 0 : case AT_ReplaceRelOptions:
1004 tgl@sss.pgh.pa.us 6535 : 0 : return NULL; /* not real grammar */
1011 peter@eisentraut.org 6536 : 0 : case AT_EnableTrig:
6537 : 0 : return "ENABLE TRIGGER";
6538 : 0 : case AT_EnableAlwaysTrig:
6539 : 0 : return "ENABLE ALWAYS TRIGGER";
6540 : 0 : case AT_EnableReplicaTrig:
6541 : 0 : return "ENABLE REPLICA TRIGGER";
6542 : 0 : case AT_DisableTrig:
6543 : 0 : return "DISABLE TRIGGER";
6544 : 0 : case AT_EnableTrigAll:
6545 : 0 : return "ENABLE TRIGGER ALL";
6546 : 0 : case AT_DisableTrigAll:
6547 : 0 : return "DISABLE TRIGGER ALL";
6548 : 0 : case AT_EnableTrigUser:
6549 : 0 : return "ENABLE TRIGGER USER";
6550 : 0 : case AT_DisableTrigUser:
6551 : 0 : return "DISABLE TRIGGER USER";
6552 : 0 : case AT_EnableRule:
6553 : 0 : return "ENABLE RULE";
6554 : 0 : case AT_EnableAlwaysRule:
6555 : 0 : return "ENABLE ALWAYS RULE";
6556 : 0 : case AT_EnableReplicaRule:
6557 : 0 : return "ENABLE REPLICA RULE";
6558 : 0 : case AT_DisableRule:
6559 : 0 : return "DISABLE RULE";
6560 : 0 : case AT_AddInherit:
6561 : 0 : return "INHERIT";
6562 : 0 : case AT_DropInherit:
6563 : 0 : return "NO INHERIT";
6564 : 0 : case AT_AddOf:
6565 : 0 : return "OF";
6566 : 0 : case AT_DropOf:
6567 : 0 : return "NOT OF";
6568 : 0 : case AT_ReplicaIdentity:
6569 : 0 : return "REPLICA IDENTITY";
6570 : 0 : case AT_EnableRowSecurity:
6571 : 0 : return "ENABLE ROW SECURITY";
6572 : 0 : case AT_DisableRowSecurity:
6573 : 0 : return "DISABLE ROW SECURITY";
6574 : 0 : case AT_ForceRowSecurity:
6575 : 0 : return "FORCE ROW SECURITY";
6576 : 0 : case AT_NoForceRowSecurity:
6577 : 0 : return "NO FORCE ROW SECURITY";
6578 : 0 : case AT_GenericOptions:
6579 : 0 : return "OPTIONS";
6580 : 0 : case AT_AttachPartition:
6581 : 0 : return "ATTACH PARTITION";
1011 peter@eisentraut.org 6582 :CBC 3 : case AT_DetachPartition:
6583 : 3 : return "DETACH PARTITION";
1011 peter@eisentraut.org 6584 :UBC 0 : case AT_DetachPartitionFinalize:
6585 : 0 : return "DETACH PARTITION ... FINALIZE";
7 akorotkov@postgresql 6586 :UNC 0 : case AT_SplitPartition:
6587 : 0 : return "SPLIT PARTITION";
6588 : 0 : case AT_MergePartitions:
6589 : 0 : return "MERGE PARTITIONS";
1011 peter@eisentraut.org 6590 :UBC 0 : case AT_AddIdentity:
6591 : 0 : return "ALTER COLUMN ... ADD IDENTITY";
6592 : 0 : case AT_SetIdentity:
6593 : 0 : return "ALTER COLUMN ... SET";
6594 : 0 : case AT_DropIdentity:
6595 : 0 : return "ALTER COLUMN ... DROP IDENTITY";
6596 : 0 : case AT_ReAddStatistics:
1004 tgl@sss.pgh.pa.us 6597 : 0 : return NULL; /* not real grammar */
6598 : : }
6599 : :
1011 peter@eisentraut.org 6600 : 0 : return NULL;
6601 : : }
6602 : :
6603 : : /*
6604 : : * ATSimplePermissions
6605 : : *
6606 : : * - Ensure that it is a relation (or possibly a view)
6607 : : * - Ensure this user is the owner
6608 : : * - Ensure that it is not a system table
6609 : : */
6610 : : static void
1011 peter@eisentraut.org 6611 :CBC 19752 : ATSimplePermissions(AlterTableType cmdtype, Relation rel, int allowed_targets)
6612 : : {
6613 : : int actual_target;
6614 : :
4852 rhaas@postgresql.org 6615 [ + + + + : 19752 : switch (rel->rd_rel->relkind)
+ + + +
- ]
6616 : : {
6617 : 18655 : case RELKIND_RELATION:
6618 : : case RELKIND_PARTITIONED_TABLE:
6619 : 18655 : actual_target = ATT_TABLE;
6620 : 18655 : break;
6621 : 198 : case RELKIND_VIEW:
6622 : 198 : actual_target = ATT_VIEW;
6623 : 198 : break;
4060 kgrittn@postgresql.o 6624 : 23 : case RELKIND_MATVIEW:
6625 : 23 : actual_target = ATT_MATVIEW;
6626 : 23 : break;
4852 rhaas@postgresql.org 6627 : 113 : case RELKIND_INDEX:
6628 : 113 : actual_target = ATT_INDEX;
6629 : 113 : break;
2277 alvherre@alvh.no-ip. 6630 : 207 : case RELKIND_PARTITIONED_INDEX:
6631 : 207 : actual_target = ATT_PARTITIONED_INDEX;
6632 : 207 : break;
4852 rhaas@postgresql.org 6633 : 107 : case RELKIND_COMPOSITE_TYPE:
6634 : 107 : actual_target = ATT_COMPOSITE_TYPE;
6635 : 107 : break;
6636 : 443 : case RELKIND_FOREIGN_TABLE:
6637 : 443 : actual_target = ATT_FOREIGN_TABLE;
6638 : 443 : break;
738 peter@eisentraut.org 6639 : 6 : case RELKIND_SEQUENCE:
6640 : 6 : actual_target = ATT_SEQUENCE;
6641 : 6 : break;
4852 rhaas@postgresql.org 6642 :UBC 0 : default:
6643 : 0 : actual_target = 0;
6644 : 0 : break;
6645 : : }
6646 : :
6647 : : /* Wrong target type? */
4852 rhaas@postgresql.org 6648 [ + + ]:CBC 19752 : if ((actual_target & allowed_targets) == 0)
6649 : : {
1011 peter@eisentraut.org 6650 : 21 : const char *action_str = alter_table_type_to_string(cmdtype);
6651 : :
6652 [ + - ]: 21 : if (action_str)
6653 [ + - ]: 21 : ereport(ERROR,
6654 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
6655 : : /* translator: %s is a group of some SQL keywords */
6656 : : errmsg("ALTER action %s cannot be performed on relation \"%s\"",
6657 : : action_str, RelationGetRelationName(rel)),
6658 : : errdetail_relkind_not_supported(rel->rd_rel->relkind)));
6659 : : else
6660 : : /* internal error? */
1011 peter@eisentraut.org 6661 [ # # ]:UBC 0 : elog(ERROR, "invalid ALTER action attempted on relation \"%s\"",
6662 : : RelationGetRelationName(rel));
6663 : : }
6664 : :
6665 : : /* Permissions checks */
518 peter@eisentraut.org 6666 [ + + ]:CBC 19731 : if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId()))
2325 peter_e@gmx.net 6667 : 6 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
7562 tgl@sss.pgh.pa.us 6668 : 6 : RelationGetRelationName(rel));
6669 : :
7574 6670 [ + + - + ]: 19725 : if (!allowSystemTableMods && IsSystemRelation(rel))
7574 tgl@sss.pgh.pa.us 6671 [ # # ]:UBC 0 : ereport(ERROR,
6672 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
6673 : : errmsg("permission denied: \"%s\" is a system catalog",
6674 : : RelationGetRelationName(rel))));
7284 tgl@sss.pgh.pa.us 6675 :CBC 19725 : }
6676 : :
6677 : : /*
6678 : : * ATSimpleRecursion
6679 : : *
6680 : : * Simple table recursion sufficient for most ALTER TABLE operations.
6681 : : * All direct and indirect children are processed in an unspecified order.
6682 : : * Note that if a child inherits from the original table via multiple
6683 : : * inheritance paths, it will be visited just once.
6684 : : */
6685 : : static void
6686 : 7678 : ATSimpleRecursion(List **wqueue, Relation rel,
6687 : : AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
6688 : : AlterTableUtilityContext *context)
6689 : : {
6690 : : /*
6691 : : * Propagate to children, if desired and if there are (or might be) any
6692 : : * children.
6693 : : */
1306 6694 [ + + + + ]: 7678 : if (recurse && rel->rd_rel->relhassubclass)
6695 : : {
7284 6696 : 71 : Oid relid = RelationGetRelid(rel);
6697 : : ListCell *child;
6698 : : List *children;
6699 : :
5009 simon@2ndQuadrant.co 6700 : 71 : children = find_all_inheritors(relid, lockmode, NULL);
6701 : :
6702 : : /*
6703 : : * find_all_inheritors does the recursive search of the inheritance
6704 : : * hierarchy, so all we have to do is process all of the relids in the
6705 : : * list that it returns.
6706 : : */
8024 tgl@sss.pgh.pa.us 6707 [ + - + + : 297 : foreach(child, children)
+ + ]
6708 : : {
7263 neilc@samurai.com 6709 : 226 : Oid childrelid = lfirst_oid(child);
6710 : : Relation childrel;
6711 : :
7284 tgl@sss.pgh.pa.us 6712 [ + + ]: 226 : if (childrelid == relid)
8024 6713 : 71 : continue;
6714 : : /* find_all_inheritors already got lock */
5451 6715 : 155 : childrel = relation_open(childrelid, NoLock);
5919 6716 : 155 : CheckTableNotInUse(childrel, "ALTER TABLE");
1551 6717 : 155 : ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, context);
7284 6718 : 155 : relation_close(childrel, NoLock);
6719 : : }
6720 : : }
6721 : 7678 : }
6722 : :
6723 : : /*
6724 : : * Obtain list of partitions of the given table, locking them all at the given
6725 : : * lockmode and ensuring that they all pass CheckTableNotInUse.
6726 : : *
6727 : : * This function is a no-op if the given relation is not a partitioned table;
6728 : : * in particular, nothing is done if it's a legacy inheritance parent.
6729 : : */
6730 : : static void
1727 alvherre@alvh.no-ip. 6731 : 473 : ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode)
6732 : : {
6733 [ + + ]: 473 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
6734 : : {
6735 : : List *inh;
6736 : : ListCell *cell;
6737 : :
6738 : 86 : inh = find_all_inheritors(RelationGetRelid(rel), lockmode, NULL);
6739 : : /* first element is the parent rel; must ignore it */
1294 tgl@sss.pgh.pa.us 6740 [ + - + + : 294 : for_each_from(cell, inh, 1)
+ + ]
6741 : : {
6742 : : Relation childrel;
6743 : :
6744 : : /* find_all_inheritors already got lock */
1727 alvherre@alvh.no-ip. 6745 : 211 : childrel = table_open(lfirst_oid(cell), NoLock);
6746 : 211 : CheckTableNotInUse(childrel, "ALTER TABLE");
6747 : 208 : table_close(childrel, NoLock);
6748 : : }
6749 : 83 : list_free(inh);
6750 : : }
6751 : 470 : }
6752 : :
6753 : : /*
6754 : : * ATTypedTableRecursion
6755 : : *
6756 : : * Propagate ALTER TYPE operations to the typed tables of that type.
6757 : : * Also check the RESTRICT/CASCADE behavior. Given CASCADE, also permit
6758 : : * recursion to inheritance children of the typed tables.
6759 : : */
6760 : : static void
4891 peter_e@gmx.net 6761 : 95 : ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
6762 : : LOCKMODE lockmode, AlterTableUtilityContext *context)
6763 : : {
6764 : : ListCell *child;
6765 : : List *children;
6766 : :
6767 [ - + ]: 95 : Assert(rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE);
6768 : :
6769 : 95 : children = find_typed_table_dependencies(rel->rd_rel->reltype,
6770 : 95 : RelationGetRelationName(rel),
6771 : : cmd->behavior);
6772 : :
6773 [ + + + + : 101 : foreach(child, children)
+ + ]
6774 : : {
6775 : 15 : Oid childrelid = lfirst_oid(child);
6776 : : Relation childrel;
6777 : :
6778 : 15 : childrel = relation_open(childrelid, lockmode);
6779 : 15 : CheckTableNotInUse(childrel, "ALTER TABLE");
1551 tgl@sss.pgh.pa.us 6780 : 15 : ATPrepCmd(wqueue, childrel, cmd, true, true, lockmode, context);
4891 peter_e@gmx.net 6781 : 15 : relation_close(childrel, NoLock);
6782 : : }
6783 : 86 : }
6784 : :
6785 : :
6786 : : /*
6787 : : * find_composite_type_dependencies
6788 : : *
6789 : : * Check to see if the type "typeOid" is being used as a column in some table
6790 : : * (possibly nested several levels deep in composite types, arrays, etc!).
6791 : : * Eventually, we'd like to propagate the check or rewrite operation
6792 : : * into such tables, but for now, just error out if we find any.
6793 : : *
6794 : : * Caller should provide either the associated relation of a rowtype,
6795 : : * or a type name (not both) for use in the error message, if any.
6796 : : *
6797 : : * Note that "typeOid" is not necessarily a composite type; it could also be
6798 : : * another container type such as an array or range, or a domain over one of
6799 : : * these things. The name of this function is therefore somewhat historical,
6800 : : * but it's not worth changing.
6801 : : *
6802 : : * We assume that functions and views depending on the type are not reasons
6803 : : * to reject the ALTER. (How safe is this really?)
6804 : : */
6805 : : void
4811 rhaas@postgresql.org 6806 : 1928 : find_composite_type_dependencies(Oid typeOid, Relation origRelation,
6807 : : const char *origTypeName)
6808 : : {
6809 : : Relation depRel;
6810 : : ScanKeyData key[2];
6811 : : SysScanDesc depScan;
6812 : : HeapTuple depTup;
6813 : :
6814 : : /* since this function recurses, it could be driven to stack overflow */
2440 tgl@sss.pgh.pa.us 6815 : 1928 : check_stack_depth();
6816 : :
6817 : : /*
6818 : : * We scan pg_depend to find those things that depend on the given type.
6819 : : * (We assume we can ignore refobjsubid for a type.)
6820 : : */
1910 andres@anarazel.de 6821 : 1928 : depRel = table_open(DependRelationId, AccessShareLock);
6822 : :
7252 tgl@sss.pgh.pa.us 6823 : 1928 : ScanKeyInit(&key[0],
6824 : : Anum_pg_depend_refclassid,
6825 : : BTEqualStrategyNumber, F_OIDEQ,
6826 : : ObjectIdGetDatum(TypeRelationId));
6827 : 1928 : ScanKeyInit(&key[1],
6828 : : Anum_pg_depend_refobjid,
6829 : : BTEqualStrategyNumber, F_OIDEQ,
6830 : : ObjectIdGetDatum(typeOid));
6831 : :
6940 6832 : 1928 : depScan = systable_beginscan(depRel, DependReferenceIndexId, true,
6833 : : NULL, 2, key);
6834 : :
7252 6835 [ + + ]: 2980 : while (HeapTupleIsValid(depTup = systable_getnext(depScan)))
6836 : : {
6837 : 1100 : Form_pg_depend pg_depend = (Form_pg_depend) GETSTRUCT(depTup);
6838 : : Relation rel;
6839 : : TupleDesc tupleDesc;
6840 : : Form_pg_attribute att;
6841 : :
6842 : : /* Check for directly dependent types */
2440 6843 [ + + ]: 1100 : if (pg_depend->classid == TypeRelationId)
6844 : : {
6845 : : /*
6846 : : * This must be an array, domain, or range containing the given
6847 : : * type, so recursively check for uses of this type. Note that
6848 : : * any error message will mention the original type not the
6849 : : * container; this is intentional.
6850 : : */
6851 : 927 : find_composite_type_dependencies(pg_depend->objid,
6852 : : origRelation, origTypeName);
6853 : 915 : continue;
6854 : : }
6855 : :
6856 : : /* Else, ignore dependees that aren't relations */
384 6857 [ + + ]: 173 : if (pg_depend->classid != RelationRelationId)
7252 6858 : 61 : continue;
6859 : :
6860 : 112 : rel = relation_open(pg_depend->objid, AccessShareLock);
384 6861 : 112 : tupleDesc = RelationGetDescr(rel);
6862 : :
6863 : : /*
6864 : : * If objsubid identifies a specific column, refer to that in error
6865 : : * messages. Otherwise, search to see if there's a user column of the
6866 : : * type. (We assume system columns are never of interesting types.)
6867 : : * The search is needed because an index containing an expression
6868 : : * column of the target type will just be recorded as a whole-relation
6869 : : * dependency. If we do not find a column of the type, the dependency
6870 : : * must indicate that the type is transiently referenced in an index
6871 : : * expression but not stored on disk, which we assume is OK, just as
6872 : : * we do for references in views. (It could also be that the target
6873 : : * type is embedded in some container type that is stored in an index
6874 : : * column, but the previous recursion should catch such cases.)
6875 : : */
6876 [ + + + - ]: 112 : if (pg_depend->objsubid > 0 && pg_depend->objsubid <= tupleDesc->natts)
6877 : 33 : att = TupleDescAttr(tupleDesc, pg_depend->objsubid - 1);
6878 : : else
6879 : : {
6880 : 79 : att = NULL;
6881 [ + + ]: 203 : for (int attno = 1; attno <= tupleDesc->natts; attno++)
6882 : : {
6883 : 127 : att = TupleDescAttr(tupleDesc, attno - 1);
6884 [ + + + - ]: 127 : if (att->atttypid == typeOid && !att->attisdropped)
6885 : 3 : break;
6886 : 124 : att = NULL;
6887 : : }
6888 [ + + ]: 79 : if (att == NULL)
6889 : : {
6890 : : /* No such column, so assume OK */
6891 : 76 : relation_close(rel, AccessShareLock);
6892 : 76 : continue;
6893 : : }
6894 : : }
6895 : :
6896 : : /*
6897 : : * We definitely should reject if the relation has storage. If it's
6898 : : * partitioned, then perhaps we don't have to reject: if there are
6899 : : * partitions then we'll fail when we find one, else there is no
6900 : : * stored data to worry about. However, it's possible that the type
6901 : : * change would affect conclusions about whether the type is sortable
6902 : : * or hashable and thus (if it's a partitioning column) break the
6903 : : * partitioning rule. For now, reject for partitioned rels too.
6904 : : */
6905 [ + + - + : 36 : if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind) ||
- - - - -
- ]
384 tgl@sss.pgh.pa.us 6906 [ # # # # ]:UBC 0 : RELKIND_HAS_PARTITIONS(rel->rd_rel->relkind))
6907 : : {
4765 tgl@sss.pgh.pa.us 6908 [ + + ]:CBC 36 : if (origTypeName)
6909 [ + - ]: 15 : ereport(ERROR,
6910 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
6911 : : errmsg("cannot alter type \"%s\" because column \"%s.%s\" uses it",
6912 : : origTypeName,
6913 : : RelationGetRelationName(rel),
6914 : : NameStr(att->attname))));
6915 [ + + ]: 21 : else if (origRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
6916 [ + - ]: 9 : ereport(ERROR,
6917 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
6918 : : errmsg("cannot alter type \"%s\" because column \"%s.%s\" uses it",
6919 : : RelationGetRelationName(origRelation),
6920 : : RelationGetRelationName(rel),
6921 : : NameStr(att->attname))));
4811 rhaas@postgresql.org 6922 [ + + ]: 12 : else if (origRelation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
4765 tgl@sss.pgh.pa.us 6923 [ + - ]: 3 : ereport(ERROR,
6924 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
6925 : : errmsg("cannot alter foreign table \"%s\" because column \"%s.%s\" uses its row type",
6926 : : RelationGetRelationName(origRelation),
6927 : : RelationGetRelationName(rel),
6928 : : NameStr(att->attname))));
6929 : : else
6930 [ + - ]: 9 : ereport(ERROR,
6931 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
6932 : : errmsg("cannot alter table \"%s\" because column \"%s.%s\" uses its row type",
6933 : : RelationGetRelationName(origRelation),
6934 : : RelationGetRelationName(rel),
6935 : : NameStr(att->attname))));
6936 : : }
7252 tgl@sss.pgh.pa.us 6937 [ # # ]:UBC 0 : else if (OidIsValid(rel->rd_rel->reltype))
6938 : : {
6939 : : /*
6940 : : * A view or composite type itself isn't a problem, but we must
6941 : : * recursively check for indirect dependencies via its rowtype.
6942 : : */
6943 : 0 : find_composite_type_dependencies(rel->rd_rel->reltype,
6944 : : origRelation, origTypeName);
6945 : : }
6946 : :
6947 : 0 : relation_close(rel, AccessShareLock);
6948 : : }
6949 : :
7252 tgl@sss.pgh.pa.us 6950 :CBC 1880 : systable_endscan(depScan);
6951 : :
6952 : 1880 : relation_close(depRel, AccessShareLock);
6953 : 1880 : }
6954 : :
6955 : :
6956 : : /*
6957 : : * find_typed_table_dependencies
6958 : : *
6959 : : * Check to see if a composite type is being used as the type of a
6960 : : * typed table. Abort if any are found and behavior is RESTRICT.
6961 : : * Else return the list of tables.
6962 : : */
6963 : : static List *
4891 peter_e@gmx.net 6964 : 107 : find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior behavior)
6965 : : {
6966 : : Relation classRel;
6967 : : ScanKeyData key[1];
6968 : : TableScanDesc scan;
6969 : : HeapTuple tuple;
6970 : 107 : List *result = NIL;
6971 : :
1910 andres@anarazel.de 6972 : 107 : classRel = table_open(RelationRelationId, AccessShareLock);
6973 : :
4949 peter_e@gmx.net 6974 : 107 : ScanKeyInit(&key[0],
6975 : : Anum_pg_class_reloftype,
6976 : : BTEqualStrategyNumber, F_OIDEQ,
6977 : : ObjectIdGetDatum(typeOid));
6978 : :
1861 andres@anarazel.de 6979 : 107 : scan = table_beginscan_catalog(classRel, 1, key);
6980 : :
4755 rhaas@postgresql.org 6981 [ + + ]: 125 : while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
6982 : : {
1972 andres@anarazel.de 6983 : 30 : Form_pg_class classform = (Form_pg_class) GETSTRUCT(tuple);
6984 : :
4891 peter_e@gmx.net 6985 [ + + ]: 30 : if (behavior == DROP_RESTRICT)
6986 [ + - ]: 12 : ereport(ERROR,
6987 : : (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
6988 : : errmsg("cannot alter type \"%s\" because it is the type of a typed table",
6989 : : typeName),
6990 : : errhint("Use ALTER ... CASCADE to alter the typed tables too.")));
6991 : : else
1972 andres@anarazel.de 6992 : 18 : result = lappend_oid(result, classform->oid);
6993 : : }
6994 : :
1861 6995 : 95 : table_endscan(scan);
1910 6996 : 95 : table_close(classRel, AccessShareLock);
6997 : :
4891 peter_e@gmx.net 6998 : 95 : return result;
6999 : : }
7000 : :
7001 : :
7002 : : /*
7003 : : * check_of_type
7004 : : *
7005 : : * Check whether a type is suitable for CREATE TABLE OF/ALTER TABLE OF. If it
7006 : : * isn't suitable, throw an error. Currently, we require that the type
7007 : : * originated with CREATE TYPE AS. We could support any row type, but doing so
7008 : : * would require handling a number of extra corner cases in the DDL commands.
7009 : : * (Also, allowing domain-over-composite would open up a can of worms about
7010 : : * whether and how the domain's constraints should apply to derived tables.)
7011 : : */
7012 : : void
4743 rhaas@postgresql.org 7013 : 85 : check_of_type(HeapTuple typetuple)
7014 : : {
7015 : 85 : Form_pg_type typ = (Form_pg_type) GETSTRUCT(typetuple);
7016 : 85 : bool typeOk = false;
7017 : :
7018 [ + - ]: 85 : if (typ->typtype == TYPTYPE_COMPOSITE)
7019 : : {
7020 : : Relation typeRelation;
7021 : :
7022 [ - + ]: 85 : Assert(OidIsValid(typ->typrelid));
7023 : 85 : typeRelation = relation_open(typ->typrelid, AccessShareLock);
7024 : 85 : typeOk = (typeRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE);
7025 : :
7026 : : /*
7027 : : * Close the parent rel, but keep our AccessShareLock on it until xact
7028 : : * commit. That will prevent someone else from deleting or ALTERing
7029 : : * the type before the typed table creation/conversion commits.
7030 : : */
7031 : 85 : relation_close(typeRelation, NoLock);
7032 : : }
7033 [ + + ]: 85 : if (!typeOk)
7034 [ + - ]: 3 : ereport(ERROR,
7035 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
7036 : : errmsg("type %s is not a composite type",
7037 : : format_type_be(typ->oid))));
7038 : 82 : }
7039 : :
7040 : :
7041 : : /*
7042 : : * ALTER TABLE ADD COLUMN
7043 : : *
7044 : : * Adds an additional attribute to a relation making the assumption that
7045 : : * CHECK, NOT NULL, and FOREIGN KEY constraints will be removed from the
7046 : : * AT_AddColumn AlterTableCmd by parse_utilcmd.c and added as independent
7047 : : * AlterTableCmd's.
7048 : : *
7049 : : * ADD COLUMN cannot use the normal ALTER TABLE recursion mechanism, because we
7050 : : * have to decide at runtime whether to recurse or not depending on whether we
7051 : : * actually add a column or merely merge with an existing column. (We can't
7052 : : * check this in a static pre-pass because it won't handle multiple inheritance
7053 : : * situations correctly.)
7054 : : */
7055 : : static void
4891 peter_e@gmx.net 7056 : 990 : ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
7057 : : bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode,
7058 : : AlterTableUtilityContext *context)
7059 : : {
7060 [ + + + + ]: 990 : if (rel->rd_rel->reloftype && !recursing)
5014 7061 [ + - ]: 3 : ereport(ERROR,
7062 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
7063 : : errmsg("cannot add column to typed table")));
7064 : :
4949 7065 [ + + ]: 987 : if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
1551 tgl@sss.pgh.pa.us 7066 : 29 : ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);
7067 : :
3334 alvherre@alvh.no-ip. 7068 [ + + + + ]: 984 : if (recurse && !is_view)
489 7069 : 934 : cmd->recurse = true;
7284 tgl@sss.pgh.pa.us 7070 : 984 : }
7071 : :
7072 : : /*
7073 : : * Add a column to a table. The return value is the address of the
7074 : : * new column in the parent relation.
7075 : : *
7076 : : * cmd is pass-by-ref so that we can replace it with the parse-transformed
7077 : : * copy (but that happens only after we check for IF NOT EXISTS).
7078 : : */
7079 : : static ObjectAddress
4760 rhaas@postgresql.org 7080 : 1293 : ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
7081 : : AlterTableCmd **cmd, bool recurse, bool recursing,
7082 : : LOCKMODE lockmode, AlterTablePass cur_pass,
7083 : : AlterTableUtilityContext *context)
7084 : : {
7284 tgl@sss.pgh.pa.us 7085 : 1293 : Oid myrelid = RelationGetRelid(rel);
1551 7086 : 1293 : ColumnDef *colDef = castNode(ColumnDef, (*cmd)->def);
7087 : 1293 : bool if_not_exists = (*cmd)->missing_ok;
7088 : : Relation pgclass,
7089 : : attrdesc;
7090 : : HeapTuple reltup;
7091 : : Form_pg_attribute attribute;
7092 : : int newattnum;
7093 : : char relkind;
7094 : : Expr *defval;
7095 : : List *children;
7096 : : ListCell *child;
7097 : : AlterTableCmd *childcmd;
7098 : : ObjectAddress address;
7099 : : TupleDesc tupdesc;
7100 : :
7101 : : /* since this function recurses, it could be driven to stack overflow */
58 akorotkov@postgresql 7102 : 1293 : check_stack_depth();
7103 : :
7104 : : /* At top level, permission check was done in ATPrepCmd, else do it */
4760 rhaas@postgresql.org 7105 [ + + ]: 1293 : if (recursing)
1011 peter@eisentraut.org 7106 : 312 : ATSimplePermissions((*cmd)->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
7107 : :
2685 rhaas@postgresql.org 7108 [ + + + + ]: 1293 : if (rel->rd_rel->relispartition && !recursing)
7109 [ + - ]: 6 : ereport(ERROR,
7110 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
7111 : : errmsg("cannot add column to a partition")));
7112 : :
1910 andres@anarazel.de 7113 : 1287 : attrdesc = table_open(AttributeRelationId, RowExclusiveLock);
7114 : :
7115 : : /*
7116 : : * Are we adding the column to a recursion child? If so, check whether to
7117 : : * merge with an existing definition for the column. If we do merge, we
7118 : : * must not recurse. Children will already have the column, and recursing
7119 : : * into them would mess up attinhcount.
7120 : : */
7284 tgl@sss.pgh.pa.us 7121 [ + + ]: 1287 : if (colDef->inhcount > 0)
7122 : : {
7123 : : HeapTuple tuple;
7124 : :
7125 : : /* Does child already have a column by this name? */
7126 : 312 : tuple = SearchSysCacheCopyAttName(myrelid, colDef->colname);
7127 [ + + ]: 312 : if (HeapTupleIsValid(tuple))
7128 : : {
7129 : 18 : Form_pg_attribute childatt = (Form_pg_attribute) GETSTRUCT(tuple);
7130 : : Oid ctypeId;
7131 : : int32 ctypmod;
7132 : : Oid ccollid;
7133 : :
7134 : : /* Child column must match on type, typmod, and collation */
4785 7135 : 18 : typenameTypeIdAndMod(NULL, colDef->typeName, &ctypeId, &ctypmod);
6315 7136 [ + - ]: 18 : if (ctypeId != childatt->atttypid ||
7137 [ - + ]: 18 : ctypmod != childatt->atttypmod)
7284 tgl@sss.pgh.pa.us 7138 [ # # ]:UBC 0 : ereport(ERROR,
7139 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
7140 : : errmsg("child table \"%s\" has different type for column \"%s\"",
7141 : : RelationGetRelationName(rel), colDef->colname)));
4785 tgl@sss.pgh.pa.us 7142 :CBC 18 : ccollid = GetColumnDefCollation(NULL, colDef, ctypeId);
4814 peter_e@gmx.net 7143 [ - + ]: 18 : if (ccollid != childatt->attcollation)
4814 peter_e@gmx.net 7144 [ # # ]:UBC 0 : ereport(ERROR,
7145 : : (errcode(ERRCODE_COLLATION_MISMATCH),
7146 : : errmsg("child table \"%s\" has different collation for column \"%s\"",
7147 : : RelationGetRelationName(rel), colDef->colname),
7148 : : errdetail("\"%s\" versus \"%s\"",
7149 : : get_collation_name(ccollid),
7150 : : get_collation_name(childatt->attcollation))));
7151 : :
7152 : : /* Bump the existing child att's inhcount */
7284 tgl@sss.pgh.pa.us 7153 :CBC 18 : childatt->attinhcount++;
383 peter@eisentraut.org 7154 [ - + ]: 18 : if (childatt->attinhcount < 0)
383 peter@eisentraut.org 7155 [ # # ]:UBC 0 : ereport(ERROR,
7156 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
7157 : : errmsg("too many inheritance parents"));
2630 alvherre@alvh.no-ip. 7158 :CBC 18 : CatalogTupleUpdate(attrdesc, &tuple->t_self, tuple);
7159 : :
7284 tgl@sss.pgh.pa.us 7160 : 18 : heap_freetuple(tuple);
7161 : :
7162 : : /* Inform the user about the merge */
7163 [ + - ]: 18 : ereport(NOTICE,
7164 : : (errmsg("merging definition of column \"%s\" for child \"%s\"",
7165 : : colDef->colname, RelationGetRelationName(rel))));
7166 : :
1910 andres@anarazel.de 7167 : 18 : table_close(attrdesc, RowExclusiveLock);
7168 : :
7169 : : /* Make the child column change visible */
81 michael@paquier.xyz 7170 : 18 : CommandCounterIncrement();
7171 : :
3308 alvherre@alvh.no-ip. 7172 : 18 : return InvalidObjectAddress;
7173 : : }
7174 : : }
7175 : :
7176 : : /* skip if the name already exists and if_not_exists is true */
1551 tgl@sss.pgh.pa.us 7177 [ + + ]: 1269 : if (!check_for_column_name_collision(rel, colDef->colname, if_not_exists))
7178 : : {
7179 : 27 : table_close(attrdesc, RowExclusiveLock);
7180 : 27 : return InvalidObjectAddress;
7181 : : }
7182 : :
7183 : : /*
7184 : : * Okay, we need to add the column, so go ahead and do parse
7185 : : * transformation. This can result in queueing up, or even immediately
7186 : : * executing, subsidiary operations (such as creation of unique indexes);
7187 : : * so we mustn't do it until we have made the if_not_exists check.
7188 : : *
7189 : : * When recursing, the command was already transformed and we needn't do
7190 : : * so again. Also, if context isn't given we can't transform. (That
7191 : : * currently happens only for AT_AddColumnToView; we expect that view.c
7192 : : * passed us a ColumnDef that doesn't need work.)
7193 : : */
7194 [ + + + + ]: 1227 : if (context != NULL && !recursing)
7195 : : {
7196 : 921 : *cmd = ATParseTransformCmd(wqueue, tab, rel, *cmd, recurse, lockmode,
7197 : : cur_pass, context);
7198 [ - + ]: 921 : Assert(*cmd != NULL);
7199 : 921 : colDef = castNode(ColumnDef, (*cmd)->def);
7200 : : }
7201 : :
7202 : : /*
7203 : : * Regular inheritance children are independent enough not to inherit the
7204 : : * identity column from parent hence cannot recursively add identity
7205 : : * column if the table has inheritance children.
7206 : : *
7207 : : * Partitions, on the other hand, are integral part of a partitioned table
7208 : : * and inherit identity column. Hence propagate identity column down the
7209 : : * partition hierarchy.
7210 : : */
2565 peter_e@gmx.net 7211 [ + + + - ]: 1227 : if (colDef->identity &&
7212 [ + + ]: 27 : recurse &&
89 peter@eisentraut.org 7213 [ + + + + ]:GNC 51 : rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
1082 alvherre@alvh.no-ip. 7214 :CBC 24 : find_inheritance_children(myrelid, NoLock) != NIL)
2565 peter_e@gmx.net 7215 [ + - ]: 3 : ereport(ERROR,
7216 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
7217 : : errmsg("cannot recursively add identity column to table that has child tables")));
7218 : :
1551 tgl@sss.pgh.pa.us 7219 : 1224 : pgclass = table_open(RelationRelationId, RowExclusiveLock);
7220 : :
7221 : 1224 : reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
7222 [ - + ]: 1224 : if (!HeapTupleIsValid(reltup))
1551 tgl@sss.pgh.pa.us 7223 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", myrelid);
1551 tgl@sss.pgh.pa.us 7224 :CBC 1224 : relkind = ((Form_pg_class) GETSTRUCT(reltup))->relkind;
7225 : :
7226 : : /* Determine the new attribute's number */
1972 andres@anarazel.de 7227 : 1224 : newattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts + 1;
7228 [ - + ]: 1224 : if (newattnum > MaxHeapAttributeNumber)
1972 andres@anarazel.de 7229 [ # # ]:UBC 0 : ereport(ERROR,
7230 : : (errcode(ERRCODE_TOO_MANY_COLUMNS),
7231 : : errmsg("tables can have at most %d columns",
7232 : : MaxHeapAttributeNumber)));
7233 : :
7234 : : /*
7235 : : * Construct new attribute's pg_attribute entry.
7236 : : */
93 peter@eisentraut.org 7237 :GNC 1224 : tupdesc = BuildDescForRelation(list_make1(colDef));
7238 : :
7239 : 1218 : attribute = TupleDescAttr(tupdesc, 0);
7240 : :
7241 : : /* Fix up attribute number */
7242 : 1218 : attribute->attnum = newattnum;
7243 : :
7244 : : /* make sure datatype is legal for a column */
7245 : 1218 : CheckAttributeType(NameStr(attribute->attname), attribute->atttypid, attribute->attcollation,
4766 tgl@sss.pgh.pa.us 7246 :CBC 1218 : list_make1_oid(rel->rd_rel->reltype),
7247 : : 0);
7248 : :
1353 michael@paquier.xyz 7249 : 1203 : InsertPgAttributeTuples(attrdesc, tupdesc, myrelid, NULL, NULL);
7250 : :
1910 andres@anarazel.de 7251 : 1203 : table_close(attrdesc, RowExclusiveLock);
7252 : :
7253 : : /*
7254 : : * Update pg_class tuple as appropriate
7255 : : */
1972 7256 : 1203 : ((Form_pg_class) GETSTRUCT(reltup))->relnatts = newattnum;
7257 : :
2630 alvherre@alvh.no-ip. 7258 : 1203 : CatalogTupleUpdate(pgclass, &reltup->t_self, reltup);
7259 : :
7284 tgl@sss.pgh.pa.us 7260 : 1203 : heap_freetuple(reltup);
7261 : :
7262 : : /* Post creation hook for new attribute */
4057 rhaas@postgresql.org 7263 [ - + ]: 1203 : InvokeObjectPostCreateHook(RelationRelationId, myrelid, newattnum);
7264 : :
1910 andres@anarazel.de 7265 : 1203 : table_close(pgclass, RowExclusiveLock);
7266 : :
7267 : : /* Make the attribute's catalog entry visible */
7284 tgl@sss.pgh.pa.us 7268 : 1203 : CommandCounterIncrement();
7269 : :
7270 : : /*
7271 : : * Store the DEFAULT, if any, in the catalogs
7272 : : */
7273 [ + + ]: 1203 : if (colDef->raw_default)
7274 : : {
7275 : : RawColumnDefault *rawEnt;
7276 : :
8024 7277 : 350 : rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
93 peter@eisentraut.org 7278 :GNC 350 : rawEnt->attnum = attribute->attnum;
7284 tgl@sss.pgh.pa.us 7279 :CBC 350 : rawEnt->raw_default = copyObject(colDef->raw_default);
7280 : :
7281 : : /*
7282 : : * Attempt to skip a complete table rewrite by storing the specified
7283 : : * DEFAULT value outside of the heap. This may be disabled inside
7284 : : * AddRelationNewConstraints if the optimization cannot be applied.
7285 : : */
1842 peter@eisentraut.org 7286 : 350 : rawEnt->missingMode = (!colDef->generated);
7287 : :
7288 : 350 : rawEnt->generated = colDef->generated;
7289 : :
7290 : : /*
7291 : : * This function is intended for CREATE TABLE, so it processes a
7292 : : * _list_ of defaults, but we just do one.
7293 : : */
4046 rhaas@postgresql.org 7294 : 350 : AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
7295 : : false, true, false, NULL);
7296 : :
7297 : : /* Make the additional catalog changes visible */
7284 tgl@sss.pgh.pa.us 7298 : 344 : CommandCounterIncrement();
7299 : :
7300 : : /*
7301 : : * Did the request for a missing value work? If not we'll have to do a
7302 : : * rewrite
7303 : : */
2209 andrew@dunslane.net 7304 [ + + ]: 344 : if (!rawEnt->missingMode)
7305 : 54 : tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
7306 : : }
7307 : :
7308 : : /*
7309 : : * Tell Phase 3 to fill in the default expression, if there is one.
7310 : : *
7311 : : * If there is no default, Phase 3 doesn't have to do anything, because
7312 : : * that effectively means that the default is NULL. The heap tuple access
7313 : : * routines always check for attnum > # of attributes in tuple, and return
7314 : : * NULL if so, so without any modification of the tuple data we will get
7315 : : * the effect of NULL values in the new column.
7316 : : *
7317 : : * An exception occurs when the new column is of a domain type: the domain
7318 : : * might have a not-null constraint, or a check constraint that indirectly
7319 : : * rejects nulls. If there are any domain constraints then we construct
7320 : : * an explicit NULL default value that will be passed through
7321 : : * CoerceToDomain processing. (This is a tad inefficient, since it causes
7322 : : * rewriting the table which we really don't have to do, but the present
7323 : : * design of domain processing doesn't offer any simple way of checking
7324 : : * the constraints more directly.)
7325 : : *
7326 : : * Note: we use build_column_default, and not just the cooked default
7327 : : * returned by AddRelationNewConstraints, so that the right thing happens
7328 : : * when a datatype's default applies.
7329 : : *
7330 : : * Note: it might seem that this should happen at the end of Phase 2, so
7331 : : * that the effects of subsequent subcommands can be taken into account.
7332 : : * It's intentional that we do it now, though. The new column should be
7333 : : * filled according to what is said in the ADD COLUMN subcommand, so that
7334 : : * the effects are the same as if this subcommand had been run by itself
7335 : : * and the later subcommands had been issued in new ALTER TABLE commands.
7336 : : *
7337 : : * We can skip this entirely for relations without storage, since Phase 3
7338 : : * is certainly not going to touch them. System attributes don't have
7339 : : * interesting defaults, either.
7340 : : */
277 peter@eisentraut.org 7341 [ + + + - :GNC 1197 : if (RELKIND_HAS_STORAGE(relkind))
+ - + - -
+ ]
7342 : : {
7343 : : /*
7344 : : * For an identity column, we can't use build_column_default(),
7345 : : * because the sequence ownership isn't set yet. So do it manually.
7346 : : */
2263 peter_e@gmx.net 7347 [ + + ]:CBC 1021 : if (colDef->identity)
7348 : : {
7349 : 21 : NextValueExpr *nve = makeNode(NextValueExpr);
7350 : :
7351 : 21 : nve->seqid = RangeVarGetRelid(colDef->identitySequence, NoLock, false);
93 peter@eisentraut.org 7352 :GNC 21 : nve->typeId = attribute->atttypid;
7353 : :
2263 peter_e@gmx.net 7354 :CBC 21 : defval = (Expr *) nve;
7355 : :
7356 : : /* must do a rewrite for identity columns */
2209 andrew@dunslane.net 7357 : 21 : tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
7358 : : }
7359 : : else
93 peter@eisentraut.org 7360 :GNC 1000 : defval = (Expr *) build_column_default(rel, attribute->attnum);
7361 : :
7362 [ + + + + ]: 1021 : if (!defval && DomainHasConstraints(attribute->atttypid))
7363 : : {
7364 : : Oid baseTypeId;
7365 : : int32 baseTypeMod;
7366 : : Oid baseTypeColl;
7367 : :
7368 : 3 : baseTypeMod = attribute->atttypmod;
7369 : 3 : baseTypeId = getBaseTypeAndTypmod(attribute->atttypid, &baseTypeMod);
4769 tgl@sss.pgh.pa.us 7370 :CBC 3 : baseTypeColl = get_typcollation(baseTypeId);
7371 : 3 : defval = (Expr *) makeNullConst(baseTypeId, baseTypeMod, baseTypeColl);
5608 bruce@momjian.us 7372 : 3 : defval = (Expr *) coerce_to_target_type(NULL,
7373 : : (Node *) defval,
7374 : : baseTypeId,
7375 : : attribute->atttypid,
7376 : : attribute->atttypmod,
7377 : : COERCION_ASSIGNMENT,
7378 : : COERCE_IMPLICIT_CAST,
7379 : : -1);
5421 7380 [ - + ]: 3 : if (defval == NULL) /* should not happen */
5608 bruce@momjian.us 7381 [ # # ]:UBC 0 : elog(ERROR, "failed to coerce base type to domain");
7382 : : }
7383 : :
5608 bruce@momjian.us 7384 [ + + ]:CBC 1021 : if (defval)
7385 : : {
7386 : : NewColumnValue *newval;
7387 : :
7388 : 303 : newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
93 peter@eisentraut.org 7389 :GNC 303 : newval->attnum = attribute->attnum;
4681 rhaas@postgresql.org 7390 :CBC 303 : newval->expr = expression_planner(defval);
1558 tgl@sss.pgh.pa.us 7391 : 303 : newval->is_generated = (colDef->generated != '\0');
7392 : :
5608 bruce@momjian.us 7393 : 303 : tab->newvals = lappend(tab->newvals, newval);
7394 : : }
7395 : :
93 peter@eisentraut.org 7396 [ + + ]:GNC 1021 : if (DomainHasConstraints(attribute->atttypid))
2209 andrew@dunslane.net 7397 :CBC 6 : tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
7398 : :
93 peter@eisentraut.org 7399 [ + + ]:GNC 1021 : if (!TupleDescAttr(rel->rd_att, attribute->attnum - 1)->atthasmissing)
7400 : : {
7401 : : /*
7402 : : * If the new column is NOT NULL, and there is no missing value,
7403 : : * tell Phase 3 it needs to check for NULLs.
7404 : : */
1859 rhaas@postgresql.org 7405 :CBC 796 : tab->verify_new_notnull |= colDef->is_not_null;
7406 : : }
7407 : : }
7408 : :
7409 : : /*
7410 : : * Add needed dependency entries for the new column.
7411 : : */
93 peter@eisentraut.org 7412 :GNC 1197 : add_column_datatype_dependency(myrelid, newattnum, attribute->atttypid);
7413 : 1197 : add_column_collation_dependency(myrelid, newattnum, attribute->attcollation);
7414 : :
7415 : : /*
7416 : : * Propagate to children as appropriate. Unlike most other ALTER
7417 : : * routines, we have to do this one level of recursion at a time; we can't
7418 : : * use find_all_inheritors to do it in one pass.
7419 : : */
7420 : : children =
1082 alvherre@alvh.no-ip. 7421 :CBC 1197 : find_inheritance_children(RelationGetRelid(rel), lockmode);
7422 : :
7423 : : /*
7424 : : * If we are told not to recurse, there had better not be any child
7425 : : * tables; else the addition would put them out of step.
7426 : : */
4760 rhaas@postgresql.org 7427 [ + + + + ]: 1197 : if (children && !recurse)
7428 [ + - ]: 6 : ereport(ERROR,
7429 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
7430 : : errmsg("column must be added to child tables too")));
7431 : :
7432 : : /* Children should see column as singly inherited */
7433 [ + + ]: 1191 : if (!recursing)
7434 : : {
1551 tgl@sss.pgh.pa.us 7435 : 897 : childcmd = copyObject(*cmd);
7436 : 897 : colDef = castNode(ColumnDef, childcmd->def);
4760 rhaas@postgresql.org 7437 : 897 : colDef->inhcount = 1;
7438 : 897 : colDef->is_local = false;
7439 : : }
7440 : : else
1551 tgl@sss.pgh.pa.us 7441 : 294 : childcmd = *cmd; /* no need to copy again */
7442 : :
4760 rhaas@postgresql.org 7443 [ + + + + : 1503 : foreach(child, children)
+ + ]
7444 : : {
7445 : 312 : Oid childrelid = lfirst_oid(child);
7446 : : Relation childrel;
7447 : : AlteredTableInfo *childtab;
7448 : :
7449 : : /* find_inheritance_children already got lock */
1910 andres@anarazel.de 7450 : 312 : childrel = table_open(childrelid, NoLock);
4760 rhaas@postgresql.org 7451 : 312 : CheckTableNotInUse(childrel, "ALTER TABLE");
7452 : :
7453 : : /* Find or create work queue entry for this table */
7454 : 312 : childtab = ATGetQueueEntry(wqueue, childrel);
7455 : :
7456 : : /* Recurse to child; return value is ignored */
7457 : 312 : ATExecAddColumn(wqueue, childtab, childrel,
7458 : : &childcmd, recurse, true,
7459 : : lockmode, cur_pass, context);
7460 : :
1910 andres@anarazel.de 7461 : 312 : table_close(childrel, NoLock);
7462 : : }
7463 : :
3308 alvherre@alvh.no-ip. 7464 : 1191 : ObjectAddressSubSet(address, RelationRelationId, myrelid, newattnum);
7465 : 1191 : return address;
7466 : : }
7467 : :
7468 : : /*
7469 : : * If a new or renamed column will collide with the name of an existing
7470 : : * column and if_not_exists is false then error out, else do nothing.
7471 : : */
7472 : : static bool
3182 andrew@dunslane.net 7473 : 1488 : check_for_column_name_collision(Relation rel, const char *colname,
7474 : : bool if_not_exists)
7475 : : {
7476 : : HeapTuple attTuple;
7477 : : int attnum;
7478 : :
7479 : : /*
7480 : : * this test is deliberately not attisdropped-aware, since if one tries to
7481 : : * add a column matching a dropped column name, it's gonna fail anyway.
7482 : : */
4462 rhaas@postgresql.org 7483 : 1488 : attTuple = SearchSysCache2(ATTNAME,
7484 : : ObjectIdGetDatum(RelationGetRelid(rel)),
7485 : : PointerGetDatum(colname));
7486 [ + + ]: 1488 : if (!HeapTupleIsValid(attTuple))
3182 andrew@dunslane.net 7487 : 1440 : return true;
7488 : :
4326 bruce@momjian.us 7489 : 48 : attnum = ((Form_pg_attribute) GETSTRUCT(attTuple))->attnum;
4462 rhaas@postgresql.org 7490 : 48 : ReleaseSysCache(attTuple);
7491 : :
7492 : : /*
7493 : : * We throw a different error message for conflicts with system column
7494 : : * names, since they are normally not shown and the user might otherwise
7495 : : * be confused about the reason for the conflict.
7496 : : */
4326 bruce@momjian.us 7497 [ + + ]: 48 : if (attnum <= 0)
7498 [ + - ]: 6 : ereport(ERROR,
7499 : : (errcode(ERRCODE_DUPLICATE_COLUMN),
7500 : : errmsg("column name \"%s\" conflicts with a system column name",
7501 : : colname)));
7502 : : else
7503 : : {
3182 andrew@dunslane.net 7504 [ + + ]: 42 : if (if_not_exists)
7505 : : {
7506 [ + - ]: 27 : ereport(NOTICE,
7507 : : (errcode(ERRCODE_DUPLICATE_COLUMN),
7508 : : errmsg("column \"%s\" of relation \"%s\" already exists, skipping",
7509 : : colname, RelationGetRelationName(rel))));
7510 : 27 : return false;
7511 : : }
7512 : :
4326 bruce@momjian.us 7513 [ + - ]: 15 : ereport(ERROR,
7514 : : (errcode(ERRCODE_DUPLICATE_COLUMN),
7515 : : errmsg("column \"%s\" of relation \"%s\" already exists",
7516 : : colname, RelationGetRelationName(rel))));
7517 : : }
7518 : :
7519 : : return true;
7520 : : }
7521 : :
7522 : : /*
7523 : : * Install a column's dependency on its datatype.
7524 : : */
7525 : : static void
4741 tgl@sss.pgh.pa.us 7526 : 1651 : add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid)
7527 : : {
7528 : : ObjectAddress myself,
7529 : : referenced;
7530 : :
6940 7531 : 1651 : myself.classId = RelationRelationId;
7284 7532 : 1651 : myself.objectId = relid;
7533 : 1651 : myself.objectSubId = attnum;
6940 7534 : 1651 : referenced.classId = TypeRelationId;
7284 7535 : 1651 : referenced.objectId = typid;
7536 : 1651 : referenced.objectSubId = 0;
7537 : 1651 : recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
4741 7538 : 1651 : }
7539 : :
7540 : : /*
7541 : : * Install a column's dependency on its collation.
7542 : : */
7543 : : static void
7544 : 1651 : add_column_collation_dependency(Oid relid, int32 attnum, Oid collid)
7545 : : {
7546 : : ObjectAddress myself,
7547 : : referenced;
7548 : :
7549 : : /* We know the default collation is pinned, so don't bother recording it */
7550 [ + + + + ]: 1651 : if (OidIsValid(collid) && collid != DEFAULT_COLLATION_OID)
7551 : : {
7552 : 9 : myself.classId = RelationRelationId;
7553 : 9 : myself.objectId = relid;
7554 : 9 : myself.objectSubId = attnum;
4810 peter_e@gmx.net 7555 : 9 : referenced.classId = CollationRelationId;
7556 : 9 : referenced.objectId = collid;
7557 : 9 : referenced.objectSubId = 0;
7558 : 9 : recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
7559 : : }
7284 tgl@sss.pgh.pa.us 7560 : 1651 : }
7561 : :
7562 : : /*
7563 : : * ALTER TABLE ALTER COLUMN DROP NOT NULL
7564 : : *
7565 : : * Return the address of the modified column. If the column was already
7566 : : * nullable, InvalidObjectAddress is returned.
7567 : : */
7568 : : static ObjectAddress
233 alvherre@alvh.no-ip. 7569 :GNC 119 : ATExecDropNotNull(Relation rel, const char *colName, bool recurse,
7570 : : LOCKMODE lockmode)
7571 : : {
7572 : : HeapTuple tuple;
7573 : : HeapTuple conTup;
7574 : : Form_pg_attribute attTup;
7575 : : AttrNumber attnum;
7576 : : Relation attr_rel;
7577 : : ObjectAddress address;
7578 : : List *readyRels;
7579 : :
7580 : : /*
7581 : : * lookup the attribute
7582 : : */
1910 andres@anarazel.de 7583 :CBC 119 : attr_rel = table_open(AttributeRelationId, RowExclusiveLock);
7584 : :
7284 tgl@sss.pgh.pa.us 7585 : 119 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
7586 [ + + ]: 119 : if (!HeapTupleIsValid(tuple))
7587 [ + - ]: 9 : ereport(ERROR,
7588 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
7589 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
7590 : : colName, RelationGetRelationName(rel))));
2000 peter_e@gmx.net 7591 : 110 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
7592 : 110 : attnum = attTup->attnum;
233 alvherre@alvh.no-ip. 7593 :GNC 110 : ObjectAddressSubSet(address, RelationRelationId,
7594 : : RelationGetRelid(rel), attnum);
7595 : :
7596 : : /* If the column is already nullable there's nothing to do. */
7597 [ + + ]: 110 : if (!attTup->attnotnull)
7598 : : {
7599 : 3 : table_close(attr_rel, RowExclusiveLock);
7600 : 3 : return InvalidObjectAddress;
7601 : : }
7602 : :
7603 : : /* Prevent them from altering a system attribute */
7284 tgl@sss.pgh.pa.us 7604 [ - + ]:CBC 107 : if (attnum <= 0)
7284 tgl@sss.pgh.pa.us 7605 [ # # ]:UBC 0 : ereport(ERROR,
7606 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
7607 : : errmsg("cannot alter system column \"%s\"",
7608 : : colName)));
7609 : :
2000 peter_e@gmx.net 7610 [ + + ]:CBC 107 : if (attTup->attidentity)
2565 7611 [ + - ]: 9 : ereport(ERROR,
7612 : : (errcode(ERRCODE_SYNTAX_ERROR),
7613 : : errmsg("column \"%s\" of relation \"%s\" is an identity column",
7614 : : colName, RelationGetRelationName(rel))));
7615 : :
7616 : : /*
7617 : : * It's not OK to remove a constraint only for the parent and leave it in
7618 : : * the children, so disallow that.
7619 : : */
233 alvherre@alvh.no-ip. 7620 [ + + ]:GNC 98 : if (!recurse)
7621 : : {
7622 [ + - ]: 6 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
7623 : : {
7624 : : PartitionDesc partdesc;
7625 : :
7626 : 6 : partdesc = RelationGetPartitionDesc(rel, true);
7627 : :
7628 [ + + ]: 6 : if (partdesc->nparts > 0)
7629 [ + - ]: 3 : ereport(ERROR,
7630 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
7631 : : errmsg("cannot remove constraint from only the partitioned table when partitions exist"),
7632 : : errhint("Do not specify the ONLY keyword."));
7633 : : }
233 alvherre@alvh.no-ip. 7634 [ # # # # ]:UNC 0 : else if (rel->rd_rel->relhassubclass &&
7635 : 0 : find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
7636 : : {
7637 [ # # ]: 0 : ereport(ERROR,
7638 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
7639 : : errmsg("not-null constraint on column \"%s\" must be removed in child tables too",
7640 : : colName),
7641 : : errhint("Do not specify the ONLY keyword."));
7642 : : }
7643 : : }
7644 : :
7645 : : /*
7646 : : * If rel is partition, shouldn't drop NOT NULL if parent has the same.
7647 : : */
2685 rhaas@postgresql.org 7648 [ + + ]:CBC 95 : if (rel->rd_rel->relispartition)
7649 : : {
368 alvherre@alvh.no-ip. 7650 : 9 : Oid parentId = get_partition_parent(RelationGetRelid(rel), false);
7651 : 9 : Relation parent = table_open(parentId, AccessShareLock);
7652 : 9 : TupleDesc tupDesc = RelationGetDescr(parent);
7653 : : AttrNumber parent_attnum;
7654 : :
2685 rhaas@postgresql.org 7655 : 9 : parent_attnum = get_attnum(parentId, colName);
2429 andres@anarazel.de 7656 [ + - ]: 9 : if (TupleDescAttr(tupDesc, parent_attnum - 1)->attnotnull)
2685 rhaas@postgresql.org 7657 [ + - ]: 9 : ereport(ERROR,
7658 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
7659 : : errmsg("column \"%s\" is marked NOT NULL in parent table",
7660 : : colName)));
1910 andres@anarazel.de 7661 :UBC 0 : table_close(parent, AccessShareLock);
7662 : : }
7663 : :
7664 : : /*
7665 : : * Find the constraint that makes this column NOT NULL.
7666 : : */
233 alvherre@alvh.no-ip. 7667 :GNC 86 : conTup = findNotNullConstraint(RelationGetRelid(rel), colName);
7668 [ + + ]: 86 : if (conTup == NULL)
7669 : : {
7670 : : Bitmapset *pkcols;
7671 : :
7672 : : /*
7673 : : * There's no not-null constraint, so throw an error. If the column
7674 : : * is in a primary key, we can throw a specific error. Otherwise,
7675 : : * this is unexpected.
7676 : : */
7677 : 6 : pkcols = RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_PRIMARY_KEY);
7678 [ + - ]: 6 : if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber,
7679 : : pkcols))
7680 [ + - ]: 6 : ereport(ERROR,
7681 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
7682 : : errmsg("column \"%s\" is in a primary key", colName));
7683 : :
7684 : : /* this shouldn't happen */
233 alvherre@alvh.no-ip. 7685 [ # # ]:UNC 0 : elog(ERROR, "could not find not-null constraint on column \"%s\", relation \"%s\"",
7686 : : colName, RelationGetRelationName(rel));
7687 : : }
7688 : :
233 alvherre@alvh.no-ip. 7689 :GNC 80 : readyRels = NIL;
7690 : 80 : dropconstraint_internal(rel, conTup, DROP_RESTRICT, recurse, false,
7691 : : false, &readyRels, lockmode);
7692 : :
7693 : 62 : heap_freetuple(conTup);
7694 : :
368 alvherre@alvh.no-ip. 7695 [ - + ]:CBC 62 : InvokeObjectPostAlterHook(RelationRelationId,
7696 : : RelationGetRelid(rel), attnum);
7697 : :
1910 andres@anarazel.de 7698 : 62 : table_close(attr_rel, RowExclusiveLock);
7699 : :
3308 alvherre@alvh.no-ip. 7700 : 62 : return address;
7701 : : }
7702 : :
7703 : : /*
7704 : : * Helper to set pg_attribute.attnotnull if it isn't set, and to tell phase 3
7705 : : * to verify it; recurses to apply the same to children.
7706 : : *
7707 : : * When called to alter an existing table, 'wqueue' must be given so that we can
7708 : : * queue a check that existing tuples pass the constraint. When called from
7709 : : * table creation, 'wqueue' should be passed as NULL.
7710 : : *
7711 : : * Returns true if the flag was set in any table, otherwise false.
7712 : : */
7713 : : static bool
233 alvherre@alvh.no-ip. 7714 :GNC 11554 : set_attnotnull(List **wqueue, Relation rel, AttrNumber attnum, bool recurse,
7715 : : LOCKMODE lockmode)
7716 : : {
7717 : : HeapTuple tuple;
7718 : : Form_pg_attribute attForm;
7719 : 11554 : bool retval = false;
7720 : :
7721 : : /* Guard against stack overflow due to overly deep inheritance tree. */
158 7722 : 11554 : check_stack_depth();
7723 : :
233 7724 : 11554 : tuple = SearchSysCacheCopyAttNum(RelationGetRelid(rel), attnum);
7725 [ - + ]: 11554 : if (!HeapTupleIsValid(tuple))
233 alvherre@alvh.no-ip. 7726 [ # # ]:UNC 0 : elog(ERROR, "cache lookup failed for attribute %d of relation %u",
7727 : : attnum, RelationGetRelid(rel));
233 alvherre@alvh.no-ip. 7728 :GNC 11554 : attForm = (Form_pg_attribute) GETSTRUCT(tuple);
7729 [ + + ]: 11554 : if (!attForm->attnotnull)
7730 : : {
7731 : : Relation attr_rel;
7732 : :
7733 : 767 : attr_rel = table_open(AttributeRelationId, RowExclusiveLock);
7734 : :
7735 : 767 : attForm->attnotnull = true;
7736 : 767 : CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple);
7737 : :
7738 : 767 : table_close(attr_rel, RowExclusiveLock);
7739 : :
7740 : : /*
7741 : : * And set up for existing values to be checked, unless another
7742 : : * constraint already proves this.
7743 : : */
7744 [ + + + + ]: 767 : if (wqueue && !NotNullImpliedByRelConstraints(rel, attForm))
7745 : : {
7746 : : AlteredTableInfo *tab;
7747 : :
7748 : 641 : tab = ATGetQueueEntry(wqueue, rel);
7749 : 641 : tab->verify_new_notnull = true;
7750 : : }
7751 : :
7752 : 767 : retval = true;
7753 : : }
7754 : :
7755 [ + + ]: 11554 : if (recurse)
2546 sfrost@snowman.net 7756 :ECB (18) : {
7757 : : List *children;
7758 : : ListCell *lc;
7759 : :
233 alvherre@alvh.no-ip. 7760 :GNC 477 : children = find_inheritance_children(RelationGetRelid(rel), lockmode);
7761 [ + + + + : 549 : foreach(lc, children)
+ + ]
7762 : : {
7763 : 72 : Oid childrelid = lfirst_oid(lc);
7764 : : Relation childrel;
7765 : : AttrNumber childattno;
7766 : :
7767 : : /* find_inheritance_children already got lock */
7768 : 72 : childrel = table_open(childrelid, NoLock);
7769 : 72 : CheckTableNotInUse(childrel, "ALTER TABLE");
7770 : :
7771 : 72 : childattno = get_attnum(RelationGetRelid(childrel),
7772 : 72 : get_attname(RelationGetRelid(rel), attnum,
7773 : : false));
7774 : 72 : retval |= set_attnotnull(wqueue, childrel, childattno,
7775 : : recurse, lockmode);
7776 : 72 : table_close(childrel, NoLock);
7777 : : }
7778 : : }
7779 : :
7780 : 11554 : return retval;
7781 : : }
7782 : :
7783 : : /*
7784 : : * ALTER TABLE ALTER COLUMN SET NOT NULL
7785 : : *
7786 : : * Add a not-null constraint to a single table and its children. Returns
7787 : : * the address of the constraint added to the parent relation, if one gets
7788 : : * added, or InvalidObjectAddress otherwise.
7789 : : *
7790 : : * We must recurse to child tables during execution, rather than using
7791 : : * ALTER TABLE's normal prep-time recursion.
7792 : : */
7793 : : static ObjectAddress
7794 : 245 : ATExecSetNotNull(List **wqueue, Relation rel, char *conName, char *colName,
7795 : : bool recurse, bool recursing, List **readyRels,
7796 : : LOCKMODE lockmode)
7797 : : {
7798 : : HeapTuple tuple;
7799 : : Relation constr_rel;
7800 : : ScanKeyData skey;
7801 : : SysScanDesc conscan;
7802 : : AttrNumber attnum;
7803 : : ObjectAddress address;
7804 : : Constraint *constraint;
7805 : : CookedConstraint *ccon;
7806 : : List *cooked;
7807 : 245 : bool is_no_inherit = false;
7808 : 245 : List *ready = NIL;
7809 : :
7810 : : /* Guard against stack overflow due to overly deep inheritance tree. */
158 7811 : 245 : check_stack_depth();
7812 : :
7813 : : /*
7814 : : * In cases of multiple inheritance, we might visit the same child more
7815 : : * than once. In the topmost call, set up a list that we fill with all
7816 : : * visited relations, to skip those.
7817 : : */
233 7818 [ + + ]: 245 : if (readyRels == NULL)
7819 : : {
7820 [ - + ]: 180 : Assert(!recursing);
7821 : 180 : readyRels = &ready;
7822 : : }
7823 [ + + ]: 245 : if (list_member_oid(*readyRels, RelationGetRelid(rel)))
7824 : 3 : return InvalidObjectAddress;
7825 : 242 : *readyRels = lappend_oid(*readyRels, RelationGetRelid(rel));
7826 : :
7827 : : /* At top level, permission check was done in ATPrepCmd, else do it */
7828 [ + + ]: 242 : if (recursing)
7829 : : {
7830 : 62 : ATSimplePermissions(AT_AddConstraint, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
7831 [ - + ]: 62 : Assert(conName != NULL);
7832 : : }
7833 : :
7834 : 242 : attnum = get_attnum(RelationGetRelid(rel), colName);
7835 [ + + ]: 242 : if (attnum == InvalidAttrNumber)
7574 tgl@sss.pgh.pa.us 7836 [ + - ]:CBC 9 : ereport(ERROR,
7837 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
7838 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
7839 : : colName, RelationGetRelationName(rel))));
7840 : :
7841 : : /* Prevent them from altering a system attribute */
7284 7842 [ - + ]: 233 : if (attnum <= 0)
7574 tgl@sss.pgh.pa.us 7843 [ # # ]:UBC 0 : ereport(ERROR,
7844 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
7845 : : errmsg("cannot alter system column \"%s\"",
7846 : : colName)));
7847 : :
7848 : : /* See if there's already a constraint */
233 alvherre@alvh.no-ip. 7849 :GNC 233 : constr_rel = table_open(ConstraintRelationId, RowExclusiveLock);
7850 : 233 : ScanKeyInit(&skey,
7851 : : Anum_pg_constraint_conrelid,
7852 : : BTEqualStrategyNumber, F_OIDEQ,
7853 : : ObjectIdGetDatum(RelationGetRelid(rel)));
7854 : 233 : conscan = systable_beginscan(constr_rel, ConstraintRelidTypidNameIndexId, true,
7855 : : NULL, 1, &skey);
7856 : :
7857 [ + + ]: 443 : while (HeapTupleIsValid(tuple = systable_getnext(conscan)))
7858 : : {
7859 : 220 : Form_pg_constraint conForm = (Form_pg_constraint) GETSTRUCT(tuple);
7860 : 220 : bool changed = false;
7861 : : HeapTuple copytup;
7862 : :
7863 [ + + ]: 220 : if (conForm->contype != CONSTRAINT_NOTNULL)
7864 : 100 : continue;
7865 : :
7866 [ + + ]: 120 : if (extractNotNullColumn(tuple) != attnum)
7867 : 110 : continue;
7868 : :
7869 : 10 : copytup = heap_copytuple(tuple);
7870 : 10 : conForm = (Form_pg_constraint) GETSTRUCT(copytup);
7871 : :
7872 : : /*
7873 : : * If we find an appropriate constraint, we're almost done, but just
7874 : : * need to change some properties on it: if we're recursing, increment
7875 : : * coninhcount; if not, set conislocal if not already set.
7876 : : */
7877 [ + + ]: 10 : if (recursing)
7878 : : {
7879 : 3 : conForm->coninhcount++;
7880 : 3 : changed = true;
7881 : : }
7882 [ - + ]: 7 : else if (!conForm->conislocal)
7883 : : {
233 alvherre@alvh.no-ip. 7884 :UNC 0 : conForm->conislocal = true;
7885 : 0 : changed = true;
7886 : : }
7887 : :
233 alvherre@alvh.no-ip. 7888 [ + + ]:GNC 10 : if (changed)
7889 : : {
7890 : 3 : CatalogTupleUpdate(constr_rel, ©tup->t_self, copytup);
7891 : 3 : ObjectAddressSet(address, ConstraintRelationId, conForm->oid);
7892 : : }
7893 : :
7894 : 10 : systable_endscan(conscan);
7895 : 10 : table_close(constr_rel, RowExclusiveLock);
7896 : :
7897 [ + + ]: 10 : if (changed)
7898 : 3 : return address;
7899 : : else
7900 : 7 : return InvalidObjectAddress;
7901 : : }
7902 : :
7903 : 223 : systable_endscan(conscan);
7904 : 223 : table_close(constr_rel, RowExclusiveLock);
7905 : :
7906 : : /*
7907 : : * If we're asked not to recurse, and children exist, raise an error for
7908 : : * partitioned tables. For inheritance, we act as if NO INHERIT had been
7909 : : * specified.
7910 : : */
7911 [ + + + + ]: 238 : if (!recurse &&
7912 : 15 : find_inheritance_children(RelationGetRelid(rel),
7913 : : NoLock) != NIL)
7914 : : {
7915 [ + + ]: 12 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
7916 [ + - ]: 6 : ereport(ERROR,
7917 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
7918 : : errmsg("constraint must be added to child tables too"),
7919 : : errhint("Do not specify the ONLY keyword."));
7920 : : else
7921 : 6 : is_no_inherit = true;
7922 : : }
7923 : :
7924 : : /*
7925 : : * No constraint exists; we must add one. First determine a name to use,
7926 : : * if we haven't already.
7927 : : */
7928 [ + + ]: 217 : if (!recursing)
7929 : : {
7930 [ - + ]: 158 : Assert(conName == NULL);
7931 : 158 : conName = ChooseConstraintName(RelationGetRelationName(rel),
7932 : : colName, "not_null",
7933 : 158 : RelationGetNamespace(rel),
7934 : : NIL);
7935 : : }
7936 : 217 : constraint = makeNode(Constraint);
7937 : 217 : constraint->contype = CONSTR_NOTNULL;
7938 : 217 : constraint->conname = conName;
7939 : 217 : constraint->deferrable = false;
7940 : 217 : constraint->initdeferred = false;
7941 : 217 : constraint->location = -1;
7942 : 217 : constraint->keys = list_make1(makeString(colName));
7943 : 217 : constraint->is_no_inherit = is_no_inherit;
7944 : 217 : constraint->inhcount = recursing ? 1 : 0;
7945 : 217 : constraint->skip_validation = false;
7946 : 217 : constraint->initially_valid = true;
7947 : :
7948 : : /* and do it */
7949 : 217 : cooked = AddRelationNewConstraints(rel, NIL, list_make1(constraint),
7950 : 217 : false, !recursing, false, NULL);
7951 : 217 : ccon = linitial(cooked);
7952 : 217 : ObjectAddressSet(address, ConstraintRelationId, ccon->conoid);
7953 : :
368 alvherre@alvh.no-ip. 7954 [ - + ]:CBC 217 : InvokeObjectPostAlterHook(RelationRelationId,
7955 : : RelationGetRelid(rel), attnum);
7956 : :
7957 : : /*
7958 : : * Mark pg_attribute.attnotnull for the column. Tell that function not to
7959 : : * recurse, because we're going to do it here.
7960 : : */
233 alvherre@alvh.no-ip. 7961 :GNC 217 : set_attnotnull(wqueue, rel, attnum, false, lockmode);
7962 : :
7963 : : /*
7964 : : * Recurse to propagate the constraint to children that don't have one.
7965 : : */
7966 [ + + ]: 217 : if (recurse)
7967 : : {
7968 : : List *children;
7969 : : ListCell *lc;
7970 : :
7971 : 208 : children = find_inheritance_children(RelationGetRelid(rel),
7972 : : lockmode);
7973 : :
7974 [ + + + + : 273 : foreach(lc, children)
+ + ]
7975 : : {
7976 : : Relation childrel;
7977 : :
7978 : 65 : childrel = table_open(lfirst_oid(lc), NoLock);
7979 : :
7980 : 65 : ATExecSetNotNull(wqueue, childrel,
7981 : : conName, colName, recurse, true,
7982 : : readyRels, lockmode);
7983 : :
7984 : 65 : table_close(childrel, NoLock);
7985 : : }
7986 : : }
7987 : :
3308 alvherre@alvh.no-ip. 7988 :CBC 217 : return address;
7989 : : }
7990 : :
7991 : : /*
7992 : : * ALTER TABLE ALTER COLUMN SET ATTNOTNULL
7993 : : *
7994 : : * This doesn't exist in the grammar; it's used when creating a
7995 : : * primary key and the column is not already marked attnotnull.
7996 : : */
7997 : : static ObjectAddress
233 alvherre@alvh.no-ip. 7998 :GNC 7120 : ATExecSetAttNotNull(List **wqueue, Relation rel,
7999 : : const char *colName, LOCKMODE lockmode)
8000 : : {
8001 : : AttrNumber attnum;
8002 : 7120 : ObjectAddress address = InvalidObjectAddress;
8003 : :
8004 : 7120 : attnum = get_attnum(RelationGetRelid(rel), colName);
8005 [ + + ]: 7120 : if (attnum == InvalidAttrNumber)
1818 tgl@sss.pgh.pa.us 8006 [ + - ]:GBC 9 : ereport(ERROR,
8007 : : errcode(ERRCODE_UNDEFINED_COLUMN),
8008 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8009 : : colName, RelationGetRelationName(rel)));
8010 : :
8011 : : /*
8012 : : * Make the change, if necessary, and only if so report the column as
8013 : : * changed
8014 : : */
233 alvherre@alvh.no-ip. 8015 [ + + ]:GNC 7111 : if (set_attnotnull(wqueue, rel, attnum, false, lockmode))
8016 : 338 : ObjectAddressSubSet(address, RelationRelationId,
8017 : : RelationGetRelid(rel), attnum);
8018 : :
8019 : 7111 : return address;
1818 tgl@sss.pgh.pa.us 8020 :ECB (9) : }
8021 : :
8022 : : /*
8023 : : * NotNullImpliedByRelConstraints
8024 : : * Does rel's existing constraints imply NOT NULL for the given attribute?
8025 : : */
8026 : : static bool
1859 rhaas@postgresql.org 8027 :CBC 666 : NotNullImpliedByRelConstraints(Relation rel, Form_pg_attribute attr)
8028 : : {
8029 : 666 : NullTest *nnulltest = makeNode(NullTest);
8030 : :
8031 : 1332 : nnulltest->arg = (Expr *) makeVar(1,
8032 : 666 : attr->attnum,
8033 : : attr->atttypid,
8034 : : attr->atttypmod,
8035 : : attr->attcollation,
8036 : : 0);
8037 : 666 : nnulltest->nulltesttype = IS_NOT_NULL;
8038 : :
8039 : : /*
8040 : : * argisrow = false is correct even for a composite column, because
8041 : : * attnotnull does not represent a SQL-spec IS NOT NULL test in such a
8042 : : * case, just IS DISTINCT FROM NULL.
8043 : : */
8044 : 666 : nnulltest->argisrow = false;
8045 : 666 : nnulltest->location = -1;
8046 : :
8047 [ + + ]: 666 : if (ConstraintImpliedByRelConstraint(rel, list_make1(nnulltest), NIL))
8048 : : {
8049 [ + + ]: 25 : ereport(DEBUG1,
8050 : : (errmsg_internal("existing constraints on column \"%s.%s\" are sufficient to prove that it does not contain nulls",
8051 : : RelationGetRelationName(rel), NameStr(attr->attname))));
8052 : 25 : return true;
8053 : : }
8054 : :
8055 : 641 : return false;
8056 : : }
8057 : :
8058 : : /*
8059 : : * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
8060 : : *
8061 : : * Return the address of the affected column.
8062 : : */
8063 : : static ObjectAddress
7284 tgl@sss.pgh.pa.us 8064 : 283 : ATExecColumnDefault(Relation rel, const char *colName,
8065 : : Node *newDefault, LOCKMODE lockmode)
8066 : : {
2000 peter_e@gmx.net 8067 : 283 : TupleDesc tupdesc = RelationGetDescr(rel);
8068 : : AttrNumber attnum;
8069 : : ObjectAddress address;
8070 : :
8071 : : /*
8072 : : * get the number of the attribute
8073 : : */
7284 tgl@sss.pgh.pa.us 8074 : 283 : attnum = get_attnum(RelationGetRelid(rel), colName);
8075 [ + + ]: 283 : if (attnum == InvalidAttrNumber)
8076 [ + - ]: 15 : ereport(ERROR,
8077 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8078 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8079 : : colName, RelationGetRelationName(rel))));
8080 : :
8081 : : /* Prevent them from altering a system attribute */
8082 [ - + ]: 268 : if (attnum <= 0)
7574 tgl@sss.pgh.pa.us 8083 [ # # ]:UBC 0 : ereport(ERROR,
8084 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8085 : : errmsg("cannot alter system column \"%s\"",
8086 : : colName)));
8087 : :
2000 peter_e@gmx.net 8088 [ + + ]:CBC 268 : if (TupleDescAttr(tupdesc, attnum - 1)->attidentity)
2565 8089 [ + - - + ]: 9 : ereport(ERROR,
8090 : : (errcode(ERRCODE_SYNTAX_ERROR),
8091 : : errmsg("column \"%s\" of relation \"%s\" is an identity column",
8092 : : colName, RelationGetRelationName(rel)),
8093 : : /* translator: %s is an SQL ALTER command */
8094 : : newDefault ? 0 : errhint("Use %s instead.",
8095 : : "ALTER TABLE ... ALTER COLUMN ... DROP IDENTITY")));
8096 : :
1842 peter@eisentraut.org 8097 [ + + ]: 259 : if (TupleDescAttr(tupdesc, attnum - 1)->attgenerated)
8098 [ + - - + : 3 : ereport(ERROR,
+ - ]
8099 : : (errcode(ERRCODE_SYNTAX_ERROR),
8100 : : errmsg("column \"%s\" of relation \"%s\" is a generated column",
8101 : : colName, RelationGetRelationName(rel)),
8102 : : newDefault ?
8103 : : /* translator: %s is an SQL ALTER command */
8104 : : errhint("Use %s instead.", "ALTER TABLE ... ALTER COLUMN ... SET EXPRESSION") :
8105 : : (TupleDescAttr(tupdesc, attnum - 1)->attgenerated == ATTRIBUTE_GENERATED_STORED ?
8106 : : errhint("Use %s instead.", "ALTER TABLE ... ALTER COLUMN ... DROP EXPRESSION") : 0)));
8107 : :
8108 : : /*
8109 : : * Remove any old default for the column. We use RESTRICT here for
8110 : : * safety, but at present we do not expect anything to depend on the
8111 : : * default.
8112 : : *
8113 : : * We treat removing the existing default as an internal operation when it
8114 : : * is preparatory to adding a new default, but as a user-initiated
8115 : : * operation when the user asked for a drop.
8116 : : */
4462 rhaas@postgresql.org 8117 : 256 : RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false,
8118 : : newDefault != NULL);
8119 : :
7284 tgl@sss.pgh.pa.us 8120 [ + + ]: 256 : if (newDefault)
8121 : : {
8122 : : /* SET DEFAULT */
8123 : : RawColumnDefault *rawEnt;
8124 : :
8125 : 169 : rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
8126 : 169 : rawEnt->attnum = attnum;
8127 : 169 : rawEnt->raw_default = newDefault;
2209 andrew@dunslane.net 8128 : 169 : rawEnt->missingMode = false;
1842 peter@eisentraut.org 8129 : 169 : rawEnt->generated = '\0';
8130 : :
8131 : : /*
8132 : : * This function is intended for CREATE TABLE, so it processes a
8133 : : * _list_ of defaults, but we just do one.
8134 : : */
4046 rhaas@postgresql.org 8135 : 169 : AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
8136 : : false, true, false, NULL);
8137 : : }
8138 : :
3308 alvherre@alvh.no-ip. 8139 : 253 : ObjectAddressSubSet(address, RelationRelationId,
8140 : : RelationGetRelid(rel), attnum);
8141 : 253 : return address;
8142 : : }
8143 : :
8144 : : /*
8145 : : * Add a pre-cooked default expression.
8146 : : *
8147 : : * Return the address of the affected column.
8148 : : */
8149 : : static ObjectAddress
1332 tgl@sss.pgh.pa.us 8150 : 55 : ATExecCookedColumnDefault(Relation rel, AttrNumber attnum,
8151 : : Node *newDefault)
8152 : : {
8153 : : ObjectAddress address;
8154 : :
8155 : : /* We assume no checking is required */
8156 : :
8157 : : /*
8158 : : * Remove any old default for the column. We use RESTRICT here for
8159 : : * safety, but at present we do not expect anything to depend on the
8160 : : * default. (In ordinary cases, there could not be a default in place
8161 : : * anyway, but it's possible when combining LIKE with inheritance.)
8162 : : */
8163 : 55 : RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false,
8164 : : true);
8165 : :
8166 : 55 : (void) StoreAttrDefault(rel, attnum, newDefault, true, false);
8167 : :
8168 : 55 : ObjectAddressSubSet(address, RelationRelationId,
8169 : : RelationGetRelid(rel), attnum);
8170 : 55 : return address;
8171 : : }
8172 : :
8173 : : /*
8174 : : * ALTER TABLE ALTER COLUMN ADD IDENTITY
8175 : : *
8176 : : * Return the address of the affected column.
8177 : : */
8178 : : static ObjectAddress
2565 peter_e@gmx.net 8179 : 74 : ATExecAddIdentity(Relation rel, const char *colName,
8180 : : Node *def, LOCKMODE lockmode, bool recurse, bool recursing)
8181 : : {
8182 : : Relation attrelation;
8183 : : HeapTuple tuple;
8184 : : Form_pg_attribute attTup;
8185 : : AttrNumber attnum;
8186 : : ObjectAddress address;
8187 : 74 : ColumnDef *cdef = castNode(ColumnDef, def);
8188 : : bool ispartitioned;
8189 : :
89 peter@eisentraut.org 8190 :GNC 74 : ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
8191 [ + + - + ]: 74 : if (ispartitioned && !recurse)
89 peter@eisentraut.org 8192 [ # # ]:UNC 0 : ereport(ERROR,
8193 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8194 : : errmsg("cannot add identity to a column of only the partitioned table"),
8195 : : errhint("Do not specify the ONLY keyword.")));
8196 : :
89 peter@eisentraut.org 8197 [ + + + + ]:GNC 74 : if (rel->rd_rel->relispartition && !recursing)
8198 [ + - ]: 6 : ereport(ERROR,
8199 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8200 : : errmsg("cannot add identity to a column of a partition"));
8201 : :
1910 andres@anarazel.de 8202 :CBC 68 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
8203 : :
2565 peter_e@gmx.net 8204 : 68 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
8205 [ - + ]: 68 : if (!HeapTupleIsValid(tuple))
2565 peter_e@gmx.net 8206 [ # # ]:UBC 0 : ereport(ERROR,
8207 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8208 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8209 : : colName, RelationGetRelationName(rel))));
2565 peter_e@gmx.net 8210 :CBC 68 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8211 : 68 : attnum = attTup->attnum;
8212 : :
8213 : : /* Can't alter a system attribute */
8214 [ - + ]: 68 : if (attnum <= 0)
2565 peter_e@gmx.net 8215 [ # # ]:UBC 0 : ereport(ERROR,
8216 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8217 : : errmsg("cannot alter system column \"%s\"",
8218 : : colName)));
8219 : :
8220 : : /*
8221 : : * Creating a column as identity implies NOT NULL, so adding the identity
8222 : : * to an existing column that is not NOT NULL would create a state that
8223 : : * cannot be reproduced without contortions.
8224 : : */
2565 peter_e@gmx.net 8225 [ + + ]:CBC 68 : if (!attTup->attnotnull)
8226 [ + - ]: 3 : ereport(ERROR,
8227 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8228 : : errmsg("column \"%s\" of relation \"%s\" must be declared NOT NULL before identity can be added",
8229 : : colName, RelationGetRelationName(rel))));
8230 : :
8231 [ + + ]: 65 : if (attTup->attidentity)
8232 [ + - ]: 9 : ereport(ERROR,
8233 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8234 : : errmsg("column \"%s\" of relation \"%s\" is already an identity column",
8235 : : colName, RelationGetRelationName(rel))));
8236 : :
8237 [ + + ]: 56 : if (attTup->atthasdef)
8238 [ + - ]: 3 : ereport(ERROR,
8239 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8240 : : errmsg("column \"%s\" of relation \"%s\" already has a default value",
8241 : : colName, RelationGetRelationName(rel))));
8242 : :
8243 : 53 : attTup->attidentity = cdef->identity;
8244 : 53 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
8245 : :
8246 [ - + ]: 53 : InvokeObjectPostAlterHook(RelationRelationId,
8247 : : RelationGetRelid(rel),
8248 : : attTup->attnum);
8249 : 53 : ObjectAddressSubSet(address, RelationRelationId,
8250 : : RelationGetRelid(rel), attnum);
8251 : 53 : heap_freetuple(tuple);
8252 : :
1910 andres@anarazel.de 8253 : 53 : table_close(attrelation, RowExclusiveLock);
8254 : :
8255 : : /*
8256 : : * Recurse to propagate the identity column to partitions. Identity is
8257 : : * not inherited in regular inheritance children.
8258 : : */
89 peter@eisentraut.org 8259 [ + - + + ]:GNC 53 : if (recurse && ispartitioned)
8260 : : {
8261 : : List *children;
8262 : : ListCell *lc;
8263 : :
8264 : 5 : children = find_inheritance_children(RelationGetRelid(rel), lockmode);
8265 : :
8266 [ + + + + : 8 : foreach(lc, children)
+ + ]
8267 : : {
8268 : : Relation childrel;
8269 : :
8270 : 3 : childrel = table_open(lfirst_oid(lc), NoLock);
8271 : 3 : ATExecAddIdentity(childrel, colName, def, lockmode, recurse, true);
8272 : 3 : table_close(childrel, NoLock);
8273 : : }
8274 : : }
8275 : :
2565 peter_e@gmx.net 8276 :CBC 53 : return address;
8277 : : }
8278 : :
8279 : : /*
8280 : : * ALTER TABLE ALTER COLUMN SET { GENERATED or sequence options }
8281 : : *
8282 : : * Return the address of the affected column.
8283 : : */
8284 : : static ObjectAddress
89 peter@eisentraut.org 8285 :GNC 37 : ATExecSetIdentity(Relation rel, const char *colName, Node *def,
8286 : : LOCKMODE lockmode, bool recurse, bool recursing)
8287 : : {
8288 : : ListCell *option;
2524 bruce@momjian.us 8289 :CBC 37 : DefElem *generatedEl = NULL;
8290 : : HeapTuple tuple;
8291 : : Form_pg_attribute attTup;
8292 : : AttrNumber attnum;
8293 : : Relation attrelation;
8294 : : ObjectAddress address;
8295 : : bool ispartitioned;
8296 : :
89 peter@eisentraut.org 8297 :GNC 37 : ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
8298 [ + + + + ]: 37 : if (ispartitioned && !recurse)
8299 [ + - ]: 3 : ereport(ERROR,
8300 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8301 : : errmsg("cannot change identity column of only the partitioned table"),
8302 : : errhint("Do not specify the ONLY keyword.")));
8303 : :
8304 [ + + + + ]: 34 : if (rel->rd_rel->relispartition && !recursing)
8305 [ + - ]: 6 : ereport(ERROR,
8306 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8307 : : errmsg("cannot change identity column of a partition"));
8308 : :
2565 peter_e@gmx.net 8309 [ + + + + :CBC 50 : foreach(option, castNode(List, def))
+ + ]
8310 : : {
2524 bruce@momjian.us 8311 : 22 : DefElem *defel = lfirst_node(DefElem, option);
8312 : :
2565 peter_e@gmx.net 8313 [ + - ]: 22 : if (strcmp(defel->defname, "generated") == 0)
8314 : : {
8315 [ - + ]: 22 : if (generatedEl)
2565 peter_e@gmx.net 8316 [ # # ]:UBC 0 : ereport(ERROR,
8317 : : (errcode(ERRCODE_SYNTAX_ERROR),
8318 : : errmsg("conflicting or redundant options")));
2565 peter_e@gmx.net 8319 :CBC 22 : generatedEl = defel;
8320 : : }
8321 : : else
2565 peter_e@gmx.net 8322 [ # # ]:UBC 0 : elog(ERROR, "option \"%s\" not recognized",
8323 : : defel->defname);
8324 : : }
8325 : :
8326 : : /*
8327 : : * Even if there is nothing to change here, we run all the checks. There
8328 : : * will be a subsequent ALTER SEQUENCE that relies on everything being
8329 : : * there.
8330 : : */
8331 : :
1910 andres@anarazel.de 8332 :CBC 28 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
2565 peter_e@gmx.net 8333 : 28 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
8334 [ - + ]: 28 : if (!HeapTupleIsValid(tuple))
2565 peter_e@gmx.net 8335 [ # # ]:UBC 0 : ereport(ERROR,
8336 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8337 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8338 : : colName, RelationGetRelationName(rel))));
8339 : :
2565 peter_e@gmx.net 8340 :CBC 28 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8341 : 28 : attnum = attTup->attnum;
8342 : :
8343 [ - + ]: 28 : if (attnum <= 0)
2565 peter_e@gmx.net 8344 [ # # ]:UBC 0 : ereport(ERROR,
8345 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8346 : : errmsg("cannot alter system column \"%s\"",
8347 : : colName)));
8348 : :
2565 peter_e@gmx.net 8349 [ + + ]:CBC 28 : if (!attTup->attidentity)
8350 [ + - ]: 3 : ereport(ERROR,
8351 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8352 : : errmsg("column \"%s\" of relation \"%s\" is not an identity column",
8353 : : colName, RelationGetRelationName(rel))));
8354 : :
8355 [ + + ]: 25 : if (generatedEl)
8356 : : {
8357 : 22 : attTup->attidentity = defGetInt32(generatedEl);
8358 : 22 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
8359 : :
8360 [ - + ]: 22 : InvokeObjectPostAlterHook(RelationRelationId,
8361 : : RelationGetRelid(rel),
8362 : : attTup->attnum);
8363 : 22 : ObjectAddressSubSet(address, RelationRelationId,
8364 : : RelationGetRelid(rel), attnum);
8365 : : }
8366 : : else
8367 : 3 : address = InvalidObjectAddress;
8368 : :
8369 : 25 : heap_freetuple(tuple);
1910 andres@anarazel.de 8370 : 25 : table_close(attrelation, RowExclusiveLock);
8371 : :
8372 : : /*
8373 : : * Recurse to propagate the identity change to partitions. Identity is not
8374 : : * inherited in regular inheritance children.
8375 : : */
89 peter@eisentraut.org 8376 [ + + + - :GNC 25 : if (generatedEl && recurse && ispartitioned)
+ + ]
8377 : : {
8378 : : List *children;
8379 : : ListCell *lc;
8380 : :
8381 : 3 : children = find_inheritance_children(RelationGetRelid(rel), lockmode);
8382 : :
8383 [ + - + + : 9 : foreach(lc, children)
+ + ]
8384 : : {
8385 : : Relation childrel;
8386 : :
8387 : 6 : childrel = table_open(lfirst_oid(lc), NoLock);
8388 : 6 : ATExecSetIdentity(childrel, colName, def, lockmode, recurse, true);
8389 : 6 : table_close(childrel, NoLock);
8390 : : }
8391 : : }
8392 : :
2565 peter_e@gmx.net 8393 :CBC 25 : return address;
8394 : : }
8395 : :
8396 : : /*
8397 : : * ALTER TABLE ALTER COLUMN DROP IDENTITY
8398 : : *
8399 : : * Return the address of the affected column.
8400 : : */
8401 : : static ObjectAddress
89 peter@eisentraut.org 8402 :GNC 46 : ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode,
8403 : : bool recurse, bool recursing)
8404 : : {
8405 : : HeapTuple tuple;
8406 : : Form_pg_attribute attTup;
8407 : : AttrNumber attnum;
8408 : : Relation attrelation;
8409 : : ObjectAddress address;
8410 : : Oid seqid;
8411 : : ObjectAddress seqaddress;
8412 : : bool ispartitioned;
8413 : :
8414 : 46 : ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
8415 [ + + + + ]: 46 : if (ispartitioned && !recurse)
8416 [ + - ]: 3 : ereport(ERROR,
8417 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8418 : : errmsg("cannot drop identity from a column of only the partitioned table"),
8419 : : errhint("Do not specify the ONLY keyword.")));
8420 : :
8421 [ + + + + ]: 43 : if (rel->rd_rel->relispartition && !recursing)
8422 [ + - ]: 3 : ereport(ERROR,
8423 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8424 : : errmsg("cannot drop identity from a column of a partition"));
8425 : :
1910 andres@anarazel.de 8426 :CBC 40 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
2565 peter_e@gmx.net 8427 : 40 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
8428 [ - + ]: 40 : if (!HeapTupleIsValid(tuple))
2565 peter_e@gmx.net 8429 [ # # ]:UBC 0 : ereport(ERROR,
8430 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8431 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8432 : : colName, RelationGetRelationName(rel))));
8433 : :
2565 peter_e@gmx.net 8434 :CBC 40 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8435 : 40 : attnum = attTup->attnum;
8436 : :
8437 [ - + ]: 40 : if (attnum <= 0)
2565 peter_e@gmx.net 8438 [ # # ]:UBC 0 : ereport(ERROR,
8439 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8440 : : errmsg("cannot alter system column \"%s\"",
8441 : : colName)));
8442 : :
2565 peter_e@gmx.net 8443 [ + + ]:CBC 40 : if (!attTup->attidentity)
8444 : : {
8445 [ + + ]: 6 : if (!missing_ok)
8446 [ + - ]: 3 : ereport(ERROR,
8447 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8448 : : errmsg("column \"%s\" of relation \"%s\" is not an identity column",
8449 : : colName, RelationGetRelationName(rel))));
8450 : : else
8451 : : {
8452 [ + - ]: 3 : ereport(NOTICE,
8453 : : (errmsg("column \"%s\" of relation \"%s\" is not an identity column, skipping",
8454 : : colName, RelationGetRelationName(rel))));
8455 : 3 : heap_freetuple(tuple);
1910 andres@anarazel.de 8456 : 3 : table_close(attrelation, RowExclusiveLock);
2565 peter_e@gmx.net 8457 : 3 : return InvalidObjectAddress;
8458 : : }
8459 : : }
8460 : :
8461 : 34 : attTup->attidentity = '\0';
8462 : 34 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
8463 : :
8464 [ - + ]: 34 : InvokeObjectPostAlterHook(RelationRelationId,
8465 : : RelationGetRelid(rel),
8466 : : attTup->attnum);
8467 : 34 : ObjectAddressSubSet(address, RelationRelationId,
8468 : : RelationGetRelid(rel), attnum);
8469 : 34 : heap_freetuple(tuple);
8470 : :
1910 andres@anarazel.de 8471 : 34 : table_close(attrelation, RowExclusiveLock);
8472 : :
8473 : : /*
8474 : : * Recurse to drop the identity from column in partitions. Identity is
8475 : : * not inherited in regular inheritance children so ignore them.
8476 : : */
89 peter@eisentraut.org 8477 [ + - + + ]:GNC 34 : if (recurse && ispartitioned)
8478 : : {
8479 : : List *children;
8480 : : ListCell *lc;
8481 : :
8482 : 3 : children = find_inheritance_children(RelationGetRelid(rel), lockmode);
8483 : :
8484 [ + - + + : 6 : foreach(lc, children)
+ + ]
8485 : : {
8486 : : Relation childrel;
8487 : :
8488 : 3 : childrel = table_open(lfirst_oid(lc), NoLock);
8489 : 3 : ATExecDropIdentity(childrel, colName, false, lockmode, recurse, true);
8490 : 3 : table_close(childrel, NoLock);
8491 : : }
8492 : : }
8493 : :
8494 [ + + ]: 34 : if (!recursing)
8495 : : {
8496 : : /* drop the internal sequence */
8497 : 16 : seqid = getIdentitySequence(RelationGetRelid(rel), attnum, false);
8498 : 16 : deleteDependencyRecordsForClass(RelationRelationId, seqid,
8499 : : RelationRelationId, DEPENDENCY_INTERNAL);
8500 : 16 : CommandCounterIncrement();
8501 : 16 : seqaddress.classId = RelationRelationId;
8502 : 16 : seqaddress.objectId = seqid;
8503 : 16 : seqaddress.objectSubId = 0;
8504 : 16 : performDeletion(&seqaddress, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
8505 : : }
8506 : :
2565 peter_e@gmx.net 8507 : 34 : return address;
8508 : : }
8509 : :
8510 : : /*
8511 : : * ALTER TABLE ALTER COLUMN SET EXPRESSION
8512 : : *
8513 : : * Return the address of the affected column.
8514 : : */
8515 : : static ObjectAddress
101 peter@eisentraut.org 8516 : 42 : ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
8517 : : Node *newExpr, LOCKMODE lockmode)
8518 : : {
8519 : : HeapTuple tuple;
8520 : : Form_pg_attribute attTup;
8521 : : AttrNumber attnum;
8522 : : Oid attrdefoid;
8523 : : ObjectAddress address;
8524 : : Expr *defval;
8525 : : NewColumnValue *newval;
8526 : : RawColumnDefault *rawEnt;
8527 : :
8528 : 42 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
8529 [ - + ]: 42 : if (!HeapTupleIsValid(tuple))
101 peter@eisentraut.org 8530 [ # # ]:UNC 0 : ereport(ERROR,
8531 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8532 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8533 : : colName, RelationGetRelationName(rel))));
8534 : :
101 peter@eisentraut.org 8535 :GNC 42 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8536 : 42 : attnum = attTup->attnum;
8537 : :
8538 [ - + ]: 42 : if (attnum <= 0)
101 peter@eisentraut.org 8539 [ # # ]:UNC 0 : ereport(ERROR,
8540 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8541 : : errmsg("cannot alter system column \"%s\"",
8542 : : colName)));
8543 : :
101 peter@eisentraut.org 8544 [ + + ]:GNC 42 : if (attTup->attgenerated != ATTRIBUTE_GENERATED_STORED)
8545 [ + - ]: 3 : ereport(ERROR,
8546 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8547 : : errmsg("column \"%s\" of relation \"%s\" is not a generated column",
8548 : : colName, RelationGetRelationName(rel))));
8549 : 39 : ReleaseSysCache(tuple);
8550 : :
8551 : : /*
8552 : : * Clear all the missing values if we're rewriting the table, since this
8553 : : * renders them pointless.
8554 : : */
8555 : 39 : RelationClearMissing(rel);
8556 : :
8557 : : /* make sure we don't conflict with later attribute modifications */
8558 : 39 : CommandCounterIncrement();
8559 : :
8560 : : /*
8561 : : * Find everything that depends on the column (constraints, indexes, etc),
8562 : : * and record enough information to let us recreate the objects after
8563 : : * rewrite.
8564 : : */
8565 : 39 : RememberAllDependentForRebuilding(tab, AT_SetExpression, rel, attnum, colName);
8566 : :
8567 : : /*
8568 : : * Drop the dependency records of the GENERATED expression, in particular
8569 : : * its INTERNAL dependency on the column, which would otherwise cause
8570 : : * dependency.c to refuse to perform the deletion.
8571 : : */
8572 : 39 : attrdefoid = GetAttrDefaultOid(RelationGetRelid(rel), attnum);
8573 [ - + ]: 39 : if (!OidIsValid(attrdefoid))
101 peter@eisentraut.org 8574 [ # # ]:UNC 0 : elog(ERROR, "could not find attrdef tuple for relation %u attnum %d",
8575 : : RelationGetRelid(rel), attnum);
101 peter@eisentraut.org 8576 :GNC 39 : (void) deleteDependencyRecordsFor(AttrDefaultRelationId, attrdefoid, false);
8577 : :
8578 : : /* Make above changes visible */
8579 : 39 : CommandCounterIncrement();
8580 : :
8581 : : /*
8582 : : * Get rid of the GENERATED expression itself. We use RESTRICT here for
8583 : : * safety, but at present we do not expect anything to depend on the
8584 : : * expression.
8585 : : */
8586 : 39 : RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT,
8587 : : false, false);
8588 : :
8589 : : /* Prepare to store the new expression, in the catalogs */
8590 : 39 : rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
8591 : 39 : rawEnt->attnum = attnum;
8592 : 39 : rawEnt->raw_default = newExpr;
8593 : 39 : rawEnt->missingMode = false;
8594 : 39 : rawEnt->generated = ATTRIBUTE_GENERATED_STORED;
8595 : :
8596 : : /* Store the generated expression */
8597 : 39 : AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
8598 : : false, true, false, NULL);
8599 : :
8600 : : /* Make above new expression visible */
8601 : 39 : CommandCounterIncrement();
8602 : :
8603 : : /* Prepare for table rewrite */
8604 : 39 : defval = (Expr *) build_column_default(rel, attnum);
8605 : :
8606 : 39 : newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
8607 : 39 : newval->attnum = attnum;
8608 : 39 : newval->expr = expression_planner(defval);
8609 : 39 : newval->is_generated = true;
8610 : :
8611 : 39 : tab->newvals = lappend(tab->newvals, newval);
8612 : 39 : tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
8613 : :
8614 : : /* Drop any pg_statistic entry for the column */
8615 : 39 : RemoveStatistics(RelationGetRelid(rel), attnum);
8616 : :
8617 [ - + ]: 39 : InvokeObjectPostAlterHook(RelationRelationId,
8618 : : RelationGetRelid(rel), attnum);
8619 : :
8620 : 39 : ObjectAddressSubSet(address, RelationRelationId,
8621 : : RelationGetRelid(rel), attnum);
101 peter@eisentraut.org 8622 :CBC 39 : return address;
8623 : : }
8624 : :
8625 : : /*
8626 : : * ALTER TABLE ALTER COLUMN DROP EXPRESSION
8627 : : */
8628 : : static void
1258 8629 : 22 : ATPrepDropExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, LOCKMODE lockmode)
8630 : : {
8631 : : /*
8632 : : * Reject ONLY if there are child tables. We could implement this, but it
8633 : : * is a bit complicated. GENERATED clauses must be attached to the column
8634 : : * definition and cannot be added later like DEFAULT, so if a child table
8635 : : * has a generation expression that the parent does not have, the child
8636 : : * column will necessarily be an attislocal column. So to implement ONLY
8637 : : * here, we'd need extra code to update attislocal of the direct child
8638 : : * tables, somewhat similar to how DROP COLUMN does it, so that the
8639 : : * resulting state can be properly dumped and restored.
8640 : : */
8641 [ + + + + ]: 28 : if (!recurse &&
1082 alvherre@alvh.no-ip. 8642 : 6 : find_inheritance_children(RelationGetRelid(rel), lockmode))
1258 peter@eisentraut.org 8643 [ + - ]: 3 : ereport(ERROR,
8644 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8645 : : errmsg("ALTER TABLE / DROP EXPRESSION must be applied to child tables too")));
8646 : :
8647 : : /*
8648 : : * Cannot drop generation expression from inherited columns.
8649 : : */
1552 8650 [ + + ]: 19 : if (!recursing)
8651 : : {
8652 : : HeapTuple tuple;
8653 : : Form_pg_attribute attTup;
8654 : :
8655 : 16 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), cmd->name);
8656 [ - + ]: 16 : if (!HeapTupleIsValid(tuple))
1552 peter@eisentraut.org 8657 [ # # ]:UBC 0 : ereport(ERROR,
8658 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8659 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8660 : : cmd->name, RelationGetRelationName(rel))));
8661 : :
1552 peter@eisentraut.org 8662 :CBC 16 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8663 : :
8664 [ + + ]: 16 : if (attTup->attinhcount > 0)
8665 [ + - ]: 3 : ereport(ERROR,
8666 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
8667 : : errmsg("cannot drop generation expression from inherited column")));
8668 : : }
8669 : 16 : }
8670 : :
8671 : : /*
8672 : : * Return the address of the affected column.
8673 : : */
8674 : : static ObjectAddress
8675 : 16 : ATExecDropExpression(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode)
8676 : : {
8677 : : HeapTuple tuple;
8678 : : Form_pg_attribute attTup;
8679 : : AttrNumber attnum;
8680 : : Relation attrelation;
8681 : : Oid attrdefoid;
8682 : : ObjectAddress address;
8683 : :
8684 : 16 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
8685 : 16 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
8686 [ - + ]: 16 : if (!HeapTupleIsValid(tuple))
1552 peter@eisentraut.org 8687 [ # # ]:UBC 0 : ereport(ERROR,
8688 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8689 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8690 : : colName, RelationGetRelationName(rel))));
8691 : :
1552 peter@eisentraut.org 8692 :CBC 16 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
8693 : 16 : attnum = attTup->attnum;
8694 : :
8695 [ - + ]: 16 : if (attnum <= 0)
1552 peter@eisentraut.org 8696 [ # # ]:UBC 0 : ereport(ERROR,
8697 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8698 : : errmsg("cannot alter system column \"%s\"",
8699 : : colName)));
8700 : :
1552 peter@eisentraut.org 8701 [ + + ]:CBC 16 : if (attTup->attgenerated != ATTRIBUTE_GENERATED_STORED)
8702 : : {
8703 [ + + ]: 6 : if (!missing_ok)
8704 [ + - ]: 3 : ereport(ERROR,
8705 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
8706 : : errmsg("column \"%s\" of relation \"%s\" is not a stored generated column",
8707 : : colName, RelationGetRelationName(rel))));
8708 : : else
8709 : : {
8710 [ + - ]: 3 : ereport(NOTICE,
8711 : : (errmsg("column \"%s\" of relation \"%s\" is not a stored generated column, skipping",
8712 : : colName, RelationGetRelationName(rel))));
8713 : 3 : heap_freetuple(tuple);
8714 : 3 : table_close(attrelation, RowExclusiveLock);
8715 : 3 : return InvalidObjectAddress;
8716 : : }
8717 : : }
8718 : :
8719 : : /*
8720 : : * Mark the column as no longer generated. (The atthasdef flag needs to
8721 : : * get cleared too, but RemoveAttrDefault will handle that.)
8722 : : */
8723 : 10 : attTup->attgenerated = '\0';
8724 : 10 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
8725 : :
8726 [ - + ]: 10 : InvokeObjectPostAlterHook(RelationRelationId,
8727 : : RelationGetRelid(rel),
8728 : : attnum);
8729 : 10 : heap_freetuple(tuple);
8730 : :
8731 : 10 : table_close(attrelation, RowExclusiveLock);
8732 : :
8733 : : /*
8734 : : * Drop the dependency records of the GENERATED expression, in particular
8735 : : * its INTERNAL dependency on the column, which would otherwise cause
8736 : : * dependency.c to refuse to perform the deletion.
8737 : : */
755 tgl@sss.pgh.pa.us 8738 : 10 : attrdefoid = GetAttrDefaultOid(RelationGetRelid(rel), attnum);
8739 [ - + ]: 10 : if (!OidIsValid(attrdefoid))
755 tgl@sss.pgh.pa.us 8740 [ # # ]:UBC 0 : elog(ERROR, "could not find attrdef tuple for relation %u attnum %d",
8741 : : RelationGetRelid(rel), attnum);
755 tgl@sss.pgh.pa.us 8742 :CBC 10 : (void) deleteDependencyRecordsFor(AttrDefaultRelationId, attrdefoid, false);
8743 : :
8744 : : /* Make above changes visible */
8745 : 10 : CommandCounterIncrement();
8746 : :
8747 : : /*
8748 : : * Get rid of the GENERATED expression itself. We use RESTRICT here for
8749 : : * safety, but at present we do not expect anything to depend on the
8750 : : * default.
8751 : : */
8752 : 10 : RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT,
8753 : : false, false);
8754 : :
8755 : 10 : ObjectAddressSubSet(address, RelationRelationId,
8756 : : RelationGetRelid(rel), attnum);
1552 peter@eisentraut.org 8757 : 10 : return address;
8758 : : }
8759 : :
8760 : : /*
8761 : : * ALTER TABLE ALTER COLUMN SET STATISTICS
8762 : : *
8763 : : * Return value is the address of the modified column
8764 : : */
8765 : : static ObjectAddress
1586 8766 : 82 : ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newValue, LOCKMODE lockmode)
8767 : : {
28 nathan@postgresql.or 8768 :GNC 82 : int newtarget = 0;
8769 : : bool newtarget_default;
8770 : : Relation attrelation;
8771 : : HeapTuple tuple,
8772 : : newtuple;
8773 : : Form_pg_attribute attrtuple;
8774 : : AttrNumber attnum;
8775 : : ObjectAddress address;
8776 : : Datum repl_val[Natts_pg_attribute];
8777 : : bool repl_null[Natts_pg_attribute];
8778 : : bool repl_repl[Natts_pg_attribute];
8779 : :
8780 : : /*
8781 : : * We allow referencing columns by numbers only for indexes, since table
8782 : : * column numbers could contain gaps if columns are later dropped.
8783 : : */
2277 alvherre@alvh.no-ip. 8784 [ + + ]:CBC 82 : if (rel->rd_rel->relkind != RELKIND_INDEX &&
8785 [ + - - + ]: 50 : rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
8786 : : !colName)
2412 simon@2ndQuadrant.co 8787 [ # # ]:UBC 0 : ereport(ERROR,
8788 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8789 : : errmsg("cannot refer to non-index column by number")));
8790 : :
8791 : : /* -1 was used in previous versions for the default setting */
28 peter@eisentraut.org 8792 [ + - + + ]:GNC 82 : if (newValue && intVal(newValue) != -1)
8793 : : {
92 8794 : 60 : newtarget = intVal(newValue);
28 8795 : 60 : newtarget_default = false;
8796 : : }
8797 : : else
8798 : 22 : newtarget_default = true;
8799 : :
8800 [ + + ]: 82 : if (!newtarget_default)
8801 : : {
8802 : : /*
8803 : : * Limit target to a sane range
8804 : : */
8805 [ - + ]: 60 : if (newtarget < 0)
8806 : : {
28 peter@eisentraut.org 8807 [ # # ]:UNC 0 : ereport(ERROR,
8808 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
8809 : : errmsg("statistics target %d is too low",
8810 : : newtarget)));
8811 : : }
28 peter@eisentraut.org 8812 [ - + ]:GNC 60 : else if (newtarget > MAX_STATISTICS_TARGET)
8813 : : {
28 peter@eisentraut.org 8814 :UNC 0 : newtarget = MAX_STATISTICS_TARGET;
8815 [ # # ]: 0 : ereport(WARNING,
8816 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
8817 : : errmsg("lowering statistics target to %d",
8818 : : newtarget)));
8819 : : }
8820 : : }
8821 : :
1910 andres@anarazel.de 8822 :CBC 82 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
8823 : :
2412 simon@2ndQuadrant.co 8824 [ + + ]: 82 : if (colName)
8825 : : {
92 peter@eisentraut.org 8826 :GNC 50 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
8827 : :
2412 simon@2ndQuadrant.co 8828 [ + + ]:CBC 50 : if (!HeapTupleIsValid(tuple))
8829 [ + - ]: 6 : ereport(ERROR,
8830 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8831 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8832 : : colName, RelationGetRelationName(rel))));
8833 : : }
8834 : : else
8835 : : {
92 peter@eisentraut.org 8836 :GNC 32 : tuple = SearchSysCacheAttNum(RelationGetRelid(rel), colNum);
8837 : :
2412 simon@2ndQuadrant.co 8838 [ + + ]:CBC 32 : if (!HeapTupleIsValid(tuple))
8839 [ + - ]: 6 : ereport(ERROR,
8840 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8841 : : errmsg("column number %d of relation \"%s\" does not exist",
8842 : : colNum, RelationGetRelationName(rel))));
8843 : : }
8844 : :
7284 tgl@sss.pgh.pa.us 8845 : 70 : attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
8846 : :
3308 alvherre@alvh.no-ip. 8847 : 70 : attnum = attrtuple->attnum;
8848 [ - + ]: 70 : if (attnum <= 0)
7574 tgl@sss.pgh.pa.us 8849 [ # # ]:UBC 0 : ereport(ERROR,
8850 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8851 : : errmsg("cannot alter system column \"%s\"",
8852 : : colName)));
8853 : :
2099 alvherre@alvh.no-ip. 8854 [ + + ]:CBC 70 : if (rel->rd_rel->relkind == RELKIND_INDEX ||
8855 [ - + ]: 44 : rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
8856 : : {
8857 [ + + ]: 26 : if (attnum > rel->rd_index->indnkeyatts)
8858 [ + - ]: 3 : ereport(ERROR,
8859 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8860 : : errmsg("cannot alter statistics on included column \"%s\" of index \"%s\"",
8861 : : NameStr(attrtuple->attname), RelationGetRelationName(rel))));
8862 [ + + ]: 23 : else if (rel->rd_index->indkey.values[attnum - 1] != 0)
8863 [ + - ]: 9 : ereport(ERROR,
8864 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8865 : : errmsg("cannot alter statistics on non-expression column \"%s\" of index \"%s\"",
8866 : : NameStr(attrtuple->attname), RelationGetRelationName(rel)),
8867 : : errhint("Alter statistics on table column instead.")));
8868 : : }
8869 : :
8870 : : /* Build new tuple. */
92 peter@eisentraut.org 8871 :GNC 58 : memset(repl_null, false, sizeof(repl_null));
8872 : 58 : memset(repl_repl, false, sizeof(repl_repl));
28 8873 [ + + ]: 58 : if (!newtarget_default)
92 8874 : 36 : repl_val[Anum_pg_attribute_attstattarget - 1] = newtarget;
8875 : : else
8876 : 22 : repl_null[Anum_pg_attribute_attstattarget - 1] = true;
8877 : 58 : repl_repl[Anum_pg_attribute_attstattarget - 1] = true;
8878 : 58 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(attrelation),
8879 : : repl_val, repl_null, repl_repl);
8880 : 58 : CatalogTupleUpdate(attrelation, &tuple->t_self, newtuple);
8881 : :
4046 rhaas@postgresql.org 8882 [ - + ]:CBC 58 : InvokeObjectPostAlterHook(RelationRelationId,
8883 : : RelationGetRelid(rel),
8884 : : attrtuple->attnum);
3308 alvherre@alvh.no-ip. 8885 : 58 : ObjectAddressSubSet(address, RelationRelationId,
8886 : : RelationGetRelid(rel), attnum);
8887 : :
92 peter@eisentraut.org 8888 :GNC 58 : heap_freetuple(newtuple);
8889 : :
8890 : 58 : ReleaseSysCache(tuple);
8891 : :
1910 andres@anarazel.de 8892 :CBC 58 : table_close(attrelation, RowExclusiveLock);
8893 : :
3308 alvherre@alvh.no-ip. 8894 : 58 : return address;
8895 : : }
8896 : :
8897 : : /*
8898 : : * Return value is the address of the modified column
8899 : : */
8900 : : static ObjectAddress
5196 rhaas@postgresql.org 8901 : 16 : ATExecSetOptions(Relation rel, const char *colName, Node *options,
8902 : : bool isReset, LOCKMODE lockmode)
8903 : : {
8904 : : Relation attrelation;
8905 : : HeapTuple tuple,
8906 : : newtuple;
8907 : : Form_pg_attribute attrtuple;
8908 : : AttrNumber attnum;
8909 : : Datum datum,
8910 : : newOptions;
8911 : : bool isnull;
8912 : : ObjectAddress address;
8913 : : Datum repl_val[Natts_pg_attribute];
8914 : : bool repl_null[Natts_pg_attribute];
8915 : : bool repl_repl[Natts_pg_attribute];
8916 : :
1910 andres@anarazel.de 8917 : 16 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
8918 : :
5196 rhaas@postgresql.org 8919 : 16 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
8920 : :
5369 tgl@sss.pgh.pa.us 8921 [ - + ]: 16 : if (!HeapTupleIsValid(tuple))
5369 tgl@sss.pgh.pa.us 8922 [ # # ]:UBC 0 : ereport(ERROR,
8923 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
8924 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
8925 : : colName, RelationGetRelationName(rel))));
5369 tgl@sss.pgh.pa.us 8926 :CBC 16 : attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
8927 : :
3308 alvherre@alvh.no-ip. 8928 : 16 : attnum = attrtuple->attnum;
8929 [ - + ]: 16 : if (attnum <= 0)
5369 tgl@sss.pgh.pa.us 8930 [ # # ]:UBC 0 : ereport(ERROR,
8931 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
8932 : : errmsg("cannot alter system column \"%s\"",
8933 : : colName)));
8934 : :
8935 : : /* Generate new proposed attoptions (text array) */
5196 rhaas@postgresql.org 8936 :CBC 16 : datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions,
8937 : : &isnull);
8938 [ + + ]: 16 : newOptions = transformRelOptions(isnull ? (Datum) 0 : datum,
2635 andres@anarazel.de 8939 : 16 : castNode(List, options), NULL, NULL,
8940 : : false, isReset);
8941 : : /* Validate new options */
5196 rhaas@postgresql.org 8942 : 16 : (void) attribute_reloptions(newOptions, true);
8943 : :
8944 : : /* Build new tuple. */
8945 : 16 : memset(repl_null, false, sizeof(repl_null));
8946 : 16 : memset(repl_repl, false, sizeof(repl_repl));
8947 [ + - ]: 16 : if (newOptions != (Datum) 0)
8948 : 16 : repl_val[Anum_pg_attribute_attoptions - 1] = newOptions;
8949 : : else
5196 rhaas@postgresql.org 8950 :UBC 0 : repl_null[Anum_pg_attribute_attoptions - 1] = true;
5196 rhaas@postgresql.org 8951 :CBC 16 : repl_repl[Anum_pg_attribute_attoptions - 1] = true;
8952 : 16 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(attrelation),
8953 : : repl_val, repl_null, repl_repl);
8954 : :
8955 : : /* Update system catalog. */
2630 alvherre@alvh.no-ip. 8956 : 16 : CatalogTupleUpdate(attrelation, &newtuple->t_self, newtuple);
8957 : :
4046 rhaas@postgresql.org 8958 [ - + ]: 16 : InvokeObjectPostAlterHook(RelationRelationId,
8959 : : RelationGetRelid(rel),
8960 : : attrtuple->attnum);
3308 alvherre@alvh.no-ip. 8961 : 16 : ObjectAddressSubSet(address, RelationRelationId,
8962 : : RelationGetRelid(rel), attnum);
8963 : :
5196 rhaas@postgresql.org 8964 : 16 : heap_freetuple(newtuple);
8965 : :
4046 8966 : 16 : ReleaseSysCache(tuple);
8967 : :
1910 andres@anarazel.de 8968 : 16 : table_close(attrelation, RowExclusiveLock);
8969 : :
3308 alvherre@alvh.no-ip. 8970 : 16 : return address;
8971 : : }
8972 : :
8973 : : /*
8974 : : * Helper function for ATExecSetStorage and ATExecSetCompression
8975 : : *
8976 : : * Set the attstorage and/or attcompression fields for index columns
8977 : : * associated with the specified table column.
8978 : : */
8979 : : static void
1122 rhaas@postgresql.org 8980 : 141 : SetIndexStorageProperties(Relation rel, Relation attrelation,
8981 : : AttrNumber attnum,
8982 : : bool setstorage, char newstorage,
8983 : : bool setcompression, char newcompression,
8984 : : LOCKMODE lockmode)
8985 : : {
8986 : : ListCell *lc;
8987 : :
8988 [ + + + + : 177 : foreach(lc, RelationGetIndexList(rel))
+ + ]
8989 : : {
8990 : 36 : Oid indexoid = lfirst_oid(lc);
8991 : : Relation indrel;
8992 : 36 : AttrNumber indattnum = 0;
8993 : : HeapTuple tuple;
8994 : :
8995 : 36 : indrel = index_open(indexoid, lockmode);
8996 : :
8997 [ + + ]: 60 : for (int i = 0; i < indrel->rd_index->indnatts; i++)
8998 : : {
8999 [ + + ]: 39 : if (indrel->rd_index->indkey.values[i] == attnum)
9000 : : {
9001 : 15 : indattnum = i + 1;
9002 : 15 : break;
9003 : : }
9004 : : }
9005 : :
9006 [ + + ]: 36 : if (indattnum == 0)
9007 : : {
9008 : 21 : index_close(indrel, lockmode);
9009 : 21 : continue;
9010 : : }
9011 : :
9012 : 15 : tuple = SearchSysCacheCopyAttNum(RelationGetRelid(indrel), indattnum);
9013 : :
9014 [ + - ]: 15 : if (HeapTupleIsValid(tuple))
9015 : : {
1053 tgl@sss.pgh.pa.us 9016 : 15 : Form_pg_attribute attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
9017 : :
9018 [ + + ]: 15 : if (setstorage)
1122 rhaas@postgresql.org 9019 : 12 : attrtuple->attstorage = newstorage;
9020 : :
1053 tgl@sss.pgh.pa.us 9021 [ + + ]: 15 : if (setcompression)
9022 : 3 : attrtuple->attcompression = newcompression;
9023 : :
1122 rhaas@postgresql.org 9024 : 15 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
9025 : :
9026 [ - + ]: 15 : InvokeObjectPostAlterHook(RelationRelationId,
9027 : : RelationGetRelid(rel),
9028 : : attrtuple->attnum);
9029 : :
9030 : 15 : heap_freetuple(tuple);
9031 : : }
9032 : :
9033 : 15 : index_close(indrel, lockmode);
9034 : : }
9035 : 141 : }
9036 : :
9037 : : /*
9038 : : * ALTER TABLE ALTER COLUMN SET STORAGE
9039 : : *
9040 : : * Return value is the address of the modified column
9041 : : */
9042 : : static ObjectAddress
5009 simon@2ndQuadrant.co 9043 : 117 : ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode)
9044 : : {
9045 : : Relation attrelation;
9046 : : HeapTuple tuple;
9047 : : Form_pg_attribute attrtuple;
9048 : : AttrNumber attnum;
9049 : : ObjectAddress address;
9050 : :
1910 andres@anarazel.de 9051 : 117 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
9052 : :
7284 tgl@sss.pgh.pa.us 9053 : 117 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
9054 : :
9055 [ + + ]: 117 : if (!HeapTupleIsValid(tuple))
9056 [ + - ]: 6 : ereport(ERROR,
9057 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
9058 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
9059 : : colName, RelationGetRelationName(rel))));
9060 : 111 : attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
9061 : :
3308 alvherre@alvh.no-ip. 9062 : 111 : attnum = attrtuple->attnum;
9063 [ - + ]: 111 : if (attnum <= 0)
7284 tgl@sss.pgh.pa.us 9064 [ # # ]:UBC 0 : ereport(ERROR,
9065 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9066 : : errmsg("cannot alter system column \"%s\"",
9067 : : colName)));
9068 : :
641 peter@eisentraut.org 9069 :CBC 111 : attrtuple->attstorage = GetAttributeStorage(attrtuple->atttypid, strVal(newValue));
9070 : :
2630 alvherre@alvh.no-ip. 9071 : 111 : CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
9072 : :
4046 rhaas@postgresql.org 9073 [ - + ]: 111 : InvokeObjectPostAlterHook(RelationRelationId,
9074 : : RelationGetRelid(rel),
9075 : : attrtuple->attnum);
9076 : :
9077 : : /*
9078 : : * Apply the change to indexes as well (only for simple index columns,
9079 : : * matching behavior of index.c ConstructTupleDescriptor()).
9080 : : */
1122 9081 : 111 : SetIndexStorageProperties(rel, attrelation, attnum,
641 peter@eisentraut.org 9082 : 111 : true, attrtuple->attstorage,
9083 : : false, 0,
9084 : : lockmode);
9085 : :
9086 : 111 : heap_freetuple(tuple);
9087 : :
1910 andres@anarazel.de 9088 : 111 : table_close(attrelation, RowExclusiveLock);
9089 : :
3308 alvherre@alvh.no-ip. 9090 : 111 : ObjectAddressSubSet(address, RelationRelationId,
9091 : : RelationGetRelid(rel), attnum);
9092 : 111 : return address;
9093 : : }
9094 : :
9095 : :
9096 : : /*
9097 : : * ALTER TABLE DROP COLUMN
9098 : : *
9099 : : * DROP COLUMN cannot use the normal ALTER TABLE recursion mechanism,
9100 : : * because we have to decide at runtime whether to recurse or not depending
9101 : : * on whether attinhcount goes to zero or not. (We can't check this in a
9102 : : * static pre-pass because it won't handle multiple inheritance situations
9103 : : * correctly.)
9104 : : */
9105 : : static void
4891 peter_e@gmx.net 9106 : 805 : ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
9107 : : AlterTableCmd *cmd, LOCKMODE lockmode,
9108 : : AlterTableUtilityContext *context)
9109 : : {
9110 [ + + + + ]: 805 : if (rel->rd_rel->reloftype && !recursing)
5014 9111 [ + - ]: 3 : ereport(ERROR,
9112 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
9113 : : errmsg("cannot drop column from typed table")));
9114 : :
4949 9115 [ + + ]: 802 : if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
1551 tgl@sss.pgh.pa.us 9116 : 41 : ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);
9117 : :
5014 peter_e@gmx.net 9118 [ + + ]: 799 : if (recurse)
489 alvherre@alvh.no-ip. 9119 : 665 : cmd->recurse = true;
5014 peter_e@gmx.net 9120 : 799 : }
9121 : :
9122 : : /*
9123 : : * Drops column 'colName' from relation 'rel' and returns the address of the
9124 : : * dropped column. The column is also dropped (or marked as no longer
9125 : : * inherited from relation) from the relation's inheritance children, if any.
9126 : : *
9127 : : * In the recursive invocations for inheritance child relations, instead of
9128 : : * dropping the column directly (if to be dropped at all), its object address
9129 : : * is added to 'addrs', which must be non-NULL in such invocations. All
9130 : : * columns are dropped at the same time after all the children have been
9131 : : * checked recursively.
9132 : : */
9133 : : static ObjectAddress
5541 tgl@sss.pgh.pa.us 9134 : 1077 : ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
9135 : : DropBehavior behavior,
9136 : : bool recurse, bool recursing,
9137 : : bool missing_ok, LOCKMODE lockmode,
9138 : : ObjectAddresses *addrs)
9139 : : {
9140 : : HeapTuple tuple;
9141 : : Form_pg_attribute targetatt;
9142 : : AttrNumber attnum;
9143 : : List *children;
9144 : : ObjectAddress object;
9145 : : bool is_expr;
9146 : :
9147 : : /* At top level, permission check was done in ATPrepCmd, else do it */
7284 9148 [ + + ]: 1077 : if (recursing)
1011 peter@eisentraut.org 9149 : 278 : ATSimplePermissions(AT_DropColumn, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
9150 : :
9151 : : /* Initialize addrs on the first invocation */
1645 michael@paquier.xyz 9152 [ + + - + ]: 1077 : Assert(!recursing || addrs != NULL);
9153 : :
9154 : : /* since this function recurses, it could be driven to stack overflow */
58 akorotkov@postgresql 9155 : 1077 : check_stack_depth();
9156 : :
1645 michael@paquier.xyz 9157 [ + + ]: 1077 : if (!recursing)
9158 : 799 : addrs = new_object_addresses();
9159 : :
9160 : : /*
9161 : : * get the number of the attribute
9162 : : */
7284 tgl@sss.pgh.pa.us 9163 : 1077 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
5161 bruce@momjian.us 9164 [ + + ]: 1077 : if (!HeapTupleIsValid(tuple))
9165 : : {
9166 [ + + ]: 27 : if (!missing_ok)
9167 : : {
5382 andrew@dunslane.net 9168 [ + - ]: 18 : ereport(ERROR,
9169 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
9170 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
9171 : : colName, RelationGetRelationName(rel))));
9172 : : }
9173 : : else
9174 : : {
9175 [ + - ]: 9 : ereport(NOTICE,
9176 : : (errmsg("column \"%s\" of relation \"%s\" does not exist, skipping",
9177 : : colName, RelationGetRelationName(rel))));
3308 alvherre@alvh.no-ip. 9178 : 9 : return InvalidObjectAddress;
9179 : : }
9180 : : }
7284 tgl@sss.pgh.pa.us 9181 : 1050 : targetatt = (Form_pg_attribute) GETSTRUCT(tuple);
9182 : :
9183 : 1050 : attnum = targetatt->attnum;
9184 : :
9185 : : /* Can't drop a system attribute */
1972 andres@anarazel.de 9186 [ + + ]: 1050 : if (attnum <= 0)
7284 tgl@sss.pgh.pa.us 9187 [ + - ]: 3 : ereport(ERROR,
9188 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9189 : : errmsg("cannot drop system column \"%s\"",
9190 : : colName)));
9191 : :
9192 : : /*
9193 : : * Don't drop inherited columns, unless recursing (presumably from a drop
9194 : : * of the parent column)
9195 : : */
9196 [ + + + + ]: 1047 : if (targetatt->attinhcount > 0 && !recursing)
9197 [ + - ]: 24 : ereport(ERROR,
9198 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
9199 : : errmsg("cannot drop inherited column \"%s\"",
9200 : : colName)));
9201 : :
9202 : : /*
9203 : : * Don't drop columns used in the partition key, either. (If we let this
9204 : : * go through, the key column's dependencies would cause a cascaded drop
9205 : : * of the whole table, which is surely not what the user expected.)
9206 : : */
2292 rhaas@postgresql.org 9207 [ + + ]: 1023 : if (has_partition_attrs(rel,
9208 : : bms_make_singleton(attnum - FirstLowInvalidHeapAttributeNumber),
9209 : : &is_expr))
1728 tgl@sss.pgh.pa.us 9210 [ + - ]: 15 : ereport(ERROR,
9211 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
9212 : : errmsg("cannot drop column \"%s\" because it is part of the partition key of relation \"%s\"",
9213 : : colName, RelationGetRelationName(rel))));
9214 : :
7284 9215 : 1008 : ReleaseSysCache(tuple);
9216 : :
9217 : : /*
9218 : : * Propagate to children as appropriate. Unlike most other ALTER
9219 : : * routines, we have to do this one level of recursion at a time; we can't
9220 : : * use find_all_inheritors to do it in one pass.
9221 : : */
9222 : : children =
1082 alvherre@alvh.no-ip. 9223 : 1008 : find_inheritance_children(RelationGetRelid(rel), lockmode);
9224 : :
7284 tgl@sss.pgh.pa.us 9225 [ + + ]: 1008 : if (children)
9226 : : {
9227 : : Relation attr_rel;
9228 : : ListCell *child;
9229 : :
9230 : : /*
9231 : : * In case of a partitioned table, the column must be dropped from the
9232 : : * partitions as well.
9233 : : */
2685 rhaas@postgresql.org 9234 [ + + + + ]: 151 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !recurse)
9235 [ + - ]: 3 : ereport(ERROR,
9236 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
9237 : : errmsg("cannot drop column from only the partitioned table when partitions exist"),
9238 : : errhint("Do not specify the ONLY keyword.")));
9239 : :
1910 andres@anarazel.de 9240 : 148 : attr_rel = table_open(AttributeRelationId, RowExclusiveLock);
7926 tgl@sss.pgh.pa.us 9241 [ + - + + : 441 : foreach(child, children)
+ + ]
9242 : : {
7263 neilc@samurai.com 9243 : 296 : Oid childrelid = lfirst_oid(child);
9244 : : Relation childrel;
9245 : : Form_pg_attribute childatt;
9246 : :
9247 : : /* find_inheritance_children already got lock */
1910 andres@anarazel.de 9248 : 296 : childrel = table_open(childrelid, NoLock);
5919 tgl@sss.pgh.pa.us 9249 : 296 : CheckTableNotInUse(childrel, "ALTER TABLE");
9250 : :
7875 9251 : 296 : tuple = SearchSysCacheCopyAttName(childrelid, colName);
2489 9252 [ - + ]: 296 : if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
7574 tgl@sss.pgh.pa.us 9253 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
9254 : : colName, childrelid);
7875 tgl@sss.pgh.pa.us 9255 :CBC 296 : childatt = (Form_pg_attribute) GETSTRUCT(tuple);
9256 : :
2489 9257 [ - + ]: 296 : if (childatt->attinhcount <= 0) /* shouldn't happen */
7574 tgl@sss.pgh.pa.us 9258 [ # # ]:UBC 0 : elog(ERROR, "relation %u has non-inherited attribute \"%s\"",
9259 : : childrelid, colName);
9260 : :
7284 tgl@sss.pgh.pa.us 9261 [ + + ]:CBC 296 : if (recurse)
9262 : : {
9263 : : /*
9264 : : * If the child column has other definition sources, just
9265 : : * decrement its inheritance count; if not, recurse to delete
9266 : : * it.
9267 : : */
9268 [ + - + + ]: 284 : if (childatt->attinhcount == 1 && !childatt->attislocal)
9269 : : {
9270 : : /* Time to delete this child column, too */
5541 9271 : 278 : ATExecDropColumn(wqueue, childrel, colName,
9272 : : behavior, true, true,
9273 : : false, lockmode, addrs);
9274 : : }
9275 : : else
9276 : : {
9277 : : /* Child column must survive my deletion */
7284 9278 : 6 : childatt->attinhcount--;
9279 : :
2630 alvherre@alvh.no-ip. 9280 : 6 : CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple);
9281 : :
9282 : : /* Make update visible */
7284 tgl@sss.pgh.pa.us 9283 : 6 : CommandCounterIncrement();
9284 : : }
9285 : : }
9286 : : else
9287 : : {
9288 : : /*
9289 : : * If we were told to drop ONLY in this table (no recursion),
9290 : : * we need to mark the inheritors' attributes as locally
9291 : : * defined rather than inherited.
9292 : : */
7875 9293 : 12 : childatt->attinhcount--;
7284 9294 : 12 : childatt->attislocal = true;
9295 : :
2630 alvherre@alvh.no-ip. 9296 : 12 : CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple);
9297 : :
9298 : : /* Make update visible */
7284 tgl@sss.pgh.pa.us 9299 : 12 : CommandCounterIncrement();
9300 : : }
9301 : :
7875 9302 : 293 : heap_freetuple(tuple);
9303 : :
1910 andres@anarazel.de 9304 : 293 : table_close(childrel, NoLock);
9305 : : }
9306 : 145 : table_close(attr_rel, RowExclusiveLock);
9307 : : }
9308 : :
9309 : : /* Add object to delete */
6940 tgl@sss.pgh.pa.us 9310 : 1002 : object.classId = RelationRelationId;
7284 9311 : 1002 : object.objectId = RelationGetRelid(rel);
7926 9312 : 1002 : object.objectSubId = attnum;
1645 michael@paquier.xyz 9313 : 1002 : add_exact_object_address(&object, addrs);
9314 : :
9315 [ + + ]: 1002 : if (!recursing)
9316 : : {
9317 : : /* Recursion has ended, drop everything that was collected */
9318 : 727 : performMultipleDeletions(addrs, behavior, 0);
9319 : 703 : free_object_addresses(addrs);
9320 : : }
9321 : :
233 alvherre@alvh.no-ip. 9322 : 978 : return object;
9323 : : }
9324 : :
9325 : : /*
9326 : : * Prepare to add a primary key on an inheritance parent, by adding NOT NULL
9327 : : * constraint on its children.
9328 : : */
9329 : : static void
233 alvherre@alvh.no-ip. 9330 :GNC 4616 : ATPrepAddPrimaryKey(List **wqueue, Relation rel, AlterTableCmd *cmd,
9331 : : LOCKMODE lockmode, AlterTableUtilityContext *context)
9332 : : {
9333 : : List *children;
9334 : 4616 : List *newconstrs = NIL;
9335 : : ListCell *lc;
9336 : : IndexStmt *indexstmt;
9337 : :
9338 : : /* No work if not creating a primary key */
226 9339 [ - + ]: 4616 : if (!IsA(cmd->def, IndexStmt))
226 alvherre@alvh.no-ip. 9340 :UNC 0 : return;
226 alvherre@alvh.no-ip. 9341 :GNC 4616 : indexstmt = castNode(IndexStmt, cmd->def);
9342 [ + + ]: 4616 : if (!indexstmt->primary)
9343 : 1944 : return;
9344 : :
9345 : : /* No work if no legacy inheritance children are present */
233 9346 [ + + ]: 2672 : if (rel->rd_rel->relkind != RELKIND_RELATION ||
9347 [ + + ]: 2606 : !rel->rd_rel->relhassubclass)
9348 : 2652 : return;
9349 : :
9350 : 20 : children = find_inheritance_children(RelationGetRelid(rel), lockmode);
9351 : :
226 9352 [ + - + + : 40 : foreach(lc, indexstmt->indexParams)
+ + ]
9353 : : {
233 9354 : 20 : IndexElem *elem = lfirst_node(IndexElem, lc);
9355 : : Constraint *nnconstr;
9356 : :
9357 [ - + ]: 20 : Assert(elem->expr == NULL);
9358 : :
9359 : 20 : nnconstr = makeNode(Constraint);
9360 : 20 : nnconstr->contype = CONSTR_NOTNULL;
9361 : 20 : nnconstr->conname = NULL; /* XXX use PK name? */
9362 : 20 : nnconstr->inhcount = 1;
9363 : 20 : nnconstr->deferrable = false;
9364 : 20 : nnconstr->initdeferred = false;
9365 : 20 : nnconstr->location = -1;
9366 : 20 : nnconstr->keys = list_make1(makeString(elem->name));
9367 : 20 : nnconstr->skip_validation = false;
9368 : 20 : nnconstr->initially_valid = true;
9369 : :
9370 : 20 : newconstrs = lappend(newconstrs, nnconstr);
9371 : : }
9372 : :
9373 [ + - + + : 60 : foreach(lc, children)
+ + ]
9374 : : {
9375 : 40 : Oid childrelid = lfirst_oid(lc);
9376 : 40 : Relation childrel = table_open(childrelid, NoLock);
9377 : 40 : AlterTableCmd *newcmd = makeNode(AlterTableCmd);
9378 : : ListCell *lc2;
9379 : :
9380 : 40 : newcmd->subtype = AT_AddConstraint;
9381 : 40 : newcmd->recurse = true;
9382 : :
9383 [ + - + + : 80 : foreach(lc2, newconstrs)
+ + ]
9384 : : {
9385 : : /* ATPrepCmd copies newcmd, so we can scribble on it here */
9386 : 40 : newcmd->def = lfirst(lc2);
9387 : :
9388 : 40 : ATPrepCmd(wqueue, childrel, newcmd,
9389 : : true, false, lockmode, context);
9390 : : }
9391 : :
9392 : 40 : table_close(childrel, NoLock);
9393 : : }
9394 : : }
9395 : :
9396 : : /*
9397 : : * ALTER TABLE ADD INDEX
9398 : : *
9399 : : * There is no such command in the grammar, but parse_utilcmd.c converts
9400 : : * UNIQUE and PRIMARY KEY constraints into AT_AddIndex subcommands. This lets
9401 : : * us schedule creation of the index at the appropriate time during ALTER.
9402 : : *
9403 : : * Return value is the address of the new index.
9404 : : */
9405 : : static ObjectAddress
7284 tgl@sss.pgh.pa.us 9406 :CBC 717 : ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
9407 : : IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
9408 : : {
9409 : : bool check_rights;
9410 : : bool skip_build;
9411 : : bool quiet;
9412 : : ObjectAddress address;
9413 : :
9414 [ - + ]: 717 : Assert(IsA(stmt, IndexStmt));
4290 9415 [ - + ]: 717 : Assert(!stmt->concurrent);
9416 : :
9417 : : /* The IndexStmt has already been through transformIndexStmt */
3339 9418 [ - + ]: 717 : Assert(stmt->transformed);
9419 : :
9420 : : /* suppress schema rights check when rebuilding existing index */
7284 9421 : 717 : check_rights = !is_rebuild;
9422 : : /* skip index build if phase 3 will do it or we're reusing an old one */
648 rhaas@postgresql.org 9423 [ + + + + ]: 717 : skip_build = tab->rewrite > 0 || RelFileNumberIsValid(stmt->oldNumber);
9424 : : /* suppress notices when rebuilding existing index */
7284 tgl@sss.pgh.pa.us 9425 : 717 : quiet = is_rebuild;
9426 : :
3330 alvherre@alvh.no-ip. 9427 : 717 : address = DefineIndex(RelationGetRelid(rel),
9428 : : stmt,
9429 : : InvalidOid, /* no predefined OID */
9430 : : InvalidOid, /* no parent index */
9431 : : InvalidOid, /* no parent constraint */
9432 : : -1, /* total_parts unknown */
9433 : : true, /* is_alter_table */
9434 : : check_rights,
9435 : : false, /* check_not_in_use - we did it already */
9436 : : skip_build,
9437 : : quiet);
9438 : :
9439 : : /*
9440 : : * If TryReuseIndex() stashed a relfilenumber for us, we used it for the
9441 : : * new index instead of building from scratch. Restore associated fields.
9442 : : * This may store InvalidSubTransactionId in both fields, in which case
9443 : : * relcache.c will assume it can rebuild the relcache entry. Hence, do
9444 : : * this after the CCI that made catalog rows visible to any rebuild. The
9445 : : * DROP of the old edition of this index will have scheduled the storage
9446 : : * for deletion at commit, so cancel that pending deletion.
9447 : : */
648 rhaas@postgresql.org 9448 [ + + ]: 653 : if (RelFileNumberIsValid(stmt->oldNumber))
9449 : : {
3330 alvherre@alvh.no-ip. 9450 : 36 : Relation irel = index_open(address.objectId, NoLock);
9451 : :
1471 noah@leadboat.com 9452 : 36 : irel->rd_createSubid = stmt->oldCreateSubid;
648 rhaas@postgresql.org 9453 : 36 : irel->rd_firstRelfilelocatorSubid = stmt->oldFirstRelfilelocatorSubid;
9454 : 36 : RelationPreserveStorage(irel->rd_locator, true);
4654 9455 : 36 : index_close(irel, NoLock);
9456 : : }
9457 : :
3308 alvherre@alvh.no-ip. 9458 : 653 : return address;
9459 : : }
9460 : :
9461 : : /*
9462 : : * ALTER TABLE ADD STATISTICS
9463 : : *
9464 : : * This is no such command in the grammar, but we use this internally to add
9465 : : * AT_ReAddStatistics subcommands to rebuild extended statistics after a table
9466 : : * column type change.
9467 : : */
9468 : : static ObjectAddress
1115 tomas.vondra@postgre 9469 : 7 : ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
9470 : : CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
9471 : : {
9472 : : ObjectAddress address;
9473 : :
9474 [ - + ]: 7 : Assert(IsA(stmt, CreateStatsStmt));
9475 : :
9476 : : /* The CreateStatsStmt has already been through transformStatsStmt */
9477 [ - + ]: 7 : Assert(stmt->transformed);
9478 : :
9479 : 7 : address = CreateStatistics(stmt);
9480 : :
9481 : 7 : return address;
9482 : : }
9483 : :
9484 : : /*
9485 : : * ALTER TABLE ADD CONSTRAINT USING INDEX
9486 : : *
9487 : : * Returns the address of the new constraint.
9488 : : */
9489 : : static ObjectAddress
4828 tgl@sss.pgh.pa.us 9490 : 4107 : ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
9491 : : IndexStmt *stmt, LOCKMODE lockmode)
9492 : : {
9493 : 4107 : Oid index_oid = stmt->indexOid;
9494 : : Relation indexRel;
9495 : : char *indexName;
9496 : : IndexInfo *indexInfo;
9497 : : char *constraintName;
9498 : : char constraintType;
9499 : : ObjectAddress address;
9500 : : bits16 flags;
9501 : :
9502 [ - + ]: 4107 : Assert(IsA(stmt, IndexStmt));
9503 [ - + ]: 4107 : Assert(OidIsValid(index_oid));
9504 [ - + ]: 4107 : Assert(stmt->isconstraint);
9505 : :
9506 : : /*
9507 : : * Doing this on partitioned tables is not a simple feature to implement,
9508 : : * so let's punt for now.
9509 : : */
2246 alvherre@alvh.no-ip. 9510 [ + + ]: 4107 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
9511 [ + - ]: 3 : ereport(ERROR,
9512 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
9513 : : errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX is not supported on partitioned tables")));
9514 : :
4828 tgl@sss.pgh.pa.us 9515 : 4104 : indexRel = index_open(index_oid, AccessShareLock);
9516 : :
9517 : 4104 : indexName = pstrdup(RelationGetRelationName(indexRel));
9518 : :
9519 : 4104 : indexInfo = BuildIndexInfo(indexRel);
9520 : :
9521 : : /* this should have been checked at parse time */
9522 [ - + ]: 4104 : if (!indexInfo->ii_Unique)
4828 tgl@sss.pgh.pa.us 9523 [ # # ]:UBC 0 : elog(ERROR, "index \"%s\" is not unique", indexName);
9524 : :
9525 : : /*
9526 : : * Determine name to assign to constraint. We require a constraint to
9527 : : * have the same name as the underlying index; therefore, use the index's
9528 : : * existing name as the default constraint name, and if the user
9529 : : * explicitly gives some other name for the constraint, rename the index
9530 : : * to match.
9531 : : */
4828 tgl@sss.pgh.pa.us 9532 :CBC 4104 : constraintName = stmt->idxname;
9533 [ + + ]: 4104 : if (constraintName == NULL)
9534 : 4094 : constraintName = indexName;
9535 [ + + ]: 10 : else if (strcmp(constraintName, indexName) != 0)
9536 : : {
9537 [ + - ]: 7 : ereport(NOTICE,
9538 : : (errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index \"%s\" to \"%s\"",
9539 : : indexName, constraintName)));
1998 peter_e@gmx.net 9540 : 7 : RenameRelationInternal(index_oid, constraintName, false, true);
9541 : : }
9542 : :
9543 : : /* Extra checks needed if making primary key */
4828 tgl@sss.pgh.pa.us 9544 [ + + ]: 4104 : if (stmt->primary)
2017 alvherre@alvh.no-ip. 9545 : 2321 : index_check_primary_key(rel, indexInfo, true, stmt);
9546 : :
9547 : : /* Note we currently don't support EXCLUSION constraints here */
4828 tgl@sss.pgh.pa.us 9548 [ + + ]: 4101 : if (stmt->primary)
9549 : 2318 : constraintType = CONSTRAINT_PRIMARY;
9550 : : else
9551 : 1783 : constraintType = CONSTRAINT_UNIQUE;
9552 : :
9553 : : /* Create the catalog entries for the constraint */
2343 alvherre@alvh.no-ip. 9554 : 4101 : flags = INDEX_CONSTR_CREATE_UPDATE_INDEX |
9555 : : INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS |
9556 [ - + ]: 8202 : (stmt->initdeferred ? INDEX_CONSTR_CREATE_INIT_DEFERRED : 0) |
9557 [ - + ]: 4101 : (stmt->deferrable ? INDEX_CONSTR_CREATE_DEFERRABLE : 0) |
9558 : 4101 : (stmt->primary ? INDEX_CONSTR_CREATE_MARK_AS_PRIMARY : 0);
9559 : :
3308 9560 : 4101 : address = index_constraint_create(rel,
9561 : : index_oid,
9562 : : InvalidOid,
9563 : : indexInfo,
9564 : : constraintName,
9565 : : constraintType,
9566 : : flags,
9567 : : allowSystemTableMods,
9568 : : false); /* is_internal */
9569 : :
4828 tgl@sss.pgh.pa.us 9570 : 4101 : index_close(indexRel, NoLock);
9571 : :
3308 alvherre@alvh.no-ip. 9572 : 4101 : return address;
9573 : : }
9574 : :
9575 : : /*
9576 : : * ALTER TABLE ADD CONSTRAINT
9577 : : *
9578 : : * Return value is the address of the new constraint; if no constraint was
9579 : : * added, InvalidObjectAddress is returned.
9580 : : */
9581 : : static ObjectAddress
5819 tgl@sss.pgh.pa.us 9582 : 1920 : ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
9583 : : Constraint *newConstraint, bool recurse, bool is_readd,
9584 : : LOCKMODE lockmode)
9585 : : {
3308 alvherre@alvh.no-ip. 9586 : 1920 : ObjectAddress address = InvalidObjectAddress;
9587 : :
5372 tgl@sss.pgh.pa.us 9588 [ - + ]: 1920 : Assert(IsA(newConstraint, Constraint));
9589 : :
9590 : : /*
9591 : : * Currently, we only expect to see CONSTR_CHECK, CONSTR_NOTNULL and
9592 : : * CONSTR_FOREIGN nodes arriving here (see the preprocessing done in
9593 : : * parse_utilcmd.c).
9594 : : */
9595 [ + + - ]: 1920 : switch (newConstraint->contype)
9596 : : {
9597 : 688 : case CONSTR_CHECK:
9598 : : case CONSTR_NOTNULL:
9599 : : address =
233 alvherre@alvh.no-ip. 9600 :GNC 688 : ATAddCheckNNConstraint(wqueue, tab, rel,
9601 : : newConstraint, recurse, false, is_readd,
9602 : : lockmode);
5372 tgl@sss.pgh.pa.us 9603 :CBC 649 : break;
9604 : :
9605 : 1232 : case CONSTR_FOREIGN:
9606 : :
9607 : : /*
9608 : : * Assign or validate constraint name
9609 : : */
9610 [ + + ]: 1232 : if (newConstraint->conname)
9611 : : {
9612 [ - + ]: 574 : if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
9613 : : RelationGetRelid(rel),
9614 : 574 : newConstraint->conname))
5372 tgl@sss.pgh.pa.us 9615 [ # # ]:UBC 0 : ereport(ERROR,
9616 : : (errcode(ERRCODE_DUPLICATE_OBJECT),
9617 : : errmsg("constraint \"%s\" for relation \"%s\" already exists",
9618 : : newConstraint->conname,
9619 : : RelationGetRelationName(rel))));
9620 : : }
9621 : : else
5372 tgl@sss.pgh.pa.us 9622 :CBC 658 : newConstraint->conname =
9623 : 658 : ChooseConstraintName(RelationGetRelationName(rel),
1859 peter@eisentraut.org 9624 : 658 : ChooseForeignKeyConstraintNameAddition(newConstraint->fk_attrs),
9625 : : "fkey",
5372 tgl@sss.pgh.pa.us 9626 : 658 : RelationGetNamespace(rel),
9627 : : NIL);
9628 : :
2202 alvherre@alvh.no-ip. 9629 : 1232 : address = ATAddForeignKeyConstraint(wqueue, tab, rel,
9630 : : newConstraint,
9631 : : recurse, false,
9632 : : lockmode);
5372 tgl@sss.pgh.pa.us 9633 : 988 : break;
9634 : :
7284 tgl@sss.pgh.pa.us 9635 :UBC 0 : default:
5372 9636 [ # # ]: 0 : elog(ERROR, "unrecognized constraint type: %d",
9637 : : (int) newConstraint->contype);
9638 : : }
9639 : :
3308 alvherre@alvh.no-ip. 9640 :CBC 1637 : return address;
9641 : : }
9642 : :
9643 : : /*
9644 : : * Generate the column-name portion of the constraint name for a new foreign
9645 : : * key given the list of column names that reference the referenced
9646 : : * table. This will be passed to ChooseConstraintName along with the parent
9647 : : * table name and the "fkey" suffix.
9648 : : *
9649 : : * We know that less than NAMEDATALEN characters will actually be used, so we
9650 : : * can truncate the result once we've generated that many.
9651 : : *
9652 : : * XXX see also ChooseExtendedStatisticNameAddition and
9653 : : * ChooseIndexNameAddition.
9654 : : */
9655 : : static char *
1859 peter@eisentraut.org 9656 : 1021 : ChooseForeignKeyConstraintNameAddition(List *colnames)
9657 : : {
9658 : : char buf[NAMEDATALEN * 2];
9659 : 1021 : int buflen = 0;
9660 : : ListCell *lc;
9661 : :
9662 : 1021 : buf[0] = '\0';
9663 [ + - + + : 2253 : foreach(lc, colnames)
+ + ]
9664 : : {
9665 : 1232 : const char *name = strVal(lfirst(lc));
9666 : :
9667 [ + + ]: 1232 : if (buflen > 0)
9668 : 211 : buf[buflen++] = '_'; /* insert _ between names */
9669 : :
9670 : : /*
9671 : : * At this point we have buflen <= NAMEDATALEN. name should be less
9672 : : * than NAMEDATALEN already, but use strlcpy for paranoia.
9673 : : */
9674 : 1232 : strlcpy(buf + buflen, name, NAMEDATALEN);
9675 : 1232 : buflen += strlen(buf + buflen);
9676 [ - + ]: 1232 : if (buflen >= NAMEDATALEN)
1859 peter@eisentraut.org 9677 :UBC 0 : break;
9678 : : }
1859 peter@eisentraut.org 9679 :CBC 1021 : return pstrdup(buf);
9680 : : }
9681 : :
9682 : : /*
9683 : : * Add a check or not-null constraint to a single table and its children.
9684 : : * Returns the address of the constraint added to the parent relation,
9685 : : * if one gets added, or InvalidObjectAddress otherwise.
9686 : : *
9687 : : * Subroutine for ATExecAddConstraint.
9688 : : *
9689 : : * We must recurse to child tables during execution, rather than using
9690 : : * ALTER TABLE's normal prep-time recursion. The reason is that all the
9691 : : * constraints *must* be given the same name, else they won't be seen as
9692 : : * related later. If the user didn't explicitly specify a name, then
9693 : : * AddRelationNewConstraints would normally assign different names to the
9694 : : * child constraints. To fix that, we must capture the name assigned at
9695 : : * the parent table and pass that down.
9696 : : */
9697 : : static ObjectAddress
233 alvherre@alvh.no-ip. 9698 :GNC 992 : ATAddCheckNNConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
9699 : : Constraint *constr, bool recurse, bool recursing,
9700 : : bool is_readd, LOCKMODE lockmode)
9701 : : {
9702 : : List *newcons;
9703 : : ListCell *lcon;
9704 : : List *children;
9705 : : ListCell *child;
3308 alvherre@alvh.no-ip. 9706 :CBC 992 : ObjectAddress address = InvalidObjectAddress;
9707 : :
9708 : : /* Guard against stack overflow due to overly deep inheritance tree. */
158 alvherre@alvh.no-ip. 9709 :GNC 992 : check_stack_depth();
9710 : :
9711 : : /* At top level, permission check was done in ATPrepCmd, else do it */
5819 tgl@sss.pgh.pa.us 9712 [ + + ]:CBC 992 : if (recursing)
1011 peter@eisentraut.org 9713 : 237 : ATSimplePermissions(AT_AddConstraint, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
9714 : :
9715 : : /*
9716 : : * Call AddRelationNewConstraints to do the work, making sure it works on
9717 : : * a copy of the Constraint so transformExpr can't modify the original. It
9718 : : * returns a list of cooked constraints.
9719 : : *
9720 : : * If the constraint ends up getting merged with a pre-existing one, it's
9721 : : * omitted from the returned list, which is what we want: we do not need
9722 : : * to do any validation work. That can only happen at child tables,
9723 : : * though, since we disallow merging at the top level.
9724 : : */
5819 tgl@sss.pgh.pa.us 9725 : 992 : newcons = AddRelationNewConstraints(rel, NIL,
9726 : 992 : list_make1(copyObject(constr)),
479 michael@paquier.xyz 9727 [ + + ]: 992 : recursing || is_readd, /* allow_merge */
2489 tgl@sss.pgh.pa.us 9728 :GIC 992 : !recursing, /* is_local */
9729 : : is_readd, /* is_internal */
2052 tgl@sss.pgh.pa.us 9730 [ + + ]:CBC 992 : NULL); /* queryString not available
9731 : : * here */
9732 : :
9733 : : /* we don't expect more than one constraint here */
3308 alvherre@alvh.no-ip. 9734 [ - + ]: 956 : Assert(list_length(newcons) <= 1);
9735 : :
9736 : : /* Add each to-be-validated constraint to Phase 3's queue */
5819 tgl@sss.pgh.pa.us 9737 [ + + + + : 1852 : foreach(lcon, newcons)
+ + ]
9738 : : {
9739 : 896 : CookedConstraint *ccon = (CookedConstraint *) lfirst(lcon);
9740 : :
233 alvherre@alvh.no-ip. 9741 [ + + + + ]:GNC 896 : if (!ccon->skip_validation && ccon->contype != CONSTR_NOTNULL)
9742 : : {
9743 : : NewConstraint *newcon;
9744 : :
4701 alvherre@alvh.no-ip. 9745 :CBC 398 : newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
9746 : 398 : newcon->name = ccon->name;
9747 : 398 : newcon->contype = ccon->contype;
2588 andres@anarazel.de 9748 : 398 : newcon->qual = ccon->expr;
9749 : :
4701 alvherre@alvh.no-ip. 9750 : 398 : tab->constraints = lappend(tab->constraints, newcon);
9751 : : }
9752 : :
9753 : : /* Save the actually assigned name if it was defaulted */
5372 tgl@sss.pgh.pa.us 9754 [ + + ]: 896 : if (constr->conname == NULL)
9755 : 274 : constr->conname = ccon->name;
9756 : :
9757 : : /*
9758 : : * If adding a not-null constraint, set the pg_attribute flag and tell
9759 : : * phase 3 to verify existing rows, if needed.
9760 : : */
233 alvherre@alvh.no-ip. 9761 [ + + ]:GNC 896 : if (constr->contype == CONSTR_NOTNULL)
9762 : 301 : set_attnotnull(wqueue, rel, ccon->attnum,
9763 : 301 : !ccon->is_no_inherit, lockmode);
9764 : :
3308 alvherre@alvh.no-ip. 9765 :CBC 896 : ObjectAddressSet(address, ConstraintRelationId, ccon->conoid);
9766 : : }
9767 : :
9768 : : /* At this point we must have a locked-down name to use */
233 alvherre@alvh.no-ip. 9769 [ + + - + ]:GNC 956 : Assert(newcons == NIL || constr->conname != NULL);
9770 : :
9771 : : /* Advance command counter in case same table is visited multiple times */
5819 tgl@sss.pgh.pa.us 9772 :CBC 956 : CommandCounterIncrement();
9773 : :
9774 : : /*
9775 : : * If the constraint got merged with an existing constraint, we're done.
9776 : : * We mustn't recurse to child tables in this case, because they've
9777 : : * already got the constraint, and visiting them again would lead to an
9778 : : * incorrect value for coninhcount.
9779 : : */
5003 rhaas@postgresql.org 9780 [ + + ]: 956 : if (newcons == NIL)
3308 alvherre@alvh.no-ip. 9781 : 60 : return address;
9782 : :
9783 : : /*
9784 : : * If adding a NO INHERIT constraint, no need to find our children.
9785 : : */
3068 tgl@sss.pgh.pa.us 9786 [ + + ]: 896 : if (constr->is_no_inherit)
3308 alvherre@alvh.no-ip. 9787 : 36 : return address;
9788 : :
9789 : : /*
9790 : : * Propagate to children as appropriate. Unlike most other ALTER
9791 : : * routines, we have to do this one level of recursion at a time; we can't
9792 : : * use find_all_inheritors to do it in one pass.
9793 : : */
9794 : : children =
1082 9795 : 860 : find_inheritance_children(RelationGetRelid(rel), lockmode);
9796 : :
9797 : : /*
9798 : : * Check if ONLY was specified with ALTER TABLE. If so, allow the
9799 : : * constraint creation only if there are no children currently. Error out
9800 : : * otherwise.
9801 : : */
4377 9802 [ + + + + ]: 860 : if (!recurse && children != NIL)
9803 [ + - ]: 3 : ereport(ERROR,
9804 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
9805 : : errmsg("constraint must be added to child tables too")));
9806 : :
9807 : : /*
9808 : : * The constraint must appear as inherited in children, so create a
9809 : : * modified constraint object to use.
9810 : : */
233 alvherre@alvh.no-ip. 9811 :GNC 857 : constr = copyObject(constr);
9812 : 857 : constr->inhcount = 1;
5819 tgl@sss.pgh.pa.us 9813 [ + + + + :CBC 1091 : foreach(child, children)
+ + ]
9814 : : {
9815 : 237 : Oid childrelid = lfirst_oid(child);
9816 : : Relation childrel;
9817 : : AlteredTableInfo *childtab;
9818 : :
9819 : : /* find_inheritance_children already got lock */
1910 andres@anarazel.de 9820 : 237 : childrel = table_open(childrelid, NoLock);
5819 tgl@sss.pgh.pa.us 9821 : 237 : CheckTableNotInUse(childrel, "ALTER TABLE");
9822 : :
9823 : : /* Find or create work queue entry for this table */
9824 : 237 : childtab = ATGetQueueEntry(wqueue, childrel);
9825 : :
9826 : : /*
9827 : : * Recurse to child. XXX if we didn't create a constraint on the
9828 : : * parent because it already existed, and we do create one on a child,
9829 : : * should we return that child's constraint ObjectAddress here?
9830 : : */
233 alvherre@alvh.no-ip. 9831 :GNC 237 : ATAddCheckNNConstraint(wqueue, childtab, childrel,
9832 : : constr, recurse, true, is_readd, lockmode);
9833 : :
1910 andres@anarazel.de 9834 :CBC 234 : table_close(childrel, NoLock);
9835 : : }
9836 : :
3308 alvherre@alvh.no-ip. 9837 : 854 : return address;
9838 : : }
9839 : :
9840 : : /*
9841 : : * Add a foreign-key constraint to a single table; return the new constraint's
9842 : : * address.
9843 : : *
9844 : : * Subroutine for ATExecAddConstraint. Must already hold exclusive
9845 : : * lock on the rel, and have done appropriate validity checks for it.
9846 : : * We do permissions checks here, however.
9847 : : *
9848 : : * When the referenced or referencing tables (or both) are partitioned,
9849 : : * multiple pg_constraint rows are required -- one for each partitioned table
9850 : : * and each partition on each side (fortunately, not one for every combination
9851 : : * thereof). We also need action triggers on each leaf partition on the
9852 : : * referenced side, and check triggers on each leaf partition on the
9853 : : * referencing side.
9854 : : */
9855 : : static ObjectAddress
2202 9856 : 1232 : ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
9857 : : Constraint *fkconstraint,
9858 : : bool recurse, bool recursing, LOCKMODE lockmode)
9859 : : {
9860 : : Relation pkrel;
638 peter@eisentraut.org 9861 : 1232 : int16 pkattnum[INDEX_MAX_KEYS] = {0};
9862 : 1232 : int16 fkattnum[INDEX_MAX_KEYS] = {0};
9863 : 1232 : Oid pktypoid[INDEX_MAX_KEYS] = {0};
9864 : 1232 : Oid fktypoid[INDEX_MAX_KEYS] = {0};
9865 : 1232 : Oid opclasses[INDEX_MAX_KEYS] = {0};
9866 : 1232 : Oid pfeqoperators[INDEX_MAX_KEYS] = {0};
9867 : 1232 : Oid ppeqoperators[INDEX_MAX_KEYS] = {0};
9868 : 1232 : Oid ffeqoperators[INDEX_MAX_KEYS] = {0};
9869 : 1232 : int16 fkdelsetcols[INDEX_MAX_KEYS] = {0};
9870 : : bool with_period;
9871 : : bool pk_has_without_overlaps;
9872 : : int i;
9873 : : int numfks,
9874 : : numpks,
9875 : : numfkdelsetcols;
9876 : : Oid indexOid;
9877 : : bool old_check_ok;
9878 : : ObjectAddress address;
4430 alvherre@alvh.no-ip. 9879 : 1232 : ListCell *old_pfeqop_item = list_head(fkconstraint->old_conpfeqop);
9880 : :
9881 : : /*
9882 : : * Grab ShareRowExclusiveLock on the pk table, so that someone doesn't
9883 : : * delete rows out from under us.
9884 : : */
3709 rhaas@postgresql.org 9885 [ + + ]: 1232 : if (OidIsValid(fkconstraint->old_pktable_oid))
1910 andres@anarazel.de 9886 : 36 : pkrel = table_open(fkconstraint->old_pktable_oid, ShareRowExclusiveLock);
9887 : : else
9888 : 1196 : pkrel = table_openrv(fkconstraint->pktable, ShareRowExclusiveLock);
9889 : :
9890 : : /*
9891 : : * Validity checks (permission checks wait till we have the column
9892 : : * numbers)
9893 : : */
2202 alvherre@alvh.no-ip. 9894 [ + + ]: 1232 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
9895 : : {
9896 [ + + ]: 165 : if (!recurse)
9897 [ + - ]: 3 : ereport(ERROR,
9898 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
9899 : : errmsg("cannot use ONLY for foreign key on partitioned table \"%s\" referencing relation \"%s\"",
9900 : : RelationGetRelationName(rel),
9901 : : RelationGetRelationName(pkrel))));
9902 [ + + + + ]: 162 : if (fkconstraint->skip_validation && !fkconstraint->initially_valid)
9903 [ + - ]: 3 : ereport(ERROR,
9904 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
9905 : : errmsg("cannot add NOT VALID foreign key on partitioned table \"%s\" referencing relation \"%s\"",
9906 : : RelationGetRelationName(rel),
9907 : : RelationGetRelationName(pkrel)),
9908 : : errdetail("This feature is not yet supported on partitioned tables.")));
9909 : : }
9910 : :
1838 9911 [ + + ]: 1226 : if (pkrel->rd_rel->relkind != RELKIND_RELATION &&
9912 [ - + ]: 148 : pkrel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
7284 tgl@sss.pgh.pa.us 9913 [ # # ]:UBC 0 : ereport(ERROR,
9914 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
9915 : : errmsg("referenced relation \"%s\" is not a table",
9916 : : RelationGetRelationName(pkrel))));
9917 : :
7284 tgl@sss.pgh.pa.us 9918 [ + + + + ]:CBC 1226 : if (!allowSystemTableMods && IsSystemRelation(pkrel))
7574 9919 [ + - ]: 1 : ereport(ERROR,
9920 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
9921 : : errmsg("permission denied: \"%s\" is a system catalog",
9922 : : RelationGetRelationName(pkrel))));
9923 : :
9924 : : /*
9925 : : * References from permanent or unlogged tables to temp tables, and from
9926 : : * permanent tables to unlogged tables, are disallowed because the
9927 : : * referenced data can vanish out from under us. References from temp
9928 : : * tables to any other table type are also disallowed, because other
9929 : : * backends might need to run the RI triggers on the perm table, but they
9930 : : * can't reliably see tuples in the local buffers of other backends.
9931 : : */
4871 rhaas@postgresql.org 9932 [ + + + - ]: 1225 : switch (rel->rd_rel->relpersistence)
9933 : : {
9934 : 1070 : case RELPERSISTENCE_PERMANENT:
1119 bruce@momjian.us 9935 [ - + ]: 1070 : if (!RelationIsPermanent(pkrel))
4871 rhaas@postgresql.org 9936 [ # # ]:UBC 0 : ereport(ERROR,
9937 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
9938 : : errmsg("constraints on permanent tables may reference only permanent tables")));
4871 rhaas@postgresql.org 9939 :CBC 1070 : break;
4855 9940 : 16 : case RELPERSISTENCE_UNLOGGED:
1119 bruce@momjian.us 9941 [ + - ]: 16 : if (!RelationIsPermanent(pkrel)
4855 rhaas@postgresql.org 9942 [ - + ]: 16 : && pkrel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED)
4855 rhaas@postgresql.org 9943 [ # # ]:UBC 0 : ereport(ERROR,
9944 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
9945 : : errmsg("constraints on unlogged tables may reference only permanent or unlogged tables")));
4855 rhaas@postgresql.org 9946 :CBC 16 : break;
4871 9947 : 139 : case RELPERSISTENCE_TEMP:
9948 [ - + ]: 139 : if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
4871 rhaas@postgresql.org 9949 [ # # ]:UBC 0 : ereport(ERROR,
9950 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
9951 : : errmsg("constraints on temporary tables may reference only temporary tables")));
4136 tgl@sss.pgh.pa.us 9952 [ + - - + ]:CBC 139 : if (!pkrel->rd_islocaltemp || !rel->rd_islocaltemp)
4136 tgl@sss.pgh.pa.us 9953 [ # # ]:UBC 0 : ereport(ERROR,
9954 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
9955 : : errmsg("constraints on temporary tables must involve temporary tables of this session")));
4871 rhaas@postgresql.org 9956 :CBC 139 : break;
9957 : : }
9958 : :
9959 : : /*
9960 : : * Look up the referencing attributes to make sure they exist, and record
9961 : : * their attnums and type OIDs.
9962 : : */
7875 tgl@sss.pgh.pa.us 9963 : 1225 : numfks = transformColumnNameList(RelationGetRelid(rel),
9964 : : fkconstraint->fk_attrs,
9965 : : fkattnum, fktypoid);
21 peter@eisentraut.org 9966 [ + + + + ]:GNC 1210 : with_period = fkconstraint->fk_with_period || fkconstraint->pk_with_period;
9967 [ + + + + ]: 1210 : if (with_period && !fkconstraint->fk_with_period)
9968 [ + - ]: 12 : ereport(ERROR,
9969 : : errcode(ERRCODE_INVALID_FOREIGN_KEY),
9970 : : errmsg("foreign key uses PERIOD on the referenced table but not the referencing table"));
9971 : :
858 peter@eisentraut.org 9972 :CBC 1198 : numfkdelsetcols = transformColumnNameList(RelationGetRelid(rel),
9973 : : fkconstraint->fk_del_set_cols,
9974 : : fkdelsetcols, NULL);
9975 : 1195 : validateFkOnDeleteSetColumns(numfks, fkattnum,
9976 : : numfkdelsetcols, fkdelsetcols,
9977 : : fkconstraint->fk_del_set_cols);
9978 : :
9979 : : /*
9980 : : * If the attribute list for the referenced table was omitted, lookup the
9981 : : * definition of the primary key and use it. Otherwise, validate the
9982 : : * supplied attribute list. In either case, discover the index OID and
9983 : : * index opclasses, and the attnums and type OIDs of the attributes.
9984 : : */
7875 tgl@sss.pgh.pa.us 9985 [ + + ]: 1192 : if (fkconstraint->pk_attrs == NIL)
9986 : : {
9987 : 535 : numpks = transformFkeyGetPrimaryKey(pkrel, &indexOid,
9988 : : &fkconstraint->pk_attrs,
9989 : : pkattnum, pktypoid,
9990 : : opclasses, &pk_has_without_overlaps);
9991 : :
9992 : : /* If the primary key uses WITHOUT OVERLAPS, the fk must use PERIOD */
21 peter@eisentraut.org 9993 [ + + + + ]:GNC 535 : if (pk_has_without_overlaps && !fkconstraint->fk_with_period)
9994 [ + - ]: 12 : ereport(ERROR,
9995 : : errcode(ERRCODE_INVALID_FOREIGN_KEY),
9996 : : errmsg("foreign key uses PERIOD on the referenced table but not the referencing table"));
9997 : : }
9998 : : else
9999 : : {
7875 tgl@sss.pgh.pa.us 10000 :CBC 657 : numpks = transformColumnNameList(RelationGetRelid(pkrel),
10001 : : fkconstraint->pk_attrs,
10002 : : pkattnum, pktypoid);
10003 : :
10004 : : /* Since we got pk_attrs, one should be a period. */
21 peter@eisentraut.org 10005 [ + + + + ]:GNC 642 : if (with_period && !fkconstraint->pk_with_period)
10006 [ + - ]: 12 : ereport(ERROR,
10007 : : errcode(ERRCODE_INVALID_FOREIGN_KEY),
10008 : : errmsg("foreign key uses PERIOD on the referencing table but not the referenced table"));
10009 : :
10010 : : /* Look for an index matching the column list */
7337 tgl@sss.pgh.pa.us 10011 :CBC 630 : indexOid = transformFkeyCheckAttrs(pkrel, numpks, pkattnum,
10012 : : with_period, opclasses, &pk_has_without_overlaps);
10013 : : }
10014 : :
10015 : : /*
10016 : : * If the referenced primary key has WITHOUT OVERLAPS, the foreign key
10017 : : * must use PERIOD.
10018 : : */
21 peter@eisentraut.org 10019 [ + + + + ]:GNC 1135 : if (pk_has_without_overlaps && !with_period)
10020 [ + - ]: 6 : ereport(ERROR,
10021 : : errcode(ERRCODE_INVALID_FOREIGN_KEY),
10022 : : errmsg("foreign key must use PERIOD when referencing a primary using WITHOUT OVERLAPS"));
10023 : :
10024 : : /*
10025 : : * Now we can check permissions.
10026 : : */
5561 tgl@sss.pgh.pa.us 10027 :CBC 1129 : checkFkeyPermissions(pkrel, pkattnum, numpks);
10028 : :
10029 : : /*
10030 : : * Check some things for generated columns.
10031 : : */
1842 peter@eisentraut.org 10032 [ + + ]: 2643 : for (i = 0; i < numfks; i++)
10033 : : {
10034 : 1520 : char attgenerated = TupleDescAttr(RelationGetDescr(rel), fkattnum[i] - 1)->attgenerated;
10035 : :
10036 [ + + ]: 1520 : if (attgenerated)
10037 : : {
10038 : : /*
10039 : : * Check restrictions on UPDATE/DELETE actions, per SQL standard
10040 : : */
10041 [ + - ]: 15 : if (fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETNULL ||
10042 [ + - ]: 15 : fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETDEFAULT ||
10043 [ + + ]: 15 : fkconstraint->fk_upd_action == FKCONSTR_ACTION_CASCADE)
10044 [ + - ]: 3 : ereport(ERROR,
10045 : : (errcode(ERRCODE_SYNTAX_ERROR),
10046 : : errmsg("invalid %s action for foreign key constraint containing generated column",
10047 : : "ON UPDATE")));
10048 [ + + ]: 12 : if (fkconstraint->fk_del_action == FKCONSTR_ACTION_SETNULL ||
10049 [ - + ]: 9 : fkconstraint->fk_del_action == FKCONSTR_ACTION_SETDEFAULT)
10050 [ + - ]: 3 : ereport(ERROR,
10051 : : (errcode(ERRCODE_SYNTAX_ERROR),
10052 : : errmsg("invalid %s action for foreign key constraint containing generated column",
10053 : : "ON DELETE")));
10054 : : }
10055 : : }
10056 : :
10057 : : /*
10058 : : * Some actions are currently unsupported for foreign keys using PERIOD.
10059 : : */
21 peter@eisentraut.org 10060 [ + + ]:GNC 1123 : if (fkconstraint->fk_with_period)
10061 : : {
10062 [ + + ]: 119 : if (fkconstraint->fk_upd_action == FKCONSTR_ACTION_CASCADE ||
10063 [ + + ]: 113 : fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETNULL ||
10064 [ + + ]: 107 : fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETDEFAULT)
10065 [ + - ]: 18 : ereport(ERROR,
10066 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
10067 : : errmsg("unsupported %s action for foreign key constraint using PERIOD",
10068 : : "ON UPDATE"));
10069 : :
10070 [ + - ]: 101 : if (fkconstraint->fk_del_action == FKCONSTR_ACTION_CASCADE ||
10071 [ + - ]: 101 : fkconstraint->fk_del_action == FKCONSTR_ACTION_SETNULL ||
10072 [ - + ]: 101 : fkconstraint->fk_del_action == FKCONSTR_ACTION_SETDEFAULT)
21 peter@eisentraut.org 10073 [ # # ]:UNC 0 : ereport(ERROR,
10074 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
10075 : : errmsg("unsupported %s action for foreign key constraint using PERIOD",
10076 : : "ON DELETE"));
10077 : : }
10078 : :
10079 : : /*
10080 : : * Look up the equality operators to use in the constraint.
10081 : : *
10082 : : * Note that we have to be careful about the difference between the actual
10083 : : * PK column type and the opclass' declared input type, which might be
10084 : : * only binary-compatible with it. The declared opcintype is the right
10085 : : * thing to probe pg_amop with.
10086 : : */
7875 tgl@sss.pgh.pa.us 10087 [ - + ]:CBC 1105 : if (numfks != numpks)
7574 tgl@sss.pgh.pa.us 10088 [ # # ]:UBC 0 : ereport(ERROR,
10089 : : (errcode(ERRCODE_INVALID_FOREIGN_KEY),
10090 : : errmsg("number of referencing and referenced columns for foreign key disagree")));
10091 : :
10092 : : /*
10093 : : * On the strength of a previous constraint, we might avoid scanning
10094 : : * tables to validate this one. See below.
10095 : : */
4430 alvherre@alvh.no-ip. 10096 :CBC 1105 : old_check_ok = (fkconstraint->old_conpfeqop != NIL);
10097 [ + + - + ]: 1105 : Assert(!old_check_ok || numfks == list_length(fkconstraint->old_conpfeqop));
10098 : :
7875 tgl@sss.pgh.pa.us 10099 [ + + ]: 2397 : for (i = 0; i < numpks; i++)
10100 : : {
6267 10101 : 1406 : Oid pktype = pktypoid[i];
10102 : 1406 : Oid fktype = fktypoid[i];
10103 : : Oid fktyped;
10104 : : HeapTuple cla_ht;
10105 : : Form_pg_opclass cla_tup;
10106 : : Oid amid;
10107 : : Oid opfamily;
10108 : : Oid opcintype;
10109 : : Oid pfeqop;
10110 : : Oid ppeqop;
10111 : : Oid ffeqop;
10112 : : int16 eqstrategy;
10113 : : Oid pfeqop_right;
10114 : :
10115 : : /* We need several fields out of the pg_opclass entry */
5173 rhaas@postgresql.org 10116 : 1406 : cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclasses[i]));
6269 tgl@sss.pgh.pa.us 10117 [ - + ]: 1406 : if (!HeapTupleIsValid(cla_ht))
6269 tgl@sss.pgh.pa.us 10118 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for opclass %u", opclasses[i]);
6269 tgl@sss.pgh.pa.us 10119 :CBC 1406 : cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
10120 : 1406 : amid = cla_tup->opcmethod;
10121 : 1406 : opfamily = cla_tup->opcfamily;
6267 10122 : 1406 : opcintype = cla_tup->opcintype;
6269 10123 : 1406 : ReleaseSysCache(cla_ht);
10124 : :
21 peter@eisentraut.org 10125 [ + + ]:GNC 1406 : if (with_period)
10126 : : {
10127 : : StrategyNumber rtstrategy;
10128 [ + - + + ]: 216 : bool for_overlaps = with_period && i == numpks - 1;
10129 : :
10130 : : /*
10131 : : * GiST indexes are required to support temporal foreign keys
10132 : : * because they combine equals and overlaps.
10133 : : */
10134 [ - + ]: 216 : if (amid != GIST_AM_OID)
21 peter@eisentraut.org 10135 [ # # ]:UNC 0 : elog(ERROR, "only GiST indexes are supported for temporal foreign keys");
10136 : :
21 peter@eisentraut.org 10137 [ + + ]:GNC 216 : rtstrategy = for_overlaps ? RTOverlapStrategyNumber : RTEqualStrategyNumber;
10138 : :
10139 : : /*
10140 : : * An opclass can use whatever strategy numbers it wants, so we
10141 : : * ask the opclass what number it actually uses instead of our RT*
10142 : : * constants.
10143 : : */
10144 : 216 : eqstrategy = GistTranslateStratnum(opclasses[i], rtstrategy);
10145 [ - + ]: 216 : if (eqstrategy == InvalidStrategy)
10146 : : {
10147 : : HeapTuple tuple;
10148 : :
21 peter@eisentraut.org 10149 :UNC 0 : tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclasses[i]));
10150 [ # # ]: 0 : if (!HeapTupleIsValid(tuple))
10151 [ # # ]: 0 : elog(ERROR, "cache lookup failed for operator class %u", opclasses[i]);
10152 : :
10153 [ # # # # ]: 0 : ereport(ERROR,
10154 : : errcode(ERRCODE_UNDEFINED_OBJECT),
10155 : : for_overlaps
10156 : : ? errmsg("could not identify an overlaps operator for foreign key")
10157 : : : errmsg("could not identify an equality operator for foreign key"),
10158 : : errdetail("Could not translate strategy number %d for operator class \"%s\" for access method \"%s\".",
10159 : : rtstrategy, NameStr(((Form_pg_opclass) GETSTRUCT(tuple))->opcname), "gist"));
10160 : : }
10161 : : }
10162 : : else
10163 : : {
10164 : : /*
10165 : : * Check it's a btree; currently this can never fail since no
10166 : : * other index AMs support unique indexes. If we ever did have
10167 : : * other types of unique indexes, we'd need a way to determine
10168 : : * which operator strategy number is equality. (We could use
10169 : : * something like GistTranslateStratnum.)
10170 : : */
21 peter@eisentraut.org 10171 [ - + ]:GNC 1190 : if (amid != BTREE_AM_OID)
21 peter@eisentraut.org 10172 [ # # ]:UNC 0 : elog(ERROR, "only b-tree indexes are supported for foreign keys");
21 peter@eisentraut.org 10173 :GNC 1190 : eqstrategy = BTEqualStrategyNumber;
10174 : : }
10175 : :
10176 : : /*
10177 : : * There had better be a primary equality operator for the index.
10178 : : * We'll use it for PK = PK comparisons.
10179 : : */
6267 tgl@sss.pgh.pa.us 10180 :CBC 1406 : ppeqop = get_opfamily_member(opfamily, opcintype, opcintype,
10181 : : eqstrategy);
10182 : :
6269 10183 [ - + ]: 1406 : if (!OidIsValid(ppeqop))
6269 tgl@sss.pgh.pa.us 10184 [ # # ]:UBC 0 : elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
10185 : : eqstrategy, opcintype, opcintype, opfamily);
10186 : :
10187 : : /*
10188 : : * Are there equality operators that take exactly the FK type? Assume
10189 : : * we should look through any domain here.
10190 : : */
6267 tgl@sss.pgh.pa.us 10191 :CBC 1406 : fktyped = getBaseType(fktype);
10192 : :
10193 : 1406 : pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
10194 : : eqstrategy);
10195 [ + + ]: 1406 : if (OidIsValid(pfeqop))
10196 : : {
4430 alvherre@alvh.no-ip. 10197 : 1065 : pfeqop_right = fktyped;
6267 tgl@sss.pgh.pa.us 10198 : 1065 : ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
10199 : : eqstrategy);
10200 : : }
10201 : : else
10202 : : {
10203 : : /* keep compiler quiet */
4430 alvherre@alvh.no-ip. 10204 : 341 : pfeqop_right = InvalidOid;
10205 : 341 : ffeqop = InvalidOid;
10206 : : }
10207 : :
6269 tgl@sss.pgh.pa.us 10208 [ + + - + ]: 1406 : if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
10209 : : {
10210 : : /*
10211 : : * Otherwise, look for an implicit cast from the FK type to the
10212 : : * opcintype, and if found, use the primary equality operator.
10213 : : * This is a bit tricky because opcintype might be a polymorphic
10214 : : * type such as ANYARRAY or ANYENUM; so what we have to test is
10215 : : * whether the two actual column types can be concurrently cast to
10216 : : * that type. (Otherwise, we'd fail to reject combinations such
10217 : : * as int[] and point[].)
10218 : : */
10219 : : Oid input_typeids[2];
10220 : : Oid target_typeids[2];
10221 : :
6267 10222 : 341 : input_typeids[0] = pktype;
10223 : 341 : input_typeids[1] = fktype;
10224 : 341 : target_typeids[0] = opcintype;
10225 : 341 : target_typeids[1] = opcintype;
10226 [ + + ]: 341 : if (can_coerce_type(2, input_typeids, target_typeids,
10227 : : COERCION_IMPLICIT))
10228 : : {
6269 10229 : 227 : pfeqop = ffeqop = ppeqop;
4430 alvherre@alvh.no-ip. 10230 : 227 : pfeqop_right = opcintype;
10231 : : }
10232 : : }
10233 : :
6269 tgl@sss.pgh.pa.us 10234 [ + + - + ]: 1406 : if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
7337 10235 [ + - ]: 114 : ereport(ERROR,
10236 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
10237 : : errmsg("foreign key constraint \"%s\" cannot be implemented",
10238 : : fkconstraint->conname),
10239 : : errdetail("Key columns \"%s\" and \"%s\" "
10240 : : "are of incompatible types: %s and %s.",
10241 : : strVal(list_nth(fkconstraint->fk_attrs, i)),
10242 : : strVal(list_nth(fkconstraint->pk_attrs, i)),
10243 : : format_type_be(fktype),
10244 : : format_type_be(pktype))));
10245 : :
4430 alvherre@alvh.no-ip. 10246 [ + + ]: 1292 : if (old_check_ok)
10247 : : {
10248 : : /*
10249 : : * When a pfeqop changes, revalidate the constraint. We could
10250 : : * permit intra-opfamily changes, but that adds subtle complexity
10251 : : * without any concrete benefit for core types. We need not
10252 : : * assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
10253 : : */
10254 : 3 : old_check_ok = (pfeqop == lfirst_oid(old_pfeqop_item));
1735 tgl@sss.pgh.pa.us 10255 : 3 : old_pfeqop_item = lnext(fkconstraint->old_conpfeqop,
10256 : : old_pfeqop_item);
10257 : : }
4430 alvherre@alvh.no-ip. 10258 [ + + ]: 1292 : if (old_check_ok)
10259 : : {
10260 : : Oid old_fktype;
10261 : : Oid new_fktype;
10262 : : CoercionPathType old_pathtype;
10263 : : CoercionPathType new_pathtype;
10264 : : Oid old_castfunc;
10265 : : Oid new_castfunc;
2429 andres@anarazel.de 10266 : 3 : Form_pg_attribute attr = TupleDescAttr(tab->oldDesc,
10267 : : fkattnum[i] - 1);
10268 : :
10269 : : /*
10270 : : * Identify coercion pathways from each of the old and new FK-side
10271 : : * column types to the right (foreign) operand type of the pfeqop.
10272 : : * We may assume that pg_constraint.conkey is not changing.
10273 : : */
10274 : 3 : old_fktype = attr->atttypid;
4430 alvherre@alvh.no-ip. 10275 : 3 : new_fktype = fktype;
10276 : 3 : old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
10277 : : &old_castfunc);
10278 : 3 : new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
10279 : : &new_castfunc);
10280 : :
10281 : : /*
10282 : : * Upon a change to the cast from the FK column to its pfeqop
10283 : : * operand, revalidate the constraint. For this evaluation, a
10284 : : * binary coercion cast is equivalent to no cast at all. While
10285 : : * type implementors should design implicit casts with an eye
10286 : : * toward consistency of operations like equality, we cannot
10287 : : * assume here that they have done so.
10288 : : *
10289 : : * A function with a polymorphic argument could change behavior
10290 : : * arbitrarily in response to get_fn_expr_argtype(). Therefore,
10291 : : * when the cast destination is polymorphic, we only avoid
10292 : : * revalidation if the input type has not changed at all. Given
10293 : : * just the core data types and operator classes, this requirement
10294 : : * prevents no would-be optimizations.
10295 : : *
10296 : : * If the cast converts from a base type to a domain thereon, then
10297 : : * that domain type must be the opcintype of the unique index.
10298 : : * Necessarily, the primary key column must then be of the domain
10299 : : * type. Since the constraint was previously valid, all values on
10300 : : * the foreign side necessarily exist on the primary side and in
10301 : : * turn conform to the domain. Consequently, we need not treat
10302 : : * domains specially here.
10303 : : *
10304 : : * Since we require that all collations share the same notion of
10305 : : * equality (which they do, because texteq reduces to bitwise
10306 : : * equality), we don't compare collation here.
10307 : : *
10308 : : * We need not directly consider the PK type. It's necessarily
10309 : : * binary coercible to the opcintype of the unique index column,
10310 : : * and ri_triggers.c will only deal with PK datums in terms of
10311 : : * that opcintype. Changing the opcintype also changes pfeqop.
10312 : : */
10313 : 3 : old_check_ok = (new_pathtype == old_pathtype &&
10314 [ + - + - : 6 : new_castfunc == old_castfunc &&
+ - ]
10315 [ + - + - : 3 : (!IsPolymorphicType(pfeqop_right) ||
+ - + - +
- + - + -
+ - + - -
+ - - ]
10316 : : new_fktype == old_fktype));
10317 : : }
10318 : :
6269 tgl@sss.pgh.pa.us 10319 : 1292 : pfeqoperators[i] = pfeqop;
10320 : 1292 : ppeqoperators[i] = ppeqop;
10321 : 1292 : ffeqoperators[i] = ffeqop;
10322 : : }
10323 : :
10324 : : /*
10325 : : * For FKs with PERIOD we need additional operators to check whether the
10326 : : * referencing row's range is contained by the aggregated ranges of the
10327 : : * referenced row(s). For rangetypes and multirangetypes this is
10328 : : * fk.periodatt <@ range_agg(pk.periodatt). Those are the only types we
10329 : : * support for now. FKs will look these up at "runtime", but we should
10330 : : * make sure the lookup works here, even if we don't use the values.
10331 : : */
21 peter@eisentraut.org 10332 [ + + ]:GNC 991 : if (with_period)
10333 : : {
10334 : : Oid periodoperoid;
10335 : : Oid aggedperiodoperoid;
10336 : :
10337 : 92 : FindFKPeriodOpers(opclasses[numpks - 1], &periodoperoid, &aggedperiodoperoid);
10338 : : }
10339 : :
10340 : : /*
10341 : : * Create all the constraint and trigger objects, recursing to partitions
10342 : : * as necessary. First handle the referenced side.
10343 : : */
1838 alvherre@alvh.no-ip. 10344 :CBC 988 : address = addFkRecurseReferenced(wqueue, fkconstraint, rel, pkrel,
10345 : : indexOid,
10346 : : InvalidOid, /* no parent constraint */
10347 : : numfks,
10348 : : pkattnum,
10349 : : fkattnum,
10350 : : pfeqoperators,
10351 : : ppeqoperators,
10352 : : ffeqoperators,
10353 : : numfkdelsetcols,
10354 : : fkdelsetcols,
10355 : : old_check_ok,
10356 : : InvalidOid, InvalidOid,
10357 : : with_period);
10358 : :
10359 : : /* Now handle the referencing side. */
10360 : 988 : addFkRecurseReferencing(wqueue, fkconstraint, rel, pkrel,
10361 : : indexOid,
10362 : : address.objectId,
10363 : : numfks,
10364 : : pkattnum,
10365 : : fkattnum,
10366 : : pfeqoperators,
10367 : : ppeqoperators,
10368 : : ffeqoperators,
10369 : : numfkdelsetcols,
10370 : : fkdelsetcols,
10371 : : old_check_ok,
10372 : : lockmode,
10373 : : InvalidOid, InvalidOid,
10374 : : with_period);
10375 : :
10376 : : /*
10377 : : * Done. Close pk table, but keep lock until we've committed.
10378 : : */
10379 : 988 : table_close(pkrel, NoLock);
10380 : :
10381 : 988 : return address;
10382 : : }
10383 : :
10384 : : /*
10385 : : * validateFkOnDeleteSetColumns
10386 : : * Verifies that columns used in ON DELETE SET NULL/DEFAULT (...)
10387 : : * column lists are valid.
10388 : : */
10389 : : void
858 peter@eisentraut.org 10390 : 1195 : validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
10391 : : int numfksetcols, const int16 *fksetcolsattnums,
10392 : : List *fksetcols)
10393 : : {
10394 [ + + ]: 1207 : for (int i = 0; i < numfksetcols; i++)
10395 : : {
703 tgl@sss.pgh.pa.us 10396 : 15 : int16 setcol_attnum = fksetcolsattnums[i];
10397 : 15 : bool seen = false;
10398 : :
858 peter@eisentraut.org 10399 [ + + ]: 27 : for (int j = 0; j < numfks; j++)
10400 : : {
10401 [ + + ]: 24 : if (fkattnums[j] == setcol_attnum)
10402 : : {
10403 : 12 : seen = true;
10404 : 12 : break;
10405 : : }
10406 : : }
10407 : :
10408 [ + + ]: 15 : if (!seen)
10409 : : {
703 tgl@sss.pgh.pa.us 10410 : 3 : char *col = strVal(list_nth(fksetcols, i));
10411 : :
858 peter@eisentraut.org 10412 [ + - ]: 3 : ereport(ERROR,
10413 : : (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
10414 : : errmsg("column \"%s\" referenced in ON DELETE SET action must be part of foreign key", col)));
10415 : : }
10416 : : }
10417 : 1192 : }
10418 : :
10419 : : /*
10420 : : * addFkRecurseReferenced
10421 : : * subroutine for ATAddForeignKeyConstraint; recurses on the referenced
10422 : : * side of the constraint
10423 : : *
10424 : : * Create pg_constraint rows for the referenced side of the constraint,
10425 : : * referencing the parent of the referencing side; also create action triggers
10426 : : * on leaf partitions. If the table is partitioned, recurse to handle each
10427 : : * partition.
10428 : : *
10429 : : * wqueue is the ALTER TABLE work queue; can be NULL when not running as part
10430 : : * of an ALTER TABLE sequence.
10431 : : * fkconstraint is the constraint being added.
10432 : : * rel is the root referencing relation.
10433 : : * pkrel is the referenced relation; might be a partition, if recursing.
10434 : : * indexOid is the OID of the index (on pkrel) implementing this constraint.
10435 : : * parentConstr is the OID of a parent constraint; InvalidOid if this is a
10436 : : * top-level constraint.
10437 : : * numfks is the number of columns in the foreign key
10438 : : * pkattnum is the attnum array of referenced attributes.
10439 : : * fkattnum is the attnum array of referencing attributes.
10440 : : * numfkdelsetcols is the number of columns in the ON DELETE SET NULL/DEFAULT
10441 : : * (...) clause
10442 : : * fkdelsetcols is the attnum array of the columns in the ON DELETE SET
10443 : : * NULL/DEFAULT clause
10444 : : * pf/pp/ffeqoperators are OID array of operators between columns.
10445 : : * old_check_ok signals that this constraint replaces an existing one that
10446 : : * was already validated (thus this one doesn't need validation).
10447 : : * parentDelTrigger and parentUpdTrigger, when being recursively called on
10448 : : * a partition, are the OIDs of the parent action triggers for DELETE and
10449 : : * UPDATE respectively.
10450 : : */
10451 : : static ObjectAddress
1838 alvherre@alvh.no-ip. 10452 : 1348 : addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
10453 : : Relation pkrel, Oid indexOid, Oid parentConstr,
10454 : : int numfks,
10455 : : int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators,
10456 : : Oid *ppeqoperators, Oid *ffeqoperators,
10457 : : int numfkdelsetcols, int16 *fkdelsetcols,
10458 : : bool old_check_ok,
10459 : : Oid parentDelTrigger, Oid parentUpdTrigger,
10460 : : bool with_period)
10461 : : {
10462 : : ObjectAddress address;
10463 : : Oid constrOid;
10464 : : char *conname;
10465 : : bool conislocal;
10466 : : int coninhcount;
10467 : : bool connoinherit;
10468 : : Oid deleteTriggerOid,
10469 : : updateTriggerOid;
10470 : :
10471 : : /*
10472 : : * Verify relkind for each referenced partition. At the top level, this
10473 : : * is redundant with a previous check, but we need it when recursing.
10474 : : */
10475 [ + + ]: 1348 : if (pkrel->rd_rel->relkind != RELKIND_RELATION &&
10476 [ - + ]: 189 : pkrel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
1838 alvherre@alvh.no-ip. 10477 [ # # ]:UBC 0 : ereport(ERROR,
10478 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
10479 : : errmsg("referenced relation \"%s\" is not a table",
10480 : : RelationGetRelationName(pkrel))));
10481 : :
10482 : : /*
10483 : : * Caller supplies us with a constraint name; however, it may be used in
10484 : : * this partition, so come up with a different one in that case.
10485 : : */
1838 alvherre@alvh.no-ip. 10486 [ + + ]:CBC 1348 : if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
10487 : : RelationGetRelid(rel),
10488 : 1348 : fkconstraint->conname))
10489 : 360 : conname = ChooseConstraintName(RelationGetRelationName(rel),
10490 : 360 : ChooseForeignKeyConstraintNameAddition(fkconstraint->fk_attrs),
10491 : : "fkey",
10492 : 360 : RelationGetNamespace(rel), NIL);
10493 : : else
10494 : 988 : conname = fkconstraint->conname;
10495 : :
10496 [ + + ]: 1348 : if (OidIsValid(parentConstr))
10497 : : {
10498 : 360 : conislocal = false;
10499 : 360 : coninhcount = 1;
10500 : 360 : connoinherit = false;
10501 : : }
10502 : : else
10503 : : {
10504 : 988 : conislocal = true;
10505 : 988 : coninhcount = 0;
10506 : :
10507 : : /*
10508 : : * always inherit for partitioned tables, never for legacy inheritance
10509 : : */
10510 : 988 : connoinherit = rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE;
10511 : : }
10512 : :
10513 : : /*
10514 : : * Record the FK constraint in pg_constraint.
10515 : : */
10516 : 1348 : constrOid = CreateConstraintEntry(conname,
7875 tgl@sss.pgh.pa.us 10517 : 1348 : RelationGetNamespace(rel),
10518 : : CONSTRAINT_FOREIGN,
10519 : 1348 : fkconstraint->deferrable,
10520 : 1348 : fkconstraint->initdeferred,
4772 simon@2ndQuadrant.co 10521 : 1348 : fkconstraint->initially_valid,
10522 : : parentConstr,
10523 : : RelationGetRelid(rel),
10524 : : fkattnum,
10525 : : numfks,
10526 : : numfks,
10527 : : InvalidOid, /* not a domain constraint */
10528 : : indexOid,
10529 : : RelationGetRelid(pkrel),
10530 : : pkattnum,
10531 : : pfeqoperators,
10532 : : ppeqoperators,
10533 : : ffeqoperators,
10534 : : numfks,
7875 tgl@sss.pgh.pa.us 10535 : 1348 : fkconstraint->fk_upd_action,
10536 : 1348 : fkconstraint->fk_del_action,
10537 : : fkdelsetcols,
10538 : : numfkdelsetcols,
10539 : 1348 : fkconstraint->fk_matchtype,
10540 : : NULL, /* no exclusion constraint */
10541 : : NULL, /* no check constraint */
10542 : : NULL,
10543 : : conislocal, /* islocal */
10544 : : coninhcount, /* inhcount */
10545 : : connoinherit, /* conNoInherit */
10546 : : with_period, /* conPeriod */
10547 : : false); /* is_internal */
10548 : :
3308 alvherre@alvh.no-ip. 10549 : 1348 : ObjectAddressSet(address, ConstraintRelationId, constrOid);
10550 : :
10551 : : /*
10552 : : * Mark the child constraint as part of the parent constraint; it must not
10553 : : * be dropped on its own. (This constraint is deleted when the partition
10554 : : * is detached, but a special check needs to occur that the partition
10555 : : * contains no referenced values.)
10556 : : */
1838 10557 [ + + ]: 1348 : if (OidIsValid(parentConstr))
10558 : : {
10559 : : ObjectAddress referenced;
10560 : :
10561 : 360 : ObjectAddressSet(referenced, ConstraintRelationId, parentConstr);
10562 : 360 : recordDependencyOn(&address, &referenced, DEPENDENCY_INTERNAL);
10563 : : }
10564 : :
10565 : : /* make new constraint visible, in case we add more */
10566 : 1348 : CommandCounterIncrement();
10567 : :
10568 : : /*
10569 : : * Create the action triggers that enforce the constraint.
10570 : : */
830 10571 : 1348 : createForeignKeyActionTriggers(rel, RelationGetRelid(pkrel),
10572 : : fkconstraint,
10573 : : constrOid, indexOid,
10574 : : parentDelTrigger, parentUpdTrigger,
10575 : : &deleteTriggerOid, &updateTriggerOid);
10576 : :
10577 : : /*
10578 : : * If the referenced table is partitioned, recurse on ourselves to handle
10579 : : * each partition. We need one pg_constraint row created for each
10580 : : * partition in addition to the pg_constraint row for the parent table.
10581 : : */
1838 10582 [ + + ]: 1348 : if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
10583 : : {
1088 10584 : 189 : PartitionDesc pd = RelationGetPartitionDesc(pkrel, true);
10585 : :
1838 10586 [ + + ]: 474 : for (int i = 0; i < pd->nparts; i++)
10587 : : {
10588 : : Relation partRel;
10589 : : AttrMap *map;
10590 : : AttrNumber *mapped_pkattnum;
10591 : : Oid partIndexId;
10592 : :
10593 : 285 : partRel = table_open(pd->oids[i], ShareRowExclusiveLock);
10594 : :
10595 : : /*
10596 : : * Map the attribute numbers in the referenced side of the FK
10597 : : * definition to match the partition's column layout.
10598 : : */
1579 michael@paquier.xyz 10599 : 285 : map = build_attrmap_by_name_if_req(RelationGetDescr(partRel),
10600 : : RelationGetDescr(pkrel),
10601 : : false);
1838 alvherre@alvh.no-ip. 10602 [ + + ]: 285 : if (map)
10603 : : {
10604 : 23 : mapped_pkattnum = palloc(sizeof(AttrNumber) * numfks);
10605 [ + + ]: 46 : for (int j = 0; j < numfks; j++)
1579 michael@paquier.xyz 10606 : 23 : mapped_pkattnum[j] = map->attnums[pkattnum[j] - 1];
10607 : : }
10608 : : else
1838 alvherre@alvh.no-ip. 10609 : 262 : mapped_pkattnum = pkattnum;
10610 : :
10611 : : /* do the deed */
10612 : 285 : partIndexId = index_get_partition(partRel, indexOid);
10613 [ - + ]: 285 : if (!OidIsValid(partIndexId))
1838 alvherre@alvh.no-ip. 10614 [ # # ]:UBC 0 : elog(ERROR, "index for %u not found in partition %s",
10615 : : indexOid, RelationGetRelationName(partRel));
1838 alvherre@alvh.no-ip. 10616 :CBC 285 : addFkRecurseReferenced(wqueue, fkconstraint, rel, partRel,
10617 : : partIndexId, constrOid, numfks,
10618 : : mapped_pkattnum, fkattnum,
10619 : : pfeqoperators, ppeqoperators, ffeqoperators,
10620 : : numfkdelsetcols, fkdelsetcols,
10621 : : old_check_ok,
10622 : : deleteTriggerOid, updateTriggerOid,
10623 : : with_period);
10624 : :
10625 : : /* Done -- clean up (but keep the lock) */
10626 : 285 : table_close(partRel, NoLock);
10627 [ + + ]: 285 : if (map)
10628 : : {
10629 : 23 : pfree(mapped_pkattnum);
1579 michael@paquier.xyz 10630 : 23 : free_attrmap(map);
10631 : : }
10632 : : }
10633 : : }
10634 : :
1838 alvherre@alvh.no-ip. 10635 : 1348 : return address;
10636 : : }
10637 : :
10638 : : /*
10639 : : * addFkRecurseReferencing
10640 : : * subroutine for ATAddForeignKeyConstraint and CloneFkReferencing
10641 : : *
10642 : : * If the referencing relation is a plain relation, create the necessary check
10643 : : * triggers that implement the constraint, and set up for Phase 3 constraint
10644 : : * verification. If the referencing relation is a partitioned table, then
10645 : : * we create a pg_constraint row for it and recurse on this routine for each
10646 : : * partition.
10647 : : *
10648 : : * We assume that the referenced relation is locked against concurrent
10649 : : * deletions. If it's a partitioned relation, every partition must be so
10650 : : * locked.
10651 : : *
10652 : : * wqueue is the ALTER TABLE work queue; can be NULL when not running as part
10653 : : * of an ALTER TABLE sequence.
10654 : : * fkconstraint is the constraint being added.
10655 : : * rel is the referencing relation; might be a partition, if recursing.
10656 : : * pkrel is the root referenced relation.
10657 : : * indexOid is the OID of the index (on pkrel) implementing this constraint.
10658 : : * parentConstr is the OID of the parent constraint (there is always one).
10659 : : * numfks is the number of columns in the foreign key
10660 : : * pkattnum is the attnum array of referenced attributes.
10661 : : * fkattnum is the attnum array of referencing attributes.
10662 : : * pf/pp/ffeqoperators are OID array of operators between columns.
10663 : : * numfkdelsetcols is the number of columns in the ON DELETE SET NULL/DEFAULT
10664 : : * (...) clause
10665 : : * fkdelsetcols is the attnum array of the columns in the ON DELETE SET
10666 : : * NULL/DEFAULT clause
10667 : : * old_check_ok signals that this constraint replaces an existing one that
10668 : : * was already validated (thus this one doesn't need validation).
10669 : : * lockmode is the lockmode to acquire on partitions when recursing.
10670 : : * parentInsTrigger and parentUpdTrigger, when being recursively called on
10671 : : * a partition, are the OIDs of the parent check triggers for INSERT and
10672 : : * UPDATE respectively.
10673 : : */
10674 : : static void
10675 : 1373 : addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
10676 : : Relation pkrel, Oid indexOid, Oid parentConstr,
10677 : : int numfks, int16 *pkattnum, int16 *fkattnum,
10678 : : Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
10679 : : int numfkdelsetcols, int16 *fkdelsetcols,
10680 : : bool old_check_ok, LOCKMODE lockmode,
10681 : : Oid parentInsTrigger, Oid parentUpdTrigger,
10682 : : bool with_period)
10683 : : {
10684 : : Oid insertTriggerOid,
10685 : : updateTriggerOid;
10686 : :
534 peter@eisentraut.org 10687 [ - + ]: 1373 : Assert(OidIsValid(parentConstr));
10688 : :
1838 alvherre@alvh.no-ip. 10689 [ - + ]: 1373 : if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
1838 alvherre@alvh.no-ip. 10690 [ # # ]:UBC 0 : ereport(ERROR,
10691 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
10692 : : errmsg("foreign key constraints are not supported on foreign tables")));
10693 : :
10694 : : /*
10695 : : * Add the check triggers to it and, if necessary, schedule it to be
10696 : : * checked in Phase 3.
10697 : : *
10698 : : * If the relation is partitioned, drill down to do it to its partitions.
10699 : : */
830 alvherre@alvh.no-ip. 10700 :CBC 1373 : createForeignKeyCheckTriggers(RelationGetRelid(rel),
10701 : : RelationGetRelid(pkrel),
10702 : : fkconstraint,
10703 : : parentConstr,
10704 : : indexOid,
10705 : : parentInsTrigger, parentUpdTrigger,
10706 : : &insertTriggerOid, &updateTriggerOid);
10707 : :
1838 10708 [ + + ]: 1373 : if (rel->rd_rel->relkind == RELKIND_RELATION)
10709 : : {
10710 : : /*
10711 : : * Tell Phase 3 to check that the constraint is satisfied by existing
10712 : : * rows. We can skip this during table creation, when requested
10713 : : * explicitly by specifying NOT VALID in an ADD FOREIGN KEY command,
10714 : : * and when we're recreating a constraint following a SET DATA TYPE
10715 : : * operation that did not impugn its validity.
10716 : : */
10717 [ + + + + : 1167 : if (wqueue && !old_check_ok && !fkconstraint->skip_validation)
+ + ]
10718 : : {
10719 : : NewConstraint *newcon;
10720 : : AlteredTableInfo *tab;
10721 : :
10722 : 396 : tab = ATGetQueueEntry(wqueue, rel);
10723 : :
1913 10724 : 396 : newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
1838 10725 : 396 : newcon->name = get_constraint_name(parentConstr);
1913 10726 : 396 : newcon->contype = CONSTR_FOREIGN;
1838 10727 : 396 : newcon->refrelid = RelationGetRelid(pkrel);
10728 : 396 : newcon->refindid = indexOid;
10729 : 396 : newcon->conid = parentConstr;
21 peter@eisentraut.org 10730 :GNC 396 : newcon->conwithperiod = fkconstraint->fk_with_period;
1913 alvherre@alvh.no-ip. 10731 :CBC 396 : newcon->qual = (Node *) fkconstraint;
10732 : :
1838 10733 : 396 : tab->constraints = lappend(tab->constraints, newcon);
10734 : : }
10735 : : }
10736 [ + - ]: 206 : else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
10737 : : {
1088 10738 : 206 : PartitionDesc pd = RelationGetPartitionDesc(rel, true);
10739 : : Relation trigrel;
10740 : :
10741 : : /*
10742 : : * Triggers of the foreign keys will be manipulated a bunch of times
10743 : : * in the loop below. To avoid repeatedly opening/closing the trigger
10744 : : * catalog relation, we open it here and pass it to the subroutines
10745 : : * called below.
10746 : : */
830 10747 : 206 : trigrel = table_open(TriggerRelationId, RowExclusiveLock);
10748 : :
10749 : : /*
10750 : : * Recurse to take appropriate action on each partition; either we
10751 : : * find an existing constraint to reparent to ours, or we create a new
10752 : : * one.
10753 : : */
1838 10754 [ + + ]: 394 : for (int i = 0; i < pd->nparts; i++)
10755 : : {
10756 : 188 : Oid partitionId = pd->oids[i];
10757 : 188 : Relation partition = table_open(partitionId, lockmode);
10758 : : List *partFKs;
10759 : : AttrMap *attmap;
10760 : : AttrNumber mapped_fkattnum[INDEX_MAX_KEYS];
10761 : : bool attached;
10762 : : char *conname;
10763 : : Oid constrOid;
10764 : : ObjectAddress address,
10765 : : referenced;
10766 : : ListCell *cell;
10767 : :
10768 : 188 : CheckTableNotInUse(partition, "ALTER TABLE");
10769 : :
1579 michael@paquier.xyz 10770 : 188 : attmap = build_attrmap_by_name(RelationGetDescr(partition),
10771 : : RelationGetDescr(rel),
10772 : : false);
1838 alvherre@alvh.no-ip. 10773 [ + + ]: 484 : for (int j = 0; j < numfks; j++)
1579 michael@paquier.xyz 10774 : 296 : mapped_fkattnum[j] = attmap->attnums[fkattnum[j] - 1];
10775 : :
10776 : : /* Check whether an existing constraint can be repurposed */
1838 alvherre@alvh.no-ip. 10777 : 188 : partFKs = copyObject(RelationGetFKeyList(partition));
10778 : 188 : attached = false;
10779 [ + + + + : 197 : foreach(cell, partFKs)
+ + ]
10780 : : {
10781 : : ForeignKeyCacheInfo *fk;
10782 : :
10783 : 15 : fk = lfirst_node(ForeignKeyCacheInfo, cell);
10784 [ + + ]: 15 : if (tryAttachPartitionForeignKey(fk,
10785 : : partitionId,
10786 : : parentConstr,
10787 : : numfks,
10788 : : mapped_fkattnum,
10789 : : pkattnum,
10790 : : pfeqoperators,
10791 : : insertTriggerOid,
10792 : : updateTriggerOid,
10793 : : trigrel))
10794 : : {
10795 : 6 : attached = true;
10796 : 6 : break;
10797 : : }
10798 : : }
10799 [ + + ]: 188 : if (attached)
10800 : : {
10801 : 6 : table_close(partition, NoLock);
10802 : 6 : continue;
10803 : : }
10804 : :
10805 : : /*
10806 : : * No luck finding a good constraint to reuse; create our own.
10807 : : */
10808 [ - + ]: 182 : if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
10809 : : RelationGetRelid(partition),
10810 : 182 : fkconstraint->conname))
1838 alvherre@alvh.no-ip. 10811 :UBC 0 : conname = ChooseConstraintName(RelationGetRelationName(partition),
10812 : 0 : ChooseForeignKeyConstraintNameAddition(fkconstraint->fk_attrs),
10813 : : "fkey",
10814 : 0 : RelationGetNamespace(partition), NIL);
10815 : : else
1838 alvherre@alvh.no-ip. 10816 :CBC 182 : conname = fkconstraint->conname;
10817 : : constrOid =
10818 : 182 : CreateConstraintEntry(conname,
10819 : 182 : RelationGetNamespace(partition),
10820 : : CONSTRAINT_FOREIGN,
10821 : 182 : fkconstraint->deferrable,
10822 : 182 : fkconstraint->initdeferred,
10823 : 182 : fkconstraint->initially_valid,
10824 : : parentConstr,
10825 : : partitionId,
10826 : : mapped_fkattnum,
10827 : : numfks,
10828 : : numfks,
10829 : : InvalidOid,
10830 : : indexOid,
10831 : : RelationGetRelid(pkrel),
10832 : : pkattnum,
10833 : : pfeqoperators,
10834 : : ppeqoperators,
10835 : : ffeqoperators,
10836 : : numfks,
10837 : 182 : fkconstraint->fk_upd_action,
10838 : 182 : fkconstraint->fk_del_action,
10839 : : fkdelsetcols,
10840 : : numfkdelsetcols,
10841 : 182 : fkconstraint->fk_matchtype,
10842 : : NULL,
10843 : : NULL,
10844 : : NULL,
10845 : : false,
10846 : : 1,
10847 : : false,
10848 : : with_period, /* conPeriod */
10849 : : false);
10850 : :
10851 : : /*
10852 : : * Give this constraint partition-type dependencies on the parent
10853 : : * constraint as well as the table.
10854 : : */
10855 : 182 : ObjectAddressSet(address, ConstraintRelationId, constrOid);
10856 : 182 : ObjectAddressSet(referenced, ConstraintRelationId, parentConstr);
10857 : 182 : recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_PRI);
10858 : 182 : ObjectAddressSet(referenced, RelationRelationId, partitionId);
10859 : 182 : recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_SEC);
10860 : :
10861 : : /* Make all this visible before recursing */
10862 : 182 : CommandCounterIncrement();
10863 : :
10864 : : /* call ourselves to finalize the creation and we're done */
10865 : 182 : addFkRecurseReferencing(wqueue, fkconstraint, partition, pkrel,
10866 : : indexOid,
10867 : : constrOid,
10868 : : numfks,
10869 : : pkattnum,
10870 : : mapped_fkattnum,
10871 : : pfeqoperators,
10872 : : ppeqoperators,
10873 : : ffeqoperators,
10874 : : numfkdelsetcols,
10875 : : fkdelsetcols,
10876 : : old_check_ok,
10877 : : lockmode,
10878 : : insertTriggerOid,
10879 : : updateTriggerOid,
10880 : : with_period);
10881 : :
10882 : 182 : table_close(partition, NoLock);
10883 : : }
10884 : :
830 10885 : 206 : table_close(trigrel, RowExclusiveLock);
10886 : : }
7875 tgl@sss.pgh.pa.us 10887 : 1373 : }
10888 : :
10889 : : /*
10890 : : * CloneForeignKeyConstraints
10891 : : * Clone foreign keys from a partitioned table to a newly acquired
10892 : : * partition.
10893 : : *
10894 : : * partitionRel is a partition of parentRel, so we can be certain that it has
10895 : : * the same columns with the same datatypes. The columns may be in different
10896 : : * order, though.
10897 : : *
10898 : : * wqueue must be passed to set up phase 3 constraint checking, unless the
10899 : : * referencing-side partition is known to be empty (such as in CREATE TABLE /
10900 : : * PARTITION OF).
10901 : : */
10902 : : static void
1838 alvherre@alvh.no-ip. 10903 : 5081 : CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
10904 : : Relation partitionRel)
10905 : : {
10906 : : /* This only works for declarative partitioning */
10907 [ - + ]: 5081 : Assert(parentRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
10908 : :
10909 : : /*
10910 : : * Clone constraints for which the parent is on the referenced side.
10911 : : */
10912 : 5081 : CloneFkReferenced(parentRel, partitionRel);
10913 : :
10914 : : /*
10915 : : * Now clone constraints where the parent is on the referencing side.
10916 : : */
10917 : 5081 : CloneFkReferencing(wqueue, parentRel, partitionRel);
10918 : 5081 : }
10919 : :
10920 : : /*
10921 : : * CloneFkReferenced
10922 : : * Subroutine for CloneForeignKeyConstraints
10923 : : *
10924 : : * Find all the FKs that have the parent relation on the referenced side;
10925 : : * clone those constraints to the given partition. This is to be called
10926 : : * when the partition is being created or attached.
10927 : : *
10928 : : * This ignores self-referencing FKs; those are handled by CloneFkReferencing.
10929 : : *
10930 : : * This recurses to partitions, if the relation being attached is partitioned.
10931 : : * Recursion is done by calling addFkRecurseReferenced.
10932 : : */
10933 : : static void
10934 : 5081 : CloneFkReferenced(Relation parentRel, Relation partitionRel)
10935 : : {
10936 : : Relation pg_constraint;
10937 : : AttrMap *attmap;
10938 : : ListCell *cell;
10939 : : SysScanDesc scan;
10940 : : ScanKeyData key[2];
10941 : : HeapTuple tuple;
1913 10942 : 5081 : List *clone = NIL;
10943 : : Relation trigrel;
10944 : :
10945 : : /*
10946 : : * Search for any constraints where this partition's parent is in the
10947 : : * referenced side. However, we must not clone any constraint whose
10948 : : * parent constraint is also going to be cloned, to avoid duplicates. So
10949 : : * do it in two steps: first construct the list of constraints to clone,
10950 : : * then go over that list cloning those whose parents are not in the list.
10951 : : * (We must not rely on the parent being seen first, since the catalog
10952 : : * scan could return children first.)
10953 : : */
1910 andres@anarazel.de 10954 : 5081 : pg_constraint = table_open(ConstraintRelationId, RowShareLock);
1838 alvherre@alvh.no-ip. 10955 : 5081 : ScanKeyInit(&key[0],
10956 : : Anum_pg_constraint_confrelid, BTEqualStrategyNumber,
10957 : : F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(parentRel)));
10958 : 5081 : ScanKeyInit(&key[1],
10959 : : Anum_pg_constraint_contype, BTEqualStrategyNumber,
10960 : : F_CHAREQ, CharGetDatum(CONSTRAINT_FOREIGN));
10961 : : /* This is a seqscan, as we don't have a usable index ... */
10962 : 5081 : scan = systable_beginscan(pg_constraint, InvalidOid, true,
10963 : : NULL, 2, key);
1913 10964 [ + + ]: 5246 : while ((tuple = systable_getnext(scan)) != NULL)
10965 : : {
1838 10966 : 165 : Form_pg_constraint constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
10967 : :
10968 : 165 : clone = lappend_oid(clone, constrForm->oid);
10969 : : }
1913 10970 : 5081 : systable_endscan(scan);
1838 10971 : 5081 : table_close(pg_constraint, RowShareLock);
10972 : :
10973 : : /*
10974 : : * Triggers of the foreign keys will be manipulated a bunch of times in
10975 : : * the loop below. To avoid repeatedly opening/closing the trigger
10976 : : * catalog relation, we open it here and pass it to the subroutines called
10977 : : * below.
10978 : : */
830 10979 : 5081 : trigrel = table_open(TriggerRelationId, RowExclusiveLock);
10980 : :
1579 michael@paquier.xyz 10981 : 5081 : attmap = build_attrmap_by_name(RelationGetDescr(partitionRel),
10982 : : RelationGetDescr(parentRel),
10983 : : false);
1838 alvherre@alvh.no-ip. 10984 [ + + + + : 5246 : foreach(cell, clone)
+ + ]
10985 : : {
10986 : 165 : Oid constrOid = lfirst_oid(cell);
10987 : : Form_pg_constraint constrForm;
10988 : : Relation fkRel;
10989 : : Oid indexOid;
10990 : : Oid partIndexId;
10991 : : int numfks;
10992 : : AttrNumber conkey[INDEX_MAX_KEYS];
10993 : : AttrNumber mapped_confkey[INDEX_MAX_KEYS];
10994 : : AttrNumber confkey[INDEX_MAX_KEYS];
10995 : : Oid conpfeqop[INDEX_MAX_KEYS];
10996 : : Oid conppeqop[INDEX_MAX_KEYS];
10997 : : Oid conffeqop[INDEX_MAX_KEYS];
10998 : : int numfkdelsetcols;
10999 : : AttrNumber confdelsetcols[INDEX_MAX_KEYS];
11000 : : Constraint *fkconstraint;
11001 : : Oid deleteTriggerOid,
11002 : : updateTriggerOid;
11003 : :
269 michael@paquier.xyz 11004 :GNC 165 : tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
1838 alvherre@alvh.no-ip. 11005 [ - + ]:CBC 165 : if (!HeapTupleIsValid(tuple))
1838 alvherre@alvh.no-ip. 11006 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for constraint %u", constrOid);
1838 alvherre@alvh.no-ip. 11007 :CBC 165 : constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
11008 : :
11009 : : /*
11010 : : * As explained above: don't try to clone a constraint for which we're
11011 : : * going to clone the parent.
11012 : : */
1528 11013 [ + + ]: 165 : if (list_member_oid(clone, constrForm->conparentid))
11014 : : {
11015 : 63 : ReleaseSysCache(tuple);
555 11016 : 90 : continue;
11017 : : }
11018 : :
11019 : : /*
11020 : : * Don't clone self-referencing foreign keys, which can be in the
11021 : : * partitioned table or in the partition-to-be.
11022 : : */
11023 [ + + ]: 102 : if (constrForm->conrelid == RelationGetRelid(parentRel) ||
11024 [ + + ]: 81 : constrForm->conrelid == RelationGetRelid(partitionRel))
11025 : : {
11026 : 27 : ReleaseSysCache(tuple);
1528 11027 : 27 : continue;
11028 : : }
11029 : :
11030 : : /*
11031 : : * Because we're only expanding the key space at the referenced side,
11032 : : * we don't need to prevent any operation in the referencing table, so
11033 : : * AccessShareLock suffices (assumes that dropping the constraint
11034 : : * acquires AEL).
11035 : : */
1838 11036 : 75 : fkRel = table_open(constrForm->conrelid, AccessShareLock);
11037 : :
11038 : 75 : indexOid = constrForm->conindid;
11039 : 75 : DeconstructFkConstraintRow(tuple,
11040 : : &numfks,
11041 : : conkey,
11042 : : confkey,
11043 : : conpfeqop,
11044 : : conppeqop,
11045 : : conffeqop,
11046 : : &numfkdelsetcols,
11047 : : confdelsetcols);
11048 : :
11049 [ + + ]: 150 : for (int i = 0; i < numfks; i++)
1579 michael@paquier.xyz 11050 : 75 : mapped_confkey[i] = attmap->attnums[confkey[i] - 1];
11051 : :
1838 alvherre@alvh.no-ip. 11052 : 75 : fkconstraint = makeNode(Constraint);
528 11053 : 75 : fkconstraint->contype = CONSTRAINT_FOREIGN;
1838 11054 : 75 : fkconstraint->conname = NameStr(constrForm->conname);
11055 : 75 : fkconstraint->deferrable = constrForm->condeferrable;
11056 : 75 : fkconstraint->initdeferred = constrForm->condeferred;
528 11057 : 75 : fkconstraint->location = -1;
11058 : 75 : fkconstraint->pktable = NULL;
11059 : : /* ->fk_attrs determined below */
11060 : 75 : fkconstraint->pk_attrs = NIL;
1838 11061 : 75 : fkconstraint->fk_matchtype = constrForm->confmatchtype;
528 11062 : 75 : fkconstraint->fk_upd_action = constrForm->confupdtype;
11063 : 75 : fkconstraint->fk_del_action = constrForm->confdeltype;
11064 : 75 : fkconstraint->fk_del_set_cols = NIL;
11065 : 75 : fkconstraint->old_conpfeqop = NIL;
11066 : 75 : fkconstraint->old_pktable_oid = InvalidOid;
11067 : 75 : fkconstraint->skip_validation = false;
11068 : 75 : fkconstraint->initially_valid = true;
11069 : :
11070 : : /* set up colnames that are used to generate the constraint name */
1838 11071 [ + + ]: 150 : for (int i = 0; i < numfks; i++)
11072 : : {
11073 : : Form_pg_attribute att;
11074 : :
11075 : 75 : att = TupleDescAttr(RelationGetDescr(fkRel),
11076 : : conkey[i] - 1);
11077 : 75 : fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
11078 : 75 : makeString(NameStr(att->attname)));
11079 : : }
11080 : :
11081 : : /*
11082 : : * Add the new foreign key constraint pointing to the new partition.
11083 : : * Because this new partition appears in the referenced side of the
11084 : : * constraint, we don't need to set up for Phase 3 check.
11085 : : */
11086 : 75 : partIndexId = index_get_partition(partitionRel, indexOid);
11087 [ - + ]: 75 : if (!OidIsValid(partIndexId))
1838 alvherre@alvh.no-ip. 11088 [ # # ]:UBC 0 : elog(ERROR, "index for %u not found in partition %s",
11089 : : indexOid, RelationGetRelationName(partitionRel));
11090 : :
11091 : : /*
11092 : : * Get the "action" triggers belonging to the constraint to pass as
11093 : : * parent OIDs for similar triggers that will be created on the
11094 : : * partition in addFkRecurseReferenced().
11095 : : */
830 alvherre@alvh.no-ip. 11096 :CBC 75 : GetForeignKeyActionTriggers(trigrel, constrOid,
11097 : : constrForm->confrelid, constrForm->conrelid,
11098 : : &deleteTriggerOid, &updateTriggerOid);
11099 : :
1838 11100 : 75 : addFkRecurseReferenced(NULL,
11101 : : fkconstraint,
11102 : : fkRel,
11103 : : partitionRel,
11104 : : partIndexId,
11105 : : constrOid,
11106 : : numfks,
11107 : : mapped_confkey,
11108 : : conkey,
11109 : : conpfeqop,
11110 : : conppeqop,
11111 : : conffeqop,
11112 : : numfkdelsetcols,
11113 : : confdelsetcols,
11114 : : true,
11115 : : deleteTriggerOid,
11116 : : updateTriggerOid,
21 peter@eisentraut.org 11117 :GNC 75 : constrForm->conperiod);
11118 : :
1838 alvherre@alvh.no-ip. 11119 :CBC 75 : table_close(fkRel, NoLock);
11120 : 75 : ReleaseSysCache(tuple);
11121 : : }
11122 : :
830 11123 : 5081 : table_close(trigrel, RowExclusiveLock);
1913 11124 : 5081 : }
11125 : :
11126 : : /*
11127 : : * CloneFkReferencing
11128 : : * Subroutine for CloneForeignKeyConstraints
11129 : : *
11130 : : * For each FK constraint of the parent relation in the given list, find an
11131 : : * equivalent constraint in its partition relation that can be reparented;
11132 : : * if one cannot be found, create a new constraint in the partition as its
11133 : : * child.
11134 : : *
11135 : : * If wqueue is given, it is used to set up phase-3 verification for each
11136 : : * cloned constraint; if omitted, we assume that such verification is not
11137 : : * needed (example: the partition is being created anew).
11138 : : */
11139 : : static void
1838 11140 : 5081 : CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
11141 : : {
11142 : : AttrMap *attmap;
11143 : : List *partFKs;
11144 : 5081 : List *clone = NIL;
11145 : : ListCell *cell;
11146 : : Relation trigrel;
11147 : :
11148 : : /* obtain a list of constraints that we need to clone */
11149 [ + + + + : 5529 : foreach(cell, RelationGetFKeyList(parentRel))
+ + ]
11150 : : {
11151 : 448 : ForeignKeyCacheInfo *fk = lfirst(cell);
11152 : :
11153 : 448 : clone = lappend_oid(clone, fk->conoid);
11154 : : }
11155 : :
11156 : : /*
11157 : : * Silently do nothing if there's nothing to do. In particular, this
11158 : : * avoids throwing a spurious error for foreign tables.
11159 : : */
11160 [ + + ]: 5081 : if (clone == NIL)
11161 : 4857 : return;
11162 : :
11163 [ - + ]: 224 : if (partRel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
1838 alvherre@alvh.no-ip. 11164 [ # # ]:UBC 0 : ereport(ERROR,
11165 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
11166 : : errmsg("foreign key constraints are not supported on foreign tables")));
11167 : :
11168 : : /*
11169 : : * Triggers of the foreign keys will be manipulated a bunch of times in
11170 : : * the loop below. To avoid repeatedly opening/closing the trigger
11171 : : * catalog relation, we open it here and pass it to the subroutines called
11172 : : * below.
11173 : : */
830 alvherre@alvh.no-ip. 11174 :CBC 224 : trigrel = table_open(TriggerRelationId, RowExclusiveLock);
11175 : :
11176 : : /*
11177 : : * The constraint key may differ, if the columns in the partition are
11178 : : * different. This map is used to convert them.
11179 : : */
1579 michael@paquier.xyz 11180 : 224 : attmap = build_attrmap_by_name(RelationGetDescr(partRel),
11181 : : RelationGetDescr(parentRel),
11182 : : false);
11183 : :
1913 alvherre@alvh.no-ip. 11184 : 224 : partFKs = copyObject(RelationGetFKeyList(partRel));
11185 : :
11186 [ + - + + : 672 : foreach(cell, clone)
+ + ]
11187 : : {
11188 : 448 : Oid parentConstrOid = lfirst_oid(cell);
11189 : : Form_pg_constraint constrForm;
11190 : : Relation pkrel;
11191 : : HeapTuple tuple;
11192 : : int numfks;
11193 : : AttrNumber conkey[INDEX_MAX_KEYS];
11194 : : AttrNumber mapped_conkey[INDEX_MAX_KEYS];
11195 : : AttrNumber confkey[INDEX_MAX_KEYS];
11196 : : Oid conpfeqop[INDEX_MAX_KEYS];
11197 : : Oid conppeqop[INDEX_MAX_KEYS];
11198 : : Oid conffeqop[INDEX_MAX_KEYS];
11199 : : int numfkdelsetcols;
11200 : : AttrNumber confdelsetcols[INDEX_MAX_KEYS];
11201 : : Constraint *fkconstraint;
11202 : : bool attached;
11203 : : Oid indexOid;
11204 : : Oid constrOid;
11205 : : ObjectAddress address,
11206 : : referenced;
11207 : : ListCell *lc;
11208 : : Oid insertTriggerOid,
11209 : : updateTriggerOid;
11210 : : bool with_period;
11211 : :
269 michael@paquier.xyz 11212 :GNC 448 : tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(parentConstrOid));
1806 tgl@sss.pgh.pa.us 11213 [ - + ]:CBC 448 : if (!HeapTupleIsValid(tuple))
1913 alvherre@alvh.no-ip. 11214 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for constraint %u",
11215 : : parentConstrOid);
1913 alvherre@alvh.no-ip. 11216 :CBC 448 : constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
11217 : :
11218 : : /* Don't clone constraints whose parents are being cloned */
1838 11219 [ + + ]: 448 : if (list_member_oid(clone, constrForm->conparentid))
11220 : : {
1913 11221 : 212 : ReleaseSysCache(tuple);
11222 : 245 : continue;
11223 : : }
11224 : :
11225 : : /*
11226 : : * Need to prevent concurrent deletions. If pkrel is a partitioned
11227 : : * relation, that means to lock all partitions.
11228 : : */
1838 11229 : 236 : pkrel = table_open(constrForm->confrelid, ShareRowExclusiveLock);
11230 [ + + ]: 236 : if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
11231 : 92 : (void) find_all_inheritors(RelationGetRelid(pkrel),
11232 : : ShareRowExclusiveLock, NULL);
11233 : :
1913 11234 : 236 : DeconstructFkConstraintRow(tuple, &numfks, conkey, confkey,
11235 : : conpfeqop, conppeqop, conffeqop,
11236 : : &numfkdelsetcols, confdelsetcols);
1838 11237 [ + + ]: 541 : for (int i = 0; i < numfks; i++)
1579 michael@paquier.xyz 11238 : 305 : mapped_conkey[i] = attmap->attnums[conkey[i] - 1];
11239 : :
11240 : : /*
11241 : : * Get the "check" triggers belonging to the constraint to pass as
11242 : : * parent OIDs for similar triggers that will be created on the
11243 : : * partition in addFkRecurseReferencing(). They are also passed to
11244 : : * tryAttachPartitionForeignKey() below to simply assign as parents to
11245 : : * the partition's existing "check" triggers, that is, if the
11246 : : * corresponding constraints is deemed attachable to the parent
11247 : : * constraint.
11248 : : */
830 alvherre@alvh.no-ip. 11249 : 236 : GetForeignKeyCheckTriggers(trigrel, constrForm->oid,
11250 : : constrForm->confrelid, constrForm->conrelid,
11251 : : &insertTriggerOid, &updateTriggerOid);
11252 : :
11253 : : /*
11254 : : * Before creating a new constraint, see whether any existing FKs are
11255 : : * fit for the purpose. If one is, attach the parent constraint to
11256 : : * it, and don't clone anything. This way we avoid the expensive
11257 : : * verification step and don't end up with a duplicate FK, and we
11258 : : * don't need to recurse to partitions for this constraint.
11259 : : */
1838 11260 : 236 : attached = false;
557 drowley@postgresql.o 11261 [ + + + + : 278 : foreach(lc, partFKs)
+ + ]
11262 : : {
11263 : 75 : ForeignKeyCacheInfo *fk = lfirst_node(ForeignKeyCacheInfo, lc);
11264 : :
1838 alvherre@alvh.no-ip. 11265 [ + + ]: 75 : if (tryAttachPartitionForeignKey(fk,
11266 : : RelationGetRelid(partRel),
11267 : : parentConstrOid,
11268 : : numfks,
11269 : : mapped_conkey,
11270 : : confkey,
11271 : : conpfeqop,
11272 : : insertTriggerOid,
11273 : : updateTriggerOid,
11274 : : trigrel))
11275 : : {
11276 : 33 : attached = true;
11277 : 33 : table_close(pkrel, NoLock);
11278 : 33 : break;
11279 : : }
11280 : : }
11281 [ + + ]: 236 : if (attached)
11282 : : {
1913 11283 : 33 : ReleaseSysCache(tuple);
11284 : 33 : continue;
11285 : : }
11286 : :
11287 : : /* No dice. Set up to create our own constraint */
1838 11288 : 203 : fkconstraint = makeNode(Constraint);
528 11289 : 203 : fkconstraint->contype = CONSTRAINT_FOREIGN;
11290 : : /* ->conname determined below */
1838 11291 : 203 : fkconstraint->deferrable = constrForm->condeferrable;
11292 : 203 : fkconstraint->initdeferred = constrForm->condeferred;
528 11293 : 203 : fkconstraint->location = -1;
11294 : 203 : fkconstraint->pktable = NULL;
11295 : : /* ->fk_attrs determined below */
11296 : 203 : fkconstraint->pk_attrs = NIL;
1838 11297 : 203 : fkconstraint->fk_matchtype = constrForm->confmatchtype;
528 11298 : 203 : fkconstraint->fk_upd_action = constrForm->confupdtype;
11299 : 203 : fkconstraint->fk_del_action = constrForm->confdeltype;
11300 : 203 : fkconstraint->fk_del_set_cols = NIL;
11301 : 203 : fkconstraint->old_conpfeqop = NIL;
11302 : 203 : fkconstraint->old_pktable_oid = InvalidOid;
11303 : 203 : fkconstraint->skip_validation = false;
11304 : 203 : fkconstraint->initially_valid = true;
1838 11305 [ + + ]: 451 : for (int i = 0; i < numfks; i++)
11306 : : {
11307 : : Form_pg_attribute att;
11308 : :
11309 : 248 : att = TupleDescAttr(RelationGetDescr(partRel),
11310 : : mapped_conkey[i] - 1);
11311 : 248 : fkconstraint->fk_attrs = lappend(fkconstraint->fk_attrs,
11312 : 248 : makeString(NameStr(att->attname)));
11313 : : }
584 11314 [ + + ]: 203 : if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
11315 : : RelationGetRelid(partRel),
11316 : 203 : NameStr(constrForm->conname)))
11317 : 3 : fkconstraint->conname =
11318 : 3 : ChooseConstraintName(RelationGetRelationName(partRel),
11319 : 3 : ChooseForeignKeyConstraintNameAddition(fkconstraint->fk_attrs),
11320 : : "fkey",
11321 : 3 : RelationGetNamespace(partRel), NIL);
11322 : : else
11323 : 200 : fkconstraint->conname = pstrdup(NameStr(constrForm->conname));
11324 : :
1838 11325 : 203 : indexOid = constrForm->conindid;
21 peter@eisentraut.org 11326 :GNC 203 : with_period = constrForm->conperiod;
11327 : : constrOid =
1838 alvherre@alvh.no-ip. 11328 :CBC 203 : CreateConstraintEntry(fkconstraint->conname,
11329 : : constrForm->connamespace,
11330 : : CONSTRAINT_FOREIGN,
11331 : 203 : fkconstraint->deferrable,
11332 : 203 : fkconstraint->initdeferred,
1913 11333 : 203 : constrForm->convalidated,
11334 : : parentConstrOid,
11335 : : RelationGetRelid(partRel),
11336 : : mapped_conkey,
11337 : : numfks,
11338 : : numfks,
11339 : : InvalidOid, /* not a domain constraint */
11340 : : indexOid,
11341 : : constrForm->confrelid, /* same foreign rel */
11342 : : confkey,
11343 : : conpfeqop,
11344 : : conppeqop,
11345 : : conffeqop,
11346 : : numfks,
1838 11347 : 203 : fkconstraint->fk_upd_action,
11348 : 203 : fkconstraint->fk_del_action,
11349 : : confdelsetcols,
11350 : : numfkdelsetcols,
11351 : 203 : fkconstraint->fk_matchtype,
11352 : : NULL,
11353 : : NULL,
11354 : : NULL,
11355 : : false, /* islocal */
11356 : : 1, /* inhcount */
11357 : : false, /* conNoInherit */
11358 : : with_period, /* conPeriod */
11359 : : true);
11360 : :
11361 : : /* Set up partition dependencies for the new constraint */
11362 : 203 : ObjectAddressSet(address, ConstraintRelationId, constrOid);
11363 : 203 : ObjectAddressSet(referenced, ConstraintRelationId, parentConstrOid);
11364 : 203 : recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_PRI);
11365 : 203 : ObjectAddressSet(referenced, RelationRelationId,
11366 : : RelationGetRelid(partRel));
11367 : 203 : recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_SEC);
11368 : :
11369 : : /* Done with the cloned constraint's tuple */
11370 : 203 : ReleaseSysCache(tuple);
11371 : :
11372 : : /* Make all this visible before recursing */
11373 : 203 : CommandCounterIncrement();
11374 : :
11375 : 203 : addFkRecurseReferencing(wqueue,
11376 : : fkconstraint,
11377 : : partRel,
11378 : : pkrel,
11379 : : indexOid,
11380 : : constrOid,
11381 : : numfks,
11382 : : confkey,
11383 : : mapped_conkey,
11384 : : conpfeqop,
11385 : : conppeqop,
11386 : : conffeqop,
11387 : : numfkdelsetcols,
11388 : : confdelsetcols,
11389 : : false, /* no old check exists */
11390 : : AccessExclusiveLock,
11391 : : insertTriggerOid,
11392 : : updateTriggerOid,
11393 : : with_period);
11394 : 203 : table_close(pkrel, NoLock);
11395 : : }
11396 : :
830 11397 : 224 : table_close(trigrel, RowExclusiveLock);
11398 : : }
11399 : :
11400 : : /*
11401 : : * When the parent of a partition receives [the referencing side of] a foreign
11402 : : * key, we must propagate that foreign key to the partition. However, the
11403 : : * partition might already have an equivalent foreign key; this routine
11404 : : * compares the given ForeignKeyCacheInfo (in the partition) to the FK defined
11405 : : * by the other parameters. If they are equivalent, create the link between
11406 : : * the two constraints and return true.
11407 : : *
11408 : : * If the given FK does not match the one defined by rest of the params,
11409 : : * return false.
11410 : : */
11411 : : static bool
1838 11412 : 90 : tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
11413 : : Oid partRelid,
11414 : : Oid parentConstrOid,
11415 : : int numfks,
11416 : : AttrNumber *mapped_conkey,
11417 : : AttrNumber *confkey,
11418 : : Oid *conpfeqop,
11419 : : Oid parentInsTrigger,
11420 : : Oid parentUpdTrigger,
11421 : : Relation trigrel)
11422 : : {
11423 : : HeapTuple parentConstrTup;
11424 : : Form_pg_constraint parentConstr;
11425 : : HeapTuple partcontup;
11426 : : Form_pg_constraint partConstr;
11427 : : ScanKeyData key;
11428 : : SysScanDesc scan;
11429 : : HeapTuple trigtup;
11430 : : Oid insertTriggerOid,
11431 : : updateTriggerOid;
11432 : :
11433 : 90 : parentConstrTup = SearchSysCache1(CONSTROID,
11434 : : ObjectIdGetDatum(parentConstrOid));
1806 tgl@sss.pgh.pa.us 11435 [ - + ]: 90 : if (!HeapTupleIsValid(parentConstrTup))
1838 alvherre@alvh.no-ip. 11436 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for constraint %u", parentConstrOid);
1838 alvherre@alvh.no-ip. 11437 :CBC 90 : parentConstr = (Form_pg_constraint) GETSTRUCT(parentConstrTup);
11438 : :
11439 : : /*
11440 : : * Do some quick & easy initial checks. If any of these fail, we cannot
11441 : : * use this constraint.
11442 : : */
11443 [ + - - + ]: 90 : if (fk->confrelid != parentConstr->confrelid || fk->nkeys != numfks)
11444 : : {
1838 alvherre@alvh.no-ip. 11445 :UBC 0 : ReleaseSysCache(parentConstrTup);
11446 : 0 : return false;
11447 : : }
1838 alvherre@alvh.no-ip. 11448 [ + + ]:CBC 255 : for (int i = 0; i < numfks; i++)
11449 : : {
11450 [ + - ]: 165 : if (fk->conkey[i] != mapped_conkey[i] ||
11451 [ + - ]: 165 : fk->confkey[i] != confkey[i] ||
11452 [ - + ]: 165 : fk->conpfeqop[i] != conpfeqop[i])
11453 : : {
1838 alvherre@alvh.no-ip. 11454 :UBC 0 : ReleaseSysCache(parentConstrTup);
11455 : 0 : return false;
11456 : : }
11457 : : }
11458 : :
11459 : : /*
11460 : : * Looks good so far; do some more extensive checks. Presumably the check
11461 : : * for 'convalidated' could be dropped, since we don't really care about
11462 : : * that, but let's be careful for now.
11463 : : */
1838 alvherre@alvh.no-ip. 11464 :CBC 90 : partcontup = SearchSysCache1(CONSTROID,
11465 : : ObjectIdGetDatum(fk->conoid));
1806 tgl@sss.pgh.pa.us 11466 [ - + ]: 90 : if (!HeapTupleIsValid(partcontup))
1806 tgl@sss.pgh.pa.us 11467 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
1838 alvherre@alvh.no-ip. 11468 :CBC 90 : partConstr = (Form_pg_constraint) GETSTRUCT(partcontup);
11469 [ + + ]: 90 : if (OidIsValid(partConstr->conparentid) ||
11470 [ + - ]: 78 : !partConstr->convalidated ||
11471 [ + + ]: 78 : partConstr->condeferrable != parentConstr->condeferrable ||
11472 [ + - ]: 64 : partConstr->condeferred != parentConstr->condeferred ||
11473 [ + + ]: 64 : partConstr->confupdtype != parentConstr->confupdtype ||
11474 [ + - ]: 46 : partConstr->confdeltype != parentConstr->confdeltype ||
11475 [ + + ]: 46 : partConstr->confmatchtype != parentConstr->confmatchtype)
11476 : : {
11477 : 51 : ReleaseSysCache(parentConstrTup);
11478 : 51 : ReleaseSysCache(partcontup);
11479 : 51 : return false;
11480 : : }
11481 : :
11482 : 39 : ReleaseSysCache(partcontup);
11483 : 39 : ReleaseSysCache(parentConstrTup);
11484 : :
11485 : : /*
11486 : : * Looks good! Attach this constraint. The action triggers in the new
11487 : : * partition become redundant -- the parent table already has equivalent
11488 : : * ones, and those will be able to reach the partition. Remove the ones
11489 : : * in the partition. We identify them because they have our constraint
11490 : : * OID, as well as being on the referenced rel.
11491 : : */
11492 : 39 : ScanKeyInit(&key,
11493 : : Anum_pg_trigger_tgconstraint,
11494 : : BTEqualStrategyNumber, F_OIDEQ,
11495 : : ObjectIdGetDatum(fk->conoid));
11496 : 39 : scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
11497 : : NULL, 1, &key);
11498 [ + + ]: 195 : while ((trigtup = systable_getnext(scan)) != NULL)
11499 : : {
11500 : 156 : Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
11501 : : ObjectAddress trigger;
11502 : :
11503 [ + + ]: 156 : if (trgform->tgconstrrelid != fk->conrelid)
11504 : 78 : continue;
11505 [ - + ]: 78 : if (trgform->tgrelid != fk->confrelid)
1838 alvherre@alvh.no-ip. 11506 :UBC 0 : continue;
11507 : :
11508 : : /*
11509 : : * The constraint is originally set up to contain this trigger as an
11510 : : * implementation object, so there's a dependency record that links
11511 : : * the two; however, since the trigger is no longer needed, we remove
11512 : : * the dependency link in order to be able to drop the trigger while
11513 : : * keeping the constraint intact.
11514 : : */
1838 alvherre@alvh.no-ip. 11515 :CBC 78 : deleteDependencyRecordsFor(TriggerRelationId,
11516 : : trgform->oid,
11517 : : false);
11518 : : /* make dependency deletion visible to performDeletion */
11519 : 78 : CommandCounterIncrement();
11520 : 78 : ObjectAddressSet(trigger, TriggerRelationId,
11521 : : trgform->oid);
11522 : 78 : performDeletion(&trigger, DROP_RESTRICT, 0);
11523 : : /* make trigger drop visible, in case the loop iterates */
11524 : 78 : CommandCounterIncrement();
11525 : : }
11526 : :
11527 : 39 : systable_endscan(scan);
11528 : :
11529 : 39 : ConstraintSetParentConstraint(fk->conoid, parentConstrOid, partRelid);
11530 : :
11531 : : /*
11532 : : * Like the constraint, attach partition's "check" triggers to the
11533 : : * corresponding parent triggers.
11534 : : */
830 11535 : 39 : GetForeignKeyCheckTriggers(trigrel,
11536 : : fk->conoid, fk->confrelid, fk->conrelid,
11537 : : &insertTriggerOid, &updateTriggerOid);
11538 [ + - - + ]: 39 : Assert(OidIsValid(insertTriggerOid) && OidIsValid(parentInsTrigger));
11539 : 39 : TriggerSetParentTrigger(trigrel, insertTriggerOid, parentInsTrigger,
11540 : : partRelid);
11541 [ + - - + ]: 39 : Assert(OidIsValid(updateTriggerOid) && OidIsValid(parentUpdTrigger));
11542 : 39 : TriggerSetParentTrigger(trigrel, updateTriggerOid, parentUpdTrigger,
11543 : : partRelid);
11544 : :
1838 11545 : 39 : CommandCounterIncrement();
11546 : 39 : return true;
11547 : : }
11548 : :
11549 : : /*
11550 : : * GetForeignKeyActionTriggers
11551 : : * Returns delete and update "action" triggers of the given relation
11552 : : * belonging to the given constraint
11553 : : */
11554 : : static void
830 11555 : 75 : GetForeignKeyActionTriggers(Relation trigrel,
11556 : : Oid conoid, Oid confrelid, Oid conrelid,
11557 : : Oid *deleteTriggerOid,
11558 : : Oid *updateTriggerOid)
11559 : : {
11560 : : ScanKeyData key;
11561 : : SysScanDesc scan;
11562 : : HeapTuple trigtup;
11563 : :
11564 : 75 : *deleteTriggerOid = *updateTriggerOid = InvalidOid;
11565 : 75 : ScanKeyInit(&key,
11566 : : Anum_pg_trigger_tgconstraint,
11567 : : BTEqualStrategyNumber, F_OIDEQ,
11568 : : ObjectIdGetDatum(conoid));
11569 : :
11570 : 75 : scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
11571 : : NULL, 1, &key);
11572 [ + + ]: 345 : while ((trigtup = systable_getnext(scan)) != NULL)
11573 : : {
11574 : 270 : Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
11575 : :
11576 [ + + ]: 270 : if (trgform->tgconstrrelid != conrelid)
11577 : 120 : continue;
11578 [ - + ]: 150 : if (trgform->tgrelid != confrelid)
830 alvherre@alvh.no-ip. 11579 :UBC 0 : continue;
11580 : : /* Only ever look at "action" triggers on the PK side. */
583 alvherre@alvh.no-ip. 11581 [ - + ]:CBC 150 : if (RI_FKey_trigger_type(trgform->tgfoid) != RI_TRIGGER_PK)
583 alvherre@alvh.no-ip. 11582 :UBC 0 : continue;
830 alvherre@alvh.no-ip. 11583 [ + + ]:CBC 150 : if (TRIGGER_FOR_DELETE(trgform->tgtype))
11584 : : {
11585 [ - + ]: 75 : Assert(*deleteTriggerOid == InvalidOid);
11586 : 75 : *deleteTriggerOid = trgform->oid;
11587 : : }
11588 [ + - ]: 75 : else if (TRIGGER_FOR_UPDATE(trgform->tgtype))
11589 : : {
11590 [ - + ]: 75 : Assert(*updateTriggerOid == InvalidOid);
11591 : 75 : *updateTriggerOid = trgform->oid;
11592 : : }
11593 : : #ifndef USE_ASSERT_CHECKING
11594 : : /* In an assert-enabled build, continue looking to find duplicates */
11595 : : if (OidIsValid(*deleteTriggerOid) && OidIsValid(*updateTriggerOid))
11596 : : break;
11597 : : #endif
11598 : : }
11599 : :
11600 [ - + ]: 75 : if (!OidIsValid(*deleteTriggerOid))
830 alvherre@alvh.no-ip. 11601 [ # # ]:UBC 0 : elog(ERROR, "could not find ON DELETE action trigger of foreign key constraint %u",
11602 : : conoid);
830 alvherre@alvh.no-ip. 11603 [ - + ]:CBC 75 : if (!OidIsValid(*updateTriggerOid))
830 alvherre@alvh.no-ip. 11604 [ # # ]:UBC 0 : elog(ERROR, "could not find ON UPDATE action trigger of foreign key constraint %u",
11605 : : conoid);
11606 : :
830 alvherre@alvh.no-ip. 11607 :CBC 75 : systable_endscan(scan);
11608 : 75 : }
11609 : :
11610 : : /*
11611 : : * GetForeignKeyCheckTriggers
11612 : : * Returns insert and update "check" triggers of the given relation
11613 : : * belonging to the given constraint
11614 : : */
11615 : : static void
11616 : 308 : GetForeignKeyCheckTriggers(Relation trigrel,
11617 : : Oid conoid, Oid confrelid, Oid conrelid,
11618 : : Oid *insertTriggerOid,
11619 : : Oid *updateTriggerOid)
11620 : : {
11621 : : ScanKeyData key;
11622 : : SysScanDesc scan;
11623 : : HeapTuple trigtup;
11624 : :
11625 : 308 : *insertTriggerOid = *updateTriggerOid = InvalidOid;
11626 : 308 : ScanKeyInit(&key,
11627 : : Anum_pg_trigger_tgconstraint,
11628 : : BTEqualStrategyNumber, F_OIDEQ,
11629 : : ObjectIdGetDatum(conoid));
11630 : :
11631 : 308 : scan = systable_beginscan(trigrel, TriggerConstraintIndexId, true,
11632 : : NULL, 1, &key);
11633 [ + + ]: 1348 : while ((trigtup = systable_getnext(scan)) != NULL)
11634 : : {
11635 : 1040 : Form_pg_trigger trgform = (Form_pg_trigger) GETSTRUCT(trigtup);
11636 : :
11637 [ + + ]: 1040 : if (trgform->tgconstrrelid != confrelid)
11638 : 382 : continue;
11639 [ - + ]: 658 : if (trgform->tgrelid != conrelid)
830 alvherre@alvh.no-ip. 11640 :UBC 0 : continue;
11641 : : /* Only ever look at "check" triggers on the FK side. */
583 alvherre@alvh.no-ip. 11642 [ + + ]:CBC 658 : if (RI_FKey_trigger_type(trgform->tgfoid) != RI_TRIGGER_FK)
11643 : 42 : continue;
830 11644 [ + + ]: 616 : if (TRIGGER_FOR_INSERT(trgform->tgtype))
11645 : : {
11646 [ - + ]: 308 : Assert(*insertTriggerOid == InvalidOid);
11647 : 308 : *insertTriggerOid = trgform->oid;
11648 : : }
11649 [ + - ]: 308 : else if (TRIGGER_FOR_UPDATE(trgform->tgtype))
11650 : : {
11651 [ - + ]: 308 : Assert(*updateTriggerOid == InvalidOid);
11652 : 308 : *updateTriggerOid = trgform->oid;
11653 : : }
11654 : : #ifndef USE_ASSERT_CHECKING
11655 : : /* In an assert-enabled build, continue looking to find duplicates. */
11656 : : if (OidIsValid(*insertTriggerOid) && OidIsValid(*updateTriggerOid))
11657 : : break;
11658 : : #endif
11659 : : }
11660 : :
11661 [ - + ]: 308 : if (!OidIsValid(*insertTriggerOid))
830 alvherre@alvh.no-ip. 11662 [ # # ]:UBC 0 : elog(ERROR, "could not find ON INSERT check triggers of foreign key constraint %u",
11663 : : conoid);
830 alvherre@alvh.no-ip. 11664 [ - + ]:CBC 308 : if (!OidIsValid(*updateTriggerOid))
830 alvherre@alvh.no-ip. 11665 [ # # ]:UBC 0 : elog(ERROR, "could not find ON UPDATE check triggers of foreign key constraint %u",
11666 : : conoid);
11667 : :
830 alvherre@alvh.no-ip. 11668 :CBC 308 : systable_endscan(scan);
11669 : 308 : }
11670 : :
11671 : : /*
11672 : : * ALTER TABLE ALTER CONSTRAINT
11673 : : *
11674 : : * Update the attributes of a constraint.
11675 : : *
11676 : : * Currently only works for Foreign Key constraints.
11677 : : *
11678 : : * If the constraint is modified, returns its address; otherwise, return
11679 : : * InvalidObjectAddress.
11680 : : */
11681 : : static ObjectAddress
1075 11682 : 63 : ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd, bool recurse,
11683 : : bool recursing, LOCKMODE lockmode)
11684 : : {
11685 : : Constraint *cmdcon;
11686 : : Relation conrel;
11687 : : Relation tgrel;
11688 : : SysScanDesc scan;
11689 : : ScanKeyData skey[3];
11690 : : HeapTuple contuple;
11691 : : Form_pg_constraint currcon;
11692 : : ObjectAddress address;
11693 : 63 : List *otherrelids = NIL;
11694 : : ListCell *lc;
11695 : :
2635 andres@anarazel.de 11696 : 63 : cmdcon = castNode(Constraint, cmd->def);
11697 : :
1910 11698 : 63 : conrel = table_open(ConstraintRelationId, RowExclusiveLock);
1075 alvherre@alvh.no-ip. 11699 : 63 : tgrel = table_open(TriggerRelationId, RowExclusiveLock);
11700 : :
11701 : : /*
11702 : : * Find and check the target constraint
11703 : : */
2049 tgl@sss.pgh.pa.us 11704 : 63 : ScanKeyInit(&skey[0],
11705 : : Anum_pg_constraint_conrelid,
11706 : : BTEqualStrategyNumber, F_OIDEQ,
11707 : : ObjectIdGetDatum(RelationGetRelid(rel)));
11708 : 63 : ScanKeyInit(&skey[1],
11709 : : Anum_pg_constraint_contypid,
11710 : : BTEqualStrategyNumber, F_OIDEQ,
11711 : : ObjectIdGetDatum(InvalidOid));
11712 : 63 : ScanKeyInit(&skey[2],
11713 : : Anum_pg_constraint_conname,
11714 : : BTEqualStrategyNumber, F_NAMEEQ,
11715 : 63 : CStringGetDatum(cmdcon->conname));
11716 : 63 : scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId,
11717 : : true, NULL, 3, skey);
11718 : :
11719 : : /* There can be at most one matching row */
11720 [ - + ]: 63 : if (!HeapTupleIsValid(contuple = systable_getnext(scan)))
3942 simon@2ndQuadrant.co 11721 [ # # ]:UBC 0 : ereport(ERROR,
11722 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
11723 : : errmsg("constraint \"%s\" of relation \"%s\" does not exist",
11724 : : cmdcon->conname, RelationGetRelationName(rel))));
11725 : :
2049 tgl@sss.pgh.pa.us 11726 :CBC 63 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
3942 simon@2ndQuadrant.co 11727 [ - + ]: 63 : if (currcon->contype != CONSTRAINT_FOREIGN)
3942 simon@2ndQuadrant.co 11728 [ # # ]:UBC 0 : ereport(ERROR,
11729 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
11730 : : errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint",
11731 : : cmdcon->conname, RelationGetRelationName(rel))));
11732 : :
11733 : : /*
11734 : : * If it's not the topmost constraint, raise an error.
11735 : : *
11736 : : * Altering a non-topmost constraint leaves some triggers untouched, since
11737 : : * they are not directly connected to this constraint; also, pg_dump would
11738 : : * ignore the deferrability status of the individual constraint, since it
11739 : : * only dumps topmost constraints. Avoid these problems by refusing this
11740 : : * operation and telling the user to alter the parent constraint instead.
11741 : : */
1075 alvherre@alvh.no-ip. 11742 [ + + ]:CBC 63 : if (OidIsValid(currcon->conparentid))
11743 : : {
11744 : : HeapTuple tp;
11745 : 6 : Oid parent = currcon->conparentid;
11746 : 6 : char *ancestorname = NULL;
11747 : 6 : char *ancestortable = NULL;
11748 : :
11749 : : /* Loop to find the topmost constraint */
11750 [ + - ]: 12 : while (HeapTupleIsValid(tp = SearchSysCache1(CONSTROID, ObjectIdGetDatum(parent))))
11751 : : {
11752 : 12 : Form_pg_constraint contup = (Form_pg_constraint) GETSTRUCT(tp);
11753 : :
11754 : : /* If no parent, this is the constraint we want */
11755 [ + + ]: 12 : if (!OidIsValid(contup->conparentid))
11756 : : {
11757 : 6 : ancestorname = pstrdup(NameStr(contup->conname));
11758 : 6 : ancestortable = get_rel_name(contup->conrelid);
11759 : 6 : ReleaseSysCache(tp);
11760 : 6 : break;
11761 : : }
11762 : :
11763 : 6 : parent = contup->conparentid;
11764 : 6 : ReleaseSysCache(tp);
11765 : : }
11766 : :
11767 [ + - + - : 6 : ereport(ERROR,
+ - ]
11768 : : (errmsg("cannot alter constraint \"%s\" on relation \"%s\"",
11769 : : cmdcon->conname, RelationGetRelationName(rel)),
11770 : : ancestorname && ancestortable ?
11771 : : errdetail("Constraint \"%s\" is derived from constraint \"%s\" of relation \"%s\".",
11772 : : cmdcon->conname, ancestorname, ancestortable) : 0,
11773 : : errhint("You may alter the constraint it derives from instead.")));
11774 : : }
11775 : :
11776 : : /*
11777 : : * Do the actual catalog work. We can skip changing if already in the
11778 : : * desired state, but not if a partitioned table: partitions need to be
11779 : : * processed regardless, in case they had the constraint locally changed.
11780 : : */
11781 : 57 : address = InvalidObjectAddress;
11782 [ + + ]: 57 : if (currcon->condeferrable != cmdcon->deferrable ||
11783 [ - + ]: 3 : currcon->condeferred != cmdcon->initdeferred ||
1075 alvherre@alvh.no-ip. 11784 [ # # ]:UBC 0 : rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
11785 : : {
1075 alvherre@alvh.no-ip. 11786 [ + - ]:CBC 57 : if (ATExecAlterConstrRecurse(cmdcon, conrel, tgrel, rel, contuple,
11787 : : &otherrelids, lockmode))
11788 : 57 : ObjectAddressSet(address, ConstraintRelationId, currcon->oid);
11789 : : }
11790 : :
11791 : : /*
11792 : : * ATExecAlterConstrRecurse already invalidated relcache for the relations
11793 : : * having the constraint itself; here we also invalidate for relations
11794 : : * that have any triggers that are part of the constraint.
11795 : : */
11796 [ + - + + : 129 : foreach(lc, otherrelids)
+ + ]
11797 : 72 : CacheInvalidateRelcacheByRelid(lfirst_oid(lc));
11798 : :
11799 : 57 : systable_endscan(scan);
11800 : :
11801 : 57 : table_close(tgrel, RowExclusiveLock);
11802 : 57 : table_close(conrel, RowExclusiveLock);
11803 : :
11804 : 57 : return address;
11805 : : }
11806 : :
11807 : : /*
11808 : : * Recursive subroutine of ATExecAlterConstraint. Returns true if the
11809 : : * constraint is altered.
11810 : : *
11811 : : * *otherrelids is appended OIDs of relations containing affected triggers.
11812 : : *
11813 : : * Note that we must recurse even when the values are correct, in case
11814 : : * indirect descendants have had their constraints altered locally.
11815 : : * (This could be avoided if we forbade altering constraints in partitions
11816 : : * but existing releases don't do that.)
11817 : : */
11818 : : static bool
11819 : 90 : ATExecAlterConstrRecurse(Constraint *cmdcon, Relation conrel, Relation tgrel,
11820 : : Relation rel, HeapTuple contuple, List **otherrelids,
11821 : : LOCKMODE lockmode)
11822 : : {
11823 : : Form_pg_constraint currcon;
11824 : : Oid conoid;
11825 : : Oid refrelid;
11826 : 90 : bool changed = false;
11827 : :
11828 : : /* since this function recurses, it could be driven to stack overflow */
58 akorotkov@postgresql 11829 : 90 : check_stack_depth();
11830 : :
1075 alvherre@alvh.no-ip. 11831 : 90 : currcon = (Form_pg_constraint) GETSTRUCT(contuple);
11832 : 90 : conoid = currcon->oid;
11833 : 90 : refrelid = currcon->confrelid;
11834 : :
11835 : : /*
11836 : : * Update pg_constraint with the flags from cmdcon.
11837 : : *
11838 : : * If called to modify a constraint that's already in the desired state,
11839 : : * silently do nothing.
11840 : : */
3942 simon@2ndQuadrant.co 11841 [ + + ]: 90 : if (currcon->condeferrable != cmdcon->deferrable ||
11842 [ + - ]: 3 : currcon->condeferred != cmdcon->initdeferred)
11843 : : {
11844 : : HeapTuple copyTuple;
11845 : : Form_pg_constraint copy_con;
11846 : : HeapTuple tgtuple;
11847 : : ScanKeyData tgkey;
11848 : : SysScanDesc tgscan;
11849 : :
11850 : 90 : copyTuple = heap_copytuple(contuple);
11851 : 90 : copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
11852 : 90 : copy_con->condeferrable = cmdcon->deferrable;
11853 : 90 : copy_con->condeferred = cmdcon->initdeferred;
2630 alvherre@alvh.no-ip. 11854 : 90 : CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);
11855 : :
3942 simon@2ndQuadrant.co 11856 [ - + ]: 90 : InvokeObjectPostAlterHook(ConstraintRelationId,
11857 : : conoid, 0);
11858 : :
11859 : 90 : heap_freetuple(copyTuple);
1075 alvherre@alvh.no-ip. 11860 : 90 : changed = true;
11861 : :
11862 : : /* Make new constraint flags visible to others */
11863 : 90 : CacheInvalidateRelcache(rel);
11864 : :
11865 : : /*
11866 : : * Now we need to update the multiple entries in pg_trigger that
11867 : : * implement the constraint.
11868 : : */
3942 simon@2ndQuadrant.co 11869 : 90 : ScanKeyInit(&tgkey,
11870 : : Anum_pg_trigger_tgconstraint,
11871 : : BTEqualStrategyNumber, F_OIDEQ,
11872 : : ObjectIdGetDatum(conoid));
11873 : 90 : tgscan = systable_beginscan(tgrel, TriggerConstraintIndexId, true,
11874 : : NULL, 1, &tgkey);
11875 [ + + ]: 384 : while (HeapTupleIsValid(tgtuple = systable_getnext(tgscan)))
11876 : : {
2727 tgl@sss.pgh.pa.us 11877 : 294 : Form_pg_trigger tgform = (Form_pg_trigger) GETSTRUCT(tgtuple);
11878 : : Form_pg_trigger copy_tg;
11879 : : HeapTuple tgCopyTuple;
11880 : :
11881 : : /*
11882 : : * Remember OIDs of other relation(s) involved in FK constraint.
11883 : : * (Note: it's likely that we could skip forcing a relcache inval
11884 : : * for other rels that don't have a trigger whose properties
11885 : : * change, but let's be conservative.)
11886 : : */
11887 [ + + ]: 294 : if (tgform->tgrelid != RelationGetRelid(rel))
1075 alvherre@alvh.no-ip. 11888 : 144 : *otherrelids = list_append_unique_oid(*otherrelids,
11889 : : tgform->tgrelid);
11890 : :
11891 : : /*
11892 : : * Update deferrability of RI_FKey_noaction_del,
11893 : : * RI_FKey_noaction_upd, RI_FKey_check_ins and RI_FKey_check_upd
11894 : : * triggers, but not others; see createForeignKeyActionTriggers
11895 : : * and CreateFKCheckTrigger.
11896 : : */
2727 tgl@sss.pgh.pa.us 11897 [ + + ]: 294 : if (tgform->tgfoid != F_RI_FKEY_NOACTION_DEL &&
11898 [ + + ]: 237 : tgform->tgfoid != F_RI_FKEY_NOACTION_UPD &&
11899 [ + + ]: 171 : tgform->tgfoid != F_RI_FKEY_CHECK_INS &&
11900 [ + + ]: 96 : tgform->tgfoid != F_RI_FKEY_CHECK_UPD)
11901 : 21 : continue;
11902 : :
557 drowley@postgresql.o 11903 : 273 : tgCopyTuple = heap_copytuple(tgtuple);
11904 : 273 : copy_tg = (Form_pg_trigger) GETSTRUCT(tgCopyTuple);
11905 : :
3942 simon@2ndQuadrant.co 11906 : 273 : copy_tg->tgdeferrable = cmdcon->deferrable;
11907 : 273 : copy_tg->tginitdeferred = cmdcon->initdeferred;
557 drowley@postgresql.o 11908 : 273 : CatalogTupleUpdate(tgrel, &tgCopyTuple->t_self, tgCopyTuple);
11909 : :
1076 alvherre@alvh.no-ip. 11910 [ - + ]: 273 : InvokeObjectPostAlterHook(TriggerRelationId, tgform->oid, 0);
11911 : :
557 drowley@postgresql.o 11912 : 273 : heap_freetuple(tgCopyTuple);
11913 : : }
11914 : :
3942 simon@2ndQuadrant.co 11915 : 90 : systable_endscan(tgscan);
11916 : : }
11917 : :
11918 : : /*
11919 : : * If the table at either end of the constraint is partitioned, we need to
11920 : : * recurse and handle every constraint that is a child of this one.
11921 : : *
11922 : : * (This assumes that the recurse flag is forcibly set for partitioned
11923 : : * tables, and not set for legacy inheritance, though we don't check for
11924 : : * that here.)
11925 : : */
1075 alvherre@alvh.no-ip. 11926 [ + + + + ]: 168 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
11927 : 78 : get_rel_relkind(refrelid) == RELKIND_PARTITIONED_TABLE)
11928 : : {
11929 : : ScanKeyData pkey;
11930 : : SysScanDesc pscan;
11931 : : HeapTuple childtup;
11932 : :
11933 : 21 : ScanKeyInit(&pkey,
11934 : : Anum_pg_constraint_conparentid,
11935 : : BTEqualStrategyNumber, F_OIDEQ,
11936 : : ObjectIdGetDatum(conoid));
11937 : :
11938 : 21 : pscan = systable_beginscan(conrel, ConstraintParentIndexId,
11939 : : true, NULL, 1, &pkey);
11940 : :
11941 [ + + ]: 54 : while (HeapTupleIsValid(childtup = systable_getnext(pscan)))
11942 : : {
11943 : 33 : Form_pg_constraint childcon = (Form_pg_constraint) GETSTRUCT(childtup);
11944 : : Relation childrel;
11945 : :
11946 : 33 : childrel = table_open(childcon->conrelid, lockmode);
11947 : 33 : ATExecAlterConstrRecurse(cmdcon, conrel, tgrel, childrel, childtup,
11948 : : otherrelids, lockmode);
11949 : 33 : table_close(childrel, NoLock);
11950 : : }
11951 : :
11952 : 21 : systable_endscan(pscan);
11953 : : }
11954 : :
11955 : 90 : return changed;
11956 : : }
11957 : :
11958 : : /*
11959 : : * ALTER TABLE VALIDATE CONSTRAINT
11960 : : *
11961 : : * XXX The reason we handle recursion here rather than at Phase 1 is because
11962 : : * there's no good way to skip recursing when handling foreign keys: there is
11963 : : * no need to lock children in that case, yet we wouldn't be able to avoid
11964 : : * doing so at that level.
11965 : : *
11966 : : * Return value is the address of the validated constraint. If the constraint
11967 : : * was already validated, InvalidObjectAddress is returned.
11968 : : */
11969 : : static ObjectAddress
1370 drowley@postgresql.o 11970 : 218 : ATExecValidateConstraint(List **wqueue, Relation rel, char *constrName,
11971 : : bool recurse, bool recursing, LOCKMODE lockmode)
11972 : : {
11973 : : Relation conrel;
11974 : : SysScanDesc scan;
11975 : : ScanKeyData skey[3];
11976 : : HeapTuple tuple;
11977 : : Form_pg_constraint con;
11978 : : ObjectAddress address;
11979 : :
1910 andres@anarazel.de 11980 : 218 : conrel = table_open(ConstraintRelationId, RowExclusiveLock);
11981 : :
11982 : : /*
11983 : : * Find and check the target constraint
11984 : : */
2049 tgl@sss.pgh.pa.us 11985 : 218 : ScanKeyInit(&skey[0],
11986 : : Anum_pg_constraint_conrelid,
11987 : : BTEqualStrategyNumber, F_OIDEQ,
11988 : : ObjectIdGetDatum(RelationGetRelid(rel)));
11989 : 218 : ScanKeyInit(&skey[1],
11990 : : Anum_pg_constraint_contypid,
11991 : : BTEqualStrategyNumber, F_OIDEQ,
11992 : : ObjectIdGetDatum(InvalidOid));
11993 : 218 : ScanKeyInit(&skey[2],
11994 : : Anum_pg_constraint_conname,
11995 : : BTEqualStrategyNumber, F_NAMEEQ,
11996 : : CStringGetDatum(constrName));
11997 : 218 : scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId,
11998 : : true, NULL, 3, skey);
11999 : :
12000 : : /* There can be at most one matching row */
12001 [ - + ]: 218 : if (!HeapTupleIsValid(tuple = systable_getnext(scan)))
4814 tgl@sss.pgh.pa.us 12002 [ # # ]:UBC 0 : ereport(ERROR,
12003 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
12004 : : errmsg("constraint \"%s\" of relation \"%s\" does not exist",
12005 : : constrName, RelationGetRelationName(rel))));
12006 : :
2049 tgl@sss.pgh.pa.us 12007 :CBC 218 : con = (Form_pg_constraint) GETSTRUCT(tuple);
4701 alvherre@alvh.no-ip. 12008 [ + + ]: 218 : if (con->contype != CONSTRAINT_FOREIGN &&
12009 [ - + ]: 66 : con->contype != CONSTRAINT_CHECK)
4701 alvherre@alvh.no-ip. 12010 [ # # ]:UBC 0 : ereport(ERROR,
12011 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
12012 : : errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key or check constraint",
12013 : : constrName, RelationGetRelationName(rel))));
12014 : :
4814 tgl@sss.pgh.pa.us 12015 [ + + ]:CBC 218 : if (!con->convalidated)
12016 : : {
12017 : : AlteredTableInfo *tab;
12018 : : HeapTuple copyTuple;
12019 : : Form_pg_constraint copy_con;
12020 : :
4701 alvherre@alvh.no-ip. 12021 [ + + ]: 209 : if (con->contype == CONSTRAINT_FOREIGN)
12022 : : {
12023 : : NewConstraint *newcon;
12024 : : Constraint *fkconstraint;
12025 : :
12026 : : /* Queue validation for phase 3 */
1370 drowley@postgresql.o 12027 : 149 : fkconstraint = makeNode(Constraint);
12028 : : /* for now this is all we need */
12029 : 149 : fkconstraint->conname = constrName;
12030 : :
12031 : 149 : newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
12032 : 149 : newcon->name = constrName;
12033 : 149 : newcon->contype = CONSTR_FOREIGN;
12034 : 149 : newcon->refrelid = con->confrelid;
12035 : 149 : newcon->refindid = con->conindid;
12036 : 149 : newcon->conid = con->oid;
12037 : 149 : newcon->qual = (Node *) fkconstraint;
12038 : :
12039 : : /* Find or create work queue entry for this table */
12040 : 149 : tab = ATGetQueueEntry(wqueue, rel);
12041 : 149 : tab->constraints = lappend(tab->constraints, newcon);
12042 : :
12043 : : /*
12044 : : * We disallow creating invalid foreign keys to or from
12045 : : * partitioned tables, so ignoring the recursion bit is okay.
12046 : : */
12047 : : }
4701 alvherre@alvh.no-ip. 12048 [ + - ]: 60 : else if (con->contype == CONSTRAINT_CHECK)
12049 : : {
12050 : 60 : List *children = NIL;
12051 : : ListCell *child;
12052 : : NewConstraint *newcon;
12053 : : Datum val;
12054 : : char *conbin;
12055 : :
12056 : : /*
12057 : : * If we're recursing, the parent has already done this, so skip
12058 : : * it. Also, if the constraint is a NO INHERIT constraint, we
12059 : : * shouldn't try to look for it in the children.
12060 : : */
2543 rhaas@postgresql.org 12061 [ + + + + ]: 60 : if (!recursing && !con->connoinherit)
4701 alvherre@alvh.no-ip. 12062 : 33 : children = find_all_inheritors(RelationGetRelid(rel),
12063 : : lockmode, NULL);
12064 : :
12065 : : /*
12066 : : * For CHECK constraints, we must ensure that we only mark the
12067 : : * constraint as validated on the parent if it's already validated
12068 : : * on the children.
12069 : : *
12070 : : * We recurse before validating on the parent, to reduce risk of
12071 : : * deadlocks.
12072 : : */
12073 [ + + + + : 117 : foreach(child, children)
+ + ]
12074 : : {
4326 bruce@momjian.us 12075 : 57 : Oid childoid = lfirst_oid(child);
12076 : : Relation childrel;
12077 : :
4701 alvherre@alvh.no-ip. 12078 [ + + ]: 57 : if (childoid == RelationGetRelid(rel))
12079 : 33 : continue;
12080 : :
12081 : : /*
12082 : : * If we are told not to recurse, there had better not be any
12083 : : * child tables, because we can't mark the constraint on the
12084 : : * parent valid unless it is valid for all child tables.
12085 : : */
12086 [ - + ]: 24 : if (!recurse)
4701 alvherre@alvh.no-ip. 12087 [ # # ]:UBC 0 : ereport(ERROR,
12088 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
12089 : : errmsg("constraint must be validated on child tables too")));
12090 : :
12091 : : /* find_all_inheritors already got lock */
1910 andres@anarazel.de 12092 :CBC 24 : childrel = table_open(childoid, NoLock);
12093 : :
1370 drowley@postgresql.o 12094 : 24 : ATExecValidateConstraint(wqueue, childrel, constrName, false,
12095 : : true, lockmode);
1910 andres@anarazel.de 12096 : 24 : table_close(childrel, NoLock);
12097 : : }
12098 : :
12099 : : /* Queue validation for phase 3 */
1370 drowley@postgresql.o 12100 : 60 : newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
12101 : 60 : newcon->name = constrName;
12102 : 60 : newcon->contype = CONSTR_CHECK;
12103 : 60 : newcon->refrelid = InvalidOid;
12104 : 60 : newcon->refindid = InvalidOid;
12105 : 60 : newcon->conid = con->oid;
12106 : :
386 dgustafsson@postgres 12107 : 60 : val = SysCacheGetAttrNotNull(CONSTROID, tuple,
12108 : : Anum_pg_constraint_conbin);
1370 drowley@postgresql.o 12109 : 60 : conbin = TextDatumGetCString(val);
12110 : 60 : newcon->qual = (Node *) stringToNode(conbin);
12111 : :
12112 : : /* Find or create work queue entry for this table */
12113 : 60 : tab = ATGetQueueEntry(wqueue, rel);
12114 : 60 : tab->constraints = lappend(tab->constraints, newcon);
12115 : :
12116 : : /*
12117 : : * Invalidate relcache so that others see the new validated
12118 : : * constraint.
12119 : : */
4701 alvherre@alvh.no-ip. 12120 : 60 : CacheInvalidateRelcache(rel);
12121 : : }
12122 : :
12123 : : /*
12124 : : * Now update the catalog, while we have the door open.
12125 : : */
12126 : 209 : copyTuple = heap_copytuple(tuple);
12127 : 209 : copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
4814 simon@2ndQuadrant.co 12128 : 209 : copy_con->convalidated = true;
2630 alvherre@alvh.no-ip. 12129 : 209 : CatalogTupleUpdate(conrel, ©Tuple->t_self, copyTuple);
12130 : :
1972 andres@anarazel.de 12131 [ - + ]: 209 : InvokeObjectPostAlterHook(ConstraintRelationId, con->oid, 0);
12132 : :
4814 simon@2ndQuadrant.co 12133 : 209 : heap_freetuple(copyTuple);
12134 : :
1972 andres@anarazel.de 12135 : 209 : ObjectAddressSet(address, ConstraintRelationId, con->oid);
12136 : : }
12137 : : else
3249 bruce@momjian.us 12138 : 9 : address = InvalidObjectAddress; /* already validated */
12139 : :
4814 simon@2ndQuadrant.co 12140 : 218 : systable_endscan(scan);
12141 : :
1910 andres@anarazel.de 12142 : 218 : table_close(conrel, RowExclusiveLock);
12143 : :
3308 alvherre@alvh.no-ip. 12144 : 218 : return address;
12145 : : }
12146 : :
12147 : :
12148 : : /*
12149 : : * transformColumnNameList - transform list of column names
12150 : : *
12151 : : * Lookup each name and return its attnum and, optionally, type OID
12152 : : *
12153 : : * Note: the name of this function suggests that it's general-purpose,
12154 : : * but actually it's only used to look up names appearing in foreign-key
12155 : : * clauses. The error messages would need work to use it in other cases,
12156 : : * and perhaps the validity checks as well.
12157 : : */
12158 : : static int
7875 tgl@sss.pgh.pa.us 12159 : 3080 : transformColumnNameList(Oid relId, List *colList,
12160 : : int16 *attnums, Oid *atttypids)
12161 : : {
12162 : : ListCell *l;
12163 : : int attnum;
12164 : :
12165 : 3080 : attnum = 0;
12166 [ + + + + : 5631 : foreach(l, colList)
+ + ]
12167 : : {
12168 : 2584 : char *attname = strVal(lfirst(l));
12169 : : HeapTuple atttuple;
12170 : : Form_pg_attribute attform;
12171 : :
12172 : 2584 : atttuple = SearchSysCacheAttName(relId, attname);
12173 [ + + ]: 2584 : if (!HeapTupleIsValid(atttuple))
7574 12174 [ + - ]: 27 : ereport(ERROR,
12175 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
12176 : : errmsg("column \"%s\" referenced in foreign key constraint does not exist",
12177 : : attname)));
380 12178 : 2557 : attform = (Form_pg_attribute) GETSTRUCT(atttuple);
12179 [ + + ]: 2557 : if (attform->attnum < 0)
12180 [ + - ]: 6 : ereport(ERROR,
12181 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
12182 : : errmsg("system columns cannot be used in foreign keys")));
7875 12183 [ - + ]: 2551 : if (attnum >= INDEX_MAX_KEYS)
7574 tgl@sss.pgh.pa.us 12184 [ # # ]:UBC 0 : ereport(ERROR,
12185 : : (errcode(ERRCODE_TOO_MANY_COLUMNS),
12186 : : errmsg("cannot have more than %d keys in a foreign key",
12187 : : INDEX_MAX_KEYS)));
380 tgl@sss.pgh.pa.us 12188 :CBC 2551 : attnums[attnum] = attform->attnum;
858 peter@eisentraut.org 12189 [ + + ]: 2551 : if (atttypids != NULL)
380 tgl@sss.pgh.pa.us 12190 : 2536 : atttypids[attnum] = attform->atttypid;
7875 12191 : 2551 : ReleaseSysCache(atttuple);
12192 : 2551 : attnum++;
12193 : : }
12194 : :
12195 : 3047 : return attnum;
12196 : : }
12197 : :
12198 : : /*
12199 : : * transformFkeyGetPrimaryKey -
12200 : : *
12201 : : * Look up the names, attnums, and types of the primary key attributes
12202 : : * for the pkrel. Also return the index OID and index opclasses of the
12203 : : * index supporting the primary key. Also return whether the index has
12204 : : * WITHOUT OVERLAPS.
12205 : : *
12206 : : * All parameters except pkrel are output parameters. Also, the function
12207 : : * return value is the number of attributes in the primary key.
12208 : : *
12209 : : * Used when the column list in the REFERENCES specification is omitted.
12210 : : */
12211 : : static int
12212 : 535 : transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
12213 : : List **attnamelist,
12214 : : int16 *attnums, Oid *atttypids,
12215 : : Oid *opclasses, bool *pk_has_without_overlaps)
12216 : : {
12217 : : List *indexoidlist;
12218 : : ListCell *indexoidscan;
12219 : 535 : HeapTuple indexTuple = NULL;
12220 : 535 : Form_pg_index indexStruct = NULL;
12221 : : Datum indclassDatum;
12222 : : oidvector *indclass;
12223 : : int i;
12224 : :
12225 : : /*
12226 : : * Get the list of index OIDs for the table from the relcache, and look up
12227 : : * each one in the pg_index syscache until we find one marked primary key
12228 : : * (hopefully there isn't more than one such). Insist it's valid, too.
12229 : : */
6777 12230 : 535 : *indexOid = InvalidOid;
12231 : :
7875 12232 : 535 : indexoidlist = RelationGetIndexList(pkrel);
12233 : :
12234 [ + - + - : 538 : foreach(indexoidscan, indexoidlist)
+ - ]
12235 : : {
7263 neilc@samurai.com 12236 : 538 : Oid indexoid = lfirst_oid(indexoidscan);
12237 : :
5173 rhaas@postgresql.org 12238 : 538 : indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
7875 tgl@sss.pgh.pa.us 12239 [ - + ]: 538 : if (!HeapTupleIsValid(indexTuple))
7574 tgl@sss.pgh.pa.us 12240 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for index %u", indexoid);
7875 tgl@sss.pgh.pa.us 12241 :CBC 538 : indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
1935 peter_e@gmx.net 12242 [ + + + - ]: 538 : if (indexStruct->indisprimary && indexStruct->indisvalid)
12243 : : {
12244 : : /*
12245 : : * Refuse to use a deferrable primary key. This is per SQL spec,
12246 : : * and there would be a lot of interesting semantic problems if we
12247 : : * tried to allow it.
12248 : : */
5373 tgl@sss.pgh.pa.us 12249 [ - + ]: 535 : if (!indexStruct->indimmediate)
5373 tgl@sss.pgh.pa.us 12250 [ # # ]:UBC 0 : ereport(ERROR,
12251 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
12252 : : errmsg("cannot use a deferrable primary key for referenced table \"%s\"",
12253 : : RelationGetRelationName(pkrel))));
12254 : :
7875 tgl@sss.pgh.pa.us 12255 :CBC 535 : *indexOid = indexoid;
12256 : 535 : break;
12257 : : }
12258 : 3 : ReleaseSysCache(indexTuple);
12259 : : }
12260 : :
7263 neilc@samurai.com 12261 : 535 : list_free(indexoidlist);
12262 : :
12263 : : /*
12264 : : * Check that we found it
12265 : : */
6777 tgl@sss.pgh.pa.us 12266 [ - + ]: 535 : if (!OidIsValid(*indexOid))
7574 tgl@sss.pgh.pa.us 12267 [ # # ]:UBC 0 : ereport(ERROR,
12268 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
12269 : : errmsg("there is no primary key for referenced table \"%s\"",
12270 : : RelationGetRelationName(pkrel))));
12271 : :
12272 : : /* Must get indclass the hard way */
386 dgustafsson@postgres 12273 :CBC 535 : indclassDatum = SysCacheGetAttrNotNull(INDEXRELID, indexTuple,
12274 : : Anum_pg_index_indclass);
6956 tgl@sss.pgh.pa.us 12275 : 535 : indclass = (oidvector *) DatumGetPointer(indclassDatum);
12276 : :
12277 : : /*
12278 : : * Now build the list of PK attributes from the indkey definition (we
12279 : : * assume a primary key cannot have expressional elements)
12280 : : */
7875 12281 : 535 : *attnamelist = NIL;
2199 teodor@sigaev.ru 12282 [ + + ]: 1252 : for (i = 0; i < indexStruct->indnkeyatts; i++)
12283 : : {
6956 tgl@sss.pgh.pa.us 12284 : 717 : int pkattno = indexStruct->indkey.values[i];
12285 : :
7875 12286 : 717 : attnums[i] = pkattno;
12287 : 717 : atttypids[i] = attnumTypeId(pkrel, pkattno);
6956 12288 : 717 : opclasses[i] = indclass->values[i];
7875 12289 : 717 : *attnamelist = lappend(*attnamelist,
2489 12290 : 717 : makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno)))));
12291 : : }
12292 : :
21 peter@eisentraut.org 12293 :GNC 535 : *pk_has_without_overlaps = indexStruct->indisexclusion;
12294 : :
7875 tgl@sss.pgh.pa.us 12295 :CBC 535 : ReleaseSysCache(indexTuple);
12296 : :
12297 : 535 : return i;
12298 : : }
12299 : :
12300 : : /*
12301 : : * transformFkeyCheckAttrs -
12302 : : *
12303 : : * Validate that the 'attnums' columns in the 'pkrel' relation are valid to
12304 : : * reference as part of a foreign key constraint.
12305 : : *
12306 : : * Returns the OID of the unique index supporting the constraint and
12307 : : * populates the caller-provided 'opclasses' array with the opclasses
12308 : : * associated with the index columns. Also sets whether the index
12309 : : * uses WITHOUT OVERLAPS.
12310 : : *
12311 : : * Raises an ERROR on validation failure.
12312 : : */
12313 : : static Oid
12314 : 630 : transformFkeyCheckAttrs(Relation pkrel,
12315 : : int numattrs, int16 *attnums,
12316 : : bool with_period, Oid *opclasses,
12317 : : bool *pk_has_without_overlaps)
12318 : : {
12319 : 630 : Oid indexoid = InvalidOid;
12320 : 630 : bool found = false;
5359 12321 : 630 : bool found_deferrable = false;
12322 : : List *indexoidlist;
12323 : : ListCell *indexoidscan;
12324 : : int i,
12325 : : j;
12326 : :
12327 : : /*
12328 : : * Reject duplicate appearances of columns in the referenced-columns list.
12329 : : * Such a case is forbidden by the SQL standard, and even if we thought it
12330 : : * useful to allow it, there would be ambiguity about how to match the
12331 : : * list to unique indexes (in particular, it'd be unclear which index
12332 : : * opclass goes with which FK column).
12333 : : */
3536 12334 [ + + ]: 1475 : for (i = 0; i < numattrs; i++)
12335 : : {
12336 [ + + ]: 1119 : for (j = i + 1; j < numattrs; j++)
12337 : : {
12338 [ + + ]: 274 : if (attnums[i] == attnums[j])
3536 tgl@sss.pgh.pa.us 12339 [ + - ]:GBC 12 : ereport(ERROR,
12340 : : (errcode(ERRCODE_INVALID_FOREIGN_KEY),
12341 : : errmsg("foreign key referenced-columns list must not contain duplicates")));
12342 : : }
12343 : : }
12344 : :
12345 : : /*
12346 : : * Get the list of index OIDs for the table from the relcache, and look up
12347 : : * each one in the pg_index syscache, and match unique indexes to the list
12348 : : * of attnums we are given.
12349 : : */
7875 tgl@sss.pgh.pa.us 12350 :CBC 618 : indexoidlist = RelationGetIndexList(pkrel);
12351 : :
12352 [ + - + + : 708 : foreach(indexoidscan, indexoidlist)
+ + ]
12353 : : {
12354 : : HeapTuple indexTuple;
12355 : : Form_pg_index indexStruct;
12356 : :
7263 neilc@samurai.com 12357 : 702 : indexoid = lfirst_oid(indexoidscan);
5173 rhaas@postgresql.org 12358 : 702 : indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
7875 tgl@sss.pgh.pa.us 12359 [ - + ]: 702 : if (!HeapTupleIsValid(indexTuple))
7574 tgl@sss.pgh.pa.us 12360 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for index %u", indexoid);
7875 tgl@sss.pgh.pa.us 12361 :CBC 702 : indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
12362 : :
12363 : : /*
12364 : : * Must have the right number of columns; must be unique (or if
12365 : : * temporal then exclusion instead) and not a partial index; forget it
12366 : : * if there are any expressions, too. Invalid indexes are out as well.
12367 : : */
2199 teodor@sigaev.ru 12368 [ + + + + : 1350 : if (indexStruct->indnkeyatts == numattrs &&
+ + ]
21 peter@eisentraut.org 12369 :GNC 648 : (with_period ? indexStruct->indisexclusion : indexStruct->indisunique) &&
1935 peter_e@gmx.net 12370 [ + - + - ]:CBC 1282 : indexStruct->indisvalid &&
2209 andrew@dunslane.net 12371 [ + - ]: 1282 : heap_attisnull(indexTuple, Anum_pg_index_indpred, NULL) &&
12372 : 641 : heap_attisnull(indexTuple, Anum_pg_index_indexprs, NULL))
12373 : : {
12374 : : Datum indclassDatum;
12375 : : oidvector *indclass;
12376 : :
12377 : : /* Must get indclass the hard way */
386 dgustafsson@postgres 12378 : 641 : indclassDatum = SysCacheGetAttrNotNull(INDEXRELID, indexTuple,
12379 : : Anum_pg_index_indclass);
6956 tgl@sss.pgh.pa.us 12380 : 641 : indclass = (oidvector *) DatumGetPointer(indclassDatum);
12381 : :
12382 : : /*
12383 : : * The given attnum list may match the index columns in any order.
12384 : : * Check for a match, and extract the appropriate opclasses while
12385 : : * we're at it.
12386 : : *
12387 : : * We know that attnums[] is duplicate-free per the test at the
12388 : : * start of this function, and we checked above that the number of
12389 : : * index columns agrees, so if we find a match for each attnums[]
12390 : : * entry then we must have a one-to-one match in some order.
12391 : : */
7627 12392 [ + + ]: 1480 : for (i = 0; i < numattrs; i++)
12393 : : {
12394 : 868 : found = false;
12395 [ + + ]: 1159 : for (j = 0; j < numattrs; j++)
12396 : : {
6956 12397 [ + + ]: 1130 : if (attnums[i] == indexStruct->indkey.values[j])
12398 : : {
3536 12399 : 839 : opclasses[i] = indclass->values[j];
7627 12400 : 839 : found = true;
12401 : 839 : break;
12402 : : }
12403 : : }
12404 [ + + ]: 868 : if (!found)
12405 : 29 : break;
12406 : : }
12407 : : /* The last attribute in the index must be the PERIOD FK part */
21 peter@eisentraut.org 12408 [ + + + + ]:GNC 641 : if (found && with_period)
12409 : : {
12410 : 65 : int16 periodattnum = attnums[numattrs - 1];
12411 : :
12412 : 65 : found = (periodattnum == indexStruct->indkey.values[numattrs - 1]);
12413 : : }
12414 : :
12415 : : /*
12416 : : * Refuse to use a deferrable unique/primary key. This is per SQL
12417 : : * spec, and there would be a lot of interesting semantic problems
12418 : : * if we tried to allow it.
12419 : : */
5359 tgl@sss.pgh.pa.us 12420 [ + + - + ]:CBC 641 : if (found && !indexStruct->indimmediate)
12421 : : {
12422 : : /*
12423 : : * Remember that we found an otherwise matching index, so that
12424 : : * we can generate a more appropriate error message.
12425 : : */
5359 tgl@sss.pgh.pa.us 12426 :UBC 0 : found_deferrable = true;
12427 : 0 : found = false;
12428 : : }
12429 : :
12430 : : /* We need to know whether the index has WITHOUT OVERLAPS */
21 peter@eisentraut.org 12431 [ + + ]:GNC 641 : if (found)
12432 : 612 : *pk_has_without_overlaps = indexStruct->indisexclusion;
12433 : : }
7875 tgl@sss.pgh.pa.us 12434 :CBC 702 : ReleaseSysCache(indexTuple);
12435 [ + + ]: 702 : if (found)
12436 : 612 : break;
12437 : : }
12438 : :
12439 [ + + ]: 618 : if (!found)
12440 : : {
5359 12441 [ - + ]: 6 : if (found_deferrable)
5359 tgl@sss.pgh.pa.us 12442 [ # # ]:UBC 0 : ereport(ERROR,
12443 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
12444 : : errmsg("cannot use a deferrable unique constraint for referenced table \"%s\"",
12445 : : RelationGetRelationName(pkrel))));
12446 : : else
5359 tgl@sss.pgh.pa.us 12447 [ + - ]:CBC 6 : ereport(ERROR,
12448 : : (errcode(ERRCODE_INVALID_FOREIGN_KEY),
12449 : : errmsg("there is no unique constraint matching given keys for referenced table \"%s\"",
12450 : : RelationGetRelationName(pkrel))));
12451 : : }
12452 : :
7263 neilc@samurai.com 12453 : 612 : list_free(indexoidlist);
12454 : :
7875 tgl@sss.pgh.pa.us 12455 : 612 : return indexoid;
12456 : : }
12457 : :
12458 : : /*
12459 : : * findFkeyCast -
12460 : : *
12461 : : * Wrapper around find_coercion_pathway() for ATAddForeignKeyConstraint().
12462 : : * Caller has equal regard for binary coercibility and for an exact match.
12463 : : */
12464 : : static CoercionPathType
4430 alvherre@alvh.no-ip. 12465 : 6 : findFkeyCast(Oid targetTypeId, Oid sourceTypeId, Oid *funcid)
12466 : : {
12467 : : CoercionPathType ret;
12468 : :
12469 [ + - ]: 6 : if (targetTypeId == sourceTypeId)
12470 : : {
12471 : 6 : ret = COERCION_PATH_RELABELTYPE;
12472 : 6 : *funcid = InvalidOid;
12473 : : }
12474 : : else
12475 : : {
4430 alvherre@alvh.no-ip. 12476 :UBC 0 : ret = find_coercion_pathway(targetTypeId, sourceTypeId,
12477 : : COERCION_IMPLICIT, funcid);
12478 [ # # ]: 0 : if (ret == COERCION_PATH_NONE)
12479 : : /* A previously-relied-upon cast is now gone. */
12480 [ # # ]: 0 : elog(ERROR, "could not find cast from %u to %u",
12481 : : sourceTypeId, targetTypeId);
12482 : : }
12483 : :
4430 alvherre@alvh.no-ip. 12484 :CBC 6 : return ret;
12485 : : }
12486 : :
12487 : : /*
12488 : : * Permissions checks on the referenced table for ADD FOREIGN KEY
12489 : : *
12490 : : * Note: we have already checked that the user owns the referencing table,
12491 : : * else we'd have failed much earlier; no additional checks are needed for it.
12492 : : */
12493 : : static void
5561 tgl@sss.pgh.pa.us 12494 : 1129 : checkFkeyPermissions(Relation rel, int16 *attnums, int natts)
12495 : : {
12496 : 1129 : Oid roleid = GetUserId();
12497 : : AclResult aclresult;
12498 : : int i;
12499 : :
12500 : : /* Okay if we have relation-level REFERENCES permission */
12501 : 1129 : aclresult = pg_class_aclcheck(RelationGetRelid(rel), roleid,
12502 : : ACL_REFERENCES);
12503 [ + - ]: 1129 : if (aclresult == ACLCHECK_OK)
12504 : 1129 : return;
12505 : : /* Else we must have REFERENCES on each column */
5561 tgl@sss.pgh.pa.us 12506 [ # # ]:UBC 0 : for (i = 0; i < natts; i++)
12507 : : {
12508 : 0 : aclresult = pg_attribute_aclcheck(RelationGetRelid(rel), attnums[i],
12509 : : roleid, ACL_REFERENCES);
12510 [ # # ]: 0 : if (aclresult != ACLCHECK_OK)
2325 peter_e@gmx.net 12511 : 0 : aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
5561 tgl@sss.pgh.pa.us 12512 : 0 : RelationGetRelationName(rel));
12513 : : }
12514 : : }
12515 : :
12516 : : /*
12517 : : * Scan the existing rows in a table to verify they meet a proposed FK
12518 : : * constraint.
12519 : : *
12520 : : * Caller must have opened and locked both relations appropriately.
12521 : : */
12522 : : static void
4814 simon@2ndQuadrant.co 12523 :CBC 542 : validateForeignKeyConstraint(char *conname,
12524 : : Relation rel,
12525 : : Relation pkrel,
12526 : : Oid pkindOid,
12527 : : Oid constraintOid,
12528 : : bool hasperiod)
12529 : : {
12530 : : TupleTableSlot *slot;
12531 : : TableScanDesc scan;
638 peter@eisentraut.org 12532 : 542 : Trigger trig = {0};
12533 : : Snapshot snapshot;
12534 : : MemoryContext oldcxt;
12535 : : MemoryContext perTupCxt;
12536 : :
4810 rhaas@postgresql.org 12537 [ - + ]: 542 : ereport(DEBUG1,
12538 : : (errmsg_internal("validating foreign key constraint \"%s\"", conname)));
12539 : :
12540 : : /*
12541 : : * Build a trigger call structure; we'll need it either way.
12542 : : */
7947 tgl@sss.pgh.pa.us 12543 : 542 : trig.tgoid = InvalidOid;
4814 simon@2ndQuadrant.co 12544 : 542 : trig.tgname = conname;
6236 JanWieck@Yahoo.com 12545 : 542 : trig.tgenabled = TRIGGER_FIRES_ON_ORIGIN;
2433 peter_e@gmx.net 12546 : 542 : trig.tgisinternal = true;
7947 tgl@sss.pgh.pa.us 12547 : 542 : trig.tgconstrrelid = RelationGetRelid(pkrel);
5374 12548 : 542 : trig.tgconstrindid = pkindOid;
6269 12549 : 542 : trig.tgconstraint = constraintOid;
2433 peter_e@gmx.net 12550 : 542 : trig.tgdeferrable = false;
12551 : 542 : trig.tginitdeferred = false;
12552 : : /* we needn't fill in remaining fields */
12553 : :
12554 : : /*
12555 : : * See if we can do it with a single LEFT JOIN query. A false result
12556 : : * indicates we must proceed with the fire-the-trigger method. We can't do
12557 : : * a LEFT JOIN for temporal FKs yet, but we can once we support temporal
12558 : : * left joins.
12559 : : */
21 peter@eisentraut.org 12560 [ + + + + ]:GNC 542 : if (!hasperiod && RI_Initial_Check(&trig, rel, pkrel))
6269 tgl@sss.pgh.pa.us 12561 :CBC 438 : return;
12562 : :
12563 : : /*
12564 : : * Scan through each tuple, calling RI_FKey_check_ins (insert trigger) as
12565 : : * if that tuple had just been inserted. If any of those fail, it should
12566 : : * ereport(ERROR) and that's that.
12567 : : */
3939 rhaas@postgresql.org 12568 : 73 : snapshot = RegisterSnapshot(GetLatestSnapshot());
1861 andres@anarazel.de 12569 : 73 : slot = table_slot_create(rel, NULL);
12570 : 73 : scan = table_beginscan(rel, snapshot, 0, NULL);
12571 : :
1834 12572 : 73 : perTupCxt = AllocSetContextCreate(CurrentMemoryContext,
12573 : : "validateForeignKeyConstraint",
12574 : : ALLOCSET_SMALL_SIZES);
12575 : 73 : oldcxt = MemoryContextSwitchTo(perTupCxt);
12576 : :
1861 12577 [ + + ]: 115 : while (table_scan_getnextslot(scan, ForwardScanDirection, slot))
12578 : : {
1905 12579 : 51 : LOCAL_FCINFO(fcinfo, 0);
1511 peter@eisentraut.org 12580 : 51 : TriggerData trigdata = {0};
12581 : :
1834 andres@anarazel.de 12582 [ - + ]: 51 : CHECK_FOR_INTERRUPTS();
12583 : :
12584 : : /*
12585 : : * Make a call to the trigger function
12586 : : *
12587 : : * No parameters are passed, but we do set a context
12588 : : */
1905 12589 [ + - + - : 255 : MemSet(fcinfo, 0, SizeForFunctionCallInfo(0));
+ - + - +
+ ]
12590 : :
12591 : : /*
12592 : : * We assume RI_FKey_check_ins won't look at flinfo...
12593 : : */
7489 bruce@momjian.us 12594 : 51 : trigdata.type = T_TriggerData;
12595 : 51 : trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
12596 : 51 : trigdata.tg_relation = rel;
1835 tgl@sss.pgh.pa.us 12597 : 51 : trigdata.tg_trigtuple = ExecFetchSlotHeapTuple(slot, false, NULL);
1861 andres@anarazel.de 12598 : 51 : trigdata.tg_trigslot = slot;
7489 bruce@momjian.us 12599 : 51 : trigdata.tg_trigger = &trig;
12600 : :
1905 andres@anarazel.de 12601 : 51 : fcinfo->context = (Node *) &trigdata;
12602 : :
12603 : 51 : RI_FKey_check_ins(fcinfo);
12604 : :
1834 12605 : 42 : MemoryContextReset(perTupCxt);
12606 : : }
12607 : :
12608 : 64 : MemoryContextSwitchTo(oldcxt);
12609 : 64 : MemoryContextDelete(perTupCxt);
1861 12610 : 64 : table_endscan(scan);
3939 rhaas@postgresql.org 12611 : 64 : UnregisterSnapshot(snapshot);
1861 andres@anarazel.de 12612 : 64 : ExecDropSingleTupleTableSlot(slot);
12613 : : }
12614 : :
12615 : : /*
12616 : : * CreateFKCheckTrigger
12617 : : * Creates the insert (on_insert=true) or update "check" trigger that
12618 : : * implements a given foreign key
12619 : : *
12620 : : * Returns the OID of the so created trigger.
12621 : : */
12622 : : static Oid
3709 rhaas@postgresql.org 12623 : 2746 : CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
12624 : : Oid constraintOid, Oid indexOid, Oid parentTrigOid,
12625 : : bool on_insert)
12626 : : {
12627 : : ObjectAddress trigAddress;
12628 : : CreateTrigStmt *fk_trigger;
12629 : :
12630 : : /*
12631 : : * Note: for a self-referential FK (referencing and referenced tables are
12632 : : * the same), it is important that the ON UPDATE action fires before the
12633 : : * CHECK action, since both triggers will fire on the same row during an
12634 : : * UPDATE event; otherwise the CHECK trigger will be checking a non-final
12635 : : * state of the row. Triggers fire in name order, so we ensure this by
12636 : : * using names like "RI_ConstraintTrigger_a_NNNN" for the action triggers
12637 : : * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
12638 : : */
7947 tgl@sss.pgh.pa.us 12639 : 2746 : fk_trigger = makeNode(CreateTrigStmt);
1247 12640 : 2746 : fk_trigger->replace = false;
12641 : 2746 : fk_trigger->isconstraint = true;
4554 12642 : 2746 : fk_trigger->trigname = "RI_ConstraintTrigger_c";
3709 rhaas@postgresql.org 12643 : 2746 : fk_trigger->relation = NULL;
12644 : :
12645 : : /* Either ON INSERT or ON UPDATE */
6894 neilc@samurai.com 12646 [ + + ]: 2746 : if (on_insert)
12647 : : {
12648 : 1373 : fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
5414 tgl@sss.pgh.pa.us 12649 : 1373 : fk_trigger->events = TRIGGER_TYPE_INSERT;
12650 : : }
12651 : : else
12652 : : {
6894 neilc@samurai.com 12653 : 1373 : fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
5414 tgl@sss.pgh.pa.us 12654 : 1373 : fk_trigger->events = TRIGGER_TYPE_UPDATE;
12655 : : }
12656 : :
1247 12657 : 2746 : fk_trigger->args = NIL;
12658 : 2746 : fk_trigger->row = true;
12659 : 2746 : fk_trigger->timing = TRIGGER_TYPE_AFTER;
5296 12660 : 2746 : fk_trigger->columns = NIL;
5259 12661 : 2746 : fk_trigger->whenClause = NULL;
1247 12662 : 2746 : fk_trigger->transitionRels = NIL;
7947 12663 : 2746 : fk_trigger->deferrable = fkconstraint->deferrable;
12664 : 2746 : fk_trigger->initdeferred = fkconstraint->initdeferred;
3709 rhaas@postgresql.org 12665 : 2746 : fk_trigger->constrrel = NULL;
12666 : :
830 alvherre@alvh.no-ip. 12667 : 2746 : trigAddress = CreateTrigger(fk_trigger, NULL, myRelOid, refRelOid,
12668 : : constraintOid, indexOid, InvalidOid,
12669 : : parentTrigOid, NULL, true, false);
12670 : :
12671 : : /* Make changes-so-far visible */
6894 neilc@samurai.com 12672 : 2746 : CommandCounterIncrement();
12673 : :
830 alvherre@alvh.no-ip. 12674 : 2746 : return trigAddress.objectId;
12675 : : }
12676 : :
12677 : : /*
12678 : : * createForeignKeyActionTriggers
12679 : : * Create the referenced-side "action" triggers that implement a foreign
12680 : : * key.
12681 : : *
12682 : : * Returns the OIDs of the so created triggers in *deleteTrigOid and
12683 : : * *updateTrigOid.
12684 : : */
12685 : : static void
2202 12686 : 1381 : createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint,
12687 : : Oid constraintOid, Oid indexOid,
12688 : : Oid parentDelTrigger, Oid parentUpdTrigger,
12689 : : Oid *deleteTrigOid, Oid *updateTrigOid)
12690 : : {
12691 : : CreateTrigStmt *fk_trigger;
12692 : : ObjectAddress trigAddress;
12693 : :
12694 : : /*
12695 : : * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
12696 : : * DELETE action on the referenced table.
12697 : : */
7284 tgl@sss.pgh.pa.us 12698 : 1381 : fk_trigger = makeNode(CreateTrigStmt);
1247 12699 : 1381 : fk_trigger->replace = false;
12700 : 1381 : fk_trigger->isconstraint = true;
4554 12701 : 1381 : fk_trigger->trigname = "RI_ConstraintTrigger_a";
3709 rhaas@postgresql.org 12702 : 1381 : fk_trigger->relation = NULL;
1247 tgl@sss.pgh.pa.us 12703 : 1381 : fk_trigger->args = NIL;
7284 12704 : 1381 : fk_trigger->row = true;
4935 12705 : 1381 : fk_trigger->timing = TRIGGER_TYPE_AFTER;
5414 12706 : 1381 : fk_trigger->events = TRIGGER_TYPE_DELETE;
5296 12707 : 1381 : fk_trigger->columns = NIL;
5259 12708 : 1381 : fk_trigger->whenClause = NULL;
1247 12709 : 1381 : fk_trigger->transitionRels = NIL;
3709 rhaas@postgresql.org 12710 : 1381 : fk_trigger->constrrel = NULL;
12711 : :
7284 tgl@sss.pgh.pa.us 12712 [ + + + + : 1381 : switch (fkconstraint->fk_del_action)
+ - ]
12713 : : {
12714 : 1050 : case FKCONSTR_ACTION_NOACTION:
7115 12715 : 1050 : fk_trigger->deferrable = fkconstraint->deferrable;
12716 : 1050 : fk_trigger->initdeferred = fkconstraint->initdeferred;
7284 12717 : 1050 : fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
12718 : 1050 : break;
12719 : 32 : case FKCONSTR_ACTION_RESTRICT:
12720 : 32 : fk_trigger->deferrable = false;
12721 : 32 : fk_trigger->initdeferred = false;
12722 : 32 : fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
12723 : 32 : break;
12724 : 220 : case FKCONSTR_ACTION_CASCADE:
7115 12725 : 220 : fk_trigger->deferrable = false;
12726 : 220 : fk_trigger->initdeferred = false;
7284 12727 : 220 : fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
12728 : 220 : break;
12729 : 49 : case FKCONSTR_ACTION_SETNULL:
7115 12730 : 49 : fk_trigger->deferrable = false;
12731 : 49 : fk_trigger->initdeferred = false;
7284 12732 : 49 : fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
12733 : 49 : break;
12734 : 30 : case FKCONSTR_ACTION_SETDEFAULT:
7115 12735 : 30 : fk_trigger->deferrable = false;
12736 : 30 : fk_trigger->initdeferred = false;
7284 12737 : 30 : fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
12738 : 30 : break;
7284 tgl@sss.pgh.pa.us 12739 :UBC 0 : default:
12740 [ # # ]: 0 : elog(ERROR, "unrecognized FK action type: %d",
12741 : : (int) fkconstraint->fk_del_action);
12742 : : break;
12743 : : }
12744 : :
830 alvherre@alvh.no-ip. 12745 :CBC 1381 : trigAddress = CreateTrigger(fk_trigger, NULL, refRelOid,
12746 : : RelationGetRelid(rel),
12747 : : constraintOid, indexOid, InvalidOid,
12748 : : parentDelTrigger, NULL, true, false);
12749 [ + + ]: 1381 : if (deleteTrigOid)
12750 : 1348 : *deleteTrigOid = trigAddress.objectId;
12751 : :
12752 : : /* Make changes-so-far visible */
7284 tgl@sss.pgh.pa.us 12753 : 1381 : CommandCounterIncrement();
12754 : :
12755 : : /*
12756 : : * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
12757 : : * UPDATE action on the referenced table.
12758 : : */
12759 : 1381 : fk_trigger = makeNode(CreateTrigStmt);
1247 12760 : 1381 : fk_trigger->replace = false;
12761 : 1381 : fk_trigger->isconstraint = true;
4554 12762 : 1381 : fk_trigger->trigname = "RI_ConstraintTrigger_a";
3709 rhaas@postgresql.org 12763 : 1381 : fk_trigger->relation = NULL;
1247 tgl@sss.pgh.pa.us 12764 : 1381 : fk_trigger->args = NIL;
7284 12765 : 1381 : fk_trigger->row = true;
4935 12766 : 1381 : fk_trigger->timing = TRIGGER_TYPE_AFTER;
5414 12767 : 1381 : fk_trigger->events = TRIGGER_TYPE_UPDATE;
5296 12768 : 1381 : fk_trigger->columns = NIL;
5259 12769 : 1381 : fk_trigger->whenClause = NULL;
1247 12770 : 1381 : fk_trigger->transitionRels = NIL;
3709 rhaas@postgresql.org 12771 : 1381 : fk_trigger->constrrel = NULL;
12772 : :
7284 tgl@sss.pgh.pa.us 12773 [ + + + + : 1381 : switch (fkconstraint->fk_upd_action)
+ - ]
12774 : : {
12775 : 1164 : case FKCONSTR_ACTION_NOACTION:
7115 12776 : 1164 : fk_trigger->deferrable = fkconstraint->deferrable;
12777 : 1164 : fk_trigger->initdeferred = fkconstraint->initdeferred;
7284 12778 : 1164 : fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
12779 : 1164 : break;
12780 : 21 : case FKCONSTR_ACTION_RESTRICT:
12781 : 21 : fk_trigger->deferrable = false;
12782 : 21 : fk_trigger->initdeferred = false;
12783 : 21 : fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
12784 : 21 : break;
12785 : 144 : case FKCONSTR_ACTION_CASCADE:
7115 12786 : 144 : fk_trigger->deferrable = false;
12787 : 144 : fk_trigger->initdeferred = false;
7284 12788 : 144 : fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
12789 : 144 : break;
12790 : 31 : case FKCONSTR_ACTION_SETNULL:
7115 12791 : 31 : fk_trigger->deferrable = false;
12792 : 31 : fk_trigger->initdeferred = false;
7284 12793 : 31 : fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
12794 : 31 : break;
12795 : 21 : case FKCONSTR_ACTION_SETDEFAULT:
7115 12796 : 21 : fk_trigger->deferrable = false;
12797 : 21 : fk_trigger->initdeferred = false;
7284 12798 : 21 : fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
12799 : 21 : break;
7284 tgl@sss.pgh.pa.us 12800 :UBC 0 : default:
12801 [ # # ]: 0 : elog(ERROR, "unrecognized FK action type: %d",
12802 : : (int) fkconstraint->fk_upd_action);
12803 : : break;
12804 : : }
12805 : :
830 alvherre@alvh.no-ip. 12806 :CBC 1381 : trigAddress = CreateTrigger(fk_trigger, NULL, refRelOid,
12807 : : RelationGetRelid(rel),
12808 : : constraintOid, indexOid, InvalidOid,
12809 : : parentUpdTrigger, NULL, true, false);
12810 [ + + ]: 1381 : if (updateTrigOid)
12811 : 1348 : *updateTrigOid = trigAddress.objectId;
2202 12812 : 1381 : }
12813 : :
12814 : : /*
12815 : : * createForeignKeyCheckTriggers
12816 : : * Create the referencing-side "check" triggers that implement a foreign
12817 : : * key.
12818 : : *
12819 : : * Returns the OIDs of the so created triggers in *insertTrigOid and
12820 : : * *updateTrigOid.
12821 : : */
12822 : : static void
12823 : 1373 : createForeignKeyCheckTriggers(Oid myRelOid, Oid refRelOid,
12824 : : Constraint *fkconstraint, Oid constraintOid,
12825 : : Oid indexOid,
12826 : : Oid parentInsTrigger, Oid parentUpdTrigger,
12827 : : Oid *insertTrigOid, Oid *updateTrigOid)
12828 : : {
830 12829 : 1373 : *insertTrigOid = CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint,
12830 : : constraintOid, indexOid,
12831 : : parentInsTrigger, true);
12832 : 1373 : *updateTrigOid = CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint,
12833 : : constraintOid, indexOid,
12834 : : parentUpdTrigger, false);
7284 tgl@sss.pgh.pa.us 12835 : 1373 : }
12836 : :
12837 : : /*
12838 : : * ALTER TABLE DROP CONSTRAINT
12839 : : *
12840 : : * Like DROP COLUMN, we can't use the normal ALTER TABLE recursion mechanism.
12841 : : */
12842 : : static void
5819 12843 : 470 : ATExecDropConstraint(Relation rel, const char *constrName,
12844 : : DropBehavior behavior, bool recurse,
12845 : : bool missing_ok, LOCKMODE lockmode)
12846 : : {
12847 : : Relation conrel;
12848 : : SysScanDesc scan;
12849 : : ScanKeyData skey[3];
12850 : : HeapTuple tuple;
12851 : 470 : bool found = false;
12852 : :
1910 andres@anarazel.de 12853 : 470 : conrel = table_open(ConstraintRelationId, RowExclusiveLock);
12854 : :
12855 : : /*
12856 : : * Find and drop the target constraint
12857 : : */
2049 tgl@sss.pgh.pa.us 12858 : 470 : ScanKeyInit(&skey[0],
12859 : : Anum_pg_constraint_conrelid,
12860 : : BTEqualStrategyNumber, F_OIDEQ,
12861 : : ObjectIdGetDatum(RelationGetRelid(rel)));
12862 : 470 : ScanKeyInit(&skey[1],
12863 : : Anum_pg_constraint_contypid,
12864 : : BTEqualStrategyNumber, F_OIDEQ,
12865 : : ObjectIdGetDatum(InvalidOid));
12866 : 470 : ScanKeyInit(&skey[2],
12867 : : Anum_pg_constraint_conname,
12868 : : BTEqualStrategyNumber, F_NAMEEQ,
12869 : : CStringGetDatum(constrName));
12870 : 470 : scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId,
12871 : : true, NULL, 3, skey);
12872 : :
12873 : : /* There can be at most one matching row */
12874 [ + + ]: 470 : if (HeapTupleIsValid(tuple = systable_getnext(scan)))
12875 : : {
233 alvherre@alvh.no-ip. 12876 :GNC 452 : List *readyRels = NIL;
12877 : :
220 12878 : 452 : dropconstraint_internal(rel, tuple, behavior, recurse, false,
12879 : : missing_ok, &readyRels, lockmode);
233 alvherre@alvh.no-ip. 12880 :CBC 365 : found = true;
12881 : : }
12882 : :
12883 : 383 : systable_endscan(scan);
12884 : :
12885 [ + + ]: 383 : if (!found)
12886 : : {
12887 [ + + ]: 18 : if (!missing_ok)
5819 tgl@sss.pgh.pa.us 12888 [ + - ]: 12 : ereport(ERROR,
12889 : : errcode(ERRCODE_UNDEFINED_OBJECT),
12890 : : errmsg("constraint \"%s\" of relation \"%s\" does not exist",
12891 : : constrName, RelationGetRelationName(rel)));
12892 : : else
233 alvherre@alvh.no-ip. 12893 [ + - ]: 6 : ereport(NOTICE,
12894 : : errmsg("constraint \"%s\" of relation \"%s\" does not exist, skipping",
12895 : : constrName, RelationGetRelationName(rel)));
12896 : : }
12897 : :
233 alvherre@alvh.no-ip. 12898 :GNC 371 : table_close(conrel, RowExclusiveLock);
12899 : 371 : }
12900 : :
12901 : : /*
12902 : : * Remove a constraint, using its pg_constraint tuple
12903 : : *
12904 : : * Implementation for ALTER TABLE DROP CONSTRAINT and ALTER TABLE ALTER COLUMN
12905 : : * DROP NOT NULL.
12906 : : *
12907 : : * Returns the address of the constraint being removed.
12908 : : */
12909 : : static ObjectAddress
12910 : 643 : dropconstraint_internal(Relation rel, HeapTuple constraintTup, DropBehavior behavior,
12911 : : bool recurse, bool recursing, bool missing_ok, List **readyRels,
12912 : : LOCKMODE lockmode)
12913 : : {
12914 : : Relation conrel;
12915 : : Form_pg_constraint con;
12916 : : ObjectAddress conobj;
12917 : : List *children;
12918 : : ListCell *child;
12919 : 643 : bool is_no_inherit_constraint = false;
12920 : 643 : bool dropping_pk = false;
12921 : : char *constrName;
12922 : 643 : List *unconstrained_cols = NIL;
12923 : : char *colname;
12924 : :
12925 [ - + ]: 643 : if (list_member_oid(*readyRels, RelationGetRelid(rel)))
233 alvherre@alvh.no-ip. 12926 :UNC 0 : return InvalidObjectAddress;
233 alvherre@alvh.no-ip. 12927 :GNC 643 : *readyRels = lappend_oid(*readyRels, RelationGetRelid(rel));
12928 : :
12929 : : /* Guard against stack overflow due to overly deep inheritance tree. */
158 12930 : 643 : check_stack_depth();
12931 : :
12932 : : /* At top level, permission check was done in ATPrepCmd, else do it */
220 12933 [ + + ]: 643 : if (recursing)
12934 : 111 : ATSimplePermissions(AT_DropConstraint, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
12935 : :
233 12936 : 640 : conrel = table_open(ConstraintRelationId, RowExclusiveLock);
12937 : :
12938 : 640 : con = (Form_pg_constraint) GETSTRUCT(constraintTup);
12939 : 640 : constrName = NameStr(con->conname);
12940 : :
12941 : : /* Don't allow drop of inherited constraints */
12942 [ + + + + ]: 640 : if (con->coninhcount > 0 && !recursing)
12943 [ + - ]: 69 : ereport(ERROR,
12944 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
12945 : : errmsg("cannot drop inherited constraint \"%s\" of relation \"%s\"",
12946 : : constrName, RelationGetRelationName(rel))));
12947 : :
12948 : : /*
12949 : : * See if we have a not-null constraint or a PRIMARY KEY. If so, we have
12950 : : * more checks and actions below, so obtain the list of columns that are
12951 : : * constrained by the constraint being dropped.
12952 : : */
12953 [ + + ]: 571 : if (con->contype == CONSTRAINT_NOTNULL)
12954 : : {
12955 : 216 : AttrNumber colnum = extractNotNullColumn(constraintTup);
12956 : :
12957 [ + - ]: 216 : if (colnum != InvalidAttrNumber)
12958 : 216 : unconstrained_cols = list_make1_int(colnum);
12959 : : }
12960 [ + + ]: 355 : else if (con->contype == CONSTRAINT_PRIMARY)
12961 : : {
12962 : : Datum adatum;
12963 : : ArrayType *arr;
12964 : : int numkeys;
12965 : : bool isNull;
12966 : : int16 *attnums;
12967 : :
12968 : 70 : dropping_pk = true;
12969 : :
12970 : 70 : adatum = heap_getattr(constraintTup, Anum_pg_constraint_conkey,
12971 : : RelationGetDescr(conrel), &isNull);
12972 [ - + ]: 70 : if (isNull)
233 alvherre@alvh.no-ip. 12973 [ # # ]:UNC 0 : elog(ERROR, "null conkey for constraint %u", con->oid);
233 alvherre@alvh.no-ip. 12974 :GNC 70 : arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
12975 : 70 : numkeys = ARR_DIMS(arr)[0];
12976 [ + - + - ]: 70 : if (ARR_NDIM(arr) != 1 ||
12977 : 70 : numkeys < 0 ||
12978 [ + - ]: 70 : ARR_HASNULL(arr) ||
12979 [ - + ]: 70 : ARR_ELEMTYPE(arr) != INT2OID)
233 alvherre@alvh.no-ip. 12980 [ # # ]:UNC 0 : elog(ERROR, "conkey is not a 1-D smallint array");
233 alvherre@alvh.no-ip. 12981 [ - + ]:GNC 70 : attnums = (int16 *) ARR_DATA_PTR(arr);
12982 : :
12983 [ + + ]: 159 : for (int i = 0; i < numkeys; i++)
12984 : 89 : unconstrained_cols = lappend_int(unconstrained_cols, attnums[i]);
12985 : : }
12986 : :
12987 : 571 : is_no_inherit_constraint = con->connoinherit;
12988 : :
12989 : : /*
12990 : : * If it's a foreign-key constraint, we'd better lock the referenced table
12991 : : * and check that that's not in use, just as we've already done for the
12992 : : * constrained table (else we might, eg, be dropping a trigger that has
12993 : : * unfired events). But we can/must skip that in the self-referential
12994 : : * case.
12995 : : */
12996 [ + + ]: 571 : if (con->contype == CONSTRAINT_FOREIGN &&
12997 [ + - ]: 96 : con->confrelid != RelationGetRelid(rel))
12998 : : {
12999 : : Relation frel;
13000 : :
13001 : : /* Must match lock taken by RemoveTriggerById: */
13002 : 96 : frel = table_open(con->confrelid, AccessExclusiveLock);
13003 : 96 : CheckTableNotInUse(frel, "ALTER TABLE");
13004 : 93 : table_close(frel, NoLock);
13005 : : }
13006 : :
13007 : : /*
13008 : : * Perform the actual constraint deletion
13009 : : */
13010 : 568 : ObjectAddressSet(conobj, ConstraintRelationId, con->oid);
13011 : 568 : performDeletion(&conobj, behavior, 0);
13012 : :
13013 : : /*
13014 : : * If this was a NOT NULL or the primary key, the constrained columns must
13015 : : * have had pg_attribute.attnotnull set. See if we need to reset it, and
13016 : : * do so.
13017 : : */
13018 [ + + ]: 550 : if (unconstrained_cols)
13019 : : {
13020 : : Relation attrel;
13021 : : Bitmapset *pkcols;
13022 : : Bitmapset *ircols;
13023 : : ListCell *lc;
13024 : :
13025 : : /* Make the above deletion visible */
13026 : 268 : CommandCounterIncrement();
13027 : :
13028 : 268 : attrel = table_open(AttributeRelationId, RowExclusiveLock);
13029 : :
13030 : : /*
13031 : : * We want to test columns for their presence in the primary key, but
13032 : : * only if we're not dropping it.
13033 : : */
13034 [ + + ]: 268 : pkcols = dropping_pk ? NULL :
13035 : 216 : RelationGetIndexAttrBitmap(rel,
13036 : : INDEX_ATTR_BITMAP_PRIMARY_KEY);
13037 : 268 : ircols = RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_IDENTITY_KEY);
13038 : :
13039 [ + - + + : 540 : foreach(lc, unconstrained_cols)
+ + ]
13040 : : {
13041 : 281 : AttrNumber attnum = lfirst_int(lc);
13042 : : HeapTuple atttup;
13043 : : HeapTuple contup;
13044 : : Form_pg_attribute attForm;
13045 : :
13046 : : /*
13047 : : * Obtain pg_attribute tuple and verify conditions on it. We use
13048 : : * a copy we can scribble on.
13049 : : */
13050 : 281 : atttup = SearchSysCacheCopyAttNum(RelationGetRelid(rel), attnum);
13051 [ - + ]: 281 : if (!HeapTupleIsValid(atttup))
233 alvherre@alvh.no-ip. 13052 [ # # ]:UNC 0 : elog(ERROR, "cache lookup failed for attribute %d of relation %u",
13053 : : attnum, RelationGetRelid(rel));
233 alvherre@alvh.no-ip. 13054 :GNC 281 : attForm = (Form_pg_attribute) GETSTRUCT(atttup);
13055 : :
13056 : : /*
13057 : : * Since the above deletion has been made visible, we can now
13058 : : * search for any remaining constraints on this column (or these
13059 : : * columns, in the case we're dropping a multicol primary key.)
13060 : : * Then, verify whether any further NOT NULL or primary key
13061 : : * exists, and reset attnotnull if none.
13062 : : *
13063 : : * However, if this is a generated identity column, abort the
13064 : : * whole thing with a specific error message, because the
13065 : : * constraint is required in that case.
13066 : : */
13067 : 281 : contup = findNotNullConstraintAttnum(RelationGetRelid(rel), attnum);
13068 [ + + + + ]: 553 : if (contup ||
13069 : 272 : bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber,
13070 : : pkcols))
13071 : 119 : continue;
13072 : :
13073 : : /*
13074 : : * It's not valid to drop the not-null constraint for a GENERATED
13075 : : * AS IDENTITY column.
13076 : : */
13077 [ - + ]: 162 : if (attForm->attidentity)
233 alvherre@alvh.no-ip. 13078 [ # # ]:UNC 0 : ereport(ERROR,
13079 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
13080 : : errmsg("column \"%s\" of relation \"%s\" is an identity column",
13081 : : get_attname(RelationGetRelid(rel), attnum,
13082 : : false),
13083 : : RelationGetRelationName(rel)));
13084 : :
13085 : : /*
13086 : : * It's not valid to drop the not-null constraint for a column in
13087 : : * the replica identity index, either. (FULL is not affected.)
13088 : : */
233 alvherre@alvh.no-ip. 13089 [ + + ]:GNC 162 : if (bms_is_member(lfirst_int(lc) - FirstLowInvalidHeapAttributeNumber, ircols))
13090 [ + - ]: 9 : ereport(ERROR,
13091 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
13092 : : errmsg("column \"%s\" is in index used as replica identity",
13093 : : get_attname(RelationGetRelid(rel), lfirst_int(lc), false)));
13094 : :
13095 : : /* Reset attnotnull */
13096 [ + - ]: 153 : if (attForm->attnotnull)
13097 : : {
13098 : 153 : attForm->attnotnull = false;
13099 : 153 : CatalogTupleUpdate(attrel, &atttup->t_self, atttup);
13100 : : }
13101 : : }
13102 : 259 : table_close(attrel, RowExclusiveLock);
13103 : : }
13104 : :
13105 : : /*
13106 : : * For partitioned tables, non-CHECK, non-NOT-NULL inherited constraints
13107 : : * are dropped via the dependency mechanism, so we're done here.
13108 : : */
13109 [ + + ]: 541 : if (con->contype != CONSTRAINT_CHECK &&
13110 [ + + ]: 379 : con->contype != CONSTRAINT_NOTNULL &&
1907 alvherre@alvh.no-ip. 13111 [ + + ]:CBC 169 : rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
13112 : : {
13113 : 33 : table_close(conrel, RowExclusiveLock);
233 alvherre@alvh.no-ip. 13114 :GNC 33 : return conobj;
13115 : : }
13116 : :
13117 : : /*
13118 : : * Propagate to children as appropriate. Unlike most other ALTER
13119 : : * routines, we have to do this one level of recursion at a time; we can't
13120 : : * use find_all_inheritors to do it in one pass.
13121 : : */
4377 alvherre@alvh.no-ip. 13122 [ + + ]:CBC 508 : if (!is_no_inherit_constraint)
1082 13123 : 268 : children = find_inheritance_children(RelationGetRelid(rel), lockmode);
13124 : : else
5819 tgl@sss.pgh.pa.us 13125 : 240 : children = NIL;
13126 : :
13127 : : /*
13128 : : * For a partitioned table, if partitions exist and we are told not to
13129 : : * recurse, it's a user error. It doesn't make sense to have a constraint
13130 : : * be defined only on the parent, especially if it's a partitioned table.
13131 : : */
2546 sfrost@snowman.net 13132 [ + + + + ]: 508 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
13133 [ + + ]: 31 : children != NIL && !recurse)
13134 [ + - ]: 3 : ereport(ERROR,
13135 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
13136 : : errmsg("cannot remove constraint from only the partitioned table when partitions exist"),
13137 : : errhint("Do not specify the ONLY keyword.")));
13138 : :
13139 : : /* For not-null constraints we recurse by column name */
233 alvherre@alvh.no-ip. 13140 [ + + ]:GNC 505 : if (con->contype == CONSTRAINT_NOTNULL)
13141 : 210 : colname = NameStr(TupleDescAttr(RelationGetDescr(rel),
13142 : : linitial_int(unconstrained_cols) - 1)->attname);
13143 : : else
13144 : 295 : colname = NULL; /* keep compiler quiet */
13145 : :
5819 tgl@sss.pgh.pa.us 13146 [ + + + + :CBC 646 : foreach(child, children)
+ + ]
13147 : : {
13148 : 144 : Oid childrelid = lfirst_oid(child);
13149 : : Relation childrel;
13150 : : HeapTuple tuple;
13151 : : Form_pg_constraint childcon;
13152 : :
233 alvherre@alvh.no-ip. 13153 [ + + ]:GNC 144 : if (list_member_oid(*readyRels, childrelid))
13154 : 3 : continue; /* child already processed */
13155 : :
13156 : : /* find_inheritance_children already got lock */
1910 andres@anarazel.de 13157 :CBC 141 : childrel = table_open(childrelid, NoLock);
5819 tgl@sss.pgh.pa.us 13158 : 141 : CheckTableNotInUse(childrel, "ALTER TABLE");
13159 : :
13160 : : /*
13161 : : * We search for not-null constraint by column number, and other
13162 : : * constraints by name.
13163 : : */
233 alvherre@alvh.no-ip. 13164 [ + + ]:GNC 141 : if (con->contype == CONSTRAINT_NOTNULL)
13165 : : {
13166 : 41 : tuple = findNotNullConstraint(childrelid, colname);
13167 [ - + ]: 41 : if (!HeapTupleIsValid(tuple))
233 alvherre@alvh.no-ip. 13168 [ # # ]:UNC 0 : elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation %u",
13169 : : colname, RelationGetRelid(childrel));
13170 : : }
13171 : : else
13172 : : {
13173 : : SysScanDesc scan;
13174 : : ScanKeyData skey[3];
13175 : :
233 alvherre@alvh.no-ip. 13176 :GNC 100 : ScanKeyInit(&skey[0],
13177 : : Anum_pg_constraint_conrelid,
13178 : : BTEqualStrategyNumber, F_OIDEQ,
13179 : : ObjectIdGetDatum(childrelid));
13180 : 100 : ScanKeyInit(&skey[1],
13181 : : Anum_pg_constraint_contypid,
13182 : : BTEqualStrategyNumber, F_OIDEQ,
13183 : : ObjectIdGetDatum(InvalidOid));
13184 : 100 : ScanKeyInit(&skey[2],
13185 : : Anum_pg_constraint_conname,
13186 : : BTEqualStrategyNumber, F_NAMEEQ,
13187 : : CStringGetDatum(constrName));
13188 : 100 : scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId,
13189 : : true, NULL, 3, skey);
13190 : : /* There can only be one, so no need to loop */
13191 : 100 : tuple = systable_getnext(scan);
13192 [ - + ]: 100 : if (!HeapTupleIsValid(tuple))
233 alvherre@alvh.no-ip. 13193 [ # # ]:UNC 0 : ereport(ERROR,
13194 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
13195 : : errmsg("constraint \"%s\" of relation \"%s\" does not exist",
13196 : : constrName,
13197 : : RelationGetRelationName(childrel))));
233 alvherre@alvh.no-ip. 13198 :GNC 100 : tuple = heap_copytuple(tuple);
13199 : 100 : systable_endscan(scan);
13200 : : }
13201 : :
13202 : 141 : childcon = (Form_pg_constraint) GETSTRUCT(tuple);
13203 : :
13204 : : /* Right now only CHECK and not-null constraints can be inherited */
13205 [ + + ]: 141 : if (childcon->contype != CONSTRAINT_CHECK &&
13206 [ - + ]: 41 : childcon->contype != CONSTRAINT_NOTNULL)
233 alvherre@alvh.no-ip. 13207 [ # # ]:UNC 0 : elog(ERROR, "inherited constraint is not a CHECK or not-null constraint");
13208 : :
233 alvherre@alvh.no-ip. 13209 [ - + ]:GNC 141 : if (childcon->coninhcount <= 0) /* shouldn't happen */
4571 rhaas@postgresql.org 13210 [ # # ]:UBC 0 : elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
13211 : : childrelid, NameStr(childcon->conname));
13212 : :
4571 rhaas@postgresql.org 13213 [ + + ]:CBC 141 : if (recurse)
13214 : : {
13215 : : /*
13216 : : * If the child constraint has other definition sources, just
13217 : : * decrement its inheritance count; if not, recurse to delete it.
13218 : : */
233 alvherre@alvh.no-ip. 13219 [ + + + + ]:GNC 138 : if (childcon->coninhcount == 1 && !childcon->conislocal)
13220 : : {
13221 : : /* Time to delete this child constraint, too */
13222 : 99 : dropconstraint_internal(childrel, tuple, behavior,
13223 : : recurse, true, missing_ok, readyRels,
13224 : : lockmode);
13225 : : }
13226 : : else
13227 : : {
13228 : : /* Child constraint must survive my deletion */
13229 : 39 : childcon->coninhcount--;
13230 : 39 : CatalogTupleUpdate(conrel, &tuple->t_self, tuple);
13231 : :
13232 : : /* Make update visible */
5819 tgl@sss.pgh.pa.us 13233 :CBC 39 : CommandCounterIncrement();
13234 : : }
13235 : : }
13236 : : else
13237 : : {
13238 : : /*
13239 : : * If we were told to drop ONLY in this table (no recursion) and
13240 : : * there are no further parents for this constraint, we need to
13241 : : * mark the inheritors' constraints as locally defined rather than
13242 : : * inherited.
13243 : : */
233 alvherre@alvh.no-ip. 13244 :GNC 3 : childcon->coninhcount--;
13245 [ + - ]: 3 : if (childcon->coninhcount == 0)
13246 : 3 : childcon->conislocal = true;
13247 : :
13248 : 3 : CatalogTupleUpdate(conrel, &tuple->t_self, tuple);
13249 : :
13250 : : /* Make update visible */
4571 rhaas@postgresql.org 13251 :CBC 3 : CommandCounterIncrement();
13252 : : }
13253 : :
233 alvherre@alvh.no-ip. 13254 :GNC 138 : heap_freetuple(tuple);
13255 : :
1910 andres@anarazel.de 13256 :CBC 138 : table_close(childrel, NoLock);
13257 : : }
13258 : :
13259 : : /*
13260 : : * In addition, when dropping a primary key from a legacy-inheritance
13261 : : * parent table, we must recurse to children to mark the corresponding NOT
13262 : : * NULL constraint as no longer inherited, or drop it if this its last
13263 : : * reference.
13264 : : */
233 alvherre@alvh.no-ip. 13265 [ + + ]:GNC 502 : if (con->contype == CONSTRAINT_PRIMARY &&
13266 [ + - ]: 46 : rel->rd_rel->relkind == RELKIND_RELATION &&
13267 [ + + ]: 46 : rel->rd_rel->relhassubclass)
13268 : : {
13269 : 6 : List *colnames = NIL;
13270 : : ListCell *lc;
13271 : 6 : List *pkready = NIL;
13272 : :
13273 : : /*
13274 : : * Because primary keys are always marked as NO INHERIT, we don't have
13275 : : * a list of children yet, so obtain one now.
13276 : : */
13277 : 6 : children = find_inheritance_children(RelationGetRelid(rel), lockmode);
13278 : :
13279 : : /*
13280 : : * Find out the list of column names to process. Fortunately, we
13281 : : * already have the list of column numbers.
13282 : : */
13283 [ + - + + : 12 : foreach(lc, unconstrained_cols)
+ + ]
13284 : : {
13285 : 6 : colnames = lappend(colnames, get_attname(RelationGetRelid(rel),
13286 : 6 : lfirst_int(lc), false));
13287 : : }
13288 : :
13289 [ + - + + : 18 : foreach(child, children)
+ + ]
13290 : : {
13291 : 12 : Oid childrelid = lfirst_oid(child);
13292 : : Relation childrel;
13293 : :
13294 [ - + ]: 12 : if (list_member_oid(pkready, childrelid))
233 alvherre@alvh.no-ip. 13295 :UNC 0 : continue; /* child already processed */
13296 : :
13297 : : /* find_inheritance_children already got lock */
233 alvherre@alvh.no-ip. 13298 :GNC 12 : childrel = table_open(childrelid, NoLock);
13299 : 12 : CheckTableNotInUse(childrel, "ALTER TABLE");
13300 : :
13301 [ + - + + : 24 : foreach(lc, colnames)
+ + ]
13302 : : {
13303 : : HeapTuple contup;
13304 : 12 : char *colName = lfirst(lc);
13305 : :
13306 : 12 : contup = findNotNullConstraint(childrelid, colName);
13307 [ - + ]: 12 : if (contup == NULL)
233 alvherre@alvh.no-ip. 13308 [ # # ]:UNC 0 : elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\", relation \"%s\"",
13309 : : colName, RelationGetRelationName(childrel));
13310 : :
233 alvherre@alvh.no-ip. 13311 :GNC 12 : dropconstraint_internal(childrel, contup,
13312 : : DROP_RESTRICT, true, true,
13313 : : false, &pkready,
13314 : : lockmode);
13315 : 12 : pkready = NIL;
13316 : : }
13317 : :
13318 : 12 : table_close(childrel, NoLock);
13319 : :
13320 : 12 : pkready = lappend_oid(pkready, childrelid);
13321 : : }
13322 : : }
13323 : :
1910 andres@anarazel.de 13324 :CBC 502 : table_close(conrel, RowExclusiveLock);
13325 : :
233 alvherre@alvh.no-ip. 13326 :GNC 502 : return conobj;
13327 : : }
13328 : :
13329 : : /*
13330 : : * ALTER COLUMN TYPE
13331 : : *
13332 : : * Unlike other subcommand types, we do parse transformation for ALTER COLUMN
13333 : : * TYPE during phase 1 --- the AlterTableCmd passed in here is already
13334 : : * transformed (and must be, because we rely on some transformed fields).
13335 : : *
13336 : : * The point of this is that the execution of all ALTER COLUMN TYPEs for a
13337 : : * table will be done "in parallel" during phase 3, so all the USING
13338 : : * expressions should be parsed assuming the original column types. Also,
13339 : : * this allows a USING expression to refer to a field that will be dropped.
13340 : : *
13341 : : * To make this work safely, AT_PASS_DROP then AT_PASS_ALTER_TYPE must be
13342 : : * the first two execution steps in phase 2; they must not see the effects
13343 : : * of any other subcommand types, since the USING expressions are parsed
13344 : : * against the unmodified table's state.
13345 : : */
13346 : : static void
7284 tgl@sss.pgh.pa.us 13347 :CBC 559 : ATPrepAlterColumnType(List **wqueue,
13348 : : AlteredTableInfo *tab, Relation rel,
13349 : : bool recurse, bool recursing,
13350 : : AlterTableCmd *cmd, LOCKMODE lockmode,
13351 : : AlterTableUtilityContext *context)
13352 : : {
13353 : 559 : char *colName = cmd->name;
4785 13354 : 559 : ColumnDef *def = (ColumnDef *) cmd->def;
13355 : 559 : TypeName *typeName = def->typeName;
3299 alvherre@alvh.no-ip. 13356 : 559 : Node *transform = def->cooked_default;
13357 : : HeapTuple tuple;
13358 : : Form_pg_attribute attTup;
13359 : : AttrNumber attnum;
13360 : : Oid targettype;
13361 : : int32 targettypmod;
13362 : : Oid targetcollid;
13363 : : NewColumnValue *newval;
7284 tgl@sss.pgh.pa.us 13364 : 559 : ParseState *pstate = make_parsestate(NULL);
13365 : : AclResult aclresult;
13366 : : bool is_expr;
13367 : :
4891 peter_e@gmx.net 13368 [ + + + + ]: 559 : if (rel->rd_rel->reloftype && !recursing)
5014 13369 [ + - ]: 3 : ereport(ERROR,
13370 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
13371 : : errmsg("cannot alter column type of typed table")));
13372 : :
13373 : : /* lookup the attribute so we can check inheritance status */
7284 tgl@sss.pgh.pa.us 13374 : 556 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
13375 [ - + ]: 556 : if (!HeapTupleIsValid(tuple))
7284 tgl@sss.pgh.pa.us 13376 [ # # ]:UBC 0 : ereport(ERROR,
13377 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
13378 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
13379 : : colName, RelationGetRelationName(rel))));
7284 tgl@sss.pgh.pa.us 13380 :CBC 556 : attTup = (Form_pg_attribute) GETSTRUCT(tuple);
13381 : 556 : attnum = attTup->attnum;
13382 : :
13383 : : /* Can't alter a system attribute */
13384 [ - + ]: 556 : if (attnum <= 0)
7284 tgl@sss.pgh.pa.us 13385 [ # # ]:UBC 0 : ereport(ERROR,
13386 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
13387 : : errmsg("cannot alter system column \"%s\"",
13388 : : colName)));
13389 : :
13390 : : /*
13391 : : * Don't alter inherited columns. At outer level, there had better not be
13392 : : * any inherited definition; when recursing, we assume this was checked at
13393 : : * the parent level (see below).
13394 : : */
7284 tgl@sss.pgh.pa.us 13395 [ + + + + ]:CBC 556 : if (attTup->attinhcount > 0 && !recursing)
13396 [ + - ]: 3 : ereport(ERROR,
13397 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
13398 : : errmsg("cannot alter inherited column \"%s\"",
13399 : : colName)));
13400 : :
13401 : : /* Don't alter columns used in the partition key */
2292 rhaas@postgresql.org 13402 [ + + ]: 553 : if (has_partition_attrs(rel,
13403 : : bms_make_singleton(attnum - FirstLowInvalidHeapAttributeNumber),
13404 : : &is_expr))
1728 tgl@sss.pgh.pa.us 13405 [ + - ]: 9 : ereport(ERROR,
13406 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
13407 : : errmsg("cannot alter column \"%s\" because it is part of the partition key of relation \"%s\"",
13408 : : colName, RelationGetRelationName(rel))));
13409 : :
13410 : : /* Look up the target type */
4785 13411 : 544 : typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
13412 : :
518 peter@eisentraut.org 13413 : 544 : aclresult = object_aclcheck(TypeRelationId, targettype, GetUserId(), ACL_USAGE);
4499 peter_e@gmx.net 13414 [ + + ]: 544 : if (aclresult != ACLCHECK_OK)
4321 13415 : 6 : aclcheck_error_type(aclresult, targettype);
13416 : :
13417 : : /* And the collation */
4785 tgl@sss.pgh.pa.us 13418 : 538 : targetcollid = GetColumnDefCollation(NULL, def, targettype);
13419 : :
13420 : : /* make sure datatype is legal for a column */
4766 13421 : 538 : CheckAttributeType(colName, targettype, targetcollid,
13422 : 538 : list_make1_oid(rel->rd_rel->reltype),
13423 : : 0);
13424 : :
2685 rhaas@postgresql.org 13425 [ + + ]: 535 : if (tab->relkind == RELKIND_RELATION ||
13426 [ + + ]: 95 : tab->relkind == RELKIND_PARTITIONED_TABLE)
13427 : : {
13428 : : /*
13429 : : * Set up an expression to transform the old data value to the new
13430 : : * type. If a USING option was given, use the expression as
13431 : : * transformed by transformAlterTableStmt, else just take the old
13432 : : * value and try to coerce it. We do this first so that type
13433 : : * incompatibility can be detected before we waste effort, and because
13434 : : * we need the expression to be parsed against the original table row
13435 : : * type.
13436 : : */
3299 alvherre@alvh.no-ip. 13437 [ + + ]: 467 : if (!transform)
13438 : : {
4949 peter_e@gmx.net 13439 : 356 : transform = (Node *) makeVar(1, attnum,
13440 : : attTup->atttypid, attTup->atttypmod,
13441 : : attTup->attcollation,
13442 : : 0);
13443 : : }
13444 : :
13445 : 467 : transform = coerce_to_target_type(pstate,
13446 : : transform, exprType(transform),
13447 : : targettype, targettypmod,
13448 : : COERCION_ASSIGNMENT,
13449 : : COERCE_IMPLICIT_CAST,
13450 : : -1);
13451 [ + + ]: 467 : if (transform == NULL)
13452 : : {
13453 : : /* error text depends on whether USING was specified or not */
3229 tgl@sss.pgh.pa.us 13454 [ + + ]: 12 : if (def->cooked_default != NULL)
13455 [ + - ]: 3 : ereport(ERROR,
13456 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
13457 : : errmsg("result of USING clause for column \"%s\""
13458 : : " cannot be cast automatically to type %s",
13459 : : colName, format_type_be(targettype)),
13460 : : errhint("You might need to add an explicit cast.")));
13461 : : else
13462 [ + - ]: 9 : ereport(ERROR,
13463 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
13464 : : errmsg("column \"%s\" cannot be cast automatically to type %s",
13465 : : colName, format_type_be(targettype)),
13466 : : /* translator: USING is SQL, don't translate it */
13467 : : errhint("You might need to specify \"USING %s::%s\".",
13468 : : quote_identifier(colName),
13469 : : format_type_with_typemod(targettype,
13470 : : targettypmod))));
13471 : : }
13472 : :
13473 : : /* Fix collations after all else */
4775 13474 : 455 : assign_expr_collations(pstate, transform);
13475 : :
13476 : : /* Plan the expr now so we can accurately assess the need to rewrite. */
4681 rhaas@postgresql.org 13477 : 455 : transform = (Node *) expression_planner((Expr *) transform);
13478 : :
13479 : : /*
13480 : : * Add a work queue item to make ATRewriteTable update the column
13481 : : * contents.
13482 : : */
4949 peter_e@gmx.net 13483 : 455 : newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
13484 : 455 : newval->attnum = attnum;
13485 : 455 : newval->expr = (Expr *) transform;
1558 tgl@sss.pgh.pa.us 13486 : 455 : newval->is_generated = false;
13487 : :
4949 peter_e@gmx.net 13488 : 455 : tab->newvals = lappend(tab->newvals, newval);
4810 rhaas@postgresql.org 13489 [ + + ]: 828 : if (ATColumnChangeRequiresRewrite(transform, attnum))
3415 simon@2ndQuadrant.co 13490 : 373 : tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
13491 : : }
4785 tgl@sss.pgh.pa.us 13492 [ + + ]: 68 : else if (transform)
13493 [ + - ]: 6 : ereport(ERROR,
13494 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
13495 : : errmsg("\"%s\" is not a table",
13496 : : RelationGetRelationName(rel))));
13497 : :
829 13498 [ + + + - : 517 : if (!RELKIND_HAS_STORAGE(tab->relkind))
+ - + - +
- ]
13499 : : {
13500 : : /*
13501 : : * For relations without storage, do this check now. Regular tables
13502 : : * will check it later when the table is being rewritten.
13503 : : */
4811 rhaas@postgresql.org 13504 : 89 : find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
13505 : : }
13506 : :
7284 tgl@sss.pgh.pa.us 13507 : 502 : ReleaseSysCache(tuple);
13508 : :
13509 : : /*
13510 : : * Recurse manually by queueing a new command for each child, if
13511 : : * necessary. We cannot apply ATSimpleRecursion here because we need to
13512 : : * remap attribute numbers in the USING expression, if any.
13513 : : *
13514 : : * If we are told not to recurse, there had better not be any child
13515 : : * tables; else the alter would put them out of step.
13516 : : */
13517 [ + + ]: 502 : if (recurse)
13518 : : {
2652 alvherre@alvh.no-ip. 13519 : 382 : Oid relid = RelationGetRelid(rel);
13520 : : List *child_oids,
13521 : : *child_numparents;
13522 : : ListCell *lo,
13523 : : *li;
13524 : :
1701 tgl@sss.pgh.pa.us 13525 : 382 : child_oids = find_all_inheritors(relid, lockmode,
13526 : : &child_numparents);
13527 : :
13528 : : /*
13529 : : * find_all_inheritors does the recursive search of the inheritance
13530 : : * hierarchy, so all we have to do is process all of the relids in the
13531 : : * list that it returns.
13532 : : */
13533 [ + - + + : 859 : forboth(lo, child_oids, li, child_numparents)
+ - + + +
+ + - +
+ ]
13534 : : {
13535 : 489 : Oid childrelid = lfirst_oid(lo);
13536 : 489 : int numparents = lfirst_int(li);
13537 : : Relation childrel;
13538 : : HeapTuple childtuple;
13539 : : Form_pg_attribute childattTup;
13540 : :
2652 alvherre@alvh.no-ip. 13541 [ + + ]: 489 : if (childrelid == relid)
13542 : 382 : continue;
13543 : :
13544 : : /* find_all_inheritors already got lock */
13545 : 107 : childrel = relation_open(childrelid, NoLock);
13546 : 107 : CheckTableNotInUse(childrel, "ALTER TABLE");
13547 : :
13548 : : /*
13549 : : * Verify that the child doesn't have any inherited definitions of
13550 : : * this column that came from outside this inheritance hierarchy.
13551 : : * (renameatt makes a similar test, though in a different way
13552 : : * because of its different recursion mechanism.)
13553 : : */
1701 tgl@sss.pgh.pa.us 13554 : 107 : childtuple = SearchSysCacheAttName(RelationGetRelid(childrel),
13555 : : colName);
13556 [ - + ]: 107 : if (!HeapTupleIsValid(childtuple))
1701 tgl@sss.pgh.pa.us 13557 [ # # ]:UBC 0 : ereport(ERROR,
13558 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
13559 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
13560 : : colName, RelationGetRelationName(childrel))));
1701 tgl@sss.pgh.pa.us 13561 :CBC 107 : childattTup = (Form_pg_attribute) GETSTRUCT(childtuple);
13562 : :
13563 [ + + ]: 107 : if (childattTup->attinhcount > numparents)
13564 [ + - ]: 3 : ereport(ERROR,
13565 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
13566 : : errmsg("cannot alter inherited column \"%s\" of relation \"%s\"",
13567 : : colName, RelationGetRelationName(childrel))));
13568 : :
13569 : 104 : ReleaseSysCache(childtuple);
13570 : :
13571 : : /*
13572 : : * Remap the attribute numbers. If no USING expression was
13573 : : * specified, there is no need for this step.
13574 : : */
2652 alvherre@alvh.no-ip. 13575 [ + + ]: 104 : if (def->cooked_default)
13576 : : {
13577 : : AttrMap *attmap;
13578 : : bool found_whole_row;
13579 : :
13580 : : /* create a copy to scribble on */
13581 : 36 : cmd = copyObject(cmd);
13582 : :
1579 michael@paquier.xyz 13583 : 36 : attmap = build_attrmap_by_name(RelationGetDescr(childrel),
13584 : : RelationGetDescr(rel),
13585 : : false);
2652 alvherre@alvh.no-ip. 13586 : 72 : ((ColumnDef *) cmd->def)->cooked_default =
13587 : 36 : map_variable_attnos(def->cooked_default,
13588 : : 1, 0,
13589 : : attmap,
13590 : : InvalidOid, &found_whole_row);
13591 [ + + ]: 36 : if (found_whole_row)
13592 [ + - ]: 3 : ereport(ERROR,
13593 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
13594 : : errmsg("cannot convert whole-row table reference"),
13595 : : errdetail("USING expression contains a whole-row table reference.")));
13596 : 33 : pfree(attmap);
13597 : : }
1551 tgl@sss.pgh.pa.us 13598 : 101 : ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, context);
2652 alvherre@alvh.no-ip. 13599 : 95 : relation_close(childrel, NoLock);
13600 : : }
13601 : : }
7284 tgl@sss.pgh.pa.us 13602 [ + + - + ]: 145 : else if (!recursing &&
1082 alvherre@alvh.no-ip. 13603 : 25 : find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
7284 tgl@sss.pgh.pa.us 13604 [ # # ]:UBC 0 : ereport(ERROR,
13605 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
13606 : : errmsg("type of inherited column \"%s\" must be changed in child tables too",
13607 : : colName)));
13608 : :
4891 peter_e@gmx.net 13609 [ + + ]:CBC 490 : if (tab->relkind == RELKIND_COMPOSITE_TYPE)
1551 tgl@sss.pgh.pa.us 13610 : 25 : ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);
7284 13611 : 487 : }
13612 : :
13613 : : /*
13614 : : * When the data type of a column is changed, a rewrite might not be required
13615 : : * if the new type is sufficiently identical to the old one, and the USING
13616 : : * clause isn't trying to insert some other value. It's safe to skip the
13617 : : * rewrite in these cases:
13618 : : *
13619 : : * - the old type is binary coercible to the new type
13620 : : * - the new type is an unconstrained domain over the old type
13621 : : * - {NEW,OLD} or {OLD,NEW} is {timestamptz,timestamp} and the timezone is UTC
13622 : : *
13623 : : * In the case of a constrained domain, we could get by with scanning the
13624 : : * table and checking the constraint rather than actually rewriting it, but we
13625 : : * don't currently try to do that.
13626 : : */
13627 : : static bool
4810 rhaas@postgresql.org 13628 : 455 : ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno)
13629 : : {
13630 [ + - ]: 455 : Assert(expr != NULL);
13631 : :
13632 : : for (;;)
13633 : : {
13634 : : /* only one varno, so no need to check that */
1429 tgl@sss.pgh.pa.us 13635 [ + + + - ]: 507 : if (IsA(expr, Var) && ((Var *) expr)->varattno == varattno)
4810 rhaas@postgresql.org 13636 : 82 : return false;
13637 [ + + ]: 425 : else if (IsA(expr, RelabelType))
13638 : 46 : expr = (Node *) ((RelabelType *) expr)->arg;
4808 13639 [ - + ]: 379 : else if (IsA(expr, CoerceToDomain))
13640 : : {
4808 rhaas@postgresql.org 13641 :UBC 0 : CoerceToDomain *d = (CoerceToDomain *) expr;
13642 : :
3332 tgl@sss.pgh.pa.us 13643 [ # # ]: 0 : if (DomainHasConstraints(d->resulttype))
4808 rhaas@postgresql.org 13644 : 0 : return true;
13645 : 0 : expr = (Node *) d->arg;
13646 : : }
1864 noah@leadboat.com 13647 [ + + ]:CBC 379 : else if (IsA(expr, FuncExpr))
13648 : : {
13649 : 279 : FuncExpr *f = (FuncExpr *) expr;
13650 : :
13651 [ + + ]: 279 : switch (f->funcid)
13652 : : {
13653 : 9 : case F_TIMESTAMPTZ_TIMESTAMP:
13654 : : case F_TIMESTAMP_TIMESTAMPTZ:
13655 [ + + ]: 9 : if (TimestampTimestampTzRequiresRewrite())
13656 : 3 : return true;
13657 : : else
13658 : 6 : expr = linitial(f->args);
13659 : 6 : break;
13660 : 270 : default:
13661 : 270 : return true;
13662 : : }
13663 : : }
13664 : : else
4810 rhaas@postgresql.org 13665 : 100 : return true;
13666 : : }
13667 : : }
13668 : :
13669 : : /*
13670 : : * ALTER COLUMN .. SET DATA TYPE
13671 : : *
13672 : : * Return the address of the modified column.
13673 : : */
13674 : : static ObjectAddress
7284 tgl@sss.pgh.pa.us 13675 : 472 : ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
13676 : : AlterTableCmd *cmd, LOCKMODE lockmode)
13677 : : {
4785 13678 : 472 : char *colName = cmd->name;
13679 : 472 : ColumnDef *def = (ColumnDef *) cmd->def;
13680 : 472 : TypeName *typeName = def->typeName;
13681 : : HeapTuple heapTup;
13682 : : Form_pg_attribute attTup,
13683 : : attOldTup;
13684 : : AttrNumber attnum;
13685 : : HeapTuple typeTuple;
13686 : : Form_pg_type tform;
13687 : : Oid targettype;
13688 : : int32 targettypmod;
13689 : : Oid targetcollid;
13690 : : Node *defaultexpr;
13691 : : Relation attrelation;
13692 : : Relation depRel;
13693 : : ScanKeyData key[3];
13694 : : SysScanDesc scan;
13695 : : HeapTuple depTup;
13696 : : ObjectAddress address;
13697 : :
13698 : : /*
13699 : : * Clear all the missing values if we're rewriting the table, since this
13700 : : * renders them pointless.
13701 : : */
1921 andrew@dunslane.net 13702 [ + + ]: 472 : if (tab->rewrite)
13703 : : {
13704 : : Relation newrel;
13705 : :
1910 andres@anarazel.de 13706 : 346 : newrel = table_open(RelationGetRelid(rel), NoLock);
1921 andrew@dunslane.net 13707 : 346 : RelationClearMissing(newrel);
13708 : 346 : relation_close(newrel, NoLock);
13709 : : /* make sure we don't conflict with later attribute modifications */
13710 : 346 : CommandCounterIncrement();
13711 : : }
13712 : :
1910 andres@anarazel.de 13713 : 472 : attrelation = table_open(AttributeRelationId, RowExclusiveLock);
13714 : :
13715 : : /* Look up the target column */
7284 tgl@sss.pgh.pa.us 13716 : 472 : heapTup = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
2489 13717 [ - + ]: 472 : if (!HeapTupleIsValid(heapTup)) /* shouldn't happen */
7284 tgl@sss.pgh.pa.us 13718 [ # # ]:UBC 0 : ereport(ERROR,
13719 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
13720 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
13721 : : colName, RelationGetRelationName(rel))));
7284 tgl@sss.pgh.pa.us 13722 :CBC 472 : attTup = (Form_pg_attribute) GETSTRUCT(heapTup);
13723 : 472 : attnum = attTup->attnum;
2429 andres@anarazel.de 13724 : 472 : attOldTup = TupleDescAttr(tab->oldDesc, attnum - 1);
13725 : :
13726 : : /* Check for multiple ALTER TYPE on same column --- can't cope */
13727 [ + - ]: 472 : if (attTup->atttypid != attOldTup->atttypid ||
13728 [ - + ]: 472 : attTup->atttypmod != attOldTup->atttypmod)
7284 tgl@sss.pgh.pa.us 13729 [ # # ]:UBC 0 : ereport(ERROR,
13730 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
13731 : : errmsg("cannot alter type of column \"%s\" twice",
13732 : : colName)));
13733 : :
13734 : : /* Look up the target type (should not fail, since prep found it) */
4785 tgl@sss.pgh.pa.us 13735 :CBC 472 : typeTuple = typenameType(NULL, typeName, &targettypmod);
7284 13736 : 472 : tform = (Form_pg_type) GETSTRUCT(typeTuple);
1972 andres@anarazel.de 13737 : 472 : targettype = tform->oid;
13738 : : /* And the collation */
4785 tgl@sss.pgh.pa.us 13739 : 472 : targetcollid = GetColumnDefCollation(NULL, def, targettype);
13740 : :
13741 : : /*
13742 : : * If there is a default expression for the column, get it and ensure we
13743 : : * can coerce it to the new datatype. (We must do this before changing
13744 : : * the column type, because build_column_default itself will try to
13745 : : * coerce, and will not issue the error message we want if it fails.)
13746 : : *
13747 : : * We remove any implicit coercion steps at the top level of the old
13748 : : * default expression; this has been agreed to satisfy the principle of
13749 : : * least surprise. (The conversion to the new column type should act like
13750 : : * it started from what the user sees as the stored expression, and the
13751 : : * implicit coercions aren't going to be shown.)
13752 : : */
7284 13753 [ + + ]: 472 : if (attTup->atthasdef)
13754 : : {
13755 : 31 : defaultexpr = build_column_default(rel, attnum);
13756 [ - + ]: 31 : Assert(defaultexpr);
7114 13757 : 31 : defaultexpr = strip_implicit_coercions(defaultexpr);
2489 13758 : 31 : defaultexpr = coerce_to_target_type(NULL, /* no UNKNOWN params */
13759 : : defaultexpr, exprType(defaultexpr),
13760 : : targettype, targettypmod,
13761 : : COERCION_ASSIGNMENT,
13762 : : COERCE_IMPLICIT_CAST,
13763 : : -1);
7284 13764 [ + + ]: 31 : if (defaultexpr == NULL)
13765 : : {
1842 peter@eisentraut.org 13766 [ + + ]: 6 : if (attTup->attgenerated)
13767 [ + - ]: 3 : ereport(ERROR,
13768 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
13769 : : errmsg("generation expression for column \"%s\" cannot be cast automatically to type %s",
13770 : : colName, format_type_be(targettype))));
13771 : : else
13772 [ + - ]: 3 : ereport(ERROR,
13773 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
13774 : : errmsg("default for column \"%s\" cannot be cast automatically to type %s",
13775 : : colName, format_type_be(targettype))));
13776 : : }
13777 : : }
13778 : : else
7284 tgl@sss.pgh.pa.us 13779 : 441 : defaultexpr = NULL;
13780 : :
13781 : : /*
13782 : : * Find everything that depends on the column (constraints, indexes, etc),
13783 : : * and record enough information to let us recreate the objects.
13784 : : *
13785 : : * The actual recreation does not happen here, but only after we have
13786 : : * performed all the individual ALTER TYPE operations. We have to save
13787 : : * the info before executing ALTER TYPE, though, else the deparser will
13788 : : * get confused.
13789 : : */
101 peter@eisentraut.org 13790 :GNC 466 : RememberAllDependentForRebuilding(tab, AT_AlterColumnType, rel, attnum, colName);
13791 : :
13792 : : /*
13793 : : * Now scan for dependencies of this column on other things. The only
13794 : : * things we should find are the dependency on the column datatype and
13795 : : * possibly a collation dependency. Those can be removed.
13796 : : */
102 13797 : 454 : depRel = table_open(DependRelationId, RowExclusiveLock);
13798 : :
102 peter@eisentraut.org 13799 :CBC 454 : ScanKeyInit(&key[0],
13800 : : Anum_pg_depend_classid,
13801 : : BTEqualStrategyNumber, F_OIDEQ,
13802 : : ObjectIdGetDatum(RelationRelationId));
13803 : 454 : ScanKeyInit(&key[1],
13804 : : Anum_pg_depend_objid,
13805 : : BTEqualStrategyNumber, F_OIDEQ,
13806 : : ObjectIdGetDatum(RelationGetRelid(rel)));
13807 : 454 : ScanKeyInit(&key[2],
13808 : : Anum_pg_depend_objsubid,
13809 : : BTEqualStrategyNumber, F_INT4EQ,
13810 : : Int32GetDatum((int32) attnum));
13811 : :
13812 : 454 : scan = systable_beginscan(depRel, DependDependerIndexId, true,
13813 : : NULL, 3, key);
13814 : :
13815 [ + + ]: 456 : while (HeapTupleIsValid(depTup = systable_getnext(scan)))
13816 : : {
13817 : 2 : Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup);
13818 : : ObjectAddress foundObject;
13819 : :
13820 : 2 : foundObject.classId = foundDep->refclassid;
13821 : 2 : foundObject.objectId = foundDep->refobjid;
13822 : 2 : foundObject.objectSubId = foundDep->refobjsubid;
13823 : :
13824 [ - + ]: 2 : if (foundDep->deptype != DEPENDENCY_NORMAL)
102 peter@eisentraut.org 13825 [ # # ]:UBC 0 : elog(ERROR, "found unexpected dependency type '%c'",
13826 : : foundDep->deptype);
102 peter@eisentraut.org 13827 [ + - ]:CBC 2 : if (!(foundDep->refclassid == TypeRelationId &&
13828 [ - + ]: 2 : foundDep->refobjid == attTup->atttypid) &&
102 peter@eisentraut.org 13829 [ # # ]:UBC 0 : !(foundDep->refclassid == CollationRelationId &&
13830 [ # # ]: 0 : foundDep->refobjid == attTup->attcollation))
13831 [ # # ]: 0 : elog(ERROR, "found unexpected dependency for column: %s",
13832 : : getObjectDescription(&foundObject, false));
13833 : :
102 peter@eisentraut.org 13834 :CBC 2 : CatalogTupleDelete(depRel, &depTup->t_self);
13835 : : }
13836 : :
13837 : 454 : systable_endscan(scan);
13838 : :
13839 : 454 : table_close(depRel, RowExclusiveLock);
13840 : :
13841 : : /*
13842 : : * Here we go --- change the recorded column type and collation. (Note
13843 : : * heapTup is a copy of the syscache entry, so okay to scribble on.) First
13844 : : * fix up the missing value if any.
13845 : : */
13846 [ + + ]: 454 : if (attTup->atthasmissing)
13847 : : {
13848 : : Datum missingval;
13849 : : bool missingNull;
13850 : :
13851 : : /* if rewrite is true the missing value should already be cleared */
13852 [ - + ]: 3 : Assert(tab->rewrite == 0);
13853 : :
13854 : : /* Get the missing value datum */
13855 : 3 : missingval = heap_getattr(heapTup,
13856 : : Anum_pg_attribute_attmissingval,
13857 : : attrelation->rd_att,
13858 : : &missingNull);
13859 : :
13860 : : /* if it's a null array there is nothing to do */
13861 : :
13862 [ + - ]: 3 : if (!missingNull)
13863 : : {
13864 : : /*
13865 : : * Get the datum out of the array and repack it in a new array
13866 : : * built with the new type data. We assume that since the table
13867 : : * doesn't need rewriting, the actual Datum doesn't need to be
13868 : : * changed, only the array metadata.
13869 : : */
13870 : :
13871 : 3 : int one = 1;
13872 : : bool isNull;
13873 : 3 : Datum valuesAtt[Natts_pg_attribute] = {0};
13874 : 3 : bool nullsAtt[Natts_pg_attribute] = {0};
13875 : 3 : bool replacesAtt[Natts_pg_attribute] = {0};
13876 : : HeapTuple newTup;
13877 : :
13878 : 6 : missingval = array_get_element(missingval,
13879 : : 1,
13880 : : &one,
13881 : : 0,
13882 : 3 : attTup->attlen,
13883 : 3 : attTup->attbyval,
13884 : 3 : attTup->attalign,
13885 : : &isNull);
13886 : 3 : missingval = PointerGetDatum(construct_array(&missingval,
13887 : : 1,
13888 : : targettype,
13889 : 3 : tform->typlen,
13890 : 3 : tform->typbyval,
13891 : 3 : tform->typalign));
13892 : :
13893 : 3 : valuesAtt[Anum_pg_attribute_attmissingval - 1] = missingval;
13894 : 3 : replacesAtt[Anum_pg_attribute_attmissingval - 1] = true;
13895 : 3 : nullsAtt[Anum_pg_attribute_attmissingval - 1] = false;
13896 : :
13897 : 3 : newTup = heap_modify_tuple(heapTup, RelationGetDescr(attrelation),
13898 : : valuesAtt, nullsAtt, replacesAtt);
13899 : 3 : heap_freetuple(heapTup);
13900 : 3 : heapTup = newTup;
13901 : 3 : attTup = (Form_pg_attribute) GETSTRUCT(heapTup);
13902 : : }
13903 : : }
13904 : :
13905 : 454 : attTup->atttypid = targettype;
13906 : 454 : attTup->atttypmod = targettypmod;
13907 : 454 : attTup->attcollation = targetcollid;
13908 [ - + ]: 454 : if (list_length(typeName->arrayBounds) > PG_INT16_MAX)
102 peter@eisentraut.org 13909 [ # # ]:UBC 0 : ereport(ERROR,
13910 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
13911 : : errmsg("too many array dimensions"));
102 peter@eisentraut.org 13912 :CBC 454 : attTup->attndims = list_length(typeName->arrayBounds);
13913 : 454 : attTup->attlen = tform->typlen;
13914 : 454 : attTup->attbyval = tform->typbyval;
13915 : 454 : attTup->attalign = tform->typalign;
13916 : 454 : attTup->attstorage = tform->typstorage;
13917 : 454 : attTup->attcompression = InvalidCompressionMethod;
13918 : :
13919 : 454 : ReleaseSysCache(typeTuple);
13920 : :
13921 : 454 : CatalogTupleUpdate(attrelation, &heapTup->t_self, heapTup);
13922 : :
13923 : 454 : table_close(attrelation, RowExclusiveLock);
13924 : :
13925 : : /* Install dependencies on new datatype and collation */
13926 : 454 : add_column_datatype_dependency(RelationGetRelid(rel), attnum, targettype);
13927 : 454 : add_column_collation_dependency(RelationGetRelid(rel), attnum, targetcollid);
13928 : :
13929 : : /*
13930 : : * Drop any pg_statistic entry for the column, since it's now wrong type
13931 : : */
13932 : 454 : RemoveStatistics(RelationGetRelid(rel), attnum);
13933 : :
13934 [ - + ]: 454 : InvokeObjectPostAlterHook(RelationRelationId,
13935 : : RelationGetRelid(rel), attnum);
13936 : :
13937 : : /*
13938 : : * Update the default, if present, by brute force --- remove and re-add
13939 : : * the default. Probably unsafe to take shortcuts, since the new version
13940 : : * may well have additional dependencies. (It's okay to do this now,
13941 : : * rather than after other ALTER TYPE commands, since the default won't
13942 : : * depend on other column types.)
13943 : : */
13944 [ + + ]: 454 : if (defaultexpr)
13945 : : {
13946 : : /*
13947 : : * If it's a GENERATED default, drop its dependency records, in
13948 : : * particular its INTERNAL dependency on the column, which would
13949 : : * otherwise cause dependency.c to refuse to perform the deletion.
13950 : : */
13951 [ + + ]: 25 : if (attTup->attgenerated)
13952 : : {
13953 : 3 : Oid attrdefoid = GetAttrDefaultOid(RelationGetRelid(rel), attnum);
13954 : :
13955 [ - + ]: 3 : if (!OidIsValid(attrdefoid))
102 peter@eisentraut.org 13956 [ # # ]:UBC 0 : elog(ERROR, "could not find attrdef tuple for relation %u attnum %d",
13957 : : RelationGetRelid(rel), attnum);
102 peter@eisentraut.org 13958 :CBC 3 : (void) deleteDependencyRecordsFor(AttrDefaultRelationId, attrdefoid, false);
13959 : : }
13960 : :
13961 : : /*
13962 : : * Make updates-so-far visible, particularly the new pg_attribute row
13963 : : * which will be updated again.
13964 : : */
13965 : 25 : CommandCounterIncrement();
13966 : :
13967 : : /*
13968 : : * We use RESTRICT here for safety, but at present we do not expect
13969 : : * anything to depend on the default.
13970 : : */
13971 : 25 : RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, true,
13972 : : true);
13973 : :
13974 : 25 : StoreAttrDefault(rel, attnum, defaultexpr, true, false);
13975 : : }
13976 : :
13977 : 454 : ObjectAddressSubSet(address, RelationRelationId,
13978 : : RelationGetRelid(rel), attnum);
13979 : :
13980 : : /* Cleanup */
13981 : 454 : heap_freetuple(heapTup);
13982 : :
13983 : 454 : return address;
13984 : : }
13985 : :
13986 : : /*
13987 : : * Subroutine for ATExecAlterColumnType and ATExecSetExpression: Find everything
13988 : : * that depends on the column (constraints, indexes, etc), and record enough
13989 : : * information to let us recreate the objects.
13990 : : */
13991 : : static void
101 peter@eisentraut.org 13992 :GNC 505 : RememberAllDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype,
13993 : : Relation rel, AttrNumber attnum, const char *colName)
13994 : : {
13995 : : Relation depRel;
13996 : : ScanKeyData key[3];
13997 : : SysScanDesc scan;
13998 : : HeapTuple depTup;
13999 : :
14000 [ + + - + ]: 505 : Assert(subtype == AT_AlterColumnType || subtype == AT_SetExpression);
14001 : :
1910 andres@anarazel.de 14002 : 505 : depRel = table_open(DependRelationId, RowExclusiveLock);
14003 : :
7284 tgl@sss.pgh.pa.us 14004 : 505 : ScanKeyInit(&key[0],
14005 : : Anum_pg_depend_refclassid,
14006 : : BTEqualStrategyNumber, F_OIDEQ,
14007 : : ObjectIdGetDatum(RelationRelationId));
14008 : 505 : ScanKeyInit(&key[1],
14009 : : Anum_pg_depend_refobjid,
14010 : : BTEqualStrategyNumber, F_OIDEQ,
14011 : : ObjectIdGetDatum(RelationGetRelid(rel)));
14012 : 505 : ScanKeyInit(&key[2],
14013 : : Anum_pg_depend_refobjsubid,
14014 : : BTEqualStrategyNumber, F_INT4EQ,
14015 : : Int32GetDatum((int32) attnum));
14016 : :
6940 14017 : 505 : scan = systable_beginscan(depRel, DependReferenceIndexId, true,
14018 : : NULL, 3, key);
14019 : :
7284 14020 [ + + ]: 970 : while (HeapTupleIsValid(depTup = systable_getnext(scan)))
14021 : : {
7168 bruce@momjian.us 14022 : 477 : Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup);
14023 : : ObjectAddress foundObject;
14024 : :
7284 tgl@sss.pgh.pa.us 14025 : 477 : foundObject.classId = foundDep->classid;
14026 : 477 : foundObject.objectId = foundDep->objid;
14027 : 477 : foundObject.objectSubId = foundDep->objsubid;
14028 : :
19 peter@eisentraut.org 14029 [ + + + - : 477 : switch (foundObject.classId)
- + + - ]
14030 : : {
14031 : 132 : case RelationRelationId:
14032 : : {
7168 bruce@momjian.us 14033 : 132 : char relKind = get_rel_relkind(foundObject.objectId);
14034 : :
2277 alvherre@alvh.no-ip. 14035 [ + + + + ]: 132 : if (relKind == RELKIND_INDEX ||
14036 : : relKind == RELKIND_PARTITIONED_INDEX)
14037 : : {
7168 bruce@momjian.us 14038 [ - + ]: 116 : Assert(foundObject.objectSubId == 0);
1756 tgl@sss.pgh.pa.us 14039 : 116 : RememberIndexForRebuilding(foundObject.objectId, tab);
14040 : : }
7168 bruce@momjian.us 14041 [ + - ]: 16 : else if (relKind == RELKIND_SEQUENCE)
14042 : : {
14043 : : /*
14044 : : * This must be a SERIAL column's sequence. We need
14045 : : * not do anything to it.
14046 : : */
14047 [ - + ]: 16 : Assert(foundObject.objectSubId == 0);
14048 : : }
14049 : : else
14050 : : {
14051 : : /* Not expecting any other direct dependencies... */
7168 bruce@momjian.us 14052 [ # # ]:UNC 0 : elog(ERROR, "unexpected object depending on column: %s",
14053 : : getObjectDescription(&foundObject, false));
14054 : : }
7168 bruce@momjian.us 14055 :GNC 132 : break;
14056 : : }
14057 : :
19 peter@eisentraut.org 14058 : 262 : case ConstraintRelationId:
7284 tgl@sss.pgh.pa.us 14059 [ - + ]: 262 : Assert(foundObject.objectSubId == 0);
1756 14060 : 262 : RememberConstraintForRebuilding(foundObject.objectId, tab);
7284 14061 : 262 : break;
14062 : :
19 peter@eisentraut.org 14063 : 6 : case RewriteRelationId:
14064 : : /* XXX someday see if we can cope with revising views */
101 14065 [ + - ]: 6 : if (subtype == AT_AlterColumnType)
14066 [ + - ]: 6 : ereport(ERROR,
14067 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
14068 : : errmsg("cannot alter type of a column used by a view or rule"),
14069 : : errdetail("%s depends on column \"%s\"",
14070 : : getObjectDescription(&foundObject, false),
14071 : : colName)));
7284 tgl@sss.pgh.pa.us 14072 :UNC 0 : break;
14073 : :
19 peter@eisentraut.org 14074 : 0 : case TriggerRelationId:
14075 : :
14076 : : /*
14077 : : * A trigger can depend on a column because the column is
14078 : : * specified as an update target, or because the column is
14079 : : * used in the trigger's WHEN condition. The first case would
14080 : : * not require any extra work, but the second case would
14081 : : * require updating the WHEN expression, which will take a
14082 : : * significant amount of new code. Since we can't easily tell
14083 : : * which case applies, we punt for both. FIXME someday.
14084 : : */
101 14085 [ # # ]: 0 : if (subtype == AT_AlterColumnType)
14086 [ # # ]: 0 : ereport(ERROR,
14087 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
14088 : : errmsg("cannot alter type of a column used in a trigger definition"),
14089 : : errdetail("%s depends on column \"%s\"",
14090 : : getObjectDescription(&foundObject, false),
14091 : : colName)));
4943 tgl@sss.pgh.pa.us 14092 : 0 : break;
14093 : :
19 peter@eisentraut.org 14094 : 0 : case PolicyRelationId:
14095 : :
14096 : : /*
14097 : : * A policy can depend on a column because the column is
14098 : : * specified in the policy's USING or WITH CHECK qual
14099 : : * expressions. It might be possible to rewrite and recheck
14100 : : * the policy expression, but punt for now. It's certainly
14101 : : * easy enough to remove and recreate the policy; still, FIXME
14102 : : * someday.
14103 : : */
101 14104 [ # # ]: 0 : if (subtype == AT_AlterColumnType)
14105 [ # # ]: 0 : ereport(ERROR,
14106 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
14107 : : errmsg("cannot alter type of a column used in a policy definition"),
14108 : : errdetail("%s depends on column \"%s\"",
14109 : : getObjectDescription(&foundObject, false),
14110 : : colName)));
3426 sfrost@snowman.net 14111 : 0 : break;
14112 : :
19 peter@eisentraut.org 14113 :GNC 70 : case AttrDefaultRelationId:
14114 : : {
755 tgl@sss.pgh.pa.us 14115 : 70 : ObjectAddress col = GetAttrDefaultColumnAddress(foundObject.objectId);
14116 : :
14117 [ + - ]: 134 : if (col.objectId == RelationGetRelid(rel) &&
14118 [ + + ]: 70 : col.objectSubId == attnum)
14119 : : {
14120 : : /*
14121 : : * Ignore the column's own default expression. The
14122 : : * caller deals with it.
14123 : : */
14124 : : }
14125 : : else
14126 : : {
14127 : : /*
14128 : : * This must be a reference from the expression of a
14129 : : * generated column elsewhere in the same table.
14130 : : * Changing the type/generated expression of a column
14131 : : * that is used by a generated column is not allowed
14132 : : * by SQL standard, so just punt for now. It might be
14133 : : * doable with some thinking and effort.
14134 : : */
101 peter@eisentraut.org 14135 [ + - ]: 6 : if (subtype == AT_AlterColumnType)
14136 [ + - ]: 6 : ereport(ERROR,
14137 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
14138 : : errmsg("cannot alter type of a column used by a generated column"),
14139 : : errdetail("Column \"%s\" is used by generated column \"%s\".",
14140 : : colName,
14141 : : get_attname(col.objectId,
14142 : : col.objectSubId,
14143 : : false))));
14144 : : }
755 tgl@sss.pgh.pa.us 14145 : 64 : break;
14146 : : }
14147 : :
19 peter@eisentraut.org 14148 : 7 : case StatisticExtRelationId:
14149 : :
14150 : : /*
14151 : : * Give the extended-stats machinery a chance to fix anything
14152 : : * that this column type change would break.
14153 : : */
1115 tomas.vondra@postgre 14154 : 7 : RememberStatisticsForRebuilding(foundObject.objectId, tab);
2527 tgl@sss.pgh.pa.us 14155 : 7 : break;
14156 : :
19 peter@eisentraut.org 14157 :UNC 0 : default:
14158 : :
14159 : : /*
14160 : : * We don't expect any other sorts of objects to depend on a
14161 : : * column.
14162 : : */
7284 tgl@sss.pgh.pa.us 14163 [ # # ]: 0 : elog(ERROR, "unexpected object depending on column: %s",
14164 : : getObjectDescription(&foundObject, false));
14165 : : break;
14166 : : }
14167 : : }
14168 : :
7284 tgl@sss.pgh.pa.us 14169 :GNC 493 : systable_endscan(scan);
102 peter@eisentraut.org 14170 : 493 : table_close(depRel, NoLock);
7947 tgl@sss.pgh.pa.us 14171 : 493 : }
14172 : :
14173 : : /*
14174 : : * Subroutine for ATExecAlterColumnType: remember that a replica identity
14175 : : * needs to be reset.
14176 : : */
14177 : : static void
1493 peter@eisentraut.org 14178 :CBC 217 : RememberReplicaIdentityForRebuilding(Oid indoid, AlteredTableInfo *tab)
14179 : : {
14180 [ + + ]: 217 : if (!get_index_isreplident(indoid))
14181 : 208 : return;
14182 : :
14183 [ - + ]: 9 : if (tab->replicaIdentityIndex)
1493 peter@eisentraut.org 14184 [ # # ]:UBC 0 : elog(ERROR, "relation %u has multiple indexes marked as replica identity", tab->relid);
14185 : :
1493 peter@eisentraut.org 14186 :CBC 9 : tab->replicaIdentityIndex = get_rel_name(indoid);
14187 : : }
14188 : :
14189 : : /*
14190 : : * Subroutine for ATExecAlterColumnType: remember any clustered index.
14191 : : */
14192 : : static void
1469 michael@paquier.xyz 14193 : 217 : RememberClusterOnForRebuilding(Oid indoid, AlteredTableInfo *tab)
14194 : : {
14195 [ + + ]: 217 : if (!get_index_isclustered(indoid))
14196 : 208 : return;
14197 : :
14198 [ - + ]: 9 : if (tab->clusterOnIndex)
1469 michael@paquier.xyz 14199 [ # # ]:UBC 0 : elog(ERROR, "relation %u has multiple clustered indexes", tab->relid);
14200 : :
1469 michael@paquier.xyz 14201 :CBC 9 : tab->clusterOnIndex = get_rel_name(indoid);
14202 : : }
14203 : :
14204 : : /*
14205 : : * Subroutine for ATExecAlterColumnType: remember that a constraint needs
14206 : : * to be rebuilt (which we might already know).
14207 : : */
14208 : : static void
1756 tgl@sss.pgh.pa.us 14209 : 268 : RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab)
14210 : : {
14211 : : /*
14212 : : * This de-duplication check is critical for two independent reasons: we
14213 : : * mustn't try to recreate the same constraint twice, and if a constraint
14214 : : * depends on more than one column whose type is to be altered, we must
14215 : : * capture its definition string before applying any of the column type
14216 : : * changes. ruleutils.c will get confused if we ask again later.
14217 : : */
14218 [ + + ]: 268 : if (!list_member_oid(tab->changedConstraintOids, conoid))
14219 : : {
14220 : : /* OK, capture the constraint's existing definition string */
14221 : 223 : char *defstring = pg_get_constraintdef_command(conoid);
14222 : : Oid indoid;
14223 : :
14224 : 223 : tab->changedConstraintOids = lappend_oid(tab->changedConstraintOids,
14225 : : conoid);
14226 : 223 : tab->changedConstraintDefs = lappend(tab->changedConstraintDefs,
14227 : : defstring);
14228 : :
14229 : : /*
14230 : : * For the index of a constraint, if any, remember if it is used for
14231 : : * the table's replica identity or if it is a clustered index, so that
14232 : : * ATPostAlterTypeCleanup() can queue up commands necessary to restore
14233 : : * those properties.
14234 : : */
1493 peter@eisentraut.org 14235 : 223 : indoid = get_constraint_index(conoid);
14236 [ + + ]: 223 : if (OidIsValid(indoid))
14237 : : {
14238 : 111 : RememberReplicaIdentityForRebuilding(indoid, tab);
1469 michael@paquier.xyz 14239 : 111 : RememberClusterOnForRebuilding(indoid, tab);
14240 : : }
14241 : : }
1756 tgl@sss.pgh.pa.us 14242 : 268 : }
14243 : :
14244 : : /*
14245 : : * Subroutine for ATExecAlterColumnType: remember that an index needs
14246 : : * to be rebuilt (which we might already know).
14247 : : */
14248 : : static void
14249 : 116 : RememberIndexForRebuilding(Oid indoid, AlteredTableInfo *tab)
14250 : : {
14251 : : /*
14252 : : * This de-duplication check is critical for two independent reasons: we
14253 : : * mustn't try to recreate the same index twice, and if an index depends
14254 : : * on more than one column whose type is to be altered, we must capture
14255 : : * its definition string before applying any of the column type changes.
14256 : : * ruleutils.c will get confused if we ask again later.
14257 : : */
14258 [ + + ]: 116 : if (!list_member_oid(tab->changedIndexOids, indoid))
14259 : : {
14260 : : /*
14261 : : * Before adding it as an index-to-rebuild, we'd better see if it
14262 : : * belongs to a constraint, and if so rebuild the constraint instead.
14263 : : * Typically this check fails, because constraint indexes normally
14264 : : * have only dependencies on their constraint. But it's possible for
14265 : : * such an index to also have direct dependencies on table columns,
14266 : : * for example with a partial exclusion constraint.
14267 : : */
14268 : 112 : Oid conoid = get_index_constraint(indoid);
14269 : :
14270 [ + + ]: 112 : if (OidIsValid(conoid))
14271 : : {
14272 : 6 : RememberConstraintForRebuilding(conoid, tab);
14273 : : }
14274 : : else
14275 : : {
14276 : : /* OK, capture the index's existing definition string */
14277 : 106 : char *defstring = pg_get_indexdef_string(indoid);
14278 : :
14279 : 106 : tab->changedIndexOids = lappend_oid(tab->changedIndexOids,
14280 : : indoid);
14281 : 106 : tab->changedIndexDefs = lappend(tab->changedIndexDefs,
14282 : : defstring);
14283 : :
14284 : : /*
14285 : : * Remember if this index is used for the table's replica identity
14286 : : * or if it is a clustered index, so that ATPostAlterTypeCleanup()
14287 : : * can queue up commands necessary to restore those properties.
14288 : : */
1493 peter@eisentraut.org 14289 : 106 : RememberReplicaIdentityForRebuilding(indoid, tab);
1469 michael@paquier.xyz 14290 : 106 : RememberClusterOnForRebuilding(indoid, tab);
14291 : : }
14292 : : }
1756 tgl@sss.pgh.pa.us 14293 : 116 : }
14294 : :
14295 : : /*
14296 : : * Subroutine for ATExecAlterColumnType: remember that a statistics object
14297 : : * needs to be rebuilt (which we might already know).
14298 : : */
14299 : : static void
1115 tomas.vondra@postgre 14300 : 7 : RememberStatisticsForRebuilding(Oid stxoid, AlteredTableInfo *tab)
14301 : : {
14302 : : /*
14303 : : * This de-duplication check is critical for two independent reasons: we
14304 : : * mustn't try to recreate the same statistics object twice, and if the
14305 : : * statistics object depends on more than one column whose type is to be
14306 : : * altered, we must capture its definition string before applying any of
14307 : : * the type changes. ruleutils.c will get confused if we ask again later.
14308 : : */
14309 [ + - ]: 7 : if (!list_member_oid(tab->changedStatisticsOids, stxoid))
14310 : : {
14311 : : /* OK, capture the statistics object's existing definition string */
14312 : 7 : char *defstring = pg_get_statisticsobjdef_string(stxoid);
14313 : :
14314 : 7 : tab->changedStatisticsOids = lappend_oid(tab->changedStatisticsOids,
14315 : : stxoid);
14316 : 7 : tab->changedStatisticsDefs = lappend(tab->changedStatisticsDefs,
14317 : : defstring);
14318 : : }
14319 : 7 : }
14320 : :
14321 : : /*
14322 : : * Cleanup after we've finished all the ALTER TYPE or SET EXPRESSION
14323 : : * operations for a particular relation. We have to drop and recreate all the
14324 : : * indexes and constraints that depend on the altered columns. We do the
14325 : : * actual dropping here, but re-creation is managed by adding work queue
14326 : : * entries to do those steps later.
14327 : : */
14328 : : static void
5009 simon@2ndQuadrant.co 14329 : 478 : ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
14330 : : {
14331 : : ObjectAddress obj;
14332 : : ObjectAddresses *objects;
14333 : : ListCell *def_item;
14334 : : ListCell *oid_item;
14335 : :
14336 : : /*
14337 : : * Collect all the constraints and indexes to drop so we can process them
14338 : : * in a single call. That way we don't have to worry about dependencies
14339 : : * among them.
14340 : : */
2039 alvherre@alvh.no-ip. 14341 : 478 : objects = new_object_addresses();
14342 : :
14343 : : /*
14344 : : * Re-parse the index and constraint definitions, and attach them to the
14345 : : * appropriate work queue entries. We do this before dropping because in
14346 : : * the case of a FOREIGN KEY constraint, we might not yet have exclusive
14347 : : * lock on the table the constraint is attached to, and we need to get
14348 : : * that before reparsing/dropping.
14349 : : *
14350 : : * We can't rely on the output of deparsing to tell us which relation to
14351 : : * operate on, because concurrent activity might have made the name
14352 : : * resolve differently. Instead, we've got to use the OID of the
14353 : : * constraint or index we're processing to figure out which relation to
14354 : : * operate on.
14355 : : */
4654 rhaas@postgresql.org 14356 [ + + + + : 701 : forboth(oid_item, tab->changedConstraintOids,
+ + + + +
+ + - +
+ ]
14357 : : def_item, tab->changedConstraintDefs)
14358 : : {
3631 bruce@momjian.us 14359 : 223 : Oid oldId = lfirst_oid(oid_item);
14360 : : HeapTuple tup;
14361 : : Form_pg_constraint con;
14362 : : Oid relid;
14363 : : Oid confrelid;
14364 : : char contype;
14365 : : bool conislocal;
14366 : :
3068 tgl@sss.pgh.pa.us 14367 : 223 : tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(oldId));
2489 14368 [ - + ]: 223 : if (!HeapTupleIsValid(tup)) /* should not happen */
3068 tgl@sss.pgh.pa.us 14369 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for constraint %u", oldId);
3068 tgl@sss.pgh.pa.us 14370 :CBC 223 : con = (Form_pg_constraint) GETSTRUCT(tup);
2356 14371 [ + + ]: 223 : if (OidIsValid(con->conrelid))
14372 : 216 : relid = con->conrelid;
14373 : : else
14374 : : {
14375 : : /* must be a domain constraint */
14376 : 7 : relid = get_typ_typrelid(getBaseType(con->contypid));
14377 [ - + ]: 7 : if (!OidIsValid(relid))
2356 tgl@sss.pgh.pa.us 14378 [ # # ]:UBC 0 : elog(ERROR, "could not identify relation associated with constraint %u", oldId);
14379 : : }
3068 tgl@sss.pgh.pa.us 14380 :CBC 223 : confrelid = con->confrelid;
2022 14381 : 223 : contype = con->contype;
3068 14382 : 223 : conislocal = con->conislocal;
14383 : 223 : ReleaseSysCache(tup);
14384 : :
2022 14385 : 223 : ObjectAddressSet(obj, ConstraintRelationId, oldId);
2039 alvherre@alvh.no-ip. 14386 : 223 : add_exact_object_address(&obj, objects);
14387 : :
14388 : : /*
14389 : : * If the constraint is inherited (only), we don't want to inject a
14390 : : * new definition here; it'll get recreated when
14391 : : * ATAddCheckNNConstraint recurses from adding the parent table's
14392 : : * constraint. But we had to carry the info this far so that we can
14393 : : * drop the constraint below.
14394 : : */
3068 tgl@sss.pgh.pa.us 14395 [ + + ]: 223 : if (!conislocal)
14396 : 8 : continue;
14397 : :
14398 : : /*
14399 : : * When rebuilding an FK constraint that references the table we're
14400 : : * modifying, we might not yet have any lock on the FK's table, so get
14401 : : * one now. We'll need AccessExclusiveLock for the DROP CONSTRAINT
14402 : : * step, so there's no value in asking for anything weaker.
14403 : : */
2022 14404 [ + + + - ]: 215 : if (relid != tab->relid && contype == CONSTRAINT_FOREIGN)
14405 : 18 : LockRelationOid(relid, AccessExclusiveLock);
14406 : :
3709 rhaas@postgresql.org 14407 : 215 : ATPostAlterTypeParse(oldId, relid, confrelid,
14408 : 215 : (char *) lfirst(def_item),
4654 14409 : 215 : wqueue, lockmode, tab->rewrite);
14410 : : }
14411 [ + + + + : 584 : forboth(oid_item, tab->changedIndexOids,
+ + + + +
+ + - +
+ ]
14412 : : def_item, tab->changedIndexDefs)
14413 : : {
3631 bruce@momjian.us 14414 : 106 : Oid oldId = lfirst_oid(oid_item);
14415 : : Oid relid;
14416 : :
3709 rhaas@postgresql.org 14417 : 106 : relid = IndexGetRelation(oldId, false);
14418 : 106 : ATPostAlterTypeParse(oldId, relid, InvalidOid,
14419 : 106 : (char *) lfirst(def_item),
4654 14420 : 106 : wqueue, lockmode, tab->rewrite);
14421 : :
2022 tgl@sss.pgh.pa.us 14422 : 106 : ObjectAddressSet(obj, RelationRelationId, oldId);
2039 alvherre@alvh.no-ip. 14423 : 106 : add_exact_object_address(&obj, objects);
14424 : : }
14425 : :
14426 : : /* add dependencies for new statistics */
1115 tomas.vondra@postgre 14427 [ + + + + : 485 : forboth(oid_item, tab->changedStatisticsOids,
+ + + + +
+ + - +
+ ]
14428 : : def_item, tab->changedStatisticsDefs)
14429 : : {
14430 : 7 : Oid oldId = lfirst_oid(oid_item);
14431 : : Oid relid;
14432 : :
14433 : 7 : relid = StatisticsGetRelation(oldId, false);
14434 : 7 : ATPostAlterTypeParse(oldId, relid, InvalidOid,
14435 : 7 : (char *) lfirst(def_item),
14436 : 7 : wqueue, lockmode, tab->rewrite);
14437 : :
14438 : 7 : ObjectAddressSet(obj, StatisticExtRelationId, oldId);
14439 : 7 : add_exact_object_address(&obj, objects);
14440 : : }
14441 : :
14442 : : /*
14443 : : * Queue up command to restore replica identity index marking
14444 : : */
1493 peter@eisentraut.org 14445 [ + + ]: 478 : if (tab->replicaIdentityIndex)
14446 : : {
14447 : 9 : AlterTableCmd *cmd = makeNode(AlterTableCmd);
14448 : 9 : ReplicaIdentityStmt *subcmd = makeNode(ReplicaIdentityStmt);
14449 : :
14450 : 9 : subcmd->identity_type = REPLICA_IDENTITY_INDEX;
14451 : 9 : subcmd->name = tab->replicaIdentityIndex;
14452 : 9 : cmd->subtype = AT_ReplicaIdentity;
14453 : 9 : cmd->def = (Node *) subcmd;
14454 : :
14455 : : /* do it after indexes and constraints */
14456 : 9 : tab->subcmds[AT_PASS_OLD_CONSTR] =
14457 : 9 : lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
14458 : : }
14459 : :
14460 : : /*
14461 : : * Queue up command to restore marking of index used for cluster.
14462 : : */
1469 michael@paquier.xyz 14463 [ + + ]: 478 : if (tab->clusterOnIndex)
14464 : : {
14465 : 9 : AlterTableCmd *cmd = makeNode(AlterTableCmd);
14466 : :
14467 : 9 : cmd->subtype = AT_ClusterOn;
14468 : 9 : cmd->name = tab->clusterOnIndex;
14469 : :
14470 : : /* do it after indexes and constraints */
14471 : 9 : tab->subcmds[AT_PASS_OLD_CONSTR] =
14472 : 9 : lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
14473 : : }
14474 : :
14475 : : /*
14476 : : * It should be okay to use DROP_RESTRICT here, since nothing else should
14477 : : * be depending on these objects.
14478 : : */
2039 alvherre@alvh.no-ip. 14479 : 478 : performMultipleDeletions(objects, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
14480 : :
14481 : 478 : free_object_addresses(objects);
14482 : :
14483 : : /*
14484 : : * The objects will get recreated during subsequent passes over the work
14485 : : * queue.
14486 : : */
7284 tgl@sss.pgh.pa.us 14487 : 478 : }
14488 : :
14489 : : /*
14490 : : * Parse the previously-saved definition string for a constraint, index or
14491 : : * statistics object against the newly-established column data type(s), and
14492 : : * queue up the resulting command parsetrees for execution.
14493 : : *
14494 : : * This might fail if, for example, you have a WHERE clause that uses an
14495 : : * operator that's not available for the new column type.
14496 : : */
14497 : : static void
3709 rhaas@postgresql.org 14498 : 328 : ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
14499 : : List **wqueue, LOCKMODE lockmode, bool rewrite)
14500 : : {
14501 : : List *raw_parsetree_list;
14502 : : List *querytree_list;
14503 : : ListCell *list_item;
14504 : : Relation rel;
14505 : :
14506 : : /*
14507 : : * We expect that we will get only ALTER TABLE and CREATE INDEX
14508 : : * statements. Hence, there is no need to pass them through
14509 : : * parse_analyze_*() or the rewriter, but instead we need to pass them
14510 : : * through parse_utilcmd.c to make them ready for execution.
14511 : : */
1196 tgl@sss.pgh.pa.us 14512 : 328 : raw_parsetree_list = raw_parser(cmd, RAW_PARSE_DEFAULT);
7284 14513 : 328 : querytree_list = NIL;
14514 [ + - + + : 656 : foreach(list_item, raw_parsetree_list)
+ + ]
14515 : : {
2561 14516 : 328 : RawStmt *rs = lfirst_node(RawStmt, list_item);
2647 14517 : 328 : Node *stmt = rs->stmt;
14518 : :
6140 14519 [ + + ]: 328 : if (IsA(stmt, IndexStmt))
14520 : 106 : querytree_list = lappend(querytree_list,
3709 rhaas@postgresql.org 14521 : 106 : transformIndexStmt(oldRelId,
14522 : : (IndexStmt *) stmt,
14523 : : cmd));
6140 tgl@sss.pgh.pa.us 14524 [ + + ]: 222 : else if (IsA(stmt, AlterTableStmt))
14525 : : {
14526 : : List *beforeStmts;
14527 : : List *afterStmts;
14528 : :
1551 14529 : 208 : stmt = (Node *) transformAlterTableStmt(oldRelId,
14530 : : (AlterTableStmt *) stmt,
14531 : : cmd,
14532 : : &beforeStmts,
14533 : : &afterStmts);
14534 : 208 : querytree_list = list_concat(querytree_list, beforeStmts);
14535 : 208 : querytree_list = lappend(querytree_list, stmt);
14536 : 208 : querytree_list = list_concat(querytree_list, afterStmts);
14537 : : }
1115 tomas.vondra@postgre 14538 [ + + ]: 14 : else if (IsA(stmt, CreateStatsStmt))
14539 : 7 : querytree_list = lappend(querytree_list,
14540 : 7 : transformStatsStmt(oldRelId,
14541 : : (CreateStatsStmt *) stmt,
14542 : : cmd));
14543 : : else
6140 tgl@sss.pgh.pa.us 14544 : 7 : querytree_list = lappend(querytree_list, stmt);
14545 : : }
14546 : :
14547 : : /* Caller should already have acquired whatever lock we need. */
3709 rhaas@postgresql.org 14548 : 328 : rel = relation_open(oldRelId, NoLock);
14549 : :
14550 : : /*
14551 : : * Attach each generated command to the proper place in the work queue.
14552 : : * Note this could result in creation of entirely new work-queue entries.
14553 : : *
14554 : : * Also note that we have to tweak the command subtypes, because it turns
14555 : : * out that re-creation of indexes and constraints has to act a bit
14556 : : * differently from initial creation.
14557 : : */
7284 tgl@sss.pgh.pa.us 14558 [ + - + + : 656 : foreach(list_item, querytree_list)
+ + ]
14559 : : {
6140 14560 : 328 : Node *stm = (Node *) lfirst(list_item);
14561 : : AlteredTableInfo *tab;
14562 : :
3197 heikki.linnakangas@i 14563 : 328 : tab = ATGetQueueEntry(wqueue, rel);
14564 : :
14565 [ + + ]: 328 : if (IsA(stm, IndexStmt))
14566 : : {
14567 : 106 : IndexStmt *stmt = (IndexStmt *) stm;
14568 : : AlterTableCmd *newcmd;
14569 : :
14570 [ + + ]: 106 : if (!rewrite)
14571 : 27 : TryReuseIndex(oldId, stmt);
1816 alvherre@alvh.no-ip. 14572 : 106 : stmt->reset_default_tblspc = true;
14573 : : /* keep the index's comment */
3197 heikki.linnakangas@i 14574 : 106 : stmt->idxcomment = GetComment(oldId, RelationRelationId, 0);
14575 : :
14576 : 106 : newcmd = makeNode(AlterTableCmd);
14577 : 106 : newcmd->subtype = AT_ReAddIndex;
14578 : 106 : newcmd->def = (Node *) stmt;
14579 : 106 : tab->subcmds[AT_PASS_OLD_INDEX] =
14580 : 106 : lappend(tab->subcmds[AT_PASS_OLD_INDEX], newcmd);
14581 : : }
14582 [ + + ]: 222 : else if (IsA(stm, AlterTableStmt))
14583 : : {
14584 : 208 : AlterTableStmt *stmt = (AlterTableStmt *) stm;
14585 : : ListCell *lcmd;
14586 : :
14587 [ + - + + : 494 : foreach(lcmd, stmt->cmds)
+ + ]
14588 : : {
1000 peter@eisentraut.org 14589 : 286 : AlterTableCmd *cmd = lfirst_node(AlterTableCmd, lcmd);
14590 : :
3197 heikki.linnakangas@i 14591 [ + + ]: 286 : if (cmd->subtype == AT_AddIndex)
14592 : : {
14593 : : IndexStmt *indstmt;
14594 : : Oid indoid;
14595 : :
2635 andres@anarazel.de 14596 : 111 : indstmt = castNode(IndexStmt, cmd->def);
3197 heikki.linnakangas@i 14597 : 111 : indoid = get_constraint_index(oldId);
14598 : :
4654 rhaas@postgresql.org 14599 [ + + ]: 111 : if (!rewrite)
3197 heikki.linnakangas@i 14600 : 24 : TryReuseIndex(indoid, indstmt);
14601 : : /* keep any comment on the index */
14602 : 111 : indstmt->idxcomment = GetComment(indoid,
14603 : : RelationRelationId, 0);
1816 alvherre@alvh.no-ip. 14604 : 111 : indstmt->reset_default_tblspc = true;
14605 : :
3197 heikki.linnakangas@i 14606 : 111 : cmd->subtype = AT_ReAddIndex;
7168 bruce@momjian.us 14607 : 111 : tab->subcmds[AT_PASS_OLD_INDEX] =
3197 heikki.linnakangas@i 14608 : 111 : lappend(tab->subcmds[AT_PASS_OLD_INDEX], cmd);
14609 : :
14610 : : /* recreate any comment on the constraint */
14611 : 111 : RebuildConstraintComment(tab,
14612 : : AT_PASS_OLD_INDEX,
14613 : : oldId,
14614 : : rel,
14615 : : NIL,
2356 tgl@sss.pgh.pa.us 14616 : 111 : indstmt->idxname);
14617 : : }
3197 heikki.linnakangas@i 14618 [ + + ]: 175 : else if (cmd->subtype == AT_AddConstraint)
14619 : : {
2356 tgl@sss.pgh.pa.us 14620 : 97 : Constraint *con = castNode(Constraint, cmd->def);
14621 : :
3197 heikki.linnakangas@i 14622 : 97 : con->old_pktable_oid = refRelId;
14623 : : /* rewriting neither side of a FK */
14624 [ + + ]: 97 : if (con->contype == CONSTR_FOREIGN &&
14625 [ + + + - ]: 36 : !rewrite && tab->rewrite == 0)
14626 : 3 : TryReuseForeignKey(oldId, con);
1816 alvherre@alvh.no-ip. 14627 : 97 : con->reset_default_tblspc = true;
3197 heikki.linnakangas@i 14628 : 97 : cmd->subtype = AT_ReAddConstraint;
14629 : 97 : tab->subcmds[AT_PASS_OLD_CONSTR] =
14630 : 97 : lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
14631 : :
14632 : : /* recreate any comment on the constraint */
14633 : 97 : RebuildConstraintComment(tab,
14634 : : AT_PASS_OLD_CONSTR,
14635 : : oldId,
14636 : : rel,
14637 : : NIL,
2356 tgl@sss.pgh.pa.us 14638 : 97 : con->conname);
14639 : : }
233 alvherre@alvh.no-ip. 14640 [ - + ]:GNC 78 : else if (cmd->subtype == AT_SetAttNotNull)
14641 : : {
14642 : : /*
14643 : : * The parser will create AT_AttSetNotNull subcommands for
14644 : : * columns of PRIMARY KEY indexes/constraints, but we need
14645 : : * not do anything with them here, because the columns'
14646 : : * NOT NULL marks will already have been propagated into
14647 : : * the new table definition.
14648 : : */
14649 : : }
14650 : : else
3068 tgl@sss.pgh.pa.us 14651 [ # # ]:UBC 0 : elog(ERROR, "unexpected statement subtype: %d",
14652 : : (int) cmd->subtype);
14653 : : }
14654 : : }
2356 tgl@sss.pgh.pa.us 14655 [ + + ]:CBC 14 : else if (IsA(stm, AlterDomainStmt))
14656 : : {
14657 : 7 : AlterDomainStmt *stmt = (AlterDomainStmt *) stm;
14658 : :
14659 [ + - ]: 7 : if (stmt->subtype == 'C') /* ADD CONSTRAINT */
14660 : : {
14661 : 7 : Constraint *con = castNode(Constraint, stmt->def);
14662 : 7 : AlterTableCmd *cmd = makeNode(AlterTableCmd);
14663 : :
14664 : 7 : cmd->subtype = AT_ReAddDomainConstraint;
14665 : 7 : cmd->def = (Node *) stmt;
14666 : 7 : tab->subcmds[AT_PASS_OLD_CONSTR] =
14667 : 7 : lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
14668 : :
14669 : : /* recreate any comment on the constraint */
14670 : 7 : RebuildConstraintComment(tab,
14671 : : AT_PASS_OLD_CONSTR,
14672 : : oldId,
14673 : : NULL,
14674 : : stmt->typeName,
14675 : 7 : con->conname);
14676 : : }
14677 : : else
2356 tgl@sss.pgh.pa.us 14678 [ # # ]:UBC 0 : elog(ERROR, "unexpected statement subtype: %d",
14679 : : (int) stmt->subtype);
14680 : : }
1115 tomas.vondra@postgre 14681 [ + - ]:CBC 7 : else if (IsA(stm, CreateStatsStmt))
14682 : : {
1068 tgl@sss.pgh.pa.us 14683 : 7 : CreateStatsStmt *stmt = (CreateStatsStmt *) stm;
14684 : : AlterTableCmd *newcmd;
14685 : :
14686 : : /* keep the statistics object's comment */
1115 tomas.vondra@postgre 14687 : 7 : stmt->stxcomment = GetComment(oldId, StatisticExtRelationId, 0);
14688 : :
14689 : 7 : newcmd = makeNode(AlterTableCmd);
14690 : 7 : newcmd->subtype = AT_ReAddStatistics;
14691 : 7 : newcmd->def = (Node *) stmt;
14692 : 7 : tab->subcmds[AT_PASS_MISC] =
14693 : 7 : lappend(tab->subcmds[AT_PASS_MISC], newcmd);
14694 : : }
14695 : : else
3197 heikki.linnakangas@i 14696 [ # # ]:UBC 0 : elog(ERROR, "unexpected statement type: %d",
14697 : : (int) nodeTag(stm));
14698 : : }
14699 : :
3709 rhaas@postgresql.org 14700 :CBC 328 : relation_close(rel, NoLock);
8024 tgl@sss.pgh.pa.us 14701 : 328 : }
14702 : :
14703 : : /*
14704 : : * Subroutine for ATPostAlterTypeParse() to recreate any existing comment
14705 : : * for a table or domain constraint that is being rebuilt.
14706 : : *
14707 : : * objid is the OID of the constraint.
14708 : : * Pass "rel" for a table constraint, or "domname" (domain's qualified name
14709 : : * as a string list) for a domain constraint.
14710 : : * (We could dig that info, as well as the conname, out of the pg_constraint
14711 : : * entry; but callers already have them so might as well pass them.)
14712 : : */
14713 : : static void
104 peter@eisentraut.org 14714 :GNC 215 : RebuildConstraintComment(AlteredTableInfo *tab, AlterTablePass pass, Oid objid,
14715 : : Relation rel, List *domname,
14716 : : const char *conname)
14717 : : {
14718 : : CommentStmt *cmd;
14719 : : char *comment_str;
14720 : : AlterTableCmd *newcmd;
14721 : :
14722 : : /* Look for comment for object wanted, and leave if none */
3197 heikki.linnakangas@i 14723 :CBC 215 : comment_str = GetComment(objid, ConstraintRelationId, 0);
14724 [ + + ]: 215 : if (comment_str == NULL)
14725 : 182 : return;
14726 : :
14727 : : /* Build CommentStmt node, copying all input data for safety */
14728 : 33 : cmd = makeNode(CommentStmt);
2356 tgl@sss.pgh.pa.us 14729 [ + + ]: 33 : if (rel)
14730 : : {
14731 : 27 : cmd->objtype = OBJECT_TABCONSTRAINT;
14732 : 27 : cmd->object = (Node *)
14733 : 27 : list_make3(makeString(get_namespace_name(RelationGetNamespace(rel))),
14734 : : makeString(pstrdup(RelationGetRelationName(rel))),
14735 : : makeString(pstrdup(conname)));
14736 : : }
14737 : : else
14738 : : {
14739 : 6 : cmd->objtype = OBJECT_DOMCONSTRAINT;
14740 : 6 : cmd->object = (Node *)
14741 : 6 : list_make2(makeTypeNameFromNameList(copyObject(domname)),
14742 : : makeString(pstrdup(conname)));
14743 : : }
3197 heikki.linnakangas@i 14744 : 33 : cmd->comment = comment_str;
14745 : :
14746 : : /* Append it to list of commands */
14747 : 33 : newcmd = makeNode(AlterTableCmd);
14748 : 33 : newcmd->subtype = AT_ReAddComment;
14749 : 33 : newcmd->def = (Node *) cmd;
14750 : 33 : tab->subcmds[pass] = lappend(tab->subcmds[pass], newcmd);
14751 : : }
14752 : :
14753 : : /*
14754 : : * Subroutine for ATPostAlterTypeParse(). Calls out to CheckIndexCompatible()
14755 : : * for the real analysis, then mutates the IndexStmt based on that verdict.
14756 : : */
14757 : : static void
4654 rhaas@postgresql.org 14758 : 51 : TryReuseIndex(Oid oldId, IndexStmt *stmt)
14759 : : {
14760 [ + - ]: 51 : if (CheckIndexCompatible(oldId,
14761 : 51 : stmt->accessMethod,
4654 rhaas@postgresql.org 14762 :GIC 51 : stmt->indexParams,
81 peter@eisentraut.org 14763 :GNC 51 : stmt->excludeOpNames,
14764 : 51 : stmt->iswithoutoverlaps))
14765 : : {
4326 bruce@momjian.us 14766 :CBC 51 : Relation irel = index_open(oldId, NoLock);
14767 : :
14768 : : /* If it's a partitioned index, there is no storage to share. */
1815 tgl@sss.pgh.pa.us 14769 [ + + ]: 51 : if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
14770 : : {
648 rhaas@postgresql.org 14771 : 36 : stmt->oldNumber = irel->rd_locator.relNumber;
1471 noah@leadboat.com 14772 : 36 : stmt->oldCreateSubid = irel->rd_createSubid;
648 rhaas@postgresql.org 14773 : 36 : stmt->oldFirstRelfilelocatorSubid = irel->rd_firstRelfilelocatorSubid;
14774 : : }
4654 14775 : 51 : index_close(irel, NoLock);
14776 : : }
14777 : 51 : }
14778 : :
14779 : : /*
14780 : : * Subroutine for ATPostAlterTypeParse().
14781 : : *
14782 : : * Stash the old P-F equality operator into the Constraint node, for possible
14783 : : * use by ATAddForeignKeyConstraint() in determining whether revalidation of
14784 : : * this constraint can be skipped.
14785 : : */
14786 : : static void
4430 alvherre@alvh.no-ip. 14787 : 3 : TryReuseForeignKey(Oid oldId, Constraint *con)
14788 : : {
14789 : : HeapTuple tup;
14790 : : Datum adatum;
14791 : : ArrayType *arr;
14792 : : Oid *rawarr;
14793 : : int numkeys;
14794 : : int i;
14795 : :
14796 [ - + ]: 3 : Assert(con->contype == CONSTR_FOREIGN);
4326 bruce@momjian.us 14797 [ - + ]: 3 : Assert(con->old_conpfeqop == NIL); /* already prepared this node */
14798 : :
4430 alvherre@alvh.no-ip. 14799 : 3 : tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(oldId));
14800 [ - + ]: 3 : if (!HeapTupleIsValid(tup)) /* should not happen */
4430 alvherre@alvh.no-ip. 14801 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for constraint %u", oldId);
14802 : :
386 dgustafsson@postgres 14803 :CBC 3 : adatum = SysCacheGetAttrNotNull(CONSTROID, tup,
14804 : : Anum_pg_constraint_conpfeqop);
4430 alvherre@alvh.no-ip. 14805 : 3 : arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
14806 : 3 : numkeys = ARR_DIMS(arr)[0];
14807 : : /* test follows the one in ri_FetchConstraintInfo() */
14808 [ + - ]: 3 : if (ARR_NDIM(arr) != 1 ||
14809 [ + - ]: 3 : ARR_HASNULL(arr) ||
14810 [ - + ]: 3 : ARR_ELEMTYPE(arr) != OIDOID)
4430 alvherre@alvh.no-ip. 14811 [ # # ]:UBC 0 : elog(ERROR, "conpfeqop is not a 1-D Oid array");
4430 alvherre@alvh.no-ip. 14812 [ - + ]:CBC 3 : rawarr = (Oid *) ARR_DATA_PTR(arr);
14813 : :
14814 : : /* stash a List of the operator Oids in our Constraint node */
14815 [ + + ]: 6 : for (i = 0; i < numkeys; i++)
1734 tgl@sss.pgh.pa.us 14816 : 3 : con->old_conpfeqop = lappend_oid(con->old_conpfeqop, rawarr[i]);
14817 : :
4430 alvherre@alvh.no-ip. 14818 : 3 : ReleaseSysCache(tup);
14819 : 3 : }
14820 : :
14821 : : /*
14822 : : * ALTER COLUMN .. OPTIONS ( ... )
14823 : : *
14824 : : * Returns the address of the modified column
14825 : : */
14826 : : static ObjectAddress
1756 tgl@sss.pgh.pa.us 14827 : 82 : ATExecAlterColumnGenericOptions(Relation rel,
14828 : : const char *colName,
14829 : : List *options,
14830 : : LOCKMODE lockmode)
14831 : : {
14832 : : Relation ftrel;
14833 : : Relation attrel;
14834 : : ForeignServer *server;
14835 : : ForeignDataWrapper *fdw;
14836 : : HeapTuple tuple;
14837 : : HeapTuple newtuple;
14838 : : bool isnull;
14839 : : Datum repl_val[Natts_pg_attribute];
14840 : : bool repl_null[Natts_pg_attribute];
14841 : : bool repl_repl[Natts_pg_attribute];
14842 : : Datum datum;
14843 : : Form_pg_foreign_table fttableform;
14844 : : Form_pg_attribute atttableform;
14845 : : AttrNumber attnum;
14846 : : ObjectAddress address;
14847 : :
14848 [ - + ]: 82 : if (options == NIL)
1756 tgl@sss.pgh.pa.us 14849 :UBC 0 : return InvalidObjectAddress;
14850 : :
14851 : : /* First, determine FDW validator associated to the foreign table. */
1756 tgl@sss.pgh.pa.us 14852 :CBC 82 : ftrel = table_open(ForeignTableRelationId, AccessShareLock);
269 michael@paquier.xyz 14853 :GNC 82 : tuple = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(rel->rd_id));
1756 tgl@sss.pgh.pa.us 14854 [ - + ]:CBC 82 : if (!HeapTupleIsValid(tuple))
1756 tgl@sss.pgh.pa.us 14855 [ # # ]:UBC 0 : ereport(ERROR,
14856 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
14857 : : errmsg("foreign table \"%s\" does not exist",
14858 : : RelationGetRelationName(rel))));
1756 tgl@sss.pgh.pa.us 14859 :CBC 82 : fttableform = (Form_pg_foreign_table) GETSTRUCT(tuple);
14860 : 82 : server = GetForeignServer(fttableform->ftserver);
14861 : 82 : fdw = GetForeignDataWrapper(server->fdwid);
14862 : :
14863 : 82 : table_close(ftrel, AccessShareLock);
14864 : 82 : ReleaseSysCache(tuple);
14865 : :
14866 : 82 : attrel = table_open(AttributeRelationId, RowExclusiveLock);
14867 : 82 : tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
14868 [ - + ]: 82 : if (!HeapTupleIsValid(tuple))
1756 tgl@sss.pgh.pa.us 14869 [ # # ]:UBC 0 : ereport(ERROR,
14870 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
14871 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
14872 : : colName, RelationGetRelationName(rel))));
14873 : :
14874 : : /* Prevent them from altering a system attribute */
1756 tgl@sss.pgh.pa.us 14875 :CBC 82 : atttableform = (Form_pg_attribute) GETSTRUCT(tuple);
14876 : 82 : attnum = atttableform->attnum;
14877 [ + + ]: 82 : if (attnum <= 0)
14878 [ + - ]: 3 : ereport(ERROR,
14879 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
14880 : : errmsg("cannot alter system column \"%s\"", colName)));
14881 : :
14882 : :
14883 : : /* Initialize buffers for new tuple values */
14884 : 79 : memset(repl_val, 0, sizeof(repl_val));
14885 : 79 : memset(repl_null, false, sizeof(repl_null));
14886 : 79 : memset(repl_repl, false, sizeof(repl_repl));
14887 : :
14888 : : /* Extract the current options */
14889 : 79 : datum = SysCacheGetAttr(ATTNAME,
14890 : : tuple,
14891 : : Anum_pg_attribute_attfdwoptions,
14892 : : &isnull);
14893 [ + + ]: 79 : if (isnull)
14894 : 74 : datum = PointerGetDatum(NULL);
14895 : :
14896 : : /* Transform the options */
14897 : 79 : datum = transformGenericOptions(AttributeRelationId,
14898 : : datum,
14899 : : options,
14900 : : fdw->fdwvalidator);
14901 : :
14902 [ + - ]: 79 : if (PointerIsValid(DatumGetPointer(datum)))
14903 : 79 : repl_val[Anum_pg_attribute_attfdwoptions - 1] = datum;
14904 : : else
1756 tgl@sss.pgh.pa.us 14905 :UBC 0 : repl_null[Anum_pg_attribute_attfdwoptions - 1] = true;
14906 : :
1756 tgl@sss.pgh.pa.us 14907 :CBC 79 : repl_repl[Anum_pg_attribute_attfdwoptions - 1] = true;
14908 : :
14909 : : /* Everything looks good - update the tuple */
14910 : :
14911 : 79 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(attrel),
14912 : : repl_val, repl_null, repl_repl);
14913 : :
14914 : 79 : CatalogTupleUpdate(attrel, &newtuple->t_self, newtuple);
14915 : :
14916 [ - + ]: 79 : InvokeObjectPostAlterHook(RelationRelationId,
14917 : : RelationGetRelid(rel),
14918 : : atttableform->attnum);
14919 : 79 : ObjectAddressSubSet(address, RelationRelationId,
14920 : : RelationGetRelid(rel), attnum);
14921 : :
14922 : 79 : ReleaseSysCache(tuple);
14923 : :
14924 : 79 : table_close(attrel, RowExclusiveLock);
14925 : :
14926 : 79 : heap_freetuple(newtuple);
14927 : :
14928 : 79 : return address;
14929 : : }
14930 : :
14931 : : /*
14932 : : * ALTER TABLE OWNER
14933 : : *
14934 : : * recursing is true if we are recursing from a table to its indexes,
14935 : : * sequences, or toast table. We don't allow the ownership of those things to
14936 : : * be changed separately from the parent table. Also, we can skip permission
14937 : : * checks (this is necessary not just an optimization, else we'd fail to
14938 : : * handle toast tables properly).
14939 : : *
14940 : : * recursing is also true if ALTER TYPE OWNER is calling us to fix up a
14941 : : * free-standing composite type.
14942 : : */
14943 : : void
5009 simon@2ndQuadrant.co 14944 : 989 : ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lockmode)
14945 : : {
14946 : : Relation target_rel;
14947 : : Relation class_rel;
14948 : : HeapTuple tuple;
14949 : : Form_pg_class tuple_class;
14950 : :
14951 : : /*
14952 : : * Get exclusive lock till end of transaction on the target table. Use
14953 : : * relation_open so that we can work on indexes and sequences.
14954 : : */
14955 : 989 : target_rel = relation_open(relationOid, lockmode);
14956 : :
14957 : : /* Get its pg_class tuple, too */
1910 andres@anarazel.de 14958 : 989 : class_rel = table_open(RelationRelationId, RowExclusiveLock);
14959 : :
5173 rhaas@postgresql.org 14960 : 989 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relationOid));
8024 tgl@sss.pgh.pa.us 14961 [ - + ]: 989 : if (!HeapTupleIsValid(tuple))
7574 tgl@sss.pgh.pa.us 14962 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", relationOid);
8024 tgl@sss.pgh.pa.us 14963 :CBC 989 : tuple_class = (Form_pg_class) GETSTRUCT(tuple);
14964 : :
14965 : : /* Can we change the ownership of this tuple? */
7574 14966 [ + + + + : 989 : switch (tuple_class->relkind)
+ + - ]
14967 : : {
14968 : 870 : case RELKIND_RELATION:
14969 : : case RELKIND_VIEW:
14970 : : case RELKIND_MATVIEW:
14971 : : case RELKIND_FOREIGN_TABLE:
14972 : : case RELKIND_PARTITIONED_TABLE:
14973 : : /* ok to change owner */
14974 : 870 : break;
6828 14975 : 42 : case RELKIND_INDEX:
6810 14976 [ - + ]: 42 : if (!recursing)
14977 : : {
14978 : : /*
14979 : : * Because ALTER INDEX OWNER used to be allowed, and in fact
14980 : : * is generated by old versions of pg_dump, we give a warning
14981 : : * and do nothing rather than erroring out. Also, to avoid
14982 : : * unnecessary chatter while restoring those old dumps, say
14983 : : * nothing at all if the command would be a no-op anyway.
14984 : : */
6810 tgl@sss.pgh.pa.us 14985 [ # # ]:UBC 0 : if (tuple_class->relowner != newOwnerId)
14986 [ # # ]: 0 : ereport(WARNING,
14987 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
14988 : : errmsg("cannot change owner of index \"%s\"",
14989 : : NameStr(tuple_class->relname)),
14990 : : errhint("Change the ownership of the index's table instead.")));
14991 : : /* quick hack to exit via the no-op path */
14992 : 0 : newOwnerId = tuple_class->relowner;
14993 : : }
6810 tgl@sss.pgh.pa.us 14994 :CBC 42 : break;
2277 alvherre@alvh.no-ip. 14995 : 10 : case RELKIND_PARTITIONED_INDEX:
14996 [ + - ]: 10 : if (recursing)
14997 : 10 : break;
2277 alvherre@alvh.no-ip. 14998 [ # # ]:UBC 0 : ereport(ERROR,
14999 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
15000 : : errmsg("cannot change owner of index \"%s\"",
15001 : : NameStr(tuple_class->relname)),
15002 : : errhint("Change the ownership of the index's table instead.")));
15003 : : break;
6446 tgl@sss.pgh.pa.us 15004 :CBC 46 : case RELKIND_SEQUENCE:
15005 [ + + ]: 46 : if (!recursing &&
15006 [ - + ]: 31 : tuple_class->relowner != newOwnerId)
15007 : : {
15008 : : /* if it's an owned sequence, disallow changing it by itself */
15009 : : Oid tableId;
15010 : : int32 colId;
15011 : :
2565 peter_e@gmx.net 15012 [ # # # # ]:UBC 0 : if (sequenceIsOwned(relationOid, DEPENDENCY_AUTO, &tableId, &colId) ||
15013 : 0 : sequenceIsOwned(relationOid, DEPENDENCY_INTERNAL, &tableId, &colId))
6446 tgl@sss.pgh.pa.us 15014 [ # # ]: 0 : ereport(ERROR,
15015 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15016 : : errmsg("cannot change owner of sequence \"%s\"",
15017 : : NameStr(tuple_class->relname)),
15018 : : errdetail("Sequence \"%s\" is linked to table \"%s\".",
15019 : : NameStr(tuple_class->relname),
15020 : : get_rel_name(tableId))));
15021 : : }
6446 tgl@sss.pgh.pa.us 15022 :CBC 46 : break;
6183 15023 : 3 : case RELKIND_COMPOSITE_TYPE:
6042 15024 [ + - ]: 3 : if (recursing)
15025 : 3 : break;
6042 tgl@sss.pgh.pa.us 15026 [ # # ]:UBC 0 : ereport(ERROR,
15027 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
15028 : : errmsg("\"%s\" is a composite type",
15029 : : NameStr(tuple_class->relname)),
15030 : : /* translator: %s is an SQL ALTER command */
15031 : : errhint("Use %s instead.",
15032 : : "ALTER TYPE")));
15033 : : break;
6042 tgl@sss.pgh.pa.us 15034 :CBC 18 : case RELKIND_TOASTVALUE:
6828 15035 [ + - ]: 18 : if (recursing)
15036 : 18 : break;
15037 : : /* FALL THRU */
15038 : : default:
7574 tgl@sss.pgh.pa.us 15039 [ # # ]:UBC 0 : ereport(ERROR,
15040 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
15041 : : errmsg("cannot change owner of relation \"%s\"",
15042 : : NameStr(tuple_class->relname)),
15043 : : errdetail_relkind_not_supported(tuple_class->relkind)));
15044 : : }
15045 : :
15046 : : /*
15047 : : * If the new owner is the same as the existing owner, consider the
15048 : : * command to have succeeded. This is for dump restoration purposes.
15049 : : */
6865 tgl@sss.pgh.pa.us 15050 [ + + ]:CBC 989 : if (tuple_class->relowner != newOwnerId)
15051 : : {
15052 : : Datum repl_val[Natts_pg_class];
15053 : : bool repl_null[Natts_pg_class];
15054 : : bool repl_repl[Natts_pg_class];
15055 : : Acl *newAcl;
15056 : : Datum aclDatum;
15057 : : bool isNull;
15058 : : HeapTuple newtuple;
15059 : :
15060 : : /* skip permission checks when recursing to index or toast table */
6828 15061 [ + + ]: 225 : if (!recursing)
15062 : : {
15063 : : /* Superusers can always do it */
6810 15064 [ + + ]: 137 : if (!superuser())
15065 : : {
6756 bruce@momjian.us 15066 : 21 : Oid namespaceOid = tuple_class->relnamespace;
15067 : : AclResult aclresult;
15068 : :
15069 : : /* Otherwise, must be owner of the existing object */
518 peter@eisentraut.org 15070 [ - + ]: 21 : if (!object_ownercheck(RelationRelationId, relationOid, GetUserId()))
2325 peter_e@gmx.net 15071 :UBC 0 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relationOid)),
6810 tgl@sss.pgh.pa.us 15072 : 0 : RelationGetRelationName(target_rel));
15073 : :
15074 : : /* Must be able to become new owner */
513 rhaas@postgresql.org 15075 :CBC 21 : check_can_set_role(GetUserId(), newOwnerId);
15076 : :
15077 : : /* New owner must have CREATE privilege on namespace */
518 peter@eisentraut.org 15078 : 15 : aclresult = object_aclcheck(NamespaceRelationId, namespaceOid, newOwnerId,
15079 : : ACL_CREATE);
6810 tgl@sss.pgh.pa.us 15080 [ - + ]: 15 : if (aclresult != ACLCHECK_OK)
2325 peter_e@gmx.net 15081 :UBC 0 : aclcheck_error(aclresult, OBJECT_SCHEMA,
6810 tgl@sss.pgh.pa.us 15082 : 0 : get_namespace_name(namespaceOid));
15083 : : }
15084 : : }
15085 : :
5642 tgl@sss.pgh.pa.us 15086 :CBC 219 : memset(repl_null, false, sizeof(repl_null));
15087 : 219 : memset(repl_repl, false, sizeof(repl_repl));
15088 : :
15089 : 219 : repl_repl[Anum_pg_class_relowner - 1] = true;
6865 15090 : 219 : repl_val[Anum_pg_class_relowner - 1] = ObjectIdGetDatum(newOwnerId);
15091 : :
15092 : : /*
15093 : : * Determine the modified ACL for the new owner. This is only
15094 : : * necessary when the ACL is non-null.
15095 : : */
7196 15096 : 219 : aclDatum = SysCacheGetAttr(RELOID, tuple,
15097 : : Anum_pg_class_relacl,
15098 : : &isNull);
15099 [ + + ]: 219 : if (!isNull)
15100 : : {
15101 : 16 : newAcl = aclnewowner(DatumGetAclP(aclDatum),
15102 : : tuple_class->relowner, newOwnerId);
5642 15103 : 16 : repl_repl[Anum_pg_class_relacl - 1] = true;
7196 15104 : 16 : repl_val[Anum_pg_class_relacl - 1] = PointerGetDatum(newAcl);
15105 : : }
15106 : :
5642 15107 : 219 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(class_rel), repl_val, repl_null, repl_repl);
15108 : :
2630 alvherre@alvh.no-ip. 15109 : 219 : CatalogTupleUpdate(class_rel, &newtuple->t_self, newtuple);
15110 : :
7196 tgl@sss.pgh.pa.us 15111 : 219 : heap_freetuple(newtuple);
15112 : :
15113 : : /*
15114 : : * We must similarly update any per-column ACLs to reflect the new
15115 : : * owner; for neatness reasons that's split out as a subroutine.
15116 : : */
4498 15117 : 219 : change_owner_fix_column_acls(relationOid,
15118 : : tuple_class->relowner,
15119 : : newOwnerId);
15120 : :
15121 : : /*
15122 : : * Update owner dependency reference, if any. A composite type has
15123 : : * none, because it's tracked for the pg_type entry instead of here;
15124 : : * indexes and TOAST tables don't have their own entries either.
15125 : : */
6183 15126 [ + + ]: 219 : if (tuple_class->relkind != RELKIND_COMPOSITE_TYPE &&
6180 15127 [ + + ]: 216 : tuple_class->relkind != RELKIND_INDEX &&
2277 alvherre@alvh.no-ip. 15128 [ + + ]: 174 : tuple_class->relkind != RELKIND_PARTITIONED_INDEX &&
6180 tgl@sss.pgh.pa.us 15129 [ + + ]: 164 : tuple_class->relkind != RELKIND_TOASTVALUE)
6183 15130 : 146 : changeDependencyOnOwner(RelationRelationId, relationOid,
15131 : : newOwnerId);
15132 : :
15133 : : /*
15134 : : * Also change the ownership of the table's row type, if it has one
15135 : : */
1377 15136 [ + + ]: 219 : if (OidIsValid(tuple_class->reltype))
3041 alvherre@alvh.no-ip. 15137 : 140 : AlterTypeOwnerInternal(tuple_class->reltype, newOwnerId);
15138 : :
15139 : : /*
15140 : : * If we are operating on a table or materialized view, also change
15141 : : * the ownership of any indexes and sequences that belong to the
15142 : : * relation, as well as its toast table (if it has one).
15143 : : */
7233 tgl@sss.pgh.pa.us 15144 [ + + ]: 219 : if (tuple_class->relkind == RELKIND_RELATION ||
2277 alvherre@alvh.no-ip. 15145 [ + + ]: 113 : tuple_class->relkind == RELKIND_PARTITIONED_TABLE ||
4060 kgrittn@postgresql.o 15146 [ + - ]: 94 : tuple_class->relkind == RELKIND_MATVIEW ||
7233 tgl@sss.pgh.pa.us 15147 [ + + ]: 94 : tuple_class->relkind == RELKIND_TOASTVALUE)
15148 : : {
15149 : : List *index_oid_list;
15150 : : ListCell *i;
15151 : :
15152 : : /* Find all the indexes belonging to this relation */
15153 : 143 : index_oid_list = RelationGetIndexList(target_rel);
15154 : :
15155 : : /* For each index, recursively change its ownership */
15156 [ + + + + : 195 : foreach(i, index_oid_list)
+ + ]
5009 simon@2ndQuadrant.co 15157 : 52 : ATExecChangeOwner(lfirst_oid(i), newOwnerId, true, lockmode);
15158 : :
7233 tgl@sss.pgh.pa.us 15159 : 143 : list_free(index_oid_list);
15160 : : }
15161 : :
15162 : : /* If it has a toast table, recurse to change its ownership */
2131 peter_e@gmx.net 15163 [ + + ]: 219 : if (tuple_class->reltoastrelid != InvalidOid)
15164 : 18 : ATExecChangeOwner(tuple_class->reltoastrelid, newOwnerId,
15165 : : true, lockmode);
15166 : :
15167 : : /* If it has dependent sequences, recurse to change them too */
15168 : 219 : change_owner_recurse_to_sequences(relationOid, newOwnerId, lockmode);
15169 : : }
15170 : :
4046 rhaas@postgresql.org 15171 [ - + ]: 983 : InvokeObjectPostAlterHook(RelationRelationId, relationOid, 0);
15172 : :
7196 tgl@sss.pgh.pa.us 15173 : 983 : ReleaseSysCache(tuple);
1910 andres@anarazel.de 15174 : 983 : table_close(class_rel, RowExclusiveLock);
7972 tgl@sss.pgh.pa.us 15175 : 983 : relation_close(target_rel, NoLock);
8026 bruce@momjian.us 15176 : 983 : }
15177 : :
15178 : : /*
15179 : : * change_owner_fix_column_acls
15180 : : *
15181 : : * Helper function for ATExecChangeOwner. Scan the columns of the table
15182 : : * and fix any non-null column ACLs to reflect the new owner.
15183 : : */
15184 : : static void
4498 tgl@sss.pgh.pa.us 15185 : 219 : change_owner_fix_column_acls(Oid relationOid, Oid oldOwnerId, Oid newOwnerId)
15186 : : {
15187 : : Relation attRelation;
15188 : : SysScanDesc scan;
15189 : : ScanKeyData key[1];
15190 : : HeapTuple attributeTuple;
15191 : :
1910 andres@anarazel.de 15192 : 219 : attRelation = table_open(AttributeRelationId, RowExclusiveLock);
4498 tgl@sss.pgh.pa.us 15193 : 219 : ScanKeyInit(&key[0],
15194 : : Anum_pg_attribute_attrelid,
15195 : : BTEqualStrategyNumber, F_OIDEQ,
15196 : : ObjectIdGetDatum(relationOid));
15197 : 219 : scan = systable_beginscan(attRelation, AttributeRelidNumIndexId,
15198 : : true, NULL, 1, key);
15199 [ + + ]: 1506 : while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
15200 : : {
15201 : 1287 : Form_pg_attribute att = (Form_pg_attribute) GETSTRUCT(attributeTuple);
15202 : : Datum repl_val[Natts_pg_attribute];
15203 : : bool repl_null[Natts_pg_attribute];
15204 : : bool repl_repl[Natts_pg_attribute];
15205 : : Acl *newAcl;
15206 : : Datum aclDatum;
15207 : : bool isNull;
15208 : : HeapTuple newtuple;
15209 : :
15210 : : /* Ignore dropped columns */
15211 [ - + ]: 1287 : if (att->attisdropped)
4498 tgl@sss.pgh.pa.us 15212 :UBC 0 : continue;
15213 : :
4498 tgl@sss.pgh.pa.us 15214 :CBC 1287 : aclDatum = heap_getattr(attributeTuple,
15215 : : Anum_pg_attribute_attacl,
15216 : : RelationGetDescr(attRelation),
15217 : : &isNull);
15218 : : /* Null ACLs do not require changes */
15219 [ + - ]: 1287 : if (isNull)
15220 : 1287 : continue;
15221 : :
4498 tgl@sss.pgh.pa.us 15222 :UBC 0 : memset(repl_null, false, sizeof(repl_null));
15223 : 0 : memset(repl_repl, false, sizeof(repl_repl));
15224 : :
15225 : 0 : newAcl = aclnewowner(DatumGetAclP(aclDatum),
15226 : : oldOwnerId, newOwnerId);
15227 : 0 : repl_repl[Anum_pg_attribute_attacl - 1] = true;
15228 : 0 : repl_val[Anum_pg_attribute_attacl - 1] = PointerGetDatum(newAcl);
15229 : :
15230 : 0 : newtuple = heap_modify_tuple(attributeTuple,
15231 : : RelationGetDescr(attRelation),
15232 : : repl_val, repl_null, repl_repl);
15233 : :
2630 alvherre@alvh.no-ip. 15234 : 0 : CatalogTupleUpdate(attRelation, &newtuple->t_self, newtuple);
15235 : :
4498 tgl@sss.pgh.pa.us 15236 : 0 : heap_freetuple(newtuple);
15237 : : }
4498 tgl@sss.pgh.pa.us 15238 :CBC 219 : systable_endscan(scan);
1910 andres@anarazel.de 15239 : 219 : table_close(attRelation, RowExclusiveLock);
4498 tgl@sss.pgh.pa.us 15240 : 219 : }
15241 : :
15242 : : /*
15243 : : * change_owner_recurse_to_sequences
15244 : : *
15245 : : * Helper function for ATExecChangeOwner. Examines pg_depend searching
15246 : : * for sequences that are dependent on serial columns, and changes their
15247 : : * ownership.
15248 : : */
15249 : : static void
5009 simon@2ndQuadrant.co 15250 : 219 : change_owner_recurse_to_sequences(Oid relationOid, Oid newOwnerId, LOCKMODE lockmode)
15251 : : {
15252 : : Relation depRel;
15253 : : SysScanDesc scan;
15254 : : ScanKeyData key[2];
15255 : : HeapTuple tup;
15256 : :
15257 : : /*
15258 : : * SERIAL sequences are those having an auto dependency on one of the
15259 : : * table's columns (we don't care *which* column, exactly).
15260 : : */
1910 andres@anarazel.de 15261 : 219 : depRel = table_open(DependRelationId, AccessShareLock);
15262 : :
7143 tgl@sss.pgh.pa.us 15263 : 219 : ScanKeyInit(&key[0],
15264 : : Anum_pg_depend_refclassid,
15265 : : BTEqualStrategyNumber, F_OIDEQ,
15266 : : ObjectIdGetDatum(RelationRelationId));
15267 : 219 : ScanKeyInit(&key[1],
15268 : : Anum_pg_depend_refobjid,
15269 : : BTEqualStrategyNumber, F_OIDEQ,
15270 : : ObjectIdGetDatum(relationOid));
15271 : : /* we leave refobjsubid unspecified */
15272 : :
6940 15273 : 219 : scan = systable_beginscan(depRel, DependReferenceIndexId, true,
15274 : : NULL, 2, key);
15275 : :
7143 15276 [ + + ]: 608 : while (HeapTupleIsValid(tup = systable_getnext(scan)))
15277 : : {
15278 : 389 : Form_pg_depend depForm = (Form_pg_depend) GETSTRUCT(tup);
15279 : : Relation seqRel;
15280 : :
15281 : : /* skip dependencies other than auto dependencies on columns */
15282 [ + + ]: 389 : if (depForm->refobjsubid == 0 ||
6940 15283 [ + + ]: 134 : depForm->classid != RelationRelationId ||
7143 15284 [ + - ]: 61 : depForm->objsubid != 0 ||
2565 peter_e@gmx.net 15285 [ - + - - ]: 61 : !(depForm->deptype == DEPENDENCY_AUTO || depForm->deptype == DEPENDENCY_INTERNAL))
7143 tgl@sss.pgh.pa.us 15286 : 328 : continue;
15287 : :
15288 : : /* Use relation_open just in case it's an index */
5009 simon@2ndQuadrant.co 15289 : 61 : seqRel = relation_open(depForm->objid, lockmode);
15290 : :
15291 : : /* skip non-sequence relations */
7143 tgl@sss.pgh.pa.us 15292 [ + + ]: 61 : if (RelationGetForm(seqRel)->relkind != RELKIND_SEQUENCE)
15293 : : {
15294 : : /* No need to keep the lock */
5009 simon@2ndQuadrant.co 15295 : 52 : relation_close(seqRel, lockmode);
7143 tgl@sss.pgh.pa.us 15296 : 52 : continue;
15297 : : }
15298 : :
15299 : : /* We don't need to close the sequence while we alter it. */
5009 simon@2ndQuadrant.co 15300 : 9 : ATExecChangeOwner(depForm->objid, newOwnerId, true, lockmode);
15301 : :
15302 : : /* Now we can close it. Keep the lock till end of transaction. */
7143 tgl@sss.pgh.pa.us 15303 : 9 : relation_close(seqRel, NoLock);
15304 : : }
15305 : :
15306 : 219 : systable_endscan(scan);
15307 : :
6960 15308 : 219 : relation_close(depRel, AccessShareLock);
7143 15309 : 219 : }
15310 : :
15311 : : /*
15312 : : * ALTER TABLE CLUSTER ON
15313 : : *
15314 : : * The only thing we have to do is to change the indisclustered bits.
15315 : : *
15316 : : * Return the address of the new clustering index.
15317 : : */
15318 : : static ObjectAddress
5009 simon@2ndQuadrant.co 15319 : 32 : ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode)
15320 : : {
15321 : : Oid indexOid;
15322 : : ObjectAddress address;
15323 : :
7696 bruce@momjian.us 15324 : 32 : indexOid = get_relname_relid(indexName, rel->rd_rel->relnamespace);
15325 : :
15326 [ - + ]: 32 : if (!OidIsValid(indexOid))
7574 tgl@sss.pgh.pa.us 15327 [ # # ]:UBC 0 : ereport(ERROR,
15328 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
15329 : : errmsg("index \"%s\" for table \"%s\" does not exist",
15330 : : indexName, RelationGetRelationName(rel))));
15331 : :
15332 : : /* Check index is valid to cluster on */
732 michael@paquier.xyz 15333 :CBC 32 : check_index_is_clusterable(rel, indexOid, lockmode);
15334 : :
15335 : : /* And do the work */
4046 rhaas@postgresql.org 15336 : 32 : mark_index_clustered(rel, indexOid, false);
15337 : :
3308 alvherre@alvh.no-ip. 15338 : 29 : ObjectAddressSet(address,
15339 : : RelationRelationId, indexOid);
15340 : :
15341 : 29 : return address;
15342 : : }
15343 : :
15344 : : /*
15345 : : * ALTER TABLE SET WITHOUT CLUSTER
15346 : : *
15347 : : * We have to find any indexes on the table that have indisclustered bit
15348 : : * set and turn it off.
15349 : : */
15350 : : static void
5009 simon@2ndQuadrant.co 15351 : 9 : ATExecDropCluster(Relation rel, LOCKMODE lockmode)
15352 : : {
4046 rhaas@postgresql.org 15353 : 9 : mark_index_clustered(rel, InvalidOid, false);
7256 bruce@momjian.us 15354 : 6 : }
15355 : :
15356 : : /*
15357 : : * Preparation phase for SET ACCESS METHOD
15358 : : *
15359 : : * Check that the access method exists and determine whether a change is
15360 : : * actually needed.
15361 : : */
15362 : : static void
991 michael@paquier.xyz 15363 : 55 : ATPrepSetAccessMethod(AlteredTableInfo *tab, Relation rel, const char *amname)
15364 : : {
15365 : : Oid amoid;
15366 : :
15367 : : /*
15368 : : * Look up the access method name and check that it differs from the
15369 : : * table's current AM. If DEFAULT was specified for a partitioned table
15370 : : * (amname is NULL), set it to InvalidOid to reset the catalogued AM.
15371 : : */
20 alvherre@alvh.no-ip. 15372 [ + + ]:GNC 55 : if (amname != NULL)
15373 : 37 : amoid = get_table_am_oid(amname, false);
15374 [ + + ]: 18 : else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
15375 : 9 : amoid = InvalidOid;
15376 : : else
15377 : 9 : amoid = get_table_am_oid(default_table_access_method, false);
15378 : :
15379 : : /* if it's a match, phase 3 doesn't need to do anything */
991 michael@paquier.xyz 15380 [ + + ]:CBC 55 : if (rel->rd_rel->relam == amoid)
991 michael@paquier.xyz 15381 :GBC 6 : return;
15382 : :
15383 : : /* Save info for Phase 3 to do the real work */
991 michael@paquier.xyz 15384 :CBC 49 : tab->rewrite |= AT_REWRITE_ACCESS_METHOD;
15385 : 49 : tab->newAccessMethod = amoid;
20 alvherre@alvh.no-ip. 15386 :GNC 49 : tab->chgAccessMethod = true;
15387 : : }
15388 : :
15389 : : /*
15390 : : * Special handling of ALTER TABLE SET ACCESS METHOD for relations with no
15391 : : * storage that have an interest in preserving AM.
15392 : : *
15393 : : * Since these have no storage, setting the access method is a catalog only
15394 : : * operation.
15395 : : */
15396 : : static void
15397 : 22 : ATExecSetAccessMethodNoStorage(Relation rel, Oid newAccessMethodId)
15398 : : {
15399 : : Relation pg_class;
15400 : : Oid oldAccessMethodId;
15401 : : HeapTuple tuple;
15402 : : Form_pg_class rd_rel;
15403 : 22 : Oid reloid = RelationGetRelid(rel);
15404 : :
15405 : : /*
15406 : : * Shouldn't be called on relations having storage; these are processed in
15407 : : * phase 3.
15408 : : */
15409 [ + - + - : 22 : Assert(!RELKIND_HAS_STORAGE(rel->rd_rel->relkind));
+ - + - -
+ ]
15410 : :
15411 : : /* Get a modifiable copy of the relation's pg_class row. */
15412 : 22 : pg_class = table_open(RelationRelationId, RowExclusiveLock);
15413 : :
15414 : 22 : tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid));
15415 [ - + ]: 22 : if (!HeapTupleIsValid(tuple))
20 alvherre@alvh.no-ip. 15416 [ # # ]:UNC 0 : elog(ERROR, "cache lookup failed for relation %u", reloid);
20 alvherre@alvh.no-ip. 15417 :GNC 22 : rd_rel = (Form_pg_class) GETSTRUCT(tuple);
15418 : :
15419 : : /* Update the pg_class row. */
15420 : 22 : oldAccessMethodId = rd_rel->relam;
15421 : 22 : rd_rel->relam = newAccessMethodId;
15422 : :
15423 : : /* Leave if no update required */
15424 [ - + ]: 22 : if (rd_rel->relam == oldAccessMethodId)
15425 : : {
20 alvherre@alvh.no-ip. 15426 :UNC 0 : heap_freetuple(tuple);
15427 : 0 : table_close(pg_class, RowExclusiveLock);
15428 : 0 : return;
15429 : : }
15430 : :
20 alvherre@alvh.no-ip. 15431 :GNC 22 : CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
15432 : :
15433 : : /*
15434 : : * Update the dependency on the new access method. No dependency is added
15435 : : * if the new access method is InvalidOid (default case). Be very careful
15436 : : * that this has to compare the previous value stored in pg_class with the
15437 : : * new one.
15438 : : */
15439 [ + + + - ]: 22 : if (!OidIsValid(oldAccessMethodId) && OidIsValid(rd_rel->relam))
15440 : 10 : {
15441 : : ObjectAddress relobj,
15442 : : referenced;
15443 : :
15444 : : /*
15445 : : * New access method is defined and there was no dependency
15446 : : * previously, so record a new one.
15447 : : */
15448 : 10 : ObjectAddressSet(relobj, RelationRelationId, reloid);
15449 : 10 : ObjectAddressSet(referenced, AccessMethodRelationId, rd_rel->relam);
15450 : 10 : recordDependencyOn(&relobj, &referenced, DEPENDENCY_NORMAL);
15451 : : }
15452 [ + - ]: 12 : else if (OidIsValid(oldAccessMethodId) &&
15453 [ + + ]: 12 : !OidIsValid(rd_rel->relam))
15454 : : {
15455 : : /*
15456 : : * There was an access method defined, and no new one, so just remove
15457 : : * the existing dependency.
15458 : : */
15459 : 6 : deleteDependencyRecordsForClass(RelationRelationId, reloid,
15460 : : AccessMethodRelationId,
15461 : : DEPENDENCY_NORMAL);
15462 : : }
15463 : : else
15464 : : {
15465 [ + - - + ]: 6 : Assert(OidIsValid(oldAccessMethodId) &&
15466 : : OidIsValid(rd_rel->relam));
15467 : :
15468 : : /* Both are valid, so update the dependency */
15469 : 6 : changeDependencyFor(RelationRelationId, reloid,
15470 : : AccessMethodRelationId,
15471 : : oldAccessMethodId, rd_rel->relam);
15472 : : }
15473 : :
15474 : : /* make the relam and dependency changes visible */
15475 : 22 : CommandCounterIncrement();
15476 : :
15477 [ - + ]: 22 : InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);
15478 : :
15479 : 22 : heap_freetuple(tuple);
15480 : 22 : table_close(pg_class, RowExclusiveLock);
15481 : : }
15482 : :
15483 : : /*
15484 : : * ALTER TABLE SET TABLESPACE
15485 : : */
15486 : : static void
2357 peter_e@gmx.net 15487 :CBC 79 : ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, const char *tablespacename, LOCKMODE lockmode)
15488 : : {
15489 : : Oid tablespaceId;
15490 : :
15491 : : /* Check that the tablespace exists */
5001 rhaas@postgresql.org 15492 : 79 : tablespaceId = get_tablespace_oid(tablespacename, false);
15493 : :
15494 : : /* Check permissions except when moving to database's default */
3739 sfrost@snowman.net 15495 [ + - + + ]: 79 : if (OidIsValid(tablespaceId) && tablespaceId != MyDatabaseTableSpace)
15496 : : {
15497 : : AclResult aclresult;
15498 : :
518 peter@eisentraut.org 15499 : 33 : aclresult = object_aclcheck(TableSpaceRelationId, tablespaceId, GetUserId(), ACL_CREATE);
3739 sfrost@snowman.net 15500 [ - + ]: 33 : if (aclresult != ACLCHECK_OK)
2325 peter_e@gmx.net 15501 :UBC 0 : aclcheck_error(aclresult, OBJECT_TABLESPACE, tablespacename);
15502 : : }
15503 : :
15504 : : /* Save info for Phase 3 to do the real work */
7217 tgl@sss.pgh.pa.us 15505 [ - + ]:CBC 79 : if (OidIsValid(tab->newTableSpace))
7217 tgl@sss.pgh.pa.us 15506 [ # # ]:UBC 0 : ereport(ERROR,
15507 : : (errcode(ERRCODE_SYNTAX_ERROR),
15508 : : errmsg("cannot have multiple SET TABLESPACE subcommands")));
15509 : :
7217 tgl@sss.pgh.pa.us 15510 :CBC 79 : tab->newTableSpace = tablespaceId;
15511 : 79 : }
15512 : :
15513 : : /*
15514 : : * Set, reset, or replace reloptions.
15515 : : */
15516 : : static void
4497 rhaas@postgresql.org 15517 : 467 : ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
15518 : : LOCKMODE lockmode)
15519 : : {
15520 : : Oid relid;
15521 : : Relation pgclass;
15522 : : HeapTuple tuple;
15523 : : HeapTuple newtuple;
15524 : : Datum datum;
15525 : : bool isnull;
15526 : : Datum newOptions;
15527 : : Datum repl_val[Natts_pg_class];
15528 : : bool repl_null[Natts_pg_class];
15529 : : bool repl_repl[Natts_pg_class];
15530 : : static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
15531 : :
15532 [ + + - + ]: 467 : if (defList == NIL && operation != AT_ReplaceRelOptions)
6495 tgl@sss.pgh.pa.us 15533 :UBC 0 : return; /* nothing to do */
15534 : :
1910 andres@anarazel.de 15535 :CBC 467 : pgclass = table_open(RelationRelationId, RowExclusiveLock);
15536 : :
15537 : : /* Fetch heap tuple */
6495 tgl@sss.pgh.pa.us 15538 : 467 : relid = RelationGetRelid(rel);
5173 rhaas@postgresql.org 15539 : 467 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
6496 bruce@momjian.us 15540 [ - + ]: 467 : if (!HeapTupleIsValid(tuple))
6496 bruce@momjian.us 15541 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
15542 : :
4497 rhaas@postgresql.org 15543 [ + + ]:CBC 467 : if (operation == AT_ReplaceRelOptions)
15544 : : {
15545 : : /*
15546 : : * If we're supposed to replace the reloptions list, we just pretend
15547 : : * there were none before.
15548 : : */
15549 : 97 : datum = (Datum) 0;
15550 : 97 : isnull = true;
15551 : : }
15552 : : else
15553 : : {
15554 : : /* Get the old reloptions */
15555 : 370 : datum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions,
15556 : : &isnull);
15557 : : }
15558 : :
15559 : : /* Generate new proposed reloptions (text array) */
6495 tgl@sss.pgh.pa.us 15560 [ + + ]: 467 : newOptions = transformRelOptions(isnull ? (Datum) 0 : datum,
15561 : : defList, NULL, validnsps, false,
15562 : : operation == AT_ResetRelOptions);
15563 : :
15564 : : /* Validate */
6496 bruce@momjian.us 15565 [ + + + + : 464 : switch (rel->rd_rel->relkind)
- ]
15566 : : {
15567 : 256 : case RELKIND_RELATION:
15568 : : case RELKIND_TOASTVALUE:
15569 : : case RELKIND_MATVIEW:
3 akorotkov@postgresql 15570 : 256 : (void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
6496 bruce@momjian.us 15571 : 256 : break;
1613 michael@paquier.xyz 15572 : 3 : case RELKIND_PARTITIONED_TABLE:
15573 : 3 : (void) partitioned_table_reloptions(newOptions, true);
1613 michael@paquier.xyz 15574 :UBC 0 : break;
3562 alvherre@alvh.no-ip. 15575 :CBC 148 : case RELKIND_VIEW:
15576 : 148 : (void) view_reloptions(newOptions, true);
15577 : 139 : break;
6496 bruce@momjian.us 15578 : 57 : case RELKIND_INDEX:
15579 : : case RELKIND_PARTITIONED_INDEX:
1910 andres@anarazel.de 15580 : 57 : (void) index_reloptions(rel->rd_indam->amoptions, newOptions, true);
6496 bruce@momjian.us 15581 : 46 : break;
6496 bruce@momjian.us 15582 :UBC 0 : default:
6495 tgl@sss.pgh.pa.us 15583 [ # # ]: 0 : ereport(ERROR,
15584 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
15585 : : errmsg("cannot set options for relation \"%s\"",
15586 : : RelationGetRelationName(rel)),
15587 : : errdetail_relkind_not_supported(rel->rd_rel->relkind)));
15588 : : break;
15589 : : }
15590 : :
15591 : : /* Special-case validation of view options */
3923 sfrost@snowman.net 15592 [ + + ]:CBC 441 : if (rel->rd_rel->relkind == RELKIND_VIEW)
15593 : : {
15594 : 139 : Query *view_query = get_view_query(rel);
15595 : 139 : List *view_options = untransformRelOptions(newOptions);
15596 : : ListCell *cell;
15597 : 139 : bool check_option = false;
15598 : :
15599 [ + + + + : 190 : foreach(cell, view_options)
+ + ]
15600 : : {
15601 : 51 : DefElem *defel = (DefElem *) lfirst(cell);
15602 : :
2270 tgl@sss.pgh.pa.us 15603 [ + + ]: 51 : if (strcmp(defel->defname, "check_option") == 0)
3923 sfrost@snowman.net 15604 : 12 : check_option = true;
15605 : : }
15606 : :
15607 : : /*
15608 : : * If the check option is specified, look to see if the view is
15609 : : * actually auto-updatable or not.
15610 : : */
15611 [ + + ]: 139 : if (check_option)
15612 : : {
15613 : : const char *view_updatable_error =
331 tgl@sss.pgh.pa.us 15614 : 12 : view_query_is_auto_updatable(view_query, true);
15615 : :
3923 sfrost@snowman.net 15616 [ - + ]: 12 : if (view_updatable_error)
3923 sfrost@snowman.net 15617 [ # # ]:UBC 0 : ereport(ERROR,
15618 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
15619 : : errmsg("WITH CHECK OPTION is supported only on automatically updatable views"),
15620 : : errhint("%s", _(view_updatable_error))));
15621 : : }
15622 : : }
15623 : :
15624 : : /*
15625 : : * All we need do here is update the pg_class row; the new options will be
15626 : : * propagated into relcaches during post-commit cache inval.
15627 : : */
6495 tgl@sss.pgh.pa.us 15628 :CBC 441 : memset(repl_val, 0, sizeof(repl_val));
5642 15629 : 441 : memset(repl_null, false, sizeof(repl_null));
15630 : 441 : memset(repl_repl, false, sizeof(repl_repl));
15631 : :
6495 15632 [ + + ]: 441 : if (newOptions != (Datum) 0)
15633 : 297 : repl_val[Anum_pg_class_reloptions - 1] = newOptions;
15634 : : else
5642 15635 : 144 : repl_null[Anum_pg_class_reloptions - 1] = true;
15636 : :
15637 : 441 : repl_repl[Anum_pg_class_reloptions - 1] = true;
15638 : :
15639 : 441 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(pgclass),
15640 : : repl_val, repl_null, repl_repl);
15641 : :
2630 alvherre@alvh.no-ip. 15642 : 441 : CatalogTupleUpdate(pgclass, &newtuple->t_self, newtuple);
15643 : :
4046 rhaas@postgresql.org 15644 [ - + ]: 441 : InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);
15645 : :
6495 tgl@sss.pgh.pa.us 15646 : 441 : heap_freetuple(newtuple);
15647 : :
6496 bruce@momjian.us 15648 : 441 : ReleaseSysCache(tuple);
15649 : :
15650 : : /* repeat the whole exercise for the toast table, if there's one */
5550 alvherre@alvh.no-ip. 15651 [ + + ]: 441 : if (OidIsValid(rel->rd_rel->reltoastrelid))
15652 : : {
15653 : : Relation toastrel;
15654 : 128 : Oid toastid = rel->rd_rel->reltoastrelid;
15655 : :
1910 andres@anarazel.de 15656 : 128 : toastrel = table_open(toastid, lockmode);
15657 : :
15658 : : /* Fetch heap tuple */
5173 rhaas@postgresql.org 15659 : 128 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(toastid));
5550 alvherre@alvh.no-ip. 15660 [ - + ]: 128 : if (!HeapTupleIsValid(tuple))
5550 alvherre@alvh.no-ip. 15661 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", toastid);
15662 : :
4497 rhaas@postgresql.org 15663 [ - + ]:CBC 128 : if (operation == AT_ReplaceRelOptions)
15664 : : {
15665 : : /*
15666 : : * If we're supposed to replace the reloptions list, we just
15667 : : * pretend there were none before.
15668 : : */
4497 rhaas@postgresql.org 15669 :UBC 0 : datum = (Datum) 0;
15670 : 0 : isnull = true;
15671 : : }
15672 : : else
15673 : : {
15674 : : /* Get the old reloptions */
4497 rhaas@postgresql.org 15675 :CBC 128 : datum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions,
15676 : : &isnull);
15677 : : }
15678 : :
5550 alvherre@alvh.no-ip. 15679 [ + + ]: 128 : newOptions = transformRelOptions(isnull ? (Datum) 0 : datum,
15680 : : defList, "toast", validnsps, false,
15681 : : operation == AT_ResetRelOptions);
15682 : :
3 akorotkov@postgresql 15683 : 128 : (void) heap_reloptions(RELKIND_TOASTVALUE, newOptions, true);
15684 : :
5550 alvherre@alvh.no-ip. 15685 : 128 : memset(repl_val, 0, sizeof(repl_val));
15686 : 128 : memset(repl_null, false, sizeof(repl_null));
15687 : 128 : memset(repl_repl, false, sizeof(repl_repl));
15688 : :
15689 [ + + ]: 128 : if (newOptions != (Datum) 0)
15690 : 21 : repl_val[Anum_pg_class_reloptions - 1] = newOptions;
15691 : : else
15692 : 107 : repl_null[Anum_pg_class_reloptions - 1] = true;
15693 : :
15694 : 128 : repl_repl[Anum_pg_class_reloptions - 1] = true;
15695 : :
15696 : 128 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(pgclass),
15697 : : repl_val, repl_null, repl_repl);
15698 : :
2630 15699 : 128 : CatalogTupleUpdate(pgclass, &newtuple->t_self, newtuple);
15700 : :
4046 rhaas@postgresql.org 15701 [ - + ]: 128 : InvokeObjectPostAlterHookArg(RelationRelationId,
15702 : : RelationGetRelid(toastrel), 0,
15703 : : InvalidOid, true);
15704 : :
5550 alvherre@alvh.no-ip. 15705 : 128 : heap_freetuple(newtuple);
15706 : :
15707 : 128 : ReleaseSysCache(tuple);
15708 : :
1910 andres@anarazel.de 15709 : 128 : table_close(toastrel, NoLock);
15710 : : }
15711 : :
15712 : 441 : table_close(pgclass, RowExclusiveLock);
15713 : : }
15714 : :
15715 : : /*
15716 : : * Execute ALTER TABLE SET TABLESPACE for cases where there is no tuple
15717 : : * rewriting to be done, so we just want to copy the data as fast as possible.
15718 : : */
15719 : : static void
5009 simon@2ndQuadrant.co 15720 : 81 : ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
15721 : : {
15722 : : Relation rel;
15723 : : Oid reltoastrelid;
15724 : : RelFileNumber newrelfilenumber;
15725 : : RelFileLocator newrlocator;
3937 fujii@postgresql.org 15726 : 81 : List *reltoastidxids = NIL;
15727 : : ListCell *lc;
15728 : :
15729 : : /*
15730 : : * Need lock here in case we are recursing to toast table or index
15731 : : */
5009 simon@2ndQuadrant.co 15732 : 81 : rel = relation_open(tableOid, lockmode);
15733 : :
15734 : : /* Check first if relation can be moved to new tablespace */
1173 michael@paquier.xyz 15735 [ + + ]: 81 : if (!CheckRelationTableSpaceMove(rel, newTableSpace))
15736 : : {
4046 rhaas@postgresql.org 15737 [ - + ]: 1 : InvokeObjectPostAlterHook(RelationRelationId,
15738 : : RelationGetRelid(rel), 0);
5180 tgl@sss.pgh.pa.us 15739 : 1 : relation_close(rel, NoLock);
15740 : 1 : return;
15741 : : }
15742 : :
7217 15743 : 80 : reltoastrelid = rel->rd_rel->reltoastrelid;
15744 : : /* Fetch the list of indexes on toast relation if necessary */
3937 fujii@postgresql.org 15745 [ + + ]: 80 : if (OidIsValid(reltoastrelid))
15746 : : {
3631 bruce@momjian.us 15747 : 10 : Relation toastRel = relation_open(reltoastrelid, lockmode);
15748 : :
3937 fujii@postgresql.org 15749 : 10 : reltoastidxids = RelationGetIndexList(toastRel);
15750 : 10 : relation_close(toastRel, lockmode);
15751 : : }
15752 : :
15753 : : /*
15754 : : * Relfilenumbers are not unique in databases across tablespaces, so we
15755 : : * need to allocate a new one in the new tablespace.
15756 : : */
564 rhaas@postgresql.org 15757 : 80 : newrelfilenumber = GetNewRelFileNumber(newTableSpace, NULL,
648 15758 : 80 : rel->rd_rel->relpersistence);
15759 : :
15760 : : /* Open old and new relation */
15761 : 80 : newrlocator = rel->rd_locator;
15762 : 80 : newrlocator.relNumber = newrelfilenumber;
15763 : 80 : newrlocator.spcOid = newTableSpace;
15764 : :
15765 : : /* hand off to AM to actually create new rel storage and copy the data */
1844 andres@anarazel.de 15766 [ + + ]: 80 : if (rel->rd_rel->relkind == RELKIND_INDEX)
15767 : : {
648 rhaas@postgresql.org 15768 : 31 : index_copy_data(rel, newrlocator);
15769 : : }
15770 : : else
15771 : : {
863 peter@eisentraut.org 15772 [ + + + + : 49 : Assert(RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind));
- + ]
648 rhaas@postgresql.org 15773 : 49 : table_relation_copy_data(rel, &newrlocator);
15774 : : }
15775 : :
15776 : : /*
15777 : : * Update the pg_class row.
15778 : : *
15779 : : * NB: This wouldn't work if ATExecSetTableSpace() were allowed to be
15780 : : * executed on pg_class or its indexes (the above copy wouldn't contain
15781 : : * the updated pg_class entry), but that's forbidden with
15782 : : * CheckRelationTableSpaceMove().
15783 : : */
15784 : 80 : SetRelationTableSpace(rel, newTableSpace, newrelfilenumber);
15785 : :
4046 15786 [ - + ]: 80 : InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);
15787 : :
648 15788 : 80 : RelationAssumeNewRelfilelocator(rel);
15789 : :
7217 tgl@sss.pgh.pa.us 15790 : 80 : relation_close(rel, NoLock);
15791 : :
15792 : : /* Make sure the reltablespace change is visible */
15793 : 80 : CommandCounterIncrement();
15794 : :
15795 : : /* Move associated toast relation and/or indexes, too */
15796 [ + + ]: 80 : if (OidIsValid(reltoastrelid))
5009 simon@2ndQuadrant.co 15797 : 10 : ATExecSetTableSpace(reltoastrelid, newTableSpace, lockmode);
3937 fujii@postgresql.org 15798 [ + + + + : 90 : foreach(lc, reltoastidxids)
+ + ]
15799 : 10 : ATExecSetTableSpace(lfirst_oid(lc), newTableSpace, lockmode);
15800 : :
15801 : : /* Clean up */
15802 : 80 : list_free(reltoastidxids);
15803 : : }
15804 : :
15805 : : /*
15806 : : * Special handling of ALTER TABLE SET TABLESPACE for relations with no
15807 : : * storage that have an interest in preserving tablespace.
15808 : : *
15809 : : * Since these have no storage the tablespace can be updated with a simple
15810 : : * metadata only operation to update the tablespace.
15811 : : */
15812 : : static void
1945 alvherre@alvh.no-ip. 15813 : 18 : ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace)
15814 : : {
15815 : : /*
15816 : : * Shouldn't be called on relations having storage; these are processed in
15817 : : * phase 3.
15818 : : */
1927 15819 [ + - + - : 18 : Assert(!RELKIND_HAS_STORAGE(rel->rd_rel->relkind));
+ - + - -
+ ]
15820 : :
15821 : : /* check if relation can be moved to its new tablespace */
1173 michael@paquier.xyz 15822 [ - + ]: 18 : if (!CheckRelationTableSpaceMove(rel, newTableSpace))
15823 : : {
1173 michael@paquier.xyz 15824 [ # # ]:UBC 0 : InvokeObjectPostAlterHook(RelationRelationId,
15825 : : RelationGetRelid(rel),
15826 : : 0);
1989 alvherre@alvh.no-ip. 15827 : 0 : return;
15828 : : }
15829 : :
15830 : : /* Update can be done, so change reltablespace */
1173 michael@paquier.xyz 15831 :CBC 15 : SetRelationTableSpace(rel, newTableSpace, InvalidOid);
15832 : :
15833 [ - + ]: 15 : InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);
15834 : :
15835 : : /* Make sure the reltablespace change is visible */
1989 alvherre@alvh.no-ip. 15836 : 15 : CommandCounterIncrement();
15837 : : }
15838 : :
15839 : : /*
15840 : : * Alter Table ALL ... SET TABLESPACE
15841 : : *
15842 : : * Allows a user to move all objects of some type in a given tablespace in the
15843 : : * current database to another tablespace. Objects can be chosen based on the
15844 : : * owner of the object also, to allow users to move only their objects.
15845 : : * The user must have CREATE rights on the new tablespace, as usual. The main
15846 : : * permissions handling is done by the lower-level table move function.
15847 : : *
15848 : : * All to-be-moved objects are locked first. If NOWAIT is specified and the
15849 : : * lock can't be acquired then we ereport(ERROR).
15850 : : */
15851 : : Oid
3524 sfrost@snowman.net 15852 : 15 : AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
15853 : : {
15854 : 15 : List *relations = NIL;
15855 : : ListCell *l;
15856 : : ScanKeyData key[1];
15857 : : Relation rel;
15858 : : TableScanDesc scan;
15859 : : HeapTuple tuple;
15860 : : Oid orig_tablespaceoid;
15861 : : Oid new_tablespaceoid;
3324 alvherre@alvh.no-ip. 15862 : 15 : List *role_oids = roleSpecsToIds(stmt->roles);
15863 : :
15864 : : /* Ensure we were not asked to move something we can't */
3524 sfrost@snowman.net 15865 [ + + + + ]: 15 : if (stmt->objtype != OBJECT_TABLE && stmt->objtype != OBJECT_INDEX &&
15866 [ - + ]: 6 : stmt->objtype != OBJECT_MATVIEW)
3524 sfrost@snowman.net 15867 [ # # ]:UBC 0 : ereport(ERROR,
15868 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
15869 : : errmsg("only tables, indexes, and materialized views exist in tablespaces")));
15870 : :
15871 : : /* Get the orig and new tablespace OIDs */
3524 sfrost@snowman.net 15872 :CBC 15 : orig_tablespaceoid = get_tablespace_oid(stmt->orig_tablespacename, false);
15873 : 15 : new_tablespaceoid = get_tablespace_oid(stmt->new_tablespacename, false);
15874 : :
15875 : : /* Can't move shared relations in to or out of pg_global */
15876 : : /* This is also checked by ATExecSetTableSpace, but nice to stop earlier */
15877 [ + - - + ]: 15 : if (orig_tablespaceoid == GLOBALTABLESPACE_OID ||
15878 : : new_tablespaceoid == GLOBALTABLESPACE_OID)
3524 sfrost@snowman.net 15879 [ # # ]:UBC 0 : ereport(ERROR,
15880 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
15881 : : errmsg("cannot move relations in to or out of pg_global tablespace")));
15882 : :
15883 : : /*
15884 : : * Must have CREATE rights on the new tablespace, unless it is the
15885 : : * database default tablespace (which all users implicitly have CREATE
15886 : : * rights on).
15887 : : */
3524 sfrost@snowman.net 15888 [ + - - + ]:CBC 15 : if (OidIsValid(new_tablespaceoid) && new_tablespaceoid != MyDatabaseTableSpace)
15889 : : {
15890 : : AclResult aclresult;
15891 : :
518 peter@eisentraut.org 15892 :UBC 0 : aclresult = object_aclcheck(TableSpaceRelationId, new_tablespaceoid, GetUserId(),
15893 : : ACL_CREATE);
3524 sfrost@snowman.net 15894 [ # # ]: 0 : if (aclresult != ACLCHECK_OK)
2325 peter_e@gmx.net 15895 : 0 : aclcheck_error(aclresult, OBJECT_TABLESPACE,
3524 sfrost@snowman.net 15896 : 0 : get_tablespace_name(new_tablespaceoid));
15897 : : }
15898 : :
15899 : : /*
15900 : : * Now that the checks are done, check if we should set either to
15901 : : * InvalidOid because it is our database's default tablespace.
15902 : : */
3524 sfrost@snowman.net 15903 [ - + ]:CBC 15 : if (orig_tablespaceoid == MyDatabaseTableSpace)
3524 sfrost@snowman.net 15904 :UBC 0 : orig_tablespaceoid = InvalidOid;
15905 : :
3524 sfrost@snowman.net 15906 [ + - ]:CBC 15 : if (new_tablespaceoid == MyDatabaseTableSpace)
15907 : 15 : new_tablespaceoid = InvalidOid;
15908 : :
15909 : : /* no-op */
15910 [ - + ]: 15 : if (orig_tablespaceoid == new_tablespaceoid)
3524 sfrost@snowman.net 15911 :UBC 0 : return new_tablespaceoid;
15912 : :
15913 : : /*
15914 : : * Walk the list of objects in the tablespace and move them. This will
15915 : : * only find objects in our database, of course.
15916 : : */
3524 sfrost@snowman.net 15917 :CBC 15 : ScanKeyInit(&key[0],
15918 : : Anum_pg_class_reltablespace,
15919 : : BTEqualStrategyNumber, F_OIDEQ,
15920 : : ObjectIdGetDatum(orig_tablespaceoid));
15921 : :
1910 andres@anarazel.de 15922 : 15 : rel = table_open(RelationRelationId, AccessShareLock);
1861 15923 : 15 : scan = table_beginscan_catalog(rel, 1, key);
3524 sfrost@snowman.net 15924 [ + + ]: 66 : while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
15925 : : {
1972 andres@anarazel.de 15926 : 51 : Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
15927 : 51 : Oid relOid = relForm->oid;
15928 : :
15929 : : /*
15930 : : * Do not move objects in pg_catalog as part of this, if an admin
15931 : : * really wishes to do so, they can issue the individual ALTER
15932 : : * commands directly.
15933 : : *
15934 : : * Also, explicitly avoid any shared tables, temp tables, or TOAST
15935 : : * (TOAST will be moved with the main table).
15936 : : */
1803 tgl@sss.pgh.pa.us 15937 [ + - ]: 51 : if (IsCatalogNamespace(relForm->relnamespace) ||
15938 [ + - + - ]: 102 : relForm->relisshared ||
3524 sfrost@snowman.net 15939 [ - + ]: 102 : isAnyTempNamespace(relForm->relnamespace) ||
1803 tgl@sss.pgh.pa.us 15940 : 51 : IsToastNamespace(relForm->relnamespace))
3524 sfrost@snowman.net 15941 :UBC 0 : continue;
15942 : :
15943 : : /* Only move the object type requested */
3524 sfrost@snowman.net 15944 [ + + ]:CBC 51 : if ((stmt->objtype == OBJECT_TABLE &&
2685 rhaas@postgresql.org 15945 [ + + ]: 30 : relForm->relkind != RELKIND_RELATION &&
15946 [ - + ]: 18 : relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
3524 sfrost@snowman.net 15947 [ + + ]: 33 : (stmt->objtype == OBJECT_INDEX &&
2277 alvherre@alvh.no-ip. 15948 [ + + ]: 18 : relForm->relkind != RELKIND_INDEX &&
15949 [ - + ]: 3 : relForm->relkind != RELKIND_PARTITIONED_INDEX) ||
3524 sfrost@snowman.net 15950 [ + + ]: 30 : (stmt->objtype == OBJECT_MATVIEW &&
15951 [ - + ]: 3 : relForm->relkind != RELKIND_MATVIEW))
15952 : 21 : continue;
15953 : :
15954 : : /* Check if we are only moving objects owned by certain roles */
15955 [ - + - - ]: 30 : if (role_oids != NIL && !list_member_oid(role_oids, relForm->relowner))
3524 sfrost@snowman.net 15956 :UBC 0 : continue;
15957 : :
15958 : : /*
15959 : : * Handle permissions-checking here since we are locking the tables
15960 : : * and also to avoid doing a bunch of work only to fail part-way. Note
15961 : : * that permissions will also be checked by AlterTableInternal().
15962 : : *
15963 : : * Caller must be considered an owner on the table to move it.
15964 : : */
518 peter@eisentraut.org 15965 [ - + ]:CBC 30 : if (!object_ownercheck(RelationRelationId, relOid, GetUserId()))
2325 peter_e@gmx.net 15966 :UBC 0 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relOid)),
3524 sfrost@snowman.net 15967 : 0 : NameStr(relForm->relname));
15968 : :
3524 sfrost@snowman.net 15969 [ - + ]:CBC 30 : if (stmt->nowait &&
3524 sfrost@snowman.net 15970 [ # # ]:UBC 0 : !ConditionalLockRelationOid(relOid, AccessExclusiveLock))
15971 [ # # ]: 0 : ereport(ERROR,
15972 : : (errcode(ERRCODE_OBJECT_IN_USE),
15973 : : errmsg("aborting because lock on relation \"%s.%s\" is not available",
15974 : : get_namespace_name(relForm->relnamespace),
15975 : : NameStr(relForm->relname))));
15976 : : else
3524 sfrost@snowman.net 15977 :CBC 30 : LockRelationOid(relOid, AccessExclusiveLock);
15978 : :
15979 : : /* Add to our list of objects to move */
15980 : 30 : relations = lappend_oid(relations, relOid);
15981 : : }
15982 : :
1861 andres@anarazel.de 15983 : 15 : table_endscan(scan);
1910 15984 : 15 : table_close(rel, AccessShareLock);
15985 : :
3524 sfrost@snowman.net 15986 [ + + ]: 15 : if (relations == NIL)
15987 [ + - + - ]: 6 : ereport(NOTICE,
15988 : : (errcode(ERRCODE_NO_DATA_FOUND),
15989 : : errmsg("no matching relations in tablespace \"%s\" found",
15990 : : orig_tablespaceoid == InvalidOid ? "(database default)" :
15991 : : get_tablespace_name(orig_tablespaceoid))));
15992 : :
15993 : : /* Everything is locked, loop through and move all of the relations. */
15994 [ + + + + : 45 : foreach(l, relations)
+ + ]
15995 : : {
15996 : 30 : List *cmds = NIL;
15997 : 30 : AlterTableCmd *cmd = makeNode(AlterTableCmd);
15998 : :
15999 : 30 : cmd->subtype = AT_SetTableSpace;
16000 : 30 : cmd->name = stmt->new_tablespacename;
16001 : :
16002 : 30 : cmds = lappend(cmds, cmd);
16003 : :
3261 alvherre@alvh.no-ip. 16004 : 30 : EventTriggerAlterTableStart((Node *) stmt);
16005 : : /* OID is set by AlterTableInternal */
3524 sfrost@snowman.net 16006 : 30 : AlterTableInternal(lfirst_oid(l), cmds, false);
3261 alvherre@alvh.no-ip. 16007 : 30 : EventTriggerAlterTableEnd();
16008 : : }
16009 : :
3524 sfrost@snowman.net 16010 : 15 : return new_tablespaceoid;
16011 : : }
16012 : :
16013 : : static void
648 rhaas@postgresql.org 16014 : 31 : index_copy_data(Relation rel, RelFileLocator newrlocator)
16015 : : {
16016 : : SMgrRelation dstrel;
16017 : :
16018 : : /*
16019 : : * Since we copy the file directly without looking at the shared buffers,
16020 : : * we'd better first flush out any pages of the source relation that are
16021 : : * in shared buffers. We assume no new changes will be made while we are
16022 : : * holding exclusive lock on the rel.
16023 : : */
1812 andres@anarazel.de 16024 : 31 : FlushRelationBuffers(rel);
16025 : :
16026 : : /*
16027 : : * Create and copy all forks of the relation, and schedule unlinking of
16028 : : * old physical files.
16029 : : *
16030 : : * NOTE: any conflict in relfilenumber value will be caught in
16031 : : * RelationCreateStorage().
16032 : : */
62 heikki.linnakangas@i 16033 :GNC 31 : dstrel = RelationCreateStorage(newrlocator, rel->rd_rel->relpersistence, true);
16034 : :
16035 : : /* copy main fork */
1007 tgl@sss.pgh.pa.us 16036 :CBC 31 : RelationCopyStorage(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM,
1844 andres@anarazel.de 16037 : 31 : rel->rd_rel->relpersistence);
16038 : :
16039 : : /* copy those extra forks that exist */
16040 : 31 : for (ForkNumber forkNum = MAIN_FORKNUM + 1;
16041 [ + + ]: 124 : forkNum <= MAX_FORKNUM; forkNum++)
16042 : : {
1007 tgl@sss.pgh.pa.us 16043 [ - + ]: 93 : if (smgrexists(RelationGetSmgr(rel), forkNum))
16044 : : {
1844 andres@anarazel.de 16045 :UBC 0 : smgrcreate(dstrel, forkNum, false);
16046 : :
16047 : : /*
16048 : : * WAL log creation if the relation is persistent, or this is the
16049 : : * init fork of an unlogged relation.
16050 : : */
1119 bruce@momjian.us 16051 [ # # ]: 0 : if (RelationIsPermanent(rel) ||
1844 andres@anarazel.de 16052 [ # # # # ]: 0 : (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
16053 : : forkNum == INIT_FORKNUM))
648 rhaas@postgresql.org 16054 : 0 : log_smgrcreate(&newrlocator, forkNum);
1007 tgl@sss.pgh.pa.us 16055 : 0 : RelationCopyStorage(RelationGetSmgr(rel), dstrel, forkNum,
1844 andres@anarazel.de 16056 : 0 : rel->rd_rel->relpersistence);
16057 : : }
16058 : : }
16059 : :
16060 : : /* drop old relation, and close new one */
1844 andres@anarazel.de 16061 :CBC 31 : RelationDropStorage(rel);
16062 : 31 : smgrclose(dstrel);
7217 tgl@sss.pgh.pa.us 16063 : 31 : }
16064 : :
16065 : : /*
16066 : : * ALTER TABLE ENABLE/DISABLE TRIGGER
16067 : : *
16068 : : * We just pass this off to trigger.c.
16069 : : */
16070 : : static void
2357 peter_e@gmx.net 16071 : 170 : ATExecEnableDisableTrigger(Relation rel, const char *trigname,
16072 : : char fires_when, bool skip_system, bool recurse,
16073 : : LOCKMODE lockmode)
16074 : : {
407 tgl@sss.pgh.pa.us 16075 : 170 : EnableDisableTrigger(rel, trigname, InvalidOid,
16076 : : fires_when, skip_system, recurse,
16077 : : lockmode);
16078 : :
241 michael@paquier.xyz 16079 [ + + ]:GNC 170 : InvokeObjectPostAlterHook(RelationRelationId,
16080 : : RelationGetRelid(rel), 0);
6236 JanWieck@Yahoo.com 16081 :CBC 170 : }
16082 : :
16083 : : /*
16084 : : * ALTER TABLE ENABLE/DISABLE RULE
16085 : : *
16086 : : * We just pass this off to rewriteDefine.c.
16087 : : */
16088 : : static void
2357 peter_e@gmx.net 16089 : 23 : ATExecEnableDisableRule(Relation rel, const char *rulename,
16090 : : char fires_when, LOCKMODE lockmode)
16091 : : {
2599 16092 : 23 : EnableDisableRule(rel, rulename, fires_when);
16093 : :
241 michael@paquier.xyz 16094 [ + + ]:GNC 23 : InvokeObjectPostAlterHook(RelationRelationId,
16095 : : RelationGetRelid(rel), 0);
6809 tgl@sss.pgh.pa.us 16096 :CBC 23 : }
16097 : :
16098 : : /*
16099 : : * ALTER TABLE INHERIT
16100 : : *
16101 : : * Add a parent to the child's parents. This verifies that all the columns and
16102 : : * check constraints of the parent appear in the child and that they have the
16103 : : * same data types and expressions.
16104 : : */
16105 : : static void
5014 peter_e@gmx.net 16106 : 163 : ATPrepAddInherit(Relation child_rel)
16107 : : {
16108 [ + + ]: 163 : if (child_rel->rd_rel->reloftype)
16109 [ + - ]: 3 : ereport(ERROR,
16110 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
16111 : : errmsg("cannot change inheritance of typed table")));
16112 : :
2685 rhaas@postgresql.org 16113 [ + + ]: 160 : if (child_rel->rd_rel->relispartition)
16114 [ + - ]: 3 : ereport(ERROR,
16115 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
16116 : : errmsg("cannot change inheritance of a partition")));
16117 : :
16118 [ + + ]: 157 : if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
16119 [ + - ]: 3 : ereport(ERROR,
16120 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
16121 : : errmsg("cannot change inheritance of partitioned table")));
5014 peter_e@gmx.net 16122 : 154 : }
16123 : :
16124 : : /*
16125 : : * Return the address of the new parent relation.
16126 : : */
16127 : : static ObjectAddress
5009 simon@2ndQuadrant.co 16128 : 154 : ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
16129 : : {
16130 : : Relation parent_rel;
16131 : : List *children;
16132 : : ObjectAddress address;
16133 : : const char *trigger_name;
16134 : :
16135 : : /*
16136 : : * A self-exclusive lock is needed here. See the similar case in
16137 : : * MergeAttributes() for a full explanation.
16138 : : */
1910 andres@anarazel.de 16139 : 154 : parent_rel = table_openrv(parent, ShareUpdateExclusiveLock);
16140 : :
16141 : : /*
16142 : : * Must be owner of both parent and child -- child was checked by
16143 : : * ATSimplePermissions call in ATPrepCmd
16144 : : */
1011 peter@eisentraut.org 16145 : 154 : ATSimplePermissions(AT_AddInherit, parent_rel, ATT_TABLE | ATT_FOREIGN_TABLE);
16146 : :
16147 : : /* Permanent rels cannot inherit from temporary ones */
4136 tgl@sss.pgh.pa.us 16148 [ + + ]: 154 : if (parent_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
16149 [ - + ]: 3 : child_rel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
6496 bruce@momjian.us 16150 [ # # ]:UBC 0 : ereport(ERROR,
16151 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
16152 : : errmsg("cannot inherit from temporary relation \"%s\"",
16153 : : RelationGetRelationName(parent_rel))));
16154 : :
16155 : : /* If parent rel is temp, it must belong to this session */
4136 tgl@sss.pgh.pa.us 16156 [ + + ]:CBC 154 : if (parent_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
16157 [ - + ]: 3 : !parent_rel->rd_islocaltemp)
4136 tgl@sss.pgh.pa.us 16158 [ # # ]:UBC 0 : ereport(ERROR,
16159 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
16160 : : errmsg("cannot inherit from temporary relation of another session")));
16161 : :
16162 : : /* Ditto for the child */
4136 tgl@sss.pgh.pa.us 16163 [ + + ]:CBC 154 : if (child_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
16164 [ - + ]: 3 : !child_rel->rd_islocaltemp)
4136 tgl@sss.pgh.pa.us 16165 [ # # ]:UBC 0 : ereport(ERROR,
16166 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
16167 : : errmsg("cannot inherit to temporary relation of another session")));
16168 : :
16169 : : /* Prevent partitioned tables from becoming inheritance parents */
2685 rhaas@postgresql.org 16170 [ + + ]:CBC 154 : if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
16171 [ + - ]: 3 : ereport(ERROR,
16172 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
16173 : : errmsg("cannot inherit from partitioned table \"%s\"",
16174 : : parent->relname)));
16175 : :
16176 : : /* Likewise for partitions */
16177 [ + + ]: 151 : if (parent_rel->rd_rel->relispartition)
16178 [ + - ]: 3 : ereport(ERROR,
16179 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
16180 : : errmsg("cannot inherit from a partition")));
16181 : :
16182 : : /*
16183 : : * Prevent circularity by seeing if proposed parent inherits from child.
16184 : : * (In particular, this disallows making a rel inherit from itself.)
16185 : : *
16186 : : * This is not completely bulletproof because of race conditions: in
16187 : : * multi-level inheritance trees, someone else could concurrently be
16188 : : * making another inheritance link that closes the loop but does not join
16189 : : * either of the rels we have locked. Preventing that seems to require
16190 : : * exclusive locks on the entire inheritance tree, which is a cure worse
16191 : : * than the disease. find_all_inheritors() will cope with circularity
16192 : : * anyway, so don't sweat it too much.
16193 : : *
16194 : : * We use weakest lock we can on child's children, namely AccessShareLock.
16195 : : */
16196 : 148 : children = find_all_inheritors(RelationGetRelid(child_rel),
16197 : : AccessShareLock, NULL);
16198 : :
16199 [ + + ]: 148 : if (list_member_oid(children, RelationGetRelid(parent_rel)))
16200 [ + - ]: 6 : ereport(ERROR,
16201 : : (errcode(ERRCODE_DUPLICATE_TABLE),
16202 : : errmsg("circular inheritance not allowed"),
16203 : : errdetail("\"%s\" is already a child of \"%s\".",
16204 : : parent->relname,
16205 : : RelationGetRelationName(child_rel))));
16206 : :
16207 : : /*
16208 : : * If child_rel has row-level triggers with transition tables, we
16209 : : * currently don't allow it to become an inheritance child. See also
16210 : : * prohibitions in ATExecAttachPartition() and CreateTrigger().
16211 : : */
2482 rhodiumtoad@postgres 16212 : 142 : trigger_name = FindTriggerIncompatibleWithInheritance(child_rel->trigdesc);
16213 [ + + ]: 142 : if (trigger_name != NULL)
16214 [ + - ]: 3 : ereport(ERROR,
16215 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
16216 : : errmsg("trigger \"%s\" prevents table \"%s\" from becoming an inheritance child",
16217 : : trigger_name, RelationGetRelationName(child_rel)),
16218 : : errdetail("ROW triggers with transition tables are not supported in inheritance hierarchies.")));
16219 : :
16220 : : /* OK to create inheritance */
89 peter@eisentraut.org 16221 :GNC 139 : CreateInheritance(child_rel, parent_rel, false);
16222 : :
16223 : : /*
16224 : : * If parent_rel has a primary key, then child_rel has not-null
16225 : : * constraints that make these columns as non nullable. Make those
16226 : : * constraints as inherited.
16227 : : */
233 alvherre@alvh.no-ip. 16228 : 115 : ATInheritAdjustNotNulls(parent_rel, child_rel, 1);
16229 : :
2685 rhaas@postgresql.org 16230 :CBC 112 : ObjectAddressSet(address, RelationRelationId,
16231 : : RelationGetRelid(parent_rel));
16232 : :
16233 : : /* keep our lock on the parent relation until commit */
1910 andres@anarazel.de 16234 : 112 : table_close(parent_rel, NoLock);
16235 : :
2685 rhaas@postgresql.org 16236 : 112 : return address;
16237 : : }
16238 : :
16239 : : /*
16240 : : * CreateInheritance
16241 : : * Catalog manipulation portion of creating inheritance between a child
16242 : : * table and a parent table.
16243 : : *
16244 : : * Common to ATExecAddInherit() and ATExecAttachPartition().
16245 : : */
16246 : : static void
89 peter@eisentraut.org 16247 :GNC 1362 : CreateInheritance(Relation child_rel, Relation parent_rel, bool ispartition)
16248 : : {
16249 : : Relation catalogRelation;
16250 : : SysScanDesc scan;
16251 : : ScanKeyData key;
16252 : : HeapTuple inheritsTuple;
16253 : : int32 inhseqno;
16254 : :
16255 : : /* Note: get RowExclusiveLock because we will write pg_inherits below. */
1910 andres@anarazel.de 16256 :CBC 1362 : catalogRelation = table_open(InheritsRelationId, RowExclusiveLock);
16257 : :
16258 : : /*
16259 : : * Check for duplicates in the list of parents, and determine the highest
16260 : : * inhseqno already present; we'll use the next one for the new parent.
16261 : : * Also, if proposed child is a partition, it cannot already be
16262 : : * inheriting.
16263 : : *
16264 : : * Note: we do not reject the case where the child already inherits from
16265 : : * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
16266 : : */
2685 rhaas@postgresql.org 16267 : 1362 : ScanKeyInit(&key,
16268 : : Anum_pg_inherits_inhrelid,
16269 : : BTEqualStrategyNumber, F_OIDEQ,
16270 : : ObjectIdGetDatum(RelationGetRelid(child_rel)));
6496 bruce@momjian.us 16271 : 1362 : scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
16272 : : true, NULL, 1, &key);
16273 : :
16274 : : /* inhseqno sequences start at 1 */
neilc@samurai.com 16275 : 1362 : inhseqno = 0;
bruce@momjian.us 16276 [ + + ]: 1385 : while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
16277 : : {
16278 : 26 : Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
16279 : :
neilc@samurai.com 16280 [ + + ]: 26 : if (inh->inhparent == RelationGetRelid(parent_rel))
bruce@momjian.us 16281 [ + - ]: 3 : ereport(ERROR,
16282 : : (errcode(ERRCODE_DUPLICATE_TABLE),
16283 : : errmsg("relation \"%s\" would be inherited from more than once",
16284 : : RelationGetRelationName(parent_rel))));
16285 : :
6393 tgl@sss.pgh.pa.us 16286 [ + - ]: 23 : if (inh->inhseqno > inhseqno)
6496 bruce@momjian.us 16287 : 23 : inhseqno = inh->inhseqno;
16288 : : }
16289 : 1359 : systable_endscan(scan);
16290 : :
16291 : : /* Match up the columns and bump attinhcount as needed */
89 peter@eisentraut.org 16292 :GNC 1359 : MergeAttributesIntoExisting(child_rel, parent_rel, ispartition);
16293 : :
16294 : : /* Match up the constraints and bump coninhcount as needed */
6496 neilc@samurai.com 16295 :CBC 1320 : MergeConstraintsIntoExisting(child_rel, parent_rel);
16296 : :
16297 : : /*
16298 : : * OK, it looks valid. Make the catalog entries that show inheritance.
16299 : : */
16300 : 1302 : StoreCatalogInheritance1(RelationGetRelid(child_rel),
16301 : : RelationGetRelid(parent_rel),
16302 : : inhseqno + 1,
16303 : : catalogRelation,
2596 simon@2ndQuadrant.co 16304 : 1302 : parent_rel->rd_rel->relkind ==
16305 : : RELKIND_PARTITIONED_TABLE);
16306 : :
16307 : : /* Now we're done with pg_inherits */
1910 andres@anarazel.de 16308 : 1302 : table_close(catalogRelation, RowExclusiveLock);
6496 bruce@momjian.us 16309 : 1302 : }
16310 : :
16311 : : /*
16312 : : * Obtain the source-text form of the constraint expression for a check
16313 : : * constraint, given its pg_constraint tuple
16314 : : */
16315 : : static char *
6393 tgl@sss.pgh.pa.us 16316 : 84 : decompile_conbin(HeapTuple contup, TupleDesc tupdesc)
16317 : : {
16318 : : Form_pg_constraint con;
16319 : : bool isnull;
16320 : : Datum attr;
16321 : : Datum expr;
16322 : :
16323 : 84 : con = (Form_pg_constraint) GETSTRUCT(contup);
16324 : 84 : attr = heap_getattr(contup, Anum_pg_constraint_conbin, tupdesc, &isnull);
16325 [ - + ]: 84 : if (isnull)
1972 andres@anarazel.de 16326 [ # # ]:UBC 0 : elog(ERROR, "null conbin for constraint %u", con->oid);
16327 : :
6393 tgl@sss.pgh.pa.us 16328 :CBC 84 : expr = DirectFunctionCall2(pg_get_expr, attr,
16329 : : ObjectIdGetDatum(con->conrelid));
5864 16330 : 84 : return TextDatumGetCString(expr);
16331 : : }
16332 : :
16333 : : /*
16334 : : * Determine whether two check constraints are functionally equivalent
16335 : : *
16336 : : * The test we apply is to see whether they reverse-compile to the same
16337 : : * source string. This insulates us from issues like whether attributes
16338 : : * have the same physical column numbers in parent and child relations.
16339 : : */
16340 : : static bool
5819 16341 : 42 : constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
16342 : : {
16343 : 42 : Form_pg_constraint acon = (Form_pg_constraint) GETSTRUCT(a);
16344 : 42 : Form_pg_constraint bcon = (Form_pg_constraint) GETSTRUCT(b);
16345 : :
16346 [ + - ]: 42 : if (acon->condeferrable != bcon->condeferrable ||
16347 [ + - ]: 42 : acon->condeferred != bcon->condeferred ||
16348 [ + + ]: 42 : strcmp(decompile_conbin(a, tupleDesc),
16349 : 42 : decompile_conbin(b, tupleDesc)) != 0)
16350 : 3 : return false;
16351 : : else
16352 : 39 : return true;
16353 : : }
16354 : :
16355 : : /*
16356 : : * Check columns in child table match up with columns in parent, and increment
16357 : : * their attinhcount.
16358 : : *
16359 : : * Called by CreateInheritance
16360 : : *
16361 : : * Currently all parent columns must be found in child. Missing columns are an
16362 : : * error. One day we might consider creating new columns like CREATE TABLE
16363 : : * does. However, that is widely unpopular --- in the common use case of
16364 : : * partitioned tables it's a foot-gun.
16365 : : *
16366 : : * The data type must match exactly. If the parent column is NOT NULL then
16367 : : * the child must be as well. Defaults are not compared, however.
16368 : : */
16369 : : static void
89 peter@eisentraut.org 16370 :GNC 1359 : MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel, bool ispartition)
16371 : : {
16372 : : Relation attrrel;
16373 : : TupleDesc parent_desc;
16374 : :
1910 andres@anarazel.de 16375 :CBC 1359 : attrrel = table_open(AttributeRelationId, RowExclusiveLock);
201 peter@eisentraut.org 16376 :GNC 1359 : parent_desc = RelationGetDescr(parent_rel);
16377 : :
16378 [ + + ]: 4664 : for (AttrNumber parent_attno = 1; parent_attno <= parent_desc->natts; parent_attno++)
16379 : : {
16380 : 3344 : Form_pg_attribute parent_att = TupleDescAttr(parent_desc, parent_attno - 1);
16381 : 3344 : char *parent_attname = NameStr(parent_att->attname);
16382 : : HeapTuple tuple;
16383 : :
16384 : : /* Ignore dropped columns in the parent. */
16385 [ + + ]: 3344 : if (parent_att->attisdropped)
6496 bruce@momjian.us 16386 :CBC 148 : continue;
16387 : :
16388 : : /* Find same column in child (matching on column name). */
201 peter@eisentraut.org 16389 :GNC 3196 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(child_rel), parent_attname);
6496 bruce@momjian.us 16390 [ + + ]:CBC 3196 : if (HeapTupleIsValid(tuple))
16391 : : {
201 peter@eisentraut.org 16392 :GNC 3190 : Form_pg_attribute child_att = (Form_pg_attribute) GETSTRUCT(tuple);
16393 : :
16394 [ + + ]: 3190 : if (parent_att->atttypid != child_att->atttypid ||
16395 [ + + ]: 3187 : parent_att->atttypmod != child_att->atttypmod)
6496 bruce@momjian.us 16396 [ + - ]:CBC 6 : ereport(ERROR,
16397 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
16398 : : errmsg("child table \"%s\" has different type for column \"%s\"",
16399 : : RelationGetRelationName(child_rel), parent_attname)));
16400 : :
201 peter@eisentraut.org 16401 [ + + ]:GNC 3184 : if (parent_att->attcollation != child_att->attcollation)
4746 tgl@sss.pgh.pa.us 16402 [ + - ]:CBC 3 : ereport(ERROR,
16403 : : (errcode(ERRCODE_COLLATION_MISMATCH),
16404 : : errmsg("child table \"%s\" has different collation for column \"%s\"",
16405 : : RelationGetRelationName(child_rel), parent_attname)));
16406 : :
16407 : : /*
16408 : : * If the parent has a not-null constraint that's not NO INHERIT,
16409 : : * make sure the child has one too.
16410 : : *
16411 : : * Other constraints are checked elsewhere.
16412 : : */
201 peter@eisentraut.org 16413 [ + + + + ]:GNC 3181 : if (parent_att->attnotnull && !child_att->attnotnull)
16414 : : {
16415 : : HeapTuple contup;
16416 : :
233 alvherre@alvh.no-ip. 16417 : 13 : contup = findNotNullConstraintAttnum(RelationGetRelid(parent_rel),
201 peter@eisentraut.org 16418 : 13 : parent_att->attnum);
226 alvherre@alvh.no-ip. 16419 [ + + ]: 13 : if (HeapTupleIsValid(contup) &&
16420 [ + + ]: 10 : !((Form_pg_constraint) GETSTRUCT(contup))->connoinherit)
233 16421 [ + - ]: 9 : ereport(ERROR,
16422 : : errcode(ERRCODE_DATATYPE_MISMATCH),
16423 : : errmsg("column \"%s\" in child table must be marked NOT NULL",
16424 : : parent_attname));
16425 : : }
16426 : :
16427 : : /*
16428 : : * Child column must be generated if and only if parent column is.
16429 : : */
201 peter@eisentraut.org 16430 [ + + + + ]: 3172 : if (parent_att->attgenerated && !child_att->attgenerated)
1076 peter@eisentraut.org 16431 [ + - ]:CBC 9 : ereport(ERROR,
16432 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
16433 : : errmsg("column \"%s\" in child table must be a generated column", parent_attname)));
201 peter@eisentraut.org 16434 [ + + + + ]:GNC 3163 : if (child_att->attgenerated && !parent_att->attgenerated)
459 tgl@sss.pgh.pa.us 16435 [ + - ]:CBC 6 : ereport(ERROR,
16436 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
16437 : : errmsg("column \"%s\" in child table must not be a generated column", parent_attname)));
16438 : :
16439 : : /*
16440 : : * Regular inheritance children are independent enough not to
16441 : : * inherit identity columns. But partitions are integral part of
16442 : : * a partitioned table and inherit identity column.
16443 : : */
89 peter@eisentraut.org 16444 [ + + ]:GNC 3157 : if (ispartition)
16445 : 2895 : child_att->attidentity = parent_att->attidentity;
16446 : :
16447 : : /*
16448 : : * OK, bump the child column's inheritance count. (If we fail
16449 : : * later on, this change will just roll back.)
16450 : : */
201 16451 : 3157 : child_att->attinhcount++;
16452 [ - + ]: 3157 : if (child_att->attinhcount < 0)
383 peter@eisentraut.org 16453 [ # # ]:UBC 0 : ereport(ERROR,
16454 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
16455 : : errmsg("too many inheritance parents"));
16456 : :
16457 : : /*
16458 : : * In case of partitions, we must enforce that value of attislocal
16459 : : * is same in all partitions. (Note: there are only inherited
16460 : : * attributes in partitions)
16461 : : */
201 peter@eisentraut.org 16462 [ + + ]:GNC 3157 : if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
16463 : : {
16464 [ - + ]: 2895 : Assert(child_att->attinhcount == 1);
16465 : 2895 : child_att->attislocal = false;
16466 : : }
16467 : :
2630 alvherre@alvh.no-ip. 16468 :CBC 3157 : CatalogTupleUpdate(attrrel, &tuple->t_self, tuple);
6393 tgl@sss.pgh.pa.us 16469 : 3157 : heap_freetuple(tuple);
16470 : : }
16471 : : else
16472 : : {
6496 bruce@momjian.us 16473 [ + - ]: 6 : ereport(ERROR,
16474 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
16475 : : errmsg("child table is missing column \"%s\"", parent_attname)));
16476 : : }
16477 : : }
16478 : :
1910 andres@anarazel.de 16479 : 1320 : table_close(attrrel, RowExclusiveLock);
6496 bruce@momjian.us 16480 : 1320 : }
16481 : :
16482 : : /*
16483 : : * Check constraints in child table match up with constraints in parent,
16484 : : * and increment their coninhcount.
16485 : : *
16486 : : * Constraints that are marked ONLY in the parent are ignored.
16487 : : *
16488 : : * Called by CreateInheritance
16489 : : *
16490 : : * Currently all constraints in parent must be present in the child. One day we
16491 : : * may consider adding new constraints like CREATE TABLE does.
16492 : : *
16493 : : * XXX This is O(N^2) which may be an issue with tables with hundreds of
16494 : : * constraints. As long as tables have more like 10 constraints it shouldn't be
16495 : : * a problem though. Even 100 constraints ought not be the end of the world.
16496 : : *
16497 : : * XXX See MergeWithExistingConstraint too if you change this code.
16498 : : */
16499 : : static void
neilc@samurai.com 16500 : 1320 : MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
16501 : : {
16502 : : Relation constraintrel;
16503 : : SysScanDesc parent_scan;
16504 : : ScanKeyData parent_key;
16505 : : HeapTuple parent_tuple;
233 alvherre@alvh.no-ip. 16506 :GNC 1320 : Oid parent_relid = RelationGetRelid(parent_rel);
16507 : :
201 peter@eisentraut.org 16508 : 1320 : constraintrel = table_open(ConstraintRelationId, RowExclusiveLock);
16509 : :
16510 : : /* Outer loop scans through the parent's constraint definitions */
5819 tgl@sss.pgh.pa.us 16511 :CBC 1320 : ScanKeyInit(&parent_key,
16512 : : Anum_pg_constraint_conrelid,
16513 : : BTEqualStrategyNumber, F_OIDEQ,
16514 : : ObjectIdGetDatum(parent_relid));
201 peter@eisentraut.org 16515 :GNC 1320 : parent_scan = systable_beginscan(constraintrel, ConstraintRelidTypidNameIndexId,
16516 : : true, NULL, 1, &parent_key);
16517 : :
5819 tgl@sss.pgh.pa.us 16518 [ + + ]:CBC 2002 : while (HeapTupleIsValid(parent_tuple = systable_getnext(parent_scan)))
16519 : : {
5421 bruce@momjian.us 16520 : 700 : Form_pg_constraint parent_con = (Form_pg_constraint) GETSTRUCT(parent_tuple);
16521 : : SysScanDesc child_scan;
16522 : : ScanKeyData child_key;
16523 : : HeapTuple child_tuple;
16524 : 700 : bool found = false;
16525 : :
233 alvherre@alvh.no-ip. 16526 [ + + ]:GNC 700 : if (parent_con->contype != CONSTRAINT_CHECK &&
16527 [ + + ]: 636 : parent_con->contype != CONSTRAINT_NOTNULL)
6496 bruce@momjian.us 16528 :CBC 411 : continue;
16529 : :
16530 : : /* if the parent's constraint is marked NO INHERIT, it's not inherited */
4377 alvherre@alvh.no-ip. 16531 [ + + ]: 339 : if (parent_con->connoinherit)
4472 16532 : 50 : continue;
16533 : :
16534 : : /* Search for a child constraint matching this one */
5819 tgl@sss.pgh.pa.us 16535 : 289 : ScanKeyInit(&child_key,
16536 : : Anum_pg_constraint_conrelid,
16537 : : BTEqualStrategyNumber, F_OIDEQ,
16538 : : ObjectIdGetDatum(RelationGetRelid(child_rel)));
201 peter@eisentraut.org 16539 :GNC 289 : child_scan = systable_beginscan(constraintrel, ConstraintRelidTypidNameIndexId,
16540 : : true, NULL, 1, &child_key);
16541 : :
5819 tgl@sss.pgh.pa.us 16542 [ + + ]:CBC 495 : while (HeapTupleIsValid(child_tuple = systable_getnext(child_scan)))
16543 : : {
5421 bruce@momjian.us 16544 : 480 : Form_pg_constraint child_con = (Form_pg_constraint) GETSTRUCT(child_tuple);
16545 : : HeapTuple child_copy;
16546 : :
233 alvherre@alvh.no-ip. 16547 [ + + ]:GNC 480 : if (child_con->contype != parent_con->contype)
5819 tgl@sss.pgh.pa.us 16548 :GBC 108 : continue;
16549 : :
16550 : : /*
16551 : : * CHECK constraint are matched by name, NOT NULL ones by
16552 : : * attribute number
16553 : : */
201 peter@eisentraut.org 16554 [ + + ]:GNC 372 : if (child_con->contype == CONSTRAINT_CHECK)
16555 : : {
16556 : 72 : if (strcmp(NameStr(parent_con->conname),
16557 [ + + ]: 57 : NameStr(child_con->conname)) != 0)
16558 : 15 : continue;
16559 : : }
233 alvherre@alvh.no-ip. 16560 [ + - ]: 315 : else if (child_con->contype == CONSTRAINT_NOTNULL)
16561 : : {
16562 : 315 : AttrNumber parent_attno = extractNotNullColumn(parent_tuple);
16563 : 315 : AttrNumber child_attno = extractNotNullColumn(child_tuple);
16564 : :
16565 [ + + ]: 315 : if (strcmp(get_attname(parent_relid, parent_attno, false),
16566 : 315 : get_attname(RelationGetRelid(child_rel), child_attno,
16567 : : false)) != 0)
16568 : 83 : continue;
16569 : : }
16570 : :
16571 [ + + ]: 274 : if (child_con->contype == CONSTRAINT_CHECK &&
201 peter@eisentraut.org 16572 [ + + ]: 42 : !constraints_equivalent(parent_tuple, child_tuple, RelationGetDescr(constraintrel)))
5819 tgl@sss.pgh.pa.us 16573 [ + - ]:CBC 3 : ereport(ERROR,
16574 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
16575 : : errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
16576 : : RelationGetRelationName(child_rel), NameStr(parent_con->conname))));
16577 : :
16578 : : /*
16579 : : * If the CHECK child constraint is "no inherit" then cannot
16580 : : * merge.
16581 : : *
16582 : : * This is not desirable for not-null constraints, mostly because
16583 : : * it breaks our pg_upgrade strategy, but it also makes sense on
16584 : : * its own: if a child has its own not-null constraint and then
16585 : : * acquires a parent with the same constraint, then we start to
16586 : : * enforce that constraint for all the descendants of that child
16587 : : * too, if any.
16588 : : */
233 alvherre@alvh.no-ip. 16589 [ + + ]:GNC 271 : if (child_con->contype == CONSTRAINT_CHECK &&
16590 [ - + ]: 39 : child_con->connoinherit)
4472 alvherre@alvh.no-ip. 16591 [ # # ]:UBC 0 : ereport(ERROR,
16592 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
16593 : : errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
16594 : : NameStr(child_con->conname), RelationGetRelationName(child_rel))));
16595 : :
16596 : : /*
16597 : : * If the child constraint is "not valid" then cannot merge with a
16598 : : * valid parent constraint
16599 : : */
2745 tgl@sss.pgh.pa.us 16600 [ + - - + ]:CBC 271 : if (parent_con->convalidated && !child_con->convalidated)
2745 tgl@sss.pgh.pa.us 16601 [ # # ]:UBC 0 : ereport(ERROR,
16602 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
16603 : : errmsg("constraint \"%s\" conflicts with NOT VALID constraint on child table \"%s\"",
16604 : : NameStr(child_con->conname), RelationGetRelationName(child_rel))));
16605 : :
16606 : : /*
16607 : : * OK, bump the child constraint's inheritance count. (If we fail
16608 : : * later on, this change will just roll back.)
16609 : : */
5819 tgl@sss.pgh.pa.us 16610 :CBC 271 : child_copy = heap_copytuple(child_tuple);
16611 : 271 : child_con = (Form_pg_constraint) GETSTRUCT(child_copy);
16612 : 271 : child_con->coninhcount++;
383 peter@eisentraut.org 16613 [ - + ]: 271 : if (child_con->coninhcount < 0)
383 peter@eisentraut.org 16614 [ # # ]:UBC 0 : ereport(ERROR,
16615 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
16616 : : errmsg("too many inheritance parents"));
233 alvherre@alvh.no-ip. 16617 [ + + ]:GNC 271 : if (child_con->contype == CONSTRAINT_NOTNULL &&
16618 [ - + ]: 232 : child_con->connoinherit)
233 alvherre@alvh.no-ip. 16619 :UNC 0 : child_con->connoinherit = false;
16620 : :
16621 : : /*
16622 : : * In case of partitions, an inherited constraint must be
16623 : : * inherited only once since it cannot have multiple parents and
16624 : : * it is never considered local.
16625 : : */
201 peter@eisentraut.org 16626 [ + + ]:GNC 271 : if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
16627 : : {
2685 rhaas@postgresql.org 16628 [ - + ]:CBC 234 : Assert(child_con->coninhcount == 1);
16629 : 234 : child_con->conislocal = false;
16630 : : }
16631 : :
201 peter@eisentraut.org 16632 :GNC 271 : CatalogTupleUpdate(constraintrel, &child_copy->t_self, child_copy);
5819 tgl@sss.pgh.pa.us 16633 :CBC 271 : heap_freetuple(child_copy);
16634 : :
16635 : 271 : found = true;
16636 : 271 : break;
16637 : : }
16638 : :
16639 : 286 : systable_endscan(child_scan);
16640 : :
6496 bruce@momjian.us 16641 [ + + ]: 286 : if (!found)
16642 : : {
226 alvherre@alvh.no-ip. 16643 [ + + ]:GNC 15 : if (parent_con->contype == CONSTRAINT_NOTNULL)
16644 [ + - ]: 3 : ereport(ERROR,
16645 : : errcode(ERRCODE_DATATYPE_MISMATCH),
16646 : : errmsg("column \"%s\" in child table must be marked NOT NULL",
16647 : : get_attname(parent_relid,
16648 : : extractNotNullColumn(parent_tuple),
16649 : : false)));
16650 : :
6496 bruce@momjian.us 16651 [ + - ]:CBC 12 : ereport(ERROR,
16652 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
16653 : : errmsg("child table is missing constraint \"%s\"",
16654 : : NameStr(parent_con->conname))));
16655 : : }
16656 : : }
16657 : :
5819 tgl@sss.pgh.pa.us 16658 : 1302 : systable_endscan(parent_scan);
201 peter@eisentraut.org 16659 :GNC 1302 : table_close(constraintrel, RowExclusiveLock);
6496 bruce@momjian.us 16660 :CBC 1302 : }
16661 : :
16662 : : /*
16663 : : * ALTER TABLE NO INHERIT
16664 : : *
16665 : : * Return value is the address of the relation that is no longer parent.
16666 : : */
16667 : : static ObjectAddress
2685 rhaas@postgresql.org 16668 : 22 : ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
16669 : : {
16670 : : ObjectAddress address;
16671 : : Relation parent_rel;
16672 : :
16673 [ - + ]: 22 : if (rel->rd_rel->relispartition)
2685 rhaas@postgresql.org 16674 [ # # ]:UBC 0 : ereport(ERROR,
16675 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
16676 : : errmsg("cannot change inheritance of a partition")));
16677 : :
16678 : : /*
16679 : : * AccessShareLock on the parent is probably enough, seeing that DROP
16680 : : * TABLE doesn't lock parent tables at all. We need some lock since we'll
16681 : : * be inspecting the parent's schema.
16682 : : */
1910 andres@anarazel.de 16683 :CBC 22 : parent_rel = table_openrv(parent, AccessShareLock);
16684 : :
16685 : : /*
16686 : : * We don't bother to check ownership of the parent table --- ownership of
16687 : : * the child is presumed enough rights.
16688 : : */
16689 : :
16690 : : /* Off to RemoveInheritance() where most of the work happens */
1116 alvherre@alvh.no-ip. 16691 : 22 : RemoveInheritance(rel, parent_rel, false);
16692 : :
16693 : : /*
16694 : : * If parent_rel has a primary key, then child_rel has not-null
16695 : : * constraints that make these columns as non nullable. Mark those
16696 : : * constraints as no longer inherited by this parent.
16697 : : */
233 alvherre@alvh.no-ip. 16698 :GNC 19 : ATInheritAdjustNotNulls(parent_rel, rel, -1);
16699 : :
16700 : : /*
16701 : : * If the parent has a primary key, then we decrement counts for all NOT
16702 : : * NULL constraints
16703 : : */
16704 : :
2685 rhaas@postgresql.org 16705 :CBC 19 : ObjectAddressSet(address, RelationRelationId,
16706 : : RelationGetRelid(parent_rel));
16707 : :
16708 : : /* keep our lock on the parent relation until commit */
1910 andres@anarazel.de 16709 : 19 : table_close(parent_rel, NoLock);
16710 : :
2685 rhaas@postgresql.org 16711 : 19 : return address;
16712 : : }
16713 : :
16714 : : /*
16715 : : * MarkInheritDetached
16716 : : *
16717 : : * Set inhdetachpending for a partition, for ATExecDetachPartition
16718 : : * in concurrent mode. While at it, verify that no other partition is
16719 : : * already pending detach.
16720 : : */
16721 : : static void
1116 alvherre@alvh.no-ip. 16722 : 73 : MarkInheritDetached(Relation child_rel, Relation parent_rel)
16723 : : {
16724 : : Relation catalogRelation;
16725 : : SysScanDesc scan;
16726 : : ScanKeyData key;
16727 : : HeapTuple inheritsTuple;
16728 : 73 : bool found = false;
16729 : :
16730 [ - + ]: 73 : Assert(parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
16731 : :
16732 : : /*
16733 : : * Find pg_inherits entries by inhparent. (We need to scan them all in
16734 : : * order to verify that no other partition is pending detach.)
16735 : : */
16736 : 73 : catalogRelation = table_open(InheritsRelationId, RowExclusiveLock);
16737 : 73 : ScanKeyInit(&key,
16738 : : Anum_pg_inherits_inhparent,
16739 : : BTEqualStrategyNumber, F_OIDEQ,
16740 : : ObjectIdGetDatum(RelationGetRelid(parent_rel)));
1082 16741 : 73 : scan = systable_beginscan(catalogRelation, InheritsParentIndexId,
16742 : : true, NULL, 1, &key);
16743 : :
1116 16744 [ + + ]: 288 : while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
16745 : : {
16746 : : Form_pg_inherits inhForm;
16747 : :
1082 16748 : 143 : inhForm = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
16749 [ + + ]: 143 : if (inhForm->inhdetachpending)
16750 [ + - ]: 1 : ereport(ERROR,
16751 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
16752 : : errmsg("partition \"%s\" already pending detach in partitioned table \"%s.%s\"",
16753 : : get_rel_name(inhForm->inhrelid),
16754 : : get_namespace_name(parent_rel->rd_rel->relnamespace),
16755 : : RelationGetRelationName(parent_rel)),
16756 : : errhint("Use ALTER TABLE ... DETACH PARTITION ... FINALIZE to complete the pending detach operation."));
16757 : :
16758 [ + + ]: 142 : if (inhForm->inhrelid == RelationGetRelid(child_rel))
16759 : : {
16760 : : HeapTuple newtup;
16761 : :
16762 : 72 : newtup = heap_copytuple(inheritsTuple);
16763 : 72 : ((Form_pg_inherits) GETSTRUCT(newtup))->inhdetachpending = true;
16764 : :
16765 : 72 : CatalogTupleUpdate(catalogRelation,
16766 : : &inheritsTuple->t_self,
16767 : : newtup);
16768 : 72 : found = true;
16769 : 72 : heap_freetuple(newtup);
16770 : : /* keep looking, to ensure we catch others pending detach */
16771 : : }
16772 : : }
16773 : :
16774 : : /* Done */
1116 16775 : 72 : systable_endscan(scan);
16776 : 72 : table_close(catalogRelation, RowExclusiveLock);
16777 : :
16778 [ - + ]: 72 : if (!found)
1116 alvherre@alvh.no-ip. 16779 [ # # ]:UBC 0 : ereport(ERROR,
16780 : : (errcode(ERRCODE_UNDEFINED_TABLE),
16781 : : errmsg("relation \"%s\" is not a partition of relation \"%s\"",
16782 : : RelationGetRelationName(child_rel),
16783 : : RelationGetRelationName(parent_rel))));
1116 alvherre@alvh.no-ip. 16784 :CBC 72 : }
16785 : :
16786 : : /*
16787 : : * RemoveInheritance
16788 : : *
16789 : : * Drop a parent from the child's parents. This just adjusts the attinhcount
16790 : : * and attislocal of the columns and removes the pg_inherit and pg_depend
16791 : : * entries. expect_detached is passed down to DeleteInheritsTuple, q.v..
16792 : : *
16793 : : * If attinhcount goes to 0 then attislocal gets set to true. If it goes back
16794 : : * up attislocal stays true, which means if a child is ever removed from a
16795 : : * parent then its columns will never be automatically dropped which may
16796 : : * surprise. But at least we'll never surprise by dropping columns someone
16797 : : * isn't expecting to be dropped which would actually mean data loss.
16798 : : *
16799 : : * coninhcount and conislocal for inherited constraints are adjusted in
16800 : : * exactly the same way.
16801 : : *
16802 : : * Common to ATExecDropInherit() and ATExecDetachPartition().
16803 : : */
16804 : : static void
16805 : 391 : RemoveInheritance(Relation child_rel, Relation parent_rel, bool expect_detached)
16806 : : {
16807 : : Relation catalogRelation;
16808 : : SysScanDesc scan;
16809 : : ScanKeyData key[3];
16810 : : HeapTuple attributeTuple,
16811 : : constraintTuple;
16812 : : List *connames;
16813 : : List *nncolumns;
16814 : : bool found;
16815 : : bool is_partitioning;
16816 : :
201 peter@eisentraut.org 16817 :GNC 391 : is_partitioning = (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
16818 : :
2277 alvherre@alvh.no-ip. 16819 :CBC 391 : found = DeleteInheritsTuple(RelationGetRelid(child_rel),
16820 : : RelationGetRelid(parent_rel),
16821 : : expect_detached,
1116 16822 : 391 : RelationGetRelationName(child_rel));
6496 bruce@momjian.us 16823 [ + + ]: 391 : if (!found)
16824 : : {
201 peter@eisentraut.org 16825 [ + + ]:GNC 12 : if (is_partitioning)
2685 rhaas@postgresql.org 16826 [ + - ]:CBC 9 : ereport(ERROR,
16827 : : (errcode(ERRCODE_UNDEFINED_TABLE),
16828 : : errmsg("relation \"%s\" is not a partition of relation \"%s\"",
16829 : : RelationGetRelationName(child_rel),
16830 : : RelationGetRelationName(parent_rel))));
16831 : : else
16832 [ + - ]: 3 : ereport(ERROR,
16833 : : (errcode(ERRCODE_UNDEFINED_TABLE),
16834 : : errmsg("relation \"%s\" is not a parent of relation \"%s\"",
16835 : : RelationGetRelationName(parent_rel),
16836 : : RelationGetRelationName(child_rel))));
16837 : : }
16838 : :
16839 : : /*
16840 : : * Search through child columns looking for ones matching parent rel
16841 : : */
1910 andres@anarazel.de 16842 : 379 : catalogRelation = table_open(AttributeRelationId, RowExclusiveLock);
6393 tgl@sss.pgh.pa.us 16843 : 379 : ScanKeyInit(&key[0],
16844 : : Anum_pg_attribute_attrelid,
16845 : : BTEqualStrategyNumber, F_OIDEQ,
16846 : : ObjectIdGetDatum(RelationGetRelid(child_rel)));
6496 bruce@momjian.us 16847 : 379 : scan = systable_beginscan(catalogRelation, AttributeRelidNumIndexId,
16848 : : true, NULL, 1, key);
16849 [ + + ]: 3589 : while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
16850 : : {
neilc@samurai.com 16851 : 3210 : Form_pg_attribute att = (Form_pg_attribute) GETSTRUCT(attributeTuple);
16852 : :
16853 : : /* Ignore if dropped or not inherited */
bruce@momjian.us 16854 [ + + ]: 3210 : if (att->attisdropped)
6496 bruce@momjian.us 16855 :GBC 18 : continue;
6393 tgl@sss.pgh.pa.us 16856 [ + + ]:CBC 3192 : if (att->attinhcount <= 0)
16857 : 2283 : continue;
16858 : :
16859 [ + + ]: 909 : if (SearchSysCacheExistsAttName(RelationGetRelid(parent_rel),
16860 : 909 : NameStr(att->attname)))
16861 : : {
16862 : : /* Decrement inhcount and possibly set islocal to true */
6496 bruce@momjian.us 16863 : 903 : HeapTuple copyTuple = heap_copytuple(attributeTuple);
neilc@samurai.com 16864 : 903 : Form_pg_attribute copy_att = (Form_pg_attribute) GETSTRUCT(copyTuple);
16865 : :
bruce@momjian.us 16866 : 903 : copy_att->attinhcount--;
16867 [ + - ]: 903 : if (copy_att->attinhcount == 0)
16868 : 903 : copy_att->attislocal = true;
16869 : :
2630 alvherre@alvh.no-ip. 16870 : 903 : CatalogTupleUpdate(catalogRelation, ©Tuple->t_self, copyTuple);
6496 bruce@momjian.us 16871 : 903 : heap_freetuple(copyTuple);
16872 : : }
16873 : : }
16874 : 379 : systable_endscan(scan);
1910 andres@anarazel.de 16875 : 379 : table_close(catalogRelation, RowExclusiveLock);
16876 : :
16877 : : /*
16878 : : * Likewise, find inherited check constraints and disinherit them. To do
16879 : : * this, we first need a list of the names of the parent's check
16880 : : * constraints. (We cheat a bit by only checking for name matches,
16881 : : * assuming that the expressions will match.)
16882 : : *
16883 : : * For NOT NULL columns, we store column numbers to match.
16884 : : */
16885 : 379 : catalogRelation = table_open(ConstraintRelationId, RowExclusiveLock);
5819 tgl@sss.pgh.pa.us 16886 : 379 : ScanKeyInit(&key[0],
16887 : : Anum_pg_constraint_conrelid,
16888 : : BTEqualStrategyNumber, F_OIDEQ,
16889 : : ObjectIdGetDatum(RelationGetRelid(parent_rel)));
2049 16890 : 379 : scan = systable_beginscan(catalogRelation, ConstraintRelidTypidNameIndexId,
16891 : : true, NULL, 1, key);
16892 : :
5819 16893 : 379 : connames = NIL;
233 alvherre@alvh.no-ip. 16894 :GNC 379 : nncolumns = NIL;
16895 : :
5819 tgl@sss.pgh.pa.us 16896 [ + + ]:CBC 537 : while (HeapTupleIsValid(constraintTuple = systable_getnext(scan)))
16897 : : {
16898 : 158 : Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(constraintTuple);
16899 : :
16900 [ + + ]: 158 : if (con->contype == CONSTRAINT_CHECK)
16901 : 9 : connames = lappend(connames, pstrdup(NameStr(con->conname)));
233 alvherre@alvh.no-ip. 16902 [ + + ]:GNC 158 : if (con->contype == CONSTRAINT_NOTNULL)
16903 : 40 : nncolumns = lappend_int(nncolumns, extractNotNullColumn(constraintTuple));
16904 : : }
16905 : :
5819 tgl@sss.pgh.pa.us 16906 :CBC 379 : systable_endscan(scan);
16907 : :
16908 : : /* Now scan the child's constraints */
16909 : 379 : ScanKeyInit(&key[0],
16910 : : Anum_pg_constraint_conrelid,
16911 : : BTEqualStrategyNumber, F_OIDEQ,
16912 : : ObjectIdGetDatum(RelationGetRelid(child_rel)));
2049 16913 : 379 : scan = systable_beginscan(catalogRelation, ConstraintRelidTypidNameIndexId,
16914 : : true, NULL, 1, key);
16915 : :
5819 16916 [ + + ]: 635 : while (HeapTupleIsValid(constraintTuple = systable_getnext(scan)))
16917 : : {
16918 : 256 : Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(constraintTuple);
233 alvherre@alvh.no-ip. 16919 :GNC 256 : bool match = false;
16920 : : ListCell *lc;
16921 : :
16922 : : /*
16923 : : * Match CHECK constraints by name, not-null constraints by column
16924 : : * number, and ignore all others.
16925 : : */
16926 [ + + ]: 256 : if (con->contype == CONSTRAINT_CHECK)
16927 : : {
16928 [ + + + + : 95 : foreach(lc, connames)
+ + ]
16929 : : {
16930 [ + - ]: 15 : if (con->contype == CONSTRAINT_CHECK &&
16931 [ + + ]: 15 : strcmp(NameStr(con->conname), (char *) lfirst(lc)) == 0)
16932 : : {
16933 : 9 : match = true;
16934 : 9 : break;
16935 : : }
16936 : : }
16937 : : }
16938 [ + + ]: 167 : else if (con->contype == CONSTRAINT_NOTNULL)
16939 : : {
16940 : 49 : AttrNumber child_attno = extractNotNullColumn(constraintTuple);
16941 : :
16942 [ + + + + : 64 : foreach(lc, nncolumns)
+ + ]
16943 : : {
16944 [ + + ]: 55 : if (lfirst_int(lc) == child_attno)
16945 : : {
16946 : 40 : match = true;
16947 : 40 : break;
16948 : : }
16949 : : }
16950 : : }
16951 : : else
16952 : 118 : continue;
16953 : :
5819 tgl@sss.pgh.pa.us 16954 [ + + ]:CBC 138 : if (match)
16955 : : {
16956 : : /* Decrement inhcount and possibly set islocal to true */
16957 : 49 : HeapTuple copyTuple = heap_copytuple(constraintTuple);
16958 : 49 : Form_pg_constraint copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
16959 : :
2489 16960 [ - + ]: 49 : if (copy_con->coninhcount <= 0) /* shouldn't happen */
5819 tgl@sss.pgh.pa.us 16961 [ # # ]:UBC 0 : elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
16962 : : RelationGetRelid(child_rel), NameStr(copy_con->conname));
16963 : :
5819 tgl@sss.pgh.pa.us 16964 :CBC 49 : copy_con->coninhcount--;
16965 [ + - ]: 49 : if (copy_con->coninhcount == 0)
16966 : 49 : copy_con->conislocal = true;
16967 : :
2630 alvherre@alvh.no-ip. 16968 : 49 : CatalogTupleUpdate(catalogRelation, ©Tuple->t_self, copyTuple);
5819 tgl@sss.pgh.pa.us 16969 : 49 : heap_freetuple(copyTuple);
16970 : : }
16971 : : }
16972 : :
16973 : 379 : systable_endscan(scan);
1910 andres@anarazel.de 16974 : 379 : table_close(catalogRelation, RowExclusiveLock);
16975 : :
2685 rhaas@postgresql.org 16976 [ + + ]: 379 : drop_parent_dependency(RelationGetRelid(child_rel),
16977 : : RelationRelationId,
16978 : : RelationGetRelid(parent_rel),
16979 : : child_dependency_type(is_partitioning));
16980 : :
16981 : : /*
16982 : : * Post alter hook of this inherits. Since object_access_hook doesn't take
16983 : : * multiple object identifiers, we relay oid of parent relation using
16984 : : * auxiliary_id argument.
16985 : : */
4046 16986 [ - + ]: 379 : InvokeObjectPostAlterHookArg(InheritsRelationId,
16987 : : RelationGetRelid(child_rel), 0,
16988 : : RelationGetRelid(parent_rel), false);
4743 16989 : 379 : }
16990 : :
16991 : : /*
16992 : : * Adjust coninhcount of not-null constraints upwards or downwards when a
16993 : : * table is marked as inheriting or no longer doing so a table with a primary
16994 : : * key.
16995 : : *
16996 : : * Note: these constraints are not dropped, even if their inhcount goes to zero
16997 : : * and conislocal is false. Instead we mark the constraints as locally defined.
16998 : : * This is seen as more useful behavior, with no downsides. The user can always
16999 : : * drop them afterwards.
17000 : : */
17001 : : static void
233 alvherre@alvh.no-ip. 17002 :GNC 134 : ATInheritAdjustNotNulls(Relation parent_rel, Relation child_rel, int inhcount)
17003 : : {
17004 : : Bitmapset *pkattnos;
17005 : :
17006 : : /* Quick exit when parent has no PK */
17007 [ + + ]: 134 : if (!parent_rel->rd_rel->relhasindex)
17008 : 113 : return;
17009 : :
17010 : 21 : pkattnos = RelationGetIndexAttrBitmap(parent_rel,
17011 : : INDEX_ATTR_BITMAP_PRIMARY_KEY);
17012 [ + + ]: 21 : if (pkattnos != NULL)
17013 : : {
17014 : 18 : Bitmapset *childattnums = NULL;
17015 : : AttrMap *attmap;
17016 : : int i;
17017 : :
17018 : 18 : attmap = build_attrmap_by_name(RelationGetDescr(parent_rel),
17019 : : RelationGetDescr(child_rel), true);
17020 : :
17021 : 18 : i = -1;
17022 [ + + ]: 36 : while ((i = bms_next_member(pkattnos, i)) >= 0)
17023 : : {
17024 : 18 : childattnums = bms_add_member(childattnums,
17025 : 18 : attmap->attnums[i + FirstLowInvalidHeapAttributeNumber - 1]);
17026 : : }
17027 : :
17028 : : /*
17029 : : * CCI is needed in case there's a NOT NULL PRIMARY KEY column in the
17030 : : * parent: the relevant not-null constraint in the child already had
17031 : : * its inhcount modified earlier.
17032 : : */
17033 : 18 : CommandCounterIncrement();
17034 : 18 : AdjustNotNullInheritance(RelationGetRelid(child_rel), childattnums,
17035 : : inhcount);
17036 : : }
17037 : : }
17038 : :
17039 : : /*
17040 : : * Drop the dependency created by StoreCatalogInheritance1 (CREATE TABLE
17041 : : * INHERITS/ALTER TABLE INHERIT -- refclassid will be RelationRelationId) or
17042 : : * heap_create_with_catalog (CREATE TABLE OF/ALTER TABLE OF -- refclassid will
17043 : : * be TypeRelationId). There's no convenient way to do this, so go trawling
17044 : : * through pg_depend.
17045 : : */
17046 : : static void
2497 rhaas@postgresql.org 17047 :CBC 385 : drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid,
17048 : : DependencyType deptype)
17049 : : {
17050 : : Relation catalogRelation;
17051 : : SysScanDesc scan;
17052 : : ScanKeyData key[3];
17053 : : HeapTuple depTuple;
17054 : :
1910 andres@anarazel.de 17055 : 385 : catalogRelation = table_open(DependRelationId, RowExclusiveLock);
17056 : :
6496 bruce@momjian.us 17057 : 385 : ScanKeyInit(&key[0],
17058 : : Anum_pg_depend_classid,
17059 : : BTEqualStrategyNumber, F_OIDEQ,
17060 : : ObjectIdGetDatum(RelationRelationId));
17061 : 385 : ScanKeyInit(&key[1],
17062 : : Anum_pg_depend_objid,
17063 : : BTEqualStrategyNumber, F_OIDEQ,
17064 : : ObjectIdGetDatum(relid));
6393 tgl@sss.pgh.pa.us 17065 : 385 : ScanKeyInit(&key[2],
17066 : : Anum_pg_depend_objsubid,
17067 : : BTEqualStrategyNumber, F_INT4EQ,
17068 : : Int32GetDatum(0));
17069 : :
6496 bruce@momjian.us 17070 : 385 : scan = systable_beginscan(catalogRelation, DependDependerIndexId, true,
17071 : : NULL, 3, key);
17072 : :
17073 [ + + ]: 1170 : while (HeapTupleIsValid(depTuple = systable_getnext(scan)))
17074 : : {
17075 : 785 : Form_pg_depend dep = (Form_pg_depend) GETSTRUCT(depTuple);
17076 : :
4743 rhaas@postgresql.org 17077 [ + + ]: 785 : if (dep->refclassid == refclassid &&
17078 [ + + ]: 391 : dep->refobjid == refobjid &&
6393 tgl@sss.pgh.pa.us 17079 [ + - ]: 385 : dep->refobjsubid == 0 &&
2497 rhaas@postgresql.org 17080 [ + - ]: 385 : dep->deptype == deptype)
2629 tgl@sss.pgh.pa.us 17081 : 385 : CatalogTupleDelete(catalogRelation, &depTuple->t_self);
17082 : : }
17083 : :
6496 neilc@samurai.com 17084 : 385 : systable_endscan(scan);
1910 andres@anarazel.de 17085 : 385 : table_close(catalogRelation, RowExclusiveLock);
4743 rhaas@postgresql.org 17086 : 385 : }
17087 : :
17088 : : /*
17089 : : * ALTER TABLE OF
17090 : : *
17091 : : * Attach a table to a composite type, as though it had been created with CREATE
17092 : : * TABLE OF. All attname, atttypid, atttypmod and attcollation must match. The
17093 : : * subject table must not have inheritance parents. These restrictions ensure
17094 : : * that you cannot create a configuration impossible with CREATE TABLE OF alone.
17095 : : *
17096 : : * The address of the type is returned.
17097 : : */
17098 : : static ObjectAddress
17099 : 33 : ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
17100 : : {
17101 : 33 : Oid relid = RelationGetRelid(rel);
17102 : : Type typetuple;
17103 : : Form_pg_type typeform;
17104 : : Oid typeid;
17105 : : Relation inheritsRelation,
17106 : : relationRelation;
17107 : : SysScanDesc scan;
17108 : : ScanKeyData key;
17109 : : AttrNumber table_attno,
17110 : : type_attno;
17111 : : TupleDesc typeTupleDesc,
17112 : : tableTupleDesc;
17113 : : ObjectAddress tableobj,
17114 : : typeobj;
17115 : : HeapTuple classtuple;
17116 : :
17117 : : /* Validate the type. */
17118 : 33 : typetuple = typenameType(NULL, ofTypename, NULL);
17119 : 33 : check_of_type(typetuple);
1972 andres@anarazel.de 17120 : 33 : typeform = (Form_pg_type) GETSTRUCT(typetuple);
17121 : 33 : typeid = typeform->oid;
17122 : :
17123 : : /* Fail if the table has any inheritance parents. */
1910 17124 : 33 : inheritsRelation = table_open(InheritsRelationId, AccessShareLock);
4743 rhaas@postgresql.org 17125 : 33 : ScanKeyInit(&key,
17126 : : Anum_pg_inherits_inhrelid,
17127 : : BTEqualStrategyNumber, F_OIDEQ,
17128 : : ObjectIdGetDatum(relid));
17129 : 33 : scan = systable_beginscan(inheritsRelation, InheritsRelidSeqnoIndexId,
17130 : : true, NULL, 1, &key);
17131 [ + + ]: 33 : if (HeapTupleIsValid(systable_getnext(scan)))
17132 [ + - ]: 3 : ereport(ERROR,
17133 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17134 : : errmsg("typed tables cannot inherit")));
17135 : 30 : systable_endscan(scan);
1910 andres@anarazel.de 17136 : 30 : table_close(inheritsRelation, AccessShareLock);
17137 : :
17138 : : /*
17139 : : * Check the tuple descriptors for compatibility. Unlike inheritance, we
17140 : : * require that the order also match. However, attnotnull need not match.
17141 : : */
4743 rhaas@postgresql.org 17142 : 30 : typeTupleDesc = lookup_rowtype_tupdesc(typeid, -1);
17143 : 30 : tableTupleDesc = RelationGetDescr(rel);
17144 : 30 : table_attno = 1;
17145 [ + + ]: 95 : for (type_attno = 1; type_attno <= typeTupleDesc->natts; type_attno++)
17146 : : {
17147 : : Form_pg_attribute type_attr,
17148 : : table_attr;
17149 : : const char *type_attname,
17150 : : *table_attname;
17151 : :
17152 : : /* Get the next non-dropped type attribute. */
2429 andres@anarazel.de 17153 : 77 : type_attr = TupleDescAttr(typeTupleDesc, type_attno - 1);
4743 rhaas@postgresql.org 17154 [ + + ]: 77 : if (type_attr->attisdropped)
17155 : 22 : continue;
17156 : 55 : type_attname = NameStr(type_attr->attname);
17157 : :
17158 : : /* Get the next non-dropped table attribute. */
17159 : : do
17160 : : {
17161 [ + + ]: 61 : if (table_attno > tableTupleDesc->natts)
17162 [ + - ]: 3 : ereport(ERROR,
17163 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
17164 : : errmsg("table is missing column \"%s\"",
17165 : : type_attname)));
2429 andres@anarazel.de 17166 : 58 : table_attr = TupleDescAttr(tableTupleDesc, table_attno - 1);
17167 : 58 : table_attno++;
4743 rhaas@postgresql.org 17168 [ + + ]: 58 : } while (table_attr->attisdropped);
17169 : 52 : table_attname = NameStr(table_attr->attname);
17170 : :
17171 : : /* Compare name. */
17172 [ + + ]: 52 : if (strncmp(table_attname, type_attname, NAMEDATALEN) != 0)
17173 [ + - ]: 3 : ereport(ERROR,
17174 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
17175 : : errmsg("table has column \"%s\" where type requires \"%s\"",
17176 : : table_attname, type_attname)));
17177 : :
17178 : : /* Compare type. */
17179 [ + + ]: 49 : if (table_attr->atttypid != type_attr->atttypid ||
17180 [ + + ]: 46 : table_attr->atttypmod != type_attr->atttypmod ||
17181 [ - + ]: 43 : table_attr->attcollation != type_attr->attcollation)
17182 [ + - ]: 6 : ereport(ERROR,
17183 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
17184 : : errmsg("table \"%s\" has different type for column \"%s\"",
17185 : : RelationGetRelationName(rel), type_attname)));
17186 : : }
851 tgl@sss.pgh.pa.us 17187 [ + - ]: 18 : ReleaseTupleDesc(typeTupleDesc);
17188 : :
17189 : : /* Any remaining columns at the end of the table had better be dropped. */
4743 rhaas@postgresql.org 17190 [ + + ]: 18 : for (; table_attno <= tableTupleDesc->natts; table_attno++)
17191 : : {
2429 andres@anarazel.de 17192 : 3 : Form_pg_attribute table_attr = TupleDescAttr(tableTupleDesc,
17193 : : table_attno - 1);
17194 : :
4743 rhaas@postgresql.org 17195 [ + - ]: 3 : if (!table_attr->attisdropped)
17196 [ + - ]: 3 : ereport(ERROR,
17197 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
17198 : : errmsg("table has extra column \"%s\"",
17199 : : NameStr(table_attr->attname))));
17200 : : }
17201 : :
17202 : : /* If the table was already typed, drop the existing dependency. */
17203 [ + + ]: 15 : if (rel->rd_rel->reloftype)
2497 17204 : 3 : drop_parent_dependency(relid, TypeRelationId, rel->rd_rel->reloftype,
17205 : : DEPENDENCY_NORMAL);
17206 : :
17207 : : /* Record a dependency on the new type. */
4743 17208 : 15 : tableobj.classId = RelationRelationId;
17209 : 15 : tableobj.objectId = relid;
17210 : 15 : tableobj.objectSubId = 0;
17211 : 15 : typeobj.classId = TypeRelationId;
17212 : 15 : typeobj.objectId = typeid;
17213 : 15 : typeobj.objectSubId = 0;
17214 : 15 : recordDependencyOn(&tableobj, &typeobj, DEPENDENCY_NORMAL);
17215 : :
17216 : : /* Update pg_class.reloftype */
1910 andres@anarazel.de 17217 : 15 : relationRelation = table_open(RelationRelationId, RowExclusiveLock);
4743 rhaas@postgresql.org 17218 : 15 : classtuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
17219 [ - + ]: 15 : if (!HeapTupleIsValid(classtuple))
4743 rhaas@postgresql.org 17220 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
4743 rhaas@postgresql.org 17221 :CBC 15 : ((Form_pg_class) GETSTRUCT(classtuple))->reloftype = typeid;
2630 alvherre@alvh.no-ip. 17222 : 15 : CatalogTupleUpdate(relationRelation, &classtuple->t_self, classtuple);
17223 : :
4046 rhaas@postgresql.org 17224 [ - + ]: 15 : InvokeObjectPostAlterHook(RelationRelationId, relid, 0);
17225 : :
4743 17226 : 15 : heap_freetuple(classtuple);
1910 andres@anarazel.de 17227 : 15 : table_close(relationRelation, RowExclusiveLock);
17228 : :
4743 rhaas@postgresql.org 17229 : 15 : ReleaseSysCache(typetuple);
17230 : :
3308 alvherre@alvh.no-ip. 17231 : 15 : return typeobj;
17232 : : }
17233 : :
17234 : : /*
17235 : : * ALTER TABLE NOT OF
17236 : : *
17237 : : * Detach a typed table from its originating type. Just clear reloftype and
17238 : : * remove the dependency.
17239 : : */
17240 : : static void
4743 rhaas@postgresql.org 17241 : 3 : ATExecDropOf(Relation rel, LOCKMODE lockmode)
17242 : : {
17243 : 3 : Oid relid = RelationGetRelid(rel);
17244 : : Relation relationRelation;
17245 : : HeapTuple tuple;
17246 : :
17247 [ - + ]: 3 : if (!OidIsValid(rel->rd_rel->reloftype))
4743 rhaas@postgresql.org 17248 [ # # ]:UBC 0 : ereport(ERROR,
17249 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17250 : : errmsg("\"%s\" is not a typed table",
17251 : : RelationGetRelationName(rel))));
17252 : :
17253 : : /*
17254 : : * We don't bother to check ownership of the type --- ownership of the
17255 : : * table is presumed enough rights. No lock required on the type, either.
17256 : : */
17257 : :
2497 rhaas@postgresql.org 17258 :CBC 3 : drop_parent_dependency(relid, TypeRelationId, rel->rd_rel->reloftype,
17259 : : DEPENDENCY_NORMAL);
17260 : :
17261 : : /* Clear pg_class.reloftype */
1910 andres@anarazel.de 17262 : 3 : relationRelation = table_open(RelationRelationId, RowExclusiveLock);
4743 rhaas@postgresql.org 17263 : 3 : tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
17264 [ - + ]: 3 : if (!HeapTupleIsValid(tuple))
4743 rhaas@postgresql.org 17265 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
4743 rhaas@postgresql.org 17266 :CBC 3 : ((Form_pg_class) GETSTRUCT(tuple))->reloftype = InvalidOid;
2630 alvherre@alvh.no-ip. 17267 : 3 : CatalogTupleUpdate(relationRelation, &tuple->t_self, tuple);
17268 : :
4046 rhaas@postgresql.org 17269 [ - + ]: 3 : InvokeObjectPostAlterHook(RelationRelationId, relid, 0);
17270 : :
4743 17271 : 3 : heap_freetuple(tuple);
1910 andres@anarazel.de 17272 : 3 : table_close(relationRelation, RowExclusiveLock);
6496 bruce@momjian.us 17273 : 3 : }
17274 : :
17275 : : /*
17276 : : * relation_mark_replica_identity: Update a table's replica identity
17277 : : *
17278 : : * Iff ri_type = REPLICA_IDENTITY_INDEX, indexOid must be the Oid of a suitable
17279 : : * index. Otherwise, it must be InvalidOid.
17280 : : *
17281 : : * Caller had better hold an exclusive lock on the relation, as the results
17282 : : * of running two of these concurrently wouldn't be pretty.
17283 : : */
17284 : : static void
3810 rhaas@postgresql.org 17285 : 200 : relation_mark_replica_identity(Relation rel, char ri_type, Oid indexOid,
17286 : : bool is_internal)
17287 : : {
17288 : : Relation pg_index;
17289 : : Relation pg_class;
17290 : : HeapTuple pg_class_tuple;
17291 : : HeapTuple pg_index_tuple;
17292 : : Form_pg_class pg_class_form;
17293 : : Form_pg_index pg_index_form;
17294 : : ListCell *index;
17295 : :
17296 : : /*
17297 : : * Check whether relreplident has changed, and update it if so.
17298 : : */
1910 andres@anarazel.de 17299 : 200 : pg_class = table_open(RelationRelationId, RowExclusiveLock);
3810 rhaas@postgresql.org 17300 : 200 : pg_class_tuple = SearchSysCacheCopy1(RELOID,
17301 : : ObjectIdGetDatum(RelationGetRelid(rel)));
17302 [ - + ]: 200 : if (!HeapTupleIsValid(pg_class_tuple))
3810 rhaas@postgresql.org 17303 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation \"%s\"",
17304 : : RelationGetRelationName(rel));
3810 rhaas@postgresql.org 17305 :CBC 200 : pg_class_form = (Form_pg_class) GETSTRUCT(pg_class_tuple);
17306 [ + + ]: 200 : if (pg_class_form->relreplident != ri_type)
17307 : : {
17308 : 175 : pg_class_form->relreplident = ri_type;
2630 alvherre@alvh.no-ip. 17309 : 175 : CatalogTupleUpdate(pg_class, &pg_class_tuple->t_self, pg_class_tuple);
17310 : : }
1910 andres@anarazel.de 17311 : 200 : table_close(pg_class, RowExclusiveLock);
3810 rhaas@postgresql.org 17312 : 200 : heap_freetuple(pg_class_tuple);
17313 : :
17314 : : /*
17315 : : * Update the per-index indisreplident flags correctly.
17316 : : */
1910 andres@anarazel.de 17317 : 200 : pg_index = table_open(IndexRelationId, RowExclusiveLock);
3810 rhaas@postgresql.org 17318 [ + + + + : 539 : foreach(index, RelationGetIndexList(rel))
+ + ]
17319 : : {
17320 : 339 : Oid thisIndexOid = lfirst_oid(index);
17321 : 339 : bool dirty = false;
17322 : :
17323 : 339 : pg_index_tuple = SearchSysCacheCopy1(INDEXRELID,
17324 : : ObjectIdGetDatum(thisIndexOid));
17325 [ - + ]: 339 : if (!HeapTupleIsValid(pg_index_tuple))
3810 rhaas@postgresql.org 17326 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for index %u", thisIndexOid);
3810 rhaas@postgresql.org 17327 :CBC 339 : pg_index_form = (Form_pg_index) GETSTRUCT(pg_index_tuple);
17328 : :
449 tgl@sss.pgh.pa.us 17329 [ + + ]: 339 : if (thisIndexOid == indexOid)
17330 : : {
17331 : : /* Set the bit if not already set. */
17332 [ + + ]: 110 : if (!pg_index_form->indisreplident)
17333 : : {
17334 : 101 : dirty = true;
17335 : 101 : pg_index_form->indisreplident = true;
17336 : : }
17337 : : }
17338 : : else
17339 : : {
17340 : : /* Unset the bit if set. */
17341 [ + + ]: 229 : if (pg_index_form->indisreplident)
17342 : : {
17343 : 23 : dirty = true;
17344 : 23 : pg_index_form->indisreplident = false;
17345 : : }
17346 : : }
17347 : :
3810 rhaas@postgresql.org 17348 [ + + ]: 339 : if (dirty)
17349 : : {
2630 alvherre@alvh.no-ip. 17350 : 124 : CatalogTupleUpdate(pg_index, &pg_index_tuple->t_self, pg_index_tuple);
3810 rhaas@postgresql.org 17351 [ - + ]: 124 : InvokeObjectPostAlterHookArg(IndexRelationId, thisIndexOid, 0,
17352 : : InvalidOid, is_internal);
17353 : :
17354 : : /*
17355 : : * Invalidate the relcache for the table, so that after we commit
17356 : : * all sessions will refresh the table's replica identity index
17357 : : * before attempting any UPDATE or DELETE on the table. (If we
17358 : : * changed the table's pg_class row above, then a relcache inval
17359 : : * is already queued due to that; but we might not have.)
17360 : : */
880 akapila@postgresql.o 17361 : 124 : CacheInvalidateRelcache(rel);
17362 : : }
3810 rhaas@postgresql.org 17363 : 339 : heap_freetuple(pg_index_tuple);
17364 : : }
17365 : :
1910 andres@anarazel.de 17366 : 200 : table_close(pg_index, RowExclusiveLock);
3810 rhaas@postgresql.org 17367 : 200 : }
17368 : :
17369 : : /*
17370 : : * ALTER TABLE <name> REPLICA IDENTITY ...
17371 : : */
17372 : : static void
17373 : 224 : ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode)
17374 : : {
17375 : : Oid indexOid;
17376 : : Relation indexRel;
17377 : : int key;
17378 : :
17379 [ + + ]: 224 : if (stmt->identity_type == REPLICA_IDENTITY_DEFAULT)
17380 : : {
17381 : 3 : relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
17382 : 3 : return;
17383 : : }
17384 [ + + ]: 221 : else if (stmt->identity_type == REPLICA_IDENTITY_FULL)
17385 : : {
17386 : 69 : relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
17387 : 69 : return;
17388 : : }
17389 [ + + ]: 152 : else if (stmt->identity_type == REPLICA_IDENTITY_NOTHING)
17390 : : {
17391 : 18 : relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
17392 : 18 : return;
17393 : : }
17394 [ - + ]: 134 : else if (stmt->identity_type == REPLICA_IDENTITY_INDEX)
17395 : : {
17396 : : /* fallthrough */ ;
17397 : : }
17398 : : else
3810 rhaas@postgresql.org 17399 [ # # ]:UBC 0 : elog(ERROR, "unexpected identity type %u", stmt->identity_type);
17400 : :
17401 : : /* Check that the index exists */
3810 rhaas@postgresql.org 17402 :CBC 134 : indexOid = get_relname_relid(stmt->name, rel->rd_rel->relnamespace);
17403 [ - + ]: 134 : if (!OidIsValid(indexOid))
3810 rhaas@postgresql.org 17404 [ # # ]:UBC 0 : ereport(ERROR,
17405 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
17406 : : errmsg("index \"%s\" for table \"%s\" does not exist",
17407 : : stmt->name, RelationGetRelationName(rel))));
17408 : :
3810 rhaas@postgresql.org 17409 :CBC 134 : indexRel = index_open(indexOid, ShareLock);
17410 : :
17411 : : /* Check that the index is on the relation we're altering. */
17412 [ + - ]: 134 : if (indexRel->rd_index == NULL ||
17413 [ + + ]: 134 : indexRel->rd_index->indrelid != RelationGetRelid(rel))
17414 [ + - ]: 3 : ereport(ERROR,
17415 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17416 : : errmsg("\"%s\" is not an index for table \"%s\"",
17417 : : RelationGetRelationName(indexRel),
17418 : : RelationGetRelationName(rel))));
17419 : : /* The AM must support uniqueness, and the index must in fact be unique. */
1910 andres@anarazel.de 17420 [ + + ]: 131 : if (!indexRel->rd_indam->amcanunique ||
3010 tgl@sss.pgh.pa.us 17421 [ + + ]: 128 : !indexRel->rd_index->indisunique)
3810 rhaas@postgresql.org 17422 [ + - ]: 6 : ereport(ERROR,
17423 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17424 : : errmsg("cannot use non-unique index \"%s\" as replica identity",
17425 : : RelationGetRelationName(indexRel))));
17426 : : /* Deferred indexes are not guaranteed to be always unique. */
17427 [ + + ]: 125 : if (!indexRel->rd_index->indimmediate)
17428 [ + - ]: 6 : ereport(ERROR,
17429 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
17430 : : errmsg("cannot use non-immediate index \"%s\" as replica identity",
17431 : : RelationGetRelationName(indexRel))));
17432 : : /* Expression indexes aren't supported. */
17433 [ + + ]: 119 : if (RelationGetIndexExpressions(indexRel) != NIL)
17434 [ + - ]: 3 : ereport(ERROR,
17435 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
17436 : : errmsg("cannot use expression index \"%s\" as replica identity",
17437 : : RelationGetRelationName(indexRel))));
17438 : : /* Predicate indexes aren't supported. */
17439 [ + + ]: 116 : if (RelationGetIndexPredicate(indexRel) != NIL)
17440 [ + - ]: 3 : ereport(ERROR,
17441 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
17442 : : errmsg("cannot use partial index \"%s\" as replica identity",
17443 : : RelationGetRelationName(indexRel))));
17444 : :
17445 : : /* Check index for nullable columns. */
2199 teodor@sigaev.ru 17446 [ + + ]: 249 : for (key = 0; key < IndexRelationGetNumberOfKeyAttributes(indexRel); key++)
17447 : : {
3631 bruce@momjian.us 17448 : 139 : int16 attno = indexRel->rd_index->indkey.values[key];
17449 : : Form_pg_attribute attr;
17450 : :
17451 : : /*
17452 : : * Reject any other system columns. (Going forward, we'll disallow
17453 : : * indexes containing such columns in the first place, but they might
17454 : : * exist in older branches.)
17455 : : */
2921 tgl@sss.pgh.pa.us 17456 [ - + ]: 139 : if (attno <= 0)
2921 tgl@sss.pgh.pa.us 17457 [ # # ]:UBC 0 : ereport(ERROR,
17458 : : (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
17459 : : errmsg("index \"%s\" cannot be used as replica identity because column %d is a system column",
17460 : : RelationGetRelationName(indexRel), attno)));
17461 : :
2429 andres@anarazel.de 17462 :CBC 139 : attr = TupleDescAttr(rel->rd_att, attno - 1);
3810 rhaas@postgresql.org 17463 [ + + ]: 139 : if (!attr->attnotnull)
17464 [ + - ]: 3 : ereport(ERROR,
17465 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
17466 : : errmsg("index \"%s\" cannot be used as replica identity because column \"%s\" is nullable",
17467 : : RelationGetRelationName(indexRel),
17468 : : NameStr(attr->attname))));
17469 : : }
17470 : :
17471 : : /* This index is suitable for use as a replica identity. Mark it. */
17472 : 110 : relation_mark_replica_identity(rel, stmt->identity_type, indexOid, true);
17473 : :
17474 : 110 : index_close(indexRel, NoLock);
17475 : : }
17476 : :
17477 : : /*
17478 : : * ALTER TABLE ENABLE/DISABLE ROW LEVEL SECURITY
17479 : : */
17480 : : static void
1139 michael@paquier.xyz 17481 : 144 : ATExecSetRowSecurity(Relation rel, bool rls)
17482 : : {
17483 : : Relation pg_class;
17484 : : Oid relid;
17485 : : HeapTuple tuple;
17486 : :
3495 sfrost@snowman.net 17487 : 144 : relid = RelationGetRelid(rel);
17488 : :
17489 : : /* Pull the record for this relation and update it */
1910 andres@anarazel.de 17490 : 144 : pg_class = table_open(RelationRelationId, RowExclusiveLock);
17491 : :
3495 sfrost@snowman.net 17492 : 144 : tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
17493 : :
17494 [ - + ]: 144 : if (!HeapTupleIsValid(tuple))
3495 sfrost@snowman.net 17495 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
17496 : :
1139 michael@paquier.xyz 17497 :CBC 144 : ((Form_pg_class) GETSTRUCT(tuple))->relrowsecurity = rls;
2630 alvherre@alvh.no-ip. 17498 : 144 : CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
17499 : :
241 michael@paquier.xyz 17500 [ + + ]:GNC 144 : InvokeObjectPostAlterHook(RelationRelationId,
17501 : : RelationGetRelid(rel), 0);
17502 : :
1910 andres@anarazel.de 17503 :CBC 144 : table_close(pg_class, RowExclusiveLock);
3115 sfrost@snowman.net 17504 : 144 : heap_freetuple(tuple);
17505 : 144 : }
17506 : :
17507 : : /*
17508 : : * ALTER TABLE FORCE/NO FORCE ROW LEVEL SECURITY
17509 : : */
17510 : : static void
17511 : 57 : ATExecForceNoForceRowSecurity(Relation rel, bool force_rls)
17512 : : {
17513 : : Relation pg_class;
17514 : : Oid relid;
17515 : : HeapTuple tuple;
17516 : :
17517 : 57 : relid = RelationGetRelid(rel);
17518 : :
1910 andres@anarazel.de 17519 : 57 : pg_class = table_open(RelationRelationId, RowExclusiveLock);
17520 : :
3115 sfrost@snowman.net 17521 : 57 : tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
17522 : :
17523 [ - + ]: 57 : if (!HeapTupleIsValid(tuple))
3115 sfrost@snowman.net 17524 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
17525 : :
3115 sfrost@snowman.net 17526 :CBC 57 : ((Form_pg_class) GETSTRUCT(tuple))->relforcerowsecurity = force_rls;
2630 alvherre@alvh.no-ip. 17527 : 57 : CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
17528 : :
241 michael@paquier.xyz 17529 [ + + ]:GNC 57 : InvokeObjectPostAlterHook(RelationRelationId,
17530 : : RelationGetRelid(rel), 0);
17531 : :
1910 andres@anarazel.de 17532 :CBC 57 : table_close(pg_class, RowExclusiveLock);
3495 sfrost@snowman.net 17533 : 57 : heap_freetuple(tuple);
17534 : 57 : }
17535 : :
17536 : : /*
17537 : : * ALTER FOREIGN TABLE <name> OPTIONS (...)
17538 : : */
17539 : : static void
4852 rhaas@postgresql.org 17540 : 25 : ATExecGenericOptions(Relation rel, List *options)
17541 : : {
17542 : : Relation ftrel;
17543 : : ForeignServer *server;
17544 : : ForeignDataWrapper *fdw;
17545 : : HeapTuple tuple;
17546 : : bool isnull;
17547 : : Datum repl_val[Natts_pg_foreign_table];
17548 : : bool repl_null[Natts_pg_foreign_table];
17549 : : bool repl_repl[Natts_pg_foreign_table];
17550 : : Datum datum;
17551 : : Form_pg_foreign_table tableform;
17552 : :
4753 bruce@momjian.us 17553 [ - + ]: 25 : if (options == NIL)
4852 rhaas@postgresql.org 17554 :UBC 0 : return;
17555 : :
1910 andres@anarazel.de 17556 :CBC 25 : ftrel = table_open(ForeignTableRelationId, RowExclusiveLock);
17557 : :
269 michael@paquier.xyz 17558 :GNC 25 : tuple = SearchSysCacheCopy1(FOREIGNTABLEREL,
17559 : : ObjectIdGetDatum(rel->rd_id));
4852 rhaas@postgresql.org 17560 [ - + ]:CBC 25 : if (!HeapTupleIsValid(tuple))
4852 rhaas@postgresql.org 17561 [ # # ]:UBC 0 : ereport(ERROR,
17562 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
17563 : : errmsg("foreign table \"%s\" does not exist",
17564 : : RelationGetRelationName(rel))));
4852 rhaas@postgresql.org 17565 :CBC 25 : tableform = (Form_pg_foreign_table) GETSTRUCT(tuple);
17566 : 25 : server = GetForeignServer(tableform->ftserver);
17567 : 25 : fdw = GetForeignDataWrapper(server->fdwid);
17568 : :
17569 : 25 : memset(repl_val, 0, sizeof(repl_val));
17570 : 25 : memset(repl_null, false, sizeof(repl_null));
17571 : 25 : memset(repl_repl, false, sizeof(repl_repl));
17572 : :
17573 : : /* Extract the current options */
17574 : 25 : datum = SysCacheGetAttr(FOREIGNTABLEREL,
17575 : : tuple,
17576 : : Anum_pg_foreign_table_ftoptions,
17577 : : &isnull);
17578 [ + + ]: 25 : if (isnull)
17579 : 2 : datum = PointerGetDatum(NULL);
17580 : :
17581 : : /* Transform the options */
17582 : 25 : datum = transformGenericOptions(ForeignTableRelationId,
17583 : : datum,
17584 : : options,
17585 : : fdw->fdwvalidator);
17586 : :
17587 [ + - ]: 24 : if (PointerIsValid(DatumGetPointer(datum)))
17588 : 24 : repl_val[Anum_pg_foreign_table_ftoptions - 1] = datum;
17589 : : else
4852 rhaas@postgresql.org 17590 :UBC 0 : repl_null[Anum_pg_foreign_table_ftoptions - 1] = true;
17591 : :
4852 rhaas@postgresql.org 17592 :CBC 24 : repl_repl[Anum_pg_foreign_table_ftoptions - 1] = true;
17593 : :
17594 : : /* Everything looks good - update the tuple */
17595 : :
17596 : 24 : tuple = heap_modify_tuple(tuple, RelationGetDescr(ftrel),
17597 : : repl_val, repl_null, repl_repl);
17598 : :
2630 alvherre@alvh.no-ip. 17599 : 24 : CatalogTupleUpdate(ftrel, &tuple->t_self, tuple);
17600 : :
17601 : : /*
17602 : : * Invalidate relcache so that all sessions will refresh any cached plans
17603 : : * that might depend on the old options.
17604 : : */
2655 tgl@sss.pgh.pa.us 17605 : 24 : CacheInvalidateRelcache(rel);
17606 : :
4046 rhaas@postgresql.org 17607 [ - + ]: 24 : InvokeObjectPostAlterHook(ForeignTableRelationId,
17608 : : RelationGetRelid(rel), 0);
17609 : :
1910 andres@anarazel.de 17610 : 24 : table_close(ftrel, RowExclusiveLock);
17611 : :
4852 rhaas@postgresql.org 17612 : 24 : heap_freetuple(tuple);
17613 : : }
17614 : :
17615 : : /*
17616 : : * ALTER TABLE ALTER COLUMN SET COMPRESSION
17617 : : *
17618 : : * Return value is the address of the modified column
17619 : : */
17620 : : static ObjectAddress
641 peter@eisentraut.org 17621 : 33 : ATExecSetCompression(Relation rel,
17622 : : const char *column,
17623 : : Node *newValue,
17624 : : LOCKMODE lockmode)
17625 : : {
17626 : : Relation attrel;
17627 : : HeapTuple tuple;
17628 : : Form_pg_attribute atttableform;
17629 : : AttrNumber attnum;
17630 : : char *compression;
17631 : : char cmethod;
17632 : : ObjectAddress address;
17633 : :
1122 rhaas@postgresql.org 17634 : 33 : compression = strVal(newValue);
17635 : :
17636 : 33 : attrel = table_open(AttributeRelationId, RowExclusiveLock);
17637 : :
17638 : : /* copy the cache entry so we can scribble on it below */
1120 tgl@sss.pgh.pa.us 17639 : 33 : tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), column);
1122 rhaas@postgresql.org 17640 [ - + ]: 33 : if (!HeapTupleIsValid(tuple))
1122 rhaas@postgresql.org 17641 [ # # ]:UBC 0 : ereport(ERROR,
17642 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
17643 : : errmsg("column \"%s\" of relation \"%s\" does not exist",
17644 : : column, RelationGetRelationName(rel))));
17645 : :
17646 : : /* prevent them from altering a system attribute */
1122 rhaas@postgresql.org 17647 :CBC 33 : atttableform = (Form_pg_attribute) GETSTRUCT(tuple);
17648 : 33 : attnum = atttableform->attnum;
17649 [ - + ]: 33 : if (attnum <= 0)
1122 rhaas@postgresql.org 17650 [ # # ]:UBC 0 : ereport(ERROR,
17651 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
17652 : : errmsg("cannot alter system column \"%s\"", column)));
17653 : :
17654 : : /*
17655 : : * Check that column type is compressible, then get the attribute
17656 : : * compression method code
17657 : : */
1053 tgl@sss.pgh.pa.us 17658 :CBC 33 : cmethod = GetAttributeCompression(atttableform->atttypid, compression);
17659 : :
17660 : : /* update pg_attribute entry */
1119 rhaas@postgresql.org 17661 : 30 : atttableform->attcompression = cmethod;
1122 17662 : 30 : CatalogTupleUpdate(attrel, &tuple->t_self, tuple);
17663 : :
17664 [ - + ]: 30 : InvokeObjectPostAlterHook(RelationRelationId,
17665 : : RelationGetRelid(rel),
17666 : : attnum);
17667 : :
17668 : : /*
17669 : : * Apply the change to indexes as well (only for simple index columns,
17670 : : * matching behavior of index.c ConstructTupleDescriptor()).
17671 : : */
1053 tgl@sss.pgh.pa.us 17672 : 30 : SetIndexStorageProperties(rel, attrel, attnum,
17673 : : false, 0,
17674 : : true, cmethod,
17675 : : lockmode);
17676 : :
1120 17677 : 30 : heap_freetuple(tuple);
17678 : :
1122 rhaas@postgresql.org 17679 : 30 : table_close(attrel, RowExclusiveLock);
17680 : :
17681 : : /* make changes visible */
17682 : 30 : CommandCounterIncrement();
17683 : :
17684 : 30 : ObjectAddressSubSet(address, RelationRelationId,
17685 : : RelationGetRelid(rel), attnum);
17686 : 30 : return address;
17687 : : }
17688 : :
17689 : :
17690 : : /*
17691 : : * Preparation phase for SET LOGGED/UNLOGGED
17692 : : *
17693 : : * This verifies that we're not trying to change a temp table. Also,
17694 : : * existing foreign key constraints are checked to avoid ending up with
17695 : : * permanent tables referencing unlogged tables.
17696 : : *
17697 : : * Return value is false if the operation is a no-op (in which case the
17698 : : * checks are skipped), otherwise true.
17699 : : */
17700 : : static bool
3520 alvherre@alvh.no-ip. 17701 : 44 : ATPrepChangePersistence(Relation rel, bool toLogged)
17702 : : {
17703 : : Relation pg_constraint;
17704 : : HeapTuple tuple;
17705 : : SysScanDesc scan;
17706 : : ScanKeyData skey[1];
17707 : :
17708 : : /*
17709 : : * Disallow changing status for a temp table. Also verify whether we can
17710 : : * get away with doing nothing; in such cases we don't need to run the
17711 : : * checks below, either.
17712 : : */
3523 17713 [ - + + - ]: 44 : switch (rel->rd_rel->relpersistence)
17714 : : {
3523 alvherre@alvh.no-ip. 17715 :UBC 0 : case RELPERSISTENCE_TEMP:
17716 [ # # ]: 0 : ereport(ERROR,
17717 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
17718 : : errmsg("cannot change logged status of table \"%s\" because it is temporary",
17719 : : RelationGetRelationName(rel)),
17720 : : errtable(rel)));
17721 : : break;
3523 alvherre@alvh.no-ip. 17722 :CBC 25 : case RELPERSISTENCE_PERMANENT:
17723 [ + + ]: 25 : if (toLogged)
17724 : : /* nothing to do */
17725 : 3 : return false;
17726 : 22 : break;
17727 : 19 : case RELPERSISTENCE_UNLOGGED:
17728 [ + + ]: 19 : if (!toLogged)
17729 : : /* nothing to do */
17730 : 3 : return false;
17731 : 16 : break;
17732 : : }
17733 : :
17734 : : /*
17735 : : * Check that the table is not part of any publication when changing to
17736 : : * UNLOGGED, as UNLOGGED tables can't be published.
17737 : : */
2642 peter_e@gmx.net 17738 [ + + - + ]: 60 : if (!toLogged &&
606 tgl@sss.pgh.pa.us 17739 : 22 : GetRelationPublications(RelationGetRelid(rel)) != NIL)
2642 peter_e@gmx.net 17740 [ # # ]:UBC 0 : ereport(ERROR,
17741 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
17742 : : errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
17743 : : RelationGetRelationName(rel)),
17744 : : errdetail("Unlogged relations cannot be replicated.")));
17745 : :
17746 : : /*
17747 : : * Check existing foreign key constraints to preserve the invariant that
17748 : : * permanent tables cannot reference unlogged ones. Self-referencing
17749 : : * foreign keys can safely be ignored.
17750 : : */
1910 andres@anarazel.de 17751 :CBC 38 : pg_constraint = table_open(ConstraintRelationId, AccessShareLock);
17752 : :
17753 : : /*
17754 : : * Scan conrelid if changing to permanent, else confrelid. This also
17755 : : * determines whether a useful index exists.
17756 : : */
3523 alvherre@alvh.no-ip. 17757 [ + + ]: 38 : ScanKeyInit(&skey[0],
17758 : : toLogged ? Anum_pg_constraint_conrelid :
17759 : : Anum_pg_constraint_confrelid,
17760 : : BTEqualStrategyNumber, F_OIDEQ,
17761 : : ObjectIdGetDatum(RelationGetRelid(rel)));
17762 [ + + ]: 38 : scan = systable_beginscan(pg_constraint,
17763 : : toLogged ? ConstraintRelidTypidNameIndexId : InvalidOid,
17764 : : true, NULL, 1, skey);
17765 : :
17766 [ + + ]: 65 : while (HeapTupleIsValid(tuple = systable_getnext(scan)))
17767 : : {
17768 : 33 : Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
17769 : :
17770 [ + + ]: 33 : if (con->contype == CONSTRAINT_FOREIGN)
17771 : : {
17772 : : Oid foreignrelid;
17773 : : Relation foreignrel;
17774 : :
17775 : : /* the opposite end of what we used as scankey */
17776 [ + + ]: 15 : foreignrelid = toLogged ? con->confrelid : con->conrelid;
17777 : :
17778 : : /* ignore if self-referencing */
17779 [ + + ]: 15 : if (RelationGetRelid(rel) == foreignrelid)
17780 : 6 : continue;
17781 : :
17782 : 9 : foreignrel = relation_open(foreignrelid, AccessShareLock);
17783 : :
17784 [ + + ]: 9 : if (toLogged)
17785 : : {
1119 bruce@momjian.us 17786 [ + - ]: 3 : if (!RelationIsPermanent(foreignrel))
3523 alvherre@alvh.no-ip. 17787 [ + - ]: 3 : ereport(ERROR,
17788 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
17789 : : errmsg("could not change table \"%s\" to logged because it references unlogged table \"%s\"",
17790 : : RelationGetRelationName(rel),
17791 : : RelationGetRelationName(foreignrel)),
17792 : : errtableconstraint(rel, NameStr(con->conname))));
17793 : : }
17794 : : else
17795 : : {
1119 bruce@momjian.us 17796 [ + + ]: 6 : if (RelationIsPermanent(foreignrel))
3523 alvherre@alvh.no-ip. 17797 [ + - ]: 3 : ereport(ERROR,
17798 : : (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
17799 : : errmsg("could not change table \"%s\" to unlogged because it references logged table \"%s\"",
17800 : : RelationGetRelationName(rel),
17801 : : RelationGetRelationName(foreignrel)),
17802 : : errtableconstraint(rel, NameStr(con->conname))));
17803 : : }
17804 : :
17805 : 3 : relation_close(foreignrel, AccessShareLock);
17806 : : }
17807 : : }
17808 : :
17809 : 32 : systable_endscan(scan);
17810 : :
1910 andres@anarazel.de 17811 : 32 : table_close(pg_constraint, AccessShareLock);
17812 : :
3523 alvherre@alvh.no-ip. 17813 : 32 : return true;
17814 : : }
17815 : :
17816 : : /*
17817 : : * Execute ALTER TABLE SET SCHEMA
17818 : : */
17819 : : ObjectAddress
3330 17820 : 52 : AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
17821 : : {
17822 : : Relation rel;
17823 : : Oid relid;
17824 : : Oid oldNspOid;
17825 : : Oid nspOid;
17826 : : RangeVar *newrv;
17827 : : ObjectAddresses *objsMoved;
17828 : : ObjectAddress myself;
17829 : :
4482 rhaas@postgresql.org 17830 : 52 : relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
2207 andres@anarazel.de 17831 : 52 : stmt->missing_ok ? RVR_MISSING_OK : 0,
17832 : : RangeVarCallbackForAlterRelation,
17833 : : (void *) stmt);
17834 : :
4465 simon@2ndQuadrant.co 17835 [ + + ]: 51 : if (!OidIsValid(relid))
17836 : : {
17837 [ + - ]: 6 : ereport(NOTICE,
17838 : : (errmsg("relation \"%s\" does not exist, skipping",
17839 : : stmt->relation->relname)));
3330 alvherre@alvh.no-ip. 17840 : 6 : return InvalidObjectAddress;
17841 : : }
17842 : :
4504 rhaas@postgresql.org 17843 : 45 : rel = relation_open(relid, NoLock);
17844 : :
17845 : 45 : oldNspOid = RelationGetNamespace(rel);
17846 : :
17847 : : /* If it's an owned sequence, disallow moving it by itself. */
17848 [ + + ]: 45 : if (rel->rd_rel->relkind == RELKIND_SEQUENCE)
17849 : : {
17850 : : Oid tableId;
17851 : : int32 colId;
17852 : :
2565 peter_e@gmx.net 17853 [ + + - + ]: 5 : if (sequenceIsOwned(relid, DEPENDENCY_AUTO, &tableId, &colId) ||
17854 : 1 : sequenceIsOwned(relid, DEPENDENCY_INTERNAL, &tableId, &colId))
4504 rhaas@postgresql.org 17855 [ + - ]: 3 : ereport(ERROR,
17856 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
17857 : : errmsg("cannot move an owned sequence into another schema"),
17858 : : errdetail("Sequence \"%s\" is linked to table \"%s\".",
17859 : : RelationGetRelationName(rel),
17860 : : get_rel_name(tableId))));
17861 : : }
17862 : :
17863 : : /* Get and lock schema OID and check its permissions. */
4472 17864 : 42 : newrv = makeRangeVar(stmt->newschema, RelationGetRelationName(rel), -1);
17865 : 42 : nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL);
17866 : :
17867 : : /* common checks on switching namespaces */
3069 17868 : 42 : CheckSetNamespace(oldNspOid, nspOid);
17869 : :
4183 alvherre@alvh.no-ip. 17870 : 42 : objsMoved = new_object_addresses();
17871 : 42 : AlterTableNamespaceInternal(rel, oldNspOid, nspOid, objsMoved);
17872 : 42 : free_object_addresses(objsMoved);
17873 : :
3330 17874 : 42 : ObjectAddressSet(myself, RelationRelationId, relid);
17875 : :
17876 [ + - ]: 42 : if (oldschema)
17877 : 42 : *oldschema = oldNspOid;
17878 : :
17879 : : /* close rel, but keep lock until commit */
4183 17880 : 42 : relation_close(rel, NoLock);
17881 : :
3330 17882 : 42 : return myself;
17883 : : }
17884 : :
17885 : : /*
17886 : : * The guts of relocating a table or materialized view to another namespace:
17887 : : * besides moving the relation itself, its dependent objects are relocated to
17888 : : * the new schema.
17889 : : */
17890 : : void
4183 17891 : 42 : AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
17892 : : ObjectAddresses *objsMoved)
17893 : : {
17894 : : Relation classRel;
17895 : :
17896 [ - + ]: 42 : Assert(objsMoved != NULL);
17897 : :
17898 : : /* OK, modify the pg_class row and pg_depend entry */
1910 andres@anarazel.de 17899 : 42 : classRel = table_open(RelationRelationId, RowExclusiveLock);
17900 : :
4183 alvherre@alvh.no-ip. 17901 : 42 : AlterRelationNamespaceInternal(classRel, RelationGetRelid(rel), oldNspOid,
17902 : : nspOid, true, objsMoved);
17903 : :
17904 : : /* Fix the table's row type too, if it has one */
1377 tgl@sss.pgh.pa.us 17905 [ + + ]: 42 : if (OidIsValid(rel->rd_rel->reltype))
17906 : 41 : AlterTypeNamespaceInternal(rel->rd_rel->reltype,
17907 : : nspOid, false, false, objsMoved);
17908 : :
17909 : : /* Fix other dependent stuff */
19 17910 : 42 : AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
17911 : 42 : AlterSeqNamespaces(classRel, rel, oldNspOid, nspOid,
17912 : : objsMoved, AccessExclusiveLock);
17913 : 42 : AlterConstraintNamespaces(RelationGetRelid(rel), oldNspOid, nspOid,
17914 : : false, objsMoved);
17915 : :
1910 andres@anarazel.de 17916 : 42 : table_close(classRel, RowExclusiveLock);
6831 tgl@sss.pgh.pa.us 17917 : 42 : }
17918 : :
17919 : : /*
17920 : : * The guts of relocating a relation to another namespace: fix the pg_class
17921 : : * entry, and the pg_depend entry if any. Caller must already have
17922 : : * opened and write-locked pg_class.
17923 : : */
17924 : : void
17925 : 91 : AlterRelationNamespaceInternal(Relation classRel, Oid relOid,
17926 : : Oid oldNspOid, Oid newNspOid,
17927 : : bool hasDependEntry,
17928 : : ObjectAddresses *objsMoved)
17929 : : {
17930 : : HeapTuple classTup;
17931 : : Form_pg_class classForm;
17932 : : ObjectAddress thisobj;
3069 rhaas@postgresql.org 17933 : 91 : bool already_done = false;
17934 : :
5173 17935 : 91 : classTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relOid));
6831 tgl@sss.pgh.pa.us 17936 [ - + ]: 91 : if (!HeapTupleIsValid(classTup))
6831 tgl@sss.pgh.pa.us 17937 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", relOid);
6831 tgl@sss.pgh.pa.us 17938 :CBC 91 : classForm = (Form_pg_class) GETSTRUCT(classTup);
17939 : :
17940 [ - + ]: 91 : Assert(classForm->relnamespace == oldNspOid);
17941 : :
4183 alvherre@alvh.no-ip. 17942 : 91 : thisobj.classId = RelationRelationId;
17943 : 91 : thisobj.objectId = relOid;
17944 : 91 : thisobj.objectSubId = 0;
17945 : :
17946 : : /*
17947 : : * If the object has already been moved, don't move it again. If it's
17948 : : * already in the right place, don't move it, but still fire the object
17949 : : * access hook.
17950 : : */
3069 rhaas@postgresql.org 17951 : 91 : already_done = object_address_present(&thisobj, objsMoved);
17952 [ + - + + ]: 91 : if (!already_done && oldNspOid != newNspOid)
17953 : : {
17954 : : /* check for duplicate name (more friendly than unique-index failure) */
4183 alvherre@alvh.no-ip. 17955 [ - + ]: 70 : if (get_relname_relid(NameStr(classForm->relname),
17956 : : newNspOid) != InvalidOid)
4183 alvherre@alvh.no-ip. 17957 [ # # ]:UBC 0 : ereport(ERROR,
17958 : : (errcode(ERRCODE_DUPLICATE_TABLE),
17959 : : errmsg("relation \"%s\" already exists in schema \"%s\"",
17960 : : NameStr(classForm->relname),
17961 : : get_namespace_name(newNspOid))));
17962 : :
17963 : : /* classTup is a copy, so OK to scribble on */
4183 alvherre@alvh.no-ip. 17964 :CBC 70 : classForm->relnamespace = newNspOid;
17965 : :
2630 17966 : 70 : CatalogTupleUpdate(classRel, &classTup->t_self, classTup);
17967 : :
17968 : : /* Update dependency on schema if caller said so */
4183 17969 [ + + - + ]: 121 : if (hasDependEntry &&
3970 sfrost@snowman.net 17970 : 51 : changeDependencyFor(RelationRelationId,
17971 : : relOid,
17972 : : NamespaceRelationId,
17973 : : oldNspOid,
17974 : : newNspOid) != 1)
279 michael@paquier.xyz 17975 [ # # ]:UNC 0 : elog(ERROR, "could not change schema dependency for relation \"%s\"",
17976 : : NameStr(classForm->relname));
17977 : : }
3069 rhaas@postgresql.org 17978 [ + - ]:CBC 91 : if (!already_done)
17979 : : {
4183 alvherre@alvh.no-ip. 17980 : 91 : add_exact_object_address(&thisobj, objsMoved);
17981 : :
4046 rhaas@postgresql.org 17982 [ - + ]: 91 : InvokeObjectPostAlterHook(RelationRelationId, relOid, 0);
17983 : : }
17984 : :
6831 tgl@sss.pgh.pa.us 17985 : 91 : heap_freetuple(classTup);
17986 : 91 : }
17987 : :
17988 : : /*
17989 : : * Move all indexes for the specified relation to another namespace.
17990 : : *
17991 : : * Note: we assume adequate permission checking was done by the caller,
17992 : : * and that the caller has a suitable lock on the owning relation.
17993 : : */
17994 : : static void
17995 : 42 : AlterIndexNamespaces(Relation classRel, Relation rel,
17996 : : Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved)
17997 : : {
17998 : : List *indexList;
17999 : : ListCell *l;
18000 : :
18001 : 42 : indexList = RelationGetIndexList(rel);
18002 : :
18003 [ + + + + : 64 : foreach(l, indexList)
+ + ]
18004 : : {
6756 bruce@momjian.us 18005 : 22 : Oid indexOid = lfirst_oid(l);
18006 : : ObjectAddress thisobj;
18007 : :
4183 alvherre@alvh.no-ip. 18008 : 22 : thisobj.classId = RelationRelationId;
18009 : 22 : thisobj.objectId = indexOid;
18010 : 22 : thisobj.objectSubId = 0;
18011 : :
18012 : : /*
18013 : : * Note: currently, the index will not have its own dependency on the
18014 : : * namespace, so we don't need to do changeDependencyFor(). There's no
18015 : : * row type in pg_type, either.
18016 : : *
18017 : : * XXX this objsMoved test may be pointless -- surely we have a single
18018 : : * dependency link from a relation to each index?
18019 : : */
18020 [ + - ]: 22 : if (!object_address_present(&thisobj, objsMoved))
18021 : : {
18022 : 22 : AlterRelationNamespaceInternal(classRel, indexOid,
18023 : : oldNspOid, newNspOid,
18024 : : false, objsMoved);
18025 : 22 : add_exact_object_address(&thisobj, objsMoved);
18026 : : }
18027 : : }
18028 : :
6831 tgl@sss.pgh.pa.us 18029 : 42 : list_free(indexList);
18030 : 42 : }
18031 : :
18032 : : /*
18033 : : * Move all identity and SERIAL-column sequences of the specified relation to another
18034 : : * namespace.
18035 : : *
18036 : : * Note: we assume adequate permission checking was done by the caller,
18037 : : * and that the caller has a suitable lock on the owning relation.
18038 : : */
18039 : : static void
18040 : 42 : AlterSeqNamespaces(Relation classRel, Relation rel,
18041 : : Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved,
18042 : : LOCKMODE lockmode)
18043 : : {
18044 : : Relation depRel;
18045 : : SysScanDesc scan;
18046 : : ScanKeyData key[2];
18047 : : HeapTuple tup;
18048 : :
18049 : : /*
18050 : : * SERIAL sequences are those having an auto dependency on one of the
18051 : : * table's columns (we don't care *which* column, exactly).
18052 : : */
1910 andres@anarazel.de 18053 : 42 : depRel = table_open(DependRelationId, AccessShareLock);
18054 : :
6831 tgl@sss.pgh.pa.us 18055 : 42 : ScanKeyInit(&key[0],
18056 : : Anum_pg_depend_refclassid,
18057 : : BTEqualStrategyNumber, F_OIDEQ,
18058 : : ObjectIdGetDatum(RelationRelationId));
18059 : 42 : ScanKeyInit(&key[1],
18060 : : Anum_pg_depend_refobjid,
18061 : : BTEqualStrategyNumber, F_OIDEQ,
18062 : : ObjectIdGetDatum(RelationGetRelid(rel)));
18063 : : /* we leave refobjsubid unspecified */
18064 : :
18065 : 42 : scan = systable_beginscan(depRel, DependReferenceIndexId, true,
18066 : : NULL, 2, key);
18067 : :
18068 [ + + ]: 294 : while (HeapTupleIsValid(tup = systable_getnext(scan)))
18069 : : {
18070 : 252 : Form_pg_depend depForm = (Form_pg_depend) GETSTRUCT(tup);
18071 : : Relation seqRel;
18072 : :
18073 : : /* skip dependencies other than auto dependencies on columns */
18074 [ + + ]: 252 : if (depForm->refobjsubid == 0 ||
18075 [ + + ]: 180 : depForm->classid != RelationRelationId ||
18076 [ + - ]: 21 : depForm->objsubid != 0 ||
2565 peter_e@gmx.net 18077 [ - + - - ]: 21 : !(depForm->deptype == DEPENDENCY_AUTO || depForm->deptype == DEPENDENCY_INTERNAL))
6831 tgl@sss.pgh.pa.us 18078 : 231 : continue;
18079 : :
18080 : : /* Use relation_open just in case it's an index */
5009 simon@2ndQuadrant.co 18081 : 21 : seqRel = relation_open(depForm->objid, lockmode);
18082 : :
18083 : : /* skip non-sequence relations */
6831 tgl@sss.pgh.pa.us 18084 [ - + ]: 21 : if (RelationGetForm(seqRel)->relkind != RELKIND_SEQUENCE)
18085 : : {
18086 : : /* No need to keep the lock */
5009 simon@2ndQuadrant.co 18087 :UBC 0 : relation_close(seqRel, lockmode);
6831 tgl@sss.pgh.pa.us 18088 : 0 : continue;
18089 : : }
18090 : :
18091 : : /* Fix the pg_class and pg_depend entries */
6831 tgl@sss.pgh.pa.us 18092 :CBC 21 : AlterRelationNamespaceInternal(classRel, depForm->objid,
18093 : : oldNspOid, newNspOid,
18094 : : true, objsMoved);
18095 : :
18096 : : /*
18097 : : * Sequences used to have entries in pg_type, but no longer do. If we
18098 : : * ever re-instate that, we'll need to move the pg_type entry to the
18099 : : * new namespace, too (using AlterTypeNamespaceInternal).
18100 : : */
1377 18101 [ - + ]: 21 : Assert(RelationGetForm(seqRel)->reltype == InvalidOid);
18102 : :
18103 : : /* Now we can close it. Keep the lock till end of transaction. */
6831 18104 : 21 : relation_close(seqRel, NoLock);
18105 : : }
18106 : :
18107 : 42 : systable_endscan(scan);
18108 : :
18109 : 42 : relation_close(depRel, AccessShareLock);
18110 : 42 : }
18111 : :
18112 : :
18113 : : /*
18114 : : * This code supports
18115 : : * CREATE TEMP TABLE ... ON COMMIT { DROP | PRESERVE ROWS | DELETE ROWS }
18116 : : *
18117 : : * Because we only support this for TEMP tables, it's sufficient to remember
18118 : : * the state in a backend-local data structure.
18119 : : */
18120 : :
18121 : : /*
18122 : : * Register a newly-created relation's ON COMMIT action.
18123 : : */
18124 : : void
7825 18125 : 83 : register_on_commit_action(Oid relid, OnCommitAction action)
18126 : : {
18127 : : OnCommitItem *oc;
18128 : : MemoryContext oldcxt;
18129 : :
18130 : : /*
18131 : : * We needn't bother registering the relation unless there is an ON COMMIT
18132 : : * action we need to take.
18133 : : */
18134 [ + - + + ]: 83 : if (action == ONCOMMIT_NOOP || action == ONCOMMIT_PRESERVE_ROWS)
7827 bruce@momjian.us 18135 : 12 : return;
18136 : :
7825 tgl@sss.pgh.pa.us 18137 : 71 : oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
18138 : :
18139 : 71 : oc = (OnCommitItem *) palloc(sizeof(OnCommitItem));
18140 : 71 : oc->relid = relid;
18141 : 71 : oc->oncommit = action;
7150 18142 : 71 : oc->creating_subid = GetCurrentSubTransactionId();
18143 : 71 : oc->deleting_subid = InvalidSubTransactionId;
18144 : :
18145 : : /*
18146 : : * We use lcons() here so that ON COMMIT actions are processed in reverse
18147 : : * order of registration. That might not be essential but it seems
18148 : : * reasonable.
18149 : : */
7825 18150 : 71 : on_commits = lcons(oc, on_commits);
18151 : :
18152 : 71 : MemoryContextSwitchTo(oldcxt);
18153 : : }
18154 : :
18155 : : /*
18156 : : * Unregister any ON COMMIT action when a relation is deleted.
18157 : : *
18158 : : * Actually, we only mark the OnCommitItem entry as to be deleted after commit.
18159 : : */
18160 : : void
18161 : 21039 : remove_on_commit_action(Oid relid)
18162 : : {
18163 : : ListCell *l;
18164 : :
18165 [ + + + + : 21106 : foreach(l, on_commits)
+ + ]
18166 : : {
7559 bruce@momjian.us 18167 : 132 : OnCommitItem *oc = (OnCommitItem *) lfirst(l);
18168 : :
7825 tgl@sss.pgh.pa.us 18169 [ + + ]: 132 : if (oc->relid == relid)
18170 : : {
7150 18171 : 65 : oc->deleting_subid = GetCurrentSubTransactionId();
7825 18172 : 65 : break;
18173 : : }
18174 : : }
7827 bruce@momjian.us 18175 : 21039 : }
18176 : :
18177 : : /*
18178 : : * Perform ON COMMIT actions.
18179 : : *
18180 : : * This is invoked just before actually committing, since it's possible
18181 : : * to encounter errors.
18182 : : */
18183 : : void
7825 tgl@sss.pgh.pa.us 18184 : 408922 : PreCommit_on_commit_actions(void)
18185 : : {
18186 : : ListCell *l;
7017 18187 : 408922 : List *oids_to_truncate = NIL;
1983 michael@paquier.xyz 18188 : 408922 : List *oids_to_drop = NIL;
18189 : :
7825 tgl@sss.pgh.pa.us 18190 [ + + + + : 409280 : foreach(l, on_commits)
+ + ]
18191 : : {
7559 bruce@momjian.us 18192 : 358 : OnCommitItem *oc = (OnCommitItem *) lfirst(l);
18193 : :
18194 : : /* Ignore entry if already dropped in this xact */
7150 tgl@sss.pgh.pa.us 18195 [ + + ]: 358 : if (oc->deleting_subid != InvalidSubTransactionId)
7825 18196 : 34 : continue;
18197 : :
18198 [ - + + - ]: 324 : switch (oc->oncommit)
18199 : : {
7825 tgl@sss.pgh.pa.us 18200 :UBC 0 : case ONCOMMIT_NOOP:
18201 : : case ONCOMMIT_PRESERVE_ROWS:
18202 : : /* Do nothing (there shouldn't be such entries, actually) */
18203 : 0 : break;
7825 tgl@sss.pgh.pa.us 18204 :CBC 299 : case ONCOMMIT_DELETE_ROWS:
18205 : :
18206 : : /*
18207 : : * If this transaction hasn't accessed any temporary
18208 : : * relations, we can skip truncating ON COMMIT DELETE ROWS
18209 : : * tables, as they must still be empty.
18210 : : */
1905 michael@paquier.xyz 18211 [ + + ]: 299 : if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
4093 heikki.linnakangas@i 18212 : 200 : oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
7825 tgl@sss.pgh.pa.us 18213 : 299 : break;
18214 : 25 : case ONCOMMIT_DROP:
1983 michael@paquier.xyz 18215 : 25 : oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
18216 : 25 : break;
18217 : : }
18218 : : }
18219 : :
18220 : : /*
18221 : : * Truncate relations before dropping so that all dependencies between
18222 : : * relations are removed after they are worked on. Doing it like this
18223 : : * might be a waste as it is possible that a relation being truncated will
18224 : : * be dropped anyway due to its parent being dropped, but this makes the
18225 : : * code more robust because of not having to re-check that the relation
18226 : : * exists at truncation time.
18227 : : */
7017 tgl@sss.pgh.pa.us 18228 [ + + ]: 408922 : if (oids_to_truncate != NIL)
18229 : 167 : heap_truncate(oids_to_truncate);
18230 : :
1983 michael@paquier.xyz 18231 [ + + ]: 408919 : if (oids_to_drop != NIL)
18232 : : {
18233 : 22 : ObjectAddresses *targetObjects = new_object_addresses();
18234 : :
18235 [ + - + + : 47 : foreach(l, oids_to_drop)
+ + ]
18236 : : {
18237 : : ObjectAddress object;
18238 : :
18239 : 25 : object.classId = RelationRelationId;
18240 : 25 : object.objectId = lfirst_oid(l);
18241 : 25 : object.objectSubId = 0;
18242 : :
18243 [ - + ]: 25 : Assert(!object_address_present(&object, targetObjects));
18244 : :
18245 : 25 : add_exact_object_address(&object, targetObjects);
18246 : : }
18247 : :
18248 : : /*
18249 : : * Object deletion might involve toast table access (to clean up
18250 : : * toasted catalog entries), so ensure we have a valid snapshot.
18251 : : */
181 tgl@sss.pgh.pa.us 18252 : 22 : PushActiveSnapshot(GetTransactionSnapshot());
18253 : :
18254 : : /*
18255 : : * Since this is an automatic drop, rather than one directly initiated
18256 : : * by the user, we pass the PERFORM_DELETION_INTERNAL flag.
18257 : : */
1983 michael@paquier.xyz 18258 : 22 : performMultipleDeletions(targetObjects, DROP_CASCADE,
18259 : : PERFORM_DELETION_INTERNAL | PERFORM_DELETION_QUIETLY);
18260 : :
181 tgl@sss.pgh.pa.us 18261 : 22 : PopActiveSnapshot();
18262 : :
18263 : : #ifdef USE_ASSERT_CHECKING
18264 : :
18265 : : /*
18266 : : * Note that table deletion will call remove_on_commit_action, so the
18267 : : * entry should get marked as deleted.
18268 : : */
1983 michael@paquier.xyz 18269 [ + - + + : 72 : foreach(l, on_commits)
+ + ]
18270 : : {
18271 : 50 : OnCommitItem *oc = (OnCommitItem *) lfirst(l);
18272 : :
18273 [ + + ]: 50 : if (oc->oncommit != ONCOMMIT_DROP)
18274 : 25 : continue;
18275 : :
18276 [ - + ]: 25 : Assert(oc->deleting_subid != InvalidSubTransactionId);
18277 : : }
18278 : : #endif
18279 : : }
7827 bruce@momjian.us 18280 : 408919 : }
18281 : :
18282 : : /*
18283 : : * Post-commit or post-abort cleanup for ON COMMIT management.
18284 : : *
18285 : : * All we do here is remove no-longer-needed OnCommitItem entries.
18286 : : *
18287 : : * During commit, remove entries that were deleted during this transaction;
18288 : : * during abort, remove those created during this transaction.
18289 : : */
18290 : : void
7150 tgl@sss.pgh.pa.us 18291 : 431482 : AtEOXact_on_commit_actions(bool isCommit)
18292 : : {
18293 : : ListCell *cur_item;
18294 : :
1735 18295 [ + + + + : 431855 : foreach(cur_item, on_commits)
+ + ]
18296 : : {
7263 neilc@samurai.com 18297 : 373 : OnCommitItem *oc = (OnCommitItem *) lfirst(cur_item);
18298 : :
7150 tgl@sss.pgh.pa.us 18299 [ + + + + ]: 424 : if (isCommit ? oc->deleting_subid != InvalidSubTransactionId :
18300 : 51 : oc->creating_subid != InvalidSubTransactionId)
18301 : : {
18302 : : /* cur_item must be removed */
1735 18303 : 71 : on_commits = foreach_delete_current(on_commits, cur_item);
7825 18304 : 71 : pfree(oc);
18305 : : }
18306 : : else
18307 : : {
18308 : : /* cur_item must be preserved */
7150 18309 : 302 : oc->creating_subid = InvalidSubTransactionId;
18310 : 302 : oc->deleting_subid = InvalidSubTransactionId;
18311 : : }
18312 : : }
7227 18313 : 431482 : }
18314 : :
18315 : : /*
18316 : : * Post-subcommit or post-subabort cleanup for ON COMMIT management.
18317 : : *
18318 : : * During subabort, we can immediately remove entries created during this
18319 : : * subtransaction. During subcommit, just relabel entries marked during
18320 : : * this subtransaction as being the parent's responsibility.
18321 : : */
18322 : : void
7150 18323 : 9927 : AtEOSubXact_on_commit_actions(bool isCommit, SubTransactionId mySubid,
18324 : : SubTransactionId parentSubid)
18325 : : {
18326 : : ListCell *cur_item;
18327 : :
1735 18328 [ - + - - : 9927 : foreach(cur_item, on_commits)
- + ]
18329 : : {
7227 tgl@sss.pgh.pa.us 18330 :UBC 0 : OnCommitItem *oc = (OnCommitItem *) lfirst(cur_item);
18331 : :
7150 18332 [ # # # # ]: 0 : if (!isCommit && oc->creating_subid == mySubid)
18333 : : {
18334 : : /* cur_item must be removed */
1735 18335 : 0 : on_commits = foreach_delete_current(on_commits, cur_item);
7227 18336 : 0 : pfree(oc);
18337 : : }
18338 : : else
18339 : : {
18340 : : /* cur_item must be preserved */
7150 18341 [ # # ]: 0 : if (oc->creating_subid == mySubid)
18342 : 0 : oc->creating_subid = parentSubid;
18343 [ # # ]: 0 : if (oc->deleting_subid == mySubid)
18344 [ # # ]: 0 : oc->deleting_subid = isCommit ? parentSubid : InvalidSubTransactionId;
18345 : : }
18346 : : }
7827 bruce@momjian.us 18347 :CBC 9927 : }
18348 : :
18349 : : /*
18350 : : * This is intended as a callback for RangeVarGetRelidExtended(). It allows
18351 : : * the relation to be locked only if (1) it's a plain or partitioned table,
18352 : : * materialized view, or TOAST table and (2) the current user is the owner (or
18353 : : * the superuser) or has been granted MAINTAIN. This meets the
18354 : : * permission-checking needs of CLUSTER, REINDEX TABLE, and REFRESH
18355 : : * MATERIALIZED VIEW; we expose it here so that it can be used by all.
18356 : : */
18357 : : void
32 nathan@postgresql.or 18358 :GNC 495 : RangeVarCallbackMaintainsTable(const RangeVar *relation,
18359 : : Oid relId, Oid oldRelId, void *arg)
18360 : : {
18361 : : char relkind;
18362 : : AclResult aclresult;
18363 : :
18364 : : /* Nothing to do if the relation was not found. */
4498 rhaas@postgresql.org 18365 [ + + ]:CBC 495 : if (!OidIsValid(relId))
18366 : 3 : return;
18367 : :
18368 : : /*
18369 : : * If the relation does exist, check whether it's an index. But note that
18370 : : * the relation might have been dropped between the time we did the name
18371 : : * lookup and now. In that case, there's nothing to do.
18372 : : */
18373 : 492 : relkind = get_rel_relkind(relId);
18374 [ - + ]: 492 : if (!relkind)
4498 rhaas@postgresql.org 18375 :UBC 0 : return;
4060 kgrittn@postgresql.o 18376 [ + + + + :CBC 492 : if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
+ + ]
2685 rhaas@postgresql.org 18377 [ + + ]: 68 : relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE)
4498 18378 [ + - ]: 14 : ereport(ERROR,
18379 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18380 : : errmsg("\"%s\" is not a table or materialized view", relation->relname)));
18381 : :
18382 : : /* Check permissions */
32 nathan@postgresql.or 18383 :GNC 478 : aclresult = pg_class_aclcheck(relId, GetUserId(), ACL_MAINTAIN);
18384 [ + + ]: 478 : if (aclresult != ACLCHECK_OK)
18385 : 15 : aclcheck_error(aclresult,
18386 : 15 : get_relkind_objtype(get_rel_relkind(relId)),
18387 : 15 : relation->relname);
18388 : : }
18389 : :
18390 : : /*
18391 : : * Callback to RangeVarGetRelidExtended() for TRUNCATE processing.
18392 : : */
18393 : : static void
2074 michael@paquier.xyz 18394 :CBC 917 : RangeVarCallbackForTruncate(const RangeVar *relation,
18395 : : Oid relId, Oid oldRelId, void *arg)
18396 : : {
18397 : : HeapTuple tuple;
18398 : :
18399 : : /* Nothing to do if the relation was not found. */
18400 [ - + ]: 917 : if (!OidIsValid(relId))
2074 michael@paquier.xyz 18401 :UBC 0 : return;
18402 : :
2074 michael@paquier.xyz 18403 :CBC 917 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relId));
18404 [ - + ]: 917 : if (!HeapTupleIsValid(tuple)) /* should not happen */
2074 michael@paquier.xyz 18405 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", relId);
18406 : :
2074 michael@paquier.xyz 18407 :CBC 917 : truncate_check_rel(relId, (Form_pg_class) GETSTRUCT(tuple));
1535 fujii@postgresql.org 18408 : 915 : truncate_check_perms(relId, (Form_pg_class) GETSTRUCT(tuple));
18409 : :
2074 michael@paquier.xyz 18410 : 899 : ReleaseSysCache(tuple);
18411 : : }
18412 : :
18413 : : /*
18414 : : * Callback for RangeVarGetRelidExtended(). Checks that the current user is
18415 : : * the owner of the relation, or superuser.
18416 : : */
18417 : : void
3709 rhaas@postgresql.org 18418 : 7498 : RangeVarCallbackOwnsRelation(const RangeVar *relation,
18419 : : Oid relId, Oid oldRelId, void *arg)
18420 : : {
18421 : : HeapTuple tuple;
18422 : :
18423 : : /* Nothing to do if the relation was not found. */
18424 [ + + ]: 7498 : if (!OidIsValid(relId))
18425 : 7 : return;
18426 : :
18427 : 7491 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relId));
2489 tgl@sss.pgh.pa.us 18428 [ - + ]: 7491 : if (!HeapTupleIsValid(tuple)) /* should not happen */
3709 rhaas@postgresql.org 18429 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", relId);
18430 : :
518 peter@eisentraut.org 18431 [ + + ]:CBC 7491 : if (!object_ownercheck(RelationRelationId, relId, GetUserId()))
2325 peter_e@gmx.net 18432 : 3 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relId)),
3709 rhaas@postgresql.org 18433 : 3 : relation->relname);
18434 : :
18435 [ + + + + ]: 14916 : if (!allowSystemTableMods &&
18436 : 7428 : IsSystemClass(relId, (Form_pg_class) GETSTRUCT(tuple)))
18437 [ + - ]: 1 : ereport(ERROR,
18438 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
18439 : : errmsg("permission denied: \"%s\" is a system catalog",
18440 : : relation->relname)));
18441 : :
18442 : 7487 : ReleaseSysCache(tuple);
18443 : : }
18444 : :
18445 : : /*
18446 : : * Common RangeVarGetRelid callback for rename, set schema, and alter table
18447 : : * processing.
18448 : : */
18449 : : static void
4482 18450 : 17844 : RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
18451 : : void *arg)
18452 : : {
4326 bruce@momjian.us 18453 : 17844 : Node *stmt = (Node *) arg;
18454 : : ObjectType reltype;
18455 : : HeapTuple tuple;
18456 : : Form_pg_class classform;
18457 : : AclResult aclresult;
18458 : : char relkind;
18459 : :
4482 rhaas@postgresql.org 18460 : 17844 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
18461 [ + + ]: 17844 : if (!HeapTupleIsValid(tuple))
4326 bruce@momjian.us 18462 : 109 : return; /* concurrently dropped */
4482 rhaas@postgresql.org 18463 : 17735 : classform = (Form_pg_class) GETSTRUCT(tuple);
18464 : 17735 : relkind = classform->relkind;
18465 : :
18466 : : /* Must own relation. */
518 peter@eisentraut.org 18467 [ + + ]: 17735 : if (!object_ownercheck(RelationRelationId, relid, GetUserId()))
2325 peter_e@gmx.net 18468 : 30 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
18469 : :
18470 : : /* No system table modifications unless explicitly allowed. */
3790 rhaas@postgresql.org 18471 [ + + + + ]: 17705 : if (!allowSystemTableMods && IsSystemClass(relid, classform))
4482 18472 [ + - ]: 14 : ereport(ERROR,
18473 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
18474 : : errmsg("permission denied: \"%s\" is a system catalog",
18475 : : rv->relname)));
18476 : :
18477 : : /*
18478 : : * Extract the specified relation type from the statement parse tree.
18479 : : *
18480 : : * Also, for ALTER .. RENAME, check permissions: the user must (still)
18481 : : * have CREATE rights on the containing namespace.
18482 : : */
18483 [ + + ]: 17691 : if (IsA(stmt, RenameStmt))
18484 : : {
518 peter@eisentraut.org 18485 : 252 : aclresult = object_aclcheck(NamespaceRelationId, classform->relnamespace,
18486 : : GetUserId(), ACL_CREATE);
4482 rhaas@postgresql.org 18487 [ - + ]: 252 : if (aclresult != ACLCHECK_OK)
2325 peter_e@gmx.net 18488 :UBC 0 : aclcheck_error(aclresult, OBJECT_SCHEMA,
4482 rhaas@postgresql.org 18489 : 0 : get_namespace_name(classform->relnamespace));
4482 rhaas@postgresql.org 18490 :CBC 252 : reltype = ((RenameStmt *) stmt)->renameType;
18491 : : }
18492 [ + + ]: 17439 : else if (IsA(stmt, AlterObjectSchemaStmt))
18493 : 46 : reltype = ((AlterObjectSchemaStmt *) stmt)->objectType;
18494 : :
18495 [ + - ]: 17393 : else if (IsA(stmt, AlterTableStmt))
1373 michael@paquier.xyz 18496 : 17393 : reltype = ((AlterTableStmt *) stmt)->objtype;
18497 : : else
18498 : : {
4482 rhaas@postgresql.org 18499 [ # # ]:UBC 0 : elog(ERROR, "unrecognized node type: %d", (int) nodeTag(stmt));
18500 : : reltype = OBJECT_TABLE; /* placate compiler */
18501 : : }
18502 : :
18503 : : /*
18504 : : * For compatibility with prior releases, we allow ALTER TABLE to be used
18505 : : * with most other types of relations (but not composite types). We allow
18506 : : * similar flexibility for ALTER INDEX in the case of RENAME, but not
18507 : : * otherwise. Otherwise, the user must select the correct form of the
18508 : : * command for the relation at issue.
18509 : : */
4482 rhaas@postgresql.org 18510 [ + + - + ]:CBC 17691 : if (reltype == OBJECT_SEQUENCE && relkind != RELKIND_SEQUENCE)
4482 rhaas@postgresql.org 18511 [ # # ]:UBC 0 : ereport(ERROR,
18512 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18513 : : errmsg("\"%s\" is not a sequence", rv->relname)));
18514 : :
4482 rhaas@postgresql.org 18515 [ + + - + ]:CBC 17691 : if (reltype == OBJECT_VIEW && relkind != RELKIND_VIEW)
4482 rhaas@postgresql.org 18516 [ # # ]:UBC 0 : ereport(ERROR,
18517 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18518 : : errmsg("\"%s\" is not a view", rv->relname)));
18519 : :
4060 kgrittn@postgresql.o 18520 [ + + - + ]:CBC 17691 : if (reltype == OBJECT_MATVIEW && relkind != RELKIND_MATVIEW)
4060 kgrittn@postgresql.o 18521 [ # # ]:UBC 0 : ereport(ERROR,
18522 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18523 : : errmsg("\"%s\" is not a materialized view", rv->relname)));
18524 : :
4482 rhaas@postgresql.org 18525 [ + + - + ]:CBC 17691 : if (reltype == OBJECT_FOREIGN_TABLE && relkind != RELKIND_FOREIGN_TABLE)
4482 rhaas@postgresql.org 18526 [ # # ]:UBC 0 : ereport(ERROR,
18527 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18528 : : errmsg("\"%s\" is not a foreign table", rv->relname)));
18529 : :
4482 rhaas@postgresql.org 18530 [ + + - + ]:CBC 17691 : if (reltype == OBJECT_TYPE && relkind != RELKIND_COMPOSITE_TYPE)
4482 rhaas@postgresql.org 18531 [ # # ]:UBC 0 : ereport(ERROR,
18532 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18533 : : errmsg("\"%s\" is not a composite type", rv->relname)));
18534 : :
2277 alvherre@alvh.no-ip. 18535 [ + + + + :CBC 17691 : if (reltype == OBJECT_INDEX && relkind != RELKIND_INDEX &&
+ + ]
18536 : : relkind != RELKIND_PARTITIONED_INDEX
4482 rhaas@postgresql.org 18537 [ + + ]: 17 : && !IsA(stmt, RenameStmt))
18538 [ + - ]: 3 : ereport(ERROR,
18539 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18540 : : errmsg("\"%s\" is not an index", rv->relname)));
18541 : :
18542 : : /*
18543 : : * Don't allow ALTER TABLE on composite types. We want people to use ALTER
18544 : : * TYPE for that.
18545 : : */
18546 [ + + - + ]: 17688 : if (reltype != OBJECT_TYPE && relkind == RELKIND_COMPOSITE_TYPE)
4482 rhaas@postgresql.org 18547 [ # # ]:UBC 0 : ereport(ERROR,
18548 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18549 : : errmsg("\"%s\" is a composite type", rv->relname),
18550 : : /* translator: %s is an SQL ALTER command */
18551 : : errhint("Use %s instead.",
18552 : : "ALTER TYPE")));
18553 : :
18554 : : /*
18555 : : * Don't allow ALTER TABLE .. SET SCHEMA on relations that can't be moved
18556 : : * to a different schema, such as indexes and TOAST tables.
18557 : : */
1011 peter@eisentraut.org 18558 [ + + ]:CBC 17688 : if (IsA(stmt, AlterObjectSchemaStmt))
18559 : : {
18560 [ + - - + ]: 46 : if (relkind == RELKIND_INDEX || relkind == RELKIND_PARTITIONED_INDEX)
1011 peter@eisentraut.org 18561 [ # # ]:UBC 0 : ereport(ERROR,
18562 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18563 : : errmsg("cannot change schema of index \"%s\"",
18564 : : rv->relname),
18565 : : errhint("Change the schema of the table instead.")));
1011 peter@eisentraut.org 18566 [ - + ]:CBC 46 : else if (relkind == RELKIND_COMPOSITE_TYPE)
1011 peter@eisentraut.org 18567 [ # # ]:UBC 0 : ereport(ERROR,
18568 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18569 : : errmsg("cannot change schema of composite type \"%s\"",
18570 : : rv->relname),
18571 : : /* translator: %s is an SQL ALTER command */
18572 : : errhint("Use %s instead.",
18573 : : "ALTER TYPE")));
1011 peter@eisentraut.org 18574 [ - + ]:CBC 46 : else if (relkind == RELKIND_TOASTVALUE)
1011 peter@eisentraut.org 18575 [ # # ]:UBC 0 : ereport(ERROR,
18576 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
18577 : : errmsg("cannot change schema of TOAST table \"%s\"",
18578 : : rv->relname),
18579 : : errhint("Change the schema of the table instead.")));
18580 : : }
18581 : :
4482 rhaas@postgresql.org 18582 :CBC 17688 : ReleaseSysCache(tuple);
18583 : : }
18584 : :
18585 : : /*
18586 : : * Transform any expressions present in the partition key
18587 : : *
18588 : : * Returns a transformed PartitionSpec.
18589 : : */
18590 : : static PartitionSpec *
528 alvherre@alvh.no-ip. 18591 : 2423 : transformPartitionSpec(Relation rel, PartitionSpec *partspec)
18592 : : {
18593 : : PartitionSpec *newspec;
18594 : : ParseState *pstate;
18595 : : ParseNamespaceItem *nsitem;
18596 : : ListCell *l;
18597 : :
2669 peter_e@gmx.net 18598 : 2423 : newspec = makeNode(PartitionSpec);
18599 : :
2685 rhaas@postgresql.org 18600 : 2423 : newspec->strategy = partspec->strategy;
18601 : 2423 : newspec->partParams = NIL;
2513 tgl@sss.pgh.pa.us 18602 : 2423 : newspec->location = partspec->location;
18603 : :
18604 : : /* Check valid number of columns for strategy */
528 alvherre@alvh.no-ip. 18605 [ + + + + ]: 3585 : if (partspec->strategy == PARTITION_STRATEGY_LIST &&
2513 tgl@sss.pgh.pa.us 18606 : 1162 : list_length(partspec->partParams) != 1)
18607 [ + - ]: 3 : ereport(ERROR,
18608 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
18609 : : errmsg("cannot use \"list\" partition strategy with more than one column")));
18610 : :
18611 : : /*
18612 : : * Create a dummy ParseState and insert the target relation as its sole
18613 : : * rangetable entry. We need a ParseState for transformExpr.
18614 : : */
2685 rhaas@postgresql.org 18615 : 2420 : pstate = make_parsestate(NULL);
1564 tgl@sss.pgh.pa.us 18616 : 2420 : nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock,
18617 : : NULL, false, true);
18618 : 2420 : addNSItemToQuery(pstate, nsitem, true, true, true);
18619 : :
18620 : : /* take care of any partition expressions */
2685 rhaas@postgresql.org 18621 [ + - + + : 5056 : foreach(l, partspec->partParams)
+ + ]
18622 : : {
1000 peter@eisentraut.org 18623 : 2648 : PartitionElem *pelem = lfirst_node(PartitionElem, l);
18624 : :
2685 rhaas@postgresql.org 18625 [ + + ]: 2648 : if (pelem->expr)
18626 : : {
18627 : : /* Copy, to avoid scribbling on the input */
2513 tgl@sss.pgh.pa.us 18628 : 149 : pelem = copyObject(pelem);
18629 : :
18630 : : /* Now do parse transformation of the expression */
2685 rhaas@postgresql.org 18631 : 149 : pelem->expr = transformExpr(pstate, pelem->expr,
18632 : : EXPR_KIND_PARTITION_EXPRESSION);
18633 : :
18634 : : /* we have to fix its collations too */
18635 : 137 : assign_expr_collations(pstate, pelem->expr);
18636 : : }
18637 : :
18638 : 2636 : newspec->partParams = lappend(newspec->partParams, pelem);
18639 : : }
18640 : :
18641 : 2408 : return newspec;
18642 : : }
18643 : :
18644 : : /*
18645 : : * Compute per-partition-column information from a list of PartitionElems.
18646 : : * Expressions in the PartitionElems must be parse-analyzed already.
18647 : : */
18648 : : static void
2062 peter_e@gmx.net 18649 : 2408 : ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNumber *partattrs,
18650 : : List **partexprs, Oid *partopclass, Oid *partcollation,
18651 : : PartitionStrategy strategy)
18652 : : {
18653 : : int attn;
18654 : : ListCell *lc;
18655 : : Oid am_oid;
18656 : :
2685 rhaas@postgresql.org 18657 : 2408 : attn = 0;
18658 [ + - + + : 5002 : foreach(lc, partParams)
+ + ]
18659 : : {
1000 peter@eisentraut.org 18660 : 2636 : PartitionElem *pelem = lfirst_node(PartitionElem, lc);
18661 : : Oid atttype;
18662 : : Oid attcollation;
18663 : :
2685 rhaas@postgresql.org 18664 [ + + ]: 2636 : if (pelem->name != NULL)
18665 : : {
18666 : : /* Simple attribute reference */
18667 : : HeapTuple atttuple;
18668 : : Form_pg_attribute attform;
18669 : :
2513 tgl@sss.pgh.pa.us 18670 : 2499 : atttuple = SearchSysCacheAttName(RelationGetRelid(rel),
18671 : 2499 : pelem->name);
2685 rhaas@postgresql.org 18672 [ + + ]: 2499 : if (!HeapTupleIsValid(atttuple))
18673 [ + - ]: 6 : ereport(ERROR,
18674 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
18675 : : errmsg("column \"%s\" named in partition key does not exist",
18676 : : pelem->name),
18677 : : parser_errposition(pstate, pelem->location)));
18678 : 2493 : attform = (Form_pg_attribute) GETSTRUCT(atttuple);
18679 : :
18680 [ + + ]: 2493 : if (attform->attnum <= 0)
18681 [ + - ]: 3 : ereport(ERROR,
18682 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
18683 : : errmsg("cannot use system column \"%s\" in partition key",
18684 : : pelem->name),
18685 : : parser_errposition(pstate, pelem->location)));
18686 : :
18687 : : /*
18688 : : * Generated columns cannot work: They are computed after BEFORE
18689 : : * triggers, but partition routing is done before all triggers.
18690 : : */
1842 peter@eisentraut.org 18691 [ + + ]: 2490 : if (attform->attgenerated)
18692 [ + - ]: 3 : ereport(ERROR,
18693 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
18694 : : errmsg("cannot use generated column in partition key"),
18695 : : errdetail("Column \"%s\" is a generated column.",
18696 : : pelem->name),
18697 : : parser_errposition(pstate, pelem->location)));
18698 : :
2685 rhaas@postgresql.org 18699 : 2487 : partattrs[attn] = attform->attnum;
18700 : 2487 : atttype = attform->atttypid;
18701 : 2487 : attcollation = attform->attcollation;
18702 : 2487 : ReleaseSysCache(atttuple);
18703 : : }
18704 : : else
18705 : : {
18706 : : /* Expression */
18707 : 137 : Node *expr = pelem->expr;
18708 : : char partattname[16];
18709 : :
18710 [ - + ]: 137 : Assert(expr != NULL);
18711 : 137 : atttype = exprType(expr);
18712 : 137 : attcollation = exprCollation(expr);
18713 : :
18714 : : /*
18715 : : * The expression must be of a storable type (e.g., not RECORD).
18716 : : * The test is the same as for whether a table column is of a safe
18717 : : * type (which is why we needn't check for the non-expression
18718 : : * case).
18719 : : */
1574 tgl@sss.pgh.pa.us 18720 : 137 : snprintf(partattname, sizeof(partattname), "%d", attn + 1);
18721 : 137 : CheckAttributeType(partattname,
18722 : : atttype, attcollation,
18723 : : NIL, CHKATYPE_IS_PARTKEY);
18724 : :
18725 : : /*
18726 : : * Strip any top-level COLLATE clause. This ensures that we treat
18727 : : * "x COLLATE y" and "(x COLLATE y)" alike.
18728 : : */
2685 rhaas@postgresql.org 18729 [ - + ]: 131 : while (IsA(expr, CollateExpr))
2685 rhaas@postgresql.org 18730 :UBC 0 : expr = (Node *) ((CollateExpr *) expr)->arg;
18731 : :
2685 rhaas@postgresql.org 18732 [ + + ]:CBC 131 : if (IsA(expr, Var) &&
2513 tgl@sss.pgh.pa.us 18733 [ + + ]: 6 : ((Var *) expr)->varattno > 0)
18734 : : {
18735 : : /*
18736 : : * User wrote "(column)" or "(column COLLATE something)".
18737 : : * Treat it like simple attribute anyway.
18738 : : */
2685 rhaas@postgresql.org 18739 : 3 : partattrs[attn] = ((Var *) expr)->varattno;
18740 : : }
18741 : : else
18742 : : {
2679 18743 : 128 : Bitmapset *expr_attrs = NULL;
18744 : : int i;
18745 : :
18746 : 128 : partattrs[attn] = 0; /* marks the column as expression */
2685 18747 : 128 : *partexprs = lappend(*partexprs, expr);
18748 : :
18749 : : /*
18750 : : * transformPartitionSpec() should have already rejected
18751 : : * subqueries, aggregates, window functions, and SRFs, based
18752 : : * on the EXPR_KIND_ for partition expressions.
18753 : : */
18754 : :
18755 : : /*
18756 : : * Cannot allow system column references, since that would
18757 : : * make partition routing impossible: their values won't be
18758 : : * known yet when we need to do that.
18759 : : */
18760 : 128 : pull_varattnos(expr, 1, &expr_attrs);
2513 tgl@sss.pgh.pa.us 18761 [ + + ]: 1024 : for (i = FirstLowInvalidHeapAttributeNumber; i < 0; i++)
18762 : : {
18763 [ - + ]: 896 : if (bms_is_member(i - FirstLowInvalidHeapAttributeNumber,
18764 : : expr_attrs))
2513 tgl@sss.pgh.pa.us 18765 [ # # ]:UBC 0 : ereport(ERROR,
18766 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
18767 : : errmsg("partition key expressions cannot contain system column references")));
18768 : : }
18769 : :
18770 : : /*
18771 : : * Generated columns cannot work: They are computed after
18772 : : * BEFORE triggers, but partition routing is done before all
18773 : : * triggers.
18774 : : */
1842 peter@eisentraut.org 18775 :CBC 128 : i = -1;
18776 [ + + ]: 282 : while ((i = bms_next_member(expr_attrs, i)) >= 0)
18777 : : {
1789 tgl@sss.pgh.pa.us 18778 : 157 : AttrNumber attno = i + FirstLowInvalidHeapAttributeNumber;
18779 : :
1572 18780 [ + + ]: 157 : if (attno > 0 &&
18781 [ + + ]: 154 : TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated)
1842 peter@eisentraut.org 18782 [ + - ]: 3 : ereport(ERROR,
18783 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
18784 : : errmsg("cannot use generated column in partition key"),
18785 : : errdetail("Column \"%s\" is a generated column.",
18786 : : get_attname(RelationGetRelid(rel), attno, false)),
18787 : : parser_errposition(pstate, pelem->location)));
18788 : : }
18789 : :
18790 : : /*
18791 : : * Preprocess the expression before checking for mutability.
18792 : : * This is essential for the reasons described in
18793 : : * contain_mutable_functions_after_planning. However, we call
18794 : : * expression_planner for ourselves rather than using that
18795 : : * function, because if constant-folding reduces the
18796 : : * expression to a constant, we'd like to know that so we can
18797 : : * complain below.
18798 : : *
18799 : : * Like contain_mutable_functions_after_planning, assume that
18800 : : * expression_planner won't scribble on its input, so this
18801 : : * won't affect the partexprs entry we saved above.
18802 : : */
150 tgl@sss.pgh.pa.us 18803 : 125 : expr = (Node *) expression_planner((Expr *) expr);
18804 : :
18805 : : /*
18806 : : * Partition expressions cannot contain mutable functions,
18807 : : * because a given row must always map to the same partition
18808 : : * as long as there is no change in the partition boundary
18809 : : * structure.
18810 : : */
18811 [ + + ]: 125 : if (contain_mutable_functions(expr))
18812 [ + - ]: 3 : ereport(ERROR,
18813 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
18814 : : errmsg("functions in partition key expression must be marked IMMUTABLE")));
18815 : :
18816 : : /*
18817 : : * While it is not exactly *wrong* for a partition expression
18818 : : * to be a constant, it seems better to reject such keys.
18819 : : */
2513 18820 [ + + ]: 122 : if (IsA(expr, Const))
18821 [ + - ]: 6 : ereport(ERROR,
18822 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
18823 : : errmsg("cannot use constant expression as partition key")));
18824 : : }
18825 : : }
18826 : :
18827 : : /*
18828 : : * Apply collation override if any
18829 : : */
2685 rhaas@postgresql.org 18830 [ + + ]: 2606 : if (pelem->collation)
18831 : 15 : attcollation = get_collation_oid(pelem->collation, false);
18832 : :
18833 : : /*
18834 : : * Check we have a collation iff it's a collatable type. The only
18835 : : * expected failures here are (1) COLLATE applied to a noncollatable
18836 : : * type, or (2) partition expression had an unresolved collation. But
18837 : : * we might as well code this to be a complete consistency check.
18838 : : */
18839 [ + + ]: 2606 : if (type_is_collatable(atttype))
18840 : : {
18841 [ - + ]: 313 : if (!OidIsValid(attcollation))
2685 rhaas@postgresql.org 18842 [ # # ]:UBC 0 : ereport(ERROR,
18843 : : (errcode(ERRCODE_INDETERMINATE_COLLATION),
18844 : : errmsg("could not determine which collation to use for partition expression"),
18845 : : errhint("Use the COLLATE clause to set the collation explicitly.")));
18846 : : }
18847 : : else
18848 : : {
2685 rhaas@postgresql.org 18849 [ - + ]:CBC 2293 : if (OidIsValid(attcollation))
2685 rhaas@postgresql.org 18850 [ # # ]:UBC 0 : ereport(ERROR,
18851 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
18852 : : errmsg("collations are not supported by type %s",
18853 : : format_type_be(atttype))));
18854 : : }
18855 : :
2685 rhaas@postgresql.org 18856 :CBC 2606 : partcollation[attn] = attcollation;
18857 : :
18858 : : /*
18859 : : * Identify the appropriate operator class. For list and range
18860 : : * partitioning, we use a btree operator class; hash partitioning uses
18861 : : * a hash operator class.
18862 : : */
2348 18863 [ + + ]: 2606 : if (strategy == PARTITION_STRATEGY_HASH)
18864 : 138 : am_oid = HASH_AM_OID;
18865 : : else
18866 : 2468 : am_oid = BTREE_AM_OID;
18867 : :
2685 18868 [ + + ]: 2606 : if (!pelem->opclass)
18869 : : {
2348 18870 : 2540 : partopclass[attn] = GetDefaultOpClass(atttype, am_oid);
18871 : :
2685 18872 [ + + ]: 2540 : if (!OidIsValid(partopclass[attn]))
18873 : : {
2348 18874 [ - + ]: 6 : if (strategy == PARTITION_STRATEGY_HASH)
2348 rhaas@postgresql.org 18875 [ # # ]:UBC 0 : ereport(ERROR,
18876 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
18877 : : errmsg("data type %s has no default operator class for access method \"%s\"",
18878 : : format_type_be(atttype), "hash"),
18879 : : errhint("You must specify a hash operator class or define a default hash operator class for the data type.")));
18880 : : else
2348 rhaas@postgresql.org 18881 [ + - ]:CBC 6 : ereport(ERROR,
18882 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
18883 : : errmsg("data type %s has no default operator class for access method \"%s\"",
18884 : : format_type_be(atttype), "btree"),
18885 : : errhint("You must specify a btree operator class or define a default btree operator class for the data type.")));
18886 : : }
18887 : : }
18888 : : else
2685 18889 [ + + ]: 66 : partopclass[attn] = ResolveOpClass(pelem->opclass,
18890 : : atttype,
18891 : : am_oid == HASH_AM_OID ? "hash" : "btree",
18892 : : am_oid);
18893 : :
18894 : 2594 : attn++;
18895 : : }
18896 : 2366 : }
18897 : :
18898 : : /*
18899 : : * PartConstraintImpliedByRelConstraint
18900 : : * Do scanrel's existing constraints imply the partition constraint?
18901 : : *
18902 : : * "Existing constraints" include its check constraints and column-level
18903 : : * not-null constraints. partConstraint describes the partition constraint,
18904 : : * in implicit-AND form.
18905 : : */
18906 : : bool
2432 18907 : 1517 : PartConstraintImpliedByRelConstraint(Relation scanrel,
18908 : : List *partConstraint)
18909 : : {
18910 : 1517 : List *existConstraint = NIL;
18911 : 1517 : TupleConstr *constr = RelationGetDescr(scanrel)->constr;
18912 : : int i;
18913 : :
18914 [ + + + + ]: 1517 : if (constr && constr->has_not_null)
18915 : : {
18916 : 357 : int natts = scanrel->rd_att->natts;
18917 : :
18918 [ + + ]: 1155 : for (i = 1; i <= natts; i++)
18919 : : {
2429 andres@anarazel.de 18920 : 798 : Form_pg_attribute att = TupleDescAttr(scanrel->rd_att, i - 1);
18921 : :
2432 rhaas@postgresql.org 18922 [ + + + - ]: 798 : if (att->attnotnull && !att->attisdropped)
18923 : : {
18924 : 466 : NullTest *ntest = makeNode(NullTest);
18925 : :
18926 : 466 : ntest->arg = (Expr *) makeVar(1,
18927 : : i,
18928 : : att->atttypid,
18929 : : att->atttypmod,
18930 : : att->attcollation,
18931 : : 0);
18932 : 466 : ntest->nulltesttype = IS_NOT_NULL;
18933 : :
18934 : : /*
18935 : : * argisrow=false is correct even for a composite column,
18936 : : * because attnotnull does not represent a SQL-spec IS NOT
18937 : : * NULL test in such a case, just IS DISTINCT FROM NULL.
18938 : : */
18939 : 466 : ntest->argisrow = false;
18940 : 466 : ntest->location = -1;
18941 : 466 : existConstraint = lappend(existConstraint, ntest);
18942 : : }
18943 : : }
18944 : : }
18945 : :
1859 18946 : 1517 : return ConstraintImpliedByRelConstraint(scanrel, partConstraint, existConstraint);
18947 : : }
18948 : :
18949 : : /*
18950 : : * ConstraintImpliedByRelConstraint
18951 : : * Do scanrel's existing constraints imply the given constraint?
18952 : : *
18953 : : * testConstraint is the constraint to validate. provenConstraint is a
18954 : : * caller-provided list of conditions which this function may assume
18955 : : * to be true. Both provenConstraint and testConstraint must be in
18956 : : * implicit-AND form, must only contain immutable clauses, and must
18957 : : * contain only Vars with varno = 1.
18958 : : */
18959 : : bool
18960 : 2183 : ConstraintImpliedByRelConstraint(Relation scanrel, List *testConstraint, List *provenConstraint)
18961 : : {
1789 tgl@sss.pgh.pa.us 18962 : 2183 : List *existConstraint = list_copy(provenConstraint);
1859 rhaas@postgresql.org 18963 : 2183 : TupleConstr *constr = RelationGetDescr(scanrel)->constr;
18964 : : int num_check,
18965 : : i;
18966 : :
2432 18967 [ + + ]: 2183 : num_check = (constr != NULL) ? constr->num_check : 0;
18968 [ + + ]: 2458 : for (i = 0; i < num_check; i++)
18969 : : {
18970 : : Node *cexpr;
18971 : :
18972 : : /*
18973 : : * If this constraint hasn't been fully validated yet, we must ignore
18974 : : * it here.
18975 : : */
18976 [ + + ]: 275 : if (!constr->check[i].ccvalid)
18977 : 3 : continue;
18978 : :
18979 : 272 : cexpr = stringToNode(constr->check[i].ccbin);
18980 : :
18981 : : /*
18982 : : * Run each expression through const-simplification and
18983 : : * canonicalization. It is necessary, because we will be comparing it
18984 : : * to similarly-processed partition constraint expressions, and may
18985 : : * fail to detect valid matches without this.
18986 : : */
18987 : 272 : cexpr = eval_const_expressions(NULL, cexpr);
2226 tgl@sss.pgh.pa.us 18988 : 272 : cexpr = (Node *) canonicalize_qual((Expr *) cexpr, true);
18989 : :
2432 rhaas@postgresql.org 18990 : 272 : existConstraint = list_concat(existConstraint,
18991 : 272 : make_ands_implicit((Expr *) cexpr));
18992 : : }
18993 : :
18994 : : /*
18995 : : * Try to make the proof. Since we are comparing CHECK constraints, we
18996 : : * need to use weak implication, i.e., we assume existConstraint is
18997 : : * not-false and try to prove the same for testConstraint.
18998 : : *
18999 : : * Note that predicate_implied_by assumes its first argument is known
19000 : : * immutable. That should always be true for both NOT NULL and partition
19001 : : * constraints, so we don't test it here.
19002 : : */
1859 19003 : 2183 : return predicate_implied_by(testConstraint, existConstraint, true);
19004 : : }
19005 : :
19006 : : /*
19007 : : * QueuePartitionConstraintValidation
19008 : : *
19009 : : * Add an entry to wqueue to have the given partition constraint validated by
19010 : : * Phase 3, for the given relation, and all its children.
19011 : : *
19012 : : * We first verify whether the given constraint is implied by pre-existing
19013 : : * relation constraints; if it is, there's no need to scan the table to
19014 : : * validate, so don't queue in that case.
19015 : : */
19016 : : static void
2195 alvherre@alvh.no-ip. 19017 : 1202 : QueuePartitionConstraintValidation(List **wqueue, Relation scanrel,
19018 : : List *partConstraint,
19019 : : bool validate_default)
19020 : : {
19021 : : /*
19022 : : * Based on the table's existing constraints, determine whether or not we
19023 : : * may skip scanning the table.
19024 : : */
2432 rhaas@postgresql.org 19025 [ + + ]: 1202 : if (PartConstraintImpliedByRelConstraint(scanrel, partConstraint))
19026 : : {
2383 19027 [ + + ]: 45 : if (!validate_default)
1681 tgl@sss.pgh.pa.us 19028 [ + + ]: 34 : ereport(DEBUG1,
19029 : : (errmsg_internal("partition constraint for table \"%s\" is implied by existing constraints",
19030 : : RelationGetRelationName(scanrel))));
19031 : : else
19032 [ + + ]: 11 : ereport(DEBUG1,
19033 : : (errmsg_internal("updated partition constraint for default partition \"%s\" is implied by existing constraints",
19034 : : RelationGetRelationName(scanrel))));
2432 rhaas@postgresql.org 19035 : 45 : return;
19036 : : }
19037 : :
19038 : : /*
19039 : : * Constraints proved insufficient. For plain relations, queue a
19040 : : * validation item now; for partitioned tables, recurse to process each
19041 : : * partition.
19042 : : */
2195 alvherre@alvh.no-ip. 19043 [ + + ]: 1157 : if (scanrel->rd_rel->relkind == RELKIND_RELATION)
19044 : : {
19045 : : AlteredTableInfo *tab;
19046 : :
19047 : : /* Grab a work queue entry. */
19048 : 960 : tab = ATGetQueueEntry(wqueue, scanrel);
19049 [ - + ]: 960 : Assert(tab->partition_constraint == NULL);
19050 : 960 : tab->partition_constraint = (Expr *) linitial(partConstraint);
19051 : 960 : tab->validate_default = validate_default;
19052 : : }
19053 [ + + ]: 197 : else if (scanrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
19054 : : {
1088 19055 : 173 : PartitionDesc partdesc = RelationGetPartitionDesc(scanrel, true);
19056 : : int i;
19057 : :
2195 19058 [ + + ]: 370 : for (i = 0; i < partdesc->nparts; i++)
19059 : : {
19060 : : Relation part_rel;
19061 : : List *thisPartConstraint;
19062 : :
19063 : : /*
19064 : : * This is the minimum lock we need to prevent deadlocks.
19065 : : */
1910 andres@anarazel.de 19066 : 197 : part_rel = table_open(partdesc->oids[i], AccessExclusiveLock);
19067 : :
19068 : : /*
19069 : : * Adjust the constraint for scanrel so that it matches this
19070 : : * partition's attribute numbers.
19071 : : */
19072 : : thisPartConstraint =
2195 alvherre@alvh.no-ip. 19073 : 197 : map_partition_varattnos(partConstraint, 1,
19074 : : part_rel, scanrel);
19075 : :
19076 : 197 : QueuePartitionConstraintValidation(wqueue, part_rel,
19077 : : thisPartConstraint,
19078 : : validate_default);
1910 andres@anarazel.de 19079 : 197 : table_close(part_rel, NoLock); /* keep lock till commit */
19080 : : }
19081 : : }
19082 : : }
19083 : :
19084 : : /*
19085 : : * attachPartitionTable: attach a new partition to the partitioned table
19086 : : *
19087 : : * wqueue: the ALTER TABLE work queue; can be NULL when not running as part
19088 : : * of an ALTER TABLE sequence.
19089 : : * rel: partitioned relation;
19090 : : * attachrel: relation of attached partition;
19091 : : * bound: bounds of attached relation.
19092 : : */
19093 : : static void
7 akorotkov@postgresql 19094 :GNC 1223 : attachPartitionTable(List **wqueue, Relation rel, Relation attachrel, PartitionBoundSpec *bound)
19095 : : {
19096 : : /* OK to create inheritance. Rest of the checks performed there */
19097 : 1223 : CreateInheritance(attachrel, rel, true);
19098 : :
19099 : : /* Update the pg_class entry. */
19100 : 1187 : StorePartitionBound(attachrel, rel, bound);
19101 : :
19102 : : /* Ensure there exists a correct set of indexes in the partition. */
19103 : 1187 : AttachPartitionEnsureIndexes(wqueue, rel, attachrel);
19104 : :
19105 : : /* and triggers */
19106 : 1169 : CloneRowTriggersToPartition(rel, attachrel);
19107 : :
19108 : : /*
19109 : : * Clone foreign key constraints. Callee is responsible for setting up
19110 : : * for phase 3 constraint verification.
19111 : : */
19112 : 1166 : CloneForeignKeyConstraints(wqueue, rel, attachrel);
19113 : 1166 : }
19114 : :
19115 : : /*
19116 : : * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
19117 : : *
19118 : : * Return the address of the newly attached partition.
19119 : : */
19120 : : static ObjectAddress
1299 tgl@sss.pgh.pa.us 19121 :CBC 1088 : ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd,
19122 : : AlterTableUtilityContext *context)
19123 : : {
19124 : : Relation attachrel,
19125 : : catalog;
19126 : : List *attachrel_children;
19127 : : List *partConstraint;
19128 : : SysScanDesc scan;
19129 : : ScanKeyData skey;
19130 : : AttrNumber attno;
19131 : : int natts;
19132 : : TupleDesc tupleDesc;
19133 : : ObjectAddress address;
19134 : : const char *trigger_name;
19135 : : Oid defaultPartOid;
19136 : : List *partBoundConstraint;
19137 : 1088 : ParseState *pstate = make_parsestate(NULL);
19138 : :
19139 : 1088 : pstate->p_sourcetext = context->queryString;
19140 : :
19141 : : /*
19142 : : * We must lock the default partition if one exists, because attaching a
19143 : : * new partition will change its partition constraint.
19144 : : */
19145 : : defaultPartOid =
1088 alvherre@alvh.no-ip. 19146 : 1088 : get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
2410 rhaas@postgresql.org 19147 [ + + ]: 1088 : if (OidIsValid(defaultPartOid))
19148 : 91 : LockRelationOid(defaultPartOid, AccessExclusiveLock);
19149 : :
1910 andres@anarazel.de 19150 : 1088 : attachrel = table_openrv(cmd->name, AccessExclusiveLock);
19151 : :
19152 : : /*
19153 : : * XXX I think it'd be a good idea to grab locks on all tables referenced
19154 : : * by FKs at this point also.
19155 : : */
19156 : :
19157 : : /*
19158 : : * Must be owner of both parent and source table -- parent was checked by
19159 : : * ATSimplePermissions call in ATPrepCmd
19160 : : */
1011 peter@eisentraut.org 19161 : 1085 : ATSimplePermissions(AT_AttachPartition, attachrel, ATT_TABLE | ATT_FOREIGN_TABLE);
19162 : :
19163 : : /* A partition can only have one parent */
2446 rhaas@postgresql.org 19164 [ + + ]: 1082 : if (attachrel->rd_rel->relispartition)
2685 19165 [ + - ]: 3 : ereport(ERROR,
19166 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19167 : : errmsg("\"%s\" is already a partition",
19168 : : RelationGetRelationName(attachrel))));
19169 : :
2446 19170 [ + + ]: 1079 : if (OidIsValid(attachrel->rd_rel->reloftype))
2685 19171 [ + - ]: 3 : ereport(ERROR,
19172 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19173 : : errmsg("cannot attach a typed table as partition")));
19174 : :
19175 : : /*
19176 : : * Table being attached should not already be part of inheritance; either
19177 : : * as a child table...
19178 : : */
1910 andres@anarazel.de 19179 : 1076 : catalog = table_open(InheritsRelationId, AccessShareLock);
2685 rhaas@postgresql.org 19180 : 1076 : ScanKeyInit(&skey,
19181 : : Anum_pg_inherits_inhrelid,
19182 : : BTEqualStrategyNumber, F_OIDEQ,
19183 : : ObjectIdGetDatum(RelationGetRelid(attachrel)));
19184 : 1076 : scan = systable_beginscan(catalog, InheritsRelidSeqnoIndexId, true,
19185 : : NULL, 1, &skey);
19186 [ + + ]: 1076 : if (HeapTupleIsValid(systable_getnext(scan)))
19187 [ + - ]: 3 : ereport(ERROR,
19188 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19189 : : errmsg("cannot attach inheritance child as partition")));
19190 : 1073 : systable_endscan(scan);
19191 : :
19192 : : /* ...or as a parent table (except the case when it is partitioned) */
19193 : 1073 : ScanKeyInit(&skey,
19194 : : Anum_pg_inherits_inhparent,
19195 : : BTEqualStrategyNumber, F_OIDEQ,
19196 : : ObjectIdGetDatum(RelationGetRelid(attachrel)));
19197 : 1073 : scan = systable_beginscan(catalog, InheritsParentIndexId, true, NULL,
19198 : : 1, &skey);
19199 [ + + ]: 1073 : if (HeapTupleIsValid(systable_getnext(scan)) &&
2446 19200 [ + + ]: 124 : attachrel->rd_rel->relkind == RELKIND_RELATION)
2685 19201 [ + - ]: 3 : ereport(ERROR,
19202 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19203 : : errmsg("cannot attach inheritance parent as partition")));
19204 : 1070 : systable_endscan(scan);
1910 andres@anarazel.de 19205 : 1070 : table_close(catalog, AccessShareLock);
19206 : :
19207 : : /*
19208 : : * Prevent circularity by seeing if rel is a partition of attachrel. (In
19209 : : * particular, this disallows making a rel a partition of itself.)
19210 : : *
19211 : : * We do that by checking if rel is a member of the list of attachrel's
19212 : : * partitions provided the latter is partitioned at all. We want to avoid
19213 : : * having to construct this list again, so we request the strongest lock
19214 : : * on all partitions. We need the strongest lock, because we may decide
19215 : : * to scan them if we find out that the table being attached (or its leaf
19216 : : * partitions) may contain rows that violate the partition constraint. If
19217 : : * the table has a constraint that would prevent such rows, which by
19218 : : * definition is present in all the partitions, we need not scan the
19219 : : * table, nor its partitions. But we cannot risk a deadlock by taking a
19220 : : * weaker lock now and the stronger one only when needed.
19221 : : */
2446 rhaas@postgresql.org 19222 : 1070 : attachrel_children = find_all_inheritors(RelationGetRelid(attachrel),
19223 : : AccessExclusiveLock, NULL);
19224 [ + + ]: 1070 : if (list_member_oid(attachrel_children, RelationGetRelid(rel)))
2685 19225 [ + - ]: 6 : ereport(ERROR,
19226 : : (errcode(ERRCODE_DUPLICATE_TABLE),
19227 : : errmsg("circular inheritance not allowed"),
19228 : : errdetail("\"%s\" is already a child of \"%s\".",
19229 : : RelationGetRelationName(rel),
19230 : : RelationGetRelationName(attachrel))));
19231 : :
19232 : : /* If the parent is permanent, so must be all of its partitions. */
2125 michael@paquier.xyz 19233 [ + + ]: 1064 : if (rel->rd_rel->relpersistence != RELPERSISTENCE_TEMP &&
19234 [ + + ]: 1052 : attachrel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
19235 [ + - ]: 3 : ereport(ERROR,
19236 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19237 : : errmsg("cannot attach a temporary relation as partition of permanent relation \"%s\"",
19238 : : RelationGetRelationName(rel))));
19239 : :
19240 : : /* Temp parent cannot have a partition that is itself not a temp */
2685 rhaas@postgresql.org 19241 [ + + ]: 1061 : if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
2446 19242 [ + + ]: 12 : attachrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
2685 19243 [ + - ]: 9 : ereport(ERROR,
19244 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19245 : : errmsg("cannot attach a permanent relation as partition of temporary relation \"%s\"",
19246 : : RelationGetRelationName(rel))));
19247 : :
19248 : : /* If the parent is temp, it must belong to this session */
19249 [ + + ]: 1052 : if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
19250 [ - + ]: 3 : !rel->rd_islocaltemp)
2685 rhaas@postgresql.org 19251 [ # # ]:UBC 0 : ereport(ERROR,
19252 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19253 : : errmsg("cannot attach as partition of temporary relation of another session")));
19254 : :
19255 : : /* Ditto for the partition */
2446 rhaas@postgresql.org 19256 [ + + ]:CBC 1052 : if (attachrel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
19257 [ - + ]: 3 : !attachrel->rd_islocaltemp)
2685 rhaas@postgresql.org 19258 [ # # ]:UBC 0 : ereport(ERROR,
19259 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19260 : : errmsg("cannot attach temporary relation of another session as partition")));
19261 : :
19262 : : /*
19263 : : * Check if attachrel has any identity columns or any columns that aren't
19264 : : * in the parent.
19265 : : */
2446 rhaas@postgresql.org 19266 :CBC 1052 : tupleDesc = RelationGetDescr(attachrel);
2685 19267 : 1052 : natts = tupleDesc->natts;
19268 [ + + ]: 3622 : for (attno = 1; attno <= natts; attno++)
19269 : : {
2429 andres@anarazel.de 19270 : 2588 : Form_pg_attribute attribute = TupleDescAttr(tupleDesc, attno - 1);
2685 rhaas@postgresql.org 19271 : 2588 : char *attributeName = NameStr(attribute->attname);
19272 : :
19273 : : /* Ignore dropped */
19274 [ + + ]: 2588 : if (attribute->attisdropped)
19275 : 296 : continue;
19276 : :
89 peter@eisentraut.org 19277 [ + + ]:GNC 2292 : if (attribute->attidentity)
19278 [ + - ]: 9 : ereport(ERROR,
19279 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
19280 : : errmsg("table \"%s\" being attached contains an identity column \"%s\"",
19281 : : RelationGetRelationName(attachrel), attributeName),
19282 : : errdetail("The new partition may not contain an identity column."));
19283 : :
19284 : : /* Try to find the column in parent (matching on column name) */
2670 rhaas@postgresql.org 19285 [ + + ]:CBC 2283 : if (!SearchSysCacheExists2(ATTNAME,
19286 : : ObjectIdGetDatum(RelationGetRelid(rel)),
19287 : : CStringGetDatum(attributeName)))
2685 19288 [ + - ]: 9 : ereport(ERROR,
19289 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
19290 : : errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
19291 : : RelationGetRelationName(attachrel), attributeName,
19292 : : RelationGetRelationName(rel)),
19293 : : errdetail("The new partition may contain only the columns present in parent.")));
19294 : : }
19295 : :
19296 : : /*
19297 : : * If child_rel has row-level triggers with transition tables, we
19298 : : * currently don't allow it to become a partition. See also prohibitions
19299 : : * in ATExecAddInherit() and CreateTrigger().
19300 : : */
2446 19301 : 1034 : trigger_name = FindTriggerIncompatibleWithInheritance(attachrel->trigdesc);
2482 rhodiumtoad@postgres 19302 [ + + ]: 1034 : if (trigger_name != NULL)
19303 [ + - ]: 3 : ereport(ERROR,
19304 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
19305 : : errmsg("trigger \"%s\" prevents table \"%s\" from becoming a partition",
19306 : : trigger_name, RelationGetRelationName(attachrel)),
19307 : : errdetail("ROW triggers with transition tables are not supported on partitions.")));
19308 : :
19309 : : /*
19310 : : * Check that the new partition's bound is valid and does not overlap any
19311 : : * of existing partitions of the parent - note that it does not return on
19312 : : * error.
19313 : : */
2446 rhaas@postgresql.org 19314 : 1031 : check_new_partition_bound(RelationGetRelationName(attachrel), rel,
19315 : : cmd->bound, pstate);
19316 : :
19317 : : /* Attach a new partition to the partitioned table. */
7 akorotkov@postgresql 19318 :GNC 1013 : attachPartitionTable(wqueue, rel, attachrel, cmd->bound);
19319 : :
19320 : : /*
19321 : : * Generate partition constraint from the partition bound specification.
19322 : : * If the parent itself is a partition, make sure to include its
19323 : : * constraint as well.
19324 : : */
1005 john.naylor@postgres 19325 :CBC 956 : partBoundConstraint = get_qual_from_partbound(rel, cmd->bound);
2410 rhaas@postgresql.org 19326 : 956 : partConstraint = list_concat(partBoundConstraint,
2657 19327 : 956 : RelationGetPartitionQual(rel));
19328 : :
19329 : : /* Skip validation if there are no constraints to validate. */
2410 19330 [ + + ]: 956 : if (partConstraint)
19331 : : {
19332 : : /*
19333 : : * Run the partition quals through const-simplification similar to
19334 : : * check constraints. We skip canonicalize_qual, though, because
19335 : : * partition quals should be in canonical form already.
19336 : : */
19337 : : partConstraint =
19338 : 932 : (List *) eval_const_expressions(NULL,
19339 : : (Node *) partConstraint);
19340 : :
19341 : : /* XXX this sure looks wrong */
19342 : 932 : partConstraint = list_make1(make_ands_explicit(partConstraint));
19343 : :
19344 : : /*
19345 : : * Adjust the generated constraint to match this partition's attribute
19346 : : * numbers.
19347 : : */
19348 : 932 : partConstraint = map_partition_varattnos(partConstraint, 1, attachrel,
19349 : : rel);
19350 : :
19351 : : /* Validate partition constraints against the table being attached. */
2195 alvherre@alvh.no-ip. 19352 : 932 : QueuePartitionConstraintValidation(wqueue, attachrel, partConstraint,
19353 : : false);
19354 : : }
19355 : :
19356 : : /*
19357 : : * If we're attaching a partition other than the default partition and a
19358 : : * default one exists, then that partition's partition constraint changes,
19359 : : * so add an entry to the work queue to validate it, too. (We must not do
19360 : : * this when the partition being attached is the default one; we already
19361 : : * did it above!)
19362 : : */
2410 rhaas@postgresql.org 19363 [ + + ]: 956 : if (OidIsValid(defaultPartOid))
19364 : : {
19365 : : Relation defaultrel;
19366 : : List *defPartConstraint;
19367 : :
2195 alvherre@alvh.no-ip. 19368 [ - + ]: 73 : Assert(!cmd->bound->is_default);
19369 : :
19370 : : /* we already hold a lock on the default partition */
1910 andres@anarazel.de 19371 : 73 : defaultrel = table_open(defaultPartOid, NoLock);
19372 : : defPartConstraint =
2410 rhaas@postgresql.org 19373 : 73 : get_proposed_default_constraint(partBoundConstraint);
19374 : :
19375 : : /*
19376 : : * Map the Vars in the constraint expression from rel's attnos to
19377 : : * defaultrel's.
19378 : : */
19379 : : defPartConstraint =
1752 alvherre@alvh.no-ip. 19380 : 73 : map_partition_varattnos(defPartConstraint,
19381 : : 1, defaultrel, rel);
2195 19382 : 73 : QueuePartitionConstraintValidation(wqueue, defaultrel,
19383 : : defPartConstraint, true);
19384 : :
19385 : : /* keep our lock until commit. */
1910 andres@anarazel.de 19386 : 73 : table_close(defaultrel, NoLock);
19387 : : }
19388 : :
2446 rhaas@postgresql.org 19389 : 956 : ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel));
19390 : :
19391 : : /*
19392 : : * If the partition we just attached is partitioned itself, invalidate
19393 : : * relcache for all descendent partitions too to ensure that their
19394 : : * rd_partcheck expression trees are rebuilt; partitions already locked at
19395 : : * the beginning of this function.
19396 : : */
909 alvherre@alvh.no-ip. 19397 [ + + ]: 956 : if (attachrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
19398 : : {
19399 : : ListCell *l;
19400 : :
19401 [ + - + + : 498 : foreach(l, attachrel_children)
+ + ]
19402 : : {
19403 : 337 : CacheInvalidateRelcacheByRelid(lfirst_oid(l));
19404 : : }
19405 : : }
19406 : :
19407 : : /* keep our lock until commit */
1910 andres@anarazel.de 19408 : 956 : table_close(attachrel, NoLock);
19409 : :
2685 rhaas@postgresql.org 19410 : 956 : return address;
19411 : : }
19412 : :
19413 : : /*
19414 : : * AttachPartitionEnsureIndexes
19415 : : * subroutine for ATExecAttachPartition to create/match indexes
19416 : : *
19417 : : * Enforce the indexing rule for partitioned tables during ALTER TABLE / ATTACH
19418 : : * PARTITION: every partition must have an index attached to each index on the
19419 : : * partitioned table.
19420 : : */
19421 : : static void
233 alvherre@alvh.no-ip. 19422 :GNC 1187 : AttachPartitionEnsureIndexes(List **wqueue, Relation rel, Relation attachrel)
19423 : : {
19424 : : List *idxes;
19425 : : List *attachRelIdxs;
19426 : : Relation *attachrelIdxRels;
19427 : : IndexInfo **attachInfos;
19428 : : ListCell *cell;
19429 : : MemoryContext cxt;
19430 : : MemoryContext oldcxt;
19431 : :
2277 alvherre@alvh.no-ip. 19432 :CBC 1187 : cxt = AllocSetContextCreate(CurrentMemoryContext,
19433 : : "AttachPartitionEnsureIndexes",
19434 : : ALLOCSET_DEFAULT_SIZES);
19435 : 1187 : oldcxt = MemoryContextSwitchTo(cxt);
19436 : :
19437 : 1187 : idxes = RelationGetIndexList(rel);
19438 : 1187 : attachRelIdxs = RelationGetIndexList(attachrel);
19439 : 1187 : attachrelIdxRels = palloc(sizeof(Relation) * list_length(attachRelIdxs));
19440 : 1187 : attachInfos = palloc(sizeof(IndexInfo *) * list_length(attachRelIdxs));
19441 : :
19442 : : /* Build arrays of all existing indexes and their IndexInfos */
19443 [ + + + + : 1351 : foreach(cell, attachRelIdxs)
+ + ]
19444 : : {
19445 : 164 : Oid cldIdxId = lfirst_oid(cell);
233 alvherre@alvh.no-ip. 19446 :GNC 164 : int i = foreach_current_index(cell);
19447 : :
2277 alvherre@alvh.no-ip. 19448 :CBC 164 : attachrelIdxRels[i] = index_open(cldIdxId, AccessShareLock);
19449 : 164 : attachInfos[i] = BuildIndexInfo(attachrelIdxRels[i]);
19450 : : }
19451 : :
19452 : : /*
19453 : : * If we're attaching a foreign table, we must fail if any of the indexes
19454 : : * is a constraint index; otherwise, there's nothing to do here. Do this
19455 : : * before starting work, to avoid wasting the effort of building a few
19456 : : * non-unique indexes before coming across a unique one.
19457 : : */
1754 19458 [ + + ]: 1187 : if (attachrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
19459 : : {
19460 [ + + + + : 43 : foreach(cell, idxes)
+ + ]
19461 : : {
19462 : 18 : Oid idx = lfirst_oid(cell);
19463 : 18 : Relation idxRel = index_open(idx, AccessShareLock);
19464 : :
19465 [ + + ]: 18 : if (idxRel->rd_index->indisunique ||
19466 [ - + ]: 12 : idxRel->rd_index->indisprimary)
19467 [ + - ]: 6 : ereport(ERROR,
19468 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
19469 : : errmsg("cannot attach foreign table \"%s\" as partition of partitioned table \"%s\"",
19470 : : RelationGetRelationName(attachrel),
19471 : : RelationGetRelationName(rel)),
19472 : : errdetail("Partitioned table \"%s\" contains unique indexes.",
19473 : : RelationGetRelationName(rel))));
19474 : 12 : index_close(idxRel, AccessShareLock);
19475 : : }
19476 : :
19477 : 25 : goto out;
19478 : : }
19479 : :
19480 : : /*
19481 : : * For each index on the partitioned table, find a matching one in the
19482 : : * partition-to-be; if one is not found, create one.
19483 : : */
2277 19484 [ + + + + : 1454 : foreach(cell, idxes)
+ + ]
19485 : : {
19486 : 310 : Oid idx = lfirst_oid(cell);
19487 : 310 : Relation idxRel = index_open(idx, AccessShareLock);
19488 : : IndexInfo *info;
19489 : : AttrMap *attmap;
19490 : 310 : bool found = false;
19491 : : Oid constraintOid;
19492 : :
19493 : : /*
19494 : : * Ignore indexes in the partitioned table other than partitioned
19495 : : * indexes.
19496 : : */
19497 [ - + ]: 310 : if (idxRel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
19498 : : {
2277 alvherre@alvh.no-ip. 19499 :UBC 0 : index_close(idxRel, AccessShareLock);
19500 : 0 : continue;
19501 : : }
19502 : :
19503 : : /* construct an indexinfo to compare existing indexes against */
2277 alvherre@alvh.no-ip. 19504 :CBC 310 : info = BuildIndexInfo(idxRel);
1579 michael@paquier.xyz 19505 : 310 : attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
19506 : : RelationGetDescr(rel),
19507 : : false);
2246 alvherre@alvh.no-ip. 19508 : 310 : constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
19509 : :
19510 : : /*
19511 : : * Scan the list of existing indexes in the partition-to-be, and mark
19512 : : * the first matching, valid, unattached one we find, if any, as
19513 : : * partition of the parent index. If we find one, we're done.
19514 : : */
233 alvherre@alvh.no-ip. 19515 [ + + ]:GNC 340 : for (int i = 0; i < list_length(attachRelIdxs); i++)
19516 : : {
2180 tgl@sss.pgh.pa.us 19517 :CBC 125 : Oid cldIdxId = RelationGetRelid(attachrelIdxRels[i]);
19518 : 125 : Oid cldConstrOid = InvalidOid;
19519 : :
19520 : : /* does this index have a parent? if so, can't use it */
2195 alvherre@alvh.no-ip. 19521 [ + + ]: 125 : if (attachrelIdxRels[i]->rd_rel->relispartition)
2277 19522 : 6 : continue;
19523 : :
19524 : : /* If this index is invalid, can't use it */
291 michael@paquier.xyz 19525 [ + + ]: 119 : if (!attachrelIdxRels[i]->rd_index->indisvalid)
19526 : 3 : continue;
19527 : :
2277 alvherre@alvh.no-ip. 19528 [ + + ]: 116 : if (CompareIndexInfo(attachInfos[i], info,
19529 : 116 : attachrelIdxRels[i]->rd_indcollation,
2277 alvherre@alvh.no-ip. 19530 :GIC 116 : idxRel->rd_indcollation,
2277 alvherre@alvh.no-ip. 19531 :CBC 116 : attachrelIdxRels[i]->rd_opfamily,
2277 alvherre@alvh.no-ip. 19532 :GIC 116 : idxRel->rd_opfamily,
19533 : : attmap))
19534 : : {
19535 : : /*
19536 : : * If this index is being created in the parent because of a
19537 : : * constraint, then the child needs to have a constraint also,
19538 : : * so look for one. If there is no such constraint, this
19539 : : * index is no good, so keep looking.
19540 : : */
2246 alvherre@alvh.no-ip. 19541 [ + + ]:CBC 98 : if (OidIsValid(constraintOid))
19542 : : {
19543 : : cldConstrOid =
19544 : 55 : get_relation_idx_constraint_oid(RelationGetRelid(attachrel),
19545 : : cldIdxId);
19546 : : /* no dice */
19547 [ + + ]: 55 : if (!OidIsValid(cldConstrOid))
19548 : 3 : continue;
19549 : : }
19550 : :
19551 : : /* bingo. */
2277 19552 : 95 : IndexSetParentIndex(attachrelIdxRels[i], idx);
2246 19553 [ + + ]: 95 : if (OidIsValid(constraintOid))
1889 tgl@sss.pgh.pa.us 19554 : 52 : ConstraintSetParentConstraint(cldConstrOid, constraintOid,
19555 : : RelationGetRelid(attachrel));
2277 alvherre@alvh.no-ip. 19556 : 95 : found = true;
19557 : :
1838 19558 : 95 : CommandCounterIncrement();
2277 19559 : 95 : break;
19560 : : }
19561 : : }
19562 : :
19563 : : /*
19564 : : * If no suitable index was found in the partition-to-be, create one
19565 : : * now.
19566 : : */
19567 [ + + ]: 310 : if (!found)
19568 : : {
19569 : : IndexStmt *stmt;
19570 : : Oid conOid;
19571 : :
1818 tgl@sss.pgh.pa.us 19572 : 215 : stmt = generateClonedIndexStmt(NULL,
19573 : : idxRel, attmap,
19574 : : &conOid);
19575 : :
19576 : : /*
19577 : : * If the index is a primary key, mark all columns as NOT NULL if
19578 : : * they aren't already.
19579 : : */
233 alvherre@alvh.no-ip. 19580 [ + + ]:GNC 215 : if (stmt->primary)
19581 : : {
19582 : 107 : MemoryContextSwitchTo(oldcxt);
19583 [ + + ]: 220 : for (int j = 0; j < info->ii_NumIndexKeyAttrs; j++)
19584 : : {
19585 : : AttrNumber childattno;
19586 : :
19587 : 113 : childattno = get_attnum(RelationGetRelid(attachrel),
19588 : 113 : get_attname(RelationGetRelid(rel),
19589 : 113 : info->ii_IndexAttrNumbers[j],
19590 : : false));
19591 : 113 : set_attnotnull(wqueue, attachrel, childattno,
19592 : : true, AccessExclusiveLock);
19593 : : }
19594 : 107 : MemoryContextSwitchTo(cxt);
19595 : : }
19596 : :
2277 alvherre@alvh.no-ip. 19597 :CBC 215 : DefineIndex(RelationGetRelid(attachrel), stmt, InvalidOid,
19598 : : RelationGetRelid(idxRel),
19599 : : conOid,
19600 : : -1,
19601 : : true, false, false, false, false);
19602 : : }
19603 : :
19604 : 298 : index_close(idxRel, AccessShareLock);
19605 : : }
19606 : :
1754 19607 : 1169 : out:
19608 : : /* Clean up. */
233 alvherre@alvh.no-ip. 19609 [ + + ]:GNC 1327 : for (int i = 0; i < list_length(attachRelIdxs); i++)
2277 alvherre@alvh.no-ip. 19610 :CBC 158 : index_close(attachrelIdxRels[i], AccessShareLock);
19611 : 1169 : MemoryContextSwitchTo(oldcxt);
19612 : 1169 : MemoryContextDelete(cxt);
19613 : 1169 : }
19614 : :
19615 : : /*
19616 : : * CloneRowTriggersToPartition
19617 : : * subroutine for ATExecAttachPartition/DefineRelation to create row
19618 : : * triggers on partitions
19619 : : */
19620 : : static void
2214 19621 : 1382 : CloneRowTriggersToPartition(Relation parent, Relation partition)
19622 : : {
19623 : : Relation pg_trigger;
19624 : : ScanKeyData key;
19625 : : SysScanDesc scan;
19626 : : HeapTuple tuple;
19627 : : MemoryContext perTupCxt;
19628 : :
19629 : 1382 : ScanKeyInit(&key, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber,
19630 : : F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(parent)));
1910 andres@anarazel.de 19631 : 1382 : pg_trigger = table_open(TriggerRelationId, RowExclusiveLock);
2214 alvherre@alvh.no-ip. 19632 : 1382 : scan = systable_beginscan(pg_trigger, TriggerRelidNameIndexId,
19633 : : true, NULL, 1, &key);
19634 : :
19635 : 1382 : perTupCxt = AllocSetContextCreate(CurrentMemoryContext,
19636 : : "clone trig", ALLOCSET_SMALL_SIZES);
19637 : :
19638 [ + + ]: 2172 : while (HeapTupleIsValid(tuple = systable_getnext(scan)))
19639 : : {
1777 tgl@sss.pgh.pa.us 19640 : 793 : Form_pg_trigger trigForm = (Form_pg_trigger) GETSTRUCT(tuple);
19641 : : CreateTrigStmt *trigStmt;
2214 alvherre@alvh.no-ip. 19642 : 793 : Node *qual = NULL;
19643 : : Datum value;
19644 : : bool isnull;
19645 : 793 : List *cols = NIL;
1741 19646 : 793 : List *trigargs = NIL;
19647 : : MemoryContext oldcxt;
19648 : :
19649 : : /*
19650 : : * Ignore statement-level triggers; those are not cloned.
19651 : : */
2214 19652 [ + + ]: 793 : if (!TRIGGER_FOR_ROW(trigForm->tgtype))
2214 alvherre@alvh.no-ip. 19653 :GBC 703 : continue;
19654 : :
19655 : : /*
19656 : : * Don't clone internal triggers, because the constraint cloning code
19657 : : * will.
19658 : : */
830 alvherre@alvh.no-ip. 19659 [ + + ]:CBC 781 : if (trigForm->tgisinternal)
2203 19660 : 691 : continue;
19661 : :
19662 : : /*
19663 : : * Complain if we find an unexpected trigger type.
19664 : : */
1488 19665 [ + + ]: 90 : if (!TRIGGER_FOR_BEFORE(trigForm->tgtype) &&
19666 [ - + ]: 81 : !TRIGGER_FOR_AFTER(trigForm->tgtype))
2214 alvherre@alvh.no-ip. 19667 [ # # ]:UBC 0 : elog(ERROR, "unexpected trigger \"%s\" found",
19668 : : NameStr(trigForm->tgname));
19669 : :
19670 : : /* Use short-lived context for CREATE TRIGGER */
1777 tgl@sss.pgh.pa.us 19671 :CBC 90 : oldcxt = MemoryContextSwitchTo(perTupCxt);
19672 : :
19673 : : /*
19674 : : * If there is a WHEN clause, generate a 'cooked' version of it that's
19675 : : * appropriate for the partition.
19676 : : */
2214 alvherre@alvh.no-ip. 19677 : 90 : value = heap_getattr(tuple, Anum_pg_trigger_tgqual,
19678 : : RelationGetDescr(pg_trigger), &isnull);
19679 [ + + ]: 90 : if (!isnull)
19680 : : {
19681 : 3 : qual = stringToNode(TextDatumGetCString(value));
19682 : 3 : qual = (Node *) map_partition_varattnos((List *) qual, PRS2_OLD_VARNO,
19683 : : partition, parent);
19684 : 3 : qual = (Node *) map_partition_varattnos((List *) qual, PRS2_NEW_VARNO,
19685 : : partition, parent);
19686 : : }
19687 : :
19688 : : /*
19689 : : * If there is a column list, transform it to a list of column names.
19690 : : * Note we don't need to map this list in any way ...
19691 : : */
19692 [ + + ]: 90 : if (trigForm->tgattr.dim1 > 0)
19693 : : {
19694 : : int i;
19695 : :
19696 [ + + ]: 6 : for (i = 0; i < trigForm->tgattr.dim1; i++)
19697 : : {
19698 : : Form_pg_attribute col;
19699 : :
19700 : 3 : col = TupleDescAttr(parent->rd_att,
19701 : : trigForm->tgattr.values[i] - 1);
2183 19702 : 3 : cols = lappend(cols,
19703 : 3 : makeString(pstrdup(NameStr(col->attname))));
19704 : : }
19705 : : }
19706 : :
19707 : : /* Reconstruct trigger arguments list. */
1741 19708 [ + + ]: 90 : if (trigForm->tgnargs > 0)
19709 : : {
19710 : : char *p;
19711 : :
19712 : 18 : value = heap_getattr(tuple, Anum_pg_trigger_tgargs,
19713 : : RelationGetDescr(pg_trigger), &isnull);
19714 [ - + ]: 18 : if (isnull)
1741 alvherre@alvh.no-ip. 19715 [ # # ]:UBC 0 : elog(ERROR, "tgargs is null for trigger \"%s\" in partition \"%s\"",
19716 : : NameStr(trigForm->tgname), RelationGetRelationName(partition));
19717 : :
1741 alvherre@alvh.no-ip. 19718 [ + - ]:CBC 18 : p = (char *) VARDATA_ANY(DatumGetByteaPP(value));
19719 : :
19720 [ + + ]: 42 : for (int i = 0; i < trigForm->tgnargs; i++)
19721 : : {
19722 : 24 : trigargs = lappend(trigargs, makeString(pstrdup(p)));
19723 : 24 : p += strlen(p) + 1;
19724 : : }
19725 : : }
19726 : :
2214 19727 : 90 : trigStmt = makeNode(CreateTrigStmt);
1247 tgl@sss.pgh.pa.us 19728 : 90 : trigStmt->replace = false;
19729 : 90 : trigStmt->isconstraint = OidIsValid(trigForm->tgconstraint);
2214 alvherre@alvh.no-ip. 19730 : 90 : trigStmt->trigname = NameStr(trigForm->tgname);
19731 : 90 : trigStmt->relation = NULL;
19732 : 90 : trigStmt->funcname = NULL; /* passed separately */
1741 19733 : 90 : trigStmt->args = trigargs;
2214 19734 : 90 : trigStmt->row = true;
19735 : 90 : trigStmt->timing = trigForm->tgtype & TRIGGER_TYPE_TIMING_MASK;
19736 : 90 : trigStmt->events = trigForm->tgtype & TRIGGER_TYPE_EVENT_MASK;
19737 : 90 : trigStmt->columns = cols;
19738 : 90 : trigStmt->whenClause = NULL; /* passed separately */
19739 : 90 : trigStmt->transitionRels = NIL; /* not supported at present */
19740 : 90 : trigStmt->deferrable = trigForm->tgdeferrable;
19741 : 90 : trigStmt->initdeferred = trigForm->tginitdeferred;
19742 : 90 : trigStmt->constrrel = NULL; /* passed separately */
19743 : :
1003 19744 : 90 : CreateTriggerFiringOn(trigStmt, NULL, RelationGetRelid(partition),
19745 : : trigForm->tgconstrrelid, InvalidOid, InvalidOid,
19746 : : trigForm->tgfoid, trigForm->oid, qual,
19747 : 90 : false, true, trigForm->tgenabled);
19748 : :
1777 tgl@sss.pgh.pa.us 19749 : 87 : MemoryContextSwitchTo(oldcxt);
2214 alvherre@alvh.no-ip. 19750 : 87 : MemoryContextReset(perTupCxt);
19751 : : }
19752 : :
19753 : 1379 : MemoryContextDelete(perTupCxt);
19754 : :
19755 : 1379 : systable_endscan(scan);
1910 andres@anarazel.de 19756 : 1379 : table_close(pg_trigger, RowExclusiveLock);
2214 alvherre@alvh.no-ip. 19757 : 1379 : }
19758 : :
19759 : : /*
19760 : : * ALTER TABLE DETACH PARTITION
19761 : : *
19762 : : * Return the address of the relation that is no longer a partition of rel.
19763 : : *
19764 : : * If concurrent mode is requested, we run in two transactions. A side-
19765 : : * effect is that this command cannot run in a multi-part ALTER TABLE.
19766 : : * Currently, that's enforced by the grammar.
19767 : : *
19768 : : * The strategy for concurrency is to first modify the partition's
19769 : : * pg_inherit catalog row to make it visible to everyone that the
19770 : : * partition is detached, lock the partition against writes, and commit
19771 : : * the transaction; anyone who requests the partition descriptor from
19772 : : * that point onwards has to ignore such a partition. In a second
19773 : : * transaction, we wait until all transactions that could have seen the
19774 : : * partition as attached are gone, then we remove the rest of partition
19775 : : * metadata (pg_inherits and pg_class.relpartbounds).
19776 : : */
19777 : : static ObjectAddress
1116 19778 : 258 : ATExecDetachPartition(List **wqueue, AlteredTableInfo *tab, Relation rel,
19779 : : RangeVar *name, bool concurrent)
19780 : : {
19781 : : Relation partRel;
19782 : : ObjectAddress address;
19783 : : Oid defaultPartOid;
19784 : :
19785 : : /*
19786 : : * We must lock the default partition, because detaching this partition
19787 : : * will change its partition constraint.
19788 : : */
19789 : : defaultPartOid =
1088 19790 : 258 : get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
2410 rhaas@postgresql.org 19791 [ + + ]: 258 : if (OidIsValid(defaultPartOid))
19792 : : {
19793 : : /*
19794 : : * Concurrent detaching when a default partition exists is not
19795 : : * supported. The main problem is that the default partition
19796 : : * constraint would change. And there's a definitional problem: what
19797 : : * should happen to the tuples that are being inserted that belong to
19798 : : * the partition being detached? Putting them on the partition being
19799 : : * detached would be wrong, since they'd become "lost" after the
19800 : : * detaching completes but we cannot put them in the default partition
19801 : : * either until we alter its partition constraint.
19802 : : *
19803 : : * I think we could solve this problem if we effected the constraint
19804 : : * change before committing the first transaction. But the lock would
19805 : : * have to remain AEL and it would cause concurrent query planning to
19806 : : * be blocked, so changing it that way would be even worse.
19807 : : */
1116 alvherre@alvh.no-ip. 19808 [ + + ]: 56 : if (concurrent)
19809 [ + - ]: 6 : ereport(ERROR,
19810 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
19811 : : errmsg("cannot detach partitions concurrently when a default partition exists")));
2410 rhaas@postgresql.org 19812 : 50 : LockRelationOid(defaultPartOid, AccessExclusiveLock);
19813 : : }
19814 : :
19815 : : /*
19816 : : * In concurrent mode, the partition is locked with share-update-exclusive
19817 : : * in the first transaction. This allows concurrent transactions to be
19818 : : * doing DML to the partition.
19819 : : */
1116 alvherre@alvh.no-ip. 19820 [ + + ]: 252 : partRel = table_openrv(name, concurrent ? ShareUpdateExclusiveLock :
19821 : : AccessExclusiveLock);
19822 : :
19823 : : /*
19824 : : * Check inheritance conditions and either delete the pg_inherits row (in
19825 : : * non-concurrent mode) or just set the inhdetachpending flag.
19826 : : */
19827 [ + + ]: 246 : if (!concurrent)
19828 : 173 : RemoveInheritance(partRel, rel, false);
19829 : : else
19830 : 73 : MarkInheritDetached(partRel, rel);
19831 : :
19832 : : /*
19833 : : * Ensure that foreign keys still hold after this detach. This keeps
19834 : : * locks on the referencing tables, which prevents concurrent transactions
19835 : : * from adding rows that we wouldn't see. For this to work in concurrent
19836 : : * mode, it is critical that the partition appears as no longer attached
19837 : : * for the RI queries as soon as the first transaction commits.
19838 : : */
1838 19839 : 236 : ATDetachCheckNoForeignKeyRefs(partRel);
19840 : :
19841 : : /*
19842 : : * Concurrent mode has to work harder; first we add a new constraint to
19843 : : * the partition that matches the partition constraint. Then we close our
19844 : : * existing transaction, and in a new one wait for all processes to catch
19845 : : * up on the catalog updates we've done so far; at that point we can
19846 : : * complete the operation.
19847 : : */
1116 19848 [ + + ]: 219 : if (concurrent)
19849 : : {
19850 : : Oid partrelid,
19851 : : parentrelid;
19852 : : LOCKTAG tag;
19853 : : char *parentrelname;
19854 : : char *partrelname;
19855 : :
19856 : : /*
19857 : : * Add a new constraint to the partition being detached, which
19858 : : * supplants the partition constraint (unless there is one already).
19859 : : */
19860 : 70 : DetachAddConstraintIfNeeded(wqueue, partRel);
19861 : :
19862 : : /*
19863 : : * We're almost done now; the only traces that remain are the
19864 : : * pg_inherits tuple and the partition's relpartbounds. Before we can
19865 : : * remove those, we need to wait until all transactions that know that
19866 : : * this is a partition are gone.
19867 : : */
19868 : :
19869 : : /*
19870 : : * Remember relation OIDs to re-acquire them later; and relation names
19871 : : * too, for error messages if something is dropped in between.
19872 : : */
19873 : 70 : partrelid = RelationGetRelid(partRel);
19874 : 70 : parentrelid = RelationGetRelid(rel);
19875 : 70 : parentrelname = MemoryContextStrdup(PortalContext,
19876 : 70 : RelationGetRelationName(rel));
19877 : 70 : partrelname = MemoryContextStrdup(PortalContext,
19878 : 70 : RelationGetRelationName(partRel));
19879 : :
19880 : : /* Invalidate relcache entries for the parent -- must be before close */
19881 : 70 : CacheInvalidateRelcache(rel);
19882 : :
19883 : 70 : table_close(partRel, NoLock);
19884 : 70 : table_close(rel, NoLock);
19885 : 70 : tab->rel = NULL;
19886 : :
19887 : : /* Make updated catalog entry visible */
19888 : 70 : PopActiveSnapshot();
19889 : 70 : CommitTransactionCommand();
19890 : :
19891 : 70 : StartTransactionCommand();
19892 : :
19893 : : /*
19894 : : * Now wait. This ensures that all queries that were planned
19895 : : * including the partition are finished before we remove the rest of
19896 : : * catalog entries. We don't need or indeed want to acquire this
19897 : : * lock, though -- that would block later queries.
19898 : : *
19899 : : * We don't need to concern ourselves with waiting for a lock on the
19900 : : * partition itself, since we will acquire AccessExclusiveLock below.
19901 : : */
19902 : 70 : SET_LOCKTAG_RELATION(tag, MyDatabaseId, parentrelid);
19903 : 70 : WaitForLockersMultiple(list_make1(&tag), AccessExclusiveLock, false);
19904 : :
19905 : : /*
19906 : : * Now acquire locks in both relations again. Note they may have been
19907 : : * removed in the meantime, so care is required.
19908 : : */
19909 : 45 : rel = try_relation_open(parentrelid, ShareUpdateExclusiveLock);
19910 : 45 : partRel = try_relation_open(partrelid, AccessExclusiveLock);
19911 : :
19912 : : /* If the relations aren't there, something bad happened; bail out */
19913 [ - + ]: 45 : if (rel == NULL)
19914 : : {
1116 alvherre@alvh.no-ip. 19915 [ # # ]:UBC 0 : if (partRel != NULL) /* shouldn't happen */
19916 [ # # ]: 0 : elog(WARNING, "dangling partition \"%s\" remains, can't fix",
19917 : : partrelname);
19918 [ # # ]: 0 : ereport(ERROR,
19919 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
19920 : : errmsg("partitioned table \"%s\" was removed concurrently",
19921 : : parentrelname)));
19922 : : }
1116 alvherre@alvh.no-ip. 19923 [ - + ]:CBC 45 : if (partRel == NULL)
1116 alvherre@alvh.no-ip. 19924 [ # # ]:UBC 0 : ereport(ERROR,
19925 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
19926 : : errmsg("partition \"%s\" was removed concurrently", partrelname)));
19927 : :
1116 alvherre@alvh.no-ip. 19928 :CBC 45 : tab->rel = rel;
19929 : : }
19930 : :
19931 : : /* Do the final part of detaching */
19932 : 194 : DetachPartitionFinalize(rel, partRel, concurrent, defaultPartOid);
19933 : :
19934 : 193 : ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
19935 : :
19936 : : /* keep our lock until commit */
19937 : 193 : table_close(partRel, NoLock);
19938 : :
19939 : 193 : return address;
19940 : : }
19941 : :
19942 : : /*
19943 : : * Second part of ALTER TABLE .. DETACH.
19944 : : *
19945 : : * This is separate so that it can be run independently when the second
19946 : : * transaction of the concurrent algorithm fails (crash or abort).
19947 : : */
19948 : : static void
19949 : 345 : DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent,
19950 : : Oid defaultPartOid)
19951 : : {
19952 : : Relation classRel;
19953 : : List *fks;
19954 : : ListCell *cell;
19955 : : List *indexes;
19956 : : Datum new_val[Natts_pg_class];
19957 : : bool new_null[Natts_pg_class],
19958 : : new_repl[Natts_pg_class];
19959 : : HeapTuple tuple,
19960 : : newtuple;
830 19961 : 345 : Relation trigrel = NULL;
19962 : :
1116 19963 [ + + ]: 345 : if (concurrent)
19964 : : {
19965 : : /*
19966 : : * We can remove the pg_inherits row now. (In the non-concurrent case,
19967 : : * this was already done).
19968 : : */
19969 : 52 : RemoveInheritance(partRel, rel, true);
19970 : : }
19971 : :
19972 : : /* Drop any triggers that were cloned on creation/attach. */
1454 19973 : 345 : DropClonedTriggersFromPartition(RelationGetRelid(partRel));
19974 : :
19975 : : /*
19976 : : * Detach any foreign keys that are inherited. This includes creating
19977 : : * additional action triggers.
19978 : : */
2011 19979 : 345 : fks = copyObject(RelationGetFKeyList(partRel));
830 19980 [ + + ]: 345 : if (fks != NIL)
19981 : 27 : trigrel = table_open(TriggerRelationId, RowExclusiveLock);
2011 19982 [ + + + + : 387 : foreach(cell, fks)
+ + ]
19983 : : {
19984 : 42 : ForeignKeyCacheInfo *fk = lfirst(cell);
19985 : : HeapTuple contup;
19986 : : Form_pg_constraint conform;
19987 : : Constraint *fkconstraint;
19988 : : Oid insertTriggerOid,
19989 : : updateTriggerOid;
19990 : :
19991 : 42 : contup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(fk->conoid));
1806 tgl@sss.pgh.pa.us 19992 [ - + ]: 42 : if (!HeapTupleIsValid(contup))
2011 alvherre@alvh.no-ip. 19993 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for constraint %u", fk->conoid);
1910 alvherre@alvh.no-ip. 19994 :CBC 42 : conform = (Form_pg_constraint) GETSTRUCT(contup);
19995 : :
19996 : : /* consider only the inherited foreign keys */
19997 [ + - ]: 42 : if (conform->contype != CONSTRAINT_FOREIGN ||
19998 [ + + ]: 42 : !OidIsValid(conform->conparentid))
19999 : : {
20000 : 9 : ReleaseSysCache(contup);
20001 : 9 : continue;
20002 : : }
20003 : :
20004 : : /* unset conparentid and adjust conislocal, coninhcount, etc. */
1889 tgl@sss.pgh.pa.us 20005 : 33 : ConstraintSetParentConstraint(fk->conoid, InvalidOid, InvalidOid);
20006 : :
20007 : : /*
20008 : : * Also, look up the partition's "check" triggers corresponding to the
20009 : : * constraint being detached and detach them from the parent triggers.
20010 : : */
830 alvherre@alvh.no-ip. 20011 : 33 : GetForeignKeyCheckTriggers(trigrel,
20012 : : fk->conoid, fk->confrelid, fk->conrelid,
20013 : : &insertTriggerOid, &updateTriggerOid);
20014 [ - + ]: 33 : Assert(OidIsValid(insertTriggerOid));
20015 : 33 : TriggerSetParentTrigger(trigrel, insertTriggerOid, InvalidOid,
20016 : : RelationGetRelid(partRel));
20017 [ - + ]: 33 : Assert(OidIsValid(updateTriggerOid));
20018 : 33 : TriggerSetParentTrigger(trigrel, updateTriggerOid, InvalidOid,
20019 : : RelationGetRelid(partRel));
20020 : :
20021 : : /*
20022 : : * Make the action triggers on the referenced relation. When this was
20023 : : * a partition the action triggers pointed to the parent rel (they
20024 : : * still do), but now we need separate ones of our own.
20025 : : */
1910 20026 : 33 : fkconstraint = makeNode(Constraint);
528 20027 : 33 : fkconstraint->contype = CONSTRAINT_FOREIGN;
1910 20028 : 33 : fkconstraint->conname = pstrdup(NameStr(conform->conname));
20029 : 33 : fkconstraint->deferrable = conform->condeferrable;
20030 : 33 : fkconstraint->initdeferred = conform->condeferred;
528 20031 : 33 : fkconstraint->location = -1;
20032 : 33 : fkconstraint->pktable = NULL;
20033 : 33 : fkconstraint->fk_attrs = NIL;
20034 : 33 : fkconstraint->pk_attrs = NIL;
20035 : 33 : fkconstraint->fk_matchtype = conform->confmatchtype;
20036 : 33 : fkconstraint->fk_upd_action = conform->confupdtype;
20037 : 33 : fkconstraint->fk_del_action = conform->confdeltype;
20038 : 33 : fkconstraint->fk_del_set_cols = NIL;
20039 : 33 : fkconstraint->old_conpfeqop = NIL;
20040 : 33 : fkconstraint->old_pktable_oid = InvalidOid;
20041 : 33 : fkconstraint->skip_validation = false;
20042 : 33 : fkconstraint->initially_valid = true;
20043 : :
1910 20044 : 33 : createForeignKeyActionTriggers(partRel, conform->confrelid,
20045 : : fkconstraint, fk->conoid,
20046 : : conform->conindid,
20047 : : InvalidOid, InvalidOid,
20048 : : NULL, NULL);
20049 : :
2011 20050 : 33 : ReleaseSysCache(contup);
20051 : : }
20052 : 345 : list_free_deep(fks);
830 20053 [ + + ]: 345 : if (trigrel)
20054 : 27 : table_close(trigrel, RowExclusiveLock);
20055 : :
20056 : : /*
20057 : : * Any sub-constraints that are in the referenced-side of a larger
20058 : : * constraint have to be removed. This partition is no longer part of the
20059 : : * key space of the constraint.
20060 : : */
1838 20061 [ + + + + : 366 : foreach(cell, GetParentedForeignKeyRefs(partRel))
+ + ]
20062 : : {
20063 : 22 : Oid constrOid = lfirst_oid(cell);
20064 : : ObjectAddress constraint;
20065 : :
20066 : 22 : ConstraintSetParentConstraint(constrOid, InvalidOid, InvalidOid);
20067 : 22 : deleteDependencyRecordsForClass(ConstraintRelationId,
20068 : : constrOid,
20069 : : ConstraintRelationId,
20070 : : DEPENDENCY_INTERNAL);
20071 : 22 : CommandCounterIncrement();
20072 : :
20073 : 22 : ObjectAddressSet(constraint, ConstraintRelationId, constrOid);
20074 : 22 : performDeletion(&constraint, DROP_RESTRICT, 0);
20075 : : }
20076 : :
20077 : : /* Now we can detach indexes */
1116 20078 : 344 : indexes = RelationGetIndexList(partRel);
20079 [ + + + + : 501 : foreach(cell, indexes)
+ + ]
20080 : : {
20081 : 157 : Oid idxid = lfirst_oid(cell);
20082 : : Relation idx;
20083 : : Oid constrOid;
20084 : :
20085 [ + + ]: 157 : if (!has_superclass(idxid))
20086 : 6 : continue;
20087 : :
20088 [ - + ]: 151 : Assert((IndexGetRelation(get_partition_parent(idxid, false), false) ==
20089 : : RelationGetRelid(rel)));
20090 : :
20091 : 151 : idx = index_open(idxid, AccessExclusiveLock);
20092 : 151 : IndexSetParentIndex(idx, InvalidOid);
20093 : :
20094 : : /* If there's a constraint associated with the index, detach it too */
20095 : 151 : constrOid = get_relation_idx_constraint_oid(RelationGetRelid(partRel),
20096 : : idxid);
20097 [ + + ]: 151 : if (OidIsValid(constrOid))
20098 : 60 : ConstraintSetParentConstraint(constrOid, InvalidOid, InvalidOid);
20099 : :
20100 : 151 : index_close(idx, NoLock);
20101 : : }
20102 : :
20103 : : /* Update pg_class tuple */
20104 : 344 : classRel = table_open(RelationRelationId, RowExclusiveLock);
20105 : 344 : tuple = SearchSysCacheCopy1(RELOID,
20106 : : ObjectIdGetDatum(RelationGetRelid(partRel)));
20107 [ - + ]: 344 : if (!HeapTupleIsValid(tuple))
1116 alvherre@alvh.no-ip. 20108 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u",
20109 : : RelationGetRelid(partRel));
1116 alvherre@alvh.no-ip. 20110 [ - + ]:CBC 344 : Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
20111 : :
20112 : : /* Clear relpartbound and reset relispartition */
20113 : 344 : memset(new_val, 0, sizeof(new_val));
20114 : 344 : memset(new_null, false, sizeof(new_null));
20115 : 344 : memset(new_repl, false, sizeof(new_repl));
20116 : 344 : new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0;
20117 : 344 : new_null[Anum_pg_class_relpartbound - 1] = true;
20118 : 344 : new_repl[Anum_pg_class_relpartbound - 1] = true;
20119 : 344 : newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
20120 : : new_val, new_null, new_repl);
20121 : :
20122 : 344 : ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false;
20123 : 344 : CatalogTupleUpdate(classRel, &newtuple->t_self, newtuple);
20124 : 344 : heap_freetuple(newtuple);
20125 : 344 : table_close(classRel, RowExclusiveLock);
20126 : :
20127 : : /*
20128 : : * Drop identity property from all identity columns of partition.
20129 : : */
89 peter@eisentraut.org 20130 [ + + ]:GNC 1226 : for (int attno = 0; attno < RelationGetNumberOfAttributes(partRel); attno++)
20131 : : {
20132 : 882 : Form_pg_attribute attr = TupleDescAttr(partRel->rd_att, attno);
20133 : :
20134 [ + + + + ]: 882 : if (!attr->attisdropped && attr->attidentity)
20135 : 15 : ATExecDropIdentity(partRel, NameStr(attr->attname), false,
20136 : : AccessExclusiveLock, true, true);
20137 : : }
20138 : :
1116 alvherre@alvh.no-ip. 20139 [ + + ]:CBC 344 : if (OidIsValid(defaultPartOid))
20140 : : {
20141 : : /*
20142 : : * If the relation being detached is the default partition itself,
20143 : : * remove it from the parent's pg_partitioned_table entry.
20144 : : *
20145 : : * If not, we must invalidate default partition's relcache entry, as
20146 : : * in StorePartitionBound: its partition constraint depends on every
20147 : : * other partition's partition constraint.
20148 : : */
20149 [ + + ]: 116 : if (RelationGetRelid(partRel) == defaultPartOid)
20150 : 19 : update_default_partition_oid(RelationGetRelid(rel), InvalidOid);
20151 : : else
20152 : 97 : CacheInvalidateRelcacheByRelid(defaultPartOid);
20153 : : }
20154 : :
20155 : : /*
20156 : : * Invalidate the parent's relcache so that the partition is no longer
20157 : : * included in its partition descriptor.
20158 : : */
2685 rhaas@postgresql.org 20159 : 344 : CacheInvalidateRelcache(rel);
20160 : :
20161 : : /*
20162 : : * If the partition we just detached is partitioned itself, invalidate
20163 : : * relcache for all descendent partitions too to ensure that their
20164 : : * rd_partcheck expression trees are rebuilt; must lock partitions before
20165 : : * doing so, using the same lockmode as what partRel has been locked with
20166 : : * by the caller.
20167 : : */
909 alvherre@alvh.no-ip. 20168 [ + + ]: 344 : if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
20169 : : {
20170 : : List *children;
20171 : :
20172 : 25 : children = find_all_inheritors(RelationGetRelid(partRel),
20173 : : AccessExclusiveLock, NULL);
20174 [ + - + + : 81 : foreach(cell, children)
+ + ]
20175 : : {
20176 : 56 : CacheInvalidateRelcacheByRelid(lfirst_oid(cell));
20177 : : }
20178 : : }
1116 20179 : 344 : }
20180 : :
20181 : : /*
20182 : : * ALTER TABLE ... DETACH PARTITION ... FINALIZE
20183 : : *
20184 : : * To use when a DETACH PARTITION command previously did not run to
20185 : : * completion; this completes the detaching process.
20186 : : */
20187 : : static ObjectAddress
20188 : 7 : ATExecDetachPartitionFinalize(Relation rel, RangeVar *name)
20189 : : {
20190 : : Relation partRel;
20191 : : ObjectAddress address;
20192 : 7 : Snapshot snap = GetActiveSnapshot();
20193 : :
20194 : 7 : partRel = table_openrv(name, AccessExclusiveLock);
20195 : :
20196 : : /*
20197 : : * Wait until existing snapshots are gone. This is important if the
20198 : : * second transaction of DETACH PARTITION CONCURRENTLY is canceled: the
20199 : : * user could immediately run DETACH FINALIZE without actually waiting for
20200 : : * existing transactions. We must not complete the detach action until
20201 : : * all such queries are complete (otherwise we would present them with an
20202 : : * inconsistent view of catalogs).
20203 : : */
20204 : 7 : WaitForOlderSnapshots(snap->xmin, false);
20205 : :
20206 : 7 : DetachPartitionFinalize(rel, partRel, true, InvalidOid);
20207 : :
2685 rhaas@postgresql.org 20208 : 7 : ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
20209 : :
1910 andres@anarazel.de 20210 : 7 : table_close(partRel, NoLock);
20211 : :
2685 rhaas@postgresql.org 20212 : 7 : return address;
20213 : : }
20214 : :
20215 : : /*
20216 : : * DetachAddConstraintIfNeeded
20217 : : * Subroutine for ATExecDetachPartition. Create a constraint that
20218 : : * takes the place of the partition constraint, but avoid creating
20219 : : * a dupe if an constraint already exists which implies the needed
20220 : : * constraint.
20221 : : */
20222 : : static void
1116 alvherre@alvh.no-ip. 20223 : 70 : DetachAddConstraintIfNeeded(List **wqueue, Relation partRel)
20224 : : {
20225 : : List *constraintExpr;
20226 : :
1089 20227 : 70 : constraintExpr = RelationGetPartitionQual(partRel);
20228 : 70 : constraintExpr = (List *) eval_const_expressions(NULL, (Node *) constraintExpr);
20229 : :
20230 : : /*
20231 : : * Avoid adding a new constraint if the needed constraint is implied by an
20232 : : * existing constraint
20233 : : */
20234 [ + + ]: 70 : if (!PartConstraintImpliedByRelConstraint(partRel, constraintExpr))
20235 : : {
20236 : : AlteredTableInfo *tab;
20237 : : Constraint *n;
20238 : :
20239 : 67 : tab = ATGetQueueEntry(wqueue, partRel);
20240 : :
20241 : : /* Add constraint on partition, equivalent to the partition constraint */
20242 : 67 : n = makeNode(Constraint);
20243 : 67 : n->contype = CONSTR_CHECK;
20244 : 67 : n->conname = NULL;
20245 : 67 : n->location = -1;
20246 : 67 : n->is_no_inherit = false;
20247 : 67 : n->raw_expr = NULL;
20248 : 67 : n->cooked_expr = nodeToString(make_ands_explicit(constraintExpr));
20249 : 67 : n->initially_valid = true;
20250 : 67 : n->skip_validation = true;
20251 : : /* It's a re-add, since it nominally already exists */
233 alvherre@alvh.no-ip. 20252 :GNC 67 : ATAddCheckNNConstraint(wqueue, tab, partRel, n,
20253 : : true, false, true, ShareUpdateExclusiveLock);
20254 : : }
1116 alvherre@alvh.no-ip. 20255 :CBC 70 : }
20256 : :
20257 : : /*
20258 : : * DropClonedTriggersFromPartition
20259 : : * subroutine for ATExecDetachPartition to remove any triggers that were
20260 : : * cloned to the partition when it was created-as-partition or attached.
20261 : : * This undoes what CloneRowTriggersToPartition did.
20262 : : */
20263 : : static void
1454 20264 : 345 : DropClonedTriggersFromPartition(Oid partitionId)
20265 : : {
20266 : : ScanKeyData skey;
20267 : : SysScanDesc scan;
20268 : : HeapTuple trigtup;
20269 : : Relation tgrel;
20270 : : ObjectAddresses *objects;
20271 : :
20272 : 345 : objects = new_object_addresses();
20273 : :
20274 : : /*
20275 : : * Scan pg_trigger to search for all triggers on this rel.
20276 : : */
20277 : 345 : ScanKeyInit(&skey, Anum_pg_trigger_tgrelid, BTEqualStrategyNumber,
20278 : : F_OIDEQ, ObjectIdGetDatum(partitionId));
20279 : 345 : tgrel = table_open(TriggerRelationId, RowExclusiveLock);
20280 : 345 : scan = systable_beginscan(tgrel, TriggerRelidNameIndexId,
20281 : : true, NULL, 1, &skey);
20282 [ + + ]: 494 : while (HeapTupleIsValid(trigtup = systable_getnext(scan)))
20283 : : {
20284 : 149 : Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(trigtup);
20285 : : ObjectAddress trig;
20286 : :
20287 : : /* Ignore triggers that weren't cloned */
20288 [ + + ]: 149 : if (!OidIsValid(pg_trigger->tgparentid))
20289 : 128 : continue;
20290 : :
20291 : : /*
20292 : : * Ignore internal triggers that are implementation objects of foreign
20293 : : * keys, because these will be detached when the foreign keys
20294 : : * themselves are.
20295 : : */
830 20296 [ + + ]: 131 : if (OidIsValid(pg_trigger->tgconstrrelid))
20297 : 110 : continue;
20298 : :
20299 : : /*
20300 : : * This is ugly, but necessary: remove the dependency markings on the
20301 : : * trigger so that it can be removed.
20302 : : */
1454 20303 : 21 : deleteDependencyRecordsForClass(TriggerRelationId, pg_trigger->oid,
20304 : : TriggerRelationId,
20305 : : DEPENDENCY_PARTITION_PRI);
20306 : 21 : deleteDependencyRecordsForClass(TriggerRelationId, pg_trigger->oid,
20307 : : RelationRelationId,
20308 : : DEPENDENCY_PARTITION_SEC);
20309 : :
20310 : : /* remember this trigger to remove it below */
20311 : 21 : ObjectAddressSet(trig, TriggerRelationId, pg_trigger->oid);
20312 : 21 : add_exact_object_address(&trig, objects);
20313 : : }
20314 : :
20315 : : /* make the dependency removal visible to the deletion below */
20316 : 345 : CommandCounterIncrement();
20317 : 345 : performMultipleDeletions(objects, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
20318 : :
20319 : : /* done */
20320 : 345 : free_object_addresses(objects);
20321 : 345 : systable_endscan(scan);
20322 : 345 : table_close(tgrel, RowExclusiveLock);
20323 : 345 : }
20324 : :
20325 : : /*
20326 : : * Before acquiring lock on an index, acquire the same lock on the owning
20327 : : * table.
20328 : : */
20329 : : struct AttachIndexCallbackState
20330 : : {
20331 : : Oid partitionOid;
20332 : : Oid parentTblOid;
20333 : : bool lockedParentTbl;
20334 : : };
20335 : :
20336 : : static void
2277 20337 : 203 : RangeVarCallbackForAttachIndex(const RangeVar *rv, Oid relOid, Oid oldRelOid,
20338 : : void *arg)
20339 : : {
20340 : : struct AttachIndexCallbackState *state;
20341 : : Form_pg_class classform;
20342 : : HeapTuple tuple;
20343 : :
20344 : 203 : state = (struct AttachIndexCallbackState *) arg;
20345 : :
20346 [ + + ]: 203 : if (!state->lockedParentTbl)
20347 : : {
20348 : 186 : LockRelationOid(state->parentTblOid, AccessShareLock);
20349 : 186 : state->lockedParentTbl = true;
20350 : : }
20351 : :
20352 : : /*
20353 : : * If we previously locked some other heap, and the name we're looking up
20354 : : * no longer refers to an index on that relation, release the now-useless
20355 : : * lock. XXX maybe we should do *after* we verify whether the index does
20356 : : * not actually belong to the same relation ...
20357 : : */
20358 [ + + - + ]: 203 : if (relOid != oldRelOid && OidIsValid(state->partitionOid))
20359 : : {
2277 alvherre@alvh.no-ip. 20360 :UBC 0 : UnlockRelationOid(state->partitionOid, AccessShareLock);
20361 : 0 : state->partitionOid = InvalidOid;
20362 : : }
20363 : :
20364 : : /* Didn't find a relation, so no need for locking or permission checks. */
2277 alvherre@alvh.no-ip. 20365 [ + + ]:CBC 203 : if (!OidIsValid(relOid))
20366 : 4 : return;
20367 : :
20368 : 199 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
20369 [ - + ]: 199 : if (!HeapTupleIsValid(tuple))
2277 alvherre@alvh.no-ip. 20370 :UBC 0 : return; /* concurrently dropped, so nothing to do */
2277 alvherre@alvh.no-ip. 20371 :CBC 199 : classform = (Form_pg_class) GETSTRUCT(tuple);
20372 [ + + ]: 199 : if (classform->relkind != RELKIND_PARTITIONED_INDEX &&
20373 [ + + ]: 152 : classform->relkind != RELKIND_INDEX)
20374 [ + - ]: 3 : ereport(ERROR,
20375 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
20376 : : errmsg("\"%s\" is not an index", rv->relname)));
20377 : 196 : ReleaseSysCache(tuple);
20378 : :
20379 : : /*
20380 : : * Since we need only examine the heap's tupledesc, an access share lock
20381 : : * on it (preventing any DDL) is sufficient.
20382 : : */
20383 : 196 : state->partitionOid = IndexGetRelation(relOid, false);
20384 : 196 : LockRelationOid(state->partitionOid, AccessShareLock);
20385 : : }
20386 : :
20387 : : /*
20388 : : * ALTER INDEX i1 ATTACH PARTITION i2
20389 : : */
20390 : : static ObjectAddress
20391 : 186 : ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
20392 : : {
20393 : : Relation partIdx;
20394 : : Relation partTbl;
20395 : : Relation parentTbl;
20396 : : ObjectAddress address;
20397 : : Oid partIdxId;
20398 : : Oid currParent;
20399 : : struct AttachIndexCallbackState state;
20400 : :
20401 : : /*
20402 : : * We need to obtain lock on the index 'name' to modify it, but we also
20403 : : * need to read its owning table's tuple descriptor -- so we need to lock
20404 : : * both. To avoid deadlocks, obtain lock on the table before doing so on
20405 : : * the index. Furthermore, we need to examine the parent table of the
20406 : : * partition, so lock that one too.
20407 : : */
20408 : 186 : state.partitionOid = InvalidOid;
20409 : 186 : state.parentTblOid = parentIdx->rd_index->indrelid;
20410 : 186 : state.lockedParentTbl = false;
20411 : : partIdxId =
2207 andres@anarazel.de 20412 : 186 : RangeVarGetRelidExtended(name, AccessExclusiveLock, 0,
20413 : : RangeVarCallbackForAttachIndex,
20414 : : (void *) &state);
20415 : : /* Not there? */
2277 alvherre@alvh.no-ip. 20416 [ - + ]: 180 : if (!OidIsValid(partIdxId))
2277 alvherre@alvh.no-ip. 20417 [ # # ]:UBC 0 : ereport(ERROR,
20418 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
20419 : : errmsg("index \"%s\" does not exist", name->relname)));
20420 : :
20421 : : /* no deadlock risk: RangeVarGetRelidExtended already acquired the lock */
2277 alvherre@alvh.no-ip. 20422 :CBC 180 : partIdx = relation_open(partIdxId, AccessExclusiveLock);
20423 : :
20424 : : /* we already hold locks on both tables, so this is safe: */
20425 : 180 : parentTbl = relation_open(parentIdx->rd_index->indrelid, AccessShareLock);
20426 : 180 : partTbl = relation_open(partIdx->rd_index->indrelid, NoLock);
20427 : :
20428 : 180 : ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partIdx));
20429 : :
20430 : : /* Silently do nothing if already in the right state */
2195 20431 : 360 : currParent = partIdx->rd_rel->relispartition ?
1116 20432 [ + + ]: 180 : get_partition_parent(partIdxId, false) : InvalidOid;
2277 20433 [ + + ]: 180 : if (currParent != RelationGetRelid(parentIdx))
20434 : : {
20435 : : IndexInfo *childInfo;
20436 : : IndexInfo *parentInfo;
20437 : : AttrMap *attmap;
20438 : : bool found;
20439 : : int i;
20440 : : PartitionDesc partDesc;
20441 : : Oid constraintOid,
2246 20442 : 174 : cldConstrId = InvalidOid;
20443 : :
20444 : : /*
20445 : : * If this partition already has an index attached, refuse the
20446 : : * operation.
20447 : : */
2277 20448 : 174 : refuseDupeIndexAttach(parentIdx, partIdx, partTbl);
20449 : :
20450 [ - + ]: 171 : if (OidIsValid(currParent))
2277 alvherre@alvh.no-ip. 20451 [ # # ]:UBC 0 : ereport(ERROR,
20452 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
20453 : : errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
20454 : : RelationGetRelationName(partIdx),
20455 : : RelationGetRelationName(parentIdx)),
20456 : : errdetail("Index \"%s\" is already attached to another index.",
20457 : : RelationGetRelationName(partIdx))));
20458 : :
20459 : : /* Make sure it indexes a partition of the other index's table */
1088 alvherre@alvh.no-ip. 20460 :CBC 171 : partDesc = RelationGetPartitionDesc(parentTbl, true);
2277 20461 : 171 : found = false;
20462 [ + + ]: 266 : for (i = 0; i < partDesc->nparts; i++)
20463 : : {
20464 [ + + ]: 263 : if (partDesc->oids[i] == state.partitionOid)
20465 : : {
20466 : 168 : found = true;
20467 : 168 : break;
20468 : : }
20469 : : }
20470 [ + + ]: 171 : if (!found)
20471 [ + - ]: 3 : ereport(ERROR,
20472 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
20473 : : errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
20474 : : RelationGetRelationName(partIdx),
20475 : : RelationGetRelationName(parentIdx)),
20476 : : errdetail("Index \"%s\" is not an index on any partition of table \"%s\".",
20477 : : RelationGetRelationName(partIdx),
20478 : : RelationGetRelationName(parentTbl))));
20479 : :
20480 : : /* Ensure the indexes are compatible */
20481 : 168 : childInfo = BuildIndexInfo(partIdx);
20482 : 168 : parentInfo = BuildIndexInfo(parentIdx);
1579 michael@paquier.xyz 20483 : 168 : attmap = build_attrmap_by_name(RelationGetDescr(partTbl),
20484 : : RelationGetDescr(parentTbl),
20485 : : false);
2277 alvherre@alvh.no-ip. 20486 [ + + ]: 168 : if (!CompareIndexInfo(childInfo, parentInfo,
2277 alvherre@alvh.no-ip. 20487 :GIC 168 : partIdx->rd_indcollation,
20488 : 168 : parentIdx->rd_indcollation,
20489 : 168 : partIdx->rd_opfamily,
20490 : 168 : parentIdx->rd_opfamily,
20491 : : attmap))
2277 alvherre@alvh.no-ip. 20492 [ + - ]:CBC 21 : ereport(ERROR,
20493 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
20494 : : errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
20495 : : RelationGetRelationName(partIdx),
20496 : : RelationGetRelationName(parentIdx)),
20497 : : errdetail("The index definitions do not match.")));
20498 : :
20499 : : /*
20500 : : * If there is a constraint in the parent, make sure there is one in
20501 : : * the child too.
20502 : : */
2246 20503 : 147 : constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(parentTbl),
20504 : : RelationGetRelid(parentIdx));
20505 : :
20506 [ + + ]: 147 : if (OidIsValid(constraintOid))
20507 : : {
20508 : 58 : cldConstrId = get_relation_idx_constraint_oid(RelationGetRelid(partTbl),
20509 : : partIdxId);
20510 [ + + ]: 58 : if (!OidIsValid(cldConstrId))
20511 [ + - ]: 3 : ereport(ERROR,
20512 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
20513 : : errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
20514 : : RelationGetRelationName(partIdx),
20515 : : RelationGetRelationName(parentIdx)),
20516 : : errdetail("The index \"%s\" belongs to a constraint in table \"%s\" but no constraint exists for index \"%s\".",
20517 : : RelationGetRelationName(parentIdx),
20518 : : RelationGetRelationName(parentTbl),
20519 : : RelationGetRelationName(partIdx))));
20520 : : }
20521 : :
20522 : : /*
20523 : : * If it's a primary key, make sure the columns in the partition are
20524 : : * NOT NULL.
20525 : : */
233 alvherre@alvh.no-ip. 20526 [ + + ]:GNC 144 : if (parentIdx->rd_index->indisprimary)
20527 : 49 : verifyPartitionIndexNotNull(childInfo, partTbl);
20528 : :
20529 : : /* All good -- do it */
2277 alvherre@alvh.no-ip. 20530 :CBC 141 : IndexSetParentIndex(partIdx, RelationGetRelid(parentIdx));
2246 20531 [ + + ]: 141 : if (OidIsValid(constraintOid))
1889 tgl@sss.pgh.pa.us 20532 : 52 : ConstraintSetParentConstraint(cldConstrId, constraintOid,
20533 : : RelationGetRelid(partTbl));
20534 : :
1579 michael@paquier.xyz 20535 : 141 : free_attrmap(attmap);
20536 : :
2277 alvherre@alvh.no-ip. 20537 : 141 : validatePartitionedIndex(parentIdx, parentTbl);
20538 : : }
20539 : :
20540 : 147 : relation_close(parentTbl, AccessShareLock);
20541 : : /* keep these locks till commit */
20542 : 147 : relation_close(partTbl, NoLock);
20543 : 147 : relation_close(partIdx, NoLock);
20544 : :
20545 : 147 : return address;
20546 : : }
20547 : :
20548 : : /*
20549 : : * Verify whether the given partition already contains an index attached
20550 : : * to the given partitioned index. If so, raise an error.
20551 : : */
20552 : : static void
20553 : 174 : refuseDupeIndexAttach(Relation parentIdx, Relation partIdx, Relation partitionTbl)
20554 : : {
20555 : : Oid existingIdx;
20556 : :
1852 20557 : 174 : existingIdx = index_get_partition(partitionTbl,
20558 : : RelationGetRelid(parentIdx));
20559 [ + + ]: 174 : if (OidIsValid(existingIdx))
20560 [ + - ]: 3 : ereport(ERROR,
20561 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
20562 : : errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
20563 : : RelationGetRelationName(partIdx),
20564 : : RelationGetRelationName(parentIdx)),
20565 : : errdetail("Another index is already attached for partition \"%s\".",
20566 : : RelationGetRelationName(partitionTbl))));
2277 20567 : 171 : }
20568 : :
20569 : : /*
20570 : : * Verify whether the set of attached partition indexes to a parent index on
20571 : : * a partitioned table is complete. If it is, mark the parent index valid.
20572 : : *
20573 : : * This should be called each time a partition index is attached.
20574 : : */
20575 : : static void
20576 : 162 : validatePartitionedIndex(Relation partedIdx, Relation partedTbl)
20577 : : {
20578 : : Relation inheritsRel;
20579 : : SysScanDesc scan;
20580 : : ScanKeyData key;
2180 tgl@sss.pgh.pa.us 20581 : 162 : int tuples = 0;
20582 : : HeapTuple inhTup;
20583 : 162 : bool updated = false;
20584 : :
2277 alvherre@alvh.no-ip. 20585 [ - + ]: 162 : Assert(partedIdx->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
20586 : :
20587 : : /*
20588 : : * Scan pg_inherits for this parent index. Count each valid index we find
20589 : : * (verifying the pg_index entry for each), and if we reach the total
20590 : : * amount we expect, we can mark this parent index as valid.
20591 : : */
1910 andres@anarazel.de 20592 : 162 : inheritsRel = table_open(InheritsRelationId, AccessShareLock);
2277 alvherre@alvh.no-ip. 20593 : 162 : ScanKeyInit(&key, Anum_pg_inherits_inhparent,
20594 : : BTEqualStrategyNumber, F_OIDEQ,
20595 : : ObjectIdGetDatum(RelationGetRelid(partedIdx)));
20596 : 162 : scan = systable_beginscan(inheritsRel, InheritsParentIndexId, true,
20597 : : NULL, 1, &key);
20598 [ + + ]: 420 : while ((inhTup = systable_getnext(scan)) != NULL)
20599 : : {
20600 : 258 : Form_pg_inherits inhForm = (Form_pg_inherits) GETSTRUCT(inhTup);
20601 : : HeapTuple indTup;
20602 : : Form_pg_index indexForm;
20603 : :
20604 : 258 : indTup = SearchSysCache1(INDEXRELID,
20605 : : ObjectIdGetDatum(inhForm->inhrelid));
1806 tgl@sss.pgh.pa.us 20606 [ - + ]: 258 : if (!HeapTupleIsValid(indTup))
1806 tgl@sss.pgh.pa.us 20607 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for index %u", inhForm->inhrelid);
2277 alvherre@alvh.no-ip. 20608 :CBC 258 : indexForm = (Form_pg_index) GETSTRUCT(indTup);
1935 peter_e@gmx.net 20609 [ + + ]: 258 : if (indexForm->indisvalid)
2277 alvherre@alvh.no-ip. 20610 : 229 : tuples += 1;
20611 : 258 : ReleaseSysCache(indTup);
20612 : : }
20613 : :
20614 : : /* Done with pg_inherits */
20615 : 162 : systable_endscan(scan);
1910 andres@anarazel.de 20616 : 162 : table_close(inheritsRel, AccessShareLock);
20617 : :
20618 : : /*
20619 : : * If we found as many inherited indexes as the partitioned table has
20620 : : * partitions, we're good; update pg_index to set indisvalid.
20621 : : */
1088 alvherre@alvh.no-ip. 20622 [ + + ]: 162 : if (tuples == RelationGetPartitionDesc(partedTbl, true)->nparts)
20623 : : {
20624 : : Relation idxRel;
20625 : : HeapTuple indTup;
20626 : : Form_pg_index indexForm;
20627 : :
1910 andres@anarazel.de 20628 : 81 : idxRel = table_open(IndexRelationId, RowExclusiveLock);
275 michael@paquier.xyz 20629 : 81 : indTup = SearchSysCacheCopy1(INDEXRELID,
20630 : : ObjectIdGetDatum(RelationGetRelid(partedIdx)));
20631 [ - + ]: 81 : if (!HeapTupleIsValid(indTup))
275 michael@paquier.xyz 20632 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for index %u",
20633 : : RelationGetRelid(partedIdx));
275 michael@paquier.xyz 20634 :CBC 81 : indexForm = (Form_pg_index) GETSTRUCT(indTup);
20635 : :
20636 : 81 : indexForm->indisvalid = true;
2277 alvherre@alvh.no-ip. 20637 : 81 : updated = true;
20638 : :
275 michael@paquier.xyz 20639 : 81 : CatalogTupleUpdate(idxRel, &indTup->t_self, indTup);
20640 : :
1910 andres@anarazel.de 20641 : 81 : table_close(idxRel, RowExclusiveLock);
275 michael@paquier.xyz 20642 : 81 : heap_freetuple(indTup);
20643 : : }
20644 : :
20645 : : /*
20646 : : * If this index is in turn a partition of a larger index, validating it
20647 : : * might cause the parent to become valid also. Try that.
20648 : : */
2195 alvherre@alvh.no-ip. 20649 [ + + + + ]: 162 : if (updated && partedIdx->rd_rel->relispartition)
20650 : : {
20651 : : Oid parentIdxId,
20652 : : parentTblId;
20653 : : Relation parentIdx,
20654 : : parentTbl;
20655 : :
20656 : : /* make sure we see the validation we just did */
2277 20657 : 21 : CommandCounterIncrement();
20658 : :
1116 20659 : 21 : parentIdxId = get_partition_parent(RelationGetRelid(partedIdx), false);
20660 : 21 : parentTblId = get_partition_parent(RelationGetRelid(partedTbl), false);
2277 20661 : 21 : parentIdx = relation_open(parentIdxId, AccessExclusiveLock);
20662 : 21 : parentTbl = relation_open(parentTblId, AccessExclusiveLock);
20663 [ - + ]: 21 : Assert(!parentIdx->rd_index->indisvalid);
20664 : :
20665 : 21 : validatePartitionedIndex(parentIdx, parentTbl);
20666 : :
20667 : 21 : relation_close(parentIdx, AccessExclusiveLock);
20668 : 21 : relation_close(parentTbl, AccessExclusiveLock);
20669 : : }
20670 : 162 : }
20671 : :
20672 : : /*
20673 : : * When attaching an index as a partition of a partitioned index which is a
20674 : : * primary key, verify that all the columns in the partition are marked NOT
20675 : : * NULL.
20676 : : */
20677 : : static void
233 alvherre@alvh.no-ip. 20678 :GNC 49 : verifyPartitionIndexNotNull(IndexInfo *iinfo, Relation partition)
20679 : : {
20680 [ + + ]: 96 : for (int i = 0; i < iinfo->ii_NumIndexKeyAttrs; i++)
20681 : : {
20682 : 50 : Form_pg_attribute att = TupleDescAttr(RelationGetDescr(partition),
20683 : : iinfo->ii_IndexAttrNumbers[i] - 1);
20684 : :
20685 [ + + ]: 50 : if (!att->attnotnull)
20686 [ + - ]: 3 : ereport(ERROR,
20687 : : errcode(ERRCODE_INVALID_TABLE_DEFINITION),
20688 : : errmsg("invalid primary key definition"),
20689 : : errdetail("Column \"%s\" of relation \"%s\" is not marked NOT NULL.",
20690 : : NameStr(att->attname),
20691 : : RelationGetRelationName(partition)));
20692 : : }
20693 : 46 : }
20694 : :
20695 : : /*
20696 : : * Return an OID list of constraints that reference the given relation
20697 : : * that are marked as having a parent constraints.
20698 : : */
20699 : : static List *
1838 alvherre@alvh.no-ip. 20700 :CBC 581 : GetParentedForeignKeyRefs(Relation partition)
20701 : : {
20702 : : Relation pg_constraint;
20703 : : HeapTuple tuple;
20704 : : SysScanDesc scan;
20705 : : ScanKeyData key[2];
20706 : 581 : List *constraints = NIL;
20707 : :
20708 : : /*
20709 : : * If no indexes, or no columns are referenceable by FKs, we can avoid the
20710 : : * scan.
20711 : : */
20712 [ + + + + ]: 822 : if (RelationGetIndexList(partition) == NIL ||
20713 : 241 : bms_is_empty(RelationGetIndexAttrBitmap(partition,
20714 : : INDEX_ATTR_BITMAP_KEY)))
20715 : 463 : return NIL;
20716 : :
20717 : : /* Search for constraints referencing this table */
20718 : 118 : pg_constraint = table_open(ConstraintRelationId, AccessShareLock);
20719 : 118 : ScanKeyInit(&key[0],
20720 : : Anum_pg_constraint_confrelid, BTEqualStrategyNumber,
20721 : : F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(partition)));
20722 : 118 : ScanKeyInit(&key[1],
20723 : : Anum_pg_constraint_contype, BTEqualStrategyNumber,
20724 : : F_CHAREQ, CharGetDatum(CONSTRAINT_FOREIGN));
20725 : :
20726 : : /* XXX This is a seqscan, as we don't have a usable index */
20727 : 118 : scan = systable_beginscan(pg_constraint, InvalidOid, true, NULL, 2, key);
20728 [ + + ]: 183 : while ((tuple = systable_getnext(scan)) != NULL)
20729 : : {
20730 : 65 : Form_pg_constraint constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
20731 : :
20732 : : /*
20733 : : * We only need to process constraints that are part of larger ones.
20734 : : */
20735 [ - + ]: 65 : if (!OidIsValid(constrForm->conparentid))
1838 alvherre@alvh.no-ip. 20736 :UBC 0 : continue;
20737 : :
1838 alvherre@alvh.no-ip. 20738 :CBC 65 : constraints = lappend_oid(constraints, constrForm->oid);
20739 : : }
20740 : :
20741 : 118 : systable_endscan(scan);
20742 : 118 : table_close(pg_constraint, AccessShareLock);
20743 : :
20744 : 118 : return constraints;
20745 : : }
20746 : :
20747 : : /*
20748 : : * During DETACH PARTITION, verify that any foreign keys pointing to the
20749 : : * partitioned table would not become invalid. An error is raised if any
20750 : : * referenced values exist.
20751 : : */
20752 : : static void
20753 : 236 : ATDetachCheckNoForeignKeyRefs(Relation partition)
20754 : : {
20755 : : List *constraints;
20756 : : ListCell *cell;
20757 : :
20758 : 236 : constraints = GetParentedForeignKeyRefs(partition);
20759 : :
20760 [ + + + + : 262 : foreach(cell, constraints)
+ + ]
20761 : : {
20762 : 43 : Oid constrOid = lfirst_oid(cell);
20763 : : HeapTuple tuple;
20764 : : Form_pg_constraint constrForm;
20765 : : Relation rel;
638 peter@eisentraut.org 20766 : 43 : Trigger trig = {0};
20767 : :
1838 alvherre@alvh.no-ip. 20768 : 43 : tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
20769 [ - + ]: 43 : if (!HeapTupleIsValid(tuple))
1838 alvherre@alvh.no-ip. 20770 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for constraint %u", constrOid);
1838 alvherre@alvh.no-ip. 20771 :CBC 43 : constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
20772 : :
20773 [ - + ]: 43 : Assert(OidIsValid(constrForm->conparentid));
20774 [ - + ]: 43 : Assert(constrForm->confrelid == RelationGetRelid(partition));
20775 : :
20776 : : /* prevent data changes into the referencing table until commit */
20777 : 43 : rel = table_open(constrForm->conrelid, ShareLock);
20778 : :
20779 : 43 : trig.tgoid = InvalidOid;
20780 : 43 : trig.tgname = NameStr(constrForm->conname);
20781 : 43 : trig.tgenabled = TRIGGER_FIRES_ON_ORIGIN;
20782 : 43 : trig.tgisinternal = true;
20783 : 43 : trig.tgconstrrelid = RelationGetRelid(partition);
20784 : 43 : trig.tgconstrindid = constrForm->conindid;
20785 : 43 : trig.tgconstraint = constrForm->oid;
20786 : 43 : trig.tgdeferrable = false;
20787 : 43 : trig.tginitdeferred = false;
20788 : : /* we needn't fill in remaining fields */
20789 : :
20790 : 43 : RI_PartitionRemove_Check(&trig, rel, partition);
20791 : :
20792 : 26 : ReleaseSysCache(tuple);
20793 : :
20794 : 26 : table_close(rel, NoLock);
20795 : : }
20796 : 219 : }
20797 : :
20798 : : /*
20799 : : * resolve column compression specification to compression method.
20800 : : */
20801 : : static char
229 peter@eisentraut.org 20802 :GNC 107084 : GetAttributeCompression(Oid atttypid, const char *compression)
20803 : : {
20804 : : char cmethod;
20805 : :
1053 tgl@sss.pgh.pa.us 20806 [ + + + + ]:CBC 107084 : if (compression == NULL || strcmp(compression, "default") == 0)
20807 : 107013 : return InvalidCompressionMethod;
20808 : :
20809 : : /*
20810 : : * To specify a nondefault method, the column data type must be toastable.
20811 : : * Note this says nothing about whether the column's attstorage setting
20812 : : * permits compression; we intentionally allow attstorage and
20813 : : * attcompression to be independent. But with a non-toastable type,
20814 : : * attstorage could not be set to a value that would permit compression.
20815 : : *
20816 : : * We don't actually need to enforce this, since nothing bad would happen
20817 : : * if attcompression were non-default; it would never be consulted. But
20818 : : * it seems more user-friendly to complain about a certainly-useless
20819 : : * attempt to set the property.
20820 : : */
20821 [ + + ]: 71 : if (!TypeIsToastable(atttypid))
1122 rhaas@postgresql.org 20822 [ + - ]: 3 : ereport(ERROR,
20823 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
20824 : : errmsg("column data type %s does not support compression",
20825 : : format_type_be(atttypid))));
20826 : :
1119 20827 : 68 : cmethod = CompressionNameToMethod(compression);
20828 [ + + ]: 68 : if (!CompressionMethodIsValid(cmethod))
20829 [ + - ]: 6 : ereport(ERROR,
20830 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
20831 : : errmsg("invalid compression method \"%s\"", compression)));
20832 : :
1122 20833 : 62 : return cmethod;
20834 : : }
20835 : :
20836 : : /*
20837 : : * resolve column storage specification
20838 : : */
20839 : : static char
641 peter@eisentraut.org 20840 : 121 : GetAttributeStorage(Oid atttypid, const char *storagemode)
20841 : : {
20842 : 121 : char cstorage = 0;
20843 : :
20844 [ + + ]: 121 : if (pg_strcasecmp(storagemode, "plain") == 0)
20845 : 25 : cstorage = TYPSTORAGE_PLAIN;
20846 [ + + ]: 96 : else if (pg_strcasecmp(storagemode, "external") == 0)
20847 : 78 : cstorage = TYPSTORAGE_EXTERNAL;
20848 [ + + ]: 18 : else if (pg_strcasecmp(storagemode, "extended") == 0)
20849 : 8 : cstorage = TYPSTORAGE_EXTENDED;
20850 [ + + ]: 10 : else if (pg_strcasecmp(storagemode, "main") == 0)
20851 : 7 : cstorage = TYPSTORAGE_MAIN;
521 tgl@sss.pgh.pa.us 20852 [ + - ]: 3 : else if (pg_strcasecmp(storagemode, "default") == 0)
20853 : 3 : cstorage = get_typstorage(atttypid);
20854 : : else
641 peter@eisentraut.org 20855 [ # # ]:UBC 0 : ereport(ERROR,
20856 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
20857 : : errmsg("invalid storage type \"%s\"",
20858 : : storagemode)));
20859 : :
20860 : : /*
20861 : : * safety check: do not allow toasted storage modes unless column datatype
20862 : : * is TOAST-aware.
20863 : : */
641 peter@eisentraut.org 20864 [ + + + + ]:CBC 121 : if (!(cstorage == TYPSTORAGE_PLAIN || TypeIsToastable(atttypid)))
20865 [ + - ]: 3 : ereport(ERROR,
20866 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
20867 : : errmsg("column data type %s can only have storage PLAIN",
20868 : : format_type_be(atttypid))));
20869 : :
20870 : 118 : return cstorage;
20871 : : }
20872 : :
20873 : : /*
20874 : : * Struct with context of new partition for insert rows from splited partition
20875 : : */
20876 : : typedef struct SplitPartitionContext
20877 : : {
20878 : : ExprState *partqualstate; /* expression for checking slot for partition
20879 : : * (NULL for DEFAULT partition) */
20880 : : BulkInsertState bistate; /* state of bulk inserts for partition */
20881 : : TupleTableSlot *dstslot; /* slot for inserting row into partition */
20882 : : Relation partRel; /* relation for partition */
20883 : : } SplitPartitionContext;
20884 : :
20885 : :
20886 : : /*
20887 : : * createSplitPartitionContext: create context for partition and fill it
20888 : : */
20889 : : static SplitPartitionContext *
7 akorotkov@postgresql 20890 :GNC 201 : createSplitPartitionContext(Relation partRel)
20891 : : {
20892 : : SplitPartitionContext *pc;
20893 : :
20894 : 201 : pc = (SplitPartitionContext *) palloc0(sizeof(SplitPartitionContext));
20895 : 201 : pc->partRel = partRel;
20896 : :
20897 : : /*
20898 : : * Prepare a BulkInsertState for table_tuple_insert. The FSM is empty, so
20899 : : * don't bother using it.
20900 : : */
20901 : 201 : pc->bistate = GetBulkInsertState();
20902 : :
20903 : : /* Create tuple slot for new partition. */
20904 : 201 : pc->dstslot = MakeSingleTupleTableSlot(RelationGetDescr(pc->partRel),
20905 : : table_slot_callbacks(pc->partRel));
20906 : 201 : ExecStoreAllNullTuple(pc->dstslot);
20907 : :
20908 : 201 : return pc;
20909 : : }
20910 : :
20911 : : /*
20912 : : * deleteSplitPartitionContext: delete context for partition
20913 : : */
20914 : : static void
20915 : 201 : deleteSplitPartitionContext(SplitPartitionContext *pc, int ti_options)
20916 : : {
20917 : 201 : ExecDropSingleTupleTableSlot(pc->dstslot);
20918 : 201 : FreeBulkInsertState(pc->bistate);
20919 : :
20920 : 201 : table_finish_bulk_insert(pc->partRel, ti_options);
20921 : :
20922 : 201 : pfree(pc);
20923 : 201 : }
20924 : :
20925 : : /*
20926 : : * moveSplitTableRows: scan split partition (splitRel) of partitioned table
20927 : : * (rel) and move rows into new partitions.
20928 : : *
20929 : : * New partitions description:
20930 : : * partlist: list of pointers to SinglePartitionSpec structures.
20931 : : * newPartRels: list of Relation's.
20932 : : * defaultPartOid: oid of DEFAULT partition, for table rel.
20933 : : */
20934 : : static void
20935 : 57 : moveSplitTableRows(Relation rel, Relation splitRel, List *partlist, List *newPartRels, Oid defaultPartOid)
20936 : : {
20937 : : /* The FSM is empty, so don't bother using it. */
20938 : 57 : int ti_options = TABLE_INSERT_SKIP_FSM;
20939 : : CommandId mycid;
20940 : : EState *estate;
20941 : : ListCell *listptr,
20942 : : *listptr2;
20943 : : TupleTableSlot *srcslot;
20944 : : ExprContext *econtext;
20945 : : TableScanDesc scan;
20946 : : Snapshot snapshot;
20947 : : MemoryContext oldCxt;
20948 : 57 : List *partContexts = NIL;
20949 : : TupleConversionMap *tuple_map;
20950 : 57 : SplitPartitionContext *defaultPartCtx = NULL,
20951 : : *pc;
20952 : 57 : bool isOldDefaultPart = false;
20953 : :
20954 : 57 : mycid = GetCurrentCommandId(true);
20955 : :
20956 : 57 : estate = CreateExecutorState();
20957 : :
20958 [ + - + + : 234 : forboth(listptr, partlist, listptr2, newPartRels)
+ - + + +
+ + - +
+ ]
20959 : : {
20960 : 177 : SinglePartitionSpec *sps = (SinglePartitionSpec *) lfirst(listptr);
20961 : :
20962 : 177 : pc = createSplitPartitionContext((Relation) lfirst(listptr2));
20963 : :
20964 [ + + ]: 177 : if (sps->bound->is_default)
20965 : : {
20966 : : /* We should not create constraint for detached DEFAULT partition. */
20967 : 15 : defaultPartCtx = pc;
20968 : : }
20969 : : else
20970 : : {
20971 : : List *partConstraint;
20972 : :
20973 : : /* Build expression execution states for partition check quals. */
20974 : 162 : partConstraint = get_qual_from_partbound(rel, sps->bound);
20975 : : partConstraint =
20976 : 162 : (List *) eval_const_expressions(NULL,
20977 : : (Node *) partConstraint);
20978 : : /* Make boolean expression for ExecCheck(). */
20979 : 162 : partConstraint = list_make1(make_ands_explicit(partConstraint));
20980 : :
20981 : : /*
20982 : : * Map the vars in the constraint expression from rel's attnos to
20983 : : * splitRel's.
20984 : : */
20985 : 162 : partConstraint = map_partition_varattnos(partConstraint,
20986 : : 1, splitRel, rel);
20987 : :
20988 : 162 : pc->partqualstate =
20989 : 162 : ExecPrepareExpr((Expr *) linitial(partConstraint), estate);
20990 [ - + ]: 162 : Assert(pc->partqualstate != NULL);
20991 : : }
20992 : :
20993 : : /* Store partition context into list. */
20994 : 177 : partContexts = lappend(partContexts, pc);
20995 : : }
20996 : :
20997 : : /*
20998 : : * Create partition context for DEFAULT partition. We can insert values
20999 : : * into this partition in case spaces with values between new partitions.
21000 : : */
21001 [ + + + + ]: 57 : if (!defaultPartCtx && OidIsValid(defaultPartOid))
21002 : : {
21003 : : /* Indicate that we allocate context for old DEFAULT partition */
21004 : 24 : isOldDefaultPart = true;
21005 : 24 : defaultPartCtx = createSplitPartitionContext(table_open(defaultPartOid, AccessExclusiveLock));
21006 : : }
21007 : :
21008 [ - + ]: 57 : econtext = GetPerTupleExprContext(estate);
21009 : :
21010 : : /* Create necessary tuple slot. */
21011 : 57 : srcslot = MakeSingleTupleTableSlot(RelationGetDescr(splitRel),
21012 : : table_slot_callbacks(splitRel));
21013 : :
21014 : : /*
21015 : : * Map computing for moving attributes of split partition to new partition
21016 : : * (for first new partition but other new partitions can use the same
21017 : : * map).
21018 : : */
21019 : 57 : pc = (SplitPartitionContext *) lfirst(list_head(partContexts));
21020 : 57 : tuple_map = convert_tuples_by_name(RelationGetDescr(splitRel),
21021 : 57 : RelationGetDescr(pc->partRel));
21022 : :
21023 : : /* Scan through the rows. */
21024 : 57 : snapshot = RegisterSnapshot(GetLatestSnapshot());
21025 : 57 : scan = table_beginscan(splitRel, snapshot, 0, NULL);
21026 : :
21027 : : /*
21028 : : * Switch to per-tuple memory context and reset it for each tuple
21029 : : * produced, so we don't leak memory.
21030 : : */
21031 [ + - ]: 57 : oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
21032 : :
21033 [ + + ]: 327 : while (table_scan_getnextslot(scan, ForwardScanDirection, srcslot))
21034 : : {
21035 : 270 : bool found = false;
21036 : : TupleTableSlot *insertslot;
21037 : :
21038 : : /* Extract data from old tuple. */
21039 : 270 : slot_getallattrs(srcslot);
21040 : :
21041 : 270 : econtext->ecxt_scantuple = srcslot;
21042 : :
21043 : : /* Search partition for current slot srcslot. */
21044 [ + - + + : 744 : foreach(listptr, partContexts)
+ + ]
21045 : : {
21046 : 687 : pc = (SplitPartitionContext *) lfirst(listptr);
21047 : :
21048 [ + + + + ]: 1320 : if (pc->partqualstate /* skip DEFAULT partition */ &&
21049 : 633 : ExecCheck(pc->partqualstate, econtext))
21050 : : {
21051 : 213 : found = true;
21052 : 213 : break;
21053 : : }
21054 : 474 : ResetExprContext(econtext);
21055 : : }
21056 [ + + ]: 270 : if (!found)
21057 : : {
21058 : : /* Use DEFAULT partition if it exists. */
21059 [ + - ]: 57 : if (defaultPartCtx)
21060 : 57 : pc = defaultPartCtx;
21061 : : else
7 akorotkov@postgresql 21062 [ # # ]:UNC 0 : ereport(ERROR,
21063 : : (errcode(ERRCODE_CHECK_VIOLATION),
21064 : : errmsg("can not find partition for split partition row"),
21065 : : errtable(splitRel)));
21066 : : }
21067 : :
7 akorotkov@postgresql 21068 [ + + ]:GNC 270 : if (tuple_map)
21069 : : {
21070 : : /* Need to use map for copy attributes. */
21071 : 12 : insertslot = execute_attr_map_slot(tuple_map->attrMap, srcslot, pc->dstslot);
21072 : : }
21073 : : else
21074 : : {
21075 : : /* Copy attributes directly. */
21076 : 258 : insertslot = pc->dstslot;
21077 : :
21078 : 258 : ExecClearTuple(insertslot);
21079 : :
21080 : 258 : memcpy(insertslot->tts_values, srcslot->tts_values,
21081 : 258 : sizeof(Datum) * srcslot->tts_nvalid);
21082 : 258 : memcpy(insertslot->tts_isnull, srcslot->tts_isnull,
21083 : 258 : sizeof(bool) * srcslot->tts_nvalid);
21084 : :
21085 : 258 : ExecStoreVirtualTuple(insertslot);
21086 : : }
21087 : :
21088 : : /* Write the tuple out to the new relation. */
3 21089 : 270 : table_tuple_insert(pc->partRel, insertslot, mycid,
21090 : : ti_options, pc->bistate);
21091 : :
7 21092 : 270 : ResetExprContext(econtext);
21093 : :
21094 [ - + ]: 270 : CHECK_FOR_INTERRUPTS();
21095 : : }
21096 : :
21097 : 57 : MemoryContextSwitchTo(oldCxt);
21098 : :
21099 : 57 : table_endscan(scan);
21100 : 57 : UnregisterSnapshot(snapshot);
21101 : :
21102 [ + + ]: 57 : if (tuple_map)
21103 : 3 : free_conversion_map(tuple_map);
21104 : :
21105 : 57 : ExecDropSingleTupleTableSlot(srcslot);
21106 : :
21107 : 57 : FreeExecutorState(estate);
21108 : :
21109 [ + - + + : 234 : foreach(listptr, partContexts)
+ + ]
21110 : 177 : deleteSplitPartitionContext((SplitPartitionContext *) lfirst(listptr), ti_options);
21111 : :
21112 : : /* Need to close table and free buffers for DEFAULT partition. */
21113 [ + + ]: 57 : if (isOldDefaultPart)
21114 : : {
21115 : 24 : Relation defaultPartRel = defaultPartCtx->partRel;
21116 : :
21117 : 24 : deleteSplitPartitionContext(defaultPartCtx, ti_options);
21118 : : /* Keep the lock until commit. */
21119 : 24 : table_close(defaultPartRel, NoLock);
21120 : : }
21121 : 57 : }
21122 : :
21123 : : /*
21124 : : * createPartitionTable: create table for a new partition with given name
21125 : : * (newPartName) like table (modelRelName)
21126 : : *
21127 : : * Emulates command: CREATE TABLE <newPartName> (LIKE <modelRelName>
21128 : : * INCLUDING ALL EXCLUDING INDEXES EXCLUDING IDENTITY)
21129 : : */
21130 : : static void
21131 : 210 : createPartitionTable(RangeVar *newPartName, RangeVar *modelRelName,
21132 : : AlterTableUtilityContext *context)
21133 : : {
21134 : : CreateStmt *createStmt;
21135 : : TableLikeClause *tlc;
21136 : : PlannedStmt *wrapper;
21137 : :
21138 : 210 : createStmt = makeNode(CreateStmt);
21139 : 210 : createStmt->relation = newPartName;
21140 : 210 : createStmt->tableElts = NIL;
21141 : 210 : createStmt->inhRelations = NIL;
21142 : 210 : createStmt->constraints = NIL;
21143 : 210 : createStmt->options = NIL;
21144 : 210 : createStmt->oncommit = ONCOMMIT_NOOP;
21145 : 210 : createStmt->tablespacename = NULL;
21146 : 210 : createStmt->if_not_exists = false;
21147 : :
21148 : 210 : tlc = makeNode(TableLikeClause);
21149 : 210 : tlc->relation = modelRelName;
21150 : :
21151 : : /*
21152 : : * Indexes will be inherited on "attach new partitions" stage, after data
21153 : : * moving.
21154 : : */
21155 : 210 : tlc->options = CREATE_TABLE_LIKE_ALL & ~(CREATE_TABLE_LIKE_INDEXES | CREATE_TABLE_LIKE_IDENTITY);
21156 : 210 : tlc->relationOid = InvalidOid;
21157 : 210 : createStmt->tableElts = lappend(createStmt->tableElts, tlc);
21158 : :
21159 : : /* Need to make a wrapper PlannedStmt. */
21160 : 210 : wrapper = makeNode(PlannedStmt);
21161 : 210 : wrapper->commandType = CMD_UTILITY;
21162 : 210 : wrapper->canSetTag = false;
21163 : 210 : wrapper->utilityStmt = (Node *) createStmt;
21164 : 210 : wrapper->stmt_location = context->pstmt->stmt_location;
21165 : 210 : wrapper->stmt_len = context->pstmt->stmt_len;
21166 : :
21167 : 210 : ProcessUtility(wrapper,
21168 : : context->queryString,
21169 : : false,
21170 : : PROCESS_UTILITY_SUBCOMMAND,
21171 : : NULL,
21172 : : NULL,
21173 : : None_Receiver,
21174 : : NULL);
21175 : 210 : }
21176 : :
21177 : : /*
21178 : : * ALTER TABLE <name> SPLIT PARTITION <partition-name> INTO <partition-list>
21179 : : */
21180 : : static void
21181 : 60 : ATExecSplitPartition(List **wqueue, AlteredTableInfo *tab, Relation rel,
21182 : : PartitionCmd *cmd, AlterTableUtilityContext *context)
21183 : : {
21184 : : Relation splitRel;
21185 : : Oid splitRelOid;
21186 : : char relname[NAMEDATALEN];
21187 : : Oid namespaceId;
21188 : : ListCell *listptr,
21189 : : *listptr2;
21190 : 60 : bool isSameName = false;
21191 : : char tmpRelName[NAMEDATALEN];
21192 : 60 : List *newPartRels = NIL;
21193 : : ObjectAddress object;
21194 : : RangeVar *parentName;
21195 : : Oid defaultPartOid;
21196 : :
21197 : 60 : defaultPartOid = get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
21198 : :
21199 : : /*
21200 : : * We are going to detach and remove this partition: need to use exclusive
21201 : : * lock for preventing DML-queries to the partition.
21202 : : */
21203 : 60 : splitRel = table_openrv(cmd->name, AccessExclusiveLock);
21204 : :
21205 : 60 : splitRelOid = RelationGetRelid(splitRel);
21206 : :
21207 : : /* Check descriptions of new partitions. */
21208 [ + - + + : 237 : foreach(listptr, cmd->partlist)
+ + ]
21209 : : {
21210 : : Oid existing_relid;
21211 : 180 : SinglePartitionSpec *sps = (SinglePartitionSpec *) lfirst(listptr);
21212 : :
21213 : 180 : strlcpy(relname, sps->name->relname, NAMEDATALEN);
21214 : :
21215 : : /*
21216 : : * Look up the namespace in which we are supposed to create the
21217 : : * partition, check we have permission to create there, lock it
21218 : : * against concurrent drop, and mark stmt->relation as
21219 : : * RELPERSISTENCE_TEMP if a temporary namespace is selected.
21220 : : */
21221 : : namespaceId =
21222 : 180 : RangeVarGetAndCheckCreationNamespace(sps->name, NoLock, NULL);
21223 : :
21224 : : /*
21225 : : * This would fail later on anyway, if the relation already exists.
21226 : : * But by catching it here we can emit a nicer error message.
21227 : : */
21228 : 180 : existing_relid = get_relname_relid(relname, namespaceId);
21229 [ + + + - ]: 180 : if (existing_relid == splitRelOid && !isSameName)
21230 : : /* One new partition can have the same name as split partition. */
21231 : 12 : isSameName = true;
21232 [ + + ]: 168 : else if (existing_relid != InvalidOid)
21233 [ + - ]: 3 : ereport(ERROR,
21234 : : (errcode(ERRCODE_DUPLICATE_TABLE),
21235 : : errmsg("relation \"%s\" already exists", relname)));
21236 : : }
21237 : :
21238 : : /* Detach split partition. */
21239 : 57 : RemoveInheritance(splitRel, rel, false);
21240 : : /* Do the final part of detaching. */
21241 : 57 : DetachPartitionFinalize(rel, splitRel, false, defaultPartOid);
21242 : :
21243 : : /*
21244 : : * If new partition has the same name as split partition then we should
21245 : : * rename split partition for reusing name.
21246 : : */
21247 [ + + ]: 57 : if (isSameName)
21248 : : {
21249 : : /*
21250 : : * We must bump the command counter to make the split partition tuple
21251 : : * visible for renaming.
21252 : : */
21253 : 12 : CommandCounterIncrement();
21254 : : /* Rename partition. */
21255 : 12 : sprintf(tmpRelName, "split-%u-%X-tmp", RelationGetRelid(rel), MyProcPid);
21256 : 12 : RenameRelationInternal(splitRelOid, tmpRelName, false, false);
21257 : :
21258 : : /*
21259 : : * We must bump the command counter to make the split partition tuple
21260 : : * visible after renaming.
21261 : : */
21262 : 12 : CommandCounterIncrement();
21263 : : }
21264 : :
21265 : : /* Create new partitions (like split partition), without indexes. */
21266 : 57 : parentName = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
21267 : 57 : RelationGetRelationName(rel), -1);
21268 [ + - + + : 234 : foreach(listptr, cmd->partlist)
+ + ]
21269 : : {
21270 : 177 : SinglePartitionSpec *sps = (SinglePartitionSpec *) lfirst(listptr);
21271 : : Relation newPartRel;
21272 : :
21273 : 177 : createPartitionTable(sps->name, parentName, context);
21274 : :
21275 : : /* Open the new partition and acquire exclusive lock on it. */
21276 : 177 : newPartRel = table_openrv(sps->name, AccessExclusiveLock);
21277 : :
21278 : 177 : newPartRels = lappend(newPartRels, newPartRel);
21279 : : }
21280 : :
21281 : : /* Copy data from split partition to new partitions. */
21282 : 57 : moveSplitTableRows(rel, splitRel, cmd->partlist, newPartRels, defaultPartOid);
21283 : : /* Keep the lock until commit. */
21284 : 57 : table_close(splitRel, NoLock);
21285 : :
21286 : : /* Attach new partitions to partitioned table. */
21287 [ + - + + : 234 : forboth(listptr, cmd->partlist, listptr2, newPartRels)
+ - + + +
+ + - +
+ ]
21288 : : {
21289 : 177 : SinglePartitionSpec *sps = (SinglePartitionSpec *) lfirst(listptr);
21290 : 177 : Relation newPartRel = (Relation) lfirst(listptr2);
21291 : :
21292 : : /* wqueue = NULL: verification for each cloned constraint is not need. */
21293 : 177 : attachPartitionTable(NULL, rel, newPartRel, sps->bound);
21294 : : /* Keep the lock until commit. */
21295 : 177 : table_close(newPartRel, NoLock);
21296 : : }
21297 : :
21298 : : /* Drop split partition. */
21299 : 57 : object.classId = RelationRelationId;
21300 : 57 : object.objectId = splitRelOid;
21301 : 57 : object.objectSubId = 0;
21302 : : /* Probably DROP_CASCADE is not needed. */
21303 : 57 : performDeletion(&object, DROP_RESTRICT, 0);
21304 : 57 : }
21305 : :
21306 : : /*
21307 : : * moveMergedTablesRows: scan partitions to be merged (mergingPartitionsList)
21308 : : * of the partitioned table (rel) and move rows into the new partition
21309 : : * (newPartRel).
21310 : : */
21311 : : static void
21312 : 33 : moveMergedTablesRows(Relation rel, List *mergingPartitionsList,
21313 : : Relation newPartRel)
21314 : : {
21315 : : CommandId mycid;
21316 : :
21317 : : /* The FSM is empty, so don't bother using it. */
21318 : 33 : int ti_options = TABLE_INSERT_SKIP_FSM;
21319 : : ListCell *listptr;
21320 : : BulkInsertState bistate; /* state of bulk inserts for partition */
21321 : : TupleTableSlot *dstslot;
21322 : :
21323 : 33 : mycid = GetCurrentCommandId(true);
21324 : :
21325 : : /* Prepare a BulkInsertState for table_tuple_insert. */
21326 : 33 : bistate = GetBulkInsertState();
21327 : :
21328 : : /* Create necessary tuple slot. */
21329 : 33 : dstslot = MakeSingleTupleTableSlot(RelationGetDescr(newPartRel),
21330 : : table_slot_callbacks(newPartRel));
21331 : 33 : ExecStoreAllNullTuple(dstslot);
21332 : :
21333 [ + - + + : 120 : foreach(listptr, mergingPartitionsList)
+ + ]
21334 : : {
21335 : 87 : Relation mergingPartition = (Relation) lfirst(listptr);
21336 : : TupleTableSlot *srcslot;
21337 : : TupleConversionMap *tuple_map;
21338 : : TableScanDesc scan;
21339 : : Snapshot snapshot;
21340 : :
21341 : : /* Create tuple slot for new partition. */
21342 : 87 : srcslot = MakeSingleTupleTableSlot(RelationGetDescr(mergingPartition),
21343 : : table_slot_callbacks(mergingPartition));
21344 : :
21345 : : /*
21346 : : * Map computing for moving attributes of merged partition to new
21347 : : * partition.
21348 : : */
21349 : 87 : tuple_map = convert_tuples_by_name(RelationGetDescr(mergingPartition),
21350 : : RelationGetDescr(newPartRel));
21351 : :
21352 : : /* Scan through the rows. */
21353 : 87 : snapshot = RegisterSnapshot(GetLatestSnapshot());
21354 : 87 : scan = table_beginscan(mergingPartition, snapshot, 0, NULL);
21355 : :
21356 [ + + ]: 327 : while (table_scan_getnextslot(scan, ForwardScanDirection, srcslot))
21357 : : {
21358 : : TupleTableSlot *insertslot;
21359 : :
21360 : : /* Extract data from old tuple. */
21361 : 153 : slot_getallattrs(srcslot);
21362 : :
21363 [ + + ]: 153 : if (tuple_map)
21364 : : {
21365 : : /* Need to use map for copy attributes. */
21366 : 15 : insertslot = execute_attr_map_slot(tuple_map->attrMap, srcslot, dstslot);
21367 : : }
21368 : : else
21369 : : {
21370 : : /* Copy attributes directly. */
21371 : 138 : insertslot = dstslot;
21372 : :
21373 : 138 : ExecClearTuple(insertslot);
21374 : :
21375 : 138 : memcpy(insertslot->tts_values, srcslot->tts_values,
21376 : 138 : sizeof(Datum) * srcslot->tts_nvalid);
21377 : 138 : memcpy(insertslot->tts_isnull, srcslot->tts_isnull,
21378 : 138 : sizeof(bool) * srcslot->tts_nvalid);
21379 : :
21380 : 138 : ExecStoreVirtualTuple(insertslot);
21381 : : }
21382 : :
21383 : : /* Write the tuple out to the new relation. */
3 21384 : 153 : table_tuple_insert(newPartRel, insertslot, mycid,
21385 : : ti_options, bistate);
21386 : :
7 21387 [ - + ]: 153 : CHECK_FOR_INTERRUPTS();
21388 : : }
21389 : :
21390 : 87 : table_endscan(scan);
21391 : 87 : UnregisterSnapshot(snapshot);
21392 : :
21393 [ + + ]: 87 : if (tuple_map)
21394 : 9 : free_conversion_map(tuple_map);
21395 : :
21396 : 87 : ExecDropSingleTupleTableSlot(srcslot);
21397 : : }
21398 : :
21399 : 33 : ExecDropSingleTupleTableSlot(dstslot);
21400 : 33 : FreeBulkInsertState(bistate);
21401 : :
21402 : 33 : table_finish_bulk_insert(newPartRel, ti_options);
21403 : 33 : }
21404 : :
21405 : : /*
21406 : : * ALTER TABLE <name> MERGE PARTITIONS <partition-list> INTO <partition-name>
21407 : : */
21408 : : static void
21409 : 33 : ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
21410 : : PartitionCmd *cmd, AlterTableUtilityContext *context)
21411 : : {
21412 : : Relation newPartRel;
21413 : : ListCell *listptr;
21414 : 33 : List *mergingPartitionsList = NIL;
21415 : : Oid defaultPartOid;
21416 : : char tmpRelName[NAMEDATALEN];
21417 : 33 : RangeVar *mergePartName = cmd->name;
21418 : 33 : bool isSameName = false;
21419 : :
21420 : : /*
21421 : : * Lock all merged partitions, check them and create list with partitions
21422 : : * contexts.
21423 : : */
21424 [ + - + + : 120 : foreach(listptr, cmd->partlist)
+ + ]
21425 : : {
21426 : 87 : RangeVar *name = (RangeVar *) lfirst(listptr);
21427 : : Relation mergingPartition;
21428 : :
21429 : : /*
21430 : : * We are going to detach and remove this partition: need to use
21431 : : * exclusive lock for preventing DML-queries to the partition.
21432 : : */
21433 : 87 : mergingPartition = table_openrv(name, AccessExclusiveLock);
21434 : :
21435 : : /*
21436 : : * Checking that two partitions have the same name was before, in
21437 : : * function transformPartitionCmdForMerge().
21438 : : */
21439 [ + + ]: 87 : if (equal(name, cmd->name))
21440 : : /* One new partition can have the same name as merged partition. */
21441 : 3 : isSameName = true;
21442 : :
21443 : : /* Store a next merging partition into the list. */
21444 : 87 : mergingPartitionsList = lappend(mergingPartitionsList,
21445 : : mergingPartition);
21446 : : }
21447 : :
21448 : : /* Detach all merged partitions. */
21449 : : defaultPartOid =
21450 : 33 : get_default_oid_from_partdesc(RelationGetPartitionDesc(rel, true));
21451 [ + - + + : 120 : foreach(listptr, mergingPartitionsList)
+ + ]
21452 : : {
21453 : 87 : Relation mergingPartition = (Relation) lfirst(listptr);
21454 : :
21455 : : /* Remove the pg_inherits row first. */
21456 : 87 : RemoveInheritance(mergingPartition, rel, false);
21457 : : /* Do the final part of detaching. */
21458 : 87 : DetachPartitionFinalize(rel, mergingPartition, false, defaultPartOid);
21459 : : }
21460 : :
21461 : : /* Create table for new partition, use partitioned table as model. */
21462 [ + + ]: 33 : if (isSameName)
21463 : : {
21464 : : /* Create partition table with generated temparary name. */
21465 : 3 : sprintf(tmpRelName, "merge-%u-%X-tmp", RelationGetRelid(rel), MyProcPid);
21466 : 3 : mergePartName = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
21467 : : tmpRelName, -1);
21468 : : }
21469 : 33 : createPartitionTable(mergePartName,
21470 : 33 : makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
21471 : 33 : RelationGetRelationName(rel), -1),
21472 : : context);
21473 : :
21474 : : /*
21475 : : * Open the new partition and acquire exclusive lock on it. This will
21476 : : * stop all the operations with partitioned table. This might seem
21477 : : * excessive, but this is the way we make sure nobody is planning queries
21478 : : * involving merging partitions.
21479 : : */
21480 : 33 : newPartRel = table_openrv(mergePartName, AccessExclusiveLock);
21481 : :
21482 : : /* Copy data from merged partitions to new partition. */
21483 : 33 : moveMergedTablesRows(rel, mergingPartitionsList, newPartRel);
21484 : :
21485 : : /*
21486 : : * Attach a new partition to the partitioned table. wqueue = NULL:
21487 : : * verification for each cloned constraint is not need.
21488 : : */
21489 : 33 : attachPartitionTable(NULL, rel, newPartRel, cmd->bound);
21490 : :
21491 : : /* Unlock and drop merged partitions. */
21492 [ + - + + : 120 : foreach(listptr, mergingPartitionsList)
+ + ]
21493 : : {
21494 : : ObjectAddress object;
21495 : 87 : Relation mergingPartition = (Relation) lfirst(listptr);
21496 : :
21497 : : /* Get relation id before table_close() call. */
21498 : 87 : object.objectId = RelationGetRelid(mergingPartition);
21499 : 87 : object.classId = RelationRelationId;
21500 : 87 : object.objectSubId = 0;
21501 : :
21502 : : /* Keep the lock until commit. */
21503 : 87 : table_close(mergingPartition, NoLock);
21504 : :
21505 : 87 : performDeletion(&object, DROP_RESTRICT, 0);
21506 : : }
21507 : 33 : list_free(mergingPartitionsList);
21508 : :
21509 : : /* Rename new partition if it is needed. */
21510 [ + + ]: 33 : if (isSameName)
21511 : : {
21512 : : /*
21513 : : * We must bump the command counter to make the new partition tuple
21514 : : * visible for rename.
21515 : : */
21516 : 3 : CommandCounterIncrement();
21517 : : /* Rename partition. */
21518 : 3 : RenameRelationInternal(RelationGetRelid(newPartRel),
21519 : 3 : cmd->name->relname, false, false);
21520 : : }
21521 : : /* Keep the lock until commit. */
21522 : 33 : table_close(newPartRel, NoLock);
21523 : 33 : }
|