1use crate::conversions::{
2 column_type_from_str, column_type_to_str, dimension_from_zm, dimension_to_zm,
3 geometry_type_from_str, geometry_type_to_str,
4};
5use crate::error::{GpkgError, Result};
6use crate::ogc_sql::{
7 SQL_INSERT_GPKG_CONTENTS, SQL_INSERT_GPKG_GEOMETRY_COLUMNS, SQL_LIST_LAYERS,
8 SQL_SELECT_GEOMETRY_COLUMN_META, execute_rtree_sqls, gpkg_rtree_drop_sql, initialize_gpkg,
9 sql_create_table, sql_drop_table, sql_table_columns,
10};
11use crate::sql_functions::register_spatial_functions;
12use crate::types::{ColumnSpec, GpkgLayerMetadata};
13use rusqlite::OpenFlags;
14use std::path::Path;
15use std::rc::Rc;
16
17use super::layer::GpkgLayer;
18
19#[derive(Debug)]
20pub struct Gpkg {
22 pub(crate) conn: Rc<rusqlite::Connection>,
23 pub(crate) read_only: bool,
24}
25
26#[cfg(not(target_family = "wasm"))]
27fn rusqlite_open_path<P: AsRef<Path>>(
28 path: P,
29 flags: rusqlite::OpenFlags,
30) -> rusqlite::Result<rusqlite::Connection> {
31 rusqlite::Connection::open_with_flags(path, flags)
32}
33
34#[cfg(target_family = "wasm")]
36fn rusqlite_open_path<P: AsRef<Path>>(
37 path: P,
38 flags: rusqlite::OpenFlags,
39) -> rusqlite::Result<rusqlite::Connection> {
40 rusqlite::Connection::open_with_flags_and_vfs(path, flags, "opfs-sahpool")
41}
42
43impl Gpkg {
44 pub(crate) fn new_from_conn(conn: Rc<rusqlite::Connection>, read_only: bool) -> Result<Self> {
45 register_spatial_functions(&conn)?;
46 Ok(Self { conn, read_only })
47 }
48
49 pub fn open_read_only<P: AsRef<Path>>(path: P) -> Result<Self> {
59 let conn = rusqlite_open_path(path, OpenFlags::SQLITE_OPEN_READ_ONLY)?;
60 Self::new_from_conn(Rc::new(conn), true)
61 }
62
63 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
73 let path = path.as_ref();
74 let is_existing = path.exists();
75
76 let conn = rusqlite_open_path(path, rusqlite::OpenFlags::default())?;
77
78 if !is_existing {
80 initialize_gpkg(&conn)?;
81 }
82
83 Self::new_from_conn(Rc::new(conn), false)
84 }
85
86 pub fn open_in_memory() -> Result<Self> {
96 let conn = rusqlite::Connection::open_in_memory()?;
97
98 initialize_gpkg(&conn)?;
99
100 Self::new_from_conn(Rc::new(conn), false)
101 }
102
103 pub fn register_srs(
132 &self,
133 srs_name: &str,
134 srs_id: i32,
135 organization: &str,
136 organization_coordsys_id: i32,
137 definition: &str,
138 description: &str,
139 ) -> Result<()> {
140 if self.read_only {
141 return Err(GpkgError::ReadOnly);
142 }
143
144 self.conn.execute(
145 "INSERT INTO gpkg_spatial_ref_sys \
146 (srs_name, srs_id, organization, organization_coordsys_id, definition, description) \
147 VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
148 rusqlite::params![
149 srs_name,
150 srs_id,
151 organization,
152 organization_coordsys_id,
153 definition,
154 description
155 ],
156 )?;
157 Ok(())
158 }
159
160 pub fn list_layers(&self) -> Result<Vec<String>> {
171 let mut stmt = self.conn.prepare(SQL_LIST_LAYERS)?;
172 let layers = stmt
173 .query_map([], |row| row.get(0))?
174 .collect::<std::result::Result<Vec<String>, _>>()?;
175 Ok(layers)
176 }
177
178 pub fn get_layer(&self, layer_name: &str) -> Result<GpkgLayer> {
189 let (geometry_column, geometry_type, geometry_dimension, srs_id) =
190 self.get_geometry_column_and_srs_id(layer_name)?;
191 let column_specs = self.get_column_specs(
192 layer_name,
193 &geometry_column,
194 geometry_type,
195 geometry_dimension,
196 srs_id,
197 )?;
198 let primary_key_column = column_specs.primary_key_column.clone();
199 let other_columns = column_specs.other_columns;
200
201 let insert_sql = GpkgLayer::build_insert_sql(layer_name, &geometry_column, &other_columns);
202 let update_sql = GpkgLayer::build_update_sql(
203 layer_name,
204 &geometry_column,
205 &primary_key_column,
206 &other_columns,
207 );
208 let property_index_by_name =
209 Rc::new(GpkgLayer::build_property_index_by_name(&other_columns));
210
211 Ok(GpkgLayer {
212 conn: self.conn.clone(),
213 is_read_only: self.read_only,
214 layer_name: layer_name.to_string(),
215 geometry_column,
216 primary_key_column,
217 geometry_type,
218 geometry_dimension,
219 srs_id,
220 property_columns: other_columns,
221 property_index_by_name,
222 insert_sql,
223 update_sql,
224 })
225 }
226
227 pub fn create_layer(
251 &self,
252 layer_name: &str,
253 geometry_column: &str,
254 geometry_type: wkb::reader::GeometryType,
255 geometry_dimension: wkb::reader::Dimension,
256 srs_id: u32,
257 other_column_specs: &[ColumnSpec],
258 ) -> Result<GpkgLayer> {
259 if self.read_only {
260 return Err(GpkgError::ReadOnly);
261 }
262
263 if self.list_layers()?.iter().any(|name| name == layer_name) {
264 return Err(GpkgError::Message(format!(
265 "Layer already exists: {layer_name}"
266 )));
267 }
268
269 let srs_exists: i64 = self.conn.query_row(
270 "SELECT EXISTS(SELECT 1 FROM gpkg_spatial_ref_sys WHERE srs_id = ?1)",
271 rusqlite::params![srs_id],
272 |row| row.get(0),
273 )?;
274 if srs_exists == 0 {
275 return Err(GpkgError::Message(format!(
276 "srs_id {srs_id} not found in gpkg_spatial_ref_sys"
277 )));
278 }
279
280 let geometry_type_name = geometry_type_to_str(geometry_type);
281 let (z, m) = dimension_to_zm(geometry_dimension);
282
283 let mut column_defs = Vec::with_capacity(other_column_specs.len() + 2);
284 column_defs.push("fid INTEGER PRIMARY KEY AUTOINCREMENT".to_string());
285 column_defs.push(format!(r#""{}" BLOB"#, geometry_column));
286 for spec in other_column_specs {
287 let col_type = column_type_to_str(spec.column_type);
288 column_defs.push(format!(r#""{}" {col_type}"#, spec.name));
289 }
290
291 let create_sql = sql_create_table(layer_name, &column_defs.join(", "));
292 self.conn.execute_batch(&create_sql)?;
293
294 self.conn.execute(
295 SQL_INSERT_GPKG_CONTENTS,
296 rusqlite::params![layer_name, layer_name, srs_id],
297 )?;
298 self.conn.execute(
299 SQL_INSERT_GPKG_GEOMETRY_COLUMNS,
300 rusqlite::params![
301 layer_name,
302 geometry_column,
303 geometry_type_name,
304 srs_id,
305 z,
306 m
307 ],
308 )?;
309
310 execute_rtree_sqls(&self.conn, layer_name, geometry_column, "fid")?;
311
312 let insert_sql =
313 GpkgLayer::build_insert_sql(layer_name, geometry_column, other_column_specs);
314 let update_sql =
315 GpkgLayer::build_update_sql(layer_name, geometry_column, "fid", other_column_specs);
316 let property_index_by_name =
317 Rc::new(GpkgLayer::build_property_index_by_name(other_column_specs));
318
319 Ok(GpkgLayer {
320 conn: self.conn.clone(),
321 is_read_only: self.read_only,
322 layer_name: layer_name.to_string(),
323 geometry_column: geometry_column.to_string(),
324 primary_key_column: "fid".to_string(),
325 geometry_type,
326 geometry_dimension,
327 srs_id,
328 property_columns: other_column_specs.to_vec(),
329 property_index_by_name,
330 insert_sql,
331 update_sql,
332 })
333 }
334
335 pub fn delete_layer(&self, layer_name: &str) -> Result<()> {
346 if self.read_only {
347 return Err(GpkgError::ReadOnly);
348 }
349
350 let (geometry_column, _, _, _) = self.get_geometry_column_and_srs_id(layer_name)?;
351
352 self.conn
353 .execute_batch(&gpkg_rtree_drop_sql(layer_name, &geometry_column))?;
354
355 self.conn.execute_batch(&sql_drop_table(layer_name))?;
356 Ok(())
357 }
358
359 pub fn to_bytes(&self) -> Result<Vec<u8>> {
374 let data: &[u8] = &self.conn.serialize("main")?;
375 Ok(data.to_vec())
376 }
377
378 pub fn from_bytes<D: AsRef<[u8]>>(data: D) -> Result<Self> {
394 let mut conn = rusqlite::Connection::open_in_memory()?;
395
396 let data_ref = data.as_ref();
397 let reader = std::io::Cursor::new(data_ref);
398 conn.deserialize_read_exact("main", reader, data_ref.len(), false)?;
399 Ok(Self {
400 conn: Rc::new(conn),
401 read_only: false,
402 })
403 }
404
405 pub(crate) fn get_column_specs(
407 &self,
408 layer_name: &str,
409 geometry_column: &str,
410 geometry_type: wkb::reader::GeometryType,
411 geometry_dimension: wkb::reader::Dimension,
412 srs_id: u32,
413 ) -> Result<GpkgLayerMetadata> {
414 let query = sql_table_columns(layer_name);
415 let mut stmt = self.conn.prepare(&query)?;
416
417 let mut primary_key_column: Option<String> = None;
418 let mut geometry_column_name: Option<String> = None;
419 let column_specs = stmt.query_map([], |row| {
420 let name: String = row.get(0)?;
421 let column_type_str: String = row.get(1)?;
422 let primary_key: i32 = row.get(2)?;
423 let primary_key = primary_key != 0;
424
425 let column_type = column_type_from_str(&column_type_str).ok_or_else(|| {
427 rusqlite::Error::InvalidColumnType(
428 1,
429 format!("Unexpected type {}", column_type_str),
430 rusqlite::types::Type::Text,
431 )
432 })?;
433
434 Ok((name, column_type, primary_key))
435 })?;
436
437 let result: std::result::Result<Vec<(String, crate::types::ColumnType, bool)>, _> =
438 column_specs.collect();
439 let mut other_columns = Vec::new();
440 for (name, column_type, is_primary_key) in result? {
441 if is_primary_key {
442 if primary_key_column.is_some() {
443 return Err(GpkgError::Message(format!(
444 "Composite primary keys are not supported yet for layer: {layer_name}"
445 )));
446 }
447 primary_key_column = Some(name.clone());
448 continue;
449 }
450 if name == geometry_column {
451 geometry_column_name = Some(name.clone());
452 } else {
453 other_columns.push(ColumnSpec { name, column_type });
454 }
455 }
456
457 let primary_key_column = primary_key_column.ok_or_else(|| {
458 GpkgError::Message(format!(
459 "No primary key column found for layer: {layer_name}"
460 ))
461 })?;
462
463 let geometry_column = geometry_column_name.ok_or_else(|| {
464 GpkgError::Message(format!("No geometry column found for layer: {layer_name}"))
465 })?;
466
467 Ok(GpkgLayerMetadata {
468 primary_key_column,
469 geometry_column,
470 geometry_type,
471 geometry_dimension,
472 srs_id,
473 other_columns,
474 })
475 }
476
477 pub(crate) fn get_geometry_column_and_srs_id(
479 &self,
480 layer_name: &str,
481 ) -> Result<(
482 String,
483 wkb::reader::GeometryType,
484 wkb::reader::Dimension,
485 u32,
486 )> {
487 let mut stmt = self.conn.prepare(SQL_SELECT_GEOMETRY_COLUMN_META)?;
488
489 let (geometry_column, geometry_type_str, z, m, srs_id) =
490 stmt.query_one([layer_name], |row| {
491 Ok((
492 row.get::<_, String>(0)?,
493 row.get::<_, String>(1)?,
494 row.get::<_, i8>(2)?,
495 row.get::<_, i8>(3)?,
496 row.get::<_, u32>(4)?,
497 ))
498 })?;
499
500 let geometry_type = geometry_type_from_str(&geometry_type_str)?;
501 let geometry_dimension = dimension_from_zm(z, m)?;
502
503 Ok((geometry_column, geometry_type, geometry_dimension, srs_id))
504 }
505}
506
507#[cfg(test)]
508mod tests {
509 use super::Gpkg;
510 use crate::error::GpkgError;
511 use crate::params;
512 use crate::types::{ColumnSpec, ColumnType};
513 use geo_types::Point;
514 use std::fs;
515 use std::time::{SystemTime, UNIX_EPOCH};
516 use wkb::reader::{Dimension, GeometryType};
517
518 #[test]
519 fn create_layer_requires_existing_srs() {
520 let gpkg = Gpkg::open_in_memory().expect("new gpkg");
521 let columns: Vec<ColumnSpec> = Vec::new();
522 let err = gpkg
523 .create_layer(
524 "missing_srs",
525 "geom",
526 wkb::reader::GeometryType::Point,
527 wkb::reader::Dimension::Xy,
528 9999,
529 &columns,
530 )
531 .expect_err("missing srs should fail");
532
533 match err {
534 GpkgError::Message(message) => {
535 assert!(message.contains("srs_id 9999"));
536 }
537 other => panic!("unexpected error: {other:?}"),
538 }
539 }
540
541 #[test]
542 fn delete_layer_rejects_read_only() {
543 let gpkg =
544 Gpkg::open_read_only("src/test/test_generated.gpkg").expect("open read-only gpkg");
545 let err = gpkg
546 .delete_layer("points")
547 .expect_err("read-only should fail");
548 assert!(matches!(err, GpkgError::ReadOnly));
549 }
550
551 #[test]
552 fn dump_roundtrips_in_memory_gpkg() -> Result<(), GpkgError> {
553 let gpkg = Gpkg::open_in_memory()?;
554
555 let columns = vec![
556 ColumnSpec {
557 name: "name".to_string(),
558 column_type: ColumnType::Varchar,
559 },
560 ColumnSpec {
561 name: "value".to_string(),
562 column_type: ColumnType::Integer,
563 },
564 ];
565 let layer = gpkg.create_layer(
566 "points",
567 "geom",
568 GeometryType::Point,
569 Dimension::Xy,
570 4326,
571 &columns,
572 )?;
573
574 let name_a = "alpha".to_string();
575 let value_a = 7_i64;
576 layer.insert(Point::new(1.0, 2.0), params![name_a, value_a])?;
577 let name_b = "beta".to_string();
578 let value_b = 9_i64;
579 layer.insert(Point::new(-3.0, 4.5), params![name_b, value_b])?;
580
581 let dump = gpkg.to_bytes()?;
582 let mut path = std::env::temp_dir();
583 let nanos = SystemTime::now()
584 .duration_since(UNIX_EPOCH)
585 .expect("time")
586 .as_nanos();
587 path.push(format!("rusqlite_gpkg_dump_{nanos}.gpkg"));
588 fs::write(&path, dump).unwrap();
589
590 let reopened = Gpkg::open_read_only(&path)?;
591 let layers = reopened.list_layers()?;
592 assert_eq!(layers, vec!["points".to_string()]);
593
594 let reopened_layer = reopened.get_layer("points")?;
595 let features = reopened_layer.features()?;
596 assert_eq!(features.len(), 2);
597
598 assert_eq!(features[0].id(), 1);
599 let name: String = features[0]
600 .property("name")
601 .ok_or("missing name")?
602 .try_into()?;
603 assert_eq!(name, "alpha");
604 let value: i64 = features[0]
605 .property("value")
606 .ok_or("missing value")?
607 .try_into()?;
608 assert_eq!(value, 7);
609 assert_eq!(features[0].geometry()?.geometry_type(), GeometryType::Point);
610
611 assert_eq!(features[1].id(), 2);
612 let name: String = features[1]
613 .property("name")
614 .ok_or("missing name")?
615 .try_into()?;
616 assert_eq!(name, "beta");
617 let value: i64 = features[1]
618 .property("value")
619 .ok_or("missing value")?
620 .try_into()?;
621 assert_eq!(value, 9);
622 assert_eq!(features[1].geometry()?.geometry_type(), GeometryType::Point);
623
624 let _ = fs::remove_file(&path);
625 Ok(())
626 }
627
628 #[test]
629 fn dump_roundtrips_in_memory_gpkg_from_bytes() -> Result<(), GpkgError> {
630 let gpkg = Gpkg::open_in_memory()?;
631
632 let columns = vec![
633 ColumnSpec {
634 name: "name".to_string(),
635 column_type: ColumnType::Varchar,
636 },
637 ColumnSpec {
638 name: "value".to_string(),
639 column_type: ColumnType::Integer,
640 },
641 ];
642 let layer = gpkg.create_layer(
643 "points",
644 "geom",
645 GeometryType::Point,
646 Dimension::Xy,
647 4326,
648 &columns,
649 )?;
650
651 let name_a = "alpha".to_string();
652 let value_a = 7_i64;
653 layer.insert(Point::new(1.0, 2.0), params![name_a, value_a])?;
654 let name_b = "beta".to_string();
655 let value_b = 9_i64;
656 layer.insert(Point::new(-3.0, 4.5), params![name_b, value_b])?;
657
658 let dump = gpkg.to_bytes()?;
659
660 let restored = Gpkg::from_bytes(&dump)?;
661
662 let layers = restored.list_layers()?;
663 assert_eq!(layers, vec!["points".to_string()]);
664
665 let restored_layer = restored.get_layer("points")?;
666 let features = restored_layer.features()?;
667 assert_eq!(features.len(), 2);
668
669 assert_eq!(features[0].id(), 1);
670 let name: String = features[0]
671 .property("name")
672 .ok_or("missing name")?
673 .try_into()?;
674 assert_eq!(name, "alpha");
675 let value: i64 = features[0]
676 .property("value")
677 .ok_or("missing value")?
678 .try_into()?;
679 assert_eq!(value, 7);
680 assert_eq!(features[0].geometry()?.geometry_type(), GeometryType::Point);
681
682 assert_eq!(features[1].id(), 2);
683 let name: String = features[1]
684 .property("name")
685 .ok_or("missing name")?
686 .try_into()?;
687 assert_eq!(name, "beta");
688 let value: i64 = features[1]
689 .property("value")
690 .ok_or("missing value")?
691 .try_into()?;
692 assert_eq!(value, 9);
693 assert_eq!(features[1].geometry()?.geometry_type(), GeometryType::Point);
694
695 Ok(())
696 }
697}