Skip to main content

tank_core/
connection.rs

1use crate::{Driver, Error, Executor, Result};
2use std::{
3    borrow::Cow,
4    future::{self, Future},
5};
6use url::Url;
7
8/// A live database handle capable of executing queries and spawning transactions.
9///
10/// Extends [`Executor`] with connection and transaction management.
11///
12/// # Lifecycle
13/// - `connect` creates (or fetches) an underlying connection. It may eagerly
14///   establish network I/O; always await it.
15/// - `begin` starts a transaction scope. Commit/rollback MUST be awaited to
16///   guarantee resource release.
17pub trait Connection: Executor {
18    /// Validates and normalizes the connection URL, handling special cases like in-memory databases.
19    fn sanitize_url(_driver: &Self::Driver, mut url: Cow<'static, str>) -> Result<Url>
20    where
21        Self: Sized,
22    {
23        let mut in_memory = false;
24        if let Some((scheme, host)) = url.split_once("://")
25            && host.starts_with(":memory:")
26        {
27            url = format!("{scheme}://localhost{}", &host[8..]).into();
28            in_memory = true;
29        }
30        let mut result = Url::parse(&url)?;
31        if in_memory {
32            result.query_pairs_mut().append_pair("mode", "memory");
33        }
34        let names = <Self::Driver as Driver>::NAME;
35        'prefix: {
36            for name in names {
37                let prefix = format!("{}://", name);
38                if url.starts_with(&prefix) {
39                    break 'prefix prefix;
40                }
41            }
42            let error = Error::msg(format!(
43                "Connection URL must start with: {}",
44                names.join(", ")
45            ));
46            log::error!("{:#}", error);
47            return Err(error);
48        };
49        Ok(result)
50    }
51
52    /// Establishes a connection (or pool) to the database specified by the URL.
53    ///
54    /// Implementations may perform I/O or validation during `connect`.
55    /// Callers should treat this as a potentially expensive operation.
56    fn connect(
57        driver: &Self::Driver,
58        url: Cow<'static, str>,
59    ) -> impl Future<Output = Result<<Self::Driver as Driver>::Connection>> + Send
60    where
61        Self: Sized;
62
63    /// Starts a new transaction on this connection.
64    ///
65    /// Must await `commit` or `rollback` to finalize the scope and release resources.
66    fn begin(
67        &mut self,
68    ) -> impl Future<Output = Result<<Self::Driver as Driver>::Transaction<'_>>> + Send;
69
70    /// Closes the connection and releases any session resources.
71    fn disconnect(self) -> impl Future<Output = Result<()>> + Send
72    where
73        Self: Sized,
74    {
75        future::ready(Ok(()))
76    }
77}
78
79impl<S: Connection> Connection for &mut S {
80    fn connect(
81        driver: &Self::Driver,
82        url: Cow<'static, str>,
83    ) -> impl Future<Output = Result<<Self::Driver as Driver>::Connection>> + Send
84    where
85        Self: Sized,
86    {
87        S::connect(driver, url)
88    }
89
90    fn begin(
91        &mut self,
92    ) -> impl Future<Output = Result<<Self::Driver as Driver>::Transaction<'_>>> + Send {
93        (**self).begin()
94    }
95
96    fn disconnect(self) -> impl Future<Output = Result<()>> + Send
97    where
98        Self: Sized,
99    {
100        future::ready(Err(Error::msg(
101            "Cannot disconnect using `&mut Connection`, use the actual object.",
102        )))
103    }
104}