fix(db): 归一化寄卖迁移旧命名,修复部署迁移顺序校验
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
|
||||
import { planLegacyConsignmentMigrationCleanup } from './migrate.js'
|
||||
|
||||
const MAIN_MIGRATIONS = [
|
||||
'147_internal_sku_product_routing',
|
||||
'148_fulfillment_execution_truth',
|
||||
'149_finance_derived_scope',
|
||||
'150_voucher_consume_order_received_scope',
|
||||
'151_sales_daily_facts_on_account_channel',
|
||||
]
|
||||
|
||||
test('迁移归一化:生产旧命名 147–152 全部清理,152 未登记时不额外删除', () => {
|
||||
const stale = planLegacyConsignmentMigrationCleanup([
|
||||
'001_init',
|
||||
'146_voucher_consume_fallback_channel',
|
||||
'147_consignment_platform',
|
||||
'148_consignment_sms_logs',
|
||||
'149_consignment_inventory_category_backfill',
|
||||
'150_work_order_consignment_original_reward',
|
||||
'151_work_order_internal_matching_deadline_guard',
|
||||
'152_consignment_internal_match_cleanup',
|
||||
])
|
||||
|
||||
assert.deepEqual(stale, [
|
||||
'147_consignment_platform',
|
||||
'148_consignment_sms_logs',
|
||||
'149_consignment_inventory_category_backfill',
|
||||
'150_work_order_consignment_original_reward',
|
||||
'151_work_order_internal_matching_deadline_guard',
|
||||
'152_consignment_internal_match_cleanup',
|
||||
])
|
||||
})
|
||||
|
||||
test('迁移归一化:dev 顺延后的 152–158 一并清理(含已登记的 152)', () => {
|
||||
const stale = planLegacyConsignmentMigrationCleanup([
|
||||
'001_init',
|
||||
'146_voucher_consume_fallback_channel',
|
||||
'152_consignment_platform',
|
||||
'153_consignment_sms_logs',
|
||||
'154_consignment_inventory_category_backfill',
|
||||
'155_work_order_consignment_original_reward',
|
||||
'156_work_order_internal_matching_deadline_guard',
|
||||
'157_consignment_internal_match_cleanup',
|
||||
'158_consignment_match_log_snapshot',
|
||||
])
|
||||
|
||||
assert.deepEqual(stale, [
|
||||
'153_consignment_sms_logs',
|
||||
'154_consignment_inventory_category_backfill',
|
||||
'155_work_order_consignment_original_reward',
|
||||
'156_work_order_internal_matching_deadline_guard',
|
||||
'157_consignment_internal_match_cleanup',
|
||||
'158_consignment_match_log_snapshot',
|
||||
'152_consignment_platform',
|
||||
])
|
||||
})
|
||||
|
||||
test('迁移归一化:main 147–151 与 152 都已登记时不做任何清理', () => {
|
||||
const stale = planLegacyConsignmentMigrationCleanup([
|
||||
'001_init',
|
||||
'146_voucher_consume_fallback_channel',
|
||||
...MAIN_MIGRATIONS,
|
||||
'152_consignment_platform',
|
||||
])
|
||||
|
||||
assert.deepEqual(stale, [])
|
||||
})
|
||||
|
||||
test('迁移归一化:main 已执行、只剩旧寄卖记录时只清理旧名字', () => {
|
||||
const stale = planLegacyConsignmentMigrationCleanup([
|
||||
'001_init',
|
||||
...MAIN_MIGRATIONS,
|
||||
'147_consignment_platform',
|
||||
'148_consignment_sms_logs',
|
||||
])
|
||||
|
||||
assert.deepEqual(stale, ['147_consignment_platform', '148_consignment_sms_logs'])
|
||||
})
|
||||
|
||||
test('迁移归一化:152 已登记但 main 147–151 缺失时,152 也要重跑', () => {
|
||||
const stale = planLegacyConsignmentMigrationCleanup([
|
||||
'001_init',
|
||||
'146_voucher_consume_fallback_channel',
|
||||
'152_consignment_platform',
|
||||
])
|
||||
|
||||
assert.deepEqual(stale, ['152_consignment_platform'])
|
||||
})
|
||||
@@ -25,9 +25,42 @@ const MIGRATIONS_TABLE = 'schema_migrations'
|
||||
const SHORT_NUMERIC_PREFIX_MESSAGE = /^Can't determine timestamp for \d+$/
|
||||
const MIGRATION_FILE_PATTERN = /^(\d{3})_.+\.sql$/i
|
||||
|
||||
/** 合并后的寄卖迁移文件名(不含 .sql);main 147–151 保持原编号。 */
|
||||
const CONSOLIDATED_CONSIGNMENT_MIGRATION = '152_consignment_platform'
|
||||
const MAIN_MIGRATIONS_BEFORE_CONSIGNMENT = [
|
||||
'147_internal_sku_product_routing',
|
||||
'148_fulfillment_execution_truth',
|
||||
'149_finance_derived_scope',
|
||||
'150_voucher_consume_order_received_scope',
|
||||
'151_sales_daily_facts_on_account_channel',
|
||||
] as const
|
||||
|
||||
/**
|
||||
* 历史上寄卖迁移用过两套编号,且都已被部分环境登记过:
|
||||
* - 147_consignment_platform … 152_consignment_internal_match_cleanup(生产当前状态)
|
||||
* - 152_consignment_platform … 158_consignment_match_log_snapshot(dev 顺延后、合并前)
|
||||
* 合并后统一为 152_consignment_platform,这些旧名字必须从 schema_migrations 清掉。
|
||||
*/
|
||||
const LEGACY_CONSIGNMENT_MIGRATION_NAMES = [
|
||||
'147_consignment_platform',
|
||||
'148_consignment_sms_logs',
|
||||
'149_consignment_inventory_category_backfill',
|
||||
'150_work_order_consignment_original_reward',
|
||||
'151_work_order_internal_matching_deadline_guard',
|
||||
'152_consignment_internal_match_cleanup',
|
||||
'153_consignment_match_log_snapshot',
|
||||
'153_consignment_sms_logs',
|
||||
'154_consignment_inventory_category_backfill',
|
||||
'155_work_order_consignment_original_reward',
|
||||
'156_work_order_internal_matching_deadline_guard',
|
||||
'157_consignment_internal_match_cleanup',
|
||||
'158_consignment_match_log_snapshot',
|
||||
] as const
|
||||
|
||||
export async function runDatabaseMigrations() {
|
||||
assertMigrationFilesOrdered()
|
||||
await ensureNodePgMigrateMetadataCompatibility()
|
||||
await normalizeLegacyConsignmentMigrationNames()
|
||||
|
||||
console.info(`[db:migrate] 迁移目录: ${MIGRATIONS_DIR}`)
|
||||
|
||||
@@ -129,6 +162,42 @@ function assertMigrationFilesOrdered() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算需要从 schema_migrations 清理的旧寄卖迁移记录(纯函数,便于单测)。
|
||||
*
|
||||
* node-pg-migrate 的 checkOrder 是按位置逐项比对「已执行列表(ORDER BY run_on, id)」和
|
||||
* 「本地迁移文件列表」,因此只改名/改 run_on 都不够:只要 152 已登记而 main 147–151 未登记,
|
||||
* 152 就会占据第 147 个位置,和 147_internal_sku_product_routing 比对失败。
|
||||
* 唯一稳妥的做法是删掉旧记录,让 147–152 按新文件顺序重新执行;152 内容幂等。
|
||||
*/
|
||||
export function planLegacyConsignmentMigrationCleanup(appliedNames: readonly string[]): string[] {
|
||||
const applied = new Set(appliedNames)
|
||||
const staleNames: string[] = LEGACY_CONSIGNMENT_MIGRATION_NAMES.filter((name) =>
|
||||
applied.has(name),
|
||||
)
|
||||
|
||||
const mainMissing = MAIN_MIGRATIONS_BEFORE_CONSIGNMENT.some((name) => !applied.has(name))
|
||||
if (mainMissing && applied.has(CONSOLIDATED_CONSIGNMENT_MIGRATION)) {
|
||||
staleNames.push(CONSOLIDATED_CONSIGNMENT_MIGRATION)
|
||||
}
|
||||
|
||||
return staleNames
|
||||
}
|
||||
|
||||
async function normalizeLegacyConsignmentMigrationNames() {
|
||||
const { rows } = await query<{ name: string }>(`SELECT name FROM ${MIGRATIONS_TABLE}`)
|
||||
const staleNames = planLegacyConsignmentMigrationCleanup(rows.map((row) => row.name))
|
||||
if (staleNames.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
await query(`DELETE FROM ${MIGRATIONS_TABLE} WHERE name = ANY($1::text[])`, [staleNames])
|
||||
console.warn(
|
||||
`[db:migrate] 检测到旧寄卖迁移命名,已清理 ${staleNames.length} 条历史记录: ${staleNames.join(', ')};` +
|
||||
'将按 147–152 的新顺序重新执行(152 内容幂等,不会重复建表/加列)',
|
||||
)
|
||||
}
|
||||
|
||||
async function ensureNodePgMigrateMetadataCompatibility() {
|
||||
await query(`
|
||||
CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE} (
|
||||
|
||||
Reference in New Issue
Block a user