diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bdbb9a..37c1045 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.4] - 2026-06-13 + +### Fixed + +- `changelog_changeset` now declares `unique_constraint(:id, name: "_pkey")` so `Ecto.ConstraintError` on `audit_logs_pkey` (or custom table pkey) during `log_changes/5` (called from `*_and_log` inside transactions) is turned into a changeset error instead of crashing the caller. The log failure path already swallows errors and returns `{:ok, reason}`. Closes OPS-4624. + ## [1.0.3] - 2026-05-28 ### Fixed diff --git a/lib/ecto_trail/ecto_trail.ex b/lib/ecto_trail/ecto_trail.ex index 74d7075..20a15ae 100644 --- a/lib/ecto_trail/ecto_trail.ex +++ b/lib/ecto_trail/ecto_trail.ex @@ -52,6 +52,8 @@ defmodule EctoTrail do # Cache frequently accessed config to avoid repeated lookups @redacted_fields_config Application.compile_env(:ecto_trail, :redacted_fields, nil) + @audit_log_table_name Application.compile_env(:ecto_trail, :table_name, "audit_log") + @audit_log_pkey_name "#{@audit_log_table_name}_pkey" @default_max_params 65_000 @changelog_fields [:actor_id, :resource, :resource_id, :changeset, :change_type] @not_loaded_pattern "Ecto.Association.NotLoaded" @@ -572,6 +574,8 @@ defmodule EctoTrail do defp map_custom_ecto_type(value), do: value defp changelog_changeset(attrs) do - Changeset.cast(%Changelog{}, attrs, @changelog_fields) + %Changelog{} + |> Changeset.cast(attrs, @changelog_fields) + |> Changeset.unique_constraint(:id, name: @audit_log_pkey_name) end end diff --git a/mix.exs b/mix.exs index c895a54..e08c811 100644 --- a/mix.exs +++ b/mix.exs @@ -1,7 +1,7 @@ defmodule EctoTrail.Mixfile do use Mix.Project - @version "1.0.3" + @version "1.0.4" def project do [ diff --git a/test/unit/ecto_trail_test.exs b/test/unit/ecto_trail_test.exs index 1a44768..ec978d7 100644 --- a/test/unit/ecto_trail_test.exs +++ b/test/unit/ecto_trail_test.exs @@ -329,4 +329,23 @@ defmodule EctoTrailTest do ) end end + + describe "audit log primary key constraint handling" do + test "does not raise Ecto.ConstraintError when audit log pkey is violated; main operation succeeds and log failure is swallowed" do + {:ok, _} = TestRepo.insert_and_log(%Resource{name: "seed"}, "actor-seed") + + %Postgrex.Result{rows: [[max_id]]} = TestRepo.query!("SELECT max(id) FROM audit_log") + next_id = max_id + 1 + + TestRepo.query!( + "INSERT INTO audit_log (id, actor_id, resource, resource_id, changeset, change_type, inserted_at) VALUES ($1, 'conflict', 'resources', '999', $2, 'insert', now())", + [next_id, %{}] + ) + + TestRepo.query!("SELECT setval('audit_log_id_seq', $1, false)", [max_id]) + + assert {:ok, %Resource{name: "after"}} = + TestRepo.insert_and_log(%Resource{name: "after"}, "actor-after") + end + end end