*** pgsql/src/backend/commands/command.c Thu Aug 3 21:16:06 2000 --- pgsql.new/src/backend/commands/command.c Sat Aug 19 20:45:23 2000 *************** *** 44,52 **** #include "commands/view.h" #include "utils/temprel.h" #include "executor/spi_priv.h" #ifdef _DROP_COLUMN_HACK__ - #include "catalog/pg_index.h" #include "parser/parse.h" #endif /* _DROP_COLUMN_HACK__ */ #include "access/genam.h" --- 44,53 ---- #include "commands/view.h" #include "utils/temprel.h" #include "executor/spi_priv.h" + #include "catalog/pg_index.h" + #include "utils/relcache.h" #ifdef _DROP_COLUMN_HACK__ #include "parser/parse.h" #endif /* _DROP_COLUMN_HACK__ */ #include "access/genam.h" *************** *** 1234,1245 **** case T_FkConstraint: { FkConstraint *fkconstraint = (FkConstraint *) newConstraint; ! Relation rel; HeapScanDesc scan; HeapTuple tuple; Trigger trig; List *list; int count; if (get_temp_rel_by_username(fkconstraint->pktable_name)!=NULL && get_temp_rel_by_username(relationName)==NULL) { --- 1235,1252 ---- case T_FkConstraint: { FkConstraint *fkconstraint = (FkConstraint *) newConstraint; ! Relation rel, pkrel; HeapScanDesc scan; HeapTuple tuple; Trigger trig; List *list; int count; + List *indexoidlist, + *indexoidscan; + Form_pg_index indexStruct = NULL; + Form_pg_attribute *rel_attrs = NULL; + int i; + int found=0; if (get_temp_rel_by_username(fkconstraint->pktable_name)!=NULL && get_temp_rel_by_username(relationName)==NULL) { *************** *** 1266,1273 **** * doesn't delete rows out from under us. */ ! rel = heap_openr(fkconstraint->pktable_name, AccessExclusiveLock); ! heap_close(rel, NoLock); /* * Grab an exclusive lock on the fk table, and then scan --- 1273,1282 ---- * doesn't delete rows out from under us. */ ! pkrel = heap_openr(fkconstraint->pktable_name, AccessExclusiveLock); ! if (pkrel == NULL) ! elog(ERROR, "referenced table \"%s\" not found", ! fkconstraint->pktable_name); /* * Grab an exclusive lock on the fk table, and then scan *************** *** 1277,1282 **** --- 1286,1367 ---- * and that's that. */ rel = heap_openr(relationName, AccessExclusiveLock); + if (rel == NULL) + elog(ERROR, "table \"%s\" not found", + relationName); + + /* First we check for limited correctness of the constraint */ + + rel_attrs = pkrel->rd_att->attrs; + indexoidlist = RelationGetIndexList(pkrel); + + foreach(indexoidscan, indexoidlist) + { + Oid indexoid = lfirsti(indexoidscan); + HeapTuple indexTuple; + List *attrl; + indexTuple = SearchSysCacheTuple(INDEXRELID, + ObjectIdGetDatum(indexoid), + 0, 0, 0); + if (!HeapTupleIsValid(indexTuple)) + elog(ERROR, "transformFkeyGetPrimaryKey: index %u not found", + indexoid); + indexStruct = (Form_pg_index) GETSTRUCT(indexTuple); + + if (indexStruct->indisunique) { + /* go through the fkconstraint->pk_attrs list */ + foreach(attrl, fkconstraint->pk_attrs) { + Ident *attr=lfirst(attrl); + found=0; + for (i = 0; i < INDEX_MAX_KEYS && indexStruct->indkey[i] != 0; i++) + { + int pkattno = indexStruct->indkey[i]; + if (pkattno>0) { + char *name = NameStr(rel_attrs[pkattno-1]->attname); + if (strcmp(name, attr->name)==0) { + found=1; + break; + } + } + } + if (!found) + break; + } + } + if (found) + break; + indexStruct = NULL; + } + if (!found) + elog(ERROR, "UNIQUE constraint matching given keys for referenced table \"%s\" not found", + fkconstraint->pktable_name); + + freeList(indexoidlist); + heap_close(pkrel, NoLock); + + rel_attrs = rel->rd_att->attrs; + if (fkconstraint->fk_attrs!=NIL) { + int found=0; + List *fkattrs; + Ident *fkattr; + foreach(fkattrs, fkconstraint->fk_attrs) { + int count=0; + found=0; + fkattr=lfirst(fkattrs); + for (; count < rel->rd_att->natts; count++) { + char *name = NameStr(rel->rd_att->attrs[count]->attname); + if (strcmp(name, fkattr->name)==0) { + found=1; + break; + } + } + if (!found) + break; + } + if (!found) + elog(ERROR, "columns referenced in foreign key constraint not found."); + } + trig.tgoid = 0; if (fkconstraint->constr_name) trig.tgname = fkconstraint->constr_name; *** pgsql/src/backend/parser/analyze.c Fri Aug 11 16:45:27 2000 --- pgsql.new/src/backend/parser/analyze.c Sat Aug 19 20:47:13 2000 *************** *** 48,53 **** --- 48,54 ---- static void transformFkeyGetPrimaryKey(FkConstraint *fkconstraint); static void transformConstraintAttrs(List *constraintList); static void transformColumnType(ParseState *pstate, ColumnDef *column); + static void transformFkeyCheckAttrs(FkConstraint *fkconstraint); /* kluge to return extra info from transformCreateStmt() */ static List *extras_before; *************** *** 1051,1056 **** --- 1052,1084 ---- if (fkconstraint->constr_name == NULL) fkconstraint->constr_name = ""; + /* + * Check to see if the attributes mentioned by the constraint + * actually exist on this table. + */ + if (fkconstraint->fk_attrs!=NIL) { + int found=0; + List *cols; + List *fkattrs; + Ident *fkattr; + ColumnDef *col; + foreach(fkattrs, fkconstraint->fk_attrs) { + found=0; + fkattr=lfirst(fkattrs); + foreach(cols, columns) { + col=lfirst(cols); + if (strcmp(col->colname, fkattr->name)==0) { + found=1; + break; + } + } + if (!found) + break; + } + if (!found) + elog(ERROR, "columns referenced in foreign key constraint not found."); + } + /* * If the attribute list for the referenced table was omitted, * lookup for the definition of the primary key. If the *************** *** 1085,1091 **** fkconstraint->pktable_name); } } ! /* * Build a CREATE CONSTRAINT TRIGGER statement for the CHECK * action. --- 1113,1155 ---- fkconstraint->pktable_name); } } ! else { ! if (strcmp(fkconstraint->pktable_name, stmt->relname)!=0) ! transformFkeyCheckAttrs(fkconstraint); ! else { ! /* Get a unique/pk constraint from above */ ! List *index; ! int found=0; ! foreach(index, ilist) ! { ! IndexStmt *ind=lfirst(index); ! IndexElem *indparm; ! List *indparms; ! List *pkattrs; ! Ident *pkattr; ! if (ind->unique) { ! foreach(pkattrs, fkconstraint->pk_attrs) { ! found=0; ! pkattr=lfirst(pkattrs); ! foreach(indparms, ind->indexParams) { ! indparm=lfirst(indparms); ! if (strcmp(indparm->name, pkattr->name)==0) { ! found=1; ! break; ! } ! } ! if (!found) ! break; ! } ! } ! if (found) ! break; ! } ! if (!found) ! elog(ERROR, "UNIQUE constraint matching given keys for referenced table \"%s\" not found", ! fkconstraint->pktable_name); ! } ! } /* * Build a CREATE CONSTRAINT TRIGGER statement for the CHECK * action. *************** *** 2015,2020 **** --- 2079,2167 ---- } qry->rowMark = rowMark; + } + + + /* + * transformFkeyCheckAttrs - + * + * Try to make sure that the attributes of a referenced table + * belong to a unique (or primary key) constraint. + * + */ + static void + transformFkeyCheckAttrs(FkConstraint *fkconstraint) + { + Relation pkrel; + Form_pg_attribute *pkrel_attrs; + List *indexoidlist, + *indexoidscan; + Form_pg_index indexStruct = NULL; + int i; + int found=0; + + /* ---------- + * Open the referenced table and get the attributes list + * ---------- + */ + pkrel = heap_openr(fkconstraint->pktable_name, AccessShareLock); + if (pkrel == NULL) + elog(ERROR, "referenced table \"%s\" not found", + fkconstraint->pktable_name); + pkrel_attrs = pkrel->rd_att->attrs; + + /* ---------- + * Get the list of index OIDs for the table from the relcache, + * and look up each one in the pg_index syscache for each unique + * one, and then compare the attributes we were given to those + * defined. + * ---------- + */ + indexoidlist = RelationGetIndexList(pkrel); + + foreach(indexoidscan, indexoidlist) + { + Oid indexoid = lfirsti(indexoidscan); + HeapTuple indexTuple; + List *attrl; + indexTuple = SearchSysCacheTuple(INDEXRELID, + ObjectIdGetDatum(indexoid), + 0, 0, 0); + if (!HeapTupleIsValid(indexTuple)) + elog(ERROR, "transformFkeyGetPrimaryKey: index %u not found", + indexoid); + indexStruct = (Form_pg_index) GETSTRUCT(indexTuple); + + if (indexStruct->indisunique) { + /* go through the fkconstraint->pk_attrs list */ + foreach(attrl, fkconstraint->pk_attrs) { + Ident *attr=lfirst(attrl); + found=0; + for (i = 0; i < INDEX_MAX_KEYS && indexStruct->indkey[i] != 0; i++) + { + int pkattno = indexStruct->indkey[i]; + if (pkattno>0) { + char *name = NameStr(pkrel_attrs[pkattno - 1]->attname); + if (strcmp(name, attr->name)==0) { + found=1; + break; + } + } + } + if (!found) + break; + } + } + if (found) + break; + indexStruct = NULL; + } + if (!found) + elog(ERROR, "UNIQUE constraint matching given keys for referenced table \"%s\" not found", + fkconstraint->pktable_name); + + freeList(indexoidlist); + heap_close(pkrel, AccessShareLock); } *** pgsql/src/test/regress/sql/alter_table.sql Tue Mar 14 15:06:58 2000 --- pgsql.new/src/test/regress/sql/alter_table.sql Sat Aug 19 21:11:47 2000 *************** *** 180,185 **** --- 180,191 ---- INSERT INTO tmp3 values (1,20); INSERT INTO tmp3 values (5,50); + -- Try (and fail) to add constraint due to invalid source columns + ALTER TABLE tmp3 add constraint tmpconstr foreign key(c) references tmp2 match full; + + -- Try (and fail) to add constraint due to invalide destination columns explicitly given + ALTER TABLE tmp3 add constraint tmpconstr foreign key(a) references tmp2(b) match full; + -- Try (and fail) to add constraint due to invalid data ALTER TABLE tmp3 add constraint tmpconstr foreign key (a) references tmp2 match full; *** pgsql/src/test/regress/sql/foreign_key.sql Thu Feb 24 08:02:28 2000 --- pgsql.new/src/test/regress/sql/foreign_key.sql Sun Aug 20 01:07:43 2000 *************** *** 411,414 **** --- 411,420 ---- DROP TABLE FKTABLE; DROP TABLE PKTABLE; + CREATE TABLE PKTABLE (ptest1 int PRIMARY KEY); + CREATE TABLE FKTABLE_FAIL1 ( ftest1 int, CONSTRAINT fkfail1 FOREIGN KEY (ftest2) REFERENCES PKTABLE); + CREATE TABLE FKTABLE_FAIL2 ( ftest1 int, CONSTRAINT fkfail1 FOREIGN KEY (ftest1) REFERENCES PKTABLE(ptest2)); + DROP TABLE FKTABLE_FAIL1; + DROP TABLE FKTABLE_FAIL2; + DROP TABLE PKTABLE; *** pgsql/src/test/regress/expected/alter_table.out Sun Jul 16 12:27:29 2000 --- pgsql.new/src/test/regress/expected/alter_table.out Sat Aug 19 21:37:05 2000 *************** *** 282,287 **** --- 282,295 ---- INSERT INTO tmp3 values (1,10); INSERT INTO tmp3 values (1,20); INSERT INTO tmp3 values (5,50); + -- Try (and fail) to add constraint due to invalid source columns + ALTER TABLE tmp3 add constraint tmpconstr foreign key(c) references tmp2 match full; + NOTICE: ALTER TABLE ... ADD CONSTRAINT will create implicit trigger(s) for FOREIGN KEY check(s) + ERROR: columns referenced in foreign key constraint not found. + -- Try (and fail) to add constraint due to invalide destination columns explicitly given + ALTER TABLE tmp3 add constraint tmpconstr foreign key(a) references tmp2(b) match full; + NOTICE: ALTER TABLE ... ADD CONSTRAINT will create implicit trigger(s) for FOREIGN KEY check(s) + ERROR: UNIQUE constraint matching given keys for referenced table "tmp2" not found -- Try (and fail) to add constraint due to invalid data ALTER TABLE tmp3 add constraint tmpconstr foreign key (a) references tmp2 match full; NOTICE: ALTER TABLE ... ADD CONSTRAINT will create implicit trigger(s) for FOREIGN KEY check(s) *** pgsql/src/test/regress/expected/foreign_key.out Thu Feb 24 08:02:27 2000 --- pgsql.new/src/test/regress/expected/foreign_key.out Sun Aug 20 01:15:55 2000 *************** *** 690,692 **** --- 690,705 ---- NOTICE: DROP TABLE implicitly drops referential integrity trigger from table "pktable" NOTICE: DROP TABLE implicitly drops referential integrity trigger from table "pktable" DROP TABLE PKTABLE; + CREATE TABLE PKTABLE (ptest1 int PRIMARY KEY); + NOTICE: CREATE TABLE/PRIMARY KEY will create implicit index 'pktable_pkey' for table 'pktable' + CREATE TABLE FKTABLE_FAIL1 ( ftest1 int, CONSTRAINT fkfail1 FOREIGN KEY (ftest2) REFERENCES PKTABLE); + NOTICE: CREATE TABLE will create implicit trigger(s) for FOREIGN KEY check(s) + ERROR: columns referenced in foreign key constraint not found. + CREATE TABLE FKTABLE_FAIL2 ( ftest1 int, CONSTRAINT fkfail1 FOREIGN KEY (ftest1) REFERENCES PKTABLE(ptest2)); + NOTICE: CREATE TABLE will create implicit trigger(s) for FOREIGN KEY check(s) + ERROR: UNIQUE constraint matching given keys for referenced table "pktable" not found + DROP TABLE FKTABLE_FAIL1; + ERROR: Relation 'fktable_fail1' does not exist + DROP TABLE FKTABLE_FAIL2; + ERROR: Relation 'fktable_fail2' does not exist + DROP TABLE PKTABLE;