C Stable API
Runtime EDB
Build complete request fact sets with the stable opaque input buffer.The stable facade accepts runtime facts either as one borrowed array passed to
maelys_datalog_session_solve(), or in an opaque, reusable
maelys_datalog_input_edb_t buffer passed to
maelys_datalog_session_solve_edb(). These are two input paths for a complete
request snapshot, not incremental updates to the previous solve. See the
EDB concept for the distinction between request facts and
policy facts.
Types
Defined on this page
| Type | Purpose |
|---|---|
maelys_datalog_input_edb_t | Opaque, reusable request-fact buffer. |
maelys_datalog_fact_t | One predicate and its typed terms, used in a batch. |
Used from other pages
These types are defined on the linked pages; this page uses them in the roles below.
| Type | Use on this page | Defined in |
|---|---|---|
maelys_datalog_diagnostic_t | Optional output argument receiving insertion details. | Errors |
Functions
Start with the common operations. Capacity and memory management are useful
when the application needs explicit limits or supplies its own storage;
inspection functions observe an existing buffer. All are declared in
<maelys/datalog.h>.
Common operations
| Function | Purpose |
|---|---|
maelys_datalog_input_edb_create() | Allocate a default-capacity EDB. |
maelys_datalog_input_edb_add_fact() | Append one typed fact. |
maelys_datalog_input_edb_add_facts() | Append one atomic batch. |
maelys_datalog_input_edb_clear() | Start a fresh request in the same buffer. |
maelys_datalog_input_edb_free() | End the handle lifetime. |
Capacity and memory management
| Function | Purpose |
|---|---|
maelys_datalog_input_edb_create_with_capacity() | Allocate an EDB with explicit, fixed fact and text capacities. |
maelys_datalog_input_edb_storage_requirements() / maelys_datalog_input_edb_init() | Place the EDB handle in caller-owned aligned memory. |
Inspection functions
| Function | Purpose |
|---|---|
maelys_datalog_input_edb_count() | Count stored raw entries. |
maelys_datalog_input_edb_text_usage() | Inspect committed string-storage occupancy. |
maelys_datalog_input_edb_view() | Borrow committed raw facts until mutation. |
Function-like macro builders
These C11 conveniences are distinct from the public EDB functions below. A macro with () accepts arguments; it is not an exported function.
| Function-like macro | Purpose |
|---|---|
MAELYS_DATALOG_SYMBOL(value) | Marks a borrowed C string as a symbol-valued term. |
MAELYS_DATALOG_BOOL(value) | Requests boolean rather than integer conversion for a C expression. |
MAELYS_DATALOG_FACT(...) | Builds one borrowed checked descriptor for an atomic batch. |
MAELYS_DATALOG_ADD_FACT(edb, diagnostic, ...) | Adds one typed fact through the input EDB API. |
MAELYS_DATALOG_ADD_FACTS(edb, diagnostic, ...) | Adds one batch through the input EDB API. |
Input EDB handle
typedef struct maelys_datalog_input_edb maelys_datalog_input_edb_t;Input fact type
typedef struct {
const char *predicate;
size_t arity;
maelys_datalog_value_t terms[MAELYS_DATALOG_PUBLIC_MAX_TERMS];
} maelys_datalog_fact_t;Common use
The quickstart follows this lifecycle: create an input buffer, add request facts, solve, then release the buffer. For later requests, clear and refill the same buffer. The default buffer has fixed capacities and never grows automatically.
Create an input buffer
maelys_datalog_status_t
maelys_datalog_input_edb_create(
maelys_datalog_input_edb_t ** out_edb
);maelys_datalog_status_tReturns OK with a new buffer; release it with input_edb_free.
Add input facts
Add one fact through the typed call below, or submit a whole request batch when partial insertion would be unsafe. Successful insertion copies predicate and symbol strings, so the caller can release its input strings afterward. Domain membership is checked at solve time.
maelys_datalog_status_t
maelys_datalog_input_edb_add_fact(
maelys_datalog_input_edb_t * edb,
const char * predicate,
const maelys_datalog_value_t * terms,
size_t arity,
maelys_datalog_diagnostic_t * out_diagnostic
);maelys_datalog_status_tReturns OK after copying the fact; on failure that fact is not inserted.
maelys_datalog_status_t
maelys_datalog_input_edb_add_facts(
maelys_datalog_input_edb_t * edb,
const maelys_datalog_fact_t * facts,
size_t fact_count,
maelys_datalog_diagnostic_t * out_diagnostic
);maelys_datalog_status_tThis call validates the complete batch before storing it; a failed batch adds no facts.
maelys_datalog_status_t status = maelys_datalog_input_edb_add_facts(
edb, facts, fact_count, &diagnostic);Without the macros: typed arrays
Use the typed API for C++, FFI, large batches or an array populated
dynamically. Assume edb is initialized, diagnostic is initialized and
rc is a status. The examples below are two alternative ways to add input:
const maelys_datalog_value_t terms[] = {
{.kind = MAELYS_DATALOG_VALUE_SYMBOL, .as.symbol = "alice"},
{.kind = MAELYS_DATALOG_VALUE_SYMBOL, .as.symbol = "roadmap.pdf"},
};
rc = maelys_datalog_input_edb_add_fact(
edb, "owns", terms, 2u, &diagnostic);
if (rc != MAELYS_DATALOG_STATUS_OK) goto cleanup;const maelys_datalog_fact_t facts[] = {
{.predicate = "user", .arity = 1u, .terms = {
{.kind = MAELYS_DATALOG_VALUE_SYMBOL, .as.symbol = "alice"},
}},
{.predicate = "owns", .arity = 2u, .terms = {
{.kind = MAELYS_DATALOG_VALUE_SYMBOL, .as.symbol = "alice"},
{.kind = MAELYS_DATALOG_VALUE_SYMBOL, .as.symbol = "roadmap.pdf"},
}},
};
rc = maelys_datalog_input_edb_add_facts(
edb, facts, sizeof(facts) / sizeof(facts[0]), &diagnostic);
if (rc != MAELYS_DATALOG_STATUS_OK) goto cleanup;Both paths use the same native validation and copying rules. The typed batch
function accepts a zero-length batch; the variadic macro requires at least one
FACT. A FACT descriptor is not a maelys_datalog_fact_t initializer.
These helpers only build input: call maelys_datalog_session_solve_edb()
after successful insertion to evaluate the request.
Reuse and release
input_edb_clear() starts another request in the same buffer; it is not a
secure erase. At the end of the handle's lifetime, call input_edb_free().
maelys_datalog_status_t
maelys_datalog_input_edb_clear(
maelys_datalog_input_edb_t * edb
);maelys_datalog_status_tReturns OK after resetting the buffer for another complete request; this is not secure erasure.
maelys_datalog_status_t
maelys_datalog_input_edb_free(
maelys_datalog_input_edb_t * edb
);maelys_datalog_status_tReleases the handle; caller-owned storage supplied to init remains the caller's responsibility.
Example — document access
For the policy from the quickstart, build the same seven-fact request: three users, two ownership facts, one delegation and one blocked user. The domain declares blocked/1 as EDB, so the last fact is request data, not policy source. Both tabs submit one atomic batch to an already initialized EDB.
request-facts.c
#include <maelys/datalog.h>
maelys_datalog_status_t add_document_request(
maelys_datalog_input_edb_t *edb, maelys_datalog_diagnostic_t *diagnostic) {
const maelys_datalog_fact_t facts[] = {
{.predicate = "user", .arity = 1u, .terms = {{.kind = MAELYS_DATALOG_VALUE_SYMBOL, .as.symbol = "alice"}}},
{.predicate = "user", .arity = 1u, .terms = {{.kind = MAELYS_DATALOG_VALUE_SYMBOL, .as.symbol = "bob"}}},
{.predicate = "user", .arity = 1u, .terms = {{.kind = MAELYS_DATALOG_VALUE_SYMBOL, .as.symbol = "mallory"}}},
{.predicate = "owns", .arity = 2u, .terms = {
{.kind = MAELYS_DATALOG_VALUE_SYMBOL, .as.symbol = "alice"},
{.kind = MAELYS_DATALOG_VALUE_SYMBOL, .as.symbol = "roadmap.pdf"}}},
{.predicate = "delegated", .arity = 2u, .terms = {
{.kind = MAELYS_DATALOG_VALUE_SYMBOL, .as.symbol = "bob"},
{.kind = MAELYS_DATALOG_VALUE_SYMBOL, .as.symbol = "roadmap.pdf"}}},
{.predicate = "owns", .arity = 2u, .terms = {
{.kind = MAELYS_DATALOG_VALUE_SYMBOL, .as.symbol = "mallory"},
{.kind = MAELYS_DATALOG_VALUE_SYMBOL, .as.symbol = "roadmap.pdf"}}},
{.predicate = "blocked", .arity = 1u, .terms = {{.kind = MAELYS_DATALOG_VALUE_SYMBOL, .as.symbol = "mallory"}}},
};
return maelys_datalog_input_edb_add_facts(
edb, facts, sizeof(facts) / sizeof(facts[0]), diagnostic);
}The batch is atomic: none of its seven facts is inserted if validation fails. A later solve reads exactly this input snapshot; it does not inherit facts from a previous request.
Capacity and memory
Use these alternatives to default creation when the application needs explicit fact and text limits or must provide the buffer's memory itself.
Explicit capacities
create_with_capacity() allocates a buffer with the requested fixed limits.
Like the default buffer, it never grows automatically.
maelys_datalog_status_t
maelys_datalog_input_edb_create_with_capacity(
size_t fact_capacity,
size_t text_capacity,
maelys_datalog_input_edb_t ** out_edb
);maelys_datalog_status_tReturns OK with the requested fixed capacities; the buffer never grows.
Caller-owned storage
Call storage_requirements() to obtain the required size and alignment, then
pass suitably aligned memory to init().
maelys_datalog_status_t
maelys_datalog_input_edb_storage_requirements(
size_t fact_capacity,
size_t text_capacity,
size_t * out_bytes,
size_t * out_alignment
);maelys_datalog_status_tReturns bytes and alignment for the requested capacities; no EDB is created.
maelys_datalog_status_t
maelys_datalog_input_edb_init(
void * storage,
size_t storage_bytes,
size_t fact_capacity,
size_t text_capacity,
maelys_datalog_input_edb_t ** out_edb
);maelys_datalog_status_tReturns OK with an EDB handle borrowing the supplied aligned memory.
Both creation paths use the common insertion and solve operations. Finish
every initialized handle with input_edb_free(); this call does not free
caller-owned storage, which remains the application's responsibility.
Inspection
These accessors inspect one already populated input buffer without solving. They are useful for admission diagnostics and for native adapters such as event windows. They do not change the EDB or allocate memory.
Count the stored entries before inspecting their text occupancy or content.
input_edb_count() includes duplicates; it does not count deduplicated facts.
maelys_datalog_status_t
maelys_datalog_input_edb_count(
const maelys_datalog_input_edb_t * edb,
size_t * out_count
);maelys_datalog_status_tReturns the number of stored raw entries, including duplicates.
When an input approaches its fixed text budget, inspect only the bytes already committed to this EDB; this is not a prediction of solve memory.
maelys_datalog_status_t
maelys_datalog_input_edb_text_usage(
const maelys_datalog_input_edb_t * edb,
size_t * out_used,
size_t * out_capacity
);maelys_datalog_status_tReturns OK and leaves both outputs unchanged on error. This measures committed input text only, not engine or candidate scratch memory.
Use the view to audit the order and content accepted into the buffer before solve. It is borrowed, not a copy.
maelys_datalog_status_t
maelys_datalog_input_edb_view(
const maelys_datalog_input_edb_t * edb,
const maelys_datalog_fact_t ** out_facts,
size_t * out_count
);maelys_datalog_status_tReturns OK and leaves outputs unchanged on error. The view lasts until the next successful mutation or free of this EDB.
Do not modify the borrowed array or its strings. Do not pass the view back into a mutating operation on the same buffer; copying to a different buffer is allowed. clear() resets text usage to zero, while a rejected append preserves the reported usage. Free text alone does not guarantee that the next solve fits every other engine limit.
Macro builder reference
MAELYS_DATALOG_SYMBOL(value)Marks a borrowed C string as a symbol-valued term.
Builds a symbol-value initializer. It neither copies the string nor inserts a fact.
A maelys_datalog_value_t initializer; the consuming call validates and copies when appropriate.
maelys_datalog_value_t user = MAELYS_DATALOG_SYMBOL("alice");MAELYS_DATALOG_BOOL(value)Requests boolean rather than integer conversion for a C expression.
Casts the expression to _Bool so the C11 typed builders select BOOLEAN. It inserts no fact.
A _Bool expression. It inserts no fact.
maelys_datalog_status_t rc = MAELYS_DATALOG_ADD_FACT(
edb, diagnostic, "enabled", MAELYS_DATALOG_BOOL(1));MAELYS_DATALOG_FACT(...)Builds one borrowed checked descriptor for an atomic batch.
Builds one borrowed, checked fact descriptor for a later atomic batch. It inserts no fact.
A temporary fact descriptor; it inserts nothing until ADD_FACTS is called.
maelys_datalog_status_t rc = MAELYS_DATALOG_ADD_FACTS(
edb, diagnostic,
MAELYS_DATALOG_FACT("owns", "alice", "roadmap.pdf"));MAELYS_DATALOG_ADD_FACT(edb, diagnostic, ...)Adds one typed fact through the input EDB API.
Converts and appends one fact. Separate successful calls remain inserted if a later call fails.
A public status; one failed insertion leaves that fact absent but earlier successful calls remain.
maelys_datalog_status_t rc = MAELYS_DATALOG_ADD_FACT(
edb, diagnostic, "owns", "alice", "roadmap.pdf");MAELYS_DATALOG_ADD_FACTS(edb, diagnostic, ...)Adds one batch through the input EDB API.
Converts and submits the descriptors in one atomic batch: failure inserts none of its facts.
A public status; a failed batch inserts none of its facts.
maelys_datalog_status_t rc = MAELYS_DATALOG_ADD_FACTS(
edb, diagnostic,
MAELYS_DATALOG_FACT("user", "alice"),
MAELYS_DATALOG_FACT("owns", "alice", "roadmap.pdf"));Using the C11 builders
The optional macros are declared
in <maelys/datalog_builders.h>, also included by <maelys/datalog.h>.
They are source conveniences, not a new ABI.
Assume edb is a successfully created or caller-initialized input buffer,
diagnostic is a maelys_datalog_diagnostic_t, and rc is a status.
One fact can be added directly:
rc = MAELYS_DATALOG_ADD_FACT(
edb, &diagnostic, "owns", "alice", "roadmap.pdf");
if (rc != MAELYS_DATALOG_STATUS_OK) goto cleanup;For a small batch written in the program, use one call:
rc = MAELYS_DATALOG_ADD_FACTS(
edb, &diagnostic,
MAELYS_DATALOG_FACT("user", "alice"),
MAELYS_DATALOG_FACT("owns", "alice", "roadmap.pdf"),
MAELYS_DATALOG_FACT("blocked", "mallory")
);
if (rc != MAELYS_DATALOG_STATUS_OK) goto cleanup;| Operation | What it does | On failure |
|---|---|---|
FACT(predicate, ...) | Constructs a checked descriptor; inserts nothing. | Range errors are retained for the enclosing batch call. Unsupported types fail compilation. |
ADD_FACT(edb, diagnostic, predicate, ...) | Appends one fact. | That fact is not added; earlier successful calls remain. |
ADD_FACTS(edb, diagnostic, FACT(...), ...) | Counts the facts and calls the native batch function once. | No fact from this batch is added; existing facts and storage remain unchanged. |
The table abbreviates the common MAELYS_DATALOG_ prefix. These macros
support C11, zero to four terms per fact, and at least one fact per batch.
They accept the same values:
| C value | Datalog term |
|---|---|
char * or const char * | Symbol, copied into the EDB on successful insertion. |
| Standard signed or unsigned integer | Integer, checked against INT64_MIN..INT64_MAX. |
_Bool or MAELYS_DATALOG_BOOL(expression) | Boolean. Plain C11 true and comparisons are int: wrap them to request boolean semantics. |
For example, MAELYS_DATALOG_FACT("enabled", "alice", MAELYS_DATALOG_BOOL(1))
builds a symbol and a boolean; MAELYS_DATALOG_FACT("quota", "alice", 1024)
builds a symbol and an integer. Their predicates still need to be declared
in the domain before solving. Floats, arbitrary pointers and structs are
rejected at compilation.
Each argument is evaluated once, but evaluation order is unspecified:
do not put mutually dependent side effects in different arguments.
FACT borrows strings until submission; ADD_FACTS copies them into native
storage before returning successfully. It uses automatic temporary arrays
proportional to the written batch, no extra malloc, and no heap fallback.
This says nothing about an allocating EDB constructor or Python allocations.
Range failures and native shape/storage failures are atomic. Diagnostic fact and term indices are zero-based within the submitted batch. Domain membership, declared arities and policy-specific limits are checked at solve time, not by these macros.