Skip to main content

mcpr_integrations/store/query/
session_detail.rs

1//! Query: `mcpr proxy session <id>` — drill into a single session with its requests.
2
3use rusqlite::params;
4use serde::Serialize;
5
6use super::QueryEngine;
7use super::logs::{LOG_COLUMNS, LogRow, map_log_row};
8
9/// A session with all its associated request logs.
10#[derive(Debug, Serialize)]
11pub struct SessionDetail {
12    pub session_id: String,
13    pub client_name: Option<String>,
14    pub client_version: Option<String>,
15    pub client_platform: Option<String>,
16    pub started_at: i64,
17    pub last_seen_at: i64,
18    pub ended_at: Option<i64>,
19    pub total_calls: i64,
20    pub total_errors: i64,
21    /// All requests in this session, ordered oldest-first.
22    pub requests: Vec<LogRow>,
23}
24
25impl QueryEngine {
26    /// Fetch a single session by ID, along with all its request logs.
27    ///
28    /// Returns `None` if the session doesn't exist.
29    pub fn session_detail(
30        &self,
31        session_id: &str,
32    ) -> Result<Option<SessionDetail>, rusqlite::Error> {
33        // Step 1: fetch the session row (supports prefix matching like git SHAs).
34        let session_sql = "
35            SELECT
36                session_id, client_name, client_version, client_platform,
37                started_at, last_seen_at, ended_at, total_calls, total_errors
38            FROM sessions
39            WHERE session_id LIKE ?1 || '%'
40        ";
41
42        let session = self
43            .conn()
44            .query_row(session_sql, params![session_id], |row| {
45                Ok((
46                    row.get::<_, String>(0)?,
47                    row.get::<_, Option<String>>(1)?,
48                    row.get::<_, Option<String>>(2)?,
49                    row.get::<_, Option<String>>(3)?,
50                    row.get::<_, i64>(4)?,
51                    row.get::<_, i64>(5)?,
52                    row.get::<_, Option<i64>>(6)?,
53                    row.get::<_, i64>(7)?,
54                    row.get::<_, i64>(8)?,
55                ))
56            });
57
58        let (
59            sid,
60            client_name,
61            client_version,
62            client_platform,
63            started_at,
64            last_seen_at,
65            ended_at,
66            total_calls,
67            total_errors,
68        ) = match session {
69            Ok(row) => row,
70            Err(rusqlite::Error::QueryReturnedNoRows) => return Ok(None),
71            Err(e) => return Err(e),
72        };
73
74        // Step 2: fetch all requests for this session, oldest first.
75        let requests_sql = format!(
76            "SELECT {LOG_COLUMNS}
77            FROM requests
78            WHERE session_id = ?1
79            ORDER BY ts ASC"
80        );
81
82        let mut stmt = self.conn().prepare(&requests_sql)?;
83        let requests: Vec<LogRow> = stmt
84            .query_map(params![&sid], map_log_row)?
85            .collect::<Result<Vec<_>, _>>()?;
86
87        Ok(Some(SessionDetail {
88            session_id: sid,
89            client_name,
90            client_version,
91            client_platform,
92            started_at,
93            last_seen_at,
94            ended_at,
95            total_calls,
96            total_errors,
97            requests,
98        }))
99    }
100}