mirror of
https://github.com/stashapp/stash.git
synced 2025-12-16 05:13:46 +01:00
Fix joined hierarchical filtering (#3775)
* Fix joined hierarchical filtering * Fix scene performer tag filter * Generalise performer tag handler * Add unit tests * Add equals handling * Make performer tags equals/not equals unsupported * Make tags not equals unsupported * Make not equals unsupported for performers criterion * Support equals/not equals for studio criterion * Fix marker scene tags equals filter * Fix scene performer tag filter * Make equals/not equals unsupported for hierarchical criterion * Use existing studio handler in movie * Hide unsupported tag modifier options * Use existing performer tags logic where possible * Restore old parent/child filter logic * Disable sub-tags in equals modifier for tags criterion
This commit is contained in:
parent
4acf843229
commit
256e0a11ea
19 changed files with 2153 additions and 938 deletions
|
|
@ -135,6 +135,17 @@ type HierarchicalMultiCriterionInput struct {
|
|||
Excludes []string `json:"excludes"`
|
||||
}
|
||||
|
||||
func (i HierarchicalMultiCriterionInput) CombineExcludes() HierarchicalMultiCriterionInput {
|
||||
ii := i
|
||||
if ii.Modifier == CriterionModifierExcludes {
|
||||
ii.Modifier = CriterionModifierIncludesAll
|
||||
ii.Excludes = append(ii.Excludes, ii.Value...)
|
||||
ii.Value = nil
|
||||
}
|
||||
|
||||
return ii
|
||||
}
|
||||
|
||||
type MultiCriterionInput struct {
|
||||
Value []string `json:"value"`
|
||||
Modifier CriterionModifier `json:"modifier"`
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
|
|
@ -694,6 +693,8 @@ func (m *joinedMultiCriterionHandlerBuilder) handler(c *models.MultiCriterionInp
|
|||
})
|
||||
havingClause = fmt.Sprintf("count(distinct %s.%s) IS %d", joinAlias, m.foreignFK, len(criterion.Value))
|
||||
args = append(args, len(criterion.Value))
|
||||
case models.CriterionModifierNotEquals:
|
||||
f.setError(fmt.Errorf("not equals modifier is not supported for multi criterion input"))
|
||||
case models.CriterionModifierIncludesAll:
|
||||
// includes all of the provided ids
|
||||
m.addJoinTable(f)
|
||||
|
|
@ -830,6 +831,33 @@ func (m *stringListCriterionHandlerBuilder) handler(criterion *models.StringCrit
|
|||
}
|
||||
}
|
||||
|
||||
func studioCriterionHandler(primaryTable string, studios *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if studios == nil {
|
||||
return
|
||||
}
|
||||
|
||||
studiosCopy := *studios
|
||||
switch studiosCopy.Modifier {
|
||||
case models.CriterionModifierEquals:
|
||||
studiosCopy.Modifier = models.CriterionModifierIncludesAll
|
||||
case models.CriterionModifierNotEquals:
|
||||
studiosCopy.Modifier = models.CriterionModifierExcludes
|
||||
}
|
||||
|
||||
hh := hierarchicalMultiCriterionHandlerBuilder{
|
||||
tx: dbWrapper{},
|
||||
|
||||
primaryTable: primaryTable,
|
||||
foreignTable: studioTable,
|
||||
foreignFK: studioIDColumn,
|
||||
parentFK: "parent_id",
|
||||
}
|
||||
|
||||
hh.handler(&studiosCopy)(ctx, f)
|
||||
}
|
||||
}
|
||||
|
||||
type hierarchicalMultiCriterionHandlerBuilder struct {
|
||||
tx dbWrapper
|
||||
|
||||
|
|
@ -838,12 +866,20 @@ type hierarchicalMultiCriterionHandlerBuilder struct {
|
|||
foreignFK string
|
||||
|
||||
parentFK string
|
||||
childFK string
|
||||
relationsTable string
|
||||
}
|
||||
|
||||
func getHierarchicalValues(ctx context.Context, tx dbWrapper, values []string, table, relationsTable, parentFK string, depth *int) string {
|
||||
func getHierarchicalValues(ctx context.Context, tx dbWrapper, values []string, table, relationsTable, parentFK string, childFK string, depth *int) (string, error) {
|
||||
var args []interface{}
|
||||
|
||||
if parentFK == "" {
|
||||
parentFK = "parent_id"
|
||||
}
|
||||
if childFK == "" {
|
||||
childFK = "child_id"
|
||||
}
|
||||
|
||||
depthVal := 0
|
||||
if depth != nil {
|
||||
depthVal = *depth
|
||||
|
|
@ -865,7 +901,7 @@ func getHierarchicalValues(ctx context.Context, tx dbWrapper, values []string, t
|
|||
}
|
||||
|
||||
if valid {
|
||||
return "VALUES" + strings.Join(valuesClauses, ",")
|
||||
return "VALUES" + strings.Join(valuesClauses, ","), nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -885,13 +921,14 @@ func getHierarchicalValues(ctx context.Context, tx dbWrapper, values []string, t
|
|||
"inBinding": getInBinding(inCount),
|
||||
"recursiveSelect": "",
|
||||
"parentFK": parentFK,
|
||||
"childFK": childFK,
|
||||
"depthCondition": depthCondition,
|
||||
"unionClause": "",
|
||||
}
|
||||
|
||||
if relationsTable != "" {
|
||||
withClauseMap["recursiveSelect"] = utils.StrFormat(`SELECT p.root_id, c.child_id, depth + 1 FROM {relationsTable} AS c
|
||||
INNER JOIN items as p ON c.parent_id = p.item_id
|
||||
withClauseMap["recursiveSelect"] = utils.StrFormat(`SELECT p.root_id, c.{childFK}, depth + 1 FROM {relationsTable} AS c
|
||||
INNER JOIN items as p ON c.{parentFK} = p.item_id
|
||||
`, withClauseMap)
|
||||
} else {
|
||||
withClauseMap["recursiveSelect"] = utils.StrFormat(`SELECT p.root_id, c.id, depth + 1 FROM {table} as c
|
||||
|
|
@ -916,12 +953,10 @@ WHERE id in {inBinding}
|
|||
var valuesClause string
|
||||
err := tx.Get(ctx, &valuesClause, query, args...)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
// return record which never matches so we don't have to handle error here
|
||||
return "VALUES(NULL, NULL)"
|
||||
return "", fmt.Errorf("failed to get hierarchical values: %w", err)
|
||||
}
|
||||
|
||||
return valuesClause
|
||||
return valuesClause, nil
|
||||
}
|
||||
|
||||
func addHierarchicalConditionClauses(f *filterBuilder, criterion models.HierarchicalMultiCriterionInput, table, idColumn string) {
|
||||
|
|
@ -942,6 +977,12 @@ func (m *hierarchicalMultiCriterionHandlerBuilder) handler(c *models.Hierarchica
|
|||
// make a copy so we don't modify the original
|
||||
criterion := *c
|
||||
|
||||
// don't support equals/not equals
|
||||
if criterion.Modifier == models.CriterionModifierEquals || criterion.Modifier == models.CriterionModifierNotEquals {
|
||||
f.setError(fmt.Errorf("modifier %s is not supported for hierarchical multi criterion", criterion.Modifier))
|
||||
return
|
||||
}
|
||||
|
||||
if criterion.Modifier == models.CriterionModifierIsNull || criterion.Modifier == models.CriterionModifierNotNull {
|
||||
var notClause string
|
||||
if criterion.Modifier == models.CriterionModifierNotNull {
|
||||
|
|
@ -968,7 +1009,11 @@ func (m *hierarchicalMultiCriterionHandlerBuilder) handler(c *models.Hierarchica
|
|||
}
|
||||
|
||||
if len(criterion.Value) > 0 {
|
||||
valuesClause := getHierarchicalValues(ctx, m.tx, criterion.Value, m.foreignTable, m.relationsTable, m.parentFK, criterion.Depth)
|
||||
valuesClause, err := getHierarchicalValues(ctx, m.tx, criterion.Value, m.foreignTable, m.relationsTable, m.parentFK, m.childFK, criterion.Depth)
|
||||
if err != nil {
|
||||
f.setError(err)
|
||||
return
|
||||
}
|
||||
|
||||
switch criterion.Modifier {
|
||||
case models.CriterionModifierIncludes:
|
||||
|
|
@ -980,7 +1025,11 @@ func (m *hierarchicalMultiCriterionHandlerBuilder) handler(c *models.Hierarchica
|
|||
}
|
||||
|
||||
if len(criterion.Excludes) > 0 {
|
||||
valuesClause := getHierarchicalValues(ctx, m.tx, criterion.Excludes, m.foreignTable, m.relationsTable, m.parentFK, criterion.Depth)
|
||||
valuesClause, err := getHierarchicalValues(ctx, m.tx, criterion.Excludes, m.foreignTable, m.relationsTable, m.parentFK, m.childFK, criterion.Depth)
|
||||
if err != nil {
|
||||
f.setError(err)
|
||||
return
|
||||
}
|
||||
|
||||
f.addWhere(fmt.Sprintf("%s.%s NOT IN (SELECT column2 FROM (%s)) OR %[1]s.%[2]s IS NULL", m.primaryTable, m.foreignFK, valuesClause))
|
||||
}
|
||||
|
|
@ -992,10 +1041,12 @@ type joinedHierarchicalMultiCriterionHandlerBuilder struct {
|
|||
tx dbWrapper
|
||||
|
||||
primaryTable string
|
||||
primaryKey string
|
||||
foreignTable string
|
||||
foreignFK string
|
||||
|
||||
parentFK string
|
||||
childFK string
|
||||
relationsTable string
|
||||
|
||||
joinAs string
|
||||
|
|
@ -1004,16 +1055,25 @@ type joinedHierarchicalMultiCriterionHandlerBuilder struct {
|
|||
}
|
||||
|
||||
func (m *joinedHierarchicalMultiCriterionHandlerBuilder) addHierarchicalConditionClauses(f *filterBuilder, criterion models.HierarchicalMultiCriterionInput, table, idColumn string) {
|
||||
if criterion.Modifier == models.CriterionModifierEquals {
|
||||
primaryKey := m.primaryKey
|
||||
if primaryKey == "" {
|
||||
primaryKey = "id"
|
||||
}
|
||||
|
||||
switch criterion.Modifier {
|
||||
case models.CriterionModifierEquals:
|
||||
// includes only the provided ids
|
||||
f.addWhere(fmt.Sprintf("%s.%s IS NOT NULL", table, idColumn))
|
||||
f.addHaving(fmt.Sprintf("count(distinct %s.%s) IS %d", table, idColumn, len(criterion.Value)))
|
||||
f.addWhere(utils.StrFormat("(SELECT COUNT(*) FROM {joinTable} s WHERE s.{primaryFK} = {primaryTable}.id) = ?", utils.StrFormatMap{
|
||||
f.addWhere(utils.StrFormat("(SELECT COUNT(*) FROM {joinTable} s WHERE s.{primaryFK} = {primaryTable}.{primaryKey}) = ?", utils.StrFormatMap{
|
||||
"joinTable": m.joinTable,
|
||||
"primaryFK": m.primaryFK,
|
||||
"primaryTable": m.primaryTable,
|
||||
"primaryKey": primaryKey,
|
||||
}), len(criterion.Value))
|
||||
} else {
|
||||
case models.CriterionModifierNotEquals:
|
||||
f.setError(fmt.Errorf("not equals modifier is not supported for hierarchical multi criterion input"))
|
||||
default:
|
||||
addHierarchicalConditionClauses(f, criterion, table, idColumn)
|
||||
}
|
||||
}
|
||||
|
|
@ -1024,6 +1084,15 @@ func (m *joinedHierarchicalMultiCriterionHandlerBuilder) handler(c *models.Hiera
|
|||
// make a copy so we don't modify the original
|
||||
criterion := *c
|
||||
joinAlias := m.joinAs
|
||||
primaryKey := m.primaryKey
|
||||
if primaryKey == "" {
|
||||
primaryKey = "id"
|
||||
}
|
||||
|
||||
if criterion.Modifier == models.CriterionModifierEquals && criterion.Depth != nil && *criterion.Depth != 0 {
|
||||
f.setError(fmt.Errorf("depth is not supported for equals modifier in hierarchical multi criterion input"))
|
||||
return
|
||||
}
|
||||
|
||||
if criterion.Modifier == models.CriterionModifierIsNull || criterion.Modifier == models.CriterionModifierNotNull {
|
||||
var notClause string
|
||||
|
|
@ -1031,7 +1100,7 @@ func (m *joinedHierarchicalMultiCriterionHandlerBuilder) handler(c *models.Hiera
|
|||
notClause = "NOT"
|
||||
}
|
||||
|
||||
f.addLeftJoin(m.joinTable, joinAlias, fmt.Sprintf("%s.%s = %s.id", joinAlias, m.primaryFK, m.primaryTable))
|
||||
f.addLeftJoin(m.joinTable, joinAlias, fmt.Sprintf("%s.%s = %s.%s", joinAlias, m.primaryFK, m.primaryTable, primaryKey))
|
||||
|
||||
f.addWhere(utils.StrFormat("{table}.{column} IS {not} NULL", utils.StrFormatMap{
|
||||
"table": joinAlias,
|
||||
|
|
@ -1053,7 +1122,11 @@ func (m *joinedHierarchicalMultiCriterionHandlerBuilder) handler(c *models.Hiera
|
|||
}
|
||||
|
||||
if len(criterion.Value) > 0 {
|
||||
valuesClause := getHierarchicalValues(ctx, m.tx, criterion.Value, m.foreignTable, m.relationsTable, m.parentFK, criterion.Depth)
|
||||
valuesClause, err := getHierarchicalValues(ctx, m.tx, criterion.Value, m.foreignTable, m.relationsTable, m.parentFK, m.childFK, criterion.Depth)
|
||||
if err != nil {
|
||||
f.setError(err)
|
||||
return
|
||||
}
|
||||
|
||||
joinTable := utils.StrFormat(`(
|
||||
SELECT j.*, d.column1 AS root_id, d.column2 AS item_id FROM {joinTable} AS j
|
||||
|
|
@ -1065,13 +1138,17 @@ func (m *joinedHierarchicalMultiCriterionHandlerBuilder) handler(c *models.Hiera
|
|||
"valuesClause": valuesClause,
|
||||
})
|
||||
|
||||
f.addLeftJoin(joinTable, joinAlias, fmt.Sprintf("%s.%s = %s.id", joinAlias, m.primaryFK, m.primaryTable))
|
||||
f.addLeftJoin(joinTable, joinAlias, fmt.Sprintf("%s.%s = %s.%s", joinAlias, m.primaryFK, m.primaryTable, primaryKey))
|
||||
|
||||
m.addHierarchicalConditionClauses(f, criterion, joinAlias, "root_id")
|
||||
}
|
||||
|
||||
if len(criterion.Excludes) > 0 {
|
||||
valuesClause := getHierarchicalValues(ctx, m.tx, criterion.Excludes, m.foreignTable, m.relationsTable, m.parentFK, criterion.Depth)
|
||||
valuesClause, err := getHierarchicalValues(ctx, m.tx, criterion.Excludes, m.foreignTable, m.relationsTable, m.parentFK, m.childFK, criterion.Depth)
|
||||
if err != nil {
|
||||
f.setError(err)
|
||||
return
|
||||
}
|
||||
|
||||
joinTable := utils.StrFormat(`(
|
||||
SELECT j2.*, e.column1 AS root_id, e.column2 AS item_id FROM {joinTable} AS j2
|
||||
|
|
@ -1085,7 +1162,7 @@ func (m *joinedHierarchicalMultiCriterionHandlerBuilder) handler(c *models.Hiera
|
|||
|
||||
joinAlias2 := joinAlias + "2"
|
||||
|
||||
f.addLeftJoin(joinTable, joinAlias2, fmt.Sprintf("%s.%s = %s.id", joinAlias2, m.primaryFK, m.primaryTable))
|
||||
f.addLeftJoin(joinTable, joinAlias2, fmt.Sprintf("%s.%s = %s.%s", joinAlias2, m.primaryFK, m.primaryTable, primaryKey))
|
||||
|
||||
// modify for exclusion
|
||||
criterionCopy := criterion
|
||||
|
|
@ -1098,6 +1175,83 @@ func (m *joinedHierarchicalMultiCriterionHandlerBuilder) handler(c *models.Hiera
|
|||
}
|
||||
}
|
||||
|
||||
type joinedPerformerTagsHandler struct {
|
||||
criterion *models.HierarchicalMultiCriterionInput
|
||||
|
||||
primaryTable string // eg scenes
|
||||
joinTable string // eg performers_scenes
|
||||
joinPrimaryKey string // eg scene_id
|
||||
}
|
||||
|
||||
func (h *joinedPerformerTagsHandler) handle(ctx context.Context, f *filterBuilder) {
|
||||
tags := h.criterion
|
||||
|
||||
if tags != nil {
|
||||
criterion := tags.CombineExcludes()
|
||||
|
||||
// validate the modifier
|
||||
switch criterion.Modifier {
|
||||
case models.CriterionModifierIncludesAll, models.CriterionModifierIncludes, models.CriterionModifierExcludes, models.CriterionModifierIsNull, models.CriterionModifierNotNull:
|
||||
// valid
|
||||
default:
|
||||
f.setError(fmt.Errorf("invalid modifier %s for performer tags", criterion.Modifier))
|
||||
}
|
||||
|
||||
strFormatMap := utils.StrFormatMap{
|
||||
"primaryTable": h.primaryTable,
|
||||
"joinTable": h.joinTable,
|
||||
"joinPrimaryKey": h.joinPrimaryKey,
|
||||
"inBinding": getInBinding(len(criterion.Value)),
|
||||
}
|
||||
|
||||
if criterion.Modifier == models.CriterionModifierIsNull || criterion.Modifier == models.CriterionModifierNotNull {
|
||||
var notClause string
|
||||
if criterion.Modifier == models.CriterionModifierNotNull {
|
||||
notClause = "NOT"
|
||||
}
|
||||
|
||||
f.addLeftJoin(h.joinTable, "", utils.StrFormat("{primaryTable}.id = {joinTable}.{joinPrimaryKey}", strFormatMap))
|
||||
f.addLeftJoin("performers_tags", "", utils.StrFormat("{joinTable}.performer_id = performers_tags.performer_id", strFormatMap))
|
||||
|
||||
f.addWhere(fmt.Sprintf("performers_tags.tag_id IS %s NULL", notClause))
|
||||
return
|
||||
}
|
||||
|
||||
if len(criterion.Value) == 0 && len(criterion.Excludes) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if len(criterion.Value) > 0 {
|
||||
valuesClause, err := getHierarchicalValues(ctx, dbWrapper{}, criterion.Value, tagTable, "tags_relations", "", "", criterion.Depth)
|
||||
if err != nil {
|
||||
f.setError(err)
|
||||
return
|
||||
}
|
||||
|
||||
f.addWith(utils.StrFormat(`performer_tags AS (
|
||||
SELECT ps.{joinPrimaryKey} as primaryID, t.column1 AS root_tag_id FROM {joinTable} ps
|
||||
INNER JOIN performers_tags pt ON pt.performer_id = ps.performer_id
|
||||
INNER JOIN (`+valuesClause+`) t ON t.column2 = pt.tag_id
|
||||
)`, strFormatMap))
|
||||
|
||||
f.addLeftJoin("performer_tags", "", utils.StrFormat("performer_tags.primaryID = {primaryTable}.id", strFormatMap))
|
||||
|
||||
addHierarchicalConditionClauses(f, criterion, "performer_tags", "root_tag_id")
|
||||
}
|
||||
|
||||
if len(criterion.Excludes) > 0 {
|
||||
valuesClause, err := getHierarchicalValues(ctx, dbWrapper{}, criterion.Excludes, tagTable, "tags_relations", "", "", criterion.Depth)
|
||||
if err != nil {
|
||||
f.setError(err)
|
||||
return
|
||||
}
|
||||
|
||||
clause := utils.StrFormat("{primaryTable}.id NOT IN (SELECT {joinTable}.{joinPrimaryKey} FROM {joinTable} INNER JOIN performers_tags ON {joinTable}.performer_id = performers_tags.performer_id WHERE performers_tags.tag_id IN (SELECT column2 FROM (%s)))", strFormatMap)
|
||||
f.addWhere(fmt.Sprintf(clause, valuesClause))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type stashIDCriterionHandler struct {
|
||||
c *models.StashIDCriterionInput
|
||||
stashIDRepository *stashIDRepository
|
||||
|
|
|
|||
|
|
@ -670,7 +670,7 @@ func (qb *GalleryStore) makeFilter(ctx context.Context, galleryFilter *models.Ga
|
|||
query.handleCriterion(ctx, galleryPerformersCriterionHandler(qb, galleryFilter.Performers))
|
||||
query.handleCriterion(ctx, galleryPerformerCountCriterionHandler(qb, galleryFilter.PerformerCount))
|
||||
query.handleCriterion(ctx, hasChaptersCriterionHandler(galleryFilter.HasChapters))
|
||||
query.handleCriterion(ctx, galleryStudioCriterionHandler(qb, galleryFilter.Studios))
|
||||
query.handleCriterion(ctx, studioCriterionHandler(galleryTable, galleryFilter.Studios))
|
||||
query.handleCriterion(ctx, galleryPerformerTagsCriterionHandler(qb, galleryFilter.PerformerTags))
|
||||
query.handleCriterion(ctx, galleryAverageResolutionCriterionHandler(qb, galleryFilter.AverageResolution))
|
||||
query.handleCriterion(ctx, galleryImageCountCriterionHandler(qb, galleryFilter.ImageCount))
|
||||
|
|
@ -968,51 +968,12 @@ func hasChaptersCriterionHandler(hasChapters *string) criterionHandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
func galleryStudioCriterionHandler(qb *GalleryStore, studios *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
|
||||
h := hierarchicalMultiCriterionHandlerBuilder{
|
||||
tx: qb.tx,
|
||||
|
||||
primaryTable: galleryTable,
|
||||
foreignTable: studioTable,
|
||||
foreignFK: studioIDColumn,
|
||||
parentFK: "parent_id",
|
||||
}
|
||||
|
||||
return h.handler(studios)
|
||||
}
|
||||
|
||||
func galleryPerformerTagsCriterionHandler(qb *GalleryStore, tags *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if tags != nil {
|
||||
if tags.Modifier == models.CriterionModifierIsNull || tags.Modifier == models.CriterionModifierNotNull {
|
||||
var notClause string
|
||||
if tags.Modifier == models.CriterionModifierNotNull {
|
||||
notClause = "NOT"
|
||||
}
|
||||
|
||||
f.addLeftJoin("performers_galleries", "", "galleries.id = performers_galleries.gallery_id")
|
||||
f.addLeftJoin("performers_tags", "", "performers_galleries.performer_id = performers_tags.performer_id")
|
||||
|
||||
f.addWhere(fmt.Sprintf("performers_tags.tag_id IS %s NULL", notClause))
|
||||
return
|
||||
}
|
||||
|
||||
if len(tags.Value) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
valuesClause := getHierarchicalValues(ctx, qb.tx, tags.Value, tagTable, "tags_relations", "", tags.Depth)
|
||||
|
||||
f.addWith(`performer_tags AS (
|
||||
SELECT pg.gallery_id, t.column1 AS root_tag_id FROM performers_galleries pg
|
||||
INNER JOIN performers_tags pt ON pt.performer_id = pg.performer_id
|
||||
INNER JOIN (` + valuesClause + `) t ON t.column2 = pt.tag_id
|
||||
)`)
|
||||
|
||||
f.addLeftJoin("performer_tags", "", "performer_tags.gallery_id = galleries.id")
|
||||
|
||||
addHierarchicalConditionClauses(f, *tags, "performer_tags", "root_tag_id")
|
||||
}
|
||||
func galleryPerformerTagsCriterionHandler(qb *GalleryStore, tags *models.HierarchicalMultiCriterionInput) criterionHandler {
|
||||
return &joinedPerformerTagsHandler{
|
||||
criterion: tags,
|
||||
primaryTable: galleryTable,
|
||||
joinTable: performersGalleriesTable,
|
||||
joinPrimaryKey: galleryIDColumn,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1945,154 +1945,369 @@ func TestGalleryQueryIsMissingDate(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGalleryQueryPerformers(t *testing.T) {
|
||||
withTxn(func(ctx context.Context) error {
|
||||
sqb := db.Gallery
|
||||
performerCriterion := models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(performerIDs[performerIdxWithGallery]),
|
||||
strconv.Itoa(performerIDs[performerIdx1WithGallery]),
|
||||
tests := []struct {
|
||||
name string
|
||||
filter models.MultiCriterionInput
|
||||
includeIdxs []int
|
||||
excludeIdxs []int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"includes",
|
||||
models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(performerIDs[performerIdxWithGallery]),
|
||||
strconv.Itoa(performerIDs[performerIdx1WithGallery]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
}
|
||||
|
||||
galleryFilter := models.GalleryFilterType{
|
||||
Performers: &performerCriterion,
|
||||
}
|
||||
|
||||
galleries := queryGallery(ctx, t, sqb, &galleryFilter, nil)
|
||||
|
||||
assert.Len(t, galleries, 2)
|
||||
|
||||
// ensure ids are correct
|
||||
for _, gallery := range galleries {
|
||||
assert.True(t, gallery.ID == galleryIDs[galleryIdxWithPerformer] || gallery.ID == galleryIDs[galleryIdxWithTwoPerformers])
|
||||
}
|
||||
|
||||
performerCriterion = models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(performerIDs[performerIdx1WithGallery]),
|
||||
strconv.Itoa(performerIDs[performerIdx2WithGallery]),
|
||||
[]int{
|
||||
galleryIdxWithPerformer,
|
||||
galleryIdxWithTwoPerformers,
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludesAll,
|
||||
}
|
||||
|
||||
galleries = queryGallery(ctx, t, sqb, &galleryFilter, nil)
|
||||
|
||||
assert.Len(t, galleries, 1)
|
||||
assert.Equal(t, galleryIDs[galleryIdxWithTwoPerformers], galleries[0].ID)
|
||||
|
||||
performerCriterion = models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(performerIDs[performerIdx1WithGallery]),
|
||||
[]int{
|
||||
galleryIdxWithImage,
|
||||
},
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
}
|
||||
false,
|
||||
},
|
||||
{
|
||||
"includes all",
|
||||
models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(performerIDs[performerIdx1WithGallery]),
|
||||
strconv.Itoa(performerIDs[performerIdx2WithGallery]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludesAll,
|
||||
},
|
||||
[]int{
|
||||
galleryIdxWithTwoPerformers,
|
||||
},
|
||||
[]int{
|
||||
galleryIdxWithPerformer,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"excludes",
|
||||
models.MultiCriterionInput{
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
Value: []string{strconv.Itoa(tagIDs[performerIdx1WithGallery])},
|
||||
},
|
||||
nil,
|
||||
[]int{galleryIdxWithTwoPerformers},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"is null",
|
||||
models.MultiCriterionInput{
|
||||
Modifier: models.CriterionModifierIsNull,
|
||||
},
|
||||
[]int{galleryIdxWithTag},
|
||||
[]int{
|
||||
galleryIdxWithPerformer,
|
||||
galleryIdxWithTwoPerformers,
|
||||
galleryIdxWithPerformerTwoTags,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not null",
|
||||
models.MultiCriterionInput{
|
||||
Modifier: models.CriterionModifierNotNull,
|
||||
},
|
||||
[]int{
|
||||
galleryIdxWithPerformer,
|
||||
galleryIdxWithTwoPerformers,
|
||||
galleryIdxWithPerformerTwoTags,
|
||||
},
|
||||
[]int{galleryIdxWithTag},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"equals",
|
||||
models.MultiCriterionInput{
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[performerIdx1WithGallery]),
|
||||
strconv.Itoa(tagIDs[performerIdx2WithGallery]),
|
||||
},
|
||||
},
|
||||
[]int{galleryIdxWithTwoPerformers},
|
||||
[]int{
|
||||
galleryIdxWithThreePerformers,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not equals",
|
||||
models.MultiCriterionInput{
|
||||
Modifier: models.CriterionModifierNotEquals,
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[performerIdx1WithGallery]),
|
||||
strconv.Itoa(tagIDs[performerIdx2WithGallery]),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
q := getGalleryStringValue(galleryIdxWithTwoPerformers, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
for _, tt := range tests {
|
||||
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
||||
assert := assert.New(t)
|
||||
|
||||
galleries = queryGallery(ctx, t, sqb, &galleryFilter, &findFilter)
|
||||
assert.Len(t, galleries, 0)
|
||||
results, _, err := db.Gallery.Query(ctx, &models.GalleryFilterType{
|
||||
Performers: &tt.filter,
|
||||
}, nil)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GalleryStore.Query() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
ids := galleriesToIDs(results)
|
||||
|
||||
include := indexesToIDs(galleryIDs, tt.includeIdxs)
|
||||
exclude := indexesToIDs(galleryIDs, tt.excludeIdxs)
|
||||
|
||||
for _, i := range include {
|
||||
assert.Contains(ids, i)
|
||||
}
|
||||
for _, e := range exclude {
|
||||
assert.NotContains(ids, e)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGalleryQueryTags(t *testing.T) {
|
||||
withTxn(func(ctx context.Context) error {
|
||||
sqb := db.Gallery
|
||||
tagCriterion := models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdxWithGallery]),
|
||||
strconv.Itoa(tagIDs[tagIdx1WithGallery]),
|
||||
tests := []struct {
|
||||
name string
|
||||
filter models.HierarchicalMultiCriterionInput
|
||||
includeIdxs []int
|
||||
excludeIdxs []int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"includes",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdxWithGallery]),
|
||||
strconv.Itoa(tagIDs[tagIdx1WithGallery]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
}
|
||||
|
||||
galleryFilter := models.GalleryFilterType{
|
||||
Tags: &tagCriterion,
|
||||
}
|
||||
|
||||
galleries := queryGallery(ctx, t, sqb, &galleryFilter, nil)
|
||||
assert.Len(t, galleries, 2)
|
||||
|
||||
// ensure ids are correct
|
||||
for _, gallery := range galleries {
|
||||
assert.True(t, gallery.ID == galleryIDs[galleryIdxWithTag] || gallery.ID == galleryIDs[galleryIdxWithTwoTags])
|
||||
}
|
||||
|
||||
tagCriterion = models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithGallery]),
|
||||
strconv.Itoa(tagIDs[tagIdx2WithGallery]),
|
||||
[]int{
|
||||
galleryIdxWithTag,
|
||||
galleryIdxWithTwoTags,
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludesAll,
|
||||
}
|
||||
|
||||
galleries = queryGallery(ctx, t, sqb, &galleryFilter, nil)
|
||||
|
||||
assert.Len(t, galleries, 1)
|
||||
assert.Equal(t, galleryIDs[galleryIdxWithTwoTags], galleries[0].ID)
|
||||
|
||||
tagCriterion = models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithGallery]),
|
||||
[]int{
|
||||
galleryIdxWithImage,
|
||||
},
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
}
|
||||
false,
|
||||
},
|
||||
{
|
||||
"includes all",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithGallery]),
|
||||
strconv.Itoa(tagIDs[tagIdx2WithGallery]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludesAll,
|
||||
},
|
||||
[]int{
|
||||
galleryIdxWithTwoTags,
|
||||
},
|
||||
[]int{
|
||||
galleryIdxWithTag,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"excludes",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
Value: []string{strconv.Itoa(tagIDs[tagIdx1WithGallery])},
|
||||
},
|
||||
nil,
|
||||
[]int{galleryIdxWithTwoTags},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"is null",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierIsNull,
|
||||
},
|
||||
[]int{galleryIdx1WithPerformer},
|
||||
[]int{
|
||||
galleryIdxWithTag,
|
||||
galleryIdxWithTwoTags,
|
||||
galleryIdxWithThreeTags,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not null",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierNotNull,
|
||||
},
|
||||
[]int{
|
||||
galleryIdxWithTag,
|
||||
galleryIdxWithTwoTags,
|
||||
galleryIdxWithThreeTags,
|
||||
},
|
||||
[]int{galleryIdx1WithPerformer},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"equals",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithGallery]),
|
||||
strconv.Itoa(tagIDs[tagIdx2WithGallery]),
|
||||
},
|
||||
},
|
||||
[]int{galleryIdxWithTwoTags},
|
||||
[]int{
|
||||
galleryIdxWithThreeTags,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not equals",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierNotEquals,
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithGallery]),
|
||||
strconv.Itoa(tagIDs[tagIdx2WithGallery]),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
q := getGalleryStringValue(galleryIdxWithTwoTags, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
for _, tt := range tests {
|
||||
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
||||
assert := assert.New(t)
|
||||
|
||||
galleries = queryGallery(ctx, t, sqb, &galleryFilter, &findFilter)
|
||||
assert.Len(t, galleries, 0)
|
||||
results, _, err := db.Gallery.Query(ctx, &models.GalleryFilterType{
|
||||
Tags: &tt.filter,
|
||||
}, nil)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GalleryStore.Query() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
ids := galleriesToIDs(results)
|
||||
|
||||
include := indexesToIDs(imageIDs, tt.includeIdxs)
|
||||
exclude := indexesToIDs(imageIDs, tt.excludeIdxs)
|
||||
|
||||
for _, i := range include {
|
||||
assert.Contains(ids, i)
|
||||
}
|
||||
for _, e := range exclude {
|
||||
assert.NotContains(ids, e)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGalleryQueryStudio(t *testing.T) {
|
||||
withTxn(func(ctx context.Context) error {
|
||||
sqb := db.Gallery
|
||||
studioCriterion := models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(studioIDs[studioIdxWithGallery]),
|
||||
tests := []struct {
|
||||
name string
|
||||
q string
|
||||
studioCriterion models.HierarchicalMultiCriterionInput
|
||||
expectedIDs []int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"includes",
|
||||
"",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(studioIDs[studioIdxWithGallery]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
}
|
||||
|
||||
galleryFilter := models.GalleryFilterType{
|
||||
Studios: &studioCriterion,
|
||||
}
|
||||
|
||||
galleries := queryGallery(ctx, t, sqb, &galleryFilter, nil)
|
||||
|
||||
assert.Len(t, galleries, 1)
|
||||
|
||||
// ensure id is correct
|
||||
assert.Equal(t, galleryIDs[galleryIdxWithStudio], galleries[0].ID)
|
||||
|
||||
studioCriterion = models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(studioIDs[studioIdxWithGallery]),
|
||||
[]int{galleryIDs[galleryIdxWithStudio]},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"excludes",
|
||||
getGalleryStringValue(galleryIdxWithStudio, titleField),
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(studioIDs[studioIdxWithGallery]),
|
||||
},
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
},
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
}
|
||||
[]int{},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"excludes includes null",
|
||||
getGalleryStringValue(galleryIdxWithImage, titleField),
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(studioIDs[studioIdxWithGallery]),
|
||||
},
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
},
|
||||
[]int{galleryIDs[galleryIdxWithImage]},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"equals",
|
||||
"",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(studioIDs[studioIdxWithGallery]),
|
||||
},
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
},
|
||||
[]int{galleryIDs[galleryIdxWithStudio]},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not equals",
|
||||
getGalleryStringValue(galleryIdxWithStudio, titleField),
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(studioIDs[studioIdxWithGallery]),
|
||||
},
|
||||
Modifier: models.CriterionModifierNotEquals,
|
||||
},
|
||||
[]int{},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
q := getGalleryStringValue(galleryIdxWithStudio, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
qb := db.Gallery
|
||||
|
||||
galleries = queryGallery(ctx, t, sqb, &galleryFilter, &findFilter)
|
||||
assert.Len(t, galleries, 0)
|
||||
for _, tt := range tests {
|
||||
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
||||
studioCriterion := tt.studioCriterion
|
||||
|
||||
return nil
|
||||
})
|
||||
galleryFilter := models.GalleryFilterType{
|
||||
Studios: &studioCriterion,
|
||||
}
|
||||
|
||||
var findFilter *models.FindFilterType
|
||||
if tt.q != "" {
|
||||
findFilter = &models.FindFilterType{
|
||||
Q: &tt.q,
|
||||
}
|
||||
}
|
||||
|
||||
gallerys := queryGallery(ctx, t, qb, &galleryFilter, findFilter)
|
||||
|
||||
assert.ElementsMatch(t, galleriesToIDs(gallerys), tt.expectedIDs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGalleryQueryStudioDepth(t *testing.T) {
|
||||
|
|
@ -2157,81 +2372,198 @@ func TestGalleryQueryStudioDepth(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGalleryQueryPerformerTags(t *testing.T) {
|
||||
withTxn(func(ctx context.Context) error {
|
||||
sqb := db.Gallery
|
||||
tagCriterion := models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdxWithPerformer]),
|
||||
strconv.Itoa(tagIDs[tagIdx1WithPerformer]),
|
||||
allDepth := -1
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
findFilter *models.FindFilterType
|
||||
filter *models.GalleryFilterType
|
||||
includeIdxs []int
|
||||
excludeIdxs []int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"includes",
|
||||
nil,
|
||||
&models.GalleryFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdxWithPerformer]),
|
||||
strconv.Itoa(tagIDs[tagIdx1WithPerformer]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
},
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
}
|
||||
|
||||
galleryFilter := models.GalleryFilterType{
|
||||
PerformerTags: &tagCriterion,
|
||||
}
|
||||
|
||||
galleries := queryGallery(ctx, t, sqb, &galleryFilter, nil)
|
||||
assert.Len(t, galleries, 2)
|
||||
|
||||
// ensure ids are correct
|
||||
for _, gallery := range galleries {
|
||||
assert.True(t, gallery.ID == galleryIDs[galleryIdxWithPerformerTag] || gallery.ID == galleryIDs[galleryIdxWithPerformerTwoTags])
|
||||
}
|
||||
|
||||
tagCriterion = models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithPerformer]),
|
||||
strconv.Itoa(tagIDs[tagIdx2WithPerformer]),
|
||||
[]int{
|
||||
galleryIdxWithPerformerTag,
|
||||
galleryIdxWithPerformerTwoTags,
|
||||
galleryIdxWithTwoPerformerTag,
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludesAll,
|
||||
}
|
||||
|
||||
galleries = queryGallery(ctx, t, sqb, &galleryFilter, nil)
|
||||
|
||||
assert.Len(t, galleries, 1)
|
||||
assert.Equal(t, galleryIDs[galleryIdxWithPerformerTwoTags], galleries[0].ID)
|
||||
|
||||
tagCriterion = models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithPerformer]),
|
||||
[]int{
|
||||
galleryIdxWithPerformer,
|
||||
},
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
}
|
||||
false,
|
||||
},
|
||||
{
|
||||
"includes sub-tags",
|
||||
nil,
|
||||
&models.GalleryFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdxWithParentAndChild]),
|
||||
},
|
||||
Depth: &allDepth,
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
},
|
||||
},
|
||||
[]int{
|
||||
galleryIdxWithPerformerParentTag,
|
||||
},
|
||||
[]int{
|
||||
galleryIdxWithPerformer,
|
||||
galleryIdxWithPerformerTag,
|
||||
galleryIdxWithPerformerTwoTags,
|
||||
galleryIdxWithTwoPerformerTag,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"includes all",
|
||||
nil,
|
||||
&models.GalleryFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithPerformer]),
|
||||
strconv.Itoa(tagIDs[tagIdx2WithPerformer]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludesAll,
|
||||
},
|
||||
},
|
||||
[]int{
|
||||
galleryIdxWithPerformerTwoTags,
|
||||
},
|
||||
[]int{
|
||||
galleryIdxWithPerformer,
|
||||
galleryIdxWithPerformerTag,
|
||||
galleryIdxWithTwoPerformerTag,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"excludes performer tag tagIdx2WithPerformer",
|
||||
nil,
|
||||
&models.GalleryFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
Value: []string{strconv.Itoa(tagIDs[tagIdx2WithPerformer])},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
[]int{galleryIdxWithTwoPerformerTag},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"excludes sub-tags",
|
||||
nil,
|
||||
&models.GalleryFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdxWithParentAndChild]),
|
||||
},
|
||||
Depth: &allDepth,
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
},
|
||||
},
|
||||
[]int{
|
||||
galleryIdxWithPerformer,
|
||||
galleryIdxWithPerformerTag,
|
||||
galleryIdxWithPerformerTwoTags,
|
||||
galleryIdxWithTwoPerformerTag,
|
||||
},
|
||||
[]int{
|
||||
galleryIdxWithPerformerParentTag,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"is null",
|
||||
nil,
|
||||
&models.GalleryFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierIsNull,
|
||||
},
|
||||
},
|
||||
[]int{galleryIdx1WithImage},
|
||||
[]int{galleryIdxWithPerformerTag},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not null",
|
||||
nil,
|
||||
&models.GalleryFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierNotNull,
|
||||
},
|
||||
},
|
||||
[]int{galleryIdxWithPerformerTag},
|
||||
[]int{galleryIdx1WithImage},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"equals",
|
||||
nil,
|
||||
&models.GalleryFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx2WithPerformer]),
|
||||
},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"not equals",
|
||||
nil,
|
||||
&models.GalleryFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierNotEquals,
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx2WithPerformer]),
|
||||
},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
q := getGalleryStringValue(galleryIdxWithPerformerTwoTags, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
for _, tt := range tests {
|
||||
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
||||
assert := assert.New(t)
|
||||
|
||||
galleries = queryGallery(ctx, t, sqb, &galleryFilter, &findFilter)
|
||||
assert.Len(t, galleries, 0)
|
||||
results, _, err := db.Gallery.Query(ctx, tt.filter, tt.findFilter)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ImageStore.Query() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
tagCriterion = models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierIsNull,
|
||||
}
|
||||
q = getGalleryStringValue(galleryIdx1WithImage, titleField)
|
||||
ids := galleriesToIDs(results)
|
||||
|
||||
galleries = queryGallery(ctx, t, sqb, &galleryFilter, &findFilter)
|
||||
assert.Len(t, galleries, 1)
|
||||
assert.Equal(t, galleryIDs[galleryIdx1WithImage], galleries[0].ID)
|
||||
include := indexesToIDs(galleryIDs, tt.includeIdxs)
|
||||
exclude := indexesToIDs(galleryIDs, tt.excludeIdxs)
|
||||
|
||||
q = getGalleryStringValue(galleryIdxWithPerformerTag, titleField)
|
||||
galleries = queryGallery(ctx, t, sqb, &galleryFilter, &findFilter)
|
||||
assert.Len(t, galleries, 0)
|
||||
|
||||
tagCriterion.Modifier = models.CriterionModifierNotNull
|
||||
|
||||
galleries = queryGallery(ctx, t, sqb, &galleryFilter, &findFilter)
|
||||
assert.Len(t, galleries, 1)
|
||||
assert.Equal(t, galleryIDs[galleryIdxWithPerformerTag], galleries[0].ID)
|
||||
|
||||
q = getGalleryStringValue(galleryIdx1WithImage, titleField)
|
||||
galleries = queryGallery(ctx, t, sqb, &galleryFilter, &findFilter)
|
||||
assert.Len(t, galleries, 0)
|
||||
|
||||
return nil
|
||||
})
|
||||
for _, i := range include {
|
||||
assert.Contains(ids, i)
|
||||
}
|
||||
for _, e := range exclude {
|
||||
assert.NotContains(ids, e)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGalleryQueryTagCount(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -669,7 +669,7 @@ func (qb *ImageStore) makeFilter(ctx context.Context, imageFilter *models.ImageF
|
|||
query.handleCriterion(ctx, imageGalleriesCriterionHandler(qb, imageFilter.Galleries))
|
||||
query.handleCriterion(ctx, imagePerformersCriterionHandler(qb, imageFilter.Performers))
|
||||
query.handleCriterion(ctx, imagePerformerCountCriterionHandler(qb, imageFilter.PerformerCount))
|
||||
query.handleCriterion(ctx, imageStudioCriterionHandler(qb, imageFilter.Studios))
|
||||
query.handleCriterion(ctx, studioCriterionHandler(imageTable, imageFilter.Studios))
|
||||
query.handleCriterion(ctx, imagePerformerTagsCriterionHandler(qb, imageFilter.PerformerTags))
|
||||
query.handleCriterion(ctx, imagePerformerFavoriteCriterionHandler(imageFilter.PerformerFavorite))
|
||||
query.handleCriterion(ctx, timestampCriterionHandler(imageFilter.CreatedAt, "images.created_at"))
|
||||
|
|
@ -946,51 +946,12 @@ GROUP BY performers_images.image_id HAVING SUM(performers.favorite) = 0)`, "nofa
|
|||
}
|
||||
}
|
||||
|
||||
func imageStudioCriterionHandler(qb *ImageStore, studios *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
|
||||
h := hierarchicalMultiCriterionHandlerBuilder{
|
||||
tx: qb.tx,
|
||||
|
||||
primaryTable: imageTable,
|
||||
foreignTable: studioTable,
|
||||
foreignFK: studioIDColumn,
|
||||
parentFK: "parent_id",
|
||||
}
|
||||
|
||||
return h.handler(studios)
|
||||
}
|
||||
|
||||
func imagePerformerTagsCriterionHandler(qb *ImageStore, tags *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if tags != nil {
|
||||
if tags.Modifier == models.CriterionModifierIsNull || tags.Modifier == models.CriterionModifierNotNull {
|
||||
var notClause string
|
||||
if tags.Modifier == models.CriterionModifierNotNull {
|
||||
notClause = "NOT"
|
||||
}
|
||||
|
||||
f.addLeftJoin("performers_images", "", "images.id = performers_images.image_id")
|
||||
f.addLeftJoin("performers_tags", "", "performers_images.performer_id = performers_tags.performer_id")
|
||||
|
||||
f.addWhere(fmt.Sprintf("performers_tags.tag_id IS %s NULL", notClause))
|
||||
return
|
||||
}
|
||||
|
||||
if len(tags.Value) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
valuesClause := getHierarchicalValues(ctx, qb.tx, tags.Value, tagTable, "tags_relations", "", tags.Depth)
|
||||
|
||||
f.addWith(`performer_tags AS (
|
||||
SELECT pi.image_id, t.column1 AS root_tag_id FROM performers_images pi
|
||||
INNER JOIN performers_tags pt ON pt.performer_id = pi.performer_id
|
||||
INNER JOIN (` + valuesClause + `) t ON t.column2 = pt.tag_id
|
||||
)`)
|
||||
|
||||
f.addLeftJoin("performer_tags", "", "performer_tags.image_id = images.id")
|
||||
|
||||
addHierarchicalConditionClauses(f, *tags, "performer_tags", "root_tag_id")
|
||||
}
|
||||
func imagePerformerTagsCriterionHandler(qb *ImageStore, tags *models.HierarchicalMultiCriterionInput) criterionHandler {
|
||||
return &joinedPerformerTagsHandler{
|
||||
criterion: tags,
|
||||
primaryTable: imageTable,
|
||||
joinTable: performersImagesTable,
|
||||
joinPrimaryKey: imageIDColumn,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2124,203 +2124,369 @@ func TestImageQueryGallery(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestImageQueryPerformers(t *testing.T) {
|
||||
withTxn(func(ctx context.Context) error {
|
||||
sqb := db.Image
|
||||
performerCriterion := models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(performerIDs[performerIdxWithImage]),
|
||||
strconv.Itoa(performerIDs[performerIdx1WithImage]),
|
||||
tests := []struct {
|
||||
name string
|
||||
filter models.MultiCriterionInput
|
||||
includeIdxs []int
|
||||
excludeIdxs []int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"includes",
|
||||
models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(performerIDs[performerIdxWithImage]),
|
||||
strconv.Itoa(performerIDs[performerIdx1WithImage]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
}
|
||||
|
||||
imageFilter := models.ImageFilterType{
|
||||
Performers: &performerCriterion,
|
||||
}
|
||||
|
||||
images := queryImages(ctx, t, sqb, &imageFilter, nil)
|
||||
assert.Len(t, images, 2)
|
||||
|
||||
// ensure ids are correct
|
||||
for _, image := range images {
|
||||
assert.True(t, image.ID == imageIDs[imageIdxWithPerformer] || image.ID == imageIDs[imageIdxWithTwoPerformers])
|
||||
}
|
||||
|
||||
performerCriterion = models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(performerIDs[performerIdx1WithImage]),
|
||||
strconv.Itoa(performerIDs[performerIdx2WithImage]),
|
||||
[]int{
|
||||
imageIdxWithPerformer,
|
||||
imageIdxWithTwoPerformers,
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludesAll,
|
||||
}
|
||||
|
||||
images = queryImages(ctx, t, sqb, &imageFilter, nil)
|
||||
assert.Len(t, images, 1)
|
||||
assert.Equal(t, imageIDs[imageIdxWithTwoPerformers], images[0].ID)
|
||||
|
||||
performerCriterion = models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(performerIDs[performerIdx1WithImage]),
|
||||
[]int{
|
||||
imageIdxWithGallery,
|
||||
},
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
}
|
||||
false,
|
||||
},
|
||||
{
|
||||
"includes all",
|
||||
models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(performerIDs[performerIdx1WithImage]),
|
||||
strconv.Itoa(performerIDs[performerIdx2WithImage]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludesAll,
|
||||
},
|
||||
[]int{
|
||||
imageIdxWithTwoPerformers,
|
||||
},
|
||||
[]int{
|
||||
imageIdxWithPerformer,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"excludes",
|
||||
models.MultiCriterionInput{
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
Value: []string{strconv.Itoa(tagIDs[performerIdx1WithImage])},
|
||||
},
|
||||
nil,
|
||||
[]int{imageIdxWithTwoPerformers},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"is null",
|
||||
models.MultiCriterionInput{
|
||||
Modifier: models.CriterionModifierIsNull,
|
||||
},
|
||||
[]int{imageIdxWithTag},
|
||||
[]int{
|
||||
imageIdxWithPerformer,
|
||||
imageIdxWithTwoPerformers,
|
||||
imageIdxWithPerformerTwoTags,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not null",
|
||||
models.MultiCriterionInput{
|
||||
Modifier: models.CriterionModifierNotNull,
|
||||
},
|
||||
[]int{
|
||||
imageIdxWithPerformer,
|
||||
imageIdxWithTwoPerformers,
|
||||
imageIdxWithPerformerTwoTags,
|
||||
},
|
||||
[]int{imageIdxWithTag},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"equals",
|
||||
models.MultiCriterionInput{
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[performerIdx1WithImage]),
|
||||
strconv.Itoa(tagIDs[performerIdx2WithImage]),
|
||||
},
|
||||
},
|
||||
[]int{imageIdxWithTwoPerformers},
|
||||
[]int{
|
||||
imageIdxWithThreePerformers,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not equals",
|
||||
models.MultiCriterionInput{
|
||||
Modifier: models.CriterionModifierNotEquals,
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[performerIdx1WithImage]),
|
||||
strconv.Itoa(tagIDs[performerIdx2WithImage]),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
q := getImageStringValue(imageIdxWithTwoPerformers, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
for _, tt := range tests {
|
||||
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
||||
assert := assert.New(t)
|
||||
|
||||
images = queryImages(ctx, t, sqb, &imageFilter, &findFilter)
|
||||
assert.Len(t, images, 0)
|
||||
results, err := db.Image.Query(ctx, models.ImageQueryOptions{
|
||||
ImageFilter: &models.ImageFilterType{
|
||||
Performers: &tt.filter,
|
||||
},
|
||||
})
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ImageStore.Query() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
performerCriterion = models.MultiCriterionInput{
|
||||
Modifier: models.CriterionModifierIsNull,
|
||||
}
|
||||
q = getImageStringValue(imageIdxWithGallery, titleField)
|
||||
include := indexesToIDs(imageIDs, tt.includeIdxs)
|
||||
exclude := indexesToIDs(imageIDs, tt.excludeIdxs)
|
||||
|
||||
images = queryImages(ctx, t, sqb, &imageFilter, &findFilter)
|
||||
assert.Len(t, images, 1)
|
||||
assert.Equal(t, imageIDs[imageIdxWithGallery], images[0].ID)
|
||||
|
||||
q = getImageStringValue(imageIdxWithPerformerTag, titleField)
|
||||
images = queryImages(ctx, t, sqb, &imageFilter, &findFilter)
|
||||
assert.Len(t, images, 0)
|
||||
|
||||
performerCriterion.Modifier = models.CriterionModifierNotNull
|
||||
|
||||
images = queryImages(ctx, t, sqb, &imageFilter, &findFilter)
|
||||
assert.Len(t, images, 1)
|
||||
assert.Equal(t, imageIDs[imageIdxWithPerformerTag], images[0].ID)
|
||||
|
||||
q = getImageStringValue(imageIdxWithGallery, titleField)
|
||||
images = queryImages(ctx, t, sqb, &imageFilter, &findFilter)
|
||||
assert.Len(t, images, 0)
|
||||
|
||||
return nil
|
||||
})
|
||||
for _, i := range include {
|
||||
assert.Contains(results.IDs, i)
|
||||
}
|
||||
for _, e := range exclude {
|
||||
assert.NotContains(results.IDs, e)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageQueryTags(t *testing.T) {
|
||||
withTxn(func(ctx context.Context) error {
|
||||
sqb := db.Image
|
||||
tagCriterion := models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdxWithImage]),
|
||||
strconv.Itoa(tagIDs[tagIdx1WithImage]),
|
||||
tests := []struct {
|
||||
name string
|
||||
filter models.HierarchicalMultiCriterionInput
|
||||
includeIdxs []int
|
||||
excludeIdxs []int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"includes",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdxWithImage]),
|
||||
strconv.Itoa(tagIDs[tagIdx1WithImage]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
}
|
||||
|
||||
imageFilter := models.ImageFilterType{
|
||||
Tags: &tagCriterion,
|
||||
}
|
||||
|
||||
images := queryImages(ctx, t, sqb, &imageFilter, nil)
|
||||
assert.Len(t, images, 2)
|
||||
|
||||
// ensure ids are correct
|
||||
for _, image := range images {
|
||||
assert.True(t, image.ID == imageIDs[imageIdxWithTag] || image.ID == imageIDs[imageIdxWithTwoTags])
|
||||
}
|
||||
|
||||
tagCriterion = models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithImage]),
|
||||
strconv.Itoa(tagIDs[tagIdx2WithImage]),
|
||||
[]int{
|
||||
imageIdxWithTag,
|
||||
imageIdxWithTwoTags,
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludesAll,
|
||||
}
|
||||
|
||||
images = queryImages(ctx, t, sqb, &imageFilter, nil)
|
||||
assert.Len(t, images, 1)
|
||||
assert.Equal(t, imageIDs[imageIdxWithTwoTags], images[0].ID)
|
||||
|
||||
tagCriterion = models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithImage]),
|
||||
[]int{
|
||||
imageIdxWithGallery,
|
||||
},
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
}
|
||||
false,
|
||||
},
|
||||
{
|
||||
"includes all",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithImage]),
|
||||
strconv.Itoa(tagIDs[tagIdx2WithImage]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludesAll,
|
||||
},
|
||||
[]int{
|
||||
imageIdxWithTwoTags,
|
||||
},
|
||||
[]int{
|
||||
imageIdxWithTag,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"excludes",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
Value: []string{strconv.Itoa(tagIDs[tagIdx1WithImage])},
|
||||
},
|
||||
nil,
|
||||
[]int{imageIdxWithTwoTags},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"is null",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierIsNull,
|
||||
},
|
||||
[]int{imageIdx1WithPerformer},
|
||||
[]int{
|
||||
imageIdxWithTag,
|
||||
imageIdxWithTwoTags,
|
||||
imageIdxWithThreeTags,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not null",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierNotNull,
|
||||
},
|
||||
[]int{
|
||||
imageIdxWithTag,
|
||||
imageIdxWithTwoTags,
|
||||
imageIdxWithThreeTags,
|
||||
},
|
||||
[]int{imageIdx1WithPerformer},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"equals",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithImage]),
|
||||
strconv.Itoa(tagIDs[tagIdx2WithImage]),
|
||||
},
|
||||
},
|
||||
[]int{imageIdxWithTwoTags},
|
||||
[]int{
|
||||
imageIdxWithThreeTags,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not equals",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierNotEquals,
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithImage]),
|
||||
strconv.Itoa(tagIDs[tagIdx2WithImage]),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
q := getImageStringValue(imageIdxWithTwoTags, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
for _, tt := range tests {
|
||||
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
||||
assert := assert.New(t)
|
||||
|
||||
images = queryImages(ctx, t, sqb, &imageFilter, &findFilter)
|
||||
assert.Len(t, images, 0)
|
||||
results, err := db.Image.Query(ctx, models.ImageQueryOptions{
|
||||
ImageFilter: &models.ImageFilterType{
|
||||
Tags: &tt.filter,
|
||||
},
|
||||
})
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ImageStore.Query() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
tagCriterion = models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierIsNull,
|
||||
}
|
||||
q = getImageStringValue(imageIdxWithGallery, titleField)
|
||||
include := indexesToIDs(imageIDs, tt.includeIdxs)
|
||||
exclude := indexesToIDs(imageIDs, tt.excludeIdxs)
|
||||
|
||||
images = queryImages(ctx, t, sqb, &imageFilter, &findFilter)
|
||||
assert.Len(t, images, 1)
|
||||
assert.Equal(t, imageIDs[imageIdxWithGallery], images[0].ID)
|
||||
|
||||
q = getImageStringValue(imageIdxWithTag, titleField)
|
||||
images = queryImages(ctx, t, sqb, &imageFilter, &findFilter)
|
||||
assert.Len(t, images, 0)
|
||||
|
||||
tagCriterion.Modifier = models.CriterionModifierNotNull
|
||||
|
||||
images = queryImages(ctx, t, sqb, &imageFilter, &findFilter)
|
||||
assert.Len(t, images, 1)
|
||||
assert.Equal(t, imageIDs[imageIdxWithTag], images[0].ID)
|
||||
|
||||
q = getImageStringValue(imageIdxWithGallery, titleField)
|
||||
images = queryImages(ctx, t, sqb, &imageFilter, &findFilter)
|
||||
assert.Len(t, images, 0)
|
||||
|
||||
return nil
|
||||
})
|
||||
for _, i := range include {
|
||||
assert.Contains(results.IDs, i)
|
||||
}
|
||||
for _, e := range exclude {
|
||||
assert.NotContains(results.IDs, e)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageQueryStudio(t *testing.T) {
|
||||
withTxn(func(ctx context.Context) error {
|
||||
sqb := db.Image
|
||||
studioCriterion := models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(studioIDs[studioIdxWithImage]),
|
||||
tests := []struct {
|
||||
name string
|
||||
q string
|
||||
studioCriterion models.HierarchicalMultiCriterionInput
|
||||
expectedIDs []int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"includes",
|
||||
"",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(studioIDs[studioIdxWithImage]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
}
|
||||
|
||||
imageFilter := models.ImageFilterType{
|
||||
Studios: &studioCriterion,
|
||||
}
|
||||
|
||||
images, _, err := queryImagesWithCount(ctx, sqb, &imageFilter, nil)
|
||||
if err != nil {
|
||||
t.Errorf("Error querying image: %s", err.Error())
|
||||
}
|
||||
|
||||
assert.Len(t, images, 1)
|
||||
|
||||
// ensure id is correct
|
||||
assert.Equal(t, imageIDs[imageIdxWithStudio], images[0].ID)
|
||||
|
||||
studioCriterion = models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(studioIDs[studioIdxWithImage]),
|
||||
[]int{imageIDs[imageIdxWithStudio]},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"excludes",
|
||||
getImageStringValue(imageIdxWithStudio, titleField),
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(studioIDs[studioIdxWithImage]),
|
||||
},
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
},
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
}
|
||||
[]int{},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"excludes includes null",
|
||||
getImageStringValue(imageIdxWithGallery, titleField),
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(studioIDs[studioIdxWithImage]),
|
||||
},
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
},
|
||||
[]int{imageIDs[imageIdxWithGallery]},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"equals",
|
||||
"",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(studioIDs[studioIdxWithImage]),
|
||||
},
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
},
|
||||
[]int{imageIDs[imageIdxWithStudio]},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not equals",
|
||||
getImageStringValue(imageIdxWithStudio, titleField),
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(studioIDs[studioIdxWithImage]),
|
||||
},
|
||||
Modifier: models.CriterionModifierNotEquals,
|
||||
},
|
||||
[]int{},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
q := getImageStringValue(imageIdxWithStudio, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
qb := db.Image
|
||||
|
||||
images, _, err = queryImagesWithCount(ctx, sqb, &imageFilter, &findFilter)
|
||||
if err != nil {
|
||||
t.Errorf("Error querying image: %s", err.Error())
|
||||
}
|
||||
assert.Len(t, images, 0)
|
||||
for _, tt := range tests {
|
||||
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
||||
studioCriterion := tt.studioCriterion
|
||||
|
||||
return nil
|
||||
})
|
||||
imageFilter := models.ImageFilterType{
|
||||
Studios: &studioCriterion,
|
||||
}
|
||||
|
||||
var findFilter *models.FindFilterType
|
||||
if tt.q != "" {
|
||||
findFilter = &models.FindFilterType{
|
||||
Q: &tt.q,
|
||||
}
|
||||
}
|
||||
|
||||
images := queryImages(ctx, t, qb, &imageFilter, findFilter)
|
||||
|
||||
assert.ElementsMatch(t, imagesToIDs(images), tt.expectedIDs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageQueryStudioDepth(t *testing.T) {
|
||||
|
|
@ -2394,81 +2560,201 @@ func queryImages(ctx context.Context, t *testing.T, sqb models.ImageReader, imag
|
|||
}
|
||||
|
||||
func TestImageQueryPerformerTags(t *testing.T) {
|
||||
withTxn(func(ctx context.Context) error {
|
||||
sqb := db.Image
|
||||
tagCriterion := models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdxWithPerformer]),
|
||||
strconv.Itoa(tagIDs[tagIdx1WithPerformer]),
|
||||
allDepth := -1
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
findFilter *models.FindFilterType
|
||||
filter *models.ImageFilterType
|
||||
includeIdxs []int
|
||||
excludeIdxs []int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"includes",
|
||||
nil,
|
||||
&models.ImageFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdxWithPerformer]),
|
||||
strconv.Itoa(tagIDs[tagIdx1WithPerformer]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
},
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
}
|
||||
|
||||
imageFilter := models.ImageFilterType{
|
||||
PerformerTags: &tagCriterion,
|
||||
}
|
||||
|
||||
images := queryImages(ctx, t, sqb, &imageFilter, nil)
|
||||
assert.Len(t, images, 2)
|
||||
|
||||
// ensure ids are correct
|
||||
for _, image := range images {
|
||||
assert.True(t, image.ID == imageIDs[imageIdxWithPerformerTag] || image.ID == imageIDs[imageIdxWithPerformerTwoTags])
|
||||
}
|
||||
|
||||
tagCriterion = models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithPerformer]),
|
||||
strconv.Itoa(tagIDs[tagIdx2WithPerformer]),
|
||||
[]int{
|
||||
imageIdxWithPerformerTag,
|
||||
imageIdxWithPerformerTwoTags,
|
||||
imageIdxWithTwoPerformerTag,
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludesAll,
|
||||
}
|
||||
|
||||
images = queryImages(ctx, t, sqb, &imageFilter, nil)
|
||||
|
||||
assert.Len(t, images, 1)
|
||||
assert.Equal(t, imageIDs[imageIdxWithPerformerTwoTags], images[0].ID)
|
||||
|
||||
tagCriterion = models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithPerformer]),
|
||||
[]int{
|
||||
imageIdxWithPerformer,
|
||||
},
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
}
|
||||
false,
|
||||
},
|
||||
{
|
||||
"includes sub-tags",
|
||||
nil,
|
||||
&models.ImageFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdxWithParentAndChild]),
|
||||
},
|
||||
Depth: &allDepth,
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
},
|
||||
},
|
||||
[]int{
|
||||
imageIdxWithPerformerParentTag,
|
||||
},
|
||||
[]int{
|
||||
imageIdxWithPerformer,
|
||||
imageIdxWithPerformerTag,
|
||||
imageIdxWithPerformerTwoTags,
|
||||
imageIdxWithTwoPerformerTag,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"includes all",
|
||||
nil,
|
||||
&models.ImageFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithPerformer]),
|
||||
strconv.Itoa(tagIDs[tagIdx2WithPerformer]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludesAll,
|
||||
},
|
||||
},
|
||||
[]int{
|
||||
imageIdxWithPerformerTwoTags,
|
||||
},
|
||||
[]int{
|
||||
imageIdxWithPerformer,
|
||||
imageIdxWithPerformerTag,
|
||||
imageIdxWithTwoPerformerTag,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"excludes performer tag tagIdx2WithPerformer",
|
||||
nil,
|
||||
&models.ImageFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
Value: []string{strconv.Itoa(tagIDs[tagIdx2WithPerformer])},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
[]int{imageIdxWithTwoPerformerTag},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"excludes sub-tags",
|
||||
nil,
|
||||
&models.ImageFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdxWithParentAndChild]),
|
||||
},
|
||||
Depth: &allDepth,
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
},
|
||||
},
|
||||
[]int{
|
||||
imageIdxWithPerformer,
|
||||
imageIdxWithPerformerTag,
|
||||
imageIdxWithPerformerTwoTags,
|
||||
imageIdxWithTwoPerformerTag,
|
||||
},
|
||||
[]int{
|
||||
imageIdxWithPerformerParentTag,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"is null",
|
||||
nil,
|
||||
&models.ImageFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierIsNull,
|
||||
},
|
||||
},
|
||||
[]int{imageIdxWithGallery},
|
||||
[]int{imageIdxWithPerformerTag},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not null",
|
||||
nil,
|
||||
&models.ImageFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierNotNull,
|
||||
},
|
||||
},
|
||||
[]int{imageIdxWithPerformerTag},
|
||||
[]int{imageIdxWithGallery},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"equals",
|
||||
nil,
|
||||
&models.ImageFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx2WithPerformer]),
|
||||
},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"not equals",
|
||||
nil,
|
||||
&models.ImageFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierNotEquals,
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx2WithPerformer]),
|
||||
},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
q := getImageStringValue(imageIdxWithPerformerTwoTags, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
for _, tt := range tests {
|
||||
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
||||
assert := assert.New(t)
|
||||
|
||||
images = queryImages(ctx, t, sqb, &imageFilter, &findFilter)
|
||||
assert.Len(t, images, 0)
|
||||
results, err := db.Image.Query(ctx, models.ImageQueryOptions{
|
||||
ImageFilter: tt.filter,
|
||||
QueryOptions: models.QueryOptions{
|
||||
FindFilter: tt.findFilter,
|
||||
},
|
||||
})
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ImageStore.Query() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
tagCriterion = models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierIsNull,
|
||||
}
|
||||
q = getImageStringValue(imageIdxWithGallery, titleField)
|
||||
include := indexesToIDs(imageIDs, tt.includeIdxs)
|
||||
exclude := indexesToIDs(imageIDs, tt.excludeIdxs)
|
||||
|
||||
images = queryImages(ctx, t, sqb, &imageFilter, &findFilter)
|
||||
assert.Len(t, images, 1)
|
||||
assert.Equal(t, imageIDs[imageIdxWithGallery], images[0].ID)
|
||||
|
||||
q = getImageStringValue(imageIdxWithPerformerTag, titleField)
|
||||
images = queryImages(ctx, t, sqb, &imageFilter, &findFilter)
|
||||
assert.Len(t, images, 0)
|
||||
|
||||
tagCriterion.Modifier = models.CriterionModifierNotNull
|
||||
|
||||
images = queryImages(ctx, t, sqb, &imageFilter, &findFilter)
|
||||
assert.Len(t, images, 1)
|
||||
assert.Equal(t, imageIDs[imageIdxWithPerformerTag], images[0].ID)
|
||||
|
||||
q = getImageStringValue(imageIdxWithGallery, titleField)
|
||||
images = queryImages(ctx, t, sqb, &imageFilter, &findFilter)
|
||||
assert.Len(t, images, 0)
|
||||
|
||||
return nil
|
||||
})
|
||||
for _, i := range include {
|
||||
assert.Contains(results.IDs, i)
|
||||
}
|
||||
for _, e := range exclude {
|
||||
assert.NotContains(results.IDs, e)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageQueryTagCount(t *testing.T) {
|
||||
|
|
@ -2587,7 +2873,7 @@ func TestImageQuerySorting(t *testing.T) {
|
|||
"date",
|
||||
models.SortDirectionEnumDesc,
|
||||
imageIdxWithTwoGalleries,
|
||||
imageIdxWithGrandChildStudio,
|
||||
imageIdxWithPerformerParentTag,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ func (qb *movieQueryBuilder) makeFilter(ctx context.Context, movieFilter *models
|
|||
query.handleCriterion(ctx, floatIntCriterionHandler(movieFilter.Duration, "movies.duration", nil))
|
||||
query.handleCriterion(ctx, movieIsMissingCriterionHandler(qb, movieFilter.IsMissing))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(movieFilter.URL, "movies.url"))
|
||||
query.handleCriterion(ctx, movieStudioCriterionHandler(qb, movieFilter.Studios))
|
||||
query.handleCriterion(ctx, studioCriterionHandler(movieTable, movieFilter.Studios))
|
||||
query.handleCriterion(ctx, moviePerformersCriterionHandler(qb, movieFilter.Performers))
|
||||
query.handleCriterion(ctx, dateCriterionHandler(movieFilter.Date, "movies.date"))
|
||||
query.handleCriterion(ctx, timestampCriterionHandler(movieFilter.CreatedAt, "movies.created_at"))
|
||||
|
|
@ -239,19 +239,6 @@ func movieIsMissingCriterionHandler(qb *movieQueryBuilder, isMissing *string) cr
|
|||
}
|
||||
}
|
||||
|
||||
func movieStudioCriterionHandler(qb *movieQueryBuilder, studios *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
|
||||
h := hierarchicalMultiCriterionHandlerBuilder{
|
||||
tx: qb.tx,
|
||||
|
||||
primaryTable: movieTable,
|
||||
foreignTable: studioTable,
|
||||
foreignFK: studioIDColumn,
|
||||
parentFK: "parent_id",
|
||||
}
|
||||
|
||||
return h.handler(studios)
|
||||
}
|
||||
|
||||
func moviePerformersCriterionHandler(qb *movieQueryBuilder, performers *models.MultiCriterionInput) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if performers != nil {
|
||||
|
|
|
|||
|
|
@ -908,7 +908,11 @@ func performerStudiosCriterionHandler(qb *PerformerStore, studios *models.Hierar
|
|||
}
|
||||
|
||||
const derivedPerformerStudioTable = "performer_studio"
|
||||
valuesClause := getHierarchicalValues(ctx, qb.tx, studios.Value, studioTable, "", "parent_id", studios.Depth)
|
||||
valuesClause, err := getHierarchicalValues(ctx, qb.tx, 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}
|
||||
|
|
|
|||
|
|
@ -513,12 +513,13 @@ func Test_PerformerStore_UpdatePartial(t *testing.T) {
|
|||
performerIDs[performerIdxWithTwoTags],
|
||||
clearPerformerPartial(),
|
||||
models.Performer{
|
||||
ID: performerIDs[performerIdxWithTwoTags],
|
||||
Name: getPerformerStringValue(performerIdxWithTwoTags, "Name"),
|
||||
Favorite: true,
|
||||
Aliases: models.NewRelatedStrings([]string{}),
|
||||
TagIDs: models.NewRelatedIDs([]int{}),
|
||||
StashIDs: models.NewRelatedStashIDs([]models.StashID{}),
|
||||
ID: performerIDs[performerIdxWithTwoTags],
|
||||
Name: getPerformerStringValue(performerIdxWithTwoTags, "Name"),
|
||||
Favorite: getPerformerBoolValue(performerIdxWithTwoTags),
|
||||
Aliases: models.NewRelatedStrings([]string{}),
|
||||
TagIDs: models.NewRelatedIDs([]int{}),
|
||||
StashIDs: models.NewRelatedStashIDs([]models.StashID{}),
|
||||
IgnoreAutoTag: getIgnoreAutoTag(performerIdxWithTwoTags),
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
|
@ -1904,10 +1905,10 @@ func TestPerformerQuerySortScenesCount(t *testing.T) {
|
|||
|
||||
assert.True(t, len(performers) > 0)
|
||||
|
||||
// first performer should be performerIdxWithTwoScenes
|
||||
// first performer should be performerIdx1WithScene
|
||||
firstPerformer := performers[0]
|
||||
|
||||
assert.Equal(t, performerIDs[performerIdxWithTwoScenes], firstPerformer.ID)
|
||||
assert.Equal(t, performerIDs[performerIdx1WithScene], firstPerformer.ID)
|
||||
|
||||
// sort in ascending order
|
||||
direction = models.SortDirectionEnumAsc
|
||||
|
|
@ -1920,7 +1921,7 @@ func TestPerformerQuerySortScenesCount(t *testing.T) {
|
|||
assert.True(t, len(performers) > 0)
|
||||
lastPerformer := performers[len(performers)-1]
|
||||
|
||||
assert.Equal(t, performerIDs[performerIdxWithTwoScenes], lastPerformer.ID)
|
||||
assert.Equal(t, performerIDs[performerIdxWithTag], lastPerformer.ID)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
|
@ -2060,7 +2061,7 @@ func TestPerformerStore_FindByStashIDStatus(t *testing.T) {
|
|||
name: "!hasStashID",
|
||||
hasStashID: false,
|
||||
stashboxEndpoint: getPerformerStringValue(performerIdxWithScene, "endpoint"),
|
||||
include: []int{performerIdxWithImage},
|
||||
include: []int{performerIdxWithTwoScenes},
|
||||
exclude: []int{performerIdx2WithScene},
|
||||
wantErr: false,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -959,7 +959,7 @@ func (qb *SceneStore) makeFilter(ctx context.Context, sceneFilter *models.SceneF
|
|||
query.handleCriterion(ctx, sceneTagCountCriterionHandler(qb, sceneFilter.TagCount))
|
||||
query.handleCriterion(ctx, scenePerformersCriterionHandler(qb, sceneFilter.Performers))
|
||||
query.handleCriterion(ctx, scenePerformerCountCriterionHandler(qb, sceneFilter.PerformerCount))
|
||||
query.handleCriterion(ctx, sceneStudioCriterionHandler(qb, sceneFilter.Studios))
|
||||
query.handleCriterion(ctx, studioCriterionHandler(sceneTable, sceneFilter.Studios))
|
||||
query.handleCriterion(ctx, sceneMoviesCriterionHandler(qb, sceneFilter.Movies))
|
||||
query.handleCriterion(ctx, scenePerformerTagsCriterionHandler(qb, sceneFilter.PerformerTags))
|
||||
query.handleCriterion(ctx, scenePerformerFavoriteCriterionHandler(sceneFilter.PerformerFavorite))
|
||||
|
|
@ -1352,19 +1352,6 @@ func scenePerformerAgeCriterionHandler(performerAge *models.IntCriterionInput) c
|
|||
}
|
||||
}
|
||||
|
||||
func sceneStudioCriterionHandler(qb *SceneStore, studios *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
|
||||
h := hierarchicalMultiCriterionHandlerBuilder{
|
||||
tx: qb.tx,
|
||||
|
||||
primaryTable: sceneTable,
|
||||
foreignTable: studioTable,
|
||||
foreignFK: studioIDColumn,
|
||||
parentFK: "parent_id",
|
||||
}
|
||||
|
||||
return h.handler(studios)
|
||||
}
|
||||
|
||||
func sceneMoviesCriterionHandler(qb *SceneStore, movies *models.MultiCriterionInput) criterionHandlerFunc {
|
||||
addJoinsFunc := func(f *filterBuilder) {
|
||||
qb.moviesRepository().join(f, "", "scenes.id")
|
||||
|
|
@ -1374,38 +1361,12 @@ func sceneMoviesCriterionHandler(qb *SceneStore, movies *models.MultiCriterionIn
|
|||
return h.handler(movies)
|
||||
}
|
||||
|
||||
func scenePerformerTagsCriterionHandler(qb *SceneStore, tags *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if tags != nil {
|
||||
if tags.Modifier == models.CriterionModifierIsNull || tags.Modifier == models.CriterionModifierNotNull {
|
||||
var notClause string
|
||||
if tags.Modifier == models.CriterionModifierNotNull {
|
||||
notClause = "NOT"
|
||||
}
|
||||
|
||||
f.addLeftJoin("performers_scenes", "", "scenes.id = performers_scenes.scene_id")
|
||||
f.addLeftJoin("performers_tags", "", "performers_scenes.performer_id = performers_tags.performer_id")
|
||||
|
||||
f.addWhere(fmt.Sprintf("performers_tags.tag_id IS %s NULL", notClause))
|
||||
return
|
||||
}
|
||||
|
||||
if len(tags.Value) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
valuesClause := getHierarchicalValues(ctx, qb.tx, tags.Value, tagTable, "tags_relations", "", tags.Depth)
|
||||
|
||||
f.addWith(`performer_tags AS (
|
||||
SELECT ps.scene_id, t.column1 AS root_tag_id FROM performers_scenes ps
|
||||
INNER JOIN performers_tags pt ON pt.performer_id = ps.performer_id
|
||||
INNER JOIN (` + valuesClause + `) t ON t.column2 = pt.tag_id
|
||||
)`)
|
||||
|
||||
f.addLeftJoin("performer_tags", "", "performer_tags.scene_id = scenes.id")
|
||||
|
||||
addHierarchicalConditionClauses(f, *tags, "performer_tags", "root_tag_id")
|
||||
}
|
||||
func scenePerformerTagsCriterionHandler(qb *SceneStore, tags *models.HierarchicalMultiCriterionInput) criterionHandler {
|
||||
return &joinedPerformerTagsHandler{
|
||||
criterion: tags,
|
||||
primaryTable: sceneTable,
|
||||
joinTable: performersScenesTable,
|
||||
joinPrimaryKey: sceneIDColumn,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -209,7 +209,11 @@ func sceneMarkerTagsCriterionHandler(qb *sceneMarkerQueryBuilder, tags *models.H
|
|||
if len(tags.Value) == 0 {
|
||||
return
|
||||
}
|
||||
valuesClause := getHierarchicalValues(ctx, qb.tx, tags.Value, tagTable, "tags_relations", "", tags.Depth)
|
||||
valuesClause, err := getHierarchicalValues(ctx, qb.tx, tags.Value, tagTable, "tags_relations", "parent_id", "child_id", tags.Depth)
|
||||
if err != nil {
|
||||
f.setError(err)
|
||||
return
|
||||
}
|
||||
|
||||
f.addWith(`marker_tags AS (
|
||||
SELECT mt.scene_marker_id, t.column1 AS root_tag_id FROM scene_markers_tags mt
|
||||
|
|
@ -229,32 +233,23 @@ INNER JOIN (` + valuesClause + `) t ON t.column2 = m.primary_tag_id
|
|||
func sceneMarkerSceneTagsCriterionHandler(qb *sceneMarkerQueryBuilder, tags *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if tags != nil {
|
||||
if tags.Modifier == models.CriterionModifierIsNull || tags.Modifier == models.CriterionModifierNotNull {
|
||||
var notClause string
|
||||
if tags.Modifier == models.CriterionModifierNotNull {
|
||||
notClause = "NOT"
|
||||
}
|
||||
f.addLeftJoin("scenes_tags", "", "scene_markers.scene_id = scenes_tags.scene_id")
|
||||
|
||||
f.addLeftJoin("scenes_tags", "", "scene_markers.scene_id = scenes_tags.scene_id")
|
||||
h := joinedHierarchicalMultiCriterionHandlerBuilder{
|
||||
tx: qb.tx,
|
||||
|
||||
f.addWhere(fmt.Sprintf("scenes_tags.tag_id IS %s NULL", notClause))
|
||||
return
|
||||
primaryTable: "scene_markers",
|
||||
primaryKey: sceneIDColumn,
|
||||
foreignTable: tagTable,
|
||||
foreignFK: tagIDColumn,
|
||||
|
||||
relationsTable: "tags_relations",
|
||||
joinTable: "scenes_tags",
|
||||
joinAs: "marker_scenes_tags",
|
||||
primaryFK: sceneIDColumn,
|
||||
}
|
||||
|
||||
if len(tags.Value) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
valuesClause := getHierarchicalValues(ctx, qb.tx, tags.Value, tagTable, "tags_relations", "", tags.Depth)
|
||||
|
||||
f.addWith(`scene_tags AS (
|
||||
SELECT st.scene_id, t.column1 AS root_tag_id FROM scenes_tags st
|
||||
INNER JOIN (` + valuesClause + `) t ON t.column2 = st.tag_id
|
||||
)`)
|
||||
|
||||
f.addLeftJoin("scene_tags", "", "scene_tags.scene_id = scene_markers.scene_id")
|
||||
|
||||
addHierarchicalConditionClauses(f, *tags, "scene_tags", "root_tag_id")
|
||||
h.handler(tags).handle(ctx, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,12 @@ package sqlite_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||
"github.com/stashapp/stash/pkg/sqlite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
|
@ -50,7 +53,7 @@ func TestMarkerCountByTagID(t *testing.T) {
|
|||
t.Errorf("error calling CountByTagID: %s", err.Error())
|
||||
}
|
||||
|
||||
assert.Equal(t, 3, markerCount)
|
||||
assert.Equal(t, 4, markerCount)
|
||||
|
||||
markerCount, err = mqb.CountByTagID(ctx, tagIDs[tagIdxWithMarkers])
|
||||
|
||||
|
|
@ -151,7 +154,7 @@ func TestMarkerQuerySceneTags(t *testing.T) {
|
|||
}
|
||||
|
||||
withTxn(func(ctx context.Context) error {
|
||||
testTags := func(m *models.SceneMarker, markerFilter *models.SceneMarkerFilterType) {
|
||||
testTags := func(t *testing.T, m *models.SceneMarker, markerFilter *models.SceneMarkerFilterType) {
|
||||
s, err := db.Scene.Find(ctx, int(m.SceneID.Int64))
|
||||
if err != nil {
|
||||
t.Errorf("error getting marker tag ids: %v", err)
|
||||
|
|
@ -164,11 +167,40 @@ func TestMarkerQuerySceneTags(t *testing.T) {
|
|||
}
|
||||
|
||||
tagIDs := s.TagIDs.List()
|
||||
if markerFilter.SceneTags.Modifier == models.CriterionModifierIsNull && len(tagIDs) > 0 {
|
||||
t.Errorf("expected marker %d to have no scene tags - found %d", m.ID, len(tagIDs))
|
||||
}
|
||||
if markerFilter.SceneTags.Modifier == models.CriterionModifierNotNull && len(tagIDs) == 0 {
|
||||
t.Errorf("expected marker %d to have scene tags - found 0", m.ID)
|
||||
values, _ := stringslice.StringSliceToIntSlice(markerFilter.SceneTags.Value)
|
||||
switch markerFilter.SceneTags.Modifier {
|
||||
case models.CriterionModifierIsNull:
|
||||
if len(tagIDs) > 0 {
|
||||
t.Errorf("expected marker %d to have no scene tags - found %d", m.ID, len(tagIDs))
|
||||
}
|
||||
case models.CriterionModifierNotNull:
|
||||
if len(tagIDs) == 0 {
|
||||
t.Errorf("expected marker %d to have scene tags - found 0", m.ID)
|
||||
}
|
||||
case models.CriterionModifierIncludes:
|
||||
for _, v := range values {
|
||||
assert.Contains(t, tagIDs, v)
|
||||
}
|
||||
case models.CriterionModifierExcludes:
|
||||
for _, v := range values {
|
||||
assert.NotContains(t, tagIDs, v)
|
||||
}
|
||||
case models.CriterionModifierEquals:
|
||||
for _, v := range values {
|
||||
assert.Contains(t, tagIDs, v)
|
||||
}
|
||||
assert.Len(t, tagIDs, len(values))
|
||||
case models.CriterionModifierNotEquals:
|
||||
foundAll := true
|
||||
for _, v := range values {
|
||||
if !intslice.IntInclude(tagIDs, v) {
|
||||
foundAll = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if foundAll && len(tagIDs) == len(values) {
|
||||
t.Errorf("expected marker %d to have scene tags not equal to %v - found %v", m.ID, values, tagIDs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -191,6 +223,70 @@ func TestMarkerQuerySceneTags(t *testing.T) {
|
|||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"includes",
|
||||
&models.SceneMarkerFilterType{
|
||||
SceneTags: &models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx3WithScene]),
|
||||
},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"includes all",
|
||||
&models.SceneMarkerFilterType{
|
||||
SceneTags: &models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierIncludesAll,
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx2WithScene]),
|
||||
strconv.Itoa(tagIDs[tagIdx3WithScene]),
|
||||
},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"equals",
|
||||
&models.SceneMarkerFilterType{
|
||||
SceneTags: &models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx2WithScene]),
|
||||
strconv.Itoa(tagIDs[tagIdx3WithScene]),
|
||||
},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
// not equals not supported
|
||||
// {
|
||||
// "not equals",
|
||||
// &models.SceneMarkerFilterType{
|
||||
// SceneTags: &models.HierarchicalMultiCriterionInput{
|
||||
// Modifier: models.CriterionModifierNotEquals,
|
||||
// Value: []string{
|
||||
// strconv.Itoa(tagIDs[tagIdx2WithScene]),
|
||||
// strconv.Itoa(tagIDs[tagIdx3WithScene]),
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// nil,
|
||||
// },
|
||||
{
|
||||
"excludes",
|
||||
&models.SceneMarkerFilterType{
|
||||
SceneTags: &models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx2WithScene]),
|
||||
},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
|
@ -198,7 +294,7 @@ func TestMarkerQuerySceneTags(t *testing.T) {
|
|||
markers := queryMarkers(ctx, t, sqlite.SceneMarkerReaderWriter, tc.markerFilter, tc.findFilter)
|
||||
assert.Greater(t, len(markers), 0)
|
||||
for _, m := range markers {
|
||||
testTags(m, tc.markerFilter)
|
||||
testTags(t, m, tc.markerFilter)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -668,7 +668,8 @@ func Test_sceneQueryBuilder_UpdatePartial(t *testing.T) {
|
|||
sceneIDs[sceneIdxWithSpacedName],
|
||||
clearScenePartial(),
|
||||
models.Scene{
|
||||
ID: sceneIDs[sceneIdxWithSpacedName],
|
||||
ID: sceneIDs[sceneIdxWithSpacedName],
|
||||
OCounter: getOCounter(sceneIdxWithSpacedName),
|
||||
Files: models.NewRelatedVideoFiles([]*file.VideoFile{
|
||||
makeSceneFile(sceneIdxWithSpacedName),
|
||||
}),
|
||||
|
|
@ -677,6 +678,10 @@ func Test_sceneQueryBuilder_UpdatePartial(t *testing.T) {
|
|||
PerformerIDs: models.NewRelatedIDs([]int{}),
|
||||
Movies: models.NewRelatedMovies([]models.MoviesScenes{}),
|
||||
StashIDs: models.NewRelatedStashIDs([]models.StashID{}),
|
||||
PlayCount: getScenePlayCount(sceneIdxWithSpacedName),
|
||||
PlayDuration: getScenePlayDuration(sceneIdxWithSpacedName),
|
||||
LastPlayedAt: getSceneLastPlayed(sceneIdxWithSpacedName),
|
||||
ResumeTime: getSceneResumeTime(sceneIdxWithSpacedName),
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
|
@ -2101,6 +2106,8 @@ func sceneQueryQ(ctx context.Context, t *testing.T, sqb models.SceneReader, q st
|
|||
|
||||
// no Q should return all results
|
||||
filter.Q = nil
|
||||
pp := totalScenes
|
||||
filter.PerPage = &pp
|
||||
scenes = queryScene(ctx, t, sqb, nil, &filter)
|
||||
|
||||
assert.Len(t, scenes, totalScenes)
|
||||
|
|
@ -2230,8 +2237,8 @@ func TestSceneQuery(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
include := indexesToIDs(performerIDs, tt.includeIdxs)
|
||||
exclude := indexesToIDs(performerIDs, tt.excludeIdxs)
|
||||
include := indexesToIDs(sceneIDs, tt.includeIdxs)
|
||||
exclude := indexesToIDs(sceneIDs, tt.excludeIdxs)
|
||||
|
||||
for _, i := range include {
|
||||
assert.Contains(results.IDs, i)
|
||||
|
|
@ -3057,7 +3064,13 @@ func queryScenes(ctx context.Context, t *testing.T, queryBuilder models.SceneRea
|
|||
},
|
||||
}
|
||||
|
||||
return queryScene(ctx, t, queryBuilder, &sceneFilter, nil)
|
||||
// needed so that we don't hit the default limit of 25 scenes
|
||||
pp := 1000
|
||||
findFilter := &models.FindFilterType{
|
||||
PerPage: &pp,
|
||||
}
|
||||
|
||||
return queryScene(ctx, t, queryBuilder, &sceneFilter, findFilter)
|
||||
}
|
||||
|
||||
func createScene(ctx context.Context, width int, height int) (*models.Scene, error) {
|
||||
|
|
@ -3329,192 +3342,473 @@ func TestSceneQueryIsMissingPhash(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSceneQueryPerformers(t *testing.T) {
|
||||
withTxn(func(ctx context.Context) error {
|
||||
sqb := db.Scene
|
||||
performerCriterion := models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(performerIDs[performerIdxWithScene]),
|
||||
strconv.Itoa(performerIDs[performerIdx1WithScene]),
|
||||
tests := []struct {
|
||||
name string
|
||||
filter models.MultiCriterionInput
|
||||
includeIdxs []int
|
||||
excludeIdxs []int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"includes",
|
||||
models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(performerIDs[performerIdxWithScene]),
|
||||
strconv.Itoa(performerIDs[performerIdx1WithScene]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
}
|
||||
|
||||
sceneFilter := models.SceneFilterType{
|
||||
Performers: &performerCriterion,
|
||||
}
|
||||
|
||||
scenes := queryScene(ctx, t, sqb, &sceneFilter, nil)
|
||||
|
||||
assert.Len(t, scenes, 2)
|
||||
|
||||
// ensure ids are correct
|
||||
for _, scene := range scenes {
|
||||
assert.True(t, scene.ID == sceneIDs[sceneIdxWithPerformer] || scene.ID == sceneIDs[sceneIdxWithTwoPerformers])
|
||||
}
|
||||
|
||||
performerCriterion = models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(performerIDs[performerIdx1WithScene]),
|
||||
strconv.Itoa(performerIDs[performerIdx2WithScene]),
|
||||
[]int{
|
||||
sceneIdxWithPerformer,
|
||||
sceneIdxWithTwoPerformers,
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludesAll,
|
||||
}
|
||||
|
||||
scenes = queryScene(ctx, t, sqb, &sceneFilter, nil)
|
||||
|
||||
assert.Len(t, scenes, 1)
|
||||
assert.Equal(t, sceneIDs[sceneIdxWithTwoPerformers], scenes[0].ID)
|
||||
|
||||
performerCriterion = models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(performerIDs[performerIdx1WithScene]),
|
||||
[]int{
|
||||
sceneIdxWithGallery,
|
||||
},
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
}
|
||||
false,
|
||||
},
|
||||
{
|
||||
"includes all",
|
||||
models.MultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(performerIDs[performerIdx1WithScene]),
|
||||
strconv.Itoa(performerIDs[performerIdx2WithScene]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludesAll,
|
||||
},
|
||||
[]int{
|
||||
sceneIdxWithTwoPerformers,
|
||||
},
|
||||
[]int{
|
||||
sceneIdxWithPerformer,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"excludes",
|
||||
models.MultiCriterionInput{
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
Value: []string{strconv.Itoa(tagIDs[performerIdx1WithScene])},
|
||||
},
|
||||
nil,
|
||||
[]int{sceneIdxWithTwoPerformers},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"is null",
|
||||
models.MultiCriterionInput{
|
||||
Modifier: models.CriterionModifierIsNull,
|
||||
},
|
||||
[]int{sceneIdxWithTag},
|
||||
[]int{
|
||||
sceneIdxWithPerformer,
|
||||
sceneIdxWithTwoPerformers,
|
||||
sceneIdxWithPerformerTwoTags,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not null",
|
||||
models.MultiCriterionInput{
|
||||
Modifier: models.CriterionModifierNotNull,
|
||||
},
|
||||
[]int{
|
||||
sceneIdxWithPerformer,
|
||||
sceneIdxWithTwoPerformers,
|
||||
sceneIdxWithPerformerTwoTags,
|
||||
},
|
||||
[]int{sceneIdxWithTag},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"equals",
|
||||
models.MultiCriterionInput{
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[performerIdx1WithScene]),
|
||||
strconv.Itoa(tagIDs[performerIdx2WithScene]),
|
||||
},
|
||||
},
|
||||
[]int{sceneIdxWithTwoPerformers},
|
||||
[]int{
|
||||
sceneIdxWithThreePerformers,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not equals",
|
||||
models.MultiCriterionInput{
|
||||
Modifier: models.CriterionModifierNotEquals,
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[performerIdx1WithScene]),
|
||||
strconv.Itoa(tagIDs[performerIdx2WithScene]),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
q := getSceneStringValue(sceneIdxWithTwoPerformers, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
for _, tt := range tests {
|
||||
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
||||
assert := assert.New(t)
|
||||
|
||||
scenes = queryScene(ctx, t, sqb, &sceneFilter, &findFilter)
|
||||
assert.Len(t, scenes, 0)
|
||||
results, err := db.Scene.Query(ctx, models.SceneQueryOptions{
|
||||
SceneFilter: &models.SceneFilterType{
|
||||
Performers: &tt.filter,
|
||||
},
|
||||
})
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("SceneStore.Query() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
include := indexesToIDs(sceneIDs, tt.includeIdxs)
|
||||
exclude := indexesToIDs(sceneIDs, tt.excludeIdxs)
|
||||
|
||||
for _, i := range include {
|
||||
assert.Contains(results.IDs, i)
|
||||
}
|
||||
for _, e := range exclude {
|
||||
assert.NotContains(results.IDs, e)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSceneQueryTags(t *testing.T) {
|
||||
withTxn(func(ctx context.Context) error {
|
||||
sqb := db.Scene
|
||||
tagCriterion := models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdxWithScene]),
|
||||
strconv.Itoa(tagIDs[tagIdx1WithScene]),
|
||||
tests := []struct {
|
||||
name string
|
||||
filter models.HierarchicalMultiCriterionInput
|
||||
includeIdxs []int
|
||||
excludeIdxs []int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"includes",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdxWithScene]),
|
||||
strconv.Itoa(tagIDs[tagIdx1WithScene]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
}
|
||||
|
||||
sceneFilter := models.SceneFilterType{
|
||||
Tags: &tagCriterion,
|
||||
}
|
||||
|
||||
scenes := queryScene(ctx, t, sqb, &sceneFilter, nil)
|
||||
assert.Len(t, scenes, 2)
|
||||
|
||||
// ensure ids are correct
|
||||
for _, scene := range scenes {
|
||||
assert.True(t, scene.ID == sceneIDs[sceneIdxWithTag] || scene.ID == sceneIDs[sceneIdxWithTwoTags])
|
||||
}
|
||||
|
||||
tagCriterion = models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithScene]),
|
||||
strconv.Itoa(tagIDs[tagIdx2WithScene]),
|
||||
[]int{
|
||||
sceneIdxWithTag,
|
||||
sceneIdxWithTwoTags,
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludesAll,
|
||||
}
|
||||
|
||||
scenes = queryScene(ctx, t, sqb, &sceneFilter, nil)
|
||||
|
||||
assert.Len(t, scenes, 1)
|
||||
assert.Equal(t, sceneIDs[sceneIdxWithTwoTags], scenes[0].ID)
|
||||
|
||||
tagCriterion = models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithScene]),
|
||||
[]int{
|
||||
sceneIdxWithGallery,
|
||||
},
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
}
|
||||
false,
|
||||
},
|
||||
{
|
||||
"includes all",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithScene]),
|
||||
strconv.Itoa(tagIDs[tagIdx2WithScene]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludesAll,
|
||||
},
|
||||
[]int{
|
||||
sceneIdxWithTwoTags,
|
||||
},
|
||||
[]int{
|
||||
sceneIdxWithTag,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"excludes",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
Value: []string{strconv.Itoa(tagIDs[tagIdx1WithScene])},
|
||||
},
|
||||
nil,
|
||||
[]int{sceneIdxWithTwoTags},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"is null",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierIsNull,
|
||||
},
|
||||
[]int{sceneIdx1WithPerformer},
|
||||
[]int{
|
||||
sceneIdxWithTag,
|
||||
sceneIdxWithTwoTags,
|
||||
sceneIdxWithMarkerAndTag,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not null",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierNotNull,
|
||||
},
|
||||
[]int{
|
||||
sceneIdxWithTag,
|
||||
sceneIdxWithTwoTags,
|
||||
sceneIdxWithMarkerAndTag,
|
||||
},
|
||||
[]int{sceneIdx1WithPerformer},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"equals",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithScene]),
|
||||
strconv.Itoa(tagIDs[tagIdx2WithScene]),
|
||||
},
|
||||
},
|
||||
[]int{sceneIdxWithTwoTags},
|
||||
[]int{
|
||||
sceneIdxWithThreeTags,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not equals",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierNotEquals,
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithScene]),
|
||||
strconv.Itoa(tagIDs[tagIdx2WithScene]),
|
||||
},
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
q := getSceneStringValue(sceneIdxWithTwoTags, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
for _, tt := range tests {
|
||||
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
||||
assert := assert.New(t)
|
||||
|
||||
scenes = queryScene(ctx, t, sqb, &sceneFilter, &findFilter)
|
||||
assert.Len(t, scenes, 0)
|
||||
results, err := db.Scene.Query(ctx, models.SceneQueryOptions{
|
||||
SceneFilter: &models.SceneFilterType{
|
||||
Tags: &tt.filter,
|
||||
},
|
||||
})
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("SceneStore.Query() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
include := indexesToIDs(sceneIDs, tt.includeIdxs)
|
||||
exclude := indexesToIDs(sceneIDs, tt.excludeIdxs)
|
||||
|
||||
for _, i := range include {
|
||||
assert.Contains(results.IDs, i)
|
||||
}
|
||||
for _, e := range exclude {
|
||||
assert.NotContains(results.IDs, e)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSceneQueryPerformerTags(t *testing.T) {
|
||||
withTxn(func(ctx context.Context) error {
|
||||
sqb := db.Scene
|
||||
tagCriterion := models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdxWithPerformer]),
|
||||
strconv.Itoa(tagIDs[tagIdx1WithPerformer]),
|
||||
allDepth := -1
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
findFilter *models.FindFilterType
|
||||
filter *models.SceneFilterType
|
||||
includeIdxs []int
|
||||
excludeIdxs []int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"includes",
|
||||
nil,
|
||||
&models.SceneFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdxWithPerformer]),
|
||||
strconv.Itoa(tagIDs[tagIdx1WithPerformer]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
},
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
}
|
||||
|
||||
sceneFilter := models.SceneFilterType{
|
||||
PerformerTags: &tagCriterion,
|
||||
}
|
||||
|
||||
scenes := queryScene(ctx, t, sqb, &sceneFilter, nil)
|
||||
assert.Len(t, scenes, 2)
|
||||
|
||||
// ensure ids are correct
|
||||
for _, scene := range scenes {
|
||||
assert.True(t, scene.ID == sceneIDs[sceneIdxWithPerformerTag] || scene.ID == sceneIDs[sceneIdxWithPerformerTwoTags])
|
||||
}
|
||||
|
||||
tagCriterion = models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithPerformer]),
|
||||
strconv.Itoa(tagIDs[tagIdx2WithPerformer]),
|
||||
[]int{
|
||||
sceneIdxWithPerformerTag,
|
||||
sceneIdxWithPerformerTwoTags,
|
||||
sceneIdxWithTwoPerformerTag,
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludesAll,
|
||||
}
|
||||
|
||||
scenes = queryScene(ctx, t, sqb, &sceneFilter, nil)
|
||||
|
||||
assert.Len(t, scenes, 1)
|
||||
assert.Equal(t, sceneIDs[sceneIdxWithPerformerTwoTags], scenes[0].ID)
|
||||
|
||||
tagCriterion = models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithPerformer]),
|
||||
[]int{
|
||||
sceneIdxWithPerformer,
|
||||
},
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
}
|
||||
false,
|
||||
},
|
||||
{
|
||||
"includes sub-tags",
|
||||
nil,
|
||||
&models.SceneFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdxWithParentAndChild]),
|
||||
},
|
||||
Depth: &allDepth,
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
},
|
||||
},
|
||||
[]int{
|
||||
sceneIdxWithPerformerParentTag,
|
||||
},
|
||||
[]int{
|
||||
sceneIdxWithPerformer,
|
||||
sceneIdxWithPerformerTag,
|
||||
sceneIdxWithPerformerTwoTags,
|
||||
sceneIdxWithTwoPerformerTag,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"includes all",
|
||||
nil,
|
||||
&models.SceneFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx1WithPerformer]),
|
||||
strconv.Itoa(tagIDs[tagIdx2WithPerformer]),
|
||||
},
|
||||
Modifier: models.CriterionModifierIncludesAll,
|
||||
},
|
||||
},
|
||||
[]int{
|
||||
sceneIdxWithPerformerTwoTags,
|
||||
},
|
||||
[]int{
|
||||
sceneIdxWithPerformer,
|
||||
sceneIdxWithPerformerTag,
|
||||
sceneIdxWithTwoPerformerTag,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"excludes performer tag tagIdx2WithPerformer",
|
||||
nil,
|
||||
&models.SceneFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
Value: []string{strconv.Itoa(tagIDs[tagIdx2WithPerformer])},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
[]int{sceneIdxWithTwoPerformerTag},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"excludes sub-tags",
|
||||
nil,
|
||||
&models.SceneFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdxWithParentAndChild]),
|
||||
},
|
||||
Depth: &allDepth,
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
},
|
||||
},
|
||||
[]int{
|
||||
sceneIdxWithPerformer,
|
||||
sceneIdxWithPerformerTag,
|
||||
sceneIdxWithPerformerTwoTags,
|
||||
sceneIdxWithTwoPerformerTag,
|
||||
},
|
||||
[]int{
|
||||
sceneIdxWithPerformerParentTag,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"is null",
|
||||
nil,
|
||||
&models.SceneFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierIsNull,
|
||||
},
|
||||
},
|
||||
[]int{sceneIdx1WithPerformer},
|
||||
[]int{sceneIdxWithPerformerTag},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not null",
|
||||
nil,
|
||||
&models.SceneFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierNotNull,
|
||||
},
|
||||
},
|
||||
[]int{sceneIdxWithPerformerTag},
|
||||
[]int{sceneIdx1WithPerformer},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"equals",
|
||||
nil,
|
||||
&models.SceneFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx2WithPerformer]),
|
||||
},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"not equals",
|
||||
nil,
|
||||
&models.SceneFilterType{
|
||||
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierNotEquals,
|
||||
Value: []string{
|
||||
strconv.Itoa(tagIDs[tagIdx2WithPerformer]),
|
||||
},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
q := getSceneStringValue(sceneIdxWithPerformerTwoTags, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
for _, tt := range tests {
|
||||
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
||||
assert := assert.New(t)
|
||||
|
||||
scenes = queryScene(ctx, t, sqb, &sceneFilter, &findFilter)
|
||||
assert.Len(t, scenes, 0)
|
||||
results, err := db.Scene.Query(ctx, models.SceneQueryOptions{
|
||||
SceneFilter: tt.filter,
|
||||
QueryOptions: models.QueryOptions{
|
||||
FindFilter: tt.findFilter,
|
||||
},
|
||||
})
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("SceneStore.Query() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
tagCriterion = models.HierarchicalMultiCriterionInput{
|
||||
Modifier: models.CriterionModifierIsNull,
|
||||
}
|
||||
q = getSceneStringValue(sceneIdx1WithPerformer, titleField)
|
||||
include := indexesToIDs(sceneIDs, tt.includeIdxs)
|
||||
exclude := indexesToIDs(sceneIDs, tt.excludeIdxs)
|
||||
|
||||
scenes = queryScene(ctx, t, sqb, &sceneFilter, &findFilter)
|
||||
assert.Len(t, scenes, 1)
|
||||
assert.Equal(t, sceneIDs[sceneIdx1WithPerformer], scenes[0].ID)
|
||||
|
||||
q = getSceneStringValue(sceneIdxWithPerformerTag, titleField)
|
||||
scenes = queryScene(ctx, t, sqb, &sceneFilter, &findFilter)
|
||||
assert.Len(t, scenes, 0)
|
||||
|
||||
tagCriterion.Modifier = models.CriterionModifierNotNull
|
||||
|
||||
scenes = queryScene(ctx, t, sqb, &sceneFilter, &findFilter)
|
||||
assert.Len(t, scenes, 1)
|
||||
assert.Equal(t, sceneIDs[sceneIdxWithPerformerTag], scenes[0].ID)
|
||||
|
||||
q = getSceneStringValue(sceneIdx1WithPerformer, titleField)
|
||||
scenes = queryScene(ctx, t, sqb, &sceneFilter, &findFilter)
|
||||
assert.Len(t, scenes, 0)
|
||||
|
||||
return nil
|
||||
})
|
||||
for _, i := range include {
|
||||
assert.Contains(results.IDs, i)
|
||||
}
|
||||
for _, e := range exclude {
|
||||
assert.NotContains(results.IDs, e)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSceneQueryStudio(t *testing.T) {
|
||||
|
|
@ -3561,6 +3855,30 @@ func TestSceneQueryStudio(t *testing.T) {
|
|||
[]int{sceneIDs[sceneIdxWithGallery]},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"equals",
|
||||
"",
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(studioIDs[studioIdxWithScene]),
|
||||
},
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
},
|
||||
[]int{sceneIDs[sceneIdxWithStudio]},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not equals",
|
||||
getSceneStringValue(sceneIdxWithStudio, titleField),
|
||||
models.HierarchicalMultiCriterionInput{
|
||||
Value: []string{
|
||||
strconv.Itoa(studioIDs[studioIdxWithScene]),
|
||||
},
|
||||
Modifier: models.CriterionModifierNotEquals,
|
||||
},
|
||||
[]int{},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
qb := db.Scene
|
||||
|
|
|
|||
|
|
@ -60,19 +60,24 @@ const (
|
|||
sceneIdx1WithPerformer
|
||||
sceneIdx2WithPerformer
|
||||
sceneIdxWithTwoPerformers
|
||||
sceneIdxWithThreePerformers
|
||||
sceneIdxWithTag
|
||||
sceneIdxWithTwoTags
|
||||
sceneIdxWithThreeTags
|
||||
sceneIdxWithMarkerAndTag
|
||||
sceneIdxWithMarkerTwoTags
|
||||
sceneIdxWithStudio
|
||||
sceneIdx1WithStudio
|
||||
sceneIdx2WithStudio
|
||||
sceneIdxWithMarkers
|
||||
sceneIdxWithPerformerTag
|
||||
sceneIdxWithTwoPerformerTag
|
||||
sceneIdxWithPerformerTwoTags
|
||||
sceneIdxWithSpacedName
|
||||
sceneIdxWithStudioPerformer
|
||||
sceneIdxWithGrandChildStudio
|
||||
sceneIdxMissingPhash
|
||||
sceneIdxWithPerformerParentTag
|
||||
// new indexes above
|
||||
lastSceneIdx
|
||||
|
||||
|
|
@ -90,16 +95,20 @@ const (
|
|||
imageIdx1WithPerformer
|
||||
imageIdx2WithPerformer
|
||||
imageIdxWithTwoPerformers
|
||||
imageIdxWithThreePerformers
|
||||
imageIdxWithTag
|
||||
imageIdxWithTwoTags
|
||||
imageIdxWithThreeTags
|
||||
imageIdxWithStudio
|
||||
imageIdx1WithStudio
|
||||
imageIdx2WithStudio
|
||||
imageIdxWithStudioPerformer
|
||||
imageIdxInZip
|
||||
imageIdxWithPerformerTag
|
||||
imageIdxWithTwoPerformerTag
|
||||
imageIdxWithPerformerTwoTags
|
||||
imageIdxWithGrandChildStudio
|
||||
imageIdxWithPerformerParentTag
|
||||
// new indexes above
|
||||
totalImages
|
||||
)
|
||||
|
|
@ -108,20 +117,25 @@ const (
|
|||
performerIdxWithScene = iota
|
||||
performerIdx1WithScene
|
||||
performerIdx2WithScene
|
||||
performerIdx3WithScene
|
||||
performerIdxWithTwoScenes
|
||||
performerIdxWithImage
|
||||
performerIdxWithTwoImages
|
||||
performerIdx1WithImage
|
||||
performerIdx2WithImage
|
||||
performerIdx3WithImage
|
||||
performerIdxWithTag
|
||||
performerIdx2WithTag
|
||||
performerIdxWithTwoTags
|
||||
performerIdxWithGallery
|
||||
performerIdxWithTwoGalleries
|
||||
performerIdx1WithGallery
|
||||
performerIdx2WithGallery
|
||||
performerIdx3WithGallery
|
||||
performerIdxWithSceneStudio
|
||||
performerIdxWithImageStudio
|
||||
performerIdxWithGalleryStudio
|
||||
performerIdxWithParentTag
|
||||
// new indexes above
|
||||
// performers with dup names start from the end
|
||||
performerIdx1WithDupName
|
||||
|
|
@ -155,16 +169,20 @@ const (
|
|||
galleryIdx1WithPerformer
|
||||
galleryIdx2WithPerformer
|
||||
galleryIdxWithTwoPerformers
|
||||
galleryIdxWithThreePerformers
|
||||
galleryIdxWithTag
|
||||
galleryIdxWithTwoTags
|
||||
galleryIdxWithThreeTags
|
||||
galleryIdxWithStudio
|
||||
galleryIdx1WithStudio
|
||||
galleryIdx2WithStudio
|
||||
galleryIdxWithPerformerTag
|
||||
galleryIdxWithTwoPerformerTag
|
||||
galleryIdxWithPerformerTwoTags
|
||||
galleryIdxWithStudioPerformer
|
||||
galleryIdxWithGrandChildStudio
|
||||
galleryIdxWithoutFile
|
||||
galleryIdxWithPerformerParentTag
|
||||
// new indexes above
|
||||
lastGalleryIdx
|
||||
|
||||
|
|
@ -182,12 +200,14 @@ const (
|
|||
tagIdxWithImage
|
||||
tagIdx1WithImage
|
||||
tagIdx2WithImage
|
||||
tagIdx3WithImage
|
||||
tagIdxWithPerformer
|
||||
tagIdx1WithPerformer
|
||||
tagIdx2WithPerformer
|
||||
tagIdxWithGallery
|
||||
tagIdx1WithGallery
|
||||
tagIdx2WithGallery
|
||||
tagIdx3WithGallery
|
||||
tagIdxWithChildTag
|
||||
tagIdxWithParentTag
|
||||
tagIdxWithGrandChild
|
||||
|
|
@ -332,19 +352,24 @@ var (
|
|||
|
||||
var (
|
||||
sceneTags = linkMap{
|
||||
sceneIdxWithTag: {tagIdxWithScene},
|
||||
sceneIdxWithTwoTags: {tagIdx1WithScene, tagIdx2WithScene},
|
||||
sceneIdxWithMarkerAndTag: {tagIdx3WithScene},
|
||||
sceneIdxWithTag: {tagIdxWithScene},
|
||||
sceneIdxWithTwoTags: {tagIdx1WithScene, tagIdx2WithScene},
|
||||
sceneIdxWithThreeTags: {tagIdx1WithScene, tagIdx2WithScene, tagIdx3WithScene},
|
||||
sceneIdxWithMarkerAndTag: {tagIdx3WithScene},
|
||||
sceneIdxWithMarkerTwoTags: {tagIdx2WithScene, tagIdx3WithScene},
|
||||
}
|
||||
|
||||
scenePerformers = linkMap{
|
||||
sceneIdxWithPerformer: {performerIdxWithScene},
|
||||
sceneIdxWithTwoPerformers: {performerIdx1WithScene, performerIdx2WithScene},
|
||||
sceneIdxWithPerformerTag: {performerIdxWithTag},
|
||||
sceneIdxWithPerformerTwoTags: {performerIdxWithTwoTags},
|
||||
sceneIdx1WithPerformer: {performerIdxWithTwoScenes},
|
||||
sceneIdx2WithPerformer: {performerIdxWithTwoScenes},
|
||||
sceneIdxWithStudioPerformer: {performerIdxWithSceneStudio},
|
||||
sceneIdxWithPerformer: {performerIdxWithScene},
|
||||
sceneIdxWithTwoPerformers: {performerIdx1WithScene, performerIdx2WithScene},
|
||||
sceneIdxWithThreePerformers: {performerIdx1WithScene, performerIdx2WithScene, performerIdx3WithScene},
|
||||
sceneIdxWithPerformerTag: {performerIdxWithTag},
|
||||
sceneIdxWithTwoPerformerTag: {performerIdxWithTag, performerIdx2WithTag},
|
||||
sceneIdxWithPerformerTwoTags: {performerIdxWithTwoTags},
|
||||
sceneIdx1WithPerformer: {performerIdxWithTwoScenes},
|
||||
sceneIdx2WithPerformer: {performerIdxWithTwoScenes},
|
||||
sceneIdxWithStudioPerformer: {performerIdxWithSceneStudio},
|
||||
sceneIdxWithPerformerParentTag: {performerIdxWithParentTag},
|
||||
}
|
||||
|
||||
sceneGalleries = linkMap{
|
||||
|
|
@ -376,6 +401,7 @@ var (
|
|||
{sceneIdxWithMarkers, tagIdxWithPrimaryMarkers, nil},
|
||||
{sceneIdxWithMarkers, tagIdxWithPrimaryMarkers, []int{tagIdxWithMarkers}},
|
||||
{sceneIdxWithMarkerAndTag, tagIdxWithPrimaryMarkers, nil},
|
||||
{sceneIdxWithMarkerTwoTags, tagIdxWithPrimaryMarkers, nil},
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -407,29 +433,36 @@ var (
|
|||
imageIdxWithGrandChildStudio: studioIdxWithGrandParent,
|
||||
}
|
||||
imageTags = linkMap{
|
||||
imageIdxWithTag: {tagIdxWithImage},
|
||||
imageIdxWithTwoTags: {tagIdx1WithImage, tagIdx2WithImage},
|
||||
imageIdxWithTag: {tagIdxWithImage},
|
||||
imageIdxWithTwoTags: {tagIdx1WithImage, tagIdx2WithImage},
|
||||
imageIdxWithThreeTags: {tagIdx1WithImage, tagIdx2WithImage, tagIdx3WithImage},
|
||||
}
|
||||
imagePerformers = linkMap{
|
||||
imageIdxWithPerformer: {performerIdxWithImage},
|
||||
imageIdxWithTwoPerformers: {performerIdx1WithImage, performerIdx2WithImage},
|
||||
imageIdxWithPerformerTag: {performerIdxWithTag},
|
||||
imageIdxWithPerformerTwoTags: {performerIdxWithTwoTags},
|
||||
imageIdx1WithPerformer: {performerIdxWithTwoImages},
|
||||
imageIdx2WithPerformer: {performerIdxWithTwoImages},
|
||||
imageIdxWithStudioPerformer: {performerIdxWithImageStudio},
|
||||
imageIdxWithPerformer: {performerIdxWithImage},
|
||||
imageIdxWithTwoPerformers: {performerIdx1WithImage, performerIdx2WithImage},
|
||||
imageIdxWithThreePerformers: {performerIdx1WithImage, performerIdx2WithImage, performerIdx3WithImage},
|
||||
imageIdxWithPerformerTag: {performerIdxWithTag},
|
||||
imageIdxWithTwoPerformerTag: {performerIdxWithTag, performerIdx2WithTag},
|
||||
imageIdxWithPerformerTwoTags: {performerIdxWithTwoTags},
|
||||
imageIdx1WithPerformer: {performerIdxWithTwoImages},
|
||||
imageIdx2WithPerformer: {performerIdxWithTwoImages},
|
||||
imageIdxWithStudioPerformer: {performerIdxWithImageStudio},
|
||||
imageIdxWithPerformerParentTag: {performerIdxWithParentTag},
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
galleryPerformers = linkMap{
|
||||
galleryIdxWithPerformer: {performerIdxWithGallery},
|
||||
galleryIdxWithTwoPerformers: {performerIdx1WithGallery, performerIdx2WithGallery},
|
||||
galleryIdxWithPerformerTag: {performerIdxWithTag},
|
||||
galleryIdxWithPerformerTwoTags: {performerIdxWithTwoTags},
|
||||
galleryIdx1WithPerformer: {performerIdxWithTwoGalleries},
|
||||
galleryIdx2WithPerformer: {performerIdxWithTwoGalleries},
|
||||
galleryIdxWithStudioPerformer: {performerIdxWithGalleryStudio},
|
||||
galleryIdxWithPerformer: {performerIdxWithGallery},
|
||||
galleryIdxWithTwoPerformers: {performerIdx1WithGallery, performerIdx2WithGallery},
|
||||
galleryIdxWithThreePerformers: {performerIdx1WithGallery, performerIdx2WithGallery, performerIdx3WithGallery},
|
||||
galleryIdxWithPerformerTag: {performerIdxWithTag},
|
||||
galleryIdxWithTwoPerformerTag: {performerIdxWithTag, performerIdx2WithTag},
|
||||
galleryIdxWithPerformerTwoTags: {performerIdxWithTwoTags},
|
||||
galleryIdx1WithPerformer: {performerIdxWithTwoGalleries},
|
||||
galleryIdx2WithPerformer: {performerIdxWithTwoGalleries},
|
||||
galleryIdxWithStudioPerformer: {performerIdxWithGalleryStudio},
|
||||
galleryIdxWithPerformerParentTag: {performerIdxWithParentTag},
|
||||
}
|
||||
|
||||
galleryStudios = map[int]int{
|
||||
|
|
@ -441,8 +474,9 @@ var (
|
|||
}
|
||||
|
||||
galleryTags = linkMap{
|
||||
galleryIdxWithTag: {tagIdxWithGallery},
|
||||
galleryIdxWithTwoTags: {tagIdx1WithGallery, tagIdx2WithGallery},
|
||||
galleryIdxWithTag: {tagIdxWithGallery},
|
||||
galleryIdxWithTwoTags: {tagIdx1WithGallery, tagIdx2WithGallery},
|
||||
galleryIdxWithThreeTags: {tagIdx1WithGallery, tagIdx2WithGallery, tagIdx3WithGallery},
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -462,8 +496,10 @@ var (
|
|||
|
||||
var (
|
||||
performerTags = linkMap{
|
||||
performerIdxWithTag: {tagIdxWithPerformer},
|
||||
performerIdxWithTwoTags: {tagIdx1WithPerformer, tagIdx2WithPerformer},
|
||||
performerIdxWithTag: {tagIdxWithPerformer},
|
||||
performerIdx2WithTag: {tagIdx2WithPerformer},
|
||||
performerIdxWithTwoTags: {tagIdx1WithPerformer, tagIdx2WithPerformer},
|
||||
performerIdxWithParentTag: {tagIdxWithParentAndChild},
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -484,6 +520,16 @@ func indexesToIDs(ids []int, indexes []int) []int {
|
|||
return ret
|
||||
}
|
||||
|
||||
func indexFromID(ids []int, id int) int {
|
||||
for i, v := range ids {
|
||||
if v == id {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
var db *sqlite.Database
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
|
@ -1431,11 +1477,8 @@ func getTagStringValue(index int, field string) string {
|
|||
}
|
||||
|
||||
func getTagSceneCount(id int) int {
|
||||
if id == tagIDs[tagIdx1WithScene] || id == tagIDs[tagIdx2WithScene] || id == tagIDs[tagIdxWithScene] || id == tagIDs[tagIdx3WithScene] {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
idx := indexFromID(tagIDs, id)
|
||||
return len(sceneTags.reverseLookup(idx))
|
||||
}
|
||||
|
||||
func getTagMarkerCount(id int) int {
|
||||
|
|
@ -1451,27 +1494,18 @@ func getTagMarkerCount(id int) int {
|
|||
}
|
||||
|
||||
func getTagImageCount(id int) int {
|
||||
if id == tagIDs[tagIdx1WithImage] || id == tagIDs[tagIdx2WithImage] || id == tagIDs[tagIdxWithImage] {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
idx := indexFromID(tagIDs, id)
|
||||
return len(imageTags.reverseLookup(idx))
|
||||
}
|
||||
|
||||
func getTagGalleryCount(id int) int {
|
||||
if id == tagIDs[tagIdx1WithGallery] || id == tagIDs[tagIdx2WithGallery] || id == tagIDs[tagIdxWithGallery] {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
idx := indexFromID(tagIDs, id)
|
||||
return len(galleryTags.reverseLookup(idx))
|
||||
}
|
||||
|
||||
func getTagPerformerCount(id int) int {
|
||||
if id == tagIDs[tagIdx1WithPerformer] || id == tagIDs[tagIdx2WithPerformer] || id == tagIDs[tagIdxWithPerformer] {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
idx := indexFromID(tagIDs, id)
|
||||
return len(performerTags.reverseLookup(idx))
|
||||
}
|
||||
|
||||
func getTagParentCount(id int) int {
|
||||
|
|
|
|||
|
|
@ -474,9 +474,19 @@ func tagMarkerCountCriterionHandler(qb *tagQueryBuilder, markerCount *models.Int
|
|||
}
|
||||
}
|
||||
|
||||
func tagParentsCriterionHandler(qb *tagQueryBuilder, tags *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
|
||||
func tagParentsCriterionHandler(qb *tagQueryBuilder, criterion *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if tags != nil {
|
||||
if criterion != nil {
|
||||
tags := criterion.CombineExcludes()
|
||||
|
||||
// validate the modifier
|
||||
switch tags.Modifier {
|
||||
case models.CriterionModifierIncludesAll, models.CriterionModifierIncludes, models.CriterionModifierExcludes, models.CriterionModifierIsNull, models.CriterionModifierNotNull:
|
||||
// valid
|
||||
default:
|
||||
f.setError(fmt.Errorf("invalid modifier %s for tag parent/children", criterion.Modifier))
|
||||
}
|
||||
|
||||
if tags.Modifier == models.CriterionModifierIsNull || tags.Modifier == models.CriterionModifierNotNull {
|
||||
var notClause string
|
||||
if tags.Modifier == models.CriterionModifierNotNull {
|
||||
|
|
@ -489,43 +499,88 @@ func tagParentsCriterionHandler(qb *tagQueryBuilder, tags *models.HierarchicalMu
|
|||
return
|
||||
}
|
||||
|
||||
if len(tags.Value) == 0 {
|
||||
if len(tags.Value) == 0 && len(tags.Excludes) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var args []interface{}
|
||||
for _, val := range tags.Value {
|
||||
args = append(args, val)
|
||||
if len(tags.Value) > 0 {
|
||||
var args []interface{}
|
||||
for _, val := range tags.Value {
|
||||
args = append(args, val)
|
||||
}
|
||||
|
||||
depthVal := 0
|
||||
if tags.Depth != nil {
|
||||
depthVal = *tags.Depth
|
||||
}
|
||||
|
||||
var depthCondition string
|
||||
if depthVal != -1 {
|
||||
depthCondition = fmt.Sprintf("WHERE depth < %d", depthVal)
|
||||
}
|
||||
|
||||
query := `parents AS (
|
||||
SELECT parent_id AS root_id, child_id AS item_id, 0 AS depth FROM tags_relations WHERE parent_id IN` + getInBinding(len(tags.Value)) + `
|
||||
UNION
|
||||
SELECT root_id, child_id, depth + 1 FROM tags_relations INNER JOIN parents ON item_id = parent_id ` + depthCondition + `
|
||||
)`
|
||||
|
||||
f.addRecursiveWith(query, args...)
|
||||
|
||||
f.addLeftJoin("parents", "", "parents.item_id = tags.id")
|
||||
|
||||
addHierarchicalConditionClauses(f, tags, "parents", "root_id")
|
||||
}
|
||||
|
||||
depthVal := 0
|
||||
if tags.Depth != nil {
|
||||
depthVal = *tags.Depth
|
||||
if len(tags.Excludes) > 0 {
|
||||
var args []interface{}
|
||||
for _, val := range tags.Excludes {
|
||||
args = append(args, val)
|
||||
}
|
||||
|
||||
depthVal := 0
|
||||
if tags.Depth != nil {
|
||||
depthVal = *tags.Depth
|
||||
}
|
||||
|
||||
var depthCondition string
|
||||
if depthVal != -1 {
|
||||
depthCondition = fmt.Sprintf("WHERE depth < %d", depthVal)
|
||||
}
|
||||
|
||||
query := `parents2 AS (
|
||||
SELECT parent_id AS root_id, child_id AS item_id, 0 AS depth FROM tags_relations WHERE parent_id IN` + getInBinding(len(tags.Excludes)) + `
|
||||
UNION
|
||||
SELECT root_id, child_id, depth + 1 FROM tags_relations INNER JOIN parents2 ON item_id = parent_id ` + depthCondition + `
|
||||
)`
|
||||
|
||||
f.addRecursiveWith(query, args...)
|
||||
|
||||
f.addLeftJoin("parents2", "", "parents2.item_id = tags.id")
|
||||
|
||||
addHierarchicalConditionClauses(f, models.HierarchicalMultiCriterionInput{
|
||||
Value: tags.Excludes,
|
||||
Depth: tags.Depth,
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
}, "parents2", "root_id")
|
||||
}
|
||||
|
||||
var depthCondition string
|
||||
if depthVal != -1 {
|
||||
depthCondition = fmt.Sprintf("WHERE depth < %d", depthVal)
|
||||
}
|
||||
|
||||
query := `parents AS (
|
||||
SELECT parent_id AS root_id, child_id AS item_id, 0 AS depth FROM tags_relations WHERE parent_id IN` + getInBinding(len(tags.Value)) + `
|
||||
UNION
|
||||
SELECT root_id, child_id, depth + 1 FROM tags_relations INNER JOIN parents ON item_id = parent_id ` + depthCondition + `
|
||||
)`
|
||||
|
||||
f.addRecursiveWith(query, args...)
|
||||
|
||||
f.addLeftJoin("parents", "", "parents.item_id = tags.id")
|
||||
|
||||
addHierarchicalConditionClauses(f, *tags, "parents", "root_id")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tagChildrenCriterionHandler(qb *tagQueryBuilder, tags *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
|
||||
func tagChildrenCriterionHandler(qb *tagQueryBuilder, criterion *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if tags != nil {
|
||||
if criterion != nil {
|
||||
tags := criterion.CombineExcludes()
|
||||
|
||||
// validate the modifier
|
||||
switch tags.Modifier {
|
||||
case models.CriterionModifierIncludesAll, models.CriterionModifierIncludes, models.CriterionModifierExcludes, models.CriterionModifierIsNull, models.CriterionModifierNotNull:
|
||||
// valid
|
||||
default:
|
||||
f.setError(fmt.Errorf("invalid modifier %s for tag parent/children", criterion.Modifier))
|
||||
}
|
||||
|
||||
if tags.Modifier == models.CriterionModifierIsNull || tags.Modifier == models.CriterionModifierNotNull {
|
||||
var notClause string
|
||||
if tags.Modifier == models.CriterionModifierNotNull {
|
||||
|
|
@ -538,36 +593,71 @@ func tagChildrenCriterionHandler(qb *tagQueryBuilder, tags *models.HierarchicalM
|
|||
return
|
||||
}
|
||||
|
||||
if len(tags.Value) == 0 {
|
||||
if len(tags.Value) == 0 && len(tags.Excludes) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var args []interface{}
|
||||
for _, val := range tags.Value {
|
||||
args = append(args, val)
|
||||
if len(tags.Value) > 0 {
|
||||
var args []interface{}
|
||||
for _, val := range tags.Value {
|
||||
args = append(args, val)
|
||||
}
|
||||
|
||||
depthVal := 0
|
||||
if tags.Depth != nil {
|
||||
depthVal = *tags.Depth
|
||||
}
|
||||
|
||||
var depthCondition string
|
||||
if depthVal != -1 {
|
||||
depthCondition = fmt.Sprintf("WHERE depth < %d", depthVal)
|
||||
}
|
||||
|
||||
query := `children AS (
|
||||
SELECT child_id AS root_id, parent_id AS item_id, 0 AS depth FROM tags_relations WHERE child_id IN` + getInBinding(len(tags.Value)) + `
|
||||
UNION
|
||||
SELECT root_id, parent_id, depth + 1 FROM tags_relations INNER JOIN children ON item_id = child_id ` + depthCondition + `
|
||||
)`
|
||||
|
||||
f.addRecursiveWith(query, args...)
|
||||
|
||||
f.addLeftJoin("children", "", "children.item_id = tags.id")
|
||||
|
||||
addHierarchicalConditionClauses(f, tags, "children", "root_id")
|
||||
}
|
||||
|
||||
depthVal := 0
|
||||
if tags.Depth != nil {
|
||||
depthVal = *tags.Depth
|
||||
if len(tags.Excludes) > 0 {
|
||||
var args []interface{}
|
||||
for _, val := range tags.Excludes {
|
||||
args = append(args, val)
|
||||
}
|
||||
|
||||
depthVal := 0
|
||||
if tags.Depth != nil {
|
||||
depthVal = *tags.Depth
|
||||
}
|
||||
|
||||
var depthCondition string
|
||||
if depthVal != -1 {
|
||||
depthCondition = fmt.Sprintf("WHERE depth < %d", depthVal)
|
||||
}
|
||||
|
||||
query := `children2 AS (
|
||||
SELECT child_id AS root_id, parent_id AS item_id, 0 AS depth FROM tags_relations WHERE child_id IN` + getInBinding(len(tags.Excludes)) + `
|
||||
UNION
|
||||
SELECT root_id, parent_id, depth + 1 FROM tags_relations INNER JOIN children2 ON item_id = child_id ` + depthCondition + `
|
||||
)`
|
||||
|
||||
f.addRecursiveWith(query, args...)
|
||||
|
||||
f.addLeftJoin("children2", "", "children2.item_id = tags.id")
|
||||
|
||||
addHierarchicalConditionClauses(f, models.HierarchicalMultiCriterionInput{
|
||||
Value: tags.Excludes,
|
||||
Depth: tags.Depth,
|
||||
Modifier: models.CriterionModifierExcludes,
|
||||
}, "children2", "root_id")
|
||||
}
|
||||
|
||||
var depthCondition string
|
||||
if depthVal != -1 {
|
||||
depthCondition = fmt.Sprintf("WHERE depth < %d", depthVal)
|
||||
}
|
||||
|
||||
query := `children AS (
|
||||
SELECT child_id AS root_id, parent_id AS item_id, 0 AS depth FROM tags_relations WHERE child_id IN` + getInBinding(len(tags.Value)) + `
|
||||
UNION
|
||||
SELECT root_id, parent_id, depth + 1 FROM tags_relations INNER JOIN children ON item_id = child_id ` + depthCondition + `
|
||||
)`
|
||||
|
||||
f.addRecursiveWith(query, args...)
|
||||
|
||||
f.addLeftJoin("children", "", "children.item_id = tags.id")
|
||||
|
||||
addHierarchicalConditionClauses(f, *tags, "children", "root_id")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ func TestTagQuerySort(t *testing.T) {
|
|||
|
||||
tags := queryTags(ctx, t, sqb, nil, findFilter)
|
||||
assert := assert.New(t)
|
||||
assert.Equal(tagIDs[tagIdxWithScene], tags[0].ID)
|
||||
assert.Equal(tagIDs[tagIdx2WithScene], tags[0].ID)
|
||||
|
||||
sortBy = "scene_markers_count"
|
||||
tags = queryTags(ctx, t, sqb, nil, findFilter)
|
||||
|
|
@ -195,15 +195,15 @@ func TestTagQuerySort(t *testing.T) {
|
|||
|
||||
sortBy = "images_count"
|
||||
tags = queryTags(ctx, t, sqb, nil, findFilter)
|
||||
assert.Equal(tagIDs[tagIdxWithImage], tags[0].ID)
|
||||
assert.Equal(tagIDs[tagIdx1WithImage], tags[0].ID)
|
||||
|
||||
sortBy = "galleries_count"
|
||||
tags = queryTags(ctx, t, sqb, nil, findFilter)
|
||||
assert.Equal(tagIDs[tagIdxWithGallery], tags[0].ID)
|
||||
assert.Equal(tagIDs[tagIdx1WithGallery], tags[0].ID)
|
||||
|
||||
sortBy = "performers_count"
|
||||
tags = queryTags(ctx, t, sqb, nil, findFilter)
|
||||
assert.Equal(tagIDs[tagIdxWithPerformer], tags[0].ID)
|
||||
assert.Equal(tagIDs[tagIdx2WithPerformer], tags[0].ID)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
|
|
|||
|
|
@ -309,14 +309,18 @@ export const HierarchicalObjectsFilter = <
|
|||
|
||||
return (
|
||||
<Form>
|
||||
<Form.Group>
|
||||
<Form.Check
|
||||
id={criterionOptionTypeToIncludeID()}
|
||||
checked={criterion.value.depth !== 0}
|
||||
label={intl.formatMessage(criterionOptionTypeToIncludeUIString())}
|
||||
onChange={() => onDepthChanged(criterion.value.depth !== 0 ? 0 : -1)}
|
||||
/>
|
||||
</Form.Group>
|
||||
{criterion.modifier !== CriterionModifier.Equals && (
|
||||
<Form.Group>
|
||||
<Form.Check
|
||||
id={criterionOptionTypeToIncludeID()}
|
||||
checked={criterion.value.depth !== 0}
|
||||
label={intl.formatMessage(criterionOptionTypeToIncludeUIString())}
|
||||
onChange={() =>
|
||||
onDepthChanged(criterion.value.depth !== 0 ? 0 : -1)
|
||||
}
|
||||
/>
|
||||
</Form.Group>
|
||||
)}
|
||||
|
||||
{criterion.value.depth !== 0 && (
|
||||
<Form.Group>
|
||||
|
|
|
|||
|
|
@ -567,6 +567,11 @@ export class IHierarchicalLabeledIdCriterion extends Criterion<IHierarchicalLabe
|
|||
|
||||
protected toCriterionInput(): HierarchicalMultiCriterionInput {
|
||||
let excludes: string[] = [];
|
||||
|
||||
// if modifier is equals, depth must be 0
|
||||
const depth =
|
||||
this.modifier === CriterionModifier.Equals ? 0 : this.value.depth;
|
||||
|
||||
if (this.value.excluded) {
|
||||
excludes = this.value.excluded.map((v) => v.id);
|
||||
}
|
||||
|
|
@ -574,7 +579,7 @@ export class IHierarchicalLabeledIdCriterion extends Criterion<IHierarchicalLabe
|
|||
value: this.value.items.map((v) => v.id),
|
||||
excludes: excludes,
|
||||
modifier: this.modifier,
|
||||
depth: this.value.depth,
|
||||
depth,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,14 +4,24 @@ import { CriterionOption, IHierarchicalLabeledIdCriterion } from "./criterion";
|
|||
|
||||
export class TagsCriterion extends IHierarchicalLabeledIdCriterion {}
|
||||
|
||||
class tagsCriterionOption extends CriterionOption {
|
||||
constructor(messageID: string, value: CriterionType, parameterName: string) {
|
||||
const modifierOptions = [
|
||||
CriterionModifier.Includes,
|
||||
CriterionModifier.IncludesAll,
|
||||
CriterionModifier.Equals,
|
||||
];
|
||||
const tagsModifierOptions = [
|
||||
CriterionModifier.Includes,
|
||||
CriterionModifier.IncludesAll,
|
||||
CriterionModifier.Equals,
|
||||
];
|
||||
|
||||
const withoutEqualsModifierOptions = [
|
||||
CriterionModifier.Includes,
|
||||
CriterionModifier.IncludesAll,
|
||||
];
|
||||
|
||||
class tagsCriterionOption extends CriterionOption {
|
||||
constructor(
|
||||
messageID: string,
|
||||
value: CriterionType,
|
||||
parameterName: string,
|
||||
modifierOptions: CriterionModifier[]
|
||||
) {
|
||||
let defaultModifier = CriterionModifier.IncludesAll;
|
||||
|
||||
super({
|
||||
|
|
@ -27,25 +37,30 @@ class tagsCriterionOption extends CriterionOption {
|
|||
export const TagsCriterionOption = new tagsCriterionOption(
|
||||
"tags",
|
||||
"tags",
|
||||
"tags"
|
||||
"tags",
|
||||
tagsModifierOptions
|
||||
);
|
||||
export const SceneTagsCriterionOption = new tagsCriterionOption(
|
||||
"sceneTags",
|
||||
"sceneTags",
|
||||
"scene_tags"
|
||||
"scene_tags",
|
||||
tagsModifierOptions
|
||||
);
|
||||
export const PerformerTagsCriterionOption = new tagsCriterionOption(
|
||||
"performerTags",
|
||||
"performerTags",
|
||||
"performer_tags"
|
||||
"performer_tags",
|
||||
withoutEqualsModifierOptions
|
||||
);
|
||||
export const ParentTagsCriterionOption = new tagsCriterionOption(
|
||||
"parent_tags",
|
||||
"parentTags",
|
||||
"parents"
|
||||
"parents",
|
||||
withoutEqualsModifierOptions
|
||||
);
|
||||
export const ChildTagsCriterionOption = new tagsCriterionOption(
|
||||
"sub_tags",
|
||||
"childTags",
|
||||
"children"
|
||||
"children",
|
||||
withoutEqualsModifierOptions
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue