Skip to content

feat(errors): classify PostgreSQL driver errors by taxonomy - #69

Merged
vistart merged 8 commits into
release/v1.0.0.dev17from
feature/error-taxonomy
Nov 11, 2026
Merged

vistart merged 8 commits into
release/v1.0.0.dev17from
feature/error-taxonomy

Conversation

@vistart

@vistart vistart commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

Description

This PR aligns python-activerecord-postgres with the new semantic error taxonomy and execute-phase contract:

  • Maps SQLSTATE 23505 unique, 23503/23502/23514 constraints, 40P01 deadlock, 55P03 lock, 40001 serialization, 25006 read-only, 57014 cancellation, 42xxx programming errors, 28xxx auth.
  • execute_many now shares the mapper, auto-commits, and preserves framework errors; retry checks transient.
  • Rollback is filtered for pre-SQL errors but COPY client errors still roll back explicit transactions.

Adds # (no issue)

Type of change

  • feat: A new feature
  • test: Adding missing tests or correcting existing tests (for the new feature)
  • docs: Documentation only changes (if related to the new feature)
  • perf: A code change that improves performance (if part of the feature)
  • chore: Changes to the build process or auxiliary tools and libraries suchs as documentation generation (if related to the new feature)
  • ci: Changes to our CI configuration files and scripts (if related to the new feature)
  • build: Changes that affect the build system or external dependencies (if related to the new feature)
  • revert: Reverts a previous commit

Breaking Change

  • Yes, for end-users (backward-incompatible API changes)
  • Yes, for backend developers (internal architectural changes that may affect custom implementations)
  • No breaking changes

Impact on End-Users:
No backward-incompatible changes: concrete error classes keep their names, and every error remains a DatabaseError. Behavioural change: auto-commit failures now raise TransactionError instead of being logged and ignored, and transient connection retries now actually run.

Impact on Backend Developers (Custom Implementations):
No signature changes. Custom _handle_error implementations should raise taxonomy errors; the core pipeline adds a defensive fallback when a handler returns.

Related Repositories/Packages

  • python-activerecord PR #139.
  • python-activerecord-testsuite PR (taxonomy contract).
  • CI pins in this repository currently point at feature/error-taxonomy for both core and testsuite, and will be restored to release branches after the merge.

Testing

  • I have added new tests to cover my changes.
  • I have updated existing tests to reflect my changes.
  • All existing tests pass.
  • This change has been tested against the backend matrix via CI.

Test Plan:

Local contract suites: 78 passed; COPY regression 4 passed. Feature-branch CI run 42976461108: success.

Reproduction:

PYTHONPATH=src:tests pytest tests -k 'test_execute_error_contract or test_driver_error_mapping or test_error_taxonomy_contract'

Checklist

  • My code follows the project's code style guidelines.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have made corresponding changes to the documentation (Docstrings, README.md, etc.).
  • My changes generate no new warnings.
  • I have added a Changelog fragment (changelog.d/<issue_number>.<type>.md).
  • I have verified that my changes do not introduce SQL injection vulnerabilities.
  • I have checked for potential performance regressions.

Additional Notes

  • The changelog fragment is added as changelog.d/<PR>.added.md once the PR number exists, because the checker requires the fragment number to equal the PR number.
  • CI intentionally installs core and testsuite from feature/error-taxonomy; restore release pins after this PR and the core PR merge.

@codecov

codecov Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 71.98444% with 72 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.13%. Comparing base (b2f6830) to head (a85774a).

Files with missing lines Patch % Lines
...activerecord/backend/impl/postgres/backend/sync.py 42.85% 29 Missing and 7 partials ⚠️
...ord/backend/impl/postgres/backend/async_backend.py 77.77% 10 Missing and 4 partials ⚠️
...iverecord/backend/impl/postgres/backend/backend.py 80.95% 9 Missing and 3 partials ⚠️
...activerecord/backend/impl/postgres/backend/base.py 84.84% 5 Missing and 5 partials ⚠️
Additional details and impacted files
@@                   Coverage Diff                    @@
##           release/v1.0.0.dev17      #69      +/-   ##
========================================================
+ Coverage                 78.07%   78.13%   +0.06%     
========================================================
  Files                       360      360              
  Lines                     19340    19399      +59     
  Branches                   3172     3183      +11     
========================================================
+ Hits                      15099    15158      +59     
+ Misses                     3109     3098      -11     
- Partials                   1132     1143      +11     
Flag Coverage Δ
postgres-18 78.13% <71.98%> (+0.06%) ⬆️
python-3.14 78.13% <71.98%> (+0.06%) ⬆️
unittests 78.13% <71.98%> (+0.06%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@vistart

vistart commented Nov 11, 2026

Copy link
Copy Markdown
Contributor Author

Additional fix included in this branch (discovered by the four-language introspection porting wave on live servers):

format_unused_indexes_query_statement read indisvalid from pg_stat_user_indexes

pg_stat_user_indexes is the statistics view and carries only counters (relid, indexrelid, schemaname, relname, indexrelname, idx_scan, ...); indisvalid lives on pg_index. Naming it on the statistics view made the server reject the whole statement at parse time:

ERROR: column i.indisvalid does not exist

so list_unused_indexes() / introspect unused-indexes could not answer on any real PostgreSQL server.

Fix: join pg_index AS ix ON i.indexrelid = ix.indexrelid (index oid on both sides) and test ix.indisvalid; i.idx_scan = 0 stays on the statistics view. The two conditions keep their distinct meanings: "no scan in the statistics window" vs "the planner would select it".

Tests added in tests/.../introspection/test_introspection_unused_indexes.py:

  • offline shape assertions: pg_index AS ix, ix.indisvalid, i.indexrelid = ix.indexrelid, no i.indisvalid, i.idx_scan still on i;
  • live sync + async cases calling list_unused_indexes(schema=None) and asserting the record shape.

Verified against the remote scenario matrix: 44 passed (2 live tests × all PostgreSQL scenarios) plus 4 offline shape tests. A changelog.d/69.fixed.md fragment was added as well.

@sonarqubecloud

Copy link
Copy Markdown

@vistart vistart added enhancement New feature or request and removed feature labels Nov 11, 2026
@vistart vistart self-assigned this Nov 11, 2026
@vistart
vistart merged commit 31dfac0 into release/v1.0.0.dev17 Nov 11, 2026
16 of 17 checks passed
vistart added a commit that referenced this pull request Nov 11, 2026
* feat(errors): classify PostgreSQL driver errors by taxonomy

* fix(execute): align PostgreSQL execute error phases

* fix(execute): align PostgreSQL execute_many and test parity

* ci: pin core and testsuite feature/error-taxonomy branches

* fix(postgres): roll back COPY client errors in explicit transactions

* docs(changelog): add fragment for the error taxonomy PR (#69)

* ci: mark feature-branch pip installs as reviewed (#69)

* fix(postgres): read indisvalid from pg_index in the unused-indexes listing

Includes the two forms the class-body sweep could not see: consumers that read the removed dunders now call the accessor, and runtime assignments that used to declare a table name or schema dynamically now install the classmethod an override would declare.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant