Testing Migrations
This commit is contained in:
1 parent
81b4162117
commit
d9abeb7fb1
7 files changed
+105
-78
No files matched your search
@@ -10,7 +10,7 @@
|
||||
//
|
||||
// The restricted account is correct - the server ingests untrusted URLs and
|
||||
// should not be able to reshape its own tables. So DDL moved out of the
|
||||
// application entirely: an admin runs ./applyScrapeSchema.sh once, and this
|
||||
// application entirely: an admin runs ./migrateDatabase.sh once, and this
|
||||
// module only checks that the result is present and usable.
|
||||
//
|
||||
// Note on why the DDL is REMOVED rather than guarded by an existence check:
|
||||
@@ -27,6 +27,17 @@ const REQUIRED_TABLE = 'scrape_cache'
|
||||
const REQUIRED_COLUMNS = ['scrape_state', 'scrape_attempts', 'enqueued_at']
|
||||
const REQUIRED_INDEX = 'attachment_scrape_state'
|
||||
|
||||
//
|
||||
// The FULLTEXT index backing Attachment.textSearch's MATCH..AGAINST.
|
||||
//
|
||||
// Checked by TYPE, not just existence: Prisma's `@@fulltext` is a preview
|
||||
// feature and MariaDB is not among its documented databases, so a future
|
||||
// migration could plausibly emit a plain BTREE index of the same name. That
|
||||
// would make attachment search return nothing at all, with no error anywhere -
|
||||
// so it is worth asserting the type explicitly on every boot.
|
||||
//
|
||||
const REQUIRED_FULLTEXT = { table: 'attachment', index: 'attachment_text_index' }
|
||||
|
||||
// The scraper writes and deletes, not just reads. DELETE is genuinely needed:
|
||||
// terminal failures drop the shell attachment row (ScrapeQueue), and
|
||||
// ScrapeCache.purgeExpired removes expired rows.
|
||||
@@ -62,6 +73,19 @@ const columnExists = async (table, column) => {
|
||||
return rows.length > 0
|
||||
}
|
||||
|
||||
//
|
||||
// Does this index exist AND is it actually FULLTEXT?
|
||||
//
|
||||
const fulltextIndexExists = async (table, index) => {
|
||||
const [rows] = await db.promise().query(
|
||||
`SELECT 1 FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND INDEX_NAME = ?
|
||||
AND INDEX_TYPE = 'FULLTEXT' LIMIT 1`,
|
||||
[table, index]
|
||||
)
|
||||
return rows.length > 0
|
||||
}
|
||||
|
||||
const indexExists = async (table, index) => {
|
||||
const [rows] = await db.promise().query(
|
||||
`SELECT 1 FROM information_schema.STATISTICS
|
||||
@@ -74,6 +98,7 @@ const indexExists = async (table, index) => {
|
||||
Schema.__tableExists = tableExists
|
||||
Schema.__columnExists = columnExists
|
||||
Schema.__indexExists = indexExists
|
||||
Schema.__fulltextIndexExists = fulltextIndexExists
|
||||
|
||||
//
|
||||
// Parse SHOW GRANTS output into the set of privileges that apply to this
|
||||
@@ -182,12 +207,16 @@ Schema.verifyScrapeSchema = async () => {
|
||||
missingObjects.push(`attachment index ${REQUIRED_INDEX}`)
|
||||
}
|
||||
|
||||
if (!await fulltextIndexExists(REQUIRED_FULLTEXT.table, REQUIRED_FULLTEXT.index)) {
|
||||
missingObjects.push(`FULLTEXT index ${REQUIRED_FULLTEXT.table}.${REQUIRED_FULLTEXT.index}`)
|
||||
}
|
||||
|
||||
const privileges = await checkPrivileges()
|
||||
const missingPrivileges = privileges.missing || []
|
||||
|
||||
const remedy = []
|
||||
if (missingObjects.length) {
|
||||
remedy.push('run ./applyScrapeSchema.sh as a database admin')
|
||||
remedy.push('run ./migrateDatabase.sh as a database admin')
|
||||
}
|
||||
if (missingPrivileges.length) {
|
||||
// Name the real database and user so this can be pasted straight into
|
||||
|
||||
Reference in new issue
Block a user