A strings collection, created with ustore_modality_strings_k, keys its values by variable-length strings, kept in the order of their bytes.
Its strings and their values both arrive as ustore_strings_view_t, so ustore_strings_write, _remove, _read, _scan and _cursor_* mirror the blob calls with strings in place of keys.
A scan lists up to wanted strings under every prefix as one tape, split by task through count + 1 offsets, and resumes from start_strings, while a strings cursor walks one prefix as a blobs cursor walks keys.
A pages collection stores ordered documents, edited by one verb per kind of edit, each taking only its own fields: ustore_pages_import, _insert, _remove, _move, _splice, _set_attributes and _remove_attributes. ustore_pages_read reads documents back into an arena, and ustore_pages_scan lists the keys of the pages.
A page stores only its current shape, so no call lists the edits that produced it.
Every engine serves them, importing and rendering XML and plain text, while HTML, Markdown and the JSON tree answer ustore_status_unsupported_k.
An edit watches the page it rewrites, except on a pages collection of the NVMe engine, which keeps the edits themselves, so two transactions editing one page both commit.
An edit of a missing page answers ustore_status_not_found_k, an edit of a missing element inside a page does nothing, and a Nil element removes the whole page.
- This is the C ABI itself. Every name is the one
c/ustore/ declares: a verb ustore_<noun>_<verb> takes a ustore_<noun>_<verb>_t, and closed sets are C enums. - The completion chooses, the verb stays. A
NULL executor runs a call on the calling thread, an executor with the default completion blocks on it, and ustore_completion_deferred_k submits it, all with the same argument struct. - Deferred calls report through the same outcome.
ustore_outcome_status polls and ustore_outcome_wait blocks, both with the acquire ordering the final store pairs with. - No life of its own. Threads live only in executors, each serving one call at a time, and every
*_free waits for the calls naming its handle first. - Three shapes in, three out. An input is one run of bytes, a strided run of fixed-width elements, or a ragged batch of strings in an Arrow tape or behind an accessor, and an output is a packed array, a tape of bytes, or packed keys split by offsets.
So Arrow arrays pass as they are, and a runtime’s own objects pass through an accessor, without copies on either side.
- Arenas are lent and reused, never shared. Every output whose length scales with the call lands in an arena the caller passes, and each call rewinds it, so the previous call’s views go stale the moment the next one starts.
- Nothing costs anything until it is used. A blocking call runs on its caller’s thread, and an arena allocates only when a call writes into it.
- One shape for every call. Each verb takes one struct set by designator, so a call names only what it changes and the struct grows without breaking source.
- Failures are statuses. Every failure is a status in the call’s outcome beside a static message,
ustore_status_name and ustore_status_from_name spell statuses both ways, and an engine without a call answers ustore_status_unsupported_k rather than missing from the build. - Closed sets spell themselves. Every enum a binding names has its
_name and _from_name functions, so a binding builds its own enums from them and keeps no table to drift. - Walks hold, scans paginate. A cursor holds its database and its view and resumes past its last key, while a scan holds nothing and starts wherever its caller says, so neither keeps an iterator open over concurrent state.
- Only progress stops a call. A callback returning
ustore_cancel_k is the one way to stop a running call, so every other call runs to a final status, and a cancelled one leaves its outputs NULL and its cursor where it was.