Skip to content

Add a public format-version marker writer for rollback #830

Description

@sehkone

Add a public format-version marker writer for rollback

Context

origin/main already carries the 0.47.0-alpha.2 database-format migration. It creates the new column families and rewrites the affected stored records; after a successful migration, migrate_data_dir records the current crate version in both data_dir/VERSION and backup_dir/VERSION. It refuses to continue if those two parsed markers differ.

The migration bodies modify data_dir/states.db, while the marker files are separate metadata. A rollback can restore the pre-update database snapshot but leave both markers at the newer format. The earlier binary then cannot select a migration for that newer marker even though the restored database contents are older. The operation-attempt record already has optional backup_id and pre_update_version fields for the caller to persist the snapshot identity and prior format version; this crate does not orchestrate their use.

The existing private marker helper atomically replaces one marker file but always writes CARGO_PKG_VERSION, so it cannot perform the rollback metadata step.

Scope

Add and export one public function with independent path argument types: pub fn write_version_markers<P: AsRef<Path>, Q: AsRef<Path>>(data_dir: P, backup_dir: Q, version: &str) -> anyhow::Result<()>. It parses the caller-supplied semantic version before any filesystem mutation, uses create_dir_all to create either missing marker directory and any intermediate components, and writes the parsed version's canonical Version::to_string() form to data_dir/VERSION and backup_dir/VERSION.

Refactor the private marker-writing helper as needed so normal migration still writes the current crate version through the same per-file atomic-write path. Keep the #[cfg(test)] write_version helper: its raw-string writes deliberately construct non-canonical and otherwise invalid-marker test cases that the public API must reject. Document the public function as the metadata companion to restoring a rollback snapshot: after the caller restores the prior states.db snapshot, it passes the recorded prior format version so both markers again describe the restored contents.

Acceptance criteria

  • review_database exports exactly one public marker-writing entry point with independent generic path arguments, a caller-supplied version string, and no public marker reader or per-marker writer.
  • The entry point parses the complete input as semver::Version before creating directories or replacing either marker. It writes that parsed version's canonical to_string() form, with no trailing bytes, to both VERSION files.
  • A missing data or backup directory is created and receives its marker after successful validation; invalid input creates neither directory nor marker and changes no existing marker.
  • Each marker replacement retains the existing write-to-a-temporary-file, file-sync, rename, and directory-sync behaviour, so an interrupted individual marker update cannot leave that marker empty or partially written.
  • Update the VERSION_TMP_FILE_NAME rustdoc to retain the fixed VERSION.tmp name and explain that both startup migration and rollback marker writing require exclusive access to each affected directory.
  • The function returns errors with context identifying whether the data-directory or backup-directory marker operation failed. It is safe to retry with the same paths and version after correcting the filesystem failure; a successful retry leaves both markers canonical and equal.
  • The function does not inspect states.db, read existing marker contents, or check whether a binary can migrate from the supplied version. It validates semantic-version syntax only; choosing a version supported by the rollback binary remains the caller's responsibility.
  • Given a populated pre-0.47 database constructed in a tempdir as the existing migration tests do—using the 0.46 column-family layout and records that require the 0.46-to-0.47 migration—writing 0.46.0 through the public API lets migrate_data_dir select and complete the migration chain rather than return migration from 0.46.0 is not supported, and it finishes with both markers at CARGO_PKG_VERSION.
  • Normal migrate_data_dir behaviour is unchanged: after a migration it writes the current crate version to both markers.
  • The public function's rustdoc explains its rollback use and includes an # Errors section.
  • CHANGELOG.md documents the new public rollback marker-writing API once under ## [Unreleased]### Added, with no issue or PR reference.

Constraints

  • Do not expose create_version_file, marker-reading helpers, or a second public marker-writing API.
  • Do not change the migration list, COMPATIBLE_VERSION_REQ, schema definitions, column families, backup API, or states.db; this function writes marker metadata only.
  • The two marker files can reside in different directories, so do not promise an impossible cross-directory atomic commit. Return a contextual error on a partial failure; the documented recovery is to correct the failure and call this idempotent operation again with the same version.
  • Use create_dir_all to create a missing requested marker directory and any intermediate components, then create only that directory's VERSION file. Do not initialize a database, infer a version from existing contents, or call retrieve_or_create_version, which could stamp the current crate version.
  • The fixed VERSION.tmp name requires callers to serialize marker writes with migration or rollback operations for each affected directory; do not add concurrent-writer support in this issue.
  • Keep the test-only write_version helper and its existing raw-marker call sites; do not replace their setup with the public canonicalizing API.
  • Use tempfile::tempdir() for filesystem tests and do not use unwrap() outside tests.

Out of scope

  • Taking or restoring database snapshots, selecting a backup id, recording OperationAttempt fields, or coordinating the update/rollback sequence in another service.
  • A down-migration, validation that a supplied version is migratable by a particular binary, or changes to the database contents restored from a snapshot.
  • Extending backups beyond the states database.

Test plan

  • Call the public API with a valid version in two temporary directories and assert that both VERSION files contain Version::parse(input)?.to_string() exactly.
  • Call the public API with paths whose directories do not yet exist and a valid version; assert that it creates both directories and canonical markers. Call it with an invalid version and absent paths; assert that neither directory nor marker is created. Repeat with pre-existing marker files and assert their original contents remain unchanged.
  • Make the backup path a regular file so its marker operation fails; assert that the error identifies the backup directory, replace the file with a directory, retry with the same version, and assert that both canonical markers are equal.
  • In a tempdir, construct a populated 0.46-format store following assert_migration_creates_new_column_families: create the 0.46 column-family layout and insert its pre-migration records, use the public API to set both markers to 0.46.0, then run migrate_data_dir. Assert that it succeeds, adds the current column families and converts the populated records, reaches CARGO_PKG_VERSION in both markers, and does not report an unsupported migration.
  • Retain the private-helper tests proving a normal migration writes CARGO_PKG_VERSION to both markers and that an individual replacement remains atomic; retain write_version for their raw, including non-canonical, marker setup.
  • Run cargo fmt -- --check --config group_imports=StdExternalCrate, cargo clippy --bins --tests --all-features -- -D warnings, and cargo test --all-features.

Dependencies

Part of #831. The required 0.47.0-alpha.2 format migration and rollback metadata fields are already present on origin/main.

Pointers

  • src/migration.rsmigrate_data_dir, retrieve_or_create_version, create_version_file, read_version_file, the test-only write_version fixture helper, and their tests.
  • src/lib.rs — the migration re-export where the new public function belongs.
  • src/backup.rs — the existing snapshot create, list, and restore surface that this marker-only API accompanies.
  • src/tables/operation_attempt.rs — the persisted backup_id and pre_update_version fields available to rollback orchestration.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions