lib/sparql/extension_function/extension_function.ex

defmodule SPARQL.ExtensionFunction do
  @moduledoc """
  A behaviour for SPARQL extension functions.

  ## Examples

  An extension function can be defined like this:

      defmodule ExampleFunction do
        use SPARQL.ExtensionFunction, name: "http://example.com/function"

        def call(distinct, arguments, data, execution) do
          # your implementation
        end
      end

  The name of the module is arbitrary and has no further meaning.

  see
  - <https://www.w3.org/TR/sparql11-query/#extensionFunctions>
  - <https://www.w3.org/TR/sparql11-query/#operatorExtensibility>
  """

  @doc """
  The name of the extension function.

  As specified in the SPARQL grammar the name of a function is an IRI.
  """
  @callback name() :: String.t

  @doc """
  Calls the extension function.

  The `distinct` argument is a boolean flag  which signifies if the `DISTINCT`
  modifier was used in the function call, which is syntactically allowed in
  custom aggregate function calls only.

  The `arguments` argument is the list of already evaluated RDF terms with which
  the extension function was called in the SPARQL query.

  The `data` argument contains the currently evaluated solution and some other
  internal information and shouldn't be relied upon, because it might be subject
  to changes and contain different elements depending on the context the function
  was called in. Since the arguments are already evaluated against the current
  solution it shouldn't be necessary anyway.

  The `execution` argument is a map with some global execution context
  information. In particular:

  - `base`: the base IRI
  - `time`: the query execution time
  - `bnode_generator`: the name of the `RDF.BlankNode.Generator` (see
    [RDF.ex documentation](http://hexdocs.pm/rdf)) used to generate unique blank
    nodes consistently

  """
  @callback call(distinct :: boolean(),
                 arguments :: list(RDF.Term.t),
                 data :: RDF.Dataset.t | RDF.Graph.t,
                 execution :: map)
              :: RDF.Term.t | :error


  defmacro __using__(opts) do
    name = Keyword.fetch!(opts, :name)
    extension_function = __CALLER__.module

    quote do
      @behaviour unquote(__MODULE__)

      @impl unquote(__MODULE__)
      def name(), do: unquote(name)


      import ProtocolEx

      defimpl_ex Registration, unquote(name),
                 for: SPARQL.ExtensionFunction.Registration do
        def extension_function(name), do: unquote(extension_function)
      end
    end
  end

end