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
13 changed files with 129 additions and 71 deletions

View File

@@ -39,6 +39,7 @@ type join struct {
table string table string
as string as string
onClause string onClause string
joinType string
} }
// equals returns true if the other join alias/table is equal to this one // 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 { func (j join) toSQL() string {
asStr := "" asStr := ""
joinStr := j.joinType
if j.as != "" && j.as != j.table { if j.as != "" && j.as != j.table {
asStr = " AS " + j.as 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 type joins []join
@@ -154,16 +159,33 @@ func (f *filterBuilder) not(n *filterBuilder) {
f.subFilterOp = notOp 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> // LEFT JOIN <table> [AS <as>] ON <onClause>
// The AS is omitted if as is empty. // The AS is omitted if as is empty.
// This method does not add a join if it its alias/table name is already // This method does not add a join if it its alias/table name is already
// present in another existing join. // present in another existing join.
func (f *filterBuilder) addJoin(table, as, onClause string) { func (f *filterBuilder) addLeftJoin(table, as, onClause string) {
newJoin := join{ newJoin := join{
table: table, table: table,
as: as, as: as,
onClause: onClause, 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) f.joins.add(newJoin)
@@ -505,7 +527,7 @@ func (m *multiCriterionHandlerBuilder) handler(criterion *models.MultiCriterionI
table := m.primaryTable table := m.primaryTable
if m.joinTable != "" { if m.joinTable != "" {
table = 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)) 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) 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") addHierarchicalConditionClauses(f, criterion, m.derivedTable, "root_id")
} }
@@ -731,7 +753,7 @@ func (m *joinedHierarchicalMultiCriterionHandlerBuilder) handler(criterion *mode
notClause = "NOT" 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{ f.addWhere(utils.StrFormat("{table}.{column} IS {not} NULL", utils.StrFormatMap{
"table": joinAlias, "table": joinAlias,
@@ -757,7 +779,7 @@ func (m *joinedHierarchicalMultiCriterionHandlerBuilder) handler(criterion *mode
"valuesClause": valuesClause, "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") addHierarchicalConditionClauses(f, criterion, joinAlias, "root_id")
} }

View File

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

View File

@@ -290,7 +290,7 @@ func galleryIsMissingCriterionHandler(qb *galleryQueryBuilder, isMissing *string
if isMissing != nil && *isMissing != "" { if isMissing != nil && *isMissing != "" {
switch *isMissing { switch *isMissing {
case "scenes": 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") f.addWhere("scenes_join.gallery_id IS NULL")
case "studio": case "studio":
f.addWhere("galleries.studio_id IS NULL") f.addWhere("galleries.studio_id IS NULL")
@@ -395,8 +395,8 @@ func galleryPerformerTagsCriterionHandler(qb *galleryQueryBuilder, tags *models.
notClause = "NOT" notClause = "NOT"
} }
f.addJoin("performers_galleries", "", "galleries.id = performers_galleries.gallery_id") f.addLeftJoin("performers_galleries", "", "galleries.id = performers_galleries.gallery_id")
f.addJoin("performers_tags", "", "performers_galleries.performer_id = performers_tags.performer_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)) f.addWhere(fmt.Sprintf("performers_tags.tag_id IS %s NULL", notClause))
return 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 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") addHierarchicalConditionClauses(f, tags, "performer_tags", "root_tag_id")
} }
@@ -425,7 +425,7 @@ func galleryAverageResolutionCriterionHandler(qb *galleryQueryBuilder, resolutio
return func(f *filterBuilder) { return func(f *filterBuilder) {
if resolution != nil && resolution.Value.IsValid() { if resolution != nil && resolution.Value.IsValid() {
qb.imagesRepository().join(f, "images_join", "galleries.id") 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() min := resolution.Value.GetMinResolution()
max := resolution.Value.GetMaxResolution() max := resolution.Value.GetMaxResolution()

View File

@@ -14,7 +14,7 @@ const performersImagesTable = "performers_images"
const imagesTagsTable = "images_tags" const imagesTagsTable = "images_tags"
var imagesForGalleryQuery = selectAll(imageTable) + ` 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 = ? WHERE galleries_join.gallery_id = ?
GROUP BY images.id GROUP BY images.id
` `
@@ -360,7 +360,7 @@ func imageIsMissingCriterionHandler(qb *imageQueryBuilder, isMissing *string) cr
qb.performersRepository().join(f, "performers_join", "images.id") qb.performersRepository().join(f, "performers_join", "images.id")
f.addWhere("performers_join.image_id IS NULL") f.addWhere("performers_join.image_id IS NULL")
case "galleries": 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") f.addWhere("galleries_join.image_id IS NULL")
case "tags": case "tags":
qb.tagsRepository().join(f, "tags_join", "images.id") 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 { func imageGalleriesCriterionHandler(qb *imageQueryBuilder, galleries *models.MultiCriterionInput) criterionHandlerFunc {
addJoinsFunc := func(f *filterBuilder) { addJoinsFunc := func(f *filterBuilder) {
qb.galleriesRepository().join(f, "galleries_join", "images.id") qb.galleriesRepository().innerJoin(f, "galleries_join", "images.id")
f.addJoin(galleryTable, "", "galleries_join.gallery_id = galleries.id") f.addInnerJoin(galleryTable, "", "galleries_join.gallery_id = galleries.id")
} }
h := qb.getMultiCriterionHandlerBuilder(galleryTable, galleriesImagesTable, galleryIDColumn, addJoinsFunc) h := qb.getMultiCriterionHandlerBuilder(galleryTable, galleriesImagesTable, galleryIDColumn, addJoinsFunc)
@@ -469,8 +469,8 @@ func imagePerformerTagsCriterionHandler(qb *imageQueryBuilder, tags *models.Hier
notClause = "NOT" notClause = "NOT"
} }
f.addJoin("performers_images", "", "images.id = performers_images.image_id") f.addLeftJoin("performers_images", "", "images.id = performers_images.image_id")
f.addJoin("performers_tags", "", "performers_images.performer_id = performers_tags.performer_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)) f.addWhere(fmt.Sprintf("performers_tags.tag_id IS %s NULL", notClause))
return 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 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") 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) { func TestImageQueryQ(t *testing.T) {
withTxn(func(r models.Repository) error { withTxn(func(r models.Repository) error {
const imageIdx = 2 const imageIdx = 2

View File

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

View File

@@ -341,10 +341,10 @@ func performerIsMissingCriterionHandler(qb *performerQueryBuilder, isMissing *st
if isMissing != nil && *isMissing != "" { if isMissing != nil && *isMissing != "" {
switch *isMissing { switch *isMissing {
case "scenes": // Deprecated: use `scene_count == 0` filter instead 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") f.addWhere("scenes_join.scene_id IS NULL")
case "image": 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") f.addWhere("image_join.performer_id IS NULL")
case "stash_id": case "stash_id":
qb.stashIDRepository().join(f, "performer_stash_ids", "performers.id") qb.stashIDRepository().join(f, "performer_stash_ids", "performers.id")
@@ -463,8 +463,8 @@ func performerStudiosCriterionHandler(qb *performerQueryBuilder, studios *models
var conditions []string var conditions []string
for _, c := range formatMaps { for _, c := range formatMaps {
f.addJoin(c["joinTable"].(string), "", fmt.Sprintf("%s.performer_id = performers.id", c["joinTable"])) f.addLeftJoin(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["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"])) 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.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)) 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, table: table,
as: as, as: as,
onClause: onClause, onClause: onClause,
joinType: "LEFT",
} }
qb.joins.add(newJoin) qb.joins.add(newJoin)

View File

@@ -294,11 +294,20 @@ func (r *repository) join(j joiner, as string, parentIDCol string) {
if as != "" { if as != "" {
t = 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 { type joiner interface {
addJoin(table, as, onClause string) addLeftJoin(table, as, onClause string)
addInnerJoin(table, as, onClause string)
} }
type joinRepository struct { type joinRepository struct {

View File

@@ -537,7 +537,7 @@ func resolutionCriterionHandler(resolution *models.ResolutionCriterionInput, hei
func hasMarkersCriterionHandler(hasMarkers *string) criterionHandlerFunc { func hasMarkersCriterionHandler(hasMarkers *string) criterionHandlerFunc {
return func(f *filterBuilder) { return func(f *filterBuilder) {
if hasMarkers != nil { 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" { if *hasMarkers == "true" {
f.addHaving("count(scene_markers.scene_id) > 0") f.addHaving("count(scene_markers.scene_id) > 0")
} else { } else {
@@ -658,7 +658,7 @@ func sceneStudioCriterionHandler(qb *sceneQueryBuilder, studios *models.Hierarch
func sceneMoviesCriterionHandler(qb *sceneQueryBuilder, movies *models.MultiCriterionInput) criterionHandlerFunc { func sceneMoviesCriterionHandler(qb *sceneQueryBuilder, movies *models.MultiCriterionInput) criterionHandlerFunc {
addJoinsFunc := func(f *filterBuilder) { addJoinsFunc := func(f *filterBuilder) {
qb.moviesRepository().join(f, "movies_join", "scenes.id") 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) h := qb.getMultiCriterionHandlerBuilder(movieTable, moviesScenesTable, "movie_id", addJoinsFunc)
return h.handler(movies) return h.handler(movies)
@@ -673,8 +673,8 @@ func scenePerformerTagsCriterionHandler(qb *sceneQueryBuilder, tags *models.Hier
notClause = "NOT" notClause = "NOT"
} }
f.addJoin("performers_scenes", "", "scenes.id = performers_scenes.scene_id") f.addLeftJoin("performers_scenes", "", "scenes.id = performers_scenes.scene_id")
f.addJoin("performers_tags", "", "performers_scenes.performer_id = performers_tags.performer_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)) f.addWhere(fmt.Sprintf("performers_tags.tag_id IS %s NULL", notClause))
return 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 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") 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 { func sceneMarkerTagIDCriterionHandler(qb *sceneMarkerQueryBuilder, tagID *string) criterionHandlerFunc {
return func(f *filterBuilder) { return func(f *filterBuilder) {
if tagID != nil { 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) 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" 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)) f.addWhere(fmt.Sprintf("%s scene_markers_tags.tag_id IS NULL", notClause))
return 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 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") addHierarchicalConditionClauses(f, tags, "marker_tags", "root_tag_id")
} }
@@ -231,7 +231,7 @@ func sceneMarkerSceneTagsCriterionHandler(qb *sceneMarkerQueryBuilder, tags *mod
notClause = "NOT" 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)) f.addWhere(fmt.Sprintf("scenes_tags.tag_id IS %s NULL", notClause))
return 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 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") addHierarchicalConditionClauses(f, tags, "scene_tags", "root_tag_id")
} }
@@ -264,14 +264,14 @@ func sceneMarkerPerformersCriterionHandler(qb *sceneMarkerQueryBuilder, performe
foreignFK: performerIDColumn, foreignFK: performerIDColumn,
addJoinTable: func(f *filterBuilder) { 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) handler := h.handler(performers)
return func(f *filterBuilder) { return func(f *filterBuilder) {
// Make sure scenes is included, otherwise excludes filter fails // 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) handler(f)
} }
} }

View File

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

View File

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