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    /// 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() as u64,
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)]
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 Default for ProxyRow {
110    fn default() -> Self {
111        Self {
112            values: BTreeMap::new(),
113        }
114    }
115}
116
117impl From<BTreeMap<String, Value>> for ProxyRow {
118    fn from(values: BTreeMap<String, Value>) -> Self {
119        Self { values }
120    }
121}
122
123impl From<ProxyRow> for BTreeMap<String, Value> {
124    fn from(row: ProxyRow) -> Self {
125        row.values
126    }
127}
128
129impl From<ProxyRow> for Vec<(String, Value)> {
130    fn from(row: ProxyRow) -> Self {
131        row.values.into_iter().collect()
132    }
133}
134
135impl From<ProxyRow> for QueryResult {
136    fn from(row: ProxyRow) -> Self {
137        QueryResult {
138            row: QueryResultRow::Proxy(row),
139        }
140    }
141}
142
143#[cfg(feature = "with-json")]
144impl Into<serde_json::Value> for ProxyRow {
145    fn into(self) -> serde_json::Value {
146        self.values
147            .into_iter()
148            .map(|(k, v)| (k, sea_query::sea_value_to_json_value(&v)))
149            .collect()
150    }
151}
152
153/// Convert [QueryResult] to [ProxyRow]
154pub fn from_query_result_to_proxy_row(result: &QueryResult) -> ProxyRow {
155    match &result.row {
156        #[cfg(feature = "sqlx-mysql")]
157        QueryResultRow::SqlxMySql(row) => crate::from_sqlx_mysql_row_to_proxy_row(&row),
158        #[cfg(feature = "sqlx-postgres")]
159        QueryResultRow::SqlxPostgres(row) => crate::from_sqlx_postgres_row_to_proxy_row(&row),
160        #[cfg(feature = "sqlx-sqlite")]
161        QueryResultRow::SqlxSqlite(row) => crate::from_sqlx_sqlite_row_to_proxy_row(&row),
162        #[cfg(feature = "mock")]
163        QueryResultRow::Mock(row) => ProxyRow {
164            values: row.values.clone(),
165        },
166        QueryResultRow::Proxy(row) => row.to_owned(),
167    }
168}
169
170impl ProxyRow {
171    /// Get a value from the [ProxyRow]
172    pub fn try_get<T, I: crate::ColIdx>(&self, index: I) -> Result<T, DbErr>
173    where
174        T: ValueType,
175    {
176        if let Some(index) = index.as_str() {
177            T::try_from(
178                self.values
179                    .get(index)
180                    .ok_or_else(|| query_err(format!("No column for ColIdx {index:?}")))?
181                    .clone(),
182            )
183            .map_err(type_err)
184        } else if let Some(index) = index.as_usize() {
185            let (_, value) = self
186                .values
187                .iter()
188                .nth(*index)
189                .ok_or_else(|| query_err(format!("Column at index {index} not found")))?;
190            T::try_from(value.clone()).map_err(type_err)
191        } else {
192            unreachable!("Missing ColIdx implementation for ProxyRow");
193        }
194    }
195
196    /// An iterator over the keys and values of a proxy row
197    pub fn into_column_value_tuples(self) -> impl Iterator<Item = (String, Value)> {
198        self.values.into_iter()
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use crate::{
205        entity::*, tests_cfg::*, Database, DbBackend, DbErr, ProxyDatabaseTrait, ProxyExecResult,
206        ProxyRow, Statement,
207    };
208    use std::sync::{Arc, Mutex};
209
210    #[derive(Debug)]
211    struct ProxyDb {}
212
213    impl ProxyDatabaseTrait for ProxyDb {
214        async fn query(&self, statement: Statement) -> Result<Vec<ProxyRow>, DbErr> {
215            println!("SQL query: {}", statement.sql);
216            Ok(vec![].into())
217        }
218
219        async fn execute(&self, statement: Statement) -> Result<ProxyExecResult, DbErr> {
220            println!("SQL execute: {}", statement.sql);
221            Ok(ProxyExecResult {
222                last_insert_id: 1,
223                rows_affected: 1,
224            })
225        }
226    }
227
228    #[smol_potat::test]
229    async fn create_proxy_conn() {
230        let _db = Database::connect_proxy(DbBackend::MySql, Arc::new(Box::new(ProxyDb {})))
231            .await
232            .unwrap();
233    }
234
235    #[smol_potat::test]
236    async fn select_rows() {
237        let db = Database::connect_proxy(DbBackend::MySql, Arc::new(Box::new(ProxyDb {})))
238            .await
239            .unwrap();
240
241        let _ = cake::Entity::find().all(&db).await;
242    }
243
244    #[smol_potat::test]
245    async fn insert_one_row() {
246        let db = Database::connect_proxy(DbBackend::MySql, Arc::new(Box::new(ProxyDb {})))
247            .await
248            .unwrap();
249
250        let item = cake::ActiveModel {
251            id: NotSet,
252            name: Set("Alice".to_string()),
253        };
254
255        cake::Entity::insert(item).exec(&db).await.unwrap();
256    }
257}