rsfbclient/connection/builders/
builder_native.rs

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