sqlint/single.rs
1//! A single connection abstraction to a SQL database.
2
3#[cfg(feature = "sqlite")]
4use crate::connector::DEFAULT_SQLITE_SCHEMA_NAME;
5use crate::{
6 ast,
7 connector::{self, ConnectionInfo, IsolationLevel, Queryable, TransactionCapable},
8};
9use async_trait::async_trait;
10use std::{fmt, sync::Arc};
11
12#[cfg(feature = "sqlite")]
13use std::convert::TryFrom;
14
15/// The main entry point and an abstraction over a database connection.
16#[derive(Clone)]
17pub struct Sqlint {
18 inner: Arc<dyn Queryable>,
19 connection_info: Arc<ConnectionInfo>,
20}
21
22impl fmt::Debug for Sqlint {
23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24 write!(f, "{:?}", self.connection_info)
25 }
26}
27
28impl TransactionCapable for Sqlint {}
29
30impl Sqlint {
31 /// Create a new connection to the database. The connection string
32 /// follows the specified format:
33 ///
34 /// `connector_type://user:password@host/database?parameters`
35 ///
36 /// Connector type can be one of the following:
37 ///
38 /// - `file` opens an SQLite connection
39 /// - `mysql` opens a MySQL connection
40 /// - `postgres`/`postgresql` opens a PostgreSQL connection
41 ///
42 /// All parameters should be given in the query string format:
43 /// `?key1=val1&key2=val2`. All parameters are optional.
44 ///
45 /// As a special case, Microsoft SQL Server connections use the JDBC URI
46 /// format:
47 ///
48 /// `jdbc:sqlserver://host\instance:port;key1=val1;key2=val2;`
49 ///
50 /// SQLite:
51 ///
52 /// - `user`/`password` do not do anything and can be emitted.
53 /// - `host` should point to the database file.
54 /// - `db_name` parameter should give a name to the database attached for
55 /// query namespacing.
56 /// - `socket_timeout` defined in seconds. Acts as the busy timeout in
57 /// SQLite. When set, queries that are waiting for a lock to be released
58 /// will return the `Timeout` error after the defined value.
59 ///
60 /// PostgreSQL:
61 ///
62 /// - `sslmode` either `disable`, `prefer` or `require`. [Read more](https://docs.rs/tokio-postgres/0.5.0-alpha.1/tokio_postgres/config/enum.SslMode.html)
63 /// - `sslcert` should point to a PEM certificate file.
64 /// - `sslidentity` should point to a PKCS12 certificate database.
65 /// - `sslpassword` the password to open the PKCS12 database.
66 /// - `sslaccept` either `strict` or `accept_invalid_certs`. If strict, the
67 /// certificate needs to be valid and in the CA certificates.
68 /// `accept_invalid_certs` accepts any certificate from the server and can
69 /// lead to weakened security. Defaults to `strict`.
70 /// - `schema` the default search path.
71 /// - `host` additionally the host can be given as a parameter, typically in
72 /// cases when connectiong to the database through a unix socket to
73 /// separate the database name from the database path, such as
74 /// `postgresql:///dbname?host=/var/run/postgresql`.
75 /// - `socket_timeout` defined in seconds. If set, a query will return a
76 /// `Timeout` error if it fails to resolve before given time.
77 /// - `connect_timeout` defined in seconds (default: 5). Connecting to a
78 /// database will return a `ConnectTimeout` error if taking more than the
79 /// defined value.
80 /// - `pgbouncer` either `true` or `false`. If set, allows usage with the
81 /// pgBouncer connection pool in transaction mode. Additionally a transaction
82 /// is required for every query for the mode to work. When starting a new
83 /// transaction, a deallocation query `DEALLOCATE ALL` is executed right after
84 /// `BEGIN` to avoid possible collisions with statements created in other
85 /// sessions.
86 /// - `statement_cache_size`, number of prepared statements kept cached.
87 /// Defaults to 500, which means caching is off. If `pgbouncer` mode is enabled,
88 /// caching is always off.
89 /// - `options` Specifies command-line options to send to the server at connection start. [Read more](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-OPTIONS)
90 ///
91 /// MySQL:
92 ///
93 /// - `sslcert` should point to a PEM certificate file.
94 /// - `sslidentity` should point to a PKCS12 certificate database.
95 /// - `sslpassword` the password to open the PKCS12 database.
96 /// - `sslaccept` either `strict` or `accept_invalid_certs`. If strict, the
97 /// certificate needs to be valid and in the CA certificates.
98 /// `accept_invalid_certs` accepts any certificate from the server and can
99 /// lead to weakened security. Defaults to `strict`.
100 /// - `socket` needed when connecting to MySQL database through a unix
101 /// socket. When set, the host parameter is dismissed.
102 /// - `socket_timeout` defined in seconds. If set, a query will return a
103 /// `Timeout` error if it fails to resolve before given time.
104 /// - `connect_timeout` defined in seconds (default: 5). Connecting to a
105 /// database will return a `ConnectTimeout` error if taking more than the
106 /// defined value.
107 ///
108 /// Microsoft SQL Server:
109 ///
110 /// - `encrypt` if set to `true` encrypts all traffic over TLS. If `false`, only
111 /// the login details are encrypted. A special value `DANGER_PLAINTEXT` will
112 /// disable TLS completely, including sending login credentials as plaintext.
113 /// - `user` sets the login name.
114 /// - `password` sets the login password.
115 /// - `database` sets the database to connect to.
116 /// - `trustServerCertificate` if set to `true`, accepts any kind of certificate
117 /// from the server.
118 /// - `socketTimeout` defined in seconds. If set, a query will return a
119 /// `Timeout` error if it fails to resolve before given time.
120 /// - `connectTimeout` defined in seconds (default: 5). Connecting to a
121 /// database will return a `ConnectTimeout` error if taking more than the
122 /// defined value.
123 /// - `connectionLimit` defines the maximum number of connections opened to the
124 /// database.
125 /// - `schema` the name of the lookup schema. Only stored to the connection,
126 /// must be used in every query to be effective.
127 /// - `isolationLevel` the transaction isolation level. Possible values:
128 /// `READ UNCOMMITTED`, `READ COMMITTED`, `REPEATABLE READ`, `SNAPSHOT`,
129 /// `SERIALIZABLE`.
130 #[allow(unreachable_code)]
131 pub async fn new(url_str: &str) -> crate::Result<Self> {
132 let inner = match url_str {
133 #[cfg(feature = "sqlite")]
134 s if s.starts_with("file") => {
135 let params = connector::SqliteParams::try_from(s)?;
136 let sqlite = connector::Sqlite::new(¶ms.file_path)?;
137
138 Arc::new(sqlite) as Arc<dyn Queryable>
139 }
140 #[cfg(feature = "mysql")]
141 s if s.starts_with("mysql") => {
142 let url = connector::MysqlUrl::new(url::Url::parse(s)?)?;
143 let mysql = connector::Mysql::new(url).await?;
144
145 Arc::new(mysql) as Arc<dyn Queryable>
146 }
147 #[cfg(feature = "postgresql")]
148 s if s.starts_with("postgres") || s.starts_with("postgresql") => {
149 let url = connector::PostgresUrl::new(url::Url::parse(s)?)?;
150 let psql = connector::PostgreSql::new(url).await?;
151 Arc::new(psql) as Arc<dyn Queryable>
152 }
153 #[cfg(feature = "mssql")]
154 s if s.starts_with("jdbc:sqlserver") | s.starts_with("sqlserver") => {
155 let url = connector::MssqlUrl::new(s)?;
156 let psql = connector::Mssql::new(url).await?;
157
158 Arc::new(psql) as Arc<dyn Queryable>
159 }
160 _ => unimplemented!("Supported url schemes: file or sqlite, mysql, postgresql or jdbc:sqlserver."),
161 };
162
163 let connection_info = Arc::new(ConnectionInfo::from_url(url_str)?);
164 Self::log_start(&connection_info);
165
166 Ok(Self { inner, connection_info })
167 }
168
169 #[cfg(feature = "sqlite")]
170 #[cfg_attr(feature = "docs", doc(cfg(sqlite)))]
171 /// Open a new SQLite database in memory.
172 pub fn new_in_memory() -> crate::Result<Sqlint> {
173 Ok(Sqlint {
174 inner: Arc::new(connector::Sqlite::new_in_memory()?),
175 connection_info: Arc::new(ConnectionInfo::InMemorySqlite {
176 db_name: DEFAULT_SQLITE_SCHEMA_NAME.to_owned(),
177 }),
178 })
179 }
180
181 /// Info about the connection and underlying database.
182 pub fn connection_info(&self) -> &ConnectionInfo {
183 &self.connection_info
184 }
185
186 fn log_start(info: &ConnectionInfo) {
187 let family = info.sql_family();
188 let pg_bouncer = if info.pg_bouncer() { " in PgBouncer mode" } else { "" };
189
190 tracing::info!("Starting a {} connection{}.", family, pg_bouncer);
191 }
192}
193
194#[async_trait]
195impl Queryable for Sqlint {
196 async fn query(&self, q: ast::Query<'_>) -> crate::Result<connector::ResultSet> {
197 self.inner.query(q).await
198 }
199
200 async fn query_raw(&self, sql: &str, params: &[ast::Value<'_>]) -> crate::Result<connector::ResultSet> {
201 self.inner.query_raw(sql, params).await
202 }
203
204 async fn query_raw_typed(&self, sql: &str, params: &[ast::Value<'_>]) -> crate::Result<connector::ResultSet> {
205 self.inner.query_raw_typed(sql, params).await
206 }
207
208 async fn execute(&self, q: ast::Query<'_>) -> crate::Result<u64> {
209 self.inner.execute(q).await
210 }
211
212 async fn execute_raw(&self, sql: &str, params: &[ast::Value<'_>]) -> crate::Result<u64> {
213 self.inner.execute_raw(sql, params).await
214 }
215
216 async fn execute_raw_typed(&self, sql: &str, params: &[ast::Value<'_>]) -> crate::Result<u64> {
217 self.inner.execute_raw_typed(sql, params).await
218 }
219
220 async fn raw_cmd(&self, cmd: &str) -> crate::Result<()> {
221 self.inner.raw_cmd(cmd).await
222 }
223
224 async fn version(&self) -> crate::Result<Option<String>> {
225 self.inner.version().await
226 }
227
228 fn is_healthy(&self) -> bool {
229 self.inner.is_healthy()
230 }
231
232 fn begin_statement(&self) -> &'static str {
233 self.inner.begin_statement()
234 }
235
236 async fn set_tx_isolation_level(&self, isolation_level: IsolationLevel) -> crate::Result<()> {
237 self.inner.set_tx_isolation_level(isolation_level).await
238 }
239
240 fn requires_isolation_first(&self) -> bool {
241 self.inner.requires_isolation_first()
242 }
243}