mirror of
https://github.com/chartdb/chartdb.git
synced 2026-05-05 01:09:55 -05:00
fix(dbml export): fix handle tables with same name under different schemas (#807)
This commit is contained in:
@@ -657,6 +657,87 @@ describe('DBML Export - Issue Fixes', () => {
|
||||
expect(result.standardDbml).not.toContain('duplicate_id');
|
||||
});
|
||||
|
||||
it('should correctly handle categories tables with public and public_2 schemas', () => {
|
||||
const diagram: Diagram = {
|
||||
id: 'test-diagram',
|
||||
name: 'Test',
|
||||
databaseType: DatabaseType.POSTGRESQL,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
|
||||
tables: [
|
||||
{
|
||||
id: 'ifi3bjrzp9mnml0l0cfhn753b',
|
||||
name: 'table_1',
|
||||
x: 32.625000000000085,
|
||||
y: 169.125,
|
||||
fields: [
|
||||
{
|
||||
id: 'agxg8ahs72urfqlprmt9dilxq',
|
||||
name: 'id',
|
||||
type: { id: 'bigint', name: 'bigint' },
|
||||
unique: true,
|
||||
nullable: false,
|
||||
primaryKey: true,
|
||||
createdAt: 1753793771079,
|
||||
},
|
||||
],
|
||||
indexes: [],
|
||||
color: '#8eb7ff',
|
||||
createdAt: 1753793771079,
|
||||
isView: false,
|
||||
order: 0,
|
||||
schema: 'public_2',
|
||||
parentAreaId: null,
|
||||
},
|
||||
{
|
||||
id: '3htgpyhl8elxx6jczuhbpjtla',
|
||||
name: 'table_1',
|
||||
x: -405.99999999999983,
|
||||
y: -155.24999999999997,
|
||||
fields: [
|
||||
{
|
||||
id: 'xxefc0h5dje2a183qdj6p6rzz',
|
||||
name: 'id',
|
||||
type: { id: 'bigint', name: 'bigint' },
|
||||
unique: true,
|
||||
nullable: false,
|
||||
primaryKey: true,
|
||||
createdAt: 1753793805822,
|
||||
},
|
||||
],
|
||||
indexes: [],
|
||||
color: '#4dee8a',
|
||||
createdAt: 1753793805822,
|
||||
isView: false,
|
||||
order: 1,
|
||||
schema: 'public',
|
||||
parentAreaId: null,
|
||||
},
|
||||
],
|
||||
dependencies: [],
|
||||
areas: [],
|
||||
customTypes: [],
|
||||
};
|
||||
|
||||
const result = generateDBMLFromDiagram(diagram);
|
||||
|
||||
// Should have both tables with correct schemas
|
||||
expect(result.standardDbml).toContain('Table "public"."table_1"');
|
||||
expect(result.standardDbml).toContain('Table "public_2"."table_1"');
|
||||
|
||||
// Should not have both tables with the same schema
|
||||
const publicMatches = result.standardDbml.match(
|
||||
/Table "public"."table_1" \{/g
|
||||
);
|
||||
const public2Matches = result.standardDbml.match(
|
||||
/Table "public_2"."table_1" \{/g
|
||||
);
|
||||
|
||||
expect(publicMatches).toHaveLength(1);
|
||||
expect(public2Matches).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should only remove tables with both same schema AND same name', () => {
|
||||
const diagram: Diagram = {
|
||||
id: 'test-diagram',
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { DBTable } from '@/lib/domain/db-table';
|
||||
import { type DBField } from '@/lib/domain/db-field';
|
||||
import type { DBCustomType } from '@/lib/domain/db-custom-type';
|
||||
import { DBCustomTypeKind } from '@/lib/domain/db-custom-type';
|
||||
import { defaultSchemas } from '@/lib/data/default-schemas';
|
||||
|
||||
// Use DBCustomType for generating Enum DBML
|
||||
const generateEnumsDBML = (customTypes: DBCustomType[] | undefined): string => {
|
||||
@@ -485,45 +486,112 @@ const fixTableBracketSyntax = (dbml: string): string => {
|
||||
const restoreTableSchemas = (dbml: string, diagram: Diagram): string => {
|
||||
if (!diagram.tables) return dbml;
|
||||
|
||||
// Group tables by name to handle duplicates
|
||||
const tablesByName = new Map<
|
||||
string,
|
||||
Array<{ table: (typeof diagram.tables)[0]; index: number }>
|
||||
>();
|
||||
diagram.tables.forEach((table, index) => {
|
||||
const existing = tablesByName.get(table.name) || [];
|
||||
existing.push({ table, index });
|
||||
tablesByName.set(table.name, existing);
|
||||
});
|
||||
|
||||
let result = dbml;
|
||||
|
||||
// For each table with a schema, restore it in the DBML
|
||||
diagram.tables.forEach((table) => {
|
||||
if (table.schema) {
|
||||
// Match table definition without schema (e.g., Table "users" {)
|
||||
const tablePattern = new RegExp(
|
||||
`Table\\s+"${table.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"\\s*{`,
|
||||
'g'
|
||||
);
|
||||
const schemaTableName = `Table "${table.schema}"."${table.name}" {`;
|
||||
result = result.replace(tablePattern, schemaTableName);
|
||||
// Process each group of tables with the same name
|
||||
tablesByName.forEach((tablesGroup, tableName) => {
|
||||
if (tablesGroup.length === 1) {
|
||||
// Single table with this name - simple case
|
||||
const table = tablesGroup[0].table;
|
||||
if (table.schema) {
|
||||
// Match table definition without schema (e.g., Table "users" {)
|
||||
const tablePattern = new RegExp(
|
||||
`Table\\s+"${table.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"\\s*{`,
|
||||
'g'
|
||||
);
|
||||
const schemaTableName = `Table "${table.schema}"."${table.name}" {`;
|
||||
result = result.replace(tablePattern, schemaTableName);
|
||||
|
||||
// Update references in Ref statements more carefully
|
||||
// Match patterns like: Ref "name":"tablename"."field" or < "tablename"."field"
|
||||
const escapedTableName = table.name.replace(
|
||||
// Update references in Ref statements
|
||||
const escapedTableName = table.name.replace(
|
||||
/[.*+?^${}()|[\]\\]/g,
|
||||
'\\$&'
|
||||
);
|
||||
|
||||
// Pattern 1: In Ref definitions - :"tablename"."field"
|
||||
const refDefPattern = new RegExp(
|
||||
`(Ref\\s+"[^"]+")\\s*:\\s*"${escapedTableName}"\\."([^"]+)"`,
|
||||
'g'
|
||||
);
|
||||
result = result.replace(
|
||||
refDefPattern,
|
||||
`$1:"${table.schema}"."${table.name}"."$2"`
|
||||
);
|
||||
|
||||
// Pattern 2: In Ref targets - [<>] "tablename"."field"
|
||||
const refTargetPattern = new RegExp(
|
||||
`([<>])\\s*"${escapedTableName}"\\."([^"]+)"`,
|
||||
'g'
|
||||
);
|
||||
result = result.replace(
|
||||
refTargetPattern,
|
||||
`$1 "${table.schema}"."${table.name}"."$2"`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Multiple tables with the same name - need to be more careful
|
||||
const defaultSchema = defaultSchemas[diagram.databaseType];
|
||||
|
||||
// Separate tables by whether they have the default schema or not
|
||||
const defaultSchemaTable = tablesGroup.find(
|
||||
({ table }) => table.schema === defaultSchema
|
||||
);
|
||||
const nonDefaultSchemaTables = tablesGroup.filter(
|
||||
({ table }) => table.schema && table.schema !== defaultSchema
|
||||
);
|
||||
|
||||
// Find all table definitions for this name
|
||||
const escapedTableName = tableName.replace(
|
||||
/[.*+?^${}()|[\]\\]/g,
|
||||
'\\$&'
|
||||
);
|
||||
|
||||
// Pattern 1: In Ref definitions - :"tablename"."field"
|
||||
const refDefPattern = new RegExp(
|
||||
`(Ref\\s+"[^"]+")\\s*:\\s*"${escapedTableName}"\\."([^"]+)"`,
|
||||
// First, handle tables that already have schema in DBML
|
||||
const schemaTablePattern = new RegExp(
|
||||
`Table\\s+"[^"]+"\\.\\s*"${escapedTableName}"\\s*{`,
|
||||
'g'
|
||||
);
|
||||
result = result.replace(
|
||||
refDefPattern,
|
||||
`$1:"${table.schema}"."${table.name}"."$2"`
|
||||
result = result.replace(schemaTablePattern, (match) => {
|
||||
// This table already has a schema, keep it as is
|
||||
return match;
|
||||
});
|
||||
|
||||
// Then handle tables without schema in DBML
|
||||
const noSchemaTablePattern = new RegExp(
|
||||
`Table\\s+"${escapedTableName}"\\s*{`,
|
||||
'g'
|
||||
);
|
||||
|
||||
// Pattern 2: In Ref targets - [<>] "tablename"."field"
|
||||
const refTargetPattern = new RegExp(
|
||||
`([<>])\\s*"${escapedTableName}"\\."([^"]+)"`,
|
||||
'g'
|
||||
);
|
||||
result = result.replace(
|
||||
refTargetPattern,
|
||||
`$1 "${table.schema}"."${table.name}"."$2"`
|
||||
);
|
||||
let noSchemaMatchIndex = 0;
|
||||
result = result.replace(noSchemaTablePattern, (match) => {
|
||||
// If we have a table with the default schema and this is the first match without schema,
|
||||
// it should be the default schema table
|
||||
if (noSchemaMatchIndex === 0 && defaultSchemaTable) {
|
||||
noSchemaMatchIndex++;
|
||||
return `Table "${defaultSchema}"."${tableName}" {`;
|
||||
}
|
||||
// Otherwise, try to match with non-default schema tables
|
||||
const remainingNonDefault =
|
||||
nonDefaultSchemaTables[
|
||||
noSchemaMatchIndex - (defaultSchemaTable ? 1 : 0)
|
||||
];
|
||||
if (remainingNonDefault) {
|
||||
noSchemaMatchIndex++;
|
||||
return `Table "${remainingNonDefault.table.schema}"."${tableName}" {`;
|
||||
}
|
||||
return match;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user