mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-07 09:51:37 +00:00
perf: add large-library browse indexes
This commit is contained in:
parent
cb57d8c372
commit
d39de74db0
3 changed files with 333 additions and 0 deletions
|
|
@ -0,0 +1,95 @@
|
|||
const chai = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const { expect } = chai
|
||||
|
||||
const { DataTypes, Sequelize } = require('sequelize')
|
||||
const Logger = require('../../../server/Logger')
|
||||
|
||||
const { up } = require('../../../server/migrations/v2.32.6-large-library-browse-indexes')
|
||||
|
||||
const TEST_POSTGRES_URL = process.env.TEST_POSTGRES_URL
|
||||
|
||||
describe('Migration v2.32.6-large-library-browse-indexes (postgres)', () => {
|
||||
if (!TEST_POSTGRES_URL) {
|
||||
it('skips because TEST_POSTGRES_URL is not set', function() {
|
||||
this.skip()
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
let sequelize
|
||||
let queryInterface
|
||||
|
||||
beforeEach(async () => {
|
||||
sequelize = new Sequelize(TEST_POSTGRES_URL, {
|
||||
dialect: 'postgres',
|
||||
logging: false
|
||||
})
|
||||
queryInterface = sequelize.getQueryInterface()
|
||||
sinon.stub(Logger, 'info')
|
||||
|
||||
await queryInterface.dropTable('mediaProgresses').catch(() => {})
|
||||
await queryInterface.dropTable('libraryItems').catch(() => {})
|
||||
await queryInterface.dropTable('books').catch(() => {})
|
||||
|
||||
await queryInterface.createTable('books', {
|
||||
id: { type: DataTypes.UUID, allowNull: false, primaryKey: true }
|
||||
})
|
||||
|
||||
await queryInterface.createTable('libraryItems', {
|
||||
id: { type: DataTypes.UUID, allowNull: false, primaryKey: true },
|
||||
libraryId: { type: DataTypes.UUID, allowNull: false },
|
||||
mediaType: { type: DataTypes.STRING, allowNull: false },
|
||||
title: { type: DataTypes.TEXT, allowNull: true },
|
||||
titleIgnorePrefix: { type: DataTypes.TEXT, allowNull: true },
|
||||
createdAt: { type: DataTypes.DATE, allowNull: true }
|
||||
})
|
||||
|
||||
await queryInterface.createTable('mediaProgresses', {
|
||||
id: { type: DataTypes.UUID, allowNull: false, primaryKey: true },
|
||||
userId: { type: DataTypes.UUID, allowNull: false },
|
||||
updatedAt: { type: DataTypes.DATE, allowNull: true }
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
sinon.restore()
|
||||
if (queryInterface) {
|
||||
await queryInterface.dropTable('mediaProgresses').catch(() => {})
|
||||
await queryInterface.dropTable('libraryItems').catch(() => {})
|
||||
await queryInterface.dropTable('books').catch(() => {})
|
||||
}
|
||||
if (sequelize) {
|
||||
await sequelize.close()
|
||||
}
|
||||
})
|
||||
|
||||
it('creates browse indexes with the expected names and column order', async () => {
|
||||
await up({ context: { queryInterface, logger: Logger } })
|
||||
|
||||
const [rows] = await queryInterface.sequelize.query(`
|
||||
SELECT indexname, indexdef
|
||||
FROM pg_indexes
|
||||
WHERE schemaname = 'public'
|
||||
AND indexname IN (
|
||||
'library_items_library_media_type_title_id',
|
||||
'library_items_library_media_type_title_ignore_prefix_id',
|
||||
'library_items_library_media_type_created_at_id',
|
||||
'media_progress_user_updated_at_id'
|
||||
)
|
||||
ORDER BY indexname ASC
|
||||
`)
|
||||
|
||||
expect(rows.map((row) => row.indexname)).to.deep.equal([
|
||||
'library_items_library_media_type_created_at_id',
|
||||
'library_items_library_media_type_title_id',
|
||||
'library_items_library_media_type_title_ignore_prefix_id',
|
||||
'media_progress_user_updated_at_id'
|
||||
])
|
||||
|
||||
expect(rows.find((row) => row.indexname === 'library_items_library_media_type_title_id').indexdef).to.include('("libraryId", "mediaType", title, id)')
|
||||
expect(rows.find((row) => row.indexname === 'library_items_library_media_type_title_ignore_prefix_id').indexdef).to.include('("libraryId", "mediaType", "titleIgnorePrefix", id)')
|
||||
expect(rows.find((row) => row.indexname === 'library_items_library_media_type_created_at_id').indexdef).to.include('("libraryId", "mediaType", "createdAt", id)')
|
||||
expect(rows.find((row) => row.indexname === 'media_progress_user_updated_at_id').indexdef).to.include('("userId", "updatedAt", id)')
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
const chai = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const { expect } = chai
|
||||
|
||||
const { DataTypes, Sequelize } = require('sequelize')
|
||||
const Logger = require('../../../server/Logger')
|
||||
|
||||
const { up } = require('../../../server/migrations/v2.32.6-large-library-browse-indexes')
|
||||
|
||||
const normalizeWhitespace = (str) => str.replace(/\s+/g, ' ').trim().replace(/`/g, '')
|
||||
|
||||
describe('Migration v2.32.6-large-library-browse-indexes (sqlite)', () => {
|
||||
let sequelize
|
||||
let queryInterface
|
||||
|
||||
beforeEach(async () => {
|
||||
sequelize = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false })
|
||||
queryInterface = sequelize.getQueryInterface()
|
||||
sinon.stub(Logger, 'info')
|
||||
|
||||
await queryInterface.createTable('books', {
|
||||
id: { type: DataTypes.UUID, allowNull: false, primaryKey: true }
|
||||
})
|
||||
|
||||
await queryInterface.createTable('libraryItems', {
|
||||
id: { type: DataTypes.UUID, allowNull: false, primaryKey: true },
|
||||
libraryId: { type: DataTypes.UUID, allowNull: false },
|
||||
mediaType: { type: DataTypes.STRING, allowNull: false },
|
||||
title: { type: DataTypes.TEXT, allowNull: true },
|
||||
titleIgnorePrefix: { type: DataTypes.TEXT, allowNull: true },
|
||||
createdAt: { type: DataTypes.DATE, allowNull: true }
|
||||
})
|
||||
|
||||
await queryInterface.createTable('mediaProgresses', {
|
||||
id: { type: DataTypes.UUID, allowNull: false, primaryKey: true },
|
||||
userId: { type: DataTypes.UUID, allowNull: false },
|
||||
updatedAt: { type: DataTypes.DATE, allowNull: true }
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
sinon.restore()
|
||||
await sequelize.close()
|
||||
})
|
||||
|
||||
it('creates browse indexes for title and progress keyset traversal', async () => {
|
||||
await up({ context: { queryInterface, logger: Logger } })
|
||||
|
||||
const [[{ count: titleExactIndexCount }]] = await queryInterface.sequelize.query(
|
||||
"SELECT COUNT(*) as count FROM sqlite_master WHERE type='index' AND name='library_items_library_media_type_title_id'"
|
||||
)
|
||||
expect(titleExactIndexCount).to.equal(1)
|
||||
|
||||
const [[{ sql: titleExactIndexSql }]] = await queryInterface.sequelize.query(
|
||||
"SELECT sql FROM sqlite_master WHERE type='index' AND name='library_items_library_media_type_title_id'"
|
||||
)
|
||||
expect(normalizeWhitespace(titleExactIndexSql)).to.equal(
|
||||
normalizeWhitespace('CREATE INDEX library_items_library_media_type_title_id ON libraryItems (libraryId, mediaType, title COLLATE NOCASE, id)')
|
||||
)
|
||||
|
||||
const [[{ count: titleIndexCount }]] = await queryInterface.sequelize.query(
|
||||
"SELECT COUNT(*) as count FROM sqlite_master WHERE type='index' AND name='library_items_library_media_type_title_ignore_prefix_id'"
|
||||
)
|
||||
expect(titleIndexCount).to.equal(1)
|
||||
|
||||
const [[{ sql: titleIndexSql }]] = await queryInterface.sequelize.query(
|
||||
"SELECT sql FROM sqlite_master WHERE type='index' AND name='library_items_library_media_type_title_ignore_prefix_id'"
|
||||
)
|
||||
expect(normalizeWhitespace(titleIndexSql)).to.equal(
|
||||
normalizeWhitespace('CREATE INDEX library_items_library_media_type_title_ignore_prefix_id ON libraryItems (libraryId, mediaType, titleIgnorePrefix COLLATE NOCASE, id)')
|
||||
)
|
||||
|
||||
const [[{ sql: createdAtIndexSql }]] = await queryInterface.sequelize.query(
|
||||
"SELECT sql FROM sqlite_master WHERE type='index' AND name='library_items_library_media_type_created_at_id'"
|
||||
)
|
||||
expect(normalizeWhitespace(createdAtIndexSql)).to.equal(
|
||||
normalizeWhitespace('CREATE INDEX library_items_library_media_type_created_at_id ON libraryItems (libraryId, mediaType, createdAt, id)')
|
||||
)
|
||||
|
||||
const [[{ sql: progressIndexSql }]] = await queryInterface.sequelize.query(
|
||||
"SELECT sql FROM sqlite_master WHERE type='index' AND name='media_progress_user_updated_at_id'"
|
||||
)
|
||||
expect(normalizeWhitespace(progressIndexSql)).to.equal(
|
||||
normalizeWhitespace('CREATE INDEX media_progress_user_updated_at_id ON mediaProgresses (userId, updatedAt, id)')
|
||||
)
|
||||
})
|
||||
|
||||
it('is idempotent', async () => {
|
||||
await up({ context: { queryInterface, logger: Logger } })
|
||||
await up({ context: { queryInterface, logger: Logger } })
|
||||
|
||||
const indexes = [
|
||||
'library_items_library_media_type_title_id',
|
||||
'library_items_library_media_type_title_ignore_prefix_id',
|
||||
'library_items_library_media_type_created_at_id',
|
||||
'media_progress_user_updated_at_id'
|
||||
]
|
||||
|
||||
for (const indexName of indexes) {
|
||||
const [[{ count }]] = await queryInterface.sequelize.query(
|
||||
`SELECT COUNT(*) as count FROM sqlite_master WHERE type='index' AND name='${indexName}'`
|
||||
)
|
||||
expect(count, indexName).to.equal(1)
|
||||
}
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue