sea_orm/database/
proxy.rs

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