Skip to main content

tank_core/
connection.rs

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