1use 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 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 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 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}
73pub struct StatementFetch<'c, 's, R, C: FirebirdClient> {
75 pub(crate) stmt: &'s mut StatementData<C>,
76 pub(crate) tr: &'s mut Transaction<'c, C>,
78 _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 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
116pub 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 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 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 self.close_cursor(conn)?;
176 }
177
178 Ok(rows_count)
179 }
180
181 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 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 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 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 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)]
244static 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 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 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 ); 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 ); 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 ); 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 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}