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
endSupported 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
endRule 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)
endNegation uses not_:
rule bachelor(p) do
male(p)
not_ married(p, _)
endConstraints use named predicates:
rule high_earner(P) do
income(P, S)
gt(S, 100_000)
endSupported 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)
endQueries 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
Declares a ground fact.
fact parent(:alice, :bob)The relation must be declared before the fact.
Declares multiple facts for the same relation.
facts :parent do
row :alice, :bob
row :bob, :carol
end
Declares a named post-materialization query.
query :descendants_of_alice do
find y
where ancestor(:alice, y)
endThe find clause specifies which variables to extract.
The where clause specifies the relation and pattern to match.
Declares a relation with typed fields.
relation :parent do
field :parent, :atom
field :child, :atom
endSupported types: :atom, :integer, :string, :any.
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)
endNegation uses not_:
rule bachelor(P) do
male(P)
not_ married(P, _)
endConstraints use named predicates:
rule high_earner(P) do
income(P, S)
gt(S, 100_000)
end
@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