diff --git a/Cargo.lock b/Cargo.lock index 452c19680..4fe937e61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -619,6 +619,12 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bsdiff" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7f2e6c4f2a017f63b5a1fd7cc437f061b53a3e890bcca840ef756d72f6b72f2" + [[package]] name = "bumpalo" version = "3.16.0" @@ -1406,6 +1412,7 @@ checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" dependencies = [ "futures-core", "futures-sink", + "nanorand", "spin", ] @@ -1642,8 +1649,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -2399,11 +2408,14 @@ dependencies = [ "ctor", "enum-display", "env_logger", + "flume", "futures", "insta", "itertools 0.13.0", "log", "martin-tile-utils", + "md5", + "num_cpus", "pretty_assertions", "rstest", "serde", @@ -2411,6 +2423,7 @@ dependencies = [ "serde_with", "serde_yaml", "size_format", + "sqlite-compressions", "sqlite-hashes", "sqlx", "thiserror", @@ -2429,6 +2442,12 @@ dependencies = [ "digest", ] +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + [[package]] name = "memchr" version = "2.7.4" @@ -2535,6 +2554,15 @@ dependencies = [ "serde", ] +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom", +] + [[package]] name = "nix" version = "0.26.4" @@ -4018,6 +4046,17 @@ dependencies = [ "unicode_categories", ] +[[package]] +name = "sqlite-compressions" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d005d84196a3cc75a3113058d333de388cef7eb665dbcee09e31bb8166a1382" +dependencies = [ + "bsdiff", + "flate2", + "rusqlite", +] + [[package]] name = "sqlite-hashes" version = "0.7.3" diff --git a/martin/src/pg/errors.rs b/martin/src/pg/errors.rs index 2b49bc7ef..58b40cdbd 100644 --- a/martin/src/pg/errors.rs +++ b/martin/src/pg/errors.rs @@ -31,6 +31,9 @@ pub enum PgError { #[error(transparent)] RustlsError(#[from] rustls::Error), + #[error("Unable to install default Rustls provider: {0}")] + RustlsInstallDefaultFailed(String), + #[error("Unknown SSL mode: {0:?}")] UnknownSslMode(deadpool_postgres::tokio_postgres::config::SslMode), diff --git a/mbtiles/.sqlx/query-e6b32e48654e75111e325f3c67b35f2a1e5e3be2f1cd60adaf50a15678513bfe.json b/mbtiles/.sqlx/query-e6b32e48654e75111e325f3c67b35f2a1e5e3be2f1cd60adaf50a15678513bfe.json deleted file mode 100644 index b2e5c09cb..000000000 --- a/mbtiles/.sqlx/query-e6b32e48654e75111e325f3c67b35f2a1e5e3be2f1cd60adaf50a15678513bfe.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT (\n -- 'bsdiffraw' table or view columns and their types are as expected:\n -- 5 columns (zoom_level, tile_column, tile_row, tile_data, tile_hash).\n -- The order is not important\n SELECT COUNT(*) = 5\n FROM pragma_table_info('bsdiffraw')\n WHERE ((name = 'zoom_level' AND type = 'INTEGER')\n OR (name = 'tile_column' AND type = 'INTEGER')\n OR (name = 'tile_row' AND type = 'INTEGER')\n OR (name = 'patch_data' AND type = 'BLOB')\n OR (name = 'uncompressed_tile_xxh3_64' AND type = 'INTEGER'))\n --\n ) as is_valid;", - "describe": { - "columns": [ - { - "name": "is_valid", - "ordinal": 0, - "type_info": "Int" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - null - ] - }, - "hash": "e6b32e48654e75111e325f3c67b35f2a1e5e3be2f1cd60adaf50a15678513bfe" -} diff --git a/mbtiles/src/bindiff.rs b/mbtiles/src/bindiff.rs index 9b643d921..b5a6f66b9 100644 --- a/mbtiles/src/bindiff.rs +++ b/mbtiles/src/bindiff.rs @@ -204,7 +204,7 @@ impl BinDiffer for BinDiffDiffer { fn process(&self, value: DifferBefore) -> MbtResult { let mut old_tile = value.old_tile_data; let mut new_tile = value.new_tile_data; - if self.patch_type == PatchType::BinDiff { + if self.patch_type == PatchType::BinDiffGz { old_tile = GzipEncoder::decode(&old_tile) .inspect_err(|e| error!("Unable to unzip source tile at {:?}: {e}", value.coord))?; new_tile = GzipEncoder::decode(&new_tile) @@ -336,7 +336,7 @@ impl BinDiffer for BinDiffPatcher { match self.dst_type { Flat =>"INSERT INTO tiles (zoom_level, tile_column, tile_row, tile_data) VALUES (?, ?, ?, ?)", FlatWithHash => "INSERT INTO tiles_with_hash (zoom_level, tile_column, tile_row, tile_data, tile_hash) VALUES (?, ?, ?, ?, ?)", - v => return Err(MbtError::BinDiffRequiresFlatWithHash(v)), + v @ Normalized { .. } => return Err(MbtError::BinDiffRequiresFlatWithHash(v)), }) .bind(value.coord.z) .bind(value.coord.x) diff --git a/mbtiles/src/copier.rs b/mbtiles/src/copier.rs index c42a5639d..288d4a278 100644 --- a/mbtiles/src/copier.rs +++ b/mbtiles/src/copier.rs @@ -16,13 +16,13 @@ use crate::mbtiles::PatchFileInfo; use crate::queries::{ create_tiles_with_hash_view, detach_db, init_mbtiles_schema, is_empty_database, }; -use crate::AggHashType::Verify; +use crate::AggHashType::{Update, Verify}; use crate::IntegrityCheckType::Quick; use crate::MbtType::{Flat, FlatWithHash, Normalized}; +use crate::PatchType::{BinDiffGz, BinDiffRaw, Whole}; use crate::{ - action_with_rusqlite, has_bsdiffraw, invert_y_value, reset_db_settings, CopyType, MbtError, - MbtType, MbtTypeCli, Mbtiles, AGG_TILES_HASH, AGG_TILES_HASH_AFTER_APPLY, - AGG_TILES_HASH_BEFORE_APPLY, + action_with_rusqlite, invert_y_value, reset_db_settings, CopyType, MbtError, MbtType, + MbtTypeCli, Mbtiles, AGG_TILES_HASH, AGG_TILES_HASH_AFTER_APPLY, AGG_TILES_HASH_BEFORE_APPLY, }; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, EnumDisplay)] @@ -42,7 +42,7 @@ pub enum PatchType { #[default] Whole, /// Use bin-diff to store only the bytes changed between two versions of each tile. Treats content as gzipped blobs, decoding them before diffing. - BinDiff, + BinDiffGz, /// Use bin-diff to store only the bytes changed between two versions of each tile. Treats content as blobs without any special encoding. BinDiffRaw, } @@ -230,7 +230,7 @@ impl MbtileCopierInt { dif_mbt.attach_to(&mut conn, "diffDb").await?; let dst_type = self.options.dst_type().unwrap_or(src_info.mbt_type); - if patch_type != PatchType::Whole && dst_type != FlatWithHash { + if patch_type != Whole && dst_type != FlatWithHash { return Err(MbtError::BinDiffRequiresFlatWithHash(dst_type)); } @@ -242,7 +242,11 @@ impl MbtileCopierInt { dif_type = dif_info.mbt_type, what = self.copy_text(), dst_path = self.dst_mbt.filepath(), - patch = if patch_type == PatchType::Whole { "" } else { " with bin-diff" } + patch = match patch_type { + Whole => {""} + BinDiffGz => {" with bin-diff"} + BinDiffRaw => {" with bin-diff-raw"} + } ); self.init_schema(&mut conn, src_info.mbt_type, dst_type) @@ -260,7 +264,7 @@ impl MbtileCopierInt { detach_db(&mut conn, "diffDb").await?; detach_db(&mut conn, "sourceDb").await?; - if patch_type != PatchType::Whole { + if patch_type != Whole { BinDiffDiffer::new(self.src_mbt.clone(), dif_mbt, dif_info.mbt_type, patch_type) .run(&mut conn, self.get_where_clause("srcTiles.")) .await?; @@ -291,14 +295,13 @@ impl MbtileCopierInt { async fn run_with_patch(self, dif_mbt: Mbtiles) -> MbtResult { let mut dif_conn = dif_mbt.open_readonly().await?; let dif_info = dif_mbt.examine_diff(&mut dif_conn).await?; - let use_bindiff = has_bsdiffraw(&mut dif_conn).await?; self.validate(&dif_mbt, &mut dif_conn).await?; dif_mbt.validate_diff_info(&dif_info, self.options.force)?; dif_conn.close().await?; let src_type = self.validate_src_file().await?.mbt_type; let dst_type = self.options.dst_type().unwrap_or(src_type); - if use_bindiff && dst_type != FlatWithHash { + if dif_info.patch_type != Whole && dst_type != FlatWithHash { return Err(MbtError::BinDiffRequiresFlatWithHash(dst_type)); } @@ -316,11 +319,12 @@ impl MbtileCopierInt { src_mbt = self.src_mbt, what = self.copy_text(), dst_path = self.dst_mbt.filepath(), - patch = if use_bindiff { - " with bin-diff" - } else { - "" - }); + patch = match dif_info.patch_type { + Whole => {""} + BinDiffGz => {" with bin-diff"} + BinDiffRaw => {" with bin-diff-raw"} + } + ); self.init_schema(&mut conn, src_type, dst_type).await?; self.copy_with_rusqlite( @@ -356,13 +360,23 @@ impl MbtileCopierInt { detach_db(&mut conn, "diffDb").await?; detach_db(&mut conn, "sourceDb").await?; - if use_bindiff { + if dif_info.patch_type != Whole { BinDiffPatcher::new(self.src_mbt.clone(), dif_mbt, dst_type) .run(&mut conn, self.get_where_clause("srcTiles.")) .await?; } - self.validate(&self.dst_mbt, &mut conn).await?; + let hash_type = if dif_info.patch_type == BinDiffGz { + Update + } else { + Verify + }; + + if self.options.validate { + self.dst_mbt.validate(&mut conn, Quick, hash_type).await?; + } else if hash_type == Update { + self.dst_mbt.update_agg_tiles_hash(&mut conn).await?; + } Ok(conn) } @@ -744,7 +758,7 @@ fn get_select_from_with_diff( }; } - let sql_cond = if patch_type == PatchType::Whole { + let sql_cond = if patch_type == Whole { "OR srcTiles.tile_data != difTiles.tile_data" } else { "" @@ -799,7 +813,6 @@ mod tests { use sqlx::{Decode, Sqlite, SqliteConnection, Type}; use super::*; - use crate::PatchType::Whole; // TODO: Most of these tests are duplicating the tests from tests/mbtiles.rs, and should be cleaned up/removed. diff --git a/mbtiles/src/errors.rs b/mbtiles/src/errors.rs index 8281673b0..e557bd020 100644 --- a/mbtiles/src/errors.rs +++ b/mbtiles/src/errors.rs @@ -104,6 +104,9 @@ pub enum MbtError { #[error("Internal error creating bin-diff table")] BindiffError, + + #[error("BinDiff patch files can be only applied with `mbtiles copy --apply-patch` command")] + UnsupportedPatchType, } pub type MbtResult = Result; diff --git a/mbtiles/src/mbtiles.rs b/mbtiles/src/mbtiles.rs index 9a8cc5ef3..04323cafb 100644 --- a/mbtiles/src/mbtiles.rs +++ b/mbtiles/src/mbtiles.rs @@ -11,7 +11,7 @@ use sqlx::sqlite::SqliteConnectOptions; use sqlx::{query, Connection as _, Executor, SqliteConnection, SqliteExecutor, Statement}; use crate::errors::{MbtError, MbtResult}; -use crate::{invert_y_value, CopyDuplicateMode, MbtType}; +use crate::{invert_y_value, CopyDuplicateMode, MbtType, PatchType}; #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize, EnumDisplay)] #[enum_display(case = "Kebab")] @@ -48,6 +48,7 @@ pub struct PatchFileInfo { pub agg_tiles_hash: Option, pub agg_tiles_hash_before_apply: Option, pub agg_tiles_hash_after_apply: Option, + pub patch_type: PatchType, } #[derive(Clone, Debug)] diff --git a/mbtiles/src/patcher.rs b/mbtiles/src/patcher.rs index 6650021cf..163b5f7fb 100644 --- a/mbtiles/src/patcher.rs +++ b/mbtiles/src/patcher.rs @@ -5,6 +5,7 @@ use sqlx::{query, Connection as _}; use crate::queries::detach_db; use crate::MbtType::{Flat, FlatWithHash, Normalized}; +use crate::PatchType::Whole; use crate::{ MbtError, MbtResult, MbtType, Mbtiles, AGG_TILES_HASH, AGG_TILES_HASH_AFTER_APPLY, AGG_TILES_HASH_BEFORE_APPLY, @@ -16,6 +17,9 @@ pub async fn apply_patch(base_file: PathBuf, patch_file: PathBuf, force: bool) - let mut conn = patch_mbt.open_readonly().await?; let patch_info = patch_mbt.examine_diff(&mut conn).await?; + if patch_info.patch_type != Whole { + return Err(MbtError::UnsupportedPatchType); + } patch_mbt.validate_diff_info(&patch_info, force)?; let patch_type = patch_info.mbt_type; conn.close().await?; diff --git a/mbtiles/src/queries.rs b/mbtiles/src/queries.rs index 892b16f06..464bead28 100644 --- a/mbtiles/src/queries.rs +++ b/mbtiles/src/queries.rs @@ -1,11 +1,11 @@ use log::debug; use martin_tile_utils::MAX_ZOOM; use sqlite_compressions::rusqlite::Connection; -use sqlx::{query, Executor as _, SqliteConnection, SqliteExecutor}; +use sqlx::{query, Executor as _, Row, SqliteConnection, SqliteExecutor}; use crate::errors::MbtResult; use crate::MbtError::InvalidZoomValue; -use crate::MbtType; +use crate::{MbtType, PatchType}; /// Returns true if the database is empty (no tables/indexes/...) pub async fn is_empty_database(conn: &mut T) -> MbtResult @@ -229,18 +229,23 @@ where Ok(()) } -/// Check if `MBTiles` has a table or a view named `bsdiffraw` with needed fields -pub async fn has_bsdiffraw(conn: &mut T) -> MbtResult +/// Check if `MBTiles` has a table or a view named `bsdiffraw` or `bsdiffrawgz` with needed fields, +/// and return the corresponding patch type. If missing, return `PatchType::Whole` +pub async fn get_patch_type(conn: &mut T) -> MbtResult where for<'e> &'e mut T: SqliteExecutor<'e>, { - let sql = query!( - "SELECT ( - -- 'bsdiffraw' table or view columns and their types are as expected: - -- 5 columns (zoom_level, tile_column, tile_row, tile_data, tile_hash). - -- The order is not important + for (tbl, pt) in [ + ("bsdiffraw", PatchType::BinDiffRaw), + ("bsdiffrawgz", PatchType::BinDiffGz), + ] { + // 'bsdiffraw' or 'bsdiffrawgz' table or view columns and their types are as expected: + // 5 columns (zoom_level, tile_column, tile_row, tile_data, tile_hash). + // The order is not important + let sql = format!( + "SELECT ( SELECT COUNT(*) = 5 - FROM pragma_table_info('bsdiffraw') + FROM pragma_table_info('{tbl}') WHERE ((name = 'zoom_level' AND type = 'INTEGER') OR (name = 'tile_column' AND type = 'INTEGER') OR (name = 'tile_row' AND type = 'INTEGER') @@ -248,14 +253,20 @@ where OR (name = 'uncompressed_tile_xxh3_64' AND type = 'INTEGER')) -- ) as is_valid;" - ); + ); + + if query(&sql) + .fetch_one(&mut *conn) + .await? + .get::, _>(0) + .unwrap_or_default() + == 1 + { + return Ok(pt); + } + } - Ok(sql - .fetch_one(&mut *conn) - .await? - .is_valid - .unwrap_or_default() - == 1) + Ok(PatchType::Whole) } pub async fn create_normalized_tables(conn: &mut T) -> MbtResult<()> diff --git a/mbtiles/src/validation.rs b/mbtiles/src/validation.rs index 8db5cac10..3178e4d68 100644 --- a/mbtiles/src/validation.rs +++ b/mbtiles/src/validation.rs @@ -20,7 +20,7 @@ use crate::MbtError::{ AggHashMismatch, AggHashValueNotFound, FailedIntegrityCheck, IncorrectTileHash, InvalidTileIndex, }; -use crate::{invert_y_value, Mbtiles}; +use crate::{get_patch_type, invert_y_value, Mbtiles}; /// Metadata key for the aggregate tiles hash value pub const AGG_TILES_HASH: &str = "agg_tiles_hash"; @@ -480,6 +480,7 @@ LIMIT 1;" agg_tiles_hash_after_apply: self .get_metadata_value(&mut *conn, AGG_TILES_HASH_AFTER_APPLY) .await?, + patch_type: get_patch_type(conn).await?, }; Ok(info) diff --git a/mbtiles/tests/copy.rs b/mbtiles/tests/copy.rs index b42b00b4d..fc07925f4 100644 --- a/mbtiles/tests/copy.rs +++ b/mbtiles/tests/copy.rs @@ -302,11 +302,9 @@ fn databases() -> Databases { } result.add("dif", mbt_typ, dmp, dif_mbt, Some(hash), dif_cn); - // ----------------- bin-diff-raw bdr (v1 -> v2) ----------------- + // ----------------- bdr (v1 -> v2) -- bin-diff-raw ----------------- if mbt_typ == FlatWithHash { let (bdr_mbt, mut bdr_cn) = open!(databases, "{typ}__bdr"); - eprintln!("TEST: bin-diff-raw (v1 -> v2) for {typ}..."); - eprintln!("INFO: bdr_mbt={bdr_mbt}"); copy! { result.path("v1", mbt_typ), path(&bdr_mbt), @@ -534,17 +532,14 @@ async fn diff_and_patch( async fn diff_and_patch_bsdiff( #[values(FlatWithHash)] a_type: MbtTypeCli, #[values(FlatWithHash)] b_type: MbtTypeCli, - #[values(None, Some(FlatWithHash))] dif_type: Option, + #[values(FlatWithHash)] dif_type: MbtTypeCli, #[values(BinDiffRaw)] patch_type: PatchType, - #[values(&[FlatWithHash])] destination_types: &[MbtTypeCli], - #[values( - ("v1", "v2", "dif"), - ("v1", "v1_clone", "dif_empty"))] - tilesets: (&'static str, &'static str, &'static str), + #[values(FlatWithHash)] dst_type: MbtTypeCli, + #[values(("v1", "v2", "bdr"))] tilesets: (&'static str, &'static str, &'static str), #[notrace] databases: &Databases, ) -> MbtResult<()> { let (a_db, b_db, dif_db) = tilesets; - let dif = dif_type.map_or("dflt", shorten); + let dif = shorten(dif_type); let prefix = format!( "{a_db}_{}--{b_db}_{}={dif}_{patch_type}", shorten(b_type), @@ -557,35 +552,21 @@ async fn diff_and_patch_bsdiff( databases.path(a_db, a_type), path(&dif_mbt), diff_with_file => Some((databases.path(b_db, b_type), patch_type)), - dst_type_cli => dif_type, + dst_type_cli => Some(dif_type), }; - pretty_assert_eq!( - &dump(&mut dif_cn).await?, - databases.dump(dif_db, dif_type.unwrap_or(a_type)) - ); - - for dst_type in destination_types { - let prefix = format!("{prefix}__to__{}", shorten(*dst_type)); - let expected_b = databases.dump(b_db, *dst_type); - - 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_bsdiff, "{prefix}__1"); - copy!(databases.path(a_db, *dst_type), path(&clone_mbt)); - apply_patch(path(&clone_mbt), path(&dif_mbt), false).await?; - let hash = clone_mbt.open_and_validate(Off, Verify).await?; - assert_eq!(hash, databases.hash(b_db, *dst_type)); - let dmp = dump(&mut clone_cn).await?; - pretty_assert_eq!(&dmp, expected_b); + pretty_assert_eq!(&dump(&mut dif_cn).await?, databases.dump(dif_db, dif_type)); - 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_bsdiff, "{prefix}__2"); - copy!(databases.path(b_db, *dst_type), path(&clone_mbt)); - apply_patch(path(&clone_mbt), path(&dif_mbt), true).await?; - let hash = clone_mbt.open_and_validate(Off, Verify).await?; - assert_eq!(hash, databases.hash(b_db, *dst_type)); - let dmp = dump(&mut clone_cn).await?; - pretty_assert_eq!(&dmp, expected_b); - } + let prefix = format!("{prefix}__to__{}", shorten(dst_type)); + let (b_mbt, mut b_cn) = open!(diff_and_patch_bsdiff, "{prefix}__{b_db}"); + copy! { + databases.path(a_db, a_type), + path(&b_mbt), + apply_patch => Some(databases.path(dif_db, dif_type)), + dst_type_cli => Some(dst_type), + }; + let actual = dump(&mut b_cn).await?; + let expected = databases.dump(b_db, dst_type); + pretty_assert_eq!(&actual, expected); Ok(()) } @@ -628,16 +609,10 @@ async fn test_one() { // Test convert // convert(Flat, Flat, &db).await.unwrap(); - // Test diff patch copy - let src_type = FlatWithHash; - let dif_type = FlatWithHash; - // let dst_type = Some(FlatWithHash); - let dst_type = None; - // diff_and_patch( - // src_type, - // dif_type, - // dst_type, + // FlatWithHash, + // FlatWithHash, + // None, // &[Flat], // ("v1", "v2", "dif"), // &db, @@ -646,20 +621,21 @@ async fn test_one() { // .unwrap(); diff_and_patch_bsdiff( - src_type, - dif_type, - dst_type, + FlatWithHash, + FlatWithHash, + FlatWithHash, BinDiffRaw, - &[Flat], - ("v1", "v2", "dif"), + FlatWithHash, + ("v1", "v2", "bdr"), &db, ) .await .unwrap(); - patch_on_copy(src_type, dif_type, dst_type, &db) - .await - .unwrap(); + // patch_on_copy(FlatWithHash, FlatWithHash, None, &db) + // .await + // .unwrap(); + panic!("ALWAYS FAIL - this test is for debugging only, and should be disabled"); } diff --git a/tests/test.sh b/tests/test.sh index 6371cc76f..5e8f66c54 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -463,6 +463,27 @@ if [[ "$MBTILES_BIN" != "-" ]]; then ./tests/fixtures/mbtiles/world_cities_modified.mbtiles \ "$TEST_TEMP_DIR/world_cities_diff2.mbtiles" \ 2>&1 | tee "$TEST_OUT_DIR/copy_diff2.txt" + + $MBTILES_BIN copy \ + ./tests/fixtures/mbtiles/world_cities.mbtiles \ + --diff-with-file ./tests/fixtures/mbtiles/world_cities_modified.mbtiles \ + "$TEST_TEMP_DIR/world_cities_bindiff.mbtiles" \ + --patch-type bin-diff-gz \ + 2>&1 | tee "$TEST_OUT_DIR/copy_bindiff.txt" + $MBTILES_BIN copy \ + ./tests/fixtures/mbtiles/world_cities.mbtiles \ + --apply-patch "$TEST_TEMP_DIR/world_cities_bindiff.mbtiles" \ + "$TEST_TEMP_DIR/world_cities_modified2.mbtiles" \ + 2>&1 | tee "$TEST_OUT_DIR/copy_bindiff2.txt" + # Ensure that world_cities_modified and world_cities_modified2 are identical (regular diff is empty) + $MBTILES_BIN copy \ + ./tests/fixtures/mbtiles/world_cities_modified.mbtiles \ + --diff-with-file "$TEST_TEMP_DIR/world_cities_modified2.mbtiles" \ + "$TEST_TEMP_DIR/world_cities_bindiff_modified.mbtiles" \ + 2>&1 | tee "$TEST_OUT_DIR/copy_bindiff3.txt" + $MBTILES_BIN summary "$TEST_TEMP_DIR/world_cities_bindiff_modified.mbtiles" \ + 2>&1 | tee "$TEST_OUT_DIR/copy_bindiff4.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"