Implement ALTER TABLE .. SET LOGGED / UNLOGGED

This enables changing permanent (logged) tables to unlogged and
vice-versa.

(Docs for ALTER TABLE / SET TABLESPACE got shuffled in an order that
hopefully makes more sense than the original.)

Author: Fabrízio de Royes Mello
Reviewed by: Christoph Berg, Andres Freund, Thom Brown
Some tweaking by Álvaro Herrera
This commit is contained in:
Alvaro Herrera 2014-08-22 14:27:00 -04:00
parent 01d15a2677
commit f41872d0c1
11 changed files with 472 additions and 55 deletions

View File

@ -63,6 +63,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
SET WITHOUT CLUSTER
SET WITH OIDS
SET WITHOUT OIDS
SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
SET {LOGGED | UNLOGGED}
SET ( <replaceable class="PARAMETER">storage_parameter</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
RESET ( <replaceable class="PARAMETER">storage_parameter</replaceable> [, ... ] )
INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
@ -70,7 +72,6 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
OF <replaceable class="PARAMETER">type_name</replaceable>
NOT OF
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
REPLICA IDENTITY {DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING}
<phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
@ -478,6 +479,42 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>SET TABLESPACE</literal></term>
<listitem>
<para>
This form changes the table's tablespace to the specified tablespace and
moves the data file(s) associated with the table to the new tablespace.
Indexes on the table, if any, are not moved; but they can be moved
separately with additional <literal>SET TABLESPACE</literal> commands.
All tables in the current database in a tablespace can be moved by using
the <literal>ALL IN TABLESPACE</literal> form, which will lock all tables
to be moved first and then move each one. This form also supports
<literal>OWNED BY</literal>, which will only move tables owned by the
roles specified. If the <literal>NOWAIT</literal> option is specified
then the command will fail if it is unable to acquire all of the locks
required immediately. Note that system catalogs are not moved by this
command, use <command>ALTER DATABASE</command> or explicit
<command>ALTER TABLE</command> invocations instead if desired. The
<literal>information_schema</literal> relations are not considered part
of the system catalogs and will be moved.
See also
<xref linkend="SQL-CREATETABLESPACE">.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>SET {LOGGED | UNLOGGED}</literal></term>
<listitem>
<para>
This form changes the table from unlogged to logged or vice-versa
(see <xref linkend="SQL-CREATETABLE-UNLOGGED">). It cannot be applied
to a temporary table.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>SET ( <replaceable class="PARAMETER">storage_parameter</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )</literal></term>
<listitem>
@ -591,31 +628,6 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>SET TABLESPACE</literal></term>
<listitem>
<para>
This form changes the table's tablespace to the specified tablespace and
moves the data file(s) associated with the table to the new tablespace.
Indexes on the table, if any, are not moved; but they can be moved
separately with additional <literal>SET TABLESPACE</literal> commands.
All tables in the current database in a tablespace can be moved by using
the <literal>ALL IN TABLESPACE</literal> form, which will lock all tables
to be moved first and then move each one. This form also supports
<literal>OWNED BY</literal>, which will only move tables owned by the
roles specified. If the <literal>NOWAIT</literal> option is specified
then the command will fail if it is unable to acquire all of the locks
required immediately. Note that system catalogs are not moved by this
command, use <command>ALTER DATABASE</command> or explicit
<command>ALTER TABLE</command> invocations instead if desired. The
<literal>information_schema</literal> relations are not considered part
of the system catalogs and will be moved.
See also
<xref linkend="SQL-CREATETABLESPACE">.
</para>
</listitem>
</varlistentry>
<varlistentry id="SQL-CREATETABLE-REPLICA-IDENTITY">
<term><literal>REPLICA IDENTITY</literal></term>
<listitem>

View File

@ -574,7 +574,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
heap_close(OldHeap, NoLock);
/* Create the transient table that will receive the re-ordered data */
OIDNewHeap = make_new_heap(tableOid, tableSpace, false,
OIDNewHeap = make_new_heap(tableOid, tableSpace,
OldHeap->rd_rel->relpersistence,
AccessExclusiveLock);
/* Copy the heap data into the new table in the desired order */
@ -595,13 +596,14 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
* Create the transient table that will be filled with new data during
* CLUSTER, ALTER TABLE, and similar operations. The transient table
* duplicates the logical structure of the OldHeap, but is placed in
* NewTableSpace which might be different from OldHeap's.
* NewTableSpace which might be different from OldHeap's. Also, it's built
* with the specified persistence, which might differ from the original's.
*
* After this, the caller should load the new heap with transferred/modified
* data, then call finish_heap_swap to complete the operation.
*/
Oid
make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, bool forcetemp,
make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
LOCKMODE lockmode)
{
TupleDesc OldHeapDesc;
@ -613,7 +615,6 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, bool forcetemp,
Datum reloptions;
bool isNull;
Oid namespaceid;
char relpersistence;
OldHeap = heap_open(OIDOldHeap, lockmode);
OldHeapDesc = RelationGetDescr(OldHeap);
@ -636,16 +637,10 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, bool forcetemp,
if (isNull)
reloptions = (Datum) 0;
if (forcetemp)
{
if (relpersistence == RELPERSISTENCE_TEMP)
namespaceid = LookupCreationNamespace("pg_temp");
relpersistence = RELPERSISTENCE_TEMP;
}
else
{
namespaceid = RelationGetNamespace(OldHeap);
relpersistence = OldHeap->rd_rel->relpersistence;
}
/*
* Create the new heap, using a temporary name in the same namespace as
@ -1109,8 +1104,10 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
/*
* Swap the physical files of two given relations.
*
* We swap the physical identity (reltablespace and relfilenode) while
* keeping the same logical identities of the two relations.
* We swap the physical identity (reltablespace, relfilenode) while keeping the
* same logical identities of the two relations. relpersistence is also
* swapped, which is critical since it determines where buffers live for each
* relation.
*
* We can swap associated TOAST data in either of two ways: recursively swap
* the physical content of the toast tables (and their indexes), or swap the
@ -1146,6 +1143,7 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
Oid relfilenode1,
relfilenode2;
Oid swaptemp;
char swptmpchr;
CatalogIndexState indstate;
/* We need writable copies of both pg_class tuples. */
@ -1166,7 +1164,10 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
if (OidIsValid(relfilenode1) && OidIsValid(relfilenode2))
{
/* Normal non-mapped relations: swap relfilenodes and reltablespaces */
/*
* Normal non-mapped relations: swap relfilenodes, reltablespaces,
* relpersistence
*/
Assert(!target_is_pg_class);
swaptemp = relform1->relfilenode;
@ -1177,6 +1178,10 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
relform1->reltablespace = relform2->reltablespace;
relform2->reltablespace = swaptemp;
swptmpchr = relform1->relpersistence;
relform1->relpersistence = relform2->relpersistence;
relform2->relpersistence = swptmpchr;
/* Also swap toast links, if we're swapping by links */
if (!swap_toast_by_content)
{
@ -1196,15 +1201,18 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
NameStr(relform1->relname));
/*
* We can't change the tablespace of a mapped rel, and we can't handle
* toast link swapping for one either, because we must not apply any
* critical changes to its pg_class row. These cases should be
* prevented by upstream permissions tests, so this check is a
* non-user-facing emergency backstop.
* We can't change the tablespace nor persistence of a mapped rel, and
* we can't handle toast link swapping for one either, because we must
* not apply any critical changes to its pg_class row. These cases
* should be prevented by upstream permissions tests, so these checks
* are non-user-facing emergency backstop.
*/
if (relform1->reltablespace != relform2->reltablespace)
elog(ERROR, "cannot change tablespace of mapped relation \"%s\"",
NameStr(relform1->relname));
if (relform1->relpersistence != relform2->relpersistence)
elog(ERROR, "cannot change persistence of mapped relation \"%s\"",
NameStr(relform1->relname));
if (!swap_toast_by_content &&
(relform1->reltoastrelid || relform2->reltoastrelid))
elog(ERROR, "cannot swap toast by links for mapped relation \"%s\"",

View File

@ -147,6 +147,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
DestReceiver *dest;
bool concurrent;
LOCKMODE lockmode;
char relpersistence;
/* Determine strength of lock needed. */
concurrent = stmt->concurrent;
@ -233,9 +234,15 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
/* Concurrent refresh builds new data in temp tablespace, and does diff. */
if (concurrent)
{
tableSpace = GetDefaultTablespace(RELPERSISTENCE_TEMP);
relpersistence = RELPERSISTENCE_TEMP;
}
else
{
tableSpace = matviewRel->rd_rel->reltablespace;
relpersistence = matviewRel->rd_rel->relpersistence;
}
owner = matviewRel->rd_rel->relowner;
@ -244,7 +251,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
* it against access by any other process until commit (by which time it
* will be gone).
*/
OIDNewHeap = make_new_heap(matviewOid, tableSpace, concurrent,
OIDNewHeap = make_new_heap(matviewOid, tableSpace, relpersistence,
ExclusiveLock);
LockRelationOid(OIDNewHeap, AccessExclusiveLock);
dest = CreateTransientRelDestReceiver(OIDNewHeap);

View File

@ -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);
/*
@ -3643,6 +3698,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
@ -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);
@ -10600,6 +10667,168 @@ ATExecGenericOptions(Relation rel, List *options)
heap_freetuple(tuple);
}
/*
* 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
*/

View File

@ -577,7 +577,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOGGED
MAPPING MATCH MATERIALIZED MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
@ -2114,6 +2114,20 @@ alter_table_cmd:
n->name = NULL;
$$ = (Node *)n;
}
/* ALTER TABLE <name> SET LOGGED */
| SET LOGGED
{
AlterTableCmd *n = makeNode(AlterTableCmd);
n->subtype = AT_SetLogged;
$$ = (Node *)n;
}
/* ALTER TABLE <name> SET UNLOGGED */
| SET UNLOGGED
{
AlterTableCmd *n = makeNode(AlterTableCmd);
n->subtype = AT_SetUnLogged;
$$ = (Node *)n;
}
/* ALTER TABLE <name> ENABLE TRIGGER <trig> */
| ENABLE_P TRIGGER name
{
@ -12963,6 +12977,7 @@ unreserved_keyword:
| LOCAL
| LOCATION
| LOCK_P
| LOGGED
| MAPPING
| MATCH
| MATERIALIZED

View File

@ -1641,12 +1641,12 @@ psql_completion(const char *text, int start, int end)
completion_info_charp = prev3_wd;
COMPLETE_WITH_QUERY(Query_for_index_of_table);
}
/* If we have TABLE <sth> SET, provide WITHOUT,TABLESPACE and SCHEMA */
/* If we have TABLE <sth> SET, provide list of attributes and '(' */
else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
pg_strcasecmp(prev_wd, "SET") == 0)
{
static const char *const list_TABLESET[] =
{"(", "WITHOUT", "TABLESPACE", "SCHEMA", NULL};
{"(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED", "WITH", "WITHOUT", NULL};
COMPLETE_WITH_LIST(list_TABLESET);
}

View File

@ -25,7 +25,7 @@ extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, bool forcetemp,
extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
LOCKMODE lockmode);
extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
bool is_system_catalog,

View File

@ -1307,6 +1307,8 @@ typedef enum AlterTableType
AT_ChangeOwner, /* change owner */
AT_ClusterOn, /* CLUSTER ON */
AT_DropCluster, /* SET WITHOUT CLUSTER */
AT_SetLogged, /* SET LOGGED */
AT_SetUnLogged, /* SET UNLOGGED */
AT_AddOids, /* SET WITH OIDS */
AT_AddOidsRecurse, /* internal to commands/tablecmds.c */
AT_DropOids, /* SET WITHOUT OIDS */

View File

@ -230,6 +230,7 @@ PG_KEYWORD("localtime", LOCALTIME, RESERVED_KEYWORD)
PG_KEYWORD("localtimestamp", LOCALTIMESTAMP, RESERVED_KEYWORD)
PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD)
PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD)
PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD)
PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD)
PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD)
PG_KEYWORD("materialized", MATERIALIZED, UNRESERVED_KEYWORD)

View File

@ -2426,3 +2426,94 @@ TRUNCATE old_system_table;
ALTER TABLE old_system_table DROP CONSTRAINT new_system_table_pkey;
ALTER TABLE old_system_table DROP COLUMN othercol;
DROP TABLE old_system_table;
-- set logged
CREATE UNLOGGED TABLE unlogged1(f1 SERIAL PRIMARY KEY, f2 TEXT);
-- check relpersistence of an unlogged table
SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged1'
UNION ALL
SELECT 'toast table', t.relkind, t.relpersistence FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid WHERE r.relname ~ '^unlogged1'
UNION ALL
SELECT 'toast index', ri.relkind, ri.relpersistence FROM pg_class r join pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid JOIN pg_class ri ON ri.oid = i.indexrelid WHERE r.relname ~ '^unlogged1'
ORDER BY relname;
relname | relkind | relpersistence
------------------+---------+----------------
toast index | i | u
toast table | t | u
unlogged1 | r | u
unlogged1_f1_seq | S | p
unlogged1_pkey | i | u
(5 rows)
CREATE UNLOGGED TABLE unlogged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged1); -- foreign key
CREATE UNLOGGED TABLE unlogged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged3); -- self-referencing foreign key
ALTER TABLE unlogged3 SET LOGGED; -- skip self-referencing foreign key
ALTER TABLE unlogged2 SET LOGGED; -- fails because a foreign key to an unlogged table exists
ERROR: cannot change status of table unlogged2 to logged
DETAIL: Table unlogged2 references unlogged table unlogged1.
ALTER TABLE unlogged1 SET LOGGED;
-- check relpersistence of an unlogged table after changing to permament
SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged1'
UNION ALL
SELECT 'toast table', t.relkind, t.relpersistence FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid WHERE r.relname ~ '^unlogged1'
UNION ALL
SELECT 'toast index', ri.relkind, ri.relpersistence FROM pg_class r join pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid JOIN pg_class ri ON ri.oid = i.indexrelid WHERE r.relname ~ '^unlogged1'
ORDER BY relname;
relname | relkind | relpersistence
------------------+---------+----------------
toast index | i | p
toast table | t | p
unlogged1 | r | p
unlogged1_f1_seq | S | p
unlogged1_pkey | i | p
(5 rows)
ALTER TABLE unlogged1 SET LOGGED; -- silently do nothing
DROP TABLE unlogged3;
DROP TABLE unlogged2;
DROP TABLE unlogged1;
-- set unlogged
CREATE TABLE logged1(f1 SERIAL PRIMARY KEY, f2 TEXT);
-- check relpersistence of a permanent table
SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^logged1'
UNION ALL
SELECT 'toast table', t.relkind, t.relpersistence FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid WHERE r.relname ~ '^logged1'
UNION ALL
SELECT 'toast index', ri.relkind, ri.relpersistence FROM pg_class r join pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid JOIN pg_class ri ON ri.oid = i.indexrelid WHERE r.relname ~ '^logged1'
ORDER BY relname;
relname | relkind | relpersistence
----------------+---------+----------------
logged1 | r | p
logged1_f1_seq | S | p
logged1_pkey | i | p
toast index | i | p
toast table | t | p
(5 rows)
CREATE TABLE logged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged1); -- foreign key
CREATE TABLE logged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged3); -- self-referencing foreign key
ALTER TABLE logged1 SET UNLOGGED; -- fails because a foreign key from a permanent table exists
ERROR: cannot change status of table logged1 to unlogged
DETAIL: Logged table logged2 is referenced by table logged1.
ALTER TABLE logged3 SET UNLOGGED; -- skip self-referencing foreign key
ALTER TABLE logged2 SET UNLOGGED;
ALTER TABLE logged1 SET UNLOGGED;
-- check relpersistence of a permanent table after changing to unlogged
SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^logged1'
UNION ALL
SELECT 'toast table', t.relkind, t.relpersistence FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid WHERE r.relname ~ '^logged1'
UNION ALL
SELECT 'toast index', ri.relkind, ri.relpersistence FROM pg_class r join pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid JOIN pg_class ri ON ri.oid = i.indexrelid WHERE r.relname ~ '^logged1'
ORDER BY relname;
relname | relkind | relpersistence
----------------+---------+----------------
logged1 | r | u
logged1_f1_seq | S | p
logged1_pkey | i | u
toast index | i | u
toast table | t | u
(5 rows)
ALTER TABLE logged1 SET UNLOGGED; -- silently do nothing
DROP TABLE logged3;
DROP TABLE logged2;
DROP TABLE logged1;

View File

@ -1624,3 +1624,55 @@ TRUNCATE old_system_table;
ALTER TABLE old_system_table DROP CONSTRAINT new_system_table_pkey;
ALTER TABLE old_system_table DROP COLUMN othercol;
DROP TABLE old_system_table;
-- set logged
CREATE UNLOGGED TABLE unlogged1(f1 SERIAL PRIMARY KEY, f2 TEXT);
-- check relpersistence of an unlogged table
SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged1'
UNION ALL
SELECT 'toast table', t.relkind, t.relpersistence FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid WHERE r.relname ~ '^unlogged1'
UNION ALL
SELECT 'toast index', ri.relkind, ri.relpersistence FROM pg_class r join pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid JOIN pg_class ri ON ri.oid = i.indexrelid WHERE r.relname ~ '^unlogged1'
ORDER BY relname;
CREATE UNLOGGED TABLE unlogged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged1); -- foreign key
CREATE UNLOGGED TABLE unlogged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged3); -- self-referencing foreign key
ALTER TABLE unlogged3 SET LOGGED; -- skip self-referencing foreign key
ALTER TABLE unlogged2 SET LOGGED; -- fails because a foreign key to an unlogged table exists
ALTER TABLE unlogged1 SET LOGGED;
-- check relpersistence of an unlogged table after changing to permament
SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged1'
UNION ALL
SELECT 'toast table', t.relkind, t.relpersistence FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid WHERE r.relname ~ '^unlogged1'
UNION ALL
SELECT 'toast index', ri.relkind, ri.relpersistence FROM pg_class r join pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid JOIN pg_class ri ON ri.oid = i.indexrelid WHERE r.relname ~ '^unlogged1'
ORDER BY relname;
ALTER TABLE unlogged1 SET LOGGED; -- silently do nothing
DROP TABLE unlogged3;
DROP TABLE unlogged2;
DROP TABLE unlogged1;
-- set unlogged
CREATE TABLE logged1(f1 SERIAL PRIMARY KEY, f2 TEXT);
-- check relpersistence of a permanent table
SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^logged1'
UNION ALL
SELECT 'toast table', t.relkind, t.relpersistence FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid WHERE r.relname ~ '^logged1'
UNION ALL
SELECT 'toast index', ri.relkind, ri.relpersistence FROM pg_class r join pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid JOIN pg_class ri ON ri.oid = i.indexrelid WHERE r.relname ~ '^logged1'
ORDER BY relname;
CREATE TABLE logged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged1); -- foreign key
CREATE TABLE logged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged3); -- self-referencing foreign key
ALTER TABLE logged1 SET UNLOGGED; -- fails because a foreign key from a permanent table exists
ALTER TABLE logged3 SET UNLOGGED; -- skip self-referencing foreign key
ALTER TABLE logged2 SET UNLOGGED;
ALTER TABLE logged1 SET UNLOGGED;
-- check relpersistence of a permanent table after changing to unlogged
SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^logged1'
UNION ALL
SELECT 'toast table', t.relkind, t.relpersistence FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid WHERE r.relname ~ '^logged1'
UNION ALL
SELECT 'toast index', ri.relkind, ri.relpersistence FROM pg_class r join pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid JOIN pg_class ri ON ri.oid = i.indexrelid WHERE r.relname ~ '^logged1'
ORDER BY relname;
ALTER TABLE logged1 SET UNLOGGED; -- silently do nothing
DROP TABLE logged3;
DROP TABLE logged2;
DROP TABLE logged1;