docs(06): create phase plan — 3 plans for SQL consolidation, event codegen, and SAFETY comments
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
---
|
||||
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>
|
||||
Reference in New Issue
Block a user