Use automated naming convention to generate indexes and constraints in database #16089
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.
Solves issue uncovered via work on #16003.
This adds an automated naming convention for database indexes and constraints added by SQLAlchemy and Alembic. This ensures that indexes and constraints will always have deterministic names regardless of the database (PostgreSQL, SQLite, etc.) and of whether they were explicitly named or not (e.g. added inline via Column parameters). This is needed for correct handling of downgrade revision scripts (as well as consistency of constraint and index naming in the database overall).
The issue surfaced thanks to a failed test, which broke on a downgrade migration after a new index had been added to the model definition. The index was unnamed (added via
index=True), and the name that was assigned to it by the database was different from the name used in the revision's downgrade script - hence, the test failed when the script tried to drop a nonexistent index. This bug will go unnoticed if the new migration is applied to an existing database (since the index is added via the revision script where it is explicitly named); however, if a new database is initialized, migrations are bypassed, which will trigger the bug on a subsequent downgrade.Setting the object's name to match whatever is generated by the database by default is easy for postgres (e.g.
[table-name]_[column-name]_fkey). However, SQLite does things differently: to quote Alembic's docs, "SQLite, unlike any other database, allows constraints to exist in the database that have no identifying name." [ref]. The solution is to either (a) always give objects explicit names (which is tedious and error-prone: no more inline specification of indexes and foreign keys!); or (b) to use an automated naming convention, recommended by SQLAlchemy and Alembic (which has a whole section on The Importance of Naming Constraints).This has been on my to-do list for a long while. It finally bit me (thanks, @davelopez 😄)
How to test the changes?
(Select all options that apply)
./manage_db.sh upgrade; ./manage_db.sh downgrade base; ./manage_db.sh upgrade: this verifies that the helper methods inmigrations.utils.DbObjectNamesused in existing revision scripts do not affect the state of an existing database.index=TrueColumn attribute (i.e., index is not explicitly named). Repeat step 1. This verifies that the naming convention specified ingalaxy.modelis automatically applied in a revision script. Remove the revision after testing.pytest test/unit/app/test_migrate_database.py. This verifies that the naming convention is automatically applied when initializing a new database (and bypassing migrations), and that the names generated by the model are identical to those generated by the migration utilityDbObjectNames.License