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
21impl 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#[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 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#[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}