@@ -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);
596598static void ATExecDropCluster(Relation rel, LOCKMODE lockmode);
597599static void ATPrepSetAccessMethod(AlteredTableInfo *tab, Relation rel, const char *amname);
600+ static void ATExecSetAccessMethodNoStorage(Relation rel, Oid newAccessMethod);
598601static bool ATPrepChangePersistence(Relation rel, bool toLogged);
599602static 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 */
1535015351static void
1535115352ATPrepSetAccessMethod(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/*
0 commit comments