Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add :duration type #4479

Merged
merged 7 commits into from
Aug 10, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ jobs:
fail-fast: false
matrix:
include:
- elixir: 1.15.6
otp: 26.1.2
- elixir: 1.17.2
otp: 27.0.1
lint: lint
- elixir: 1.15.6
otp: 24.3.4.13
- elixir: 1.17.2
otp: 25.0.4
- elixir: 1.11.4
otp: 21.3.8.24

Expand Down Expand Up @@ -66,8 +66,8 @@ jobs:
fail-fast: false
matrix:
elixirbase:
- "1.15.6-erlang-26.1.2-alpine-3.16.7"
- "1.15.6-erlang-24.3.4.14-alpine-3.16.7"
- "1.17.2-erlang-27.0.1-alpine-3.17.8"
- "1.17.2-erlang-25.0.4-alpine-3.17.8"
- "1.11.4-erlang-21.3.8.24-alpine-3.13.3"
steps:
- uses: earthly/actions-setup@v1
Expand Down
4 changes: 2 additions & 2 deletions Earthfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ all:

integration-test-base:
ARG ELIXIR_BASE=1.15.6-erlang-25.3.2.6-alpine-3.18.4
ARG TARGETARCH
ARG TARGETARCH
FROM hexpm/elixir:$ELIXIR_BASE
RUN apk add --no-progress --update git build-base
RUN mix local.rebar --force
Expand All @@ -24,7 +24,7 @@ integration-test-base:
apk del .build-dependencies && rm -f msodbcsql*.sig mssql-tools*.apk
ENV PATH="/opt/mssql-tools18/bin:${PATH}"

GIT CLONE https://github.com/elixir-ecto/ecto_sql.git /src/ecto_sql
GIT CLONE --branch duration_type https://github.com/greg-rychlewski/ecto_sql.git /src/ecto_sql
WORKDIR /src/ecto_sql
RUN mix deps.get

Expand Down
47 changes: 47 additions & 0 deletions integration_test/cases/type.exs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,53 @@ defmodule Ecto.Integration.TypeTest do
assert [<<42::6>>] = TestRepo.all(from p in Bitstring, limit: 1, select: p.bs_with_default)
end

if Code.ensure_loaded?(Duration) do
@tag :duration_type
test "duration type" do
duration = %Duration{year: 1, month: 1, second: 1, microsecond: {100, 6}}

struct = %Ecto.Integration.Duration{
dur: duration,
dur_with_fields: duration,
dur_with_precision: duration,
dur_with_fields_and_precision: duration
}

TestRepo.insert!(struct)

persisted_duration =
from(d in Ecto.Integration.Duration, where: d.dur == ^duration)
|> TestRepo.one()

assert persisted_duration.dur == duration

# `:field` option set to MONTH so it ignores all units lower than `:month`
assert persisted_duration.dur_with_fields == %Duration{
year: 1,
month: 1,
microsecond: {0, 6}
}

assert persisted_duration.dur_with_precision == %Duration{
year: 1,
month: 1,
second: 1,
microsecond: {100, 4}
}

# `:field` option is set to HOUR TO SECOND so it ignores all units lower than `:second`
assert persisted_duration.dur_with_fields_and_precision == %Duration{
year: 1,
month: 1,
second: 1,
microsecond: {0, 1}
}

# `:default set in migration`
assert persisted_duration.dur_with_default == %Duration{month: 10, microsecond: {0, 6}}
end
end

@tag :select_not
test "primitive types boolean negate" do
TestRepo.insert!(%Post{public: true})
Expand Down
18 changes: 18 additions & 0 deletions integration_test/support/schemas.exs
Original file line number Diff line number Diff line change
Expand Up @@ -403,3 +403,21 @@ defmodule Ecto.Integration.Bitstring do
field :bs_with_size, :bitstring
end
end

if Code.ensure_loaded?(Duration) do
defmodule Ecto.Integration.Duration do
@moduledoc """
This module is used to test:
* Duration type
"""
use Ecto.Integration.Schema

schema "durations" do
field :dur, :duration
field :dur_with_fields, :duration
field :dur_with_precision, :duration
field :dur_with_fields_and_precision, :duration
field :dur_with_default, :duration
end
end
end
5 changes: 5 additions & 0 deletions lib/ecto/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ defmodule Ecto.Schema do
`:naive_datetime_usec` | `NaiveDateTime` |
`:utc_datetime` | `DateTime` |
`:utc_datetime_usec` | `DateTime` |
`:duration` | `Duration` |

**Notes:**

Expand All @@ -294,6 +295,10 @@ defmodule Ecto.Schema do
where casting has to be done explicitly and is never performed
implicitly when loading from or dumping to the database.

* For `the `:duration` type, you may need to enable `Duration` support in
your adapter. For information on how to enable it in Postgrex, see their
[HexDocs page](https://hexdocs.pm/postgrex/readme.html#data-representation).

### Custom types

Besides providing primitive types, Ecto allows custom types to be
Expand Down
17 changes: 17 additions & 0 deletions lib/ecto/type.ex
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ defmodule Ecto.Type do
| :utc_datetime_usec
| :naive_datetime_usec
| :time_usec
| :duration

@type composite :: {:array, t} | {:map, t} | private_composite

Expand All @@ -231,6 +232,7 @@ defmodule Ecto.Type do
integer float decimal boolean string bitstring map binary id binary_id any
utc_datetime naive_datetime date time
utc_datetime_usec naive_datetime_usec time_usec
duration
)a
@composite ~w(array map try in param)a
@variadic ~w(in splice)a
Expand Down Expand Up @@ -564,6 +566,7 @@ defmodule Ecto.Type do
def dump(:naive_datetime_usec, value, _dumper), do: dump_naive_datetime_usec(value)
def dump(:utc_datetime, value, _dumper), do: dump_utc_datetime(value)
def dump(:utc_datetime_usec, value, _dumper), do: dump_utc_datetime_usec(value)
def dump(:duration, value, _dumper), do: same_duration(value)
def dump({:supertype, :datetime}, value, _dumper), do: dump_any_datetime(value)
def dump(mod, value, _dumper) when is_atom(mod), do: mod.dump(value)

Expand Down Expand Up @@ -661,6 +664,7 @@ defmodule Ecto.Type do
def load(:naive_datetime_usec, value, _loader), do: load_naive_datetime_usec(value)
def load(:utc_datetime, value, _loader), do: load_utc_datetime(value)
def load(:utc_datetime_usec, value, _loader), do: load_utc_datetime_usec(value)
def load(:duration, value, _loader), do: same_duration(value)
def load(mod, value, _loader), do: mod.load(value)

defp load_float(term) when is_float(term), do: {:ok, term}
Expand Down Expand Up @@ -833,6 +837,7 @@ defmodule Ecto.Type do
defp cast_fun(:naive_datetime_usec), do: &maybe_pad_usec(cast_naive_datetime(&1))
defp cast_fun(:utc_datetime), do: &maybe_truncate_usec(cast_utc_datetime(&1))
defp cast_fun(:utc_datetime_usec), do: &maybe_pad_usec(cast_utc_datetime(&1))
defp cast_fun(:duration), do: &cast_duration/1
defp cast_fun({:supertype, :datetime}), do: &cast_any_datetime(&1)
defp cast_fun({:parameterized, {mod, params}}), do: &mod.cast(&1, params)
defp cast_fun({qual, type}) when qual in @variadic, do: cast_fun({:array, type})
Expand Down Expand Up @@ -911,6 +916,12 @@ defmodule Ecto.Type do
defp cast_map(term) when is_map(term), do: {:ok, term}
defp cast_map(_), do: :error

if Code.ensure_loaded?(Duration) do
defp cast_duration(%Duration{} = term), do: {:ok, term}
end

defp cast_duration(_), do: :error

@doc """
Casts a value to the given type or raises an error.

Expand Down Expand Up @@ -967,6 +978,12 @@ defmodule Ecto.Type do
defp same_date(%Date{} = term), do: {:ok, term}
defp same_date(_), do: :error

if Code.ensure_loaded?(Duration) do
defp same_duration(%Duration{} = term), do: {:ok, term}
end

defp same_duration(_), do: :error

@doc false
def empty_trimmed_string?(value) do
is_binary(value) and String.trim_leading(value) == ""
Expand Down
Loading