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 <yuriastrakhan@gmail.com>
This commit is contained in:
Lucas 2024-02-08 11:08:47 +08:00 committed by GitHub
parent 0aa6bd73fc
commit 79a89125ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 82 additions and 2 deletions

26
docs/src/mbtiles-diff.md Normal file
View File

@ -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
```

View File

@ -52,6 +52,13 @@ enum Commands {
/// Value to set, or nothing if the key should be deleted.
value: Option<String>,
},
/// 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"),
}
}
);
}
}

View File

@ -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"