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