C Low-level API
Full program
Complete document-access program for the C Low-level API, with checked C source.The C Low-level API overview explains the lifecycle and compares the two C integration paths. This page keeps the complete, compiled example so you can inspect and run every call.
Document access with the low-level C API
This implements exactly the same document-access decision as the stable C API and the quickstart: the same policy, seven request facts and five ground queries. The difference is visible in C: the application owns the fact pool, shares the ruleset's symbol table and registry, finalizes the EDB, then queries a transparent solve result.
Both programs pass the following identical Datalog source to the inline loader:
can_read(User, Doc) :-
owns(User, Doc) or delegated(User, Doc),
not(blocked(User)).
has_any_document(User) :- owns(User, _).
allow(User, Doc) :- user(User), can_read(User, Doc).
They supply user("alice"), user("bob"), user("mallory"),
owns("alice", "roadmap.pdf"), delegated("bob", "roadmap.pdf"),
owns("mallory", "roadmap.pdf"), and blocked("mallory") as request
facts. Neither program puts a blocked-user fact in policy source.
| Ground query | Answer |
|---|---|
allow("alice", "roadmap.pdf") | true |
allow("bob", "roadmap.pdf") | true |
allow("mallory", "roadmap.pdf") | false |
has_any_document("alice") | true |
has_any_document("bob") | false |
The complete, fail-closed C program for this API is:
#include <maelys_datalog.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
static const maelys_datalog_predicate_def_t predicates[] = {
{"user", 1u, MAELYS_DATALOG_PRED_KIND_EDB},
{"owns", 2u, MAELYS_DATALOG_PRED_KIND_EDB},
{"delegated", 2u, MAELYS_DATALOG_PRED_KIND_EDB},
{"blocked", 1u, MAELYS_DATALOG_PRED_KIND_EDB},
{"can_read", 2u, MAELYS_DATALOG_PRED_KIND_IDB},
{"has_any_document", 1u,
MAELYS_DATALOG_PRED_KIND_IDB | MAELYS_DATALOG_PRED_KIND_QUERY},
{"allow", 2u,
MAELYS_DATALOG_PRED_KIND_IDB | MAELYS_DATALOG_PRED_KIND_QUERY},
};
static const char source[] =
"can_read(User, Doc) :-\n"
" owns(User, Doc) or delegated(User, Doc),\n"
" not(blocked(User)).\n"
"has_any_document(User) :- owns(User, _).\n"
"allow(User, Doc) :- user(User), can_read(User, Doc).\n";
static int ok(maelys_result_t status, const char *step) {
if (status == MAELYS_OK) return 1;
fprintf(stderr, "%s: status %d\n", step, (int)status);
return 0;
}
static int expect(const maelys_datalog_solve_result_t *result,
const char *predicate, maelys_datalog_symbol_id_t first,
maelys_datalog_symbol_id_t second, const char *first_text,
const char *second_text, int expected) {
const maelys_datalog_term_t terms[] = {
{.kind = MAELYS_DATALOG_TERM_SYMBOL, .as.symbol = first},
{.kind = MAELYS_DATALOG_TERM_SYMBOL, .as.symbol = second},
};
bool present = false;
const size_t arity = second_text == NULL ? 1u : 2u;
if (!ok(maelys_datalog_query_solved_ground_fact(
result, predicate, terms, arity, &present), "query")) return 0;
printf("%s(%s", predicate, first_text);
if (second_text != NULL) printf(", %s", second_text);
printf(") = %s\n", present ? "true" : "false");
return present == (expected != 0);
}
int main(void) {
maelys_datalog_policy_set_t policy_set = {0};
maelys_datalog_diagnostic_t diagnostic = {0};
maelys_datalog_solve_result_t *result = NULL;
static maelys_datalog_edb_t edb;
static maelys_datalog_fact_t fact_pool[64];
int edb_ready = 0;
int exit_code = 1;
if (!ok(maelys_datalog_load_policy_inline_with_static_domain(
predicates, sizeof(predicates) / sizeof(predicates[0]),
"documents", "documents.main", source, strlen(source),
0u, &policy_set, &diagnostic), "load policy")) {
if (diagnostic.message[0] != '\0') fprintf(stderr, "%s\n", diagnostic.message);
goto cleanup;
}
maelys_datalog_ruleset_t *policy = &policy_set.policies[0];
if (!ok(maelys_datalog_edb_init(&edb, fact_pool, 64u,
&policy->symbols, &policy->registry), "initialize EDB")) goto cleanup;
edb_ready = 1;
maelys_datalog_symbol_id_t alice, bob, mallory, document;
if (!ok(maelys_datalog_edb_intern_runtime_symbol(&edb, "alice", &alice), "alice") ||
!ok(maelys_datalog_edb_intern_runtime_symbol(&edb, "bob", &bob), "bob") ||
!ok(maelys_datalog_edb_intern_runtime_symbol(&edb, "mallory", &mallory), "mallory") ||
!ok(maelys_datalog_edb_intern_runtime_symbol(&edb, "roadmap.pdf", &document), "document"))
goto cleanup;
if (!ok(maelys_datalog_edb_add_symbol_id_fact(&edb, "user", alice), "user alice") ||
!ok(maelys_datalog_edb_add_symbol_id_fact(&edb, "user", bob), "user bob") ||
!ok(maelys_datalog_edb_add_symbol_id_fact(&edb, "user", mallory), "user mallory") ||
!ok(maelys_datalog_edb_add_symbol_ids_fact(&edb, "owns", alice, document), "owns alice") ||
!ok(maelys_datalog_edb_add_symbol_ids_fact(&edb, "delegated", bob, document), "delegated bob") ||
!ok(maelys_datalog_edb_add_symbol_ids_fact(&edb, "owns", mallory, document), "owns mallory") ||
!ok(maelys_datalog_edb_add_symbol_id_fact(&edb, "blocked", mallory), "blocked mallory"))
goto cleanup;
if (!ok(maelys_datalog_edb_finalize(&edb), "finalize EDB") ||
!ok(maelys_datalog_solve_once(policy, &edb, &result), "solve"))
goto cleanup;
int answers_match = 1;
answers_match &= expect(result, "allow", alice, document, "alice", "roadmap.pdf", 1);
answers_match &= expect(result, "allow", bob, document, "bob", "roadmap.pdf", 1);
answers_match &= expect(result, "allow", mallory, document, "mallory", "roadmap.pdf", 0);
answers_match &= expect(result, "has_any_document", alice, 0, "alice", NULL, 1);
answers_match &= expect(result, "has_any_document", bob, 0, "bob", NULL, 0);
exit_code = answers_match ? 0 : 2;
cleanup:
maelys_datalog_solve_result_free(result);
if (edb_ready) maelys_datalog_edb_clear(&edb);
maelys_datalog_policy_set_clear(&policy_set);
return exit_code;
}The extra plumbing is the point of the comparison. Symbol IDs are meaningful only with this policy's symbol table; the fact pool belongs to the caller. Finalize the EDB before solving, release the result, then clear the EDB and policy set. Any API error must deny access.