Skip to content

Commit a5f457e

Browse files
authored
improvement: read touch_update_defaults? from options instead of changeset context (#208)
fix: bulk_create with upsert now updates update_timestamp fields Ports ash-project/ash_postgres#697 to ash_sqlite. update_timestamp attributes (e.g. updated_at) were never included in the ON CONFLICT DO UPDATE SET clause because they have writable?: false. Now fields with update_defaults are always included when an upsert modifies fields. Can be disabled via context: %{data_layer: %{touch_update_defaults?: false}}
1 parent 21dd0b2 commit a5f457e

File tree

5 files changed

+77
-10
lines changed

5 files changed

+77
-10
lines changed

lib/data_layer.ex

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -811,9 +811,12 @@ defmodule AshSqlite.DataLayer do
811811
# Include fields with update_defaults (e.g. update_timestamp)
812812
# even if they aren't in the changeset attributes or upsert_fields.
813813
# These fields should always be refreshed when an upsert modifies fields.
814-
# Can be disabled via context: %{data_layer: %{touch_update_defaults?: false}}
814+
# Can be disabled via touch_update_defaults?: false in the changeset
815+
# context (either in [:private] or [:data_layer]) or via options map
815816
touch_update_defaults? =
816-
Enum.at(changesets, 0).context[:data_layer][:touch_update_defaults?] != false
817+
Map.get(options, :touch_update_defaults?, true) &&
818+
Enum.at(changesets, 0).context[:private][:touch_update_defaults?] != false &&
819+
Enum.at(changesets, 0).context[:data_layer][:touch_update_defaults?] != false
817820

818821
if touch_update_defaults? do
819822
update_default_fields =
@@ -1286,12 +1289,21 @@ defmodule AshSqlite.DataLayer do
12861289
def upsert(resource, changeset, keys \\ nil) do
12871290
keys = keys || Ash.Resource.Info.primary_key(keys)
12881291

1292+
touch_update_defaults? =
1293+
changeset.context[:private][:touch_update_defaults?] != false
1294+
12891295
update_defaults = update_defaults(resource)
12901296

12911297
explicitly_changing_attributes =
12921298
changeset.attributes
12931299
|> Map.keys()
1294-
|> Enum.concat(Keyword.keys(update_defaults))
1300+
|> then(fn attrs ->
1301+
if touch_update_defaults? do
1302+
Enum.concat(attrs, Keyword.keys(update_defaults))
1303+
else
1304+
attrs
1305+
end
1306+
end)
12951307
|> Kernel.--(Map.get(changeset, :defaults, []))
12961308
|> Kernel.--(keys)
12971309

@@ -1304,6 +1316,7 @@ defmodule AshSqlite.DataLayer do
13041316
tenant: changeset.tenant,
13051317
upsert_keys: keys,
13061318
upsert_fields: upsert_fields,
1319+
touch_update_defaults?: touch_update_defaults?,
13071320
return_records?: true
13081321
}) do
13091322
{:ok, []} ->

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ defmodule AshSqlite.MixProject do
146146
{:ecto_sqlite3, "~> 0.12"},
147147
{:ecto, "~> 3.13"},
148148
{:jason, "~> 1.0"},
149-
{:ash, ash_version("~> 3.9")},
149+
{:ash, ash_version("~> 3.19")},
150150
{:ash_sql, ash_sql_version("~> 0.2 and >= 0.2.20")},
151151
{:igniter, "~> 0.6 and >= 0.6.14", optional: true},
152152
{:simple_sat, ">= 0.0.0", only: [:dev, :test]},

mix.lock

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
%{
2-
"ash": {:hex, :ash, "3.16.0", "6389927b322ca7fa7990a75730133db44fcff6368adb63f41cf9eec7a5d38862", [:mix], [{:crux, ">= 0.1.2 and < 1.0.0-0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 1.0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.14 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.3", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1ea69d932ea2ae6cc2971b92576d8ac2721218a8f2f3599e0e25305edb56949b"},
2+
"ash": {:hex, :ash, "3.19.1", "b5e933547d948e44d27adaed5737195488292fc2066e7fe60dd3ac83a0c4e54f", [:mix], [{:crux, ">= 0.1.2 and < 1.0.0-0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 1.0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.14 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.3", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "697ac3e4fc6080cb03b1e4ee9088cb8a313a5299686ba1aa91efc86ec4028b6e"},
33
"ash_sql": {:hex, :ash_sql, "0.4.5", "30030675ce995570fcedccd3c0671d85beff03cc0c480e7da5002842dccf0277", [:mix], [{:ash, "~> 3.7", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, ">= 3.13.4 and < 4.0.0-0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "131e06e13ebcf06fc8d050267a5b29f6cc8ef6a781712e61a456f17726a64ea5"},
44
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
55
"cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"},
@@ -43,14 +43,14 @@
4343
"rewrite": {:hex, :rewrite, "1.2.0", "80220eb14010e175b67c939397e1a8cdaa2c32db6e2e0a9d5e23e45c0414ce21", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "a1cd702bbb9d51613ab21091f04a386d750fc6f4516b81900df082d78b2d8c50"},
4444
"simple_sat": {:hex, :simple_sat, "0.1.4", "39baf72cdca14f93c0b6ce2b6418b72bbb67da98fa9ca4384e2f79bbc299899d", [:mix], [], "hexpm", "3569b68e346a5fd7154b8d14173ff8bcc829f2eb7b088c30c3f42a383443930b"},
4545
"sobelow": {:hex, :sobelow, "0.14.1", "2f81e8632f15574cba2402bcddff5497b413c01e6f094bc0ab94e83c2f74db81", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8fac9a2bd90fdc4b15d6fca6e1608efb7f7c600fa75800813b794ee9364c87f2"},
46-
"sourceror": {:hex, :sourceror, "1.10.1", "325753ed460fe9fa34ebb4deda76d57b2e1507dcd78a5eb9e1c41bfb78b7cdfe", [:mix], [], "hexpm", "288f3079d93865cd1e3e20df5b884ef2cb440e0e03e8ae393624ee8a770ba588"},
47-
"spark": {:hex, :spark, "2.4.0", "f93d3ae6b5f3004e956d52f359fa40670366685447631bc7c058f4fbf250ebf3", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "4e5185f5737cd987bb9ef377ae3462a55b8312f5007c2bc4ad6e850d14ac0111"},
48-
"spitfire": {:hex, :spitfire, "0.3.5", "6e5a775256fec72d8a4f4bb19eb8b91ad64b6f64dc51b1c8b9689e78b16c6e8b", [:mix], [], "hexpm", "7ffcb11de2f6544868148f8fc996482040eb329a990e1624795e53598934a680"},
46+
"sourceror": {:hex, :sourceror, "1.11.0", "df2cdaffdc323e804009ff50b50bb31e6f2d6e116d936ccf22981f592594d624", [:mix], [], "hexpm", "6e26f572bdfc21d7ad397f596b4cfbbf31d7112126fe3e902c120947073231a8"},
47+
"spark": {:hex, :spark, "2.4.1", "d6807291e74b51f6efb6dd4e0d58216ae3729d45c35c456e049556e7e946e364", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "8b065733de9840cac584515f82182ac5ba66a973a47bc5036348dc740662b46b"},
48+
"spitfire": {:hex, :spitfire, "0.3.7", "d6051f94f554d33d038ab3c1d7e017293ae30429cc6b267b08cb6ad69e35e9a3", [:mix], [], "hexpm", "798ff97db02477b05fa3db8e2810cebda6ed5d90c6de6b21aa65abd577599744"},
4949
"splode": {:hex, :splode, "0.3.0", "ff8effecc509a51245df2f864ec78d849248647c37a75886033e3b1a53ca9470", [:mix], [], "hexpm", "73cfd0892d7316d6f2c93e6e8784bd6e137b2aa38443de52fd0a25171d106d81"},
5050
"stream_data": {:hex, :stream_data, "1.2.0", "58dd3f9e88afe27dc38bef26fce0c84a9e7a96772b2925c7b32cd2435697a52b", [:mix], [], "hexpm", "eb5c546ee3466920314643edf68943a5b14b32d1da9fe01698dc92b73f89a9ed"},
5151
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
5252
"text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"},
5353
"yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"},
54-
"yaml_elixir": {:hex, :yaml_elixir, "2.12.0", "30343ff5018637a64b1b7de1ed2a3ca03bc641410c1f311a4dbdc1ffbbf449c7", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "ca6bacae7bac917a7155dca0ab6149088aa7bc800c94d0fe18c5238f53b313c6"},
54+
"yaml_elixir": {:hex, :yaml_elixir, "2.12.1", "d74f2d82294651b58dac849c45a82aaea639766797359baff834b64439f6b3f4", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "d9ac16563c737d55f9bfeed7627489156b91268a3a21cd55c54eb2e335207fed"},
5555
"ymlr": {:hex, :ymlr, "5.1.4", "b924d61e1fc1ec371cde6ab3ccd9311110b1e052fc5c2460fb322e8380e7712a", [:mix], [], "hexpm", "75f16cf0709fbd911b30311a0359a7aa4b5476346c01882addefd5f2b1cfaa51"},
5656
}

test/bulk_create_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ defmodule AshSqlite.BulkCreateTest do
174174
upsert?: true,
175175
upsert_identity: :uniq_one_and_two,
176176
upsert_fields: [:price],
177-
context: %{data_layer: %{touch_update_defaults?: false}},
177+
touch_update_defaults?: false,
178178
return_stream?: true,
179179
return_errors?: true,
180180
return_records?: true

test/upsert_test.exs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,58 @@ defmodule AshSqlite.Test.UpsertTest do
6161
assert updated_post.id == id
6262
assert Decimal.equal?(updated_post.decimal, Decimal.new(5))
6363
end
64+
65+
test "upsert with touch_update_defaults? false does not update updated_at" do
66+
id = Ash.UUID.generate()
67+
past = DateTime.to_iso8601(DateTime.add(DateTime.utc_now(), -60, :second))
68+
69+
Post
70+
|> Ash.Changeset.for_create(:create, %{
71+
id: id,
72+
title: "title"
73+
})
74+
|> Ash.create!()
75+
76+
AshSqlite.TestRepo.query!("UPDATE posts SET updated_at = ? WHERE id = ?", [past, id])
77+
78+
assert [%{updated_at: backdated}] = Ash.read!(Post)
79+
assert DateTime.compare(backdated, DateTime.from_iso8601(past) |> elem(1)) == :eq
80+
81+
upserted =
82+
Post
83+
|> Ash.Changeset.for_create(:create, %{
84+
id: id,
85+
title: "title2"
86+
})
87+
|> Ash.create!(upsert?: true, touch_update_defaults?: false)
88+
89+
assert DateTime.compare(upserted.updated_at, DateTime.from_iso8601(past) |> elem(1)) == :eq
90+
end
91+
92+
test "upsert with empty upsert_fields does not update updated_at" do
93+
id = Ash.UUID.generate()
94+
past = DateTime.to_iso8601(DateTime.add(DateTime.utc_now(), -60, :second))
95+
96+
Post
97+
|> Ash.Changeset.for_create(:create, %{
98+
id: id,
99+
title: "title"
100+
})
101+
|> Ash.create!()
102+
103+
AshSqlite.TestRepo.query!("UPDATE posts SET updated_at = ? WHERE id = ?", [past, id])
104+
105+
assert [%{updated_at: backdated}] = Ash.read!(Post)
106+
assert DateTime.compare(backdated, DateTime.from_iso8601(past) |> elem(1)) == :eq
107+
108+
upserted =
109+
Post
110+
|> Ash.Changeset.for_create(:create, %{
111+
id: id,
112+
title: "title2"
113+
})
114+
|> Ash.create!(upsert?: true, upsert_fields: [])
115+
116+
assert DateTime.compare(upserted.updated_at, DateTime.from_iso8601(past) |> elem(1)) == :eq
117+
end
64118
end

0 commit comments

Comments
 (0)