skip sqlite-only migrations on postgres

This commit is contained in:
Kevin Gatera 2026-03-03 17:17:01 -05:00
parent 7d97d09191
commit 024f892902
No known key found for this signature in database
GPG key ID: F0D9F5932458CFB9
10 changed files with 134 additions and 2 deletions

View file

@ -19,8 +19,12 @@ async function up({ context: { queryInterface, logger } }) {
logger.info('[2.15.0 migration] UPGRADE BEGIN: 2.15.0-series-column-unique ') logger.info('[2.15.0 migration] UPGRADE BEGIN: 2.15.0-series-column-unique ')
// Run reindex nocase to fix potential corruption issues due to the bad sqlite extension introduced in v2.12.0 // Run reindex nocase to fix potential corruption issues due to the bad sqlite extension introduced in v2.12.0
logger.info('[2.15.0 migration] Reindexing NOCASE indices to fix potential hidden corruption issues') if (queryInterface.sequelize.getDialect() === 'sqlite') {
await queryInterface.sequelize.query('REINDEX NOCASE;') logger.info('[2.15.0 migration] Reindexing NOCASE indices to fix potential hidden corruption issues')
await queryInterface.sequelize.query('REINDEX NOCASE;')
} else {
logger.info('[2.15.0 migration] Skipping NOCASE reindex on non-sqlite dialect')
}
// Check if the unique index already exists // Check if the unique index already exists
const seriesIndexes = await queryInterface.showIndex('Series') const seriesIndexes = await queryInterface.showIndex('Series')

View file

@ -17,6 +17,12 @@ async function up({ context: { queryInterface, logger } }) {
// Upwards migration script // Upwards migration script
logger.info('[2.15.1 migration] UPGRADE BEGIN: 2.15.1-reindex-nocase ') logger.info('[2.15.1 migration] UPGRADE BEGIN: 2.15.1-reindex-nocase ')
if (queryInterface.sequelize.getDialect() !== 'sqlite') {
logger.info('[2.15.1 migration] Skipping NOCASE reindex on non-sqlite dialect')
logger.info('[2.15.1 migration] UPGRADE END: 2.15.1-reindex-nocase ')
return
}
// Run reindex nocase to fix potential corruption issues due to the bad sqlite extension introduced in v2.12.0 // Run reindex nocase to fix potential corruption issues due to the bad sqlite extension introduced in v2.12.0
logger.info('[2.15.1 migration] Reindexing NOCASE indices to fix potential hidden corruption issues') logger.info('[2.15.1 migration] Reindexing NOCASE indices to fix potential hidden corruption issues')
await queryInterface.sequelize.query('REINDEX NOCASE;') await queryInterface.sequelize.query('REINDEX NOCASE;')

View file

@ -18,6 +18,12 @@ async function up({ context: { queryInterface, logger } }) {
// Upwards migration script // Upwards migration script
logger.info('[2.17.3 migration] UPGRADE BEGIN: 2.17.3-fk-constraints') logger.info('[2.17.3 migration] UPGRADE BEGIN: 2.17.3-fk-constraints')
if (queryInterface.sequelize.getDialect() !== 'sqlite') {
logger.info('[2.17.3 migration] Skipping sqlite-specific foreign key rewrite on non-sqlite dialect')
logger.info('[2.17.3 migration] UPGRADE END: 2.17.3-fk-constraints')
return
}
const execQuery = queryInterface.sequelize.query.bind(queryInterface.sequelize) const execQuery = queryInterface.sequelize.query.bind(queryInterface.sequelize)
// Disable foreign key constraints for the next sequence of operations // Disable foreign key constraints for the next sequence of operations

View file

@ -25,6 +25,12 @@ async function up({ context: { queryInterface, logger } }) {
// Upwards migration script // Upwards migration script
logger.info(`${loggerPrefix} UPGRADE BEGIN: ${migrationName}`) logger.info(`${loggerPrefix} UPGRADE BEGIN: ${migrationName}`)
if (queryInterface.sequelize.getDialect() !== 'sqlite') {
logger.info(`${loggerPrefix} skipping sqlite-specific migration on non-sqlite dialect`)
logger.info(`${loggerPrefix} UPGRADE END: ${migrationName}`)
return
}
await addColumn(queryInterface, logger, 'libraryItems', 'title', { type: queryInterface.sequelize.Sequelize.STRING, allowNull: true }) await addColumn(queryInterface, logger, 'libraryItems', 'title', { type: queryInterface.sequelize.Sequelize.STRING, allowNull: true })
await copyColumn(queryInterface, logger, 'books', 'title', 'id', 'libraryItems', 'title', 'mediaId') await copyColumn(queryInterface, logger, 'books', 'title', 'id', 'libraryItems', 'title', 'mediaId')
await addTrigger(queryInterface, logger, 'books', 'title', 'id', 'libraryItems', 'title', 'mediaId') await addTrigger(queryInterface, logger, 'books', 'title', 'id', 'libraryItems', 'title', 'mediaId')
@ -51,6 +57,12 @@ async function down({ context: { queryInterface, logger } }) {
// Downward migration script // Downward migration script
logger.info(`${loggerPrefix} DOWNGRADE BEGIN: ${migrationName}`) logger.info(`${loggerPrefix} DOWNGRADE BEGIN: ${migrationName}`)
if (queryInterface.sequelize.getDialect() !== 'sqlite') {
logger.info(`${loggerPrefix} skipping sqlite-specific rollback on non-sqlite dialect`)
logger.info(`${loggerPrefix} DOWNGRADE END: ${migrationName}`)
return
}
await removeIndex(queryInterface, logger, 'libraryItems', ['libraryId', 'mediaType', 'title']) await removeIndex(queryInterface, logger, 'libraryItems', ['libraryId', 'mediaType', 'title'])
await removeTrigger(queryInterface, logger, 'libraryItems', 'title') await removeTrigger(queryInterface, logger, 'libraryItems', 'title')
await removeColumn(queryInterface, logger, 'libraryItems', 'title') await removeColumn(queryInterface, logger, 'libraryItems', 'title')

View file

@ -26,6 +26,12 @@ async function up({ context: { queryInterface, logger } }) {
// Upwards migration script // Upwards migration script
logger.info(`${loggerPrefix} UPGRADE BEGIN: ${migrationName}`) logger.info(`${loggerPrefix} UPGRADE BEGIN: ${migrationName}`)
if (queryInterface.sequelize.getDialect() !== 'sqlite') {
logger.info(`${loggerPrefix} skipping sqlite-specific migration on non-sqlite dialect`)
logger.info(`${loggerPrefix} UPGRADE END: ${migrationName}`)
return
}
// Add numEpisodes column to podcasts table // Add numEpisodes column to podcasts table
await addColumn(queryInterface, logger, 'podcasts', 'numEpisodes', { type: queryInterface.sequelize.Sequelize.INTEGER, allowNull: false, defaultValue: 0 }) await addColumn(queryInterface, logger, 'podcasts', 'numEpisodes', { type: queryInterface.sequelize.Sequelize.INTEGER, allowNull: false, defaultValue: 0 })
@ -60,6 +66,12 @@ async function down({ context: { queryInterface, logger } }) {
// Downward migration script // Downward migration script
logger.info(`${loggerPrefix} DOWNGRADE BEGIN: ${migrationName}`) logger.info(`${loggerPrefix} DOWNGRADE BEGIN: ${migrationName}`)
if (queryInterface.sequelize.getDialect() !== 'sqlite') {
logger.info(`${loggerPrefix} skipping sqlite-specific rollback on non-sqlite dialect`)
logger.info(`${loggerPrefix} DOWNGRADE END: ${migrationName}`)
return
}
// Remove triggers from libraryItems // Remove triggers from libraryItems
await removeTrigger(queryInterface, logger, 'podcasts', 'title', 'libraryItems', 'title') await removeTrigger(queryInterface, logger, 'podcasts', 'title', 'libraryItems', 'title')
await removeTrigger(queryInterface, logger, 'podcasts', 'titleIgnorePrefix', 'libraryItems', 'titleIgnorePrefix') await removeTrigger(queryInterface, logger, 'podcasts', 'titleIgnorePrefix', 'libraryItems', 'titleIgnorePrefix')

View file

@ -88,6 +88,23 @@ describe('migration-v2.15.0-series-column-unique', () => {
await queryInterface.dropTable('Series') await queryInterface.dropTable('Series')
await queryInterface.dropTable('BookSeries') await queryInterface.dropTable('BookSeries')
}) })
it('should skip NOCASE reindex on postgres dialect', async () => {
sinon.stub(queryInterface.sequelize, 'getDialect').returns('postgres')
const originalQuery = queryInterface.sequelize.query.bind(queryInterface.sequelize)
const queryStub = sinon.stub(queryInterface.sequelize, 'query').callsFake((sql, options) => {
if (sql === 'REINDEX NOCASE;') {
throw new Error('Unexpected NOCASE reindex in postgres mode')
}
return originalQuery(sql, options)
})
await up({ context: { queryInterface, logger: Logger } })
expect(queryStub.neverCalledWith('REINDEX NOCASE;')).to.be.true
expect(loggerInfoStub.calledWith(sinon.match('[2.15.0 migration] Skipping NOCASE reindex on non-sqlite dialect'))).to.be.true
})
it('upgrade with no duplicate series', async () => { it('upgrade with no duplicate series', async () => {
// Add some entries to the Series table using the UUID for the ids // Add some entries to the Series table using the UUID for the ids
await queryInterface.bulkInsert('Series', [ await queryInterface.bulkInsert('Series', [

View file

@ -0,0 +1,41 @@
const { expect } = require('chai')
const sinon = require('sinon')
const { up, down } = require('../../../server/migrations/v2.15.1-reindex-nocase')
const Logger = require('../../../server/Logger')
describe('migration-v2.15.1-reindex-nocase', () => {
afterEach(() => {
sinon.restore()
})
it('should skip reindex on non-sqlite dialect', async () => {
const queryStub = sinon.stub().resolves()
const loggerInfoStub = sinon.stub(Logger, 'info')
const queryInterface = {
sequelize: {
getDialect: () => 'postgres',
query: queryStub
}
}
await up({ context: { queryInterface, logger: Logger } })
expect(queryStub.called).to.equal(false)
expect(loggerInfoStub.calledWith(sinon.match('[2.15.1 migration] Skipping NOCASE reindex on non-sqlite dialect'))).to.equal(true)
})
it('should log no-op on down migration', async () => {
const loggerInfoStub = sinon.stub(Logger, 'info')
const queryInterface = {
sequelize: {
getDialect: () => 'postgres',
query: sinon.stub().resolves()
}
}
await down({ context: { queryInterface, logger: Logger } })
expect(loggerInfoStub.calledWith(sinon.match('[2.15.1 migration] No action required for downgrade'))).to.equal(true)
})
})

View file

@ -34,6 +34,16 @@ describe('migration-v2.17.3-fk-constraints', () => {
await queryInterface.dropAllTables() await queryInterface.dropAllTables()
}) })
it('should skip sqlite-specific migration on non-sqlite dialect', async () => {
sinon.stub(queryInterface.sequelize, 'getDialect').returns('postgres')
const queryStub = sinon.stub(queryInterface.sequelize, 'query').resolves([])
await up({ context: { queryInterface, logger: Logger } })
expect(queryStub.called).to.equal(false)
expect(loggerInfoStub.calledWith(sinon.match('[2.17.3 migration] Skipping sqlite-specific foreign key rewrite on non-sqlite dialect'))).to.equal(true)
})
it('should fix table foreign key constraints', async () => { it('should fix table foreign key constraints', async () => {
// Create tables with missing foreign key constraints: libraryItems, feeds, mediaItemShares, playbackSessions, playlistMediaItems, mediaProgresses // Create tables with missing foreign key constraints: libraryItems, feeds, mediaItemShares, playbackSessions, playlistMediaItems, mediaProgresses
await queryInterface.sequelize.query('CREATE TABLE `libraryItems` (`id` UUID UNIQUE PRIMARY KEY, `libraryId` UUID REFERENCES `libraries` (`id`), `libraryFolderId` UUID REFERENCES `libraryFolders` (`id`));') await queryInterface.sequelize.query('CREATE TABLE `libraryItems` (`id` UUID UNIQUE PRIMARY KEY, `libraryId` UUID REFERENCES `libraries` (`id`), `libraryFolderId` UUID REFERENCES `libraryFolders` (`id`));')

View file

@ -47,6 +47,18 @@ describe('Migration v2.19.1-copy-title-to-library-items', () => {
}) })
describe('up', () => { describe('up', () => {
it('should skip sqlite-specific migration on non-sqlite dialect', async () => {
sinon.stub(queryInterface.sequelize, 'getDialect').returns('postgres')
await up({ context: { queryInterface, logger: Logger } })
const table = await queryInterface.describeTable('libraryItems')
expect(table).to.have.property('id')
expect(table).to.not.have.property('title')
expect(table).to.not.have.property('titleIgnorePrefix')
expect(loggerInfoStub.calledWith(sinon.match('[2.19.1 migration] skipping sqlite-specific migration on non-sqlite dialect'))).to.be.true
})
it('should copy title and titleIgnorePrefix to libraryItems', async () => { it('should copy title and titleIgnorePrefix to libraryItems', async () => {
await up({ context: { queryInterface, logger: Logger } }) await up({ context: { queryInterface, logger: Logger } })

View file

@ -75,6 +75,18 @@ describe('Migration v2.19.4-improve-podcast-queries', () => {
}) })
describe('up', () => { describe('up', () => {
it('should skip sqlite-specific migration on non-sqlite dialect', async () => {
sinon.stub(queryInterface.sequelize, 'getDialect').returns('postgres')
await up({ context: { queryInterface, logger: Logger } })
const podcastsTable = await queryInterface.describeTable('podcasts')
const mediaProgressesTable = await queryInterface.describeTable('mediaProgresses')
expect(podcastsTable).to.not.have.property('numEpisodes')
expect(mediaProgressesTable).to.not.have.property('podcastId')
expect(loggerInfoStub.calledWith(sinon.match('[2.19.4 migration] skipping sqlite-specific migration on non-sqlite dialect'))).to.be.true
})
it('should add numEpisodes column to podcasts', async () => { it('should add numEpisodes column to podcasts', async () => {
await up({ context: { queryInterface, logger: Logger } }) await up({ context: { queryInterface, logger: Logger } })