Translates a validated AshDyan.Request into an Ash.Query, runs it through
the resource's normal read action (so Ash policies apply), and aggregates the
result in memory into a chart-ready shape.
Design note
Ash's Ash.Query (3.x) does not expose a generic group_by builder, and the
shape of grouped aggregates is data-layer dependent. To keep AshDyan
data-layer agnostic, safe, and predictable, the engine:
- selects only the columns it needs (the metric column, the time field, the group_by fields),
- applies the caller's filters and the configured
limit(a hard cap that prevents full-cardinality group-bys from blowing up the DB), - runs the query through the resource's read action — so
Ash.Policyauthorization applies unchanged, - aggregates the returned rows in memory into the stable
labels/seriesoutput shape.
This keeps the security boundary (the dyan DSL whitelist + enforced limits)
intact while avoiding data-layer-specific query shapes. Percentiles, in
particular, are computed in memory so they work on any data layer; the
capability check still surfaces data-layer limits explicitly via
AshDyan.supports?/2.
Pipeline Hooks
Extensions can hook into the pipeline via config :ash_dyan, :hooks:
config :ash_dyan, :hooks, %{
before_query: [MyApp.Hooks.BeforeQuery],
after_query: [MyApp.Hooks.AfterQuery],
before_format: [MyApp.Hooks.BeforeFormat],
after_format: [MyApp.Hooks.AfterFormat]
}Each hook module must implement AshDyan.Engine.Hook behaviour.
Summary
Functions
Build an Ash.Query that selects exactly the columns needed for the request.
Format raw records into an AshDyan.Result using the analysis module's format callback.
Run a built query through the resource's read action.
Types
@type hook_module() :: module()
Functions
@spec build_query(AshDyan.Request.t(), [AshDyan.run_opt()]) :: {:ok, Ash.Query.t()} | {:error, term()}
Build an Ash.Query that selects exactly the columns needed for the request.
@spec format(AshDyan.Request.t(), [Ash.Resource.Record.t()]) :: {:ok, AshDyan.Result.t()} | {:error, term()}
Format raw records into an AshDyan.Result using the analysis module's format callback.
@spec run_query(Ash.Query.t(), AshDyan.Request.t(), [AshDyan.run_opt()]) :: {:ok, [Ash.Resource.Record.t()]} | {:error, term()}
Run a built query through the resource's read action.