C Low-level API
C Low-level API
Understand the transparent low-level C surface retained for existing consumers and engine extensions.Advanced alpha · source compatibility during migration
The original Maelys Datalog C surface remains available for existing applications, bindings, engine extensions, and low-level study. It exposes the engine's concrete ruleset, EDB, registry, symbol-table, parser, and solve-result types through one aggregation header.
#include <maelys_datalog.h>Why this surface still exists
The low-level API predates the opaque facade. It gave early consumers direct control over fixed-capacity storage, domain registries, runtime symbol interning, EDB finalization, and solve results. Existing bindings and native integrations still use parts of that model.
Removing it immediately would break those consumers and erase useful technical documentation. The migration strategy is therefore additive:
existing advanced API remains available during migration
stable opaque API recommended for new application integrations
engine internals become private as consumers stop depending on themDocument 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 |
Read the full low-level C program to inspect, compile, and run all five queries.
Compare with the Stable C API
The stable C program uses the same policy, seven request facts and five answers as this historical example. The C calls differ where the APIs expose different ownership and memory models:
| Task | C Stable API | C Low-level API |
|---|---|---|
| Declare the vocabulary | domain_register() with a public domain | Static predicate table passed to the loader |
| Load the policy | policy_load_inline() returns an opaque handle | load_policy_inline_with_static_domain() fills a concrete policy set |
| Supply the seven request facts | input_edb_create() and atomic ADD_FACTS() | Caller-owned fact pool, symbol interning, individual insertions, then edb_finalize() |
| Solve | session_solve_edb() reuses a prepared session | solve_once() takes the selected ruleset and finalized EDB |
| Ask the five ground queries | result_query() accepts typed values | query_solved_ground_fact() accepts interned symbol IDs |
| Release state | Free opaque result, input EDB and session handles | Free result, clear EDB and policy set |
Use the low-level version of this exact program to compare each stage without changing the policy or its test data.
Exposed areas
| Area | Current role | Documentation |
|---|---|---|
| Domain and predicate registries | Build and freeze a closed vocabulary with explicit predicate IDs and flags. | Registries |
| Manifest and ruleset loading | Load governed policy sets or parse policy source into transparent structures. | Manifest loading · Rulesets |
| Symbol table and EDB | Intern runtime symbols, manage caller-provided fact pools, finalize and reset an EDB. | Runtime EDB |
| Raw solver | Solve one transparent ruleset and finalized EDB, with low-level diagnostics. | Solving |
| Queries and explanations | Build ID-based ground facts, enumerate IDB facts, and inspect structured explanations. | Querying |
| Parser and audit helpers | Inspect source-level mechanics and internal validation paths. | Engine-oriented; not an application ABI |
The detailed pages remain part of this site and are not replaced by the opaque guide:
Stability boundary
The umbrella currently aggregates the version header and thirteen headers from the engine source tree. Syntactic visibility does not turn every exposed name into a supported ABI promise.
| Classification | Meaning |
|---|---|
| Stable public | Declared by <maelys/datalog.h> and usable from an installed consumer. |
| Advanced alpha | Exposed through <maelys_datalog.h> for compatibility and low-level use. |
| Experimental | Usable for evaluation, but its signature or semantics may evolve; Why-false is the current example. |
| Internal | Implementation detail under the source tree, even when the legacy umbrella historically makes part of it reachable. |
Who should use it
Use the advanced surface when you are:
- maintaining an existing integration that already depends on it;
- developing an in-tree language binding;
- testing the fresh low-level solve path as an oracle;
- contributing to the parser, solver, registries, or explanation machinery;
- intentionally controlling fixed-capacity storage at the structure level.
The advanced quick start walks through this surface step by step.
Use the stable C API instead when you are embedding Maelys Datalog in a new application and need loading, repeated solves, queries, enumeration, fingerprints, diagnostics, symbol rendering, Why-true and Why-false.
Responsibilities of advanced consumers
Advanced consumers must preserve more invariants themselves:
- install and freeze compatible predicate registries;
- use the ruleset's exact symbol and registry authority when constructing EDB facts;
- finalize an EDB before solving;
- avoid retaining pointers across resets or structure copies;
- keep result, ruleset, symbol-table, and session lifetimes consistent;
- treat all errors as fail-closed;
- re-test against every engine update.
The opaque facade performs or hides most of these operations for application integrators.
Migration direction
The intended migration is consumer-by-consumer, not a sudden removal:
- Add a stable-facade path next to the existing path.
- Compare decisions, fingerprints, diagnostics, and Why-true output.
- Move production traffic to opaque sessions.
- Keep the low-level path temporarily as a test oracle where useful.
- Remove direct structure access only after the replacement is proven.
The Python and WASM bindings are public integration surfaces in their own right, but they do not define the stable C ABI. See the bindings overview, Python API, and WASM API for their language-specific contracts.
Experimental features
The umbrella also reaches the structured form of Why-false
(maelys_datalog_explain_absent_solved_fact, caller-supplied limits, the
large maelys_datalog_why_false_explanation_t). Since 0.1.0-alpha.4 the
stable header exposes Why-false as text through
maelys_datalog_result_explain_false_text, and since 0.4.0 through the
caller-owned prepared explanations. The structured form is what the reference
backend uses underneath its backend ABI 3 callbacks, and its layout may
change with the engine. See the experimental API for what is
promised about Why-false today and what is not.