diff --git a/sqlx-core/Cargo.toml b/sqlx-core/Cargo.toml index 90ed446b4b..fb301ea44e 100644 --- a/sqlx-core/Cargo.toml +++ b/sqlx-core/Cargo.toml @@ -106,6 +106,7 @@ hashbrown = "0.16.0" thiserror.workspace = true [dev-dependencies] +tempfile = "3.10.1" tokio = { version = "1.25.0", features = ["rt"] } [dev-dependencies.sqlx] diff --git a/sqlx-core/src/migrate/source.rs b/sqlx-core/src/migrate/source.rs index 10fc7c7b8e..d07db2133c 100644 --- a/sqlx-core/src/migrate/source.rs +++ b/sqlx-core/src/migrate/source.rs @@ -16,7 +16,8 @@ use std::path::{Path, PathBuf}; /// `` is a string that can be parsed into `i64` and its value is /// greater than zero, and `` is a string. /// -/// Files that don't match this format are silently ignored. +/// Files that don't end in `.sql` are silently ignored. A `.sql` file that doesn't match this +/// format is an error, since it is almost certainly a migration that was named incorrectly. /// /// You can create a new empty migration script using sqlx-cli: /// `sqlx migrate add `. @@ -204,6 +205,18 @@ pub fn resolve_blocking_with_config( let parts = file_name.splitn(2, '_').collect::>(); if parts.len() != 2 || !parts[1].ends_with(".sql") { + if file_name.ends_with(".sql") { + // A `.sql` file that doesn't parse is almost certainly a migration that was + // named incorrectly, so erroring is more useful than silently skipping it. + return Err(ResolveError { + message: format!( + "error parsing migration filename {file_name:?}; \ + expected the format `_.sql` (e.g. `01_foo.sql`)" + ), + source: None, + }); + } + // not of the format: _..sql; ignore continue; } @@ -296,3 +309,31 @@ fn checksum_with_ignored_chars() { assert_eq!(digest_ignored, digest_stripped); } + +#[test] +fn resolve_errors_on_sql_file_without_version_prefix() { + let dir = tempfile::tempdir().unwrap(); + fs::write(dir.path().join("schema.sql"), "create table foo();").unwrap(); + + let error = resolve_blocking(dir.path()) + .expect_err("expected an error for a `.sql` file with no version prefix"); + + assert!( + error.to_string().contains("schema.sql"), + "error should name the offending file, got: {error}" + ); +} + +#[test] +fn resolve_ignores_files_not_ending_in_sql() { + let dir = tempfile::tempdir().unwrap(); + fs::write(dir.path().join("README.md"), "these are the migrations").unwrap(); + fs::write(dir.path().join(".gitkeep"), "").unwrap(); + fs::write(dir.path().join("1_foo.sql"), "create table foo();").unwrap(); + + let migrations = resolve_blocking(dir.path()).unwrap(); + + assert_eq!(migrations.len(), 1); + assert_eq!(migrations[0].0.version, 1); + assert_eq!(migrations[0].0.description, "foo"); +}