Packages

Production-grade Elixir library for the Open Knowledge Format (OKF). Standards-compliant parsing, validation, lossless I/O, graph indexing, querying, and provenance-aware AI context assembly.

Current section

Files

Jump to
ex_okf lib ex_okf query.ex
Raw

lib/ex_okf/query.ex

defmodule ExOKF.Query do
@moduledoc """
High-level query helpers over an OKF bundle.
Filters are ANDed together. Unknown filter keys are ignored (match fails).
"""
alias ExOKF.{Bundle, Concept}
@typedoc """
A single query filter.
* `{:type, String.t() | Regex.t()}` — exact or regex match on `concept.type`
* `{:tag, String.t()}` — concept must include this tag
* `{:id, String.t() | Regex.t()}` — exact or regex match on `concept.id`
* `{:has_resource, boolean()}` — require / forbid a non-empty `resource`
## Examples
{:type, "BigQuery Table"}
{:type, ~r/Table/}
{:tag, "sales"}
{:id, "tables/orders"}
{:id, ~r/^tables\\//}
{:has_resource, true}
"""
@type filter ::
{:type, String.t() | Regex.t()}
| {:tag, String.t()}
| {:id, String.t() | Regex.t()}
| {:has_resource, boolean()}
@doc """
Queries concepts matching **all** given filters.
## Parameters
* `bundle` (`ExOKF.Bundle.t()`) — loaded OKF bundle
* `filters` (`[t:filter/0] | keyword()`) — AND-combined filters
## Returns
A list of matching `ExOKF.Concept.t()` (order follows `ExOKF.Bundle.concepts/1`).
## Examples
iex> bundle = ExOKF.TestSupport.sample_bundle()
iex> ids = ExOKF.Query.query(bundle, type: "BigQuery Table") |> Enum.map(& &1.id)
iex> "tables/orders" in ids
true
"""
@spec query(Bundle.t(), [filter()] | keyword()) :: [Concept.t()]
def query(%Bundle{} = bundle, filters \\ []) do
bundle
|> Bundle.concepts()
|> Enum.filter(&matches_all?(&1, filters))
end
@doc """
Finds a concept by id.
## Parameters
* `bundle` (`ExOKF.Bundle.t()`) — loaded OKF bundle
* `id` (`String.t()`) — concept id, e.g. `"tables/orders"`
## Returns
The concept, or `nil` if not found.
## Examples
iex> bundle = ExOKF.TestSupport.sample_bundle()
iex> ExOKF.Query.get(bundle, "tables/orders").title
"Orders"
"""
@spec get(Bundle.t(), String.t()) :: Concept.t() | nil
def get(%Bundle{} = bundle, id), do: Bundle.get_concept(bundle, id)
@doc """
Returns all distinct concept types in the bundle, sorted.
## Parameters
* `bundle` (`ExOKF.Bundle.t()`) — loaded OKF bundle
## Examples
iex> bundle = ExOKF.TestSupport.sample_bundle()
iex> ExOKF.Query.types(bundle)
["BigQuery Dataset", "BigQuery Table"]
"""
@spec types(Bundle.t()) :: [String.t()]
def types(%Bundle{} = bundle) do
bundle
|> Bundle.concepts()
|> Enum.map(& &1.type)
|> Enum.reject(&is_nil/1)
|> Enum.uniq()
|> Enum.sort()
end
@doc """
Returns all distinct tags in the bundle, sorted.
## Parameters
* `bundle` (`ExOKF.Bundle.t()`) — loaded OKF bundle
## Examples
iex> bundle = ExOKF.TestSupport.sample_bundle()
iex> "sales" in ExOKF.Query.tags(bundle)
true
"""
@spec tags(Bundle.t()) :: [String.t()]
def tags(%Bundle{} = bundle) do
bundle
|> Bundle.concepts()
|> Enum.flat_map(& &1.tags)
|> Enum.uniq()
|> Enum.sort()
end
@doc """
Returns concepts that link to `concept_id` (backlinks).
## Parameters
* `bundle` (`ExOKF.Bundle.t()`) — loaded OKF bundle
* `concept_id` (`String.t()`) — target concept id
## Examples
iex> bundle = ExOKF.TestSupport.sample_bundle()
iex> ids = ExOKF.Query.backlinks(bundle, "tables/customers") |> Enum.map(& &1.id)
iex> "tables/orders" in ids
true
"""
@spec backlinks(Bundle.t(), String.t()) :: [Concept.t()]
def backlinks(%Bundle{} = bundle, concept_id) do
bundle
|> ExOKF.Graph.reverse_neighbors(concept_id)
|> Enum.map(&Bundle.get_concept(bundle, &1))
|> Enum.reject(&is_nil/1)
end
defp matches_all?(concept, filters) do
Enum.all?(filters, &matches?(concept, &1))
end
defp matches?(%Concept{type: type}, {:type, %Regex{} = re}), do: type && Regex.match?(re, type)
defp matches?(%Concept{type: type}, {:type, expected}), do: type == expected
defp matches?(%Concept{id: id}, {:id, %Regex{} = re}), do: Regex.match?(re, id)
defp matches?(%Concept{id: id}, {:id, expected}), do: id == expected
defp matches?(%Concept{tags: tags}, {:tag, tag}), do: tag in tags
defp matches?(%Concept{resource: res}, {:has_resource, true}), do: not is_nil(res) and res != ""
defp matches?(%Concept{resource: res}, {:has_resource, false}), do: is_nil(res) or res == ""
defp matches?(_, _), do: false
end