Skip to main content

refinery_core/drivers/
config.rs

1use crate::config::Config;
2use crate::traits::r#async::{AsyncQuery, AsyncTransaction};
3use crate::traits::sync::{Query, Transaction};
4use crate::Migration;
5#[cfg(any(
6    feature = "mysql",
7    feature = "postgres",
8    feature = "rusqlite",
9    feature = "tokio-postgres",
10    feature = "mysql_async",
11    feature = "tiberius-config"
12))]
13use crate::{
14    config::ConfigDbType,
15    error::WrapMigrationError,
16    traits::{GET_APPLIED_MIGRATIONS_QUERY, GET_LAST_APPLIED_MIGRATION_QUERY},
17    Error, Report, Target,
18};
19use std::convert::Infallible;
20
21// we impl all the dependent traits as noop's and then override the methods that call them on Migrate and AsyncMigrate
22impl Transaction for Config {
23    type Error = Infallible;
24
25    fn execute<'a, T: Iterator<Item = &'a str>>(
26        &mut self,
27        _queries: T,
28    ) -> Result<usize, Self::Error> {
29        Ok(0)
30    }
31}
32
33impl Query<Vec<Migration>> for Config {
34    fn query(&mut self, _query: &str) -> Result<Vec<Migration>, Self::Error> {
35        Ok(Vec::new())
36    }
37}
38
39impl AsyncTransaction for Config {
40    type Error = Infallible;
41
42    async fn execute<'a, T: Iterator<Item = &'a str> + Send + 'a>(
43        &mut self,
44        _queries: T,
45    ) -> Result<usize, Self::Error> {
46        Ok(0)
47    }
48}
49
50impl AsyncQuery<Vec<Migration>> for Config {
51    async fn query(
52        &mut self,
53        _query: &str,
54    ) -> Result<Vec<Migration>, <Self as AsyncTransaction>::Error> {
55        Ok(Vec::new())
56    }
57}
58// this is written as macro so that we don't have to deal with type signatures
59#[cfg(any(feature = "mysql", feature = "postgres", feature = "rusqlite"))]
60#[allow(clippy::redundant_closure_call)]
61macro_rules! with_connection {
62    ($config:ident, $op: expr) => {
63        #[allow(clippy::redundant_closure_call)]
64        match $config.db_type() {
65            ConfigDbType::Mysql => {
66                cfg_if::cfg_if! {
67                    if #[cfg(feature = "mysql")] {
68                        let url = crate::config::build_db_url("mysql", &$config);
69                        let opts = mysql::Opts::from_url(&url).migration_err("could not parse url", None)?;
70                        let conn = mysql::Conn::new(opts).migration_err("could not connect to database", None)?;
71                        $op(conn)
72                    } else {
73                        panic!("tried to migrate from config for a mysql database, but feature mysql not enabled!");
74                    }
75                }
76            }
77            ConfigDbType::Sqlite => {
78                cfg_if::cfg_if! {
79                    if #[cfg(feature = "rusqlite")] {
80                        //may have been checked earlier on config parsing, even if not let it fail with a Rusqlite db file not found error
81                        let path = $config.db_path().map(|p| p.to_path_buf()).unwrap_or_default();
82                        let conn = rusqlite::Connection::open_with_flags(path, rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE).migration_err("could not open database", None)?;
83                        $op(conn)
84                    } else {
85                        panic!("tried to migrate from config for a sqlite database, but feature rusqlite not enabled!");
86                    }
87                }
88            }
89            ConfigDbType::Postgres => {
90                cfg_if::cfg_if! {
91                    if #[cfg(feature = "postgres")] {
92                        let path = crate::config::build_db_url("postgresql", &$config);
93
94                        let conn;
95                        cfg_if::cfg_if! {
96                            if #[cfg(feature = "tls")] {
97                                if $config.use_tls() {
98                                    let connector = native_tls::TlsConnector::new().unwrap();
99                                    let connector = postgres_native_tls::MakeTlsConnector::new(connector);
100                                    conn = postgres::Client::connect(path.as_str(), connector).migration_err("could not connect to database", None)?;
101                                } else {
102                                    conn = postgres::Client::connect(path.as_str(), postgres::NoTls).migration_err("could not connect to database", None)?;
103                                }
104                            } else if #[cfg(feature = "tokio-postgres-rustls")] {
105                                if $config.use_tls() {
106                                    panic!("tokio-postgres-rustls only supports the async tokio-postgres driver; enable the 'tls' feature to use TLS with the sync postgres driver");
107                                }
108                                conn = postgres::Client::connect(path.as_str(), postgres::NoTls).migration_err("could not connect to database", None)?;
109                            } else {
110                                if $config.use_tls() {
111                                    panic!("TLS was requested but neither 'tls' nor 'tokio-postgres-rustls' feature is enabled in refinery-core");
112                                }
113                                conn = postgres::Client::connect(path.as_str(), postgres::NoTls).migration_err("could not connect to database", None)?;
114                            }
115                        }
116
117                        $op(conn)
118                    } else {
119                        panic!("tried to migrate from config for a postgresql database, but feature postgres not enabled!");
120                    }
121                }
122            }
123            ConfigDbType::Mssql => {
124                panic!("tried to synchronously migrate from config for a mssql database, but tiberius is an async driver");
125            }
126        }
127    }
128}
129
130#[cfg(any(
131    feature = "tokio-postgres",
132    feature = "mysql_async",
133    feature = "tiberius-config"
134))]
135macro_rules! with_connection_async {
136    ($config: ident, $op: expr) => {
137        #[allow(clippy::redundant_closure_call)]
138        match $config.db_type() {
139            ConfigDbType::Mysql => {
140                cfg_if::cfg_if! {
141                    if #[cfg(feature = "mysql_async")] {
142                        let url = crate::config::build_db_url("mysql", $config);
143                        let pool = mysql_async::Pool::from_url(&url).migration_err("could not connect to the database", None)?;
144                        $op(pool).await
145                    } else {
146                        panic!("tried to migrate async from config for a mysql database, but feature mysql_async not enabled!");
147                    }
148                }
149            }
150            ConfigDbType::Sqlite => {
151                panic!("tried to migrate async from config for a sqlite database, but this feature is not implemented yet");
152            }
153            ConfigDbType::Postgres => {
154                cfg_if::cfg_if! {
155                    if #[cfg(feature = "tokio-postgres")] {
156                        let path = crate::config::build_db_url("postgresql", $config);
157                        cfg_if::cfg_if! {
158                            if #[cfg(feature = "tls")] {
159                                if $config.use_tls() {
160                                    let connector = native_tls::TlsConnector::new().unwrap();
161                                    let connector = postgres_native_tls::MakeTlsConnector::new(connector);
162                                    let (client, connection) = tokio_postgres::connect(path.as_str(), connector).await.migration_err("could not connect to database", None)?;
163                                    tokio::spawn(async move {
164                                        if let Err(e) = connection.await {
165                                            eprintln!("connection error: {}", e);
166                                        }
167                                    });
168                                    $op(client).await
169                                } else {
170                                    let (client, connection) = tokio_postgres::connect(path.as_str(), tokio_postgres::NoTls).await.migration_err("could not connect to database", None)?;
171                                    tokio::spawn(async move {
172                                        if let Err(e) = connection.await {
173                                            eprintln!("connection error: {}", e);
174                                        }
175                                    });
176                                    $op(client).await
177                                }
178                            } else if #[cfg(feature = "tokio-postgres-rustls")] {
179                                if $config.use_tls() {
180                                    let native_certs = rustls_native_certs::load_native_certs();
181                                    for err in &native_certs.errors {
182                                        log::warn!("Failed to load native TLS certificate: {err}");
183                                    }
184                                    let mut root_store = rustls::RootCertStore::empty();
185                                    root_store.add_parsable_certificates(native_certs.certs);
186                                    let tls_config = rustls::ClientConfig::builder()
187                                        .with_root_certificates(root_store)
188                                        .with_no_client_auth();
189                                    let tls = tokio_postgres_rustls::MakeRustlsConnect::new(tls_config);
190                                    let (client, connection) = tokio_postgres::connect(path.as_str(), tls).await.migration_err("could not connect to database", None)?;
191                                    tokio::spawn(async move {
192                                        if let Err(e) = connection.await {
193                                            eprintln!("connection error: {}", e);
194                                        }
195                                    });
196                                    $op(client).await
197                                } else {
198                                    let (client, connection) = tokio_postgres::connect(path.as_str(), tokio_postgres::NoTls).await.migration_err("could not connect to database", None)?;
199                                    tokio::spawn(async move {
200                                        if let Err(e) = connection.await {
201                                            eprintln!("connection error: {}", e);
202                                        }
203                                    });
204                                    $op(client).await
205                                }
206                            } else {
207                                if $config.use_tls() {
208                                    panic!("TLS was requested but neither 'tls' nor 'tokio-postgres-rustls' feature is enabled in refinery-core");
209                                }
210                                let (client, connection) = tokio_postgres::connect(path.as_str(), tokio_postgres::NoTls).await.migration_err("could not connect to database", None)?;
211                                tokio::spawn(async move {
212                                    if let Err(e) = connection.await {
213                                        eprintln!("connection error: {}", e);
214                                    }
215                                });
216                                $op(client).await
217                            }
218                        }
219                    } else {
220                        panic!("tried to migrate async from config for a postgresql database, but tokio-postgres was not enabled!");
221                    }
222                }
223            }
224            ConfigDbType::Mssql => {
225                cfg_if::cfg_if! {
226                    if #[cfg(feature = "tiberius-config")] {
227                        use tiberius::{Client, Config};
228                        use tokio::net::TcpStream;
229                        use tokio_util::compat::TokioAsyncWriteCompatExt;
230                        use std::convert::TryInto;
231
232                        let config: Config = (&*$config).try_into()?;
233                        let tcp = TcpStream::connect(config.get_addr())
234                            .await
235                            .migration_err("could not connect to database", None)?;
236                        let client = Client::connect(config, tcp.compat_write())
237                            .await
238                            .migration_err("could not connect to database", None)?;
239
240                        $op(client).await
241                    } else {
242                        panic!("tried to migrate async from config for a mssql database, but tiberius-config feature was not enabled!");
243                    }
244                }
245            }
246        }
247    }
248}
249
250// rewrite all the default methods as we overrode Transaction and Query
251#[cfg(any(feature = "mysql", feature = "postgres", feature = "rusqlite"))]
252impl crate::Migrate for Config {
253    fn get_last_applied_migration(
254        &mut self,
255        migration_table_name: &str,
256    ) -> Result<Option<Migration>, Error> {
257        with_connection!(self, |mut conn| {
258            let mut migrations: Vec<Migration> = Query::query(
259                &mut conn,
260                &GET_LAST_APPLIED_MIGRATION_QUERY
261                    .replace("%MIGRATION_TABLE_NAME%", migration_table_name),
262            )
263            .migration_err("error getting last applied migration", None)?;
264
265            Ok(migrations.pop())
266        })
267    }
268
269    fn get_applied_migrations(
270        &mut self,
271        migration_table_name: &str,
272    ) -> Result<Vec<Migration>, Error> {
273        with_connection!(self, |mut conn| {
274            let migrations: Vec<Migration> = Query::query(
275                &mut conn,
276                &GET_APPLIED_MIGRATIONS_QUERY
277                    .replace("%MIGRATION_TABLE_NAME%", migration_table_name),
278            )
279            .migration_err("error getting applied migrations", None)?;
280
281            Ok(migrations)
282        })
283    }
284
285    fn migrate(
286        &mut self,
287        migrations: &[Migration],
288        abort_divergent: bool,
289        abort_missing: bool,
290        grouped: bool,
291        target: Target,
292        migration_table_name: &str,
293    ) -> Result<Report, Error> {
294        with_connection!(self, |mut conn| {
295            crate::Migrate::migrate(
296                &mut conn,
297                migrations,
298                abort_divergent,
299                abort_missing,
300                grouped,
301                target,
302                migration_table_name,
303            )
304        })
305    }
306}
307
308#[cfg(any(
309    feature = "mysql_async",
310    feature = "tokio-postgres",
311    feature = "tiberius-config"
312))]
313impl crate::AsyncMigrate for Config {
314    async fn get_last_applied_migration(
315        &mut self,
316        migration_table_name: &str,
317    ) -> Result<Option<Migration>, Error> {
318        with_connection_async!(self, move |mut conn| async move {
319            crate::AsyncMigrate::get_last_applied_migration(&mut conn, migration_table_name).await
320        })
321    }
322
323    async fn get_applied_migrations(
324        &mut self,
325        migration_table_name: &str,
326    ) -> Result<Vec<Migration>, Error> {
327        with_connection_async!(self, move |mut conn| async move {
328            crate::AsyncMigrate::get_applied_migrations(&mut conn, migration_table_name).await
329        })
330    }
331
332    async fn migrate(
333        &mut self,
334        migrations: &[Migration],
335        abort_divergent: bool,
336        abort_missing: bool,
337        grouped: bool,
338        target: Target,
339        migration_table_name: &str,
340    ) -> Result<Report, Error> {
341        with_connection_async!(self, move |mut conn| async move {
342            crate::AsyncMigrate::migrate(
343                &mut conn,
344                migrations,
345                abort_divergent,
346                abort_missing,
347                grouped,
348                target,
349                migration_table_name,
350            )
351            .await
352        })
353    }
354}