aboutsummaryrefslogtreecommitdiff
path: root/src/backend/commands/tablecmds.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/commands/tablecmds.c')
-rw-r--r--src/backend/commands/tablecmds.c165
1 files changed, 135 insertions, 30 deletions
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 71740984f33..259b4237a24 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -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);
}
/*