sqlx_core/migrate/
source.rs1use crate::error::BoxDynError;
2use crate::migrate::{Migration, MigrationType};
3use futures_core::future::BoxFuture;
4
5use std::borrow::Cow;
6use std::fmt::Debug;
7use std::fs;
8use std::io;
9use std::path::{Path, PathBuf};
10
11pub trait MigrationSource<'s>: Debug {
26 fn resolve(self) -> BoxFuture<'s, Result<Vec<Migration>, BoxDynError>>;
27}
28
29impl<'s> MigrationSource<'s> for &'s Path {
30 fn resolve(self) -> BoxFuture<'s, Result<Vec<Migration>, BoxDynError>> {
31 Box::pin(async move {
32 let canonical = self.canonicalize()?;
33 let migrations_with_paths =
34 crate::rt::spawn_blocking(move || resolve_blocking(&canonical)).await?;
35
36 Ok(migrations_with_paths.into_iter().map(|(m, _p)| m).collect())
37 })
38 }
39}
40
41impl MigrationSource<'static> for PathBuf {
42 fn resolve(self) -> BoxFuture<'static, Result<Vec<Migration>, BoxDynError>> {
43 Box::pin(async move { self.as_path().resolve().await })
44 }
45}
46
47#[derive(thiserror::Error, Debug)]
48#[error("{message}")]
49pub struct ResolveError {
50 message: String,
51 #[source]
52 source: Option<io::Error>,
53}
54
55pub fn resolve_blocking(path: &Path) -> Result<Vec<(Migration, PathBuf)>, ResolveError> {
58 let s = fs::read_dir(path).map_err(|e| ResolveError {
59 message: format!("error reading migration directory {}: {e}", path.display()),
60 source: Some(e),
61 })?;
62
63 let mut migrations = Vec::new();
64
65 for res in s {
66 let entry = res.map_err(|e| ResolveError {
67 message: format!(
68 "error reading contents of migration directory {}: {e}",
69 path.display()
70 ),
71 source: Some(e),
72 })?;
73
74 let entry_path = entry.path();
75
76 let metadata = fs::metadata(&entry_path).map_err(|e| ResolveError {
77 message: format!(
78 "error getting metadata of migration path {}",
79 entry_path.display()
80 ),
81 source: Some(e),
82 })?;
83
84 if !metadata.is_file() {
85 continue;
87 }
88
89 let file_name = entry.file_name();
90 let file_name = file_name.to_string_lossy();
96
97 let parts = file_name.splitn(2, '_').collect::<Vec<_>>();
98
99 if parts.len() != 2 || !parts[1].ends_with(".sql") {
100 continue;
102 }
103
104 let version: i64 = parts[0].parse()
105 .map_err(|_e| ResolveError {
106 message: format!("error parsing migration filename {file_name:?}; expected integer version prefix (e.g. `01_foo.sql`)"),
107 source: None,
108 })?;
109
110 let migration_type = MigrationType::from_filename(parts[1]);
111
112 let description = parts[1]
114 .trim_end_matches(migration_type.suffix())
115 .replace('_', " ")
116 .to_owned();
117
118 let sql = fs::read_to_string(&entry_path).map_err(|e| ResolveError {
119 message: format!(
120 "error reading contents of migration {}: {e}",
121 entry_path.display()
122 ),
123 source: Some(e),
124 })?;
125
126 let no_tx = sql.starts_with("-- no-transaction");
128
129 migrations.push((
130 Migration::new(
131 version,
132 Cow::Owned(description),
133 migration_type,
134 Cow::Owned(sql),
135 no_tx,
136 ),
137 entry_path,
138 ));
139 }
140
141 migrations.sort_by_key(|(m, _)| m.version);
143
144 Ok(migrations)
145}