Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## [1.0.0-rc.2]
### Added
- `apply` and `redo` accept `--no-sync` to disable the cross-process advisory lock, for when
migration runs are serialized by an external mechanism. On `redo` it applies to both the down
and up runs

### Changed
- Update `migrant_lib` to 1.0.0-rc.2: owned-`self` builder chains for the settings builders and
`Migrator`, `#[non_exhaustive]` `DbKind`/`ForceMode`, and `MigrationStatus` field accessors.
See the migrant_lib changelog. These are source-compatible for existing chained call sites

## [1.0.0-rc.1]
### Added
- `--force` takes an optional mode: bare `--force` (or `--force=accept-failures`) records a
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "migrant"
version = "1.0.0-rc.1"
version = "1.0.0-rc.2"
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
Expand All @@ -26,7 +26,7 @@ authors = ["James Kominick <james@kominick.com>"]
license = "MIT"

[workspace.dependencies]
migrant_lib = { path = "migrant_lib", version = "1.0.0-rc.1" }
migrant_lib = { path = "migrant_lib", version = "1.0.0-rc.2" }

# shared between the cli and the library
rusqlite = "0.40"
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ When run interactively (without `--no-confirm`), `setup` will be run automatical

`migrant list` - Display all available .sql files and mark those applied.

`migrant apply [--down, --all, --force, --fake]` - Apply the next available migration[s].
`migrant apply [--down, --all, --force, --fake, --no-sync]` - Apply the next available migration[s].

`migrant redo [--all, --force]` - Re-apply the latest migration (down then up).
`migrant redo [--all, --force, --no-sync]` - Re-apply the latest migration (down then up).

`migrant tui` - Open an interactive terminal UI for viewing and applying migrations.

Expand Down
10 changes: 7 additions & 3 deletions docs/src/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,21 @@ run from anywhere inside the project; migrant searches upward for the config.
`migrant list`
: List available migrations and mark those applied.

`migrant apply [--down] [--all] [--force[=<mode>]] [--fake]`
`migrant apply [--down] [--all] [--force[=<mode>]] [--fake] [--no-sync]`
: Apply the next migration. `--down` reverts instead of applying. `--all` runs
every remaining migration in the chosen direction. `--force` continues past a
failed migration: bare `--force` (or `--force=accept-failures`) records the
failed migration as applied anyway, so it is not retried on later runs;
`--force=skip-failures` leaves it unrecorded and retries it on the next run.
`--fake` records the migration as (un)applied without running its SQL.
`--no-sync` disables the cross-process advisory lock that is otherwise on by
default for PostgreSQL/MySQL; use it when migrations are already serialized
by an external mechanism.

`migrant redo [--all] [--force[=<mode>]]`
`migrant redo [--all] [--force[=<mode>]] [--no-sync]`
: Shortcut for the latest `down` then `up`. Useful while iterating on a migration
you are still writing.
you are still writing. `--no-sync` disables the advisory lock for both the
down and up runs.

## Inspect and connect

Expand Down
3 changes: 3 additions & 0 deletions docs/src/concurrency.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,7 @@ Migrator::with_config(&config)
.apply()?;
```

The `migrant` CLI exposes the same toggle as `--no-sync` on `apply` and `redo`,
for callers that serialize migrations externally; see [The CLI](cli.md).

See [Using migrant_lib](library.md) for the rest of the `Migrator` API.
17 changes: 17 additions & 0 deletions migrant_lib/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# Changelog

## [1.0.0-rc.2]
Breaking pre-1.0 release, continuing the API cleanup from rc.1.

### Changed
- Settings builders (`SqliteSettingsBuilder`/`PostgresSettingsBuilder`/`MySqlSettingsBuilder`) and
`SettingsFileInitializer` now use owned `self` builder chains: their setters take and return
`Self` instead of `&mut Self`, unifying them with the migration builders. `build(&self)` is
unchanged. `SettingsFileInitializer::with_sqlite_options`/`with_postgres_options`/
`with_mysql_options` now take the builder by value instead of by reference
- `Migrator` setters (`direction`/`force`/`fake`/`all`/`show_output`/`synchronized`) now take and
return owned `self`. `Migrator::with_config` and `Migrator::apply` are unchanged
- `MigrationStatus` fields `tag`/`applied` are now private; read them via the `tag()` and
`applied()` accessor methods
- `DbKind` and `ForceMode` are now `#[non_exhaustive]` (like `Error`), so a downstream `match`
must include a wildcard arm. This allows future backends and force modes to be added without a
breaking change

## [1.0.0-rc.1]
Breaking pre-1.0 release. See the "Changed"/"Removed" sections for migration notes.

Expand Down
2 changes: 1 addition & 1 deletion migrant_lib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "migrant_lib"
version = "1.0.0-rc.1"
version = "1.0.0-rc.2"
# explicit (not workspace-inherited) so `cargo readme` can parse the manifest
edition = "2021"
rust-version = "1.88"
Expand Down
8 changes: 4 additions & 4 deletions migrant_lib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
include `rusqlite` and enable the `bundled` feature in your project.


### Usage
## Usage

- Migrations can be defined as files, string literals, or functions.
- File migrations can be either read from files at runtime or embedded in your executable at compile time
Expand Down Expand Up @@ -76,7 +76,7 @@ config.use_migrations(&[
```


### In-memory sqlite databases
## In-memory sqlite databases

With the `sqlite` feature, the special database path `:memory:` selects an
in-memory sqlite database. The underlying connection is established once and
Expand All @@ -93,7 +93,7 @@ config.setup()?;
```


### CLI Compatibility
## CLI Compatibility

Migration management identical to the [`migrant`](https://github.com/jaemk/migrant) CLI tool can also be embedded.
This method only supports file-based migrations (so `FileMigration`s or `EmbeddedMigration`s using `include_str!`)
Expand All @@ -110,7 +110,7 @@ example for a working sample where the `migrant` CLI tool can be used during dev
and migration file contents are embedded in the application.


### Development
## Development

See [CONTRIBUTING](https://github.com/jaemk/migrant/blob/main/migrant_lib/CONTRIBUTING.md)

Expand Down
110 changes: 99 additions & 11 deletions migrant_lib/src/config/builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ impl SqliteSettingsBuilder {
/// `Config::init_in(...).with_sqlite_options(...)`, a relative path is also accepted --
/// it is written into the generated settings file and resolved relative to that
/// settings file's directory when the config is later loaded.
pub fn database_path<T: AsRef<Path>>(&mut self, p: T) -> Result<&mut Self> {
pub fn database_path<T: AsRef<Path>>(mut self, p: T) -> Result<Self> {
self.database_path = Some(path_to_string(p.as_ref())?);
Ok(self)
}
Expand All @@ -48,7 +48,7 @@ impl SqliteSettingsBuilder {
/// The database connection is established once and then kept alive
/// (shared by all clones of the built `Config`) so migrations and
/// application queries all see the same database.
pub fn memory(&mut self) -> &mut Self {
pub fn memory(mut self) -> Self {
self.database_path = Some(SQLITE_MEMORY_PATH.to_string());
self
}
Expand All @@ -58,7 +58,7 @@ impl SqliteSettingsBuilder {
/// This can be an absolute or relative path. An absolute path should be preferred.
/// If a relative path is provided, the path will be assumed relative to either the
/// settings file's directory if a settings file exists, or the current directory.
pub fn migration_location<T: AsRef<Path>>(&mut self, p: T) -> Result<&mut Self> {
pub fn migration_location<T: AsRef<Path>>(mut self, p: T) -> Result<Self> {
self.migration_location = Some(path_to_string(p.as_ref())?);
Ok(self)
}
Expand Down Expand Up @@ -132,37 +132,37 @@ impl ServerSettingsBuilder {
macro_rules! server_builder_methods {
() => {
/// **Required** -- Set the database name.
pub fn database_name(&mut self, name: &str) -> &mut Self {
pub fn database_name(mut self, name: &str) -> Self {
self.inner.database_name = Some(name.into());
self
}

/// **Required** -- Set the database user.
pub fn database_user(&mut self, user: &str) -> &mut Self {
pub fn database_user(mut self, user: &str) -> Self {
self.inner.database_user = Some(user.into());
self
}

/// **Required** -- Set the database password.
pub fn database_password(&mut self, pass: &str) -> &mut Self {
pub fn database_password(mut self, pass: &str) -> Self {
self.inner.database_password = Some(pass.into());
self
}

/// Set the database host.
pub fn database_host(&mut self, host: &str) -> &mut Self {
pub fn database_host(mut self, host: &str) -> Self {
self.inner.database_host = Some(host.into());
self
}

/// Set the database port.
pub fn database_port(&mut self, port: u16) -> &mut Self {
pub fn database_port(mut self, port: u16) -> Self {
self.inner.database_port = Some(port.to_string());
self
}

/// Set a collection of database connection parameters.
pub fn database_params(&mut self, params: &[(&str, &str)]) -> &mut Self {
pub fn database_params(mut self, params: &[(&str, &str)]) -> Self {
self.inner.set_params(params);
self
}
Expand All @@ -172,7 +172,7 @@ macro_rules! server_builder_methods {
/// This can be an absolute or relative path. An absolute path should be preferred.
/// If a relative path is provided, the path will be assumed relative to either the
/// settings file's directory if a settings file exists, or the current directory.
pub fn migration_location<T: AsRef<Path>>(&mut self, p: T) -> Result<&mut Self> {
pub fn migration_location<T: AsRef<Path>>(mut self, p: T) -> Result<Self> {
self.inner.migration_location = Some(path_to_string(p.as_ref())?);
Ok(self)
}
Expand All @@ -194,7 +194,7 @@ impl PostgresSettingsBuilder {
server_builder_methods!();

/// Set a custom ssl cert file
pub fn ssl_cert_file<P: AsRef<Path>>(&mut self, file: P) -> &mut Self {
pub fn ssl_cert_file<P: AsRef<Path>>(mut self, file: P) -> Self {
self.inner.ssl_cert_file = Some(file.as_ref().to_path_buf());
self
}
Expand Down Expand Up @@ -224,3 +224,91 @@ impl MySqlSettingsBuilder {
Ok(Settings::new(DbSettings::MySql(self.inner.build()?)))
}
}

#[cfg(test)]
mod tests {
use super::*;

// The owned (`self -> Self` / `self -> Result<Self>`) setters must chain
// without an intermediate `mut` binding and carry every value through.

#[test]
fn sqlite_owned_setters_chain_and_build() {
let settings = SqliteSettingsBuilder::empty()
.database_path("/abs/path/to/my.db")
.unwrap()
.migration_location("/abs/migrations")
.unwrap()
.build()
.unwrap();
match settings.inner {
DbSettings::Sqlite(s) => {
assert_eq!(s.database_path, "/abs/path/to/my.db");
assert_eq!(s.migration_location.as_deref(), Some("/abs/migrations"));
}
other => panic!("expected sqlite settings, got {:?}", other),
}
}

#[test]
fn sqlite_memory_owned_setter_chains() {
// `memory()` consumes and returns owned self, chainable into `build()`.
let builder = SqliteSettingsBuilder::empty().memory();
assert_eq!(builder.database_path.as_deref(), Some(SQLITE_MEMORY_PATH));
let settings = builder.build().unwrap();
match settings.inner {
DbSettings::Sqlite(s) => assert_eq!(s.database_path, SQLITE_MEMORY_PATH),
other => panic!("expected sqlite settings, got {:?}", other),
}
}

#[test]
fn postgres_owned_server_setters_chain_and_build() {
let settings = PostgresSettingsBuilder::empty()
.database_name("mydb")
.database_user("me")
.database_password("secret")
.database_host("db.example.com")
.database_port(4444)
.database_params(&[("sslmode", "require")])
.ssl_cert_file("/certs/db.pem")
.migration_location("/abs/migrations")
.unwrap()
.build()
.unwrap();
match settings.inner {
DbSettings::Postgres(s) => {
assert_eq!(s.database_name, "mydb");
assert_eq!(s.database_user, "me");
assert_eq!(s.database_password, "secret");
assert_eq!(s.database_host.as_deref(), Some("db.example.com"));
assert_eq!(s.database_port.as_deref(), Some("4444"));
assert_eq!(
s.database_params.unwrap().get("sslmode"),
Some(&"require".to_string())
);
assert_eq!(s.ssl_cert_file, Some(PathBuf::from("/certs/db.pem")));
assert_eq!(s.migration_location.as_deref(), Some("/abs/migrations"));
}
other => panic!("expected postgres settings, got {:?}", other),
}
}

#[test]
fn mysql_owned_server_setters_chain_and_build() {
let settings = MySqlSettingsBuilder::empty()
.database_name("mydb")
.database_user("me")
.database_password("secret")
.database_port(3307)
.build()
.unwrap();
match settings.inner {
DbSettings::MySql(s) => {
assert_eq!(s.database_name, "mydb");
assert_eq!(s.database_port.as_deref(), Some("3307"));
}
other => panic!("expected mysql settings, got {:?}", other),
}
}
}
Loading
Loading