-- 017_query_performance.sql
-- Ytelsesoptimalisering: composite indexes og forbedret recompute_access.
--
-- Profileringsresultater viste at:
-- 1. recompute_access step 2 bruker idx_edges_type (lav selektivitet) og
--    filtrerer på target_id etterpå — trenger composite index.
-- 2. RLS edge_select policy gjør IN-sjekk mot node_access for source_id
--    og target_id — composite index på edges (target_id, edge_type) hjelper
--    joinmønsteret i recompute_access og belongs_to-oppslag.
-- 3. Alias-oppslag (source_id + edge_type + system) mangler composite index.
-- 4. nodes-oppslag sortert på created_at DESC mangler index.
-- 5. recompute_access kjører steg 3 og 4 uansett, selv om de sjelden
--    treffer — optimaliser med betingede sjekker.
--
-- Ref: oppgave 12.4, docs/retninger/datalaget.md

BEGIN;

-- =============================================================================
-- Composite indexes på edges
-- =============================================================================

-- Dekker recompute_access steg 2 (belongs_to-oppslag mot target)
-- og generelle "hent barn av node"-spørringer (query_board, etc.)
CREATE INDEX IF NOT EXISTS idx_edges_target_type
    ON edges (target_id, edge_type);

-- Dekker alias-oppslag og "finn utgående edges av type"-spørringer
CREATE INDEX IF NOT EXISTS idx_edges_source_type
    ON edges (source_id, edge_type);

-- Dekker member_of-oppslag i recompute_access steg 3 (covering index)
-- Inkluderer source_id for å unngå heap-oppslag
CREATE INDEX IF NOT EXISTS idx_edges_target_memberof
    ON edges (target_id)
    WHERE edge_type = 'member_of';

-- =============================================================================
-- Composite index på nodes for sortering
-- =============================================================================

-- query_nodes bruker ORDER BY created_at DESC med LIMIT/OFFSET.
-- Med RLS kreves sekvensielt scan, men for ikke-RLS-spørringer
-- (superuser context) gir dette stor gevinst.
CREATE INDEX IF NOT EXISTS idx_nodes_created_at_desc
    ON nodes (created_at DESC);

-- Dekker vanlig mønster: filtrer på node_kind, sorter på created_at
CREATE INDEX IF NOT EXISTS idx_nodes_kind_created
    ON nodes (node_kind, created_at DESC);

-- =============================================================================
-- Covering index på node_access for RLS
-- =============================================================================

-- RLS-policyen gjør: WHERE subject_id = current_node_id()
-- og returnerer object_id. Covering index unngår heap-oppslag.
-- Erstatter ikke idx_na_subject som brukes av PK, men PostgreSQL
-- velger denne fordi den dekker hele spørringen.
CREATE INDEX IF NOT EXISTS idx_na_subject_covering
    ON node_access (subject_id) INCLUDE (object_id);

-- =============================================================================
-- Optimalisert recompute_access
-- =============================================================================

-- Hovedforbedring: steg 3 (team→medlemmer) og steg 4 (arv fra team)
-- kjøres nå bare når det er relevant. Steg 3 sjekker om subject faktisk
-- er et team (har member_of-edges mot seg). Steg 4 sjekker om root_node
-- faktisk har egne node_access-rader (= er et team/entitet med tilgang).
-- For den vanlige casen (bruker→samling owner/admin) gjør dette at
-- steg 3 og 4 er billige EXISTS-sjekker i stedet for fulle INSERT-selects.

CREATE OR REPLACE FUNCTION recompute_access(
    p_subject_id   UUID,
    p_root_node_id UUID,
    p_access       access_level,
    p_via_edge     UUID
) RETURNS void AS $$
BEGIN
    -- Steg 1: Direkte tilgang til roten
    INSERT INTO node_access (subject_id, object_id, access, via_edge)
    VALUES (p_subject_id, p_root_node_id, p_access, p_via_edge)
    ON CONFLICT (subject_id, object_id)
    DO UPDATE SET access = GREATEST(node_access.access, p_access),
                  via_edge = CASE
                      WHEN p_access > node_access.access THEN p_via_edge
                      ELSE node_access.via_edge
                  END;

    -- Steg 2: Transitiv: noder som tilhører roten (belongs_to)
    -- Bruker idx_edges_target_type (target_id, edge_type) for rask oppslag
    INSERT INTO node_access (subject_id, object_id, access, via_edge)
    SELECT p_subject_id, e.source_id, p_access, p_via_edge
    FROM edges e
    WHERE e.target_id = p_root_node_id
      AND e.edge_type = 'belongs_to'
    ON CONFLICT (subject_id, object_id)
    DO UPDATE SET access = GREATEST(node_access.access, p_access),
                  via_edge = CASE
                      WHEN p_access > node_access.access THEN p_via_edge
                      ELSE node_access.via_edge
                  END;

    -- Steg 3: Hvis subject er et team: propager til alle teammedlemmer.
    -- Sjekk først om det finnes member_of-edges (= subject er et team).
    -- For vanlige brukere er dette en billig EXISTS som returnerer false.
    IF EXISTS (
        SELECT 1 FROM edges
        WHERE target_id = p_subject_id AND edge_type = 'member_of'
        LIMIT 1
    ) THEN
        INSERT INTO node_access (subject_id, object_id, access, via_edge)
        SELECT e.source_id, na.object_id, na.access, na.via_edge
        FROM node_access na
        JOIN edges e ON e.target_id = p_subject_id
          AND e.edge_type = 'member_of'
        WHERE na.subject_id = p_subject_id
        ON CONFLICT (subject_id, object_id)
        DO UPDATE SET access = GREATEST(node_access.access, EXCLUDED.access),
                      via_edge = CASE
                          WHEN EXCLUDED.access > node_access.access THEN EXCLUDED.via_edge
                          ELSE node_access.via_edge
                      END;
    END IF;

    -- Steg 4: Team-transitivitet — arv tilgang fra teamet.
    -- Bare relevant når subject meldes inn i et team (root_node er team).
    -- Sjekk om root_node har egne node_access-rader som subject.
    IF EXISTS (
        SELECT 1 FROM node_access
        WHERE subject_id = p_root_node_id
        LIMIT 1
    ) THEN
        INSERT INTO node_access (subject_id, object_id, access, via_edge)
        SELECT p_subject_id, na.object_id, na.access, na.via_edge
        FROM node_access na
        WHERE na.subject_id = p_root_node_id
        ON CONFLICT (subject_id, object_id)
        DO UPDATE SET access = GREATEST(node_access.access, EXCLUDED.access),
                      via_edge = CASE
                          WHEN EXCLUDED.access > node_access.access THEN EXCLUDED.via_edge
                          ELSE node_access.via_edge
                      END;
    END IF;
END;
$$ LANGUAGE plpgsql;

-- =============================================================================
-- pg_stat_statements (krever manuell konfigurasjon)
-- =============================================================================

-- pg_stat_statements krever shared_preload_libraries i postgresql.conf.
-- For å aktivere:
-- 1. Legg til i docker-compose.yml for postgres:
--    command: postgres -c shared_preload_libraries=pg_stat_statements
--                      -c pg_stat_statements.track=all
-- 2. Restart postgres-containeren
-- 3. Kjør: CREATE EXTENSION pg_stat_statements;
-- 4. Se topp-spørringer: SELECT * FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 20;

-- =============================================================================
-- Oppdater statistikk for query planner
-- =============================================================================

ANALYZE nodes;
ANALYZE edges;
ANALYZE node_access;

COMMIT;
