-- ── Covering index for channel-id → community lookups ───────────────────────
-- `channels` is keyed PRIMARY KEY (community_id, id), and every secondary index
-- leads with community_id:
--
--   idx_channels_nip29_group        (community_id, nip29_group_id)
--   idx_channels_dm_hash           (community_id, participant_hash)
--   idx_channels_community_type    (community_id, channel_type)
--   idx_channels_community_visibility (community_id, visibility)
--   idx_channels_created_by        (community_id, created_by)
--
-- The tenant-independent lookups in buzz-db resolve a channel's owning
-- community *without* a community_id predicate — that independence is the
-- point (buzz-db/src/lib.rs: the read-seam projects a row's true label
-- regardless of the fetch query's WHERE clause, which is what makes
-- Inv_NonInterference non-vacuous):
--
--   Db::communities_of_channels  SELECT id, community_id FROM channels
--                                WHERE id = ANY($1) AND deleted_at IS NULL
--   Db::community_of_channel     SELECT community_id FROM channels
--                                WHERE id = $1 AND deleted_at IS NULL
--
-- A composite btree is only usable when its leading column is constrained, so
-- neither query can use the primary key and no other index leads with `id`.
-- Both therefore sequentially scan `channels` on every call. Observed as the
-- top "Load by waits (AAS)" on the staging writer (db.r8g.8xlarge, ~53% CPU).
--
-- INCLUDE (community_id): both queries select only (id, community_id), so the
-- index is covering and the planner can serve them index-only, with no heap
-- fetch for visible rows.
--
-- Partial on deleted_at IS NULL: matches both predicates exactly, keeps the
-- index off soft-deleted history, and lets Postgres skip re-checking the
-- predicate.
--
-- NOT UNIQUE, deliberately. `id` alone is not unique in this table —
-- handlers/command_executor.rs documents that community_of_channel(channel_id)
-- is ambiguous because the same channel id can appear under more than one
-- community. A unique index would encode a false constraint and would fail to
-- build on any database that already holds such a pair.
--
-- Lock note: built without CONCURRENTLY, matching migration 0004's precedent —
-- sqlx runs each migration inside a transaction and CREATE INDEX CONCURRENTLY
-- cannot run in one. This takes a SHARE lock on `channels` (blocking writes,
-- not reads) for the duration of the build. `channels` is a small table
-- relative to `events`, so this is expected to be brief, but on a large
-- brownfield database an operator may prefer to pre-build it by hand:
--
--   CREATE INDEX CONCURRENTLY idx_channels_id_live
--       ON channels (id) INCLUDE (community_id)
--       WHERE deleted_at IS NULL;
--
-- IF NOT EXISTS then makes this migration a no-op on that database.
--
-- Additive migration: previously applied files must not change checksum.

CREATE INDEX IF NOT EXISTS idx_channels_id_live
    ON channels (id) INCLUDE (community_id)
    WHERE deleted_at IS NULL;
