Ecto Ranges with [nil, nil]

:wave:

I know it needs some fixies, but I’m just trying to work out the basics right now. I want to be able to give [nil, nil] to a date range, but I can’t seem to figure out the correct way to do it. I get an error ERROR 08P01 (protocol violation): insufficient data left in message.

date_range.ex

defmodule DataManager.Types.DateRange do
  @behaviour Ecto.Type

  def type, do: :daterange

  def cast([nil, nil]) do
    {:ok, [nil, nil]}
  end

  def cast([lower, upper]) do
    case Enum.map([lower, upper], &to_date/1) do
      [:error, _] -> :error
      [_, :error] -> :error
      [date_lower, date_upper] -> {:ok, [date_lower, date_upper]}
    end
  end
  def cast(_), do: :error

  def load(%Postgrex.Range{lower: lower, upper: upper}) do
    case Enum.map([lower, upper], &load_date/1) do
      [:error, _] -> :error
      [_, :error] -> :error
      [date_lower, date_upper] -> {:ok, [date_lower, date_upper]}
    end
  end
  def load(_), do: :error

  def dump([lower, upper]) do
    case Enum.map([lower, upper], &dump_date/1) do
      [:error, _] -> :error
      [_, :error] -> :error
      [date_lower, date_upper] -> {:ok, %Postgrex.Range{lower: date_lower, upper: date_upper, upper_inclusive: true, lower_inclusive: true}}
    end
  end

  def to_date(nil), do: nil
  def to_date(date) do
    {:ok, casted_date} = Ecto.Date.cast(date)
    casted_date
  end

  def load_date(nil), do: nil
  def load_date(date) do
    {:ok, loaded_date} = Ecto.Date.load(date)
    loaded_date
  end

  def dump_date(nil), do: nil
  def dump_date(date) do
    case Ecto.Date.dump(date) do
      {:ok, dumped_date} -> dumped_date
      :error -> :error
    end
  end
end

date_range_test.ex

defmodule DataManager.DateRangeTest do
  use Ecto.Schema

  schema "date_range_tests" do
    field :date_range, DataManager.Types.DateRange
  end
end

iex

Erlang/OTP 19 [erts-8.3] [source] [64-bit] [smp:2:2] [async-threads:10] [kernel-poll:false]

Interactive Elixir (1.4.5) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> alias DataManager.{Repo, DateRangeTest}
[DataManager.Repo, DataManager.DateRangeTest]
iex(2)> %DateRangeTest{date_range: [nil, Ecto.Date.utc()]} |> Repo.insert()

12:24:14.215 [debug] QUERY OK db=8.0ms
INSERT INTO "date_range_tests" ("date_range") VALUES ($1) RETURNING "id" [%Postgrex.Range{lower: nil, lower_inclusive: true, upper: {2017, 7, 29}, upper_inclusive: true}]
{:ok,
 %DataManager.DateRangeTest{__meta__: #Ecto.Schema.Metadata<:loaded, "date_range_tests">,
  date_range: [nil, #Ecto.Date<2017-07-29>], id: 5}}
iex(3)> %DateRangeTest{date_range: [Ecto.Date.utc(), Ecto.Date.utc()]} |> Repo.insert()

12:24:27.133 [debug] QUERY OK db=2.6ms
INSERT INTO "date_range_tests" ("date_range") VALUES ($1) RETURNING "id" [%Postgrex.Range{lower: {2017, 7, 29}, lower_inclusive: true, upper: {2017, 7, 29}, upper_inclusive: true}]
{:ok,
 %DataManager.DateRangeTest{__meta__: #Ecto.Schema.Metadata<:loaded, "date_range_tests">,
  date_range: [#Ecto.Date<2017-07-29>, #Ecto.Date<2017-07-29>], id: 6}}
iex(4)> %DateRangeTest{date_range: [Ecto.Date.utc(), nil]} |> Repo.insert()            

12:24:41.339 [debug] QUERY OK db=5.9ms queue=0.3ms
INSERT INTO "date_range_tests" ("date_range") VALUES ($1) RETURNING "id" [%Postgrex.Range{lower: {2017, 7, 29}, lower_inclusive: true, upper: nil, upper_inclusive: true}]
{:ok,
 %DataManager.DateRangeTest{__meta__: #Ecto.Schema.Metadata<:loaded, "date_range_tests">,
  date_range: [#Ecto.Date<2017-07-29>, nil], id: 7}}
iex(5)> %DateRangeTest{date_range: [nil, nil]} |> Repo.insert()            

12:24:55.710 [debug] QUERY ERROR db=23.6ms queue=0.5ms
INSERT INTO "date_range_tests" ("date_range") VALUES ($1) RETURNING "id" [%Postgrex.Range{lower: nil, lower_inclusive: true, upper: nil, upper_inclusive: true}]
** (Postgrex.Error) ERROR 08P01 (protocol_violation): insufficient data left in message
    (ecto) lib/ecto/adapters/sql.ex:571: Ecto.Adapters.SQL.struct/7
    (ecto) lib/ecto/repo/schema.ex:467: Ecto.Repo.Schema.apply/4
    (ecto) lib/ecto/repo/schema.ex:205: anonymous fn/13 in Ecto.Repo.Schema.do_insert/4

Here is the iex prompt filtered to just the error:

12:24:55.710 [debug] QUERY ERROR db=23.6ms queue=0.5ms
INSERT INTO "date_range_tests" ("date_range") VALUES ($1) RETURNING "id" [%Postgrex.Range{lower: nil, lower_inclusive: true, upper: nil, upper_inclusive: true}]
** (Postgrex.Error) ERROR 08P01 (protocol_violation): insufficient data left in message
    (ecto) lib/ecto/adapters/sql.ex:571: Ecto.Adapters.SQL.struct/7
    (ecto) lib/ecto/repo/schema.ex:467: Ecto.Repo.Schema.apply/4
    (ecto) lib/ecto/repo/schema.ex:205: anonymous fn/13 in Ecto.Repo.Schema.do_insert/4

So, what’s the right way to do this? What do I need to do in the type behaviour to get [nil, nil] to just be nil?

I figured it out!

If it’s [nil, nil], the tuple {:ok, nil} should be returned.

1 Like