if Code.ensure_loaded?(Ecto.Query.API) do
defmodule QuackDB.Ecto.Predicates do
@moduledoc false
defmacro contains(left, right) do
cond do
text_contains?(left, right, __CALLER__) ->
quote do
fragment("contains(?, ?)", unquote(left), unquote(right))
end
spatial_contains?(left, right, __CALLER__) ->
quote do
fragment("ST_Contains(?, ?)", unquote(left), unquote(right))
end
true ->
raise ArgumentError,
"ambiguous contains/2; use contains_text/2 for DuckDB string containment " <>
"or st_contains/2 for DuckDB Spatial containment"
end
end
defp text_contains?(left, right, caller) do
not spatial_contains?(left, right, caller) and binary_literal?(right)
end
defp spatial_contains?(left, right, caller) do
expands_to_spatial?(left, caller) or expands_to_spatial?(right, caller)
end
defp expands_to_spatial?(expression, caller) do
expression
|> Macro.expand_once(caller)
|> spatial_fragment?()
end
defp spatial_fragment?({:fragment, _meta, [sql | _args]}) when is_binary(sql),
do: String.starts_with?(sql, "ST_")
defp spatial_fragment?(_expression), do: false
defp binary_literal?(value) when is_binary(value), do: true
defp binary_literal?({:^, _meta, [_value]}), do: true
defp binary_literal?(_value), do: false
end
end