1use super::rows::*;
2use super::*;
3
4impl Store {
5 pub fn list_sessions(&self, workspace: &str) -> Result<Vec<SessionRecord>> {
6 Ok(self
7 .list_sessions_page(workspace, 0, i64::MAX as usize, SessionFilter::default())?
8 .rows)
9 }
10
11 pub fn list_sessions_page(
12 &self,
13 workspace: &str,
14 offset: usize,
15 limit: usize,
16 filter: SessionFilter,
17 ) -> Result<SessionPage> {
18 let (where_sql, args) = session_filter_sql(workspace, &filter);
19 let total = self.query_session_page_count(&where_sql, &args)?;
20 let rows = self.query_session_page_rows(&where_sql, &args, offset, limit)?;
21 let next = offset.saturating_add(rows.len());
22 Ok(SessionPage {
23 rows,
24 total,
25 next_offset: (next < total).then_some(next),
26 })
27 }
28
29 pub(super) fn query_session_page_count(
30 &self,
31 where_sql: &str,
32 args: &[Value],
33 ) -> Result<usize> {
34 let sql = format!("SELECT COUNT(*) FROM sessions {where_sql}");
35 let total: i64 = self
36 .conn
37 .query_row(&sql, params_from_iter(args.iter()), |r| r.get(0))?;
38 Ok(total as usize)
39 }
40
41 pub(super) fn query_session_page_rows(
42 &self,
43 where_sql: &str,
44 args: &[Value],
45 offset: usize,
46 limit: usize,
47 ) -> Result<Vec<SessionRecord>> {
48 let sql = format!(
49 "{SESSION_SELECT} {where_sql} ORDER BY started_at_ms DESC, id ASC LIMIT ? OFFSET ?"
50 );
51 let mut values = args.to_vec();
52 values.push(Value::Integer(limit.min(i64::MAX as usize) as i64));
53 values.push(Value::Integer(offset.min(i64::MAX as usize) as i64));
54 let mut stmt = self.conn.prepare(&sql)?;
55 let rows = stmt.query_map(params_from_iter(values.iter()), session_row)?;
56 rows.map(|r| r.map_err(anyhow::Error::from)).collect()
57 }
58
59 pub fn list_sessions_started_after(
60 &self,
61 workspace: &str,
62 after_started_at_ms: u64,
63 ) -> Result<Vec<SessionRecord>> {
64 let mut stmt = self.conn.prepare(
65 "SELECT id, agent, model, workspace, started_at_ms, ended_at_ms, status, trace_path,
66 start_commit, end_commit, branch, dirty_start, dirty_end, repo_binding_source,
67 prompt_fingerprint, parent_session_id, agent_version, os, arch,
68 repo_file_count, repo_total_loc
69 FROM sessions
70 WHERE workspace = ?1 AND started_at_ms > ?2
71 ORDER BY started_at_ms DESC, id ASC",
72 )?;
73 let rows = stmt.query_map(params![workspace, after_started_at_ms as i64], session_row)?;
74 rows.map(|r| r.map_err(anyhow::Error::from)).collect()
75 }
76
77 pub fn session_statuses(&self, ids: &[String]) -> Result<Vec<SessionStatusRow>> {
78 if ids.is_empty() {
79 return Ok(Vec::new());
80 }
81 let placeholders = ids.iter().map(|_| "?").collect::<Vec<_>>().join(",");
82 let sql =
83 format!("SELECT id, status, ended_at_ms FROM sessions WHERE id IN ({placeholders})");
84 let mut stmt = self.conn.prepare(&sql)?;
85 let params: Vec<&dyn rusqlite::ToSql> =
86 ids.iter().map(|s| s as &dyn rusqlite::ToSql).collect();
87 let rows = stmt.query_map(params.as_slice(), |r| {
88 let status: String = r.get(1)?;
89 Ok(SessionStatusRow {
90 id: r.get(0)?,
91 status: status_from_str(&status),
92 ended_at_ms: r.get::<_, Option<i64>>(2)?.map(|v| v as u64),
93 })
94 })?;
95 rows.map(|r| r.map_err(anyhow::Error::from)).collect()
96 }
97
98 pub fn get_session(&self, id: &str) -> Result<Option<SessionRecord>> {
99 let mut stmt = self.conn.prepare(
100 "SELECT id, agent, model, workspace, started_at_ms, ended_at_ms, status, trace_path,
101 start_commit, end_commit, branch, dirty_start, dirty_end, repo_binding_source,
102 prompt_fingerprint, parent_session_id, agent_version, os, arch,
103 repo_file_count, repo_total_loc
104 FROM sessions WHERE id = ?1",
105 )?;
106 let mut rows = stmt.query_map(params![id], |row| {
107 Ok((
108 row.get::<_, String>(0)?,
109 row.get::<_, String>(1)?,
110 row.get::<_, Option<String>>(2)?,
111 row.get::<_, String>(3)?,
112 row.get::<_, i64>(4)?,
113 row.get::<_, Option<i64>>(5)?,
114 row.get::<_, String>(6)?,
115 row.get::<_, String>(7)?,
116 row.get::<_, Option<String>>(8)?,
117 row.get::<_, Option<String>>(9)?,
118 row.get::<_, Option<String>>(10)?,
119 row.get::<_, Option<i64>>(11)?,
120 row.get::<_, Option<i64>>(12)?,
121 row.get::<_, String>(13)?,
122 row.get::<_, Option<String>>(14)?,
123 row.get::<_, Option<String>>(15)?,
124 row.get::<_, Option<String>>(16)?,
125 row.get::<_, Option<String>>(17)?,
126 row.get::<_, Option<String>>(18)?,
127 row.get::<_, Option<i64>>(19)?,
128 row.get::<_, Option<i64>>(20)?,
129 ))
130 })?;
131
132 if let Some(row) = rows.next() {
133 let (
134 id,
135 agent,
136 model,
137 workspace,
138 started,
139 ended,
140 status_str,
141 trace,
142 start_commit,
143 end_commit,
144 branch,
145 dirty_start,
146 dirty_end,
147 source,
148 prompt_fingerprint,
149 parent_session_id,
150 agent_version,
151 os,
152 arch,
153 repo_file_count,
154 repo_total_loc,
155 ) = row?;
156 Ok(Some(SessionRecord {
157 id,
158 agent,
159 model,
160 workspace,
161 started_at_ms: started as u64,
162 ended_at_ms: ended.map(|v| v as u64),
163 status: status_from_str(&status_str),
164 trace_path: trace,
165 start_commit,
166 end_commit,
167 branch,
168 dirty_start: dirty_start.map(i64_to_bool),
169 dirty_end: dirty_end.map(i64_to_bool),
170 repo_binding_source: empty_to_none(source),
171 prompt_fingerprint,
172 parent_session_id,
173 agent_version,
174 os,
175 arch,
176 repo_file_count: repo_file_count.map(|v| v as u32),
177 repo_total_loc: repo_total_loc.map(|v| v as u64),
178 }))
179 } else {
180 Ok(None)
181 }
182 }
183}