ExDatalog.Constraints.Aggregate (ExDatalog v0.5.0)

Copy Markdown View Source

Aggregate constraint evaluation: count, sum, min, max.

Aggregates are not evaluated per binding. Unlike comparison or arithmetic constraints, an aggregate must see the full set of surviving bindings for a rule, group them, and reduce each group to a single value. That grouping is performed by ExDatalog.Engine.Evaluator via group_and_reduce/5; the evaluate/3 callback exists only to satisfy the ExDatalog.Constraint behaviour and raises if ever invoked through the per-binding pipeline.

Aggregates are integer-only. sum, min, and max require integer inputs, guarded at runtime: a non-integer input raises ArgumentError from the reducer (build-time type enforcement is future work). count returns the group size regardless of input type. min/max return the smallest/largest input value in the group.

Summary

Functions

Not supported per-binding. Aggregates are evaluated by the engine's group-and-reduce path. Always raises.

Groups bindings by group_vars and reduces each group's input_var values with the aggregate op, binding the reduced value to result_var.

Functions

evaluate(constraint, binding, context)

Not supported per-binding. Aggregates are evaluated by the engine's group-and-reduce path. Always raises.

group_and_reduce(bindings, group_vars, op, input_var, result_var)

@spec group_and_reduce([map()], [String.t()], atom(), String.t(), String.t()) :: [
  map()
]

Groups bindings by group_vars and reduces each group's input_var values with the aggregate op, binding the reduced value to result_var.

Returns one extended binding per non-empty group. Empty groups never occur in practice: a group exists only because at least one binding produced its key. The min/max reducers still carry a defensive empty-list clause that raises a clear error should that invariant ever be violated.

Examples

iex> bindings = [%{"D" => :eng, "E" => :a}, %{"D" => :eng, "E" => :b}, %{"D" => :ops, "E" => :c}]
iex> ExDatalog.Constraints.Aggregate.group_and_reduce(bindings, ["D"], :count, "E", "N")
...> |> Enum.map(fn b -> {b["D"], b["N"]} end)
...> |> Enum.sort()
[{:eng, 2}, {:ops, 1}]