From dbb29d88f1380f0eb5db3016751421729dbf16fe Mon Sep 17 00:00:00 2001 From: Slick Daddy <129640104+slick-daddy@users.noreply.github.com> Date: Thu, 30 Apr 2026 18:26:16 +0300 Subject: [PATCH] Document performer age sort semantics and add multi-performer tests --- pkg/sqlite/gallery.go | 6 ++-- pkg/sqlite/gallery_test.go | 69 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/pkg/sqlite/gallery.go b/pkg/sqlite/gallery.go index 6a02c8868..bcd1c1a08 100644 --- a/pkg/sqlite/gallery.go +++ b/pkg/sqlite/gallery.go @@ -860,10 +860,12 @@ func (qb *GalleryStore) setGallerySort(query *queryBuilder, findFilter *models.F case "performer_count": query.sortAndPagination += getCountSort(galleryTable, performersGalleriesTable, galleryIDColumn, direction) case "performer_age": - // Looking at the youngest performer by default. + // Multi-performer semantics: + // - ASC sorts by the youngest performer in each gallery (MIN age) + // - DESC sorts by the oldest performer in each gallery (MAX age) aggregation := "MIN" if direction == "DESC" { - // When sorting by performer age DESC, consider oldest performer instead. + // DESC uses oldest performer age for each gallery. aggregation = "MAX" } fallback := "-9223372036854775808" diff --git a/pkg/sqlite/gallery_test.go b/pkg/sqlite/gallery_test.go index 12c173081..de497db49 100644 --- a/pkg/sqlite/gallery_test.go +++ b/pkg/sqlite/gallery_test.go @@ -2960,6 +2960,75 @@ func TestGalleryQuerySortingPerformerAgeNullHandling(t *testing.T) { }) } +func TestGalleryQuerySortingPerformerAgeMultiPerformerAggregation(t *testing.T) { + runWithRollbackTxn(t, "performer age multi performer aggregation", func(t *testing.T, ctx context.Context) { + assert := assert.New(t) + + youngBirthdate, err := models.ParseDate("2000-01-01") + require.NoError(t, err) + midBirthdate, err := models.ParseDate("1990-01-01") + require.NoError(t, err) + oldBirthdate, err := models.ParseDate("1980-01-01") + require.NoError(t, err) + + young := models.Performer{Name: "performer-young", Birthdate: &youngBirthdate} + mid := models.Performer{Name: "performer-mid", Birthdate: &midBirthdate} + old := models.Performer{Name: "performer-old", Birthdate: &oldBirthdate} + require.NoError(t, db.Performer.Create(ctx, &models.CreatePerformerInput{Performer: &young})) + require.NoError(t, db.Performer.Create(ctx, &models.CreatePerformerInput{Performer: &mid})) + require.NoError(t, db.Performer.Create(ctx, &models.CreatePerformerInput{Performer: &old})) + + galleryYoungAndOld := models.Gallery{ + Title: "gallery-young-and-old", + Date: models.NewString("2020-01-01"), + PerformerIDs: models.NewRelatedIDs([]int{ + young.ID, + old.ID, + }), + } + require.NoError(t, db.Gallery.Create(ctx, &models.CreateGalleryInput{Gallery: &galleryYoungAndOld})) + + galleryMidOnly := models.Gallery{ + Title: "gallery-mid-only", + Date: models.NewString("2020-01-01"), + PerformerIDs: models.NewRelatedIDs([]int{ + mid.ID, + }), + } + require.NoError(t, db.Gallery.Create(ctx, &models.CreateGalleryInput{Gallery: &galleryMidOnly})) + + findIndex := func(galleries []*models.Gallery, id int) int { + for i, g := range galleries { + if g.ID == id { + return i + } + } + return -1 + } + + sortBy := "performer_age" + asc := models.SortDirectionEnumAsc + ascGot, _, err := db.Gallery.Query(ctx, nil, &models.FindFilterType{Sort: &sortBy, Direction: &asc}) + require.NoError(t, err) + ascYoungAndOld := findIndex(ascGot, galleryYoungAndOld.ID) + ascMidOnly := findIndex(ascGot, galleryMidOnly.ID) + assert.NotEqual(-1, ascYoungAndOld) + assert.NotEqual(-1, ascMidOnly) + // ASC uses MIN(age), so gallery with youngest performer should come first. + assert.Less(ascYoungAndOld, ascMidOnly) + + desc := models.SortDirectionEnumDesc + descGot, _, err := db.Gallery.Query(ctx, nil, &models.FindFilterType{Sort: &sortBy, Direction: &desc}) + require.NoError(t, err) + descYoungAndOld := findIndex(descGot, galleryYoungAndOld.ID) + descMidOnly := findIndex(descGot, galleryMidOnly.ID) + assert.NotEqual(-1, descYoungAndOld) + assert.NotEqual(-1, descMidOnly) + // DESC uses MAX(age), so gallery with oldest performer should come first. + assert.Less(descYoungAndOld, descMidOnly) + }) +} + func TestGalleryStore_AddImages(t *testing.T) { tests := []struct { name string