package db import ( "context" "strings" ) // UpsertFileContent replaces the FTS content for a given file_id. func (q *Queries) UpsertFileContent(ctx context.Context, fileID int64, content string) error { // Delete existing content for this file if _, err := q.db.ExecContext(ctx, `DELETE FROM file_contents WHERE file_id = ?`, fileID); err != nil { return err } // Insert new content _, err := q.db.ExecContext(ctx, `INSERT INTO file_contents (file_id, content) VALUES (?, ?)`, fileID, content) return err } // DeleteFileContentByFileID removes FTS content for a file. func (q *Queries) DeleteFileContentByFileID(ctx context.Context, fileID int64) error { _, err := q.db.ExecContext(ctx, `DELETE FROM file_contents WHERE file_id = ?`, fileID) return err } // DeleteStaleFileContents removes FTS content for files not in the given path list. func (q *Queries) DeleteStaleFileContents(ctx context.Context, paths []string) error { if len(paths) == 0 { _, err := q.db.ExecContext(ctx, `DELETE FROM file_contents`) return err } placeholders := make([]string, len(paths)) args := make([]interface{}, len(paths)) for i, p := range paths { placeholders[i] = "?" args[i] = p } query := `DELETE FROM file_contents WHERE file_id NOT IN ( SELECT id FROM files WHERE path IN (` + strings.Join(placeholders, ",") + `) )` _, err := q.db.ExecContext(ctx, query, args...) return err } // SearchResult holds a single FTS search hit. type SearchResult struct { FileID int64 Path string Snippet string } // SearchFileContents performs a full-text search across all file contents. // Returns matching file paths with a snippet of the match context. func (q *Queries) SearchFileContents(ctx context.Context, query string, limit int) ([]SearchResult, error) { rows, err := q.db.QueryContext(ctx, ` SELECT fc.file_id, f.path, snippet(file_contents, 1, '>>>', '<<<', '...', 20) FROM file_contents fc JOIN files f ON f.id = fc.file_id WHERE file_contents MATCH ? ORDER BY rank LIMIT ? `, query, limit) if err != nil { return nil, err } defer rows.Close() var results []SearchResult for rows.Next() { var r SearchResult if err := rows.Scan(&r.FileID, &r.Path, &r.Snippet); err != nil { return nil, err } results = append(results, r) } return results, rows.Err() }