diff --git a/graphql/schema/types/filters.graphql b/graphql/schema/types/filters.graphql index 23ec4ca48..da309bead 100644 --- a/graphql/schema/types/filters.graphql +++ b/graphql/schema/types/filters.graphql @@ -330,6 +330,8 @@ input SceneFilterType { groups_filter: GroupFilterType "Filter by related markers that meet this criteria" markers_filter: SceneMarkerFilterType + "Filter by related files that meet this criteria" + files_filter: FileFilterType } input MovieFilterType { @@ -534,6 +536,10 @@ input GalleryFilterType { studios_filter: StudioFilterType "Filter by related tags that meet this criteria" tags_filter: TagFilterType + "Filter by related files that meet this criteria" + files_filter: FileFilterType + "Filter by related folders that meet this criteria" + folders_filter: FolderFilterType } input TagFilterType { @@ -679,6 +685,8 @@ input ImageFilterType { studios_filter: StudioFilterType "Filter by related tags that meet this criteria" tags_filter: TagFilterType + "Filter by related files that meet this criteria" + files_filter: FileFilterType } input FileFilterType { diff --git a/pkg/models/gallery.go b/pkg/models/gallery.go index 73fa287d2..5b75febc5 100644 --- a/pkg/models/gallery.go +++ b/pkg/models/gallery.go @@ -59,6 +59,10 @@ type GalleryFilterType struct { StudiosFilter *StudioFilterType `json:"studios_filter"` // Filter by related tags that meet this criteria TagsFilter *TagFilterType `json:"tags_filter"` + // Filter by related files that meet this criteria + FilesFilter *FileFilterType `json:"files_filter"` + // Filter by related folders that meet this criteria + FoldersFilter *FolderFilterType `json:"folders_filter"` // Filter by created at CreatedAt *TimestampCriterionInput `json:"created_at"` // Filter by updated at diff --git a/pkg/models/image.go b/pkg/models/image.go index 9d2c6f016..4ab10eabf 100644 --- a/pkg/models/image.go +++ b/pkg/models/image.go @@ -57,6 +57,8 @@ type ImageFilterType struct { StudiosFilter *StudioFilterType `json:"studios_filter"` // Filter by related tags that meet this criteria TagsFilter *TagFilterType `json:"tags_filter"` + // Filter by related files that meet this criteria + FilesFilter *FileFilterType `json:"files_filter"` // Filter by created at CreatedAt *TimestampCriterionInput `json:"created_at"` // Filter by updated at diff --git a/pkg/models/scene.go b/pkg/models/scene.go index 9f28d40ba..f0a863bf7 100644 --- a/pkg/models/scene.go +++ b/pkg/models/scene.go @@ -111,6 +111,8 @@ type SceneFilterType struct { MoviesFilter *GroupFilterType `json:"movies_filter"` // Filter by related markers that meet this criteria MarkersFilter *SceneMarkerFilterType `json:"markers_filter"` + // Filter by related files that meet this criteria + FilesFilter *FileFilterType `json:"files_filter"` // Filter by created at CreatedAt *TimestampCriterionInput `json:"created_at"` // Filter by updated at diff --git a/pkg/sqlite/criterion_handlers.go b/pkg/sqlite/criterion_handlers.go index 82b9cfc65..fe6d1fcb5 100644 --- a/pkg/sqlite/criterion_handlers.go +++ b/pkg/sqlite/criterion_handlers.go @@ -1041,6 +1041,7 @@ type relatedFilterHandler struct { relatedRepo repository relatedHandler criterionHandler joinFn func(f *filterBuilder) + directJoin bool } func (h *relatedFilterHandler) handle(ctx context.Context, f *filterBuilder) { @@ -1054,6 +1055,16 @@ func (h *relatedFilterHandler) handle(ctx context.Context, f *filterBuilder) { return } + if h.joinFn != nil { + h.joinFn(f) + } + + if h.directJoin { + // rerun handler using existing filter builder + h.relatedHandler.handle(ctx, f) + return + } + subQuery := h.relatedRepo.newQuery() selectIDs(&subQuery, subQuery.repository.tableName) if err := subQuery.addFilter(ff); err != nil { @@ -1061,9 +1072,5 @@ func (h *relatedFilterHandler) handle(ctx context.Context, f *filterBuilder) { return } - if h.joinFn != nil { - h.joinFn(f) - } - f.addWhere(fmt.Sprintf("%s IN ("+subQuery.toSQL(false)+")", h.relatedIDCol), subQuery.args...) } diff --git a/pkg/sqlite/file.go b/pkg/sqlite/file.go index ad3442ff7..2144356c5 100644 --- a/pkg/sqlite/file.go +++ b/pkg/sqlite/file.go @@ -285,7 +285,7 @@ type fileRepositoryType struct { var ( fileRepository = fileRepositoryType{ repository: repository{ - tableName: sceneTable, + tableName: fileTable, idColumn: idColumn, }, scenes: joinRepository{ diff --git a/pkg/sqlite/file_filter.go b/pkg/sqlite/file_filter.go index 60ca01648..12c7ba3d5 100644 --- a/pkg/sqlite/file_filter.go +++ b/pkg/sqlite/file_filter.go @@ -10,6 +10,8 @@ import ( type fileFilterHandler struct { fileFilter *models.FileFilterType + // if true, don't allow use of related filters + isRelated bool } func (qb *fileFilterHandler) validate() error { @@ -22,8 +24,12 @@ func (qb *fileFilterHandler) validate() error { return err } + if qb.isRelated && (fileFilter.ScenesFilter != nil || fileFilter.ImagesFilter != nil || fileFilter.GalleriesFilter != nil) { + return fmt.Errorf("cannot use related filters inside a related filter") + } + if subFilter := fileFilter.SubFilter(); subFilter != nil { - sqb := &fileFilterHandler{fileFilter: subFilter} + sqb := &fileFilterHandler{fileFilter: subFilter, isRelated: qb.isRelated} if err := sqb.validate(); err != nil { return err } @@ -45,7 +51,7 @@ func (qb *fileFilterHandler) handle(ctx context.Context, f *filterBuilder) { sf := fileFilter.SubFilter() if sf != nil { - sub := &fileFilterHandler{sf} + sub := &fileFilterHandler{sf, qb.isRelated} handleSubFilter(ctx, sub, f, fileFilter.OperatorFilter) } diff --git a/pkg/sqlite/folder_filter.go b/pkg/sqlite/folder_filter.go index 2fda0d1e3..6b2bd96e9 100644 --- a/pkg/sqlite/folder_filter.go +++ b/pkg/sqlite/folder_filter.go @@ -9,6 +9,8 @@ import ( type folderFilterHandler struct { folderFilter *models.FolderFilterType + table sqlTable + isRelated bool } func (qb *folderFilterHandler) validate() error { @@ -21,8 +23,12 @@ func (qb *folderFilterHandler) validate() error { return err } + if qb.isRelated && (folderFilter.GalleriesFilter != nil) { + return fmt.Errorf("cannot use related filters inside a related filter") + } + if subFilter := folderFilter.SubFilter(); subFilter != nil { - sqb := &folderFilterHandler{folderFilter: subFilter} + sqb := &folderFilterHandler{folderFilter: subFilter, isRelated: qb.isRelated} if err := sqb.validate(); err != nil { return err } @@ -44,7 +50,7 @@ func (qb *folderFilterHandler) handle(ctx context.Context, f *filterBuilder) { sf := folderFilter.SubFilter() if sf != nil { - sub := &folderFilterHandler{sf} + sub := &folderFilterHandler{folderFilter: sf, table: qb.table} handleSubFilter(ctx, sub, f, folderFilter.OperatorFilter) } @@ -52,25 +58,29 @@ func (qb *folderFilterHandler) handle(ctx context.Context, f *filterBuilder) { } func (qb *folderFilterHandler) criterionHandler() criterionHandler { + if qb.table == "" { + qb.table = folderTable + } + folderFilter := qb.folderFilter return compoundHandler{ - stringCriterionHandler(folderFilter.Path, "folders.path"), - ×tampCriterionHandler{folderFilter.ModTime, "folders.mod_time", nil}, + stringCriterionHandler(folderFilter.Path, qb.table.Col("path")), + ×tampCriterionHandler{folderFilter.ModTime, qb.table.Col("mod_time"), nil}, qb.parentFolderCriterionHandler(folderFilter.ParentFolder), qb.zipFileCriterionHandler(folderFilter.ZipFile), qb.galleryCountCriterionHandler(folderFilter.GalleryCount), - ×tampCriterionHandler{folderFilter.CreatedAt, "folders.created_at", nil}, - ×tampCriterionHandler{folderFilter.UpdatedAt, "folders.updated_at", nil}, + ×tampCriterionHandler{folderFilter.CreatedAt, qb.table.Col("created_at"), nil}, + ×tampCriterionHandler{folderFilter.UpdatedAt, qb.table.Col("updated_at"), nil}, &relatedFilterHandler{ - relatedIDCol: "galleries.id", + relatedIDCol: qb.table.Col("id"), relatedRepo: galleryRepository.repository, relatedHandler: &galleryFilterHandler{folderFilter.GalleriesFilter}, joinFn: func(f *filterBuilder) { - folderRepository.galleries.innerJoin(f, "", "folders.id") + folderRepository.galleries.innerJoin(f, "", qb.table.Col("id")) }, }, } @@ -85,7 +95,7 @@ func (qb *folderFilterHandler) zipFileCriterionHandler(criterion *models.MultiCr notClause = "NOT" } - f.addWhere(fmt.Sprintf("folders.zip_file_id IS %s NULL", notClause)) + f.addWhere(fmt.Sprintf("%s.zip_file_id IS %s NULL", qb.table.Name(), notClause)) return } @@ -102,9 +112,9 @@ func (qb *folderFilterHandler) zipFileCriterionHandler(criterion *models.MultiCr havingClause := "" switch criterion.Modifier { case models.CriterionModifierIncludes: - whereClause = "folders.zip_file_id IN " + getInBinding(len(criterion.Value)) + whereClause = fmt.Sprintf("%s.zip_file_id IN %s", qb.table.Name(), getInBinding(len(criterion.Value))) case models.CriterionModifierExcludes: - whereClause = "folders.zip_file_id NOT IN " + getInBinding(len(criterion.Value)) + whereClause = fmt.Sprintf("%s.zip_file_id NOT IN %s", qb.table.Name(), getInBinding(len(criterion.Value))) } f.addWhere(whereClause, args...) @@ -128,8 +138,8 @@ func (qb *folderFilterHandler) parentFolderCriterionHandler(folder *models.Hiera } hh := hierarchicalMultiCriterionHandlerBuilder{ - primaryTable: folderTable, - foreignTable: folderTable, + primaryTable: qb.table.Name(), + foreignTable: qb.table.Name(), foreignFK: "parent_folder_id", parentFK: "parent_folder_id", } diff --git a/pkg/sqlite/gallery_filter.go b/pkg/sqlite/gallery_filter.go index 18718c511..f05ff7b81 100644 --- a/pkg/sqlite/gallery_filter.go +++ b/pkg/sqlite/gallery_filter.go @@ -146,6 +146,36 @@ func (qb *galleryFilterHandler) criterionHandler() criterionHandler { galleryRepository.tags.innerJoin(f, "gallery_tag", "galleries.id") }, }, + + &relatedFilterHandler{ + relatedIDCol: "files.id", + relatedRepo: fileRepository.repository, + relatedHandler: &fileFilterHandler{ + fileFilter: filter.FilesFilter, + isRelated: true, + }, + joinFn: func(f *filterBuilder) { + galleryRepository.addFilesTable(f) + galleryRepository.addFoldersTable(f) + }, + // don't use a subquery; join directly + directJoin: true, + }, + + &relatedFilterHandler{ + relatedIDCol: "gallery_folder.id", + relatedRepo: folderRepository.repository, + relatedHandler: &folderFilterHandler{ + folderFilter: filter.FoldersFilter, + table: "gallery_folder", + isRelated: true, + }, + joinFn: func(f *filterBuilder) { + f.addLeftJoin(folderTable, "gallery_folder", "galleries.folder_id = gallery_folder.id") + }, + // don't use a subquery; join directly + directJoin: true, + }, } } diff --git a/pkg/sqlite/image_filter.go b/pkg/sqlite/image_filter.go index 8f2d5d6b9..1d119bfde 100644 --- a/pkg/sqlite/image_filter.go +++ b/pkg/sqlite/image_filter.go @@ -123,6 +123,21 @@ func (qb *imageFilterHandler) criterionHandler() criterionHandler { imageRepository.tags.innerJoin(f, "image_tag", "images.id") }, }, + + &relatedFilterHandler{ + relatedIDCol: "files.id", + relatedRepo: fileRepository.repository, + relatedHandler: &fileFilterHandler{ + fileFilter: imageFilter.FilesFilter, + isRelated: true, + }, + joinFn: func(f *filterBuilder) { + imageRepository.addFilesTable(f) + imageRepository.addFoldersTable(f) + }, + // don't use a subquery; join directly + directJoin: true, + }, } } diff --git a/pkg/sqlite/scene_filter.go b/pkg/sqlite/scene_filter.go index 86432a4af..fad300248 100644 --- a/pkg/sqlite/scene_filter.go +++ b/pkg/sqlite/scene_filter.go @@ -202,6 +202,21 @@ func (qb *sceneFilterHandler) criterionHandler() criterionHandler { }, }, + &relatedFilterHandler{ + relatedIDCol: "files.id", + relatedRepo: fileRepository.repository, + relatedHandler: &fileFilterHandler{ + fileFilter: sceneFilter.FilesFilter, + isRelated: true, + }, + joinFn: func(f *filterBuilder) { + qb.addFilesTable(f) + qb.addFoldersTable(f) + }, + // don't use a subquery; join directly + directJoin: true, + }, + &relatedFilterHandler{ relatedIDCol: "scene_markers.id", relatedRepo: sceneMarkerRepository.repository, diff --git a/pkg/sqlite/sql.go b/pkg/sqlite/sql.go index 780d2e988..2d5922555 100644 --- a/pkg/sqlite/sql.go +++ b/pkg/sqlite/sql.go @@ -362,3 +362,13 @@ func coalesce(column string) string { func like(v string) string { return "%" + v + "%" } + +type sqlTable string + +func (t sqlTable) Name() string { + return string(t) +} + +func (t sqlTable) Col(n string) string { + return fmt.Sprintf("%s.%s", string(t), n) +}