quaint/single.rs
1//! A single connection abstraction to a SQL database.
2
3use crate::{
4 ast,
5 connector::{self, ConnectionInfo, Queryable, SqlFamily, TransactionCapable, DBIO},
6};
7use futures::lock::Mutex;
8use url::Url;
9use std::sync::Arc;
10
11#[cfg(feature = "sqlite")]
12use std::convert::TryFrom;
13
14/// The main entry point and an abstraction over a database connection.
15#[derive(Clone)]
16pub struct Quaint {
17 inner: Arc<Mutex<Box<dyn Queryable + Send + Sync>>>,
18 connection_info: Arc<ConnectionInfo>,
19}
20
21impl TransactionCapable for Quaint {}
22
23impl Quaint {
24 /// Create a new connection to the database. The connection string
25 /// follows the specified format:
26 ///
27 /// `connector_type://user:password@host/database?parameters`
28 ///
29 /// Connector type can be one of the following:
30 ///
31 /// - `sqlite`/`file` opens an SQLite connection
32 /// - `mysql` opens a MySQL connection
33 /// - `postgres`/`postgresql` opens a PostgreSQL connection
34 ///
35 /// All parameters should be given in the query string format:
36 /// `?key1=val1&key2=val2`. All parameters are optional.
37 ///
38 /// SQLite:
39 ///
40 /// - `user`/`password` do not do anything and can be emitted.
41 /// - `host` should point to the database file.
42 /// - `db_name` parameter should give a name to the database attached for
43 /// query namespacing.
44 /// - `socket_timeout` defined in seconds. Acts as the busy timeout in
45 /// SQLite. When set, queries that are waiting for a lock to be released
46 /// will return the `Timeout` error after the defined value.
47 ///
48 /// PostgreSQL:
49 ///
50 /// - `sslmode` either `disable`, `prefer` or `require`. [Read more](https://docs.rs/tokio-postgres/0.5.0-alpha.1/tokio_postgres/config/enum.SslMode.html)
51 /// - `sslcert` should point to a PEM certificate file.
52 /// - `sslidentity` should point to a PKCS12 certificate database.
53 /// - `sslpassword` the password to open the PKCS12 database.
54 /// - `sslaccept` either `strict` or `accept_invalid_certs`. If strict, the
55 /// certificate needs to be valid and in the CA certificates.
56 /// `accept_invalid_certs` accepts any certificate from the server and can
57 /// lead to weakened security. Defaults to `strict`.
58 /// - `schema` the default search path.
59 /// - `host` additionally the host can be given as a parameter, typically in
60 /// cases when connectiong to the database through a unix socket to
61 /// separate the database name from the database path, such as
62 /// `postgresql:///dbname?host=/var/run/postgresql`.
63 /// - `socket_timeout` defined in seconds. If set, a query will return a
64 /// `Timeout` error if it fails to resolve before given time.
65 /// - `connect_timeout` defined in seconds (default: 5). Connecting to a
66 /// database will return a `ConnectTimeout` error if taking more than the
67 /// defined value.
68 ///
69 /// MySQL:
70 ///
71 /// - `sslcert` should point to a PEM certificate file.
72 /// - `sslidentity` should point to a PKCS12 certificate database.
73 /// - `sslpassword` the password to open the PKCS12 database.
74 /// - `sslaccept` either `strict` or `accept_invalid_certs`. If strict, the
75 /// certificate needs to be valid and in the CA certificates.
76 /// `accept_invalid_certs` accepts any certificate from the server and can
77 /// lead to weakened security. Defaults to `strict`.
78 /// - `socket` needed when connecting to MySQL database through a unix
79 /// socket. When set, the host parameter is dismissed.
80 /// - `socket_timeout` defined in seconds. If set, a query will return a
81 /// `Timeout` error if it fails to resolve before given time.
82 /// - `connect_timeout` defined in seconds (default: 5). Connecting to a
83 /// database will return a `ConnectTimeout` error if taking more than the
84 /// defined value.
85 pub async fn new(url_str: &str) -> crate::Result<Self> {
86 let url = Url::parse(url_str)?;
87
88 let inner = match url.scheme() {
89 #[cfg(feature = "sqlite")]
90 "file" | "sqlite" => {
91 let params = connector::SqliteParams::try_from(url_str)?;
92 let mut sqlite = connector::Sqlite::new(¶ms.file_path)?;
93
94 sqlite.attach_database(¶ms.db_name)?;
95
96 Mutex::new(Box::new(sqlite) as Box<dyn Queryable + Send + Sync>)
97 }
98 #[cfg(feature = "mysql")]
99 "mysql" => {
100 let url = connector::MysqlUrl::new(url)?;
101 let mysql = connector::Mysql::new(url)?;
102
103 Mutex::new(Box::new(mysql) as Box<dyn Queryable + Send + Sync>)
104 }
105 #[cfg(feature = "postgresql")]
106 "postgres" | "postgresql" => {
107 let url = connector::PostgresUrl::new(url)?;
108 let psql = connector::PostgreSql::new(url).await?;
109
110 Mutex::new(Box::new(psql) as Box<dyn Queryable + Send + Sync>)
111 }
112 _ => unimplemented!("Supported url schemes: file or sqlite, mysql, postgres or postgresql."),
113 };
114
115 let connection_info = Arc::new(ConnectionInfo::from_url(url_str)?);
116 Self::log_start(connection_info.sql_family(), 1);
117
118 let inner = Arc::new(inner);
119
120 Ok(Self { inner, connection_info })
121 }
122
123 /// Info about the connection and underlying database.
124 pub fn connection_info(&self) -> &ConnectionInfo {
125 &self.connection_info
126 }
127
128 fn log_start(family: SqlFamily, connection_limit: u32) {
129 #[cfg(not(feature = "tracing-log"))]
130 {
131 info!("Starting a {} pool with {} connections.", family, connection_limit);
132 }
133 #[cfg(feature = "tracing-log")]
134 {
135 tracing::info!("Starting a {} pool with {} connections.", family, connection_limit);
136 }
137 }
138}
139
140impl Queryable for Quaint {
141 fn execute<'a>(&'a self, q: ast::Query<'a>) -> DBIO<'a, Option<ast::Id>> {
142 DBIO::new(async move { self.inner.lock().await.execute(q).await })
143 }
144
145 fn query<'a>(&'a self, q: ast::Query<'a>) -> DBIO<'a, connector::ResultSet> {
146 DBIO::new(async move { self.inner.lock().await.query(q).await })
147 }
148
149 fn query_raw<'a>(&'a self, sql: &'a str, params: &'a [ast::ParameterizedValue]) -> DBIO<'a, connector::ResultSet> {
150 DBIO::new(async move { self.inner.lock().await.query_raw(sql, params).await })
151 }
152
153 fn execute_raw<'a>(&'a self, sql: &'a str, params: &'a [ast::ParameterizedValue]) -> DBIO<'a, u64> {
154 DBIO::new(async move { self.inner.lock().await.execute_raw(sql, params).await })
155 }
156
157 fn raw_cmd<'a>(&'a self, cmd: &'a str) -> DBIO<'a, ()> {
158 DBIO::new(async move { self.inner.lock().await.raw_cmd(cmd).await })
159 }
160}