C Stable API
Querying
Query results, enumerate relations, and render explanations through the stable C facade.The stable C result API checks a ground fact, enumerates a declared query relation, or renders a text explanation. It does not expose the internal symbol table or structured proof graph. Read the querying concept first if membership and query permissions are new.
Types
Defined on this page
| Type | Purpose |
|---|---|
maelys_datalog_fact_view_t | Borrowed view of an enumerated result fact. |
Used from other pages
These types are defined on the linked pages; this page uses them in the roles below.
| Type | Use on this page | Defined in |
|---|---|---|
maelys_datalog_result_t | Input handle for queries, enumeration and explanations. | Solving |
Functions
| Function | Purpose |
|---|---|
maelys_datalog_result_query() | Ask whether one fully specified fact is present. |
maelys_datalog_result_enumerate() | Enumerate a queryable relation. |
maelys_datalog_result_symbol_text() | Resolve a result-scoped symbol ID. |
maelys_datalog_result_derived_fact_count() | Count distinct derived IDB facts. |
maelys_datalog_result_explain_true_text() / maelys_datalog_result_explain_false_text() | Render a bounded explanation document. |
maelys_datalog_result_free() | End this result's lifetime before another solve. |
Function-like macro builders
The optional builders header adds these source conveniences; the API functions above remain available without them.
| Function-like macro | Purpose |
|---|---|
MAELYS_DATALOG_QUERY(result, present, ...) | Queries a fully specified fact using C11 term conversion. |
MAELYS_DATALOG_EXPLANATION_STORAGE(name, bytes) | Declares suitably aligned caller-owned bytes for prepared explanations. |
Enumerated fact type
typedef struct {
size_t arity;
maelys_datalog_term_view_t terms[MAELYS_DATALOG_PUBLIC_MAX_TERMS];
} maelys_datalog_fact_view_t;Interpreting query results
Do not confuse absence with failure: authorization requires a successful solve,
a successful query, and a present allow fact. Release each result with
maelys_datalog_result_free() when all queries and explanations are finished.
Querying and enumeration
A ground query supplies every term. Its answer is membership, not a list of matching bindings.
maelys_datalog_status_t
maelys_datalog_result_query(
const maelys_datalog_result_t * result,
const char * predicate,
const maelys_datalog_value_t * terms,
size_t arity,
int * out_present
);maelys_datalog_status_tAn absent fact is a successful negative answer; an API error is separate.
int present = 0;
maelys_datalog_status_t status = maelys_datalog_result_query(
result, "allow", terms, 2u, &present);maelys_datalog_result_query() accepts the same public value representation as
the solve input. A symbol that was never interned produces a successful
negative answer rather than mutating the result.
Enumeration is for callers that need all facts in a declared query relation, not just one membership answer. First measure, then provide an array:
maelys_datalog_status_t
maelys_datalog_result_enumerate(
const maelys_datalog_result_t * result,
const char * predicate,
size_t arity,
maelys_datalog_fact_view_t * out_facts,
size_t out_capacity,
size_t * out_count
);maelys_datalog_status_tReturns OK and the matching count. Supply caller storage to fetch views after measuring.
size_t count = 0u;
maelys_datalog_status_t status = maelys_datalog_result_enumerate(
result, "allow", 1u, NULL, 0u, &count);When a returned term has kind MAELYS_DATALOG_VALUE_SYMBOL, its view contains
a result-owned symbol identifier. Resolve it with the same result:
maelys_datalog_status_t
maelys_datalog_result_symbol_text(
const maelys_datalog_result_t * result,
uint32_t symbol_id,
const char ** out_text,
size_t * out_length
);maelys_datalog_status_tReturns borrowed text tied to this result; never free it independently.
const char *text = NULL;
size_t text_length = 0u;
maelys_datalog_result_symbol_text(
result, view.terms[0].as.symbol_id, &text, &text_length);The returned text is borrowed. Do not free it or retain it after freeing the result.
When a caller needs an overall derived-fact metric, count distinct IDB facts without enumerating every relation:
maelys_datalog_status_t
maelys_datalog_result_derived_fact_count(
const maelys_datalog_result_t * result,
size_t * out_count
);maelys_datalog_status_tReturns OK and a total across IDB predicates, including those not queryable.
size_t derived = 0u;
maelys_datalog_status_t status =
maelys_datalog_result_derived_fact_count(result, &derived);Example — document access
After solving the same seven-fact request, query the fully specified allow/2 fact for blocked Mallory. Both tabs have the same status and membership semantics; neither a query error nor a successful absent fact authorizes access. Assume result is live.
query.c
#include <maelys/datalog.h>
int present = 0;
const maelys_datalog_value_t mallory_query[] = {
{.kind = MAELYS_DATALOG_VALUE_SYMBOL, .as.symbol = "mallory"},
{.kind = MAELYS_DATALOG_VALUE_SYMBOL, .as.symbol = "roadmap.pdf"},
};
maelys_datalog_status_t status = maelys_datalog_result_query(
result, "allow", mallory_query, 2u, &present);
if (status != MAELYS_DATALOG_STATUS_OK) { /* API error: deny. */ }
else if (present == 0) { /* Successful absence: deny. */ }With the same result, allow("alice","roadmap.pdf") and
allow("bob","roadmap.pdf") are present.
has_any_document("alice") is present and
has_any_document("bob") is absent: delegation permits reading but does
not establish ownership. These are the five checks in the
full program.
Why-true text
The stable facade exposes canonical Why-true as text. Use a count-then-render sequence so the application, not the engine, chooses the output buffer.
maelys_datalog_status_t
maelys_datalog_result_explain_true_text(
const maelys_datalog_result_t * result,
const char * predicate,
const maelys_datalog_value_t * terms,
size_t arity,
char * out_text,
size_t out_capacity,
size_t * out_required
);maelys_datalog_status_tReturns OK with the required byte count; an insufficient text buffer is separate from a truncated explanation.
const maelys_datalog_value_t alice_query[] = {
MAELYS_DATALOG_SYMBOL("alice"),
MAELYS_DATALOG_SYMBOL("roadmap.pdf"),
};
size_t required = 0u;
maelys_datalog_status_t status =
maelys_datalog_result_explain_true_text(
result, "allow", alice_query, 2u, NULL, 0u, &required);
char *text = calloc(required + 1u, 1u);
if (text != NULL && status == MAELYS_DATALOG_STATUS_OK) {
status = maelys_datalog_result_explain_true_text(
result, "allow", alice_query, 2u,
text, required + 1u, &required);
}Why-true is a retained derivation witness. For a fact absent after solving, ask why it was not derived:
maelys_datalog_status_t
maelys_datalog_result_explain_false_text(
const maelys_datalog_result_t * result,
const char * predicate,
const maelys_datalog_value_t * terms,
size_t arity,
char * out_text,
size_t out_capacity,
size_t * out_required
);maelys_datalog_status_tReturns OK with the required byte count; the document may describe a bounded, truncated exploration.
maelys_datalog_result_explain_false_text has the same buffer contract and renders the bounded post-solve Why-false
exploration under the same MAELYS-DATALOG-v2 envelope, with
document=why-false on the second line (since 0.4.0); what that document does
and does not promise is on the experimental API
page.
Release the result when all its queries and explanations are finished. A live prepared explanation must be released first.
maelys_datalog_status_t
maelys_datalog_result_free(
maelys_datalog_result_t * result
);maelys_datalog_status_tReturns OK after ending the result lease; another solve may then reuse its session.
maelys_datalog_status_t status = maelys_datalog_result_free(result);
result = NULL;Prepared explanations
Use the prepared API when you supply bounded memory yourself, or want explicit control over how long an explanation is retained across writes. This path is available in 0.4.0 for both Why-true and Why-false. Preparation validates the workspace size and alignment; it does not allocate inside the native reference backend.
Manual ownership does not increase explanation capability. With sufficient buffers, the manual path and the session-managed path produce the same explanation for the same result, query, backend and build profile. The automatic path also reuses preparation across writes while its one-entry cache still holds that query. The difference is memory ownership and lifetime, not a larger proof or exploration budget. See the quickstart memory model.
- Workspacecaller-owned memory
- Prepare onceretain explanation
- Text sizecached length
- Write textrepeat without rebuilding
- Releaseunlock result
The example uses fixed application budgets, not guaranteed library maxima. An insufficient workspace is rejected by preparation; an insufficient text buffer is detected before writing. There is no heap fallback. Release the explanation before releasing its result or reusing its workspace. The helper does this even when sizing or writing fails.
To try this manual alternative with the complete quickstart, omit the call to maelys_datalog_session_config_set_explanation_workspace() and its status check when configuring the session. Keep the remaining configuration, session creation and cleanup. This avoids reserving an automatic workspace in addition to the manual buffers below. Use this helper instead of the quickstart's direct-text helper, above main():
#include <stddef.h>
/* Fixed application budgets; the API checks the supplied capacities. */
typedef struct {
_Alignas(max_align_t) unsigned char arena[256u * 1024u];
char text[16u * 1024u];
} explanation_buffers_t;
/* Add above main(), after the includes in the complete example. */
static int print_explanation(
maelys_datalog_result_t *result,
const char *predicate,
const maelys_datalog_value_t *terms,
size_t arity,
explanation_buffers_t *buffers) {
int present = 0;
maelys_datalog_status_t rc = maelys_datalog_result_query(
result, predicate, terms, arity, &present);
if (rc != MAELYS_DATALOG_STATUS_OK) return 0;
maelys_datalog_explanation_kind_t kind;
if (present) {
kind = MAELYS_DATALOG_EXPLAIN_TRUE;
} else {
kind = MAELYS_DATALOG_EXPLAIN_FALSE;
}
/* Preparation validates the workspace size and alignment. */
maelys_datalog_prepared_explanation_t *explanation = NULL;
rc = maelys_datalog_result_prepare_explanation(
result, kind, predicate, terms, arity,
buffers->arena, sizeof(buffers->arena), &explanation);
if (rc != MAELYS_DATALOG_STATUS_OK) {
fprintf(stderr, "prepare explanation: %s\n", maelys_datalog_status_name(rc));
return 0;
}
int success = 0;
size_t required = 0;
/* Read the cached text length, excluding its terminating NUL. */
rc = maelys_datalog_prepared_explanation_text_size(explanation, &required);
if (rc != MAELYS_DATALOG_STATUS_OK) goto release;
if (required >= sizeof(buffers->text)) {
fprintf(stderr, "Explanation text buffer is too small\n");
goto release;
}
/* Format the prepared explanation; do not build it again. */
rc = maelys_datalog_prepared_explanation_write_text(
explanation, buffers->text, sizeof(buffers->text));
if (rc != MAELYS_DATALOG_STATUS_OK) goto release;
success = fputs(buffers->text, stdout) >= 0;
release:
/* Always release the handle before its workspace or result is reused. */
rc = maelys_datalog_prepared_explanation_release(explanation);
return success && rc == MAELYS_DATALOG_STATUS_OK;
}Use it while the result is live, after the five membership checks and before
exit_code = answers_match ? 0 : 2:
/* After the five checks, before freeing result. */
/* One reusable buffer pair for this single-threaded example. */
static explanation_buffers_t explanation_buffers;
const maelys_datalog_value_t query_alice[] = {
MAELYS_DATALOG_SYMBOL("alice"),
MAELYS_DATALOG_SYMBOL("roadmap.pdf"),
};
const maelys_datalog_value_t query_mallory[] = {
MAELYS_DATALOG_SYMBOL("mallory"),
MAELYS_DATALOG_SYMBOL("roadmap.pdf"),
};
if (!print_explanation(result, "allow", query_alice, 2u, &explanation_buffers)
|| !print_explanation(result, "allow", query_mallory, 2u, &explanation_buffers)) {
fprintf(stderr, "Could not render the explanation\n");
goto cleanup;
}The two calls reuse one buffer pair sequentially; concurrent or reentrant
callers need separate storage. A write needs at least text_size + 1 bytes.
Unlike the direct-text convenience call, prepared_explanation_write_text()
does not have a NULL-buffer sizing mode. After a short write, the same handle
can be written into a larger buffer without preparation again. A successfully
rendered status=truncated document is not a buffer error: it reports proof or
exploration limits, which neither a larger text buffer nor a larger workspace
can remove. In contrast, STORAGE_TOO_SMALL rejects insufficient caller-provided
workspace and PAYLOAD_TOO_LARGE rejects an insufficient output buffer. These
API errors are not successfully returned truncated documents. Some internal
limits bound stored proof data, but their capacities are set by the engine,
not by the caller's choice of automatic or manual memory management.
This helper adds no allocation for its workspace or text; that is not a promise
about standard I/O or arbitrary filter/backend callbacks. Python's
explain_true() and explain_false() already use native preparation internally,
but Python/CFFI allocate their own buffers.
Since 0.4.1, maelys_datalog_result_explain_text_in() performs preparation, sizing, writing and release in one caller-owned operation. maelys_datalog_session_explanation_storage_bound() reports the reference-session bound. Since 0.5.0, a configured session can own and reuse this workspace itself; see the 0.5.0 release notes. The explicit prepared path remains useful for retaining a handle across several writes.
Macro builder reference
MAELYS_DATALOG_QUERY(result, present, ...)Queries a fully specified fact using C11 term conversion.
Converts the supplied terms and calls the public ground-query function. It does not solve the policy again.
The same public status and presence output as maelys_datalog_result_query().
int present = 0;
maelys_datalog_status_t rc = MAELYS_DATALOG_QUERY(
result, &present, "allow", "alice", "roadmap.pdf");MAELYS_DATALOG_EXPLANATION_STORAGE(name, bytes)Declares suitably aligned caller-owned bytes for prepared explanations.
Declares aligned caller-owned bytes. It does not size, prepare or release an explanation; the example size is illustrative, not a guaranteed bound.
An aligned fixed-size byte array; it neither prepares nor releases an explanation.
MAELYS_DATALOG_EXPLANATION_STORAGE(
explanation_storage, 16u * 1024u);