2002-07-12 20:43:19 +02:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* pg_constraint.c
|
|
|
|
* routines to support manipulation of the pg_constraint relation
|
|
|
|
*
|
|
|
|
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
|
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
2002-07-16 07:53:34 +02:00
|
|
|
* $Header: /cvsroot/pgsql/src/backend/catalog/pg_constraint.c,v 1.2 2002/07/16 05:53:33 tgl Exp $
|
2002-07-12 20:43:19 +02:00
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
|
|
|
|
#include "access/heapam.h"
|
|
|
|
#include "access/genam.h"
|
|
|
|
#include "catalog/catalog.h"
|
|
|
|
#include "catalog/catname.h"
|
|
|
|
#include "catalog/dependency.h"
|
|
|
|
#include "catalog/indexing.h"
|
|
|
|
#include "catalog/pg_constraint.h"
|
|
|
|
#include "miscadmin.h"
|
|
|
|
#include "utils/array.h"
|
|
|
|
#include "utils/builtins.h"
|
|
|
|
#include "utils/fmgroids.h"
|
|
|
|
#include "utils/syscache.h"
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* CreateConstraintEntry
|
|
|
|
* Create a constraint table entry.
|
|
|
|
*
|
|
|
|
* Subsidiary records (such as triggers or indexes to implement the
|
|
|
|
* constraint) are *not* created here. But we do make dependency links
|
|
|
|
* from the constraint to the things it depends on.
|
|
|
|
*/
|
|
|
|
Oid
|
|
|
|
CreateConstraintEntry(const char *constraintName,
|
|
|
|
Oid constraintNamespace,
|
|
|
|
char constraintType,
|
|
|
|
bool isDeferrable,
|
|
|
|
bool isDeferred,
|
|
|
|
Oid relId,
|
|
|
|
const int16 *constraintKey,
|
|
|
|
int constraintNKeys,
|
|
|
|
Oid domainId,
|
|
|
|
Oid foreignRelId,
|
|
|
|
const int16 *foreignKey,
|
|
|
|
int foreignNKeys,
|
|
|
|
char foreignUpdateType,
|
|
|
|
char foreignDeleteType,
|
|
|
|
char foreignMatchType,
|
2002-07-16 07:53:34 +02:00
|
|
|
Node *conExpr,
|
2002-07-12 20:43:19 +02:00
|
|
|
const char *conBin,
|
|
|
|
const char *conSrc)
|
|
|
|
{
|
|
|
|
Relation conDesc;
|
|
|
|
Oid conOid;
|
|
|
|
HeapTuple tup;
|
|
|
|
char nulls[Natts_pg_constraint];
|
|
|
|
Datum values[Natts_pg_constraint];
|
|
|
|
ArrayType *conkeyArray;
|
|
|
|
ArrayType *confkeyArray;
|
|
|
|
NameData cname;
|
|
|
|
int i;
|
|
|
|
ObjectAddress conobject;
|
|
|
|
|
|
|
|
conDesc = heap_openr(ConstraintRelationName, RowExclusiveLock);
|
|
|
|
|
|
|
|
Assert(constraintName);
|
|
|
|
namestrcpy(&cname, constraintName);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert C arrays into Postgres arrays.
|
|
|
|
*/
|
|
|
|
if (constraintNKeys > 0)
|
|
|
|
{
|
|
|
|
Datum *conkey;
|
|
|
|
|
|
|
|
conkey = (Datum *) palloc(constraintNKeys * sizeof(Datum));
|
|
|
|
for (i = 0; i < constraintNKeys; i++)
|
|
|
|
conkey[i] = Int16GetDatum(constraintKey[i]);
|
|
|
|
conkeyArray = construct_array(conkey, constraintNKeys,
|
|
|
|
true, 2, 's');
|
|
|
|
}
|
|
|
|
else
|
|
|
|
conkeyArray = NULL;
|
|
|
|
|
|
|
|
if (foreignNKeys > 0)
|
|
|
|
{
|
|
|
|
Datum *confkey;
|
|
|
|
|
|
|
|
confkey = (Datum *) palloc(foreignNKeys * sizeof(Datum));
|
|
|
|
for (i = 0; i < foreignNKeys; i++)
|
|
|
|
confkey[i] = Int16GetDatum(foreignKey[i]);
|
|
|
|
confkeyArray = construct_array(confkey, foreignNKeys,
|
|
|
|
true, 2, 's');
|
|
|
|
}
|
|
|
|
else
|
|
|
|
confkeyArray = NULL;
|
|
|
|
|
|
|
|
/* initialize nulls and values */
|
|
|
|
for (i = 0; i < Natts_pg_constraint; i++)
|
|
|
|
{
|
|
|
|
nulls[i] = ' ';
|
|
|
|
values[i] = (Datum) NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
values[Anum_pg_constraint_conname - 1] = NameGetDatum(&cname);
|
|
|
|
values[Anum_pg_constraint_connamespace - 1] = ObjectIdGetDatum(constraintNamespace);
|
|
|
|
values[Anum_pg_constraint_contype - 1] = CharGetDatum(constraintType);
|
|
|
|
values[Anum_pg_constraint_condeferrable - 1] = BoolGetDatum(isDeferrable);
|
|
|
|
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_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);
|
|
|
|
|
|
|
|
if (conkeyArray)
|
|
|
|
values[Anum_pg_constraint_conkey - 1] = PointerGetDatum(conkeyArray);
|
|
|
|
else
|
|
|
|
nulls[Anum_pg_constraint_conkey - 1] = 'n';
|
|
|
|
|
|
|
|
if (confkeyArray)
|
|
|
|
values[Anum_pg_constraint_confkey - 1] = PointerGetDatum(confkeyArray);
|
|
|
|
else
|
|
|
|
nulls[Anum_pg_constraint_confkey - 1] = 'n';
|
|
|
|
|
|
|
|
/*
|
|
|
|
* initialize the binary form of the check constraint.
|
|
|
|
*/
|
|
|
|
if (conBin)
|
|
|
|
values[Anum_pg_constraint_conbin - 1] = DirectFunctionCall1(textin,
|
|
|
|
CStringGetDatum(conBin));
|
|
|
|
else
|
|
|
|
nulls[Anum_pg_constraint_conbin - 1] = 'n';
|
|
|
|
|
|
|
|
/*
|
|
|
|
* initialize the text form of the check constraint
|
|
|
|
*/
|
|
|
|
if (conSrc)
|
|
|
|
values[Anum_pg_constraint_consrc - 1] = DirectFunctionCall1(textin,
|
|
|
|
CStringGetDatum(conSrc));
|
|
|
|
else
|
|
|
|
nulls[Anum_pg_constraint_consrc - 1] = 'n';
|
|
|
|
|
|
|
|
tup = heap_formtuple(RelationGetDescr(conDesc), values, nulls);
|
|
|
|
|
|
|
|
conOid = simple_heap_insert(conDesc, tup);
|
|
|
|
|
|
|
|
/* Handle Indices */
|
|
|
|
if (RelationGetForm(conDesc)->relhasindex)
|
|
|
|
{
|
|
|
|
Relation idescs[Num_pg_constraint_indices];
|
|
|
|
|
|
|
|
CatalogOpenIndices(Num_pg_constraint_indices, Name_pg_constraint_indices, idescs);
|
|
|
|
CatalogIndexInsert(idescs, Num_pg_constraint_indices, conDesc, tup);
|
|
|
|
CatalogCloseIndices(Num_pg_constraint_indices, idescs);
|
|
|
|
}
|
|
|
|
|
|
|
|
conobject.classId = RelationGetRelid(conDesc);
|
|
|
|
conobject.objectId = conOid;
|
|
|
|
conobject.objectSubId = 0;
|
|
|
|
|
|
|
|
heap_close(conDesc, RowExclusiveLock);
|
|
|
|
|
|
|
|
if (OidIsValid(relId))
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* 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.objectId = relId;
|
|
|
|
if (constraintNKeys > 0)
|
|
|
|
{
|
|
|
|
for (i = 0; i < constraintNKeys; i++)
|
|
|
|
{
|
|
|
|
relobject.objectSubId = constraintKey[i];
|
|
|
|
|
|
|
|
recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
relobject.objectSubId = 0;
|
|
|
|
|
|
|
|
recordDependencyOn(&conobject, &relobject, DEPENDENCY_AUTO);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (OidIsValid(foreignRelId))
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Register dependency from constraint to foreign relation,
|
|
|
|
* or to specific column(s) if any are mentioned.
|
|
|
|
*
|
|
|
|
* In normal case of two separate relations, make this a NORMAL
|
|
|
|
* dependency (so dropping the FK table would require CASCADE).
|
|
|
|
* However, for a self-reference just make it AUTO.
|
|
|
|
*/
|
|
|
|
DependencyType deptype;
|
|
|
|
ObjectAddress relobject;
|
|
|
|
|
|
|
|
deptype = (foreignRelId == relId) ? DEPENDENCY_AUTO : DEPENDENCY_NORMAL;
|
|
|
|
relobject.classId = RelOid_pg_class;
|
|
|
|
relobject.objectId = foreignRelId;
|
|
|
|
if (foreignNKeys > 0)
|
|
|
|
{
|
|
|
|
for (i = 0; i < foreignNKeys; i++)
|
|
|
|
{
|
|
|
|
relobject.objectSubId = foreignKey[i];
|
|
|
|
|
|
|
|
recordDependencyOn(&conobject, &relobject, deptype);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
relobject.objectSubId = 0;
|
|
|
|
|
|
|
|
recordDependencyOn(&conobject, &relobject, deptype);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2002-07-16 07:53:34 +02:00
|
|
|
if (conExpr != NULL)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Register dependencies from constraint to objects mentioned
|
|
|
|
* in CHECK expression. We gin up a rather bogus rangetable
|
|
|
|
* list to handle any Vars in the constraint.
|
|
|
|
*/
|
|
|
|
RangeTblEntry rte;
|
|
|
|
|
|
|
|
MemSet(&rte, 0, sizeof(rte));
|
|
|
|
rte.type = T_RangeTblEntry;
|
|
|
|
rte.rtekind = RTE_RELATION;
|
|
|
|
rte.relid = relId;
|
|
|
|
|
|
|
|
recordDependencyOnExpr(&conobject, conExpr, makeList1(&rte),
|
|
|
|
DEPENDENCY_NORMAL);
|
|
|
|
}
|
|
|
|
|
2002-07-12 20:43:19 +02:00
|
|
|
return conOid;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Test whether given name is currently used as a constraint name
|
|
|
|
* for the given relation.
|
|
|
|
*
|
|
|
|
* NB: Caller should hold exclusive lock on the given relation, else
|
|
|
|
* this test is not very meaningful.
|
|
|
|
*/
|
|
|
|
bool
|
|
|
|
ConstraintNameIsUsed(Oid relId, Oid relNamespace, const char *cname)
|
|
|
|
{
|
|
|
|
bool found;
|
|
|
|
Relation conDesc;
|
|
|
|
SysScanDesc conscan;
|
|
|
|
ScanKeyData skey[2];
|
|
|
|
HeapTuple tup;
|
|
|
|
|
|
|
|
conDesc = heap_openr(ConstraintRelationName, RowExclusiveLock);
|
|
|
|
|
|
|
|
found = false;
|
|
|
|
|
|
|
|
ScanKeyEntryInitialize(&skey[0], 0x0,
|
|
|
|
Anum_pg_constraint_conname, F_NAMEEQ,
|
|
|
|
CStringGetDatum(cname));
|
|
|
|
|
|
|
|
ScanKeyEntryInitialize(&skey[1], 0x0,
|
|
|
|
Anum_pg_constraint_connamespace, F_OIDEQ,
|
|
|
|
ObjectIdGetDatum(relNamespace));
|
|
|
|
|
|
|
|
conscan = systable_beginscan(conDesc, ConstraintNameNspIndex, true,
|
|
|
|
SnapshotNow, 2, skey);
|
|
|
|
|
|
|
|
while (HeapTupleIsValid(tup = systable_getnext(conscan)))
|
|
|
|
{
|
|
|
|
Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tup);
|
|
|
|
|
|
|
|
if (con->conrelid == relId)
|
|
|
|
{
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
systable_endscan(conscan);
|
|
|
|
heap_close(conDesc, RowExclusiveLock);
|
|
|
|
|
|
|
|
return found;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Generate a currently-unused constraint name for the given relation.
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
* NB: Caller should hold exclusive lock on the given relation, else
|
|
|
|
* someone else might choose the same name concurrently!
|
|
|
|
*/
|
|
|
|
char *
|
|
|
|
GenerateConstraintName(Oid relId, Oid relNamespace, int *counter)
|
|
|
|
{
|
|
|
|
bool found;
|
|
|
|
Relation conDesc;
|
|
|
|
char *cname;
|
|
|
|
|
|
|
|
cname = (char *) palloc(NAMEDATALEN * sizeof(char));
|
|
|
|
|
|
|
|
conDesc = heap_openr(ConstraintRelationName, RowExclusiveLock);
|
|
|
|
|
|
|
|
/* Loop until we find a non-conflicting constraint name */
|
|
|
|
/* We assume there will be one eventually ... */
|
|
|
|
do
|
|
|
|
{
|
|
|
|
SysScanDesc conscan;
|
|
|
|
ScanKeyData skey[2];
|
|
|
|
HeapTuple tup;
|
|
|
|
|
|
|
|
++(*counter);
|
|
|
|
snprintf(cname, NAMEDATALEN, "$%d", *counter);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This duplicates ConstraintNameIsUsed() so that we can avoid
|
|
|
|
* re-opening pg_constraint for each iteration.
|
|
|
|
*/
|
|
|
|
found = false;
|
|
|
|
|
|
|
|
ScanKeyEntryInitialize(&skey[0], 0x0,
|
|
|
|
Anum_pg_constraint_conname, F_NAMEEQ,
|
|
|
|
CStringGetDatum(cname));
|
|
|
|
|
|
|
|
ScanKeyEntryInitialize(&skey[1], 0x0,
|
|
|
|
Anum_pg_constraint_connamespace, F_OIDEQ,
|
|
|
|
ObjectIdGetDatum(relNamespace));
|
|
|
|
|
|
|
|
conscan = systable_beginscan(conDesc, ConstraintNameNspIndex, true,
|
|
|
|
SnapshotNow, 2, skey);
|
|
|
|
|
|
|
|
while (HeapTupleIsValid(tup = systable_getnext(conscan)))
|
|
|
|
{
|
|
|
|
Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tup);
|
|
|
|
|
|
|
|
if (con->conrelid == relId)
|
|
|
|
{
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
systable_endscan(conscan);
|
|
|
|
} while (found);
|
|
|
|
|
|
|
|
heap_close(conDesc, RowExclusiveLock);
|
|
|
|
|
|
|
|
return cname;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Delete a single constraint record.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
RemoveConstraintById(Oid conId)
|
|
|
|
{
|
|
|
|
Relation conDesc;
|
|
|
|
ScanKeyData skey[1];
|
|
|
|
SysScanDesc conscan;
|
|
|
|
HeapTuple tup;
|
|
|
|
Form_pg_constraint con;
|
|
|
|
|
|
|
|
conDesc = heap_openr(ConstraintRelationName, RowExclusiveLock);
|
|
|
|
|
|
|
|
ScanKeyEntryInitialize(&skey[0], 0x0,
|
|
|
|
ObjectIdAttributeNumber, F_OIDEQ,
|
|
|
|
ObjectIdGetDatum(conId));
|
|
|
|
|
|
|
|
conscan = systable_beginscan(conDesc, ConstraintOidIndex, true,
|
|
|
|
SnapshotNow, 1, skey);
|
|
|
|
|
|
|
|
tup = systable_getnext(conscan);
|
|
|
|
if (!HeapTupleIsValid(tup))
|
|
|
|
elog(ERROR, "RemoveConstraintById: constraint %u not found",
|
|
|
|
conId);
|
|
|
|
con = (Form_pg_constraint) GETSTRUCT(tup);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the constraint is for a relation, open and exclusive-lock
|
|
|
|
* the relation it's for.
|
|
|
|
*
|
|
|
|
* XXX not clear what we should lock, if anything, for other constraints.
|
|
|
|
*/
|
|
|
|
if (OidIsValid(con->conrelid))
|
|
|
|
{
|
|
|
|
Relation rel;
|
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
if (con->contype == CONSTRAINT_CHECK)
|
|
|
|
{
|
|
|
|
Relation pgrel;
|
|
|
|
HeapTuple relTup;
|
|
|
|
Form_pg_class classForm;
|
|
|
|
Relation ridescs[Num_pg_class_indices];
|
|
|
|
|
|
|
|
pgrel = heap_openr(RelationRelationName, RowExclusiveLock);
|
|
|
|
relTup = SearchSysCacheCopy(RELOID,
|
|
|
|
ObjectIdGetDatum(con->conrelid),
|
|
|
|
0, 0, 0);
|
|
|
|
if (!HeapTupleIsValid(relTup))
|
|
|
|
elog(ERROR, "cache lookup of relation %u failed",
|
|
|
|
con->conrelid);
|
|
|
|
classForm = (Form_pg_class) GETSTRUCT(relTup);
|
|
|
|
|
|
|
|
if (classForm->relchecks == 0)
|
|
|
|
elog(ERROR, "RemoveConstraintById: relation %s has relchecks = 0",
|
|
|
|
RelationGetRelationName(rel));
|
|
|
|
classForm->relchecks--;
|
|
|
|
|
|
|
|
simple_heap_update(pgrel, &relTup->t_self, relTup);
|
|
|
|
|
|
|
|
CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, ridescs);
|
|
|
|
CatalogIndexInsert(ridescs, Num_pg_class_indices, pgrel, relTup);
|
|
|
|
CatalogCloseIndices(Num_pg_class_indices, ridescs);
|
|
|
|
|
|
|
|
heap_freetuple(relTup);
|
|
|
|
|
|
|
|
heap_close(pgrel, RowExclusiveLock);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Keep lock on constraint's rel until end of xact */
|
|
|
|
heap_close(rel, NoLock);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Fry the constraint itself */
|
|
|
|
simple_heap_delete(conDesc, &tup->t_self);
|
|
|
|
|
|
|
|
/* Clean up */
|
|
|
|
systable_endscan(conscan);
|
|
|
|
heap_close(conDesc, RowExclusiveLock);
|
|
|
|
}
|