#[cfg(feature = "pool")]
pub mod pool;
pub mod stmt_cache;
use rsfbclient_core::{
charset::Charset, charset::UTF_8, Dialect, FbError, FirebirdClient,
FirebirdClientEmbeddedAttach, FirebirdClientRemoteAttach, FromRow, IntoParams,
};
use std::{cell::RefCell, marker};
use crate::{query::Queryable, statement::StatementData, Execute, Transaction};
use stmt_cache::{StmtCache, StmtCacheData};
pub struct ConnectionBuilder<C: FirebirdClient> {
host: String,
port: u16,
pass: String,
db_name: String,
user: String,
dialect: Dialect,
stmt_cache_size: usize,
cli_args: C::Args,
_cli_type: marker::PhantomData<C>,
charset: Charset,
}
pub struct ConnectionBuilderEmbedded<C: FirebirdClient> {
db_name: String,
user: String,
dialect: Dialect,
stmt_cache_size: usize,
cli_args: C::Args,
_cli_type: marker::PhantomData<C>,
charset: Charset,
}
unsafe impl<C> Sync for ConnectionBuilder<C> where C: FirebirdClient {}
unsafe impl<C> Sync for ConnectionBuilderEmbedded<C> where C: FirebirdClient {}
impl<C> Clone for ConnectionBuilder<C>
where
C: FirebirdClient,
{
fn clone(&self) -> Self {
Self {
db_name: self.db_name.clone(),
pass: self.pass.clone(),
port: self.port,
host: self.host.clone(),
user: self.user.clone(),
dialect: self.dialect,
stmt_cache_size: self.stmt_cache_size,
cli_args: self.cli_args.clone(),
_cli_type: Default::default(),
charset: self.charset.clone(),
}
}
}
impl<C> Clone for ConnectionBuilderEmbedded<C>
where
C: FirebirdClient,
{
fn clone(&self) -> Self {
Self {
db_name: self.db_name.clone(),
user: self.user.clone(),
dialect: self.dialect,
stmt_cache_size: self.stmt_cache_size,
cli_args: self.cli_args.clone(),
_cli_type: Default::default(),
charset: self.charset.clone(),
}
}
}
#[cfg(any(feature = "linking", feature = "dynamic_loading"))]
impl ConnectionBuilder<rsfbclient_native::NativeFbClient> {
#[cfg(feature = "linking")]
pub fn linked() -> Self {
Self {
host: "localhost".to_string(),
port: 3050,
db_name: "test.fdb".to_string(),
user: "SYSDBA".to_string(),
pass: "masterkey".to_string(),
dialect: Dialect::D3,
stmt_cache_size: 20,
cli_args: rsfbclient_native::Args::Linking,
_cli_type: Default::default(),
charset: UTF_8,
}
}
#[cfg(feature = "dynamic_loading")]
pub fn with_client<S: Into<String>>(fbclient: S) -> Self {
Self {
host: "localhost".to_string(),
port: 3050,
db_name: "test.fdb".to_string(),
user: "SYSDBA".to_string(),
pass: "masterkey".to_string(),
dialect: Dialect::D3,
stmt_cache_size: 20,
cli_args: rsfbclient_native::Args::DynamicLoading {
lib_path: fbclient.into(),
},
_cli_type: Default::default(),
charset: UTF_8,
}
}
pub fn embedded(self) -> ConnectionBuilderEmbedded<rsfbclient_native::NativeFbClient> {
ConnectionBuilderEmbedded {
db_name: self.db_name,
user: self.user,
dialect: self.dialect,
stmt_cache_size: self.stmt_cache_size,
cli_args: self.cli_args,
_cli_type: Default::default(),
charset: UTF_8,
}
}
}
#[cfg(feature = "pure_rust")]
impl ConnectionBuilder<rsfbclient_rust::RustFbClient> {
pub fn pure_rust() -> Self {
Self {
host: "localhost".to_string(),
port: 3050,
db_name: "test.fdb".to_string(),
user: "SYSDBA".to_string(),
pass: "masterkey".to_string(),
dialect: Dialect::D3,
stmt_cache_size: 20,
cli_args: (),
_cli_type: Default::default(),
charset: UTF_8,
}
}
}
impl<C> ConnectionBuilder<C>
where
C: FirebirdClient + FirebirdClientRemoteAttach,
{
pub fn host<S: Into<String>>(&mut self, host: S) -> &mut Self {
self.host = host.into();
self
}
pub fn port(&mut self, port: u16) -> &mut Self {
self.port = port;
self
}
pub fn db_name<S: Into<String>>(&mut self, db_name: S) -> &mut Self {
self.db_name = db_name.into();
self
}
pub fn user<S: Into<String>>(&mut self, user: S) -> &mut Self {
self.user = user.into();
self
}
pub fn pass<S: Into<String>>(&mut self, pass: S) -> &mut Self {
self.pass = pass.into();
self
}
pub fn dialect(&mut self, dialect: Dialect) -> &mut Self {
self.dialect = dialect;
self
}
pub fn stmt_cache_size(&mut self, stmt_cache_size: usize) -> &mut Self {
self.stmt_cache_size = stmt_cache_size;
self
}
pub fn charset(&mut self, charset: Charset) -> &mut Self {
self.charset = charset;
self
}
pub fn connect(&self) -> Result<Connection<C>, FbError> {
Connection::open_remote(self, C::new(self.charset.clone(), self.cli_args.clone())?)
}
}
impl<C> ConnectionBuilderEmbedded<C>
where
C: FirebirdClient + FirebirdClientEmbeddedAttach,
{
pub fn db_name<S: Into<String>>(&mut self, db_name: S) -> &mut Self {
self.db_name = db_name.into();
self
}
pub fn user<S: Into<String>>(&mut self, user: S) -> &mut Self {
self.user = user.into();
self
}
pub fn dialect(&mut self, dialect: Dialect) -> &mut Self {
self.dialect = dialect;
self
}
pub fn stmt_cache_size(&mut self, stmt_cache_size: usize) -> &mut Self {
self.stmt_cache_size = stmt_cache_size;
self
}
pub fn charset(&mut self, charset: Charset) -> &mut Self {
self.charset = charset;
self
}
pub fn connect(&self) -> Result<Connection<C>, FbError> {
Connection::open_embedded(self, C::new(self.charset.clone(), self.cli_args.clone())?)
}
}
pub struct Connection<C>
where
C: FirebirdClient,
{
pub(crate) handle: C::DbHandle,
pub(crate) dialect: Dialect,
pub(crate) stmt_cache: RefCell<StmtCache<StatementData<C::StmtHandle>>>,
pub(crate) cli: RefCell<C>,
}
impl<C> Connection<C>
where
C: FirebirdClient + FirebirdClientRemoteAttach,
{
fn open_remote(builder: &ConnectionBuilder<C>, mut cli: C) -> Result<Connection<C>, FbError> {
let handle = cli.attach_database(
&builder.host,
builder.port,
&builder.db_name,
&builder.user,
&builder.pass,
)?;
let stmt_cache = RefCell::new(StmtCache::new(builder.stmt_cache_size));
Ok(Connection {
handle,
dialect: builder.dialect,
stmt_cache,
cli: RefCell::new(cli),
})
}
}
impl<C> Connection<C>
where
C: FirebirdClient + FirebirdClientEmbeddedAttach,
{
fn open_embedded(
builder: &ConnectionBuilderEmbedded<C>,
mut cli: C,
) -> Result<Connection<C>, FbError> {
let handle = cli.attach_database(&builder.db_name, &builder.user)?;
let stmt_cache = RefCell::new(StmtCache::new(builder.stmt_cache_size));
Ok(Connection {
handle,
dialect: builder.dialect,
stmt_cache,
cli: RefCell::new(cli),
})
}
}
impl<C> Connection<C>
where
C: FirebirdClient,
{
pub fn drop_database(mut self) -> Result<(), FbError> {
self.cli.get_mut().drop_database(self.handle)?;
Ok(())
}
pub fn with_transaction<T>(
&self,
closure: impl FnOnce(&mut Transaction<C>) -> Result<T, FbError>,
) -> Result<T, FbError> {
let mut tr = Transaction::new(self)?;
let res = closure(&mut tr);
if res.is_ok() {
tr.commit_retaining()?;
} else {
tr.rollback_retaining()?;
};
res
}
pub fn close(mut self) -> Result<(), FbError> {
self.__close()
}
fn __close(&mut self) -> Result<(), FbError> {
self.stmt_cache.borrow_mut().close_all(self);
self.cli.get_mut().detach_database(self.handle)?;
Ok(())
}
}
impl<C> Drop for Connection<C>
where
C: FirebirdClient,
{
fn drop(&mut self) {
self.__close().ok();
}
}
pub struct StmtIter<'a, R, C: FirebirdClient> {
stmt_cache_data: Option<StmtCacheData<StatementData<C::StmtHandle>>>,
tr: Transaction<'a, C>,
_marker: marker::PhantomData<R>,
}
impl<R, C> Drop for StmtIter<'_, R, C>
where
C: FirebirdClient,
{
fn drop(&mut self) {
self.stmt_cache_data
.as_mut()
.unwrap()
.stmt
.close_cursor(self.tr.conn)
.ok();
self.tr
.conn
.stmt_cache
.borrow_mut()
.insert_and_close(self.tr.conn, self.stmt_cache_data.take().unwrap())
.ok();
self.tr.commit_retaining().ok();
}
}
impl<R, C> Iterator for StmtIter<'_, R, C>
where
R: FromRow,
C: FirebirdClient,
{
type Item = Result<R, FbError>;
fn next(&mut self) -> Option<Self::Item> {
self.stmt_cache_data
.as_mut()
.unwrap()
.stmt
.fetch(&self.tr.conn, &self.tr.data)
.and_then(|row| row.map(FromRow::try_from).transpose())
.transpose()
}
}
impl<C> Queryable for Connection<C>
where
C: FirebirdClient,
{
fn query_iter<'a, P, R>(
&'a mut self,
sql: &str,
params: P,
) -> Result<Box<dyn Iterator<Item = Result<R, FbError>> + 'a>, FbError>
where
P: IntoParams,
R: FromRow + 'static,
{
let mut tr = Transaction::new(self)?;
let mut stmt_cache_data =
self.stmt_cache
.borrow_mut()
.get_or_prepare(self, &mut tr.data, sql)?;
match stmt_cache_data.stmt.query(self, &mut tr.data, params) {
Ok(_) => {
let iter = StmtIter {
stmt_cache_data: Some(stmt_cache_data),
tr,
_marker: Default::default(),
};
Ok(Box::new(iter))
}
Err(e) => {
self.stmt_cache
.borrow_mut()
.insert_and_close(self, stmt_cache_data)?;
Err(e)
}
}
}
}
impl<C> Execute for Connection<C>
where
C: FirebirdClient,
{
fn execute<P>(&mut self, sql: &str, params: P) -> Result<(), FbError>
where
P: IntoParams,
{
let mut tr = Transaction::new(self)?;
let mut stmt_cache_data =
self.stmt_cache
.borrow_mut()
.get_or_prepare(self, &mut tr.data, sql)?;
let res = stmt_cache_data.stmt.execute(self, &mut tr.data, params);
self.stmt_cache
.borrow_mut()
.insert_and_close(self, stmt_cache_data)?;
res?;
tr.commit()?;
Ok(())
}
}
#[cfg(test)]
mk_tests_default! {
use crate::*;
#[test]
fn remote_connection() -> Result<(), FbError> {
let conn = cbuilder().connect()?;
conn.close().expect("error closing the connection");
Ok(())
}
#[test]
fn query_iter() -> Result<(), FbError> {
let mut conn = cbuilder().connect()?;
let mut rows = 0;
for row in conn
.query_iter("SELECT -3 FROM RDB$DATABASE WHERE 1 = ?", (1,))?
{
let (v,): (i32,) = row?;
assert_eq!(v, -3);
rows += 1;
}
assert_eq!(rows, 1);
Ok(())
}
}