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)]
16pub 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
32const GEOMETRY_INDEX: usize = 0;
35const PRIMARY_INDEX: usize = 1;
36
37impl GpkgLayer {
38 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 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 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 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 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}