diff --git a/mbtiles/src/bin/mbtiles.rs b/mbtiles/src/bin/mbtiles.rs index 6776bcbe0..74c5392c7 100644 --- a/mbtiles/src/bin/mbtiles.rs +++ b/mbtiles/src/bin/mbtiles.rs @@ -65,6 +65,9 @@ enum Commands { base_file: PathBuf, /// Diff file patch_file: PathBuf, + /// Force patching operation, ignoring some warnings that otherwise would prevent the operation. Use with caution. + #[arg(short, long)] + force: bool, }, /// Update metadata to match the content of the file #[command(name = "meta-update", alias = "update-meta")] @@ -152,6 +155,9 @@ pub struct SharedCopyOpts { /// Skip generating a global hash for mbtiles validation. By default, `mbtiles` will compute `agg_tiles_hash` metadata value. #[arg(long)] skip_agg_tiles_hash: bool, + /// Force copy operation, ignoring some warnings that otherwise would prevent the operation. Use with caution. + #[arg(short, long)] + force: bool, } impl SharedCopyOpts { @@ -177,6 +183,7 @@ impl SharedCopyOpts { zoom_levels: self.zoom_levels, bbox: self.bbox, skip_agg_tiles_hash: self.skip_agg_tiles_hash, + force: self.force, // Constants dst_type: None, // Taken from dst_type_cli } @@ -229,8 +236,9 @@ async fn main_int() -> anyhow::Result<()> { Commands::ApplyPatch { base_file, patch_file, + force, } => { - apply_patch(base_file, patch_file).await?; + apply_patch(base_file, patch_file, force).await?; } Commands::UpdateMetadata { file, update_zoom } => { let mbt = Mbtiles::new(file.as_path())?; @@ -593,6 +601,7 @@ mod tests { command: ApplyPatch { base_file: PathBuf::from("src_file"), patch_file: PathBuf::from("diff_file"), + force: false, } } ); diff --git a/mbtiles/src/copier.rs b/mbtiles/src/copier.rs index 0224b7b9a..9ceec73ec 100644 --- a/mbtiles/src/copier.rs +++ b/mbtiles/src/copier.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use enum_display::EnumDisplay; use itertools::Itertools as _; -use log::{debug, info, trace}; +use log::{debug, info, trace, warn}; use martin_tile_utils::{bbox_to_xyz, MAX_ZOOM}; use serde::{Deserialize, Serialize}; use sqlite_hashes::rusqlite::Connection; @@ -17,7 +17,7 @@ use crate::queries::{ use crate::MbtType::{Flat, FlatWithHash, Normalized}; use crate::{ invert_y_value, reset_db_settings, CopyType, MbtError, MbtType, MbtTypeCli, Mbtiles, - AGG_TILES_HASH, AGG_TILES_HASH_AFTER_APPLY, + AGG_TILES_HASH, AGG_TILES_HASH_AFTER_APPLY, AGG_TILES_HASH_BEFORE_APPLY, }; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, EnumDisplay)] @@ -68,6 +68,8 @@ pub struct MbtilesCopier { pub apply_patch: Option, /// Skip generating a global hash for mbtiles validation. By default, `mbtiles` will compute `agg_tiles_hash` metadata value. pub skip_agg_tiles_hash: bool, + /// Ignore some warnings and continue with the copying operation + pub force: bool, } #[derive(Clone, Debug)] @@ -129,7 +131,8 @@ impl MbtileCopierInt { } pub async fn run_simple(self) -> MbtResult { - let src_type = self.src_mbtiles.open_and_detect_type().await?; + let mut conn = self.src_mbtiles.open_readonly().await?; + let src_type = self.src_mbtiles.detect_type(&mut conn).await?; let mut conn = self.dst_mbtiles.open_or_new().await?; let is_empty_db = is_empty_database(&mut conn).await?; @@ -166,7 +169,6 @@ impl MbtileCopierInt { on_duplicate, dst_type, get_select_from(src_type, dst_type), - false, ) .await?; @@ -180,53 +182,100 @@ impl MbtileCopierInt { } pub async fn run_with_diff_or_patch(self) -> MbtResult { + let is_creating_diff = self.options.diff_with_file.is_some(); let ((Some(dif_file), None) | (None, Some(dif_file))) = (&self.options.diff_with_file, &self.options.apply_patch) else { unreachable!() }; + let dif_mbt = Mbtiles::new(dif_file)?; - let dif_type = dif_mbt.open_and_detect_type().await?; - let is_creating_diff = self.options.diff_with_file.is_some(); + let mut dif_conn = dif_mbt.open_readonly().await?; + let dif_info = dif_mbt.get_diff_info(&mut dif_conn).await?; + let dif_type = dif_info.mbt_type; + if is_creating_diff { + dif_mbt.validate_file_info(&dif_info, self.options.force)?; + } else { + dif_mbt.validate_diff_info(&dif_info, self.options.force)?; + } + drop(dif_conn); - let src_type = self.src_mbtiles.open_and_detect_type().await?; + let src_mbt = &self.src_mbtiles; + let mut src_conn = src_mbt.open_readonly().await?; + let src_info = src_mbt.get_diff_info(&mut src_conn).await?; + let src_type = src_info.mbt_type; + src_mbt.validate_file_info(&src_info, self.options.force)?; + drop(src_conn); let mut conn = self.dst_mbtiles.open_or_new().await?; if !is_empty_database(&mut conn).await? { return Err(MbtError::NonEmptyTargetFile(self.options.dst_file)); } - self.src_mbtiles.attach_to(&mut conn, "sourceDb").await?; + src_mbt.attach_to(&mut conn, "sourceDb").await?; dif_mbt.attach_to(&mut conn, "diffDb").await?; let what = self.copy_text(); - let src_path = &self.src_mbtiles.filepath(); - let dst_path = &self.dst_mbtiles.filepath(); + let dst_path = self.dst_mbtiles.filepath(); let dif_path = dif_mbt.filepath(); let dst_type = self.options.dst_type().unwrap_or(src_type); if is_creating_diff { - info!("Comparing {src_path} ({src_type}) and {dif_path} ({dif_type}) {what}into a new file {dst_path} ({dst_type})"); + info!("Comparing {src_mbt} ({src_type}) and {dif_path} ({dif_type}) {what}into a new file {dst_path} ({dst_type})"); } else { - info!("Applying patch from {dif_path} ({dif_type}) to {src_path} ({src_type}) {what}into a new file {dst_path} ({dst_type})"); + info!("Applying patch from {dif_path} ({dif_type}) to {src_mbt} ({src_type}) {what}into a new file {dst_path} ({dst_type})"); } self.init_new_schema(&mut conn, src_type, dst_type).await?; + let select_from = if is_creating_diff { + get_select_from_with_diff(dif_type, dst_type) + } else { + get_select_from_apply_patch(src_type, dif_type, dst_type) + }; + self.copy_with_rusqlite( &mut conn, CopyDuplicateMode::Override, dst_type, - &(if is_creating_diff { - get_select_from_with_diff(dif_type, dst_type) - } else { - get_select_from_apply_patch(src_type, dif_type, dst_type) - }), - true, + &select_from, ) .await?; + if is_creating_diff { + if let Some(hash) = src_info.agg_tiles_hash { + self.dst_mbtiles + .set_metadata_value(&mut conn, AGG_TILES_HASH_BEFORE_APPLY, &hash) + .await?; + } + if let Some(hash) = dif_info.agg_tiles_hash { + self.dst_mbtiles + .set_metadata_value(&mut conn, AGG_TILES_HASH_AFTER_APPLY, &hash) + .await?; + } + } + + // TODO: perhaps disable all except --copy all when using with diffs, or else is not making much sense if self.options.copy.copy_tiles() && !self.options.skip_agg_tiles_hash { self.dst_mbtiles.update_agg_tiles_hash(&mut conn).await?; + + if !is_creating_diff { + let new_hash = self.dst_mbtiles.get_agg_tiles_hash(&mut conn).await?; + match (dif_info.agg_tiles_hash_after_apply, new_hash) { + (Some(expected), Some(actual)) if expected != actual => { + let err = MbtError::AggHashMismatchAfterApply( + dif_path.to_string(), + expected, + dst_path.to_string(), + actual, + ); + if !self.options.force { + return Err(err); + } + warn!("{err}"); + } + _ => {} + } + } } detach_db(&mut conn, "diffDb").await?; @@ -249,7 +298,6 @@ impl MbtileCopierInt { on_duplicate: CopyDuplicateMode, dst_type: MbtType, select_from: &str, - is_diff: bool, ) -> Result<(), MbtError> { // SAFETY: This must be scoped to make sure the handle is dropped before we continue using conn // Make sure not to execute any other queries while the handle is locked @@ -266,7 +314,7 @@ impl MbtileCopierInt { } if self.options.copy.copy_metadata() { - self.copy_metadata(&rusqlite_conn, is_diff, on_duplicate) + self.copy_metadata(&rusqlite_conn, on_duplicate) } else { debug!("Skipping copying metadata"); Ok(()) @@ -276,38 +324,35 @@ impl MbtileCopierInt { fn copy_metadata( &self, rusqlite_conn: &Connection, - is_diff: bool, on_duplicate: CopyDuplicateMode, ) -> Result<(), MbtError> { let on_dupl = on_duplicate.to_sql(); let sql; - if is_diff { - // Insert all rows from diffDb.metadata if they do not exist or are different in sourceDb.metadata. - // Also insert all names from sourceDb.metadata that do not exist in diffDb.metadata, with their value set to NULL. - // Rename agg_tiles_hash to agg_tiles_hash_after_apply because agg_tiles_hash will be auto-added later - if self.options.diff_with_file.is_some() { - // Include agg_tiles_hash value even if it is the same because we will still need it when applying the diff - sql = format!( - " + + // Insert all rows from diffDb.metadata if they do not exist or are different in sourceDb.metadata. + // Also insert all names from sourceDb.metadata that do not exist in diffDb.metadata, with their value set to NULL. + // Skip agg_tiles_hash because that requires special handling + if self.options.diff_with_file.is_some() { + // Include agg_tiles_hash value even if it is the same because we will still need it when applying the diff + sql = format!( + " INSERT {on_dupl} INTO metadata (name, value) - SELECT IIF(name = '{AGG_TILES_HASH}','{AGG_TILES_HASH_AFTER_APPLY}', name) as name - , value + SELECT name, value FROM ( SELECT COALESCE(difMD.name, srcMD.name) as name , difMD.value as value FROM sourceDb.metadata AS srcMD FULL JOIN diffDb.metadata AS difMD ON srcMD.name = difMD.name - WHERE srcMD.value != difMD.value OR srcMD.value ISNULL OR difMD.value ISNULL OR srcMD.name = '{AGG_TILES_HASH}' + WHERE srcMD.value != difMD.value OR srcMD.value ISNULL OR difMD.value ISNULL ) joinedMD - WHERE name != '{AGG_TILES_HASH_AFTER_APPLY}'" - ); - debug!("Copying metadata, taking into account diff file with {sql}"); - } else { - sql = format!( - " + WHERE name NOT IN ('{AGG_TILES_HASH}', '{AGG_TILES_HASH_BEFORE_APPLY}', '{AGG_TILES_HASH_AFTER_APPLY}')" + ); + debug!("Copying metadata, taking into account diff file with {sql}"); + } else if self.options.apply_patch.is_some() { + sql = format!( + " INSERT {on_dupl} INTO metadata (name, value) - SELECT IIF(name = '{AGG_TILES_HASH_AFTER_APPLY}','{AGG_TILES_HASH}', name) as name - , value + SELECT name, value FROM ( SELECT COALESCE(srcMD.name, difMD.name) as name , COALESCE(difMD.value, srcMD.value) as value @@ -315,10 +360,9 @@ impl MbtileCopierInt { ON srcMD.name = difMD.name WHERE difMD.name ISNULL OR difMD.value NOTNULL ) joinedMD - WHERE name != '{AGG_TILES_HASH}'" - ); - debug!("Copying metadata, and applying the diff file with {sql}"); - } + WHERE name NOT IN ('{AGG_TILES_HASH}', '{AGG_TILES_HASH_BEFORE_APPLY}', '{AGG_TILES_HASH_AFTER_APPLY}')" + ); + debug!("Copying metadata, and applying the diff file with {sql}"); } else { sql = format!( " @@ -826,6 +870,7 @@ mod tests { src_file: src.clone(), dst_file: dst.clone(), diff_with_file: Some(diff_file.clone()), + force: true, ..Default::default() }; let mut dst_conn = opt.run().await?; diff --git a/mbtiles/src/errors.rs b/mbtiles/src/errors.rs index b68137fbd..2f4e60924 100644 --- a/mbtiles/src/errors.rs +++ b/mbtiles/src/errors.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use martin_tile_utils::{TileInfo, MAX_ZOOM}; use sqlite_hashes::rusqlite; -use crate::MbtType; +use crate::{MbtType, AGG_TILES_HASH, AGG_TILES_HASH_AFTER_APPLY, AGG_TILES_HASH_BEFORE_APPLY}; #[derive(thiserror::Error, Debug)] pub enum MbtError { @@ -77,6 +77,24 @@ pub enum MbtError { #[error("Invalid zoom value {0}={1}, expecting an integer between 0..{MAX_ZOOM}")] InvalidZoomValue(&'static str, String), + + #[error("A file {0} does not have an {AGG_TILES_HASH} metadata entry, probably because it was not created by this tool. Use `--force` to ignore this warning, or run this to update hash value: `mbtiles validate --agg-hash update {0}`")] + CannotDiffFileWithoutHash(String), + + #[error("File {0} has {AGG_TILES_HASH_BEFORE_APPLY} or {AGG_TILES_HASH_AFTER_APPLY} metadata entry, indicating it is a patch file which should not be diffed with another file. Use `--force` to ignore this warning.")] + DiffingDiffFile(String), + + #[error("A file {0} does not seem to be a patch diff file because it has no {AGG_TILES_HASH_BEFORE_APPLY} and {AGG_TILES_HASH_AFTER_APPLY} metadata entries. These entries are automatically created when using `mbtiles diff` and `mbitiles copy --diff-with-file`. Use `--force` to ignore this warning.")] + PatchFileHasNoHashes(String), + + #[error("A file {0} does not have {AGG_TILES_HASH_BEFORE_APPLY} metadata, probably because it was created by an older version of the `mbtiles` tool. Use `--force` to ignore this warning, but ensure you are applying the patch to the right file.")] + PatchFileHasNoBeforeHash(String), + + #[error("The {AGG_TILES_HASH_BEFORE_APPLY}='{1}' in patch file {0} does not match {AGG_TILES_HASH}='{3}' in the file {2}")] + AggHashMismatchWithDiff(String, String, String, String), + + #[error("The {AGG_TILES_HASH_AFTER_APPLY}='{1}' in patch file {0} does not match {AGG_TILES_HASH}='{3}' in the file {2} after the patch was applied")] + AggHashMismatchAfterApply(String, String, String, String), } pub type MbtResult = Result; diff --git a/mbtiles/src/lib.rs b/mbtiles/src/lib.rs index 272915f8b..1f54ebb66 100644 --- a/mbtiles/src/lib.rs +++ b/mbtiles/src/lib.rs @@ -32,7 +32,7 @@ pub use update::UpdateZoomType; mod validation; pub use validation::{ calc_agg_tiles_hash, AggHashType, IntegrityCheckType, MbtType, AGG_TILES_HASH, - AGG_TILES_HASH_AFTER_APPLY, + AGG_TILES_HASH_AFTER_APPLY, AGG_TILES_HASH_BEFORE_APPLY, }; /// `MBTiles` uses a TMS (Tile Map Service) scheme for its tile coordinates (inverted along the Y axis). diff --git a/mbtiles/src/mbtiles.rs b/mbtiles/src/mbtiles.rs index 9808fb194..ebd2e5c2d 100644 --- a/mbtiles/src/mbtiles.rs +++ b/mbtiles/src/mbtiles.rs @@ -42,6 +42,13 @@ impl CopyType { } } +pub struct PatchFileInfo { + pub mbt_type: MbtType, + pub agg_tiles_hash: Option, + pub agg_tiles_hash_before_apply: Option, + pub agg_tiles_hash_after_apply: Option, +} + #[derive(Clone, Debug)] pub struct Mbtiles { filepath: String, @@ -212,11 +219,6 @@ impl Mbtiles { ), } } - - pub async fn open_and_detect_type(&self) -> MbtResult { - let mut conn = self.open_readonly().await?; - self.detect_type(&mut conn).await - } } pub async fn attach_hash_fn(conn: &mut SqliteConnection) -> MbtResult<()> { diff --git a/mbtiles/src/patcher.rs b/mbtiles/src/patcher.rs index 6e983ffec..8f567f7f6 100644 --- a/mbtiles/src/patcher.rs +++ b/mbtiles/src/patcher.rs @@ -1,19 +1,45 @@ use std::path::PathBuf; -use log::{debug, info}; +use log::{debug, info, warn}; use sqlx::query; use crate::queries::detach_db; use crate::MbtType::{Flat, FlatWithHash, Normalized}; -use crate::{MbtResult, MbtType, Mbtiles, AGG_TILES_HASH, AGG_TILES_HASH_AFTER_APPLY}; +use crate::{ + MbtError, MbtResult, MbtType, Mbtiles, AGG_TILES_HASH, AGG_TILES_HASH_AFTER_APPLY, + AGG_TILES_HASH_BEFORE_APPLY, +}; -pub async fn apply_patch(base_file: PathBuf, patch_file: PathBuf) -> MbtResult<()> { +pub async fn apply_patch(base_file: PathBuf, patch_file: PathBuf, force: bool) -> MbtResult<()> { let base_mbt = Mbtiles::new(base_file)?; let patch_mbt = Mbtiles::new(patch_file)?; - let patch_type = patch_mbt.open_and_detect_type().await?; + + let mut conn = patch_mbt.open_readonly().await?; + let patch_info = patch_mbt.get_diff_info(&mut conn).await?; + patch_mbt.validate_diff_info(&patch_info, force)?; + let patch_type = patch_info.mbt_type; + drop(conn); let mut conn = base_mbt.open().await?; - let base_type = base_mbt.detect_type(&mut conn).await?; + let base_info = base_mbt.get_diff_info(&mut conn).await?; + let base_type = base_info.mbt_type; + let base_hash = base_mbt.get_agg_tiles_hash(&mut conn).await?; + base_mbt.validate_file_info(&base_info, force)?; + + match (force, base_hash, patch_info.agg_tiles_hash_before_apply) { + (false, Some(base_hash), Some(expected_hash)) if base_hash != expected_hash => { + return Err(MbtError::AggHashMismatchWithDiff( + patch_mbt.filepath().to_string(), + expected_hash, + base_mbt.filepath().to_string(), + base_hash, + )); + } + (true, Some(base_hash), Some(expected_hash)) if base_hash != expected_hash => { + warn!("Aggregate tiles hash mismatch: Patch file expected {expected_hash} but found {base_hash} in {base_mbt} (force mode)"); + } + _ => {} + } info!("Applying patch file {patch_mbt} ({patch_type}) to {base_mbt} ({base_type})"); @@ -53,7 +79,7 @@ pub async fn apply_patch(base_file: PathBuf, patch_file: PathBuf) -> MbtResult<( SELECT IIF(name = '{AGG_TILES_HASH_AFTER_APPLY}', '{AGG_TILES_HASH}', name) as name, value FROM patchDb.metadata - WHERE name NOTNULL AND name != '{AGG_TILES_HASH}';" + WHERE name NOTNULL AND name NOT IN ('{AGG_TILES_HASH}', '{AGG_TILES_HASH_BEFORE_APPLY}');" ); query(&sql).execute(&mut conn).await?; @@ -151,7 +177,7 @@ mod tests { // Apply patch to the src data in in-memory DB let patch_file = PathBuf::from("../tests/fixtures/mbtiles/world_cities_diff.mbtiles"); - apply_patch(src, patch_file).await?; + apply_patch(src, patch_file, true).await?; // Verify the data is the same as the file the patch was generated from Mbtiles::new("../tests/fixtures/mbtiles/world_cities_modified.mbtiles")? @@ -183,7 +209,7 @@ mod tests { // Apply patch to the src data in in-memory DB let patch_file = PathBuf::from("../tests/fixtures/mbtiles/geography-class-jpg-diff.mbtiles"); - apply_patch(src, patch_file).await?; + apply_patch(src, patch_file, true).await?; // Verify the data is the same as the file the patch was generated from Mbtiles::new("../tests/fixtures/mbtiles/geography-class-jpg-modified.mbtiles")? diff --git a/mbtiles/src/validation.rs b/mbtiles/src/validation.rs index 1ac919f2c..dab783b22 100644 --- a/mbtiles/src/validation.rs +++ b/mbtiles/src/validation.rs @@ -7,10 +7,11 @@ use martin_tile_utils::{Format, TileInfo, MAX_ZOOM}; use serde::Serialize; use serde_json::Value; use sqlx::sqlite::SqliteRow; -use sqlx::{query, Row, SqliteExecutor}; +use sqlx::{query, Row, SqliteConnection, SqliteExecutor}; use tilejson::TileJSON; use crate::errors::{MbtError, MbtResult}; +use crate::mbtiles::PatchFileInfo; use crate::queries::{ has_tiles_with_hash, is_flat_tables_type, is_flat_with_hash_tables_type, is_normalized_tables_type, @@ -27,6 +28,9 @@ pub const AGG_TILES_HASH: &str = "agg_tiles_hash"; /// Metadata key for a diff file, describing the eventual [`AGG_TILES_HASH`] value of the resulting tileset once the diff is applied pub const AGG_TILES_HASH_AFTER_APPLY: &str = "agg_tiles_hash_after_apply"; +/// Metadata key for a diff file, describing the expected [`AGG_TILES_HASH`] value of the tileset to which the diff will be applied. +pub const AGG_TILES_HASH_BEFORE_APPLY: &str = "agg_tiles_hash_before_apply"; + #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, EnumDisplay, Serialize)] #[enum_display(case = "Kebab")] pub enum MbtType { @@ -453,6 +457,67 @@ LIMIT 1;" info!("All tile hashes are valid for {self}"); Ok(()) } + + pub async fn get_diff_info(&self, conn: &mut SqliteConnection) -> MbtResult { + Ok(PatchFileInfo { + mbt_type: self.detect_type(&mut *conn).await?, + agg_tiles_hash: self.get_agg_tiles_hash(&mut *conn).await?, + agg_tiles_hash_before_apply: self + .get_metadata_value(&mut *conn, AGG_TILES_HASH_BEFORE_APPLY) + .await?, + agg_tiles_hash_after_apply: self + .get_metadata_value(&mut *conn, AGG_TILES_HASH_AFTER_APPLY) + .await?, + }) + } + + pub fn validate_file_info(&self, info: &PatchFileInfo, force: bool) -> MbtResult<()> { + if info.agg_tiles_hash.is_none() { + if !force { + return Err(MbtError::CannotDiffFileWithoutHash( + self.filepath().to_string(), + )); + } + warn!("File {self} has no {AGG_TILES_HASH} metadata field, probably because it was created by an older version of the `mbtiles` tool. Use this command to update the value:\nmbtiles validate --agg-hash update {self}"); + } else if info.agg_tiles_hash_before_apply.is_some() + || info.agg_tiles_hash_after_apply.is_some() + { + if !force { + return Err(MbtError::DiffingDiffFile(self.filepath().to_string())); + } + warn!("File {self} has {AGG_TILES_HASH_BEFORE_APPLY} or {AGG_TILES_HASH_AFTER_APPLY} metadata field, indicating it is a patch file which should not be diffed with another file."); + } + Ok(()) + } + + pub fn validate_diff_info(&self, info: &PatchFileInfo, force: bool) -> MbtResult<()> { + match ( + &info.agg_tiles_hash_before_apply, + &info.agg_tiles_hash_after_apply, + ) { + (Some(before), Some(after)) => { + info!( + "The patch file {self} expects to be applied to a tileset with {AGG_TILES_HASH}={before}, and should result in hash {after} after applying", + ); + } + (None, Some(_)) => { + if !force { + return Err(MbtError::PatchFileHasNoBeforeHash( + self.filepath().to_string(), + )); + } + warn!( + "The patch file {self} has no {AGG_TILES_HASH_BEFORE_APPLY} metadata field, probably because it was created by an older version of the `mbtiles` tool."); + } + _ => { + if !force { + return Err(MbtError::PatchFileHasNoHashes(self.filepath().to_string())); + } + warn!("The patch file {self} has no {AGG_TILES_HASH_AFTER_APPLY} metadata field, probably because it was not properly created by the `mbtiles` tool."); + } + } + Ok(()) + } } /// Compute the hash of the combined tiles in the mbtiles file tiles table/view. diff --git a/mbtiles/tests/copy.rs b/mbtiles/tests/copy.rs index e0525cd31..098a0ab64 100644 --- a/mbtiles/tests/copy.rs +++ b/mbtiles/tests/copy.rs @@ -483,7 +483,7 @@ async fn diff_and_patch( eprintln!("TEST: Applying the difference ({b_db} - {a_db} = {dif_db}) to {a_db}, should get {b_db}"); let (clone_mbt, mut clone_cn) = open!(diff_and_patch, "{prefix}__1"); copy!(databases.path(a_db, *dst_type), path(&clone_mbt)); - apply_patch(path(&clone_mbt), path(&dif_mbt)).await?; + apply_patch(path(&clone_mbt), path(&dif_mbt), false).await?; let hash = clone_mbt.validate(Off, Verify).await?; assert_eq!(hash, databases.hash(b_db, *dst_type)); let dmp = dump(&mut clone_cn).await?; @@ -492,7 +492,7 @@ async fn diff_and_patch( eprintln!("TEST: Applying the difference ({b_db} - {a_db} = {dif_db}) to {b_db}, should not modify it"); let (clone_mbt, mut clone_cn) = open!(diff_and_patch, "{prefix}__2"); copy!(databases.path(b_db, *dst_type), path(&clone_mbt)); - apply_patch(path(&clone_mbt), path(&dif_mbt)).await?; + apply_patch(path(&clone_mbt), path(&dif_mbt), true).await?; let hash = clone_mbt.validate(Off, Verify).await?; assert_eq!(hash, databases.hash(b_db, *dst_type)); let dmp = dump(&mut clone_cn).await?; @@ -522,10 +522,9 @@ async fn patch_on_copy( apply_patch => Some(databases.path("dif", dif_type)), dst_type_cli => v2_type, }; - pretty_assert_eq!( - &dump(&mut v2_cn).await?, - databases.dump("v2", v2_type.unwrap_or(v1_type)) - ); + let actual = dump(&mut v2_cn).await?; + let expected = databases.dump("v2", v2_type.unwrap_or(v1_type)); + pretty_assert_eq!(&actual, expected); Ok(()) } @@ -539,7 +538,7 @@ async fn test_one() { // let db = Databases::default(); // Test convert - convert(Flat, Flat, &db).await.unwrap(); + // convert(Flat, Flat, &db).await.unwrap(); // Test diff patch copy let src_type = FlatWithHash; diff --git a/mbtiles/tests/snapshots/copy__databases@flat__dif.snap b/mbtiles/tests/snapshots/copy__databases@flat__dif.snap index 36193a4f5..317787984 100644 --- a/mbtiles/tests/snapshots/copy__databases@flat__dif.snap +++ b/mbtiles/tests/snapshots/copy__databases@flat__dif.snap @@ -12,6 +12,7 @@ CREATE TABLE metadata ( values = [ '( "agg_tiles_hash", "B86122579EDCDD4C51F3910894FCC1A1" )', '( "agg_tiles_hash_after_apply", "3BCDEE3F52407FF1315629298CB99133" )', + '( "agg_tiles_hash_before_apply", "9ED9178D7025276336C783C2B54D6258" )', '( "md-edit", "value - v2" )', '( "md-new", "value - new" )', '( "md-remove", NULL )', diff --git a/mbtiles/tests/snapshots/copy__databases@flat__dif_empty.snap b/mbtiles/tests/snapshots/copy__databases@flat__dif_empty.snap index b88309497..e2b5de434 100644 --- a/mbtiles/tests/snapshots/copy__databases@flat__dif_empty.snap +++ b/mbtiles/tests/snapshots/copy__databases@flat__dif_empty.snap @@ -12,6 +12,7 @@ CREATE TABLE metadata ( values = [ '( "agg_tiles_hash", "D41D8CD98F00B204E9800998ECF8427E" )', '( "agg_tiles_hash_after_apply", "9ED9178D7025276336C783C2B54D6258" )', + '( "agg_tiles_hash_before_apply", "9ED9178D7025276336C783C2B54D6258" )', ] [[]] diff --git a/mbtiles/tests/snapshots/copy__databases@hash__dif.snap b/mbtiles/tests/snapshots/copy__databases@hash__dif.snap index ae6e8941c..d9feedca6 100644 --- a/mbtiles/tests/snapshots/copy__databases@hash__dif.snap +++ b/mbtiles/tests/snapshots/copy__databases@hash__dif.snap @@ -12,6 +12,7 @@ CREATE TABLE metadata ( values = [ '( "agg_tiles_hash", "B86122579EDCDD4C51F3910894FCC1A1" )', '( "agg_tiles_hash_after_apply", "3BCDEE3F52407FF1315629298CB99133" )', + '( "agg_tiles_hash_before_apply", "9ED9178D7025276336C783C2B54D6258" )', '( "md-edit", "value - v2" )', '( "md-new", "value - new" )', '( "md-remove", NULL )', diff --git a/mbtiles/tests/snapshots/copy__databases@hash__dif_empty.snap b/mbtiles/tests/snapshots/copy__databases@hash__dif_empty.snap index 7dc8868c9..aedfc8819 100644 --- a/mbtiles/tests/snapshots/copy__databases@hash__dif_empty.snap +++ b/mbtiles/tests/snapshots/copy__databases@hash__dif_empty.snap @@ -12,6 +12,7 @@ CREATE TABLE metadata ( values = [ '( "agg_tiles_hash", "D41D8CD98F00B204E9800998ECF8427E" )', '( "agg_tiles_hash_after_apply", "9ED9178D7025276336C783C2B54D6258" )', + '( "agg_tiles_hash_before_apply", "9ED9178D7025276336C783C2B54D6258" )', ] [[]] diff --git a/mbtiles/tests/snapshots/copy__databases@norm__dif.snap b/mbtiles/tests/snapshots/copy__databases@norm__dif.snap index 3dda51925..0231bb4f6 100644 --- a/mbtiles/tests/snapshots/copy__databases@norm__dif.snap +++ b/mbtiles/tests/snapshots/copy__databases@norm__dif.snap @@ -50,6 +50,7 @@ CREATE TABLE metadata ( values = [ '( "agg_tiles_hash", "B86122579EDCDD4C51F3910894FCC1A1" )', '( "agg_tiles_hash_after_apply", "3BCDEE3F52407FF1315629298CB99133" )', + '( "agg_tiles_hash_before_apply", "9ED9178D7025276336C783C2B54D6258" )', '( "md-edit", "value - v2" )', '( "md-new", "value - new" )', '( "md-remove", NULL )', diff --git a/mbtiles/tests/snapshots/copy__databases@norm__dif_empty.snap b/mbtiles/tests/snapshots/copy__databases@norm__dif_empty.snap index d84884200..e45a492f8 100644 --- a/mbtiles/tests/snapshots/copy__databases@norm__dif_empty.snap +++ b/mbtiles/tests/snapshots/copy__databases@norm__dif_empty.snap @@ -33,6 +33,7 @@ CREATE TABLE metadata ( values = [ '( "agg_tiles_hash", "D41D8CD98F00B204E9800998ECF8427E" )', '( "agg_tiles_hash_after_apply", "9ED9178D7025276336C783C2B54D6258" )', + '( "agg_tiles_hash_before_apply", "9ED9178D7025276336C783C2B54D6258" )', ] [[]] diff --git a/tests/expected/mbtiles/meta-all.txt b/tests/expected/mbtiles/meta-all.txt index 2949d92f4..42cf8feef 100644 --- a/tests/expected/mbtiles/meta-all.txt +++ b/tests/expected/mbtiles/meta-all.txt @@ -109,4 +109,5 @@ json: count: 68 geometry: Point layer: cities +agg_tiles_hash: 84792BF4EE9AEDDC5B1A60E707011FEE diff --git a/tests/fixtures/mbtiles/world_cities.mbtiles b/tests/fixtures/mbtiles/world_cities.mbtiles index d5fc05955..92c23a9e9 100644 Binary files a/tests/fixtures/mbtiles/world_cities.mbtiles and b/tests/fixtures/mbtiles/world_cities.mbtiles differ diff --git a/tests/fixtures/mbtiles/world_cities_modified.mbtiles b/tests/fixtures/mbtiles/world_cities_modified.mbtiles index e6d104bf7..85f88c640 100644 Binary files a/tests/fixtures/mbtiles/world_cities_modified.mbtiles and b/tests/fixtures/mbtiles/world_cities_modified.mbtiles differ