This commit is contained in:
WithoutPants 2026-03-23 21:12:22 +00:00 committed by GitHub
commit 76a182e0f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 319 additions and 165 deletions

View file

@ -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:

View file

@ -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")
}

View file

@ -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,
}

View file

@ -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")

View file

@ -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")
},
}

View file

@ -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 (

View file

@ -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")
},
}

View file

@ -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")
},
}

View file

@ -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

View file

@ -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)

View file

@ -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,

View file

@ -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")
},
}

View file

@ -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 != "" {

View file

@ -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{