sonamu 0.2.48 → 0.2.49

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.
@@ -158,6 +158,10 @@ export class FixtureManagerClass {
158
158
  await transaction(tableName).truncate();
159
159
 
160
160
  const rows = await frdb(tableName);
161
+ if (rows.length === 0) {
162
+ return;
163
+ }
164
+
161
165
  console.log(chalk.blue(tableName), rows.length);
162
166
  await transaction
163
167
  .insert(
@@ -307,58 +311,72 @@ export class FixtureManagerClass {
307
311
  throw new Error("No records found");
308
312
  }
309
313
 
310
- const visitedEntities = new Set<string>();
311
- const records: FixtureRecord[] = [];
314
+ const fixtures: FixtureRecord[] = [];
312
315
  for (const row of rows) {
313
- const initialRecordsLength = records.length;
314
- await this.createFixtureRecord(entity, row, visitedEntities, records);
315
- const currentFixtureRecord = records.find(
316
+ const initialRecordsLength = fixtures.length;
317
+ const newRecords = await this.createFixtureRecord(entity, row);
318
+ fixtures.push(...newRecords);
319
+ const currentFixtureRecord = fixtures.find(
316
320
  (r) => r.fixtureId === `${entityId}#${row.id}`
317
321
  );
318
322
 
319
323
  if (currentFixtureRecord) {
320
324
  // 현재 fixture로부터 생성된 fetchedRecords 설정
321
- currentFixtureRecord.fetchedRecords = records
325
+ currentFixtureRecord.fetchedRecords = fixtures
322
326
  .filter((r) => r.fixtureId !== currentFixtureRecord.fixtureId)
323
327
  .slice(initialRecordsLength)
324
328
  .map((r) => r.fixtureId);
325
329
  }
326
330
  }
327
331
 
328
- for await (const record of records) {
329
- const entity = EntityManager.get(record.entityId);
330
- const rows: FixtureRecord[] = [];
331
- const row = await targetDB(entity.table).where("id", record.id).first();
332
+ for await (const fixture of fixtures) {
333
+ const entity = EntityManager.get(fixture.entityId);
334
+
335
+ // targetDB에 해당 레코드가 존재하는지 확인
336
+ const row = await targetDB(entity.table).where("id", fixture.id).first();
332
337
  if (row) {
333
- await this.createFixtureRecord(
334
- entity,
335
- row,
336
- new Set(),
337
- rows,
338
- true,
339
- targetDB
340
- );
341
- record.target = rows[0];
338
+ const [record] = await this.createFixtureRecord(entity, row, {
339
+ singleRecord: true,
340
+ _db: targetDB,
341
+ });
342
+ fixture.target = record;
343
+ continue;
344
+ }
345
+
346
+ // targetDB에 해당 레코드가 존재하지 않는 경우, unique 제약을 위반하는지 확인
347
+ const uniqueRow = await this.checkUniqueViolation(
348
+ targetDB,
349
+ entity,
350
+ fixture
351
+ );
352
+ if (uniqueRow) {
353
+ const [record] = await this.createFixtureRecord(entity, uniqueRow, {
354
+ singleRecord: true,
355
+ _db: targetDB,
356
+ });
357
+ fixture.unique = record;
342
358
  }
343
359
  }
344
360
 
345
- return records;
361
+ return fixtures;
346
362
  }
347
363
 
348
364
  async createFixtureRecord(
349
365
  entity: Entity,
350
366
  row: any,
351
- visitedEntities: Set<string>,
352
- records: FixtureRecord[],
353
- singleRecord = false,
354
- _db?: Knex
355
- ) {
367
+ options?: {
368
+ singleRecord?: boolean;
369
+ _db?: Knex;
370
+ },
371
+ visitedEntities = new Set<string>()
372
+ ): Promise<FixtureRecord[]> {
356
373
  const fixtureId = `${entity.id}#${row.id}`;
357
374
  if (visitedEntities.has(fixtureId)) {
358
- return;
375
+ return [];
359
376
  }
360
377
  visitedEntities.add(fixtureId);
361
378
 
379
+ const records: FixtureRecord[] = [];
362
380
  const record: FixtureRecord = {
363
381
  fixtureId,
364
382
  entityId: entity.id,
@@ -378,7 +396,7 @@ export class FixtureManagerClass {
378
396
  value: row[prop.name],
379
397
  };
380
398
 
381
- const db = _db ?? BaseModel.getDB("w");
399
+ const db = options?._db ?? BaseModel.getDB("w");
382
400
  if (isManyToManyRelationProp(prop)) {
383
401
  const relatedEntity = EntityManager.get(prop.with);
384
402
  const throughTable = prop.joinTable;
@@ -412,32 +430,34 @@ export class FixtureManagerClass {
412
430
  if (relatedId) {
413
431
  record.belongsRecords.push(`${prop.with}#${relatedId}`);
414
432
  }
415
- if (!singleRecord && relatedId) {
433
+ if (!options?.singleRecord && relatedId) {
416
434
  const relatedEntity = EntityManager.get(prop.with);
417
435
  const relatedRow = await db(relatedEntity.table)
418
436
  .where("id", relatedId)
419
437
  .first();
420
438
  if (relatedRow) {
421
- await this.createFixtureRecord(
439
+ const newRecords = await this.createFixtureRecord(
422
440
  relatedEntity,
423
441
  relatedRow,
424
- visitedEntities,
425
- records,
426
- singleRecord,
427
- _db
442
+ options,
443
+ visitedEntities
428
444
  );
445
+ records.push(...newRecords);
429
446
  }
430
447
  }
431
448
  }
432
449
  }
433
450
 
434
451
  records.push(record);
452
+ return records;
435
453
  }
436
454
 
437
455
  async insertFixtures(
438
456
  dbName: keyof SonamuDBConfig,
439
- fixtures: FixtureRecord[]
457
+ _fixtures: FixtureRecord[]
440
458
  ) {
459
+ const fixtures = _.uniqBy(_fixtures, (f) => f.fixtureId);
460
+
441
461
  this.buildDependencyGraph(fixtures);
442
462
  const insertionOrder = this.getInsertionOrder();
443
463
  const db = knex(Sonamu.dbConfig[dbName]);
@@ -447,7 +467,27 @@ export class FixtureManagerClass {
447
467
 
448
468
  for (const fixtureId of insertionOrder) {
449
469
  const fixture = fixtures.find((f) => f.fixtureId === fixtureId)!;
450
- await this.insertFixture(trx, fixture);
470
+ const result = await this.insertFixture(trx, fixture);
471
+ if (result.id !== fixture.id) {
472
+ // ID가 변경된 경우, 다른 fixture에서 참조하는 경우가 찾아서 수정
473
+ console.log(
474
+ chalk.yellow(
475
+ `Unique constraint violation: ${fixture.entityId}#${fixture.id} -> ${fixture.entityId}#${result.id}`
476
+ )
477
+ );
478
+ fixtures.forEach((f) => {
479
+ Object.values(f.columns).forEach((column) => {
480
+ if (
481
+ column.prop.type === "relation" &&
482
+ column.prop.with === result.entityId &&
483
+ column.value === fixture.id
484
+ ) {
485
+ column.value = result.id;
486
+ }
487
+ });
488
+ });
489
+ fixture.id = result.id;
490
+ }
451
491
  }
452
492
 
453
493
  for (const fixtureId of insertionOrder) {
@@ -468,7 +508,7 @@ export class FixtureManagerClass {
468
508
  });
469
509
  }
470
510
 
471
- return records;
511
+ return _.uniqBy(records, (r) => `${r.entityId}#${r.data.id}`);
472
512
  }
473
513
 
474
514
  private getInsertionOrder() {
@@ -598,6 +638,14 @@ export class FixtureManagerClass {
598
638
  const entity = EntityManager.get(fixture.entityId);
599
639
 
600
640
  try {
641
+ const uniqueFound = await this.checkUniqueViolation(db, entity, fixture);
642
+ if (uniqueFound) {
643
+ return {
644
+ entityId: fixture.entityId,
645
+ id: uniqueFound.id,
646
+ };
647
+ }
648
+
601
649
  const found = await db(entity.table).where("id", fixture.id).first();
602
650
  if (found && !fixture.override) {
603
651
  return {
@@ -688,5 +736,43 @@ export class FixtureManagerClass {
688
736
  throw new Error("Failed to find fixtureLoader in fixture.ts");
689
737
  }
690
738
  }
739
+
740
+ // 해당 픽스쳐의 값으로 유니크 제약에 위배되는 레코드가 있는지 확인
741
+ private async checkUniqueViolation(
742
+ db: Knex,
743
+ entity: Entity,
744
+ fixture: FixtureRecord
745
+ ) {
746
+ const uniqueIndexes = entity.indexes.filter((i) => i.type === "unique");
747
+ if (uniqueIndexes.length === 0) {
748
+ return null;
749
+ }
750
+
751
+ let uniqueQuery = db(entity.table);
752
+ for (const index of uniqueIndexes) {
753
+ // 컬럼 중 하나라도 null이면 유니크 제약을 위반하지 않기 때문에 해당 인덱스는 무시
754
+ if (
755
+ index.columns.some(
756
+ (column) => fixture.columns[column.split("_id")[0]].value === null
757
+ )
758
+ ) {
759
+ continue;
760
+ }
761
+
762
+ uniqueQuery = uniqueQuery.orWhere((qb) => {
763
+ for (const column of index.columns) {
764
+ const field = column.split("_id")[0];
765
+
766
+ if (Array.isArray(fixture.columns[field].value)) {
767
+ qb.whereIn(column, fixture.columns[field].value);
768
+ } else {
769
+ qb.andWhere(column, fixture.columns[field].value);
770
+ }
771
+ }
772
+ });
773
+ }
774
+ const [uniqueFound] = await uniqueQuery;
775
+ return uniqueFound;
776
+ }
691
777
  }
692
778
  export const FixtureManager = new FixtureManagerClass();
@@ -738,8 +738,9 @@ export type FixtureRecord = {
738
738
  };
739
739
  };
740
740
  fetchedRecords: string[];
741
- belongsRecords: string[]; //
742
- target?: FixtureRecord; // Import 대상 DB 레코드
741
+ belongsRecords: string[];
742
+ target?: FixtureRecord; // Import 대상 DB 레코드(id가 같은)
743
+ unique?: FixtureRecord; // Import 대상 DB 레코드(unique key가 같은)
743
744
  override?: boolean;
744
745
  };
745
746
 
@@ -17,19 +17,21 @@ export async function importMultiple(
17
17
  filePaths: string[],
18
18
  doRefresh: boolean = false
19
19
  ): Promise<{ filePath: string; imported: any }[]> {
20
- return Promise.all(
21
- filePaths.map(async (filePath) => {
22
- const importPath = "./" + path.relative(__dirname, filePath);
23
- if (doRefresh) {
24
- delete require.cache[require.resolve(importPath)];
25
- }
26
- const imported = await import(importPath);
27
- return {
28
- filePath,
29
- imported,
30
- };
31
- })
32
- );
20
+ const results: { filePath: string; imported: any }[] = [];
21
+
22
+ for (const filePath of filePaths) {
23
+ const importPath = "./" + path.relative(__dirname, filePath);
24
+ if (doRefresh) {
25
+ delete require.cache[require.resolve(importPath)];
26
+ }
27
+ const imported = await import(importPath);
28
+ results.push({
29
+ filePath,
30
+ imported,
31
+ });
32
+ }
33
+
34
+ return results;
33
35
  }
34
36
  export async function findAppRootPath() {
35
37
  const apiRootPath = await findApiRootPath();