Skip to content

Commit

Permalink
Allow specifying an access method for partitioned tables
Browse files Browse the repository at this point in the history
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 <[email protected]>
Author: Soumyadeep Chakraborty <[email protected]>
Author: Michaël Paquier <[email protected]>
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
  • Loading branch information
alvherre committed Mar 25, 2024
1 parent b8528fe commit 374c7a2
Show file tree
Hide file tree
Showing 12 changed files with 469 additions and 49 deletions.
10 changes: 7 additions & 3 deletions doc/src/sgml/catalogs.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -1988,9 +1988,13 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
(references <link linkend="catalog-pg-am"><structname>pg_am</structname></link>.<structfield>oid</structfield>)
</para>
<para>
If this is a table or an index, the access method used (heap,
B-tree, hash, etc.); otherwise zero (zero occurs for sequences,
as well as relations without storage, such as views)
The access method used to access this table or index.
Not meaningful if the relation is a sequence or
has no on-disk file,
except for partitioned tables, where, if set, it takes
precedence over <varname>default_table_access_method</varname>
when determining the access method to use for partitions created
when one is not specified in the creation command.
</para></entry>
</row>

Expand Down
18 changes: 14 additions & 4 deletions doc/src/sgml/ref/alter_table.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -732,10 +732,20 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
<term><literal>SET ACCESS METHOD</literal></term>
<listitem>
<para>
This form changes the access method of the table by rewriting it. See
<xref linkend="tableam"/> for more information. Writing
<literal>DEFAULT</literal> changes the access method of the table
to <xref linkend="guc-default-table-access-method"/>.
This form changes the access method of the table by rewriting it
using the indicated access method; specifying
<literal>DEFAULT</literal> selects the access method set as the
<xref linkend="guc-default-table-access-method"/> configuration
parameter.
See <xref linkend="tableam"/> for more information.
</para>
<para>
When applied to a partitioned table, there is no data to rewrite,
but partitions created afterwards will default to the given access
method unless overridden by a <literal>USING</literal> clause.
Specifying <varname>DEFAULT</varname> removes a previous value,
causing future partitions to default to
<varname>default_table_access_method</varname>.
</para>
</listitem>
</varlistentry>
Expand Down
4 changes: 4 additions & 0 deletions doc/src/sgml/ref/create_table.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -1365,6 +1365,10 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
method is chosen for the new table. See <xref
linkend="guc-default-table-access-method"/> for more information.
</para>
<para>
When creating a partition, the table access method is the access method
of its partitioned table, if set.
</para>
</listitem>
</varlistentry>

Expand Down
165 changes: 135 additions & 30 deletions src/backend/commands/tablecmds.c
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,9 @@ 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 */
bool chgAccessMethod; /* T if SET ACCESS METHOD is used */
Oid newAccessMethod; /* new access method; 0 means no change,
* if above is true */
Oid newTableSpace; /* new tablespace; 0 means no change */
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
Expand Down Expand Up @@ -595,6 +597,7 @@ 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 void ATExecSetAccessMethodNoStorage(Relation rel, Oid newAccessMethod);
static bool ATPrepChangePersistence(Relation rel, bool toLogged);
static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
const char *tablespacename, LOCKMODE lockmode);
Expand Down Expand Up @@ -709,7 +712,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
Oid ofTypeId;
ObjectAddress address;
LOCKMODE parentLockmode;
const char *accessMethod = NULL;
Oid accessMethodId = InvalidOid;

/*
Expand Down Expand Up @@ -954,24 +956,22 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
}

/*
* If the statement hasn't specified an access method, but we're defining
* a type of relation that needs one, use the default.
* Select access method to use: an explicitly indicated one, or (in the
* case of a partitioned table) the parent's, if it has one.
*/
if (stmt->accessMethod != NULL)
accessMethodId = get_table_am_oid(stmt->accessMethod, false);
else if (stmt->partbound)
{
accessMethod = stmt->accessMethod;

if (partitioned)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("specifying a table access method is not supported on a partitioned table")));
Assert(list_length(inheritOids) == 1);
accessMethodId = get_rel_relam(linitial_oid(inheritOids));
}
else if (RELKIND_HAS_TABLE_AM(relkind))
accessMethod = default_table_access_method;
else
accessMethodId = InvalidOid;

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

/*
* Create the relation. Inherited defaults and constraints are passed in
Expand Down Expand Up @@ -5047,14 +5047,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
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))
if (tab->chgAccessMethod)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot have multiple SET ACCESS METHOD subcommands")));
Expand Down Expand Up @@ -5408,7 +5402,14 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
/* nothing to do here, oid columns don't exist anymore */
break;
case AT_SetAccessMethod: /* SET ACCESS METHOD */
/* handled specially in Phase 3 */

/*
* Only do this for partitioned tables, for which this is just a
* catalog change. Tables with storage are handled by Phase 3.
*/
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
tab->chgAccessMethod)
ATExecSetAccessMethodNoStorage(rel, tab->newAccessMethod);
break;
case AT_SetTableSpace: /* SET TABLESPACE */

Expand Down Expand Up @@ -5814,7 +5815,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
* Select destination access method (same as original unless user
* requested a change)
*/
if (OidIsValid(tab->newAccessMethod))
if (tab->chgAccessMethod)
NewAccessMethod = tab->newAccessMethod;
else
NewAccessMethod = OldHeap->rd_rel->relam;
Expand Down Expand Up @@ -6402,6 +6403,7 @@ ATGetQueueEntry(List **wqueue, Relation rel)
tab->relkind = rel->rd_rel->relkind;
tab->oldDesc = CreateTupleDescCopyConstr(RelationGetDescr(rel));
tab->newAccessMethod = InvalidOid;
tab->chgAccessMethod = false;
tab->newTableSpace = InvalidOid;
tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
tab->chgPersistence = false;
Expand Down Expand Up @@ -15343,25 +15345,128 @@ ATExecDropCluster(Relation rel, LOCKMODE lockmode)
/*
* 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.
* If amname is NULL, select default_table_access_method as access method.
* Check that the access method exists and determine whether a change is
* actually needed.
*/
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 ? amname : default_table_access_method,
false);
/*
* Look up the access method name and check that it differs from the
* table's current AM. If DEFAULT was specified for a partitioned table
* (amname is NULL), set it to InvalidOid to reset the catalogued AM.
*/
if (amname != NULL)
amoid = get_table_am_oid(amname, false);
else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
amoid = InvalidOid;
else
amoid = get_table_am_oid(default_table_access_method, false);

/* if it's a match, phase 3 doesn't need to do anything */
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;
tab->chgAccessMethod = true;
}

/*
* Special handling of ALTER TABLE SET ACCESS METHOD for relations with no
* storage that have an interest in preserving AM.
*
* Since these have no storage, setting the access method is a catalog only
* operation.
*/
static void
ATExecSetAccessMethodNoStorage(Relation rel, Oid newAccessMethodId)
{
Relation pg_class;
Oid oldAccessMethodId;
HeapTuple tuple;
Form_pg_class rd_rel;
Oid reloid = RelationGetRelid(rel);

/*
* Shouldn't be called on relations having storage; these are processed in
* phase 3.
*/
Assert(!RELKIND_HAS_STORAGE(rel->rd_rel->relkind));

/* Get a modifiable copy of the relation's pg_class row. */
pg_class = table_open(RelationRelationId, RowExclusiveLock);

tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u", reloid);
rd_rel = (Form_pg_class) GETSTRUCT(tuple);

/* Update the pg_class row. */
oldAccessMethodId = rd_rel->relam;
rd_rel->relam = newAccessMethodId;

/* Leave if no update required */
if (rd_rel->relam == oldAccessMethodId)
{
heap_freetuple(tuple);
table_close(pg_class, RowExclusiveLock);
return;
}

CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);

/*
* Update the dependency on the new access method. No dependency is added
* if the new access method is InvalidOid (default case). Be very careful
* that this has to compare the previous value stored in pg_class with the
* new one.
*/
if (!OidIsValid(oldAccessMethodId) && OidIsValid(rd_rel->relam))
{
ObjectAddress relobj,
referenced;

/*
* New access method is defined and there was no dependency
* previously, so record a new one.
*/
ObjectAddressSet(relobj, RelationRelationId, reloid);
ObjectAddressSet(referenced, AccessMethodRelationId, rd_rel->relam);
recordDependencyOn(&relobj, &referenced, DEPENDENCY_NORMAL);
}
else if (OidIsValid(oldAccessMethodId) &&
!OidIsValid(rd_rel->relam))
{
/*
* There was an access method defined, and no new one, so just remove
* the existing dependency.
*/
deleteDependencyRecordsForClass(RelationRelationId, reloid,
AccessMethodRelationId,
DEPENDENCY_NORMAL);
}
else
{
Assert(OidIsValid(oldAccessMethodId) &&
OidIsValid(rd_rel->relam));

/* Both are valid, so update the dependency */
changeDependencyFor(RelationRelationId, reloid,
AccessMethodRelationId,
oldAccessMethodId, rd_rel->relam);
}

/* make the relam and dependency changes visible */
CommandCounterIncrement();

InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);

heap_freetuple(tuple);
table_close(pg_class, RowExclusiveLock);
}

/*
Expand Down
22 changes: 22 additions & 0 deletions src/backend/utils/cache/lsyscache.c
Original file line number Diff line number Diff line change
Expand Up @@ -2069,6 +2069,28 @@ get_rel_persistence(Oid relid)
return result;
}

/*
* get_rel_relam
*
* Returns the relam associated with a given relation.
*/
Oid
get_rel_relam(Oid relid)
{
HeapTuple tp;
Form_pg_class reltup;
Oid result;

tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for relation %u", relid);
reltup = (Form_pg_class) GETSTRUCT(tp);
result = reltup->relam;
ReleaseSysCache(tp);

return result;
}


/* ---------- TRANSFORM CACHE ---------- */

Expand Down
7 changes: 7 additions & 0 deletions src/backend/utils/cache/relcache.c
Original file line number Diff line number Diff line change
Expand Up @@ -1208,6 +1208,13 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) ||
relation->rd_rel->relkind == RELKIND_SEQUENCE)
RelationInitTableAccessMethod(relation);
else if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* Do nothing: access methods are a setting that partitions can
* inherit.
*/
}
else
Assert(relation->rd_rel->relam == InvalidOid);

Expand Down
3 changes: 2 additions & 1 deletion src/bin/pg_dump/pg_dump.c
Original file line number Diff line number Diff line change
Expand Up @@ -16656,7 +16656,8 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
if (RELKIND_HAS_TABLESPACE(tbinfo->relkind))
tablespace = tbinfo->reltablespace;

if (RELKIND_HAS_TABLE_AM(tbinfo->relkind))
if (RELKIND_HAS_TABLE_AM(tbinfo->relkind) ||
tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
tableam = tbinfo->amname;

ArchiveEntry(fout, tbinfo->dobj.catId, tbinfo->dobj.dumpId,
Expand Down
35 changes: 35 additions & 0 deletions src/bin/pg_dump/t/002_pg_dump.pl
Original file line number Diff line number Diff line change
Expand Up @@ -4587,6 +4587,41 @@
no_table_access_method => 1,
only_dump_measurement => 1,
},
},
# CREATE TABLE with partitioned table and various AMs. One
# partition uses the same default as the parent, and a second
# uses its own AM.
'CREATE TABLE regress_pg_dump_table_part' => {
create_order => 19,
create_sql => '
CREATE TABLE dump_test.regress_pg_dump_table_am_parent (id int) PARTITION BY LIST (id);
ALTER TABLE dump_test.regress_pg_dump_table_am_parent SET ACCESS METHOD regress_table_am;
CREATE TABLE dump_test.regress_pg_dump_table_am_child_1
PARTITION OF dump_test.regress_pg_dump_table_am_parent FOR VALUES IN (1) USING heap;
CREATE TABLE dump_test.regress_pg_dump_table_am_child_2
PARTITION OF dump_test.regress_pg_dump_table_am_parent FOR VALUES IN (2);',
regexp => qr/^
\QSET default_table_access_method = regress_table_am;\E
(\n(?!SET[^;]+;)[^\n]*)*
\n\QCREATE TABLE dump_test.regress_pg_dump_table_am_parent (\E
(.*\n)*
\QSET default_table_access_method = heap;\E
(\n(?!SET[^;]+;)[^\n]*)*
\n\QCREATE TABLE dump_test.regress_pg_dump_table_am_child_1 (\E
(.*\n)*
\QSET default_table_access_method = regress_table_am;\E
(\n(?!SET[^;]+;)[^\n]*)*
\n\QCREATE TABLE dump_test.regress_pg_dump_table_am_child_2 (\E
(.*\n)*/xm,
like => {
%full_runs, %dump_test_schema_runs, section_pre_data => 1,
},
unlike => {
exclude_dump_test_schema => 1,
no_table_access_method => 1,
only_dump_measurement => 1,
},
});
#########################################
Expand Down
Loading

0 comments on commit 374c7a2

Please sign in to comment.