rsdbc_core/
connection.rs

1use std::collections::HashMap;
2use std::str::FromStr;
3use std::time::Duration;
4use futures::future::BoxFuture;
5use url::Url;
6use crate::{OptionValue, RsdbcErrors, Result};
7
8pub trait ConnectionFactory: 'static + Send + Sync {
9    // TODO: should have associated type for Error so that we have multiple error types?
10    // TODO: remove associated type?
11    // type Connection: Connection + ?Sized;
12
13    // // TODO: this was a generic fn/impl
14    // // TODO: move out of this trait so that implementations dont worry about this here
15    // /// Returns a [ConnectionFactory] from an available implementation, created from a Connection URL.
16    // fn new(url: String) -> Result<Box<Self>>;
17    //
18    // /// Returns a [ConnectionFactory] from an available implementation,
19    // /// created from a collection of [ConnectionFactoryOptions].
20    // fn from(options: ConnectionFactoryOptions) -> Result<Box<Self>>;
21
22    // TODO: rename to create?
23    // /// Establish a new database connection with the options specified by `self`.
24    // fn connect(&self) -> BoxFuture<'_, Result<Self::Connection>>
25    //     where
26    //         Self::Connection: Sized;
27
28    // TODO: create instead of connect?
29    /// Establish a new database connection with the options specified by ConnectionOptions.
30    fn connect(&self) -> BoxFuture<'_, Result<Box<dyn Connection>>>;
31
32    /// Returns the [ConnectionFactoryMetadata] about the product this [ConnectionFactory] is applicable to.
33    fn get_metadata(&self) -> Box<dyn ConnectionFactoryMetadata>;
34}
35
36#[derive(Debug, Clone)]
37pub struct ConnectionFactoryOptionsBuilder {
38    pub options: HashMap<String, OptionValue>,
39}
40
41impl ConnectionFactoryOptionsBuilder {
42
43    pub fn new() -> Self {
44        Self {
45            options: Default::default()
46        }
47    }
48
49    pub fn build(&self) -> ConnectionFactoryOptions {
50        ConnectionFactoryOptions::from(self.options.to_owned())
51    }
52
53    pub fn from_options(connection_factory_options: ConnectionFactoryOptions) -> Self {
54        let mut options = HashMap::new();
55        for (key, value) in connection_factory_options.options {
56            options.insert(key, value);
57        }
58
59        Self {
60            options
61        }
62    }
63
64    pub fn add_option<K: Into<String>>(&mut self, key: K, value: OptionValue) -> &mut Self {
65        self.options.insert(key.into(), value);
66        self
67    }
68
69    pub fn add_bool<K: Into<String>>(&mut self, key: K, value: bool) -> &mut Self {
70        self.options.insert(key.into(), value.into());
71        self
72    }
73
74    pub fn add_duration<K: Into<String>>(&mut self, key: K, value: Duration) -> &mut Self {
75        self.options.insert(key.into(), value.into());
76        self
77    }
78
79    pub fn i32<K: Into<String>>(&mut self, key: K, value: i32) -> &mut Self {
80        self.options.insert(key.into(), value.into());
81        self
82    }
83
84    pub fn add_map<K: Into<String>>(&mut self, key: K, value: HashMap<String, String>) -> &mut Self {
85        self.options.insert(key.into(), value.into());
86        self
87    }
88
89    pub fn add_string<K: Into<String>>(&mut self, key: K, value: String) -> &mut Self {
90        self.options.insert(key.into(), value.into());
91        self
92    }
93
94}
95
96#[derive(Debug, Clone)]
97pub struct ConnectionFactoryOptions {
98    pub options: HashMap<String, OptionValue>,
99}
100
101// TODO: where should build reside?
102impl ConnectionFactoryOptions {
103
104    pub fn new() -> Self {
105        Self {
106            options: Default::default()
107        }
108    }
109
110    pub fn from(options: HashMap<String, OptionValue>) -> Self {
111        Self {
112            options
113        }
114    }
115
116    pub fn get_value(&self, option: &str) -> Option<&OptionValue> {
117        self.options.get(option)
118    }
119
120    pub fn try_as_bool(&self, option: &str) -> Result<bool> {
121        let value = self.get_value(option);
122        if value.is_none() {
123            // TODO: missing value?
124            return Err(RsdbcErrors::Unsupported("".to_string()));
125        }
126        match value.unwrap() {
127            OptionValue::Bool(v) => {
128                Ok(*v)
129            }
130            OptionValue::Int(v) => {
131                Ok(*v != 0)
132            }
133            OptionValue::String(v) => {
134                match v.as_str() {
135                    "true" => Ok(true),
136                    "false" => Ok(false),
137                    "yes" => Ok(true),
138                    "no" => Ok(false),
139                    "1" => Ok(true),
140                    "0" => Ok(false),
141                    _ => Err(RsdbcErrors::Unsupported("".to_string()))
142                }
143            }
144            _ => Err(RsdbcErrors::Unsupported("".to_string()))
145        }
146    }
147
148    pub fn try_as_i32(&self, option: &str) -> Result<i32> {
149        let value = self.get_value(option);
150        if value.is_none() {
151            // TODO: missing value?
152            return Err(RsdbcErrors::Unsupported("".to_string()));
153        }
154        match value.unwrap() {
155            OptionValue::Bool(v) => {
156                Ok(*v as i32)
157            }
158            OptionValue::Int(v) => {
159                Ok(*v)
160            }
161            OptionValue::String(v) => {
162                // let my_int: i32 = my_string.parse().unwrap();
163                Ok(v.parse::<i32>()?)
164            }
165            _ => Err(RsdbcErrors::Unsupported("".to_string()))
166        }
167    }
168
169
170    pub fn has_option(&self, option: &str) -> bool {
171        self.options.contains_key(option)
172    }
173
174    pub fn parse<S: AsRef<str>>(url: S) -> Result<Self> {
175        url.as_ref().parse()
176    }
177}
178
179impl FromStr for ConnectionFactoryOptions {
180    type Err = RsdbcErrors;
181
182    // TODO: clean this up
183    fn from_str(s: &str) -> Result<Self> {
184        let u = Url::parse(s)?;
185        println!("{}", u);
186        println!("path: {}", u.path());
187        println!("host: {}", u.host_str().unwrap());
188        println!("domain: {}", u.domain().unwrap());
189        println!("fragment: {}", u.fragment().or(Some("")).unwrap());
190        println!("scheme: {}", u.scheme());
191
192        validate(&s)?;
193
194        // let scheme_parts: Vec<&str> = s.splitn(3, ":").collect();
195        // let scheme = scheme_parts[0];
196        // let driver = scheme_parts[1];
197        // let protocol = scheme_parts[2];
198
199        // TODO: use .ok_or here instead?
200        // let scheme_specific_part_index = s.find("://").unwrap();
201        // let rewritten_url = scheme.to_owned() + &s[scheme_specific_part_index..];
202        // let uri = Url::parse(rewritten_url.as_str())?;
203        let uri = Url::parse(s)?;
204
205        // TODO: builder
206        let mut connection_factory_builder = ConnectionFactoryOptionsBuilder::new();
207        // TODO: ssl?
208
209
210        connection_factory_builder.add_option("driver", uri.scheme().into());
211
212        // connection_factory_builder.add_option("driver", driver.into());
213
214        // let protocol_end = protocol.find("://");
215        // if let Some(protocol_end) = protocol_end {
216        //     let protocol_bits = &protocol[..protocol_end];
217        //     if !protocol_bits.trim().is_empty() {
218        //         connection_factory_builder.add_option("protocol", protocol_bits.into());
219        //     }
220        // }
221
222
223        if uri.has_host() {
224            connection_factory_builder.add_option("host", uri.host_str().unwrap().into());
225            if !uri.username().is_empty() {
226                connection_factory_builder.add_option("user", uri.username().into());
227            }
228
229            if let Some(password) = uri.password() {
230                connection_factory_builder.add_option("password", password.into());
231            }
232        }
233
234        if let Some(port) = uri.port() {
235            connection_factory_builder.add_option("port", port.into());
236        }
237
238        // TODO: validate this
239        if !uri.path().is_empty() {
240            connection_factory_builder.add_option("database", uri.path().into());
241        }
242
243        for (k, v) in uri.query_pairs() {
244            // TODO: prohibit certain options
245            connection_factory_builder.add_option(k, v.into());
246
247        }
248
249
250        Ok(connection_factory_builder.build())
251    }
252}
253
254pub trait ConnectionFactoryProvider {
255    type C: ConnectionFactory;
256    fn create(options: ConnectionFactoryOptions) -> Result<Self::C>;
257}
258
259
260/// Metadata about the product a [ConnectionFactory] is applicable to.
261pub trait ConnectionFactoryMetadata {
262
263    /// Returns the name of the product a [ConnectionFactory] can connect to
264    fn name(&self) -> String;
265
266}
267
268// TODO: add cancel that returns cancellation token
269/// Represents a connection to a database
270// pub trait Connection<'conn> {
271pub trait Connection: Send {
272    // type Statement: Statement<'conn> + ?Sized;
273
274    // trait attributes
275    // TransactionDefinition
276    // Batch
277    // Statement...this could be simple or prepared so probably doesnt work here
278    // ConnectionMetadata
279
280
281    /// Begins a new transaction.
282    fn begin_transaction(&mut self) -> Result<()>;
283
284    // TODO: how to handle object safety for this?
285    // /// Begins a new transaction.
286    // /// Beginning the transaction may fail if the [TransactionDefinition] conflicts with the
287    // /// connection configuration.
288    // fn begin_transaction_with_definition(&mut self, definition: Box<dyn TransactionDefinition>);
289
290
291    // Explicitly close this database connection.
292    //
293    // This method is **not required** for safe and consistent operation. However, it is
294    // recommended to call it instead of letting a connection `drop` as the database backend
295    // will be faster at cleaning up resources.
296    /// Releases this Connection object's database and resources immediately instead of waiting
297    /// for them to be automatically released.
298    fn close(&mut self) -> Result<()>;
299
300    /// Commits the current transaction.
301    fn commit_transaction(&mut self);
302
303    /// Creates a new [Batch] instance for building a batched request.
304    fn create_batch(&mut self) -> Result<Box<dyn Batch>>;
305
306    /// Creates a savepoint in the current transaction.
307    /// Arguments:
308    ///
309    /// * `name`: name the name of the savepoint to create.
310    ///
311    /// UnsupportedOperationException if savepoints are not supported
312    fn create_savepoint(&mut self, name: &str);
313
314    /// Creates a new statement for building a statement-based request.
315    /// Arguments:
316    ///
317    /// * `name`: the SQL of the statement
318    ///
319    // fn create_statement(&mut self, sql: &str) -> Result<Box<Self::Statement>>;
320    // rustc --explain E0759
321    // to declare that the trait object captures data from argument `self`, you can add an explicit `'_` lifetime bound
322    fn create_statement(&mut self, sql: &str) -> Result<Box<dyn Statement<'_> + '_>>;
323
324    /// Returns the auto-commit mode for this connection.
325    ///
326    /// @return true if the connection is in auto-commit mode; false otherwise.
327    fn is_auto_commit(&mut self) -> bool;
328
329    /// Returns the [ConnectionMetadata] about the product this [Connection] is connected to.
330    fn metadata(&mut self) -> Result<Box<dyn ConnectionMetadata>>;
331
332    /// Returns the [IsolationLevel] for this connection.
333    ///
334    /// Isolation level is typically one of the following constants:
335    /// - READ_UNCOMMITTED
336    /// - READ_COMMITTED
337    /// - REPEATABLE_READ
338    /// - SERIALIZABLE
339    ///
340    /// [IsolationLevel] is extensible so drivers can return a vendor-specific [IsolationLevel].
341    fn transaction_isolation_level(&mut self) -> IsolationLevel;
342
343    // TODO: This makes sense if the connection is dealing with underlying transaction
344    // not sure it makes sense here if we return the transaction to the client
345    /// Releases a savepoint in the current transaction.
346    /// Calling this for drivers not supporting savepoint release results in a no-op.
347    /// Arguments:
348    ///
349    /// * `name`: the name of the savepoint to release
350    fn release_savepoint(&mut self, name: &str);
351
352    /// Rolls back the current transaction.
353    fn rollback_transaction(&mut self);
354
355    // TODO: This makes sense if the connection is dealing with underlying transaction
356    // not sure it makes sense here if we return the transaction to the client
357    /// Rolls back to a savepoint in the current transaction.
358    /// Arguments:
359    ///
360    /// * `name`: the name of the savepoint to rollback to
361    ///
362    /// @throws UnsupportedOperationException if savepoints are not supported
363    fn rollback_transaction_to_savepoint(&mut self, name: String);
364
365    /// Configures the auto-commit mode for the current transaction.
366    /// If a connection is in auto-commit mode, then all [Statement]s will be executed
367    /// and committed as individual transactions.
368    /// Otherwise, in explicit transaction mode, transactions have to
369    /// be [beginTransaction()] started explicitly.
370    /// A transaction needs to be either [commitTransaction()] committed
371    /// or [rollbackTransaction()] rolled back to clean up the transaction state.
372    ///
373    /// Calling this method during an active transaction and the auto-commit mode is changed,
374    /// the transaction is committed.
375    /// Calling this method without changing auto-commit mode this invocation results in a no-op.
376    ///
377    /// Arguments:
378    ///
379    /// * `name`: the isolation level for this transaction
380    fn auto_commit(&mut self, commit: bool);
381
382    /// Configures the isolation level for the current transaction.
383    /// Isolation level is typically one of the following constants:
384    /// - READ_UNCOMMITTED
385    /// - READ_COMMITTED
386    /// - REPEATABLE_READ
387    /// - SERIALIZABLE
388    /// [IsolationLevel] is extensible so drivers can accept a vendor-specific [IsolationLevel].
389    /// isolationLevel the isolation level for this transaction
390    fn set_transaction_isolation_level(&mut self, isolation_level: IsolationLevel);
391
392    /// Validates the connection according to the given [ValidationDepth].
393    /// Emits true if the validation was successful or false if the validation failed.
394    /// Does not emit errors and does not complete empty.
395    /// Arguments:
396    ///
397    /// * `depth`: the validation depth
398    fn validate(&mut self, depth: ValidationDepth) -> bool;
399
400
401
402    // /// Makes all changes made since the previous commit/rollback permanent and releases any database locks currently held by this Connection object.
403    // fn commit(&mut self);
404
405// Statement	createStatement()
406// Creates a Statement object for sending SQL statements to the database.
407// Statement	createStatement(int resultSetType, int resultSetConcurrency)
408// Creates a Statement object that will generate ResultSet objects with the given type and concurrency.
409// Statement	createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
410// Creates a Statement object that will generate ResultSet objects with the given type, concurrency, and holdability.
411//
412//     /// Create a statement for execution
413//     fn create(&mut self, sql: &str) -> Result<Box<dyn Statement + '_>>;
414//
415//     /// Retrieves this Connection object's current catalog name.
416//     fn get_catalog(&mut self) -> &str;
417//
418//     /// Returns a list containing the name and current value of each client info property supported by the driver.
419//     fn get_all_client_info(&mut self) -> HashMap<String, String>;
420//
421//     /// Returns the value of the client info property specified by name.
422//     fn get_client_info(&mut self, name: &str) -> &str;
423//
424//     /// Retrieves a DatabaseMetadata object that contains metadata about the database to which this Connection object represents a connection.
425//     fn get_metadata(&mut self) -> DatabaseMetadata;
426//
427//     /// Retrieves the number of milliseconds the driver will wait for a database request to complete.
428//     fn get_network_timeout(&mut self) -> i32;
429//
430//     /// Retrieves this Connection object's current schema name.
431//     fn get_schema(&mut self) -> &str;
432//
433//     /// Retrieves this Connection object's current transaction isolation level.
434//     fn get_transaction_isolation(&mut self) -> i32;
435//
436//     /// Retrieves the first warning reported by calls on this Connection object.
437//     fn get_warnings(&mut self) -> SQLWarning;
438//
439//     /// Retrieves whether this Connection object has been closed.
440//     fn is_closed(&mut self) -> bool;
441//
442//     /// Retrieves whether this Connection object is in read-only mode.
443//     fn is_read_only(&mut self) -> bool;
444//
445//     /// Returns true if the connection has not been closed and is still valid.
446//     fn is_valid(&mut self, time_out: i32) -> bool;
447//
448//     /// Converts the given SQL statement into the system's native SQL grammar.
449//     fn native_sql(&mut self, sql: &str) -> &str;
450//
451//     /// Create a prepared statement for execution
452//     fn prepare(&mut self, sql: &str) -> Result<Box<dyn Statement + '_>>;
453
454    // CallableStatement	prepareCall(String sql)
455    // Creates a CallableStatement object for calling database stored procedures.
456    // CallableStatement	prepareCall(String sql, int resultSetType, int resultSetConcurrency)
457    // Creates a CallableStatement object that will generate ResultSet objects with the given type and concurrency.
458    // CallableStatement	prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
459    // Creates a CallableStatement object that will generate ResultSet objects with the given type and concurrency.
460    // PreparedStatement	prepareStatement(String sql)
461    // Creates a PreparedStatement object for sending parameterized SQL statements to the database.
462    // PreparedStatement	prepareStatement(String sql, int autoGeneratedKeys)
463    // Creates a default PreparedStatement object that has the capability to retrieve auto-generated keys.
464    // PreparedStatement	prepareStatement(String sql, int[] columnIndexes)
465    // Creates a default PreparedStatement object capable of returning the auto-generated keys designated by the given array.
466    // PreparedStatement	prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
467    // Creates a PreparedStatement object that will generate ResultSet objects with the given type and concurrency.
468    // PreparedStatement	prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
469    // Creates a PreparedStatement object that will generate ResultSet objects with the given type, concurrency, and holdability.
470    // PreparedStatement	prepareStatement(String sql, String[] columnNames)
471    // Creates a default PreparedStatement object capable of returning the auto-generated keys designated by the given array.
472
473    // /// Undoes all changes made in the current transaction and releases any database locks currently held by this Connection object.
474    // fn rollback(&mut self);
475}
476
477pub trait Transaction {
478
479    // exec
480    // prepare
481    // query
482    // statement
483
484    /// Commits the current transaction.
485    fn commit_transaction(&mut self) -> Result<()>;
486
487    /// Creates a new [Batch] instance for building a batched request.
488    fn create_batch(&mut self) -> Result<Box<dyn Batch>>;
489
490    /// Creates a savepoint in the current transaction.
491    /// Arguments:
492    ///
493    /// * `name`: name the name of the savepoint to create.
494    ///
495    /// UnsupportedOperationException if savepoints are not supported
496    fn create_savepoint(&mut self, name: &str) -> Result<Box<Self>>;
497
498
499    /// Releases a savepoint in the current transaction.
500    /// Calling this for drivers not supporting savepoint release results in a no-op.
501    /// Arguments:
502    ///
503    /// * `name`: the name of the savepoint to release
504    fn release_savepoint(&mut self, name: &str);
505
506    /// Rolls back the current transaction.
507    fn rollback_transaction(&mut self) -> Result<()>;
508
509
510}
511
512
513fn validate(url: &str) -> Result<()> {
514    Ok(())
515}
516
517/// Metadata about the product a [Connection] is connected to.
518pub trait ConnectionMetadata {
519
520    /// Retrieves the name of this database product.
521    /// May contain additional information about editions.
522    fn database_product_name(&self) -> &str;
523
524    /// Retrieves the version number of this database product.
525    fn database_version(&self) -> &str;
526}
527
528/// A collection of statements that are executed in a batch for performance reasons.
529pub trait Batch {
530
531    /// Add a statement to this batch.
532    fn add(&mut self, sql: String) -> &mut Self where Self: Sized;
533
534    /// Executes one or more SQL statements and returns the [Result]s.
535    fn execute(&mut self) -> Result<Box<dyn SQLResult>>;
536}
537
538// TODO: Should this include None or just use Option? I'm currently leaning Option
539/// Represents a transaction isolation level constant.
540#[derive(Debug, Clone, Copy, PartialEq, Eq)]
541#[non_exhaustive]
542pub enum IsolationLevel {
543    /// The read committed isolation level.
544    ReadCommitted,
545    /// The read uncommitted isolation level.
546    ReadUncommitted,
547    /// The repeatable read isolation level.
548    RepeatableRead,
549    /// The serializable isolation level.
550    Serializable
551}
552
553impl IsolationLevel {
554    pub(crate) fn new(raw: &str) -> Result<IsolationLevel> {
555        if raw.eq_ignore_ascii_case("READ UNCOMMITTED") {
556            Ok(IsolationLevel::ReadUncommitted)
557        } else if raw.eq_ignore_ascii_case("READ COMMITTED") {
558            Ok(IsolationLevel::ReadCommitted)
559        } else if raw.eq_ignore_ascii_case("REPEATABLE READ") {
560            Ok(IsolationLevel::RepeatableRead)
561        } else if raw.eq_ignore_ascii_case("SERIALIZABLE") {
562            Ok(IsolationLevel::Serializable)
563        } else {
564            // Err(bad_response().into())
565            // Err(Error::Io(bad_response()))
566            // Err(io::Error::new(
567            //     io::ErrorKind::InvalidInput,
568            //     "the server returned an unexpected response",
569            // ))
570            Err(RsdbcErrors::General(String::from("the server returned an unexpected response")))
571        }
572    }
573
574    // TODO: review https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv
575    pub fn as_sql(&self) -> &'static str {
576        match *self {
577            IsolationLevel::ReadUncommitted => "READ UNCOMMITTED",
578            IsolationLevel::ReadCommitted => "READ COMMITTED",
579            IsolationLevel::RepeatableRead => "REPEATABLE READ",
580            IsolationLevel::Serializable => "SERIALIZABLE",
581        }
582    }
583}
584
585
586pub trait SQLResult {
587    fn get_rows_updated(&self) -> Option<u32>;
588
589    // TODO: map function
590
591    // <T> Publisher<T> map(BiFunction<Row, RowMetadata, ? extends T> mappingFunction);
592    // fn map<F, B>(self, f: F) -> MappedRows<'stmt, F>
593    //     where
594    //         F: FnMut(&dyn Row<'_>) -> Result<B>,
595    // {
596    //     MappedRows { rows: self, map: f }
597    // }
598}
599
600/// Constants indicating validation depth for a [Connection].
601pub enum ValidationDepth {
602    /// Perform a client-side only validation.
603    /// Typically to determine whether a connection is still active or other mechanism
604    /// that does not involve remote communication.
605    Local,
606    /// Perform a remote connection validations.
607    /// Typically by sending a database message or some other mechanism to validate that
608    /// the database connection and session are active and can be used for
609    /// database queries.
610    /// Any query submitted by the driver to validate the connection is executed in
611    /// the context of the current transaction.
612    Remote,
613}
614
615
616/// Represents an executable statement
617pub trait Statement<'conn> {
618
619    // from java rsdbc
620    fn add(&mut self) -> &mut Self where Self: Sized; //Box<dyn A>
621
622    fn bind_index<T>(&mut self, index: u32, value: T) -> &mut Self where Self: Sized; //Box<dyn A>
623
624    fn bind_name<T>(&mut self, name: &str, value: T) -> &mut Self where Self: Sized; //Box<dyn A>
625
626    // TODO: not sure what type should be here
627    // these might not be needed
628    // removed type for now
629    fn bind_null_index(&mut self, index: u32) -> &mut Self where Self: Sized; //Box<dyn A>
630    fn bind_null_name(&mut self, name: &str) -> &mut Self where Self: Sized; //Box<dyn A>
631
632    // TODO: should be a stream?
633    // not sure about this where Self: Sized
634    fn execute<T: SQLResult>(&self) -> Result<T> where Self: Sized;
635
636    /// Configures [Statement] to return the generated values from any rows created by this
637    /// [Statement] in the [SQLResult] returned from [execute()].
638    /// If no columns are specified, implementations are free to choose which columns
639    /// will be returned.
640    /// If called multiple times, only the columns requested in the final invocation will be returned.
641    ///
642    /// The default implementation of this method is a no-op.
643    fn return_generated_values(&mut self, columns: &[&str]) -> &mut Self where Self: Sized { //Box<dyn A>
644        // default is no-op
645        self
646    }
647
648    /// Configures [Statement] to retrieve a fixed number of rows when fetching results from a
649    /// query instead deriving fetch size from back pressure.
650    /// If called multiple times, only the fetch size configured in the final invocation
651    /// will be applied.
652    /// If the value specified is zero, then the hint is ignored.
653    /// The default implementation of this method is a no op and the default value is zero.
654    fn fetch_size(&mut self, rows: u32) -> &mut Self where Self: Sized { //Box<dyn A>
655        // The default implementation of this method is a no op and the default value is zero.
656        self
657    }
658
659
660
661    // /// Execute a query that is expected to return a result set, such as a `SELECT` statement
662    // fn execute_query(&mut self, params: &[Value]) -> Result<Box<dyn ResultSet + '_>>;
663    //
664    // /// Execute a query that is expected to update some rows.
665    // fn execute_update(&mut self, params: &[Value]) -> Result<u64>;
666}
667
668
669// TODO: each db probably has a different set so this probably doesnt make sense as an enum here
670#[derive(Debug, Copy, Clone, PartialEq)]
671#[non_exhaustive]
672pub enum SslMode {
673    /// Do not use TLS.
674    Disable,
675    /// Attempt to connect with TLS but allow sessions without.
676    Prefer,
677    /// Require the use of TLS.
678    Require,
679}
680
681
682#[cfg(test)]
683mod tests {
684    use std::collections::HashMap;
685    use crate::connection::{ConnectionFactoryOptions, ConnectionFactoryOptionsBuilder};
686    use crate::RsdbcErrors;
687    use crate::Result;
688
689    #[test]
690    fn programmatic_connection_factory_builder() {
691        let options = HashMap::from([
692            ("lock_timeout", "10s"),
693            ("statement_timeout", "5m"),
694        ]);
695
696        let connection_factory_options = ConnectionFactoryOptionsBuilder::new()
697            .add_option("driver", "postgresql".into())
698            .add_string("localhost", "localhost".to_string())
699            .add_option("port", 5432.into())
700            .add_option("options", options.into())
701            .build();
702
703        assert_eq!(5432, connection_factory_options.try_as_i32("port").unwrap());
704    }
705
706    #[test]
707    fn missing_connection_factory_option_should_return_none() {
708        let connection_factory_options = ConnectionFactoryOptionsBuilder::new().build();
709
710        assert_eq!(None, connection_factory_options.get_value("driver"));
711    }
712
713    #[test]
714    fn connection_factory_has_option_should_return_appropriate_bool() {
715        let connection_factory_options = ConnectionFactoryOptionsBuilder::new()
716            .add_option("driver", "postgresql".into())
717            .build();
718
719        assert!(connection_factory_options.has_option("driver"));
720        assert!(!connection_factory_options.has_option("port"));
721    }
722
723    #[test]
724    fn should_successfully_parse_valid_connection_string() {
725        let result = ConnectionFactoryOptions::parse("postgres://admin:password@localhost/test");
726        assert!(result.is_ok());
727
728        println!("{:?}", result.unwrap());
729    }
730
731    #[test]
732    fn parse_empty_string_should_return_err() {
733        let result = ConnectionFactoryOptions::parse("");
734        assert!(result.is_err());
735    }
736
737    #[test]
738    fn connection_factory_options_should_implement_from_str() {
739        let result: Result<ConnectionFactoryOptions> = "postgres://admin:password@localhost/test".parse();
740        assert!(result.is_ok());
741    }
742}