From ff360ba5b159cbc9940328cff88ca641da3bf73c Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Fri, 5 Dec 2025 17:19:43 +1100 Subject: [PATCH] Omit joins that are only used for sorting when skipping sorting Should provide some marginal improvement on systems with a lot of items. --- pkg/sqlite/filter.go | 15 +++++++++++++-- pkg/sqlite/gallery.go | 4 ++++ pkg/sqlite/group.go | 2 +- pkg/sqlite/image.go | 3 +++ pkg/sqlite/query.go | 24 +++++++++++++++++++----- pkg/sqlite/scene.go | 11 ++++++++--- pkg/sqlite/scene_marker.go | 4 ++-- 7 files changed, 50 insertions(+), 13 deletions(-) diff --git a/pkg/sqlite/filter.go b/pkg/sqlite/filter.go index 81a58f74b..fa6759ae6 100644 --- a/pkg/sqlite/filter.go +++ b/pkg/sqlite/filter.go @@ -96,6 +96,9 @@ type join struct { onClause string joinType string args []interface{} + + // if true, indicates this is required for sorting only + sort bool } // equals returns true if the other join alias/table is equal to this one @@ -131,9 +134,13 @@ type joins []join // returns true if added func (j *joins) addUnique(newJoin join) bool { found := false - for _, jj := range *j { + for i, jj := range *j { if jj.equals(newJoin) { found = true + // if sort is false on the new join, but true on the existing, set the false + if !newJoin.sort && jj.sort { + (*j)[i].sort = false + } break } } @@ -151,13 +158,17 @@ func (j *joins) add(newJoins ...join) { } } -func (j *joins) toSQL() string { +func (j *joins) toSQL(includeSortPagination bool) string { if len(*j) == 0 { return "" } var ret []string for _, jj := range *j { + // skip sort-only joins if not including sort/pagination + if !includeSortPagination && jj.sort { + continue + } ret = append(ret, jj.toSQL()) } diff --git a/pkg/sqlite/gallery.go b/pkg/sqlite/gallery.go index 9cfe38b1f..a63e84214 100644 --- a/pkg/sqlite/gallery.go +++ b/pkg/sqlite/gallery.go @@ -800,10 +800,12 @@ func (qb *GalleryStore) setGallerySort(query *queryBuilder, findFilter *models.F addFileTable := func() { query.addJoins( join{ + sort: true, table: galleriesFilesTable, onClause: "galleries_files.gallery_id = galleries.id", }, join{ + sort: true, table: fileTable, onClause: "galleries_files.file_id = files.id", }, @@ -813,10 +815,12 @@ func (qb *GalleryStore) setGallerySort(query *queryBuilder, findFilter *models.F addFolderTable := func() { query.addJoins( join{ + sort: true, table: folderTable, onClause: "folders.id = galleries.folder_id", }, join{ + sort: true, table: folderTable, as: "file_folder", onClause: "files.parent_folder_id = file_folder.id", diff --git a/pkg/sqlite/group.go b/pkg/sqlite/group.go index f0f8d6b40..9d0527752 100644 --- a/pkg/sqlite/group.go +++ b/pkg/sqlite/group.go @@ -518,7 +518,7 @@ func (qb *GroupStore) setGroupSort(query *queryBuilder, findFilter *models.FindF } else { // this will give unexpected results if the query is not filtered by a parent group and // the group has multiple parents and order indexes - query.join(groupRelationsTable, "", "groups.id = groups_relations.sub_id") + query.joinSort(groupRelationsTable, "", "groups.id = groups_relations.sub_id") query.sortAndPagination += getSort("order_index", direction, groupRelationsTable) } case "tag_count": diff --git a/pkg/sqlite/image.go b/pkg/sqlite/image.go index 1588fa415..a903a62fd 100644 --- a/pkg/sqlite/image.go +++ b/pkg/sqlite/image.go @@ -965,10 +965,12 @@ func (qb *ImageStore) setImageSortAndPagination(q *queryBuilder, findFilter *mod addFilesJoin := func() { q.addJoins( join{ + sort: true, table: imagesFilesTable, onClause: "images_files.image_id = images.id", }, join{ + sort: true, table: fileTable, onClause: "images_files.file_id = files.id", }, @@ -977,6 +979,7 @@ func (qb *ImageStore) setImageSortAndPagination(q *queryBuilder, findFilter *mod addFolderJoin := func() { q.addJoins(join{ + sort: true, table: folderTable, onClause: "files.parent_folder_id = folders.id", }) diff --git a/pkg/sqlite/query.go b/pkg/sqlite/query.go index 478543829..99c1f4e5f 100644 --- a/pkg/sqlite/query.go +++ b/pkg/sqlite/query.go @@ -24,8 +24,8 @@ type queryBuilder struct { sortAndPagination string } -func (qb queryBuilder) body() string { - return fmt.Sprintf("SELECT %s FROM %s%s", strings.Join(qb.columns, ", "), qb.from, qb.joins.toSQL()) +func (qb queryBuilder) body(includeSortPagination bool) string { + return fmt.Sprintf("SELECT %s FROM %s%s", strings.Join(qb.columns, ", "), qb.from, qb.joins.toSQL(includeSortPagination)) } func (qb *queryBuilder) addColumn(column string) { @@ -33,7 +33,7 @@ func (qb *queryBuilder) addColumn(column string) { } func (qb queryBuilder) toSQL(includeSortPagination bool) string { - body := qb.body() + body := qb.body(includeSortPagination) withClause := "" if len(qb.withClauses) > 0 { @@ -59,12 +59,14 @@ func (qb queryBuilder) findIDs(ctx context.Context) ([]int, error) { } func (qb queryBuilder) executeFind(ctx context.Context) ([]int, int, error) { - body := qb.body() + const includeSortPagination = true + body := qb.body(includeSortPagination) return qb.repository.executeFindQuery(ctx, body, qb.args, qb.sortAndPagination, qb.whereClauses, qb.havingClauses, qb.withClauses, qb.recursiveWith) } func (qb queryBuilder) executeCount(ctx context.Context) (int, error) { - body := qb.body() + const includeSortPagination = false + body := qb.body(includeSortPagination) withClause := "" if len(qb.withClauses) > 0 { @@ -131,6 +133,18 @@ func (qb *queryBuilder) join(table, as, onClause string) { qb.joins.add(newJoin) } +func (qb *queryBuilder) joinSort(table, as, onClause string) { + newJoin := join{ + sort: true, + table: table, + as: as, + onClause: onClause, + joinType: "LEFT", + } + + qb.joins.add(newJoin) +} + func (qb *queryBuilder) addJoins(joins ...join) { for _, j := range joins { if qb.joins.addUnique(j) { diff --git a/pkg/sqlite/scene.go b/pkg/sqlite/scene.go index 40feb5847..955a98419 100644 --- a/pkg/sqlite/scene.go +++ b/pkg/sqlite/scene.go @@ -1157,10 +1157,12 @@ func (qb *SceneStore) setSceneSort(query *queryBuilder, findFilter *models.FindF addFileTable := func() { query.addJoins( join{ + sort: true, table: scenesFilesTable, onClause: "scenes_files.scene_id = scenes.id", }, join{ + sort: true, table: fileTable, onClause: "scenes_files.file_id = files.id", }, @@ -1171,6 +1173,7 @@ func (qb *SceneStore) setSceneSort(query *queryBuilder, findFilter *models.FindF addFileTable() query.addJoins( join{ + sort: true, table: videoFileTable, onClause: "video_files.file_id = scenes_files.file_id", }, @@ -1180,6 +1183,7 @@ func (qb *SceneStore) setSceneSort(query *queryBuilder, findFilter *models.FindF addFolderTable := func() { query.addJoins( join{ + sort: true, table: folderTable, onClause: "files.parent_folder_id = folders.id", }, @@ -1189,10 +1193,10 @@ func (qb *SceneStore) setSceneSort(query *queryBuilder, findFilter *models.FindF direction := findFilter.GetDirection() switch sort { case "movie_scene_number": - query.join(groupsScenesTable, "", "scenes.id = groups_scenes.scene_id") + query.joinSort(groupsScenesTable, "", "scenes.id = groups_scenes.scene_id") query.sortAndPagination += getSort("scene_index", direction, groupsScenesTable) case "group_scene_number": - query.join(groupsScenesTable, "scene_group", "scenes.id = scene_group.scene_id") + query.joinSort(groupsScenesTable, "scene_group", "scenes.id = scene_group.scene_id") query.sortAndPagination += getSort("scene_index", direction, "scene_group") case "tag_count": query.sortAndPagination += getCountSort(sceneTable, scenesTagsTable, sceneIDColumn, direction) @@ -1210,6 +1214,7 @@ func (qb *SceneStore) setSceneSort(query *queryBuilder, findFilter *models.FindF addFileTable() query.addJoins( join{ + sort: true, table: fingerprintTable, as: "fingerprints_phash", onClause: "scenes_files.file_id = fingerprints_phash.file_id AND fingerprints_phash.type = 'phash'", @@ -1274,7 +1279,7 @@ func (qb *SceneStore) setSceneSort(query *queryBuilder, findFilter *models.FindF getSortDirection(direction), ) case "studio": - query.join(studioTable, "", "scenes.studio_id = studios.id") + query.joinSort(studioTable, "", "scenes.studio_id = studios.id") query.sortAndPagination += getSort("name", direction, studioTable) default: query.sortAndPagination += getSort(sort, direction, "scenes") diff --git a/pkg/sqlite/scene_marker.go b/pkg/sqlite/scene_marker.go index 59a4137e1..d47df0e0f 100644 --- a/pkg/sqlite/scene_marker.go +++ b/pkg/sqlite/scene_marker.go @@ -392,10 +392,10 @@ func (qb *SceneMarkerStore) setSceneMarkerSort(query *queryBuilder, findFilter * switch sort { case "scenes_updated_at": sort = "updated_at" - query.join(sceneTable, "", "scenes.id = scene_markers.scene_id") + query.joinSort(sceneTable, "", "scenes.id = scene_markers.scene_id") query.sortAndPagination += getSort(sort, direction, sceneTable) case "title": - query.join(tagTable, "", "scene_markers.primary_tag_id = tags.id") + query.joinSort(tagTable, "", "scene_markers.primary_tag_id = tags.id") query.sortAndPagination += " ORDER BY COALESCE(NULLIF(scene_markers.title,''), tags.name) COLLATE NATURAL_CI " + direction case "duration": sort = "(scene_markers.end_seconds - scene_markers.seconds)"