geopackage-ffi 0.12.0

C ABI for the geopackage crate: read and write OGC GeoPackage (.gpkg) files from C
Documentation
//! A C ABI for the [`geopackage`] crate.
//!
//! Rust code should use [`geopackage`] directly: it is safe and has a larger
//! API. Every function here is `unsafe extern "C"`.
//!
//! The header, `include/geopackage.h`, is generated by cbindgen and checked
//! in, so the documentation of each item below also appears beside its C
//! declaration. `cargo cinstall -p geopackage-ffi` installs the header, a
//! versioned library, and a pkg-config file named `geopackage`.
//!
//! # Opening a file
//!
//! Every function that can fail takes an optional `gpkg_error_t *`. On failure
//! it receives a status code and a message. [`gpkg_error_clear`] frees the
//! message and resets the code, so a program can reuse one error variable.
//!
//! ```c
//! #include <stdio.h>
//! #include "geopackage.h"
//!
//! int main(void) {
//!     gpkg_error_t error = {GPKG_STATUS_OK, NULL};
//!
//!     gpkg_t *gpkg = gpkg_open_read_only("places.gpkg", &error);
//!     if (!gpkg) {
//!         fprintf(stderr, "open failed (%d): %s\n", (int)error.code,
//!                 error.message ? error.message : "(no message)");
//!         gpkg_error_clear(&error);
//!         return 1;
//!     }
//!
//!     char *version = gpkg_version(gpkg, &error);
//!     if (version) {
//!         printf("GeoPackage %s\n", version);
//!         gpkg_string_free(version);
//!     }
//!
//!     if (gpkg_close(gpkg, &error) != GPKG_STATUS_OK) {
//!         fprintf(stderr, "close failed: %s\n",
//!                 error.message ? error.message : "(no message)");
//!         gpkg_error_clear(&error);
//!         return 1;
//!     }
//!     return 0;
//! }
//! ```
//!
//! # Examples
//!
//! The `examples` directory contains five complete programs. CI compiles them
//! against the committed header and runs them, so they stay in sync with the
//! ABI.
//!
//! - **`smoke.c`**: Open a file, list its layers, read one as an Arrow stream,
//!   and check that closing fails while the stream is open
//! - **`inspect.c`**: Open a file leniently and read-only, then check
//!   everything before writing: open warnings, layers and tile pyramids,
//!   extensions and their support levels, and validation findings
//! - **`query.c`**: Open a layer with only the columns it needs, resolve its
//!   CRS, and read by bounding box, by bounding box and `WHERE` clause with a
//!   bound parameter, and by feature ID
//! - **`roundtrip.c`**: Create a layer from another layer's Arrow schema, and
//!   copy the rows in one transaction
//! - **`tilepipe.c`**: Create a Web Mercator tile pyramid, fill it, and copy
//!   it tile by tile with a cursor
//!
//! # Editing a file
//!
//! A typical program opens an existing file, inspects it, appends to it, and
//! closes it. Here the new rows come from `rows_to_append`, which stands in
//! for any producer of the Arrow C Data Interface, such as another layer's
//! [`gpkg_layer_read_arrow`].
//!
//! ```c
//! #include <stdio.h>
//! #include <string.h>
//! #include "geopackage.h"
//!
//! // The rows to append, from any Arrow C Data Interface producer.
//! extern void rows_to_append(struct ArrowArrayStream *out);
//!
//! static int fail(const char *what, gpkg_error_t *error) {
//!     fprintf(stderr, "%s: code=%d message=%s\n", what, (int)error->code,
//!             error->message ? error->message : "(none)");
//!     gpkg_error_clear(error);
//!     return 1;
//! }
//!
//! int main(void) {
//!     gpkg_error_t error = {GPKG_STATUS_OK, NULL};
//!
//!     gpkg_t *gpkg = gpkg_open("places.gpkg", &error);   // read-write
//!     if (!gpkg) {
//!         return fail("gpkg_open", &error);
//!     }
//!     gpkg_layer_t *layer = gpkg_layer_open(gpkg, "points", &error);
//!     if (!layer) {
//!         return fail("gpkg_layer_open", &error);
//!     }
//!
//!     uint64_t rows = 0;
//!     if (gpkg_layer_count(layer, &rows, &error) != GPKG_STATUS_OK) {
//!         return fail("gpkg_layer_count", &error);
//!     }
//!     printf("points: %llu rows\n", (unsigned long long)rows);
//!
//!     // Another program may have written this file. Repair fixes a stale or
//!     // legacy index, and leaves a current or missing one alone.
//!     char *index_state = gpkg_layer_spatial_index_status(layer, &error);
//!     if (!index_state) {
//!         return fail("gpkg_layer_spatial_index_status", &error);
//!     }
//!     printf("spatial index: %s\n", index_state);
//!     gpkg_string_free(index_state);
//!     if (gpkg_layer_repair_spatial_index(layer, &error) != GPKG_STATUS_OK) {
//!         return fail("gpkg_layer_repair_spatial_index", &error);
//!     }
//!
//!     struct ArrowArrayStream new_rows;
//!     memset(&new_rows, 0, sizeof(new_rows));
//!     rows_to_append(&new_rows);
//!
//!     if (gpkg_begin(gpkg, &error) != GPKG_STATUS_OK) {
//!         return fail("gpkg_begin", &error);
//!     }
//!     uint64_t written = 0;
//!     if (gpkg_layer_write_arrow(layer, &new_rows, 0, &written, &error)
//!         != GPKG_STATUS_OK) {
//!         gpkg_rollback(gpkg, NULL);
//!         return fail("gpkg_layer_write_arrow", &error);
//!     }
//!     if (gpkg_commit(gpkg, &error) != GPKG_STATUS_OK) {
//!         return fail("gpkg_commit", &error);
//!     }
//!     printf("appended %llu rows\n", (unsigned long long)written);
//!
//!     gpkg_layer_free(layer);   // children before the container
//!     if (gpkg_close(gpkg, &error) != GPKG_STATUS_OK) {
//!         return fail("gpkg_close", &error);
//!     }
//!     return 0;
//! }
//! ```
//!
//! - Writes made through this library keep the spatial index up to date, so
//!   check the index once when a file arrives, not on every write.
//! - There is no save function. [`gpkg_commit`] commits the transaction, and
//!   without [`gpkg_begin`] each write is committed when its call returns.
//! - [`gpkg_layer_write_arrow`] only appends. To update or delete existing
//!   rows, use a [`gpkg_writer_t`] from [`gpkg_layer_writer`]; its changes are
//!   committed together by [`gpkg_writer_commit`].
//!
//! # Conventions
//!
//! - **Strings** are NUL-terminated UTF-8. Strings returned by this library
//!   belong to the caller, who frees them with [`gpkg_string_free`]. Strings
//!   passed in are borrowed only for the duration of the call.
//! - **Errors** are reported through an optional `gpkg_error_t *`; pass NULL
//!   to ignore them. A filled-in error owns its message, so clear it before
//!   reusing the variable, or the first message leaks.
//! - **Failure** is NULL from a function that returns a pointer, and a status
//!   other than `GPKG_STATUS_OK` from a function that returns [`Status`]. The
//!   status is a broad category to branch on; the message has the details.
//! - **Handles** are opaque, and each is freed by exactly one function. Using
//!   a handle after freeing it is undefined behaviour.
//!
//! | Handle | Created by | Freed by |
//! |---|---|---|
//! | `gpkg_t` | [`gpkg_open`], [`gpkg_open_read_only`], [`gpkg_create`] | [`gpkg_close`] |
//! | `gpkg_layer_t` | [`gpkg_layer_open`], [`gpkg_attributes_open`], and their `_with_columns` variants | [`gpkg_layer_free`] |
//! | `gpkg_tiles_t` | [`gpkg_tiles_open`], [`gpkg_tiles_create`], [`gpkg_tiles_create_web_mercator`] | [`gpkg_tiles_free`] |
//! | `gpkg_tile_cursor_t` | [`gpkg_tiles_cursor`], [`gpkg_tiles_cursor_at`], [`gpkg_tiles_cursor_in`] | [`gpkg_tile_cursor_free`] |
//! | `gpkg_findings_t` | [`gpkg_validate`] | [`gpkg_findings_free`] |
//! | `gpkg_writer_t` | [`gpkg_layer_writer`] | [`gpkg_writer_commit`] (keeps changes) or [`gpkg_writer_free`] (discards them) |
//!
//! # Handle lifetimes
//!
//! Layers, tiles handles, writers, tile cursors and Arrow streams all borrow
//! the container they came from. While any of them exists, [`gpkg_close`]
//! returns `GPKG_STATUS_HANDLE_IN_USE` and leaves the container open and
//! usable, so free them first. A tile cursor counts separately, and can
//! outlive the tiles handle it came from. `gpkg_findings_t` is the exception:
//! it borrows nothing, never blocks a close, and remains readable afterwards.
//!
//! ```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);
//!
//! struct ArrowArrayStream stream;
//! memset(&stream, 0, sizeof(stream));
//! gpkg_layer_read_arrow(layer, &stream, &error);
//!
//! // Fails while either child is alive, and nothing is torn down.
//! if (gpkg_close(gpkg, &error) == GPKG_STATUS_HANDLE_IN_USE) {
//!     gpkg_error_clear(&error);
//! }
//!
//! stream.release(&stream);
//! gpkg_layer_free(layer);
//! gpkg_close(gpkg, &error);  // now it succeeds
//! ```
//!
//! In Rust, the compiler enforces this rule: `close` takes `self`, and a live
//! `Layer` borrows it. The [`handle`] module explains how the C API checks the
//! same rule at runtime, and why that is sound.
//!
//! # Modules
//!
//! - **[`container`]**: Open and close files, read the declared version and
//!   any lenient-open warnings, look up a spatial reference system
//!   ([`gpkg_srs`]), and manage transactions ([`gpkg_begin`], [`gpkg_commit`],
//!   [`gpkg_rollback`], [`gpkg_in_transaction`])
//! - **[`layer`]**: Open feature and attribute layers by name, optionally
//!   limited to some columns; list feature layers; count rows
//! - **[`schema`]**: A layer's columns and types, geometry column, spatial
//!   reference system, extent and spatial index
//! - **[`extensions`]**: The `gpkg_extensions` table, with this library's
//!   support level for each row, so a program can fail before it writes rather
//!   than partway through
//! - **[`validate`]**: Every check the library has, as findings with a
//!   severity and repair advice
//! - **[`stream`]**: Read and write layers as Arrow record batches through the
//!   C Data Interface, optionally filtered by bounding box, `WHERE` clause, or
//!   both; create a layer from an Arrow schema
//! - **[`tiles`]**: Create, list, read and iterate over tile pyramids.
//!   Payloads are the stored bytes, never decoded
//! - **[`writer`]**: Insert, update and delete individual features, with
//!   geometry as WKB and values as [`gpkg_value_t`]
//! - **[`error`]**, **[`util`]**: Status codes, the error out-parameter, and
//!   [`gpkg_string_free`]. [`gpkg_bytes_free`] is in [`tiles`], the only module
//!   that returns owned buffers
//!
//! Features are read only through Arrow. There is no row-by-row reader,
//! because it would duplicate the Arrow path; to read a single row, filter on
//! `fid = ?1`. Writing has both forms because they do different things:
//! [`gpkg_layer_write_arrow`] appends in bulk, and a [`gpkg_writer_t`] changes
//! existing rows.
//!
//! Metadata, related tables and tiled gridded coverages aren't available
//! through the C API yet. They are ordinary tables, so a program that needs
//! them can query them with SQLite directly.
//!
//! # Threading
//!
//! A container and every handle taken from it (layers, tiles handles, writers,
//! tile cursors and Arrow streams) must be used from one thread at a time.
//! They share the container's SQLite connection, and `rusqlite::Connection` is
//! `Send` but not `Sync`. The whole set may move to another thread together,
//! but you must not use a child on one thread while its container, or another
//! child of the same container, is in use on another. This includes freeing a
//! child. A `gpkg_findings_t` is the exception: it borrows nothing from its
//! container, so it can move to another thread on its own.
//!
//! There is no internal locking. For concurrent access, open the file once per
//! thread. Reading through separate connections is safe, and is how the
//! library's multithreaded Arrow reader works.

pub mod container;
pub mod error;
pub mod extensions;
pub mod handle;
pub mod layer;
pub mod schema;
pub mod stream;
pub mod tiles;
pub mod util;
pub mod validate;
pub mod value;
pub mod writer;

pub use container::{
    gpkg_begin, gpkg_close, gpkg_commit, gpkg_create, gpkg_in_transaction, gpkg_open,
    gpkg_open_read_only, gpkg_open_warning, gpkg_open_warning_count, gpkg_rollback, gpkg_srs,
    gpkg_t, gpkg_version,
};
pub use error::{Status, gpkg_error_clear, gpkg_error_t};
pub use extensions::{gpkg_extension_at, gpkg_extensions_count};
pub use layer::{
    gpkg_attributes_open, gpkg_attributes_open_with_columns, gpkg_layer_count, gpkg_layer_free,
    gpkg_layer_name, gpkg_layer_name_at, gpkg_layer_names_count, gpkg_layer_open,
    gpkg_layer_open_with_columns, gpkg_layer_t,
};
pub use schema::{
    gpkg_layer_column_count, gpkg_layer_column_is_primary_key, gpkg_layer_column_name,
    gpkg_layer_column_type, gpkg_layer_create_spatial_index, gpkg_layer_drop_spatial_index,
    gpkg_layer_extent, gpkg_layer_geometry_column, gpkg_layer_geometry_type,
    gpkg_layer_has_spatial_index, gpkg_layer_kind, gpkg_layer_repair_spatial_index,
    gpkg_layer_spatial_index_status, gpkg_layer_srs_id,
};
pub use stream::{
    gpkg_add_epsg_srs, gpkg_create_layer_from_arrow_schema, gpkg_layer_read_arrow,
    gpkg_layer_read_arrow_filtered, gpkg_layer_read_arrow_in, gpkg_layer_write_arrow,
};
pub use tiles::{
    gpkg_bytes_free, gpkg_tile_cursor_free, gpkg_tile_cursor_next, gpkg_tile_cursor_t,
    gpkg_tiles_count, gpkg_tiles_count_at, gpkg_tiles_create, gpkg_tiles_create_web_mercator,
    gpkg_tiles_cursor, gpkg_tiles_cursor_at, gpkg_tiles_cursor_in, gpkg_tiles_delete,
    gpkg_tiles_extent, gpkg_tiles_free, gpkg_tiles_get, gpkg_tiles_get_into, gpkg_tiles_has,
    gpkg_tiles_matrix_at, gpkg_tiles_name, gpkg_tiles_name_at, gpkg_tiles_names_count,
    gpkg_tiles_open, gpkg_tiles_put, gpkg_tiles_t, gpkg_tiles_zoom_level_count,
};
pub use util::gpkg_string_free;
pub use validate::{
    gpkg_finding_at, gpkg_findings_count, gpkg_findings_free, gpkg_findings_t, gpkg_validate,
};
pub use value::{
    ValueKind, gpkg_blob_t, gpkg_date_t, gpkg_datetime_t, gpkg_value_payload, gpkg_value_t,
};
pub use writer::{
    gpkg_layer_writer, gpkg_writer_commit, gpkg_writer_delete, gpkg_writer_free,
    gpkg_writer_insert, gpkg_writer_t, gpkg_writer_update, gpkg_writer_update_column,
};