Add related object filter criteria to various filter types in graphql schema (#4861)

* Move filter criterion handlers into separate file
* Add related filters for image filter
* Add related filters for scene filter
* Add related filters to gallery filter
* Add related filters to movie filter
* Add related filters to performer filter
* Add related filters to studio filter
* Add related filters to tag filter
* Add scene filter to scene marker filter
This commit is contained in:
WithoutPants
2024-06-11 11:34:38 +10:00
committed by GitHub
parent ff23d4e20b
commit e843c890fb
41 changed files with 4562 additions and 3805 deletions

View File

@@ -75,24 +75,41 @@ func (r *sceneMarkerRowRecord) fromPartial(o models.SceneMarkerPartial) {
r.setTimestamp("updated_at", o.UpdatedAt)
}
type SceneMarkerStore struct {
type sceneMarkerRepositoryType struct {
repository
tableMgr *table
scenes repository
tags joinRepository
}
func NewSceneMarkerStore() *SceneMarkerStore {
return &SceneMarkerStore{
var (
sceneMarkerRepository = sceneMarkerRepositoryType{
repository: repository{
tableName: sceneMarkerTable,
idColumn: idColumn,
},
tableMgr: sceneMarkerTableMgr,
scenes: repository{
tableName: sceneTable,
idColumn: idColumn,
},
tags: joinRepository{
repository: repository{
tableName: "scene_markers_tags",
idColumn: "scene_marker_id",
},
fkColumn: tagIDColumn,
},
}
)
type SceneMarkerStore struct{}
func NewSceneMarkerStore() *SceneMarkerStore {
return &SceneMarkerStore{}
}
func (qb *SceneMarkerStore) table() exp.IdentifierExpression {
return qb.tableMgr.table
return sceneMarkerTableMgr.table
}
func (qb *SceneMarkerStore) selectDataset() *goqu.SelectDataset {
@@ -103,7 +120,7 @@ func (qb *SceneMarkerStore) Create(ctx context.Context, newObject *models.SceneM
var r sceneMarkerRow
r.fromSceneMarker(*newObject)
id, err := qb.tableMgr.insertID(ctx, r)
id, err := sceneMarkerTableMgr.insertID(ctx, r)
if err != nil {
return err
}
@@ -128,7 +145,7 @@ func (qb *SceneMarkerStore) UpdatePartial(ctx context.Context, id int, partial m
r.fromPartial(partial)
if len(r.Record) > 0 {
if err := qb.tableMgr.updateByID(ctx, id, r.Record); err != nil {
if err := sceneMarkerTableMgr.updateByID(ctx, id, r.Record); err != nil {
return nil, err
}
}
@@ -140,7 +157,7 @@ func (qb *SceneMarkerStore) Update(ctx context.Context, updatedObject *models.Sc
var r sceneMarkerRow
r.fromSceneMarker(*updatedObject)
if err := qb.tableMgr.updateByID(ctx, updatedObject.ID, r); err != nil {
if err := sceneMarkerTableMgr.updateByID(ctx, updatedObject.ID, r); err != nil {
return err
}
@@ -148,7 +165,7 @@ func (qb *SceneMarkerStore) Update(ctx context.Context, updatedObject *models.Sc
}
func (qb *SceneMarkerStore) Destroy(ctx context.Context, id int) error {
return qb.destroyExisting(ctx, []int{id})
return sceneMarkerRepository.destroyExisting(ctx, []int{id})
}
// returns nil, nil if not found
@@ -186,7 +203,7 @@ func (qb *SceneMarkerStore) FindMany(ctx context.Context, ids []int) ([]*models.
// returns nil, sql.ErrNoRows if not found
func (qb *SceneMarkerStore) find(ctx context.Context, id int) (*models.SceneMarker, error) {
q := qb.selectDataset().Where(qb.tableMgr.byID(id))
q := qb.selectDataset().Where(sceneMarkerTableMgr.byID(id))
ret, err := qb.get(ctx, q)
if err != nil {
@@ -243,7 +260,7 @@ func (qb *SceneMarkerStore) FindBySceneID(ctx context.Context, sceneID int) ([]*
func (qb *SceneMarkerStore) CountByTagID(ctx context.Context, tagID int) (int, error) {
args := []interface{}{tagID, tagID}
return qb.runCountQuery(ctx, qb.buildCountQuery(countSceneMarkersForTagQuery), args)
return sceneMarkerRepository.runCountQuery(ctx, sceneMarkerRepository.buildCountQuery(countSceneMarkersForTagQuery), args)
}
func (qb *SceneMarkerStore) GetMarkerStrings(ctx context.Context, q *string, sort *string) ([]*models.MarkerStringsResultType, error) {
@@ -272,21 +289,6 @@ func (qb *SceneMarkerStore) Wall(ctx context.Context, q *string) ([]*models.Scen
return qb.getMany(ctx, qq)
}
func (qb *SceneMarkerStore) makeFilter(ctx context.Context, sceneMarkerFilter *models.SceneMarkerFilterType) *filterBuilder {
query := &filterBuilder{}
query.handleCriterion(ctx, sceneMarkerTagIDCriterionHandler(qb, sceneMarkerFilter.TagID))
query.handleCriterion(ctx, sceneMarkerTagsCriterionHandler(qb, sceneMarkerFilter.Tags))
query.handleCriterion(ctx, sceneMarkerSceneTagsCriterionHandler(qb, sceneMarkerFilter.SceneTags))
query.handleCriterion(ctx, sceneMarkerPerformersCriterionHandler(qb, sceneMarkerFilter.Performers))
query.handleCriterion(ctx, timestampCriterionHandler(sceneMarkerFilter.CreatedAt, "scene_markers.created_at"))
query.handleCriterion(ctx, timestampCriterionHandler(sceneMarkerFilter.UpdatedAt, "scene_markers.updated_at"))
query.handleCriterion(ctx, dateCriterionHandler(sceneMarkerFilter.SceneDate, "scenes.date"))
query.handleCriterion(ctx, timestampCriterionHandler(sceneMarkerFilter.SceneCreatedAt, "scenes.created_at"))
query.handleCriterion(ctx, timestampCriterionHandler(sceneMarkerFilter.SceneUpdatedAt, "scenes.updated_at"))
return query
}
func (qb *SceneMarkerStore) makeQuery(ctx context.Context, sceneMarkerFilter *models.SceneMarkerFilterType, findFilter *models.FindFilterType) (*queryBuilder, error) {
if sceneMarkerFilter == nil {
sceneMarkerFilter = &models.SceneMarkerFilterType{}
@@ -295,7 +297,7 @@ func (qb *SceneMarkerStore) makeQuery(ctx context.Context, sceneMarkerFilter *mo
findFilter = &models.FindFilterType{}
}
query := qb.newQuery()
query := sceneMarkerRepository.newQuery()
distinctIDs(&query, sceneMarkerTable)
if q := findFilter.Q; q != nil && *q != "" {
@@ -304,7 +306,9 @@ func (qb *SceneMarkerStore) makeQuery(ctx context.Context, sceneMarkerFilter *mo
query.parseQueryString(searchColumns, *q)
}
filter := qb.makeFilter(ctx, sceneMarkerFilter)
filter := filterBuilderFromHandler(ctx, &sceneMarkerFilterHandler{
sceneMarkerFilter: sceneMarkerFilter,
})
if err := query.addFilter(filter); err != nil {
return nil, err
@@ -346,135 +350,6 @@ func (qb *SceneMarkerStore) QueryCount(ctx context.Context, sceneMarkerFilter *m
return query.executeCount(ctx)
}
func sceneMarkerTagIDCriterionHandler(qb *SceneMarkerStore, tagID *string) criterionHandlerFunc {
return func(ctx context.Context, f *filterBuilder) {
if tagID != nil {
f.addLeftJoin("scene_markers_tags", "", "scene_markers_tags.scene_marker_id = scene_markers.id")
f.addWhere("(scene_markers.primary_tag_id = ? OR scene_markers_tags.tag_id = ?)", *tagID, *tagID)
}
}
}
func sceneMarkerTagsCriterionHandler(qb *SceneMarkerStore, criterion *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
return func(ctx context.Context, f *filterBuilder) {
if criterion != nil {
tags := criterion.CombineExcludes()
if tags.Modifier == models.CriterionModifierIsNull || tags.Modifier == models.CriterionModifierNotNull {
var notClause string
if tags.Modifier == models.CriterionModifierNotNull {
notClause = "NOT"
}
f.addLeftJoin("scene_markers_tags", "", "scene_markers.id = scene_markers_tags.scene_marker_id")
f.addWhere(fmt.Sprintf("%s scene_markers_tags.tag_id IS NULL", notClause))
return
}
if tags.Modifier == models.CriterionModifierEquals && tags.Depth != nil && *tags.Depth != 0 {
f.setError(fmt.Errorf("depth is not supported for equals modifier for marker tag filtering"))
return
}
if len(tags.Value) == 0 && len(tags.Excludes) == 0 {
return
}
if len(tags.Value) > 0 {
valuesClause, err := getHierarchicalValues(ctx, qb.tx, tags.Value, tagTable, "tags_relations", "parent_id", "child_id", tags.Depth)
if err != nil {
f.setError(err)
return
}
f.addWith(`marker_tags AS (
SELECT mt.scene_marker_id, t.column1 AS root_tag_id FROM scene_markers_tags mt
INNER JOIN (` + valuesClause + `) t ON t.column2 = mt.tag_id
UNION
SELECT m.id, t.column1 FROM scene_markers m
INNER JOIN (` + valuesClause + `) t ON t.column2 = m.primary_tag_id
)`)
f.addLeftJoin("marker_tags", "", "marker_tags.scene_marker_id = scene_markers.id")
switch tags.Modifier {
case models.CriterionModifierEquals:
// includes only the provided ids
f.addWhere("marker_tags.root_tag_id IS NOT NULL")
tagsLen := len(tags.Value)
f.addHaving(fmt.Sprintf("count(distinct marker_tags.root_tag_id) IS %d", tagsLen))
// decrement by one to account for primary tag id
f.addWhere("(SELECT COUNT(*) FROM scene_markers_tags s WHERE s.scene_marker_id = scene_markers.id) = ?", tagsLen-1)
case models.CriterionModifierNotEquals:
f.setError(fmt.Errorf("not equals modifier is not supported for scene marker tags"))
default:
addHierarchicalConditionClauses(f, tags, "marker_tags", "root_tag_id")
}
}
if len(criterion.Excludes) > 0 {
valuesClause, err := getHierarchicalValues(ctx, dbWrapper{}, tags.Excludes, tagTable, "tags_relations", "parent_id", "child_id", tags.Depth)
if err != nil {
f.setError(err)
return
}
clause := "scene_markers.id NOT IN (SELECT scene_markers_tags.scene_marker_id FROM scene_markers_tags WHERE scene_markers_tags.tag_id IN (SELECT column2 FROM (%s)))"
f.addWhere(fmt.Sprintf(clause, valuesClause))
f.addWhere(fmt.Sprintf("scene_markers.primary_tag_id NOT IN (SELECT column2 FROM (%s))", valuesClause))
}
}
}
}
func sceneMarkerSceneTagsCriterionHandler(qb *SceneMarkerStore, tags *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
return func(ctx context.Context, f *filterBuilder) {
if tags != nil {
f.addLeftJoin("scenes_tags", "", "scene_markers.scene_id = scenes_tags.scene_id")
h := joinedHierarchicalMultiCriterionHandlerBuilder{
tx: qb.tx,
primaryTable: "scene_markers",
primaryKey: sceneIDColumn,
foreignTable: tagTable,
foreignFK: tagIDColumn,
relationsTable: "tags_relations",
joinTable: "scenes_tags",
joinAs: "marker_scenes_tags",
primaryFK: sceneIDColumn,
}
h.handler(tags).handle(ctx, f)
}
}
}
func sceneMarkerPerformersCriterionHandler(qb *SceneMarkerStore, performers *models.MultiCriterionInput) criterionHandlerFunc {
h := joinedMultiCriterionHandlerBuilder{
primaryTable: sceneTable,
joinTable: performersScenesTable,
joinAs: "performers_join",
primaryFK: sceneIDColumn,
foreignFK: performerIDColumn,
addJoinTable: func(f *filterBuilder) {
f.addLeftJoin(performersScenesTable, "performers_join", "performers_join.scene_id = scene_markers.scene_id")
},
}
handler := h.handler(performers)
return func(ctx context.Context, f *filterBuilder) {
// Make sure scenes is included, otherwise excludes filter fails
f.addLeftJoin(sceneTable, "", "scenes.id = scene_markers.scene_id")
handler(ctx, f)
}
}
var sceneMarkerSortOptions = sortOptions{
"created_at",
"id",
@@ -514,7 +389,7 @@ func (qb *SceneMarkerStore) setSceneMarkerSort(query *queryBuilder, findFilter *
func (qb *SceneMarkerStore) querySceneMarkers(ctx context.Context, query string, args []interface{}) ([]*models.SceneMarker, error) {
const single = false
var ret []*models.SceneMarker
if err := qb.queryFunc(ctx, query, args, single, func(r *sqlx.Rows) error {
if err := sceneMarkerRepository.queryFunc(ctx, query, args, single, func(r *sqlx.Rows) error {
var f sceneMarkerRow
if err := r.StructScan(&f); err != nil {
return err
@@ -532,7 +407,7 @@ func (qb *SceneMarkerStore) querySceneMarkers(ctx context.Context, query string,
}
func (qb *SceneMarkerStore) queryMarkerStringsResultType(ctx context.Context, query string, args []interface{}) ([]*models.MarkerStringsResultType, error) {
rows, err := qb.tx.Queryx(ctx, query, args...)
rows, err := dbWrapper.Queryx(ctx, query, args...)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, err
}
@@ -554,24 +429,13 @@ func (qb *SceneMarkerStore) queryMarkerStringsResultType(ctx context.Context, qu
return markerStrings, nil
}
func (qb *SceneMarkerStore) tagsRepository() *joinRepository {
return &joinRepository{
repository: repository{
tx: qb.tx,
tableName: "scene_markers_tags",
idColumn: "scene_marker_id",
},
fkColumn: tagIDColumn,
}
}
func (qb *SceneMarkerStore) GetTagIDs(ctx context.Context, id int) ([]int, error) {
return qb.tagsRepository().getIDs(ctx, id)
return sceneMarkerRepository.tags.getIDs(ctx, id)
}
func (qb *SceneMarkerStore) UpdateTags(ctx context.Context, id int, tagIDs []int) error {
// Delete the existing joins and then create new ones
return qb.tagsRepository().replace(ctx, id, tagIDs)
return sceneMarkerRepository.tags.replace(ctx, id, tagIDs)
}
func (qb *SceneMarkerStore) Count(ctx context.Context) (int, error) {