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

Datalog Rules

Datalog rules let you define custom inference logic that goes beyond what OWL and RDFS provide. Rules are expressed in a familiar JSON-LD pattern syntax with where (conditions) and insert (conclusions) clauses, and execute in a fixpoint loop that can chain rules together.

For background concepts see Reasoning and inference; for enabling reasoning in queries see Query-time reasoning.

Quick example

Infer a grandparent relationship from two parent hops:

{
  "@context": {"ex": "http://example.org/"},
  "select": ["?gp"],
  "where": {"@id": "ex:alice", "ex:grandparent": "?gp"},
  "reasoning": "datalog",
  "rules": [
    {
      "@context": {"ex": "http://example.org/"},
      "where": {"@id": "?person", "ex:parent": {"ex:parent": "?gp"}},
      "insert": {"@id": "?person", "ex:grandparent": {"@id": "?gp"}}
    }
  ]
}

The rule says: “For any ?person whose parent has a parent ?gp, insert that ?person has a grandparent ?gp.” The query then finds Alice’s grandparents using the inferred facts.

Rule format

Each rule is a JSON object with three parts:

KeyRequiredDescription
@contextYesJSON-LD context for expanding compact IRIs
whereYesPattern(s) that must match for the rule to fire
insertYesPattern(s) of new facts to derive when the rule fires
@idNoOptional name/IRI for the rule (for documentation/debugging)

Where clause

The where clause defines the conditions under which the rule fires. It follows the same pattern syntax as JSON-LD queries.

Single pattern:

"where": {"@id": "?person", "ex:parent": "?parent"}

Multiple patterns (implicit join on shared variables):

"where": [
  {"@id": "?person", "ex:parent": "?parent"},
  {"@id": "?parent", "ex:name": "?parentName"}
]

Nested patterns (shorthand for multi-hop traversal):

"where": {"@id": "?person", "ex:parent": {"ex:parent": "?gp"}}

This is equivalent to two patterns joined on an intermediate variable.

With filters:

"where": [
  {"@id": "?person", "ex:age": "?age"},
  ["filter", "(>= ?age 65)"]
]

Insert clause

The insert clause defines what facts to produce for each set of matching variable bindings.

"insert": {"@id": "?person", "ex:grandparent": {"@id": "?gp"}}
  • Variables (?person, ?gp) are replaced with the bound values from where.
  • Use {"@id": "?var"} for IRI/entity values; use "?var" directly for literal values.
  • Multiple triples can be generated from a single insert pattern.

Providing rules

Rules can be provided in two ways:

1. Query-time rules

Pass rules directly in the query via the rules array. This is the simplest approach and doesn’t require any prior setup:

{
  "select": ["?result"],
  "where": {"@id": "?s", "ex:derived": "?result"},
  "reasoning": "datalog",
  "rules": [ ... ]
}

Note: Providing a rules array automatically enables datalog reasoning — you don’t strictly need "reasoning": "datalog", though including it is recommended for clarity.

2. Database-stored rules

Rules can be stored in the database as f:rule assertions and referenced via ledger configuration. This is useful for rules that should apply consistently across all queries.

Store a rule:

{
  "@context": {
    "f": "https://ns.flur.ee/db#",
    "ex": "http://example.org/"
  },
  "insert": {
    "@id": "ex:grandparentRule",
    "f:rule": {
      "@context": {"ex": "http://example.org/"},
      "where": {"@id": "?person", "ex:parent": {"ex:parent": "?gp"}},
      "insert": {"@id": "?person", "ex:grandparent": {"@id": "?gp"}}
    }
  }
}

Configure the ledger to use stored rules:

{
  "insert": {
    "@id": "urn:fluree:mydb:main:config:ledger",
    "@type": "f:LedgerConfig",
    "f:datalogDefaults": {
      "f:datalogEnabled": true,
      "f:rulesSource": {
        "@type": "f:GraphRef",
        "f:graphSource": {"f:graphSelector": {"@id": "f:defaultGraph"}}
      },
      "f:allowQueryTimeRules": true
    }
  }
}

See Setting groups — datalogDefaults for full configuration options.

When both stored and query-time rules are present, they are merged and execute together in the same fixpoint loop.

Examples

Sibling inference

Infer siblings from shared parents:

{
  "@context": {"ex": "http://example.org/"},
  "select": ["?sibling"],
  "where": {"@id": "ex:alice", "ex:sibling": "?sibling"},
  "reasoning": "datalog",
  "rules": [
    {
      "@context": {"ex": "http://example.org/"},
      "where": [
        {"@id": "?person", "ex:parent": "?parent"},
        {"@id": "?sibling", "ex:parent": "?parent"}
      ],
      "insert": {"@id": "?person", "ex:sibling": {"@id": "?sibling"}}
    }
  ]
}

Note: This rule will also infer that a person is their own sibling. You could add a filter ["filter", "(!= ?person ?sibling)"] to exclude self-references.

Chained rules (uncle + aunt)

Multiple rules that build on each other:

{
  "@context": {"ex": "http://example.org/"},
  "select": ["?aunt"],
  "where": {"@id": "ex:alice", "ex:aunt": "?aunt"},
  "reasoning": "datalog",
  "rules": [
    {
      "@context": {"ex": "http://example.org/"},
      "where": {"@id": "?person", "ex:parent": {"ex:brother": "?uncle"}},
      "insert": {"@id": "?person", "ex:uncle": {"@id": "?uncle"}}
    },
    {
      "@context": {"ex": "http://example.org/"},
      "where": {
        "@id": "?person",
        "ex:uncle": {
          "ex:spouse": {"@id": "?aunt", "ex:gender": {"@id": "ex:Female"}}
        }
      },
      "insert": {"@id": "?person", "ex:aunt": {"@id": "?aunt"}}
    }
  ]
}

The second rule (aunt) depends on facts derived by the first rule (uncle). The fixpoint loop handles this automatically — it keeps iterating until no new facts are produced.

Rules with filters

Classify people by age:

{
  "@context": {"ex": "http://example.org/"},
  "select": ["?person"],
  "where": {"@id": "?person", "ex:status": "senior"},
  "reasoning": "datalog",
  "rules": [
    {
      "@context": {"ex": "http://example.org/"},
      "where": [
        {"@id": "?person", "ex:age": "?age"},
        ["filter", "(>= ?age 65)"]
      ],
      "insert": {"@id": "?person", "ex:status": "senior"}
    }
  ]
}

Combining with OWL reasoning

Datalog rules can build on OWL-derived facts. For example, use OWL 2 RL to materialize transitive and symmetric properties, then use Datalog for custom business logic:

{
  "select": ["?recommendation"],
  "where": {"@id": "ex:alice", "ex:recommended": "?recommendation"},
  "reasoning": ["owl2rl", "datalog"],
  "rules": [
    {
      "@context": {"ex": "http://example.org/"},
      "where": [
        {"@id": "?person", "ex:friend": "?friend"},
        {"@id": "?friend", "ex:likes": "?item"},
        {"@id": "?person", "ex:likes": "?item"}
      ],
      "insert": {"@id": "?person", "ex:recommended": {"@id": "?item"}}
    }
  ]
}

If ex:friend is declared as a owl:SymmetricProperty, OWL 2 RL materializes the reverse friendship links, and then the Datalog rule can find items liked by mutual friends.

Execution model

Fixpoint evaluation

Rules execute in a fixpoint loop:

  1. All rules are applied against the current data (base + previously derived facts).
  2. New facts produced in this iteration are collected.
  3. If any new facts were produced, go back to step 1 with the expanded fact set.
  4. When no new facts are produced (fixpoint reached), the loop terminates.

This means:

  • Recursive rules work. A rule can produce facts that trigger itself again.
  • Rule chaining works. Rule A can produce facts that trigger Rule B, and vice versa.
  • Termination is guaranteed by the budget controls (max iterations, max facts, max time, max memory).

Execution order

Rules are topologically sorted by their predicate dependencies: a rule that generates ex:uncle triples runs before a rule that consumes ex:uncle in its where clause. This minimizes the number of fixpoint iterations needed.

Interaction with OWL 2 RL

When both OWL 2 RL and Datalog are enabled:

  1. OWL 2 RL materialization runs first.
  2. Datalog rules run over the combined base data + OWL-derived facts.
  3. Both result sets are merged into a single overlay for query execution.

Filter expressions

Filters use S-expression syntax within the where array:

["filter", "(expression)"]

Available operators

CategoryOperators
Comparison=, !=, <, >, <=, >=
Logicaland, or, not
Arithmetic+, -, *, /
Stringstr, strlen, contains, strstarts, strends
Type checkingisIRI, isBlank, isLiteral, bound

Examples

["filter", "(> ?age 21)"]
["filter", "(and (>= ?age 18) (< ?age 65))"]
["filter", "(contains ?name \"Smith\")"]
["filter", "(!= ?person ?other)"]

Performance considerations

  • Keep rules focused. Broad rules that match many patterns produce more derived facts and require more iterations.
  • Budget limits apply. The same time/fact/memory budgets as OWL 2 RL materialization apply to Datalog execution (default: 30s, 1M facts, 100MB).
  • Results are cached. The same rule set + database state returns instantly from cache on subsequent queries.
  • Query-time rules disable caching across queries with different rule sets, since the cache key includes a hash of the rules.
TopicPage
Conceptual introductionReasoning and inference
Enabling reasoning in queriesQuery-time reasoning
OWL & RDFS constructsOWL & RDFS reference
Ledger-wide configSetting groups