tank_core/connection.rs
1use crate::{Driver, Error, Executor, Result, Transaction, 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 let mut in_memory = false;
26 if let Some((scheme, host)) = url.split_once("://")
27 && host.starts_with(":memory:")
28 {
29 url = format!("{scheme}://localhost{}", &host[8..]).into();
30 in_memory = true;
31 }
32 let context = || format!("While trying to connect to `{}`", truncate_long!(url));
33 let mut result = Url::parse(&url).with_context(context)?;
34 if in_memory {
35 result.query_pairs_mut().append_pair("mode", "memory");
36 }
37 let names = <Self::Driver as Driver>::NAME;
38 'prefix: {
39 for name in names {
40 let prefix = format!("{}://", name);
41 if url.starts_with(&prefix) {
42 break 'prefix prefix;
43 }
44 }
45 let error = Error::msg(format!(
46 "Connection URL must start with: {}",
47 names.join(", ")
48 ))
49 .context(context());
50 log::error!("{:#}", error);
51 return Err(error);
52 };
53 Ok(result)
54 }
55
56 /// Create a connection (or pool) with at least one underlying session
57 /// established to the given URL.
58 ///
59 /// The returned future must be awaited to obtain the connection object
60 /// (type `Self::Driver::Connection`). Implementations may perform I/O or validation
61 /// during `connect`; callers should treat this as a potentially expensive operation.
62 fn connect(
63 url: Cow<'static, str>,
64 ) -> impl Future<Output = Result<<Self::Driver as Driver>::Connection>>;
65
66 /// Begin a transaction scope tied to the current connection.
67 ///
68 /// The returned value implements [`Transaction`] for the underlying driver.
69 /// `commit` / `rollback` MUST be awaited to ensure resources are released and the scope is finalized.
70 fn begin(&mut self) -> impl Future<Output = Result<impl Transaction<'_>>>;
71
72 /// Disconnect and release the underlying session(s).
73 ///
74 /// Default implementation is a no-op; drivers may override to close sockets
75 /// or return the connection to a pool asynchronously.
76 fn disconnect(self) -> impl Future<Output = Result<()>> {
77 future::ready(Ok(()))
78 }
79}