aboutsummaryrefslogtreecommitdiff
path: root/src/backend/commands/tablecmds.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/commands/tablecmds.c')
-rw-r--r--src/backend/commands/tablecmds.c239
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