diff --git a/meilisearch-lib/src/index_controller/mod.rs b/meilisearch-lib/src/index_controller/mod.rs index 4a01253cd..001bbdbd7 100644 --- a/meilisearch-lib/src/index_controller/mod.rs +++ b/meilisearch-lib/src/index_controller/mod.rs @@ -39,6 +39,7 @@ use crate::update_file_store::UpdateFileStore; mod dump_actor; pub mod error; +pub mod versioning; /// Concrete implementation of the IndexController, exposed by meilisearch-lib pub type MeiliSearch = IndexController; @@ -162,6 +163,11 @@ impl IndexControllerBuilder { .max_task_store_size .ok_or_else(|| anyhow::anyhow!("Missing update database size"))?; + let db_exists = db_path.as_ref().exists(); + if db_exists { + versioning::check_version_file(db_path.as_ref())?; + } + if let Some(ref path) = self.import_snapshot { log::info!("Loading from snapshot {:?}", path); load_snapshot( @@ -189,6 +195,8 @@ impl IndexControllerBuilder { let meta_env = options.open(&db_path)?; let update_file_store = UpdateFileStore::new(&db_path)?; + // Create or overwrite the version file for this DB + versioning::create_version_file(db_path.as_ref())?; let index_resolver = Arc::new(create_index_resolver( &db_path, diff --git a/meilisearch-lib/src/index_controller/versioning/error.rs b/meilisearch-lib/src/index_controller/versioning/error.rs new file mode 100644 index 000000000..4dc29e862 --- /dev/null +++ b/meilisearch-lib/src/index_controller/versioning/error.rs @@ -0,0 +1,16 @@ +#[derive(thiserror::Error, Debug)] +pub enum VersionFileError { + #[error("Version file is missing or the previous MeiliSearch engine version was below 0.24.0. Use a dump to update Meilisearch.")] + MissingVersionFile, + #[error("Version file is corrupted and thus MeiliSearch is unable to determine the version of the database.")] + MalformedVersionFile, + #[error( + "Expected MeiliSearch engine version: {major}.{minor}.{patch}, current engine version: {}. To update Meilisearch use a dump.", + env!("CARGO_PKG_VERSION").to_string() + )] + VersionMismatch { + major: String, + minor: String, + patch: String, + }, +} diff --git a/meilisearch-lib/src/index_controller/versioning/mod.rs b/meilisearch-lib/src/index_controller/versioning/mod.rs new file mode 100644 index 000000000..eba5d477e --- /dev/null +++ b/meilisearch-lib/src/index_controller/versioning/mod.rs @@ -0,0 +1,56 @@ +use std::fs; +use std::io::ErrorKind; +use std::path::Path; + +use self::error::VersionFileError; + +mod error; + +pub const VERSION_FILE_NAME: &str = "VERSION"; + +static VERSION_MAJOR: &str = env!("CARGO_PKG_VERSION_MAJOR"); +static VERSION_MINOR: &str = env!("CARGO_PKG_VERSION_MINOR"); +static VERSION_PATCH: &str = env!("CARGO_PKG_VERSION_PATCH"); + +// Persists the version of the current MeiliSearch binary to a VERSION file +pub fn create_version_file(db_path: &Path) -> anyhow::Result<()> { + let version_path = db_path.join(VERSION_FILE_NAME); + fs::write( + version_path, + format!("{}.{}.{}", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH), + )?; + + Ok(()) +} + +// Ensures Meilisearch version is compatible with the database, returns an error versions mismatch. +pub fn check_version_file(db_path: &Path) -> anyhow::Result<()> { + let version_path = db_path.join(VERSION_FILE_NAME); + + match fs::read_to_string(&version_path) { + Ok(version) => { + let version_components = version.split('.').collect::>(); + let (major, minor, patch) = match &version_components[..] { + [major, minor, patch] => (major.to_string(), minor.to_string(), patch.to_string()), + _ => return Err(VersionFileError::MalformedVersionFile.into()), + }; + + if major != VERSION_MAJOR || minor != VERSION_MINOR { + return Err(VersionFileError::VersionMismatch { + major, + minor, + patch, + } + .into()); + } + } + Err(error) => { + return match error.kind() { + ErrorKind::NotFound => Err(VersionFileError::MissingVersionFile.into()), + _ => Err(error.into()), + } + } + } + + Ok(()) +} diff --git a/meilisearch-lib/src/snapshot.rs b/meilisearch-lib/src/snapshot.rs index 2f3ee8474..556e7fabd 100644 --- a/meilisearch-lib/src/snapshot.rs +++ b/meilisearch-lib/src/snapshot.rs @@ -9,6 +9,7 @@ use tokio::time::sleep; use walkdir::WalkDir; use crate::compression::from_tar_gz; +use crate::index_controller::versioning::VERSION_FILE_NAME; use crate::tasks::task::Job; use crate::tasks::TaskStore; @@ -102,6 +103,7 @@ impl SnapshotJob { let temp_snapshot_dir = tempfile::tempdir()?; let temp_snapshot_path = temp_snapshot_dir.path(); + self.snapshot_version_file(temp_snapshot_path)?; self.snapshot_meta_env(temp_snapshot_path)?; self.snapshot_file_store(temp_snapshot_path)?; self.snapshot_indexes(temp_snapshot_path)?; @@ -133,6 +135,15 @@ impl SnapshotJob { Ok(()) } + fn snapshot_version_file(&self, path: &Path) -> anyhow::Result<()> { + let dst = path.join(VERSION_FILE_NAME); + let src = self.src_path.join(VERSION_FILE_NAME); + + fs::copy(src, dst)?; + + Ok(()) + } + fn snapshot_meta_env(&self, path: &Path) -> anyhow::Result<()> { let mut options = heed::EnvOpenOptions::new(); options.map_size(self.meta_env_size);