@@ -160,7 +160,7 @@ typedef struct AlteredTableInfo
160160 /* Information saved by Phases 1/2 for Phase 3: */
161161 List * constraints ; /* List of NewConstraint */
162162 List * newvals ; /* List of NewColumnValue */
163- bool new_notnull ; /* T if we added new NOT NULL constraints */
163+ bool verify_new_notnull ; /* T if we should recheck NOT NULL */
164164 int rewrite ; /* Reason for forced rewrite, if any */
165165 Oid newTableSpace ; /* new tablespace; 0 means no change */
166166 bool chgPersistence ; /* T if SET LOGGED/UNLOGGED is used */
@@ -372,6 +372,9 @@ static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMO
372372static void ATPrepSetNotNull (Relation rel , bool recurse , bool recursing );
373373static ObjectAddress ATExecSetNotNull (AlteredTableInfo * tab , Relation rel ,
374374 const char * colName , LOCKMODE lockmode );
375+ static bool NotNullImpliedByRelConstraints (Relation rel , Form_pg_attribute attr );
376+ static bool ConstraintImpliedByRelConstraint (Relation scanrel ,
377+ List * partConstraint , List * existedConstraints );
375378static ObjectAddress ATExecColumnDefault (Relation rel , const char * colName ,
376379 Node * newDefault , LOCKMODE lockmode );
377380static ObjectAddress ATExecAddIdentity (Relation rel , const char * colName ,
@@ -4550,10 +4553,11 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
45504553 else
45514554 {
45524555 /*
4553- * Test the current data within the table against new constraints
4554- * generated by ALTER TABLE commands, but don't rebuild data.
4556+ * If required, test the current data within the table against new
4557+ * constraints generated by ALTER TABLE commands, but don't rebuild
4558+ * data.
45554559 */
4556- if (tab -> constraints != NIL || tab -> new_notnull ||
4560+ if (tab -> constraints != NIL || tab -> verify_new_notnull ||
45574561 tab -> partition_constraint != NULL )
45584562 ATRewriteTable (tab , InvalidOid , lockmode );
45594563
@@ -4714,13 +4718,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
47144718 }
47154719
47164720 notnull_attrs = NIL ;
4717- if (newrel || tab -> new_notnull )
4721+ if (newrel || tab -> verify_new_notnull )
47184722 {
47194723 /*
4720- * If we are rebuilding the tuples OR if we added any new NOT NULL
4721- * constraints, check all not-null constraints. This is a bit of
4722- * overkill but it minimizes risk of bugs, and heap_attisnull is a
4723- * pretty cheap test anyway.
4724+ * If we are rebuilding the tuples OR if we added any new but not
4725+ * verified NOT NULL constraints, check all not-null constraints.
4726+ * This is a bit of overkill but it minimizes risk of bugs, and
4727+ * heap_attisnull is a pretty cheap test anyway.
47244728 */
47254729 for (i = 0 ; i < newTupDesc -> natts ; i ++ )
47264730 {
@@ -5749,11 +5753,9 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
57495753 {
57505754 /*
57515755 * If the new column is NOT NULL, and there is no missing value,
5752- * tell Phase 3 it needs to test that. (Note we don't do this for
5753- * an OID column. OID will be marked not null, but since it's
5754- * filled specially, there's no need to test anything.)
5756+ * tell Phase 3 it needs to check for NULLs.
57555757 */
5756- tab -> new_notnull |= colDef -> is_not_null ;
5758+ tab -> verify_new_notnull |= colDef -> is_not_null ;
57575759 }
57585760 }
57595761
@@ -6121,8 +6123,19 @@ ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
61216123
61226124 CatalogTupleUpdate (attr_rel , & tuple -> t_self , tuple );
61236125
6124- /* Tell Phase 3 it needs to test the constraint */
6125- tab -> new_notnull = true;
6126+ /*
6127+ * Ordinarily phase 3 must ensure that no NULLs exist in columns that
6128+ * are set NOT NULL; however, if we can find a constraint which proves
6129+ * this then we can skip that. We needn't bother looking if
6130+ * we've already found that we must verify some other NOT NULL
6131+ * constraint.
6132+ */
6133+ if (!tab -> verify_new_notnull &&
6134+ !NotNullImpliedByRelConstraints (rel , (Form_pg_attribute ) GETSTRUCT (tuple )))
6135+ {
6136+ /* Tell Phase 3 it needs to test the constraint */
6137+ tab -> verify_new_notnull = true;
6138+ }
61266139
61276140 ObjectAddressSubSet (address , RelationRelationId ,
61286141 RelationGetRelid (rel ), attnum );
@@ -6138,6 +6151,42 @@ ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
61386151 return address ;
61396152}
61406153
6154+ /*
6155+ * NotNullImpliedByRelConstraints
6156+ * Does rel's existing constraints imply NOT NULL for the given attribute?
6157+ */
6158+ static bool
6159+ NotNullImpliedByRelConstraints (Relation rel , Form_pg_attribute attr )
6160+ {
6161+ NullTest * nnulltest = makeNode (NullTest );
6162+
6163+ nnulltest -> arg = (Expr * ) makeVar (1 ,
6164+ attr -> attnum ,
6165+ attr -> atttypid ,
6166+ attr -> atttypmod ,
6167+ attr -> attcollation ,
6168+ 0 );
6169+ nnulltest -> nulltesttype = IS_NOT_NULL ;
6170+
6171+ /*
6172+ * argisrow = false is correct even for a composite column, because
6173+ * attnotnull does not represent a SQL-spec IS NOT NULL test in such a
6174+ * case, just IS DISTINCT FROM NULL.
6175+ */
6176+ nnulltest -> argisrow = false;
6177+ nnulltest -> location = -1 ;
6178+
6179+ if (ConstraintImpliedByRelConstraint (rel , list_make1 (nnulltest ), NIL ))
6180+ {
6181+ ereport (DEBUG1 ,
6182+ (errmsg ("existing constraints on column \"%s\".\"%s\" are sufficient to prove that it does not contain nulls" ,
6183+ RelationGetRelationName (rel ), NameStr (attr -> attname ))));
6184+ return true;
6185+ }
6186+
6187+ return false;
6188+ }
6189+
61416190/*
61426191 * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
61436192 *
@@ -14416,8 +14465,7 @@ PartConstraintImpliedByRelConstraint(Relation scanrel,
1441614465{
1441714466 List * existConstraint = NIL ;
1441814467 TupleConstr * constr = RelationGetDescr (scanrel )-> constr ;
14419- int num_check ,
14420- i ;
14468+ int i ;
1442114469
1442214470 if (constr && constr -> has_not_null )
1442314471 {
@@ -14451,6 +14499,27 @@ PartConstraintImpliedByRelConstraint(Relation scanrel,
1445114499 }
1445214500 }
1445314501
14502+ return ConstraintImpliedByRelConstraint (scanrel , partConstraint , existConstraint );
14503+ }
14504+
14505+ /*
14506+ * ConstraintImpliedByRelConstraint
14507+ * Do scanrel's existing constraints imply the given constraint?
14508+ *
14509+ * testConstraint is the constraint to validate. provenConstraint is a
14510+ * caller-provided list of conditions which this function may assume
14511+ * to be true. Both provenConstraint and testConstraint must be in
14512+ * implicit-AND form, must only contain immutable clauses, and must
14513+ * contain only Vars with varno = 1.
14514+ */
14515+ bool
14516+ ConstraintImpliedByRelConstraint (Relation scanrel , List * testConstraint , List * provenConstraint )
14517+ {
14518+ List * existConstraint = list_copy (provenConstraint );
14519+ TupleConstr * constr = RelationGetDescr (scanrel )-> constr ;
14520+ int num_check ,
14521+ i ;
14522+
1445414523 num_check = (constr != NULL ) ? constr -> num_check : 0 ;
1445514524 for (i = 0 ; i < num_check ; i ++ )
1445614525 {
@@ -14481,13 +14550,13 @@ PartConstraintImpliedByRelConstraint(Relation scanrel,
1448114550 /*
1448214551 * Try to make the proof. Since we are comparing CHECK constraints, we
1448314552 * need to use weak implication, i.e., we assume existConstraint is
14484- * not-false and try to prove the same for partConstraint .
14553+ * not-false and try to prove the same for testConstraint .
1448514554 *
1448614555 * Note that predicate_implied_by assumes its first argument is known
14487- * immutable. That should always be true for partition constraints, so we
14488- * don't test it here.
14556+ * immutable. That should always be true for both NOT NULL and
14557+ * partition constraints, so we don't test it here.
1448914558 */
14490- return predicate_implied_by (partConstraint , existConstraint , true);
14559+ return predicate_implied_by (testConstraint , existConstraint , true);
1449114560}
1449214561
1449314562/*
0 commit comments