Allow specifying an access method for partitioned tables
authorAlvaro Herrera <[email protected]>
Mon, 25 Mar 2024 15:30:36 +0000 (16:30 +0100)
committerAlvaro Herrera <[email protected]>
Mon, 25 Mar 2024 15:30:36 +0000 (16:30 +0100)
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

12 files changed:
doc/src/sgml/catalogs.sgml
doc/src/sgml/ref/alter_table.sgml
doc/src/sgml/ref/create_table.sgml
src/backend/commands/tablecmds.c
src/backend/utils/cache/lsyscache.c
src/backend/utils/cache/relcache.c
src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/t/002_pg_dump.pl
src/include/catalog/pg_class.h
src/include/utils/lsyscache.h
src/test/regress/expected/create_am.out
src/test/regress/sql/create_am.sql

index 29817fb33c606a5f7224d77830fd61b05989cefe..096ddab481c173e5920c31e0d1dd57961bbac913 100644 (file)
@@ -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>
 
index 96e3d776051f26ee647384742c12f368ae82d60b..6cdcd779ef83a4e35a929cf95c5f70ae1ae769a7 100644 (file)
@@ -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>
index 118c7053e6058ec23f05b95d2e298d120a567422..dfb7822985e446d61a074957355c468d8993eef0 100644 (file)
@@ -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>
 
index 71740984f3375e9720e0b2a77745eaeaa62ac5fe..259b4237a2430cc167429b6b484935da3fd9aca7 100644 (file)
@@ -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 */
@@ -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);
@@ -709,7 +712,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
    Oid         ofTypeId;
    ObjectAddress address;
    LOCKMODE    parentLockmode;
-   const char *accessMethod = NULL;
    Oid         accessMethodId = InvalidOid;
 
    /*
@@ -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
@@ -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")));
@@ -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 */
 
@@ -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;
@@ -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;
@@ -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);
 }
 
 /*
index 6418d1c6ebade9db22ed92e99f3d2b5b8ad96257..26368ffcc97cc7c12a28b07023757a03a91f133a 100644 (file)
@@ -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 ----------                        */
 
index dcd18e462686c1581dcec4bb80ad9430420e502d..1f419c2a6dd055a6d61f4b3a73a47ab0fae04264 100644 (file)
@@ -1208,6 +1208,13 @@ retry:
    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);
 
index d275b316054e30526a15c64b7815d28055c6a3ec..b1c4c3ec7f0d13f0cde88700a1f807f74dbe4475 100644 (file)
@@ -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,
index c8b489d94ef919ac49733febe26b14fb5e0c08ca..f0410ce6a1360180144891eebb2dfaf8e074f731 100644 (file)
@@ -4587,6 +4587,41 @@ my %tests = (
            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,
+       },
    });
 
 #########################################
index 3b7533e7bb3ba6c1e2074dbe68eed7bbf4c1a4a3..0fc2c093b0d4e315f26fca0522e1ea837e405fe1 100644 (file)
@@ -219,7 +219,9 @@ MAKE_SYSCACHE(RELNAMENSP, pg_class_relname_nsp_index, 128);
 /*
  * Relation kinds with a table access method (rd_tableam).  Although sequences
  * use the heap table AM, they are enough of a special case in most uses that
- * they are not included here.
+ * they are not included here.  Likewise, partitioned tables can have an access
+ * method defined so that their partitions can inherit it, but they do not set
+ * rd_tableam; hence, this is handled specially outside of this macro.
  */
 #define RELKIND_HAS_TABLE_AM(relkind) \
    ((relkind) == RELKIND_RELATION || \
index e4a200b00ec9c15030cdadee53c25a74dacb79bf..35a8dec2b9f3bd61746bdd0f0b24b57ec4cf4a88 100644 (file)
@@ -139,6 +139,7 @@ extern char get_rel_relkind(Oid relid);
 extern bool get_rel_relispartition(Oid relid);
 extern Oid get_rel_tablespace(Oid relid);
 extern char get_rel_persistence(Oid relid);
+extern Oid get_rel_relam(Oid relid);
 extern Oid get_transform_fromsql(Oid typid, Oid langid, List *trftypes);
 extern Oid get_transform_tosql(Oid typid, Oid langid, List *trftypes);
 extern bool get_typisdefined(Oid typid);
index 8d73e213563929d2fd9b12c5f773ffcdfef5e760..a27805a8f5eb41d061d38006c5ce5d6eb0cccf4d 100644 (file)
@@ -176,9 +176,16 @@ SELECT f1 FROM tableam_tblmv_heap2 ORDER BY f1;
   1
 (1 row)
 
--- CREATE TABLE ..  PARTITION BY doesn't not support USING
+-- CREATE TABLE ..  PARTITION BY supports USING.
 CREATE TABLE tableam_parted_heap2 (a text, b int) PARTITION BY list (a) USING heap2;
-ERROR:  specifying a table access method is not supported on a partitioned table
+SELECT a.amname FROM pg_class c, pg_am a
+  WHERE c.relname = 'tableam_parted_heap2' AND a.oid = c.relam;
+ amname 
+--------
+ heap2
+(1 row)
+
+DROP TABLE tableam_parted_heap2;
 CREATE TABLE tableam_parted_heap2 (a text, b int) PARTITION BY list (a);
 -- new partitions will inherit from the current default, rather the partition root
 SET default_table_access_method = 'heap';
@@ -336,12 +343,151 @@ ALTER MATERIALIZED VIEW heapmv SET ACCESS METHOD heap, SET ACCESS METHOD heap2;
 ERROR:  cannot have multiple SET ACCESS METHOD subcommands
 DROP MATERIALIZED VIEW heapmv;
 DROP TABLE heaptable;
--- No support for partitioned tables.
-CREATE TABLE am_partitioned(x INT, y INT)
-  PARTITION BY hash (x);
+-- Partition hierarchies with access methods
+BEGIN;
+SET LOCAL default_table_access_method = 'heap';
+CREATE TABLE am_partitioned(x INT, y INT) PARTITION BY hash (x);
+-- pg_class.relam is 0, no dependency recorded between the AM and the
+-- partitioned table.
+SELECT relam FROM pg_class WHERE relname = 'am_partitioned';
+ relam 
+-------
+     0
+(1 row)
+
+SELECT pg_describe_object(classid, objid, objsubid) AS obj,
+       pg_describe_object(refclassid, refobjid, refobjsubid) as refobj
+  FROM pg_depend, pg_am
+  WHERE pg_depend.refclassid = 'pg_am'::regclass
+    AND pg_am.oid = pg_depend.refobjid
+    AND pg_depend.objid = 'am_partitioned'::regclass;
+ obj | refobj 
+-----+--------
+(0 rows)
+
+-- New default is set, with dependency added.
+ALTER TABLE am_partitioned SET ACCESS METHOD heap2;
+SELECT a.amname FROM pg_class c, pg_am a
+  WHERE c.relname = 'am_partitioned' AND a.oid = c.relam;
+ amname 
+--------
+ heap2
+(1 row)
+
+SELECT pg_describe_object(classid, objid, objsubid) AS obj,
+       pg_describe_object(refclassid, refobjid, refobjsubid) as refobj
+  FROM pg_depend, pg_am
+  WHERE pg_depend.refclassid = 'pg_am'::regclass
+    AND pg_am.oid = pg_depend.refobjid
+    AND pg_depend.objid = 'am_partitioned'::regclass;
+         obj          |       refobj        
+----------------------+---------------------
+ table am_partitioned | access method heap2
+(1 row)
+
+-- Default is set, with dependency updated.
+SET LOCAL default_table_access_method = 'heap2';
+ALTER TABLE am_partitioned SET ACCESS METHOD heap;
+SELECT a.amname FROM pg_class c, pg_am a
+  WHERE c.relname = 'am_partitioned' AND a.oid = c.relam;
+ amname 
+--------
+ heap
+(1 row)
+
+-- Dependency pinned, hence removed.
+SELECT pg_describe_object(classid, objid, objsubid) AS obj,
+       pg_describe_object(refclassid, refobjid, refobjsubid) as refobj
+  FROM pg_depend, pg_am
+  WHERE pg_depend.refclassid = 'pg_am'::regclass
+    AND pg_am.oid = pg_depend.refobjid
+    AND pg_depend.objid = 'am_partitioned'::regclass;
+ obj | refobj 
+-----+--------
+(0 rows)
+
+-- Default and AM set in the clause are the same, relam should be set.
+SET LOCAL default_table_access_method = 'heap2';
+ALTER TABLE am_partitioned SET ACCESS METHOD heap2;
+SELECT a.amname FROM pg_class c, pg_am a
+  WHERE c.relname = 'am_partitioned' AND a.oid = c.relam;
+ amname 
+--------
+ heap2
+(1 row)
+
+-- Reset to default
+ALTER TABLE am_partitioned SET ACCESS METHOD DEFAULT;
+SELECT relam FROM pg_class WHERE relname = 'am_partitioned';
+ relam 
+-------
+     0
+(1 row)
+
+-- Upon ALTER TABLE SET ACCESS METHOD on a partitioned table, new partitions
+-- will inherit the AM set.  Existing partitioned are unchanged.
+SELECT relam FROM pg_class WHERE relname = 'am_partitioned';
+ relam 
+-------
+     0
+(1 row)
+
+SET LOCAL default_table_access_method = 'heap';
+CREATE TABLE am_partitioned_0 PARTITION OF am_partitioned
+  FOR VALUES WITH (MODULUS 10, REMAINDER 0);
+SET LOCAL default_table_access_method = 'heap2';
+CREATE TABLE am_partitioned_1 PARTITION OF am_partitioned
+  FOR VALUES WITH (MODULUS 10, REMAINDER 1);
+SET LOCAL default_table_access_method = 'heap';
 ALTER TABLE am_partitioned SET ACCESS METHOD heap2;
-ERROR:  cannot change access method of a partitioned table
+CREATE TABLE am_partitioned_2 PARTITION OF am_partitioned
+  FOR VALUES WITH (MODULUS 10, REMAINDER 2);
+ALTER TABLE am_partitioned SET ACCESS METHOD DEFAULT;
+SELECT relam FROM pg_class WHERE relname = 'am_partitioned';
+ relam 
+-------
+     0
+(1 row)
+
+CREATE TABLE am_partitioned_3 PARTITION OF am_partitioned
+  FOR VALUES WITH (MODULUS 10, REMAINDER 3);
+-- Partitioned table with relam at 0
+ALTER TABLE am_partitioned SET ACCESS METHOD DEFAULT;
+CREATE TABLE am_partitioned_5p PARTITION OF am_partitioned
+  FOR VALUES WITH (MODULUS 10, REMAINDER 5) PARTITION BY hash(y);
+-- Partitions of this partitioned table inherit default AM at creation
+-- time.
+CREATE TABLE am_partitioned_5p1 PARTITION OF am_partitioned_5p
+  FOR VALUES WITH (MODULUS 10, REMAINDER 1);
+-- Partitioned table with relam set.
+ALTER TABLE am_partitioned SET ACCESS METHOD heap2;
+CREATE TABLE am_partitioned_6p PARTITION OF am_partitioned
+  FOR VALUES WITH (MODULUS 10, REMAINDER 6) PARTITION BY hash(y);
+-- Partitions of this partitioned table inherit its AM.
+CREATE TABLE am_partitioned_6p1 PARTITION OF am_partitioned_6p
+  FOR VALUES WITH (MODULUS 10, REMAINDER 1);
+SELECT c.relname, a.amname FROM pg_class c, pg_am a
+  WHERE c.relam = a.oid AND
+        c.relname LIKE 'am_partitioned%'
+UNION ALL
+SELECT c.relname, 'default' FROM pg_class c
+  WHERE c.relam = 0
+        AND c.relname LIKE 'am_partitioned%' ORDER BY 1;
+      relname       | amname  
+--------------------+---------
+ am_partitioned     | heap2
+ am_partitioned_0   | heap
+ am_partitioned_1   | heap2
+ am_partitioned_2   | heap2
+ am_partitioned_3   | heap
+ am_partitioned_5p  | default
+ am_partitioned_5p1 | heap
+ am_partitioned_6p  | heap2
+ am_partitioned_6p1 | heap2
+(9 rows)
+
 DROP TABLE am_partitioned;
+COMMIT;
 -- Second, create objects in the new AM by changing the default AM
 BEGIN;
 SET LOCAL default_table_access_method = 'heap2';
index 606ee4cb241fc3040b36fef86ba2d9db0fb9ca0f..adff0079f989728027610dd3a3746efffd6b86d5 100644 (file)
@@ -124,8 +124,11 @@ CREATE SEQUENCE tableam_seq_heap2 USING heap2;
 CREATE MATERIALIZED VIEW tableam_tblmv_heap2 USING heap2 AS SELECT * FROM tableam_tbl_heap2;
 SELECT f1 FROM tableam_tblmv_heap2 ORDER BY f1;
 
--- CREATE TABLE ..  PARTITION BY doesn't not support USING
+-- CREATE TABLE ..  PARTITION BY supports USING.
 CREATE TABLE tableam_parted_heap2 (a text, b int) PARTITION BY list (a) USING heap2;
+SELECT a.amname FROM pg_class c, pg_am a
+  WHERE c.relname = 'tableam_parted_heap2' AND a.oid = c.relam;
+DROP TABLE tableam_parted_heap2;
 
 CREATE TABLE tableam_parted_heap2 (a text, b int) PARTITION BY list (a);
 -- new partitions will inherit from the current default, rather the partition root
@@ -213,11 +216,91 @@ ALTER TABLE heaptable SET ACCESS METHOD DEFAULT, SET ACCESS METHOD heap2;
 ALTER MATERIALIZED VIEW heapmv SET ACCESS METHOD heap, SET ACCESS METHOD heap2;
 DROP MATERIALIZED VIEW heapmv;
 DROP TABLE heaptable;
--- No support for partitioned tables.
-CREATE TABLE am_partitioned(x INT, y INT)
-  PARTITION BY hash (x);
+
+-- Partition hierarchies with access methods
+BEGIN;
+SET LOCAL default_table_access_method = 'heap';
+CREATE TABLE am_partitioned(x INT, y INT) PARTITION BY hash (x);
+-- pg_class.relam is 0, no dependency recorded between the AM and the
+-- partitioned table.
+SELECT relam FROM pg_class WHERE relname = 'am_partitioned';
+SELECT pg_describe_object(classid, objid, objsubid) AS obj,
+       pg_describe_object(refclassid, refobjid, refobjsubid) as refobj
+  FROM pg_depend, pg_am
+  WHERE pg_depend.refclassid = 'pg_am'::regclass
+    AND pg_am.oid = pg_depend.refobjid
+    AND pg_depend.objid = 'am_partitioned'::regclass;
+-- New default is set, with dependency added.
+ALTER TABLE am_partitioned SET ACCESS METHOD heap2;
+SELECT a.amname FROM pg_class c, pg_am a
+  WHERE c.relname = 'am_partitioned' AND a.oid = c.relam;
+SELECT pg_describe_object(classid, objid, objsubid) AS obj,
+       pg_describe_object(refclassid, refobjid, refobjsubid) as refobj
+  FROM pg_depend, pg_am
+  WHERE pg_depend.refclassid = 'pg_am'::regclass
+    AND pg_am.oid = pg_depend.refobjid
+    AND pg_depend.objid = 'am_partitioned'::regclass;
+-- Default is set, with dependency updated.
+SET LOCAL default_table_access_method = 'heap2';
+ALTER TABLE am_partitioned SET ACCESS METHOD heap;
+SELECT a.amname FROM pg_class c, pg_am a
+  WHERE c.relname = 'am_partitioned' AND a.oid = c.relam;
+-- Dependency pinned, hence removed.
+SELECT pg_describe_object(classid, objid, objsubid) AS obj,
+       pg_describe_object(refclassid, refobjid, refobjsubid) as refobj
+  FROM pg_depend, pg_am
+  WHERE pg_depend.refclassid = 'pg_am'::regclass
+    AND pg_am.oid = pg_depend.refobjid
+    AND pg_depend.objid = 'am_partitioned'::regclass;
+-- Default and AM set in the clause are the same, relam should be set.
+SET LOCAL default_table_access_method = 'heap2';
+ALTER TABLE am_partitioned SET ACCESS METHOD heap2;
+SELECT a.amname FROM pg_class c, pg_am a
+  WHERE c.relname = 'am_partitioned' AND a.oid = c.relam;
+-- Reset to default
+ALTER TABLE am_partitioned SET ACCESS METHOD DEFAULT;
+SELECT relam FROM pg_class WHERE relname = 'am_partitioned';
+-- Upon ALTER TABLE SET ACCESS METHOD on a partitioned table, new partitions
+-- will inherit the AM set.  Existing partitioned are unchanged.
+SELECT relam FROM pg_class WHERE relname = 'am_partitioned';
+SET LOCAL default_table_access_method = 'heap';
+CREATE TABLE am_partitioned_0 PARTITION OF am_partitioned
+  FOR VALUES WITH (MODULUS 10, REMAINDER 0);
+SET LOCAL default_table_access_method = 'heap2';
+CREATE TABLE am_partitioned_1 PARTITION OF am_partitioned
+  FOR VALUES WITH (MODULUS 10, REMAINDER 1);
+SET LOCAL default_table_access_method = 'heap';
+ALTER TABLE am_partitioned SET ACCESS METHOD heap2;
+CREATE TABLE am_partitioned_2 PARTITION OF am_partitioned
+  FOR VALUES WITH (MODULUS 10, REMAINDER 2);
+ALTER TABLE am_partitioned SET ACCESS METHOD DEFAULT;
+SELECT relam FROM pg_class WHERE relname = 'am_partitioned';
+CREATE TABLE am_partitioned_3 PARTITION OF am_partitioned
+  FOR VALUES WITH (MODULUS 10, REMAINDER 3);
+-- Partitioned table with relam at 0
+ALTER TABLE am_partitioned SET ACCESS METHOD DEFAULT;
+CREATE TABLE am_partitioned_5p PARTITION OF am_partitioned
+  FOR VALUES WITH (MODULUS 10, REMAINDER 5) PARTITION BY hash(y);
+-- Partitions of this partitioned table inherit default AM at creation
+-- time.
+CREATE TABLE am_partitioned_5p1 PARTITION OF am_partitioned_5p
+  FOR VALUES WITH (MODULUS 10, REMAINDER 1);
+-- Partitioned table with relam set.
 ALTER TABLE am_partitioned SET ACCESS METHOD heap2;
+CREATE TABLE am_partitioned_6p PARTITION OF am_partitioned
+  FOR VALUES WITH (MODULUS 10, REMAINDER 6) PARTITION BY hash(y);
+-- Partitions of this partitioned table inherit its AM.
+CREATE TABLE am_partitioned_6p1 PARTITION OF am_partitioned_6p
+  FOR VALUES WITH (MODULUS 10, REMAINDER 1);
+SELECT c.relname, a.amname FROM pg_class c, pg_am a
+  WHERE c.relam = a.oid AND
+        c.relname LIKE 'am_partitioned%'
+UNION ALL
+SELECT c.relname, 'default' FROM pg_class c
+  WHERE c.relam = 0
+        AND c.relname LIKE 'am_partitioned%' ORDER BY 1;
 DROP TABLE am_partitioned;
+COMMIT;
 
 -- Second, create objects in the new AM by changing the default AM
 BEGIN;