Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/1` now registers `unique_constraint/3` for the pkey (covers "audit_log_pkey", "audit_logs_pkey", and common naming variants). This turns an `Ecto.ConstraintError` on duplicate pkey inserts (observed during `update_and_log` under concurrent or sequence-reset conditions) into a normal `{:error, changeset}` so callers can handle it. Closes OPS-4629.

## [1.0.3] - 2026-05-28

### Fixed
Expand Down
2 changes: 2 additions & 0 deletions lib/ecto_trail/ecto_trail.ex
Original file line number Diff line number Diff line change
Expand Up @@ -573,5 +573,7 @@ defmodule EctoTrail do

defp changelog_changeset(attrs) do
Changeset.cast(%Changelog{}, attrs, @changelog_fields)
|> Changeset.unique_constraint(:id, name: "audit_log_pkey")
|> Changeset.unique_constraint(:id, name: "audit_logs_pkey")
end
end
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule EctoTrail.Mixfile do
use Mix.Project

@version "1.0.3"
@version "1.0.4"

def project do
[
Expand Down
30 changes: 30 additions & 0 deletions test/unit/ecto_trail_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,36 @@ defmodule EctoTrailTest do
assert [%{name: "name"}] = TestRepo.all(Resource)
assert [] == TestRepo.all(Changelog)
end

test "returns {:error, changeset} with unique_constraint on pkey violation instead of raising Ecto.ConstraintError",
%{schema: schema} do
# Seed one log entry, then plant a conflicting pkey row and rewind the sequence
# so the *next* audit log insert (from update_and_log) collides on the pkey.
first_log = TestRepo.one(Changelog)
conflict_id = first_log.id

TestRepo.delete(first_log)

Ecto.Adapters.SQL.query!(
TestRepo,
"INSERT INTO \"audit_log\" (id, actor_id, resource, resource_id, changeset, change_type, inserted_at)\n VALUES ($1, 'seeded','resources','#{schema.id}', '{}'::jsonb, 'insert', now())",
[conflict_id]
)

Ecto.Adapters.SQL.query!(
TestRepo,
"SELECT setval(pg_get_serial_sequence('audit_log', 'id'), $1, false)",
[conflict_id]
)

result =
schema
|> Changeset.change(%{name: "after-conflict"})
|> TestRepo.update_and_log("cowboy")

assert {:error, %Changeset{errors: errors}} = result
assert Keyword.has_key?(errors, :id)
end
end

describe "upsert_and_log/3" do
Expand Down