From 80f703e33e5c36a4a0dde60a26113cd2c6a72e60 Mon Sep 17 00:00:00 2001 From: Lopes Lenny Date: Wed, 17 Sep 2025 11:23:14 +0200 Subject: [PATCH] quote and escape identifiers with spaces or non-ascii chars, fixes #581 --- src/utils/exportAs/dbml.js | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/utils/exportAs/dbml.js b/src/utils/exportAs/dbml.js index 0ee093376..d9769d4cf 100644 --- a/src/utils/exportAs/dbml.js +++ b/src/utils/exportAs/dbml.js @@ -4,6 +4,18 @@ import i18n from "../../i18n/i18n"; import { escapeQuotes } from "../exportSQL/shared"; import { isFunction, isKeyword } from "../utils"; +const IDENT_SAFE_RE = /^[A-Za-z_][A-Za-z0-9_]*$/; + +function escapeIdentifier(s) { + return String(s).replace(/\\/g, "\\\\").replace(/"/g, '\\"'); +} + +function quoteIdentifier(name) { + if (name == null) return name; + const s = String(name); + return IDENT_SAFE_RE.test(s) ? s : `"${escapeIdentifier(s)}"`; +} + function parseDefaultDbml(field, database) { if (isFunction(field.default)) { return `\`${field.default}\``; @@ -96,7 +108,7 @@ export function toDBML(diagram) { (f) => f.id === rel.endFieldId, ); - return `Ref ${rel.name} {\n\t${startTableName}.${startFieldName} ${cardinality(rel)} ${endTableName}.${endFieldName} [ delete: ${rel.deleteConstraint.toLowerCase()}, update: ${rel.updateConstraint.toLowerCase()} ]\n}`; + return `Ref ${rel.name} {\n\t${quoteIdentifier(startTableName)}.${quoteIdentifier(startFieldName)} ${cardinality(rel)} ${quoteIdentifier(endTableName)}.${quoteIdentifier(endFieldName)} [ delete: ${rel.deleteConstraint.toLowerCase()}, update: ${rel.updateConstraint.toLowerCase()} ]\n}`; }; let enumDefinitions = ""; @@ -107,7 +119,7 @@ export function toDBML(diagram) { (field.type === "ENUM" || field.type === "SET") && Array.isArray(field.values) ) { - enumDefinitions += `enum ${field.name}_${field.values.join("_")}_t {\n\t${field.values.join("\n\t")}\n}\n\n`; + enumDefinitions += `enum ${quoteIdentifier(`${field.name}_${field.values.join("_")}_t`)} {\n\t${field.values.map((v) => quoteIdentifier(v)).join("\n\t")}\n}\n\n`; } } } @@ -115,17 +127,17 @@ export function toDBML(diagram) { return `${diagram.enums .map( (en) => - `enum ${en.name} {\n${en.values.map((v) => `\t${v}`).join("\n")}\n}\n\n`, + `enum ${quoteIdentifier(en.name)} {\n${en.values.map((v) => `\t${quoteIdentifier(v)}`).join("\n")}\n}\n\n`, ) .join("\n\n")}${enumDefinitions}${diagram.tables .map( (table) => - `Table ${table.name} [headercolor: ${table.color}] {\n${table.fields + `Table ${quoteIdentifier(table.name)} [headercolor: ${table.color}] {\n${table.fields .map( (field) => - `\t${field.name} ${ + `\t${quoteIdentifier(field.name)} ${ field.type === "ENUM" || field.type === "SET" - ? `${field.name}_${field.values.join("_")}_t` + ? quoteIdentifier(`${field.name}_${field.values.join("_")}_t`) : field.type.toLowerCase() }${fieldSize( field, @@ -138,7 +150,9 @@ export function toDBML(diagram) { table.indices .map( (index) => - `\t\t(${index.fields.join(", ")}) [ name: '${ + `\t\t(${index.fields + .map((f) => quoteIdentifier(f)) + .join(", ")}) [ name: '${ index.name }'${index.unique ? ", unique" : ""} ]`, )