rsfbclient_native/
connection.rs

1//! `FirebirdConnection` implementation for the native fbclient
2
3use crate::{
4    ibase::{self, IBase},
5    params::Params,
6    row::ColumnBuffer,
7    status::Status,
8    xsqlda::XSqlDa,
9};
10use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
11use rsfbclient_core::*;
12use std::ffi::CString;
13use std::os::raw::c_char;
14use std::{convert::TryFrom, io::Cursor, ptr, str};
15
16type NativeDbHandle = ibase::isc_db_handle;
17type NativeTrHandle = ibase::isc_tr_handle;
18type NativeStmtHandle = ibase::isc_stmt_handle;
19
20/// Client that wraps the native fbclient library
21pub struct NativeFbClient<T: LinkageMarker> {
22    ibase: T::L,
23    status: Status,
24    charset: Charset,
25}
26
27/// The remote part of native client configuration
28#[derive(Clone, Default)]
29pub struct RemoteConfig {
30    pub host: String,
31    pub port: u16,
32    pub pass: String,
33}
34
35/// Data associated with a prepared statement
36pub struct StmtHandleData {
37    /// Statement handle
38    handle: NativeStmtHandle,
39    /// Output xsqlda
40    xsqlda: XSqlDa,
41    /// Buffers for the output xsqlda
42    col_buffers: Vec<ColumnBuffer>,
43}
44
45///The common part of native client configuration (for both embedded/remote)
46#[derive(Clone, Default)]
47pub struct NativeFbAttachmentConfig {
48    pub db_name: String,
49    pub user: String,
50    pub role_name: Option<String>,
51    pub remote: Option<RemoteConfig>,
52}
53
54/// A marker trait which can be used to
55/// obtain the associated client instance
56pub trait LinkageMarker: Send + Sync {
57    type L: IBase + Send;
58}
59
60/// Configuration details for dynamic linking
61#[derive(Clone)]
62pub struct DynLink(pub Charset);
63
64#[cfg(feature = "linking")]
65impl LinkageMarker for DynLink {
66    type L = ibase::IBaseLinking;
67}
68
69#[cfg(feature = "linking")]
70impl DynLink {
71    pub fn to_client(&self) -> NativeFbClient<DynLink> {
72        let result: NativeFbClient<DynLink> = NativeFbClient {
73            ibase: ibase::IBaseLinking,
74            status: Default::default(),
75            charset: self.0.clone(),
76        };
77        result
78    }
79}
80
81/// Configuration details for dynamic loading
82#[derive(Clone)]
83pub struct DynLoad {
84    pub charset: Charset,
85    pub lib_path: String,
86}
87
88#[cfg(feature = "dynamic_loading")]
89impl LinkageMarker for DynLoad {
90    type L = ibase::IBaseDynLoading;
91}
92
93#[cfg(feature = "dynamic_loading")]
94impl DynLoad {
95    pub fn try_to_client(&self) -> Result<NativeFbClient<Self>, FbError> {
96        let load_result = ibase::IBaseDynLoading::with_client(self.lib_path.as_ref())
97            .map_err(|e| FbError::from(e.to_string()))?;
98
99        let result: NativeFbClient<DynLoad> = NativeFbClient {
100            ibase: load_result,
101            status: Default::default(),
102            charset: self.charset.clone(),
103        };
104
105        Ok(result)
106    }
107}
108
109impl<T: LinkageMarker> FirebirdClientDbOps for NativeFbClient<T> {
110    type DbHandle = NativeDbHandle;
111    type AttachmentConfig = NativeFbAttachmentConfig;
112
113    fn attach_database(
114        &mut self,
115        config: &Self::AttachmentConfig,
116        dialect: Dialect,
117        no_db_triggers: bool,
118    ) -> Result<NativeDbHandle, FbError> {
119        let (mut dpb, conn_string) = self.build_dpb(config, dialect);
120        let mut handle = 0;
121
122        if no_db_triggers {
123            dpb.extend(&[ibase::isc_dpb_no_db_triggers as u8, 1 as u8]);
124            dpb.extend(&[1 as u8]);
125        }
126
127        unsafe {
128            if self.ibase.isc_attach_database()(
129                &mut self.status[0],
130                conn_string.len() as i16,
131                conn_string.as_ptr() as *const _,
132                &mut handle,
133                dpb.len() as i16,
134                dpb.as_ptr() as *const _,
135            ) != 0
136            {
137                return Err(self.status.as_error(&self.ibase));
138            }
139        }
140
141        // Assert that the handle is valid
142        debug_assert_ne!(handle, 0);
143
144        Ok(handle)
145    }
146
147    fn detach_database(&mut self, db_handle: &mut NativeDbHandle) -> Result<(), FbError> {
148        unsafe {
149            // Close the connection, if the handle is valid
150            if *db_handle != 0
151                && self.ibase.isc_detach_database()(&mut self.status[0], db_handle) != 0
152            {
153                return Err(self.status.as_error(&self.ibase));
154            }
155        }
156        Ok(())
157    }
158
159    fn drop_database(&mut self, db_handle: &mut NativeDbHandle) -> Result<(), FbError> {
160        unsafe {
161            if self.ibase.isc_drop_database()(&mut self.status[0], db_handle) != 0 {
162                return Err(self.status.as_error(&self.ibase));
163            }
164        }
165        Ok(())
166    }
167
168    fn create_database(
169        &mut self,
170        config: &Self::AttachmentConfig,
171        page_size: Option<u32>,
172        dialect: Dialect,
173    ) -> Result<NativeDbHandle, FbError> {
174        let (mut dpb, conn_string) = self.build_dpb(config, dialect);
175        let mut handle = 0;
176
177        if let Some(ps) = page_size {
178            dpb.extend(&[ibase::isc_dpb_page_size as u8, 4]);
179            dpb.write_u32::<LittleEndian>(ps)?;
180        }
181
182        unsafe {
183            if self.ibase.isc_create_database()(
184                &mut self.status[0],
185                conn_string.len() as i16,
186                conn_string.as_ptr() as *const _,
187                &mut handle,
188                dpb.len() as i16,
189                dpb.as_ptr() as *const _,
190                0,
191            ) != 0
192            {
193                return Err(self.status.as_error(&self.ibase));
194            }
195        }
196
197        // Assert that the handle is valid
198        debug_assert_ne!(handle, 0);
199
200        Ok(handle)
201    }
202}
203
204impl<T: LinkageMarker> FirebirdClientSqlOps for NativeFbClient<T> {
205    type DbHandle = NativeDbHandle;
206    type TrHandle = NativeTrHandle;
207    type StmtHandle = StmtHandleData;
208
209    fn begin_transaction(
210        &mut self,
211        db_handle: &mut Self::DbHandle,
212        confs: TransactionConfiguration,
213    ) -> Result<Self::TrHandle, FbError> {
214        let mut handle = 0;
215
216        // Transaction parameter buffer
217        let mut tpb = vec![
218            ibase::isc_tpb_version3 as u8,
219            confs.isolation.into(),
220            confs.data_access as u8,
221            confs.lock_resolution.into(),
222        ];
223        if let TrLockResolution::Wait(Some(time)) = confs.lock_resolution {
224            tpb.push(ibase::isc_tpb_lock_timeout as u8);
225            tpb.push(4 as u8);
226            tpb.extend_from_slice(&time.to_le_bytes());
227        }
228
229        if let TrIsolationLevel::ReadCommited(rec) = confs.isolation {
230            tpb.push(rec as u8);
231        }
232
233        #[repr(C)]
234        struct IscTeb {
235            db_handle: *mut ibase::isc_db_handle,
236            tpb_len: usize,
237            tpb_ptr: *const u8,
238        }
239
240        unsafe {
241            if self.ibase.isc_start_multiple()(
242                &mut self.status[0],
243                &mut handle,
244                1,
245                &mut IscTeb {
246                    db_handle,
247                    tpb_len: tpb.len(),
248                    tpb_ptr: &tpb[0],
249                } as *mut _ as _,
250            ) != 0
251            {
252                return Err(self.status.as_error(&self.ibase));
253            }
254        }
255
256        // Assert that the handle is valid
257        debug_assert_ne!(handle, 0);
258
259        Ok(handle)
260    }
261
262    fn transaction_operation(
263        &mut self,
264        tr_handle: &mut Self::TrHandle,
265        op: TrOp,
266    ) -> Result<(), FbError> {
267        let handle = tr_handle;
268        unsafe {
269            if match op {
270                TrOp::Commit => self.ibase.isc_commit_transaction()(&mut self.status[0], handle),
271                TrOp::CommitRetaining => {
272                    self.ibase.isc_commit_retaining()(&mut self.status[0], handle)
273                }
274                TrOp::Rollback => {
275                    self.ibase.isc_rollback_transaction()(&mut self.status[0], handle)
276                }
277                TrOp::RollbackRetaining => {
278                    self.ibase.isc_rollback_retaining()(&mut self.status[0], handle)
279                }
280            } != 0
281            {
282                return Err(self.status.as_error(&self.ibase));
283            }
284        }
285        Ok(())
286    }
287
288    fn exec_immediate(
289        &mut self,
290        db_handle: &mut Self::DbHandle,
291        tr_handle: &mut Self::TrHandle,
292        dialect: Dialect,
293        sql: &str,
294    ) -> Result<(), FbError> {
295        let sql = self.charset.encode(sql)?;
296
297        unsafe {
298            if self.ibase.isc_dsql_execute_immediate()(
299                &mut self.status[0],
300                db_handle,
301                tr_handle,
302                sql.len() as u16,
303                sql.as_ptr() as *const _,
304                dialect as u16,
305                ptr::null(),
306            ) != 0
307            {
308                return Err(self.status.as_error(&self.ibase));
309            }
310        }
311        Ok(())
312    }
313
314    fn prepare_statement(
315        &mut self,
316        db_handle: &mut Self::DbHandle,
317        tr_handle: &mut Self::TrHandle,
318        dialect: Dialect,
319        sql: &str,
320    ) -> Result<(StmtType, Self::StmtHandle), FbError> {
321        let sql = self.charset.encode(sql)?;
322
323        let mut handle = 0;
324
325        let mut xsqlda = XSqlDa::new(1);
326
327        let mut stmt_type = 0;
328
329        unsafe {
330            if self.ibase.isc_dsql_allocate_statement()(&mut self.status[0], db_handle, &mut handle)
331                != 0
332            {
333                return Err(self.status.as_error(&self.ibase));
334            }
335
336            if self.ibase.isc_dsql_prepare()(
337                &mut self.status[0],
338                tr_handle,
339                &mut handle,
340                sql.len() as u16,
341                sql.as_ptr() as *const _,
342                dialect as u16,
343                &mut *xsqlda,
344            ) != 0
345            {
346                return Err(self.status.as_error(&self.ibase));
347            }
348
349            let row_count = xsqlda.sqld;
350
351            if row_count > xsqlda.sqln {
352                // Need more XSQLVARs
353                xsqlda = XSqlDa::new(row_count);
354
355                if self.ibase.isc_dsql_describe()(&mut self.status[0], &mut handle, 1, &mut *xsqlda)
356                    != 0
357                {
358                    return Err(self.status.as_error(&self.ibase));
359                }
360            }
361
362            // Get the statement type
363            let info_req = [ibase::isc_info_sql_stmt_type as std::os::raw::c_char];
364            let mut info_buf = [0; 10];
365
366            if self.ibase.isc_dsql_sql_info()(
367                &mut self.status[0],
368                &mut handle,
369                info_req.len() as i16,
370                &info_req[0],
371                info_buf.len() as i16,
372                &mut info_buf[0],
373            ) != 0
374            {
375                return Err(self.status.as_error(&self.ibase));
376            }
377
378            for &v in &info_buf[3..] {
379                // Search for the data
380                if v != 0 {
381                    stmt_type = v;
382                    break;
383                }
384            }
385        }
386
387        let stmt_type = StmtType::try_from(stmt_type as u8)
388            .map_err(|_| FbError::from(format!("Invalid statement type: {}", stmt_type)))?;
389
390        // Create the column buffers and set the xsqlda conercions
391        let col_buffers = (0..xsqlda.sqld)
392            .map(|col| {
393                let xcol = xsqlda
394                    .get_xsqlvar_mut(col as usize)
395                    .ok_or_else(|| FbError::from("Error getting the xsqlvar"))?;
396
397                ColumnBuffer::from_xsqlvar(xcol)
398            })
399            .collect::<Result<_, _>>()?;
400
401        Ok((
402            stmt_type,
403            StmtHandleData {
404                handle,
405                xsqlda,
406                col_buffers,
407            },
408        ))
409    }
410
411    fn free_statement(
412        &mut self,
413        stmt_handle: &mut Self::StmtHandle,
414        op: FreeStmtOp,
415    ) -> Result<(), FbError> {
416        unsafe {
417            if self.ibase.isc_dsql_free_statement()(
418                &mut self.status[0],
419                &mut stmt_handle.handle,
420                op as u16,
421            ) != 0
422            {
423                return Err(self.status.as_error(&self.ibase));
424            }
425        }
426
427        Ok(())
428    }
429
430    fn execute(
431        &mut self,
432        db_handle: &mut Self::DbHandle,
433        tr_handle: &mut Self::TrHandle,
434        stmt_handle: &mut Self::StmtHandle,
435        params: Vec<SqlType>,
436    ) -> Result<usize, FbError> {
437        let params = Params::new(
438            db_handle,
439            tr_handle,
440            &self.ibase,
441            &mut self.status,
442            &mut stmt_handle.handle,
443            params,
444            &self.charset,
445        )?;
446
447        unsafe {
448            if self.ibase.isc_dsql_execute()(
449                &mut self.status[0],
450                tr_handle,
451                &mut stmt_handle.handle,
452                1,
453                if let Some(xsqlda) = &params.xsqlda {
454                    &**xsqlda
455                } else {
456                    ptr::null()
457                },
458            ) != 0
459            {
460                return Err(self.status.as_error(&self.ibase));
461            }
462        }
463
464        // Just to make sure the params are not dropped too soon
465        drop(params);
466
467        // Get the affected rows count
468        let info_req = [ibase::isc_info_sql_records as std::os::raw::c_char];
469        let mut info_buf = [0u8; 64];
470
471        unsafe {
472            if self.ibase.isc_dsql_sql_info()(
473                &mut self.status[0],
474                &mut stmt_handle.handle,
475                info_req.len() as i16,
476                &info_req[0],
477                info_buf.len() as i16,
478                info_buf.as_mut_ptr() as _,
479            ) != 0
480            {
481                return Err(self.status.as_error(&self.ibase));
482            }
483        }
484
485        let mut affected = 0;
486
487        let mut data = Cursor::new(info_buf);
488
489        if data.read_u8()? == ibase::isc_info_sql_records as u8 {
490            let _info_buf_size = data.read_u16::<LittleEndian>()?;
491
492            loop {
493                match data.read_u8()? as u32 {
494                    ibase::isc_info_req_select_count => {
495                        // Not interested in the selected count
496                        let len = data.read_u16::<LittleEndian>()? as usize;
497                        let _selected = data.read_uint::<LittleEndian>(len)?;
498                    }
499
500                    ibase::isc_info_req_insert_count
501                    | ibase::isc_info_req_update_count
502                    | ibase::isc_info_req_delete_count => {
503                        let len = data.read_u16::<LittleEndian>()? as usize;
504
505                        affected += data.read_uint::<LittleEndian>(len)? as usize;
506                    }
507
508                    ibase::isc_info_end => {
509                        break;
510                    }
511
512                    _ => return Err(FbError::from("Invalid affected rows response")),
513                }
514            }
515        }
516
517        Ok(affected as usize)
518    }
519
520    fn fetch(
521        &mut self,
522        db_handle: &mut Self::DbHandle,
523        tr_handle: &mut Self::TrHandle,
524        stmt_handle: &mut Self::StmtHandle,
525    ) -> Result<Option<Vec<Column>>, FbError> {
526        unsafe {
527            let fetch_status = self.ibase.isc_dsql_fetch()(
528                &mut self.status[0],
529                &mut stmt_handle.handle,
530                1,
531                &*stmt_handle.xsqlda,
532            );
533
534            // 100 indicates that no more rows: http://docwiki.embarcadero.com/InterBase/2020/en/Isc_dsql_fetch()
535            if fetch_status == 100 {
536                return Ok(None);
537            }
538
539            if fetch_status != 0 {
540                return Err(self.status.as_error(&self.ibase));
541            };
542        }
543
544        let cols = stmt_handle
545            .col_buffers
546            .iter()
547            .map(|cb| cb.to_column(db_handle, tr_handle, &self.ibase, &self.charset))
548            .collect::<Result<_, _>>()?;
549
550        Ok(Some(cols))
551    }
552
553    fn execute2(
554        &mut self,
555        db_handle: &mut Self::DbHandle,
556        tr_handle: &mut Self::TrHandle,
557        stmt_handle: &mut Self::StmtHandle,
558        params: Vec<SqlType>,
559    ) -> Result<Vec<Column>, FbError> {
560        let params = Params::new(
561            db_handle,
562            tr_handle,
563            &self.ibase,
564            &mut self.status,
565            &mut stmt_handle.handle,
566            params,
567            &self.charset,
568        )?;
569
570        unsafe {
571            if self.ibase.isc_dsql_execute2()(
572                &mut self.status[0],
573                tr_handle,
574                &mut stmt_handle.handle,
575                1,
576                if let Some(xsqlda) = &params.xsqlda {
577                    &**xsqlda
578                } else {
579                    ptr::null()
580                },
581                &*stmt_handle.xsqlda,
582            ) != 0
583            {
584                return Err(self.status.as_error(&self.ibase));
585            }
586        }
587
588        // Just to make sure the params are not dropped too soon
589        drop(params);
590
591        let rcol = stmt_handle
592            .col_buffers
593            .iter()
594            .map(|cb| cb.to_column(db_handle, tr_handle, &self.ibase, &self.charset))
595            .collect::<Result<_, _>>()?;
596
597        Ok(rcol)
598    }
599}
600
601impl<T: LinkageMarker> FirebirdClientDbEvents for NativeFbClient<T> {
602    fn wait_for_event(
603        &mut self,
604        db_handle: &mut Self::DbHandle,
605        name: String,
606    ) -> Result<(), FbError> {
607        let mut event_buffer = ptr::null_mut();
608        let mut result_buffer = ptr::null_mut();
609
610        let name = CString::new(name.clone()).unwrap();
611        let len = unsafe {
612            self.ibase.isc_event_block()(
613                &mut event_buffer,
614                &mut result_buffer,
615                1,
616                name.as_ptr() as *mut c_char,
617            )
618        };
619        debug_assert!(!event_buffer.is_null() && !result_buffer.is_null());
620
621        // Preparing the event_buffer. Yes, I need call the isc_wait_for_event
622        // before call isc_wait_for_event.
623        //
624        // PHP example: https://github.com/FirebirdSQL/php-firebird/blob/b6c288326678f7b8613f5a7d0648ad010c67674c/ibase_events.c#L126
625        {
626            unsafe {
627                if self.ibase.isc_wait_for_event()(
628                    &mut self.status[0],
629                    db_handle,
630                    len as i16,
631                    event_buffer,
632                    result_buffer,
633                ) != 0
634                {
635                    self.ibase.isc_free()(event_buffer);
636                    self.ibase.isc_free()(result_buffer);
637
638                    return Err(self.status.as_error(&self.ibase));
639                }
640            }
641
642            unsafe {
643                self.ibase.isc_event_counts()(
644                    &mut self.status[0],
645                    len as i16,
646                    event_buffer,
647                    result_buffer,
648                );
649            }
650        }
651
652        unsafe {
653            if self.ibase.isc_wait_for_event()(
654                &mut self.status[0],
655                db_handle,
656                len as i16,
657                event_buffer,
658                result_buffer,
659            ) != 0
660            {
661                self.ibase.isc_free()(event_buffer);
662                self.ibase.isc_free()(result_buffer);
663
664                return Err(self.status.as_error(&self.ibase));
665            }
666
667            self.ibase.isc_free()(event_buffer);
668            self.ibase.isc_free()(result_buffer);
669        }
670
671        Ok(())
672    }
673}
674
675impl<T: LinkageMarker> NativeFbClient<T> {
676    /// Build the dpb and the connection string
677    ///
678    /// Used by attach database operations
679    fn build_dpb(
680        &mut self,
681        config: &NativeFbAttachmentConfig,
682        dialect: Dialect,
683    ) -> (Vec<u8>, String) {
684        let user = &config.user;
685        let mut password = None;
686        let db_name = &config.db_name;
687
688        let conn_string = match &config.remote {
689            None => db_name.clone(),
690            Some(remote_conf) => {
691                password = Some(remote_conf.pass.as_str());
692                format!(
693                    "{}/{}:{}",
694                    remote_conf.host.as_str(),
695                    remote_conf.port,
696                    db_name.as_str()
697                )
698            }
699        };
700
701        let dpb = {
702            let mut dpb: Vec<u8> = Vec::with_capacity(64);
703
704            dpb.extend(&[ibase::isc_dpb_version1 as u8]);
705
706            dpb.extend(&[ibase::isc_dpb_user_name as u8, user.len() as u8]);
707            dpb.extend(user.bytes());
708
709            if let Some(pass_str) = password {
710                dpb.extend(&[ibase::isc_dpb_password as u8, pass_str.len() as u8]);
711                dpb.extend(pass_str.bytes());
712            };
713
714            let charset = self.charset.on_firebird.bytes();
715
716            dpb.extend(&[ibase::isc_dpb_lc_ctype as u8, charset.len() as u8]);
717            dpb.extend(charset);
718
719            if let Some(role) = &config.role_name {
720                dpb.extend(&[ibase::isc_dpb_sql_role_name as u8, role.len() as u8]);
721                dpb.extend(role.bytes());
722            }
723
724            dpb.extend(&[ibase::isc_dpb_sql_dialect as u8, 1 as u8]);
725            dpb.extend(&[dialect as u8]);
726
727            dpb
728        };
729
730        (dpb, conn_string)
731    }
732}