1use std::sync::Arc;
2use std::time::{Duration, SystemTime};
3
4use alopex_cluster::TableLifecycleEffect;
5use alopex_core::async_runtime::{BoxFuture, BoxStream};
6use alopex_sql::catalog::TableMetadata;
7use alopex_sql::executor::{ExecutionResult, ExecutorError, Row};
8use alopex_sql::planner::PlannedStatement;
9use alopex_sql::storage::erased::ErasedAsyncSqlTransaction;
10use dashmap::DashMap;
11use futures::StreamExt;
12use tokio::sync::mpsc;
13use tokio_stream::wrappers::ReceiverStream;
14use uuid::Uuid;
15
16use crate::error::{Result, ServerError};
17
18#[derive(Clone, Debug, Eq, Hash, PartialEq, serde::Serialize, serde::Deserialize)]
20pub struct SessionId(Uuid);
21
22impl SessionId {
23 pub fn new() -> Self {
24 Self(Uuid::new_v4())
25 }
26}
27
28impl Default for SessionId {
29 fn default() -> Self {
30 Self::new()
31 }
32}
33
34impl std::fmt::Display for SessionId {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 write!(f, "{}", self.0)
37 }
38}
39
40impl std::str::FromStr for SessionId {
41 type Err = uuid::Error;
42
43 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
44 Ok(Self(Uuid::parse_str(s)?))
45 }
46}
47
48#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
50pub enum SessionState {
51 Idle,
52 InTransaction,
53 Committing,
54 RollingBack,
55}
56
57#[derive(Clone, Debug, serde::Serialize)]
59pub struct SessionSnapshot {
60 pub id: SessionId,
61 pub has_transaction: bool,
62 pub created_at: SystemTime,
63 pub last_active: SystemTime,
64 pub expires_at: SystemTime,
65 pub state: SessionState,
66}
67
68#[derive(Clone)]
70pub struct TxnHandle {
71 inner: Arc<TxnHandleInner>,
72}
73
74#[derive(Clone)]
75pub enum CatalogRollbackEffect {
76 DropTable { table_name: String },
77 CreateTable { table: Box<TableMetadata> },
78}
79
80struct TxnHandleInner {
81 txn: tokio::sync::Mutex<Option<Box<dyn ErasedAsyncSqlTransaction>>>,
82 pending_table_lifecycle_effects: tokio::sync::Mutex<Vec<TableLifecycleEffect>>,
83 pending_catalog_rollback_effects: tokio::sync::Mutex<Vec<CatalogRollbackEffect>>,
84 created_at: SystemTime,
85}
86
87impl TxnHandle {
88 pub fn new(txn: Box<dyn ErasedAsyncSqlTransaction>) -> Self {
89 Self {
90 inner: Arc::new(TxnHandleInner {
91 txn: tokio::sync::Mutex::new(Some(txn)),
92 pending_table_lifecycle_effects: tokio::sync::Mutex::new(Vec::new()),
93 pending_catalog_rollback_effects: tokio::sync::Mutex::new(Vec::new()),
94 created_at: SystemTime::now(),
95 }),
96 }
97 }
98
99 pub fn created_at(&self) -> SystemTime {
100 self.inner.created_at
101 }
102
103 pub fn execute<'a>(
104 &'a self,
105 sql: &'a str,
106 ) -> BoxFuture<'a, alopex_sql::executor::Result<ExecutionResult>> {
107 Box::pin(async move {
108 let mut guard = self.inner.txn.lock().await;
109 let txn = guard
110 .as_mut()
111 .ok_or_else(|| ExecutorError::InvalidOperation {
112 operation: "execute".into(),
113 reason: "transaction is closed".into(),
114 })?;
115 txn.execute(sql).await
116 })
117 }
118
119 pub fn execute_multi<'a>(
120 &'a self,
121 sql: &'a str,
122 ) -> BoxFuture<'a, alopex_sql::executor::Result<Vec<ExecutionResult>>> {
123 Box::pin(async move {
124 let mut guard = self.inner.txn.lock().await;
125 let txn = guard
126 .as_mut()
127 .ok_or_else(|| ExecutorError::InvalidOperation {
128 operation: "execute_multi".into(),
129 reason: "transaction is closed".into(),
130 })?;
131 txn.execute_multi(sql).await
132 })
133 }
134
135 pub fn query<'a>(&'a self, sql: &'a str) -> BoxStream<'a, alopex_sql::executor::Result<Row>> {
136 let (sender, receiver) = mpsc::channel(32);
137 let sql = sql.to_string();
138 let inner = Arc::clone(&self.inner);
139
140 tokio::spawn(async move {
141 let guard = inner.txn.lock().await;
142 let Some(txn) = guard.as_ref() else {
143 let _ = sender
144 .send(Err(ExecutorError::InvalidOperation {
145 operation: "query".into(),
146 reason: "transaction is closed".into(),
147 }))
148 .await;
149 return;
150 };
151 let mut stream = txn.query(&sql);
152 while let Some(item) = stream.next().await {
153 if sender.send(item).await.is_err() {
154 break;
155 }
156 }
157 });
158
159 Box::pin(ReceiverStream::new(receiver))
160 }
161
162 pub fn plan_for_routing<'a>(
163 &'a self,
164 sql: &'a str,
165 ) -> BoxFuture<'a, alopex_sql::executor::Result<Vec<PlannedStatement>>> {
166 Box::pin(async move {
167 let guard = self.inner.txn.lock().await;
168 let txn = guard
169 .as_ref()
170 .ok_or_else(|| ExecutorError::InvalidOperation {
171 operation: "plan_for_routing".into(),
172 reason: "transaction is closed".into(),
173 })?;
174 txn.plan_for_routing(sql).await
175 })
176 }
177
178 pub async fn buffer_table_lifecycle_effects(&self, effects: Vec<TableLifecycleEffect>) {
179 if effects.is_empty() {
180 return;
181 }
182 self.inner
183 .pending_table_lifecycle_effects
184 .lock()
185 .await
186 .extend(effects);
187 }
188
189 pub async fn buffer_catalog_rollback_effects(&self, effects: Vec<CatalogRollbackEffect>) {
190 if effects.is_empty() {
191 return;
192 }
193 self.inner
194 .pending_catalog_rollback_effects
195 .lock()
196 .await
197 .extend(effects);
198 }
199
200 pub async fn commit(self) -> alopex_sql::executor::Result<Vec<TableLifecycleEffect>> {
201 let mut guard = self.inner.txn.lock().await;
202 let txn = guard
203 .take()
204 .ok_or_else(|| ExecutorError::InvalidOperation {
205 operation: "commit".into(),
206 reason: "transaction is closed".into(),
207 })?;
208 txn.commit_boxed().await?;
209 let mut effects = self.inner.pending_table_lifecycle_effects.lock().await;
210 Ok(std::mem::take(&mut *effects))
211 }
212
213 pub async fn rollback(self) -> alopex_sql::executor::Result<Vec<CatalogRollbackEffect>> {
214 let mut guard = self.inner.txn.lock().await;
215 let txn = guard
216 .take()
217 .ok_or_else(|| ExecutorError::InvalidOperation {
218 operation: "rollback".into(),
219 reason: "transaction is closed".into(),
220 })?;
221 let result = txn.rollback_boxed().await;
222 self.inner
223 .pending_table_lifecycle_effects
224 .lock()
225 .await
226 .clear();
227 result?;
228 let mut effects = self.inner.pending_catalog_rollback_effects.lock().await;
229 Ok(std::mem::take(&mut *effects))
230 }
231}
232
233#[derive(Clone, Copy, Debug)]
235pub struct SessionConfig {
236 pub ttl: Duration,
237}
238
239pub type TransactionFactory =
241 Arc<dyn Fn() -> BoxFuture<'static, Result<Box<dyn ErasedAsyncSqlTransaction>>> + Send + Sync>;
242
243pub struct SessionManager {
245 sessions: DashMap<SessionId, Session>,
246 config: SessionConfig,
247 txn_factory: TransactionFactory,
248}
249
250struct Session {
251 id: SessionId,
252 txn_handle: Option<TxnHandle>,
253 created_at: SystemTime,
254 last_active: SystemTime,
255 expires_at: SystemTime,
256 state: SessionState,
257}
258
259impl SessionManager {
260 pub fn new(config: SessionConfig, txn_factory: TransactionFactory) -> Self {
261 Self {
262 sessions: DashMap::new(),
263 config,
264 txn_factory,
265 }
266 }
267
268 pub async fn create_session(&self) -> Result<SessionId> {
269 let now = SystemTime::now();
270 let id = SessionId::new();
271 let session = Session {
272 id: id.clone(),
273 txn_handle: None,
274 created_at: now,
275 last_active: now,
276 expires_at: now + self.config.ttl,
277 state: SessionState::Idle,
278 };
279 self.sessions.insert(id.clone(), session);
280 Ok(id)
281 }
282
283 pub async fn get_session(&self, id: &SessionId) -> Result<SessionSnapshot> {
284 let entry = self
285 .sessions
286 .get(id)
287 .ok_or_else(|| ServerError::NotFound("session not found".into()))?;
288 if entry.expires_at <= SystemTime::now() {
289 drop(entry);
290 self.sessions.remove(id);
291 return Err(ServerError::SessionExpired("session expired".into()));
292 }
293 Ok(SessionSnapshot {
294 id: entry.id.clone(),
295 has_transaction: entry.txn_handle.is_some(),
296 created_at: entry.created_at,
297 last_active: entry.last_active,
298 expires_at: entry.expires_at,
299 state: entry.state,
300 })
301 }
302
303 pub async fn begin_transaction(&self, id: &SessionId) -> Result<TxnHandle> {
304 let mut entry = self
305 .sessions
306 .get_mut(id)
307 .ok_or_else(|| ServerError::NotFound("session not found".into()))?;
308 if entry.expires_at <= SystemTime::now() {
309 drop(entry);
310 self.sessions.remove(id);
311 return Err(ServerError::SessionExpired("session expired".into()));
312 }
313 if entry.txn_handle.is_some() {
314 return Err(ServerError::Conflict("transaction already active".into()));
315 }
316 let txn = (self.txn_factory)().await?;
317 let handle = TxnHandle::new(txn);
318 entry.txn_handle = Some(handle.clone());
319 entry.last_active = SystemTime::now();
320 entry.state = SessionState::InTransaction;
321 Ok(handle)
322 }
323
324 pub async fn get_transaction(&self, id: &SessionId) -> Result<TxnHandle> {
325 let mut entry = self
326 .sessions
327 .get_mut(id)
328 .ok_or_else(|| ServerError::NotFound("session not found".into()))?;
329 if entry.expires_at <= SystemTime::now() {
330 drop(entry);
331 self.sessions.remove(id);
332 return Err(ServerError::SessionExpired("session expired".into()));
333 }
334 let handle = entry
335 .txn_handle
336 .clone()
337 .ok_or_else(|| ServerError::BadRequest("transaction not started".into()))?;
338 entry.last_active = SystemTime::now();
339 entry.state = SessionState::InTransaction;
340 Ok(handle)
341 }
342
343 pub async fn execute_in_session(&self, id: &SessionId, sql: &str) -> Result<ExecutionResult> {
344 let handle = {
345 let mut entry = self
346 .sessions
347 .get_mut(id)
348 .ok_or_else(|| ServerError::NotFound("session not found".into()))?;
349 if entry.expires_at <= SystemTime::now() {
350 drop(entry);
351 self.sessions.remove(id);
352 return Err(ServerError::SessionExpired("session expired".into()));
353 }
354 let handle = entry
355 .txn_handle
356 .clone()
357 .ok_or_else(|| ServerError::BadRequest("transaction not started".into()))?;
358 entry.last_active = SystemTime::now();
359 handle
360 };
361
362 handle
363 .execute(sql)
364 .await
365 .map_err(|err| ServerError::Sql(err.into()))
366 }
367
368 pub async fn commit(&self, id: &SessionId) -> Result<Vec<TableLifecycleEffect>> {
369 let handle = self.take_handle(id, SessionState::Committing)?;
370 let effects = handle
371 .commit()
372 .await
373 .map_err(|err| ServerError::Sql(err.into()))?;
374 Ok(effects)
375 }
376
377 pub async fn rollback(&self, id: &SessionId) -> Result<Vec<CatalogRollbackEffect>> {
378 let handle = self.take_handle(id, SessionState::RollingBack)?;
379 let effects = handle
380 .rollback()
381 .await
382 .map_err(|err| ServerError::Sql(err.into()))?;
383 Ok(effects)
384 }
385
386 pub fn cleanup_expired(&self) {
387 let now = SystemTime::now();
388 let expired: Vec<SessionId> = self
389 .sessions
390 .iter()
391 .filter(|entry| entry.expires_at <= now)
392 .map(|entry| entry.id.clone())
393 .collect();
394 for id in expired {
395 self.sessions.remove(&id);
396 }
397 }
398
399 fn take_handle(&self, id: &SessionId, state: SessionState) -> Result<TxnHandle> {
400 let mut entry = self
401 .sessions
402 .get_mut(id)
403 .ok_or_else(|| ServerError::NotFound("session not found".into()))?;
404 if entry.expires_at <= SystemTime::now() {
405 drop(entry);
406 self.sessions.remove(id);
407 return Err(ServerError::SessionExpired("session expired".into()));
408 }
409 let handle = entry
410 .txn_handle
411 .take()
412 .ok_or_else(|| ServerError::BadRequest("transaction not started".into()))?;
413 entry.state = state;
414 entry.last_active = SystemTime::now();
415 Ok(handle)
416 }
417}