From f6403289dc8c3e11307946aa5cf6593fe7329c62 Mon Sep 17 00:00:00 2001 From: Brian Reardon Date: Fri, 31 Oct 2025 09:10:10 -0700 Subject: [PATCH] remove indexes from code, put them into migrations for one source of truth --- taco/internal/query/common/org_indexes.go | 74 ------------------- taco/internal/query/common/sql_store.go | 7 +- taco/internal/query/types/models.go | 12 +-- ...51031000001_add_unique_org_constraints.sql | 12 +++ ...51031000001_add_unique_org_constraints.sql | 12 +++ ...51031000001_add_unique_org_constraints.sql | 12 +++ 6 files changed, 45 insertions(+), 84 deletions(-) delete mode 100644 taco/internal/query/common/org_indexes.go create mode 100644 taco/migrations/mysql/20251031000001_add_unique_org_constraints.sql create mode 100644 taco/migrations/postgres/20251031000001_add_unique_org_constraints.sql create mode 100644 taco/migrations/sqlite/20251031000001_add_unique_org_constraints.sql diff --git a/taco/internal/query/common/org_indexes.go b/taco/internal/query/common/org_indexes.go deleted file mode 100644 index 20386b32f..000000000 --- a/taco/internal/query/common/org_indexes.go +++ /dev/null @@ -1,74 +0,0 @@ -package common - -import ( - "fmt" - "log" - "gorm.io/gorm" -) - -func CreateOrgScopedIndexes(db *gorm.DB) error { - dialect := db.Dialector.Name() - - indexes := []struct { - table string - name string - columns string - }{ - {"units", "idx_units_org_name", "org_id, name"}, - {"roles", "idx_roles_org_name", "org_id, name"}, - {"permissions", "idx_permissions_org_name", "org_id, name"}, - {"tags", "idx_tags_org_name", "org_id, name"}, - } - - for _, idx := range indexes { - if err := createUniqueIndexIfNotExists(db, dialect, idx.table, idx.name, idx.columns); err != nil { - return fmt.Errorf("failed to create index %s: %w", idx.name, err) - } - log.Printf("Ensured unique index %s on %s(%s)", idx.name, idx.table, idx.columns) - } - - return nil -} - -func createUniqueIndexIfNotExists(db *gorm.DB, dialect, table, indexName, columns string) error { - switch dialect { - case "sqlite": - sql := fmt.Sprintf("CREATE UNIQUE INDEX IF NOT EXISTS %s ON %s(%s)", indexName, table, columns) - return db.Exec(sql).Error - - case "postgres": - sql := fmt.Sprintf("CREATE UNIQUE INDEX IF NOT EXISTS %s ON %s(%s)", indexName, table, columns) - return db.Exec(sql).Error - - case "mysql": - var count int64 - checkSQL := fmt.Sprintf(` - SELECT COUNT(*) FROM information_schema.statistics - WHERE table_schema = DATABASE() - AND table_name = '%s' - AND index_name = '%s' - `, table, indexName) - - if err := db.Raw(checkSQL).Scan(&count).Error; err != nil { - return err - } - - if count == 0 { - sql := fmt.Sprintf("CREATE UNIQUE INDEX %s ON %s(%s)", indexName, table, columns) - return db.Exec(sql).Error - } - - case "mssql", "sqlserver": - checkSQL := fmt.Sprintf(` - IF NOT EXISTS ( - SELECT * FROM sys.indexes - WHERE name = '%s' AND object_id = OBJECT_ID('%s') - ) - CREATE UNIQUE INDEX %s ON %s(%s) - `, indexName, table, indexName, table, columns) - return db.Exec(checkSQL).Error - } - - return nil -} - diff --git a/taco/internal/query/common/sql_store.go b/taco/internal/query/common/sql_store.go index a72b333f3..9ef433ba6 100644 --- a/taco/internal/query/common/sql_store.go +++ b/taco/internal/query/common/sql_store.go @@ -28,10 +28,9 @@ type SQLStore struct { func NewSQLStore(db *gorm.DB) (*SQLStore, error) { store := &SQLStore{db: db} - // Create org-scoped indexes (not handled by Atlas migrations) - if err := CreateOrgScopedIndexes(db); err != nil { - return nil, fmt.Errorf("failed to create org-scoped indexes: %w", err) - } + // All org-scoped unique indexes are now managed by Atlas migrations: + // - migrations/*/20251031000000_add_unique_unit_name_per_org.sql + // - migrations/*/20251031000001_add_unique_org_constraints.sql // Create database views (not handled by Atlas migrations) if err := store.createViews(); err != nil { diff --git a/taco/internal/query/types/models.go b/taco/internal/query/types/models.go index 1a5b0d28f..0a6c6aaeb 100644 --- a/taco/internal/query/types/models.go +++ b/taco/internal/query/types/models.go @@ -8,8 +8,8 @@ import ( type Role struct { ID string `gorm:"type:varchar(36);primaryKey"` - OrgID string `gorm:"type:varchar(36);index"` // Foreign key to organizations.id (UUID) - Name string `gorm:"type:varchar(255);not null;index"` // Unique identifier (e.g., "admin", "viewer") + OrgID string `gorm:"type:varchar(36);index;uniqueIndex:unique_org_role_name"` // Foreign key to organizations.id (UUID) + Name string `gorm:"type:varchar(255);not null;index;uniqueIndex:unique_org_role_name"` // Unique identifier per org (e.g., "admin", "viewer") Description string Permissions []Permission `gorm:"many2many:role_permissions;constraint:OnDelete:CASCADE,OnUpdate:CASCADE"` CreatedAt time.Time @@ -27,8 +27,8 @@ func (Role) TableName() string { return "roles" } type Permission struct { ID string `gorm:"type:varchar(36);primaryKey"` - OrgID string `gorm:"type:varchar(36);index"` // Foreign key to organizations.id (UUID) - Name string `gorm:"type:varchar(255);not null;index"` // Unique identifier (e.g., "unit-read", "unit-write") + OrgID string `gorm:"type:varchar(36);index;uniqueIndex:unique_org_permission_name"` // Foreign key to organizations.id (UUID) + Name string `gorm:"type:varchar(255);not null;index;uniqueIndex:unique_org_permission_name"` // Unique identifier per org (e.g., "unit-read", "unit-write") Description string Rules []Rule `gorm:"constraint:OnDelete:CASCADE"` CreatedBy string @@ -163,8 +163,8 @@ func (Unit) TableName() string { return "units" } type Tag struct { ID string `gorm:"type:varchar(36);primaryKey"` - OrgID string `gorm:"type:varchar(36);index"` // Foreign key to organizations.id (UUID) - Name string `gorm:"type:varchar(255);not null;index"` + OrgID string `gorm:"type:varchar(36);index;uniqueIndex:unique_org_tag_name"` // Foreign key to organizations.id (UUID) + Name string `gorm:"type:varchar(255);not null;index;uniqueIndex:unique_org_tag_name"` // Unique per org } func (t *Tag) BeforeCreate(tx *gorm.DB) error { diff --git a/taco/migrations/mysql/20251031000001_add_unique_org_constraints.sql b/taco/migrations/mysql/20251031000001_add_unique_org_constraints.sql new file mode 100644 index 000000000..d863aaa4c --- /dev/null +++ b/taco/migrations/mysql/20251031000001_add_unique_org_constraints.sql @@ -0,0 +1,12 @@ +-- Add unique constraints on (org_id, name) for roles, permissions, and tags tables +-- This ensures names are unique within each organization + +-- Create unique index on roles (org_id, name) +CREATE UNIQUE INDEX `unique_org_role_name` ON `roles` (`org_id`, `name`); + +-- Create unique index on permissions (org_id, name) +CREATE UNIQUE INDEX `unique_org_permission_name` ON `permissions` (`org_id`, `name`); + +-- Create unique index on tags (org_id, name) +CREATE UNIQUE INDEX `unique_org_tag_name` ON `tags` (`org_id`, `name`); + diff --git a/taco/migrations/postgres/20251031000001_add_unique_org_constraints.sql b/taco/migrations/postgres/20251031000001_add_unique_org_constraints.sql new file mode 100644 index 000000000..3acf39253 --- /dev/null +++ b/taco/migrations/postgres/20251031000001_add_unique_org_constraints.sql @@ -0,0 +1,12 @@ +-- Add unique constraints on (org_id, name) for roles, permissions, and tags tables +-- This ensures names are unique within each organization + +-- Create unique index on roles (org_id, name) +CREATE UNIQUE INDEX "unique_org_role_name" ON "public"."roles" ("org_id", "name"); + +-- Create unique index on permissions (org_id, name) +CREATE UNIQUE INDEX "unique_org_permission_name" ON "public"."permissions" ("org_id", "name"); + +-- Create unique index on tags (org_id, name) +CREATE UNIQUE INDEX "unique_org_tag_name" ON "public"."tags" ("org_id", "name"); + diff --git a/taco/migrations/sqlite/20251031000001_add_unique_org_constraints.sql b/taco/migrations/sqlite/20251031000001_add_unique_org_constraints.sql new file mode 100644 index 000000000..d863aaa4c --- /dev/null +++ b/taco/migrations/sqlite/20251031000001_add_unique_org_constraints.sql @@ -0,0 +1,12 @@ +-- Add unique constraints on (org_id, name) for roles, permissions, and tags tables +-- This ensures names are unique within each organization + +-- Create unique index on roles (org_id, name) +CREATE UNIQUE INDEX `unique_org_role_name` ON `roles` (`org_id`, `name`); + +-- Create unique index on permissions (org_id, name) +CREATE UNIQUE INDEX `unique_org_permission_name` ON `permissions` (`org_id`, `name`); + +-- Create unique index on tags (org_id, name) +CREATE UNIQUE INDEX `unique_org_tag_name` ON `tags` (`org_id`, `name`); +