Actually implement TagFilter.marker_count (#1603)

* Actually implement TagFilter.marker_count

The marker_count filter/criterion as defined in TagFilterType isn't
actually implemented. This adds an implementation for it.

Do note this implementation _might_ have performance issues because of
using OR (in the join). Another implentation would be to remove both
joins and use:
```SQL
COUNT(
    SELECT id FROM scene_markers WHERE primary_tag_id = tags.id
  UNION
    SELECT scene_marker_id FROM scene_markers_tags WHERE tag_id = tags.id
)
```
Note this doesn't require a DISTINCT as UNION already removes any
duplicate records.

* Restore marker count filter and sorting

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
gitgiggety 2021-08-04 05:39:24 +02:00 committed by GitHub
parent ac41416cd7
commit 9d641c64e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 42 additions and 46 deletions

View file

@ -271,14 +271,6 @@ func (qb *tagQueryBuilder) makeFilter(tagFilter *models.TagFilterType) *filterBu
query.not(qb.makeFilter(tagFilter.Not))
}
// if markerCount := tagFilter.MarkerCount; markerCount != nil {
// clause, count := getIntCriterionWhereClause("count(distinct scene_markers.id)", *markerCount)
// query.addHaving(clause)
// if count == 1 {
// query.addArg(markerCount.Value)
// }
// }
query.handleCriterion(stringCriterionHandler(tagFilter.Name, tagTable+".name"))
query.handleCriterion(tagAliasCriterionHandler(qb, tagFilter.Aliases))
@ -287,6 +279,7 @@ func (qb *tagQueryBuilder) makeFilter(tagFilter *models.TagFilterType) *filterBu
query.handleCriterion(tagImageCountCriterionHandler(qb, tagFilter.ImageCount))
query.handleCriterion(tagGalleryCountCriterionHandler(qb, tagFilter.GalleryCount))
query.handleCriterion(tagPerformerCountCriterionHandler(qb, tagFilter.PerformerCount))
query.handleCriterion(tagMarkerCountCriterionHandler(qb, tagFilter.MarkerCount))
return query
}
@ -303,19 +296,6 @@ func (qb *tagQueryBuilder) Query(tagFilter *models.TagFilterType, findFilter *mo
query.body = selectDistinctIDs(tagTable)
/*
query.body += `
left join tags_image on tags_image.tag_id = tags.id
left join scenes_tags on scenes_tags.tag_id = tags.id
left join scene_markers_tags on scene_markers_tags.tag_id = tags.id
left join scene_markers on scene_markers.primary_tag_id = tags.id OR scene_markers.id = scene_markers_tags.scene_marker_id
left join scenes on scenes_tags.scene_id = scenes.id`
*/
// the presence of joining on scene_markers.primary_tag_id and scene_markers_tags.tag_id
// appears to confuse sqlite and causes serious performance issues.
// Disabling querying/sorting on marker count for now.
if q := findFilter.Q; q != nil && *q != "" {
query.join(tagAliasesTable, "", "tag_aliases.tag_id = tags.id")
searchColumns := []string{"tags.name", "tag_aliases.alias"}
@ -439,6 +419,23 @@ func tagPerformerCountCriterionHandler(qb *tagQueryBuilder, performerCount *mode
}
}
func tagMarkerCountCriterionHandler(qb *tagQueryBuilder, markerCount *models.IntCriterionInput) criterionHandlerFunc {
return func(f *filterBuilder) {
if markerCount != nil {
f.addJoin("scene_markers_tags", "", "scene_markers_tags.tag_id = tags.id")
f.addJoin("scene_markers", "", "scene_markers_tags.scene_marker_id = scene_markers.id OR scene_markers.primary_tag_id = tags.id")
clause, count := getIntCriterionWhereClause("count(distinct scene_markers.id)", *markerCount)
args := []interface{}{}
if count == 1 {
args = append(args, markerCount.Value)
}
f.addHaving(clause, args...)
}
}
}
func (qb *tagQueryBuilder) getDefaultTagSort() string {
return getSort("name", "ASC", "tags")
}
@ -459,6 +456,9 @@ func (qb *tagQueryBuilder) getTagSort(query *queryBuilder, findFilter *models.Fi
case "scenes_count":
query.join("scenes_tags", "", "scenes_tags.tag_id = tags.id")
return " ORDER BY COUNT(distinct scenes_tags.scene_id) " + direction
case "scene_markers_count":
query.join("scene_markers_tags", "", "scene_markers_tags.tag_id = tags.id")
return " ORDER BY COUNT(distinct scene_markers_tags.scene_marker_id) " + direction
case "images_count":
query.join("images_tags", "", "images_tags.tag_id = tags.id")
return " ORDER BY COUNT(distinct images_tags.image_id) " + direction

View file

@ -320,26 +320,24 @@ func verifyTagSceneCount(t *testing.T, sceneCountCriterion models.IntCriterionIn
})
}
// disabled due to performance issues
func TestTagQueryMarkerCount(t *testing.T) {
countCriterion := models.IntCriterionInput{
Value: 1,
Modifier: models.CriterionModifierEquals,
}
// func TestTagQueryMarkerCount(t *testing.T) {
// countCriterion := models.IntCriterionInput{
// Value: 1,
// Modifier: models.CriterionModifierEquals,
// }
verifyTagMarkerCount(t, countCriterion)
// verifyTagMarkerCount(t, countCriterion)
countCriterion.Modifier = models.CriterionModifierNotEquals
verifyTagMarkerCount(t, countCriterion)
// countCriterion.Modifier = models.CriterionModifierNotEquals
// verifyTagMarkerCount(t, countCriterion)
countCriterion.Modifier = models.CriterionModifierLessThan
verifyTagMarkerCount(t, countCriterion)
// countCriterion.Modifier = models.CriterionModifierLessThan
// verifyTagMarkerCount(t, countCriterion)
// countCriterion.Value = 0
// countCriterion.Modifier = models.CriterionModifierGreaterThan
// verifyTagMarkerCount(t, countCriterion)
// }
countCriterion.Value = 0
countCriterion.Modifier = models.CriterionModifierGreaterThan
verifyTagMarkerCount(t, countCriterion)
}
func verifyTagMarkerCount(t *testing.T, markerCountCriterion models.IntCriterionInput) {
withTxn(func(r models.Repository) error {

View file

@ -1,4 +1,5 @@
### ✨ New Features
* Added filtering and sorting on scene marker count for tags. ([#1603](https://github.com/stashapp/stash/pull/1603))
* Support excluding fields and editing tags when saving from scene tagger view. ([#1605](https://github.com/stashapp/stash/pull/1605))
* Added not equals/greater than/less than modifiers for resolution criteria. ([#1568](https://github.com/stashapp/stash/pull/1568))

View file

@ -8,13 +8,7 @@ import { ListFilterOptions } from "./filter-options";
import { DisplayMode } from "./types";
const defaultSortBy = "name";
// scene markers count has been disabled for now due to performance
// issues
const sortByOptions = [
"name",
"random",
/* "scene_markers_count" */
]
const sortByOptions = ["name", "random"]
.map(ListFilterOptions.createSortBy)
.concat([
{
@ -33,6 +27,10 @@ const sortByOptions = [
messageID: "scene_count",
value: "scenes_count",
},
{
messageID: "marker_count",
value: "scene_markers_count",
},
]);
const displayModeOptions = [DisplayMode.Grid, DisplayMode.List];
@ -44,8 +42,7 @@ const criterionOptions = [
createMandatoryNumberCriterionOption("image_count"),
createMandatoryNumberCriterionOption("gallery_count"),
createMandatoryNumberCriterionOption("performer_count"),
// marker count has been disabled for now due to performance issues
// ListFilterModel.createCriterionOption("marker_count"),
createMandatoryNumberCriterionOption("marker_count"),
];
export const TagListFilterOptions = new ListFilterOptions(