AshDyan.Engine (AshDyan v0.1.0)

Copy Markdown View Source

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:

  1. selects only the columns it needs (the metric column, the time field, the group_by fields, and the filter fields),
  2. applies the caller's filters and the configured limit (a hard cap that prevents full-cardinality group-bys from blowing up the DB),
  3. runs the query through the resource's read action — so Ash.Policy authorization applies unchanged,
  4. aggregates the returned rows in memory into the stable labels/series output 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.

Summary

Functions

Build an Ash.Query that selects exactly the columns needed for the request.

Run a built query through the resource's read action.

Functions

build_query(request, opts \\ [])

@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.

run_query(query, request, opts)

@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.