Declare the unique indexes production already has - #741
Open
mircealungu wants to merge 1 commit into
Open
mircealungu wants to merge 1 commit into
mircealungu wants to merge 1 commit into
Conversation
The test database is built by db.create_all() from the models, so a UNIQUE index that exists only in production is absent from the SQLite schema tests run against. Tests then happily create rows MySQL would refuse, and a test written to exercise a duplicate-key race passes against entirely unfixed code. That has already produced two bugs in a week (#733 for user_language, #738 for basic_sr_schedule). Migrations are not the source of truth here -- user_language's index appears in no migration at all -- so this compares the live production index list against what SQLAlchemy actually puts on db.metadata, rather than against tools/migrations/*.sql or a reading of the model files. Ten indexes were missing. Each is declared under production's exact name, so nobody later generates a second one under a tidier name: context_type unique_context_type level_adapted_article_text uq_article_level new_text HASH_INDEX source_text HASH_INDEX starred_article url_id teacher_cohort_map user_id_2 text content_hash user_avatar user_id user_onboarding_message ux_user_onboarding_message_user_message user_word unique_user_word starred_article needed a second fix. Its constraint was already declared as a bare UniqueConstraint in the class body -- a form that does attach to the table, contrary to the comment in 26-05-26-a--dedupe-and-unique-user-video.sql -- but the module was missing from zeeguu.core.model's imports, so the table never reached db.create_all() and the test database had no starred_article at all. Moved into __table_args__ under production's name and imported. No DDL and no migration: production already has every one of these. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
ArchLens - No architecturally relevant changes to the existing views |
This was referenced Sep 11, 2026
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.
Why
The test database is built by
db.create_all()from the models. A UNIQUE index that exists only in production is therefore absent from the SQLite schema tests run against — tests create rows MySQL would refuse, and a test written to exercise a duplicate-key race passes against entirely unfixed code.That has already produced two bugs in one week: #733 (
user_language) and #738 (basic_sr_schedule).How the list was built
Migrations are not the source of truth —
user_language's unique index appears in no migration file at all. So this compares the live production index list against what SQLAlchemy actually puts ondb.metadata(dumped from the real metadata, not from reading model files).That distinction mattered twice:
user_mwe_override/unique_user_article_sentence_mweandexercise_report/unique_user_bookmark_sourceare in checked-in migrations but not in production — declaring them would have made tests stricter than prod.teacher_cohort_map,text,source_text,new_textanduser_avatarhave indexes in production that a migration sweep alone would have ranked differently.Production had 43 unique indexes; the models declared 39, overlapping imperfectly. Ten were missing.
What changed
Each is declared under production's exact index name, so nobody later generates a conflicting migration:
context_typeunique_context_typetypelevel_adapted_article_textuq_article_levelarticle_id, cefr_levelnew_textHASH_INDEXcontent_hashsource_textHASH_INDEXcontent_hashstarred_articleurl_idurl_id, user_idteacher_cohort_mapuser_id_2user_id, cohort_idtextcontent_hashcontent_hashuser_avataruser_iduser_iduser_onboarding_messageux_user_onboarding_message_user_messageuser_id, onboarding_message_iduser_wordunique_user_worduser_id, meaning_idSome of those names are ugly (
user_id_2,HASH_INDEX,url_id). They are production's, and matching them is the point.starred_articleneeded a second fixIts constraint was already declared as a bare
UniqueConstraint(url_id, user_id)in the class body — a form that does attach to the table, contrary to the comment intools/migrations/26-05-26-a--dedupe-and-unique-user-video.sql:2which calls it "a no-op outside__table_args__". The actual problem was that the module was missing fromzeeguu/core/model/__init__.py, so the table never reacheddb.create_all()and the test database had nostarred_articletable at all. Moved into__table_args__under production's name, and imported.Not included, deliberately
basic_sr_schedule/unique_user_word_schedule— belongs to Keep the learner's answer when a scheduling race is lost #738, still open. Left alone to avoid conflicting.ai_models/model_name— a stale table.25-07-31renamedai_models→ai_generatorand25-08-20-bprepped dropping it; it was never dropped. No model maps to it. (Worth dropping in prod separately.)exercise_outcome/idandexercise_source/id— unique indexes duplicating the primary key. Declaringunique=Trueon a PK column is noise.topic_user_feedback/article_id— confirmed stale. Production holds both tables:article_topic_user_feedback(138 rows, the live one the model maps to) andtopic_user_feedback(0 rows, the leftover carrying the index). Nothing to declare.A gap in the other direction, worth a separate look
The same comparison run backwards finds 11 constraints the models declare that production does not have — so production can already hold rows the test database refuses. Not fixable from the model side and out of scope here, but notable:
article.source_idandvideo.source_id—CLAUDE.mdstates the Article↔Source and Video↔Source one-to-one is "enforced by unique constraint on source_id". In production it is not enforced at all.session.uuid— no unique index in production.level_adapted_article_summary_context/uq_alsc_bookmark_summary— added by26-08-12as(bookmark_id, article_level_summary_id)and absent from production, while its siblinguq_latc_bookmark_titleis present. Likely lost when the column was renamed tolevel_adapted_article_text_id.article.img_url_id,phrase(content, language_id),search_filter,search_subscription,topic_filter,topic_subscription,article_topic_user_feedback.Each needs its production data checked for duplicates before any could be added as DDL.
Testing
Full suite after each individual addition, not just at the end:
Baseline 494 passed, 12 subtests; after every one of the eleven changes, still 494 passed, 12 subtests. No test turned out to be relying on the laxer schema.
Scope
Code-only. No DDL, no migration — production already has every one of these indexes, so there is nothing to apply. Nothing to deploy beyond the normal API release.
🤖 Generated with Claude Code