Fix 502: extract materialized views into canonical files, detect migration collisions - #587
Merged
Merged
Conversation
…ation collisions Two concurrent PRs (#581, #583) created conflicting 0049_* migrations that both DROP and recreate v_activity/activity_summary. The second migration set statement_timeout=60s which caused CREATE MATERIALIZED VIEW to timeout, leaving views dropped but not recreated — crash-looping the container and causing 502s. Root cause: PostgreSQL materialized views can't be ALTERed to add columns, so any change requires DROP+CREATE. When two migrations do this independently, the second clobbers the first's changes. This is a systemic migration problem. Fix: - Extract view definitions into canonical files (drizzle/views/) that are the single source of truth. The migration runner recreates all views from these files after every migration run (similar to Flyway's repeatable migrations). - Add duplicate migration prefix detection — the runner now refuses to run if two files share a numeric prefix (e.g., two 0049_* files), catching this class of conflict at startup instead of silently corrupting state. - Merge both 0049 migrations' v_activity changes (timezone + source_external_ids) into the canonical v_activity.sql definition. - Remove statement_timeout from migrations (was causing view creation to timeout). - Strip view DROP/CREATE from individual migrations (now handled by canonical files). https://claude.ai/code/session_01YWKdigbgtBfQYnkq4LwZHo
Asherlc
enabled auto-merge (squash)
March 28, 2026 00:34
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #587 +/- ##
=======================================
Coverage 96.31% 96.32%
=======================================
Files 258 258
Lines 38316 38370 +54
Branches 7620 7641 +21
=======================================
+ Hits 36905 36959 +54
Misses 1411 1411
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
…ations for duplicates - test-helpers.ts: recreate materialized views from drizzle/views/ after running migrations (integration tests were missing source_external_ids) - migrate.ts: only check pending migrations for duplicate prefixes, not all files (old migrations have legitimate duplicate prefixes from before this safeguard existed) https://claude.ai/code/session_01YWKdigbgtBfQYnkq4LwZHo
…discovery - Rename drizzle/views/ to drizzle/_views/ — vitest auto-discovers directories named "views" as workspace roots, breaking project resolution - Add numeric prefixes (01_, 02_) to view files for dependency ordering: 01_v_activity.sql must be created before 02_activity_summary.sql - Drop existing views by querying pg_matviews instead of deriving names from filenames (avoids prefix-in-name issues) https://claude.ai/code/session_01YWKdigbgtBfQYnkq4LwZHo
The recreateViews function was dropping ALL materialized views in the fitness schema but only recreating v_activity and activity_summary. This broke v_daily_metrics, v_sleep, and other views. Now only drops views that have canonical definitions in drizzle/_views/. Also adds matviews, matviewname, schemaname to cspell dictionary. https://claude.ai/code/session_01YWKdigbgtBfQYnkq4LwZHo
Restructure parallel arrays into a single mapped array of objects, removing the unreachable `if (!content) continue` guard that would fail mutation testing. https://claude.ai/code/session_01YWKdigbgtBfQYnkq4LwZHo
Covers the `if (!viewName) continue` branch in recreateViews to prevent surviving mutation. https://claude.ai/code/session_01YWKdigbgtBfQYnkq4LwZHo
On a fresh DB (E2E), all migrations are pending including historical duplicates (0018, 0022, 0023, 0024, 0041). Throwing an error blocks fresh-DB migrations entirely. Log a warning instead. https://claude.ai/code/session_01YWKdigbgtBfQYnkq4LwZHo
Kills Stryker mutant that survived by removing the logger.warn call — the test now verifies the warning is actually emitted. https://claude.ai/code/session_01YWKdigbgtBfQYnkq4LwZHo
Verify info logging for migration application, view recreation, and the count>0 conditional. Ensures removing logger calls fails tests. https://claude.ai/code/session_01YWKdigbgtBfQYnkq4LwZHo
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.
Two concurrent PRs (#581, #583) created conflicting 0049_* migrations that both
DROP and recreate v_activity/activity_summary. The second migration set
statement_timeout=60s which caused CREATE MATERIALIZED VIEW to timeout, leaving
views dropped but not recreated — crash-looping the container and causing 502s.
Root cause: PostgreSQL materialized views can't be ALTERed to add columns, so
any change requires DROP+CREATE. When two migrations do this independently,
the second clobbers the first's changes. This is a systemic migration problem.
Fix:
single source of truth. The migration runner recreates all views from these
files after every migration run (similar to Flyway's repeatable migrations).
two files share a numeric prefix (e.g., two 0049_* files), catching this
class of conflict at startup instead of silently corrupting state.
into the canonical v_activity.sql definition.
https://claude.ai/code/session_01YWKdigbgtBfQYnkq4LwZHo