fix: resolve image duplicate finder issues

- Wrap FindDuplicateImages query in r.withReadTxn() to ensure a database transaction in context.
- Use queryFunc instead of queryStruct for fetching multiple hashes, preventing runtime errors.
- Fix N+1 query issue in duplicate grouping by using qb.FindMany() instead of qb.Find() for each duplicate image.
- Revert searchColumns array to exclude "images.details" which was from another PR and remove related failing test.
This commit is contained in:
notsafeforgit 2026-03-13 18:39:34 -07:00
parent 8358c794ba
commit 9295655d19
3 changed files with 25 additions and 31 deletions

View file

@ -135,6 +135,13 @@ func (r *queryResolver) AllImages(ctx context.Context) (ret []*models.Image, err
return ret, nil
}
func (r *queryResolver) FindDuplicateImages(ctx context.Context, distance int) ([][]*models.Image, error) {
return r.repository.Image.FindDuplicates(ctx, distance)
func (r *queryResolver) FindDuplicateImages(ctx context.Context, distance int) (ret [][]*models.Image, err error) {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
ret, err = r.repository.Image.FindDuplicates(ctx, distance)
return err
}); err != nil {
return nil, err
}
return ret, nil
}

View file

@ -838,7 +838,7 @@ func (qb *ImageStore) makeQuery(ctx context.Context, imageFilter *models.ImageFi
)
filepathColumn := "folders.path || '" + string(filepath.Separator) + "' || files.basename"
searchColumns := []string{"images.title", "images.details", filepathColumn, "files_fingerprints.fingerprint"}
searchColumns := []string{"images.title", filepathColumn, "files_fingerprints.fingerprint"}
query.parseQueryString(searchColumns, *q)
}
@ -1104,29 +1104,30 @@ func (qb *ImageStore) FindDuplicates(ctx context.Context, distance int) ([][]*mo
WHERE files_fingerprints.type = 'phash'`
var hashes []*utils.Phash
err := imageRepository.queryStruct(ctx, query, nil, &hashes)
if err != nil {
return nil, err
}
if err := imageRepository.queryFunc(ctx, query, nil, false, func(rows *sqlx.Rows) error {
phash := utils.Phash{
Bucket: -1,
Duration: -1,
}
if err := rows.StructScan(&phash); err != nil {
return err
}
for _, h := range hashes {
h.Bucket = -1
hashes = append(hashes, &phash)
return nil
}); err != nil {
return nil, err
}
dupeIds := utils.FindDuplicates(hashes, distance, -1)
var result [][]*models.Image
for _, comp := range dupeIds {
var group []*models.Image
for _, id := range comp {
img, err := qb.Find(ctx, id)
if err == nil && img != nil {
group = append(group, img)
if images, err := qb.FindMany(ctx, comp); err == nil {
if len(images) > 1 {
result = append(result, images)
}
}
if len(group) > 1 {
result = append(result, group)
}
}
return result, nil

View file

@ -1596,20 +1596,6 @@ func TestImageQueryQ(t *testing.T) {
})
}
func TestImageQueryQ_Details(t *testing.T) {
withTxn(func(ctx context.Context) error {
const imageIdx = 3
q := getImageStringValue(imageIdx, detailsField)
sqb := db.Image
imageQueryQ(ctx, t, sqb, q, imageIdx)
return nil
})
}
func queryImagesWithCount(ctx context.Context, sqb models.ImageReader, imageFilter *models.ImageFilterType, findFilter *models.FindFilterType) ([]*models.Image, int, error) {
result, err := sqb.Query(ctx, models.ImageQueryOptions{
QueryOptions: models.QueryOptions{