Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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):

StringMode
"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:Professor is inferred (from domain)
  • ex:cs101 rdf:type ex:Course is 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:schemaSource configured for the ledger (same- or cross-ledger). Both contribute to the bundle the reasoner sees.
  • Transient. Axioms never persist. The next query without ontology runs against only the configured bundle.
  • Reasoning mode still required. Inline axioms don’t enable reasoning on their own — set reasoning so 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:schemaSource if 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 the fluree-track-* headers) carry a top-level reasoning block:

    {
      "status": 200,
      "result": [...],
      "reasoning": {
        "capped": true,
        "capped_reason": "facts",
        "derived_facts": 1000000,
        "iterations": 3,
        "duration_ms": 12450
      }
    }
    
  • The same JSON rides the x-fdb-reasoning response header.

  • capped_reason is "facts", "time", "memory" or, for datalog, "iterations" (the fixpoint hit its round limit before converging).

  • rules_fired breaks 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):

  1. 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’s f:overrideControl on f:reasoningDefaults.
  2. Per ledger — f:reasoningMaxFacts / f:reasoningMaxSeconds / f:reasoningMaxMemoryMb in f:reasoningDefaults (see Setting groups).
  3. Server-wide — FLUREE_REASONING_MAX_FACTS / FLUREE_REASONING_MAX_SECONDS / FLUREE_REASONING_MAX_MEMORY_MB environment variables.

Performance considerations

ModeOverheadCaching
RDFSNegligible — query rewriting onlyN/A
OWL 2 QLNegligible — query rewriting onlyN/A
OWL 2 RLFirst query materializes derived facts; subsequent queries use cacheLRU cache (16 entries), keyed on database state + reasoning modes + budget
DatalogRule bodies run on the query executor; the first query materializes, later queries with the same rule set hit the cacheSame 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 deriving ex:isHighEarner from a hidden ex: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.

TopicPage
Conceptual introductionReasoning and inference
Custom inference rulesDatalog rules
Supported OWL & RDFS constructsOWL & RDFS reference
Ledger-wide reasoning configSetting groups
Reasoning under access policyPolicy in queries