* pg_constraint.c
* routines to support manipulation of the pg_constraint relation
*
- * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/catalog/pg_constraint.c,v 1.18 2003/11/12 21:15:48 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/pg_constraint.c,v 1.47 2009/07/28 02:56:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
-#include "access/heapam.h"
#include "access/genam.h"
-#include "catalog/catalog.h"
-#include "catalog/catname.h"
+#include "access/heapam.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/pg_constraint.h"
+#include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
-#include "miscadmin.h"
+#include "commands/defrem.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
+#include "utils/tqual.h"
/*
const int16 *constraintKey,
int constraintNKeys,
Oid domainId,
+ Oid indexRelId,
Oid foreignRelId,
const int16 *foreignKey,
+ const Oid *pfEqOp,
+ const Oid *ppEqOp,
+ const Oid *ffEqOp,
int foreignNKeys,
char foreignUpdateType,
char foreignDeleteType,
char foreignMatchType,
- Oid indexRelId,
Node *conExpr,
const char *conBin,
- const char *conSrc)
+ const char *conSrc,
+ bool conIsLocal,
+ int conInhCount)
{
Relation conDesc;
Oid conOid;
HeapTuple tup;
- char nulls[Natts_pg_constraint];
+ bool nulls[Natts_pg_constraint];
Datum values[Natts_pg_constraint];
ArrayType *conkeyArray;
ArrayType *confkeyArray;
+ ArrayType *conpfeqopArray;
+ ArrayType *conppeqopArray;
+ ArrayType *conffeqopArray;
NameData cname;
int i;
ObjectAddress conobject;
- conDesc = heap_openr(ConstraintRelationName, RowExclusiveLock);
+ conDesc = heap_open(ConstraintRelationId, RowExclusiveLock);
Assert(constraintName);
namestrcpy(&cname, constraintName);
if (foreignNKeys > 0)
{
- Datum *confkey;
+ Datum *fkdatums;
- confkey = (Datum *) palloc(foreignNKeys * sizeof(Datum));
+ fkdatums = (Datum *) palloc(foreignNKeys * sizeof(Datum));
for (i = 0; i < foreignNKeys; i++)
- confkey[i] = Int16GetDatum(foreignKey[i]);
- confkeyArray = construct_array(confkey, foreignNKeys,
+ fkdatums[i] = Int16GetDatum(foreignKey[i]);
+ confkeyArray = construct_array(fkdatums, foreignNKeys,
INT2OID, 2, true, 's');
+ for (i = 0; i < foreignNKeys; i++)
+ fkdatums[i] = ObjectIdGetDatum(pfEqOp[i]);
+ conpfeqopArray = construct_array(fkdatums, foreignNKeys,
+ OIDOID, sizeof(Oid), true, 'i');
+ for (i = 0; i < foreignNKeys; i++)
+ fkdatums[i] = ObjectIdGetDatum(ppEqOp[i]);
+ conppeqopArray = construct_array(fkdatums, foreignNKeys,
+ OIDOID, sizeof(Oid), true, 'i');
+ for (i = 0; i < foreignNKeys; i++)
+ fkdatums[i] = ObjectIdGetDatum(ffEqOp[i]);
+ conffeqopArray = construct_array(fkdatums, foreignNKeys,
+ OIDOID, sizeof(Oid), true, 'i');
}
else
+ {
confkeyArray = NULL;
+ conpfeqopArray = NULL;
+ conppeqopArray = NULL;
+ conffeqopArray = NULL;
+ }
/* initialize nulls and values */
for (i = 0; i < Natts_pg_constraint; i++)
{
- nulls[i] = ' ';
+ nulls[i] = false;
values[i] = (Datum) NULL;
}
values[Anum_pg_constraint_condeferred - 1] = BoolGetDatum(isDeferred);
values[Anum_pg_constraint_conrelid - 1] = ObjectIdGetDatum(relId);
values[Anum_pg_constraint_contypid - 1] = ObjectIdGetDatum(domainId);
+ values[Anum_pg_constraint_conindid - 1] = ObjectIdGetDatum(indexRelId);
values[Anum_pg_constraint_confrelid - 1] = ObjectIdGetDatum(foreignRelId);
values[Anum_pg_constraint_confupdtype - 1] = CharGetDatum(foreignUpdateType);
values[Anum_pg_constraint_confdeltype - 1] = CharGetDatum(foreignDeleteType);
values[Anum_pg_constraint_confmatchtype - 1] = CharGetDatum(foreignMatchType);
+ values[Anum_pg_constraint_conislocal - 1] = BoolGetDatum(conIsLocal);
+ values[Anum_pg_constraint_coninhcount - 1] = Int32GetDatum(conInhCount);
if (conkeyArray)
values[Anum_pg_constraint_conkey - 1] = PointerGetDatum(conkeyArray);
else
- nulls[Anum_pg_constraint_conkey - 1] = 'n';
+ nulls[Anum_pg_constraint_conkey - 1] = true;
if (confkeyArray)
values[Anum_pg_constraint_confkey - 1] = PointerGetDatum(confkeyArray);
else
- nulls[Anum_pg_constraint_confkey - 1] = 'n';
+ nulls[Anum_pg_constraint_confkey - 1] = true;
+
+ if (conpfeqopArray)
+ values[Anum_pg_constraint_conpfeqop - 1] = PointerGetDatum(conpfeqopArray);
+ else
+ nulls[Anum_pg_constraint_conpfeqop - 1] = true;
+
+ if (conppeqopArray)
+ values[Anum_pg_constraint_conppeqop - 1] = PointerGetDatum(conppeqopArray);
+ else
+ nulls[Anum_pg_constraint_conppeqop - 1] = true;
+
+ if (conffeqopArray)
+ values[Anum_pg_constraint_conffeqop - 1] = PointerGetDatum(conffeqopArray);
+ else
+ nulls[Anum_pg_constraint_conffeqop - 1] = true;
/*
* initialize the binary form of the check constraint.
*/
if (conBin)
- values[Anum_pg_constraint_conbin - 1] = DirectFunctionCall1(textin,
- CStringGetDatum(conBin));
+ values[Anum_pg_constraint_conbin - 1] = CStringGetTextDatum(conBin);
else
- nulls[Anum_pg_constraint_conbin - 1] = 'n';
+ nulls[Anum_pg_constraint_conbin - 1] = true;
/*
* initialize the text form of the check constraint
*/
if (conSrc)
- values[Anum_pg_constraint_consrc - 1] = DirectFunctionCall1(textin,
- CStringGetDatum(conSrc));
+ values[Anum_pg_constraint_consrc - 1] = CStringGetTextDatum(conSrc);
else
- nulls[Anum_pg_constraint_consrc - 1] = 'n';
+ nulls[Anum_pg_constraint_consrc - 1] = true;
- tup = heap_formtuple(RelationGetDescr(conDesc), values, nulls);
+ tup = heap_form_tuple(RelationGetDescr(conDesc), values, nulls);
conOid = simple_heap_insert(conDesc, tup);
/* update catalog indexes */
CatalogUpdateIndexes(conDesc, tup);
- conobject.classId = RelationGetRelid(conDesc);
+ conobject.classId = ConstraintRelationId;
conobject.objectId = conOid;
conobject.objectSubId = 0;
if (OidIsValid(relId))
{
/*
- * Register auto dependency from constraint to owning relation, or
- * to specific column(s) if any are mentioned.
+ * Register auto dependency from constraint to owning relation, or to
+ * specific column(s) if any are mentioned.
*/
ObjectAddress relobject;
- relobject.classId = RelOid_pg_class;
+ relobject.classId = RelationRelationId;
relobject.objectId = relId;
if (constraintNKeys > 0)
{
*/
ObjectAddress domobject;
- domobject.classId = RelOid_pg_type;
+ domobject.classId = TypeRelationId;
domobject.objectId = domainId;
domobject.objectSubId = 0;
if (OidIsValid(foreignRelId))
{
/*
- * Register normal dependency from constraint to foreign relation,
- * or to specific column(s) if any are mentioned.
+ * Register normal dependency from constraint to foreign relation, or
+ * to specific column(s) if any are mentioned.
*/
ObjectAddress relobject;
- relobject.classId = RelOid_pg_class;
+ relobject.classId = RelationRelationId;
relobject.objectId = foreignRelId;
if (foreignNKeys > 0)
{
}
}
- if (OidIsValid(indexRelId))
+ if (OidIsValid(indexRelId) && constraintType == CONSTRAINT_FOREIGN)
{
/*
* Register normal dependency on the unique index that supports a
- * foreign-key constraint.
+ * foreign-key constraint. (Note: for indexes associated with
+ * unique or primary-key constraints, the dependency runs the other
+ * way, and is not made here.)
*/
ObjectAddress relobject;
- relobject.classId = RelOid_pg_class;
+ relobject.classId = RelationRelationId;
relobject.objectId = indexRelId;
relobject.objectSubId = 0;
recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
}
+ if (foreignNKeys > 0)
+ {
+ /*
+ * Register normal dependencies on the equality operators that support
+ * a foreign-key constraint. If the PK and FK types are the same then
+ * all three operators for a column are the same; otherwise they are
+ * different.
+ */
+ ObjectAddress oprobject;
+
+ oprobject.classId = OperatorRelationId;
+ oprobject.objectSubId = 0;
+
+ for (i = 0; i < foreignNKeys; i++)
+ {
+ oprobject.objectId = pfEqOp[i];
+ recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
+ if (ppEqOp[i] != pfEqOp[i])
+ {
+ oprobject.objectId = ppEqOp[i];
+ recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
+ }
+ if (ffEqOp[i] != pfEqOp[i])
+ {
+ oprobject.objectId = ffEqOp[i];
+ recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
+ }
+ }
+ }
+
if (conExpr != NULL)
{
/*
- * Register dependencies from constraint to objects mentioned in
- * CHECK expression.
+ * Register dependencies from constraint to objects mentioned in CHECK
+ * expression.
*/
recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
DEPENDENCY_NORMAL,
/*
* Test whether given name is currently used as a constraint name
- * for the given relation.
+ * for the given object (relation or domain).
*
- * NB: Caller should hold exclusive lock on the given relation, else
- * this test is not very meaningful.
+ * This is used to decide whether to accept a user-specified constraint name.
+ * It is deliberately not the same test as ChooseConstraintName uses to decide
+ * whether an auto-generated name is OK: here, we will allow it unless there
+ * is an identical constraint name in use *on the same object*.
+ *
+ * NB: Caller should hold exclusive lock on the given object, else
+ * this test can be fooled by concurrent additions.
*/
bool
-ConstraintNameIsUsed(CONSTRAINTCATEGORY conCat, Oid objId, Oid objNamespace, const char *cname)
+ConstraintNameIsUsed(ConstraintCategory conCat, Oid objId,
+ Oid objNamespace, const char *conname)
{
bool found;
Relation conDesc;
ScanKeyData skey[2];
HeapTuple tup;
- conDesc = heap_openr(ConstraintRelationName, RowExclusiveLock);
+ conDesc = heap_open(ConstraintRelationId, AccessShareLock);
found = false;
ScanKeyInit(&skey[0],
Anum_pg_constraint_conname,
BTEqualStrategyNumber, F_NAMEEQ,
- CStringGetDatum(cname));
+ CStringGetDatum(conname));
ScanKeyInit(&skey[1],
Anum_pg_constraint_connamespace,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(objNamespace));
- conscan = systable_beginscan(conDesc, ConstraintNameNspIndex, true,
+ conscan = systable_beginscan(conDesc, ConstraintNameNspIndexId, true,
SnapshotNow, 2, skey);
while (HeapTupleIsValid(tup = systable_getnext(conscan)))
}
systable_endscan(conscan);
- heap_close(conDesc, RowExclusiveLock);
+ heap_close(conDesc, AccessShareLock);
return found;
}
/*
- * Generate a currently-unused constraint name for the given relation.
+ * Select a nonconflicting name for a new constraint.
*
- * The passed counter should be initialized to 0 the first time through.
- * If multiple constraint names are to be generated in a single command,
- * pass the new counter value to each successive call, else the same
- * name will be generated each time.
+ * The objective here is to choose a name that is unique within the
+ * specified namespace. Postgres does not require this, but the SQL
+ * spec does, and some apps depend on it. Therefore we avoid choosing
+ * default names that so conflict.
*
- * NB: Caller should hold exclusive lock on the given relation, else
- * someone else might choose the same name concurrently!
+ * name1, name2, and label are used the same way as for makeObjectName(),
+ * except that the label can't be NULL; digits will be appended to the label
+ * if needed to create a name that is unique within the specified namespace.
+ *
+ * 'others' can be a list of string names already chosen within the current
+ * command (but not yet reflected into the catalogs); we will not choose
+ * a duplicate of one of these either.
+ *
+ * Note: it is theoretically possible to get a collision anyway, if someone
+ * else chooses the same name concurrently. This is fairly unlikely to be
+ * a problem in practice, especially if one is holding an exclusive lock on
+ * the relation identified by name1.
+ *
+ * Returns a palloc'd string.
*/
char *
-GenerateConstraintName(CONSTRAINTCATEGORY conCat, Oid objId, Oid objNamespace, int *counter)
+ChooseConstraintName(const char *name1, const char *name2,
+ const char *label, Oid namespaceid,
+ List *others)
{
- bool found;
+ int pass = 0;
+ char *conname = NULL;
+ char modlabel[NAMEDATALEN];
Relation conDesc;
- char *cname;
+ SysScanDesc conscan;
+ ScanKeyData skey[2];
+ bool found;
+ ListCell *l;
- cname = (char *) palloc(NAMEDATALEN * sizeof(char));
+ conDesc = heap_open(ConstraintRelationId, AccessShareLock);
- conDesc = heap_openr(ConstraintRelationName, RowExclusiveLock);
+ /* try the unmodified label first */
+ StrNCpy(modlabel, label, sizeof(modlabel));
- /* Loop until we find a non-conflicting constraint name */
- /* We assume there will be one eventually ... */
- do
+ for (;;)
{
- SysScanDesc conscan;
- ScanKeyData skey[2];
- HeapTuple tup;
-
- ++(*counter);
- snprintf(cname, NAMEDATALEN, "$%d", *counter);
+ conname = makeObjectName(name1, name2, modlabel);
- /*
- * This duplicates ConstraintNameIsUsed() so that we can avoid
- * re-opening pg_constraint for each iteration.
- */
found = false;
- ScanKeyInit(&skey[0],
- Anum_pg_constraint_conname,
- BTEqualStrategyNumber, F_NAMEEQ,
- CStringGetDatum(cname));
-
- ScanKeyInit(&skey[1],
- Anum_pg_constraint_connamespace,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(objNamespace));
-
- conscan = systable_beginscan(conDesc, ConstraintNameNspIndex, true,
- SnapshotNow, 2, skey);
-
- while (HeapTupleIsValid(tup = systable_getnext(conscan)))
+ foreach(l, others)
{
- Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tup);
-
- if (conCat == CONSTRAINT_RELATION && con->conrelid == objId)
- {
- found = true;
- break;
- }
- else if (conCat == CONSTRAINT_DOMAIN && con->contypid == objId)
+ if (strcmp((char *) lfirst(l), conname) == 0)
{
found = true;
break;
}
}
- systable_endscan(conscan);
- } while (found);
+ if (!found)
+ {
+ ScanKeyInit(&skey[0],
+ Anum_pg_constraint_conname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(conname));
- heap_close(conDesc, RowExclusiveLock);
+ ScanKeyInit(&skey[1],
+ Anum_pg_constraint_connamespace,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(namespaceid));
- return cname;
-}
+ conscan = systable_beginscan(conDesc, ConstraintNameNspIndexId, true,
+ SnapshotNow, 2, skey);
-/*
- * Does the given name look like a generated constraint name?
- *
- * This is a test on the form of the name, *not* on whether it has
- * actually been assigned.
- */
-bool
-ConstraintNameIsGenerated(const char *cname)
-{
- if (cname[0] != '$')
- return false;
- if (strspn(cname + 1, "0123456789") != strlen(cname + 1))
- return false;
- return true;
+ found = (HeapTupleIsValid(systable_getnext(conscan)));
+
+ systable_endscan(conscan);
+ }
+
+ if (!found)
+ break;
+
+ /* found a conflict, so try a new name component */
+ pfree(conname);
+ snprintf(modlabel, sizeof(modlabel), "%s%d", label, ++pass);
+ }
+
+ heap_close(conDesc, AccessShareLock);
+
+ return conname;
}
/*
RemoveConstraintById(Oid conId)
{
Relation conDesc;
- ScanKeyData skey[1];
- SysScanDesc conscan;
HeapTuple tup;
Form_pg_constraint con;
- conDesc = heap_openr(ConstraintRelationName, RowExclusiveLock);
+ conDesc = heap_open(ConstraintRelationId, RowExclusiveLock);
- ScanKeyInit(&skey[0],
- ObjectIdAttributeNumber,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(conId));
-
- conscan = systable_beginscan(conDesc, ConstraintOidIndex, true,
- SnapshotNow, 1, skey);
-
- tup = systable_getnext(conscan);
- if (!HeapTupleIsValid(tup))
- elog(ERROR, "could not find tuple for constraint %u", conId);
+ tup = SearchSysCache(CONSTROID,
+ ObjectIdGetDatum(conId),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for constraint %u", conId);
con = (Form_pg_constraint) GETSTRUCT(tup);
/*
Relation rel;
/*
- * If the constraint is for a relation, open and exclusive-lock
- * the relation it's for.
+ * If the constraint is for a relation, open and exclusive-lock the
+ * relation it's for.
*/
rel = heap_open(con->conrelid, AccessExclusiveLock);
/*
- * We need to update the relcheck count if it is a check
- * constraint being dropped. This update will force backends to
- * rebuild relcache entries when we commit.
+ * We need to update the relcheck count if it is a check constraint
+ * being dropped. This update will force backends to rebuild relcache
+ * entries when we commit.
*/
if (con->contype == CONSTRAINT_CHECK)
{
HeapTuple relTup;
Form_pg_class classForm;
- pgrel = heap_openr(RelationRelationName, RowExclusiveLock);
+ pgrel = heap_open(RelationRelationId, RowExclusiveLock);
relTup = SearchSysCacheCopy(RELOID,
ObjectIdGetDatum(con->conrelid),
0, 0, 0);
else if (OidIsValid(con->contypid))
{
/*
- * XXX for now, do nothing special when dropping a domain
- * constraint
+ * XXX for now, do nothing special when dropping a domain constraint
*
* Probably there should be some form of locking on the domain type,
* but we have no such concept at the moment.
simple_heap_delete(conDesc, &tup->t_self);
/* Clean up */
- systable_endscan(conscan);
+ ReleaseSysCache(tup);
+ heap_close(conDesc, RowExclusiveLock);
+}
+
+/*
+ * RenameConstraintById
+ * Rename a constraint.
+ *
+ * Note: this isn't intended to be a user-exposed function; it doesn't check
+ * permissions etc. Currently this is only invoked when renaming an index
+ * that is associated with a constraint, but it's made a little more general
+ * than that with the expectation of someday having ALTER TABLE RENAME
+ * CONSTRAINT.
+ */
+void
+RenameConstraintById(Oid conId, const char *newname)
+{
+ Relation conDesc;
+ HeapTuple tuple;
+ Form_pg_constraint con;
+
+ conDesc = heap_open(ConstraintRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy(CONSTROID,
+ ObjectIdGetDatum(conId),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for constraint %u", conId);
+ con = (Form_pg_constraint) GETSTRUCT(tuple);
+
+ /*
+ * We need to check whether the name is already in use --- note that there
+ * currently is not a unique index that would catch this.
+ */
+ if (OidIsValid(con->conrelid) &&
+ ConstraintNameIsUsed(CONSTRAINT_RELATION,
+ con->conrelid,
+ con->connamespace,
+ newname))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("constraint \"%s\" for relation \"%s\" already exists",
+ newname, get_rel_name(con->conrelid))));
+ if (OidIsValid(con->contypid) &&
+ ConstraintNameIsUsed(CONSTRAINT_DOMAIN,
+ con->contypid,
+ con->connamespace,
+ newname))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("constraint \"%s\" for domain \"%s\" already exists",
+ newname, format_type_be(con->contypid))));
+
+ /* OK, do the rename --- tuple is a copy, so OK to scribble on it */
+ namestrcpy(&(con->conname), newname);
+
+ simple_heap_update(conDesc, &tuple->t_self, tuple);
+
+ /* update the system catalog indexes */
+ CatalogUpdateIndexes(conDesc, tuple);
+
+ heap_freetuple(tuple);
heap_close(conDesc, RowExclusiveLock);
}
+
+/*
+ * AlterConstraintNamespaces
+ * Find any constraints belonging to the specified object,
+ * and move them to the specified new namespace.
+ *
+ * isType indicates whether the owning object is a type or a relation.
+ */
+void
+AlterConstraintNamespaces(Oid ownerId, Oid oldNspId,
+ Oid newNspId, bool isType)
+{
+ Relation conRel;
+ ScanKeyData key[1];
+ SysScanDesc scan;
+ HeapTuple tup;
+
+ conRel = heap_open(ConstraintRelationId, RowExclusiveLock);
+
+ if (isType)
+ {
+ ScanKeyInit(&key[0],
+ Anum_pg_constraint_contypid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(ownerId));
+
+ scan = systable_beginscan(conRel, ConstraintTypidIndexId, true,
+ SnapshotNow, 1, key);
+ }
+ else
+ {
+ ScanKeyInit(&key[0],
+ Anum_pg_constraint_conrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(ownerId));
+
+ scan = systable_beginscan(conRel, ConstraintRelidIndexId, true,
+ SnapshotNow, 1, key);
+ }
+
+ while (HeapTupleIsValid((tup = systable_getnext(scan))))
+ {
+ Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(tup);
+
+ if (conform->connamespace == oldNspId)
+ {
+ tup = heap_copytuple(tup);
+ conform = (Form_pg_constraint) GETSTRUCT(tup);
+
+ conform->connamespace = newNspId;
+
+ simple_heap_update(conRel, &tup->t_self, tup);
+ CatalogUpdateIndexes(conRel, tup);
+
+ /*
+ * Note: currently, the constraint will not have its own
+ * dependency on the namespace, so we don't need to do
+ * changeDependencyFor().
+ */
+ }
+ }
+
+ systable_endscan(scan);
+
+ heap_close(conRel, RowExclusiveLock);
+}