diff options
Diffstat (limited to 'src/backend/commands/tablecmds.c')
-rw-r--r-- | src/backend/commands/tablecmds.c | 296 |
1 files changed, 295 insertions, 1 deletions
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index c06c272cbdd..7d9a73917dd 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.164 2005/07/14 21:46:29 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.165 2005/08/01 04:03:55 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -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 } * |