use geopackage_core::geometry::{self, GeometryError};
use geopackage_core::gpb::GpbError;
use rusqlite::Connection;
use rusqlite::functions::{Context, FunctionFlags};
use rusqlite::types::ValueRef;
pub fn register(conn: &Connection) -> rusqlite::Result<()> {
let flags = FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC;
conn.create_scalar_function("ST_IsEmpty", 1, flags, |ctx| {
with_blob(ctx, |blob| Ok(Some(geometry::blob_is_empty(blob)?)))
})?;
type Select = fn([f64; 4]) -> f64;
for (name, select) in [
("ST_MinX", (|[min_x, _, _, _]| min_x) as Select),
("ST_MaxX", |[_, max_x, _, _]| max_x),
("ST_MinY", |[_, _, min_y, _]| min_y),
("ST_MaxY", |[_, _, _, max_y]| max_y),
] {
conn.create_scalar_function(name, 1, flags, move |ctx| {
with_blob(ctx, |blob| {
match geometry::blob_xy_envelope(blob)? {
Some(bounds) => Ok(Some(select(bounds))),
None => Ok(Some(f64::NAN)),
}
})
})?;
}
Ok(())
}
#[derive(Debug, thiserror::Error)]
enum FnError {
#[error("argument is not a BLOB")]
NotABlob,
#[error(transparent)]
Gpb(#[from] GpbError),
#[error(transparent)]
Geometry(#[from] GeometryError),
}
fn with_blob<T: rusqlite::types::ToSql>(
ctx: &Context<'_>,
f: impl FnOnce(&[u8]) -> Result<Option<T>, FnError>,
) -> rusqlite::Result<Option<T>> {
match ctx.get_raw(0) {
ValueRef::Null => Ok(None),
ValueRef::Blob(b) => f(b).map_err(|e| rusqlite::Error::UserFunctionError(Box::new(e))),
_ => Err(rusqlite::Error::UserFunctionError(Box::new(
FnError::NotABlob,
))),
}
}