Use inner join when getting images in a gallery (#2083)

* Added joinType to join struct
* Added addInnerJoin function to perform INNER JOIN type of joins
* Added innerJoin function to perform INNER JOIN type of joins
* Use inner joins when querying images in a gallery
* Renamed addJoin to addLeftJoin
This commit is contained in:
Esteban Sanchez 2021-12-06 02:30:40 +01:00 committed by GitHub
parent 2460664dc3
commit 70d9a05580
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 129 additions and 71 deletions

View File

@ -39,6 +39,7 @@ type join struct {
table string
as string
onClause string
joinType string
}
// equals returns true if the other join alias/table is equal to this one
@ -57,11 +58,15 @@ func (j join) alias() string {
func (j join) toSQL() string {
asStr := ""
joinStr := j.joinType
if j.as != "" && j.as != j.table {
asStr = " AS " + j.as
}
if j.joinType == "" {
joinStr = "LEFT"
}
return fmt.Sprintf("LEFT JOIN %s%s ON %s", j.table, asStr, j.onClause)
return fmt.Sprintf("%s JOIN %s%s ON %s", joinStr, j.table, asStr, j.onClause)
}
type joins []join
@ -154,16 +159,33 @@ func (f *filterBuilder) not(n *filterBuilder) {
f.subFilterOp = notOp
}
// addJoin adds a join to the filter. The join is expressed in SQL as:
// 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.
// This method does not add a join if it its alias/table name is already
// present in another existing join.
func (f *filterBuilder) addJoin(table, as, onClause string) {
func (f *filterBuilder) addLeftJoin(table, as, onClause string) {
newJoin := join{
table: table,
as: as,
onClause: onClause,
joinType: "LEFT",
}
f.joins.add(newJoin)
}
// addInnerJoin adds an inner join to the filter. The join is expressed in SQL as:
// INNER 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) addInnerJoin(table, as, onClause string) {
newJoin := join{
table: table,
as: as,
onClause: onClause,
joinType: "INNER",
}
f.joins.add(newJoin)
@ -505,7 +527,7 @@ func (m *multiCriterionHandlerBuilder) handler(criterion *models.MultiCriterionI
table := m.primaryTable
if m.joinTable != "" {
table = m.joinTable
f.addJoin(table, "", fmt.Sprintf("%s.%s = %s.id", table, m.primaryFK, m.primaryTable))
f.addLeftJoin(table, "", fmt.Sprintf("%s.%s = %s.id", table, m.primaryFK, m.primaryTable))
}
f.addWhere(fmt.Sprintf("%s.%s IS %s NULL", table, m.foreignFK, notClause))
@ -698,7 +720,7 @@ func (m *hierarchicalMultiCriterionHandlerBuilder) handler(criterion *models.Hie
valuesClause := getHierarchicalValues(m.tx, criterion.Value, m.foreignTable, m.relationsTable, m.parentFK, criterion.Depth)
f.addJoin("(SELECT column1 AS root_id, column2 AS item_id FROM ("+valuesClause+"))", m.derivedTable, fmt.Sprintf("%s.item_id = %s.%s", m.derivedTable, m.primaryTable, m.foreignFK))
f.addLeftJoin("(SELECT column1 AS root_id, column2 AS item_id FROM ("+valuesClause+"))", m.derivedTable, fmt.Sprintf("%s.item_id = %s.%s", m.derivedTable, m.primaryTable, m.foreignFK))
addHierarchicalConditionClauses(f, criterion, m.derivedTable, "root_id")
}
@ -731,7 +753,7 @@ func (m *joinedHierarchicalMultiCriterionHandlerBuilder) handler(criterion *mode
notClause = "NOT"
}
f.addJoin(m.joinTable, joinAlias, fmt.Sprintf("%s.%s = %s.id", joinAlias, m.primaryFK, m.primaryTable))
f.addLeftJoin(m.joinTable, joinAlias, fmt.Sprintf("%s.%s = %s.id", joinAlias, m.primaryFK, m.primaryTable))
f.addWhere(utils.StrFormat("{table}.{column} IS {not} NULL", utils.StrFormatMap{
"table": joinAlias,
@ -757,7 +779,7 @@ func (m *joinedHierarchicalMultiCriterionHandlerBuilder) handler(criterion *mode
"valuesClause": valuesClause,
})
f.addJoin(joinTable, joinAlias, fmt.Sprintf("%s.%s = %s.id", joinAlias, m.primaryFK, m.primaryTable))
f.addLeftJoin(joinTable, joinAlias, fmt.Sprintf("%s.%s = %s.id", joinAlias, m.primaryFK, m.primaryTable))
addHierarchicalConditionClauses(f, criterion, joinAlias, "root_id")
}

View File

@ -152,36 +152,36 @@ func TestAddJoin(t *testing.T) {
onClause = "onClause1"
)
f.addJoin(table1Name, as1Name, onClause)
f.addLeftJoin(table1Name, as1Name, onClause)
// ensure join is added
assert.Len(f.joins, 1)
assert.Equal(fmt.Sprintf("LEFT JOIN %s AS %s ON %s", table1Name, as1Name, onClause), f.joins[0].toSQL())
// ensure join with same as is not added
f.addJoin(table2Name, as1Name, onClause)
f.addLeftJoin(table2Name, as1Name, onClause)
assert.Len(f.joins, 1)
// ensure same table with different alias can be added
f.addJoin(table1Name, as2Name, onClause)
f.addLeftJoin(table1Name, as2Name, onClause)
assert.Len(f.joins, 2)
assert.Equal(fmt.Sprintf("LEFT JOIN %s AS %s ON %s", table1Name, as2Name, onClause), f.joins[1].toSQL())
// ensure table without alias can be added if tableName != existing alias/tableName
f.addJoin(table1Name, "", onClause)
f.addLeftJoin(table1Name, "", onClause)
assert.Len(f.joins, 3)
assert.Equal(fmt.Sprintf("LEFT JOIN %s ON %s", table1Name, onClause), f.joins[2].toSQL())
// ensure table with alias == table name of a join without alias is not added
f.addJoin(table2Name, table1Name, onClause)
f.addLeftJoin(table2Name, table1Name, onClause)
assert.Len(f.joins, 3)
// ensure table without alias cannot be added if tableName == existing alias
f.addJoin(as2Name, "", onClause)
f.addLeftJoin(as2Name, "", onClause)
assert.Len(f.joins, 3)
// ensure AS is not used if same as table name
f.addJoin(table2Name, table2Name, onClause)
f.addLeftJoin(table2Name, table2Name, onClause)
assert.Len(f.joins, 4)
assert.Equal(fmt.Sprintf("LEFT JOIN %s ON %s", table2Name, onClause), f.joins[3].toSQL())
}
@ -407,7 +407,7 @@ func TestGetAllJoins(t *testing.T) {
onClause = "onClause1"
)
f.addJoin(table1Name, as1Name, onClause)
f.addLeftJoin(table1Name, as1Name, onClause)
// ensure join is returned
joins := f.getAllJoins()
@ -417,14 +417,14 @@ func TestGetAllJoins(t *testing.T) {
// ensure joins in sub-filter are returned
subFilter := &filterBuilder{}
f.and(subFilter)
subFilter.addJoin(table2Name, as2Name, onClause)
subFilter.addLeftJoin(table2Name, as2Name, onClause)
joins = f.getAllJoins()
assert.Len(joins, 2)
assert.Equal(fmt.Sprintf("LEFT JOIN %s AS %s ON %s", table2Name, as2Name, onClause), joins[1].toSQL())
// ensure redundant joins are not returned
subFilter.addJoin(as1Name, "", onClause)
subFilter.addLeftJoin(as1Name, "", onClause)
joins = f.getAllJoins()
assert.Len(joins, 2)
}

View File

@ -290,7 +290,7 @@ func galleryIsMissingCriterionHandler(qb *galleryQueryBuilder, isMissing *string
if isMissing != nil && *isMissing != "" {
switch *isMissing {
case "scenes":
f.addJoin("scenes_galleries", "scenes_join", "scenes_join.gallery_id = galleries.id")
f.addLeftJoin("scenes_galleries", "scenes_join", "scenes_join.gallery_id = galleries.id")
f.addWhere("scenes_join.gallery_id IS NULL")
case "studio":
f.addWhere("galleries.studio_id IS NULL")
@ -395,8 +395,8 @@ func galleryPerformerTagsCriterionHandler(qb *galleryQueryBuilder, tags *models.
notClause = "NOT"
}
f.addJoin("performers_galleries", "", "galleries.id = performers_galleries.gallery_id")
f.addJoin("performers_tags", "", "performers_galleries.performer_id = performers_tags.performer_id")
f.addLeftJoin("performers_galleries", "", "galleries.id = performers_galleries.gallery_id")
f.addLeftJoin("performers_tags", "", "performers_galleries.performer_id = performers_tags.performer_id")
f.addWhere(fmt.Sprintf("performers_tags.tag_id IS %s NULL", notClause))
return
@ -414,7 +414,7 @@ INNER JOIN performers_tags pt ON pt.performer_id = pg.performer_id
INNER JOIN (` + valuesClause + `) t ON t.column2 = pt.tag_id
)`)
f.addJoin("performer_tags", "", "performer_tags.gallery_id = galleries.id")
f.addLeftJoin("performer_tags", "", "performer_tags.gallery_id = galleries.id")
addHierarchicalConditionClauses(f, tags, "performer_tags", "root_tag_id")
}
@ -425,7 +425,7 @@ func galleryAverageResolutionCriterionHandler(qb *galleryQueryBuilder, resolutio
return func(f *filterBuilder) {
if resolution != nil && resolution.Value.IsValid() {
qb.imagesRepository().join(f, "images_join", "galleries.id")
f.addJoin("images", "", "images_join.image_id = images.id")
f.addLeftJoin("images", "", "images_join.image_id = images.id")
min := resolution.Value.GetMinResolution()
max := resolution.Value.GetMaxResolution()

View File

@ -14,7 +14,7 @@ const performersImagesTable = "performers_images"
const imagesTagsTable = "images_tags"
var imagesForGalleryQuery = selectAll(imageTable) + `
LEFT JOIN galleries_images as galleries_join on galleries_join.image_id = images.id
INNER JOIN galleries_images as galleries_join on galleries_join.image_id = images.id
WHERE galleries_join.gallery_id = ?
GROUP BY images.id
`
@ -360,7 +360,7 @@ func imageIsMissingCriterionHandler(qb *imageQueryBuilder, isMissing *string) cr
qb.performersRepository().join(f, "performers_join", "images.id")
f.addWhere("performers_join.image_id IS NULL")
case "galleries":
qb.galleriesRepository().join(f, "galleries_join", "images.id")
qb.galleriesRepository().innerJoin(f, "galleries_join", "images.id")
f.addWhere("galleries_join.image_id IS NULL")
case "tags":
qb.tagsRepository().join(f, "tags_join", "images.id")
@ -412,8 +412,8 @@ func imageTagCountCriterionHandler(qb *imageQueryBuilder, tagCount *models.IntCr
func imageGalleriesCriterionHandler(qb *imageQueryBuilder, galleries *models.MultiCriterionInput) criterionHandlerFunc {
addJoinsFunc := func(f *filterBuilder) {
qb.galleriesRepository().join(f, "galleries_join", "images.id")
f.addJoin(galleryTable, "", "galleries_join.gallery_id = galleries.id")
qb.galleriesRepository().innerJoin(f, "galleries_join", "images.id")
f.addInnerJoin(galleryTable, "", "galleries_join.gallery_id = galleries.id")
}
h := qb.getMultiCriterionHandlerBuilder(galleryTable, galleriesImagesTable, galleryIDColumn, addJoinsFunc)
@ -469,8 +469,8 @@ func imagePerformerTagsCriterionHandler(qb *imageQueryBuilder, tags *models.Hier
notClause = "NOT"
}
f.addJoin("performers_images", "", "images.id = performers_images.image_id")
f.addJoin("performers_tags", "", "performers_images.performer_id = performers_tags.performer_id")
f.addLeftJoin("performers_images", "", "images.id = performers_images.image_id")
f.addLeftJoin("performers_tags", "", "performers_images.performer_id = performers_tags.performer_id")
f.addWhere(fmt.Sprintf("performers_tags.tag_id IS %s NULL", notClause))
return
@ -488,7 +488,7 @@ INNER JOIN performers_tags pt ON pt.performer_id = pi.performer_id
INNER JOIN (` + valuesClause + `) t ON t.column2 = pt.tag_id
)`)
f.addJoin("performer_tags", "", "performer_tags.image_id = images.id")
f.addLeftJoin("performer_tags", "", "performer_tags.image_id = images.id")
addHierarchicalConditionClauses(f, tags, "performer_tags", "root_tag_id")
}

View File

@ -69,6 +69,32 @@ func TestImageFindByPath(t *testing.T) {
})
}
func TestImageFindByGalleryID(t *testing.T) {
withTxn(func(r models.Repository) error {
sqb := r.Image()
images, err := sqb.FindByGalleryID(galleryIDs[galleryIdxWithTwoImages])
if err != nil {
t.Errorf("Error finding images: %s", err.Error())
}
assert.Len(t, images, 2)
assert.Equal(t, imageIDs[imageIdx1WithGallery], images[0].ID)
assert.Equal(t, imageIDs[imageIdx2WithGallery], images[1].ID)
images, err = sqb.FindByGalleryID(galleryIDs[galleryIdxWithScene])
if err != nil {
t.Errorf("Error finding images: %s", err.Error())
}
assert.Len(t, images, 0)
return nil
})
}
func TestImageQueryQ(t *testing.T) {
withTxn(func(r models.Repository) error {
const imageIdx = 2

View File

@ -176,13 +176,13 @@ func movieIsMissingCriterionHandler(qb *movieQueryBuilder, isMissing *string) cr
if isMissing != nil && *isMissing != "" {
switch *isMissing {
case "front_image":
f.addJoin("movies_images", "", "movies_images.movie_id = movies.id")
f.addLeftJoin("movies_images", "", "movies_images.movie_id = movies.id")
f.addWhere("movies_images.front_image IS NULL")
case "back_image":
f.addJoin("movies_images", "", "movies_images.movie_id = movies.id")
f.addLeftJoin("movies_images", "", "movies_images.movie_id = movies.id")
f.addWhere("movies_images.back_image IS NULL")
case "scenes":
f.addJoin("movies_scenes", "", "movies_scenes.movie_id = movies.id")
f.addLeftJoin("movies_scenes", "", "movies_scenes.movie_id = movies.id")
f.addWhere("movies_scenes.scene_id IS NULL")
default:
f.addWhere("(movies." + *isMissing + " IS NULL OR TRIM(movies." + *isMissing + ") = '')")
@ -214,8 +214,8 @@ func moviePerformersCriterionHandler(qb *movieQueryBuilder, performers *models.M
notClause = "NOT"
}
f.addJoin("movies_scenes", "", "movies.id = movies_scenes.movie_id")
f.addJoin("performers_scenes", "", "movies_scenes.scene_id = performers_scenes.scene_id")
f.addLeftJoin("movies_scenes", "", "movies.id = movies_scenes.movie_id")
f.addLeftJoin("performers_scenes", "", "movies_scenes.scene_id = performers_scenes.scene_id")
f.addWhere(fmt.Sprintf("performers_scenes.performer_id IS %s NULL", notClause))
return
@ -237,7 +237,7 @@ func moviePerformersCriterionHandler(qb *movieQueryBuilder, performers *models.M
INNER JOIN performers_scenes ON movies_scenes.scene_id = performers_scenes.scene_id
WHERE performers_scenes.performer_id IN`+getInBinding(len(performers.Value))+`
)`, args...)
f.addJoin("movies_performers", "", "movies.id = movies_performers.movie_id")
f.addLeftJoin("movies_performers", "", "movies.id = movies_performers.movie_id")
switch performers.Modifier {
case models.CriterionModifierIncludes:

View File

@ -341,10 +341,10 @@ func performerIsMissingCriterionHandler(qb *performerQueryBuilder, isMissing *st
if isMissing != nil && *isMissing != "" {
switch *isMissing {
case "scenes": // Deprecated: use `scene_count == 0` filter instead
f.addJoin(performersScenesTable, "scenes_join", "scenes_join.performer_id = performers.id")
f.addLeftJoin(performersScenesTable, "scenes_join", "scenes_join.performer_id = performers.id")
f.addWhere("scenes_join.scene_id IS NULL")
case "image":
f.addJoin(performersImageTable, "image_join", "image_join.performer_id = performers.id")
f.addLeftJoin(performersImageTable, "image_join", "image_join.performer_id = performers.id")
f.addWhere("image_join.performer_id IS NULL")
case "stash_id":
qb.stashIDRepository().join(f, "performer_stash_ids", "performers.id")
@ -463,8 +463,8 @@ func performerStudiosCriterionHandler(qb *performerQueryBuilder, studios *models
var conditions []string
for _, c := range formatMaps {
f.addJoin(c["joinTable"].(string), "", fmt.Sprintf("%s.performer_id = performers.id", c["joinTable"]))
f.addJoin(c["primaryTable"].(string), "", fmt.Sprintf("%s.%s = %s.id", c["joinTable"], c["primaryFK"], c["primaryTable"]))
f.addLeftJoin(c["joinTable"].(string), "", fmt.Sprintf("%s.performer_id = performers.id", c["joinTable"]))
f.addLeftJoin(c["primaryTable"].(string), "", fmt.Sprintf("%s.%s = %s.id", c["joinTable"], c["primaryFK"], c["primaryTable"]))
conditions = append(conditions, fmt.Sprintf("%s.studio_id IS NULL", c["primaryTable"]))
}
@ -505,7 +505,7 @@ func performerStudiosCriterionHandler(qb *performerQueryBuilder, studios *models
f.addWith(fmt.Sprintf("%s AS (%s)", derivedPerformerStudioTable, strings.Join(unions, " UNION ")))
f.addJoin(derivedPerformerStudioTable, "", fmt.Sprintf("performers.id = %s.performer_id", derivedPerformerStudioTable))
f.addLeftJoin(derivedPerformerStudioTable, "", fmt.Sprintf("performers.id = %s.performer_id", derivedPerformerStudioTable))
f.addWhere(fmt.Sprintf("%s.performer_id IS %s NULL", derivedPerformerStudioTable, clauseCondition))
}
}

View File

@ -127,6 +127,7 @@ func (qb *queryBuilder) join(table, as, onClause string) {
table: table,
as: as,
onClause: onClause,
joinType: "LEFT",
}
qb.joins.add(newJoin)

View File

@ -294,11 +294,20 @@ func (r *repository) join(j joiner, as string, parentIDCol string) {
if as != "" {
t = as
}
j.addJoin(r.tableName, as, fmt.Sprintf("%s.%s = %s", t, r.idColumn, parentIDCol))
j.addLeftJoin(r.tableName, as, fmt.Sprintf("%s.%s = %s", t, r.idColumn, parentIDCol))
}
func (r *repository) innerJoin(j joiner, as string, parentIDCol string) {
t := r.tableName
if as != "" {
t = as
}
j.addInnerJoin(r.tableName, as, fmt.Sprintf("%s.%s = %s", t, r.idColumn, parentIDCol))
}
type joiner interface {
addJoin(table, as, onClause string)
addLeftJoin(table, as, onClause string)
addInnerJoin(table, as, onClause string)
}
type joinRepository struct {

View File

@ -537,7 +537,7 @@ func resolutionCriterionHandler(resolution *models.ResolutionCriterionInput, hei
func hasMarkersCriterionHandler(hasMarkers *string) criterionHandlerFunc {
return func(f *filterBuilder) {
if hasMarkers != nil {
f.addJoin("scene_markers", "", "scene_markers.scene_id = scenes.id")
f.addLeftJoin("scene_markers", "", "scene_markers.scene_id = scenes.id")
if *hasMarkers == "true" {
f.addHaving("count(scene_markers.scene_id) > 0")
} else {
@ -658,7 +658,7 @@ func sceneStudioCriterionHandler(qb *sceneQueryBuilder, studios *models.Hierarch
func sceneMoviesCriterionHandler(qb *sceneQueryBuilder, movies *models.MultiCriterionInput) criterionHandlerFunc {
addJoinsFunc := func(f *filterBuilder) {
qb.moviesRepository().join(f, "movies_join", "scenes.id")
f.addJoin("movies", "", "movies_join.movie_id = movies.id")
f.addLeftJoin("movies", "", "movies_join.movie_id = movies.id")
}
h := qb.getMultiCriterionHandlerBuilder(movieTable, moviesScenesTable, "movie_id", addJoinsFunc)
return h.handler(movies)
@ -673,8 +673,8 @@ func scenePerformerTagsCriterionHandler(qb *sceneQueryBuilder, tags *models.Hier
notClause = "NOT"
}
f.addJoin("performers_scenes", "", "scenes.id = performers_scenes.scene_id")
f.addJoin("performers_tags", "", "performers_scenes.performer_id = performers_tags.performer_id")
f.addLeftJoin("performers_scenes", "", "scenes.id = performers_scenes.scene_id")
f.addLeftJoin("performers_tags", "", "performers_scenes.performer_id = performers_tags.performer_id")
f.addWhere(fmt.Sprintf("performers_tags.tag_id IS %s NULL", notClause))
return
@ -692,7 +692,7 @@ INNER JOIN performers_tags pt ON pt.performer_id = ps.performer_id
INNER JOIN (` + valuesClause + `) t ON t.column2 = pt.tag_id
)`)
f.addJoin("performer_tags", "", "performer_tags.scene_id = scenes.id")
f.addLeftJoin("performer_tags", "", "performer_tags.scene_id = scenes.id")
addHierarchicalConditionClauses(f, tags, "performer_tags", "root_tag_id")
}

View File

@ -180,7 +180,7 @@ func (qb *sceneMarkerQueryBuilder) Query(sceneMarkerFilter *models.SceneMarkerFi
func sceneMarkerTagIDCriterionHandler(qb *sceneMarkerQueryBuilder, tagID *string) criterionHandlerFunc {
return func(f *filterBuilder) {
if tagID != nil {
f.addJoin("scene_markers_tags", "", "scene_markers_tags.scene_marker_id = scene_markers.id")
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)
}
@ -196,7 +196,7 @@ func sceneMarkerTagsCriterionHandler(qb *sceneMarkerQueryBuilder, tags *models.H
notClause = "NOT"
}
f.addJoin("scene_markers_tags", "", "scene_markers.id = scene_markers_tags.scene_marker_id")
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
@ -215,7 +215,7 @@ SELECT m.id, t.column1 FROM scene_markers m
INNER JOIN (` + valuesClause + `) t ON t.column2 = m.primary_tag_id
)`)
f.addJoin("marker_tags", "", "marker_tags.scene_marker_id = scene_markers.id")
f.addLeftJoin("marker_tags", "", "marker_tags.scene_marker_id = scene_markers.id")
addHierarchicalConditionClauses(f, tags, "marker_tags", "root_tag_id")
}
@ -231,7 +231,7 @@ func sceneMarkerSceneTagsCriterionHandler(qb *sceneMarkerQueryBuilder, tags *mod
notClause = "NOT"
}
f.addJoin("scenes_tags", "", "scene_markers.scene_id = scenes_tags.scene_id")
f.addLeftJoin("scenes_tags", "", "scene_markers.scene_id = scenes_tags.scene_id")
f.addWhere(fmt.Sprintf("scenes_tags.tag_id IS %s NULL", notClause))
return
@ -248,7 +248,7 @@ SELECT st.scene_id, t.column1 AS root_tag_id FROM scenes_tags st
INNER JOIN (` + valuesClause + `) t ON t.column2 = st.tag_id
)`)
f.addJoin("scene_tags", "", "scene_tags.scene_id = scene_markers.scene_id")
f.addLeftJoin("scene_tags", "", "scene_tags.scene_id = scene_markers.scene_id")
addHierarchicalConditionClauses(f, tags, "scene_tags", "root_tag_id")
}
@ -264,14 +264,14 @@ func sceneMarkerPerformersCriterionHandler(qb *sceneMarkerQueryBuilder, performe
foreignFK: performerIDColumn,
addJoinTable: func(f *filterBuilder) {
f.addJoin(performersScenesTable, "performers_join", "performers_join.scene_id = scene_markers.scene_id")
f.addLeftJoin(performersScenesTable, "performers_join", "performers_join.scene_id = scene_markers.scene_id")
},
}
handler := h.handler(performers)
return func(f *filterBuilder) {
// Make sure scenes is included, otherwise excludes filter fails
f.addJoin(sceneTable, "", "scenes.id = scene_markers.scene_id")
f.addLeftJoin(sceneTable, "", "scenes.id = scene_markers.scene_id")
handler(f)
}
}

View File

@ -278,7 +278,7 @@ func studioIsMissingCriterionHandler(qb *studioQueryBuilder, isMissing *string)
if isMissing != nil && *isMissing != "" {
switch *isMissing {
case "image":
f.addJoin("studios_image", "", "studios_image.studio_id = studios.id")
f.addLeftJoin("studios_image", "", "studios_image.studio_id = studios.id")
f.addWhere("studios_image.studio_id IS NULL")
case "stash_id":
qb.stashIDRepository().join(f, "studio_stash_ids", "studios.id")
@ -293,7 +293,7 @@ func studioIsMissingCriterionHandler(qb *studioQueryBuilder, isMissing *string)
func studioSceneCountCriterionHandler(qb *studioQueryBuilder, sceneCount *models.IntCriterionInput) criterionHandlerFunc {
return func(f *filterBuilder) {
if sceneCount != nil {
f.addJoin("scenes", "", "scenes.studio_id = studios.id")
f.addLeftJoin("scenes", "", "scenes.studio_id = studios.id")
clause, args := getIntCriterionWhereClause("count(distinct scenes.id)", *sceneCount)
f.addHaving(clause, args...)
@ -304,7 +304,7 @@ func studioSceneCountCriterionHandler(qb *studioQueryBuilder, sceneCount *models
func studioImageCountCriterionHandler(qb *studioQueryBuilder, imageCount *models.IntCriterionInput) criterionHandlerFunc {
return func(f *filterBuilder) {
if imageCount != nil {
f.addJoin("images", "", "images.studio_id = studios.id")
f.addLeftJoin("images", "", "images.studio_id = studios.id")
clause, args := getIntCriterionWhereClause("count(distinct images.id)", *imageCount)
f.addHaving(clause, args...)
@ -315,7 +315,7 @@ func studioImageCountCriterionHandler(qb *studioQueryBuilder, imageCount *models
func studioGalleryCountCriterionHandler(qb *studioQueryBuilder, galleryCount *models.IntCriterionInput) criterionHandlerFunc {
return func(f *filterBuilder) {
if galleryCount != nil {
f.addJoin("galleries", "", "galleries.studio_id = studios.id")
f.addLeftJoin("galleries", "", "galleries.studio_id = studios.id")
clause, args := getIntCriterionWhereClause("count(distinct galleries.id)", *galleryCount)
f.addHaving(clause, args...)
@ -325,7 +325,7 @@ func studioGalleryCountCriterionHandler(qb *studioQueryBuilder, galleryCount *mo
func studioParentCriterionHandler(qb *studioQueryBuilder, parents *models.MultiCriterionInput) criterionHandlerFunc {
addJoinsFunc := func(f *filterBuilder) {
f.addJoin("studios", "parent_studio", "parent_studio.id = studios.parent_id")
f.addLeftJoin("studios", "parent_studio", "parent_studio.id = studios.parent_id")
}
h := multiCriterionHandlerBuilder{
primaryTable: studioTable,

View File

@ -386,7 +386,7 @@ func tagIsMissingCriterionHandler(qb *tagQueryBuilder, isMissing *string) criter
func tagSceneCountCriterionHandler(qb *tagQueryBuilder, sceneCount *models.IntCriterionInput) criterionHandlerFunc {
return func(f *filterBuilder) {
if sceneCount != nil {
f.addJoin("scenes_tags", "", "scenes_tags.tag_id = tags.id")
f.addLeftJoin("scenes_tags", "", "scenes_tags.tag_id = tags.id")
clause, args := getIntCriterionWhereClause("count(distinct scenes_tags.scene_id)", *sceneCount)
f.addHaving(clause, args...)
@ -397,7 +397,7 @@ func tagSceneCountCriterionHandler(qb *tagQueryBuilder, sceneCount *models.IntCr
func tagImageCountCriterionHandler(qb *tagQueryBuilder, imageCount *models.IntCriterionInput) criterionHandlerFunc {
return func(f *filterBuilder) {
if imageCount != nil {
f.addJoin("images_tags", "", "images_tags.tag_id = tags.id")
f.addLeftJoin("images_tags", "", "images_tags.tag_id = tags.id")
clause, args := getIntCriterionWhereClause("count(distinct images_tags.image_id)", *imageCount)
f.addHaving(clause, args...)
@ -408,7 +408,7 @@ func tagImageCountCriterionHandler(qb *tagQueryBuilder, imageCount *models.IntCr
func tagGalleryCountCriterionHandler(qb *tagQueryBuilder, galleryCount *models.IntCriterionInput) criterionHandlerFunc {
return func(f *filterBuilder) {
if galleryCount != nil {
f.addJoin("galleries_tags", "", "galleries_tags.tag_id = tags.id")
f.addLeftJoin("galleries_tags", "", "galleries_tags.tag_id = tags.id")
clause, args := getIntCriterionWhereClause("count(distinct galleries_tags.gallery_id)", *galleryCount)
f.addHaving(clause, args...)
@ -419,7 +419,7 @@ func tagGalleryCountCriterionHandler(qb *tagQueryBuilder, galleryCount *models.I
func tagPerformerCountCriterionHandler(qb *tagQueryBuilder, performerCount *models.IntCriterionInput) criterionHandlerFunc {
return func(f *filterBuilder) {
if performerCount != nil {
f.addJoin("performers_tags", "", "performers_tags.tag_id = tags.id")
f.addLeftJoin("performers_tags", "", "performers_tags.tag_id = tags.id")
clause, args := getIntCriterionWhereClause("count(distinct performers_tags.performer_id)", *performerCount)
f.addHaving(clause, args...)
@ -430,8 +430,8 @@ func tagPerformerCountCriterionHandler(qb *tagQueryBuilder, performerCount *mode
func tagMarkerCountCriterionHandler(qb *tagQueryBuilder, markerCount *models.IntCriterionInput) criterionHandlerFunc {
return func(f *filterBuilder) {
if markerCount != nil {
f.addJoin("scene_markers_tags", "", "scene_markers_tags.tag_id = tags.id")
f.addJoin("scene_markers", "", "scene_markers_tags.scene_marker_id = scene_markers.id OR scene_markers.primary_tag_id = tags.id")
f.addLeftJoin("scene_markers_tags", "", "scene_markers_tags.tag_id = tags.id")
f.addLeftJoin("scene_markers", "", "scene_markers_tags.scene_marker_id = scene_markers.id OR scene_markers.primary_tag_id = tags.id")
clause, args := getIntCriterionWhereClause("count(distinct scene_markers.id)", *markerCount)
f.addHaving(clause, args...)
@ -448,7 +448,7 @@ func tagParentsCriterionHandler(qb *tagQueryBuilder, tags *models.HierarchicalMu
notClause = "NOT"
}
f.addJoin("tags_relations", "parent_relations", "tags.id = parent_relations.child_id")
f.addLeftJoin("tags_relations", "parent_relations", "tags.id = parent_relations.child_id")
f.addWhere(fmt.Sprintf("parent_relations.parent_id IS %s NULL", notClause))
return
@ -481,7 +481,7 @@ func tagParentsCriterionHandler(qb *tagQueryBuilder, tags *models.HierarchicalMu
f.addRecursiveWith(query, args...)
f.addJoin("parents", "", "parents.item_id = tags.id")
f.addLeftJoin("parents", "", "parents.item_id = tags.id")
addHierarchicalConditionClauses(f, tags, "parents", "root_id")
}
@ -497,7 +497,7 @@ func tagChildrenCriterionHandler(qb *tagQueryBuilder, tags *models.HierarchicalM
notClause = "NOT"
}
f.addJoin("tags_relations", "child_relations", "tags.id = child_relations.parent_id")
f.addLeftJoin("tags_relations", "child_relations", "tags.id = child_relations.parent_id")
f.addWhere(fmt.Sprintf("child_relations.child_id IS %s NULL", notClause))
return
@ -530,7 +530,7 @@ func tagChildrenCriterionHandler(qb *tagQueryBuilder, tags *models.HierarchicalM
f.addRecursiveWith(query, args...)
f.addJoin("children", "", "children.item_id = tags.id")
f.addLeftJoin("children", "", "children.item_id = tags.id")
addHierarchicalConditionClauses(f, tags, "children", "root_id")
}
@ -540,7 +540,7 @@ func tagChildrenCriterionHandler(qb *tagQueryBuilder, tags *models.HierarchicalM
func tagParentCountCriterionHandler(qb *tagQueryBuilder, parentCount *models.IntCriterionInput) criterionHandlerFunc {
return func(f *filterBuilder) {
if parentCount != nil {
f.addJoin("tags_relations", "parents_count", "parents_count.child_id = tags.id")
f.addLeftJoin("tags_relations", "parents_count", "parents_count.child_id = tags.id")
clause, args := getIntCriterionWhereClause("count(distinct parents_count.parent_id)", *parentCount)
f.addHaving(clause, args...)
@ -551,7 +551,7 @@ func tagParentCountCriterionHandler(qb *tagQueryBuilder, parentCount *models.Int
func tagChildCountCriterionHandler(qb *tagQueryBuilder, childCount *models.IntCriterionInput) criterionHandlerFunc {
return func(f *filterBuilder) {
if childCount != nil {
f.addJoin("tags_relations", "children_count", "children_count.parent_id = tags.id")
f.addLeftJoin("tags_relations", "children_count", "children_count.parent_id = tags.id")
clause, args := getIntCriterionWhereClause("count(distinct children_count.child_id)", *childCount)
f.addHaving(clause, args...)