Skip to content

Commit

Permalink
sql: properly enforce uniqueness of key columns for FK references
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
lucy-zhang committed Jan 14, 2020
1 parent 992a49e commit 6430e47
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 2 deletions.
15 changes: 15 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/fk
Original file line number Diff line number Diff line change
Expand Up @@ -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);
13 changes: 13 additions & 0 deletions pkg/sql/sqlbase/structured.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions pkg/sql/sqlbase/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down

0 comments on commit 6430e47

Please sign in to comment.