1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
//! Error type for the `geopackage` crate.
/// Errors returned by this crate.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
/// Underlying SQLite error.
#[error(transparent)]
Sqlite(#[from] rusqlite::Error),
/// Spec-level error from `geopackage-core`.
#[error(transparent)]
Core(#[from] geopackage_core::Error),
/// Underlying Arrow error (feature `arrow`).
#[cfg(feature = "arrow")]
#[error(transparent)]
Arrow(#[from] arrow_schema::ArrowError),
/// A stored value's storage class cannot go into the Arrow array its column
/// maps to (feature `arrow`).
///
/// The columnar counterpart of [`Self::ValueTypeMismatch`], reported
/// separately because it names an Arrow type rather than a declared
/// GeoPackage one.
#[cfg(feature = "arrow")]
#[error("column {column:?} maps to Arrow {expected} but holds a {found} value")]
ArrowValueMismatch {
/// The column that was read.
column: String,
/// The Arrow type the column maps to.
expected: &'static str,
/// The SQLite storage class actually found: one of `NULL`, `INTEGER`,
/// `REAL`, `TEXT`, or `BLOB`.
found: &'static str,
},
/// A schema field whose Arrow type the columnar reader has no builder for
/// (feature `arrow`).
///
/// Unreachable from a schema this crate derived; it exists so that adding a
/// mapping without adding its builder is an error rather than a panic.
#[cfg(feature = "arrow")]
#[error("no columnar builder for Arrow type {data_type}")]
UnsupportedArrowType {
/// The Arrow type that has no builder.
data_type: String,
},
/// The file is not identifiable as a GeoPackage.
#[error(
"not a GeoPackage: {reason} (application_id={application_id:#010x}, user_version={user_version})"
)]
NotAGeoPackage {
/// Why identification failed.
reason: &'static str,
/// The file's `application_id` pragma.
application_id: u32,
/// The file's `user_version` pragma.
user_version: u32,
},
/// `create` was asked to overwrite an existing non-empty file.
#[error("refusing to create GeoPackage over existing non-empty file: {0}")]
AlreadyExists(std::path::PathBuf),
/// An EPSG code outside the vendored definition subset.
#[error(
"EPSG:{code} is not in the vendored definition subset; \
supply the WKT yourself via GeoPackage::add_srs"
)]
UnknownEpsgCode {
/// The requested EPSG code.
code: i32,
},
/// A `gpkg_geometry_columns.geometry_type_name` value outside the
/// spec vocabulary (Annex G).
#[error("unknown geometry type name {name:?} for table {table_name:?}")]
UnknownGeometryType {
/// The table the row describes.
table_name: String,
/// The unrecognised type name as stored.
name: String,
},
/// A `gpkg_geometry_columns.z` or `.m` value outside `0`/`1`/`2`.
#[error("invalid {column} flag {value} in gpkg_geometry_columns for table {table_name:?}")]
InvalidZmFlag {
/// The table the row describes.
table_name: String,
/// Which column carried the bad value: `"z"` or `"m"`.
column: &'static str,
/// The value as stored.
value: i64,
},
/// Introspection was asked for a table that does not exist (its
/// `PRAGMA table_info` returned no rows).
#[error("no such table: {table_name:?}")]
NoSuchTable {
/// The requested table name.
table_name: String,
},
/// A column was requested that the table does not have.
#[error("table {table_name:?} has no column {column_name:?}")]
NoSuchColumn {
/// The table that was queried.
table_name: String,
/// The requested column name.
column_name: String,
},
/// A stored value's SQLite storage class is incompatible with the column's
/// declared GeoPackage type; the value is surfaced rather than coerced.
#[error("column {column:?} declared {declared:?} holds an incompatible {found} value")]
ValueTypeMismatch {
/// The column that was read.
column: String,
/// The column's declared GeoPackage type.
declared: geopackage_core::types::ColumnType,
/// The SQLite storage class actually found: one of `NULL`, `INTEGER`,
/// `REAL`, `TEXT`, or `BLOB`.
found: &'static str,
},
/// A `BOOLEAN` column holds an INTEGER other than `0` or `1`, read under
/// [`StorageStrictness::Strict`](crate::StorageStrictness::Strict).
///
/// The storage class is the right one, so this is not a
/// [`Self::ValueTypeMismatch`]: the value itself is outside what the
/// declared type permits. Lenient conversion, the default, reads any
/// non-zero integer as `true` instead.
#[error("column {column:?} declared BOOLEAN holds {value}, which is neither 0 nor 1")]
NonBooleanInteger {
/// The column that was read.
column: String,
/// The integer the column actually holds.
value: i64,
},
/// A `DATE` or `DATETIME` column holds text that does not parse.
#[error("column {column:?} holds invalid date/datetime text {text:?}")]
InvalidDateTimeValue {
/// The column that was read.
column: String,
/// The offending text as stored.
text: String,
/// The underlying parse error.
#[source]
source: geopackage_core::datetime::DateTimeError,
},
/// A geometry column was read through the value API, which handles only
/// non-geometry columns; geometry is read through the feature API.
#[error("column {column:?} is a geometry column and cannot be read as a Value")]
GeometryValueUnsupported {
/// The geometry column that was read.
column: String,
},
/// A geometry blob's WKB type does not satisfy the column's declared
/// `gpkg_geometry_columns` type (opt-in check; see
/// `Layer::with_geometry_type_validation`).
#[error(
"geometry in {table_name:?}.{column_name:?} is {found} but the column is declared {declared}"
)]
GeometryTypeMismatch {
/// The feature table.
table_name: String,
/// The geometry column.
column_name: String,
/// The declared `gpkg_geometry_columns` type.
declared: geopackage_core::types::GeometryType,
/// The WKB body's actual type.
found: geopackage_core::types::GeometryType,
},
/// A layer was requested by a name that is not present in `gpkg_contents`.
#[error("no such layer: {table_name:?} is not registered in gpkg_contents")]
NoSuchLayer {
/// The requested layer name.
table_name: String,
},
/// A layer was requested with the wrong accessor: its `gpkg_contents`
/// `data_type` does not match the accessor used ([`crate::GeoPackage::layer`]
/// expects `features`, [`crate::GeoPackage::attributes`] expects
/// `attributes`).
#[error("layer {table_name:?} has data_type {found:?}, not {expected:?}")]
WrongDataType {
/// The layer as named in `gpkg_contents`.
table_name: String,
/// The `data_type` the accessor requires.
expected: &'static str,
/// The `data_type` actually recorded in `gpkg_contents`.
found: String,
},
/// A spatial (bounding-box) query was requested on a layer that has no
/// geometry column (an attribute layer, or a feature table whose
/// `gpkg_geometry_columns` row is missing).
#[error("layer {table_name:?} has no geometry column; spatial queries are unavailable")]
NoGeometryColumn {
/// The layer that was queried.
table_name: String,
},
/// A table was requested with a name beginning `gpkg_`, which the spec
/// reserves for its own tables (Requirement 25).
#[error("table name {table_name:?} is reserved: user table names must not begin with 'gpkg_'")]
ReservedTablePrefix {
/// The rejected table name.
table_name: String,
},
/// A table was created with a name that a table or view already uses.
#[error("table {table_name:?} already exists")]
TableAlreadyExists {
/// The clashing table name.
table_name: String,
},
/// A layer was created referencing an `srs_id` absent from
/// `gpkg_spatial_ref_sys`.
#[error(
"srs_id {srs_id} is not present in gpkg_spatial_ref_sys; \
register it first with GeoPackage::add_srs or GeoPackage::add_epsg_srs"
)]
UnknownSrs {
/// The unregistered spatial reference system identifier.
srs_id: i32,
},
/// A layer was created with an extension (non-linear or abstract) geometry
/// type. Writing those types is a later milestone.
#[error(
"geometry type {geometry_type} requires a gpkg_geom_<TYPE> extension; \
writing extension geometry types is not yet supported"
)]
ExtensionGeometryUnsupported {
/// The rejected geometry type.
geometry_type: geopackage_core::types::GeometryType,
},
/// [`crate::GeoPackage::create_layer`] was called with a builder that has no
/// geometry column (use [`crate::GeoPackage::create_attributes_table`] for a
/// non-spatial table).
#[error("cannot create feature layer {table_name:?}: the builder has no geometry column")]
MissingGeometrySpec {
/// The table the builder describes.
table_name: String,
},
/// [`crate::GeoPackage::create_attributes_table`] was called with a builder
/// that has a geometry column (use [`crate::GeoPackage::create_layer`] for a
/// feature table).
#[error("cannot create attributes table {table_name:?}: the builder has a geometry column")]
UnexpectedGeometrySpec {
/// The table the builder describes.
table_name: String,
},
/// [`crate::Layer::extent`] measured a layer's extent but could not record
/// it, for a reason that is not another connection holding a lock.
///
/// The measurement succeeded and is carried here, so nothing is lost by the
/// failure: the caller can use `extent` and decide what to make of the
/// store being unwritable. Lock contention does not produce this, because a
/// concurrent writer means the measurement is not one the crate could vouch
/// for anyway; what does are the conditions that mean the store is broken
/// or unwritable in a way that will not clear, such as an unwritable
/// directory, a full disk, or an I/O error.
#[error("measured the extent of table {table_name:?} but could not record it: {source}")]
ExtentPersist {
/// The table whose extent was measured.
table_name: String,
/// The measured extent, which is what [`crate::Layer::extent`] would
/// have returned. `None` when the layer had nothing to measure.
extent: Option<crate::BoundingBox>,
/// Why the write failed. Boxed only to keep this variant from setting
/// the size of every `Result` in the crate.
source: Box<rusqlite::Error>,
},
/// [`crate::Feature::geometry`] was called on a row whose read did not
/// select the geometry column.
///
/// Distinct from `Ok(None)`, which means the row's geometry is NULL. This
/// says the geometry was never read, because
/// [`crate::Layer::with_columns`] did not name it or
/// [`crate::Layer::without_geometry`] excluded it, and it is an error
/// rather than an empty answer so the two cannot be confused.
/// [`crate::Feature::has_geometry_column`] tests for it without erroring.
#[error("this feature's read did not select the geometry column")]
GeometryNotProjected,
/// A partial update named the same column more than once.
///
/// SQLite accepts a repeated assignment and applies the last one, so this
/// is rejected rather than resolved: naming a column twice with different
/// values is more likely to be a caller's mistake than an intention.
#[error("update of table {table_name:?} names column {column_name:?} more than once")]
DuplicateUpdateColumn {
/// The table being written.
table_name: String,
/// The column named twice.
column_name: String,
},
/// A write supplied a value slice whose length does not match the layer's
/// non-geometry column count.
#[error("table {table_name:?} expects {expected} value(s) per row, got {found}")]
ValueCountMismatch {
/// The table being written.
table_name: String,
/// The number of non-geometry columns.
expected: usize,
/// The number of values supplied.
found: usize,
},
/// A spatial-index operation was requested on a layer whose table has no
/// single-column primary key. The RTree triggers key the index on that
/// column, so one is required to build, repair, or use the index.
#[error("table {table_name:?} has no single-column primary key; a spatial index requires one")]
NoPrimaryKey {
/// The table that was queried.
table_name: String,
},
/// [`crate::Layer::create_spatial_index`] was called on a layer whose
/// geometry column already carries an RTree spatial index (its
/// `rtree_<table>_<column>` virtual table already exists).
#[error("table {table_name:?} column {column_name:?} already has a spatial index")]
SpatialIndexExists {
/// The feature table.
table_name: String,
/// The geometry column.
column_name: String,
},
/// [`crate::Layer::repair_spatial_index`] was called on a layer that has no
/// RTree triggers to repair. Build an index with
/// [`crate::Layer::create_spatial_index`] first.
#[error(
"table {table_name:?} column {column_name:?} has no spatial index to repair; \
create one with Layer::create_spatial_index"
)]
NoSpatialIndex {
/// The feature table.
table_name: String,
/// The geometry column.
column_name: String,
},
/// A written geometry's `z`/`m` presence violates the geometry column's
/// declared constraint (`gpkg_geometry_columns.z` / `.m`).
#[error(
"geometry for {table_name:?}.{column:?} {verb} a {dimension} dimension, \
but the column declares it {constraint:?}"
)]
ZmViolation {
/// The feature table.
table_name: String,
/// The geometry column.
column: String,
/// Which dimension: `"z"` or `"m"`.
dimension: &'static str,
/// The column's declared constraint.
constraint: geopackage_core::types::ZmFlag,
/// `"carries"` when the geometry has the dimension, `"lacks"` when it
/// does not.
verb: &'static str,
},
}
/// Convenience alias.
pub type Result<T, E = Error> = std::result::Result<T, E>;