diff --git a/src/lib/data/sql-export/export-sql-script.ts b/src/lib/data/sql-export/export-sql-script.ts index 4f149ef9..826b0f79 100644 --- a/src/lib/data/sql-export/export-sql-script.ts +++ b/src/lib/data/sql-export/export-sql-script.ts @@ -469,10 +469,16 @@ export const exportBaseSQL = ({ .join(', '); if (fieldNames) { - const indexName = + const rawIndexName = table.schema && !isDBMLFlow ? `${table.schema}_${index.name}` : index.name; + // Quote index name if it contains special characters + // For DBML flow, also quote if contains special characters + const needsQuoting = /[^a-zA-Z0-9_]/.test(rawIndexName); + const indexName = needsQuoting + ? `"${rawIndexName}"` + : rawIndexName; sqlScript += `CREATE ${index.unique ? 'UNIQUE ' : ''}INDEX ${indexName} ON ${tableName} (${fieldNames});\n`; } }); diff --git a/src/lib/dbml/dbml-export/dbml-export.ts b/src/lib/dbml/dbml-export/dbml-export.ts index 0c0a8935..4ee8d014 100644 --- a/src/lib/dbml/dbml-export/dbml-export.ts +++ b/src/lib/dbml/dbml-export/dbml-export.ts @@ -3,7 +3,6 @@ import { exportBaseSQL } from '@/lib/data/sql-export/export-sql-script'; import type { Diagram } from '@/lib/domain/diagram'; import { DatabaseType } from '@/lib/domain/database-type'; 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'; @@ -502,24 +501,6 @@ const convertToInlineRefs = (dbml: string): string => { return cleanedDbml; }; -// Function to check for DBML reserved keywords -const isDBMLKeyword = (name: string): boolean => { - const keywords = new Set([ - 'YES', - 'NO', - 'TRUE', - 'FALSE', - 'NULL', // DBML reserved keywords (boolean literals) - ]); - return keywords.has(name.toUpperCase()); -}; - -// Function to check for SQL keywords (add more if needed) -const isSQLKeyword = (name: string): boolean => { - const keywords = new Set(['CASE', 'ORDER', 'GROUP', 'FROM', 'TO', 'USER']); // Common SQL keywords - return keywords.has(name.toUpperCase()); -}; - // Function to remove duplicate relationships from the diagram const deduplicateRelationships = (diagram: Diagram): Diagram => { if (!diagram.relationships) return diagram; @@ -543,48 +524,6 @@ const deduplicateRelationships = (diagram: Diagram): Diagram => { }; }; -// Function to append comment statements for renamed tables and fields -const appendRenameComments = ( - baseScript: string, - sqlRenamedTables: Map, - 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 const normalizeCharTypeFormat = (dbml: string): string => { // Replace "char (N)" with "char(N)" to match varchar's formatting @@ -843,105 +782,33 @@ export function generateDBMLFromDiagram(diagram: Diagram): DBMLExportResult { // Sanitize field names ('from'/'to' in 'relation' table) const cleanDiagram = fixProblematicFieldNames(filteredDiagram); - // --- Final sanitization and renaming pass --- - // Only rename keywords for PostgreSQL/SQLite - // For other databases, we'll wrap problematic names in quotes instead - const shouldRenameKeywords = - diagram.databaseType === DatabaseType.POSTGRESQL || - diagram.databaseType === DatabaseType.SQLITE; - const sqlRenamedTables = new Map(); - const fieldRenames: Array<{ - table: string; - originalName: string; - newName: string; - }> = []; - + // Simplified processing - just handle duplicate field names const processTable = (table: DBTable) => { - const originalName = table.name; - let safeTableName = originalName; - - // If name contains spaces or special characters, wrap in quotes - if (/[^\w]/.test(originalName)) { - safeTableName = `"${originalName.replace(/"/g, '\\"')}"`; - } - - // Rename table if it's a keyword (PostgreSQL/SQLite only) - if ( - shouldRenameKeywords && - (isDBMLKeyword(originalName) || isSQLKeyword(originalName)) - ) { - const newName = `${originalName}_table`; - sqlRenamedTables.set(newName, originalName); - safeTableName = /[^\w]/.test(newName) - ? `"${newName.replace(/"/g, '\\"')}"` - : newName; - } - // For other databases, just quote DBML keywords - else if (!shouldRenameKeywords && isDBMLKeyword(originalName)) { - safeTableName = `"${originalName.replace(/"/g, '\\"')}"`; - } - const fieldNameCounts = new Map(); const processedFields = table.fields.map((field) => { - 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(field.name) || 0; if (count > 0) { const newName = `${field.name}_${count + 1}`; - finalSafeName = /[^\w]/.test(newName) - ? `"${newName.replace(/"/g, '\\"')}"` - : newName; + return { + ...field, + name: newName, + }; } fieldNameCounts.set(field.name, count + 1); - - // Create sanitized field - const sanitizedField: DBField = { - ...field, - name: finalSafeName, - }; - - // Rename field if it's a keyword (PostgreSQL/SQLite only) - if ( - shouldRenameKeywords && - (isDBMLKeyword(field.name) || isSQLKeyword(field.name)) - ) { - const newFieldName = `${field.name}_field`; - fieldRenames.push({ - table: safeTableName, - originalName: field.name, - newName: newFieldName, - }); - sanitizedField.name = /[^\w]/.test(newFieldName) - ? `"${newFieldName.replace(/"/g, '\\"')}"` - : newFieldName; - } - // For other databases, just quote DBML keywords - else if (!shouldRenameKeywords && isDBMLKeyword(field.name)) { - sanitizedField.name = `"${field.name.replace(/"/g, '\\"')}"`; - } - - return sanitizedField; + return field; }); return { ...table, - name: safeTableName, fields: processedFields, indexes: (table.indexes || []) .filter((index) => !index.isPrimaryKey) // Filter out PK indexes as they're handled separately .map((index) => ({ ...index, - name: index.name - ? /[^\w]/.test(index.name) - ? `"${index.name.replace(/"/g, '\\"')}"` - : index.name - : `idx_${Math.random().toString(36).substring(2, 8)}`, + name: + index.name || + `idx_${Math.random().toString(36).substring(2, 8)}`, })), }; }; @@ -979,19 +846,6 @@ export function generateDBMLFromDiagram(diagram: Diagram): DBMLExportResult { baseScript = sanitizeSQLforDBML(baseScript); - // Append comments for renamed tables and fields (PostgreSQL/SQLite only) - if ( - shouldRenameKeywords && - (sqlRenamedTables.size > 0 || fieldRenames.length > 0) - ) { - baseScript = appendRenameComments( - baseScript, - sqlRenamedTables, - fieldRenames, - finalDiagramForExport - ); - } - standard = fixArrayTypes( normalizeCharTypeFormat( fixMultilineTableNames( diff --git a/src/lib/dbml/dbml-import/__tests__/dbml-schema-handling.test.ts b/src/lib/dbml/dbml-import/__tests__/dbml-schema-handling.test.ts index 27c26f4c..e811ac60 100644 --- a/src/lib/dbml/dbml-import/__tests__/dbml-schema-handling.test.ts +++ b/src/lib/dbml/dbml-import/__tests__/dbml-schema-handling.test.ts @@ -180,7 +180,7 @@ describe('DBML Schema Handling - Fantasy Realm Database', () => { expect(artifactsTable?.schema).toBe(''); // No schema = empty string }); - it('should rename reserved keywords for PostgreSQL', async () => { + it('should handle reserved keywords for PostgreSQL', async () => { const dbmlContent = ` Table "magic_items" { "id" bigint [pk] @@ -197,10 +197,9 @@ describe('DBML Schema Handling - Fantasy Realm Database', () => { const exported = generateDBMLFromDiagram(diagram); - // For PostgreSQL, keywords should be renamed in export - expect(exported.standardDbml).toContain('Order_field'); - expect(exported.standardDbml).toContain('Yes_field'); - expect(exported.standardDbml).toContain('No_field'); + expect(exported.standardDbml).toContain('Order'); + expect(exported.standardDbml).toContain('Yes'); + expect(exported.standardDbml).toContain('No'); }); });