rusqlite_gpkg/gpkg/
layer.rs

1use crate::Value;
2use crate::error::{GpkgError, Result};
3use crate::ogc_sql::{sql_delete_all, sql_insert_feature, sql_select_features};
4use crate::types::{ColumnSpec, params_from_geom_and_properties};
5use geo_traits::GeometryTrait;
6use rusqlite::types::Type;
7use std::collections::HashMap;
8use std::rc::Rc;
9use wkb::reader::Wkb;
10
11use super::{GpkgFeature, wkb_to_gpkg_geometry};
12
13use crate::GpkgFeatureBatchIterator;
14
15#[derive(Debug)]
16/// A GeoPackage layer with geometry metadata and column specs.
17pub struct GpkgLayer {
18    pub(super) conn: Rc<rusqlite::Connection>,
19    pub(super) is_read_only: bool,
20    pub layer_name: String,
21    pub geometry_column: String,
22    pub primary_key_column: String,
23    pub geometry_type: wkb::reader::GeometryType,
24    pub geometry_dimension: wkb::reader::Dimension,
25    pub srs_id: u32,
26    pub property_columns: Vec<ColumnSpec>,
27    pub(super) property_index_by_name: Rc<HashMap<String, usize>>,
28    pub(super) insert_sql: String,
29    pub(super) update_sql: String,
30}
31
32// When issueing the SELECT query, always place these columns first so that
33// we don't need to find the positions every time.
34const GEOMETRY_INDEX: usize = 0;
35const PRIMARY_INDEX: usize = 1;
36
37impl GpkgLayer {
38    /// Return all the features in the layer.
39    ///
40    /// Example:
41    /// ```no_run
42    /// use rusqlite_gpkg::Gpkg;
43    ///
44    /// let gpkg = Gpkg::open_read_only("data/example.gpkg")?;
45    /// let layer = gpkg.get_layer("points")?;
46    /// for feature in layer.features()? {
47    ///     let _id = feature.id();
48    ///     let _geom = feature.geometry()?;
49    /// }
50    /// # Ok::<(), rusqlite_gpkg::GpkgError>(())
51    /// ```
52    ///
53    /// ## Why does this return a vector instead of an iterator?
54    ///
55    /// I was hoping we could avoid allocation here, but it seems rusqlite's
56    /// API requires allocation.
57    pub fn features(&self) -> Result<Vec<GpkgFeature>> {
58        let columns = self.property_columns.iter().map(|spec| spec.name.as_str());
59        let sql = sql_select_features(
60            &self.layer_name,
61            &self.geometry_column,
62            &self.primary_key_column,
63            columns,
64            None,
65        );
66
67        let sql: &str = &sql;
68        let mut stmt = self.conn.prepare(sql)?;
69        let features = stmt
70            .query_map([], |row| {
71                row_to_feature(
72                    row,
73                    &self.property_columns,
74                    &self.geometry_column,
75                    &self.primary_key_column,
76                    &self.property_index_by_name,
77                )
78            })?
79            .collect::<rusqlite::Result<Vec<GpkgFeature>>>()?;
80
81        Ok(features)
82    }
83
84    /// Return an iterator that yields features in batches.
85    ///
86    /// This is intended for large layers where allocating a single `Vec<GpkgFeature>`
87    /// could be expensive. Each iterator item is a `Vec<GpkgFeature>` with up to
88    /// `batch_size` features.
89    ///
90    /// Example:
91    /// ```no_run
92    /// use rusqlite_gpkg::Gpkg;
93    ///
94    /// let gpkg = Gpkg::open_read_only("data/example.gpkg")?;
95    /// let layer = gpkg.get_layer("points")?;
96    /// for batch in layer.features_batch(100)? {
97    ///     let features = batch?;
98    ///     for feature in features {
99    ///         let _id = feature.id();
100    ///         let _geom = feature.geometry()?;
101    ///     }
102    /// }
103    /// # Ok::<(), rusqlite_gpkg::GpkgError>(())
104    /// ```
105    pub fn features_batch<'a>(&'a self, batch_size: u32) -> Result<GpkgFeatureBatchIterator<'a>> {
106        let columns = self.property_columns.iter().map(|spec| spec.name.as_str());
107        let sql = sql_select_features(
108            &self.layer_name,
109            &self.geometry_column,
110            &self.primary_key_column,
111            columns,
112            Some(batch_size),
113        );
114
115        let stmt = self.conn.prepare(&sql)?;
116
117        Ok(GpkgFeatureBatchIterator::new(stmt, self, batch_size))
118    }
119
120    /// Remove all rows from the layer.
121    ///
122    /// Example:
123    /// ```no_run
124    /// use rusqlite_gpkg::Gpkg;
125    ///
126    /// let gpkg = Gpkg::open("data/example.gpkg")?;
127    /// let layer = gpkg.get_layer("points")?;
128    /// layer.truncate()?;
129    /// # Ok::<(), rusqlite_gpkg::GpkgError>(())
130    /// ```
131    pub fn truncate(&self) -> Result<usize> {
132        self.ensure_writable()?;
133        let sql = sql_delete_all(&self.layer_name);
134        Ok(self.conn.execute(&sql, [])?)
135    }
136
137    /// Insert a feature with geometry and ordered property values.
138    ///
139    /// Example:
140    /// ```no_run
141    /// use geo_types::Point;
142    /// use rusqlite_gpkg::{Gpkg, params};
143    ///
144    /// let gpkg = Gpkg::open("data/example.gpkg")?;
145    /// let layer = gpkg.get_layer("points")?;
146    ///
147    /// layer.insert(Point::new(1.0, 2.0), params!["alpha", 1])?;
148    /// # Ok::<(), rusqlite_gpkg::GpkgError>(())
149    /// ```
150    pub fn insert<'p, G, P>(&self, geometry: G, properties: P) -> Result<()>
151    where
152        G: GeometryTrait<T = f64>,
153        P: IntoIterator<Item = &'p Value>,
154    {
155        let geom = self.geom_from_geometry(geometry)?;
156        let params = params_from_geom_and_properties(geom, properties, None);
157        let mut stmt = self.conn.prepare_cached(&self.insert_sql)?;
158        stmt.execute(params)?;
159        Ok(())
160    }
161
162    /// Update the feature with geometry and ordered property values.
163    ///
164    /// Example:
165    /// ```no_run
166    /// use geo_types::Point;
167    /// use rusqlite_gpkg::{Gpkg, params};
168    ///
169    /// let gpkg = Gpkg::open("data/example.gpkg")?;
170    /// let layer = gpkg.get_layer("points")?;
171    /// layer.update(Point::new(3.0, 4.0), params!["beta", false], 1)?;
172    /// # Ok::<(), rusqlite_gpkg::GpkgError>(())
173    /// ```
174    pub fn update<'p, G, P>(&self, geometry: G, properties: P, id: i64) -> Result<()>
175    where
176        G: GeometryTrait<T = f64>,
177        P: IntoIterator<Item = &'p Value>,
178    {
179        let geom = self.geom_from_geometry(geometry)?;
180        let params = params_from_geom_and_properties(geom, properties, Some(id));
181        let mut stmt = self.conn.prepare_cached(&self.update_sql)?;
182        stmt.execute(params)?;
183        Ok(())
184    }
185
186    fn ensure_writable(&self) -> Result<()> {
187        if self.is_read_only {
188            return Err(GpkgError::ReadOnly);
189        }
190        Ok(())
191    }
192
193    pub(crate) fn build_insert_sql(
194        layer_name: &str,
195        geometry_column: &str,
196        property_columns: &[ColumnSpec],
197    ) -> String {
198        let mut columns = Vec::with_capacity(property_columns.len() + 1);
199        columns.push(format!(r#""{}""#, geometry_column));
200        columns.extend(
201            property_columns
202                .iter()
203                .map(|spec| format!(r#""{}""#, spec.name)),
204        );
205
206        let placeholders = (1..=columns.len())
207            .map(|i| format!("?{i}"))
208            .collect::<Vec<String>>()
209            .join(",");
210
211        sql_insert_feature(layer_name, &columns.join(","), &placeholders)
212    }
213
214    pub(crate) fn build_update_sql(
215        layer_name: &str,
216        geometry_column: &str,
217        primary_key_column: &str,
218        property_columns: &[ColumnSpec],
219    ) -> String {
220        let mut column_names = Vec::with_capacity(property_columns.len() + 1);
221        column_names.push(geometry_column);
222        column_names.extend(property_columns.iter().map(|spec| spec.name.as_str()));
223
224        let assignments = column_names
225            .iter()
226            .enumerate()
227            .map(|(idx, name)| format!(r#""{}"=?{}"#, name, idx + 1))
228            .collect::<Vec<String>>()
229            .join(",");
230        let id_idx = column_names.len() + 1;
231
232        format!(
233            r#"UPDATE "{}" SET {} WHERE "{}"=?{}"#,
234            layer_name, assignments, primary_key_column, id_idx
235        )
236    }
237
238    pub(crate) fn build_property_index_by_name(
239        property_columns: &[ColumnSpec],
240    ) -> HashMap<String, usize> {
241        let mut property_index_by_name = HashMap::with_capacity(property_columns.len());
242        for (idx, column) in property_columns.iter().enumerate() {
243            property_index_by_name.insert(column.name.clone(), idx);
244        }
245        property_index_by_name
246    }
247
248    fn geom_from_geometry<G>(&self, geometry: G) -> Result<Vec<u8>>
249    where
250        G: GeometryTrait<T = f64>,
251    {
252        self.ensure_writable()?;
253
254        let mut buf = Vec::new();
255        wkb::writer::write_geometry(&mut buf, &geometry, &Default::default())?;
256        let wkb = Wkb::try_new(&buf)?;
257        let geom = wkb_to_gpkg_geometry(wkb, self.srs_id)?;
258
259        Ok(geom)
260    }
261}
262
263pub(crate) fn row_to_feature(
264    row: &rusqlite::Row<'_>,
265    property_columns: &[ColumnSpec],
266    geometry_column: &str,
267    primary_key_column: &str,
268    property_index_by_name: &Rc<HashMap<String, usize>>,
269) -> std::result::Result<GpkgFeature, rusqlite::Error> {
270    let mut id: Option<i64> = None;
271    let mut geometry: Option<Vec<u8>> = None;
272    let mut properties = Vec::with_capacity(property_columns.len());
273    let row_len = property_columns.len() + 2;
274
275    for idx in 0..row_len {
276        let value_ref = row.get_ref(idx)?;
277        let value = Value::from(value_ref);
278        let name = if idx == GEOMETRY_INDEX {
279            geometry_column
280        } else if idx == PRIMARY_INDEX {
281            primary_key_column
282        } else {
283            property_columns[idx - 2].name.as_str()
284        };
285
286        if idx == GEOMETRY_INDEX {
287            match value {
288                Value::Blob(bytes) => geometry = Some(bytes),
289                Value::Null => geometry = None,
290                _ => {
291                    return Err(rusqlite::Error::InvalidColumnType(
292                        idx,
293                        name.to_string(),
294                        value_ref.data_type(),
295                    ));
296                }
297            }
298        } else if idx == PRIMARY_INDEX {
299            match &value {
300                Value::Integer(value) => id = Some(*value),
301                _ => {
302                    return Err(rusqlite::Error::InvalidColumnType(
303                        idx,
304                        name.to_string(),
305                        value_ref.data_type(),
306                    ));
307                }
308            }
309        } else {
310            properties.push(value);
311        }
312    }
313
314    let id = id.ok_or_else(|| {
315        rusqlite::Error::InvalidColumnType(
316            PRIMARY_INDEX,
317            primary_key_column.to_string(),
318            Type::Null,
319        )
320    })?;
321
322    Ok(GpkgFeature {
323        id,
324        geometry,
325        properties,
326        property_index_by_name: property_index_by_name.clone(),
327    })
328}
329
330#[cfg(test)]
331mod tests {
332    use crate::Result;
333    use crate::Value;
334    use crate::conversions::geometry_type_to_str;
335    use crate::gpkg::Gpkg;
336    use crate::params;
337    use crate::types::{ColumnSpec, ColumnType};
338    use geo_traits::GeometryTrait;
339    use geo_types::{
340        Geometry, GeometryCollection, LineString, MultiLineString, MultiPoint, MultiPolygon, Point,
341        Polygon,
342    };
343    use std::str::FromStr;
344    use wkb::reader::{GeometryType, Wkb};
345    use wkt::Wkt;
346
347    fn generated_gpkg_path() -> &'static str {
348        "src/test/test_generated.gpkg"
349    }
350
351    fn gpkg_blob_from_geometry<G: GeometryTrait<T = f64>>(
352        geometry: G,
353        srs_id: u32,
354    ) -> Result<Vec<u8>> {
355        let mut buf = Vec::new();
356        wkb::writer::write_geometry(&mut buf, &geometry, &Default::default())?;
357        let wkb = Wkb::try_new(&buf)?;
358        super::super::wkb_to_gpkg_geometry(wkb, srs_id)
359    }
360
361    fn assert_geometry_roundtrip<G: GeometryTrait<T = f64> + Clone>(
362        gpkg: &Gpkg,
363        layer_name: &str,
364        geometry_type: GeometryType,
365        geometry_dimension: wkb::reader::Dimension,
366        geometry: G,
367    ) -> Result<()> {
368        let columns: Vec<ColumnSpec> = Vec::new();
369        let layer = gpkg.create_layer(
370            layer_name,
371            "geom",
372            geometry_type,
373            geometry_dimension,
374            4326,
375            &columns,
376        )?;
377
378        let expected_blob = gpkg_blob_from_geometry(geometry.clone(), 4326)?;
379        let mut expected_wkb_bytes = Vec::new();
380        wkb::writer::write_geometry(&mut expected_wkb_bytes, &geometry, &Default::default())?;
381        let expected_wkb = Wkb::try_new(&expected_wkb_bytes)?;
382
383        layer.insert(geometry, std::iter::empty::<&Value>())?;
384
385        let geom_blob: Vec<u8> = layer.conn.query_row(
386            &format!(r#"SELECT "geom" FROM "{}""#, layer_name),
387            [],
388            |row| row.get(0),
389        )?;
390        assert_eq!(geom_blob, expected_blob);
391
392        let features = layer.features()?;
393        let feature = features.first().expect("inserted feature");
394        let geom = feature.geometry()?;
395        assert_eq!(geom.geometry_type(), geometry_type);
396        assert_eq!(geom.dimension(), geometry_dimension);
397        assert_eq!(geom.buf(), expected_wkb.buf());
398
399        Ok(())
400    }
401
402    #[test]
403    fn reads_generated_layers_and_counts() -> Result<()> {
404        let gpkg = Gpkg::open_read_only(generated_gpkg_path())?;
405        let mut layers = gpkg.list_layers()?;
406        layers.sort();
407        assert_eq!(layers, vec!["lines", "points", "polygons"]);
408
409        let points = gpkg.get_layer("points")?;
410        let lines = gpkg.get_layer("lines")?;
411        let polygons = gpkg.get_layer("polygons")?;
412
413        assert_eq!(points.features()?.len(), 5);
414        assert_eq!(lines.features()?.len(), 3);
415        assert_eq!(polygons.features()?.len(), 2);
416
417        Ok(())
418    }
419
420    #[test]
421    fn reads_geometry_and_properties_from_points() -> Result<()> {
422        let gpkg = Gpkg::open_read_only(generated_gpkg_path())?;
423        let layer = gpkg.get_layer("points")?;
424        let features = layer.features()?;
425        let feature = features.first().expect("first feature");
426
427        let geom = feature.geometry()?;
428        assert_eq!(geom.geometry_type(), GeometryType::Point);
429
430        assert_eq!(feature.id(), 1);
431        let name: String = feature.property("name").ok_or("missing name")?.try_into()?;
432        assert_eq!(name, "alpha");
433
434        let active: bool = feature
435            .property("active")
436            .ok_or("missing active")?
437            .try_into()?;
438        assert_eq!(active, true);
439
440        let note = feature.property("note").ok_or("missing note")?;
441        assert_eq!(note, Value::Text("first".to_string()));
442
443        Ok(())
444    }
445
446    #[test]
447    fn creates_layer_metadata() -> Result<()> {
448        let gpkg = Gpkg::open_in_memory()?;
449        let columns = vec![
450            ColumnSpec {
451                name: "name".to_string(),
452                column_type: ColumnType::Varchar,
453            },
454            ColumnSpec {
455                name: "value".to_string(),
456                column_type: ColumnType::Integer,
457            },
458        ];
459
460        gpkg.create_layer(
461            "points",
462            "geom",
463            GeometryType::Point,
464            wkb::reader::Dimension::Xy,
465            4326,
466            &columns,
467        )?;
468
469        let (geometry_type_name, srs_id, z, m): (String, u32, i8, i8) =
470            gpkg.conn.query_row(
471                "SELECT geometry_type_name, srs_id, z, m FROM gpkg_geometry_columns WHERE table_name = 'points'",
472                [],
473                |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
474            )?;
475
476        assert_eq!(
477            geometry_type_name,
478            geometry_type_to_str(GeometryType::Point)
479        );
480        assert_eq!(srs_id, 4326);
481        assert_eq!(z, 0);
482        assert_eq!(m, 0);
483
484        Ok(())
485    }
486
487    #[test]
488    fn inserts_and_updates_by_primary_key() -> Result<()> {
489        let gpkg = Gpkg::open_in_memory()?;
490        let columns = vec![
491            ColumnSpec {
492                name: "name".to_string(),
493                column_type: ColumnType::Varchar,
494            },
495            ColumnSpec {
496                name: "value".to_string(),
497                column_type: ColumnType::Integer,
498            },
499        ];
500
501        let layer = gpkg.create_layer(
502            "points",
503            "geom",
504            GeometryType::Point,
505            wkb::reader::Dimension::Xy,
506            4326,
507            &columns,
508        )?;
509
510        let point_a = Point::new(1.0, 2.0);
511        let name_a = "alpha".to_string();
512        let value_a = 7_i64;
513        layer.insert(point_a, params![name_a, value_a])?;
514        let id = layer.conn.last_insert_rowid();
515
516        let point_b = Point::new(4.0, 5.0);
517        let name_b = "beta".to_string();
518        let value_b = 9_i64;
519        layer.update(point_b, params![name_b, value_b], id)?;
520
521        let (geom_blob, name, value): (Vec<u8>, String, i64) = layer.conn.query_row(
522            "SELECT geom, name, value FROM points WHERE fid = ?1",
523            [id],
524            |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
525        )?;
526
527        let expected_geom = gpkg_blob_from_geometry(Point::new(4.0, 5.0), 4326)?;
528        assert_eq!(geom_blob, expected_geom);
529        assert_eq!(name, "beta");
530        assert_eq!(value, 9);
531
532        Ok(())
533    }
534
535    #[test]
536    fn roundtrips_all_geometry_types() -> Result<()> {
537        let gpkg = Gpkg::open_in_memory()?;
538
539        let line = LineString::from(vec![(0.0, 0.0), (1.5, 1.0), (2.0, 0.5)]);
540        let line_b = LineString::from(vec![(-1.0, -1.0), (-2.0, -3.0)]);
541        let exterior = LineString::from(vec![
542            (0.0, 0.0),
543            (3.0, 0.0),
544            (3.0, 3.0),
545            (0.0, 3.0),
546            (0.0, 0.0),
547        ]);
548        let polygon = Polygon::new(exterior, vec![]);
549        let polygon_b = Polygon::new(
550            LineString::from(vec![
551                (10.0, 10.0),
552                (12.0, 10.0),
553                (12.0, 12.0),
554                (10.0, 12.0),
555                (10.0, 10.0),
556            ]),
557            vec![],
558        );
559
560        assert_geometry_roundtrip(
561            &gpkg,
562            "rt_points",
563            GeometryType::Point,
564            wkb::reader::Dimension::Xy,
565            Point::new(1.0, 2.0),
566        )?;
567        assert_geometry_roundtrip(
568            &gpkg,
569            "rt_lines",
570            GeometryType::LineString,
571            wkb::reader::Dimension::Xy,
572            line.clone(),
573        )?;
574        assert_geometry_roundtrip(
575            &gpkg,
576            "rt_polygons",
577            GeometryType::Polygon,
578            wkb::reader::Dimension::Xy,
579            polygon.clone(),
580        )?;
581        assert_geometry_roundtrip(
582            &gpkg,
583            "rt_multi_points",
584            GeometryType::MultiPoint,
585            wkb::reader::Dimension::Xy,
586            MultiPoint::from(vec![Point::new(1.0, 1.0), Point::new(2.0, 2.0)]),
587        )?;
588        assert_geometry_roundtrip(
589            &gpkg,
590            "rt_multi_lines",
591            GeometryType::MultiLineString,
592            wkb::reader::Dimension::Xy,
593            MultiLineString::new(vec![line.clone(), line_b]),
594        )?;
595        assert_geometry_roundtrip(
596            &gpkg,
597            "rt_multi_polygons",
598            GeometryType::MultiPolygon,
599            wkb::reader::Dimension::Xy,
600            MultiPolygon::new(vec![polygon.clone(), polygon_b]),
601        )?;
602        assert_geometry_roundtrip(
603            &gpkg,
604            "rt_geometry_collections",
605            GeometryType::GeometryCollection,
606            wkb::reader::Dimension::Xy,
607            GeometryCollection::from(vec![
608                Geometry::Point(Point::new(-1.0, -2.0)),
609                Geometry::LineString(line),
610                Geometry::Polygon(polygon),
611            ]),
612        )?;
613
614        Ok(())
615    }
616
617    #[test]
618    fn roundtrips_z_and_m_dimensions() -> Result<()> {
619        let gpkg = Gpkg::open_in_memory()?;
620
621        let point_z = Wkt::from_str("POINT Z (1 2 3)")
622            .map_err(|err| crate::error::GpkgError::Message(err.to_string()))?;
623        let line_m = Wkt::from_str("LINESTRING M (0 0 5, 1 1 6)")
624            .map_err(|err| crate::error::GpkgError::Message(err.to_string()))?;
625        let polygon_zm = Wkt::from_str("POLYGON ZM ((0 0 1 10, 2 0 2 11, 2 2 3 12, 0 0 1 10))")
626            .map_err(|err| crate::error::GpkgError::Message(err.to_string()))?;
627
628        assert_geometry_roundtrip(
629            &gpkg,
630            "rt_point_z",
631            GeometryType::Point,
632            wkb::reader::Dimension::Xyz,
633            point_z,
634        )?;
635        assert_geometry_roundtrip(
636            &gpkg,
637            "rt_linestring_m",
638            GeometryType::LineString,
639            wkb::reader::Dimension::Xym,
640            line_m,
641        )?;
642        assert_geometry_roundtrip(
643            &gpkg,
644            "rt_polygon_zm",
645            GeometryType::Polygon,
646            wkb::reader::Dimension::Xyzm,
647            polygon_zm,
648        )?;
649
650        Ok(())
651    }
652
653    #[test]
654    fn rtree_updates_on_insert_update_delete() -> Result<()> {
655        let gpkg = Gpkg::open_in_memory()?;
656        let columns: Vec<ColumnSpec> = Vec::new();
657        let layer = gpkg.create_layer(
658            "rtree_points",
659            "geom",
660            GeometryType::Point,
661            wkb::reader::Dimension::Xy,
662            4326,
663            &columns,
664        )?;
665
666        let point_a = Point::new(1.5, -2.0);
667        layer.insert(point_a, std::iter::empty::<&Value>())?;
668        let id = layer.conn.last_insert_rowid();
669
670        let (minx, maxx, miny, maxy): (f64, f64, f64, f64) = layer.conn.query_row(
671            "SELECT minx, maxx, miny, maxy FROM rtree_rtree_points_geom WHERE id = ?1",
672            [id],
673            |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
674        )?;
675        assert_eq!(minx, 1.5);
676        assert_eq!(maxx, 1.5);
677        assert_eq!(miny, -2.0);
678        assert_eq!(maxy, -2.0);
679
680        let point_b = Point::new(-4.0, 6.25);
681        layer.update(point_b, std::iter::empty::<&Value>(), id)?;
682        let (minx, maxx, miny, maxy): (f64, f64, f64, f64) = layer.conn.query_row(
683            "SELECT minx, maxx, miny, maxy FROM rtree_rtree_points_geom WHERE id = ?1",
684            [id],
685            |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
686        )?;
687        assert_eq!(minx, -4.0);
688        assert_eq!(maxx, -4.0);
689        assert_eq!(miny, 6.25);
690        assert_eq!(maxy, 6.25);
691
692        layer.truncate()?;
693        let count: i64 =
694            layer
695                .conn
696                .query_row("SELECT COUNT(*) FROM rtree_rtree_points_geom", [], |row| {
697                    row.get(0)
698                })?;
699        assert_eq!(count, 0);
700
701        Ok(())
702    }
703
704    #[test]
705    fn truncates_rows() -> Result<()> {
706        let gpkg = Gpkg::open_in_memory()?;
707        let columns = vec![ColumnSpec {
708            name: "name".to_string(),
709            column_type: ColumnType::Varchar,
710        }];
711
712        let layer = gpkg.create_layer(
713            "points",
714            "geom",
715            GeometryType::Point,
716            wkb::reader::Dimension::Xy,
717            4326,
718            &columns,
719        )?;
720
721        let value_a = "a".to_string();
722        let value_b = "b".to_string();
723        layer.insert(Point::new(0.0, 0.0), params![value_a])?;
724        layer.insert(Point::new(1.0, 1.0), params![value_b])?;
725
726        let deleted = layer.truncate()?;
727        assert_eq!(deleted, 2);
728
729        let count: i64 = layer
730            .conn
731            .query_row("SELECT COUNT(*) FROM points", [], |row| row.get(0))?;
732        assert_eq!(count, 0);
733
734        Ok(())
735    }
736
737    #[test]
738    fn rejects_invalid_property_count() -> Result<()> {
739        let gpkg = Gpkg::open_in_memory()?;
740        let columns = vec![
741            ColumnSpec {
742                name: "name".to_string(),
743                column_type: ColumnType::Varchar,
744            },
745            ColumnSpec {
746                name: "value".to_string(),
747                column_type: ColumnType::Integer,
748            },
749        ];
750
751        let layer = gpkg.create_layer(
752            "points",
753            "geom",
754            GeometryType::Point,
755            wkb::reader::Dimension::Xy,
756            4326,
757            &columns,
758        )?;
759
760        let only = "only".to_string();
761        let result = layer.insert(Point::new(0.0, 0.0), params![only]);
762        match result {
763            Err(crate::GpkgError::Sql(rusqlite::Error::InvalidParameterCount(_, _))) => {}
764            e => panic!("expected InvalidParameterCount error: {e:?}"),
765        }
766
767        Ok(())
768    }
769
770    #[test]
771    fn params_macro_supports_nullable_values() -> Result<()> {
772        let gpkg = Gpkg::open_in_memory()?;
773        let columns = vec![
774            ColumnSpec {
775                name: "a".to_string(),
776                column_type: ColumnType::Double,
777            },
778            ColumnSpec {
779                name: "b".to_string(),
780                column_type: ColumnType::Integer,
781            },
782        ];
783
784        let layer = gpkg.create_layer(
785            "nullable_params",
786            "geom",
787            GeometryType::Point,
788            wkb::reader::Dimension::Xy,
789            4326,
790            &columns,
791        )?;
792
793        layer.insert(
794            Point::new(0.0, 0.0),
795            params![Some(1.0_f64), Option::<i64>::None],
796        )?;
797
798        let (a, b): (Option<f64>, Option<i64>) = layer.conn.query_row(
799            "SELECT a, b FROM nullable_params WHERE fid = 1",
800            [],
801            |row| Ok((row.get(0)?, row.get(1)?)),
802        )?;
803
804        assert_eq!(a, Some(1.0));
805        assert_eq!(b, None);
806
807        Ok(())
808    }
809}