github.com/PDOK/gokoala@v0.50.6/internal/ogc/features/datasources/geopackage/metadata.go (about) 1 package geopackage 2 3 import ( 4 "errors" 5 "fmt" 6 "log" 7 8 "github.com/PDOK/gokoala/config" 9 10 "github.com/jmoiron/sqlx" 11 ) 12 13 // Read metadata about gpkg and sqlite driver 14 func readDriverMetadata(db *sqlx.DB) (string, error) { 15 type pragma struct { 16 UserVersion string `db:"user_version"` 17 } 18 type metadata struct { 19 Sqlite string `db:"sqlite"` 20 Spatialite string `db:"spatialite"` 21 Arch string `db:"arch"` 22 } 23 24 var m metadata 25 err := db.QueryRowx(` 26 select sqlite_version() as sqlite, 27 spatialite_version() as spatialite, 28 spatialite_target_cpu() as arch`).StructScan(&m) 29 if err != nil { 30 return "", err 31 } 32 33 var gpkgVersion pragma 34 _ = db.QueryRowx(`pragma user_version`).StructScan(&gpkgVersion) 35 if gpkgVersion.UserVersion == "" { 36 gpkgVersion.UserVersion = "unknown" 37 } 38 39 return fmt.Sprintf("geopackage version: %s, sqlite version: %s, spatialite version: %s on %s", 40 gpkgVersion.UserVersion, m.Sqlite, m.Spatialite, m.Arch), nil 41 } 42 43 // Read gpkg_contents table. This table contains metadata about feature tables. The result is a mapping from 44 // collection ID -> feature table metadata. We match each feature table to the collection ID by looking at the 45 // 'identifier' column. Also in case there's no exact match between 'collection ID' and 'identifier' we use 46 // the explicitly configured table name. 47 func readGpkgContents(collections config.GeoSpatialCollections, db *sqlx.DB) (map[string]*featureTable, error) { 48 query := ` 49 select 50 c.table_name, c.data_type, c.identifier, c.description, c.last_change, 51 c.min_x, c.min_y, c.max_x, c.max_y, c.srs_id, gc.column_name, gc.geometry_type_name 52 from 53 gpkg_contents c join gpkg_geometry_columns gc on c.table_name == gc.table_name 54 where 55 c.data_type = 'features' and 56 c.min_x is not null` 57 58 rows, err := db.Queryx(query) 59 if err != nil { 60 return nil, fmt.Errorf("failed to retrieve gpkg_contents using query: %v\n, error: %w", query, err) 61 } 62 defer rows.Close() 63 64 result := make(map[string]*featureTable, 10) 65 for rows.Next() { 66 row := featureTable{ 67 ColumnsWithDateType: make(map[string]string), 68 } 69 if err = rows.StructScan(&row); err != nil { 70 return nil, fmt.Errorf("failed to read gpkg_contents record, error: %w", err) 71 } 72 if row.TableName == "" { 73 return nil, fmt.Errorf("feature table name is blank, error: %w", err) 74 } 75 if err = readFeatureTableInfo(db, row); err != nil { 76 return nil, fmt.Errorf("failed to read feature table metadata, error: %w", err) 77 } 78 79 for _, collection := range collections { 80 if row.Identifier == collection.ID { 81 result[collection.ID] = &row 82 } else if hasMatchingTableName(collection, row) { 83 result[collection.ID] = &row 84 } 85 } 86 } 87 88 if err = rows.Err(); err != nil { 89 return nil, err 90 } 91 if len(result) == 0 { 92 return nil, errors.New("no records found in gpkg_contents, can't serve features") 93 } 94 uniqueTables := make(map[string]struct{}) 95 for _, table := range result { 96 uniqueTables[table.TableName] = struct{}{} 97 } 98 if len(uniqueTables) != len(result) { 99 log.Printf("Warning: found %d unique table names for %d collections, "+ 100 "usually each collection is backed by its own unique table\n", len(uniqueTables), len(result)) 101 } 102 return result, nil 103 } 104 105 func readFeatureTableInfo(db *sqlx.DB, table featureTable) error { 106 rows, err := db.Queryx(fmt.Sprintf("select name, type from pragma_table_info('%s')", table.TableName)) 107 if err != nil { 108 return err 109 } 110 defer rows.Close() 111 112 for rows.Next() { 113 var colName, colType string 114 err = rows.Scan(&colName, &colType) 115 if err != nil { 116 return err 117 } 118 table.ColumnsWithDateType[colName] = colType 119 } 120 return nil 121 } 122 123 func hasMatchingTableName(collection config.GeoSpatialCollection, row featureTable) bool { 124 return collection.Features != nil && collection.Features.TableName != nil && 125 row.Identifier == *collection.Features.TableName 126 }