Skip to content

Commit a35c5b7

Browse files
committed
Fix handling of inherited check constraints in ALTER COLUMN TYPE (again).
The previous way of reconstructing check constraints was to do a separate "ALTER TABLE ONLY tab ADD CONSTRAINT" for each table in an inheritance hierarchy. However, that way has no hope of reconstructing the check constraints' own inheritance properties correctly, as pointed out in bug #13779 from Jan Dirk Zijlstra. What we should do instead is to do a regular "ALTER TABLE", allowing recursion, at the topmost table that has a particular constraint, and then suppress the work queue entries for inherited instances of the constraint. Annoyingly, we'd tried to fix this behavior before, in commit 5ed6546, but we failed to notice that it wasn't reconstructing the pg_constraint field values correctly. As long as I'm touching pg_get_constraintdef_worker anyway, tweak it to always schema-qualify the target table name; this seems like useful backup to the protections installed by commit 5f17304. In HEAD/9.5, get rid of get_constraint_relation_oids, which is now unused. (I could alternatively have modified it to also return conislocal, but that seemed like a pretty single-purpose API, so let's not pretend it has some other use.) It's unused in the back branches as well, but I left it in place just in case some third-party code has decided to use it. In HEAD/9.5, also rename pg_get_constraintdef_string to pg_get_constraintdef_command, as the previous name did nothing to explain what that entry point did differently from others (and its comment was equally useless). Again, that change doesn't seem like material for back-patching. I did a bit of re-pgindenting in tablecmds.c in HEAD/9.5, as well. Otherwise, back-patch to all supported branches.
1 parent 8ee1a77 commit a35c5b7

File tree

7 files changed

+239
-71
lines changed

7 files changed

+239
-71
lines changed

src/backend/catalog/pg_constraint.c

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -753,25 +753,6 @@ AlterConstraintNamespaces(Oid ownerId, Oid oldNspId,
753753
heap_close(conRel, RowExclusiveLock);
754754
}
755755

756-
/*
757-
* get_constraint_relation_oids
758-
* Find the IDs of the relations to which a constraint refers.
759-
*/
760-
void
761-
get_constraint_relation_oids(Oid constraint_oid, Oid *conrelid, Oid *confrelid)
762-
{
763-
HeapTuple tup;
764-
Form_pg_constraint con;
765-
766-
tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraint_oid));
767-
if (!HeapTupleIsValid(tup)) /* should not happen */
768-
elog(ERROR, "cache lookup failed for constraint %u", constraint_oid);
769-
con = (Form_pg_constraint) GETSTRUCT(tup);
770-
*conrelid = con->conrelid;
771-
*confrelid = con->confrelid;
772-
ReleaseSysCache(tup);
773-
}
774-
775756
/*
776757
* get_relation_constraint_oid
777758
* Find a constraint on the specified relation with the specified name.

src/backend/commands/tablecmds.c

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3519,7 +3519,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
35193519
* constraint */
35203520
address =
35213521
ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
3522-
false, true, lockmode);
3522+
true, true, lockmode);
35233523
break;
35243524
case AT_ReAddComment: /* Re-add existing comment */
35253525
address = CommentObject((CommentStmt *) cmd->def);
@@ -6072,13 +6072,6 @@ ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
60726072
* AddRelationNewConstraints would normally assign different names to the
60736073
* child constraints. To fix that, we must capture the name assigned at
60746074
* the parent table and pass that down.
6075-
*
6076-
* When re-adding a previously existing constraint (during ALTER COLUMN TYPE),
6077-
* we don't need to recurse here, because recursion will be carried out at a
6078-
* higher level; the constraint name issue doesn't apply because the names
6079-
* have already been assigned and are just being re-used. We need a separate
6080-
* "is_readd" flag for that; just setting recurse=false would result in an
6081-
* error if there are child tables.
60826075
*/
60836076
static ObjectAddress
60846077
ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
@@ -6107,7 +6100,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
61076100
*/
61086101
newcons = AddRelationNewConstraints(rel, NIL,
61096102
list_make1(copyObject(constr)),
6110-
recursing, /* allow_merge */
6103+
recursing | is_readd, /* allow_merge */
61116104
!recursing, /* is_local */
61126105
is_readd); /* is_internal */
61136106

@@ -6156,10 +6149,8 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
61566149

61576150
/*
61586151
* If adding a NO INHERIT constraint, no need to find our children.
6159-
* Likewise, in a re-add operation, we don't need to recurse (that will be
6160-
* handled at higher levels).
61616152
*/
6162-
if (constr->is_no_inherit || is_readd)
6153+
if (constr->is_no_inherit)
61636154
return address;
61646155

61656156
/*
@@ -8191,7 +8182,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
81918182
if (!list_member_oid(tab->changedConstraintOids,
81928183
foundObject.objectId))
81938184
{
8194-
char *defstring = pg_get_constraintdef_string(foundObject.objectId);
8185+
char *defstring = pg_get_constraintdef_command(foundObject.objectId);
81958186

81968187
/*
81978188
* Put NORMAL dependencies at the front of the list and
@@ -8566,10 +8557,30 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
85668557
def_item, tab->changedConstraintDefs)
85678558
{
85688559
Oid oldId = lfirst_oid(oid_item);
8560+
HeapTuple tup;
8561+
Form_pg_constraint con;
85698562
Oid relid;
85708563
Oid confrelid;
8564+
bool conislocal;
8565+
8566+
tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(oldId));
8567+
if (!HeapTupleIsValid(tup)) /* should not happen */
8568+
elog(ERROR, "cache lookup failed for constraint %u", oldId);
8569+
con = (Form_pg_constraint) GETSTRUCT(tup);
8570+
relid = con->conrelid;
8571+
confrelid = con->confrelid;
8572+
conislocal = con->conislocal;
8573+
ReleaseSysCache(tup);
8574+
8575+
/*
8576+
* If the constraint is inherited (only), we don't want to inject a
8577+
* new definition here; it'll get recreated when ATAddCheckConstraint
8578+
* recurses from adding the parent table's constraint. But we had to
8579+
* carry the info this far so that we can drop the constraint below.
8580+
*/
8581+
if (!conislocal)
8582+
continue;
85718583

8572-
get_constraint_relation_oids(oldId, &relid, &confrelid);
85738584
ATPostAlterTypeParse(oldId, relid, confrelid,
85748585
(char *) lfirst(def_item),
85758586
wqueue, lockmode, tab->rewrite);
@@ -8743,7 +8754,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
87438754
rel, con->conname);
87448755
}
87458756
else
8746-
elog(ERROR, "unexpected statement type: %d",
8757+
elog(ERROR, "unexpected statement subtype: %d",
87478758
(int) cmd->subtype);
87488759
}
87498760
}
@@ -11254,19 +11265,19 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
1125411265
if (foreignrel->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT)
1125511266
ereport(ERROR,
1125611267
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
11257-
errmsg("could not change table \"%s\" to logged because it references unlogged table \"%s\"",
11258-
RelationGetRelationName(rel),
11259-
RelationGetRelationName(foreignrel)),
11268+
errmsg("could not change table \"%s\" to logged because it references unlogged table \"%s\"",
11269+
RelationGetRelationName(rel),
11270+
RelationGetRelationName(foreignrel)),
1126011271
errtableconstraint(rel, NameStr(con->conname))));
1126111272
}
1126211273
else
1126311274
{
1126411275
if (foreignrel->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT)
1126511276
ereport(ERROR,
1126611277
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
11267-
errmsg("could not change table \"%s\" to unlogged because it references logged table \"%s\"",
11268-
RelationGetRelationName(rel),
11269-
RelationGetRelationName(foreignrel)),
11278+
errmsg("could not change table \"%s\" to unlogged because it references logged table \"%s\"",
11279+
RelationGetRelationName(rel),
11280+
RelationGetRelationName(foreignrel)),
1127011281
errtableconstraint(rel, NameStr(con->conname))));
1127111282
}
1127211283

src/backend/utils/adt/ruleutils.c

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,7 @@ static Node *processIndirection(Node *node, deparse_context *context,
433433
static void printSubscripts(ArrayRef *aref, deparse_context *context);
434434
static char *get_relation_name(Oid relid);
435435
static char *generate_relation_name(Oid relid, List *namespaces);
436+
static char *generate_qualified_relation_name(Oid relid);
436437
static char *generate_function_name(Oid funcid, int nargs,
437438
List *argnames, Oid *argtypes,
438439
bool has_variadic, bool *use_variadic_p,
@@ -1314,9 +1315,11 @@ pg_get_constraintdef_ext(PG_FUNCTION_ARGS)
13141315
prettyFlags)));
13151316
}
13161317

1317-
/* Internal version that returns a palloc'd C string; no pretty-printing */
1318+
/*
1319+
* Internal version that returns a full ALTER TABLE ... ADD CONSTRAINT command
1320+
*/
13181321
char *
1319-
pg_get_constraintdef_string(Oid constraintId)
1322+
pg_get_constraintdef_command(Oid constraintId)
13201323
{
13211324
return pg_get_constraintdef_worker(constraintId, true, 0);
13221325
}
@@ -1363,10 +1366,16 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
13631366

13641367
initStringInfo(&buf);
13651368

1366-
if (fullCommand && OidIsValid(conForm->conrelid))
1369+
if (fullCommand)
13671370
{
1368-
appendStringInfo(&buf, "ALTER TABLE ONLY %s ADD CONSTRAINT %s ",
1369-
generate_relation_name(conForm->conrelid, NIL),
1371+
/*
1372+
* Currently, callers want ALTER TABLE (without ONLY) for CHECK
1373+
* constraints, and other types of constraints don't inherit anyway so
1374+
* it doesn't matter whether we say ONLY or not. Someday we might
1375+
* need to let callers specify whether to put ONLY in the command.
1376+
*/
1377+
appendStringInfo(&buf, "ALTER TABLE %s ADD CONSTRAINT %s ",
1378+
generate_qualified_relation_name(conForm->conrelid),
13701379
quote_identifier(NameStr(conForm->conname)));
13711380
}
13721381

@@ -1890,28 +1899,9 @@ pg_get_serial_sequence(PG_FUNCTION_ARGS)
18901899

18911900
if (OidIsValid(sequenceId))
18921901
{
1893-
HeapTuple classtup;
1894-
Form_pg_class classtuple;
1895-
char *nspname;
18961902
char *result;
18971903

1898-
/* Get the sequence's pg_class entry */
1899-
classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(sequenceId));
1900-
if (!HeapTupleIsValid(classtup))
1901-
elog(ERROR, "cache lookup failed for relation %u", sequenceId);
1902-
classtuple = (Form_pg_class) GETSTRUCT(classtup);
1903-
1904-
/* Get the namespace */
1905-
nspname = get_namespace_name(classtuple->relnamespace);
1906-
if (!nspname)
1907-
elog(ERROR, "cache lookup failed for namespace %u",
1908-
classtuple->relnamespace);
1909-
1910-
/* And construct the result string */
1911-
result = quote_qualified_identifier(nspname,
1912-
NameStr(classtuple->relname));
1913-
1914-
ReleaseSysCache(classtup);
1904+
result = generate_qualified_relation_name(sequenceId);
19151905

19161906
PG_RETURN_TEXT_P(string_to_text(result));
19171907
}
@@ -9577,6 +9567,39 @@ generate_relation_name(Oid relid, List *namespaces)
95779567
return result;
95789568
}
95799569

9570+
/*
9571+
* generate_qualified_relation_name
9572+
* Compute the name to display for a relation specified by OID
9573+
*
9574+
* As above, but unconditionally schema-qualify the name.
9575+
*/
9576+
static char *
9577+
generate_qualified_relation_name(Oid relid)
9578+
{
9579+
HeapTuple tp;
9580+
Form_pg_class reltup;
9581+
char *relname;
9582+
char *nspname;
9583+
char *result;
9584+
9585+
tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
9586+
if (!HeapTupleIsValid(tp))
9587+
elog(ERROR, "cache lookup failed for relation %u", relid);
9588+
reltup = (Form_pg_class) GETSTRUCT(tp);
9589+
relname = NameStr(reltup->relname);
9590+
9591+
nspname = get_namespace_name(reltup->relnamespace);
9592+
if (!nspname)
9593+
elog(ERROR, "cache lookup failed for namespace %u",
9594+
reltup->relnamespace);
9595+
9596+
result = quote_qualified_identifier(nspname, relname);
9597+
9598+
ReleaseSysCache(tp);
9599+
9600+
return result;
9601+
}
9602+
95809603
/*
95819604
* generate_function_name
95829605
* Compute the name to display for a function specified by OID,

src/include/catalog/pg_constraint.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,6 @@ extern char *ChooseConstraintName(const char *name1, const char *name2,
247247

248248
extern void AlterConstraintNamespaces(Oid ownerId, Oid oldNspId,
249249
Oid newNspId, bool isType, ObjectAddresses *objsMoved);
250-
extern void get_constraint_relation_oids(Oid constraint_oid, Oid *conrelid, Oid *confrelid);
251250
extern Oid get_relation_constraint_oid(Oid relid, const char *conname, bool missing_ok);
252251
extern Oid get_domain_constraint_oid(Oid typid, const char *conname, bool missing_ok);
253252

src/include/utils/ruleutils.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
extern char *pg_get_indexdef_string(Oid indexrelid);
2222
extern char *pg_get_indexdef_columns(Oid indexrelid, bool pretty);
2323

24-
extern char *pg_get_constraintdef_string(Oid constraintId);
24+
extern char *pg_get_constraintdef_command(Oid constraintId);
2525
extern char *deparse_expression(Node *expr, List *dpcontext,
2626
bool forceprefix, bool showimplicit);
2727
extern List *deparse_context_for(const char *aliasname, Oid relid);

0 commit comments

Comments
 (0)