Improve book library page query performance for author sort order (#4080)

* Add migration to create authorNames* columns, in libraryItems including update triggers and indices

* Add authorNames columns and indices to LibraryItem model

* Add database triggers for updating author names in libraryItems (for new databases)

* Populate authorNames during book scanning

* Update book sorting to use new authorNames columns

* Add an index on podcastEpisodes.publishedAt

* Fix group_concat order by and update to sqlite 3.44.2

---------

Co-authored-by: advplyr <advplyr@protonmail.com>
This commit is contained in:
mikiher 2025-03-18 00:09:49 +02:00 committed by GitHub
parent bba09626a7
commit 40504da4d7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 1133 additions and 151 deletions

View file

@ -264,9 +264,9 @@ module.exports = {
} else if (sortBy === 'media.metadata.publishedYear') {
return [[Sequelize.literal(`CAST(\`book\`.\`publishedYear\` AS INTEGER)`), dir]]
} else if (sortBy === 'media.metadata.authorNameLF') {
return [[Sequelize.literal('author_name COLLATE NOCASE'), dir]]
return [[Sequelize.literal('`libraryItem`.`authorNamesLastFirst` COLLATE NOCASE'), dir]]
} else if (sortBy === 'media.metadata.authorName') {
return [[Sequelize.literal('author_name COLLATE NOCASE'), dir]]
return [[Sequelize.literal('`libraryItem`.`authorNamesFirstLast` COLLATE NOCASE'), dir]]
} else if (sortBy === 'media.metadata.title') {
if (collapseseries) {
return [[Sequelize.literal('display_title COLLATE NOCASE'), dir]]
@ -397,18 +397,7 @@ module.exports = {
const includeRSSFeed = include.includes('rssfeed')
const includeMediaItemShare = !!user?.isAdminOrUp && include.includes('share')
// For sorting by author name an additional attribute must be added
// with author names concatenated
let bookAttributes = null
if (sortBy === 'media.metadata.authorNameLF') {
bookAttributes = {
include: [[Sequelize.literal(`(SELECT group_concat(lastFirst, ", ") FROM (SELECT a.lastFirst FROM authors AS a, bookAuthors as ba WHERE ba.authorId = a.id AND ba.bookId = book.id ORDER BY ba.createdAt ASC))`), 'author_name']]
}
} else if (sortBy === 'media.metadata.authorName') {
bookAttributes = {
include: [[Sequelize.literal(`(SELECT group_concat(name, ", ") FROM (SELECT a.name FROM authors AS a, bookAuthors as ba WHERE ba.authorId = a.id AND ba.bookId = book.id ORDER BY ba.createdAt ASC))`), 'author_name']]
}
}
const libraryItemWhere = {
libraryId

View file

@ -465,7 +465,7 @@ module.exports = {
async getRecentEpisodes(user, library, limit, offset) {
const userPermissionPodcastWhere = this.getUserPermissionPodcastWhereQuery(user)
const episodes = await Database.podcastEpisodeModel.findAll({
const findOptions = {
where: {
'$mediaProgresses.isFinished$': {
[Sequelize.Op.or]: [null, false]
@ -496,7 +496,11 @@ module.exports = {
subQuery: false,
limit,
offset
})
}
const findtAll = process.env.QUERY_PROFILING ? profile(Database.podcastEpisodeModel.findAll.bind(Database.podcastEpisodeModel)) : Database.podcastEpisodeModel.findAll.bind(Database.podcastEpisodeModel)
const episodes = await findtAll(findOptions)
const episodeResults = episodes.map((ep) => {
ep.podcast.podcastEpisodes = [] // Not needed