mirror of
https://github.com/navidrome/navidrome.git
synced 2025-12-23 23:18:05 -05:00
fix(scanner): prevent foreign key constraint error in tag UpdateCounts (#4370)
* fix: prevent foreign key constraint error in tag UpdateCounts Added JOIN clause with tag table in UpdateCounts SQL query to filter out tag IDs from JSON that don't exist in the tag table. This prevents 'FOREIGN KEY constraint failed' errors when the library_tag table tries to reference non-existent tag IDs during scanner operations. The fix ensures only valid tag references are counted while maintaining data integrity and preventing scanner failures during library updates. * test(tag): add regression tests for foreign key constraint fix Add comprehensive regression tests to prevent the foreign key constraint error when tag IDs in JSON data don't exist in the tag table. Tests cover both album and media file scenarios with non-existent tag IDs. - Test UpdateCounts() with albums containing non-existent tag IDs - Test UpdateCounts() with media files containing non-existent tag IDs - Verify operations complete without foreign key errors Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
@@ -56,6 +56,7 @@ INSERT INTO library_tag (tag_id, library_id, %[1]s_count)
|
||||
SELECT jt.value as tag_id, %[1]s.library_id, count(distinct %[1]s.id) as %[1]s_count
|
||||
FROM %[1]s
|
||||
JOIN json_tree(%[1]s.tags, '$.genre') as jt ON jt.atom IS NOT NULL AND jt.key = 'id'
|
||||
JOIN tag ON tag.id = jt.value
|
||||
GROUP BY jt.value, %[1]s.library_id
|
||||
ON CONFLICT (tag_id, library_id)
|
||||
DO UPDATE SET %[1]s_count = excluded.%[1]s_count;
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/navidrome/navidrome/model/request"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/pocketbase/dbx"
|
||||
)
|
||||
|
||||
var _ = Describe("TagRepository", func() {
|
||||
@@ -135,6 +136,67 @@ var _ = Describe("TagRepository", func() {
|
||||
err = repo.UpdateCounts()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should handle albums with non-existent tag IDs in JSON gracefully", func() {
|
||||
// Regression test for foreign key constraint error
|
||||
// Create an album with tag IDs in JSON that don't exist in tag table
|
||||
db := GetDBXBuilder()
|
||||
|
||||
// First, create a non-existent tag ID (this simulates tags in JSON that aren't in tag table)
|
||||
nonExistentTagID := id.NewTagID("genre", "nonexistent-genre")
|
||||
|
||||
// Create album with JSON containing the non-existent tag ID
|
||||
albumWithBadTags := `{"genre":[{"id":"` + nonExistentTagID + `","value":"nonexistent-genre"}]}`
|
||||
|
||||
// Insert album directly into database with the problematic JSON
|
||||
_, err := db.NewQuery("INSERT INTO album (id, name, library_id, tags) VALUES ({:id}, {:name}, {:lib}, {:tags})").
|
||||
Bind(dbx.Params{
|
||||
"id": "test-album-bad-tags",
|
||||
"name": "Album With Bad Tags",
|
||||
"lib": 1,
|
||||
"tags": albumWithBadTags,
|
||||
}).Execute()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// This should not fail with foreign key constraint error
|
||||
err = repo.UpdateCounts()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Cleanup
|
||||
_, err = db.NewQuery("DELETE FROM album WHERE id = {:id}").
|
||||
Bind(dbx.Params{"id": "test-album-bad-tags"}).Execute()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should handle media files with non-existent tag IDs in JSON gracefully", func() {
|
||||
// Regression test for foreign key constraint error with media files
|
||||
db := GetDBXBuilder()
|
||||
|
||||
// Create a non-existent tag ID
|
||||
nonExistentTagID := id.NewTagID("genre", "another-nonexistent-genre")
|
||||
|
||||
// Create media file with JSON containing the non-existent tag ID
|
||||
mediaFileWithBadTags := `{"genre":[{"id":"` + nonExistentTagID + `","value":"another-nonexistent-genre"}]}`
|
||||
|
||||
// Insert media file directly into database with the problematic JSON
|
||||
_, err := db.NewQuery("INSERT INTO media_file (id, title, library_id, tags) VALUES ({:id}, {:title}, {:lib}, {:tags})").
|
||||
Bind(dbx.Params{
|
||||
"id": "test-media-bad-tags",
|
||||
"title": "Media File With Bad Tags",
|
||||
"lib": 1,
|
||||
"tags": mediaFileWithBadTags,
|
||||
}).Execute()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// This should not fail with foreign key constraint error
|
||||
err = repo.UpdateCounts()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Cleanup
|
||||
_, err = db.NewQuery("DELETE FROM media_file WHERE id = {:id}").
|
||||
Bind(dbx.Params{"id": "test-media-bad-tags"}).Execute()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Count", func() {
|
||||
|
||||
Reference in New Issue
Block a user