mirror of
https://github.com/chartdb/chartdb.git
synced 2025-10-23 07:11:56 +00:00
fix(dbml): support spaces in names (#794)
This commit is contained in:
@@ -213,4 +213,139 @@ describe('DBML Export - Issue Fixes', () => {
|
||||
// The foreign key is on the users.tenant_id field, referencing service.tenant.id
|
||||
expect(result.inlineDbml).toContain('ref: < "service"."tenant"."id"');
|
||||
});
|
||||
|
||||
it('should wrap table and field names with spaces in quotes instead of replacing with underscores', () => {
|
||||
const diagram: Diagram = {
|
||||
id: 'test-diagram',
|
||||
name: 'Test',
|
||||
databaseType: DatabaseType.POSTGRESQL,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
tables: [
|
||||
{
|
||||
id: 'table1',
|
||||
name: 'user profile',
|
||||
x: 0,
|
||||
y: 0,
|
||||
fields: [
|
||||
{
|
||||
id: 'field1',
|
||||
name: 'user id',
|
||||
type: { id: 'bigint', name: 'bigint' },
|
||||
primaryKey: true,
|
||||
nullable: false,
|
||||
unique: false,
|
||||
collation: null,
|
||||
default: null,
|
||||
characterMaximumLength: null,
|
||||
createdAt: Date.now(),
|
||||
},
|
||||
{
|
||||
id: 'field2',
|
||||
name: 'full name',
|
||||
type: { id: 'varchar', name: 'varchar' },
|
||||
primaryKey: false,
|
||||
nullable: true,
|
||||
unique: false,
|
||||
collation: null,
|
||||
default: null,
|
||||
characterMaximumLength: '255',
|
||||
createdAt: Date.now(),
|
||||
},
|
||||
],
|
||||
indexes: [
|
||||
{
|
||||
id: 'idx1',
|
||||
name: 'idx user name',
|
||||
unique: false,
|
||||
fieldIds: ['field2'],
|
||||
createdAt: Date.now(),
|
||||
},
|
||||
],
|
||||
color: 'blue',
|
||||
isView: false,
|
||||
createdAt: Date.now(),
|
||||
},
|
||||
{
|
||||
id: 'table2',
|
||||
name: 'order details',
|
||||
x: 0,
|
||||
y: 0,
|
||||
fields: [
|
||||
{
|
||||
id: 'field3',
|
||||
name: 'order id',
|
||||
type: { id: 'bigint', name: 'bigint' },
|
||||
primaryKey: true,
|
||||
nullable: false,
|
||||
unique: false,
|
||||
collation: null,
|
||||
default: null,
|
||||
characterMaximumLength: null,
|
||||
createdAt: Date.now(),
|
||||
},
|
||||
{
|
||||
id: 'field4',
|
||||
name: 'user id',
|
||||
type: { id: 'bigint', name: 'bigint' },
|
||||
primaryKey: false,
|
||||
nullable: false,
|
||||
unique: false,
|
||||
collation: null,
|
||||
default: null,
|
||||
characterMaximumLength: null,
|
||||
createdAt: Date.now(),
|
||||
},
|
||||
],
|
||||
indexes: [],
|
||||
color: 'blue',
|
||||
isView: false,
|
||||
createdAt: Date.now(),
|
||||
},
|
||||
],
|
||||
relationships: [
|
||||
{
|
||||
id: 'rel1',
|
||||
name: 'fk order user',
|
||||
sourceTableId: 'table2',
|
||||
sourceFieldId: 'field4',
|
||||
targetTableId: 'table1',
|
||||
targetFieldId: 'field1',
|
||||
sourceCardinality: 'many',
|
||||
targetCardinality: 'one',
|
||||
createdAt: Date.now(),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = generateDBMLFromDiagram(diagram);
|
||||
|
||||
// Check that table names with spaces are wrapped in quotes
|
||||
expect(result.standardDbml).toContain('Table "user profile"');
|
||||
expect(result.standardDbml).toContain('Table "order details"');
|
||||
|
||||
// Check that field names with spaces are wrapped in quotes
|
||||
expect(result.standardDbml).toContain('"user id" bigint');
|
||||
expect(result.standardDbml).toContain('"full name" varchar(255)');
|
||||
expect(result.standardDbml).toContain('"order id" bigint');
|
||||
|
||||
// Check that index names with spaces are wrapped in quotes (in DBML format)
|
||||
expect(result.standardDbml).toContain('[name: "idx user name"]');
|
||||
|
||||
// Check that relationship names with spaces are replaced with underscores in constraint names
|
||||
expect(result.standardDbml).toContain('Ref "fk_0_fk_order_user"');
|
||||
|
||||
// Verify that spaces are NOT replaced with underscores
|
||||
expect(result.standardDbml).not.toContain('user_profile');
|
||||
expect(result.standardDbml).not.toContain('user_id');
|
||||
expect(result.standardDbml).not.toContain('full_name');
|
||||
expect(result.standardDbml).not.toContain('order_details');
|
||||
expect(result.standardDbml).not.toContain('order_id');
|
||||
expect(result.standardDbml).not.toContain('idx_user_name');
|
||||
|
||||
// Check inline DBML as well - the ref is on the order details table
|
||||
expect(result.inlineDbml).toContain(
|
||||
'"user id" bigint [not null, ref: < "user profile"."user id"]'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@@ -603,26 +603,40 @@ export function generateDBMLFromDiagram(diagram: Diagram): DBMLExportResult {
|
||||
|
||||
const processTable = (table: DBTable) => {
|
||||
const originalName = table.name;
|
||||
let safeTableName = originalName.replace(/[^\w]/g, '_');
|
||||
let safeTableName = originalName;
|
||||
|
||||
// If name contains spaces or special characters, wrap in quotes
|
||||
if (/[^\w]/.test(originalName)) {
|
||||
safeTableName = `"${originalName.replace(/"/g, '\\"')}"`;
|
||||
}
|
||||
|
||||
// Rename table if SQL keyword (PostgreSQL only)
|
||||
if (shouldRenameKeywords && isSQLKeyword(safeTableName)) {
|
||||
const newName = `${safeTableName}_table`;
|
||||
if (shouldRenameKeywords && isSQLKeyword(originalName)) {
|
||||
const newName = `${originalName}_table`;
|
||||
sqlRenamedTables.set(newName, originalName);
|
||||
safeTableName = newName;
|
||||
safeTableName = /[^\w]/.test(newName)
|
||||
? `"${newName.replace(/"/g, '\\"')}"`
|
||||
: newName;
|
||||
}
|
||||
|
||||
const fieldNameCounts = new Map<string, number>();
|
||||
const processedFields = table.fields.map((field) => {
|
||||
const originalSafeName = field.name.replace(/[^\w]/g, '_');
|
||||
let finalSafeName = originalSafeName;
|
||||
let finalSafeName = field.name;
|
||||
|
||||
// If field name contains spaces or special characters, wrap in quotes
|
||||
if (/[^\w]/.test(field.name)) {
|
||||
finalSafeName = `"${field.name.replace(/"/g, '\\"')}"`;
|
||||
}
|
||||
|
||||
// Handle duplicate field names
|
||||
const count = fieldNameCounts.get(originalSafeName) || 0;
|
||||
const count = fieldNameCounts.get(field.name) || 0;
|
||||
if (count > 0) {
|
||||
finalSafeName = `${originalSafeName}_${count + 1}`;
|
||||
const newName = `${field.name}_${count + 1}`;
|
||||
finalSafeName = /[^\w]/.test(newName)
|
||||
? `"${newName.replace(/"/g, '\\"')}"`
|
||||
: newName;
|
||||
}
|
||||
fieldNameCounts.set(originalSafeName, count + 1);
|
||||
fieldNameCounts.set(field.name, count + 1);
|
||||
|
||||
// Create sanitized field
|
||||
const sanitizedField: DBField = {
|
||||
@@ -632,14 +646,16 @@ export function generateDBMLFromDiagram(diagram: Diagram): DBMLExportResult {
|
||||
delete sanitizedField.comments;
|
||||
|
||||
// Rename field if SQL keyword (PostgreSQL only)
|
||||
if (shouldRenameKeywords && isSQLKeyword(finalSafeName)) {
|
||||
const newFieldName = `${finalSafeName}_field`;
|
||||
if (shouldRenameKeywords && isSQLKeyword(field.name)) {
|
||||
const newFieldName = `${field.name}_field`;
|
||||
fieldRenames.push({
|
||||
table: safeTableName,
|
||||
originalName: finalSafeName,
|
||||
originalName: field.name,
|
||||
newName: newFieldName,
|
||||
});
|
||||
sanitizedField.name = newFieldName;
|
||||
sanitizedField.name = /[^\w]/.test(newFieldName)
|
||||
? `"${newFieldName.replace(/"/g, '\\"')}"`
|
||||
: newFieldName;
|
||||
}
|
||||
|
||||
return sanitizedField;
|
||||
@@ -652,7 +668,9 @@ export function generateDBMLFromDiagram(diagram: Diagram): DBMLExportResult {
|
||||
indexes: (table.indexes || []).map((index) => ({
|
||||
...index,
|
||||
name: index.name
|
||||
? index.name.replace(/[^\w]/g, '_')
|
||||
? /[^\w]/.test(index.name)
|
||||
? `"${index.name.replace(/"/g, '\\"')}"`
|
||||
: index.name
|
||||
: `idx_${Math.random().toString(36).substring(2, 8)}`,
|
||||
})),
|
||||
};
|
||||
@@ -662,10 +680,15 @@ export function generateDBMLFromDiagram(diagram: Diagram): DBMLExportResult {
|
||||
...cleanDiagram,
|
||||
tables: cleanDiagram.tables?.map(processTable) ?? [],
|
||||
relationships:
|
||||
cleanDiagram.relationships?.map((rel, index) => ({
|
||||
...rel,
|
||||
name: `fk_${index}_${rel.name ? rel.name.replace(/[^\w]/g, '_') : Math.random().toString(36).substring(2, 8)}`,
|
||||
})) ?? [],
|
||||
cleanDiagram.relationships?.map((rel, index) => {
|
||||
const safeName = rel.name
|
||||
? rel.name.replace(/[^\w]/g, '_')
|
||||
: Math.random().toString(36).substring(2, 8);
|
||||
return {
|
||||
...rel,
|
||||
name: `fk_${index}_${safeName}`,
|
||||
};
|
||||
}) ?? [],
|
||||
} as Diagram);
|
||||
|
||||
let standard = '';
|
||||
|
Reference in New Issue
Block a user