summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Lane2005-08-01 04:03:59 +0000
committerTom Lane2005-08-01 04:03:59 +0000
commit5cb1c9ee60e6e84c9dec1aeeb825fffe6590f513 (patch)
treefb61e55e3cba7d46abca0a36b5bda986491771ef
parenta75175cc1991be61025a3d19f216e2e7f2ee2d83 (diff)
Add ALTER object SET SCHEMA capability for a limited but useful set of
object kinds (tables, functions, types). Documentation is not here yet. Original code by Bernd Helmle, extensive rework by Bruce Momjian and Tom Lane.
-rw-r--r--src/backend/catalog/namespace.c58
-rw-r--r--src/backend/catalog/pg_constraint.c66
-rw-r--r--src/backend/catalog/pg_depend.c99
-rw-r--r--src/backend/commands/alter.c32
-rw-r--r--src/backend/commands/functioncmds.c86
-rw-r--r--src/backend/commands/tablecmds.c294
-rw-r--r--src/backend/commands/typecmds.c169
-rw-r--r--src/backend/nodes/copyfuncs.c22
-rw-r--r--src/backend/nodes/equalfuncs.c20
-rw-r--r--src/backend/parser/gram.y65
-rw-r--r--src/backend/tcop/utility.c42
-rw-r--r--src/include/catalog/dependency.h4
-rw-r--r--src/include/catalog/namespace.h2
-rw-r--r--src/include/catalog/pg_constraint.h3
-rw-r--r--src/include/commands/alter.h2
-rw-r--r--src/include/commands/defrem.h2
-rw-r--r--src/include/commands/tablecmds.h7
-rw-r--r--src/include/commands/typecmds.h3
-rw-r--r--src/include/nodes/nodes.h1
-rw-r--r--src/include/nodes/parsenodes.h19
-rw-r--r--src/test/regress/expected/alter_table.out59
-rw-r--r--src/test/regress/sql/alter_table.sql41
22 files changed, 1075 insertions, 21 deletions
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index bd26962c0d..f972d815ad 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -1236,11 +1236,42 @@ LookupExplicitNamespace(const char *nspname)
}
/*
+ * LookupCreationNamespace
+ * Look up the schema and verify we have CREATE rights on it.
+ *
+ * This is just like LookupExplicitNamespace except for the permission check.
+ */
+Oid
+LookupCreationNamespace(const char *nspname)
+{
+ Oid namespaceId;
+ AclResult aclresult;
+
+ namespaceId = GetSysCacheOid(NAMESPACENAME,
+ CStringGetDatum(nspname),
+ 0, 0, 0);
+ if (!OidIsValid(namespaceId))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("schema \"%s\" does not exist", nspname)));
+
+ aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_CREATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
+ nspname);
+
+ return namespaceId;
+}
+
+/*
* QualifiedNameGetCreationNamespace
* Given a possibly-qualified name for an object (in List-of-Values
* format), determine what namespace the object should be created in.
* Also extract and return the object name (last component of list).
*
+ * Note: this does not apply any permissions check. Callers must check
+ * for CREATE rights on the selected namespace when appropriate.
+ *
* This is *not* used for tables. Hence, the TEMP table namespace is
* never selected as the creation target.
*/
@@ -1277,8 +1308,6 @@ QualifiedNameGetCreationNamespace(List *names, char **objname_p)
errmsg("no schema has been selected to create in")));
}
- /* Note: callers will check for CREATE rights when appropriate */
-
*objname_p = objname;
return namespaceId;
}
@@ -1379,19 +1408,16 @@ isTempNamespace(Oid namespaceId)
}
/*
- * isOtherTempNamespace - is the given namespace some other backend's
- * temporary-table namespace?
+ * isAnyTempNamespace - is the given namespace a temporary-table namespace
+ * (either my own, or another backend's)?
*/
bool
-isOtherTempNamespace(Oid namespaceId)
+isAnyTempNamespace(Oid namespaceId)
{
bool result;
char *nspname;
- /* If it's my own temp namespace, say "false" */
- if (isTempNamespace(namespaceId))
- return false;
- /* Else, if the namespace name starts with "pg_temp_", say "true" */
+ /* If the namespace name starts with "pg_temp_", say "true" */
nspname = get_namespace_name(namespaceId);
if (!nspname)
return false; /* no such namespace? */
@@ -1401,6 +1427,20 @@ isOtherTempNamespace(Oid namespaceId)
}
/*
+ * isOtherTempNamespace - is the given namespace some other backend's
+ * temporary-table namespace?
+ */
+bool
+isOtherTempNamespace(Oid namespaceId)
+{
+ /* If it's my own temp namespace, say "false" */
+ if (isTempNamespace(namespaceId))
+ return false;
+ /* Else, if the namespace name starts with "pg_temp_", say "true" */
+ return isAnyTempNamespace(namespaceId);
+}
+
+/*
* PushSpecialNamespace - push a "special" namespace onto the front of the
* search path.
*
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 0b61bd7e2d..cf026092b1 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -599,3 +599,69 @@ GetConstraintNameForTrigger(Oid triggerId)
return result;
}
+
+/*
+ * AlterConstraintNamespaces
+ * Find any constraints belonging to the specified object,
+ * and move them to the specified new namespace.
+ *
+ * isType indicates whether the owning object is a type or a relation.
+ */
+void
+AlterConstraintNamespaces(Oid ownerId, Oid oldNspId,
+ Oid newNspId, bool isType)
+{
+ Relation conRel;
+ ScanKeyData key[1];
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ conRel = heap_open(ConstraintRelationId, RowExclusiveLock);
+
+ if (isType)
+ {
+ ScanKeyInit(&key[0],
+ Anum_pg_constraint_contypid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(ownerId));
+
+ scan = systable_beginscan(conRel, ConstraintTypidIndexId, true,
+ SnapshotNow, 1, key);
+ }
+ else
+ {
+ ScanKeyInit(&key[0],
+ Anum_pg_constraint_conrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(ownerId));
+
+ scan = systable_beginscan(conRel, ConstraintRelidIndexId, true,
+ SnapshotNow, 1, key);
+ }
+
+ while (HeapTupleIsValid((tup = systable_getnext(scan))))
+ {
+ Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(tup);
+
+ if (conform->connamespace == oldNspId)
+ {
+ tup = heap_copytuple(tup);
+ conform = (Form_pg_constraint) GETSTRUCT(tup);
+
+ conform->connamespace = newNspId;
+
+ simple_heap_update(conRel, &tup->t_self, tup);
+ CatalogUpdateIndexes(conRel, tup);
+
+ /*
+ * Note: currently, the constraint will not have its own
+ * dependency on the namespace, so we don't need to do
+ * changeDependencyFor().
+ */
+ }
+ }
+
+ systable_endscan(scan);
+
+ heap_close(conRel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 6a5ad9830c..55d1d10649 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -163,6 +163,105 @@ deleteDependencyRecordsFor(Oid classId, Oid objectId)
}
/*
+ * Adjust dependency record(s) to point to a different object of the same type
+ *
+ * classId/objectId specify the referencing object.
+ * refClassId/oldRefObjectId specify the old referenced object.
+ * newRefObjectId is the new referenced object (must be of class refClassId).
+ *
+ * Note the lack of objsubid parameters. If there are subobject references
+ * they will all be readjusted.
+ *
+ * Returns the number of records updated.
+ */
+long
+changeDependencyFor(Oid classId, Oid objectId,
+ Oid refClassId, Oid oldRefObjectId,
+ Oid newRefObjectId)
+{
+ long count = 0;
+ Relation depRel;
+ ScanKeyData key[2];
+ SysScanDesc scan;
+ HeapTuple tup;
+ ObjectAddress objAddr;
+ bool newIsPinned;
+
+ depRel = heap_open(DependRelationId, RowExclusiveLock);
+
+ /*
+ * If oldRefObjectId is pinned, there won't be any dependency entries
+ * on it --- we can't cope in that case. (This isn't really worth
+ * expending code to fix, in current usage; it just means you can't
+ * rename stuff out of pg_catalog, which would likely be a bad move
+ * anyway.)
+ */
+ objAddr.classId = refClassId;
+ objAddr.objectId = oldRefObjectId;
+ objAddr.objectSubId = 0;
+
+ if (isObjectPinned(&objAddr, depRel))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot remove dependency on %s because it is a system object",
+ getObjectDescription(&objAddr))));
+
+ /*
+ * We can handle adding a dependency on something pinned, though,
+ * since that just means deleting the dependency entry.
+ */
+ objAddr.objectId = newRefObjectId;
+
+ newIsPinned = isObjectPinned(&objAddr, depRel);
+
+ /* Now search for dependency records */
+ ScanKeyInit(&key[0],
+ Anum_pg_depend_classid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(classId));
+ ScanKeyInit(&key[1],
+ Anum_pg_depend_objid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(objectId));
+
+ scan = systable_beginscan(depRel, DependDependerIndexId, true,
+ SnapshotNow, 2, key);
+
+ while (HeapTupleIsValid((tup = systable_getnext(scan))))
+ {
+ Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup);
+
+ if (depform->refclassid == refClassId &&
+ depform->refobjid == oldRefObjectId)
+ {
+ if (newIsPinned)
+ simple_heap_delete(depRel, &tup->t_self);
+ else
+ {
+ /* make a modifiable copy */
+ tup = heap_copytuple(tup);
+ depform = (Form_pg_depend) GETSTRUCT(tup);
+
+ depform->refobjid = newRefObjectId;
+
+ simple_heap_update(depRel, &tup->t_self, tup);
+ CatalogUpdateIndexes(depRel, tup);
+
+ heap_freetuple(tup);
+ }
+
+ count++;
+ }
+ }
+
+ systable_endscan(scan);
+
+ heap_close(depRel, RowExclusiveLock);
+
+ return count;
+}
+
+/*
* isObjectPinned()
*
* Test if an object is required for basic database functionality.
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 9898ac2b2b..3220b757b9 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -143,6 +143,38 @@ ExecRenameStmt(RenameStmt *stmt)
}
/*
+ * Executes an ALTER OBJECT / SET SCHEMA statement. Based on the object
+ * type, the function appropriate to that type is executed.
+ */
+void
+ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt)
+{
+ switch (stmt->objectType)
+ {
+ case OBJECT_AGGREGATE:
+ case OBJECT_FUNCTION:
+ AlterFunctionNamespace(stmt->object, stmt->objarg,
+ stmt->newschema);
+ break;
+
+ case OBJECT_SEQUENCE:
+ case OBJECT_TABLE:
+ CheckRelationOwnership(stmt->relation, true);
+ AlterTableNamespace(stmt->relation, stmt->newschema);
+ break;
+
+ case OBJECT_TYPE:
+ case OBJECT_DOMAIN:
+ AlterTypeNamespace(stmt->object, stmt->newschema);
+ break;
+
+ default:
+ elog(ERROR, "unrecognized AlterObjectSchemaStmt type: %d",
+ (int) stmt->objectType);
+ }
+}
+
+/*
* Executes an ALTER OBJECT / OWNER TO statement. Based on the object
* type, the function appropriate to that type is executed.
*/
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 134b1d9bdb..88699aef3e 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -40,6 +40,7 @@
#include "catalog/pg_aggregate.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_language.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
@@ -1427,3 +1428,88 @@ DropCastById(Oid castOid)
systable_endscan(scan);
heap_close(relation, RowExclusiveLock);
}
+
+/*
+ * Execute ALTER FUNCTION SET SCHEMA
+ */
+void
+AlterFunctionNamespace(List *name, List *argtypes, const char *newschema)
+{
+ Oid procOid;
+ Oid oldNspOid;
+ Oid nspOid;
+ HeapTuple tup;
+ Relation procRel;
+ Form_pg_proc proc;
+
+ procRel = heap_open(ProcedureRelationId, RowExclusiveLock);
+
+ /* get function OID */
+ procOid = LookupFuncNameTypeNames(name, argtypes, false);
+
+ /* check permissions on function */
+ if (!pg_proc_ownercheck(procOid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC,
+ NameListToString(name));
+
+ tup = SearchSysCacheCopy(PROCOID,
+ ObjectIdGetDatum(procOid),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for function %u", procOid);
+ proc = (Form_pg_proc) GETSTRUCT(tup);
+
+ oldNspOid = proc->pronamespace;
+
+ /* get schema OID and check its permissions */
+ nspOid = LookupCreationNamespace(newschema);
+
+ if (oldNspOid == nspOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_FUNCTION),
+ errmsg("function \"%s\" is already in schema \"%s\"",
+ NameListToString(name),
+ newschema)));
+
+ /* disallow renaming into or out of temp schemas */
+ if (isAnyTempNamespace(nspOid) || isAnyTempNamespace(oldNspOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move objects into or out of temporary schemas")));
+
+ /* same for TOAST schema */
+ if (nspOid == PG_TOAST_NAMESPACE || oldNspOid == PG_TOAST_NAMESPACE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move objects into or out of TOAST schema")));
+
+ /* check for duplicate name (more friendly than unique-index failure) */
+ if (SearchSysCacheExists(PROCNAMEARGSNSP,
+ CStringGetDatum(NameStr(proc->proname)),
+ PointerGetDatum(&proc->proargtypes),
+ ObjectIdGetDatum(nspOid),
+ 0))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_FUNCTION),
+ errmsg("function \"%s\" already exists in schema \"%s\"",
+ NameStr(proc->proname),
+ newschema)));
+
+ /* OK, modify the pg_proc row */
+
+ /* tup is a copy, so we can scribble directly on it */
+ proc->pronamespace = nspOid;
+
+ simple_heap_update(procRel, &tup->t_self, tup);
+ CatalogUpdateIndexes(procRel, tup);
+
+ /* Update dependency on schema */
+ if (changeDependencyFor(ProcedureRelationId, procOid,
+ NamespaceRelationId, oldNspOid, nspOid) != 1)
+ elog(ERROR, "failed to change schema dependency for function \"%s\"",
+ NameListToString(name));
+
+ heap_freetuple(tup);
+
+ heap_close(procRel, RowExclusiveLock);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b5d42ee040..05423c43c0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -163,6 +163,13 @@ static void StoreCatalogInheritance(Oid relationId, List *supers);
static int findAttrByName(const char *attributeName, List *schema);
static void setRelhassubclassInRelation(Oid relationId, bool relhassubclass);
static bool needs_toast_table(Relation rel);
+static void AlterIndexNamespaces(Relation classRel, Relation rel,
+ Oid oldNspOid, Oid newNspOid);
+static void AlterSeqNamespaces(Relation classRel, Relation rel,
+ Oid oldNspOid, Oid newNspOid,
+ const char *newNspName);
+static void RebuildSerialDefaultExpr(Relation rel, AttrNumber attnum,
+ const char *seqname, const char *nspname);
static int transformColumnNameList(Oid relId, List *colList,
int16 *attnums, Oid *atttypids);
static int transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
@@ -5999,6 +6006,293 @@ needs_toast_table(Relation rel)
/*
+ * Execute ALTER TABLE SET SCHEMA
+ *
+ * Note: caller must have checked ownership of the relation already
+ */
+void
+AlterTableNamespace(RangeVar *relation, const char *newschema)
+{
+ Relation rel;
+ Oid relid;
+ Oid oldNspOid;
+ Oid nspOid;
+ Relation classRel;
+
+ rel = heap_openrv(relation, AccessExclusiveLock);
+
+ /* heap_openrv allows TOAST, but we don't want to */
+ if (rel->rd_rel->relkind == RELKIND_TOASTVALUE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a TOAST relation",
+ RelationGetRelationName(rel))));
+
+ relid = RelationGetRelid(rel);
+ oldNspOid = RelationGetNamespace(rel);
+
+ /* get schema OID and check its permissions */
+ nspOid = LookupCreationNamespace(newschema);
+
+ if (oldNspOid == nspOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_TABLE),
+ errmsg("relation \"%s\" is already in schema \"%s\"",
+ RelationGetRelationName(rel),
+ newschema)));
+
+ /* disallow renaming into or out of temp schemas */
+ if (isAnyTempNamespace(nspOid) || isAnyTempNamespace(oldNspOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move objects into or out of temporary schemas")));
+
+ /* same for TOAST schema */
+ if (nspOid == PG_TOAST_NAMESPACE || oldNspOid == PG_TOAST_NAMESPACE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move objects into or out of TOAST schema")));
+
+ /* OK, modify the pg_class row and pg_depend entry */
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+
+ AlterRelationNamespaceInternal(classRel, relid, oldNspOid, nspOid, true);
+
+ /* Fix the table's rowtype too */
+ AlterTypeNamespaceInternal(rel->rd_rel->reltype, nspOid, false);
+
+ /* Fix other dependent stuff */
+ if (rel->rd_rel->relkind == RELKIND_RELATION)
+ {
+ AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid);
+ AlterSeqNamespaces(classRel, rel, oldNspOid, nspOid, newschema);
+ AlterConstraintNamespaces(relid, oldNspOid, nspOid, false);
+ }
+
+ heap_close(classRel, RowExclusiveLock);
+
+ /* close rel, but keep lock until commit */
+ relation_close(rel, NoLock);
+}
+
+/*
+ * The guts of relocating a relation to another namespace: fix the pg_class
+ * entry, and the pg_depend entry if any. Caller must already have
+ * opened and write-locked pg_class.
+ */
+void
+AlterRelationNamespaceInternal(Relation classRel, Oid relOid,
+ Oid oldNspOid, Oid newNspOid,
+ bool hasDependEntry)
+{
+ HeapTuple classTup;
+ Form_pg_class classForm;
+
+ classTup = SearchSysCacheCopy(RELOID,
+ ObjectIdGetDatum(relOid),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(classTup))
+ elog(ERROR, "cache lookup failed for relation %u", relOid);
+ classForm = (Form_pg_class) GETSTRUCT(classTup);
+
+ 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))));
+
+ /* classTup is a copy, so OK to scribble on */
+ classForm->relnamespace = newNspOid;
+
+ 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));
+
+ heap_freetuple(classTup);
+}
+
+/*
+ * Move all indexes for the specified relation to another namespace.
+ *
+ * Note: we assume adequate permission checking was done by the caller,
+ * and that the caller has a suitable lock on the owning relation.
+ */
+static void
+AlterIndexNamespaces(Relation classRel, Relation rel,
+ Oid oldNspOid, Oid newNspOid)
+{
+ List *indexList;
+ ListCell *l;
+
+ indexList = RelationGetIndexList(rel);
+
+ foreach(l, indexList)
+ {
+ Oid indexOid = lfirst_oid(l);
+
+ /*
+ * Note: currently, the index will not have its own dependency
+ * on the namespace, so we don't need to do changeDependencyFor().
+ * There's no rowtype in pg_type, either.
+ */
+ AlterRelationNamespaceInternal(classRel, indexOid,
+ oldNspOid, newNspOid,
+ false);
+ }
+
+ list_free(indexList);
+}
+
+/*
+ * Move all SERIAL-column sequences of the specified relation to another
+ * namespace.
+ *
+ * Note: we assume adequate permission checking was done by the caller,
+ * and that the caller has a suitable lock on the owning relation.
+ */
+static void
+AlterSeqNamespaces(Relation classRel, Relation rel,
+ Oid oldNspOid, Oid newNspOid, const char *newNspName)
+{
+ Relation depRel;
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ HeapTuple tup;
+
+ /*
+ * SERIAL sequences are those having an internal dependency on one
+ * of the table's columns (we don't care *which* column, exactly).
+ */
+ depRel = heap_open(DependRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_depend_refclassid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationRelationId));
+ ScanKeyInit(&key[1],
+ Anum_pg_depend_refobjid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ /* we leave refobjsubid unspecified */
+
+ scan = systable_beginscan(depRel, DependReferenceIndexId, true,
+ SnapshotNow, 2, key);
+
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_depend depForm = (Form_pg_depend) GETSTRUCT(tup);
+ Relation seqRel;
+
+ /* skip dependencies other than internal dependencies on columns */
+ if (depForm->refobjsubid == 0 ||
+ depForm->classid != RelationRelationId ||
+ depForm->objsubid != 0 ||
+ depForm->deptype != DEPENDENCY_INTERNAL)
+ continue;
+
+ /* Use relation_open just in case it's an index */
+ seqRel = relation_open(depForm->objid, AccessExclusiveLock);
+
+ /* skip non-sequence relations */
+ if (RelationGetForm(seqRel)->relkind != RELKIND_SEQUENCE)
+ {
+ /* No need to keep the lock */
+ relation_close(seqRel, AccessExclusiveLock);
+ continue;
+ }
+
+ /* Fix the pg_class and pg_depend entries */
+ AlterRelationNamespaceInternal(classRel, depForm->objid,
+ oldNspOid, newNspOid,
+ true);
+ /*
+ * 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);
+ /*
+ * And we need to rebuild the column default expression that
+ * relies on this sequence.
+ */
+ if (depForm->refobjsubid > 0)
+ RebuildSerialDefaultExpr(rel,
+ depForm->refobjsubid,
+ RelationGetRelationName(seqRel),
+ newNspName);
+
+ /* Now we can close it. Keep the lock till end of transaction. */
+ relation_close(seqRel, NoLock);
+ }
+
+ systable_endscan(scan);
+
+ relation_close(depRel, AccessShareLock);
+}
+
+/*
+ * Rebuild the default expression for a SERIAL column identified by rel
+ * and attnum. This is annoying, but we have to do it because the
+ * stored expression has the schema name as a text constant.
+ *
+ * The caller must be sure the specified column is really a SERIAL column,
+ * because no further checks are done here.
+ */
+static void
+RebuildSerialDefaultExpr(Relation rel, AttrNumber attnum,
+ const char *seqname, const char *nspname)
+{
+ char *qstring;
+ A_Const *snamenode;
+ FuncCall *funccallnode;
+ RawColumnDefault *rawEnt;
+
+ /*
+ * Create raw parse tree for the updated column default expression.
+ * This should match transformColumnDefinition() in parser/analyze.c.
+ */
+ qstring = quote_qualified_identifier(nspname, seqname);
+ snamenode = makeNode(A_Const);
+ snamenode->val.type = T_String;
+ snamenode->val.val.str = qstring;
+ funccallnode = makeNode(FuncCall);
+ funccallnode->funcname = SystemFuncName("nextval");
+ funccallnode->args = list_make1(snamenode);
+ funccallnode->agg_star = false;
+ funccallnode->agg_distinct = false;
+
+ /*
+ * Remove any old default for the column. We use RESTRICT here for
+ * safety, but at present we do not expect anything to depend on the
+ * default.
+ */
+ RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false);
+
+ /* Do the equivalent of ALTER TABLE ... SET DEFAULT */
+ rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
+ rawEnt->attnum = attnum;
+ rawEnt->raw_default = (Node *) funccallnode;
+
+ /*
+ * This function is intended for CREATE TABLE, so it processes a
+ * _list_ of defaults, but we just do one.
+ */
+ AddRelationRawConstraints(rel, list_make1(rawEnt), NIL);
+}
+
+
+/*
* This code supports
* CREATE TEMP TABLE ... ON COMMIT { DROP | PRESERVE ROWS | DELETE ROWS }
*
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 52947f249c..02574de185 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -39,6 +39,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_depend.h"
+#include "catalog/pg_namespace.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "commands/tablecmds.h"
@@ -2100,3 +2101,171 @@ AlterTypeOwner(List *names, Oid newOwnerId)
/* Clean up */
heap_close(rel, RowExclusiveLock);
}
+
+/*
+ * Execute ALTER TYPE SET SCHEMA
+ */
+void
+AlterTypeNamespace(List *names, const char *newschema)
+{
+ TypeName *typename;
+ Oid typeOid;
+ Oid nspOid;
+
+ /* get type OID */
+ typename = makeNode(TypeName);
+ typename->names = names;
+ typename->typmod = -1;
+ typename->arrayBounds = NIL;
+
+ typeOid = LookupTypeName(typename);
+
+ if (!OidIsValid(typeOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("type \"%s\" does not exist",
+ TypeNameToString(typename))));
+
+ /* check permissions on type */
+ if (!pg_type_ownercheck(typeOid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE,
+ format_type_be(typeOid));
+
+ /* get schema OID and check its permissions */
+ nspOid = LookupCreationNamespace(newschema);
+
+ /* and do the work */
+ AlterTypeNamespaceInternal(typeOid, nspOid, true);
+}
+
+/*
+ * Move specified type to new namespace.
+ *
+ * Caller must have already checked privileges.
+ *
+ * If errorOnTableType is TRUE, the function errors out if the type is
+ * a table type. ALTER TABLE has to be used to move a table to a new
+ * namespace.
+ */
+void
+AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid,
+ bool errorOnTableType)
+{
+ Relation rel;
+ HeapTuple tup;
+ Form_pg_type typform;
+ Oid oldNspOid;
+ bool isCompositeType;
+
+ rel = heap_open(TypeRelationId, RowExclusiveLock);
+
+ tup = SearchSysCacheCopy(TYPEOID,
+ ObjectIdGetDatum(typeOid),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for type %u", typeOid);
+ typform = (Form_pg_type) GETSTRUCT(tup);
+
+ oldNspOid = typform->typnamespace;
+
+ if (oldNspOid == nspOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("type %s is already in schema \"%s\"",
+ format_type_be(typeOid),
+ get_namespace_name(nspOid))));
+
+ /* disallow renaming into or out of temp schemas */
+ if (isAnyTempNamespace(nspOid) || isAnyTempNamespace(oldNspOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move objects into or out of temporary schemas")));
+
+ /* same for TOAST schema */
+ if (nspOid == PG_TOAST_NAMESPACE || oldNspOid == PG_TOAST_NAMESPACE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move objects into or out of TOAST schema")));
+
+ /* check for duplicate name (more friendly than unique-index failure) */
+ if (SearchSysCacheExists(TYPENAMENSP,
+ CStringGetDatum(NameStr(typform->typname)),
+ ObjectIdGetDatum(nspOid),
+ 0, 0))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("type \"%s\" already exists in schema \"%s\"",
+ NameStr(typform->typname),
+ get_namespace_name(nspOid))));
+
+ /* Detect whether type is a composite type (but not a table rowtype) */
+ isCompositeType =
+ (typform->typtype == 'c' &&
+ get_rel_relkind(typform->typrelid) == RELKIND_COMPOSITE_TYPE);
+
+ /* Enforce not-table-type if requested */
+ if (typform->typtype == 'c' && !isCompositeType && errorOnTableType)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("%s is a table's row type",
+ format_type_be(typeOid)),
+ errhint("Use ALTER TABLE SET SCHEMA instead.")));
+
+ /* OK, modify the pg_type row */
+
+ /* tup is a copy, so we can scribble directly on it */
+ typform->typnamespace = nspOid;
+
+ simple_heap_update(rel, &tup->t_self, tup);
+ CatalogUpdateIndexes(rel, tup);
+
+ /*
+ * Composite types have pg_class entries.
+ *
+ * We need to modify the pg_class tuple as well to
+ * reflect the change of schema.
+ */
+ if (isCompositeType)
+ {
+ Relation classRel;
+
+ classRel = heap_open(RelationRelationId, RowExclusiveLock);
+
+ /*
+ * The dependency on the schema is listed under the pg_class entry,
+ * so tell AlterRelationNamespaceInternal to fix it.
+ */
+ AlterRelationNamespaceInternal(classRel, typform->typrelid,
+ oldNspOid, nspOid,
+ true);
+
+ heap_close(classRel, RowExclusiveLock);
+
+ /*
+ * Check for constraints associated with the composite type
+ * (we don't currently support this, but probably will someday).
+ */
+ AlterConstraintNamespaces(typform->typrelid, oldNspOid,
+ nspOid, false);
+ }
+ else
+ {
+ /* If it's a domain, it might have constraints */
+ if (typform->typtype == 'd')
+ AlterConstraintNamespaces(typeOid, oldNspOid, nspOid, true);
+
+ /*
+ * Update dependency on schema, if any --- a table rowtype has not
+ * got one.
+ */
+ if (typform->typtype != 'c')
+ if (changeDependencyFor(TypeRelationId, typeOid,
+ NamespaceRelationId, oldNspOid, nspOid) != 1)
+ elog(ERROR, "failed to change schema dependency for type %s",
+ format_type_be(typeOid));
+ }
+
+ heap_freetuple(tup);
+
+ heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index fda66c3532..85b215ada3 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2035,12 +2035,27 @@ _copyRenameStmt(RenameStmt *from)
{
RenameStmt *newnode = makeNode(RenameStmt);
+ COPY_SCALAR_FIELD(renameType);
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(object);
COPY_NODE_FIELD(objarg);
COPY_STRING_FIELD(subname);
COPY_STRING_FIELD(newname);
- COPY_SCALAR_FIELD(renameType);
+
+ return newnode;
+}
+
+static AlterObjectSchemaStmt *
+_copyAlterObjectSchemaStmt(AlterObjectSchemaStmt *from)
+{
+ AlterObjectSchemaStmt *newnode = makeNode(AlterObjectSchemaStmt);
+
+ COPY_SCALAR_FIELD(objectType);
+ COPY_NODE_FIELD(relation);
+ COPY_NODE_FIELD(object);
+ COPY_NODE_FIELD(objarg);
+ COPY_STRING_FIELD(addname);
+ COPY_STRING_FIELD(newschema);
return newnode;
}
@@ -2050,12 +2065,12 @@ _copyAlterOwnerStmt(AlterOwnerStmt *from)
{
AlterOwnerStmt *newnode = makeNode(AlterOwnerStmt);
+ COPY_SCALAR_FIELD(objectType);
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(object);
COPY_NODE_FIELD(objarg);
COPY_STRING_FIELD(addname);
COPY_STRING_FIELD(newowner);
- COPY_SCALAR_FIELD(objectType);
return newnode;
}
@@ -2983,6 +2998,9 @@ copyObject(void *from)
case T_RenameStmt:
retval = _copyRenameStmt(from);
break;
+ case T_AlterObjectSchemaStmt:
+ retval = _copyAlterObjectSchemaStmt(from);
+ break;
case T_AlterOwnerStmt:
retval = _copyAlterOwnerStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 9596935def..582ff17fde 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1008,12 +1008,25 @@ _equalRemoveOpClassStmt(RemoveOpClassStmt *a, RemoveOpClassStmt *b)
static bool
_equalRenameStmt(RenameStmt *a, RenameStmt *b)
{
+ COMPARE_SCALAR_FIELD(renameType);
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(object);
COMPARE_NODE_FIELD(objarg);
COMPARE_STRING_FIELD(subname);
COMPARE_STRING_FIELD(newname);
- COMPARE_SCALAR_FIELD(renameType);
+
+ return true;
+}
+
+static bool
+_equalAlterObjectSchemaStmt(AlterObjectSchemaStmt *a, AlterObjectSchemaStmt *b)
+{
+ COMPARE_SCALAR_FIELD(objectType);
+ COMPARE_NODE_FIELD(relation);
+ COMPARE_NODE_FIELD(object);
+ COMPARE_NODE_FIELD(objarg);
+ COMPARE_STRING_FIELD(addname);
+ COMPARE_STRING_FIELD(newschema);
return true;
}
@@ -1021,12 +1034,12 @@ _equalRenameStmt(RenameStmt *a, RenameStmt *b)
static bool
_equalAlterOwnerStmt(AlterOwnerStmt *a, AlterOwnerStmt *b)
{
+ COMPARE_SCALAR_FIELD(objectType);
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(object);
COMPARE_NODE_FIELD(objarg);
COMPARE_STRING_FIELD(addname);
COMPARE_STRING_FIELD(newowner);
- COMPARE_SCALAR_FIELD(objectType);
return true;
}
@@ -2029,6 +2042,9 @@ equal(void *a, void *b)
case T_RenameStmt:
retval = _equalRenameStmt(a, b);
break;
+ case T_AlterObjectSchemaStmt:
+ retval = _equalAlterObjectSchemaStmt(a, b);
+ break;
case T_AlterOwnerStmt:
retval = _equalAlterOwnerStmt(a, b);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6eb01db7ba..db25828ee7 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -132,7 +132,7 @@ static void doNegateFloat(Value *v);
%type <node> stmt schema_stmt
AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterGroupStmt
- AlterOwnerStmt AlterSeqStmt AlterTableStmt
+ AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
AlterUserStmt AlterUserSetStmt AlterRoleStmt AlterRoleSetStmt
AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
@@ -493,6 +493,7 @@ stmt :
| AlterDomainStmt
| AlterFunctionStmt
| AlterGroupStmt
+ | AlterObjectSchemaStmt
| AlterOwnerStmt
| AlterSeqStmt
| AlterTableStmt
@@ -3954,10 +3955,10 @@ RenameStmt: ALTER AGGREGATE func_name '(' aggr_argtype ')' RENAME TO name
| ALTER TRIGGER name ON relation_expr RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_TRIGGER;
n->relation = $5;
n->subname = $3;
n->newname = $8;
- n->renameType = OBJECT_TRIGGER;
$$ = (Node *)n;
}
| ALTER ROLE RoleId RENAME TO RoleId
@@ -3990,10 +3991,68 @@ opt_column: COLUMN { $$ = COLUMN; }
| /*EMPTY*/ { $$ = 0; }
;
+/*****************************************************************************
+ *
+ * ALTER THING name SET SCHEMA name
+ *
+ *****************************************************************************/
+
+AlterObjectSchemaStmt:
+ ALTER AGGREGATE func_name '(' aggr_argtype ')' SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_AGGREGATE;
+ n->object = $3;
+ n->objarg = list_make1($5);
+ n->newschema = $9;
+ $$ = (Node *)n;
+ }
+ | ALTER DOMAIN_P any_name SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_DOMAIN;
+ n->object = $3;
+ n->newschema = $6;
+ $$ = (Node *)n;
+ }
+ | ALTER FUNCTION func_name func_args SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_FUNCTION;
+ n->object = $3;
+ n->objarg = extractArgTypes($4);
+ n->newschema = $7;
+ $$ = (Node *)n;
+ }
+ | ALTER SEQUENCE relation_expr SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_SEQUENCE;
+ n->relation = $3;
+ n->newschema = $6;
+ $$ = (Node *)n;
+ }
+ | ALTER TABLE relation_expr SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_TABLE;
+ n->relation = $3;
+ n->newschema = $6;
+ $$ = (Node *)n;
+ }
+ | ALTER TYPE_P any_name SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_TYPE;
+ n->object = $3;
+ n->newschema = $6;
+ $$ = (Node *)n;
+ }
+ ;
/*****************************************************************************
*
- * ALTER THING name OWNER TO newname.
+ * ALTER THING name OWNER TO newname
*
*****************************************************************************/
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f0061a3397..cb6e275bfe 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -281,6 +281,7 @@ check_xact_readonly(Node *parsetree)
case T_AlterFunctionStmt:
case T_AlterRoleStmt:
case T_AlterRoleSetStmt:
+ case T_AlterObjectSchemaStmt:
case T_AlterOwnerStmt:
case T_AlterSeqStmt:
case T_AlterTableStmt:
@@ -625,6 +626,10 @@ ProcessUtility(Node *parsetree,
ExecRenameStmt((RenameStmt *) parsetree);
break;
+ case T_AlterObjectSchemaStmt:
+ ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree);
+ break;
+
case T_AlterOwnerStmt:
ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
break;
@@ -1358,6 +1363,10 @@ CreateCommandTag(Node *parsetree)
case OBJECT_SCHEMA:
tag = "ALTER SCHEMA";
break;
+ case OBJECT_COLUMN:
+ case OBJECT_TABLE:
+ tag = "ALTER TABLE";
+ break;
case OBJECT_TABLESPACE:
tag = "ALTER TABLESPACE";
break;
@@ -1365,10 +1374,38 @@ CreateCommandTag(Node *parsetree)
tag = "ALTER TRIGGER";
break;
default:
- tag = "ALTER TABLE";
+ tag = "???";
+ break;
}
break;
+ case T_AlterObjectSchemaStmt:
+ switch (((AlterObjectSchemaStmt *) parsetree)->objectType)
+ {
+ case OBJECT_AGGREGATE:
+ tag = "ALTER AGGREGATE";
+ break;
+ case OBJECT_DOMAIN:
+ tag = "ALTER DOMAIN";
+ break;
+ case OBJECT_FUNCTION:
+ tag = "ALTER FUNCTION";
+ break;
+ case OBJECT_SEQUENCE:
+ tag = "ALTER SEQUENCE";
+ break;
+ case OBJECT_TABLE:
+ tag = "ALTER TABLE";
+ break;
+ case OBJECT_TYPE:
+ tag = "ALTER TYPE";
+ break;
+ default:
+ tag = "???";
+ break;
+ }
+ break;
+
case T_AlterOwnerStmt:
switch (((AlterOwnerStmt *) parsetree)->objectType)
{
@@ -1403,7 +1440,8 @@ CreateCommandTag(Node *parsetree)
tag = "ALTER TYPE";
break;
default:
- tag = "ALTER TABLE";
+ tag = "???";
+ break;
}
break;
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 0c3e20596b..fad91780b2 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -174,6 +174,10 @@ extern void recordMultipleDependencies(const ObjectAddress *depender,
extern long deleteDependencyRecordsFor(Oid classId, Oid objectId);
+extern long changeDependencyFor(Oid classId, Oid objectId,
+ Oid refClassId, Oid oldRefObjectId,
+ Oid newRefObjectId);
+
/* in pg_shdepend.c */
extern void recordSharedDependencyOn(ObjectAddress *depender,
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index 158aac8b47..00d6990874 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -74,12 +74,14 @@ extern void DeconstructQualifiedName(List *names,
char **objname_p);
extern Oid LookupExplicitNamespace(const char *nspname);
+extern Oid LookupCreationNamespace(const char *nspname);
extern Oid QualifiedNameGetCreationNamespace(List *names, char **objname_p);
extern RangeVar *makeRangeVarFromNameList(List *names);
extern char *NameListToString(List *names);
extern char *NameListToQuotedString(List *names);
extern bool isTempNamespace(Oid namespaceId);
+extern bool isAnyTempNamespace(Oid namespaceId);
extern bool isOtherTempNamespace(Oid namespaceId);
extern void PushSpecialNamespace(Oid namespaceId);
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index c5c22862ea..8478ace0ef 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -185,4 +185,7 @@ extern char *ChooseConstraintName(const char *name1, const char *name2,
extern char *GetConstraintNameForTrigger(Oid triggerId);
+extern void AlterConstraintNamespaces(Oid ownerId, Oid oldNspId,
+ Oid newNspId, bool isType);
+
#endif /* PG_CONSTRAINT_H */
diff --git a/src/include/commands/alter.h b/src/include/commands/alter.h
index 0287812ac5..e0241eaa6c 100644
--- a/src/include/commands/alter.h
+++ b/src/include/commands/alter.h
@@ -17,7 +17,7 @@
#include "nodes/parsenodes.h"
extern void ExecRenameStmt(RenameStmt *stmt);
-
+extern void ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt);
extern void ExecAlterOwnerStmt(AlterOwnerStmt *stmt);
#endif /* ALTER_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 9e1cdac3f2..f3d2fcacdb 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -55,6 +55,8 @@ extern void AlterFunction(AlterFunctionStmt *stmt);
extern void CreateCast(CreateCastStmt *stmt);
extern void DropCast(DropCastStmt *stmt);
extern void DropCastById(Oid castOid);
+extern void AlterFunctionNamespace(List *name, List *argtypes,
+ const char *newschema);
/* commands/operatorcmds.c */
extern void DefineOperator(List *names, List *parameters);
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 8529d01579..71a4a0a03b 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -15,6 +15,7 @@
#define TABLECMDS_H
#include "nodes/parsenodes.h"
+#include "utils/rel.h"
extern Oid DefineRelation(CreateStmt *stmt, char relkind);
@@ -27,6 +28,12 @@ extern void AlterTableInternal(Oid relid, List *cmds, bool recurse);
extern void AlterTableCreateToastTable(Oid relOid, bool silent);
+extern void AlterTableNamespace(RangeVar *relation, const char *newschema);
+
+extern void AlterRelationNamespaceInternal(Relation classRel, Oid relOid,
+ Oid oldNspOid, Oid newNspOid,
+ bool hasDependEntry);
+
extern void ExecuteTruncate(List *relations);
extern void renameatt(Oid myrelid,
diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h
index e2551ce794..283ce9851d 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -35,5 +35,8 @@ extern void AlterDomainDropConstraint(List *names, const char *constrName,
extern List *GetDomainConstraints(Oid typeOid);
extern void AlterTypeOwner(List *names, Oid newOwnerId);
+extern void AlterTypeNamespace(List *names, const char *newschema);
+extern void AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid,
+ bool errorOnTableType);
#endif /* TYPECMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index f22c811908..f16fd7e4b0 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -284,6 +284,7 @@ typedef enum NodeTag
T_DeclareCursorStmt,
T_CreateTableSpaceStmt,
T_DropTableSpaceStmt,
+ T_AlterObjectSchemaStmt,
T_AlterOwnerStmt,
T_A_Expr = 800,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d18db83812..93697869c1 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1477,28 +1477,43 @@ typedef struct RemoveOpClassStmt
typedef struct RenameStmt
{
NodeTag type;
+ ObjectType renameType; /* OBJECT_TABLE, OBJECT_COLUMN, etc */
RangeVar *relation; /* in case it's a table */
List *object; /* in case it's some other object */
List *objarg; /* argument types, if applicable */
char *subname; /* name of contained object (column, rule,
* trigger, etc) */
char *newname; /* the new name */
- ObjectType renameType; /* OBJECT_TABLE, OBJECT_COLUMN, etc */
} RenameStmt;
/* ----------------------
+ * ALTER object SET SCHEMA Statement
+ * ----------------------
+ */
+typedef struct AlterObjectSchemaStmt
+{
+ NodeTag type;
+ ObjectType objectType; /* OBJECT_TABLE, OBJECT_TYPE, etc */
+ RangeVar *relation; /* in case it's a table */
+ List *object; /* in case it's some other object */
+ List *objarg; /* argument types, if applicable */
+ char *addname; /* additional name if needed */
+ char *newschema; /* the new schema */
+} AlterObjectSchemaStmt;
+
+/* ----------------------
* Alter Object Owner Statement
* ----------------------
*/
typedef struct AlterOwnerStmt
{
NodeTag type;
+ ObjectType objectType; /* OBJECT_TABLE, OBJECT_TYPE, etc */
RangeVar *relation; /* in case it's a table */
List *object; /* in case it's some other object */
List *objarg; /* argument types, if applicable */
char *addname; /* additional name if needed */
char *newowner; /* the new owner */
- ObjectType objectType; /* OBJECT_TABLE, OBJECT_TYPE, etc */
} AlterOwnerStmt;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 54205395e2..b6e46393b4 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1274,3 +1274,62 @@ select non_strict(NULL);
(1 row)
+--
+-- alter object set schema
+--
+create schema alter1;
+create schema alter2;
+create table alter1.t1(f1 serial primary key, f2 int check (f2 > 0));
+NOTICE: CREATE TABLE will create implicit sequence "t1_f1_seq" for serial column "t1.f1"
+NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t1_pkey" for table "t1"
+create view alter1.v1 as select * from alter1.t1;
+create function alter1.plus1(int) returns int as 'select $1+1' language sql;
+create domain alter1.posint integer check (value > 0);
+create type alter1.ctype as (f1 int, f2 text);
+insert into alter1.t1(f2) values(11);
+insert into alter1.t1(f2) values(12);
+alter table alter1.t1 set schema alter2;
+alter table alter1.v1 set schema alter2;
+alter function alter1.plus1(int) set schema alter2;
+alter domain alter1.posint set schema alter2;
+alter type alter1.ctype set schema alter2;
+-- this should succeed because nothing is left in alter1
+drop schema alter1;
+insert into alter2.t1(f2) values(13);
+insert into alter2.t1(f2) values(14);
+select * from alter2.t1;
+ f1 | f2
+----+----
+ 1 | 11
+ 2 | 12
+ 3 | 13
+ 4 | 14
+(4 rows)
+
+select * from alter2.v1;
+ f1 | f2
+----+----
+ 1 | 11
+ 2 | 12
+ 3 | 13
+ 4 | 14
+(4 rows)
+
+select alter2.plus1(41);
+ plus1
+-------
+ 42
+(1 row)
+
+-- clean up
+drop schema alter2 cascade;
+NOTICE: drop cascades to composite type alter2.ctype
+NOTICE: drop cascades to type alter2.ctype
+NOTICE: drop cascades to type alter2.posint
+NOTICE: drop cascades to function alter2.plus1(integer)
+NOTICE: drop cascades to view alter2.v1
+NOTICE: drop cascades to rule _RETURN on view alter2.v1
+NOTICE: drop cascades to sequence alter2.t1_f1_seq
+NOTICE: drop cascades to table alter2.t1 column f1
+NOTICE: drop cascades to table alter2.t1
+NOTICE: drop cascades to constraint t1_f2_check on table alter2.t1
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index aa3d17ccf7..8690f61dbe 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -998,3 +998,44 @@ create function non_strict(text) returns text as
select non_strict(NULL);
alter function non_strict(text) returns null on null input;
select non_strict(NULL);
+
+--
+-- alter object set schema
+--
+
+create schema alter1;
+create schema alter2;
+
+create table alter1.t1(f1 serial primary key, f2 int check (f2 > 0));
+
+create view alter1.v1 as select * from alter1.t1;
+
+create function alter1.plus1(int) returns int as 'select $1+1' language sql;
+
+create domain alter1.posint integer check (value > 0);
+
+create type alter1.ctype as (f1 int, f2 text);
+
+insert into alter1.t1(f2) values(11);
+insert into alter1.t1(f2) values(12);
+
+alter table alter1.t1 set schema alter2;
+alter table alter1.v1 set schema alter2;
+alter function alter1.plus1(int) set schema alter2;
+alter domain alter1.posint set schema alter2;
+alter type alter1.ctype set schema alter2;
+
+-- this should succeed because nothing is left in alter1
+drop schema alter1;
+
+insert into alter2.t1(f2) values(13);
+insert into alter2.t1(f2) values(14);
+
+select * from alter2.t1;
+
+select * from alter2.v1;
+
+select alter2.plus1(41);
+
+-- clean up
+drop schema alter2 cascade;