diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 1d52be6..4efb02e 100644 *** a/doc/src/sgml/ref/alter_table.sgml --- b/doc/src/sgml/ref/alter_table.sgml *************** *** 403,411 **** ALTER TABLE name for details on the available parameters. Note that the table contents will not be modified immediately by this command; depending on the parameter you might need to rewrite the table to get the desired effects. ! That can be done with ! or one of the forms of ALTER ! TABLE that forces a table rewrite. --- 403,411 ---- for details on the available parameters. Note that the table contents will not be modified immediately by this command; depending on the parameter you might need to rewrite the table to get the desired effects. ! That can be done with VACUUM ! FULL, or one of the forms ! of ALTER TABLE that forces a table rewrite. *************** *** 746,759 **** ALTER TABLE name ! Adding a column with a non-null default or changing the type of an ! existing column will require the entire table and indexes to be rewritten. ! This might take a significant amount of time for a large table; and it will ! temporarily require double the disk space. Adding or removing a system oid column likewise requires rewriting the entire table. Adding a CHECK or NOT NULL constraint requires scanning the table to verify that existing rows meet the constraint. --- 746,770 ---- ! Adding a column with a non-null default will rewrite the entire table and ! all indexes. Changing the type of an existing column will do the same ! unless a binary-coercible cast implements the type conversion. Refer to ! for further information. A rewrite might ! take a significant amount of time for a large table, and it will temporarily ! require double the disk space. Adding or removing a system oid column likewise requires rewriting the entire table. + Similar to the behavior of VACUUM FULL, the + rewriting process eliminates any dead space in the table. Prior + to PostgreSQL 9.0, SET DATA TYPE + outpaced VACUUM FULL at this task, so it was useful even absent + the need for a column type change. This speed advantage no longer holds, + and SET DATA TYPE may not even rewrite the table. + + + Adding a CHECK or NOT NULL constraint requires scanning the table to verify that existing rows meet the constraint. *************** *** 777,797 **** ALTER TABLE name - The fact that SET DATA TYPE requires rewriting the whole table - is sometimes an advantage, because the rewriting process eliminates - any dead space in the table. For example, to reclaim the space occupied - by a dropped column immediately, the fastest way is: - - ALTER TABLE table ALTER COLUMN anycol TYPE anytype; - - where anycol is any remaining table column and - anytype is the same type that column already has. - This results in no semantically-visible change in the table, - but the command forces rewriting, which gets rid of no-longer-useful - data. - - - The USING option of SET DATA TYPE can actually specify any expression involving the old values of the row; that is, it can refer to other columns as well as the one being converted. This allows --- 788,793 ---- diff --git a/src/backend/catalog/index.cindex 5254b65..411de09 100644 *** a/src/backend/catalog/index.c --- b/src/backend/catalog/index.c *************** *** 1673,1678 **** index_build(Relation heapRelation, --- 1673,1688 ---- procedure = indexRelation->rd_am->ambuild; Assert(RegProcedureIsValid(procedure)); + if (indexInfo->ii_ToastForRelName != NULL) + ereport(DEBUG1, + (errmsg("building TOAST index for table \"%s\"", + indexInfo->ii_ToastForRelName))); + else + ereport(DEBUG1, + (errmsg("building index \"%s\" on table \"%s\"", + RelationGetRelationName(indexRelation), + RelationGetRelationName(heapRelation)))); + /* * Switch to the table owner's userid, so that any index functions are run * as that user. Also lock down security-restricted operations and *************** *** 2663,2669 **** IndexGetRelation(Oid indexId) * reindex_index - This routine is used to recreate a single index */ void ! reindex_index(Oid indexId, bool skip_constraint_checks) { Relation iRel, heapRelation, --- 2673,2680 ---- * reindex_index - This routine is used to recreate a single index */ void ! reindex_index(Oid indexId, const char *toastFor, ! bool skip_constraint_checks) { Relation iRel, heapRelation, *************** *** 2721,2726 **** reindex_index(Oid indexId, bool skip_constraint_checks) --- 2732,2740 ---- indexInfo->ii_ExclusionStrats = NULL; } + /* Pass the name of relation this TOAST index serves, if any. */ + indexInfo->ii_ToastForRelName = toastFor; + /* We'll build a new physical relation for the index */ RelationSetNewRelfilenode(iRel, InvalidTransactionId); *************** *** 2780,2785 **** reindex_index(Oid indexId, bool skip_constraint_checks) --- 2794,2802 ---- * reindex_relation - This routine is used to recreate all indexes * of a relation (and optionally its toast relation too, if any). * + * If this is a TOAST relation, toastFor may bear the parent relation name, + * facilitating improved messages. + * * "flags" can include REINDEX_SUPPRESS_INDEX_USE and REINDEX_CHECK_CONSTRAINTS. * * If flags has REINDEX_SUPPRESS_INDEX_USE, the relation was just completely *************** *** 2802,2808 **** reindex_index(Oid indexId, bool skip_constraint_checks) * CommandCounterIncrement will occur after each index rebuild. */ bool ! reindex_relation(Oid relid, bool toast_too, int flags) { Relation rel; Oid toast_relid; --- 2819,2826 ---- * CommandCounterIncrement will occur after each index rebuild. */ bool ! reindex_relation(Oid relid, const char *toastFor, ! bool toast_too, int flags) { Relation rel; Oid toast_relid; *************** *** 2879,2885 **** reindex_relation(Oid relid, bool toast_too, int flags) if (is_pg_class) RelationSetIndexList(rel, doneIndexes, InvalidOid); ! reindex_index(indexOid, !(flags & REINDEX_CHECK_CONSTRAINTS)); CommandCounterIncrement(); --- 2897,2903 ---- if (is_pg_class) RelationSetIndexList(rel, doneIndexes, InvalidOid); ! reindex_index(indexOid, toastFor, !(flags & REINDEX_CHECK_CONSTRAINTS)); CommandCounterIncrement(); *************** *** 2902,2912 **** reindex_relation(Oid relid, bool toast_too, int flags) if (is_pg_class) RelationSetIndexList(rel, indexIds, ClassOidIndexId); - /* - * Close rel, but continue to hold the lock. - */ - heap_close(rel, NoLock); - result = (indexIds != NIL); /* --- 2920,2925 ---- *************** *** 2916,2922 **** reindex_relation(Oid relid, bool toast_too, int flags) */ Assert(!(toast_too && (flags & REINDEX_SUPPRESS_INDEX_USE))); if (toast_too && OidIsValid(toast_relid)) ! result |= reindex_relation(toast_relid, false, flags); return result; } --- 2929,2941 ---- */ Assert(!(toast_too && (flags & REINDEX_SUPPRESS_INDEX_USE))); if (toast_too && OidIsValid(toast_relid)) ! result |= reindex_relation(toast_relid, RelationGetRelationName(rel), ! false, flags); ! ! /* ! * Close rel, but continue to hold the lock. ! */ ! heap_close(rel, NoLock); return result; } diff --git a/src/backend/catalog/tindex c4be3a9..f60b7c1 100644 *** a/src/backend/catalog/toasting.c --- b/src/backend/catalog/toasting.c *************** *** 36,43 **** extern Oid binary_upgrade_next_toast_pg_class_oid; Oid binary_upgrade_next_toast_pg_type_oid = InvalidOid; ! static bool create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, ! Datum reloptions); static bool needs_toast_table(Relation rel); --- 36,43 ---- Oid binary_upgrade_next_toast_pg_type_oid = InvalidOid; ! static bool create_toast_table(Relation rel, const char *finalRelName, ! Oid toastOid, Oid toastIndexOid, Datum reloptions); static bool needs_toast_table(Relation rel); *************** *** 46,51 **** static bool needs_toast_table(Relation rel); --- 46,54 ---- * If the table needs a toast table, and doesn't already have one, * then create a toast table for it. * + * make_new_heap fills finalRelName, so messages display the permanent table + * name, not the rewrite-temporary name. Most callers should pass NULL. + * * reloptions for the toast table can be passed, too. Pass (Datum) 0 * for default reloptions. * *************** *** 54,60 **** static bool needs_toast_table(Relation rel); * to end with CommandCounterIncrement if it makes any changes. */ void ! AlterTableCreateToastTable(Oid relOid, Datum reloptions) { Relation rel; --- 57,64 ---- * to end with CommandCounterIncrement if it makes any changes. */ void ! AlterTableCreateToastTable(Oid relOid, const char *finalRelName, ! Datum reloptions) { Relation rel; *************** *** 66,72 **** AlterTableCreateToastTable(Oid relOid, Datum reloptions) rel = heap_open(relOid, AccessExclusiveLock); /* create_toast_table does all the work */ ! (void) create_toast_table(rel, InvalidOid, InvalidOid, reloptions); heap_close(rel, NoLock); } --- 70,77 ---- rel = heap_open(relOid, AccessExclusiveLock); /* create_toast_table does all the work */ ! (void) create_toast_table(rel, finalRelName, ! InvalidOid, InvalidOid, reloptions); heap_close(rel, NoLock); } *************** *** 92,98 **** BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid) relName))); /* create_toast_table does all the work */ ! if (!create_toast_table(rel, toastOid, toastIndexOid, (Datum) 0)) elog(ERROR, "\"%s\" does not require a toast table", relName); --- 97,103 ---- relName))); /* create_toast_table does all the work */ ! if (!create_toast_table(rel, NULL, toastOid, toastIndexOid, (Datum) 0)) elog(ERROR, "\"%s\" does not require a toast table", relName); *************** *** 104,114 **** BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid) * create_toast_table --- internal workhorse * * rel is already opened and exclusive-locked * toastOid and toastIndexOid are normally InvalidOid, but during * bootstrap they can be nonzero to specify hand-assigned OIDs */ static bool ! create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, Datum reloptions) { Oid relOid = RelationGetRelid(rel); HeapTuple reltup; --- 109,121 ---- * create_toast_table --- internal workhorse * * rel is already opened and exclusive-locked + * finalRelName is normally NULL; make_new_heap overrides it * toastOid and toastIndexOid are normally InvalidOid, but during * bootstrap they can be nonzero to specify hand-assigned OIDs */ static bool ! create_toast_table(Relation rel, const char *finalRelName, ! Oid toastOid, Oid toastIndexOid, Datum reloptions) { Oid relOid = RelationGetRelid(rel); HeapTuple reltup; *************** *** 259,264 **** create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, Datum reloptio --- 266,273 ---- indexInfo->ii_ExclusionOps = NULL; indexInfo->ii_ExclusionProcs = NULL; indexInfo->ii_ExclusionStrats = NULL; + indexInfo->ii_ToastForRelName + = finalRelName != NULL ? finalRelName : RelationGetRelationName(rel); indexInfo->ii_Unique = true; indexInfo->ii_ReadyForInserts = true; indexInfo->ii_Concurrent = false; diff --git a/src/backend/commands/cluindex 59a4394..87af84f 100644 *** a/src/backend/commands/cluster.c --- b/src/backend/commands/cluster.c *************** *** 681,687 **** make_new_heap(Oid OIDOldHeap, Oid NewTableSpace) if (isNull) reloptions = (Datum) 0; ! AlterTableCreateToastTable(OIDNewHeap, reloptions); ReleaseSysCache(tuple); } --- 681,688 ---- if (isNull) reloptions = (Datum) 0; ! AlterTableCreateToastTable(OIDNewHeap, RelationGetRelationName(OldHeap), ! reloptions); ReleaseSysCache(tuple); } *************** *** 1400,1406 **** finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, reindex_flags = REINDEX_SUPPRESS_INDEX_USE; if (check_constraints) reindex_flags |= REINDEX_CHECK_CONSTRAINTS; ! reindex_relation(OIDOldHeap, false, reindex_flags); /* Destroy new heap with old filenode */ object.classId = RelationRelationId; --- 1401,1407 ---- reindex_flags = REINDEX_SUPPRESS_INDEX_USE; if (check_constraints) reindex_flags |= REINDEX_CHECK_CONSTRAINTS; ! reindex_relation(OIDOldHeap, NULL, false, reindex_flags); /* Destroy new heap with old filenode */ object.classId = RelationRelationId; diff --git a/src/backend/commands/indindex 94ed437..1dab757 100644 *** a/src/backend/commands/indexcmds.c --- b/src/backend/commands/indexcmds.c *************** *** 1482,1488 **** ReindexIndex(RangeVar *indexRelation) ReleaseSysCache(tuple); ! reindex_index(indOid, false); } /* --- 1482,1488 ---- ReleaseSysCache(tuple); ! reindex_index(indOid, NULL, false); } /* *************** *** 1514,1520 **** ReindexTable(RangeVar *relation) ReleaseSysCache(tuple); ! if (!reindex_relation(heapOid, true, 0)) ereport(NOTICE, (errmsg("table \"%s\" has no indexes", relation->relname))); --- 1514,1520 ---- ReleaseSysCache(tuple); ! if (!reindex_relation(heapOid, NULL, true, 0)) ereport(NOTICE, (errmsg("table \"%s\" has no indexes", relation->relname))); *************** *** 1627,1633 **** ReindexDatabase(const char *databaseName, bool do_system, bool do_user) StartTransactionCommand(); /* functions in indexes may want a snapshot set */ PushActiveSnapshot(GetTransactionSnapshot()); ! if (reindex_relation(relid, true, 0)) ereport(NOTICE, (errmsg("table \"%s.%s\" was reindexed", get_namespace_name(get_rel_namespace(relid)), --- 1627,1633 ---- StartTransactionCommand(); /* functions in indexes may want a snapshot set */ PushActiveSnapshot(GetTransactionSnapshot()); ! if (reindex_relation(relid, NULL, true, 0)) ereport(NOTICE, (errmsg("table \"%s.%s\" was reindexed", get_namespace_name(get_rel_namespace(relid)), diff --git a/src/backend/commands/tableindex 1ecba02..67fdafc 100644 *** a/src/backend/commands/tablecmds.c --- b/src/backend/commands/tablecmds.c *************** *** 71,76 **** --- 71,77 ---- #include "storage/smgr.h" #include "utils/acl.h" #include "utils/builtins.h" + #include "utils/datum.h" #include "utils/fmgroids.h" #include "utils/inval.h" #include "utils/lsyscache.h" *************** *** 129,134 **** static List *on_commits = NIL; --- 130,138 ---- #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: */ *************** *** 142,147 **** typedef struct AlteredTableInfo --- 146,152 ---- 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 */ + 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 */ *************** *** 1078,1084 **** ExecuteTruncate(TruncateStmt *stmt) /* * Reconstruct the indexes to match, and we're done. */ ! reindex_relation(heap_relid, true, 0); } } --- 1083,1089 ---- /* * Reconstruct the indexes to match, and we're done. */ ! reindex_relation(heap_relid, NULL, true, 0); } } *************** *** 2990,2996 **** ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode) (tab->subcmds[AT_PASS_ADD_COL] || tab->subcmds[AT_PASS_ALTER_TYPE] || tab->subcmds[AT_PASS_COL_ATTRS])) ! AlterTableCreateToastTable(tab->relid, (Datum) 0); } } --- 2995,3001 ---- (tab->subcmds[AT_PASS_ADD_COL] || tab->subcmds[AT_PASS_ALTER_TYPE] || tab->subcmds[AT_PASS_COL_ATTRS])) ! AlterTableCreateToastTable(tab->relid, NULL, (Datum) 0); } } *************** *** 3193,3202 **** ATRewriteTables(List **wqueue, LOCKMODE lockmode) 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; --- 3198,3232 ---- continue; /* + * Any operation has to be propagated to tables that use this table's + * rowtype as a column type, but this is not yet implemented. Reject + * changes where this ommission would be particularly glaring: column + * type changes, new columns with default values, and OIDs changes. + * + * (Eventually this will probably become true for scans as well, but at + * the moment a composite type does not enforce any constraints, so it's + * not necessary/appropriate to enforce them just during ALTER.) + */ + if (tab->newvals != NIL || tab->new_changeoids) + { + Relation rel; + + rel = heap_open(tab->relid, NoLock); + find_composite_type_dependencies(rel->rd_rel->reltype, + RelationGetRelationName(rel), + NULL); + heap_close(rel, NoLock); + } + + /* New NOT NULL constraints always require a scan. */ + if (tab->new_notnull) + tab->worklevel = Max(tab->worklevel, WORK_SCAN); + + /* * 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->worklevel == WORK_REWRITE) { /* Build a temporary relation and copy data */ Relation OldHeap; *************** *** 3263,3269 **** ATRewriteTables(List **wqueue, LOCKMODE lockmode) * 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); /* --- 3293,3299 ---- * Test the current data within the table against new constraints * generated by ALTER TABLE commands, but don't rebuild data. */ ! if (tab->worklevel == WORK_SCAN) ATRewriteTable(tab, InvalidOid, lockmode); /* *************** *** 3332,3338 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) Relation newrel; TupleDesc oldTupDesc; TupleDesc newTupDesc; - bool needscan = false; List *notnull_attrs; int i; ListCell *l; --- 3362,3367 ---- *************** *** 3378,3396 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) } /* - * If we need to rewrite the table, the operation has to be propagated to - * tables that use this table's rowtype as a column type. - * - * (Eventually this will probably become true for scans as well, but at - * the moment a composite type does not enforce any constraints, so it's - * not necessary/appropriate to enforce them just during ALTER.) - */ - if (newrel) - find_composite_type_dependencies(oldrel->rd_rel->reltype, - RelationGetRelationName(oldrel), - NULL); - - /* * Generate the constraint and default execution states */ --- 3407,3412 ---- *************** *** 3404,3410 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) switch (con->contype) { case CONSTR_CHECK: - needscan = true; con->qualstate = (List *) ExecPrepareExpr((Expr *) con->qual, estate); break; --- 3420,3425 ---- *************** *** 3439,3450 **** 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; --- 3454,3462 ---- !newTupDesc->attrs[i]->attisdropped) notnull_attrs = lappend_int(notnull_attrs, i); } } ! { /* XXX reindent? */ ExprContext *econtext; Datum *values; bool *isnull; *************** *** 3456,3461 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) --- 3468,3482 ---- 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); /* *************** *** 3498,3515 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) 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. --- 3519,3549 ---- while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) { ! /* ! * If we're changing the TupleDesc, compute new tuple values using ! * each transformation expression. When rewriting, also form a new ! * physical tuple. In Assert-enabled builds, check for cases that ! * should have been WORK_REWRITE by comparing the data. ! */ ! if (tab->newvals != NIL || tab->new_changeoids) { Oid tupOid = InvalidOid; ! if (newrel ! #ifdef USE_ASSERT_CHECKING ! || assert_enabled ! #endif ! ) ! { ! /* 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. *************** *** 3526,3542 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) 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 */ --- 3560,3603 ---- econtext, &isnull[ex->attnum - 1], NULL); + + #ifdef USE_ASSERT_CHECKING + if (assert_enabled) + { + Datum oldval = values[ex->attnum - 1]; + bool oldisnull = isnull[ex->attnum - 1]; + Form_pg_attribute f = newTupDesc->attrs[ex->attnum - 1]; + + if (f->attbyval && f->attlen == -1) + oldval = PointerGetDatum(PG_DETOAST_DATUM(oldval)); + + /* + * We don't detect the gross error of !newrel when the + * typlen actually changed. attbyval could differ in + * theory, but we assume it does not. + */ + Assert(newrel || + (isnull[ex->attnum - 1] == oldisnull + && (oldisnull || + datumIsEqual(oldval, + values[ex->attnum - 1], + f->attbyval, f->attlen)))); + } + #endif } ! if (newrel) ! { ! /* ! * 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 */ *************** *** 4283,4288 **** ATExecAddColumn(AlteredTableInfo *tab, Relation rel, --- 4344,4350 ---- newval->expr = defval; tab->newvals = lappend(tab->newvals, newval); + tab->worklevel = WORK_REWRITE; } /* *************** *** 4299,4305 **** ATExecAddColumn(AlteredTableInfo *tab, Relation rel, --- 4361,4370 ---- * table to fix that. */ if (isOid) + { tab->new_changeoids = true; + tab->worklevel = WORK_REWRITE; + } /* * Add needed dependency entries for the new column. *************** *** 4973,4978 **** ATExecDropColumn(List **wqueue, Relation rel, const char *colName, --- 5038,5044 ---- /* Tell Phase 3 to physically remove the OID column */ tab->new_changeoids = true; + tab->worklevel = WORK_REWRITE; } } *************** *** 4996,5002 **** 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; --- 5062,5068 ---- /* 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; *************** *** 5205,5210 **** ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, --- 5271,5277 ---- 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) *************** *** 5551,5556 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, --- 5618,5627 ---- 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. + */ } /* *************** *** 5874,5879 **** validateForeignKeyConstraint(Constraint *fkconstraint, --- 5945,5954 ---- HeapTuple tuple; Trigger trig; + ereport(DEBUG1, + (errmsg("validating foreign key constraint \"%s\"", + fkconstraint->conname))); + /* * Build a trigger call structure; we'll need it either way. */ *************** *** 6361,6366 **** ATPrepAlterColumnType(List **wqueue, --- 6436,6443 ---- if (tab->relkind == RELKIND_RELATION) { + CoerceExemptions exempt; + /* * Set up an expression to transform the old data value to the new type. * If a USING option was given, transform and use that expression, else *************** *** 6421,6426 **** ATPrepAlterColumnType(List **wqueue, --- 6498,6504 ---- (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("column \"%s\" cannot be cast to type %s", colName, format_type_be(targettype)))); + exempt = GetCoerceExemptions(transform, 1, attnum); /* * Add a work queue item to make ATRewriteTable update the column *************** *** 6431,6436 **** ATPrepAlterColumnType(List **wqueue, --- 6509,6519 ---- newval->expr = (Expr *) transform; tab->newvals = lappend(tab->newvals, newval); + if (!(exempt & COERCE_EXEMPT_NOCHANGE)) + tab->worklevel = WORK_REWRITE; + else if (!(exempt & COERCE_EXEMPT_NOERROR)) + tab->worklevel = Max(tab->worklevel, WORK_SCAN); + /* else, both bits set: WORK_NONE */ } else if (tab->relkind == RELKIND_FOREIGN_TABLE) { diff --git a/src/backend/executor/execMindex 600f7e0..56b10a3 100644 *** a/src/backend/executor/execMain.c --- b/src/backend/executor/execMain.c *************** *** 2307,2313 **** OpenIntoRel(QueryDesc *queryDesc) (void) heap_reloptions(RELKIND_TOASTVALUE, reloptions, true); ! AlterTableCreateToastTable(intoRelationId, reloptions); /* * And open the constructed table for writing. --- 2307,2313 ---- (void) heap_reloptions(RELKIND_TOASTVALUE, reloptions, true); ! AlterTableCreateToastTable(intoRelationId, NULL, reloptions); /* * And open the constructed table for writing. diff --git a/src/backend/parser/parse_index 5b0dc14..b27e5d2 100644 *** a/src/backend/parser/parse_coerce.c --- b/src/backend/parser/parse_coerce.c *************** *** 19,24 **** --- 19,25 ---- #include "catalog/pg_inherits_fn.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" + #include "commands/typecmds.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "parser/parse_coerce.h" *************** *** 1805,1810 **** IsBinaryCoercible(Oid srctype, Oid targettype) --- 1806,1871 ---- } + /* GetCoerceExemptions() + * Assess invariants of a coercion expression. + * + * Various common expressions arising from type coercion are subject to + * optimizations. For example, a simple varchar -> text cast will never change + * the underlying data (COERCE_EXEMPT_NOCHANGE) and never yield an error + * (COERCE_EXEMPT_NOERROR). A varchar(8) -> varchar(4) will never change the + * data, but it may yield an error. Given a varno and varattno denoting "the" + * source datum, determine which invariants hold for an expression by walking it + * per these rules: + * + * 1. A Var with the varno/varattno in question has both invariants. + * 2. A RelabelType node inherits the invariants of its sole argument. + * 3. A CoerceToDomain node inherits any COERCE_EXEMPT_NOCHANGE invariant from + * its sole argument. When GetDomainConstraints() == NIL, it also inherits + * COERCE_EXEMPT_NOERROR. Otherwise, COERCE_EXEMPT_NOERROR becomes false. + * 4. All other nodes have neither invariant. + * + * Returns a bit string that may contain the following bits: + * COERCE_EXEMPT_NOCHANGE: expression result will always have the same binary + * representation as a Var expression having the given varno and + * varattno + * COERCE_EXEMPT_NOERROR: expression will never throw an error + */ + CoerceExemptions + GetCoerceExemptions(Node *expr, + Index varno, AttrNumber varattno) + { + CoerceExemptions ret = COERCE_EXEMPT_NOCHANGE | COERCE_EXEMPT_NOERROR; + + Assert(expr != NULL); + + for (;;) + { + if (IsA(expr, Var) + && ((Var *) expr)->varno == varno + && ((Var *) expr)->varattno == varattno) + { + return ret; + } + if (IsA(expr, RelabelType)) + { + expr = (Node *) ((RelabelType *) expr)->arg; + } + else if (IsA(expr, CoerceToDomain)) + { + CoerceToDomain *d = (CoerceToDomain *) expr; + + if (GetDomainConstraints(d->resulttype) != NIL) + ret &= ~COERCE_EXEMPT_NOERROR; + expr = (Node *) d->arg; + } + else + { + return 0; + } + } + } + + /* * find_coercion_pathway * Look for a coercion pathway between two types. diff --git a/src/backend/tcop/utility.c index 9500037..e6416e4 100644 *** a/src/backend/tcop/utility.c --- b/src/backend/tcop/utility.c *************** *** 540,546 **** standard_ProcessUtility(Node *parsetree, (void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true); ! AlterTableCreateToastTable(relOid, toast_options); } else if (IsA(stmt, CreateForeignTableStmt)) { --- 540,546 ---- (void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true); ! AlterTableCreateToastTable(relOid, NULL, toast_options); } else if (IsA(stmt, CreateForeignTableStmt)) { diff --git a/src/include/catalog/index 60387cc..3e90656 100644 *** a/src/include/catalog/index.h --- b/src/include/catalog/index.h *************** *** 85,95 **** extern double IndexBuildHeapScan(Relation heapRelation, extern void validate_index(Oid heapId, Oid indexId, Snapshot snapshot); ! extern void reindex_index(Oid indexId, bool skip_constraint_checks); #define REINDEX_CHECK_CONSTRAINTS 0x1 #define REINDEX_SUPPRESS_INDEX_USE 0x2 ! extern bool reindex_relation(Oid relid, bool toast_too, int flags); extern bool ReindexIsProcessingHeap(Oid heapOid); extern bool ReindexIsProcessingIndex(Oid indexOid); --- 85,97 ---- extern void validate_index(Oid heapId, Oid indexId, Snapshot snapshot); ! extern void reindex_index(Oid indexId, const char *toastFor, ! bool skip_constraint_checks); #define REINDEX_CHECK_CONSTRAINTS 0x1 #define REINDEX_SUPPRESS_INDEX_USE 0x2 ! extern bool reindex_relation(Oid relid, const char *toastFor, ! bool toast_too, int flags); extern bool ReindexIsProcessingHeap(Oid heapOid); extern bool ReindexIsProcessingIndex(Oid indexOid); diff --git a/src/include/catalog/tindex de3623a..7bd2bdd 100644 *** a/src/include/catalog/toasting.h --- b/src/include/catalog/toasting.h *************** *** 17,23 **** /* * toasting.c prototypes */ ! extern void AlterTableCreateToastTable(Oid relOid, Datum reloptions); extern void BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid); --- 17,24 ---- /* * toasting.c prototypes */ ! extern void AlterTableCreateToastTable(Oid relOid, const char *finalRelName, ! Datum reloptions); extern void BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid); diff --git a/src/include/nodes/execnoindex 546b581..46d9d1a 100644 *** a/src/include/nodes/execnodes.h --- b/src/include/nodes/execnodes.h *************** *** 64,69 **** typedef struct IndexInfo --- 64,70 ---- Oid *ii_ExclusionOps; /* array with one entry per column */ Oid *ii_ExclusionProcs; /* array with one entry per column */ uint16 *ii_ExclusionStrats; /* array with one entry per column */ + const char *ii_ToastForRelName; /* TOAST index only: name of main rel */ bool ii_Unique; bool ii_ReadyForInserts; bool ii_Concurrent; diff --git a/src/include/parser/parsindex ceaff2f..4303acf 100644 *** a/src/include/parser/parse_coerce.h --- b/src/include/parser/parse_coerce.h *************** *** 30,37 **** typedef enum CoercionPathType --- 30,44 ---- COERCION_PATH_COERCEVIAIO /* need a CoerceViaIO node */ } CoercionPathType; + /* Bits in the return value of GetCoerceExemptions. */ + typedef int CoerceExemptions; + + #define COERCE_EXEMPT_NOCHANGE 0x1 /* expression never changes storage */ + #define COERCE_EXEMPT_NOERROR 0x2 /* expression never throws an error */ extern bool IsBinaryCoercible(Oid srctype, Oid targettype); + extern CoerceExemptions GetCoerceExemptions(Node *expr, + Index varno, AttrNumber varattno); extern bool IsPreferredType(TYPCATEGORY category, Oid type); extern TYPCATEGORY TypeCategory(Oid type); diff --git a/src/test/regress/GNUmakefilindex 15b9ec4..c33ecb9 100644 diff --git a/src/test/regress/expecindex 3280065..7a5b96e 100644 *** a/src/test/regress/expected/alter_table.out --- b/src/test/regress/expected/alter_table.out *************** *** 1482,1487 **** create table tab2 (x int, y tab1); --- 1482,1630 ---- alter table tab1 alter column b type varchar; -- fails ERROR: cannot alter table "tab1" because column "tab2"."y" uses its rowtype -- + -- ALTER COLUMN ... SET DATA TYPE optimizations + -- + SET client_min_messages = debug1; -- Track rewrites. + -- Model a type change that throws the semantics of dependent expressions. + CREATE DOMAIN trickint AS int; + CREATE FUNCTION touchy_f(trickint) RETURNS int4 LANGUAGE sql AS 'SELECT 100'; + CREATE FUNCTION touchy_f(int4) RETURNS int4 LANGUAGE sql AS 'SELECT $1'; + CREATE DOMAIN lendom AS varchar(8); + CREATE DOMAIN checkdom AS text CHECK (VALUE LIKE '<%'); + CREATE TABLE parent (keycol numeric PRIMARY KEY); + DEBUG: building TOAST index for table "parent" + NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "parent_pkey" for table "parent" + DEBUG: building index "parent_pkey" on table "parent" + INSERT INTO parent VALUES (0.12), (1.12); + CREATE TABLE t ( + integral int4 NOT NULL, + rational numeric(9,4) UNIQUE NOT NULL REFERENCES parent, + string varchar(4) NOT NULL, + strarr varchar(2)[] NOT NULL, + CHECK (touchy_f(integral) < 10), + EXCLUDE (integral WITH =) + ); + DEBUG: building TOAST index for table "t" + NOTICE: CREATE TABLE / UNIQUE will create implicit index "t_rational_key" for table "t" + DEBUG: building index "t_rational_key" on table "t" + NOTICE: CREATE TABLE / EXCLUDE will create implicit index "t_integral_excl" for table "t" + DEBUG: building index "t_integral_excl" on table "t" + CREATE INDEX ON t USING gin (strarr); + DEBUG: building index "t_strarr_idx" on table "t" + INSERT INTO t VALUES (1, 0.12, '', '{ab,cd}'), (2, 1.12, '', '{ef,gh}'); + -- Comments "rewrite", "verify" and "noop" signify whether ATRewriteTables + -- rewrites, scans or does nothing to the table proper. An "-e" suffix denotes + -- an error outcome. + ALTER TABLE t ALTER integral TYPE trickint; -- verify-e + DEBUG: building index "t_integral_excl" on table "t" + DEBUG: verifying table "t" + ERROR: check constraint "t_integral_check" is violated by some row + ALTER TABLE t DROP CONSTRAINT t_integral_check; + ALTER TABLE t ALTER integral TYPE abstime USING integral::abstime; -- noop + DEBUG: building index "t_integral_excl" on table "t" + ALTER TABLE t ALTER integral TYPE oid USING integral::int4; -- noop + DEBUG: building index "t_integral_excl" on table "t" + ALTER TABLE t ALTER integral TYPE regtype; -- noop + DEBUG: building index "t_integral_excl" on table "t" + ALTER TABLE t ALTER rational TYPE numeric(7,4); -- verify + DEBUG: building TOAST index for table "t" + DEBUG: rewriting table "t" + DEBUG: building index "t_strarr_idx" on table "t" + DEBUG: building index "t_integral_excl" on table "t" + DEBUG: building index "t_rational_key" on table "t" + DEBUG: validating foreign key constraint "t_rational_fkey" + ALTER TABLE t ALTER rational TYPE numeric(8,4); -- noop + DEBUG: building TOAST index for table "t" + DEBUG: rewriting table "t" + DEBUG: building index "t_strarr_idx" on table "t" + DEBUG: building index "t_integral_excl" on table "t" + DEBUG: building index "t_rational_key" on table "t" + DEBUG: validating foreign key constraint "t_rational_fkey" + ALTER TABLE t ALTER rational TYPE numeric(8,1); -- rewrite-e + DEBUG: building TOAST index for table "t" + DEBUG: rewriting table "t" + DEBUG: building index "t_strarr_idx" on table "t" + DEBUG: building index "t_integral_excl" on table "t" + DEBUG: building index "t_rational_key" on table "t" + DEBUG: validating foreign key constraint "t_rational_fkey" + ERROR: insert or update on table "t" violates foreign key constraint "t_rational_fkey" + DETAIL: Key (rational)=(0.1) is not present in table "parent". + ALTER TABLE t ALTER string TYPE varchar(6); -- noop + DEBUG: building TOAST index for table "t" + DEBUG: rewriting table "t" + DEBUG: building index "t_strarr_idx" on table "t" + DEBUG: building index "t_integral_excl" on table "t" + DEBUG: building index "t_rational_key" on table "t" + ALTER TABLE t ALTER string TYPE lendom; -- noop + DEBUG: building TOAST index for table "t" + DEBUG: rewriting table "t" + DEBUG: building index "t_strarr_idx" on table "t" + DEBUG: building index "t_integral_excl" on table "t" + DEBUG: building index "t_rational_key" on table "t" + ALTER TABLE t ALTER string TYPE xml USING string::xml; -- verify + DEBUG: building TOAST index for table "t" + DEBUG: rewriting table "t" + DEBUG: building index "t_strarr_idx" on table "t" + DEBUG: building index "t_integral_excl" on table "t" + DEBUG: building index "t_rational_key" on table "t" + ALTER TABLE t ALTER string TYPE checkdom; -- verify + DEBUG: verifying table "t" + ALTER TABLE t ALTER string TYPE text USING 'foo'::varchar; -- rewrite + DEBUG: building TOAST index for table "t" + DEBUG: rewriting table "t" + DEBUG: building index "t_strarr_idx" on table "t" + DEBUG: building index "t_integral_excl" on table "t" + DEBUG: building index "t_rational_key" on table "t" + ALTER TABLE t ALTER strarr TYPE varchar(4)[]; -- noop + DEBUG: building TOAST index for table "t" + DEBUG: rewriting table "t" + DEBUG: building index "t_integral_excl" on table "t" + DEBUG: building index "t_rational_key" on table "t" + DEBUG: building index "t_strarr_idx" on table "t" + ALTER TABLE t ADD CONSTRAINT u0 UNIQUE (integral), -- build index exactly once + ALTER integral TYPE int8; -- rewrite + NOTICE: ALTER TABLE / ADD UNIQUE will create implicit index "u0" for table "t" + DEBUG: building TOAST index for table "t" + DEBUG: rewriting table "t" + DEBUG: building index "t_rational_key" on table "t" + DEBUG: building index "t_strarr_idx" on table "t" + DEBUG: building index "t_integral_excl" on table "t" + DEBUG: building index "u0" on table "t" + -- Data and catalog end state. We omit the columns that bear unstable OIDs. + SELECT * FROM t ORDER BY 1; + integral | rational | string | strarr + ----------+----------+--------+--------- + 1 | 0.1200 | foo | {ab,cd} + 2 | 1.1200 | foo | {ef,gh} + (2 rows) + + SELECT relname, indclass FROM pg_index JOIN pg_class c ON c.oid = indexrelid + WHERE indrelid = 't'::regclass ORDER BY 1; + relname | indclass + -----------------+---------- + t_integral_excl | 10029 + t_rational_key | 10037 + t_strarr_idx | 10103 + u0 | 10029 + (4 rows) + + SELECT relname, attname, atttypid, atttypmod + FROM pg_attribute JOIN pg_class c ON c.oid = attrelid + WHERE attnum > 0 AND + attrelid IN (SELECT indexrelid FROM pg_index WHERE indrelid = 't'::regclass) + ORDER BY 1, 2; + relname | attname | atttypid | atttypmod + -----------------+----------+----------+----------- + t_integral_excl | integral | 20 | -1 + t_rational_key | rational | 1700 | 524296 + t_strarr_idx | strarr | 1043 | -1 + u0 | integral | 20 | -1 + (4 rows) + + -- Done. Retain the table under a less-generic name. + ALTER TABLE t RENAME TO alter_type_test; + RESET client_min_messages; + -- -- lock levels -- drop type lockmodes; diff --git a/src/test/regress/expected/big_alternew file mode 100644 index 0000000..1609c01 diff --git a/src/test/regress/sql/alter_table.sql b/index cfbfbb6..2525fe0 100644 *** a/src/test/regress/sql/alter_table.sql --- b/src/test/regress/sql/alter_table.sql *************** *** 1098,1103 **** create table tab2 (x int, y tab1); --- 1098,1165 ---- alter table tab1 alter column b type varchar; -- fails -- + -- ALTER COLUMN ... SET DATA TYPE optimizations + -- + SET client_min_messages = debug1; -- Track rewrites. + + -- Model a type change that throws the semantics of dependent expressions. + CREATE DOMAIN trickint AS int; + CREATE FUNCTION touchy_f(trickint) RETURNS int4 LANGUAGE sql AS 'SELECT 100'; + CREATE FUNCTION touchy_f(int4) RETURNS int4 LANGUAGE sql AS 'SELECT $1'; + CREATE DOMAIN lendom AS varchar(8); + CREATE DOMAIN checkdom AS text CHECK (VALUE LIKE '<%'); + + CREATE TABLE parent (keycol numeric PRIMARY KEY); + INSERT INTO parent VALUES (0.12), (1.12); + + CREATE TABLE t ( + integral int4 NOT NULL, + rational numeric(9,4) UNIQUE NOT NULL REFERENCES parent, + string varchar(4) NOT NULL, + strarr varchar(2)[] NOT NULL, + CHECK (touchy_f(integral) < 10), + EXCLUDE (integral WITH =) + ); + CREATE INDEX ON t USING gin (strarr); + INSERT INTO t VALUES (1, 0.12, '', '{ab,cd}'), (2, 1.12, '', '{ef,gh}'); + + -- Comments "rewrite", "verify" and "noop" signify whether ATRewriteTables + -- rewrites, scans or does nothing to the table proper. An "-e" suffix denotes + -- an error outcome. + ALTER TABLE t ALTER integral TYPE trickint; -- verify-e + ALTER TABLE t DROP CONSTRAINT t_integral_check; + ALTER TABLE t ALTER integral TYPE abstime USING integral::abstime; -- noop + ALTER TABLE t ALTER integral TYPE oid USING integral::int4; -- noop + ALTER TABLE t ALTER integral TYPE regtype; -- noop + ALTER TABLE t ALTER rational TYPE numeric(7,4); -- verify + ALTER TABLE t ALTER rational TYPE numeric(8,4); -- noop + ALTER TABLE t ALTER rational TYPE numeric(8,1); -- rewrite-e + ALTER TABLE t ALTER string TYPE varchar(6); -- noop + ALTER TABLE t ALTER string TYPE lendom; -- noop + ALTER TABLE t ALTER string TYPE xml USING string::xml; -- verify + ALTER TABLE t ALTER string TYPE checkdom; -- verify + ALTER TABLE t ALTER string TYPE text USING 'foo'::varchar; -- rewrite + ALTER TABLE t ALTER strarr TYPE varchar(4)[]; -- noop + ALTER TABLE t ADD CONSTRAINT u0 UNIQUE (integral), -- build index exactly once + ALTER integral TYPE int8; -- rewrite + + -- Data and catalog end state. We omit the columns that bear unstable OIDs. + SELECT * FROM t ORDER BY 1; + + SELECT relname, indclass FROM pg_index JOIN pg_class c ON c.oid = indexrelid + WHERE indrelid = 't'::regclass ORDER BY 1; + + SELECT relname, attname, atttypid, atttypmod + FROM pg_attribute JOIN pg_class c ON c.oid = attrelid + WHERE attnum > 0 AND + attrelid IN (SELECT indexrelid FROM pg_index WHERE indrelid = 't'::regclass) + ORDER BY 1, 2; + + -- Done. Retain the table under a less-generic name. + ALTER TABLE t RENAME TO alter_type_test; + RESET client_min_messages; + + -- -- lock levels -- drop type lockmodes; diff --git a/src/test/regress/sql/big_alternew file mode 100644 index 0000000..3824d96