优化数据库

This commit is contained in:
yml2213
2026-07-10 08:47:08 +08:00
parent 3318ed18ee
commit ce01c2aa34
9 changed files with 790 additions and 102 deletions
+102 -22
View File
@@ -1,42 +1,122 @@
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { closeDb, query, withTransaction } from './client.js'
import { runner, type RunnerOption } from 'node-pg-migrate'
import type { ClientConfig } from 'pg'
import { runtimeConfig } from '../config/runtime.js'
import { closeDb, query } from './client.js'
const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url))
const MIGRATIONS_DIR = path.join(CURRENT_DIR, 'migrations')
const MIGRATIONS_TABLE = 'schema_migrations'
const SHORT_NUMERIC_PREFIX_MESSAGE = /^Can't determine timestamp for \d+$/
export async function runDatabaseMigrations() {
const files = fs.readdirSync(MIGRATIONS_DIR)
.filter((name) => name.endsWith('.sql'))
.sort()
await ensureNodePgMigrateMetadataCompatibility()
await runner({
databaseUrl: createMigrationDatabaseConfig(),
dir: MIGRATIONS_DIR,
direction: 'up',
migrationsTable: MIGRATIONS_TABLE,
migrationsSchema: 'public',
schema: 'public',
singleTransaction: true,
checkOrder: true,
logger: {
debug: () => undefined,
info: (message) => {
console.info(`[db:migrate] ${message}`)
},
warn: (message) => {
console.warn(`[db:migrate] ${message}`)
},
error: (message) => {
if (SHORT_NUMERIC_PREFIX_MESSAGE.test(message)) {
return
}
console.error(`[db:migrate] ${message}`)
},
},
} satisfies RunnerOption)
await syncLegacyMigrationColumns()
}
async function ensureNodePgMigrateMetadataCompatibility() {
await query(`
CREATE TABLE IF NOT EXISTS schema_migrations (
CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE} (
id BIGSERIAL PRIMARY KEY,
filename TEXT NOT NULL UNIQUE,
name VARCHAR(255),
run_on TIMESTAMPTZ,
filename TEXT,
applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
`)
const appliedRows = await query('SELECT filename FROM schema_migrations')
const applied = new Set(appliedRows.rows.map((row) => String(row.filename)))
await query(`
ALTER TABLE ${MIGRATIONS_TABLE}
ADD COLUMN IF NOT EXISTS name VARCHAR(255),
ADD COLUMN IF NOT EXISTS run_on TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS filename TEXT,
ADD COLUMN IF NOT EXISTS applied_at TIMESTAMPTZ
`)
for (const filename of files) {
if (applied.has(filename)) {
continue
}
await query(`
ALTER TABLE ${MIGRATIONS_TABLE}
ALTER COLUMN filename DROP NOT NULL,
ALTER COLUMN applied_at SET DEFAULT NOW()
`)
const sql = fs.readFileSync(path.join(MIGRATIONS_DIR, filename), 'utf8')
await withTransaction(async (client) => {
await client.query(sql)
await client.query(
'INSERT INTO schema_migrations (filename, applied_at) VALUES ($1, NOW())',
[filename],
)
})
await query(`
UPDATE ${MIGRATIONS_TABLE}
SET
name = COALESCE(
NULLIF(name, ''),
NULLIF(REGEXP_REPLACE(COALESCE(filename, ''), '\\.[^.]+$', ''), ''),
CONCAT('legacy_', id)
),
run_on = COALESCE(run_on, applied_at, NOW())
WHERE name IS NULL
OR name = ''
OR run_on IS NULL
`)
await query(`
ALTER TABLE ${MIGRATIONS_TABLE}
ALTER COLUMN name SET NOT NULL,
ALTER COLUMN run_on SET NOT NULL
`)
await query(`
CREATE UNIQUE INDEX IF NOT EXISTS idx_schema_migrations_name
ON ${MIGRATIONS_TABLE}(name)
`)
}
async function syncLegacyMigrationColumns() {
await query(`
UPDATE ${MIGRATIONS_TABLE}
SET
filename = COALESCE(NULLIF(filename, ''), CONCAT(name, '.sql')),
applied_at = COALESCE(applied_at, run_on, NOW())
WHERE filename IS NULL
OR filename = ''
OR applied_at IS NULL
`)
}
function createMigrationDatabaseConfig(): ClientConfig {
const connectionString = String(runtimeConfig.database?.url || '').trim()
if (!connectionString) {
throw new Error('DATABASE_URL 未配置,无法执行数据库迁移')
}
return {
connectionString,
ssl: runtimeConfig.database?.ssl ? { rejectUnauthorized: false } : false,
}
}
@@ -50,6 +130,6 @@ if (process.argv[1] && path.resolve(process.argv[1]) === currentFilePath) {
.catch(async (error) => {
console.error(error)
await closeDb()
process.exit(1)
process.exitCode = 1
})
}