From fdb5f4160beb7a29c25eecec0f9ae071db74dc1e Mon Sep 17 00:00:00 2001 From: Gerry Shaw Date: Thu, 14 Dec 2023 06:27:32 -0800 Subject: [PATCH] Add support for nil value in datetime column (#136) --- CHANGELOG.md | 2 ++ lib/ecto/adapters/sqlite3/codec.ex | 6 ++++++ test/ecto/adapters/sqlite3/codec_test.exs | 10 ++++++++++ test/ecto/integration/timestamps_test.exs | 22 ++++++++++++++++++++++ test/support/migration.ex | 1 + test/support/schemas/product.ex | 3 ++- 6 files changed, 43 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d69e7b..4f744dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ project adheres to [Semantic Versioning][semver]. ## Unreleased +- added: Support for encoding nil values in `:utc_datetime`, `:utc_datetime_usec`, `:naive_datetime`, and `:naive_datetime_usec` column dates. + ## v0.13.0 - added: Support fragment splicing. diff --git a/lib/ecto/adapters/sqlite3/codec.ex b/lib/ecto/adapters/sqlite3/codec.ex index dc9a604..11f5c4e 100644 --- a/lib/ecto/adapters/sqlite3/codec.ex +++ b/lib/ecto/adapters/sqlite3/codec.ex @@ -110,6 +110,9 @@ defmodule Ecto.Adapters.SQLite3.Codec do @text_datetime_format "%Y-%m-%d %H:%M:%S" + def utc_datetime_encode(nil, :iso8601), do: {:ok, nil} + def utc_datetime_encode(nil, :text_datetime), do: {:ok, nil} + def utc_datetime_encode(%{time_zone: "Etc/UTC"} = value, :iso8601) do {:ok, NaiveDateTime.to_iso8601(value)} end @@ -123,6 +126,9 @@ defmodule Ecto.Adapters.SQLite3.Codec do "expected datetime type to be either `:iso8601` or `:text_datetime`, but received #{inspect(type)}" end + def naive_datetime_encode(nil, :iso8601), do: {:ok, nil} + def naive_datetime_encode(nil, :text_datetime), do: {:ok, nil} + def naive_datetime_encode(value, :iso8601) do {:ok, NaiveDateTime.to_iso8601(value)} end diff --git a/test/ecto/adapters/sqlite3/codec_test.exs b/test/ecto/adapters/sqlite3/codec_test.exs index f7bfa9e..2be15cb 100644 --- a/test/ecto/adapters/sqlite3/codec_test.exs +++ b/test/ecto/adapters/sqlite3/codec_test.exs @@ -132,6 +132,11 @@ defmodule Ecto.Adapters.SQLite3.CodecTest do [dt: ~U[2021-08-25 10:58:59Z]] end + test "nil" do + assert {:ok, nil} = Codec.utc_datetime_encode(nil, :iso8601) + assert {:ok, nil} = Codec.utc_datetime_encode(nil, :text_datetime) + end + test "iso8601", %{dt: dt} do dt_str = "2021-08-25T10:58:59" assert {:ok, ^dt_str} = Codec.utc_datetime_encode(dt, :iso8601) @@ -157,6 +162,11 @@ defmodule Ecto.Adapters.SQLite3.CodecTest do [dt: ~U[2021-08-25 10:58:59Z], dt_str: "2021-08-25T10:58:59"] end + test "nil" do + assert {:ok, nil} = Codec.naive_datetime_encode(nil, :iso8601) + assert {:ok, nil} = Codec.naive_datetime_encode(nil, :text_datetime) + end + test "iso8601", %{dt: dt} do dt_str = "2021-08-25T10:58:59" assert {:ok, ^dt_str} = Codec.naive_datetime_encode(dt, :iso8601) diff --git a/test/ecto/integration/timestamps_test.exs b/test/ecto/integration/timestamps_test.exs index 08f68d1..fca29b7 100644 --- a/test/ecto/integration/timestamps_test.exs +++ b/test/ecto/integration/timestamps_test.exs @@ -123,6 +123,28 @@ defmodule Ecto.Integration.TimestampsTest do assert user end + test "insert and fetch nil values" do + now = DateTime.utc_now() + + {:ok, product} = + %Product{} + |> Product.changeset(%{name: "Nil Date Test", approved_at: now, ordered_at: now}) + |> TestRepo.insert() + + product = TestRepo.get(Product, product.id) + assert product.name == "Nil Date Test" + assert product.approved_at != now + assert product.ordered_at != now + assert product.approved_at == DateTime.truncate(now, :second) |> DateTime.to_naive() + assert product.ordered_at == DateTime.truncate(now, :second) + + changeset = Product.changeset(product, %{approved_at: nil, ordered_at: nil}) + TestRepo.update(changeset) + product = TestRepo.get(Product, product.id) + assert product.approved_at == nil + assert product.ordered_at == nil + end + test "datetime comparisons" do account = %Account{} diff --git a/test/support/migration.ex b/test/support/migration.ex index 94b07f7..0341bdc 100644 --- a/test/support/migration.ex +++ b/test/support/migration.ex @@ -31,6 +31,7 @@ defmodule EctoSQLite3.Integration.Migration do add(:bid, :binary_id) add(:tags, {:array, :string}) add(:approved_at, :naive_datetime) + add(:ordered_at, :utc_datetime) add(:price, :decimal) timestamps() end diff --git a/test/support/schemas/product.ex b/test/support/schemas/product.ex index 582eb4e..654e023 100644 --- a/test/support/schemas/product.ex +++ b/test/support/schemas/product.ex @@ -14,6 +14,7 @@ defmodule EctoSQLite3.Schemas.Product do field(:bid, :binary_id) field(:tags, {:array, :string}, default: []) field(:approved_at, :naive_datetime) + field(:ordered_at, :utc_datetime) field(:price, :decimal) belongs_to(:account, Account) @@ -23,7 +24,7 @@ defmodule EctoSQLite3.Schemas.Product do def changeset(struct, attrs) do struct - |> cast(attrs, [:name, :description, :tags, :account_id, :approved_at]) + |> cast(attrs, [:name, :description, :tags, :account_id, :approved_at, :ordered_at]) |> validate_required([:name]) |> maybe_generate_external_id() end