Skip to main content

geopackage_ffi/
lib.rs

1//! A C ABI for the [`geopackage`] crate.
2//!
3//! Rust code should use [`geopackage`] directly: it is safe and has a larger
4//! API. Every function here is `unsafe extern "C"`.
5//!
6//! The header, `include/geopackage.h`, is generated by cbindgen and checked
7//! in, so the documentation of each item below also appears beside its C
8//! declaration. `cargo cinstall -p geopackage-ffi` installs the header, a
9//! versioned library, and a pkg-config file named `geopackage`.
10//!
11//! # Opening a file
12//!
13//! Every function that can fail takes an optional `gpkg_error_t *`. On failure
14//! it receives a status code and a message. [`gpkg_error_clear`] frees the
15//! message and resets the code, so a program can reuse one error variable.
16//!
17//! ```c
18//! #include <stdio.h>
19//! #include "geopackage.h"
20//!
21//! int main(void) {
22//!     gpkg_error_t error = {GPKG_STATUS_OK, NULL};
23//!
24//!     gpkg_t *gpkg = gpkg_open_read_only("places.gpkg", &error);
25//!     if (!gpkg) {
26//!         fprintf(stderr, "open failed (%d): %s\n", (int)error.code,
27//!                 error.message ? error.message : "(no message)");
28//!         gpkg_error_clear(&error);
29//!         return 1;
30//!     }
31//!
32//!     char *version = gpkg_version(gpkg, &error);
33//!     if (version) {
34//!         printf("GeoPackage %s\n", version);
35//!         gpkg_string_free(version);
36//!     }
37//!
38//!     if (gpkg_close(gpkg, &error) != GPKG_STATUS_OK) {
39//!         fprintf(stderr, "close failed: %s\n",
40//!                 error.message ? error.message : "(no message)");
41//!         gpkg_error_clear(&error);
42//!         return 1;
43//!     }
44//!     return 0;
45//! }
46//! ```
47//!
48//! # Examples
49//!
50//! The `examples` directory contains five complete programs. CI compiles them
51//! against the committed header and runs them, so they stay in sync with the
52//! ABI.
53//!
54//! - **`smoke.c`**: Open a file, list its layers, read one as an Arrow stream,
55//!   and check that closing fails while the stream is open
56//! - **`inspect.c`**: Open a file leniently and read-only, then check
57//!   everything before writing: open warnings, layers and tile pyramids,
58//!   extensions and their support levels, and validation findings
59//! - **`query.c`**: Open a layer with only the columns it needs, resolve its
60//!   CRS, and read by bounding box, by bounding box and `WHERE` clause with a
61//!   bound parameter, and by feature ID
62//! - **`roundtrip.c`**: Create a layer from another layer's Arrow schema, and
63//!   copy the rows in one transaction
64//! - **`tilepipe.c`**: Create a Web Mercator tile pyramid, fill it, and copy
65//!   it tile by tile with a cursor
66//!
67//! # Editing a file
68//!
69//! A typical program opens an existing file, inspects it, appends to it, and
70//! closes it. Here the new rows come from `rows_to_append`, which stands in
71//! for any producer of the Arrow C Data Interface, such as another layer's
72//! [`gpkg_layer_read_arrow`].
73//!
74//! ```c
75//! #include <stdio.h>
76//! #include <string.h>
77//! #include "geopackage.h"
78//!
79//! // The rows to append, from any Arrow C Data Interface producer.
80//! extern void rows_to_append(struct ArrowArrayStream *out);
81//!
82//! static int fail(const char *what, gpkg_error_t *error) {
83//!     fprintf(stderr, "%s: code=%d message=%s\n", what, (int)error->code,
84//!             error->message ? error->message : "(none)");
85//!     gpkg_error_clear(error);
86//!     return 1;
87//! }
88//!
89//! int main(void) {
90//!     gpkg_error_t error = {GPKG_STATUS_OK, NULL};
91//!
92//!     gpkg_t *gpkg = gpkg_open("places.gpkg", &error);   // read-write
93//!     if (!gpkg) {
94//!         return fail("gpkg_open", &error);
95//!     }
96//!     gpkg_layer_t *layer = gpkg_layer_open(gpkg, "points", &error);
97//!     if (!layer) {
98//!         return fail("gpkg_layer_open", &error);
99//!     }
100//!
101//!     uint64_t rows = 0;
102//!     if (gpkg_layer_count(layer, &rows, &error) != GPKG_STATUS_OK) {
103//!         return fail("gpkg_layer_count", &error);
104//!     }
105//!     printf("points: %llu rows\n", (unsigned long long)rows);
106//!
107//!     // Another program may have written this file. Repair fixes a stale or
108//!     // legacy index, and leaves a current or missing one alone.
109//!     char *index_state = gpkg_layer_spatial_index_status(layer, &error);
110//!     if (!index_state) {
111//!         return fail("gpkg_layer_spatial_index_status", &error);
112//!     }
113//!     printf("spatial index: %s\n", index_state);
114//!     gpkg_string_free(index_state);
115//!     if (gpkg_layer_repair_spatial_index(layer, &error) != GPKG_STATUS_OK) {
116//!         return fail("gpkg_layer_repair_spatial_index", &error);
117//!     }
118//!
119//!     struct ArrowArrayStream new_rows;
120//!     memset(&new_rows, 0, sizeof(new_rows));
121//!     rows_to_append(&new_rows);
122//!
123//!     if (gpkg_begin(gpkg, &error) != GPKG_STATUS_OK) {
124//!         return fail("gpkg_begin", &error);
125//!     }
126//!     uint64_t written = 0;
127//!     if (gpkg_layer_write_arrow(layer, &new_rows, 0, &written, &error)
128//!         != GPKG_STATUS_OK) {
129//!         gpkg_rollback(gpkg, NULL);
130//!         return fail("gpkg_layer_write_arrow", &error);
131//!     }
132//!     if (gpkg_commit(gpkg, &error) != GPKG_STATUS_OK) {
133//!         return fail("gpkg_commit", &error);
134//!     }
135//!     printf("appended %llu rows\n", (unsigned long long)written);
136//!
137//!     gpkg_layer_free(layer);   // children before the container
138//!     if (gpkg_close(gpkg, &error) != GPKG_STATUS_OK) {
139//!         return fail("gpkg_close", &error);
140//!     }
141//!     return 0;
142//! }
143//! ```
144//!
145//! - Writes made through this library keep the spatial index up to date, so
146//!   check the index once when a file arrives, not on every write.
147//! - There is no save function. [`gpkg_commit`] commits the transaction, and
148//!   without [`gpkg_begin`] each write is committed when its call returns.
149//! - [`gpkg_layer_write_arrow`] only appends. To update or delete existing
150//!   rows, use a [`gpkg_writer_t`] from [`gpkg_layer_writer`]; its changes are
151//!   committed together by [`gpkg_writer_commit`].
152//!
153//! # Conventions
154//!
155//! - **Strings** are NUL-terminated UTF-8. Strings returned by this library
156//!   belong to the caller, who frees them with [`gpkg_string_free`]. Strings
157//!   passed in are borrowed only for the duration of the call.
158//! - **Errors** are reported through an optional `gpkg_error_t *`; pass NULL
159//!   to ignore them. A filled-in error owns its message, so clear it before
160//!   reusing the variable, or the first message leaks.
161//! - **Failure** is NULL from a function that returns a pointer, and a status
162//!   other than `GPKG_STATUS_OK` from a function that returns [`Status`]. The
163//!   status is a broad category to branch on; the message has the details.
164//! - **Handles** are opaque, and each is freed by exactly one function. Using
165//!   a handle after freeing it is undefined behaviour.
166//!
167//! | Handle | Created by | Freed by |
168//! |---|---|---|
169//! | `gpkg_t` | [`gpkg_open`], [`gpkg_open_read_only`], [`gpkg_create`] | [`gpkg_close`] |
170//! | `gpkg_layer_t` | [`gpkg_layer_open`], [`gpkg_attributes_open`], and their `_with_columns` variants | [`gpkg_layer_free`] |
171//! | `gpkg_tiles_t` | [`gpkg_tiles_open`], [`gpkg_tiles_create`], [`gpkg_tiles_create_web_mercator`] | [`gpkg_tiles_free`] |
172//! | `gpkg_tile_cursor_t` | [`gpkg_tiles_cursor`], [`gpkg_tiles_cursor_at`], [`gpkg_tiles_cursor_in`] | [`gpkg_tile_cursor_free`] |
173//! | `gpkg_findings_t` | [`gpkg_validate`] | [`gpkg_findings_free`] |
174//! | `gpkg_writer_t` | [`gpkg_layer_writer`] | [`gpkg_writer_commit`] (keeps changes) or [`gpkg_writer_free`] (discards them) |
175//!
176//! # Handle lifetimes
177//!
178//! Layers, tiles handles, writers, tile cursors and Arrow streams all borrow
179//! the container they came from. While any of them exists, [`gpkg_close`]
180//! returns `GPKG_STATUS_HANDLE_IN_USE` and leaves the container open and
181//! usable, so free them first. A tile cursor counts separately, and can
182//! outlive the tiles handle it came from. `gpkg_findings_t` is the exception:
183//! it borrows nothing, never blocks a close, and remains readable afterwards.
184//!
185//! ```c
186//! gpkg_error_t error = {GPKG_STATUS_OK, NULL};
187//! gpkg_t *gpkg = gpkg_open_read_only("places.gpkg", &error);
188//! gpkg_layer_t *layer = gpkg_layer_open(gpkg, "points", &error);
189//!
190//! struct ArrowArrayStream stream;
191//! memset(&stream, 0, sizeof(stream));
192//! gpkg_layer_read_arrow(layer, &stream, &error);
193//!
194//! // Fails while either child is alive, and nothing is torn down.
195//! if (gpkg_close(gpkg, &error) == GPKG_STATUS_HANDLE_IN_USE) {
196//!     gpkg_error_clear(&error);
197//! }
198//!
199//! stream.release(&stream);
200//! gpkg_layer_free(layer);
201//! gpkg_close(gpkg, &error);  // now it succeeds
202//! ```
203//!
204//! In Rust, the compiler enforces this rule: `close` takes `self`, and a live
205//! `Layer` borrows it. The [`handle`] module explains how the C API checks the
206//! same rule at runtime, and why that is sound.
207//!
208//! # Modules
209//!
210//! - **[`container`]**: Open and close files, read the declared version and
211//!   any lenient-open warnings, look up a spatial reference system
212//!   ([`gpkg_srs`]), and manage transactions ([`gpkg_begin`], [`gpkg_commit`],
213//!   [`gpkg_rollback`], [`gpkg_in_transaction`])
214//! - **[`layer`]**: Open feature and attribute layers by name, optionally
215//!   limited to some columns; list feature layers; count rows
216//! - **[`schema`]**: A layer's columns and types, geometry column, spatial
217//!   reference system, extent and spatial index
218//! - **[`extensions`]**: The `gpkg_extensions` table, with this library's
219//!   support level for each row, so a program can fail before it writes rather
220//!   than partway through
221//! - **[`validate`]**: Every check the library has, as findings with a
222//!   severity and repair advice
223//! - **[`stream`]**: Read and write layers as Arrow record batches through the
224//!   C Data Interface, optionally filtered by bounding box, `WHERE` clause, or
225//!   both; create a layer from an Arrow schema
226//! - **[`tiles`]**: Create, list, read and iterate over tile pyramids.
227//!   Payloads are the stored bytes, never decoded
228//! - **[`writer`]**: Insert, update and delete individual features, with
229//!   geometry as WKB and values as [`gpkg_value_t`]
230//! - **[`error`]**, **[`util`]**: Status codes, the error out-parameter, and
231//!   [`gpkg_string_free`]. [`gpkg_bytes_free`] is in [`tiles`], the only module
232//!   that returns owned buffers
233//!
234//! Features are read only through Arrow. There is no row-by-row reader,
235//! because it would duplicate the Arrow path; to read a single row, filter on
236//! `fid = ?1`. Writing has both forms because they do different things:
237//! [`gpkg_layer_write_arrow`] appends in bulk, and a [`gpkg_writer_t`] changes
238//! existing rows.
239//!
240//! Metadata, related tables and tiled gridded coverages aren't available
241//! through the C API yet. They are ordinary tables, so a program that needs
242//! them can query them with SQLite directly.
243//!
244//! # Threading
245//!
246//! A container and every handle taken from it (layers, tiles handles, writers,
247//! tile cursors and Arrow streams) must be used from one thread at a time.
248//! They share the container's SQLite connection, and `rusqlite::Connection` is
249//! `Send` but not `Sync`. The whole set may move to another thread together,
250//! but you must not use a child on one thread while its container, or another
251//! child of the same container, is in use on another. This includes freeing a
252//! child. A `gpkg_findings_t` is the exception: it borrows nothing from its
253//! container, so it can move to another thread on its own.
254//!
255//! There is no internal locking. For concurrent access, open the file once per
256//! thread. Reading through separate connections is safe, and is how the
257//! library's multithreaded Arrow reader works.
258
259pub mod container;
260pub mod error;
261pub mod extensions;
262pub mod handle;
263pub mod layer;
264pub mod schema;
265pub mod stream;
266pub mod tiles;
267pub mod util;
268pub mod validate;
269pub mod value;
270pub mod writer;
271
272pub use container::{
273    gpkg_begin, gpkg_close, gpkg_commit, gpkg_create, gpkg_in_transaction, gpkg_open,
274    gpkg_open_read_only, gpkg_open_warning, gpkg_open_warning_count, gpkg_rollback, gpkg_srs,
275    gpkg_t, gpkg_version,
276};
277pub use error::{Status, gpkg_error_clear, gpkg_error_t};
278pub use extensions::{gpkg_extension_at, gpkg_extensions_count};
279pub use layer::{
280    gpkg_attributes_open, gpkg_attributes_open_with_columns, gpkg_layer_count, gpkg_layer_free,
281    gpkg_layer_name, gpkg_layer_name_at, gpkg_layer_names_count, gpkg_layer_open,
282    gpkg_layer_open_with_columns, gpkg_layer_t,
283};
284pub use schema::{
285    gpkg_layer_column_count, gpkg_layer_column_is_primary_key, gpkg_layer_column_name,
286    gpkg_layer_column_type, gpkg_layer_create_spatial_index, gpkg_layer_drop_spatial_index,
287    gpkg_layer_extent, gpkg_layer_geometry_column, gpkg_layer_geometry_type,
288    gpkg_layer_has_spatial_index, gpkg_layer_kind, gpkg_layer_repair_spatial_index,
289    gpkg_layer_spatial_index_status, gpkg_layer_srs_id,
290};
291pub use stream::{
292    gpkg_add_epsg_srs, gpkg_create_layer_from_arrow_schema, gpkg_layer_read_arrow,
293    gpkg_layer_read_arrow_filtered, gpkg_layer_read_arrow_in, gpkg_layer_write_arrow,
294};
295pub use tiles::{
296    gpkg_bytes_free, gpkg_tile_cursor_free, gpkg_tile_cursor_next, gpkg_tile_cursor_t,
297    gpkg_tiles_count, gpkg_tiles_count_at, gpkg_tiles_create, gpkg_tiles_create_web_mercator,
298    gpkg_tiles_cursor, gpkg_tiles_cursor_at, gpkg_tiles_cursor_in, gpkg_tiles_delete,
299    gpkg_tiles_extent, gpkg_tiles_free, gpkg_tiles_get, gpkg_tiles_get_into, gpkg_tiles_has,
300    gpkg_tiles_matrix_at, gpkg_tiles_name, gpkg_tiles_name_at, gpkg_tiles_names_count,
301    gpkg_tiles_open, gpkg_tiles_put, gpkg_tiles_t, gpkg_tiles_zoom_level_count,
302};
303pub use util::gpkg_string_free;
304pub use validate::{
305    gpkg_finding_at, gpkg_findings_count, gpkg_findings_free, gpkg_findings_t, gpkg_validate,
306};
307pub use value::{
308    ValueKind, gpkg_blob_t, gpkg_date_t, gpkg_datetime_t, gpkg_value_payload, gpkg_value_t,
309};
310pub use writer::{
311    gpkg_layer_writer, gpkg_writer_commit, gpkg_writer_delete, gpkg_writer_free,
312    gpkg_writer_insert, gpkg_writer_t, gpkg_writer_update, gpkg_writer_update_column,
313};