C Low-level API

Solving

Low-level C operations for solving; concepts and integration code are documented separately.

This page documents the transparent C functions behind the solving concepts. New application integrations should start with the stable C API; this reference is for consumers of the advanced, structure-level API.

Solving APIs

Two variants are available.

APIUse when
maelys_datalog_solve_onceStandard path. Diagnostics are not needed on solve failure.
maelys_datalog_solve_once_exThe caller wants structured diagnostics on solve failure.

Standard variant

CODE
maelys_result_t
maelys_datalog_solve_once(
    const maelys_datalog_ruleset_t *ruleset,
    const maelys_datalog_edb_t *edb,
    maelys_datalog_solve_result_t **out_result);

Extended variant

CODE
maelys_result_t
maelys_datalog_solve_once_ex(
    const maelys_datalog_ruleset_t *ruleset,
    const maelys_datalog_edb_t *edb,
    maelys_datalog_solve_result_t **out_result,
    maelys_datalog_solve_diagnostic_t *out_diag);

out_diag may be NULL. When provided, structured diagnostics for solver failures are written there. Diagnostics do not change the return code or the fail-closed behavior.

Parameters

ParameterMeaning
rulesetPointer to the first loaded ruleset. Typically &policy_set.policies[0].
edbFinalized EDB containing runtime input facts.
out_resultReceives the solve result on success. Set to NULL on failure. Must not be NULL itself.
out_diagOptional structured diagnostics. solve_once_ex only. May be NULL.

Example — document access

With the shared documents.main policy loaded and the same seven-fact EDB finalized, call the low-level solver with its transparent ruleset:

CODE
maelys_datalog_ruleset_t *policy = &policy_set.policies[0];
maelys_datalog_solve_result_t *result = NULL;
maelys_result_t rc = maelys_datalog_solve_once(policy, &edb, &result);
if (rc != MAELYS_OK) {
    /* No result: deny. */
}

The stable API performs the same solve through maelys_datalog_session_solve_edb(session, edb, ...). The low-level result contains allow("alice","roadmap.pdf") and allow("bob","roadmap.pdf"), but not allow("mallory","roadmap.pdf"). The separate example below demonstrates the solver call with a different legacy policy; do not copy its allow/1 query into this allow/2 domain.

Example

This helper applies the shared documents.main domain with its two-term allow(User, Doc) query. The caller interns alice and roadmap.pdf when building the EDB and passes those symbol IDs here. It does not alter the ruleset's symbol table during a query.

CODE
#include <maelys_datalog.h>

maelys_result_t solve_and_query(
    const maelys_datalog_policy_set_t *policy_set,
    const maelys_datalog_edb_t *edb,
    maelys_datalog_symbol_id_t alice,
    maelys_datalog_symbol_id_t document)
{
    maelys_datalog_solve_result_t *result = NULL;
    maelys_result_t rc = maelys_datalog_solve_once(
        &policy_set->policies[0], edb, &result);
    if (rc != MAELYS_OK) return rc;

    const maelys_datalog_term_t terms[] = {
        {.kind = MAELYS_DATALOG_TERM_SYMBOL, .as.symbol = alice},
        {.kind = MAELYS_DATALOG_TERM_SYMBOL, .as.symbol = document},
    };
    bool present = false;
    rc = maelys_datalog_query_solved_ground_fact(
        result, "allow", terms, 2u, &present);
    maelys_datalog_solve_result_free(result);
    if (rc != MAELYS_OK) return rc;
    return present ? MAELYS_OK : MAELYS_ERR_FORBIDDEN;
}

This function returns the application-level FORBIDDEN decision for an absent allow fact. A failed solve or query remains a distinct error.

Freeing the result

CODE
void
maelys_datalog_solve_result_free(
    maelys_datalog_solve_result_t *result);

The solve result is heap-allocated. It must be freed after all queries are done. Passing NULL is safe — solve_result_free(NULL) is a no-op.

Solve diagnostics

When using solve_once_ex, structured diagnostics explain solve failures.

Diagnostic codeMeaning
MAELYS_DATALOG_SOLVE_DIAG_MAX_DEPTHRecursion depth limit exceeded.
MAELYS_DATALOG_SOLVE_DIAG_IDB_OVERFLOWDerived fact capacity exceeded.
MAELYS_DATALOG_SOLVE_DIAG_COMPARISON_TYPE_ERRORRuntime cross-type comparison: a variable resolved to a type incompatible with the operator. solve_once_ex() returns MAELYS_ERR_INVALID_FIELD, result is NULL. Diagnostic fields: lhs_kind, rhs_kind, comparison_op. Denial reason: MAELYS_DATALOG_DENY_COMPARISON_TYPE_ERROR. See Comparison operators.
MAELYS_DATALOG_SOLVE_DIAG_FILTER_ERRORInvalid filter program or runtime value, or exhausted filter budget. An ordinary filter non-match is not an error.
MAELYS_DATALOG_SOLVE_DIAG_MALFORMED_FACTA fact is structurally invalid for the active registry.
MAELYS_DATALOG_SOLVE_DIAG_MALFORMED_EDBEDB not finalized, not sorted, or structurally invalid.
MAELYS_DATALOG_SOLVE_DIAG_INVALID_STATERuleset or solve state is invalid.
MAELYS_DATALOG_SOLVE_DIAG_INVALID_ARGUMENTA required solve argument is invalid.
MAELYS_DATALOG_SOLVE_DIAG_INTERNAL_ERRORInternal allocation or solver error.

Ruleset pointer for WASM

For WASM and embedded callers that use solve_once directly, the ruleset pointer is:

CODE
const maelys_datalog_ruleset_t *ruleset =
    &policy_set.policies[0];

The WASM binding no longer exports a native ruleset pointer. Its typed queries and explanations operate on the owned public result handle.

Return values

Return codeMeaning
MAELYS_OKSolving succeeded. *out_result is valid.
MAELYS_ERR_INVALID_ARGUMENTNULL pointer argument.
MAELYS_ERR_INVALID_STATEEDB not finalized.
MAELYS_ERR_PAYLOAD_TOO_LARGEIDB fact capacity exceeded during solving.
MAELYS_ERR_INTERNALInternal solver error.

On any failure, *out_result is NULL and no memory needs to be freed.

Additional C example 1

CODE
maelys_datalog_edb_finalize(&edb);                 /* 1 — freeze */

maelys_datalog_solve_result_t *result = NULL;
maelys_datalog_solve_once(                         /* 2 — derive */
    &policy_set.policies[0], &edb, &result);

maelys_datalog_query_solved_ground_fact(           /* 3 — inspect, repeat */
    result, "allow", terms, arity, &present);

maelys_datalog_solve_result_free(result);          /* 4 — release */

Additional C example 2

CODE
maelys_datalog_solve_result_t *result = NULL;
maelys_result_t rc = maelys_datalog_solve_once(
    &policy_set.policies[0],
    &edb,
    &result);

if (rc != MAELYS_OK) {
    /* result is NULL — nothing to free */
    return DENY;
}