mirror of
https://github.com/postgres/postgres.git
synced 2025-06-16 06:01:02 +03:00
Fix ALTER EXTENSION / SET SCHEMA
In its original conception, it was leaving some objects into the old schema, but without their proper pg_depend entries; this meant that the old schema could be dropped, causing future pg_dump calls to fail on the affected database. This was originally reported by Jeff Frost as #6704; there have been other complaints elsewhere that can probably be traced to this bug. To fix, be more consistent about altering a table's subsidiary objects along the table itself; this requires some restructuring in how tables are relocated when altering an extension -- hence the new AlterTableNamespaceInternal routine which encapsulates it for both the ALTER TABLE and the ALTER EXTENSION cases. There was another bug lurking here, which was unmasked after fixing the previous one: certain objects would be reached twice via the dependency graph, and the second attempt to move them would cause the entire operation to fail. Per discussion, it seems the best fix for this is to do more careful tracking of objects already moved: we now maintain a list of moved objects, to avoid attempting to do it twice for the same object. Authors: Alvaro Herrera, Dimitri Fontaine Reviewed by Tom Lane
This commit is contained in:
@ -261,10 +261,10 @@ static void StoreCatalogInheritance1(Oid relationId, Oid parentOid,
|
||||
int16 seqNumber, Relation inhRelation);
|
||||
static int findAttrByName(const char *attributeName, List *schema);
|
||||
static void AlterIndexNamespaces(Relation classRel, Relation rel,
|
||||
Oid oldNspOid, Oid newNspOid);
|
||||
Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved);
|
||||
static void AlterSeqNamespaces(Relation classRel, Relation rel,
|
||||
Oid oldNspOid, Oid newNspOid,
|
||||
const char *newNspName, LOCKMODE lockmode);
|
||||
Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved,
|
||||
LOCKMODE lockmode);
|
||||
static void ATExecValidateConstraint(Relation rel, char *constrName,
|
||||
bool recurse, bool recursing, LOCKMODE lockmode);
|
||||
static int transformColumnNameList(Oid relId, List *colList,
|
||||
@ -9711,8 +9711,8 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt)
|
||||
Oid relid;
|
||||
Oid oldNspOid;
|
||||
Oid nspOid;
|
||||
Relation classRel;
|
||||
RangeVar *newrv;
|
||||
ObjectAddresses *objsMoved;
|
||||
|
||||
relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
|
||||
stmt->missing_ok, false,
|
||||
@ -9753,27 +9753,47 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt)
|
||||
/* common checks on switching namespaces */
|
||||
CheckSetNamespace(oldNspOid, nspOid, RelationRelationId, relid);
|
||||
|
||||
objsMoved = new_object_addresses();
|
||||
AlterTableNamespaceInternal(rel, oldNspOid, nspOid, objsMoved);
|
||||
free_object_addresses(objsMoved);
|
||||
|
||||
/* close rel, but keep lock until commit */
|
||||
relation_close(rel, NoLock);
|
||||
}
|
||||
|
||||
/*
|
||||
* The guts of relocating a table to another namespace: besides moving
|
||||
* the table itself, its dependent objects are relocated to the new schema.
|
||||
*/
|
||||
void
|
||||
AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
|
||||
ObjectAddresses *objsMoved)
|
||||
{
|
||||
Relation classRel;
|
||||
|
||||
Assert(objsMoved != NULL);
|
||||
|
||||
/* OK, modify the pg_class row and pg_depend entry */
|
||||
classRel = heap_open(RelationRelationId, RowExclusiveLock);
|
||||
|
||||
AlterRelationNamespaceInternal(classRel, relid, oldNspOid, nspOid, true);
|
||||
AlterRelationNamespaceInternal(classRel, RelationGetRelid(rel), oldNspOid,
|
||||
nspOid, true, objsMoved);
|
||||
|
||||
/* Fix the table's row type too */
|
||||
AlterTypeNamespaceInternal(rel->rd_rel->reltype, nspOid, false, false);
|
||||
AlterTypeNamespaceInternal(rel->rd_rel->reltype,
|
||||
nspOid, false, false, objsMoved);
|
||||
|
||||
/* Fix other dependent stuff */
|
||||
if (rel->rd_rel->relkind == RELKIND_RELATION)
|
||||
{
|
||||
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid);
|
||||
AlterSeqNamespaces(classRel, rel, oldNspOid, nspOid, stmt->newschema,
|
||||
AccessExclusiveLock);
|
||||
AlterConstraintNamespaces(relid, oldNspOid, nspOid, false);
|
||||
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
|
||||
AlterSeqNamespaces(classRel, rel, oldNspOid, nspOid,
|
||||
objsMoved, AccessExclusiveLock);
|
||||
AlterConstraintNamespaces(RelationGetRelid(rel), oldNspOid, nspOid,
|
||||
false, objsMoved);
|
||||
}
|
||||
|
||||
heap_close(classRel, RowExclusiveLock);
|
||||
|
||||
/* close rel, but keep lock until commit */
|
||||
relation_close(rel, NoLock);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -9784,10 +9804,11 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt)
|
||||
void
|
||||
AlterRelationNamespaceInternal(Relation classRel, Oid relOid,
|
||||
Oid oldNspOid, Oid newNspOid,
|
||||
bool hasDependEntry)
|
||||
bool hasDependEntry, ObjectAddresses *objsMoved)
|
||||
{
|
||||
HeapTuple classTup;
|
||||
Form_pg_class classForm;
|
||||
ObjectAddress thisobj;
|
||||
|
||||
classTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relOid));
|
||||
if (!HeapTupleIsValid(classTup))
|
||||
@ -9796,27 +9817,39 @@ AlterRelationNamespaceInternal(Relation classRel, Oid relOid,
|
||||
|
||||
Assert(classForm->relnamespace == oldNspOid);
|
||||
|
||||
/* check for duplicate name (more friendly than unique-index failure) */
|
||||
if (get_relname_relid(NameStr(classForm->relname),
|
||||
newNspOid) != InvalidOid)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DUPLICATE_TABLE),
|
||||
errmsg("relation \"%s\" already exists in schema \"%s\"",
|
||||
NameStr(classForm->relname),
|
||||
get_namespace_name(newNspOid))));
|
||||
thisobj.classId = RelationRelationId;
|
||||
thisobj.objectId = relOid;
|
||||
thisobj.objectSubId = 0;
|
||||
|
||||
/* classTup is a copy, so OK to scribble on */
|
||||
classForm->relnamespace = newNspOid;
|
||||
/*
|
||||
* Do nothing when there's nothing to do.
|
||||
*/
|
||||
if (!object_address_present(&thisobj, objsMoved))
|
||||
{
|
||||
/* check for duplicate name (more friendly than unique-index failure) */
|
||||
if (get_relname_relid(NameStr(classForm->relname),
|
||||
newNspOid) != InvalidOid)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DUPLICATE_TABLE),
|
||||
errmsg("relation \"%s\" already exists in schema \"%s\"",
|
||||
NameStr(classForm->relname),
|
||||
get_namespace_name(newNspOid))));
|
||||
|
||||
simple_heap_update(classRel, &classTup->t_self, classTup);
|
||||
CatalogUpdateIndexes(classRel, classTup);
|
||||
/* classTup is a copy, so OK to scribble on */
|
||||
classForm->relnamespace = newNspOid;
|
||||
|
||||
/* Update dependency on schema if caller said so */
|
||||
if (hasDependEntry &&
|
||||
changeDependencyFor(RelationRelationId, relOid,
|
||||
NamespaceRelationId, oldNspOid, newNspOid) != 1)
|
||||
elog(ERROR, "failed to change schema dependency for relation \"%s\"",
|
||||
NameStr(classForm->relname));
|
||||
simple_heap_update(classRel, &classTup->t_self, classTup);
|
||||
CatalogUpdateIndexes(classRel, classTup);
|
||||
|
||||
/* Update dependency on schema if caller said so */
|
||||
if (hasDependEntry &&
|
||||
changeDependencyFor(RelationRelationId, relOid,
|
||||
NamespaceRelationId, oldNspOid, newNspOid) != 1)
|
||||
elog(ERROR, "failed to change schema dependency for relation \"%s\"",
|
||||
NameStr(classForm->relname));
|
||||
|
||||
add_exact_object_address(&thisobj, objsMoved);
|
||||
}
|
||||
|
||||
heap_freetuple(classTup);
|
||||
}
|
||||
@ -9829,7 +9862,7 @@ AlterRelationNamespaceInternal(Relation classRel, Oid relOid,
|
||||
*/
|
||||
static void
|
||||
AlterIndexNamespaces(Relation classRel, Relation rel,
|
||||
Oid oldNspOid, Oid newNspOid)
|
||||
Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved)
|
||||
{
|
||||
List *indexList;
|
||||
ListCell *l;
|
||||
@ -9839,15 +9872,27 @@ AlterIndexNamespaces(Relation classRel, Relation rel,
|
||||
foreach(l, indexList)
|
||||
{
|
||||
Oid indexOid = lfirst_oid(l);
|
||||
ObjectAddress thisobj;
|
||||
|
||||
thisobj.classId = RelationRelationId;
|
||||
thisobj.objectId = indexOid;
|
||||
thisobj.objectSubId = 0;
|
||||
|
||||
/*
|
||||
* Note: currently, the index will not have its own dependency on the
|
||||
* namespace, so we don't need to do changeDependencyFor(). There's no
|
||||
* row type in pg_type, either.
|
||||
*
|
||||
* XXX this objsMoved test may be pointless -- surely we have a single
|
||||
* dependency link from a relation to each index?
|
||||
*/
|
||||
AlterRelationNamespaceInternal(classRel, indexOid,
|
||||
oldNspOid, newNspOid,
|
||||
false);
|
||||
if (!object_address_present(&thisobj, objsMoved))
|
||||
{
|
||||
AlterRelationNamespaceInternal(classRel, indexOid,
|
||||
oldNspOid, newNspOid,
|
||||
false, objsMoved);
|
||||
add_exact_object_address(&thisobj, objsMoved);
|
||||
}
|
||||
}
|
||||
|
||||
list_free(indexList);
|
||||
@ -9862,7 +9907,8 @@ AlterIndexNamespaces(Relation classRel, Relation rel,
|
||||
*/
|
||||
static void
|
||||
AlterSeqNamespaces(Relation classRel, Relation rel,
|
||||
Oid oldNspOid, Oid newNspOid, const char *newNspName, LOCKMODE lockmode)
|
||||
Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved,
|
||||
LOCKMODE lockmode)
|
||||
{
|
||||
Relation depRel;
|
||||
SysScanDesc scan;
|
||||
@ -9914,14 +9960,14 @@ AlterSeqNamespaces(Relation classRel, Relation rel,
|
||||
/* Fix the pg_class and pg_depend entries */
|
||||
AlterRelationNamespaceInternal(classRel, depForm->objid,
|
||||
oldNspOid, newNspOid,
|
||||
true);
|
||||
true, objsMoved);
|
||||
|
||||
/*
|
||||
* Sequences have entries in pg_type. We need to be careful to move
|
||||
* them to the new namespace, too.
|
||||
*/
|
||||
AlterTypeNamespaceInternal(RelationGetForm(seqRel)->reltype,
|
||||
newNspOid, false, false);
|
||||
newNspOid, false, false, objsMoved);
|
||||
|
||||
/* Now we can close it. Keep the lock till end of transaction. */
|
||||
relation_close(seqRel, NoLock);
|
||||
|
Reference in New Issue
Block a user