From 6430e4711a76914a99567d86c73ac001dfe280d6 Mon Sep 17 00:00:00 2001 From: Lucy Zhang Date: Tue, 7 Jan 2020 14:59:32 -0500 Subject: [PATCH] sql: properly enforce uniqueness of key columns for FK references We introduced a bug in 19.2 that would allow, e.g., a unique index on `(a, b, c)` to be used as an index that is supposed to enforce uniqueness for a foreign key constraint pointing only to `(a, b)`. This PR reintroduces a check that the indexed columns match the FK columns exactly. Release note (bug fix): Fixed a bug introduced in 19.2 that would allow foreign keys to use a unique index on the referenced columns that indexed more columns than were included in the columns used in the FK constraint, which allows potentially violating uniqueness in the referenced columns themselves. --- pkg/sql/logictest/testdata/logic_test/fk | 15 +++++++++++++++ pkg/sql/sqlbase/structured.go | 13 +++++++++++++ pkg/sql/sqlbase/table.go | 4 ++-- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/pkg/sql/logictest/testdata/logic_test/fk b/pkg/sql/logictest/testdata/logic_test/fk index e8f8ab88cab8..14af5df12d3d 100644 --- a/pkg/sql/logictest/testdata/logic_test/fk +++ b/pkg/sql/logictest/testdata/logic_test/fk @@ -2742,3 +2742,18 @@ ALTER TABLE table1_42498 ADD FOREIGN KEY (col2, col1) REFERENCES table2_42498 (c statement ok DROP TABLE table1_42498, table2_42498 CASCADE + +# Regression test for #42680: The unique index used for the referenced columns +# must index only those columns and no others, in order to enforce uniqueness +# for the FK constraint. +subtest 42680_unique_index_must_exactly_match_columns + +# The table has a unique index on (a, b) but not (a). +statement ok +CREATE TABLE target (a INT, b INT, UNIQUE INDEX (a, b)); + +statement ok +CREATE TABLE source (a INT, INDEX (a)); + +statement error there is no unique constraint matching given keys for referenced table target +ALTER TABLE source ADD FOREIGN KEY (a) REFERENCES target (a); diff --git a/pkg/sql/sqlbase/structured.go b/pkg/sql/sqlbase/structured.go index 487fe0fa16f2..e66ace8ab320 100644 --- a/pkg/sql/sqlbase/structured.go +++ b/pkg/sql/sqlbase/structured.go @@ -86,6 +86,19 @@ func (c ColumnIDs) HasPrefix(input ColumnIDs) bool { return true } +// Equals returns true if the input list is equal to this list. +func (c ColumnIDs) Equals(input ColumnIDs) bool { + if len(input) != len(c) { + return false + } + for i := range input { + if input[i] != c[i] { + return false + } + } + return true +} + // FamilyID is a custom type for ColumnFamilyDescriptor IDs. type FamilyID uint32 diff --git a/pkg/sql/sqlbase/table.go b/pkg/sql/sqlbase/table.go index 6d416a32fa1c..9175bc708e17 100644 --- a/pkg/sql/sqlbase/table.go +++ b/pkg/sql/sqlbase/table.go @@ -423,12 +423,12 @@ func FindFKReferencedIndex( ) (*IndexDescriptor, error) { // Search for a unique index on the referenced table that matches our foreign // key columns. - if ColumnIDs(referencedTable.PrimaryIndex.ColumnIDs).HasPrefix(referencedColIDs) { + if ColumnIDs(referencedTable.PrimaryIndex.ColumnIDs).Equals(referencedColIDs) { return &referencedTable.PrimaryIndex, nil } // If the PK doesn't match, find the index corresponding to the referenced column. for _, idx := range referencedTable.Indexes { - if idx.Unique && ColumnIDs(idx.ColumnIDs).HasPrefix(referencedColIDs) { + if idx.Unique && ColumnIDs(idx.ColumnIDs).Equals(referencedColIDs) { return &idx, nil } }