sqlx_core_oldapi/migrate/
source.rs

1use crate::error::BoxDynError;
2use crate::migrate::{Migration, MigrationType};
3use futures_core::future::BoxFuture;
4use futures_util::TryStreamExt;
5use sqlx_rt::fs;
6use std::borrow::Cow;
7use std::fmt::Debug;
8use std::path::{Path, PathBuf};
9
10pub trait MigrationSource<'s>: Debug {
11    fn resolve(self) -> BoxFuture<'s, Result<Vec<Migration>, BoxDynError>>;
12}
13
14/// Implementation of the `MigrationSource` for [std::path::Path].
15///
16/// The path has to point to a directory, which contains the migration SQL scripts. All these
17/// scripts must be stored in files with names using the format `<VERSION>_<DESCRIPTION>.sql`,
18/// where `<VERSION>` is a string that can be parsed into `i64` and its value is greater than zero,
19/// and `<DESCRIPTION>` is a string.
20impl<'s> MigrationSource<'s> for &'s Path {
21    fn resolve(self) -> BoxFuture<'s, Result<Vec<Migration>, BoxDynError>> {
22        Box::pin(async move {
23            #[allow(unused_mut)]
24            let mut s = fs::read_dir(self.canonicalize()?).await?;
25            let mut migrations = Vec::new();
26
27            #[cfg(feature = "_rt-tokio")]
28            let mut s = tokio_stream::wrappers::ReadDirStream::new(s);
29
30            while let Some(entry) = s.try_next().await? {
31                if !entry.metadata().await?.is_file() {
32                    // not a file; ignore
33                    continue;
34                }
35
36                let file_name = entry.file_name();
37                let file_name = file_name.to_string_lossy();
38
39                let parts = file_name.splitn(2, '_').collect::<Vec<_>>();
40
41                if parts.len() != 2 || !parts[1].ends_with(".sql") {
42                    // not of the format: <VERSION>_<DESCRIPTION>.sql; ignore
43                    continue;
44                }
45
46                let version: i64 = parts[0].parse()?;
47
48                let migration_type = MigrationType::from_filename(parts[1]);
49                // remove the `.sql` and replace `_` with ` `
50                let description = parts[1]
51                    .trim_end_matches(migration_type.suffix())
52                    .replace('_', " ")
53                    .to_owned();
54
55                let sql = fs::read_to_string(&entry.path()).await?;
56
57                migrations.push(Migration::new(
58                    version,
59                    Cow::Owned(description),
60                    migration_type,
61                    Cow::Owned(sql),
62                ));
63            }
64
65            // ensure that we are sorted by `VERSION ASC`
66            migrations.sort_by_key(|m| m.version);
67
68            Ok(migrations)
69        })
70    }
71}
72
73impl MigrationSource<'static> for PathBuf {
74    fn resolve(self) -> BoxFuture<'static, Result<Vec<Migration>, BoxDynError>> {
75        Box::pin(async move { self.as_path().resolve().await })
76    }
77}