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.

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

CODE
existing advanced API     remains available during migration
stable opaque API         recommended for new application integrations
engine internals          become private as consumers stop depending on them

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

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:

TaskC Stable APIC Low-level API
Declare the vocabularydomain_register() with a public domainStatic predicate table passed to the loader
Load the policypolicy_load_inline() returns an opaque handleload_policy_inline_with_static_domain() fills a concrete policy set
Supply the seven request factsinput_edb_create() and atomic ADD_FACTS()Caller-owned fact pool, symbol interning, individual insertions, then edb_finalize()
Solvesession_solve_edb() reuses a prepared sessionsolve_once() takes the selected ruleset and finalized EDB
Ask the five ground queriesresult_query() accepts typed valuesquery_solved_ground_fact() accepts interned symbol IDs
Release stateFree opaque result, input EDB and session handlesFree 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

AreaCurrent roleDocumentation
Domain and predicate registriesBuild and freeze a closed vocabulary with explicit predicate IDs and flags.Registries
Manifest and ruleset loadingLoad governed policy sets or parse policy source into transparent structures.Manifest loading · Rulesets
Symbol table and EDBIntern runtime symbols, manage caller-provided fact pools, finalize and reset an EDB.Runtime EDB
Raw solverSolve one transparent ruleset and finalized EDB, with low-level diagnostics.Solving
Queries and explanationsBuild ID-based ground facts, enumerate IDB facts, and inspect structured explanations.Querying
Parser and audit helpersInspect 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.

ClassificationMeaning
Stable publicDeclared by <maelys/datalog.h> and usable from an installed consumer.
Advanced alphaExposed through <maelys_datalog.h> for compatibility and low-level use.
ExperimentalUsable for evaluation, but its signature or semantics may evolve; Why-false is the current example.
InternalImplementation 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:

  1. Add a stable-facade path next to the existing path.
  2. Compare decisions, fingerprints, diagnostics, and Why-true output.
  3. Move production traffic to opaque sessions.
  4. Keep the low-level path temporarily as a test oracle where useful.
  5. 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.