defmodule PgRanges do @moduledoc """ PgRanges provides a simple wrapper around `Postgrex.Range` so that you can create schemas with range type fields and use the native range type in migrations. defmodule MyApp.Employee do use Ecto.Schema alias PgRanges.DateRange schema "employees" do field :name, :string field :employed_dates, DateRange end end defmodule MyApp.Repo.Migrations.CreateEmployees do use Ecto.Migration def change do create table(:employees) do add :name, :string add :employed_dates, :daterange end end end When used in composing queries, you must cast to the PgRange type. import Ecto.Query alias PgRanges.DateRange alias MyApp.Repo range = DateRange.new(~D[2018-11-01], ~D[2019-01-01]) Repo.all( from e in Employee, where: fragment("? @> ?", e.employed_dates, type(^range, DateRange)) ) """ @callback new(lower :: any, upper :: any) :: any @callback new(any, any, any) :: any @callback from_postgrex(any) :: any @callback to_postgrex(any) :: any @optional_callbacks new: 2, new: 3, from_postgrex: 1, to_postgrex: 1 @doc false defmacro __using__(opts) do quote location: :keep, bind_quoted: [opts: opts] do alias Postgrex.Range use Ecto.Type @behaviour PgRanges @before_compile PgRanges defstruct lower: nil, lower_inclusive: true, upper: nil, upper_inclusive: false @spec new(any, any, keyword()) :: __MODULE__.t() def new(lower, upper, opts \\ []) do fields = Keyword.merge(opts, lower: lower, upper: upper) struct!(__MODULE__, fields) end @doc false @spec from_postgrex(Range.t()) :: __MODULE__.t() def from_postgrex(%Range{} = range), do: struct!(__MODULE__, Map.from_struct(range)) @doc false @spec to_postgrex(__MODULE__.t()) :: Range.t() def to_postgrex(%__MODULE__{} = range), do: struct!(Range, Map.from_struct(range)) @doc false def cast(nil), do: {:ok, nil} def cast(%Range{} = range), do: {:ok, from_postgrex(range)} def cast(%__MODULE__{} = range), do: {:ok, range} def cast(_), do: :error @doc false def load(nil), do: {:ok, nil} def load(%Range{} = range), do: {:ok, from_postgrex(range)} def load(_), do: :error @doc false def dump(nil), do: {:ok, nil} def dump(%__MODULE__{} = range), do: {:ok, to_postgrex(range)} def dump(_), do: :error defoverridable new: 2, new: 3, from_postgrex: 1, to_postgrex: 1 end end @doc false defmacro __before_compile__(env) do unless Module.defines?(env.module, {:type, 0}) do message = """ function type/0 required by behaviour Ecto.Type is not implemented \ (in module #{inspect(env.module)}). """ IO.warn(message, Macro.Env.stacktrace(env)) quote do @doc false def type, do: :not_a_valid_type end end end end