Files
yellowjacket/.planning/phases/06-sql-consolidation-code-quality/06-01-PLAN.md
T

10 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
06-sql-consolidation-code-quality 01 execute 1
backend/database/database.go
backend/database/search.go
backend/database/sql/schemas/track_metadata_view.sql
backend/database/sql/sqlcgen/models.go
true
QUAL-01
truths artifacts key_links
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
path provides contains
backend/database/sql/schemas/track_metadata_view.sql VIEW definition for sqlc schema awareness CREATE VIEW IF NOT EXISTS track_metadata
path provides contains
backend/database/database.go Migration 4 creating VIEW for existing databases migration4TrackMetadataView
path provides contains
backend/database/search.go Consolidated search queries using VIEW track_metadata
from to via pattern
backend/database/search.go track_metadata VIEW JOIN track_metadata tm ON tm.id = si.rowid JOIN track_metadata
from to via pattern
backend/database/database.go track_metadata VIEW migration 4 CREATE VIEW 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.

<execution_context> @/home/caleb/.config/opencode/get-shit-done/workflows/execute-plan.md @/home/caleb/.config/opencode/get-shit-done/templates/summary.md </execution_context>

@.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 ./...

</verification>

<success_criteria>
- 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
</success_criteria>

<output>
After completion, create `.planning/phases/06-sql-consolidation-code-quality/06-01-SUMMARY.md`
</output>