mirror of
https://github.com/stashapp/stash.git
synced 2026-04-20 05:52:40 +02:00
Merge 5fe32fe517 into 2e48dbfc63
This commit is contained in:
commit
76a182e0f6
14 changed files with 319 additions and 165 deletions
|
|
@ -70,11 +70,52 @@ func stringCriterionHandler(c *models.StringCriterionInput, column string) crite
|
|||
}
|
||||
}
|
||||
|
||||
func joinedStringCriterionHandler(c *models.StringCriterionInput, column string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
func stringNoTrimCriterionHandler(c *models.StringCriterionInput, column string) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if c != nil {
|
||||
if modifier := c.Modifier; c.Modifier.IsValid() {
|
||||
switch modifier {
|
||||
case models.CriterionModifierIncludes:
|
||||
f.whereClauses = append(f.whereClauses, getStringSearchClause([]string{column}, c.Value, false))
|
||||
case models.CriterionModifierExcludes:
|
||||
f.whereClauses = append(f.whereClauses, getStringSearchClause([]string{column}, c.Value, true))
|
||||
case models.CriterionModifierEquals:
|
||||
f.addWhere(column+" LIKE ?", c.Value)
|
||||
case models.CriterionModifierNotEquals:
|
||||
f.addWhere(column+" NOT LIKE ?", c.Value)
|
||||
case models.CriterionModifierMatchesRegex:
|
||||
if _, err := regexp.Compile(c.Value); err != nil {
|
||||
f.setError(err)
|
||||
return
|
||||
}
|
||||
f.addWhere(fmt.Sprintf("(%s IS NOT NULL AND %[1]s regexp ?)", column), c.Value)
|
||||
case models.CriterionModifierNotMatchesRegex:
|
||||
if _, err := regexp.Compile(c.Value); err != nil {
|
||||
f.setError(err)
|
||||
return
|
||||
}
|
||||
f.addWhere(fmt.Sprintf("(%s IS NULL OR %[1]s NOT regexp ?)", column), c.Value)
|
||||
case models.CriterionModifierIsNull:
|
||||
f.addWhere("(" + column + " IS NULL)")
|
||||
case models.CriterionModifierNotNull:
|
||||
f.addWhere("(" + column + " IS NOT NULL)")
|
||||
default:
|
||||
panic("unsupported string filter modifier")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func joinedStringCriterionHandler(c *models.StringCriterionInput, column string, addJoinFn func(f *filterBuilder, joinType joinType)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if c != nil {
|
||||
if addJoinFn != nil {
|
||||
addJoinFn(f)
|
||||
joinType := joinTypeInner
|
||||
if c.Modifier == models.CriterionModifierIsNull || c.Modifier == models.CriterionModifierNotMatchesRegex {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
addJoinFn(f, joinType)
|
||||
}
|
||||
stringCriterionHandler(c, column)(ctx, f)
|
||||
}
|
||||
|
|
@ -104,16 +145,20 @@ func enumCriterionHandler(modifier models.CriterionModifier, values []string, co
|
|||
}
|
||||
}
|
||||
|
||||
func pathCriterionHandler(c *models.StringCriterionInput, pathColumn string, basenameColumn string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
func pathCriterionHandler(c *models.StringCriterionInput, pathColumn string, basenameColumn string, addJoinFn func(f *filterBuilder, joinType joinType)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if c != nil {
|
||||
if addJoinFn != nil {
|
||||
addJoinFn(f)
|
||||
}
|
||||
addWildcards := true
|
||||
not := false
|
||||
|
||||
if modifier := c.Modifier; c.Modifier.IsValid() {
|
||||
if addJoinFn != nil {
|
||||
joinType := joinTypeInner
|
||||
if modifier == models.CriterionModifierIsNull || modifier == models.CriterionModifierNotMatchesRegex {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
addJoinFn(f, joinType)
|
||||
}
|
||||
addWildcards := true
|
||||
not := false
|
||||
|
||||
switch modifier {
|
||||
case models.CriterionModifierIncludes:
|
||||
f.whereClauses = append(f.whereClauses, getPathSearchClauseMany(pathColumn, basenameColumn, c.Value, addWildcards, not))
|
||||
|
|
@ -194,11 +239,15 @@ func getPathSearchClauseMany(pathColumn, basenameColumn, p string, addWildcards,
|
|||
return getPathSearchClause(pathColumn, basenameColumn, trimmedQuery, addWildcards, not)
|
||||
}
|
||||
|
||||
func intCriterionHandler(c *models.IntCriterionInput, column string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
func intCriterionHandler(c *models.IntCriterionInput, column string, addJoinFn func(f *filterBuilder, joinType joinType)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if c != nil {
|
||||
if addJoinFn != nil {
|
||||
addJoinFn(f)
|
||||
joinType := joinTypeInner
|
||||
if c.Modifier == models.CriterionModifierIsNull {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
addJoinFn(f, joinType)
|
||||
}
|
||||
clause, args := getIntCriterionWhereClause(column, *c)
|
||||
f.addWhere(clause, args...)
|
||||
|
|
@ -206,11 +255,15 @@ func intCriterionHandler(c *models.IntCriterionInput, column string, addJoinFn f
|
|||
}
|
||||
}
|
||||
|
||||
func floatCriterionHandler(c *models.FloatCriterionInput, column string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
func floatCriterionHandler(c *models.FloatCriterionInput, column string, addJoinFn func(f *filterBuilder, joinType joinType)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if c != nil {
|
||||
if addJoinFn != nil {
|
||||
addJoinFn(f)
|
||||
joinType := joinTypeInner
|
||||
if c.Modifier == models.CriterionModifierIsNull {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
addJoinFn(f, joinType)
|
||||
}
|
||||
clause, args := getFloatCriterionWhereClause(column, *c)
|
||||
f.addWhere(clause, args...)
|
||||
|
|
@ -218,11 +271,15 @@ func floatCriterionHandler(c *models.FloatCriterionInput, column string, addJoin
|
|||
}
|
||||
}
|
||||
|
||||
func floatIntCriterionHandler(durationFilter *models.IntCriterionInput, column string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
func floatIntCriterionHandler(durationFilter *models.IntCriterionInput, column string, addJoinFn func(f *filterBuilder, joinType joinType)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if durationFilter != nil {
|
||||
if addJoinFn != nil {
|
||||
addJoinFn(f)
|
||||
joinType := joinTypeInner
|
||||
if durationFilter.Modifier == models.CriterionModifierIsNull {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
addJoinFn(f, joinType)
|
||||
}
|
||||
clause, args := getIntCriterionWhereClause("cast("+column+" as int)", *durationFilter)
|
||||
f.addWhere(clause, args...)
|
||||
|
|
@ -230,11 +287,11 @@ func floatIntCriterionHandler(durationFilter *models.IntCriterionInput, column s
|
|||
}
|
||||
}
|
||||
|
||||
func boolCriterionHandler(c *bool, column string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
func boolCriterionHandler(c *bool, column string, addJoinFn func(f *filterBuilder, joinType joinType)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if c != nil {
|
||||
if addJoinFn != nil {
|
||||
addJoinFn(f)
|
||||
addJoinFn(f, joinTypeInner)
|
||||
}
|
||||
var v string
|
||||
if *c {
|
||||
|
|
@ -289,11 +346,11 @@ func yearFilterCriterionHandler(year *models.IntCriterionInput, col string) crit
|
|||
}
|
||||
}
|
||||
|
||||
func resolutionCriterionHandler(resolution *models.ResolutionCriterionInput, heightColumn string, widthColumn string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
func resolutionCriterionHandler(resolution *models.ResolutionCriterionInput, heightColumn string, widthColumn string, addJoinFn func(f *filterBuilder, joinType joinType)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if resolution != nil && resolution.Value.IsValid() {
|
||||
if addJoinFn != nil {
|
||||
addJoinFn(f)
|
||||
addJoinFn(f, joinTypeInner)
|
||||
}
|
||||
|
||||
mn := resolution.Value.GetMinResolution()
|
||||
|
|
@ -315,11 +372,11 @@ func resolutionCriterionHandler(resolution *models.ResolutionCriterionInput, hei
|
|||
}
|
||||
}
|
||||
|
||||
func orientationCriterionHandler(orientation *models.OrientationCriterionInput, heightColumn string, widthColumn string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
func orientationCriterionHandler(orientation *models.OrientationCriterionInput, heightColumn string, widthColumn string, addJoinFn func(f *filterBuilder, joinType joinType)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if orientation != nil {
|
||||
if addJoinFn != nil {
|
||||
addJoinFn(f)
|
||||
addJoinFn(f, joinTypeInner)
|
||||
}
|
||||
|
||||
var clauses []sqlClause
|
||||
|
|
@ -362,7 +419,7 @@ type joinedMultiCriterionHandlerBuilder struct {
|
|||
// foreign key of the foreign object on the join table
|
||||
foreignFK string
|
||||
|
||||
addJoinTable func(f *filterBuilder)
|
||||
addJoinTable func(f *filterBuilder, joinType joinType)
|
||||
}
|
||||
|
||||
func (m *joinedMultiCriterionHandlerBuilder) handler(c *models.MultiCriterionInput) criterionHandlerFunc {
|
||||
|
|
@ -378,11 +435,13 @@ func (m *joinedMultiCriterionHandlerBuilder) handler(c *models.MultiCriterionInp
|
|||
|
||||
if criterion.Modifier == models.CriterionModifierIsNull || criterion.Modifier == models.CriterionModifierNotNull {
|
||||
var notClause string
|
||||
joinType := joinTypeLeft
|
||||
if criterion.Modifier == models.CriterionModifierNotNull {
|
||||
notClause = "NOT"
|
||||
joinType = joinTypeInner
|
||||
}
|
||||
|
||||
m.addJoinTable(f)
|
||||
m.addJoinTable(f, joinType)
|
||||
|
||||
f.addWhere(utils.StrFormat("{table}.{column} IS {not} NULL", utils.StrFormatMap{
|
||||
"table": joinAlias,
|
||||
|
|
@ -415,11 +474,11 @@ func (m *joinedMultiCriterionHandlerBuilder) handler(c *models.MultiCriterionInp
|
|||
switch criterion.Modifier {
|
||||
case models.CriterionModifierIncludes:
|
||||
// includes any of the provided ids
|
||||
m.addJoinTable(f)
|
||||
m.addJoinTable(f, joinTypeInner)
|
||||
whereClause = fmt.Sprintf("%s.%s IN %s", joinAlias, m.foreignFK, getInBinding(len(criterion.Value)))
|
||||
case models.CriterionModifierEquals:
|
||||
// includes only the provided ids
|
||||
m.addJoinTable(f)
|
||||
m.addJoinTable(f, joinTypeInner)
|
||||
whereClause = utils.StrFormat("{joinAlias}.{foreignFK} IN {inBinding} AND (SELECT COUNT(*) FROM {joinTable} s WHERE s.{primaryFK} = {primaryTable}.id) = ?", utils.StrFormatMap{
|
||||
"joinAlias": joinAlias,
|
||||
"foreignFK": m.foreignFK,
|
||||
|
|
@ -434,7 +493,7 @@ func (m *joinedMultiCriterionHandlerBuilder) handler(c *models.MultiCriterionInp
|
|||
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)
|
||||
m.addJoinTable(f, joinTypeInner)
|
||||
whereClause = fmt.Sprintf("%s.%s IN %s", joinAlias, m.foreignFK, getInBinding(len(criterion.Value)))
|
||||
havingClause = fmt.Sprintf("count(distinct %s.%s) IS %d", joinAlias, m.foreignFK, len(criterion.Value))
|
||||
}
|
||||
|
|
@ -468,7 +527,7 @@ type multiCriterionHandlerBuilder struct {
|
|||
foreignFK string
|
||||
|
||||
// function that will be called to perform any necessary joins
|
||||
addJoinsFunc func(f *filterBuilder)
|
||||
addJoinsFunc func(f *filterBuilder, joinType joinType)
|
||||
}
|
||||
|
||||
func (m *multiCriterionHandlerBuilder) handler(criterion *models.MultiCriterionInput) criterionHandlerFunc {
|
||||
|
|
@ -500,7 +559,7 @@ func (m *multiCriterionHandlerBuilder) handler(criterion *models.MultiCriterionI
|
|||
}
|
||||
|
||||
if m.addJoinsFunc != nil {
|
||||
m.addJoinsFunc(f)
|
||||
m.addJoinsFunc(f, joinTypeInner)
|
||||
}
|
||||
|
||||
whereClause, havingClause := getMultiCriterionClause(m.primaryTable, m.foreignTable, m.joinTable, m.primaryFK, m.foreignFK, criterion)
|
||||
|
|
@ -536,7 +595,7 @@ type stringListCriterionHandlerBuilder struct {
|
|||
// string field on the join table
|
||||
stringColumn string
|
||||
|
||||
addJoinTable func(f *filterBuilder)
|
||||
addJoinTable func(f *filterBuilder, joinType joinType)
|
||||
excludeHandler func(f *filterBuilder, criterion *models.StringCriterionInput)
|
||||
}
|
||||
|
||||
|
|
@ -570,7 +629,11 @@ func (m *stringListCriterionHandlerBuilder) handler(criterion *models.StringCrit
|
|||
// Modifier: models.CriterionModifierNotNull,
|
||||
// }, m.joinTable+"."+m.stringColumn)(ctx, f)
|
||||
} else {
|
||||
m.addJoinTable(f)
|
||||
joinType := joinTypeInner
|
||||
if criterion.Modifier == models.CriterionModifierIsNull || criterion.Modifier == models.CriterionModifierNotMatchesRegex {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
m.addJoinTable(f, joinType)
|
||||
stringCriterionHandler(criterion, m.joinTable+"."+m.stringColumn)(ctx, f)
|
||||
}
|
||||
}
|
||||
|
|
@ -1028,14 +1091,18 @@ func (h *stashIDCriterionHandler) handle(ctx context.Context, f *filterBuilder)
|
|||
joinClause += fmt.Sprintf(" AND %s.endpoint = '%s'", t, *h.c.Endpoint)
|
||||
}
|
||||
|
||||
f.addLeftJoin(stashIDRepo.tableName, h.stashIDTableAs, joinClause)
|
||||
joinType := joinTypeInner
|
||||
if h.c.Modifier == models.CriterionModifierIsNull || h.c.Modifier == models.CriterionModifierNotMatchesRegex {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
f.addJoin(joinType, stashIDRepo.tableName, h.stashIDTableAs, joinClause)
|
||||
|
||||
v := ""
|
||||
if h.c.StashID != nil {
|
||||
v = *h.c.StashID
|
||||
}
|
||||
|
||||
stringCriterionHandler(&models.StringCriterionInput{
|
||||
stringNoTrimCriterionHandler(&models.StringCriterionInput{
|
||||
Value: v,
|
||||
Modifier: h.c.Modifier,
|
||||
}, t+".stash_id")(ctx, f)
|
||||
|
|
@ -1064,7 +1131,12 @@ func (h *stashIDsCriterionHandler) handle(ctx context.Context, f *filterBuilder)
|
|||
joinClause += fmt.Sprintf(" AND %s.endpoint = '%s'", t, *h.c.Endpoint)
|
||||
}
|
||||
|
||||
f.addLeftJoin(stashIDRepo.tableName, h.stashIDTableAs, joinClause)
|
||||
joinType := joinTypeInner
|
||||
if h.c.Modifier == models.CriterionModifierIsNull {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
|
||||
f.addJoin(joinType, stashIDRepo.tableName, h.stashIDTableAs, joinClause)
|
||||
|
||||
switch h.c.Modifier {
|
||||
case models.CriterionModifierIsNull:
|
||||
|
|
|
|||
|
|
@ -300,15 +300,19 @@ func (qb *videoFileFilterHandler) criterionHandler() criterionHandler {
|
|||
}
|
||||
}
|
||||
|
||||
func (qb *videoFileFilterHandler) addVideoFilesTable(f *filterBuilder) {
|
||||
f.addLeftJoin(videoFileTable, "", "video_files.file_id = files.id")
|
||||
func (qb *videoFileFilterHandler) addVideoFilesTable(f *filterBuilder, joinType joinType) {
|
||||
f.addJoin(joinType, videoFileTable, "", "video_files.file_id = files.id")
|
||||
}
|
||||
|
||||
func (qb *videoFileFilterHandler) codecCriterionHandler(codec *models.StringCriterionInput, codecColumn string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
func (qb *videoFileFilterHandler) codecCriterionHandler(codec *models.StringCriterionInput, codecColumn string, addJoinFn func(f *filterBuilder, joinType joinType)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if codec != nil {
|
||||
if addJoinFn != nil {
|
||||
addJoinFn(f)
|
||||
joinType := joinTypeInner
|
||||
if codec.Modifier == models.CriterionModifierIsNull || codec.Modifier == models.CriterionModifierNotMatchesRegex {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
addJoinFn(f, joinType)
|
||||
}
|
||||
|
||||
stringCriterionHandler(codec, codecColumn)(ctx, f)
|
||||
|
|
@ -322,8 +326,8 @@ func (qb *videoFileFilterHandler) captionCriterionHandler(captions *models.Strin
|
|||
primaryFK: sceneIDColumn,
|
||||
joinTable: videoCaptionsTable,
|
||||
stringColumn: captionCodeColumn,
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
f.addLeftJoin(videoCaptionsTable, "", "video_captions.file_id = files.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
f.addJoin(joinType, videoCaptionsTable, "", "video_captions.file_id = files.id")
|
||||
},
|
||||
excludeHandler: func(f *filterBuilder, criterion *models.StringCriterionInput) {
|
||||
excludeClause := `files.id NOT IN (
|
||||
|
|
@ -361,6 +365,6 @@ func (qb *imageFileFilterHandler) criterionHandler() criterionHandler {
|
|||
}
|
||||
}
|
||||
|
||||
func (qb *imageFileFilterHandler) addImageFilesTable(f *filterBuilder) {
|
||||
f.addLeftJoin(imageFileTable, "", "image_files.file_id = files.id")
|
||||
func (qb *imageFileFilterHandler) addImageFilesTable(f *filterBuilder, joinType joinType) {
|
||||
f.addJoin(joinType, imageFileTable, "", "image_files.file_id = files.id")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,11 +90,18 @@ func andClauses(clauses ...sqlClause) sqlClause {
|
|||
return joinClauses("AND", clauses...)
|
||||
}
|
||||
|
||||
type joinType string
|
||||
|
||||
const (
|
||||
joinTypeLeft joinType = "LEFT"
|
||||
joinTypeInner joinType = "INNER"
|
||||
)
|
||||
|
||||
type join struct {
|
||||
table string
|
||||
as string
|
||||
onClause string
|
||||
joinType string
|
||||
joinType joinType
|
||||
args []interface{}
|
||||
|
||||
// if true, indicates this is required for sorting only
|
||||
|
|
@ -115,15 +122,19 @@ func (j join) alias() string {
|
|||
return j.as
|
||||
}
|
||||
|
||||
func (j join) getJoinType() joinType {
|
||||
if j.joinType == "" {
|
||||
return joinTypeLeft
|
||||
}
|
||||
return j.joinType
|
||||
}
|
||||
|
||||
func (j join) toSQL() string {
|
||||
asStr := ""
|
||||
joinStr := j.joinType
|
||||
joinStr := j.getJoinType()
|
||||
if j.as != "" && j.as != j.table {
|
||||
asStr = " AS " + j.as
|
||||
}
|
||||
if j.joinType == "" {
|
||||
joinStr = "LEFT"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s JOIN %s%s ON %s", joinStr, j.table, asStr, j.onClause)
|
||||
}
|
||||
|
|
@ -141,6 +152,12 @@ func (j *joins) addUnique(newJoin join) bool {
|
|||
if !newJoin.sort && jj.sort {
|
||||
(*j)[i].sort = false
|
||||
}
|
||||
|
||||
// if the new join is inner, override existing left join
|
||||
if newJoin.getJoinType() == joinTypeInner && jj.getJoinType() == joinTypeLeft {
|
||||
(*j)[i].joinType = joinTypeInner
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -243,6 +260,23 @@ func (f *filterBuilder) not(n *filterBuilder) {
|
|||
f.subFilterOp = notOp
|
||||
}
|
||||
|
||||
// addJoin adds a join to the filter. The join is expressed in SQL as:
|
||||
// <joinType> JOIN <table> [AS <as>] ON <onClause>
|
||||
// The AS is omitted if as is empty.
|
||||
// This method does not add a join if it its alias/table name is already
|
||||
// present in another existing join.
|
||||
func (f *filterBuilder) addJoin(joinType joinType, table, as, onClause string, args ...interface{}) {
|
||||
newJoin := join{
|
||||
table: table,
|
||||
as: as,
|
||||
onClause: onClause,
|
||||
joinType: joinType,
|
||||
args: args,
|
||||
}
|
||||
|
||||
f.joins.add(newJoin)
|
||||
}
|
||||
|
||||
// addLeftJoin adds a left join to the filter. The join is expressed in SQL as:
|
||||
// LEFT JOIN <table> [AS <as>] ON <onClause>
|
||||
// The AS is omitted if as is empty.
|
||||
|
|
@ -253,7 +287,7 @@ func (f *filterBuilder) addLeftJoin(table, as, onClause string, args ...interfac
|
|||
table: table,
|
||||
as: as,
|
||||
onClause: onClause,
|
||||
joinType: "LEFT",
|
||||
joinType: joinTypeLeft,
|
||||
args: args,
|
||||
}
|
||||
|
||||
|
|
@ -270,7 +304,7 @@ func (f *filterBuilder) addInnerJoin(table, as, onClause string, args ...interfa
|
|||
table: table,
|
||||
as: as,
|
||||
onClause: onClause,
|
||||
joinType: "INNER",
|
||||
joinType: joinTypeInner,
|
||||
args: args,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -193,15 +193,15 @@ func (qb *galleryFilterHandler) urlsCriterionHandler(url *models.StringCriterion
|
|||
primaryFK: galleryIDColumn,
|
||||
joinTable: galleriesURLsTable,
|
||||
stringColumn: galleriesURLColumn,
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
galleriesURLsTableMgr.join(f, "", "galleries.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
galleriesURLsTableMgr.join(f, joinType, "", "galleries.id")
|
||||
},
|
||||
}
|
||||
|
||||
return h.handler(url)
|
||||
}
|
||||
|
||||
func (qb *galleryFilterHandler) getMultiCriterionHandlerBuilder(foreignTable, joinTable, foreignFK string, addJoinsFunc func(f *filterBuilder)) multiCriterionHandlerBuilder {
|
||||
func (qb *galleryFilterHandler) getMultiCriterionHandlerBuilder(foreignTable, joinTable, foreignFK string, addJoinsFunc func(f *filterBuilder, joinType joinType)) multiCriterionHandlerBuilder {
|
||||
return multiCriterionHandlerBuilder{
|
||||
primaryTable: galleryTable,
|
||||
foreignTable: foreignTable,
|
||||
|
|
@ -353,7 +353,7 @@ func (qb *galleryFilterHandler) missingCriterionHandler(isMissing *string) crite
|
|||
if isMissing != nil && *isMissing != "" {
|
||||
switch *isMissing {
|
||||
case "url":
|
||||
galleriesURLsTableMgr.join(f, "", "galleries.id")
|
||||
galleriesURLsTableMgr.leftJoin(f, "", "galleries.id")
|
||||
f.addWhere("gallery_urls.url IS NULL")
|
||||
case "scenes":
|
||||
f.addLeftJoin("scenes_galleries", "scenes_join", "scenes_join.gallery_id = galleries.id")
|
||||
|
|
@ -361,12 +361,12 @@ func (qb *galleryFilterHandler) missingCriterionHandler(isMissing *string) crite
|
|||
case "studio":
|
||||
f.addWhere("galleries.studio_id IS NULL")
|
||||
case "performers":
|
||||
galleryRepository.performers.join(f, "performers_join", "galleries.id")
|
||||
galleryRepository.performers.leftJoin(f, "performers_join", "galleries.id")
|
||||
f.addWhere("performers_join.gallery_id IS NULL")
|
||||
case "date":
|
||||
f.addWhere("galleries.date IS NULL OR galleries.date IS \"\"")
|
||||
case "tags":
|
||||
galleryRepository.tags.join(f, "tags_join", "galleries.id")
|
||||
galleryRepository.tags.leftJoin(f, "tags_join", "galleries.id")
|
||||
f.addWhere("tags_join.gallery_id IS NULL")
|
||||
case "cover":
|
||||
f.addLeftJoin("galleries_images", "cover_join", "cover_join.gallery_id = galleries.id AND cover_join.cover = 1")
|
||||
|
|
@ -410,9 +410,9 @@ func (qb *galleryFilterHandler) tagCountCriterionHandler(tagCount *models.IntCri
|
|||
}
|
||||
|
||||
func (qb *galleryFilterHandler) scenesCriterionHandler(scenes *models.MultiCriterionInput) criterionHandlerFunc {
|
||||
addJoinsFunc := func(f *filterBuilder) {
|
||||
galleryRepository.scenes.join(f, "", "galleries.id")
|
||||
f.addLeftJoin("scenes", "", "scenes_galleries.scene_id = scenes.id")
|
||||
addJoinsFunc := func(f *filterBuilder, joinType joinType) {
|
||||
galleryRepository.scenes.join(f, joinType, "", "galleries.id")
|
||||
f.addJoin(joinType, "scenes", "", "scenes_galleries.scene_id = scenes.id")
|
||||
}
|
||||
h := qb.getMultiCriterionHandlerBuilder(sceneTable, galleriesScenesTable, "scene_id", addJoinsFunc)
|
||||
return h.handler(scenes)
|
||||
|
|
@ -426,8 +426,8 @@ func (qb *galleryFilterHandler) performersCriterionHandler(performers *models.Mu
|
|||
primaryFK: galleryIDColumn,
|
||||
foreignFK: performerIDColumn,
|
||||
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
galleryRepository.performers.join(f, "performers_join", "galleries.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
galleryRepository.performers.join(f, joinType, "performers_join", "galleries.id")
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -515,7 +515,7 @@ func (qb *galleryFilterHandler) performerAgeCriterionHandler(performerAge *model
|
|||
func (qb *galleryFilterHandler) averageResolutionCriterionHandler(resolution *models.ResolutionCriterionInput) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if resolution != nil && resolution.Value.IsValid() {
|
||||
galleryRepository.images.join(f, "images_join", "galleries.id")
|
||||
galleryRepository.images.leftJoin(f, "images_join", "galleries.id")
|
||||
f.addLeftJoin("images", "", "images_join.image_id = images.id")
|
||||
f.addLeftJoin("images_files", "", "images.id = images_files.image_id")
|
||||
f.addLeftJoin("image_files", "", "images_files.file_id = image_files.file_id")
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ func (qb *groupFilterHandler) missingCriterionHandler(isMissing *string) criteri
|
|||
f.addLeftJoin("groups_scenes", "", "groups_scenes.group_id = groups.id")
|
||||
f.addWhere("groups_scenes.scene_id IS NULL")
|
||||
case "url":
|
||||
groupsURLsTableMgr.join(f, "", "groups.id")
|
||||
groupsURLsTableMgr.leftJoin(f, "", "groups.id")
|
||||
f.addWhere("group_urls.url IS NULL")
|
||||
case "studio":
|
||||
f.addWhere("groups.studio_id IS NULL")
|
||||
|
|
@ -129,7 +129,7 @@ func (qb *groupFilterHandler) missingCriterionHandler(isMissing *string) criteri
|
|||
f.addLeftJoin("performers_scenes", "ps_perf", "gs_perf.scene_id = ps_perf.scene_id")
|
||||
f.addWhere("ps_perf.performer_id IS NULL")
|
||||
case "tags":
|
||||
groupRepository.tags.join(f, "tags_join", "groups.id")
|
||||
groupRepository.tags.leftJoin(f, "tags_join", "groups.id")
|
||||
f.addWhere("tags_join.group_id IS NULL")
|
||||
default:
|
||||
if err := validateIsMissing(*isMissing, []string{
|
||||
|
|
@ -150,8 +150,8 @@ func (qb *groupFilterHandler) urlsCriterionHandler(url *models.StringCriterionIn
|
|||
primaryFK: groupIDColumn,
|
||||
joinTable: groupURLsTable,
|
||||
stringColumn: groupURLColumn,
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
groupsURLsTableMgr.join(f, "", "groups.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
groupsURLsTableMgr.join(f, joinType, "", "groups.id")
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -123,23 +123,23 @@ type imageRepositoryType struct {
|
|||
files filesRepository
|
||||
}
|
||||
|
||||
func (r *imageRepositoryType) addImagesFilesTable(f *filterBuilder) {
|
||||
f.addLeftJoin(imagesFilesTable, "", "images_files.image_id = images.id")
|
||||
func (r *imageRepositoryType) addImagesFilesTable(f *filterBuilder, joinType joinType) {
|
||||
f.addJoin(joinType, imagesFilesTable, "", "images_files.image_id = images.id")
|
||||
}
|
||||
|
||||
func (r *imageRepositoryType) addFilesTable(f *filterBuilder) {
|
||||
r.addImagesFilesTable(f)
|
||||
f.addLeftJoin(fileTable, "", "images_files.file_id = files.id")
|
||||
func (r *imageRepositoryType) addFilesTable(f *filterBuilder, joinType joinType) {
|
||||
r.addImagesFilesTable(f, joinType)
|
||||
f.addJoin(joinType, fileTable, "", "images_files.file_id = files.id")
|
||||
}
|
||||
|
||||
func (r *imageRepositoryType) addFoldersTable(f *filterBuilder) {
|
||||
r.addFilesTable(f)
|
||||
f.addLeftJoin(folderTable, "", "files.parent_folder_id = folders.id")
|
||||
func (r *imageRepositoryType) addFoldersTable(f *filterBuilder, joinType joinType) {
|
||||
r.addFilesTable(f, joinType)
|
||||
f.addJoin(joinType, folderTable, "", "files.parent_folder_id = folders.id")
|
||||
}
|
||||
|
||||
func (r *imageRepositoryType) addImageFilesTable(f *filterBuilder) {
|
||||
r.addImagesFilesTable(f)
|
||||
f.addLeftJoin(imageFileTable, "", "image_files.file_id = images_files.file_id")
|
||||
func (r *imageRepositoryType) addImageFilesTable(f *filterBuilder, joinType joinType) {
|
||||
r.addImagesFilesTable(f, joinType)
|
||||
f.addJoin(joinType, imageFileTable, "", "image_files.file_id = images_files.file_id")
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
|
|||
|
|
@ -56,8 +56,12 @@ func (qb *imageFilterHandler) criterionHandler() criterionHandler {
|
|||
intCriterionHandler(imageFilter.ID, "images.id", nil),
|
||||
criterionHandlerFunc(func(ctx context.Context, f *filterBuilder) {
|
||||
if imageFilter.Checksum != nil {
|
||||
imageRepository.addImagesFilesTable(f)
|
||||
f.addInnerJoin(fingerprintTable, "fingerprints_md5", "images_files.file_id = fingerprints_md5.file_id AND fingerprints_md5.type = 'md5'")
|
||||
joinType := joinTypeInner
|
||||
if imageFilter.Checksum.Modifier == models.CriterionModifierIsNull || imageFilter.Checksum.Modifier == models.CriterionModifierNotMatchesRegex {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
imageRepository.addImagesFilesTable(f, joinType)
|
||||
f.addJoin(joinType, fingerprintTable, "fingerprints_md5", "images_files.file_id = fingerprints_md5.file_id AND fingerprints_md5.type = 'md5'")
|
||||
}
|
||||
|
||||
stringCriterionHandler(imageFilter.Checksum, "fingerprints_md5.fingerprint")(ctx, f)
|
||||
|
|
@ -65,8 +69,8 @@ func (qb *imageFilterHandler) criterionHandler() criterionHandler {
|
|||
|
||||
&phashDistanceCriterionHandler{
|
||||
joinFn: func(f *filterBuilder) {
|
||||
imageRepository.addImagesFilesTable(f)
|
||||
f.addLeftJoin(fingerprintTable, "fingerprints_phash", "images_files.file_id = fingerprints_phash.file_id AND fingerprints_phash.type = 'phash'")
|
||||
imageRepository.addImagesFilesTable(f, joinTypeInner)
|
||||
f.addInnerJoin(fingerprintTable, "fingerprints_phash", "images_files.file_id = fingerprints_phash.file_id AND fingerprints_phash.type = 'phash'")
|
||||
},
|
||||
criterion: imageFilter.PhashDistance,
|
||||
},
|
||||
|
|
@ -148,8 +152,8 @@ func (qb *imageFilterHandler) criterionHandler() criterionHandler {
|
|||
isRelated: true,
|
||||
},
|
||||
joinFn: func(f *filterBuilder) {
|
||||
imageRepository.addFilesTable(f)
|
||||
imageRepository.addFoldersTable(f)
|
||||
imageRepository.addFilesTable(f, joinTypeInner)
|
||||
imageRepository.addFoldersTable(f, joinTypeInner)
|
||||
},
|
||||
// don't use a subquery; join directly
|
||||
directJoin: true,
|
||||
|
|
@ -172,18 +176,18 @@ func (qb *imageFilterHandler) missingCriterionHandler(isMissing *string) criteri
|
|||
if isMissing != nil && *isMissing != "" {
|
||||
switch *isMissing {
|
||||
case "url":
|
||||
imagesURLsTableMgr.join(f, "", "images.id")
|
||||
imagesURLsTableMgr.leftJoin(f, "", "images.id")
|
||||
f.addWhere("image_urls.url IS NULL")
|
||||
case "studio":
|
||||
f.addWhere("images.studio_id IS NULL")
|
||||
case "performers":
|
||||
imageRepository.performers.join(f, "performers_join", "images.id")
|
||||
imageRepository.performers.leftJoin(f, "performers_join", "images.id")
|
||||
f.addWhere("performers_join.image_id IS NULL")
|
||||
case "galleries":
|
||||
imageRepository.galleries.join(f, "galleries_join", "images.id")
|
||||
imageRepository.galleries.leftJoin(f, "galleries_join", "images.id")
|
||||
f.addWhere("galleries_join.image_id IS NULL")
|
||||
case "tags":
|
||||
imageRepository.tags.join(f, "tags_join", "images.id")
|
||||
imageRepository.tags.leftJoin(f, "tags_join", "images.id")
|
||||
f.addWhere("tags_join.image_id IS NULL")
|
||||
default:
|
||||
if err := validateIsMissing(*isMissing, []string{
|
||||
|
|
@ -204,15 +208,15 @@ func (qb *imageFilterHandler) urlsCriterionHandler(url *models.StringCriterionIn
|
|||
primaryFK: imageIDColumn,
|
||||
joinTable: imagesURLsTable,
|
||||
stringColumn: imageURLColumn,
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
imagesURLsTableMgr.join(f, "", "images.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
imagesURLsTableMgr.join(f, joinType, "", "images.id")
|
||||
},
|
||||
}
|
||||
|
||||
return h.handler(url)
|
||||
}
|
||||
|
||||
func (qb *imageFilterHandler) getMultiCriterionHandlerBuilder(foreignTable, joinTable, foreignFK string, addJoinsFunc func(f *filterBuilder)) multiCriterionHandlerBuilder {
|
||||
func (qb *imageFilterHandler) getMultiCriterionHandlerBuilder(foreignTable, joinTable, foreignFK string, addJoinsFunc func(f *filterBuilder, joinType joinType)) multiCriterionHandlerBuilder {
|
||||
return multiCriterionHandlerBuilder{
|
||||
primaryTable: imageTable,
|
||||
foreignTable: foreignTable,
|
||||
|
|
@ -249,7 +253,7 @@ func (qb *imageFilterHandler) tagCountCriterionHandler(tagCount *models.IntCrite
|
|||
}
|
||||
|
||||
func (qb *imageFilterHandler) galleriesCriterionHandler(galleries *models.MultiCriterionInput) criterionHandlerFunc {
|
||||
addJoinsFunc := func(f *filterBuilder) {
|
||||
addJoinsFunc := func(f *filterBuilder, joinType joinType) {
|
||||
if galleries.Modifier == models.CriterionModifierIncludes || galleries.Modifier == models.CriterionModifierIncludesAll {
|
||||
f.addInnerJoin(galleriesImagesTable, "", "galleries_images.image_id = images.id")
|
||||
f.addInnerJoin(galleryTable, "", "galleries_images.gallery_id = galleries.id")
|
||||
|
|
@ -268,8 +272,8 @@ func (qb *imageFilterHandler) performersCriterionHandler(performers *models.Mult
|
|||
primaryFK: imageIDColumn,
|
||||
foreignFK: performerIDColumn,
|
||||
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
imageRepository.performers.join(f, "performers_join", "images.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
imageRepository.performers.join(f, joinType, "performers_join", "images.id")
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ func (qb *performerFilterHandler) criterionHandler() criterionHandler {
|
|||
intCriterionHandler(filter.Weight, tableName+".weight", nil),
|
||||
criterionHandlerFunc(func(ctx context.Context, f *filterBuilder) {
|
||||
if filter.StashID != nil {
|
||||
performerRepository.stashIDs.join(f, "performer_stash_ids", "performers.id")
|
||||
performerRepository.stashIDs.leftJoin(f, "performer_stash_ids", "performers.id")
|
||||
stringCriterionHandler(filter.StashID, "performer_stash_ids.stash_id")(ctx, f)
|
||||
}
|
||||
}),
|
||||
|
|
@ -333,7 +333,7 @@ func (qb *performerFilterHandler) performerIsMissingCriterionHandler(isMissing *
|
|||
if isMissing != nil && *isMissing != "" {
|
||||
switch *isMissing {
|
||||
case "url":
|
||||
performersURLsTableMgr.join(f, "", "performers.id")
|
||||
performersURLsTableMgr.leftJoin(f, "", "performers.id")
|
||||
f.addWhere("performer_urls.url IS NULL")
|
||||
case "scenes": // Deprecated: use `scene_count == 0` filter instead
|
||||
f.addLeftJoin(performersScenesTable, "scenes_join", "scenes_join.performer_id = performers.id")
|
||||
|
|
@ -341,10 +341,10 @@ func (qb *performerFilterHandler) performerIsMissingCriterionHandler(isMissing *
|
|||
case "image":
|
||||
f.addWhere("performers.image_blob IS NULL")
|
||||
case "stash_id":
|
||||
performersStashIDsTableMgr.join(f, "performer_stash_ids", "performers.id")
|
||||
performersStashIDsTableMgr.leftJoin(f, "performer_stash_ids", "performers.id")
|
||||
f.addWhere("performer_stash_ids.performer_id IS NULL")
|
||||
case "aliases":
|
||||
performersAliasesTableMgr.join(f, "", "performers.id")
|
||||
performersAliasesTableMgr.leftJoin(f, "", "performers.id")
|
||||
f.addWhere("performer_aliases.alias IS NULL")
|
||||
case "tags":
|
||||
f.addLeftJoin(performersTagsTable, "tags_join", "tags_join.performer_id = performers.id")
|
||||
|
|
@ -383,8 +383,8 @@ func (qb *performerFilterHandler) urlsCriterionHandler(url *models.StringCriteri
|
|||
primaryFK: performerIDColumn,
|
||||
joinTable: performerURLsTable,
|
||||
stringColumn: performerURLColumn,
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
performersURLsTableMgr.join(f, "", "performers.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
performersURLsTableMgr.join(f, joinType, "", "performers.id")
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -397,8 +397,8 @@ func (qb *performerFilterHandler) aliasCriterionHandler(alias *models.StringCrit
|
|||
primaryFK: performerIDColumn,
|
||||
joinTable: performersAliasesTable,
|
||||
stringColumn: performerAliasColumn,
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
performersAliasesTableMgr.join(f, "", "performers.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
performersAliasesTableMgr.join(f, joinType, "", "performers.id")
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -204,7 +204,15 @@ func (r *repository) newQuery() queryBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
func (r *repository) join(j joiner, as string, parentIDCol string) {
|
||||
func (r *repository) join(j joiner, t joinType, as string, parentIDCol string) {
|
||||
fn := r.innerJoin
|
||||
if t == joinTypeLeft {
|
||||
fn = r.leftJoin
|
||||
}
|
||||
fn(j, as, parentIDCol)
|
||||
}
|
||||
|
||||
func (r *repository) leftJoin(j joiner, as string, parentIDCol string) {
|
||||
t := r.tableName
|
||||
if as != "" {
|
||||
t = as
|
||||
|
|
|
|||
|
|
@ -63,8 +63,12 @@ func (qb *sceneFilterHandler) criterionHandler() criterionHandler {
|
|||
stringCriterionHandler(sceneFilter.Director, "scenes.director"),
|
||||
criterionHandlerFunc(func(ctx context.Context, f *filterBuilder) {
|
||||
if sceneFilter.Oshash != nil {
|
||||
qb.addSceneFilesTable(f)
|
||||
f.addLeftJoin(fingerprintTable, "fingerprints_oshash", "scenes_files.file_id = fingerprints_oshash.file_id AND fingerprints_oshash.type = 'oshash'")
|
||||
joinType := joinTypeInner
|
||||
if sceneFilter.Oshash.Modifier == models.CriterionModifierIsNull {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
qb.addSceneFilesTable(f, joinType)
|
||||
f.addJoin(joinType, fingerprintTable, "fingerprints_oshash", "scenes_files.file_id = fingerprints_oshash.file_id AND fingerprints_oshash.type = 'oshash'")
|
||||
}
|
||||
|
||||
stringCriterionHandler(sceneFilter.Oshash, "fingerprints_oshash.fingerprint")(ctx, f)
|
||||
|
|
@ -72,8 +76,12 @@ func (qb *sceneFilterHandler) criterionHandler() criterionHandler {
|
|||
|
||||
criterionHandlerFunc(func(ctx context.Context, f *filterBuilder) {
|
||||
if sceneFilter.Checksum != nil {
|
||||
qb.addSceneFilesTable(f)
|
||||
f.addLeftJoin(fingerprintTable, "fingerprints_md5", "scenes_files.file_id = fingerprints_md5.file_id AND fingerprints_md5.type = 'md5'")
|
||||
joinType := joinTypeInner
|
||||
if sceneFilter.Checksum.Modifier == models.CriterionModifierIsNull {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
qb.addSceneFilesTable(f, joinType)
|
||||
f.addJoin(joinType, fingerprintTable, "fingerprints_md5", "scenes_files.file_id = fingerprints_md5.file_id AND fingerprints_md5.type = 'md5'")
|
||||
}
|
||||
|
||||
stringCriterionHandler(sceneFilter.Checksum, "fingerprints_md5.fingerprint")(ctx, f)
|
||||
|
|
@ -84,8 +92,12 @@ func (qb *sceneFilterHandler) criterionHandler() criterionHandler {
|
|||
// backwards compatibility
|
||||
h := phashDistanceCriterionHandler{
|
||||
joinFn: func(f *filterBuilder) {
|
||||
qb.addSceneFilesTable(f)
|
||||
f.addLeftJoin(fingerprintTable, "fingerprints_phash", "scenes_files.file_id = fingerprints_phash.file_id AND fingerprints_phash.type = 'phash'")
|
||||
joinType := joinTypeInner
|
||||
if sceneFilter.Phash.Modifier == models.CriterionModifierIsNull {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
qb.addSceneFilesTable(f, joinType)
|
||||
f.addJoin(joinType, fingerprintTable, "fingerprints_phash", "scenes_files.file_id = fingerprints_phash.file_id AND fingerprints_phash.type = 'phash'")
|
||||
},
|
||||
criterion: &models.PhashDistanceCriterionInput{
|
||||
Value: sceneFilter.Phash.Value,
|
||||
|
|
@ -98,8 +110,9 @@ func (qb *sceneFilterHandler) criterionHandler() criterionHandler {
|
|||
|
||||
&phashDistanceCriterionHandler{
|
||||
joinFn: func(f *filterBuilder) {
|
||||
qb.addSceneFilesTable(f)
|
||||
f.addLeftJoin(fingerprintTable, "fingerprints_phash", "scenes_files.file_id = fingerprints_phash.file_id AND fingerprints_phash.type = 'phash'")
|
||||
const joinType = joinTypeInner
|
||||
qb.addSceneFilesTable(f, joinType)
|
||||
f.addJoin(joinType, fingerprintTable, "fingerprints_phash", "scenes_files.file_id = fingerprints_phash.file_id AND fingerprints_phash.type = 'phash'")
|
||||
},
|
||||
criterion: sceneFilter.PhashDistance,
|
||||
},
|
||||
|
|
@ -122,7 +135,7 @@ func (qb *sceneFilterHandler) criterionHandler() criterionHandler {
|
|||
|
||||
criterionHandlerFunc(func(ctx context.Context, f *filterBuilder) {
|
||||
if sceneFilter.StashID != nil {
|
||||
sceneRepository.stashIDs.join(f, "scene_stash_ids", "scenes.id")
|
||||
sceneRepository.stashIDs.leftJoin(f, "scene_stash_ids", "scenes.id")
|
||||
stringCriterionHandler(sceneFilter.StashID, "scene_stash_ids.stash_id")(ctx, f)
|
||||
}
|
||||
}),
|
||||
|
|
@ -236,8 +249,8 @@ func (qb *sceneFilterHandler) criterionHandler() criterionHandler {
|
|||
isRelated: true,
|
||||
},
|
||||
joinFn: func(f *filterBuilder) {
|
||||
qb.addFilesTable(f)
|
||||
qb.addFoldersTable(f)
|
||||
qb.addFilesTable(f, joinTypeInner)
|
||||
qb.addFoldersTable(f, joinTypeInner)
|
||||
},
|
||||
// don't use a subquery; join directly
|
||||
directJoin: true,
|
||||
|
|
@ -254,23 +267,23 @@ func (qb *sceneFilterHandler) criterionHandler() criterionHandler {
|
|||
}
|
||||
}
|
||||
|
||||
func (qb *sceneFilterHandler) addSceneFilesTable(f *filterBuilder) {
|
||||
f.addLeftJoin(scenesFilesTable, "", "scenes_files.scene_id = scenes.id")
|
||||
func (qb *sceneFilterHandler) addSceneFilesTable(f *filterBuilder, joinType joinType) {
|
||||
f.addJoin(joinType, scenesFilesTable, "", "scenes_files.scene_id = scenes.id")
|
||||
}
|
||||
|
||||
func (qb *sceneFilterHandler) addFilesTable(f *filterBuilder) {
|
||||
qb.addSceneFilesTable(f)
|
||||
f.addLeftJoin(fileTable, "", "scenes_files.file_id = files.id")
|
||||
func (qb *sceneFilterHandler) addFilesTable(f *filterBuilder, joinType joinType) {
|
||||
qb.addSceneFilesTable(f, joinType)
|
||||
f.addJoin(joinType, fileTable, "", "scenes_files.file_id = files.id")
|
||||
}
|
||||
|
||||
func (qb *sceneFilterHandler) addFoldersTable(f *filterBuilder) {
|
||||
qb.addFilesTable(f)
|
||||
f.addLeftJoin(folderTable, "", "files.parent_folder_id = folders.id")
|
||||
func (qb *sceneFilterHandler) addFoldersTable(f *filterBuilder, joinType joinType) {
|
||||
qb.addFilesTable(f, joinType)
|
||||
f.addJoin(joinType, folderTable, "", "files.parent_folder_id = folders.id")
|
||||
}
|
||||
|
||||
func (qb *sceneFilterHandler) addVideoFilesTable(f *filterBuilder) {
|
||||
qb.addSceneFilesTable(f)
|
||||
f.addLeftJoin(videoFileTable, "", "video_files.file_id = scenes_files.file_id")
|
||||
func (qb *sceneFilterHandler) addVideoFilesTable(f *filterBuilder, joinType joinType) {
|
||||
qb.addSceneFilesTable(f, joinType)
|
||||
f.addJoin(joinType, videoFileTable, "", "video_files.file_id = scenes_files.file_id")
|
||||
}
|
||||
|
||||
func (qb *sceneFilterHandler) playCountCriterionHandler(count *models.IntCriterionInput) criterionHandlerFunc {
|
||||
|
|
@ -318,7 +331,7 @@ func (qb *sceneFilterHandler) duplicatedCriterionHandler(duplicatedFilter *model
|
|||
|
||||
// Handle explicit fields
|
||||
if duplicatedFilter.Phash != nil {
|
||||
qb.addSceneFilesTable(f)
|
||||
qb.addSceneFilesTable(f, joinTypeInner)
|
||||
qb.applyPhashDuplication(f, *duplicatedFilter.Phash)
|
||||
}
|
||||
|
||||
|
|
@ -368,11 +381,15 @@ func (qb *sceneFilterHandler) applyURLDuplication(f *filterBuilder, duplicated b
|
|||
f.addInnerJoin("(SELECT scene_id FROM scene_urls INNER JOIN (SELECT url FROM scene_urls GROUP BY url HAVING COUNT(DISTINCT scene_id) "+v+" 1) dupes ON scene_urls.url = dupes.url)", "scurl", "scenes.id = scurl.scene_id")
|
||||
}
|
||||
|
||||
func (qb *sceneFilterHandler) codecCriterionHandler(codec *models.StringCriterionInput, codecColumn string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
func (qb *sceneFilterHandler) codecCriterionHandler(codec *models.StringCriterionInput, codecColumn string, addJoinFn func(f *filterBuilder, joinType joinType)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if codec != nil {
|
||||
if addJoinFn != nil {
|
||||
addJoinFn(f)
|
||||
joinType := joinTypeInner
|
||||
if codec.Modifier == models.CriterionModifierIsNull {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
addJoinFn(f, joinType)
|
||||
}
|
||||
|
||||
stringCriterionHandler(codec, codecColumn)(ctx, f)
|
||||
|
|
@ -398,29 +415,29 @@ func (qb *sceneFilterHandler) isMissingCriterionHandler(isMissing *string) crite
|
|||
if isMissing != nil && *isMissing != "" {
|
||||
switch *isMissing {
|
||||
case "url":
|
||||
scenesURLsTableMgr.join(f, "", "scenes.id")
|
||||
scenesURLsTableMgr.leftJoin(f, "", "scenes.id")
|
||||
f.addWhere("scene_urls.url IS NULL")
|
||||
case "galleries":
|
||||
sceneRepository.galleries.join(f, "galleries_join", "scenes.id")
|
||||
sceneRepository.galleries.leftJoin(f, "galleries_join", "scenes.id")
|
||||
f.addWhere("galleries_join.scene_id IS NULL")
|
||||
case "studio":
|
||||
f.addWhere("scenes.studio_id IS NULL")
|
||||
case "movie", "group":
|
||||
sceneRepository.groups.join(f, "groups_join", "scenes.id")
|
||||
sceneRepository.groups.leftJoin(f, "groups_join", "scenes.id")
|
||||
f.addWhere("groups_join.scene_id IS NULL")
|
||||
case "performers":
|
||||
sceneRepository.performers.join(f, "performers_join", "scenes.id")
|
||||
sceneRepository.performers.leftJoin(f, "performers_join", "scenes.id")
|
||||
f.addWhere("performers_join.scene_id IS NULL")
|
||||
case "date":
|
||||
f.addWhere(`scenes.date IS NULL OR scenes.date IS ""`)
|
||||
case "tags":
|
||||
sceneRepository.tags.join(f, "tags_join", "scenes.id")
|
||||
sceneRepository.tags.leftJoin(f, "tags_join", "scenes.id")
|
||||
f.addWhere("tags_join.scene_id IS NULL")
|
||||
case "stash_id":
|
||||
sceneRepository.stashIDs.join(f, "scene_stash_ids", "scenes.id")
|
||||
sceneRepository.stashIDs.leftJoin(f, "scene_stash_ids", "scenes.id")
|
||||
f.addWhere("scene_stash_ids.scene_id IS NULL")
|
||||
case "phash":
|
||||
qb.addSceneFilesTable(f)
|
||||
qb.addSceneFilesTable(f, joinTypeLeft)
|
||||
f.addLeftJoin(fingerprintTable, "fingerprints_phash", "scenes_files.file_id = fingerprints_phash.file_id AND fingerprints_phash.type = 'phash'")
|
||||
f.addWhere("fingerprints_phash.fingerprint IS NULL")
|
||||
case "cover":
|
||||
|
|
@ -444,15 +461,15 @@ func (qb *sceneFilterHandler) urlsCriterionHandler(url *models.StringCriterionIn
|
|||
primaryFK: sceneIDColumn,
|
||||
joinTable: scenesURLsTable,
|
||||
stringColumn: sceneURLColumn,
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
scenesURLsTableMgr.join(f, "", "scenes.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
scenesURLsTableMgr.join(f, joinType, "", "scenes.id")
|
||||
},
|
||||
}
|
||||
|
||||
return h.handler(url)
|
||||
}
|
||||
|
||||
func (qb *sceneFilterHandler) getMultiCriterionHandlerBuilder(foreignTable, joinTable, foreignFK string, addJoinsFunc func(f *filterBuilder)) multiCriterionHandlerBuilder {
|
||||
func (qb *sceneFilterHandler) getMultiCriterionHandlerBuilder(foreignTable, joinTable, foreignFK string, addJoinsFunc func(f *filterBuilder, joinType joinType)) multiCriterionHandlerBuilder {
|
||||
return multiCriterionHandlerBuilder{
|
||||
primaryTable: sceneTable,
|
||||
foreignTable: foreignTable,
|
||||
|
|
@ -469,9 +486,9 @@ func (qb *sceneFilterHandler) captionCriterionHandler(captions *models.StringCri
|
|||
primaryFK: sceneIDColumn,
|
||||
joinTable: videoCaptionsTable,
|
||||
stringColumn: captionCodeColumn,
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
qb.addSceneFilesTable(f)
|
||||
f.addLeftJoin(videoCaptionsTable, "", "video_captions.file_id = scenes_files.file_id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
qb.addSceneFilesTable(f, joinTypeLeft)
|
||||
f.addJoin(joinType, videoCaptionsTable, "", "video_captions.file_id = scenes_files.file_id")
|
||||
},
|
||||
excludeHandler: func(f *filterBuilder, criterion *models.StringCriterionInput) {
|
||||
excludeClause := `scenes.id NOT IN (
|
||||
|
|
@ -531,8 +548,8 @@ func (qb *sceneFilterHandler) performersCriterionHandler(performers *models.Mult
|
|||
primaryFK: sceneIDColumn,
|
||||
foreignFK: performerIDColumn,
|
||||
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
sceneRepository.performers.join(f, "performers_join", "scenes.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
sceneRepository.performers.join(f, joinType, "performers_join", "scenes.id")
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -587,9 +604,9 @@ func (qb *sceneFilterHandler) performerAgeCriterionHandler(performerAge *models.
|
|||
|
||||
// legacy handler
|
||||
func (qb *sceneFilterHandler) moviesCriterionHandler(movies *models.MultiCriterionInput) criterionHandlerFunc {
|
||||
addJoinsFunc := func(f *filterBuilder) {
|
||||
sceneRepository.groups.join(f, "", "scenes.id")
|
||||
f.addLeftJoin("groups", "", "groups_scenes.group_id = groups.id")
|
||||
addJoinsFunc := func(f *filterBuilder, joinType joinType) {
|
||||
sceneRepository.groups.leftJoin(f, "", "scenes.id")
|
||||
f.addJoin(joinType, "groups", "", "groups_scenes.group_id = groups.id")
|
||||
}
|
||||
h := qb.getMultiCriterionHandlerBuilder(groupTable, groupsScenesTable, "group_id", addJoinsFunc)
|
||||
return h.handler(movies)
|
||||
|
|
@ -613,9 +630,9 @@ func (qb *sceneFilterHandler) groupsCriterionHandler(groups *models.Hierarchical
|
|||
}
|
||||
|
||||
func (qb *sceneFilterHandler) galleriesCriterionHandler(galleries *models.MultiCriterionInput) criterionHandlerFunc {
|
||||
addJoinsFunc := func(f *filterBuilder) {
|
||||
sceneRepository.galleries.join(f, "", "scenes.id")
|
||||
f.addLeftJoin("galleries", "", "scenes_galleries.gallery_id = galleries.id")
|
||||
addJoinsFunc := func(f *filterBuilder, joinType joinType) {
|
||||
sceneRepository.galleries.leftJoin(f, "", "scenes.id")
|
||||
f.addJoin(joinType, "galleries", "", "scenes_galleries.gallery_id = galleries.id")
|
||||
}
|
||||
h := qb.getMultiCriterionHandlerBuilder(galleryTable, scenesGalleriesTable, "gallery_id", addJoinsFunc)
|
||||
return h.handler(galleries)
|
||||
|
|
|
|||
|
|
@ -173,8 +173,8 @@ func (qb *sceneMarkerFilterHandler) performersCriterionHandler(performers *model
|
|||
primaryFK: sceneIDColumn,
|
||||
foreignFK: performerIDColumn,
|
||||
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
f.addLeftJoin(performersScenesTable, "performers_join", "performers_join.scene_id = scene_markers.scene_id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
f.addJoin(joinType, performersScenesTable, "performers_join", "performers_join.scene_id = scene_markers.scene_id")
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -191,8 +191,8 @@ func (qb *sceneMarkerFilterHandler) performersCriterionHandler(performers *model
|
|||
}
|
||||
|
||||
func (qb *sceneMarkerFilterHandler) scenesCriterionHandler(scenes *models.MultiCriterionInput) criterionHandlerFunc {
|
||||
addJoinsFunc := func(f *filterBuilder) {
|
||||
f.addLeftJoin(sceneTable, "markers_scenes", "markers_scenes.id = scene_markers.scene_id")
|
||||
addJoinsFunc := func(f *filterBuilder, joinType joinType) {
|
||||
f.addJoin(joinType, sceneTable, "markers_scenes", "markers_scenes.id = scene_markers.scene_id")
|
||||
}
|
||||
h := multiCriterionHandlerBuilder{
|
||||
primaryTable: sceneMarkerTable,
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ func (qb *studioFilterHandler) criterionHandler() criterionHandler {
|
|||
|
||||
criterionHandlerFunc(func(ctx context.Context, f *filterBuilder) {
|
||||
if studioFilter.StashID != nil {
|
||||
studioRepository.stashIDs.join(f, "studio_stash_ids", "studios.id")
|
||||
studioRepository.stashIDs.leftJoin(f, "studio_stash_ids", "studios.id")
|
||||
stringCriterionHandler(studioFilter.StashID, "studio_stash_ids.stash_id")(ctx, f)
|
||||
}
|
||||
}),
|
||||
|
|
@ -143,15 +143,15 @@ func (qb *studioFilterHandler) isMissingCriterionHandler(isMissing *string) crit
|
|||
if isMissing != nil && *isMissing != "" {
|
||||
switch *isMissing {
|
||||
case "url":
|
||||
studiosURLsTableMgr.join(f, "", "studios.id")
|
||||
studiosURLsTableMgr.leftJoin(f, "", "studios.id")
|
||||
f.addWhere("studio_urls.url IS NULL")
|
||||
case "image":
|
||||
f.addWhere("studios.image_blob IS NULL")
|
||||
case "stash_id":
|
||||
studioRepository.stashIDs.join(f, "studio_stash_ids", "studios.id")
|
||||
studioRepository.stashIDs.leftJoin(f, "studio_stash_ids", "studios.id")
|
||||
f.addWhere("studio_stash_ids.studio_id IS NULL")
|
||||
case "aliases":
|
||||
studiosAliasesTableMgr.join(f, "", "studios.id")
|
||||
studiosAliasesTableMgr.leftJoin(f, "", "studios.id")
|
||||
f.addWhere("studio_aliases.alias IS NULL")
|
||||
case "tags":
|
||||
f.addLeftJoin(studiosTagsTable, "tags_join", "tags_join.studio_id = studios.id")
|
||||
|
|
@ -224,8 +224,8 @@ func (qb *studioFilterHandler) tagCountCriterionHandler(tagCount *models.IntCrit
|
|||
}
|
||||
|
||||
func (qb *studioFilterHandler) parentCriterionHandler(parents *models.MultiCriterionInput) criterionHandlerFunc {
|
||||
addJoinsFunc := func(f *filterBuilder) {
|
||||
f.addLeftJoin("studios", "parent_studio", "parent_studio.id = studios.parent_id")
|
||||
addJoinsFunc := func(f *filterBuilder, joinType joinType) {
|
||||
f.addJoin(joinType, "studios", "parent_studio", "parent_studio.id = studios.parent_id")
|
||||
}
|
||||
h := multiCriterionHandlerBuilder{
|
||||
primaryTable: studioTable,
|
||||
|
|
@ -244,8 +244,8 @@ func (qb *studioFilterHandler) aliasCriterionHandler(alias *models.StringCriteri
|
|||
primaryFK: studioIDColumn,
|
||||
joinTable: studioAliasesTable,
|
||||
stringColumn: studioAliasColumn,
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
studiosAliasesTableMgr.join(f, "", "studios.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
studiosAliasesTableMgr.join(f, joinType, "", "studios.id")
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -258,8 +258,8 @@ func (qb *studioFilterHandler) urlsCriterionHandler(url *models.StringCriterionI
|
|||
primaryFK: studioIDColumn,
|
||||
joinTable: studioURLsTable,
|
||||
stringColumn: studioURLColumn,
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
studiosURLsTableMgr.join(f, "", "studios.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
studiosURLsTableMgr.join(f, joinType, "", "studios.id")
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -129,7 +129,22 @@ func (t *table) destroy(ctx context.Context, ids []int) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *table) join(j joiner, as string, parentIDCol string) {
|
||||
func (t *table) join(j joiner, jt joinType, as string, parentIDCol string) {
|
||||
tableName := t.table.GetTable()
|
||||
tt := tableName
|
||||
if as != "" {
|
||||
tt = as
|
||||
}
|
||||
|
||||
fn := j.addInnerJoin
|
||||
if jt == joinTypeLeft {
|
||||
fn = j.addLeftJoin
|
||||
}
|
||||
|
||||
fn(tableName, as, fmt.Sprintf("%s.%s = %s", tt, t.idColumn.GetCol(), parentIDCol))
|
||||
}
|
||||
|
||||
func (t *table) leftJoin(j joiner, as string, parentIDCol string) {
|
||||
tableName := t.table.GetTable()
|
||||
tt := tableName
|
||||
if as != "" {
|
||||
|
|
|
|||
|
|
@ -184,8 +184,8 @@ func (qb *tagFilterHandler) aliasCriterionHandler(alias *models.StringCriterionI
|
|||
primaryFK: tagIDColumn,
|
||||
joinTable: tagAliasesTable,
|
||||
stringColumn: tagAliasColumn,
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
tagRepository.aliases.join(f, "", "tags.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
tagRepository.aliases.join(f, joinType, "", "tags.id")
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -199,10 +199,10 @@ func (qb *tagFilterHandler) isMissingCriterionHandler(isMissing *string) criteri
|
|||
case "image":
|
||||
f.addWhere("tags.image_blob IS NULL")
|
||||
case "aliases":
|
||||
tagRepository.aliases.join(f, "", "tags.id")
|
||||
tagRepository.aliases.leftJoin(f, "", "tags.id")
|
||||
f.addWhere("tag_aliases.alias IS NULL")
|
||||
case "stash_id":
|
||||
tagRepository.stashIDs.join(f, "tag_stash_ids", "tags.id")
|
||||
tagRepository.stashIDs.leftJoin(f, "tag_stash_ids", "tags.id")
|
||||
f.addWhere("tag_stash_ids.tag_id IS NULL")
|
||||
default:
|
||||
if err := validateIsMissing(*isMissing, []string{
|
||||
|
|
|
|||
Loading…
Reference in a new issue