1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432
use super::*;
use crate::connection::{conn_string, TransactionConfiguration};
use std::marker::PhantomData;
#[doc(hidden)]
pub use rsfbclient_native::{DynLink, DynLoad};
use crate::transaction::{transaction_builder, TransactionConfigurationBuilder};
use rsfbclient_native::{LinkageMarker, NativeFbAttachmentConfig, NativeFbClient, RemoteConfig};
//used as markers
#[doc(hidden)]
#[derive(Clone)]
pub struct LinkageNotConfigured;
#[doc(hidden)]
#[derive(Clone)]
pub struct ConnTypeNotConfigured;
#[doc(hidden)]
#[derive(Clone)]
pub struct ConnEmbedded;
#[doc(hidden)]
#[derive(Clone)]
pub struct ConnRemote;
#[doc(hidden)]
#[derive(Clone)]
pub struct ConnByString;
#[doc(hidden)]
#[cfg(feature = "linking")]
pub type DynByString = DynLink;
#[cfg(not(feature = "linking"))]
pub type DynByString = DynLoad;
//These traits are used to avoid duplicating some impl blocks
//while at the same time statically disallowing certain methods for
//NativeConnectionBuilder<A,B> where one of A or B is
//XYZNotConfigured
#[doc(hidden)]
pub trait ConfiguredConnType: Send + Sync {}
#[doc(hidden)]
impl ConfiguredConnType for ConnEmbedded {}
#[doc(hidden)]
impl ConfiguredConnType for ConnRemote {}
#[doc(hidden)]
impl ConfiguredConnType for ConnByString {}
#[doc(hidden)]
//note that there is also LinkageMarker implemented for DynLink and
//DynLoad in rsfbclient-native
pub trait ConfiguredLinkage {}
#[doc(hidden)]
impl ConfiguredLinkage for DynLink {}
#[doc(hidden)]
impl ConfiguredLinkage for DynLoad {}
/// A builder for a client using the official ('native') Firebird dll.
///
/// Use the `builder_native()` method to get a new builder instance, and the
/// provided configuration methods to change the default configuration params.
///
/// Note that one of `with_remote()`/`with_embedded()` and one of
/// `with_dyn_link()`/`with_dyn_load(...)` **must** be called in order to
/// enable creating a connection or calling other configuration methods.
#[derive(Clone)]
pub struct NativeConnectionBuilder<LinkageType, ConnectionType> {
_marker_linkage: PhantomData<LinkageType>,
_marker_conntype: PhantomData<ConnectionType>,
conn_conf: ConnectionConfiguration<NativeFbAttachmentConfig>,
charset: Charset,
lib_path: Option<String>,
page_size: Option<u32>,
}
impl<A, B> From<&NativeConnectionBuilder<A, B>>
for ConnectionConfiguration<NativeFbAttachmentConfig>
{
fn from(arg: &NativeConnectionBuilder<A, B>) -> Self {
arg.conn_conf.clone()
}
}
// dev notes: impl combinations for NativeConnectionBuilder:
// <LinkageNotConfigured, ConnTypeNotConfigured>: What you get when you first call builder_native()
// <A,ConnTypeNotConfigured>: user still has to choose a connection type
// (LinkageNotConfigured,A): user still has to choose a linkage type
// <Configured,Configured> user configured linkage and connectiontype,
// so they can continue to do other configuration or call connect()
/// Get a new instance of NativeConnectionBuilder
pub fn builder_native() -> NativeConnectionBuilder<LinkageNotConfigured, ConnTypeNotConfigured> {
Default::default()
}
#[cfg(feature = "dynamic_loading")]
impl<A> FirebirdClientFactory for NativeConnectionBuilder<DynLoad, A>
where
A: ConfiguredConnType,
{
//would be better if we could use 'impl FirebirdClient' here
type C = NativeFbClient<rsfbclient_native::DynLoad>;
fn new_instance(&self) -> Result<Self::C, FbError> {
let path = self
.lib_path
.as_ref()
.ok_or_else(|| FbError::from("The lib path is required to use the dynload loading"))?;
rsfbclient_native::DynLoad {
charset: self.charset.clone(),
lib_path: path.clone(),
}
.try_to_client()
}
fn get_conn_conf(&self) -> &ConnectionConfiguration<NativeFbAttachmentConfig> {
&self.conn_conf
}
}
#[cfg(feature = "linking")]
impl<A> FirebirdClientFactory for NativeConnectionBuilder<DynLink, A>
where
A: ConfiguredConnType,
{
//would be better if we could use 'impl FirebirdClient' here
type C = NativeFbClient<rsfbclient_native::DynLink>;
fn new_instance(&self) -> Result<Self::C, FbError> {
Ok(rsfbclient_native::DynLink(self.charset.clone()).to_client())
}
fn get_conn_conf(&self) -> &ConnectionConfiguration<NativeFbAttachmentConfig> {
&self.conn_conf
}
}
impl<A, B> NativeConnectionBuilder<A, B>
where
A: ConfiguredLinkage,
B: ConfiguredConnType,
//Needed to satisfy the bounds in rsfbclient_native
A: LinkageMarker,
Self: FirebirdClientFactory<C = NativeFbClient<A>>,
{
/// Start a new connection from the fully-built builder
pub fn connect(&self) -> Result<Connection<NativeFbClient<A>>, FbError> {
Connection::open(self.new_instance()?, &self.conn_conf)
}
/// Create the database and start new connection from the fully-built builder
pub fn create_database(&self) -> Result<Connection<NativeFbClient<A>>, FbError> {
Connection::create_database(self.new_instance()?, &self.conn_conf, self.page_size)
}
}
impl<A, B> NativeConnectionBuilder<A, B>
where
A: ConfiguredLinkage,
B: ConfiguredConnType,
{
/// Username. Default: SYSDBA
pub fn user<S: Into<String>>(&mut self, user: S) -> &mut Self {
self.conn_conf.attachment_conf.user = user.into();
self
}
/// Database name or path. Default: test.fdb
pub fn db_name<S: Into<String>>(&mut self, db_name: S) -> &mut Self {
self.conn_conf.attachment_conf.db_name = db_name.into();
self
}
/// SQL Dialect. Default: 3
pub fn dialect(&mut self, dialect: Dialect) -> &mut Self {
self.conn_conf.dialect = dialect;
self
}
/// Connection charset. Default: UTF-8
pub fn charset(&mut self, charset: Charset) -> &mut Self {
self.charset = charset;
self
}
/// Statement cache size. Default: 20
pub fn stmt_cache_size(&mut self, stmt_cache_size: usize) -> &mut Self {
self.conn_conf.stmt_cache_size = stmt_cache_size;
self
}
/// Database page size. Used on db creation. Default: depends on firebird version
pub fn page_size(&mut self, size: u32) -> &mut Self {
self.page_size = Some(size);
self
}
/// User role name. Default: empty
pub fn role<S: Into<String>>(&mut self, name: S) -> &mut Self {
self.conn_conf.attachment_conf.role_name = Some(name.into());
self
}
/// Default transaction configuration
pub fn transaction(&mut self, conf: TransactionConfiguration) -> &mut Self {
self.conn_conf.transaction_conf = conf;
self
}
/// Default transaction configuration builder
pub fn with_transaction<F>(&mut self, builder: F) -> &mut Self
where
F: FnOnce(&mut TransactionConfigurationBuilder) -> &mut TransactionConfigurationBuilder,
{
self.conn_conf.transaction_conf = builder(&mut transaction_builder()).build();
self
}
/// Disabled the database triggers
pub fn no_db_triggers(&mut self) -> &mut Self {
self.conn_conf.no_db_triggers = true;
self
}
}
impl<A, B> NativeConnectionBuilder<A, B> {
//never export this. It would allow users to bypass the type safety.
fn safe_transmute<X, Y>(self) -> NativeConnectionBuilder<X, Y> {
NativeConnectionBuilder {
_marker_linkage: PhantomData,
_marker_conntype: PhantomData,
conn_conf: self.conn_conf,
charset: self.charset,
lib_path: self.lib_path,
page_size: self.page_size,
}
}
}
impl Default for NativeConnectionBuilder<LinkageNotConfigured, ConnTypeNotConfigured> {
fn default() -> Self {
let mut self_result = Self {
_marker_linkage: PhantomData,
_marker_conntype: PhantomData,
conn_conf: Default::default(),
charset: charset::UTF_8,
lib_path: None,
page_size: None,
};
self_result.conn_conf.dialect = Dialect::D3;
self_result.conn_conf.stmt_cache_size = 20;
self_result.conn_conf.attachment_conf.remote = None;
self_result.conn_conf.attachment_conf.user = "SYSDBA".to_string();
self_result.conn_conf.attachment_conf.db_name = "test.fdb".to_string();
self_result.conn_conf.attachment_conf.role_name = None;
self_result
}
}
//can only use these methods on a remote builder
impl<A> NativeConnectionBuilder<A, ConnRemote> {
//private helper accessor for the Option<RemoteConfig> buried inside
//the configuration
fn get_initialized_remote(&mut self) -> &mut RemoteConfig {
self.conn_conf
.attachment_conf
.remote
.get_or_insert(Default::default())
}
/// Hostname or IP address of the server. Default: localhost
pub fn host<S: Into<String>>(&mut self, host: S) -> &mut Self {
self.get_initialized_remote().host = host.into();
self
}
/// TCP Port of the server. Default: 3050
pub fn port(&mut self, port: u16) -> &mut Self {
self.get_initialized_remote().port = port;
self
}
/// Password. Default: masterkey
pub fn pass<S: Into<String>>(&mut self, pass: S) -> &mut Self {
self.get_initialized_remote().pass = pass.into();
self
}
}
impl<A> NativeConnectionBuilder<A, ConnTypeNotConfigured> {
/// Configure the native client for remote connections.
/// This will allow configuration via the 'host', 'port' and 'pass' methods.
pub fn with_remote(mut self) -> NativeConnectionBuilder<A, ConnRemote> {
let remote = rsfbclient_native::RemoteConfig {
host: "localhost".to_string(),
port: 3050,
pass: "masterkey".to_string(),
};
self.conn_conf.attachment_conf.remote = Some(remote);
self.safe_transmute()
}
//does nothing since the embedded config is common to both connection types
/// Configure the native client for embedded connections.
/// There is no 'host', 'port' or 'pass' to configure on the result of this
/// method and attempts to call those methods will result in a
/// compile error.
///
/// Note that the embedded builder is only tested for firebird >=3.0.
/// If the embedded connection fails, the client dll may attempt to use
/// other means of connection automatically, such as XNET or localhost.
///
/// On firebird 3.0 and above this may be restricted via the `Providers`
/// config parameter of `firebird.conf` see official firebird documentation
/// for more information.
pub fn with_embedded(self) -> NativeConnectionBuilder<A, ConnEmbedded> {
self.safe_transmute()
}
}
impl<A> NativeConnectionBuilder<LinkageNotConfigured, A> {
/// Uses the native client with dynamic linking.
/// Requires that the dynamic library .dll/.so/.dylib can be found
/// at compile time as well as runtime.
///
/// Requires feature `linking`
#[cfg(feature = "linking")]
pub fn with_dyn_link(self) -> NativeConnectionBuilder<DynLink, A> {
self.safe_transmute()
}
#[cfg(feature = "dynamic_loading")]
/// Searches for the firebird client at runtime only, at the specified
/// location.
///
/// # Example
///
/// ```no_run
/// // On windows
/// rsfbclient::builder_native()
/// .with_dyn_load("fbclient.dll")
/// .with_embedded();
///
///
/// // On linux
/// rsfbclient::builder_native()
/// .with_dyn_load("libfbclient.so")
/// .with_remote();
///
/// // Any platform, file located relative to the
/// // folder where the executable was run
/// rsfbclient::builder_native()
/// .with_dyn_load("./fbclient.lib")
/// .with_embedded();
/// ```
/// Requires feature 'dynamic_loading'.
pub fn with_dyn_load<S: Into<String>>(
mut self,
lib_path: S,
) -> NativeConnectionBuilder<DynLoad, A> {
self.lib_path = Some(lib_path.into());
self.safe_transmute()
}
/// Setup the connection using the string
/// pattern.
///
/// Basic string syntax: `firebird://{user}:{pass}@{host}:{port}/{db_name}?charset={charset}&lib={fbclient}&dialect={dialect}`
///
/// Some considerations:
/// - If you not provide the host, we will consider this a embedded connection.
/// - The port, user and pass parameters only will be accepted if you provide the host.
/// - If you not provide the `lib={fbclient}`, we will consider this a dynamic linked connection. The default, by the way.
#[allow(clippy::wrong_self_convention)]
pub fn from_string(
self,
s_conn: &str,
) -> Result<NativeConnectionBuilder<DynByString, ConnByString>, FbError> {
let settings = conn_string::parse(s_conn)?;
let mut cb: NativeConnectionBuilder<DynByString, ConnRemote> = {
// When we have a host, we will consider
// this a remote connection
if let Some(host) = settings.host {
let mut cb = self.safe_transmute().with_remote();
cb.host(host);
if let Some(port) = settings.port {
cb.port(port);
}
if let Some(user) = settings.user {
cb.user(user);
}
if let Some(pass) = settings.pass {
cb.pass(pass);
}
cb
} else {
self.safe_transmute()
}
};
cb.db_name(settings.db_name);
if let Some(charset) = settings.charset {
cb.charset(charset);
}
if let Some(dialect) = settings.dialect {
cb.dialect(dialect);
}
if let Some(lib_path) = settings.lib_path {
cb.lib_path = Some(lib_path);
}
if let Some(stmt_cache_size) = settings.stmt_cache_size {
cb.stmt_cache_size(stmt_cache_size);
}
if let Some(role_name) = settings.role_name {
cb.role(role_name);
}
Ok(cb.safe_transmute())
}
}