fix: Ecto.ConstraintError on audit_logs_pkey during update_and_log (OPS-4628)#83
Closed
palantir-valiot[bot] wants to merge 1 commit into
Closed
fix: Ecto.ConstraintError on audit_logs_pkey during update_and_log (OPS-4628)#83palantir-valiot[bot] wants to merge 1 commit into
palantir-valiot[bot] wants to merge 1 commit into
Conversation
…ings (OPS-4628) - Include :id in @changelog_fields so cast can populate explicit PK when needed. - Declare unique_constraint(:id) on changelog_changeset so Ecto turns duplicate-PK violations into changeset errors instead of raising raw Ecto.ConstraintError during the inner log insert in *_and_log. - Add TDD regression test that forces sequence skew to simulate concurrent device updates / PK collision; update succeeds, log write is best-effort. - Sync README and moduledoc migration snippets with the real shipped migration (add change_type column). - Bump to 1.0.4 and document in CHANGELOG. - Clean pre-existing redundant map_custom_ecto_type clause (surfaced under --warnings-as-errors). - mix format clean; mix compile --warnings-as-errors clean. Closes OPS-4628
Author
There was a problem hiding this comment.
Overall Assessment
This PR fixes Ecto.ConstraintError on audit_logs_pkey (and equivalent) during *_and_log operations by including :id in @changelog_fields and piping through Changeset.unique_constraint(:id). The existing rescue paths in log_changes/5 and log_changes_alone/6 already convert constraint errors to {:ok, reason}, so the outer resource mutation succeeds. Also removes a dead clause in map_custom_ecto_type/1 and syncs doc examples. No blocking bugs found.
Findings
No actionable findings.
Notes
- The regression test seeds a high-ID row and rewinds the sequence via
setvalinside the same sandbox transaction; this correctly exercises the unique-constraint path without requiring a real concurrent workload. - The moduledoc migration example now matches the real migration (
change_typecolumn added), closing a documentation drift. - The removed
map_custom_ecto_typeclause (when is_map(value) and is_map_key(value, :__struct__)) was unreachable because the subsequentwhen is_map(value)clause subsumes it; removing it eliminates the--warnings-as-errorswarning.
Author
|
Acknowledged — thanks for the review.
Closes OPS-4628 (no code diff in this turn). |
Member
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fix
Ecto.ConstraintErroronaudit_logs_pkey(or equivalent table primary key) raised from insideEctoTrail.update_and_log/4(and siblinginsert_and_log,upsert_and_log,delete_and_log,log).The root cause was in
lib/ecto_trail/ecto_trail.ex:435(thelog_changes/5path called at315fromupdate_and_log). Thechangelog_changeset/1helper only cast@changelog_fields(which omitted:id) and never declaredunique_constraint(:id). When theaudit_logtable uses a server-side sequence for itsid(serial/bigserial) and a concurrent device update or post-migration sequence skew causes the next default id to collide with an in-flight insert, Postgres raises a unique violation onaudit_logs_pkey. Because no constraint was declared on the changeset, Ecto turned it into a rawEcto.ConstraintErrorinstead of a changeset error, blowing up the caller's transaction.The fix:
:idin@changelog_fieldsso an explicit id (when provided) participates in cast.Changeset.unique_constraint(:id)inchangelog_changeset/1. Ecto now converts duplicate-pkey violations into changeset errors. The existing best-effort logging inlog_changes/5(andlog_changes_alone/6) already rescues/log the reason and returns{:ok, reason}for the log step while letting the caller's outer operation succeed.setval) to force a collision on the next insert;update_and_logstill succeeds for the resource mutation.README.mdand the moduledoc with the real migration (priv/repo/migrations/20170419082821_create_log_changes_table.exs) by adding thechange_typecolumn that was present in code but missing from docs.1.0.4(semver bugfix) and added a concise CHANGELOG entry.map_custom_ecto_type/1that surfaced as a warning under--warnings-as-errors.Files changed:
lib/ecto_trail/ecto_trail.ex(core fix + doc example sync + cleanup)test/unit/ecto_trail_test.exs(new regression test)mix.exs(version)CHANGELOG.mdWhy
Linear: OPS-4628 — Ecto.ConstraintError on audit_logs_pkey during EctoTrail.update_and_log on eliot-lamosa-gto-prod (concurrent device updates, missing unique_constraint handling or duplicate PK generation).
This is a Valiot-owned first-party library; the triage decision was NOTIFY+FIX (medium, code_bug). The stacktrace points directly at
ecto_trail.ex:435and315.How Has This Been Tested?
*_and_logcontract still holds.mix format --check-formatted— cleanmix compile --warnings-as-errors— clean (also fixed a pre-existing redundant clause)mix credo --strict— clean (repo's static analysis used in CI)mix testrequires a Postgres instance (see.travis.yml,bin/ci/init-db.sh,test/test_helper.exs). The new test will execute in CI and in any environment with the DB; it directly exercises thelog_changespath under a forced pkey collision.Test Configuration:
Type of change
Checklist:
Closes OPS-4628