lib/sparql/algebra/expression/left_join.ex

defmodule SPARQL.Algebra.LeftJoin do
  defstruct [:expr1, :expr2, :filters]

  alias __MODULE__
  alias SPARQL.Algebra.Expression
  alias SPARQL.Algebra.{Filter, Join}
  alias SPARQL.Query.Result
  alias SPARQL.Query.Result.SolutionMapping
  alias RDF.XSD

  def result_set(results1, results2, filter_expr, data, execution) do
    # TODO: optimization: if variables are disjoint, build cross-product directly, without checking compatibility for every pair (Assuming the variables of all solutions are the same)
    # TODO: optimization: build the results in one loop, i.e. produce the diff results during join
    Join.result_set(results1, results2)
    |> filter(filter_expr, data, execution)
    |> Result.append(diff(results1, results2, filter_expr, data, execution))
  end

  defp filter(result, %RDF.Literal{literal: %XSD.Boolean{value: true}}, _, _), do: result
  defp filter(result, filters, data, execution),
    do: Filter.result_set(result, filters, data, execution)

  defp diff(results1, results2, filter_expr, data, execution) do
    Enum.filter results1, fn result1 ->
      Enum.all? results2, fn result2 ->
        not SolutionMapping.compatible?(result1, result2) or
          not (
            result1
            |> SolutionMapping.merge(result2)
            |> Filter.apply?(filter_expr, data, execution)
          )
      end
    end
  end

  defimpl Expression do
    def evaluate(left_join, data, execution) do
      LeftJoin.result_set(
        Expression.evaluate(left_join.expr1, data, execution),
        Expression.evaluate(left_join.expr2, data, execution),
        left_join.filters, data, execution)
    end

    def variables(left_join) do
      Expression.variables(left_join.expr1) ++ Expression.variables(left_join.expr2)
    end
  end
end