A multi-hop graph traversal as an Ash.Expr value, pushed down to a Cypher
path pattern (#321).
From the queried node, follow a chain of typed/directed edges and reach the
node(s) at the far end — then use that reached node inside a filter:
# reached-node field comparison
Service
|> Ash.Query.filter(traverse(^chain, :status) == "active")
|> Ash.read!()
# compose with spatial — "services whose site is within 5 km of a point"
Service
|> Ash.Query.filter(st_dwithin(traverse(^chain, :location), ^point, 5_000))
|> Ash.read!()
# membership / cardinality over the reached set (#334)
Service |> Ash.Query.filter(traverse(^chain, :exists) == true) # reaches a node
Service |> Ash.Query.filter(traverse(^chain, :exists) == false) # reaches none
Service |> Ash.Query.filter(traverse(^chain, :count) > 0) # cardinalityArguments
hop_chain— a list of hops, each{:forward | :reverse, edge_selector}.:forwardwalks an outgoing edge,:reversean incoming one.edge_selectoris an Ash relationship name (atom, resolved on the source resource to its edge label + destination label) or an explicit{:edge, label}/{:edge, label, dest_label}.projection(optional, default:node) — what to pull from the reached set::node(default) — the reached node itself- a field atom (e.g.
:status,:location) — a field on the reached node, to compare or feed tost_dwithin/vector_similarity :exists— membership; renders toEXISTS {}/NOT EXISTS {}, spelled== true/== false:count— cardinality of distinct reached nodes; renders toCOUNT { … } <op> n
Ash's count()/sum()/… are inline aggregates welded to a relationship path
and expanded at filter hydration, so they can't nest as traverse(count());
the aggregate is carried as a projection literal (:count) instead.
Pushdown only
Traversal needs the graph, so it cannot be computed from argument values
alone — evaluate/1 returns :unknown and the data layer must push it down
to Cypher. In this first slice it is recognised in filter; sort/
calculate/policy contexts are a fast-follow.
Summary
Functions
Callback implementation for Ash.Query.Function.args/0.