rsfbclient_diesel/fb/
connection.rs

1//! The Firebird connection
2
3use super::backend::Fb;
4use super::query_builder::FbQueryBuilder;
5use super::transaction::FbTransactionManager;
6use super::value::FbRow;
7use diesel::connection::*;
8use diesel::query_builder::bind_collector::RawBytesBindCollector;
9use diesel::query_builder::*;
10use diesel::result::Error::DatabaseError;
11use diesel::result::*;
12use rsfbclient::{Execute, SqlType};
13use rsfbclient::{Queryable, Row, SimpleConnection as FbRawConnection};
14
15pub struct FbConnection {
16    pub raw: FbRawConnection,
17    tr_manager: FbTransactionManager,
18}
19
20impl SimpleConnection for FbConnection {
21    fn batch_execute(&mut self, query: &str) -> QueryResult<()> {
22        self.raw
23            .execute(query, ())
24            .map_err(|e| DatabaseError(DatabaseErrorKind::Unknown, Box::new(e.to_string())))
25            .map(|_| ())
26    }
27}
28
29impl<'conn, 'query> ConnectionGatWorkaround<'conn, 'query, Fb, DefaultLoadingMode>
30    for FbConnection
31{
32    type Cursor = Box<dyn Iterator<Item = QueryResult<Self::Row>>>;
33    type Row = FbRow;
34}
35
36impl Connection for FbConnection {
37    type TransactionManager = FbTransactionManager;
38    type Backend = Fb;
39
40    fn establish(database_url: &str) -> ConnectionResult<Self> {
41        #[cfg(all(
42            feature = "pure_rust",
43            not(any(feature = "linking", feature = "dynamic_loading"))
44        ))]
45        let mut raw_builder = rsfbclient::builder_pure_rust();
46        #[cfg(any(feature = "linking", feature = "dynamic_loading"))]
47        let raw_builder = rsfbclient::builder_native();
48
49        let raw = raw_builder
50            .from_string(database_url)
51            .map_err(|e| ConnectionError::BadConnection(e.to_string()))?
52            .connect()
53            .map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
54
55        FbConnection::init(raw.into())
56    }
57
58    fn execute_returning_count<T>(&mut self, source: &T) -> QueryResult<usize>
59    where
60        T: QueryFragment<Self::Backend> + QueryId,
61    {
62        let mut bc = RawBytesBindCollector::<Fb>::new();
63        source.collect_binds(&mut bc, &mut (), &Fb)?;
64
65        let mut qb = FbQueryBuilder::new();
66        source.to_sql(&mut qb, &Fb)?;
67        let sql = qb.finish();
68
69        let params: Vec<SqlType> = bc
70            .metadata
71            .into_iter()
72            .zip(bc.binds)
73            .map(|(tp, val)| tp.into_param(val))
74            .collect();
75
76        self.raw
77            .execute(&sql, params)
78            .map_err(|e| DatabaseError(DatabaseErrorKind::Unknown, Box::new(e.to_string())))
79    }
80
81    fn transaction_state(
82        &mut self,
83    ) -> &mut <Self::TransactionManager as TransactionManager<Self>>::TransactionStateData {
84        &mut self.tr_manager
85    }
86}
87
88trait Helper {
89    fn load<'conn, 'query, T>(
90        conn: &'conn mut FbConnection,
91        source: T,
92    ) -> QueryResult<Box<dyn Iterator<Item = QueryResult<FbRow>>>>
93    where
94        T: Query + QueryFragment<Fb> + QueryId + 'query,
95        Fb: diesel::expression::QueryMetadata<T::SqlType>;
96}
97
98impl Helper for ()
99where
100    for<'b> Fb: diesel::backend::HasBindCollector<'b, BindCollector = RawBytesBindCollector<Fb>>,
101{
102    fn load<'conn, 'query, T>(
103        conn: &'conn mut FbConnection,
104        source: T,
105    ) -> QueryResult<Box<dyn Iterator<Item = QueryResult<FbRow>>>>
106    where
107        T: Query + QueryFragment<Fb> + QueryId + 'query,
108        Fb: diesel::expression::QueryMetadata<T::SqlType>,
109    {
110        let source = &source.as_query();
111        let mut bc = RawBytesBindCollector::<Fb>::new();
112        source.collect_binds(&mut bc, &mut (), &Fb)?;
113
114        let mut qb = FbQueryBuilder::new();
115        source.to_sql(&mut qb, &Fb)?;
116        let has_cursor = qb.has_cursor;
117        let sql = qb.finish();
118
119        let params: Vec<SqlType> = bc
120            .metadata
121            .into_iter()
122            .zip(bc.binds)
123            .map(|(tp, val)| tp.into_param(val))
124            .collect();
125
126        let results = if has_cursor {
127            conn.raw.query::<Vec<SqlType>, Row>(&sql, params)
128        } else {
129            match conn
130                .raw
131                .execute_returnable::<Vec<SqlType>, Row>(&sql, params)
132            {
133                Ok(result) => Ok(vec![result]),
134                Err(e) => Err(e),
135            }
136        };
137
138        Ok(Box::new(
139            results
140                .map_err(|e| DatabaseError(DatabaseErrorKind::Unknown, Box::new(e.to_string())))?
141                .into_iter()
142                .map(FbRow::new)
143                .map(Ok),
144        ))
145    }
146}
147
148impl LoadConnection<DefaultLoadingMode> for FbConnection
149where
150    // this additional trait is somehow required
151    // because rustc fails to understand the bound here
152    (): Helper,
153{
154    fn load<'conn, 'query, T>(
155        &'conn mut self,
156        source: T,
157    ) -> QueryResult<LoadRowIter<'conn, 'query, Self, Self::Backend, DefaultLoadingMode>>
158    where
159        T: Query + QueryFragment<Self::Backend> + QueryId + 'query,
160        Self::Backend: diesel::expression::QueryMetadata<T::SqlType>,
161    {
162        <() as Helper>::load(self, source)
163    }
164}
165
166impl FbConnection {
167    /// Create a diesel instance from a active firebird connection
168    pub fn init(conn: FbRawConnection) -> ConnectionResult<Self> {
169        Ok(FbConnection {
170            raw: conn,
171            tr_manager: FbTransactionManager::new(),
172        })
173    }
174}
175
176#[cfg(not(any(feature = "dynamic_loading", feature = "embedded_tests")))]
177#[cfg(test)]
178mod tests {
179
180    use crate::FbConnection;
181    use diesel::connection::SimpleConnection;
182    use diesel::prelude::*;
183    use diesel::result::Error;
184    use rsfbclient::prelude::*;
185
186    #[test]
187    fn establish() -> Result<(), ConnectionError> {
188        FbConnection::establish("firebird://SYSDBA:masterkey@localhost/test.fdb")?;
189
190        Ok(())
191    }
192
193    #[test]
194    fn execute() -> Result<(), Error> {
195        let mut conn =
196            FbConnection::establish("firebird://SYSDBA:masterkey@localhost/test.fdb").unwrap();
197
198        conn.batch_execute("drop table conn_exec").ok();
199
200        conn.batch_execute("create table conn_exec(id int, name varchar(50))")?;
201
202        let affected_rows =
203            diesel::sql_query("insert into conn_exec(id, name) values (10, 'café')")
204                .execute(&mut conn)?;
205        assert_eq!(1, affected_rows);
206
207        Ok(())
208    }
209
210    #[test]
211    fn establish_from_lib_conn() -> Result<(), String> {
212        #[cfg(all(feature = "pure_rust", not(feature = "linking")))]
213        let mut raw_builder = rsfbclient::builder_pure_rust();
214        #[cfg(feature = "linking")]
215        let raw_builder = rsfbclient::builder_native();
216
217        let conn = raw_builder
218            .from_string("firebird://SYSDBA:masterkey@localhost/test.fdb")
219            .map_err(|e| e.to_string())?
220            .transaction(TransactionConfiguration {
221                lock_resolution: TrLockResolution::NoWait,
222                ..TransactionConfiguration::default()
223            })
224            .connect()
225            .map_err(|e| e.to_string())?
226            .into();
227
228        let _ = FbConnection::init(conn).map_err(|e| e.to_string())?;
229
230        Ok(())
231    }
232}