diff --git a/pkg/sqlite/filter.go b/pkg/sqlite/filter.go
index 9d5edeb8f..8935140c2 100644
--- a/pkg/sqlite/filter.go
+++ b/pkg/sqlite/filter.go
@@ -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
[AS ] ON
// 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 [AS ] ON
+// 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")
}
diff --git a/pkg/sqlite/filter_internal_test.go b/pkg/sqlite/filter_internal_test.go
index 9a5042ba1..e9f173de0 100644
--- a/pkg/sqlite/filter_internal_test.go
+++ b/pkg/sqlite/filter_internal_test.go
@@ -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)
}
diff --git a/pkg/sqlite/gallery.go b/pkg/sqlite/gallery.go
index 6f3b9522b..f76f84d4c 100644
--- a/pkg/sqlite/gallery.go
+++ b/pkg/sqlite/gallery.go
@@ -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()
diff --git a/pkg/sqlite/image.go b/pkg/sqlite/image.go
index 96ffad6a7..aa2125cd0 100644
--- a/pkg/sqlite/image.go
+++ b/pkg/sqlite/image.go
@@ -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")
}
diff --git a/pkg/sqlite/image_test.go b/pkg/sqlite/image_test.go
index 141fbb3d6..c90fb4547 100644
--- a/pkg/sqlite/image_test.go
+++ b/pkg/sqlite/image_test.go
@@ -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
diff --git a/pkg/sqlite/movies.go b/pkg/sqlite/movies.go
index 2f3ed96c8..eac02ae54 100644
--- a/pkg/sqlite/movies.go
+++ b/pkg/sqlite/movies.go
@@ -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:
diff --git a/pkg/sqlite/performer.go b/pkg/sqlite/performer.go
index 34b6bf6c6..d824dc37e 100644
--- a/pkg/sqlite/performer.go
+++ b/pkg/sqlite/performer.go
@@ -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))
}
}
diff --git a/pkg/sqlite/query.go b/pkg/sqlite/query.go
index 95fdef5f3..27ce213b5 100644
--- a/pkg/sqlite/query.go
+++ b/pkg/sqlite/query.go
@@ -127,6 +127,7 @@ func (qb *queryBuilder) join(table, as, onClause string) {
table: table,
as: as,
onClause: onClause,
+ joinType: "LEFT",
}
qb.joins.add(newJoin)
diff --git a/pkg/sqlite/repository.go b/pkg/sqlite/repository.go
index 160cbbc88..ed0d4ac0d 100644
--- a/pkg/sqlite/repository.go
+++ b/pkg/sqlite/repository.go
@@ -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 {
diff --git a/pkg/sqlite/scene.go b/pkg/sqlite/scene.go
index c8d3ff4ec..3149c4f53 100644
--- a/pkg/sqlite/scene.go
+++ b/pkg/sqlite/scene.go
@@ -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")
}
diff --git a/pkg/sqlite/scene_marker.go b/pkg/sqlite/scene_marker.go
index ae6e9cda9..c79c1dc16 100644
--- a/pkg/sqlite/scene_marker.go
+++ b/pkg/sqlite/scene_marker.go
@@ -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)
}
}
diff --git a/pkg/sqlite/studio.go b/pkg/sqlite/studio.go
index f3666e85b..91e6c63ea 100644
--- a/pkg/sqlite/studio.go
+++ b/pkg/sqlite/studio.go
@@ -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,
diff --git a/pkg/sqlite/tag.go b/pkg/sqlite/tag.go
index a9c46f2f3..c5f3858d7 100644
--- a/pkg/sqlite/tag.go
+++ b/pkg/sqlite/tag.go
@@ -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...)