mirror of
https://github.com/postgres/postgres.git
synced 2025-06-20 15:22:23 +03:00
Allow ALTER TABLE .. SET NOT NULL to skip provably unnecessary scans.
If existing CHECK or NOT NULL constraints preclude the presence of nulls, we need not look to see whether any are present. Sergei Kornilov, reviewed by Stephen Frost, Ildar Musin, David Rowley, and by me. Discussion: http://postgr.es/m/81911511895540@web58j.yandex.ru
This commit is contained in:
@ -160,7 +160,7 @@ typedef struct AlteredTableInfo
|
||||
/* Information saved by Phases 1/2 for Phase 3: */
|
||||
List *constraints; /* List of NewConstraint */
|
||||
List *newvals; /* List of NewColumnValue */
|
||||
bool new_notnull; /* T if we added new NOT NULL constraints */
|
||||
bool verify_new_notnull; /* T if we should recheck NOT NULL */
|
||||
int rewrite; /* Reason for forced rewrite, if any */
|
||||
Oid newTableSpace; /* new tablespace; 0 means no change */
|
||||
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
|
||||
@ -372,6 +372,9 @@ static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMO
|
||||
static void ATPrepSetNotNull(Relation rel, bool recurse, bool recursing);
|
||||
static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
|
||||
const char *colName, LOCKMODE lockmode);
|
||||
static bool NotNullImpliedByRelConstraints(Relation rel, Form_pg_attribute attr);
|
||||
static bool ConstraintImpliedByRelConstraint(Relation scanrel,
|
||||
List *partConstraint, List *existedConstraints);
|
||||
static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
|
||||
Node *newDefault, LOCKMODE lockmode);
|
||||
static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName,
|
||||
@ -4550,10 +4553,11 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Test the current data within the table against new constraints
|
||||
* generated by ALTER TABLE commands, but don't rebuild data.
|
||||
* If required, test the current data within the table against new
|
||||
* constraints generated by ALTER TABLE commands, but don't rebuild
|
||||
* data.
|
||||
*/
|
||||
if (tab->constraints != NIL || tab->new_notnull ||
|
||||
if (tab->constraints != NIL || tab->verify_new_notnull ||
|
||||
tab->partition_constraint != NULL)
|
||||
ATRewriteTable(tab, InvalidOid, lockmode);
|
||||
|
||||
@ -4714,13 +4718,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
|
||||
}
|
||||
|
||||
notnull_attrs = NIL;
|
||||
if (newrel || tab->new_notnull)
|
||||
if (newrel || tab->verify_new_notnull)
|
||||
{
|
||||
/*
|
||||
* If we are rebuilding the tuples OR if we added any new NOT NULL
|
||||
* constraints, check all not-null constraints. This is a bit of
|
||||
* overkill but it minimizes risk of bugs, and heap_attisnull is a
|
||||
* pretty cheap test anyway.
|
||||
* If we are rebuilding the tuples OR if we added any new but not
|
||||
* verified NOT NULL constraints, check all not-null constraints.
|
||||
* This is a bit of overkill but it minimizes risk of bugs, and
|
||||
* heap_attisnull is a pretty cheap test anyway.
|
||||
*/
|
||||
for (i = 0; i < newTupDesc->natts; i++)
|
||||
{
|
||||
@ -5749,11 +5753,9 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
|
||||
{
|
||||
/*
|
||||
* If the new column is NOT NULL, and there is no missing value,
|
||||
* tell Phase 3 it needs to test that. (Note we don't do this for
|
||||
* an OID column. OID will be marked not null, but since it's
|
||||
* filled specially, there's no need to test anything.)
|
||||
* tell Phase 3 it needs to check for NULLs.
|
||||
*/
|
||||
tab->new_notnull |= colDef->is_not_null;
|
||||
tab->verify_new_notnull |= colDef->is_not_null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -6121,8 +6123,19 @@ ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
|
||||
|
||||
CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple);
|
||||
|
||||
/* Tell Phase 3 it needs to test the constraint */
|
||||
tab->new_notnull = true;
|
||||
/*
|
||||
* Ordinarily phase 3 must ensure that no NULLs exist in columns that
|
||||
* are set NOT NULL; however, if we can find a constraint which proves
|
||||
* this then we can skip that. We needn't bother looking if
|
||||
* we've already found that we must verify some other NOT NULL
|
||||
* constraint.
|
||||
*/
|
||||
if (!tab->verify_new_notnull &&
|
||||
!NotNullImpliedByRelConstraints(rel, (Form_pg_attribute) GETSTRUCT(tuple)))
|
||||
{
|
||||
/* Tell Phase 3 it needs to test the constraint */
|
||||
tab->verify_new_notnull = true;
|
||||
}
|
||||
|
||||
ObjectAddressSubSet(address, RelationRelationId,
|
||||
RelationGetRelid(rel), attnum);
|
||||
@ -6138,6 +6151,42 @@ ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
|
||||
return address;
|
||||
}
|
||||
|
||||
/*
|
||||
* NotNullImpliedByRelConstraints
|
||||
* Does rel's existing constraints imply NOT NULL for the given attribute?
|
||||
*/
|
||||
static bool
|
||||
NotNullImpliedByRelConstraints(Relation rel, Form_pg_attribute attr)
|
||||
{
|
||||
NullTest *nnulltest = makeNode(NullTest);
|
||||
|
||||
nnulltest->arg = (Expr *) makeVar(1,
|
||||
attr->attnum,
|
||||
attr->atttypid,
|
||||
attr->atttypmod,
|
||||
attr->attcollation,
|
||||
0);
|
||||
nnulltest->nulltesttype = IS_NOT_NULL;
|
||||
|
||||
/*
|
||||
* argisrow = false is correct even for a composite column, because
|
||||
* attnotnull does not represent a SQL-spec IS NOT NULL test in such a
|
||||
* case, just IS DISTINCT FROM NULL.
|
||||
*/
|
||||
nnulltest->argisrow = false;
|
||||
nnulltest->location = -1;
|
||||
|
||||
if (ConstraintImpliedByRelConstraint(rel, list_make1(nnulltest), NIL))
|
||||
{
|
||||
ereport(DEBUG1,
|
||||
(errmsg("existing constraints on column \"%s\".\"%s\" are sufficient to prove that it does not contain nulls",
|
||||
RelationGetRelationName(rel), NameStr(attr->attname))));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
|
||||
*
|
||||
@ -14416,8 +14465,7 @@ PartConstraintImpliedByRelConstraint(Relation scanrel,
|
||||
{
|
||||
List *existConstraint = NIL;
|
||||
TupleConstr *constr = RelationGetDescr(scanrel)->constr;
|
||||
int num_check,
|
||||
i;
|
||||
int i;
|
||||
|
||||
if (constr && constr->has_not_null)
|
||||
{
|
||||
@ -14451,6 +14499,27 @@ PartConstraintImpliedByRelConstraint(Relation scanrel,
|
||||
}
|
||||
}
|
||||
|
||||
return ConstraintImpliedByRelConstraint(scanrel, partConstraint, existConstraint);
|
||||
}
|
||||
|
||||
/*
|
||||
* ConstraintImpliedByRelConstraint
|
||||
* Do scanrel's existing constraints imply the given constraint?
|
||||
*
|
||||
* testConstraint is the constraint to validate. provenConstraint is a
|
||||
* caller-provided list of conditions which this function may assume
|
||||
* to be true. Both provenConstraint and testConstraint must be in
|
||||
* implicit-AND form, must only contain immutable clauses, and must
|
||||
* contain only Vars with varno = 1.
|
||||
*/
|
||||
bool
|
||||
ConstraintImpliedByRelConstraint(Relation scanrel, List *testConstraint, List *provenConstraint)
|
||||
{
|
||||
List *existConstraint = list_copy(provenConstraint);
|
||||
TupleConstr *constr = RelationGetDescr(scanrel)->constr;
|
||||
int num_check,
|
||||
i;
|
||||
|
||||
num_check = (constr != NULL) ? constr->num_check : 0;
|
||||
for (i = 0; i < num_check; i++)
|
||||
{
|
||||
@ -14481,13 +14550,13 @@ PartConstraintImpliedByRelConstraint(Relation scanrel,
|
||||
/*
|
||||
* Try to make the proof. Since we are comparing CHECK constraints, we
|
||||
* need to use weak implication, i.e., we assume existConstraint is
|
||||
* not-false and try to prove the same for partConstraint.
|
||||
* not-false and try to prove the same for testConstraint.
|
||||
*
|
||||
* Note that predicate_implied_by assumes its first argument is known
|
||||
* immutable. That should always be true for partition constraints, so we
|
||||
* don't test it here.
|
||||
* immutable. That should always be true for both NOT NULL and
|
||||
* partition constraints, so we don't test it here.
|
||||
*/
|
||||
return predicate_implied_by(partConstraint, existConstraint, true);
|
||||
return predicate_implied_by(testConstraint, existConstraint, true);
|
||||
}
|
||||
|
||||
/*
|
||||
|
Reference in New Issue
Block a user