From 79a89125edd6529177b00bca8d09fc9e9fb898a5 Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 8 Feb 2024 11:08:47 +0800 Subject: [PATCH] Add `mbtiles diff` command (#1068) Implement `mbtiles diff a.mbtiles b.mbtiles diff.mbtiles` command This should behave exactly the same as `mbtiles copy a.mbtiles --diff-with-file b.mbtiles diff.mbtiles`. --------- Co-authored-by: Yuri Astrakhan --- docs/src/mbtiles-diff.md | 26 +++++++++++++++++++ mbtiles/src/bin/mbtiles.rs | 52 +++++++++++++++++++++++++++++++++++++- tests/test.sh | 6 ++++- 3 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 docs/src/mbtiles-diff.md diff --git a/docs/src/mbtiles-diff.md b/docs/src/mbtiles-diff.md new file mode 100644 index 00000000..9f24356c --- /dev/null +++ b/docs/src/mbtiles-diff.md @@ -0,0 +1,26 @@ +# Diffing MBTiles + +## `mbtiles diff` + +Diff command compares two mbtiles files `A` and `B`, and generates a diff (delta) file. +If the diff file is [applied](mbtiles-copy.md#mbtiles-apply-patch) to `A`, it will produce `B`. +The diff file will contain all tiles that are different between the two files +(modifications, insertions, and deletions as `NULL` values), for both the tile and metadata tables. +The only exception is `agg_tiles_has` metadata value. It will be renamed to `agg_tiles_hash_in_diff` and a +new `agg_tiles_hash` will be generated for the diff file itself. + +```shell +# This command will comapre `a.mbtiles` and `b.mbtiles`, and generate a new diff file `diff.mbtiles`. +mbtiles diff a.mbtiles b.mbtiles diff.mbtiles + +# If diff.mbtiles is applied to a.mbtiles, it will produce b.mbtiles +mbtiles apply-diff a.mbtiles diff.mbtiles b2.mbtiles + +# b.mbtiles and b2.mbtiles should now be the same +# Validate both files and see that their hash values are identical +mbtiles validate b.mbtiles +[INFO ] The agg_tiles_hashes=E95C1081447FB25674DCC1EB97F60C26 has been verified for b.mbtiles + +mbtiles validate b2.mbtiles +[INFO ] The agg_tiles_hashes=E95C1081447FB25674DCC1EB97F60C26 has been verified for b2.mbtiles +``` diff --git a/mbtiles/src/bin/mbtiles.rs b/mbtiles/src/bin/mbtiles.rs index 0f907407..bc870239 100644 --- a/mbtiles/src/bin/mbtiles.rs +++ b/mbtiles/src/bin/mbtiles.rs @@ -52,6 +52,13 @@ enum Commands { /// Value to set, or nothing if the key should be deleted. value: Option, }, + /// Compare two files A and B, and generate a new diff file. If the diff file is applied to A, it will produce B. + #[command(name = "diff")] + Diff { + file_a: PathBuf, + file_b: PathBuf, + diff: PathBuf, + }, /// Copy tiles from one mbtiles file to another. #[command(name = "copy", alias = "cp")] Copy(CopyArgs), @@ -212,6 +219,28 @@ async fn main_int() -> anyhow::Result<()> { println!("MBTiles file summary for {mbt}"); println!("{}", mbt.summary(&mut conn).await?); } + Commands::Diff { + file_a, + file_b, + diff, + } => { + let opts = MbtilesCopier { + src_file: file_a, + diff_with_file: Some(file_b), + dst_file: diff, + copy: CopyType::All, + skip_agg_tiles_hash: false, + on_duplicate: Some(CopyDuplicateMode::Override), + dst_type_cli: None, + dst_type: None, + min_zoom: None, + max_zoom: None, + zoom_levels: vec![], + bbox: vec![], + apply_patch: None, + }; + opts.run().await?; + } } Ok(()) @@ -253,7 +282,7 @@ mod tests { use mbtiles::CopyDuplicateMode; use super::*; - use crate::Commands::{ApplyPatch, Copy, MetaGetValue, MetaSetValue, Validate}; + use crate::Commands::{ApplyPatch, Copy, Diff, MetaGetValue, MetaSetValue, Validate}; use crate::{Args, IntegrityCheckType}; #[test] @@ -524,4 +553,25 @@ mod tests { } ); } + + #[test] + fn test_diff() { + assert_eq!( + Args::parse_from([ + "mbtiles", + "diff", + "file-a.mbtiles", + "file-b.mbtiles", + "../delta.mbtiles", + ]), + Args { + verbose: false, + command: Diff { + file_a: PathBuf::from("file-a.mbtiles"), + file_b: PathBuf::from("file-b.mbtiles"), + diff: PathBuf::from("../delta.mbtiles"), + } + } + ); + } } diff --git a/tests/test.sh b/tests/test.sh index e53580ce..6371cc76 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -458,7 +458,11 @@ if [[ "$MBTILES_BIN" != "-" ]]; then "$TEST_TEMP_DIR/world_cities_diff.mbtiles" \ --diff-with-file ./tests/fixtures/mbtiles/world_cities_modified.mbtiles \ 2>&1 | tee "$TEST_OUT_DIR/copy_diff.txt" - + $MBTILES_BIN diff \ + ./tests/fixtures/mbtiles/world_cities.mbtiles \ + ./tests/fixtures/mbtiles/world_cities_modified.mbtiles \ + "$TEST_TEMP_DIR/world_cities_diff2.mbtiles" \ + 2>&1 | tee "$TEST_OUT_DIR/copy_diff2.txt" if command -v sqlite3 > /dev/null; then # Apply this diff to the original version of the file cp ./tests/fixtures/mbtiles/world_cities.mbtiles "$TEST_TEMP_DIR/world_cities_copy.mbtiles"