ExDatalog.Schema (ExDatalog v0.4.1)

Copy Markdown View Source

An Ecto-inspired DSL for defining Datalog programs.

use ExDatalog.Schema in a module to declare relations, facts, rules, and queries. The module then exposes program/0, materialize/0,1, and query/2 functions.

Example

defmodule FamilyRules do
  use ExDatalog.Schema

  relation :parent do
    field :parent, :atom
    field :child, :atom
  end

  relation :ancestor do
    field :ancestor, :atom
    field :descendant, :atom
  end

  fact parent(:alice, :bob)
  fact parent(:bob, :carol)

  rule ancestor(X, Y) do
    parent(X, Y)
  end

  rule ancestor(X, Z) do
    parent(X, Y)
    ancestor(Y, Z)
  end

  query :descendants_of_alice do
    find Y
    where ancestor(:alice, Y)
  end
end

{:ok, knowledge} = FamilyRules.materialize()
FamilyRules.query(:descendants_of_alice, knowledge)
#=> [:bob, :carol]

Relation DSL

Relations declare named schemas with typed fields:

relation :parent do
  field :parent, :atom
  field :child, :atom
end

Supported field types: :atom, :integer, :string, :any.

Fact DSL

Facts assert ground tuples:

fact parent(:alice, :bob)

Bulk facts:

facts :parent do
  row :alice, :bob
  row :bob, :carol
end

Rule DSL

Rules derive new facts. Uppercase identifiers are logic variables, lowercase atoms and :atoms are constants, and _ is a wildcard:

rule ancestor(X, Y) do
  parent(X, Y)
end

Negation uses not_:

rule bachelor(p) do
  male(p)
  not_ married(p, _)
end

Constraints use named predicates:

rule high_earner(P) do
  income(P, S)
  gt(S, 100_000)
end

Supported constraints: eq, neq, gt, gte, lt, lte, add, sub, mul, div, is_integer, is_binary, is_atom, starts_with, contains, member.

Query DSL

Queries define named post-materialization lookups:

query :all_ancestors do
  find X, Y
  where ancestor(X, Y)
end

Queries operate on materialized knowledge and use Knowledge.match/3 internally.

Aggregate Syntax (Preview)

Aggregates are not yet supported. Using agg(...) in a rule head or body raises ExDatalog.DSL.CompileError at compile time:

rule employee_count(dept, agg(:count, emp)) do
  employee(emp, dept)
end
#=> ** (ExDatalog.DSL.CompileError) aggregates are not yet supported (planned for v0.6.0)

Backward Compatibility

The DSL compiles into the existing Program builder API. All existing builder APIs (Program.add_relation/3, Program.add_fact/3, Program.add_rule/2,3,4, ExDatalog.materialize/2) continue to work.

Summary

Functions

Declares a ground fact.

Declares multiple facts for the same relation.

Declares a named post-materialization query.

Declares a relation with typed fields.

Declares a Datalog rule.

Explicit wildcard for use in rule bodies and queries.

Functions

fact(rel_call)

(macro)

Declares a ground fact.

fact parent(:alice, :bob)

The relation must be declared before the fact.

facts(rel_name, list)

(macro)

Declares multiple facts for the same relation.

facts :parent do
  row :alice, :bob
  row :bob, :carol
end

query(name, list)

(macro)

Declares a named post-materialization query.

query :descendants_of_alice do
  find y
  where ancestor(:alice, y)
end

The find clause specifies which variables to extract. The where clause specifies the relation and pattern to match.

relation(name, list)

(macro)

Declares a relation with typed fields.

relation :parent do
  field :parent, :atom
  field :child, :atom
end

Supported types: :atom, :integer, :string, :any.

rule(head, list)

(macro)

Declares a Datalog rule.

Uppercase identifiers in the head and body are logic variables. Lowercase atoms and :atoms are constants. _ is a wildcard.

rule ancestor(X, Y) do
  parent(X, Y)
end

Negation uses not_:

rule bachelor(P) do
  male(P)
  not_ married(P, _)
end

Constraints use named predicates:

rule high_earner(P) do
  income(P, S)
  gt(S, 100_000)
end

wildcard()

@spec wildcard() :: :wildcard

Explicit wildcard for use in rule bodies and queries.

Inside DSL rule and query bodies, _ is treated as a wildcard. If Elixir's treatment of _ as a special form causes issues, use wildcard() as an explicit alternative.

Examples

iex> ExDatalog.Schema.wildcard()
:wildcard

rule bachelor(P) do
  male(P)
  not_ married(P, _)
end