package guilds import ( "context" "fmt" "time" "eq2emu/internal/database" ) // DatabaseGuildManager implements GuildDatabase interface using the existing database wrapper type DatabaseGuildManager struct { db database.DBInterface } // NewDatabaseGuildManager creates a new database guild manager func NewDatabaseGuildManager(db database.DBInterface) *DatabaseGuildManager { return &DatabaseGuildManager{ db: db, } } // LoadGuilds retrieves all guilds from database func (dgm *DatabaseGuildManager) LoadGuilds(ctx context.Context) ([]GuildData, error) { query := "SELECT `id`, `name`, `motd`, `level`, `xp`, `xp_needed`, `formed_on` FROM `guilds`" var guilds []GuildData err := dgm.db.Query(query, func(row *database.Row) error { var guild GuildData guild.ID = int32(row.Int(0)) guild.Name = row.Text(1) if !row.IsNull(2) { guild.MOTD = row.Text(2) } guild.Level = int8(row.Int(3)) guild.EXPCurrent = row.Int64(4) guild.EXPToNextLevel = row.Int64(5) guild.FormedDate = time.Unix(row.Int64(6), 0) guilds = append(guilds, guild) return nil }) if err != nil { return nil, fmt.Errorf("failed to query guilds: %w", err) } return guilds, nil } // LoadGuild retrieves a specific guild from database func (dgm *DatabaseGuildManager) LoadGuild(ctx context.Context, guildID int32) (*GuildData, error) { query := "SELECT `id`, `name`, `motd`, `level`, `xp`, `xp_needed`, `formed_on` FROM `guilds` WHERE `id` = ?" row, err := dgm.db.QueryRow(query, guildID) if err != nil { return nil, fmt.Errorf("failed to load guild %d: %w", guildID, err) } if row == nil { return nil, fmt.Errorf("guild %d not found", guildID) } defer row.Close() var guild GuildData guild.ID = int32(row.Int(0)) guild.Name = row.Text(1) if !row.IsNull(2) { guild.MOTD = row.Text(2) } guild.Level = int8(row.Int(3)) guild.EXPCurrent = row.Int64(4) guild.EXPToNextLevel = row.Int64(5) guild.FormedDate = time.Unix(row.Int64(6), 0) return &guild, nil } // LoadGuildMembers retrieves all members for a guild func (dgm *DatabaseGuildManager) LoadGuildMembers(ctx context.Context, guildID int32) ([]GuildMemberData, error) { query := `SELECT char_id, guild_id, account_id, recruiter_id, name, guild_status, points, adventure_class, adventure_level, tradeskill_class, tradeskill_level, rank, member_flags, zone, join_date, last_login_date, note, officer_note, recruiter_description, recruiter_picture_data, recruiting_show_adventure_class FROM guild_members WHERE guild_id = ?` // Use the main implementation with zero-copy optimization var members []GuildMemberData err := dgm.db.Query(query, func(row *database.Row) error { var member GuildMemberData member.CharacterID = int32(row.Int(0)) member.GuildID = int32(row.Int(1)) member.AccountID = int32(row.Int(2)) member.RecruiterID = int32(row.Int(3)) member.Name = row.Text(4) member.GuildStatus = int32(row.Int(5)) member.Points = row.Float(6) member.AdventureClass = int8(row.Int(7)) member.AdventureLevel = int8(row.Int(8)) member.TradeskillClass = int8(row.Int(9)) member.TradeskillLevel = int8(row.Int(10)) member.Rank = int8(row.Int(11)) member.MemberFlags = int8(row.Int(12)) member.Zone = row.Text(13) member.JoinDate = time.Unix(row.Int64(14), 0) member.LastLoginDate = time.Unix(row.Int64(15), 0) if !row.IsNull(16) { member.Note = row.Text(16) } if !row.IsNull(17) { member.OfficerNote = row.Text(17) } if !row.IsNull(18) { member.RecruiterDescription = row.Text(18) } // TODO: Handle blob data for recruiter_picture_data (column 19) member.RecruitingShowAdventureClass = int8(row.Int(20)) members = append(members, member) return nil }, guildID) if err != nil { return nil, fmt.Errorf("failed to query guild members for guild %d: %w", guildID, err) } return members, nil } // LoadGuildEvents retrieves events for a guild func (dgm *DatabaseGuildManager) LoadGuildEvents(ctx context.Context, guildID int32) ([]GuildEventData, error) { query := `SELECT event_id, guild_id, date, type, description, locked FROM guild_events WHERE guild_id = ? ORDER BY event_id DESC LIMIT ?` var events []GuildEventData err := dgm.db.Query(query, func(row *database.Row) error { var event GuildEventData event.EventID = row.Int64(0) event.GuildID = int32(row.Int(1)) event.Date = time.Unix(row.Int64(2), 0) event.Type = int32(row.Int(3)) event.Description = row.Text(4) event.Locked = int8(row.Int(5)) events = append(events, event) return nil }, guildID, MaxEvents) if err != nil { return nil, fmt.Errorf("failed to query guild events for guild %d: %w", guildID, err) } return events, nil } // LoadGuildRanks retrieves custom rank names for a guild func (dgm *DatabaseGuildManager) LoadGuildRanks(ctx context.Context, guildID int32) ([]GuildRankData, error) { query := "SELECT guild_id, rank, name FROM guild_ranks WHERE guild_id = ?" var ranks []GuildRankData err := dgm.db.Query(query, func(row *database.Row) error { var rank GuildRankData rank.GuildID = int32(row.Int(0)) rank.Rank = int8(row.Int(1)) rank.Name = row.Text(2) ranks = append(ranks, rank) return nil }, guildID) if err != nil { return nil, fmt.Errorf("failed to query guild ranks for guild %d: %w", guildID, err) } return ranks, nil } // LoadGuildPermissions retrieves permissions for a guild func (dgm *DatabaseGuildManager) LoadGuildPermissions(ctx context.Context, guildID int32) ([]GuildPermissionData, error) { query := "SELECT guild_id, rank, permission, value FROM guild_permissions WHERE guild_id = ?" var permissions []GuildPermissionData err := dgm.db.Query(query, func(row *database.Row) error { var permission GuildPermissionData permission.GuildID = int32(row.Int(0)) permission.Rank = int8(row.Int(1)) permission.Permission = int8(row.Int(2)) permission.Value = int8(row.Int(3)) permissions = append(permissions, permission) return nil }, guildID) if err != nil { return nil, fmt.Errorf("failed to query guild permissions for guild %d: %w", guildID, err) } return permissions, nil } // LoadGuildEventFilters retrieves event filters for a guild func (dgm *DatabaseGuildManager) LoadGuildEventFilters(ctx context.Context, guildID int32) ([]GuildEventFilterData, error) { query := "SELECT guild_id, event_id, category, value FROM guild_event_filters WHERE guild_id = ?" var filters []GuildEventFilterData err := dgm.db.Query(query, func(row *database.Row) error { var filter GuildEventFilterData filter.GuildID = int32(row.Int(0)) filter.EventID = int8(row.Int(1)) filter.Category = int8(row.Int(2)) filter.Value = int8(row.Int(3)) filters = append(filters, filter) return nil }, guildID) if err != nil { return nil, fmt.Errorf("failed to query guild event filters for guild %d: %w", guildID, err) } return filters, nil } // LoadGuildRecruiting retrieves recruiting settings for a guild func (dgm *DatabaseGuildManager) LoadGuildRecruiting(ctx context.Context, guildID int32) ([]GuildRecruitingData, error) { query := "SELECT guild_id, flag, value FROM guild_recruiting WHERE guild_id = ?" var recruiting []GuildRecruitingData err := dgm.db.Query(query, func(row *database.Row) error { var recruit GuildRecruitingData recruit.GuildID = int32(row.Int(0)) recruit.Flag = int8(row.Int(1)) recruit.Value = int8(row.Int(2)) recruiting = append(recruiting, recruit) return nil }, guildID) if err != nil { return nil, fmt.Errorf("failed to query guild recruiting for guild %d: %w", guildID, err) } return recruiting, nil } // LoadPointHistory retrieves point history for a member func (dgm *DatabaseGuildManager) LoadPointHistory(ctx context.Context, characterID int32) ([]PointHistoryData, error) { query := `SELECT char_id, date, modified_by, comment, points FROM guild_point_history WHERE char_id = ? ORDER BY date DESC LIMIT ?` var history []PointHistoryData err := dgm.db.Query(query, func(row *database.Row) error { var entry PointHistoryData entry.CharacterID = int32(row.Int(0)) entry.Date = time.Unix(row.Int64(1), 0) entry.ModifiedBy = row.Text(2) entry.Comment = row.Text(3) entry.Points = row.Float(4) history = append(history, entry) return nil }, characterID, MaxPointHistory) if err != nil { return nil, fmt.Errorf("failed to query point history for character %d: %w", characterID, err) } return history, nil } // SaveGuild saves guild basic data func (dgm *DatabaseGuildManager) SaveGuild(ctx context.Context, guild *Guild) error { query := `INSERT OR REPLACE INTO guilds (id, name, motd, level, xp, xp_needed, formed_on) VALUES (?, ?, ?, ?, ?, ?, ?)` formedTimestamp := guild.GetFormedDate().Unix() err := dgm.db.Exec(query, guild.GetID(), guild.GetName(), guild.GetMOTD(), guild.GetLevel(), guild.GetEXPCurrent(), guild.GetEXPToNextLevel(), formedTimestamp, ) if err != nil { return fmt.Errorf("failed to save guild %d: %w", guild.GetID(), err) } return nil } // SaveGuildMembers saves all guild members func (dgm *DatabaseGuildManager) SaveGuildMembers(ctx context.Context, guildID int32, members []*GuildMember) error { if len(members) == 0 { return nil } // Use a transaction for atomic updates return dgm.db.Transaction(func(db database.DBInterface) error { // Delete existing members for this guild if err := db.Exec("DELETE FROM guild_members WHERE guild_id = ?", guildID); err != nil { return fmt.Errorf("failed to delete existing guild members: %w", err) } // Insert all members insertQuery := `INSERT INTO guild_members (char_id, guild_id, account_id, recruiter_id, name, guild_status, points, adventure_class, adventure_level, tradeskill_class, tradeskill_level, rank, member_flags, zone, join_date, last_login_date, note, officer_note, recruiter_description, recruiter_picture_data, recruiting_show_adventure_class) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` for _, member := range members { joinTimestamp := member.GetJoinDate().Unix() lastLoginTimestamp := member.GetLastLoginDate().Unix() if err := db.Exec(insertQuery, member.GetCharacterID(), guildID, member.AccountID, member.GetRecruiterID(), member.GetName(), member.GuildStatus, member.GetPoints(), member.GetAdventureClass(), member.GetAdventureLevel(), member.GetTradeskillClass(), member.GetTradeskillLevel(), member.GetRank(), member.GetMemberFlags(), member.GetZone(), joinTimestamp, lastLoginTimestamp, member.GetNote(), member.GetOfficerNote(), member.GetRecruiterDescription(), member.GetRecruiterPictureData(), member.RecruitingShowAdventureClass, ); err != nil { return fmt.Errorf("failed to insert guild member %d: %w", member.GetCharacterID(), err) } } return nil }) } // SaveGuildEvents saves guild events func (dgm *DatabaseGuildManager) SaveGuildEvents(ctx context.Context, guildID int32, events []GuildEvent) error { if len(events) == 0 { return nil } query := `INSERT OR REPLACE INTO guild_events (event_id, guild_id, date, type, description, locked) VALUES (?, ?, ?, ?, ?, ?)` for _, event := range events { if !event.SaveNeeded { continue } dateTimestamp := event.Date.Unix() if err := dgm.db.Exec(query, event.EventID, guildID, dateTimestamp, event.Type, event.Description, event.Locked, ); err != nil { return fmt.Errorf("failed to save guild event %d: %w", event.EventID, err) } } return nil } // SaveGuildRanks saves guild rank names func (dgm *DatabaseGuildManager) SaveGuildRanks(ctx context.Context, guildID int32, ranks map[int8]string) error { // Use a transaction for atomic updates return dgm.db.Transaction(func(db database.DBInterface) error { // Delete existing ranks for this guild if err := db.Exec("DELETE FROM guild_ranks WHERE guild_id = ?", guildID); err != nil { return fmt.Errorf("failed to delete existing guild ranks: %w", err) } // Insert all ranks insertQuery := "INSERT INTO guild_ranks (guild_id, rank, name) VALUES (?, ?, ?)" for rank, name := range ranks { // Only save non-default rank names if defaultName, exists := DefaultRankNames[rank]; !exists || name != defaultName { if err := db.Exec(insertQuery, guildID, rank, name); err != nil { return fmt.Errorf("failed to insert guild rank %d: %w", rank, err) } } } return nil }) } // SaveGuildPermissions saves guild permissions func (dgm *DatabaseGuildManager) SaveGuildPermissions(ctx context.Context, guildID int32, permissions map[int8]map[int8]int8) error { // Use a transaction for atomic updates return dgm.db.Transaction(func(db database.DBInterface) error { // Delete existing permissions for this guild if err := db.Exec("DELETE FROM guild_permissions WHERE guild_id = ?", guildID); err != nil { return fmt.Errorf("failed to delete existing guild permissions: %w", err) } // Insert all permissions insertQuery := "INSERT INTO guild_permissions (guild_id, rank, permission, value) VALUES (?, ?, ?, ?)" for rank, rankPermissions := range permissions { for permission, value := range rankPermissions { if err := db.Exec(insertQuery, guildID, rank, permission, value); err != nil { return fmt.Errorf("failed to insert guild permission %d/%d: %w", rank, permission, err) } } } return nil }) } // SaveGuildEventFilters saves guild event filters func (dgm *DatabaseGuildManager) SaveGuildEventFilters(ctx context.Context, guildID int32, filters map[int8]map[int8]int8) error { // Use a transaction for atomic updates return dgm.db.Transaction(func(db database.DBInterface) error { // Delete existing filters for this guild if err := db.Exec("DELETE FROM guild_event_filters WHERE guild_id = ?", guildID); err != nil { return fmt.Errorf("failed to delete existing guild event filters: %w", err) } // Insert all filters insertQuery := "INSERT INTO guild_event_filters (guild_id, event_id, category, value) VALUES (?, ?, ?, ?)" for eventID, eventFilters := range filters { for category, value := range eventFilters { if err := db.Exec(insertQuery, guildID, eventID, category, value); err != nil { return fmt.Errorf("failed to insert guild event filter %d/%d: %w", eventID, category, err) } } } return nil }) } // SaveGuildRecruiting saves guild recruiting settings func (dgm *DatabaseGuildManager) SaveGuildRecruiting(ctx context.Context, guildID int32, flags, descTags map[int8]int8) error { // Use a transaction for atomic updates return dgm.db.Transaction(func(db database.DBInterface) error { // Delete existing recruiting settings for this guild if err := db.Exec("DELETE FROM guild_recruiting WHERE guild_id = ?", guildID); err != nil { return fmt.Errorf("failed to delete existing guild recruiting: %w", err) } // Insert recruiting flags insertQuery := "INSERT INTO guild_recruiting (guild_id, flag, value) VALUES (?, ?, ?)" for flag, value := range flags { if err := db.Exec(insertQuery, guildID, flag, value); err != nil { return fmt.Errorf("failed to insert guild recruiting flag %d: %w", flag, err) } } // Insert description tags (with negative flag values to distinguish) for tag, value := range descTags { if err := db.Exec(insertQuery, guildID, -tag-1, value); err != nil { // Negative to distinguish from flags return fmt.Errorf("failed to insert guild recruiting desc tag %d: %w", tag, err) } } return nil }) } // SavePointHistory saves point history for a member func (dgm *DatabaseGuildManager) SavePointHistory(ctx context.Context, characterID int32, history []PointHistory) error { if len(history) == 0 { return nil } // Use a transaction for atomic updates return dgm.db.Transaction(func(db database.DBInterface) error { // Delete existing history for this character if err := db.Exec("DELETE FROM guild_point_history WHERE char_id = ?", characterID); err != nil { return fmt.Errorf("failed to delete existing point history: %w", err) } // Insert all history entries insertQuery := "INSERT INTO guild_point_history (char_id, date, modified_by, comment, points) VALUES (?, ?, ?, ?, ?)" for _, entry := range history { dateTimestamp := entry.Date.Unix() if err := db.Exec(insertQuery, characterID, dateTimestamp, entry.ModifiedBy, entry.Comment, entry.Points, ); err != nil { return fmt.Errorf("failed to insert point history entry: %w", err) } } return nil }) } // GetGuildIDByCharacterID returns guild ID for a character func (dgm *DatabaseGuildManager) GetGuildIDByCharacterID(ctx context.Context, characterID int32) (int32, error) { query := "SELECT guild_id FROM guild_members WHERE char_id = ?" row, err := dgm.db.QueryRow(query, characterID) if err != nil { return 0, fmt.Errorf("failed to get guild ID for character %d: %w", characterID, err) } if row == nil { return 0, fmt.Errorf("character %d not found in any guild", characterID) } defer row.Close() guildID := int32(row.Int(0)) return guildID, nil } // CreateGuild creates a new guild func (dgm *DatabaseGuildManager) CreateGuild(ctx context.Context, guildData GuildData) (int32, error) { query := `INSERT INTO guilds (name, motd, level, xp, xp_needed, formed_on) VALUES (?, ?, ?, ?, ?, ?)` formedTimestamp := guildData.FormedDate.Unix() id, err := dgm.db.ExecAndGetLastInsertID(query, guildData.Name, guildData.MOTD, guildData.Level, guildData.EXPCurrent, guildData.EXPToNextLevel, formedTimestamp, ) if err != nil { return 0, fmt.Errorf("failed to create guild: %w", err) } return int32(id), nil } // DeleteGuild removes a guild and all related data func (dgm *DatabaseGuildManager) DeleteGuild(ctx context.Context, guildID int32) error { // Use a transaction for atomic deletion return dgm.db.Transaction(func(db database.DBInterface) error { // Delete related data first (foreign key constraints) tables := []string{ "guild_point_history", "guild_members", "guild_events", "guild_ranks", "guild_permissions", "guild_event_filters", "guild_recruiting", } for _, table := range tables { var query string if table == "guild_point_history" { // Special case: delete point history for all members of this guild query = "DELETE FROM guild_point_history WHERE character_id IN (SELECT char_id FROM guild_members WHERE guild_id = ?)" } else { query = fmt.Sprintf("DELETE FROM %s WHERE guild_id = ?", table) } if err := db.Exec(query, guildID); err != nil { return fmt.Errorf("failed to delete from %s: %w", table, err) } } // Finally delete the guild itself if err := db.Exec("DELETE FROM guilds WHERE id = ?", guildID); err != nil { return fmt.Errorf("failed to delete guild: %w", err) } return nil }) } // GetNextGuildID returns the next available guild ID func (dgm *DatabaseGuildManager) GetNextGuildID(ctx context.Context) (int32, error) { query := "SELECT COALESCE(MAX(id), 0) + 1 FROM guilds" row, err := dgm.db.QueryRow(query) if err != nil { return 0, fmt.Errorf("failed to get next guild ID: %w", err) } if row == nil { return 1, nil // If no guilds exist, start with ID 1 } defer row.Close() nextID := int32(row.Int(0)) return nextID, nil } // GetNextEventID returns the next available event ID for a guild func (dgm *DatabaseGuildManager) GetNextEventID(ctx context.Context, guildID int32) (int64, error) { query := "SELECT COALESCE(MAX(event_id), 0) + 1 FROM guild_events WHERE guild_id = ?" row, err := dgm.db.QueryRow(query, guildID) if err != nil { return 0, fmt.Errorf("failed to get next event ID for guild %d: %w", guildID, err) } if row == nil { return 1, nil // If no events exist for this guild, start with ID 1 } defer row.Close() nextID := row.Int64(0) return nextID, nil } // EnsureGuildTables creates the guild tables if they don't exist func (dgm *DatabaseGuildManager) EnsureGuildTables(ctx context.Context) error { queries := []string{ `CREATE TABLE IF NOT EXISTS guilds ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, motd TEXT, level INTEGER NOT NULL DEFAULT 1, xp INTEGER NOT NULL DEFAULT 111, xp_needed INTEGER NOT NULL DEFAULT 2521, formed_on INTEGER NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP )`, `CREATE TABLE IF NOT EXISTS guild_members ( char_id INTEGER NOT NULL, guild_id INTEGER NOT NULL, account_id INTEGER NOT NULL DEFAULT 0, recruiter_id INTEGER NOT NULL DEFAULT 0, name TEXT NOT NULL, guild_status INTEGER NOT NULL DEFAULT 0, points REAL NOT NULL DEFAULT 0.0, adventure_class INTEGER NOT NULL DEFAULT 0, adventure_level INTEGER NOT NULL DEFAULT 1, tradeskill_class INTEGER NOT NULL DEFAULT 0, tradeskill_level INTEGER NOT NULL DEFAULT 1, rank INTEGER NOT NULL DEFAULT 7, member_flags INTEGER NOT NULL DEFAULT 0, zone TEXT NOT NULL DEFAULT '', join_date INTEGER NOT NULL, last_login_date INTEGER NOT NULL, note TEXT NOT NULL DEFAULT '', officer_note TEXT NOT NULL DEFAULT '', recruiter_description TEXT NOT NULL DEFAULT '', recruiter_picture_data BLOB, recruiting_show_adventure_class INTEGER NOT NULL DEFAULT 1, PRIMARY KEY (char_id), FOREIGN KEY (guild_id) REFERENCES guilds(id) ON DELETE CASCADE )`, `CREATE TABLE IF NOT EXISTS guild_events ( event_id INTEGER NOT NULL, guild_id INTEGER NOT NULL, date INTEGER NOT NULL, type INTEGER NOT NULL, description TEXT NOT NULL, locked INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (event_id, guild_id), FOREIGN KEY (guild_id) REFERENCES guilds(id) ON DELETE CASCADE )`, `CREATE TABLE IF NOT EXISTS guild_ranks ( guild_id INTEGER NOT NULL, rank INTEGER NOT NULL, name TEXT NOT NULL, PRIMARY KEY (guild_id, rank), FOREIGN KEY (guild_id) REFERENCES guilds(id) ON DELETE CASCADE )`, `CREATE TABLE IF NOT EXISTS guild_permissions ( guild_id INTEGER NOT NULL, rank INTEGER NOT NULL, permission INTEGER NOT NULL, value INTEGER NOT NULL, PRIMARY KEY (guild_id, rank, permission), FOREIGN KEY (guild_id) REFERENCES guilds(id) ON DELETE CASCADE )`, `CREATE TABLE IF NOT EXISTS guild_event_filters ( guild_id INTEGER NOT NULL, event_id INTEGER NOT NULL, category INTEGER NOT NULL, value INTEGER NOT NULL, PRIMARY KEY (guild_id, event_id, category), FOREIGN KEY (guild_id) REFERENCES guilds(id) ON DELETE CASCADE )`, `CREATE TABLE IF NOT EXISTS guild_recruiting ( guild_id INTEGER NOT NULL, flag INTEGER NOT NULL, value INTEGER NOT NULL, PRIMARY KEY (guild_id, flag), FOREIGN KEY (guild_id) REFERENCES guilds(id) ON DELETE CASCADE )`, `CREATE TABLE IF NOT EXISTS guild_point_history ( char_id INTEGER NOT NULL, date INTEGER NOT NULL, modified_by TEXT NOT NULL, comment TEXT NOT NULL, points REAL NOT NULL, PRIMARY KEY (char_id, date), FOREIGN KEY (char_id) REFERENCES guild_members(char_id) ON DELETE CASCADE )`, } for i, query := range queries { if err := dgm.db.Exec(query); err != nil { return fmt.Errorf("failed to create guild table %d: %w", i+1, err) } } // Create indexes for better performance indexes := []string{ `CREATE INDEX IF NOT EXISTS idx_guild_members_guild_id ON guild_members(guild_id)`, `CREATE INDEX IF NOT EXISTS idx_guild_members_char_id ON guild_members(char_id)`, `CREATE INDEX IF NOT EXISTS idx_guild_events_guild_id ON guild_events(guild_id)`, `CREATE INDEX IF NOT EXISTS idx_guild_events_date ON guild_events(date)`, `CREATE INDEX IF NOT EXISTS idx_guild_point_history_char_id ON guild_point_history(char_id)`, `CREATE INDEX IF NOT EXISTS idx_guilds_name ON guilds(name)`, `CREATE INDEX IF NOT EXISTS idx_guild_members_rank ON guild_members(rank)`, `CREATE INDEX IF NOT EXISTS idx_guild_members_last_login ON guild_members(last_login_date)`, } for i, query := range indexes { if err := dgm.db.Exec(query); err != nil { return fmt.Errorf("failed to create guild index %d: %w", i+1, err) } } return nil }