1use 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
20pub struct NativeFbClient<T: LinkageMarker> {
22 ibase: T::L,
23 status: Status,
24 charset: Charset,
25}
26
27#[derive(Clone, Default)]
29pub struct RemoteConfig {
30 pub host: String,
31 pub port: u16,
32 pub pass: String,
33}
34
35pub struct StmtHandleData {
37 handle: NativeStmtHandle,
39 xsqlda: XSqlDa,
41 col_buffers: Vec<ColumnBuffer>,
43}
44
45#[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
54pub trait LinkageMarker: Send + Sync {
57 type L: IBase + Send;
58}
59
60#[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#[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 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 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 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 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 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 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 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 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 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) = ¶ms.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 drop(params);
466
467 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 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 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) = ¶ms.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 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 {
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 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}