Query-Time Reasoning
This page covers how to enable and use reasoning in your queries. For background concepts see Reasoning and inference; for the full list of supported OWL/RDFS constructs see the OWL & RDFS reference.
The reasoning parameter
Add a "reasoning" key to any JSON-LD query to control which inference modes
are active:
Single mode
{
"@context": {"ex": "http://example.org/"},
"select": ["?s"],
"where": {"@id": "?s", "@type": "ex:Person"},
"reasoning": "rdfs"
}
Multiple modes
{
"select": ["?s"],
"where": {"@id": "?s", "@type": "ex:Person"},
"reasoning": ["rdfs", "owl2rl"]
}
Disable reasoning
{
"select": ["?s"],
"where": {"@id": "?s", "@type": "ex:Person"},
"reasoning": "none"
}
Use "none" to override any view- or ledger-wide reasoning defaults for
this query (with no defaults configured, it is a no-op affirmation).
Valid mode strings
Each mode has exactly one accepted string (case-insensitive):
| String | Mode |
|---|---|
"rdfs" | RDFS subclass/subproperty expansion |
"owl2ql" | OWL 2 QL query rewriting (includes RDFS) |
"owl2rl" | OWL 2 RL forward-chaining materialization |
"owl-datalog" | OWL 2 RL plus datalog rule execution (superset of owl2rl) |
"datalog" | Datalog rule execution |
"none" | Disable all reasoning |
Inside opts
The reasoning keys (reasoning, rules, ontology, reasoningBudget and
reasoningTime) can also go inside the query’s opts object. Each key is
read from the top level when it is there and from opts otherwise, so
{
"select": ["?s"],
"where": {"@id": "?s", "ex:p": "?o"},
"reasoningBudget": {"maxFacts": 20000000},
"opts": {"reasoning": "datalog"}
}
runs datalog under that budget. When both carry the same key, the top level’s value is used.
Default behavior
Reasoning is opt-in. When the reasoning key is absent from a query, no
reasoning runs — even if the data contains rdfs:subClassOf /
rdfs:subPropertyOf hierarchies. Plain queries match asserted triples only,
pay no reasoning-prep cost, and behave like other SPARQL engines under simple
entailment.
To enable reasoning without setting it per query, configure a default at the
view level (GraphDb::with_reasoning(...) in the Rust API) or in the ledger
configuration graph (reasoningModes). To override such a default for a
single query, use "reasoning": "none".
Behavior change: versions prior to this release auto-enabled RDFS when a schema hierarchy existed in the data (though for imported ledgers the auto-detection often silently failed). If you relied on automatic subclass / subproperty expansion, add
"reasoning": "rdfs"to your queries or set a ledger default.
Examples
The examples below assume this schema and data have been transacted:
{
"@context": {
"ex": "http://example.org/",
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
"owl": "http://www.w3.org/2002/07/owl#"
},
"insert": [
{"@id": "ex:Student", "rdfs:subClassOf": {"@id": "ex:Person"}},
{"@id": "ex:GradStudent", "rdfs:subClassOf": {"@id": "ex:Student"}},
{"@id": "ex:alice", "@type": "ex:GradStudent", "ex:name": "Alice"},
{"@id": "ex:bob", "@type": "ex:Person", "ex:name": "Bob"},
{"@id": "ex:livesWith", "@type": "owl:SymmetricProperty"},
{"@id": "ex:alice", "ex:livesWith": {"@id": "ex:bob"}},
{"@id": "ex:hasAncestor", "@type": "owl:TransitiveProperty"},
{"@id": "ex:carol", "ex:hasAncestor": {"@id": "ex:dave"}},
{"@id": "ex:dave", "ex:hasAncestor": {"@id": "ex:eve"}},
{"@id": "ex:hasMother", "owl:inverseOf": {"@id": "ex:childOf"}},
{"@id": "ex:frank", "ex:hasMother": {"@id": "ex:grace"}}
]
}
RDFS: subclass expansion
Query for all ex:Person instances — Alice is returned even though she was
only typed as ex:GradStudent:
{
"@context": {"ex": "http://example.org/"},
"select": ["?name"],
"where": {
"@id": "?s", "@type": "ex:Person",
"ex:name": "?name"
},
"reasoning": "rdfs"
}
Result: ["Alice", "Bob"]
Without reasoning (or with "reasoning": "none"), only "Bob" is returned
because Alice’s explicit type is GradStudent, not Person.
OWL 2 RL: symmetric properties
Query who lives with Bob — Alice is inferred even though only
alice livesWith bob was asserted:
{
"@context": {"ex": "http://example.org/"},
"select": ["?who"],
"where": {"@id": "ex:bob", "ex:livesWith": "?who"},
"reasoning": "owl2rl"
}
Result: ["ex:alice"]
OWL 2 RL: transitive properties
Query for all ancestors of Carol — Eve is inferred through transitivity:
{
"@context": {"ex": "http://example.org/"},
"select": ["?ancestor"],
"where": {"@id": "ex:carol", "ex:hasAncestor": "?ancestor"},
"reasoning": "owl2rl"
}
Result: ["ex:dave", "ex:eve"]
OWL 2 QL: inverse properties
Query childOf — inferred from the hasMother / inverseOf declaration:
{
"@context": {"ex": "http://example.org/"},
"select": ["?child"],
"where": {"@id": "ex:grace", "ex:childOf": "?child"},
"reasoning": "owl2ql"
}
Result: ["ex:frank"]
OWL 2 RL: domain and range inference
If your schema declares rdfs:domain and rdfs:range:
{
"insert": [
{"@id": "ex:teaches", "rdfs:domain": {"@id": "ex:Professor"},
"rdfs:range": {"@id": "ex:Course"}},
{"@id": "ex:alice", "ex:teaches": {"@id": "ex:cs101"}}
]
}
Then with "reasoning": "owl2rl":
ex:alice rdf:type ex:Professoris inferred (from domain)ex:cs101 rdf:type ex:Courseis inferred (from range)
Combined modes
Enable RDFS + OWL 2 RL + Datalog together:
{
"select": ["?s"],
"where": {"@id": "?s", "@type": "ex:Person"},
"reasoning": ["rdfs", "owl2rl", "datalog"],
"rules": [
{
"@context": {"ex": "http://example.org/"},
"where": {"@id": "?p", "ex:parent": {"ex:parent": "?gp"}},
"insert": {"@id": "?p", "ex:grandparent": {"@id": "?gp"}}
}
]
}
OWL 2 RL facts are materialized first, then Datalog rules run over the
combined base + OWL data. OWL 2 RL materializes everything RDFS and OWL 2 QL
query rewriting would add, so with owl2rl on the rewrite is skipped.
Rule bodies don’t use the rdfs and owl2ql rewrites. Under either mode
without owl2rl, a program with a rule that negates or aggregates is
refused: the rule would read an entailment the query sees as absent. Add
owl2rl. See Datalog rules.
A rule that fails to parse or validate — a stored f:rule as much as a
query-time one — fails the query with an error naming the rule and the
offending construct. Rules are never skipped silently, because a reasoning
query answered over a partial rule set is wrong in a way the caller cannot
see. See Datalog rules.
SPARQL
In SPARQL queries, reasoning is controlled via the Fluree-specific
PRAGMA reasoning directive. Property paths (+, *, ^) provide a
complementary mechanism for navigating transitive and inverse relationships
directly in the query pattern — see SPARQL for details.
Inline ontology per query
In addition to ontology axioms stored in the ledger (via
f:schemaSource), a query can supply inline ontology axioms
via the top-level ontology field. The axioms are used only for
this query’s reasoning pass and never persist.
{
"@context": {"ex": "http://example.org/ns/"},
"select": "?name",
"where": {"@id": "?p", "@type": "ex:Person", "ex:name": "?name"},
"reasoning": "rdfs",
"ontology": {
"@context": {
"ex": "http://example.org/ns/",
"rdfs": "http://www.w3.org/2000/01/rdf-schema#"
},
"@id": "ex:Employee",
"rdfs:subClassOf": {"@id": "ex:Person"}
}
}
Semantics:
- Additive, not replacing. Inline axioms layer on top of
whatever
f:schemaSourceconfigured for the ledger (same- or cross-ledger). Both contribute to the bundle the reasoner sees. - Transient. Axioms never persist. The next query without
ontologyruns against only the configured bundle. - Reasoning mode still required. Inline axioms don’t enable
reasoning on their own — set
reasoningso the engine actually uses them. - Namespace-scoped. IRIs the snapshot already knows reuse their codes; previously-unseen IRIs allocate request-scoped codes that are discarded with the response — the on-disk dictionary is untouched.
- No audit trail. Without persistence, “which axioms drove
which result” can’t be reconstructed from history. Store
long-lived ontologies in a graph and reference via
f:schemaSourceif auditability matters.
Use cases that fit well: testing a candidate ontology before committing it to the ledger, per-tenant axiom layers, exploratory analytics with hypothetical sub-class chains.
Interaction with ledger configuration
If f:reasoningDefaults is set in the ledger configuration graph (see
Setting groups), those modes apply to
every query that does not set reasoning itself. The per-query reasoning
parameter can:
- Replace them — a query that sets
reasoningruns its own modes instead. The two are not merged, so a query that wants a default mode and another lists both. - Disable all —
"reasoning": "none"runs with no reasoning.
The f:overrideControl setting on the ledger config determines whether
query-time overrides are allowed. See
Override control for details.
Materialization budget
Materialization — OWL 2 RL and datalog rules alike — runs under a budget
(default: 1,000,000 derived facts / 30 seconds). The limits are checked as
work is dispatched rather than once a round, so a long round is bounded far
more tightly than it used to be. They are not checked between every derived
fact everywhere: an OWL 2 RL property chain, and the rules that produce
owl:sameAs, each run to completion before the next check, so a single
high-fan-out input can still carry a round past a limit before it is noticed.
There is a third limit on the bytes those derived facts occupy. It has no
default of its own: it is derived from the fact ceiling, at an allowance well
above what an ordinary fact costs, so the fact cap is what binds on normal
data and the memory cap only fires when facts are abnormally large — long
IRIs, or big string and JSON literals. Raising maxFacts raises it with them.
Both limits count only derived facts, not the base data the fixpoint reads. When the closure exceeds the budget it is
capped: the query still answers, but over an incomplete closure —
results may be missing entailments. A capped run is therefore surfaced, not
just logged:
-
Tracked responses (
"opts": {"meta": true}or thefluree-track-*headers) carry a top-levelreasoningblock:{ "status": 200, "result": [...], "reasoning": { "capped": true, "capped_reason": "facts", "derived_facts": 1000000, "iterations": 3, "duration_ms": 12450 } } -
The same JSON rides the
x-fdb-reasoningresponse header. -
capped_reasonis"facts","time","memory"or, for datalog,"iterations"(the fixpoint hit its round limit before converging). -
rules_firedbreaks the tally down per rule. It counts derived facts per rule, not rule applications: a rule that matches a thousand rows and derives one new fact from them counts once. -
When datalog rules are capped,
capped_rulesnames the rules that were running when the cap fired andpending_rulesthe rules it kept from running. When a rule callsNOW()orTODAY(),evaluation_timeis the time they returned (see Datalog rules). -
The server logs a WARN per capped materialization.
The budget is configurable at three levels (highest precedence first):
- Per query — JSON-LD
"reasoningBudget": {"maxFacts": 20000000, "maxSeconds": 300, "maxMemoryMb": 512}, or SPARQL# PRAGMA reasoning-max-facts: 20000000/# PRAGMA reasoning-max-seconds: 300/# PRAGMA reasoning-max-memory-mb: 512. Subject to the ledger’sf:overrideControlonf:reasoningDefaults. - Per ledger —
f:reasoningMaxFacts/f:reasoningMaxSeconds/f:reasoningMaxMemoryMbinf:reasoningDefaults(see Setting groups). - Server-wide —
FLUREE_REASONING_MAX_FACTS/FLUREE_REASONING_MAX_SECONDS/FLUREE_REASONING_MAX_MEMORY_MBenvironment variables.
The query’s own limits apply too. Materialization runs on the query’s behalf, so cancelling the query — a client disconnect, or a query timeout — stops OWL 2 RL and datalog rule bodies mid-run, and the query fails with the cancellation rather than answering over a partial closure. The work counts toward the query’s fuel: OWL 2 RL charges 0.001 fuel for each fact a round processes, and rule bodies are charged like any query, including the property paths a transitive-closure rule runs as. Only the query that computes a materialization pays for it; queries answered from the cached result do not, and a run that was cancelled or ran out of fuel caches nothing.
Performance considerations
| Mode | Overhead | Caching |
|---|---|---|
| RDFS | Negligible — query rewriting only | N/A |
| OWL 2 QL | Negligible — query rewriting only | N/A |
| OWL 2 RL | First query materializes derived facts; subsequent queries use cache | LRU cache (16 entries), keyed on database state + reasoning modes + budget |
| Datalog | Rule bodies run on the query executor; the first query materializes, later queries with the same rule set hit the cache | Same LRU cache as OWL 2 RL, keyed additionally on a content hash of the rule set (stored and query-time rules) |
Tips:
- Start with RDFS if you only need class/property hierarchies — it has virtually zero overhead.
- Use OWL 2 QL when you also need inverse properties and domain/range inference but want to stay in the query-rewriting approach.
- Use OWL 2 RL when you need the full rule set (transitive, symmetric,
functional properties,
owl:sameAs, restrictions, property chains). - The materialization cache is invalidated when the underlying data changes (new transactions), so the first query after a write will re-materialize.
Reasoning under access policy
Reasoning composes with view policy, but mind the contract when both are on:
- OWL 2 QL and RDFS rewrite the query and execute under your identity, so they are filtered like any normal query.
- OWL 2 RL and datalog materialize derived facts into the query overlay; those facts are filtered by the same per-flake view policy as base data. A derived flake you may not view is dropped.
- The engine filters a derived fact by its own
(subject, predicate, object)— not by the base facts it was derived from. A rule or ontology axiom can therefore re-express hidden data under a viewable predicate (e.g.ex:ssn rdfs:subPropertyOf ex:identifier, or a rule derivingex:isHighEarnerfrom a hiddenex:salary) and the derived value will surface. - Materialized facts are computed once over all of the ledger’s data and shared by every caller; the policy applies when rows are read, not while facts are derived.
- The two approaches can therefore answer the same query differently under the same policy. With
ex:ssn rdfs:subPropertyOf ex:identifierandex:ssnhidden, a query forex:identifierreturns nothing under"rdfs", because the rewritten query scans the hiddenex:ssn. Under"owl2rl"it returns the value, because the derivedex:identifierfact is itself viewable.
If you run reasoning under a non-root policy, your policy must cover the derived properties and classes — deny them, or use default-allow: false so anything not explicitly allowed (including reasoning-introduced predicates) stays hidden.
Query-time rules are admin-only. Under a non-root view policy, caller-supplied rules are stripped before execution — a restricted caller cannot inject inference rules (a rule with a viewable head could launder hidden data). Database-stored f:rule definitions and OWL/RDFS reasoning are administrator-controlled and still apply. See Policy in queries → Reasoning.
Related pages
| Topic | Page |
|---|---|
| Conceptual introduction | Reasoning and inference |
| Custom inference rules | Datalog rules |
| Supported OWL & RDFS constructs | OWL & RDFS reference |
| Ledger-wide reasoning config | Setting groups |
| Reasoning under access policy | Policy in queries |