@@ -160,7 +160,7 @@ typedef struct AlteredTableInfo
160
160
/* Information saved by Phases 1/2 for Phase 3: */
161
161
List * constraints ; /* List of NewConstraint */
162
162
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 */
164
164
int rewrite ; /* Reason for forced rewrite, if any */
165
165
Oid newTableSpace ; /* new tablespace; 0 means no change */
166
166
bool chgPersistence ; /* T if SET LOGGED/UNLOGGED is used */
@@ -372,6 +372,9 @@ static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMO
372
372
static void ATPrepSetNotNull (Relation rel , bool recurse , bool recursing );
373
373
static ObjectAddress ATExecSetNotNull (AlteredTableInfo * tab , Relation rel ,
374
374
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 );
375
378
static ObjectAddress ATExecColumnDefault (Relation rel , const char * colName ,
376
379
Node * newDefault , LOCKMODE lockmode );
377
380
static ObjectAddress ATExecAddIdentity (Relation rel , const char * colName ,
@@ -4550,10 +4553,11 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
4550
4553
else
4551
4554
{
4552
4555
/*
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.
4555
4559
*/
4556
- if (tab -> constraints != NIL || tab -> new_notnull ||
4560
+ if (tab -> constraints != NIL || tab -> verify_new_notnull ||
4557
4561
tab -> partition_constraint != NULL )
4558
4562
ATRewriteTable (tab , InvalidOid , lockmode );
4559
4563
@@ -4714,13 +4718,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
4714
4718
}
4715
4719
4716
4720
notnull_attrs = NIL ;
4717
- if (newrel || tab -> new_notnull )
4721
+ if (newrel || tab -> verify_new_notnull )
4718
4722
{
4719
4723
/*
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.
4724
4728
*/
4725
4729
for (i = 0 ; i < newTupDesc -> natts ; i ++ )
4726
4730
{
@@ -5749,11 +5753,9 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
5749
5753
{
5750
5754
/*
5751
5755
* 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.
5755
5757
*/
5756
- tab -> new_notnull |= colDef -> is_not_null ;
5758
+ tab -> verify_new_notnull |= colDef -> is_not_null ;
5757
5759
}
5758
5760
}
5759
5761
@@ -6121,8 +6123,19 @@ ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
6121
6123
6122
6124
CatalogTupleUpdate (attr_rel , & tuple -> t_self , tuple );
6123
6125
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
+ }
6126
6139
6127
6140
ObjectAddressSubSet (address , RelationRelationId ,
6128
6141
RelationGetRelid (rel ), attnum );
@@ -6138,6 +6151,42 @@ ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
6138
6151
return address ;
6139
6152
}
6140
6153
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
+
6141
6190
/*
6142
6191
* ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
6143
6192
*
@@ -14416,8 +14465,7 @@ PartConstraintImpliedByRelConstraint(Relation scanrel,
14416
14465
{
14417
14466
List * existConstraint = NIL ;
14418
14467
TupleConstr * constr = RelationGetDescr (scanrel )-> constr ;
14419
- int num_check ,
14420
- i ;
14468
+ int i ;
14421
14469
14422
14470
if (constr && constr -> has_not_null )
14423
14471
{
@@ -14451,6 +14499,27 @@ PartConstraintImpliedByRelConstraint(Relation scanrel,
14451
14499
}
14452
14500
}
14453
14501
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
+
14454
14523
num_check = (constr != NULL ) ? constr -> num_check : 0 ;
14455
14524
for (i = 0 ; i < num_check ; i ++ )
14456
14525
{
@@ -14481,13 +14550,13 @@ PartConstraintImpliedByRelConstraint(Relation scanrel,
14481
14550
/*
14482
14551
* Try to make the proof. Since we are comparing CHECK constraints, we
14483
14552
* 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 .
14485
14554
*
14486
14555
* 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.
14489
14558
*/
14490
- return predicate_implied_by (partConstraint , existConstraint , true);
14559
+ return predicate_implied_by (testConstraint , existConstraint , true);
14491
14560
}
14492
14561
14493
14562
/*
0 commit comments