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

TypePurpose
maelys_datalog_fact_view_tBorrowed 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.

TypeUse on this pageDefined in
maelys_datalog_result_tInput handle for queries, enumeration and explanations.Solving

Functions

FunctionPurpose
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 macroPurpose
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

C · struct
maelys_datalog_fact_view_t
typedef struct {
    size_t arity;
    maelys_datalog_term_view_t terms[MAELYS_DATALOG_PUBLIC_MAX_TERMS];
} maelys_datalog_fact_view_t;
Borrowed view of one result fact returned by enumeration. Its symbol IDs belong to the same live result.
Fields
aritysize_t
Number of populated result terms.
termsmaelys_datalog_term_view_t[4]
Typed result terms; resolve symbol IDs with result_symbol_text() on this result.

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.

C · function
Query a ground fact
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
);
Checks whether one fully specified fact is present in an existing result. It does not solve the policy again.
Arguments
resultconst maelys_datalog_result_t *
Live result returned by a successful solve.
predicateconst char *
Name of a queryable predicate.
termsconst maelys_datalog_value_t *
Fully specified values for the query.
aritysize_t
Number of values in terms; must match the registered predicate.
out_presentint *
Receives 1 if the fact is derived or 0 if absent.
Return value
maelys_datalog_status_t

An absent fact is a successful negative answer; an API error is separate.

CODE
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:

C · function
Enumerate a query relation
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
);
Lists the derived facts of a query-enabled predicate in an existing result, using the supplied output array.
Arguments
resultconst maelys_datalog_result_t *
Live result to inspect or release.
predicateconst char *
Queryable predicate name.
aritysize_t
Declared predicate arity.
out_factsmaelys_datalog_fact_view_t *
Caller-owned array for returned fact views; NULL when measuring.
out_capacitysize_t
Available output entries or text bytes.
out_countsize_t *
Receives the number of matching or derived facts.
Return value
maelys_datalog_status_t

Returns OK and the matching count. Supply caller storage to fetch views after measuring.

CODE
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:

C · function
Resolve a result symbol
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
);
Looks up the string represented by a symbol ID belonging to this result. That ID is not a reusable symbol identifier for another result.
Arguments
resultconst maelys_datalog_result_t *
Live result to inspect or release.
symbol_iduint32_t
Result-scoped identifier from an enumerated fact.
out_textconst char **
Receives borrowed symbol text or explanation text.
out_lengthsize_t *
Receives borrowed symbol text length.
Return value
maelys_datalog_status_t

Returns borrowed text tied to this result; never free it independently.

CODE
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:

C · function
Count derived facts
maelys_datalog_status_t
maelys_datalog_result_derived_fact_count(
const maelys_datalog_result_t * result,
size_t * out_count
);
Reads the number of facts derived during this solve, without enumerating them or deriving additional facts.
Arguments
resultconst maelys_datalog_result_t *
Live result to inspect or release.
out_countsize_t *
Receives the number of matching or derived facts.
Return value
maelys_datalog_status_t

Returns OK and a total across IDB predicates, including those not queryable.

CODE
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.

CODEDocument-access membership query

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.

C · function
Render Why-true text
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
);
Produces a bounded Why-true document describing a derivation of the requested fact in this result. It explains the existing answer rather than solving the policy again.
Arguments
resultconst maelys_datalog_result_t *
Live result to inspect or release.
predicateconst char *
Queryable predicate name.
termsconst maelys_datalog_value_t *
Fully specified typed query terms.
aritysize_t
Declared predicate arity.
out_textchar *
Receives borrowed symbol text or explanation text.
out_capacitysize_t
Available output entries or text bytes.
out_requiredsize_t *
Receives required text length, excluding NUL.
Return value
maelys_datalog_status_t

Returns OK with the required byte count; an insufficient text buffer is separate from a truncated explanation.

CODE
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:

C · function
Render Why-false text
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
);
Produces a bounded Why-false document describing obstacles encountered when investigating an absent fact. This is a diagnostic exploration, not an exhaustive proof of every possible obstruction.
Arguments
resultconst maelys_datalog_result_t *
Live result to inspect or release.
predicateconst char *
Queryable predicate name.
termsconst maelys_datalog_value_t *
Fully specified typed query terms.
aritysize_t
Declared predicate arity.
out_textchar *
Receives borrowed symbol text or explanation text.
out_capacitysize_t
Available output entries or text bytes.
out_requiredsize_t *
Receives required text length, excluding NUL.
Return value
maelys_datalog_status_t

Returns 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.

C · function
Release a result
maelys_datalog_status_t
maelys_datalog_result_free(
maelys_datalog_result_t * result
);
Releases a result and its result-local data, allowing the session to solve another request once explanation leases have also been released.
Arguments
resultmaelys_datalog_result_t *
Live result to inspect or release.
Return value
maelys_datalog_status_t

Returns OK after ending the result lease; another solve may then reuse its session.

CODE
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.

  1. Workspace
    caller-owned memory
  2. Prepare once
    retain explanation
  3. Text size
    cached length
  4. Write text
    repeat without rebuilding
  5. Release
    unlock 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():

CODE
#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:

CODE
/* 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

C · macro builder
MAELYS_DATALOG_QUERY()
MAELYS_DATALOG_QUERY(result, present, ...)

Queries a fully specified fact using C11 term conversion.

Arguments
resultconst maelys_datalog_result_t *
One live result returned by a successful solve.
presentint *
Receives 0 or 1 only after a successful query.
...const char * predicate, then 0–4 supported C term expressions
The ground fact to query; strings, integers and explicit booleans use C11 conversion.
Effect

Converts the supplied terms and calls the public ground-query function. It does not solve the policy again.

Result

The same public status and presence output as maelys_datalog_result_query().

Example
int present = 0;
maelys_datalog_status_t rc = MAELYS_DATALOG_QUERY(
    result, &present, "allow", "alice", "roadmap.pdf");
C · macro builder
MAELYS_DATALOG_EXPLANATION_STORAGE()
MAELYS_DATALOG_EXPLANATION_STORAGE(name, bytes)

Declares suitably aligned caller-owned bytes for prepared explanations.

Arguments
nameC identifier token
Name of the byte array being declared, not a string expression.
bytespositive integer constant expression
Compile-time array size in bytes; check it against the runtime storage bound.
Effect

Declares aligned caller-owned bytes. It does not size, prepare or release an explanation; the example size is illustrative, not a guaranteed bound.

Result

An aligned fixed-size byte array; it neither prepares nor releases an explanation.

Example
MAELYS_DATALOG_EXPLANATION_STORAGE(
    explanation_storage, 16u * 1024u);