The attached patch implements ALTER TABLE ... SET ACCESS METHOD. For simplicity, I used the normal alter table path, ATRewriteTable(), which does not follow the stricter isolation semantics that VACUUM FULL follows. If someone thinks that's unacceptable, please let me know.
Regards, Jeff Davis
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 6166b263345..255317883bc 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -76,6 +76,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> SET WITHOUT CLUSTER SET WITHOUT OIDS SET TABLESPACE <replaceable class="parameter">new_tablespace</replaceable> + SET ACCESS METHOD <replaceable class="parameter">new_access_method</replaceable> SET { LOGGED | UNLOGGED } SET ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) RESET ( <replaceable class="parameter">storage_parameter</replaceable> [, ... ] ) @@ -706,6 +707,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM </listitem> </varlistentry> + <varlistentry> + <term><literal>SET ACCESS METHOD</literal></term> + <listitem> + <para> + This form changes the access method of the table. See <xref + linkend="tableam"/> for more information. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><literal>SET { LOGGED | UNLOGGED }</literal></term> <listitem> @@ -1219,6 +1230,15 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM </listitem> </varlistentry> + <varlistentry> + <term><replaceable class="parameter">new_access_method</replaceable></term> + <listitem> + <para> + The name of the access method to which the table will be converted. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><replaceable class="parameter">new_schema</replaceable></term> <listitem> diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 6487a9e3fcb..e0c84d649ee 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -597,6 +597,7 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose) /* Create the transient table that will receive the re-ordered data */ OIDNewHeap = make_new_heap(tableOid, tableSpace, + OldHeap->rd_rel->relam, relpersistence, AccessExclusiveLock); @@ -626,8 +627,8 @@ 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, char relpersistence, - LOCKMODE lockmode) +make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, + char relpersistence, LOCKMODE lockmode) { TupleDesc OldHeapDesc; char NewHeapName[NAMEDATALEN]; @@ -686,7 +687,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence, InvalidOid, InvalidOid, OldHeap->rd_rel->relowner, - OldHeap->rd_rel->relam, + NewAccessMethod, OldHeapDesc, NIL, RELKIND_RELATION, @@ -1036,6 +1037,10 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class, relform1->reltablespace = relform2->reltablespace; relform2->reltablespace = swaptemp; + swaptemp = relform1->relam; + relform1->relam = relform2->relam; + relform2->relam = swaptemp; + swptmpchr = relform1->relpersistence; relform1->relpersistence = relform2->relpersistence; relform2->relpersistence = swptmpchr; diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 172ec6e9828..1ebd02fd684 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -298,8 +298,9 @@ 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, relpersistence, - ExclusiveLock); + OIDNewHeap = make_new_heap(matviewOid, tableSpace, + matviewRel->rd_rel->relam, + relpersistence, ExclusiveLock); LockRelationOid(OIDNewHeap, AccessExclusiveLock); dest = CreateTransientRelDestReceiver(OIDNewHeap); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 0da61784d7a..be91a00a170 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -177,6 +177,7 @@ typedef struct AlteredTableInfo List *afterStmts; /* List of utility command parsetrees */ bool verify_new_notnull; /* T if we should recheck NOT NULL */ int rewrite; /* Reason for forced rewrite, if any */ + Oid newAccessMethod; /* new access method; 0 means no change */ Oid newTableSpace; /* new tablespace; 0 means no change */ bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */ char newrelpersistence; /* if above is true */ @@ -540,6 +541,7 @@ static void change_owner_recurse_to_sequences(Oid relationOid, static ObjectAddress ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode); static void ATExecDropCluster(Relation rel, LOCKMODE lockmode); +static void ATPrepSetAccessMethod(AlteredTableInfo *tab, Relation rel, const char *amname); static bool ATPrepChangePersistence(Relation rel, bool toLogged); static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, const char *tablespacename, LOCKMODE lockmode); @@ -4287,6 +4289,7 @@ AlterTableGetLockLevel(List *cmds) cmd_lockmode = ShareUpdateExclusiveLock; break; + case AT_SetAccessMethod: case AT_SetLogged: case AT_SetUnLogged: cmd_lockmode = AccessExclusiveLock; @@ -4609,6 +4612,15 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* No command-specific prep needed */ pass = AT_PASS_MISC; break; + case AT_SetAccessMethod: /* SET ACCESS METHOD */ + ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW); + if (tab->newAccessMethod) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot change access method setting twice"))); + ATPrepSetAccessMethod(tab, rel, cmd->name); + pass = AT_PASS_MISC; + break; case AT_SetLogged: /* SET LOGGED */ ATSimplePermissions(rel, ATT_TABLE); if (tab->chgPersistence) @@ -5012,6 +5024,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, case AT_DropCluster: /* SET WITHOUT CLUSTER */ ATExecDropCluster(rel, lockmode); break; + case AT_SetAccessMethod: /* SET ACCESS METHOD */ + break; case AT_SetLogged: /* SET LOGGED */ case AT_SetUnLogged: /* SET UNLOGGED */ break; @@ -5365,6 +5379,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, Relation OldHeap; Oid OIDNewHeap; Oid NewTableSpace; + Oid accessmethod; char persistence; OldHeap = table_open(tab->relid, NoLock); @@ -5411,6 +5426,13 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, persistence = tab->chgPersistence ? tab->newrelpersistence : OldHeap->rd_rel->relpersistence; + /* + * Select access method of transient table (same as original + * unless user requested a change) + */ + accessmethod = OidIsValid(tab->newAccessMethod) ? + tab->newAccessMethod : OldHeap->rd_rel->relam; + table_close(OldHeap, NoLock); /* @@ -5443,8 +5465,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, * persistence. That wouldn't work for pg_class, but that can't be * unlogged anyway. */ - OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, persistence, - lockmode); + OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, accessmethod, + persistence, lockmode); /* * Copy the heap data into the new table with the desired @@ -15695,6 +15717,34 @@ ATExecSetCompression(AlteredTableInfo *tab, } +/* + * Preparation phase for SET TABLESPACE + * + * Check that access method exists. If it's the same as the table's current + * access method, it's a no-op. Otherwise, a table rewrite is necessary. + */ +static void +ATPrepSetAccessMethod(AlteredTableInfo *tab, Relation rel, const char *amname) +{ + Oid amoid; + + /* Check that the table access method exists */ + amoid = get_table_am_oid(amname, false); + + if (rel->rd_rel->relam == amoid) + return; + + /* Save info for Phase 3 to do the real work */ + if (OidIsValid(tab->newAccessMethod)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot have multiple SET ACCESS METHOD subcommands"))); + + tab->rewrite |= AT_REWRITE_ACCESS_METHOD; + tab->newAccessMethod = amoid; +} + + /* * Preparation phase for SET LOGGED/UNLOGGED * diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index b4ab4014c87..9e41e84b64c 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -2475,6 +2475,14 @@ alter_table_cmd: n->name = NULL; $$ = (Node *)n; } + /* ALTER TABLE <name> SET ACCESS METHOD <tablespacename> */ + | SET ACCESS METHOD name + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_SetAccessMethod; + n->name = $4; + $$ = (Node *)n; + } /* ALTER TABLE <name> SET LOGGED */ | SET LOGGED { diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h index a941f2accda..b64d3bc2040 100644 --- a/src/include/commands/cluster.h +++ b/src/include/commands/cluster.h @@ -35,8 +35,8 @@ 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, char relpersistence, - LOCKMODE lockmode); +extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, + char relpersistence, LOCKMODE lockmode); extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool is_system_catalog, bool swap_toast_by_content, diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h index c11bf2d7810..e765e67fd10 100644 --- a/src/include/commands/event_trigger.h +++ b/src/include/commands/event_trigger.h @@ -32,6 +32,7 @@ typedef struct EventTriggerData #define AT_REWRITE_ALTER_PERSISTENCE 0x01 #define AT_REWRITE_DEFAULT_VAL 0x02 #define AT_REWRITE_COLUMN_REWRITE 0x04 +#define AT_REWRITE_ACCESS_METHOD 0x08 /* * EventTriggerData is the node type that is passed as fmgr "context" info diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 615dfa26aa2..cce5c0834ee 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1898,6 +1898,7 @@ typedef enum AlterTableType AT_ChangeOwner, /* change owner */ AT_ClusterOn, /* CLUSTER ON */ AT_DropCluster, /* SET WITHOUT CLUSTER */ + AT_SetAccessMethod, /* SET ACCESS METHOD */ AT_SetLogged, /* SET LOGGED */ AT_SetUnLogged, /* SET UNLOGGED */ AT_DropOids, /* SET WITHOUT OIDS */