@@ -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 */
1717export 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