Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to copy to existing mbtiles files #778

Merged
merged 15 commits into from
Jul 27, 2023
Merged
2 changes: 1 addition & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ git-pre-push: stop start
# Update sqlite database schema.
prepare-sqlite: install-sqlx
mkdir -p martin-mbtiles/.sqlx
cd martin-mbtiles && cargo sqlx prepare --database-url sqlite://$PWD/../tests/fixtures/files/world_cities.mbtiles
cd martin-mbtiles && cargo sqlx prepare --database-url sqlite://$PWD/../tests/fixtures/files/world_cities.mbtiles -- --lib --tests

# Install SQLX cli if not already installed.
[private]
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 64 additions & 1 deletion martin-mbtiles/src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ mod tests {

use clap::error::ErrorKind;
use clap::Parser;
use martin_mbtiles::TileCopierOptions;
use martin_mbtiles::{CopyDuplicateMode, TileCopierOptions};

use crate::Args;
use crate::Commands::{ApplyDiff, Copy, MetaGetValue};
Expand Down Expand Up @@ -245,6 +245,69 @@ mod tests {
);
}

#[test]
fn test_copy_diff_with_override_copy_duplicate_mode() {
assert_eq!(
Args::parse_from([
"mbtiles",
"copy",
"src_file",
"dst_file",
"--on-duplicate",
"override"
]),
Args {
verbose: false,
command: Copy(
TileCopierOptions::new(PathBuf::from("src_file"), PathBuf::from("dst_file"))
.on_duplicate(CopyDuplicateMode::Override)
)
}
);
}

#[test]
fn test_copy_diff_with_ignore_copy_duplicate_mode() {
assert_eq!(
Args::parse_from([
"mbtiles",
"copy",
"src_file",
"dst_file",
"--on-duplicate",
"ignore"
]),
Args {
verbose: false,
command: Copy(
TileCopierOptions::new(PathBuf::from("src_file"), PathBuf::from("dst_file"))
.on_duplicate(CopyDuplicateMode::Ignore)
)
}
);
}

#[test]
fn test_copy_diff_with_abort_copy_duplicate_mode() {
assert_eq!(
Args::parse_from([
"mbtiles",
"copy",
"src_file",
"dst_file",
"--on-duplicate",
"abort"
]),
Args {
verbose: false,
command: Copy(
TileCopierOptions::new(PathBuf::from("src_file"), PathBuf::from("dst_file"))
.on_duplicate(CopyDuplicateMode::Abort)
)
}
);
}

#[test]
fn test_meta_get_no_arguments() {
assert_eq!(
Expand Down
9 changes: 9 additions & 0 deletions martin-mbtiles/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ pub enum MbtError {

#[error("The destination file {0} is non-empty")]
NonEmptyTargetFile(PathBuf),

#[error("The file {0} does not have the required uniqueness constraint")]
NoUniquenessConstraint(String),

#[error("Could not copy MBTiles file: {reason}")]
UnsupportedCopyOperation { reason: String },

#[error("Unexpected duplicate tiles found when copying")]
DuplicateValues,
}

pub type MbtResult<T> = Result<T, MbtError>;
4 changes: 3 additions & 1 deletion martin-mbtiles/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ mod tile_copier;
pub use errors::MbtError;
pub use mbtiles::{Mbtiles, Metadata};
pub use mbtiles_pool::MbtilesPool;
pub use tile_copier::{apply_mbtiles_diff, copy_mbtiles_file, TileCopierOptions};
pub use tile_copier::{
apply_mbtiles_diff, copy_mbtiles_file, CopyDuplicateMode, TileCopierOptions,
};
64 changes: 58 additions & 6 deletions martin-mbtiles/src/mbtiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

extern crate core;

use std::collections::HashSet;
use std::ffi::OsStr;
use std::fmt::Display;
use std::path::Path;
Expand All @@ -11,7 +12,7 @@ use futures::TryStreamExt;
use log::{debug, info, warn};
use martin_tile_utils::{Format, TileInfo};
use serde_json::{Value as JSONValue, Value};
use sqlx::{query, SqliteExecutor};
use sqlx::{query, Row, SqliteExecutor};
use tilejson::{tilejson, Bounds, Center, TileJSON};

use crate::errors::{MbtError, MbtResult};
Expand All @@ -26,7 +27,7 @@ pub struct Metadata {
pub json: Option<JSONValue>,
}

#[derive(Debug, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
pub enum MbtType {
TileTables,
DeDuplicated,
Expand Down Expand Up @@ -280,13 +281,64 @@ impl Mbtiles {
where
for<'e> &'e mut T: SqliteExecutor<'e>,
{
if is_deduplicated_type(&mut *conn).await? {
Ok(MbtType::DeDuplicated)
let mbt_type = if is_deduplicated_type(&mut *conn).await? {
MbtType::DeDuplicated
} else if is_tile_tables_type(&mut *conn).await? {
Ok(MbtType::TileTables)
MbtType::TileTables
} else {
Err(MbtError::InvalidDataFormat(self.filepath.clone()))
return Err(MbtError::InvalidDataFormat(self.filepath.clone()));
};

self.check_for_uniqueness_constraint(&mut *conn, &mbt_type)
.await?;

Ok(mbt_type)
}

async fn check_for_uniqueness_constraint<T>(
&self,
conn: &mut T,
mbt_type: &MbtType,
) -> MbtResult<()>
where
for<'e> &'e mut T: SqliteExecutor<'e>,
{
let table_name = match mbt_type {
MbtType::TileTables => "tiles",
MbtType::DeDuplicated => "map",
};

let indexes = query("SELECT name FROM pragma_index_list(?) WHERE [unique] = 1")
.bind(table_name)
.fetch_all(&mut *conn)
.await?;

// Ensure there is some index on tiles that has a unique constraint on (zoom_level, tile_row, tile_column)
for index in indexes {
let mut unique_idx_cols = HashSet::new();
let rows = query("SELECT DISTINCT name FROM pragma_index_info(?)")
.bind(index.get::<String, _>("name"))
.fetch_all(&mut *conn)
.await?;

for row in rows {
unique_idx_cols.insert(row.get("name"));
}

if unique_idx_cols
.symmetric_difference(&HashSet::from([
"zoom_level".to_string(),
"tile_column".to_string(),
"tile_row".to_string(),
]))
.collect::<Vec<_>>()
.is_empty()
{
return Ok(());
}
}

Err(MbtError::NoUniquenessConstraint(self.filepath.clone()))
}
}

Expand Down
Loading
Loading