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 |
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, and finally RDFS query rewriting is applied.
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 are the
baseline for every query. The per-query reasoning parameter can:
- Add modes — the query modes are merged with the defaults.
- Disable all —
"reasoning": "none"overrides the defaults entirely.
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. -
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.
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.
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 |