@@ -6101,6 +6101,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
6101
6101
TupleDesc newTupDesc;
6102
6102
bool needscan = false;
6103
6103
List *notnull_attrs;
6104
+ List *notnull_virtual_attrs;
6104
6105
int i;
6105
6106
ListCell *l;
6106
6107
EState *estate;
@@ -6185,22 +6186,32 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
6185
6186
ex->exprstate = ExecInitExpr((Expr *) ex->expr, NULL);
6186
6187
}
6187
6188
6188
- notnull_attrs = NIL;
6189
+ notnull_attrs = notnull_virtual_attrs = NIL;
6189
6190
if (newrel || tab->verify_new_notnull)
6190
6191
{
6191
6192
/*
6192
6193
* If we are rebuilding the tuples OR if we added any new but not
6193
6194
* verified not-null constraints, check all not-null constraints. This
6194
6195
* is a bit of overkill but it minimizes risk of bugs.
6196
+ *
6197
+ * notnull_attrs does *not* collect attribute numbers for not-null
6198
+ * constraints over virtual generated columns; instead, they are
6199
+ * collected in notnull_virtual_attrs.
6195
6200
*/
6196
6201
for (i = 0; i < newTupDesc->natts; i++)
6197
6202
{
6198
6203
Form_pg_attribute attr = TupleDescAttr(newTupDesc, i);
6199
6204
6200
6205
if (attr->attnotnull && !attr->attisdropped)
6201
- notnull_attrs = lappend_int(notnull_attrs, attr->attnum);
6206
+ {
6207
+ if (attr->attgenerated != ATTRIBUTE_GENERATED_VIRTUAL)
6208
+ notnull_attrs = lappend_int(notnull_attrs, attr->attnum);
6209
+ else
6210
+ notnull_virtual_attrs = lappend_int(notnull_virtual_attrs,
6211
+ attr->attnum);
6212
+ }
6202
6213
}
6203
- if (notnull_attrs)
6214
+ if (notnull_attrs || notnull_virtual_attrs )
6204
6215
needscan = true;
6205
6216
}
6206
6217
@@ -6214,6 +6225,29 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
6214
6225
List *dropped_attrs = NIL;
6215
6226
ListCell *lc;
6216
6227
Snapshot snapshot;
6228
+ ResultRelInfo *rInfo = NULL;
6229
+
6230
+ /*
6231
+ * When adding or changing a virtual generated column with a not-null
6232
+ * constraint, we need to evaluate whether the generation expression
6233
+ * is null. For that, we borrow ExecRelGenVirtualNotNull(). Here, we
6234
+ * prepare a dummy ResultRelInfo.
6235
+ */
6236
+ if (notnull_virtual_attrs != NIL)
6237
+ {
6238
+ MemoryContext oldcontext;
6239
+
6240
+ Assert(newTupDesc->constr->has_generated_virtual);
6241
+ Assert(newTupDesc->constr->has_not_null);
6242
+ oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
6243
+ rInfo = makeNode(ResultRelInfo);
6244
+ InitResultRelInfo(rInfo,
6245
+ oldrel,
6246
+ 0, /* dummy rangetable index */
6247
+ NULL,
6248
+ estate->es_instrument);
6249
+ MemoryContextSwitchTo(oldcontext);
6250
+ }
6217
6251
6218
6252
if (newrel)
6219
6253
ereport(DEBUG1,
@@ -6394,6 +6428,26 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
6394
6428
}
6395
6429
}
6396
6430
6431
+ if (notnull_virtual_attrs != NIL)
6432
+ {
6433
+ AttrNumber attnum;
6434
+
6435
+ attnum = ExecRelGenVirtualNotNull(rInfo, insertslot,
6436
+ estate,
6437
+ notnull_virtual_attrs);
6438
+ if (attnum != InvalidAttrNumber)
6439
+ {
6440
+ Form_pg_attribute attr = TupleDescAttr(newTupDesc, attnum - 1);
6441
+
6442
+ ereport(ERROR,
6443
+ errcode(ERRCODE_NOT_NULL_VIOLATION),
6444
+ errmsg("column \"%s\" of relation \"%s\" contains null values",
6445
+ NameStr(attr->attname),
6446
+ RelationGetRelationName(oldrel)),
6447
+ errtablecol(oldrel, attnum));
6448
+ }
6449
+ }
6450
+
6397
6451
foreach(l, tab->constraints)
6398
6452
{
6399
6453
NewConstraint *con = lfirst(l);
@@ -7843,14 +7897,6 @@ ATExecSetNotNull(List **wqueue, Relation rel, char *conName, char *colName,
7843
7897
errmsg("cannot alter system column \"%s\"",
7844
7898
colName)));
7845
7899
7846
- /* TODO: see transformColumnDefinition() */
7847
- if (TupleDescAttr(RelationGetDescr(rel), attnum - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
7848
- ereport(ERROR,
7849
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
7850
- errmsg("not-null constraints are not supported on virtual generated columns"),
7851
- errdetail("Column \"%s\" of relation \"%s\" is a virtual generated column.",
7852
- colName, RelationGetRelationName(rel))));
7853
-
7854
7900
/* See if there's already a constraint */
7855
7901
tuple = findNotNullConstraintAttnum(RelationGetRelid(rel), attnum);
7856
7902
if (HeapTupleIsValid(tuple))
@@ -8519,6 +8565,9 @@ ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
8519
8565
errdetail("Column \"%s\" of relation \"%s\" is a virtual generated column.",
8520
8566
colName, RelationGetRelationName(rel))));
8521
8567
8568
+ if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL && attTup->attnotnull)
8569
+ tab->verify_new_notnull = true;
8570
+
8522
8571
/*
8523
8572
* We need to prevent this because a change of expression could affect a
8524
8573
* row filter and inject expressions that are not permitted in a row
0 commit comments