diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 78262d8..63e2538 100644 *** a/src/backend/commands/tablecmds.c --- b/src/backend/commands/tablecmds.c *************** *** 129,134 **** static List *on_commits = NIL; --- 129,137 ---- #define AT_PASS_MISC 8 /* other stuff */ #define AT_NUM_PASSES 9 + /* Level of effort required in Phase 3 (ATRewriteTables). */ + typedef enum { WORK_NONE = 0, WORK_SCAN = 1, WORK_REWRITE = 2 } WorkLevel; + typedef struct AlteredTableInfo { /* Information saved before any work commences: */ *************** *** 141,147 **** typedef struct AlteredTableInfo List *constraints; /* List of NewConstraint */ List *newvals; /* List of NewColumnValue */ bool new_notnull; /* T if we added new NOT NULL constraints */ ! bool new_changeoids; /* T if we added/dropped the OID column */ Oid newTableSpace; /* new tablespace; 0 means no change */ /* Objects to rebuild after completing ALTER TYPE operations */ List *changedConstraintOids; /* OIDs of constraints to rebuild */ --- 144,150 ---- List *constraints; /* List of NewConstraint */ List *newvals; /* List of NewColumnValue */ bool new_notnull; /* T if we added new NOT NULL constraints */ ! WorkLevel worklevel; /* How much work shall we do on the heap? */ Oid newTableSpace; /* new tablespace; 0 means no change */ /* Objects to rebuild after completing ALTER TYPE operations */ List *changedConstraintOids; /* OIDs of constraints to rebuild */ *************** *** 3180,3265 **** ATRewriteTables(List **wqueue, LOCKMODE lockmode) if (tab->relkind == RELKIND_FOREIGN_TABLE) continue; ! /* ! * We only need to rewrite the table if at least one column needs to ! * be recomputed, or we are adding/removing the OID column. ! */ ! if (tab->newvals != NIL || tab->new_changeoids) { ! /* Build a temporary relation and copy data */ ! Relation OldHeap; ! Oid OIDNewHeap; ! Oid NewTableSpace; ! OldHeap = heap_open(tab->relid, NoLock); ! /* ! * We don't support rewriting of system catalogs; there are too ! * many corner cases and too little benefit. In particular this ! * is certainly not going to work for mapped catalogs. ! */ ! if (IsSystemRelation(OldHeap)) ! ereport(ERROR, ! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("cannot rewrite system relation \"%s\"", ! RelationGetRelationName(OldHeap)))); ! /* ! * Don't allow rewrite on temp tables of other backends ... their ! * local buffer manager is not going to cope. ! */ ! if (RELATION_IS_OTHER_TEMP(OldHeap)) ! ereport(ERROR, ! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("cannot rewrite temporary tables of other sessions"))); ! /* ! * Select destination tablespace (same as original unless user ! * requested a change) ! */ ! if (tab->newTableSpace) ! NewTableSpace = tab->newTableSpace; ! else ! NewTableSpace = OldHeap->rd_rel->reltablespace; ! heap_close(OldHeap, NoLock); ! /* Create transient table that will receive the modified data */ ! OIDNewHeap = make_new_heap(tab->relid, NewTableSpace); ! /* ! * Copy the heap data into the new table with the desired ! * modifications, and test the current data within the table ! * against new constraints generated by ALTER TABLE commands. ! */ ! ATRewriteTable(tab, OIDNewHeap, lockmode); ! /* ! * Swap the physical files of the old and new heaps, then rebuild ! * indexes and discard the old heap. We can use RecentXmin for ! * the table's new relfrozenxid because we rewrote all the tuples ! * in ATRewriteTable, so no older Xid remains in the table. Also, ! * we never try to swap toast tables by content, since we have no ! * interest in letting this code work on system catalogs. ! */ ! finish_heap_swap(tab->relid, OIDNewHeap, ! false, false, true, RecentXmin); ! } ! else ! { ! /* ! * Test the current data within the table against new constraints ! * generated by ALTER TABLE commands, but don't rebuild data. ! */ ! if (tab->constraints != NIL || tab->new_notnull) ATRewriteTable(tab, InvalidOid, lockmode); ! /* ! * If we had SET TABLESPACE but no reason to reconstruct tuples, ! * just do a block-by-block copy. ! */ ! if (tab->newTableSpace) ! ATExecSetTableSpace(tab->relid, tab->newTableSpace, lockmode); } } --- 3183,3271 ---- if (tab->relkind == RELKIND_FOREIGN_TABLE) continue; ! /* New NOT NULL constraints always require a scan. */ ! if (tab->new_notnull) ! tab->worklevel = Max(tab->worklevel, WORK_SCAN); ! ! switch (tab->worklevel) { ! case WORK_REWRITE: ! { ! /* Build a temporary relation and copy data */ ! Relation OldHeap; ! Oid OIDNewHeap; ! Oid NewTableSpace; ! OldHeap = heap_open(tab->relid, NoLock); ! /* ! * We don't support rewriting of system catalogs; there are ! * too many corner cases and too little benefit. In ! * particular, it would fail for mapped catalogs. ! */ ! if (IsSystemRelation(OldHeap)) ! ereport(ERROR, ! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("cannot rewrite system relation \"%s\"", ! RelationGetRelationName(OldHeap)))); ! /* ! * Don't allow rewrite on temp tables of other backends ! * ... their local buffer manager is not going to cope. ! */ ! if (RELATION_IS_OTHER_TEMP(OldHeap)) ! ereport(ERROR, ! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("cannot rewrite temporary tables of other sessions"))); ! /* ! * Select destination tablespace (same as original unless ! * user requested a change) ! */ ! if (tab->newTableSpace) ! NewTableSpace = tab->newTableSpace; ! else ! NewTableSpace = OldHeap->rd_rel->reltablespace; ! heap_close(OldHeap, NoLock); ! /* Create transient table for the modified data */ ! OIDNewHeap = make_new_heap(tab->relid, NewTableSpace); ! /* ! * Copy the heap data into the new table with the desired ! * modifications, and test the current data within the table ! * against new constraints. ! */ ! ATRewriteTable(tab, OIDNewHeap, lockmode); ! /* ! * Swap the physical files of the old and new heaps, then ! * rebuild indexes and discard the old heap. We can use ! * RecentXmin for the table's new relfrozenxid because we ! * rewrote all the tuples in ATRewriteTable, so no older Xid ! * remains in the table. Also, we never try to swap toast ! * tables by content, since we have no interest in letting ! * this code work on system catalogs. ! */ ! finish_heap_swap(tab->relid, OIDNewHeap, ! false, false, true, RecentXmin); ! } ! break; ! ! case WORK_SCAN: ! /* Test the current table data. */ ATRewriteTable(tab, InvalidOid, lockmode); + break; ! case WORK_NONE: ! /* ! * If we had SET TABLESPACE but no reason to reconstruct tuples, ! * just do a block-by-block copy. ! */ ! if (tab->newTableSpace) ! ATExecSetTableSpace(tab->relid, tab->newTableSpace, lockmode); ! break; } } *************** *** 3320,3326 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) Relation newrel; TupleDesc oldTupDesc; TupleDesc newTupDesc; - bool needscan = false; List *notnull_attrs; int i; ListCell *l; --- 3326,3331 ---- *************** *** 3328,3333 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) --- 3333,3348 ---- CommandId mycid; BulkInsertState bistate; int hi_options; + ExprContext *econtext; + Datum *values; + bool *isnull; + TupleTableSlot *oldslot; + TupleTableSlot *newslot; + HeapScanDesc scan; + HeapTuple tuple; + MemoryContext oldCxt; + List *dropped_attrs = NIL; + ListCell *lc; /* * Open the relation(s). We have surely already locked the existing *************** *** 3379,3385 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) switch (con->contype) { case CONSTR_CHECK: - needscan = true; con->qualstate = (List *) ExecPrepareExpr((Expr *) con->qual, estate); break; --- 3394,3399 ---- *************** *** 3414,3580 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) !newTupDesc->attrs[i]->attisdropped) notnull_attrs = lappend_int(notnull_attrs, i); } - if (notnull_attrs) - needscan = true; } ! if (newrel || needscan) ! { ! ExprContext *econtext; ! Datum *values; ! bool *isnull; ! TupleTableSlot *oldslot; ! TupleTableSlot *newslot; ! HeapScanDesc scan; ! HeapTuple tuple; ! MemoryContext oldCxt; ! List *dropped_attrs = NIL; ! ListCell *lc; ! ! if (newrel) ! ereport(DEBUG1, ! (errmsg("Rewriting table \"%s\"", ! RelationGetRelationName(oldrel)))); ! else ! ereport(DEBUG1, ! (errmsg("Verifying table \"%s\"", ! RelationGetRelationName(oldrel)))); ! econtext = GetPerTupleExprContext(estate); ! /* ! * Make tuple slots for old and new tuples. Note that even when the ! * tuples are the same, the tupDescs might not be (consider ADD COLUMN ! * without a default). ! */ ! oldslot = MakeSingleTupleTableSlot(oldTupDesc); ! newslot = MakeSingleTupleTableSlot(newTupDesc); ! /* Preallocate values/isnull arrays */ ! i = Max(newTupDesc->natts, oldTupDesc->natts); ! values = (Datum *) palloc(i * sizeof(Datum)); ! isnull = (bool *) palloc(i * sizeof(bool)); ! memset(values, 0, i * sizeof(Datum)); ! memset(isnull, true, i * sizeof(bool)); ! /* ! * Any attributes that are dropped according to the new tuple ! * descriptor can be set to NULL. We precompute the list of dropped ! * attributes to avoid needing to do so in the per-tuple loop. ! */ ! for (i = 0; i < newTupDesc->natts; i++) ! { ! if (newTupDesc->attrs[i]->attisdropped) ! dropped_attrs = lappend_int(dropped_attrs, i); ! } ! /* ! * Scan through the rows, generating a new row if needed and then ! * checking all the constraints. ! */ ! scan = heap_beginscan(oldrel, SnapshotNow, 0, NULL); ! /* ! * Switch to per-tuple memory context and reset it for each tuple ! * produced, so we don't leak memory. ! */ ! oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); ! while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) { ! if (newrel) ! { ! Oid tupOid = InvalidOid; ! /* Extract data from old tuple */ ! heap_deform_tuple(tuple, oldTupDesc, values, isnull); ! if (oldTupDesc->tdhasoid) ! tupOid = HeapTupleGetOid(tuple); ! /* Set dropped attributes to null in new tuple */ ! foreach(lc, dropped_attrs) ! isnull[lfirst_int(lc)] = true; ! /* ! * Process supplied expressions to replace selected columns. ! * Expression inputs come from the old tuple. ! */ ! ExecStoreTuple(tuple, oldslot, InvalidBuffer, false); ! econtext->ecxt_scantuple = oldslot; ! foreach(l, tab->newvals) ! { ! NewColumnValue *ex = lfirst(l); ! values[ex->attnum - 1] = ExecEvalExpr(ex->exprstate, ! econtext, ! &isnull[ex->attnum - 1], ! NULL); ! } ! /* ! * Form the new tuple. Note that we don't explicitly pfree it, ! * since the per-tuple memory context will be reset shortly. ! */ ! tuple = heap_form_tuple(newTupDesc, values, isnull); ! /* Preserve OID, if any */ ! if (newTupDesc->tdhasoid) ! HeapTupleSetOid(tuple, tupOid); ! } ! /* Now check any constraints on the possibly-changed tuple */ ! ExecStoreTuple(tuple, newslot, InvalidBuffer, false); ! 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) ! { ! NewConstraint *con = lfirst(l); ! switch (con->contype) ! { ! case CONSTR_CHECK: ! if (!ExecQual(con->qualstate, econtext, true)) ! ereport(ERROR, ! (errcode(ERRCODE_CHECK_VIOLATION), ! errmsg("check constraint \"%s\" is violated by some row", ! con->name))); ! break; ! case CONSTR_FOREIGN: ! /* Nothing to do here */ ! break; ! default: ! elog(ERROR, "unrecognized constraint type: %d", ! (int) con->contype); ! } } ! /* Write the tuple out to the new relation */ ! if (newrel) ! heap_insert(newrel, tuple, mycid, hi_options, bistate); ! ResetExprContext(econtext); ! CHECK_FOR_INTERRUPTS(); ! } ! MemoryContextSwitchTo(oldCxt); ! heap_endscan(scan); ! ExecDropSingleTupleTableSlot(oldslot); ! ExecDropSingleTupleTableSlot(newslot); ! } FreeExecutorState(estate); --- 3428,3578 ---- !newTupDesc->attrs[i]->attisdropped) notnull_attrs = lappend_int(notnull_attrs, i); } } ! if (newrel) ! ereport(DEBUG1, ! (errmsg("Rewriting table \"%s\"", ! RelationGetRelationName(oldrel)))); ! else ! ereport(DEBUG1, ! (errmsg("Verifying table \"%s\"", ! RelationGetRelationName(oldrel)))); ! econtext = GetPerTupleExprContext(estate); ! /* ! * Make tuple slots for old and new tuples. Note that even when the ! * tuples are the same, the tupDescs might not be (consider ADD COLUMN ! * without a default). ! */ ! oldslot = MakeSingleTupleTableSlot(oldTupDesc); ! newslot = MakeSingleTupleTableSlot(newTupDesc); ! /* Preallocate values/isnull arrays */ ! i = Max(newTupDesc->natts, oldTupDesc->natts); ! values = (Datum *) palloc(i * sizeof(Datum)); ! isnull = (bool *) palloc(i * sizeof(bool)); ! memset(values, 0, i * sizeof(Datum)); ! memset(isnull, true, i * sizeof(bool)); ! /* ! * Any attributes that are dropped according to the new tuple ! * descriptor can be set to NULL. We precompute the list of dropped ! * attributes to avoid needing to do so in the per-tuple loop. ! */ ! for (i = 0; i < newTupDesc->natts; i++) ! { ! if (newTupDesc->attrs[i]->attisdropped) ! dropped_attrs = lappend_int(dropped_attrs, i); ! } ! /* ! * Scan through the rows, generating a new row if needed and then ! * checking all the constraints. ! */ ! scan = heap_beginscan(oldrel, SnapshotNow, 0, NULL); ! /* ! * Switch to per-tuple memory context and reset it for each tuple ! * produced, so we don't leak memory. ! */ ! oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); ! while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) ! { ! if (newrel) { ! Oid tupOid = InvalidOid; ! /* Extract data from old tuple */ ! heap_deform_tuple(tuple, oldTupDesc, values, isnull); ! if (oldTupDesc->tdhasoid) ! tupOid = HeapTupleGetOid(tuple); ! /* Set dropped attributes to null in new tuple */ ! foreach(lc, dropped_attrs) ! isnull[lfirst_int(lc)] = true; ! /* ! * Process supplied expressions to replace selected columns. ! * Expression inputs come from the old tuple. ! */ ! ExecStoreTuple(tuple, oldslot, InvalidBuffer, false); ! econtext->ecxt_scantuple = oldslot; ! foreach(l, tab->newvals) ! { ! NewColumnValue *ex = lfirst(l); ! values[ex->attnum - 1] = ExecEvalExpr(ex->exprstate, ! econtext, ! &isnull[ex->attnum - 1], ! NULL); ! } ! /* ! * Form the new tuple. Note that we don't explicitly pfree it, ! * since the per-tuple memory context will be reset shortly. ! */ ! tuple = heap_form_tuple(newTupDesc, values, isnull); ! /* Preserve OID, if any */ ! if (newTupDesc->tdhasoid) ! HeapTupleSetOid(tuple, tupOid); ! } ! /* Now check any constraints on the possibly-changed tuple */ ! ExecStoreTuple(tuple, newslot, InvalidBuffer, false); ! 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) ! { ! NewConstraint *con = lfirst(l); ! switch (con->contype) ! { ! case CONSTR_CHECK: ! if (!ExecQual(con->qualstate, econtext, true)) ! ereport(ERROR, ! (errcode(ERRCODE_CHECK_VIOLATION), ! errmsg("check constraint \"%s\" is violated by some row", ! con->name))); ! break; ! case CONSTR_FOREIGN: ! /* Nothing to do here */ ! break; ! default: ! elog(ERROR, "unrecognized constraint type: %d", ! (int) con->contype); } + } ! /* Write the tuple out to the new relation */ ! if (newrel) ! heap_insert(newrel, tuple, mycid, hi_options, bistate); ! ResetExprContext(econtext); ! CHECK_FOR_INTERRUPTS(); ! } ! MemoryContextSwitchTo(oldCxt); ! heap_endscan(scan); ! ExecDropSingleTupleTableSlot(oldslot); ! ExecDropSingleTupleTableSlot(newslot); FreeExecutorState(estate); *************** *** 4271,4276 **** ATExecAddColumn(AlteredTableInfo *tab, Relation rel, --- 4269,4275 ---- newval->expr = defval; tab->newvals = lappend(tab->newvals, newval); + tab->worklevel = WORK_REWRITE; } /* *************** *** 4288,4294 **** ATExecAddColumn(AlteredTableInfo *tab, Relation rel, */ if (isOid) { ! tab->new_changeoids = true; /* See comments in ATExecDropColumn. */ find_composite_type_dependencies(rel->rd_rel->reltype, --- 4287,4293 ---- */ if (isOid) { ! tab->worklevel = WORK_REWRITE; /* See comments in ATExecDropColumn. */ find_composite_type_dependencies(rel->rd_rel->reltype, *************** *** 4967,4973 **** ATExecDropColumn(List **wqueue, Relation rel, const char *colName, tab = ATGetQueueEntry(wqueue, rel); /* Tell Phase 3 to physically remove the OID column */ ! tab->new_changeoids = true; /* * We would also need to rewrite all tables using this table's rowtype --- 4966,4972 ---- tab = ATGetQueueEntry(wqueue, rel); /* Tell Phase 3 to physically remove the OID column */ ! tab->worklevel = WORK_REWRITE; /* * We would also need to rewrite all tables using this table's rowtype *************** *** 5000,5006 **** ATExecAddIndex(AlteredTableInfo *tab, Relation rel, /* suppress schema rights check when rebuilding existing index */ check_rights = !is_rebuild; /* skip index build if phase 3 will have to rewrite table anyway */ ! skip_build = (tab->newvals != NIL); /* suppress notices when rebuilding existing index */ quiet = is_rebuild; --- 4999,5005 ---- /* suppress schema rights check when rebuilding existing index */ check_rights = !is_rebuild; /* skip index build if phase 3 will have to rewrite table anyway */ ! skip_build = (tab->worklevel == WORK_REWRITE); /* suppress notices when rebuilding existing index */ quiet = is_rebuild; *************** *** 5139,5144 **** ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, --- 5138,5144 ---- newcon->qual = (Node *) make_ands_implicit((Expr *) ccon->expr); tab->constraints = lappend(tab->constraints, newcon); + tab->worklevel = Max(tab->worklevel, WORK_SCAN); /* Save the actually assigned name if it was defaulted */ if (constr->conname == NULL) *************** *** 5485,5490 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, --- 5485,5494 ---- newcon->qual = (Node *) fkconstraint; tab->constraints = lappend(tab->constraints, newcon); + /* + * No need to set tab->worklevel; foreign key validation is a distinct + * aspect of Phase 3. + */ } /* *************** *** 6369,6374 **** ATPrepAlterColumnType(List **wqueue, --- 6373,6379 ---- newval->expr = (Expr *) transform; tab->newvals = lappend(tab->newvals, newval); + tab->worklevel = WORK_REWRITE; /* * If we need to rewrite or scan this table, tables using its rowtype as