fix(dbml): support multiple relationships on same field in inline DBML (#822)

This commit is contained in:
Guy Ben-Aharon
2025-08-03 12:04:05 +03:00
committed by GitHub
parent 8ffde62c1a
commit a5f8e56b3c
2 changed files with 286 additions and 13 deletions

View File

@@ -286,9 +286,14 @@ const convertToInlineRefs = (dbml: string): string => {
// Create a map for faster table lookup
const tableMap = new Map(Object.entries(tables));
// 1. Add inline refs to table contents
// 1. First, collect all refs per field
const fieldRefs = new Map<
string,
{ table: string; refs: string[]; relatedTables: string[] }
>();
refs.forEach((ref) => {
let targetTableName, fieldNameToModify, inlineRefSyntax;
let targetTableName, fieldNameToModify, inlineRefSyntax, relatedTable;
if (ref.direction === '<') {
targetTableName = ref.targetSchema
@@ -299,6 +304,7 @@ const convertToInlineRefs = (dbml: string): string => {
? `"${ref.sourceSchema}"."${ref.sourceTable}"."${ref.sourceField}"`
: `"${ref.sourceTable}"."${ref.sourceField}"`;
inlineRefSyntax = `ref: < ${sourceRef}`;
relatedTable = ref.sourceTable;
} else {
targetTableName = ref.sourceSchema
? `${ref.sourceSchema}.${ref.sourceTable}`
@@ -308,13 +314,32 @@ const convertToInlineRefs = (dbml: string): string => {
? `"${ref.targetSchema}"."${ref.targetTable}"."${ref.targetField}"`
: `"${ref.targetTable}"."${ref.targetField}"`;
inlineRefSyntax = `ref: > ${targetRef}`;
relatedTable = ref.targetTable;
}
const tableData = tableMap.get(targetTableName);
const fieldKey = `${targetTableName}.${fieldNameToModify}`;
const existing = fieldRefs.get(fieldKey) || {
table: targetTableName,
refs: [],
relatedTables: [],
};
existing.refs.push(inlineRefSyntax);
existing.relatedTables.push(relatedTable);
fieldRefs.set(fieldKey, existing);
});
// 2. Apply all refs to fields
fieldRefs.forEach((fieldData, fieldKey) => {
// fieldKey might be "schema.table.field" or just "table.field"
const lastDotIndex = fieldKey.lastIndexOf('.');
const tableName = fieldKey.substring(0, lastDotIndex);
const fieldName = fieldKey.substring(lastDotIndex + 1);
const tableData = tableMap.get(tableName);
if (tableData) {
// Updated pattern to capture field definition and all existing attributes in brackets
const fieldPattern = new RegExp(
`^([ \t]*"${fieldNameToModify}"[^\\n]*?)(?:\\s*(\\[[^\\]]*\\]))*\\s*(//.*)?$`,
`^([ \t]*"${fieldName}"[^\\n]*?)(?:\\s*(\\[[^\\]]*\\]))*\\s*(//.*)?$`,
'gm'
);
let newContent = tableData.content;
@@ -322,11 +347,6 @@ const convertToInlineRefs = (dbml: string): string => {
newContent = newContent.replace(
fieldPattern,
(lineMatch, fieldPart, existingBrackets, commentPart) => {
// Avoid adding duplicate refs
if (lineMatch.includes('ref:')) {
return lineMatch;
}
// Collect all attributes from existing brackets
const allAttributes: string[] = [];
if (existingBrackets) {
@@ -344,8 +364,8 @@ const convertToInlineRefs = (dbml: string): string => {
}
}
// Add the new ref
allAttributes.push(inlineRefSyntax);
// Add all refs for this field
allAttributes.push(...fieldData.refs);
// Combine all attributes into a single bracket
const combinedAttributes = allAttributes.join(', ');
@@ -353,6 +373,7 @@ const convertToInlineRefs = (dbml: string): string => {
// Preserve original spacing from fieldPart
const leadingSpaces = fieldPart.match(/^(\s*)/)?.[1] || '';
const fieldDefWithoutSpaces = fieldPart.trim();
return `${leadingSpaces}${fieldDefWithoutSpaces} [${combinedAttributes}]${commentPart || ''}`;
}
);
@@ -360,7 +381,7 @@ const convertToInlineRefs = (dbml: string): string => {
// Update the table content if modified
if (newContent !== tableData.content) {
tableData.content = newContent;
tableMap.set(targetTableName, tableData);
tableMap.set(tableName, tableData);
}
}
});
@@ -376,9 +397,18 @@ const convertToInlineRefs = (dbml: string): string => {
reconstructedDbml += dbml.substring(lastIndex, tableData.start);
// Preserve the original table definition format but with updated content
const originalTableDef = tableData.fullMatch;
// Ensure the content ends with proper whitespace before the closing brace
let content = tableData.content;
// Check if content ends with a field that has inline refs
if (content.match(/\[.*ref:.*\]\s*$/)) {
// Ensure there's a newline before the closing brace
content = content.trimEnd() + '\n';
}
const updatedTableDef = originalTableDef.replace(
/{[^}]*}/,
`{${tableData.content}}`
`{${content}}`
);
reconstructedDbml += updatedTableDef;
lastIndex = tableData.end;