Skip to main content

rustlavel_db/
driver.rs

1//! The contract every database driver implements.
2//!
3//! Above this line the framework is one codebase: the query builder, the schema
4//! builder, the migrator and the ORM are written once. Below it sits a wire
5//! protocol per database, each written from scratch.
6//!
7//! Both traits hand back boxed futures rather than using `async fn`, because a
8//! trait with `async fn` is not object-safe, and the pool has to hold a driver
9//! whose type it does not know.
10
11use crate::dialect::Dialect;
12use crate::row::Row;
13use crate::value::Value;
14use rustlavel_core::Result;
15use std::pin::Pin;
16use std::sync::Arc;
17
18/// A future returned from a trait method.
19pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
20
21/// What a statement returned.
22#[derive(Debug, Default)]
23pub struct QueryResult {
24    pub rows: Vec<Row>,
25    /// Rows affected, as the database reported it.
26    pub affected: u64,
27    /// The key an insert generated, when the database volunteered one.
28    ///
29    /// PostgreSQL and SQL Server return it as a row; MySQL reports it in the
30    /// packet that acknowledges the insert, with no row at all — which is why
31    /// this is separate from `rows`.
32    pub last_insert_id: Option<i64>,
33}
34
35/// One physical connection.
36pub trait DriverConnection: Send {
37    /// Run a statement with bound parameters.
38    ///
39    /// Parameters never enter the SQL text, whatever the database: that is what
40    /// makes injection structurally impossible rather than a matter of
41    /// remembering to escape.
42    fn query<'a>(
43        &'a mut self,
44        sql: &'a str,
45        params: &'a [Value],
46    ) -> BoxFuture<'a, Result<QueryResult>>;
47
48    /// Run a statement with no parameters — DDL, and transaction control.
49    fn simple_query<'a>(&'a mut self, sql: &'a str) -> BoxFuture<'a, Result<QueryResult>>;
50
51    /// Whether this connection is known to be unusable, so the pool discards it
52    /// instead of handing it to the next caller.
53    fn is_broken(&self) -> bool;
54
55    /// Whether a transaction is still open. A connection left inside one must
56    /// never go back into rotation.
57    fn in_transaction(&self) -> bool;
58
59    /// Say goodbye politely, then hang up.
60    fn close(self: Box<Self>) -> BoxFuture<'static, ()>;
61}
62
63/// Opens connections, and knows what dialect they speak.
64pub trait Driver: Send + Sync + 'static {
65    fn dialect(&self) -> Arc<dyn Dialect>;
66
67    fn connect(&self) -> BoxFuture<'_, Result<Box<dyn DriverConnection>>>;
68
69    /// How this connection is described in an error or a log line.
70    ///
71    /// Must never contain the password — it ends up in messages people paste
72    /// into issues.
73    fn describe(&self) -> String;
74
75    /// How many connections the pool should allow at once.
76    fn max_connections(&self) -> usize {
77        10
78    }
79
80    /// Which generation of credentials a connection opened now would belong to.
81    ///
82    /// Zero when the driver's credentials never change, which is the common
83    /// case and the one that costs nothing: the pool compares the number it
84    /// stored against this, and zero always equals zero.
85    fn generation(&self) -> u64 {
86        0
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use crate::dialect::Postgres;
94    use rustlavel_core::Error;
95
96    /// A driver that never connects, to prove the traits compose without a
97    /// database anywhere near them.
98    struct Offline;
99
100    impl Driver for Offline {
101        fn dialect(&self) -> Arc<dyn Dialect> {
102            Arc::new(Postgres)
103        }
104
105        fn connect(&self) -> BoxFuture<'_, Result<Box<dyn DriverConnection>>> {
106            Box::pin(async { Err(Error::msg("this driver never connects")) })
107        }
108
109        fn describe(&self) -> String {
110            "offline://nowhere".into()
111        }
112    }
113
114    #[tokio::test]
115    async fn a_driver_can_be_held_without_naming_its_type() {
116        let driver: Arc<dyn Driver> = Arc::new(Offline);
117
118        assert_eq!(driver.dialect().name(), "postgres");
119        assert_eq!(driver.describe(), "offline://nowhere");
120        assert_eq!(driver.max_connections(), 10);
121        assert!(driver.connect().await.is_err());
122    }
123}