mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 12:24:38 +03:00
Join count filter criteria (#1254)
Co-authored-by: mrbrdo <mrbrdo@gmail.com> Co-authored-by: peolic <66393006+peolic@users.noreply.github.com>
This commit is contained in:
@@ -61,6 +61,14 @@ input PerformerFilterType {
|
|||||||
is_missing: String
|
is_missing: String
|
||||||
"""Filter to only include performers with these tags"""
|
"""Filter to only include performers with these tags"""
|
||||||
tags: MultiCriterionInput
|
tags: MultiCriterionInput
|
||||||
|
"""Filter by tag count"""
|
||||||
|
tag_count: IntCriterionInput
|
||||||
|
"""Filter by scene count"""
|
||||||
|
scene_count: IntCriterionInput
|
||||||
|
"""Filter by image count"""
|
||||||
|
image_count: IntCriterionInput
|
||||||
|
"""Filter by gallery count"""
|
||||||
|
gallery_count: IntCriterionInput
|
||||||
"""Filter by StashID"""
|
"""Filter by StashID"""
|
||||||
stash_id: String
|
stash_id: String
|
||||||
"""Filter by url"""
|
"""Filter by url"""
|
||||||
@@ -105,10 +113,14 @@ input SceneFilterType {
|
|||||||
movies: MultiCriterionInput
|
movies: MultiCriterionInput
|
||||||
"""Filter to only include scenes with these tags"""
|
"""Filter to only include scenes with these tags"""
|
||||||
tags: MultiCriterionInput
|
tags: MultiCriterionInput
|
||||||
|
"""Filter by tag count"""
|
||||||
|
tag_count: IntCriterionInput
|
||||||
"""Filter to only include scenes with performers with these tags"""
|
"""Filter to only include scenes with performers with these tags"""
|
||||||
performer_tags: MultiCriterionInput
|
performer_tags: MultiCriterionInput
|
||||||
"""Filter to only include scenes with these performers"""
|
"""Filter to only include scenes with these performers"""
|
||||||
performers: MultiCriterionInput
|
performers: MultiCriterionInput
|
||||||
|
"""Filter by performer count"""
|
||||||
|
performer_count: IntCriterionInput
|
||||||
"""Filter by StashID"""
|
"""Filter by StashID"""
|
||||||
stash_id: String
|
stash_id: String
|
||||||
"""Filter by url"""
|
"""Filter by url"""
|
||||||
@@ -152,10 +164,14 @@ input GalleryFilterType {
|
|||||||
studios: MultiCriterionInput
|
studios: MultiCriterionInput
|
||||||
"""Filter to only include galleries with these tags"""
|
"""Filter to only include galleries with these tags"""
|
||||||
tags: MultiCriterionInput
|
tags: MultiCriterionInput
|
||||||
|
"""Filter by tag count"""
|
||||||
|
tag_count: IntCriterionInput
|
||||||
"""Filter to only include galleries with performers with these tags"""
|
"""Filter to only include galleries with performers with these tags"""
|
||||||
performer_tags: MultiCriterionInput
|
performer_tags: MultiCriterionInput
|
||||||
"""Filter to only include galleries with these performers"""
|
"""Filter to only include galleries with these performers"""
|
||||||
performers: MultiCriterionInput
|
performers: MultiCriterionInput
|
||||||
|
"""Filter by performer count"""
|
||||||
|
performer_count: IntCriterionInput
|
||||||
"""Filter by number of images in this gallery"""
|
"""Filter by number of images in this gallery"""
|
||||||
image_count: IntCriterionInput
|
image_count: IntCriterionInput
|
||||||
"""Filter by url"""
|
"""Filter by url"""
|
||||||
@@ -203,10 +219,14 @@ input ImageFilterType {
|
|||||||
studios: MultiCriterionInput
|
studios: MultiCriterionInput
|
||||||
"""Filter to only include images with these tags"""
|
"""Filter to only include images with these tags"""
|
||||||
tags: MultiCriterionInput
|
tags: MultiCriterionInput
|
||||||
|
"""Filter by tag count"""
|
||||||
|
tag_count: IntCriterionInput
|
||||||
"""Filter to only include images with performers with these tags"""
|
"""Filter to only include images with performers with these tags"""
|
||||||
performer_tags: MultiCriterionInput
|
performer_tags: MultiCriterionInput
|
||||||
"""Filter to only include images with these performers"""
|
"""Filter to only include images with these performers"""
|
||||||
performers: MultiCriterionInput
|
performers: MultiCriterionInput
|
||||||
|
"""Filter by performer count"""
|
||||||
|
performer_count: IntCriterionInput
|
||||||
"""Filter to only include images with these galleries"""
|
"""Filter to only include images with these galleries"""
|
||||||
galleries: MultiCriterionInput
|
galleries: MultiCriterionInput
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -405,3 +405,23 @@ func (m *multiCriterionHandlerBuilder) handler(criterion *models.MultiCriterionI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type countCriterionHandlerBuilder struct {
|
||||||
|
primaryTable string
|
||||||
|
joinTable string
|
||||||
|
primaryFK string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *countCriterionHandlerBuilder) handler(criterion *models.IntCriterionInput) criterionHandlerFunc {
|
||||||
|
return func(f *filterBuilder) {
|
||||||
|
if criterion != nil {
|
||||||
|
clause, count := getCountCriterionClause(m.primaryTable, m.joinTable, m.primaryFK, *criterion)
|
||||||
|
|
||||||
|
if count == 1 {
|
||||||
|
f.addWhere(clause, criterion.Value)
|
||||||
|
} else {
|
||||||
|
f.addWhere(clause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -239,6 +239,16 @@ func (qb *galleryQueryBuilder) Query(galleryFilter *models.GalleryFilterType, fi
|
|||||||
query.addHaving(havingClause)
|
query.addHaving(havingClause)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tagCountFilter := galleryFilter.TagCount; tagCountFilter != nil {
|
||||||
|
clause, count := getCountCriterionClause(galleryTable, galleriesTagsTable, galleryIDColumn, *tagCountFilter)
|
||||||
|
|
||||||
|
if count == 1 {
|
||||||
|
query.addArg(tagCountFilter.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
query.addWhere(clause)
|
||||||
|
}
|
||||||
|
|
||||||
if performersFilter := galleryFilter.Performers; performersFilter != nil && len(performersFilter.Value) > 0 {
|
if performersFilter := galleryFilter.Performers; performersFilter != nil && len(performersFilter.Value) > 0 {
|
||||||
for _, performerID := range performersFilter.Value {
|
for _, performerID := range performersFilter.Value {
|
||||||
query.addArg(performerID)
|
query.addArg(performerID)
|
||||||
@@ -250,6 +260,16 @@ func (qb *galleryQueryBuilder) Query(galleryFilter *models.GalleryFilterType, fi
|
|||||||
query.addHaving(havingClause)
|
query.addHaving(havingClause)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if performerCountFilter := galleryFilter.PerformerCount; performerCountFilter != nil {
|
||||||
|
clause, count := getCountCriterionClause(galleryTable, performersGalleriesTable, galleryIDColumn, *performerCountFilter)
|
||||||
|
|
||||||
|
if count == 1 {
|
||||||
|
query.addArg(performerCountFilter.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
query.addWhere(clause)
|
||||||
|
}
|
||||||
|
|
||||||
if studiosFilter := galleryFilter.Studios; studiosFilter != nil && len(studiosFilter.Value) > 0 {
|
if studiosFilter := galleryFilter.Studios; studiosFilter != nil && len(studiosFilter.Value) > 0 {
|
||||||
for _, studioID := range studiosFilter.Value {
|
for _, studioID := range studiosFilter.Value {
|
||||||
query.addArg(studioID)
|
query.addArg(studioID)
|
||||||
@@ -382,7 +402,15 @@ func (qb *galleryQueryBuilder) getGallerySort(findFilter *models.FindFilterType)
|
|||||||
sort = findFilter.GetSort("path")
|
sort = findFilter.GetSort("path")
|
||||||
direction = findFilter.GetDirection()
|
direction = findFilter.GetDirection()
|
||||||
}
|
}
|
||||||
return getSort(sort, direction, "galleries")
|
|
||||||
|
switch sort {
|
||||||
|
case "tag_count":
|
||||||
|
return getCountSort(galleryTable, galleriesTagsTable, galleryIDColumn, direction)
|
||||||
|
case "performer_count":
|
||||||
|
return getCountSort(galleryTable, performersGalleriesTable, galleryIDColumn, direction)
|
||||||
|
default:
|
||||||
|
return getSort(sort, direction, "galleries")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (qb *galleryQueryBuilder) queryGallery(query string, args []interface{}) (*models.Gallery, error) {
|
func (qb *galleryQueryBuilder) queryGallery(query string, args []interface{}) (*models.Gallery, error) {
|
||||||
|
|||||||
@@ -630,6 +630,88 @@ func TestGalleryQueryPerformerTags(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGalleryQueryTagCount(t *testing.T) {
|
||||||
|
const tagCount = 1
|
||||||
|
tagCountCriterion := models.IntCriterionInput{
|
||||||
|
Value: tagCount,
|
||||||
|
Modifier: models.CriterionModifierEquals,
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyGalleriesTagCount(t, tagCountCriterion)
|
||||||
|
|
||||||
|
tagCountCriterion.Modifier = models.CriterionModifierNotEquals
|
||||||
|
verifyGalleriesTagCount(t, tagCountCriterion)
|
||||||
|
|
||||||
|
tagCountCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||||
|
verifyGalleriesTagCount(t, tagCountCriterion)
|
||||||
|
|
||||||
|
tagCountCriterion.Modifier = models.CriterionModifierLessThan
|
||||||
|
verifyGalleriesTagCount(t, tagCountCriterion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyGalleriesTagCount(t *testing.T, tagCountCriterion models.IntCriterionInput) {
|
||||||
|
withTxn(func(r models.Repository) error {
|
||||||
|
sqb := r.Gallery()
|
||||||
|
galleryFilter := models.GalleryFilterType{
|
||||||
|
TagCount: &tagCountCriterion,
|
||||||
|
}
|
||||||
|
|
||||||
|
galleries := queryGallery(t, sqb, &galleryFilter, nil)
|
||||||
|
assert.Greater(t, len(galleries), 0)
|
||||||
|
|
||||||
|
for _, gallery := range galleries {
|
||||||
|
ids, err := sqb.GetTagIDs(gallery.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
verifyInt(t, len(ids), tagCountCriterion)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGalleryQueryPerformerCount(t *testing.T) {
|
||||||
|
const performerCount = 1
|
||||||
|
performerCountCriterion := models.IntCriterionInput{
|
||||||
|
Value: performerCount,
|
||||||
|
Modifier: models.CriterionModifierEquals,
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyGalleriesPerformerCount(t, performerCountCriterion)
|
||||||
|
|
||||||
|
performerCountCriterion.Modifier = models.CriterionModifierNotEquals
|
||||||
|
verifyGalleriesPerformerCount(t, performerCountCriterion)
|
||||||
|
|
||||||
|
performerCountCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||||
|
verifyGalleriesPerformerCount(t, performerCountCriterion)
|
||||||
|
|
||||||
|
performerCountCriterion.Modifier = models.CriterionModifierLessThan
|
||||||
|
verifyGalleriesPerformerCount(t, performerCountCriterion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyGalleriesPerformerCount(t *testing.T, performerCountCriterion models.IntCriterionInput) {
|
||||||
|
withTxn(func(r models.Repository) error {
|
||||||
|
sqb := r.Gallery()
|
||||||
|
galleryFilter := models.GalleryFilterType{
|
||||||
|
PerformerCount: &performerCountCriterion,
|
||||||
|
}
|
||||||
|
|
||||||
|
galleries := queryGallery(t, sqb, &galleryFilter, nil)
|
||||||
|
assert.Greater(t, len(galleries), 0)
|
||||||
|
|
||||||
|
for _, gallery := range galleries {
|
||||||
|
ids, err := sqb.GetPerformerIDs(gallery.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
verifyInt(t, len(ids), performerCountCriterion)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// TODO Count
|
// TODO Count
|
||||||
// TODO All
|
// TODO All
|
||||||
// TODO Query
|
// TODO Query
|
||||||
|
|||||||
@@ -328,6 +328,16 @@ func (qb *imageQueryBuilder) Query(imageFilter *models.ImageFilterType, findFilt
|
|||||||
query.addHaving(havingClause)
|
query.addHaving(havingClause)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tagCountFilter := imageFilter.TagCount; tagCountFilter != nil {
|
||||||
|
clause, count := getCountCriterionClause(imageTable, imagesTagsTable, imageIDColumn, *tagCountFilter)
|
||||||
|
|
||||||
|
if count == 1 {
|
||||||
|
query.addArg(tagCountFilter.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
query.addWhere(clause)
|
||||||
|
}
|
||||||
|
|
||||||
if galleriesFilter := imageFilter.Galleries; galleriesFilter != nil && len(galleriesFilter.Value) > 0 {
|
if galleriesFilter := imageFilter.Galleries; galleriesFilter != nil && len(galleriesFilter.Value) > 0 {
|
||||||
for _, galleryID := range galleriesFilter.Value {
|
for _, galleryID := range galleriesFilter.Value {
|
||||||
query.addArg(galleryID)
|
query.addArg(galleryID)
|
||||||
@@ -350,6 +360,16 @@ func (qb *imageQueryBuilder) Query(imageFilter *models.ImageFilterType, findFilt
|
|||||||
query.addHaving(havingClause)
|
query.addHaving(havingClause)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if performerCountFilter := imageFilter.PerformerCount; performerCountFilter != nil {
|
||||||
|
clause, count := getCountCriterionClause(imageTable, performersImagesTable, imageIDColumn, *performerCountFilter)
|
||||||
|
|
||||||
|
if count == 1 {
|
||||||
|
query.addArg(performerCountFilter.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
query.addWhere(clause)
|
||||||
|
}
|
||||||
|
|
||||||
if studiosFilter := imageFilter.Studios; studiosFilter != nil && len(studiosFilter.Value) > 0 {
|
if studiosFilter := imageFilter.Studios; studiosFilter != nil && len(studiosFilter.Value) > 0 {
|
||||||
for _, studioID := range studiosFilter.Value {
|
for _, studioID := range studiosFilter.Value {
|
||||||
query.addArg(studioID)
|
query.addArg(studioID)
|
||||||
@@ -412,7 +432,15 @@ func (qb *imageQueryBuilder) getImageSort(findFilter *models.FindFilterType) str
|
|||||||
}
|
}
|
||||||
sort := findFilter.GetSort("title")
|
sort := findFilter.GetSort("title")
|
||||||
direction := findFilter.GetDirection()
|
direction := findFilter.GetDirection()
|
||||||
return getSort(sort, direction, "images")
|
|
||||||
|
switch sort {
|
||||||
|
case "tag_count":
|
||||||
|
return getCountSort(imageTable, imagesTagsTable, imageIDColumn, direction)
|
||||||
|
case "performer_count":
|
||||||
|
return getCountSort(imageTable, performersImagesTable, imageIDColumn, direction)
|
||||||
|
default:
|
||||||
|
return getSort(sort, direction, "images")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (qb *imageQueryBuilder) queryImage(query string, args []interface{}) (*models.Image, error) {
|
func (qb *imageQueryBuilder) queryImage(query string, args []interface{}) (*models.Image, error) {
|
||||||
|
|||||||
@@ -683,6 +683,88 @@ func TestImageQueryPerformerTags(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImageQueryTagCount(t *testing.T) {
|
||||||
|
const tagCount = 1
|
||||||
|
tagCountCriterion := models.IntCriterionInput{
|
||||||
|
Value: tagCount,
|
||||||
|
Modifier: models.CriterionModifierEquals,
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyImagesTagCount(t, tagCountCriterion)
|
||||||
|
|
||||||
|
tagCountCriterion.Modifier = models.CriterionModifierNotEquals
|
||||||
|
verifyImagesTagCount(t, tagCountCriterion)
|
||||||
|
|
||||||
|
tagCountCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||||
|
verifyImagesTagCount(t, tagCountCriterion)
|
||||||
|
|
||||||
|
tagCountCriterion.Modifier = models.CriterionModifierLessThan
|
||||||
|
verifyImagesTagCount(t, tagCountCriterion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyImagesTagCount(t *testing.T, tagCountCriterion models.IntCriterionInput) {
|
||||||
|
withTxn(func(r models.Repository) error {
|
||||||
|
sqb := r.Image()
|
||||||
|
imageFilter := models.ImageFilterType{
|
||||||
|
TagCount: &tagCountCriterion,
|
||||||
|
}
|
||||||
|
|
||||||
|
images := queryImages(t, sqb, &imageFilter, nil)
|
||||||
|
assert.Greater(t, len(images), 0)
|
||||||
|
|
||||||
|
for _, image := range images {
|
||||||
|
ids, err := sqb.GetTagIDs(image.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
verifyInt(t, len(ids), tagCountCriterion)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImageQueryPerformerCount(t *testing.T) {
|
||||||
|
const performerCount = 1
|
||||||
|
performerCountCriterion := models.IntCriterionInput{
|
||||||
|
Value: performerCount,
|
||||||
|
Modifier: models.CriterionModifierEquals,
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyImagesPerformerCount(t, performerCountCriterion)
|
||||||
|
|
||||||
|
performerCountCriterion.Modifier = models.CriterionModifierNotEquals
|
||||||
|
verifyImagesPerformerCount(t, performerCountCriterion)
|
||||||
|
|
||||||
|
performerCountCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||||
|
verifyImagesPerformerCount(t, performerCountCriterion)
|
||||||
|
|
||||||
|
performerCountCriterion.Modifier = models.CriterionModifierLessThan
|
||||||
|
verifyImagesPerformerCount(t, performerCountCriterion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyImagesPerformerCount(t *testing.T, performerCountCriterion models.IntCriterionInput) {
|
||||||
|
withTxn(func(r models.Repository) error {
|
||||||
|
sqb := r.Image()
|
||||||
|
imageFilter := models.ImageFilterType{
|
||||||
|
PerformerCount: &performerCountCriterion,
|
||||||
|
}
|
||||||
|
|
||||||
|
images := queryImages(t, sqb, &imageFilter, nil)
|
||||||
|
assert.Greater(t, len(images), 0)
|
||||||
|
|
||||||
|
for _, image := range images {
|
||||||
|
ids, err := sqb.GetPerformerIDs(image.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
verifyInt(t, len(ids), performerCountCriterion)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestImageQuerySorting(t *testing.T) {
|
func TestImageQuerySorting(t *testing.T) {
|
||||||
withTxn(func(r models.Repository) error {
|
withTxn(func(r models.Repository) error {
|
||||||
sort := titleField
|
sort := titleField
|
||||||
|
|||||||
@@ -271,6 +271,11 @@ func (qb *performerQueryBuilder) Query(performerFilter *models.PerformerFilterTy
|
|||||||
query.addHaving(havingClause)
|
query.addHaving(havingClause)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query.handleCountCriterion(performerFilter.TagCount, performerTable, performersTagsTable, performerIDColumn)
|
||||||
|
query.handleCountCriterion(performerFilter.SceneCount, performerTable, performersScenesTable, performerIDColumn)
|
||||||
|
query.handleCountCriterion(performerFilter.ImageCount, performerTable, performersImagesTable, performerIDColumn)
|
||||||
|
query.handleCountCriterion(performerFilter.GalleryCount, performerTable, performersGalleriesTable, performerIDColumn)
|
||||||
|
|
||||||
query.sortAndPagination = qb.getPerformerSort(findFilter) + getPagination(findFilter)
|
query.sortAndPagination = qb.getPerformerSort(findFilter) + getPagination(findFilter)
|
||||||
idsResult, countResult, err := query.executeFind()
|
idsResult, countResult, err := query.executeFind()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -370,6 +375,11 @@ func (qb *performerQueryBuilder) getPerformerSort(findFilter *models.FindFilterT
|
|||||||
sort = findFilter.GetSort("name")
|
sort = findFilter.GetSort("name")
|
||||||
direction = findFilter.GetDirection()
|
direction = findFilter.GetDirection()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sort == "tag_count" {
|
||||||
|
return getCountSort(performerTable, performersTagsTable, performerIDColumn, direction)
|
||||||
|
}
|
||||||
|
|
||||||
return getSort(sort, direction, "performers")
|
return getSort(sort, direction, "performers")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -387,6 +387,188 @@ func TestPerformerQueryTags(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPerformerQueryTagCount(t *testing.T) {
|
||||||
|
const tagCount = 1
|
||||||
|
tagCountCriterion := models.IntCriterionInput{
|
||||||
|
Value: tagCount,
|
||||||
|
Modifier: models.CriterionModifierEquals,
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyPerformersTagCount(t, tagCountCriterion)
|
||||||
|
|
||||||
|
tagCountCriterion.Modifier = models.CriterionModifierNotEquals
|
||||||
|
verifyPerformersTagCount(t, tagCountCriterion)
|
||||||
|
|
||||||
|
tagCountCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||||
|
verifyPerformersTagCount(t, tagCountCriterion)
|
||||||
|
|
||||||
|
tagCountCriterion.Modifier = models.CriterionModifierLessThan
|
||||||
|
verifyPerformersTagCount(t, tagCountCriterion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyPerformersTagCount(t *testing.T, tagCountCriterion models.IntCriterionInput) {
|
||||||
|
withTxn(func(r models.Repository) error {
|
||||||
|
sqb := r.Performer()
|
||||||
|
performerFilter := models.PerformerFilterType{
|
||||||
|
TagCount: &tagCountCriterion,
|
||||||
|
}
|
||||||
|
|
||||||
|
performers := queryPerformers(t, sqb, &performerFilter, nil)
|
||||||
|
assert.Greater(t, len(performers), 0)
|
||||||
|
|
||||||
|
for _, performer := range performers {
|
||||||
|
ids, err := sqb.GetTagIDs(performer.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
verifyInt(t, len(ids), tagCountCriterion)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPerformerQuerySceneCount(t *testing.T) {
|
||||||
|
const sceneCount = 1
|
||||||
|
sceneCountCriterion := models.IntCriterionInput{
|
||||||
|
Value: sceneCount,
|
||||||
|
Modifier: models.CriterionModifierEquals,
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyPerformersSceneCount(t, sceneCountCriterion)
|
||||||
|
|
||||||
|
sceneCountCriterion.Modifier = models.CriterionModifierNotEquals
|
||||||
|
verifyPerformersSceneCount(t, sceneCountCriterion)
|
||||||
|
|
||||||
|
sceneCountCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||||
|
verifyPerformersSceneCount(t, sceneCountCriterion)
|
||||||
|
|
||||||
|
sceneCountCriterion.Modifier = models.CriterionModifierLessThan
|
||||||
|
verifyPerformersSceneCount(t, sceneCountCriterion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyPerformersSceneCount(t *testing.T, sceneCountCriterion models.IntCriterionInput) {
|
||||||
|
withTxn(func(r models.Repository) error {
|
||||||
|
sqb := r.Performer()
|
||||||
|
performerFilter := models.PerformerFilterType{
|
||||||
|
SceneCount: &sceneCountCriterion,
|
||||||
|
}
|
||||||
|
|
||||||
|
performers := queryPerformers(t, sqb, &performerFilter, nil)
|
||||||
|
assert.Greater(t, len(performers), 0)
|
||||||
|
|
||||||
|
for _, performer := range performers {
|
||||||
|
ids, err := r.Scene().FindByPerformerID(performer.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
verifyInt(t, len(ids), sceneCountCriterion)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPerformerQueryImageCount(t *testing.T) {
|
||||||
|
const imageCount = 1
|
||||||
|
imageCountCriterion := models.IntCriterionInput{
|
||||||
|
Value: imageCount,
|
||||||
|
Modifier: models.CriterionModifierEquals,
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyPerformersImageCount(t, imageCountCriterion)
|
||||||
|
|
||||||
|
imageCountCriterion.Modifier = models.CriterionModifierNotEquals
|
||||||
|
verifyPerformersImageCount(t, imageCountCriterion)
|
||||||
|
|
||||||
|
imageCountCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||||
|
verifyPerformersImageCount(t, imageCountCriterion)
|
||||||
|
|
||||||
|
imageCountCriterion.Modifier = models.CriterionModifierLessThan
|
||||||
|
verifyPerformersImageCount(t, imageCountCriterion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyPerformersImageCount(t *testing.T, imageCountCriterion models.IntCriterionInput) {
|
||||||
|
withTxn(func(r models.Repository) error {
|
||||||
|
sqb := r.Performer()
|
||||||
|
performerFilter := models.PerformerFilterType{
|
||||||
|
ImageCount: &imageCountCriterion,
|
||||||
|
}
|
||||||
|
|
||||||
|
performers := queryPerformers(t, sqb, &performerFilter, nil)
|
||||||
|
assert.Greater(t, len(performers), 0)
|
||||||
|
|
||||||
|
for _, performer := range performers {
|
||||||
|
pp := 0
|
||||||
|
|
||||||
|
_, count, err := r.Image().Query(&models.ImageFilterType{
|
||||||
|
Performers: &models.MultiCriterionInput{
|
||||||
|
Value: []string{strconv.Itoa(performer.ID)},
|
||||||
|
Modifier: models.CriterionModifierIncludes,
|
||||||
|
},
|
||||||
|
}, &models.FindFilterType{
|
||||||
|
PerPage: &pp,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
verifyInt(t, count, imageCountCriterion)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPerformerQueryGalleryCount(t *testing.T) {
|
||||||
|
const galleryCount = 1
|
||||||
|
galleryCountCriterion := models.IntCriterionInput{
|
||||||
|
Value: galleryCount,
|
||||||
|
Modifier: models.CriterionModifierEquals,
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyPerformersGalleryCount(t, galleryCountCriterion)
|
||||||
|
|
||||||
|
galleryCountCriterion.Modifier = models.CriterionModifierNotEquals
|
||||||
|
verifyPerformersGalleryCount(t, galleryCountCriterion)
|
||||||
|
|
||||||
|
galleryCountCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||||
|
verifyPerformersGalleryCount(t, galleryCountCriterion)
|
||||||
|
|
||||||
|
galleryCountCriterion.Modifier = models.CriterionModifierLessThan
|
||||||
|
verifyPerformersGalleryCount(t, galleryCountCriterion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyPerformersGalleryCount(t *testing.T, galleryCountCriterion models.IntCriterionInput) {
|
||||||
|
withTxn(func(r models.Repository) error {
|
||||||
|
sqb := r.Performer()
|
||||||
|
performerFilter := models.PerformerFilterType{
|
||||||
|
GalleryCount: &galleryCountCriterion,
|
||||||
|
}
|
||||||
|
|
||||||
|
performers := queryPerformers(t, sqb, &performerFilter, nil)
|
||||||
|
assert.Greater(t, len(performers), 0)
|
||||||
|
|
||||||
|
for _, performer := range performers {
|
||||||
|
pp := 0
|
||||||
|
|
||||||
|
_, count, err := r.Gallery().Query(&models.GalleryFilterType{
|
||||||
|
Performers: &models.MultiCriterionInput{
|
||||||
|
Value: []string{strconv.Itoa(performer.ID)},
|
||||||
|
Modifier: models.CriterionModifierIncludes,
|
||||||
|
},
|
||||||
|
}, &models.FindFilterType{
|
||||||
|
PerPage: &pp,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
verifyInt(t, count, galleryCountCriterion)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestPerformerStashIDs(t *testing.T) {
|
func TestPerformerStashIDs(t *testing.T) {
|
||||||
if err := withTxn(func(r models.Repository) error {
|
if err := withTxn(func(r models.Repository) error {
|
||||||
qb := r.Performer()
|
qb := r.Performer()
|
||||||
|
|||||||
@@ -151,3 +151,15 @@ func (qb *queryBuilder) handleStringCriterionInput(c *models.StringCriterionInpu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (qb *queryBuilder) handleCountCriterion(countFilter *models.IntCriterionInput, primaryTable, joinTable, primaryFK string) {
|
||||||
|
if countFilter != nil {
|
||||||
|
clause, count := getCountCriterionClause(primaryTable, joinTable, primaryFK, *countFilter)
|
||||||
|
|
||||||
|
if count == 1 {
|
||||||
|
qb.addArg(countFilter.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
qb.addWhere(clause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ func (qb *sceneQueryBuilder) Wall(q *string) ([]*models.Scene, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (qb *sceneQueryBuilder) All() ([]*models.Scene, error) {
|
func (qb *sceneQueryBuilder) All() ([]*models.Scene, error) {
|
||||||
return qb.queryScenes(selectAll(sceneTable)+qb.getSceneSort(nil), nil)
|
return qb.queryScenes(selectAll(sceneTable)+qb.getDefaultSceneSort(), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func illegalFilterCombination(type1, type2 string) error {
|
func illegalFilterCombination(type1, type2 string) error {
|
||||||
@@ -348,7 +348,9 @@ func (qb *sceneQueryBuilder) makeFilter(sceneFilter *models.SceneFilterType) *fi
|
|||||||
query.handleCriterionFunc(stringCriterionHandler(sceneFilter.URL, "scenes.url"))
|
query.handleCriterionFunc(stringCriterionHandler(sceneFilter.URL, "scenes.url"))
|
||||||
|
|
||||||
query.handleCriterionFunc(sceneTagsCriterionHandler(qb, sceneFilter.Tags))
|
query.handleCriterionFunc(sceneTagsCriterionHandler(qb, sceneFilter.Tags))
|
||||||
|
query.handleCriterionFunc(sceneTagCountCriterionHandler(qb, sceneFilter.TagCount))
|
||||||
query.handleCriterionFunc(scenePerformersCriterionHandler(qb, sceneFilter.Performers))
|
query.handleCriterionFunc(scenePerformersCriterionHandler(qb, sceneFilter.Performers))
|
||||||
|
query.handleCriterionFunc(scenePerformerCountCriterionHandler(qb, sceneFilter.PerformerCount))
|
||||||
query.handleCriterionFunc(sceneStudioCriterionHandler(qb, sceneFilter.Studios))
|
query.handleCriterionFunc(sceneStudioCriterionHandler(qb, sceneFilter.Studios))
|
||||||
query.handleCriterionFunc(sceneMoviesCriterionHandler(qb, sceneFilter.Movies))
|
query.handleCriterionFunc(sceneMoviesCriterionHandler(qb, sceneFilter.Movies))
|
||||||
query.handleCriterionFunc(sceneStashIDsHandler(qb, sceneFilter.StashID))
|
query.handleCriterionFunc(sceneStashIDsHandler(qb, sceneFilter.StashID))
|
||||||
@@ -384,7 +386,8 @@ func (qb *sceneQueryBuilder) Query(sceneFilter *models.SceneFilterType, findFilt
|
|||||||
|
|
||||||
query.addFilter(filter)
|
query.addFilter(filter)
|
||||||
|
|
||||||
query.sortAndPagination = qb.getSceneSort(findFilter) + getPagination(findFilter)
|
qb.setSceneSort(&query, findFilter)
|
||||||
|
query.sortAndPagination += getPagination(findFilter)
|
||||||
|
|
||||||
idsResult, countResult, err := query.executeFind()
|
idsResult, countResult, err := query.executeFind()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -520,6 +523,7 @@ func (qb *sceneQueryBuilder) getMultiCriterionHandlerBuilder(foreignTable, joinT
|
|||||||
addJoinsFunc: addJoinsFunc,
|
addJoinsFunc: addJoinsFunc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sceneTagsCriterionHandler(qb *sceneQueryBuilder, tags *models.MultiCriterionInput) criterionHandlerFunc {
|
func sceneTagsCriterionHandler(qb *sceneQueryBuilder, tags *models.MultiCriterionInput) criterionHandlerFunc {
|
||||||
addJoinsFunc := func(f *filterBuilder) {
|
addJoinsFunc := func(f *filterBuilder) {
|
||||||
qb.tagsRepository().join(f, "tags_join", "scenes.id")
|
qb.tagsRepository().join(f, "tags_join", "scenes.id")
|
||||||
@@ -530,6 +534,16 @@ func sceneTagsCriterionHandler(qb *sceneQueryBuilder, tags *models.MultiCriterio
|
|||||||
return h.handler(tags)
|
return h.handler(tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sceneTagCountCriterionHandler(qb *sceneQueryBuilder, tagCount *models.IntCriterionInput) criterionHandlerFunc {
|
||||||
|
h := countCriterionHandlerBuilder{
|
||||||
|
primaryTable: sceneTable,
|
||||||
|
joinTable: scenesTagsTable,
|
||||||
|
primaryFK: sceneIDColumn,
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.handler(tagCount)
|
||||||
|
}
|
||||||
|
|
||||||
func scenePerformersCriterionHandler(qb *sceneQueryBuilder, performers *models.MultiCriterionInput) criterionHandlerFunc {
|
func scenePerformersCriterionHandler(qb *sceneQueryBuilder, performers *models.MultiCriterionInput) criterionHandlerFunc {
|
||||||
addJoinsFunc := func(f *filterBuilder) {
|
addJoinsFunc := func(f *filterBuilder) {
|
||||||
qb.performersRepository().join(f, "performers_join", "scenes.id")
|
qb.performersRepository().join(f, "performers_join", "scenes.id")
|
||||||
@@ -540,6 +554,16 @@ func scenePerformersCriterionHandler(qb *sceneQueryBuilder, performers *models.M
|
|||||||
return h.handler(performers)
|
return h.handler(performers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func scenePerformerCountCriterionHandler(qb *sceneQueryBuilder, performerCount *models.IntCriterionInput) criterionHandlerFunc {
|
||||||
|
h := countCriterionHandlerBuilder{
|
||||||
|
primaryTable: sceneTable,
|
||||||
|
joinTable: performersScenesTable,
|
||||||
|
primaryFK: sceneIDColumn,
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.handler(performerCount)
|
||||||
|
}
|
||||||
|
|
||||||
func sceneStudioCriterionHandler(qb *sceneQueryBuilder, studios *models.MultiCriterionInput) criterionHandlerFunc {
|
func sceneStudioCriterionHandler(qb *sceneQueryBuilder, studios *models.MultiCriterionInput) criterionHandlerFunc {
|
||||||
addJoinsFunc := func(f *filterBuilder) {
|
addJoinsFunc := func(f *filterBuilder) {
|
||||||
f.addJoin("studios", "studio", "studio.id = scenes.studio_id")
|
f.addJoin("studios", "studio", "studio.id = scenes.studio_id")
|
||||||
@@ -621,13 +645,25 @@ func handleScenePerformerTagsCriterion(query *queryBuilder, performerTagsFilter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (qb *sceneQueryBuilder) getSceneSort(findFilter *models.FindFilterType) string {
|
func (qb *sceneQueryBuilder) getDefaultSceneSort() string {
|
||||||
|
return " ORDER BY scenes.path, scenes.date ASC "
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qb *sceneQueryBuilder) setSceneSort(query *queryBuilder, findFilter *models.FindFilterType) {
|
||||||
if findFilter == nil {
|
if findFilter == nil {
|
||||||
return " ORDER BY scenes.path, scenes.date ASC "
|
query.sortAndPagination += qb.getDefaultSceneSort()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
sort := findFilter.GetSort("title")
|
sort := findFilter.GetSort("title")
|
||||||
direction := findFilter.GetDirection()
|
direction := findFilter.GetDirection()
|
||||||
return getSort(sort, direction, "scenes")
|
switch sort {
|
||||||
|
case "tag_count":
|
||||||
|
query.sortAndPagination += getCountSort(sceneTable, scenesTagsTable, sceneIDColumn, direction)
|
||||||
|
case "performer_count":
|
||||||
|
query.sortAndPagination += getCountSort(sceneTable, performersScenesTable, sceneIDColumn, direction)
|
||||||
|
default:
|
||||||
|
query.sortAndPagination += getSort(sort, direction, "scenes")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (qb *sceneQueryBuilder) queryScene(query string, args []interface{}) (*models.Scene, error) {
|
func (qb *sceneQueryBuilder) queryScene(query string, args []interface{}) (*models.Scene, error) {
|
||||||
|
|||||||
@@ -1214,6 +1214,88 @@ func TestSceneQueryPagination(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSceneQueryTagCount(t *testing.T) {
|
||||||
|
const tagCount = 1
|
||||||
|
tagCountCriterion := models.IntCriterionInput{
|
||||||
|
Value: tagCount,
|
||||||
|
Modifier: models.CriterionModifierEquals,
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyScenesTagCount(t, tagCountCriterion)
|
||||||
|
|
||||||
|
tagCountCriterion.Modifier = models.CriterionModifierNotEquals
|
||||||
|
verifyScenesTagCount(t, tagCountCriterion)
|
||||||
|
|
||||||
|
tagCountCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||||
|
verifyScenesTagCount(t, tagCountCriterion)
|
||||||
|
|
||||||
|
tagCountCriterion.Modifier = models.CriterionModifierLessThan
|
||||||
|
verifyScenesTagCount(t, tagCountCriterion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyScenesTagCount(t *testing.T, tagCountCriterion models.IntCriterionInput) {
|
||||||
|
withTxn(func(r models.Repository) error {
|
||||||
|
sqb := r.Scene()
|
||||||
|
sceneFilter := models.SceneFilterType{
|
||||||
|
TagCount: &tagCountCriterion,
|
||||||
|
}
|
||||||
|
|
||||||
|
scenes := queryScene(t, sqb, &sceneFilter, nil)
|
||||||
|
assert.Greater(t, len(scenes), 0)
|
||||||
|
|
||||||
|
for _, scene := range scenes {
|
||||||
|
ids, err := sqb.GetTagIDs(scene.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
verifyInt(t, len(ids), tagCountCriterion)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSceneQueryPerformerCount(t *testing.T) {
|
||||||
|
const performerCount = 1
|
||||||
|
performerCountCriterion := models.IntCriterionInput{
|
||||||
|
Value: performerCount,
|
||||||
|
Modifier: models.CriterionModifierEquals,
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyScenesPerformerCount(t, performerCountCriterion)
|
||||||
|
|
||||||
|
performerCountCriterion.Modifier = models.CriterionModifierNotEquals
|
||||||
|
verifyScenesPerformerCount(t, performerCountCriterion)
|
||||||
|
|
||||||
|
performerCountCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||||
|
verifyScenesPerformerCount(t, performerCountCriterion)
|
||||||
|
|
||||||
|
performerCountCriterion.Modifier = models.CriterionModifierLessThan
|
||||||
|
verifyScenesPerformerCount(t, performerCountCriterion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyScenesPerformerCount(t *testing.T, performerCountCriterion models.IntCriterionInput) {
|
||||||
|
withTxn(func(r models.Repository) error {
|
||||||
|
sqb := r.Scene()
|
||||||
|
sceneFilter := models.SceneFilterType{
|
||||||
|
PerformerCount: &performerCountCriterion,
|
||||||
|
}
|
||||||
|
|
||||||
|
scenes := queryScene(t, sqb, &sceneFilter, nil)
|
||||||
|
assert.Greater(t, len(scenes), 0)
|
||||||
|
|
||||||
|
for _, scene := range scenes {
|
||||||
|
ids, err := sqb.GetPerformerIDs(scene.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
verifyInt(t, len(ids), performerCountCriterion)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestSceneCountByTagID(t *testing.T) {
|
func TestSceneCountByTagID(t *testing.T) {
|
||||||
withTxn(func(r models.Repository) error {
|
withTxn(func(r models.Repository) error {
|
||||||
sqb := r.Scene()
|
sqb := r.Scene()
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ const (
|
|||||||
sceneIdxWithMovie = iota
|
sceneIdxWithMovie = iota
|
||||||
sceneIdxWithGallery
|
sceneIdxWithGallery
|
||||||
sceneIdxWithPerformer
|
sceneIdxWithPerformer
|
||||||
|
sceneIdx1WithPerformer
|
||||||
|
sceneIdx2WithPerformer
|
||||||
sceneIdxWithTwoPerformers
|
sceneIdxWithTwoPerformers
|
||||||
sceneIdxWithTag
|
sceneIdxWithTag
|
||||||
sceneIdxWithTwoTags
|
sceneIdxWithTwoTags
|
||||||
@@ -40,6 +42,8 @@ const (
|
|||||||
const (
|
const (
|
||||||
imageIdxWithGallery = iota
|
imageIdxWithGallery = iota
|
||||||
imageIdxWithPerformer
|
imageIdxWithPerformer
|
||||||
|
imageIdx1WithPerformer
|
||||||
|
imageIdx2WithPerformer
|
||||||
imageIdxWithTwoPerformers
|
imageIdxWithTwoPerformers
|
||||||
imageIdxWithTag
|
imageIdxWithTag
|
||||||
imageIdxWithTwoTags
|
imageIdxWithTwoTags
|
||||||
@@ -55,12 +59,15 @@ const (
|
|||||||
performerIdxWithScene = iota
|
performerIdxWithScene = iota
|
||||||
performerIdx1WithScene
|
performerIdx1WithScene
|
||||||
performerIdx2WithScene
|
performerIdx2WithScene
|
||||||
|
performerIdxWithTwoScenes
|
||||||
performerIdxWithImage
|
performerIdxWithImage
|
||||||
|
performerIdxWithTwoImages
|
||||||
performerIdx1WithImage
|
performerIdx1WithImage
|
||||||
performerIdx2WithImage
|
performerIdx2WithImage
|
||||||
performerIdxWithTag
|
performerIdxWithTag
|
||||||
performerIdxWithTwoTags
|
performerIdxWithTwoTags
|
||||||
performerIdxWithGallery
|
performerIdxWithGallery
|
||||||
|
performerIdxWithTwoGalleries
|
||||||
performerIdx1WithGallery
|
performerIdx1WithGallery
|
||||||
performerIdx2WithGallery
|
performerIdx2WithGallery
|
||||||
// new indexes above
|
// new indexes above
|
||||||
@@ -87,6 +94,8 @@ const (
|
|||||||
galleryIdxWithScene = iota
|
galleryIdxWithScene = iota
|
||||||
galleryIdxWithImage
|
galleryIdxWithImage
|
||||||
galleryIdxWithPerformer
|
galleryIdxWithPerformer
|
||||||
|
galleryIdx1WithPerformer
|
||||||
|
galleryIdx2WithPerformer
|
||||||
galleryIdxWithTwoPerformers
|
galleryIdxWithTwoPerformers
|
||||||
galleryIdxWithTag
|
galleryIdxWithTag
|
||||||
galleryIdxWithTwoTags
|
galleryIdxWithTwoTags
|
||||||
@@ -185,6 +194,8 @@ var (
|
|||||||
{sceneIdxWithTwoPerformers, performerIdx2WithScene},
|
{sceneIdxWithTwoPerformers, performerIdx2WithScene},
|
||||||
{sceneIdxWithPerformerTag, performerIdxWithTag},
|
{sceneIdxWithPerformerTag, performerIdxWithTag},
|
||||||
{sceneIdxWithPerformerTwoTags, performerIdxWithTwoTags},
|
{sceneIdxWithPerformerTwoTags, performerIdxWithTwoTags},
|
||||||
|
{sceneIdx1WithPerformer, performerIdxWithTwoScenes},
|
||||||
|
{sceneIdx2WithPerformer, performerIdxWithTwoScenes},
|
||||||
}
|
}
|
||||||
|
|
||||||
sceneGalleryLinks = [][2]int{
|
sceneGalleryLinks = [][2]int{
|
||||||
@@ -218,6 +229,8 @@ var (
|
|||||||
{imageIdxWithTwoPerformers, performerIdx2WithImage},
|
{imageIdxWithTwoPerformers, performerIdx2WithImage},
|
||||||
{imageIdxWithPerformerTag, performerIdxWithTag},
|
{imageIdxWithPerformerTag, performerIdxWithTag},
|
||||||
{imageIdxWithPerformerTwoTags, performerIdxWithTwoTags},
|
{imageIdxWithPerformerTwoTags, performerIdxWithTwoTags},
|
||||||
|
{imageIdx1WithPerformer, performerIdxWithTwoImages},
|
||||||
|
{imageIdx2WithPerformer, performerIdxWithTwoImages},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -228,6 +241,8 @@ var (
|
|||||||
{galleryIdxWithTwoPerformers, performerIdx2WithGallery},
|
{galleryIdxWithTwoPerformers, performerIdx2WithGallery},
|
||||||
{galleryIdxWithPerformerTag, performerIdxWithTag},
|
{galleryIdxWithPerformerTag, performerIdxWithTag},
|
||||||
{galleryIdxWithPerformerTwoTags, performerIdxWithTwoTags},
|
{galleryIdxWithPerformerTwoTags, performerIdxWithTwoTags},
|
||||||
|
{galleryIdx1WithPerformer, performerIdxWithTwoGalleries},
|
||||||
|
{galleryIdx2WithPerformer, performerIdxWithTwoGalleries},
|
||||||
}
|
}
|
||||||
|
|
||||||
galleryTagLinks = [][2]int{
|
galleryTagLinks = [][2]int{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package sqlite
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -44,10 +45,15 @@ func getPaginationSQL(page int, perPage int) string {
|
|||||||
return " LIMIT " + strconv.Itoa(perPage) + " OFFSET " + strconv.Itoa(page) + " "
|
return " LIMIT " + strconv.Itoa(perPage) + " OFFSET " + strconv.Itoa(page) + " "
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSort(sort string, direction string, tableName string) string {
|
func getSortDirection(direction string) string {
|
||||||
if direction != "ASC" && direction != "DESC" {
|
if direction != "ASC" && direction != "DESC" {
|
||||||
direction = "ASC"
|
return "ASC"
|
||||||
|
} else {
|
||||||
|
return direction
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
func getSort(sort string, direction string, tableName string) string {
|
||||||
|
direction = getSortDirection(direction)
|
||||||
|
|
||||||
const randomSeedPrefix = "random_"
|
const randomSeedPrefix = "random_"
|
||||||
|
|
||||||
@@ -96,6 +102,10 @@ func getRandomSort(tableName string, direction string, seed float64) string {
|
|||||||
return " ORDER BY " + "(substr(" + colName + " * " + randomSortString + ", length(" + colName + ") + 2))" + " " + direction
|
return " ORDER BY " + "(substr(" + colName + " * " + randomSortString + ", length(" + colName + ") + 2))" + " " + direction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getCountSort(primaryTable, joinTable, primaryFK, direction string) string {
|
||||||
|
return fmt.Sprintf(" ORDER BY (SELECT COUNT(*) FROM %s WHERE %s = %s.id) %s", joinTable, primaryFK, primaryTable, getSortDirection(direction))
|
||||||
|
}
|
||||||
|
|
||||||
func getSearchBinding(columns []string, q string, not bool) (string, []interface{}) {
|
func getSearchBinding(columns []string, q string, not bool) (string, []interface{}) {
|
||||||
var likeClauses []string
|
var likeClauses []string
|
||||||
var args []interface{}
|
var args []interface{}
|
||||||
@@ -213,6 +223,11 @@ func getMultiCriterionClause(primaryTable, foreignTable, joinTable, primaryFK, f
|
|||||||
return whereClause, havingClause
|
return whereClause, havingClause
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getCountCriterionClause(primaryTable, joinTable, primaryFK string, criterion models.IntCriterionInput) (string, int) {
|
||||||
|
lhs := fmt.Sprintf("(SELECT COUNT(*) FROM %s s WHERE s.%s = %s.id)", joinTable, primaryFK, primaryTable)
|
||||||
|
return getIntCriterionWhereClause(lhs, criterion)
|
||||||
|
}
|
||||||
|
|
||||||
func ensureTx(tx *sqlx.Tx) {
|
func ensureTx(tx *sqlx.Tx) {
|
||||||
if tx == nil {
|
if tx == nil {
|
||||||
panic("must use a transaction")
|
panic("must use a transaction")
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
* Added scene queue.
|
* Added scene queue.
|
||||||
|
|
||||||
### 🎨 Improvements
|
### 🎨 Improvements
|
||||||
|
* Add various `count` filter criteria and sort options.
|
||||||
* Scroll to top when changing page number.
|
* Scroll to top when changing page number.
|
||||||
* Add URL filter criteria for scenes, galleries, movies, performers and studios.
|
* Add URL filter criteria for scenes, galleries, movies, performers and studios.
|
||||||
* Add HTTP endpoint for health checking at `/healthz`.
|
* Add HTTP endpoint for health checking at `/healthz`.
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export type CriterionType =
|
|||||||
| "tags"
|
| "tags"
|
||||||
| "sceneTags"
|
| "sceneTags"
|
||||||
| "performerTags"
|
| "performerTags"
|
||||||
|
| "tag_count"
|
||||||
| "performers"
|
| "performers"
|
||||||
| "studios"
|
| "studios"
|
||||||
| "movies"
|
| "movies"
|
||||||
@@ -90,6 +91,8 @@ export abstract class Criterion {
|
|||||||
return "Scene Tags";
|
return "Scene Tags";
|
||||||
case "performerTags":
|
case "performerTags":
|
||||||
return "Performer Tags";
|
return "Performer Tags";
|
||||||
|
case "tag_count":
|
||||||
|
return "Tag Count";
|
||||||
case "performers":
|
case "performers":
|
||||||
return "Performers";
|
return "Performers";
|
||||||
case "studios":
|
case "studios":
|
||||||
@@ -358,6 +361,15 @@ export class NumberCriterion extends Criterion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class MandatoryNumberCriterion extends NumberCriterion {
|
||||||
|
public modifierOptions = [
|
||||||
|
Criterion.getModifierOption(CriterionModifier.Equals),
|
||||||
|
Criterion.getModifierOption(CriterionModifier.NotEquals),
|
||||||
|
Criterion.getModifierOption(CriterionModifier.GreaterThan),
|
||||||
|
Criterion.getModifierOption(CriterionModifier.LessThan),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
export class DurationCriterion extends Criterion {
|
export class DurationCriterion extends Criterion {
|
||||||
public type: CriterionType;
|
public type: CriterionType;
|
||||||
public parameterName: string;
|
public parameterName: string;
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
/* eslint-disable consistent-return, default-case */
|
/* eslint-disable consistent-return, default-case */
|
||||||
import { CriterionModifier } from "src/core/generated-graphql";
|
|
||||||
import {
|
import {
|
||||||
Criterion,
|
|
||||||
CriterionType,
|
CriterionType,
|
||||||
StringCriterion,
|
StringCriterion,
|
||||||
NumberCriterion,
|
NumberCriterion,
|
||||||
DurationCriterion,
|
DurationCriterion,
|
||||||
MandatoryStringCriterion,
|
MandatoryStringCriterion,
|
||||||
|
MandatoryNumberCriterion,
|
||||||
} from "./criterion";
|
} from "./criterion";
|
||||||
import { OrganizedCriterion } from "./organized";
|
import { OrganizedCriterion } from "./organized";
|
||||||
import { FavoriteCriterion } from "./favorite";
|
import { FavoriteCriterion } from "./favorite";
|
||||||
@@ -46,7 +45,8 @@ export function makeCriteria(type: CriterionType = "none") {
|
|||||||
case "image_count":
|
case "image_count":
|
||||||
case "gallery_count":
|
case "gallery_count":
|
||||||
case "performer_count":
|
case "performer_count":
|
||||||
return new NumberCriterion(type, type);
|
case "tag_count":
|
||||||
|
return new MandatoryNumberCriterion(type, type);
|
||||||
case "resolution":
|
case "resolution":
|
||||||
return new ResolutionCriterion();
|
return new ResolutionCriterion();
|
||||||
case "average_resolution":
|
case "average_resolution":
|
||||||
@@ -89,17 +89,8 @@ export function makeCriteria(type: CriterionType = "none") {
|
|||||||
return new GalleriesCriterion();
|
return new GalleriesCriterion();
|
||||||
case "birth_year":
|
case "birth_year":
|
||||||
return new NumberCriterion(type, type);
|
return new NumberCriterion(type, type);
|
||||||
case "age": {
|
case "age":
|
||||||
const ret = new NumberCriterion(type, type);
|
return new MandatoryNumberCriterion(type, type);
|
||||||
// null/not null doesn't make sense for these criteria
|
|
||||||
ret.modifierOptions = [
|
|
||||||
Criterion.getModifierOption(CriterionModifier.Equals),
|
|
||||||
Criterion.getModifierOption(CriterionModifier.NotEquals),
|
|
||||||
Criterion.getModifierOption(CriterionModifier.GreaterThan),
|
|
||||||
Criterion.getModifierOption(CriterionModifier.LessThan),
|
|
||||||
];
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
case "gender":
|
case "gender":
|
||||||
return new GenderCriterion();
|
return new GenderCriterion();
|
||||||
case "ethnicity":
|
case "ethnicity":
|
||||||
|
|||||||
@@ -128,6 +128,8 @@ export class ListFilterModel {
|
|||||||
"duration",
|
"duration",
|
||||||
"framerate",
|
"framerate",
|
||||||
"bitrate",
|
"bitrate",
|
||||||
|
"tag_count",
|
||||||
|
"performer_count",
|
||||||
"random",
|
"random",
|
||||||
];
|
];
|
||||||
this.displayModeOptions = [
|
this.displayModeOptions = [
|
||||||
@@ -147,8 +149,10 @@ export class ListFilterModel {
|
|||||||
new HasMarkersCriterionOption(),
|
new HasMarkersCriterionOption(),
|
||||||
new SceneIsMissingCriterionOption(),
|
new SceneIsMissingCriterionOption(),
|
||||||
new TagsCriterionOption(),
|
new TagsCriterionOption(),
|
||||||
|
ListFilterModel.createCriterionOption("tag_count"),
|
||||||
new PerformerTagsCriterionOption(),
|
new PerformerTagsCriterionOption(),
|
||||||
new PerformersCriterionOption(),
|
new PerformersCriterionOption(),
|
||||||
|
ListFilterModel.createCriterionOption("performer_count"),
|
||||||
new StudiosCriterionOption(),
|
new StudiosCriterionOption(),
|
||||||
new MoviesCriterionOption(),
|
new MoviesCriterionOption(),
|
||||||
ListFilterModel.createCriterionOption("url"),
|
ListFilterModel.createCriterionOption("url"),
|
||||||
@@ -163,6 +167,8 @@ export class ListFilterModel {
|
|||||||
"o_counter",
|
"o_counter",
|
||||||
"filesize",
|
"filesize",
|
||||||
"file_mod_time",
|
"file_mod_time",
|
||||||
|
"tag_count",
|
||||||
|
"performer_count",
|
||||||
"random",
|
"random",
|
||||||
];
|
];
|
||||||
this.displayModeOptions = [DisplayMode.Grid, DisplayMode.Wall];
|
this.displayModeOptions = [DisplayMode.Grid, DisplayMode.Wall];
|
||||||
@@ -175,8 +181,10 @@ export class ListFilterModel {
|
|||||||
new ResolutionCriterionOption(),
|
new ResolutionCriterionOption(),
|
||||||
new ImageIsMissingCriterionOption(),
|
new ImageIsMissingCriterionOption(),
|
||||||
new TagsCriterionOption(),
|
new TagsCriterionOption(),
|
||||||
|
ListFilterModel.createCriterionOption("tag_count"),
|
||||||
new PerformerTagsCriterionOption(),
|
new PerformerTagsCriterionOption(),
|
||||||
new PerformersCriterionOption(),
|
new PerformersCriterionOption(),
|
||||||
|
ListFilterModel.createCriterionOption("performer_count"),
|
||||||
new StudiosCriterionOption(),
|
new StudiosCriterionOption(),
|
||||||
];
|
];
|
||||||
break;
|
break;
|
||||||
@@ -187,6 +195,7 @@ export class ListFilterModel {
|
|||||||
"height",
|
"height",
|
||||||
"birthdate",
|
"birthdate",
|
||||||
"scenes_count",
|
"scenes_count",
|
||||||
|
"tag_count",
|
||||||
"random",
|
"random",
|
||||||
];
|
];
|
||||||
this.displayModeOptions = [DisplayMode.Grid, DisplayMode.List];
|
this.displayModeOptions = [DisplayMode.Grid, DisplayMode.List];
|
||||||
@@ -212,6 +221,10 @@ export class ListFilterModel {
|
|||||||
new PerformerIsMissingCriterionOption(),
|
new PerformerIsMissingCriterionOption(),
|
||||||
new TagsCriterionOption(),
|
new TagsCriterionOption(),
|
||||||
ListFilterModel.createCriterionOption("url"),
|
ListFilterModel.createCriterionOption("url"),
|
||||||
|
ListFilterModel.createCriterionOption("tag_count"),
|
||||||
|
ListFilterModel.createCriterionOption("scene_count"),
|
||||||
|
ListFilterModel.createCriterionOption("image_count"),
|
||||||
|
ListFilterModel.createCriterionOption("gallery_count"),
|
||||||
...numberCriteria
|
...numberCriteria
|
||||||
.concat(stringCriteria)
|
.concat(stringCriteria)
|
||||||
.map((c) => ListFilterModel.createCriterionOption(c)),
|
.map((c) => ListFilterModel.createCriterionOption(c)),
|
||||||
@@ -247,6 +260,8 @@ export class ListFilterModel {
|
|||||||
"path",
|
"path",
|
||||||
"file_mod_time",
|
"file_mod_time",
|
||||||
"images_count",
|
"images_count",
|
||||||
|
"tag_count",
|
||||||
|
"performer_count",
|
||||||
"random",
|
"random",
|
||||||
];
|
];
|
||||||
this.displayModeOptions = [DisplayMode.Grid, DisplayMode.List];
|
this.displayModeOptions = [DisplayMode.Grid, DisplayMode.List];
|
||||||
@@ -258,8 +273,10 @@ export class ListFilterModel {
|
|||||||
new AverageResolutionCriterionOption(),
|
new AverageResolutionCriterionOption(),
|
||||||
new GalleryIsMissingCriterionOption(),
|
new GalleryIsMissingCriterionOption(),
|
||||||
new TagsCriterionOption(),
|
new TagsCriterionOption(),
|
||||||
|
ListFilterModel.createCriterionOption("tag_count"),
|
||||||
new PerformerTagsCriterionOption(),
|
new PerformerTagsCriterionOption(),
|
||||||
new PerformersCriterionOption(),
|
new PerformersCriterionOption(),
|
||||||
|
ListFilterModel.createCriterionOption("performer_count"),
|
||||||
new StudiosCriterionOption(),
|
new StudiosCriterionOption(),
|
||||||
ListFilterModel.createCriterionOption("url"),
|
ListFilterModel.createCriterionOption("url"),
|
||||||
];
|
];
|
||||||
@@ -563,6 +580,14 @@ export class ListFilterModel {
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "tag_count": {
|
||||||
|
const tagCountCrit = criterion as NumberCriterion;
|
||||||
|
result.tag_count = {
|
||||||
|
value: tagCountCrit.value,
|
||||||
|
modifier: tagCountCrit.modifier,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "performers": {
|
case "performers": {
|
||||||
const perfCrit = criterion as PerformersCriterion;
|
const perfCrit = criterion as PerformersCriterion;
|
||||||
result.performers = {
|
result.performers = {
|
||||||
@@ -571,6 +596,14 @@ export class ListFilterModel {
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "performer_count": {
|
||||||
|
const performerCountCrit = criterion as NumberCriterion;
|
||||||
|
result.performer_count = {
|
||||||
|
value: performerCountCrit.value,
|
||||||
|
modifier: performerCountCrit.modifier,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "studios": {
|
case "studios": {
|
||||||
const studCrit = criterion as StudiosCriterion;
|
const studCrit = criterion as StudiosCriterion;
|
||||||
result.studios = {
|
result.studios = {
|
||||||
@@ -711,9 +744,42 @@ export class ListFilterModel {
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "tag_count": {
|
||||||
|
const tagCountCrit = criterion as NumberCriterion;
|
||||||
|
result.tag_count = {
|
||||||
|
value: tagCountCrit.value,
|
||||||
|
modifier: tagCountCrit.modifier,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "scene_count": {
|
||||||
|
const countCrit = criterion as NumberCriterion;
|
||||||
|
result.scene_count = {
|
||||||
|
value: countCrit.value,
|
||||||
|
modifier: countCrit.modifier,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "image_count": {
|
||||||
|
const countCrit = criterion as NumberCriterion;
|
||||||
|
result.image_count = {
|
||||||
|
value: countCrit.value,
|
||||||
|
modifier: countCrit.modifier,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "gallery_count": {
|
||||||
|
const countCrit = criterion as NumberCriterion;
|
||||||
|
result.gallery_count = {
|
||||||
|
value: countCrit.value,
|
||||||
|
modifier: countCrit.modifier,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
// no default
|
// no default
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -839,6 +905,14 @@ export class ListFilterModel {
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "tag_count": {
|
||||||
|
const tagCountCrit = criterion as NumberCriterion;
|
||||||
|
result.tag_count = {
|
||||||
|
value: tagCountCrit.value,
|
||||||
|
modifier: tagCountCrit.modifier,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "performerTags": {
|
case "performerTags": {
|
||||||
const performerTagsCrit = criterion as TagsCriterion;
|
const performerTagsCrit = criterion as TagsCriterion;
|
||||||
result.performer_tags = {
|
result.performer_tags = {
|
||||||
@@ -855,6 +929,14 @@ export class ListFilterModel {
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "performer_count": {
|
||||||
|
const countCrit = criterion as NumberCriterion;
|
||||||
|
result.performer_count = {
|
||||||
|
value: countCrit.value,
|
||||||
|
modifier: countCrit.modifier,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "studios": {
|
case "studios": {
|
||||||
const studCrit = criterion as StudiosCriterion;
|
const studCrit = criterion as StudiosCriterion;
|
||||||
result.studios = {
|
result.studios = {
|
||||||
@@ -1014,6 +1096,14 @@ export class ListFilterModel {
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "tag_count": {
|
||||||
|
const tagCountCrit = criterion as NumberCriterion;
|
||||||
|
result.tag_count = {
|
||||||
|
value: tagCountCrit.value,
|
||||||
|
modifier: tagCountCrit.modifier,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "performerTags": {
|
case "performerTags": {
|
||||||
const performerTagsCrit = criterion as TagsCriterion;
|
const performerTagsCrit = criterion as TagsCriterion;
|
||||||
result.performer_tags = {
|
result.performer_tags = {
|
||||||
@@ -1030,6 +1120,14 @@ export class ListFilterModel {
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "performer_count": {
|
||||||
|
const countCrit = criterion as NumberCriterion;
|
||||||
|
result.performer_count = {
|
||||||
|
value: countCrit.value,
|
||||||
|
modifier: countCrit.modifier,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "studios": {
|
case "studios": {
|
||||||
const studCrit = criterion as StudiosCriterion;
|
const studCrit = criterion as StudiosCriterion;
|
||||||
result.studios = {
|
result.studios = {
|
||||||
|
|||||||
Reference in New Issue
Block a user