Skip to content

Commit 374c7a2

Browse files
committed
Allow specifying an access method for partitioned tables
It's now possible to specify a table access method via CREATE TABLE ... USING for a partitioned table, as well change it with ALTER TABLE ... SET ACCESS METHOD. Specifying an AM for a partitioned table lets the value be used for all future partitions created under it, closely mirroring the behavior of the TABLESPACE option for partitioned tables. Existing partitions are not modified. For a partitioned table with no AM specified, any new partitions are created with the default_table_access_method. Also add ALTER TABLE ... SET ACCESS METHOD DEFAULT, which reverts to the original state of using the default for new partitions. The relcache of partitioned tables is not changed: rd_tableam is not set, even if a partitioned table has a relam set. Author: Justin Pryzby <pryzby@telsasoft.com> Author: Soumyadeep Chakraborty <soumyadeep2007@gmail.com> Author: Michaël Paquier <michael@paquier.xyz> Reviewed-by: The authors themselves Discussion: https://postgr.es/m/CAE-ML+9zM4wJCGCBGv01k96qQ3gFv4WFcFy=zqPHKeaEFwwv6A@mail.gmail.com Discussion: https://postgr.es/m/20210308010707.GA29832%40telsasoft.com
1 parent b8528fe commit 374c7a2

File tree

12 files changed

+469
-49
lines changed

12 files changed

+469
-49
lines changed

doc/src/sgml/catalogs.sgml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1988,9 +1988,13 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
19881988
(references <link linkend="catalog-pg-am"><structname>pg_am</structname></link>.<structfield>oid</structfield>)
19891989
</para>
19901990
<para>
1991-
If this is a table or an index, the access method used (heap,
1992-
B-tree, hash, etc.); otherwise zero (zero occurs for sequences,
1993-
as well as relations without storage, such as views)
1991+
The access method used to access this table or index.
1992+
Not meaningful if the relation is a sequence or
1993+
has no on-disk file,
1994+
except for partitioned tables, where, if set, it takes
1995+
precedence over <varname>default_table_access_method</varname>
1996+
when determining the access method to use for partitions created
1997+
when one is not specified in the creation command.
19941998
</para></entry>
19951999
</row>
19962000

doc/src/sgml/ref/alter_table.sgml

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -732,10 +732,20 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
732732
<term><literal>SET ACCESS METHOD</literal></term>
733733
<listitem>
734734
<para>
735-
This form changes the access method of the table by rewriting it. See
736-
<xref linkend="tableam"/> for more information. Writing
737-
<literal>DEFAULT</literal> changes the access method of the table
738-
to <xref linkend="guc-default-table-access-method"/>.
735+
This form changes the access method of the table by rewriting it
736+
using the indicated access method; specifying
737+
<literal>DEFAULT</literal> selects the access method set as the
738+
<xref linkend="guc-default-table-access-method"/> configuration
739+
parameter.
740+
See <xref linkend="tableam"/> for more information.
741+
</para>
742+
<para>
743+
When applied to a partitioned table, there is no data to rewrite,
744+
but partitions created afterwards will default to the given access
745+
method unless overridden by a <literal>USING</literal> clause.
746+
Specifying <varname>DEFAULT</varname> removes a previous value,
747+
causing future partitions to default to
748+
<varname>default_table_access_method</varname>.
739749
</para>
740750
</listitem>
741751
</varlistentry>

doc/src/sgml/ref/create_table.sgml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1365,6 +1365,10 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
13651365
method is chosen for the new table. See <xref
13661366
linkend="guc-default-table-access-method"/> for more information.
13671367
</para>
1368+
<para>
1369+
When creating a partition, the table access method is the access method
1370+
of its partitioned table, if set.
1371+
</para>
13681372
</listitem>
13691373
</varlistentry>
13701374

src/backend/commands/tablecmds.c

Lines changed: 135 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,9 @@ typedef struct AlteredTableInfo
184184
List *afterStmts; /* List of utility command parsetrees */
185185
bool verify_new_notnull; /* T if we should recheck NOT NULL */
186186
int rewrite; /* Reason for forced rewrite, if any */
187-
Oid newAccessMethod; /* new access method; 0 means no change */
187+
bool chgAccessMethod; /* T if SET ACCESS METHOD is used */
188+
Oid newAccessMethod; /* new access method; 0 means no change,
189+
* if above is true */
188190
Oid newTableSpace; /* new tablespace; 0 means no change */
189191
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
190192
char newrelpersistence; /* if above is true */
@@ -595,6 +597,7 @@ static ObjectAddress ATExecClusterOn(Relation rel, const char *indexName,
595597
LOCKMODE lockmode);
596598
static void ATExecDropCluster(Relation rel, LOCKMODE lockmode);
597599
static void ATPrepSetAccessMethod(AlteredTableInfo *tab, Relation rel, const char *amname);
600+
static void ATExecSetAccessMethodNoStorage(Relation rel, Oid newAccessMethod);
598601
static bool ATPrepChangePersistence(Relation rel, bool toLogged);
599602
static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
600603
const char *tablespacename, LOCKMODE lockmode);
@@ -709,7 +712,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
709712
Oid ofTypeId;
710713
ObjectAddress address;
711714
LOCKMODE parentLockmode;
712-
const char *accessMethod = NULL;
713715
Oid accessMethodId = InvalidOid;
714716

715717
/*
@@ -954,24 +956,22 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
954956
}
955957

956958
/*
957-
* If the statement hasn't specified an access method, but we're defining
958-
* a type of relation that needs one, use the default.
959+
* Select access method to use: an explicitly indicated one, or (in the
960+
* case of a partitioned table) the parent's, if it has one.
959961
*/
960962
if (stmt->accessMethod != NULL)
963+
accessMethodId = get_table_am_oid(stmt->accessMethod, false);
964+
else if (stmt->partbound)
961965
{
962-
accessMethod = stmt->accessMethod;
963-
964-
if (partitioned)
965-
ereport(ERROR,
966-
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
967-
errmsg("specifying a table access method is not supported on a partitioned table")));
966+
Assert(list_length(inheritOids) == 1);
967+
accessMethodId = get_rel_relam(linitial_oid(inheritOids));
968968
}
969-
else if (RELKIND_HAS_TABLE_AM(relkind))
970-
accessMethod = default_table_access_method;
969+
else
970+
accessMethodId = InvalidOid;
971971

972-
/* look up the access method, verify it is for a table */
973-
if (accessMethod != NULL)
974-
accessMethodId = get_table_am_oid(accessMethod, false);
972+
/* still nothing? use the default */
973+
if (RELKIND_HAS_TABLE_AM(relkind) && !OidIsValid(accessMethodId))
974+
accessMethodId = get_table_am_oid(default_table_access_method, false);
975975

976976
/*
977977
* Create the relation. Inherited defaults and constraints are passed in
@@ -5047,14 +5047,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
50475047
case AT_SetAccessMethod: /* SET ACCESS METHOD */
50485048
ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_MATVIEW);
50495049

5050-
/* partitioned tables don't have an access method */
5051-
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
5052-
ereport(ERROR,
5053-
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
5054-
errmsg("cannot change access method of a partitioned table")));
5055-
50565050
/* check if another access method change was already requested */
5057-
if (OidIsValid(tab->newAccessMethod))
5051+
if (tab->chgAccessMethod)
50585052
ereport(ERROR,
50595053
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
50605054
errmsg("cannot have multiple SET ACCESS METHOD subcommands")));
@@ -5408,7 +5402,14 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
54085402
/* nothing to do here, oid columns don't exist anymore */
54095403
break;
54105404
case AT_SetAccessMethod: /* SET ACCESS METHOD */
5411-
/* handled specially in Phase 3 */
5405+
5406+
/*
5407+
* Only do this for partitioned tables, for which this is just a
5408+
* catalog change. Tables with storage are handled by Phase 3.
5409+
*/
5410+
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
5411+
tab->chgAccessMethod)
5412+
ATExecSetAccessMethodNoStorage(rel, tab->newAccessMethod);
54125413
break;
54135414
case AT_SetTableSpace: /* SET TABLESPACE */
54145415

@@ -5814,7 +5815,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
58145815
* Select destination access method (same as original unless user
58155816
* requested a change)
58165817
*/
5817-
if (OidIsValid(tab->newAccessMethod))
5818+
if (tab->chgAccessMethod)
58185819
NewAccessMethod = tab->newAccessMethod;
58195820
else
58205821
NewAccessMethod = OldHeap->rd_rel->relam;
@@ -6402,6 +6403,7 @@ ATGetQueueEntry(List **wqueue, Relation rel)
64026403
tab->relkind = rel->rd_rel->relkind;
64036404
tab->oldDesc = CreateTupleDescCopyConstr(RelationGetDescr(rel));
64046405
tab->newAccessMethod = InvalidOid;
6406+
tab->chgAccessMethod = false;
64056407
tab->newTableSpace = InvalidOid;
64066408
tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
64076409
tab->chgPersistence = false;
@@ -15343,25 +15345,128 @@ ATExecDropCluster(Relation rel, LOCKMODE lockmode)
1534315345
/*
1534415346
* Preparation phase for SET ACCESS METHOD
1534515347
*
15346-
* Check that access method exists. If it is the same as the table's current
15347-
* access method, it is a no-op. Otherwise, a table rewrite is necessary.
15348-
* If amname is NULL, select default_table_access_method as access method.
15348+
* Check that the access method exists and determine whether a change is
15349+
* actually needed.
1534915350
*/
1535015351
static void
1535115352
ATPrepSetAccessMethod(AlteredTableInfo *tab, Relation rel, const char *amname)
1535215353
{
1535315354
Oid amoid;
1535415355

15355-
/* Check that the table access method exists */
15356-
amoid = get_table_am_oid(amname ? amname : default_table_access_method,
15357-
false);
15356+
/*
15357+
* Look up the access method name and check that it differs from the
15358+
* table's current AM. If DEFAULT was specified for a partitioned table
15359+
* (amname is NULL), set it to InvalidOid to reset the catalogued AM.
15360+
*/
15361+
if (amname != NULL)
15362+
amoid = get_table_am_oid(amname, false);
15363+
else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
15364+
amoid = InvalidOid;
15365+
else
15366+
amoid = get_table_am_oid(default_table_access_method, false);
1535815367

15368+
/* if it's a match, phase 3 doesn't need to do anything */
1535915369
if (rel->rd_rel->relam == amoid)
1536015370
return;
1536115371

1536215372
/* Save info for Phase 3 to do the real work */
1536315373
tab->rewrite |= AT_REWRITE_ACCESS_METHOD;
1536415374
tab->newAccessMethod = amoid;
15375+
tab->chgAccessMethod = true;
15376+
}
15377+
15378+
/*
15379+
* Special handling of ALTER TABLE SET ACCESS METHOD for relations with no
15380+
* storage that have an interest in preserving AM.
15381+
*
15382+
* Since these have no storage, setting the access method is a catalog only
15383+
* operation.
15384+
*/
15385+
static void
15386+
ATExecSetAccessMethodNoStorage(Relation rel, Oid newAccessMethodId)
15387+
{
15388+
Relation pg_class;
15389+
Oid oldAccessMethodId;
15390+
HeapTuple tuple;
15391+
Form_pg_class rd_rel;
15392+
Oid reloid = RelationGetRelid(rel);
15393+
15394+
/*
15395+
* Shouldn't be called on relations having storage; these are processed in
15396+
* phase 3.
15397+
*/
15398+
Assert(!RELKIND_HAS_STORAGE(rel->rd_rel->relkind));
15399+
15400+
/* Get a modifiable copy of the relation's pg_class row. */
15401+
pg_class = table_open(RelationRelationId, RowExclusiveLock);
15402+
15403+
tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid));
15404+
if (!HeapTupleIsValid(tuple))
15405+
elog(ERROR, "cache lookup failed for relation %u", reloid);
15406+
rd_rel = (Form_pg_class) GETSTRUCT(tuple);
15407+
15408+
/* Update the pg_class row. */
15409+
oldAccessMethodId = rd_rel->relam;
15410+
rd_rel->relam = newAccessMethodId;
15411+
15412+
/* Leave if no update required */
15413+
if (rd_rel->relam == oldAccessMethodId)
15414+
{
15415+
heap_freetuple(tuple);
15416+
table_close(pg_class, RowExclusiveLock);
15417+
return;
15418+
}
15419+
15420+
CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
15421+
15422+
/*
15423+
* Update the dependency on the new access method. No dependency is added
15424+
* if the new access method is InvalidOid (default case). Be very careful
15425+
* that this has to compare the previous value stored in pg_class with the
15426+
* new one.
15427+
*/
15428+
if (!OidIsValid(oldAccessMethodId) && OidIsValid(rd_rel->relam))
15429+
{
15430+
ObjectAddress relobj,
15431+
referenced;
15432+
15433+
/*
15434+
* New access method is defined and there was no dependency
15435+
* previously, so record a new one.
15436+
*/
15437+
ObjectAddressSet(relobj, RelationRelationId, reloid);
15438+
ObjectAddressSet(referenced, AccessMethodRelationId, rd_rel->relam);
15439+
recordDependencyOn(&relobj, &referenced, DEPENDENCY_NORMAL);
15440+
}
15441+
else if (OidIsValid(oldAccessMethodId) &&
15442+
!OidIsValid(rd_rel->relam))
15443+
{
15444+
/*
15445+
* There was an access method defined, and no new one, so just remove
15446+
* the existing dependency.
15447+
*/
15448+
deleteDependencyRecordsForClass(RelationRelationId, reloid,
15449+
AccessMethodRelationId,
15450+
DEPENDENCY_NORMAL);
15451+
}
15452+
else
15453+
{
15454+
Assert(OidIsValid(oldAccessMethodId) &&
15455+
OidIsValid(rd_rel->relam));
15456+
15457+
/* Both are valid, so update the dependency */
15458+
changeDependencyFor(RelationRelationId, reloid,
15459+
AccessMethodRelationId,
15460+
oldAccessMethodId, rd_rel->relam);
15461+
}
15462+
15463+
/* make the relam and dependency changes visible */
15464+
CommandCounterIncrement();
15465+
15466+
InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);
15467+
15468+
heap_freetuple(tuple);
15469+
table_close(pg_class, RowExclusiveLock);
1536515470
}
1536615471

1536715472
/*

src/backend/utils/cache/lsyscache.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2069,6 +2069,28 @@ get_rel_persistence(Oid relid)
20692069
return result;
20702070
}
20712071

2072+
/*
2073+
* get_rel_relam
2074+
*
2075+
* Returns the relam associated with a given relation.
2076+
*/
2077+
Oid
2078+
get_rel_relam(Oid relid)
2079+
{
2080+
HeapTuple tp;
2081+
Form_pg_class reltup;
2082+
Oid result;
2083+
2084+
tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
2085+
if (!HeapTupleIsValid(tp))
2086+
elog(ERROR, "cache lookup failed for relation %u", relid);
2087+
reltup = (Form_pg_class) GETSTRUCT(tp);
2088+
result = reltup->relam;
2089+
ReleaseSysCache(tp);
2090+
2091+
return result;
2092+
}
2093+
20722094

20732095
/* ---------- TRANSFORM CACHE ---------- */
20742096

src/backend/utils/cache/relcache.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,6 +1208,13 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
12081208
else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) ||
12091209
relation->rd_rel->relkind == RELKIND_SEQUENCE)
12101210
RelationInitTableAccessMethod(relation);
1211+
else if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
1212+
{
1213+
/*
1214+
* Do nothing: access methods are a setting that partitions can
1215+
* inherit.
1216+
*/
1217+
}
12111218
else
12121219
Assert(relation->rd_rel->relam == InvalidOid);
12131220

src/bin/pg_dump/pg_dump.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16656,7 +16656,8 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
1665616656
if (RELKIND_HAS_TABLESPACE(tbinfo->relkind))
1665716657
tablespace = tbinfo->reltablespace;
1665816658

16659-
if (RELKIND_HAS_TABLE_AM(tbinfo->relkind))
16659+
if (RELKIND_HAS_TABLE_AM(tbinfo->relkind) ||
16660+
tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
1666016661
tableam = tbinfo->amname;
1666116662

1666216663
ArchiveEntry(fout, tbinfo->dobj.catId, tbinfo->dobj.dumpId,

src/bin/pg_dump/t/002_pg_dump.pl

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4587,6 +4587,41 @@
45874587
no_table_access_method => 1,
45884588
only_dump_measurement => 1,
45894589
},
4590+
},
4591+
4592+
# CREATE TABLE with partitioned table and various AMs. One
4593+
# partition uses the same default as the parent, and a second
4594+
# uses its own AM.
4595+
'CREATE TABLE regress_pg_dump_table_part' => {
4596+
create_order => 19,
4597+
create_sql => '
4598+
CREATE TABLE dump_test.regress_pg_dump_table_am_parent (id int) PARTITION BY LIST (id);
4599+
ALTER TABLE dump_test.regress_pg_dump_table_am_parent SET ACCESS METHOD regress_table_am;
4600+
CREATE TABLE dump_test.regress_pg_dump_table_am_child_1
4601+
PARTITION OF dump_test.regress_pg_dump_table_am_parent FOR VALUES IN (1) USING heap;
4602+
CREATE TABLE dump_test.regress_pg_dump_table_am_child_2
4603+
PARTITION OF dump_test.regress_pg_dump_table_am_parent FOR VALUES IN (2);',
4604+
regexp => qr/^
4605+
\QSET default_table_access_method = regress_table_am;\E
4606+
(\n(?!SET[^;]+;)[^\n]*)*
4607+
\n\QCREATE TABLE dump_test.regress_pg_dump_table_am_parent (\E
4608+
(.*\n)*
4609+
\QSET default_table_access_method = heap;\E
4610+
(\n(?!SET[^;]+;)[^\n]*)*
4611+
\n\QCREATE TABLE dump_test.regress_pg_dump_table_am_child_1 (\E
4612+
(.*\n)*
4613+
\QSET default_table_access_method = regress_table_am;\E
4614+
(\n(?!SET[^;]+;)[^\n]*)*
4615+
\n\QCREATE TABLE dump_test.regress_pg_dump_table_am_child_2 (\E
4616+
(.*\n)*/xm,
4617+
like => {
4618+
%full_runs, %dump_test_schema_runs, section_pre_data => 1,
4619+
},
4620+
unlike => {
4621+
exclude_dump_test_schema => 1,
4622+
no_table_access_method => 1,
4623+
only_dump_measurement => 1,
4624+
},
45904625
});
45914626
45924627
#########################################

0 commit comments

Comments
 (0)