diff --git a/server/migrations/v2.19.6-improve-author-sort-queries.js b/server/migrations/v2.19.6-improve-author-sort-queries.js index 7d3c1846a..a21f64284 100644 --- a/server/migrations/v2.19.6-improve-author-sort-queries.js +++ b/server/migrations/v2.19.6-improve-author-sort-queries.js @@ -18,6 +18,7 @@ const loggerPrefix = `[${migrationVersion} migration]` const libraryItems = 'libraryItems' const bookAuthors = 'bookAuthors' const authors = 'authors' +const podcastEpisodes = 'podcastEpisodes' const columns = [ { name: 'authorNamesFirstLast', source: `${authors}.name`, spec: { type: Sequelize.STRING, allowNull: true } }, { name: 'authorNamesLastFirst', source: `${authors}.lastFirst`, spec: { type: Sequelize.STRING, allowNull: true } } @@ -32,6 +33,8 @@ const authorsSort = `${bookAuthors}.createdAt ASC` * It also creates triggers to update the authorNames column when the corresponding bookAuthors and authors records are updated. * It also creates an index on the authorNames column. * + * It also adds an index on publishedAt to the podcastEpisodes table. + * * @param {MigrationOptions} options - an object containing the migration context. * @returns {Promise} - A promise that resolves when the migration is complete. */ @@ -53,6 +56,9 @@ async function up({ context: { queryInterface, logger } }) { // Create indexes on the authorNames columns await helper.addIndexes() + // Add index on publishedAt to the podcastEpisodes table + await helper.addIndex(podcastEpisodes, ['publishedAt']) + logger.info(`${loggerPrefix} UPGRADE END: ${migrationName}`) } @@ -60,6 +66,8 @@ async function up({ context: { queryInterface, logger } }) { * This downward migration removes the authorNames column from the libraryItems table, * the triggers on the bookAuthors and authors tables, and the index on the authorNames column. * + * It also removes the index on publishedAt from the podcastEpisodes table. + * * @param {MigrationOptions} options - an object containing the migration context. * @returns {Promise} - A promise that resolves when the migration is complete. */ @@ -72,6 +80,9 @@ async function down({ context: { queryInterface, logger } }) { // Remove triggers to update authorNames columns await helper.removeTriggers() + // Remove index on publishedAt from the podcastEpisodes table + await helper.removeIndex(podcastEpisodes, ['publishedAt']) + // Remove indexes on the authorNames columns await helper.removeIndexes() diff --git a/server/models/PodcastEpisode.js b/server/models/PodcastEpisode.js index 4746f3150..e6d629167 100644 --- a/server/models/PodcastEpisode.js +++ b/server/models/PodcastEpisode.js @@ -122,6 +122,10 @@ class PodcastEpisode extends Model { { name: 'podcastEpisode_createdAt_podcastId', fields: ['createdAt', 'podcastId'] + }, + { + name: 'podcast_episodes_published_at', + fields: ['publishedAt'] } ] } diff --git a/server/utils/queries/libraryItemsPodcastFilters.js b/server/utils/queries/libraryItemsPodcastFilters.js index 6527cfbd6..70400f87d 100644 --- a/server/utils/queries/libraryItemsPodcastFilters.js +++ b/server/utils/queries/libraryItemsPodcastFilters.js @@ -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 diff --git a/test/server/migrations/v2.19.6-improve-author-sort-queries.test.js b/test/server/migrations/v2.19.6-improve-author-sort-queries.test.js index 38102aa3d..039318641 100644 --- a/test/server/migrations/v2.19.6-improve-author-sort-queries.test.js +++ b/test/server/migrations/v2.19.6-improve-author-sort-queries.test.js @@ -39,6 +39,11 @@ describe('Migration v2.19.6-improve-author-sort-queries', () => { createdAt: { type: DataTypes.DATE, allowNull: false } }) + await queryInterface.createTable('podcastEpisodes', { + id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, unique: true }, + publishedAt: { type: DataTypes.DATE, allowNull: true } + }) + await queryInterface.bulkInsert('libraryItems', [ { id: 1, mediaId: 1, mediaType: 'book', libraryId: 1 }, { id: 2, mediaId: 2, mediaType: 'book', libraryId: 1 } @@ -55,6 +60,12 @@ describe('Migration v2.19.6-improve-author-sort-queries', () => { { id: 2, bookId: 2, authorId: 2, createdAt: '2025-01-02 00:00:00.000 +00:00' }, { id: 3, bookId: 1, authorId: 3, createdAt: '2025-01-03 00:00:00.000 +00:00' } ]) + + await queryInterface.bulkInsert('podcastEpisodes', [ + { id: 1, publishedAt: '2025-01-01 00:00:00.000 +00:00' }, + { id: 2, publishedAt: '2025-01-02 00:00:00.000 +00:00' }, + { id: 3, publishedAt: '2025-01-03 00:00:00.000 +00:00' } + ]) }) afterEach(() => { @@ -219,6 +230,20 @@ describe('Migration v2.19.6-improve-author-sort-queries', () => { ]) }) + it('should add an index on publishedAt to the podcastEpisodes table', async () => { + await up({ context: { queryInterface, logger: Logger } }) + + const [[{ count }]] = await queryInterface.sequelize.query(`SELECT COUNT(*) as count FROM sqlite_master WHERE type='index' AND name='podcast_episodes_published_at'`) + expect(count).to.equal(1) + + const [[{ sql }]] = await queryInterface.sequelize.query(`SELECT sql FROM sqlite_master WHERE type='index' AND name='podcast_episodes_published_at'`) + expect(normalizeWhitespaceAndBackticks(sql)).to.equal( + normalizeWhitespaceAndBackticks(` + CREATE INDEX podcast_episodes_published_at ON podcastEpisodes (publishedAt) + `) + ) + }) + it('should be idempotent', async () => { await up({ context: { queryInterface, logger: Logger } }) await up({ context: { queryInterface, logger: Logger } }) @@ -242,6 +267,9 @@ describe('Migration v2.19.6-improve-author-sort-queries', () => { const [[{ count: count5 }]] = await queryInterface.sequelize.query(`SELECT COUNT(*) as count FROM sqlite_master WHERE type='index' AND name='library_items_library_id_media_type_author_names_last_first'`) expect(count5).to.equal(1) + const [[{ count: count6 }]] = await queryInterface.sequelize.query(`SELECT COUNT(*) as count FROM sqlite_master WHERE type='index' AND name='podcast_episodes_published_at'`) + expect(count6).to.equal(1) + const [libraryItems] = await queryInterface.sequelize.query(`SELECT * FROM libraryItems`) expect(libraryItems).to.deep.equal([ { id: 1, mediaId: 1, mediaType: 'book', libraryId: 1, authorNamesFirstLast: 'John Doe, John Smith', authorNamesLastFirst: 'Doe, John, Smith, John' }, @@ -291,6 +319,14 @@ describe('Migration v2.19.6-improve-author-sort-queries', () => { expect(count2).to.equal(0) }) + it('should remove the index on publishedAt from the podcastEpisodes table', async () => { + await up({ context: { queryInterface, logger: Logger } }) + await down({ context: { queryInterface, logger: Logger } }) + + const [[{ count }]] = await queryInterface.sequelize.query(`SELECT COUNT(*) as count FROM sqlite_master WHERE type='index' AND name='podcast_episodes_published_at'`) + expect(count).to.equal(0) + }) + it('should be idempotent', async () => { await up({ context: { queryInterface, logger: Logger } }) await down({ context: { queryInterface, logger: Logger } }) @@ -320,6 +356,9 @@ describe('Migration v2.19.6-improve-author-sort-queries', () => { const [[{ count: count5 }]] = await queryInterface.sequelize.query(`SELECT COUNT(*) as count FROM sqlite_master WHERE type='index' AND name='library_items_library_id_media_type_author_names_last_first'`) expect(count5).to.equal(0) + + const [[{ count: count6 }]] = await queryInterface.sequelize.query(`SELECT COUNT(*) as count FROM sqlite_master WHERE type='index' AND name='podcast_episodes_published_at'`) + expect(count6).to.equal(0) }) }) })