Docs
View as markdown

C ABI

Every function in relate.h, grouped, with the handle types, value kinds, params builder, error shape, ownership and threading rules, and limits.

c

The C application binary interface (ABI) is the header relate.h and the library librelate_ffi: opaque handles for a database, a connection, a result, a value and a parameter builder, and 81 functions over them. The ABI version is 2, and relate_abi_version() returns it. The Node.js, Java, Go and Swift client A language package that runs the relate engine inside a program's process: the Python, Node.js, Rust, Java, Go and Swift clients. Read more in the glossary wrap this library, and the library wraps the Rust crate's Database. The header is the contract; this page groups its declarations as of relate 0.1.2. The guide Use the C ABI covers the same calls with runnable programs.

Synopsis

#include "relate.h"

relate_database_open(uri, &db, &err)              -> relate_status
relate_connect_namespace(db, NULL, &conn, &err)   -> relate_status
relate_query_params(conn, cypher, params, &r, &err) -> relate_status
  relate_result_is_error(r) ? relate_result_error_json(r)
                            : relate_value_at(r, row, col) -> relate_value
relate_destroy_result(r)
relate_disconnect(conn)
relate_database_close(db)

Each release attaches a relate-ffi-<target>.tar.gz archive per platform (aarch64-apple-darwin, x86_64-apple-darwin, aarch64-unknown-linux-gnu, x86_64-unknown-linux-gnu, x86_64-pc-windows-msvc) holding include/relate.h and the static and shared libraries. A program links with -lrelate_ffi. After the first public release the ABI changes only by addition; a version bump means symbols were removed or signatures changed.

Types

TypeKindMeaning
relate_databaseOpaque handleAn open store; may be shared across threads
relate_connectionOpaque handleOne session on a database, with a session namespace; never used from two threads at once
relate_query_resultOpaque handleOne statement's result or error
relate_valueOpaque handleOne value inside a result, owned by that result
relate_paramsOpaque handleA params builder: a root or a nested list or map
relate_statusEnumRELATE_OK = 0, RELATE_ERROR = 1
enum relate_value_kindEnumThe 18 value kinds in Value kinds

Error shape

Every failure carries one JSON object, delivered with the call that failed. There is no thread-local error slot, so a call on a worker thread reports its own error.

{"code":"Neo.ClientError.Statement.ArithmeticError","gql_status":"22G03","stage":"runtime","retryable":false,"message":"type error: / by zero"}
KeyTypeMeaning
codestringThe Neo4j status code
gql_statusstringThe five-character GQLSTATUS code
stagestringcompile (rejected before any work), runtime (found while executing) or internal (not the statement's fault: a storage, I/O or engine failure, a NULL or poisoned handle, a refused argument)
retryablebooleantrue only when running the same work again can succeed, such as after a write-write conflict
messagestringThe engine's message for a compile or runtime failure; the boundary's own text for a refused argument or handle; the fixed internal error for storage, I/O, lock and engine-internal failures and caught panics
FailureStatusWhere the shape isOwner
A statement failsrelate_query_params returns RELATE_OKrelate_result_error_json(r), with relate_result_is_error(r) = 1The result
relate_query_params itself fails (a NULL or poisoned connection, invalid UTF-8, a list builder as params)RELATE_ERROR, *out = NULLThe char **error_json out-paramCaller: relate_free_string
Any other fallible call: open, connect, set namespace, begin, commit, rollback, checkpoint, every putterRELATE_ERRORThe char **error_json out-paramCaller: relate_free_string
A value accessor refusesNULL, -1 or RELATE_ERRORNone: the accessor's result is the whole report—
relate_database_close(NULL), relate_disconnect(NULL)RELATE_ERRORNone—

An error_json out-param is written only on failure; NULL in its place drops the detail, and the status alone reports the failure. The classification comes from the engine's one table, the same one the CLI, the clients, Bolt, and HTTP use. With RELATE_FFI_LOG=1 set in the environment (read once per process), the original message of an internal failure goes to the host's stderr, prefixed ffi internal error: .

Lifecycle

FunctionReturnsContract
int relate_abi_version(void)2The ABI version
relate_database_open(const char *uri, relate_database *out, char **error_json)statusOpens ":memory:", a .rl path (a single-file store; the legacy .cl too), any other path or file:// URI (a directory store); creates a missing store. On failure *out is NULL.
relate_database_open_read_only(const char *uri, relate_database *out, char **error_json)statusOpens an existing store without writing, checkpointing or creating it; writes and checkpoints then fail with WriteOnReadOnlyAccessDatabase
relate_database_close(relate_database db)statusFrees the handle, poisoned or not; NULL gives RELATE_ERROR
relate_connect_namespace(relate_database db, const char *name, relate_connection *out, char **error_json)statusOpens a connection whose session namespace is name: neo4j, system or system_ontologies, matched case-insensitively; NULL selects neo4j. Another name fails with a message ending Graph not found: <name>.
relate_conn_set_namespace(relate_connection conn, const char *name, char **error_json)statusChanges the session namespace between transactions; inside one, fails with a message ending Cannot change the namespace while a transaction is open.; a NULL name is refused
relate_disconnect(relate_connection conn)statusFrees the connection, poisoned or not; NULL gives RELATE_ERROR
relate_checkpoint(relate_database db, char **error_json)statusWrites a snapshot and recycles closed WAL segments; not the commit durability boundary
void relate_free_string(char *s)—Frees a caller-owned string; NULL is a no-op

Statements and transactions

FunctionReturnsContract
relate_query_params(relate_connection conn, const char *cypher, const relate_params params, relate_query_result *out, char **error_json)statusRuns one statement. params is a builder root, a put_map child, or NULL for none; it is read, never changed, and can back later statements. A list builder is refused. A statement failure returns RELATE_OK with an error result.
void relate_destroy_result(relate_query_result result)—Frees the result and every value handle and string it owns; NULL is a no-op
relate_conn_begin(relate_connection conn, char **error_json)statusStarts an explicit transaction; one per connection, and a second fails with semantic error: already in a transaction
relate_conn_commit(relate_connection conn, char **bookmark_out, char **error_json)statusCommits. RELATE_OK: *bookmark_out is the bookmark, caller-owned, or NULL when the transaction wrote nothing. RELATE_ERROR: the commit failed and *bookmark_out is NULL. A NULL bookmark_out is refused with null out pointer and the transaction is not committed.
relate_conn_rollback(relate_connection conn, char **error_json)statusDiscards the transaction; with none open, fails with semantic error: no transaction

A bookmark The marker a commit returns, relate:<n>, naming its place in the store's commit sequence; returned by the clients and over Bolt. Read more in the glossary is relate:<n>, n being the store's commit sequence across every connection and surface. Bookmarks order by the parsed n, not as strings: as strings, relate:10 sorts before relate:9.

Results

Every string a result or value accessor returns belongs to the result and lives until relate_destroy_result. A NULL result gives 0, 0.0 or NULL; an error result gives NULL from the string accessors, 0.0 from the timings and 0 rows.

FunctionReturnsContract
relate_column_count(result)long longThe number of columns
relate_row_count(result)long longThe number of rows
relate_column_name(result, long long col)const char *Column col's name; NULL out of range
relate_result_is_error(result)uint8_t1 for an error result, else 0
relate_result_error_json(result)const char *The error shape of an error result; NULL on a good result
relate_result_bookmark(result)const char *An auto-commit write's bookmark, relate:<n>; NULL for a read, an EXPLAIN, a write whose counters are all zero (it changed nothing), and any statement inside an explicit transaction, and an error result
relate_result_query_type(result)const char *r, w (a write returning no columns), rw (a write returning columns) or s (schema), from the plan; EXPLAIN and PROFILE report the statement they wrap
relate_result_namespace(result)const char *The namespace the statement ran in: its USE target, else the session namespace
relate_result_stats(result)const char *A JSON object of 11 counters, zeros included: nodes_created, nodes_deleted, relationships_created, relationships_deleted, properties_set, labels_added, labels_removed, indexes_added, indexes_removed, constraints_added, constraints_removed
relate_result_notifications(result)const char *A JSON array of notifications, each with code, gql_status, severity, category, title, description and remedy, plus operator_id for an operator's own; [] on a plain run, filled by EXPLAIN and PROFILE
relate_result_plan_json(result)const char *The plan as a relate.plan/v1 document after EXPLAIN or PROFILE; NULL on a plain run
relate_result_elapsed_ms(result)doubleThe engine's wall-clock time for the statement: parse, plan and run, excluding row handling
relate_result_parse_ms(result), relate_result_plan_ms(result), relate_result_execute_ms(result)doubleThree disjoint parts of the elapsed time; their sum never exceeds it

The plan document is {schema, profiled, statement, root}. Each node holds operator, details, identifiers, estimated_rows (or null), actual (null under EXPLAIN; rows, time, database hits and memory under PROFILE), children and warnings (the remedy-bearing notifications raised for that operator). Keys are snake_case, and a breaking change bumps the schema version.

Value kinds

EnumeratorValueRead with
RELATE_V_NULL0Nothing: a Cypher null is a handle of this kind, never a NULL handle
RELATE_V_BOOL1relate_value_bool
RELATE_V_INT2relate_value_int
RELATE_V_FLOAT3relate_value_float, relate_value_is_nan, relate_value_is_inf
RELATE_V_STRING4relate_value_string; a UUID reads as a string
RELATE_V_NODE5relate_value_element_id, relate_value_id; labels through the list accessors, properties through the map accessors
RELATE_V_RELATIONSHIP6relate_value_element_id, _start_element_id, _end_element_id, relate_value_id, _start_id, _end_id; type through relate_value_string, properties through the map accessors
RELATE_V_PATH7The list accessors: 2n + 1 elements for n hops, node, relationship, …, node
RELATE_V_LIST8relate_value_list_len, relate_value_list_get; a byte array reads as a list of INT
RELATE_V_MAP9relate_value_map_len, relate_value_map_key, relate_value_map_get; always a map, even when shaped like a node
RELATE_V_POINT10relate_value_point
RELATE_V_DATE11relate_value_year, _month, _day
RELATE_V_TIME12relate_value_hour, _minute, _second, _nanosecond, _offset_seconds; relate_value_zone is ""
RELATE_V_LOCAL_TIME13relate_value_hour, _minute, _second, _nanosecond
RELATE_V_DATETIME14The date and time components, _offset_seconds, and relate_value_zone (a named time zone such as Europe/Berlin, or "" for an offset only)
RELATE_V_LOCAL_DATETIME15The date and time components
RELATE_V_DURATION16relate_value_duration
RELATE_V_VECTOR17relate_value_vector_dtype, _vector_dim, _vector_float, _vector_int

A kind that a later, additive ABI introduces is a number a program does not know; the header asks clients to treat it as a reportable case.

Value accessors

An accessor called on another kind, on a NULL handle, or with a NULL out-param refuses without side effects: a pointer return gives NULL, a count return gives -1, and a status return gives RELATE_ERROR with the out-params untouched. A refusal never poisons anything. Every handle and string belongs to the result.

FunctionServesContract
relate_value_at(result, long long row, long long col)—The cell's root handle; NULL for an index out of range, an error result or a NULL result
relate_value_kind(v)anyThe enum relate_value_kind enumerator as an int; -1 for a NULL handle
relate_value_bool(v, uint8_t *out)BOOL0 or 1
relate_value_int(v, int64_t *out)INTThe 64-bit integer
relate_value_float(v, double *out)FLOATThe double, NaN and infinities included
relate_value_is_nan(v, uint8_t *out), relate_value_is_inf(v, uint8_t *out)FLOAT1 when NaN, or infinite of either sign
relate_value_string(v, size_t *len)STRING, RELATIONSHIPNUL-terminated UTF-8 (a relationship's type); *len, when not NULL, is the byte length, so a string with an interior NUL crosses whole
relate_value_element_id(v)NODE, RELATIONSHIPThe element id as elementId() spells it, such as 4:relate-neo4j:n0; NULL in the system namespace
relate_value_start_element_id(v), relate_value_end_element_id(v)RELATIONSHIPThe end nodes' element ids
relate_value_id(v, int64_t *out)NODE, RELATIONSHIPThe numeric id id() returns
relate_value_start_id(v, int64_t *out), relate_value_end_id(v, int64_t *out)RELATIONSHIPThe end nodes' numeric ids
relate_value_list_len(v)LIST, PATH, NODEThe items, the path's elements, or the node's labels
relate_value_list_get(v, long long i)LIST, PATH, NODEItem i; NULL past the end
relate_value_map_len(v)MAP, NODE, RELATIONSHIPThe entries, or the entity's properties, in the engine's order
relate_value_map_key(v, long long i, size_t *len)MAP, NODE, RELATIONSHIPKey i; NULL past the end
relate_value_map_get(v, const char *key)MAP, NODE, RELATIONSHIPThe value under key; NULL when absent, a RELATE_V_NULL handle when present and null
relate_value_year(v, int64_t *out), relate_value_month(…), relate_value_day(…)DATE, DATETIME, LOCAL_DATETIMEMonth 1 to 12, day 1 to 31
relate_value_hour(v, int64_t *out), relate_value_minute(…), relate_value_second(…), relate_value_nanosecond(…)TIME, LOCAL_TIME, DATETIME, LOCAL_DATETIMEThe local wall clock; nanosecond 0 to 999999999
relate_value_offset_seconds(v, int64_t *out)TIME, DATETIMEThe UTC offset in seconds
relate_value_zone(v)TIME, DATETIMEThe time zone name, or "" when the value carries only an offset (always for TIME)
relate_value_duration(v, int64_t *months, int64_t *days, int64_t *seconds, int64_t *nanos)DURATIONThe four components, losslessly; every out-param required
relate_value_point(v, int32_t *srid, int32_t *dim, double *x, double *y, double *z)POINTdim is 2 or 3; x and y are longitude and latitude for a geographic spatial reference identifier (srid); *z written only when dim is 3; every out-param required
relate_value_vector_dtype(v)VECTORINTEGER8, INTEGER16, INTEGER32, INTEGER, FLOAT16, BFLOAT16, FLOAT32 or FLOAT
relate_value_vector_dim(v)VECTORThe dimension
relate_value_vector_float(v, long long i, double *out)VECTORCoordinate i of a FLOAT* vector; another dtype or an index out of range refuses
relate_value_vector_int(v, long long i, int64_t *out)VECTORCoordinate i of an INTEGER* vector; another dtype or an index out of range refuses

Params builder

relate_params_new() returns a root: the statement's map of named parameters. _put_list and _put_map insert a nested container and write its child builder, which takes the same putters. A map takes a unique, non-NULL UTF-8 name; a list takes a NULL name and appends. Every putter copies its input and returns a status; a refusal returns RELATE_ERROR with an internal-stage shape and leaves the builder unchanged and usable.

FunctionBinds
relate_params relate_params_new(void)A new root; never NULL
void relate_params_free(relate_params p)Frees a root and every child; NULL or a child is a no-op
relate_params_put_null(p, name, &err)null
relate_params_put_bool(p, name, uint8_t v, &err)A boolean; 0 is false, any other value true
relate_params_put_int(p, name, int64_t v, &err)An integer, the whole int64_t range
relate_params_put_float(p, name, double v, &err)A float, NaN and infinities included
relate_params_put_string(p, name, const char *v, size_t len, &err)len bytes of UTF-8; invalid UTF-8 refuses. A NULL v with len 0 binds the empty string; a NULL v with a nonzero len refuses with null string value
relate_params_put_date(p, name, year, month, day, &err)A date that exists in the calendar
relate_params_put_local_time(p, name, hour, minute, second, nanosecond, &err)A local time
relate_params_put_time(p, name, hour, minute, second, nanosecond, offset_seconds, zone, &err)A zoned time; a non-NULL zone supplies the offset at the current instant, and the time keeps no zone id
relate_params_put_local_datetime(p, name, year, month, day, hour, minute, second, nanosecond, &err)A local datetime
relate_params_put_datetime(p, name, year, month, day, hour, minute, second, nanosecond, offset_seconds, zone, &err)A zoned datetime. NULL zone: the offset alone. A time zone name such as Europe/Berlin: kept with the value, its offset resolved at that wall clock and offset_seconds ignored; a daylight-saving overlap takes the earlier offset, and a wall clock in a gap refuses.
relate_params_put_duration(p, name, months, days, seconds, nanos, &err)A duration from any int64_t components
relate_params_put_point(p, name, int32_t srid, double x, double y, const double *z, &err)A point: srid 7203 (Cartesian) or 4326 (WGS-84) with z NULL, or 9157 (3D Cartesian) or 4979 (3D WGS-84) with z pointing at the third coordinate; any other pairing refuses
relate_params_put_vector_float(p, name, const char *dtype, const double *values, size_t dim, &err)A FLOAT16, BFLOAT16, FLOAT32 or FLOAT vector; each value exactly representable in the dtype
relate_params_put_vector_int(p, name, const char *dtype, const int64_t *values, size_t dim, &err)An INTEGER8, INTEGER16, INTEGER32 or INTEGER vector; each value within the dtype's width
relate_params_put_list(p, name, relate_params *child, &err)An empty list, with its child builder in *child
relate_params_put_map(p, name, relate_params *child, &err)An empty map, with its child builder in *child
LimitValue
Entries per list or map100000
Bytes per parameter name4096
Bytes per builder, across every nested container16 MiB
ComponentsMonth 1 to 12, day 1 to 31, hour 0 to 23, minute 0 to 59, second 0 to 59, nanosecond 0 to 999999999, offset_seconds within ±64800 (±18:00)

Each limit refuses one past its value. A len or dim argument is trusted: a buffer shorter than it is undefined behavior. A statement that references a parameter the builder does not hold fails as an error result with semantic error: ParameterMissing: Expected parameter(s): <names>.

Ownership

The return type is the rule: a char * belongs to the caller, a const char * to relate.

Handle or pointerOwnerFreed by
relate_databaseCallerrelate_database_close
relate_connectionCallerrelate_disconnect
relate_query_resultCallerrelate_destroy_result
Every relate_value handle, roots and childrenThe resultrelate_destroy_result; no per-value free exists
Every const char * from a result or value accessorThe resultrelate_destroy_result
char * from an error_json out-param, or from relate_conn_commit's bookmark_outCallerrelate_free_string
A relate_params root and its childrenCallerrelate_params_free on the root
Strings and arrays passed to a putterCallerCopied at the call; released whenever the caller likes

Threading and panic safety

RuleContract
DatabasesA relate_database may be shared across threads
ConnectionsA relate_connection must not be: calls on one connection are serialized by the caller; distinct connections may run on distinct threads
Params buildersOne thread at a time, as for a connection
PanicsEvery entry point catches a panic at the boundary. The handle it happened on is then poisoned: every later call on it returns RELATE_ERROR with an internal-stage shape, or 0 or NULL. Close and destroy still free a poisoned handle.

Environment

VariableEffect
RELATE_FFI_LOG1 logs an internal failure's original message to stderr; read once per process
RELATE_SNAPSHOT_FORMAT, RELATE_DURABILITY_MODE, RELATE_CHECKPOINT_INTERVAL_SECS, RELATE_LOGICAL_GRAPH_SIZE_LIMIT, RELATE_LOAD_CSV_DIR and the other open settingsRead at open, as the Rust crate's default OpenOptions reads them; the C ABI has no options argument

Limits

The limits come from the engine and are the same on every surface: a statement's text is at most 4194304 bytes (syntax error: query text of <n> bytes exceeds the maximum query size of 4194304 bytes), one value a statement builds is bounded by the expression memory cap, and a file-backed store has one writing open across every process. Limits and sizing lists each limit with its default and the setting that changes it.

Capabilities

FeatureC
In-memory storeA store that lives only in the process.yesrelate_database_open(":memory:", …)
Directory storeA store in a directory on disk.yesrelate_database_open(path, …)
Single-file .rl storeA store in one .rl file.yesa *.rl URI
Read-only openAn open that loads the store once and refuses every write.yesrelate_database_open_read_only
Explicit transactionsSeveral statements committed or rolled back together.yesrelate_conn_begin, relate_conn_commit, relate_conn_rollback
Parameters$name values passed beside the statement.yesthe params builder
Column types, counters and timingsMetadata returned with each result.partialcounters, timings and column names; no column-type call (read each value's kind with relate_value_kind)
NotificationsThe engine's query notifications (for example a Cartesian product), each with a remedy.yesrelate_result_notifications
Arrow outputResults as Apache Arrow.nono Arrow call in the client
Change data captureReading committed changes from a slot.nono change-data-capture call in the client
Full-text searchFull-text indexes and their queries.yesCREATE FULLTEXT INDEX and full-text SEARCH through the C ABI
LOAD CSV from local filesLocal LOAD CSV under a configured directory (denied unless one is set).yesRELATE_LOAD_CSV_DIR, read by the C ABI's open
Load a store from a URLOpening a .rl store fetched over https.nono open from a URL in the client
Agent modeThe CLI's agent defaults: the 100-row cap and the piped toon output.noA CLI setting

Each cell is yes, no, partial or unknown, with the call, flag or reason. Checked on 2026-11-09 against relate 0.1.2: runs of the CLI binary and relate serve (Bolt, HTTP and MCP), the WebAssembly build this site ships, and the Python, Node.js, C, Java, Go, Swift and Rust clients, and each surface's upstream reference. The C, Java, Go and Swift clients load the same C ABI library as the Node.js client.

Example

This program binds a zoned datetime, a point and a vector, then reads each back by its components:

#include <stdio.h>
#include "relate.h"

/* Bind a zoned datetime, a WGS-84 point and a FLOAT32 vector, then read them back. */
int main(void) {
    relate_database db = NULL;
    relate_connection conn = NULL;
    relate_query_result r = NULL;
    char *err = NULL;
    if (relate_database_open(":memory:", &db, &err) != RELATE_OK) return 1;
    if (relate_connect_namespace(db, NULL, &conn, &err) != RELATE_OK) return 1;

    double embedding[3] = {0.25, -1.0, 0.5};
    relate_params p = relate_params_new();
    if (relate_params_put_datetime(p, "at", 2026, 11, 4, 9, 30, 0, 0, 0, "Europe/Berlin", &err) != RELATE_OK ||
        relate_params_put_point(p, "loc", 4326, 12.5, 55.6, NULL, &err) != RELATE_OK ||
        relate_params_put_vector_float(p, "emb", "FLOAT32", embedding, 3, &err) != RELATE_OK) {
        fprintf(stderr, "put refused: %s\n", err);
        relate_free_string(err);
        return 1;
    }
    if (relate_query_params(conn, "RETURN $at AS at, $loc AS loc, $emb AS emb", p, &r, &err) != RELATE_OK) return 1;
    if (relate_result_is_error(r)) return 1;

    relate_value at = relate_value_at(r, 0, 0);
    int64_t y = 0, mo = 0, d = 0, h = 0, mi = 0, off = 0;
    relate_value_year(at, &y); relate_value_month(at, &mo); relate_value_day(at, &d);
    relate_value_hour(at, &h); relate_value_minute(at, &mi); relate_value_offset_seconds(at, &off);
    printf("at: kind=%d %04lld-%02lld-%02lld %02lld:%02lld offset=%lld zone=%s\n",
           relate_value_kind(at), (long long)y, (long long)mo, (long long)d,
           (long long)h, (long long)mi, (long long)off, relate_value_zone(at));

    int32_t srid = 0, dim = 0;
    double x = 0, yy = 0, z = 0;
    relate_value_point(relate_value_at(r, 0, 1), &srid, &dim, &x, &yy, &z);
    printf("loc: kind=%d srid=%d dim=%d x=%g y=%g\n", relate_value_kind(relate_value_at(r, 0, 1)), srid, dim, x, yy);

    relate_value emb = relate_value_at(r, 0, 2);
    printf("emb: kind=%d dtype=%s dim=%lld [", relate_value_kind(emb), relate_value_vector_dtype(emb),
           relate_value_vector_dim(emb));
    for (long long i = 0; i < relate_value_vector_dim(emb); i++) {
        double c = 0;
        relate_value_vector_float(emb, i, &c);
        printf("%s%g", i ? ", " : "", c);
    }
    int64_t wrong = 0;
    printf("]\nint accessor on a FLOAT32 vector: %s\n",
           relate_value_vector_int(emb, 0, &wrong) == RELATE_ERROR ? "RELATE_ERROR" : "RELATE_OK");

    relate_destroy_result(r);
    relate_params_free(p);
    relate_disconnect(conn);
    relate_database_close(db);
    return 0;
}
at: kind=14 2026-11-04 09:30 offset=3600 zone=Europe/Berlin
loc: kind=10 srid=4326 dim=2 x=12.5 y=55.6
emb: kind=17 dtype=FLOAT32 dim=3 [0.25, -1, 0.5]
int accessor on a FLOAT32 vector: RELATE_ERROR

See also