mirror of
https://github.com/chartdb/chartdb.git
synced 2025-10-23 16:13:40 +00:00
fix: add support for parsing default values in DBML (#948)
This commit is contained in:
@@ -13,6 +13,55 @@ import { exportSQLite } from './export-per-type/sqlite';
|
||||
import { exportMySQL } from './export-per-type/mysql';
|
||||
import { escapeSQLComment } from './export-per-type/common';
|
||||
|
||||
// Function to format default values with proper quoting
|
||||
const formatDefaultValue = (value: string): string => {
|
||||
const trimmed = value.trim();
|
||||
|
||||
// SQL keywords and function-like keywords that don't need quotes
|
||||
const keywords = [
|
||||
'TRUE',
|
||||
'FALSE',
|
||||
'NULL',
|
||||
'CURRENT_TIMESTAMP',
|
||||
'CURRENT_DATE',
|
||||
'CURRENT_TIME',
|
||||
'NOW',
|
||||
'GETDATE',
|
||||
'NEWID',
|
||||
'UUID',
|
||||
];
|
||||
if (keywords.includes(trimmed.toUpperCase())) {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
// Function calls (contain parentheses) don't need quotes
|
||||
if (trimmed.includes('(') && trimmed.includes(')')) {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
// Numbers don't need quotes
|
||||
if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
// Already quoted strings - keep as is
|
||||
if (
|
||||
(trimmed.startsWith("'") && trimmed.endsWith("'")) ||
|
||||
(trimmed.startsWith('"') && trimmed.endsWith('"'))
|
||||
) {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
// Check if it's a simple identifier (alphanumeric, no spaces) that might be a currency or enum
|
||||
// These typically don't have spaces and are short (< 10 chars)
|
||||
if (/^[A-Z][A-Z0-9_]*$/i.test(trimmed) && trimmed.length <= 10) {
|
||||
return trimmed; // Treat as unquoted identifier (e.g., EUR, USD)
|
||||
}
|
||||
|
||||
// Everything else needs to be quoted and escaped
|
||||
return `'${trimmed.replace(/'/g, "''")}'`;
|
||||
};
|
||||
|
||||
// Function to simplify verbose data type names
|
||||
const simplifyDataType = (typeName: string): string => {
|
||||
const typeMap: Record<string, string> = {};
|
||||
@@ -391,7 +440,9 @@ export const exportBaseSQL = ({
|
||||
}
|
||||
}
|
||||
|
||||
sqlScript += ` DEFAULT ${fieldDefault}`;
|
||||
// Format default value with proper quoting
|
||||
const formattedDefault = formatDefaultValue(fieldDefault);
|
||||
sqlScript += ` DEFAULT ${formattedDefault}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -295,4 +295,51 @@ describe('DBML Import cases', () => {
|
||||
it('should handle case 2 - tables with relationships', async () => {
|
||||
await testDBMLImportCase('2');
|
||||
});
|
||||
|
||||
it('should handle table with default values', async () => {
|
||||
const dbmlContent = `Table "public"."products" {
|
||||
"id" bigint [pk, not null]
|
||||
"name" varchar(255) [not null]
|
||||
"price" decimal(10,2) [not null, default: 0]
|
||||
"is_active" boolean [not null, default: true]
|
||||
"status" varchar(50) [not null, default: "deprecated"]
|
||||
"description" varchar(100) [default: \`complex "value" with quotes\`]
|
||||
"created_at" timestamp [not null, default: "now()"]
|
||||
|
||||
Indexes {
|
||||
(name) [name: "idx_products_name"]
|
||||
}
|
||||
}`;
|
||||
|
||||
const result = await importDBMLToDiagram(dbmlContent, {
|
||||
databaseType: DatabaseType.POSTGRESQL,
|
||||
});
|
||||
|
||||
expect(result.tables).toHaveLength(1);
|
||||
const table = result.tables![0];
|
||||
expect(table.name).toBe('products');
|
||||
expect(table.fields).toHaveLength(7);
|
||||
|
||||
// Check numeric default (0)
|
||||
const priceField = table.fields.find((f) => f.name === 'price');
|
||||
expect(priceField?.default).toBe('0');
|
||||
|
||||
// Check boolean default (true)
|
||||
const isActiveField = table.fields.find((f) => f.name === 'is_active');
|
||||
expect(isActiveField?.default).toBe('true');
|
||||
|
||||
// Check string default with all quotes removed
|
||||
const statusField = table.fields.find((f) => f.name === 'status');
|
||||
expect(statusField?.default).toBe('deprecated');
|
||||
|
||||
// Check backtick string - all quotes removed
|
||||
const descField = table.fields.find((f) => f.name === 'description');
|
||||
expect(descField?.default).toBe('complex value with quotes');
|
||||
|
||||
// Check function default with all quotes removed
|
||||
const createdAtField = table.fields.find(
|
||||
(f) => f.name === 'created_at'
|
||||
);
|
||||
expect(createdAtField?.default).toBe('now()');
|
||||
});
|
||||
});
|
||||
|
@@ -89,6 +89,7 @@ interface DBMLField {
|
||||
precision?: number | null;
|
||||
scale?: number | null;
|
||||
note?: string | { value: string } | null;
|
||||
default?: string | null;
|
||||
}
|
||||
|
||||
interface DBMLIndexColumn {
|
||||
@@ -334,6 +335,20 @@ export const importDBMLToDiagram = async (
|
||||
schema: schemaName,
|
||||
note: table.note,
|
||||
fields: table.fields.map((field): DBMLField => {
|
||||
// Extract default value and remove all quotes
|
||||
let defaultValue: string | undefined;
|
||||
if (
|
||||
field.dbdefault !== undefined &&
|
||||
field.dbdefault !== null
|
||||
) {
|
||||
const rawDefault = String(
|
||||
field.dbdefault.value
|
||||
);
|
||||
// Remove ALL quotes (single, double, backticks) to clean the value
|
||||
// The SQL export layer will handle adding proper quotes when needed
|
||||
defaultValue = rawDefault.replace(/['"`]/g, '');
|
||||
}
|
||||
|
||||
return {
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
@@ -342,6 +357,7 @@ export const importDBMLToDiagram = async (
|
||||
not_null: field.not_null,
|
||||
increment: field.increment,
|
||||
note: field.note,
|
||||
default: defaultValue,
|
||||
...getFieldExtraAttributes(field, allEnums),
|
||||
} satisfies DBMLField;
|
||||
}),
|
||||
@@ -488,6 +504,7 @@ export const importDBMLToDiagram = async (
|
||||
precision: field.precision,
|
||||
scale: field.scale,
|
||||
...(fieldComment ? { comments: fieldComment } : {}),
|
||||
...(field.default ? { default: field.default } : {}),
|
||||
};
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user