diff --git a/Cargo.lock b/Cargo.lock index fbdfe457a..097c9d88e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2978,9 +2978,9 @@ dependencies = [ [[package]] name = "sqlite-hashes" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f756a7c1f66e2d70c9acb5881776ba0ae25ba2aaf68e2f69ed32d96c42313fab" +checksum = "fd203121770e67b5f689ebf9592c88d3529193743f35630413f419be8ef1e835" dependencies = [ "digest", "md-5", diff --git a/docs/src/tools.md b/docs/src/tools.md index d59d8e6cb..a2e35b5e0 100644 --- a/docs/src/tools.md +++ b/docs/src/tools.md @@ -74,7 +74,7 @@ The original [MBTiles specification](https://github.com/mapbox/mbtiles-spec#read A typical Normalized schema generated by tools like [tilelive-copy](https://github.com/mapbox/TileLive#bintilelive-copy) use MD5 hash in the `tile_id` column. The Martin's `mbtiles` tool can use this hash to verify the content of each tile. We also define a new `flat-with-hash` schema that stores the hash and tile data in the same table. This schema is more efficient than the `normalized` schema when data has no duplicate tiles (see below). Per tile validation is not available for `flat` schema. -Per-tile validation will catch individual invalid tiles, but it will not detect overall datastore corruption (e.g. missing tiles or tiles that shouldn't exist, or tiles with incorrect z/x/y values). For that, Martin `mbtiles` tool defines a new metadata value called `agg_tiles_hash`. The value is computed by hashing `cast(zoom_level AS text), cast(tile_column AS text), cast(tile_row AS text), tile_data` combined for all rows in the `tiles` table/view, ordered by z,x,y. In case there are no rows or all are NULL, the hash value of an empty string is used. +Per-tile validation will catch individual invalid tiles, but it will not detect overall datastore corruption (e.g. missing tiles or tiles that shouldn't exist, or tiles with incorrect z/x/y values). For that, Martin `mbtiles` tool defines a new metadata value called `agg_tiles_hash`. The value is computed by hashing `cast(zoom_level AS text), cast(tile_column AS text), cast(tile_row AS text), cast(tile_data as blob)` combined for all rows in the `tiles` table/view, ordered by z,x,y. In case there are no rows or all are NULL, the hash value of an empty string is used. The `mbtiles` tool will compute `agg_tiles_hash` value when copying or validating mbtiles files. diff --git a/martin-mbtiles/src/mbtiles.rs b/martin-mbtiles/src/mbtiles.rs index ce4bb93eb..b37cbd8df 100644 --- a/martin-mbtiles/src/mbtiles.rs +++ b/martin-mbtiles/src/mbtiles.rs @@ -564,20 +564,21 @@ where let query = query( // The md5_concat func will return NULL if there are no rows in the tiles table. // For our use case, we will treat it as an empty string, and hash that. + // Note that in some weird rare cases, a column with blob type may be stored as an integer value "SELECT - hex( - coalesce( - md5_concat( - cast(zoom_level AS text), - cast(tile_column AS text), - cast(tile_row AS text), - tile_data - ), - md5('') - ) - ) - FROM tiles - ORDER BY zoom_level, tile_column, tile_row;", + hex( + coalesce( + md5_concat( + cast(zoom_level AS text), + cast(tile_column AS text), + cast(tile_row AS text), + cast(tile_data as blob) + ), + md5('') + ) + ) + FROM tiles + ORDER BY zoom_level, tile_column, tile_row;", ); Ok(query.fetch_one(conn).await?.get::(0)) }