# `ExDatalog.Rule`
[🔗](https://github.com/thanos/ex_datalog/blob/v0.5.0/lib/ex_datalog/rule.ex#L1)

A Datalog rule: `head :- body [, constraints]`.

A rule consists of:

- A **head** atom (`ExDatalog.Atom.t()`) — the relation being derived.
- A **body** list of literals — each is one of:
  - `{:positive, ExDatalog.Atom.t()}` — a positive relational atom.
  - `{:negative, ExDatalog.Atom.t()}` — a negated relational atom.
- A **constraints** list of `ExDatalog.Constraint.t()` — built-in predicates
  (comparisons and arithmetic) evaluated after joining body atoms.

Polarity is carried at the rule level, not in the atom itself. Constraints
are kept separate from the body literal list for clarity and evaluation ordering.

## Examples

    iex> alias ExDatalog.{Rule, Atom, Term}
    iex> head = Atom.new("ancestor", [Term.var("X"), Term.var("Y")])
    iex> body = [{:positive, Atom.new("parent", [Term.var("X"), Term.var("Y")])}]
    iex> Rule.new(head, body)
    %ExDatalog.Rule{
      head: %ExDatalog.Atom{relation: "ancestor", terms: [{:var, "X"}, {:var, "Y"}]},
      body: [{:positive, %ExDatalog.Atom{relation: "parent", terms: [{:var, "X"}, {:var, "Y"}]}}],
      constraints: []
    }

# `literal`

```elixir
@type literal() ::
  {:positive, ExDatalog.Atom.t()}
  | {:negative, ExDatalog.Atom.t()}
  | {:callback, ExDatalog.Callback.t()}
```

# `t`

```elixir
@type t() :: %ExDatalog.Rule{
  body: [literal()],
  constraints: [ExDatalog.Constraint.t()],
  head: ExDatalog.Atom.t()
}
```

# `aggregate_constraints`

```elixir
@spec aggregate_constraints(t()) :: [ExDatalog.Constraint.t()]
```

Returns the aggregate constraints in the rule.

# `body_atoms`

```elixir
@spec body_atoms(t()) :: [ExDatalog.Atom.t()]
```

Returns all body atoms (stripping polarity).

## Examples

    iex> alias ExDatalog.{Rule, Atom, Term}
    iex> a1 = Atom.new("parent", [Term.var("X"), Term.var("Y")])
    iex> a2 = Atom.new("alive", [Term.var("X")])
    iex> rule = Rule.new(Atom.new("ok", [Term.var("X")]), [{:positive, a1}, {:negative, a2}])
    iex> Rule.body_atoms(rule)
    [
      %ExDatalog.Atom{relation: "parent", terms: [{:var, "X"}, {:var, "Y"}]},
      %ExDatalog.Atom{relation: "alive", terms: [{:var, "X"}]}
    ]

# `callbacks`

```elixir
@spec callbacks(t()) :: [ExDatalog.Callback.t()]
```

Returns the callback predicates in the rule body.

# `has_aggregates?`

```elixir
@spec has_aggregates?(t()) :: boolean()
```

Returns `true` if the rule contains any aggregate constraint
(`count`, `sum`, `min`, `max`).

## Examples

    iex> alias ExDatalog.{Rule, Atom, Term, Constraint}
    iex> head = Atom.new("dept_count", [Term.var("D"), Term.var("N")])
    iex> body = [{:positive, Atom.new("emp", [Term.var("E"), Term.var("D")])}]
    iex> rule = Rule.new(head, body, [Constraint.count(Term.var("E"), Term.var("N"))])
    iex> Rule.has_aggregates?(rule)
    true

# `has_callbacks?`

```elixir
@spec has_callbacks?(t()) :: boolean()
```

Returns `true` if the rule contains any callback predicate in its body.

# `has_negation?`

```elixir
@spec has_negation?(t()) :: boolean()
```

Returns `true` if the rule contains any negative body literals.

## Examples

    iex> alias ExDatalog.{Rule, Atom, Term}
    iex> head = Atom.new("bachelor", [Term.var("X")])
    iex> body = [
    ...>   {:positive, Atom.new("male", [Term.var("X")])},
    ...>   {:negative, Atom.new("married", [Term.var("X"), :wildcard])}
    ...> ]
    iex> Rule.has_negation?(Rule.new(head, body))
    true

    iex> alias ExDatalog.{Rule, Atom, Term}
    iex> Rule.has_negation?(Rule.new(
    ...>   Atom.new("r", [Term.var("X")]),
    ...>   [{:positive, Atom.new("s", [Term.var("X")])}]
    ...> ))
    false

# `head_variables`

```elixir
@spec head_variables(t()) :: [String.t()]
```

Returns all variable names that appear in the rule head.

## Examples

    iex> alias ExDatalog.{Rule, Atom, Term}
    iex> head = Atom.new("ancestor", [Term.var("X"), Term.var("Z")])
    iex> body = [{:positive, Atom.new("parent", [Term.var("X"), Term.var("Y")])}]
    iex> Rule.head_variables(Rule.new(head, body))
    ["X", "Z"]

# `new`

```elixir
@spec new(ExDatalog.Atom.t(), [literal()], [ExDatalog.Constraint.t()]) :: t()
```

Constructs a new rule with a head atom and body literals.

Body literals must be `{:positive, atom}` or `{:negative, atom}` tuples.
Constraints default to an empty list.

## Examples

    iex> alias ExDatalog.{Rule, Atom, Term}
    iex> head = Atom.new("reachable", [Term.var("X"), Term.var("Y")])
    iex> body = [{:positive, Atom.new("edge", [Term.var("X"), Term.var("Y")])}]
    iex> Rule.new(head, body)
    %ExDatalog.Rule{
      head: %ExDatalog.Atom{relation: "reachable", terms: [{:var, "X"}, {:var, "Y"}]},
      body: [{:positive, %ExDatalog.Atom{relation: "edge", terms: [{:var, "X"}, {:var, "Y"}]}}],
      constraints: []
    }

# `non_aggregate_constraints`

```elixir
@spec non_aggregate_constraints(t()) :: [ExDatalog.Constraint.t()]
```

Returns the non-aggregate constraints in the rule.

# `positive_body_variables`

```elixir
@spec positive_body_variables(t()) :: [String.t()]
```

Returns all variable names that appear in positive body atoms.

These are the "safe" bindings available for use in the head and constraints.

## Examples

    iex> alias ExDatalog.{Rule, Atom, Term}
    iex> head = Atom.new("result", [Term.var("X")])
    iex> body = [
    ...>   {:positive, Atom.new("a", [Term.var("X"), Term.var("Y")])},
    ...>   {:negative, Atom.new("b", [Term.var("Y")])}
    ...> ]
    iex> Rule.positive_body_variables(Rule.new(head, body))
    ["X", "Y"]

# `variables`

```elixir
@spec variables(t()) :: [String.t()]
```

Returns all variable names that appear anywhere in the rule
(head + body + constraints).

## Examples

    iex> alias ExDatalog.{Rule, Atom, Term}
    iex> head = Atom.new("ancestor", [Term.var("X"), Term.var("Z")])
    iex> body = [
    ...>   {:positive, Atom.new("parent", [Term.var("X"), Term.var("Y")])},
    ...>   {:positive, Atom.new("ancestor", [Term.var("Y"), Term.var("Z")])}
    ...> ]
    iex> rule = Rule.new(head, body)
    iex> Rule.variables(rule) |> Enum.sort()
    ["X", "Y", "Z"]

---

*Consult [api-reference.md](api-reference.md) for complete listing*
