mirror of
https://github.com/chartdb/chartdb.git
synced 2026-02-11 22:20:11 -06:00
fix: dbml editor spaces + refs on schemas (#1023)
This commit is contained in:
44
package-lock.json
generated
44
package-lock.json
generated
@@ -12,7 +12,7 @@
|
||||
"@dbml/core": "^3.14.1",
|
||||
"@dbml/parse": "^5.3.0",
|
||||
"@dnd-kit/sortable": "^8.0.0",
|
||||
"@monaco-editor/react": "^4.6.0",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@radix-ui/react-accordion": "^1.2.0",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.1",
|
||||
"@radix-ui/react-avatar": "^1.1.0",
|
||||
@@ -48,7 +48,7 @@
|
||||
"i18next": "^23.14.0",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"lucide-react": "^0.525.0",
|
||||
"monaco-editor": "^0.52.0",
|
||||
"monaco-editor": "^0.55.1",
|
||||
"motion": "^12.23.6",
|
||||
"nanoid": "^5.0.7",
|
||||
"node-sql-parser": "^5.3.2",
|
||||
@@ -3626,6 +3626,13 @@
|
||||
"@types/react": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@types/unist": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
||||
@@ -5411,6 +5418,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz",
|
||||
"integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"optionalDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
@@ -7795,6 +7811,18 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "14.0.0",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz",
|
||||
"integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/matchmediaquery": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/matchmediaquery/-/matchmediaquery-0.4.2.tgz",
|
||||
@@ -8744,11 +8772,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/monaco-editor": {
|
||||
"version": "0.52.2",
|
||||
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz",
|
||||
"integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==",
|
||||
"version": "0.55.1",
|
||||
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
|
||||
"integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"dompurify": "3.2.7",
|
||||
"marked": "14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/motion": {
|
||||
"version": "12.23.26",
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"@dbml/core": "^3.14.1",
|
||||
"@dbml/parse": "^5.3.0",
|
||||
"@dnd-kit/sortable": "^8.0.0",
|
||||
"@monaco-editor/react": "^4.6.0",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@radix-ui/react-accordion": "^1.2.0",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.1",
|
||||
"@radix-ui/react-avatar": "^1.1.0",
|
||||
@@ -56,7 +56,7 @@
|
||||
"i18next": "^23.14.0",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"lucide-react": "^0.525.0",
|
||||
"monaco-editor": "^0.52.0",
|
||||
"monaco-editor": "^0.55.1",
|
||||
"motion": "^12.23.6",
|
||||
"nanoid": "^5.0.7",
|
||||
"node-sql-parser": "^5.3.2",
|
||||
|
||||
@@ -203,6 +203,7 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
||||
theme={effectiveTheme}
|
||||
{...editorProps}
|
||||
options={{
|
||||
editContext: false,
|
||||
readOnly: true,
|
||||
automaticLayout: true,
|
||||
scrollBeyondLastLine: false,
|
||||
|
||||
@@ -515,6 +515,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
||||
: 'dbml-light'
|
||||
}
|
||||
options={{
|
||||
editContext: false,
|
||||
formatOnPaste: false, // Never format on paste - we handle it manually
|
||||
minimap: { enabled: false },
|
||||
scrollBeyondLastLine: false,
|
||||
|
||||
30
src/lib/dbml/dbml-export/__tests__/cases/8.dbml
Normal file
30
src/lib/dbml/dbml-export/__tests__/cases/8.dbml
Normal file
@@ -0,0 +1,30 @@
|
||||
Table "pokemon"."abilities" {
|
||||
"abil_id" int [pk, not null]
|
||||
"abil_name" varchar(500) [not null]
|
||||
}
|
||||
|
||||
Table "pokemon"."pokemon_abilities" {
|
||||
"pok_id" int [not null]
|
||||
"abil_id" int [not null]
|
||||
"is_hidden" int [not null]
|
||||
"slot" int [not null]
|
||||
"music" bigint
|
||||
|
||||
Indexes {
|
||||
(pok_id, slot) [pk]
|
||||
is_hidden [name: "pokemon_abilities_pokemon_ix_pokemon_abilities_is_hidden"]
|
||||
abil_id [name: "pokemon_abilities_pokemon_abil_id"]
|
||||
}
|
||||
}
|
||||
|
||||
Table "pokemon"."pokemon" {
|
||||
"pok_id" int [pk, not null]
|
||||
"pok_name" varchar(500) [not null]
|
||||
"pok_height" int
|
||||
"pok_weight" int
|
||||
"pok_base_experience" int
|
||||
}
|
||||
|
||||
Ref "fk_0_fk_pokemon_abilities_pok_id_pokemon_pok_id":"pokemon"."pokemon"."pok_id" < "pokemon"."pokemon_abilities"."pok_id"
|
||||
|
||||
Ref "fk_1_fk_pokemon_abilities_abil_id_abilities_abil_id":"pokemon"."abilities"."abil_id" < "pokemon"."pokemon_abilities"."abil_id"
|
||||
26
src/lib/dbml/dbml-export/__tests__/cases/8.inline.dbml
Normal file
26
src/lib/dbml/dbml-export/__tests__/cases/8.inline.dbml
Normal file
@@ -0,0 +1,26 @@
|
||||
Table "pokemon"."abilities" {
|
||||
"abil_id" int [pk, not null]
|
||||
"abil_name" varchar(500) [not null]
|
||||
}
|
||||
|
||||
Table "pokemon"."pokemon_abilities" {
|
||||
"pok_id" int [not null, ref: < "pokemon"."pokemon"."pok_id"]
|
||||
"abil_id" int [not null, ref: < "pokemon"."abilities"."abil_id"]
|
||||
"is_hidden" int [not null]
|
||||
"slot" int [not null]
|
||||
"music" bigint
|
||||
|
||||
Indexes {
|
||||
(pok_id, slot) [pk]
|
||||
is_hidden [name: "pokemon_abilities_pokemon_ix_pokemon_abilities_is_hidden"]
|
||||
abil_id [name: "pokemon_abilities_pokemon_abil_id"]
|
||||
}
|
||||
}
|
||||
|
||||
Table "pokemon"."pokemon" {
|
||||
"pok_id" int [pk, not null]
|
||||
"pok_name" varchar(500) [not null]
|
||||
"pok_height" int
|
||||
"pok_weight" int
|
||||
"pok_base_experience" int
|
||||
}
|
||||
1
src/lib/dbml/dbml-export/__tests__/cases/8.json
Normal file
1
src/lib/dbml/dbml-export/__tests__/cases/8.json
Normal file
@@ -0,0 +1 @@
|
||||
{"id":"0kfb46kt4fqz","name":"SQL Import (postgresql)","createdAt":"2025-12-18T12:58:52.104Z","updatedAt":"2025-12-18T13:08:00.838Z","databaseType":"postgresql","tables":[{"id":"meui8qxt4ui15zn0wbpn8kf4h","name":"abilities","schema":"pokemon","order":13,"fields":[{"id":"40alqf0d9uw4pkhc43yg0xbsy","name":"abil_id","type":{"id":"int","name":"int"},"nullable":false,"primaryKey":true,"unique":true,"default":"","createdAt":1766062732104,"increment":false},{"id":"3iipmd7o8icmen0jn8124gl9n","name":"abil_name","type":{"name":"varchar","id":"varchar","fieldAttributes":{"hasCharMaxLength":true},"usageLevel":1},"nullable":false,"primaryKey":false,"unique":false,"default":"","createdAt":1766062732104,"increment":false,"characterMaximumLength":"500"}],"indexes":[],"x":-1330.1839360655863,"y":-318.9120909292717,"color":"#8eb7ff","isView":false,"createdAt":1766062732104,"diagramId":"0kfb46kt4fqz"},{"id":"ndqyi89iebp8w1my9t4su9nlo","name":"pokemon_abilities","schema":"pokemon","order":5,"fields":[{"id":"v99oydscnxxyqg6knbycm39aj","name":"pok_id","type":{"id":"int","name":"int"},"nullable":false,"primaryKey":true,"unique":false,"default":"","createdAt":1766062732104,"increment":false},{"id":"vrwyacj8mb0oj6fspen6vqpc1","name":"abil_id","type":{"id":"int","name":"int"},"nullable":false,"primaryKey":false,"unique":false,"default":"","createdAt":1766062732104,"increment":false},{"id":"1eg794qd6zw2i281os7ycw27f","name":"is_hidden","type":{"id":"int","name":"int"},"nullable":false,"primaryKey":false,"unique":false,"default":"","createdAt":1766062732104,"increment":false},{"id":"k6dj4hreohg8ufybojn6re6wz","name":"slot","type":{"id":"int","name":"int"},"nullable":false,"primaryKey":true,"unique":false,"default":"","createdAt":1766062732104,"increment":false},{"id":"a35s1d192eompkn7yvuxng62d","name":"music","type":{"name":"bigint","id":"bigint"},"nullable":true,"primaryKey":false,"unique":false,"default":"","createdAt":1766062732104,"increment":false}],"indexes":[{"id":"hluplg0pndapd1fk086cawlsl","name":"pokemon_abilities_pokemon_ix_pokemon_abilities_is_hidden","fieldIds":["1eg794qd6zw2i281os7ycw27f"],"unique":false,"createdAt":1766062732104},{"id":"7t8x8wl5hjrsi914w95hpn2r9","name":"pokemon_abilities_pokemon_abil_id","fieldIds":["vrwyacj8mb0oj6fspen6vqpc1"],"unique":false,"createdAt":1766062732104}],"x":-958.2017535132559,"y":-415.8808703335114,"color":"#8eb7ff","isView":false,"createdAt":1766062732104,"diagramId":"0kfb46kt4fqz"},{"id":"uf6nokjc3llz4dmfze0bjhzyo","name":"pokemon","schema":"pokemon","order":10,"fields":[{"id":"zqyqypyht1uaum3sce3bvwlhi","name":"pok_id","type":{"id":"int","name":"int"},"nullable":false,"primaryKey":true,"unique":true,"default":"","createdAt":1766062732104,"increment":false},{"id":"d7o7wjf1gowlsq5p3etnper8o","name":"pok_name","type":{"name":"varchar","id":"varchar","fieldAttributes":{"hasCharMaxLength":true},"usageLevel":1},"nullable":false,"primaryKey":false,"unique":false,"default":"","createdAt":1766062732104,"increment":false,"characterMaximumLength":"500"},{"id":"yvw4bu3bgozb8hz7z3t8nkmz8","name":"pok_height","type":{"id":"int","name":"int"},"nullable":true,"primaryKey":false,"unique":false,"default":"","createdAt":1766062732104,"increment":false},{"id":"snci66nxy39m97a7eouwybn3x","name":"pok_weight","type":{"id":"int","name":"int"},"nullable":true,"primaryKey":false,"unique":false,"default":"","createdAt":1766062732104,"increment":false},{"id":"ekqck9gw9z1ktciuh95ryhpy9","name":"pok_base_experience","type":{"id":"int","name":"int"},"nullable":true,"primaryKey":false,"unique":false,"default":"","createdAt":1766062732104,"increment":false}],"indexes":[],"x":-627.5363678111918,"y":-454.0032933604259,"color":"#8eb7ff","isView":false,"createdAt":1766062732104,"diagramId":"0kfb46kt4fqz"}],"relationships":[{"id":"ylhfaz43z8l46l6zlwpsmjdql","name":"fk_pokemon_abilities_pok_id_pokemon_pok_id","sourceSchema":"pokemon","targetSchema":"pokemon","sourceTableId":"uf6nokjc3llz4dmfze0bjhzyo","targetTableId":"ndqyi89iebp8w1my9t4su9nlo","sourceFieldId":"zqyqypyht1uaum3sce3bvwlhi","targetFieldId":"v99oydscnxxyqg6knbycm39aj","sourceCardinality":"one","targetCardinality":"many","createdAt":1766062732104,"diagramId":"0kfb46kt4fqz"},{"id":"pcw4h4jau7sbprolm8b1m4f6k","name":"fk_pokemon_abilities_abil_id_abilities_abil_id","sourceSchema":"pokemon","targetSchema":"pokemon","sourceTableId":"meui8qxt4ui15zn0wbpn8kf4h","targetTableId":"ndqyi89iebp8w1my9t4su9nlo","sourceFieldId":"40alqf0d9uw4pkhc43yg0xbsy","targetFieldId":"vrwyacj8mb0oj6fspen6vqpc1","sourceCardinality":"one","targetCardinality":"many","createdAt":1766062732104,"diagramId":"0kfb46kt4fqz"}],"dependencies":[],"areas":[],"customTypes":[],"notes":[]}
|
||||
@@ -78,4 +78,8 @@ describe('DBML Export cases', () => {
|
||||
it('should handle case 7 diagram', { timeout: 30000 }, async () => {
|
||||
testCase('7');
|
||||
});
|
||||
|
||||
it('should handle case 8 diagram', { timeout: 30000 }, async () => {
|
||||
testCase('8');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -819,39 +819,52 @@ const restoreTableSchemas = (dbml: string, tables: DBTable[]): string => {
|
||||
// Single table with this name - simple case
|
||||
const table = tablesGroup[0].table;
|
||||
if (table.schema) {
|
||||
// Match table definition without schema (e.g., Table "users" {)
|
||||
const tablePattern = new RegExp(
|
||||
`Table\\s+"${table.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"\\s*{`,
|
||||
'g'
|
||||
);
|
||||
const schemaTableName = `Table "${table.schema}"."${table.name}" {`;
|
||||
result = result.replace(tablePattern, schemaTableName);
|
||||
|
||||
// Update references in Ref statements
|
||||
const escapedTableName = table.name.replace(
|
||||
/[.*+?^${}()|[\]\\]/g,
|
||||
'\\$&'
|
||||
);
|
||||
|
||||
// Pattern 1: In Ref definitions - :"tablename"."field"
|
||||
const refDefPattern = new RegExp(
|
||||
`(Ref\\s+"[^"]+")\\s*:\\s*"${escapedTableName}"\\."([^"]+)"`,
|
||||
'g'
|
||||
);
|
||||
result = result.replace(
|
||||
refDefPattern,
|
||||
`$1:"${table.schema}"."${table.name}"."$2"`
|
||||
const escapedSchema = table.schema.replace(
|
||||
/[.*+?^${}()|[\]\\]/g,
|
||||
'\\$&'
|
||||
);
|
||||
|
||||
// Pattern 2: In Ref targets - [<>] "tablename"."field"
|
||||
const refTargetPattern = new RegExp(
|
||||
`([<>])\\s*"${escapedTableName}"\\."([^"]+)"`,
|
||||
// Check if the schema is already present in the table definition
|
||||
const schemaAlreadyPresent = new RegExp(
|
||||
`Table\\s+"${escapedSchema}"\\."${escapedTableName}"\\s*{`,
|
||||
'g'
|
||||
);
|
||||
result = result.replace(
|
||||
refTargetPattern,
|
||||
`$1 "${table.schema}"."${table.name}"."$2"`
|
||||
);
|
||||
).test(result);
|
||||
|
||||
// Only add schema if it's not already present
|
||||
if (!schemaAlreadyPresent) {
|
||||
// Match table definition without schema (e.g., Table "users" {)
|
||||
const tablePattern = new RegExp(
|
||||
`Table\\s+"${escapedTableName}"\\s*{`,
|
||||
'g'
|
||||
);
|
||||
const schemaTableName = `Table "${table.schema}"."${table.name}" {`;
|
||||
result = result.replace(tablePattern, schemaTableName);
|
||||
|
||||
// Update references in Ref statements
|
||||
// Pattern 1: In Ref definitions - :"tablename"."field"
|
||||
const refDefPattern = new RegExp(
|
||||
`(Ref\\s+"[^"]+")\\s*:\\s*"${escapedTableName}"\\."([^"]+)"`,
|
||||
'g'
|
||||
);
|
||||
result = result.replace(
|
||||
refDefPattern,
|
||||
`$1:"${table.schema}"."${table.name}"."$2"`
|
||||
);
|
||||
|
||||
// Pattern 2: In Ref targets - [<>] "tablename"."field"
|
||||
const refTargetPattern = new RegExp(
|
||||
`([<>])\\s*"${escapedTableName}"\\."([^"]+)"`,
|
||||
'g'
|
||||
);
|
||||
result = result.replace(
|
||||
refTargetPattern,
|
||||
`$1 "${table.schema}"."${table.name}"."$2"`
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Multiple tables with the same name - need to be more careful
|
||||
|
||||
Reference in New Issue
Block a user