Exograph.DSL is an Ecto-shaped query layer over fragments, definitions,
references, and Reach call edges.
import Exograph.DSLSources
Supported sources:
Fragment— structural fragments verified by ExASTDefinition— normalized definitionsReference— normalized referencesCallEdge— Reach-derived call edges
Fragment queries
Fragment queries support structural predicates:
query =
from(f in Fragment,
where: matches(f, "def _ do ... end"),
where: contains(f, "Repo.transaction(_)")
)
{:ok, results} = Exograph.all(index, query)Fragment field predicates are also supported:
from(f in Fragment,
where: f.kind in [:def, :defp],
where: f.mass > 4,
where: contains(f, "Repo.transaction(_)")
)Definition, reference, and call-edge queries
from(d in Definition,
where: prefix_search(d.name, "parse_resp"),
where: d.kind == :def
)
from(r in Reference,
where: r.qualified_name == "Repo.transaction/1"
)
from(e in CallEdge,
where: e.callee_qualified_name == "Repo.transaction/1"
)These run directly against normalized Postgres facts and return typed hits.
Joins
Fragment queries can join normalized definitions, references, or Reach call edges before ExAST verification:
from(f in Fragment,
join: r in assoc(f, :references),
where: f.kind == :def,
where: r.qualified_name == "Repo.transaction/1",
where: matches(f, "def _ do ... end")
)Definition queries can join call edges:
from(d in Definition,
join: e in assoc(d, :calls),
where: d.kind == :defp,
where: e.callee_qualified_name == "Repo.transaction/1"
)Multi-join fragment queries
Fragment queries can combine definitions, references, and calls:
query =
from(f in Fragment,
join: d in assoc(f, :definitions),
join: r in assoc(f, :references),
join: e in assoc(f, :calls),
where: d.kind == :defp,
where: r.qualified_name == "Repo.transaction/1",
where: e.callee_qualified_name == "Repo.transaction/1",
where: matches(f, "defp _ do ... end"),
select: {f, d, r, e}
)
{:ok, [{hit, definition, reference, call_edge}]} = Exograph.all(index, query)Fragment joins use containing-function semantics: facts from later functions do not satisfy predicates on earlier fragments. When a definition join and a call join are present, call edges are paired by caller qualified name.
Selects
Joined fragment queries can select the fragment hit, a joined fact, or a tuple:
from(f in Fragment,
join: e in assoc(f, :calls),
where: e.callee_qualified_name == "Repo.transaction/1",
where: matches(f, "def _ do ... end"),
select: {f, e}
)Planner validation
DSL queries are normalized through an internal plan before execution. The planner validates unsupported sources, joins, duplicate bindings, unbound predicates, structural predicates on non-fragment bindings, and invalid selects.