mirror of
https://github.com/chartdb/chartdb.git
synced 2026-02-11 22:20:11 -06:00
fix: diff logic (#927)
This commit is contained in:
70
src/lib/domain/diff/area-diff.ts
Normal file
70
src/lib/domain/diff/area-diff.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { z } from 'zod';
|
||||
import type { Area } from '../area';
|
||||
|
||||
export type AreaDiffAttribute = keyof Pick<Area, 'name' | 'color'>;
|
||||
|
||||
const areaDiffAttributeSchema: z.ZodType<AreaDiffAttribute> = z.union([
|
||||
z.literal('name'),
|
||||
z.literal('color'),
|
||||
]);
|
||||
|
||||
export interface AreaDiffChanged {
|
||||
object: 'area';
|
||||
type: 'changed';
|
||||
areaId: string;
|
||||
attribute: AreaDiffAttribute;
|
||||
oldValue?: string | null;
|
||||
newValue?: string | null;
|
||||
}
|
||||
|
||||
export const AreaDiffChangedSchema: z.ZodType<AreaDiffChanged> = z.object({
|
||||
object: z.literal('area'),
|
||||
type: z.literal('changed'),
|
||||
areaId: z.string(),
|
||||
attribute: areaDiffAttributeSchema,
|
||||
oldValue: z.string().or(z.null()).optional(),
|
||||
newValue: z.string().or(z.null()).optional(),
|
||||
});
|
||||
|
||||
export interface AreaDiffRemoved {
|
||||
object: 'area';
|
||||
type: 'removed';
|
||||
areaId: string;
|
||||
}
|
||||
|
||||
export const AreaDiffRemovedSchema: z.ZodType<AreaDiffRemoved> = z.object({
|
||||
object: z.literal('area'),
|
||||
type: z.literal('removed'),
|
||||
areaId: z.string(),
|
||||
});
|
||||
|
||||
export interface AreaDiffAdded<T = Area> {
|
||||
object: 'area';
|
||||
type: 'added';
|
||||
areaAdded: T;
|
||||
}
|
||||
|
||||
export const createAreaDiffAddedSchema = <T = Area>(
|
||||
areaSchema: z.ZodType<T>
|
||||
): z.ZodType<AreaDiffAdded<T>> => {
|
||||
return z.object({
|
||||
object: z.literal('area'),
|
||||
type: z.literal('added'),
|
||||
areaAdded: areaSchema,
|
||||
}) as z.ZodType<AreaDiffAdded<T>>;
|
||||
};
|
||||
|
||||
export type AreaDiff<T = Area> =
|
||||
| AreaDiffChanged
|
||||
| AreaDiffRemoved
|
||||
| AreaDiffAdded<T>;
|
||||
|
||||
export const createAreaDiffSchema = <T = Area>(
|
||||
areaSchema: z.ZodType<T>
|
||||
): z.ZodType<AreaDiff<T>> => {
|
||||
return z.union([
|
||||
AreaDiffChangedSchema,
|
||||
AreaDiffRemovedSchema,
|
||||
createAreaDiffAddedSchema(areaSchema),
|
||||
]) as z.ZodType<AreaDiff<T>>;
|
||||
};
|
||||
@@ -2,7 +2,14 @@ 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 { ChartDBDiff, DiffMap, DiffObject } from '@/lib/domain/diff/diff';
|
||||
import type { FieldDiffAttribute } from '@/lib/domain/diff/field-diff';
|
||||
import type {
|
||||
FieldDiff,
|
||||
FieldDiffAttribute,
|
||||
} from '@/lib/domain/diff/field-diff';
|
||||
import type { TableDiff, TableDiffAttribute } from '../table-diff';
|
||||
import type { AreaDiff, AreaDiffAttribute } from '../area-diff';
|
||||
import type { IndexDiff } from '../index-diff';
|
||||
import type { RelationshipDiff } from '../relationship-diff';
|
||||
|
||||
export function getDiffMapKey({
|
||||
diffObject,
|
||||
@@ -18,23 +25,64 @@ export function getDiffMapKey({
|
||||
: `${diffObject}-${objectId}`;
|
||||
}
|
||||
|
||||
export interface GenerateDiffOptions {
|
||||
includeTables?: boolean;
|
||||
includeFields?: boolean;
|
||||
includeIndexes?: boolean;
|
||||
includeRelationships?: boolean;
|
||||
includeAreas?: boolean;
|
||||
attributes?: {
|
||||
tables?: TableDiffAttribute[];
|
||||
fields?: FieldDiffAttribute[];
|
||||
areas?: AreaDiffAttribute[];
|
||||
};
|
||||
changeTypes?: {
|
||||
tables?: TableDiff['type'][];
|
||||
fields?: FieldDiff['type'][];
|
||||
indexes?: IndexDiff['type'][];
|
||||
relationships?: RelationshipDiff['type'][];
|
||||
areas?: AreaDiff['type'][];
|
||||
};
|
||||
}
|
||||
|
||||
export function generateDiff({
|
||||
diagram,
|
||||
newDiagram,
|
||||
options = {
|
||||
includeTables: true,
|
||||
includeFields: true,
|
||||
includeIndexes: true,
|
||||
includeRelationships: true,
|
||||
includeAreas: false,
|
||||
attributes: {},
|
||||
changeTypes: {},
|
||||
},
|
||||
}: {
|
||||
diagram: Diagram;
|
||||
newDiagram: Diagram;
|
||||
options?: GenerateDiffOptions;
|
||||
}): {
|
||||
diffMap: DiffMap;
|
||||
changedTables: Map<string, boolean>;
|
||||
changedFields: Map<string, boolean>;
|
||||
changedAreas: Map<string, boolean>;
|
||||
} {
|
||||
const newDiffs = new Map<string, ChartDBDiff>();
|
||||
const changedTables = new Map<string, boolean>();
|
||||
const changedFields = new Map<string, boolean>();
|
||||
const changedAreas = new Map<string, boolean>();
|
||||
|
||||
// Compare tables
|
||||
compareTables({ diagram, newDiagram, diffMap: newDiffs, changedTables });
|
||||
if (options.includeTables) {
|
||||
compareTables({
|
||||
diagram,
|
||||
newDiagram,
|
||||
diffMap: newDiffs,
|
||||
changedTables,
|
||||
attributes: options.attributes?.tables,
|
||||
changeTypes: options.changeTypes?.tables,
|
||||
});
|
||||
}
|
||||
|
||||
// Compare fields and indexes for matching tables
|
||||
compareTableContents({
|
||||
@@ -43,12 +91,32 @@ export function generateDiff({
|
||||
diffMap: newDiffs,
|
||||
changedTables,
|
||||
changedFields,
|
||||
options,
|
||||
});
|
||||
|
||||
// Compare relationships
|
||||
compareRelationships({ diagram, newDiagram, diffMap: newDiffs });
|
||||
if (options.includeRelationships) {
|
||||
compareRelationships({
|
||||
diagram,
|
||||
newDiagram,
|
||||
diffMap: newDiffs,
|
||||
changeTypes: options.changeTypes?.relationships,
|
||||
});
|
||||
}
|
||||
|
||||
return { diffMap: newDiffs, changedTables, changedFields };
|
||||
// Compare areas if enabled
|
||||
if (options.includeAreas) {
|
||||
compareAreas({
|
||||
diagram,
|
||||
newDiagram,
|
||||
diffMap: newDiffs,
|
||||
changedAreas,
|
||||
attributes: options.attributes?.areas,
|
||||
changeTypes: options.changeTypes?.areas,
|
||||
});
|
||||
}
|
||||
|
||||
return { diffMap: newDiffs, changedTables, changedFields, changedAreas };
|
||||
}
|
||||
|
||||
// Compare tables between diagrams
|
||||
@@ -57,112 +125,150 @@ function compareTables({
|
||||
newDiagram,
|
||||
diffMap,
|
||||
changedTables,
|
||||
attributes,
|
||||
changeTypes,
|
||||
}: {
|
||||
diagram: Diagram;
|
||||
newDiagram: Diagram;
|
||||
diffMap: DiffMap;
|
||||
changedTables: Map<string, boolean>;
|
||||
attributes?: TableDiffAttribute[];
|
||||
changeTypes?: TableDiff['type'][];
|
||||
}) {
|
||||
const oldTables = diagram.tables || [];
|
||||
const newTables = newDiagram.tables || [];
|
||||
|
||||
// If changeTypes is empty array, don't check any changes
|
||||
if (changeTypes && changeTypes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If changeTypes is undefined, check all types
|
||||
const typesToCheck = changeTypes ?? ['added', 'removed', 'changed'];
|
||||
|
||||
// Check for added tables
|
||||
for (const newTable of newTables) {
|
||||
if (!oldTables.find((t) => t.id === newTable.id)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({ diffObject: 'table', objectId: newTable.id }),
|
||||
{
|
||||
object: 'table',
|
||||
type: 'added',
|
||||
tableAdded: newTable,
|
||||
}
|
||||
);
|
||||
changedTables.set(newTable.id, true);
|
||||
if (typesToCheck.includes('added')) {
|
||||
for (const newTable of newTables) {
|
||||
if (!oldTables.find((t) => t.id === newTable.id)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'table',
|
||||
objectId: newTable.id,
|
||||
}),
|
||||
{
|
||||
object: 'table',
|
||||
type: 'added',
|
||||
tableAdded: newTable,
|
||||
}
|
||||
);
|
||||
changedTables.set(newTable.id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for removed tables
|
||||
for (const oldTable of oldTables) {
|
||||
if (!newTables.find((t) => t.id === oldTable.id)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({ diffObject: 'table', objectId: oldTable.id }),
|
||||
{
|
||||
object: 'table',
|
||||
type: 'removed',
|
||||
tableId: oldTable.id,
|
||||
}
|
||||
);
|
||||
changedTables.set(oldTable.id, true);
|
||||
if (typesToCheck.includes('removed')) {
|
||||
for (const oldTable of oldTables) {
|
||||
if (!newTables.find((t) => t.id === oldTable.id)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'table',
|
||||
objectId: oldTable.id,
|
||||
}),
|
||||
{
|
||||
object: 'table',
|
||||
type: 'removed',
|
||||
tableId: oldTable.id,
|
||||
}
|
||||
);
|
||||
changedTables.set(oldTable.id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for table name, comments and color changes
|
||||
for (const oldTable of oldTables) {
|
||||
const newTable = newTables.find((t) => t.id === oldTable.id);
|
||||
if (typesToCheck.includes('changed')) {
|
||||
for (const oldTable of oldTables) {
|
||||
const newTable = newTables.find((t) => t.id === oldTable.id);
|
||||
|
||||
if (!newTable) continue;
|
||||
if (!newTable) continue;
|
||||
|
||||
if (oldTable.name !== newTable.name) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'table',
|
||||
objectId: oldTable.id,
|
||||
attribute: 'name',
|
||||
}),
|
||||
{
|
||||
object: 'table',
|
||||
type: 'changed',
|
||||
tableId: oldTable.id,
|
||||
attribute: 'name',
|
||||
newValue: newTable.name,
|
||||
oldValue: oldTable.name,
|
||||
}
|
||||
);
|
||||
// If attributes are specified, only check those attributes
|
||||
const attributesToCheck: TableDiffAttribute[] = attributes ?? [
|
||||
'name',
|
||||
'comments',
|
||||
'color',
|
||||
];
|
||||
|
||||
changedTables.set(oldTable.id, true);
|
||||
}
|
||||
if (
|
||||
attributesToCheck.includes('name') &&
|
||||
oldTable.name !== newTable.name
|
||||
) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'table',
|
||||
objectId: oldTable.id,
|
||||
attribute: 'name',
|
||||
}),
|
||||
{
|
||||
object: 'table',
|
||||
type: 'changed',
|
||||
tableId: oldTable.id,
|
||||
attribute: 'name',
|
||||
newValue: newTable.name,
|
||||
oldValue: oldTable.name,
|
||||
}
|
||||
);
|
||||
|
||||
if (
|
||||
(oldTable.comments || newTable.comments) &&
|
||||
oldTable.comments !== newTable.comments
|
||||
) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'table',
|
||||
objectId: oldTable.id,
|
||||
attribute: 'comments',
|
||||
}),
|
||||
{
|
||||
object: 'table',
|
||||
type: 'changed',
|
||||
tableId: oldTable.id,
|
||||
attribute: 'comments',
|
||||
newValue: newTable.comments,
|
||||
oldValue: oldTable.comments,
|
||||
}
|
||||
);
|
||||
changedTables.set(oldTable.id, true);
|
||||
}
|
||||
|
||||
changedTables.set(oldTable.id, true);
|
||||
}
|
||||
if (
|
||||
attributesToCheck.includes('comments') &&
|
||||
(oldTable.comments || newTable.comments) &&
|
||||
oldTable.comments !== newTable.comments
|
||||
) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'table',
|
||||
objectId: oldTable.id,
|
||||
attribute: 'comments',
|
||||
}),
|
||||
{
|
||||
object: 'table',
|
||||
type: 'changed',
|
||||
tableId: oldTable.id,
|
||||
attribute: 'comments',
|
||||
newValue: newTable.comments,
|
||||
oldValue: oldTable.comments,
|
||||
}
|
||||
);
|
||||
|
||||
if (oldTable.color !== newTable.color) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'table',
|
||||
objectId: oldTable.id,
|
||||
attribute: 'color',
|
||||
}),
|
||||
{
|
||||
object: 'table',
|
||||
type: 'changed',
|
||||
tableId: oldTable.id,
|
||||
attribute: 'color',
|
||||
newValue: newTable.color,
|
||||
oldValue: oldTable.color,
|
||||
}
|
||||
);
|
||||
changedTables.set(oldTable.id, true);
|
||||
}
|
||||
|
||||
changedTables.set(oldTable.id, true);
|
||||
if (
|
||||
attributesToCheck.includes('color') &&
|
||||
oldTable.color !== newTable.color
|
||||
) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'table',
|
||||
objectId: oldTable.id,
|
||||
attribute: 'color',
|
||||
}),
|
||||
{
|
||||
object: 'table',
|
||||
type: 'changed',
|
||||
tableId: oldTable.id,
|
||||
attribute: 'color',
|
||||
newValue: newTable.color,
|
||||
oldValue: oldTable.color,
|
||||
}
|
||||
);
|
||||
|
||||
changedTables.set(oldTable.id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,12 +280,14 @@ function compareTableContents({
|
||||
diffMap,
|
||||
changedTables,
|
||||
changedFields,
|
||||
options,
|
||||
}: {
|
||||
diagram: Diagram;
|
||||
newDiagram: Diagram;
|
||||
diffMap: DiffMap;
|
||||
changedTables: Map<string, boolean>;
|
||||
changedFields: Map<string, boolean>;
|
||||
options?: GenerateDiffOptions;
|
||||
}) {
|
||||
const oldTables = diagram.tables || [];
|
||||
const newTables = newDiagram.tables || [];
|
||||
@@ -190,23 +298,30 @@ function compareTableContents({
|
||||
if (!newTable) continue;
|
||||
|
||||
// Compare fields
|
||||
compareFields({
|
||||
tableId: oldTable.id,
|
||||
oldFields: oldTable.fields,
|
||||
newFields: newTable.fields,
|
||||
diffMap,
|
||||
changedTables,
|
||||
changedFields,
|
||||
});
|
||||
if (options?.includeFields) {
|
||||
compareFields({
|
||||
tableId: oldTable.id,
|
||||
oldFields: oldTable.fields,
|
||||
newFields: newTable.fields,
|
||||
diffMap,
|
||||
changedTables,
|
||||
changedFields,
|
||||
attributes: options?.attributes?.fields,
|
||||
changeTypes: options?.changeTypes?.fields,
|
||||
});
|
||||
}
|
||||
|
||||
// Compare indexes
|
||||
compareIndexes({
|
||||
tableId: oldTable.id,
|
||||
oldIndexes: oldTable.indexes,
|
||||
newIndexes: newTable.indexes,
|
||||
diffMap,
|
||||
changedTables,
|
||||
});
|
||||
if (options?.includeIndexes) {
|
||||
compareIndexes({
|
||||
tableId: oldTable.id,
|
||||
oldIndexes: oldTable.indexes,
|
||||
newIndexes: newTable.indexes,
|
||||
diffMap,
|
||||
changedTables,
|
||||
changeTypes: options?.changeTypes?.indexes,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,6 +333,8 @@ function compareFields({
|
||||
diffMap,
|
||||
changedTables,
|
||||
changedFields,
|
||||
attributes,
|
||||
changeTypes,
|
||||
}: {
|
||||
tableId: string;
|
||||
oldFields: DBField[];
|
||||
@@ -225,62 +342,78 @@ function compareFields({
|
||||
diffMap: DiffMap;
|
||||
changedTables: Map<string, boolean>;
|
||||
changedFields: Map<string, boolean>;
|
||||
attributes?: FieldDiffAttribute[];
|
||||
changeTypes?: FieldDiff['type'][];
|
||||
}) {
|
||||
// If changeTypes is empty array, don't check any changes
|
||||
if (changeTypes && changeTypes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If changeTypes is undefined, check all types
|
||||
const typesToCheck = changeTypes ?? ['added', 'removed', 'changed'];
|
||||
// Check for added fields
|
||||
for (const newField of newFields) {
|
||||
if (!oldFields.find((f) => f.id === newField.id)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'field',
|
||||
objectId: newField.id,
|
||||
}),
|
||||
{
|
||||
object: 'field',
|
||||
type: 'added',
|
||||
newField,
|
||||
tableId,
|
||||
}
|
||||
);
|
||||
changedTables.set(tableId, true);
|
||||
changedFields.set(newField.id, true);
|
||||
if (typesToCheck.includes('added')) {
|
||||
for (const newField of newFields) {
|
||||
if (!oldFields.find((f) => f.id === newField.id)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'field',
|
||||
objectId: newField.id,
|
||||
}),
|
||||
{
|
||||
object: 'field',
|
||||
type: 'added',
|
||||
newField,
|
||||
tableId,
|
||||
}
|
||||
);
|
||||
changedTables.set(tableId, true);
|
||||
changedFields.set(newField.id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for removed fields
|
||||
for (const oldField of oldFields) {
|
||||
if (!newFields.find((f) => f.id === oldField.id)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'field',
|
||||
objectId: oldField.id,
|
||||
}),
|
||||
{
|
||||
object: 'field',
|
||||
type: 'removed',
|
||||
fieldId: oldField.id,
|
||||
tableId,
|
||||
}
|
||||
);
|
||||
if (typesToCheck.includes('removed')) {
|
||||
for (const oldField of oldFields) {
|
||||
if (!newFields.find((f) => f.id === oldField.id)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'field',
|
||||
objectId: oldField.id,
|
||||
}),
|
||||
{
|
||||
object: 'field',
|
||||
type: 'removed',
|
||||
fieldId: oldField.id,
|
||||
tableId,
|
||||
}
|
||||
);
|
||||
|
||||
changedTables.set(tableId, true);
|
||||
changedFields.set(oldField.id, true);
|
||||
changedTables.set(tableId, true);
|
||||
changedFields.set(oldField.id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for field changes
|
||||
for (const oldField of oldFields) {
|
||||
const newField = newFields.find((f) => f.id === oldField.id);
|
||||
if (!newField) continue;
|
||||
if (typesToCheck.includes('changed')) {
|
||||
for (const oldField of oldFields) {
|
||||
const newField = newFields.find((f) => f.id === oldField.id);
|
||||
if (!newField) continue;
|
||||
|
||||
// Compare basic field properties
|
||||
compareFieldProperties({
|
||||
tableId,
|
||||
oldField,
|
||||
newField,
|
||||
diffMap,
|
||||
changedTables,
|
||||
changedFields,
|
||||
});
|
||||
// Compare basic field properties
|
||||
compareFieldProperties({
|
||||
tableId,
|
||||
oldField,
|
||||
newField,
|
||||
diffMap,
|
||||
changedTables,
|
||||
changedFields,
|
||||
attributes,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,6 +425,7 @@ function compareFieldProperties({
|
||||
diffMap,
|
||||
changedTables,
|
||||
changedFields,
|
||||
attributes,
|
||||
}: {
|
||||
tableId: string;
|
||||
oldField: DBField;
|
||||
@@ -299,30 +433,57 @@ function compareFieldProperties({
|
||||
diffMap: DiffMap;
|
||||
changedTables: Map<string, boolean>;
|
||||
changedFields: Map<string, boolean>;
|
||||
attributes?: FieldDiffAttribute[];
|
||||
}) {
|
||||
// If attributes are specified, only check those attributes
|
||||
const attributesToCheck: FieldDiffAttribute[] = attributes ?? [
|
||||
'name',
|
||||
'type',
|
||||
'primaryKey',
|
||||
'unique',
|
||||
'nullable',
|
||||
'comments',
|
||||
'characterMaximumLength',
|
||||
'scale',
|
||||
'precision',
|
||||
];
|
||||
|
||||
const changedAttributes: FieldDiffAttribute[] = [];
|
||||
|
||||
if (oldField.name !== newField.name) {
|
||||
if (attributesToCheck.includes('name') && oldField.name !== newField.name) {
|
||||
changedAttributes.push('name');
|
||||
}
|
||||
|
||||
if (oldField.type.id !== newField.type.id) {
|
||||
if (
|
||||
attributesToCheck.includes('type') &&
|
||||
oldField.type.id !== newField.type.id
|
||||
) {
|
||||
changedAttributes.push('type');
|
||||
}
|
||||
|
||||
if (oldField.primaryKey !== newField.primaryKey) {
|
||||
if (
|
||||
attributesToCheck.includes('primaryKey') &&
|
||||
oldField.primaryKey !== newField.primaryKey
|
||||
) {
|
||||
changedAttributes.push('primaryKey');
|
||||
}
|
||||
|
||||
if (oldField.unique !== newField.unique) {
|
||||
if (
|
||||
attributesToCheck.includes('unique') &&
|
||||
oldField.unique !== newField.unique
|
||||
) {
|
||||
changedAttributes.push('unique');
|
||||
}
|
||||
|
||||
if (oldField.nullable !== newField.nullable) {
|
||||
if (
|
||||
attributesToCheck.includes('nullable') &&
|
||||
oldField.nullable !== newField.nullable
|
||||
) {
|
||||
changedAttributes.push('nullable');
|
||||
}
|
||||
|
||||
if (
|
||||
attributesToCheck.includes('comments') &&
|
||||
(newField.comments || oldField.comments) &&
|
||||
oldField.comments !== newField.comments
|
||||
) {
|
||||
@@ -330,6 +491,7 @@ function compareFieldProperties({
|
||||
}
|
||||
|
||||
if (
|
||||
attributesToCheck.includes('characterMaximumLength') &&
|
||||
(newField.characterMaximumLength || oldField.characterMaximumLength) &&
|
||||
oldField.characterMaximumLength !== newField.characterMaximumLength
|
||||
) {
|
||||
@@ -337,6 +499,7 @@ function compareFieldProperties({
|
||||
}
|
||||
|
||||
if (
|
||||
attributesToCheck.includes('scale') &&
|
||||
(newField.scale || oldField.scale) &&
|
||||
oldField.scale !== newField.scale
|
||||
) {
|
||||
@@ -344,6 +507,7 @@ function compareFieldProperties({
|
||||
}
|
||||
|
||||
if (
|
||||
attributesToCheck.includes('precision') &&
|
||||
(newField.precision || oldField.precision) &&
|
||||
oldField.precision !== newField.precision
|
||||
) {
|
||||
@@ -381,48 +545,61 @@ function compareIndexes({
|
||||
newIndexes,
|
||||
diffMap,
|
||||
changedTables,
|
||||
changeTypes,
|
||||
}: {
|
||||
tableId: string;
|
||||
oldIndexes: DBIndex[];
|
||||
newIndexes: DBIndex[];
|
||||
diffMap: DiffMap;
|
||||
changedTables: Map<string, boolean>;
|
||||
changeTypes?: IndexDiff['type'][];
|
||||
}) {
|
||||
// If changeTypes is empty array, don't check any changes
|
||||
if (changeTypes && changeTypes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If changeTypes is undefined, check all types
|
||||
const typesToCheck = changeTypes ?? ['added', 'removed'];
|
||||
// Check for added indexes
|
||||
for (const newIndex of newIndexes) {
|
||||
if (!oldIndexes.find((i) => i.id === newIndex.id)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'index',
|
||||
objectId: newIndex.id,
|
||||
}),
|
||||
{
|
||||
object: 'index',
|
||||
type: 'added',
|
||||
newIndex,
|
||||
tableId,
|
||||
}
|
||||
);
|
||||
changedTables.set(tableId, true);
|
||||
if (typesToCheck.includes('added')) {
|
||||
for (const newIndex of newIndexes) {
|
||||
if (!oldIndexes.find((i) => i.id === newIndex.id)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'index',
|
||||
objectId: newIndex.id,
|
||||
}),
|
||||
{
|
||||
object: 'index',
|
||||
type: 'added',
|
||||
newIndex,
|
||||
tableId,
|
||||
}
|
||||
);
|
||||
changedTables.set(tableId, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for removed indexes
|
||||
for (const oldIndex of oldIndexes) {
|
||||
if (!newIndexes.find((i) => i.id === oldIndex.id)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'index',
|
||||
objectId: oldIndex.id,
|
||||
}),
|
||||
{
|
||||
object: 'index',
|
||||
type: 'removed',
|
||||
indexId: oldIndex.id,
|
||||
tableId,
|
||||
}
|
||||
);
|
||||
changedTables.set(tableId, true);
|
||||
if (typesToCheck.includes('removed')) {
|
||||
for (const oldIndex of oldIndexes) {
|
||||
if (!newIndexes.find((i) => i.id === oldIndex.id)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'index',
|
||||
objectId: oldIndex.id,
|
||||
}),
|
||||
{
|
||||
object: 'index',
|
||||
type: 'removed',
|
||||
indexId: oldIndex.id,
|
||||
tableId,
|
||||
}
|
||||
);
|
||||
changedTables.set(tableId, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -432,45 +609,185 @@ function compareRelationships({
|
||||
diagram,
|
||||
newDiagram,
|
||||
diffMap,
|
||||
changeTypes,
|
||||
}: {
|
||||
diagram: Diagram;
|
||||
newDiagram: Diagram;
|
||||
diffMap: DiffMap;
|
||||
changeTypes?: RelationshipDiff['type'][];
|
||||
}) {
|
||||
// If changeTypes is empty array, don't check any changes
|
||||
if (changeTypes && changeTypes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If changeTypes is undefined, check all types
|
||||
const typesToCheck = changeTypes ?? ['added', 'removed'];
|
||||
const oldRelationships = diagram.relationships || [];
|
||||
const newRelationships = newDiagram.relationships || [];
|
||||
|
||||
// Check for added relationships
|
||||
for (const newRelationship of newRelationships) {
|
||||
if (!oldRelationships.find((r) => r.id === newRelationship.id)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'relationship',
|
||||
objectId: newRelationship.id,
|
||||
}),
|
||||
{
|
||||
object: 'relationship',
|
||||
type: 'added',
|
||||
newRelationship,
|
||||
}
|
||||
);
|
||||
if (typesToCheck.includes('added')) {
|
||||
for (const newRelationship of newRelationships) {
|
||||
if (!oldRelationships.find((r) => r.id === newRelationship.id)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'relationship',
|
||||
objectId: newRelationship.id,
|
||||
}),
|
||||
{
|
||||
object: 'relationship',
|
||||
type: 'added',
|
||||
newRelationship,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for removed relationships
|
||||
for (const oldRelationship of oldRelationships) {
|
||||
if (!newRelationships.find((r) => r.id === oldRelationship.id)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'relationship',
|
||||
objectId: oldRelationship.id,
|
||||
}),
|
||||
{
|
||||
object: 'relationship',
|
||||
type: 'removed',
|
||||
relationshipId: oldRelationship.id,
|
||||
}
|
||||
);
|
||||
if (typesToCheck.includes('removed')) {
|
||||
for (const oldRelationship of oldRelationships) {
|
||||
if (!newRelationships.find((r) => r.id === oldRelationship.id)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'relationship',
|
||||
objectId: oldRelationship.id,
|
||||
}),
|
||||
{
|
||||
object: 'relationship',
|
||||
type: 'removed',
|
||||
relationshipId: oldRelationship.id,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compare areas between diagrams
|
||||
function compareAreas({
|
||||
diagram,
|
||||
newDiagram,
|
||||
diffMap,
|
||||
changedAreas,
|
||||
attributes,
|
||||
changeTypes,
|
||||
}: {
|
||||
diagram: Diagram;
|
||||
newDiagram: Diagram;
|
||||
diffMap: DiffMap;
|
||||
changedAreas: Map<string, boolean>;
|
||||
attributes?: AreaDiffAttribute[];
|
||||
changeTypes?: AreaDiff['type'][];
|
||||
}) {
|
||||
const oldAreas = diagram.areas || [];
|
||||
const newAreas = newDiagram.areas || [];
|
||||
|
||||
// If changeTypes is empty array, don't check any changes
|
||||
if (changeTypes && changeTypes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If changeTypes is undefined, check all types
|
||||
const typesToCheck = changeTypes ?? ['added', 'removed', 'changed'];
|
||||
|
||||
// Check for added areas
|
||||
if (typesToCheck.includes('added')) {
|
||||
for (const newArea of newAreas) {
|
||||
if (!oldAreas.find((a) => a.id === newArea.id)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'area',
|
||||
objectId: newArea.id,
|
||||
}),
|
||||
{
|
||||
object: 'area',
|
||||
type: 'added',
|
||||
areaAdded: newArea,
|
||||
}
|
||||
);
|
||||
changedAreas.set(newArea.id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for removed areas
|
||||
if (typesToCheck.includes('removed')) {
|
||||
for (const oldArea of oldAreas) {
|
||||
if (!newAreas.find((a) => a.id === oldArea.id)) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'area',
|
||||
objectId: oldArea.id,
|
||||
}),
|
||||
{
|
||||
object: 'area',
|
||||
type: 'removed',
|
||||
areaId: oldArea.id,
|
||||
}
|
||||
);
|
||||
changedAreas.set(oldArea.id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
if (!newArea) continue;
|
||||
|
||||
// If attributes are specified, only check those attributes
|
||||
const attributesToCheck: AreaDiffAttribute[] = attributes ?? [
|
||||
'name',
|
||||
'color',
|
||||
];
|
||||
|
||||
if (
|
||||
attributesToCheck.includes('name') &&
|
||||
oldArea.name !== newArea.name
|
||||
) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'area',
|
||||
objectId: oldArea.id,
|
||||
attribute: 'name',
|
||||
}),
|
||||
{
|
||||
object: 'area',
|
||||
type: 'changed',
|
||||
areaId: oldArea.id,
|
||||
attribute: 'name',
|
||||
newValue: newArea.name,
|
||||
oldValue: oldArea.name,
|
||||
}
|
||||
);
|
||||
changedAreas.set(oldArea.id, true);
|
||||
}
|
||||
|
||||
if (
|
||||
attributesToCheck.includes('color') &&
|
||||
oldArea.color !== newArea.color
|
||||
) {
|
||||
diffMap.set(
|
||||
getDiffMapKey({
|
||||
diffObject: 'area',
|
||||
objectId: oldArea.id,
|
||||
attribute: 'color',
|
||||
}),
|
||||
{
|
||||
object: 'area',
|
||||
type: 'changed',
|
||||
areaId: oldArea.id,
|
||||
attribute: 'color',
|
||||
newValue: newArea.color,
|
||||
oldValue: oldArea.color,
|
||||
}
|
||||
);
|
||||
changedAreas.set(oldArea.id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,36 +8,43 @@ import type { RelationshipDiff } from './relationship-diff';
|
||||
import { createRelationshipDiffSchema } from './relationship-diff';
|
||||
import type { TableDiff } from './table-diff';
|
||||
import { createTableDiffSchema } from './table-diff';
|
||||
import type { DBField, DBIndex, DBRelationship, DBTable } from '..';
|
||||
import type { AreaDiff } from './area-diff';
|
||||
import { createAreaDiffSchema } from './area-diff';
|
||||
import type { DBField, DBIndex, DBRelationship, DBTable, Area } from '..';
|
||||
|
||||
export type ChartDBDiff<
|
||||
TTable = DBTable,
|
||||
TField = DBField,
|
||||
TIndex = DBIndex,
|
||||
TRelationship = DBRelationship,
|
||||
TArea = Area,
|
||||
> =
|
||||
| TableDiff<TTable>
|
||||
| FieldDiff<TField>
|
||||
| IndexDiff<TIndex>
|
||||
| RelationshipDiff<TRelationship>;
|
||||
| RelationshipDiff<TRelationship>
|
||||
| AreaDiff<TArea>;
|
||||
|
||||
export const createChartDBDiffSchema = <
|
||||
TTable = DBTable,
|
||||
TField = DBField,
|
||||
TIndex = DBIndex,
|
||||
TRelationship = DBRelationship,
|
||||
TArea = Area,
|
||||
>(
|
||||
tableSchema: z.ZodType<TTable>,
|
||||
fieldSchema: z.ZodType<TField>,
|
||||
indexSchema: z.ZodType<TIndex>,
|
||||
relationshipSchema: z.ZodType<TRelationship>
|
||||
): z.ZodType<ChartDBDiff<TTable, TField, TIndex, TRelationship>> => {
|
||||
relationshipSchema: z.ZodType<TRelationship>,
|
||||
areaSchema: z.ZodType<TArea>
|
||||
): z.ZodType<ChartDBDiff<TTable, TField, TIndex, TRelationship, TArea>> => {
|
||||
return z.union([
|
||||
createTableDiffSchema(tableSchema),
|
||||
createFieldDiffSchema(fieldSchema),
|
||||
createIndexDiffSchema(indexSchema),
|
||||
createRelationshipDiffSchema(relationshipSchema),
|
||||
]) as z.ZodType<ChartDBDiff<TTable, TField, TIndex, TRelationship>>;
|
||||
createAreaDiffSchema(areaSchema),
|
||||
]) as z.ZodType<ChartDBDiff<TTable, TField, TIndex, TRelationship, TArea>>;
|
||||
};
|
||||
|
||||
export type DiffMap<
|
||||
@@ -45,18 +52,21 @@ export type DiffMap<
|
||||
TField = DBField,
|
||||
TIndex = DBIndex,
|
||||
TRelationship = DBRelationship,
|
||||
> = Map<string, ChartDBDiff<TTable, TField, TIndex, TRelationship>>;
|
||||
TArea = Area,
|
||||
> = Map<string, ChartDBDiff<TTable, TField, TIndex, TRelationship, TArea>>;
|
||||
|
||||
export type DiffObject<
|
||||
TTable = DBTable,
|
||||
TField = DBField,
|
||||
TIndex = DBIndex,
|
||||
TRelationship = DBRelationship,
|
||||
TArea = Area,
|
||||
> =
|
||||
| TableDiff<TTable>['object']
|
||||
| FieldDiff<TField>['object']
|
||||
| IndexDiff<TIndex>['object']
|
||||
| RelationshipDiff<TRelationship>['object'];
|
||||
| RelationshipDiff<TRelationship>['object']
|
||||
| AreaDiff<TArea>['object'];
|
||||
|
||||
type ExtractDiffKind<T> = T extends { object: infer O; type: infer Type }
|
||||
? T extends { attribute: infer A }
|
||||
@@ -69,16 +79,18 @@ export type DiffKind<
|
||||
TField = DBField,
|
||||
TIndex = DBIndex,
|
||||
TRelationship = DBRelationship,
|
||||
> = ExtractDiffKind<ChartDBDiff<TTable, TField, TIndex, TRelationship>>;
|
||||
TArea = Area,
|
||||
> = ExtractDiffKind<ChartDBDiff<TTable, TField, TIndex, TRelationship, TArea>>;
|
||||
|
||||
export const isDiffOfKind = <
|
||||
TTable = DBTable,
|
||||
TField = DBField,
|
||||
TIndex = DBIndex,
|
||||
TRelationship = DBRelationship,
|
||||
TArea = Area,
|
||||
>(
|
||||
diff: ChartDBDiff<TTable, TField, TIndex, TRelationship>,
|
||||
kind: DiffKind<TTable, TField, TIndex, TRelationship>
|
||||
diff: ChartDBDiff<TTable, TField, TIndex, TRelationship, TArea>,
|
||||
kind: DiffKind<TTable, TField, TIndex, TRelationship, TArea>
|
||||
): boolean => {
|
||||
if ('attribute' in kind) {
|
||||
return (
|
||||
|
||||
687
src/lib/utils/__tests__/apply-ids.test.ts
Normal file
687
src/lib/utils/__tests__/apply-ids.test.ts
Normal file
@@ -0,0 +1,687 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { applyIds } from '../apply-ids';
|
||||
import {
|
||||
DatabaseType,
|
||||
DBCustomTypeKind,
|
||||
type Diagram,
|
||||
type DBTable,
|
||||
type DBField,
|
||||
type DBIndex,
|
||||
type DBRelationship,
|
||||
type DBDependency,
|
||||
type DBCustomType,
|
||||
} from '../../domain';
|
||||
|
||||
describe('applyIds', () => {
|
||||
const createBaseDiagram = (overrides?: Partial<Diagram>): Diagram => ({
|
||||
id: 'diagram1',
|
||||
name: 'Test Diagram',
|
||||
databaseType: DatabaseType.POSTGRESQL,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const createTable = (overrides: Partial<DBTable>): DBTable => ({
|
||||
id: 'table-1',
|
||||
name: 'table',
|
||||
schema: 'public',
|
||||
x: 0,
|
||||
y: 0,
|
||||
fields: [],
|
||||
indexes: [],
|
||||
color: '#000000',
|
||||
comments: null,
|
||||
isView: false,
|
||||
createdAt: Date.now(),
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const createField = (overrides: Partial<DBField>): DBField => ({
|
||||
id: 'field-1',
|
||||
name: 'field',
|
||||
type: { id: 'integer', name: 'integer' },
|
||||
primaryKey: false,
|
||||
nullable: false,
|
||||
unique: false,
|
||||
comments: null,
|
||||
collation: null,
|
||||
createdAt: Date.now(),
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const createIndex = (overrides: Partial<DBIndex>): DBIndex => ({
|
||||
id: 'index-1',
|
||||
name: 'index',
|
||||
unique: false,
|
||||
fieldIds: [],
|
||||
createdAt: Date.now(),
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const createRelationship = (
|
||||
overrides: Partial<DBRelationship>
|
||||
): DBRelationship => ({
|
||||
id: 'rel-1',
|
||||
name: 'relationship',
|
||||
sourceTableId: 'table-1',
|
||||
sourceFieldId: 'field-1',
|
||||
targetTableId: 'table-2',
|
||||
targetFieldId: 'field-2',
|
||||
sourceCardinality: 'many',
|
||||
targetCardinality: 'one',
|
||||
createdAt: Date.now(),
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const createDependency = (
|
||||
overrides: Partial<DBDependency>
|
||||
): DBDependency => ({
|
||||
id: 'dep-1',
|
||||
tableId: 'table-1',
|
||||
dependentTableId: 'table-2',
|
||||
createdAt: Date.now(),
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const createCustomType = (
|
||||
overrides: Partial<DBCustomType>
|
||||
): DBCustomType => ({
|
||||
id: 'type-1',
|
||||
name: 'custom_type',
|
||||
schema: 'public',
|
||||
kind: DBCustomTypeKind.enum,
|
||||
values: [],
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe('table ID mapping', () => {
|
||||
it('should preserve table IDs when tables match by name and schema', () => {
|
||||
const sourceDiagram = createBaseDiagram({
|
||||
tables: [
|
||||
createTable({
|
||||
id: 'source-table-1',
|
||||
name: 'users',
|
||||
schema: 'public',
|
||||
}),
|
||||
createTable({
|
||||
id: 'source-table-2',
|
||||
name: 'posts',
|
||||
schema: 'public',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const targetDiagram = createBaseDiagram({
|
||||
tables: [
|
||||
createTable({
|
||||
id: 'target-table-1',
|
||||
name: 'users',
|
||||
schema: 'public',
|
||||
x: 100,
|
||||
y: 100,
|
||||
color: '#ff0000',
|
||||
comments: 'Users table',
|
||||
}),
|
||||
createTable({
|
||||
id: 'target-table-2',
|
||||
name: 'posts',
|
||||
schema: 'public',
|
||||
x: 200,
|
||||
y: 200,
|
||||
color: '#00ff00',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const result = applyIds({ sourceDiagram, targetDiagram });
|
||||
|
||||
expect(result.tables).toHaveLength(2);
|
||||
expect(result.tables?.[0].id).toBe('source-table-1');
|
||||
expect(result.tables?.[0].name).toBe('users');
|
||||
expect(result.tables?.[0].x).toBe(100); // Should keep target's position
|
||||
expect(result.tables?.[0].color).toBe('#ff0000'); // Should keep target's color
|
||||
expect(result.tables?.[1].id).toBe('source-table-2');
|
||||
expect(result.tables?.[1].name).toBe('posts');
|
||||
});
|
||||
|
||||
it('should keep target table IDs when no matching source table exists', () => {
|
||||
const sourceDiagram = createBaseDiagram({
|
||||
tables: [
|
||||
createTable({
|
||||
id: 'source-table-1',
|
||||
name: 'users',
|
||||
schema: 'public',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const targetDiagram = createBaseDiagram({
|
||||
tables: [
|
||||
createTable({
|
||||
id: 'target-table-1',
|
||||
name: 'orders',
|
||||
schema: 'public',
|
||||
x: 100,
|
||||
y: 100,
|
||||
color: '#ff0000',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const result = applyIds({ sourceDiagram, targetDiagram });
|
||||
|
||||
expect(result.tables).toHaveLength(1);
|
||||
expect(result.tables?.[0].id).toBe('target-table-1'); // Should keep target ID
|
||||
expect(result.tables?.[0].name).toBe('orders');
|
||||
});
|
||||
});
|
||||
|
||||
describe('field ID mapping', () => {
|
||||
it('should preserve field IDs when fields match by name within the same table', () => {
|
||||
const sourceDiagram = createBaseDiagram({
|
||||
tables: [
|
||||
createTable({
|
||||
id: 'source-table-1',
|
||||
name: 'users',
|
||||
schema: 'public',
|
||||
fields: [
|
||||
createField({
|
||||
id: 'source-field-1',
|
||||
name: 'id',
|
||||
type: { id: 'integer', name: 'integer' },
|
||||
primaryKey: true,
|
||||
nullable: false,
|
||||
unique: true,
|
||||
}),
|
||||
createField({
|
||||
id: 'source-field-2',
|
||||
name: 'email',
|
||||
type: { id: 'varchar', name: 'varchar' },
|
||||
primaryKey: false,
|
||||
nullable: false,
|
||||
unique: true,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const targetDiagram = createBaseDiagram({
|
||||
tables: [
|
||||
createTable({
|
||||
id: 'target-table-1',
|
||||
name: 'users',
|
||||
schema: 'public',
|
||||
x: 100,
|
||||
y: 100,
|
||||
color: '#ff0000',
|
||||
fields: [
|
||||
createField({
|
||||
id: 'target-field-1',
|
||||
name: 'id',
|
||||
type: { id: 'bigint', name: 'bigint' },
|
||||
primaryKey: true,
|
||||
nullable: false,
|
||||
unique: true,
|
||||
comments: 'Primary key',
|
||||
}),
|
||||
createField({
|
||||
id: 'target-field-2',
|
||||
name: 'email',
|
||||
type: { id: 'text', name: 'text' },
|
||||
primaryKey: false,
|
||||
nullable: true,
|
||||
unique: false,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const result = applyIds({ sourceDiagram, targetDiagram });
|
||||
|
||||
expect(result.tables?.[0].fields).toHaveLength(2);
|
||||
expect(result.tables?.[0].fields[0].id).toBe('source-field-1');
|
||||
expect(result.tables?.[0].fields[0].name).toBe('id');
|
||||
expect(result.tables?.[0].fields[0].type.id).toBe('bigint'); // Should keep target's type
|
||||
expect(result.tables?.[0].fields[1].id).toBe('source-field-2');
|
||||
expect(result.tables?.[0].fields[1].name).toBe('email');
|
||||
expect(result.tables?.[0].fields[1].nullable).toBe(true); // Should keep target's nullable
|
||||
});
|
||||
});
|
||||
|
||||
describe('index ID mapping', () => {
|
||||
it('should preserve index IDs and update field references', () => {
|
||||
const sourceDiagram = createBaseDiagram({
|
||||
tables: [
|
||||
createTable({
|
||||
id: 'source-table-1',
|
||||
name: 'users',
|
||||
schema: 'public',
|
||||
fields: [
|
||||
createField({
|
||||
id: 'source-field-1',
|
||||
name: 'email',
|
||||
type: { id: 'varchar', name: 'varchar' },
|
||||
}),
|
||||
],
|
||||
indexes: [
|
||||
createIndex({
|
||||
id: 'source-index-1',
|
||||
name: 'idx_email',
|
||||
unique: true,
|
||||
fieldIds: ['source-field-1'],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const targetDiagram = createBaseDiagram({
|
||||
tables: [
|
||||
createTable({
|
||||
id: 'target-table-1',
|
||||
name: 'users',
|
||||
schema: 'public',
|
||||
x: 100,
|
||||
y: 100,
|
||||
color: '#ff0000',
|
||||
fields: [
|
||||
createField({
|
||||
id: 'target-field-1',
|
||||
name: 'email',
|
||||
type: { id: 'text', name: 'text' },
|
||||
}),
|
||||
],
|
||||
indexes: [
|
||||
createIndex({
|
||||
id: 'target-index-1',
|
||||
name: 'idx_email',
|
||||
unique: false,
|
||||
fieldIds: ['target-field-1'],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const result = applyIds({ sourceDiagram, targetDiagram });
|
||||
|
||||
expect(result.tables?.[0].indexes).toHaveLength(1);
|
||||
expect(result.tables?.[0].indexes[0].id).toBe('source-index-1');
|
||||
expect(result.tables?.[0].indexes[0].fieldIds).toEqual([
|
||||
'source-field-1',
|
||||
]); // Should update field reference
|
||||
expect(result.tables?.[0].indexes[0].unique).toBe(false); // Should keep target's unique setting
|
||||
});
|
||||
});
|
||||
|
||||
describe('relationship ID mapping', () => {
|
||||
it('should preserve relationship IDs and update table/field references', () => {
|
||||
const sourceDiagram = createBaseDiagram({
|
||||
tables: [
|
||||
createTable({
|
||||
id: 'source-table-1',
|
||||
name: 'users',
|
||||
schema: 'public',
|
||||
fields: [
|
||||
createField({
|
||||
id: 'source-field-1',
|
||||
name: 'id',
|
||||
type: { id: 'integer', name: 'integer' },
|
||||
primaryKey: true,
|
||||
unique: true,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
createTable({
|
||||
id: 'source-table-2',
|
||||
name: 'posts',
|
||||
schema: 'public',
|
||||
fields: [
|
||||
createField({
|
||||
id: 'source-field-2',
|
||||
name: 'user_id',
|
||||
type: { id: 'integer', name: 'integer' },
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
relationships: [
|
||||
createRelationship({
|
||||
id: 'source-rel-1',
|
||||
name: 'fk_posts_users',
|
||||
sourceTableId: 'source-table-2',
|
||||
sourceFieldId: 'source-field-2',
|
||||
targetTableId: 'source-table-1',
|
||||
targetFieldId: 'source-field-1',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const targetDiagram = createBaseDiagram({
|
||||
tables: [
|
||||
createTable({
|
||||
id: 'target-table-1',
|
||||
name: 'users',
|
||||
schema: 'public',
|
||||
x: 100,
|
||||
y: 100,
|
||||
color: '#ff0000',
|
||||
fields: [
|
||||
createField({
|
||||
id: 'target-field-1',
|
||||
name: 'id',
|
||||
type: { id: 'bigint', name: 'bigint' },
|
||||
primaryKey: true,
|
||||
unique: true,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
createTable({
|
||||
id: 'target-table-2',
|
||||
name: 'posts',
|
||||
schema: 'public',
|
||||
x: 200,
|
||||
y: 200,
|
||||
color: '#00ff00',
|
||||
fields: [
|
||||
createField({
|
||||
id: 'target-field-2',
|
||||
name: 'user_id',
|
||||
type: { id: 'bigint', name: 'bigint' },
|
||||
nullable: true,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
relationships: [
|
||||
createRelationship({
|
||||
id: 'target-rel-1',
|
||||
name: 'fk_posts_users',
|
||||
sourceTableId: 'target-table-2',
|
||||
sourceFieldId: 'target-field-2',
|
||||
targetTableId: 'target-table-1',
|
||||
targetFieldId: 'target-field-1',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const result = applyIds({ sourceDiagram, targetDiagram });
|
||||
|
||||
expect(result.relationships).toHaveLength(1);
|
||||
expect(result.relationships?.[0].id).toBe('source-rel-1');
|
||||
expect(result.relationships?.[0].sourceTableId).toBe(
|
||||
'source-table-2'
|
||||
);
|
||||
expect(result.relationships?.[0].sourceFieldId).toBe(
|
||||
'source-field-2'
|
||||
);
|
||||
expect(result.relationships?.[0].targetTableId).toBe(
|
||||
'source-table-1'
|
||||
);
|
||||
expect(result.relationships?.[0].targetFieldId).toBe(
|
||||
'source-field-1'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dependency ID mapping', () => {
|
||||
it('should preserve dependency IDs and update table references', () => {
|
||||
const sourceDiagram = createBaseDiagram({
|
||||
tables: [
|
||||
createTable({
|
||||
id: 'source-table-1',
|
||||
name: 'users',
|
||||
schema: 'public',
|
||||
}),
|
||||
createTable({
|
||||
id: 'source-table-2',
|
||||
name: 'user_view',
|
||||
schema: 'public',
|
||||
isView: true,
|
||||
}),
|
||||
],
|
||||
dependencies: [
|
||||
createDependency({
|
||||
id: 'source-dep-1',
|
||||
tableId: 'source-table-2',
|
||||
dependentTableId: 'source-table-1',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const targetDiagram = createBaseDiagram({
|
||||
tables: [
|
||||
createTable({
|
||||
id: 'target-table-1',
|
||||
name: 'users',
|
||||
schema: 'public',
|
||||
x: 100,
|
||||
y: 100,
|
||||
color: '#ff0000',
|
||||
}),
|
||||
createTable({
|
||||
id: 'target-table-2',
|
||||
name: 'user_view',
|
||||
schema: 'public',
|
||||
x: 200,
|
||||
y: 200,
|
||||
color: '#00ff00',
|
||||
isView: true,
|
||||
}),
|
||||
],
|
||||
dependencies: [
|
||||
createDependency({
|
||||
id: 'target-dep-1',
|
||||
tableId: 'target-table-2',
|
||||
dependentTableId: 'target-table-1',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const result = applyIds({ sourceDiagram, targetDiagram });
|
||||
|
||||
expect(result.dependencies).toHaveLength(1);
|
||||
expect(result.dependencies?.[0].id).toBe('source-dep-1');
|
||||
expect(result.dependencies?.[0].tableId).toBe('source-table-2');
|
||||
expect(result.dependencies?.[0].dependentTableId).toBe(
|
||||
'source-table-1'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('custom type ID mapping', () => {
|
||||
it('should preserve custom type IDs when types match by name and schema', () => {
|
||||
const sourceDiagram = createBaseDiagram({
|
||||
customTypes: [
|
||||
createCustomType({
|
||||
id: 'source-type-1',
|
||||
name: 'user_role',
|
||||
schema: 'public',
|
||||
values: ['admin', 'user', 'guest'],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const targetDiagram = createBaseDiagram({
|
||||
customTypes: [
|
||||
createCustomType({
|
||||
id: 'target-type-1',
|
||||
name: 'user_role',
|
||||
schema: 'public',
|
||||
values: ['admin', 'user', 'guest', 'moderator'],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const result = applyIds({ sourceDiagram, targetDiagram });
|
||||
|
||||
expect(result.customTypes).toHaveLength(1);
|
||||
expect(result.customTypes?.[0].id).toBe('source-type-1');
|
||||
expect(result.customTypes?.[0].values).toEqual([
|
||||
'admin',
|
||||
'user',
|
||||
'guest',
|
||||
'moderator',
|
||||
]); // Should keep target's values
|
||||
});
|
||||
});
|
||||
|
||||
describe('complex scenarios', () => {
|
||||
it('should handle partial matches correctly', () => {
|
||||
const sourceDiagram = createBaseDiagram({
|
||||
tables: [
|
||||
createTable({
|
||||
id: 'source-table-1',
|
||||
name: 'users',
|
||||
schema: 'public',
|
||||
fields: [
|
||||
createField({
|
||||
id: 'source-field-1',
|
||||
name: 'id',
|
||||
type: { id: 'integer', name: 'integer' },
|
||||
primaryKey: true,
|
||||
unique: true,
|
||||
}),
|
||||
createField({
|
||||
id: 'source-field-2',
|
||||
name: 'email',
|
||||
type: { id: 'varchar', name: 'varchar' },
|
||||
unique: true,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const targetDiagram = createBaseDiagram({
|
||||
tables: [
|
||||
createTable({
|
||||
id: 'target-table-1',
|
||||
name: 'users',
|
||||
schema: 'public',
|
||||
x: 100,
|
||||
y: 100,
|
||||
color: '#ff0000',
|
||||
fields: [
|
||||
createField({
|
||||
id: 'target-field-1',
|
||||
name: 'id',
|
||||
type: { id: 'bigint', name: 'bigint' },
|
||||
primaryKey: true,
|
||||
unique: true,
|
||||
}),
|
||||
createField({
|
||||
id: 'target-field-3',
|
||||
name: 'username',
|
||||
type: { id: 'varchar', name: 'varchar' },
|
||||
unique: true,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
createTable({
|
||||
id: 'target-table-2',
|
||||
name: 'posts',
|
||||
schema: 'public',
|
||||
x: 200,
|
||||
y: 200,
|
||||
color: '#00ff00',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const result = applyIds({ sourceDiagram, targetDiagram });
|
||||
|
||||
expect(result.tables).toHaveLength(2);
|
||||
expect(result.tables?.[0].id).toBe('source-table-1');
|
||||
expect(result.tables?.[0].fields).toHaveLength(2);
|
||||
expect(result.tables?.[0].fields[0].id).toBe('source-field-1'); // Matched field
|
||||
expect(result.tables?.[0].fields[1].id).toBe('target-field-3'); // Unmatched field keeps target ID
|
||||
expect(result.tables?.[1].id).toBe('target-table-2'); // Unmatched table keeps target ID
|
||||
});
|
||||
|
||||
it('should handle different schemas correctly', () => {
|
||||
const sourceDiagram = createBaseDiagram({
|
||||
databaseType: DatabaseType.POSTGRESQL,
|
||||
tables: [
|
||||
createTable({
|
||||
id: 'source-table-1',
|
||||
name: 'users',
|
||||
schema: 'auth',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const targetDiagram = createBaseDiagram({
|
||||
databaseType: DatabaseType.POSTGRESQL,
|
||||
tables: [
|
||||
createTable({
|
||||
id: 'target-table-1',
|
||||
name: 'users',
|
||||
schema: 'public',
|
||||
x: 100,
|
||||
y: 100,
|
||||
color: '#ff0000',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const result = applyIds({ sourceDiagram, targetDiagram });
|
||||
|
||||
expect(result.tables?.[0].id).toBe('target-table-1'); // Different schemas, no match
|
||||
});
|
||||
|
||||
it('should handle empty diagrams', () => {
|
||||
const sourceDiagram = createBaseDiagram();
|
||||
const targetDiagram = createBaseDiagram();
|
||||
|
||||
const result = applyIds({ sourceDiagram, targetDiagram });
|
||||
|
||||
expect(result).toEqual(targetDiagram);
|
||||
});
|
||||
|
||||
it('should return target diagram unchanged when source has no matching entities', () => {
|
||||
const sourceDiagram = createBaseDiagram({
|
||||
tables: [
|
||||
createTable({
|
||||
id: 'source-table-1',
|
||||
name: 'products',
|
||||
schema: 'inventory',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const targetDiagram = createBaseDiagram({
|
||||
tables: [
|
||||
createTable({
|
||||
id: 'target-table-1',
|
||||
name: 'users',
|
||||
schema: 'public',
|
||||
x: 100,
|
||||
y: 100,
|
||||
color: '#ff0000',
|
||||
fields: [
|
||||
createField({
|
||||
id: 'target-field-1',
|
||||
name: 'id',
|
||||
type: { id: 'integer', name: 'integer' },
|
||||
primaryKey: true,
|
||||
unique: true,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const result = applyIds({ sourceDiagram, targetDiagram });
|
||||
|
||||
// Should keep all target IDs since nothing matches
|
||||
expect(result.tables?.[0].id).toBe('target-table-1');
|
||||
expect(result.tables?.[0].fields[0].id).toBe('target-field-1');
|
||||
expect(result.tables?.[0].name).toBe('users');
|
||||
expect(result.tables?.[0].schema).toBe('public');
|
||||
});
|
||||
});
|
||||
});
|
||||
328
src/lib/utils/apply-ids.ts
Normal file
328
src/lib/utils/apply-ids.ts
Normal file
@@ -0,0 +1,328 @@
|
||||
import { defaultSchemas } from '../data/default-schemas';
|
||||
import type { DBCustomType, DBTable, Diagram } from '../domain';
|
||||
|
||||
const createTableKey = ({
|
||||
table,
|
||||
defaultSchema,
|
||||
}: {
|
||||
table: DBTable;
|
||||
defaultSchema?: string;
|
||||
}) => {
|
||||
return `${table.schema ?? defaultSchema ?? ''}::${table.name}`;
|
||||
};
|
||||
|
||||
const createFieldKey = ({
|
||||
table,
|
||||
fieldName,
|
||||
defaultSchema,
|
||||
}: {
|
||||
table: DBTable;
|
||||
fieldName: string;
|
||||
defaultSchema?: string;
|
||||
}) => {
|
||||
return `${table.schema ?? defaultSchema ?? ''}::${table.name}::${fieldName}`;
|
||||
};
|
||||
|
||||
const createIndexKey = ({
|
||||
table,
|
||||
indexName,
|
||||
defaultSchema,
|
||||
}: {
|
||||
table: DBTable;
|
||||
indexName: string;
|
||||
defaultSchema?: string;
|
||||
}) => {
|
||||
return `${table.schema ?? defaultSchema ?? ''}::${table.name}::${indexName}`;
|
||||
};
|
||||
|
||||
const createRelationshipKey = ({
|
||||
relationshipName,
|
||||
defaultSchema,
|
||||
}: {
|
||||
relationshipName: string;
|
||||
defaultSchema?: string;
|
||||
}) => {
|
||||
return `${defaultSchema ?? ''}::${relationshipName}`;
|
||||
};
|
||||
|
||||
const createDependencyKey = ({
|
||||
table,
|
||||
dependentTable,
|
||||
defaultSchema,
|
||||
}: {
|
||||
table: DBTable;
|
||||
dependentTable: DBTable;
|
||||
defaultSchema?: string;
|
||||
}) => {
|
||||
return `${table.schema ?? defaultSchema ?? ''}::${table.name}::${dependentTable.schema ?? defaultSchema ?? ''}::${dependentTable.name}`;
|
||||
};
|
||||
|
||||
const createCustomTypeKey = ({
|
||||
customType,
|
||||
defaultSchema,
|
||||
}: {
|
||||
customType: DBCustomType;
|
||||
defaultSchema?: string;
|
||||
}) => {
|
||||
return `${customType.schema ?? defaultSchema ?? ''}::${customType.name}`;
|
||||
};
|
||||
|
||||
export const applyIds = ({
|
||||
sourceDiagram,
|
||||
targetDiagram,
|
||||
}: {
|
||||
sourceDiagram: Diagram;
|
||||
targetDiagram: Diagram;
|
||||
}): Diagram => {
|
||||
// Create a mapping of old IDs to new IDs
|
||||
const tablesIdMapping = new Map<string, string>();
|
||||
const fieldsIdMapping = new Map<string, string>();
|
||||
const indexesIdMapping = new Map<string, string>();
|
||||
const relationshipsIdMapping = new Map<string, string>();
|
||||
const dependenciesIdMapping = new Map<string, string>();
|
||||
const customTypesIdMapping = new Map<string, string>();
|
||||
|
||||
const sourceDefaultSchema = defaultSchemas[sourceDiagram.databaseType];
|
||||
const targetDefaultSchema = defaultSchemas[targetDiagram.databaseType];
|
||||
|
||||
// build idMapping
|
||||
sourceDiagram?.tables?.forEach((sourceTable) => {
|
||||
const sourceKey = createTableKey({
|
||||
table: sourceTable,
|
||||
defaultSchema: sourceDefaultSchema,
|
||||
});
|
||||
tablesIdMapping.set(sourceKey, sourceTable.id);
|
||||
|
||||
sourceTable.fields.forEach((field) => {
|
||||
const fieldKey = createFieldKey({
|
||||
table: sourceTable,
|
||||
fieldName: field.name,
|
||||
defaultSchema: sourceDefaultSchema,
|
||||
});
|
||||
fieldsIdMapping.set(fieldKey, field.id);
|
||||
});
|
||||
|
||||
sourceTable.indexes.forEach((index) => {
|
||||
const indexKey = createIndexKey({
|
||||
table: sourceTable,
|
||||
indexName: index.name,
|
||||
defaultSchema: sourceDefaultSchema,
|
||||
});
|
||||
indexesIdMapping.set(indexKey, index.id);
|
||||
});
|
||||
});
|
||||
|
||||
sourceDiagram.relationships?.forEach((relationship) => {
|
||||
const relationshipKey = createRelationshipKey({
|
||||
relationshipName: relationship.name,
|
||||
defaultSchema: sourceDefaultSchema,
|
||||
});
|
||||
relationshipsIdMapping.set(relationshipKey, relationship.id);
|
||||
});
|
||||
|
||||
sourceDiagram.dependencies?.forEach((dependency) => {
|
||||
const table = sourceDiagram.tables?.find(
|
||||
(t) => t.id === dependency.tableId
|
||||
);
|
||||
const dependentTable = sourceDiagram.tables?.find(
|
||||
(t) => t.id === dependency.dependentTableId
|
||||
);
|
||||
|
||||
if (!table || !dependentTable) return;
|
||||
|
||||
const dependencyKey = createDependencyKey({
|
||||
table,
|
||||
dependentTable,
|
||||
defaultSchema: sourceDefaultSchema,
|
||||
});
|
||||
|
||||
dependenciesIdMapping.set(dependencyKey, dependency.id);
|
||||
});
|
||||
|
||||
sourceDiagram.customTypes?.forEach((customType) => {
|
||||
const customTypeKey = createCustomTypeKey({
|
||||
customType,
|
||||
defaultSchema: sourceDefaultSchema,
|
||||
});
|
||||
customTypesIdMapping.set(customTypeKey, customType.id);
|
||||
});
|
||||
|
||||
// Map current ID -> new ID for target diagram entities
|
||||
const targetTableIdMapping = new Map<string, string>();
|
||||
const targetFieldIdMapping = new Map<string, string>();
|
||||
const targetIndexIdMapping = new Map<string, string>();
|
||||
const targetRelationshipIdMapping = new Map<string, string>();
|
||||
const targetDependencyIdMapping = new Map<string, string>();
|
||||
const targetCustomTypeIdMapping = new Map<string, string>();
|
||||
|
||||
targetDiagram?.tables?.forEach((targetTable) => {
|
||||
const targetKey = createTableKey({
|
||||
table: targetTable,
|
||||
defaultSchema: targetDefaultSchema,
|
||||
});
|
||||
const newId = tablesIdMapping.get(targetKey);
|
||||
if (newId) {
|
||||
targetTableIdMapping.set(targetTable.id, newId);
|
||||
}
|
||||
|
||||
targetTable.fields.forEach((field) => {
|
||||
const fieldKey = createFieldKey({
|
||||
table: targetTable,
|
||||
fieldName: field.name,
|
||||
defaultSchema: targetDefaultSchema,
|
||||
});
|
||||
const newFieldId = fieldsIdMapping.get(fieldKey);
|
||||
if (newFieldId) {
|
||||
targetFieldIdMapping.set(field.id, newFieldId);
|
||||
}
|
||||
});
|
||||
|
||||
targetTable.indexes.forEach((index) => {
|
||||
const indexKey = createIndexKey({
|
||||
table: targetTable,
|
||||
indexName: index.name,
|
||||
defaultSchema: targetDefaultSchema,
|
||||
});
|
||||
const newIndexId = indexesIdMapping.get(indexKey);
|
||||
if (newIndexId) {
|
||||
targetIndexIdMapping.set(index.id, newIndexId);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
targetDiagram.relationships?.forEach((relationship) => {
|
||||
const relationshipKey = createRelationshipKey({
|
||||
relationshipName: relationship.name,
|
||||
defaultSchema: targetDefaultSchema,
|
||||
});
|
||||
const newId = relationshipsIdMapping.get(relationshipKey);
|
||||
if (newId) {
|
||||
targetRelationshipIdMapping.set(relationship.id, newId);
|
||||
}
|
||||
});
|
||||
|
||||
targetDiagram.dependencies?.forEach((dependency) => {
|
||||
const table = targetDiagram.tables?.find(
|
||||
(t) => t.id === dependency.tableId
|
||||
);
|
||||
const dependentTable = targetDiagram.tables?.find(
|
||||
(t) => t.id === dependency.dependentTableId
|
||||
);
|
||||
|
||||
if (!table || !dependentTable) return;
|
||||
|
||||
const dependencyKey = createDependencyKey({
|
||||
table,
|
||||
dependentTable,
|
||||
defaultSchema: targetDefaultSchema,
|
||||
});
|
||||
|
||||
const newId = dependenciesIdMapping.get(dependencyKey);
|
||||
if (newId) {
|
||||
targetDependencyIdMapping.set(dependency.id, newId);
|
||||
}
|
||||
});
|
||||
|
||||
targetDiagram.customTypes?.forEach((customType) => {
|
||||
const customTypeKey = createCustomTypeKey({
|
||||
customType,
|
||||
defaultSchema: targetDefaultSchema,
|
||||
});
|
||||
const newId = customTypesIdMapping.get(customTypeKey);
|
||||
if (newId) {
|
||||
targetCustomTypeIdMapping.set(customType.id, newId);
|
||||
}
|
||||
});
|
||||
|
||||
// Apply the ID mappings to create the final diagram
|
||||
const result: Diagram = {
|
||||
...targetDiagram,
|
||||
tables: targetDiagram.tables?.map((table) => {
|
||||
const newTableId = targetTableIdMapping.get(table.id) ?? table.id;
|
||||
|
||||
return {
|
||||
...table,
|
||||
id: newTableId,
|
||||
fields: table.fields.map((field) => {
|
||||
const newFieldId =
|
||||
targetFieldIdMapping.get(field.id) ?? field.id;
|
||||
return {
|
||||
...field,
|
||||
id: newFieldId,
|
||||
};
|
||||
}),
|
||||
indexes: table.indexes.map((index) => {
|
||||
const newIndexId =
|
||||
targetIndexIdMapping.get(index.id) ?? index.id;
|
||||
|
||||
// Update field IDs in index
|
||||
const updatedFieldIds = index.fieldIds.map((fieldId) => {
|
||||
return targetFieldIdMapping.get(fieldId) ?? fieldId;
|
||||
});
|
||||
|
||||
return {
|
||||
...index,
|
||||
id: newIndexId,
|
||||
fieldIds: updatedFieldIds,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}),
|
||||
relationships: targetDiagram.relationships?.map((relationship) => {
|
||||
const newRelationshipId =
|
||||
targetRelationshipIdMapping.get(relationship.id) ??
|
||||
relationship.id;
|
||||
|
||||
// Update table and field IDs in relationships
|
||||
const newSourceTableId =
|
||||
targetTableIdMapping.get(relationship.sourceTableId) ??
|
||||
relationship.sourceTableId;
|
||||
const newTargetTableId =
|
||||
targetTableIdMapping.get(relationship.targetTableId) ??
|
||||
relationship.targetTableId;
|
||||
const newSourceFieldId =
|
||||
targetFieldIdMapping.get(relationship.sourceFieldId) ??
|
||||
relationship.sourceFieldId;
|
||||
const newTargetFieldId =
|
||||
targetFieldIdMapping.get(relationship.targetFieldId) ??
|
||||
relationship.targetFieldId;
|
||||
|
||||
return {
|
||||
...relationship,
|
||||
id: newRelationshipId,
|
||||
sourceTableId: newSourceTableId,
|
||||
targetTableId: newTargetTableId,
|
||||
sourceFieldId: newSourceFieldId,
|
||||
targetFieldId: newTargetFieldId,
|
||||
};
|
||||
}),
|
||||
dependencies: targetDiagram.dependencies?.map((dependency) => {
|
||||
const newDependencyId =
|
||||
targetDependencyIdMapping.get(dependency.id) ?? dependency.id;
|
||||
const newTableId =
|
||||
targetTableIdMapping.get(dependency.tableId) ??
|
||||
dependency.tableId;
|
||||
const newDependentTableId =
|
||||
targetTableIdMapping.get(dependency.dependentTableId) ??
|
||||
dependency.dependentTableId;
|
||||
|
||||
return {
|
||||
...dependency,
|
||||
id: newDependencyId,
|
||||
tableId: newTableId,
|
||||
dependentTableId: newDependentTableId,
|
||||
};
|
||||
}),
|
||||
customTypes: targetDiagram.customTypes?.map((customType) => {
|
||||
const newCustomTypeId =
|
||||
targetCustomTypeIdMapping.get(customType.id) ?? customType.id;
|
||||
|
||||
return {
|
||||
...customType,
|
||||
id: newCustomTypeId,
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
return result;
|
||||
};
|
||||
Reference in New Issue
Block a user