use crate::{Error, GeoPackage, Result, table_exists};
use geopackage_core::ident;
use geopackage_core::types::{ColumnType, GeometryType, ZmFlag};
use rusqlite::OptionalExtension;
#[derive(Debug, Clone, PartialEq)]
pub struct GeometryColumn {
pub table_name: String,
pub column_name: String,
pub geometry_type: GeometryType,
pub srs_id: i32,
pub z: ZmFlag,
pub m: ZmFlag,
}
impl GeometryColumn {
fn from_raw(
table_name: String,
column_name: String,
geometry_type_name: String,
srs_id: i32,
z: i64,
m: i64,
) -> Result<Self> {
let geometry_type =
GeometryType::parse(&geometry_type_name).ok_or_else(|| Error::UnknownGeometryType {
table_name: table_name.clone(),
name: geometry_type_name,
})?;
let z = zm_flag(&table_name, "z", z)?;
let m = zm_flag(&table_name, "m", m)?;
Ok(Self {
table_name,
column_name,
geometry_type,
srs_id,
z,
m,
})
}
}
fn zm_flag(table_name: &str, column: &'static str, value: i64) -> Result<ZmFlag> {
u8::try_from(value)
.ok()
.and_then(ZmFlag::from_code)
.ok_or(Error::InvalidZmFlag {
table_name: table_name.to_owned(),
column,
value,
})
}
const GEOMETRY_COLUMNS_SELECT: &str =
"SELECT table_name, column_name, geometry_type_name, srs_id, z, m FROM gpkg_geometry_columns";
impl GeoPackage {
pub fn geometry_column(&self, table_name: &str) -> Result<Option<GeometryColumn>> {
if !table_exists(self.connection(), "gpkg_geometry_columns")? {
return Ok(None);
}
let raw = self
.connection()
.query_row(
&format!("{GEOMETRY_COLUMNS_SELECT} WHERE table_name = ?1"),
[table_name],
|r| {
Ok((
r.get::<_, String>(0)?,
r.get::<_, String>(1)?,
r.get::<_, String>(2)?,
r.get::<_, i32>(3)?,
r.get::<_, i64>(4)?,
r.get::<_, i64>(5)?,
))
},
)
.optional()?;
raw.map(|(t, c, g, s, z, m)| GeometryColumn::from_raw(t, c, g, s, z, m))
.transpose()
}
pub(crate) fn geometry_column_ci(&self, table_name: &str) -> Result<Option<GeometryColumn>> {
if !table_exists(self.connection(), "gpkg_geometry_columns")? {
return Ok(None);
}
let raw = self
.connection()
.query_row(
&format!("{GEOMETRY_COLUMNS_SELECT} WHERE table_name = ?1 COLLATE NOCASE"),
[table_name],
|r| {
Ok((
r.get::<_, String>(0)?,
r.get::<_, String>(1)?,
r.get::<_, String>(2)?,
r.get::<_, i32>(3)?,
r.get::<_, i64>(4)?,
r.get::<_, i64>(5)?,
))
},
)
.optional()?;
raw.map(|(t, c, g, s, z, m)| GeometryColumn::from_raw(t, c, g, s, z, m))
.transpose()
}
pub fn geometry_columns(&self) -> Result<Vec<GeometryColumn>> {
if !table_exists(self.connection(), "gpkg_geometry_columns")? {
return Ok(Vec::new());
}
let mut stmt = self
.connection()
.prepare(&format!("{GEOMETRY_COLUMNS_SELECT} ORDER BY table_name"))?;
let raw = stmt
.query_map([], |r| {
Ok((
r.get::<_, String>(0)?,
r.get::<_, String>(1)?,
r.get::<_, String>(2)?,
r.get::<_, i32>(3)?,
r.get::<_, i64>(4)?,
r.get::<_, i64>(5)?,
))
})?
.collect::<rusqlite::Result<Vec<_>>>()?;
raw.into_iter()
.map(|(t, c, g, s, z, m)| GeometryColumn::from_raw(t, c, g, s, z, m))
.collect()
}
pub fn table_schema(&self, table_name: &str) -> Result<TableSchema> {
let sql = format!("PRAGMA table_info({})", ident::quote(table_name)?);
let mut stmt = self.connection().prepare(&sql)?;
let columns = stmt
.query_map([], |r| {
let declared_type: String = r.get(2)?;
Ok(Column {
name: r.get(1)?,
column_type: ColumnType::parse(&declared_type),
declared_type,
not_null: r.get::<_, i64>(3)? != 0,
default_value: r.get(4)?,
primary_key: r.get::<_, u32>(5)?,
})
})?
.collect::<rusqlite::Result<Vec<_>>>()?;
if columns.is_empty() {
return Err(Error::NoSuchTable {
table_name: table_name.to_owned(),
});
}
Ok(TableSchema {
table_name: table_name.to_owned(),
columns,
geometry_column: self.geometry_column(table_name)?,
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Column {
pub name: String,
pub declared_type: String,
pub column_type: Option<ColumnType>,
pub not_null: bool,
pub default_value: Option<String>,
pub primary_key: u32,
}
impl Column {
pub fn is_primary_key(&self) -> bool {
self.primary_key != 0
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TableSchema {
pub table_name: String,
pub columns: Vec<Column>,
pub geometry_column: Option<GeometryColumn>,
}
impl TableSchema {
pub fn column(&self, name: &str) -> Option<&Column> {
self.columns.iter().find(|c| c.name == name)
}
pub fn primary_key_columns(&self) -> Vec<&Column> {
let mut pk: Vec<&Column> = self.columns.iter().filter(|c| c.is_primary_key()).collect();
pk.sort_by_key(|c| c.primary_key);
pk
}
pub fn primary_key(&self) -> Option<&Column> {
let pk = self.primary_key_columns();
match pk.as_slice() {
[single] => Some(single),
_ => None,
}
}
}