diesel_migrations/
file_based_migrations.rs1use std::fmt::Display;
2use std::fs::{DirEntry, File};
3use std::io::Read;
4use std::path::{Path, PathBuf};
5
6use diesel::backend::Backend;
7use diesel::connection::BoxableConnection;
8use diesel::migration::{
9 self, Migration, MigrationMetadata, MigrationName, MigrationSource, MigrationVersion,
10};
11use migrations_internals::TomlMetadata;
12
13use crate::errors::{MigrationError, RunMigrationsError};
14
15#[derive(#[automatically_derived]
impl ::core::clone::Clone for FileBasedMigrations {
#[inline]
fn clone(&self) -> FileBasedMigrations {
FileBasedMigrations {
base_path: ::core::clone::Clone::clone(&self.base_path),
}
}
}Clone)]
95pub struct FileBasedMigrations {
96 base_path: PathBuf,
97}
98
99impl FileBasedMigrations {
100 pub fn from_path(path: impl AsRef<Path>) -> Result<Self, MigrationError> {
104 for dir in migrations_directories(path.as_ref())? {
105 let path = dir?.path();
106 if !migrations_internals::valid_sql_migration_directory(&path) {
107 return Err(MigrationError::UnknownMigrationFormat(path));
108 }
109 }
110 Ok(Self {
111 base_path: path.as_ref().to_path_buf(),
112 })
113 }
114
115 pub fn find_migrations_directory() -> Result<Self, MigrationError> {
122 Self::find_migrations_directory_in_path(std::env::current_dir()?.as_path())
123 }
124
125 pub fn find_migrations_directory_in_path(
133 path: impl AsRef<Path>,
134 ) -> Result<Self, MigrationError> {
135 let migrations_directory = search_for_migrations_directory(path.as_ref())?;
136 Self::from_path(migrations_directory.as_path())
137 }
138
139 #[doc(hidden)]
140 pub fn path(&self) -> &Path {
141 &self.base_path
142 }
143}
144
145fn search_for_migrations_directory(path: &Path) -> Result<PathBuf, MigrationError> {
146 migrations_internals::search_for_migrations_directory(path)
147 .ok_or_else(|| MigrationError::MigrationDirectoryNotFound(path.to_path_buf()))
148}
149
150fn migrations_directories(
151 path: &'_ Path,
152) -> Result<impl Iterator<Item = Result<DirEntry, MigrationError>> + '_, MigrationError> {
153 Ok(migrations_internals::migrations_directories(path)?.map(move |e| e.map_err(Into::into)))
154}
155
156fn migrations_in_directory(
157 path: &'_ Path,
158) -> Result<impl Iterator<Item = Result<SqlFileMigration, MigrationError>> + '_, MigrationError> {
159 Ok(migrations_directories(path)?.map(|entry| SqlFileMigration::from_path(&entry?.path())))
160}
161
162impl<DB: Backend> MigrationSource<DB> for FileBasedMigrations {
163 fn migrations(&self) -> migration::Result<Vec<Box<dyn Migration<DB>>>> {
164 migrations_in_directory(&self.base_path)?
165 .map(|r| Ok(Box::new(r?) as Box<dyn Migration<DB>>))
166 .collect()
167 }
168}
169
170struct SqlFileMigration {
171 base_path: PathBuf,
172 metadata: TomlMetadataWrapper,
173 name: DieselMigrationName,
174}
175
176impl SqlFileMigration {
177 fn from_path(path: &Path) -> Result<Self, MigrationError> {
178 if migrations_internals::valid_sql_migration_directory(path) {
179 let metadata = TomlMetadataWrapper(
180 TomlMetadata::read_from_file(&path.join("metadata.toml")).unwrap_or_default(),
181 );
182 Ok(Self {
183 base_path: path.to_path_buf(),
184 metadata,
185 name: DieselMigrationName::from_path(path)?,
186 })
187 } else {
188 Err(MigrationError::UnknownMigrationFormat(path.to_path_buf()))
189 }
190 }
191}
192
193impl<DB: Backend> Migration<DB> for SqlFileMigration {
194 fn run(&self, conn: &mut dyn BoxableConnection<DB>) -> migration::Result<()> {
195 Ok(run_sql_from_file(
196 conn,
197 &self.base_path.join("up.sql"),
198 &self.name,
199 )?)
200 }
201
202 fn revert(&self, conn: &mut dyn BoxableConnection<DB>) -> migration::Result<()> {
203 let down_path = self.base_path.join("down.sql");
204 if #[allow(non_exhaustive_omitted_patterns)] match down_path.metadata() {
Err(e) if e.kind() == std::io::ErrorKind::NotFound => true,
_ => false,
}matches!(down_path.metadata(), Err(e) if e.kind() == std::io::ErrorKind::NotFound) {
205 Err(MigrationError::NoMigrationRevertFile.into())
206 } else {
207 Ok(run_sql_from_file(conn, &down_path, &self.name)?)
208 }
209 }
210
211 fn metadata(&self) -> &dyn MigrationMetadata {
212 &self.metadata
213 }
214
215 fn name(&self) -> &dyn MigrationName {
216 &self.name
217 }
218}
219
220#[derive(#[automatically_derived]
impl ::core::fmt::Debug for DieselMigrationName {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field2_finish(f,
"DieselMigrationName", "name", &self.name, "version",
&&self.version)
}
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for DieselMigrationName {
#[inline]
fn eq(&self, other: &DieselMigrationName) -> bool {
self.name == other.name && self.version == other.version
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for DieselMigrationName {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<String>;
let _: ::core::cmp::AssertParamIsEq<MigrationVersion<'static>>;
}
}Eq)]
221pub struct DieselMigrationName {
222 name: String,
223 version: MigrationVersion<'static>,
224}
225
226impl Clone for DieselMigrationName {
227 fn clone(&self) -> Self {
228 Self {
229 name: self.name.clone(),
230 version: self.version.as_owned(),
231 }
232 }
233}
234
235impl DieselMigrationName {
236 fn from_path(path: &Path) -> Result<Self, MigrationError> {
237 let name = path
238 .file_name()
239 .ok_or_else(|| MigrationError::UnknownMigrationFormat(path.to_path_buf()))?
240 .to_string_lossy();
241 Self::from_name(&name)
242 }
243
244 pub(crate) fn from_name(name: &str) -> Result<Self, MigrationError> {
245 let version = migrations_internals::version_from_string(name)
246 .ok_or_else(|| MigrationError::UnknownMigrationFormat(PathBuf::from(name)))?;
247 Ok(Self {
248 name: name.to_owned(),
249 version: MigrationVersion::from(version),
250 })
251 }
252}
253
254impl MigrationName for DieselMigrationName {
255 fn version(&self) -> MigrationVersion<'_> {
256 self.version.as_owned()
257 }
258}
259
260impl Display for DieselMigrationName {
261 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
262 f.write_fmt(format_args!("{0}", self.name))write!(f, "{}", self.name)
263 }
264}
265
266#[derive(#[automatically_derived]
impl ::core::default::Default for TomlMetadataWrapper {
#[inline]
fn default() -> TomlMetadataWrapper {
TomlMetadataWrapper(::core::default::Default::default())
}
}Default)]
267#[doc(hidden)]
268pub struct TomlMetadataWrapper(TomlMetadata);
269
270impl TomlMetadataWrapper {
271 #[doc(hidden)]
272 pub const fn new(run_in_transaction: bool) -> Self {
273 Self(TomlMetadata::new(run_in_transaction))
274 }
275}
276
277impl MigrationMetadata for TomlMetadataWrapper {
278 fn run_in_transaction(&self) -> bool {
279 self.0.run_in_transaction
280 }
281}
282
283fn run_sql_from_file<DB: Backend>(
284 conn: &mut dyn BoxableConnection<DB>,
285 path: &Path,
286 name: &DieselMigrationName,
287) -> Result<(), RunMigrationsError> {
288 let map_io_err = |e| RunMigrationsError::MigrationError(name.clone(), MigrationError::from(e));
289
290 let mut sql = String::new();
291 let mut file = File::open(path).map_err(map_io_err)?;
292 file.read_to_string(&mut sql).map_err(map_io_err)?;
293
294 if sql.is_empty() {
295 return Err(RunMigrationsError::EmptyMigration(name.clone()));
296 }
297
298 conn.batch_execute(&sql)
299 .map_err(|e| RunMigrationsError::QueryError(name.clone(), e))?;
300 Ok(())
301}
302
303#[cfg(test)]
304mod tests {
305 extern crate tempfile;
306
307 use super::*;
308
309 use self::tempfile::Builder;
310 use std::fs;
311
312 #[test]
313 fn migration_directory_not_found_if_no_migration_dir_exists() {
314 let dir = Builder::new().prefix("diesel").tempdir().unwrap();
315
316 assert_eq!(
317 Err(MigrationError::MigrationDirectoryNotFound(
318 dir.path().into()
319 )),
320 search_for_migrations_directory(dir.path())
321 );
322 }
323
324 #[test]
325 fn migration_directory_defaults_to_pwd_slash_migrations() {
326 let dir = Builder::new().prefix("diesel").tempdir().unwrap();
327 let temp_path = dir.path().canonicalize().unwrap();
328 let migrations_path = temp_path.join("migrations");
329
330 fs::create_dir(&migrations_path).unwrap();
331
332 assert_eq!(
333 Ok(migrations_path),
334 search_for_migrations_directory(&temp_path)
335 );
336 }
337
338 #[test]
339 fn migration_directory_checks_parents() {
340 let dir = Builder::new().prefix("diesel").tempdir().unwrap();
341 let temp_path = dir.path().canonicalize().unwrap();
342 let migrations_path = temp_path.join("migrations");
343 let child_path = temp_path.join("child");
344
345 fs::create_dir(&child_path).unwrap();
346 fs::create_dir(&migrations_path).unwrap();
347
348 assert_eq!(
349 Ok(migrations_path),
350 search_for_migrations_directory(&child_path)
351 );
352 }
353
354 #[test]
355 fn migration_paths_in_directory_ignores_files() {
356 let dir = Builder::new().prefix("diesel").tempdir().unwrap();
357 let temp_path = dir.path().canonicalize().unwrap();
358 let migrations_path = temp_path.join("migrations");
359 let file_path = migrations_path.join("README.md");
360
361 fs::create_dir(migrations_path.as_path()).unwrap();
362 fs::File::create(file_path.as_path()).unwrap();
363
364 let migrations = migrations_in_directory(&migrations_path)
365 .unwrap()
366 .collect::<Result<Vec<_>, _>>()
367 .unwrap();
368
369 assert_eq!(0, migrations.len());
370 }
371
372 #[test]
373 fn migration_paths_in_directory_ignores_dot_directories() {
374 let dir = Builder::new().prefix("diesel").tempdir().unwrap();
375 let temp_path = dir.path().canonicalize().unwrap();
376 let migrations_path = temp_path.join("migrations");
377 let dot_path = migrations_path.join(".hidden_dir");
378
379 fs::create_dir(migrations_path.as_path()).unwrap();
380 fs::create_dir(dot_path.as_path()).unwrap();
381
382 let migrations = migrations_in_directory(&migrations_path)
383 .unwrap()
384 .collect::<Result<Vec<_>, _>>()
385 .unwrap();
386
387 assert_eq!(0, migrations.len());
388 }
389
390 #[test]
391 fn migration_paths_in_directory_ignores_empty_directories() {
392 let dir = Builder::new().prefix("diesel").tempdir().unwrap();
393 let temp_path = dir.path().canonicalize().unwrap();
394 let migrations_path = temp_path.join("migrations");
395 let empty_path = migrations_path.join("an_empty_migration_dir");
396
397 fs::create_dir(migrations_path.as_path()).unwrap();
398 fs::create_dir(empty_path.as_path()).unwrap();
399
400 let migrations = migrations_in_directory(&migrations_path)
401 .unwrap()
402 .collect::<Result<Vec<_>, _>>()
403 .unwrap();
404
405 assert_eq!(0, migrations.len());
406 }
407}