AshAge.ManualRelationships.Traverse (AshAge v1.0.0)

Copy Markdown View Source

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}
end

load/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 DISTINCT is used: per-path rows are returned raw so row_count is the genuine pre-dedup fan-out signal (§5.4); dedup is done in Elixir by dedup/2 (keyed by dest PK), which yields destination_count. Direction from direction (:both is undirected). Tenancy is FAIL-CLOSED: :context resolves a per-tenant graph; :attribute scopes EVERY node on the path via a fixed-length UNION expansion — one basic-MATCH branch per length in min..max, each binding every node (a, unlabeled intermediates m1..m(len-1), b) and AND-ing <node>.<attr> = $tenant, the branches UNIONed (UNWIND repeated 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 is validate_identifier!-checked.