tx2_query/
backend.rs

1use crate::error::Result;
2use async_trait::async_trait;
3use serde_json::Value;
4use std::collections::HashMap;
5
6/// Row from a query result
7#[derive(Debug, Clone)]
8pub struct QueryRow {
9    pub columns: HashMap<String, Value>,
10}
11
12impl QueryRow {
13    pub fn new() -> Self {
14        Self {
15            columns: HashMap::new(),
16        }
17    }
18
19    pub fn insert(&mut self, key: String, value: Value) {
20        self.columns.insert(key, value);
21    }
22
23    pub fn get<T>(&self, key: &str) -> Option<T>
24    where
25        T: serde::de::DeserializeOwned,
26    {
27        self.columns
28            .get(key)
29            .and_then(|v| serde_json::from_value(v.clone()).ok())
30    }
31
32    pub fn get_string(&self, key: &str) -> Option<String> {
33        self.columns
34            .get(key)
35            .and_then(|v| v.as_str().map(String::from))
36    }
37
38    pub fn get_i64(&self, key: &str) -> Option<i64> {
39        self.columns.get(key).and_then(|v| v.as_i64())
40    }
41
42    pub fn get_f64(&self, key: &str) -> Option<f64> {
43        self.columns.get(key).and_then(|v| v.as_f64())
44    }
45
46    pub fn get_bool(&self, key: &str) -> Option<bool> {
47        self.columns.get(key).and_then(|v| v.as_bool())
48    }
49}
50
51impl Default for QueryRow {
52    fn default() -> Self {
53        Self::new()
54    }
55}
56
57/// Result of a query
58pub type QueryResult = Vec<QueryRow>;
59
60/// Database backend trait
61#[async_trait]
62pub trait DatabaseBackend: Send + Sync {
63    /// Connect to the database
64    async fn connect(url: &str) -> Result<Self>
65    where
66        Self: Sized;
67
68    /// Execute a SQL statement (no results)
69    async fn execute(&mut self, sql: &str) -> Result<u64>;
70
71    /// Query and return results
72    async fn query(&mut self, sql: &str) -> Result<QueryResult>;
73
74    /// Begin a transaction
75    async fn begin_transaction(&mut self) -> Result<()>;
76
77    /// Commit the current transaction
78    async fn commit(&mut self) -> Result<()>;
79
80    /// Rollback the current transaction
81    async fn rollback(&mut self) -> Result<()>;
82
83    /// Check if connected
84    fn is_connected(&self) -> bool;
85
86    /// Close the connection
87    async fn close(self) -> Result<()>;
88}
89
90/// Transaction guard for automatic rollback
91pub struct Transaction<'a, B: DatabaseBackend> {
92    backend: &'a mut B,
93    committed: bool,
94}
95
96impl<'a, B: DatabaseBackend> Transaction<'a, B> {
97    pub async fn new(backend: &'a mut B) -> Result<Self> {
98        backend.begin_transaction().await?;
99        Ok(Self {
100            backend,
101            committed: false,
102        })
103    }
104
105    pub async fn commit(mut self) -> Result<()> {
106        self.backend.commit().await?;
107        self.committed = true;
108        Ok(())
109    }
110
111    pub async fn execute(&mut self, sql: &str) -> Result<u64> {
112        self.backend.execute(sql).await
113    }
114
115    pub async fn query(&mut self, sql: &str) -> Result<QueryResult> {
116        self.backend.query(sql).await
117    }
118}
119
120impl<'a, B: DatabaseBackend> Drop for Transaction<'a, B> {
121    fn drop(&mut self) {
122        if !self.committed {
123            // Rollback on drop if not committed
124            // Can't use async in drop, so this is best-effort
125            // The backend should handle this on connection drop
126        }
127    }
128}