@@ -209,16 +209,19 @@ typedef struct AlteredTableInfo
209
209
List *changedStatisticsDefs; /* string definitions of same */
210
210
} AlteredTableInfo;
211
211
212
- /* Struct describing one new constraint to check in Phase 3 scan */
213
- /* Note: new not-null constraints are handled elsewhere */
212
+ /*
213
+ * Struct describing one new constraint to check in Phase 3 scan. Note: new
214
+ * not-null constraints are handled here too.
215
+ */
214
216
typedef struct NewConstraint
215
217
{
216
218
char *name; /* Constraint name, or NULL if none */
217
- ConstrType contype; /* CHECK or FOREIGN */
219
+ ConstrType contype; /* CHECK or FOREIGN or NOT NULL */
218
220
Oid refrelid; /* PK rel, if FOREIGN */
219
221
Oid refindid; /* OID of PK's index, if FOREIGN */
220
222
bool conwithperiod; /* Whether the new FOREIGN KEY uses PERIOD */
221
223
Oid conid; /* OID of pg_constraint entry, if FOREIGN */
224
+ int attnum; /* NOT NULL constraint attribute number */
222
225
Node *qual; /* Check expr or CONSTR_FOREIGN Constraint */
223
226
ExprState *qualstate; /* Execution state for CHECK expr */
224
227
} NewConstraint;
@@ -6182,6 +6185,9 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
6182
6185
needscan = true;
6183
6186
con->qualstate = ExecPrepareExpr((Expr *) expand_generated_columns_in_expr(con->qual, oldrel, 1), estate);
6184
6187
break;
6188
+ case CONSTR_NOTNULL:
6189
+ /* Nothing to do here */
6190
+ break;
6185
6191
case CONSTR_FOREIGN:
6186
6192
/* Nothing to do here */
6187
6193
break;
@@ -6235,9 +6241,81 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
6235
6241
wholeatt->attnum);
6236
6242
}
6237
6243
}
6238
- if (notnull_attrs || notnull_virtual_attrs)
6244
+ }
6245
+
6246
+ /*
6247
+ * The conditions for using indexscan mechanism fast vertifing not-null
6248
+ * constraints are quite strict. All of the following conditions must be
6249
+ * met.
6250
+ * 1. We must have the need to validate not-null constraints
6251
+ * That means AlteredTableInfo->verify_new_notnull must be true.
6252
+ * 2. If table scan or table rewrite is expected later, using indexscan is
6253
+ * just wastes cycles.
6254
+ * 3. Indexes cannot be created on virtual generated columns, so fast
6255
+ * checking not-null constraints is not applicable to them.
6256
+ * 4. Only apply to regular relations.
6257
+ * 5. To avoid concurrency issue, we only do it when table was locked with
6258
+ * an AccessExclusiveLock, which is the lock acquired during ALTER TABLE
6259
+ * SET NOT NULL.
6260
+ */
6261
+ if (!needscan &&
6262
+ newrel == NULL &&
6263
+ !tab->rewrite &&
6264
+ tab->verify_new_notnull &&
6265
+ notnull_virtual_attrs == NIL &&
6266
+ oldrel->rd_rel->relkind == RELKIND_RELATION &&
6267
+ CheckRelationLockedByMe(oldrel, AccessExclusiveLock, false))
6268
+ {
6269
+ List *notnull_attnums = NIL;
6270
+
6271
+ foreach(l, tab->constraints)
6272
+ {
6273
+ NewConstraint *con = lfirst(l);
6274
+
6275
+ Form_pg_attribute attr = TupleDescAttr(newTupDesc, con->attnum - 1);
6276
+
6277
+ if (con->contype != CONSTR_NOTNULL)
6278
+ continue;
6279
+
6280
+ Assert(attr->attnotnull);
6281
+ Assert(con->attnum > 0);
6282
+ Assert(attr->attnum == con->attnum);
6283
+
6284
+ if (attr->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
6285
+ {
6286
+ needscan = true;
6287
+ break;
6288
+ }
6289
+
6290
+ notnull_attnums = lappend_int(notnull_attnums, attr->attnum);
6291
+ }
6292
+
6293
+ /*
6294
+ * notnull_attnums contains all attributes with NOT NULL constraints
6295
+ * that need validation. If needscan is true, later table scan
6296
+ * will do the validation, indedxscan would just waste cycle.
6297
+ */
6298
+ if (notnull_attnums != NIL && !needscan)
6299
+ {
6300
+ if (!index_check_notnull(oldrel, notnull_attnums))
6301
+ needscan = true;
6302
+ else
6303
+ ereport(DEBUG1,
6304
+ errmsg_internal("all new not-null constraints on relation \"%s\" have been validated by using index scan",
6305
+ RelationGetRelationName(oldrel)));
6306
+ }
6307
+ else
6308
+ {
6309
+ /*
6310
+ * corner case: in Phase2, somehow we didn't add the NOT NULL
6311
+ * constraint to AlteredTableInfo->constraints, but already set
6312
+ * verify_new_notnull to true then table scan is needed.
6313
+ */
6239
6314
needscan = true;
6315
+ }
6240
6316
}
6317
+ else if (notnull_attrs || notnull_virtual_attrs)
6318
+ needscan = true;
6241
6319
6242
6320
if (newrel || needscan)
6243
6321
{
@@ -7910,6 +7988,7 @@ ATExecSetNotNull(List **wqueue, Relation rel, char *conName, char *colName,
7910
7988
CookedConstraint *ccon;
7911
7989
List *cooked;
7912
7990
bool is_no_inherit = false;
7991
+ AlteredTableInfo *tab = NULL;
7913
7992
7914
7993
/* Guard against stack overflow due to overly deep inheritance tree. */
7915
7994
check_stack_depth();
@@ -8034,10 +8113,31 @@ ATExecSetNotNull(List **wqueue, Relation rel, char *conName, char *colName,
8034
8113
constraint->is_no_inherit = is_no_inherit;
8035
8114
constraint->conname = conName;
8036
8115
8116
+ tab = ATGetQueueEntry(wqueue, rel);
8117
+
8037
8118
/* and do it */
8038
8119
cooked = AddRelationNewConstraints(rel, NIL, list_make1(constraint),
8039
8120
false, !recursing, false, NULL);
8040
8121
ccon = linitial(cooked);
8122
+
8123
+ Assert(ccon->contype == CONSTR_NOTNULL);
8124
+
8125
+ /*
8126
+ * we may use indexscan to validate not-null constraint in Phase3. Add the
8127
+ * to-be-validated not-null constraint to Phase 3's queue.
8128
+ */
8129
+ if (!ccon->skip_validation)
8130
+ {
8131
+ NewConstraint *newcon;
8132
+ newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
8133
+
8134
+ newcon->name = ccon->name;
8135
+ newcon->contype = ccon->contype;
8136
+ newcon->attnum = ccon->attnum;
8137
+
8138
+ tab->constraints = lappend(tab->constraints, newcon);
8139
+ }
8140
+
8041
8141
ObjectAddressSet(address, ConstraintRelationId, ccon->conoid);
8042
8142
8043
8143
InvokeObjectPostAlterHook(RelationRelationId,
@@ -9918,13 +10018,15 @@ ATAddCheckNNConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
9918
10018
{
9919
10019
CookedConstraint *ccon = (CookedConstraint *) lfirst(lcon);
9920
10020
9921
- if (!ccon->skip_validation && ccon->contype != CONSTR_NOTNULL )
10021
+ if (!ccon->skip_validation)
9922
10022
{
9923
10023
NewConstraint *newcon;
9924
10024
9925
10025
newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
9926
10026
newcon->name = ccon->name;
9927
10027
newcon->contype = ccon->contype;
10028
+ if (ccon->contype == CONSTR_NOTNULL)
10029
+ newcon->attnum = ccon->attnum;
9928
10030
newcon->qual = ccon->expr;
9929
10031
9930
10032
tab->constraints = lappend(tab->constraints, newcon);
@@ -13166,6 +13268,7 @@ QueueNNConstraintValidation(List **wqueue, Relation conrel, Relation rel,
13166
13268
List *children = NIL;
13167
13269
AttrNumber attnum;
13168
13270
char *colname;
13271
+ NewConstraint *newcon;
13169
13272
13170
13273
con = (Form_pg_constraint) GETSTRUCT(contuple);
13171
13274
Assert(con->contype == CONSTRAINT_NOTNULL);
@@ -13229,7 +13332,19 @@ QueueNNConstraintValidation(List **wqueue, Relation conrel, Relation rel,
13229
13332
/* Set attnotnull appropriately without queueing another validation */
13230
13333
set_attnotnull(NULL, rel, attnum, true, false);
13231
13334
13335
+ /*
13336
+ * Queue validation for phase 3. ALTER TABLE SET NOT NULL will add NOT NULL
13337
+ * constraint to AlteredTableInfo->constraints, for consistency, do the same
13338
+ * here.
13339
+ */
13340
+ newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
13341
+ newcon->name = colname;
13342
+ newcon->contype = CONSTR_NOTNULL;
13343
+ newcon->attnum = attnum;
13344
+
13345
+ /* Find or create work queue entry for this table */
13232
13346
tab = ATGetQueueEntry(wqueue, rel);
13347
+ tab->constraints = lappend(tab->constraints, newcon);
13233
13348
tab->verify_new_notnull = true;
13234
13349
13235
13350
/*
0 commit comments