Fix excludes handling in performer studio filter (#6413)

This commit is contained in:
WithoutPants
2025-12-15 11:49:30 +11:00
committed by GitHub
parent 7a8a2c7687
commit 1691280d1b
3 changed files with 162 additions and 34 deletions

View File

@@ -447,7 +447,7 @@ func (qb *performerFilterHandler) studiosCriterionHandler(studios *models.Hierar
return return
} }
if len(studios.Value) == 0 { if len(studios.Value) == 0 && len(studios.Excludes) == 0 {
return return
} }
@@ -464,27 +464,54 @@ func (qb *performerFilterHandler) studiosCriterionHandler(studios *models.Hierar
return return
} }
const derivedPerformerStudioTable = "performer_studio" if len(studios.Value) > 0 {
valuesClause, err := getHierarchicalValues(ctx, studios.Value, studioTable, "", "parent_id", "child_id", studios.Depth) const derivedPerformerStudioTable = "performer_studio"
if err != nil { valuesClause, err := getHierarchicalValues(ctx, studios.Value, studioTable, "", "parent_id", "child_id", studios.Depth)
f.setError(err) if err != nil {
return f.setError(err)
} return
f.addWith("studio(root_id, item_id) AS (" + valuesClause + ")") }
f.addWith("studio(root_id, item_id) AS (" + valuesClause + ")")
templStr := `SELECT performer_id FROM {primaryTable} templStr := `SELECT performer_id FROM {primaryTable}
INNER JOIN {joinTable} ON {primaryTable}.id = {joinTable}.{primaryFK}
INNER JOIN studio ON {primaryTable}.studio_id = studio.item_id`
var unions []string
for _, c := range formatMaps {
unions = append(unions, utils.StrFormat(templStr, c))
}
f.addWith(fmt.Sprintf("%s AS (%s)", derivedPerformerStudioTable, strings.Join(unions, " UNION ")))
f.addLeftJoin(derivedPerformerStudioTable, "", fmt.Sprintf("performers.id = %s.performer_id", derivedPerformerStudioTable))
f.addWhere(fmt.Sprintf("%s.performer_id IS %s NULL", derivedPerformerStudioTable, clauseCondition))
}
// #6412 - handle excludes as well
if len(studios.Excludes) > 0 {
excludeValuesClause, err := getHierarchicalValues(ctx, studios.Excludes, studioTable, "", "parent_id", "child_id", studios.Depth)
if err != nil {
f.setError(err)
return
}
f.addWith("exclude_studio(root_id, item_id) AS (" + excludeValuesClause + ")")
excludeTemplStr := `SELECT performer_id FROM {primaryTable}
INNER JOIN {joinTable} ON {primaryTable}.id = {joinTable}.{primaryFK} INNER JOIN {joinTable} ON {primaryTable}.id = {joinTable}.{primaryFK}
INNER JOIN studio ON {primaryTable}.studio_id = studio.item_id` INNER JOIN exclude_studio ON {primaryTable}.studio_id = exclude_studio.item_id`
var unions []string var unions []string
for _, c := range formatMaps { for _, c := range formatMaps {
unions = append(unions, utils.StrFormat(templStr, c)) unions = append(unions, utils.StrFormat(excludeTemplStr, c))
}
const excludePerformerStudioTable = "performer_studio_exclude"
f.addWith(fmt.Sprintf("%s AS (%s)", excludePerformerStudioTable, strings.Join(unions, " UNION ")))
f.addLeftJoin(excludePerformerStudioTable, "", fmt.Sprintf("performers.id = %s.performer_id", excludePerformerStudioTable))
f.addWhere(fmt.Sprintf("%s.performer_id IS NULL", excludePerformerStudioTable))
} }
f.addWith(fmt.Sprintf("%s AS (%s)", derivedPerformerStudioTable, strings.Join(unions, " UNION ")))
f.addLeftJoin(derivedPerformerStudioTable, "", fmt.Sprintf("performers.id = %s.performer_id", derivedPerformerStudioTable))
f.addWhere(fmt.Sprintf("%s.performer_id IS %s NULL", derivedPerformerStudioTable, clauseCondition))
} }
} }
} }

View File

@@ -1160,6 +1160,98 @@ func TestPerformerQuery(t *testing.T) {
[]int{performerIdx1WithScene, performerIdxWithScene}, []int{performerIdx1WithScene, performerIdxWithScene},
false, false,
}, },
{
"include scene studio",
nil,
&models.PerformerFilterType{
Studios: &models.HierarchicalMultiCriterionInput{
Value: []string{strconv.Itoa(studioIDs[studioIdxWithScenePerformer])},
Modifier: models.CriterionModifierIncludes,
},
},
[]int{performerIdxWithSceneStudio},
nil,
false,
},
{
"include image studio",
nil,
&models.PerformerFilterType{
Studios: &models.HierarchicalMultiCriterionInput{
Value: []string{strconv.Itoa(studioIDs[studioIdxWithImagePerformer])},
Modifier: models.CriterionModifierIncludes,
},
},
[]int{performerIdxWithImageStudio},
nil,
false,
},
{
"include gallery studio",
nil,
&models.PerformerFilterType{
Studios: &models.HierarchicalMultiCriterionInput{
Value: []string{strconv.Itoa(studioIDs[studioIdxWithGalleryPerformer])},
Modifier: models.CriterionModifierIncludes,
},
},
[]int{performerIdxWithGalleryStudio},
nil,
false,
},
{
"exclude scene studio",
nil,
&models.PerformerFilterType{
Studios: &models.HierarchicalMultiCriterionInput{
Value: []string{strconv.Itoa(studioIDs[studioIdxWithScenePerformer])},
Modifier: models.CriterionModifierExcludes,
},
},
nil,
[]int{performerIdxWithSceneStudio},
false,
},
{
"exclude image studio",
nil,
&models.PerformerFilterType{
Studios: &models.HierarchicalMultiCriterionInput{
Value: []string{strconv.Itoa(studioIDs[studioIdxWithImagePerformer])},
Modifier: models.CriterionModifierExcludes,
},
},
nil,
[]int{performerIdxWithImageStudio},
false,
},
{
"exclude gallery studio",
nil,
&models.PerformerFilterType{
Studios: &models.HierarchicalMultiCriterionInput{
Value: []string{strconv.Itoa(studioIDs[studioIdxWithGalleryPerformer])},
Modifier: models.CriterionModifierExcludes,
},
},
nil,
[]int{performerIdxWithGalleryStudio},
false,
},
{
"include and exclude scene studio",
nil,
&models.PerformerFilterType{
Studios: &models.HierarchicalMultiCriterionInput{
Value: []string{strconv.Itoa(studioIDs[studioIdx1WithTwoScenePerformer])},
Modifier: models.CriterionModifierIncludes,
Excludes: []string{strconv.Itoa(studioIDs[studioIdx2WithTwoScenePerformer])},
},
},
nil,
[]int{performerIdxWithTwoSceneStudio},
false,
},
} }
for _, tt := range tests { for _, tt := range tests {
@@ -2260,7 +2352,7 @@ func TestPerformerQuerySortScenesCount(t *testing.T) {
assert.True(t, len(performers) > 0) assert.True(t, len(performers) > 0)
lastPerformer := performers[len(performers)-1] lastPerformer := performers[len(performers)-1]
assert.Equal(t, performerIDs[performerIdxWithTag], lastPerformer.ID) assert.Equal(t, performerIDs[performerIdxWithTwoSceneStudio], lastPerformer.ID)
return nil return nil
}) })

View File

@@ -77,6 +77,8 @@ const (
sceneIdxWithPerformerTwoTags sceneIdxWithPerformerTwoTags
sceneIdxWithSpacedName sceneIdxWithSpacedName
sceneIdxWithStudioPerformer sceneIdxWithStudioPerformer
sceneIdx1WithTwoStudioPerformer
sceneIdx2WithTwoStudioPerformer
sceneIdxWithGrandChildStudio sceneIdxWithGrandChildStudio
sceneIdxMissingPhash sceneIdxMissingPhash
sceneIdxWithPerformerParentTag sceneIdxWithPerformerParentTag
@@ -138,6 +140,7 @@ const (
performerIdxWithSceneStudio performerIdxWithSceneStudio
performerIdxWithImageStudio performerIdxWithImageStudio
performerIdxWithGalleryStudio performerIdxWithGalleryStudio
performerIdxWithTwoSceneStudio
performerIdxWithParentTag performerIdxWithParentTag
// new indexes above // new indexes above
// performers with dup names start from the end // performers with dup names start from the end
@@ -257,6 +260,8 @@ const (
studioIdxWithScenePerformer studioIdxWithScenePerformer
studioIdxWithImagePerformer studioIdxWithImagePerformer
studioIdxWithGalleryPerformer studioIdxWithGalleryPerformer
studioIdx1WithTwoScenePerformer
studioIdx2WithTwoScenePerformer
studioIdxWithTag studioIdxWithTag
studioIdx2WithTag studioIdx2WithTag
studioIdxWithTwoTags studioIdxWithTwoTags
@@ -384,16 +389,18 @@ var (
} }
scenePerformers = linkMap{ scenePerformers = linkMap{
sceneIdxWithPerformer: {performerIdxWithScene}, sceneIdxWithPerformer: {performerIdxWithScene},
sceneIdxWithTwoPerformers: {performerIdx1WithScene, performerIdx2WithScene}, sceneIdxWithTwoPerformers: {performerIdx1WithScene, performerIdx2WithScene},
sceneIdxWithThreePerformers: {performerIdx1WithScene, performerIdx2WithScene, performerIdx3WithScene}, sceneIdxWithThreePerformers: {performerIdx1WithScene, performerIdx2WithScene, performerIdx3WithScene},
sceneIdxWithPerformerTag: {performerIdxWithTag}, sceneIdxWithPerformerTag: {performerIdxWithTag},
sceneIdxWithTwoPerformerTag: {performerIdxWithTag, performerIdx2WithTag}, sceneIdxWithTwoPerformerTag: {performerIdxWithTag, performerIdx2WithTag},
sceneIdxWithPerformerTwoTags: {performerIdxWithTwoTags}, sceneIdxWithPerformerTwoTags: {performerIdxWithTwoTags},
sceneIdx1WithPerformer: {performerIdxWithTwoScenes}, sceneIdx1WithPerformer: {performerIdxWithTwoScenes},
sceneIdx2WithPerformer: {performerIdxWithTwoScenes}, sceneIdx2WithPerformer: {performerIdxWithTwoScenes},
sceneIdxWithStudioPerformer: {performerIdxWithSceneStudio}, sceneIdxWithStudioPerformer: {performerIdxWithSceneStudio},
sceneIdxWithPerformerParentTag: {performerIdxWithParentTag}, sceneIdx1WithTwoStudioPerformer: {performerIdxWithTwoSceneStudio},
sceneIdx2WithTwoStudioPerformer: {performerIdxWithTwoSceneStudio},
sceneIdxWithPerformerParentTag: {performerIdxWithParentTag},
} }
sceneGalleries = linkMap{ sceneGalleries = linkMap{
@@ -406,11 +413,13 @@ var (
} }
sceneStudios = map[int]int{ sceneStudios = map[int]int{
sceneIdxWithStudio: studioIdxWithScene, sceneIdxWithStudio: studioIdxWithScene,
sceneIdx1WithStudio: studioIdxWithTwoScenes, sceneIdx1WithStudio: studioIdxWithTwoScenes,
sceneIdx2WithStudio: studioIdxWithTwoScenes, sceneIdx2WithStudio: studioIdxWithTwoScenes,
sceneIdxWithStudioPerformer: studioIdxWithScenePerformer, sceneIdxWithStudioPerformer: studioIdxWithScenePerformer,
sceneIdxWithGrandChildStudio: studioIdxWithGrandParent, sceneIdx1WithTwoStudioPerformer: studioIdx1WithTwoScenePerformer,
sceneIdx2WithTwoStudioPerformer: studioIdx2WithTwoScenePerformer,
sceneIdxWithGrandChildStudio: studioIdxWithGrandParent,
} }
) )