/*------------------------------------------------------------------------- * * pg_constraint.c * routines to support manipulation of the pg_namespace relation * * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * $Header$ * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/heapam.h" #include "access/genam.h" #include "catalog/catname.h" #include "catalog/indexing.h" #include "catalog/pg_constraint.h" #include "catalog/pg_namespace.h" #include "catalog/pg_depend.h" #include "commands/trigger.h" #include "nodes/makefuncs.h" #include "nodes/parsenodes.h" #include "parser/gramparse.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/syscache.h" /* * ConstraintCreate * Create the constraint table portion as well as any dependencies. */ Oid constraintCreate(Oid relId, const char *constraintName, char constraintType, bool isDeferrable, bool isDeferred, const AttrNumber *constraintKey, int constraintNKeys, Oid foreignRelId, const AttrNumber *foreignKey, int foreignNKeys, char foreignUpdateType, char foreignDeleteType, char foreignMatchType, char *conBin, char *conSrc) { Relation conDesc; HeapTuple tup; char nulls[Natts_pg_constraint]; Datum values[Natts_pg_constraint]; int i = 0; Oid conOid; Datum conkey[constraintNKeys]; Datum confkey[foreignNKeys]; SysScanDesc rcscan; ScanKeyData skey[2]; ObjectAddress myself, dependee; NameData cname; conDesc = heap_openr(ConstraintRelationName, RowExclusiveLock); /* sanity checks */ if (!constraintName) { namestrcpy(&cname, getConstraintName(relId)); } else { namestrcpy(&cname, constraintName); /* make sure there is no existing constraints of the same name */ ScanKeyEntryInitialize(&skey[i++], 0, Anum_pg_constraint_conrelid, F_OIDEQ, ObjectIdGetDatum(relId)); ScanKeyEntryInitialize(&skey[i++], 0, Anum_pg_constraint_conname, F_NAMEEQ, NameGetDatum(&cname)); rcscan = systable_beginscan(conDesc, ConstraintRelidNameIndex, true, SnapshotNow, i, skey); tup = systable_getnext(rcscan); if (HeapTupleIsValid(tup)) elog(ERROR, "constraint \"%s\" already exists", NameStr(cname)); systable_endscan(rcscan); } /* Build Datum array for ConstraintKey */ for (i = 0; i < constraintNKeys; i++) conkey[i] = Int16GetDatum(constraintKey[i]); /* * Build Datum array for foreignKey. Use a * placeholder entry if otherwise NULL. */ if (foreignNKeys && foreignNKeys > 0) for (i = 0; i < foreignNKeys; i++) confkey[i] = Int16GetDatum(foreignKey[i]); else confkey[0] = Int16GetDatum(0); /* initialize nulls and values */ for (i = 0; i < Natts_pg_constraint; i++) { nulls[i] = ' '; values[i] = (Datum) NULL; } values[Anum_pg_constraint_conrelid - 1] = ObjectIdGetDatum(relId); values[Anum_pg_constraint_conname - 1] = NameGetDatum(&cname); 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_conkey - 1] = PointerGetDatum(construct_array(conkey, constraintNKeys, true, 2, 'i')); /* Record what we were given, or a placeholder if NULL */ if (foreignRelId) values[Anum_pg_constraint_confrelid - 1] = ObjectIdGetDatum(foreignRelId); else values[Anum_pg_constraint_confrelid - 1] = ObjectIdGetDatum(InvalidOid); /* Record what we were given, or a placeholder if NULL */ values[Anum_pg_constraint_confkey - 1] = PointerGetDatum(construct_array(confkey, foreignNKeys > 0 ? foreignNKeys : 1, true, 2, 'i')); /* Record what we were given, or a placeholder if NULL */ if (foreignUpdateType) values[Anum_pg_constraint_confupdtype - 1] = CharGetDatum(foreignUpdateType); else values[Anum_pg_constraint_confupdtype - 1] = CharGetDatum(CONSTRAINT_FKEY_RESTRICT); /* Record what we were given, or a placeholder if NULL */ if (foreignDeleteType) values[Anum_pg_constraint_confdeltype - 1] = CharGetDatum(foreignDeleteType); else values[Anum_pg_constraint_confdeltype - 1] = CharGetDatum(CONSTRAINT_FKEY_RESTRICT); /* Record what we were given, or a placeholder if NULL */ if (foreignMatchType) values[Anum_pg_constraint_confmatchtype - 1] = CharGetDatum(foreignMatchType); else values[Anum_pg_constraint_confmatchtype - 1] = CharGetDatum(CONSTRAINT_FKEY_FULL); /* * 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); if (!HeapTupleIsValid(tup)) elog(ERROR, "ConstraintCreate: heap_formtuple failed"); conOid = simple_heap_insert(conDesc, tup); if (!OidIsValid(conOid)) elog(ERROR, "ConstraintCreate: heap_insert failed"); /* Handle Indicies */ 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); } /* * Handle Dependencies */ myself.classId = RelationGetRelid(conDesc); myself.objectId = conOid; myself.objectSubId = 0; /* The constraint depends on the relation */ dependee.classId = RelOid_pg_class; dependee.objectId = relId; dependee.objectSubId = 0; dependCreate(&myself, &dependee, true); /* * The constraint depends on the foreign relation columns * * Relation dependencies are skipped if we depend * directly on ourselves */ if (foreignNKeys && foreignNKeys > 0 && relId != foreignRelId) { Assert(foreignNKeys = constraintNKeys); for (i = 0; i < foreignNKeys; i++) { ObjectAddress depender; depender.classId = RelOid_pg_class; depender.objectId = relId; depender.objectSubId = conkey[i]; dependee.classId = RelOid_pg_class; dependee.objectId = foreignRelId; dependee.objectSubId = foreignKey[i]; dependCreate(&depender, &dependee, false); dependCreate(&myself, &dependee, true); } } /* * Create the required triggers to enforce the requested * foreign key constraint. Record dependencies of the * trigger to the FK Constraint. */ if (foreignNKeys && foreignNKeys > 0) { CreateTrigStmt *fk_trigger; Oid trigId; RangeVar *foreignRel; RangeVar *localRel; char *foreignNameSpace; char *localNameSpace; char *foreignRelation; char *localRelation; char *mType = "UNSPECIFIED"; /* Pull relation names */ foreignNameSpace = get_namespace_name(get_rel_namespace(foreignRelId)); localNameSpace = get_namespace_name(get_rel_namespace(relId)); foreignRelation = get_rel_name(foreignRelId); localRelation = get_rel_name(relId); localRel = makeRangeVar(localNameSpace, localRelation); foreignRel = makeRangeVar(foreignNameSpace, foreignRelation); /* Find the trigger name for match types */ switch (foreignMatchType) { case CONSTRAINT_FKEY_FULL: mType = "FULL"; break; case CONSTRAINT_FKEY_PARTIAL: mType = "PARTIAL"; break; case CONSTRAINT_FKEY_UNSPECIFIED: mType = "UNSPECIFIED"; break; default: elog(ERROR, "constraintCreate: Unknown MATCH TYPE"); } /* Double check keys align */ Assert(foreignNKeys = constraintNKeys); /* * Build a CREATE CONSTRAINT TRIGGER statement for the CHECK * action. */ fk_trigger = (CreateTrigStmt *) makeNode(CreateTrigStmt); fk_trigger->trigname = getTriggerName(relId, NameStr(cname)); fk_trigger->relation = localRel; fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins"); fk_trigger->before = false; fk_trigger->row = true; fk_trigger->actions[0] = 'i'; fk_trigger->actions[1] = 'u'; fk_trigger->actions[2] = '\0'; fk_trigger->lang = NULL; fk_trigger->text = NULL; fk_trigger->attr = NIL; fk_trigger->when = NULL; fk_trigger->isconstraint = true; fk_trigger->deferrable = isDeferrable; fk_trigger->initdeferred = isDeferred; fk_trigger->constrrel = foreignRel; fk_trigger->args = NIL; fk_trigger->args = lappend(fk_trigger->args, makeString(NameStr(cname))); fk_trigger->args = lappend(fk_trigger->args, makeString(localRel->relname)); fk_trigger->args = lappend(fk_trigger->args, makeString(foreignRel->relname)); fk_trigger->args = lappend(fk_trigger->args, makeString(mType)); for (i = 0; i < foreignNKeys; i++) { fk_trigger->args = lappend(fk_trigger->args, makeString(get_attname(relId, constraintKey[i]))); fk_trigger->args = lappend(fk_trigger->args, makeString(get_attname(foreignRelId, foreignKey[i]))); } trigId = CreateTrigger(fk_trigger); /* The trigger depends on the constraint */ dependee.classId = get_relname_relid(TriggerRelationName, PG_CATALOG_NAMESPACE);; dependee.objectId = trigId; dependee.objectSubId = 0; dependCreate(&dependee, &myself, true); /* * Bump the command counter to prevent the next trigger * from attempting to use the same name as the previous */ CommandCounterIncrement(); /* * Build a CREATE CONSTRAINT TRIGGER statement for the ON DELETE * action fired on the PK table !!! */ fk_trigger = (CreateTrigStmt *) makeNode(CreateTrigStmt); fk_trigger->trigname = getTriggerName(foreignRelId, NameStr(cname)); fk_trigger->relation = foreignRel; fk_trigger->before = false; fk_trigger->row = true; fk_trigger->actions[0] = 'd'; fk_trigger->actions[1] = '\0'; fk_trigger->lang = NULL; fk_trigger->text = NULL; fk_trigger->attr = NIL; fk_trigger->when = NULL; fk_trigger->isconstraint = true; fk_trigger->deferrable = isDeferrable; fk_trigger->initdeferred = isDeferred; fk_trigger->constrrel = localRel; switch (foreignDeleteType) { case CONSTRAINT_FKEY_NOACTION: fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del"); break; case CONSTRAINT_FKEY_RESTRICT: fk_trigger->deferrable = false; fk_trigger->initdeferred = false; fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del"); break; case CONSTRAINT_FKEY_CASCADE: fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del"); break; case CONSTRAINT_FKEY_NULL: fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del"); break; case CONSTRAINT_FKEY_DEFAULT: fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del"); break; } fk_trigger->args = NIL; fk_trigger->args = lappend(fk_trigger->args, makeString(NameStr(cname))); fk_trigger->args = lappend(fk_trigger->args, makeString(localRel->relname)); fk_trigger->args = lappend(fk_trigger->args, makeString(foreignRel->relname)); fk_trigger->args = lappend(fk_trigger->args, makeString(mType)); for (i = 0; i < foreignNKeys; i++) { fk_trigger->args = lappend(fk_trigger->args, makeString(get_attname(relId, constraintKey[i]))); fk_trigger->args = lappend(fk_trigger->args, makeString(get_attname(foreignRelId, foreignKey[i]))); } trigId = CreateTrigger(fk_trigger); /* The trigger depends on the constraint */ dependee.classId = get_relname_relid(TriggerRelationName, PG_CATALOG_NAMESPACE);; dependee.objectId = trigId; dependee.objectSubId = 0; dependCreate(&dependee, &myself, true); /* * Bump the command counter to prevent the next trigger * from attempting to use the same name as the previous */ CommandCounterIncrement(); /* * Build a CREATE CONSTRAINT TRIGGER statement for the ON UPDATE * action fired on the PK table !!! */ fk_trigger = (CreateTrigStmt *) makeNode(CreateTrigStmt); fk_trigger->trigname = getTriggerName(foreignRelId, NameStr(cname)); fk_trigger->relation = foreignRel; fk_trigger->before = false; fk_trigger->row = true; fk_trigger->actions[0] = 'u'; fk_trigger->actions[1] = '\0'; fk_trigger->lang = NULL; fk_trigger->text = NULL; fk_trigger->attr = NIL; fk_trigger->when = NULL; fk_trigger->isconstraint = true; fk_trigger->deferrable = isDeferrable; fk_trigger->initdeferred = isDeferred; fk_trigger->constrrel = localRel; switch (foreignUpdateType) { case CONSTRAINT_FKEY_NOACTION: fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd"); break; case CONSTRAINT_FKEY_RESTRICT: fk_trigger->deferrable = false; fk_trigger->initdeferred = false; fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd"); break; case CONSTRAINT_FKEY_CASCADE: fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd"); break; case CONSTRAINT_FKEY_NULL: fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd"); break; case CONSTRAINT_FKEY_DEFAULT: fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd"); break; } fk_trigger->args = NIL; fk_trigger->args = lappend(fk_trigger->args, makeString(NameStr(cname))); fk_trigger->args = lappend(fk_trigger->args, makeString(localRel->relname)); fk_trigger->args = lappend(fk_trigger->args, makeString(foreignRel->relname)); fk_trigger->args = lappend(fk_trigger->args, makeString(mType)); for (i = 0; i < foreignNKeys; i++) { fk_trigger->args = lappend(fk_trigger->args, makeString(get_attname(relId, constraintKey[i]))); fk_trigger->args = lappend(fk_trigger->args, makeString(get_attname(foreignRelId, foreignKey[i]))); } trigId = CreateTrigger(fk_trigger); /* The trigger depends on the constraint */ dependee.classId = get_relname_relid(TriggerRelationName, PG_CATALOG_NAMESPACE);; dependee.objectId = trigId; dependee.objectSubId = 0; dependCreate(&dependee, &myself, true); } /* Cleanup, but keep lock */ heap_close(conDesc, NoLock); return conOid; } char * getConstraintName(Oid relId) { int j = 1; bool success; Relation conDesc; HeapTuple tup; char *cname; cname = palloc(NAMEDATALEN * sizeof(char)); conDesc = heap_openr(ConstraintRelationName, RowExclusiveLock); /* Loop until we find a non-conflicting constraint name */ /* What happens if this loops forever? */ do { int i = 0; SysScanDesc rcscan; ScanKeyData skey[2]; success = false; snprintf(cname, NAMEDATALEN, "constraint_%d", j); /* make sure there is no existing constraints of the same name */ ScanKeyEntryInitialize(&skey[i++], 0, Anum_pg_constraint_conrelid, F_OIDEQ, ObjectIdGetDatum(relId)); ScanKeyEntryInitialize(&skey[i++], 0, Anum_pg_constraint_conname, F_NAMEEQ, NameGetDatum(cname)); rcscan = systable_beginscan(conDesc, ConstraintRelidNameIndex, true, SnapshotNow, i, skey); tup = systable_getnext(rcscan); if (!HeapTupleIsValid(tup)) success = true; systable_endscan(rcscan); ++j; } while (!success); /* Cleanup, but keep lock */ heap_close(conDesc, NoLock); return cname; } void DropConstraintById(Oid conId, int behavior) { ObjectAddress myself; Relation conDesc; HeapTuple tup; ScanKeyData skey[1]; SysScanDesc rcscan; int i = 0; Relation ridescs[Num_pg_class_indices]; Form_pg_constraint con; /* Better be a valid Id */ Assert(OidIsValid(conId)); /* CONSTRAINT */ ScanKeyEntryInitialize(&skey[i++], 0, ObjectIdAttributeNumber, F_OIDEQ, ObjectIdGetDatum(conId)); conDesc = heap_openr(ConstraintRelationName, RowExclusiveLock); rcscan = systable_beginscan(conDesc, ConstraintOidIndex, true, SnapshotNow, i, skey); tup = systable_getnext(rcscan); /* * Due to circular constraint dependencies we simply * skip the drop when we don't find the constraint rather * than the below: * * */ if (!HeapTupleIsValid(tup)) elog(ERROR, "Constraint OID %d missing", conId); con = (Form_pg_constraint) GETSTRUCT(tup); /* * Now we need to update the relcheck count * if it was a check constraint being dropped */ if (con->contype == CONSTRAINT_CHECK) { Relation rel; HeapTuple relTup; rel = heap_openr(RelationRelationName, RowExclusiveLock); relTup = SearchSysCache(RELOID, ObjectIdGetDatum(con->conrelid), 0, 0, 0); if (!HeapTupleIsValid(relTup)) elog(ERROR, "DropConstraintById: Relation Tuple non-existant"); ((Form_pg_class) GETSTRUCT(relTup))->relchecks -= 1; simple_heap_update(rel, &relTup->t_self, relTup); CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, ridescs); CatalogIndexInsert(ridescs, Num_pg_class_indices, rel, relTup); CatalogCloseIndices(Num_pg_class_indices, ridescs); ReleaseSysCache(relTup); heap_close(rel, RowExclusiveLock); } /* Fry the constraint itself*/ simple_heap_delete(conDesc, &tup->t_self); /* Clean up */ systable_endscan(rcscan); heap_close(conDesc, RowExclusiveLock); /* Deal with dependencies */ myself.classId = get_relname_relid(ConstraintRelationName, PG_CATALOG_NAMESPACE); myself.objectId = conId; myself.objectSubId = 0; dependDelete(&myself, behavior); };