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
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);| Parameter | Meaning |
|---|---|
result | Solve result from solve_once. Must not be NULL. |
predicate | Predicate name to query, for example "allow". Must not be NULL. |
terms | Array of ground terms. |
arity | Number of terms. Must match the predicate's declared arity. |
out_present | Set 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:
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:
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);| Parameter | Meaning |
|---|---|
result | Solve result from solve_once. Must not be NULL. |
predicate | Predicate name to enumerate, for example "allow". |
arity | Number of terms per fact. Must match the predicate's declared arity. |
out_facts | Caller-provided buffer of out_capacity facts. May be NULL only if out_capacity == 0 (count-only mode). |
out_capacity | Number of facts out_facts can hold. |
out_count | Set 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:
allow(X, "roadmap.pdf")
→ enumerate_predicate_facts(result, "allow", 2, ...)
→ filter facts where terms[1] == doc_sid
→ X = the remaining terms[0] valuesThe 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
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.
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
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
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.
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.
maelys_datalog_solve_result_free(result);Passing NULL is safe. Do not query the result after freeing it.
Return values
| Return code | Meaning |
|---|---|
MAELYS_OK | Query succeeded. *out_present is valid. |
MAELYS_ERR_INVALID_ARGUMENT | A required pointer is NULL, including terms when arity is positive. |
MAELYS_ERR_INVALID_FIELD | Arity 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_STATE | The solve result is not finalized, failed, or no longer has a valid ruleset authority. |
MAELYS_ERR_FORBIDDEN | Predicate 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
/* 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);