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.
| API | Use when |
|---|---|
maelys_datalog_solve_once | Standard path. Diagnostics are not needed on solve failure. |
maelys_datalog_solve_once_ex | The caller wants structured diagnostics on solve failure. |
Standard variant
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
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
| Parameter | Meaning |
|---|---|
ruleset | Pointer to the first loaded ruleset. Typically &policy_set.policies[0]. |
edb | Finalized EDB containing runtime input facts. |
out_result | Receives the solve result on success. Set to NULL on failure. Must not be NULL itself. |
out_diag | Optional 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:
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.
#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
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 code | Meaning |
|---|---|
MAELYS_DATALOG_SOLVE_DIAG_MAX_DEPTH | Recursion depth limit exceeded. |
MAELYS_DATALOG_SOLVE_DIAG_IDB_OVERFLOW | Derived fact capacity exceeded. |
MAELYS_DATALOG_SOLVE_DIAG_COMPARISON_TYPE_ERROR | Runtime 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_ERROR | Invalid filter program or runtime value, or exhausted filter budget. An ordinary filter non-match is not an error. |
MAELYS_DATALOG_SOLVE_DIAG_MALFORMED_FACT | A fact is structurally invalid for the active registry. |
MAELYS_DATALOG_SOLVE_DIAG_MALFORMED_EDB | EDB not finalized, not sorted, or structurally invalid. |
MAELYS_DATALOG_SOLVE_DIAG_INVALID_STATE | Ruleset or solve state is invalid. |
MAELYS_DATALOG_SOLVE_DIAG_INVALID_ARGUMENT | A required solve argument is invalid. |
MAELYS_DATALOG_SOLVE_DIAG_INTERNAL_ERROR | Internal allocation or solver error. |
Ruleset pointer for WASM
For WASM and embedded callers that use solve_once directly, the ruleset
pointer is:
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 code | Meaning |
|---|---|
MAELYS_OK | Solving succeeded. *out_result is valid. |
MAELYS_ERR_INVALID_ARGUMENT | NULL pointer argument. |
MAELYS_ERR_INVALID_STATE | EDB not finalized. |
MAELYS_ERR_PAYLOAD_TOO_LARGE | IDB fact capacity exceeded during solving. |
MAELYS_ERR_INTERNAL | Internal solver error. |
On any failure, *out_result is NULL and no memory needs to be freed.
Additional C example 1
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
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;
}