geopackage-ffi 0.13.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
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
//! Schema introspection and the spatial index, for `gpkg_layer_t`.
//!
//! These calls report what a layer declares: its columns and their types, its
//! geometry column and that column's spatial reference system, its extent, and
//! the state of its spatial index. The column calls read a schema the handle
//! already stores, so they cost nothing beyond copying a string. The extent and
//! index calls query the file.
//!
//! # Describing a layer
//!
//! ```c
//! char *kind = gpkg_layer_kind(layer, &error);   // "features" or "attributes"
//! printf("kind: %s\n", kind ? kind : "?");
//! gpkg_string_free(kind);
//!
//! size_t columns = 0;
//! if (gpkg_layer_column_count(layer, &columns, &error) != GPKG_STATUS_OK) {
//!     return fail("gpkg_layer_column_count", &error);
//! }
//! for (size_t i = 0; i < columns; i++) {
//!     char *name = gpkg_layer_column_name(layer, i, &error);
//!     char *type = gpkg_layer_column_type(layer, i, &error);
//!     printf("  %s %s%s\n", name ? name : "?", type ? type : "",
//!            gpkg_layer_column_is_primary_key(layer, i) ? " PRIMARY KEY" : "");
//!     gpkg_string_free(name);
//!     gpkg_string_free(type);
//! }
//!
//! // NULL here means the layer has no geometry, which is not a failure.
//! char *geometry = gpkg_layer_geometry_column(layer, &error);
//! if (geometry) {
//!     char *type = gpkg_layer_geometry_type(layer, &error);
//!     int32_t srs_id = 0;
//!     gpkg_layer_srs_id(layer, &srs_id, &error);
//!     printf("  geometry: %s %s, srs %d\n", geometry, type ? type : "?",
//!            (int)srs_id);
//!     gpkg_string_free(type);
//!     gpkg_string_free(geometry);
//! }
//!
//! double min_x, min_y, max_x, max_y;
//! gpkg_status extent =
//!     gpkg_layer_extent(layer, &min_x, &min_y, &max_x, &max_y, &error);
//! if (extent == GPKG_STATUS_OK) {
//!     printf("  extent: %f %f %f %f\n", min_x, min_y, max_x, max_y);
//! } else if (extent == GPKG_STATUS_NOT_FOUND) {
//!     // An empty layer, or one with no geometry column: there is nothing to
//!     // report rather than something that went wrong.
//!     gpkg_error_clear(&error);
//! } else {
//!     return fail("gpkg_layer_extent", &error);
//! }
//! ```
//!
//! # The spatial index
//!
//! A feature layer may have an RTree index over its geometry column.
//! [`gpkg_layer_has_spatial_index`] tells you whether a bounding-box read will
//! be served by an index or by a full scan. [`gpkg_layer_spatial_index_status`]
//! gives more detail: whether the index is absent, current, maintained by the
//! pre-1.4 trigger set, or out of step with its table.
//! [`gpkg_layer_create_spatial_index`], [`gpkg_layer_drop_spatial_index`] and
//! [`gpkg_layer_repair_spatial_index`] change the index. Each of them writes,
//! so each fails on a read-only handle.
//!
//! ```c
//! bool indexed = false;
//! if (gpkg_layer_has_spatial_index(layer, &indexed, &error) != GPKG_STATUS_OK) {
//!     return fail("gpkg_layer_has_spatial_index", &error);
//! }
//! if (!indexed && gpkg_layer_create_spatial_index(layer, &error) != GPKG_STATUS_OK) {
//!     return fail("gpkg_layer_create_spatial_index", &error);
//! }
//! ```
//!
//! Everything here reads or acts on a layer you already hold, so none of it
//! takes a new token. The layer handle's own token keeps the container alive.

use std::ffi::c_char;

use geopackage::SpatialIndexStatus;

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

/// Returns whether a layer contains features or attributes, as `"features"` or
/// `"attributes"`.
///
/// 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_kind(
    layer: *const LayerHandle,
    error: *mut gpkg_error_t,
) -> *mut c_char {
    // SAFETY: forwarded to this function's contract.
    let Some(handle) = (unsafe { checked(layer, error, "gpkg_layer_kind") }) else {
        return std::ptr::null_mut();
    };
    let kind = handle.layer().kind().as_str();
    // SAFETY: forwarded to the caller's guarantee on `error`.
    unsafe { out_string(kind, error) }
}

/// Writes the number of columns in the layer's table through `out`, including
/// the geometry and primary key columns.
///
/// # Safety
///
/// `layer` must be a live handle, `out` writable, `error` NULL or writable.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_layer_column_count(
    layer: *const LayerHandle,
    out: *mut usize,
    error: *mut gpkg_error_t,
) -> Status {
    if out.is_null() {
        // SAFETY: forwarded to the caller's guarantee on `error`.
        unsafe { set_error(error, Status::BadArgument, "out is NULL") };
        return Status::BadArgument;
    }
    // SAFETY: forwarded to this function's contract.
    let Some(handle) = (unsafe { checked(layer, error, "gpkg_layer_column_count") }) else {
        return Status::BadArgument;
    };
    let count = handle.layer().schema().columns.len();
    // SAFETY: the caller guarantees `out` is writable.
    unsafe { *out = count };
    Status::Ok
}

/// Returns the name of the `index`th column, or NULL when out of range.
///
/// 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_column_name(
    layer: *const LayerHandle,
    index: usize,
    error: *mut gpkg_error_t,
) -> *mut c_char {
    // SAFETY: forwarded to this function's contract.
    let Some(handle) = (unsafe { checked(layer, error, "gpkg_layer_column_name") }) else {
        return std::ptr::null_mut();
    };
    let Some(column) = handle.layer().schema().columns.get(index) else {
        // SAFETY: forwarded to the caller's guarantee on `error`.
        unsafe { set_error(error, Status::NotFound, "column index out of range") };
        return std::ptr::null_mut();
    };
    let name = column.name.clone();
    // SAFETY: forwarded to the caller's guarantee on `error`.
    unsafe { out_string(&name, error) }
}

/// Returns the declared SQL type of the `index`th column, exactly as the file
/// spells it, or the empty string for a column declared without one.
///
/// The result is the declared text, not a parsed type, because a GeoPackage
/// may declare a type outside the spec's vocabulary and the library keeps such
/// a column rather than rejecting it. To check for one of the spec's own type
/// names, compare against it.
///
/// 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_column_type(
    layer: *const LayerHandle,
    index: usize,
    error: *mut gpkg_error_t,
) -> *mut c_char {
    // SAFETY: forwarded to this function's contract.
    let Some(handle) = (unsafe { checked(layer, error, "gpkg_layer_column_type") }) else {
        return std::ptr::null_mut();
    };
    let Some(column) = handle.layer().schema().columns.get(index) else {
        // SAFETY: forwarded to the caller's guarantee on `error`.
        unsafe { set_error(error, Status::NotFound, "column index out of range") };
        return std::ptr::null_mut();
    };
    let declared = column.declared_type.clone();
    // SAFETY: forwarded to the caller's guarantee on `error`.
    unsafe { out_string(&declared, error) }
}

/// Returns whether the `index`th column is the primary key.
///
/// Returns `false` for an index out of range. Use [`gpkg_layer_column_count`]
/// to tell the two cases apart.
///
/// # Safety
///
/// `layer` must be a live handle.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_layer_column_is_primary_key(
    layer: *const LayerHandle,
    index: usize,
) -> bool {
    if layer.is_null() {
        return false;
    }
    // SAFETY: the caller guarantees a live layer handle.
    let handle = unsafe { &*layer };
    handle
        .layer()
        .schema()
        .columns
        .get(index)
        .is_some_and(geopackage::Column::is_primary_key)
}

/// Returns the geometry column's name, or NULL for an attribute layer.
///
/// A NULL return with `error` left at `GPKG_STATUS_OK` means the layer has no
/// geometry. That is not a failure.
///
/// 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_geometry_column(
    layer: *const LayerHandle,
    error: *mut gpkg_error_t,
) -> *mut c_char {
    // SAFETY: forwarded to this function's contract.
    let Some(handle) = (unsafe { checked(layer, error, "gpkg_layer_geometry_column") }) else {
        return std::ptr::null_mut();
    };
    let Some(geometry) = handle.layer().geometry_column() else {
        return std::ptr::null_mut();
    };
    let name = geometry.column_name.clone();
    // SAFETY: forwarded to the caller's guarantee on `error`.
    unsafe { out_string(&name, error) }
}

/// Returns the geometry column's declared type, such as `"POINT"`, or NULL for
/// an attribute layer.
///
/// 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_geometry_type(
    layer: *const LayerHandle,
    error: *mut gpkg_error_t,
) -> *mut c_char {
    // SAFETY: forwarded to this function's contract.
    let Some(handle) = (unsafe { checked(layer, error, "gpkg_layer_geometry_type") }) else {
        return std::ptr::null_mut();
    };
    let Some(geometry) = handle.layer().geometry_column() else {
        return std::ptr::null_mut();
    };
    let name = geometry.geometry_type.to_string();
    // SAFETY: forwarded to the caller's guarantee on `error`.
    unsafe { out_string(&name, error) }
}

/// Writes the geometry column's spatial reference system id through `out`.
///
/// The id is a `gpkg_spatial_ref_sys.srs_id`, which for a file written by this
/// library is the EPSG code. Returns `GPKG_STATUS_NOT_FOUND` for a layer with
/// no geometry column, leaving `out` untouched.
///
/// # Safety
///
/// `layer` must be a live handle, `out` writable, `error` NULL or writable.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_layer_srs_id(
    layer: *const LayerHandle,
    out: *mut i32,
    error: *mut gpkg_error_t,
) -> Status {
    if out.is_null() {
        // SAFETY: forwarded to the caller's guarantee on `error`.
        unsafe { set_error(error, Status::BadArgument, "out is NULL") };
        return Status::BadArgument;
    }
    // SAFETY: forwarded to this function's contract.
    let Some(handle) = (unsafe { checked(layer, error, "gpkg_layer_srs_id") }) else {
        return Status::BadArgument;
    };
    let Some(geometry) = handle.layer().geometry_column() else {
        // SAFETY: forwarded to the caller's guarantee on `error`.
        unsafe { set_error(error, Status::NotFound, "layer has no geometry column") };
        return Status::NotFound;
    };
    // SAFETY: the caller guarantees `out` is writable.
    unsafe { *out = geometry.srs_id };
    Status::Ok
}

/// Writes the layer's extent through the four out-parameters.
///
/// The extent is the bounds recorded in `gpkg_contents`, in the layer's own
/// spatial reference system. If those bounds are unusable, the call measures
/// them from the geometries and records them, so it can write to the file. On
/// a read-only handle it measures and returns the bounds but records nothing.
///
/// Returns `GPKG_STATUS_NOT_FOUND` when the layer has no extent to report,
/// because it is empty or has no geometry column. The out-parameters are then
/// left untouched.
///
/// # Safety
///
/// `layer` must be a live handle, the four out-parameters writable, and
/// `error` NULL or writable.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_layer_extent(
    layer: *const LayerHandle,
    min_x: *mut f64,
    min_y: *mut f64,
    max_x: *mut f64,
    max_y: *mut f64,
    error: *mut gpkg_error_t,
) -> Status {
    if min_x.is_null() || min_y.is_null() || max_x.is_null() || max_y.is_null() {
        // SAFETY: forwarded to the caller's guarantee on `error`.
        unsafe {
            set_error(
                error,
                Status::BadArgument,
                "an extent out-parameter is NULL",
            )
        };
        return Status::BadArgument;
    }
    // SAFETY: forwarded to this function's contract.
    let Some(handle) = (unsafe { checked(layer, error, "gpkg_layer_extent") }) else {
        return Status::BadArgument;
    };
    let extent = match handle.layer().extent() {
        Ok(extent) => extent,
        Err(err) => {
            // SAFETY: forwarded to the caller's guarantee on `error`.
            unsafe { set_library_error(error, &err) };
            return Status::from(&err);
        }
    };
    let Some(bbox) = extent else {
        // SAFETY: forwarded to the caller's guarantee on `error`.
        unsafe { set_error(error, Status::NotFound, "layer has no extent") };
        return Status::NotFound;
    };
    // SAFETY: the caller guarantees all four are writable.
    unsafe {
        *min_x = bbox.min_x;
    }
    // SAFETY: as above.
    unsafe {
        *min_y = bbox.min_y;
    }
    // SAFETY: as above.
    unsafe {
        *max_x = bbox.max_x;
    }
    // SAFETY: as above.
    unsafe {
        *max_y = bbox.max_y;
    }
    Status::Ok
}

/// Returns the spatial index's state, as `"absent"`, `"current"`, `"legacy trigger
/// set"` or `"stale"`.
///
/// `"legacy trigger set"` is an index kept by the pre-1.4 triggers, which
/// corrupt an index under `UPSERT`. `"stale"` is a virtual table without its
/// triggers, or triggers without their table, as an interrupted build leaves
/// them. `gpkg_layer_repair_spatial_index` fixes either.
///
/// 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_spatial_index_status(
    layer: *const LayerHandle,
    error: *mut gpkg_error_t,
) -> *mut c_char {
    // SAFETY: forwarded to this function's contract.
    let Some(handle) = (unsafe { checked(layer, error, "gpkg_layer_spatial_index_status") }) else {
        return std::ptr::null_mut();
    };
    match handle.layer().spatial_index_status() {
        Ok(status) => {
            // SAFETY: forwarded to the caller's guarantee on `error`.
            unsafe { out_string(status.as_str(), error) }
        }
        Err(err) => {
            // SAFETY: forwarded to the caller's guarantee on `error`.
            unsafe { set_library_error(error, &err) };
            std::ptr::null_mut()
        }
    }
}

/// Writes through `out` whether a bounding-box query on this layer uses the
/// spatial index.
///
/// Reports false for a `stale` or absent index. A desynchronised index is not
/// trusted, so the query falls back to a full scan, which is still correct.
/// This tells you how a bounding-box read will be served, not whether the file
/// contains an RTree table. Use `gpkg_layer_spatial_index_status` for that.
///
/// # Safety
///
/// `layer` must be a live handle, `out` writable, `error` NULL or writable.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_layer_has_spatial_index(
    layer: *const LayerHandle,
    out: *mut bool,
    error: *mut gpkg_error_t,
) -> Status {
    if out.is_null() {
        // SAFETY: forwarded to the caller's guarantee on `error`.
        unsafe { set_error(error, Status::BadArgument, "out is NULL") };
        return Status::BadArgument;
    }
    // SAFETY: forwarded to this function's contract.
    let Some(handle) = (unsafe { checked(layer, error, "gpkg_layer_has_spatial_index") }) else {
        return Status::BadArgument;
    };
    match handle.layer().has_spatial_index() {
        Ok(has) => {
            // SAFETY: the caller guarantees `out` is writable.
            unsafe { *out = has };
            Status::Ok
        }
        Err(err) => {
            // SAFETY: forwarded to the caller's guarantee on `error`.
            unsafe { set_library_error(error, &err) };
            Status::from(&err)
        }
    }
}

/// Builds the spatial index on a layer that has none.
///
/// Creates the RTree virtual table, installs the GeoPackage 1.4 trigger set,
/// populates it from the rows already there, and registers the extension, all
/// in one transaction. Fails with `GPKG_STATUS_ALREADY_EXISTS` when the
/// layer has an index, and with `GPKG_STATUS_NOT_FOUND` when it has no
/// geometry column or no single-column primary key to key the index on.
///
/// # Safety
///
/// `layer` must be a live handle; `error` NULL or writable.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_layer_create_spatial_index(
    layer: *const LayerHandle,
    error: *mut gpkg_error_t,
) -> Status {
    // SAFETY: forwarded to this function's contract.
    let Some(handle) = (unsafe { checked(layer, error, "gpkg_layer_create_spatial_index") }) else {
        return Status::BadArgument;
    };
    // SAFETY: forwarded to the caller's guarantee on `error`.
    unsafe { report(handle.layer().create_spatial_index(), error) }
}

/// Drops the spatial index and its triggers.
///
/// Removes the RTree virtual table, its triggers and its `gpkg_extensions`
/// row, in one transaction. On a feature layer with no index there is nothing
/// to remove, and the call succeeds. Bounding-box reads still work afterwards.
/// They use a full scan and return the same rows.
///
/// # Safety
///
/// `layer` must be a live handle; `error` NULL or writable.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_layer_drop_spatial_index(
    layer: *const LayerHandle,
    error: *mut gpkg_error_t,
) -> Status {
    // SAFETY: forwarded to this function's contract.
    let Some(handle) = (unsafe { checked(layer, error, "gpkg_layer_drop_spatial_index") }) else {
        return Status::BadArgument;
    };
    // SAFETY: forwarded to the caller's guarantee on `error`.
    unsafe { report(handle.layer().drop_spatial_index(), error) }
}

/// Repairs a legacy or desynchronised spatial index by rebuilding it and
/// installing the GeoPackage 1.4 trigger set.
///
/// A layer with no index is left alone, because an absent index is a choice
/// rather than a defect. Use `gpkg_layer_create_spatial_index` to build one.
///
/// # Safety
///
/// `layer` must be a live handle; `error` NULL or writable.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_layer_repair_spatial_index(
    layer: *const LayerHandle,
    error: *mut gpkg_error_t,
) -> Status {
    // SAFETY: forwarded to this function's contract.
    let Some(handle) = (unsafe { checked(layer, error, "gpkg_layer_repair_spatial_index") }) else {
        return Status::BadArgument;
    };
    let status = match handle.layer().spatial_index_status() {
        Ok(status) => status,
        Err(err) => {
            // SAFETY: forwarded to the caller's guarantee on `error`.
            unsafe { set_library_error(error, &err) };
            return Status::from(&err);
        }
    };
    match status {
        SpatialIndexStatus::Legacy | SpatialIndexStatus::Stale => {
            // SAFETY: forwarded to the caller's guarantee on `error`.
            unsafe { report(handle.layer().repair_spatial_index(), error) }
        }
        // Nothing to put right, which is success rather than failure.
        _ => Status::Ok,
    }
}

/// Borrows a layer handle, reporting a NULL one.
///
/// # Safety
///
/// `layer` must be NULL or a live layer handle; `error` NULL or writable.
unsafe fn checked<'a>(
    layer: *const LayerHandle,
    error: *mut gpkg_error_t,
    what: &str,
) -> Option<&'a LayerHandle> {
    if layer.is_null() {
        // SAFETY: forwarded to the caller's guarantee on `error`.
        unsafe {
            set_error(
                error,
                Status::BadArgument,
                &format!("{what}: layer is NULL"),
            )
        };
        return None;
    }
    // SAFETY: the caller guarantees a live layer handle.
    Some(unsafe { &*layer })
}