diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 1c7f48938b..bf90040aa2 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -75,6 +75,7 @@ ALTER TABLE [ IF EXISTS ] name CLUSTER ON index_name SET WITHOUT CLUSTER SET WITHOUT OIDS + SET ACCESS METHOD new_access_method SET TABLESPACE new_tablespace SET { LOGGED | UNLOGGED } SET ( storage_parameter [= value] [, ... ] ) @@ -692,6 +693,16 @@ WITH ( MODULUS numeric_literal, REM + + SET ACCESS METHOD + + + This form changes the access method of the table by rewriting it. See + for more information. + + + + SET TABLESPACE @@ -1228,6 +1239,15 @@ WITH ( MODULUS numeric_literal, REM + + new_access_method + + + The name of the access method to which the table will be converted. + + + + new_tablespace diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 6487a9e3fc..b3d8b6deb0 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -576,6 +576,7 @@ static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose) { Oid tableOid = RelationGetRelid(OldHeap); + Oid accessMethod = OldHeap->rd_rel->relam; Oid tableSpace = OldHeap->rd_rel->reltablespace; Oid OIDNewHeap; char relpersistence; @@ -597,6 +598,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, + accessMethod, relpersistence, AccessExclusiveLock); @@ -618,16 +620,16 @@ 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. Also, it's built - * with the specified persistence, which might differ from the original's. + * duplicates the logical structure of the OldHeap; but will have the + * specified physical storage properties NewTableSpace, NewAccessMethod, and + * relpersistence. * * 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) +make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, + char relpersistence, LOCKMODE lockmode) { TupleDesc OldHeapDesc; char NewHeapName[NAMEDATALEN]; @@ -686,7 +688,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 +1038,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; @@ -1071,6 +1077,9 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class, if (relform1->relpersistence != relform2->relpersistence) elog(ERROR, "cannot change persistence of mapped relation \"%s\"", NameStr(relform1->relname)); + if (relform1->relam != relform2->relam) + elog(ERROR, "cannot change access method 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\"", diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 25bbd8a5c1..9493b227b4 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 a16e749506..fcd778c62a 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -176,6 +176,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 */ @@ -538,6 +539,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); @@ -4096,6 +4098,7 @@ AlterTableGetLockLevel(List *cmds) */ case AT_AddColumn: /* may rewrite heap, in some cases and visible * to SELECT */ + case AT_SetAccessMethod: /* must rewrite heap */ case AT_SetTableSpace: /* must rewrite heap */ case AT_AlterColumnType: /* must rewrite heap */ cmd_lockmode = AccessExclusiveLock; @@ -4622,6 +4625,24 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE); pass = AT_PASS_DROP; break; + case AT_SetAccessMethod: /* SET ACCESS METHOD */ + ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_MATVIEW); + + /* partitioned tables don't have an access method */ + if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot change access method of a partitioned table"))); + + /* check if another access method change was already requested */ + if (OidIsValid(tab->newAccessMethod)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot have multiple SET ACCESS METHOD subcommands"))); + + ATPrepSetAccessMethod(tab, rel, cmd->name); + pass = AT_PASS_MISC; /* does not matter; no work in Phase 2 */ + break; case AT_SetTableSpace: /* SET TABLESPACE */ ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_PARTITIONED_INDEX); @@ -4997,6 +5018,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, case AT_DropOids: /* SET WITHOUT OIDS */ /* nothing to do here, oid columns don't exist anymore */ break; + case AT_SetAccessMethod: /* SET ACCESS METHOD */ + /* handled specially in Phase 3 */ + break; case AT_SetTableSpace: /* SET TABLESPACE */ /* @@ -5324,7 +5348,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, /* * We only need to rewrite the table if at least one column needs to - * be recomputed, or we are changing its persistence. + * be recomputed, or we are changing its persistence or access method. * * There are two reasons for requiring a rewrite when changing * persistence: on one hand, we need to ensure that the buffers @@ -5338,6 +5362,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, /* Build a temporary relation and copy data */ Relation OldHeap; Oid OIDNewHeap; + Oid NewAccessMethod; Oid NewTableSpace; char persistence; @@ -5378,6 +5403,15 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, else NewTableSpace = OldHeap->rd_rel->reltablespace; + /* + * Select destination access method (same as original unless user + * requested a change) + */ + if (OidIsValid(tab->newAccessMethod)) + NewAccessMethod = tab->newAccessMethod; + else + NewAccessMethod = OldHeap->rd_rel->relam; + /* * Select persistence of transient table (same as original unless * user requested a change) @@ -5417,8 +5451,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, NewAccessMethod, + persistence, lockmode); /* * Copy the heap data into the new table with the desired @@ -5933,6 +5967,8 @@ ATGetQueueEntry(List **wqueue, Relation rel) tab->rel = NULL; /* set later */ tab->relkind = rel->rd_rel->relkind; tab->oldDesc = CreateTupleDescCopyConstr(RelationGetDescr(rel)); + tab->newAccessMethod = InvalidOid; + tab->newTableSpace = InvalidOid; tab->newrelpersistence = RELPERSISTENCE_PERMANENT; tab->chgPersistence = false; @@ -6003,6 +6039,8 @@ alter_table_type_to_string(AlterTableType cmdtype) return "CLUSTER ON"; case AT_DropCluster: return "SET WITHOUT CLUSTER"; + case AT_SetAccessMethod: + return "SET ACCESS METHOD"; case AT_SetLogged: return "SET LOGGED"; case AT_SetUnLogged: @@ -13609,6 +13647,28 @@ ATExecDropCluster(Relation rel, LOCKMODE lockmode) mark_index_clustered(rel, InvalidOid, false); } +/* + * Preparation phase for SET ACCESS METHOD + * + * Check that access method exists. If it is the same as the table's current + * access method, it is 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 */ + tab->rewrite |= AT_REWRITE_ACCESS_METHOD; + tab->newAccessMethod = amoid; +} + /* * ALTER TABLE SET TABLESPACE */ diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 10da5c5c51..39a2849eba 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -2623,6 +2623,14 @@ alter_table_cmd: n->newowner = $3; $$ = (Node *)n; } + /* ALTER TABLE SET ACCESS METHOD */ + | SET ACCESS METHOD name + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_SetAccessMethod; + n->name = $4; + $$ = (Node *)n; + } /* ALTER TABLE SET TABLESPACE */ | SET TABLESPACE name { diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index d6bf725971..cc13fc05bb 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -2141,8 +2141,15 @@ psql_completion(const char *text, int start, int end) } /* If we have ALTER TABLE SET, provide list of attributes and '(' */ else if (Matches("ALTER", "TABLE", MatchAny, "SET")) - COMPLETE_WITH("(", "LOGGED", "SCHEMA", "TABLESPACE", "UNLOGGED", - "WITH", "WITHOUT"); + COMPLETE_WITH("(", "ACCESS METHOD", "LOGGED", "SCHEMA", + "TABLESPACE", "UNLOGGED", "WITH", "WITHOUT"); + + /* + * If we have ALTER TABLE SET ACCESS METHOD provide a list of table + * AMs. + */ + else if (Matches("ALTER", "TABLE", MatchAny, "SET", "ACCESS", "METHOD")) + COMPLETE_WITH_QUERY(Query_for_list_of_table_access_methods); /* * If we have ALTER TABLE SET TABLESPACE provide a list of diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h index a941f2accd..b64d3bc204 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 c11bf2d781..e765e67fd1 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 947660a4b0..e28248af32 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1901,6 +1901,7 @@ typedef enum AlterTableType AT_SetLogged, /* SET LOGGED */ AT_SetUnLogged, /* SET UNLOGGED */ AT_DropOids, /* SET WITHOUT OIDS */ + AT_SetAccessMethod, /* SET ACCESS METHOD */ AT_SetTableSpace, /* SET TABLESPACE */ AT_SetRelOptions, /* SET (...) -- AM specific parameters */ AT_ResetRelOptions, /* RESET (...) -- AM specific parameters */ diff --git a/src/test/regress/expected/create_am.out b/src/test/regress/expected/create_am.out index 0dfb26c301..32b7134080 100644 --- a/src/test/regress/expected/create_am.out +++ b/src/test/regress/expected/create_am.out @@ -230,6 +230,40 @@ ORDER BY classid, objid, objsubid; table tableam_parted_d_heap2 (5 rows) +-- ALTER TABLE SET ACCESS METHOD +CREATE TABLE heaptable USING heap AS + SELECT a, repeat(a::text, 100) FROM generate_series(1,9) AS a; +SELECT amname FROM pg_class c, pg_am am + WHERE c.relam = am.oid AND c.oid = 'heaptable'::regclass; + amname +-------- + heap +(1 row) + +ALTER TABLE heaptable SET ACCESS METHOD heap2; +SELECT amname FROM pg_class c, pg_am am + WHERE c.relam = am.oid AND c.oid = 'heaptable'::regclass; + amname +-------- + heap2 +(1 row) + +SELECT COUNT(a), COUNT(1) FILTER(WHERE a=1) FROM heaptable; + count | count +-------+------- + 9 | 1 +(1 row) + +-- No support for multiple subcommands +ALTER TABLE heaptable SET ACCESS METHOD heap, SET ACCESS METHOD heap2; +ERROR: cannot have multiple SET ACCESS METHOD subcommands +DROP TABLE heaptable; +-- No support for partitioned tables. +CREATE TABLE am_partitioned(x INT, y INT) + PARTITION BY hash (x); +ALTER TABLE am_partitioned SET ACCESS METHOD heap2; +ERROR: cannot change access method of a partitioned table +DROP TABLE am_partitioned; -- Second, create objects in the new AM by changing the default AM BEGIN; SET LOCAL default_table_access_method = 'heap2'; diff --git a/src/test/regress/sql/create_am.sql b/src/test/regress/sql/create_am.sql index 9a359466ce..967bfac21a 100644 --- a/src/test/regress/sql/create_am.sql +++ b/src/test/regress/sql/create_am.sql @@ -161,6 +161,23 @@ WHERE pg_depend.refclassid = 'pg_am'::regclass AND pg_am.amname = 'heap2' ORDER BY classid, objid, objsubid; +-- ALTER TABLE SET ACCESS METHOD +CREATE TABLE heaptable USING heap AS + SELECT a, repeat(a::text, 100) FROM generate_series(1,9) AS a; +SELECT amname FROM pg_class c, pg_am am + WHERE c.relam = am.oid AND c.oid = 'heaptable'::regclass; +ALTER TABLE heaptable SET ACCESS METHOD heap2; +SELECT amname FROM pg_class c, pg_am am + WHERE c.relam = am.oid AND c.oid = 'heaptable'::regclass; +SELECT COUNT(a), COUNT(1) FILTER(WHERE a=1) FROM heaptable; +-- No support for multiple subcommands +ALTER TABLE heaptable SET ACCESS METHOD heap, SET ACCESS METHOD heap2; +DROP TABLE heaptable; +-- No support for partitioned tables. +CREATE TABLE am_partitioned(x INT, y INT) + PARTITION BY hash (x); +ALTER TABLE am_partitioned SET ACCESS METHOD heap2; +DROP TABLE am_partitioned; -- Second, create objects in the new AM by changing the default AM BEGIN;