use crate::{
Error, GeoPackage, Result, functions, read_header_u32, resolve_table_name, table_exists,
};
use geopackage_core::GpkgVersion;
use geopackage_core::version::{APPLICATION_ID_GP10, APPLICATION_ID_GP11};
use rusqlite::{Connection, OpenFlags};
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,
},
}
impl GeoPackage {
pub fn open_lenient<P: AsRef<Path>>(path: P) -> Result<Self> {
let conn = Connection::open_with_flags(path, OpenFlags::SQLITE_OPEN_READ_WRITE)?;
Self::from_connection_lenient(conn)
}
pub fn open_warnings(&self) -> &[OpenWarning] {
&self.warnings
}
fn from_connection_lenient(conn: Connection) -> Result<Self> {
let application_id = read_header_u32(&conn, "application_id")?;
let user_version = read_header_u32(&conn, "user_version")?;
let version = GpkgVersion::from_pragmas(application_id, user_version).ok_or(
Error::NotAGeoPackage {
reason: "unrecognized application_id/user_version",
application_id,
user_version,
},
)?;
for required in ["gpkg_spatial_ref_sys", "gpkg_contents"] {
if !table_exists(&conn, required)? {
return Err(Error::NotAGeoPackage {
reason: "missing required core table",
application_id,
user_version,
});
}
}
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)?;
functions::register(&conn)?;
Ok(Self {
conn: Some(conn),
version,
warnings,
journal_mode: crate::JournalMode::Delete,
})
}
}
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(())
}