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
//! `gpkg_layer_t`: a handle onto one layer of an open GeoPackage.
//!
//! Open a layer by name, either as a feature layer with [`gpkg_layer_open`] or
//! as a non-spatial attribute table with [`gpkg_attributes_open`], and release
//! it with [`gpkg_layer_free`]. The schema calls in [`crate::schema`] and the
//! Arrow reads and writes in [`crate::stream`] all take this handle.
//!
//! [`gpkg_layer_names_count`] and [`gpkg_layer_name_at`] enumerate the file's
//! feature layers, in table-name order. Attribute tables are not in that list,
//! so to open one you need to know its name already.
//!
//! ```c
//! size_t layers = 0;
//! if (gpkg_layer_names_count(gpkg, &layers, &error) != GPKG_STATUS_OK) {
//!     return fail("gpkg_layer_names_count", &error);
//! }
//!
//! for (size_t i = 0; i < layers; i++) {
//!     char *name = gpkg_layer_name_at(gpkg, i, &error);
//!     if (!name) {
//!         return fail("gpkg_layer_name_at", &error);
//!     }
//!
//!     gpkg_layer_t *layer = gpkg_layer_open(gpkg, name, &error);
//!     if (!layer) {
//!         gpkg_string_free(name);
//!         return fail("gpkg_layer_open", &error);
//!     }
//!
//!     uint64_t rows = 0;
//!     if (gpkg_layer_count(layer, &rows, &error) != GPKG_STATUS_OK) {
//!         gpkg_layer_free(layer);
//!         gpkg_string_free(name);
//!         return fail("gpkg_layer_count", &error);
//!     }
//!     printf("%s: %llu rows\n", name, (unsigned long long)rows);
//!
//!     gpkg_layer_free(layer);
//!     gpkg_string_free(name);
//! }
//! ```
//!
//! A layer handle borrows its container. [`crate::handle`] erases that borrow
//! to `'static`. The erasure is sound because of the rule enforced here and in
//! `gpkg_close`: a container cannot close while any layer handle it produced is
//! still alive. Free the layer first.

use std::ffi::c_char;

use crate::error::{Status, gpkg_error_t, set_error, set_library_error};
use crate::handle::{Container, LayerHandle};
use crate::util::{borrow_str, out_string};

/// An opaque handle to a layer of an open GeoPackage. Create one with
/// `gpkg_layer_open`, `gpkg_attributes_open` or their `_with_columns`
/// variants, and destroy it with `gpkg_layer_free`.
#[expect(
    non_camel_case_types,
    reason = "the C name is the type's name; cbindgen emits it verbatim"
)]
pub type gpkg_layer_t = LayerHandle;

/// Opens a feature layer by name.
///
/// The name is a `gpkg_contents.table_name`, matched without regard to case,
/// as SQLite resolves table names. A name no row declares fails with
/// `GPKG_STATUS_NOT_FOUND`. A name that declares something other than a
/// feature layer, such as an attribute table or a tile pyramid, fails with
/// `GPKG_STATUS_INVALID_ARGUMENT`, and the message names what was found.
///
/// Opening reads the table's schema, which is the expensive part of working
/// with a layer. Keep the handle for as long as you need it rather than
/// reopening the layer for each call.
///
/// The returned handle borrows `gpkg`, which cannot be closed until you
/// release the handle with `gpkg_layer_free`.
///
/// Returns NULL on failure.
///
/// # Safety
///
/// `gpkg` must be a live container handle, `name` a NUL-terminated UTF-8
/// string, and `error` NULL or a writable `gpkg_error_t`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_layer_open(
    gpkg: *const Container,
    name: *const c_char,
    error: *mut gpkg_error_t,
) -> *mut gpkg_layer_t {
    // SAFETY: forwarded to this function's contract.
    unsafe { open_layer(gpkg, name, error, false) }
}

/// Opens an attribute (non-spatial) layer by name.
///
/// Works like `gpkg_layer_open`, for a table that `gpkg_contents` declares as
/// `attributes` rather than `features`. Such a table has no geometry column,
/// so `gpkg_layer_geometry_column` returns NULL for it and the bounding-box
/// read does not apply. Everything else works the same way, including the
/// Arrow read and write.
///
/// # Safety
///
/// As [`gpkg_layer_open`].
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_attributes_open(
    gpkg: *const Container,
    name: *const c_char,
    error: *mut gpkg_error_t,
) -> *mut gpkg_layer_t {
    // SAFETY: forwarded to this function's contract.
    unsafe { open_layer(gpkg, name, error, true) }
}

/// The body of both, differing only in which library method opens the layer.
///
/// # Safety
///
/// As [`gpkg_layer_open`].
unsafe fn open_layer(
    gpkg: *const Container,
    name: *const c_char,
    error: *mut gpkg_error_t,
    attributes: bool,
) -> *mut gpkg_layer_t {
    if gpkg.is_null() {
        // SAFETY: forwarded to the caller's guarantee on `error`.
        unsafe { set_error(error, Status::BadArgument, "gpkg handle is NULL") };
        return std::ptr::null_mut();
    }
    // SAFETY: forwarded to the caller's guarantee on `name`.
    let Some(name) = (unsafe { borrow_str(name, error, "layer name") }) else {
        return std::ptr::null_mut();
    };

    // SAFETY: the caller guarantees a live container handle.
    let container = unsafe { &*gpkg };
    let opened = if attributes {
        container.attributes(name)
    } else {
        container.layer(name)
    };
    match opened {
        Ok(handle) => Box::into_raw(Box::new(handle)),
        Err(err) => {
            // SAFETY: forwarded to the caller's guarantee on `error`.
            unsafe { set_library_error(error, &err) };
            std::ptr::null_mut()
        }
    }
}

/// Opens a feature layer that reads only the named columns.
///
/// Works like `gpkg_layer_open`, but a read of the returned handle selects
/// only the feature id, the named value columns, and the geometry if its
/// column is named. On a layer with large geometries, reading one attribute
/// through a projected handle touches none of the geometry blobs, which make
/// up most of the read on such layers. The Arrow schema narrows to match, so
/// a stream from this handle yields exactly these fields.
///
/// Columns come back in the table's order, whatever order you name them in,
/// and a column named twice is selected once. You don't need to name the
/// feature id. A name that is neither a value column nor the geometry column
/// fails with `GPKG_STATUS_NOT_FOUND`, and the message names the column. A
/// typo therefore fails at the open rather than quietly selecting nothing.
///
/// A bounding-box read still works when the geometry is not named. Each
/// candidate is still tested exactly against its true envelope, but the
/// geometry is left out of every batch. The projection does not affect
/// writing. A writer writes the layer's whole column list, so you cannot
/// insert a partial row by accident.
///
/// ```c
/// const char *columns[] = {"name", "population"};
/// gpkg_layer_t *narrow =
///     gpkg_layer_open_with_columns(gpkg, "cities", columns, 2, &error);
/// ```
///
/// # Safety
///
/// As [`gpkg_layer_open`], and additionally: `columns` must be NULL (only
/// when `columns_len` is 0) or point at `columns_len` readable pointers, each
/// a NUL-terminated UTF-8 string.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_layer_open_with_columns(
    gpkg: *const Container,
    name: *const c_char,
    columns: *const *const c_char,
    columns_len: usize,
    error: *mut gpkg_error_t,
) -> *mut gpkg_layer_t {
    // SAFETY: forwarded to this function's contract.
    unsafe { open_layer_with_columns(gpkg, name, columns, columns_len, error, false) }
}

/// Opens an attribute (non-spatial) layer that reads only the named columns.
///
/// Combines `gpkg_attributes_open` with the column projection of
/// `gpkg_layer_open_with_columns`, on the same terms. An attribute layer has
/// no geometry column to name.
///
/// # Safety
///
/// As [`gpkg_layer_open_with_columns`].
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_attributes_open_with_columns(
    gpkg: *const Container,
    name: *const c_char,
    columns: *const *const c_char,
    columns_len: usize,
    error: *mut gpkg_error_t,
) -> *mut gpkg_layer_t {
    // SAFETY: forwarded to this function's contract.
    unsafe { open_layer_with_columns(gpkg, name, columns, columns_len, error, true) }
}

/// The body of both projected opens.
///
/// # Safety
///
/// As [`gpkg_layer_open_with_columns`].
unsafe fn open_layer_with_columns(
    gpkg: *const Container,
    name: *const c_char,
    columns: *const *const c_char,
    columns_len: usize,
    error: *mut gpkg_error_t,
    attributes: bool,
) -> *mut gpkg_layer_t {
    if gpkg.is_null() || (columns.is_null() && columns_len != 0) {
        // SAFETY: forwarded to the caller's guarantee on `error`.
        unsafe { set_error(error, Status::BadArgument, "gpkg handle or columns is NULL") };
        return std::ptr::null_mut();
    }
    // SAFETY: forwarded to the caller's guarantee on `name`.
    let Some(name) = (unsafe { borrow_str(name, error, "layer name") }) else {
        return std::ptr::null_mut();
    };
    let mut names = Vec::with_capacity(columns_len);
    for index in 0..columns_len {
        // SAFETY: the caller guarantees `columns_len` readable pointers, so
        // every offset below `columns_len` is in bounds.
        let slot = unsafe { columns.add(index) };
        // SAFETY: `slot` is in bounds per the line above, and readable.
        let column = unsafe { *slot };
        // SAFETY: forwarded to the caller's guarantee on each column string.
        let Some(text) = (unsafe { borrow_str(column, error, "column name") }) else {
            return std::ptr::null_mut();
        };
        names.push(text);
    }

    // SAFETY: the caller guarantees a live container handle.
    let container = unsafe { &*gpkg };
    match container.layer_with_columns(name, &names, attributes) {
        Ok(handle) => Box::into_raw(Box::new(handle)),
        Err(err) => {
            // SAFETY: forwarded to the caller's guarantee on `error`.
            unsafe { set_library_error(error, &err) };
            std::ptr::null_mut()
        }
    }
}

/// Frees a layer handle.
///
/// The container it came from can be closed once every handle taken from it
/// has been freed. When `gpkg_close` fails with `GPKG_STATUS_HANDLE_IN_USE`,
/// free the layer handles with this. Passing NULL does nothing, so a cleanup
/// path can free a handle it may never have opened.
///
/// An Arrow stream taken from this layer counts as a separate child of the
/// container. Freeing the layer alone does not allow a close: you must release
/// the stream too.
///
/// # Safety
///
/// `layer` must be NULL, or a handle from `gpkg_layer_open`,
/// `gpkg_attributes_open`, `gpkg_layer_open_with_columns` or
/// `gpkg_attributes_open_with_columns` that has not already been freed. The
/// container it came from must still be alive, which is guaranteed by
/// `gpkg_close` failing while this handle exists.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_layer_free(layer: *mut gpkg_layer_t) {
    if layer.is_null() {
        return;
    }
    // SAFETY: the caller guarantees the pointer came from `Box::into_raw` in
    // `open_layer` or `open_layer_with_columns` and has not been freed.
    // Dropping it decrements the container's child count, which is what later
    // permits a close.
    drop(unsafe { Box::from_raw(layer) });
}

/// Returns the layer's table name.
///
/// Owned by the caller; release with `gpkg_string_free`.
///
/// # Safety
///
/// `layer` must be a live handle; `error` NULL or writable.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_layer_name(
    layer: *const gpkg_layer_t,
    error: *mut gpkg_error_t,
) -> *mut c_char {
    if layer.is_null() {
        // SAFETY: forwarded to the caller's guarantee on `error`.
        unsafe { set_error(error, Status::BadArgument, "layer handle is NULL") };
        return std::ptr::null_mut();
    }
    // SAFETY: the caller guarantees a live layer handle.
    let name = unsafe { (*layer).layer().table_name().to_owned() };
    // SAFETY: forwarded to the caller's guarantee on `error`.
    unsafe { out_string(&name, error) }
}

/// Writes the number of rows in the layer through `out`.
///
/// This runs `SELECT count(*)` over the table rather than reading a stored
/// figure, so it costs as much as that query does.
///
/// # Safety
///
/// `layer` must be a live handle, `out` must point at a writable `uint64_t`,
/// and `error` must be NULL or writable.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_layer_count(
    layer: *const gpkg_layer_t,
    out: *mut u64,
    error: *mut gpkg_error_t,
) -> Status {
    if layer.is_null() || out.is_null() {
        // SAFETY: forwarded to the caller's guarantee on `error`.
        unsafe { set_error(error, Status::BadArgument, "layer handle or out is NULL") };
        return Status::BadArgument;
    }
    // SAFETY: the caller guarantees a live layer handle.
    match unsafe { (*layer).layer().count() } {
        Ok(count) => {
            // SAFETY: the caller guarantees `out` is writable.
            unsafe { *out = count };
            Status::Ok
        }
        Err(err) => {
            // SAFETY: forwarded to the caller's guarantee on `error`.
            unsafe { set_library_error(error, &err) };
            Status::from(&err)
        }
    }
}

/// Writes the number of feature layers the file declares through `out`.
///
/// Only feature layers are counted. A table that `gpkg_contents` declares as
/// `attributes` or `tiles` is not counted, and neither is a `features` row
/// with no matching `gpkg_geometry_columns` entry. `gpkg_layer_name_at` walks
/// the same list, which is ordered by table name.
///
/// # Safety
///
/// `gpkg` must be a live container handle; `error` NULL or writable.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_layer_names_count(
    gpkg: *const Container,
    out: *mut usize,
    error: *mut gpkg_error_t,
) -> Status {
    if gpkg.is_null() || out.is_null() {
        // SAFETY: forwarded to the caller's guarantee on `error`.
        unsafe { set_error(error, Status::BadArgument, "gpkg handle or out is NULL") };
        return Status::BadArgument;
    }
    // SAFETY: the caller guarantees a live container handle.
    match unsafe { (*gpkg).gpkg().layers() } {
        Ok(layers) => {
            // SAFETY: the caller guarantees `out` is writable.
            unsafe { *out = layers.len() };
            Status::Ok
        }
        Err(err) => {
            // SAFETY: forwarded to the caller's guarantee on `error`.
            unsafe { set_library_error(error, &err) };
            Status::from(&err)
        }
    }
}

/// Returns the name of the `index`th feature layer, or NULL when out of range.
///
/// The list is the one `gpkg_layer_names_count` counts, ordered by table name,
/// so together the two functions walk a file's layers. An index at or beyond
/// the count fails with `GPKG_STATUS_NOT_FOUND`, not with a read error.
///
/// Owned by the caller; release with `gpkg_string_free`.
///
/// # Safety
///
/// `gpkg` must be a live container handle; `error` NULL or writable.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_layer_name_at(
    gpkg: *const Container,
    index: usize,
    error: *mut gpkg_error_t,
) -> *mut c_char {
    if gpkg.is_null() {
        // SAFETY: forwarded to the caller's guarantee on `error`.
        unsafe { set_error(error, Status::BadArgument, "gpkg handle is NULL") };
        return std::ptr::null_mut();
    }
    // SAFETY: the caller guarantees a live container handle.
    let layers = match unsafe { (*gpkg).gpkg().layers() } {
        Ok(layers) => layers,
        Err(err) => {
            // SAFETY: forwarded to the caller's guarantee on `error`.
            unsafe { set_library_error(error, &err) };
            return std::ptr::null_mut();
        }
    };
    let Some(layer) = layers.get(index) else {
        // SAFETY: forwarded to the caller's guarantee on `error`.
        unsafe { set_error(error, Status::NotFound, "layer index out of range") };
        return std::ptr::null_mut();
    };
    let name = layer.table_name().to_owned();
    // SAFETY: forwarded to the caller's guarantee on `error`.
    unsafe { out_string(&name, error) }
}