mirror of
https://github.com/chartdb/chartdb.git
synced 2025-10-23 07:11:56 +00:00
662
src/lib/domain/diff/diff-check/__tests__/diff-check.test.ts
Normal file
662
src/lib/domain/diff/diff-check/__tests__/diff-check.test.ts
Normal file
@@ -0,0 +1,662 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { generateDiff } from '../diff-check';
|
||||
import type { Diagram } from '@/lib/domain/diagram';
|
||||
import type { DBTable } from '@/lib/domain/db-table';
|
||||
import type { DBField } from '@/lib/domain/db-field';
|
||||
import type { DBIndex } from '@/lib/domain/db-index';
|
||||
import type { DBRelationship } from '@/lib/domain/db-relationship';
|
||||
import type { Area } from '@/lib/domain/area';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
import type { TableDiffChanged } from '../../table-diff';
|
||||
import type { FieldDiffChanged } from '../../field-diff';
|
||||
|
||||
// Helper function to create a mock diagram
|
||||
function createMockDiagram(overrides?: Partial<Diagram>): Diagram {
|
||||
return {
|
||||
id: 'diagram-1',
|
||||
name: 'Test Diagram',
|
||||
databaseType: DatabaseType.POSTGRESQL,
|
||||
tables: [],
|
||||
relationships: [],
|
||||
areas: [],
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function to create a mock table
|
||||
function createMockTable(overrides?: Partial<DBTable>): DBTable {
|
||||
return {
|
||||
id: 'table-1',
|
||||
name: 'users',
|
||||
fields: [],
|
||||
indexes: [],
|
||||
x: 0,
|
||||
y: 0,
|
||||
...overrides,
|
||||
} as DBTable;
|
||||
}
|
||||
|
||||
// Helper function to create a mock field
|
||||
function createMockField(overrides?: Partial<DBField>): DBField {
|
||||
return {
|
||||
id: 'field-1',
|
||||
name: 'id',
|
||||
type: { id: 'integer', name: 'integer' },
|
||||
primaryKey: false,
|
||||
nullable: true,
|
||||
unique: false,
|
||||
...overrides,
|
||||
} as DBField;
|
||||
}
|
||||
|
||||
// Helper function to create a mock relationship
|
||||
function createMockRelationship(
|
||||
overrides?: Partial<DBRelationship>
|
||||
): DBRelationship {
|
||||
return {
|
||||
id: 'rel-1',
|
||||
sourceTableId: 'table-1',
|
||||
targetTableId: 'table-2',
|
||||
sourceFieldId: 'field-1',
|
||||
targetFieldId: 'field-2',
|
||||
type: 'one-to-many',
|
||||
...overrides,
|
||||
} as DBRelationship;
|
||||
}
|
||||
|
||||
// Helper function to create a mock area
|
||||
function createMockArea(overrides?: Partial<Area>): Area {
|
||||
return {
|
||||
id: 'area-1',
|
||||
name: 'Main Area',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 100,
|
||||
color: 'blue',
|
||||
...overrides,
|
||||
} as Area;
|
||||
}
|
||||
|
||||
describe('generateDiff', () => {
|
||||
describe('Basic Table Diffing', () => {
|
||||
it('should detect added tables', () => {
|
||||
const oldDiagram = createMockDiagram({ tables: [] });
|
||||
const newDiagram = createMockDiagram({
|
||||
tables: [createMockTable()],
|
||||
});
|
||||
|
||||
const result = generateDiff({
|
||||
diagram: oldDiagram,
|
||||
newDiagram,
|
||||
});
|
||||
|
||||
expect(result.diffMap.size).toBe(1);
|
||||
const diff = result.diffMap.get('table-table-1');
|
||||
expect(diff).toBeDefined();
|
||||
expect(diff?.type).toBe('added');
|
||||
expect(result.changedTables.has('table-1')).toBe(true);
|
||||
});
|
||||
|
||||
it('should detect removed tables', () => {
|
||||
const oldDiagram = createMockDiagram({
|
||||
tables: [createMockTable()],
|
||||
});
|
||||
const newDiagram = createMockDiagram({ tables: [] });
|
||||
|
||||
const result = generateDiff({
|
||||
diagram: oldDiagram,
|
||||
newDiagram,
|
||||
});
|
||||
|
||||
expect(result.diffMap.size).toBe(1);
|
||||
const diff = result.diffMap.get('table-table-1');
|
||||
expect(diff).toBeDefined();
|
||||
expect(diff?.type).toBe('removed');
|
||||
expect(result.changedTables.has('table-1')).toBe(true);
|
||||
});
|
||||
|
||||
it('should detect table name changes', () => {
|
||||
const oldDiagram = createMockDiagram({
|
||||
tables: [createMockTable({ name: 'users' })],
|
||||
});
|
||||
const newDiagram = createMockDiagram({
|
||||
tables: [createMockTable({ name: 'customers' })],
|
||||
});
|
||||
|
||||
const result = generateDiff({
|
||||
diagram: oldDiagram,
|
||||
newDiagram,
|
||||
});
|
||||
|
||||
expect(result.diffMap.size).toBe(1);
|
||||
const diff = result.diffMap.get('table-name-table-1');
|
||||
expect(diff).toBeDefined();
|
||||
expect(diff?.type).toBe('changed');
|
||||
expect((diff as TableDiffChanged)?.attribute).toBe('name');
|
||||
});
|
||||
|
||||
it('should detect table position changes', () => {
|
||||
const oldDiagram = createMockDiagram({
|
||||
tables: [createMockTable({ x: 0, y: 0 })],
|
||||
});
|
||||
const newDiagram = createMockDiagram({
|
||||
tables: [createMockTable({ x: 100, y: 200 })],
|
||||
});
|
||||
|
||||
const result = generateDiff({
|
||||
diagram: oldDiagram,
|
||||
newDiagram,
|
||||
options: {
|
||||
attributes: {
|
||||
tables: ['name', 'comments', 'color', 'x', 'y'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.diffMap.size).toBe(2);
|
||||
expect(result.diffMap.has('table-x-table-1')).toBe(true);
|
||||
expect(result.diffMap.has('table-y-table-1')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Field Diffing', () => {
|
||||
it('should detect added fields', () => {
|
||||
const oldDiagram = createMockDiagram({
|
||||
tables: [createMockTable({ fields: [] })],
|
||||
});
|
||||
const newDiagram = createMockDiagram({
|
||||
tables: [
|
||||
createMockTable({
|
||||
fields: [createMockField()],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const result = generateDiff({
|
||||
diagram: oldDiagram,
|
||||
newDiagram,
|
||||
});
|
||||
|
||||
expect(result.diffMap.size).toBe(1);
|
||||
const diff = result.diffMap.get('field-field-1');
|
||||
expect(diff).toBeDefined();
|
||||
expect(diff?.type).toBe('added');
|
||||
expect(result.changedFields.has('field-1')).toBe(true);
|
||||
});
|
||||
|
||||
it('should detect removed fields', () => {
|
||||
const oldDiagram = createMockDiagram({
|
||||
tables: [
|
||||
createMockTable({
|
||||
fields: [createMockField()],
|
||||
}),
|
||||
],
|
||||
});
|
||||
const newDiagram = createMockDiagram({
|
||||
tables: [createMockTable({ fields: [] })],
|
||||
});
|
||||
|
||||
const result = generateDiff({
|
||||
diagram: oldDiagram,
|
||||
newDiagram,
|
||||
});
|
||||
|
||||
expect(result.diffMap.size).toBe(1);
|
||||
const diff = result.diffMap.get('field-field-1');
|
||||
expect(diff).toBeDefined();
|
||||
expect(diff?.type).toBe('removed');
|
||||
});
|
||||
|
||||
it('should detect field type changes', () => {
|
||||
const oldDiagram = createMockDiagram({
|
||||
tables: [
|
||||
createMockTable({
|
||||
fields: [
|
||||
createMockField({
|
||||
type: { id: 'integer', name: 'integer' },
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
const newDiagram = createMockDiagram({
|
||||
tables: [
|
||||
createMockTable({
|
||||
fields: [
|
||||
createMockField({
|
||||
type: { id: 'varchar', name: 'varchar' },
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const result = generateDiff({
|
||||
diagram: oldDiagram,
|
||||
newDiagram,
|
||||
});
|
||||
|
||||
expect(result.diffMap.size).toBe(1);
|
||||
const diff = result.diffMap.get('field-type-field-1');
|
||||
expect(diff).toBeDefined();
|
||||
expect(diff?.type).toBe('changed');
|
||||
expect((diff as FieldDiffChanged)?.attribute).toBe('type');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Relationship Diffing', () => {
|
||||
it('should detect added relationships', () => {
|
||||
const oldDiagram = createMockDiagram({ relationships: [] });
|
||||
const newDiagram = createMockDiagram({
|
||||
relationships: [createMockRelationship()],
|
||||
});
|
||||
|
||||
const result = generateDiff({
|
||||
diagram: oldDiagram,
|
||||
newDiagram,
|
||||
});
|
||||
|
||||
expect(result.diffMap.size).toBe(1);
|
||||
const diff = result.diffMap.get('relationship-rel-1');
|
||||
expect(diff).toBeDefined();
|
||||
expect(diff?.type).toBe('added');
|
||||
});
|
||||
|
||||
it('should detect removed relationships', () => {
|
||||
const oldDiagram = createMockDiagram({
|
||||
relationships: [createMockRelationship()],
|
||||
});
|
||||
const newDiagram = createMockDiagram({ relationships: [] });
|
||||
|
||||
const result = generateDiff({
|
||||
diagram: oldDiagram,
|
||||
newDiagram,
|
||||
});
|
||||
|
||||
expect(result.diffMap.size).toBe(1);
|
||||
const diff = result.diffMap.get('relationship-rel-1');
|
||||
expect(diff).toBeDefined();
|
||||
expect(diff?.type).toBe('removed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Area Diffing', () => {
|
||||
it('should detect added areas when includeAreas is true', () => {
|
||||
const oldDiagram = createMockDiagram({ areas: [] });
|
||||
const newDiagram = createMockDiagram({
|
||||
areas: [createMockArea()],
|
||||
});
|
||||
|
||||
const result = generateDiff({
|
||||
diagram: oldDiagram,
|
||||
newDiagram,
|
||||
options: {
|
||||
includeAreas: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.diffMap.size).toBe(1);
|
||||
const diff = result.diffMap.get('area-area-1');
|
||||
expect(diff).toBeDefined();
|
||||
expect(diff?.type).toBe('added');
|
||||
expect(result.changedAreas.has('area-1')).toBe(true);
|
||||
});
|
||||
|
||||
it('should not detect area changes when includeAreas is false', () => {
|
||||
const oldDiagram = createMockDiagram({ areas: [] });
|
||||
const newDiagram = createMockDiagram({
|
||||
areas: [createMockArea()],
|
||||
});
|
||||
|
||||
const result = generateDiff({
|
||||
diagram: oldDiagram,
|
||||
newDiagram,
|
||||
options: {
|
||||
includeAreas: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.diffMap.size).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Custom Matchers', () => {
|
||||
it('should use custom table matcher to match by name', () => {
|
||||
const oldDiagram = createMockDiagram({
|
||||
tables: [createMockTable({ id: 'table-1', name: 'users' })],
|
||||
});
|
||||
const newDiagram = createMockDiagram({
|
||||
tables: [createMockTable({ id: 'table-2', name: 'users' })],
|
||||
});
|
||||
|
||||
const result = generateDiff({
|
||||
diagram: oldDiagram,
|
||||
newDiagram,
|
||||
options: {
|
||||
matchers: {
|
||||
table: (table, tables) =>
|
||||
tables.find((t) => t.name === table.name),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Should not detect any changes since tables match by name
|
||||
expect(result.diffMap.size).toBe(0);
|
||||
});
|
||||
|
||||
it('should detect changes when custom matcher finds no match', () => {
|
||||
const oldDiagram = createMockDiagram({
|
||||
tables: [createMockTable({ id: 'table-1', name: 'users' })],
|
||||
});
|
||||
const newDiagram = createMockDiagram({
|
||||
tables: [createMockTable({ id: 'table-2', name: 'customers' })],
|
||||
});
|
||||
|
||||
const result = generateDiff({
|
||||
diagram: oldDiagram,
|
||||
newDiagram,
|
||||
options: {
|
||||
matchers: {
|
||||
table: (table, tables) =>
|
||||
tables.find((t) => t.name === table.name),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Should detect both added and removed since names don't match
|
||||
expect(result.diffMap.size).toBe(2);
|
||||
expect(result.diffMap.has('table-table-1')).toBe(true); // removed
|
||||
expect(result.diffMap.has('table-table-2')).toBe(true); // added
|
||||
});
|
||||
|
||||
it('should use custom field matcher to match by name', () => {
|
||||
const field1 = createMockField({
|
||||
id: 'field-1',
|
||||
name: 'email',
|
||||
nullable: true,
|
||||
});
|
||||
const field2 = createMockField({
|
||||
id: 'field-2',
|
||||
name: 'email',
|
||||
nullable: false,
|
||||
});
|
||||
|
||||
const oldDiagram = createMockDiagram({
|
||||
tables: [createMockTable({ id: 'table-1', fields: [field1] })],
|
||||
});
|
||||
const newDiagram = createMockDiagram({
|
||||
tables: [createMockTable({ id: 'table-1', fields: [field2] })],
|
||||
});
|
||||
|
||||
const result = generateDiff({
|
||||
diagram: oldDiagram,
|
||||
newDiagram,
|
||||
options: {
|
||||
matchers: {
|
||||
field: (field, fields) =>
|
||||
fields.find((f) => f.name === field.name),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// With name-based matching, field-1 should match field-2 by name
|
||||
// and detect the nullable change
|
||||
const nullableChange = result.diffMap.get('field-nullable-field-1');
|
||||
expect(nullableChange).toBeDefined();
|
||||
expect(nullableChange?.type).toBe('changed');
|
||||
expect((nullableChange as FieldDiffChanged)?.attribute).toBe(
|
||||
'nullable'
|
||||
);
|
||||
});
|
||||
|
||||
it('should use case-insensitive custom matcher', () => {
|
||||
const oldDiagram = createMockDiagram({
|
||||
tables: [createMockTable({ id: 'table-1', name: 'Users' })],
|
||||
});
|
||||
const newDiagram = createMockDiagram({
|
||||
tables: [createMockTable({ id: 'table-2', name: 'users' })],
|
||||
});
|
||||
|
||||
const result = generateDiff({
|
||||
diagram: oldDiagram,
|
||||
newDiagram,
|
||||
options: {
|
||||
matchers: {
|
||||
table: (table, tables) =>
|
||||
tables.find(
|
||||
(t) =>
|
||||
t.name.toLowerCase() ===
|
||||
table.name.toLowerCase()
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// With case-insensitive name matching, the tables are matched
|
||||
// but the name case difference is still detected as a change
|
||||
expect(result.diffMap.size).toBe(1);
|
||||
const nameChange = result.diffMap.get('table-name-table-1');
|
||||
expect(nameChange).toBeDefined();
|
||||
expect(nameChange?.type).toBe('changed');
|
||||
expect((nameChange as TableDiffChanged)?.attribute).toBe('name');
|
||||
expect((nameChange as TableDiffChanged)?.oldValue).toBe('Users');
|
||||
expect((nameChange as TableDiffChanged)?.newValue).toBe('users');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Filtering Options', () => {
|
||||
it('should only check specified change types', () => {
|
||||
const oldDiagram = createMockDiagram({
|
||||
tables: [createMockTable({ id: 'table-1', name: 'users' })],
|
||||
});
|
||||
const newDiagram = createMockDiagram({
|
||||
tables: [createMockTable({ id: 'table-2', name: 'products' })],
|
||||
});
|
||||
|
||||
const result = generateDiff({
|
||||
diagram: oldDiagram,
|
||||
newDiagram,
|
||||
options: {
|
||||
changeTypes: {
|
||||
tables: ['added'], // Only check for added tables
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Should only detect added table (table-2)
|
||||
const addedTables = Array.from(result.diffMap.values()).filter(
|
||||
(diff) => diff.type === 'added' && diff.object === 'table'
|
||||
);
|
||||
expect(addedTables.length).toBe(1);
|
||||
|
||||
// Should not detect removed table (table-1)
|
||||
const removedTables = Array.from(result.diffMap.values()).filter(
|
||||
(diff) => diff.type === 'removed' && diff.object === 'table'
|
||||
);
|
||||
expect(removedTables.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should only check specified attributes', () => {
|
||||
const oldDiagram = createMockDiagram({
|
||||
tables: [
|
||||
createMockTable({
|
||||
id: 'table-1',
|
||||
name: 'users',
|
||||
color: 'blue',
|
||||
comments: 'old comment',
|
||||
}),
|
||||
],
|
||||
});
|
||||
const newDiagram = createMockDiagram({
|
||||
tables: [
|
||||
createMockTable({
|
||||
id: 'table-1',
|
||||
name: 'customers',
|
||||
color: 'red',
|
||||
comments: 'new comment',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const result = generateDiff({
|
||||
diagram: oldDiagram,
|
||||
newDiagram,
|
||||
options: {
|
||||
attributes: {
|
||||
tables: ['name'], // Only check name changes
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Should only detect name change
|
||||
const nameChanges = Array.from(result.diffMap.values()).filter(
|
||||
(diff) =>
|
||||
diff.type === 'changed' &&
|
||||
diff.attribute === 'name' &&
|
||||
diff.object === 'table'
|
||||
);
|
||||
expect(nameChanges.length).toBe(1);
|
||||
|
||||
// Should not detect color or comments changes
|
||||
const otherChanges = Array.from(result.diffMap.values()).filter(
|
||||
(diff) =>
|
||||
diff.type === 'changed' &&
|
||||
(diff.attribute === 'color' ||
|
||||
diff.attribute === 'comments') &&
|
||||
diff.object === 'table'
|
||||
);
|
||||
expect(otherChanges.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should respect include flags', () => {
|
||||
const oldDiagram = createMockDiagram({
|
||||
tables: [
|
||||
createMockTable({
|
||||
fields: [createMockField()],
|
||||
indexes: [{ id: 'idx-1', name: 'idx' } as DBIndex],
|
||||
}),
|
||||
],
|
||||
});
|
||||
const newDiagram = createMockDiagram({
|
||||
tables: [
|
||||
createMockTable({
|
||||
fields: [],
|
||||
indexes: [],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const result = generateDiff({
|
||||
diagram: oldDiagram,
|
||||
newDiagram,
|
||||
options: {
|
||||
includeFields: false,
|
||||
includeIndexes: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Should only detect index removal, not field removal
|
||||
expect(result.diffMap.has('index-idx-1')).toBe(true);
|
||||
expect(result.diffMap.has('field-field-1')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Complex Scenarios', () => {
|
||||
it('should handle multiple simultaneous changes', () => {
|
||||
const oldDiagram = createMockDiagram({
|
||||
tables: [
|
||||
createMockTable({
|
||||
id: 'table-1',
|
||||
name: 'users',
|
||||
fields: [
|
||||
createMockField({ id: 'field-1', name: 'id' }),
|
||||
createMockField({ id: 'field-2', name: 'email' }),
|
||||
],
|
||||
}),
|
||||
createMockTable({
|
||||
id: 'table-2',
|
||||
name: 'products',
|
||||
}),
|
||||
],
|
||||
relationships: [createMockRelationship()],
|
||||
});
|
||||
|
||||
const newDiagram = createMockDiagram({
|
||||
tables: [
|
||||
createMockTable({
|
||||
id: 'table-1',
|
||||
name: 'customers', // Changed name
|
||||
fields: [
|
||||
createMockField({ id: 'field-1', name: 'id' }),
|
||||
// Removed field-2
|
||||
createMockField({ id: 'field-3', name: 'name' }), // Added field
|
||||
],
|
||||
}),
|
||||
// Removed table-2
|
||||
createMockTable({
|
||||
id: 'table-3',
|
||||
name: 'orders', // Added table
|
||||
}),
|
||||
],
|
||||
relationships: [], // Removed relationship
|
||||
});
|
||||
|
||||
const result = generateDiff({
|
||||
diagram: oldDiagram,
|
||||
newDiagram,
|
||||
});
|
||||
|
||||
// Verify all changes are detected
|
||||
expect(result.diffMap.has('table-name-table-1')).toBe(true); // Table name change
|
||||
expect(result.diffMap.has('field-field-2')).toBe(true); // Removed field
|
||||
expect(result.diffMap.has('field-field-3')).toBe(true); // Added field
|
||||
expect(result.diffMap.has('table-table-2')).toBe(true); // Removed table
|
||||
expect(result.diffMap.has('table-table-3')).toBe(true); // Added table
|
||||
expect(result.diffMap.has('relationship-rel-1')).toBe(true); // Removed relationship
|
||||
});
|
||||
|
||||
it('should handle empty diagrams', () => {
|
||||
const emptyDiagram1 = createMockDiagram();
|
||||
const emptyDiagram2 = createMockDiagram();
|
||||
|
||||
const result = generateDiff({
|
||||
diagram: emptyDiagram1,
|
||||
newDiagram: emptyDiagram2,
|
||||
});
|
||||
|
||||
expect(result.diffMap.size).toBe(0);
|
||||
expect(result.changedTables.size).toBe(0);
|
||||
expect(result.changedFields.size).toBe(0);
|
||||
expect(result.changedAreas.size).toBe(0);
|
||||
});
|
||||
|
||||
it('should handle diagrams with undefined collections', () => {
|
||||
const diagram1 = createMockDiagram({
|
||||
tables: undefined,
|
||||
relationships: undefined,
|
||||
areas: undefined,
|
||||
});
|
||||
const diagram2 = createMockDiagram({
|
||||
tables: [createMockTable({ id: 'table-1' })],
|
||||
relationships: [createMockRelationship({ id: 'rel-1' })],
|
||||
areas: [createMockArea({ id: 'area-1' })],
|
||||
});
|
||||
|
||||
const result = generateDiff({
|
||||
diagram: diagram1,
|
||||
newDiagram: diagram2,
|
||||
options: {
|
||||
includeAreas: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Should detect all as added
|
||||
expect(result.diffMap.has('table-table-1')).toBe(true);
|
||||
expect(result.diffMap.has('relationship-rel-1')).toBe(true);
|
||||
expect(result.diffMap.has('area-area-1')).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,6 +1,9 @@
|
||||
import type { Diagram } from '@/lib/domain/diagram';
|
||||
import type { DBField } from '@/lib/domain/db-field';
|
||||
import type { DBIndex } from '@/lib/domain/db-index';
|
||||
import type { DBTable } from '@/lib/domain/db-table';
|
||||
import type { DBRelationship } from '@/lib/domain/db-relationship';
|
||||
import type { Area } from '@/lib/domain/area';
|
||||
import type { ChartDBDiff, DiffMap, DiffObject } from '@/lib/domain/diff/diff';
|
||||
import type {
|
||||
FieldDiff,
|
||||
@@ -43,20 +46,22 @@ export interface GenerateDiffOptions {
|
||||
relationships?: RelationshipDiff['type'][];
|
||||
areas?: AreaDiff['type'][];
|
||||
};
|
||||
matchers?: {
|
||||
table?: (table: DBTable, tables: DBTable[]) => DBTable | undefined;
|
||||
field?: (field: DBField, fields: DBField[]) => DBField | undefined;
|
||||
index?: (index: DBIndex, indexes: DBIndex[]) => DBIndex | undefined;
|
||||
relationship?: (
|
||||
relationship: DBRelationship,
|
||||
relationships: DBRelationship[]
|
||||
) => DBRelationship | undefined;
|
||||
area?: (area: Area, areas: Area[]) => Area | undefined;
|
||||
};
|
||||
}
|
||||
|
||||
export function generateDiff({
|
||||
diagram,
|
||||
newDiagram,
|
||||
options = {
|
||||
includeTables: true,
|
||||
includeFields: true,
|
||||
includeIndexes: true,
|
||||
includeRelationships: true,
|
||||
includeAreas: false,
|
||||
attributes: {},
|
||||
changeTypes: {},
|
||||
},
|
||||
options = {},
|
||||
}: {
|
||||
diagram: Diagram;
|
||||
newDiagram: Diagram;
|
||||
@@ -67,20 +72,41 @@ export function generateDiff({
|
||||
changedFields: Map<string, boolean>;
|
||||
changedAreas: Map<string, boolean>;
|
||||
} {
|
||||
// Merge with default options
|
||||
const mergedOptions: GenerateDiffOptions = {
|
||||
includeTables: options.includeTables ?? true,
|
||||
includeFields: options.includeFields ?? true,
|
||||
includeIndexes: options.includeIndexes ?? true,
|
||||
includeRelationships: options.includeRelationships ?? true,
|
||||
includeAreas: options.includeAreas ?? false,
|
||||
attributes: options.attributes ?? {},
|
||||
changeTypes: options.changeTypes ?? {},
|
||||
matchers: options.matchers ?? {},
|
||||
};
|
||||
|
||||
const newDiffs = new Map<string, ChartDBDiff>();
|
||||
const changedTables = new Map<string, boolean>();
|
||||
const changedFields = new Map<string, boolean>();
|
||||
const changedAreas = new Map<string, boolean>();
|
||||
|
||||
// Use provided matchers or default ones
|
||||
const tableMatcher = mergedOptions.matchers?.table ?? defaultTableMatcher;
|
||||
const fieldMatcher = mergedOptions.matchers?.field ?? defaultFieldMatcher;
|
||||
const indexMatcher = mergedOptions.matchers?.index ?? defaultIndexMatcher;
|
||||
const relationshipMatcher =
|
||||
mergedOptions.matchers?.relationship ?? defaultRelationshipMatcher;
|
||||
const areaMatcher = mergedOptions.matchers?.area ?? defaultAreaMatcher;
|
||||
|
||||
// Compare tables
|
||||
if (options.includeTables) {
|
||||
if (mergedOptions.includeTables) {
|
||||
compareTables({
|
||||
diagram,
|
||||
newDiagram,
|
||||
diffMap: newDiffs,
|
||||
changedTables,
|
||||
attributes: options.attributes?.tables,
|
||||
changeTypes: options.changeTypes?.tables,
|
||||
attributes: mergedOptions.attributes?.tables,
|
||||
changeTypes: mergedOptions.changeTypes?.tables,
|
||||
tableMatcher,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -91,28 +117,33 @@ export function generateDiff({
|
||||
diffMap: newDiffs,
|
||||
changedTables,
|
||||
changedFields,
|
||||
options,
|
||||
options: mergedOptions,
|
||||
tableMatcher,
|
||||
fieldMatcher,
|
||||
indexMatcher,
|
||||
});
|
||||
|
||||
// Compare relationships
|
||||
if (options.includeRelationships) {
|
||||
if (mergedOptions.includeRelationships) {
|
||||
compareRelationships({
|
||||
diagram,
|
||||
newDiagram,
|
||||
diffMap: newDiffs,
|
||||
changeTypes: options.changeTypes?.relationships,
|
||||
changeTypes: mergedOptions.changeTypes?.relationships,
|
||||
relationshipMatcher,
|
||||
});
|
||||
}
|
||||
|
||||
// Compare areas if enabled
|
||||
if (options.includeAreas) {
|
||||
if (mergedOptions.includeAreas) {
|
||||
compareAreas({
|
||||
diagram,
|
||||
newDiagram,
|
||||
diffMap: newDiffs,
|
||||
changedAreas,
|
||||
attributes: options.attributes?.areas,
|
||||
changeTypes: options.changeTypes?.areas,
|
||||
attributes: mergedOptions.attributes?.areas,
|
||||
changeTypes: mergedOptions.changeTypes?.areas,
|
||||
areaMatcher,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -127,6 +158,7 @@ function compareTables({
|
||||
changedTables,
|
||||
attributes,
|
||||
changeTypes,
|
||||
tableMatcher,
|
||||
}: {
|
||||
diagram: Diagram;
|
||||
newDiagram: Diagram;
|
||||
@@ -134,6 +166,7 @@ function compareTables({
|
||||
changedTables: Map<string, boolean>;
|
||||
attributes?: TableDiffAttribute[];
|
||||
changeTypes?: TableDiff['type'][];
|
||||
tableMatcher: (table: DBTable, tables: DBTable[]) => DBTable | undefined;
|
||||
}) {
|
||||
const oldTables = diagram.tables || [];
|
||||
const newTables = newDiagram.tables || [];
|
||||
@@ -149,7 +182,7 @@ function compareTables({
|
||||
// Check for added tables
|
||||
if (typesToCheck.includes('added')) {
|
||||
for (const newTable of newTables) {
|
||||
if (!oldTables.find((t) => t.id === newTable.id)) {
|
||||
if (!tableMatcher(newTable, oldTables)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'table',
|
||||
@@ -169,7 +202,7 @@ function compareTables({
|
||||
// Check for removed tables
|
||||
if (typesToCheck.includes('removed')) {
|
||||
for (const oldTable of oldTables) {
|
||||
if (!newTables.find((t) => t.id === oldTable.id)) {
|
||||
if (!tableMatcher(oldTable, newTables)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'table',
|
||||
@@ -189,7 +222,7 @@ function compareTables({
|
||||
// Check for table name, comments and color changes
|
||||
if (typesToCheck.includes('changed')) {
|
||||
for (const oldTable of oldTables) {
|
||||
const newTable = newTables.find((t) => t.id === oldTable.id);
|
||||
const newTable = tableMatcher(oldTable, newTables);
|
||||
|
||||
if (!newTable) continue;
|
||||
|
||||
@@ -321,6 +354,9 @@ function compareTableContents({
|
||||
changedTables,
|
||||
changedFields,
|
||||
options,
|
||||
tableMatcher,
|
||||
fieldMatcher,
|
||||
indexMatcher,
|
||||
}: {
|
||||
diagram: Diagram;
|
||||
newDiagram: Diagram;
|
||||
@@ -328,13 +364,16 @@ function compareTableContents({
|
||||
changedTables: Map<string, boolean>;
|
||||
changedFields: Map<string, boolean>;
|
||||
options?: GenerateDiffOptions;
|
||||
tableMatcher: (table: DBTable, tables: DBTable[]) => DBTable | undefined;
|
||||
fieldMatcher: (field: DBField, fields: DBField[]) => DBField | undefined;
|
||||
indexMatcher: (index: DBIndex, indexes: DBIndex[]) => DBIndex | undefined;
|
||||
}) {
|
||||
const oldTables = diagram.tables || [];
|
||||
const newTables = newDiagram.tables || [];
|
||||
|
||||
// For each table that exists in both diagrams
|
||||
for (const oldTable of oldTables) {
|
||||
const newTable = newTables.find((t) => t.id === oldTable.id);
|
||||
const newTable = tableMatcher(oldTable, newTables);
|
||||
if (!newTable) continue;
|
||||
|
||||
// Compare fields
|
||||
@@ -348,6 +387,7 @@ function compareTableContents({
|
||||
changedFields,
|
||||
attributes: options?.attributes?.fields,
|
||||
changeTypes: options?.changeTypes?.fields,
|
||||
fieldMatcher,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -360,6 +400,7 @@ function compareTableContents({
|
||||
diffMap,
|
||||
changedTables,
|
||||
changeTypes: options?.changeTypes?.indexes,
|
||||
indexMatcher,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -375,6 +416,7 @@ function compareFields({
|
||||
changedFields,
|
||||
attributes,
|
||||
changeTypes,
|
||||
fieldMatcher,
|
||||
}: {
|
||||
tableId: string;
|
||||
oldFields: DBField[];
|
||||
@@ -384,6 +426,7 @@ function compareFields({
|
||||
changedFields: Map<string, boolean>;
|
||||
attributes?: FieldDiffAttribute[];
|
||||
changeTypes?: FieldDiff['type'][];
|
||||
fieldMatcher: (field: DBField, fields: DBField[]) => DBField | undefined;
|
||||
}) {
|
||||
// If changeTypes is empty array, don't check any changes
|
||||
if (changeTypes && changeTypes.length === 0) {
|
||||
@@ -395,7 +438,7 @@ function compareFields({
|
||||
// Check for added fields
|
||||
if (typesToCheck.includes('added')) {
|
||||
for (const newField of newFields) {
|
||||
if (!oldFields.find((f) => f.id === newField.id)) {
|
||||
if (!fieldMatcher(newField, oldFields)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'field',
|
||||
@@ -417,7 +460,7 @@ function compareFields({
|
||||
// Check for removed fields
|
||||
if (typesToCheck.includes('removed')) {
|
||||
for (const oldField of oldFields) {
|
||||
if (!newFields.find((f) => f.id === oldField.id)) {
|
||||
if (!fieldMatcher(oldField, newFields)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'field',
|
||||
@@ -440,7 +483,7 @@ function compareFields({
|
||||
// Check for field changes
|
||||
if (typesToCheck.includes('changed')) {
|
||||
for (const oldField of oldFields) {
|
||||
const newField = newFields.find((f) => f.id === oldField.id);
|
||||
const newField = fieldMatcher(oldField, newFields);
|
||||
if (!newField) continue;
|
||||
|
||||
// Compare basic field properties
|
||||
@@ -586,6 +629,7 @@ function compareIndexes({
|
||||
diffMap,
|
||||
changedTables,
|
||||
changeTypes,
|
||||
indexMatcher,
|
||||
}: {
|
||||
tableId: string;
|
||||
oldIndexes: DBIndex[];
|
||||
@@ -593,6 +637,7 @@ function compareIndexes({
|
||||
diffMap: DiffMap;
|
||||
changedTables: Map<string, boolean>;
|
||||
changeTypes?: IndexDiff['type'][];
|
||||
indexMatcher: (index: DBIndex, indexes: DBIndex[]) => DBIndex | undefined;
|
||||
}) {
|
||||
// If changeTypes is empty array, don't check any changes
|
||||
if (changeTypes && changeTypes.length === 0) {
|
||||
@@ -604,7 +649,7 @@ function compareIndexes({
|
||||
// Check for added indexes
|
||||
if (typesToCheck.includes('added')) {
|
||||
for (const newIndex of newIndexes) {
|
||||
if (!oldIndexes.find((i) => i.id === newIndex.id)) {
|
||||
if (!indexMatcher(newIndex, oldIndexes)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'index',
|
||||
@@ -625,7 +670,7 @@ function compareIndexes({
|
||||
// Check for removed indexes
|
||||
if (typesToCheck.includes('removed')) {
|
||||
for (const oldIndex of oldIndexes) {
|
||||
if (!newIndexes.find((i) => i.id === oldIndex.id)) {
|
||||
if (!indexMatcher(oldIndex, newIndexes)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'index',
|
||||
@@ -650,11 +695,16 @@ function compareRelationships({
|
||||
newDiagram,
|
||||
diffMap,
|
||||
changeTypes,
|
||||
relationshipMatcher,
|
||||
}: {
|
||||
diagram: Diagram;
|
||||
newDiagram: Diagram;
|
||||
diffMap: DiffMap;
|
||||
changeTypes?: RelationshipDiff['type'][];
|
||||
relationshipMatcher: (
|
||||
relationship: DBRelationship,
|
||||
relationships: DBRelationship[]
|
||||
) => DBRelationship | undefined;
|
||||
}) {
|
||||
// If changeTypes is empty array, don't check any changes
|
||||
if (changeTypes && changeTypes.length === 0) {
|
||||
@@ -669,7 +719,7 @@ function compareRelationships({
|
||||
// Check for added relationships
|
||||
if (typesToCheck.includes('added')) {
|
||||
for (const newRelationship of newRelationships) {
|
||||
if (!oldRelationships.find((r) => r.id === newRelationship.id)) {
|
||||
if (!relationshipMatcher(newRelationship, oldRelationships)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'relationship',
|
||||
@@ -688,7 +738,7 @@ function compareRelationships({
|
||||
// Check for removed relationships
|
||||
if (typesToCheck.includes('removed')) {
|
||||
for (const oldRelationship of oldRelationships) {
|
||||
if (!newRelationships.find((r) => r.id === oldRelationship.id)) {
|
||||
if (!relationshipMatcher(oldRelationship, newRelationships)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'relationship',
|
||||
@@ -713,6 +763,7 @@ function compareAreas({
|
||||
changedAreas,
|
||||
attributes,
|
||||
changeTypes,
|
||||
areaMatcher,
|
||||
}: {
|
||||
diagram: Diagram;
|
||||
newDiagram: Diagram;
|
||||
@@ -720,6 +771,7 @@ function compareAreas({
|
||||
changedAreas: Map<string, boolean>;
|
||||
attributes?: AreaDiffAttribute[];
|
||||
changeTypes?: AreaDiff['type'][];
|
||||
areaMatcher: (area: Area, areas: Area[]) => Area | undefined;
|
||||
}) {
|
||||
const oldAreas = diagram.areas || [];
|
||||
const newAreas = newDiagram.areas || [];
|
||||
@@ -735,7 +787,7 @@ function compareAreas({
|
||||
// Check for added areas
|
||||
if (typesToCheck.includes('added')) {
|
||||
for (const newArea of newAreas) {
|
||||
if (!oldAreas.find((a) => a.id === newArea.id)) {
|
||||
if (!areaMatcher(newArea, oldAreas)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'area',
|
||||
@@ -755,7 +807,7 @@ function compareAreas({
|
||||
// Check for removed areas
|
||||
if (typesToCheck.includes('removed')) {
|
||||
for (const oldArea of oldAreas) {
|
||||
if (!newAreas.find((a) => a.id === oldArea.id)) {
|
||||
if (!areaMatcher(oldArea, newAreas)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'area',
|
||||
@@ -775,7 +827,7 @@ function compareAreas({
|
||||
// Check for area name and color changes
|
||||
if (typesToCheck.includes('changed')) {
|
||||
for (const oldArea of oldAreas) {
|
||||
const newArea = newAreas.find((a) => a.id === oldArea.id);
|
||||
const newArea = areaMatcher(oldArea, newAreas);
|
||||
|
||||
if (!newArea) continue;
|
||||
|
||||
@@ -869,3 +921,35 @@ function compareAreas({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const defaultTableMatcher = (
|
||||
table: DBTable,
|
||||
tables: DBTable[]
|
||||
): DBTable | undefined => {
|
||||
return tables.find((t) => t.id === table.id);
|
||||
};
|
||||
|
||||
const defaultFieldMatcher = (
|
||||
field: DBField,
|
||||
fields: DBField[]
|
||||
): DBField | undefined => {
|
||||
return fields.find((f) => f.id === field.id);
|
||||
};
|
||||
|
||||
const defaultIndexMatcher = (
|
||||
index: DBIndex,
|
||||
indexes: DBIndex[]
|
||||
): DBIndex | undefined => {
|
||||
return indexes.find((i) => i.id === index.id);
|
||||
};
|
||||
|
||||
const defaultRelationshipMatcher = (
|
||||
relationship: DBRelationship,
|
||||
relationships: DBRelationship[]
|
||||
): DBRelationship | undefined => {
|
||||
return relationships.find((r) => r.id === relationship.id);
|
||||
};
|
||||
|
||||
const defaultAreaMatcher = (area: Area, areas: Area[]): Area | undefined => {
|
||||
return areas.find((a) => a.id === area.id);
|
||||
};
|
||||
|
Reference in New Issue
Block a user