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
}
if len(studios.Value) == 0 {
if len(studios.Value) == 0 && len(studios.Excludes) == 0 {
return
}
@@ -464,27 +464,54 @@ func (qb *performerFilterHandler) studiosCriterionHandler(studios *models.Hierar
return
}
const derivedPerformerStudioTable = "performer_studio"
valuesClause, err := getHierarchicalValues(ctx, studios.Value, studioTable, "", "parent_id", "child_id", studios.Depth)
if err != nil {
f.setError(err)
return
}
f.addWith("studio(root_id, item_id) AS (" + valuesClause + ")")
if len(studios.Value) > 0 {
const derivedPerformerStudioTable = "performer_studio"
valuesClause, err := getHierarchicalValues(ctx, studios.Value, studioTable, "", "parent_id", "child_id", studios.Depth)
if err != nil {
f.setError(err)
return
}
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 studio ON {primaryTable}.studio_id = studio.item_id`
INNER JOIN exclude_studio ON {primaryTable}.studio_id = exclude_studio.item_id`
var unions []string
for _, c := range formatMaps {
unions = append(unions, utils.StrFormat(templStr, c))
var unions []string
for _, c := range formatMaps {
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},
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 {
@@ -2260,7 +2352,7 @@ func TestPerformerQuerySortScenesCount(t *testing.T) {
assert.True(t, len(performers) > 0)
lastPerformer := performers[len(performers)-1]
assert.Equal(t, performerIDs[performerIdxWithTag], lastPerformer.ID)
assert.Equal(t, performerIDs[performerIdxWithTwoSceneStudio], lastPerformer.ID)
return nil
})

View File

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