mirror of
https://github.com/chartdb/chartdb.git
synced 2025-11-06 15:03:22 +00:00
fix(dbml-editor): for some cases that the dbml had issues (#739)
This commit is contained in:
@@ -217,7 +217,10 @@ export const exportBaseSQL = ({
|
|||||||
sqlScript += ` ${field.name} ${typeName}`;
|
sqlScript += ` ${field.name} ${typeName}`;
|
||||||
|
|
||||||
// Add size for character types
|
// Add size for character types
|
||||||
if (field.characterMaximumLength) {
|
if (
|
||||||
|
field.characterMaximumLength &&
|
||||||
|
parseInt(field.characterMaximumLength) > 0
|
||||||
|
) {
|
||||||
sqlScript += `(${field.characterMaximumLength})`;
|
sqlScript += `(${field.characterMaximumLength})`;
|
||||||
} else if (field.type.name.toLowerCase().includes('varchar')) {
|
} else if (field.type.name.toLowerCase().includes('varchar')) {
|
||||||
// Keep varchar sizing, but don't apply to TEXT (previously enum)
|
// Keep varchar sizing, but don't apply to TEXT (previously enum)
|
||||||
|
|||||||
@@ -184,6 +184,22 @@ const sanitizeSQLforDBML = (sql: string): string => {
|
|||||||
});
|
});
|
||||||
sanitized = processedLines.join('\n');
|
sanitized = processedLines.join('\n');
|
||||||
|
|
||||||
|
// Fix PostgreSQL type casting syntax that the DBML parser doesn't understand
|
||||||
|
sanitized = sanitized.replace(/::regclass/g, '');
|
||||||
|
sanitized = sanitized.replace(/: :regclass/g, ''); // Fix corrupted version
|
||||||
|
|
||||||
|
// Fix duplicate columns in index definitions
|
||||||
|
sanitized = sanitized.replace(
|
||||||
|
/CREATE\s+(?:UNIQUE\s+)?INDEX\s+\S+\s+ON\s+\S+\s*\(([^)]+)\)/gi,
|
||||||
|
(match, columnList) => {
|
||||||
|
const columns = columnList
|
||||||
|
.split(',')
|
||||||
|
.map((col: string) => col.trim());
|
||||||
|
const uniqueColumns = [...new Set(columns)]; // Remove duplicates
|
||||||
|
return match.replace(columnList, uniqueColumns.join(', '));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Replace any remaining problematic characters
|
// Replace any remaining problematic characters
|
||||||
sanitized = sanitized.replace(/\?\?/g, '__');
|
sanitized = sanitized.replace(/\?\?/g, '__');
|
||||||
|
|
||||||
@@ -316,6 +332,71 @@ const isSQLKeyword = (name: string): boolean => {
|
|||||||
return keywords.has(name.toUpperCase());
|
return keywords.has(name.toUpperCase());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Function to remove duplicate relationships from the diagram
|
||||||
|
const deduplicateRelationships = (diagram: Diagram): Diagram => {
|
||||||
|
if (!diagram.relationships) return diagram;
|
||||||
|
|
||||||
|
const seenRelationships = new Set<string>();
|
||||||
|
const uniqueRelationships = diagram.relationships.filter((rel) => {
|
||||||
|
// Create a unique key based on the relationship endpoints
|
||||||
|
const relationshipKey = `${rel.sourceTableId}-${rel.sourceFieldId}->${rel.targetTableId}-${rel.targetFieldId}`;
|
||||||
|
|
||||||
|
if (seenRelationships.has(relationshipKey)) {
|
||||||
|
return false; // Skip duplicate
|
||||||
|
}
|
||||||
|
|
||||||
|
seenRelationships.add(relationshipKey);
|
||||||
|
return true; // Keep unique relationship
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...diagram,
|
||||||
|
relationships: uniqueRelationships,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to append comment statements for renamed tables and fields
|
||||||
|
const appendRenameComments = (
|
||||||
|
baseScript: string,
|
||||||
|
sqlRenamedTables: Map<string, string>,
|
||||||
|
fieldRenames: Array<{
|
||||||
|
table: string;
|
||||||
|
originalName: string;
|
||||||
|
newName: string;
|
||||||
|
}>,
|
||||||
|
finalDiagramForExport: Diagram
|
||||||
|
): string => {
|
||||||
|
let script = baseScript;
|
||||||
|
|
||||||
|
// Append COMMENTS for tables renamed due to SQL keywords
|
||||||
|
sqlRenamedTables.forEach((originalName, newName) => {
|
||||||
|
const escapedOriginal = originalName.replace(/'/g, "\\'");
|
||||||
|
// Find the table to get its schema
|
||||||
|
const table = finalDiagramForExport.tables?.find(
|
||||||
|
(t) => t.name === newName
|
||||||
|
);
|
||||||
|
const tableIdentifier = table?.schema
|
||||||
|
? `"${table.schema}"."${newName}"`
|
||||||
|
: `"${newName}"`;
|
||||||
|
script += `\nCOMMENT ON TABLE ${tableIdentifier} IS 'Original name was "${escapedOriginal}" (renamed due to SQL keyword conflict).';`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Append COMMENTS for fields renamed due to SQL keyword conflicts
|
||||||
|
fieldRenames.forEach(({ table, originalName, newName }) => {
|
||||||
|
const escapedOriginal = originalName.replace(/'/g, "\\'");
|
||||||
|
// Find the table to get its schema
|
||||||
|
const tableObj = finalDiagramForExport.tables?.find(
|
||||||
|
(t) => t.name === table
|
||||||
|
);
|
||||||
|
const tableIdentifier = tableObj?.schema
|
||||||
|
? `"${tableObj.schema}"."${table}"`
|
||||||
|
: `"${table}"`;
|
||||||
|
script += `\nCOMMENT ON COLUMN ${tableIdentifier}."${newName}" IS 'Original name was "${escapedOriginal}" (renamed due to SQL keyword conflict).';`;
|
||||||
|
});
|
||||||
|
|
||||||
|
return script;
|
||||||
|
};
|
||||||
|
|
||||||
// Fix DBML formatting to ensure consistent display of char and varchar types
|
// Fix DBML formatting to ensure consistent display of char and varchar types
|
||||||
const normalizeCharTypeFormat = (dbml: string): string => {
|
const normalizeCharTypeFormat = (dbml: string): string => {
|
||||||
// Replace "char (N)" with "char(N)" to match varchar's formatting
|
// Replace "char (N)" with "char(N)" to match varchar's formatting
|
||||||
@@ -402,23 +483,22 @@ export const TableDBML: React.FC<TableDBMLProps> = ({ filteredTables }) => {
|
|||||||
const cleanDiagram = fixProblematicFieldNames(filteredDiagram);
|
const cleanDiagram = fixProblematicFieldNames(filteredDiagram);
|
||||||
|
|
||||||
// --- Final sanitization and renaming pass ---
|
// --- Final sanitization and renaming pass ---
|
||||||
// Track tables renamed due to SQL keyword conflicts
|
const shouldRenameKeywords =
|
||||||
|
currentDiagram.databaseType === DatabaseType.POSTGRESQL ||
|
||||||
|
currentDiagram.databaseType === DatabaseType.SQLITE;
|
||||||
const sqlRenamedTables = new Map<string, string>();
|
const sqlRenamedTables = new Map<string, string>();
|
||||||
// Track fields renamed due to SQL keyword conflicts
|
|
||||||
const fieldRenames: Array<{
|
const fieldRenames: Array<{
|
||||||
table: string;
|
table: string;
|
||||||
originalName: string;
|
originalName: string;
|
||||||
newName: string;
|
newName: string;
|
||||||
}> = [];
|
}> = [];
|
||||||
const finalDiagramForExport: Diagram = {
|
|
||||||
...cleanDiagram,
|
const processTable = (table: DBTable) => {
|
||||||
tables:
|
|
||||||
cleanDiagram.tables?.map((table) => {
|
|
||||||
const originalName = table.name;
|
const originalName = table.name;
|
||||||
// Sanitize table name
|
|
||||||
let safeTableName = originalName.replace(/[^\w]/g, '_');
|
let safeTableName = originalName.replace(/[^\w]/g, '_');
|
||||||
// Rename if SQL keyword
|
|
||||||
if (isSQLKeyword(safeTableName)) {
|
// Rename table if SQL keyword (PostgreSQL only)
|
||||||
|
if (shouldRenameKeywords && isSQLKeyword(safeTableName)) {
|
||||||
const newName = `${safeTableName}_table`;
|
const newName = `${safeTableName}_table`;
|
||||||
sqlRenamedTables.set(newName, originalName);
|
sqlRenamedTables.set(newName, originalName);
|
||||||
safeTableName = newName;
|
safeTableName = newName;
|
||||||
@@ -426,28 +506,25 @@ export const TableDBML: React.FC<TableDBMLProps> = ({ filteredTables }) => {
|
|||||||
|
|
||||||
const fieldNameCounts = new Map<string, number>();
|
const fieldNameCounts = new Map<string, number>();
|
||||||
const processedFields = table.fields.map((field) => {
|
const processedFields = table.fields.map((field) => {
|
||||||
const originalSafeName = field.name.replace(
|
const originalSafeName = field.name.replace(/[^\w]/g, '_');
|
||||||
/[^\w]/g,
|
|
||||||
'_'
|
|
||||||
);
|
|
||||||
let finalSafeName = originalSafeName;
|
let finalSafeName = originalSafeName;
|
||||||
const count =
|
|
||||||
fieldNameCounts.get(originalSafeName) || 0;
|
|
||||||
|
|
||||||
|
// Handle duplicate field names
|
||||||
|
const count = fieldNameCounts.get(originalSafeName) || 0;
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
finalSafeName = `${originalSafeName}_${count + 1}`; // Rename duplicate
|
finalSafeName = `${originalSafeName}_${count + 1}`;
|
||||||
}
|
}
|
||||||
fieldNameCounts.set(originalSafeName, count + 1);
|
fieldNameCounts.set(originalSafeName, count + 1);
|
||||||
|
|
||||||
// Create a copy and remove comments
|
// Create sanitized field
|
||||||
const sanitizedField: DBField = {
|
const sanitizedField: DBField = {
|
||||||
...field,
|
...field,
|
||||||
name: finalSafeName,
|
name: finalSafeName,
|
||||||
};
|
};
|
||||||
delete sanitizedField.comments;
|
delete sanitizedField.comments;
|
||||||
|
|
||||||
// Rename if SQL keyword
|
// Rename field if SQL keyword (PostgreSQL only)
|
||||||
if (isSQLKeyword(finalSafeName)) {
|
if (shouldRenameKeywords && isSQLKeyword(finalSafeName)) {
|
||||||
const newFieldName = `${finalSafeName}_field`;
|
const newFieldName = `${finalSafeName}_field`;
|
||||||
fieldRenames.push({
|
fieldRenames.push({
|
||||||
table: safeTableName,
|
table: safeTableName,
|
||||||
@@ -456,13 +533,14 @@ export const TableDBML: React.FC<TableDBMLProps> = ({ filteredTables }) => {
|
|||||||
});
|
});
|
||||||
sanitizedField.name = newFieldName;
|
sanitizedField.name = newFieldName;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sanitizedField;
|
return sanitizedField;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...table,
|
...table,
|
||||||
name: safeTableName,
|
name: safeTableName,
|
||||||
fields: processedFields, // Use fields with renamed duplicates
|
fields: processedFields,
|
||||||
indexes: (table.indexes || []).map((index) => ({
|
indexes: (table.indexes || []).map((index) => ({
|
||||||
...index,
|
...index,
|
||||||
name: index.name
|
name: index.name
|
||||||
@@ -470,13 +548,17 @@ export const TableDBML: React.FC<TableDBMLProps> = ({ filteredTables }) => {
|
|||||||
: `idx_${Math.random().toString(36).substring(2, 8)}`,
|
: `idx_${Math.random().toString(36).substring(2, 8)}`,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
}) ?? [],
|
};
|
||||||
|
|
||||||
|
const finalDiagramForExport: Diagram = deduplicateRelationships({
|
||||||
|
...cleanDiagram,
|
||||||
|
tables: cleanDiagram.tables?.map(processTable) ?? [],
|
||||||
relationships:
|
relationships:
|
||||||
cleanDiagram.relationships?.map((rel, index) => ({
|
cleanDiagram.relationships?.map((rel, index) => ({
|
||||||
...rel,
|
...rel,
|
||||||
name: `fk_${index}_${rel.name ? rel.name.replace(/[^\w]/g, '_') : Math.random().toString(36).substring(2, 8)}`,
|
name: `fk_${index}_${rel.name ? rel.name.replace(/[^\w]/g, '_') : Math.random().toString(36).substring(2, 8)}`,
|
||||||
})) ?? [],
|
})) ?? [],
|
||||||
} as Diagram;
|
} as Diagram);
|
||||||
|
|
||||||
let standard = '';
|
let standard = '';
|
||||||
let inline = '';
|
let inline = '';
|
||||||
@@ -494,31 +576,15 @@ export const TableDBML: React.FC<TableDBMLProps> = ({ filteredTables }) => {
|
|||||||
|
|
||||||
baseScript = sanitizeSQLforDBML(baseScript);
|
baseScript = sanitizeSQLforDBML(baseScript);
|
||||||
|
|
||||||
// Append COMMENTS for tables renamed due to SQL keywords
|
// Append comments for renamed tables and fields (PostgreSQL only)
|
||||||
sqlRenamedTables.forEach((originalName, newName) => {
|
if (shouldRenameKeywords) {
|
||||||
const escapedOriginal = originalName.replace(/'/g, "\\'");
|
baseScript = appendRenameComments(
|
||||||
// Find the table to get its schema
|
baseScript,
|
||||||
const table = finalDiagramForExport.tables?.find(
|
sqlRenamedTables,
|
||||||
(t) => t.name === newName
|
fieldRenames,
|
||||||
|
finalDiagramForExport
|
||||||
);
|
);
|
||||||
const tableIdentifier = table?.schema
|
}
|
||||||
? `"${table.schema}"."${newName}"`
|
|
||||||
: `"${newName}"`;
|
|
||||||
baseScript += `\nCOMMENT ON TABLE ${tableIdentifier} IS 'Original name was "${escapedOriginal}" (renamed due to SQL keyword conflict).';`;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Append COMMENTS for fields renamed due to SQL keyword conflicts
|
|
||||||
fieldRenames.forEach(({ table, originalName, newName }) => {
|
|
||||||
const escapedOriginal = originalName.replace(/'/g, "\\'");
|
|
||||||
// Find the table to get its schema
|
|
||||||
const tableObj = finalDiagramForExport.tables?.find(
|
|
||||||
(t) => t.name === table
|
|
||||||
);
|
|
||||||
const tableIdentifier = tableObj?.schema
|
|
||||||
? `"${tableObj.schema}"."${table}"`
|
|
||||||
: `"${table}"`;
|
|
||||||
baseScript += `\nCOMMENT ON COLUMN ${tableIdentifier}."${newName}" IS 'Original name was "${escapedOriginal}" (renamed due to SQL keyword conflict).';`;
|
|
||||||
});
|
|
||||||
|
|
||||||
standard = normalizeCharTypeFormat(
|
standard = normalizeCharTypeFormat(
|
||||||
importer.import(
|
importer.import(
|
||||||
|
|||||||
Reference in New Issue
Block a user