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:

DATALOG
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 queryAnswer
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:

CODE
#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.