feat(inspector): fail on duplicate index names per schema
PostgreSQL index names share the schema-wide relation namespace, so a reused name makes CREATE INDEX IF NOT EXISTS silently skip the duplicate. Add duplicate_index_name rule (enforce by default) covering indexes and PK/unique constraint backing indexes.
This commit is contained in:
@@ -835,3 +835,84 @@ func TestFormatLocation(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateDuplicateIndexName(t *testing.T) {
|
||||
db := &models.Database{
|
||||
Name: "testdb",
|
||||
Schemas: []*models.Schema{
|
||||
{
|
||||
Name: "entity",
|
||||
Tables: []*models.Table{
|
||||
{
|
||||
Name: "actor_phone",
|
||||
Indexes: map[string]*models.Index{
|
||||
"idx_actor": {Name: "idx_actor", Columns: []string{"rid_actor"}},
|
||||
"uk_phone": {Name: "uk_phone", Columns: []string{"phone"}, Unique: true},
|
||||
},
|
||||
Constraints: map[string]*models.Constraint{
|
||||
// Same name as the index on the same table: one object.
|
||||
"uk_phone": {Name: "uk_phone", Type: models.UniqueConstraint, Columns: []string{"phone"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "actor_email",
|
||||
Indexes: map[string]*models.Index{
|
||||
"idx_actor": {Name: "idx_actor", Columns: []string{"rid_actor"}},
|
||||
},
|
||||
Constraints: map[string]*models.Constraint{
|
||||
"UK_Phone": {Name: "UK_Phone", Type: models.UniqueConstraint, Columns: []string{"email"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "actor_address",
|
||||
Indexes: map[string]*models.Index{
|
||||
"idx_actor": {Name: "idx_actor", Columns: []string{"rid_actor"}},
|
||||
"idx_actor#2": {Name: "idx_actor", Columns: []string{"rid_actor", "kind"}},
|
||||
"idx_address": {Name: "idx_address", Columns: []string{"line1"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Same names in another schema do not collide.
|
||||
Name: "org",
|
||||
Tables: []*models.Table{
|
||||
{Name: "api_provider", Indexes: map[string]*models.Index{"idx_actor": {Name: "idx_actor"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
results := validateDuplicateIndexName(db, Rule{Message: "dup"}, "duplicate_index_name")
|
||||
|
||||
got := map[string]bool{}
|
||||
occ := map[string]int{}
|
||||
for _, r := range results {
|
||||
key := r.Context["schema"].(string) + "." + r.Context["index"].(string)
|
||||
got[key] = r.Passed
|
||||
occ[key] = r.Context["occurrences"].(int)
|
||||
}
|
||||
|
||||
want := map[string]struct {
|
||||
passed bool
|
||||
occ int
|
||||
}{
|
||||
"entity.idx_actor": {false, 4},
|
||||
"entity.uk_phone": {false, 2},
|
||||
"entity.idx_address": {true, 1},
|
||||
"org.idx_actor": {true, 1},
|
||||
}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("got %d results %v, want %d", len(got), got, len(want))
|
||||
}
|
||||
for k, w := range want {
|
||||
p, ok := got[k]
|
||||
if !ok {
|
||||
t.Errorf("missing result for %s", k)
|
||||
continue
|
||||
}
|
||||
if p != w.passed || occ[k] != w.occ {
|
||||
t.Errorf("%s: passed=%v occurrences=%d, want passed=%v occurrences=%d", k, p, occ[k], w.passed, w.occ)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user