use crate::{GeoPackage, Result, resolve_table_name, table_exists};
use geopackage_core::GpkgVersion;
use geopackage_core::extensions::{ExtensionScope, ExtensionSupport};
use geopackage_core::version::{APPLICATION_ID_GP10, APPLICATION_ID_GP11};
use rusqlite::Connection;
use std::path::Path;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum OpenWarning {
LegacyApplicationId {
version: GpkgVersion,
application_id: u32,
},
MissingGeometryColumns,
TableNameCaseMismatch {
declared: String,
actual: String,
},
UnsupportedExtension {
extension_name: String,
table_name: Option<String>,
scope: ExtensionScope,
},
}
impl std::fmt::Display for OpenWarning {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::LegacyApplicationId {
version,
application_id,
} => {
let bytes = application_id.to_be_bytes();
let tag = String::from_utf8_lossy(&bytes);
write!(
f,
"file declares the GeoPackage {version} application_id {tag:?}, which predates the current \"GPKG\""
)
}
Self::MissingGeometryColumns => write!(
f,
"no gpkg_geometry_columns table: the file carries no feature layers"
),
Self::TableNameCaseMismatch { declared, actual } => write!(
f,
"gpkg_contents says {declared:?} but the table is {actual:?}: they differ only in case"
),
Self::UnsupportedExtension {
extension_name,
table_name,
scope,
} => {
let on = match table_name {
Some(table) => format!(" on {table:?}"),
None => String::new(),
};
write!(
f,
"extension {extension_name:?}{on} is not one this crate recognises (scope {scope}); writes to what it covers are refused"
)
}
}
}
}
impl GeoPackage {
pub fn open_lenient<P: AsRef<Path>>(path: P) -> Result<Self> {
crate::OpenOptions::new().lenient(true).open(path)
}
pub fn open_read_only_lenient<P: AsRef<Path>>(path: P) -> Result<Self> {
crate::OpenOptions::new().lenient(true).open_read_only(path)
}
pub fn open_warnings(&self) -> &[OpenWarning] {
&self.warnings
}
}
pub(crate) fn collect_warnings(
conn: &Connection,
application_id: u32,
version: GpkgVersion,
) -> Result<Vec<OpenWarning>> {
let mut warnings = Vec::new();
if application_id == APPLICATION_ID_GP10 || application_id == APPLICATION_ID_GP11 {
warnings.push(OpenWarning::LegacyApplicationId {
version,
application_id,
});
}
if !table_exists(conn, "gpkg_geometry_columns")? {
warnings.push(OpenWarning::MissingGeometryColumns);
}
collect_case_mismatches(conn, &mut warnings)?;
collect_unsupported_extensions(conn, &mut warnings)?;
Ok(warnings)
}
fn collect_unsupported_extensions(
conn: &Connection,
warnings: &mut Vec<OpenWarning>,
) -> Result<()> {
for row in crate::extensions::read_all(conn)? {
if row.support() == ExtensionSupport::Unrecognised {
warnings.push(OpenWarning::UnsupportedExtension {
extension_name: row.name,
table_name: row.table_name,
scope: row.scope,
});
}
}
Ok(())
}
fn collect_case_mismatches(conn: &Connection, warnings: &mut Vec<OpenWarning>) -> Result<()> {
let declared_names: Vec<String> = {
let mut stmt = conn.prepare("SELECT table_name FROM gpkg_contents")?;
stmt.query_map([], |r| r.get(0))?
.collect::<rusqlite::Result<_>>()?
};
for declared in declared_names {
if let Some(actual) = resolve_table_name(conn, &declared)?
&& actual != declared
{
warnings.push(OpenWarning::TableNameCaseMismatch { declared, actual });
}
}
Ok(())
}