mirror of
https://github.com/postgres/postgres.git
synced 2025-11-24 00:23:06 +03:00
Better handle indirect constraint drops
It is possible for certain cases to remove not-null constraints without maintaining the attnotnull in its correct state; for example if you drop a column that's part of the primary key, and the other columns of the PK don't have not-null constraints, then we should reset the attnotnull flags for those other columns; up to this commit, we didn't. Handle those cases better by doing the attnotnull reset in RemoveConstraintById() instead of in dropconstraint_internal(). However, there are some cases where we must not do so. For example if those other columns are in replica identity indexes or are generated identity columns, we must keep attnotnull set, even though it results in the catalog inconsistency that no not-null constraint supports that. Because the attnotnull reset now happens in more places than before, for instance when a column of the primary key changes type, we need an additional trick to reinstate it as necessary. Introduce a new alter-table pass that does this, which needs simply reschedule some AT_SetAttNotNull subcommands that were already being generated and ignored. Because of the exceptions in which attnotnull is not reset noted above, we also include a pg_dump hack to include a not-null constraint when the attnotnull flag is set even if no pg_constraint row exists. This part is undesirable but necessary, because failing to handle the case can result in unrestorable dumps. Reported-by: Tender Wang <tndrwang@gmail.com> Co-authored-by: Tender Wang <tndrwang@gmail.com> Reviewed-by: jian he <jian.universality@gmail.com> Discussion: https://postgr.es/m/CAHewXN=hMbNa3d43NOR=OCgdgpTt18S-1fmueCoEGesyeK4bqw@mail.gmail.com
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
#include "access/htup_details.h"
|
||||
#include "access/sysattr.h"
|
||||
#include "access/table.h"
|
||||
#include "access/xact.h"
|
||||
#include "catalog/catalog.h"
|
||||
#include "catalog/dependency.h"
|
||||
#include "catalog/heap.h"
|
||||
@@ -933,6 +934,8 @@ RemoveConstraintById(Oid conId)
|
||||
Relation conDesc;
|
||||
HeapTuple tup;
|
||||
Form_pg_constraint con;
|
||||
bool dropping_pk = false;
|
||||
List *unconstrained_cols = NIL;
|
||||
|
||||
conDesc = table_open(ConstraintRelationId, RowExclusiveLock);
|
||||
|
||||
@@ -957,7 +960,9 @@ RemoveConstraintById(Oid conId)
|
||||
/*
|
||||
* We need to update the relchecks count if it is a check constraint
|
||||
* being dropped. This update will force backends to rebuild relcache
|
||||
* entries when we commit.
|
||||
* entries when we commit. For not-null and primary key constraints,
|
||||
* obtain the list of columns affected, so that we can reset their
|
||||
* attnotnull flags below.
|
||||
*/
|
||||
if (con->contype == CONSTRAINT_CHECK)
|
||||
{
|
||||
@@ -984,6 +989,36 @@ RemoveConstraintById(Oid conId)
|
||||
|
||||
table_close(pgrel, RowExclusiveLock);
|
||||
}
|
||||
else if (con->contype == CONSTRAINT_NOTNULL)
|
||||
{
|
||||
unconstrained_cols = list_make1_int(extractNotNullColumn(tup));
|
||||
}
|
||||
else if (con->contype == CONSTRAINT_PRIMARY)
|
||||
{
|
||||
Datum adatum;
|
||||
ArrayType *arr;
|
||||
int numkeys;
|
||||
bool isNull;
|
||||
int16 *attnums;
|
||||
|
||||
dropping_pk = true;
|
||||
|
||||
adatum = heap_getattr(tup, Anum_pg_constraint_conkey,
|
||||
RelationGetDescr(conDesc), &isNull);
|
||||
if (isNull)
|
||||
elog(ERROR, "null conkey for constraint %u", con->oid);
|
||||
arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
|
||||
numkeys = ARR_DIMS(arr)[0];
|
||||
if (ARR_NDIM(arr) != 1 ||
|
||||
numkeys < 0 ||
|
||||
ARR_HASNULL(arr) ||
|
||||
ARR_ELEMTYPE(arr) != INT2OID)
|
||||
elog(ERROR, "conkey is not a 1-D smallint array");
|
||||
attnums = (int16 *) ARR_DATA_PTR(arr);
|
||||
|
||||
for (int i = 0; i < numkeys; i++)
|
||||
unconstrained_cols = lappend_int(unconstrained_cols, attnums[i]);
|
||||
}
|
||||
|
||||
/* Keep lock on constraint's rel until end of xact */
|
||||
table_close(rel, NoLock);
|
||||
@@ -1003,6 +1038,86 @@ RemoveConstraintById(Oid conId)
|
||||
/* Fry the constraint itself */
|
||||
CatalogTupleDelete(conDesc, &tup->t_self);
|
||||
|
||||
/*
|
||||
* If this was a NOT NULL or the primary key, the constrained columns must
|
||||
* have had pg_attribute.attnotnull set. See if we need to reset it, and
|
||||
* do so.
|
||||
*/
|
||||
if (unconstrained_cols != NIL)
|
||||
{
|
||||
Relation tablerel;
|
||||
Relation attrel;
|
||||
Bitmapset *pkcols;
|
||||
ListCell *lc;
|
||||
|
||||
/* Make the above deletion visible */
|
||||
CommandCounterIncrement();
|
||||
|
||||
tablerel = table_open(con->conrelid, NoLock); /* already have lock */
|
||||
attrel = table_open(AttributeRelationId, RowExclusiveLock);
|
||||
|
||||
/*
|
||||
* We want to test columns for their presence in the primary key, but
|
||||
* only if we're not dropping it.
|
||||
*/
|
||||
pkcols = dropping_pk ? NULL :
|
||||
RelationGetIndexAttrBitmap(tablerel,
|
||||
INDEX_ATTR_BITMAP_PRIMARY_KEY);
|
||||
|
||||
foreach(lc, unconstrained_cols)
|
||||
{
|
||||
AttrNumber attnum = lfirst_int(lc);
|
||||
HeapTuple atttup;
|
||||
HeapTuple contup;
|
||||
Bitmapset *ircols;
|
||||
Form_pg_attribute attForm;
|
||||
|
||||
/*
|
||||
* Obtain pg_attribute tuple and verify conditions on it. We use
|
||||
* a copy we can scribble on.
|
||||
*/
|
||||
atttup = SearchSysCacheCopyAttNum(con->conrelid, attnum);
|
||||
if (!HeapTupleIsValid(atttup))
|
||||
elog(ERROR, "cache lookup failed for attribute %d of relation %u",
|
||||
attnum, con->conrelid);
|
||||
attForm = (Form_pg_attribute) GETSTRUCT(atttup);
|
||||
|
||||
/*
|
||||
* Since the above deletion has been made visible, we can now
|
||||
* search for any remaining constraints setting this column as
|
||||
* not-nullable; if we find any, no need to reset attnotnull.
|
||||
*/
|
||||
if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber,
|
||||
pkcols))
|
||||
continue;
|
||||
contup = findNotNullConstraintAttnum(con->conrelid, attnum);
|
||||
if (contup)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Also no reset if the column is in the replica identity or it's
|
||||
* a generated column
|
||||
*/
|
||||
if (attForm->attidentity != '\0')
|
||||
continue;
|
||||
ircols = RelationGetIndexAttrBitmap(tablerel,
|
||||
INDEX_ATTR_BITMAP_IDENTITY_KEY);
|
||||
if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber,
|
||||
ircols))
|
||||
continue;
|
||||
|
||||
/* Reset attnotnull */
|
||||
if (attForm->attnotnull)
|
||||
{
|
||||
attForm->attnotnull = false;
|
||||
CatalogTupleUpdate(attrel, &atttup->t_self, atttup);
|
||||
}
|
||||
}
|
||||
|
||||
table_close(attrel, RowExclusiveLock);
|
||||
table_close(tablerel, NoLock);
|
||||
}
|
||||
|
||||
/* Clean up */
|
||||
ReleaseSysCache(tup);
|
||||
table_close(conDesc, RowExclusiveLock);
|
||||
|
||||
Reference in New Issue
Block a user