Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query optimizations #478

Merged
merged 16 commits into from
May 11, 2020
2 changes: 1 addition & 1 deletion pkg/models/querybuilder_gallery.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (qb *GalleryQueryBuilder) FindByPath(path string) (*Gallery, error) {
}

func (qb *GalleryQueryBuilder) FindBySceneID(sceneID int, tx *sqlx.Tx) (*Gallery, error) {
query := "SELECT galleries.* FROM galleries JOIN scenes ON scenes.id = galleries.scene_id WHERE scenes.id = ? LIMIT 1"
query := "SELECT galleries.* FROM galleries WHERE galleries.scene_id = ? LIMIT 1"
args := []interface{}{sceneID}
return qb.queryGallery(query, args, tx)
}
Expand Down
3 changes: 1 addition & 2 deletions pkg/models/querybuilder_movies.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ func (qb *MovieQueryBuilder) FindBySceneID(sceneID int, tx *sqlx.Tx) ([]*Movie,
query := `
SELECT movies.* FROM movies
LEFT JOIN movies_scenes as scenes_join on scenes_join.movie_id = movies.id
LEFT JOIN scenes on scenes_join.scene_id = scenes.id
WHERE scenes.id = ?
WHERE scenes_join.scene_id = ?
GROUP BY movies.id
`
args := []interface{}{sceneID}
Expand Down
8 changes: 2 additions & 6 deletions pkg/models/querybuilder_performer.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,9 @@ func (qb *PerformerQueryBuilder) Find(id int) (*Performer, error) {
}

func (qb *PerformerQueryBuilder) FindBySceneID(sceneID int, tx *sqlx.Tx) ([]*Performer, error) {
query := `
SELECT performers.* FROM performers
query := selectAll("performers") + `
LEFT JOIN performers_scenes as scenes_join on scenes_join.performer_id = performers.id
LEFT JOIN scenes on scenes_join.scene_id = scenes.id
WHERE scenes.id = ?
GROUP BY performers.id
WHERE scenes_join.scene_id = ?
`
args := []interface{}{sceneID}
return qb.queryPerformers(query, args, tx)
Expand All @@ -93,7 +90,6 @@ func (qb *PerformerQueryBuilder) FindNameBySceneID(sceneID int, tx *sqlx.Tx) ([]
SELECT performers.name FROM performers
LEFT JOIN performers_scenes as scenes_join on scenes_join.performer_id = performers.id
WHERE scenes_join.scene_id = ?
GROUP BY performers.name
`
args := []interface{}{sceneID}
return qb.queryPerformers(query, args, tx)
Expand Down
25 changes: 24 additions & 1 deletion pkg/models/querybuilder_performer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,33 @@ func TestPerformerFindBySceneID(t *testing.T) {
assert.Equal(t, 0, len(performers))
}

func TestPerformerFindNameBySceneID(t *testing.T) {
pqb := models.NewPerformerQueryBuilder()
sceneID := sceneIDs[sceneIdxWithPerformer]

performers, err := pqb.FindNameBySceneID(sceneID, nil)

if err != nil {
t.Fatalf("Error finding performer: %s", err.Error())
}

assert.Equal(t, 1, len(performers))
performer := performers[0]

assert.Equal(t, getPerformerStringValue(performerIdxWithScene, "Name"), performer.Name.String)

performers, err = pqb.FindBySceneID(0, nil)

if err != nil {
t.Fatalf("Error finding performer: %s", err.Error())
}

assert.Equal(t, 0, len(performers))
}

// TODO Update
// TODO Destroy
// TODO Find
// TODO FindNameBySceneID
// TODO FindByNames
// TODO Count
// TODO All
Expand Down
128 changes: 65 additions & 63 deletions pkg/models/querybuilder_scene.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,34 @@ import (
"github.com/stashapp/stash/pkg/database"
)

const scenesForPerformerQuery = `
SELECT scenes.* FROM scenes
const sceneTable = "scenes"

var scenesForPerformerQuery = selectAll(sceneTable) + `
LEFT JOIN performers_scenes as performers_join on performers_join.scene_id = scenes.id
LEFT JOIN performers on performers_join.performer_id = performers.id
WHERE performers.id = ?
WHERE performers_join.performer_id = ?
GROUP BY scenes.id
`

const scenesForStudioQuery = `
SELECT scenes.* FROM scenes
var countScenesForPerformerQuery = `
SELECT performer_id FROM performers_scenes as performers_join
WHERE performer_id = ?
GROUP BY scene_id
`

var scenesForStudioQuery = selectAll(sceneTable) + `
JOIN studios ON studios.id = scenes.studio_id
WHERE studios.id = ?
GROUP BY scenes.id
`
const scenesForMovieQuery = `
SELECT scenes.* FROM scenes
var scenesForMovieQuery = selectAll(sceneTable) + `
LEFT JOIN movies_scenes as movies_join on movies_join.scene_id = scenes.id
LEFT JOIN movies on movies_join.movie_id = movies.id
WHERE movies.id = ?
WHERE movies_join.movie_id = ?
GROUP BY scenes.id
`

const scenesForTagQuery = `
SELECT scenes.* FROM scenes
var scenesForTagQuery = selectAll(sceneTable) + `
LEFT JOIN scenes_tags as tags_join on tags_join.scene_id = scenes.id
LEFT JOIN tags on tags_join.tag_id = tags.id
WHERE tags.id = ?
WHERE tags_join.tag_id = ?
GROUP BY scenes.id
`

Expand Down Expand Up @@ -150,7 +151,7 @@ func (qb *SceneQueryBuilder) Find(id int) (*Scene, error) {
}

func (qb *SceneQueryBuilder) find(id int, tx *sqlx.Tx) (*Scene, error) {
query := "SELECT * FROM scenes WHERE id = ? LIMIT 1"
query := selectAll(sceneTable) + "WHERE id = ? LIMIT 1"
args := []interface{}{id}
return qb.queryScene(query, args, tx)
}
Expand All @@ -162,7 +163,7 @@ func (qb *SceneQueryBuilder) FindByChecksum(checksum string) (*Scene, error) {
}

func (qb *SceneQueryBuilder) FindByPath(path string) (*Scene, error) {
query := "SELECT * FROM scenes WHERE path = ? LIMIT 1"
query := selectAll(sceneTable) + "WHERE path = ? LIMIT 1"
args := []interface{}{path}
return qb.queryScene(query, args, nil)
}
Expand All @@ -174,7 +175,7 @@ func (qb *SceneQueryBuilder) FindByPerformerID(performerID int) ([]*Scene, error

func (qb *SceneQueryBuilder) CountByPerformerID(performerID int) (int, error) {
args := []interface{}{performerID}
return runCountQuery(buildCountQuery(scenesForPerformerQuery), args)
return runCountQuery(buildCountQuery(countScenesForPerformerQuery), args)
}

func (qb *SceneQueryBuilder) FindByStudioID(studioID int) ([]*Scene, error) {
Expand Down Expand Up @@ -219,12 +220,12 @@ func (qb *SceneQueryBuilder) Wall(q *string) ([]*Scene, error) {
if q != nil {
s = *q
}
query := "SELECT scenes.* FROM scenes WHERE scenes.details LIKE '%" + s + "%' ORDER BY RANDOM() LIMIT 80"
query := selectAll(sceneTable) + "WHERE scenes.details LIKE '%" + s + "%' ORDER BY RANDOM() LIMIT 80"
return qb.queryScenes(query, nil, nil)
}

func (qb *SceneQueryBuilder) All() ([]*Scene, error) {
return qb.queryScenes(selectAll("scenes")+qb.getSceneSort(nil), nil, nil)
return qb.queryScenes(selectAll(sceneTable)+qb.getSceneSort(nil), nil, nil)
}

func (qb *SceneQueryBuilder) Query(sceneFilter *SceneFilterType, findFilter *FindFilterType) ([]*Scene, int) {
Expand All @@ -235,137 +236,138 @@ func (qb *SceneQueryBuilder) Query(sceneFilter *SceneFilterType, findFilter *Fin
findFilter = &FindFilterType{}
}

var whereClauses []string
var havingClauses []string
var args []interface{}
body := selectDistinctIDs("scenes")
body = body + `
query := queryBuilder{
tableName: sceneTable,
}

query.body = selectDistinctIDs(sceneTable)
query.body += `
left join scene_markers on scene_markers.scene_id = scenes.id
left join performers_scenes as performers_join on performers_join.scene_id = scenes.id
left join movies_scenes as movies_join on movies_join.scene_id = scenes.id
left join performers on performers_join.performer_id = performers.id
left join movies on movies_join.movie_id = movies.id
left join studios as studio on studio.id = scenes.studio_id
left join galleries as gallery on gallery.scene_id = scenes.id
left join scenes_tags as tags_join on tags_join.scene_id = scenes.id
left join tags on tags_join.tag_id = tags.id
`

if q := findFilter.Q; q != nil && *q != "" {
searchColumns := []string{"scenes.title", "scenes.details", "scenes.path", "scenes.checksum", "scene_markers.title"}
clause, thisArgs := getSearchBinding(searchColumns, *q, false)
whereClauses = append(whereClauses, clause)
args = append(args, thisArgs...)
query.addWhere(clause)
query.addArg(thisArgs...)
}

if rating := sceneFilter.Rating; rating != nil {
clause, count := getIntCriterionWhereClause("scenes.rating", *sceneFilter.Rating)
whereClauses = append(whereClauses, clause)
query.addWhere(clause)
if count == 1 {
args = append(args, sceneFilter.Rating.Value)
query.addArg(sceneFilter.Rating.Value)
}
}

if oCounter := sceneFilter.OCounter; oCounter != nil {
clause, count := getIntCriterionWhereClause("scenes.o_counter", *sceneFilter.OCounter)
whereClauses = append(whereClauses, clause)
query.addWhere(clause)
if count == 1 {
args = append(args, sceneFilter.OCounter.Value)
query.addArg(sceneFilter.OCounter.Value)
}
}

if durationFilter := sceneFilter.Duration; durationFilter != nil {
clause, thisArgs := getDurationWhereClause(*durationFilter)
whereClauses = append(whereClauses, clause)
args = append(args, thisArgs...)
query.addWhere(clause)
query.addArg(thisArgs...)
}

if resolutionFilter := sceneFilter.Resolution; resolutionFilter != nil {
if resolution := resolutionFilter.String(); resolutionFilter.IsValid() {
switch resolution {
case "LOW":
whereClauses = append(whereClauses, "scenes.height < 480")
query.addWhere("scenes.height < 480")
case "STANDARD":
whereClauses = append(whereClauses, "(scenes.height >= 480 AND scenes.height < 720)")
query.addWhere("(scenes.height >= 480 AND scenes.height < 720)")
case "STANDARD_HD":
whereClauses = append(whereClauses, "(scenes.height >= 720 AND scenes.height < 1080)")
query.addWhere("(scenes.height >= 720 AND scenes.height < 1080)")
case "FULL_HD":
whereClauses = append(whereClauses, "(scenes.height >= 1080 AND scenes.height < 2160)")
query.addWhere("(scenes.height >= 1080 AND scenes.height < 2160)")
case "FOUR_K":
whereClauses = append(whereClauses, "scenes.height >= 2160")
query.addWhere("scenes.height >= 2160")
}
}
}

if hasMarkersFilter := sceneFilter.HasMarkers; hasMarkersFilter != nil {
if strings.Compare(*hasMarkersFilter, "true") == 0 {
havingClauses = append(havingClauses, "count(scene_markers.scene_id) > 0")
query.addHaving("count(scene_markers.scene_id) > 0")
} else {
whereClauses = append(whereClauses, "scene_markers.id IS NULL")
query.addWhere("scene_markers.id IS NULL")
}
}

if isMissingFilter := sceneFilter.IsMissing; isMissingFilter != nil && *isMissingFilter != "" {
switch *isMissingFilter {
case "gallery":
whereClauses = append(whereClauses, "gallery.scene_id IS NULL")
query.addWhere("gallery.scene_id IS NULL")
case "studio":
whereClauses = append(whereClauses, "scenes.studio_id IS NULL")
query.addWhere("scenes.studio_id IS NULL")
case "movie":
whereClauses = append(whereClauses, "movies_join.scene_id IS NULL")
query.addWhere("movies_join.scene_id IS NULL")
case "performers":
whereClauses = append(whereClauses, "performers_join.scene_id IS NULL")
query.addWhere("performers_join.scene_id IS NULL")
case "date":
whereClauses = append(whereClauses, "scenes.date IS \"\" OR scenes.date IS \"0001-01-01\"")
query.addWhere("scenes.date IS \"\" OR scenes.date IS \"0001-01-01\"")
case "tags":
whereClauses = append(whereClauses, "tags_join.scene_id IS NULL")
query.addWhere("tags_join.scene_id IS NULL")
default:
whereClauses = append(whereClauses, "scenes."+*isMissingFilter+" IS NULL")
query.addWhere("scenes." + *isMissingFilter + " IS NULL")
}
}

if tagsFilter := sceneFilter.Tags; tagsFilter != nil && len(tagsFilter.Value) > 0 {
for _, tagID := range tagsFilter.Value {
args = append(args, tagID)
query.addArg(tagID)
}

query.body += " LEFT JOIN tags on tags_join.tag_id = tags.id"
whereClause, havingClause := getMultiCriterionClause("tags", "scenes_tags", "tag_id", tagsFilter)
whereClauses = appendClause(whereClauses, whereClause)
havingClauses = appendClause(havingClauses, havingClause)
query.addWhere(whereClause)
query.addHaving(havingClause)
}

if performersFilter := sceneFilter.Performers; performersFilter != nil && len(performersFilter.Value) > 0 {
for _, performerID := range performersFilter.Value {
args = append(args, performerID)
query.addArg(performerID)
}

query.body += " LEFT JOIN performers ON performers_join.performer_id = performers.id"
whereClause, havingClause := getMultiCriterionClause("performers", "performers_scenes", "performer_id", performersFilter)
whereClauses = appendClause(whereClauses, whereClause)
havingClauses = appendClause(havingClauses, havingClause)
query.addWhere(whereClause)
query.addHaving(havingClause)
}

if studiosFilter := sceneFilter.Studios; studiosFilter != nil && len(studiosFilter.Value) > 0 {
for _, studioID := range studiosFilter.Value {
args = append(args, studioID)
query.addArg(studioID)
}

whereClause, havingClause := getMultiCriterionClause("studio", "", "studio_id", studiosFilter)
whereClauses = appendClause(whereClauses, whereClause)
havingClauses = appendClause(havingClauses, havingClause)
query.addWhere(whereClause)
query.addHaving(havingClause)
}

if moviesFilter := sceneFilter.Movies; moviesFilter != nil && len(moviesFilter.Value) > 0 {
for _, movieID := range moviesFilter.Value {
args = append(args, movieID)
query.addArg(movieID)
}

query.body += " LEFT JOIN movies ON movies_join.movie_id = movies.id"
whereClause, havingClause := getMultiCriterionClause("movies", "movies_scenes", "movie_id", moviesFilter)
whereClauses = appendClause(whereClauses, whereClause)
havingClauses = appendClause(havingClauses, havingClause)
query.addWhere(whereClause)
query.addHaving(havingClause)
}

sortAndPagination := qb.getSceneSort(findFilter) + getPagination(findFilter)
idsResult, countResult := executeFindQuery("scenes", body, args, sortAndPagination, whereClauses, havingClauses)
query.sortAndPagination = qb.getSceneSort(findFilter) + getPagination(findFilter)
idsResult, countResult := query.executeFind()

var scenes []*Scene
for _, id := range idsResult {
Expand Down
3 changes: 1 addition & 2 deletions pkg/models/querybuilder_scene_marker.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@ func (qb *SceneMarkerQueryBuilder) Find(id int) (*SceneMarker, error) {
func (qb *SceneMarkerQueryBuilder) FindBySceneID(sceneID int, tx *sqlx.Tx) ([]*SceneMarker, error) {
query := `
SELECT scene_markers.* FROM scene_markers
JOIN scenes ON scenes.id = scene_markers.scene_id
WHERE scenes.id = ?
WHERE scene_markers.scene_id = ?
GROUP BY scene_markers.id
ORDER BY scene_markers.seconds ASC
`
Expand Down
Loading