Fix ALTER TABLE to check pre-existing NOT NULL constraints when rewriting
a table. Otherwise a USING clause that yields NULL can leave the table violating its constraint (possibly there are other cases too). Per report from Alexander Pravking.
This commit is contained in:
parent
c5133e5920
commit
cb33de773d
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.192 2006/07/03 22:45:38 tgl Exp $
|
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.193 2006/07/10 22:10:39 tgl Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -123,6 +123,7 @@ typedef struct AlteredTableInfo
|
|||||||
/* Information saved by Phases 1/2 for Phase 3: */
|
/* Information saved by Phases 1/2 for Phase 3: */
|
||||||
List *constraints; /* List of NewConstraint */
|
List *constraints; /* List of NewConstraint */
|
||||||
List *newvals; /* List of NewColumnValue */
|
List *newvals; /* List of NewColumnValue */
|
||||||
|
bool new_notnull; /* T if we added new NOT NULL constraints */
|
||||||
Oid newTableSpace; /* new tablespace; 0 means no change */
|
Oid newTableSpace; /* new tablespace; 0 means no change */
|
||||||
/* Objects to rebuild after completing ALTER TYPE operations */
|
/* Objects to rebuild after completing ALTER TYPE operations */
|
||||||
List *changedConstraintOids; /* OIDs of constraints to rebuild */
|
List *changedConstraintOids; /* OIDs of constraints to rebuild */
|
||||||
@ -132,11 +133,11 @@ typedef struct AlteredTableInfo
|
|||||||
} AlteredTableInfo;
|
} AlteredTableInfo;
|
||||||
|
|
||||||
/* Struct describing one new constraint to check in Phase 3 scan */
|
/* Struct describing one new constraint to check in Phase 3 scan */
|
||||||
|
/* Note: new NOT NULL constraints are handled elsewhere */
|
||||||
typedef struct NewConstraint
|
typedef struct NewConstraint
|
||||||
{
|
{
|
||||||
char *name; /* Constraint name, or NULL if none */
|
char *name; /* Constraint name, or NULL if none */
|
||||||
ConstrType contype; /* CHECK, NOT_NULL, or FOREIGN */
|
ConstrType contype; /* CHECK or FOREIGN */
|
||||||
AttrNumber attnum; /* only relevant for NOT_NULL */
|
|
||||||
Oid refrelid; /* PK rel, if FOREIGN */
|
Oid refrelid; /* PK rel, if FOREIGN */
|
||||||
Node *qual; /* Check expr or FkConstraint struct */
|
Node *qual; /* Check expr or FkConstraint struct */
|
||||||
List *qualstate; /* Execution state for CHECK */
|
List *qualstate; /* Execution state for CHECK */
|
||||||
@ -2438,7 +2439,7 @@ ATRewriteTables(List **wqueue)
|
|||||||
* Test the current data within the table against new constraints
|
* Test the current data within the table against new constraints
|
||||||
* generated by ALTER TABLE commands, but don't rebuild data.
|
* generated by ALTER TABLE commands, but don't rebuild data.
|
||||||
*/
|
*/
|
||||||
if (tab->constraints != NIL)
|
if (tab->constraints != NIL || tab->new_notnull)
|
||||||
ATRewriteTable(tab, InvalidOid);
|
ATRewriteTable(tab, InvalidOid);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -2504,6 +2505,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
|
|||||||
TupleDesc oldTupDesc;
|
TupleDesc oldTupDesc;
|
||||||
TupleDesc newTupDesc;
|
TupleDesc newTupDesc;
|
||||||
bool needscan = false;
|
bool needscan = false;
|
||||||
|
List *notnull_attrs;
|
||||||
int i;
|
int i;
|
||||||
ListCell *l;
|
ListCell *l;
|
||||||
EState *estate;
|
EState *estate;
|
||||||
@ -2554,9 +2556,6 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
|
|||||||
case CONSTR_FOREIGN:
|
case CONSTR_FOREIGN:
|
||||||
/* Nothing to do here */
|
/* Nothing to do here */
|
||||||
break;
|
break;
|
||||||
case CONSTR_NOTNULL:
|
|
||||||
needscan = true;
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
elog(ERROR, "unrecognized constraint type: %d",
|
elog(ERROR, "unrecognized constraint type: %d",
|
||||||
(int) con->contype);
|
(int) con->contype);
|
||||||
@ -2572,6 +2571,25 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
|
|||||||
ex->exprstate = ExecPrepareExpr((Expr *) ex->expr, estate);
|
ex->exprstate = ExecPrepareExpr((Expr *) ex->expr, estate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notnull_attrs = NIL;
|
||||||
|
if (newrel || tab->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.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < newTupDesc->natts; i++)
|
||||||
|
{
|
||||||
|
if (newTupDesc->attrs[i]->attnotnull &&
|
||||||
|
!newTupDesc->attrs[i]->attisdropped)
|
||||||
|
notnull_attrs = lappend_int(notnull_attrs, i);
|
||||||
|
}
|
||||||
|
if (notnull_attrs)
|
||||||
|
needscan = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (needscan)
|
if (needscan)
|
||||||
{
|
{
|
||||||
ExprContext *econtext;
|
ExprContext *econtext;
|
||||||
@ -2672,6 +2690,17 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
|
|||||||
ExecStoreTuple(tuple, newslot, InvalidBuffer, false);
|
ExecStoreTuple(tuple, newslot, InvalidBuffer, false);
|
||||||
econtext->ecxt_scantuple = newslot;
|
econtext->ecxt_scantuple = newslot;
|
||||||
|
|
||||||
|
foreach(l, notnull_attrs)
|
||||||
|
{
|
||||||
|
int attn = lfirst_int(l);
|
||||||
|
|
||||||
|
if (heap_attisnull(tuple, attn+1))
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_NOT_NULL_VIOLATION),
|
||||||
|
errmsg("column \"%s\" contains null values",
|
||||||
|
NameStr(newTupDesc->attrs[attn]->attname))));
|
||||||
|
}
|
||||||
|
|
||||||
foreach(l, tab->constraints)
|
foreach(l, tab->constraints)
|
||||||
{
|
{
|
||||||
NewConstraint *con = lfirst(l);
|
NewConstraint *con = lfirst(l);
|
||||||
@ -2685,21 +2714,6 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
|
|||||||
errmsg("check constraint \"%s\" is violated by some row",
|
errmsg("check constraint \"%s\" is violated by some row",
|
||||||
con->name)));
|
con->name)));
|
||||||
break;
|
break;
|
||||||
case CONSTR_NOTNULL:
|
|
||||||
{
|
|
||||||
Datum d;
|
|
||||||
bool isnull;
|
|
||||||
|
|
||||||
d = heap_getattr(tuple, con->attnum, newTupDesc,
|
|
||||||
&isnull);
|
|
||||||
if (isnull)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_NOT_NULL_VIOLATION),
|
|
||||||
errmsg("column \"%s\" contains null values",
|
|
||||||
get_attname(tab->relid,
|
|
||||||
con->attnum))));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case CONSTR_FOREIGN:
|
case CONSTR_FOREIGN:
|
||||||
/* Nothing to do here */
|
/* Nothing to do here */
|
||||||
break;
|
break;
|
||||||
@ -3398,7 +3412,6 @@ ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
|
|||||||
HeapTuple tuple;
|
HeapTuple tuple;
|
||||||
AttrNumber attnum;
|
AttrNumber attnum;
|
||||||
Relation attr_rel;
|
Relation attr_rel;
|
||||||
NewConstraint *newcon;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* lookup the attribute
|
* lookup the attribute
|
||||||
@ -3434,13 +3447,8 @@ ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
|
|||||||
/* keep the system catalog indexes current */
|
/* keep the system catalog indexes current */
|
||||||
CatalogUpdateIndexes(attr_rel, tuple);
|
CatalogUpdateIndexes(attr_rel, tuple);
|
||||||
|
|
||||||
/* Tell Phase 3 to test the constraint */
|
/* Tell Phase 3 it needs to test the constraint */
|
||||||
newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
|
tab->new_notnull = true;
|
||||||
newcon->contype = CONSTR_NOTNULL;
|
|
||||||
newcon->attnum = attnum;
|
|
||||||
newcon->name = "NOT NULL";
|
|
||||||
|
|
||||||
tab->constraints = lappend(tab->constraints, newcon);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
heap_close(attr_rel, RowExclusiveLock);
|
heap_close(attr_rel, RowExclusiveLock);
|
||||||
@ -3909,7 +3917,6 @@ ATExecAddConstraint(AlteredTableInfo *tab, Relation rel, Node *newConstraint)
|
|||||||
newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
|
newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
|
||||||
newcon->name = ccon->name;
|
newcon->name = ccon->name;
|
||||||
newcon->contype = ccon->contype;
|
newcon->contype = ccon->contype;
|
||||||
newcon->attnum = ccon->attnum;
|
|
||||||
/* ExecQual wants implicit-AND format */
|
/* ExecQual wants implicit-AND format */
|
||||||
newcon->qual = (Node *)
|
newcon->qual = (Node *)
|
||||||
make_ands_implicit((Expr *) ccon->expr);
|
make_ands_implicit((Expr *) ccon->expr);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user