mirror of
https://github.com/postgres/postgres.git
synced 2025-11-24 00:23:06 +03:00
Revert structural changes to not-null constraints
There are some problems with the new way to handle these constraints
that were detected at the last minute, and require fixes that appear too
invasive to be doing this late in the cycle. Revert this (again) for
now, we'll try again with these problems fixed.
The following commits are reverted:
b0e96f3119 Catalog not-null constraints
9b581c5341 Disallow changing NO INHERIT status of a not-null constraint
d0ec2ddbe0 Fix not-null constraint test
ac22a9545c Move privilege check to the right place
b0f7dd915b Check stack depth in new recursive functions
3af7217942 Update information_schema definition for not-null constraints
c3709100be Fix propagating attnotnull in multiple inheritance
d9f686a72e Fix restore of not-null constraints with inheritance
d72d32f52d Don't try to assign smart names to constraints
0cd711271d Better handle indirect constraint drops
13daa33fa5 Disallow NO INHERIT not-null constraints on partitioned tables
d45597f72f Disallow direct change of NO INHERIT of not-null constraints
21ac38f498 Fix inconsistencies in error messages
Discussion: https://postgr.es/m/202405110940.joxlqcx4dogd@alvherre.pgsql
This commit is contained in:
@@ -19,10 +19,8 @@
|
||||
#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"
|
||||
#include "catalog/indexing.h"
|
||||
#include "catalog/objectaccess.h"
|
||||
#include "catalog/pg_constraint.h"
|
||||
@@ -565,72 +563,6 @@ ChooseConstraintName(const char *name1, const char *name2,
|
||||
return conname;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find and return a copy of the pg_constraint tuple that implements a
|
||||
* validated not-null constraint for the given column of the given relation.
|
||||
*
|
||||
* XXX This would be easier if we had pg_attribute.notnullconstr with the OID
|
||||
* of the constraint that implements the not-null constraint for that column.
|
||||
* I'm not sure it's worth the catalog bloat and de-normalization, however.
|
||||
*/
|
||||
HeapTuple
|
||||
findNotNullConstraintAttnum(Oid relid, AttrNumber attnum)
|
||||
{
|
||||
Relation pg_constraint;
|
||||
HeapTuple conTup,
|
||||
retval = NULL;
|
||||
SysScanDesc scan;
|
||||
ScanKeyData key;
|
||||
|
||||
pg_constraint = table_open(ConstraintRelationId, AccessShareLock);
|
||||
ScanKeyInit(&key,
|
||||
Anum_pg_constraint_conrelid,
|
||||
BTEqualStrategyNumber, F_OIDEQ,
|
||||
ObjectIdGetDatum(relid));
|
||||
scan = systable_beginscan(pg_constraint, ConstraintRelidTypidNameIndexId,
|
||||
true, NULL, 1, &key);
|
||||
|
||||
while (HeapTupleIsValid(conTup = systable_getnext(scan)))
|
||||
{
|
||||
Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(conTup);
|
||||
AttrNumber conkey;
|
||||
|
||||
/*
|
||||
* We're looking for a NOTNULL constraint that's marked validated,
|
||||
* with the column we're looking for as the sole element in conkey.
|
||||
*/
|
||||
if (con->contype != CONSTRAINT_NOTNULL)
|
||||
continue;
|
||||
if (!con->convalidated)
|
||||
continue;
|
||||
|
||||
conkey = extractNotNullColumn(conTup);
|
||||
if (conkey != attnum)
|
||||
continue;
|
||||
|
||||
/* Found it */
|
||||
retval = heap_copytuple(conTup);
|
||||
break;
|
||||
}
|
||||
|
||||
systable_endscan(scan);
|
||||
table_close(pg_constraint, AccessShareLock);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find and return the pg_constraint tuple that implements a validated
|
||||
* not-null constraint for the given column of the given relation.
|
||||
*/
|
||||
HeapTuple
|
||||
findNotNullConstraint(Oid relid, const char *colname)
|
||||
{
|
||||
AttrNumber attnum = get_attnum(relid, colname);
|
||||
|
||||
return findNotNullConstraintAttnum(relid, attnum);
|
||||
}
|
||||
|
||||
/*
|
||||
* Find and return the pg_constraint tuple that implements a validated
|
||||
* not-null constraint for the given domain.
|
||||
@@ -675,263 +607,6 @@ findDomainNotNullConstraint(Oid typid)
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a pg_constraint tuple for a not-null constraint, return the column
|
||||
* number it is for.
|
||||
*/
|
||||
AttrNumber
|
||||
extractNotNullColumn(HeapTuple constrTup)
|
||||
{
|
||||
AttrNumber colnum;
|
||||
Datum adatum;
|
||||
ArrayType *arr;
|
||||
|
||||
/* only tuples for not-null constraints should be given */
|
||||
Assert(((Form_pg_constraint) GETSTRUCT(constrTup))->contype == CONSTRAINT_NOTNULL);
|
||||
|
||||
adatum = SysCacheGetAttrNotNull(CONSTROID, constrTup,
|
||||
Anum_pg_constraint_conkey);
|
||||
arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
|
||||
if (ARR_NDIM(arr) != 1 ||
|
||||
ARR_HASNULL(arr) ||
|
||||
ARR_ELEMTYPE(arr) != INT2OID ||
|
||||
ARR_DIMS(arr)[0] != 1)
|
||||
elog(ERROR, "conkey is not a 1-D smallint array");
|
||||
|
||||
memcpy(&colnum, ARR_DATA_PTR(arr), sizeof(AttrNumber));
|
||||
|
||||
if ((Pointer) arr != DatumGetPointer(adatum))
|
||||
pfree(arr); /* free de-toasted copy, if any */
|
||||
|
||||
return colnum;
|
||||
}
|
||||
|
||||
/*
|
||||
* AdjustNotNullInheritance1
|
||||
* Adjust inheritance count for a single not-null constraint
|
||||
*
|
||||
* If no not-null constraint is found for the column, return 0.
|
||||
* Caller can create one.
|
||||
* If the constraint does exist and it's inheritable, adjust its
|
||||
* inheritance count (and possibly islocal status) and return 1.
|
||||
* No further action needs to be taken.
|
||||
* If the constraint exists but is marked NO INHERIT, adjust it as above
|
||||
* and reset connoinherit to false, and return -1. Caller is
|
||||
* responsible for adding the same constraint to the children, if any.
|
||||
*/
|
||||
int
|
||||
AdjustNotNullInheritance1(Oid relid, AttrNumber attnum, int count,
|
||||
bool is_no_inherit, bool allow_noinherit_change)
|
||||
{
|
||||
HeapTuple tup;
|
||||
|
||||
Assert(count >= 0);
|
||||
|
||||
tup = findNotNullConstraintAttnum(relid, attnum);
|
||||
if (HeapTupleIsValid(tup))
|
||||
{
|
||||
Relation pg_constraint;
|
||||
Form_pg_constraint conform;
|
||||
int retval = 1;
|
||||
|
||||
pg_constraint = table_open(ConstraintRelationId, RowExclusiveLock);
|
||||
conform = (Form_pg_constraint) GETSTRUCT(tup);
|
||||
|
||||
/*
|
||||
* If we're asked for a NO INHERIT constraint and this relation
|
||||
* already has an inheritable one, throw an error.
|
||||
*/
|
||||
if (is_no_inherit && !conform->connoinherit)
|
||||
ereport(ERROR,
|
||||
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
||||
errmsg("cannot change NO INHERIT status of NOT NULL constraint \"%s\" on relation \"%s\"",
|
||||
NameStr(conform->conname), get_rel_name(relid)));
|
||||
|
||||
/*
|
||||
* If the constraint already exists in this relation but it's marked
|
||||
* NO INHERIT, we can just remove that flag (provided caller allows
|
||||
* such a change), and instruct caller to recurse to add the
|
||||
* constraint to children.
|
||||
*/
|
||||
if (!is_no_inherit && conform->connoinherit)
|
||||
{
|
||||
if (!allow_noinherit_change)
|
||||
ereport(ERROR,
|
||||
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
||||
errmsg("cannot change NO INHERIT status of NOT NULL constraint \"%s\" on relation \"%s\"",
|
||||
NameStr(conform->conname), get_rel_name(relid)));
|
||||
|
||||
conform->connoinherit = false;
|
||||
retval = -1; /* caller must add constraint on child rels */
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
conform->coninhcount += count;
|
||||
|
||||
/* sanity check */
|
||||
if (conform->coninhcount < 0)
|
||||
elog(ERROR, "invalid inhcount %d for constraint \"%s\" on relation \"%s\"",
|
||||
conform->coninhcount, NameStr(conform->conname),
|
||||
get_rel_name(relid));
|
||||
|
||||
/*
|
||||
* If the constraint is no longer inherited, mark it local. It's
|
||||
* arguable that we should drop it instead, but it's hard to see that
|
||||
* being better. The user can drop it manually later.
|
||||
*/
|
||||
if (conform->coninhcount == 0)
|
||||
conform->conislocal = true;
|
||||
|
||||
CatalogTupleUpdate(pg_constraint, &tup->t_self, tup);
|
||||
|
||||
table_close(pg_constraint, RowExclusiveLock);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* AdjustNotNullInheritance
|
||||
* Adjust not-null constraints' inhcount/islocal for
|
||||
* ALTER TABLE [NO] INHERITS
|
||||
*
|
||||
* Mark the NOT NULL constraints for the given relation columns as
|
||||
* inherited, so that they can't be dropped.
|
||||
*
|
||||
* Caller must have checked beforehand that attnotnull was set for all
|
||||
* columns. However, some of those could be set because of a primary
|
||||
* key, so throw a proper user-visible error if one is not found.
|
||||
*/
|
||||
void
|
||||
AdjustNotNullInheritance(Oid relid, Bitmapset *columns, int count)
|
||||
{
|
||||
Relation pg_constraint;
|
||||
int attnum;
|
||||
|
||||
pg_constraint = table_open(ConstraintRelationId, RowExclusiveLock);
|
||||
|
||||
/*
|
||||
* Scan the set of columns and bump inhcount for each.
|
||||
*/
|
||||
attnum = -1;
|
||||
while ((attnum = bms_next_member(columns, attnum)) >= 0)
|
||||
{
|
||||
HeapTuple tup;
|
||||
Form_pg_constraint conform;
|
||||
|
||||
tup = findNotNullConstraintAttnum(relid, attnum);
|
||||
if (!HeapTupleIsValid(tup))
|
||||
ereport(ERROR,
|
||||
errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("column \"%s\" in child table must be marked NOT NULL",
|
||||
get_attname(relid, attnum,
|
||||
false)));
|
||||
|
||||
conform = (Form_pg_constraint) GETSTRUCT(tup);
|
||||
conform->coninhcount += count;
|
||||
if (conform->coninhcount < 0)
|
||||
elog(ERROR, "invalid inhcount %d for constraint \"%s\" on relation \"%s\"",
|
||||
conform->coninhcount, NameStr(conform->conname),
|
||||
get_rel_name(relid));
|
||||
|
||||
/*
|
||||
* If the constraints are no longer inherited, mark them local. It's
|
||||
* arguable that we should drop them instead, but it's hard to see
|
||||
* that being better. The user can drop it manually later.
|
||||
*/
|
||||
if (conform->coninhcount == 0)
|
||||
conform->conislocal = true;
|
||||
|
||||
CatalogTupleUpdate(pg_constraint, &tup->t_self, tup);
|
||||
}
|
||||
|
||||
table_close(pg_constraint, RowExclusiveLock);
|
||||
}
|
||||
|
||||
/*
|
||||
* RelationGetNotNullConstraints
|
||||
* Return the list of not-null constraints for the given rel
|
||||
*
|
||||
* Caller can request cooked constraints, or raw.
|
||||
*
|
||||
* This is seldom needed, so we just scan pg_constraint each time.
|
||||
*
|
||||
* XXX This is only used to create derived tables, so NO INHERIT constraints
|
||||
* are always skipped.
|
||||
*/
|
||||
List *
|
||||
RelationGetNotNullConstraints(Oid relid, bool cooked)
|
||||
{
|
||||
List *notnulls = NIL;
|
||||
Relation constrRel;
|
||||
HeapTuple htup;
|
||||
SysScanDesc conscan;
|
||||
ScanKeyData skey;
|
||||
|
||||
constrRel = table_open(ConstraintRelationId, AccessShareLock);
|
||||
ScanKeyInit(&skey,
|
||||
Anum_pg_constraint_conrelid,
|
||||
BTEqualStrategyNumber, F_OIDEQ,
|
||||
ObjectIdGetDatum(relid));
|
||||
conscan = systable_beginscan(constrRel, ConstraintRelidTypidNameIndexId, true,
|
||||
NULL, 1, &skey);
|
||||
|
||||
while (HeapTupleIsValid(htup = systable_getnext(conscan)))
|
||||
{
|
||||
Form_pg_constraint conForm = (Form_pg_constraint) GETSTRUCT(htup);
|
||||
AttrNumber colnum;
|
||||
|
||||
if (conForm->contype != CONSTRAINT_NOTNULL)
|
||||
continue;
|
||||
if (conForm->connoinherit)
|
||||
continue;
|
||||
|
||||
colnum = extractNotNullColumn(htup);
|
||||
|
||||
if (cooked)
|
||||
{
|
||||
CookedConstraint *cooked;
|
||||
|
||||
cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
|
||||
|
||||
cooked->contype = CONSTR_NOTNULL;
|
||||
cooked->name = pstrdup(NameStr(conForm->conname));
|
||||
cooked->attnum = colnum;
|
||||
cooked->expr = NULL;
|
||||
cooked->skip_validation = false;
|
||||
cooked->is_local = true;
|
||||
cooked->inhcount = 0;
|
||||
cooked->is_no_inherit = conForm->connoinherit;
|
||||
|
||||
notnulls = lappend(notnulls, cooked);
|
||||
}
|
||||
else
|
||||
{
|
||||
Constraint *constr;
|
||||
|
||||
constr = makeNode(Constraint);
|
||||
constr->contype = CONSTR_NOTNULL;
|
||||
constr->conname = pstrdup(NameStr(conForm->conname));
|
||||
constr->deferrable = false;
|
||||
constr->initdeferred = false;
|
||||
constr->location = -1;
|
||||
constr->keys = list_make1(makeString(get_attname(relid, colnum,
|
||||
false)));
|
||||
constr->skip_validation = false;
|
||||
constr->initially_valid = true;
|
||||
notnulls = lappend(notnulls, constr);
|
||||
}
|
||||
}
|
||||
|
||||
systable_endscan(conscan);
|
||||
table_close(constrRel, AccessShareLock);
|
||||
|
||||
return notnulls;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Delete a single constraint record.
|
||||
*/
|
||||
@@ -941,8 +616,6 @@ RemoveConstraintById(Oid conId)
|
||||
Relation conDesc;
|
||||
HeapTuple tup;
|
||||
Form_pg_constraint con;
|
||||
bool dropping_pk = false;
|
||||
List *unconstrained_cols = NIL;
|
||||
|
||||
conDesc = table_open(ConstraintRelationId, RowExclusiveLock);
|
||||
|
||||
@@ -967,9 +640,7 @@ 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. For not-null and primary key constraints,
|
||||
* obtain the list of columns affected, so that we can reset their
|
||||
* attnotnull flags below.
|
||||
* entries when we commit.
|
||||
*/
|
||||
if (con->contype == CONSTRAINT_CHECK)
|
||||
{
|
||||
@@ -996,36 +667,6 @@ 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);
|
||||
@@ -1045,86 +686,6 @@ 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