use crate::error::GpkgError;
use wkb::reader::{Dimension, GeometryType, Wkb};
#[derive(Clone, Copy, Debug, PartialEq)]
#[repr(C)]
pub enum ColumnType {
Boolean,
Varchar,
Double,
Integer,
Geometry,
Blob,
Date,
Datetime,
}
#[derive(Clone, Debug)]
pub struct ColumnSpec {
pub name: String,
pub column_type: ColumnType,
}
#[derive(Clone, Debug)]
pub struct GpkgLayerMetadata {
pub primary_key_column: String,
pub geometry_column: String,
pub geometry_type: GeometryType,
pub geometry_dimension: Dimension,
pub srs_id: u32,
pub other_columns: Vec<ColumnSpec>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Value {
Null,
Integer(i64),
Real(f64),
Text(String),
Blob(Vec<u8>),
Geometry(Vec<u8>), }
impl From<&str> for Value {
fn from(value: &str) -> Self {
Value::Text(value.to_string())
}
}
impl From<String> for Value {
fn from(value: String) -> Self {
Value::Text(value)
}
}
impl From<bool> for Value {
fn from(value: bool) -> Self {
Value::Integer(if value { 1 } else { 0 })
}
}
macro_rules! impl_from_int {
($($t:ty),+ $(,)?) => {
$(
impl From<$t> for Value {
#[inline]
fn from(value: $t) -> Self {
Value::Integer(value as i64)
}
}
)+
};
}
macro_rules! impl_from_uint {
($($t:ty),+ $(,)?) => {
$(
impl From<$t> for Value {
#[inline]
fn from(value: $t) -> Self {
Value::Integer(value as i64)
}
}
)+
};
}
impl_from_int!(i8, i16, i32, i64, isize);
impl_from_uint!(u8, u16, u32, u64, usize);
impl From<f32> for Value {
#[inline]
fn from(value: f32) -> Self {
Value::Real(value as f64)
}
}
impl From<f64> for Value {
fn from(value: f64) -> Self {
Value::Real(value)
}
}
impl<T: Into<Value>> From<Option<T>> for Value {
fn from(value: Option<T>) -> Self {
match value {
Some(v) => v.into(),
None => Value::Null,
}
}
}
#[macro_export]
macro_rules! params {
() => {
&[]
};
($($value:expr),+ $(,)?) => {
&[$($crate::Value::from($value)),+]
};
}
#[inline]
fn value_to_sql_output(value: &Value) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
use rusqlite::types::{ToSqlOutput, ValueRef};
let output = match value {
Value::Null => ToSqlOutput::Borrowed(ValueRef::Null),
Value::Integer(v) => ToSqlOutput::Borrowed(ValueRef::Integer(*v)),
Value::Real(v) => ToSqlOutput::Borrowed(ValueRef::Real(*v)),
Value::Text(s) => ToSqlOutput::Borrowed(ValueRef::Text(s.as_bytes())),
Value::Blob(items) | Value::Geometry(items) => {
ToSqlOutput::Borrowed(ValueRef::Blob(items.as_slice()))
}
};
Ok(output)
}
impl rusqlite::ToSql for Value {
#[inline]
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
value_to_sql_output(self)
}
}
impl From<rusqlite::types::Value> for Value {
#[inline]
fn from(value: rusqlite::types::Value) -> Self {
match value {
rusqlite::types::Value::Null => Value::Null,
rusqlite::types::Value::Integer(value) => Value::Integer(value),
rusqlite::types::Value::Real(value) => Value::Real(value),
rusqlite::types::Value::Text(value) => Value::Text(value),
rusqlite::types::Value::Blob(value) => Value::Blob(value),
}
}
}
impl<'a> From<rusqlite::types::ValueRef<'a>> for Value {
#[inline]
fn from(value: rusqlite::types::ValueRef<'a>) -> Self {
match value {
rusqlite::types::ValueRef::Null => Value::Null,
rusqlite::types::ValueRef::Integer(value) => Value::Integer(value),
rusqlite::types::ValueRef::Real(value) => Value::Real(value),
rusqlite::types::ValueRef::Text(value) => {
let s = std::str::from_utf8(value).expect("invalid UTF-8");
Value::Text(s.to_string())
}
rusqlite::types::ValueRef::Blob(value) => Value::Blob(value.to_vec()),
}
}
}
impl From<Value> for rusqlite::types::Value {
#[inline]
fn from(value: Value) -> Self {
match value {
Value::Null => rusqlite::types::Value::Null,
Value::Integer(value) => rusqlite::types::Value::Integer(value),
Value::Real(value) => rusqlite::types::Value::Real(value),
Value::Text(value) => rusqlite::types::Value::Text(value),
Value::Blob(value) | Value::Geometry(value) => rusqlite::types::Value::Blob(value),
}
}
}
#[inline]
fn value_type_name(value: &Value) -> &'static str {
match value {
Value::Null => "NULL",
Value::Integer(_) => "INTEGER",
Value::Real(_) => "REAL",
Value::Text(_) => "TEXT",
Value::Blob(_) => "BLOB",
Value::Geometry(_) => "GEOMETRY",
}
}
#[inline]
fn invalid_type(expected: &'static str, value: &Value) -> GpkgError {
GpkgError::ValueTypeMismatch {
expected,
actual: value_type_name(value),
}
}
#[inline]
fn out_of_range(expected: &'static str) -> GpkgError {
GpkgError::ValueOutOfRange { target: expected }
}
macro_rules! impl_try_from_int_ref {
($t:ty) => {
impl TryFrom<&Value> for $t {
type Error = GpkgError;
#[inline]
fn try_from(value: &Value) -> Result<Self, Self::Error> {
match value {
Value::Integer(v) => {
<$t>::try_from(*v).map_err(|_| out_of_range(stringify!($t)))
}
_ => Err(invalid_type(stringify!($t), value)),
}
}
}
impl TryFrom<Value> for $t {
type Error = GpkgError;
#[inline]
fn try_from(value: Value) -> Result<Self, Self::Error> {
(&value).try_into()
}
}
};
}
impl TryFrom<&Value> for i64 {
type Error = GpkgError;
#[inline]
fn try_from(value: &Value) -> Result<Self, Self::Error> {
match value {
Value::Integer(v) => Ok(*v),
_ => Err(invalid_type("i64", value)),
}
}
}
impl TryFrom<Value> for i64 {
type Error = GpkgError;
#[inline]
fn try_from(value: Value) -> Result<Self, Self::Error> {
(&value).try_into()
}
}
impl_try_from_int_ref!(i32);
impl_try_from_int_ref!(i16);
impl_try_from_int_ref!(i8);
impl_try_from_int_ref!(isize);
impl_try_from_int_ref!(u64);
impl_try_from_int_ref!(u32);
impl_try_from_int_ref!(u16);
impl_try_from_int_ref!(u8);
impl_try_from_int_ref!(usize);
impl TryFrom<&Value> for f64 {
type Error = GpkgError;
#[inline]
fn try_from(value: &Value) -> Result<Self, Self::Error> {
match value {
Value::Real(v) => Ok(*v),
Value::Integer(v) => Ok(*v as f64),
_ => Err(invalid_type("f64", value)),
}
}
}
impl TryFrom<Value> for f64 {
type Error = GpkgError;
#[inline]
fn try_from(value: Value) -> Result<Self, Self::Error> {
(&value).try_into()
}
}
impl TryFrom<&Value> for f32 {
type Error = GpkgError;
#[inline]
fn try_from(value: &Value) -> Result<Self, Self::Error> {
match value {
Value::Real(v) => Ok(*v as f32),
Value::Integer(v) => Ok(*v as f32),
_ => Err(invalid_type("f32", value)),
}
}
}
impl TryFrom<Value> for f32 {
type Error = GpkgError;
#[inline]
fn try_from(value: Value) -> Result<Self, Self::Error> {
(&value).try_into()
}
}
impl TryFrom<&Value> for bool {
type Error = GpkgError;
#[inline]
fn try_from(value: &Value) -> Result<Self, Self::Error> {
match value {
Value::Integer(0) => Ok(false),
Value::Integer(1) => Ok(true),
_ => Err(invalid_type("bool", value)),
}
}
}
impl TryFrom<Value> for bool {
type Error = GpkgError;
#[inline]
fn try_from(value: Value) -> Result<Self, Self::Error> {
(&value).try_into()
}
}
impl<T> TryFrom<&Value> for Option<T>
where
T: for<'a> TryFrom<&'a Value, Error = GpkgError>,
{
type Error = GpkgError;
#[inline]
fn try_from(value: &Value) -> Result<Self, Self::Error> {
match value {
Value::Null => Ok(None),
_ => Ok(Some(T::try_from(value)?)),
}
}
}
impl<T> TryFrom<Value> for Option<T>
where
T: for<'a> TryFrom<&'a Value, Error = GpkgError>,
{
type Error = GpkgError;
#[inline]
fn try_from(value: Value) -> Result<Self, Self::Error> {
(&value).try_into()
}
}
impl TryFrom<Value> for String {
type Error = GpkgError;
#[inline]
fn try_from(value: Value) -> Result<Self, Self::Error> {
match value {
Value::Text(s) => Ok(s),
other => Err(invalid_type("String", &other)),
}
}
}
impl<'a> TryFrom<&'a Value> for &'a str {
type Error = GpkgError;
#[inline]
fn try_from(value: &'a Value) -> Result<Self, Self::Error> {
match value {
Value::Text(s) => Ok(s.as_str()),
_ => Err(invalid_type("&str", value)),
}
}
}
impl<'a> TryFrom<&'a Value> for Wkb<'a> {
type Error = GpkgError;
#[inline]
fn try_from(value: &'a Value) -> Result<Self, Self::Error> {
match value {
Value::Geometry(bytes) => crate::gpkg::gpkg_geometry_to_wkb(bytes.as_slice()),
Value::Blob(bytes) => {
let bytes = bytes.as_slice();
if bytes.len() >= 4 && bytes[0] == 0x47 && bytes[1] == 0x50 {
return crate::gpkg::gpkg_geometry_to_wkb(bytes);
}
Ok(Wkb::try_new(bytes)?)
}
_ => Err(invalid_type("Wkb", value)),
}
}
}
enum SqlParam<'a> {
Owned(Value),
Borrowed(&'a Value),
}
impl<'a> rusqlite::ToSql for SqlParam<'a> {
#[inline]
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
match self {
SqlParam::Owned(value) => value.to_sql(),
SqlParam::Borrowed(value) => value.to_sql(),
}
}
}
pub(crate) fn params_from_geom_and_properties<'p, P>(
geom: Vec<u8>,
properties: P,
id: Option<i64>,
) -> impl rusqlite::Params
where
P: IntoIterator<Item = &'p Value>,
{
let params = std::iter::once(SqlParam::Owned(Value::Geometry(geom)))
.chain(properties.into_iter().map(SqlParam::Borrowed))
.chain(id.into_iter().map(|i| SqlParam::Owned(Value::Integer(i))));
rusqlite::params_from_iter(params)
}
#[cfg(test)]
mod tests {
use super::{GpkgError, Value};
#[test]
fn option_try_from_value_null_is_none() -> Result<(), GpkgError> {
let value = Value::Null;
let parsed: Option<i64> = value.try_into()?;
assert_eq!(parsed, None);
Ok(())
}
#[test]
fn option_try_from_value_some_is_some() -> Result<(), GpkgError> {
let value = Value::Integer(7);
let parsed: Option<i64> = value.try_into()?;
assert_eq!(parsed, Some(7));
Ok(())
}
#[test]
fn option_try_from_ref_handles_integer() -> Result<(), GpkgError> {
let value = Value::Integer(42);
let parsed: Option<i64> = (&value).try_into()?;
assert_eq!(parsed, Some(42));
Ok(())
}
}