--- phase: 06-sql-consolidation-code-quality plan: 01 type: execute wave: 1 depends_on: [] files_modified: - backend/database/database.go - backend/database/search.go - backend/database/sql/schemas/track_metadata_view.sql - backend/database/sql/sqlcgen/models.go autonomous: true requirements: [QUAL-01] must_haves: truths: - "All FTS5 search queries (SearchFTS, SearchFTSByFilename, SearchFTSTracks) use the track_metadata VIEW instead of inline 5-table JOINs" - "RebuildSearchIndex SELECTs from track_metadata VIEW instead of duplicating the JOIN" - "Migration 4 creates the track_metadata VIEW for existing databases" - "sqlc generate succeeds with the VIEW schema file and produces updated models" - "Existing FTS5 search tests (15 tests) pass unchanged after VIEW consolidation" artifacts: - path: "backend/database/sql/schemas/track_metadata_view.sql" provides: "VIEW definition for sqlc schema awareness" contains: "CREATE VIEW IF NOT EXISTS track_metadata" - path: "backend/database/database.go" provides: "Migration 4 creating VIEW for existing databases" contains: "migration4TrackMetadataView" - path: "backend/database/search.go" provides: "Consolidated search queries using VIEW" contains: "track_metadata" key_links: - from: "backend/database/search.go" to: "track_metadata VIEW" via: "JOIN track_metadata tm ON tm.id = si.rowid" pattern: "JOIN track_metadata" - from: "backend/database/database.go" to: "track_metadata VIEW" via: "migration 4 CREATE VIEW" pattern: "CREATE VIEW IF NOT EXISTS track_metadata" --- Consolidate the duplicated 5-table FTS5 JOIN pattern into a single SQLite VIEW named `track_metadata`, and update all search queries to use it. Purpose: Eliminate 4+ copies of the same complex JOIN across search.go and database.go. A single VIEW is the source of truth for audio file metadata JOINs — changes to the schema only need updating in one place. Output: Migration 4 (VIEW creation), sqlc schema file, consolidated search.go queries, updated sqlc-generated code. @/home/caleb/.config/opencode/get-shit-done/workflows/execute-plan.md @/home/caleb/.config/opencode/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/06-sql-consolidation-code-quality/06-RESEARCH.md @backend/database/database.go @backend/database/search.go @backend/database/sql/schemas/ @backend/database/sqlc.yaml From backend/database/database.go: - Migrations are Go functions registered in a slice, applied sequentially by PRAGMA user_version - Pattern: `migration2BasenameAndFTS`, `migration3UniqueArtistCreditArtist` — each bumps user_version - Current highest migration: 3 (user_version=3) - `//go:generate go tool sqlc generate` directive at line 21 From backend/database/search.go: - `func (d *DB) SearchFTS(query string, limit int) ([]SearchResult, error)` — line 22 - `func (d *DB) SearchFTSByFilename(query string, limit int) ([]SearchResult, error)` — line 72 - `func (d *DB) RebuildSearchIndex() error` — line 161 - `func (d *DB) SearchFTSTracks(query string, limit int) ([]SearchTrackResult, error)` — line 222 - All 4 functions contain inline 5-table JOINs (audio_files → recordings → artist_credit → release_group_recordings subquery → release_groups) From backend/database/sql/schemas/ directory: - Schema files sorted alphabetically; sqlc processes them in filesystem order - Tables: artist_credit.sql, artists.sql, audio_files.sql, cover_art.sql, file_types.sql, genres.sql, recordings.sql, release_group_recordings.sql, release_groups.sql, etc. - `track_metadata_view.sql` will sort after all table schemas (t > all existing prefixes) Task 1: Create track_metadata VIEW schema and migration backend/database/sql/schemas/track_metadata_view.sql backend/database/database.go 1. Create `backend/database/sql/schemas/track_metadata_view.sql` with the VIEW definition: ```sql CREATE VIEW IF NOT EXISTS track_metadata AS SELECT af.id, af.file_path, af.length_milliseconds, COALESCE(r.name, '') AS title, COALESCE(ac.text, '') AS artist_name, r.track_number, r.disc_number, COALESCE(rg.name, '') AS album, CAST(COALESCE( (SELECT GROUP_CONCAT(g.name, '||') FROM recording_genres rg_sub JOIN genres g ON rg_sub.genre_id = g.id WHERE rg_sub.recording_id = r.id), '' ) AS TEXT) AS genre, COALESCE(r.year, 0) AS year, COALESCE(r.composer, '') AS composer, COALESCE(ft.extension, '') AS file_type, af.sample_rate, af.bit_depth, af.channels, af.bitrate, af.file_size FROM audio_files af LEFT JOIN recordings r ON af.recording_id = r.id LEFT JOIN artist_credit ac ON r.artist_credit_id = ac.id LEFT JOIN ( SELECT recording_id, MIN(release_group_id) AS release_group_id FROM release_group_recordings GROUP BY recording_id ) rgr ON r.id = rgr.recording_id LEFT JOIN release_groups rg ON rgr.release_group_id = rg.id LEFT JOIN file_types ft ON af.file_type_id = ft.id; ``` 2. In `backend/database/database.go`, add migration 4 (`migration4TrackMetadataView`): - The migration function should execute `CREATE VIEW IF NOT EXISTS track_metadata AS ...` (same SQL as the schema file) - Register it in the migrations slice after migration 3 - Follow the existing migration function pattern (takes `*sql.DB` and `context.Context`, returns `error`) 3. Run `go tool sqlc generate` from `backend/database/` to regenerate code with VIEW awareness. 4. **CRITICAL:** Do NOT change `migration2BasenameAndFTS` to use the VIEW — migration 2 runs before migration 4 for databases upgrading from version 1. The inline JOIN in migration 2 must stay as-is. 5. Verify sqlc generate succeeds without errors. cd backend/database && go tool sqlc generate && echo "sqlc OK" - `track_metadata_view.sql` exists in schemas directory with the VIEW definition - Migration 4 registered in database.go, creates the VIEW for existing databases - `sqlc generate` succeeds and recognizes the VIEW - migration2 code is unchanged (still uses inline JOIN) Task 2: Consolidate search queries to use track_metadata VIEW backend/database/search.go Update all 4 search functions in `search.go` to use the `track_metadata` VIEW instead of inline JOINs: 1. **SearchFTS** (line ~22): Replace the inline 5-table JOIN with: ```sql SELECT tm.file_path, tm.length_milliseconds, tm.title, tm.artist_name, tm.album FROM search_index si JOIN track_metadata tm ON tm.id = si.rowid WHERE search_index MATCH ? ORDER BY rank LIMIT ? ``` Only select the 5 columns the function actually uses — SQLite optimizes away unused VIEW columns. 2. **SearchFTSByFilename** (line ~72): Same pattern as SearchFTS but with the filename-specific FTS query logic. Replace the inline JOIN with `JOIN track_metadata tm ON tm.id = si.rowid`. Keep the same column selection. 3. **SearchFTSTracks** (line ~222): Replace the inline 6-table JOIN (includes file_types) with the VIEW. The VIEW already includes `file_type` (from the file_types JOIN), so this becomes simpler. Select the columns needed by `SearchTrackResult`: file_path, length_milliseconds, title, artist_name, album, track_number, disc_number, genre, year, composer, file_type, sample_rate, bit_depth, channels, bitrate, file_size. 4. **RebuildSearchIndex** (line ~161): Replace the inline JOIN with: ```sql INSERT INTO search_index(rowid, file_path, title, artist, album) SELECT id, file_path, title, artist_name, album FROM track_metadata ``` **Preserve:** All FTS5 MATCH syntax, ORDER BY rank, LIMIT clauses, error handling, row scanning, and function signatures remain identical. Only the FROM/JOIN clauses change. **Do NOT touch:** `InsertSearchIndex`, `DeleteSearchIndex`, `ClearSearchIndex` — these are single-row FTS5 operations that don't use JOINs. cd backend && go test -tags webkit2_41 -race -count=1 -timeout 60s ./database/... - SearchFTS, SearchFTSByFilename, SearchFTSTracks, and RebuildSearchIndex all use `track_metadata` VIEW - No inline 5-table JOIN patterns remain in search.go (except in comments) - All 15 existing FTS5 search tests pass with -race - Function signatures unchanged — callers are unaffected ```bash # 1. Verify sqlc generates cleanly cd backend/database && go tool sqlc generate # 2. Verify all database tests pass (15 search tests + migrations) cd backend && go test -tags webkit2_41 -race -count=1 -timeout 60s ./database/... # 3. Verify no inline JOIN duplication remains in search.go grep -c "LEFT JOIN recordings" backend/database/search.go # Should be 0 # 4. Verify VIEW is referenced grep -c "track_metadata" backend/database/search.go # Should be 4+ # 5. Verify migration2 is unchanged grep "LEFT JOIN recordings" backend/database/database.go # Should still exist (migration2 only) # 6. Full build check go build -tags webkit2_41 ./... ``` - The duplicated 5-table JOIN pattern is eliminated from search.go (0 copies remain) - All search queries use the `track_metadata` VIEW - Migration 4 creates the VIEW for existing databases - sqlc schema file enables future sqlc queries against the VIEW - All 15 existing database tests pass with -race - Full project builds without errors After completion, create `.planning/phases/06-sql-consolidation-code-quality/06-01-SUMMARY.md`