* tablecmds.c
* Commands for creating and altering table structures and settings
*
- * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
#include "access/genam.h"
#include "access/heapam.h"
-#include "access/heapam_xlog.h"
#include "access/multixact.h"
#include "access/reloptions.h"
#include "access/relscan.h"
#include "access/sysattr.h"
#include "access/xact.h"
+#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "catalog/pg_type.h"
#include "catalog/pg_type_fn.h"
#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/comment.h"
#include "commands/defrem.h"
+#include "commands/event_trigger.h"
+#include "commands/policy.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "commands/typecmds.h"
-#include "common/relpath.h"
+#include "commands/user.h"
#include "executor/executor.h"
#include "foreign/foreign.h"
#include "miscadmin.h"
#include "parser/parse_type.h"
#include "parser/parse_utilcmd.h"
#include "parser/parser.h"
+#include "pgstat.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/relcache.h"
+#include "utils/ruleutils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/tqual.h"
List *constraints; /* List of NewConstraint */
List *newvals; /* List of NewColumnValue */
bool new_notnull; /* T if we added new NOT NULL constraints */
- bool rewrite; /* T if a rewrite is forced */
+ int rewrite; /* Reason for forced rewrite, if any */
Oid newTableSpace; /* new tablespace; 0 means no change */
+ bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
+ char newrelpersistence; /* if above is true */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
static void AlterSeqNamespaces(Relation classRel, Relation rel,
Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved,
LOCKMODE lockmode);
-static void ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
- bool recurse, bool recursing, LOCKMODE lockmode);
-static void ATExecValidateConstraint(Relation rel, char *constrName,
+static ObjectAddress ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
+ bool recurse, bool recursing, LOCKMODE lockmode);
+static ObjectAddress ATExecValidateConstraint(Relation rel, char *constrName,
bool recurse, bool recursing, LOCKMODE lockmode);
static int transformColumnNameList(Oid relId, List *colList,
int16 *attnums, Oid *atttypids);
static void validateForeignKeyConstraint(char *conname,
Relation rel, Relation pkrel,
Oid pkindOid, Oid constraintOid);
-static void createForeignKeyTriggers(Relation rel, Constraint *fkconstraint,
+static void createForeignKeyTriggers(Relation rel, Oid refRelOid,
+ Constraint *fkconstraint,
Oid constraintOid, Oid indexOid);
-static void ATController(Relation rel, List *cmds, bool recurse, LOCKMODE lockmode);
+static void ATController(AlterTableStmt *parsetree,
+ Relation rel, List *cmds, bool recurse, LOCKMODE lockmode);
static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
bool recurse, bool recursing, LOCKMODE lockmode);
static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode);
static void ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
AlterTableCmd *cmd, LOCKMODE lockmode);
-static void ATRewriteTables(List **wqueue, LOCKMODE lockmode);
+static void ATRewriteTables(AlterTableStmt *parsetree,
+ List **wqueue, LOCKMODE lockmode);
static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode);
static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
static void ATSimplePermissions(Relation rel, int allowed_targets);
static List *find_typed_table_dependencies(Oid typeOid, const char *typeName,
DropBehavior behavior);
static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
- AlterTableCmd *cmd, LOCKMODE lockmode);
-static void ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
- ColumnDef *colDef, bool isOid,
- bool recurse, bool recursing, LOCKMODE lockmode);
-static void check_for_column_name_collision(Relation rel, const char *colname);
+ bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode);
+static ObjectAddress ATExecAddColumn(List **wqueue, AlteredTableInfo *tab,
+ Relation rel, ColumnDef *colDef, bool isOid,
+ bool recurse, bool recursing,
+ bool if_not_exists, LOCKMODE lockmode);
+static bool check_for_column_name_collision(Relation rel, const char *colname,
+ bool if_not_exists);
static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
AlterTableCmd *cmd, LOCKMODE lockmode);
-static void ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
-static void ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
+static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
+static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
const char *colName, LOCKMODE lockmode);
-static void ATExecColumnDefault(Relation rel, const char *colName,
+static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
Node *newDefault, LOCKMODE lockmode);
static void ATPrepSetStatistics(Relation rel, const char *colName,
Node *newValue, LOCKMODE lockmode);
-static void ATExecSetStatistics(Relation rel, const char *colName,
+static ObjectAddress ATExecSetStatistics(Relation rel, const char *colName,
Node *newValue, LOCKMODE lockmode);
-static void ATExecSetOptions(Relation rel, const char *colName,
+static ObjectAddress ATExecSetOptions(Relation rel, const char *colName,
Node *options, bool isReset, LOCKMODE lockmode);
-static void ATExecSetStorage(Relation rel, const char *colName,
+static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
Node *newValue, LOCKMODE lockmode);
static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
AlterTableCmd *cmd, LOCKMODE lockmode);
-static void ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
DropBehavior behavior,
bool recurse, bool recursing,
bool missing_ok, LOCKMODE lockmode);
-static void ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
+static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
-static void ATExecAddConstraint(List **wqueue,
+static ObjectAddress ATExecAddConstraint(List **wqueue,
AlteredTableInfo *tab, Relation rel,
Constraint *newConstraint, bool recurse, bool is_readd,
LOCKMODE lockmode);
-static void ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
+static ObjectAddress ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
IndexStmt *stmt, LOCKMODE lockmode);
-static void ATAddCheckConstraint(List **wqueue,
+static ObjectAddress ATAddCheckConstraint(List **wqueue,
AlteredTableInfo *tab, Relation rel,
Constraint *constr,
bool recurse, bool recursing, bool is_readd,
LOCKMODE lockmode);
-static void ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
+static ObjectAddress ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
Constraint *fkconstraint, LOCKMODE lockmode);
static void ATExecDropConstraint(Relation rel, const char *constrName,
DropBehavior behavior,
bool recurse, bool recursing,
AlterTableCmd *cmd, LOCKMODE lockmode);
static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno);
-static void ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
+static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
AlterTableCmd *cmd, LOCKMODE lockmode);
-static void ATExecAlterColumnGenericOptions(Relation rel, const char *colName,
+static ObjectAddress ATExecAlterColumnGenericOptions(Relation rel, const char *colName,
List *options, LOCKMODE lockmode);
-static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode);
-static void ATPostAlterTypeParse(Oid oldId, char *cmd,
- List **wqueue, LOCKMODE lockmode, bool rewrite);
+static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab,
+ LOCKMODE lockmode);
+static void ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId,
+ char *cmd, List **wqueue, LOCKMODE lockmode,
+ bool rewrite);
+static void RebuildConstraintComment(AlteredTableInfo *tab, int pass,
+ Oid objid, Relation rel, char *conname);
static void TryReuseIndex(Oid oldId, IndexStmt *stmt);
static void TryReuseForeignKey(Oid oldId, Constraint *con);
static void change_owner_fix_column_acls(Oid relationOid,
Oid oldOwnerId, Oid newOwnerId);
static void change_owner_recurse_to_sequences(Oid relationOid,
Oid newOwnerId, LOCKMODE lockmode);
-static void ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode);
+static ObjectAddress ATExecClusterOn(Relation rel, const char *indexName,
+ LOCKMODE lockmode);
static void ATExecDropCluster(Relation rel, LOCKMODE lockmode);
+static bool ATPrepChangePersistence(Relation rel, bool toLogged);
static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
char *tablespacename, LOCKMODE lockmode);
static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
static void ATExecEnableDisableRule(Relation rel, char *rulename,
char fires_when, LOCKMODE lockmode);
static void ATPrepAddInherit(Relation child_rel);
-static void ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode);
-static void ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode);
+static ObjectAddress ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode);
+static ObjectAddress ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode);
static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid);
-static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
+static ObjectAddress ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode);
static void ATExecGenericOptions(Relation rel, List *options);
+static void ATExecEnableRowSecurity(Relation rel);
+static void ATExecDisableRowSecurity(Relation rel);
+static void ATExecForceNoForceRowSecurity(Relation rel, bool force_rls);
static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
ForkNumber forkNum, char relpersistence);
* The other arguments are used to extend the behavior for other cases:
* relkind: relkind to assign to the new relation
* ownerId: if not InvalidOid, use this as the new relation's owner.
+ * typaddress: if not null, it's set to the pg_type entry's address.
*
* Note that permissions checks are done against current user regardless of
* ownerId. A nonzero ownerId is used when someone is creating a relation
* "on behalf of" someone else, so we still want to see that the current user
* has permissions to do it.
*
- * If successful, returns the OID of the new relation.
+ * If successful, returns the address of the new relation.
* ----------------------------------------------------------------
*/
-Oid
-DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
+ObjectAddress
+DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
+ ObjectAddress *typaddress)
{
char relname[NAMEDATALEN];
Oid namespaceId;
AttrNumber attnum;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
Oid ofTypeId;
+ ObjectAddress address;
/*
* Truncate relname to appropriate length (probably a waste of time, as
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
- if (stmt->constraints != NIL && relkind == RELKIND_FOREIGN_TABLE)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("constraints are not supported on foreign tables")));
/*
* Look up the namespace in which we are supposed to create the relation,
reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
true, false);
- (void) heap_reloptions(relkind, reloptions, true);
+ if (relkind == RELKIND_VIEW)
+ (void) view_reloptions(reloptions, true);
+ else
+ (void) heap_reloptions(relkind, reloptions, true);
if (stmt->ofTypename)
{
&inheritOids, &old_constraints, &parentOidCount);
/*
- * Create a tuple descriptor from the relation schema. Note that this
+ * Create a tuple descriptor from the relation schema. Note that this
* deals with column names, types, and NOT NULL constraints, but not
* default values or CHECK constraints; we handle those below.
*/
descriptor = BuildDescForRelation(schema);
+ /*
+ * Notice that we allow OIDs here only for plain tables, even though some
+ * other relkinds can support them. This is necessary because the
+ * default_with_oids GUC must apply only to plain tables and not any other
+ * relkind; doing otherwise would break existing pg_dump files. We could
+ * allow explicit "WITH OIDS" while not allowing default_with_oids to
+ * affect other relkinds, but it would complicate interpretOidsOption().
+ */
localHasOids = interpretOidsOption(stmt->options,
- (relkind == RELKIND_RELATION ||
- relkind == RELKIND_FOREIGN_TABLE));
+ (relkind == RELKIND_RELATION));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
cooked->contype = CONSTR_DEFAULT;
+ cooked->conoid = InvalidOid; /* until created */
cooked->name = NULL;
cooked->attnum = attnum;
cooked->expr = colDef->cooked_default;
reloptions,
true,
allowSystemTableMods,
- false);
+ false,
+ typaddress);
/* Store inheritance information for new rel. */
StoreCatalogInheritance(relationId, inheritOids);
CommandCounterIncrement();
/*
- * Open the new relation and acquire exclusive lock on it. This isn't
+ * Open the new relation and acquire exclusive lock on it. This isn't
* really necessary for locking out other backends (since they can't see
* the new rel anyway until we commit), but it keeps the lock manager from
* complaining about deadlock risks.
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);
+ ObjectAddressSet(address, RelationRelationId, relationId);
+
/*
* Clean up. We keep lock on new relation (although it shouldn't be
* visible to anyone else anyway, until commit).
*/
relation_close(rel, NoLock);
- return relationId;
+ return address;
}
/*
* non-existent relation
*/
static void
-DropErrorMsgNonExistent(const char *relname, char rightkind, bool missing_ok)
+DropErrorMsgNonExistent(RangeVar *rel, char rightkind, bool missing_ok)
{
const struct dropmsgstrings *rentry;
+ if (rel->schemaname != NULL &&
+ !OidIsValid(LookupNamespaceNoError(rel->schemaname)))
+ {
+ if (!missing_ok)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("schema \"%s\" does not exist", rel->schemaname)));
+ }
+ else
+ {
+ ereport(NOTICE,
+ (errmsg("schema \"%s\" does not exist, skipping",
+ rel->schemaname)));
+ }
+ return;
+ }
+
for (rentry = dropmsgstringarray; rentry->kind != '\0'; rentry++)
{
if (rentry->kind == rightkind)
{
ereport(ERROR,
(errcode(rentry->nonexistent_code),
- errmsg(rentry->nonexistent_msg, relname)));
+ errmsg(rentry->nonexistent_msg, rel->relname)));
}
else
{
- ereport(NOTICE, (errmsg(rentry->skipping_msg, relname)));
+ ereport(NOTICE, (errmsg(rentry->skipping_msg, rel->relname)));
break;
}
}
/* Not there? */
if (!OidIsValid(relOid))
{
- DropErrorMsgNonExistent(rel->relname, relkind, drop->missing_ok);
+ DropErrorMsgNonExistent(rel, relkind, drop->missing_ok);
continue;
}
}
/*
- * In CASCADE mode, suck in all referencing relations as well. This
+ * In CASCADE mode, suck in all referencing relations as well. This
* requires multiple iterations to find indirectly-dependent relations. At
* each phase, we need to exclusive-lock new rels before looking for their
- * dependencies, else we might miss something. Also, we check each rel as
+ * dependencies, else we might miss something. Also, we check each rel as
* soon as we open it, to avoid a faux pas such as holding lock for a long
* time on a rel we have no permissions for.
*/
* as the relfilenode value. The old storage file is scheduled for
* deletion at commit.
*/
- RelationSetNewRelfilenode(rel, RecentXmin, minmulti);
+ RelationSetNewRelfilenode(rel, rel->rd_rel->relpersistence,
+ RecentXmin, minmulti);
if (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
heap_create_init_fork(rel);
if (OidIsValid(toast_relid))
{
rel = relation_open(toast_relid, AccessExclusiveLock);
- RelationSetNewRelfilenode(rel, RecentXmin, minmulti);
+ RelationSetNewRelfilenode(rel, rel->rd_rel->relpersistence,
+ RecentXmin, minmulti);
if (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
heap_create_init_fork(rel);
heap_close(rel, NoLock);
/*
* Reconstruct the indexes to match, and we're done.
*/
- reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST);
+ reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST, 0);
}
+
+ pgstat_count_truncate(rel);
}
/*
}
/*
- * Check that a given rel is safe to truncate. Subroutine for ExecuteTruncate
+ * Check that a given rel is safe to truncate. Subroutine for ExecuteTruncate
*/
static void
truncate_check_rel(Relation rel)
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("inherited relation \"%s\" is not a table",
+ errmsg("inherited relation \"%s\" is not a table or foreign table",
parent->relname)));
/* Permanent rels cannot inherit from temporary ones */
if (relpersistence != RELPERSISTENCE_TEMP &&
errmsg("inherited column \"%s\" has a type conflict",
attributeName),
errdetail("%s versus %s",
- TypeNameToString(def->typeName),
- format_type_be(attribute->atttypid))));
+ format_type_with_typemod(defTypeId,
+ deftypmod),
+ format_type_with_typemod(attribute->atttypid,
+ attribute->atttypmod))));
defCollId = GetColumnDefCollation(NULL, def, defTypeId);
if (defCollId != attribute->attcollation)
ereport(ERROR,
/*
* Now copy the CHECK constraints of this parent, adjusting attnos
- * using the completed newattno[] map. Identically named constraints
+ * using the completed newattno[] map. Identically named constraints
* are merged if possible, else we throw error.
*/
if (constr && constr->num_check > 0)
cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
cooked->contype = CONSTR_CHECK;
+ cooked->conoid = InvalidOid; /* until created */
cooked->name = pstrdup(name);
cooked->attnum = 0; /* not used for constraints */
cooked->expr = expr;
pfree(newattno);
/*
- * Close the parent rel, but keep our AccessShareLock on it until xact
- * commit. That will prevent someone else from deleting or ALTERing
- * the parent before the child is committed.
+ * Close the parent rel, but keep our ShareUpdateExclusiveLock on it
+ * until xact commit. That will prevent someone else from deleting or
+ * ALTERing the parent before the child is committed.
*/
heap_close(relation, NoLock);
}
*/
if (inhSchema != NIL)
{
+ int schema_attno = 0;
+
foreach(entry, schema)
{
ColumnDef *newdef = lfirst(entry);
char *attributeName = newdef->colname;
int exist_attno;
+ schema_attno++;
+
/*
* Does it conflict with some previously inherited column?
*/
* Yes, try to merge the two column definitions. They must
* have the same type, typmod, and collation.
*/
- ereport(NOTICE,
- (errmsg("merging column \"%s\" with inherited definition",
- attributeName)));
+ if (exist_attno == schema_attno)
+ ereport(NOTICE,
+ (errmsg("merging column \"%s\" with inherited definition",
+ attributeName)));
+ else
+ ereport(NOTICE,
+ (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
+ errdetail("User-specified column moved to the position of the inherited column.")));
def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod);
errmsg("column \"%s\" has a type conflict",
attributeName),
errdetail("%s versus %s",
- TypeNameToString(def->typeName),
- TypeNameToString(newdef->typeName))));
+ format_type_with_typemod(defTypeId,
+ deftypmod),
+ format_type_with_typemod(newTypeId,
+ newtypmod))));
defcollid = GetColumnDefCollation(NULL, def, defTypeId);
newcollid = GetColumnDefCollation(NULL, newdef, newTypeId);
if (defcollid != newcollid)
/*
* renameatt_internal - workhorse for renameatt
+ *
+ * Return value is the attribute number in the 'myrelid' relation.
*/
-static void
+static AttrNumber
renameatt_internal(Oid myrelid,
const char *oldattname,
const char *newattname,
Relation attrelation;
HeapTuple atttup;
Form_pg_attribute attform;
- int attnum;
+ AttrNumber attnum;
/*
* Grab an exclusive lock on the target table, which we will NOT release
oldattname)));
/*
- * if the attribute is inherited, forbid the renaming. if this is a
+ * if the attribute is inherited, forbid the renaming. if this is a
* top-level call to renameatt(), then expected_parents will be 0, so the
* effect of this code will be to prohibit the renaming if the attribute
* is inherited at all. if this is a recursive call to renameatt(),
oldattname)));
/* new name should not already exist */
- check_for_column_name_collision(targetrelation, newattname);
+ (void) check_for_column_name_collision(targetrelation, newattname, false);
/* apply the update */
namestrcpy(&(attform->attname), newattname);
heap_close(attrelation, RowExclusiveLock);
relation_close(targetrelation, NoLock); /* close rel but keep lock */
+
+ return attnum;
}
/*
}
/*
- * renameatt - changes the name of a attribute in a relation
+ * renameatt - changes the name of an attribute in a relation
+ *
+ * The returned ObjectAddress is that of the renamed column.
*/
-Oid
+ObjectAddress
renameatt(RenameStmt *stmt)
{
Oid relid;
+ AttrNumber attnum;
+ ObjectAddress address;
/* lock level taken here should match renameatt_internal */
relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
ereport(NOTICE,
(errmsg("relation \"%s\" does not exist, skipping",
stmt->relation->relname)));
- return InvalidOid;
+ return InvalidObjectAddress;
}
- renameatt_internal(relid,
- stmt->subname, /* old att name */
- stmt->newname, /* new att name */
- interpretInhOption(stmt->relation->inhOpt), /* recursive? */
- false, /* recursing? */
- 0, /* expected inhcount */
- stmt->behavior);
+ attnum =
+ renameatt_internal(relid,
+ stmt->subname, /* old att name */
+ stmt->newname, /* new att name */
+ interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+ false, /* recursing? */
+ 0, /* expected inhcount */
+ stmt->behavior);
- /* This is an ALTER TABLE command so it's about the relid */
- return relid;
-}
+ ObjectAddressSubSet(address, RelationRelationId, relid, attnum);
+ return address;
+}
/*
* same logic as renameatt_internal
*/
-static Oid
+static ObjectAddress
rename_constraint_internal(Oid myrelid,
Oid mytypid,
const char *oldconname,
Oid constraintOid;
HeapTuple tuple;
Form_pg_constraint con;
+ ObjectAddress address;
AssertArg(!myrelid || !mytypid);
else
RenameConstraintById(constraintOid, newconname);
+ ObjectAddressSet(address, ConstraintRelationId, constraintOid);
+
ReleaseSysCache(tuple);
if (targetrelation)
relation_close(targetrelation, NoLock); /* close rel but keep lock */
- return constraintOid;
+ return address;
}
-Oid
+ObjectAddress
RenameConstraint(RenameStmt *stmt)
{
Oid relid = InvalidOid;
Oid typid = InvalidOid;
- if (stmt->relationType == OBJECT_DOMAIN)
+ if (stmt->renameType == OBJECT_DOMCONSTRAINT)
{
Relation rel;
HeapTuple tup;
{
/* lock level taken here should match rename_constraint_internal */
relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
- false, false,
+ stmt->missing_ok, false,
RangeVarCallbackForRenameAttribute,
NULL);
+ if (!OidIsValid(relid))
+ {
+ ereport(NOTICE,
+ (errmsg("relation \"%s\" does not exist, skipping",
+ stmt->relation->relname)));
+ return InvalidObjectAddress;
+ }
}
return
}
/*
- * Execute ALTER TABLE/INDEX/SEQUENCE/VIEW/FOREIGN TABLE RENAME
+ * Execute ALTER TABLE/INDEX/SEQUENCE/VIEW/MATERIALIZED VIEW/FOREIGN TABLE
+ * RENAME
*/
-Oid
+ObjectAddress
RenameRelation(RenameStmt *stmt)
{
Oid relid;
+ ObjectAddress address;
/*
- * Grab an exclusive lock on the target table, index, sequence or view,
- * which we will NOT release until end of transaction.
+ * Grab an exclusive lock on the target table, index, sequence, view,
+ * materialized view, or foreign table, which we will NOT release until
+ * end of transaction.
*
* Lock level used here should match RenameRelationInternal, to avoid lock
* escalation.
ereport(NOTICE,
(errmsg("relation \"%s\" does not exist, skipping",
stmt->relation->relname)));
- return InvalidOid;
+ return InvalidObjectAddress;
}
/* Do the work */
RenameRelationInternal(relid, stmt->newname, false);
- return relid;
+ ObjectAddressSet(address, RelationRelationId, relid);
+
+ return address;
}
/*
Oid namespaceId;
/*
- * Grab an exclusive lock on the target table, index, sequence or view,
- * which we will NOT release until end of transaction.
+ * Grab an exclusive lock on the target table, index, sequence, view,
+ * materialized view, or foreign table, which we will NOT release until
+ * end of transaction.
*/
targetrelation = relation_open(myrelid, AccessExclusiveLock);
namespaceId = RelationGetNamespace(targetrelation);
newrelname)));
/*
- * Update pg_class tuple with new relname. (Scribbling on reltup is OK
+ * Update pg_class tuple with new relname. (Scribbling on reltup is OK
* because it's a copy...)
*/
namestrcpy(&(relform->relname), newrelname);
* We also reject these commands if there are any pending AFTER trigger events
* for the rel. This is certainly necessary for the rewriting variants of
* ALTER TABLE, because they don't preserve tuple TIDs and so the pending
- * events would try to fetch the wrong tuples. It might be overly cautious
+ * events would try to fetch the wrong tuples. It might be overly cautious
* in other cases, but again it seems better to err on the side of paranoia.
*
* REINDEX calls this with "rel" referencing the index to be rebuilt; here
* 3. Scan table(s) to check new constraints, and optionally recopy
* the data into new table(s).
* Phase 3 is not performed unless one or more of the subcommands requires
- * it. The intention of this design is to allow multiple independent
+ * it. The intention of this design is to allow multiple independent
* updates of the table schema to be performed with only one pass over the
* data.
*
- * ATPrepCmd performs phase 1. A "work queue" entry is created for
+ * ATPrepCmd performs phase 1. A "work queue" entry is created for
* each table to be affected (there may be multiple affected tables if the
* commands traverse a table inheritance hierarchy). Also we do preliminary
* validation of the subcommands, including parse transformation of those
* expressions that need to be evaluated with respect to the old table
* schema.
*
- * ATRewriteCatalogs performs phase 2 for each affected table. (Note that
+ * ATRewriteCatalogs performs phase 2 for each affected table. (Note that
* phases 2 and 3 normally do no explicit recursion, since phase 1 already
* did it --- although some subcommands have to recurse in phase 2 instead.)
* Certain subcommands need to be performed before others to avoid
* unnecessary conflicts; for example, DROP COLUMN should come before
- * ADD COLUMN. Therefore phase 1 divides the subcommands into multiple
+ * ADD COLUMN. Therefore phase 1 divides the subcommands into multiple
* lists, one for each logical "pass" of phase 2.
*
* ATRewriteTables performs phase 3 for those tables that need it.
* the whole operation; we don't have to do anything special to clean up.
*
* The caller must lock the relation, with an appropriate lock level
- * for the subcommands requested. Any subcommand that needs to rewrite
- * tuples in the table forces the whole command to be executed with
- * AccessExclusiveLock (actually, that is currently required always, but
- * we hope to relax it at some point). We pass the lock level down
+ * for the subcommands requested, using AlterTableGetLockLevel(stmt->cmds)
+ * or higher. We pass the lock level down
* so that we can apply it recursively to inherited tables. Note that the
* lock level we want as we recurse might well be higher than required for
* that specific subcommand. So we pass down the overall lock requirement,
CheckTableNotInUse(rel, "ALTER TABLE");
- ATController(rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt),
+ ATController(stmt,
+ rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt),
lockmode);
}
rel = relation_open(relid, lockmode);
- ATController(rel, cmds, recurse, lockmode);
+ EventTriggerAlterTableRelid(relid);
+
+ ATController(NULL, rel, cmds, recurse, lockmode);
}
/*
* ALTER TABLE RENAME which is treated as a different statement type T_RenameStmt
* and does not travel through this section of code and cannot be combined with
* any of the subcommands given here.
+ *
+ * Note that Hot Standby only knows about AccessExclusiveLocks on the master
+ * so any changes that might affect SELECTs running on standbys need to use
+ * AccessExclusiveLocks even if you think a lesser lock would do, unless you
+ * have a solution for that also.
+ *
+ * Also note that pg_dump uses only an AccessShareLock, meaning that anything
+ * that takes a lock less than AccessExclusiveLock can change object definitions
+ * while pg_dump is running. Be careful to check that the appropriate data is
+ * derived by pg_dump using an MVCC snapshot, rather than syscache lookups,
+ * otherwise we might end up with an inconsistent dump that can't restore.
*/
LOCKMODE
AlterTableGetLockLevel(List *cmds)
{
/*
- * Late in 9.1 dev cycle a number of issues were uncovered with access to
- * catalog relations, leading to the decision to re-enforce all DDL at
- * AccessExclusiveLock level by default.
- *
- * The issues are that there is a pervasive assumption in the code that
- * the catalogs will not be read unless an AccessExclusiveLock is held. If
- * that rule is relaxed, we must protect against a number of potential
- * effects - infrequent, but proven possible with test cases where
- * multiple DDL operations occur in a stream against frequently accessed
- * tables.
- *
- * 1. Catalog tables were read using SnapshotNow, which has a race bug that
- * allows a scan to return no valid rows even when one is present in the
- * case of a commit of a concurrent update of the catalog table.
- * SnapshotNow also ignores transactions in progress, so takes the latest
- * committed version without waiting for the latest changes.
- *
- * 2. Relcache needs to be internally consistent, so unless we lock the
- * definition during reads we have no way to guarantee that.
- *
- * 3. Catcache access isn't coordinated at all so refreshes can occur at
- * any time.
+ * This only works if we read catalog tables using MVCC snapshots.
*/
-#ifdef REDUCED_ALTER_TABLE_LOCK_LEVELS
ListCell *lcmd;
LOCKMODE lockmode = ShareUpdateExclusiveLock;
switch (cmd->subtype)
{
/*
- * Need AccessExclusiveLock for these subcommands because they
- * affect or potentially affect both read and write
- * operations.
- *
- * New subcommand types should be added here by default.
+ * These subcommands rewrite the heap, so require full locks.
*/
case AT_AddColumn: /* may rewrite heap, in some cases and visible
* to SELECT */
- case AT_DropColumn: /* change visible to SELECT */
- case AT_AddColumnToView: /* CREATE VIEW */
+ case AT_SetTableSpace: /* must rewrite heap */
case AT_AlterColumnType: /* must rewrite heap */
- case AT_DropConstraint: /* as DROP INDEX */
case AT_AddOids: /* must rewrite heap */
+ cmd_lockmode = AccessExclusiveLock;
+ break;
+
+ /*
+ * These subcommands may require addition of toast tables. If
+ * we add a toast table to a table currently being scanned, we
+ * might miss data added to the new toast table by concurrent
+ * insert transactions.
+ */
+ case AT_SetStorage:/* may add toast tables, see
+ * ATRewriteCatalogs() */
+ cmd_lockmode = AccessExclusiveLock;
+ break;
+
+ /*
+ * Removing constraints can affect SELECTs that have been
+ * optimised assuming the constraint holds true.
+ */
+ case AT_DropConstraint: /* as DROP INDEX */
+ case AT_DropNotNull: /* may change some SQL plans */
+ cmd_lockmode = AccessExclusiveLock;
+ break;
+
+ /*
+ * Subcommands that may be visible to concurrent SELECTs
+ */
+ case AT_DropColumn: /* change visible to SELECT */
+ case AT_AddColumnToView: /* CREATE VIEW */
case AT_DropOids: /* calls AT_DropColumn */
case AT_EnableAlwaysRule: /* may change SELECT rules */
case AT_EnableReplicaRule: /* may change SELECT rules */
case AT_EnableRule: /* may change SELECT rules */
case AT_DisableRule: /* may change SELECT rules */
+ cmd_lockmode = AccessExclusiveLock;
+ break;
+
+ /*
+ * Changing owner may remove implicit SELECT privileges
+ */
case AT_ChangeOwner: /* change visible to SELECT */
- case AT_SetTableSpace: /* must rewrite heap */
- case AT_DropNotNull: /* may change some SQL plans */
- case AT_SetNotNull:
+ cmd_lockmode = AccessExclusiveLock;
+ break;
+
+ /*
+ * Changing foreign table options may affect optimisation.
+ */
case AT_GenericOptions:
case AT_AlterColumnGenericOptions:
cmd_lockmode = AccessExclusiveLock;
/*
* These subcommands affect write operations only.
*/
- case AT_ColumnDefault:
- case AT_ProcessedConstraint: /* becomes AT_AddConstraint */
- case AT_AddConstraintRecurse: /* becomes AT_AddConstraint */
- case AT_ReAddConstraint: /* becomes AT_AddConstraint */
case AT_EnableTrig:
case AT_EnableAlwaysTrig:
case AT_EnableReplicaTrig:
case AT_DisableTrig:
case AT_DisableTrigAll:
case AT_DisableTrigUser:
+ cmd_lockmode = ShareRowExclusiveLock;
+ break;
+
+ /*
+ * These subcommands affect write operations only. XXX
+ * Theoretically, these could be ShareRowExclusiveLock.
+ */
+ case AT_ColumnDefault:
+ case AT_AlterConstraint:
case AT_AddIndex: /* from ADD CONSTRAINT */
case AT_AddIndexConstraint:
case AT_ReplicaIdentity:
- cmd_lockmode = ShareRowExclusiveLock;
+ case AT_SetNotNull:
+ case AT_EnableRowSecurity:
+ case AT_DisableRowSecurity:
+ case AT_ForceRowSecurity:
+ case AT_NoForceRowSecurity:
+ cmd_lockmode = AccessExclusiveLock;
break;
case AT_AddConstraint:
+ case AT_ProcessedConstraint: /* becomes AT_AddConstraint */
+ case AT_AddConstraintRecurse: /* becomes AT_AddConstraint */
+ case AT_ReAddConstraint: /* becomes AT_AddConstraint */
if (IsA(cmd->def, Constraint))
{
Constraint *con = (Constraint *) cmd->def;
* Cases essentially the same as CREATE INDEX. We
* could reduce the lock strength to ShareLock if
* we can work out how to allow concurrent catalog
- * updates.
+ * updates. XXX Might be set down to
+ * ShareRowExclusiveLock but requires further
+ * analysis.
*/
- cmd_lockmode = ShareRowExclusiveLock;
+ cmd_lockmode = AccessExclusiveLock;
break;
case CONSTR_FOREIGN:
break;
default:
- cmd_lockmode = ShareRowExclusiveLock;
+ cmd_lockmode = AccessExclusiveLock;
}
}
break;
* started before us will continue to see the old inheritance
* behaviour, while queries started after we commit will see
* new behaviour. No need to prevent reads or writes to the
- * subtable while we hook it up though.
+ * subtable while we hook it up though. Changing the TupDesc
+ * may be a problem, so keep highest lock.
*/
case AT_AddInherit:
case AT_DropInherit:
- cmd_lockmode = ShareUpdateExclusiveLock;
+ cmd_lockmode = AccessExclusiveLock;
break;
/*
* These subcommands affect implicit row type conversion. They
- * have affects similar to CREATE/DROP CAST on queries. We
- * don't provide for invalidating parse trees as a result of
- * such changes. Do avoid concurrent pg_class updates,
- * though.
+ * have affects similar to CREATE/DROP CAST on queries. don't
+ * provide for invalidating parse trees as a result of such
+ * changes, so we keep these at AccessExclusiveLock.
*/
case AT_AddOf:
case AT_DropOf:
- cmd_lockmode = ShareUpdateExclusiveLock;
+ cmd_lockmode = AccessExclusiveLock;
+ break;
+
+ /*
+ * Only used by CREATE OR REPLACE VIEW which must conflict
+ * with an SELECTs currently using the view.
+ */
+ case AT_ReplaceRelOptions:
+ cmd_lockmode = AccessExclusiveLock;
+ break;
/*
* These subcommands affect general strategies for performance
* applies: we don't currently allow concurrent catalog
* updates.
*/
- case AT_SetStatistics:
- case AT_ClusterOn:
- case AT_DropCluster:
- case AT_SetRelOptions:
- case AT_ResetRelOptions:
- case AT_ReplaceRelOptions:
- case AT_SetOptions:
- case AT_ResetOptions:
- case AT_SetStorage:
- case AT_AlterConstraint:
- case AT_ValidateConstraint:
+ case AT_SetStatistics: /* Uses MVCC in getTableAttrs() */
+ case AT_ClusterOn: /* Uses MVCC in getIndexes() */
+ case AT_DropCluster: /* Uses MVCC in getIndexes() */
+ case AT_SetOptions: /* Uses MVCC in getTableAttrs() */
+ case AT_ResetOptions: /* Uses MVCC in getTableAttrs() */
+ cmd_lockmode = ShareUpdateExclusiveLock;
+ break;
+
+ case AT_SetLogged:
+ case AT_SetUnLogged:
+ cmd_lockmode = AccessExclusiveLock;
+ break;
+
+ case AT_ValidateConstraint: /* Uses MVCC in
+ * getConstraints() */
cmd_lockmode = ShareUpdateExclusiveLock;
break;
+ /*
+ * Rel options are more complex than first appears. Options
+ * are set here for tables, views and indexes; for historical
+ * reasons these can all be used with ALTER TABLE, so we can't
+ * decide between them using the basic grammar.
+ */
+ case AT_SetRelOptions: /* Uses MVCC in getIndexes() and
+ * getTables() */
+ case AT_ResetRelOptions: /* Uses MVCC in getIndexes() and
+ * getTables() */
+ cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
+ break;
+
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
if (cmd_lockmode > lockmode)
lockmode = cmd_lockmode;
}
-#else
- LOCKMODE lockmode = AccessExclusiveLock;
-#endif
return lockmode;
}
+/*
+ * ATController provides top level control over the phases.
+ *
+ * parsetree is passed in to allow it to be passed to event triggers
+ * when requested.
+ */
static void
-ATController(Relation rel, List *cmds, bool recurse, LOCKMODE lockmode)
+ATController(AlterTableStmt *parsetree,
+ Relation rel, List *cmds, bool recurse, LOCKMODE lockmode)
{
List *wqueue = NIL;
ListCell *lcmd;
ATRewriteCatalogs(&wqueue, lockmode);
/* Phase 3: scan/rewrite tables as needed */
- ATRewriteTables(&wqueue, lockmode);
+ ATRewriteTables(parsetree, &wqueue, lockmode);
}
/*
case AT_AddColumn: /* ADD COLUMN */
ATSimplePermissions(rel,
ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
- ATPrepAddColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
+ ATPrepAddColumn(wqueue, rel, recurse, recursing, false, cmd,
+ lockmode);
/* Recursion occurs during execution phase */
pass = AT_PASS_ADD_COL;
break;
case AT_AddColumnToView: /* add column via CREATE OR REPLACE
* VIEW */
ATSimplePermissions(rel, ATT_VIEW);
- ATPrepAddColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
+ ATPrepAddColumn(wqueue, rel, recurse, recursing, true, cmd,
+ lockmode);
/* Recursion occurs during execution phase */
pass = AT_PASS_ADD_COL;
break;
pass = AT_PASS_MISC;
break;
case AT_SetStorage: /* ALTER COLUMN SET STORAGE */
- ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
+ ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_MISC;
pass = AT_PASS_ADD_INDEX;
break;
case AT_AddConstraint: /* ADD CONSTRAINT */
- ATSimplePermissions(rel, ATT_TABLE);
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/* Recursion occurs during execution phase */
/* No command-specific prep needed except saving recurse flag */
if (recurse)
pass = AT_PASS_ADD_CONSTR;
break;
case AT_DropConstraint: /* DROP CONSTRAINT */
- ATSimplePermissions(rel, ATT_TABLE);
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/* Recursion occurs during execution phase */
/* No command-specific prep needed except saving recurse flag */
if (recurse)
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
- case AT_AddOids: /* SET WITH OIDS */
+ case AT_SetLogged: /* SET LOGGED */
+ ATSimplePermissions(rel, ATT_TABLE);
+ tab->chgPersistence = ATPrepChangePersistence(rel, true);
+ /* force rewrite if necessary; see comment in ATRewriteTables */
+ if (tab->chgPersistence)
+ {
+ tab->rewrite |= AT_REWRITE_ALTER_PERSISTENCE;
+ tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
+ }
+ pass = AT_PASS_MISC;
+ break;
+ case AT_SetUnLogged: /* SET UNLOGGED */
ATSimplePermissions(rel, ATT_TABLE);
+ tab->chgPersistence = ATPrepChangePersistence(rel, false);
+ /* force rewrite if necessary; see comment in ATRewriteTables */
+ if (tab->chgPersistence)
+ {
+ tab->rewrite |= AT_REWRITE_ALTER_PERSISTENCE;
+ tab->newrelpersistence = RELPERSISTENCE_UNLOGGED;
+ }
+ pass = AT_PASS_MISC;
+ break;
+ case AT_AddOids: /* SET WITH OIDS */
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
if (!rel->rd_rel->relhasoids || recursing)
ATPrepAddOids(wqueue, rel, recurse, cmd, lockmode);
/* Recursion occurs during execution phase */
pass = AT_PASS_ADD_COL;
break;
case AT_DropOids: /* SET WITHOUT OIDS */
- ATSimplePermissions(rel, ATT_TABLE);
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/* Performs own recursion */
if (rel->rd_rel->relhasoids)
{
pass = AT_PASS_MISC;
break;
case AT_AddInherit: /* INHERIT */
- ATSimplePermissions(rel, ATT_TABLE);
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/* This command never recurses */
ATPrepAddInherit(rel);
pass = AT_PASS_MISC;
break;
+ case AT_DropInherit: /* NO INHERIT */
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ /* This command never recurses */
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
case AT_AlterConstraint: /* ALTER CONSTRAINT */
ATSimplePermissions(rel, ATT_TABLE);
pass = AT_PASS_MISC;
break;
case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
- ATSimplePermissions(rel, ATT_TABLE);
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/* Recursion occurs during execution phase */
/* No command-specific prep needed except saving recurse flag */
if (recurse)
cmd->subtype = AT_ValidateConstraintRecurse;
pass = AT_PASS_MISC;
break;
- case AT_ReplicaIdentity: /* REPLICA IDENTITY ... */
+ case AT_ReplicaIdentity: /* REPLICA IDENTITY ... */
ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
pass = AT_PASS_MISC;
/* This command never recurses */
case AT_DisableTrig: /* DISABLE TRIGGER variants */
case AT_DisableTrigAll:
case AT_DisableTrigUser:
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ pass = AT_PASS_MISC;
+ break;
case AT_EnableRule: /* ENABLE/DISABLE RULE variants */
case AT_EnableAlwaysRule:
case AT_EnableReplicaRule:
case AT_DisableRule:
- case AT_DropInherit: /* NO INHERIT */
case AT_AddOf: /* OF */
case AT_DropOf: /* NOT OF */
+ case AT_EnableRowSecurity:
+ case AT_DisableRowSecurity:
+ case AT_ForceRowSecurity:
+ case AT_NoForceRowSecurity:
ATSimplePermissions(rel, ATT_TABLE);
/* These commands never recurse */
/* No command-specific prep needed */
/*
* ATRewriteCatalogs
*
- * Traffic cop for ALTER TABLE Phase 2 operations. Subcommands are
+ * Traffic cop for ALTER TABLE Phase 2 operations. Subcommands are
* dispatched in a "safe" execution order (designed to avoid unnecessary
* conflicts).
*/
if (tab->relkind == RELKIND_RELATION ||
tab->relkind == RELKIND_MATVIEW)
- AlterTableCreateToastTable(tab->relid, (Datum) 0);
+ AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode);
}
}
ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
AlterTableCmd *cmd, LOCKMODE lockmode)
{
+ ObjectAddress address = InvalidObjectAddress;
+
switch (cmd->subtype)
{
case AT_AddColumn: /* ADD COLUMN */
case AT_AddColumnToView: /* add column via CREATE OR REPLACE
* VIEW */
- ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
- false, false, false, lockmode);
+ address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+ false, false, false,
+ false, lockmode);
break;
case AT_AddColumnRecurse:
- ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
- false, true, false, lockmode);
+ address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+ false, true, false,
+ cmd->missing_ok, lockmode);
break;
case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */
- ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
+ address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
break;
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
- ATExecDropNotNull(rel, cmd->name, lockmode);
+ address = ATExecDropNotNull(rel, cmd->name, lockmode);
break;
case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
- ATExecSetNotNull(tab, rel, cmd->name, lockmode);
+ address = ATExecSetNotNull(tab, rel, cmd->name, lockmode);
break;
case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */
- ATExecSetStatistics(rel, cmd->name, cmd->def, lockmode);
+ address = ATExecSetStatistics(rel, cmd->name, cmd->def, lockmode);
break;
case AT_SetOptions: /* ALTER COLUMN SET ( options ) */
- ATExecSetOptions(rel, cmd->name, cmd->def, false, lockmode);
+ address = ATExecSetOptions(rel, cmd->name, cmd->def, false, lockmode);
break;
case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */
- ATExecSetOptions(rel, cmd->name, cmd->def, true, lockmode);
+ address = ATExecSetOptions(rel, cmd->name, cmd->def, true, lockmode);
break;
case AT_SetStorage: /* ALTER COLUMN SET STORAGE */
- ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
+ address = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
break;
case AT_DropColumn: /* DROP COLUMN */
- ATExecDropColumn(wqueue, rel, cmd->name,
- cmd->behavior, false, false, cmd->missing_ok, lockmode);
+ address = ATExecDropColumn(wqueue, rel, cmd->name,
+ cmd->behavior, false, false,
+ cmd->missing_ok, lockmode);
break;
case AT_DropColumnRecurse: /* DROP COLUMN with recursion */
- ATExecDropColumn(wqueue, rel, cmd->name,
- cmd->behavior, true, false, cmd->missing_ok, lockmode);
+ address = ATExecDropColumn(wqueue, rel, cmd->name,
+ cmd->behavior, true, false,
+ cmd->missing_ok, lockmode);
break;
case AT_AddIndex: /* ADD INDEX */
- ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false, lockmode);
+ address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
+ lockmode);
break;
case AT_ReAddIndex: /* ADD INDEX */
- ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true, lockmode);
+ address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true,
+ lockmode);
break;
case AT_AddConstraint: /* ADD CONSTRAINT */
- ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
- false, false, lockmode);
+ address =
+ ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
+ false, false, lockmode);
break;
case AT_AddConstraintRecurse: /* ADD CONSTRAINT with recursion */
- ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
- true, false, lockmode);
+ address =
+ ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
+ true, false, lockmode);
break;
case AT_ReAddConstraint: /* Re-add pre-existing check
* constraint */
- ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
- false, true, lockmode);
+ address =
+ ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
+ true, true, lockmode);
+ break;
+ case AT_ReAddComment: /* Re-add existing comment */
+ address = CommentObject((CommentStmt *) cmd->def);
break;
case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */
- ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def, lockmode);
+ address = ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def,
+ lockmode);
break;
case AT_AlterConstraint: /* ALTER CONSTRAINT */
- ATExecAlterConstraint(rel, cmd, false, false, lockmode);
+ address = ATExecAlterConstraint(rel, cmd, false, false, lockmode);
break;
case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
- ATExecValidateConstraint(rel, cmd->name, false, false, lockmode);
+ address = ATExecValidateConstraint(rel, cmd->name, false, false,
+ lockmode);
break;
case AT_ValidateConstraintRecurse: /* VALIDATE CONSTRAINT with
* recursion */
- ATExecValidateConstraint(rel, cmd->name, true, false, lockmode);
+ address = ATExecValidateConstraint(rel, cmd->name, true, false,
+ lockmode);
break;
case AT_DropConstraint: /* DROP CONSTRAINT */
ATExecDropConstraint(rel, cmd->name, cmd->behavior,
cmd->missing_ok, lockmode);
break;
case AT_AlterColumnType: /* ALTER COLUMN TYPE */
- ATExecAlterColumnType(tab, rel, cmd, lockmode);
+ address = ATExecAlterColumnType(tab, rel, cmd, lockmode);
break;
case AT_AlterColumnGenericOptions: /* ALTER COLUMN OPTIONS */
- ATExecAlterColumnGenericOptions(rel, cmd->name, (List *) cmd->def, lockmode);
+ address =
+ ATExecAlterColumnGenericOptions(rel, cmd->name,
+ (List *) cmd->def, lockmode);
break;
case AT_ChangeOwner: /* ALTER OWNER */
ATExecChangeOwner(RelationGetRelid(rel),
- get_role_oid(cmd->name, false),
+ get_rolespec_oid(cmd->newowner, false),
false, lockmode);
break;
case AT_ClusterOn: /* CLUSTER ON */
- ATExecClusterOn(rel, cmd->name, lockmode);
+ address = ATExecClusterOn(rel, cmd->name, lockmode);
break;
case AT_DropCluster: /* SET WITHOUT CLUSTER */
ATExecDropCluster(rel, lockmode);
break;
+ case AT_SetLogged: /* SET LOGGED */
+ case AT_SetUnLogged: /* SET UNLOGGED */
+ break;
case AT_AddOids: /* SET WITH OIDS */
/* Use the ADD COLUMN code, unless prep decided to do nothing */
if (cmd->def != NULL)
- ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
- true, false, false, lockmode);
+ address =
+ ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+ true, false, false,
+ cmd->missing_ok, lockmode);
break;
case AT_AddOidsRecurse: /* SET WITH OIDS */
/* Use the ADD COLUMN code, unless prep decided to do nothing */
if (cmd->def != NULL)
- ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
- true, true, false, lockmode);
+ address =
+ ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+ true, true, false,
+ cmd->missing_ok, lockmode);
break;
case AT_DropOids: /* SET WITHOUT OIDS */
break;
case AT_AddInherit:
- ATExecAddInherit(rel, (RangeVar *) cmd->def, lockmode);
+ address = ATExecAddInherit(rel, (RangeVar *) cmd->def, lockmode);
break;
case AT_DropInherit:
- ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode);
+ address = ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode);
break;
case AT_AddOf:
- ATExecAddOf(rel, (TypeName *) cmd->def, lockmode);
+ address = ATExecAddOf(rel, (TypeName *) cmd->def, lockmode);
break;
case AT_DropOf:
ATExecDropOf(rel, lockmode);
case AT_ReplicaIdentity:
ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
break;
+ case AT_EnableRowSecurity:
+ ATExecEnableRowSecurity(rel);
+ break;
+ case AT_DisableRowSecurity:
+ ATExecDisableRowSecurity(rel);
+ break;
+ case AT_ForceRowSecurity:
+ ATExecForceNoForceRowSecurity(rel, true);
+ break;
+ case AT_NoForceRowSecurity:
+ ATExecForceNoForceRowSecurity(rel, false);
+ break;
case AT_GenericOptions:
ATExecGenericOptions(rel, (List *) cmd->def);
break;
break;
}
+ /*
+ * Report the subcommand to interested event triggers.
+ */
+ EventTriggerCollectAlterTableSubcmd((Node *) cmd, address);
+
/*
* Bump the command counter to ensure the next subcommand in the sequence
* can see the changes so far
* ATRewriteTables: ALTER TABLE phase 3
*/
static void
-ATRewriteTables(List **wqueue, LOCKMODE lockmode)
+ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
{
ListCell *ltab;
* constraints, so it's not necessary/appropriate to enforce them just
* during ALTER.)
*/
- if (tab->newvals != NIL || tab->rewrite)
+ if (tab->newvals != NIL || tab->rewrite > 0)
{
Relation rel;
/*
* We only need to rewrite the table if at least one column needs to
- * be recomputed, or we are adding/removing the OID column.
+ * be recomputed, we are adding/removing the OID column, or we are
+ * changing its persistence.
+ *
+ * There are two reasons for requiring a rewrite when changing
+ * persistence: on one hand, we need to ensure that the buffers
+ * belonging to each of the two relations are marked with or without
+ * BM_PERMANENT properly. On the other hand, since rewriting creates
+ * and assigns a new relfilenode, we automatically create or drop an
+ * init fork for the relation as appropriate.
*/
- if (tab->rewrite)
+ if (tab->rewrite > 0)
{
/* Build a temporary relation and copy data */
Relation OldHeap;
Oid OIDNewHeap;
Oid NewTableSpace;
+ char persistence;
OldHeap = heap_open(tab->relid, NoLock);
errmsg("cannot rewrite system relation \"%s\"",
RelationGetRelationName(OldHeap))));
+ if (RelationIsUsedAsCatalogTable(OldHeap))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot rewrite table \"%s\" used as a catalog table",
+ RelationGetRelationName(OldHeap))));
+
/*
* Don't allow rewrite on temp tables of other backends ... their
* local buffer manager is not going to cope.
else
NewTableSpace = OldHeap->rd_rel->reltablespace;
+ /*
+ * Select persistence of transient table (same as original unless
+ * user requested a change)
+ */
+ persistence = tab->chgPersistence ?
+ tab->newrelpersistence : OldHeap->rd_rel->relpersistence;
+
heap_close(OldHeap, NoLock);
- /* Create transient table that will receive the modified data */
- OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, false,
- AccessExclusiveLock);
+ /*
+ * Fire off an Event Trigger now, before actually rewriting the
+ * table.
+ *
+ * We don't support Event Trigger for nested commands anywhere,
+ * here included, and parsetree is given NULL when coming from
+ * AlterTableInternal.
+ *
+ * And fire it only once.
+ */
+ if (parsetree)
+ EventTriggerTableRewrite((Node *) parsetree,
+ tab->relid,
+ tab->rewrite);
+
+ /*
+ * Create transient table that will receive the modified data.
+ *
+ * Ensure it is marked correctly as logged or unlogged. We have
+ * to do this here so that buffers for the new relfilenode will
+ * have the right persistence set, and at the same time ensure
+ * that the original filenode's buffers will get read in with the
+ * correct setting (i.e. the original one). Otherwise a rollback
+ * after the rewrite would possibly result with buffers for the
+ * original filenode having the wrong persistence setting.
+ *
+ * NB: This relies on swap_relation_files() also swapping the
+ * persistence. That wouldn't work for pg_class, but that can't be
+ * unlogged anyway.
+ */
+ OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, persistence,
+ lockmode);
/*
* Copy the heap data into the new table with the desired
false, false, true,
!OidIsValid(tab->newTableSpace),
RecentXmin,
- ReadNextMultiXactId());
+ ReadNextMultiXactId(),
+ persistence);
}
else
{
{
/*
* All predicate locks on the tuples or pages are about to be made
- * invalid, because we move tuples around. Promote them to
+ * invalid, because we move tuples around. Promote them to
* relation locks.
*/
TransferPredicateLocksToHeapRelation(oldrel);
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
- if (tab->rewrite)
+ if (tab->rewrite > 0)
{
Oid tupOid = InvalidOid;
HeapTupleSetOid(tuple, tupOid);
/*
- * Constraints might reference the tableoid column, so initialize
- * t_tableOid before evaluating them.
+ * Constraints might reference the tableoid column, so
+ * initialize t_tableOid before evaluating them.
*/
tuple->t_tableOid = RelationGetRelid(oldrel);
}
tab->relid = relid;
tab->relkind = rel->rd_rel->relkind;
tab->oldDesc = CreateTupleDescCopy(RelationGetDescr(rel));
+ tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
+ tab->chgPersistence = false;
*wqueue = lappend(*wqueue, tab);
case ATT_TABLE | ATT_VIEW:
msg = _("\"%s\" is not a table or view");
break;
+ case ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE:
+ msg = _("\"%s\" is not a table, view, or foreign table");
+ break;
case ATT_TABLE | ATT_VIEW | ATT_MATVIEW | ATT_INDEX:
msg = _("\"%s\" is not a table, view, materialized view, or index");
break;
case ATT_TABLE | ATT_MATVIEW | ATT_INDEX:
msg = _("\"%s\" is not a table, materialized view, or index");
break;
+ case ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE:
+ msg = _("\"%s\" is not a table, materialized view, or foreign table");
+ break;
case ATT_TABLE | ATT_FOREIGN_TABLE:
msg = _("\"%s\" is not a table or foreign table");
break;
msg = _("\"%s\" is not a table, composite type, or foreign table");
break;
case ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_FOREIGN_TABLE:
- msg = _("\"%s\" is not a table, materialized view, composite type, or foreign table");
+ msg = _("\"%s\" is not a table, materialized view, index, or foreign table");
break;
case ATT_VIEW:
msg = _("\"%s\" is not a view");
AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode)
{
/*
- * Propagate to children if desired. Non-table relations never have
- * children, so no need to search in that case.
+ * Propagate to children if desired. Only plain tables and foreign tables
+ * have children, so no need to search for other relkinds.
*/
- if (recurse && rel->rd_rel->relkind == RELKIND_RELATION)
+ if (recurse &&
+ (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
*
* Check whether a type is suitable for CREATE TABLE OF/ALTER TABLE OF. If it
* isn't suitable, throw an error. Currently, we require that the type
- * originated with CREATE TYPE AS. We could support any row type, but doing so
+ * originated with CREATE TYPE AS. We could support any row type, but doing so
* would require handling a number of extra corner cases in the DDL commands.
*/
void
/*
* Close the parent rel, but keep our AccessShareLock on it until xact
- * commit. That will prevent someone else from deleting or ALTERing
+ * commit. That will prevent someone else from deleting or ALTERing
* the type before the typed table creation/conversion commits.
*/
relation_close(typeRelation, NoLock);
*/
static void
ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
- AlterTableCmd *cmd, LOCKMODE lockmode)
+ bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode)
{
if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
ATTypedTableRecursion(wqueue, rel, cmd, lockmode);
- if (recurse)
+ if (recurse && !is_view)
cmd->subtype = AT_AddColumnRecurse;
}
-static void
+/*
+ * Add a column to a table; this handles the AT_AddOids cases as well. The
+ * return value is the address of the new column in the parent relation.
+ */
+static ObjectAddress
ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
ColumnDef *colDef, bool isOid,
- bool recurse, bool recursing, LOCKMODE lockmode)
+ bool recurse, bool recursing,
+ bool if_not_exists, LOCKMODE lockmode)
{
Oid myrelid = RelationGetRelid(rel);
Relation pgclass,
List *children;
ListCell *child;
AclResult aclresult;
+ ObjectAddress address;
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
- ATSimplePermissions(rel, ATT_TABLE);
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
colDef->colname, RelationGetRelationName(rel))));
heap_close(attrdesc, RowExclusiveLock);
- return;
+ return InvalidObjectAddress;
}
}
elog(ERROR, "cache lookup failed for relation %u", myrelid);
relkind = ((Form_pg_class) GETSTRUCT(reltup))->relkind;
- /* new name should not already exist */
- check_for_column_name_collision(rel, colDef->colname);
+ /* skip if the name already exists and if_not_exists is true */
+ if (!check_for_column_name_collision(rel, colDef->colname, if_not_exists))
+ {
+ heap_close(attrdesc, RowExclusiveLock);
+ heap_freetuple(reltup);
+ heap_close(pgclass, RowExclusiveLock);
+ return InvalidObjectAddress;
+ }
/* Determine the new attribute's number */
if (isOid)
{
defval = (Expr *) build_column_default(rel, attribute.attnum);
- if (!defval && GetDomainConstraints(typeOid) != NIL)
+ if (!defval && DomainHasConstraints(typeOid))
{
Oid baseTypeId;
int32 baseTypeMod;
newval->expr = expression_planner(defval);
tab->newvals = lappend(tab->newvals, newval);
- tab->rewrite = true;
+ tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
}
/*
* table to fix that.
*/
if (isOid)
- tab->rewrite = true;
+ tab->rewrite |= AT_REWRITE_ALTER_OID;
/*
* Add needed dependency entries for the new column.
/* Find or create work queue entry for this table */
childtab = ATGetQueueEntry(wqueue, childrel);
- /* Recurse to child */
+ /* Recurse to child; return value is ignored */
ATExecAddColumn(wqueue, childtab, childrel,
- colDef, isOid, recurse, true, lockmode);
+ colDef, isOid, recurse, true,
+ if_not_exists, lockmode);
heap_close(childrel, NoLock);
}
+
+ ObjectAddressSubSet(address, RelationRelationId, myrelid, newattnum);
+ return address;
}
/*
* If a new or renamed column will collide with the name of an existing
- * column, error out.
+ * column and if_not_exists is false then error out, else do nothing.
*/
-static void
-check_for_column_name_collision(Relation rel, const char *colname)
+static bool
+check_for_column_name_collision(Relation rel, const char *colname,
+ bool if_not_exists)
{
HeapTuple attTuple;
int attnum;
ObjectIdGetDatum(RelationGetRelid(rel)),
PointerGetDatum(colname));
if (!HeapTupleIsValid(attTuple))
- return;
+ return true;
attnum = ((Form_pg_attribute) GETSTRUCT(attTuple))->attnum;
ReleaseSysCache(attTuple);
errmsg("column name \"%s\" conflicts with a system column name",
colname)));
else
+ {
+ if (if_not_exists)
+ {
+ ereport(NOTICE,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" already exists, skipping",
+ colname, RelationGetRelationName(rel))));
+ return false;
+ }
+
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("column \"%s\" of relation \"%s\" already exists",
colname, RelationGetRelationName(rel))));
+ }
+
+ return true;
}
/*
/*
* ALTER TABLE SET WITH OIDS
*
- * Basically this is an ADD COLUMN for the special OID column. We have
+ * Basically this is an ADD COLUMN for the special OID column. We have
* to cons up a ColumnDef node because the ADD COLUMN code needs one.
*/
static void
cdef->location = -1;
cmd->def = (Node *) cdef;
}
- ATPrepAddColumn(wqueue, rel, recurse, false, cmd, lockmode);
+ ATPrepAddColumn(wqueue, rel, recurse, false, false, cmd, lockmode);
if (recurse)
cmd->subtype = AT_AddOidsRecurse;
/*
* ALTER TABLE ALTER COLUMN DROP NOT NULL
+ *
+ * Return the address of the modified column. If the column was already
+ * nullable, InvalidObjectAddress is returned.
*/
-static void
+static ObjectAddress
ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
{
HeapTuple tuple;
Relation attr_rel;
List *indexoidlist;
ListCell *indexoidscan;
+ ObjectAddress address;
/*
* lookup the attribute
/* keep the system catalog indexes current */
CatalogUpdateIndexes(attr_rel, tuple);
+
+ ObjectAddressSubSet(address, RelationRelationId,
+ RelationGetRelid(rel), attnum);
}
+ else
+ address = InvalidObjectAddress;
InvokeObjectPostAlterHook(RelationRelationId,
RelationGetRelid(rel), attnum);
heap_close(attr_rel, RowExclusiveLock);
+
+ return address;
}
/*
* ALTER TABLE ALTER COLUMN SET NOT NULL
+ *
+ * Return the address of the modified column. If the column was already NOT
+ * NULL, InvalidObjectAddress is returned.
*/
-static void
+static ObjectAddress
ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
const char *colName, LOCKMODE lockmode)
{
HeapTuple tuple;
AttrNumber attnum;
Relation attr_rel;
+ ObjectAddress address;
/*
* lookup the attribute
/* Tell Phase 3 it needs to test the constraint */
tab->new_notnull = true;
+
+ ObjectAddressSubSet(address, RelationRelationId,
+ RelationGetRelid(rel), attnum);
}
+ else
+ address = InvalidObjectAddress;
InvokeObjectPostAlterHook(RelationRelationId,
RelationGetRelid(rel), attnum);
heap_close(attr_rel, RowExclusiveLock);
+
+ return address;
}
/*
* ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
+ *
+ * Return the address of the affected column.
*/
-static void
+static ObjectAddress
ATExecColumnDefault(Relation rel, const char *colName,
Node *newDefault, LOCKMODE lockmode)
{
AttrNumber attnum;
+ ObjectAddress address;
/*
* get the number of the attribute
AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
false, true, false);
}
+
+ ObjectAddressSubSet(address, RelationRelationId,
+ RelationGetRelid(rel), attnum);
+ return address;
}
/*
RelationGetRelationName(rel));
}
-static void
+/*
+ * Return value is the address of the modified column
+ */
+static ObjectAddress
ATExecSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode)
{
int newtarget;
Relation attrelation;
HeapTuple tuple;
Form_pg_attribute attrtuple;
+ AttrNumber attnum;
+ ObjectAddress address;
Assert(IsA(newValue, Integer));
newtarget = intVal(newValue);
colName, RelationGetRelationName(rel))));
attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
- if (attrtuple->attnum <= 0)
+ attnum = attrtuple->attnum;
+ if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
InvokeObjectPostAlterHook(RelationRelationId,
RelationGetRelid(rel),
attrtuple->attnum);
+ ObjectAddressSubSet(address, RelationRelationId,
+ RelationGetRelid(rel), attnum);
heap_freetuple(tuple);
heap_close(attrelation, RowExclusiveLock);
+
+ return address;
}
-static void
+/*
+ * Return value is the address of the modified column
+ */
+static ObjectAddress
ATExecSetOptions(Relation rel, const char *colName, Node *options,
bool isReset, LOCKMODE lockmode)
{
HeapTuple tuple,
newtuple;
Form_pg_attribute attrtuple;
+ AttrNumber attnum;
Datum datum,
newOptions;
bool isnull;
+ ObjectAddress address;
Datum repl_val[Natts_pg_attribute];
bool repl_null[Natts_pg_attribute];
bool repl_repl[Natts_pg_attribute];
colName, RelationGetRelationName(rel))));
attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
- if (attrtuple->attnum <= 0)
+ attnum = attrtuple->attnum;
+ if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
InvokeObjectPostAlterHook(RelationRelationId,
RelationGetRelid(rel),
attrtuple->attnum);
+ ObjectAddressSubSet(address, RelationRelationId,
+ RelationGetRelid(rel), attnum);
+
heap_freetuple(newtuple);
ReleaseSysCache(tuple);
heap_close(attrelation, RowExclusiveLock);
+
+ return address;
}
/*
* ALTER TABLE ALTER COLUMN SET STORAGE
+ *
+ * Return value is the address of the modified column
*/
-static void
+static ObjectAddress
ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode)
{
char *storagemode;
Relation attrelation;
HeapTuple tuple;
Form_pg_attribute attrtuple;
+ AttrNumber attnum;
+ ObjectAddress address;
Assert(IsA(newValue, String));
storagemode = strVal(newValue);
colName, RelationGetRelationName(rel))));
attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
- if (attrtuple->attnum <= 0)
+ attnum = attrtuple->attnum;
+ if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
heap_freetuple(tuple);
heap_close(attrelation, RowExclusiveLock);
+
+ ObjectAddressSubSet(address, RelationRelationId,
+ RelationGetRelid(rel), attnum);
+ return address;
}
*
* DROP COLUMN cannot use the normal ALTER TABLE recursion mechanism,
* because we have to decide at runtime whether to recurse or not depending
- * on whether attinhcount goes to zero or not. (We can't check this in a
+ * on whether attinhcount goes to zero or not. (We can't check this in a
* static pre-pass because it won't handle multiple inheritance situations
* correctly.)
*/
cmd->subtype = AT_DropColumnRecurse;
}
-static void
-ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+/*
+ * Return value is the address of the dropped column.
+ */
+static ObjectAddress
+ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
DropBehavior behavior,
bool recurse, bool recursing,
bool missing_ok, LOCKMODE lockmode)
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
- ATSimplePermissions(rel, ATT_TABLE);
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/*
* get the number of the attribute
ereport(NOTICE,
(errmsg("column \"%s\" of relation \"%s\" does not exist, skipping",
colName, RelationGetRelationName(rel))));
- return;
+ return InvalidObjectAddress;
}
}
targetatt = (Form_pg_attribute) GETSTRUCT(tuple);
tab = ATGetQueueEntry(wqueue, rel);
/* Tell Phase 3 to physically remove the OID column */
- tab->rewrite = true;
+ tab->rewrite |= AT_REWRITE_ALTER_OID;
}
+
+ return object;
}
/*
* There is no such command in the grammar, but parse_utilcmd.c converts
* UNIQUE and PRIMARY KEY constraints into AT_AddIndex subcommands. This lets
* us schedule creation of the index at the appropriate time during ALTER.
+ *
+ * Return value is the address of the new index.
*/
-static void
+static ObjectAddress
ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
bool check_rights;
bool skip_build;
bool quiet;
- Oid new_index;
+ ObjectAddress address;
Assert(IsA(stmt, IndexStmt));
Assert(!stmt->concurrent);
+ /* The IndexStmt has already been through transformIndexStmt */
+ Assert(stmt->transformed);
+
/* suppress schema rights check when rebuilding existing index */
check_rights = !is_rebuild;
/* skip index build if phase 3 will do it or we're reusing an old one */
- skip_build = tab->rewrite || OidIsValid(stmt->oldNode);
+ skip_build = tab->rewrite > 0 || OidIsValid(stmt->oldNode);
/* suppress notices when rebuilding existing index */
quiet = is_rebuild;
- /* The IndexStmt has already been through transformIndexStmt */
-
- new_index = DefineIndex(stmt,
- InvalidOid, /* no predefined OID */
- true, /* is_alter_table */
- check_rights,
- skip_build,
- quiet);
+ address = DefineIndex(RelationGetRelid(rel),
+ stmt,
+ InvalidOid, /* no predefined OID */
+ true, /* is_alter_table */
+ check_rights,
+ skip_build,
+ quiet);
/*
* If TryReuseIndex() stashed a relfilenode for us, we used it for the new
- * index instead of building from scratch. The DROP of the old edition of
+ * index instead of building from scratch. The DROP of the old edition of
* this index will have scheduled the storage for deletion at commit, so
* cancel that pending deletion.
*/
if (OidIsValid(stmt->oldNode))
{
- Relation irel = index_open(new_index, NoLock);
+ Relation irel = index_open(address.objectId, NoLock);
RelationPreserveStorage(irel->rd_node, true);
index_close(irel, NoLock);
}
+
+ return address;
}
/*
* ALTER TABLE ADD CONSTRAINT USING INDEX
+ *
+ * Returns the address of the new constraint.
*/
-static void
+static ObjectAddress
ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
IndexStmt *stmt, LOCKMODE lockmode)
{
IndexInfo *indexInfo;
char *constraintName;
char constraintType;
+ ObjectAddress address;
Assert(IsA(stmt, IndexStmt));
Assert(OidIsValid(index_oid));
elog(ERROR, "index \"%s\" is not unique", indexName);
/*
- * Determine name to assign to constraint. We require a constraint to
+ * Determine name to assign to constraint. We require a constraint to
* have the same name as the underlying index; therefore, use the index's
* existing name as the default constraint name, and if the user
* explicitly gives some other name for the constraint, rename the index
constraintType = CONSTRAINT_UNIQUE;
/* Create the catalog entries for the constraint */
- index_constraint_create(rel,
- index_oid,
- indexInfo,
- constraintName,
- constraintType,
- stmt->deferrable,
- stmt->initdeferred,
- stmt->primary,
- true, /* update pg_index */
- true, /* remove old dependencies */
- allowSystemTableMods,
- false); /* is_internal */
+ address = index_constraint_create(rel,
+ index_oid,
+ indexInfo,
+ constraintName,
+ constraintType,
+ stmt->deferrable,
+ stmt->initdeferred,
+ stmt->primary,
+ true, /* update pg_index */
+ true, /* remove old dependencies */
+ allowSystemTableMods,
+ false); /* is_internal */
index_close(indexRel, NoLock);
+
+ return address;
}
/*
* ALTER TABLE ADD CONSTRAINT
+ *
+ * Return value is the address of the new constraint; if no constraint was
+ * added, InvalidObjectAddress is returned.
*/
-static void
+static ObjectAddress
ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
Constraint *newConstraint, bool recurse, bool is_readd,
LOCKMODE lockmode)
{
+ ObjectAddress address = InvalidObjectAddress;
+
Assert(IsA(newConstraint, Constraint));
/*
switch (newConstraint->contype)
{
case CONSTR_CHECK:
- ATAddCheckConstraint(wqueue, tab, rel,
- newConstraint, recurse, false, is_readd,
- lockmode);
+ address =
+ ATAddCheckConstraint(wqueue, tab, rel,
+ newConstraint, recurse, false, is_readd,
+ lockmode);
break;
case CONSTR_FOREIGN:
RelationGetNamespace(rel),
NIL);
- ATAddForeignKeyConstraint(tab, rel, newConstraint, lockmode);
+ address = ATAddForeignKeyConstraint(tab, rel, newConstraint,
+ lockmode);
break;
default:
elog(ERROR, "unrecognized constraint type: %d",
(int) newConstraint->contype);
}
+
+ return address;
}
/*
- * Add a check constraint to a single table and its children
+ * Add a check constraint to a single table and its children. Returns the
+ * address of the constraint added to the parent relation, if one gets added,
+ * or InvalidObjectAddress otherwise.
*
* Subroutine for ATExecAddConstraint.
*
* AddRelationNewConstraints would normally assign different names to the
* child constraints. To fix that, we must capture the name assigned at
* the parent table and pass that down.
- *
- * When re-adding a previously existing constraint (during ALTER COLUMN TYPE),
- * we don't need to recurse here, because recursion will be carried out at a
- * higher level; the constraint name issue doesn't apply because the names
- * have already been assigned and are just being re-used. We need a separate
- * "is_readd" flag for that; just setting recurse=false would result in an
- * error if there are child tables.
*/
-static void
+static ObjectAddress
ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
Constraint *constr, bool recurse, bool recursing,
bool is_readd, LOCKMODE lockmode)
ListCell *lcon;
List *children;
ListCell *child;
+ ObjectAddress address = InvalidObjectAddress;
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
- ATSimplePermissions(rel, ATT_TABLE);
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/*
* Call AddRelationNewConstraints to do the work, making sure it works on
*/
newcons = AddRelationNewConstraints(rel, NIL,
list_make1(copyObject(constr)),
- recursing, /* allow_merge */
+ recursing | is_readd, /* allow_merge */
!recursing, /* is_local */
is_readd); /* is_internal */
+ /* we don't expect more than one constraint here */
+ Assert(list_length(newcons) <= 1);
+
/* Add each to-be-validated constraint to Phase 3's queue */
foreach(lcon, newcons)
{
/* Save the actually assigned name if it was defaulted */
if (constr->conname == NULL)
constr->conname = ccon->name;
+
+ ObjectAddressSet(address, ConstraintRelationId, ccon->conoid);
}
/* At this point we must have a locked-down name to use */
* incorrect value for coninhcount.
*/
if (newcons == NIL)
- return;
+ return address;
/*
* If adding a NO INHERIT constraint, no need to find our children.
- * Likewise, in a re-add operation, we don't need to recurse (that will be
- * handled at higher levels).
*/
- if (constr->is_no_inherit || is_readd)
- return;
+ if (constr->is_no_inherit)
+ return address;
/*
* Propagate to children as appropriate. Unlike most other ALTER
/*
* Check if ONLY was specified with ALTER TABLE. If so, allow the
- * contraint creation only if there are no children currently. Error out
+ * contraint creation only if there are no children currently. Error out
* otherwise.
*/
if (!recurse && children != NIL)
heap_close(childrel, NoLock);
}
+
+ return address;
}
/*
- * Add a foreign-key constraint to a single table
+ * Add a foreign-key constraint to a single table; return the new constraint's
+ * address.
*
- * Subroutine for ATExecAddConstraint. Must already hold exclusive
+ * Subroutine for ATExecAddConstraint. Must already hold exclusive
* lock on the rel, and have done appropriate validity checks for it.
* We do permissions checks here, however.
*/
-static void
+static ObjectAddress
ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
Constraint *fkconstraint, LOCKMODE lockmode)
{
Oid indexOid;
Oid constrOid;
bool old_check_ok;
+ ObjectAddress address;
ListCell *old_pfeqop_item = list_head(fkconstraint->old_conpfeqop);
/*
- * Grab an exclusive lock on the pk table, so that someone doesn't delete
- * rows out from under us. (Although a lesser lock would do for that
- * purpose, we'll need exclusive lock anyway to add triggers to the pk
- * table; trying to start with a lesser lock will just create a risk of
- * deadlock.)
+ * Grab ShareRowExclusiveLock on the pk table, so that someone doesn't
+ * delete rows out from under us.
*/
- pkrel = heap_openrv(fkconstraint->pktable, AccessExclusiveLock);
+ if (OidIsValid(fkconstraint->old_pktable_oid))
+ pkrel = heap_open(fkconstraint->old_pktable_oid, ShareRowExclusiveLock);
+ else
+ pkrel = heap_openrv(fkconstraint->pktable, ShareRowExclusiveLock);
/*
* Validity checks (permission checks wait till we have the column
*
* Note that we have to be careful about the difference between the actual
* PK column type and the opclass' declared input type, which might be
- * only binary-compatible with it. The declared opcintype is the right
+ * only binary-compatible with it. The declared opcintype is the right
* thing to probe pg_amop with.
*/
if (numfks != numpks)
/*
* Upon a change to the cast from the FK column to its pfeqop
- * operand, revalidate the constraint. For this evaluation, a
+ * operand, revalidate the constraint. For this evaluation, a
* binary coercion cast is equivalent to no cast at all. While
* type implementors should design implicit casts with an eye
* toward consistency of operations like equality, we cannot
* Necessarily, the primary key column must then be of the domain
* type. Since the constraint was previously valid, all values on
* the foreign side necessarily exist on the primary side and in
- * turn conform to the domain. Consequently, we need not treat
+ * turn conform to the domain. Consequently, we need not treat
* domains specially here.
*
* Since we require that all collations share the same notion of
* We need not directly consider the PK type. It's necessarily
* binary coercible to the opcintype of the unique index column,
* and ri_triggers.c will only deal with PK datums in terms of
- * that opcintype. Changing the opcintype also changes pfeqop.
+ * that opcintype. Changing the opcintype also changes pfeqop.
*/
old_check_ok = (new_pathtype == old_pathtype &&
new_castfunc == old_castfunc &&
0, /* inhcount */
true, /* isnoinherit */
false); /* is_internal */
+ ObjectAddressSet(address, ConstraintRelationId, constrOid);
/*
* Create the triggers that will enforce the constraint.
*/
- createForeignKeyTriggers(rel, fkconstraint, constrOid, indexOid);
+ createForeignKeyTriggers(rel, RelationGetRelid(pkrel), fkconstraint,
+ constrOid, indexOid);
/*
* Tell Phase 3 to check that the constraint is satisfied by existing
* Close pk table, but keep lock until we've committed.
*/
heap_close(pkrel, NoLock);
+
+ return address;
}
/*
* Foreign keys do not inherit, so we purposely ignore the
* recursion bit here, but we keep the API the same for when
* other constraint types are supported.
+ *
+ * If the constraint is modified, returns its address; otherwise, return
+ * InvalidObjectAddress.
*/
-static void
+static ObjectAddress
ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd,
- bool recurse, bool recursing, LOCKMODE lockmode)
+ bool recurse, bool recursing, LOCKMODE lockmode)
{
+ Constraint *cmdcon;
Relation conrel;
SysScanDesc scan;
ScanKeyData key;
HeapTuple contuple;
Form_pg_constraint currcon = NULL;
- Constraint *cmdcon = NULL;
bool found = false;
+ ObjectAddress address;
Assert(IsA(cmd->def, Constraint));
cmdcon = (Constraint *) cmd->def;
HeapTuple copyTuple;
HeapTuple tgtuple;
Form_pg_constraint copy_con;
- Form_pg_trigger copy_tg;
+ List *otherrelids = NIL;
ScanKeyData tgkey;
SysScanDesc tgscan;
Relation tgrel;
+ ListCell *lc;
/*
* Now update the catalog, while we have the door open.
heap_freetuple(copyTuple);
/*
- * Now we need to update the multiple entries in pg_trigger
- * that implement the constraint.
+ * Now we need to update the multiple entries in pg_trigger that
+ * implement the constraint.
*/
tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
while (HeapTupleIsValid(tgtuple = systable_getnext(tgscan)))
{
+ Form_pg_trigger copy_tg;
+
copyTuple = heap_copytuple(tgtuple);
copy_tg = (Form_pg_trigger) GETSTRUCT(copyTuple);
+
+ /* Remember OIDs of other relation(s) involved in FK constraint */
+ if (copy_tg->tgrelid != RelationGetRelid(rel))
+ otherrelids = list_append_unique_oid(otherrelids,
+ copy_tg->tgrelid);
+
copy_tg->tgdeferrable = cmdcon->deferrable;
copy_tg->tginitdeferred = cmdcon->initdeferred;
simple_heap_update(tgrel, ©Tuple->t_self, copyTuple);
CatalogUpdateIndexes(tgrel, copyTuple);
InvokeObjectPostAlterHook(TriggerRelationId,
- HeapTupleGetOid(tgtuple), 0);
+ HeapTupleGetOid(tgtuple), 0);
heap_freetuple(copyTuple);
}
heap_close(tgrel, RowExclusiveLock);
/*
- * Invalidate relcache so that others see the new attributes.
+ * Invalidate relcache so that others see the new attributes. We must
+ * inval both the named rel and any others having relevant triggers.
+ * (At present there should always be exactly one other rel, but
+ * there's no need to hard-wire such an assumption here.)
*/
CacheInvalidateRelcache(rel);
+ foreach(lc, otherrelids)
+ {
+ CacheInvalidateRelcacheByRelid(lfirst_oid(lc));
+ }
+
+ ObjectAddressSet(address, ConstraintRelationId,
+ HeapTupleGetOid(contuple));
}
+ else
+ address = InvalidObjectAddress;
systable_endscan(scan);
heap_close(conrel, RowExclusiveLock);
+
+ return address;
}
/*
* there's no good way to skip recursing when handling foreign keys: there is
* no need to lock children in that case, yet we wouldn't be able to avoid
* doing so at that level.
+ *
+ * Return value is the address of the validated constraint. If the constraint
+ * was already validated, InvalidObjectAddress is returned.
*/
-static void
+static ObjectAddress
ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
bool recursing, LOCKMODE lockmode)
{
HeapTuple tuple;
Form_pg_constraint con = NULL;
bool found = false;
+ ObjectAddress address;
conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
if (con->contype == CONSTRAINT_FOREIGN)
{
- Oid conid = HeapTupleGetOid(tuple);
Relation refrel;
/*
validateForeignKeyConstraint(constrName, rel, refrel,
con->conindid,
- conid);
+ HeapTupleGetOid(tuple));
heap_close(refrel, NoLock);
/*
HeapTupleGetOid(tuple), 0);
heap_freetuple(copyTuple);
+
+ ObjectAddressSet(address, ConstraintRelationId,
+ HeapTupleGetOid(tuple));
}
+ else
+ address = InvalidObjectAddress; /* already validated */
systable_endscan(scan);
heap_close(conrel, RowExclusiveLock);
+
+ return address;
}
* transformFkeyGetPrimaryKey -
*
* Look up the names, attnums, and types of the primary key attributes
- * for the pkrel. Also return the index OID and index opclasses of the
+ * for the pkrel. Also return the index OID and index opclasses of the
* index supporting the primary key.
*
- * All parameters except pkrel are output parameters. Also, the function
+ * All parameters except pkrel are output parameters. Also, the function
* return value is the number of attributes in the primary key.
*
* Used when the column list in the REFERENCES specification is omitted.
if (indexStruct->indisprimary && IndexIsValid(indexStruct))
{
/*
- * Refuse to use a deferrable primary key. This is per SQL spec,
+ * Refuse to use a deferrable primary key. This is per SQL spec,
* and there would be a lot of interesting semantic problems if we
* tried to allow it.
*/
bool found_deferrable = false;
List *indexoidlist;
ListCell *indexoidscan;
+ int i,
+ j;
+
+ /*
+ * Reject duplicate appearances of columns in the referenced-columns list.
+ * Such a case is forbidden by the SQL standard, and even if we thought it
+ * useful to allow it, there would be ambiguity about how to match the
+ * list to unique indexes (in particular, it'd be unclear which index
+ * opclass goes with which FK column).
+ */
+ for (i = 0; i < numattrs; i++)
+ {
+ for (j = i + 1; j < numattrs; j++)
+ {
+ if (attnums[i] == attnums[j])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FOREIGN_KEY),
+ errmsg("foreign key referenced-columns list must not contain duplicates")));
+ }
+ }
/*
* Get the list of index OIDs for the table from the relcache, and look up
{
HeapTuple indexTuple;
Form_pg_index indexStruct;
- int i,
- j;
indexoid = lfirst_oid(indexoidscan);
indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
heap_attisnull(indexTuple, Anum_pg_index_indpred) &&
heap_attisnull(indexTuple, Anum_pg_index_indexprs))
{
- /* Must get indclass the hard way */
Datum indclassDatum;
bool isnull;
oidvector *indclass;
+ /* Must get indclass the hard way */
indclassDatum = SysCacheGetAttr(INDEXRELID, indexTuple,
Anum_pg_index_indclass, &isnull);
Assert(!isnull);
/*
* The given attnum list may match the index columns in any order.
- * Check that each list is a subset of the other.
+ * Check for a match, and extract the appropriate opclasses while
+ * we're at it.
+ *
+ * We know that attnums[] is duplicate-free per the test at the
+ * start of this function, and we checked above that the number of
+ * index columns agrees, so if we find a match for each attnums[]
+ * entry then we must have a one-to-one match in some order.
*/
for (i = 0; i < numattrs; i++)
{
{
if (attnums[i] == indexStruct->indkey.values[j])
{
+ opclasses[i] = indclass->values[j];
found = true;
break;
}
if (!found)
break;
}
- if (found)
- {
- for (i = 0; i < numattrs; i++)
- {
- found = false;
- for (j = 0; j < numattrs; j++)
- {
- if (attnums[j] == indexStruct->indkey.values[i])
- {
- opclasses[j] = indclass->values[i];
- found = true;
- break;
- }
- }
- if (!found)
- break;
- }
- }
/*
* Refuse to use a deferrable unique/primary key. This is per SQL
bool isnull;
Snapshot snapshot;
+ /* VALIDATE CONSTRAINT is a no-op for foreign tables */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ return;
+
constrForm = (Form_pg_constraint) GETSTRUCT(constrtup);
estate = CreateExecutorState();
}
static void
-CreateFKCheckTrigger(RangeVar *myRel, Constraint *fkconstraint,
+CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
Oid constraintOid, Oid indexOid, bool on_insert)
{
CreateTrigStmt *fk_trigger;
*/
fk_trigger = makeNode(CreateTrigStmt);
fk_trigger->trigname = "RI_ConstraintTrigger_c";
- fk_trigger->relation = myRel;
+ fk_trigger->relation = NULL;
fk_trigger->row = true;
fk_trigger->timing = TRIGGER_TYPE_AFTER;
fk_trigger->isconstraint = true;
fk_trigger->deferrable = fkconstraint->deferrable;
fk_trigger->initdeferred = fkconstraint->initdeferred;
- fk_trigger->constrrel = fkconstraint->pktable;
+ fk_trigger->constrrel = NULL;
fk_trigger->args = NIL;
- (void) CreateTrigger(fk_trigger, NULL, constraintOid, indexOid, true);
+ (void) CreateTrigger(fk_trigger, NULL, myRelOid, refRelOid, constraintOid,
+ indexOid, true);
/* Make changes-so-far visible */
CommandCounterIncrement();
* Create the triggers that implement an FK constraint.
*/
static void
-createForeignKeyTriggers(Relation rel, Constraint *fkconstraint,
+createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint,
Oid constraintOid, Oid indexOid)
{
- RangeVar *myRel;
+ Oid myRelOid;
CreateTrigStmt *fk_trigger;
- /*
- * Reconstruct a RangeVar for my relation (not passed in, unfortunately).
- */
- myRel = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
- pstrdup(RelationGetRelationName(rel)),
- -1);
+ myRelOid = RelationGetRelid(rel);
/* Make changes-so-far visible */
CommandCounterIncrement();
*/
fk_trigger = makeNode(CreateTrigStmt);
fk_trigger->trigname = "RI_ConstraintTrigger_a";
- fk_trigger->relation = fkconstraint->pktable;
+ fk_trigger->relation = NULL;
fk_trigger->row = true;
fk_trigger->timing = TRIGGER_TYPE_AFTER;
fk_trigger->events = TRIGGER_TYPE_DELETE;
fk_trigger->columns = NIL;
fk_trigger->whenClause = NULL;
fk_trigger->isconstraint = true;
- fk_trigger->constrrel = myRel;
+ fk_trigger->constrrel = NULL;
switch (fkconstraint->fk_del_action)
{
case FKCONSTR_ACTION_NOACTION:
}
fk_trigger->args = NIL;
- (void) CreateTrigger(fk_trigger, NULL, constraintOid, indexOid, true);
+ (void) CreateTrigger(fk_trigger, NULL, refRelOid, myRelOid, constraintOid,
+ indexOid, true);
/* Make changes-so-far visible */
CommandCounterIncrement();
*/
fk_trigger = makeNode(CreateTrigStmt);
fk_trigger->trigname = "RI_ConstraintTrigger_a";
- fk_trigger->relation = fkconstraint->pktable;
+ fk_trigger->relation = NULL;
fk_trigger->row = true;
fk_trigger->timing = TRIGGER_TYPE_AFTER;
fk_trigger->events = TRIGGER_TYPE_UPDATE;
fk_trigger->columns = NIL;
fk_trigger->whenClause = NULL;
fk_trigger->isconstraint = true;
- fk_trigger->constrrel = myRel;
+ fk_trigger->constrrel = NULL;
switch (fkconstraint->fk_upd_action)
{
case FKCONSTR_ACTION_NOACTION:
}
fk_trigger->args = NIL;
- (void) CreateTrigger(fk_trigger, NULL, constraintOid, indexOid, true);
+ (void) CreateTrigger(fk_trigger, NULL, refRelOid, myRelOid, constraintOid,
+ indexOid, true);
/* Make changes-so-far visible */
CommandCounterIncrement();
* Build and execute CREATE CONSTRAINT TRIGGER statements for the CHECK
* action for both INSERTs and UPDATEs on the referencing table.
*/
- CreateFKCheckTrigger(myRel, fkconstraint, constraintOid, indexOid, true);
- CreateFKCheckTrigger(myRel, fkconstraint, constraintOid, indexOid, false);
+ CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint, constraintOid,
+ indexOid, true);
+ CreateFKCheckTrigger(myRelOid, refRelOid, fkconstraint, constraintOid,
+ indexOid, false);
}
/*
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
- ATSimplePermissions(rel, ATT_TABLE);
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
char *colName = cmd->name;
ColumnDef *def = (ColumnDef *) cmd->def;
TypeName *typeName = def->typeName;
- Node *transform = def->raw_default;
+ Node *transform = def->cooked_default;
HeapTuple tuple;
Form_pg_attribute attTup;
AttrNumber attnum;
{
/*
* Set up an expression to transform the old data value to the new
- * type. If a USING option was given, transform and use that
- * expression, else just take the old value and try to coerce it. We
- * do this first so that type incompatibility can be detected before
- * we waste effort, and because we need the expression to be parsed
- * against the original table row type.
+ * type. If a USING option was given, use the expression as
+ * transformed by transformAlterTableStmt, else just take the old
+ * value and try to coerce it. We do this first so that type
+ * incompatibility can be detected before we waste effort, and because
+ * we need the expression to be parsed against the original table row
+ * type.
*/
- if (transform)
- {
- RangeTblEntry *rte;
-
- /* Expression must be able to access vars of old table */
- rte = addRangeTableEntryForRelation(pstate,
- rel,
- NULL,
- false,
- true);
- addRTEtoQuery(pstate, rte, false, true, true);
-
- transform = transformExpr(pstate, transform,
- EXPR_KIND_ALTER_COL_TRANSFORM);
-
- /* It can't return a set */
- if (expression_returns_set(transform))
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("transform expression must not return a set")));
- }
- else
+ if (!transform)
{
transform = (Node *) makeVar(1, attnum,
attTup->atttypid, attTup->atttypmod,
COERCE_IMPLICIT_CAST,
-1);
if (transform == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("column \"%s\" cannot be cast automatically to type %s",
- colName, format_type_be(targettype)),
- errhint("Specify a USING expression to perform the conversion.")));
+ {
+ /* error text depends on whether USING was specified or not */
+ if (def->cooked_default != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("result of USING clause for column \"%s\""
+ " cannot be cast automatically to type %s",
+ colName, format_type_be(targettype)),
+ errhint("You might need to add an explicit cast.")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("column \"%s\" cannot be cast automatically to type %s",
+ colName, format_type_be(targettype)),
+ /* translator: USING is SQL, don't translate it */
+ errhint("You might need to specify \"USING %s::%s\".",
+ quote_identifier(colName),
+ format_type_with_typemod(targettype,
+ targettypmod))));
+ }
/* Fix collations after all else */
assign_expr_collations(pstate, transform);
tab->newvals = lappend(tab->newvals, newval);
if (ATColumnChangeRequiresRewrite(transform, attnum))
- tab->rewrite = true;
+ tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
}
else if (transform)
ereport(ERROR,
tab->relkind == RELKIND_FOREIGN_TABLE)
{
/*
- * For composite types, do this check now. Tables will check it later
+ * For composite types, do this check now. Tables will check it later
* when the table is being rewritten.
*/
find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL);
ReleaseSysCache(tuple);
/*
- * The recursion case is handled by ATSimpleRecursion. However, if we are
+ * The recursion case is handled by ATSimpleRecursion. However, if we are
* told not to recurse, there had better not be any child tables; else the
* alter would put them out of step.
*/
{
CoerceToDomain *d = (CoerceToDomain *) expr;
- if (GetDomainConstraints(d->resulttype) != NIL)
+ if (DomainHasConstraints(d->resulttype))
return true;
expr = (Node *) d->arg;
}
}
}
-static void
+/*
+ * ALTER COLUMN .. SET DATA TYPE
+ *
+ * Return the address of the modified column.
+ */
+static ObjectAddress
ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
AlterTableCmd *cmd, LOCKMODE lockmode)
{
ScanKeyData key[3];
SysScanDesc scan;
HeapTuple depTup;
+ ObjectAddress address;
attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
*
* We remove any implicit coercion steps at the top level of the old
* default expression; this has been agreed to satisfy the principle of
- * least surprise. (The conversion to the new column type should act like
+ * least surprise. (The conversion to the new column type should act like
* it started from what the user sees as the stored expression, and the
* implicit coercions aren't going to be shown.)
*/
* and record enough information to let us recreate the objects.
*
* The actual recreation does not happen here, but only after we have
- * performed all the individual ALTER TYPE operations. We have to save
+ * performed all the individual ALTER TYPE operations. We have to save
* the info before executing ALTER TYPE, though, else the deparser will
* get confused.
*
if (!list_member_oid(tab->changedConstraintOids,
foundObject.objectId))
{
- char *defstring = pg_get_constraintdef_string(foundObject.objectId);
+ char *defstring = pg_get_constraintdef_command(foundObject.objectId);
/*
* Put NORMAL dependencies at the front of the list and
* used in the trigger's WHEN condition. The first case would
* not require any extra work, but the second case would
* require updating the WHEN expression, which will take a
- * significant amount of new code. Since we can't easily tell
+ * significant amount of new code. Since we can't easily tell
* which case applies, we punt for both. FIXME someday.
*/
ereport(ERROR,
colName)));
break;
+ case OCLASS_POLICY:
+
+ /*
+ * A policy can depend on a column because the column is
+ * specified in the policy's USING or WITH CHECK qual
+ * expressions. It might be possible to rewrite and recheck
+ * the policy expression, but punt for now. It's certainly
+ * easy enough to remove and recreate the policy; still, FIXME
+ * someday.
+ */
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot alter type of a column used in a policy definition"),
+ errdetail("%s depends on column \"%s\"",
+ getObjectDescription(&foundObject),
+ colName)));
+ break;
+
case OCLASS_DEFAULT:
/*
StoreAttrDefault(rel, attnum, defaultexpr, true);
}
+ ObjectAddressSubSet(address, RelationRelationId,
+ RelationGetRelid(rel), attnum);
+
/* Cleanup */
heap_freetuple(heapTup);
+
+ return address;
}
-static void
+/*
+ * Returns the address of the modified column
+ */
+static ObjectAddress
ATExecAlterColumnGenericOptions(Relation rel,
const char *colName,
List *options,
Datum datum;
Form_pg_foreign_table fttableform;
Form_pg_attribute atttableform;
+ AttrNumber attnum;
+ ObjectAddress address;
if (options == NIL)
- return;
+ return InvalidObjectAddress;
/* First, determine FDW validator associated to the foreign table. */
ftrel = heap_open(ForeignTableRelationId, AccessShareLock);
/* Prevent them from altering a system attribute */
atttableform = (Form_pg_attribute) GETSTRUCT(tuple);
- if (atttableform->attnum <= 0)
+ attnum = atttableform->attnum;
+ if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"", colName)));
InvokeObjectPostAlterHook(RelationRelationId,
RelationGetRelid(rel),
atttableform->attnum);
+ ObjectAddressSubSet(address, RelationRelationId,
+ RelationGetRelid(rel), attnum);
ReleaseSysCache(tuple);
heap_close(attrel, RowExclusiveLock);
heap_freetuple(newtuple);
+
+ return address;
}
/*
/*
* Re-parse the index and constraint definitions, and attach them to the
- * appropriate work queue entries. We do this before dropping because in
+ * appropriate work queue entries. We do this before dropping because in
* the case of a FOREIGN KEY constraint, we might not yet have exclusive
* lock on the table the constraint is attached to, and we need to get
* that before dropping. It's safe because the parser won't actually look
* at the catalogs to detect the existing entry.
+ *
+ * We can't rely on the output of deparsing to tell us which relation to
+ * operate on, because concurrent activity might have made the name
+ * resolve differently. Instead, we've got to use the OID of the
+ * constraint or index we're processing to figure out which relation to
+ * operate on.
*/
forboth(oid_item, tab->changedConstraintOids,
def_item, tab->changedConstraintDefs)
- ATPostAlterTypeParse(lfirst_oid(oid_item), (char *) lfirst(def_item),
+ {
+ Oid oldId = lfirst_oid(oid_item);
+ HeapTuple tup;
+ Form_pg_constraint con;
+ Oid relid;
+ Oid confrelid;
+ bool conislocal;
+
+ tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(oldId));
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for constraint %u", oldId);
+ con = (Form_pg_constraint) GETSTRUCT(tup);
+ relid = con->conrelid;
+ confrelid = con->confrelid;
+ conislocal = con->conislocal;
+ ReleaseSysCache(tup);
+
+ /*
+ * If the constraint is inherited (only), we don't want to inject a
+ * new definition here; it'll get recreated when ATAddCheckConstraint
+ * recurses from adding the parent table's constraint. But we had to
+ * carry the info this far so that we can drop the constraint below.
+ */
+ if (!conislocal)
+ continue;
+
+ ATPostAlterTypeParse(oldId, relid, confrelid,
+ (char *) lfirst(def_item),
wqueue, lockmode, tab->rewrite);
+ }
forboth(oid_item, tab->changedIndexOids,
def_item, tab->changedIndexDefs)
- ATPostAlterTypeParse(lfirst_oid(oid_item), (char *) lfirst(def_item),
+ {
+ Oid oldId = lfirst_oid(oid_item);
+ Oid relid;
+
+ relid = IndexGetRelation(oldId, false);
+ ATPostAlterTypeParse(oldId, relid, InvalidOid,
+ (char *) lfirst(def_item),
wqueue, lockmode, tab->rewrite);
+ }
/*
* Now we can drop the existing constraints and indexes --- constraints
}
static void
-ATPostAlterTypeParse(Oid oldId, char *cmd,
+ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
List **wqueue, LOCKMODE lockmode, bool rewrite)
{
List *raw_parsetree_list;
List *querytree_list;
ListCell *list_item;
+ Relation rel;
/*
* We expect that we will get only ALTER TABLE and CREATE INDEX
if (IsA(stmt, IndexStmt))
querytree_list = lappend(querytree_list,
- transformIndexStmt((IndexStmt *) stmt,
+ transformIndexStmt(oldRelId,
+ (IndexStmt *) stmt,
cmd));
else if (IsA(stmt, AlterTableStmt))
querytree_list = list_concat(querytree_list,
- transformAlterTableStmt((AlterTableStmt *) stmt,
- cmd));
+ transformAlterTableStmt(oldRelId,
+ (AlterTableStmt *) stmt,
+ cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
+ /* Caller should already have acquired whatever lock we need. */
+ rel = relation_open(oldRelId, NoLock);
+
/*
* Attach each generated command to the proper place in the work queue.
* Note this could result in creation of entirely new work-queue entries.
foreach(list_item, querytree_list)
{
Node *stm = (Node *) lfirst(list_item);
- Relation rel;
AlteredTableInfo *tab;
- switch (nodeTag(stm))
+ tab = ATGetQueueEntry(wqueue, rel);
+
+ if (IsA(stm, IndexStmt))
+ {
+ IndexStmt *stmt = (IndexStmt *) stm;
+ AlterTableCmd *newcmd;
+
+ if (!rewrite)
+ TryReuseIndex(oldId, stmt);
+ /* keep the index's comment */
+ stmt->idxcomment = GetComment(oldId, RelationRelationId, 0);
+
+ newcmd = makeNode(AlterTableCmd);
+ newcmd->subtype = AT_ReAddIndex;
+ newcmd->def = (Node *) stmt;
+ tab->subcmds[AT_PASS_OLD_INDEX] =
+ lappend(tab->subcmds[AT_PASS_OLD_INDEX], newcmd);
+ }
+ else if (IsA(stm, AlterTableStmt))
{
- case T_IndexStmt:
+ AlterTableStmt *stmt = (AlterTableStmt *) stm;
+ ListCell *lcmd;
+
+ foreach(lcmd, stmt->cmds)
+ {
+ AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
+
+ if (cmd->subtype == AT_AddIndex)
{
- IndexStmt *stmt = (IndexStmt *) stm;
- AlterTableCmd *newcmd;
+ IndexStmt *indstmt;
+ Oid indoid;
+
+ Assert(IsA(cmd->def, IndexStmt));
+
+ indstmt = (IndexStmt *) cmd->def;
+ indoid = get_constraint_index(oldId);
if (!rewrite)
- TryReuseIndex(oldId, stmt);
+ TryReuseIndex(indoid, indstmt);
+ /* keep any comment on the index */
+ indstmt->idxcomment = GetComment(indoid,
+ RelationRelationId, 0);
- rel = relation_openrv(stmt->relation, lockmode);
- tab = ATGetQueueEntry(wqueue, rel);
- newcmd = makeNode(AlterTableCmd);
- newcmd->subtype = AT_ReAddIndex;
- newcmd->def = (Node *) stmt;
+ cmd->subtype = AT_ReAddIndex;
tab->subcmds[AT_PASS_OLD_INDEX] =
- lappend(tab->subcmds[AT_PASS_OLD_INDEX], newcmd);
- relation_close(rel, NoLock);
- break;
+ lappend(tab->subcmds[AT_PASS_OLD_INDEX], cmd);
+
+ /* recreate any comment on the constraint */
+ RebuildConstraintComment(tab,
+ AT_PASS_OLD_INDEX,
+ oldId,
+ rel, indstmt->idxname);
}
- case T_AlterTableStmt:
+ else if (cmd->subtype == AT_AddConstraint)
{
- AlterTableStmt *stmt = (AlterTableStmt *) stm;
- ListCell *lcmd;
-
- rel = relation_openrv(stmt->relation, lockmode);
- tab = ATGetQueueEntry(wqueue, rel);
- foreach(lcmd, stmt->cmds)
- {
- AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
- Constraint *con;
-
- switch (cmd->subtype)
- {
- case AT_AddIndex:
- Assert(IsA(cmd->def, IndexStmt));
- if (!rewrite)
- TryReuseIndex(get_constraint_index(oldId),
- (IndexStmt *) cmd->def);
- cmd->subtype = AT_ReAddIndex;
- tab->subcmds[AT_PASS_OLD_INDEX] =
- lappend(tab->subcmds[AT_PASS_OLD_INDEX], cmd);
- break;
- case AT_AddConstraint:
- Assert(IsA(cmd->def, Constraint));
- con = (Constraint *) cmd->def;
- /* rewriting neither side of a FK */
- if (con->contype == CONSTR_FOREIGN &&
- !rewrite && !tab->rewrite)
- TryReuseForeignKey(oldId, con);
- cmd->subtype = AT_ReAddConstraint;
- tab->subcmds[AT_PASS_OLD_CONSTR] =
- lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
- break;
- default:
- elog(ERROR, "unexpected statement type: %d",
- (int) cmd->subtype);
- }
- }
- relation_close(rel, NoLock);
- break;
+ Constraint *con;
+
+ Assert(IsA(cmd->def, Constraint));
+
+ con = (Constraint *) cmd->def;
+ con->old_pktable_oid = refRelId;
+ /* rewriting neither side of a FK */
+ if (con->contype == CONSTR_FOREIGN &&
+ !rewrite && tab->rewrite == 0)
+ TryReuseForeignKey(oldId, con);
+ cmd->subtype = AT_ReAddConstraint;
+ tab->subcmds[AT_PASS_OLD_CONSTR] =
+ lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
+
+ /* recreate any comment on the constraint */
+ RebuildConstraintComment(tab,
+ AT_PASS_OLD_CONSTR,
+ oldId,
+ rel, con->conname);
}
- default:
- elog(ERROR, "unexpected statement type: %d",
- (int) nodeTag(stm));
+ else
+ elog(ERROR, "unexpected statement subtype: %d",
+ (int) cmd->subtype);
+ }
}
+ else
+ elog(ERROR, "unexpected statement type: %d",
+ (int) nodeTag(stm));
}
+
+ relation_close(rel, NoLock);
+}
+
+/*
+ * Subroutine for ATPostAlterTypeParse() to recreate a comment entry for
+ * a constraint that is being re-added.
+ */
+static void
+RebuildConstraintComment(AlteredTableInfo *tab, int pass, Oid objid,
+ Relation rel, char *conname)
+{
+ CommentStmt *cmd;
+ char *comment_str;
+ AlterTableCmd *newcmd;
+
+ /* Look for comment for object wanted, and leave if none */
+ comment_str = GetComment(objid, ConstraintRelationId, 0);
+ if (comment_str == NULL)
+ return;
+
+ /* Build node CommentStmt */
+ cmd = makeNode(CommentStmt);
+ cmd->objtype = OBJECT_TABCONSTRAINT;
+ cmd->objname = list_make3(
+ makeString(get_namespace_name(RelationGetNamespace(rel))),
+ makeString(RelationGetRelationName(rel)),
+ makeString(conname));
+ cmd->objargs = NIL;
+ cmd->comment = comment_str;
+
+ /* Append it to list of commands */
+ newcmd = makeNode(AlterTableCmd);
+ newcmd->subtype = AT_ReAddComment;
+ newcmd->def = (Node *) cmd;
+ tab->subcmds[pass] = lappend(tab->subcmds[pass], newcmd);
}
/*
TryReuseIndex(Oid oldId, IndexStmt *stmt)
{
if (CheckIndexCompatible(oldId,
- stmt->relation,
stmt->accessMethod,
stmt->indexParams,
stmt->excludeOpNames))
* Also change the ownership of the table's row type, if it has one
*/
if (tuple_class->relkind != RELKIND_INDEX)
- AlterTypeOwnerInternal(tuple_class->reltype, newOwnerId,
- tuple_class->relkind == RELKIND_COMPOSITE_TYPE);
+ AlterTypeOwnerInternal(tuple_class->reltype, newOwnerId);
/*
* If we are operating on a table or materialized view, also change
* ALTER TABLE CLUSTER ON
*
* The only thing we have to do is to change the indisclustered bits.
+ *
+ * Return the address of the new clustering index.
*/
-static void
+static ObjectAddress
ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE lockmode)
{
Oid indexOid;
+ ObjectAddress address;
indexOid = get_relname_relid(indexName, rel->rd_rel->relnamespace);
/* And do the work */
mark_index_clustered(rel, indexOid, false);
+
+ ObjectAddressSet(address,
+ RelationRelationId, indexOid);
+
+ return address;
}
/*
ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, char *tablespacename, LOCKMODE lockmode)
{
Oid tablespaceId;
- AclResult aclresult;
/* Check that the tablespace exists */
tablespaceId = get_tablespace_oid(tablespacename, false);
- /* Check its permissions */
- aclresult = pg_tablespace_aclcheck(tablespaceId, GetUserId(), ACL_CREATE);
- if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult, ACL_KIND_TABLESPACE, tablespacename);
+ /* Check permissions except when moving to database's default */
+ if (OidIsValid(tablespaceId) && tablespaceId != MyDatabaseTableSpace)
+ {
+ AclResult aclresult;
+
+ aclresult = pg_tablespace_aclcheck(tablespaceId, GetUserId(), ACL_CREATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_TABLESPACE, tablespacename);
+ }
/* Save info for Phase 3 to do the real work */
if (OidIsValid(tab->newTableSpace))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot have multiple SET TABLESPACE subcommands")));
+
tab->newTableSpace = tablespaceId;
}
{
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
- case RELKIND_VIEW:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
break;
+ case RELKIND_VIEW:
+ (void) view_reloptions(newOptions, true);
+ break;
case RELKIND_INDEX:
(void) index_reloptions(rel->rd_am->amoptions, newOptions, true);
break;
List *view_options = untransformRelOptions(newOptions);
ListCell *cell;
bool check_option = false;
- bool security_barrier = false;
foreach(cell, view_options)
{
if (pg_strcasecmp(defel->defname, "check_option") == 0)
check_option = true;
- if (pg_strcasecmp(defel->defname, "security_barrier") == 0)
- security_barrier = defGetBoolean(defel);
}
/*
if (check_option)
{
const char *view_updatable_error =
- view_query_is_auto_updatable(view_query,
- security_barrier, true);
+ view_query_is_auto_updatable(view_query, true);
if (view_updatable_error)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("WITH CHECK OPTION is supported only on auto-updatable views"),
- errhint("%s", view_updatable_error)));
+ errmsg("WITH CHECK OPTION is supported only on automatically updatable views"),
+ errhint("%s", view_updatable_error)));
}
}
/* Fetch the list of indexes on toast relation if necessary */
if (OidIsValid(reltoastrelid))
{
- Relation toastRel = relation_open(reltoastrelid, lockmode);
+ Relation toastRel = relation_open(reltoastrelid, lockmode);
+
reltoastidxids = RelationGetIndexList(toastRel);
relation_close(toastRel, lockmode);
}
FlushRelationBuffers(rel);
/*
- * Relfilenodes are not unique across tablespaces, so we need to allocate
- * a new one in the new tablespace.
+ * Relfilenodes are not unique in databases across tablespaces, so we need
+ * to allocate a new one in the new tablespace.
*/
newrelfilenode = GetNewRelFileNode(newTableSpace, NULL,
rel->rd_rel->relpersistence);
if (smgrexists(rel->rd_smgr, forkNum))
{
smgrcreate(dstrel, forkNum, false);
+
+ /*
+ * WAL log creation if the relation is persistent, or this is the
+ * init fork of an unlogged relation.
+ */
+ if (rel->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT ||
+ (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
+ forkNum == INIT_FORKNUM))
+ log_smgrcreate(&newrnode, forkNum);
copy_relation_data(rel->rd_smgr, dstrel, forkNum,
rel->rd_rel->relpersistence);
}
list_free(reltoastidxids);
}
+/*
+ * Alter Table ALL ... SET TABLESPACE
+ *
+ * Allows a user to move all objects of some type in a given tablespace in the
+ * current database to another tablespace. Objects can be chosen based on the
+ * owner of the object also, to allow users to move only their objects.
+ * The user must have CREATE rights on the new tablespace, as usual. The main
+ * permissions handling is done by the lower-level table move function.
+ *
+ * All to-be-moved objects are locked first. If NOWAIT is specified and the
+ * lock can't be acquired then we ereport(ERROR).
+ */
+Oid
+AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
+{
+ List *relations = NIL;
+ ListCell *l;
+ ScanKeyData key[1];
+ Relation rel;
+ HeapScanDesc scan;
+ HeapTuple tuple;
+ Oid orig_tablespaceoid;
+ Oid new_tablespaceoid;
+ List *role_oids = roleSpecsToIds(stmt->roles);
+
+ /* Ensure we were not asked to move something we can't */
+ if (stmt->objtype != OBJECT_TABLE && stmt->objtype != OBJECT_INDEX &&
+ stmt->objtype != OBJECT_MATVIEW)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("only tables, indexes, and materialized views exist in tablespaces")));
+
+ /* Get the orig and new tablespace OIDs */
+ orig_tablespaceoid = get_tablespace_oid(stmt->orig_tablespacename, false);
+ new_tablespaceoid = get_tablespace_oid(stmt->new_tablespacename, false);
+
+ /* Can't move shared relations in to or out of pg_global */
+ /* This is also checked by ATExecSetTableSpace, but nice to stop earlier */
+ if (orig_tablespaceoid == GLOBALTABLESPACE_OID ||
+ new_tablespaceoid == GLOBALTABLESPACE_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot move relations in to or out of pg_global tablespace")));
+
+ /*
+ * Must have CREATE rights on the new tablespace, unless it is the
+ * database default tablespace (which all users implicitly have CREATE
+ * rights on).
+ */
+ if (OidIsValid(new_tablespaceoid) && new_tablespaceoid != MyDatabaseTableSpace)
+ {
+ AclResult aclresult;
+
+ aclresult = pg_tablespace_aclcheck(new_tablespaceoid, GetUserId(),
+ ACL_CREATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_TABLESPACE,
+ get_tablespace_name(new_tablespaceoid));
+ }
+
+ /*
+ * Now that the checks are done, check if we should set either to
+ * InvalidOid because it is our database's default tablespace.
+ */
+ if (orig_tablespaceoid == MyDatabaseTableSpace)
+ orig_tablespaceoid = InvalidOid;
+
+ if (new_tablespaceoid == MyDatabaseTableSpace)
+ new_tablespaceoid = InvalidOid;
+
+ /* no-op */
+ if (orig_tablespaceoid == new_tablespaceoid)
+ return new_tablespaceoid;
+
+ /*
+ * Walk the list of objects in the tablespace and move them. This will
+ * only find objects in our database, of course.
+ */
+ ScanKeyInit(&key[0],
+ Anum_pg_class_reltablespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(orig_tablespaceoid));
+
+ rel = heap_open(RelationRelationId, AccessShareLock);
+ scan = heap_beginscan_catalog(rel, 1, key);
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ Oid relOid = HeapTupleGetOid(tuple);
+ Form_pg_class relForm;
+
+ relForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ /*
+ * Do not move objects in pg_catalog as part of this, if an admin
+ * really wishes to do so, they can issue the individual ALTER
+ * commands directly.
+ *
+ * Also, explicitly avoid any shared tables, temp tables, or TOAST
+ * (TOAST will be moved with the main table).
+ */
+ if (IsSystemNamespace(relForm->relnamespace) || relForm->relisshared ||
+ isAnyTempNamespace(relForm->relnamespace) ||
+ relForm->relnamespace == PG_TOAST_NAMESPACE)
+ continue;
+
+ /* Only move the object type requested */
+ if ((stmt->objtype == OBJECT_TABLE &&
+ relForm->relkind != RELKIND_RELATION) ||
+ (stmt->objtype == OBJECT_INDEX &&
+ relForm->relkind != RELKIND_INDEX) ||
+ (stmt->objtype == OBJECT_MATVIEW &&
+ relForm->relkind != RELKIND_MATVIEW))
+ continue;
+
+ /* Check if we are only moving objects owned by certain roles */
+ if (role_oids != NIL && !list_member_oid(role_oids, relForm->relowner))
+ continue;
+
+ /*
+ * Handle permissions-checking here since we are locking the tables
+ * and also to avoid doing a bunch of work only to fail part-way. Note
+ * that permissions will also be checked by AlterTableInternal().
+ *
+ * Caller must be considered an owner on the table to move it.
+ */
+ if (!pg_class_ownercheck(relOid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
+ NameStr(relForm->relname));
+
+ if (stmt->nowait &&
+ !ConditionalLockRelationOid(relOid, AccessExclusiveLock))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("aborting because lock on relation \"%s.%s\" is not available",
+ get_namespace_name(relForm->relnamespace),
+ NameStr(relForm->relname))));
+ else
+ LockRelationOid(relOid, AccessExclusiveLock);
+
+ /* Add to our list of objects to move */
+ relations = lappend_oid(relations, relOid);
+ }
+
+ heap_endscan(scan);
+ heap_close(rel, AccessShareLock);
+
+ if (relations == NIL)
+ ereport(NOTICE,
+ (errcode(ERRCODE_NO_DATA_FOUND),
+ errmsg("no matching relations in tablespace \"%s\" found",
+ orig_tablespaceoid == InvalidOid ? "(database default)" :
+ get_tablespace_name(orig_tablespaceoid))));
+
+ /* Everything is locked, loop through and move all of the relations. */
+ foreach(l, relations)
+ {
+ List *cmds = NIL;
+ AlterTableCmd *cmd = makeNode(AlterTableCmd);
+
+ cmd->subtype = AT_SetTableSpace;
+ cmd->name = stmt->new_tablespacename;
+
+ cmds = lappend(cmds, cmd);
+
+ EventTriggerAlterTableStart((Node *) stmt);
+ /* OID is set by AlterTableInternal */
+ AlterTableInternal(lfirst_oid(l), cmds, false);
+ EventTriggerAlterTableEnd();
+ }
+
+ return new_tablespaceoid;
+}
+
/*
* Copy data, block by block
*/
char *buf;
Page page;
bool use_wal;
+ bool copying_initfork;
BlockNumber nblocks;
BlockNumber blkno;
buf = (char *) palloc(BLCKSZ);
page = (Page) buf;
+ /*
+ * The init fork for an unlogged relation in many respects has to be
+ * treated the same as normal relation, changes need to be WAL logged and
+ * it needs to be synced to disk.
+ */
+ copying_initfork = relpersistence == RELPERSISTENCE_UNLOGGED &&
+ forkNum == INIT_FORKNUM;
+
/*
* We need to log the copied data in WAL iff WAL archiving/streaming is
* enabled AND it's a permanent relation.
*/
- use_wal = XLogIsNeeded() && relpersistence == RELPERSISTENCE_PERMANENT;
+ use_wal = XLogIsNeeded() &&
+ (relpersistence == RELPERSISTENCE_PERMANENT || copying_initfork);
nblocks = smgrnblocks(src, forkNum);
src->smgr_rnode.backend,
forkNum))));
- /* XLOG stuff */
+ /*
+ * WAL-log the copied page. Unfortunately we don't know what kind of a
+ * page this is, so we have to log the full page including any unused
+ * space.
+ */
if (use_wal)
- log_newpage(&dst->smgr_rnode.node, forkNum, blkno, page);
+ log_newpage(&dst->smgr_rnode.node, forkNum, blkno, page, false);
PageSetChecksumInplace(page, blkno);
/*
- * Now write the page. We say isTemp = true even if it's not a temp
+ * Now write the page. We say isTemp = true even if it's not a temp
* rel, because there's no need for smgr to schedule an fsync for this
* write; we'll do it ourselves below.
*/
pfree(buf);
/*
- * If the rel is WAL-logged, must fsync before commit. We use heap_sync
+ * If the rel is WAL-logged, must fsync before commit. We use heap_sync
* to ensure that the toast table gets fsync'd too. (For a temp or
* unlogged rel we don't care since the data will be gone after a crash
* anyway.)
* wouldn't replay our earlier WAL entries. If we do not fsync those pages
* here, they might still not be on disk when the crash occurs.
*/
- if (relpersistence == RELPERSISTENCE_PERMANENT)
+ if (relpersistence == RELPERSISTENCE_PERMANENT || copying_initfork)
smgrimmedsync(dst, forkNum);
}
errmsg("cannot change inheritance of typed table")));
}
-static void
+/*
+ * Return the address of the new parent relation.
+ */
+static ObjectAddress
ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
{
Relation parent_rel,
HeapTuple inheritsTuple;
int32 inhseqno;
List *children;
+ ObjectAddress address;
/*
* A self-exclusive lock is needed here. See the similar case in
* Must be owner of both parent and child -- child was checked by
* ATSimplePermissions call in ATPrepCmd
*/
- ATSimplePermissions(parent_rel, ATT_TABLE);
+ ATSimplePermissions(parent_rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/* Permanent rels cannot inherit from temporary ones */
if (parent_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
MergeConstraintsIntoExisting(child_rel, parent_rel);
/*
- * OK, it looks valid. Make the catalog entries that show inheritance.
+ * OK, it looks valid. Make the catalog entries that show inheritance.
*/
StoreCatalogInheritance1(RelationGetRelid(child_rel),
RelationGetRelid(parent_rel),
inhseqno + 1,
catalogRelation);
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
/* Now we're done with pg_inherits */
heap_close(catalogRelation, RowExclusiveLock);
/* keep our lock on the parent relation until commit */
heap_close(parent_rel, NoLock);
+
+ return address;
}
/*
*
* coninhcount and conislocal for inherited constraints are adjusted in
* exactly the same way.
+ *
+ * Return value is the address of the relation that is no longer parent.
*/
-static void
+static ObjectAddress
ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
{
Relation parent_rel;
+ Oid parent_oid;
Relation catalogRelation;
SysScanDesc scan;
ScanKeyData key[3];
constraintTuple;
List *connames;
bool found = false;
+ ObjectAddress address;
/*
* AccessShareLock on the parent is probably enough, seeing that DROP
}
}
+ parent_oid = RelationGetRelid(parent_rel);
+
systable_endscan(scan);
heap_close(catalogRelation, RowExclusiveLock);
/* keep our lock on the parent relation until commit */
heap_close(parent_rel, NoLock);
+
+ ObjectAddressSet(address, RelationRelationId, parent_oid);
+
+ return address;
}
/*
* Drop the dependency created by StoreCatalogInheritance1 (CREATE TABLE
* INHERITS/ALTER TABLE INHERIT -- refclassid will be RelationRelationId) or
* heap_create_with_catalog (CREATE TABLE OF/ALTER TABLE OF -- refclassid will
- * be TypeRelationId). There's no convenient way to do this, so go trawling
+ * be TypeRelationId). There's no convenient way to do this, so go trawling
* through pg_depend.
*/
static void
* TABLE OF. All attname, atttypid, atttypmod and attcollation must match. The
* subject table must not have inheritance parents. These restrictions ensure
* that you cannot create a configuration impossible with CREATE TABLE OF alone.
+ *
+ * The address of the type is returned.
*/
-static void
+static ObjectAddress
ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
{
Oid relid = RelationGetRelid(rel);
heap_close(relationRelation, RowExclusiveLock);
ReleaseSysCache(typetuple);
+
+ return typeobj;
}
/*
* ALTER TABLE NOT OF
*
- * Detach a typed table from its originating type. Just clear reloftype and
+ * Detach a typed table from its originating type. Just clear reloftype and
* remove the dependency.
*/
static void
*/
pg_class = heap_open(RelationRelationId, RowExclusiveLock);
pg_class_tuple = SearchSysCacheCopy1(RELOID,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(rel)));
if (!HeapTupleIsValid(pg_class_tuple))
elog(ERROR, "cache lookup failed for relation \"%s\"",
RelationGetRelationName(rel));
}
/*
- * Clear the indisreplident flag from any index that had it previously, and
- * set it for any index that should have it now.
+ * Clear the indisreplident flag from any index that had it previously,
+ * and set it for any index that should have it now.
*/
pg_index = heap_open(IndexRelationId, RowExclusiveLock);
foreach(index, RelationGetIndexList(rel))
bool dirty = false;
pg_index_tuple = SearchSysCacheCopy1(INDEXRELID,
- ObjectIdGetDatum(thisIndexOid));
+ ObjectIdGetDatum(thisIndexOid));
if (!HeapTupleIsValid(pg_index_tuple))
elog(ERROR, "cache lookup failed for index %u", thisIndexOid);
pg_index_form = (Form_pg_index) GETSTRUCT(pg_index_tuple);
}
else if (stmt->identity_type == REPLICA_IDENTITY_INDEX)
{
- /* fallthrough */;
+ /* fallthrough */ ;
}
else
elog(ERROR, "unexpected identity type %u", stmt->identity_type);
if (!indexRel->rd_am->amcanunique || !indexRel->rd_index->indisunique)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot use non-unique index \"%s\" as replica identity",
- RelationGetRelationName(indexRel))));
+ errmsg("cannot use non-unique index \"%s\" as replica identity",
+ RelationGetRelationName(indexRel))));
/* Deferred indexes are not guaranteed to be always unique. */
if (!indexRel->rd_index->indimmediate)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot use non-immediate index \"%s\" as replica identity",
- RelationGetRelationName(indexRel))));
+ errmsg("cannot use non-immediate index \"%s\" as replica identity",
+ RelationGetRelationName(indexRel))));
/* Expression indexes aren't supported. */
if (RelationGetIndexExpressions(indexRel) != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot use expression index \"%s\" as replica identity",
- RelationGetRelationName(indexRel))));
+ errmsg("cannot use expression index \"%s\" as replica identity",
+ RelationGetRelationName(indexRel))));
/* Predicate indexes aren't supported. */
if (RelationGetIndexPredicate(indexRel) != NIL)
ereport(ERROR,
/* Check index for nullable columns. */
for (key = 0; key < indexRel->rd_index->indnatts; key++)
{
- int16 attno = indexRel->rd_index->indkey.values[key];
+ int16 attno = indexRel->rd_index->indkey.values[key];
Form_pg_attribute attr;
/* Of the system columns, only oid is indexable. */
index_close(indexRel, NoLock);
}
+/*
+ * ALTER TABLE ENABLE/DISABLE ROW LEVEL SECURITY
+ */
+static void
+ATExecEnableRowSecurity(Relation rel)
+{
+ Relation pg_class;
+ Oid relid;
+ HeapTuple tuple;
+
+ relid = RelationGetRelid(rel);
+
+ pg_class = heap_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+
+ ((Form_pg_class) GETSTRUCT(tuple))->relrowsecurity = true;
+ simple_heap_update(pg_class, &tuple->t_self, tuple);
+
+ /* keep catalog indexes current */
+ CatalogUpdateIndexes(pg_class, tuple);
+
+ heap_close(pg_class, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
+
+static void
+ATExecDisableRowSecurity(Relation rel)
+{
+ Relation pg_class;
+ Oid relid;
+ HeapTuple tuple;
+
+ relid = RelationGetRelid(rel);
+
+ /* Pull the record for this relation and update it */
+ pg_class = heap_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+
+ ((Form_pg_class) GETSTRUCT(tuple))->relrowsecurity = false;
+ simple_heap_update(pg_class, &tuple->t_self, tuple);
+
+ /* keep catalog indexes current */
+ CatalogUpdateIndexes(pg_class, tuple);
+
+ heap_close(pg_class, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
+
+/*
+ * ALTER TABLE FORCE/NO FORCE ROW LEVEL SECURITY
+ */
+static void
+ATExecForceNoForceRowSecurity(Relation rel, bool force_rls)
+{
+ Relation pg_class;
+ Oid relid;
+ HeapTuple tuple;
+
+ relid = RelationGetRelid(rel);
+
+ pg_class = heap_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+
+ ((Form_pg_class) GETSTRUCT(tuple))->relforcerowsecurity = force_rls;
+ simple_heap_update(pg_class, &tuple->t_self, tuple);
+
+ /* keep catalog indexes current */
+ CatalogUpdateIndexes(pg_class, tuple);
+
+ heap_close(pg_class, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
+
/*
* ALTER FOREIGN TABLE <name> OPTIONS (...)
*/
heap_freetuple(tuple);
}
+/*
+ * Preparation phase for SET LOGGED/UNLOGGED
+ *
+ * This verifies that we're not trying to change a temp table. Also,
+ * existing foreign key constraints are checked to avoid ending up with
+ * permanent tables referencing unlogged tables.
+ *
+ * Return value is false if the operation is a no-op (in which case the
+ * checks are skipped), otherwise true.
+ */
+static bool
+ATPrepChangePersistence(Relation rel, bool toLogged)
+{
+ Relation pg_constraint;
+ HeapTuple tuple;
+ SysScanDesc scan;
+ ScanKeyData skey[1];
+
+ /*
+ * Disallow changing status for a temp table. Also verify whether we can
+ * get away with doing nothing; in such cases we don't need to run the
+ * checks below, either.
+ */
+ switch (rel->rd_rel->relpersistence)
+ {
+ case RELPERSISTENCE_TEMP:
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot change logged status of table \"%s\" because it is temporary",
+ RelationGetRelationName(rel)),
+ errtable(rel)));
+ break;
+ case RELPERSISTENCE_PERMANENT:
+ if (toLogged)
+ /* nothing to do */
+ return false;
+ break;
+ case RELPERSISTENCE_UNLOGGED:
+ if (!toLogged)
+ /* nothing to do */
+ return false;
+ break;
+ }
+
+ /*
+ * Check existing foreign key constraints to preserve the invariant that
+ * permanent tables cannot reference unlogged ones. Self-referencing
+ * foreign keys can safely be ignored.
+ */
+ pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
+
+ /*
+ * Scan conrelid if changing to permanent, else confrelid. This also
+ * determines whether a useful index exists.
+ */
+ ScanKeyInit(&skey[0],
+ toLogged ? Anum_pg_constraint_conrelid :
+ Anum_pg_constraint_confrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ scan = systable_beginscan(pg_constraint,
+ toLogged ? ConstraintRelidIndexId : InvalidOid,
+ true, NULL, 1, skey);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
+
+ if (con->contype == CONSTRAINT_FOREIGN)
+ {
+ Oid foreignrelid;
+ Relation foreignrel;
+
+ /* the opposite end of what we used as scankey */
+ foreignrelid = toLogged ? con->confrelid : con->conrelid;
+
+ /* ignore if self-referencing */
+ if (RelationGetRelid(rel) == foreignrelid)
+ continue;
+
+ foreignrel = relation_open(foreignrelid, AccessShareLock);
+
+ if (toLogged)
+ {
+ if (foreignrel->rd_rel->relpersistence != RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("could not change table \"%s\" to logged because it references unlogged table \"%s\"",
+ RelationGetRelationName(rel),
+ RelationGetRelationName(foreignrel)),
+ errtableconstraint(rel, NameStr(con->conname))));
+ }
+ else
+ {
+ if (foreignrel->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("could not change table \"%s\" to unlogged because it references logged table \"%s\"",
+ RelationGetRelationName(rel),
+ RelationGetRelationName(foreignrel)),
+ errtableconstraint(rel, NameStr(con->conname))));
+ }
+
+ relation_close(foreignrel, AccessShareLock);
+ }
+ }
+
+ systable_endscan(scan);
+
+ heap_close(pg_constraint, AccessShareLock);
+
+ return true;
+}
+
/*
* Execute ALTER TABLE SET SCHEMA
*/
-Oid
-AlterTableNamespace(AlterObjectSchemaStmt *stmt)
+ObjectAddress
+AlterTableNamespace(AlterObjectSchemaStmt *stmt, Oid *oldschema)
{
Relation rel;
Oid relid;
Oid nspOid;
RangeVar *newrv;
ObjectAddresses *objsMoved;
+ ObjectAddress myself;
relid = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
stmt->missing_ok, false,
ereport(NOTICE,
(errmsg("relation \"%s\" does not exist, skipping",
stmt->relation->relname)));
- return InvalidOid;
+ return InvalidObjectAddress;
}
rel = relation_open(relid, NoLock);
nspOid = RangeVarGetAndCheckCreationNamespace(newrv, NoLock, NULL);
/* common checks on switching namespaces */
- CheckSetNamespace(oldNspOid, nspOid, RelationRelationId, relid);
+ CheckSetNamespace(oldNspOid, nspOid);
objsMoved = new_object_addresses();
AlterTableNamespaceInternal(rel, oldNspOid, nspOid, objsMoved);
free_object_addresses(objsMoved);
+ ObjectAddressSet(myself, RelationRelationId, relid);
+
+ if (oldschema)
+ *oldschema = oldNspOid;
+
/* close rel, but keep lock until commit */
relation_close(rel, NoLock);
- return relid;
+ return myself;
}
/*
HeapTuple classTup;
Form_pg_class classForm;
ObjectAddress thisobj;
+ bool already_done = false;
classTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relOid));
if (!HeapTupleIsValid(classTup))
thisobj.objectSubId = 0;
/*
- * Do nothing when there's nothing to do.
+ * If the object has already been moved, don't move it again. If it's
+ * already in the right place, don't move it, but still fire the object
+ * access hook.
*/
- if (!object_address_present(&thisobj, objsMoved))
+ already_done = object_address_present(&thisobj, objsMoved);
+ if (!already_done && oldNspOid != newNspOid)
{
/* check for duplicate name (more friendly than unique-index failure) */
if (get_relname_relid(NameStr(classForm->relname),
newNspOid) != 1)
elog(ERROR, "failed to change schema dependency for relation \"%s\"",
NameStr(classForm->relname));
-
+ }
+ if (!already_done)
+ {
add_exact_object_address(&thisobj, objsMoved);
InvokeObjectPostAlterHook(RelationRelationId, relOid, 0);
* Post-subcommit or post-subabort cleanup for ON COMMIT management.
*
* During subabort, we can immediately remove entries created during this
- * subtransaction. During subcommit, just relabel entries marked during
+ * subtransaction. During subcommit, just relabel entries marked during
* this subtransaction as being the parent's responsibility.
*/
void
* This is intended as a callback for RangeVarGetRelidExtended(). It allows
* the relation to be locked only if (1) it's a plain table, materialized
* view, or TOAST table and (2) the current user is the owner (or the
- * superuser). This meets the permission-checking needs of CLUSTER, REINDEX
+ * superuser). This meets the permission-checking needs of CLUSTER, REINDEX
* TABLE, and REFRESH MATERIALIZED VIEW; we expose it here so that it can be
* used by all.
*/
/*
* If the relation does exist, check whether it's an index. But note that
* the relation might have been dropped between the time we did the name
- * lookup and now. In that case, there's nothing to do.
+ * lookup and now. In that case, there's nothing to do.
*/
relkind = get_rel_relkind(relId);
if (!relkind)
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, relation->relname);
}
+/*
+ * Callback to RangeVarGetRelidExtended(), similar to
+ * RangeVarCallbackOwnsTable() but without checks on the type of the relation.
+ */
+void
+RangeVarCallbackOwnsRelation(const RangeVar *relation,
+ Oid relId, Oid oldRelId, void *arg)
+{
+ HeapTuple tuple;
+
+ /* Nothing to do if the relation was not found. */
+ if (!OidIsValid(relId))
+ return;
+
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relId));
+ if (!HeapTupleIsValid(tuple)) /* should not happen */
+ elog(ERROR, "cache lookup failed for relation %u", relId);
+
+ if (!pg_class_ownercheck(relId, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
+ relation->relname);
+
+ if (!allowSystemTableMods &&
+ IsSystemClass(relId, (Form_pg_class) GETSTRUCT(tuple)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ relation->relname)));
+
+ ReleaseSysCache(tuple);
+}
+
/*
* Common RangeVarGetRelid callback for rename, set schema, and alter table
* processing.
relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is not a table, view, materialized view, sequence, or foreign table",
- rv->relname)));
+ errmsg("\"%s\" is not a table, view, materialized view, sequence, or foreign table",
+ rv->relname)));
ReleaseSysCache(tuple);
}