fix: unique_constraint/3 on audit log pkey to prevent Ecto.ConstraintError in *_and_log (OPS-4629)#86
Conversation
…ngeset so *_and_log returns {:error, changeset} instead of raising Ecto.ConstraintError (OPS-4629)
There was a problem hiding this comment.
Overall Assessment
This PR adds unique_constraint(:id, name: ...) declarations for two common pkey constraint names ("audit_log_pkey", "audit_logs_pkey") inside changelog_changeset/1. This converts an unhandled Ecto.ConstraintError on duplicate audit log pkey inserts into a normal {:error, changeset} return value. The implementation follows Ecto's documented pattern and is minimal. No blocking bugs found.
Findings
No actionable findings.
Notes
- The fix is narrowly scoped to
changelog_changeset/1, which is the single code path used bylog/5,log_bulk/5, and all*_and_log/*functions before theirrepo.insert()calls. - The regression test follows TDD (written to fail first) and asserts the exact error shape (
Keyword.has_key?(errors, :id)) that callers need to handle gracefully. - The hardcoded constraint names are acceptable per the PR's stated scope (observed production names + README example migration); a fully dynamic approach would require additional configuration surface not justified by the bug report.
mix formatandmix compilepassed; the pre-existing redundant-map-clause warning was not introduced by this change.
|
Acknowledged review. No actionable findings; the diff is minimal and targeted. mix format + compile clean. Test added via TDD (infra in this env lacks Postgres so full No further changes. Re-affirming the same PR. |
Description
Bug fix:
EctoTrail.*_and_log/*(andlog/5) now return{:error, %Ecto.Changeset{}}with an:iderror instead of letting the database raise an unhandledEcto.ConstraintErroron the audit log primary key.Root cause
changelog_changeset/1(the only place that builds theEctoTrail.Changelogrow beforerepo.insert) never calledunique_constraint/3for the pkey. When a collision occurred onaudit_log_pkey/audit_logs_pkey(sequence rewind, concurrent writers, or explicit id reuse inside the caller's tx), Ecto had no constraint mapping and raised at the point shown in the stack:This is the exact identifiable code bug called out in the triage for OPS-4629.
Changes
lib/ecto_trail/ecto_trail.ex: addunique_constraint(:id, name: "audit_log_pkey")andunique_constraint(:id, name: "audit_logs_pkey")insidechangelog_changeset/1(covers the names observed in production and the example migration in the README).test/unit/ecto_trail_test.exs: TDD test underdescribe "update_and_log/3"that seeds a conflicting pkey row, rewinds the sequence, then callsupdate_and_logand asserts{:error, %Changeset{errors: errors}}withKeyword.has_key?(errors, :id).mix.exs: version bumped to 1.0.4 (semver patch for bug fix).CHANGELOG.md: one-line entry for the release.No other behavior or public API changed. The fix is the smallest possible change that satisfies Ecto's documented guidance in the original error message.
Fixes # (issue) — see Linear OPS-4629
Type of change
How Has This Been Tested?
mix format— clean.mix compile— clean (pre-existing unrelated warning on a redundant map clause remains).mix test— could not run to completion in the agent pod (Postgres is not reachable on localhost:5432; the test helper doesEcto.Migrator.runat startup and fails with connection refused). The test and fix are syntactically valid and compile; once a DB is present the new case will exercise the pkey collision that previously producedEcto.ConstraintError.Test Configuration:
TestRepo+ Ecto sandbox (no new test infrastructure)Checklist:
Summary
Register
unique_constraint/3for the audit log pkey so that all*_and_logpaths (andlog) surface a normal changeset error on duplicate pkey instead of raisingEcto.ConstraintError. One-line change inchangelog_changeset/1+ a regression test + version + changelog.Why
Matches the stack trace and triage decision for OPS-4629 (high-severity code bug in first-party valiot/ecto_trail). The error message from Ecto explicitly told callers to add the unique_constraint with the constraint
:name.Test plan
mix formatmix compilemix test(env limitation: no Postgres on localhost:5432 from the pod; TDD test added and will validate the fix)git diff; no debug prints, no scope creep, follows existing patterns and the Linear-mandated branch name.Closes OPS-4629