fix: drop+recreate contentless FTS5 index instead of DELETE
The search_index is a contentless FTS5 table (content=''), which SQLite does not support DELETE on. ClearSearchIndex now drops and recreates the virtual table. Single-row DeleteSearchIndex becomes a no-op since contentless FTS5 also cannot delete individual rows; stale entries are harmless (search JOINs filter them out) and the index is fully rebuilt during FullRescan.
This commit is contained in:
@@ -860,7 +860,12 @@ func (l *Library) updateAudioFileMetadata(
|
||||
)
|
||||
}
|
||||
|
||||
// Index in FTS5 search_index (delete old entry, insert new).
|
||||
// Re-index in FTS5 search_index.
|
||||
// Contentless FTS5 (content='') does not support DELETE, so we
|
||||
// cannot remove the old entry. Inserting a new row with the
|
||||
// same rowid is accepted by FTS5 — the old entry becomes stale
|
||||
// but harmless (search JOINs against track_metadata filter it).
|
||||
// The index is fully rebuilt during FullRescan.
|
||||
tags := result.tags
|
||||
if tags == nil {
|
||||
tags = &metadata.TrackMetadata{}
|
||||
@@ -875,21 +880,6 @@ func (l *Library) updateAudioFileMetadata(
|
||||
|
||||
album := tags.Album
|
||||
|
||||
// SAFETY: FTS5 virtual table, see search.go:DeleteSearchIndex. Rowid parameterized.
|
||||
if _, err := tx.ExecContext(
|
||||
l.ctx,
|
||||
`DELETE FROM search_index WHERE rowid = ?`,
|
||||
result.existingFileID,
|
||||
); err != nil {
|
||||
l.logger.Warn(
|
||||
"could not remove old FTS entry",
|
||||
"id", result.existingFileID,
|
||||
"err", err,
|
||||
)
|
||||
|
||||
metrics.addWarning(result.absolutePath, "commit", err)
|
||||
}
|
||||
|
||||
// SAFETY: FTS5 virtual table, see search.go:InsertSearchIndex. All values parameterized.
|
||||
if _, err := tx.ExecContext(
|
||||
l.ctx,
|
||||
|
||||
@@ -160,22 +160,21 @@ func (l *Library) clearLibraryTables() error {
|
||||
)
|
||||
}
|
||||
|
||||
// Clear FTS5 search index.
|
||||
// SAFETY: FTS5 virtual table, see search.go:ClearSearchIndex. No parameters; unconditional delete.
|
||||
if _, err := tx.ExecContext(
|
||||
l.ctx, `DELETE FROM search_index`,
|
||||
); err != nil {
|
||||
return fmt.Errorf(
|
||||
"could not clear search index: %w", err,
|
||||
)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return fmt.Errorf(
|
||||
"could not commit library clear transaction: %w", err,
|
||||
)
|
||||
}
|
||||
|
||||
// Clear FTS5 search index AFTER the transaction.
|
||||
// ClearSearchIndex drops and recreates the contentless FTS5
|
||||
// virtual table, which cannot run inside a transaction.
|
||||
if err := l.db.ClearSearchIndex(); err != nil {
|
||||
return fmt.Errorf(
|
||||
"could not clear search index: %w", err,
|
||||
)
|
||||
}
|
||||
|
||||
l.logger.Info("all library tables cleared")
|
||||
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user