Skip to content

Commit 25f3e37

Browse files
committed
fix(migration): fix the bug in migration
1 parent 23eb7c0 commit 25f3e37

File tree

2 files changed

+85
-45
lines changed

2 files changed

+85
-45
lines changed

packages/server/src/database.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ export const initializeDatabase = async (
3030
FridayAppReplyTable,
3131
FridayAppReplyView,
3232
],
33-
synchronize: true, // 可以改回 true 了,因为表由 Migration 创建
33+
synchronize: true,
3434
migrations: migrations,
35-
migrationsRun: true, // 自动运行迁移
35+
migrationsRun: true, // Run migrations automatically
3636
logging: false,
3737
};
3838

packages/server/src/migrations/1730000000000-AddMessageReplyForeignKey.ts

Lines changed: 83 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,70 @@ import {
77
} from 'typeorm';
88

99
/**
10-
* 迁移:添加 Reply 表并建立 Message 的外键关系
10+
* Migration: Add Reply table and establish foreign key relationship with Message
1111
*
12-
* 任务:
13-
* 1. 创建 reply_table
14-
* 2. 迁移历史数据:为所有 replyId 创建对应的 Reply 记录
15-
* 3. message_table.replyId 改为不可空的外键
12+
* Tasks:
13+
* 1. Create reply_table
14+
* 2. Migrate historical data: create Reply records for all replyIds
15+
* 3. Change message_table.replyId to a non-nullable foreign key
1616
*/
1717
export class AddMessageReplyForeignKey1730000000000
1818
implements MigrationInterface
1919
{
2020
name = 'AddMessageReplyForeignKey1730000000000';
2121

2222
public async up(queryRunner: QueryRunner): Promise<void> {
23-
console.log('开始迁移:添加 Reply 表并建立外键关系...');
23+
console.log(
24+
'Starting migration: Adding Reply table and establishing foreign key relationship...',
25+
);
2426

2527
// ========================================
26-
// 步骤 1: 创建 reply_table
28+
// Step 0: Check current database state
2729
// ========================================
28-
console.log('步骤 1: 创建 reply_table...');
30+
const messageTableExists = await queryRunner.hasTable('message_table');
31+
32+
console.log(`Database state check:`);
33+
console.log(` - message_table exists: ${messageTableExists}`);
34+
35+
// Scenario 1: First-time installation - message_table does not exist
36+
// TypeORM will create all tables from entity definitions, including foreign key relationships
37+
if (!messageTableExists) {
38+
console.log(
39+
'⏭️ message_table does not exist. Skipping migration (first-time installation).',
40+
);
41+
console.log(
42+
' TypeORM will create all tables from entity definitions.',
43+
);
44+
return;
45+
}
46+
47+
// Scenario 2: Check if migration is already completed
48+
// If foreign key constraint exists, migration has already been completed
49+
const messageTable = await queryRunner.getTable('message_table');
50+
const hasForeignKey = messageTable?.foreignKeys.some(
51+
(fk) =>
52+
fk.columnNames.includes('replyId') &&
53+
fk.referencedTableName === 'reply_table',
54+
);
55+
56+
if (hasForeignKey) {
57+
console.log(
58+
'✅ Foreign key constraint already exists. Migration already completed.',
59+
);
60+
return;
61+
}
62+
63+
// ========================================
64+
// Step 1: Create reply_table
65+
// ========================================
66+
console.log('Step 1: Creating reply_table...');
2967

3068
await queryRunner.createTable(
3169
new Table({
3270
name: 'reply_table',
3371
columns: [
3472
{
35-
name: 'replyId', // 保持驼峰,与 message_table 一致
73+
name: 'replyId', // Keep camelCase, consistent with message_table
3674
type: 'varchar',
3775
isPrimary: true,
3876
},
@@ -45,7 +83,7 @@ export class AddMessageReplyForeignKey1730000000000
4583
type: 'varchar',
4684
},
4785
{
48-
name: 'run_id', // 保持下划线,与其他表一致
86+
name: 'run_id', // Keep underscore, consistent with other tables
4987
type: 'varchar',
5088
},
5189
{
@@ -62,7 +100,7 @@ export class AddMessageReplyForeignKey1730000000000
62100
true, // ifNotExists
63101
);
64102

65-
// 添加 run_id 的外键
103+
// Add foreign key for run_id
66104
await queryRunner.createForeignKey(
67105
'reply_table',
68106
new TableForeignKey({
@@ -74,21 +112,21 @@ export class AddMessageReplyForeignKey1730000000000
74112
}),
75113
);
76114

77-
console.log('reply_table 创建成功');
115+
console.log('reply_table created successfully');
78116

79117
// ========================================
80-
// 步骤 2: 迁移历史数据
118+
// Step 2: Migrate historical data
81119
// ========================================
82-
console.log('步骤 2: 迁移历史数据到 reply_table...');
120+
console.log('Step 2: Migrating historical data to reply_table...');
83121

84-
// 查询所有消息(注意:列名是 replyId 驼峰)
122+
// Query all messages (Note: column name is replyId in camelCase)
85123
const allMessages = await queryRunner.query(
86124
`SELECT id, run_id, msg, replyId FROM message_table ORDER BY id`,
87125
);
88126

89-
console.log(`找到 ${allMessages.length} 条消息需要处理`);
127+
console.log(`Found ${allMessages.length} messages to process`);
90128

91-
// 收集所有唯一的 Reply 信息
129+
// Collect all unique Reply information
92130
const replyMap = new Map<
93131
string,
94132
{
@@ -140,10 +178,10 @@ export class AddMessageReplyForeignKey1730000000000
140178
}
141179

142180
console.log(
143-
`需要创建 ${replyMap.size} Reply 记录,更新 ${nullReplyCount} 条消息`,
181+
`Need to create ${replyMap.size} Reply records, update ${nullReplyCount} messages`,
144182
);
145183

146-
// 批量插入 Reply 记录
184+
// Batch insert Reply records
147185
let insertedCount = 0;
148186
for (const reply of replyMap.values()) {
149187
await queryRunner.query(
@@ -161,26 +199,28 @@ export class AddMessageReplyForeignKey1730000000000
161199

162200
if (insertedCount % 100 === 0) {
163201
console.log(
164-
`进度:已插入 ${insertedCount}/${replyMap.size} 个 Reply`,
202+
`Progress: Inserted ${insertedCount}/${replyMap.size} Replies`,
165203
);
166204
}
167205
}
168206

169-
console.log(`已创建 ${insertedCount} Reply 记录`);
207+
console.log(`Created ${insertedCount} Reply records`);
170208

171-
// 批量更新 replyId 为 NULL 的消息
209+
// Batch update messages with NULL replyId
172210
if (nullReplyCount > 0) {
173-
console.log(`开始更新 ${nullReplyCount} 条消息的 replyId...`);
211+
console.log(
212+
`Starting to update replyId for ${nullReplyCount} messages...`,
213+
);
174214
await queryRunner.query(
175215
`UPDATE message_table SET replyId = id WHERE replyId IS NULL OR replyId = ''`,
176216
);
177-
console.log(`已更新 ${nullReplyCount} 条消息`);
217+
console.log(`Updated ${nullReplyCount} messages`);
178218
}
179219

180220
// ========================================
181-
// 步骤 3: 验证数据完整性
221+
// Step 3: Validate data integrity
182222
// ========================================
183-
console.log('步骤 3: 验证数据...');
223+
console.log('Step 3: Validating data...');
184224

185225
const nullCount = await queryRunner.query(
186226
`SELECT COUNT(*) as count FROM message_table WHERE replyId IS NULL OR replyId = ''`,
@@ -189,48 +229,48 @@ export class AddMessageReplyForeignKey1730000000000
189229
const count = nullCount[0].count || nullCount[0].COUNT;
190230
if (count > 0) {
191231
throw new Error(
192-
`数据迁移失败:仍有 ${count} 条消息的 replyId 为空`,
232+
`Data migration failed: ${count} messages still have empty replyId`,
193233
);
194234
}
195235

196-
console.log('验证通过:所有消息都有 replyId');
236+
console.log('Validation passed: All messages have replyId');
197237

198238
// ========================================
199-
// 步骤 4: 添加外键约束
239+
// Step 4: Add foreign key constraint
200240
// ========================================
201-
console.log('步骤 4: 添加外键约束...');
241+
console.log('Step 4: Adding foreign key constraint...');
202242

203-
// 先将列改为不可空
243+
// First change column to non-nullable
204244
await queryRunner.changeColumn(
205245
'message_table',
206-
'replyId', // 注意:保持驼峰命名
246+
'replyId', // Note: Keep camelCase naming
207247
new TableColumn({
208248
name: 'replyId',
209249
type: 'varchar',
210250
isNullable: false,
211251
}),
212252
);
213253

214-
// 添加外键约束
254+
// Add foreign key constraint
215255
await queryRunner.createForeignKey(
216256
'message_table',
217257
new TableForeignKey({
218258
name: 'FK_message_reply',
219-
columnNames: ['replyId'], // message_table 中的列名(驼峰)
259+
columnNames: ['replyId'], // Column name in message_table (camelCase)
220260
referencedTableName: 'reply_table',
221-
referencedColumnNames: ['replyId'], // reply_table 中的列名(驼峰)
261+
referencedColumnNames: ['replyId'], // Column name in reply_table (camelCase)
222262
onDelete: 'CASCADE',
223263
}),
224264
);
225265

226-
console.log('外键约束添加成功');
227-
console.log('✅ 迁移完成!');
266+
console.log('Foreign key constraint added successfully');
267+
console.log('✅ Migration completed!');
228268
}
229269

230270
public async down(queryRunner: QueryRunner): Promise<void> {
231-
console.log('开始回滚迁移...');
271+
console.log('Starting migration rollback...');
232272

233-
// 删除外键约束
273+
// Drop foreign key constraint
234274
const messageTable = await queryRunner.getTable('message_table');
235275
const foreignKey = messageTable?.foreignKeys.find((fk) =>
236276
fk.columnNames.includes('replyId'),
@@ -240,7 +280,7 @@ export class AddMessageReplyForeignKey1730000000000
240280
await queryRunner.dropForeignKey('message_table', foreignKey);
241281
}
242282

243-
// 将列改回可空
283+
// Change column back to nullable
244284
await queryRunner.changeColumn(
245285
'message_table',
246286
'replyId',
@@ -251,12 +291,12 @@ export class AddMessageReplyForeignKey1730000000000
251291
}),
252292
);
253293

254-
// Message replyId 设为 NULL
294+
// Set Message replyId to NULL
255295
await queryRunner.query(`UPDATE message_table SET replyId = NULL`);
256296

257-
// 删除 reply_table
297+
// Drop reply_table
258298
await queryRunner.dropTable('reply_table', true);
259299

260-
console.log('✅ 回滚完成');
300+
console.log('✅ Rollback completed');
261301
}
262302
}

0 commit comments

Comments
 (0)