sea_orm/database/
proxy.rs

1use crate::{error::*, ExecResult, ExecResultHolder, QueryResult, QueryResultRow, Statement};
2
3use sea_query::{Value, ValueType};
4use std::{collections::BTreeMap, fmt::Debug};
5
6/// Defines the [ProxyDatabaseTrait] to save the functions
7#[async_trait::async_trait]
8pub trait ProxyDatabaseTrait: Send + Sync + std::fmt::Debug {
9    /// Execute a query in the [ProxyDatabase], and return the query results
10    async fn query(&self, statement: Statement) -> Result<Vec<ProxyRow>, DbErr>;
11
12    /// Execute a command in the [ProxyDatabase], and report the number of rows affected
13    async fn execute(&self, statement: Statement) -> Result<ProxyExecResult, DbErr>;
14
15    /// Begin a transaction in the [ProxyDatabase]
16    async fn begin(&self) {}
17
18    /// Commit a transaction in the [ProxyDatabase]
19    async fn commit(&self) {}
20
21    /// Rollback a transaction in the [ProxyDatabase]
22    async fn rollback(&self) {}
23
24    /// Ping the [ProxyDatabase], it should return an error if the database is not available
25    async fn ping(&self) -> Result<(), DbErr> {
26        Ok(())
27    }
28}
29
30/// Defines the results obtained from a [ProxyDatabase]
31#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
32pub struct ProxyExecResult {
33    /// The last inserted id on auto-increment
34    pub last_insert_id: u64,
35    /// The number of rows affected by the database operation
36    pub rows_affected: u64,
37}
38
39impl ProxyExecResult {
40    /// Create a new [ProxyExecResult] from the last inserted id and the number of rows affected
41    pub fn new(last_insert_id: u64, rows_affected: u64) -> Self {
42        Self {
43            last_insert_id,
44            rows_affected,
45        }
46    }
47}
48
49impl Default for ExecResultHolder {
50    fn default() -> Self {
51        Self::Proxy(ProxyExecResult::default())
52    }
53}
54
55impl From<ProxyExecResult> for ExecResult {
56    fn from(result: ProxyExecResult) -> Self {
57        Self {
58            result: ExecResultHolder::Proxy(result),
59        }
60    }
61}
62
63impl From<ExecResult> for ProxyExecResult {
64    fn from(result: ExecResult) -> Self {
65        match result.result {
66            #[cfg(feature = "sqlx-mysql")]
67            ExecResultHolder::SqlxMySql(result) => Self {
68                last_insert_id: result.last_insert_id() as u64,
69                rows_affected: result.rows_affected(),
70            },
71            #[cfg(feature = "sqlx-postgres")]
72            ExecResultHolder::SqlxPostgres(result) => Self {
73                last_insert_id: 0,
74                rows_affected: result.rows_affected(),
75            },
76            #[cfg(feature = "sqlx-sqlite")]
77            ExecResultHolder::SqlxSqlite(result) => Self {
78                last_insert_id: result.last_insert_rowid() as u64,
79                rows_affected: result.rows_affected(),
80            },
81            #[cfg(feature = "mock")]
82            ExecResultHolder::Mock(result) => Self {
83                last_insert_id: result.last_insert_id,
84                rows_affected: result.rows_affected,
85            },
86            ExecResultHolder::Proxy(result) => result,
87        }
88    }
89}
90
91/// Defines the structure of a Row for the [ProxyDatabase]
92/// which is just a [BTreeMap]<[String], [Value]>
93#[derive(Clone, Debug)]
94pub struct ProxyRow {
95    /// The values of the single row
96    pub values: BTreeMap<String, Value>,
97}
98
99impl ProxyRow {
100    /// Create a new [ProxyRow] from a [BTreeMap]<[String], [Value]>
101    pub fn new(values: BTreeMap<String, Value>) -> Self {
102        Self { values }
103    }
104}
105
106impl Default for ProxyRow {
107    fn default() -> Self {
108        Self {
109            values: BTreeMap::new(),
110        }
111    }
112}
113
114impl From<BTreeMap<String, Value>> for ProxyRow {
115    fn from(values: BTreeMap<String, Value>) -> Self {
116        Self { values }
117    }
118}
119
120impl From<ProxyRow> for BTreeMap<String, Value> {
121    fn from(row: ProxyRow) -> Self {
122        row.values
123    }
124}
125
126impl From<ProxyRow> for Vec<(String, Value)> {
127    fn from(row: ProxyRow) -> Self {
128        row.values.into_iter().collect()
129    }
130}
131
132impl From<ProxyRow> for QueryResult {
133    fn from(row: ProxyRow) -> Self {
134        QueryResult {
135            row: QueryResultRow::Proxy(row),
136        }
137    }
138}
139
140#[cfg(feature = "with-json")]
141impl Into<serde_json::Value> for ProxyRow {
142    fn into(self) -> serde_json::Value {
143        self.values
144            .into_iter()
145            .map(|(k, v)| (k, sea_query::sea_value_to_json_value(&v)))
146            .collect()
147    }
148}
149
150/// Convert [QueryResult] to [ProxyRow]
151pub fn from_query_result_to_proxy_row(result: &QueryResult) -> ProxyRow {
152    match &result.row {
153        #[cfg(feature = "sqlx-mysql")]
154        QueryResultRow::SqlxMySql(row) => crate::from_sqlx_mysql_row_to_proxy_row(&row),
155        #[cfg(feature = "sqlx-postgres")]
156        QueryResultRow::SqlxPostgres(row) => crate::from_sqlx_postgres_row_to_proxy_row(&row),
157        #[cfg(feature = "sqlx-sqlite")]
158        QueryResultRow::SqlxSqlite(row) => crate::from_sqlx_sqlite_row_to_proxy_row(&row),
159        #[cfg(feature = "mock")]
160        QueryResultRow::Mock(row) => ProxyRow {
161            values: row.values.clone(),
162        },
163        QueryResultRow::Proxy(row) => row.to_owned(),
164    }
165}
166
167impl ProxyRow {
168    /// Get a value from the [ProxyRow]
169    pub fn try_get<T, I: crate::ColIdx>(&self, index: I) -> Result<T, DbErr>
170    where
171        T: ValueType,
172    {
173        if let Some(index) = index.as_str() {
174            T::try_from(
175                self.values
176                    .get(index)
177                    .ok_or_else(|| query_err(format!("No column for ColIdx {index:?}")))?
178                    .clone(),
179            )
180            .map_err(type_err)
181        } else if let Some(index) = index.as_usize() {
182            let (_, value) = self
183                .values
184                .iter()
185                .nth(*index)
186                .ok_or_else(|| query_err(format!("Column at index {index} not found")))?;
187            T::try_from(value.clone()).map_err(type_err)
188        } else {
189            unreachable!("Missing ColIdx implementation for ProxyRow");
190        }
191    }
192
193    /// An iterator over the keys and values of a proxy row
194    pub fn into_column_value_tuples(self) -> impl Iterator<Item = (String, Value)> {
195        self.values.into_iter()
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use crate::{
202        entity::*, tests_cfg::*, Database, DbBackend, DbErr, ProxyDatabaseTrait, ProxyExecResult,
203        ProxyRow, Statement,
204    };
205    use std::sync::{Arc, Mutex};
206
207    #[derive(Debug)]
208    struct ProxyDb {}
209
210    impl ProxyDatabaseTrait for ProxyDb {
211        async fn query(&self, statement: Statement) -> Result<Vec<ProxyRow>, DbErr> {
212            println!("SQL query: {}", statement.sql);
213            Ok(vec![].into())
214        }
215
216        async fn execute(&self, statement: Statement) -> Result<ProxyExecResult, DbErr> {
217            println!("SQL execute: {}", statement.sql);
218            Ok(ProxyExecResult {
219                last_insert_id: 1,
220                rows_affected: 1,
221            })
222        }
223    }
224
225    #[smol_potat::test]
226    async fn create_proxy_conn() {
227        let _db = Database::connect_proxy(DbBackend::MySql, Arc::new(Box::new(ProxyDb {})))
228            .await
229            .unwrap();
230    }
231
232    #[smol_potat::test]
233    async fn select_rows() {
234        let db = Database::connect_proxy(DbBackend::MySql, Arc::new(Box::new(ProxyDb {})))
235            .await
236            .unwrap();
237
238        let _ = cake::Entity::find().all(&db).await;
239    }
240
241    #[smol_potat::test]
242    async fn insert_one_row() {
243        let db = Database::connect_proxy(DbBackend::MySql, Arc::new(Box::new(ProxyDb {})))
244            .await
245            .unwrap();
246
247        let item = cake::ActiveModel {
248            id: NotSet,
249            name: Set("Alice".to_string()),
250        };
251
252        cake::Entity::insert(item).exec(&db).await.unwrap();
253    }
254}