CREATE TABLE guild_members ( guild_id BYTEA NOT NULL CHECK(length(guild_id) = 26), user_id BYTEA NOT NULL CHECK(length(user_id) = 26), -- Core identity nick TEXT CHECK (LENGTH(COALESCE(nick, '')) <= 32), -- display name in guild (NULL = uses user global name) joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- Roles & permissions roles BYTEA[] NOT NULL DEFAULT ARRAY[]::BYTEA[], -- array of role IDs (26-byte each) boosting_since TIMESTAMPTZ, -- when they started boosting (NULL = not boosting) -- Voice state (lightweight caching) voice_channel_id BYTEA CHECK(length(voice_channel_id) = 26), deafened BOOLEAN NOT NULL DEFAULT FALSE, muted BOOLEAN NOT NULL DEFAULT FALSE, -- Moderation & management pending BOOLEAN NOT NULL DEFAULT FALSE, -- requires membership screening timed_out_until TIMESTAMPTZ, -- NULL = not timed out -- Audit & integrity updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- Composite primary key PRIMARY KEY (guild_id, user_id) ); -- Critical indexes CREATE INDEX idx_guild_members_user_id ON guild_members (user_id); CREATE INDEX idx_guild_members_guild_id ON guild_members (guild_id); CREATE INDEX idx_guild_members_roles ON guild_members USING GIN (roles); -- for role-based lookups CREATE INDEX idx_guild_members_voice_channel_id ON guild_members (voice_channel_id) WHERE voice_channel_id IS NOT NULL; -- For quick "all members in guild" queries (covering index) CREATE INDEX idx_guild_members_guild_id_nick ON guild_members (guild_id, nick);