mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 04:14:39 +03:00
Gallery filter fix (#1147)
* Fix gallery performer and tags filters * Add unit tests
This commit is contained in:
@@ -233,7 +233,7 @@ func (qb *galleryQueryBuilder) Query(galleryFilter *models.GalleryFilterType, fi
|
||||
}
|
||||
|
||||
query.body += " LEFT JOIN tags on tags_join.tag_id = tags.id"
|
||||
whereClause, havingClause := getMultiCriterionClause("galleries", "tags", "tags_join", "gallery_id", "tag_id", tagsFilter)
|
||||
whereClause, havingClause := getMultiCriterionClause("galleries", "tags", "galleries_tags", "gallery_id", "tag_id", tagsFilter)
|
||||
query.addWhere(whereClause)
|
||||
query.addHaving(havingClause)
|
||||
}
|
||||
@@ -244,7 +244,7 @@ func (qb *galleryQueryBuilder) Query(galleryFilter *models.GalleryFilterType, fi
|
||||
}
|
||||
|
||||
query.body += " LEFT JOIN performers ON performers_join.performer_id = performers.id"
|
||||
whereClause, havingClause := getMultiCriterionClause("galleries", "performers", "performers_join", "gallery_id", "performer_id", performersFilter)
|
||||
whereClause, havingClause := getMultiCriterionClause("galleries", "performers", "performers_galleries", "gallery_id", "performer_id", performersFilter)
|
||||
query.addWhere(whereClause)
|
||||
query.addHaving(havingClause)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
package sqlite_test
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -272,6 +273,252 @@ func TestGalleryQueryIsMissingScene(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func queryGallery(t *testing.T, sqb models.GalleryReader, galleryFilter *models.GalleryFilterType, findFilter *models.FindFilterType) []*models.Gallery {
|
||||
galleries, _, err := sqb.Query(galleryFilter, findFilter)
|
||||
if err != nil {
|
||||
t.Errorf("Error querying gallery: %s", err.Error())
|
||||
}
|
||||
|
||||
return galleries
|
||||
}
|
||||
|
||||
func TestGalleryQueryIsMissingStudio(t *testing.T) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
sqb := r.Gallery()
|
||||
isMissing := "studio"
|
||||
galleryFilter := models.GalleryFilterType{
|
||||
IsMissing: &isMissing,
|
||||
}
|
||||
|
||||
q := getGalleryStringValue(galleryIdxWithStudio, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
|
||||
galleries := queryGallery(t, sqb, &galleryFilter, &findFilter)
|
||||
|
||||
assert.Len(t, galleries, 0)
|
||||
|
||||
findFilter.Q = nil
|
||||
galleries = queryGallery(t, sqb, &galleryFilter, &findFilter)
|
||||
|
||||
// ensure non of the ids equal the one with studio
|
||||
for _, gallery := range galleries {
|
||||
assert.NotEqual(t, galleryIDs[galleryIdxWithStudio], gallery.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestGalleryQueryIsMissingPerformers(t *testing.T) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
sqb := r.Gallery()
|
||||
isMissing := "performers"
|
||||
galleryFilter := models.GalleryFilterType{
|
||||
IsMissing: &isMissing,
|
||||
}
|
||||
|
||||
q := getGalleryStringValue(galleryIdxWithPerformer, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
|
||||
galleries := queryGallery(t, sqb, &galleryFilter, &findFilter)
|
||||
|
||||
assert.Len(t, galleries, 0)
|
||||
|
||||
findFilter.Q = nil
|
||||
galleries = queryGallery(t, sqb, &galleryFilter, &findFilter)
|
||||
|
||||
assert.True(t, len(galleries) > 0)
|
||||
|
||||
// ensure non of the ids equal the one with movies
|
||||
for _, gallery := range galleries {
|
||||
assert.NotEqual(t, galleryIDs[galleryIdxWithPerformer], gallery.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestGalleryQueryIsMissingTags(t *testing.T) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
sqb := r.Gallery()
|
||||
isMissing := "tags"
|
||||
galleryFilter := models.GalleryFilterType{
|
||||
IsMissing: &isMissing,
|
||||
}
|
||||
|
||||
q := getGalleryStringValue(galleryIdxWithTwoTags, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
|
||||
galleries := queryGallery(t, sqb, &galleryFilter, &findFilter)
|
||||
|
||||
assert.Len(t, galleries, 0)
|
||||
|
||||
findFilter.Q = nil
|
||||
galleries = queryGallery(t, sqb, &galleryFilter, &findFilter)
|
||||
|
||||
assert.True(t, len(galleries) > 0)
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestGalleryQueryPerformers(t *testing.T) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
sqb := r.Gallery()
|
||||
performerCriterion := models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(performerIDs[performerIdxWithGallery]),
|
||||
strconv.Itoa(performerIDs[performerIdx1WithGallery]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
}
|
||||
|
||||
galleryFilter := models.GalleryFilterType{
|
||||
Performers: &performerCriterion,
|
||||
}
|
||||
|
||||
galleries := queryGallery(t, sqb, &galleryFilter, nil)
|
||||
|
||||
assert.Len(t, galleries, 2)
|
||||
|
||||
// ensure ids are correct
|
||||
for _, gallery := range galleries {
|
||||
assert.True(t, gallery.ID == galleryIDs[galleryIdxWithPerformer] || gallery.ID == galleryIDs[galleryIdxWithTwoPerformers])
|
||||
}
|
||||
|
||||
performerCriterion = models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(performerIDs[performerIdx1WithGallery]),
|
||||
strconv.Itoa(performerIDs[performerIdx2WithGallery]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludesAll,
|
||||
}
|
||||
|
||||
galleries = queryGallery(t, sqb, &galleryFilter, nil)
|
||||
|
||||
assert.Len(t, galleries, 1)
|
||||
assert.Equal(t, galleryIDs[galleryIdxWithTwoPerformers], galleries[0].ID)
|
||||
|
||||
performerCriterion = models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(performerIDs[performerIdx1WithGallery]),
|
||||
},
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
}
|
||||
|
||||
q := getGalleryStringValue(galleryIdxWithTwoPerformers, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
|
||||
galleries = queryGallery(t, sqb, &galleryFilter, &findFilter)
|
||||
assert.Len(t, galleries, 0)
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestGalleryQueryTags(t *testing.T) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
sqb := r.Gallery()
|
||||
tagCriterion := models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdxWithGallery]),
|
||||
strconv.Itoa(tagIDs[tagIdx1WithGallery]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
}
|
||||
|
||||
galleryFilter := models.GalleryFilterType{
|
||||
Tags: &tagCriterion,
|
||||
}
|
||||
|
||||
galleries := queryGallery(t, sqb, &galleryFilter, nil)
|
||||
assert.Len(t, galleries, 2)
|
||||
|
||||
// ensure ids are correct
|
||||
for _, gallery := range galleries {
|
||||
assert.True(t, gallery.ID == galleryIDs[galleryIdxWithTag] || gallery.ID == galleryIDs[galleryIdxWithTwoTags])
|
||||
}
|
||||
|
||||
tagCriterion = models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithGallery]),
|
||||
strconv.Itoa(tagIDs[tagIdx2WithGallery]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludesAll,
|
||||
}
|
||||
|
||||
galleries = queryGallery(t, sqb, &galleryFilter, nil)
|
||||
|
||||
assert.Len(t, galleries, 1)
|
||||
assert.Equal(t, galleryIDs[galleryIdxWithTwoTags], galleries[0].ID)
|
||||
|
||||
tagCriterion = models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithGallery]),
|
||||
},
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
}
|
||||
|
||||
q := getGalleryStringValue(galleryIdxWithTwoTags, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
|
||||
galleries = queryGallery(t, sqb, &galleryFilter, &findFilter)
|
||||
assert.Len(t, galleries, 0)
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestGalleryQueryStudio(t *testing.T) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
sqb := r.Gallery()
|
||||
studioCriterion := models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(studioIDs[studioIdxWithGallery]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
}
|
||||
|
||||
galleryFilter := models.GalleryFilterType{
|
||||
Studios: &studioCriterion,
|
||||
}
|
||||
|
||||
galleries := queryGallery(t, sqb, &galleryFilter, nil)
|
||||
|
||||
assert.Len(t, galleries, 1)
|
||||
|
||||
// ensure id is correct
|
||||
assert.Equal(t, galleryIDs[galleryIdxWithStudio], galleries[0].ID)
|
||||
|
||||
studioCriterion = models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(studioIDs[studioIdxWithGallery]),
|
||||
},
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
}
|
||||
|
||||
q := getGalleryStringValue(galleryIdxWithStudio, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
|
||||
galleries = queryGallery(t, sqb, &galleryFilter, &findFilter)
|
||||
assert.Len(t, galleries, 0)
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// TODO Count
|
||||
// TODO All
|
||||
// TODO Query
|
||||
|
||||
@@ -44,6 +44,14 @@ func TestPerformerFindBySceneID(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPerformerFindByNames(t *testing.T) {
|
||||
getNames := func(p []*models.Performer) []string {
|
||||
var ret []string
|
||||
for _, pp := range p {
|
||||
ret = append(ret, pp.Name.String)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
withTxn(func(r models.Repository) error {
|
||||
var names []string
|
||||
|
||||
@@ -72,19 +80,20 @@ func TestPerformerFindByNames(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Error finding performers: %s", err.Error())
|
||||
}
|
||||
assert.Len(t, performers, 2) // performerIdxWithScene and performerIdx1WithScene
|
||||
assert.Equal(t, performerNames[performerIdxWithScene], performers[0].Name.String)
|
||||
assert.Equal(t, performerNames[performerIdx1WithScene], performers[1].Name.String)
|
||||
retNames := getNames(performers)
|
||||
assert.Equal(t, names, retNames)
|
||||
|
||||
performers, err = pqb.FindByNames(names, true) // find performers by names ( 2 names nocase)
|
||||
if err != nil {
|
||||
t.Errorf("Error finding performers: %s", err.Error())
|
||||
}
|
||||
assert.Len(t, performers, 4) // performerIdxWithScene and performerIdxWithDupName , performerIdx1WithScene and performerIdx1WithDupName
|
||||
assert.Equal(t, performerNames[performerIdxWithScene], performers[0].Name.String)
|
||||
assert.Equal(t, performerNames[performerIdx1WithScene], performers[1].Name.String)
|
||||
assert.Equal(t, performerNames[performerIdx1WithDupName], performers[2].Name.String)
|
||||
assert.Equal(t, performerNames[performerIdxWithDupName], performers[3].Name.String)
|
||||
retNames = getNames(performers)
|
||||
assert.Equal(t, []string{
|
||||
performerNames[performerIdxWithScene],
|
||||
performerNames[performerIdx1WithScene],
|
||||
performerNames[performerIdx1WithDupName],
|
||||
performerNames[performerIdxWithDupName],
|
||||
}, retNames)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -22,14 +22,14 @@ import (
|
||||
|
||||
const totalScenes = 12
|
||||
const totalImages = 6 // TODO - add one for zip file
|
||||
const performersNameCase = 6
|
||||
const performersNameCase = 9
|
||||
const performersNameNoCase = 2
|
||||
const moviesNameCase = 2
|
||||
const moviesNameNoCase = 1
|
||||
const totalGalleries = 3
|
||||
const totalGalleries = 8
|
||||
const tagsNameNoCase = 2
|
||||
const tagsNameCase = 9
|
||||
const studiosNameCase = 5
|
||||
const tagsNameCase = 12
|
||||
const studiosNameCase = 6
|
||||
const studiosNameNoCase = 1
|
||||
|
||||
var sceneIDs []int
|
||||
@@ -69,10 +69,13 @@ const performerIdx2WithScene = 2
|
||||
const performerIdxWithImage = 3
|
||||
const performerIdx1WithImage = 4
|
||||
const performerIdx2WithImage = 5
|
||||
const performerIdxWithGallery = 6
|
||||
const performerIdx1WithGallery = 7
|
||||
const performerIdx2WithGallery = 8
|
||||
|
||||
// performers with dup names start from the end
|
||||
const performerIdx1WithDupName = 6
|
||||
const performerIdxWithDupName = 7
|
||||
const performerIdx1WithDupName = 9
|
||||
const performerIdxWithDupName = 10
|
||||
|
||||
const movieIdxWithScene = 0
|
||||
const movieIdxWithStudio = 1
|
||||
@@ -82,6 +85,11 @@ const movieIdxWithDupName = 2
|
||||
|
||||
const galleryIdxWithScene = 0
|
||||
const galleryIdxWithImage = 1
|
||||
const galleryIdxWithPerformer = 2
|
||||
const galleryIdxWithTwoPerformers = 3
|
||||
const galleryIdxWithTag = 4
|
||||
const galleryIdxWithTwoTags = 5
|
||||
const galleryIdxWithStudio = 6
|
||||
|
||||
const tagIdxWithScene = 0
|
||||
const tagIdx1WithScene = 1
|
||||
@@ -92,19 +100,23 @@ const tagIdxWithCoverImage = 5
|
||||
const tagIdxWithImage = 6
|
||||
const tagIdx1WithImage = 7
|
||||
const tagIdx2WithImage = 8
|
||||
const tagIdxWithGallery = 9
|
||||
const tagIdx1WithGallery = 10
|
||||
const tagIdx2WithGallery = 11
|
||||
|
||||
// tags with dup names start from the end
|
||||
const tagIdx1WithDupName = 9
|
||||
const tagIdxWithDupName = 10
|
||||
const tagIdx1WithDupName = 12
|
||||
const tagIdxWithDupName = 13
|
||||
|
||||
const studioIdxWithScene = 0
|
||||
const studioIdxWithMovie = 1
|
||||
const studioIdxWithChildStudio = 2
|
||||
const studioIdxWithParentStudio = 3
|
||||
const studioIdxWithImage = 4
|
||||
const studioIdxWithGallery = 5
|
||||
|
||||
// studios with dup names start from the end
|
||||
const studioIdxWithDupName = 5
|
||||
const studioIdxWithDupName = 6
|
||||
|
||||
const markerIdxWithScene = 0
|
||||
|
||||
@@ -237,6 +249,18 @@ func populateDB() error {
|
||||
return fmt.Errorf("error linking studio parent: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := linkGalleryPerformers(r.Gallery()); err != nil {
|
||||
return fmt.Errorf("error linking gallery performers: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := linkGalleryTags(r.Gallery()); err != nil {
|
||||
return fmt.Errorf("error linking gallery tags: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := linkGalleryStudio(r.Gallery(), galleryIdxWithStudio, studioIdxWithGallery); err != nil {
|
||||
return fmt.Errorf("error linking gallery studio: %s", err.Error())
|
||||
}
|
||||
|
||||
if err := createMarker(r.SceneMarker(), sceneIdxWithMarker, tagIdxWithPrimaryMarker, []int{tagIdxWithMarker}); err != nil {
|
||||
return fmt.Errorf("error creating scene marker: %s", err.Error())
|
||||
}
|
||||
@@ -756,3 +780,65 @@ func linkStudioParent(qb models.StudioWriter, parentIndex, childIndex int) error
|
||||
func addTagImage(qb models.TagWriter, tagIndex int) error {
|
||||
return qb.UpdateImage(tagIDs[tagIndex], models.DefaultTagImage)
|
||||
}
|
||||
|
||||
func linkGalleryTags(iqb models.GalleryReaderWriter) error {
|
||||
if err := linkGalleryTag(iqb, galleryIdxWithTag, tagIdxWithGallery); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := linkGalleryTag(iqb, galleryIdxWithTwoTags, tagIdx1WithGallery); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := linkGalleryTag(iqb, galleryIdxWithTwoTags, tagIdx2WithGallery); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func linkGalleryTag(iqb models.GalleryReaderWriter, galleryIndex, tagIndex int) error {
|
||||
galleryID := galleryIDs[galleryIndex]
|
||||
tags, err := iqb.GetTagIDs(galleryID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tags = append(tags, tagIDs[tagIndex])
|
||||
|
||||
return iqb.UpdateTags(galleryID, tags)
|
||||
}
|
||||
|
||||
func linkGalleryStudio(qb models.GalleryWriter, galleryIndex, studioIndex int) error {
|
||||
gallery := models.GalleryPartial{
|
||||
ID: galleryIDs[galleryIndex],
|
||||
StudioID: &sql.NullInt64{Int64: int64(studioIDs[studioIndex]), Valid: true},
|
||||
}
|
||||
_, err := qb.UpdatePartial(gallery)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func linkGalleryPerformers(qb models.GalleryReaderWriter) error {
|
||||
if err := linkGalleryPerformer(qb, galleryIdxWithPerformer, performerIdxWithGallery); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := linkGalleryPerformer(qb, galleryIdxWithTwoPerformers, performerIdx1WithGallery); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := linkGalleryPerformer(qb, galleryIdxWithTwoPerformers, performerIdx2WithGallery); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func linkGalleryPerformer(iqb models.GalleryReaderWriter, galleryIndex, performerIndex int) error {
|
||||
galleryID := galleryIDs[galleryIndex]
|
||||
performers, err := iqb.GetPerformerIDs(galleryID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
performers = append(performers, performerIDs[performerIndex])
|
||||
|
||||
return iqb.UpdatePerformers(galleryID, performers)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* Added Rescan button to scene, image, gallery details overflow button.
|
||||
|
||||
### 🐛 Bug fixes
|
||||
* Fix SQL error when filtering galleries excluding performers or tags.
|
||||
* Fix version checking for armv7 and arm64.
|
||||
* Change "Is NULL" filter to include empty string values.
|
||||
* Prevent scene card previews playing in full-screen on iOS devices.
|
||||
|
||||
Reference in New Issue
Block a user