[GSoC2014] Patch ALTER TABLE ... SET LOGGED
Hi all,
As part of GSoC2014 I'm sending a patch to add the capability of change an
unlogged table to logged [1]https://wiki.postgresql.org/wiki/Allow_an_unlogged_table_to_be_changed_to_logged_GSoC_2014.
I'll add it to the 9.5CF1.
Regards,
[1]: https://wiki.postgresql.org/wiki/Allow_an_unlogged_table_to_be_changed_to_logged_GSoC_2014
https://wiki.postgresql.org/wiki/Allow_an_unlogged_table_to_be_changed_to_logged_GSoC_2014
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog sobre TI: http://fabriziomello.blogspot.com
Perfil Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Attachments:
gsoc2014_alter_table_set_logged_v1.patchtext/x-diff; charset=US-ASCII; name=gsoc2014_alter_table_set_logged_v1.patchDownload
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 69a1e14..f822375 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -58,6 +58,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
ENABLE REPLICA RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable>
ENABLE ALWAYS RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable>
CLUSTER ON <replaceable class="PARAMETER">index_name</replaceable>
+ SET LOGGED
SET WITHOUT CLUSTER
SET WITH OIDS
SET WITHOUT OIDS
@@ -432,6 +433,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
</varlistentry>
<varlistentry>
+ <term><literal>SET LOGGED</literal></term>
+ <listitem>
+ <para>
+ This form change the table persistence type from unlogged to permanent by
+ rewriting the entire contents of the table and associated indexes into
+ new disk files.
+ </para>
+ <para>
+ Changing the table persistence type acquires an <literal>ACCESS EXCLUSIVE</literal> lock.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>SET WITHOUT CLUSTER</literal></term>
<listitem>
<para>
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 341262b..f378f6a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -136,7 +136,8 @@ static List *on_commits = NIL;
#define AT_PASS_ADD_INDEX 6 /* ADD indexes */
#define AT_PASS_ADD_CONSTR 7 /* ADD constraints, defaults */
#define AT_PASS_MISC 8 /* other stuff */
-#define AT_NUM_PASSES 9
+#define AT_PASS_SET_LOGGED 9 /* SET LOGGED */
+#define AT_NUM_PASSES 10
typedef struct AlteredTableInfo
{
@@ -376,6 +377,7 @@ static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMOD
static void ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId,
char *cmd, List **wqueue, LOCKMODE lockmode,
bool rewrite);
+static void ATPostAlterSetLogged(Oid relid);
static void TryReuseIndex(Oid oldId, IndexStmt *stmt);
static void TryReuseForeignKey(Oid oldId, Constraint *con);
static void change_owner_fix_column_acls(Oid relationOid,
@@ -402,6 +404,8 @@ static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockm
static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode);
static void ATExecGenericOptions(Relation rel, List *options);
+static void ATPrepSetLogged(Relation rel);
+static void ATExecSetLogged(Relation rel);
static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
ForkNumber forkNum, char relpersistence);
@@ -2855,6 +2859,7 @@ AlterTableGetLockLevel(List *cmds)
case AT_AddIndexConstraint:
case AT_ReplicaIdentity:
case AT_SetNotNull:
+ case AT_SetLogged:
cmd_lockmode = AccessExclusiveLock;
break;
@@ -3246,6 +3251,11 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_SetLogged:
+ ATSimplePermissions(rel, ATT_TABLE); /* SET LOGGED */
+ ATPrepSetLogged(rel);
+ pass = AT_PASS_SET_LOGGED;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3308,6 +3318,9 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
ATPostAlterTypeCleanup(wqueue, tab, lockmode);
relation_close(rel, NoLock);
+
+ if (pass == AT_PASS_SET_LOGGED)
+ ATPostAlterSetLogged(RelationGetRelid(rel));
}
}
@@ -3527,6 +3540,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_GenericOptions:
ATExecGenericOptions(rel, (List *) cmd->def);
break;
+ case AT_SetLogged:
+ ATExecSetLogged(rel);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -10420,6 +10436,175 @@ ATExecGenericOptions(Relation rel, List *options)
}
/*
+ * ALTER TABLE <name> SET LOGGED
+ *
+ * Change the table persistence type from unlogged to permanent by
+ * rewriting the entire contents of the table and associated indexes
+ * into new disk files.
+ *
+ * The ATPrepSetLogged function check all precondictions to perform
+ * the operation:
+ * - check if the target table is unlogged
+ * - check if not exists a foreign key to other unlogged table
+ */
+static void
+ATPrepSetLogged(Relation rel)
+{
+ Relation pg_constraint;
+ HeapTuple tuple;
+ SysScanDesc scan;
+ ScanKeyData skey[1];
+
+ /* check if is an unlogged relation */
+ if (rel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s is not unlogged",
+ RelationGetRelationName(rel))));
+
+ /*
+ * Fetch the constraint tuple from pg_constraint.
+ */
+ pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
+
+ ScanKeyInit(&skey[0],
+ Anum_pg_constraint_conrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+
+ scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true,
+ NULL, 1, skey);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
+ if (con->contype == CONSTRAINT_FOREIGN)
+ {
+ Relation relfk = relation_open(con->confrelid, AccessShareLock);
+
+ if (RelationGetRelid(rel) != con->confrelid &&
+ relfk->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s references unlogged table %s",
+ RelationGetRelationName(rel),
+ RelationGetRelationName(relfk))));
+
+ relation_close(relfk, AccessShareLock);
+ }
+ }
+
+ systable_endscan(scan);
+
+ heap_close(pg_constraint, AccessShareLock);
+}
+
+/*
+ * ALTER TABLE <name> SET LOGGED
+ *
+ * The ATUpdateToLogged function perform the catalog changes,
+ * i.e. update pg_class.relpersistence to 'p'
+ */
+static void
+ATUpdateToLogged(Relation rel, Relation relrelation)
+{
+ HeapTuple tuple;
+ Form_pg_class pg_class_form;
+
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u",
+ RelationGetRelid(rel));
+
+ pg_class_form = (Form_pg_class) GETSTRUCT(tuple);
+
+ Assert(pg_class_form->relpersistence == RELPERSISTENCE_UNLOGGED);
+
+ pg_class_form->relpersistence = RELPERSISTENCE_PERMANENT;
+
+ simple_heap_update(relrelation, &tuple->t_self, tuple);
+
+ /* keep catalog indexes current */
+ CatalogUpdateIndexes(relrelation, tuple);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * ALTER TABLE <name> SET LOGGED
+ *
+ * The ATExecSetLogged function contains the main logic of the operation,
+ * changing the catalog for main heap, toast and indexes
+ */
+static void
+ATExecSetLogged(Relation rel)
+{
+ Oid relid;
+ Relation indexRelation;
+ ScanKeyData skey;
+ SysScanDesc scan;
+ HeapTuple indexTuple;
+ Relation relrel;
+
+ relid = RelationGetRelid(rel);
+
+ /* open pg_class to update relpersistence */
+ relrel = heap_open(RelationRelationId, RowExclusiveLock);
+
+ /* main heap */
+ ATUpdateToLogged(rel, relrel);
+
+ /* toast heap, if any */
+ if (OidIsValid(rel->rd_rel->reltoastrelid))
+ {
+ Relation toastrel;
+
+ toastrel = heap_open(rel->rd_rel->reltoastrelid, AccessShareLock);
+ ATUpdateToLogged(toastrel, relrel);
+ heap_close(toastrel, AccessShareLock);
+ }
+
+ /* Prepare to scan pg_index for entries having indrelid = this rel. */
+ indexRelation = heap_open(IndexRelationId, AccessShareLock);
+ ScanKeyInit(&skey,
+ Anum_pg_index_indrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ relid);
+
+ scan = systable_beginscan(indexRelation, IndexIndrelidIndexId, true,
+ NULL, 1, &skey);
+
+ while (HeapTupleIsValid(indexTuple = systable_getnext(scan)))
+ {
+ Form_pg_index index = (Form_pg_index) GETSTRUCT(indexTuple);
+ Relation indxrel = index_open(index->indexrelid, AccessShareLock);
+
+ ATUpdateToLogged(indxrel, relrel);
+
+ index_close(indxrel, AccessShareLock);
+ }
+
+ systable_endscan(scan);
+ heap_close(indexRelation, AccessShareLock);
+
+ heap_close(relrel, RowExclusiveLock);
+}
+
+/*
+ * ALTER TABLE <name> SET LOGGED
+ *
+ * The ATPostAlterSetLogged function is called after all to
+ * guarantee that heap is closed to perform the cluster_rel
+ */
+static void
+ATPostAlterSetLogged(Oid relid)
+{
+ /* rebuild the relation using CLUSTER algorithm */
+ cluster_rel(relid, InvalidOid, false, false);
+}
+
+/*
* Execute ALTER TABLE SET SCHEMA
*/
Oid
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7b9895d..7b8c2f5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -563,7 +563,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P LC_COLLATE_P LC_CTYPE_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
@@ -2198,6 +2198,13 @@ alter_table_cmd:
n->def = $3;
$$ = (Node *)n;
}
+ /* ALTER TABLE <name> SET LOGGED */
+ | SET LOGGED
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_SetLogged;
+ $$ = (Node *)n;
+ }
| alter_generic_options
{
AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -12921,6 +12928,7 @@ unreserved_keyword:
| LOCAL
| LOCATION
| LOCK_P
+ | LOGGED
| MAPPING
| MATCH
| MATERIALIZED
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 3bb727f..ac4ff60 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1575,7 +1575,7 @@ psql_completion(const char *text, int start, int end)
pg_strcasecmp(prev_wd, "SET") == 0)
{
static const char *const list_TABLESET[] =
- {"(", "WITHOUT", "TABLESPACE", "SCHEMA", NULL};
+ {"(", "WITHOUT", "TABLESPACE", "SCHEMA", "LOGGED", NULL};
COMPLETE_WITH_LIST(list_TABLESET);
}
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7e560a1..a97a0f8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1317,7 +1317,8 @@ typedef enum AlterTableType
AT_AddOf, /* OF <type_name> */
AT_DropOf, /* NOT OF */
AT_ReplicaIdentity, /* REPLICA IDENTITY */
- AT_GenericOptions /* OPTIONS (...) */
+ AT_GenericOptions, /* OPTIONS (...) */
+ AT_SetLogged /* SET LOGGED */
} AlterTableType;
typedef struct ReplicaIdentityStmt
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 61fae22..b62ce0e 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -231,6 +231,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)
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index a182176..696295e 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2421,3 +2421,29 @@ 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);
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+------------------+----------------+----------------------
+ unlogged1 | u | t
+ unlogged1_f1_seq | p | t
+ unlogged1_pkey | u | t
+(3 rows)
+
+CREATE UNLOGGED TABLE unlogged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged1); -- fk reference
+CREATE UNLOGGED TABLE unlogged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged3); -- self fk reference
+ALTER TABLE unlogged3 SET LOGGED;
+ALTER TABLE unlogged2 SET LOGGED;
+ERROR: table unlogged2 references unlogged table unlogged1
+ALTER TABLE unlogged1 SET LOGGED;
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+------------------+----------------+----------------------
+ unlogged1 | p | f
+ unlogged1_f1_seq | p | t
+ unlogged1_pkey | p | f
+(3 rows)
+
+ALTER TABLE unlogged1 SET LOGGED;
+ERROR: table unlogged1 is not unlogged
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 3f641f9..1294fa0 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1618,3 +1618,14 @@ 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);
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+CREATE UNLOGGED TABLE unlogged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged1); -- fk reference
+CREATE UNLOGGED TABLE unlogged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged3); -- self fk reference
+ALTER TABLE unlogged3 SET LOGGED;
+ALTER TABLE unlogged2 SET LOGGED;
+ALTER TABLE unlogged1 SET LOGGED;
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+ALTER TABLE unlogged1 SET LOGGED;
On Wed, Jun 11, 2014 at 1:19 PM, Fabrízio de Royes Mello <
fabriziomello@gmail.com> wrote:
Hi all,
As part of GSoC2014 I'm sending a patch to add the capability of change an
unlogged table to logged [1].
Hi all,
As part of GSoC2014 and with agreement of my mentor and reviewer (Stephen
Frost) I've send a complement of the first patch to add the capability to
change a regular table to unlogged.
With this patch we finish the main goals of the GSoC2014 and now we'll work
in the additional goals.
Regards,
[1]: https://wiki.postgresql.org/wiki/Allow_an_unlogged_table_to_be_changed_to_logged_GSoC_2014
https://wiki.postgresql.org/wiki/Allow_an_unlogged_table_to_be_changed_to_logged_GSoC_2014
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog sobre TI: http://fabriziomello.blogspot.com
Perfil Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Attachments:
gsoc2014_alter_table_set_logged_v2.patchtext/x-diff; charset=US-ASCII; name=gsoc2014_alter_table_set_logged_v2.patchDownload
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 69a1e14..424f2e9 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -58,6 +58,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
ENABLE REPLICA RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable>
ENABLE ALWAYS RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable>
CLUSTER ON <replaceable class="PARAMETER">index_name</replaceable>
+ SET {LOGGED | UNLOGGED}
SET WITHOUT CLUSTER
SET WITH OIDS
SET WITHOUT OIDS
@@ -432,6 +433,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
</varlistentry>
<varlistentry>
+ <term><literal>SET {LOGGED | UNLOGGED}</literal></term>
+ <listitem>
+ <para>
+ This form change the table persistence type from unlogged to permanent or
+ from unlogged to permanent by rewriting the entire contents of the table
+ and associated indexes into new disk files.
+ </para>
+ <para>
+ Changing the table persistence type acquires an <literal>ACCESS EXCLUSIVE</literal> lock.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>SET WITHOUT CLUSTER</literal></term>
<listitem>
<para>
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 60d387a..9dfdca2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -125,18 +125,19 @@ static List *on_commits = NIL;
* a pass determined by subcommand type.
*/
-#define AT_PASS_UNSET -1 /* UNSET will cause ERROR */
-#define AT_PASS_DROP 0 /* DROP (all flavors) */
-#define AT_PASS_ALTER_TYPE 1 /* ALTER COLUMN TYPE */
-#define AT_PASS_OLD_INDEX 2 /* re-add existing indexes */
-#define AT_PASS_OLD_CONSTR 3 /* re-add existing constraints */
-#define AT_PASS_COL_ATTRS 4 /* set other column attributes */
+#define AT_PASS_UNSET -1 /* UNSET will cause ERROR */
+#define AT_PASS_DROP 0 /* DROP (all flavors) */
+#define AT_PASS_ALTER_TYPE 1 /* ALTER COLUMN TYPE */
+#define AT_PASS_OLD_INDEX 2 /* re-add existing indexes */
+#define AT_PASS_OLD_CONSTR 3 /* re-add existing constraints */
+#define AT_PASS_COL_ATTRS 4 /* set other column attributes */
/* We could support a RENAME COLUMN pass here, but not currently used */
-#define AT_PASS_ADD_COL 5 /* ADD COLUMN */
-#define AT_PASS_ADD_INDEX 6 /* ADD indexes */
-#define AT_PASS_ADD_CONSTR 7 /* ADD constraints, defaults */
-#define AT_PASS_MISC 8 /* other stuff */
-#define AT_NUM_PASSES 9
+#define AT_PASS_ADD_COL 5 /* ADD COLUMN */
+#define AT_PASS_ADD_INDEX 6 /* ADD indexes */
+#define AT_PASS_ADD_CONSTR 7 /* ADD constraints, defaults */
+#define AT_PASS_MISC 8 /* other stuff */
+#define AT_PASS_SET_LOGGED_UNLOGGED 9 /* SET LOGGED and UNLOGGED */
+#define AT_NUM_PASSES 10
typedef struct AlteredTableInfo
{
@@ -376,6 +377,7 @@ static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMOD
static void ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId,
char *cmd, List **wqueue, LOCKMODE lockmode,
bool rewrite);
+static void ATPostAlterSetLoggedUnlogged(Oid relid);
static void TryReuseIndex(Oid oldId, IndexStmt *stmt);
static void TryReuseForeignKey(Oid oldId, Constraint *con);
static void change_owner_fix_column_acls(Oid relationOid,
@@ -402,6 +404,13 @@ static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockm
static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode);
static void ATExecGenericOptions(Relation rel, List *options);
+static void ATPrepSetLogged(Relation rel);
+static void ATPrepSetUnLogged(Relation rel);
+static void ATExecSetLogged(Relation rel);
+static void ATExecSetUnLogged(Relation rel);
+
+static void AlterTableSetLoggedCheckForeignConstraints(Relation rel);
+static void AlterTableSetLoggedOrUnlogged(Relation rel, bool toLogged);
static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
ForkNumber forkNum, char relpersistence);
@@ -2854,6 +2863,8 @@ AlterTableGetLockLevel(List *cmds)
case AT_AddIndexConstraint:
case AT_ReplicaIdentity:
case AT_SetNotNull:
+ case AT_SetLogged:
+ case AT_SetUnLogged:
cmd_lockmode = AccessExclusiveLock;
break;
@@ -3245,6 +3256,15 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_SetLogged:
+ case AT_SetUnLogged:
+ ATSimplePermissions(rel, ATT_TABLE);
+ if (cmd->subtype == AT_SetLogged)
+ ATPrepSetLogged(rel); /* SET LOGGED */
+ else
+ ATPrepSetUnLogged(rel); /* SET UNLOGGED */
+ pass = AT_PASS_SET_LOGGED_UNLOGGED;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3307,6 +3327,9 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
ATPostAlterTypeCleanup(wqueue, tab, lockmode);
relation_close(rel, NoLock);
+
+ if (pass == AT_PASS_SET_LOGGED_UNLOGGED)
+ ATPostAlterSetLoggedUnlogged(RelationGetRelid(rel));
}
}
@@ -3526,6 +3549,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_GenericOptions:
ATExecGenericOptions(rel, (List *) cmd->def);
break;
+ case AT_SetLogged:
+ ATExecSetLogged(rel);
+ break;
+ case AT_SetUnLogged:
+ ATExecSetUnLogged(rel);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -10419,6 +10448,226 @@ ATExecGenericOptions(Relation rel, List *options)
}
/*
+ * ALTER TABLE <name> SET LOGGED
+ *
+ * Change the table persistence type from unlogged to permanent by
+ * rewriting the entire contents of the table and associated indexes
+ * into new disk files.
+ *
+ * The ATPrepSetLogged function check all precondictions to perform
+ * the operation:
+ * - check if the target table is unlogged
+ * - check if not exists a foreign key to other unlogged table
+ */
+static void
+ATPrepSetLogged(Relation rel)
+{
+ /* check if is an unlogged relation */
+ if (rel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s is not unlogged",
+ RelationGetRelationName(rel))));
+
+ /* check fk constraints */
+ AlterTableSetLoggedCheckForeignConstraints(rel);
+}
+
+static void
+AlterTableSetLoggedCheckForeignConstraints(Relation rel)
+{
+ Relation pg_constraint;
+ HeapTuple tuple;
+ SysScanDesc scan;
+ ScanKeyData skey[1];
+
+ /*
+ * Fetch the constraint tuple from pg_constraint.
+ */
+ pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
+
+ ScanKeyInit(&skey[0],
+ Anum_pg_constraint_conrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+
+ scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true,
+ NULL, 1, skey);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
+ if (con->contype == CONSTRAINT_FOREIGN)
+ {
+ Relation relfk = relation_open(con->confrelid, AccessShareLock);
+
+ if (RelationGetRelid(rel) != con->confrelid &&
+ relfk->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s references unlogged table %s",
+ RelationGetRelationName(rel),
+ RelationGetRelationName(relfk))));
+
+ relation_close(relfk, AccessShareLock);
+ }
+ }
+
+ systable_endscan(scan);
+
+ heap_close(pg_constraint, AccessShareLock);
+}
+
+/*
+ * ALTER TABLE <name> SET UNLOGGED
+ *
+ * Change the table persistence type from permanent to unlogged by
+ * rewriting the entire contents of the table and associated indexes
+ * into new disk files.
+ *
+ * The ATPrepSetUnLogged function check all precondictions to perform
+ * the operation:
+ * - check if the target table is permanent
+ */
+static void
+ATPrepSetUnLogged(Relation rel)
+{
+ /* check if is an permanent relation */
+ if (rel->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s is not permanent",
+ RelationGetRelationName(rel))));
+}
+
+/*
+ * The AlterTableChangeCatalogToLoggedOrUnlogged function perform the
+ * catalog changes, i.e. update pg_class.relpersistence to 'p' or 'u'
+ */
+static void
+AlterTableChangeCatalogToLoggedOrUnlogged(Relation rel, Relation relrelation, bool toLogged)
+{
+ HeapTuple tuple;
+ Form_pg_class pg_class_form;
+
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u",
+ RelationGetRelid(rel));
+
+ pg_class_form = (Form_pg_class) GETSTRUCT(tuple);
+
+ Assert(pg_class_form->relpersistence ==
+ ((toLogged) ? RELPERSISTENCE_UNLOGGED : RELPERSISTENCE_PERMANENT));
+
+ pg_class_form->relpersistence = toLogged ?
+ RELPERSISTENCE_PERMANENT : RELPERSISTENCE_UNLOGGED;
+
+ simple_heap_update(relrelation, &tuple->t_self, tuple);
+
+ /* keep catalog indexes current */
+ CatalogUpdateIndexes(relrelation, tuple);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * ALTER TABLE <name> SET LOGGED
+ *
+ * The ATExecSetLogged execute the code to change a relation from
+ * a unlogged to persistence state
+ */
+static void
+ATExecSetLogged(Relation rel)
+{
+ AlterTableSetLoggedOrUnlogged(rel, true);
+}
+
+/*
+ * ALTER TABLE <name> SET UNLOGGED
+ *
+ * The ATExecSetUnLogged execute the code to change a relation from
+ * a persistence to unlogged state
+ */
+static void
+ATExecSetUnLogged(Relation rel)
+{
+ AlterTableSetLoggedOrUnlogged(rel, false);
+}
+
+/*
+ * The AlterTableSetLoggedOrUnlogged function contains the main logic
+ * of the operation, changing the catalog for main heap, toast and indexes
+ */
+static void
+AlterTableSetLoggedOrUnlogged(Relation rel, bool toLogged)
+{
+ Oid relid;
+ Relation indexRelation;
+ ScanKeyData skey;
+ SysScanDesc scan;
+ HeapTuple indexTuple;
+ Relation relrel;
+
+ relid = RelationGetRelid(rel);
+
+ /* open pg_class to update relpersistence */
+ relrel = heap_open(RelationRelationId, RowExclusiveLock);
+
+ /* main heap */
+ AlterTableChangeCatalogToLoggedOrUnlogged(rel, relrel, toLogged);
+
+ /* toast heap, if any */
+ if (OidIsValid(rel->rd_rel->reltoastrelid))
+ {
+ Relation toastrel;
+
+ toastrel = heap_open(rel->rd_rel->reltoastrelid, AccessShareLock);
+ AlterTableChangeCatalogToLoggedOrUnlogged(toastrel, relrel, toLogged);
+ heap_close(toastrel, AccessShareLock);
+ }
+
+ /* Prepare to scan pg_index for entries having indrelid = this rel. */
+ indexRelation = heap_open(IndexRelationId, AccessShareLock);
+ ScanKeyInit(&skey,
+ Anum_pg_index_indrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ relid);
+
+ scan = systable_beginscan(indexRelation, IndexIndrelidIndexId, true,
+ NULL, 1, &skey);
+
+ while (HeapTupleIsValid(indexTuple = systable_getnext(scan)))
+ {
+ Form_pg_index index = (Form_pg_index) GETSTRUCT(indexTuple);
+ Relation indxrel = index_open(index->indexrelid, AccessShareLock);
+
+ AlterTableChangeCatalogToLoggedOrUnlogged(indxrel, relrel, toLogged);
+
+ index_close(indxrel, AccessShareLock);
+ }
+
+ systable_endscan(scan);
+ heap_close(indexRelation, AccessShareLock);
+
+ heap_close(relrel, RowExclusiveLock);
+}
+
+/*
+ * ALTER TABLE <name> SET { LOGGED | UNLOGGED }
+ *
+ * The ATPostAlterSetLoggedUnlogged function is called after all to
+ * guarantee that heap is closed to perform the cluster_rel
+ */
+static void
+ATPostAlterSetLoggedUnlogged(Oid relid)
+{
+ /* rebuild the relation using CLUSTER algorithm */
+ cluster_rel(relid, InvalidOid, false, false);
+}
+
+/*
* Execute ALTER TABLE SET SCHEMA
*/
Oid
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 605c9b4..a784d73 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -566,7 +566,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P LC_COLLATE_P LC_CTYPE_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
@@ -2201,6 +2201,20 @@ alter_table_cmd:
n->def = $3;
$$ = (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_generic_options
{
AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -12967,6 +12981,7 @@ unreserved_keyword:
| LOCAL
| LOCATION
| LOCK_P
+ | LOGGED
| MAPPING
| MATCH
| MATERIALIZED
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index be5c3c5..f2a2759 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1641,7 +1641,7 @@ psql_completion(const char *text, int start, int end)
pg_strcasecmp(prev_wd, "SET") == 0)
{
static const char *const list_TABLESET[] =
- {"(", "WITHOUT", "TABLESPACE", "SCHEMA", NULL};
+ {"(", "WITHOUT", "TABLESPACE", "SCHEMA", "LOGGED", "UNLOGGED", NULL};
COMPLETE_WITH_LIST(list_TABLESET);
}
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ff126eb..dc9f8fa 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1331,7 +1331,9 @@ typedef enum AlterTableType
AT_AddOf, /* OF <type_name> */
AT_DropOf, /* NOT OF */
AT_ReplicaIdentity, /* REPLICA IDENTITY */
- AT_GenericOptions /* OPTIONS (...) */
+ AT_GenericOptions, /* OPTIONS (...) */
+ AT_SetLogged, /* SET LOGGED */
+ AT_SetUnLogged /* SET UNLOGGED */
} AlterTableType;
typedef struct ReplicaIdentityStmt
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 61fae22..b62ce0e 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -231,6 +231,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)
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 9b89e58..516627e 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2426,3 +2426,60 @@ 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);
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+------------------+----------------+----------------------
+ unlogged1 | u | t
+ unlogged1_f1_seq | p | t
+ unlogged1_pkey | u | t
+(3 rows)
+
+CREATE UNLOGGED TABLE unlogged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged1); -- fk reference
+CREATE UNLOGGED TABLE unlogged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged3); -- self fk reference
+ALTER TABLE unlogged3 SET LOGGED;
+ALTER TABLE unlogged2 SET LOGGED;
+ERROR: table unlogged2 references unlogged table unlogged1
+ALTER TABLE unlogged1 SET LOGGED;
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+------------------+----------------+----------------------
+ unlogged1 | p | f
+ unlogged1_f1_seq | p | t
+ unlogged1_pkey | p | f
+(3 rows)
+
+ALTER TABLE unlogged1 SET LOGGED;
+ERROR: table unlogged1 is not unlogged
+DROP TABLE unlogged3;
+DROP TABLE unlogged2;
+DROP TABLE unlogged1;
+-- set unlogged
+CREATE TABLE logged1(f1 SERIAL PRIMARY KEY);
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+----------------+----------------+----------------------
+ logged1 | p | t
+ logged1_f1_seq | p | t
+ logged1_pkey | p | t
+(3 rows)
+
+CREATE TABLE logged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged1); -- fk reference
+CREATE TABLE logged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged3); -- self fk reference
+ALTER TABLE logged3 SET UNLOGGED;
+ALTER TABLE logged2 SET UNLOGGED;
+ALTER TABLE logged1 SET UNLOGGED;
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+----------------+----------------+----------------------
+ logged1 | u | f
+ logged1_f1_seq | p | t
+ logged1_pkey | u | f
+(3 rows)
+
+ALTER TABLE logged1 SET UNLOGGED;
+ERROR: table logged1 is not permanent
+DROP TABLE logged3;
+DROP TABLE logged2;
+DROP TABLE logged1;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 22a2dd0..26f7375 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1624,3 +1624,32 @@ 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);
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+CREATE UNLOGGED TABLE unlogged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged1); -- fk reference
+CREATE UNLOGGED TABLE unlogged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged3); -- self fk reference
+ALTER TABLE unlogged3 SET LOGGED;
+ALTER TABLE unlogged2 SET LOGGED;
+ALTER TABLE unlogged1 SET LOGGED;
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+ALTER TABLE unlogged1 SET LOGGED;
+DROP TABLE unlogged3;
+DROP TABLE unlogged2;
+DROP TABLE unlogged1;
+
+-- set unlogged
+CREATE TABLE logged1(f1 SERIAL PRIMARY KEY);
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+CREATE TABLE logged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged1); -- fk reference
+CREATE TABLE logged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged3); -- self fk reference
+ALTER TABLE logged3 SET UNLOGGED;
+ALTER TABLE logged2 SET UNLOGGED;
+ALTER TABLE logged1 SET UNLOGGED;
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+ALTER TABLE logged1 SET UNLOGGED;
+DROP TABLE logged3;
+DROP TABLE logged2;
+DROP TABLE logged1;
+
Hi,
here's my review for this patch.
Generally, the patch addresses a real need, tables often only turn
out to be desired as unlogged if there are performance problems in
practice, and the other way round changing an unlogged table to logged
is way more involved manually than it could be with this patch. So
yes, we want the feature.
I've tried the patch, and it works as desired, including tab
completion in psql. There are a few issues to be resolved, though.
Re: Fabr�zio de Royes Mello 2014-06-26 <CAFcNs+pyV0PBjLo97OSyqV1yQOC7s+JGvWXc8UnY5jSRDwy45A@mail.gmail.com>
As part of GSoC2014 and with agreement of my mentor and reviewer (Stephen
Frost) I've send a complement of the first patch to add the capability to
change a regular table to unlogged.
(Fwiw, I believe this direction is the more interesting one.)
With this patch we finish the main goals of the GSoC2014 and now we'll work
in the additional goals.
... that being the non-WAL-logging with wal_level=minimal, or more?
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 69a1e14..424f2e9 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -58,6 +58,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> ENABLE REPLICA RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable> ENABLE ALWAYS RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable> CLUSTER ON <replaceable class="PARAMETER">index_name</replaceable> + SET {LOGGED | UNLOGGED} SET WITHOUT CLUSTER SET WITH OIDS SET WITHOUT OIDS
This must not be between the two CLUSTER lines. I think the best spot
would just be one line down, before SET WITH OIDS.
(While we are at it, SET TABLESPACE should probably be moved up to the
other SET options...)
@@ -432,6 +433,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
</varlistentry><varlistentry> + <term><literal>SET {LOGGED | UNLOGGED}</literal></term> + <listitem> + <para> + This form change the table persistence type from unlogged to permanent or
This grammar bug pops up consistently: This form *changes*...
There's trailing whitespace.
+ from unlogged to permanent by rewriting the entire contents of the table + and associated indexes into new disk files. + </para> + <para> + Changing the table persistence type acquires an <literal>ACCESS EXCLUSIVE</literal> lock. + </para>
I'd rewrite that and add a reference:
... from unlogged to permanent (see <xref linkend="SQL-CREATETABLE-UNLOGGED">).
</para>
<para>
Changing the table persistence type acquires an <literal>ACCESS EXCLUSIVE</literal> lock
and rewrites the table contents and associated indexes into new disk files.
</para>
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 60d387a..9dfdca2 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -125,18 +125,19 @@ static List *on_commits = NIL; * a pass determined by subcommand type. */-#define AT_PASS_UNSET -1 /* UNSET will cause ERROR */ -#define AT_PASS_DROP 0 /* DROP (all flavors) */ -#define AT_PASS_ALTER_TYPE 1 /* ALTER COLUMN TYPE */ -#define AT_PASS_OLD_INDEX 2 /* re-add existing indexes */ -#define AT_PASS_OLD_CONSTR 3 /* re-add existing constraints */ -#define AT_PASS_COL_ATTRS 4 /* set other column attributes */ +#define AT_PASS_UNSET -1 /* UNSET will cause ERROR */ +#define AT_PASS_DROP 0 /* DROP (all flavors) */ +#define AT_PASS_ALTER_TYPE 1 /* ALTER COLUMN TYPE */ +#define AT_PASS_OLD_INDEX 2 /* re-add existing indexes */ +#define AT_PASS_OLD_CONSTR 3 /* re-add existing constraints */ +#define AT_PASS_COL_ATTRS 4 /* set other column attributes */ /* We could support a RENAME COLUMN pass here, but not currently used */ -#define AT_PASS_ADD_COL 5 /* ADD COLUMN */ -#define AT_PASS_ADD_INDEX 6 /* ADD indexes */ -#define AT_PASS_ADD_CONSTR 7 /* ADD constraints, defaults */ -#define AT_PASS_MISC 8 /* other stuff */ -#define AT_NUM_PASSES 9 +#define AT_PASS_ADD_COL 5 /* ADD COLUMN */ +#define AT_PASS_ADD_INDEX 6 /* ADD indexes */ +#define AT_PASS_ADD_CONSTR 7 /* ADD constraints, defaults */ +#define AT_PASS_MISC 8 /* other stuff */ +#define AT_PASS_SET_LOGGED_UNLOGGED 9 /* SET LOGGED and UNLOGGED */ +#define AT_NUM_PASSES 10
This unnecessarily rewrites all the tabs, but see below.
@@ -376,6 +377,7 @@ static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMOD static void ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, List **wqueue, LOCKMODE lockmode, bool rewrite); +static void ATPostAlterSetLoggedUnlogged(Oid relid);
(See below)
static void TryReuseIndex(Oid oldId, IndexStmt *stmt); static void TryReuseForeignKey(Oid oldId, Constraint *con); static void change_owner_fix_column_acls(Oid relationOid, @@ -402,6 +404,13 @@ static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockm static void ATExecDropOf(Relation rel, LOCKMODE lockmode); static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode); static void ATExecGenericOptions(Relation rel, List *options); +static void ATPrepSetLogged(Relation rel); +static void ATPrepSetUnLogged(Relation rel); +static void ATExecSetLogged(Relation rel); +static void ATExecSetUnLogged(Relation rel); + +static void AlterTableSetLoggedCheckForeignConstraints(Relation rel); +static void AlterTableSetLoggedOrUnlogged(Relation rel, bool toLogged);
All that should be ordered like in the docs, i.e. after
ATExecDropCluster. (... and the SetTableSpace stuff is already here ...)
+ case AT_SetLogged: + case AT_SetUnLogged: + ATSimplePermissions(rel, ATT_TABLE); + if (cmd->subtype == AT_SetLogged) + ATPrepSetLogged(rel); /* SET LOGGED */ + else + ATPrepSetUnLogged(rel); /* SET UNLOGGED */ + pass = AT_PASS_SET_LOGGED_UNLOGGED; + break;
I'm wondering if you shouldn't make a single ATPrepSetLogged function
that takes and additional toLogged argument. Or alternatively get rid
of the if() by putting the code also into case AT_SetLogged.
@@ -3307,6 +3327,9 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
ATPostAlterTypeCleanup(wqueue, tab, lockmode);relation_close(rel, NoLock); + + if (pass == AT_PASS_SET_LOGGED_UNLOGGED) + ATPostAlterSetLoggedUnlogged(RelationGetRelid(rel));
This must be done before relation_close() which releases all locks.
Moreover, I think you can get rid of that extra PASS here.
AT_PASS_ALTER_TYPE has its own pass because you can alter several
columns in a single ALTER TABLE statement, but you can have only one
SET (UN)LOGGED, so you can to the cluster_rel() directly in
AlterTableSetLoggedOrUnlogged() (unless cluster_rel() is too intrusive
and would interfere with other ALTER TABLE operations in this command,
no idea).
}
}@@ -3526,6 +3549,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, case AT_GenericOptions: ATExecGenericOptions(rel, (List *) cmd->def); break; + case AT_SetLogged: + ATExecSetLogged(rel); + break; + case AT_SetUnLogged: + ATExecSetUnLogged(rel); + break;
I'd replace ATExecSetLogged and ATExecSetUnLogged directly with
AlterTableSetLoggedOrUnlogged(rel, true/false). The 1-line wrappers
don't buy you anything.
+static void +AlterTableSetLoggedCheckForeignConstraints(Relation rel)
[...]
I can't comment on the quality of this function, but it seems to be
doing its job.
+/* + * ALTER TABLE <name> SET UNLOGGED + * + * Change the table persistence type from permanent to unlogged by + * rewriting the entire contents of the table and associated indexes + * into new disk files. + * + * The ATPrepSetUnLogged function check all precondictions to perform + * the operation: + * - check if the target table is permanent + */ +static void +ATPrepSetUnLogged(Relation rel) +{ + /* check if is an permanent relation */ + if (rel->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("table %s is not permanent", + RelationGetRelationName(rel)))); +}
Here's the big gotcha: Just like SET LOGGED must check for outgoing
FKs to unlogged tables, SET UNLOGGED must check for incoming FKs from
permanent tables. This is missing.
+/* + * The AlterTableChangeCatalogToLoggedOrUnlogged function perform the + * catalog changes, i.e. update pg_class.relpersistence to 'p' or 'u' + */ +static void +AlterTableChangeCatalogToLoggedOrUnlogged(Relation rel, Relation relrelation, bool toLogged) +{ + HeapTuple tuple; + Form_pg_class pg_class_form; + + tuple = SearchSysCacheCopy1(RELOID, + ObjectIdGetDatum(RelationGetRelid(rel))); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", + RelationGetRelid(rel)); + + pg_class_form = (Form_pg_class) GETSTRUCT(tuple); + + Assert(pg_class_form->relpersistence == + ((toLogged) ? RELPERSISTENCE_UNLOGGED : RELPERSISTENCE_PERMANENT)); + + pg_class_form->relpersistence = toLogged ? + RELPERSISTENCE_PERMANENT : RELPERSISTENCE_UNLOGGED; + + simple_heap_update(relrelation, &tuple->t_self, tuple); + + /* keep catalog indexes current */ + CatalogUpdateIndexes(relrelation, tuple); + + heap_freetuple(tuple); +}
Again I can't comment on the low-level catalog stuff - though I'd
remove some of those blank lines.
+/* + * The AlterTableSetLoggedOrUnlogged function contains the main logic + * of the operation, changing the catalog for main heap, toast and indexes + */ +static void +AlterTableSetLoggedOrUnlogged(Relation rel, bool toLogged) +{ + Oid relid; + Relation indexRelation; + ScanKeyData skey; + SysScanDesc scan; + HeapTuple indexTuple; + Relation relrel; + + relid = RelationGetRelid(rel); + + /* open pg_class to update relpersistence */ + relrel = heap_open(RelationRelationId, RowExclusiveLock); + + /* main heap */ + AlterTableChangeCatalogToLoggedOrUnlogged(rel, relrel, toLogged); + + /* toast heap, if any */ + if (OidIsValid(rel->rd_rel->reltoastrelid)) + { + Relation toastrel; + + toastrel = heap_open(rel->rd_rel->reltoastrelid, AccessShareLock); + AlterTableChangeCatalogToLoggedOrUnlogged(toastrel, relrel, toLogged); + heap_close(toastrel, AccessShareLock);
The comment on heap_open() suggests that you could directly invoke
relation_open() because you know this is a toast table, similarly for
index_open(). (I can't say which is better style.)
+ } + + /* Prepare to scan pg_index for entries having indrelid = this rel. */ + indexRelation = heap_open(IndexRelationId, AccessShareLock); + ScanKeyInit(&skey, + Anum_pg_index_indrelid, + BTEqualStrategyNumber, F_OIDEQ, + relid); + + scan = systable_beginscan(indexRelation, IndexIndrelidIndexId, true, + NULL, 1, &skey); + + while (HeapTupleIsValid(indexTuple = systable_getnext(scan))) + { + Form_pg_index index = (Form_pg_index) GETSTRUCT(indexTuple); + Relation indxrel = index_open(index->indexrelid, AccessShareLock); + + AlterTableChangeCatalogToLoggedOrUnlogged(indxrel, relrel, toLogged); + + index_close(indxrel, AccessShareLock); + }
You forgot the TOAST index.
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 605c9b4..a784d73 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y
@@ -2201,6 +2201,20 @@ alter_table_cmd: n->def = $3; $$ = (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; + }
Also move up to match the docs/other code ordering.
index ff126eb..dc9f8fa 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1331,7 +1331,9 @@ typedef enum AlterTableType AT_AddOf, /* OF <type_name> */ AT_DropOf, /* NOT OF */ AT_ReplicaIdentity, /* REPLICA IDENTITY */ - AT_GenericOptions /* OPTIONS (...) */ + AT_GenericOptions, /* OPTIONS (...) */ + AT_SetLogged, /* SET LOGGED */ + AT_SetUnLogged /* SET UNLOGGED */
Likewise.
--- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out
Please add test cases for incoming FKs, TOAST table relpersistence,
and TOAST index relpersistence.
Thanks for working on this feature, I'm looking forward to it!
Christoph
--
cb@df7cb.de | http://www.df7cb.de/
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Jul 8, 2014 at 4:53 PM, Christoph Berg <cb@df7cb.de> wrote:
Hi,
here's my review for this patch.
Generally, the patch addresses a real need, tables often only turn
out to be desired as unlogged if there are performance problems in
practice, and the other way round changing an unlogged table to logged
is way more involved manually than it could be with this patch. So
yes, we want the feature.I've tried the patch, and it works as desired, including tab
completion in psql. There are a few issues to be resolved, though.
Thank you so much for your review!
Re: Fabrízio de Royes Mello 2014-06-26 <
CAFcNs+pyV0PBjLo97OSyqV1yQOC7s+JGvWXc8UnY5jSRDwy45A@mail.gmail.com>
As part of GSoC2014 and with agreement of my mentor and reviewer
(Stephen
Frost) I've send a complement of the first patch to add the capability
to
change a regular table to unlogged.
(Fwiw, I believe this direction is the more interesting one.)
:-)
With this patch we finish the main goals of the GSoC2014 and now we'll
work
in the additional goals.
... that being the non-WAL-logging with wal_level=minimal, or more?
This is the first of additional goals, but we have others. Please see [1]https://wiki.postgresql.org/wiki/Allow_an_unlogged_table_to_be_changed_to_logged_GSoC_2014.
diff --git a/doc/src/sgml/ref/alter_table.sgml
b/doc/src/sgml/ref/alter_table.sgml
index 69a1e14..424f2e9 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -58,6 +58,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable
class="PARAMETER">name</replaceable>
ENABLE REPLICA RULE <replaceable
class="PARAMETER">rewrite_rule_name</replaceable>
ENABLE ALWAYS RULE <replaceable
class="PARAMETER">rewrite_rule_name</replaceable>
CLUSTER ON <replaceable class="PARAMETER">index_name</replaceable>
+ SET {LOGGED | UNLOGGED}
SET WITHOUT CLUSTER
SET WITH OIDS
SET WITHOUT OIDSThis must not be between the two CLUSTER lines. I think the best spot
would just be one line down, before SET WITH OIDS.
Fixed.
(While we are at it, SET TABLESPACE should probably be moved up to the
other SET options...)
Fixed.
@@ -432,6 +433,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable
class="PARAMETER">name</replaceable>
</varlistentry>
<varlistentry> + <term><literal>SET {LOGGED | UNLOGGED}</literal></term> + <listitem> + <para> + This form change the table persistence type from unlogged to
permanent or
This grammar bug pops up consistently: This form *changes*...
There's trailing whitespace.
Fixed.
+ from unlogged to permanent by rewriting the entire contents of
the table
+ and associated indexes into new disk files. + </para> + <para> + Changing the table persistence type acquires an <literal>ACCESS
EXCLUSIVE</literal> lock.
+ </para>
I'd rewrite that and add a reference:
... from unlogged to permanent (see <xref
linkend="SQL-CREATETABLE-UNLOGGED">).
</para>
<para>
Changing the table persistence type acquires an <literal>ACCESS
EXCLUSIVE</literal> lock
and rewrites the table contents and associated indexes into new disk
files.
</para>
Fixed.
diff --git a/src/backend/commands/tablecmds.c
b/src/backend/commands/tablecmds.c
index 60d387a..9dfdca2 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -125,18 +125,19 @@ static List *on_commits = NIL; * a pass determined by subcommand type. */-#define AT_PASS_UNSET -1 /* UNSET
will cause ERROR */
-#define AT_PASS_DROP 0 /* DROP (all
flavors) */
-#define AT_PASS_ALTER_TYPE 1 /* ALTER COLUMN
TYPE */
-#define AT_PASS_OLD_INDEX 2 /* re-add
existing indexes */
-#define AT_PASS_OLD_CONSTR 3 /* re-add
existing constraints */
-#define AT_PASS_COL_ATTRS 4 /* set other
column attributes */
+#define AT_PASS_UNSET -1
/* UNSET will cause ERROR */
+#define AT_PASS_DROP 0 /* DROP
(all flavors) */
+#define AT_PASS_ALTER_TYPE 1 /* ALTER
COLUMN TYPE */
+#define AT_PASS_OLD_INDEX 2 /* re-add
existing indexes */
+#define AT_PASS_OLD_CONSTR 3 /* re-add
existing constraints */
+#define AT_PASS_COL_ATTRS 4 /* set
other column attributes */
/* We could support a RENAME COLUMN pass here, but not currently used
*/
-#define AT_PASS_ADD_COL 5 /* ADD
COLUMN */
-#define AT_PASS_ADD_INDEX 6 /* ADD indexes */
-#define AT_PASS_ADD_CONSTR 7 /* ADD
constraints, defaults */
-#define AT_PASS_MISC 8 /* other stuff */ -#define AT_NUM_PASSES 9 +#define AT_PASS_ADD_COL 5
/* ADD COLUMN */
+#define AT_PASS_ADD_INDEX 6 /* ADD
indexes */
+#define AT_PASS_ADD_CONSTR 7 /* ADD
constraints, defaults */
+#define AT_PASS_MISC 8 /* other
stuff */
+#define AT_PASS_SET_LOGGED_UNLOGGED 9 /* SET LOGGED and
UNLOGGED */
+#define AT_NUM_PASSES 10
This unnecessarily rewrites all the tabs, but see below.
I did that because the new constant AT_PASS_SET_LOGGED_UNLOGGED is larger
than others.
@@ -376,6 +377,7 @@ static void ATPostAlterTypeCleanup(List **wqueue,
AlteredTableInfo *tab, LOCKMOD
static void ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId,
char *cmd, List **wqueue,
LOCKMODE lockmode,
bool rewrite);
+static void ATPostAlterSetLoggedUnlogged(Oid relid);(See below)
static void TryReuseIndex(Oid oldId, IndexStmt *stmt);
static void TryReuseForeignKey(Oid oldId, Constraint *con);
static void change_owner_fix_column_acls(Oid relationOid,
@@ -402,6 +404,13 @@ static void ATExecAddOf(Relation rel, const
TypeName *ofTypename, LOCKMODE lockm
static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt
*stmt, LOCKMODE lockmode);
static void ATExecGenericOptions(Relation rel, List *options); +static void ATPrepSetLogged(Relation rel); +static void ATPrepSetUnLogged(Relation rel); +static void ATExecSetLogged(Relation rel); +static void ATExecSetUnLogged(Relation rel); + +static void AlterTableSetLoggedCheckForeignConstraints(Relation rel); +static void AlterTableSetLoggedOrUnlogged(Relation rel, bool toLogged);All that should be ordered like in the docs, i.e. after
ATExecDropCluster. (... and the SetTableSpace stuff is already here ...)
Fixed.
+ case AT_SetLogged: + case AT_SetUnLogged: + ATSimplePermissions(rel, ATT_TABLE); + if (cmd->subtype == AT_SetLogged) + ATPrepSetLogged(rel);
/* SET LOGGED */
+ else + ATPrepSetUnLogged(rel);
/* SET UNLOGGED */
+ pass = AT_PASS_SET_LOGGED_UNLOGGED; + break;I'm wondering if you shouldn't make a single ATPrepSetLogged function
that takes and additional toLogged argument. Or alternatively get rid
of the if() by putting the code also into case AT_SetLogged.
Actually I started that way... with just one ATPrep function we have some
additional complexity to check relpersistence, define the error message and
to run AlterTableSetLoggedCheckForeignConstraints(rel) function. So to
simplify the code I decided split in two small functions.
@@ -3307,6 +3327,9 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE
lockmode)
ATPostAlterTypeCleanup(wqueue, tab,
lockmode);
relation_close(rel, NoLock); + + if (pass == AT_PASS_SET_LOGGED_UNLOGGED) +
ATPostAlterSetLoggedUnlogged(RelationGetRelid(rel));
This must be done before relation_close() which releases all locks.
Moreover, I think you can get rid of that extra PASS here.
AT_PASS_ALTER_TYPE has its own pass because you can alter several
columns in a single ALTER TABLE statement, but you can have only one
SET (UN)LOGGED, so you can to the cluster_rel() directly in
AlterTableSetLoggedOrUnlogged() (unless cluster_rel() is too intrusive
and would interfere with other ALTER TABLE operations in this command,
no idea).
I had some troubles here so I decided to do in that way, but I confess I'm
not comfortable with this implementation. Looking more carefully on
tablecmds.c code, at the ATController we have three phases and the third is
'scan/rewrite tables as needed' so my doubt is if can I use it instead of
call 'cluster_rel'?
@@ -3526,6 +3549,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
Relation rel,
case AT_GenericOptions: ATExecGenericOptions(rel, (List *) cmd->def); break; + case AT_SetLogged: + ATExecSetLogged(rel); + break; + case AT_SetUnLogged: + ATExecSetUnLogged(rel); + break;I'd replace ATExecSetLogged and ATExecSetUnLogged directly with
AlterTableSetLoggedOrUnlogged(rel, true/false). The 1-line wrappers
don't buy you anything.
Fixed.
+static void +AlterTableSetLoggedCheckForeignConstraints(Relation rel)[...]
I can't comment on the quality of this function, but it seems to be
doing its job.
:-)
+/* + * ALTER TABLE <name> SET UNLOGGED + * + * Change the table persistence type from permanent to unlogged by + * rewriting the entire contents of the table and associated indexes + * into new disk files. + * + * The ATPrepSetUnLogged function check all precondictions to perform + * the operation: + * - check if the target table is permanent + */ +static void +ATPrepSetUnLogged(Relation rel) +{ + /* check if is an permanent relation */ + if (rel->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT) + ereport(ERROR, +
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s is not permanent", + RelationGetRelationName(rel)))); +}Here's the big gotcha: Just like SET LOGGED must check for outgoing
FKs to unlogged tables, SET UNLOGGED must check for incoming FKs from
permanent tables. This is missing.
I don't think so... we can create an unlogged table with a FK referring to
a regular table...
fabrizio=# create table foo(id integer primary key);
CREATE TABLE
fabrizio=# create unlogged table bar(id integer primary key, foo integer
references foo);
CREATE TABLE
... but is not possible create a FK from a regular table referring to an
unlogged table:
fabrizio=# create unlogged table foo(id integer primary key);
CREATE TABLE
fabrizio=# create table bar(id integer primary key, foo integer references
foo);
ERROR: constraints on permanent tables may reference only permanent tables
... and a FK from an unlogged table referring other unlogged table works:
fabrizio=# create unlogged table bar(id integer primary key, foo integer
references foo);
CREATE TABLE
So we must take carefull just when changing an unlogged table to a regular
table.
Am I correct or I miss something?
+/* + * The AlterTableChangeCatalogToLoggedOrUnlogged function perform the + * catalog changes, i.e. update pg_class.relpersistence to 'p' or 'u' + */ +static void +AlterTableChangeCatalogToLoggedOrUnlogged(Relation rel, Relation
relrelation, bool toLogged)
+{ + HeapTuple tuple; + Form_pg_class pg_class_form; + + tuple = SearchSysCacheCopy1(RELOID, +
ObjectIdGetDatum(RelationGetRelid(rel)));
+ if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", + RelationGetRelid(rel)); + + pg_class_form = (Form_pg_class) GETSTRUCT(tuple); + + Assert(pg_class_form->relpersistence == + ((toLogged) ? RELPERSISTENCE_UNLOGGED :
RELPERSISTENCE_PERMANENT));
+ + pg_class_form->relpersistence = toLogged ? + RELPERSISTENCE_PERMANENT :
RELPERSISTENCE_UNLOGGED;
+ + simple_heap_update(relrelation, &tuple->t_self, tuple); + + /* keep catalog indexes current */ + CatalogUpdateIndexes(relrelation, tuple); + + heap_freetuple(tuple); +}Again I can't comment on the low-level catalog stuff - though I'd
remove some of those blank lines.
Fixed.
+/* + * The AlterTableSetLoggedOrUnlogged function contains the main logic + * of the operation, changing the catalog for main heap, toast and
indexes
+ */ +static void +AlterTableSetLoggedOrUnlogged(Relation rel, bool toLogged) +{ + Oid relid; + Relation indexRelation; + ScanKeyData skey; + SysScanDesc scan; + HeapTuple indexTuple; + Relation relrel; + + relid = RelationGetRelid(rel); + + /* open pg_class to update relpersistence */ + relrel = heap_open(RelationRelationId, RowExclusiveLock); + + /* main heap */ + AlterTableChangeCatalogToLoggedOrUnlogged(rel, relrel, toLogged); + + /* toast heap, if any */ + if (OidIsValid(rel->rd_rel->reltoastrelid)) + { + Relation toastrel; + + toastrel = heap_open(rel->rd_rel->reltoastrelid,
AccessShareLock);
+ AlterTableChangeCatalogToLoggedOrUnlogged(toastrel,
relrel, toLogged);
+ heap_close(toastrel, AccessShareLock);
The comment on heap_open() suggests that you could directly invoke
relation_open() because you know this is a toast table, similarly for
index_open(). (I can't say which is better style.)
I don't know which is better style too... other opinions??
+ } + + /* Prepare to scan pg_index for entries having indrelid = this
rel. */
+ indexRelation = heap_open(IndexRelationId, AccessShareLock); + ScanKeyInit(&skey, + Anum_pg_index_indrelid, + BTEqualStrategyNumber, F_OIDEQ, + relid); + + scan = systable_beginscan(indexRelation, IndexIndrelidIndexId,
true,
+ NULL, 1, &skey); + + while (HeapTupleIsValid(indexTuple = systable_getnext(scan))) + { + Form_pg_index index = (Form_pg_index)
GETSTRUCT(indexTuple);
+ Relation indxrel = index_open(index->indexrelid,
AccessShareLock);
+ + AlterTableChangeCatalogToLoggedOrUnlogged(indxrel,
relrel, toLogged);
+ + index_close(indxrel, AccessShareLock); + }You forgot the TOAST index.
Fixed.
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 605c9b4..a784d73 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y@@ -2201,6 +2201,20 @@ alter_table_cmd: n->def = $3; $ = (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; + }Also move up to match the docs/other code ordering.
Fixed. But other ALTER commands in gram.y aren't in the same order of
documentation.
index ff126eb..dc9f8fa 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1331,7 +1331,9 @@ typedef enum AlterTableType AT_AddOf, /* OF <type_name>
*/
AT_DropOf, /* NOT OF */ AT_ReplicaIdentity, /* REPLICA IDENTITY */ - AT_GenericOptions /* OPTIONS (...) */ + AT_GenericOptions, /* OPTIONS (...) */ + AT_SetLogged, /* SET LOGGED */ + AT_SetUnLogged /* SET UNLOGGED */Likewise.
Fixed.
--- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.outPlease add test cases for incoming FKs, TOAST table relpersistence,
and TOAST index relpersistence.
Added.
Thanks for working on this feature, I'm looking forward to it!
You're welcome!
Regards,
[1]: https://wiki.postgresql.org/wiki/Allow_an_unlogged_table_to_be_changed_to_logged_GSoC_2014
https://wiki.postgresql.org/wiki/Allow_an_unlogged_table_to_be_changed_to_logged_GSoC_2014
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog sobre TI: http://fabriziomello.blogspot.com
Perfil Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Attachments:
gsoc2014_alter_table_set_logged_v3.patchtext/x-diff; charset=US-ASCII; name=gsoc2014_alter_table_set_logged_v3.patchDownload
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 69a1e14..87f196f 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -59,16 +59,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
ENABLE ALWAYS RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable>
CLUSTER ON <replaceable class="PARAMETER">index_name</replaceable>
SET WITHOUT CLUSTER
+ SET {LOGGED | UNLOGGED}
SET WITH OIDS
SET WITHOUT OIDS
SET ( <replaceable class="PARAMETER">storage_parameter</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
+ SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
RESET ( <replaceable class="PARAMETER">storage_parameter</replaceable> [, ... ] )
INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
NO INHERIT <replaceable class="PARAMETER">parent_table</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>
@@ -432,6 +433,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
</varlistentry>
<varlistentry>
+ <term><literal>SET {LOGGED | UNLOGGED}</literal></term>
+ <listitem>
+ <para>
+ This form changes the table persistence type from unlogged to permanent or
+ from unlogged to permanent (see <xref linkend="SQL-CREATETABLE-UNLOGGED">).
+ </para>
+ <para>
+ Changing the table persistence type acquires an <literal>ACCESS EXCLUSIVE</literal> lock
+ and rewrites the table contents and associated indexes into new disk files.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>SET WITHOUT CLUSTER</literal></term>
<listitem>
<para>
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 60d387a..be6c613 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -125,18 +125,19 @@ static List *on_commits = NIL;
* a pass determined by subcommand type.
*/
-#define AT_PASS_UNSET -1 /* UNSET will cause ERROR */
-#define AT_PASS_DROP 0 /* DROP (all flavors) */
-#define AT_PASS_ALTER_TYPE 1 /* ALTER COLUMN TYPE */
-#define AT_PASS_OLD_INDEX 2 /* re-add existing indexes */
-#define AT_PASS_OLD_CONSTR 3 /* re-add existing constraints */
-#define AT_PASS_COL_ATTRS 4 /* set other column attributes */
+#define AT_PASS_UNSET -1 /* UNSET will cause ERROR */
+#define AT_PASS_DROP 0 /* DROP (all flavors) */
+#define AT_PASS_ALTER_TYPE 1 /* ALTER COLUMN TYPE */
+#define AT_PASS_OLD_INDEX 2 /* re-add existing indexes */
+#define AT_PASS_OLD_CONSTR 3 /* re-add existing constraints */
+#define AT_PASS_COL_ATTRS 4 /* set other column attributes */
/* We could support a RENAME COLUMN pass here, but not currently used */
-#define AT_PASS_ADD_COL 5 /* ADD COLUMN */
-#define AT_PASS_ADD_INDEX 6 /* ADD indexes */
-#define AT_PASS_ADD_CONSTR 7 /* ADD constraints, defaults */
-#define AT_PASS_MISC 8 /* other stuff */
-#define AT_NUM_PASSES 9
+#define AT_PASS_ADD_COL 5 /* ADD COLUMN */
+#define AT_PASS_ADD_INDEX 6 /* ADD indexes */
+#define AT_PASS_ADD_CONSTR 7 /* ADD constraints, defaults */
+#define AT_PASS_MISC 8 /* other stuff */
+#define AT_PASS_SET_LOGGED_UNLOGGED 9 /* SET LOGGED and UNLOGGED */
+#define AT_NUM_PASSES 10
typedef struct AlteredTableInfo
{
@@ -376,6 +377,7 @@ static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMOD
static void ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId,
char *cmd, List **wqueue, LOCKMODE lockmode,
bool rewrite);
+static void ATPostAlterSetLoggedUnlogged(Oid relid);
static void TryReuseIndex(Oid oldId, IndexStmt *stmt);
static void TryReuseForeignKey(Oid oldId, Constraint *con);
static void change_owner_fix_column_acls(Oid relationOid,
@@ -384,6 +386,10 @@ 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 ATExecDropCluster(Relation rel, LOCKMODE lockmode);
+static void ATPrepSetLogged(Relation rel);
+static void ATPrepSetUnLogged(Relation rel);
+static void AlterTableSetLoggedCheckForeignConstraints(Relation rel);
+static void AlterTableSetLoggedOrUnlogged(Relation rel, bool toLogged);
static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
char *tablespacename, LOCKMODE lockmode);
static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
@@ -2854,6 +2860,8 @@ AlterTableGetLockLevel(List *cmds)
case AT_AddIndexConstraint:
case AT_ReplicaIdentity:
case AT_SetNotNull:
+ case AT_SetLogged:
+ case AT_SetUnLogged:
cmd_lockmode = AccessExclusiveLock;
break;
@@ -3245,6 +3253,15 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_SetLogged:
+ case AT_SetUnLogged:
+ ATSimplePermissions(rel, ATT_TABLE);
+ if (cmd->subtype == AT_SetLogged)
+ ATPrepSetLogged(rel); /* SET LOGGED */
+ else
+ ATPrepSetUnLogged(rel); /* SET UNLOGGED */
+ pass = AT_PASS_SET_LOGGED_UNLOGGED;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3307,6 +3324,9 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
ATPostAlterTypeCleanup(wqueue, tab, lockmode);
relation_close(rel, NoLock);
+
+ if (pass == AT_PASS_SET_LOGGED_UNLOGGED)
+ ATPostAlterSetLoggedUnlogged(RelationGetRelid(rel));
}
}
@@ -3526,6 +3546,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_GenericOptions:
ATExecGenericOptions(rel, (List *) cmd->def);
break;
+ case AT_SetLogged:
+ AlterTableSetLoggedOrUnlogged(rel, true);
+ break;
+ case AT_SetUnLogged:
+ AlterTableSetLoggedOrUnlogged(rel, false);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -10419,6 +10445,217 @@ ATExecGenericOptions(Relation rel, List *options)
}
/*
+ * ALTER TABLE <name> SET LOGGED
+ *
+ * Change the table persistence type from unlogged to permanent by
+ * rewriting the entire contents of the table and associated indexes
+ * into new disk files.
+ *
+ * The ATPrepSetLogged function check all precondictions to perform
+ * the operation:
+ * - check if the target table is unlogged
+ * - check if not exists a foreign key to other unlogged table
+ */
+static void
+ATPrepSetLogged(Relation rel)
+{
+ /* check if is an unlogged relation */
+ if (rel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s is not unlogged",
+ RelationGetRelationName(rel))));
+
+ /* check fk constraints */
+ AlterTableSetLoggedCheckForeignConstraints(rel);
+}
+
+static void
+AlterTableSetLoggedCheckForeignConstraints(Relation rel)
+{
+ Relation pg_constraint;
+ HeapTuple tuple;
+ SysScanDesc scan;
+ ScanKeyData skey[1];
+
+ /*
+ * Fetch the constraint tuple from pg_constraint.
+ */
+ pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
+
+ ScanKeyInit(&skey[0],
+ Anum_pg_constraint_conrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+
+ scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true,
+ NULL, 1, skey);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
+ if (con->contype == CONSTRAINT_FOREIGN)
+ {
+ Relation relfk = relation_open(con->confrelid, AccessShareLock);
+
+ if (RelationGetRelid(rel) != con->confrelid &&
+ relfk->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s references unlogged table %s",
+ RelationGetRelationName(rel),
+ RelationGetRelationName(relfk))));
+
+ relation_close(relfk, AccessShareLock);
+ }
+ }
+
+ systable_endscan(scan);
+
+ heap_close(pg_constraint, AccessShareLock);
+}
+
+/*
+ * ALTER TABLE <name> SET UNLOGGED
+ *
+ * Change the table persistence type from permanent to unlogged by
+ * rewriting the entire contents of the table and associated indexes
+ * into new disk files.
+ *
+ * The ATPrepSetUnLogged function check all precondictions to perform
+ * the operation:
+ * - check if the target table is permanent
+ */
+static void
+ATPrepSetUnLogged(Relation rel)
+{
+ /* check if is an permanent relation */
+ if (rel->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s is not permanent",
+ RelationGetRelationName(rel))));
+}
+
+/*
+ * The AlterTableChangeCatalogToLoggedOrUnlogged function perform the
+ * catalog changes, i.e. update pg_class.relpersistence to 'p' or 'u'
+ */
+static void
+AlterTableChangeCatalogToLoggedOrUnlogged(Oid relid, Relation relrelation, bool toLogged)
+{
+ HeapTuple tuple;
+ Form_pg_class pg_class_form;
+ Relation rel;
+
+ /* open relation */
+ rel = relation_open(relid, AccessShareLock);
+
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u",
+ RelationGetRelid(rel));
+
+ pg_class_form = (Form_pg_class) GETSTRUCT(tuple);
+ Assert(pg_class_form->relpersistence ==
+ ((toLogged) ? RELPERSISTENCE_UNLOGGED : RELPERSISTENCE_PERMANENT));
+
+ pg_class_form->relpersistence = toLogged ?
+ RELPERSISTENCE_PERMANENT : RELPERSISTENCE_UNLOGGED;
+ simple_heap_update(relrelation, &tuple->t_self, tuple);
+
+ /* keep catalog indexes current */
+ CatalogUpdateIndexes(relrelation, tuple);
+
+ heap_freetuple(tuple);
+ heap_close(rel, AccessShareLock);
+}
+
+/*
+ * The AlterTableChangeIndexesToLoggedOrUnlogged function scan all indexes
+ * of a relation to change the relpersistence of each one
+ */
+static void
+AlterTableChangeIndexesToLoggedOrUnlogged(Oid relid, Relation relrel, bool toLogged)
+{
+ Relation indexRelation;
+ ScanKeyData skey;
+ SysScanDesc scan;
+ HeapTuple indexTuple;
+
+ /* Prepare to scan pg_index for entries having indrelid = relid. */
+ indexRelation = heap_open(IndexRelationId, AccessShareLock);
+ ScanKeyInit(&skey,
+ Anum_pg_index_indrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ relid);
+
+ scan = systable_beginscan(indexRelation, IndexIndrelidIndexId, true,
+ NULL, 1, &skey);
+
+ while (HeapTupleIsValid(indexTuple = systable_getnext(scan)))
+ {
+ Form_pg_index index = (Form_pg_index) GETSTRUCT(indexTuple);
+ AlterTableChangeCatalogToLoggedOrUnlogged(index->indexrelid, relrel, toLogged);
+ }
+
+ systable_endscan(scan);
+ heap_close(indexRelation, AccessShareLock);
+}
+
+/*
+ *
+ * ALTER TABLE <name> SET {LOGGED | UNLOGGED}
+ *
+ * The AlterTableSetLoggedOrUnlogged function contains the main logic
+ * of the operation, changing the catalog for main heap, toast and indexes
+ */
+static void
+AlterTableSetLoggedOrUnlogged(Relation rel, bool toLogged)
+{
+ Relation relrel;
+ Oid relid;
+
+ /* get relation's oid */
+ relid = RelationGetRelid(rel);
+
+ /* open pg_class to update relpersistence */
+ relrel = heap_open(RelationRelationId, RowExclusiveLock);
+
+ /* main heap */
+ AlterTableChangeCatalogToLoggedOrUnlogged(relid, relrel, toLogged);
+
+ /* indexes */
+ AlterTableChangeIndexesToLoggedOrUnlogged(relid, relrel, toLogged);
+
+ /* toast heap, if any */
+ if (OidIsValid(rel->rd_rel->reltoastrelid))
+ {
+ /* toast */
+ AlterTableChangeCatalogToLoggedOrUnlogged(rel->rd_rel->reltoastrelid, relrel, toLogged);
+
+ /* toast index */
+ AlterTableChangeIndexesToLoggedOrUnlogged(rel->rd_rel->reltoastrelid, relrel, toLogged);
+ }
+
+ heap_close(relrel, RowExclusiveLock);
+}
+
+/*
+ * ALTER TABLE <name> SET { LOGGED | UNLOGGED }
+ *
+ * The ATPostAlterSetLoggedUnlogged function is called after all to
+ * guarantee that heap is closed to perform the cluster_rel
+ */
+static void
+ATPostAlterSetLoggedUnlogged(Oid relid)
+{
+ /* rebuild the relation using CLUSTER algorithm */
+ cluster_rel(relid, InvalidOid, false, false);
+}
+
+/*
* Execute ALTER TABLE SET SCHEMA
*/
Oid
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a113809..bc5913a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -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
@@ -2048,6 +2048,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
{
@@ -12992,6 +13006,7 @@ unreserved_keyword:
| LOCAL
| LOCATION
| LOCK_P
+ | LOGGED
| MAPPING
| MATCH
| MATERIALIZED
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 6c2d431..c9c507f 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1643,7 +1643,7 @@ psql_completion(const char *text, int start, int end)
pg_strcasecmp(prev_wd, "SET") == 0)
{
static const char *const list_TABLESET[] =
- {"(", "WITHOUT", "TABLESPACE", "SCHEMA", NULL};
+ {"(", "WITHOUT", "TABLESPACE", "SCHEMA", "LOGGED", "UNLOGGED", NULL};
COMPLETE_WITH_LIST(list_TABLESET);
}
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8364bef..ca68590 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -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 */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b52e507..17888ad 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -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)
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 9b89e58..b6eb191 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2426,3 +2426,108 @@ 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);
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+------------------+----------------+----------------------
+ unlogged1 | u | t
+ unlogged1_f1_seq | p | t
+ unlogged1_pkey | u | t
+(3 rows)
+
+SELECT t.relpersistence, t.oid = t.relfilenode AS original_relfilenode FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid WHERE r.relname ~ '^unlogged1' ORDER BY 1;
+ relpersistence | original_relfilenode
+----------------+----------------------
+ u | t
+(1 row)
+
+SELECT ti.relpersistence, ti.oid = ti.relfilenode AS original_relfilenode 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 ti ON ti.oid = i.indexrelid WHERE r.relname ~ '^unlogged1' ORDER BY 1;
+ relpersistence | original_relfilenode
+----------------+----------------------
+ u | t
+(1 row)
+
+CREATE UNLOGGED TABLE unlogged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged1); -- fk reference
+CREATE UNLOGGED TABLE unlogged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged3); -- self fk reference
+ALTER TABLE unlogged3 SET LOGGED;
+ALTER TABLE unlogged2 SET LOGGED;
+ERROR: table unlogged2 references unlogged table unlogged1
+ALTER TABLE unlogged1 SET LOGGED;
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+------------------+----------------+----------------------
+ unlogged1 | p | f
+ unlogged1_f1_seq | p | t
+ unlogged1_pkey | p | f
+(3 rows)
+
+SELECT t.relpersistence, t.oid = t.relfilenode AS original_relfilenode FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid WHERE r.relname ~ '^unlogged1' ORDER BY 1;
+ relpersistence | original_relfilenode
+----------------+----------------------
+ p | f
+(1 row)
+
+SELECT ti.relpersistence, ti.oid = ti.relfilenode AS original_relfilenode 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 ti ON ti.oid = i.indexrelid WHERE r.relname ~ '^unlogged1' ORDER BY 1;
+ relpersistence | original_relfilenode
+----------------+----------------------
+ p | f
+(1 row)
+
+ALTER TABLE unlogged1 SET LOGGED;
+ERROR: table unlogged1 is not unlogged
+DROP TABLE unlogged3;
+DROP TABLE unlogged2;
+DROP TABLE unlogged1;
+-- set unlogged
+CREATE TABLE logged1(f1 SERIAL PRIMARY KEY, f2 TEXT);
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+----------------+----------------+----------------------
+ logged1 | p | t
+ logged1_f1_seq | p | t
+ logged1_pkey | p | t
+(3 rows)
+
+SELECT t.relpersistence, t.oid = t.relfilenode AS original_relfilenode FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid WHERE r.relname ~ '^logged1' ORDER BY 1;
+ relpersistence | original_relfilenode
+----------------+----------------------
+ p | t
+(1 row)
+
+SELECT ti.relpersistence, ti.oid = ti.relfilenode AS original_relfilenode 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 ti ON ti.oid = i.indexrelid WHERE r.relname ~ '^logged1' ORDER BY 1;
+ relpersistence | original_relfilenode
+----------------+----------------------
+ p | t
+(1 row)
+
+CREATE TABLE logged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged1); -- fk reference
+CREATE TABLE logged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged3); -- self fk reference
+ALTER TABLE logged3 SET UNLOGGED;
+ALTER TABLE logged2 SET UNLOGGED;
+ALTER TABLE logged1 SET UNLOGGED;
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+----------------+----------------+----------------------
+ logged1 | u | f
+ logged1_f1_seq | p | t
+ logged1_pkey | u | f
+(3 rows)
+
+SELECT t.relpersistence, t.oid = t.relfilenode AS original_relfilenode FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid WHERE r.relname ~ '^logged1' ORDER BY 1;
+ relpersistence | original_relfilenode
+----------------+----------------------
+ u | f
+(1 row)
+
+SELECT ti.relpersistence, ti.oid = ti.relfilenode AS original_relfilenode 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 ti ON ti.oid = i.indexrelid WHERE r.relname ~ '^logged1' ORDER BY 1;
+ relpersistence | original_relfilenode
+----------------+----------------------
+ u | f
+(1 row)
+
+ALTER TABLE logged1 SET UNLOGGED;
+ERROR: table logged1 is not permanent
+DROP TABLE logged3;
+DROP TABLE logged2;
+DROP TABLE logged1;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 22a2dd0..5f6438e 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1624,3 +1624,40 @@ 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);
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+SELECT t.relpersistence, t.oid = t.relfilenode AS original_relfilenode FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid WHERE r.relname ~ '^unlogged1' ORDER BY 1;
+SELECT ti.relpersistence, ti.oid = ti.relfilenode AS original_relfilenode 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 ti ON ti.oid = i.indexrelid WHERE r.relname ~ '^unlogged1' ORDER BY 1;
+CREATE UNLOGGED TABLE unlogged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged1); -- fk reference
+CREATE UNLOGGED TABLE unlogged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged3); -- self fk reference
+ALTER TABLE unlogged3 SET LOGGED;
+ALTER TABLE unlogged2 SET LOGGED;
+ALTER TABLE unlogged1 SET LOGGED;
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+SELECT t.relpersistence, t.oid = t.relfilenode AS original_relfilenode FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid WHERE r.relname ~ '^unlogged1' ORDER BY 1;
+SELECT ti.relpersistence, ti.oid = ti.relfilenode AS original_relfilenode 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 ti ON ti.oid = i.indexrelid WHERE r.relname ~ '^unlogged1' ORDER BY 1;
+ALTER TABLE unlogged1 SET LOGGED;
+DROP TABLE unlogged3;
+DROP TABLE unlogged2;
+DROP TABLE unlogged1;
+
+-- set unlogged
+CREATE TABLE logged1(f1 SERIAL PRIMARY KEY, f2 TEXT);
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+SELECT t.relpersistence, t.oid = t.relfilenode AS original_relfilenode FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid WHERE r.relname ~ '^logged1' ORDER BY 1;
+SELECT ti.relpersistence, ti.oid = ti.relfilenode AS original_relfilenode 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 ti ON ti.oid = i.indexrelid WHERE r.relname ~ '^logged1' ORDER BY 1;
+CREATE TABLE logged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged1); -- fk reference
+CREATE TABLE logged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged3); -- self fk reference
+ALTER TABLE logged3 SET UNLOGGED;
+ALTER TABLE logged2 SET UNLOGGED;
+ALTER TABLE logged1 SET UNLOGGED;
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+SELECT t.relpersistence, t.oid = t.relfilenode AS original_relfilenode FROM pg_class r JOIN pg_class t ON t.oid = r.reltoastrelid JOIN pg_index i ON i.indrelid = t.oid WHERE r.relname ~ '^logged1' ORDER BY 1;
+SELECT ti.relpersistence, ti.oid = ti.relfilenode AS original_relfilenode 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 ti ON ti.oid = i.indexrelid WHERE r.relname ~ '^logged1' ORDER BY 1;
+ALTER TABLE logged1 SET UNLOGGED;
+DROP TABLE logged3;
+DROP TABLE logged2;
+DROP TABLE logged1;
+
Re: Fabrízio de Royes Mello 2014-07-12 <CAFcNs+qF5fUkkp9vdJWokiaBn_rRM4+HJqXeeVpD_7-tO0L4AA@mail.gmail.com>
... that being the non-WAL-logging with wal_level=minimal, or more?
This is the first of additional goals, but we have others. Please see [1].
Oh I wasn't aware of the wiki page, I had just read the old thread.
Thanks for the pointer.
diff --git a/doc/src/sgml/ref/alter_table.sgmlb/doc/src/sgml/ref/alter_table.sgml
index 69a1e14..424f2e9 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -58,6 +58,7 @@ ALTER TABLE [ IF EXISTS ] <replaceableclass="PARAMETER">name</replaceable>
ENABLE REPLICA RULE <replaceable
class="PARAMETER">rewrite_rule_name</replaceable>
ENABLE ALWAYS RULE <replaceable
class="PARAMETER">rewrite_rule_name</replaceable>
CLUSTER ON <replaceable class="PARAMETER">index_name</replaceable>
+ SET {LOGGED | UNLOGGED}
SET WITHOUT CLUSTER
SET WITH OIDS
SET WITHOUT OIDSThis must not be between the two CLUSTER lines. I think the best spot
would just be one line down, before SET WITH OIDS.Fixed.
The (long) SET LOGGED paragraph is still between CLUSTER and SET
WITHOUT CLUSTER.
This grammar bug pops up consistently: This form *changes*...
Fixed.
Two more:
+ * The AlterTableChangeCatalogToLoggedOrUnlogged function perform the
+ * The AlterTableChangeIndexesToLoggedOrUnlogged function scan all indexes
This unnecessarily rewrites all the tabs, but see below.
I did that because the new constant AT_PASS_SET_LOGGED_UNLOGGED is larger
than others.
Ah, ok then.
I'm wondering if you shouldn't make a single ATPrepSetLogged function
that takes and additional toLogged argument. Or alternatively get rid
of the if() by putting the code also into case AT_SetLogged.Actually I started that way... with just one ATPrep function we have some
additional complexity to check relpersistence, define the error message and
to run AlterTableSetLoggedCheckForeignConstraints(rel) function. So to
simplify the code I decided split in two small functions.
Nod.
relation_close(rel, NoLock); + + if (pass == AT_PASS_SET_LOGGED_UNLOGGED) +ATPostAlterSetLoggedUnlogged(RelationGetRelid(rel));
This must be done before relation_close() which releases all locks.
You didn't address that. I'm not sure about it, but either way, this
deserves a comment on the lock level necessary.
Moreover, I think you can get rid of that extra PASS here.
AT_PASS_ALTER_TYPE has its own pass because you can alter several
columns in a single ALTER TABLE statement, but you can have only one
SET (UN)LOGGED, so you can to the cluster_rel() directly in
AlterTableSetLoggedOrUnlogged() (unless cluster_rel() is too intrusive
and would interfere with other ALTER TABLE operations in this command,
no idea).I had some troubles here so I decided to do in that way, but I confess I'm
not comfortable with this implementation. Looking more carefully on
tablecmds.c code, at the ATController we have three phases and the third is
'scan/rewrite tables as needed' so my doubt is if can I use it instead of
call 'cluster_rel'?
I've just tried some SET (UN)LOGGED operations with altering column
types in the same operation, that works. But:
Yes, you should use the existing table rewriting machinery, or at
least clearly document (in comments) why it doesn't work for you.
Also looking at ATController, there's a wqueue mechanism to queue
catalog updates. You should probably use this, too, or again document
why it doesn't work for you.
Here's the big gotcha: Just like SET LOGGED must check for outgoing
FKs to unlogged tables, SET UNLOGGED must check for incoming FKs from
permanent tables. This is missing.I don't think so... we can create an unlogged table with a FK referring to
a regular table...
... but is not possible create a FK from a regular table referring to an
unlogged table:
... and a FK from an unlogged table referring other unlogged table works:
So we must take carefull just when changing an unlogged table to a regular
table.Am I correct or I miss something?
You miss the symmetric case the other way round. When changing a table
to unlogged, you need to make sure no other permanent table is
referencing our table.
+AlterTableChangeCatalogToLoggedOrUnlogged(Relation rel, Relation
relrelation, bool toLogged)
You are using "relrelation" and "relrel". I'd change all occurrences
to "relrelation" because that's also used elsewhere.
The comment on heap_open() suggests that you could directly invoke
relation_open() because you know this is a toast table, similarly for
index_open(). (I can't say which is better style.)I don't know which is better style too... other opinions??
Both are used several times in tablecmds.c, so both are probably fine.
(Didn't check the contexts, though.)
Christoph
--
cb@df7cb.de | http://www.df7cb.de/
On Mon, Jul 14, 2014 at 3:31 PM, Christoph Berg <cb@df7cb.de> wrote:
Oh I wasn't aware of the wiki page, I had just read the old thread.
Thanks for the pointer.
:-)
Thanks again for your review!
diff --git a/doc/src/sgml/ref/alter_table.sgmlb/doc/src/sgml/ref/alter_table.sgml
index 69a1e14..424f2e9 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -58,6 +58,7 @@ ALTER TABLE [ IF EXISTS ] <replaceableclass="PARAMETER">name</replaceable>
ENABLE REPLICA RULE <replaceable
class="PARAMETER">rewrite_rule_name</replaceable>
ENABLE ALWAYS RULE <replaceable
class="PARAMETER">rewrite_rule_name</replaceable>
CLUSTER ON <replaceable
class="PARAMETER">index_name</replaceable>
+ SET {LOGGED | UNLOGGED}
SET WITHOUT CLUSTER
SET WITH OIDS
SET WITHOUT OIDSThis must not be between the two CLUSTER lines. I think the best spot
would just be one line down, before SET WITH OIDS.Fixed.
The (long) SET LOGGED paragraph is still between CLUSTER and SET
WITHOUT CLUSTER.
Fixed.
This grammar bug pops up consistently: This form *changes*...
Fixed.
Two more:
+ * The AlterTableChangeCatalogToLoggedOrUnlogged function perform the + * The AlterTableChangeIndexesToLoggedOrUnlogged function scan all
indexes
Fixed.
relation_close(rel, NoLock); + + if (pass == AT_PASS_SET_LOGGED_UNLOGGED) +ATPostAlterSetLoggedUnlogged(RelationGetRelid(rel));
This must be done before relation_close() which releases all locks.
You didn't address that. I'm not sure about it, but either way, this
deserves a comment on the lock level necessary.
Actually relation_close(rel, NoLock) don't release the locks.
See src/backend/access/heap/heapam.c:1167
Moreover, I think you can get rid of that extra PASS here.
AT_PASS_ALTER_TYPE has its own pass because you can alter several
columns in a single ALTER TABLE statement, but you can have only one
SET (UN)LOGGED, so you can to the cluster_rel() directly in
AlterTableSetLoggedOrUnlogged() (unless cluster_rel() is too intrusive
and would interfere with other ALTER TABLE operations in this command,
no idea).I had some troubles here so I decided to do in that way, but I confess
I'm
not comfortable with this implementation. Looking more carefully on
tablecmds.c code, at the ATController we have three phases and the
third is
'scan/rewrite tables as needed' so my doubt is if can I use it instead
of
call 'cluster_rel'?
I've just tried some SET (UN)LOGGED operations with altering column
types in the same operation, that works. But:Yes, you should use the existing table rewriting machinery, or at
least clearly document (in comments) why it doesn't work for you.Also looking at ATController, there's a wqueue mechanism to queue
catalog updates. You should probably use this, too, or again document
why it doesn't work for you.
This works... fixed!
Here's the big gotcha: Just like SET LOGGED must check for outgoing
FKs to unlogged tables, SET UNLOGGED must check for incoming FKs from
permanent tables. This is missing.I don't think so... we can create an unlogged table with a FK referring
to
a regular table...
... but is not possible create a FK from a regular table referring to an
unlogged table:
... and a FK from an unlogged table referring other unlogged table
works:
So we must take carefull just when changing an unlogged table to a
regular
table.
Am I correct or I miss something?
You miss the symmetric case the other way round. When changing a table
to unlogged, you need to make sure no other permanent table is
referencing our table.
Ohh yeas... sorry... you're completely correct... fixed!
+AlterTableChangeCatalogToLoggedOrUnlogged(Relation rel, Relation
relrelation, bool toLogged)
You are using "relrelation" and "relrel". I'd change all occurrences
to "relrelation" because that's also used elsewhere.
Fixed.
The comment on heap_open() suggests that you could directly invoke
relation_open() because you know this is a toast table, similarly for
index_open(). (I can't say which is better style.)I don't know which is better style too... other opinions??
Both are used several times in tablecmds.c, so both are probably fine.
(Didn't check the contexts, though.)
Then we can leave that way. Is ok for you?
Greetings,
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog sobre TI: http://fabriziomello.blogspot.com
Perfil Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Attachments:
gsoc2014_alter_table_set_logged_v4.patchtext/x-diff; charset=US-ASCII; name=gsoc2014_alter_table_set_logged_v4.patchDownload
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 69a1e14..2d131df 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -59,16 +59,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
ENABLE ALWAYS RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable>
CLUSTER ON <replaceable class="PARAMETER">index_name</replaceable>
SET WITHOUT CLUSTER
+ SET {LOGGED | UNLOGGED}
SET WITH OIDS
SET WITHOUT OIDS
SET ( <replaceable class="PARAMETER">storage_parameter</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
+ SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
RESET ( <replaceable class="PARAMETER">storage_parameter</replaceable> [, ... ] )
INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
NO INHERIT <replaceable class="PARAMETER">parent_table</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>
@@ -447,6 +448,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
</varlistentry>
<varlistentry>
+ <term><literal>SET {LOGGED | UNLOGGED}</literal></term>
+ <listitem>
+ <para>
+ This form changes the table persistence type from unlogged to permanent or
+ from unlogged to permanent (see <xref linkend="SQL-CREATETABLE-UNLOGGED">).
+ </para>
+ <para>
+ Changing the table persistence type acquires an <literal>ACCESS EXCLUSIVE</literal> lock
+ and rewrites the table contents and associated indexes into new disk files.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>SET WITH OIDS</literal></term>
<listitem>
<para>
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 60d387a..7be5cfa 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -384,6 +384,10 @@ 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 ATExecDropCluster(Relation rel, LOCKMODE lockmode);
+static void ATPrepSetLogged(Relation rel);
+static void ATPrepSetUnLogged(Relation rel);
+static void AlterTableSetLoggedCheckForeignConstraints(Relation rel, bool toLogged);
+static void AlterTableSetLoggedOrUnlogged(Relation rel, bool toLogged);
static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
char *tablespacename, LOCKMODE lockmode);
static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
@@ -2854,6 +2858,8 @@ AlterTableGetLockLevel(List *cmds)
case AT_AddIndexConstraint:
case AT_ReplicaIdentity:
case AT_SetNotNull:
+ case AT_SetLogged:
+ case AT_SetUnLogged:
cmd_lockmode = AccessExclusiveLock;
break;
@@ -3245,6 +3251,16 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_SetLogged:
+ case AT_SetUnLogged:
+ ATSimplePermissions(rel, ATT_TABLE);
+ if (cmd->subtype == AT_SetLogged)
+ ATPrepSetLogged(rel); /* SET LOGGED */
+ else
+ ATPrepSetUnLogged(rel); /* SET UNLOGGED */
+ pass = AT_PASS_MISC;
+ tab->rewrite = true;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3526,6 +3542,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_GenericOptions:
ATExecGenericOptions(rel, (List *) cmd->def);
break;
+ case AT_SetLogged:
+ AlterTableSetLoggedOrUnlogged(rel, true);
+ break;
+ case AT_SetUnLogged:
+ AlterTableSetLoggedOrUnlogged(rel, false);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -10419,6 +10441,225 @@ ATExecGenericOptions(Relation rel, List *options)
}
/*
+ * ALTER TABLE <name> SET LOGGED
+ *
+ * Change the table persistence type from unlogged to permanent by
+ * rewriting the entire contents of the table and associated indexes
+ * into new disk files.
+ *
+ * The ATPrepSetLogged function check all precondictions to perform
+ * the operation:
+ * - check if the target table is unlogged
+ * - check if not exists a foreign key to other unlogged table
+ */
+static void
+ATPrepSetLogged(Relation rel)
+{
+ /* check if is an unlogged relation */
+ if (rel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s is not unlogged",
+ RelationGetRelationName(rel))));
+
+ /* check fk constraints */
+ AlterTableSetLoggedCheckForeignConstraints(rel, true);
+}
+
+static void
+AlterTableSetLoggedCheckForeignConstraints(Relation rel, bool toLogged)
+{
+ Relation pg_constraint;
+ HeapTuple tuple;
+ SysScanDesc scan;
+ ScanKeyData skey[1];
+
+ /*
+ * Fetch the constraint tuple from pg_constraint.
+ */
+ pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
+
+ 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), toLogged,
+ NULL, 1, skey);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
+ if (con->contype == CONSTRAINT_FOREIGN)
+ {
+ Relation relfk;
+
+ if (toLogged)
+ {
+ relfk = relation_open(con->confrelid, AccessShareLock);
+
+ if (RelationGetRelid(rel) != con->confrelid &&
+ relfk->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s references unlogged table %s",
+ RelationGetRelationName(rel),
+ RelationGetRelationName(relfk))));
+ }
+ else
+ {
+ relfk = relation_open(con->conrelid, AccessShareLock);
+
+ if (RelationGetRelid(rel) != con->conrelid &&
+ relfk->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s references unlogged table %s",
+ RelationGetRelationName(relfk),
+ RelationGetRelationName(rel))));
+ }
+
+ relation_close(relfk, AccessShareLock);
+ }
+ }
+
+ systable_endscan(scan);
+
+ heap_close(pg_constraint, AccessShareLock);
+}
+
+/*
+ * ALTER TABLE <name> SET UNLOGGED
+ *
+ * Change the table persistence type from permanent to unlogged by
+ * rewriting the entire contents of the table and associated indexes
+ * into new disk files.
+ *
+ * The ATPrepSetUnLogged function check all precondictions to perform
+ * the operation:
+ * - check if the target table is permanent
+ */
+static void
+ATPrepSetUnLogged(Relation rel)
+{
+ /* check if is an permanent relation */
+ if (rel->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s is not permanent",
+ RelationGetRelationName(rel))));
+
+ /* check incoming fk constraints */
+ AlterTableSetLoggedCheckForeignConstraints(rel, false);
+}
+
+/*
+ * The AlterTableChangeCatalogToLoggedOrUnlogged function performs the
+ * catalog changes, i.e. update pg_class.relpersistence to 'p' or 'u'
+ */
+static void
+AlterTableChangeCatalogToLoggedOrUnlogged(Oid relid, Relation relrelation, bool toLogged)
+{
+ HeapTuple tuple;
+ Form_pg_class pg_class_form;
+ Relation rel;
+
+ /* open relation */
+ rel = relation_open(relid, AccessShareLock);
+
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u",
+ RelationGetRelid(rel));
+
+ pg_class_form = (Form_pg_class) GETSTRUCT(tuple);
+ Assert(pg_class_form->relpersistence ==
+ ((toLogged) ? RELPERSISTENCE_UNLOGGED : RELPERSISTENCE_PERMANENT));
+
+ pg_class_form->relpersistence = toLogged ?
+ RELPERSISTENCE_PERMANENT : RELPERSISTENCE_UNLOGGED;
+ simple_heap_update(relrelation, &tuple->t_self, tuple);
+
+ /* keep catalog indexes current */
+ CatalogUpdateIndexes(relrelation, tuple);
+
+ heap_freetuple(tuple);
+ heap_close(rel, AccessShareLock);
+}
+
+/*
+ * The AlterTableChangeIndexesToLoggedOrUnlogged function scans all indexes
+ * of a relation to change the relpersistence of each one
+ */
+static void
+AlterTableChangeIndexesToLoggedOrUnlogged(Oid relid, Relation relrelation, bool toLogged)
+{
+ Relation indexRelation;
+ ScanKeyData skey;
+ SysScanDesc scan;
+ HeapTuple indexTuple;
+
+ /* Prepare to scan pg_index for entries having indrelid = relid. */
+ indexRelation = heap_open(IndexRelationId, AccessShareLock);
+ ScanKeyInit(&skey,
+ Anum_pg_index_indrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ relid);
+
+ scan = systable_beginscan(indexRelation, IndexIndrelidIndexId, true,
+ NULL, 1, &skey);
+
+ while (HeapTupleIsValid(indexTuple = systable_getnext(scan)))
+ {
+ Form_pg_index index = (Form_pg_index) GETSTRUCT(indexTuple);
+ AlterTableChangeCatalogToLoggedOrUnlogged(index->indexrelid, relrelation, toLogged);
+ }
+
+ systable_endscan(scan);
+ heap_close(indexRelation, AccessShareLock);
+}
+
+/*
+ *
+ * ALTER TABLE <name> SET {LOGGED | UNLOGGED}
+ *
+ * The AlterTableSetLoggedOrUnlogged function contains the main logic
+ * of the operation, changing the catalog for main heap, toast and indexes
+ */
+static void
+AlterTableSetLoggedOrUnlogged(Relation rel, bool toLogged)
+{
+ Relation relrelation;
+ Oid relid;
+
+ /* get relation's oid */
+ relid = RelationGetRelid(rel);
+
+ /* open pg_class to update relpersistence */
+ relrelation = heap_open(RelationRelationId, RowExclusiveLock);
+
+ /* main heap */
+ AlterTableChangeCatalogToLoggedOrUnlogged(relid, relrelation, toLogged);
+
+ /* indexes */
+ AlterTableChangeIndexesToLoggedOrUnlogged(relid, relrelation, toLogged);
+
+ /* toast heap, if any */
+ if (OidIsValid(rel->rd_rel->reltoastrelid))
+ {
+ /* toast */
+ AlterTableChangeCatalogToLoggedOrUnlogged(rel->rd_rel->reltoastrelid, relrelation, toLogged);
+
+ /* toast index */
+ AlterTableChangeIndexesToLoggedOrUnlogged(rel->rd_rel->reltoastrelid, relrelation, toLogged);
+ }
+
+ heap_close(relrelation, RowExclusiveLock);
+}
+
+/*
* Execute ALTER TABLE SET SCHEMA
*/
Oid
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a113809..bc5913a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -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
@@ -2048,6 +2048,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
{
@@ -12992,6 +13006,7 @@ unreserved_keyword:
| LOCAL
| LOCATION
| LOCK_P
+ | LOGGED
| MAPPING
| MATCH
| MATERIALIZED
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 24e60b7..56a42c3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1643,7 +1643,7 @@ psql_completion(const char *text, int start, int end)
pg_strcasecmp(prev_wd, "SET") == 0)
{
static const char *const list_TABLESET[] =
- {"(", "WITHOUT", "TABLESPACE", "SCHEMA", NULL};
+ {"(", "WITHOUT", "TABLESPACE", "SCHEMA", "LOGGED", "UNLOGGED", NULL};
COMPLETE_WITH_LIST(list_TABLESET);
}
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8364bef..ca68590 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -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 */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b52e507..17888ad 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -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)
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 9b89e58..859a615 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2426,3 +2426,58 @@ 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);
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+------------------+----------------+----------------------
+ unlogged1 | u | t
+ unlogged1_f1_seq | p | t
+ unlogged1_pkey | u | t
+(3 rows)
+
+CREATE UNLOGGED TABLE unlogged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged1); -- fk reference
+CREATE UNLOGGED TABLE unlogged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged3); -- self fk reference
+ALTER TABLE unlogged3 SET LOGGED;
+ALTER TABLE unlogged2 SET LOGGED;
+ERROR: table unlogged2 references unlogged table unlogged1
+ALTER TABLE unlogged1 SET LOGGED;
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+------------------+----------------+----------------------
+ unlogged1 | p | f
+ unlogged1_f1_seq | p | t
+ unlogged1_pkey | p | f
+(3 rows)
+
+DROP TABLE unlogged3;
+DROP TABLE unlogged2;
+DROP TABLE unlogged1;
+-- set unlogged
+CREATE TABLE logged1(f1 SERIAL PRIMARY KEY, f2 TEXT);
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+----------------+----------------+----------------------
+ logged1 | p | t
+ logged1_f1_seq | p | t
+ logged1_pkey | p | t
+(3 rows)
+
+CREATE TABLE logged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged1); -- fk reference
+CREATE TABLE logged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged3); -- self fk reference
+ALTER TABLE logged1 SET UNLOGGED;
+ERROR: table logged2 references unlogged table logged1
+ALTER TABLE logged3 SET UNLOGGED;
+ALTER TABLE logged2 SET UNLOGGED;
+ALTER TABLE logged1 SET UNLOGGED;
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+----------------+----------------+----------------------
+ logged1 | u | f
+ logged1_f1_seq | p | t
+ logged1_pkey | u | f
+(3 rows)
+
+DROP TABLE logged3;
+DROP TABLE logged2;
+DROP TABLE logged1;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 22a2dd0..2775d6a 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1624,3 +1624,31 @@ 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);
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+CREATE UNLOGGED TABLE unlogged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged1); -- fk reference
+CREATE UNLOGGED TABLE unlogged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged3); -- self fk reference
+ALTER TABLE unlogged3 SET LOGGED;
+ALTER TABLE unlogged2 SET LOGGED;
+ALTER TABLE unlogged1 SET LOGGED;
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+DROP TABLE unlogged3;
+DROP TABLE unlogged2;
+DROP TABLE unlogged1;
+
+-- set unlogged
+CREATE TABLE logged1(f1 SERIAL PRIMARY KEY, f2 TEXT);
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+CREATE TABLE logged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged1); -- fk reference
+CREATE TABLE logged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged3); -- self fk reference
+ALTER TABLE logged1 SET UNLOGGED;
+ALTER TABLE logged3 SET UNLOGGED;
+ALTER TABLE logged2 SET UNLOGGED;
+ALTER TABLE logged1 SET UNLOGGED;
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+DROP TABLE logged3;
+DROP TABLE logged2;
+DROP TABLE logged1;
+
Hi Fabr�zio,
thanks for the speedy new version.
Re: Fabr�zio de Royes Mello 2014-07-15 <CAFcNs+opBuHjUo1sOATHv8zqcOwqp-yeRjFoo15v1xPXSpCdDw@mail.gmail.com>
+ + if (pass == AT_PASS_SET_LOGGED_UNLOGGED) +ATPostAlterSetLoggedUnlogged(RelationGetRelid(rel));
This must be done before relation_close() which releases all locks.
You didn't address that. I'm not sure about it, but either way, this
deserves a comment on the lock level necessary.Actually relation_close(rel, NoLock) don't release the locks.
See src/backend/access/heap/heapam.c:1167
Oh there was one "not" too much for me, sorry. Anyway, this isn't
relevant anymore. :)
I've just tried some SET (UN)LOGGED operations with altering column
types in the same operation, that works. But:Yes, you should use the existing table rewriting machinery, or at
least clearly document (in comments) why it doesn't work for you.Also looking at ATController, there's a wqueue mechanism to queue
catalog updates. You should probably use this, too, or again document
why it doesn't work for you.This works... fixed!
Thanks.
What about the wqueue mechanism, though? Isn't that made exactly for
the kind of catalog updates you are doing?
You miss the symmetric case the other way round. When changing a table
to unlogged, you need to make sure no other permanent table is
referencing our table.Ohh yeas... sorry... you're completely correct... fixed!
Can you move ATPrepSetUnLogged next to ATPrepSetLogged as both
reference AlterTableSetLoggedCheckForeignConstraints now, and fix the
comment on ATPrepSetUnLogged to also mention FKs? I'd also reiterate
my proposal to merge these into one function, given they are now doing
the same checks.
In AlterTableSetLoggedCheckForeignConstraints, move "relfk =
relation_open..." out of the "if" because it's duplicated, and also for
symmetry with relation_close().
The function needs comments. It is somewhat clear that
self-referencing FKs will be skipped, but the two "if (toLogged)"
branches should be annotated to say which case they are really about.
Instead of just switching the argument order in the errmsg arguments,
the error text should be updated to read "table %s is referenced
by permanent table %s". At the moment the error text is wrong because
the table logged1 is not yet unlogged:
+ALTER TABLE logged1 SET UNLOGGED;
+ERROR: table logged2 references unlogged table logged1
-> table logged1 is referenced by permanent table logged2
The comment on heap_open() suggests that you could directly invoke
relation_open() because you know this is a toast table, similarly for
index_open(). (I can't say which is better style.)I don't know which is better style too... other opinions??
Both are used several times in tablecmds.c, so both are probably fine.
(Didn't check the contexts, though.)Then we can leave that way. Is ok for you?
Yes. It was just a minor nitpick anyway.
Compared to v3, you've removed a lot of "SELECT t.relpersistence..."
from the regression tests, was that intended?
I think the tests could also use a bit more comments, notably the
commands that are expected to fail. So far I haven't tried to read
them but trusted that they did the right thing. (Though with the
SELECTs removed, it's pretty readable now.)
Christoph
--
cb@df7cb.de | http://www.df7cb.de/
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Jul 15, 2014 at 3:04 PM, Christoph Berg <cb@df7cb.de> wrote:
Hi Fabrízio,
thanks for the speedy new version.
You're welcome... If all happen ok I would like to have this patch commited
before the GSoC2014 ends.
I've just tried some SET (UN)LOGGED operations with altering column
types in the same operation, that works. But:Yes, you should use the existing table rewriting machinery, or at
least clearly document (in comments) why it doesn't work for you.Also looking at ATController, there's a wqueue mechanism to queue
catalog updates. You should probably use this, too, or again document
why it doesn't work for you.This works... fixed!
Thanks.
What about the wqueue mechanism, though? Isn't that made exactly for
the kind of catalog updates you are doing?
Works, but this mechanism create a new entry in pg_class for toast, so it's
a little different than use the cluster_rel that generate a new
relfilenode. The important is both mechanisms create new datafiles.
You miss the symmetric case the other way round. When changing a table
to unlogged, you need to make sure no other permanent table is
referencing our table.Ohh yeas... sorry... you're completely correct... fixed!
Can you move ATPrepSetUnLogged next to ATPrepSetLogged as both
reference AlterTableSetLoggedCheckForeignConstraints now, and fix the
comment on ATPrepSetUnLogged to also mention FKs? I'd also reiterate
my proposal to merge these into one function, given they are now doing
the same checks.
Merged both to a single ATPrepSetLoggedOrUnlogged(Relation rel, bool
toLogged);
In AlterTableSetLoggedCheckForeignConstraints, move "relfk =
relation_open..." out of the "if" because it's duplicated, and also for
symmetry with relation_close().
But they aren't duplicated... the first opens "con->confrelid" and the
other opens "con->conrelid" according "toLogged" branch.
The function needs comments. It is somewhat clear that
self-referencing FKs will be skipped, but the two "if (toLogged)"
branches should be annotated to say which case they are really about.
Fixed.
Instead of just switching the argument order in the errmsg arguments,
the error text should be updated to read "table %s is referenced
by permanent table %s". At the moment the error text is wrong because
the table logged1 is not yet unlogged:+ALTER TABLE logged1 SET UNLOGGED; +ERROR: table logged2 references unlogged table logged1-> table logged1 is referenced by permanent table logged2
Sorry... my mistake... fixed
Compared to v3, you've removed a lot of "SELECT t.relpersistence..."
from the regression tests, was that intended?
I removed because they are not so useful than I was thinking before.
Actually they just bloated our test cases.
I think the tests could also use a bit more comments, notably the
commands that are expected to fail. So far I haven't tried to read
them but trusted that they did the right thing. (Though with the
SELECTs removed, it's pretty readable now.)
Added some comments.
Regards,
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog sobre TI: http://fabriziomello.blogspot.com
Perfil Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Attachments:
gsoc2014_alter_table_set_logged_v5.patchtext/x-diff; charset=US-ASCII; name=gsoc2014_alter_table_set_logged_v5.patchDownload
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 69a1e14..2d131df 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -59,16 +59,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
ENABLE ALWAYS RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable>
CLUSTER ON <replaceable class="PARAMETER">index_name</replaceable>
SET WITHOUT CLUSTER
+ SET {LOGGED | UNLOGGED}
SET WITH OIDS
SET WITHOUT OIDS
SET ( <replaceable class="PARAMETER">storage_parameter</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
+ SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
RESET ( <replaceable class="PARAMETER">storage_parameter</replaceable> [, ... ] )
INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
NO INHERIT <replaceable class="PARAMETER">parent_table</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>
@@ -447,6 +448,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
</varlistentry>
<varlistentry>
+ <term><literal>SET {LOGGED | UNLOGGED}</literal></term>
+ <listitem>
+ <para>
+ This form changes the table persistence type from unlogged to permanent or
+ from unlogged to permanent (see <xref linkend="SQL-CREATETABLE-UNLOGGED">).
+ </para>
+ <para>
+ Changing the table persistence type acquires an <literal>ACCESS EXCLUSIVE</literal> lock
+ and rewrites the table contents and associated indexes into new disk files.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>SET WITH OIDS</literal></term>
<listitem>
<para>
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5dc4d18..184784c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -384,6 +384,9 @@ 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 ATExecDropCluster(Relation rel, LOCKMODE lockmode);
+static void ATPrepSetLoggedOrUnlogged(Relation rel, bool toLogged);
+static void AlterTableSetLoggedUnloggedCheckForeignConstraints(Relation rel, bool toLogged);
+static void AlterTableSetLoggedOrUnlogged(Relation rel, bool toLogged);
static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
char *tablespacename, LOCKMODE lockmode);
static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
@@ -2857,6 +2860,8 @@ AlterTableGetLockLevel(List *cmds)
case AT_AddIndexConstraint:
case AT_ReplicaIdentity:
case AT_SetNotNull:
+ case AT_SetLogged:
+ case AT_SetUnLogged:
cmd_lockmode = AccessExclusiveLock;
break;
@@ -3248,6 +3253,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_SetLogged:
+ case AT_SetUnLogged:
+ ATSimplePermissions(rel, ATT_TABLE);
+ ATPrepSetLoggedOrUnlogged(rel, (cmd->subtype == AT_SetLogged)); /* SET {LOGGED | UNLOGGED} */
+ pass = AT_PASS_MISC;
+ tab->rewrite = true;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3529,6 +3541,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_GenericOptions:
ATExecGenericOptions(rel, (List *) cmd->def);
break;
+ case AT_SetLogged:
+ AlterTableSetLoggedOrUnlogged(rel, true);
+ break;
+ case AT_SetUnLogged:
+ AlterTableSetLoggedOrUnlogged(rel, false);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -10424,6 +10442,225 @@ ATExecGenericOptions(Relation rel, List *options)
}
/*
+ * ALTER TABLE <name> SET {LOGGED | UNLOGGED}
+ *
+ * Change the table persistence type from unlogged to permanent by
+ * rewriting the entire contents of the table and associated indexes
+ * into new disk files.
+ *
+ * The ATPrepSetLoggedOrUnlogged function check all precondictions
+ * to perform the operation:
+ * - check if the target table is unlogged/permanente
+ * - check if not exists a foreign key to/from other unlogged/permanent
+ * table
+ */
+static void
+ATPrepSetLoggedOrUnlogged(Relation rel, bool toLogged)
+{
+ char relpersistence;
+
+ relpersistence = (toLogged) ? RELPERSISTENCE_UNLOGGED :
+ RELPERSISTENCE_PERMANENT;
+
+ /* check if is an unlogged or permanent relation */
+ if (rel->rd_rel->relpersistence != relpersistence)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s is not %s",
+ RelationGetRelationName(rel),
+ (toLogged) ? "unlogged" : "permanent")));
+
+ /* check fk constraints */
+ AlterTableSetLoggedUnloggedCheckForeignConstraints(rel, toLogged);
+}
+
+/*
+ * AlterTableSetLoggedUnloggedCheckForeignConstraints: checks for Foreign Key
+ * constraints consistency when changing from unlogged to permanent or
+ * from permanent to unlogged.
+ *
+ * Throws an exception when:
+ *
+ * - if changing to permanent (toLogged = true) then checks if doesn't
+ * exists a foreign key to another unlogged table.
+ *
+ * - if changing to unlogged (toLogged = false) then checks if doesn't
+ * exists a foreign key from another permanent table.
+ *
+ * Self foreign keys are skipped from the check.
+ */
+static void
+AlterTableSetLoggedUnloggedCheckForeignConstraints(Relation rel, bool toLogged)
+{
+ Relation pg_constraint;
+ HeapTuple tuple;
+ SysScanDesc scan;
+ ScanKeyData skey[1];
+
+ /*
+ * Fetch the constraint tuple from pg_constraint.
+ */
+ pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
+
+ /* scans conrelid if toLogged is true else scans confreld */
+ 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), toLogged,
+ NULL, 1, skey);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
+ if (con->contype == CONSTRAINT_FOREIGN)
+ {
+ Relation relfk;
+
+ if (toLogged)
+ {
+ relfk = relation_open(con->confrelid, AccessShareLock);
+
+ /* skip if self FK or check if exists a FK to an unlogged table */
+ if (RelationGetRelid(rel) != con->confrelid &&
+ relfk->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s references unlogged table %s",
+ RelationGetRelationName(rel),
+ RelationGetRelationName(relfk))));
+ }
+ else
+ {
+ relfk = relation_open(con->conrelid, AccessShareLock);
+
+ /* skip if self FK or check if exists a FK from a permanent table */
+ if (RelationGetRelid(rel) != con->conrelid &&
+ relfk->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s is referenced by permanent table %s",
+ RelationGetRelationName(relfk),
+ RelationGetRelationName(rel))));
+ }
+
+ relation_close(relfk, AccessShareLock);
+ }
+ }
+
+ systable_endscan(scan);
+
+ heap_close(pg_constraint, AccessShareLock);
+}
+
+/*
+ * The AlterTableChangeCatalogToLoggedOrUnlogged function performs the
+ * catalog changes, i.e. update pg_class.relpersistence to 'p' or 'u'
+ */
+static void
+AlterTableChangeCatalogToLoggedOrUnlogged(Oid relid, Relation relrelation, bool toLogged)
+{
+ HeapTuple tuple;
+ Form_pg_class pg_class_form;
+ Relation rel;
+
+ /* open relation */
+ rel = relation_open(relid, AccessShareLock);
+
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u",
+ RelationGetRelid(rel));
+
+ pg_class_form = (Form_pg_class) GETSTRUCT(tuple);
+ Assert(pg_class_form->relpersistence ==
+ ((toLogged) ? RELPERSISTENCE_UNLOGGED : RELPERSISTENCE_PERMANENT));
+
+ pg_class_form->relpersistence = toLogged ?
+ RELPERSISTENCE_PERMANENT : RELPERSISTENCE_UNLOGGED;
+ simple_heap_update(relrelation, &tuple->t_self, tuple);
+
+ /* keep catalog indexes current */
+ CatalogUpdateIndexes(relrelation, tuple);
+
+ heap_freetuple(tuple);
+ heap_close(rel, AccessShareLock);
+}
+
+/*
+ * The AlterTableChangeIndexesToLoggedOrUnlogged function scans all indexes
+ * of a relation to change the relpersistence of each one
+ */
+static void
+AlterTableChangeIndexesToLoggedOrUnlogged(Oid relid, Relation relrelation, bool toLogged)
+{
+ Relation indexRelation;
+ ScanKeyData skey;
+ SysScanDesc scan;
+ HeapTuple indexTuple;
+
+ /* Prepare to scan pg_index for entries having indrelid = relid. */
+ indexRelation = heap_open(IndexRelationId, AccessShareLock);
+ ScanKeyInit(&skey,
+ Anum_pg_index_indrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ relid);
+
+ scan = systable_beginscan(indexRelation, IndexIndrelidIndexId, true,
+ NULL, 1, &skey);
+
+ while (HeapTupleIsValid(indexTuple = systable_getnext(scan)))
+ {
+ Form_pg_index index = (Form_pg_index) GETSTRUCT(indexTuple);
+ AlterTableChangeCatalogToLoggedOrUnlogged(index->indexrelid, relrelation, toLogged);
+ }
+
+ systable_endscan(scan);
+ heap_close(indexRelation, AccessShareLock);
+}
+
+/*
+ *
+ * ALTER TABLE <name> SET {LOGGED | UNLOGGED}
+ *
+ * The AlterTableSetLoggedOrUnlogged function contains the main logic
+ * of the operation, changing the catalog for main heap, toast and indexes
+ */
+static void
+AlterTableSetLoggedOrUnlogged(Relation rel, bool toLogged)
+{
+ Relation relrelation;
+ Oid relid;
+
+ /* get relation's oid */
+ relid = RelationGetRelid(rel);
+
+ /* open pg_class to update relpersistence */
+ relrelation = heap_open(RelationRelationId, RowExclusiveLock);
+
+ /* main heap */
+ AlterTableChangeCatalogToLoggedOrUnlogged(relid, relrelation, toLogged);
+
+ /* indexes */
+ AlterTableChangeIndexesToLoggedOrUnlogged(relid, relrelation, toLogged);
+
+ /* toast heap, if any */
+ if (OidIsValid(rel->rd_rel->reltoastrelid))
+ {
+ /* toast */
+ AlterTableChangeCatalogToLoggedOrUnlogged(rel->rd_rel->reltoastrelid, relrelation, toLogged);
+
+ /* toast index */
+ AlterTableChangeIndexesToLoggedOrUnlogged(rel->rd_rel->reltoastrelid, relrelation, toLogged);
+ }
+
+ heap_close(relrelation, RowExclusiveLock);
+}
+
+/*
* Execute ALTER TABLE SET SCHEMA
*/
Oid
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a113809..bc5913a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -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
@@ -2048,6 +2048,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
{
@@ -12992,6 +13006,7 @@ unreserved_keyword:
| LOCAL
| LOCATION
| LOCK_P
+ | LOGGED
| MAPPING
| MATCH
| MATERIALIZED
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 24e60b7..56a42c3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1643,7 +1643,7 @@ psql_completion(const char *text, int start, int end)
pg_strcasecmp(prev_wd, "SET") == 0)
{
static const char *const list_TABLESET[] =
- {"(", "WITHOUT", "TABLESPACE", "SCHEMA", NULL};
+ {"(", "WITHOUT", "TABLESPACE", "SCHEMA", "LOGGED", "UNLOGGED", NULL};
COMPLETE_WITH_LIST(list_TABLESET);
}
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8364bef..ca68590 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -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 */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b52e507..17888ad 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -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)
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 9b89e58..f462548 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2426,3 +2426,62 @@ 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, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+------------------+----------------+----------------------
+ unlogged1 | u | t
+ unlogged1_f1_seq | p | t
+ unlogged1_pkey | u | t
+(3 rows)
+
+CREATE UNLOGGED TABLE unlogged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged1); -- fk reference
+CREATE UNLOGGED TABLE unlogged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged3); -- self fk reference
+ALTER TABLE unlogged3 SET LOGGED; -- skip self FK reference
+ALTER TABLE unlogged2 SET LOGGED; -- fails because exists a FK to an unlogged table
+ERROR: table unlogged2 references unlogged table unlogged1
+ALTER TABLE unlogged1 SET LOGGED;
+-- check relpersistence of an unlogged table after changed to permament
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+------------------+----------------+----------------------
+ unlogged1 | p | f
+ unlogged1_f1_seq | p | t
+ unlogged1_pkey | p | f
+(3 rows)
+
+DROP TABLE unlogged3;
+DROP TABLE unlogged2;
+DROP TABLE unlogged1;
+-- set unlogged
+CREATE TABLE logged1(f1 SERIAL PRIMARY KEY, f2 TEXT);
+-- check relpersistence of an permanent table
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+----------------+----------------+----------------------
+ logged1 | p | t
+ logged1_f1_seq | p | t
+ logged1_pkey | p | t
+(3 rows)
+
+CREATE TABLE logged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged1); -- fk reference
+CREATE TABLE logged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged3); -- self fk reference
+ALTER TABLE logged1 SET UNLOGGED; -- fails because exists a FK from a permanent table
+ERROR: table logged2 is referenced by permanent table logged1
+ALTER TABLE logged3 SET UNLOGGED; -- skip self FK reference
+ALTER TABLE logged2 SET UNLOGGED;
+ALTER TABLE logged1 SET UNLOGGED;
+-- check relpersistence of a permanent table after changed to unlogged
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+----------------+----------------+----------------------
+ logged1 | u | f
+ logged1_f1_seq | p | t
+ logged1_pkey | u | f
+(3 rows)
+
+DROP TABLE logged3;
+DROP TABLE logged2;
+DROP TABLE logged1;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 22a2dd0..3e30ca8 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1624,3 +1624,35 @@ 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, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+CREATE UNLOGGED TABLE unlogged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged1); -- fk reference
+CREATE UNLOGGED TABLE unlogged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged3); -- self fk reference
+ALTER TABLE unlogged3 SET LOGGED; -- skip self FK reference
+ALTER TABLE unlogged2 SET LOGGED; -- fails because exists a FK to an unlogged table
+ALTER TABLE unlogged1 SET LOGGED;
+-- check relpersistence of an unlogged table after changed to permament
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+DROP TABLE unlogged3;
+DROP TABLE unlogged2;
+DROP TABLE unlogged1;
+
+-- set unlogged
+CREATE TABLE logged1(f1 SERIAL PRIMARY KEY, f2 TEXT);
+-- check relpersistence of an permanent table
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+CREATE TABLE logged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged1); -- fk reference
+CREATE TABLE logged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged3); -- self fk reference
+ALTER TABLE logged1 SET UNLOGGED; -- fails because exists a FK from a permanent table
+ALTER TABLE logged3 SET UNLOGGED; -- skip self FK reference
+ALTER TABLE logged2 SET UNLOGGED;
+ALTER TABLE logged1 SET UNLOGGED;
+-- check relpersistence of a permanent table after changed to unlogged
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+DROP TABLE logged3;
+DROP TABLE logged2;
+DROP TABLE logged1;
+
Re: Fabr�zio de Royes Mello 2014-07-15 <CAFcNs+pXpmfwi_rKF-cSBOHWC+E=xtsRNxicRGAY6BcmthBNKg@mail.gmail.com>
What about the wqueue mechanism, though? Isn't that made exactly for
the kind of catalog updates you are doing?Works, but this mechanism create a new entry in pg_class for toast, so it's
a little different than use the cluster_rel that generate a new
relfilenode. The important is both mechanisms create new datafiles.
Ok, I had thought that any catalog changes in AT should be queued
using this mechanism to be executed later by ATExecCmd(). The queueing
only seems to be used for the cases that recurse into child tables,
which we don't.
Merged both to a single ATPrepSetLoggedOrUnlogged(Relation rel, bool
toLogged);
Thanks.
But they aren't duplicated... the first opens "con->confrelid" and the
other opens "con->conrelid" according "toLogged" branch.
Oh sorry. I had looked for that, but still missed it.
I removed because they are not so useful than I was thinking before.
Actually they just bloated our test cases.
Nod.
I think the tests could also use a bit more comments, notably the
commands that are expected to fail. So far I haven't tried to read
them but trusted that they did the right thing. (Though with the
SELECTs removed, it's pretty readable now.)Added some comments.
Thanks, looks nice now.
+ SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable> RESET ( <replaceable class="PARAMETER">storage_parameter</replaceable> [, ... ] ) INHERIT <replaceable class="PARAMETER">parent_table</replaceable> NO INHERIT <replaceable class="PARAMETER">parent_table</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>
That should get a footnote in the final commit message.
@@ -2857,6 +2860,8 @@ AlterTableGetLockLevel(List *cmds)
case AT_AddIndexConstraint:
case AT_ReplicaIdentity:
case AT_SetNotNull:
+ case AT_SetLogged:
+ case AT_SetUnLogged:
cmd_lockmode = AccessExclusiveLock;
break;@@ -3248,6 +3253,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* No command-specific prep needed */ pass = AT_PASS_MISC; break; + case AT_SetLogged: + case AT_SetUnLogged: + ATSimplePermissions(rel, ATT_TABLE); + ATPrepSetLoggedOrUnlogged(rel, (cmd->subtype == AT_SetLogged)); /* SET {LOGGED | UNLOGGED} */ + pass = AT_PASS_MISC; + tab->rewrite = true; + break; default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype); @@ -3529,6 +3541,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, case AT_GenericOptions: ATExecGenericOptions(rel, (List *) cmd->def); break; + case AT_SetLogged: + AlterTableSetLoggedOrUnlogged(rel, true); + break; + case AT_SetUnLogged: + AlterTableSetLoggedOrUnlogged(rel, false); + break; default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype);
I'd move all these next to the AT_DropCluster things like in all the
other lists.
/* + * ALTER TABLE <name> SET {LOGGED | UNLOGGED} + * + * Change the table persistence type from unlogged to permanent by + * rewriting the entire contents of the table and associated indexes + * into new disk files. + * + * The ATPrepSetLoggedOrUnlogged function check all precondictions
preconditions (without trailing space :)
+ * to perform the operation: + * - check if the target table is unlogged/permanente
permanent
+ * - check if not exists a foreign key to/from other unlogged/permanent
if no ... exists
+ */ +static void +ATPrepSetLoggedOrUnlogged(Relation rel, bool toLogged) +{ + char relpersistence; + + relpersistence = (toLogged) ? RELPERSISTENCE_UNLOGGED : + RELPERSISTENCE_PERMANENT; + + /* check if is an unlogged or permanent relation */ + if (rel->rd_rel->relpersistence != relpersistence) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("table %s is not %s", + RelationGetRelationName(rel), + (toLogged) ? "unlogged" : "permanent")));
I think this will break translation of the error message; you will
likely need to provide two full strings. (I don't know if
errmsg(toLogged ? "" : ""...) is acceptable or if you need to put a
full if() around it.)
+/* + * AlterTableSetLoggedUnloggedCheckForeignConstraints: checks for Foreign Key + * constraints consistency when changing from unlogged to permanent or + * from permanent to unlogged.
checks foreign key
constraint consistency when changing relation persistence.
+ *
+ * Throws an exception when:
"when: if" is duplicated.
Throws exceptions:
+ * + * - if changing to permanent (toLogged = true) then checks if doesn't + * exists a foreign key to another unlogged table. + * + * - if changing to unlogged (toLogged = false) then checks if doesn't + * exists a foreign key from another permanent table.
- when changing to ... then checks in no ... exists.
+ * + * Self foreign keys are skipped from the check.
Self-referencing foreign keys ...
+ */ +static void +AlterTableSetLoggedUnloggedCheckForeignConstraints(Relation rel, bool toLogged) +{ + Relation pg_constraint; + HeapTuple tuple; + SysScanDesc scan; + ScanKeyData skey[1]; + + /* + * Fetch the constraint tuple from pg_constraint. + */ + pg_constraint = heap_open(ConstraintRelationId, AccessShareLock); + + /* scans conrelid if toLogged is true else scans confreld */ + 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), toLogged, + NULL, 1, skey);
This ": InvalidOid" needs a comment. (I have no idea what it does.)
+ while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple); + if (con->contype == CONSTRAINT_FOREIGN) + { + Relation relfk; + + if (toLogged) + { + relfk = relation_open(con->confrelid, AccessShareLock); + + /* skip if self FK or check if exists a FK to an unlogged table */
... same grammar fix as above...
+ if (RelationGetRelid(rel) != con->confrelid && + relfk->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("table %s references unlogged table %s", + RelationGetRelationName(rel), + RelationGetRelationName(relfk)))); + } + else + { + relfk = relation_open(con->conrelid, AccessShareLock); + + /* skip if self FK or check if exists a FK from a permanent table */
...
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index 22a2dd0..3e30ca8 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -1624,3 +1624,35 @@ 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, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1; +CREATE UNLOGGED TABLE unlogged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged1); -- fk reference +CREATE UNLOGGED TABLE unlogged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES unlogged3); -- self fk reference +ALTER TABLE unlogged3 SET LOGGED; -- skip self FK reference +ALTER TABLE unlogged2 SET LOGGED; -- fails because exists a FK to an unlogged table
...
+ALTER TABLE unlogged1 SET LOGGED; +-- check relpersistence of an unlogged table after changed to permament
after changing to permament
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1; +DROP TABLE unlogged3; +DROP TABLE unlogged2; +DROP TABLE unlogged1; + +-- set unlogged +CREATE TABLE logged1(f1 SERIAL PRIMARY KEY, f2 TEXT); +-- check relpersistence of an permanent table
a permanent
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1; +CREATE TABLE logged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged1); -- fk reference +CREATE TABLE logged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES logged3); -- self fk reference +ALTER TABLE logged1 SET UNLOGGED; -- fails because exists a FK from a permanent table
...
+ALTER TABLE logged3 SET UNLOGGED; -- skip self FK reference +ALTER TABLE logged2 SET UNLOGGED; +ALTER TABLE logged1 SET UNLOGGED; +-- check relpersistence of a permanent table after changed to unlogged
after changing
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1; +DROP TABLE logged3; +DROP TABLE logged2; +DROP TABLE logged1;
I think we are almost there :)
Christoph
--
cb@df7cb.de | http://www.df7cb.de/
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jul 16, 2014 at 1:10 PM, Christoph Berg <cb@df7cb.de> wrote:
Re: Fabrízio de Royes Mello 2014-07-15 <CAFcNs+pXpmfwi_rKF-cSBOHWC+E=
xtsRNxicRGAY6BcmthBNKg@mail.gmail.com>
What about the wqueue mechanism, though? Isn't that made exactly for
the kind of catalog updates you are doing?Works, but this mechanism create a new entry in pg_class for toast, so
it's
a little different than use the cluster_rel that generate a new
relfilenode. The important is both mechanisms create new datafiles.Ok, I had thought that any catalog changes in AT should be queued
using this mechanism to be executed later by ATExecCmd(). The queueing
only seems to be used for the cases that recurse into child tables,
which we don't.
Actually the AT processing ALTER TABLE subcommands in three phases:
1) Prepare the subcommands (ATPrepCmd for each subcommand)
2) Rewrite Catalogs (update system catalogs): in this phase the ATExecCmd
is called to run the properly checks and change the system catalog.
3) Rewrite Tables (if needed of course): this phase rewrite the relation as
needed (we force it setting tab>rewrite = true in ATPrepCmd)
And if we have just one subcommand (the case of SET (UN)LOGGED) then will
be exists just one entry in wqueue .
Anyway I think all is ok now. Is this ok for you?
+ SET TABLESPACE <replaceable
class="PARAMETER">new_tablespace</replaceable>
RESET ( <replaceable
class="PARAMETER">storage_parameter</replaceable> [, ... ] )
INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
NO INHERIT <replaceable
class="PARAMETER">parent_table</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>
That should get a footnote in the final commit message.
Sorry, I didn't understand what you meant.
@@ -2857,6 +2860,8 @@ AlterTableGetLockLevel(List *cmds) case AT_AddIndexConstraint: case AT_ReplicaIdentity: case AT_SetNotNull: + case AT_SetLogged: + case AT_SetUnLogged: cmd_lockmode = AccessExclusiveLock; break;@@ -3248,6 +3253,13 @@ ATPrepCmd(List **wqueue, Relation rel,
AlterTableCmd *cmd,
/* No command-specific prep needed */ pass = AT_PASS_MISC; break; + case AT_SetLogged: + case AT_SetUnLogged: + ATSimplePermissions(rel, ATT_TABLE); + ATPrepSetLoggedOrUnlogged(rel, (cmd->subtype ==
AT_SetLogged)); /* SET {LOGGED | UNLOGGED} */
+ pass = AT_PASS_MISC; + tab->rewrite = true; + break; default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype); @@ -3529,6 +3541,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
Relation rel,
case AT_GenericOptions: ATExecGenericOptions(rel, (List *) cmd->def); break; + case AT_SetLogged: + AlterTableSetLoggedOrUnlogged(rel, true); + break; + case AT_SetUnLogged: + AlterTableSetLoggedOrUnlogged(rel, false); + break; default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype);I'd move all these next to the AT_DropCluster things like in all the
other lists.
Fixed.
/* + * ALTER TABLE <name> SET {LOGGED | UNLOGGED} + * + * Change the table persistence type from unlogged to permanent by + * rewriting the entire contents of the table and associated indexes + * into new disk files. + * + * The ATPrepSetLoggedOrUnlogged function check all precondictionspreconditions (without trailing space :)
Fixed.
+ * to perform the operation: + * - check if the target table is unlogged/permanentepermanent
Fixed.
+ * - check if not exists a foreign key to/from other unlogged/permanent
if no ... exists
Fixed.
+ */ +static void +ATPrepSetLoggedOrUnlogged(Relation rel, bool toLogged) +{ + char relpersistence; + + relpersistence = (toLogged) ? RELPERSISTENCE_UNLOGGED : + RELPERSISTENCE_PERMANENT; + + /* check if is an unlogged or permanent relation */ + if (rel->rd_rel->relpersistence != relpersistence) + ereport(ERROR, +
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s is not %s", +
RelationGetRelationName(rel),
+ (toLogged) ? "unlogged"
: "permanent")));
I think this will break translation of the error message; you will
likely need to provide two full strings. (I don't know if
errmsg(toLogged ? "" : ""...) is acceptable or if you need to put a
full if() around it.)
Fixed.
+/* + * AlterTableSetLoggedUnloggedCheckForeignConstraints: checks for
Foreign Key
+ * constraints consistency when changing from unlogged to permanent or + * from permanent to unlogged.checks foreign key
constraint consistency when changing relation persistence.
Fixed.
+ *
+ * Throws an exception when:"when: if" is duplicated.
Throws exceptions:
Fixed.
+ * + * - if changing to permanent (toLogged = true) then checks if doesn't + * exists a foreign key to another unlogged table. + * + * - if changing to unlogged (toLogged = false) then checks if doesn't + * exists a foreign key from another permanent table.- when changing to ... then checks in no ... exists.
Fixed. (learning a lot about English grammar... thanks)
+ * + * Self foreign keys are skipped from the check.Self-referencing foreign keys ...
Fixed.
+ */ +static void +AlterTableSetLoggedUnloggedCheckForeignConstraints(Relation rel, bool
toLogged)
+{ + Relation pg_constraint; + HeapTuple tuple; + SysScanDesc scan; + ScanKeyData skey[1]; + + /* + * Fetch the constraint tuple from pg_constraint. + */ + pg_constraint = heap_open(ConstraintRelationId, AccessShareLock); + + /* scans conrelid if toLogged is true else scans confreld */ + 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), toLogged,
+ NULL, 1, skey);
This ": InvalidOid" needs a comment. (I have no idea what it does.)
The second argument of "systable_beginscan" is the Oid of index to
conditionally use. If we search by "conrelid" we have an index on pg_proc
for this column, but "confrelid" don't has an index. See another use case
in src/backend/commands/vacuum.c:812
Added a comment.
+ while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + Form_pg_constraint con = (Form_pg_constraint)
GETSTRUCT(tuple);
+ if (con->contype == CONSTRAINT_FOREIGN) + { + Relation relfk; + + if (toLogged) + { + relfk = relation_open(con->confrelid,
AccessShareLock);
+ + /* skip if self FK or check if exists a
FK to an unlogged table */
... same grammar fix as above...
Fixed.
+ if (RelationGetRelid(rel) !=
con->confrelid &&
+ relfk->rd_rel->relpersistence !=
RELPERSISTENCE_PERMANENT)
+ ereport(ERROR, +
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s
references unlogged table %s",
+
RelationGetRelationName(rel),
+
RelationGetRelationName(relfk))));
+ } + else + { + relfk = relation_open(con->conrelid,
AccessShareLock);
+ + /* skip if self FK or check if exists a
FK from a permanent table */
...
Fixed.
diff --git a/src/test/regress/sql/alter_table.sql
b/src/test/regress/sql/alter_table.sql
index 22a2dd0..3e30ca8 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -1624,3 +1624,35 @@ 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, relpersistence, oid = relfilenode AS
original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+CREATE UNLOGGED TABLE unlogged2(f1 SERIAL PRIMARY KEY, f2 INTEGER
REFERENCES unlogged1); -- fk reference
+CREATE UNLOGGED TABLE unlogged3(f1 SERIAL PRIMARY KEY, f2 INTEGER
REFERENCES unlogged3); -- self fk reference
+ALTER TABLE unlogged3 SET LOGGED; -- skip self FK reference +ALTER TABLE unlogged2 SET LOGGED; -- fails because exists a FK to an
unlogged table
...
Fixed.
+ALTER TABLE unlogged1 SET LOGGED; +-- check relpersistence of an unlogged table after changed to permamentafter changing to permament
Fixed.
+SELECT relname, relpersistence, oid = relfilenode AS
original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+DROP TABLE unlogged3; +DROP TABLE unlogged2; +DROP TABLE unlogged1; + +-- set unlogged +CREATE TABLE logged1(f1 SERIAL PRIMARY KEY, f2 TEXT); +-- check relpersistence of an permanent tablea permanent
Fixed.
+SELECT relname, relpersistence, oid = relfilenode AS
original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+CREATE TABLE logged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES
logged1); -- fk reference
+CREATE TABLE logged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES
logged3); -- self fk reference
+ALTER TABLE logged1 SET UNLOGGED; -- fails because exists a FK from a
permanent table
...
Fixed.
+ALTER TABLE logged3 SET UNLOGGED; -- skip self FK reference +ALTER TABLE logged2 SET UNLOGGED; +ALTER TABLE logged1 SET UNLOGGED; +-- check relpersistence of a permanent table after changed to unloggedafter changing
Fixed.
I think we are almost there :)
Yeah... thanks a lot for your help.
Att,
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog sobre TI: http://fabriziomello.blogspot.com
Perfil Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Attachments:
gsoc2014_alter_table_set_logged_v6.patchtext/x-diff; charset=US-ASCII; name=gsoc2014_alter_table_set_logged_v6.patchDownload
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 69a1e14..2d131df 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -59,16 +59,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
ENABLE ALWAYS RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable>
CLUSTER ON <replaceable class="PARAMETER">index_name</replaceable>
SET WITHOUT CLUSTER
+ SET {LOGGED | UNLOGGED}
SET WITH OIDS
SET WITHOUT OIDS
SET ( <replaceable class="PARAMETER">storage_parameter</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
+ SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
RESET ( <replaceable class="PARAMETER">storage_parameter</replaceable> [, ... ] )
INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
NO INHERIT <replaceable class="PARAMETER">parent_table</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>
@@ -447,6 +448,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
</varlistentry>
<varlistentry>
+ <term><literal>SET {LOGGED | UNLOGGED}</literal></term>
+ <listitem>
+ <para>
+ This form changes the table persistence type from unlogged to permanent or
+ from unlogged to permanent (see <xref linkend="SQL-CREATETABLE-UNLOGGED">).
+ </para>
+ <para>
+ Changing the table persistence type acquires an <literal>ACCESS EXCLUSIVE</literal> lock
+ and rewrites the table contents and associated indexes into new disk files.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>SET WITH OIDS</literal></term>
<listitem>
<para>
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5dc4d18..3ce2da2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -384,6 +384,9 @@ 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 ATExecDropCluster(Relation rel, LOCKMODE lockmode);
+static void ATPrepSetLoggedUnlogged(Relation rel, bool toLogged);
+static void AlterTableSetLoggedUnloggedCheckForeignConstraints(Relation rel, bool toLogged);
+static void ATExecSetLoggedUnlogged(Relation rel, bool toLogged);
static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
char *tablespacename, LOCKMODE lockmode);
static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
@@ -2949,6 +2952,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 +3169,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_SetLogged: /* SET LOGGED */
+ case AT_SetUnLogged: /* SET UNLOGGED */
+ ATSimplePermissions(rel, ATT_TABLE);
+ ATPrepSetLoggedUnlogged(rel, (cmd->subtype == AT_SetLogged));
+ pass = AT_PASS_MISC;
+ tab->rewrite = true; /* force the rewrite of the relation */
+ break;
case AT_AddOids: /* SET WITH OIDS */
ATSimplePermissions(rel, ATT_TABLE);
if (!rel->rd_rel->relhasoids || recursing)
@@ -3431,6 +3446,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_DropCluster: /* SET WITHOUT CLUSTER */
ATExecDropCluster(rel, lockmode);
break;
+ case AT_SetLogged: /* SET LOGGED */
+ ATExecSetLoggedUnlogged(rel, true);
+ break;
+ case AT_SetUnLogged: /* SET UNLOGGED */
+ ATExecSetLoggedUnlogged(rel, false);
+ break;
case AT_AddOids: /* SET WITH OIDS */
/* Use the ADD COLUMN code, unless prep decided to do nothing */
if (cmd->def != NULL)
@@ -10424,6 +10445,231 @@ ATExecGenericOptions(Relation rel, List *options)
}
/*
+ * ALTER TABLE <name> SET {LOGGED | UNLOGGED}
+ *
+ * Change the table persistence type from unlogged to permanent by
+ * rewriting the entire contents of the table and associated indexes
+ * into new disk files.
+ *
+ * The ATPrepSetLoggedUnlogged function check all preconditions
+ * to perform the operation:
+ * - check if the target table is unlogged/permanent
+ * - check if no foreign key exists to/from other unlogged/permanent
+ * table
+ */
+static void
+ATPrepSetLoggedUnlogged(Relation rel, bool toLogged)
+{
+ char relpersistence;
+
+ relpersistence = (toLogged) ? RELPERSISTENCE_UNLOGGED :
+ RELPERSISTENCE_PERMANENT;
+
+ /* check if is an unlogged or permanent relation */
+ if (rel->rd_rel->relpersistence != relpersistence)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg( (toLogged) ? "table %s is not unlogged" :
+ "table %s is not permanent",
+ RelationGetRelationName(rel))));
+
+ /* check fk constraints */
+ AlterTableSetLoggedUnloggedCheckForeignConstraints(rel, toLogged);
+}
+
+/*
+ * AlterTableSetLoggedUnloggedCheckForeignConstraints: checks foreign key
+ * constraints consistency when changing relation persistence.
+ *
+ * Throws exceptions:
+ *
+ * - when changing to permanent (toLogged = true) then checks if no
+ * foreign key to another unlogged table exists.
+ *
+ * - when changing to unlogged (toLogged = false) then checks if do
+ * foreign key from another permanent table exists.
+ *
+ * Self-referencing foreign keys are skipped from the check.
+ */
+static void
+AlterTableSetLoggedUnloggedCheckForeignConstraints(Relation rel, bool toLogged)
+{
+ Relation pg_constraint;
+ HeapTuple tuple;
+ SysScanDesc scan;
+ ScanKeyData skey[1];
+
+ /*
+ * Fetch the constraint tuple from pg_constraint.
+ */
+ pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
+
+ /* scans conrelid if toLogged is true else scans confreld */
+ ScanKeyInit(&skey[0],
+ ((toLogged) ? Anum_pg_constraint_conrelid : Anum_pg_constraint_confrelid),
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+
+ scan = systable_beginscan(pg_constraint,
+ /* don't use index to scan if changing to unlogged */
+ ((toLogged) ? ConstraintRelidIndexId : InvalidOid),
+ toLogged, NULL, 1, skey);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
+ if (con->contype == CONSTRAINT_FOREIGN)
+ {
+ Relation relfk;
+
+ if (toLogged)
+ {
+ relfk = relation_open(con->confrelid, AccessShareLock);
+
+ /*
+ * Skip if self-referencing foreign key or check if a foreign key
+ * to an unlogged table exists
+ */
+ if (RelationGetRelid(rel) != con->confrelid &&
+ relfk->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s references unlogged table %s",
+ RelationGetRelationName(rel),
+ RelationGetRelationName(relfk))));
+ }
+ else
+ {
+ relfk = relation_open(con->conrelid, AccessShareLock);
+
+ /*
+ * Skip if self-referencing foreign key or check if a foreign key
+ * from a permanent table exists
+ */
+ if (RelationGetRelid(rel) != con->conrelid &&
+ relfk->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s is referenced by permanent table %s",
+ RelationGetRelationName(relfk),
+ RelationGetRelationName(rel))));
+ }
+
+ relation_close(relfk, AccessShareLock);
+ }
+ }
+
+ systable_endscan(scan);
+
+ heap_close(pg_constraint, AccessShareLock);
+}
+
+/*
+ * The AlterTableChangeCatalogToLoggedOrUnlogged function performs the
+ * catalog changes, i.e. update pg_class.relpersistence to 'p' or 'u'
+ */
+static void
+AlterTableChangeCatalogToLoggedOrUnlogged(Oid relid, Relation relrelation, bool toLogged)
+{
+ HeapTuple tuple;
+ Form_pg_class pg_class_form;
+ Relation rel;
+
+ /* open relation */
+ rel = relation_open(relid, AccessShareLock);
+
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u",
+ RelationGetRelid(rel));
+
+ pg_class_form = (Form_pg_class) GETSTRUCT(tuple);
+ Assert(pg_class_form->relpersistence ==
+ ((toLogged) ? RELPERSISTENCE_UNLOGGED : RELPERSISTENCE_PERMANENT));
+
+ pg_class_form->relpersistence = toLogged ?
+ RELPERSISTENCE_PERMANENT : RELPERSISTENCE_UNLOGGED;
+ simple_heap_update(relrelation, &tuple->t_self, tuple);
+
+ /* keep catalog indexes current */
+ CatalogUpdateIndexes(relrelation, tuple);
+
+ heap_freetuple(tuple);
+ heap_close(rel, AccessShareLock);
+}
+
+/*
+ * The AlterTableChangeIndexesToLoggedOrUnlogged function scans all indexes
+ * of a relation to change the relpersistence of each one
+ */
+static void
+AlterTableChangeIndexesToLoggedOrUnlogged(Oid relid, Relation relrelation, bool toLogged)
+{
+ Relation indexRelation;
+ ScanKeyData skey;
+ SysScanDesc scan;
+ HeapTuple indexTuple;
+
+ /* Prepare to scan pg_index for entries having indrelid = relid. */
+ indexRelation = heap_open(IndexRelationId, AccessShareLock);
+ ScanKeyInit(&skey,
+ Anum_pg_index_indrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ relid);
+
+ scan = systable_beginscan(indexRelation, IndexIndrelidIndexId, true,
+ NULL, 1, &skey);
+
+ while (HeapTupleIsValid(indexTuple = systable_getnext(scan)))
+ {
+ Form_pg_index index = (Form_pg_index) GETSTRUCT(indexTuple);
+ AlterTableChangeCatalogToLoggedOrUnlogged(index->indexrelid, relrelation, toLogged);
+ }
+
+ systable_endscan(scan);
+ heap_close(indexRelation, AccessShareLock);
+}
+
+/*
+ *
+ * ALTER TABLE <name> SET {LOGGED | UNLOGGED}
+ *
+ * The ATExecSetLoggedUnlogged function contains the main logic of the
+ * operation, changing the catalog for main heap, toast and indexes
+ */
+static void
+ATExecSetLoggedUnlogged(Relation rel, bool toLogged)
+{
+ Relation relrelation;
+ Oid relid;
+
+ /* get relation's oid */
+ relid = RelationGetRelid(rel);
+
+ /* open pg_class to update relpersistence */
+ relrelation = heap_open(RelationRelationId, RowExclusiveLock);
+
+ /* main heap */
+ AlterTableChangeCatalogToLoggedOrUnlogged(relid, relrelation, toLogged);
+
+ /* indexes */
+ AlterTableChangeIndexesToLoggedOrUnlogged(relid, relrelation, toLogged);
+
+ /* toast heap, if any */
+ if (OidIsValid(rel->rd_rel->reltoastrelid))
+ {
+ /* toast */
+ AlterTableChangeCatalogToLoggedOrUnlogged(rel->rd_rel->reltoastrelid, relrelation, toLogged);
+
+ /* toast index */
+ AlterTableChangeIndexesToLoggedOrUnlogged(rel->rd_rel->reltoastrelid, relrelation, toLogged);
+ }
+
+ heap_close(relrelation, RowExclusiveLock);
+}
+
+/*
* Execute ALTER TABLE SET SCHEMA
*/
Oid
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a113809..bc5913a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -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
@@ -2048,6 +2048,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
{
@@ -12992,6 +13006,7 @@ unreserved_keyword:
| LOCAL
| LOCATION
| LOCK_P
+ | LOGGED
| MAPPING
| MATCH
| MATERIALIZED
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 24e60b7..56a42c3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1643,7 +1643,7 @@ psql_completion(const char *text, int start, int end)
pg_strcasecmp(prev_wd, "SET") == 0)
{
static const char *const list_TABLESET[] =
- {"(", "WITHOUT", "TABLESPACE", "SCHEMA", NULL};
+ {"(", "WITHOUT", "TABLESPACE", "SCHEMA", "LOGGED", "UNLOGGED", NULL};
COMPLETE_WITH_LIST(list_TABLESET);
}
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8364bef..ca68590 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -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 */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b52e507..17888ad 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -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)
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 9b89e58..6d46f6f 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2426,3 +2426,62 @@ 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, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+------------------+----------------+----------------------
+ unlogged1 | u | t
+ unlogged1_f1_seq | p | t
+ unlogged1_pkey | u | t
+(3 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: table unlogged2 references unlogged table unlogged1
+ALTER TABLE unlogged1 SET LOGGED;
+-- check relpersistence of an unlogged table after changing to permament
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+------------------+----------------+----------------------
+ unlogged1 | p | f
+ unlogged1_f1_seq | p | t
+ unlogged1_pkey | p | f
+(3 rows)
+
+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, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+----------------+----------------+----------------------
+ logged1 | p | t
+ logged1_f1_seq | p | t
+ logged1_pkey | p | t
+(3 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: table logged2 is referenced by permanent 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, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+----------------+----------------+----------------------
+ logged1 | u | f
+ logged1_f1_seq | p | t
+ logged1_pkey | u | f
+(3 rows)
+
+DROP TABLE logged3;
+DROP TABLE logged2;
+DROP TABLE logged1;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 22a2dd0..a7c12ac 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1624,3 +1624,35 @@ 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, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+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, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+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, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+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, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+DROP TABLE logged3;
+DROP TABLE logged2;
+DROP TABLE logged1;
+
On Wed, Jul 16, 2014 at 3:13 PM, Fabrízio de Royes Mello <
fabriziomello@gmail.com> wrote:
On Wed, Jul 16, 2014 at 1:10 PM, Christoph Berg <cb@df7cb.de> wrote:
Re: Fabrízio de Royes Mello 2014-07-15 <CAFcNs+pXpmfwi_rKF-cSBOHWC+E=
xtsRNxicRGAY6BcmthBNKg@mail.gmail.com>
What about the wqueue mechanism, though? Isn't that made exactly for
the kind of catalog updates you are doing?Works, but this mechanism create a new entry in pg_class for toast,
so it's
a little different than use the cluster_rel that generate a new
relfilenode. The important is both mechanisms create new datafiles.Ok, I had thought that any catalog changes in AT should be queued
using this mechanism to be executed later by ATExecCmd(). The queueing
only seems to be used for the cases that recurse into child tables,
which we don't.Actually the AT processing ALTER TABLE subcommands in three phases:
1) Prepare the subcommands (ATPrepCmd for each subcommand)
2) Rewrite Catalogs (update system catalogs): in this phase the ATExecCmd
is called to run the properly checks and change the system catalog.
3) Rewrite Tables (if needed of course): this phase rewrite the relation
as needed (we force it setting tab>rewrite = true in ATPrepCmd)
And if we have just one subcommand (the case of SET (UN)LOGGED) then will
be exists just one entry in wqueue .
Anyway I think all is ok now. Is this ok for you?
+ SET TABLESPACE <replaceable
class="PARAMETER">new_tablespace</replaceable>
RESET ( <replaceable
class="PARAMETER">storage_parameter</replaceable> [, ... ] )
INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
NO INHERIT <replaceable
class="PARAMETER">parent_table</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>
That should get a footnote in the final commit message.
Sorry, I didn't understand what you meant.
@@ -2857,6 +2860,8 @@ AlterTableGetLockLevel(List *cmds) case AT_AddIndexConstraint: case AT_ReplicaIdentity: case AT_SetNotNull: + case AT_SetLogged: + case AT_SetUnLogged: cmd_lockmode = AccessExclusiveLock; break;@@ -3248,6 +3253,13 @@ ATPrepCmd(List **wqueue, Relation rel,
AlterTableCmd *cmd,
/* No command-specific prep needed */ pass = AT_PASS_MISC; break; + case AT_SetLogged: + case AT_SetUnLogged: + ATSimplePermissions(rel, ATT_TABLE); + ATPrepSetLoggedOrUnlogged(rel, (cmd->subtype ==
AT_SetLogged)); /* SET {LOGGED | UNLOGGED} */
+ pass = AT_PASS_MISC; + tab->rewrite = true; + break; default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype); @@ -3529,6 +3541,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo
*tab, Relation rel,
case AT_GenericOptions: ATExecGenericOptions(rel, (List *) cmd->def); break; + case AT_SetLogged: + AlterTableSetLoggedOrUnlogged(rel, true); + break; + case AT_SetUnLogged: + AlterTableSetLoggedOrUnlogged(rel, false); + break; default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype);I'd move all these next to the AT_DropCluster things like in all the
other lists.Fixed.
/* + * ALTER TABLE <name> SET {LOGGED | UNLOGGED} + * + * Change the table persistence type from unlogged to permanent by + * rewriting the entire contents of the table and associated indexes + * into new disk files. + * + * The ATPrepSetLoggedOrUnlogged function check all precondictionspreconditions (without trailing space :)
Fixed.
+ * to perform the operation: + * - check if the target table is unlogged/permanentepermanent
Fixed.
+ * - check if not exists a foreign key to/from other
unlogged/permanent
if no ... exists
Fixed.
+ */ +static void +ATPrepSetLoggedOrUnlogged(Relation rel, bool toLogged) +{ + char relpersistence; + + relpersistence = (toLogged) ? RELPERSISTENCE_UNLOGGED : + RELPERSISTENCE_PERMANENT; + + /* check if is an unlogged or permanent relation */ + if (rel->rd_rel->relpersistence != relpersistence) + ereport(ERROR, +
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s is not %s", +
RelationGetRelationName(rel),
+ (toLogged) ?
"unlogged" : "permanent")));
I think this will break translation of the error message; you will
likely need to provide two full strings. (I don't know if
errmsg(toLogged ? "" : ""...) is acceptable or if you need to put a
full if() around it.)Fixed.
+/* + * AlterTableSetLoggedUnloggedCheckForeignConstraints: checks for
Foreign Key
+ * constraints consistency when changing from unlogged to permanent
or
+ * from permanent to unlogged.
checks foreign key
constraint consistency when changing relation persistence.Fixed.
+ *
+ * Throws an exception when:"when: if" is duplicated.
Throws exceptions:
Fixed.
+ * + * - if changing to permanent (toLogged = true) then checks if
doesn't
+ * exists a foreign key to another unlogged table. + * + * - if changing to unlogged (toLogged = false) then checks if
doesn't
+ * exists a foreign key from another permanent table.
- when changing to ... then checks in no ... exists.
Fixed. (learning a lot about English grammar... thanks)
+ * + * Self foreign keys are skipped from the check.Self-referencing foreign keys ...
Fixed.
+ */ +static void +AlterTableSetLoggedUnloggedCheckForeignConstraints(Relation rel,
bool toLogged)
+{ + Relation pg_constraint; + HeapTuple tuple; + SysScanDesc scan; + ScanKeyData skey[1]; + + /* + * Fetch the constraint tuple from pg_constraint. + */ + pg_constraint = heap_open(ConstraintRelationId,
AccessShareLock);
+ + /* scans conrelid if toLogged is true else scans confreld */ + 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), toLogged,
+ NULL, 1, skey);
This ": InvalidOid" needs a comment. (I have no idea what it does.)
The second argument of "systable_beginscan" is the Oid of index to
conditionally use. If we search by "conrelid" we have an index on pg_proc
for this column, but "confrelid" don't has an index. See another use case
in src/backend/commands/vacuum.c:812
Added a comment.
+ while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + Form_pg_constraint con = (Form_pg_constraint)
GETSTRUCT(tuple);
+ if (con->contype == CONSTRAINT_FOREIGN) + { + Relation relfk; + + if (toLogged) + { + relfk = relation_open(con->confrelid,
AccessShareLock);
+ + /* skip if self FK or check if exists a
FK to an unlogged table */
... same grammar fix as above...
Fixed.
+ if (RelationGetRelid(rel) !=
con->confrelid &&
+ relfk->rd_rel->relpersistence
!= RELPERSISTENCE_PERMANENT)
+ ereport(ERROR, +
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table
%s references unlogged table %s",
+
RelationGetRelationName(rel),
+
RelationGetRelationName(relfk))));
+ } + else + { + relfk = relation_open(con->conrelid,
AccessShareLock);
+ + /* skip if self FK or check if exists a
FK from a permanent table */
...
Fixed.
diff --git a/src/test/regress/sql/alter_table.sql
b/src/test/regress/sql/alter_table.sql
index 22a2dd0..3e30ca8 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -1624,3 +1624,35 @@ 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, relpersistence, oid = relfilenode AS
original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+CREATE UNLOGGED TABLE unlogged2(f1 SERIAL PRIMARY KEY, f2 INTEGER
REFERENCES unlogged1); -- fk reference
+CREATE UNLOGGED TABLE unlogged3(f1 SERIAL PRIMARY KEY, f2 INTEGER
REFERENCES unlogged3); -- self fk reference
+ALTER TABLE unlogged3 SET LOGGED; -- skip self FK reference +ALTER TABLE unlogged2 SET LOGGED; -- fails because exists a FK to an
unlogged table
...
Fixed.
+ALTER TABLE unlogged1 SET LOGGED; +-- check relpersistence of an unlogged table after changed to
permament
after changing to permament
Fixed.
+SELECT relname, relpersistence, oid = relfilenode AS
original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+DROP TABLE unlogged3; +DROP TABLE unlogged2; +DROP TABLE unlogged1; + +-- set unlogged +CREATE TABLE logged1(f1 SERIAL PRIMARY KEY, f2 TEXT); +-- check relpersistence of an permanent tablea permanent
Fixed.
+SELECT relname, relpersistence, oid = relfilenode AS
original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+CREATE TABLE logged2(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES
logged1); -- fk reference
+CREATE TABLE logged3(f1 SERIAL PRIMARY KEY, f2 INTEGER REFERENCES
logged3); -- self fk reference
+ALTER TABLE logged1 SET UNLOGGED; -- fails because exists a FK from
a permanent table
...
Fixed.
+ALTER TABLE logged3 SET UNLOGGED; -- skip self FK reference +ALTER TABLE logged2 SET UNLOGGED; +ALTER TABLE logged1 SET UNLOGGED; +-- check relpersistence of a permanent table after changed to
unlogged
after changing
Fixed.
I think we are almost there :)
Yeah... thanks a lot for your help.
The previous patch (v6) has some trailing spaces... fixed.
(sorry by the noise)
Att,
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog sobre TI: http://fabriziomello.blogspot.com
Perfil Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Attachments:
gsoc2014_alter_table_set_logged_v7.patchtext/x-diff; charset=US-ASCII; name=gsoc2014_alter_table_set_logged_v7.patchDownload
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 69a1e14..2d131df 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -59,16 +59,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
ENABLE ALWAYS RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable>
CLUSTER ON <replaceable class="PARAMETER">index_name</replaceable>
SET WITHOUT CLUSTER
+ SET {LOGGED | UNLOGGED}
SET WITH OIDS
SET WITHOUT OIDS
SET ( <replaceable class="PARAMETER">storage_parameter</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
+ SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
RESET ( <replaceable class="PARAMETER">storage_parameter</replaceable> [, ... ] )
INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
NO INHERIT <replaceable class="PARAMETER">parent_table</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>
@@ -447,6 +448,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
</varlistentry>
<varlistentry>
+ <term><literal>SET {LOGGED | UNLOGGED}</literal></term>
+ <listitem>
+ <para>
+ This form changes the table persistence type from unlogged to permanent or
+ from unlogged to permanent (see <xref linkend="SQL-CREATETABLE-UNLOGGED">).
+ </para>
+ <para>
+ Changing the table persistence type acquires an <literal>ACCESS EXCLUSIVE</literal> lock
+ and rewrites the table contents and associated indexes into new disk files.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>SET WITH OIDS</literal></term>
<listitem>
<para>
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5dc4d18..7d9f974 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -384,6 +384,9 @@ 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 ATExecDropCluster(Relation rel, LOCKMODE lockmode);
+static void ATPrepSetLoggedUnlogged(Relation rel, bool toLogged);
+static void AlterTableSetLoggedUnloggedCheckForeignConstraints(Relation rel, bool toLogged);
+static void ATExecSetLoggedUnlogged(Relation rel, bool toLogged);
static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
char *tablespacename, LOCKMODE lockmode);
static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
@@ -2949,6 +2952,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 +3169,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_SetLogged: /* SET LOGGED */
+ case AT_SetUnLogged: /* SET UNLOGGED */
+ ATSimplePermissions(rel, ATT_TABLE);
+ ATPrepSetLoggedUnlogged(rel, (cmd->subtype == AT_SetLogged));
+ pass = AT_PASS_MISC;
+ tab->rewrite = true; /* force the rewrite of the relation */
+ break;
case AT_AddOids: /* SET WITH OIDS */
ATSimplePermissions(rel, ATT_TABLE);
if (!rel->rd_rel->relhasoids || recursing)
@@ -3431,6 +3446,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_DropCluster: /* SET WITHOUT CLUSTER */
ATExecDropCluster(rel, lockmode);
break;
+ case AT_SetLogged: /* SET LOGGED */
+ ATExecSetLoggedUnlogged(rel, true);
+ break;
+ case AT_SetUnLogged: /* SET UNLOGGED */
+ ATExecSetLoggedUnlogged(rel, false);
+ break;
case AT_AddOids: /* SET WITH OIDS */
/* Use the ADD COLUMN code, unless prep decided to do nothing */
if (cmd->def != NULL)
@@ -10424,6 +10445,231 @@ ATExecGenericOptions(Relation rel, List *options)
}
/*
+ * ALTER TABLE <name> SET {LOGGED | UNLOGGED}
+ *
+ * Change the table persistence type from unlogged to permanent by
+ * rewriting the entire contents of the table and associated indexes
+ * into new disk files.
+ *
+ * The ATPrepSetLoggedUnlogged function check all preconditions
+ * to perform the operation:
+ * - check if the target table is unlogged/permanent
+ * - check if no foreign key exists to/from other unlogged/permanent
+ * table
+ */
+static void
+ATPrepSetLoggedUnlogged(Relation rel, bool toLogged)
+{
+ char relpersistence;
+
+ relpersistence = (toLogged) ? RELPERSISTENCE_UNLOGGED :
+ RELPERSISTENCE_PERMANENT;
+
+ /* check if is an unlogged or permanent relation */
+ if (rel->rd_rel->relpersistence != relpersistence)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg( (toLogged) ? "table %s is not unlogged" :
+ "table %s is not permanent",
+ RelationGetRelationName(rel))));
+
+ /* check fk constraints */
+ AlterTableSetLoggedUnloggedCheckForeignConstraints(rel, toLogged);
+}
+
+/*
+ * AlterTableSetLoggedUnloggedCheckForeignConstraints: checks foreign key
+ * constraints consistency when changing relation persistence.
+ *
+ * Throws exceptions:
+ *
+ * - when changing to permanent (toLogged = true) then checks if no
+ * foreign key to another unlogged table exists.
+ *
+ * - when changing to unlogged (toLogged = false) then checks if do
+ * foreign key from another permanent table exists.
+ *
+ * Self-referencing foreign keys are skipped from the check.
+ */
+static void
+AlterTableSetLoggedUnloggedCheckForeignConstraints(Relation rel, bool toLogged)
+{
+ Relation pg_constraint;
+ HeapTuple tuple;
+ SysScanDesc scan;
+ ScanKeyData skey[1];
+
+ /*
+ * Fetch the constraint tuple from pg_constraint.
+ */
+ pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
+
+ /* scans conrelid if toLogged is true else scans confreld */
+ ScanKeyInit(&skey[0],
+ ((toLogged) ? Anum_pg_constraint_conrelid : Anum_pg_constraint_confrelid),
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+
+ scan = systable_beginscan(pg_constraint,
+ /* don't use index to scan if changing to unlogged */
+ ((toLogged) ? ConstraintRelidIndexId : InvalidOid),
+ toLogged, NULL, 1, skey);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
+ if (con->contype == CONSTRAINT_FOREIGN)
+ {
+ Relation relfk;
+
+ if (toLogged)
+ {
+ relfk = relation_open(con->confrelid, AccessShareLock);
+
+ /*
+ * Skip if self-referencing foreign key or check if a foreign key
+ * to an unlogged table exists
+ */
+ if (RelationGetRelid(rel) != con->confrelid &&
+ relfk->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s references unlogged table %s",
+ RelationGetRelationName(rel),
+ RelationGetRelationName(relfk))));
+ }
+ else
+ {
+ relfk = relation_open(con->conrelid, AccessShareLock);
+
+ /*
+ * Skip if self-referencing foreign key or check if a foreign key
+ * from a permanent table exists
+ */
+ if (RelationGetRelid(rel) != con->conrelid &&
+ relfk->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s is referenced by permanent table %s",
+ RelationGetRelationName(relfk),
+ RelationGetRelationName(rel))));
+ }
+
+ relation_close(relfk, AccessShareLock);
+ }
+ }
+
+ systable_endscan(scan);
+
+ heap_close(pg_constraint, AccessShareLock);
+}
+
+/*
+ * The AlterTableChangeCatalogToLoggedOrUnlogged function performs the
+ * catalog changes, i.e. update pg_class.relpersistence to 'p' or 'u'
+ */
+static void
+AlterTableChangeCatalogToLoggedOrUnlogged(Oid relid, Relation relrelation, bool toLogged)
+{
+ HeapTuple tuple;
+ Form_pg_class pg_class_form;
+ Relation rel;
+
+ /* open relation */
+ rel = relation_open(relid, AccessShareLock);
+
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u",
+ RelationGetRelid(rel));
+
+ pg_class_form = (Form_pg_class) GETSTRUCT(tuple);
+ Assert(pg_class_form->relpersistence ==
+ ((toLogged) ? RELPERSISTENCE_UNLOGGED : RELPERSISTENCE_PERMANENT));
+
+ pg_class_form->relpersistence = toLogged ?
+ RELPERSISTENCE_PERMANENT : RELPERSISTENCE_UNLOGGED;
+ simple_heap_update(relrelation, &tuple->t_self, tuple);
+
+ /* keep catalog indexes current */
+ CatalogUpdateIndexes(relrelation, tuple);
+
+ heap_freetuple(tuple);
+ heap_close(rel, AccessShareLock);
+}
+
+/*
+ * The AlterTableChangeIndexesToLoggedOrUnlogged function scans all indexes
+ * of a relation to change the relpersistence of each one
+ */
+static void
+AlterTableChangeIndexesToLoggedOrUnlogged(Oid relid, Relation relrelation, bool toLogged)
+{
+ Relation indexRelation;
+ ScanKeyData skey;
+ SysScanDesc scan;
+ HeapTuple indexTuple;
+
+ /* Prepare to scan pg_index for entries having indrelid = relid. */
+ indexRelation = heap_open(IndexRelationId, AccessShareLock);
+ ScanKeyInit(&skey,
+ Anum_pg_index_indrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ relid);
+
+ scan = systable_beginscan(indexRelation, IndexIndrelidIndexId, true,
+ NULL, 1, &skey);
+
+ while (HeapTupleIsValid(indexTuple = systable_getnext(scan)))
+ {
+ Form_pg_index index = (Form_pg_index) GETSTRUCT(indexTuple);
+ AlterTableChangeCatalogToLoggedOrUnlogged(index->indexrelid, relrelation, toLogged);
+ }
+
+ systable_endscan(scan);
+ heap_close(indexRelation, AccessShareLock);
+}
+
+/*
+ *
+ * ALTER TABLE <name> SET {LOGGED | UNLOGGED}
+ *
+ * The ATExecSetLoggedUnlogged function contains the main logic of the
+ * operation, changing the catalog for main heap, toast and indexes
+ */
+static void
+ATExecSetLoggedUnlogged(Relation rel, bool toLogged)
+{
+ Relation relrelation;
+ Oid relid;
+
+ /* get relation's oid */
+ relid = RelationGetRelid(rel);
+
+ /* open pg_class to update relpersistence */
+ relrelation = heap_open(RelationRelationId, RowExclusiveLock);
+
+ /* main heap */
+ AlterTableChangeCatalogToLoggedOrUnlogged(relid, relrelation, toLogged);
+
+ /* indexes */
+ AlterTableChangeIndexesToLoggedOrUnlogged(relid, relrelation, toLogged);
+
+ /* toast heap, if any */
+ if (OidIsValid(rel->rd_rel->reltoastrelid))
+ {
+ /* toast */
+ AlterTableChangeCatalogToLoggedOrUnlogged(rel->rd_rel->reltoastrelid, relrelation, toLogged);
+
+ /* toast index */
+ AlterTableChangeIndexesToLoggedOrUnlogged(rel->rd_rel->reltoastrelid, relrelation, toLogged);
+ }
+
+ heap_close(relrelation, RowExclusiveLock);
+}
+
+/*
* Execute ALTER TABLE SET SCHEMA
*/
Oid
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a113809..bc5913a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -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
@@ -2048,6 +2048,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
{
@@ -12992,6 +13006,7 @@ unreserved_keyword:
| LOCAL
| LOCATION
| LOCK_P
+ | LOGGED
| MAPPING
| MATCH
| MATERIALIZED
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 24e60b7..56a42c3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1643,7 +1643,7 @@ psql_completion(const char *text, int start, int end)
pg_strcasecmp(prev_wd, "SET") == 0)
{
static const char *const list_TABLESET[] =
- {"(", "WITHOUT", "TABLESPACE", "SCHEMA", NULL};
+ {"(", "WITHOUT", "TABLESPACE", "SCHEMA", "LOGGED", "UNLOGGED", NULL};
COMPLETE_WITH_LIST(list_TABLESET);
}
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8364bef..ca68590 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -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 */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b52e507..17888ad 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -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)
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 9b89e58..6d46f6f 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2426,3 +2426,62 @@ 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, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+------------------+----------------+----------------------
+ unlogged1 | u | t
+ unlogged1_f1_seq | p | t
+ unlogged1_pkey | u | t
+(3 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: table unlogged2 references unlogged table unlogged1
+ALTER TABLE unlogged1 SET LOGGED;
+-- check relpersistence of an unlogged table after changing to permament
+SELECT relname, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+------------------+----------------+----------------------
+ unlogged1 | p | f
+ unlogged1_f1_seq | p | t
+ unlogged1_pkey | p | f
+(3 rows)
+
+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, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+----------------+----------------+----------------------
+ logged1 | p | t
+ logged1_f1_seq | p | t
+ logged1_pkey | p | t
+(3 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: table logged2 is referenced by permanent 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, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+ relname | relpersistence | original_relfilenode
+----------------+----------------+----------------------
+ logged1 | u | f
+ logged1_f1_seq | p | t
+ logged1_pkey | u | f
+(3 rows)
+
+DROP TABLE logged3;
+DROP TABLE logged2;
+DROP TABLE logged1;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 22a2dd0..a7c12ac 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1624,3 +1624,35 @@ 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, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+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, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^unlogged1' ORDER BY 1;
+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, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+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, relpersistence, oid = relfilenode AS original_relfilenode FROM pg_class WHERE relname ~ '^logged1' ORDER BY 1;
+DROP TABLE logged3;
+DROP TABLE logged2;
+DROP TABLE logged1;
+
Hi,
I quickly looked at this patch and I think there's major missing pieces
around buffer management and wal logging.
a) Currently buffers that are in memory marked as
permanent/non-permanent aren't forced out to disk/pruned from cache,
not even when they're dirty.
b) When converting from a unlogged to a logged table the relation needs
to be fsynced.
c) Currently a unlogged table changed into a logged one will be
corrupted on a standby because its contents won't ever be WAL logged.
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2014-07-16 20:25:42 +0200, Andres Freund wrote:
Hi,
I quickly looked at this patch and I think there's major missing pieces
around buffer management and wal logging.a) Currently buffers that are in memory marked as
permanent/non-permanent aren't forced out to disk/pruned from cache,
not even when they're dirty.
b) When converting from a unlogged to a logged table the relation needs
to be fsynced.
c) Currently a unlogged table changed into a logged one will be
corrupted on a standby because its contents won't ever be WAL logged.
Forget that, didn't notice that you're setting tab->rewrite = true.
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jul 16, 2014 at 3:53 PM, Andres Freund <andres@2ndquadrant.com>
wrote:
On 2014-07-16 20:25:42 +0200, Andres Freund wrote:
Hi,
I quickly looked at this patch and I think there's major missing pieces
around buffer management and wal logging.a) Currently buffers that are in memory marked as
permanent/non-permanent aren't forced out to disk/pruned from cache,
not even when they're dirty.
b) When converting from a unlogged to a logged table the relation needs
to be fsynced.
c) Currently a unlogged table changed into a logged one will be
corrupted on a standby because its contents won't ever be WAL logged.Forget that, didn't notice that you're setting tab->rewrite = true.
:-)
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog sobre TI: http://fabriziomello.blogspot.com
Perfil Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
On 2014-07-16 20:53:06 +0200, Andres Freund wrote:
On 2014-07-16 20:25:42 +0200, Andres Freund wrote:
Hi,
I quickly looked at this patch and I think there's major missing pieces
around buffer management and wal logging.a) Currently buffers that are in memory marked as
permanent/non-permanent aren't forced out to disk/pruned from cache,
not even when they're dirty.
b) When converting from a unlogged to a logged table the relation needs
to be fsynced.
c) Currently a unlogged table changed into a logged one will be
corrupted on a standby because its contents won't ever be WAL logged.Forget that, didn't notice that you're setting tab->rewrite = true.
So, while that danger luckily isn't there I think there's something
similar. Consider:
CREATE TABLE blub(...);
INSERT INTO blub ...;
BEGIN;
ALTER TABLE blub SET UNLOGGED;
ROLLBACK;
The rewrite will read in the 'old' contents - but because it's done
after the pg_class.relpersistence is changed they'll all not be marked
as BM_PERMANENT in memory. Then the ALTER TABLE is rolled back,
including the relpersistence setting. Which will unfortunately leave
pages with the wrong persistency setting in memory, right?
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jul 16, 2014 at 7:26 PM, Andres Freund <andres@2ndquadrant.com>
wrote:
On 2014-07-16 20:53:06 +0200, Andres Freund wrote:
On 2014-07-16 20:25:42 +0200, Andres Freund wrote:
Hi,
I quickly looked at this patch and I think there's major missing
pieces
around buffer management and wal logging.
a) Currently buffers that are in memory marked as
permanent/non-permanent aren't forced out to disk/pruned from
cache,
not even when they're dirty.
b) When converting from a unlogged to a logged table the relation
needs
to be fsynced.
c) Currently a unlogged table changed into a logged one will be
corrupted on a standby because its contents won't ever be WAL
logged.
Forget that, didn't notice that you're setting tab->rewrite = true.
So, while that danger luckily isn't there I think there's something
similar. Consider:CREATE TABLE blub(...);
INSERT INTO blub ...;BEGIN;
ALTER TABLE blub SET UNLOGGED;
ROLLBACK;The rewrite will read in the 'old' contents - but because it's done
after the pg_class.relpersistence is changed they'll all not be marked
as BM_PERMANENT in memory. Then the ALTER TABLE is rolled back,
including the relpersistence setting. Which will unfortunately leave
pages with the wrong persistency setting in memory, right?
That means should I "FlushRelationBuffers(rel)" before change the
relpersistence?
Regards,
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog sobre TI: http://fabriziomello.blogspot.com
Perfil Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
On 2014-07-16 20:45:15 -0300, Fabr�zio de Royes Mello wrote:
On Wed, Jul 16, 2014 at 7:26 PM, Andres Freund <andres@2ndquadrant.com>
wrote:On 2014-07-16 20:53:06 +0200, Andres Freund wrote:
On 2014-07-16 20:25:42 +0200, Andres Freund wrote:
Hi,
I quickly looked at this patch and I think there's major missing
pieces
around buffer management and wal logging.
a) Currently buffers that are in memory marked as
permanent/non-permanent aren't forced out to disk/pruned fromcache,
not even when they're dirty.
b) When converting from a unlogged to a logged table the relationneeds
to be fsynced.
c) Currently a unlogged table changed into a logged one will be
corrupted on a standby because its contents won't ever be WALlogged.
Forget that, didn't notice that you're setting tab->rewrite = true.
So, while that danger luckily isn't there I think there's something
similar. Consider:CREATE TABLE blub(...);
INSERT INTO blub ...;BEGIN;
ALTER TABLE blub SET UNLOGGED;
ROLLBACK;The rewrite will read in the 'old' contents - but because it's done
after the pg_class.relpersistence is changed they'll all not be marked
as BM_PERMANENT in memory. Then the ALTER TABLE is rolled back,
including the relpersistence setting. Which will unfortunately leave
pages with the wrong persistency setting in memory, right?That means should I "FlushRelationBuffers(rel)" before change the
relpersistence?
I don't think that'd help. I think what this means that you simply
cannot change the relpersistence of the old relation before the heap
swap is successful. So I guess it has to be something like (pseudocode):
OIDNewHeap = make_new_heap(..);
newrel = heap_open(OIDNewHeap, AEL);
/*
* Change the temporary relation to be unlogged/logged. We have to do
* that here so buffers for the new relfilenode will have the right
* persistency set while the original filenode's buffers won't get read
* in with the wrong (i.e. new) persistency setting. Otherwise a
* rollback after the rewrite would possibly result with buffers for the
* original filenode having the wrong persistency setting.
*
* NB: This relies on swap_relation_files() also swapping the
* persistency. That wouldn't work for pg_class, but that can't be
* unlogged anyway.
*/
AlterTableChangeCatalogToLoggedOrUnlogged(newrel);
FlushRelationBuffers(newrel);
/* copy heap data into newrel */
finish_heap_swap();
And then in swap_relation_files() also copy the persistency.
That's the best I can come up right now at least.
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Re: Fabr�zio de Royes Mello 2014-07-16 <CAFcNs+r_LMDDxJrAbWNJ3rMY0Qegwa-vv6V2SmDescOfb2dj+Q@mail.gmail.com>
Anyway I think all is ok now. Is this ok for you?
Hi Fabr�zio,
it's ok for me now, though Andres' concerns seem valid.
+ SET TABLESPACE <replaceable
class="PARAMETER">new_tablespace</replaceable>
RESET ( <replaceable
class="PARAMETER">storage_parameter</replaceable> [, ... ] )
INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
NO INHERIT <replaceableclass="PARAMETER">parent_table</replaceable>
OF <replaceable class="PARAMETER">type_name</replaceable>
NOT OF
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
- SET TABLESPACE <replaceableclass="PARAMETER">new_tablespace</replaceable>
That should get a footnote in the final commit message.
Sorry, I didn't understand what you meant.
I meant to say that when this patch gets committed, the commit message
might want to explain that while we are at editing this doc part, we
moved SET TABLESPACE to the place where it really should be.
I think we are almost there :)
Yeah... thanks a lot for your help.
Welcome. I'll look forward to use this in production :)
Christoph
--
cb@df7cb.de | http://www.df7cb.de/
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2014-07-16 20:45:15 -0300, Fabr�zio de Royes Mello wrote:
The rewrite will read in the 'old' contents - but because it's done
after the pg_class.relpersistence is changed they'll all not be marked
as BM_PERMANENT in memory. Then the ALTER TABLE is rolled back,
including the relpersistence setting. Which will unfortunately leave
pages with the wrong persistency setting in memory, right?That means should I "FlushRelationBuffers(rel)" before change the
relpersistence?
Did my explanation clarify the problem + possible solution sufficiently?
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Jul 21, 2014 at 9:51 AM, Andres Freund <andres@2ndquadrant.com>
wrote:
On 2014-07-16 20:45:15 -0300, Fabrízio de Royes Mello wrote:
The rewrite will read in the 'old' contents - but because it's done
after the pg_class.relpersistence is changed they'll all not be marked
as BM_PERMANENT in memory. Then the ALTER TABLE is rolled back,
including the relpersistence setting. Which will unfortunately leave
pages with the wrong persistency setting in memory, right?That means should I "FlushRelationBuffers(rel)" before change the
relpersistence?Did my explanation clarify the problem + possible solution sufficiently?
Yes, your explanation was enough. I didn't had time to working on it yet.
But I hope working on it today or tomorrow at least.
Regards,
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog sobre TI: http://fabriziomello.blogspot.com
Perfil Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
On Thu, Jul 17, 2014 at 8:02 PM, Andres Freund <andres@2ndquadrant.com>
wrote:
That means should I "FlushRelationBuffers(rel)" before change the
relpersistence?I don't think that'd help. I think what this means that you simply
cannot change the relpersistence of the old relation before the heap
swap is successful. So I guess it has to be something like (pseudocode):OIDNewHeap = make_new_heap(..);
newrel = heap_open(OIDNewHeap, AEL);/*
* Change the temporary relation to be unlogged/logged. We have to do
* that here so buffers for the new relfilenode will have the right
* persistency set while the original filenode's buffers won't get read
* in with the wrong (i.e. new) persistency setting. Otherwise a
* rollback after the rewrite would possibly result with buffers for the
* original filenode having the wrong persistency setting.
*
* NB: This relies on swap_relation_files() also swapping the
* persistency. That wouldn't work for pg_class, but that can't be
* unlogged anyway.
*/
AlterTableChangeCatalogToLoggedOrUnlogged(newrel);
FlushRelationBuffers(newrel);
/* copy heap data into newrel */
finish_heap_swap();And then in swap_relation_files() also copy the persistency.
That's the best I can come up right now at least.
Isn't better if we can set the relpersistence as an argument to
"make_new_heap" ?
I'm thinking to change the make_new_heap:
From:
make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, bool forcetemp,
LOCKMODE lockmode)
To:
make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
LOCKMODE lockmode)
That way we can create the new heap with the appropriate relpersistence, so
in the swap_relation_files also copy the persistency, of course.
And after copy the heap data to the new table (ATRewriteTable) change
relpersistence of the OldHeap's indexes, because in the "finish_heap_swap"
they'll be rebuild.
Thoughts?
Regards,
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog sobre TI: http://fabriziomello.blogspot.com
Perfil Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
On Tue, Jul 22, 2014 at 12:01 PM, Fabrízio de Royes Mello <
fabriziomello@gmail.com> wrote:
On Thu, Jul 17, 2014 at 8:02 PM, Andres Freund <andres@2ndquadrant.com>
wrote:
That means should I "FlushRelationBuffers(rel)" before change the
relpersistence?I don't think that'd help. I think what this means that you simply
cannot change the relpersistence of the old relation before the heap
swap is successful. So I guess it has to be something like (pseudocode):OIDNewHeap = make_new_heap(..);
newrel = heap_open(OIDNewHeap, AEL);/*
* Change the temporary relation to be unlogged/logged. We have to do
* that here so buffers for the new relfilenode will have the right
* persistency set while the original filenode's buffers won't get read
* in with the wrong (i.e. new) persistency setting. Otherwise a
* rollback after the rewrite would possibly result with buffers for the
* original filenode having the wrong persistency setting.
*
* NB: This relies on swap_relation_files() also swapping the
* persistency. That wouldn't work for pg_class, but that can't be
* unlogged anyway.
*/
AlterTableChangeCatalogToLoggedOrUnlogged(newrel);
FlushRelationBuffers(newrel);
/* copy heap data into newrel */
finish_heap_swap();And then in swap_relation_files() also copy the persistency.
That's the best I can come up right now at least.
Isn't better if we can set the relpersistence as an argument to
"make_new_heap" ?
I'm thinking to change the make_new_heap:
From:
make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, bool forcetemp,
LOCKMODE lockmode)To:
make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
LOCKMODE lockmode)That way we can create the new heap with the appropriate relpersistence,
so in the swap_relation_files also copy the persistency, of course.
And after copy the heap data to the new table (ATRewriteTable) change
relpersistence of the OldHeap's indexes, because in the "finish_heap_swap"
they'll be rebuild.
Thoughts?
The attached patch implement my previous idea based on Andres thoughts.
Regards,
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog sobre TI: http://fabriziomello.blogspot.com
Perfil Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Attachments:
gsoc2014_alter_table_set_logged_v8.patchtext/x-diff; charset=US-ASCII; name=gsoc2014_alter_table_set_logged_v8.patchDownload
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 69a1e14..2d131df 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -59,16 +59,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
ENABLE ALWAYS RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable>
CLUSTER ON <replaceable class="PARAMETER">index_name</replaceable>
SET WITHOUT CLUSTER
+ SET {LOGGED | UNLOGGED}
SET WITH OIDS
SET WITHOUT OIDS
SET ( <replaceable class="PARAMETER">storage_parameter</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
+ SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
RESET ( <replaceable class="PARAMETER">storage_parameter</replaceable> [, ... ] )
INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
NO INHERIT <replaceable class="PARAMETER">parent_table</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>
@@ -447,6 +448,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
</varlistentry>
<varlistentry>
+ <term><literal>SET {LOGGED | UNLOGGED}</literal></term>
+ <listitem>
+ <para>
+ This form changes the table persistence type from unlogged to permanent or
+ from unlogged to permanent (see <xref linkend="SQL-CREATETABLE-UNLOGGED">).
+ </para>
+ <para>
+ Changing the table persistence type acquires an <literal>ACCESS EXCLUSIVE</literal> lock
+ and rewrites the table contents and associated indexes into new disk files.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>SET WITH OIDS</literal></term>
<listitem>
<para>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b1c411a..7f497c7 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -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 */
@@ -601,7 +602,7 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
* 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 +614,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 +636,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
@@ -1146,6 +1140,7 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
Oid relfilenode1,
relfilenode2;
Oid swaptemp;
+ char swaprelpersistence;
CatalogIndexState indstate;
/* We need writable copies of both pg_class tuples. */
@@ -1177,6 +1172,10 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
relform1->reltablespace = relform2->reltablespace;
relform2->reltablespace = swaptemp;
+ swaprelpersistence = relform1->relpersistence;
+ relform1->relpersistence = relform2->relpersistence;
+ relform2->relpersistence = swaprelpersistence;
+
/* Also swap toast links, if we're swapping by links */
if (!swap_toast_by_content)
{
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 5130d51..a49e66f 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -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);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5dc4d18..fc5b9f3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -384,6 +384,10 @@ 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 ATExecDropCluster(Relation rel, LOCKMODE lockmode);
+static void ATPrepSetLoggedUnlogged(Relation rel, bool toLogged);
+static void AlterTableSetLoggedUnloggedCheckForeignConstraints(Relation rel, bool toLogged);
+static void AlterTableChangeCatalogToLoggedOrUnlogged(Oid relid, Relation relrelation, char relpersistence);
+static void AlterTableChangeIndexesToLoggedOrUnlogged(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 +2953,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 +3170,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_SetLogged: /* SET LOGGED */
+ case AT_SetUnLogged: /* SET UNLOGGED */
+ ATSimplePermissions(rel, ATT_TABLE);
+ ATPrepSetLoggedUnlogged(rel, (cmd->subtype == AT_SetLogged));
+ pass = AT_PASS_MISC;
+ tab->rewrite = true; /* force the rewrite of the relation */
+ break;
case AT_AddOids: /* SET WITH OIDS */
ATSimplePermissions(rel, ATT_TABLE);
if (!rel->rd_rel->relhasoids || recursing)
@@ -3431,6 +3447,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)
@@ -3592,6 +3611,9 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
Relation OldHeap;
Oid OIDNewHeap;
Oid NewTableSpace;
+ int pass;
+ char newrelpersistence = RELPERSISTENCE_PERMANENT;
+ bool isSetLoggedUnlogged = false;
OldHeap = heap_open(tab->relid, NoLock);
@@ -3632,8 +3654,46 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
heap_close(OldHeap, NoLock);
- /* Create transient table that will receive the modified data */
- OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, false,
+ /* check if SetUnlogged or SetLogged exists in subcmds */
+ for(pass = 0; pass < AT_NUM_PASSES; pass++)
+ {
+ List *subcmds = tab->subcmds[pass];
+ ListCell *lcmd;
+
+ if (subcmds == NIL)
+ continue;
+
+ foreach(lcmd, subcmds)
+ {
+ AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
+
+ if (cmd->subtype == AT_SetUnLogged || cmd->subtype == AT_SetLogged)
+ {
+ /*
+ * Change the temporary relation to be unlogged/logged. We have to do
+ * that here so buffers for the new relfilenode will have the right
+ * persistency set while the original filenode's buffers won't get read
+ * in with the wrong (i.e. new) persistency setting. Otherwise a
+ * rollback after the rewrite would possibly result with buffers for the
+ * original filenode having the wrong persistency setting.
+ *
+ * NB: This relies on swap_relation_files() also swapping the
+ * persistency. That wouldn't work for pg_class, but that can't be
+ * unlogged anyway.
+ */
+ if (cmd->subtype == AT_SetUnLogged)
+ newrelpersistence = RELPERSISTENCE_UNLOGGED;
+
+ isSetLoggedUnlogged = true;
+ }
+ }
+ }
+
+ /*
+ * Create transient table that will receive the modified data
+ * with the new relpersistence in case of SET (UN)LOGGED
+ */
+ OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, newrelpersistence,
lockmode);
/*
@@ -3644,6 +3704,12 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
ATRewriteTable(tab, OIDNewHeap, lockmode);
/*
+ * Change the relpersistence of the OldHeap's indexes before reindex
+ */
+ if (isSetLoggedUnlogged)
+ AlterTableChangeIndexesToLoggedOrUnlogged(tab->relid, 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
@@ -10424,6 +10490,194 @@ ATExecGenericOptions(Relation rel, List *options)
}
/*
+ * ALTER TABLE <name> SET {LOGGED | UNLOGGED}
+ *
+ * Change the table persistence type from unlogged to permanent by
+ * rewriting the entire contents of the table and associated indexes
+ * into new disk files.
+ *
+ * The ATPrepSetLoggedUnlogged function check all preconditions
+ * to perform the operation:
+ * - check if the target table is unlogged/permanent
+ * - check if no foreign key exists to/from other unlogged/permanent
+ * table
+ */
+static void
+ATPrepSetLoggedUnlogged(Relation rel, bool toLogged)
+{
+ char relpersistence;
+
+ relpersistence = (toLogged) ? RELPERSISTENCE_UNLOGGED :
+ RELPERSISTENCE_PERMANENT;
+
+ /* check if is an unlogged or permanent relation */
+ if (rel->rd_rel->relpersistence != relpersistence)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg( (toLogged) ? "table %s is not unlogged" :
+ "table %s is not permanent",
+ RelationGetRelationName(rel))));
+
+ /* check fk constraints */
+ AlterTableSetLoggedUnloggedCheckForeignConstraints(rel, toLogged);
+}
+
+/*
+ * AlterTableSetLoggedUnloggedCheckForeignConstraints: checks foreign key
+ * constraints consistency when changing relation persistence.
+ *
+ * Throws exceptions:
+ *
+ * - when changing to permanent (toLogged = true) then checks if no
+ * foreign key to another unlogged table exists.
+ *
+ * - when changing to unlogged (toLogged = false) then checks if do
+ * foreign key from another permanent table exists.
+ *
+ * Self-referencing foreign keys are skipped from the check.
+ */
+static void
+AlterTableSetLoggedUnloggedCheckForeignConstraints(Relation rel, bool toLogged)
+{
+ Relation pg_constraint;
+ HeapTuple tuple;
+ SysScanDesc scan;
+ ScanKeyData skey[1];
+
+ /*
+ * Fetch the constraint tuple from pg_constraint.
+ */
+ pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
+
+ /* scans conrelid if toLogged is true else scans confreld */
+ ScanKeyInit(&skey[0],
+ ((toLogged) ? Anum_pg_constraint_conrelid : Anum_pg_constraint_confrelid),
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+
+ scan = systable_beginscan(pg_constraint,
+ /* don't use index to scan if changing to unlogged */
+ ((toLogged) ? ConstraintRelidIndexId : InvalidOid),
+ toLogged, NULL, 1, skey);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
+ if (con->contype == CONSTRAINT_FOREIGN)
+ {
+ Relation relfk;
+
+ if (toLogged)
+ {
+ relfk = relation_open(con->confrelid, AccessShareLock);
+
+ /*
+ * Skip if self-referencing foreign key or check if a foreign key
+ * to an unlogged table exists
+ */
+ if (RelationGetRelid(rel) != con->confrelid &&
+ relfk->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s references unlogged table %s",
+ RelationGetRelationName(rel),
+ RelationGetRelationName(relfk))));
+ }
+ else
+ {
+ relfk = relation_open(con->conrelid, AccessShareLock);
+
+ /*
+ * Skip if self-referencing foreign key or check if a foreign key
+ * from a permanent table exists
+ */
+ if (RelationGetRelid(rel) != con->conrelid &&
+ relfk->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s is referenced by permanent table %s",
+ RelationGetRelationName(relfk),
+ RelationGetRelationName(rel))));
+ }
+
+ relation_close(relfk, AccessShareLock);
+ }
+ }
+
+ systable_endscan(scan);
+
+ heap_close(pg_constraint, AccessShareLock);
+}
+
+/*
+ * The AlterTableChangeCatalogToLoggedOrUnlogged function performs the
+ * catalog changes, i.e. update pg_class.relpersistence to 'p' or 'u'
+ */
+static void
+AlterTableChangeCatalogToLoggedOrUnlogged(Oid relid, Relation relrelation, char relpersistence)
+{
+ HeapTuple tuple;
+ Form_pg_class pg_class_form;
+ Relation rel;
+
+ /* open relation */
+ rel = relation_open(relid, AccessExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u",
+ RelationGetRelid(rel));
+
+ pg_class_form = (Form_pg_class) GETSTRUCT(tuple);
+ pg_class_form->relpersistence = relpersistence;
+ simple_heap_update(relrelation, &tuple->t_self, tuple);
+
+ /* keep catalog indexes current */
+ CatalogUpdateIndexes(relrelation, tuple);
+
+ heap_freetuple(tuple);
+ heap_close(rel, AccessExclusiveLock);
+}
+
+/*
+ * The AlterTableChangeIndexesToLoggedOrUnlogged function scans all indexes
+ * of a relation to change the relpersistence of each one
+ */
+static void
+AlterTableChangeIndexesToLoggedOrUnlogged(Oid relid, char relpersistence)
+{
+ Relation indexRelation;
+ Relation relrelation;
+ ScanKeyData skey;
+ SysScanDesc scan;
+ HeapTuple indexTuple;
+
+ /* open pg_class to update relpersistence */
+ relrelation = heap_open(RelationRelationId, RowExclusiveLock);
+
+ /* Prepare to scan pg_index for entries having indrelid = relid. */
+ indexRelation = heap_open(IndexRelationId, AccessShareLock);
+ ScanKeyInit(&skey,
+ Anum_pg_index_indrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ relid);
+
+ scan = systable_beginscan(indexRelation, IndexIndrelidIndexId, true,
+ NULL, 1, &skey);
+
+ while (HeapTupleIsValid(indexTuple = systable_getnext(scan)))
+ {
+ Form_pg_index index = (Form_pg_index) GETSTRUCT(indexTuple);
+ AlterTableChangeCatalogToLoggedOrUnlogged(index->indexrelid, relrelation, relpersistence);
+ }
+
+ systable_endscan(scan);
+ heap_close(indexRelation, AccessShareLock);
+ heap_close(relrelation, RowExclusiveLock);
+}
+
+/*
* Execute ALTER TABLE SET SCHEMA
*/
Oid
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a113809..bc5913a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -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
@@ -2048,6 +2048,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
{
@@ -12992,6 +13006,7 @@ unreserved_keyword:
| LOCAL
| LOCATION
| LOCK_P
+ | LOGGED
| MAPPING
| MATCH
| MATERIALIZED
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 24e60b7..56a42c3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1643,7 +1643,7 @@ psql_completion(const char *text, int start, int end)
pg_strcasecmp(prev_wd, "SET") == 0)
{
static const char *const list_TABLESET[] =
- {"(", "WITHOUT", "TABLESPACE", "SCHEMA", NULL};
+ {"(", "WITHOUT", "TABLESPACE", "SCHEMA", "LOGGED", "UNLOGGED", NULL};
COMPLETE_WITH_LIST(list_TABLESET);
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 0ada3d6..f7730a9 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -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,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8364bef..ca68590 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -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 */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b52e507..17888ad 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -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)
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 9b89e58..bb88c7d 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2426,3 +2426,90 @@ 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: 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)
+
+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: table logged2 is referenced by permanent 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)
+
+DROP TABLE logged3;
+DROP TABLE logged2;
+DROP TABLE logged1;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 22a2dd0..ee44957 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1624,3 +1624,54 @@ 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;
+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;
+DROP TABLE logged3;
+DROP TABLE logged2;
+DROP TABLE logged1;
+
On Tue, Jul 22, 2014 at 3:29 PM, Fabrízio de Royes Mello <
fabriziomello@gmail.com> wrote:
On Tue, Jul 22, 2014 at 12:01 PM, Fabrízio de Royes Mello <
fabriziomello@gmail.com> wrote:
On Thu, Jul 17, 2014 at 8:02 PM, Andres Freund <andres@2ndquadrant.com>
wrote:
That means should I "FlushRelationBuffers(rel)" before change the
relpersistence?I don't think that'd help. I think what this means that you simply
cannot change the relpersistence of the old relation before the heap
swap is successful. So I guess it has to be something like
(pseudocode):
OIDNewHeap = make_new_heap(..);
newrel = heap_open(OIDNewHeap, AEL);/*
* Change the temporary relation to be unlogged/logged. We have to do
* that here so buffers for the new relfilenode will have the right
* persistency set while the original filenode's buffers won't get
read
* in with the wrong (i.e. new) persistency setting. Otherwise a
* rollback after the rewrite would possibly result with buffers for
the
* original filenode having the wrong persistency setting.
*
* NB: This relies on swap_relation_files() also swapping the
* persistency. That wouldn't work for pg_class, but that can't be
* unlogged anyway.
*/
AlterTableChangeCatalogToLoggedOrUnlogged(newrel);
FlushRelationBuffers(newrel);
/* copy heap data into newrel */
finish_heap_swap();And then in swap_relation_files() also copy the persistency.
That's the best I can come up right now at least.
Isn't better if we can set the relpersistence as an argument to
"make_new_heap" ?
I'm thinking to change the make_new_heap:
From:
make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, bool forcetemp,
LOCKMODE lockmode)To:
make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
LOCKMODE lockmode)That way we can create the new heap with the appropriate
relpersistence, so in the swap_relation_files also copy the persistency, of
course.
And after copy the heap data to the new table (ATRewriteTable) change
relpersistence of the OldHeap's indexes, because in the "finish_heap_swap"
they'll be rebuild.
Thoughts?
The attached patch implement my previous idea based on Andres thoughts.
I don't liked the last version of the patch, especially this part:
+ /* check if SetUnlogged or SetLogged exists in subcmds */
+ for(pass = 0; pass < AT_NUM_PASSES; pass++)
+ {
+ List *subcmds = tab->subcmds[pass];
+ ListCell *lcmd;
+
+ if (subcmds == NIL)
+ continue;
+
+ foreach(lcmd, subcmds)
+ {
+ AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
+
+ if (cmd->subtype == AT_SetUnLogged || cmd->subtype ==
AT_SetLogged)
+ {
+ /*
+ * Change the temporary relation to be
unlogged/logged. We have to do
+ * that here so buffers for the new relfilenode
will have the right
+ * persistency set while the original filenode's
buffers won't get read
+ * in with the wrong (i.e. new) persistency
setting. Otherwise a
+ * rollback after the rewrite would possibly
result with buffers for the
+ * original filenode having the wrong persistency
setting.
+ *
+ * NB: This relies on swap_relation_files() also
swapping the
+ * persistency. That wouldn't work for pg_class,
but that can't be
+ * unlogged anyway.
+ */
+ if (cmd->subtype == AT_SetUnLogged)
+ newrelpersistence = RELPERSISTENCE_UNLOGGED;
+
+ isSetLoggedUnlogged = true;
+ }
+ }
+ }
So I did a refactoring adding new items to AlteredTableInfo to pass the
information through the phases.
Regards,
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog sobre TI: http://fabriziomello.blogspot.com
Perfil Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Attachments:
gsoc2014_alter_table_set_logged_v9.patchtext/x-diff; charset=US-ASCII; name=gsoc2014_alter_table_set_logged_v9.patchDownload
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 69a1e14..2d131df 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -59,16 +59,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
ENABLE ALWAYS RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable>
CLUSTER ON <replaceable class="PARAMETER">index_name</replaceable>
SET WITHOUT CLUSTER
+ SET {LOGGED | UNLOGGED}
SET WITH OIDS
SET WITHOUT OIDS
SET ( <replaceable class="PARAMETER">storage_parameter</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
+ SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
RESET ( <replaceable class="PARAMETER">storage_parameter</replaceable> [, ... ] )
INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
NO INHERIT <replaceable class="PARAMETER">parent_table</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>
@@ -447,6 +448,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
</varlistentry>
<varlistentry>
+ <term><literal>SET {LOGGED | UNLOGGED}</literal></term>
+ <listitem>
+ <para>
+ This form changes the table persistence type from unlogged to permanent or
+ from unlogged to permanent (see <xref linkend="SQL-CREATETABLE-UNLOGGED">).
+ </para>
+ <para>
+ Changing the table persistence type acquires an <literal>ACCESS EXCLUSIVE</literal> lock
+ and rewrites the table contents and associated indexes into new disk files.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>SET WITH OIDS</literal></term>
<listitem>
<para>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b1c411a..7f497c7 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -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 */
@@ -601,7 +602,7 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
* 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 +614,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 +636,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
@@ -1146,6 +1140,7 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
Oid relfilenode1,
relfilenode2;
Oid swaptemp;
+ char swaprelpersistence;
CatalogIndexState indstate;
/* We need writable copies of both pg_class tuples. */
@@ -1177,6 +1172,10 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
relform1->reltablespace = relform2->reltablespace;
relform2->reltablespace = swaptemp;
+ swaprelpersistence = relform1->relpersistence;
+ relform1->relpersistence = relform2->relpersistence;
+ relform2->relpersistence = swaprelpersistence;
+
/* Also swap toast links, if we're swapping by links */
if (!swap_toast_by_content)
{
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 5130d51..a49e66f 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -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);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5dc4d18..f085167 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 toLoggedUnlogged; /* T if SET (UN)LOGGED is used */
+ char newrelpersistence; /* if toLoggedUnlogged is T then the new relpersistence */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -384,6 +386,10 @@ 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 ATExecDropCluster(Relation rel, LOCKMODE lockmode);
+static void ATPrepSetLoggedUnlogged(Relation rel, bool toLogged);
+static void AlterTableSetLoggedUnloggedCheckForeignConstraints(Relation rel, bool toLogged);
+static void AlterTableChangeCatalogToLoggedOrUnlogged(Oid relid, Relation relrelation, char relpersistence);
+static void AlterTableChangeIndexesToLoggedOrUnlogged(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,18 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_SetLogged: /* SET LOGGED */
+ case AT_SetUnLogged: /* SET UNLOGGED */
+ ATSimplePermissions(rel, ATT_TABLE);
+ ATPrepSetLoggedUnlogged(rel, (cmd->subtype == AT_SetLogged));
+ tab->rewrite = true; /* force the rewrite of the relation */
+ tab->toLoggedUnlogged = true;
+ if (cmd->subtype == AT_SetLogged)
+ tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
+ else
+ tab->newrelpersistence = RELPERSISTENCE_UNLOGGED;
+ pass = AT_PASS_MISC;
+ break;
case AT_AddOids: /* SET WITH OIDS */
ATSimplePermissions(rel, ATT_TABLE);
if (!rel->rd_rel->relhasoids || recursing)
@@ -3431,6 +3454,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)
@@ -3632,8 +3658,21 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
heap_close(OldHeap, NoLock);
+ /*
+ * Change the temporary relation to be unlogged/logged. We have to do
+ * that here so buffers for the new relfilenode will have the right
+ * persistency set while the original filenode's buffers won't get read
+ * in with the wrong (i.e. new) persistency setting. Otherwise a
+ * rollback after the rewrite would possibly result with buffers for the
+ * original filenode having the wrong persistency setting.
+ *
+ * NB: This relies on swap_relation_files() also swapping the
+ * persistency. That wouldn't work for pg_class, but that can't be
+ * unlogged anyway.
+ */
+
/* Create transient table that will receive the modified data */
- OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, false,
+ OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, tab->newrelpersistence,
lockmode);
/*
@@ -3644,6 +3683,12 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
ATRewriteTable(tab, OIDNewHeap, lockmode);
/*
+ * Change the relpersistence of the OldHeap's indexes before reindex
+ */
+ if (tab->toLoggedUnlogged)
+ AlterTableChangeIndexesToLoggedOrUnlogged(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 +4098,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->toLoggedUnlogged = false;
*wqueue = lappend(*wqueue, tab);
@@ -10424,6 +10471,194 @@ ATExecGenericOptions(Relation rel, List *options)
}
/*
+ * ALTER TABLE <name> SET {LOGGED | UNLOGGED}
+ *
+ * Change the table persistence type from unlogged to permanent by
+ * rewriting the entire contents of the table and associated indexes
+ * into new disk files.
+ *
+ * The ATPrepSetLoggedUnlogged function check all preconditions
+ * to perform the operation:
+ * - check if the target table is unlogged/permanent
+ * - check if no foreign key exists to/from other unlogged/permanent
+ * table
+ */
+static void
+ATPrepSetLoggedUnlogged(Relation rel, bool toLogged)
+{
+ char relpersistence;
+
+ relpersistence = (toLogged) ? RELPERSISTENCE_UNLOGGED :
+ RELPERSISTENCE_PERMANENT;
+
+ /* check if is an unlogged or permanent relation */
+ if (rel->rd_rel->relpersistence != relpersistence)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg( (toLogged) ? "table %s is not unlogged" :
+ "table %s is not permanent",
+ RelationGetRelationName(rel))));
+
+ /* check fk constraints */
+ AlterTableSetLoggedUnloggedCheckForeignConstraints(rel, toLogged);
+}
+
+/*
+ * AlterTableSetLoggedUnloggedCheckForeignConstraints: checks foreign key
+ * constraints consistency when changing relation persistence.
+ *
+ * Throws exceptions:
+ *
+ * - when changing to permanent (toLogged = true) then checks if no
+ * foreign key to another unlogged table exists.
+ *
+ * - when changing to unlogged (toLogged = false) then checks if do
+ * foreign key from another permanent table exists.
+ *
+ * Self-referencing foreign keys are skipped from the check.
+ */
+static void
+AlterTableSetLoggedUnloggedCheckForeignConstraints(Relation rel, bool toLogged)
+{
+ Relation pg_constraint;
+ HeapTuple tuple;
+ SysScanDesc scan;
+ ScanKeyData skey[1];
+
+ /*
+ * Fetch the constraint tuple from pg_constraint.
+ */
+ pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
+
+ /* scans conrelid if toLogged is true else scans confreld */
+ ScanKeyInit(&skey[0],
+ ((toLogged) ? Anum_pg_constraint_conrelid : Anum_pg_constraint_confrelid),
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+
+ scan = systable_beginscan(pg_constraint,
+ /* don't use index to scan if changing to unlogged */
+ ((toLogged) ? ConstraintRelidIndexId : InvalidOid),
+ toLogged, NULL, 1, skey);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
+ if (con->contype == CONSTRAINT_FOREIGN)
+ {
+ Relation relfk;
+
+ if (toLogged)
+ {
+ relfk = relation_open(con->confrelid, AccessShareLock);
+
+ /*
+ * Skip if self-referencing foreign key or check if a foreign key
+ * to an unlogged table exists
+ */
+ if (RelationGetRelid(rel) != con->confrelid &&
+ relfk->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s references unlogged table %s",
+ RelationGetRelationName(rel),
+ RelationGetRelationName(relfk))));
+ }
+ else
+ {
+ relfk = relation_open(con->conrelid, AccessShareLock);
+
+ /*
+ * Skip if self-referencing foreign key or check if a foreign key
+ * from a permanent table exists
+ */
+ if (RelationGetRelid(rel) != con->conrelid &&
+ relfk->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s is referenced by permanent table %s",
+ RelationGetRelationName(relfk),
+ RelationGetRelationName(rel))));
+ }
+
+ relation_close(relfk, AccessShareLock);
+ }
+ }
+
+ systable_endscan(scan);
+
+ heap_close(pg_constraint, AccessShareLock);
+}
+
+/*
+ * The AlterTableChangeCatalogToLoggedOrUnlogged function performs the
+ * catalog changes, i.e. update pg_class.relpersistence to 'p' or 'u'
+ */
+static void
+AlterTableChangeCatalogToLoggedOrUnlogged(Oid relid, Relation relrelation, char relpersistence)
+{
+ HeapTuple tuple;
+ Form_pg_class pg_class_form;
+ Relation rel;
+
+ /* open relation */
+ rel = relation_open(relid, AccessExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u",
+ RelationGetRelid(rel));
+
+ pg_class_form = (Form_pg_class) GETSTRUCT(tuple);
+ pg_class_form->relpersistence = relpersistence;
+ simple_heap_update(relrelation, &tuple->t_self, tuple);
+
+ /* keep catalog indexes current */
+ CatalogUpdateIndexes(relrelation, tuple);
+
+ heap_freetuple(tuple);
+ heap_close(rel, AccessExclusiveLock);
+}
+
+/*
+ * The AlterTableChangeIndexesToLoggedOrUnlogged function scans all indexes
+ * of a relation to change the relpersistence of each one
+ */
+static void
+AlterTableChangeIndexesToLoggedOrUnlogged(Oid relid, char relpersistence)
+{
+ Relation indexRelation;
+ Relation relrelation;
+ ScanKeyData skey;
+ SysScanDesc scan;
+ HeapTuple indexTuple;
+
+ /* open pg_class to update relpersistence */
+ relrelation = heap_open(RelationRelationId, RowExclusiveLock);
+
+ /* Prepare to scan pg_index for entries having indrelid = relid. */
+ indexRelation = heap_open(IndexRelationId, AccessShareLock);
+ ScanKeyInit(&skey,
+ Anum_pg_index_indrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ relid);
+
+ scan = systable_beginscan(indexRelation, IndexIndrelidIndexId, true,
+ NULL, 1, &skey);
+
+ while (HeapTupleIsValid(indexTuple = systable_getnext(scan)))
+ {
+ Form_pg_index index = (Form_pg_index) GETSTRUCT(indexTuple);
+ AlterTableChangeCatalogToLoggedOrUnlogged(index->indexrelid, relrelation, relpersistence);
+ }
+
+ systable_endscan(scan);
+ heap_close(indexRelation, AccessShareLock);
+ heap_close(relrelation, RowExclusiveLock);
+}
+
+/*
* Execute ALTER TABLE SET SCHEMA
*/
Oid
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a113809..bc5913a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -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
@@ -2048,6 +2048,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
{
@@ -12992,6 +13006,7 @@ unreserved_keyword:
| LOCAL
| LOCATION
| LOCK_P
+ | LOGGED
| MAPPING
| MATCH
| MATERIALIZED
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 24e60b7..56a42c3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1643,7 +1643,7 @@ psql_completion(const char *text, int start, int end)
pg_strcasecmp(prev_wd, "SET") == 0)
{
static const char *const list_TABLESET[] =
- {"(", "WITHOUT", "TABLESPACE", "SCHEMA", NULL};
+ {"(", "WITHOUT", "TABLESPACE", "SCHEMA", "LOGGED", "UNLOGGED", NULL};
COMPLETE_WITH_LIST(list_TABLESET);
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 0ada3d6..f7730a9 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -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,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8364bef..ca68590 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -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 */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b52e507..17888ad 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -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)
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 9b89e58..bb88c7d 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2426,3 +2426,90 @@ 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: 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)
+
+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: table logged2 is referenced by permanent 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)
+
+DROP TABLE logged3;
+DROP TABLE logged2;
+DROP TABLE logged1;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 22a2dd0..ee44957 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1624,3 +1624,54 @@ 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;
+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;
+DROP TABLE logged3;
+DROP TABLE logged2;
+DROP TABLE logged1;
+
On Wed, Jul 23, 2014 at 5:48 PM, Fabrízio de Royes Mello <
fabriziomello@gmail.com> wrote:
On Tue, Jul 22, 2014 at 3:29 PM, Fabrízio de Royes Mello <
fabriziomello@gmail.com> wrote:
On Tue, Jul 22, 2014 at 12:01 PM, Fabrízio de Royes Mello <
fabriziomello@gmail.com> wrote:
On Thu, Jul 17, 2014 at 8:02 PM, Andres Freund <andres@2ndquadrant.com>
wrote:
That means should I "FlushRelationBuffers(rel)" before change the
relpersistence?I don't think that'd help. I think what this means that you simply
cannot change the relpersistence of the old relation before the heap
swap is successful. So I guess it has to be something like
(pseudocode):
OIDNewHeap = make_new_heap(..);
newrel = heap_open(OIDNewHeap, AEL);/*
* Change the temporary relation to be unlogged/logged. We have to
do
* that here so buffers for the new relfilenode will have the right
* persistency set while the original filenode's buffers won't get
read
* in with the wrong (i.e. new) persistency setting. Otherwise a
* rollback after the rewrite would possibly result with buffers
for the
* original filenode having the wrong persistency setting.
*
* NB: This relies on swap_relation_files() also swapping the
* persistency. That wouldn't work for pg_class, but that can't be
* unlogged anyway.
*/
AlterTableChangeCatalogToLoggedOrUnlogged(newrel);
FlushRelationBuffers(newrel);
/* copy heap data into newrel */
finish_heap_swap();And then in swap_relation_files() also copy the persistency.
That's the best I can come up right now at least.
Isn't better if we can set the relpersistence as an argument to
"make_new_heap" ?
I'm thinking to change the make_new_heap:
From:
make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, bool forcetemp,
LOCKMODE lockmode)To:
make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
LOCKMODE lockmode)That way we can create the new heap with the appropriate
relpersistence, so in the swap_relation_files also copy the persistency, of
course.
And after copy the heap data to the new table (ATRewriteTable) change
relpersistence of the OldHeap's indexes, because in the "finish_heap_swap"
they'll be rebuild.
Thoughts?
The attached patch implement my previous idea based on Andres thoughts.
I don't liked the last version of the patch, especially this part:
+ /* check if SetUnlogged or SetLogged exists in subcmds */ + for(pass = 0; pass < AT_NUM_PASSES; pass++) + { + List *subcmds = tab->subcmds[pass]; + ListCell *lcmd; + + if (subcmds == NIL) + continue; + + foreach(lcmd, subcmds) + { + AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd); + + if (cmd->subtype == AT_SetUnLogged || cmd->subtype
== AT_SetLogged)
+ { + /* + * Change the temporary relation to be
unlogged/logged. We have to do
+ * that here so buffers for the new relfilenode
will have the right
+ * persistency set while the original filenode's
buffers won't get read
+ * in with the wrong (i.e. new) persistency
setting. Otherwise a
+ * rollback after the rewrite would possibly
result with buffers for the
+ * original filenode having the wrong
persistency setting.
+ * + * NB: This relies on swap_relation_files() also
swapping the
+ * persistency. That wouldn't work for pg_class,
but that can't be
+ * unlogged anyway. + */ + if (cmd->subtype == AT_SetUnLogged) + newrelpersistence = RELPERSISTENCE_UNLOGGED; + + isSetLoggedUnlogged = true; + } + } + }So I did a refactoring adding new items to AlteredTableInfo to pass the
information through the phases.
Hi all,
There are something that should I do on this patch yet?
Regards
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog sobre TI: http://fabriziomello.blogspot.com
Perfil Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Re: Fabr�zio de Royes Mello 2014-07-28 <CAFcNs+pctx4Q2UYsLOvVFWaznO3U0XhPpkMx5DRhR=Jw8w3tYg@mail.gmail.com>
There are something that should I do on this patch yet?
I haven't got around to have a look at the newest incarnation yet, but
I plan to do that soonish. (Of course that shouldn't stop others from
doing that as well if they wish.)
Christoph
--
cb@df7cb.de | http://www.df7cb.de/
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Jul 28, 2014 at 1:41 PM, Christoph Berg <cb@df7cb.de> wrote:
Re: Fabrízio de Royes Mello 2014-07-28
<CAFcNs+pctx4Q2UYsLOvVFWaznO3U0XhPpkMx5DRhR=Jw8w3tYg@mail.gmail.com>
There are something that should I do on this patch yet?
I haven't got around to have a look at the newest incarnation yet, but
I plan to do that soonish. (Of course that shouldn't stop others from
doing that as well if they wish.)
Thanks!
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog sobre TI: http://fabriziomello.blogspot.com
Perfil Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
On Mon, Jul 28, 2014 at 2:24 PM, Fabrízio de Royes Mello <
fabriziomello@gmail.com> wrote:
On Mon, Jul 28, 2014 at 1:41 PM, Christoph Berg <cb@df7cb.de> wrote:
Re: Fabrízio de Royes Mello 2014-07-28
<CAFcNs+pctx4Q2UYsLOvVFWaznO3U0XhPpkMx5DRhR=Jw8w3tYg@mail.gmail.com>
There are something that should I do on this patch yet?
I haven't got around to have a look at the newest incarnation yet, but
I plan to do that soonish. (Of course that shouldn't stop others from
doing that as well if they wish.)Thanks!
Updated version.
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog sobre TI: http://fabriziomello.blogspot.com
Perfil Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Attachments:
gsoc2014_alter_table_set_logged_v10.patchtext/x-diff; charset=US-ASCII; name=gsoc2014_alter_table_set_logged_v10.patchDownload
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 69a1e14..2d131df 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -59,16 +59,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
ENABLE ALWAYS RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable>
CLUSTER ON <replaceable class="PARAMETER">index_name</replaceable>
SET WITHOUT CLUSTER
+ SET {LOGGED | UNLOGGED}
SET WITH OIDS
SET WITHOUT OIDS
SET ( <replaceable class="PARAMETER">storage_parameter</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
+ SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
RESET ( <replaceable class="PARAMETER">storage_parameter</replaceable> [, ... ] )
INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
NO INHERIT <replaceable class="PARAMETER">parent_table</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>
@@ -447,6 +448,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
</varlistentry>
<varlistentry>
+ <term><literal>SET {LOGGED | UNLOGGED}</literal></term>
+ <listitem>
+ <para>
+ This form changes the table persistence type from unlogged to permanent or
+ from unlogged to permanent (see <xref linkend="SQL-CREATETABLE-UNLOGGED">).
+ </para>
+ <para>
+ Changing the table persistence type acquires an <literal>ACCESS EXCLUSIVE</literal> lock
+ and rewrites the table contents and associated indexes into new disk files.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>SET WITH OIDS</literal></term>
<listitem>
<para>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b1c411a..7f497c7 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -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 */
@@ -601,7 +602,7 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
* 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 +614,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 +636,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
@@ -1146,6 +1140,7 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
Oid relfilenode1,
relfilenode2;
Oid swaptemp;
+ char swaprelpersistence;
CatalogIndexState indstate;
/* We need writable copies of both pg_class tuples. */
@@ -1177,6 +1172,10 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
relform1->reltablespace = relform2->reltablespace;
relform2->reltablespace = swaptemp;
+ swaprelpersistence = relform1->relpersistence;
+ relform1->relpersistence = relform2->relpersistence;
+ relform2->relpersistence = swaprelpersistence;
+
/* Also swap toast links, if we're swapping by links */
if (!swap_toast_by_content)
{
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 5130d51..a49e66f 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -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);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c86b999..86c55df 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -151,6 +151,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 toLoggedUnlogged; /* T if SET (UN)LOGGED is used */
+ char newrelpersistence; /* if toLoggedUnlogged is T then the new relpersistence */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -383,6 +385,10 @@ 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 ATExecDropCluster(Relation rel, LOCKMODE lockmode);
+static void ATPrepSetLoggedUnlogged(Relation rel, bool toLogged);
+static void AlterTableSetLoggedUnloggedCheckForeignConstraints(Relation rel, bool toLogged);
+static void AlterTableChangeCatalogToLoggedOrUnlogged(Oid relid, Relation relrelation, char relpersistence);
+static void AlterTableChangeIndexesToLoggedOrUnlogged(Oid relid, char relpersistence);
static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
char *tablespacename, LOCKMODE lockmode);
static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
@@ -2948,6 +2954,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;
@@ -3160,6 +3171,18 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_SetLogged: /* SET LOGGED */
+ case AT_SetUnLogged: /* SET UNLOGGED */
+ ATSimplePermissions(rel, ATT_TABLE);
+ ATPrepSetLoggedUnlogged(rel, (cmd->subtype == AT_SetLogged));
+ tab->rewrite = true; /* force the rewrite of the relation */
+ tab->toLoggedUnlogged = true;
+ if (cmd->subtype == AT_SetLogged)
+ tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
+ else
+ tab->newrelpersistence = RELPERSISTENCE_UNLOGGED;
+ pass = AT_PASS_MISC;
+ break;
case AT_AddOids: /* SET WITH OIDS */
ATSimplePermissions(rel, ATT_TABLE);
if (!rel->rd_rel->relhasoids || recursing)
@@ -3430,6 +3453,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)
@@ -3631,8 +3657,21 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
heap_close(OldHeap, NoLock);
+ /*
+ * Change the temporary relation to be unlogged/logged. We have to do
+ * that here so buffers for the new relfilenode will have the right
+ * persistency set while the original filenode's buffers won't get read
+ * in with the wrong (i.e. new) persistency setting. Otherwise a
+ * rollback after the rewrite would possibly result with buffers for the
+ * original filenode having the wrong persistency setting.
+ *
+ * NB: This relies on swap_relation_files() also swapping the
+ * persistency. That wouldn't work for pg_class, but that can't be
+ * unlogged anyway.
+ */
+
/* Create transient table that will receive the modified data */
- OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, false,
+ OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, tab->newrelpersistence,
lockmode);
/*
@@ -3643,6 +3682,12 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
ATRewriteTable(tab, OIDNewHeap, lockmode);
/*
+ * Change the relpersistence of the OldHeap's indexes before reindex
+ */
+ if (tab->toLoggedUnlogged)
+ AlterTableChangeIndexesToLoggedOrUnlogged(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
@@ -4052,6 +4097,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->toLoggedUnlogged = false;
*wqueue = lappend(*wqueue, tab);
@@ -10430,6 +10477,194 @@ ATExecGenericOptions(Relation rel, List *options)
}
/*
+ * ALTER TABLE <name> SET {LOGGED | UNLOGGED}
+ *
+ * Change the table persistence type from unlogged to permanent by
+ * rewriting the entire contents of the table and associated indexes
+ * into new disk files.
+ *
+ * The ATPrepSetLoggedUnlogged function check all preconditions
+ * to perform the operation:
+ * - check if the target table is unlogged/permanent
+ * - check if no foreign key exists to/from other unlogged/permanent
+ * table
+ */
+static void
+ATPrepSetLoggedUnlogged(Relation rel, bool toLogged)
+{
+ char relpersistence;
+
+ relpersistence = (toLogged) ? RELPERSISTENCE_UNLOGGED :
+ RELPERSISTENCE_PERMANENT;
+
+ /* check if is an unlogged or permanent relation */
+ if (rel->rd_rel->relpersistence != relpersistence)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg( (toLogged) ? "table %s is not unlogged" :
+ "table %s is not permanent",
+ RelationGetRelationName(rel))));
+
+ /* check fk constraints */
+ AlterTableSetLoggedUnloggedCheckForeignConstraints(rel, toLogged);
+}
+
+/*
+ * AlterTableSetLoggedUnloggedCheckForeignConstraints: checks foreign key
+ * constraints consistency when changing relation persistence.
+ *
+ * Throws exceptions:
+ *
+ * - when changing to permanent (toLogged = true) then checks if no
+ * foreign key to another unlogged table exists.
+ *
+ * - when changing to unlogged (toLogged = false) then checks if do
+ * foreign key from another permanent table exists.
+ *
+ * Self-referencing foreign keys are skipped from the check.
+ */
+static void
+AlterTableSetLoggedUnloggedCheckForeignConstraints(Relation rel, bool toLogged)
+{
+ Relation pg_constraint;
+ HeapTuple tuple;
+ SysScanDesc scan;
+ ScanKeyData skey[1];
+
+ /*
+ * Fetch the constraint tuple from pg_constraint.
+ */
+ pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
+
+ /* scans conrelid if toLogged is true else scans confreld */
+ ScanKeyInit(&skey[0],
+ ((toLogged) ? Anum_pg_constraint_conrelid : Anum_pg_constraint_confrelid),
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+
+ scan = systable_beginscan(pg_constraint,
+ /* don't use index to scan if changing to unlogged */
+ ((toLogged) ? ConstraintRelidIndexId : InvalidOid),
+ toLogged, NULL, 1, skey);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
+ if (con->contype == CONSTRAINT_FOREIGN)
+ {
+ Relation relfk;
+
+ if (toLogged)
+ {
+ relfk = relation_open(con->confrelid, AccessShareLock);
+
+ /*
+ * Skip if self-referencing foreign key or check if a foreign key
+ * to an unlogged table exists
+ */
+ if (RelationGetRelid(rel) != con->confrelid &&
+ relfk->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s references unlogged table %s",
+ RelationGetRelationName(rel),
+ RelationGetRelationName(relfk))));
+ }
+ else
+ {
+ relfk = relation_open(con->conrelid, AccessShareLock);
+
+ /*
+ * Skip if self-referencing foreign key or check if a foreign key
+ * from a permanent table exists
+ */
+ if (RelationGetRelid(rel) != con->conrelid &&
+ relfk->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s is referenced by permanent table %s",
+ RelationGetRelationName(relfk),
+ RelationGetRelationName(rel))));
+ }
+
+ relation_close(relfk, AccessShareLock);
+ }
+ }
+
+ systable_endscan(scan);
+
+ heap_close(pg_constraint, AccessShareLock);
+}
+
+/*
+ * The AlterTableChangeCatalogToLoggedOrUnlogged function performs the
+ * catalog changes, i.e. update pg_class.relpersistence to 'p' or 'u'
+ */
+static void
+AlterTableChangeCatalogToLoggedOrUnlogged(Oid relid, Relation relrelation, char relpersistence)
+{
+ HeapTuple tuple;
+ Form_pg_class pg_class_form;
+ Relation rel;
+
+ /* open relation */
+ rel = relation_open(relid, AccessExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u",
+ RelationGetRelid(rel));
+
+ pg_class_form = (Form_pg_class) GETSTRUCT(tuple);
+ pg_class_form->relpersistence = relpersistence;
+ simple_heap_update(relrelation, &tuple->t_self, tuple);
+
+ /* keep catalog indexes current */
+ CatalogUpdateIndexes(relrelation, tuple);
+
+ heap_freetuple(tuple);
+ heap_close(rel, AccessExclusiveLock);
+}
+
+/*
+ * The AlterTableChangeIndexesToLoggedOrUnlogged function scans all indexes
+ * of a relation to change the relpersistence of each one
+ */
+static void
+AlterTableChangeIndexesToLoggedOrUnlogged(Oid relid, char relpersistence)
+{
+ Relation indexRelation;
+ Relation relrelation;
+ ScanKeyData skey;
+ SysScanDesc scan;
+ HeapTuple indexTuple;
+
+ /* open pg_class to update relpersistence */
+ relrelation = heap_open(RelationRelationId, RowExclusiveLock);
+
+ /* Prepare to scan pg_index for entries having indrelid = relid. */
+ indexRelation = heap_open(IndexRelationId, AccessShareLock);
+ ScanKeyInit(&skey,
+ Anum_pg_index_indrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ relid);
+
+ scan = systable_beginscan(indexRelation, IndexIndrelidIndexId, true,
+ NULL, 1, &skey);
+
+ while (HeapTupleIsValid(indexTuple = systable_getnext(scan)))
+ {
+ Form_pg_index index = (Form_pg_index) GETSTRUCT(indexTuple);
+ AlterTableChangeCatalogToLoggedOrUnlogged(index->indexrelid, relrelation, relpersistence);
+ }
+
+ systable_endscan(scan);
+ heap_close(indexRelation, AccessShareLock);
+ heap_close(relrelation, RowExclusiveLock);
+}
+
+/*
* Execute ALTER TABLE SET SCHEMA
*/
Oid
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a113809..bc5913a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -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
@@ -2048,6 +2048,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
{
@@ -12992,6 +13006,7 @@ unreserved_keyword:
| LOCAL
| LOCATION
| LOCK_P
+ | LOGGED
| MAPPING
| MATCH
| MATERIALIZED
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b4f1856..021017d 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1646,7 +1646,7 @@ psql_completion(const char *text, int start, int end)
pg_strcasecmp(prev_wd, "SET") == 0)
{
static const char *const list_TABLESET[] =
- {"(", "WITHOUT", "TABLESPACE", "SCHEMA", NULL};
+ {"(", "WITHOUT", "TABLESPACE", "SCHEMA", "LOGGED", "UNLOGGED", NULL};
COMPLETE_WITH_LIST(list_TABLESET);
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 0ada3d6..f7730a9 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -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,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8364bef..ca68590 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -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 */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b52e507..17888ad 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -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)
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 9b89e58..bb88c7d 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2426,3 +2426,90 @@ 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: 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)
+
+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: table logged2 is referenced by permanent 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)
+
+DROP TABLE logged3;
+DROP TABLE logged2;
+DROP TABLE logged1;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 22a2dd0..27a8e26 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1624,3 +1624,53 @@ 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;
+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;
+DROP TABLE logged3;
+DROP TABLE logged2;
+DROP TABLE logged1;
On 17 August 2014 21:45, Fabrízio de Royes Mello <fabriziomello@gmail.com>
wrote:
On Mon, Jul 28, 2014 at 2:24 PM, Fabrízio de Royes Mello <
fabriziomello@gmail.com> wrote:On Mon, Jul 28, 2014 at 1:41 PM, Christoph Berg <cb@df7cb.de> wrote:
Re: Fabrízio de Royes Mello 2014-07-28
<CAFcNs+pctx4Q2UYsLOvVFWaznO3U0XhPpkMx5DRhR=Jw8w3tYg@mail.gmail.com>
There are something that should I do on this patch yet?
I haven't got around to have a look at the newest incarnation yet, but
I plan to do that soonish. (Of course that shouldn't stop others from
doing that as well if they wish.)Thanks!
Updated version.
Hi Fabrizio,
+ This form changes the table persistence type from unlogged to
permanent or
+ from unlogged to permanent (see <xref
linkend="SQL-CREATETABLE-UNLOGGED">).
Shouldn't this read "unlogged to permanent or from permanent to unlogged"?
"ERROR: table test is not permanent"
Perhaps this would be better as "table test is unlogged" as "permanent"
doesn't match the term used in the DDL syntax.
Gave the patch a quick test-drive on a replicated instance, and it appears
to operate as described.
--
Thom
On Wed, Aug 20, 2014 at 12:35 PM, Thom Brown <thom@linux.com> wrote:
Hi Fabrizio,
+ This form changes the table persistence type from unlogged to
permanent or
+ from unlogged to permanent (see <xref
linkend="SQL-CREATETABLE-UNLOGGED">).
Shouldn't this read "unlogged to permanent or from permanent to unlogged"?
Fixed.
"ERROR: table test is not permanent"
Perhaps this would be better as "table test is unlogged" as "permanent"
doesn't match the term used in the DDL syntax.
Fixed.
Gave the patch a quick test-drive on a replicated instance, and it
appears to operate as described.
Thanks for your review.
Att,
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog: http://fabriziomello.github.io
Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Github: http://github.com/fabriziomello
Attachments:
gsoc2014_alter_table_set_logged_v11.patchtext/x-diff; charset=US-ASCII; name=gsoc2014_alter_table_set_logged_v11.patchDownload
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 69a1e14..397b4e6 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -59,16 +59,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
ENABLE ALWAYS RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable>
CLUSTER ON <replaceable class="PARAMETER">index_name</replaceable>
SET WITHOUT CLUSTER
+ SET {LOGGED | UNLOGGED}
SET WITH OIDS
SET WITHOUT OIDS
SET ( <replaceable class="PARAMETER">storage_parameter</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
+ SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
RESET ( <replaceable class="PARAMETER">storage_parameter</replaceable> [, ... ] )
INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
NO INHERIT <replaceable class="PARAMETER">parent_table</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>
@@ -447,6 +448,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
</varlistentry>
<varlistentry>
+ <term><literal>SET {LOGGED | UNLOGGED}</literal></term>
+ <listitem>
+ <para>
+ This form changes the table persistence type from unlogged to permanent or
+ from permanent to unlogged (see <xref linkend="SQL-CREATETABLE-UNLOGGED">).
+ </para>
+ <para>
+ Changing the table persistence type acquires an <literal>ACCESS EXCLUSIVE</literal> lock
+ and rewrites the table contents and associated indexes into new disk files.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>SET WITH OIDS</literal></term>
<listitem>
<para>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b1c411a..7f497c7 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -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 */
@@ -601,7 +602,7 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
* 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 +614,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 +636,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
@@ -1146,6 +1140,7 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
Oid relfilenode1,
relfilenode2;
Oid swaptemp;
+ char swaprelpersistence;
CatalogIndexState indstate;
/* We need writable copies of both pg_class tuples. */
@@ -1177,6 +1172,10 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
relform1->reltablespace = relform2->reltablespace;
relform2->reltablespace = swaptemp;
+ swaprelpersistence = relform1->relpersistence;
+ relform1->relpersistence = relform2->relpersistence;
+ relform2->relpersistence = swaprelpersistence;
+
/* Also swap toast links, if we're swapping by links */
if (!swap_toast_by_content)
{
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 5130d51..a49e66f 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -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);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c86b999..d4a1365 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -151,6 +151,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 toLoggedUnlogged; /* T if SET (UN)LOGGED is used */
+ char newrelpersistence; /* if toLoggedUnlogged is T then the new relpersistence */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -383,6 +385,10 @@ 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 ATExecDropCluster(Relation rel, LOCKMODE lockmode);
+static void ATPrepSetLoggedUnlogged(Relation rel, bool toLogged);
+static void AlterTableSetLoggedUnloggedCheckForeignConstraints(Relation rel, bool toLogged);
+static void AlterTableChangeCatalogToLoggedOrUnlogged(Oid relid, Relation relrelation, char relpersistence);
+static void AlterTableChangeIndexesToLoggedOrUnlogged(Oid relid, char relpersistence);
static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
char *tablespacename, LOCKMODE lockmode);
static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
@@ -2948,6 +2954,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;
@@ -3160,6 +3171,18 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_SetLogged: /* SET LOGGED */
+ case AT_SetUnLogged: /* SET UNLOGGED */
+ ATSimplePermissions(rel, ATT_TABLE);
+ ATPrepSetLoggedUnlogged(rel, (cmd->subtype == AT_SetLogged));
+ tab->rewrite = true; /* force the rewrite of the relation */
+ tab->toLoggedUnlogged = true;
+ if (cmd->subtype == AT_SetLogged)
+ tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
+ else
+ tab->newrelpersistence = RELPERSISTENCE_UNLOGGED;
+ pass = AT_PASS_MISC;
+ break;
case AT_AddOids: /* SET WITH OIDS */
ATSimplePermissions(rel, ATT_TABLE);
if (!rel->rd_rel->relhasoids || recursing)
@@ -3430,6 +3453,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)
@@ -3631,8 +3657,21 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
heap_close(OldHeap, NoLock);
+ /*
+ * Change the temporary relation to be unlogged/logged. We have to do
+ * that here so buffers for the new relfilenode will have the right
+ * persistency set while the original filenode's buffers won't get read
+ * in with the wrong (i.e. new) persistency setting. Otherwise a
+ * rollback after the rewrite would possibly result with buffers for the
+ * original filenode having the wrong persistency setting.
+ *
+ * NB: This relies on swap_relation_files() also swapping the
+ * persistency. That wouldn't work for pg_class, but that can't be
+ * unlogged anyway.
+ */
+
/* Create transient table that will receive the modified data */
- OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, false,
+ OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, tab->newrelpersistence,
lockmode);
/*
@@ -3643,6 +3682,12 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
ATRewriteTable(tab, OIDNewHeap, lockmode);
/*
+ * Change the relpersistence of the OldHeap's indexes before reindex
+ */
+ if (tab->toLoggedUnlogged)
+ AlterTableChangeIndexesToLoggedOrUnlogged(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
@@ -4052,6 +4097,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->toLoggedUnlogged = false;
*wqueue = lappend(*wqueue, tab);
@@ -10430,6 +10477,194 @@ ATExecGenericOptions(Relation rel, List *options)
}
/*
+ * ALTER TABLE <name> SET {LOGGED | UNLOGGED}
+ *
+ * Change the table persistence type from unlogged to permanent by
+ * rewriting the entire contents of the table and associated indexes
+ * into new disk files.
+ *
+ * The ATPrepSetLoggedUnlogged function check all preconditions
+ * to perform the operation:
+ * - check if the target table is unlogged/permanent
+ * - check if no foreign key exists to/from other unlogged/permanent
+ * table
+ */
+static void
+ATPrepSetLoggedUnlogged(Relation rel, bool toLogged)
+{
+ char relpersistence;
+
+ relpersistence = (toLogged) ? RELPERSISTENCE_UNLOGGED :
+ RELPERSISTENCE_PERMANENT;
+
+ /* check if is an unlogged or permanent relation */
+ if (rel->rd_rel->relpersistence != relpersistence)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg( (toLogged) ? "table %s is not unlogged" :
+ "table %s is unlogged",
+ RelationGetRelationName(rel))));
+
+ /* check fk constraints */
+ AlterTableSetLoggedUnloggedCheckForeignConstraints(rel, toLogged);
+}
+
+/*
+ * AlterTableSetLoggedUnloggedCheckForeignConstraints: checks foreign key
+ * constraints consistency when changing relation persistence.
+ *
+ * Throws exceptions:
+ *
+ * - when changing to permanent (toLogged = true) then checks if no
+ * foreign key to another unlogged table exists.
+ *
+ * - when changing to unlogged (toLogged = false) then checks if do
+ * foreign key from another permanent table exists.
+ *
+ * Self-referencing foreign keys are skipped from the check.
+ */
+static void
+AlterTableSetLoggedUnloggedCheckForeignConstraints(Relation rel, bool toLogged)
+{
+ Relation pg_constraint;
+ HeapTuple tuple;
+ SysScanDesc scan;
+ ScanKeyData skey[1];
+
+ /*
+ * Fetch the constraint tuple from pg_constraint.
+ */
+ pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
+
+ /* scans conrelid if toLogged is true else scans confreld */
+ ScanKeyInit(&skey[0],
+ ((toLogged) ? Anum_pg_constraint_conrelid : Anum_pg_constraint_confrelid),
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+
+ scan = systable_beginscan(pg_constraint,
+ /* don't use index to scan if changing to unlogged */
+ ((toLogged) ? ConstraintRelidIndexId : InvalidOid),
+ toLogged, NULL, 1, skey);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
+ if (con->contype == CONSTRAINT_FOREIGN)
+ {
+ Relation relfk;
+
+ if (toLogged)
+ {
+ relfk = relation_open(con->confrelid, AccessShareLock);
+
+ /*
+ * Skip if self-referencing foreign key or check if a foreign key
+ * to an unlogged table exists
+ */
+ if (RelationGetRelid(rel) != con->confrelid &&
+ relfk->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s references unlogged table %s",
+ RelationGetRelationName(rel),
+ RelationGetRelationName(relfk))));
+ }
+ else
+ {
+ relfk = relation_open(con->conrelid, AccessShareLock);
+
+ /*
+ * Skip if self-referencing foreign key or check if a foreign key
+ * from a permanent table exists
+ */
+ if (RelationGetRelid(rel) != con->conrelid &&
+ relfk->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s is referenced by permanent table %s",
+ RelationGetRelationName(relfk),
+ RelationGetRelationName(rel))));
+ }
+
+ relation_close(relfk, AccessShareLock);
+ }
+ }
+
+ systable_endscan(scan);
+
+ heap_close(pg_constraint, AccessShareLock);
+}
+
+/*
+ * The AlterTableChangeCatalogToLoggedOrUnlogged function performs the
+ * catalog changes, i.e. update pg_class.relpersistence to 'p' or 'u'
+ */
+static void
+AlterTableChangeCatalogToLoggedOrUnlogged(Oid relid, Relation relrelation, char relpersistence)
+{
+ HeapTuple tuple;
+ Form_pg_class pg_class_form;
+ Relation rel;
+
+ /* open relation */
+ rel = relation_open(relid, AccessExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u",
+ RelationGetRelid(rel));
+
+ pg_class_form = (Form_pg_class) GETSTRUCT(tuple);
+ pg_class_form->relpersistence = relpersistence;
+ simple_heap_update(relrelation, &tuple->t_self, tuple);
+
+ /* keep catalog indexes current */
+ CatalogUpdateIndexes(relrelation, tuple);
+
+ heap_freetuple(tuple);
+ heap_close(rel, AccessExclusiveLock);
+}
+
+/*
+ * The AlterTableChangeIndexesToLoggedOrUnlogged function scans all indexes
+ * of a relation to change the relpersistence of each one
+ */
+static void
+AlterTableChangeIndexesToLoggedOrUnlogged(Oid relid, char relpersistence)
+{
+ Relation indexRelation;
+ Relation relrelation;
+ ScanKeyData skey;
+ SysScanDesc scan;
+ HeapTuple indexTuple;
+
+ /* open pg_class to update relpersistence */
+ relrelation = heap_open(RelationRelationId, RowExclusiveLock);
+
+ /* Prepare to scan pg_index for entries having indrelid = relid. */
+ indexRelation = heap_open(IndexRelationId, AccessShareLock);
+ ScanKeyInit(&skey,
+ Anum_pg_index_indrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ relid);
+
+ scan = systable_beginscan(indexRelation, IndexIndrelidIndexId, true,
+ NULL, 1, &skey);
+
+ while (HeapTupleIsValid(indexTuple = systable_getnext(scan)))
+ {
+ Form_pg_index index = (Form_pg_index) GETSTRUCT(indexTuple);
+ AlterTableChangeCatalogToLoggedOrUnlogged(index->indexrelid, relrelation, relpersistence);
+ }
+
+ systable_endscan(scan);
+ heap_close(indexRelation, AccessShareLock);
+ heap_close(relrelation, RowExclusiveLock);
+}
+
+/*
* Execute ALTER TABLE SET SCHEMA
*/
Oid
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a113809..bc5913a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -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
@@ -2048,6 +2048,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
{
@@ -12992,6 +13006,7 @@ unreserved_keyword:
| LOCAL
| LOCATION
| LOCK_P
+ | LOGGED
| MAPPING
| MATCH
| MATERIALIZED
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b4f1856..021017d 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1646,7 +1646,7 @@ psql_completion(const char *text, int start, int end)
pg_strcasecmp(prev_wd, "SET") == 0)
{
static const char *const list_TABLESET[] =
- {"(", "WITHOUT", "TABLESPACE", "SCHEMA", NULL};
+ {"(", "WITHOUT", "TABLESPACE", "SCHEMA", "LOGGED", "UNLOGGED", NULL};
COMPLETE_WITH_LIST(list_TABLESET);
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 0ada3d6..f7730a9 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -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,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8364bef..ca68590 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -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 */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b52e507..17888ad 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -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)
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 9b89e58..bb88c7d 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2426,3 +2426,90 @@ 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: 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)
+
+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: table logged2 is referenced by permanent 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)
+
+DROP TABLE logged3;
+DROP TABLE logged2;
+DROP TABLE logged1;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 22a2dd0..27a8e26 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1624,3 +1624,53 @@ 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;
+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;
+DROP TABLE logged3;
+DROP TABLE logged2;
+DROP TABLE logged1;
Re: Thom Brown 2014-08-20 <CAA-aLv7TeF8iM=7U7TsgL4S5Jh1a+shQ_Ny7gOrZc_g_YJ7uKA@mail.gmail.com>
"ERROR: table test is not permanent"
Perhaps this would be better as "table test is unlogged" as "permanent"
doesn't match the term used in the DDL syntax.
I was also wondering that, but then figured that when ALTER TABLE SET
UNLOGGED is invoked on temp tables, the error message "is not
permanent" was correct while the apparent opposite "is unlogged" is
wrong.
Christoph
--
cb@df7cb.de | http://www.df7cb.de/
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Aug 21, 2014 at 5:23 AM, Christoph Berg <cb@df7cb.de> wrote:
Re: Thom Brown 2014-08-20 <CAA-aLv7TeF8iM=
7U7TsgL4S5Jh1a+shQ_Ny7gOrZc_g_YJ7uKA@mail.gmail.com>
"ERROR: table test is not permanent"
Perhaps this would be better as "table test is unlogged" as "permanent"
doesn't match the term used in the DDL syntax.I was also wondering that, but then figured that when ALTER TABLE SET
UNLOGGED is invoked on temp tables, the error message "is not
permanent" was correct while the apparent opposite "is unlogged" is
wrong.Christoph
--
cb@df7cb.de | http://www.df7cb.de/
Thom,
Christoph is right... make no sense the message... see the example:
fabrizio=# create temp table foo();
CREATE TABLE
fabrizio=# alter table foo set unlogged;
ERROR: table foo is unlogged
The previous message is better:
fabrizio=# create temp table foo();
CREATE TABLE
fabrizio=# alter table foo set unlogged;
ERROR: table foo is not permanent
fabrizio=#
fabrizio=# create unlogged table foo2();
CREATE TABLE
fabrizio=# alter table foo2 set unlogged;
ERROR: table foo2 is not permanent
Patch attached.
Regards,
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog: http://fabriziomello.github.io
Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Github: http://github.com/fabriziomello
Attachments:
gsoc2014_alter_table_set_logged_v12.patchtext/x-diff; charset=US-ASCII; name=gsoc2014_alter_table_set_logged_v12.patchDownload
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 69a1e14..397b4e6 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -59,16 +59,17 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
ENABLE ALWAYS RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable>
CLUSTER ON <replaceable class="PARAMETER">index_name</replaceable>
SET WITHOUT CLUSTER
+ SET {LOGGED | UNLOGGED}
SET WITH OIDS
SET WITHOUT OIDS
SET ( <replaceable class="PARAMETER">storage_parameter</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
+ SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
RESET ( <replaceable class="PARAMETER">storage_parameter</replaceable> [, ... ] )
INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
NO INHERIT <replaceable class="PARAMETER">parent_table</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>
@@ -447,6 +448,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
</varlistentry>
<varlistentry>
+ <term><literal>SET {LOGGED | UNLOGGED}</literal></term>
+ <listitem>
+ <para>
+ This form changes the table persistence type from unlogged to permanent or
+ from permanent to unlogged (see <xref linkend="SQL-CREATETABLE-UNLOGGED">).
+ </para>
+ <para>
+ Changing the table persistence type acquires an <literal>ACCESS EXCLUSIVE</literal> lock
+ and rewrites the table contents and associated indexes into new disk files.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>SET WITH OIDS</literal></term>
<listitem>
<para>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b1c411a..7f497c7 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -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 */
@@ -601,7 +602,7 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
* 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 +614,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 +636,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
@@ -1146,6 +1140,7 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
Oid relfilenode1,
relfilenode2;
Oid swaptemp;
+ char swaprelpersistence;
CatalogIndexState indstate;
/* We need writable copies of both pg_class tuples. */
@@ -1177,6 +1172,10 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
relform1->reltablespace = relform2->reltablespace;
relform2->reltablespace = swaptemp;
+ swaprelpersistence = relform1->relpersistence;
+ relform1->relpersistence = relform2->relpersistence;
+ relform2->relpersistence = swaprelpersistence;
+
/* Also swap toast links, if we're swapping by links */
if (!swap_toast_by_content)
{
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 5130d51..a49e66f 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -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);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c86b999..86c55df 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -151,6 +151,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 toLoggedUnlogged; /* T if SET (UN)LOGGED is used */
+ char newrelpersistence; /* if toLoggedUnlogged is T then the new relpersistence */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -383,6 +385,10 @@ 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 ATExecDropCluster(Relation rel, LOCKMODE lockmode);
+static void ATPrepSetLoggedUnlogged(Relation rel, bool toLogged);
+static void AlterTableSetLoggedUnloggedCheckForeignConstraints(Relation rel, bool toLogged);
+static void AlterTableChangeCatalogToLoggedOrUnlogged(Oid relid, Relation relrelation, char relpersistence);
+static void AlterTableChangeIndexesToLoggedOrUnlogged(Oid relid, char relpersistence);
static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
char *tablespacename, LOCKMODE lockmode);
static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
@@ -2948,6 +2954,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;
@@ -3160,6 +3171,18 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_SetLogged: /* SET LOGGED */
+ case AT_SetUnLogged: /* SET UNLOGGED */
+ ATSimplePermissions(rel, ATT_TABLE);
+ ATPrepSetLoggedUnlogged(rel, (cmd->subtype == AT_SetLogged));
+ tab->rewrite = true; /* force the rewrite of the relation */
+ tab->toLoggedUnlogged = true;
+ if (cmd->subtype == AT_SetLogged)
+ tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
+ else
+ tab->newrelpersistence = RELPERSISTENCE_UNLOGGED;
+ pass = AT_PASS_MISC;
+ break;
case AT_AddOids: /* SET WITH OIDS */
ATSimplePermissions(rel, ATT_TABLE);
if (!rel->rd_rel->relhasoids || recursing)
@@ -3430,6 +3453,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)
@@ -3631,8 +3657,21 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
heap_close(OldHeap, NoLock);
+ /*
+ * Change the temporary relation to be unlogged/logged. We have to do
+ * that here so buffers for the new relfilenode will have the right
+ * persistency set while the original filenode's buffers won't get read
+ * in with the wrong (i.e. new) persistency setting. Otherwise a
+ * rollback after the rewrite would possibly result with buffers for the
+ * original filenode having the wrong persistency setting.
+ *
+ * NB: This relies on swap_relation_files() also swapping the
+ * persistency. That wouldn't work for pg_class, but that can't be
+ * unlogged anyway.
+ */
+
/* Create transient table that will receive the modified data */
- OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, false,
+ OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, tab->newrelpersistence,
lockmode);
/*
@@ -3643,6 +3682,12 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
ATRewriteTable(tab, OIDNewHeap, lockmode);
/*
+ * Change the relpersistence of the OldHeap's indexes before reindex
+ */
+ if (tab->toLoggedUnlogged)
+ AlterTableChangeIndexesToLoggedOrUnlogged(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
@@ -4052,6 +4097,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->toLoggedUnlogged = false;
*wqueue = lappend(*wqueue, tab);
@@ -10430,6 +10477,194 @@ ATExecGenericOptions(Relation rel, List *options)
}
/*
+ * ALTER TABLE <name> SET {LOGGED | UNLOGGED}
+ *
+ * Change the table persistence type from unlogged to permanent by
+ * rewriting the entire contents of the table and associated indexes
+ * into new disk files.
+ *
+ * The ATPrepSetLoggedUnlogged function check all preconditions
+ * to perform the operation:
+ * - check if the target table is unlogged/permanent
+ * - check if no foreign key exists to/from other unlogged/permanent
+ * table
+ */
+static void
+ATPrepSetLoggedUnlogged(Relation rel, bool toLogged)
+{
+ char relpersistence;
+
+ relpersistence = (toLogged) ? RELPERSISTENCE_UNLOGGED :
+ RELPERSISTENCE_PERMANENT;
+
+ /* check if is an unlogged or permanent relation */
+ if (rel->rd_rel->relpersistence != relpersistence)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg( (toLogged) ? "table %s is not unlogged" :
+ "table %s is not permanent",
+ RelationGetRelationName(rel))));
+
+ /* check fk constraints */
+ AlterTableSetLoggedUnloggedCheckForeignConstraints(rel, toLogged);
+}
+
+/*
+ * AlterTableSetLoggedUnloggedCheckForeignConstraints: checks foreign key
+ * constraints consistency when changing relation persistence.
+ *
+ * Throws exceptions:
+ *
+ * - when changing to permanent (toLogged = true) then checks if no
+ * foreign key to another unlogged table exists.
+ *
+ * - when changing to unlogged (toLogged = false) then checks if do
+ * foreign key from another permanent table exists.
+ *
+ * Self-referencing foreign keys are skipped from the check.
+ */
+static void
+AlterTableSetLoggedUnloggedCheckForeignConstraints(Relation rel, bool toLogged)
+{
+ Relation pg_constraint;
+ HeapTuple tuple;
+ SysScanDesc scan;
+ ScanKeyData skey[1];
+
+ /*
+ * Fetch the constraint tuple from pg_constraint.
+ */
+ pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
+
+ /* scans conrelid if toLogged is true else scans confreld */
+ ScanKeyInit(&skey[0],
+ ((toLogged) ? Anum_pg_constraint_conrelid : Anum_pg_constraint_confrelid),
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+
+ scan = systable_beginscan(pg_constraint,
+ /* don't use index to scan if changing to unlogged */
+ ((toLogged) ? ConstraintRelidIndexId : InvalidOid),
+ toLogged, NULL, 1, skey);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
+ if (con->contype == CONSTRAINT_FOREIGN)
+ {
+ Relation relfk;
+
+ if (toLogged)
+ {
+ relfk = relation_open(con->confrelid, AccessShareLock);
+
+ /*
+ * Skip if self-referencing foreign key or check if a foreign key
+ * to an unlogged table exists
+ */
+ if (RelationGetRelid(rel) != con->confrelid &&
+ relfk->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s references unlogged table %s",
+ RelationGetRelationName(rel),
+ RelationGetRelationName(relfk))));
+ }
+ else
+ {
+ relfk = relation_open(con->conrelid, AccessShareLock);
+
+ /*
+ * Skip if self-referencing foreign key or check if a foreign key
+ * from a permanent table exists
+ */
+ if (RelationGetRelid(rel) != con->conrelid &&
+ relfk->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("table %s is referenced by permanent table %s",
+ RelationGetRelationName(relfk),
+ RelationGetRelationName(rel))));
+ }
+
+ relation_close(relfk, AccessShareLock);
+ }
+ }
+
+ systable_endscan(scan);
+
+ heap_close(pg_constraint, AccessShareLock);
+}
+
+/*
+ * The AlterTableChangeCatalogToLoggedOrUnlogged function performs the
+ * catalog changes, i.e. update pg_class.relpersistence to 'p' or 'u'
+ */
+static void
+AlterTableChangeCatalogToLoggedOrUnlogged(Oid relid, Relation relrelation, char relpersistence)
+{
+ HeapTuple tuple;
+ Form_pg_class pg_class_form;
+ Relation rel;
+
+ /* open relation */
+ rel = relation_open(relid, AccessExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u",
+ RelationGetRelid(rel));
+
+ pg_class_form = (Form_pg_class) GETSTRUCT(tuple);
+ pg_class_form->relpersistence = relpersistence;
+ simple_heap_update(relrelation, &tuple->t_self, tuple);
+
+ /* keep catalog indexes current */
+ CatalogUpdateIndexes(relrelation, tuple);
+
+ heap_freetuple(tuple);
+ heap_close(rel, AccessExclusiveLock);
+}
+
+/*
+ * The AlterTableChangeIndexesToLoggedOrUnlogged function scans all indexes
+ * of a relation to change the relpersistence of each one
+ */
+static void
+AlterTableChangeIndexesToLoggedOrUnlogged(Oid relid, char relpersistence)
+{
+ Relation indexRelation;
+ Relation relrelation;
+ ScanKeyData skey;
+ SysScanDesc scan;
+ HeapTuple indexTuple;
+
+ /* open pg_class to update relpersistence */
+ relrelation = heap_open(RelationRelationId, RowExclusiveLock);
+
+ /* Prepare to scan pg_index for entries having indrelid = relid. */
+ indexRelation = heap_open(IndexRelationId, AccessShareLock);
+ ScanKeyInit(&skey,
+ Anum_pg_index_indrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ relid);
+
+ scan = systable_beginscan(indexRelation, IndexIndrelidIndexId, true,
+ NULL, 1, &skey);
+
+ while (HeapTupleIsValid(indexTuple = systable_getnext(scan)))
+ {
+ Form_pg_index index = (Form_pg_index) GETSTRUCT(indexTuple);
+ AlterTableChangeCatalogToLoggedOrUnlogged(index->indexrelid, relrelation, relpersistence);
+ }
+
+ systable_endscan(scan);
+ heap_close(indexRelation, AccessShareLock);
+ heap_close(relrelation, RowExclusiveLock);
+}
+
+/*
* Execute ALTER TABLE SET SCHEMA
*/
Oid
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a113809..bc5913a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -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
@@ -2048,6 +2048,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
{
@@ -12992,6 +13006,7 @@ unreserved_keyword:
| LOCAL
| LOCATION
| LOCK_P
+ | LOGGED
| MAPPING
| MATCH
| MATERIALIZED
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b4f1856..021017d 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1646,7 +1646,7 @@ psql_completion(const char *text, int start, int end)
pg_strcasecmp(prev_wd, "SET") == 0)
{
static const char *const list_TABLESET[] =
- {"(", "WITHOUT", "TABLESPACE", "SCHEMA", NULL};
+ {"(", "WITHOUT", "TABLESPACE", "SCHEMA", "LOGGED", "UNLOGGED", NULL};
COMPLETE_WITH_LIST(list_TABLESET);
}
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 0ada3d6..f7730a9 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -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,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8364bef..ca68590 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -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 */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b52e507..17888ad 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -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)
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 9b89e58..bb88c7d 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2426,3 +2426,90 @@ 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: 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)
+
+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: table logged2 is referenced by permanent 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)
+
+DROP TABLE logged3;
+DROP TABLE logged2;
+DROP TABLE logged1;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 22a2dd0..27a8e26 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1624,3 +1624,53 @@ 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;
+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;
+DROP TABLE logged3;
+DROP TABLE logged2;
+DROP TABLE logged1;
On 21 August 2014 14:45, Fabrízio de Royes Mello <fabriziomello@gmail.com>
wrote:
On Thu, Aug 21, 2014 at 5:23 AM, Christoph Berg <cb@df7cb.de> wrote:
Re: Thom Brown 2014-08-20 <CAA-aLv7TeF8iM=
7U7TsgL4S5Jh1a+shQ_Ny7gOrZc_g_YJ7uKA@mail.gmail.com>
"ERROR: table test is not permanent"
Perhaps this would be better as "table test is unlogged" as "permanent"
doesn't match the term used in the DDL syntax.I was also wondering that, but then figured that when ALTER TABLE SET
UNLOGGED is invoked on temp tables, the error message "is not
permanent" was correct while the apparent opposite "is unlogged" is
wrong.Christoph
--
cb@df7cb.de | http://www.df7cb.de/Thom,
Christoph is right... make no sense the message... see the example:
fabrizio=# create temp table foo();
CREATE TABLE
fabrizio=# alter table foo set unlogged;
ERROR: table foo is unloggedThe previous message is better:
fabrizio=# create temp table foo();
CREATE TABLE
fabrizio=# alter table foo set unlogged;
ERROR: table foo is not permanent
fabrizio=#
fabrizio=# create unlogged table foo2();
CREATE TABLE
fabrizio=# alter table foo2 set unlogged;
ERROR: table foo2 is not permanent
To me, that's even more confusing:
CREATE TEMP TABLE test();
CREATE UNLOGGED TABLE test2();
# ALTER TABLE test SET LOGGED;
ERROR: table test is not unlogged
# ALTER TABLE test SET UNLOGGED;
ERROR: table test is not permanent
# ALTER TABLE test2 SET UNLOGGED;
ERROR: table test2 is not permanent
They're being rejected for different reasons but the error message is
identical. Permanent suggests the opposite of temporary, and unlogged
tables aren't temporary.
--
Thom
On 08/21/2014 05:04 PM, Thom Brown wrote:
On 21 August 2014 14:45, Fabrízio de Royes Mello <fabriziomello@gmail.com>
wrote:On Thu, Aug 21, 2014 at 5:23 AM, Christoph Berg <cb@df7cb.de> wrote:
Re: Thom Brown 2014-08-20 <CAA-aLv7TeF8iM=
7U7TsgL4S5Jh1a+shQ_Ny7gOrZc_g_YJ7uKA@mail.gmail.com>
"ERROR: table test is not permanent"
Perhaps this would be better as "table test is unlogged" as "permanent"
doesn't match the term used in the DDL syntax.I was also wondering that, but then figured that when ALTER TABLE SET
UNLOGGED is invoked on temp tables, the error message "is not
permanent" was correct while the apparent opposite "is unlogged" is
wrong.Thom,
Christoph is right... make no sense the message... see the example:
fabrizio=# create temp table foo();
CREATE TABLE
fabrizio=# alter table foo set unlogged;
ERROR: table foo is unloggedThe previous message is better:
fabrizio=# create temp table foo();
CREATE TABLE
fabrizio=# alter table foo set unlogged;
ERROR: table foo is not permanent
fabrizio=#
fabrizio=# create unlogged table foo2();
CREATE TABLE
fabrizio=# alter table foo2 set unlogged;
ERROR: table foo2 is not permanentTo me, that's even more confusing:
CREATE TEMP TABLE test();
CREATE UNLOGGED TABLE test2();# ALTER TABLE test SET LOGGED;
ERROR: table test is not unlogged# ALTER TABLE test SET UNLOGGED;
ERROR: table test is not permanent# ALTER TABLE test2 SET UNLOGGED;
ERROR: table test2 is not permanentThey're being rejected for different reasons but the error message is
identical. Permanent suggests the opposite of temporary, and unlogged
tables aren't temporary.
In Postgres internals slang, non-permanent means temporary or unlogged.
But I agree we shouldn't expose users to that term; we use it in the
docs, and it's not used in command names either.
I wonder if throwing an error is correct behavior anyway. Other ALTER
TABLE commands just silently do nothing in similar situations, e.g:
lowerdb=# CREATE TABLE foo () WITH OIDS;
CREATE TABLE
lowerdb=# ALTER TABLE foo SET WITH OIDS;
ALTER TABLE
But if we want to throw an error anyway, I'd suggest phrasing it "table
foo is already unlogged"
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Heikki Linnakangas wrote:
In Postgres internals slang, non-permanent means temporary or
unlogged. But I agree we shouldn't expose users to that term; we use
it in the docs, and it's not used in command names either.
Agreed. I am going over this patch, and the last bit I need to sort out
is the wording of these messages. I have temporarily settled on this:
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot change logged status of table %s to logged",
RelationGetRelationName(rel)),
errdetail("Table %s references unlogged table %s.",
RelationGetRelationName(rel),
RelationGetRelationName(relfk))));
Note the term "logged status" to talk about whether a table is logged or
not. I thought about "loggedness" but I'm not sure english speakers are
going to love me for that. Any other ideas there?
I wonder if throwing an error is correct behavior anyway. Other
ALTER TABLE commands just silently do nothing in similar situations,
e.g:lowerdb=# CREATE TABLE foo () WITH OIDS;
CREATE TABLE
lowerdb=# ALTER TABLE foo SET WITH OIDS;
ALTER TABLEBut if we want to throw an error anyway, I'd suggest phrasing it
"table foo is already unlogged"
Yeah, there is precedent for silently doing nothing. We previously
threw warnings or notices, but nowadays even that is gone. Throwing an
error definitely seems the wrong thing. In the patch I have it's like
this:
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot change logged status of table %s to unlogged",
RelationGetRelationName(rel)),
errdetail("Table %s is already unlogged.",
RelationGetRelationName(rel))));
but I think I will just take that out as a whole and set a flag to
indicate nothing is to be done.
(This also means that setting tab->rewrite while analyzing the command
is the wrong thing to do. Instead, the test for tab->rewrite should
have an || tab->chgLoggedness test, and we set chgLoggedness off if we
see that it's a no-op. That way we avoid a pointless table rewrite and
a pointless error in a multi-command ALTER TABLE that has a no-op SET
LOGGED subcommand among other things.)
I changed the doc item in ALTER TABLE,
<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>
I removed the fact that it needs ACCESS EXCLUSIVE because that's already
mentioned in the introductory paragraph. I also removed the phrase that
it requires a table rewrite, because on reading existing text I noticed
that we don't document which forms cause rewrites. Perhaps we should
document that somehow, but adding it to only one item seems wrong.
I will post an updated patch as soon as I fix a bug I introduced in the
check for FKs.
--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Agreed. I am going over this patch, and the last bit I need to sort out
is the wording of these messages. I have temporarily settled on this:
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot change logged status of table %s to logged",
RelationGetRelationName(rel)),
errdetail("Table %s references unlogged table %s.",
RelationGetRelationName(rel),
RelationGetRelationName(relfk))));
Note the term "logged status" to talk about whether a table is logged or
not. I thought about "loggedness" but I'm not sure english speakers are
going to love me for that. Any other ideas there?
Just say "cannot change status of table %s to logged".
Yeah, there is precedent for silently doing nothing. We previously
threw warnings or notices, but nowadays even that is gone. Throwing an
error definitely seems the wrong thing.
Agreed, just do nothing if it's already the right setting.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2014-08-21 16:59:17 -0400, Alvaro Herrera wrote:
Heikki Linnakangas wrote:
In Postgres internals slang, non-permanent means temporary or
unlogged. But I agree we shouldn't expose users to that term; we use
it in the docs, and it's not used in command names either.Agreed. I am going over this patch, and the last bit I need to sort out
is the wording of these messages. I have temporarily settled on this:ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot change logged status of table %s to logged",
RelationGetRelationName(rel)),
errdetail("Table %s references unlogged table %s.",
RelationGetRelationName(rel),
RelationGetRelationName(relfk))));
Maybe 'cannot change persistency of table .. from unlogged to logged'; possibly with
s/persistency/durability/?
Have you looked at the correctness of the patch itself? Last time I'd
looked it has fundamental correctness issues. I'd outlined a possible
solution, but I haven't looked since.
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Tom Lane wrote:
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Agreed. I am going over this patch, and the last bit I need to sort out
is the wording of these messages. I have temporarily settled on this:ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot change logged status of table %s to logged",
RelationGetRelationName(rel)),
errdetail("Table %s references unlogged table %s.",
RelationGetRelationName(rel),
RelationGetRelationName(relfk))));Note the term "logged status" to talk about whether a table is logged or
not. I thought about "loggedness" but I'm not sure english speakers are
going to love me for that. Any other ideas there?Just say "cannot change status of table %s to logged".
I like this one, thanks.
Yeah, there is precedent for silently doing nothing. We previously
threw warnings or notices, but nowadays even that is gone. Throwing an
error definitely seems the wrong thing.Agreed, just do nothing if it's already the right setting.
Done that way.
Andres Freund wrote:
Have you looked at the correctness of the patch itself? Last time I'd
looked it has fundamental correctness issues. I'd outlined a possible
solution, but I haven't looked since.
Yeah, Fabrizio had it passing the relpersistence down to make_new_heap,
so the transient table is created with the right setting. AFAICS it's
good now. I'm a bit uneasy about the way it changes indexes: it just
updates pg_class for them just before invoking the reindex in
finish_heap_swap. I think it's correct as it stands though; at least,
the rewrite phase happens with the right setting, so that if there are
constraints being checked and these invoke index scans, such accesses
would not leave buffers with the wrong setting in shared_buffers.
Another option would be to pass the new relpersistence down to
finish_heap_swap, but that would be hugely complicated AFAICS.
Here's the updated patch.
--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
Attachments:
gsoc2014_alter_table_set_logged_v13.patchtext/x-diff; charset=us-asciiDownload
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 61,66 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 61,68 ----
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>
***************
*** 68,74 **** ALTER TABLE [ IF EXISTS ] <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>
--- 70,75 ----
***************
*** 477,482 **** ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
--- 478,508 ----
</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.
+ 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>
<para>
***************
*** 589,608 **** ALTER TABLE [ IF EXISTS ] <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.
- See also
- <xref linkend="SQL-CREATETABLESPACE">.
- </para>
- </listitem>
- </varlistentry>
-
<varlistentry id="SQL-CREATETABLE-REPLICA-IDENTITY">
<term><literal>REPLICA IDENTITY</literal></term>
<listitem>
--- 615,620 ----
*** a/src/backend/commands/cluster.c
--- b/src/backend/commands/cluster.c
***************
*** 574,580 **** 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,
AccessExclusiveLock);
/* Copy the heap data into the new table in the desired order */
--- 574,581 ----
heap_close(OldHeap, NoLock);
/* Create the transient table that will receive the re-ordered data */
! OIDNewHeap = make_new_heap(tableOid, tableSpace,
! OldHeap->rd_rel->relpersistence,
AccessExclusiveLock);
/* Copy the heap data into the new table in the desired order */
***************
*** 595,607 **** 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.
*
* 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,
LOCKMODE lockmode)
{
TupleDesc OldHeapDesc;
--- 596,609 ----
* 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. 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, char relpersistence,
LOCKMODE lockmode)
{
TupleDesc OldHeapDesc;
***************
*** 613,619 **** 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);
--- 615,620 ----
***************
*** 636,651 **** make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, bool forcetemp,
if (isNull)
reloptions = (Datum) 0;
! if (forcetemp)
! {
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
--- 637,646 ----
if (isNull)
reloptions = (Datum) 0;
! if (relpersistence == RELPERSISTENCE_TEMP)
namespaceid = LookupCreationNamespace("pg_temp");
else
namespaceid = RelationGetNamespace(OldHeap);
/*
* Create the new heap, using a temporary name in the same namespace as
***************
*** 1109,1116 **** 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 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
--- 1104,1113 ----
/*
* Swap the physical files of two given 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,1151 **** swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
--- 1143,1149 ----
Oid relfilenode1,
relfilenode2;
Oid swaptemp;
+ char swptmpchr;
CatalogIndexState indstate;
/* We need writable copies of both pg_class tuples. */
***************
*** 1166,1172 **** 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 */
Assert(!target_is_pg_class);
swaptemp = relform1->relfilenode;
--- 1164,1173 ----
if (OidIsValid(relfilenode1) && OidIsValid(relfilenode2))
{
! /*
! * Normal non-mapped relations: swap relfilenodes, reltablespaces,
! * relpersistence
! */
Assert(!target_is_pg_class);
swaptemp = relform1->relfilenode;
***************
*** 1177,1182 **** swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
--- 1178,1187 ----
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,1210 **** 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.
*/
if (relform1->reltablespace != relform2->reltablespace)
elog(ERROR, "cannot change tablespace 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\"",
--- 1201,1218 ----
NameStr(relform1->relname));
/*
! * 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\"",
*** a/src/backend/commands/matview.c
--- b/src/backend/commands/matview.c
***************
*** 147,152 **** ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
--- 147,153 ----
DestReceiver *dest;
bool concurrent;
LOCKMODE lockmode;
+ char relpersistence;
/* Determine strength of lock needed. */
concurrent = stmt->concurrent;
***************
*** 233,241 **** ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
--- 234,248 ----
/* 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,250 **** 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,
ExclusiveLock);
LockRelationOid(OIDNewHeap, AccessExclusiveLock);
dest = CreateTransientRelDestReceiver(OIDNewHeap);
--- 251,257 ----
* it against access by any other process until commit (by which time it
* will be gone).
*/
! OIDNewHeap = make_new_heap(matviewOid, tableSpace, relpersistence,
ExclusiveLock);
LockRelationOid(OIDNewHeap, AccessExclusiveLock);
dest = CreateTransientRelDestReceiver(OIDNewHeap);
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 151,156 **** typedef struct AlteredTableInfo
--- 151,158 ----
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 */
***************
*** 371,377 **** 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 ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId,
char *cmd, List **wqueue, LOCKMODE lockmode,
bool rewrite);
--- 373,380 ----
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 ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId,
char *cmd, List **wqueue, LOCKMODE lockmode,
bool rewrite);
***************
*** 381,388 **** 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 ATExecDropCluster(Relation rel, LOCKMODE lockmode);
static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
char *tablespacename, LOCKMODE lockmode);
static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
--- 384,394 ----
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 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);
***************
*** 2948,2953 **** AlterTableGetLockLevel(List *cmds)
--- 2954,2964 ----
cmd_lockmode = ShareUpdateExclusiveLock;
break;
+ case AT_SetLogged:
+ case AT_SetUnLogged:
+ cmd_lockmode = AccessExclusiveLock;
+ break;
+
case AT_ValidateConstraint: /* Uses MVCC in
* getConstraints() */
cmd_lockmode = ShareUpdateExclusiveLock;
***************
*** 3160,3165 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
--- 3171,3194 ----
/* 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)
***************
*** 3430,3435 **** ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 3459,3467 ----
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)
***************
*** 3583,3589 **** 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.
*/
if (tab->rewrite)
{
--- 3615,3622 ----
/*
* We only need to rewrite the table if at least one column needs to
! * be recomputed, we are adding/removing the OID column, or we are
! * changing its persistence.
*/
if (tab->rewrite)
{
***************
*** 3591,3596 **** ATRewriteTables(List **wqueue, LOCKMODE lockmode)
--- 3624,3630 ----
Relation OldHeap;
Oid OIDNewHeap;
Oid NewTableSpace;
+ char persistence;
OldHeap = heap_open(tab->relid, NoLock);
***************
*** 3629,3638 **** ATRewriteTables(List **wqueue, LOCKMODE lockmode)
else
NewTableSpace = OldHeap->rd_rel->reltablespace;
heap_close(OldHeap, NoLock);
! /* Create transient table that will receive the modified data */
! OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, false,
lockmode);
/*
--- 3663,3693 ----
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.
! *
! * 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,3648 **** ATRewriteTables(List **wqueue, LOCKMODE lockmode)
--- 3698,3713 ----
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
***************
*** 4052,4057 **** ATGetQueueEntry(List **wqueue, Relation rel)
--- 4117,4124 ----
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);
***************
*** 10430,10435 **** ATExecGenericOptions(Relation rel, List *options)
--- 10497,10664 ----
}
/*
+ * 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
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 577,583 **** 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
MAPPING MATCH MATERIALIZED MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
--- 577,583 ----
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
! LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOGGED
MAPPING MATCH MATERIALIZED MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
***************
*** 2048,2053 **** alter_table_cmd:
--- 2048,2067 ----
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
{
***************
*** 12992,12997 **** unreserved_keyword:
--- 13006,13012 ----
| LOCAL
| LOCATION
| LOCK_P
+ | LOGGED
| MAPPING
| MATCH
| MATERIALIZED
*** a/src/bin/psql/tab-complete.c
--- b/src/bin/psql/tab-complete.c
***************
*** 1641,1652 **** 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 */
else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
pg_strcasecmp(prev_wd, "SET") == 0)
{
static const char *const list_TABLESET[] =
! {"(", "WITHOUT", "TABLESPACE", "SCHEMA", NULL};
COMPLETE_WITH_LIST(list_TABLESET);
}
--- 1641,1652 ----
completion_info_charp = prev3_wd;
COMPLETE_WITH_QUERY(Query_for_index_of_table);
}
! /* 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[] =
! {"(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED", "WITH", "WITHOUT", NULL};
COMPLETE_WITH_LIST(list_TABLESET);
}
*** a/src/include/commands/cluster.h
--- b/src/include/commands/cluster.h
***************
*** 25,31 **** 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,
LOCKMODE lockmode);
extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
bool is_system_catalog,
--- 25,31 ----
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, char relpersistence,
LOCKMODE lockmode);
extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
bool is_system_catalog,
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1307,1312 **** typedef enum AlterTableType
--- 1307,1314 ----
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 */
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 230,235 **** PG_KEYWORD("localtime", LOCALTIME, RESERVED_KEYWORD)
--- 230,236 ----
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)
*** a/src/test/regress/expected/alter_table.out
--- b/src/test/regress/expected/alter_table.out
***************
*** 2426,2428 **** TRUNCATE old_system_table;
--- 2426,2517 ----
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)
+
+ 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)
+
+ DROP TABLE logged3;
+ DROP TABLE logged2;
+ DROP TABLE logged1;
*** a/src/test/regress/sql/alter_table.sql
--- b/src/test/regress/sql/alter_table.sql
***************
*** 1624,1626 **** TRUNCATE old_system_table;
--- 1624,1676 ----
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;
+ 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;
+ DROP TABLE logged3;
+ DROP TABLE logged2;
+ DROP TABLE logged1;
Import Notes
Reply to msg id not found: 20140821211730.GD17406@alap3.anarazel.de9889.1408655808@sss.pgh.pa.us | Resolved by subject fallback
On Thu, Aug 21, 2014 at 8:04 PM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:
Andres Freund wrote:
Have you looked at the correctness of the patch itself? Last time I'd
looked it has fundamental correctness issues. I'd outlined a possible
solution, but I haven't looked since.Yeah, Fabrizio had it passing the relpersistence down to make_new_heap,
so the transient table is created with the right setting. AFAICS it's
good now. I'm a bit uneasy about the way it changes indexes: it just
updates pg_class for them just before invoking the reindex in
finish_heap_swap. I think it's correct as it stands though; at least,
the rewrite phase happens with the right setting, so that if there are
constraints being checked and these invoke index scans, such accesses
would not leave buffers with the wrong setting in shared_buffers.
Ok.
Another option would be to pass the new relpersistence down to
finish_heap_swap, but that would be hugely complicated AFAICS.
I think isn't so complicated to do it, but will this improve something ?
Maybe I didn't understand it very well. IMHO it just complicate a
simple thing.
Here's the updated patch.
Thanks Alvaro!
Regards,
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog: http://fabriziomello.github.io
Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Github: http://github.com/fabriziomello
On Thu, Aug 21, 2014 at 10:26 PM, Fabrízio de Royes Mello <
fabriziomello@gmail.com> wrote:
On Thu, Aug 21, 2014 at 8:04 PM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:
Andres Freund wrote:
Have you looked at the correctness of the patch itself? Last time I'd
looked it has fundamental correctness issues. I'd outlined a possible
solution, but I haven't looked since.Yeah, Fabrizio had it passing the relpersistence down to make_new_heap,
so the transient table is created with the right setting. AFAICS it's
good now. I'm a bit uneasy about the way it changes indexes: it just
updates pg_class for them just before invoking the reindex in
finish_heap_swap. I think it's correct as it stands though; at least,
the rewrite phase happens with the right setting, so that if there are
constraints being checked and these invoke index scans, such accesses
would not leave buffers with the wrong setting in shared_buffers.Ok.
Another option would be to pass the new relpersistence down to
finish_heap_swap, but that would be hugely complicated AFAICS.I think isn't so complicated to do it, but will this improve something ?
Maybe I didn't understand it very well. IMHO it just complicate a
simple thing.Here's the updated patch.
Thanks Alvaro!
I forgot to mention... I did again a lot of tests using different
replication scenarios to make sure all is ok:
- slaves async
- slaves sync
- cascade slaves
Regards,
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog: http://fabriziomello.github.io
Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Github: http://github.com/fabriziomello
Fabr�zio de Royes Mello wrote:
I forgot to mention... I did again a lot of tests using different
replication scenarios to make sure all is ok:
- slaves async
- slaves sync
- cascade slaves
On v13 you mean?
--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Em sexta-feira, 22 de agosto de 2014, Alvaro Herrera <
alvherre@2ndquadrant.com> escreveu:
Fabrízio de Royes Mello wrote:
I forgot to mention... I did again a lot of tests using different
replication scenarios to make sure all is ok:
- slaves async
- slaves sync
- cascade slavesOn v13 you mean?
Exactly!
Fabrízio Mello
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog: http://fabriziomello.github.io
Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Github: http://github.com/fabriziomello
Fabr�zio de Royes Mello wrote:
Em sexta-feira, 22 de agosto de 2014, Alvaro Herrera <
alvherre@2ndquadrant.com> escreveu:Fabr�zio de Royes Mello wrote:
I forgot to mention... I did again a lot of tests using different
replication scenarios to make sure all is ok:
- slaves async
- slaves sync
- cascade slavesOn v13 you mean?
Exactly!
Great. Pushed. Thanks for the patch.
--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Aug 22, 2014 at 3:32 PM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:
Fabrízio de Royes Mello wrote:
Em sexta-feira, 22 de agosto de 2014, Alvaro Herrera <
alvherre@2ndquadrant.com> escreveu:Fabrízio de Royes Mello wrote:
I forgot to mention... I did again a lot of tests using different
replication scenarios to make sure all is ok:
- slaves async
- slaves sync
- cascade slavesOn v13 you mean?
Exactly!
Great. Pushed. Thanks for the patch.
Awesome!!! Actually I should say thank you my friend!! See you in
Campinas...
Regards,
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog: http://fabriziomello.github.io
Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Github: http://github.com/fabriziomello
On Fri, Aug 22, 2014 at 2:32 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:
Fabrízio de Royes Mello wrote:
Em sexta-feira, 22 de agosto de 2014, Alvaro Herrera <
alvherre@2ndquadrant.com> escreveu:Fabrízio de Royes Mello wrote:
I forgot to mention... I did again a lot of tests using different
replication scenarios to make sure all is ok:
- slaves async
- slaves sync
- cascade slavesOn v13 you mean?
Exactly!
Great. Pushed. Thanks for the patch.
Hmm. I confess to not having paid enough attention to this, but:
1. Loggedness is not a word. I think that "persistence" or
"relpersistence" would be better.
2. The patch seems to think that it can sometimes be safe to change
the relpersistence of an existing relation. Unless you can be sure
that no buffers can possibly be present in shared_buffers and nobody
will use an existing relcache entry to read a new one in, it's not,
because the buffers won't have the right BM_PERSISTENT marking. I'm
very nervous about the fact that this patch seems not to have touched
bufmgr.c, but maybe I'm missing something.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Aug 22, 2014 at 4:22 PM, Robert Haas <robertmhaas@gmail.com> wrote:
On Fri, Aug 22, 2014 at 2:32 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:Fabrízio de Royes Mello wrote:
Em sexta-feira, 22 de agosto de 2014, Alvaro Herrera <
alvherre@2ndquadrant.com> escreveu:Fabrízio de Royes Mello wrote:
I forgot to mention... I did again a lot of tests using different
replication scenarios to make sure all is ok:
- slaves async
- slaves sync
- cascade slavesOn v13 you mean?
Exactly!
Great. Pushed. Thanks for the patch.
Hmm. I confess to not having paid enough attention to this, but:
1. Loggedness is not a word. I think that "persistence" or
"relpersistence" would be better.
I changed to "Persistence"...
2. The patch seems to think that it can sometimes be safe to change
the relpersistence of an existing relation. Unless you can be sure
that no buffers can possibly be present in shared_buffers and nobody
will use an existing relcache entry to read a new one in, it's not,
because the buffers won't have the right BM_PERSISTENT marking. I'm
very nervous about the fact that this patch seems not to have touched
bufmgr.c, but maybe I'm missing something.
Because of this concern I implement a solution pointed by Andres [1]/messages/by-id/20140717230220.GK21370@awork2.anarazel.de.
Regards,
[1]: /messages/by-id/20140717230220.GK21370@awork2.anarazel.de
/messages/by-id/20140717230220.GK21370@awork2.anarazel.de
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog: http://fabriziomello.github.io
Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Github: http://github.com/fabriziomello
Attachments:
change-loggedness-to-persistence.patchtext/x-diff; charset=US-ASCII; name=change-loggedness-to-persistence.patchDownload
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d37534e..5a233e2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -152,7 +152,7 @@ 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 */
+ bool chgPersistence; /* 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 */
@@ -388,8 +388,8 @@ static void change_owner_recurse_to_sequences(Oid relationOid,
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 bool ATPrepChangePersistence(Relation rel, bool toLogged);
+static void ATChangeIndexesPersistence(Oid relid, char relpersistence);
static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
char *tablespacename, LOCKMODE lockmode);
static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
@@ -3174,19 +3174,19 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_SetLogged: /* SET LOGGED */
ATSimplePermissions(rel, ATT_TABLE);
- tab->chgLoggedness = ATPrepChangeLoggedness(rel, true);
+ tab->chgPersistence = ATPrepChangePersistence(rel, true);
tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
/* force rewrite if necessary */
- if (tab->chgLoggedness)
+ if (tab->chgPersistence)
tab->rewrite = true;
pass = AT_PASS_MISC;
break;
case AT_SetUnLogged: /* SET UNLOGGED */
ATSimplePermissions(rel, ATT_TABLE);
- tab->chgLoggedness = ATPrepChangeLoggedness(rel, false);
+ tab->chgPersistence = ATPrepChangePersistence(rel, false);
tab->newrelpersistence = RELPERSISTENCE_UNLOGGED;
/* force rewrite if necessary */
- if (tab->chgLoggedness)
+ if (tab->chgPersistence)
tab->rewrite = true;
pass = AT_PASS_MISC;
break;
@@ -3668,7 +3668,7 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
* Select persistence of transient table (same as original unless
* user requested a change)
*/
- persistence = tab->chgLoggedness ?
+ persistence = tab->chgPersistence ?
tab->newrelpersistence : OldHeap->rd_rel->relpersistence;
heap_close(OldHeap, NoLock);
@@ -3705,8 +3705,8 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
* 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);
+ if (tab->chgPersistence)
+ ATChangeIndexesPersistence(tab->relid, tab->newrelpersistence);
/*
* Swap the physical files of the old and new heaps, then rebuild
@@ -4119,7 +4119,7 @@ ATGetQueueEntry(List **wqueue, Relation rel)
tab->relkind = rel->rd_rel->relkind;
tab->oldDesc = CreateTupleDescCopy(RelationGetDescr(rel));
tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
- tab->chgLoggedness = false;
+ tab->chgPersistence = false;
*wqueue = lappend(*wqueue, tab);
@@ -10678,7 +10678,7 @@ ATExecGenericOptions(Relation rel, List *options)
* checks are skipped), otherwise true.
*/
static bool
-ATPrepChangeLoggedness(Relation rel, bool toLogged)
+ATPrepChangePersistence(Relation rel, bool toLogged)
{
Relation pg_constraint;
HeapTuple tuple;
@@ -10792,7 +10792,7 @@ ATPrepChangeLoggedness(Relation rel, bool toLogged)
* given persistence.
*/
static void
-ATChangeIndexesLoggedness(Oid relid, char relpersistence)
+ATChangeIndexesPersistence(Oid relid, char relpersistence)
{
Relation rel;
Relation pg_class;
Robert Haas wrote:
Hmm. I confess to not having paid enough attention to this,
Sorry about that. I guess I should somehow flag threads "I'm planning
to commit this" so that other people can review stuff carefully.
but:
1. Loggedness is not a word. I think that "persistence" or
"relpersistence" would be better.
Yeah, AFAICS this is only used in the variable chgLoggedness and a
couple of functions. I don't think we're tense about unwordness of
words we use in source code (as opposed to error messages and docs), and
we have lots of russianisms left by Vadim and others, so I didn't see it
as a serious issue. But then I'm not a native english speaker and I'm
not bothered by it. OTOH I came up with "loggedness" on my own -- you
wouldn't have seen it even in Fabrizio's latest version of the patch.
Who knows, it might become a real english word one day.
You want me to change that to chgPersistence and so on? No prob, just
LMK.
2. The patch seems to think that it can sometimes be safe to change
the relpersistence of an existing relation. Unless you can be sure
that no buffers can possibly be present in shared_buffers and nobody
will use an existing relcache entry to read a new one in, it's not,
because the buffers won't have the right BM_PERSISTENT marking. I'm
very nervous about the fact that this patch seems not to have touched
bufmgr.c, but maybe I'm missing something.
Right. Andres pointed this out previously, and the patch was updated
consequently. The only remaining case in which we do that, again AFAIR,
is for indexes, just after the table has been rewritten and just before
the indexes are reindexed. There should be no buffer access of the old
indexes at that point, so there would be no buffer marked with the wrong
flag. Also, the table is locked access-exclusively (is that a word?),
so no other transaction could possibly be reading buffers for its
indexes.
I pointed out, in the email just before pushing the patch, that perhaps
we should pass down the new relpersistence flag into finish_heap_swap,
and from there we could pass it down to reindex_index() which is where
it would be needed. I'm not sure it's worth the trouble, but I think we
can still ask Fabrizio to rework that part.
Maybe it is me missing something.
BTW why is it that index_build() checks the heap's relpersistence flag
rather than the index'?
--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Fabr�zio de Royes Mello wrote:
On Fri, Aug 22, 2014 at 4:22 PM, Robert Haas <robertmhaas@gmail.com> wrote:
2. The patch seems to think that it can sometimes be safe to change
the relpersistence of an existing relation. Unless you can be sure
that no buffers can possibly be present in shared_buffers and nobody
will use an existing relcache entry to read a new one in, it's not,
because the buffers won't have the right BM_PERSISTENT marking. I'm
very nervous about the fact that this patch seems not to have touched
bufmgr.c, but maybe I'm missing something.Because of this concern I implement a solution pointed by Andres [1].
Actually what you did was better than what he suggested.
--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Aug 22, 2014 at 4:45 PM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:
2. The patch seems to think that it can sometimes be safe to change
the relpersistence of an existing relation. Unless you can be sure
that no buffers can possibly be present in shared_buffers and nobody
will use an existing relcache entry to read a new one in, it's not,
because the buffers won't have the right BM_PERSISTENT marking. I'm
very nervous about the fact that this patch seems not to have touched
bufmgr.c, but maybe I'm missing something.Right. Andres pointed this out previously, and the patch was updated
consequently. The only remaining case in which we do that, again AFAIR,
is for indexes, just after the table has been rewritten and just before
the indexes are reindexed. There should be no buffer access of the old
indexes at that point, so there would be no buffer marked with the wrong
flag. Also, the table is locked access-exclusively (is that a word?),
so no other transaction could possibly be reading buffers for its
indexes.I pointed out, in the email just before pushing the patch, that perhaps
we should pass down the new relpersistence flag into finish_heap_swap,
and from there we could pass it down to reindex_index() which is where
it would be needed. I'm not sure it's worth the trouble, but I think we
can still ask Fabrizio to rework that part.Maybe it is me missing something.
BTW why is it that index_build() checks the heap's relpersistence flag
rather than the index'?
I can rework this part if it's a real concern.
Regards,
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog: http://fabriziomello.github.io
Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Github: http://github.com/fabriziomello
Fabr�zio de Royes Mello wrote:
On Fri, Aug 22, 2014 at 4:45 PM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:
I pointed out, in the email just before pushing the patch, that perhaps
we should pass down the new relpersistence flag into finish_heap_swap,
and from there we could pass it down to reindex_index() which is where
it would be needed. I'm not sure it's worth the trouble, but I think we
can still ask Fabrizio to rework that part.
I can rework this part if it's a real concern.
I guess we can make a better assessment by seeing what it would take.
I'm afraid it will turn out to be really ugly.
Now, there's some desire to have unlogged indexes on logged tables; I
guess if we have that, then eventually there will also be a desire to
swap individual indexes from logged to unlogged. Perhaps whatever fix
we come up with here would pave the road for that future feature.
--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
2. The patch seems to think that it can sometimes be safe to change
the relpersistence of an existing relation. Unless you can be sure
that no buffers can possibly be present in shared_buffers and nobody
will use an existing relcache entry to read a new one in, it's not,
because the buffers won't have the right BM_PERSISTENT marking. I'm
very nervous about the fact that this patch seems not to have touched
bufmgr.c, but maybe I'm missing something.
Maybe I misunderstood something, but I had the impression that this was
handled by assigning a new relfilenode (and hence copying all the data).
So the buffers with one marking would be disjoint from the ones with the
other marking.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Robert Haas wrote:
1. Loggedness is not a word. I think that "persistence" or
"relpersistence" would be better.
You want me to change that to chgPersistence and so on? No prob, just
LMK.
+1 for s/loggedness/persistence/ -- I agree with Robert that it's
a bit grating on the native ear.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Aug 22, 2014 at 4:45 PM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:
BTW why is it that index_build() checks the heap's relpersistence flag
rather than the index'?
I'm curious about it too... the code in src/backend/catalog/index.c is:
1975 if (heapRelation->rd_rel->relpersistence ==
RELPERSISTENCE_UNLOGGED &&
1976 !smgrexists(indexRelation->rd_smgr, INIT_FORKNUM))
1977 {
Should not to be in that way?
1975 if (indexRelation->rd_rel->relpersistence ==
RELPERSISTENCE_UNLOGGED &&
1976 !smgrexists(indexRelation->rd_smgr, INIT_FORKNUM))
1977 {
Alvaro, is this your concern? Right?
Regards,
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog: http://fabriziomello.github.io
Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Github: http://github.com/fabriziomello
On Sat, Aug 23, 2014 at 3:32 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:
Great. Pushed. Thanks for the patch.
There is a typo in what has been pushed. Patch attached.
--
Michael
Attachments:
20140823_tablecmds_typo.patchtext/x-patch; charset=US-ASCII; name=20140823_tablecmds_typo.patchDownload
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d37534e..1bb46ea 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -10722,7 +10722,7 @@ ATPrepChangeLoggedness(Relation rel, bool toLogged)
/*
* Scan conrelid if changing to permanent, else confrelid. This also
- * determines whether an useful index exists.
+ * determines whether a useful index exists.
*/
ScanKeyInit(&skey[0],
toLogged ? Anum_pg_constraint_conrelid :
On Sat, Aug 23, 2014 at 8:53 AM, Michael Paquier <michael.paquier@gmail.com>
wrote:
On Sat, Aug 23, 2014 at 3:32 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:Great. Pushed. Thanks for the patch.
There is a typo in what has been pushed. Patch attached.
Thanks... I fixed that in my last patch to change 'loggedness' to
'persistence'. Attached.
Regards,
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog: http://fabriziomello.github.io
Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Github: http://github.com/fabriziomello
Attachments:
change-loggedness-to-persistence-and-fix-a-typo.patchtext/x-diff; charset=US-ASCII; name=change-loggedness-to-persistence-and-fix-a-typo.patchDownload
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d37534e..1d2fe1f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -152,7 +152,7 @@ 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 */
+ bool chgPersistence; /* 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 */
@@ -388,8 +388,8 @@ static void change_owner_recurse_to_sequences(Oid relationOid,
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 bool ATPrepChangePersistence(Relation rel, bool toLogged);
+static void ATChangeIndexesPersistence(Oid relid, char relpersistence);
static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
char *tablespacename, LOCKMODE lockmode);
static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
@@ -3174,19 +3174,19 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_SetLogged: /* SET LOGGED */
ATSimplePermissions(rel, ATT_TABLE);
- tab->chgLoggedness = ATPrepChangeLoggedness(rel, true);
+ tab->chgPersistence = ATPrepChangePersistence(rel, true);
tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
/* force rewrite if necessary */
- if (tab->chgLoggedness)
+ if (tab->chgPersistence)
tab->rewrite = true;
pass = AT_PASS_MISC;
break;
case AT_SetUnLogged: /* SET UNLOGGED */
ATSimplePermissions(rel, ATT_TABLE);
- tab->chgLoggedness = ATPrepChangeLoggedness(rel, false);
+ tab->chgPersistence = ATPrepChangePersistence(rel, false);
tab->newrelpersistence = RELPERSISTENCE_UNLOGGED;
/* force rewrite if necessary */
- if (tab->chgLoggedness)
+ if (tab->chgPersistence)
tab->rewrite = true;
pass = AT_PASS_MISC;
break;
@@ -3668,7 +3668,7 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
* Select persistence of transient table (same as original unless
* user requested a change)
*/
- persistence = tab->chgLoggedness ?
+ persistence = tab->chgPersistence ?
tab->newrelpersistence : OldHeap->rd_rel->relpersistence;
heap_close(OldHeap, NoLock);
@@ -3705,8 +3705,8 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
* 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);
+ if (tab->chgPersistence)
+ ATChangeIndexesPersistence(tab->relid, tab->newrelpersistence);
/*
* Swap the physical files of the old and new heaps, then rebuild
@@ -4119,7 +4119,7 @@ ATGetQueueEntry(List **wqueue, Relation rel)
tab->relkind = rel->rd_rel->relkind;
tab->oldDesc = CreateTupleDescCopy(RelationGetDescr(rel));
tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
- tab->chgLoggedness = false;
+ tab->chgPersistence = false;
*wqueue = lappend(*wqueue, tab);
@@ -10678,7 +10678,7 @@ ATExecGenericOptions(Relation rel, List *options)
* checks are skipped), otherwise true.
*/
static bool
-ATPrepChangeLoggedness(Relation rel, bool toLogged)
+ATPrepChangePersistence(Relation rel, bool toLogged)
{
Relation pg_constraint;
HeapTuple tuple;
@@ -10722,7 +10722,7 @@ ATPrepChangeLoggedness(Relation rel, bool toLogged)
/*
* Scan conrelid if changing to permanent, else confrelid. This also
- * determines whether an useful index exists.
+ * determines whether a useful index exists.
*/
ScanKeyInit(&skey[0],
toLogged ? Anum_pg_constraint_conrelid :
@@ -10792,7 +10792,7 @@ ATPrepChangeLoggedness(Relation rel, bool toLogged)
* given persistence.
*/
static void
-ATChangeIndexesLoggedness(Oid relid, char relpersistence)
+ATChangeIndexesPersistence(Oid relid, char relpersistence)
{
Relation rel;
Relation pg_class;
Fabr�zio de Royes Mello wrote:
On Sat, Aug 23, 2014 at 8:53 AM, Michael Paquier <michael.paquier@gmail.com>
wrote:On Sat, Aug 23, 2014 at 3:32 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:Great. Pushed. Thanks for the patch.
There is a typo in what has been pushed. Patch attached.
Thanks... I fixed that in my last patch to change 'loggedness' to
'persistence'. Attached.
Thanks, pushed. I also added a comment explaining why it's okay to do
what we're doing.
--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Aug 25, 2014 at 2:55 PM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:
Fabrízio de Royes Mello wrote:
On Sat, Aug 23, 2014 at 8:53 AM, Michael Paquier <
michael.paquier@gmail.com>
wrote:
On Sat, Aug 23, 2014 at 3:32 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:Great. Pushed. Thanks for the patch.
There is a typo in what has been pushed. Patch attached.
Thanks... I fixed that in my last patch to change 'loggedness' to
'persistence'. Attached.Thanks, pushed. I also added a comment explaining why it's okay to do
what we're doing.
Thanks...
I'm working on a refactoring to pass down the relpersistence flag to
finish_heap_swap... is this valid for now or I should leave it to another
patch?
Regards,
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog: http://fabriziomello.github.io
Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Github: http://github.com/fabriziomello
On Fri, Aug 22, 2014 at 5:23 PM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:
Fabrízio de Royes Mello wrote:
On Fri, Aug 22, 2014 at 4:45 PM, Alvaro Herrera <
alvherre@2ndquadrant.com>
wrote:
I pointed out, in the email just before pushing the patch, that
perhaps
we should pass down the new relpersistence flag into finish_heap_swap,
and from there we could pass it down to reindex_index() which is where
it would be needed. I'm not sure it's worth the trouble, but I think
we
can still ask Fabrizio to rework that part.
I can rework this part if it's a real concern.
I guess we can make a better assessment by seeing what it would take.
I'm afraid it will turn out to be really ugly.Now, there's some desire to have unlogged indexes on logged tables; I
guess if we have that, then eventually there will also be a desire to
swap individual indexes from logged to unlogged. Perhaps whatever fix
we come up with here would pave the road for that future feature.
Álvaro,
I did a refactoring to pass down the relpersistence to "finish_heap_swap"
and then change the catalog inside the "reindex_index" instead of in the
rewrite table phase.
That way we can get rid the function ATChangeIndexesPersistence in the
src/backend/commands/tablecmds.c.
Thoughts?
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog: http://fabriziomello.github.io
Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Github: http://github.com/fabriziomello
Attachments:
refactoring-tablecmds-pass-down-relpersistence_v1.patchtext/x-diff; charset=US-ASCII; name=refactoring-tablecmds-pass-down-relpersistence_v1.patchDownload
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index ee10594..173f412 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1973,7 +1973,7 @@ index_build(Relation heapRelation,
* created it, or truncated twice in a subsequent transaction, the
* relfilenode won't change, and nothing needs to be done here.
*/
- if (heapRelation->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
+ if (indexRelation->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
!smgrexists(indexRelation->rd_smgr, INIT_FORKNUM))
{
RegProcedure ambuildempty = indexRelation->rd_am->ambuildempty;
@@ -3099,7 +3099,7 @@ IndexGetRelation(Oid indexId, bool missing_ok)
* reindex_index - This routine is used to recreate a single index
*/
void
-reindex_index(Oid indexId, bool skip_constraint_checks)
+reindex_index(Oid indexId, bool skip_constraint_checks, char relpersistence)
{
Relation iRel,
heapRelation;
@@ -3160,6 +3160,12 @@ reindex_index(Oid indexId, bool skip_constraint_checks)
indexInfo->ii_ExclusionStrats = NULL;
}
+ /*
+ * Check if need to set the new relpersistence
+ */
+ if (iRel->rd_rel->relpersistence != relpersistence)
+ iRel->rd_rel->relpersistence = relpersistence;
+
/* We'll build a new physical relation for the index */
RelationSetNewRelfilenode(iRel, InvalidTransactionId,
InvalidMultiXactId);
@@ -3358,11 +3364,19 @@ reindex_relation(Oid relid, int flags)
foreach(indexId, indexIds)
{
Oid indexOid = lfirst_oid(indexId);
+ char relpersistence = rel->rd_rel->relpersistence;
if (is_pg_class)
RelationSetIndexList(rel, doneIndexes, InvalidOid);
- reindex_index(indexOid, !(flags & REINDEX_REL_CHECK_CONSTRAINTS));
+ /* Check for flags to force UNLOGGED or PERMANENT persistence */
+ if (flags & REINDEX_REL_FORCE_UNLOGGED)
+ relpersistence = RELPERSISTENCE_UNLOGGED;
+ else if (flags & REINDEX_REL_FORCE_PERMANENT)
+ relpersistence = RELPERSISTENCE_PERMANENT;
+
+ reindex_index(indexOid, !(flags & REINDEX_REL_CHECK_CONSTRAINTS),
+ relpersistence);
CommandCounterIncrement();
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index ff80b09..f285026 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -588,7 +588,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
*/
finish_heap_swap(tableOid, OIDNewHeap, is_system_catalog,
swap_toast_by_content, false, true,
- frozenXid, cutoffMulti);
+ frozenXid, cutoffMulti,
+ OldHeap->rd_rel->relpersistence);
}
@@ -1474,7 +1475,8 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
bool check_constraints,
bool is_internal,
TransactionId frozenXid,
- MultiXactId cutoffMulti)
+ MultiXactId cutoffMulti,
+ char newrelpersistence)
{
ObjectAddress object;
Oid mapped_tables[4];
@@ -1518,6 +1520,12 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
reindex_flags = REINDEX_REL_SUPPRESS_INDEX_USE;
if (check_constraints)
reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
+
+ if (newrelpersistence == RELPERSISTENCE_UNLOGGED)
+ reindex_flags |= REINDEX_REL_FORCE_UNLOGGED;
+ else if (newrelpersistence == RELPERSISTENCE_PERMANENT)
+ reindex_flags |= REINDEX_REL_FORCE_PERMANENT;
+
reindex_relation(OIDOldHeap, reindex_flags);
/*
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index fdfa6ca..6156fcc 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1678,7 +1678,7 @@ ReindexIndex(RangeVar *indexRelation)
RangeVarCallbackForReindexIndex,
(void *) &heapOid);
- reindex_index(indOid, false);
+ reindex_index(indOid, false, indexRelation->relpersistence);
return indOid;
}
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index d8d3c08..b2c0fc9 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -65,7 +65,7 @@ static char *make_temptable_name_n(char *tempname, int n);
static void mv_GenerateOper(StringInfo buf, Oid opoid);
static void refresh_by_match_merge(Oid matviewOid, Oid tempOid);
-static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap);
+static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersistence);
static void OpenMatViewIncrementalMaintenance(void);
static void CloseMatViewIncrementalMaintenance(void);
@@ -280,7 +280,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
Assert(matview_maintenance_depth == old_depth);
}
else
- refresh_by_heap_swap(matviewOid, OIDNewHeap);
+ refresh_by_heap_swap(matviewOid, OIDNewHeap, relpersistence);
return matviewOid;
}
@@ -761,10 +761,10 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid)
* swapping is handled by the called function, so it is not needed here.
*/
static void
-refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap)
+refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersistence)
{
finish_heap_swap(matviewOid, OIDNewHeap, false, false, true, true,
- RecentXmin, ReadNextMultiXactId());
+ RecentXmin, ReadNextMultiXactId(), relpersistence);
}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3720a0f..0802d1e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -389,7 +389,6 @@ static void ATExecClusterOn(Relation rel, const char *indexName,
LOCKMODE lockmode);
static void ATExecDropCluster(Relation rel, LOCKMODE lockmode);
static bool ATPrepChangePersistence(Relation rel, bool toLogged);
-static void ATChangeIndexesPersistence(Oid relid, char relpersistence);
static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
char *tablespacename, LOCKMODE lockmode);
static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
@@ -3710,16 +3709,6 @@ 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->chgPersistence)
- ATChangeIndexesPersistence(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
@@ -3731,7 +3720,8 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
false, false, true,
!OidIsValid(tab->newTableSpace),
RecentXmin,
- ReadNextMultiXactId());
+ ReadNextMultiXactId(),
+ persistence);
}
else
{
@@ -10799,48 +10789,6 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
}
/*
- * Update the pg_class entry of each index for the given relation to the
- * given persistence.
- */
-static void
-ATChangeIndexesPersistence(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
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index b6f03df..8b1d30a 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2983,6 +2983,7 @@ RelationSetNewRelfilenode(Relation relation, TransactionId freezeXid,
}
classform->relfrozenxid = freezeXid;
classform->relminmxid = minmulti;
+ classform->relpersistence = relation->rd_rel->relpersistence;
simple_heap_update(pg_class, &tuple->t_self, tuple);
CatalogUpdateIndexes(pg_class, tuple);
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 006b180..74111b6 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -102,12 +102,15 @@ extern void validate_index(Oid heapId, Oid indexId, Snapshot snapshot);
extern void index_set_state_flags(Oid indexId, IndexStateFlagsAction action);
-extern void reindex_index(Oid indexId, bool skip_constraint_checks);
+extern void reindex_index(Oid indexId, bool skip_constraint_checks,
+ char relpersistence);
/* Flag bits for reindex_relation(): */
#define REINDEX_REL_PROCESS_TOAST 0x01
#define REINDEX_REL_SUPPRESS_INDEX_USE 0x02
#define REINDEX_REL_CHECK_CONSTRAINTS 0x04
+#define REINDEX_REL_FORCE_UNLOGGED 0x08
+#define REINDEX_REL_FORCE_PERMANENT 0x10
extern bool reindex_relation(Oid relid, int flags);
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index f7730a9..0b7e877 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -33,6 +33,7 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
bool check_constraints,
bool is_internal,
TransactionId frozenXid,
- MultiXactId minMulti);
+ MultiXactId minMulti,
+ char newrelpersistence);
#endif /* CLUSTER_H */
On Tue, Aug 26, 2014 at 1:42 AM, Fabrízio de Royes Mello <
fabriziomello@gmail.com> wrote:
On Fri, Aug 22, 2014 at 5:23 PM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:
Fabrízio de Royes Mello wrote:
On Fri, Aug 22, 2014 at 4:45 PM, Alvaro Herrera <
alvherre@2ndquadrant.com>
wrote:
I pointed out, in the email just before pushing the patch, that
perhaps
we should pass down the new relpersistence flag into
finish_heap_swap,
and from there we could pass it down to reindex_index() which is
where
it would be needed. I'm not sure it's worth the trouble, but I
think we
can still ask Fabrizio to rework that part.
I can rework this part if it's a real concern.
I guess we can make a better assessment by seeing what it would take.
I'm afraid it will turn out to be really ugly.Now, there's some desire to have unlogged indexes on logged tables; I
guess if we have that, then eventually there will also be a desire to
swap individual indexes from logged to unlogged. Perhaps whatever fix
we come up with here would pave the road for that future feature.Álvaro,
I did a refactoring to pass down the relpersistence to "finish_heap_swap"
and then change the catalog inside the "reindex_index" instead of in the
rewrite table phase.
That way we can get rid the function ATChangeIndexesPersistence in the
src/backend/commands/tablecmds.c.
Thoughts?
Patch rebased and added to commitfest [1]https://commitfest.postgresql.org/action/commitfest_view?id=24.
Regards,
[1]: https://commitfest.postgresql.org/action/commitfest_view?id=24
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog: http://fabriziomello.github.io
Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Github: http://github.com/fabriziomello
Attachments:
refactoring-tablecmds-pass-down-relpersistence_v1.patchtext/x-diff; charset=US-ASCII; name=refactoring-tablecmds-pass-down-relpersistence_v1.patchDownload
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index ee10594..173f412 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1973,7 +1973,7 @@ index_build(Relation heapRelation,
* created it, or truncated twice in a subsequent transaction, the
* relfilenode won't change, and nothing needs to be done here.
*/
- if (heapRelation->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
+ if (indexRelation->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
!smgrexists(indexRelation->rd_smgr, INIT_FORKNUM))
{
RegProcedure ambuildempty = indexRelation->rd_am->ambuildempty;
@@ -3099,7 +3099,7 @@ IndexGetRelation(Oid indexId, bool missing_ok)
* reindex_index - This routine is used to recreate a single index
*/
void
-reindex_index(Oid indexId, bool skip_constraint_checks)
+reindex_index(Oid indexId, bool skip_constraint_checks, char relpersistence)
{
Relation iRel,
heapRelation;
@@ -3160,6 +3160,12 @@ reindex_index(Oid indexId, bool skip_constraint_checks)
indexInfo->ii_ExclusionStrats = NULL;
}
+ /*
+ * Check if need to set the new relpersistence
+ */
+ if (iRel->rd_rel->relpersistence != relpersistence)
+ iRel->rd_rel->relpersistence = relpersistence;
+
/* We'll build a new physical relation for the index */
RelationSetNewRelfilenode(iRel, InvalidTransactionId,
InvalidMultiXactId);
@@ -3358,11 +3364,19 @@ reindex_relation(Oid relid, int flags)
foreach(indexId, indexIds)
{
Oid indexOid = lfirst_oid(indexId);
+ char relpersistence = rel->rd_rel->relpersistence;
if (is_pg_class)
RelationSetIndexList(rel, doneIndexes, InvalidOid);
- reindex_index(indexOid, !(flags & REINDEX_REL_CHECK_CONSTRAINTS));
+ /* Check for flags to force UNLOGGED or PERMANENT persistence */
+ if (flags & REINDEX_REL_FORCE_UNLOGGED)
+ relpersistence = RELPERSISTENCE_UNLOGGED;
+ else if (flags & REINDEX_REL_FORCE_PERMANENT)
+ relpersistence = RELPERSISTENCE_PERMANENT;
+
+ reindex_index(indexOid, !(flags & REINDEX_REL_CHECK_CONSTRAINTS),
+ relpersistence);
CommandCounterIncrement();
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index ff80b09..f285026 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -588,7 +588,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
*/
finish_heap_swap(tableOid, OIDNewHeap, is_system_catalog,
swap_toast_by_content, false, true,
- frozenXid, cutoffMulti);
+ frozenXid, cutoffMulti,
+ OldHeap->rd_rel->relpersistence);
}
@@ -1474,7 +1475,8 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
bool check_constraints,
bool is_internal,
TransactionId frozenXid,
- MultiXactId cutoffMulti)
+ MultiXactId cutoffMulti,
+ char newrelpersistence)
{
ObjectAddress object;
Oid mapped_tables[4];
@@ -1518,6 +1520,12 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
reindex_flags = REINDEX_REL_SUPPRESS_INDEX_USE;
if (check_constraints)
reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
+
+ if (newrelpersistence == RELPERSISTENCE_UNLOGGED)
+ reindex_flags |= REINDEX_REL_FORCE_UNLOGGED;
+ else if (newrelpersistence == RELPERSISTENCE_PERMANENT)
+ reindex_flags |= REINDEX_REL_FORCE_PERMANENT;
+
reindex_relation(OIDOldHeap, reindex_flags);
/*
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 8a1cb4b..83ac1d3 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1682,7 +1682,7 @@ ReindexIndex(RangeVar *indexRelation)
RangeVarCallbackForReindexIndex,
(void *) &heapOid);
- reindex_index(indOid, false);
+ reindex_index(indOid, false, indexRelation->relpersistence);
return indOid;
}
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index d1c8bb0..f2221a8 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -66,7 +66,7 @@ static void mv_GenerateOper(StringInfo buf, Oid opoid);
static void refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
int save_sec_context);
-static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap);
+static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersistence);
static void OpenMatViewIncrementalMaintenance(void);
static void CloseMatViewIncrementalMaintenance(void);
@@ -302,7 +302,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
Assert(matview_maintenance_depth == old_depth);
}
else
- refresh_by_heap_swap(matviewOid, OIDNewHeap);
+ refresh_by_heap_swap(matviewOid, OIDNewHeap, relpersistence);
/* Roll back any GUC changes */
AtEOXact_GUC(false, save_nestlevel);
@@ -758,10 +758,10 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
* swapping is handled by the called function, so it is not needed here.
*/
static void
-refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap)
+refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersistence)
{
finish_heap_swap(matviewOid, OIDNewHeap, false, false, true, true,
- RecentXmin, ReadNextMultiXactId());
+ RecentXmin, ReadNextMultiXactId(), relpersistence);
}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7bc579b..972920b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -389,7 +389,6 @@ static void ATExecClusterOn(Relation rel, const char *indexName,
LOCKMODE lockmode);
static void ATExecDropCluster(Relation rel, LOCKMODE lockmode);
static bool ATPrepChangePersistence(Relation rel, bool toLogged);
-static void ATChangeIndexesPersistence(Oid relid, char relpersistence);
static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
char *tablespacename, LOCKMODE lockmode);
static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
@@ -3719,16 +3718,6 @@ 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->chgPersistence)
- ATChangeIndexesPersistence(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
@@ -3740,7 +3729,8 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
false, false, true,
!OidIsValid(tab->newTableSpace),
RecentXmin,
- ReadNextMultiXactId());
+ ReadNextMultiXactId(),
+ persistence);
}
else
{
@@ -10808,48 +10798,6 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
}
/*
- * Update the pg_class entry of each index for the given relation to the
- * given persistence.
- */
-static void
-ATChangeIndexesPersistence(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
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index b6f03df..8b1d30a 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2983,6 +2983,7 @@ RelationSetNewRelfilenode(Relation relation, TransactionId freezeXid,
}
classform->relfrozenxid = freezeXid;
classform->relminmxid = minmulti;
+ classform->relpersistence = relation->rd_rel->relpersistence;
simple_heap_update(pg_class, &tuple->t_self, tuple);
CatalogUpdateIndexes(pg_class, tuple);
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 006b180..74111b6 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -102,12 +102,15 @@ extern void validate_index(Oid heapId, Oid indexId, Snapshot snapshot);
extern void index_set_state_flags(Oid indexId, IndexStateFlagsAction action);
-extern void reindex_index(Oid indexId, bool skip_constraint_checks);
+extern void reindex_index(Oid indexId, bool skip_constraint_checks,
+ char relpersistence);
/* Flag bits for reindex_relation(): */
#define REINDEX_REL_PROCESS_TOAST 0x01
#define REINDEX_REL_SUPPRESS_INDEX_USE 0x02
#define REINDEX_REL_CHECK_CONSTRAINTS 0x04
+#define REINDEX_REL_FORCE_UNLOGGED 0x08
+#define REINDEX_REL_FORCE_PERMANENT 0x10
extern bool reindex_relation(Oid relid, int flags);
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index f7730a9..0b7e877 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -33,6 +33,7 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
bool check_constraints,
bool is_internal,
TransactionId frozenXid,
- MultiXactId minMulti);
+ MultiXactId minMulti,
+ char newrelpersistence);
#endif /* CLUSTER_H */
On Sat, Sep 13, 2014 at 11:02 PM, Fabrízio de Royes Mello
<fabriziomello@gmail.com> wrote:
Patch rebased and added to commitfest [1].
It looks like a good thing to remove ATChangeIndexesPersistence, this
puts the persistence switch directly into reindex process.
A couple of minor comments about this patch:
1) Reading it, I am wondering if it would not be finally time to
switch to a macro to get a relation's persistence, something like
RelationGetPersistence in rel.h... Not related directly to this patch.
2) reindex_index has as new argument a relpersislence value for the
new index. reindex_relation has differently a new set of flags to
enforce the relpersistence of all the underling indexes. Wouldn't it
be better for API consistency to pass directly a relpersistence value
through reindex_relation? In any case, the comment block of
reindex_relation does not contain a description of the new flags.
3) Here you may as well just set the value and be done:
+ /*
+ * Check if need to set the new relpersistence
+ */
+ if (iRel->rd_rel->relpersistence != relpersistence)
+ iRel->rd_rel->relpersistence = relpersistence;
Regards,
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Nov 6, 2014 at 3:42 AM, Michael Paquier <michael.paquier@gmail.com>
wrote:
On Sat, Sep 13, 2014 at 11:02 PM, Fabrízio de Royes Mello
<fabriziomello@gmail.com> wrote:Patch rebased and added to commitfest [1].
It looks like a good thing to remove ATChangeIndexesPersistence, this
puts the persistence switch directly into reindex process.A couple of minor comments about this patch:
1) Reading it, I am wondering if it would not be finally time to
switch to a macro to get a relation's persistence, something like
RelationGetPersistence in rel.h... Not related directly to this patch.
Good idea... I'll provide a patch soon.
2) reindex_index has as new argument a relpersislence value for the
new index. reindex_relation has differently a new set of flags to
enforce the relpersistence of all the underling indexes. Wouldn't it
be better for API consistency to pass directly a relpersistence value
through reindex_relation? In any case, the comment block of
reindex_relation does not contain a description of the new flags.
I did it because the ReindexDatabase build a list of oids to run
reindex_relation for each item of the list. I can change the list to store
the relpersistence also, but this can lead us to a concurrency trouble
because if one session execute REINDEX DATABASE and other session run
"ALTER TABLE name SET {LOGGED|UNLOGGED}" during the building of the list
the reindex can lead us to a inconsitence state.
Added comments to comment block of reindex_relation.
3) Here you may as well just set the value and be done: + /* + * Check if need to set the new relpersistence + */ + if (iRel->rd_rel->relpersistence != relpersistence) + iRel->rd_rel->relpersistence = relpersistence;
Hmmm... I really don't know why I did it... fixed.
Thanks!
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog: http://fabriziomello.github.io
Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Github: http://github.com/fabriziomello
Attachments:
refactoring-tablecmds-pass-down-relpersistence_v2.patchtext/x-diff; charset=US-ASCII; name=refactoring-tablecmds-pass-down-relpersistence_v2.patchDownload
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 912038a..50cf0ef 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1980,7 +1980,7 @@ index_build(Relation heapRelation,
* created it, or truncated twice in a subsequent transaction, the
* relfilenode won't change, and nothing needs to be done here.
*/
- if (heapRelation->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
+ if (indexRelation->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
!smgrexists(indexRelation->rd_smgr, INIT_FORKNUM))
{
RegProcedure ambuildempty = indexRelation->rd_am->ambuildempty;
@@ -3130,7 +3130,7 @@ IndexGetRelation(Oid indexId, bool missing_ok)
* reindex_index - This routine is used to recreate a single index
*/
void
-reindex_index(Oid indexId, bool skip_constraint_checks)
+reindex_index(Oid indexId, bool skip_constraint_checks, char relpersistence)
{
Relation iRel,
heapRelation;
@@ -3191,6 +3191,9 @@ reindex_index(Oid indexId, bool skip_constraint_checks)
indexInfo->ii_ExclusionStrats = NULL;
}
+ /* Set the relpersistence of the new index */
+ iRel->rd_rel->relpersistence = relpersistence;
+
/* We'll build a new physical relation for the index */
RelationSetNewRelfilenode(iRel, InvalidTransactionId,
InvalidMultiXactId);
@@ -3310,6 +3313,12 @@ reindex_index(Oid indexId, bool skip_constraint_checks)
* performance, other callers should include the flag only after transforming
* the data in a manner that risks a change in constraint validity.
*
+ * REINDEX_REL_FORCE_UNLOGGED: if true, force the new rebuilded indexes to be
+ * unlogged.
+ *
+ * REINDEX_REL_FORCE_LOGGED: if true, force the new rebuilded indexes to be
+ * permanent.
+ *
* Returns true if any indexes were rebuilt (including toast table's index
* when relevant). Note that a CommandCounterIncrement will occur after each
* index rebuild.
@@ -3389,11 +3398,19 @@ reindex_relation(Oid relid, int flags)
foreach(indexId, indexIds)
{
Oid indexOid = lfirst_oid(indexId);
+ char relpersistence = rel->rd_rel->relpersistence;
if (is_pg_class)
RelationSetIndexList(rel, doneIndexes, InvalidOid);
- reindex_index(indexOid, !(flags & REINDEX_REL_CHECK_CONSTRAINTS));
+ /* Check for flags to force UNLOGGED or PERMANENT persistence */
+ if (flags & REINDEX_REL_FORCE_UNLOGGED)
+ relpersistence = RELPERSISTENCE_UNLOGGED;
+ else if (flags & REINDEX_REL_FORCE_PERMANENT)
+ relpersistence = RELPERSISTENCE_PERMANENT;
+
+ reindex_index(indexOid, !(flags & REINDEX_REL_CHECK_CONSTRAINTS),
+ relpersistence);
CommandCounterIncrement();
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 6a578ec..95b9a4f 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -589,7 +589,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
*/
finish_heap_swap(tableOid, OIDNewHeap, is_system_catalog,
swap_toast_by_content, false, true,
- frozenXid, cutoffMulti);
+ frozenXid, cutoffMulti,
+ OldHeap->rd_rel->relpersistence);
}
@@ -1475,7 +1476,8 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
bool check_constraints,
bool is_internal,
TransactionId frozenXid,
- MultiXactId cutoffMulti)
+ MultiXactId cutoffMulti,
+ char newrelpersistence)
{
ObjectAddress object;
Oid mapped_tables[4];
@@ -1519,6 +1521,12 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
reindex_flags = REINDEX_REL_SUPPRESS_INDEX_USE;
if (check_constraints)
reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
+
+ if (newrelpersistence == RELPERSISTENCE_UNLOGGED)
+ reindex_flags |= REINDEX_REL_FORCE_UNLOGGED;
+ else if (newrelpersistence == RELPERSISTENCE_PERMANENT)
+ reindex_flags |= REINDEX_REL_FORCE_PERMANENT;
+
reindex_relation(OIDOldHeap, reindex_flags);
/*
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 0205595..12b4ac7 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1689,7 +1689,7 @@ ReindexIndex(RangeVar *indexRelation)
RangeVarCallbackForReindexIndex,
(void *) &heapOid);
- reindex_index(indOid, false);
+ reindex_index(indOid, false, indexRelation->relpersistence);
return indOid;
}
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 523ba35..b19da4a 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -67,7 +67,7 @@ static void mv_GenerateOper(StringInfo buf, Oid opoid);
static void refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
int save_sec_context);
-static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap);
+static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersistence);
static void OpenMatViewIncrementalMaintenance(void);
static void CloseMatViewIncrementalMaintenance(void);
@@ -303,7 +303,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
Assert(matview_maintenance_depth == old_depth);
}
else
- refresh_by_heap_swap(matviewOid, OIDNewHeap);
+ refresh_by_heap_swap(matviewOid, OIDNewHeap, relpersistence);
/* Roll back any GUC changes */
AtEOXact_GUC(false, save_nestlevel);
@@ -759,10 +759,10 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
* swapping is handled by the called function, so it is not needed here.
*/
static void
-refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap)
+refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersistence)
{
finish_heap_swap(matviewOid, OIDNewHeap, false, false, true, true,
- RecentXmin, ReadNextMultiXactId());
+ RecentXmin, ReadNextMultiXactId(), relpersistence);
}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 714a9f1..093224f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -393,7 +393,6 @@ static void ATExecClusterOn(Relation rel, const char *indexName,
LOCKMODE lockmode);
static void ATExecDropCluster(Relation rel, LOCKMODE lockmode);
static bool ATPrepChangePersistence(Relation rel, bool toLogged);
-static void ATChangeIndexesPersistence(Oid relid, char relpersistence);
static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
char *tablespacename, LOCKMODE lockmode);
static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
@@ -3735,16 +3734,6 @@ 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->chgPersistence)
- ATChangeIndexesPersistence(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
@@ -3756,7 +3745,8 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
false, false, true,
!OidIsValid(tab->newTableSpace),
RecentXmin,
- ReadNextMultiXactId());
+ ReadNextMultiXactId(),
+ persistence);
}
else
{
@@ -10880,48 +10870,6 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
}
/*
- * Update the pg_class entry of each index for the given relation to the
- * given persistence.
- */
-static void
-ATChangeIndexesPersistence(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
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index e8ed999..e486007 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -3077,6 +3077,7 @@ RelationSetNewRelfilenode(Relation relation, TransactionId freezeXid,
}
classform->relfrozenxid = freezeXid;
classform->relminmxid = minmulti;
+ classform->relpersistence = relation->rd_rel->relpersistence;
simple_heap_update(pg_class, &tuple->t_self, tuple);
CatalogUpdateIndexes(pg_class, tuple);
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index c36a729..76b5d57 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -111,12 +111,15 @@ extern void validate_index(Oid heapId, Oid indexId, Snapshot snapshot);
extern void index_set_state_flags(Oid indexId, IndexStateFlagsAction action);
-extern void reindex_index(Oid indexId, bool skip_constraint_checks);
+extern void reindex_index(Oid indexId, bool skip_constraint_checks,
+ char relpersistence);
/* Flag bits for reindex_relation(): */
#define REINDEX_REL_PROCESS_TOAST 0x01
#define REINDEX_REL_SUPPRESS_INDEX_USE 0x02
#define REINDEX_REL_CHECK_CONSTRAINTS 0x04
+#define REINDEX_REL_FORCE_UNLOGGED 0x08
+#define REINDEX_REL_FORCE_PERMANENT 0x10
extern bool reindex_relation(Oid relid, int flags);
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index f7730a9..0b7e877 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -33,6 +33,7 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
bool check_constraints,
bool is_internal,
TransactionId frozenXid,
- MultiXactId minMulti);
+ MultiXactId minMulti,
+ char newrelpersistence);
#endif /* CLUSTER_H */
Thanks for the updated patch, Fabrizio.
On Tue, Nov 11, 2014 at 7:44 AM, Fabrízio de Royes Mello <
fabriziomello@gmail.com> wrote:
A couple of minor comments about this patch:
1) Reading it, I am wondering if it would not be finally time to
switch to a macro to get a relation's persistence, something like
RelationGetPersistence in rel.h... Not related directly to this patch.Good idea... I'll provide a patch soon.
I created a thread dedicated to that:
/messages/by-id/CAB7nPqSEB+HwiTXfWKQyPA7+9bjoLNxiO47QSrO3HCBSoQ0qFA@mail.gmail.com
Now few people cared enough to comment :)
2) reindex_index has as new argument a relpersislence value for the
new index. reindex_relation has differently a new set of flags to
enforce the relpersistence of all the underling indexes. Wouldn't it
be better for API consistency to pass directly a relpersistence value
through reindex_relation? In any case, the comment block of
reindex_relation does not contain a description of the new flags.I did it because the ReindexDatabase build a list of oids to run
reindex_relation for each item of the list. I can change the list to store
the relpersistence also, but this can lead us to a concurrency trouble
because if one session execute REINDEX DATABASE and other session run
"ALTER TABLE name SET {LOGGED|UNLOGGED}" during the building of the list
the reindex can lead us to a inconsistent state.
Fair point. I forgot this code path.
Except a typo with s/rebuilded/rebuilt/ in the patch, corrected in the
patch attached with some extra comments bundled, it seems to be that this
patch can be passed to a committer, so marking it so.
Btw, perhaps this diff should be pushed as a different patch as this is a
rather different thing:
- if (heapRelation->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED
&&
+ if (indexRelation->rd_rel->relpersistence ==
RELPERSISTENCE_UNLOGGED &&
!smgrexists(indexRelation->rd_smgr, INIT_FORKNUM)
As this is a correctness fix, it does not seem necessary to back-patch it:
the parent relation always has the same persistence as its indexes.
Regards,
--
Michael
Attachments:
20141111_persistence_fix.patchapplication/x-patch; name=20141111_persistence_fix.patchDownload
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 912038a..a0a81e8 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1980,7 +1980,7 @@ index_build(Relation heapRelation,
* created it, or truncated twice in a subsequent transaction, the
* relfilenode won't change, and nothing needs to be done here.
*/
- if (heapRelation->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
+ if (indexRelation->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
!smgrexists(indexRelation->rd_smgr, INIT_FORKNUM))
{
RegProcedure ambuildempty = indexRelation->rd_am->ambuildempty;
@@ -3130,7 +3130,7 @@ IndexGetRelation(Oid indexId, bool missing_ok)
* reindex_index - This routine is used to recreate a single index
*/
void
-reindex_index(Oid indexId, bool skip_constraint_checks)
+reindex_index(Oid indexId, bool skip_constraint_checks, char relpersistence)
{
Relation iRel,
heapRelation;
@@ -3191,6 +3191,9 @@ reindex_index(Oid indexId, bool skip_constraint_checks)
indexInfo->ii_ExclusionStrats = NULL;
}
+ /* Set the relpersistence of the new index */
+ iRel->rd_rel->relpersistence = relpersistence;
+
/* We'll build a new physical relation for the index */
RelationSetNewRelfilenode(iRel, InvalidTransactionId,
InvalidMultiXactId);
@@ -3310,6 +3313,12 @@ reindex_index(Oid indexId, bool skip_constraint_checks)
* performance, other callers should include the flag only after transforming
* the data in a manner that risks a change in constraint validity.
*
+ * REINDEX_REL_FORCE_UNLOGGED: if true, set the persistence of the rebuilt
+ * indexes to unlogged.
+ *
+ * REINDEX_REL_FORCE_LOGGED: if true, set the persistence of the rebuilt
+ * indexes to permanent.
+ *
* Returns true if any indexes were rebuilt (including toast table's index
* when relevant). Note that a CommandCounterIncrement will occur after each
* index rebuild.
@@ -3389,11 +3398,19 @@ reindex_relation(Oid relid, int flags)
foreach(indexId, indexIds)
{
Oid indexOid = lfirst_oid(indexId);
+ char relpersistence = rel->rd_rel->relpersistence;
if (is_pg_class)
RelationSetIndexList(rel, doneIndexes, InvalidOid);
- reindex_index(indexOid, !(flags & REINDEX_REL_CHECK_CONSTRAINTS));
+ /* Check for flags to enforce UNLOGGED or PERMANENT persistence */
+ if (flags & REINDEX_REL_FORCE_UNLOGGED)
+ relpersistence = RELPERSISTENCE_UNLOGGED;
+ else if (flags & REINDEX_REL_FORCE_PERMANENT)
+ relpersistence = RELPERSISTENCE_PERMANENT;
+
+ reindex_index(indexOid, !(flags & REINDEX_REL_CHECK_CONSTRAINTS),
+ relpersistence);
CommandCounterIncrement();
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 6a578ec..e067abc 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -589,7 +589,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
*/
finish_heap_swap(tableOid, OIDNewHeap, is_system_catalog,
swap_toast_by_content, false, true,
- frozenXid, cutoffMulti);
+ frozenXid, cutoffMulti,
+ OldHeap->rd_rel->relpersistence);
}
@@ -1475,7 +1476,8 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
bool check_constraints,
bool is_internal,
TransactionId frozenXid,
- MultiXactId cutoffMulti)
+ MultiXactId cutoffMulti,
+ char newrelpersistence)
{
ObjectAddress object;
Oid mapped_tables[4];
@@ -1519,6 +1521,16 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
reindex_flags = REINDEX_REL_SUPPRESS_INDEX_USE;
if (check_constraints)
reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
+
+ /*
+ * Ensure that the indexes of this relation have the same persistence
+ * as their parent relation.
+ */
+ if (newrelpersistence == RELPERSISTENCE_UNLOGGED)
+ reindex_flags |= REINDEX_REL_FORCE_UNLOGGED;
+ else if (newrelpersistence == RELPERSISTENCE_PERMANENT)
+ reindex_flags |= REINDEX_REL_FORCE_PERMANENT;
+
reindex_relation(OIDOldHeap, reindex_flags);
/*
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 0205595..12b4ac7 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1689,7 +1689,7 @@ ReindexIndex(RangeVar *indexRelation)
RangeVarCallbackForReindexIndex,
(void *) &heapOid);
- reindex_index(indOid, false);
+ reindex_index(indOid, false, indexRelation->relpersistence);
return indOid;
}
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 523ba35..b19da4a 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -67,7 +67,7 @@ static void mv_GenerateOper(StringInfo buf, Oid opoid);
static void refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
int save_sec_context);
-static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap);
+static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersistence);
static void OpenMatViewIncrementalMaintenance(void);
static void CloseMatViewIncrementalMaintenance(void);
@@ -303,7 +303,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
Assert(matview_maintenance_depth == old_depth);
}
else
- refresh_by_heap_swap(matviewOid, OIDNewHeap);
+ refresh_by_heap_swap(matviewOid, OIDNewHeap, relpersistence);
/* Roll back any GUC changes */
AtEOXact_GUC(false, save_nestlevel);
@@ -759,10 +759,10 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
* swapping is handled by the called function, so it is not needed here.
*/
static void
-refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap)
+refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersistence)
{
finish_heap_swap(matviewOid, OIDNewHeap, false, false, true, true,
- RecentXmin, ReadNextMultiXactId());
+ RecentXmin, ReadNextMultiXactId(), relpersistence);
}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 714a9f1..093224f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -393,7 +393,6 @@ static void ATExecClusterOn(Relation rel, const char *indexName,
LOCKMODE lockmode);
static void ATExecDropCluster(Relation rel, LOCKMODE lockmode);
static bool ATPrepChangePersistence(Relation rel, bool toLogged);
-static void ATChangeIndexesPersistence(Oid relid, char relpersistence);
static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
char *tablespacename, LOCKMODE lockmode);
static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
@@ -3735,16 +3734,6 @@ 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->chgPersistence)
- ATChangeIndexesPersistence(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
@@ -3756,7 +3745,8 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
false, false, true,
!OidIsValid(tab->newTableSpace),
RecentXmin,
- ReadNextMultiXactId());
+ ReadNextMultiXactId(),
+ persistence);
}
else
{
@@ -10880,48 +10870,6 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
}
/*
- * Update the pg_class entry of each index for the given relation to the
- * given persistence.
- */
-static void
-ATChangeIndexesPersistence(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
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index e8ed999..e486007 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -3077,6 +3077,7 @@ RelationSetNewRelfilenode(Relation relation, TransactionId freezeXid,
}
classform->relfrozenxid = freezeXid;
classform->relminmxid = minmulti;
+ classform->relpersistence = relation->rd_rel->relpersistence;
simple_heap_update(pg_class, &tuple->t_self, tuple);
CatalogUpdateIndexes(pg_class, tuple);
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index c36a729..76b5d57 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -111,12 +111,15 @@ extern void validate_index(Oid heapId, Oid indexId, Snapshot snapshot);
extern void index_set_state_flags(Oid indexId, IndexStateFlagsAction action);
-extern void reindex_index(Oid indexId, bool skip_constraint_checks);
+extern void reindex_index(Oid indexId, bool skip_constraint_checks,
+ char relpersistence);
/* Flag bits for reindex_relation(): */
#define REINDEX_REL_PROCESS_TOAST 0x01
#define REINDEX_REL_SUPPRESS_INDEX_USE 0x02
#define REINDEX_REL_CHECK_CONSTRAINTS 0x04
+#define REINDEX_REL_FORCE_UNLOGGED 0x08
+#define REINDEX_REL_FORCE_PERMANENT 0x10
extern bool reindex_relation(Oid relid, int flags);
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index f7730a9..0b7e877 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -33,6 +33,7 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
bool check_constraints,
bool is_internal,
TransactionId frozenXid,
- MultiXactId minMulti);
+ MultiXactId minMulti,
+ char newrelpersistence);
#endif /* CLUSTER_H */
Michael Paquier wrote:
Btw, perhaps this diff should be pushed as a different patch as this is a rather different thing: - if (heapRelation->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED && + if (indexRelation->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED && !smgrexists(indexRelation->rd_smgr, INIT_FORKNUM) As this is a correctness fix, it does not seem necessary to back-patch it: the parent relation always has the same persistence as its indexes.
There was an argument for doing it this way that only applies if this
patch went in, but I can't remember now what it was.
Anyway I pushed the patch after some slight additional changes. Thanks!
--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sat, Nov 15, 2014 at 2:23 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:
Michael Paquier wrote:
Btw, perhaps this diff should be pushed as a different patch as this is
a
rather different thing:
- if (heapRelation->rd_rel->relpersistence ==
RELPERSISTENCE_UNLOGGED
&&
+ if (indexRelation->rd_rel->relpersistence ==
RELPERSISTENCE_UNLOGGED &&
!smgrexists(indexRelation->rd_smgr, INIT_FORKNUM)
As this is a correctness fix, it does not seem necessary to back-patch
it:
the parent relation always has the same persistence as its indexes.
There was an argument for doing it this way that only applies if this
patch went in, but I can't remember now what it was.Anyway I pushed the patch after some slight additional changes. Thanks!
Thanks!
--
Fabrízio de Royes Mello
Consultoria/Coaching PostgreSQL
Show quoted text
Timbira: http://www.timbira.com.br
Blog: http://fabriziomello.github.io
Linkedin: http://br.linkedin.com/in/fabriziomello
Twitter: http://twitter.com/fabriziomello
Github: http://github.com/fabriziomello
* Alvaro Herrera wrote:
Michael Paquier wrote:
Btw, perhaps this diff should be pushed as a different patch as this is a rather different thing: - if (heapRelation->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED && + if (indexRelation->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED && !smgrexists(indexRelation->rd_smgr, INIT_FORKNUM) As this is a correctness fix, it does not seem necessary to back-patch it: the parent relation always has the same persistence as its indexes.There was an argument for doing it this way that only applies if this
patch went in, but I can't remember now what it was.Anyway I pushed the patch after some slight additional changes. Thanks!
The buildfarm says -DCLOBBER_CACHE_ALWAYS does not like this patch.
--
Christian
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Nov 17, 2014 at 5:56 AM, Christian Ullrich <chris@chrullrich.net> wrote:
* Alvaro Herrera wrote:
Michael Paquier wrote:
Btw, perhaps this diff should be pushed as a different patch as this is a rather different thing: - if (heapRelation->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED && + if (indexRelation->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED && !smgrexists(indexRelation->rd_smgr, INIT_FORKNUM) As this is a correctness fix, it does not seem necessary to back-patch it: the parent relation always has the same persistence as its indexes.There was an argument for doing it this way that only applies if this
patch went in, but I can't remember now what it was.Anyway I pushed the patch after some slight additional changes. Thanks!
The buildfarm says -DCLOBBER_CACHE_ALWAYS does not like this patch.
The complaint comes from jaguarundi, like here:
http://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=jaguarundi&dt=2014-11-16%2015%3A33%3A23
Adding a new parameter to RelationSetNewRelFile
As Tom mentioned, adding a new parameter to set the persistence
through RelationSetNewRelfilenode works. Patch, actually tested with
CLOBBER_CACHE_ALWAYS, attached.
/messages/by-id/27238.1416073917@sss.pgh.pa.us
Regards,
--
Michael
Attachments:
0001-Fix-CLOBBER_CACHE_ALWAYS-broken-by-85b506b.patchtext/x-patch; charset=US-ASCII; name=0001-Fix-CLOBBER_CACHE_ALWAYS-broken-by-85b506b.patchDownload
From a1a7e4182bd1b5d66260220f040aee4a35e91c75 Mon Sep 17 00:00:00 2001
From: Michael Paquier <mpaquier@otacoo.com>
Date: Mon, 17 Nov 2014 17:04:05 +0000
Subject: [PATCH] Fix CLOBBER_CACHE_ALWAYS broken by 85b506b
Previous commit that removed ATChangeIndexesPersistence to replace it
with a lower-level API able to define a new relpersistence for a relation
when reindexing it failed with CLOBBER_CACHE_ALWAYS enabled. This patch
fixes it by setting the relpersistence of the newly-created relation after
its relfilenode is created by passing a new parameter to RelationSetNewRelfilenode
able to set the persistence of a relation.
---
src/backend/catalog/index.c | 5 +----
src/backend/commands/sequence.c | 3 ++-
src/backend/commands/tablecmds.c | 6 ++++--
src/backend/utils/cache/relcache.c | 6 +++---
src/include/utils/relcache.h | 4 +++-
5 files changed, 13 insertions(+), 11 deletions(-)
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index b57aa95..825235a 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3191,12 +3191,9 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence)
indexInfo->ii_ExclusionStrats = NULL;
}
- /* Set the relpersistence of the new index */
- iRel->rd_rel->relpersistence = persistence;
-
/* We'll build a new physical relation for the index */
RelationSetNewRelfilenode(iRel, InvalidTransactionId,
- InvalidMultiXactId);
+ InvalidMultiXactId, persistence);
/* Initialize the index and rebuild */
/* Note: we do not need to re-establish pkey setting */
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index e5f7765..4f7f586 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -304,7 +304,8 @@ ResetSequence(Oid seq_relid)
* Same with relminmxid, since a sequence will never contain multixacts.
*/
RelationSetNewRelfilenode(seq_rel, InvalidTransactionId,
- InvalidMultiXactId);
+ InvalidMultiXactId,
+ seq_rel->rd_rel->relpersistence);
/*
* Insert the modified tuple into the new storage file.
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 093224f..c57751e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1196,7 +1196,8 @@ ExecuteTruncate(TruncateStmt *stmt)
* as the relfilenode value. The old storage file is scheduled for
* deletion at commit.
*/
- RelationSetNewRelfilenode(rel, RecentXmin, minmulti);
+ RelationSetNewRelfilenode(rel, RecentXmin, minmulti,
+ rel->rd_rel->relpersistence);
if (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
heap_create_init_fork(rel);
@@ -1209,7 +1210,8 @@ ExecuteTruncate(TruncateStmt *stmt)
if (OidIsValid(toast_relid))
{
rel = relation_open(toast_relid, AccessExclusiveLock);
- RelationSetNewRelfilenode(rel, RecentXmin, minmulti);
+ RelationSetNewRelfilenode(rel, RecentXmin, minmulti,
+ rel->rd_rel->relpersistence);
if (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
heap_create_init_fork(rel);
heap_close(rel, NoLock);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2250c56..fa75771 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -3008,7 +3008,7 @@ RelationBuildLocalRelation(const char *relname,
*/
void
RelationSetNewRelfilenode(Relation relation, TransactionId freezeXid,
- MultiXactId minmulti)
+ MultiXactId minmulti, char relpersistence)
{
Oid newrelfilenode;
RelFileNodeBackend newrnode;
@@ -3048,7 +3048,7 @@ RelationSetNewRelfilenode(Relation relation, TransactionId freezeXid,
newrnode.node = relation->rd_node;
newrnode.node.relNode = newrelfilenode;
newrnode.backend = relation->rd_backend;
- RelationCreateStorage(newrnode.node, relation->rd_rel->relpersistence);
+ RelationCreateStorage(newrnode.node, relpersistence);
smgrclosenode(newrnode);
/*
@@ -3078,7 +3078,7 @@ RelationSetNewRelfilenode(Relation relation, TransactionId freezeXid,
}
classform->relfrozenxid = freezeXid;
classform->relminmxid = minmulti;
- classform->relpersistence = relation->rd_rel->relpersistence;
+ classform->relpersistence = relpersistence;
simple_heap_update(pg_class, &tuple->t_self, tuple);
CatalogUpdateIndexes(pg_class, tuple);
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index e4ca70f..6e0fc56 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -96,7 +96,9 @@ extern Relation RelationBuildLocalRelation(const char *relname,
* Routine to manage assignment of new relfilenode to a relation
*/
extern void RelationSetNewRelfilenode(Relation relation,
- TransactionId freezeXid, MultiXactId minmulti);
+ TransactionId freezeXid,
+ MultiXactId minmulti,
+ char relpersistence);
/*
* Routines for flushing/rebuilding relcache entries in various scenarios
--
2.1.3
Michael Paquier wrote:
The complaint comes from jaguarundi, like here:
http://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=jaguarundi&dt=2014-11-16%2015%3A33%3A23As Tom mentioned, adding a new parameter to set the persistence
through RelationSetNewRelfilenode works. Patch, actually tested with
CLOBBER_CACHE_ALWAYS, attached.
I wrote an identical patch on Saturday and watched it pass
CLOBBER_CACHE_ALWAYS test on Sunday. But the reason I didn't push is I
couldn't understand *why* is there a problem here. I imagine that the
source of the issue is supposed to be a relcache invalidation that takes
place (in the original code) after reindex_index changes relpersistence,
and before RelationSetNewRelfilenode() creates the filenode. But at
what point does that code absorb invalidation messages? Or is there a
completely different mechanism that causes the problem? If so, what?
--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Alvaro Herrera wrote:
I wrote an identical patch on Saturday and watched it pass
CLOBBER_CACHE_ALWAYS test on Sunday. But the reason I didn't push is I
couldn't understand *why* is there a problem here. I imagine that the
source of the issue is supposed to be a relcache invalidation that takes
place (in the original code) after reindex_index changes relpersistence,
and before RelationSetNewRelfilenode() creates the filenode. But at
what point does that code absorb invalidation messages? Or is there a
completely different mechanism that causes the problem? If so, what?
Ah, it's the anti-collision stuff in GetNewOid(), isn't it.
--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers