geopackage-ffi 0.12.0

C ABI for the geopackage crate: read and write OGC GeoPackage (.gpkg) files from C
Documentation
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
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
//! Handle representation, and the one place the crate erases a lifetime.
//!
//! # What a C caller sees
//!
//! A C caller sees six opaque pointer types, which the crate documentation
//! lists with the functions that create and free them. One rule connects them:
//! you cannot close a container while any handle that borrows it is still
//! alive. The attempt returns `GPKG_STATUS_HANDLE_IN_USE`, changes nothing,
//! and leaves the container open, so you can free the children and try again.
//!
//! ```c
//! gpkg_error_t error = {GPKG_STATUS_OK, NULL};
//! gpkg_t *gpkg = gpkg_open_read_only("places.gpkg", &error);
//! gpkg_layer_t *layer = gpkg_layer_open(gpkg, "points", &error);
//! gpkg_tiles_t *tiles = gpkg_tiles_open(gpkg, "basemap", &error);
//!
//! if (gpkg_close(gpkg, &error) == GPKG_STATUS_HANDLE_IN_USE) {
//!     // The message counts the outstanding children. Nothing was torn down:
//!     // every handle above, gpkg included, is still usable.
//!     gpkg_error_clear(&error);
//! }
//!
//! gpkg_tiles_free(tiles);
//! gpkg_layer_free(layer);
//! gpkg_close(gpkg, &error);
//! ```
//!
//! An Arrow stream from `gpkg_layer_read_arrow` counts as a child in the same
//! way. You free it through its own `release` callback rather than with a
//! `gpkg_*_free` call. Freeing a child twice, or using one after it has been
//! freed, is undefined behaviour. The rule protects the container from its
//! children, not each handle from its own caller.
//!
//! # The problem it solves
//!
//! `geopackage::Layer<'a>` borrows the `GeoPackage` it came from, and so do
//! `TilePyramid<'a>` and the cursors. A C caller holds independent pointers and
//! frees them in whatever order it likes, so a borrow cannot be expressed
//! across the boundary.
//!
//! The alternative to erasing the borrow is for each call to rebuild the
//! borrowed handle from its parent. This cost was measured rather than assumed.
//! Rebuilding runs a fixed set of catalogue queries and takes a near-constant
//! 37 to 51 microseconds, however much the call that follows costs. The
//! overhead is +778% on `get_tile`, and +131% to +150% on a small bounding-box
//! query. So the crate erases the borrow once, when it makes the handle, and a
//! call from C costs only what the call itself costs.
//!
//! # Representation
//!
//! A container handle owns its `GeoPackage` behind a `Box`, so the address of
//! the `GeoPackage` is fixed for as long as the handle lives, whatever happens
//! to the handle itself. A child handle stores a `Layer<'static>` produced by
//! erasing the borrow, plus a pointer back to its parent so that it can tell
//! the parent when it is freed.
//!
//! # Soundness
//!
//! The crate enforces three invariants itself, rather than assuming them of the
//! caller:
//!
//! 1. **The borrowed-from value never moves.** `Container::gpkg` is a `Box`,
//!    and nothing in this crate takes the `GeoPackage` out of it or produces a
//!    `&mut` to it. Moving the `Container` itself, which cannot happen anyway
//!    once it is behind a raw pointer, would not move the `GeoPackage`.
//! 2. **A child never outlives its parent.** The only path that destroys a
//!    container checks [`Container::outstanding_children`] first and fails
//!    while it is non-zero. Freeing a child decrements it. So the `GeoPackage`
//!    a `Layer<'static>` points into is still alive whenever that layer is
//!    used.
//! 3. **A container and its children are used from one thread at a time.**
//!    `geopackage::GeoPackage` is `Send` but not `Sync`, because
//!    `rusqlite::Connection` is, and every child handle borrows the same
//!    connection. The ABI documents this as a rule for the whole set: it may
//!    move between threads together, but no two of its handles may be in use
//!    on different threads at once. On that basis the counter below is a plain
//!    `Cell` rather than an atomic. An atomic would make only the count
//!    thread-safe, not the shared connection, so it would imply a guarantee
//!    this crate does not make.
//!
//! A C caller sees invariant 2 as the `GPKG_STATUS_HANDLE_IN_USE` failure in
//! the example above. The Rust API enforces the same rule at compile time,
//! since `close` there takes `self` and a live `Layer` borrows it.

use std::cell::Cell;

use geopackage::{
    BoundingBox, FeatureWriter, GeoPackage, Layer, Tile, TileCursor, TilePyramid, TileStream,
};

/// A `gpkg_t`: an open GeoPackage, and a count of the handles borrowing it.
pub struct Container {
    /// Boxed so its address is stable. Never replaced, never moved out of, and
    /// never lent mutably, which is invariant 1 above.
    gpkg: Box<GeoPackage>,
    /// Live child handles. While it is non-zero, [`crate::gpkg_close`] refuses:
    /// it checks [`Self::outstanding_children`] before [`Self::close`]
    /// consumes anything.
    ///
    /// A `Cell`, not an `AtomicUsize`: the container and its children share
    /// one connection, and the ABI requires them to be used from one thread at
    /// a time, so an atomic here would suggest a cross-thread guarantee that
    /// the rest of the type cannot keep.
    children: Cell<usize>,
}

impl Container {
    /// Wraps an open GeoPackage as a container handle.
    pub fn new(gpkg: GeoPackage) -> Self {
        Self {
            gpkg: Box::new(gpkg),
            children: Cell::new(0),
        }
    }

    /// Returns the GeoPackage, borrowed for as long as the caller holds the
    /// container.
    pub fn gpkg(&self) -> &GeoPackage {
        &self.gpkg
    }

    /// Opens a layer as a child handle, registering it against this
    /// container.
    ///
    /// # Errors
    ///
    /// Whatever [`GeoPackage::layer`] returns.
    pub fn layer(&self, name: &str) -> geopackage::Result<LayerHandle> {
        let layer = self.gpkg.layer(name)?;
        Ok(self.adopt(layer))
    }

    /// Opens an attribute layer as a child handle.
    ///
    /// # Errors
    ///
    /// Whatever [`GeoPackage::attributes`] returns.
    pub fn attributes(&self, name: &str) -> geopackage::Result<LayerHandle> {
        let layer = self.gpkg.attributes(name)?;
        Ok(self.adopt(layer))
    }

    /// Opens a layer projected to the named columns, as a child handle.
    ///
    /// `attributes` picks which open runs underneath. That is the only place
    /// the two entry points differ.
    ///
    /// # Errors
    ///
    /// Whatever the open returns, or [`geopackage::Error::NoSuchColumn`] from
    /// the projection.
    pub fn layer_with_columns(
        &self,
        name: &str,
        columns: &[&str],
        attributes: bool,
    ) -> geopackage::Result<LayerHandle> {
        let layer = if attributes {
            self.gpkg.attributes(name)?
        } else {
            self.gpkg.layer(name)?
        };
        Ok(self.adopt(layer.with_columns(columns)?))
    }

    /// Erases a layer's borrow and counts it as a child.
    ///
    /// The lifetime `'_` on the way in is a borrow of `*self.gpkg`, which lives
    /// as long as `self` and does not move. On the way out it is `'static`,
    /// which claims more than the borrow can support on its own. What supports
    /// it is the module's invariants. The layer is only reachable through a
    /// [`LayerHandle`], and dropping that handle is what decrements the counter
    /// that stops `self` being closed.
    fn adopt(&self, layer: Layer<'_>) -> LayerHandle {
        // SAFETY: `Layer<'a>` borrows the `GeoPackage` inside `self.gpkg`,
        // which is boxed and never moved or replaced (invariant 1), and this
        // container cannot be closed while the returned handle is alive
        // (invariant 2, enforced by `gpkg_close` checking `children`). So the
        // erased-to-`'static` borrow never outlives what it points at. The
        // transmute changes only the lifetime parameter; `Layer` has no other
        // generic parameter and the same layout either way.
        let erased: Layer<'static> = unsafe { std::mem::transmute(layer) };
        LayerHandle {
            layer: erased,
            _token: self.token(),
        }
    }

    /// Opens a tile pyramid as a child handle, registering it against this
    /// container.
    ///
    /// # Errors
    ///
    /// Whatever [`GeoPackage::tiles`] returns.
    pub fn tiles(&self, name: &str) -> geopackage::Result<TilesHandle> {
        let pyramid = self.gpkg.tiles(name)?;
        // SAFETY: the same argument as `adopt`. `TilePyramid<'a>` borrows the
        // `GeoPackage` inside `self.gpkg`, which is boxed and never moved or
        // replaced (invariant 1), and the token taken below stops this
        // container closing while the handle lives (invariant 2). The
        // transmute changes only the lifetime parameter.
        let erased: TilePyramid<'static> = unsafe { std::mem::transmute(pyramid) };
        Ok(TilesHandle {
            pyramid: erased,
            _token: self.token(),
        })
    }

    /// Creates a tile pyramid and returns it as a child handle.
    ///
    /// # Errors
    ///
    /// Whatever [`GeoPackage::create_tile_pyramid`] returns.
    pub fn create_tiles(
        &self,
        builder: &geopackage::TilePyramidBuilder,
    ) -> geopackage::Result<TilesHandle> {
        let pyramid = self.gpkg.create_tile_pyramid(builder)?;
        // SAFETY: the same argument as `tiles` above: the pyramid borrows the
        // boxed, never-moved `GeoPackage`, and the token stops this container
        // closing while the handle lives. The transmute changes only the
        // lifetime parameter.
        let erased: TilePyramid<'static> = unsafe { std::mem::transmute(pyramid) };
        Ok(TilesHandle {
            pyramid: erased,
            _token: self.token(),
        })
    }

    /// Takes one count against this container, released when the token drops.
    ///
    /// Used by anything that borrows this container: layer handles, tiles
    /// handles, writers, tile cursors, and the Arrow streams in
    /// [`crate::stream`].
    pub fn token(&self) -> ChildToken {
        self.children.set(self.children.get() + 1);
        ChildToken {
            parent: std::ptr::from_ref(self),
        }
    }

    /// Records that one child handle has gone.
    fn release_child(&self) {
        self.children.set(self.children.get().saturating_sub(1));
    }

    /// Returns the number of child handles still borrowing this container.
    ///
    /// Checked before [`Self::close`] consumes anything, so a failed close
    /// leaves the container open and usable rather than half-torn-down.
    pub fn outstanding_children(&self) -> usize {
        self.children.get()
    }

    /// Closes the container.
    ///
    /// The caller must have checked [`Self::outstanding_children`] first: this
    /// consumes the container, so there is nothing to return on failure.
    ///
    /// # Errors
    ///
    /// Whatever [`GeoPackage::close`] returns, which for a WAL handle is the
    /// checkpoint-and-reset that makes the file a single file again.
    pub fn close(self) -> geopackage::Result<()> {
        self.gpkg.close()
    }
}

/// One count against a container's child tally, released on drop.
///
/// Every child handle owns one: a layer handle, a tiles handle, a writer and
/// a tile cursor. So does an Arrow stream, which borrows the same container
/// and needs the same protection: while a token is alive, the container
/// cannot close, so an erased `'static` borrow cannot outlive what it points
/// at.
pub struct ChildToken {
    /// The container counted against. Raw rather than a reference, because a
    /// reference would reintroduce the borrow this exists to erase.
    parent: *const Container,
}

impl Drop for ChildToken {
    fn drop(&mut self) {
        // SAFETY: `parent` came from `std::ptr::from_ref` on a live `Container`
        // in `Container::token`, and the container outlives every token it
        // made, because closing refuses while any is outstanding. The ABI's
        // threading rule means no other thread is using the container while
        // this runs, so the non-atomic decrement does not race.
        let parent = unsafe { &*self.parent };
        parent.release_child();
    }
}

/// A `gpkg_layer_t`: a layer, and the container it borrows.
pub struct LayerHandle {
    /// The erased borrow. See [`Container::adopt`] for why it is sound.
    layer: Layer<'static>,
    /// What stops the container being closed while this handle exists. Dropped with
    /// the handle, which is what later permits a close.
    _token: ChildToken,
}

impl LayerHandle {
    /// Returns the layer, borrowed for as long as the caller holds the
    /// handle.
    pub fn layer(&self) -> &Layer<'static> {
        &self.layer
    }

    /// Begins a write transaction over this layer as a child handle.
    ///
    /// No borrow is erased here, unlike the layer and pyramid handles.
    /// `Layer::writer` returns a `FeatureWriter` with the layer's own lifetime
    /// parameter rather than a borrow of `&self`, and this layer's parameter is
    /// already `'static`, so the writer arrives erased. What it still needs is
    /// the count: the writer points into the same container, so the container
    /// must not close while it lives.
    ///
    /// # Errors
    ///
    /// Whatever [`geopackage::Layer::writer`] returns, which fails on a
    /// read-only container.
    pub fn writer(&self) -> geopackage::Result<WriterHandle> {
        let writer = self.layer.writer()?;
        Ok(WriterHandle {
            writer,
            _token: self.token(),
        })
    }

    /// Takes another count against this handle's container, for something
    /// that borrows the same container independently, such as an Arrow
    /// stream.
    pub fn token(&self) -> ChildToken {
        // SAFETY: this handle owns a token of its own, so the container it
        // points at is still alive; taking a second count against it is the
        // same operation `Container::token` performs.
        let parent = unsafe { &*self._token.parent };
        parent.token()
    }
}

/// A `gpkg_writer_t`: a write transaction over a layer, and the container it
/// borrows.
///
/// Held by value rather than boxed, because unlike a container nothing borrows
/// *this*: the writer is the leaf of the handle graph.
pub struct WriterHandle {
    /// Already `'static` when it arrives, since it has its layer's lifetime
    /// parameter rather than a borrow of the layer handle. See
    /// [`LayerHandle::writer`].
    writer: FeatureWriter<'static>,
    /// What stops the container being closed while this handle exists.
    _token: ChildToken,
}

impl WriterHandle {
    /// Returns a mutable reference to the writer, which every staging call
    /// needs.
    pub fn writer_mut(&mut self) -> &mut FeatureWriter<'static> {
        &mut self.writer
    }

    /// Commits the transaction, consuming the handle.
    ///
    /// The token drops with `self` whatever the outcome, so a failed commit
    /// still releases the container rather than leaving it uncloseable.
    ///
    /// # Errors
    ///
    /// Whatever [`geopackage::FeatureWriter::commit`] returns.
    pub fn commit(self) -> geopackage::Result<()> {
        self.writer.commit()
    }
}

/// A `gpkg_tiles_t`: a tile pyramid, and the container it borrows.
pub struct TilesHandle {
    /// The erased borrow. See [`Container::tiles`] for why it is sound.
    pyramid: TilePyramid<'static>,
    /// What stops the container being closed while this handle exists. Dropped with
    /// the handle, which is what later permits a close.
    _token: ChildToken,
}

/// Which stored-tile scan a cursor runs.
pub enum CursorScan {
    /// Every stored tile, in matrix order.
    All,
    /// One zoom level.
    At(i64),
    /// One zoom level, within a bounding box in the pyramid's own SRS.
    In(i64, BoundingBox),
}

impl TilesHandle {
    /// Returns the pyramid, borrowed for as long as the caller holds the
    /// handle.
    pub fn pyramid(&self) -> &TilePyramid<'static> {
        &self.pyramid
    }

    /// Takes another count against this handle's container, for something
    /// that borrows the same container independently, such as a tile cursor.
    pub fn token(&self) -> ChildToken {
        // SAFETY: this handle owns a token of its own, so the container it
        // points at is still alive; taking a second count against it is the
        // same operation `Container::token` performs.
        let parent = unsafe { &*self._token.parent };
        parent.token()
    }

    /// Begins a stored-tile scan as a child handle.
    ///
    /// The cursor visits the tiles the pyramid stores instead of probing the
    /// declared grid, so on a sparse pyramid it costs O(stored) rather than
    /// O(grid). It counts against the *container*, not against
    /// this handle: the statement underneath borrows the container's
    /// connection, so the tiles handle itself may be freed while the cursor
    /// lives, and the container still cannot close until the cursor is
    /// freed.
    ///
    /// # Errors
    ///
    /// Whatever the corresponding [`TilePyramid`] cursor constructor returns.
    pub fn cursor(&self, scan: &CursorScan) -> geopackage::Result<TileCursorHandle> {
        let cursor = match scan {
            CursorScan::All => self.pyramid.cursor()?,
            CursorScan::At(zoom) => self.pyramid.cursor_at(*zoom)?,
            CursorScan::In(zoom, bbox) => self.pyramid.cursor_in(*zoom, *bbox)?,
        };
        // SAFETY: the cursor's borrow is a prepared statement against the
        // container's connection, which is boxed and never moved or replaced
        // (invariant 1), and the container cannot close while the token below
        // is alive (invariant 2). So the erased-to-`'static` borrow never
        // outlives what it points at; the transmute changes only the lifetime
        // parameter.
        let erased: TileCursor<'static> = unsafe { std::mem::transmute(cursor) };
        let mut cursor = Box::new(erased);
        let stream = cursor.tiles()?;
        // SAFETY: the stream borrows the boxed cursor's statement. The box
        // gives that statement a stable address, the struct below owns both
        // and declares the stream first, so it drops before the cursor, and
        // nothing hands out a second borrow of the cursor while the stream
        // lives. The transmute changes only the lifetime parameter.
        let stream: TileStream<'static> = unsafe { std::mem::transmute(stream) };
        Ok(TileCursorHandle {
            stream,
            _cursor: cursor,
            _token: self.token(),
        })
    }
}

/// A `gpkg_tile_cursor_t`: one scan over a pyramid's stored tiles.
///
/// Owns the whole borrow chain: the stream borrows the boxed cursor, the
/// cursor's statement borrows the container's connection, and the token is
/// what stops the container closing underneath both. Field order matters:
/// the stream is declared first so it drops before the cursor it borrows.
pub struct TileCursorHandle {
    /// The scan in progress. Erased; borrows `_cursor`.
    stream: TileStream<'static>,
    /// The prepared statement the stream walks. Boxed so its address is
    /// stable while the stream borrows it. Never touched again directly.
    _cursor: Box<TileCursor<'static>>,
    /// What stops the container being closed while this handle exists.
    _token: ChildToken,
}

impl TileCursorHandle {
    /// Returns the next stored tile, or `None` at the end of the scan.
    ///
    /// The returned [`Tile`] borrows the row's payload, so it is valid until
    /// the next call on this handle. The Rust lending cursor's borrow rules
    /// state this, and the C contract repeats it.
    ///
    /// # Errors
    ///
    /// Whatever [`TileStream::next`] returns.
    #[expect(
        clippy::should_implement_trait,
        reason = "a lending cursor cannot implement Iterator: its item borrows the iterator. The name matches TileStream::next, whose expectation records the same reasoning"
    )]
    pub fn next(&mut self) -> geopackage::Result<Option<Tile<'_>>> {
        self.stream.next()
    }
}