C Low-level API

Querying

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

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

Query API

CODE
maelys_result_t
maelys_datalog_query_solved_ground_fact(
    const maelys_datalog_solve_result_t *result,
    const char *predicate,
    const maelys_datalog_term_t *terms,
    size_t arity,
    bool *out_present);
ParameterMeaning
resultSolve result from solve_once. Must not be NULL.
predicatePredicate name to query, for example "allow". Must not be NULL.
termsArray of ground terms.
arityNumber of terms. Must match the predicate's declared arity.
out_presentSet to true if the fact is present, false otherwise. Must not be NULL.

Example — document access

The low-level query uses symbol IDs from the same ruleset and EDB. After the seven facts have been inserted, the IDs for mallory and roadmap.pdf are available. Querying the two-term allow/2 predicate:

CODE
const maelys_datalog_term_t terms[] = {
    {.kind = MAELYS_DATALOG_TERM_SYMBOL, .as.symbol = mallory},
    {.kind = MAELYS_DATALOG_TERM_SYMBOL, .as.symbol = document},
};
bool present = false;
maelys_result_t rc = maelys_datalog_query_solved_ground_fact(
    result, "allow", terms, 2u, &present);
if (rc != MAELYS_OK) {
    /* API failure: deny. */
} else if (!present) {
    /* Successful negative answer for blocked Mallory: deny. */
}

For the same result, use Alice or Bob's symbol ID in terms[0] to obtain present allow facts. The complete comparison checks those three answers plus has_any_document("alice") and has_any_document("bob") on the full program. The query examples below show additional types and access patterns.

Enumerating a whole relation

A pattern with a variable — "all X such that allow(X, "roadmap.pdf")" — is not a ground query. Testing it with query_solved_ground_fact alone would require already knowing every candidate X to check, one call at a time.

maelys_datalog_solve_result_enumerate_predicate_facts reads back every already-derived fact of a QUERY-flagged predicate directly, without guessing ground terms in advance:

CODE
maelys_result_t
maelys_datalog_solve_result_enumerate_predicate_facts(
    const maelys_datalog_solve_result_t *result,
    const char *predicate,
    size_t arity,
    maelys_datalog_fact_t *out_facts,
    size_t out_capacity,
    size_t *out_count);
ParameterMeaning
resultSolve result from solve_once. Must not be NULL.
predicatePredicate name to enumerate, for example "allow".
arityNumber of terms per fact. Must match the predicate's declared arity.
out_factsCaller-provided buffer of out_capacity facts. May be NULL only if out_capacity == 0 (count-only mode).
out_capacityNumber of facts out_facts can hold.
out_countSet to the total number of matching facts, even if it exceeds out_capacity — compare *out_count > out_capacity to detect truncation.

A pattern query with a variable is not a separate primitive — enumerate the predicate in full, then filter the returned facts on the caller side:

CODE
allow(X, "roadmap.pdf")
    → enumerate_predicate_facts(result, "allow", 2, ...)
    → filter facts where terms[1] == doc_sid
    → X = the remaining terms[0] values

The same access control as ground queries applies: the predicate must be QUERY-flagged, and manifest-loaded policies still enforce the Public Query Whitelist. Results are maelys_datalog_fact_t — symbol IDs, never resolved to strings; enumerates only already-derived IDB facts, not EDB or policy-source facts. This accessor is available at the C API level; a WASM/JS binding is not implemented yet.

Example: symbol query

CODE
maelys_result_t rc;
bool allowed = false;

/* Resolve the symbol values without mutating the table */
maelys_datalog_symbol_id_t user_sid, doc_sid;
int user_found = 0, doc_found = 0;
maelys_datalog_symbol_lookup_readonly(
    &policy_set.policies[0].symbols,
    "alice", 5, &user_sid, &user_found);
maelys_datalog_symbol_lookup_readonly(
    &policy_set.policies[0].symbols,
    "roadmap.pdf", 11, &doc_sid, &doc_found);
if (!user_found || !doc_found) {
    /* never interned anywhere: the fact cannot be present */
}

/* Build the query terms */
maelys_datalog_term_t terms[2];
terms[0].kind = MAELYS_DATALOG_TERM_SYMBOL; terms[0].as.symbol = user_sid;
terms[1].kind = MAELYS_DATALOG_TERM_SYMBOL; terms[1].as.symbol = doc_sid;

/* Query */
rc = maelys_datalog_query_solved_ground_fact(
    result, "allow", terms, 2, &allowed);

if (rc == MAELYS_OK && allowed) {
    /* allow("alice", "roadmap.pdf") is derived */
}

Symbol lookup for queries

Query terms use symbol IDs, not raw strings. On the query path, resolve a string with maelys_datalog_symbol_lookup_readonly: it never mutates the symbol table and never consumes capacity. If the symbol was never interned, it reports found = 0 — the queried fact cannot hold, and the caller returns false without touching the table.

CODE
maelys_datalog_symbol_id_t sid = 0;
int found = 0;
rc = maelys_datalog_symbol_lookup_readonly(
    &ruleset.symbols, "alice", 5u, &sid, &found);
if (rc == MAELYS_OK && !found) {
    /* "alice" was never interned: the fact is not present. */
}

Example: integer query

CODE
maelys_datalog_term_t terms[2];
maelys_datalog_symbol_id_t user_sid;
int user_found = 0;
maelys_datalog_symbol_lookup_readonly(
    &policy_set.policies[0].symbols,
    "alice", 5, &user_sid, &user_found);

terms[0].kind       = MAELYS_DATALOG_TERM_SYMBOL;
terms[0].as.symbol  = user_sid;
terms[1].kind       = MAELYS_DATALOG_TERM_INT;
terms[1].as.integer = 1024;      /* file size limit */

bool present = false;
maelys_datalog_query_solved_ground_fact(
    result, "quota_exceeded", terms, 2, &present);

Example: 1-arity boolean query

CODE
maelys_datalog_term_t term;
maelys_datalog_symbol_id_t sid;
int sid_found = 0;
maelys_datalog_symbol_lookup_readonly(
    &policy_set.policies[0].symbols,
    "req-1", 5, &sid, &sid_found);
term.kind      = MAELYS_DATALOG_TERM_SYMBOL;
term.as.symbol = sid;

bool allowed = false;
maelys_datalog_query_solved_ground_fact(
    result, "allow", &term, 1, &allowed);

Multiple queries on one result

A solve result may be queried multiple times. Each call to query_solved_ground_fact is independent. The result is not modified by querying.

CODE
bool alice_allowed = false;
bool bob_allowed   = false;

maelys_datalog_query_solved_ground_fact(
    result, "allow", alice_terms, 2, &alice_allowed);

maelys_datalog_query_solved_ground_fact(
    result, "allow", bob_terms, 2, &bob_allowed);

maelys_datalog_solve_result_free(result);

Freeing the result

Always free the solve result after all queries are done.

CODE
maelys_datalog_solve_result_free(result);

Passing NULL is safe. Do not query the result after freeing it.

Return values

Return codeMeaning
MAELYS_OKQuery succeeded. *out_present is valid.
MAELYS_ERR_INVALID_ARGUMENTA required pointer is NULL, including terms when arity is positive.
MAELYS_ERR_INVALID_FIELDArity exceeds the build limit, a term is not ground, the predicate is unknown, or its arity does not match the domain declaration.
MAELYS_ERR_INVALID_STATEThe solve result is not finalized, failed, or no longer has a valid ruleset authority.
MAELYS_ERR_FORBIDDENPredicate exists but is not in the manifest Public Query Whitelist.

MAELYS_OK with *out_present = false means the fact is absent. It is not an error. The solver completed normally and the queried fact was not derived.

Additional C example 1

CODE
/* Inline loading — all domain QUERY predicates accessible */
maelys_datalog_load_policy_inline(
    "access", "access.inline", src, src_len,
    0, &policy_set, &diag);

/* Querying allow/1 — permitted if declared QUERY by domain */
maelys_datalog_query_solved_ground_fact(
    result, "allow", &term, 1, &present);