///|
fn describe_enqueue(
  resource : Term,
  pending : Array[Term],
  visited : Map[String, Bool],
  budget : Budget,
) -> Unit raise QueryError {
  budget.tick()
  if resource.kind == LiteralKind {
    return
  }
  let key = resource.key()
  if visited.get(key) is None {
    budget.rows(pending.length() + 1)
    visited[key] = true
    pending.push(resource)
  }
}

///|
fn describe_graph(
  data : Dataset,
  q : Query,
  solutions : Array[Solution],
  budget : Budget,
) -> Array[Quad] raise QueryError {
  let pending = []
  let visited = Map([])
  for slot in q.describe {
    if slot is Fixed(resource) {
      describe_enqueue(resource, pending, visited, budget)
    }
  }
  let variables = solution_variables(q)
  for solution in solutions {
    for name in variables {
      budget.tick()
      if solution.source.get(name) is Some(resource) {
        describe_enqueue(resource, pending, visited, budget)
      }
    }
  }
  let output = []
  for i = 0; i < pending.length(); i = i + 1 {
    let resource = pending[i]
    for q in data.scan(Some(resource), None, None, None, budget) {
      if output.length() >= budget.limits.max_results {
        raise Resource("description triples")
      }
      output.push(q)
      if q.object.kind == BlankKind {
        describe_enqueue(q.object, pending, visited, budget)
      }
    }
  }
  output
}