Bounded variable-length graph traversal as an Ash manual relationship.
has_many :descendants, MyApp.Node do
manual {AshAge.ManualRelationships.Traverse,
edge_label: :PARENT_OF, direction: :outgoing, max_depth: 3, min_depth: 1}
endload/3 emits UNWIND $ids AS sid MATCH (a)<pattern>(b) WHERE a.<pk> = sid.<pk> … RETURN a.<pk> AS s1[, …], b, riding the P1-proven list-param UNWIND
- map-access mechanism, and returns an F3 source-PK-keyed map of materialized
destination records (deduped per source, cardinality-aware). No SQL
DISTINCTis used: per-path rows are returned raw sorow_countis the genuine pre-dedup fan-out signal (§5.4); dedup is done in Elixir bydedup/2(keyed by dest PK), which yieldsdestination_count. Direction fromdirection(:bothis undirected). Tenancy is FAIL-CLOSED::contextresolves a per-tenant graph;:attributescopes EVERY node on the path via a fixed-length UNION expansion — one basic-MATCH branch per length inmin..max, each binding every node (a, unlabeled intermediatesm1..m(len-1),b) and AND-ing<node>.<attr> = $tenant, the branches UNIONed (UNWINDrepeated per branch). This AGE build's Cypher parser rejects a bound path variable +ALL(n IN nodes(p) WHERE …)(probe P-S5b), so the per-hop predicate is unavailable; the UNION expansion is the equivalent, probe-validated (P-S5b-UNION) scoping. Values reach Cypher only as parameters; every identifier isvalidate_identifier!-checked.