rusqlite_gpkg/
types.rs

1use crate::error::GpkgError;
2use wkb::reader::{Dimension, GeometryType, Wkb};
3
4/// Logical column types used by GeoPackage layers and DDL helpers.
5#[derive(Clone, Copy, Debug, PartialEq)]
6#[repr(C)]
7pub enum ColumnType {
8    /// Boolean value stored as an integer 0/1.
9    Boolean,
10    /// UTF-8 text column.
11    Varchar,
12    /// Floating point column (SQLite REAL).
13    Double,
14    /// Integer column (SQLite INTEGER).
15    Integer,
16    /// Geometry column stored as a GeoPackage BLOB.
17    Geometry,
18}
19
20/// Column definition used when creating or describing layer properties.
21#[derive(Clone, Debug)]
22pub struct ColumnSpec {
23    pub name: String,
24    pub column_type: ColumnType,
25}
26
27/// Layer-wide metadata and property column definitions.
28#[derive(Clone, Debug)]
29pub struct GpkgLayerMetadata {
30    pub primary_key_column: String,
31    pub geometry_column: String,
32    pub geometry_type: GeometryType,
33    pub geometry_dimension: Dimension,
34    pub srs_id: u32,
35    pub other_columns: Vec<ColumnSpec>,
36}
37
38/// Owned dynamic value used for feature properties.
39///
40/// `Value` mirrors SQLite's dynamic types and is the primary property container
41/// in this crate. Access is explicit: `GpkgFeature::property` returns
42/// `Option<Value>`, and callers convert using `try_into()` or pattern matching.
43///
44/// Common conversions:
45/// - Integers: `i64`, `i32`, `u64`, etc.
46/// - Floats: `f64`, `f32`
47/// - Text: `String`, `&str`
48/// - Geometry: `wkb::reader::Wkb<'_>` from `Value::Geometry` or `Value::Blob`
49///
50/// ```no_run
51/// use rusqlite_gpkg::Value;
52///
53/// let value = Value::Text("alpha".to_string());
54/// let name: &str = (&value).try_into()?;
55/// # Ok::<(), rusqlite_gpkg::GpkgError>(())
56/// ```
57#[derive(Clone, Debug, PartialEq)]
58pub enum Value {
59    Null,
60    Integer(i64),
61    Real(f64),
62    Text(String),
63    Blob(Vec<u8>),
64    Geometry(Vec<u8>), // we want to use Wkb struct here, but it requires a lifetime
65}
66
67impl From<&str> for Value {
68    fn from(value: &str) -> Self {
69        Value::Text(value.to_string())
70    }
71}
72
73impl From<String> for Value {
74    fn from(value: String) -> Self {
75        Value::Text(value)
76    }
77}
78
79impl From<bool> for Value {
80    fn from(value: bool) -> Self {
81        Value::Integer(if value { 1 } else { 0 })
82    }
83}
84
85macro_rules! impl_from_int {
86    ($($t:ty),+ $(,)?) => {
87        $(
88            impl From<$t> for Value {
89                #[inline]
90                fn from(value: $t) -> Self {
91                    Value::Integer(value as i64)
92                }
93            }
94        )+
95    };
96}
97
98macro_rules! impl_from_uint {
99    ($($t:ty),+ $(,)?) => {
100        $(
101            impl From<$t> for Value {
102                #[inline]
103                fn from(value: $t) -> Self {
104                    Value::Integer(value as i64)
105                }
106            }
107        )+
108    };
109}
110
111impl_from_int!(i8, i16, i32, i64, isize);
112impl_from_uint!(u8, u16, u32, u64, usize);
113
114impl From<f32> for Value {
115    #[inline]
116    fn from(value: f32) -> Self {
117        Value::Real(value as f64)
118    }
119}
120
121impl From<f64> for Value {
122    fn from(value: f64) -> Self {
123        Value::Real(value)
124    }
125}
126
127impl<T: Into<Value>> From<Option<T>> for Value {
128    fn from(value: Option<T>) -> Self {
129        match value {
130            Some(v) => v.into(),
131            None => Value::Null,
132        }
133    }
134}
135
136#[macro_export]
137macro_rules! params {
138    () => {
139        &[]
140    };
141    ($($value:expr),+ $(,)?) => {
142        &[$($crate::Value::from($value)),+]
143    };
144}
145
146#[inline]
147fn value_to_sql_output(value: &Value) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
148    use rusqlite::types::{ToSqlOutput, ValueRef};
149
150    let output = match value {
151        Value::Null => ToSqlOutput::Borrowed(ValueRef::Null),
152        Value::Integer(v) => ToSqlOutput::Borrowed(ValueRef::Integer(*v)),
153        Value::Real(v) => ToSqlOutput::Borrowed(ValueRef::Real(*v)),
154        Value::Text(s) => ToSqlOutput::Borrowed(ValueRef::Text(s.as_bytes())),
155        Value::Blob(items) | Value::Geometry(items) => {
156            ToSqlOutput::Borrowed(ValueRef::Blob(items.as_slice()))
157        }
158    };
159
160    Ok(output)
161}
162
163impl rusqlite::ToSql for Value {
164    #[inline]
165    fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
166        value_to_sql_output(self)
167    }
168}
169
170impl From<rusqlite::types::Value> for Value {
171    #[inline]
172    fn from(value: rusqlite::types::Value) -> Self {
173        match value {
174            rusqlite::types::Value::Null => Value::Null,
175            rusqlite::types::Value::Integer(value) => Value::Integer(value),
176            rusqlite::types::Value::Real(value) => Value::Real(value),
177            rusqlite::types::Value::Text(value) => Value::Text(value),
178            rusqlite::types::Value::Blob(value) => Value::Blob(value),
179        }
180    }
181}
182
183impl<'a> From<rusqlite::types::ValueRef<'a>> for Value {
184    #[inline]
185    fn from(value: rusqlite::types::ValueRef<'a>) -> Self {
186        match value {
187            rusqlite::types::ValueRef::Null => Value::Null,
188            rusqlite::types::ValueRef::Integer(value) => Value::Integer(value),
189            rusqlite::types::ValueRef::Real(value) => Value::Real(value),
190            rusqlite::types::ValueRef::Text(value) => {
191                let s = std::str::from_utf8(value).expect("invalid UTF-8");
192                Value::Text(s.to_string())
193            }
194            rusqlite::types::ValueRef::Blob(value) => Value::Blob(value.to_vec()),
195        }
196    }
197}
198
199impl From<Value> for rusqlite::types::Value {
200    #[inline]
201    fn from(value: Value) -> Self {
202        match value {
203            Value::Null => rusqlite::types::Value::Null,
204            Value::Integer(value) => rusqlite::types::Value::Integer(value),
205            Value::Real(value) => rusqlite::types::Value::Real(value),
206            Value::Text(value) => rusqlite::types::Value::Text(value),
207            Value::Blob(value) | Value::Geometry(value) => rusqlite::types::Value::Blob(value),
208        }
209    }
210}
211
212#[inline]
213fn value_type_name(value: &Value) -> &'static str {
214    match value {
215        Value::Null => "NULL",
216        Value::Integer(_) => "INTEGER",
217        Value::Real(_) => "REAL",
218        Value::Text(_) => "TEXT",
219        Value::Blob(_) => "BLOB",
220        Value::Geometry(_) => "GEOMETRY",
221    }
222}
223
224#[inline]
225fn invalid_type(expected: &'static str, value: &Value) -> GpkgError {
226    GpkgError::Message(format!(
227        "expected {expected}, got {}",
228        value_type_name(value)
229    ))
230}
231
232#[inline]
233fn out_of_range(expected: &'static str) -> GpkgError {
234    GpkgError::Message(format!("value out of range for {expected}"))
235}
236
237macro_rules! impl_try_from_int_ref {
238    ($t:ty) => {
239        impl TryFrom<&Value> for $t {
240            type Error = GpkgError;
241
242            #[inline]
243            fn try_from(value: &Value) -> Result<Self, Self::Error> {
244                match value {
245                    Value::Integer(v) => {
246                        <$t>::try_from(*v).map_err(|_| out_of_range(stringify!($t)))
247                    }
248                    _ => Err(invalid_type(stringify!($t), value)),
249                }
250            }
251        }
252
253        impl TryFrom<Value> for $t {
254            type Error = GpkgError;
255
256            #[inline]
257            fn try_from(value: Value) -> Result<Self, Self::Error> {
258                (&value).try_into()
259            }
260        }
261    };
262}
263
264impl TryFrom<&Value> for i64 {
265    type Error = GpkgError;
266
267    #[inline]
268    fn try_from(value: &Value) -> Result<Self, Self::Error> {
269        match value {
270            Value::Integer(v) => Ok(*v),
271            _ => Err(invalid_type("i64", value)),
272        }
273    }
274}
275
276impl TryFrom<Value> for i64 {
277    type Error = GpkgError;
278
279    #[inline]
280    fn try_from(value: Value) -> Result<Self, Self::Error> {
281        (&value).try_into()
282    }
283}
284
285impl_try_from_int_ref!(i32);
286impl_try_from_int_ref!(i16);
287impl_try_from_int_ref!(i8);
288impl_try_from_int_ref!(isize);
289impl_try_from_int_ref!(u64);
290impl_try_from_int_ref!(u32);
291impl_try_from_int_ref!(u16);
292impl_try_from_int_ref!(u8);
293impl_try_from_int_ref!(usize);
294
295impl TryFrom<&Value> for f64 {
296    type Error = GpkgError;
297
298    #[inline]
299    fn try_from(value: &Value) -> Result<Self, Self::Error> {
300        match value {
301            Value::Real(v) => Ok(*v),
302            Value::Integer(v) => Ok(*v as f64),
303            _ => Err(invalid_type("f64", value)),
304        }
305    }
306}
307
308impl TryFrom<Value> for f64 {
309    type Error = GpkgError;
310
311    #[inline]
312    fn try_from(value: Value) -> Result<Self, Self::Error> {
313        (&value).try_into()
314    }
315}
316
317impl TryFrom<&Value> for f32 {
318    type Error = GpkgError;
319
320    #[inline]
321    fn try_from(value: &Value) -> Result<Self, Self::Error> {
322        match value {
323            Value::Real(v) => Ok(*v as f32),
324            Value::Integer(v) => Ok(*v as f32),
325            _ => Err(invalid_type("f32", value)),
326        }
327    }
328}
329
330impl TryFrom<Value> for f32 {
331    type Error = GpkgError;
332
333    #[inline]
334    fn try_from(value: Value) -> Result<Self, Self::Error> {
335        (&value).try_into()
336    }
337}
338
339impl TryFrom<&Value> for bool {
340    type Error = GpkgError;
341
342    #[inline]
343    fn try_from(value: &Value) -> Result<Self, Self::Error> {
344        match value {
345            Value::Integer(0) => Ok(false),
346            Value::Integer(1) => Ok(true),
347            _ => Err(invalid_type("bool", value)),
348        }
349    }
350}
351
352impl TryFrom<Value> for bool {
353    type Error = GpkgError;
354
355    #[inline]
356    fn try_from(value: Value) -> Result<Self, Self::Error> {
357        (&value).try_into()
358    }
359}
360
361impl<T> TryFrom<&Value> for Option<T>
362where
363    T: for<'a> TryFrom<&'a Value, Error = GpkgError>,
364{
365    type Error = GpkgError;
366
367    #[inline]
368    fn try_from(value: &Value) -> Result<Self, Self::Error> {
369        match value {
370            Value::Null => Ok(None),
371            _ => Ok(Some(T::try_from(value)?)),
372        }
373    }
374}
375
376impl<T> TryFrom<Value> for Option<T>
377where
378    T: for<'a> TryFrom<&'a Value, Error = GpkgError>,
379{
380    type Error = GpkgError;
381
382    #[inline]
383    fn try_from(value: Value) -> Result<Self, Self::Error> {
384        (&value).try_into()
385    }
386}
387
388impl TryFrom<Value> for String {
389    type Error = GpkgError;
390
391    #[inline]
392    fn try_from(value: Value) -> Result<Self, Self::Error> {
393        match value {
394            Value::Text(s) => Ok(s),
395            other => Err(invalid_type("String", &other)),
396        }
397    }
398}
399
400impl<'a> TryFrom<&'a Value> for &'a str {
401    type Error = GpkgError;
402
403    #[inline]
404    fn try_from(value: &'a Value) -> Result<Self, Self::Error> {
405        match value {
406            Value::Text(s) => Ok(s.as_str()),
407            _ => Err(invalid_type("&str", value)),
408        }
409    }
410}
411
412impl<'a> TryFrom<&'a Value> for Wkb<'a> {
413    type Error = GpkgError;
414
415    #[inline]
416    fn try_from(value: &'a Value) -> Result<Self, Self::Error> {
417        match value {
418            Value::Geometry(bytes) => crate::gpkg::gpkg_geometry_to_wkb(bytes.as_slice()),
419            Value::Blob(bytes) => {
420                let bytes = bytes.as_slice();
421                if bytes.len() >= 4 && bytes[0] == 0x47 && bytes[1] == 0x50 {
422                    return crate::gpkg::gpkg_geometry_to_wkb(bytes);
423                }
424                Ok(Wkb::try_new(bytes)?)
425            }
426            _ => Err(invalid_type("Wkb", value)),
427        }
428    }
429}
430
431// When inserting, geom is a owned value, while the rest are borrowed value.
432// This is a utility to handle these transparently.
433enum SqlParam<'a> {
434    Owned(Value),
435    Borrowed(&'a Value),
436}
437
438impl<'a> rusqlite::ToSql for SqlParam<'a> {
439    #[inline]
440    fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
441        match self {
442            SqlParam::Owned(value) => value.to_sql(),
443            SqlParam::Borrowed(value) => value.to_sql(),
444        }
445    }
446}
447
448pub(crate) fn params_from_geom_and_properties<'p, P>(
449    geom: Vec<u8>,
450    properties: P,
451    id: Option<i64>,
452) -> impl rusqlite::Params
453where
454    P: IntoIterator<Item = &'p Value>,
455{
456    let params = std::iter::once(SqlParam::Owned(Value::Geometry(geom)))
457        .chain(properties.into_iter().map(SqlParam::Borrowed))
458        .chain(id.into_iter().map(|i| SqlParam::Owned(Value::Integer(i))));
459    rusqlite::params_from_iter(params)
460}
461
462#[cfg(test)]
463mod tests {
464    use super::{GpkgError, Value};
465
466    #[test]
467    fn option_try_from_value_null_is_none() -> Result<(), GpkgError> {
468        let value = Value::Null;
469        let parsed: Option<i64> = value.try_into()?;
470        assert_eq!(parsed, None);
471        Ok(())
472    }
473
474    #[test]
475    fn option_try_from_value_some_is_some() -> Result<(), GpkgError> {
476        let value = Value::Integer(7);
477        let parsed: Option<i64> = value.try_into()?;
478        assert_eq!(parsed, Some(7));
479        Ok(())
480    }
481
482    #[test]
483    fn option_try_from_ref_handles_integer() -> Result<(), GpkgError> {
484        let value = Value::Integer(42);
485        let parsed: Option<i64> = (&value).try_into()?;
486        assert_eq!(parsed, Some(42));
487        Ok(())
488    }
489}