fix(dbml): fix dbml output format (#815)

This commit is contained in:
Guy Ben-Aharon
2025-07-30 14:31:56 +03:00
committed by GitHub
parent 00bd535b3c
commit eed104be5b
4 changed files with 62 additions and 45 deletions

View File

@@ -44,6 +44,7 @@ export interface CodeSnippetProps {
editorProps?: React.ComponentProps<EditorType>; editorProps?: React.ComponentProps<EditorType>;
actions?: CodeSnippetAction[]; actions?: CodeSnippetAction[];
actionsTooltipSide?: 'top' | 'right' | 'bottom' | 'left'; actionsTooltipSide?: 'top' | 'right' | 'bottom' | 'left';
allowCopy?: boolean;
} }
export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo( export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
@@ -58,6 +59,7 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
editorProps, editorProps,
actions, actions,
actionsTooltipSide, actionsTooltipSide,
allowCopy = true,
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const monaco = useMonaco(); const monaco = useMonaco();
@@ -131,33 +133,37 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
<Suspense fallback={<Spinner />}> <Suspense fallback={<Spinner />}>
{isComplete ? ( {isComplete ? (
<div className="absolute right-1 top-1 z-10 flex flex-col gap-1"> <div className="absolute right-1 top-1 z-10 flex flex-col gap-1">
<Tooltip {allowCopy ? (
onOpenChange={setTooltipOpen} <Tooltip
open={isCopied || tooltipOpen} onOpenChange={setTooltipOpen}
> open={isCopied || tooltipOpen}
<TooltipTrigger asChild> >
<span> <TooltipTrigger asChild>
<Button <span>
className="h-fit p-1.5" <Button
variant="outline" className="h-fit p-1.5"
onClick={copyToClipboard} variant="outline"
> onClick={copyToClipboard}
{isCopied ? ( >
<CopyCheck size={16} /> {isCopied ? (
) : ( <CopyCheck size={16} />
<Copy size={16} /> ) : (
)} <Copy size={16} />
</Button> )}
</span> </Button>
</TooltipTrigger> </span>
<TooltipContent side={actionsTooltipSide}> </TooltipTrigger>
{t( <TooltipContent
isCopied side={actionsTooltipSide}
? 'copied' >
: 'copy_to_clipboard' {t(
)} isCopied
</TooltipContent> ? 'copied'
</Tooltip> : 'copy_to_clipboard'
)}
</TooltipContent>
</Tooltip>
) : null}
{actions && {actions &&
actions.length > 0 && actions.length > 0 &&

View File

@@ -362,9 +362,10 @@ export const exportBaseSQL = ({
.join(', '); .join(', ');
if (fieldNames) { if (fieldNames) {
const indexName = table.schema const indexName =
? `${table.schema}_${index.name}` table.schema && !isDBMLFlow
: index.name; ? `${table.schema}_${index.name}`
: index.name;
sqlScript += `CREATE ${index.unique ? 'UNIQUE ' : ''}INDEX ${indexName} ON ${tableName} (${fieldNames});\n`; sqlScript += `CREATE ${index.unique ? 'UNIQUE ' : ''}INDEX ${indexName} ON ${tableName} (${fieldNames});\n`;
} }
}); });

View File

@@ -937,26 +937,24 @@ describe('DBML Export - Issue Fixes', () => {
// Check that indexes are properly formatted with names // Check that indexes are properly formatted with names
// Note: When a table has a schema, index names are prefixed with the schema // Note: When a table has a schema, index names are prefixed with the schema
expect(result.standardDbml).toContain( expect(result.standardDbml).toContain(
'email [unique, name: "public_idx_email"]' 'email [unique, name: "idx_email"]'
); );
expect(result.standardDbml).toContain( expect(result.standardDbml).toContain(
'created_at [name: "public_idx_created_at"]' 'created_at [name: "idx_created_at"]'
); );
expect(result.standardDbml).toContain( expect(result.standardDbml).toContain(
'(email, created_at) [name: "public_idx_email_created"]' '(email, created_at) [name: "idx_email_created"]'
); );
// Verify proper index syntax in the table // Verify proper index syntax in the table
const indexSection = result.standardDbml.match(/Indexes \{[\s\S]*?\}/); const indexSection = result.standardDbml.match(/Indexes \{[\s\S]*?\}/);
expect(indexSection).toBeTruthy(); expect(indexSection).toBeTruthy();
expect(indexSection![0]).toContain('email [unique, name: "idx_email"]');
expect(indexSection![0]).toContain( expect(indexSection![0]).toContain(
'email [unique, name: "public_idx_email"]' 'created_at [name: "idx_created_at"]'
); );
expect(indexSection![0]).toContain( expect(indexSection![0]).toContain(
'created_at [name: "public_idx_created_at"]' '(email, created_at) [name: "idx_email_created"]'
);
expect(indexSection![0]).toContain(
'(email, created_at) [name: "public_idx_email_created"]'
); );
}); });
}); });

View File

@@ -350,7 +350,10 @@ const convertToInlineRefs = (dbml: string): string => {
// Combine all attributes into a single bracket // Combine all attributes into a single bracket
const combinedAttributes = allAttributes.join(', '); const combinedAttributes = allAttributes.join(', ');
return `${fieldPart.trim()} [${combinedAttributes}]${commentPart || ''}`; // Preserve original spacing from fieldPart
const leadingSpaces = fieldPart.match(/^(\s*)/)?.[1] || '';
const fieldDefWithoutSpaces = fieldPart.trim();
return `${leadingSpaces}${fieldDefWithoutSpaces} [${combinedAttributes}]${commentPart || ''}`;
} }
); );
@@ -388,7 +391,10 @@ const convertToInlineRefs = (dbml: string): string => {
.filter((line) => !line.trim().startsWith('Ref ')); .filter((line) => !line.trim().startsWith('Ref '));
const finalDbml = finalLines.join('\n').trim(); const finalDbml = finalLines.join('\n').trim();
return finalDbml; // Clean up excessive empty lines - replace multiple consecutive empty lines with just one
const cleanedDbml = finalDbml.replace(/\n\s*\n\s*\n/g, '\n\n');
return cleanedDbml;
}; };
// Function to check for SQL keywords (add more if needed) // Function to check for SQL keywords (add more if needed)
@@ -804,9 +810,15 @@ export function generateDBMLFromDiagram(diagram: Diagram): DBMLExportResult {
standard = restoreTableSchemas(standard, diagram); standard = restoreTableSchemas(standard, diagram);
// Prepend Enum DBML to the standard output // Prepend Enum DBML to the standard output
standard = enumsDBML + '\n' + standard; if (enumsDBML) {
standard = enumsDBML + '\n\n' + standard;
}
inline = normalizeCharTypeFormat(convertToInlineRefs(standard)); inline = normalizeCharTypeFormat(convertToInlineRefs(standard));
// Clean up excessive empty lines in both outputs
standard = standard.replace(/\n\s*\n\s*\n/g, '\n\n');
inline = inline.replace(/\n\s*\n\s*\n/g, '\n\n');
} catch (error: unknown) { } catch (error: unknown) {
console.error( console.error(
'Error during DBML generation process:', 'Error during DBML generation process:',
@@ -822,11 +834,11 @@ export function generateDBMLFromDiagram(diagram: Diagram): DBMLExportResult {
// If an error occurred, still prepend enums if they exist, or they'll be lost. // If an error occurred, still prepend enums if they exist, or they'll be lost.
// The error message will then follow. // The error message will then follow.
if (standard.startsWith('// Error generating DBML:')) { if (standard.startsWith('// Error generating DBML:') && enumsDBML) {
standard = enumsDBML + standard; standard = enumsDBML + '\n\n' + standard;
} }
if (inline.startsWith('// Error generating DBML:')) { if (inline.startsWith('// Error generating DBML:') && enumsDBML) {
inline = enumsDBML + inline; inline = enumsDBML + '\n\n' + inline;
} }
} }