Files
usenet-indexer/commands/FilesMoveDetails.ts
T
Daan MeijerandClaude Opus 5.5 7c43abd230
Test and deploy / test (push) Successful in 1m15s
Test and deploy / deploy (push) Successful in 1m47s
files:move-details: short batches, so files deleted meanwhile don't fail the copy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 23:08:31 +02:00

57 lines
2.3 KiB
TypeScript

import { BaseCommand } from '@adonisjs/core/ace'
import { CommandOptions } from '@adonisjs/core/types/ace'
import db from '@adonisjs/lucid/services/db'
const BATCH = 50_000
/**
* One-off (LOG.md 2026-10-08 (14)): copies the content types and archive
* listings still on `files` into file_details, a partition at a time.
* Values written since the deploy (by the new code, into file_details) win:
* a file that has details already is skipped.
*/
export default class FilesMoveDetails extends BaseCommand {
public static commandName = 'files:move-details'
public static description = "Copies files' content types and archive listings into file_details."
public static options: CommandOptions = {
startApp: true,
}
public async run() {
const { rows: partitions } = await db.rawQuery(
`SELECT c.relname FROM pg_inherits i JOIN pg_class c ON c.oid = i.inhrelid WHERE i.inhparent = 'files'::regclass ORDER BY c.relname`
)
let total = 0
for (const { relname } of partitions) {
const started = Date.now()
const { rows } = await db.rawQuery(`SELECT min(id) AS lo, max(id) AS hi FROM ${relname}`)
if (rows[0].lo === null) continue
let copied = 0
// Short statements in id order: one statement over a whole partition took 30 minutes, and the
// garbage filter deleted files meanwhile, which failed the foreign key (LOG.md 2026-10-08 (15)).
for (let lo = Number(rows[0].lo); lo <= Number(rows[0].hi); lo += BATCH) {
for (let attempt = 1; ; attempt++) {
try {
const result = await db.rawQuery(
`INSERT INTO file_details (file_id, content_type, archive)
SELECT id, content_type, archive FROM ${relname}
WHERE id >= ? AND id < ? AND (content_type IS NOT NULL OR archive IS NOT NULL)
ON CONFLICT (file_id) DO NOTHING`,
[lo, lo + BATCH]
)
copied += result.rowCount ?? 0
break
} catch (error: any) {
// A file deleted while this batch ran: the batch again, without it.
if (error.code !== '23503' || attempt >= 5) throw error
}
}
}
total += copied
this.logger.info(`${relname}: ${copied} copied in ${Math.round((Date.now() - started) / 1000)} s.`)
}
this.logger.info(`${total} files' details copied.`)
}
}