Enforce SQLite foreign keys on application connections - #65
Merged
Merged
Conversation
SQLite ignores declared FOREIGN KEY clauses unless each connection runs PRAGMA foreign_keys = ON. build_engine() now attaches an engine-scoped connect listener for SQLite URLs only, so orphan player_seasons and player_season_hitting rows are rejected. Alembic keeps its own engine with enforcement off; migrations were verified to pass either way. Closes #63 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Player models declare ForeignKeys but no relationship(), so SQLAlchemy's unit of work has no dependency edge between the players, player_seasons, and player_season_hitting mappers and orders their INSERT batches by module + class name. Valid writes only succeeded because PlayerRecord sorts first alphabetically. upsert_player() now flushes a newly added player immediately, so the parent row exists in the caller's open transaction before child rows reference it. Ingestion stays one transaction with one commit; updates and unchanged players do not flush. Harden the SQLite connect hook per SQLAlchemy's dialect guidance: run PRAGMA foreign_keys = ON with sqlite3 autocommit temporarily enabled, restore the previous mode, and read the pragma back, raising SQLiteForeignKeyEnforcementError if enforcement is not on. Tests run valid single-player and catalog ingestion (including reruns and updates) under both unit-of-work orders, prove a failed child write rolls back the flushed parent, and cover the hook with an already-open transaction. Docs now state precisely that Alembic runs on its own non-enforcing engine and that only the migrated result is checked under enforcement, correcting the earlier "pass either way" and "ORM flushes parents first" claims. Refs #63 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Member
Author
|
@claude review |
|
Claude finished @Mattsface's task in 25s —— View job Review of PR #65: Enforce SQLite foreign keys
I found no blocking issues. I read the code and did not run the test suite, so CI is the source for pass/fail. What I checked
Tests
Docs
Minor suggestions (non-blocking)
VerdictThe change is focused, matches the repository's architecture rules, and the limitations are documented. It looks good to merge once the CI checks ( |
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.
Summary
Closes #63.
SQLite parses declared
FOREIGN KEYconstraints but does not enforce them unless each connection enables:This PR enables FK enforcement for application-created SQLite connections and fixes Player persistence ordering so valid parent/child writes remain atomic and reliable under enforcement.
What changed
build_engine()using an engine-scoped connection listener.PlayerRecordbefore dependent season membership or hitting rows are added, without committing the transaction.Parent-before-child persistence
The Player mappers declare
ForeignKeyconstraints but do not define ORMrelationship()dependencies. With FK enforcement enabled, relying on incidental mapper ordering would be unsafe.upsert_player()now flushes a newly added Player row immediately so the parent exists inside the same open transaction beforeplayer_seasonsorplayer_season_hittingrows reference it.This is a flush only, not a commit. A later failure still rolls back the entire logical ingestion.
Alembic note
Alembic continues to construct its own engine in
alembic/env.pyand therefore does not use the application FK-enforcement hook.That is deliberate for this issue. The test suite verifies:
PRAGMA foreign_key_checkreports no violations.This PR does not claim that migrations themselves execute with FK enforcement enabled.
Verification
Expected PR checks: