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

242 lines
10 KiB
Markdown

---
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"
---
<objective>
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.
</objective>
<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>
<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
<interfaces>
<!-- Key types and contracts the executor needs. -->
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)
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Create track_metadata VIEW schema and migration</name>
<files>
backend/database/sql/schemas/track_metadata_view.sql
backend/database/database.go
</files>
<action>
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.
</action>
<verify>
<automated>cd backend/database && go tool sqlc generate && echo "sqlc OK"</automated>
</verify>
<done>
- `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)
</done>
</task>
<task type="auto">
<name>Task 2: Consolidate search queries to use track_metadata VIEW</name>
<files>
backend/database/search.go
</files>
<action>
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.
</action>
<verify>
<automated>cd backend && go test -tags webkit2_41 -race -count=1 -timeout 60s ./database/...</automated>
</verify>
<done>
- 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
</done>
</task>
</tasks>
<verification>
```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>