Exograph.DSL is an Ecto-shaped query layer over fragments, definitions, references, and Reach call edges.

import Exograph.DSL

Sources

Supported sources:

  • Fragment — structural fragments verified by ExAST
  • Definition — normalized definitions
  • Reference — normalized references
  • CallEdge — 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.