tank_core/
connection.rs

1use crate::{Driver, Error, Executor, Result, truncate_long};
2use anyhow::Context;
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/// This trait extends [`Executor`] adding functionality to acquire a connection
12/// and to begin transactional scopes.
13///
14/// Drivers implement concrete `Connection` types to expose backend-specific
15/// behavior (timeouts, pooling strategies, prepared statement caching, etc.).
16///
17/// # Lifecycle
18/// - `connect` creates (or fetches) an underlying connection. It may eagerly
19///   establish network I/O for validation; always await it.
20/// - `begin` starts a transaction returning an object implementing
21///   [`Transaction`]. Commit / rollback MUST be awaited to guarantee resource
22///   release.
23pub trait Connection: Executor {
24    fn sanitize_url(mut url: Cow<'static, str>) -> Result<Url>
25    where
26        Self: Sized,
27    {
28        let mut in_memory = false;
29        if let Some((scheme, host)) = url.split_once("://")
30            && host.starts_with(":memory:")
31        {
32            url = format!("{scheme}://localhost{}", &host[8..]).into();
33            in_memory = true;
34        }
35        let context = || format!("While trying to connect to `{}`", truncate_long!(url));
36        let mut result = Url::parse(&url).with_context(context)?;
37        if in_memory {
38            result.query_pairs_mut().append_pair("mode", "memory");
39        }
40        let names = <Self::Driver as Driver>::NAME;
41        'prefix: {
42            for name in names {
43                let prefix = format!("{}://", name);
44                if url.starts_with(&prefix) {
45                    break 'prefix prefix;
46                }
47            }
48            let error = Error::msg(format!(
49                "Connection URL must start with: {}",
50                names.join(", ")
51            ))
52            .context(context());
53            log::error!("{:#}", error);
54            return Err(error);
55        };
56        Ok(result)
57    }
58
59    /// Create a connection (or pool) with at least one underlying session
60    /// established to the given URL.
61    ///
62    /// The returned future must be awaited to obtain the connection object
63    /// (type `Self::Driver::Connection`). Implementations may  perform I/O or validation
64    /// during `connect`; callers should treat this as a potentially expensive operation.
65    fn connect(
66        url: Cow<'static, str>,
67    ) -> impl Future<Output = Result<<Self::Driver as Driver>::Connection>>
68    where
69        Self: Sized;
70
71    /// Begin a transaction scope tied to the current connection.
72    ///
73    /// The returned value implements [`Transaction`] for the underlying driver.
74    /// `commit` / `rollback` MUST be awaited to ensure resources are released and the scope is finalized.
75    fn begin(&mut self) -> impl Future<Output = Result<<Self::Driver as Driver>::Transaction<'_>>>;
76
77    /// Disconnect and release the underlying session(s).
78    ///
79    /// Default implementation is a no-op; drivers may override to close sockets
80    /// or return the connection to a pool asynchronously.
81    fn disconnect(self) -> impl Future<Output = Result<()>>
82    where
83        Self: Sized,
84    {
85        future::ready(Ok(()))
86    }
87}