Query bug fixes (#1510)

* Fix joins being dropped
* Fix missing scene stash_id criterion
* Refactor criterion handlers
* Add tag alias filter
* Remove handleCriterionFunc
This commit is contained in:
WithoutPants 2021-06-21 15:48:28 +10:00 committed by GitHub
parent df6e06aaf6
commit 5fdfbaa7f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 150 additions and 151 deletions

View File

@ -28,6 +28,10 @@ type criterionHandler interface {
type criterionHandlerFunc func(f *filterBuilder)
func (h criterionHandlerFunc) handle(f *filterBuilder) {
h(f)
}
type join struct {
table string
as string
@ -62,13 +66,17 @@ type joins []join
func (j *joins) add(newJoins ...join) {
// only add if not already joined
for _, newJoin := range newJoins {
found := false
for _, jj := range *j {
if jj.equals(newJoin) {
return
found = true
break
}
}
*j = append(*j, newJoin)
if !found {
*j = append(*j, newJoin)
}
}
}
@ -284,15 +292,7 @@ func (f *filterBuilder) getError() error {
// handleCriterion calls the handle function on the provided criterionHandler,
// providing itself.
func (f *filterBuilder) handleCriterion(handler criterionHandler) {
f.handleCriterionFunc(func(h *filterBuilder) {
handler.handle(h)
})
}
// handleCriterionFunc calls the provided criterion handler function providing
// itself.
func (f *filterBuilder) handleCriterionFunc(handler criterionHandlerFunc) {
handler(f)
handler.handle(f)
}
func (f *filterBuilder) setError(e error) {
@ -519,15 +519,9 @@ type stringListCriterionHandlerBuilder struct {
func (m *stringListCriterionHandlerBuilder) handler(criterion *models.StringCriterionInput) criterionHandlerFunc {
return func(f *filterBuilder) {
if criterion != nil && len(criterion.Value) > 0 {
var args []interface{}
for _, tagID := range criterion.Value {
args = append(args, tagID)
}
m.addJoinTable(f)
stringCriterionHandler(criterion, m.joinTable+"."+m.stringColumn)(f)
}
}
}

View File

@ -9,6 +9,31 @@ import (
"github.com/stretchr/testify/assert"
)
func TestJoinsAddJoin(t *testing.T) {
var joins joins
// add a single join
joins.add(join{table: "test"})
assert := assert.New(t)
// ensure join was added
assert.Len(joins, 1)
// add the same join and another
joins.add([]join{
{
table: "test",
},
{
table: "foo",
},
}...)
// should have added a single join
assert.Len(joins, 2)
}
func TestFilterBuilderAnd(t *testing.T) {
assert := assert.New(t)
@ -437,7 +462,7 @@ func TestStringCriterionHandlerIncludes(t *testing.T) {
const quotedValue = `"two words"`
f := &filterBuilder{}
f.handleCriterionFunc(stringCriterionHandler(&models.StringCriterionInput{
f.handleCriterion(stringCriterionHandler(&models.StringCriterionInput{
Modifier: models.CriterionModifierIncludes,
Value: value1,
}, column))
@ -449,7 +474,7 @@ func TestStringCriterionHandlerIncludes(t *testing.T) {
assert.Equal("%words%", f.whereClauses[0].args[1])
f = &filterBuilder{}
f.handleCriterionFunc(stringCriterionHandler(&models.StringCriterionInput{
f.handleCriterion(stringCriterionHandler(&models.StringCriterionInput{
Modifier: models.CriterionModifierIncludes,
Value: quotedValue,
}, column))
@ -468,7 +493,7 @@ func TestStringCriterionHandlerExcludes(t *testing.T) {
const quotedValue = `"two words"`
f := &filterBuilder{}
f.handleCriterionFunc(stringCriterionHandler(&models.StringCriterionInput{
f.handleCriterion(stringCriterionHandler(&models.StringCriterionInput{
Modifier: models.CriterionModifierExcludes,
Value: value1,
}, column))
@ -480,7 +505,7 @@ func TestStringCriterionHandlerExcludes(t *testing.T) {
assert.Equal("%words%", f.whereClauses[0].args[1])
f = &filterBuilder{}
f.handleCriterionFunc(stringCriterionHandler(&models.StringCriterionInput{
f.handleCriterion(stringCriterionHandler(&models.StringCriterionInput{
Modifier: models.CriterionModifierExcludes,
Value: quotedValue,
}, column))
@ -498,7 +523,7 @@ func TestStringCriterionHandlerEquals(t *testing.T) {
const value1 = "two words"
f := &filterBuilder{}
f.handleCriterionFunc(stringCriterionHandler(&models.StringCriterionInput{
f.handleCriterion(stringCriterionHandler(&models.StringCriterionInput{
Modifier: models.CriterionModifierEquals,
Value: value1,
}, column))
@ -516,7 +541,7 @@ func TestStringCriterionHandlerNotEquals(t *testing.T) {
const value1 = "two words"
f := &filterBuilder{}
f.handleCriterionFunc(stringCriterionHandler(&models.StringCriterionInput{
f.handleCriterion(stringCriterionHandler(&models.StringCriterionInput{
Modifier: models.CriterionModifierNotEquals,
Value: value1,
}, column))
@ -535,7 +560,7 @@ func TestStringCriterionHandlerMatchesRegex(t *testing.T) {
const invalidValue = "*two words"
f := &filterBuilder{}
f.handleCriterionFunc(stringCriterionHandler(&models.StringCriterionInput{
f.handleCriterion(stringCriterionHandler(&models.StringCriterionInput{
Modifier: models.CriterionModifierMatchesRegex,
Value: validValue,
}, column))
@ -547,7 +572,7 @@ func TestStringCriterionHandlerMatchesRegex(t *testing.T) {
// ensure invalid regex sets error state
f = &filterBuilder{}
f.handleCriterionFunc(stringCriterionHandler(&models.StringCriterionInput{
f.handleCriterion(stringCriterionHandler(&models.StringCriterionInput{
Modifier: models.CriterionModifierMatchesRegex,
Value: invalidValue,
}, column))
@ -563,7 +588,7 @@ func TestStringCriterionHandlerNotMatchesRegex(t *testing.T) {
const invalidValue = "*two words"
f := &filterBuilder{}
f.handleCriterionFunc(stringCriterionHandler(&models.StringCriterionInput{
f.handleCriterion(stringCriterionHandler(&models.StringCriterionInput{
Modifier: models.CriterionModifierNotMatchesRegex,
Value: validValue,
}, column))
@ -575,7 +600,7 @@ func TestStringCriterionHandlerNotMatchesRegex(t *testing.T) {
// ensure invalid regex sets error state
f = &filterBuilder{}
f.handleCriterionFunc(stringCriterionHandler(&models.StringCriterionInput{
f.handleCriterion(stringCriterionHandler(&models.StringCriterionInput{
Modifier: models.CriterionModifierNotMatchesRegex,
Value: invalidValue,
}, column))
@ -589,7 +614,7 @@ func TestStringCriterionHandlerIsNull(t *testing.T) {
const column = "column"
f := &filterBuilder{}
f.handleCriterionFunc(stringCriterionHandler(&models.StringCriterionInput{
f.handleCriterion(stringCriterionHandler(&models.StringCriterionInput{
Modifier: models.CriterionModifierIsNull,
}, column))
@ -604,7 +629,7 @@ func TestStringCriterionHandlerNotNull(t *testing.T) {
const column = "column"
f := &filterBuilder{}
f.handleCriterionFunc(stringCriterionHandler(&models.StringCriterionInput{
f.handleCriterion(stringCriterionHandler(&models.StringCriterionInput{
Modifier: models.CriterionModifierNotNull,
}, column))

View File

@ -203,20 +203,20 @@ func (qb *galleryQueryBuilder) makeFilter(galleryFilter *models.GalleryFilterTyp
query.not(qb.makeFilter(galleryFilter.Not))
}
query.handleCriterionFunc(boolCriterionHandler(galleryFilter.IsZip, "galleries.zip"))
query.handleCriterionFunc(stringCriterionHandler(galleryFilter.Path, "galleries.path"))
query.handleCriterionFunc(intCriterionHandler(galleryFilter.Rating, "galleries.rating"))
query.handleCriterionFunc(stringCriterionHandler(galleryFilter.URL, "galleries.url"))
query.handleCriterionFunc(boolCriterionHandler(galleryFilter.Organized, "galleries.organized"))
query.handleCriterionFunc(galleryIsMissingCriterionHandler(qb, galleryFilter.IsMissing))
query.handleCriterionFunc(galleryTagsCriterionHandler(qb, galleryFilter.Tags))
query.handleCriterionFunc(galleryTagCountCriterionHandler(qb, galleryFilter.TagCount))
query.handleCriterionFunc(galleryPerformersCriterionHandler(qb, galleryFilter.Performers))
query.handleCriterionFunc(galleryPerformerCountCriterionHandler(qb, galleryFilter.PerformerCount))
query.handleCriterionFunc(galleryStudioCriterionHandler(qb, galleryFilter.Studios))
query.handleCriterionFunc(galleryPerformerTagsCriterionHandler(qb, galleryFilter.PerformerTags))
query.handleCriterionFunc(galleryAverageResolutionCriterionHandler(qb, galleryFilter.AverageResolution))
query.handleCriterionFunc(galleryImageCountCriterionHandler(qb, galleryFilter.ImageCount))
query.handleCriterion(boolCriterionHandler(galleryFilter.IsZip, "galleries.zip"))
query.handleCriterion(stringCriterionHandler(galleryFilter.Path, "galleries.path"))
query.handleCriterion(intCriterionHandler(galleryFilter.Rating, "galleries.rating"))
query.handleCriterion(stringCriterionHandler(galleryFilter.URL, "galleries.url"))
query.handleCriterion(boolCriterionHandler(galleryFilter.Organized, "galleries.organized"))
query.handleCriterion(galleryIsMissingCriterionHandler(qb, galleryFilter.IsMissing))
query.handleCriterion(galleryTagsCriterionHandler(qb, galleryFilter.Tags))
query.handleCriterion(galleryTagCountCriterionHandler(qb, galleryFilter.TagCount))
query.handleCriterion(galleryPerformersCriterionHandler(qb, galleryFilter.Performers))
query.handleCriterion(galleryPerformerCountCriterionHandler(qb, galleryFilter.PerformerCount))
query.handleCriterion(galleryStudioCriterionHandler(qb, galleryFilter.Studios))
query.handleCriterion(galleryPerformerTagsCriterionHandler(qb, galleryFilter.PerformerTags))
query.handleCriterion(galleryAverageResolutionCriterionHandler(qb, galleryFilter.AverageResolution))
query.handleCriterion(galleryImageCountCriterionHandler(qb, galleryFilter.ImageCount))
return query
}

View File

@ -231,20 +231,20 @@ func (qb *imageQueryBuilder) makeFilter(imageFilter *models.ImageFilterType) *fi
query.not(qb.makeFilter(imageFilter.Not))
}
query.handleCriterionFunc(stringCriterionHandler(imageFilter.Path, "images.path"))
query.handleCriterionFunc(intCriterionHandler(imageFilter.Rating, "images.rating"))
query.handleCriterionFunc(intCriterionHandler(imageFilter.OCounter, "images.o_counter"))
query.handleCriterionFunc(boolCriterionHandler(imageFilter.Organized, "images.organized"))
query.handleCriterionFunc(resolutionCriterionHandler(imageFilter.Resolution, "images.height", "images.width"))
query.handleCriterionFunc(imageIsMissingCriterionHandler(qb, imageFilter.IsMissing))
query.handleCriterion(stringCriterionHandler(imageFilter.Path, "images.path"))
query.handleCriterion(intCriterionHandler(imageFilter.Rating, "images.rating"))
query.handleCriterion(intCriterionHandler(imageFilter.OCounter, "images.o_counter"))
query.handleCriterion(boolCriterionHandler(imageFilter.Organized, "images.organized"))
query.handleCriterion(resolutionCriterionHandler(imageFilter.Resolution, "images.height", "images.width"))
query.handleCriterion(imageIsMissingCriterionHandler(qb, imageFilter.IsMissing))
query.handleCriterionFunc(imageTagsCriterionHandler(qb, imageFilter.Tags))
query.handleCriterionFunc(imageTagCountCriterionHandler(qb, imageFilter.TagCount))
query.handleCriterionFunc(imageGalleriesCriterionHandler(qb, imageFilter.Galleries))
query.handleCriterionFunc(imagePerformersCriterionHandler(qb, imageFilter.Performers))
query.handleCriterionFunc(imagePerformerCountCriterionHandler(qb, imageFilter.PerformerCount))
query.handleCriterionFunc(imageStudioCriterionHandler(qb, imageFilter.Studios))
query.handleCriterionFunc(imagePerformerTagsCriterionHandler(qb, imageFilter.PerformerTags))
query.handleCriterion(imageTagsCriterionHandler(qb, imageFilter.Tags))
query.handleCriterion(imageTagCountCriterionHandler(qb, imageFilter.TagCount))
query.handleCriterion(imageGalleriesCriterionHandler(qb, imageFilter.Galleries))
query.handleCriterion(imagePerformersCriterionHandler(qb, imageFilter.Performers))
query.handleCriterion(imagePerformerCountCriterionHandler(qb, imageFilter.PerformerCount))
query.handleCriterion(imageStudioCriterionHandler(qb, imageFilter.Studios))
query.handleCriterion(imagePerformerTagsCriterionHandler(qb, imageFilter.PerformerTags))
return query
}

View File

@ -118,9 +118,9 @@ func (qb *movieQueryBuilder) All() ([]*models.Movie, error) {
func (qb *movieQueryBuilder) makeFilter(movieFilter *models.MovieFilterType) *filterBuilder {
query := &filterBuilder{}
query.handleCriterionFunc(movieIsMissingCriterionHandler(qb, movieFilter.IsMissing))
query.handleCriterionFunc(stringCriterionHandler(movieFilter.URL, "movies.url"))
query.handleCriterionFunc(movieStudioCriterionHandler(qb, movieFilter.Studios))
query.handleCriterion(movieIsMissingCriterionHandler(qb, movieFilter.IsMissing))
query.handleCriterion(stringCriterionHandler(movieFilter.URL, "movies.url"))
query.handleCriterion(movieStudioCriterionHandler(qb, movieFilter.Studios))
return query
}

View File

@ -239,51 +239,51 @@ func (qb *performerQueryBuilder) makeFilter(filter *models.PerformerFilterType)
}
const tableName = performerTable
query.handleCriterionFunc(boolCriterionHandler(filter.FilterFavorites, tableName+".favorite"))
query.handleCriterion(boolCriterionHandler(filter.FilterFavorites, tableName+".favorite"))
query.handleCriterionFunc(yearFilterCriterionHandler(filter.BirthYear, tableName+".birthdate"))
query.handleCriterionFunc(yearFilterCriterionHandler(filter.DeathYear, tableName+".death_date"))
query.handleCriterion(yearFilterCriterionHandler(filter.BirthYear, tableName+".birthdate"))
query.handleCriterion(yearFilterCriterionHandler(filter.DeathYear, tableName+".death_date"))
query.handleCriterionFunc(performerAgeFilterCriterionHandler(filter.Age))
query.handleCriterion(performerAgeFilterCriterionHandler(filter.Age))
query.handleCriterionFunc(func(f *filterBuilder) {
query.handleCriterion(criterionHandlerFunc(func(f *filterBuilder) {
if gender := filter.Gender; gender != nil {
f.addWhere(tableName+".gender = ?", gender.Value.String())
}
})
}))
query.handleCriterionFunc(performerIsMissingCriterionHandler(qb, filter.IsMissing))
query.handleCriterionFunc(stringCriterionHandler(filter.Ethnicity, tableName+".ethnicity"))
query.handleCriterionFunc(stringCriterionHandler(filter.Country, tableName+".country"))
query.handleCriterionFunc(stringCriterionHandler(filter.EyeColor, tableName+".eye_color"))
query.handleCriterionFunc(stringCriterionHandler(filter.Height, tableName+".height"))
query.handleCriterionFunc(stringCriterionHandler(filter.Measurements, tableName+".measurements"))
query.handleCriterionFunc(stringCriterionHandler(filter.FakeTits, tableName+".fake_tits"))
query.handleCriterionFunc(stringCriterionHandler(filter.CareerLength, tableName+".career_length"))
query.handleCriterionFunc(stringCriterionHandler(filter.Tattoos, tableName+".tattoos"))
query.handleCriterionFunc(stringCriterionHandler(filter.Piercings, tableName+".piercings"))
query.handleCriterionFunc(intCriterionHandler(filter.Rating, tableName+".rating"))
query.handleCriterionFunc(stringCriterionHandler(filter.HairColor, tableName+".hair_color"))
query.handleCriterionFunc(stringCriterionHandler(filter.URL, tableName+".url"))
query.handleCriterionFunc(intCriterionHandler(filter.Weight, tableName+".weight"))
query.handleCriterionFunc(func(f *filterBuilder) {
query.handleCriterion(performerIsMissingCriterionHandler(qb, filter.IsMissing))
query.handleCriterion(stringCriterionHandler(filter.Ethnicity, tableName+".ethnicity"))
query.handleCriterion(stringCriterionHandler(filter.Country, tableName+".country"))
query.handleCriterion(stringCriterionHandler(filter.EyeColor, tableName+".eye_color"))
query.handleCriterion(stringCriterionHandler(filter.Height, tableName+".height"))
query.handleCriterion(stringCriterionHandler(filter.Measurements, tableName+".measurements"))
query.handleCriterion(stringCriterionHandler(filter.FakeTits, tableName+".fake_tits"))
query.handleCriterion(stringCriterionHandler(filter.CareerLength, tableName+".career_length"))
query.handleCriterion(stringCriterionHandler(filter.Tattoos, tableName+".tattoos"))
query.handleCriterion(stringCriterionHandler(filter.Piercings, tableName+".piercings"))
query.handleCriterion(intCriterionHandler(filter.Rating, tableName+".rating"))
query.handleCriterion(stringCriterionHandler(filter.HairColor, tableName+".hair_color"))
query.handleCriterion(stringCriterionHandler(filter.URL, tableName+".url"))
query.handleCriterion(intCriterionHandler(filter.Weight, tableName+".weight"))
query.handleCriterion(criterionHandlerFunc(func(f *filterBuilder) {
if filter.StashID != nil {
qb.stashIDRepository().join(f, "performer_stash_ids", "performers.id")
stringCriterionHandler(filter.StashID, "performer_stash_ids.stash_id")(f)
}
})
}))
// TODO - need better handling of aliases
query.handleCriterionFunc(stringCriterionHandler(filter.Aliases, tableName+".aliases"))
query.handleCriterion(stringCriterionHandler(filter.Aliases, tableName+".aliases"))
query.handleCriterionFunc(performerTagsCriterionHandler(qb, filter.Tags))
query.handleCriterion(performerTagsCriterionHandler(qb, filter.Tags))
query.handleCriterionFunc(performerStudiosCriterionHandler(filter.Studios))
query.handleCriterion(performerStudiosCriterionHandler(filter.Studios))
query.handleCriterionFunc(performerTagCountCriterionHandler(qb, filter.TagCount))
query.handleCriterionFunc(performerSceneCountCriterionHandler(qb, filter.SceneCount))
query.handleCriterionFunc(performerImageCountCriterionHandler(qb, filter.ImageCount))
query.handleCriterionFunc(performerGalleryCountCriterionHandler(qb, filter.GalleryCount))
query.handleCriterion(performerTagCountCriterionHandler(qb, filter.TagCount))
query.handleCriterion(performerSceneCountCriterionHandler(qb, filter.SceneCount))
query.handleCriterion(performerImageCountCriterionHandler(qb, filter.ImageCount))
query.handleCriterion(performerGalleryCountCriterionHandler(qb, filter.GalleryCount))
return query
}

View File

@ -353,25 +353,32 @@ func (qb *sceneQueryBuilder) makeFilter(sceneFilter *models.SceneFilterType) *fi
query.not(qb.makeFilter(sceneFilter.Not))
}
query.handleCriterionFunc(stringCriterionHandler(sceneFilter.Path, "scenes.path"))
query.handleCriterionFunc(intCriterionHandler(sceneFilter.Rating, "scenes.rating"))
query.handleCriterionFunc(intCriterionHandler(sceneFilter.OCounter, "scenes.o_counter"))
query.handleCriterionFunc(boolCriterionHandler(sceneFilter.Organized, "scenes.organized"))
query.handleCriterionFunc(durationCriterionHandler(sceneFilter.Duration, "scenes.duration"))
query.handleCriterionFunc(resolutionCriterionHandler(sceneFilter.Resolution, "scenes.height", "scenes.width"))
query.handleCriterionFunc(hasMarkersCriterionHandler(sceneFilter.HasMarkers))
query.handleCriterionFunc(sceneIsMissingCriterionHandler(qb, sceneFilter.IsMissing))
query.handleCriterionFunc(stringCriterionHandler(sceneFilter.URL, "scenes.url"))
query.handleCriterionFunc(stringCriterionHandler(sceneFilter.StashID, "scene_stash_ids.stash_id"))
query.handleCriterionFunc(boolCriterionHandler(sceneFilter.Interactive, "scenes.interactive"))
query.handleCriterion(stringCriterionHandler(sceneFilter.Path, "scenes.path"))
query.handleCriterion(intCriterionHandler(sceneFilter.Rating, "scenes.rating"))
query.handleCriterion(intCriterionHandler(sceneFilter.OCounter, "scenes.o_counter"))
query.handleCriterion(boolCriterionHandler(sceneFilter.Organized, "scenes.organized"))
query.handleCriterion(durationCriterionHandler(sceneFilter.Duration, "scenes.duration"))
query.handleCriterion(resolutionCriterionHandler(sceneFilter.Resolution, "scenes.height", "scenes.width"))
query.handleCriterion(hasMarkersCriterionHandler(sceneFilter.HasMarkers))
query.handleCriterion(sceneIsMissingCriterionHandler(qb, sceneFilter.IsMissing))
query.handleCriterion(stringCriterionHandler(sceneFilter.URL, "scenes.url"))
query.handleCriterionFunc(sceneTagsCriterionHandler(qb, sceneFilter.Tags))
query.handleCriterionFunc(sceneTagCountCriterionHandler(qb, sceneFilter.TagCount))
query.handleCriterionFunc(scenePerformersCriterionHandler(qb, sceneFilter.Performers))
query.handleCriterionFunc(scenePerformerCountCriterionHandler(qb, sceneFilter.PerformerCount))
query.handleCriterionFunc(sceneStudioCriterionHandler(qb, sceneFilter.Studios))
query.handleCriterionFunc(sceneMoviesCriterionHandler(qb, sceneFilter.Movies))
query.handleCriterionFunc(scenePerformerTagsCriterionHandler(qb, sceneFilter.PerformerTags))
query.handleCriterion(criterionHandlerFunc(func(f *filterBuilder) {
if sceneFilter.StashID != nil {
qb.stashIDRepository().join(f, "scene_stash_ids", "scenes.id")
stringCriterionHandler(sceneFilter.StashID, "scene_stash_ids.stash_id")(f)
}
}))
query.handleCriterion(boolCriterionHandler(sceneFilter.Interactive, "scenes.interactive"))
query.handleCriterion(sceneTagsCriterionHandler(qb, sceneFilter.Tags))
query.handleCriterion(sceneTagCountCriterionHandler(qb, sceneFilter.TagCount))
query.handleCriterion(scenePerformersCriterionHandler(qb, sceneFilter.Performers))
query.handleCriterion(scenePerformerCountCriterionHandler(qb, sceneFilter.PerformerCount))
query.handleCriterion(sceneStudioCriterionHandler(qb, sceneFilter.Studios))
query.handleCriterion(sceneMoviesCriterionHandler(qb, sceneFilter.Movies))
query.handleCriterion(scenePerformerTagsCriterionHandler(qb, sceneFilter.PerformerTags))
return query
}
@ -401,10 +408,6 @@ func (qb *sceneQueryBuilder) Query(sceneFilter *models.SceneFilterType, findFilt
}
filter := qb.makeFilter(sceneFilter)
if sceneFilter.StashID != nil {
qb.stashIDRepository().join(filter, "scene_stash_ids", "scenes.id")
}
query.addFilter(filter)
qb.setSceneSort(&query, findFilter)
@ -427,14 +430,6 @@ func (qb *sceneQueryBuilder) Query(sceneFilter *models.SceneFilterType, findFilt
return scenes, countResult, nil
}
func appendClause(clauses []string, clause string) []string {
if clause != "" {
return append(clauses, clause)
}
return clauses
}
func durationCriterionHandler(durationFilter *models.IntCriterionInput, column string) criterionHandlerFunc {
return func(f *filterBuilder) {
if durationFilter != nil {
@ -524,6 +519,9 @@ func sceneIsMissingCriterionHandler(qb *sceneQueryBuilder, isMissing *string) cr
case "tags":
qb.tagsRepository().join(f, "tags_join", "scenes.id")
f.addWhere("tags_join.scene_id IS NULL")
case "stash_id":
qb.stashIDRepository().join(f, "scene_stash_ids", "scenes.id")
f.addWhere("scene_stash_ids.scene_id IS NULL")
default:
f.addWhere("(scenes." + *isMissing + " IS NULL OR TRIM(scenes." + *isMissing + ") = '')")
}
@ -638,31 +636,6 @@ func scenePerformerTagsCriterionHandler(qb *sceneQueryBuilder, performerTagsFilt
}
}
func handleScenePerformerTagsCriterion(query *queryBuilder, performerTagsFilter *models.MultiCriterionInput) {
if performerTagsFilter != nil && len(performerTagsFilter.Value) > 0 {
for _, tagID := range performerTagsFilter.Value {
query.addArg(tagID)
}
query.body += " LEFT JOIN performers_tags AS performer_tags_join on performers_join.performer_id = performer_tags_join.performer_id"
if performerTagsFilter.Modifier == models.CriterionModifierIncludes {
// includes any of the provided ids
query.addWhere("performer_tags_join.tag_id IN " + getInBinding(len(performerTagsFilter.Value)))
} else if performerTagsFilter.Modifier == models.CriterionModifierIncludesAll {
// includes all of the provided ids
query.addWhere("performer_tags_join.tag_id IN " + getInBinding(len(performerTagsFilter.Value)))
query.addHaving(fmt.Sprintf("count(distinct performer_tags_join.tag_id) IS %d", len(performerTagsFilter.Value)))
} else if performerTagsFilter.Modifier == models.CriterionModifierExcludes {
query.addWhere(fmt.Sprintf(`not exists
(select performers_scenes.performer_id from performers_scenes
left join performers_tags on performers_tags.performer_id = performers_scenes.performer_id where
performers_scenes.scene_id = scenes.id AND
performers_tags.tag_id in %s)`, getInBinding(len(performerTagsFilter.Value))))
}
}
}
func (qb *sceneQueryBuilder) getDefaultSceneSort() string {
return " ORDER BY scenes.path, scenes.date ASC "
}

View File

@ -1,3 +1,5 @@
// +build integration
package sqlite_test
import (

View File

@ -279,14 +279,14 @@ func (qb *tagQueryBuilder) makeFilter(tagFilter *models.TagFilterType) *filterBu
// }
// }
query.handleCriterionFunc(stringCriterionHandler(tagFilter.Name, tagTable+".name"))
query.handleCriterionFunc(tagAliasCriterionHandler(qb, tagFilter.Aliases))
query.handleCriterion(stringCriterionHandler(tagFilter.Name, tagTable+".name"))
query.handleCriterion(tagAliasCriterionHandler(qb, tagFilter.Aliases))
query.handleCriterionFunc(tagIsMissingCriterionHandler(qb, tagFilter.IsMissing))
query.handleCriterionFunc(tagSceneCountCriterionHandler(qb, tagFilter.SceneCount))
query.handleCriterionFunc(tagImageCountCriterionHandler(qb, tagFilter.ImageCount))
query.handleCriterionFunc(tagGalleryCountCriterionHandler(qb, tagFilter.GalleryCount))
query.handleCriterionFunc(tagPerformerCountCriterionHandler(qb, tagFilter.PerformerCount))
query.handleCriterion(tagIsMissingCriterionHandler(qb, tagFilter.IsMissing))
query.handleCriterion(tagSceneCountCriterionHandler(qb, tagFilter.SceneCount))
query.handleCriterion(tagImageCountCriterionHandler(qb, tagFilter.ImageCount))
query.handleCriterion(tagGalleryCountCriterionHandler(qb, tagFilter.GalleryCount))
query.handleCriterion(tagPerformerCountCriterionHandler(qb, tagFilter.PerformerCount))
return query
}

View File

@ -26,6 +26,7 @@
* Add button to remove studio stash ID. ([#1378](https://github.com/stashapp/stash/pull/1378))
### 🐛 Bug fixes
* Fix query with multiple table joins causing invalid query SQL. ([#1510](https://github.com/stashapp/stash/pull/1510))
* Fix file move detection when case of filename is changed on case-insensitive file systems. ([#1426](https://github.com/stashapp/stash/issues/1426))
* Fix auto-tagger not tagging scenes with no whitespace in name. ([#1488](https://github.com/stashapp/stash/pull/1488))
* Fix click/drag to select scenes. ([#1476](https://github.com/stashapp/stash/pull/1476))

View File

@ -1,4 +1,7 @@
import { createMandatoryNumberCriterionOption } from "./criteria/criterion";
import {
createMandatoryNumberCriterionOption,
createStringCriterionOption,
} from "./criteria/criterion";
import { TagIsMissingCriterionOption } from "./criteria/is-missing";
import { ListFilterOptions } from "./filter-options";
import { DisplayMode } from "./types";
@ -34,6 +37,7 @@ const sortByOptions = [
const displayModeOptions = [DisplayMode.Grid, DisplayMode.List];
const criterionOptions = [
TagIsMissingCriterionOption,
createStringCriterionOption("aliases"),
createMandatoryNumberCriterionOption("scene_count"),
createMandatoryNumberCriterionOption("image_count"),
createMandatoryNumberCriterionOption("gallery_count"),