diff --git a/graphql/documents/data/gallery-slim.graphql b/graphql/documents/data/gallery-slim.graphql index 51dbc3484..c408f8deb 100644 --- a/graphql/documents/data/gallery-slim.graphql +++ b/graphql/documents/data/gallery-slim.graphql @@ -1,4 +1,4 @@ -fragment GallerySlimData on Gallery { +fragment SlimGalleryData on Gallery { id checksum path @@ -10,16 +10,31 @@ fragment GallerySlimData on Gallery { organized image_count cover { - ...SlimImageData + file { + size + width + height + } + + paths { + thumbnail + } } studio { - ...StudioData + id + name + image_path } tags { - ...TagData + id + name } performers { - ...PerformerData + id + name + gender + favorite + image_path } scenes { id diff --git a/graphql/documents/data/gallery.graphql b/graphql/documents/data/gallery.graphql index 7c7fd8e24..d1475157a 100644 --- a/graphql/documents/data/gallery.graphql +++ b/graphql/documents/data/gallery.graphql @@ -15,16 +15,16 @@ fragment GalleryData on Gallery { ...SlimImageData } studio { - ...StudioData + ...SlimStudioData } tags { - ...TagData + ...SlimTagData } performers { ...PerformerData } scenes { - ...SceneData + ...SlimSceneData } } diff --git a/graphql/documents/data/image.graphql b/graphql/documents/data/image.graphql index cf4d30e41..14317988e 100644 --- a/graphql/documents/data/image.graphql +++ b/graphql/documents/data/image.graphql @@ -23,11 +23,11 @@ fragment ImageData on Image { } studio { - ...StudioData + ...SlimStudioData } tags { - ...TagData + ...SlimTagData } performers { diff --git a/graphql/documents/data/movie.graphql b/graphql/documents/data/movie.graphql index ef3ab3f9f..e8e378926 100644 --- a/graphql/documents/data/movie.graphql +++ b/graphql/documents/data/movie.graphql @@ -9,7 +9,7 @@ fragment MovieData on Movie { director studio { - ...StudioData + ...SlimStudioData } synopsis diff --git a/graphql/documents/data/performer.graphql b/graphql/documents/data/performer.graphql index ef0dde256..4c3033c1a 100644 --- a/graphql/documents/data/performer.graphql +++ b/graphql/documents/data/performer.graphql @@ -24,7 +24,7 @@ fragment PerformerData on Performer { gallery_count tags { - ...TagData + ...SlimTagData } stash_ids { diff --git a/graphql/documents/data/scene.graphql b/graphql/documents/data/scene.graphql index 491983b4f..83077895c 100644 --- a/graphql/documents/data/scene.graphql +++ b/graphql/documents/data/scene.graphql @@ -37,11 +37,11 @@ fragment SceneData on Scene { } galleries { - ...GallerySlimData + ...SlimGalleryData } studio { - ...StudioData + ...SlimStudioData } movies { @@ -52,7 +52,7 @@ fragment SceneData on Scene { } tags { - ...TagData + ...SlimTagData } performers { diff --git a/graphql/documents/data/tag-slim.graphql b/graphql/documents/data/tag-slim.graphql new file mode 100644 index 000000000..61fd320e5 --- /dev/null +++ b/graphql/documents/data/tag-slim.graphql @@ -0,0 +1,5 @@ +fragment SlimTagData on Tag { + id + name + image_path +} diff --git a/graphql/documents/queries/gallery.graphql b/graphql/documents/queries/gallery.graphql index c289d9758..bfc034de4 100644 --- a/graphql/documents/queries/gallery.graphql +++ b/graphql/documents/queries/gallery.graphql @@ -2,7 +2,7 @@ query FindGalleries($filter: FindFilterType, $gallery_filter: GalleryFilterType) findGalleries(gallery_filter: $gallery_filter, filter: $filter) { count galleries { - ...GallerySlimData + ...SlimGalleryData } } } diff --git a/pkg/sqlite/filter.go b/pkg/sqlite/filter.go index db3a5b8d0..d851fb7f0 100644 --- a/pkg/sqlite/filter.go +++ b/pkg/sqlite/filter.go @@ -376,6 +376,60 @@ func stringLiteralCriterionHandler(v *string, column string) criterionHandlerFun } } +// handle for MultiCriterion where there is a join table between the new +// objects +type joinedMultiCriterionHandlerBuilder struct { + // table containing the primary objects + primaryTable string + // table joining primary and foreign objects + joinTable string + // alias for join table, if required + joinAs string + // foreign key of the primary object on the join table + primaryFK string + // foreign key of the foreign object on the join table + foreignFK string + + addJoinTable func(f *filterBuilder) +} + +func (m *joinedMultiCriterionHandlerBuilder) handler(criterion *models.MultiCriterionInput) criterionHandlerFunc { + return func(f *filterBuilder) { + if criterion != nil && len(criterion.Value) > 0 { + var args []interface{} + for _, tagID := range criterion.Value { + args = append(args, tagID) + } + + joinAlias := m.joinAs + if joinAlias == "" { + joinAlias = m.joinTable + } + + whereClause := "" + havingClause := "" + if criterion.Modifier == models.CriterionModifierIncludes { + // includes any of the provided ids + m.addJoinTable(f) + whereClause = fmt.Sprintf("%s.%s IN %s", joinAlias, m.foreignFK, getInBinding(len(criterion.Value))) + } else if criterion.Modifier == models.CriterionModifierIncludesAll { + // includes all of the provided ids + m.addJoinTable(f) + 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)) + } else if criterion.Modifier == models.CriterionModifierExcludes { + // excludes all of the provided ids + // need to use actual join table name for this + // not exists (select . from where . = .id and . in ) + whereClause = fmt.Sprintf("not exists (select %[1]s.%[2]s from %[1]s where %[1]s.%[2]s = %[3]s.id and %[1]s.%[4]s in %[5]s)", m.joinTable, m.primaryFK, m.primaryTable, m.foreignFK, getInBinding(len(criterion.Value))) + } + + f.addWhere(whereClause, args...) + f.addHaving(havingClause) + } + } +} + type multiCriterionHandlerBuilder struct { primaryTable string foreignTable string diff --git a/pkg/sqlite/gallery.go b/pkg/sqlite/gallery.go index 466238061..b366d301d 100644 --- a/pkg/sqlite/gallery.go +++ b/pkg/sqlite/gallery.go @@ -321,11 +321,17 @@ func (qb *galleryQueryBuilder) getMultiCriterionHandlerBuilder(foreignTable, joi } func galleryTagsCriterionHandler(qb *galleryQueryBuilder, tags *models.MultiCriterionInput) criterionHandlerFunc { - addJoinsFunc := func(f *filterBuilder) { - qb.tagsRepository().join(f, "tags_join", "galleries.id") - f.addJoin(tagTable, "", "tags_join.tag_id = tags.id") + h := joinedMultiCriterionHandlerBuilder{ + primaryTable: galleryTable, + joinTable: galleriesTagsTable, + joinAs: "tags_join", + primaryFK: galleryIDColumn, + foreignFK: tagIDColumn, + + addJoinTable: func(f *filterBuilder) { + qb.tagsRepository().join(f, "tags_join", "galleries.id") + }, } - h := qb.getMultiCriterionHandlerBuilder(tagTable, galleriesTagsTable, tagIDColumn, addJoinsFunc) return h.handler(tags) } @@ -341,11 +347,17 @@ func galleryTagCountCriterionHandler(qb *galleryQueryBuilder, tagCount *models.I } func galleryPerformersCriterionHandler(qb *galleryQueryBuilder, performers *models.MultiCriterionInput) criterionHandlerFunc { - addJoinsFunc := func(f *filterBuilder) { - qb.performersRepository().join(f, "performers_join", "galleries.id") - f.addJoin(performerTable, "", "performers_join.performer_id = performers.id") + h := joinedMultiCriterionHandlerBuilder{ + primaryTable: galleryTable, + joinTable: performersGalleriesTable, + joinAs: "performers_join", + primaryFK: galleryIDColumn, + foreignFK: performerIDColumn, + + addJoinTable: func(f *filterBuilder) { + qb.performersRepository().join(f, "performers_join", "galleries.id") + }, } - h := qb.getMultiCriterionHandlerBuilder(performerTable, performersGalleriesTable, performerIDColumn, addJoinsFunc) return h.handler(performers) } diff --git a/pkg/sqlite/image.go b/pkg/sqlite/image.go index 61ece04c6..51cc0d945 100644 --- a/pkg/sqlite/image.go +++ b/pkg/sqlite/image.go @@ -347,11 +347,17 @@ func (qb *imageQueryBuilder) getMultiCriterionHandlerBuilder(foreignTable, joinT } func imageTagsCriterionHandler(qb *imageQueryBuilder, tags *models.MultiCriterionInput) criterionHandlerFunc { - addJoinsFunc := func(f *filterBuilder) { - qb.tagsRepository().join(f, "tags_join", "images.id") - f.addJoin(tagTable, "", "tags_join.tag_id = tags.id") + h := joinedMultiCriterionHandlerBuilder{ + primaryTable: imageTable, + joinTable: imagesTagsTable, + joinAs: "tags_join", + primaryFK: imageIDColumn, + foreignFK: tagIDColumn, + + addJoinTable: func(f *filterBuilder) { + qb.tagsRepository().join(f, "tags_join", "images.id") + }, } - h := qb.getMultiCriterionHandlerBuilder(tagTable, imagesTagsTable, tagIDColumn, addJoinsFunc) return h.handler(tags) } @@ -377,11 +383,17 @@ func imageGalleriesCriterionHandler(qb *imageQueryBuilder, galleries *models.Mul } func imagePerformersCriterionHandler(qb *imageQueryBuilder, performers *models.MultiCriterionInput) criterionHandlerFunc { - addJoinsFunc := func(f *filterBuilder) { - qb.performersRepository().join(f, "performers_join", "images.id") - f.addJoin(performerTable, "", "performers_join.performer_id = performers.id") + h := joinedMultiCriterionHandlerBuilder{ + primaryTable: imageTable, + joinTable: performersImagesTable, + joinAs: "performers_join", + primaryFK: imageIDColumn, + foreignFK: performerIDColumn, + + addJoinTable: func(f *filterBuilder) { + qb.performersRepository().join(f, "performers_join", "images.id") + }, } - h := qb.getMultiCriterionHandlerBuilder(performerTable, performersImagesTable, performerIDColumn, addJoinsFunc) return h.handler(performers) } diff --git a/pkg/sqlite/scene.go b/pkg/sqlite/scene.go index 8ed7a711e..c3780a573 100644 --- a/pkg/sqlite/scene.go +++ b/pkg/sqlite/scene.go @@ -562,11 +562,17 @@ func sceneTagCountCriterionHandler(qb *sceneQueryBuilder, tagCount *models.IntCr } func scenePerformersCriterionHandler(qb *sceneQueryBuilder, performers *models.MultiCriterionInput) criterionHandlerFunc { - addJoinsFunc := func(f *filterBuilder) { - qb.performersRepository().join(f, "performers_join", "scenes.id") - f.addJoin("performers", "", "performers_join.performer_id = performers.id") + h := joinedMultiCriterionHandlerBuilder{ + primaryTable: sceneTable, + joinTable: performersScenesTable, + joinAs: "performers_join", + primaryFK: sceneIDColumn, + foreignFK: performerIDColumn, + + addJoinTable: func(f *filterBuilder) { + qb.performersRepository().join(f, "performers_join", "scenes.id") + }, } - h := qb.getMultiCriterionHandlerBuilder(performerTable, performersScenesTable, performerIDColumn, addJoinsFunc) return h.handler(performers) } diff --git a/ui/v2.5/src/components/Galleries/EditGalleriesDialog.tsx b/ui/v2.5/src/components/Galleries/EditGalleriesDialog.tsx index f54668ca9..78ce68b60 100644 --- a/ui/v2.5/src/components/Galleries/EditGalleriesDialog.tsx +++ b/ui/v2.5/src/components/Galleries/EditGalleriesDialog.tsx @@ -10,7 +10,7 @@ import MultiSet from "../Shared/MultiSet"; import { RatingStars } from "../Scenes/SceneDetails/RatingStars"; interface IListOperationProps { - selected: GQL.GallerySlimDataFragment[]; + selected: GQL.SlimGalleryDataFragment[]; onClose: (applied: boolean) => void; } @@ -146,7 +146,7 @@ export const EditGalleriesDialog: React.FC = ( setIsUpdating(false); } - function getRating(state: GQL.GallerySlimDataFragment[]) { + function getRating(state: GQL.SlimGalleryDataFragment[]) { let ret: number | undefined; let first = true; @@ -162,7 +162,7 @@ export const EditGalleriesDialog: React.FC = ( return ret; } - function getStudioId(state: GQL.GallerySlimDataFragment[]) { + function getStudioId(state: GQL.SlimGalleryDataFragment[]) { let ret: string | undefined; let first = true; @@ -181,7 +181,7 @@ export const EditGalleriesDialog: React.FC = ( return ret; } - function getPerformerIds(state: GQL.GallerySlimDataFragment[]) { + function getPerformerIds(state: GQL.SlimGalleryDataFragment[]) { let ret: string[] = []; let first = true; @@ -205,7 +205,7 @@ export const EditGalleriesDialog: React.FC = ( return ret; } - function getTagIds(state: GQL.GallerySlimDataFragment[]) { + function getTagIds(state: GQL.SlimGalleryDataFragment[]) { let ret: string[] = []; let first = true; @@ -234,7 +234,7 @@ export const EditGalleriesDialog: React.FC = ( let updateOrganized: boolean | undefined; let first = true; - state.forEach((gallery: GQL.GallerySlimDataFragment) => { + state.forEach((gallery: GQL.SlimGalleryDataFragment) => { const galleryRating = gallery.rating; const GalleriestudioID = gallery?.studio?.id; const galleryPerformerIDs = (gallery.performers ?? []) diff --git a/ui/v2.5/src/components/Galleries/GalleryCard.tsx b/ui/v2.5/src/components/Galleries/GalleryCard.tsx index 6bbb4a952..0a1f8c458 100644 --- a/ui/v2.5/src/components/Galleries/GalleryCard.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryCard.tsx @@ -15,7 +15,7 @@ import { TextUtils } from "src/utils"; import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton"; interface IProps { - gallery: GQL.GallerySlimDataFragment; + gallery: GQL.SlimGalleryDataFragment; selecting?: boolean; selected?: boolean | undefined; zoomIndex?: number; diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryScenesPanel.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryScenesPanel.tsx index 8f21d9798..e2d5d4814 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryScenesPanel.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryScenesPanel.tsx @@ -3,7 +3,7 @@ import * as GQL from "src/core/generated-graphql"; import { SceneCard } from "src/components/Scenes/SceneCard"; interface IGalleryScenesPanelProps { - scenes: GQL.SceneDataFragment[]; + scenes: GQL.SlimSceneDataFragment[]; } export const GalleryScenesPanel: React.FC = ({ diff --git a/ui/v2.5/src/components/Galleries/GalleryList.tsx b/ui/v2.5/src/components/Galleries/GalleryList.tsx index 8e3096a16..ee56499ca 100644 --- a/ui/v2.5/src/components/Galleries/GalleryList.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryList.tsx @@ -5,7 +5,7 @@ import { Link, useHistory } from "react-router-dom"; import Mousetrap from "mousetrap"; import { FindGalleriesQueryResult, - GallerySlimDataFragment, + SlimGalleryDataFragment, } from "src/core/generated-graphql"; import { useGalleriesList } from "src/hooks"; import { TextUtils } from "src/utils"; @@ -130,7 +130,7 @@ export const GalleryList: React.FC = ({ } function renderEditGalleriesDialog( - selectedImages: GallerySlimDataFragment[], + selectedImages: SlimGalleryDataFragment[], onClose: (applied: boolean) => void ) { return ( @@ -141,7 +141,7 @@ export const GalleryList: React.FC = ({ } function renderDeleteGalleriesDialog( - selectedImages: GallerySlimDataFragment[], + selectedImages: SlimGalleryDataFragment[], onClose: (confirmed: boolean) => void ) { return ( diff --git a/ui/v2.5/src/components/Galleries/GalleryWallCard.tsx b/ui/v2.5/src/components/Galleries/GalleryWallCard.tsx index b724ea09b..d2d02aba1 100644 --- a/ui/v2.5/src/components/Galleries/GalleryWallCard.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryWallCard.tsx @@ -12,7 +12,7 @@ const CLASSNAME_IMG = `${CLASSNAME}-img`; const CLASSNAME_TITLE = `${CLASSNAME}-title`; interface IProps { - gallery: GQL.GallerySlimDataFragment; + gallery: GQL.SlimGalleryDataFragment; } const GalleryWallCard: React.FC = ({ gallery }) => { diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneGalleriesPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneGalleriesPanel.tsx index eeb8c8392..30b8ba83d 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneGalleriesPanel.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneGalleriesPanel.tsx @@ -3,7 +3,7 @@ import * as GQL from "src/core/generated-graphql"; import { GalleryCard } from "src/components/Galleries/GalleryCard"; interface ISceneGalleriesPanelProps { - galleries: GQL.GallerySlimDataFragment[]; + galleries: GQL.SlimGalleryDataFragment[]; } export const SceneGalleriesPanel: React.FC = ({ diff --git a/ui/v2.5/src/hooks/ListHook.tsx b/ui/v2.5/src/hooks/ListHook.tsx index c2891c4ff..6108c9acb 100644 --- a/ui/v2.5/src/hooks/ListHook.tsx +++ b/ui/v2.5/src/hooks/ListHook.tsx @@ -13,7 +13,7 @@ import Mousetrap from "mousetrap"; import { SlimSceneDataFragment, SceneMarkerDataFragment, - GallerySlimDataFragment, + SlimGalleryDataFragment, StudioDataFragment, PerformerDataFragment, FindScenesQueryResult, @@ -621,9 +621,9 @@ export const useImagesList = ( }); export const useGalleriesList = ( - props: IListHookOptions + props: IListHookOptions ) => - useList({ + useList({ ...props, filterMode: FilterMode.Galleries, useData: useFindGalleries,