sonamu 0.3.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.pnp.cjs +11 -0
- package/dist/base-model-BzMJ2E_I.d.mts +43 -0
- package/dist/base-model-CWRKUX49.d.ts +43 -0
- package/dist/bin/cli.js +118 -89
- package/dist/bin/cli.js.map +1 -1
- package/dist/bin/cli.mjs +74 -45
- package/dist/bin/cli.mjs.map +1 -1
- package/dist/chunk-6HSW7OS3.js +1567 -0
- package/dist/chunk-6HSW7OS3.js.map +1 -0
- package/dist/chunk-FLPD24HS.mjs +231 -0
- package/dist/chunk-FLPD24HS.mjs.map +1 -0
- package/dist/{chunk-MPXE4IHO.mjs → chunk-PP2PSSAG.mjs} +5284 -5617
- package/dist/chunk-PP2PSSAG.mjs.map +1 -0
- package/dist/chunk-QK5XXJUX.mjs +280 -0
- package/dist/chunk-QK5XXJUX.mjs.map +1 -0
- package/dist/chunk-S6FYTR3V.mjs +1567 -0
- package/dist/chunk-S6FYTR3V.mjs.map +1 -0
- package/dist/chunk-U636LQJJ.js +231 -0
- package/dist/chunk-U636LQJJ.js.map +1 -0
- package/dist/chunk-W7KDVJLQ.js +280 -0
- package/dist/chunk-W7KDVJLQ.js.map +1 -0
- package/dist/{chunk-YXILRRDT.js → chunk-XT6LHCX5.js} +5252 -5585
- package/dist/chunk-XT6LHCX5.js.map +1 -0
- package/dist/database/drivers/knex/base-model.d.mts +16 -0
- package/dist/database/drivers/knex/base-model.d.ts +16 -0
- package/dist/database/drivers/knex/base-model.js +55 -0
- package/dist/database/drivers/knex/base-model.js.map +1 -0
- package/dist/database/drivers/knex/base-model.mjs +56 -0
- package/dist/database/drivers/knex/base-model.mjs.map +1 -0
- package/dist/database/drivers/kysely/base-model.d.mts +22 -0
- package/dist/database/drivers/kysely/base-model.d.ts +22 -0
- package/dist/database/drivers/kysely/base-model.js +64 -0
- package/dist/database/drivers/kysely/base-model.js.map +1 -0
- package/dist/database/drivers/kysely/base-model.mjs +65 -0
- package/dist/database/drivers/kysely/base-model.mjs.map +1 -0
- package/dist/index.d.mts +222 -928
- package/dist/index.d.ts +222 -928
- package/dist/index.js +13 -26
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +18 -31
- package/dist/index.mjs.map +1 -1
- package/dist/model-CAH_4oQh.d.mts +1042 -0
- package/dist/model-CAH_4oQh.d.ts +1042 -0
- package/import-to-require.js +27 -0
- package/package.json +24 -3
- package/src/api/caster.ts +6 -0
- package/src/api/code-converters.ts +3 -1
- package/src/api/sonamu.ts +41 -22
- package/src/bin/cli.ts +79 -46
- package/src/database/_batch_update.ts +16 -11
- package/src/database/base-model.abstract.ts +97 -0
- package/src/database/base-model.ts +214 -280
- package/src/database/code-generator.ts +72 -0
- package/src/database/db.abstract.ts +75 -0
- package/src/database/db.ts +21 -82
- package/src/database/drivers/knex/base-model.ts +55 -0
- package/src/database/drivers/knex/client.ts +209 -0
- package/src/database/drivers/knex/db.ts +227 -0
- package/src/database/drivers/knex/generator.ts +659 -0
- package/src/database/drivers/kysely/base-model.ts +89 -0
- package/src/database/drivers/kysely/client.ts +309 -0
- package/src/database/drivers/kysely/db.ts +238 -0
- package/src/database/drivers/kysely/generator.ts +714 -0
- package/src/database/types.ts +117 -0
- package/src/database/upsert-builder.ts +31 -18
- package/src/entity/entity-utils.ts +1 -1
- package/src/entity/migrator.ts +148 -711
- package/src/index.ts +1 -1
- package/src/syncer/syncer.ts +69 -27
- package/src/templates/generated_http.template.ts +14 -0
- package/src/templates/kysely_types.template.ts +205 -0
- package/src/templates/model.template.ts +2 -139
- package/src/templates/service.template.ts +3 -1
- package/src/testing/_relation-graph.ts +111 -0
- package/src/testing/fixture-manager.ts +216 -332
- package/src/types/types.ts +56 -6
- package/src/utils/utils.ts +56 -4
- package/src/utils/zod-error.ts +189 -0
- package/tsconfig.json +2 -2
- package/tsup.config.js +11 -10
- package/dist/chunk-MPXE4IHO.mjs.map +0 -1
- package/dist/chunk-YXILRRDT.js.map +0 -1
- /package/src/database/{knex-plugins → drivers/knex/plugins}/knex-on-duplicate-update.ts +0 -0
package/src/entity/migrator.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import _ from "lodash";
|
|
2
|
-
import knex, { Knex } from "knex";
|
|
3
|
-
import prettier from "prettier";
|
|
4
2
|
import chalk from "chalk";
|
|
5
3
|
import { DateTime } from "luxon";
|
|
6
4
|
import fs from "fs-extra";
|
|
@@ -38,6 +36,9 @@ import { EntityManager } from "./entity-manager";
|
|
|
38
36
|
import { Entity } from "./entity";
|
|
39
37
|
import { Sonamu } from "../api";
|
|
40
38
|
import { ServiceUnavailableException } from "../exceptions/so-exceptions";
|
|
39
|
+
import { DB } from "../database/db";
|
|
40
|
+
import { KnexClient } from "../database/drivers/knex/client";
|
|
41
|
+
import { KyselyClient } from "../database/drivers/kysely/client";
|
|
41
42
|
|
|
42
43
|
type MigratorMode = "dev" | "deploy";
|
|
43
44
|
export type MigratorOptions = {
|
|
@@ -65,33 +66,29 @@ export class Migrator {
|
|
|
65
66
|
readonly mode: MigratorMode;
|
|
66
67
|
|
|
67
68
|
targets: {
|
|
68
|
-
compare?:
|
|
69
|
-
pending:
|
|
70
|
-
shadow:
|
|
71
|
-
apply:
|
|
69
|
+
compare?: KnexClient | KyselyClient;
|
|
70
|
+
pending: KnexClient | KyselyClient;
|
|
71
|
+
shadow: KnexClient | KyselyClient;
|
|
72
|
+
apply: (KnexClient | KyselyClient)[];
|
|
72
73
|
};
|
|
73
74
|
|
|
74
75
|
constructor(options: MigratorOptions) {
|
|
75
76
|
this.mode = options.mode;
|
|
76
|
-
const { dbConfig } = Sonamu;
|
|
77
77
|
|
|
78
78
|
if (this.mode === "dev") {
|
|
79
|
-
const devDB =
|
|
80
|
-
const testDB =
|
|
81
|
-
const fixtureLocalDB =
|
|
82
|
-
|
|
79
|
+
const devDB = DB.getClient("development_master");
|
|
80
|
+
const testDB = DB.getClient("test");
|
|
81
|
+
const fixtureLocalDB = DB.getClient("fixture_local");
|
|
82
|
+
|
|
83
|
+
const uniqConfigs = DB.getUniqueConfigs([
|
|
84
|
+
"development_master",
|
|
85
|
+
"test",
|
|
86
|
+
"fixture_local",
|
|
87
|
+
"fixture_remote",
|
|
88
|
+
]);
|
|
83
89
|
const applyDBs = [devDB, testDB, fixtureLocalDB];
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
.host !==
|
|
87
|
-
(dbConfig.fixture_remote.connection as Knex.MySql2ConnectionConfig)
|
|
88
|
-
.host ||
|
|
89
|
-
(dbConfig.fixture_local.connection as Knex.MySql2ConnectionConfig)
|
|
90
|
-
.database !==
|
|
91
|
-
(dbConfig.fixture_remote.connection as Knex.MySql2ConnectionConfig)
|
|
92
|
-
.database
|
|
93
|
-
) {
|
|
94
|
-
const fixtureRemoteDB = knex(dbConfig.fixture_remote);
|
|
90
|
+
if (uniqConfigs.length === 4) {
|
|
91
|
+
const fixtureRemoteDB = DB.getClient("fixture_remote");
|
|
95
92
|
applyDBs.push(fixtureRemoteDB);
|
|
96
93
|
}
|
|
97
94
|
|
|
@@ -102,8 +99,8 @@ export class Migrator {
|
|
|
102
99
|
apply: applyDBs,
|
|
103
100
|
};
|
|
104
101
|
} else if (this.mode === "deploy") {
|
|
105
|
-
const productionDB =
|
|
106
|
-
const testDB =
|
|
102
|
+
const productionDB = DB.getClient("production_master");
|
|
103
|
+
const testDB = DB.getClient("test");
|
|
107
104
|
|
|
108
105
|
this.targets = {
|
|
109
106
|
pending: productionDB,
|
|
@@ -197,51 +194,48 @@ export class Migrator {
|
|
|
197
194
|
);
|
|
198
195
|
}
|
|
199
196
|
|
|
200
|
-
const connKeys = Object.keys(
|
|
197
|
+
const connKeys = Object.keys(DB.fullConfig).filter(
|
|
201
198
|
(key) => key.endsWith("_slave") === false
|
|
202
|
-
) as (keyof typeof
|
|
199
|
+
) as (keyof typeof DB.fullConfig)[];
|
|
203
200
|
|
|
204
201
|
const statuses = await Promise.all(
|
|
205
202
|
connKeys.map(async (connKey) => {
|
|
206
|
-
const
|
|
207
|
-
const tConn = knex(knexOptions);
|
|
203
|
+
const tConn = DB.getClient(connKey);
|
|
208
204
|
|
|
209
205
|
const status = await (async () => {
|
|
210
206
|
try {
|
|
211
|
-
return await tConn.
|
|
207
|
+
return await tConn.status();
|
|
212
208
|
} catch (err) {
|
|
209
|
+
console.error(err);
|
|
213
210
|
return "error";
|
|
214
211
|
}
|
|
215
212
|
})();
|
|
216
213
|
const pending = await (async () => {
|
|
217
214
|
try {
|
|
218
|
-
|
|
219
|
-
return fdList.map((fd: { file: string }) =>
|
|
220
|
-
fd.file.replace(".js", "")
|
|
221
|
-
);
|
|
215
|
+
return await tConn.getMigrations();
|
|
222
216
|
} catch (err) {
|
|
217
|
+
console.error(err);
|
|
223
218
|
return [];
|
|
224
219
|
}
|
|
225
220
|
})();
|
|
226
221
|
const currentVersion = await (async () => {
|
|
227
|
-
try {
|
|
228
|
-
|
|
229
|
-
} catch (err) {
|
|
230
|
-
|
|
231
|
-
}
|
|
222
|
+
// try {
|
|
223
|
+
// return tConn.migrate.currentVersion();
|
|
224
|
+
// } catch (err) {
|
|
225
|
+
return "error";
|
|
226
|
+
// }
|
|
232
227
|
})();
|
|
233
228
|
|
|
234
|
-
const
|
|
235
|
-
knexOptions.connection as Knex.MySql2ConnectionConfig;
|
|
229
|
+
const info = tConn.connectionInfo;
|
|
236
230
|
|
|
237
231
|
await tConn.destroy();
|
|
238
232
|
|
|
239
233
|
return {
|
|
240
234
|
name: connKey.replace("_master", ""),
|
|
241
235
|
connKey,
|
|
242
|
-
connString:
|
|
243
|
-
|
|
244
|
-
}:${
|
|
236
|
+
connString: `mysql2://${info.user ?? ""}@${
|
|
237
|
+
info.host
|
|
238
|
+
}:${info.port}/${info.database}` as ConnString,
|
|
245
239
|
currentVersion,
|
|
246
240
|
status,
|
|
247
241
|
pending,
|
|
@@ -255,7 +249,7 @@ export class Migrator {
|
|
|
255
249
|
return [];
|
|
256
250
|
}
|
|
257
251
|
|
|
258
|
-
const compareDBconn =
|
|
252
|
+
const compareDBconn = DB.getClient(status0conn.connKey);
|
|
259
253
|
const genCodes = await this.compareMigrations(compareDBconn);
|
|
260
254
|
|
|
261
255
|
await compareDBconn.destroy();
|
|
@@ -269,17 +263,17 @@ export class Migrator {
|
|
|
269
263
|
preparedCodes,
|
|
270
264
|
};
|
|
271
265
|
/*
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
266
|
+
TS/JS 코드 컴파일 상태 확인
|
|
267
|
+
1. 원본 파일 없는 JS파일이 존재하는 경우: 삭제
|
|
268
|
+
2. 컴파일 되지 않은 TS파일이 존재하는 경우: throw 쳐서 데브 서버 오픈 요청
|
|
269
|
+
|
|
270
|
+
DB 마이그레이션 상태 확인
|
|
271
|
+
1. 전체 DB설정에 대해서 현재 마이그레이션 상태 확인
|
|
272
|
+
- connKey: string
|
|
273
|
+
- status: number
|
|
274
|
+
- currentVersion: string
|
|
275
|
+
- list: { file: string; directory: string }[]
|
|
276
|
+
|
|
283
277
|
*/
|
|
284
278
|
}
|
|
285
279
|
|
|
@@ -294,34 +288,24 @@ export class Migrator {
|
|
|
294
288
|
}[]
|
|
295
289
|
> {
|
|
296
290
|
// get uniq knex configs
|
|
297
|
-
const configs =
|
|
298
|
-
targets
|
|
299
|
-
.map((target) => ({
|
|
300
|
-
connKey: target,
|
|
301
|
-
options: Sonamu.dbConfig[target as keyof typeof Sonamu.dbConfig],
|
|
302
|
-
}))
|
|
303
|
-
.filter((c) => c.options !== undefined),
|
|
304
|
-
({ options }) =>
|
|
305
|
-
`${(options.connection as Knex.MySql2ConnectionConfig).host}:${
|
|
306
|
-
(options.connection as Knex.MySql2ConnectionConfig).port ?? 3306
|
|
307
|
-
}/${(options.connection as Knex.MySql2ConnectionConfig).database}`
|
|
308
|
-
);
|
|
291
|
+
const configs = DB.getUniqueConfigs(targets as any);
|
|
309
292
|
|
|
310
293
|
// get connections
|
|
311
294
|
const conns = await Promise.all(
|
|
312
295
|
configs.map(async (config) => ({
|
|
313
296
|
connKey: config.connKey,
|
|
314
|
-
|
|
297
|
+
db: DB.getClient(config.connKey),
|
|
315
298
|
}))
|
|
316
299
|
);
|
|
317
300
|
|
|
318
301
|
// action
|
|
302
|
+
// TODO: 마이그레이션 결과 리턴값 정리해야됨(kysely/knex)
|
|
319
303
|
const result = await (async () => {
|
|
320
304
|
switch (action) {
|
|
321
305
|
case "latest":
|
|
322
306
|
return Promise.all(
|
|
323
|
-
conns.map(async ({ connKey,
|
|
324
|
-
const [batchNo, applied] = await
|
|
307
|
+
conns.map(async ({ connKey, db }) => {
|
|
308
|
+
const [batchNo, applied] = await db.migrate();
|
|
325
309
|
return {
|
|
326
310
|
connKey,
|
|
327
311
|
batchNo,
|
|
@@ -331,8 +315,8 @@ export class Migrator {
|
|
|
331
315
|
);
|
|
332
316
|
case "rollback":
|
|
333
317
|
return Promise.all(
|
|
334
|
-
conns.map(async ({ connKey,
|
|
335
|
-
const [batchNo, applied] = await
|
|
318
|
+
conns.map(async ({ connKey, db }) => {
|
|
319
|
+
const [batchNo, applied] = await db.rollback();
|
|
336
320
|
return {
|
|
337
321
|
connKey,
|
|
338
322
|
batchNo,
|
|
@@ -345,8 +329,8 @@ export class Migrator {
|
|
|
345
329
|
|
|
346
330
|
// destroy
|
|
347
331
|
await Promise.all(
|
|
348
|
-
conns.map(({
|
|
349
|
-
return
|
|
332
|
+
conns.map(({ db }) => {
|
|
333
|
+
return db.destroy();
|
|
350
334
|
})
|
|
351
335
|
);
|
|
352
336
|
|
|
@@ -411,16 +395,10 @@ export class Migrator {
|
|
|
411
395
|
}
|
|
412
396
|
|
|
413
397
|
async clearPendingList(): Promise<void> {
|
|
414
|
-
const
|
|
415
|
-
unknown,
|
|
416
|
-
{
|
|
417
|
-
file: string;
|
|
418
|
-
directory: string;
|
|
419
|
-
}[],
|
|
420
|
-
];
|
|
398
|
+
const pendingList = await this.targets.pending.getMigrations();
|
|
421
399
|
const migrationsDir = `${Sonamu.apiRootPath}/src/migrations`;
|
|
422
400
|
const delList = pendingList.map((df) => {
|
|
423
|
-
return path.join(migrationsDir, df.
|
|
401
|
+
return path.join(migrationsDir, `${df}.ts`);
|
|
424
402
|
});
|
|
425
403
|
for (let p of delList) {
|
|
426
404
|
if (fs.existsSync(p)) {
|
|
@@ -444,7 +422,7 @@ export class Migrator {
|
|
|
444
422
|
|
|
445
423
|
async run(): Promise<void> {
|
|
446
424
|
// pending 마이그레이션 확인
|
|
447
|
-
const
|
|
425
|
+
const pendingList = await this.targets.pending.getMigrations();
|
|
448
426
|
if (pendingList.length > 0) {
|
|
449
427
|
console.log(
|
|
450
428
|
chalk.red("pending 된 마이그레이션이 존재합니다."),
|
|
@@ -467,19 +445,16 @@ export class Migrator {
|
|
|
467
445
|
console.timeEnd(chalk.blue("Migrator - runShadowTest"));
|
|
468
446
|
await Promise.all(
|
|
469
447
|
this.targets.apply.map(async (applyDb) => {
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
applyDb.client.connectionSettings.host
|
|
473
|
-
} ${applyDb.client.database()}`
|
|
474
|
-
);
|
|
448
|
+
const info = applyDb.connectionInfo;
|
|
449
|
+
const label = chalk.green(`APPLIED ${info.host} ${info.database}`);
|
|
475
450
|
console.time(label);
|
|
476
|
-
|
|
451
|
+
await applyDb.migrate();
|
|
477
452
|
console.timeEnd(label);
|
|
478
453
|
})
|
|
479
454
|
);
|
|
480
455
|
}
|
|
481
456
|
|
|
482
|
-
//
|
|
457
|
+
// Entity-DB간 비교하여 코드 생성 리턴
|
|
483
458
|
const codes = await this.compareMigrations(this.targets.compare!);
|
|
484
459
|
if (codes.length === 0) {
|
|
485
460
|
console.log(chalk.green("\n현재 모두 싱크된 상태입니다."));
|
|
@@ -523,8 +498,8 @@ export class Migrator {
|
|
|
523
498
|
console.time(chalk.red("rollback:"));
|
|
524
499
|
const rollbackAllResult = await Promise.all(
|
|
525
500
|
this.targets.apply.map(async (db) => {
|
|
526
|
-
await db.migrate.forceFreeMigrationsLock();
|
|
527
|
-
return db.
|
|
501
|
+
// await db.migrate.forceFreeMigrationsLock();
|
|
502
|
+
return db.rollback();
|
|
528
503
|
})
|
|
529
504
|
);
|
|
530
505
|
console.dir({ rollbackAllResult }, { depth: null });
|
|
@@ -606,8 +581,8 @@ export class Migrator {
|
|
|
606
581
|
}[]
|
|
607
582
|
> {
|
|
608
583
|
// ShadowDB 생성 후 테스트 진행
|
|
609
|
-
const tdb =
|
|
610
|
-
const tdbConn =
|
|
584
|
+
const tdb = DB.getClient("test");
|
|
585
|
+
const tdbConn = tdb.connectionInfo;
|
|
611
586
|
const shadowDatabase = tdbConn.database + "__migration_shadow";
|
|
612
587
|
const tmpSqlPath = `/tmp/${shadowDatabase}.sql`;
|
|
613
588
|
|
|
@@ -616,7 +591,7 @@ export class Migrator {
|
|
|
616
591
|
chalk.magenta(`${tdbConn.database}의 데이터 ${tmpSqlPath}로 덤프`)
|
|
617
592
|
);
|
|
618
593
|
execSync(
|
|
619
|
-
`mysqldump -h${tdbConn.host} -u${tdbConn.user} -p'${tdbConn.password}' ${tdbConn.database} --single-transaction --no-create-db --triggers > ${tmpSqlPath};`
|
|
594
|
+
`mysqldump -h${tdbConn.host} -P${tdbConn.port} -u${tdbConn.user} -p'${tdbConn.password}' ${tdbConn.database} --single-transaction --no-create-db --triggers > ${tmpSqlPath};`
|
|
620
595
|
);
|
|
621
596
|
execSync(
|
|
622
597
|
`sed -i'' -e 's/\`${tdbConn.database}\`/\`${shadowDatabase}\`/g' ${tmpSqlPath};`
|
|
@@ -630,24 +605,13 @@ export class Migrator {
|
|
|
630
605
|
// ShadowDB 테이블 + 데이터 생성
|
|
631
606
|
console.log(chalk.magenta(`${shadowDatabase} 데이터베이스 생성`));
|
|
632
607
|
execSync(
|
|
633
|
-
`mysql -h${tdbConn.host} -u${tdbConn.user} -p'${tdbConn.password}' ${shadowDatabase} < ${tmpSqlPath};`
|
|
608
|
+
`mysql -h${tdbConn.host} -P${tdbConn.port} -u${tdbConn.user} -p'${tdbConn.password}' ${shadowDatabase} < ${tmpSqlPath};`
|
|
634
609
|
);
|
|
635
610
|
|
|
636
|
-
// tdb 연결 종료
|
|
637
|
-
await tdb.destroy();
|
|
638
|
-
|
|
639
611
|
// shadow db 테스트 진행
|
|
640
|
-
const sdb = knex({
|
|
641
|
-
...Sonamu.dbConfig.test,
|
|
642
|
-
connection: {
|
|
643
|
-
...tdbConn,
|
|
644
|
-
database: shadowDatabase,
|
|
645
|
-
password: tdbConn.password,
|
|
646
|
-
},
|
|
647
|
-
});
|
|
648
|
-
|
|
649
612
|
try {
|
|
650
|
-
|
|
613
|
+
await tdb.raw(`USE \`${shadowDatabase}\`;`);
|
|
614
|
+
const [batchNo, applied] = await tdb.migrate();
|
|
651
615
|
console.log(chalk.green("Shadow DB 테스트에 성공했습니다!"), {
|
|
652
616
|
batchNo,
|
|
653
617
|
applied,
|
|
@@ -655,7 +619,7 @@ export class Migrator {
|
|
|
655
619
|
|
|
656
620
|
// 생성한 Shadow DB 삭제
|
|
657
621
|
console.log(chalk.magenta(`${shadowDatabase} 삭제`));
|
|
658
|
-
await
|
|
622
|
+
await tdb.raw(`DROP DATABASE IF EXISTS \`${shadowDatabase}\`;`);
|
|
659
623
|
|
|
660
624
|
return [
|
|
661
625
|
{
|
|
@@ -668,7 +632,7 @@ export class Migrator {
|
|
|
668
632
|
console.error(e);
|
|
669
633
|
throw new ServiceUnavailableException("Shadow DB 테스트 진행 중 에러");
|
|
670
634
|
} finally {
|
|
671
|
-
await
|
|
635
|
+
await tdb.destroy();
|
|
672
636
|
}
|
|
673
637
|
}
|
|
674
638
|
|
|
@@ -686,8 +650,8 @@ export class Migrator {
|
|
|
686
650
|
console.time(chalk.red("rollback-all:"));
|
|
687
651
|
const rollbackAllResult = await Promise.all(
|
|
688
652
|
this.targets.apply.map(async (db) => {
|
|
689
|
-
await db.migrate.forceFreeMigrationsLock();
|
|
690
|
-
return db.
|
|
653
|
+
// await db.migrate.forceFreeMigrationsLock();
|
|
654
|
+
return db.rollbackAll();
|
|
691
655
|
})
|
|
692
656
|
);
|
|
693
657
|
console.log({ rollbackAllResult });
|
|
@@ -700,11 +664,13 @@ export class Migrator {
|
|
|
700
664
|
console.timeEnd(chalk.red("delete migration files"));
|
|
701
665
|
}
|
|
702
666
|
|
|
703
|
-
async compareMigrations(
|
|
704
|
-
|
|
667
|
+
async compareMigrations(
|
|
668
|
+
compareDB: KnexClient | KyselyClient
|
|
669
|
+
): Promise<GenMigrationCode[]> {
|
|
670
|
+
// Entity 순회하여 싱크
|
|
705
671
|
const entityIds = EntityManager.getAllIds();
|
|
706
672
|
|
|
707
|
-
// 조인테이블 포함하여
|
|
673
|
+
// 조인테이블 포함하여 Entity에서 MigrationSet 추출
|
|
708
674
|
const entitySetsWithJoinTable = entityIds
|
|
709
675
|
.filter((entityId) => {
|
|
710
676
|
const entity = EntityManager.get(entityId);
|
|
@@ -712,16 +678,28 @@ export class Migrator {
|
|
|
712
678
|
})
|
|
713
679
|
.map((entityId) => {
|
|
714
680
|
const entity = EntityManager.get(entityId);
|
|
715
|
-
return this.
|
|
681
|
+
return this.getMigrationSetFromEntity(entity);
|
|
716
682
|
});
|
|
717
683
|
|
|
718
684
|
// 조인테이블만 추출
|
|
719
|
-
const
|
|
720
|
-
|
|
721
|
-
(
|
|
722
|
-
|
|
685
|
+
const joinTablesWithDup = entitySetsWithJoinTable
|
|
686
|
+
.map((entitySet) => entitySet.joinTables)
|
|
687
|
+
.flat();
|
|
688
|
+
// 중복 제거 (중복인 경우 indexes를 병합)
|
|
689
|
+
const joinTables = Object.values(
|
|
690
|
+
_.groupBy(joinTablesWithDup, (jt) => jt.table)
|
|
691
|
+
).map((tables) => {
|
|
692
|
+
if (tables.length === 1) {
|
|
693
|
+
return tables[0];
|
|
723
694
|
}
|
|
724
|
-
|
|
695
|
+
return {
|
|
696
|
+
...tables[0],
|
|
697
|
+
indexes: _.uniqBy(
|
|
698
|
+
tables.flatMap((t) => t.indexes),
|
|
699
|
+
(index) => [index.type, ...index.columns.sort()].join("-")
|
|
700
|
+
),
|
|
701
|
+
};
|
|
702
|
+
});
|
|
725
703
|
|
|
726
704
|
// 조인테이블 포함하여 MigrationSet 배열
|
|
727
705
|
const entitySets: MigrationSet[] = [
|
|
@@ -729,7 +707,7 @@ export class Migrator {
|
|
|
729
707
|
...joinTables,
|
|
730
708
|
];
|
|
731
709
|
|
|
732
|
-
|
|
710
|
+
const codes: GenMigrationCode[] = (
|
|
733
711
|
await Promise.all(
|
|
734
712
|
entitySets.map(async (entitySet) => {
|
|
735
713
|
const dbSet = await this.getMigrationSetFromDB(
|
|
@@ -739,12 +717,12 @@ export class Migrator {
|
|
|
739
717
|
if (dbSet === null) {
|
|
740
718
|
// 기존 테이블 없음, 새로 테이블 생성
|
|
741
719
|
return [
|
|
742
|
-
await
|
|
720
|
+
await DB.generator.generateCreateCode_ColumnAndIndexes(
|
|
743
721
|
entitySet.table,
|
|
744
722
|
entitySet.columns,
|
|
745
723
|
entitySet.indexes
|
|
746
724
|
),
|
|
747
|
-
...(await
|
|
725
|
+
...(await DB.generator.generateCreateCode_Foreign(
|
|
748
726
|
entitySet.table,
|
|
749
727
|
entitySet.foreigns
|
|
750
728
|
)),
|
|
@@ -810,9 +788,9 @@ export class Migrator {
|
|
|
810
788
|
if (isEqualColumns && isEqualIndexes) {
|
|
811
789
|
return null;
|
|
812
790
|
} else {
|
|
813
|
-
// this.showMigrationSet("
|
|
791
|
+
// this.showMigrationSet("Entity", entitySet);
|
|
814
792
|
// this.showMigrationSet("DB", dbSet);
|
|
815
|
-
return
|
|
793
|
+
return DB.generator.generateAlterCode_ColumnAndIndexes(
|
|
816
794
|
entitySet.table,
|
|
817
795
|
entityColumns,
|
|
818
796
|
entityIndexes,
|
|
@@ -841,8 +819,16 @@ export class Migrator {
|
|
|
841
819
|
).map((f) => replaceNoActionOnMySQL(f));
|
|
842
820
|
|
|
843
821
|
if (equal(entityForeigns, dbForeigns) === false) {
|
|
844
|
-
// console.dir(
|
|
845
|
-
|
|
822
|
+
// console.dir(
|
|
823
|
+
// {
|
|
824
|
+
// debugOn: "foreign",
|
|
825
|
+
// table: entitySet.table,
|
|
826
|
+
// entityForeigns,
|
|
827
|
+
// dbForeigns,
|
|
828
|
+
// },
|
|
829
|
+
// { depth: null }
|
|
830
|
+
// );
|
|
831
|
+
return DB.generator.generateAlterCode_Foreigns(
|
|
846
832
|
entitySet.table,
|
|
847
833
|
entityForeigns,
|
|
848
834
|
dbForeigns
|
|
@@ -883,7 +869,7 @@ export class Migrator {
|
|
|
883
869
|
기존 테이블 정보 읽어서 MigrationSet 형식으로 리턴
|
|
884
870
|
*/
|
|
885
871
|
async getMigrationSetFromDB(
|
|
886
|
-
compareDB:
|
|
872
|
+
compareDB: KnexClient | KyselyClient,
|
|
887
873
|
table: string
|
|
888
874
|
): Promise<MigrationSet | null> {
|
|
889
875
|
let dbColumns: DBColumn[], dbIndexes: DBIndex[], dbForeigns: DBForeign[];
|
|
@@ -1009,7 +995,7 @@ export class Migrator {
|
|
|
1009
995
|
};
|
|
1010
996
|
case "datetime":
|
|
1011
997
|
return {
|
|
1012
|
-
type: "
|
|
998
|
+
type: "datetime",
|
|
1013
999
|
};
|
|
1014
1000
|
case "tinyint":
|
|
1015
1001
|
return {
|
|
@@ -1048,14 +1034,15 @@ export class Migrator {
|
|
|
1048
1034
|
기존 테이블 읽어서 cols, indexes 반환
|
|
1049
1035
|
*/
|
|
1050
1036
|
async readTable(
|
|
1051
|
-
compareDB:
|
|
1037
|
+
compareDB: KnexClient | KyselyClient,
|
|
1052
1038
|
tableName: string
|
|
1053
1039
|
): Promise<[DBColumn[], DBIndex[], DBForeign[]]> {
|
|
1054
1040
|
// 테이블 정보
|
|
1055
1041
|
try {
|
|
1056
|
-
const
|
|
1042
|
+
const _cols = await compareDB.raw<DBColumn>(
|
|
1057
1043
|
`SHOW FIELDS FROM ${tableName}`
|
|
1058
|
-
)
|
|
1044
|
+
);
|
|
1045
|
+
|
|
1059
1046
|
const cols = _cols.map((col) => ({
|
|
1060
1047
|
...col,
|
|
1061
1048
|
// Default 값은 숫자나 MySQL Expression이 아닌 경우 ""로 감싸줌
|
|
@@ -1068,8 +1055,12 @@ export class Migrator {
|
|
|
1068
1055
|
}),
|
|
1069
1056
|
}));
|
|
1070
1057
|
|
|
1071
|
-
const
|
|
1072
|
-
|
|
1058
|
+
const indexes = await compareDB.raw<DBIndex>(
|
|
1059
|
+
`SHOW INDEX FROM ${tableName}`
|
|
1060
|
+
);
|
|
1061
|
+
const [row] = await compareDB.raw<{
|
|
1062
|
+
"Create Table": string;
|
|
1063
|
+
}>(`SHOW CREATE TABLE ${tableName}`);
|
|
1073
1064
|
const ddl = row["Create Table"];
|
|
1074
1065
|
const matched = ddl.match(/CONSTRAINT .+/g);
|
|
1075
1066
|
const foreignKeys = (matched ?? []).map((line: string) => {
|
|
@@ -1091,7 +1082,8 @@ export class Migrator {
|
|
|
1091
1082
|
const onDelete =
|
|
1092
1083
|
(onClause ?? "")
|
|
1093
1084
|
.replace(onUpdateFull ?? "", "")
|
|
1094
|
-
.match(/ON DELETE ([A-Z ]+)
|
|
1085
|
+
.match(/ON DELETE ([A-Z ]+)/)?.[1]
|
|
1086
|
+
?.trim() ?? "NO ACTION";
|
|
1095
1087
|
|
|
1096
1088
|
return {
|
|
1097
1089
|
keyName,
|
|
@@ -1109,9 +1101,9 @@ export class Migrator {
|
|
|
1109
1101
|
}
|
|
1110
1102
|
|
|
1111
1103
|
/*
|
|
1112
|
-
|
|
1104
|
+
Entity 내용 읽어서 MigrationSetAndJoinTable 추출
|
|
1113
1105
|
*/
|
|
1114
|
-
|
|
1106
|
+
getMigrationSetFromEntity(entity: Entity): MigrationSetAndJoinTable {
|
|
1115
1107
|
const migrationSet: MigrationSetAndJoinTable = entity.props.reduce(
|
|
1116
1108
|
(r, prop) => {
|
|
1117
1109
|
// virtual 필드 제외
|
|
@@ -1151,6 +1143,7 @@ export class Migrator {
|
|
|
1151
1143
|
}
|
|
1152
1144
|
return {};
|
|
1153
1145
|
})(),
|
|
1146
|
+
// FIXME: float(N, M) deprecated
|
|
1154
1147
|
// Decimal, Float 타입의 경우 precision, scale 추가
|
|
1155
1148
|
...((isDecimalProp(prop) || isFloatProp(prop)) && {
|
|
1156
1149
|
precision: prop.precision ?? 8,
|
|
@@ -1220,9 +1213,21 @@ export class Migrator {
|
|
|
1220
1213
|
},
|
|
1221
1214
|
],
|
|
1222
1215
|
foreigns: fields.map((field) => {
|
|
1216
|
+
// 현재 필드가 어떤 테이블에 속하는지 판단
|
|
1217
|
+
const col = field.split(".")[1];
|
|
1218
|
+
const to = (() => {
|
|
1219
|
+
if (
|
|
1220
|
+
inflection.singularize(join.to.split(".")[0]) + "_id" ===
|
|
1221
|
+
col
|
|
1222
|
+
) {
|
|
1223
|
+
return join.to;
|
|
1224
|
+
} else {
|
|
1225
|
+
return join.from;
|
|
1226
|
+
}
|
|
1227
|
+
})();
|
|
1223
1228
|
return {
|
|
1224
|
-
columns: [
|
|
1225
|
-
to
|
|
1229
|
+
columns: [col],
|
|
1230
|
+
to,
|
|
1226
1231
|
onUpdate: through.onUpdate,
|
|
1227
1232
|
onDelete: through.onDelete,
|
|
1228
1233
|
};
|
|
@@ -1279,197 +1284,13 @@ export class Migrator {
|
|
|
1279
1284
|
return migrationSet;
|
|
1280
1285
|
}
|
|
1281
1286
|
|
|
1282
|
-
/*
|
|
1283
|
-
MigrationColumn[] 읽어서 컬럼 정의하는 구문 생성
|
|
1284
|
-
*/
|
|
1285
|
-
genColumnDefinitions(columns: MigrationColumn[]): string[] {
|
|
1286
|
-
return columns.map((column) => {
|
|
1287
|
-
const chains: string[] = [];
|
|
1288
|
-
if (column.name === "id") {
|
|
1289
|
-
return `table.increments().primary();`;
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
|
-
if (column.type === "float" || column.type === "decimal") {
|
|
1293
|
-
chains.push(
|
|
1294
|
-
`${column.type}('${column.name}', ${column.precision}, ${column.scale})`
|
|
1295
|
-
);
|
|
1296
|
-
} else {
|
|
1297
|
-
// type, length
|
|
1298
|
-
let columnType = column.type;
|
|
1299
|
-
let extraType: string | undefined;
|
|
1300
|
-
if (columnType.includes("text") && columnType !== "text") {
|
|
1301
|
-
extraType = columnType;
|
|
1302
|
-
columnType = "text";
|
|
1303
|
-
}
|
|
1304
|
-
chains.push(
|
|
1305
|
-
`${column.type}('${column.name}'${
|
|
1306
|
-
column.length ? `, ${column.length}` : ""
|
|
1307
|
-
}${extraType ? `, '${extraType}'` : ""})`
|
|
1308
|
-
);
|
|
1309
|
-
}
|
|
1310
|
-
if (column.unsigned) {
|
|
1311
|
-
chains.push("unsigned()");
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
// nullable
|
|
1315
|
-
chains.push(column.nullable ? "nullable()" : "notNullable()");
|
|
1316
|
-
|
|
1317
|
-
// defaultTo
|
|
1318
|
-
if (column.defaultTo !== undefined) {
|
|
1319
|
-
if (
|
|
1320
|
-
typeof column.defaultTo === "string" &&
|
|
1321
|
-
column.defaultTo.startsWith(`"`)
|
|
1322
|
-
) {
|
|
1323
|
-
chains.push(`defaultTo(${column.defaultTo})`);
|
|
1324
|
-
} else {
|
|
1325
|
-
chains.push(`defaultTo(knex.raw('${column.defaultTo}'))`);
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
return `table.${chains.join(".")};`;
|
|
1330
|
-
});
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
/*
|
|
1334
|
-
MigrationIndex[] 읽어서 인덱스/유니크 정의하는 구문 생성
|
|
1335
|
-
*/
|
|
1336
|
-
genIndexDefinitions(indexes: MigrationIndex[]): string[] {
|
|
1337
|
-
if (indexes.length === 0) {
|
|
1338
|
-
return [];
|
|
1339
|
-
}
|
|
1340
|
-
const lines = _.uniq(
|
|
1341
|
-
indexes.reduce((r, index) => {
|
|
1342
|
-
r.push(
|
|
1343
|
-
`table.${index.type}([${index.columns
|
|
1344
|
-
.map((col) => `'${col}'`)
|
|
1345
|
-
.join(",")}])`
|
|
1346
|
-
);
|
|
1347
|
-
return r;
|
|
1348
|
-
}, [] as string[])
|
|
1349
|
-
);
|
|
1350
|
-
return lines;
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
/*
|
|
1354
|
-
MigrationForeign[] 읽어서 외부키 constraint 정의하는 구문 생성
|
|
1355
|
-
*/
|
|
1356
|
-
genForeignDefinitions(
|
|
1357
|
-
table: string,
|
|
1358
|
-
foreigns: MigrationForeign[]
|
|
1359
|
-
): { up: string[]; down: string[] } {
|
|
1360
|
-
return foreigns.reduce(
|
|
1361
|
-
(r, foreign) => {
|
|
1362
|
-
const columnsStringQuote = foreign.columns
|
|
1363
|
-
.map((col) => `'${col.replace(`${table}.`, "")}'`)
|
|
1364
|
-
.join(",");
|
|
1365
|
-
r.up.push(
|
|
1366
|
-
`table.foreign('${foreign.columns.join(",")}')
|
|
1367
|
-
.references('${foreign.to}')
|
|
1368
|
-
.onUpdate('${foreign.onUpdate}')
|
|
1369
|
-
.onDelete('${foreign.onDelete}')`
|
|
1370
|
-
);
|
|
1371
|
-
r.down.push(`table.dropForeign([${columnsStringQuote}])`);
|
|
1372
|
-
return r;
|
|
1373
|
-
},
|
|
1374
|
-
{
|
|
1375
|
-
up: [] as string[],
|
|
1376
|
-
down: [] as string[],
|
|
1377
|
-
}
|
|
1378
|
-
);
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
/*
|
|
1382
|
-
테이블 생성하는 케이스 - 컬럼/인덱스 생성
|
|
1383
|
-
*/
|
|
1384
|
-
async generateCreateCode_ColumnAndIndexes(
|
|
1385
|
-
table: string,
|
|
1386
|
-
columns: MigrationColumn[],
|
|
1387
|
-
indexes: MigrationIndex[]
|
|
1388
|
-
): Promise<GenMigrationCode> {
|
|
1389
|
-
// 컬럼, 인덱스 처리
|
|
1390
|
-
const lines: string[] = [
|
|
1391
|
-
'import { Knex } from "knex";',
|
|
1392
|
-
"",
|
|
1393
|
-
"export async function up(knex: Knex): Promise<void> {",
|
|
1394
|
-
`return knex.schema.createTable("${table}", (table) => {`,
|
|
1395
|
-
"// columns",
|
|
1396
|
-
...this.genColumnDefinitions(columns),
|
|
1397
|
-
"",
|
|
1398
|
-
"// indexes",
|
|
1399
|
-
...this.genIndexDefinitions(indexes),
|
|
1400
|
-
"});",
|
|
1401
|
-
"}",
|
|
1402
|
-
"",
|
|
1403
|
-
"export async function down(knex: Knex): Promise<void> {",
|
|
1404
|
-
` return knex.schema.dropTable("${table}");`,
|
|
1405
|
-
"}",
|
|
1406
|
-
];
|
|
1407
|
-
return {
|
|
1408
|
-
table,
|
|
1409
|
-
type: "normal",
|
|
1410
|
-
title: `create__${table}`,
|
|
1411
|
-
formatted: await prettier.format(lines.join("\n"), {
|
|
1412
|
-
parser: "typescript",
|
|
1413
|
-
}),
|
|
1414
|
-
};
|
|
1415
|
-
}
|
|
1416
|
-
/*
|
|
1417
|
-
테이블 생성하는 케이스 - FK 생성
|
|
1418
|
-
*/
|
|
1419
|
-
async generateCreateCode_Foreign(
|
|
1420
|
-
table: string,
|
|
1421
|
-
foreigns: MigrationForeign[]
|
|
1422
|
-
): Promise<GenMigrationCode[]> {
|
|
1423
|
-
if (foreigns.length === 0) {
|
|
1424
|
-
return [];
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
const { up, down } = this.genForeignDefinitions(table, foreigns);
|
|
1428
|
-
if (up.length === 0 && down.length === 0) {
|
|
1429
|
-
console.log("fk 가 뭔가 다릅니다");
|
|
1430
|
-
return [];
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
const lines: string[] = [
|
|
1434
|
-
'import { Knex } from "knex";',
|
|
1435
|
-
"",
|
|
1436
|
-
"export async function up(knex: Knex): Promise<void> {",
|
|
1437
|
-
`return knex.schema.alterTable("${table}", (table) => {`,
|
|
1438
|
-
"// create fk",
|
|
1439
|
-
...up,
|
|
1440
|
-
"});",
|
|
1441
|
-
"}",
|
|
1442
|
-
"",
|
|
1443
|
-
"export async function down(knex: Knex): Promise<void> {",
|
|
1444
|
-
`return knex.schema.alterTable("${table}", (table) => {`,
|
|
1445
|
-
"// drop fk",
|
|
1446
|
-
...down,
|
|
1447
|
-
"});",
|
|
1448
|
-
"}",
|
|
1449
|
-
];
|
|
1450
|
-
|
|
1451
|
-
const foreignKeysString = foreigns
|
|
1452
|
-
.map((foreign) => foreign.columns.join("_"))
|
|
1453
|
-
.join("_");
|
|
1454
|
-
return [
|
|
1455
|
-
{
|
|
1456
|
-
table,
|
|
1457
|
-
type: "foreign",
|
|
1458
|
-
title: `foreign__${table}__${foreignKeysString}`,
|
|
1459
|
-
formatted: await prettier.format(lines.join("\n"), {
|
|
1460
|
-
parser: "typescript",
|
|
1461
|
-
}),
|
|
1462
|
-
},
|
|
1463
|
-
];
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
1287
|
/*
|
|
1467
1288
|
마이그레이션 컬럼 배열 비교용 코드
|
|
1468
1289
|
*/
|
|
1469
|
-
showMigrationSet(which:
|
|
1290
|
+
showMigrationSet(which: "Entity" | "DB", migrationSet: MigrationSet): void {
|
|
1470
1291
|
const { columns, indexes, foreigns } = migrationSet;
|
|
1471
1292
|
const styledChalk =
|
|
1472
|
-
which === "
|
|
1293
|
+
which === "Entity" ? chalk.bgGreen.black : chalk.bgBlue.black;
|
|
1473
1294
|
console.log(
|
|
1474
1295
|
styledChalk(
|
|
1475
1296
|
`${which} ${migrationSet.table} Columns\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t`
|
|
@@ -1533,390 +1354,6 @@ export class Migrator {
|
|
|
1533
1354
|
}
|
|
1534
1355
|
}
|
|
1535
1356
|
|
|
1536
|
-
async generateAlterCode_ColumnAndIndexes(
|
|
1537
|
-
table: string,
|
|
1538
|
-
entityColumns: MigrationColumn[],
|
|
1539
|
-
entityIndexes: MigrationIndex[],
|
|
1540
|
-
dbColumns: MigrationColumn[],
|
|
1541
|
-
dbIndexes: MigrationIndex[]
|
|
1542
|
-
): Promise<GenMigrationCode[]> {
|
|
1543
|
-
/*
|
|
1544
|
-
세부 비교 후 다른점 찾아서 코드 생성
|
|
1545
|
-
|
|
1546
|
-
1. 컬럼갯수 다름: MD에 있으나, DB에 없다면 추가
|
|
1547
|
-
2. 컬럼갯수 다름: MD에 없으나, DB에 있다면 삭제
|
|
1548
|
-
3. 그외 컬럼(컬럼 갯수가 동일하거나, 다른 경우 동일한 컬럼끼리) => alter
|
|
1549
|
-
4. 다른거 다 동일하고 index만 변경되는 경우
|
|
1550
|
-
|
|
1551
|
-
** 컬럼명을 변경하는 경우는 따로 핸들링하지 않음
|
|
1552
|
-
=> drop/add 형태의 마이그레이션 코드가 생성되는데, 수동으로 rename 코드로 수정하여 처리
|
|
1553
|
-
*/
|
|
1554
|
-
|
|
1555
|
-
// 각 컬럼 이름 기준으로 add, drop, alter 여부 확인
|
|
1556
|
-
const alterColumnsTo = this.getAlterColumnsTo(entityColumns, dbColumns);
|
|
1557
|
-
|
|
1558
|
-
// 추출된 컬럼들을 기준으로 각각 라인 생성
|
|
1559
|
-
const alterColumnLinesTo = this.getAlterColumnLinesTo(
|
|
1560
|
-
alterColumnsTo,
|
|
1561
|
-
entityColumns
|
|
1562
|
-
);
|
|
1563
|
-
|
|
1564
|
-
// 인덱스의 add, drop 여부 확인
|
|
1565
|
-
const alterIndexesTo = this.getAlterIndexesTo(entityIndexes, dbIndexes);
|
|
1566
|
-
|
|
1567
|
-
// 추출된 인덱스들을 기준으로 각각 라인 생성
|
|
1568
|
-
const alterIndexLinesTo = this.getAlterIndexLinesTo(
|
|
1569
|
-
alterIndexesTo,
|
|
1570
|
-
alterColumnsTo
|
|
1571
|
-
);
|
|
1572
|
-
|
|
1573
|
-
const lines: string[] = [
|
|
1574
|
-
'import { Knex } from "knex";',
|
|
1575
|
-
"",
|
|
1576
|
-
"export async function up(knex: Knex): Promise<void> {",
|
|
1577
|
-
`return knex.schema.alterTable("${table}", (table) => {`,
|
|
1578
|
-
...(alterColumnsTo.add.length > 0 ? alterColumnLinesTo.add.up : []),
|
|
1579
|
-
...(alterColumnsTo.drop.length > 0 ? alterColumnLinesTo.drop.up : []),
|
|
1580
|
-
...(alterColumnsTo.alter.length > 0 ? alterColumnLinesTo.alter.up : []),
|
|
1581
|
-
...(alterIndexesTo.add.length > 0 ? alterIndexLinesTo.add.up : []),
|
|
1582
|
-
...(alterIndexesTo.drop.length > 0 ? alterIndexLinesTo.drop.up : []),
|
|
1583
|
-
"})",
|
|
1584
|
-
"}",
|
|
1585
|
-
"",
|
|
1586
|
-
"export async function down(knex: Knex): Promise<void> {",
|
|
1587
|
-
`return knex.schema.alterTable("${table}", (table) => {`,
|
|
1588
|
-
...(alterColumnsTo.add.length > 0 ? alterColumnLinesTo.add.down : []),
|
|
1589
|
-
...(alterColumnsTo.drop.length > 0 ? alterColumnLinesTo.drop.down : []),
|
|
1590
|
-
...(alterColumnsTo.alter.length > 0 ? alterColumnLinesTo.alter.down : []),
|
|
1591
|
-
...(alterIndexLinesTo.add.down.length > 0
|
|
1592
|
-
? alterIndexLinesTo.add.down
|
|
1593
|
-
: []),
|
|
1594
|
-
...(alterIndexLinesTo.drop.down.length > 0
|
|
1595
|
-
? alterIndexLinesTo.drop.down
|
|
1596
|
-
: []),
|
|
1597
|
-
"})",
|
|
1598
|
-
"}",
|
|
1599
|
-
];
|
|
1600
|
-
|
|
1601
|
-
const formatted = await prettier.format(lines.join("\n"), {
|
|
1602
|
-
parser: "typescript",
|
|
1603
|
-
});
|
|
1604
|
-
|
|
1605
|
-
const title = [
|
|
1606
|
-
"alter",
|
|
1607
|
-
table,
|
|
1608
|
-
...(["add", "drop", "alter"] as const)
|
|
1609
|
-
.map((action) => {
|
|
1610
|
-
const len = alterColumnsTo[action].length;
|
|
1611
|
-
if (len > 0) {
|
|
1612
|
-
return action + len;
|
|
1613
|
-
}
|
|
1614
|
-
return null;
|
|
1615
|
-
})
|
|
1616
|
-
.filter((part) => part !== null),
|
|
1617
|
-
].join("_");
|
|
1618
|
-
|
|
1619
|
-
return [
|
|
1620
|
-
{
|
|
1621
|
-
table,
|
|
1622
|
-
title,
|
|
1623
|
-
formatted,
|
|
1624
|
-
type: "normal",
|
|
1625
|
-
},
|
|
1626
|
-
];
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
|
-
getAlterColumnsTo(
|
|
1630
|
-
entityColumns: MigrationColumn[],
|
|
1631
|
-
dbColumns: MigrationColumn[]
|
|
1632
|
-
) {
|
|
1633
|
-
const columnsTo = {
|
|
1634
|
-
add: [] as MigrationColumn[],
|
|
1635
|
-
drop: [] as MigrationColumn[],
|
|
1636
|
-
alter: [] as MigrationColumn[],
|
|
1637
|
-
};
|
|
1638
|
-
|
|
1639
|
-
// 컬럼명 기준 비교
|
|
1640
|
-
const extraColumns = {
|
|
1641
|
-
db: _.differenceBy(dbColumns, entityColumns, (col) => col.name),
|
|
1642
|
-
entity: _.differenceBy(entityColumns, dbColumns, (col) => col.name),
|
|
1643
|
-
};
|
|
1644
|
-
if (extraColumns.entity.length > 0) {
|
|
1645
|
-
columnsTo.add = columnsTo.add.concat(extraColumns.entity);
|
|
1646
|
-
}
|
|
1647
|
-
if (extraColumns.db.length > 0) {
|
|
1648
|
-
columnsTo.drop = columnsTo.drop.concat(extraColumns.db);
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1651
|
-
// 동일 컬럼명의 세부 필드 비교
|
|
1652
|
-
const sameDbColumns = _.intersectionBy(
|
|
1653
|
-
dbColumns,
|
|
1654
|
-
entityColumns,
|
|
1655
|
-
(col) => col.name
|
|
1656
|
-
);
|
|
1657
|
-
const sameMdColumns = _.intersectionBy(
|
|
1658
|
-
entityColumns,
|
|
1659
|
-
dbColumns,
|
|
1660
|
-
(col) => col.name
|
|
1661
|
-
);
|
|
1662
|
-
columnsTo.alter = _.differenceWith(sameDbColumns, sameMdColumns, (a, b) =>
|
|
1663
|
-
equal(a, b)
|
|
1664
|
-
);
|
|
1665
|
-
|
|
1666
|
-
return columnsTo;
|
|
1667
|
-
}
|
|
1668
|
-
|
|
1669
|
-
getAlterColumnLinesTo(
|
|
1670
|
-
columnsTo: ReturnType<Migrator["getAlterColumnsTo"]>,
|
|
1671
|
-
entityColumns: MigrationColumn[]
|
|
1672
|
-
) {
|
|
1673
|
-
let linesTo = {
|
|
1674
|
-
add: {
|
|
1675
|
-
up: [] as string[],
|
|
1676
|
-
down: [] as string[],
|
|
1677
|
-
},
|
|
1678
|
-
drop: {
|
|
1679
|
-
up: [] as string[],
|
|
1680
|
-
down: [] as string[],
|
|
1681
|
-
},
|
|
1682
|
-
alter: {
|
|
1683
|
-
up: [] as string[],
|
|
1684
|
-
down: [] as string[],
|
|
1685
|
-
},
|
|
1686
|
-
};
|
|
1687
|
-
|
|
1688
|
-
linesTo.add = {
|
|
1689
|
-
up: ["// add", ...this.genColumnDefinitions(columnsTo.add)],
|
|
1690
|
-
down: [
|
|
1691
|
-
"// rollback - add",
|
|
1692
|
-
`table.dropColumns(${columnsTo.add
|
|
1693
|
-
.map((col) => `'${col.name}'`)
|
|
1694
|
-
.join(", ")})`,
|
|
1695
|
-
],
|
|
1696
|
-
};
|
|
1697
|
-
linesTo.drop = {
|
|
1698
|
-
up: [
|
|
1699
|
-
"// drop",
|
|
1700
|
-
`table.dropColumns(${columnsTo.drop
|
|
1701
|
-
.map((col) => `'${col.name}'`)
|
|
1702
|
-
.join(", ")})`,
|
|
1703
|
-
],
|
|
1704
|
-
down: [
|
|
1705
|
-
"// rollback - drop",
|
|
1706
|
-
...this.genColumnDefinitions(columnsTo.drop),
|
|
1707
|
-
],
|
|
1708
|
-
};
|
|
1709
|
-
linesTo.alter = columnsTo.alter.reduce(
|
|
1710
|
-
(r, dbColumn) => {
|
|
1711
|
-
const entityColumn = entityColumns.find(
|
|
1712
|
-
(col) => col.name == dbColumn.name
|
|
1713
|
-
);
|
|
1714
|
-
if (entityColumn === undefined) {
|
|
1715
|
-
return r;
|
|
1716
|
-
}
|
|
1717
|
-
|
|
1718
|
-
// 컬럼 변경사항
|
|
1719
|
-
const columnDiffUp = _.difference(
|
|
1720
|
-
this.genColumnDefinitions([entityColumn]),
|
|
1721
|
-
this.genColumnDefinitions([dbColumn])
|
|
1722
|
-
);
|
|
1723
|
-
const columnDiffDown = _.difference(
|
|
1724
|
-
this.genColumnDefinitions([dbColumn]),
|
|
1725
|
-
this.genColumnDefinitions([entityColumn])
|
|
1726
|
-
);
|
|
1727
|
-
if (columnDiffUp.length > 0) {
|
|
1728
|
-
r.up = [
|
|
1729
|
-
...r.up,
|
|
1730
|
-
"// alter column",
|
|
1731
|
-
...columnDiffUp.map((l) => l.replace(";", "") + ".alter();"),
|
|
1732
|
-
];
|
|
1733
|
-
r.down = [
|
|
1734
|
-
...r.down,
|
|
1735
|
-
"// rollback - alter column",
|
|
1736
|
-
...columnDiffDown.map((l) => l.replace(";", "") + ".alter();"),
|
|
1737
|
-
];
|
|
1738
|
-
}
|
|
1739
|
-
|
|
1740
|
-
return r;
|
|
1741
|
-
},
|
|
1742
|
-
{
|
|
1743
|
-
up: [] as string[],
|
|
1744
|
-
down: [] as string[],
|
|
1745
|
-
}
|
|
1746
|
-
);
|
|
1747
|
-
|
|
1748
|
-
return linesTo;
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1751
|
-
getAlterIndexesTo(
|
|
1752
|
-
entityIndexes: MigrationIndex[],
|
|
1753
|
-
dbIndexes: MigrationIndex[]
|
|
1754
|
-
) {
|
|
1755
|
-
// 인덱스 비교
|
|
1756
|
-
let indexesTo = {
|
|
1757
|
-
add: [] as MigrationIndex[],
|
|
1758
|
-
drop: [] as MigrationIndex[],
|
|
1759
|
-
};
|
|
1760
|
-
const extraIndexes = {
|
|
1761
|
-
db: _.differenceBy(dbIndexes, entityIndexes, (col) =>
|
|
1762
|
-
[col.type, col.columns.join("-")].join("//")
|
|
1763
|
-
),
|
|
1764
|
-
entity: _.differenceBy(entityIndexes, dbIndexes, (col) =>
|
|
1765
|
-
[col.type, col.columns.join("-")].join("//")
|
|
1766
|
-
),
|
|
1767
|
-
};
|
|
1768
|
-
if (extraIndexes.entity.length > 0) {
|
|
1769
|
-
indexesTo.add = indexesTo.add.concat(extraIndexes.entity);
|
|
1770
|
-
}
|
|
1771
|
-
if (extraIndexes.db.length > 0) {
|
|
1772
|
-
indexesTo.drop = indexesTo.drop.concat(extraIndexes.db);
|
|
1773
|
-
}
|
|
1774
|
-
|
|
1775
|
-
return indexesTo;
|
|
1776
|
-
}
|
|
1777
|
-
|
|
1778
|
-
getAlterIndexLinesTo(
|
|
1779
|
-
indexesTo: ReturnType<Migrator["getAlterIndexesTo"]>,
|
|
1780
|
-
columnsTo: ReturnType<Migrator["getAlterColumnsTo"]>
|
|
1781
|
-
) {
|
|
1782
|
-
let linesTo = {
|
|
1783
|
-
add: {
|
|
1784
|
-
up: [] as string[],
|
|
1785
|
-
down: [] as string[],
|
|
1786
|
-
},
|
|
1787
|
-
drop: {
|
|
1788
|
-
up: [] as string[],
|
|
1789
|
-
down: [] as string[],
|
|
1790
|
-
},
|
|
1791
|
-
};
|
|
1792
|
-
|
|
1793
|
-
// 인덱스가 추가되는 경우, 컬럼과 같이 추가된 케이스에는 drop에서 제외해야함!
|
|
1794
|
-
linesTo.add = {
|
|
1795
|
-
up: ["// add indexes", ...this.genIndexDefinitions(indexesTo.add)],
|
|
1796
|
-
down: [
|
|
1797
|
-
"// rollback - add indexes",
|
|
1798
|
-
...indexesTo.add
|
|
1799
|
-
.filter(
|
|
1800
|
-
(index) =>
|
|
1801
|
-
index.columns.every((colName) =>
|
|
1802
|
-
columnsTo.add.map((col) => col.name).includes(colName)
|
|
1803
|
-
) === false
|
|
1804
|
-
)
|
|
1805
|
-
.map(
|
|
1806
|
-
(index) =>
|
|
1807
|
-
`table.drop${inflection.capitalize(index.type)}([${index.columns
|
|
1808
|
-
.map((columnName) => `'${columnName}'`)
|
|
1809
|
-
.join(",")}])`
|
|
1810
|
-
),
|
|
1811
|
-
],
|
|
1812
|
-
};
|
|
1813
|
-
// 인덱스가 삭제되는 경우, 컬럼과 같이 삭제된 케이스에는 drop에서 제외해야함!
|
|
1814
|
-
linesTo.drop = {
|
|
1815
|
-
up: [
|
|
1816
|
-
...indexesTo.drop
|
|
1817
|
-
.filter(
|
|
1818
|
-
(index) =>
|
|
1819
|
-
index.columns.every((colName) =>
|
|
1820
|
-
columnsTo.drop.map((col) => col.name).includes(colName)
|
|
1821
|
-
) === false
|
|
1822
|
-
)
|
|
1823
|
-
.map(
|
|
1824
|
-
(index) =>
|
|
1825
|
-
`table.drop${inflection.capitalize(index.type)}([${index.columns
|
|
1826
|
-
.map((columnName) => `'${columnName}'`)
|
|
1827
|
-
.join(",")}])`
|
|
1828
|
-
),
|
|
1829
|
-
],
|
|
1830
|
-
down: [
|
|
1831
|
-
"// rollback - drop indexes",
|
|
1832
|
-
...this.genIndexDefinitions(indexesTo.drop),
|
|
1833
|
-
],
|
|
1834
|
-
};
|
|
1835
|
-
|
|
1836
|
-
return linesTo;
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
|
-
async generateAlterCode_Foreigns(
|
|
1840
|
-
table: string,
|
|
1841
|
-
entityForeigns: MigrationForeign[],
|
|
1842
|
-
dbForeigns: MigrationForeign[]
|
|
1843
|
-
): Promise<GenMigrationCode[]> {
|
|
1844
|
-
// console.log({ entityForeigns, dbForeigns });
|
|
1845
|
-
|
|
1846
|
-
const getKey = (mf: MigrationForeign): string => {
|
|
1847
|
-
return [mf.columns.join("-"), mf.to].join("///");
|
|
1848
|
-
};
|
|
1849
|
-
const fkTo = entityForeigns.reduce(
|
|
1850
|
-
(result, entityF) => {
|
|
1851
|
-
const matchingDbF = dbForeigns.find(
|
|
1852
|
-
(dbF) => getKey(entityF) === getKey(dbF)
|
|
1853
|
-
);
|
|
1854
|
-
if (!matchingDbF) {
|
|
1855
|
-
result.add.push(entityF);
|
|
1856
|
-
return result;
|
|
1857
|
-
}
|
|
1858
|
-
|
|
1859
|
-
if (equal(entityF, matchingDbF) === false) {
|
|
1860
|
-
result.alterSrc.push(matchingDbF);
|
|
1861
|
-
result.alterDst.push(entityF);
|
|
1862
|
-
return result;
|
|
1863
|
-
}
|
|
1864
|
-
return result;
|
|
1865
|
-
},
|
|
1866
|
-
{
|
|
1867
|
-
add: [] as MigrationForeign[],
|
|
1868
|
-
alterSrc: [] as MigrationForeign[],
|
|
1869
|
-
alterDst: [] as MigrationForeign[],
|
|
1870
|
-
}
|
|
1871
|
-
);
|
|
1872
|
-
|
|
1873
|
-
const linesTo = {
|
|
1874
|
-
add: this.genForeignDefinitions(table, fkTo.add),
|
|
1875
|
-
alterSrc: this.genForeignDefinitions(table, fkTo.alterSrc),
|
|
1876
|
-
alterDst: this.genForeignDefinitions(table, fkTo.alterDst),
|
|
1877
|
-
};
|
|
1878
|
-
|
|
1879
|
-
const lines: string[] = [
|
|
1880
|
-
'import { Knex } from "knex";',
|
|
1881
|
-
"",
|
|
1882
|
-
"export async function up(knex: Knex): Promise<void> {",
|
|
1883
|
-
`return knex.schema.alterTable("${table}", (table) => {`,
|
|
1884
|
-
...linesTo.add.up,
|
|
1885
|
-
...linesTo.alterSrc.down,
|
|
1886
|
-
...linesTo.alterDst.up,
|
|
1887
|
-
"})",
|
|
1888
|
-
"}",
|
|
1889
|
-
"",
|
|
1890
|
-
"export async function down(knex: Knex): Promise<void> {",
|
|
1891
|
-
`return knex.schema.alterTable("${table}", (table) => {`,
|
|
1892
|
-
...linesTo.add.down,
|
|
1893
|
-
...linesTo.alterDst.down,
|
|
1894
|
-
...linesTo.alterSrc.up,
|
|
1895
|
-
"})",
|
|
1896
|
-
"}",
|
|
1897
|
-
];
|
|
1898
|
-
|
|
1899
|
-
const formatted = await prettier.format(lines.join("\n"), {
|
|
1900
|
-
parser: "typescript",
|
|
1901
|
-
});
|
|
1902
|
-
|
|
1903
|
-
const title = [
|
|
1904
|
-
"alter",
|
|
1905
|
-
table,
|
|
1906
|
-
"foreigns",
|
|
1907
|
-
// TODO 바뀌는 부분
|
|
1908
|
-
].join("_");
|
|
1909
|
-
|
|
1910
|
-
return [
|
|
1911
|
-
{
|
|
1912
|
-
table,
|
|
1913
|
-
title,
|
|
1914
|
-
formatted,
|
|
1915
|
-
type: "normal",
|
|
1916
|
-
},
|
|
1917
|
-
];
|
|
1918
|
-
}
|
|
1919
|
-
|
|
1920
1357
|
async destroy(): Promise<void> {
|
|
1921
1358
|
await Promise.all(
|
|
1922
1359
|
this.targets.apply.map((db) => {
|