diff options
Diffstat (limited to 'src/backend/commands/tablecmds.c')
-rw-r--r-- | src/backend/commands/tablecmds.c | 239 |
1 files changed, 234 insertions, 5 deletions
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index feceed7f3ac..d37534ed369 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -152,6 +152,8 @@ typedef struct AlteredTableInfo bool new_notnull; /* T if we added new NOT NULL constraints */ bool rewrite; /* T if a rewrite is forced */ Oid newTableSpace; /* new tablespace; 0 means no change */ + bool chgLoggedness; /* T if SET LOGGED/UNLOGGED is used */ + char newrelpersistence; /* if above is true */ /* Objects to rebuild after completing ALTER TYPE operations */ List *changedConstraintOids; /* OIDs of constraints to rebuild */ List *changedConstraintDefs; /* string definitions of same */ @@ -372,7 +374,8 @@ static void ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, AlterTableCmd *cmd, LOCKMODE lockmode); static void ATExecAlterColumnGenericOptions(Relation rel, const char *colName, List *options, LOCKMODE lockmode); -static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode); +static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, + LOCKMODE lockmode); static void ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, List **wqueue, LOCKMODE lockmode, bool rewrite); @@ -382,8 +385,11 @@ static void change_owner_fix_column_acls(Oid relationOid, Oid oldOwnerId, Oid newOwnerId); static void change_owner_recurse_to_sequences(Oid relationOid, Oid newOwnerId, LOCKMODE lockmode); -static void ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode); +static void ATExecClusterOn(Relation rel, const char *indexName, + LOCKMODE lockmode); static void ATExecDropCluster(Relation rel, LOCKMODE lockmode); +static bool ATPrepChangeLoggedness(Relation rel, bool toLogged); +static void ATChangeIndexesLoggedness(Oid relid, char relpersistence); static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, char *tablespacename, LOCKMODE lockmode); static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode); @@ -2949,6 +2955,11 @@ AlterTableGetLockLevel(List *cmds) cmd_lockmode = ShareUpdateExclusiveLock; break; + case AT_SetLogged: + case AT_SetUnLogged: + cmd_lockmode = AccessExclusiveLock; + break; + case AT_ValidateConstraint: /* Uses MVCC in * getConstraints() */ cmd_lockmode = ShareUpdateExclusiveLock; @@ -3161,6 +3172,24 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* No command-specific prep needed */ pass = AT_PASS_MISC; break; + case AT_SetLogged: /* SET LOGGED */ + ATSimplePermissions(rel, ATT_TABLE); + tab->chgLoggedness = ATPrepChangeLoggedness(rel, true); + tab->newrelpersistence = RELPERSISTENCE_PERMANENT; + /* force rewrite if necessary */ + if (tab->chgLoggedness) + tab->rewrite = true; + pass = AT_PASS_MISC; + break; + case AT_SetUnLogged: /* SET UNLOGGED */ + ATSimplePermissions(rel, ATT_TABLE); + tab->chgLoggedness = ATPrepChangeLoggedness(rel, false); + tab->newrelpersistence = RELPERSISTENCE_UNLOGGED; + /* force rewrite if necessary */ + if (tab->chgLoggedness) + tab->rewrite = true; + pass = AT_PASS_MISC; + break; case AT_AddOids: /* SET WITH OIDS */ ATSimplePermissions(rel, ATT_TABLE); if (!rel->rd_rel->relhasoids || recursing) @@ -3431,6 +3460,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, case AT_DropCluster: /* SET WITHOUT CLUSTER */ ATExecDropCluster(rel, lockmode); break; + case AT_SetLogged: /* SET LOGGED */ + case AT_SetUnLogged: /* SET UNLOGGED */ + break; case AT_AddOids: /* SET WITH OIDS */ /* Use the ADD COLUMN code, unless prep decided to do nothing */ if (cmd->def != NULL) @@ -3584,7 +3616,8 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode) /* * We only need to rewrite the table if at least one column needs to - * be recomputed, or we are adding/removing the OID column. + * be recomputed, we are adding/removing the OID column, or we are + * changing its persistence. */ if (tab->rewrite) { @@ -3592,6 +3625,7 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode) Relation OldHeap; Oid OIDNewHeap; Oid NewTableSpace; + char persistence; OldHeap = heap_open(tab->relid, NoLock); @@ -3630,10 +3664,31 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode) else NewTableSpace = OldHeap->rd_rel->reltablespace; + /* + * Select persistence of transient table (same as original unless + * user requested a change) + */ + persistence = tab->chgLoggedness ? + tab->newrelpersistence : OldHeap->rd_rel->relpersistence; + heap_close(OldHeap, NoLock); - /* Create transient table that will receive the modified data */ - OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, false, + /* + * Create transient table that will receive the modified data. + * + * Ensure it is marked correctly as logged or unlogged. We have + * to do this here so that buffers for the new relfilenode will + * have the right persistence set, and at the same time ensure + * that the original filenode's buffers will get read in with the + * correct setting (i.e. the original one). Otherwise a rollback + * after the rewrite would possibly result with buffers for the + * original filenode having the wrong persistence setting. + * + * NB: This relies on swap_relation_files() also swapping the + * persistence. That wouldn't work for pg_class, but that can't be + * unlogged anyway. + */ + OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, persistence, lockmode); /* @@ -3644,6 +3699,16 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode) ATRewriteTable(tab, OIDNewHeap, lockmode); /* + * Change the persistence marking of indexes, if necessary. This + * is so that the new copies are built with the right persistence + * in the reindex step below. Note we cannot do this earlier, + * because the rewrite step might read the indexes, and that would + * cause buffers for them to have the wrong setting. + */ + if (tab->chgLoggedness) + ATChangeIndexesLoggedness(tab->relid, tab->newrelpersistence); + + /* * 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 @@ -4053,6 +4118,8 @@ ATGetQueueEntry(List **wqueue, Relation rel) tab->relid = relid; tab->relkind = rel->rd_rel->relkind; tab->oldDesc = CreateTupleDescCopy(RelationGetDescr(rel)); + tab->newrelpersistence = RELPERSISTENCE_PERMANENT; + tab->chgLoggedness = false; *wqueue = lappend(*wqueue, tab); @@ -10601,6 +10668,168 @@ ATExecGenericOptions(Relation rel, List *options) } /* + * Preparation phase for SET LOGGED/UNLOGGED + * + * This verifies that we're not trying to change a temp table. Also, + * existing foreign key constraints are checked to avoid ending up with + * permanent tables referencing unlogged tables. + * + * Return value is false if the operation is a no-op (in which case the + * checks are skipped), otherwise true. + */ +static bool +ATPrepChangeLoggedness(Relation rel, bool toLogged) +{ + Relation pg_constraint; + HeapTuple tuple; + SysScanDesc scan; + ScanKeyData skey[1]; + + /* + * Disallow changing status for a temp table. Also verify whether we can + * get away with doing nothing; in such cases we don't need to run the + * checks below, either. + */ + switch (rel->rd_rel->relpersistence) + { + case RELPERSISTENCE_TEMP: + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot change logged status of table %s", + RelationGetRelationName(rel)), + errdetail("Table %s is temporary.", + RelationGetRelationName(rel)), + errtable(rel))); + break; + case RELPERSISTENCE_PERMANENT: + if (toLogged) + /* nothing to do */ + return false; + break; + case RELPERSISTENCE_UNLOGGED: + if (!toLogged) + /* nothing to do */ + return false; + break; + } + + /* + * Check existing foreign key constraints to preserve the invariant that + * no permanent tables cannot reference unlogged ones. Self-referencing + * foreign keys can safely be ignored. + */ + pg_constraint = heap_open(ConstraintRelationId, AccessShareLock); + + /* + * Scan conrelid if changing to permanent, else confrelid. This also + * determines whether an useful index exists. + */ + ScanKeyInit(&skey[0], + toLogged ? Anum_pg_constraint_conrelid : + Anum_pg_constraint_confrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(rel))); + scan = systable_beginscan(pg_constraint, + toLogged ? ConstraintRelidIndexId : InvalidOid, + true, NULL, 1, skey); + + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple); + + if (con->contype == CONSTRAINT_FOREIGN) + { + Oid foreignrelid; + Relation foreignrel; + + /* the opposite end of what we used as scankey */ + foreignrelid = toLogged ? con->confrelid : con->conrelid; + + /* ignore if self-referencing */ + if (RelationGetRelid(rel) == foreignrelid) + continue; + + foreignrel = relation_open(foreignrelid, AccessShareLock); + + if (toLogged) + { + if (foreignrel->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot change status of table %s to logged", + RelationGetRelationName(rel)), + errdetail("Table %s references unlogged table %s.", + RelationGetRelationName(rel), + RelationGetRelationName(foreignrel)), + errtableconstraint(rel, NameStr(con->conname)))); + } + else + { + if (foreignrel->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot change status of table %s to unlogged", + RelationGetRelationName(rel)), + errdetail("Logged table %s is referenced by table %s.", + RelationGetRelationName(foreignrel), + RelationGetRelationName(rel)), + errtableconstraint(rel, NameStr(con->conname)))); + } + + relation_close(foreignrel, AccessShareLock); + } + } + + systable_endscan(scan); + + heap_close(pg_constraint, AccessShareLock); + + return true; +} + +/* + * Update the pg_class entry of each index for the given relation to the + * given persistence. + */ +static void +ATChangeIndexesLoggedness(Oid relid, char relpersistence) +{ + Relation rel; + Relation pg_class; + List *indexes; + ListCell *cell; + + pg_class = heap_open(RelationRelationId, RowExclusiveLock); + + /* We already have a lock on the table */ + rel = relation_open(relid, NoLock); + indexes = RelationGetIndexList(rel); + foreach(cell, indexes) + { + Oid indexid = lfirst_oid(cell); + HeapTuple tuple; + Form_pg_class pg_class_form; + + tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(indexid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", + indexid); + + pg_class_form = (Form_pg_class) GETSTRUCT(tuple); + pg_class_form->relpersistence = relpersistence; + simple_heap_update(pg_class, &tuple->t_self, tuple); + + /* keep catalog indexes current */ + CatalogUpdateIndexes(pg_class, tuple); + + heap_freetuple(tuple); + } + + heap_close(pg_class, RowExclusiveLock); + heap_close(rel, NoLock); +} + +/* * Execute ALTER TABLE SET SCHEMA */ Oid |