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}