rsfbclient/
statement.rs

1//!
2//! Rust Firebird Client
3//!
4//! Preparation and execution of statements
5//!
6
7use crate::{
8    transaction::{Transaction, TransactionData},
9    Connection,
10};
11use rsfbclient_core::{
12    Column, FbError, FirebirdClient, FreeStmtOp, FromRow, IntoParams, NamedParams, StmtType,
13};
14
15pub struct Statement<'c, 't, C: FirebirdClient> {
16    pub(crate) data: StatementData<C>,
17    pub(crate) tr: &'t mut Transaction<'c, C>,
18}
19
20impl<'c, 't, C> Statement<'c, 't, C>
21where
22    C: FirebirdClient,
23{
24    /// Prepare the statement that will be executed
25    pub fn prepare(
26        tr: &'t mut Transaction<'c, C>,
27        sql: &str,
28        named_params: bool,
29    ) -> Result<Self, FbError> {
30        let data = StatementData::prepare(tr.conn, &mut tr.data, sql, named_params)?;
31
32        Ok(Statement { data, tr })
33    }
34
35    /// Execute the current statement, returning a
36    /// count of affected rows upon success
37    ///
38    /// Use `()` for no parameters or a tuple of parameters
39    pub fn execute<T>(&mut self, params: T) -> Result<usize, FbError>
40    where
41        T: IntoParams,
42    {
43        self.data.execute(self.tr.conn, &mut self.tr.data, params)
44    }
45
46    /// Execute the current statement
47    /// and returns the lines founds
48    ///
49    /// Use `()` for no parameters or a tuple of parameters
50    pub fn query<'s, R, P>(&'s mut self, params: P) -> Result<StatementFetch<'c, 's, R, C>, FbError>
51    where
52        R: FromRow,
53        P: IntoParams,
54    {
55        self.data.query(self.tr.conn, &mut self.tr.data, params)?;
56
57        Ok(StatementFetch {
58            stmt: &mut self.data,
59            tr: self.tr,
60            _marker: Default::default(),
61        })
62    }
63}
64
65impl<C> Drop for Statement<'_, '_, C>
66where
67    C: FirebirdClient,
68{
69    fn drop(&mut self) {
70        self.data.close(self.tr.conn).ok();
71    }
72}
73/// Cursor to fetch the results of a statement
74pub struct StatementFetch<'c, 's, R, C: FirebirdClient> {
75    pub(crate) stmt: &'s mut StatementData<C>,
76    /// Transaction needs to be alive for the fetch to work
77    pub(crate) tr: &'s mut Transaction<'c, C>,
78    /// Type to convert the rows
79    _marker: std::marker::PhantomData<R>,
80}
81
82impl<'c, 's, R, C> StatementFetch<'c, 's, R, C>
83where
84    R: FromRow,
85    C: FirebirdClient,
86{
87    /// Fetch for the next row
88    pub fn fetch(&mut self) -> Result<Option<R>, FbError> {
89        self.stmt
90            .fetch(self.tr.conn, &mut self.tr.data)
91            .and_then(|row| row.map(FromRow::try_from).transpose())
92    }
93}
94
95impl<T, C> Iterator for StatementFetch<'_, '_, T, C>
96where
97    T: FromRow,
98    C: FirebirdClient,
99{
100    type Item = Result<T, FbError>;
101
102    fn next(&mut self) -> Option<Self::Item> {
103        self.fetch().transpose()
104    }
105}
106
107impl<R, C> Drop for StatementFetch<'_, '_, R, C>
108where
109    C: FirebirdClient,
110{
111    fn drop(&mut self) {
112        self.stmt.close_cursor(self.tr.conn).ok();
113    }
114}
115
116/// Low level statement handler.
117///
118/// Needs to be closed calling `close` before dropping.
119pub struct StatementData<C: FirebirdClient> {
120    pub(crate) handle: C::StmtHandle,
121    pub(crate) stmt_type: StmtType,
122    named_params: NamedParams,
123}
124
125impl<C: FirebirdClient> StatementData<C>
126where
127    C::StmtHandle: Send,
128{
129    /// Prepare the statement that will be executed
130    pub fn prepare(
131        conn: &mut Connection<C>,
132        tr: &mut TransactionData<C>,
133        raw_sql: &str,
134        named_params: bool,
135    ) -> Result<Self, FbError> {
136        let named_params = if named_params {
137            NamedParams::parse(raw_sql)?
138        } else {
139            NamedParams::empty(raw_sql)
140        };
141        let sql = &named_params.sql;
142
143        let (stmt_type, handle) =
144            conn.cli
145                .prepare_statement(&mut conn.handle, &mut tr.handle, conn.dialect, sql)?;
146
147        Ok(Self {
148            stmt_type,
149            handle,
150            named_params,
151        })
152    }
153
154    /// Execute the current statement without returnig any row
155    ///
156    /// Use `()` for no parameters or a tuple of parameters
157    pub fn execute<T>(
158        &mut self,
159        conn: &mut Connection<C>,
160        tr: &mut TransactionData<C>,
161        params: T,
162    ) -> Result<usize, FbError>
163    where
164        T: IntoParams,
165    {
166        let rows_count = conn.cli.execute(
167            &mut conn.handle,
168            &mut tr.handle,
169            &mut self.handle,
170            self.named_params.convert(params)?,
171        )?;
172
173        if self.stmt_type == StmtType::Select {
174            // Close the cursor, as it will not be used
175            self.close_cursor(conn)?;
176        }
177
178        Ok(rows_count)
179    }
180
181    /// Execute the current statement with input and returns a single row
182    ///
183    /// Use `()` for no parameters or a tuple of parameters
184    pub fn execute2<T>(
185        &mut self,
186        conn: &mut Connection<C>,
187        tr: &mut TransactionData<C>,
188        params: T,
189    ) -> Result<Vec<Column>, FbError>
190    where
191        T: IntoParams,
192    {
193        conn.cli.execute2(
194            &mut conn.handle,
195            &mut tr.handle,
196            &mut self.handle,
197            self.named_params.convert(params)?,
198        )
199    }
200
201    /// Execute the current statement
202    /// and returns the affected rows count
203    ///
204    /// Use `()` for no parameters or a tuple of parameters
205    pub fn query<'s, T>(
206        &'s mut self,
207        conn: &'s mut Connection<C>,
208        tr: &mut TransactionData<C>,
209        params: T,
210    ) -> Result<usize, FbError>
211    where
212        T: IntoParams,
213    {
214        conn.cli.execute(
215            &mut conn.handle,
216            &mut tr.handle,
217            &mut self.handle,
218            self.named_params.convert(params)?,
219        )
220    }
221
222    /// Fetch for the next row, needs to be called after `query`
223    pub fn fetch(
224        &mut self,
225        conn: &mut Connection<C>,
226        tr: &mut TransactionData<C>,
227    ) -> Result<Option<Vec<Column>>, FbError> {
228        conn.cli
229            .fetch(&mut conn.handle, &mut tr.handle, &mut self.handle)
230    }
231
232    /// Closes the statement cursor, if it was open
233    pub fn close_cursor(&mut self, conn: &mut Connection<C>) -> Result<(), FbError> {
234        conn.cli.free_statement(&mut self.handle, FreeStmtOp::Close)
235    }
236
237    /// Closes the statement
238    pub fn close(&mut self, conn: &mut Connection<C>) -> Result<(), FbError> {
239        conn.cli.free_statement(&mut self.handle, FreeStmtOp::Drop)
240    }
241}
242
243#[cfg(test)]
244/// Counter to allow the tests to be run in parallel without interfering in each other
245static TABLE_COUNTER: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
246
247#[cfg(test)]
248mk_tests_default! {
249    use crate::{prelude::*, Connection, Row};
250    use rsfbclient_core::FirebirdClient;
251
252    #[test]
253    fn new_api_select() {
254        let (mut conn, table) = setup();
255
256        let vals = vec![
257            (Some(2), "coffee".to_string()),
258            (Some(3), "milk".to_string()),
259            (None, "fail coffee".to_string()),
260        ];
261
262        conn.with_transaction(|tr| {
263            for val in &vals {
264                tr.execute(&format!("insert into {} (id, name) values (?, ?)", table), val.clone())
265                    .expect("Error on insert");
266            }
267
268            Ok(())
269        })
270        .expect("Error commiting the transaction");
271
272        let rows = conn
273            .query(&format!("select id, name from {}", table), ())
274            .expect("Error executing query");
275
276        // Asserts that all values are equal
277        assert_eq!(vals, rows);
278    }
279
280    #[test]
281    fn old_api_select() {
282        let (mut conn, table) = setup();
283
284        let vals = vec![
285            (Some(2), "coffee".to_string()),
286            (Some(3), "milk".to_string()),
287            (None, "fail coffee".to_string()),
288        ];
289
290        conn.with_transaction(|tr| {
291            let mut stmt = tr
292                .prepare(&format!("insert into {} (id, name) values (?, ?)", table), false)
293                .expect("Error preparing the insert statement");
294
295            for val in &vals {
296                stmt.execute(val.clone()).expect("Error on insert");
297            }
298
299            Ok(())
300        })
301        .expect("Error commiting the transaction");
302
303        conn.with_transaction(|tr| {
304            let mut stmt = tr
305                .prepare(&format!("select id, name from {}", table), false)
306                .expect("Error on prepare the select");
307
308            let rows: Vec<(Option<i32>, String)> = stmt
309                .query(())
310                .expect("Error on query")
311                .collect::<Result<_, _>>()
312                .expect("Error on fetch");
313
314            // Asserts that all values are equal
315            assert_eq!(vals, rows);
316
317            let mut rows = stmt.query(()).expect("Error on query");
318
319            let row1: Row = rows
320                .fetch()
321                .expect("Error on fetch the next row")
322                .expect("No more rows");
323
324            assert_eq!(
325                2,
326                row1.get::<i32>(0)
327                    .expect("Error on get the first column value")
328            );
329            assert_eq!(
330                "coffee".to_string(),
331                row1.get::<String>(1)
332                    .expect("Error on get the second column value")
333            );
334
335            let row = rows
336                .fetch()
337                .expect("Error on fetch the next row")
338                .expect("No more rows");
339
340            assert_eq!(
341                3,
342                row.get::<i32>(0)
343                    .expect("Error on get the first column value")
344            );
345            assert_eq!(
346                "milk".to_string(),
347                row.get::<String>(1)
348                    .expect("Error on get the second column value")
349            );
350
351            let row = rows
352                .fetch()
353                .expect("Error on fetch the next row")
354                .expect("No more rows");
355
356            assert!(
357                row.get::<i32>(0).is_err(),
358                "The 3° row have a null value, then should return an error"
359            ); // null value
360            assert!(
361                row.get::<Option<i32>>(0)
362                    .expect("Error on get the first column value")
363                    .is_none(),
364                "The 3° row have a null value, then should return a None"
365            ); // null value
366            assert_eq!(
367                "fail coffee".to_string(),
368                row.get::<String>(1)
369                    .expect("Error on get the second column value")
370            );
371
372            let row = rows.fetch().expect("Error on fetch the next row");
373
374            assert!(
375                row.is_none(),
376                "The 4° row dont exists, then should return a None"
377            ); // null value
378
379            Ok(())
380        })
381        .expect("Error commiting the transaction");
382
383        conn.close().expect("error on close the connection");
384    }
385
386    #[test]
387    fn prepared_insert() {
388        let (mut conn, table) = setup();
389
390        let vals = vec![(Some(9), "apple"), (Some(12), "jack"), (None, "coffee")];
391
392        conn.with_transaction(|tr| {
393            for val in vals.into_iter() {
394                tr.execute(&format!("insert into {} (id, name) values (?, ?)", table), val)
395                    .expect("Error on insert");
396            }
397
398            Ok(())
399        })
400        .expect("Error in the transaction");
401
402        conn.close().expect("error on close the connection");
403    }
404
405    // #[test]
406    // fn immediate_insert() {
407    //     let (mut conn, table) = setup();
408
409    //     conn.with_transaction(|tr| {
410    //         tr.execute_immediate(&format!("insert into {} (id, name) values (?, ?)", (1, "apple", table)))
411    //             .expect("Error on 1° insert");
412
413    //         tr.execute_immediate(&format!("insert into {} (id, name) values (?, ?)", (2, "coffe", table)))
414    //             .expect("Error on 2° insert");
415
416    //         Ok(())
417    //     })
418    //     .expect("Error in the transaction");
419
420    //     conn.close().expect("error on close the connection");
421    // }
422
423    fn setup() -> (Connection<impl FirebirdClient>, String) {
424        let mut conn = cbuilder().connect()
425            .expect("Error on connect in the test database");
426
427        let table_num = super::TABLE_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
428        let table = format!("product{}", table_num);
429
430        conn.with_transaction(|tr| {
431            tr.execute_immediate(&format!("DROP TABLE {}", table)).ok();
432
433            tr.execute_immediate(&format!("CREATE TABLE {} (id int, name varchar(60), quantity int)", table))
434                .expect("Error on create the table product");
435
436            Ok(())
437        })
438        .expect("Error in the transaction");
439
440        (conn, table)
441    }
442}